Compare commits
387 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ae91b8c31 | |||
| 894a23d701 | |||
| 3598906cbd | |||
| 75c12e2d4b | |||
| d6e0519a3d | |||
| 41efc71626 | |||
| 6e3d97f472 | |||
| 9d58d02522 | |||
| fe86396b21 | |||
| 97994f7632 | |||
| 33d63457b3 | |||
| ed36d9e953 | |||
| 9a478d74d2 | |||
| 72d72544a4 | |||
| 4bbbe81182 | |||
| a0af0c2492 | |||
| ce7d3e4702 | |||
| 1bb4ca8541 | |||
| ea65445772 | |||
| 972db8fcea | |||
| a3c12631f0 | |||
| 3cadfd08d8 | |||
| 104f3de013 | |||
| 713b41bd52 | |||
| 253093fa2f | |||
| f36af5af64 | |||
| 97b6c0e44d | |||
| c4f6dabd4d | |||
| d1c8aeb25d | |||
| 6e1cb2e0fe | |||
| da9762f60e | |||
| 27affdec14 | |||
| 433a19e46a | |||
| da9db9d3d1 | |||
| ed4b0eba2f | |||
| 615aecf80f | |||
| 4622d1a610 | |||
| f1b80d8f57 | |||
| e76e303383 | |||
| 97133c3fcb | |||
| 450610b6e6 | |||
| 4dc3fd92cc | |||
| f4b5e7c044 | |||
| 6c0b2a468d | |||
| e33f724f1b | |||
| b0d5562917 | |||
| eecf7a2194 | |||
| 54fd8a0332 | |||
| b6ca91980b | |||
| 6af7e2d749 | |||
| 86d334c204 | |||
| 585a4fa449 | |||
| 7438073e7e | |||
| 6e808ae35a | |||
| 99ec64e852 | |||
| eeac63c0a5 | |||
| 5d5a3c3301 | |||
| 31e9730236 | |||
| 69b32a02ff | |||
| a222df8176 | |||
| 7f4c99be60 | |||
| ccff657a62 | |||
| fb258499e1 | |||
| 79c6d6c742 | |||
| 80d9d5480c | |||
| 958a553922 | |||
| a44bbc3513 | |||
| d7f2f4a3e7 | |||
| 073566a23e | |||
| 590aecfcf1 | |||
| 77ab52310e | |||
| 2c2ccddbe4 | |||
| 87062db9d5 | |||
| b74701dbc5 | |||
| a88db8830b | |||
| 36cd83c796 | |||
| a039c93c05 | |||
| 57b4ade3be | |||
| 87ce6cfa98 | |||
| 6a99c8c81d | |||
| 46e8188d5a | |||
| 3c990df1fe | |||
| 8969bc5aa6 | |||
| 45e8ca8d42 | |||
| 39930153c9 | |||
| 5890e46db3 | |||
| c5c06a08ba | |||
| 2a0e677a89 | |||
| 8f7a968dc9 | |||
| 20cfc50448 | |||
| 4bf019ec7e | |||
| 57fe45484c | |||
| 7744f4ed76 | |||
| a43e81e229 | |||
| f6ad7e250b | |||
| 0f2b0482ec | |||
| 21d850d39e | |||
| d3f2e42301 | |||
| df68154f10 | |||
| f8ebf03afd | |||
| 23f8b97319 | |||
| d712054353 | |||
| 58da896b14 | |||
| 8db57bda6e | |||
| 53f29ec710 | |||
| 43a8fc0e86 | |||
| 7f2adb068e | |||
| 218ae9f9bf | |||
| 350c03874d | |||
| 575c0e5bf9 | |||
| 3a890ba2c7 | |||
| 2a345f4869 | |||
| 658ebbd84d | |||
| 0c54ade367 | |||
| f16ba64026 | |||
| d72127aaeb | |||
| 40e0b1291c | |||
| 0fae7f0166 | |||
| 6f916a4c32 | |||
| 41979d5389 | |||
| 0d51205bd6 | |||
| 416dd52a30 | |||
| 850e45b9a5 | |||
| 040d2ca7f6 | |||
| e173072622 | |||
| 991dd80382 | |||
| 5fc5d02134 | |||
| 0f51256add | |||
| a78860dbc4 | |||
| f655a3c52d | |||
| b69aebd5be | |||
| 769a7c391f | |||
| 9a4d55aa36 | |||
| e776acfbab | |||
| b4f58286b4 | |||
| 59779cc931 | |||
| 567bcecc80 | |||
| 0ca87dc29b | |||
| 6e0824e357 | |||
| 9a8a620658 | |||
| eb1db3120d | |||
| 98a9225c32 | |||
| 1b8fb766a8 | |||
| c28ef3ec3b | |||
| fab3f0630c | |||
| d2499ad157 | |||
| 724a37bbf4 | |||
| c5f1c30b1c | |||
| e90363df71 | |||
| 0932008619 | |||
| a24e00ad5a | |||
| fab938055f | |||
| b2026b0dac | |||
| c1c742084e | |||
| 7140f590cf | |||
| dcc26ad666 | |||
| 70f1ecad49 | |||
| 03c7383b54 | |||
| d3734971cc | |||
| cff8358b3e | |||
| 42d691c4ce | |||
| 33dcbe8b5a | |||
| 980c7a4390 | |||
| 1b7f881d5a | |||
| 8635e2cf67 | |||
| e36bc8bab2 | |||
| 693da7733f | |||
| 6b6128d92d | |||
| 64586a44b4 | |||
| 2d28ecca32 | |||
| 92f3edb337 | |||
| 32f8edecdd | |||
| 14b061cd28 | |||
| aeb90cbdd2 | |||
| e2a0b627b2 | |||
| de5eb0d914 | |||
| fc97499504 | |||
| 681f5622c6 | |||
| 323812710e | |||
| d421c0c62c | |||
| 8cbcd74f1a | |||
| 328cdad26e | |||
| 25b83cb86e | |||
| d3c5e4c376 | |||
| bf518f548c | |||
| 3999416366 | |||
| 707811cc4a | |||
| 05a7f4ba83 | |||
| be339435b8 | |||
| ea7adb9b5b | |||
| 3583cb007a | |||
| cc06a4e1b1 | |||
| 6e0c09816b | |||
| 50a4514954 | |||
| 91bca917c2 | |||
| 0550aa9bd2 | |||
| c9d172d301 | |||
| 77c950bc1e | |||
| d0bbfa82d1 | |||
| 53d657ea0e | |||
| bf8e677b55 | |||
| 8deebf83ee | |||
| 31ec2a0b3a | |||
| e6b568d7d6 | |||
| dfbfea5169 | |||
| 66862c9bd3 | |||
| 84c217717f | |||
| 67f309f32e | |||
| 6e7e7cc7fa | |||
| 5f39a1e08c | |||
| b175ddb0ca | |||
| 4f36b2eab9 | |||
| 853c86c40b | |||
| 242dfa6c9e | |||
| d98b68ca65 | |||
| a41df16f14 | |||
| 48b545d731 | |||
| 707b6a6f1b | |||
| c21b374b49 | |||
| e09749c6f2 | |||
| b7876d1774 | |||
| d0590600f3 | |||
| fe5b30b4c6 | |||
| b7ce73d338 | |||
| 8139eb607c | |||
| 96c08e6563 | |||
| 38977af9d3 | |||
| 0584081c33 | |||
| ea5e471bf8 | |||
| 230847dace | |||
| 5a91810e9a | |||
| 49ff63fad4 | |||
| 96cc87cde8 | |||
| baddd82ecc | |||
| a4b127a8f3 | |||
| 2851fd20b8 | |||
| dd278fe3d3 | |||
| 196c29da17 | |||
| a1f7fb57d9 | |||
| 27acd37a2c | |||
| 6fb7202e89 | |||
| dddd12a036 | |||
| f4c0211cba | |||
| 57d291a13c | |||
| 5b2011cf82 | |||
| d14f0e9295 | |||
| f2a15f84a8 | |||
| d86831467b | |||
| d12365667f | |||
| dcc07d4590 | |||
| c996149476 | |||
| 39ff81dee4 | |||
| 22aa3aef96 | |||
| 3faff72519 | |||
| bac3245eb7 | |||
| ec9e8e348f | |||
| 131da2ec60 | |||
| 38d44deef6 | |||
| 71c31b182a | |||
| 02baeba8b9 | |||
| 1d6ce959c8 | |||
| f599015473 | |||
| e98d804902 | |||
| 18f5ac1ead | |||
| 5f2e346bd8 | |||
| 96c23bf138 | |||
| 446a724077 | |||
| 6e9afa7f9f | |||
| 5c13d124de | |||
| 4f94e21ea8 | |||
| 7498aab76a | |||
| e65ac4ac8b | |||
| df2ec69448 | |||
| 6fe474e494 | |||
| 6408477939 | |||
| 5a20a4260b | |||
| b604b81f37 | |||
| 40e36b3203 | |||
| e098b63beb | |||
| 56e99bc6ba | |||
| 0abc42bf2c | |||
| f648eba8dd | |||
| 20648b479f | |||
| ef82bac8fc | |||
| 28e330520b | |||
| b481bb08cc | |||
| 506d7fff22 | |||
| da3b42b6ac | |||
| eea50c23b5 | |||
| b1f5860335 | |||
| 958c567b6b | |||
| b443f278da | |||
| f5ae187012 | |||
| 6e48279c8a | |||
| 61976e8c13 | |||
| 2009b0ff7e | |||
| 60dd0d45b9 | |||
| a7422e4c1e | |||
| ffd79d2404 | |||
| 7363a461de | |||
| f7325f030c | |||
| 2668e5d8b2 | |||
| f09e3ffcdb | |||
| de4bba2e85 | |||
| bb50ecc86c | |||
| 2191fe4cdd | |||
| 321e0b2331 | |||
| f4611280a7 | |||
| ed3f2415bb | |||
| 495bc24b2f | |||
| 397c84cacb | |||
| f04fb7e756 | |||
| dcbed8b173 | |||
| 3e5e79ba18 | |||
| ddaee77b59 | |||
| 2d5a08a921 | |||
| 240a325ef1 | |||
| 663a0f15df | |||
| cb7afac17b | |||
| b04710cf50 | |||
| ce3fd894ae | |||
| fd11f4e866 | |||
| 5422af1e82 | |||
| 444002b006 | |||
| f01c474536 | |||
| 696b42666f | |||
| 84190e0806 | |||
| 80253426b7 | |||
| 26ccc63c96 | |||
| 1124ac41f9 | |||
| d534d8b25c | |||
| 618afaacd4 | |||
| 53b6ce56bf | |||
| 8257c7d7e4 | |||
| 769416f474 | |||
| f978e5d261 | |||
| 9d3660a1e2 | |||
| f2637aad46 | |||
| 371e8a9570 | |||
| 56987fe7a0 | |||
| dfe5138cad | |||
| 90a2d83670 | |||
| 8c8981ea9f | |||
| de49d67361 | |||
| 5826c383b7 | |||
| 24962f44e1 | |||
| 9f2fc40c76 | |||
| b8bdcf4c71 | |||
| c9c6dc7666 | |||
| 9f686b91a2 | |||
| 9e879d6582 | |||
| 2fc1df729b | |||
| 6586b9746a | |||
| 37d3ba3bc1 | |||
| 1126860834 | |||
| c26141bc5d | |||
| 3d6cfb44bb | |||
| 4b1bdc55f1 | |||
| c2bdd5f0bb | |||
| c22544672c | |||
| 78064cc07c | |||
| 84f5897e38 | |||
| 9044f13d2b | |||
| cf9ee44970 | |||
| 0cf5830671 | |||
| f25d8aec3c | |||
| 4ecb3f9943 | |||
| 3dcb521422 | |||
| de4db1de9a | |||
| a6c2b958a2 | |||
| f721d9d774 | |||
| 3a8c1c3fd9 | |||
| 891c70dd4c | |||
| f2b99722e3 | |||
| 32204d3e17 | |||
| 112dfe08b3 | |||
| 4c3736fad7 | |||
| 69a5b76e97 | |||
| f941207699 | |||
| 084a8956ca | |||
| 571b5270a2 | |||
| dcae6e1cd0 | |||
| a7d84d27fd | |||
| 9fba81f51d | |||
| 35e399dbaf | |||
| c3fc013002 | |||
| a34fc6eaa4 |
@@ -0,0 +1,76 @@
|
||||
import requests, json
|
||||
from pathlib import Path
|
||||
|
||||
# GitHub API URL to fetch all .json files describing scripts
|
||||
API_URL = "https://api.github.com/repos/community-scripts/ProxmoxVE/contents/frontend/public/json"
|
||||
|
||||
# Base path to build the full URL for the installable scripts
|
||||
SCRIPT_BASE = "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
|
||||
|
||||
# Output file where the consolidated helper scripts cache will be stored
|
||||
OUTPUT_FILE = Path("json/helpers_cache.json")
|
||||
OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
res = requests.get(API_URL)
|
||||
data = res.json()
|
||||
cache = []
|
||||
|
||||
# Loop over each file in the JSON directory
|
||||
for item in data:
|
||||
url = item.get("download_url")
|
||||
if not url or not url.endswith(".json"):
|
||||
continue
|
||||
try:
|
||||
raw = requests.get(url).json()
|
||||
if not isinstance(raw, dict):
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
|
||||
# Extract fields required to identify a valid helper script
|
||||
name = raw.get("name", "")
|
||||
slug = raw.get("slug")
|
||||
type_ = raw.get("type", "")
|
||||
script = raw.get("install_methods", [{}])[0].get("script", "")
|
||||
if not slug or not script:
|
||||
continue # Skip if it's not a valid script
|
||||
|
||||
desc = raw.get("description", "")
|
||||
categories = raw.get("categories", [])
|
||||
notes = [note.get("text", "") for note in raw.get("notes", []) if isinstance(note, dict)]
|
||||
full_script_url = f"{SCRIPT_BASE}/{script}"
|
||||
|
||||
|
||||
credentials = raw.get("default_credentials", {})
|
||||
cred_username = credentials.get("username")
|
||||
cred_password = credentials.get("password")
|
||||
|
||||
add_credentials = (
|
||||
(cred_username is not None and str(cred_username).strip() != "") or
|
||||
(cred_password is not None and str(cred_password).strip() != "")
|
||||
)
|
||||
|
||||
entry = {
|
||||
"name": name,
|
||||
"slug": slug,
|
||||
"desc": desc,
|
||||
"script": script,
|
||||
"script_url": full_script_url,
|
||||
"categories": categories,
|
||||
"notes": notes,
|
||||
"type": type_
|
||||
}
|
||||
if add_credentials:
|
||||
entry["default_credentials"] = {
|
||||
"username": cred_username,
|
||||
"password": cred_password
|
||||
}
|
||||
|
||||
cache.append(entry)
|
||||
|
||||
|
||||
# Write the JSON cache to disk
|
||||
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(cache, f, indent=2)
|
||||
|
||||
print(f"✅ helpers_cache.json created at {OUTPUT_FILE} with {len(cache)} valid scripts.")
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Update Helper Scripts Cache
|
||||
|
||||
on:
|
||||
# Manual trigger from GitHub Actions UI
|
||||
workflow_dispatch:
|
||||
|
||||
# Automatic run every 6 hours
|
||||
schedule:
|
||||
- cron: "0 */6 * * *"
|
||||
|
||||
jobs:
|
||||
update-cache:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to push changes to the repository
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout the repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 🐍 Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
|
||||
- name: 📦 Install Python dependencies
|
||||
run: pip install requests
|
||||
|
||||
- name: ⚙️ Generate json/helpers_cache.json
|
||||
run: python .github/scripts/generate_helpers_cache.py
|
||||
|
||||
- name: 📤 Commit and push if updated
|
||||
run: |
|
||||
git config user.name "ProxMenuxBot"
|
||||
git config user.email "bot@proxmenux.local"
|
||||
git add json/helpers_cache.json
|
||||
git diff --cached --quiet || git commit -m "Update helpers_cache.json"
|
||||
git push
|
||||
+256
@@ -1,3 +1,259 @@
|
||||
## 2025-08-06
|
||||
|
||||
### New version v1.1.4
|
||||
|
||||
### Added
|
||||
|
||||
- **Proxmox 9 Compatibility Preparation**
|
||||
This version prepares **ProxMenux** for the upcoming **Proxmox VE 9**:
|
||||
- The function to add the official Proxmox repositories now supports the new `.sources` format used in Proxmox 9, while maintaining backward compatibility with Proxmox 8.
|
||||
- Banner removal is now optionally supported for Proxmox 9.
|
||||
|
||||
- **xshok-proxmox Detection**
|
||||
Added a check to detect if the `xshok-proxmox` post-install script has already been executed.
|
||||
If detected, a warning is shown to avoid conflicting adjustments:
|
||||
|
||||
```
|
||||
It appears that you have already executed the xshok-proxmox post-install script on this system.
|
||||
|
||||
If you continue, some adjustments may be duplicated or conflict with those already made by xshok.
|
||||
|
||||
Do you want to continue anyway?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Improved
|
||||
|
||||
- **Banner Removal (Proxmox 8.4.9+)**
|
||||
Updated the logic for removing the subscription banner in **Proxmox 8.4.9**, due to changes in `proxmoxlib.js`.
|
||||
|
||||
- **LXC Disk Passthrough (Persistent UUID)**
|
||||
The function to add a physical disk to an LXC container now uses **UUID-based persistent paths**.
|
||||
This ensures that disks remain correctly mounted, even if the `/dev/sdX` order changes due to new hardware.
|
||||
|
||||
```bash
|
||||
PERSISTENT_DISK=$(get_persistent_path "$DISK")
|
||||
if [[ "$PERSISTENT_DISK" != "$DISK" ]] ...
|
||||
```
|
||||
|
||||
- **System Utilities Installer**
|
||||
Now checks whether APT sources are available before installing selected tools.
|
||||
If a new Proxmox installation has no active repos, it will **automatically add the default sources** to avoid installation failure.
|
||||
|
||||
- **IOMMU Activation on ZFS Systems**
|
||||
The function that enables IOMMU for passthrough now verifies existing kernel parameters to avoid duplication if the user has already configured them manually.
|
||||
|
||||
---
|
||||
|
||||
### Fixed
|
||||
|
||||
- Minor code cleanup and improved runtime performance across several modules.
|
||||
|
||||
|
||||
|
||||
## 2025-07-20
|
||||
|
||||
### Changed
|
||||
|
||||
- **Subscription Banner Removal (Proxmox 8.4.5+)**
|
||||
Improved the `remove_subscription_banner` function to ensure compatibility with Proxmox 8.4.5, where the banner removal method was failing after clean installations.
|
||||
|
||||
- **Improved Log2RAM Detection**
|
||||
In both the automatic and customizable post-install scripts, the logic for Log2RAM installation has been improved.
|
||||
Now it correctly detects if Log2RAM is already configured and avoids triggering errors or reconfiguration.
|
||||
|
||||
- **Optimized Figurine Installation**
|
||||
The `install_figurine` function now avoids duplicating `.bashrc` entries if the customization for the root prompt already exists.
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
- **New Function: Persistent Network Interface Naming**
|
||||
Added a new function `setup_persistent_network` to create stable network interface names using `.link` files based on MAC addresses.
|
||||
This avoids unpredictable renaming (e.g., `enp2s0` becoming `enp3s0`) when hardware changes, PCI topology shifts, or passthrough configurations are applied.
|
||||
|
||||
**Why use `.link` files?**
|
||||
Because predictable interface names in `systemd` can change with hardware reordering or replacement. Using static `.link` files bound to MAC addresses ensures consistency, especially on systems with multiple NICs or passthrough setups.
|
||||
|
||||
Special thanks to [@Andres_Eduardo_Rojas_Moya] for contributing the persistent
|
||||
network naming function and for the original idea.
|
||||
|
||||
```bash
|
||||
[Match]
|
||||
MACAddress=XX:XX:XX:XX:XX:XX
|
||||
|
||||
[Link]
|
||||
Name=eth0
|
||||
```
|
||||
|
||||
|
||||
## 2025-07-01
|
||||
|
||||
### New version v1.1.3
|
||||
|
||||

|
||||
|
||||
- **Dual Installation Modes for ProxMenux**
|
||||
The installer now offers two distinct modes:
|
||||
1. **Lite version (no translations):** Only installs two official Debian packages (`dialog`, `jq`) to enable menus and JSON parsing. No files are written beyond the configuration directory.
|
||||
2. **Full version (with translations):** Uses a virtual environment and allows selecting the interface language during installation.
|
||||
|
||||
When updating, if the user switches from full to lite, the old version will be **automatically removed** for a clean transition.
|
||||
|
||||
### Added
|
||||
|
||||
- **New Script: Automated Post-Installation Setup**
|
||||
A new minimal post-install script that performs essential setup automatically:
|
||||
- System upgrade and sync
|
||||
- Remove enterprise banner
|
||||
- Optimize APT, journald, logrotate, system limits
|
||||
- Improve kernel panic handling, memory settings, entropy, network
|
||||
- Add `.bashrc` tweaks and **Log2RAM auto-install** (if SSD/M.2 is detected)
|
||||
|
||||
- **New Function: Log2RAM Configuration**
|
||||
Now available in both the customizable and automatic post-install scripts.
|
||||
On systems with SSD/NVMe, Log2RAM is **enabled automatically** to preserve disk life.
|
||||
|
||||
- **New Menus:**
|
||||
- 🧰 **System Utilities Menu**
|
||||
Lets users select and install useful CLI tools with proper command validation.
|
||||
- 🌐 **Network Configuration & Repair**
|
||||
A new interactive menu for analyzing and repairing network interfaces.
|
||||
|
||||
### Improved
|
||||
|
||||
- **Post-Install Menu Logic**
|
||||
Options are now grouped more logically for better usability.
|
||||
|
||||
- **VM Creation Menu**
|
||||
Enhanced with improved CPU model support and custom options.
|
||||
|
||||
- **UUP Dump ISO Creator Script**
|
||||
- Added option to **customize the temporary folder location**
|
||||
- Fixed issue where entire temp folder was deleted instead of just contents
|
||||
💡 Suggested by [@igrokit](https://github.com/igrokit)
|
||||
[#17](https://github.com/MacRimi/ProxMenux/issues/17), [#11](https://github.com/MacRimi/ProxMenux/issues/11)
|
||||
|
||||
- **Physical Disk to LXC Script**
|
||||
Now handles **XFS-formatted disks** correctly.
|
||||
Thanks to [@antroxin](https://github.com/antroxin) for reporting and testing!
|
||||
|
||||
- **System Utilities Installer**
|
||||
Rewritten to **verify command availability** after installation, ensuring tools work as expected.
|
||||
🐛 Fix for [#18](https://github.com/MacRimi/ProxMenux/issues/18) by [@DST73](https://github.com/DST73)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Enable IOMMU on ZFS**
|
||||
The detection and configuration for enabling IOMMU on ZFS-based systems is now fully functional.
|
||||
🐛 Fix for [#15](https://github.com/MacRimi/ProxMenux/issues/15) by [@troponaut](https://github.com/troponaut)
|
||||
|
||||
### Other
|
||||
|
||||
- Performance and code cleanup improvements across several modules.
|
||||
|
||||
|
||||
|
||||
## 2025-06-06
|
||||
|
||||
### Added
|
||||
|
||||
- **New Menu: Proxmox PVE Helper Scripts**
|
||||
Officially introduced the new **Proxmox PVE Helper Scripts** menu, replacing the previous: Esenciales Proxmox.
|
||||
This new menu includes:
|
||||
- Script search by name in real time
|
||||
- Category-based browsing
|
||||
|
||||
It’s a cleaner, faster, and more functional way to access community scripts in Proxmox.
|
||||
|
||||

|
||||
|
||||
|
||||
- **New CPU Models in VM Creation**
|
||||
The CPU selection menu in VM creation has been greatly expanded to support advanced QEMU and x86-64 CPU profiles.
|
||||
This allows better compatibility with modern guest systems and fine-tuning performance for specific workloads, including nested virtualization and hardware-assisted features.
|
||||
|
||||
|
||||

|
||||
|
||||
Thanks to **@Nida Légé (Nidouille)** for suggesting this enhancement.
|
||||
|
||||
|
||||
- **Support for `.raw` Disk Images**
|
||||
The disk import tool for VMs now supports `.raw` files, in addition to `.img`, `.qcow2`, and `.vmdk`.
|
||||
This improves compatibility when working with disk exports from other hypervisors or backup tools.
|
||||
|
||||
💡 Suggested by **@guilloking** in [GitHub Issue #5](https://github.com/MacRimi/ProxMenux/issues/5)
|
||||
|
||||
|
||||
- **Locale Detection in Language Skipping**
|
||||
The function that disables extra APT languages now includes:
|
||||
- Automatic locale detection (`LANG`)
|
||||
- Auto-generation of `en_US.UTF-8` if none is found
|
||||
- Prevents warnings during script execution due to undefined locale
|
||||
|
||||
|
||||
### Improved
|
||||
|
||||
- **APT Language Skipping Logic**
|
||||
Improved locale handling ensures system compatibility before disabling translations:
|
||||
```bash
|
||||
if ! locale -a | grep -qi "^${default_locale//-/_}$"; then
|
||||
echo "$default_locale UTF-8" >> /etc/locale.gen
|
||||
locale-gen "$default_locale"
|
||||
fi
|
||||
```
|
||||
|
||||
- **System Update Speed**
|
||||
Post-install system upgrades are now faster:
|
||||
- The upgrade process (`dist-upgrade`) is separated from container template index updates.
|
||||
- Index refresh is now an optional feature selected in the script.
|
||||
|
||||
|
||||
|
||||
## 2025-05-27
|
||||
|
||||
### Fixed
|
||||
- **Kali Linux ISO URL Updated**
|
||||
Fixed the incorrect download URL for Kali Linux ISO in the Linux installer module. The new correct path is:
|
||||
```
|
||||
https://cdimage.kali.org/kali-2025.1c/kali-linux-2025.1c-installer-amd64.iso
|
||||
```
|
||||
|
||||
### Improved
|
||||
- **Faster Dialog Menu Transitions**
|
||||
Improved UI responsiveness across all interactive menus by replacing `whiptail` with `dialog`, offering faster transitions and smoother navigation.
|
||||
|
||||
- **Coral USB Support in LXC**
|
||||
Improved the logic for configuring Coral USB TPU passthrough into LXC containers:
|
||||
- Refactored configuration into modular blocks with better structure and inline comments.
|
||||
- Clear separation of Coral USB (`/dev/coral`) and Coral M.2 (`/dev/apex_0`) logic.
|
||||
- Maintains backward compatibility with existing LXC configurations.
|
||||
- Introduced persistent Coral USB passthrough using a udev rule:
|
||||
```bash
|
||||
# Create udev rule for Coral USB
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral"
|
||||
|
||||
# Map /dev/coral if it exists
|
||||
if [ -e /dev/coral ]; then
|
||||
echo "lxc.mount.entry: /dev/coral dev/coral none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||
fi
|
||||
```
|
||||
- Special thanks to **@Blaspt** for validating the persistent Coral USB passthrough and suggesting the use of `/dev/coral` symbolic link.
|
||||
|
||||
|
||||
### Added
|
||||
- **Persistent Coral USB Passthrough Support**
|
||||
Added udev rule support for Coral USB devices to persistently map them as `/dev/coral`, enabling consistent passthrough across reboots. This path is automatically detected and mapped in the container configuration.
|
||||
|
||||
- **RSS Feed Integration**
|
||||
Added support for generating an RSS feed for the changelog, allowing users to stay informed of updates through news clients.
|
||||
|
||||
- **Release Service Automation**
|
||||
Implemented a new release management service to automate publishing and tagging of versions, starting with version **v1.1.2**.
|
||||
|
||||
|
||||
## 2025-05-13
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -29,6 +29,7 @@ Instead, please report it privately via email:
|
||||
|
||||
📧 proxmenux@macrimi.pro
|
||||
|
||||
For detailed information on security vulnerabilities and how to report them, please refer to our [Security Policy](./SECURITY.md).
|
||||
|
||||
## 🤝 3. Community Guidelines
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# 🔒 Security Policy
|
||||
|
||||
## 📅 Supported Versions
|
||||
|
||||
We actively maintain the latest release of ProxMenux. Only the most recent version receives security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| Latest | ✅ |
|
||||
| Older versions | ❌ |
|
||||
|
||||
## 📢 Reporting a Vulnerability
|
||||
|
||||
If you discover a **security vulnerability**, please help us keep the community safe by reporting it **privately**.
|
||||
|
||||
**Do not report vulnerabilities in public GitHub Issues or Discussions.**
|
||||
|
||||
### 📬 Contact
|
||||
|
||||
To report a vulnerability, email:
|
||||
|
||||
**📧 proxmenux@macrimi.pro**
|
||||
|
||||
Please include as much detail as possible, including:
|
||||
|
||||
- Steps to reproduce the issue
|
||||
- A description of the impact
|
||||
- Any known mitigations
|
||||
|
||||
We aim to respond as soon as possible, typically within **48 hours**.
|
||||
|
||||
## ⚠️ Coordinated Disclosure
|
||||
|
||||
We follow responsible disclosure principles. If a vulnerability is confirmed, we will:
|
||||
|
||||
1. Work on a fix immediately.
|
||||
2. Inform you of the resolution status.
|
||||
|
||||
---
|
||||
|
||||
🔐 Thank you for helping make ProxMenux a safer project for everyone!
|
||||
+451
-115
@@ -6,8 +6,8 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.2
|
||||
# Last Updated: 04/04/2025
|
||||
# Version : 1.3
|
||||
# Last Updated: 04/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script installs and configures ProxMenux, a menu-driven
|
||||
@@ -33,7 +33,6 @@
|
||||
# the system for running ProxMenux efficiently.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
UTILS_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/utils.sh"
|
||||
@@ -42,42 +41,181 @@ BASE_DIR="/usr/local/share/proxmenux"
|
||||
CONFIG_FILE="$BASE_DIR/config.json"
|
||||
CACHE_FILE="$BASE_DIR/cache.json"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
#EMERGENCY_FILE="$BASE_DIR/emergency_repair.sh"
|
||||
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
|
||||
MENU_SCRIPT="menu"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
# Source utils.sh for common functions and styles
|
||||
if ! source <(curl -sSf "$UTILS_URL"); then
|
||||
echo "Error: Could not load utils.sh from $UTILS_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_corrupted_files() {
|
||||
if [ -f "$CONFIG_FILE" ] && ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
||||
echo "Cleaning up corrupted configuration file..."
|
||||
rm -f "$CONFIG_FILE"
|
||||
fi
|
||||
if [ -f "$CACHE_FILE" ] && ! jq empty "$CACHE_FILE" >/dev/null 2>&1; then
|
||||
echo "Cleaning up corrupted cache file..."
|
||||
rm -f "$CACHE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
check_existing_installation() {
|
||||
local has_venv=false
|
||||
local has_config=false
|
||||
local has_language=false
|
||||
local has_menu=false
|
||||
|
||||
if [ -f "$INSTALL_DIR/$MENU_SCRIPT" ]; then
|
||||
has_menu=true
|
||||
fi
|
||||
|
||||
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
has_venv=true
|
||||
fi
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
if jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
||||
has_config=true
|
||||
local current_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
|
||||
if [[ -n "$current_language" && "$current_language" != "null" && "$current_language" != "empty" ]]; then
|
||||
has_language=true
|
||||
fi
|
||||
else
|
||||
echo "Warning: Corrupted config file detected, removing..."
|
||||
rm -f "$CONFIG_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$has_venv" = true ] && [ "$has_language" = true ]; then
|
||||
echo "translation"
|
||||
elif [ "$has_menu" = true ] && [ "$has_venv" = false ]; then
|
||||
echo "normal"
|
||||
elif [ "$has_menu" = true ]; then
|
||||
echo "unknown"
|
||||
else
|
||||
echo "none"
|
||||
fi
|
||||
}
|
||||
|
||||
uninstall_proxmenu() {
|
||||
local install_type="$1"
|
||||
local force_clean="$2"
|
||||
|
||||
if [ "$force_clean" != "force" ]; then
|
||||
if ! whiptail --title "Uninstall ProxMenu" --yesno "Are you sure you want to uninstall ProxMenu?" 10 60; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Uninstalling ProxMenu..."
|
||||
|
||||
if [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
echo "Removing googletrans and virtual environment..."
|
||||
source "$VENV_PATH/bin/activate"
|
||||
pip uninstall -y googletrans >/dev/null 2>&1
|
||||
deactivate
|
||||
rm -rf "$VENV_PATH"
|
||||
fi
|
||||
|
||||
if [ "$install_type" = "translation" ] && [ "$force_clean" != "force" ]; then
|
||||
DEPS_TO_REMOVE=$(whiptail --title "Remove Translation Dependencies" --checklist \
|
||||
"Select translation-specific dependencies to remove:" 15 60 3 \
|
||||
"python3-venv" "Python virtual environment" OFF \
|
||||
"python3-pip" "Python package installer" OFF \
|
||||
"python3" "Python interpreter" OFF \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -n "$DEPS_TO_REMOVE" ]; then
|
||||
echo "Removing selected dependencies..."
|
||||
read -r -a DEPS_ARRAY <<< "$(echo "$DEPS_TO_REMOVE" | tr -d '"')"
|
||||
for dep in "${DEPS_ARRAY[@]}"; do
|
||||
echo "Removing $dep..."
|
||||
apt-mark auto "$dep" >/dev/null 2>&1
|
||||
apt-get -y --purge autoremove "$dep" >/dev/null 2>&1
|
||||
done
|
||||
apt-get autoremove -y --purge >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
|
||||
rm -rf "$BASE_DIR"
|
||||
|
||||
[ -f /root/.bashrc.bak ] && mv /root/.bashrc.bak /root/.bashrc
|
||||
if [ -f /etc/motd.bak ]; then
|
||||
mv /etc/motd.bak /etc/motd
|
||||
else
|
||||
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
|
||||
fi
|
||||
|
||||
echo "ProxMenu has been uninstalled."
|
||||
return 0
|
||||
}
|
||||
|
||||
handle_installation_change() {
|
||||
local current_type="$1"
|
||||
local new_type="$2"
|
||||
|
||||
if [ "$current_type" = "$new_type" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$current_type-$new_type" in
|
||||
"translation-1"|"translation-normal")
|
||||
if whiptail --title "Installation Type Change" \
|
||||
--yesno "Switch from Translation to Normal Version?\n\nThis will remove translation components." 10 60; then
|
||||
echo "Preparing for installation type change..."
|
||||
uninstall_proxmenu "translation" "force" >/dev/null 2>&1
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
"normal-2"|"normal-translation")
|
||||
if whiptail --title "Installation Type Change" \
|
||||
--yesno "Switch from Normal to Translation Version?\n\nThis will add translation components." 10 60; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
update_config() {
|
||||
local component="$1"
|
||||
local status="$2"
|
||||
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# List of components we want to track
|
||||
local tracked_components=("whiptail" "dialog" "curl" "jq" "python3" "python3-venv" "python3-pip" "virtual_environment" "pip" "googletrans")
|
||||
local tracked_components=("dialog" "curl" "jq" "python3" "python3-venv" "python3-pip" "virtual_environment" "pip" "googletrans")
|
||||
|
||||
# Check if the component is in the list of tracked components
|
||||
if [[ " ${tracked_components[@]} " =~ " ${component} " ]]; then
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ] || ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
||||
echo '{}' > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
tmp=$(mktemp)
|
||||
jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
|
||||
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
||||
local tmp_file=$(mktemp)
|
||||
if jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
|
||||
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp_file" 2>/dev/null; then
|
||||
mv "$tmp_file" "$CONFIG_FILE"
|
||||
else
|
||||
echo '{}' > "$CONFIG_FILE"
|
||||
jq --arg comp "$component" --arg stat "$status" --arg time "$timestamp" \
|
||||
'.[$comp] = {status: $stat, timestamp: $time}' "$CONFIG_FILE" > "$tmp_file" && mv "$tmp_file" "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
[ -f "$tmp_file" ] && rm -f "$tmp_file"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
show_progress() {
|
||||
local step="$1"
|
||||
local total="$2"
|
||||
@@ -88,63 +226,203 @@ show_progress() {
|
||||
msg_info2 "$message"
|
||||
}
|
||||
|
||||
select_language() {
|
||||
if [ -f "$CONFIG_FILE" ] && jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
||||
local existing_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
|
||||
if [[ -n "$existing_language" && "$existing_language" != "null" && "$existing_language" != "empty" ]]; then
|
||||
LANGUAGE="$existing_language"
|
||||
msg_ok "Using existing language configuration: $LANGUAGE"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
LANGUAGE=$(whiptail --title "Select Language" --menu "Choose a language for the menu:" 20 60 12 \
|
||||
"en" "English (Recommended)" \
|
||||
"es" "Spanish" \
|
||||
"fr" "French" \
|
||||
"de" "German" \
|
||||
"it" "Italian" \
|
||||
"pt" "Portuguese" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$LANGUAGE" ]; then
|
||||
msg_error "No language selected. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ] || ! jq empty "$CONFIG_FILE" >/dev/null 2>&1; then
|
||||
echo '{}' > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
local tmp_file=$(mktemp)
|
||||
if jq --arg lang "$LANGUAGE" '. + {language: $lang}' "$CONFIG_FILE" > "$tmp_file" 2>/dev/null; then
|
||||
mv "$tmp_file" "$CONFIG_FILE"
|
||||
else
|
||||
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
[ -f "$tmp_file" ] && rm -f "$tmp_file"
|
||||
|
||||
msg_ok "Language set to: $LANGUAGE"
|
||||
}
|
||||
|
||||
# # Main installation function =============================
|
||||
# Show installation confirmation for new installations
|
||||
show_installation_confirmation() {
|
||||
local install_type="$1"
|
||||
|
||||
case "$install_type" in
|
||||
"1")
|
||||
if whiptail --title "ProxMenux - Normal Version Installation" \
|
||||
--yesno "ProxMenux Normal Version will install:\n\n• dialog (interactive menus) - Official Debian package\n• curl (file downloads) - Official Debian package\n• jq (JSON processing) - Official Debian package\n• ProxMenux core files (/usr/local/share/proxmenux)\n\nThis is a lightweight installation with minimal dependencies.\n\nProceed with installation?" 18 70; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
"2")
|
||||
if whiptail --title "ProxMenux - Translation Version Installation" \
|
||||
--yesno "ProxMenux Translation Version will install:\n\n• dialog (interactive menus)\n• curl (file downloads)\n• jq (JSON processing)\n• python3 + python3-venv + python3-pip\n• Google Translate library (googletrans)\n• Virtual environment (/opt/googletrans-env)\n• Translation cache system\n• ProxMenux core files\n\nThis version requires more dependencies for translation support.\n\nProceed with installation?" 18 70; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_proxmenu() {
|
||||
####################################################
|
||||
install_normal_version() {
|
||||
local total_steps=3
|
||||
local current_step=1
|
||||
|
||||
show_progress $current_step $total_steps "Installing basic dependencies"
|
||||
|
||||
if ! dpkg -l | grep -qw "jq"; then
|
||||
msg_info "Installing jq..."
|
||||
apt-get update > /dev/null 2>&1
|
||||
if apt-get install -y jq > /dev/null 2>&1; then
|
||||
msg_ok "jq installed successfully."
|
||||
update_config "jq" "installed"
|
||||
else
|
||||
msg_error "Failed to install jq. Please install it manually."
|
||||
update_config "jq" "failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
msg_ok "jq is already installed."
|
||||
update_config "jq" "already_installed"
|
||||
fi
|
||||
|
||||
BASIC_DEPS=("dialog" "curl")
|
||||
for pkg in "${BASIC_DEPS[@]}"; do
|
||||
if ! dpkg -l | grep -qw "$pkg"; then
|
||||
msg_info "Installing $pkg..."
|
||||
if apt-get install -y "$pkg" > /dev/null 2>&1; then
|
||||
msg_ok "$pkg installed successfully."
|
||||
update_config "$pkg" "installed"
|
||||
else
|
||||
msg_error "Failed to install $pkg. Please install it manually."
|
||||
update_config "$pkg" "failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
msg_ok "$pkg is already installed."
|
||||
update_config "$pkg" "already_installed"
|
||||
fi
|
||||
done
|
||||
|
||||
((current_step++))
|
||||
|
||||
show_progress $current_step $total_steps "Creating directories and configuration"
|
||||
|
||||
mkdir -p "$BASE_DIR"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo '{}' > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
msg_ok "Directories and configuration created."
|
||||
((current_step++))
|
||||
|
||||
show_progress $current_step $total_steps "Downloading necessary files"
|
||||
|
||||
FILES=(
|
||||
"$UTILS_FILE $REPO_URL/scripts/utils.sh"
|
||||
# "$EMERGENCY_FILE $REPO_URL/scripts/emergency_repair.sh"
|
||||
"$INSTALL_DIR/$MENU_SCRIPT $REPO_URL/$MENU_SCRIPT"
|
||||
"$LOCAL_VERSION_FILE $REPO_URL/version.txt"
|
||||
)
|
||||
|
||||
for file in "${FILES[@]}"; do
|
||||
IFS=" " read -r dest url <<< "$file"
|
||||
msg_info "Downloading ${dest##*/}..."
|
||||
sleep 2
|
||||
if wget -qO "$dest" "$url"; then
|
||||
msg_ok "${dest##*/} downloaded successfully."
|
||||
else
|
||||
msg_error "Failed to download ${dest##*/}. Check your Internet connection."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
chmod +x "$INSTALL_DIR/$MENU_SCRIPT"
|
||||
# chmod +x "$EMERGENCY_FILE"
|
||||
}
|
||||
|
||||
####################################################
|
||||
install_translation_version() {
|
||||
local total_steps=4
|
||||
local current_step=1
|
||||
|
||||
# Step 1: Check and install system dependencies
|
||||
|
||||
show_progress $current_step $total_steps "Checking system dependencies"
|
||||
|
||||
|
||||
if ! dpkg -l | grep -qw "jq"; then
|
||||
msg_info "Installing jq..."
|
||||
apt-get update > /dev/null 2>&1
|
||||
if apt-get install -y jq > /dev/null 2>&1; then
|
||||
msg_ok "jq installed successfully."
|
||||
update_config "jq" "installed"
|
||||
else
|
||||
msg_error "Failed to install jq. Please install it manually."
|
||||
update_config "jq" "failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
msg_ok "jq is already installed."
|
||||
update_config "jq" "already_installed"
|
||||
fi
|
||||
|
||||
|
||||
DEPS=("whiptail" "dialog" "curl" "python3" "python3-venv" "python3-pip")
|
||||
for pkg in "${DEPS[@]}"; do
|
||||
if ! dpkg -l | grep -qw "$pkg"; then
|
||||
msg_info "Installing $pkg..."
|
||||
if apt-get install -y "$pkg" > /dev/null 2>&1; then
|
||||
msg_ok "$pkg installed successfully."
|
||||
update_config "$pkg" "installed"
|
||||
else
|
||||
msg_error "Failed to install $pkg. Please install it manually."
|
||||
update_config "$pkg" "failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
msg_ok "$pkg is already installed."
|
||||
update_config "$pkg" "already_installed"
|
||||
fi
|
||||
done
|
||||
|
||||
((current_step++))
|
||||
|
||||
|
||||
# Step 2: Set up virtual environment
|
||||
|
||||
show_progress $current_step $total_steps "Setting up virtual environment for translate"
|
||||
|
||||
show_progress $current_step $total_steps "Language selection"
|
||||
select_language
|
||||
((current_step++))
|
||||
|
||||
show_progress $current_step $total_steps "Installing system dependencies"
|
||||
|
||||
if ! dpkg -l | grep -qw "jq"; then
|
||||
msg_info "Installing jq..."
|
||||
apt-get update > /dev/null 2>&1
|
||||
if apt-get install -y jq > /dev/null 2>&1; then
|
||||
msg_ok "jq installed successfully."
|
||||
update_config "jq" "installed"
|
||||
else
|
||||
msg_error "Failed to install jq. Please install it manually."
|
||||
update_config "jq" "failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
msg_ok "jq is already installed."
|
||||
update_config "jq" "already_installed"
|
||||
fi
|
||||
|
||||
DEPS=("dialog" "curl" "python3" "python3-venv" "python3-pip")
|
||||
for pkg in "${DEPS[@]}"; do
|
||||
if ! dpkg -l | grep -qw "$pkg"; then
|
||||
msg_info "Installing $pkg..."
|
||||
if apt-get install -y "$pkg" > /dev/null 2>&1; then
|
||||
msg_ok "$pkg installed successfully."
|
||||
update_config "$pkg" "installed"
|
||||
else
|
||||
msg_error "Failed to install $pkg. Please install it manually."
|
||||
update_config "$pkg" "failed"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
msg_ok "$pkg is already installed."
|
||||
update_config "$pkg" "already_installed"
|
||||
fi
|
||||
done
|
||||
|
||||
((current_step++))
|
||||
|
||||
show_progress $current_step $total_steps "Setting up translation environment"
|
||||
|
||||
if [ ! -d "$VENV_PATH" ] || [ ! -f "$VENV_PATH/bin/activate" ]; then
|
||||
msg_info "Creating the virtual environment..."
|
||||
python3 -m venv --system-site-packages "$VENV_PATH" > /dev/null 2>&1
|
||||
|
||||
if [ ! -f "$VENV_PATH/bin/activate" ]; then
|
||||
msg_error "Failed to create virtual environment. Please check your Python installation."
|
||||
update_config "virtual_environment" "failed"
|
||||
@@ -157,12 +435,9 @@ install_proxmenu() {
|
||||
msg_ok "Virtual environment already exists."
|
||||
update_config "virtual_environment" "already_exists"
|
||||
fi
|
||||
|
||||
source "$VENV_PATH/bin/activate"
|
||||
((current_step++))
|
||||
|
||||
# Step 3: Install and upgrade pip and googletrans
|
||||
|
||||
show_progress $current_step $total_steps "Installing and upgrading pip and googletrans"
|
||||
|
||||
msg_info "Upgrading pip..."
|
||||
if pip install --upgrade pip > /dev/null 2>&1; then
|
||||
msg_ok "Pip upgraded successfully."
|
||||
@@ -172,7 +447,7 @@ install_proxmenu() {
|
||||
update_config "pip" "upgrade_failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
msg_info "Installing googletrans..."
|
||||
if pip install --break-system-packages --no-cache-dir googletrans==4.0.0-rc1 > /dev/null 2>&1; then
|
||||
msg_ok "Googletrans installed successfully."
|
||||
@@ -183,79 +458,140 @@ install_proxmenu() {
|
||||
deactivate
|
||||
return 1
|
||||
fi
|
||||
|
||||
deactivate
|
||||
((current_step++))
|
||||
|
||||
# Step 4: Download necessary files
|
||||
|
||||
|
||||
show_progress $current_step $total_steps "Downloading necessary files"
|
||||
|
||||
mkdir -p "$BASE_DIR"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
FILES=(
|
||||
"$CACHE_FILE $REPO_URL/json/cache.json"
|
||||
"$UTILS_FILE $REPO_URL/scripts/utils.sh"
|
||||
# "$EMERGENCY_FILE $REPO_URL/scripts/emergency_repair.sh"
|
||||
"$INSTALL_DIR/$MENU_SCRIPT $REPO_URL/$MENU_SCRIPT"
|
||||
"$LOCAL_VERSION_FILE $REPO_URL/version.txt"
|
||||
)
|
||||
|
||||
for file in "${FILES[@]}"; do
|
||||
IFS=" " read -r dest url <<< "$file"
|
||||
msg_info "Downloading ${dest##*/}..."
|
||||
sleep 2
|
||||
if wget -qO "$dest" "$url"; then
|
||||
msg_ok "${dest##*/} downloaded successfully."
|
||||
if [[ "$dest" == "$CACHE_FILE" ]]; then
|
||||
msg_ok "Cache file updated with latest translations."
|
||||
fi
|
||||
else
|
||||
msg_error "Failed to download ${dest##*/}. Check your Internet connection."
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
((current_step++))
|
||||
|
||||
# Final setup
|
||||
|
||||
|
||||
chmod +x "$INSTALL_DIR/$MENU_SCRIPT"
|
||||
|
||||
|
||||
|
||||
# Installation complete ====================================
|
||||
echo
|
||||
#echo -e "${YW}╭─────────────────────────────────────────────────────╮${CL}"
|
||||
#echo -e "${YW}│${CL} ${GN}🌟 ProxMenux has been installed successfull 🌟 ${CL} ${YW}│${CL}"
|
||||
#echo -e "${YW}╰─────────────────────────────────────────────────────╯${CL}"
|
||||
msg_title "ProxMenux has been installed successfull"
|
||||
echo
|
||||
echo -ne "${GN}"
|
||||
type_text "To run ProxMenux, simply execute this command in the console or terminal:"
|
||||
echo -e "${YWB} menu${CL}"
|
||||
echo
|
||||
|
||||
|
||||
#chmod +x "$EMERGENCY_FILE"
|
||||
}
|
||||
|
||||
####################################################
|
||||
show_installation_options() {
|
||||
local current_install_type
|
||||
current_install_type=$(check_existing_installation)
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
|
||||
local menu_title="ProxMenux Installation"
|
||||
local menu_text="Choose installation type:"
|
||||
|
||||
if [ "$current_install_type" != "none" ]; then
|
||||
case "$current_install_type" in
|
||||
"translation")
|
||||
menu_title="ProxMenux Update - Translation Version Detected"
|
||||
;;
|
||||
"normal")
|
||||
menu_title="ProxMenux Update - Normal Version Detected"
|
||||
;;
|
||||
"unknown")
|
||||
menu_title="ProxMenux Update - Existing Installation Detected"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
if [[ "$pve_version" -ge 9 ]]; then
|
||||
INSTALL_TYPE=$(whiptail --backtitle "ProxMenux" --title "$menu_title" --menu "\n$menu_text" 14 70 2 \
|
||||
"1" "Normal Version (English only)" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$INSTALL_TYPE" ]; then
|
||||
msg_warn "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
INSTALL_TYPE=$(whiptail --backtitle "ProxMenux" --title "$menu_title" --menu "\n$menu_text" 14 70 2 \
|
||||
"1" "Normal Version (English only)" \
|
||||
"2" "Translation Version (Multi-language support)" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$INSTALL_TYPE" ]; then
|
||||
msg_warn "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if [ -z "$INSTALL_TYPE" ]; then
|
||||
msg_warn "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# For new installations, show confirmation with details
|
||||
if [ "$current_install_type" = "none" ]; then
|
||||
if ! show_installation_confirmation "$INSTALL_TYPE"; then
|
||||
msg_warn "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! handle_installation_change "$current_install_type" "$INSTALL_TYPE"; then
|
||||
msg_warn "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_proxmenu() {
|
||||
show_installation_options
|
||||
|
||||
case "$INSTALL_TYPE" in
|
||||
"1")
|
||||
show_proxmenux_logo
|
||||
msg_title "Installing ProxMenux - Normal Version"
|
||||
install_normal_version
|
||||
;;
|
||||
"2")
|
||||
show_proxmenux_logo
|
||||
msg_title "Installing ProxMenux - Translation Version"
|
||||
install_translation_version
|
||||
;;
|
||||
*)
|
||||
msg_error "Invalid option selected."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_title "$(translate "ProxMenux has been installed successfully")"
|
||||
echo -ne "${GN}"
|
||||
type_text "$(translate "To run ProxMenux, simply execute this command in the console or terminal:")"
|
||||
echo -e "${YWB} menu${CL}"
|
||||
echo
|
||||
}
|
||||
|
||||
# Main execution ==========================================
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
msg_error "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
|
||||
echo
|
||||
echo -e "${BOLD}${YW}To function correctly, ProxMenux needs to install the following components:${CL}"
|
||||
echo -e "${TAB}- whiptail (if not already installed)"
|
||||
echo -e "${TAB}- curl (if not already installed)"
|
||||
echo -e "${TAB}- jq (if not already installed)"
|
||||
echo -e "${TAB}- Python 3 (if not already installed)"
|
||||
echo -e "${TAB}- Virtual environment for Google Translate"
|
||||
echo -e "${TAB}- ProxMenux scripts and configuration files"
|
||||
echo
|
||||
read -p "Do you want to proceed with the installation? (y/n) " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]
|
||||
then
|
||||
install_proxmenu
|
||||
else
|
||||
msg_warn "Installation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
cleanup_corrupted_files
|
||||
install_proxmenu
|
||||
+794
-13
@@ -1,4 +1,18 @@
|
||||
{
|
||||
"ProxMenux has been installed successfully": {
|
||||
"es": "ProxMenux se ha instalado correctamente.",
|
||||
"fr": "ProxMenux a été installé avec succès.",
|
||||
"de": "ProxMenux wurde erfolgreich installiert.",
|
||||
"it": "ProxMenux è stato installato correttamente.",
|
||||
"pt": "ProxMenux foi instalado com sucesso."
|
||||
},
|
||||
"To run ProxMenux, simply execute this command in the console or terminal:": {
|
||||
"es": "Para ejecutar ProxMenux, simplemente ejecute este comando en la consola o terminal:",
|
||||
"fr": "Pour exécuter ProxMenux, lancez simplement cette commande dans la console ou le terminal :",
|
||||
"de": "Um ProxMenux zu starten, führen Sie diesen Befehl einfach in der Konsole oder im Terminal aus:",
|
||||
"it": "Per avviare ProxMenux, esegui semplicemente questo comando nel terminale:",
|
||||
"pt": "Para executar o ProxMenux, basta executar este comando no console ou terminal:"
|
||||
},
|
||||
"Language changed to": {
|
||||
"es": "Idioma cambiado a",
|
||||
"fr": "Langue changée en",
|
||||
@@ -34,12 +48,12 @@
|
||||
"it": "Dischi e archiviazione",
|
||||
"pt": "Discos e armazenamento"
|
||||
},
|
||||
"Network": {
|
||||
"es": "Red",
|
||||
"fr": "Réseau",
|
||||
"de": "Netzwerk",
|
||||
"it": "Rete",
|
||||
"pt": "Rede"
|
||||
"Network Management": {
|
||||
"es": "Gestión de Red",
|
||||
"fr": "Gestion de Réseau",
|
||||
"de": "Netzwerkmanagement",
|
||||
"it": "Gestione della Rete",
|
||||
"pt": "Gestão de Rede"
|
||||
},
|
||||
"Settings": {
|
||||
"es": "Configuración",
|
||||
@@ -223,12 +237,12 @@
|
||||
"it": "Hardware: GPUs e Coral-TPU",
|
||||
"pt": "Hardware: GPUs e Coral-TPU"
|
||||
},
|
||||
"Essential Proxmox VE Helper-Scripts": {
|
||||
"es": "Esenciales Proxmox VE Helper-Scripts",
|
||||
"fr": "Essentiels Proxmox VE Helper-Scripts",
|
||||
"de": "Essentielle Proxmox VE Helper-Scriptse",
|
||||
"it": "Essenziali Proxmox VE Helper-Scripts",
|
||||
"pt": "Essenciais Proxmox VE Helper-Scripts"
|
||||
"Proxmox VE Helper Scripts": {
|
||||
"es": "Proxmox VE Helper Scripts",
|
||||
"fr": "Proxmox VE Helper Scripts",
|
||||
"de": "Proxmox VE Helper Scriptse",
|
||||
"it": "Proxmox VE Helper Scripts",
|
||||
"pt": "Proxmox VE Helper Scripts"
|
||||
},
|
||||
"Generating automatic translations...": {
|
||||
"es": "Generando traducciones...",
|
||||
@@ -889,7 +903,11 @@
|
||||
"es": "Comandos de ayuda e información"
|
||||
},
|
||||
"Create VM from template or script": {
|
||||
"es": "Crear VM a partir de plantilla o script"
|
||||
"es": "Crear VM a partir de plantilla o script",
|
||||
"fr": "Créer une VM à partir d’un modèle ou d’un script",
|
||||
"de": "VM aus Vorlage oder Skript erstellen",
|
||||
"it": "Crea VM da template o script",
|
||||
"pt": "Criar VM a partir de modelo ou script"
|
||||
},
|
||||
"Disk and Storage Manager": {
|
||||
"es": "Administrador de disco y almacenamiento"
|
||||
@@ -1352,5 +1370,768 @@
|
||||
},
|
||||
"Manage and inspect VM disk images": {
|
||||
"es": "Administrar e inspeccionar imágenes de disco de VM"
|
||||
},
|
||||
"Utilities and Tools": {
|
||||
"es": "Utilidades y herramientas",
|
||||
"fr": "Utilitaires et outils",
|
||||
"de": "Dienstprogramme und Tools"
|
||||
},
|
||||
"Utilities Menu": {
|
||||
"es": "Menú de utilidades",
|
||||
"fr": "Menu des utilitaires",
|
||||
"de": "Dienstprogramm-Menü"
|
||||
},
|
||||
"UUp Dump ISO creator Custom": {
|
||||
"es": "Creador personalizado de ISOs UUP Dump",
|
||||
"fr": "Créateur personnalisé d’ISO UUP Dump",
|
||||
"de": "Benutzerdefinierter UUP Dump ISO-Ersteller"
|
||||
},
|
||||
"System Utilities Installer": {
|
||||
"es": "Instalador de utilidades del sistema",
|
||||
"fr": "Programme d’installation des utilitaires système",
|
||||
"de": "Systemdienstprogramme-Installer"
|
||||
},
|
||||
"Proxmox System Update": {
|
||||
"es": "Actualización del sistema Proxmox",
|
||||
"fr": "Mise à jour du système Proxmox",
|
||||
"de": "Proxmox-Systemaktualisierung"
|
||||
},
|
||||
"Enter destination path for ISO file": {
|
||||
"es": "Introduce la ruta de destino para el archivo ISO",
|
||||
"fr": "Indiquez le chemin de destination du fichier ISO",
|
||||
"de": "Geben Sie den Zielpfad für die ISO-Datei ein"
|
||||
},
|
||||
"Paste the UUP Dump URL here": {
|
||||
"es": "Pega aquí la URL de UUP Dump",
|
||||
"fr": "Collez ici l’URL de UUP Dump",
|
||||
"de": "Fügen Sie hier die UUP Dump-URL ein"
|
||||
},
|
||||
"Utilities Installation Menu": {
|
||||
"es": "Menú de instalación de utilidades",
|
||||
"fr": "Menu d’installation des utilitaires",
|
||||
"de": "Installationsmenü für Dienstprogramme"
|
||||
},
|
||||
"Select an option": {
|
||||
"es": "Selecciona una opción",
|
||||
"fr": "Sélectionnez une option",
|
||||
"de": "Wählen Sie eine Option"
|
||||
},
|
||||
"Custom selection": {
|
||||
"es": "Selección personalizada",
|
||||
"fr": "Sélection personnalisée",
|
||||
"de": "Benutzerdefinierte Auswahl"
|
||||
},
|
||||
"Install ALL utilities": {
|
||||
"es": "Instalar todas las utilidades",
|
||||
"fr": "Installer tous les utilitaires",
|
||||
"de": "Alle Dienstprogramme installieren"
|
||||
},
|
||||
"Install basic utilities": {
|
||||
"es": "Instalar utilidades básicas",
|
||||
"fr": "Installer les utilitaires de base",
|
||||
"de": "Basis-Dienstprogramme installieren"
|
||||
},
|
||||
"Install development tools": {
|
||||
"es": "Instalar herramientas de desarrollo",
|
||||
"fr": "Installer les outils de développement",
|
||||
"de": "Entwicklungstools installieren"
|
||||
},
|
||||
"Install compression tools": {
|
||||
"es": "Instalar herramientas de compresión",
|
||||
"fr": "Installer les outils de compression",
|
||||
"de": "Komprimierungstools installieren"
|
||||
},
|
||||
"Install terminal multiplexers": {
|
||||
"es": "Instalar multiplexores de terminal",
|
||||
"fr": "Installer les multiplexeurs de terminal",
|
||||
"de": "Terminal-Multiplexer installieren"
|
||||
},
|
||||
"Install analysis tools": {
|
||||
"es": "Instalar herramientas de análisis",
|
||||
"fr": "Installer les outils d’analyse",
|
||||
"de": "Analysetools installieren"
|
||||
},
|
||||
"Install network tools": {
|
||||
"es": "Instalar herramientas de red",
|
||||
"fr": "Installer les outils réseau",
|
||||
"de": "Netzwerktools installieren"
|
||||
},
|
||||
"Verify installations": {
|
||||
"es": "Verificar instalaciones",
|
||||
"fr": "Vérifier les installations",
|
||||
"de": "Installationen überprüfen"
|
||||
},
|
||||
"Return to main menu": {
|
||||
"es": "Volver al menú principal",
|
||||
"fr": "Retour au menu principal",
|
||||
"de": "Zurück zum Hauptmenü"
|
||||
},
|
||||
"Select utilities to install": {
|
||||
"es": "Selecciona las utilidades a instalar",
|
||||
"fr": "Sélectionnez les utilitaires à installer",
|
||||
"de": "Wählen Sie die zu installierenden Dienstprogramme aus"
|
||||
},
|
||||
"Use SPACE to select/deselect, ENTER to confirm": {
|
||||
"es": "Usa ESPACIO para seleccionar/deseleccionar, ENTER para confirmar",
|
||||
"fr": "Utilisez ESPACE pour sélectionner/désélectionner, ENTRÉE pour confirmer",
|
||||
"de": "Mit LEERTASTE auswählen/abwählen, mit ENTER bestätigen"
|
||||
},
|
||||
"No Selection": {
|
||||
"es": "Sin selección",
|
||||
"fr": "Aucune sélection",
|
||||
"de": "Keine Auswahl"
|
||||
},
|
||||
"No utilities were selected": {
|
||||
"es": "No se seleccionó ninguna utilidad",
|
||||
"fr": "Aucun utilitaire sélectionné",
|
||||
"de": "Es wurden keine Dienstprogramme ausgewählt"
|
||||
},
|
||||
"This script will update your Proxmox VE system with advanced options:": {
|
||||
"es": "Este script actualizará su sistema Proxmox VE con opciones avanzadas:",
|
||||
"fr": "Ce script va mettre à jour votre système Proxmox VE avec des options avancées :",
|
||||
"de": "Dieses Skript aktualisiert Ihr Proxmox VE-System mit erweiterten Optionen:"
|
||||
},
|
||||
"Repairs and optimizes repositories": {
|
||||
"es": "Repara y optimiza los repositorios",
|
||||
"fr": "Répare et optimise les dépôts",
|
||||
"de": "Repariert und optimiert die Paketquellen"
|
||||
},
|
||||
"Cleans duplicate or conflicting sources": {
|
||||
"es": "Limpia fuentes duplicadas o conflictivas",
|
||||
"fr": "Nettoie les sources dupliquées ou conflictuelles",
|
||||
"de": "Bereinigt doppelte oder widersprüchliche Quellen"
|
||||
},
|
||||
"Switches to the free no-subscription repository": {
|
||||
"es": "Cambia al repositorio gratuito sin suscripción",
|
||||
"fr": "Passe au dépôt gratuit sans abonnement",
|
||||
"de": "Wechselt zum kostenlosen Repository ohne Abonnement"
|
||||
},
|
||||
"Updates all Proxmox and Debian packages": {
|
||||
"es": "Actualiza todos los paquetes de Proxmox y Debian",
|
||||
"fr": "Met à jour tous les paquets Proxmox et Debian",
|
||||
"de": "Aktualisiert alle Proxmox- und Debian-Pakete"
|
||||
},
|
||||
"Installs essential packages if missing": {
|
||||
"es": "Instala los paquetes esenciales si faltan",
|
||||
"fr": "Installe les paquets essentiels s’ils manquent",
|
||||
"de": "Installiert fehlende essenzielle Pakete"
|
||||
},
|
||||
"Checks for LVM and storage issues": {
|
||||
"es": "Comprueba problemas de LVM y almacenamiento",
|
||||
"fr": "Vérifie les problèmes LVM et de stockage",
|
||||
"de": "Überprüft LVM- und Speicherprobleme"
|
||||
},
|
||||
"Performs automatic cleanup after updating": {
|
||||
"es": "Realiza una limpieza automática tras la actualización",
|
||||
"fr": "Effectue un nettoyage automatique après la mise à jour",
|
||||
"de": "Führt eine automatische Bereinigung nach der Aktualisierung durch"
|
||||
},
|
||||
"Do you want to proceed and run the Proxmox System Update?": {
|
||||
"es": "¿Quieres continuar y ejecutar la actualización del sistema Proxmox?",
|
||||
"fr": "Voulez-vous continuer et lancer la mise à jour du système Proxmox ?",
|
||||
"de": "Möchten Sie fortfahren und die Proxmox-Systemaktualisierung ausführen?"
|
||||
},
|
||||
"Update Proxmox VE Appliance Manager": {
|
||||
"es": "Actualizar Proxmox VE Appliance Manager",
|
||||
"fr": "Mettre à jour Proxmox VE Appliance Manager",
|
||||
"de": "Proxmox VE Appliance Manager aktualisieren",
|
||||
"it": "Aggiorna Proxmox VE Appliance Manager",
|
||||
"pt": "Atualizar o Proxmox VE Appliance Manager"
|
||||
},
|
||||
"Post-Installation Options": {
|
||||
"es": "Ajustes post-instalación",
|
||||
"fr": "Options post-installation",
|
||||
"de": "Optionen nach der Installation",
|
||||
"it": "Opzioni post-installazione",
|
||||
"pt": "Opções pós-instalação"
|
||||
},
|
||||
"Automated post-installation script": {
|
||||
"es": "Script post-instalación automatico",
|
||||
"fr": "Script post-installation automatisé",
|
||||
"de": "Automatisches Post-Installationsskript",
|
||||
"it": "Script post-installazione automatico",
|
||||
"pt": "Script pós-instalação automatizado"
|
||||
},
|
||||
"Customizable post-installation script": {
|
||||
"es": "Script post-instalación personalizable",
|
||||
"fr": "Script post-installation personnalisable",
|
||||
"de": "Anpassbares Post-Installationsskript",
|
||||
"it": "Script post-installazione personalizzabile",
|
||||
"pt": "Script pós-instalação personalizável"
|
||||
},
|
||||
"Uninstall optimizations": {
|
||||
"es": "Desinstalar optimizaciones",
|
||||
"fr": "Désinstaller les optimisations",
|
||||
"de": "Optimierungen deinstallieren",
|
||||
"it": "Disinstalla ottimizzazioni",
|
||||
"pt": "Desinstalar otimizações"
|
||||
},
|
||||
"Community Scripts": {
|
||||
"es": "Scripts comunitarios",
|
||||
"fr": "Scripts communautaires",
|
||||
"de": "Community-Skripte",
|
||||
"it": "Script della comunità",
|
||||
"pt": "Scripts da comunidade"
|
||||
},
|
||||
"Xshok-proxmox Post install": {
|
||||
"es": "Pos-tinstalación Xshok-Proxmox",
|
||||
"fr": "Post-installation Xshok-Proxmox",
|
||||
"de": "Xshok-Proxmox-Post-Installation",
|
||||
"it": "Post-installazione Xshok-Proxmox",
|
||||
"pt": "Pós-instalação Xshok-Proxmox"
|
||||
},
|
||||
"Post-Installation Scripts": {
|
||||
"es": "Scripts de post-instalación",
|
||||
"fr": "Scripts de post-installation",
|
||||
"de": "Post-Installationsskripte",
|
||||
"it": "Script di post-installazione",
|
||||
"pt": "Scripts de pós-instalação"
|
||||
},
|
||||
"Select a post-installation script:": {
|
||||
"es": "Seleccione un script de post-instalación:",
|
||||
"fr": "Sélectionnez un script de post-installation :",
|
||||
"de": "Wählen Sie ein Post-Installationsskript aus:",
|
||||
"it": "Seleziona uno script di post-installazione:",
|
||||
"pt": "Selecione um script de pós-instalação:"
|
||||
},
|
||||
"The automated post-install script performs the following system adjustments": {
|
||||
"es": "El script automatizado de postinstalación realiza los siguientes ajustes en el sistema",
|
||||
"fr": "Le script automatisé de post-installation effectue les ajustements système suivants",
|
||||
"de": "Das automatische Post-Installationsskript führt folgende Systemanpassungen durch",
|
||||
"it": "Lo script automatico di post-installazione esegue i seguenti aggiustamenti di sistema",
|
||||
"pt": "O script automatizado de pós-instalação executa os seguintes ajustes no sistema"
|
||||
},
|
||||
"Configure": {
|
||||
"es": "Configurar",
|
||||
"fr": "Configurer",
|
||||
"de": "Konfigurieren",
|
||||
"it": "Configurare",
|
||||
"pt": "Configurar"
|
||||
},
|
||||
"and upgrade system": {
|
||||
"es": "y actualizar el sistema",
|
||||
"fr": "et mettre à niveau le système",
|
||||
"de": "und das System aktualisieren",
|
||||
"it": "e aggiornare il sistema",
|
||||
"pt": "e atualizar o sistema"
|
||||
},
|
||||
"Remove": {
|
||||
"es": "Eliminar",
|
||||
"fr": "Supprimer",
|
||||
"de": "Entfernen",
|
||||
"it": "Rimuovere",
|
||||
"pt": "Remover"
|
||||
},
|
||||
"from Proxmox interface": {
|
||||
"es": "de la interfaz de Proxmox",
|
||||
"fr": "de l'interface Proxmox",
|
||||
"de": "aus der Proxmox-Oberfläche",
|
||||
"it": "dall'interfaccia Proxmox",
|
||||
"pt": "da interface Proxmox"
|
||||
},
|
||||
"Optimize": {
|
||||
"es": "Optimizar",
|
||||
"fr": "Optimiser",
|
||||
"de": "Optimieren",
|
||||
"it": "Ottimizzare",
|
||||
"pt": "Otimizar"
|
||||
},
|
||||
"and": {
|
||||
"es": "y",
|
||||
"fr": "et",
|
||||
"de": "und",
|
||||
"it": "e",
|
||||
"pt": "e"
|
||||
},
|
||||
"Enhance": {
|
||||
"es": "Mejorar",
|
||||
"fr": "Améliorer",
|
||||
"de": "Verbessern",
|
||||
"it": "Migliorare",
|
||||
"pt": "Melhorar"
|
||||
},
|
||||
"performance and": {
|
||||
"es": "el rendimiento y",
|
||||
"fr": "les performances et",
|
||||
"de": "die Leistung und",
|
||||
"it": "le prestazioni e",
|
||||
"pt": "o desempenho e"
|
||||
},
|
||||
"Install": {
|
||||
"es": "Instalar",
|
||||
"fr": "Installer",
|
||||
"de": "Installieren",
|
||||
"it": "Installare",
|
||||
"pt": "Instalar"
|
||||
},
|
||||
"for": {
|
||||
"es": "para",
|
||||
"fr": "pour",
|
||||
"de": "für",
|
||||
"it": "per",
|
||||
"pt": "para"
|
||||
},
|
||||
"Apply": {
|
||||
"es": "Aplicar",
|
||||
"fr": "Appliquer",
|
||||
"de": "Anwenden",
|
||||
"it": "Applicare",
|
||||
"pt": "Aplicar"
|
||||
},
|
||||
"optimizations": {
|
||||
"es": "optimizaciones",
|
||||
"fr": "optimisations",
|
||||
"de": "Optimierungen",
|
||||
"it": "ottimizzazioni",
|
||||
"pt": "otimizações"
|
||||
},
|
||||
"Do you want to run this script?": {
|
||||
"es": "¿Desea ejecutar este script?",
|
||||
"fr": "Voulez-vous exécuter ce script ?",
|
||||
"de": "Möchten Sie dieses Skript ausführen?",
|
||||
"it": "Vuoi eseguire questo script?",
|
||||
"pt": "Deseja executar este script?"
|
||||
},
|
||||
"Automated post-install Script": {
|
||||
"es": "Script automatizado de post-instalación",
|
||||
"fr": "Script automatisé de post-installation",
|
||||
"de": "Automatisches Post-Installationsskript",
|
||||
"it": "Script automatico di post-installazione",
|
||||
"pt": "Script automatizado de pós-instalação"
|
||||
},
|
||||
"Cancelled by user.": {
|
||||
"es": "Cancelado por el usuario.",
|
||||
"fr": "Annulé par l'utilisateur.",
|
||||
"de": "Vom Benutzer abgebrochen.",
|
||||
"it": "Annullato dall'utente.",
|
||||
"pt": "Cancelado pelo usuário."
|
||||
},
|
||||
"No optimizations detected to uninstall.": {
|
||||
"es": "No se detectaron optimizaciones para desinstalar.",
|
||||
"fr": "Aucune optimisation détectée à désinstaller.",
|
||||
"de": "Keine zu deinstallierenden Optimierungen erkannt.",
|
||||
"it": "Nessuna ottimizzazione rilevata da disinstallare.",
|
||||
"pt": "Nenhuma otimização detectada para desinstalar."
|
||||
},
|
||||
"Uninstall Optimizations": {
|
||||
"es": "Desinstalar optimizaciones",
|
||||
"fr": "Désinstaller les optimisations",
|
||||
"de": "Optimierungen deinstallieren",
|
||||
"it": "Disinstalla ottimizzazioni",
|
||||
"pt": "Desinstalar otimizações"
|
||||
},
|
||||
"Select optimizations to uninstall:": {
|
||||
"es": "Seleccione optimizaciones para desinstalar:",
|
||||
"fr": "Sélectionnez les optimisations à désinstaller :",
|
||||
"de": "Wählen Sie die zu deinstallierenden Optimierungen aus:",
|
||||
"it": "Seleziona le ottimizzazioni da disinstallare:",
|
||||
"pt": "Selecione as otimizações para desinstalar:"
|
||||
},
|
||||
"Install and configure Log2RAM": {
|
||||
"es": "Instalar y configurar Log2Ram",
|
||||
"fr": "Installer et configurer Log2Ram",
|
||||
"de": "Log2Ram installieren und konfigurieren",
|
||||
"it": "Installare e configurare Log2Ram",
|
||||
"pt": "Instalar e configurar o Log2Ram"
|
||||
},
|
||||
"Select System Type": {
|
||||
"es": "Seleccionar tipo de sistema",
|
||||
"fr": "Sélectionner le type de système",
|
||||
"de": "Systemtyp auswählen",
|
||||
"it": "Seleziona il tipo di sistema",
|
||||
"pt": "Selecionar o tipo de sistema"
|
||||
},
|
||||
"Choose the type of virtual system to install:": {
|
||||
"es": "Elija el tipo de sistema virtual a instalar:",
|
||||
"fr": "Choisissez le type de système virtuel à installer :",
|
||||
"de": "Wählen Sie den zu installierenden virtuellen Systemtyp:",
|
||||
"it": "Scegli il tipo di sistema virtuale da installare:",
|
||||
"pt": "Escolha o tipo de sistema virtual a instalar:"
|
||||
},
|
||||
"Create": {
|
||||
"es": "Crear",
|
||||
"fr": "Créer",
|
||||
"de": "Erstellen",
|
||||
"it": "Crea",
|
||||
"pt": "Criar"
|
||||
},
|
||||
"Other Prebuilt Linux VMs": {
|
||||
"es": "Otras máquinas virtuales Linux preinstaladas",
|
||||
"fr": "Autres machines virtuelles Linux préinstallées",
|
||||
"de": "Weitere vorgefertigte Linux-VMs",
|
||||
"it": "Altre VM Linux preconfigurate",
|
||||
"pt": "Outras VMs Linux pré-construídas"
|
||||
},
|
||||
"Select one of the ready-to-run Linux VMs:": {
|
||||
"es": "Seleccione una de las máquinas virtuales Linux listas para usar:",
|
||||
"fr": "Sélectionnez l'une des machines virtuelles Linux prêtes à l'emploi :",
|
||||
"de": "Wählen Sie eine der einsatzbereiten Linux-VMs aus:",
|
||||
"it": "Seleziona una delle VM Linux pronte all'uso:",
|
||||
"pt": "Selecione uma das VMs Linux prontas para usar:"
|
||||
},
|
||||
"Disk and Storage Manager Menu": {
|
||||
"es": "Menú de gestión de discos y almacenamiento",
|
||||
"fr": "Menu du gestionnaire de disques et de stockage",
|
||||
"de": "Menü für Festplatten- und Speicherverwaltung",
|
||||
"it": "Menu di gestione disco e storage",
|
||||
"pt": "Menu de gerenciamento de disco e armazenamento"
|
||||
},
|
||||
"Add Disk": {
|
||||
"es": "Agregar disco",
|
||||
"fr": "Ajouter un disque",
|
||||
"de": "Festplatte hinzufügen",
|
||||
"it": "Aggiungi disco",
|
||||
"pt": "Adicionar disco"
|
||||
},
|
||||
"to a VM": {
|
||||
"es": "a una VM",
|
||||
"fr": "à une VM",
|
||||
"de": "zu einer VM",
|
||||
"it": "a una VM",
|
||||
"pt": "a uma VM"
|
||||
},
|
||||
"to a LXC": {
|
||||
"es": "a un LXC",
|
||||
"fr": "à un LXC",
|
||||
"de": "zu einem LXC",
|
||||
"it": "a un LXC",
|
||||
"pt": "a um LXC"
|
||||
},
|
||||
"Installation type:": {
|
||||
"es": "Tipo de instalación:",
|
||||
"fr": "Type d'installation :",
|
||||
"de": "Installationstyp:",
|
||||
"it": "Tipo di installazione:",
|
||||
"pt": "Tipo de instalação:"
|
||||
},
|
||||
"Translation Version (Multi-language support)": {
|
||||
"es": "Versión de traducción (soporte multilenguaje)",
|
||||
"fr": "Version traduction (prise en charge multilingue)",
|
||||
"de": "Übersetzungsversion (Mehrsprachige Unterstützung)",
|
||||
"it": "Versione traduzione (supporto multilingue)",
|
||||
"pt": "Versão de tradução (suporte multilíngue)"
|
||||
},
|
||||
"This script will apply the following optimizations and advanced adjustments to your Proxmox VE server": {
|
||||
"es": "Este script aplicará las siguientes optimizaciones y ajustes avanzados a su servidor Proxmox VE",
|
||||
"fr": "Ce script appliquera les optimisations et ajustements avancés suivants à votre serveur Proxmox VE",
|
||||
"de": "Dieses Skript wendet die folgenden Optimierungen und erweiterten Anpassungen auf Ihren Proxmox VE-Server an",
|
||||
"it": "Questo script applicherà le seguenti ottimizzazioni e regolazioni avanzate al tuo server Proxmox VE",
|
||||
"pt": "Este script aplicará as seguintes otimizações e ajustes avançados ao seu servidor Proxmox VE"
|
||||
},
|
||||
"and upgrade the system (disables the enterprise repo)": {
|
||||
"es": "y actualiza el sistema (desactiva el repositorio enterprise).",
|
||||
"fr": "et mettra à niveau le système (désactive le dépôt entreprise)",
|
||||
"de": "und aktualisiert das System (deaktiviert das Enterprise-Repository)",
|
||||
"it": "e aggiornerà il sistema (disabilita il repository enterprise)",
|
||||
"pt": "e atualizará o sistema (desativa o repositório enterprise)"
|
||||
},
|
||||
"Optionally remove": {
|
||||
"es": "Opcionalmente eliminara",
|
||||
"fr": "Supprimer en option",
|
||||
"de": "Optional entfernen",
|
||||
"it": "Rimuovi opzionalmente",
|
||||
"pt": "Remover opcionalmente"
|
||||
},
|
||||
"from Proxmox web interface (you will be asked)": {
|
||||
"es": "de la interfaz web de Proxmox.",
|
||||
"fr": "de l'interface web Proxmox.",
|
||||
"de": "aus der Proxmox-Weboberfläche.",
|
||||
"it": "dall'interfaccia web di Proxmox.",
|
||||
"pt": "da interface web do Proxmox."
|
||||
},
|
||||
"for better performance and stability": {
|
||||
"es": "para un mejor rendimiento y estabilidad",
|
||||
"fr": "pour de meilleures performances et stabilité",
|
||||
"de": "für bessere Leistung und Stabilität",
|
||||
"it": "per migliori prestazioni e stabilità",
|
||||
"pt": "para melhor desempenho e estabilidade"
|
||||
},
|
||||
"Install and configure": {
|
||||
"es": "Instala y configura",
|
||||
"fr": "Installer et configurer",
|
||||
"de": "Installieren und konfigurieren",
|
||||
"it": "Installare e configurare",
|
||||
"pt": "Instalar e configurar"
|
||||
},
|
||||
"(only on SSD/NVMe) to protect your disk": {
|
||||
"es": "(solo para SSD/NVMe) para proteger su disco",
|
||||
"fr": "(uniquement sur SSD/NVMe) pour protéger votre disque",
|
||||
"de": "(nur auf SSD/NVMe) zum Schutz Ihrer Festplatte",
|
||||
"it": "(solo su SSD/NVMe) per proteggere il tuo disco",
|
||||
"pt": "(apenas em SSD/NVMe) para proteger o seu disco"
|
||||
},
|
||||
"Improve log rotation and limit log size to save space and extend disk life": {
|
||||
"es": "Mejora la rotación de logs y limita el tamaño para ahorrar espacio y prolongar la vida útil del disco",
|
||||
"fr": "Améliorer la rotation des logs et limiter leur taille pour économiser de l'espace et prolonger la durée de vie du disque",
|
||||
"de": "Verbessern Sie die Logrotation und begrenzen Sie die Loggröße, um Speicherplatz zu sparen und die Lebensdauer der Festplatte zu verlängern",
|
||||
"it": "Migliora la rotazione dei log e limita la dimensione per risparmiare spazio e prolungare la vita del disco",
|
||||
"pt": "Melhorar a rotação de logs e limitar o tamanho para economizar espaço e prolongar a vida útil do disco"
|
||||
},
|
||||
"Increase file and process limits for advanced workloads": {
|
||||
"es": "Aumenta los límites de archivos y procesos para cargas avanzadas",
|
||||
"fr": "Augmenter les limites de fichiers et de processus pour les charges de travail avancées",
|
||||
"de": "Datei- und Prozesslimits für anspruchsvolle Workloads erhöhen",
|
||||
"it": "Aumentare i limiti di file e processi per carichi di lavoro avanzati",
|
||||
"pt": "Aumentar os limites de arquivos e processos para cargas de trabalho avançadas"
|
||||
},
|
||||
"Set up time synchronization and entropy generation": {
|
||||
"es": "Configura la sincronización horaria y la generación de entropía",
|
||||
"fr": "Configurer la synchronisation de l'heure et la génération d'entropie",
|
||||
"de": "Zeitsynchronisation und Entropieerzeugung einrichten",
|
||||
"it": "Configurare la sincronizzazione dell'orario e la generazione di entropia",
|
||||
"pt": "Configurar a sincronização do tempo e a geração de entropia"
|
||||
},
|
||||
"Add color prompts and useful aliases to the terminal environment": {
|
||||
"es": "Añade avisos de color y alias útiles al entorno de terminal",
|
||||
"fr": "Ajouter des invites colorées et des alias utiles à l'environnement terminal",
|
||||
"de": "Farbige Prompts und nützliche Aliase zur Terminalumgebung hinzufügen",
|
||||
"it": "Aggiungere prompt colorati e alias utili all'ambiente terminale",
|
||||
"pt": "Adicionar prompts coloridos e aliases úteis ao ambiente do terminal"
|
||||
},
|
||||
"All changes are reversible using the ProxMenux uninstaller.": {
|
||||
"es": "Todos los cambios son reversibles usando el desinstalador de ProxMenux.",
|
||||
"fr": "Toutes les modifications sont réversibles avec le désinstalleur de ProxMenux.",
|
||||
"de": "Alle Änderungen sind mit dem ProxMenux-Deinstallationsprogramm reversibel.",
|
||||
"it": "Tutte le modifiche sono reversibili utilizzando il programma di disinstallazione di ProxMenux.",
|
||||
"pt": "Todas as alterações são reversíveis usando o desinstalador do ProxMenux."
|
||||
},
|
||||
"Do you want to apply these optimizations now?": {
|
||||
"es": "¿Desea aplicar estas optimizaciones ahora?",
|
||||
"fr": "Voulez-vous appliquer ces optimisations maintenant ?",
|
||||
"de": "Möchten Sie diese Optimierungen jetzt anwenden?",
|
||||
"it": "Vuoi applicare queste ottimizzazioni ora?",
|
||||
"pt": "Deseja aplicar essas otimizações agora?"
|
||||
},
|
||||
"Automated Post-Install Script": {
|
||||
"es": "Script automatizado de post-instalación",
|
||||
"fr": "Script automatisé de post-installation",
|
||||
"de": "Automatisiertes Post-Installationsskript",
|
||||
"it": "Script automatico post-installazione",
|
||||
"pt": "Script automatizado pós-instalação"
|
||||
},
|
||||
"Network Management - SAFE MODE": {
|
||||
"es": "Gestión de red - Modo seguro",
|
||||
"fr": "Gestion du réseau - Mode sans risque",
|
||||
"de": "Netzwerkverwaltung - SICHERER MODUS",
|
||||
"it": "Gestione rete - Modalità sicura",
|
||||
"pt": "Gerenciamento de rede - Modo seguro"
|
||||
},
|
||||
"Show Interface Details": {
|
||||
"es": "Mostrar detalles de la interfaz",
|
||||
"fr": "Afficher les détails de l'interface",
|
||||
"de": "Schnittstellendetails anzeigen",
|
||||
"it": "Mostra dettagli dell'interfaccia",
|
||||
"pt": "Mostrar detalhes da interface"
|
||||
},
|
||||
"Show Bridge Status": {
|
||||
"es": "Mostrar estado del puente",
|
||||
"fr": "Afficher l'état du pont",
|
||||
"de": "Bridge-Status anzeigen",
|
||||
"it": "Mostra stato del bridge",
|
||||
"pt": "Mostrar status da ponte"
|
||||
},
|
||||
"Show Routing Table": {
|
||||
"es": "Mostrar tabla de enrutamiento",
|
||||
"fr": "Afficher la table de routage",
|
||||
"de": "Routing-Tabelle anzeigen",
|
||||
"it": "Mostra tabella di routing",
|
||||
"pt": "Mostrar tabela de roteamento"
|
||||
},
|
||||
"Test Connectivity": {
|
||||
"es": "Probar conectividad",
|
||||
"fr": "Tester la connectivité",
|
||||
"de": "Konnektivität testen",
|
||||
"it": "Verifica la connettività",
|
||||
"pt": "Testar conectividade"
|
||||
},
|
||||
"Advanced Diagnostics": {
|
||||
"es": "Diagnóstico avanzado",
|
||||
"fr": "Diagnostic avancé",
|
||||
"de": "Erweiterte Diagnose",
|
||||
"it": "Diagnostica avanzata",
|
||||
"pt": "Diagnóstico avançado"
|
||||
},
|
||||
"Analyze Bridge Configuration": {
|
||||
"es": "Analizar configuración del puente",
|
||||
"fr": "Analyser la configuration du pont",
|
||||
"de": "Bridge-Konfiguration analysieren",
|
||||
"it": "Analizza configurazione bridge",
|
||||
"pt": "Analisar configuração da ponte"
|
||||
},
|
||||
"Analyze Network Configuration": {
|
||||
"es": "Analizar configuración de red",
|
||||
"fr": "Analyser la configuration du réseau",
|
||||
"de": "Netzwerkkonfiguration analysieren",
|
||||
"it": "Analizza configurazione di rete",
|
||||
"pt": "Analisar configuração de rede"
|
||||
},
|
||||
"Restart Network Service": {
|
||||
"es": "Reiniciar servicio de red",
|
||||
"fr": "Redémarrer le service réseau",
|
||||
"de": "Netzwerkdienst neu starten",
|
||||
"it": "Riavvia servizio di rete",
|
||||
"pt": "Reiniciar serviço de rede"
|
||||
},
|
||||
"Show Network Config File": {
|
||||
"es": "Mostrar archivo de configuración de red",
|
||||
"fr": "Afficher le fichier de configuration réseau",
|
||||
"de": "Netzwerkkonfigurationsdatei anzeigen",
|
||||
"it": "Mostra file di configurazione di rete",
|
||||
"pt": "Mostrar arquivo de configuração de rede"
|
||||
},
|
||||
"Create Network Backup": {
|
||||
"es": "Crear copia de seguridad de red",
|
||||
"fr": "Créer une sauvegarde du réseau",
|
||||
"de": "Netzwerk-Backup erstellen",
|
||||
"it": "Crea backup della rete",
|
||||
"pt": "Criar backup de rede"
|
||||
},
|
||||
"Restore Network Backup": {
|
||||
"es": "Restaurar copia de seguridad de red",
|
||||
"fr": "Restaurer la sauvegarde du réseau",
|
||||
"de": "Netzwerk-Backup wiederherstellen",
|
||||
"it": "Ripristina backup della rete",
|
||||
"pt": "Restaurar backup de rede"
|
||||
},
|
||||
"Analyzing Bridge Configuration - READ ONLY MODE": {
|
||||
"es": "Analizando configuración del puente - Solo lectura",
|
||||
"fr": "Analyse de la configuration du pont - Mode lecture seule",
|
||||
"de": "Bridge-Konfiguration analysieren - Nur-Lese-Modus",
|
||||
"it": "Analisi configurazione bridge - Solo lettura",
|
||||
"pt": "Analisando configuração da ponte - Modo somente leitura"
|
||||
},
|
||||
"BRIDGE CONFIGURATION ANALYSIS": {
|
||||
"es": "Análisis de configuración del puente",
|
||||
"fr": "Analyse de la configuration du pont",
|
||||
"de": "Bridge-Konfigurationsanalyse",
|
||||
"it": "Analisi configurazione bridge",
|
||||
"pt": "Análise de configuração da ponte"
|
||||
},
|
||||
"Bridge": {
|
||||
"es": "Puente",
|
||||
"fr": "Pont",
|
||||
"de": "Bridge",
|
||||
"it": "Bridge",
|
||||
"pt": "Ponte"
|
||||
},
|
||||
"Status": {
|
||||
"es": "Estado",
|
||||
"fr": "Statut",
|
||||
"de": "Status",
|
||||
"it": "Stato",
|
||||
"pt": "Status"
|
||||
},
|
||||
"IP": {
|
||||
"es": "IP",
|
||||
"fr": "IP",
|
||||
"de": "IP",
|
||||
"it": "IP",
|
||||
"pt": "IP"
|
||||
},
|
||||
"Configured Ports": {
|
||||
"es": "Puertos configurados",
|
||||
"fr": "Ports configurés",
|
||||
"de": "Konfigurierte Ports",
|
||||
"it": "Porte configurate",
|
||||
"pt": "Portas configuradas"
|
||||
},
|
||||
"Port": {
|
||||
"es": "Puerto",
|
||||
"fr": "Port",
|
||||
"de": "Port",
|
||||
"it": "Porta",
|
||||
"pt": "Porta"
|
||||
},
|
||||
"EXISTS": {
|
||||
"es": "Existe",
|
||||
"fr": "Existe",
|
||||
"de": "Existiert",
|
||||
"it": "Esiste",
|
||||
"pt": "Existe"
|
||||
},
|
||||
"ANALYSIS SUMMARY": {
|
||||
"es": "Resumen del análisis",
|
||||
"fr": "Résumé de l'analyse",
|
||||
"de": "Analyse-Zusammenfassung",
|
||||
"it": "Riepilogo analisi",
|
||||
"pt": "Resumo da análise"
|
||||
},
|
||||
"Bridges analyzed": {
|
||||
"es": "Puentes analizados",
|
||||
"fr": "Ponts analysés",
|
||||
"de": "Analysierte Bridges",
|
||||
"it": "Bridge analizzati",
|
||||
"pt": "Pontes analisadas"
|
||||
},
|
||||
"Issues found": {
|
||||
"es": "Problemas encontrados",
|
||||
"fr": "Problèmes trouvés",
|
||||
"de": "Gefundene Probleme",
|
||||
"it": "Problemi trovati",
|
||||
"pt": "Problemas encontrados"
|
||||
},
|
||||
"Physical interfaces available": {
|
||||
"es": "Interfaces físicas disponibles",
|
||||
"fr": "Interfaces physiques disponibles",
|
||||
"de": "Verfügbare physische Schnittstellen",
|
||||
"it": "Interfacce fisiche disponibili",
|
||||
"pt": "Interfaces físicas disponíveis"
|
||||
},
|
||||
"No bridge configuration issues found": {
|
||||
"es": "No se encontraron problemas en la configuración del puente",
|
||||
"fr": "Aucun problème de configuration du pont trouvé",
|
||||
"de": "Keine Bridge-Konfigurationsprobleme gefunden",
|
||||
"it": "Nessun problema di configurazione del bridge trovato",
|
||||
"pt": "Nenhum problema encontrado na configuração da ponte"
|
||||
},
|
||||
"Bridge Configuration Analysis": {
|
||||
"es": "Análisis de configuración del puente",
|
||||
"fr": "Analyse de la configuration du pont",
|
||||
"de": "Bridge-Konfigurationsanalyse",
|
||||
"it": "Analisi configurazione bridge",
|
||||
"pt": "Análise de configuração da ponte"
|
||||
},
|
||||
"Real-time network usage (iftop)": {
|
||||
"es": "Uso de red en tiempo real (iftop)",
|
||||
"fr": "Utilisation du réseau en temps réel (iftop)",
|
||||
"de": "Echtzeit-Netzwerkauslastung (iftop)",
|
||||
"it": "Utilizzo rete in tempo reale (iftop)",
|
||||
"pt": "Uso de rede em tempo real (iftop)"
|
||||
},
|
||||
"Network monitoring tool (iptraf-ng)": {
|
||||
"es": "Herramienta de monitoreo de red (iptraf-ng)",
|
||||
"fr": "Outil de surveillance réseau (iptraf-ng)",
|
||||
"de": "Netzwerküberwachungstool (iptraf-ng)",
|
||||
"it": "Strumento di monitoraggio rete (iptraf-ng)",
|
||||
"pt": "Ferramenta de monitoramento de rede (iptraf-ng)"
|
||||
},
|
||||
"iftop usage": {
|
||||
"es": "Uso de iftop",
|
||||
"fr": "Utilisation de iftop",
|
||||
"de": "Verwendung von iftop",
|
||||
"it": "Utilizzo di iftop",
|
||||
"pt": "Uso do iftop"
|
||||
},
|
||||
"To exit iftop, press q": {
|
||||
"es": "Para salir de iftop, pulse q",
|
||||
"fr": "Pour quitter iftop, appuyez sur q",
|
||||
"de": "Zum Beenden von iftop, q drücken",
|
||||
"it": "Per uscire da iftop, premi q",
|
||||
"pt": "Para sair do iftop, pressione q"
|
||||
},
|
||||
"iptraf-ng usage": {
|
||||
"es": "Uso de iptraf-ng",
|
||||
"fr": "Utilisation de iptraf-ng",
|
||||
"de": "Verwendung von iptraf-ng",
|
||||
"it": "Utilizzo di iptraf-ng",
|
||||
"pt": "Uso do iptraf-ng"
|
||||
},
|
||||
"To exit iptraf-ng, press x": {
|
||||
"es": "Para salir de iptraf-ng, pulse x",
|
||||
"fr": "Pour quitter iptraf-ng, appuyez sur x",
|
||||
"de": "Zum Beenden von iptraf-ng, x drücken",
|
||||
"it": "Per uscire da iptraf-ng, premi x",
|
||||
"pt": "Para sair do iptraf-ng, pressione x"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.1
|
||||
# Last Updated: 04/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script serves as the main entry point for ProxMenux,
|
||||
@@ -29,6 +29,7 @@
|
||||
# for managing Proxmox VE using ProxMenux.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
@@ -41,78 +42,37 @@ VENV_PATH="/opt/googletrans-env"
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
# ==========================================================
|
||||
|
||||
#show_proxmenux_logo
|
||||
|
||||
|
||||
|
||||
# Initialize language configuration
|
||||
initialize_config() {
|
||||
show_proxmenux_logo
|
||||
# Check if config file exists and has language field
|
||||
if [ ! -f "$CONFIG_FILE" ] || [ -z "$(jq -r '.language // empty' "$CONFIG_FILE")" ]; then
|
||||
LANGUAGE=$(whiptail --title "$(translate "Select Language")" --menu "$(translate "Choose a language for the menu:")" 20 60 12 \
|
||||
"en" "$(translate "English (Recommended)")" \
|
||||
"es" "$(translate "Spanish")" \
|
||||
"fr" "$(translate "French")" \
|
||||
"de" "$(translate "German")" \
|
||||
"it" "$(translate "Italian")" \
|
||||
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$LANGUAGE" ]; then
|
||||
msg_error "$(translate "No language selected. Exiting.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
# Update existing config file with new language
|
||||
tmp=$(mktemp)
|
||||
jq --arg lang "$LANGUAGE" '. + {language: $lang}' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
||||
else
|
||||
# Create new config file if it doesn't exist
|
||||
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Initial language set to:") $LANGUAGE"
|
||||
fi
|
||||
}
|
||||
|
||||
# =========================================================
|
||||
|
||||
check_updates() {
|
||||
local INSTALL_SCRIPT="$BASE_DIR/install_proxmenux.sh"
|
||||
|
||||
# Fetch the remote version
|
||||
local INSTALL_SCRIPT="$BASE_DIR/install_proxmenux.sh"
|
||||
|
||||
local REMOTE_VERSION
|
||||
REMOTE_VERSION=$(curl -fsSL "$REPO_URL/version.txt" | head -n 1)
|
||||
|
||||
|
||||
# Exit silently if unable to fetch the remote version
|
||||
if [ -z "$REMOTE_VERSION" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
# Read the local version
|
||||
local LOCAL_VERSION
|
||||
LOCAL_VERSION=$(head -n 1 "$LOCAL_VERSION_FILE")
|
||||
|
||||
|
||||
# If the local version matches the remote version, no update is needed
|
||||
[ "$LOCAL_VERSION" = "$REMOTE_VERSION" ] && return 0
|
||||
|
||||
|
||||
# Prompt the user for update confirmation
|
||||
if whiptail --title "$(translate "Update Available")" \
|
||||
--yesno "$(translate "New version available") ($REMOTE_VERSION)\n\n$(translate "Do you want to update now?")" \
|
||||
10 60 --defaultno; then
|
||||
|
||||
|
||||
msg_warn "$(translate "Starting ProxMenu update...")"
|
||||
|
||||
# Download the installation script
|
||||
if wget -qO "$INSTALL_SCRIPT" "$REPO_URL/install_proxmenux.sh"; then
|
||||
chmod +x "$INSTALL_SCRIPT"
|
||||
|
||||
# Execute the script directly in the current environment
|
||||
source "$INSTALL_SCRIPT"
|
||||
|
||||
fi
|
||||
else
|
||||
msg_warn "$(translate "Update postponed. You can update later from the menu.")"
|
||||
@@ -126,8 +86,6 @@ main_menu() {
|
||||
}
|
||||
|
||||
|
||||
# Main flow
|
||||
initialize_config
|
||||
load_language
|
||||
initialize_cache
|
||||
check_updates
|
||||
|
||||
@@ -0,0 +1,854 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenux - Complete Post-Installation Script with Registration
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 06/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
#
|
||||
# The script performs system optimizations including:
|
||||
# - Repository configuration and system upgrades
|
||||
# - Subscription banner removal and UI enhancements
|
||||
# - Advanced memory management and kernel optimizations
|
||||
# - Network stack tuning and security hardening
|
||||
# - Storage optimizations including log2ram for SSD protection
|
||||
# - System limits increases and entropy generation improvements
|
||||
# - Journald and logrotate optimizations for better log management
|
||||
# - Security enhancements including RPC disabling and time synchronization
|
||||
# - Bash environment customization and system monitoring setup
|
||||
#
|
||||
# Key Features:
|
||||
# - Zero-interaction automation: Runs completely unattended
|
||||
# - Intelligent hardware detection: Automatically detects SSD/NVMe for log2ram
|
||||
# - RAM-aware configurations: Adjusts settings based on available system memory
|
||||
# - Comprehensive error handling: Robust installation with fallback mechanisms
|
||||
# - Registration system: Tracks installed optimizations for easy management
|
||||
# - Reboot management: Intelligently handles reboot requirements
|
||||
# - Translation support: Multi-language compatible through ProxMenux framework
|
||||
# - Rollback compatibility: All optimizations can be reversed using the uninstall script
|
||||
#
|
||||
# This script is based on the post-install script cutotomizable
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Global variables
|
||||
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) / 1024 / 1000))
|
||||
NECESSARY_REBOOT=0
|
||||
SCRIPT_TITLE="Customizable post-installation optimization script"
|
||||
|
||||
# ==========================================================
|
||||
# Tool registration system
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
lvm_repair_check() {
|
||||
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
|
||||
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header")
|
||||
if [ -z "$pvs_output" ]; then
|
||||
msg_ok "$(translate "No PVs with old headers found.")"
|
||||
register_tool "lvm_repair" true
|
||||
return
|
||||
fi
|
||||
|
||||
declare -A vg_map
|
||||
while read -r line; do
|
||||
pv=$(echo "$line" | grep -o '/dev/[^ ]*')
|
||||
vg=$(pvs -o vg_name --noheadings "$pv" | awk '{print $1}')
|
||||
if [ -n "$vg" ]; then
|
||||
vg_map["$vg"]=1
|
||||
fi
|
||||
done <<< "$pvs_output"
|
||||
|
||||
for vg in "${!vg_map[@]}"; do
|
||||
msg_warn "$(translate "Old PV header(s) found in VG $vg. Updating metadata...")"
|
||||
vgck --updatemetadata "$vg"
|
||||
vgchange -ay "$vg"
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_warn "$(translate "Metadata update failed for VG $vg. Review manually.")"
|
||||
else
|
||||
msg_ok "$(translate "Metadata updated successfully for VG $vg")"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate "LVM PV headers check completed")"
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
cleanup_duplicate_repos() {
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
local temp_file=$(mktemp)
|
||||
local cleaned_count=0
|
||||
declare -A seen_repos
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^deb ]]; then
|
||||
read -r _ url dist components <<< "$line"
|
||||
local key="${url}_${dist}"
|
||||
if [[ -v "seen_repos[$key]" ]]; then
|
||||
echo "# $line" >> "$temp_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
seen_repos[$key]="$components"
|
||||
fi
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$sources_file"
|
||||
|
||||
mv "$temp_file" "$sources_file"
|
||||
chmod 644 "$sources_file"
|
||||
|
||||
|
||||
local pve_files=(/etc/apt/sources.list.d/*proxmox*.list /etc/apt/sources.list.d/*pve*.list)
|
||||
local pve_content="deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription"
|
||||
local pve_public_repo="/etc/apt/sources.list.d/pve-public-repo.list"
|
||||
local pve_public_repo_exists=false
|
||||
|
||||
if [ -f "$pve_public_repo" ] && grep -q "^deb.*pve-no-subscription" "$pve_public_repo"; then
|
||||
pve_public_repo_exists=true
|
||||
fi
|
||||
|
||||
for file in "${pve_files[@]}"; do
|
||||
if [ -f "$file" ] && grep -q "^deb.*pve-no-subscription" "$file"; then
|
||||
if ! $pve_public_repo_exists && [[ "$file" == "$pve_public_repo" ]]; then
|
||||
sed -i 's/^# *deb/deb/' "$file"
|
||||
pve_public_repo_exists=true
|
||||
elif [[ "$file" != "$pve_public_repo" ]]; then
|
||||
sed -i 's/^deb/# deb/' "$file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
apt update
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
apt_upgrade() {
|
||||
|
||||
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
|
||||
msg_info "$(translate "Disabling enterprise Proxmox repository...")"
|
||||
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
|
||||
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
|
||||
fi
|
||||
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
|
||||
msg_info "$(translate "Disabling enterprise Proxmox Ceph repository...")"
|
||||
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
|
||||
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
|
||||
msg_info "$(translate "Enabling free public Proxmox repository...")"
|
||||
echo "deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
|
||||
msg_ok "$(translate "Free public Proxmox repository enabled")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
sources_file="/etc/apt/sources.list"
|
||||
need_update=false
|
||||
|
||||
|
||||
sed -i 's|ftp.es.debian.org|deb.debian.org|g' "$sources_file"
|
||||
|
||||
|
||||
if grep -q "^deb http://security.debian.org ${OS_CODENAME}-security main contrib" "$sources_file"; then
|
||||
sed -i "s|^deb http://security.debian.org ${OS_CODENAME}-security main contrib|deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware|" "$sources_file"
|
||||
msg_ok "$(translate "Replaced security repository with full version")"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
|
||||
if ! grep -q "deb http://security.debian.org/debian-security ${OS_CODENAME}-security" "$sources_file"; then
|
||||
echo "deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
|
||||
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME} " "$sources_file"; then
|
||||
echo "deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
|
||||
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME}-updates" "$sources_file"; then
|
||||
echo "deb http://deb.debian.org/debian ${OS_CODENAME}-updates main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Debian repositories configured correctly")"
|
||||
|
||||
# ===================================================
|
||||
|
||||
|
||||
if [ ! -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf ]; then
|
||||
msg_info "$(translate "Disabling non-free firmware warnings...")"
|
||||
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > /etc/apt/apt.conf.d/no-bookworm-firmware.conf
|
||||
msg_ok "$(translate "Non-free firmware warnings disabled")"
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Updating package lists...")"
|
||||
if apt-get update > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Removing conflicting utilities...")"
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Conflicting utilities removed")"
|
||||
else
|
||||
msg_error "$(translate "Failed to remove conflicting utilities")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Performing packages upgrade...")"
|
||||
apt-get install pv -y > /dev/null 2>&1
|
||||
total_packages=$(apt-get -s dist-upgrade | grep "^Inst" | wc -l)
|
||||
|
||||
if [ "$total_packages" -eq 0 ]; then
|
||||
total_packages=1
|
||||
fi
|
||||
msg_ok "$(translate "Packages upgrade successfull")"
|
||||
tput civis
|
||||
tput sc
|
||||
|
||||
|
||||
(
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' dist-upgrade 2>&1 | \
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ]]; then
|
||||
|
||||
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ ]+).*/\2/')
|
||||
|
||||
|
||||
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
|
||||
|
||||
|
||||
tput rc
|
||||
tput ed
|
||||
|
||||
|
||||
row=$(( $(tput lines) - 6 ))
|
||||
tput cup $row 0; echo "$(translate "Installing packages...")"
|
||||
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
|
||||
tput cup $((row + 2)) 0; echo "Package: $package_name"
|
||||
tput cup $((row + 3)) 0; echo "Progress: [ ] 0%"
|
||||
tput cup $((row + 4)) 0; echo "──────────────────────────────────────────────"
|
||||
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
progress=$((i * 10))
|
||||
tput cup $((row + 3)) 9
|
||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||
|
||||
done
|
||||
fi
|
||||
done
|
||||
)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
tput rc
|
||||
tput ed
|
||||
msg_ok "$(translate "System upgrade completed")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Installing additional Proxmox packages...")"
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install zfsutils-linux proxmox-backup-restore-image chrony > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Additional Proxmox packages installed")"
|
||||
else
|
||||
msg_error "$(translate "Failed to install additional Proxmox packages")"
|
||||
fi
|
||||
|
||||
lvm_repair_check
|
||||
|
||||
cleanup_duplicate_repos
|
||||
|
||||
msg_ok "$(translate "Proxmox update completed")"
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
remove_subscription_banner() {
|
||||
msg_info "$(translate "Removing Proxmox subscription nag banner...")"
|
||||
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
|
||||
if [[ ! -f "$APT_HOOK" ]]; then
|
||||
cat <<'EOF' > "$APT_HOOK"
|
||||
DPkg::Post-Invoke { "dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ $? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz; }; fi"; };
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [[ -f "$JS_FILE" ]]; then
|
||||
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
|
||||
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
|
||||
touch "$JS_FILE"
|
||||
fi
|
||||
|
||||
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Subscription nag banner removed successfully")"
|
||||
register_tool "subscription_banner" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
configure_time_sync() {
|
||||
msg_info "$(translate "Configuring system time settings...")"
|
||||
|
||||
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
|
||||
if [ -z "$this_ip" ]; then
|
||||
msg_warn "$(translate "Failed to obtain public IP address")"
|
||||
timezone="UTC"
|
||||
else
|
||||
|
||||
timezone=$(curl -s "https://ipapi.co/${this_ip}/timezone")
|
||||
if [ -z "$timezone" ]; then
|
||||
msg_warn "$(translate "Failed to determine timezone from IP address")"
|
||||
timezone="UTC"
|
||||
else
|
||||
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Enabling automatic time synchronization...")"
|
||||
if timedatectl set-ntp true; then
|
||||
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
|
||||
register_tool "time_sync" true
|
||||
else
|
||||
msg_error "$(translate "Failed to enable automatic time synchronization")"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
skip_apt_languages() {
|
||||
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
|
||||
local default_locale=""
|
||||
|
||||
if [ -f /etc/default/locale ]; then
|
||||
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
|
||||
elif [ -f /etc/environment ]; then
|
||||
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
|
||||
fi
|
||||
|
||||
default_locale="${default_locale:-en_US.UTF-8}"
|
||||
local normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
|
||||
|
||||
if ! locale -a | grep -qi "^$normalized_locale$"; then
|
||||
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
|
||||
echo "$default_locale UTF-8" >> /etc/locale.gen
|
||||
fi
|
||||
locale-gen "$default_locale" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99-disable-translations
|
||||
|
||||
msg_ok "$(translate "APT configured to skip additional languages")"
|
||||
register_tool "apt_languages" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_journald() {
|
||||
msg_info "$(translate "Limiting size and optimizing journald...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/systemd/journald.conf
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
SplitMode=none
|
||||
RateLimitInterval=0
|
||||
RateLimitIntervalSec=0
|
||||
RateLimitBurst=0
|
||||
ForwardToSyslog=no
|
||||
ForwardToWall=yes
|
||||
Seal=no
|
||||
Compress=yes
|
||||
SystemMaxUse=64M
|
||||
RuntimeMaxUse=60M
|
||||
MaxLevelStore=warning
|
||||
MaxLevelSyslog=warning
|
||||
MaxLevelKMsg=warning
|
||||
MaxLevelConsole=notice
|
||||
MaxLevelWall=crit
|
||||
EOF
|
||||
|
||||
systemctl restart systemd-journald.service > /dev/null 2>&1
|
||||
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
|
||||
journalctl --rotate > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Journald optimized - Max size: 64M")"
|
||||
register_tool "journald" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_logrotate() {
|
||||
msg_info "$(translate "Optimizing logrotate configuration...")"
|
||||
local logrotate_conf="/etc/logrotate.conf"
|
||||
local backup_conf="${logrotate_conf}.bak"
|
||||
|
||||
if ! grep -q "# ProxMenux optimized configuration" "$logrotate_conf"; then
|
||||
cp "$logrotate_conf" "$backup_conf"
|
||||
cat <<EOF > "$logrotate_conf"
|
||||
# ProxMenux optimized configuration
|
||||
daily
|
||||
su root adm
|
||||
rotate 7
|
||||
create
|
||||
compress
|
||||
size=10M
|
||||
delaycompress
|
||||
copytruncate
|
||||
include /etc/logrotate.d
|
||||
EOF
|
||||
systemctl restart logrotate > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Logrotate optimization completed")"
|
||||
register_tool "logrotate" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
increase_system_limits() {
|
||||
msg_info "$(translate "Increasing various system limits...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-maxwatches.conf << EOF
|
||||
# ProxMenux configuration
|
||||
fs.inotify.max_user_watches = 1048576
|
||||
fs.inotify.max_user_instances = 1048576
|
||||
fs.inotify.max_queued_events = 1048576
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/security/limits.d/99-limits.conf << EOF
|
||||
# ProxMenux configuration
|
||||
* soft nproc 1048576
|
||||
* hard nproc 1048576
|
||||
* soft nofile 1048576
|
||||
* hard nofile 1048576
|
||||
root soft nproc unlimited
|
||||
root hard nproc unlimited
|
||||
root soft nofile unlimited
|
||||
root hard nofile unlimited
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-maxkeys.conf << EOF
|
||||
# ProxMenux configuration
|
||||
kernel.keys.root_maxkeys=1000000
|
||||
kernel.keys.maxkeys=1000000
|
||||
EOF
|
||||
|
||||
|
||||
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
|
||||
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
|
||||
echo "DefaultLimitNOFILE=256000" >> "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
|
||||
if ! grep -q "^session required pam_limits.so" "$file"; then
|
||||
echo 'session required pam_limits.so' >> "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if ! grep -q "ulimit -n 256000" /root/.profile; then
|
||||
echo "ulimit -n 256000" >> /root/.profile
|
||||
fi
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-swap.conf << EOF
|
||||
# ProxMenux configuration
|
||||
vm.swappiness = 10
|
||||
vm.vfs_cache_pressure = 100
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-fs.conf << EOF
|
||||
# ProxMenux configuration
|
||||
fs.nr_open = 12000000
|
||||
fs.file-max = 9223372036854775807
|
||||
fs.aio-max-nr = 1048576
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "System limits increase completed.")"
|
||||
register_tool "system_limits" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
configure_entropy() {
|
||||
msg_info "$(translate "Configuring entropy generation to prevent slowdowns...")"
|
||||
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install haveged > /dev/null 2>&1
|
||||
|
||||
cat <<EOF > /etc/default/haveged
|
||||
# -w sets low entropy watermark (in bits)
|
||||
DAEMON_ARGS="-w 1024"
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload > /dev/null 2>&1
|
||||
systemctl enable haveged > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Entropy generation configuration completed")"
|
||||
register_tool "entropy" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_memory_settings() {
|
||||
msg_info "$(translate "Optimizing memory settings...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-memory.conf
|
||||
# Balanced Memory Optimization
|
||||
vm.swappiness = 10
|
||||
vm.dirty_ratio = 15
|
||||
vm.dirty_background_ratio = 5
|
||||
vm.overcommit_memory = 1
|
||||
vm.max_map_count = 65530
|
||||
EOF
|
||||
|
||||
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
|
||||
echo "vm.compaction_proactiveness = 20" >> /etc/sysctl.d/99-memory.conf
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Memory optimization completed.")"
|
||||
register_tool "memory_settings" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
configure_kernel_panic() {
|
||||
msg_info "$(translate "Configuring kernel panic behavior")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-kernelpanic.conf
|
||||
# Enable restart on kernel panic, kernel oops and hardlockup
|
||||
kernel.core_pattern = /var/crash/core.%t.%p
|
||||
kernel.panic = 10
|
||||
kernel.panic_on_oops = 1
|
||||
kernel.hardlockup_panic = 1
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "Kernel panic behavior configuration completed")"
|
||||
register_tool "kernel_panic" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
force_apt_ipv4() {
|
||||
msg_info "$(translate "Configuring APT to use IPv4...")"
|
||||
|
||||
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99-force-ipv4
|
||||
|
||||
msg_ok "$(translate "APT IPv4 configuration completed")"
|
||||
register_tool "apt_ipv4" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
apply_network_optimizations() {
|
||||
msg_info "$(translate "Optimizing network settings...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-network.conf
|
||||
net.core.netdev_max_backlog=8192
|
||||
net.core.optmem_max=8192
|
||||
net.core.rmem_max=16777216
|
||||
net.core.somaxconn=8151
|
||||
net.core.wmem_max=16777216
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.all.log_martians = 0
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.all.secure_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
net.ipv4.conf.default.log_martians = 0
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.default.secure_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
net.ipv4.ip_local_port_range=1024 65535
|
||||
net.ipv4.tcp_base_mss = 1024
|
||||
net.ipv4.tcp_challenge_ack_limit = 999999999
|
||||
net.ipv4.tcp_fin_timeout=10
|
||||
net.ipv4.tcp_keepalive_intvl=30
|
||||
net.ipv4.tcp_keepalive_probes=3
|
||||
net.ipv4.tcp_keepalive_time=240
|
||||
net.ipv4.tcp_limit_output_bytes=65536
|
||||
net.ipv4.tcp_max_syn_backlog=8192
|
||||
net.ipv4.tcp_max_tw_buckets = 1440000
|
||||
net.ipv4.tcp_mtu_probing = 1
|
||||
net.ipv4.tcp_rfc1337=1
|
||||
net.ipv4.tcp_rmem=8192 87380 16777216
|
||||
net.ipv4.tcp_sack=1
|
||||
net.ipv4.tcp_slow_start_after_idle=0
|
||||
net.ipv4.tcp_syn_retries=3
|
||||
net.ipv4.tcp_synack_retries = 2
|
||||
net.ipv4.tcp_tw_recycle = 0
|
||||
net.ipv4.tcp_tw_reuse = 0
|
||||
net.ipv4.tcp_wmem=8192 65536 16777216
|
||||
net.netfilter.nf_conntrack_generic_timeout = 60
|
||||
net.netfilter.nf_conntrack_helper=0
|
||||
net.netfilter.nf_conntrack_max = 524288
|
||||
net.netfilter.nf_conntrack_tcp_timeout_established = 28800
|
||||
net.unix.max_dgram_qlen = 4096
|
||||
EOF
|
||||
|
||||
sysctl --system > /dev/null 2>&1
|
||||
|
||||
local interfaces_file="/etc/network/interfaces"
|
||||
if ! grep -q 'source /etc/network/interfaces.d/*' "$interfaces_file"; then
|
||||
echo "source /etc/network/interfaces.d/*" >> "$interfaces_file"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Network optimization completed")"
|
||||
register_tool "network_optimization" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
disable_rpc() {
|
||||
msg_info "$(translate "Disabling portmapper/rpcbind for security...")"
|
||||
|
||||
systemctl disable rpcbind > /dev/null 2>&1
|
||||
systemctl stop rpcbind > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "portmapper/rpcbind has been disabled and removed")"
|
||||
register_tool "disable_rpc" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
customize_bashrc() {
|
||||
msg_info "$(translate "Customizing bashrc for root user...")"
|
||||
local bashrc="/root/.bashrc"
|
||||
local bash_profile="/root/.bash_profile"
|
||||
|
||||
if [ ! -f "${bashrc}.bak" ]; then
|
||||
cp "$bashrc" "${bashrc}.bak"
|
||||
fi
|
||||
|
||||
|
||||
cat >> "$bashrc" << 'EOF'
|
||||
|
||||
# ProxMenux customizations
|
||||
export HISTTIMEFORMAT="%d/%m/%y %T "
|
||||
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]\W\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
|
||||
alias l='ls -CF'
|
||||
alias la='ls -A'
|
||||
alias ll='ls -alF'
|
||||
alias ls='ls --color=auto'
|
||||
alias grep='grep --color=auto'
|
||||
alias fgrep='fgrep --color=auto'
|
||||
alias egrep='egrep --color=auto'
|
||||
source /etc/profile.d/bash_completion.sh
|
||||
EOF
|
||||
|
||||
if ! grep -q "source /root/.bashrc" "$bash_profile"; then
|
||||
echo "source /root/.bashrc" >> "$bash_profile"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Bashrc customization completed")"
|
||||
register_tool "bashrc_custom" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
install_log2ram_auto() {
|
||||
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
|
||||
|
||||
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
|
||||
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null)
|
||||
SYSTEM_DISK=${SYSTEM_DISK:-sda}
|
||||
|
||||
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
|
||||
msg_ok "$(translate "System disk ($SYSTEM_DISK) is SSD or M.2. Proceeding with log2ram setup.")"
|
||||
else
|
||||
msg_warn "$(translate "System disk ($SYSTEM_DISK) is not SSD/M.2. Skipping log2ram installation.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Clean up previous state
|
||||
rm -rf /tmp/log2ram
|
||||
rm -f /etc/systemd/system/log2ram*
|
||||
rm -f /etc/systemd/system/log2ram-daily.*
|
||||
rm -f /etc/cron.d/log2ram*
|
||||
rm -f /usr/sbin/log2ram
|
||||
rm -f /etc/log2ram.conf
|
||||
rm -f /usr/local/bin/log2ram-check.sh
|
||||
rm -rf /var/log.hdd
|
||||
systemctl daemon-reexec >/dev/null 2>&1
|
||||
systemctl daemon-reload >/dev/null 2>&1
|
||||
|
||||
msg_info "$(translate "Installing log2ram from GitHub...")"
|
||||
|
||||
git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log
|
||||
cd /tmp/log2ram || return 1
|
||||
bash install.sh >>/tmp/log2ram_install.log 2>&1
|
||||
|
||||
if [[ -f /etc/log2ram.conf ]] && systemctl list-units --all | grep -q log2ram; then
|
||||
msg_ok "$(translate "log2ram installed successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to install log2ram. See /tmp/log2ram_install.log")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Detect RAM
|
||||
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
|
||||
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
|
||||
|
||||
if (( RAM_SIZE_GB <= 8 )); then
|
||||
LOG2RAM_SIZE="128M"
|
||||
CRON_HOURS=1
|
||||
elif (( RAM_SIZE_GB <= 16 )); then
|
||||
LOG2RAM_SIZE="256M"
|
||||
CRON_HOURS=3
|
||||
else
|
||||
LOG2RAM_SIZE="512M"
|
||||
CRON_HOURS=6
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "log2ram size set to:") $LOG2RAM_SIZE"
|
||||
|
||||
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
|
||||
rm -f /etc/cron.hourly/log2ram
|
||||
echo "0 */$CRON_HOURS * * * root /usr/sbin/log2ram write" > /etc/cron.d/log2ram
|
||||
msg_ok "$(translate "log2ram write scheduled every") $CRON_HOURS $(translate "hour(s)")"
|
||||
|
||||
cat << 'EOF' > /usr/local/bin/log2ram-check.sh
|
||||
#!/bin/bash
|
||||
CONF_FILE="/etc/log2ram.conf"
|
||||
LIMIT_KB=$(grep '^SIZE=' "$CONF_FILE" | cut -d'=' -f2 | tr -d 'M')000
|
||||
USED_KB=$(df /var/log --output=used | tail -1)
|
||||
THRESHOLD=$(( LIMIT_KB * 90 / 100 ))
|
||||
if (( USED_KB > THRESHOLD )); then
|
||||
/usr/sbin/log2ram write
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x /usr/local/bin/log2ram-check.sh
|
||||
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh" > /etc/cron.d/log2ram-auto-sync
|
||||
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
|
||||
|
||||
|
||||
register_tool "log2ram" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
run_complete_optimization() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "ProxMenux Optimization Post-Installation")"
|
||||
|
||||
ensure_tools_json
|
||||
|
||||
apt_upgrade
|
||||
remove_subscription_banner
|
||||
configure_time_sync
|
||||
skip_apt_languages
|
||||
optimize_journald
|
||||
optimize_logrotate
|
||||
increase_system_limits
|
||||
configure_entropy
|
||||
optimize_memory_settings
|
||||
configure_kernel_panic
|
||||
force_apt_ipv4
|
||||
apply_network_optimizations
|
||||
disable_rpc
|
||||
customize_bashrc
|
||||
install_log2ram_auto
|
||||
|
||||
|
||||
echo -e
|
||||
msg_success "$(translate "Complete post-installation optimization finished!")"
|
||||
|
||||
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
|
||||
whiptail --title "Reboot Required" \
|
||||
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
|
||||
if [[ $? -eq 0 ]]; then
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
msg_warn "$(translate "Rebooting the system...")"
|
||||
reboot
|
||||
else
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
msg_info2 "$(translate "You can reboot later manually.")"
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_success "$(translate "All changes applied. No reboot required.")"
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
clear
|
||||
}
|
||||
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
run_complete_optimization
|
||||
fi
|
||||
@@ -0,0 +1,917 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
|
||||
get_external_backup_mount_point() {
|
||||
local BACKUP_MOUNT_FILE="/usr/local/share/proxmenux/last_backup_mount.txt"
|
||||
local STORAGE_REPO="$REPO_URL/scripts/backup_restore"
|
||||
local MOUNT_POINT
|
||||
|
||||
if [[ -f "$BACKUP_MOUNT_FILE" ]]; then
|
||||
MOUNT_POINT=$(head -n1 "$BACKUP_MOUNT_FILE" | tr -d '\r\n' | xargs)
|
||||
>&2 echo "DEBUG: Valor MOUNT_POINT='$MOUNT_POINT'"
|
||||
if [[ ! -d "$MOUNT_POINT" ]]; then
|
||||
msg_error "Mount point does not exist: $MOUNT_POINT"
|
||||
rm -f "$BACKUP_MOUNT_FILE"
|
||||
return 1
|
||||
fi
|
||||
if ! mountpoint -q "$MOUNT_POINT"; then
|
||||
msg_error "Mount point is not mounted: $MOUNT_POINT"
|
||||
rm -f "$BACKUP_MOUNT_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$MOUNT_POINT"
|
||||
return 0
|
||||
else
|
||||
source <(curl -s "$STORAGE_REPO/mount_disk_host_bk.sh")
|
||||
MOUNT_POINT=$(mount_disk_host_bk)
|
||||
[[ -z "$MOUNT_POINT" ]] && msg_error "$(translate "No disk mounted.")" && return 1
|
||||
echo "$MOUNT_POINT"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# === Host Backup Main Menu ===
|
||||
host_backup_menu() {
|
||||
while true; do
|
||||
local CHOICE
|
||||
CHOICE=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate 'Host Backup')" \
|
||||
--menu "\n$(translate 'Select backup option:')" 22 70 12 \
|
||||
"" "$(translate '--- FULL BACKUP ---')" \
|
||||
1 "$(translate 'Full backup to Proxmox Backup Server (PBS)')" \
|
||||
2 "$(translate 'Full backup with BorgBackup')" \
|
||||
3 "$(translate 'Full backup to local .tar.gz')" \
|
||||
"" "$(translate '--- CUSTOM BACKUP ---')" \
|
||||
4 "$(translate 'Custom backup to PBS')" \
|
||||
5 "$(translate 'Custom backup with BorgBackup')" \
|
||||
6 "$(translate 'Custom backup to local .tar.gz')" \
|
||||
0 "$(translate 'Return')" \
|
||||
3>&1 1>&2 2>&3) || return 0
|
||||
|
||||
case "$CHOICE" in
|
||||
1) backup_full_pbs_root ;;
|
||||
2) backup_with_borg "/boot/efi /etc/pve /etc/network /var/lib/pve-cluster /root /etc/ssh /home /usr/local/bin /etc/cron.d /etc/systemd/system /var/lib/vz" ;;
|
||||
3) backup_to_local_tar "/boot/efi /etc/pve /etc/network /var/lib/pve-cluster /root /etc/ssh /home /usr/local/bin /etc/cron.d /etc/systemd/system /var/lib/vz" ;;
|
||||
4) custom_backup_menu backup_to_pbs ;;
|
||||
5) custom_backup_menu backup_with_borg ;;
|
||||
6) custom_backup_menu backup_to_local_tar ;;
|
||||
0) break ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
|
||||
# === Menu checklist for custom backup ===
|
||||
custom_backup_menu() {
|
||||
declare -A BACKUP_PATHS=(
|
||||
[etc-pve]="/etc/pve"
|
||||
[etc-network]="/etc/network"
|
||||
[var-lib-pve-cluster]="/var/lib/pve-cluster"
|
||||
[root-dir]="/root"
|
||||
[etc-ssh]="/etc/ssh"
|
||||
[home]="/home"
|
||||
[local-bin]="/usr/local/bin"
|
||||
[cron]="/etc/cron.d"
|
||||
[custom-systemd]="/etc/systemd/system"
|
||||
[var-lib-vz]="/var/lib/vz"
|
||||
)
|
||||
local CHECKLIST_OPTIONS=()
|
||||
for KEY in "${!BACKUP_PATHS[@]}"; do
|
||||
DIR="${BACKUP_PATHS[$KEY]}"
|
||||
CHECKLIST_OPTIONS+=("$KEY" "$DIR" "off")
|
||||
done
|
||||
|
||||
SELECTED_KEYS=$(dialog --separate-output --checklist \
|
||||
"$(translate 'Select directories to backup:')" 22 70 12 \
|
||||
"${CHECKLIST_OPTIONS[@]}" \
|
||||
3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
local BACKUP_DIRS=()
|
||||
for KEY in $SELECTED_KEYS; do
|
||||
BACKUP_DIRS+=("${BACKUP_PATHS[$KEY]}")
|
||||
done
|
||||
|
||||
|
||||
# "$1" "${BACKUP_DIRS[*]}"
|
||||
"$1" "${BACKUP_DIRS[@]}"
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
# === Configure PBS ===
|
||||
configure_pbs_repository() {
|
||||
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
|
||||
local PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
|
||||
local PBS_MANUAL_CONFIGS="/usr/local/share/proxmenux/pbs-manual-configs.txt"
|
||||
|
||||
|
||||
[[ ! -f "$PBS_MANUAL_CONFIGS" ]] && touch "$PBS_MANUAL_CONFIGS"
|
||||
|
||||
local PBS_CONFIGS=()
|
||||
local PBS_SOURCES=()
|
||||
|
||||
|
||||
if [[ -f "/etc/pve/storage.cfg" ]]; then
|
||||
local current_pbs="" server="" datastore="" username=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^pbs:\ (.+)$ ]]; then
|
||||
if [[ -n "$current_pbs" && -n "$server" && -n "$datastore" && -n "$username" ]]; then
|
||||
PBS_CONFIGS+=("$current_pbs|$username@$server:$datastore")
|
||||
PBS_SOURCES+=("proxmox|$current_pbs")
|
||||
fi
|
||||
current_pbs="${BASH_REMATCH[1]}"
|
||||
server="" datastore="" username=""
|
||||
elif [[ -n "$current_pbs" ]]; then
|
||||
if [[ $line =~ ^[[:space:]]*server[[:space:]]+(.+)$ ]]; then
|
||||
server="${BASH_REMATCH[1]}"
|
||||
elif [[ $line =~ ^[[:space:]]*datastore[[:space:]]+(.+)$ ]]; then
|
||||
datastore="${BASH_REMATCH[1]}"
|
||||
elif [[ $line =~ ^[[:space:]]*username[[:space:]]+(.+)$ ]]; then
|
||||
username="${BASH_REMATCH[1]}"
|
||||
elif [[ $line =~ ^[a-zA-Z]+: ]]; then
|
||||
if [[ -n "$server" && -n "$datastore" && -n "$username" ]]; then
|
||||
PBS_CONFIGS+=("$current_pbs|$username@$server:$datastore")
|
||||
PBS_SOURCES+=("proxmox|$current_pbs")
|
||||
fi
|
||||
current_pbs=""
|
||||
fi
|
||||
fi
|
||||
done < "/etc/pve/storage.cfg"
|
||||
|
||||
|
||||
if [[ -n "$current_pbs" && -n "$server" && -n "$datastore" && -n "$username" ]]; then
|
||||
PBS_CONFIGS+=("$current_pbs|$username@$server:$datastore")
|
||||
PBS_SOURCES+=("proxmox|$current_pbs")
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [[ -f "$PBS_MANUAL_CONFIGS" ]]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ -n "$line" ]]; then
|
||||
PBS_CONFIGS+=("$line")
|
||||
local name="${line%%|*}"
|
||||
PBS_SOURCES+=("manual|$name")
|
||||
fi
|
||||
done < "$PBS_MANUAL_CONFIGS"
|
||||
fi
|
||||
|
||||
|
||||
local menu_options=()
|
||||
local i=1
|
||||
|
||||
|
||||
for j in "${!PBS_CONFIGS[@]}"; do
|
||||
local config="${PBS_CONFIGS[$j]}"
|
||||
local source="${PBS_SOURCES[$j]}"
|
||||
local name="${config%%|*}"
|
||||
local repo="${config##*|}"
|
||||
local source_type="${source%%|*}"
|
||||
|
||||
|
||||
if [[ "$source_type" == "proxmox" ]]; then
|
||||
menu_options+=("$i" " $name ($repo) [Proxmox]")
|
||||
else
|
||||
menu_options+=("$i" " $name ($repo) [Manual]")
|
||||
fi
|
||||
((i++))
|
||||
done
|
||||
|
||||
|
||||
menu_options+=("" "")
|
||||
menu_options+=("$i" "\Z4\Zb $(translate 'Configure new PBS')\Zn")
|
||||
local choice
|
||||
choice=$(dialog --colors --backtitle "ProxMenux" --title "PBS Server Selection" \
|
||||
--menu "\n$(translate 'Select PBS server for this backup:')" 22 70 12 "${menu_options[@]}" 3>&1 1>&2 2>&3)
|
||||
local dialog_result=$?
|
||||
clear
|
||||
|
||||
|
||||
if [[ $dialog_result -ne 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ $choice -eq $i ]]; then
|
||||
configure_pbs_manually
|
||||
else
|
||||
|
||||
local selected_config="${PBS_CONFIGS[$((choice-1))]}"
|
||||
local selected_source="${PBS_SOURCES[$((choice-1))]}"
|
||||
local pbs_name="${selected_config%%|*}"
|
||||
local source_type="${selected_source%%|*}"
|
||||
PBS_REPO="${selected_config##*|}"
|
||||
|
||||
|
||||
{
|
||||
mkdir -p "$(dirname "$PBS_REPO_FILE")"
|
||||
echo "$PBS_REPO" > "$PBS_REPO_FILE"
|
||||
} >/dev/null 2>&1
|
||||
|
||||
|
||||
local password_found=false
|
||||
if [[ "$source_type" == "proxmox" ]]; then
|
||||
|
||||
local password_file="/etc/pve/priv/storage/${pbs_name}.pw"
|
||||
if [[ -f "$password_file" ]]; then
|
||||
{
|
||||
cp "$password_file" "$PBS_PASS_FILE"
|
||||
chmod 600 "$PBS_PASS_FILE"
|
||||
} >/dev/null 2>&1
|
||||
password_found=true
|
||||
|
||||
fi
|
||||
else
|
||||
|
||||
local manual_pass_file="/usr/local/share/proxmenux/pbs-pass-${pbs_name}.txt"
|
||||
if [[ -f "$manual_pass_file" ]]; then
|
||||
{
|
||||
cp "$manual_pass_file" "$PBS_PASS_FILE"
|
||||
chmod 600 "$PBS_PASS_FILE"
|
||||
} >/dev/null 2>&1
|
||||
password_found=true
|
||||
dialog --backtitle "ProxMenux" --title "PBS Selected" --msgbox "$(translate 'Using manual PBS:') $pbs_name\n\n$(translate 'Repository:') $PBS_REPO\n$(translate 'Password:') $(translate 'Previously saved')" 12 80
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if ! $password_found; then
|
||||
dialog --backtitle "ProxMenux" --title "Password Required" --msgbox "$(translate 'Password not found for:') $pbs_name\n$(translate 'Please enter the password.')" 10 60
|
||||
get_pbs_password "$pbs_name"
|
||||
fi
|
||||
|
||||
clear
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
configure_pbs_manually() {
|
||||
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
|
||||
local PBS_MANUAL_CONFIGS="/usr/local/share/proxmenux/pbs-manual-configs.txt"
|
||||
|
||||
|
||||
local PBS_NAME
|
||||
PBS_NAME=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter a name for this PBS configuration:')" 10 60 "PBS-$(date +%m%d)" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
PBS_USER=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter PBS username:')" 10 50 "root@pam" 3>&1 1>&2 2>&3) || return 1
|
||||
PBS_HOST=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter PBS host or IP:')" 10 50 "" 3>&1 1>&2 2>&3) || return 1
|
||||
PBS_DATASTORE=$(dialog --backtitle "ProxMenux" --title "New PBS Configuration" --inputbox "$(translate 'Enter PBS datastore name:')" 10 50 "" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
|
||||
if [[ -z "$PBS_NAME" || -z "$PBS_USER" || -z "$PBS_HOST" || -z "$PBS_DATASTORE" ]]; then
|
||||
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'All fields are required!')" 8 40
|
||||
return 1
|
||||
fi
|
||||
|
||||
PBS_REPO="${PBS_USER}@${PBS_HOST}:${PBS_DATASTORE}"
|
||||
|
||||
|
||||
{
|
||||
mkdir -p "$(dirname "$PBS_REPO_FILE")"
|
||||
echo "$PBS_REPO" > "$PBS_REPO_FILE"
|
||||
} >/dev/null 2>&1
|
||||
|
||||
|
||||
local config_line="$PBS_NAME|$PBS_REPO"
|
||||
if ! grep -Fxq "$config_line" "$PBS_MANUAL_CONFIGS" 2>/dev/null; then
|
||||
echo "$config_line" >> "$PBS_MANUAL_CONFIGS"
|
||||
fi
|
||||
|
||||
|
||||
get_pbs_password "$PBS_NAME"
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "Success" --msgbox "$(translate 'PBS configuration saved:') $PBS_NAME\n\n$(translate 'Repository:') $PBS_REPO\n\n$(translate 'This configuration will appear in future backups.')" 12 80
|
||||
}
|
||||
|
||||
|
||||
get_pbs_password() {
|
||||
local PBS_NAME="$1"
|
||||
local PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
|
||||
local PBS_MANUAL_PASS_FILE="/usr/local/share/proxmenux/pbs-pass-${PBS_NAME}.txt"
|
||||
|
||||
while true; do
|
||||
PBS_REPO_PASS=$(dialog --backtitle "ProxMenux" --title "PBS Password" --insecure --passwordbox "$(translate 'Enter PBS repository password for:') $PBS_NAME" 10 70 "" 3>&1 1>&2 2>&3) || return 1
|
||||
PBS_REPO_PASS2=$(dialog --backtitle "ProxMenux" --title "PBS Password" --insecure --passwordbox "$(translate 'Confirm PBS repository password:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [[ "$PBS_REPO_PASS" == "$PBS_REPO_PASS2" ]]; then
|
||||
break
|
||||
else
|
||||
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'Repository passwords do not match! Please try again.')" 8 50
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
{
|
||||
echo "$PBS_REPO_PASS" > "$PBS_PASS_FILE"
|
||||
chmod 600 "$PBS_PASS_FILE"
|
||||
} >/dev/null 2>&1
|
||||
|
||||
|
||||
{
|
||||
echo "$PBS_REPO_PASS" > "$PBS_MANUAL_PASS_FILE"
|
||||
chmod 600 "$PBS_MANUAL_PASS_FILE"
|
||||
} >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ===============================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ========== PBS BACKUP ==========
|
||||
backup_full_pbs_root() {
|
||||
local HOSTNAME PBS_REPO PBS_KEY_FILE PBS_PASS_FILE PBS_ENCRYPTION_PASS_FILE ENCRYPT_OPT=""
|
||||
HOSTNAME=$(hostname)
|
||||
|
||||
|
||||
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
|
||||
PBS_KEY_FILE="/usr/local/share/proxmenux/pbs-key.conf"
|
||||
PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
|
||||
PBS_ENCRYPTION_PASS_FILE="/usr/local/share/proxmenux/pbs-encryption-pass.txt"
|
||||
LOGFILE="/tmp/pbs-backup-${HOSTNAME}.log"
|
||||
|
||||
|
||||
configure_pbs_repository
|
||||
if [[ ! -f "$PBS_REPO_FILE" ]]; then
|
||||
msg_error "$(translate "Failed to configure PBS connection")"
|
||||
sleep 3
|
||||
return 1
|
||||
fi
|
||||
PBS_REPO=$(<"$PBS_REPO_FILE")
|
||||
|
||||
|
||||
if [[ ! -f "$PBS_PASS_FILE" ]]; then
|
||||
msg_error "$(translate "PBS password not configured")"
|
||||
sleep 3
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "Encryption" --yesno "$(translate 'Do you want to encrypt the backup?')" 8 60
|
||||
if [[ $? -eq 0 ]]; then
|
||||
|
||||
if [[ ! -f "$PBS_ENCRYPTION_PASS_FILE" ]]; then
|
||||
while true; do
|
||||
PBS_KEY_PASS=$(dialog --backtitle "ProxMenux" --title "Encryption Password" --insecure --passwordbox "$(translate 'Enter encryption password (different from PBS login):')" 12 70 "" 3>&1 1>&2 2>&3) || return 1
|
||||
PBS_KEY_PASS2=$(dialog --backtitle "ProxMenux" --title "Encryption Password" --insecure --passwordbox "$(translate 'Confirm encryption password:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [[ "$PBS_KEY_PASS" == "$PBS_KEY_PASS2" ]]; then
|
||||
break
|
||||
else
|
||||
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'Passwords do not match! Please try again.')" 8 50
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
{
|
||||
echo "$PBS_KEY_PASS" > "$PBS_ENCRYPTION_PASS_FILE"
|
||||
chmod 600 "$PBS_ENCRYPTION_PASS_FILE"
|
||||
} >/dev/null 2>&1
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "Success" --msgbox "$(translate 'Encryption password saved successfully!')" 8 50
|
||||
fi
|
||||
|
||||
|
||||
if [[ ! -f "$PBS_KEY_FILE" ]]; then
|
||||
PBS_ENCRYPTION_PASS=$(<"$PBS_ENCRYPTION_PASS_FILE")
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "Encryption" --infobox "$(translate 'Creating encryption key...')" 5 50
|
||||
|
||||
expect -c "
|
||||
set timeout 30
|
||||
spawn proxmox-backup-client key create \"$PBS_KEY_FILE\"
|
||||
expect {
|
||||
\"Encryption Key Password:\" {
|
||||
send \"$PBS_ENCRYPTION_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
\"Verify Password:\" {
|
||||
send \"$PBS_ENCRYPTION_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
eof
|
||||
}
|
||||
" >/dev/null 2>&1
|
||||
|
||||
if [[ ! -f "$PBS_KEY_FILE" ]]; then
|
||||
dialog --backtitle "ProxMenux" --title "Error" --msgbox "$(translate 'Error creating encryption key.')" 8 40
|
||||
return 1
|
||||
fi
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "Important" --msgbox "$(translate 'IMPORTANT: Save the key file. Without it you will not be able to restore your backups!')\n\n$(translate 'Key file location:') $PBS_KEY_FILE" 12 70
|
||||
fi
|
||||
ENCRYPT_OPT="--keyfile $PBS_KEY_FILE"
|
||||
else
|
||||
ENCRYPT_OPT=""
|
||||
fi
|
||||
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
msg_info2 "$(translate "Starting backup to PBS")"
|
||||
echo -e
|
||||
echo -e "${BL}$(translate "PBS Repository:")${WHITE} $PBS_REPO${RESET}"
|
||||
echo -e "${BL}$(translate "Backup ID:")${WHITE} $HOSTNAME${RESET}"
|
||||
echo -e "${BL}$(translate "Included:")${WHITE} /boot/efi /etc/pve (all root)${RESET}"
|
||||
echo -e "${BL}$(translate "Encryption:")${WHITE} $([[ -n "$ENCRYPT_OPT" ]] && echo "Enabled" || echo "Disabled")${RESET}"
|
||||
echo -e "${BL}$(translate "Log file:")${WHITE} $LOGFILE${RESET}"
|
||||
echo -e "${BOLD}${NEON_PURPLE_BLUE}-------------------------------${RESET}"
|
||||
echo ""
|
||||
|
||||
|
||||
PBS_REPO_PASS=$(<"$PBS_PASS_FILE")
|
||||
|
||||
if [[ -n "$ENCRYPT_OPT" ]]; then
|
||||
|
||||
PBS_ENCRYPTION_PASS=$(<"$PBS_ENCRYPTION_PASS_FILE")
|
||||
echo "$(translate "Starting encrypted full backup...")"
|
||||
echo ""
|
||||
|
||||
expect -c "
|
||||
set timeout 3600
|
||||
log_file $LOGFILE
|
||||
spawn proxmox-backup-client backup \
|
||||
--include-dev /boot/efi \
|
||||
--include-dev /etc/pve \
|
||||
root-${HOSTNAME}.pxar:/ \
|
||||
--repository \"$PBS_REPO\" \
|
||||
$ENCRYPT_OPT \
|
||||
--backup-type host \
|
||||
--backup-id \"$HOSTNAME\" \
|
||||
--backup-time \"$(date +%s)\"
|
||||
expect {
|
||||
-re \"Password for .*:\" {
|
||||
send \"$PBS_REPO_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
\"Encryption Key Password:\" {
|
||||
send \"$PBS_ENCRYPTION_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
eof
|
||||
}
|
||||
" | tee -a "$LOGFILE"
|
||||
else
|
||||
|
||||
echo "$(translate "Starting unencrypted full backup...")"
|
||||
echo ""
|
||||
|
||||
expect -c "
|
||||
set timeout 3600
|
||||
log_file $LOGFILE
|
||||
spawn proxmox-backup-client backup \
|
||||
--include-dev /boot/efi \
|
||||
--include-dev /etc/pve \
|
||||
root-${HOSTNAME}.pxar:/ \
|
||||
--repository \"$PBS_REPO\" \
|
||||
--backup-type host \
|
||||
--backup-id \"$HOSTNAME\" \
|
||||
--backup-time \"$(date +%s)\"
|
||||
expect {
|
||||
-re \"Password for .*:\" {
|
||||
send \"$PBS_REPO_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
eof
|
||||
}
|
||||
" | tee -a "$LOGFILE"
|
||||
fi
|
||||
local backup_result=$?
|
||||
|
||||
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
|
||||
if [[ $backup_result -eq 0 ]]; then
|
||||
msg_ok "$(translate "Full backup process completed successfully")"
|
||||
else
|
||||
msg_error "$(translate "Backup process finished with errors")"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
backup_to_pbs() {
|
||||
local HOSTNAME TIMESTAMP SNAPSHOT
|
||||
HOSTNAME=$(hostname)
|
||||
TIMESTAMP=$(date +%Y-%m-%d_%H-%M)
|
||||
SNAPSHOT="${HOSTNAME}-${TIMESTAMP}"
|
||||
|
||||
local PBS_REPO_FILE="/usr/local/share/proxmenux/pbs-repo.conf"
|
||||
local PBS_KEY_FILE="/usr/local/share/proxmenux/pbs-key.conf"
|
||||
local PBS_PASS_FILE="/usr/local/share/proxmenux/pbs-pass.txt"
|
||||
local PBS_ENCRYPTION_PASS_FILE="/usr/local/share/proxmenux/pbs-encryption-pass.txt"
|
||||
local PBS_REPO ENCRYPT_OPT USE_ENCRYPTION
|
||||
local PBS_KEY_PASS PBS_REPO_PASS
|
||||
|
||||
|
||||
configure_pbs_repository
|
||||
PBS_REPO=$(<"$PBS_REPO_FILE")
|
||||
|
||||
|
||||
USE_ENCRYPTION=false
|
||||
dialog --backtitle "ProxMenux" --yesno "$(translate 'Do you want to encrypt the backup?')" 8 60
|
||||
[[ $? -eq 0 ]] && USE_ENCRYPTION=true
|
||||
|
||||
|
||||
if $USE_ENCRYPTION && ! command -v expect >/dev/null 2>&1; then
|
||||
apt-get update -qq >/dev/null 2>&1
|
||||
apt-get install -y expect >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
if [[ "$#" -lt 1 ]]; then
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "No directories specified for backup.")"
|
||||
sleep 2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local TOTAL="$#"
|
||||
local COUNT=1
|
||||
|
||||
for dir in "$@"; do
|
||||
local SAFE_NAME SAFE_ID PXAR_NAME
|
||||
SAFE_NAME=$(basename "$dir" | tr '.-/' '_')
|
||||
PXAR_NAME="root-custom-${SAFE_NAME}-${SNAPSHOT}.pxar"
|
||||
SAFE_ID="custom-${HOSTNAME}-${SAFE_NAME}"
|
||||
|
||||
msg_info2 "$(translate "[$COUNT/$TOTAL] Backing up") $dir $(translate "as") $PXAR_NAME"
|
||||
|
||||
ENCRYPT_OPT=""
|
||||
|
||||
|
||||
if $USE_ENCRYPTION; then
|
||||
if [[ -f "$PBS_KEY_FILE" ]]; then
|
||||
ENCRYPT_OPT="--keyfile $PBS_KEY_FILE"
|
||||
else
|
||||
|
||||
while true; do
|
||||
PBS_KEY_PASS=$(dialog --backtitle "ProxMenux" --insecure --passwordbox "$(translate 'Enter encryption password (different from PBS login):')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
|
||||
PBS_KEY_PASS2=$(dialog --backtitle "ProxMenux" --insecure --passwordbox "$(translate 'Confirm encryption password:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [[ "$PBS_KEY_PASS" == "$PBS_KEY_PASS2" ]]; then
|
||||
break
|
||||
else
|
||||
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Passwords do not match! Please try again.')" 8 50
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
{
|
||||
echo "$PBS_KEY_PASS" > "$PBS_ENCRYPTION_PASS_FILE"
|
||||
chmod 600 "$PBS_ENCRYPTION_PASS_FILE"
|
||||
} >/dev/null 2>&1
|
||||
|
||||
|
||||
expect -c "
|
||||
set timeout 30
|
||||
spawn proxmox-backup-client key create \"$PBS_KEY_FILE\"
|
||||
expect {
|
||||
\"Encryption Key Password:\" {
|
||||
send \"$PBS_KEY_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
\"Verify Password:\" {
|
||||
send \"$PBS_KEY_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
eof
|
||||
}
|
||||
" >/dev/null 2>&1
|
||||
|
||||
if [[ ! -f "$PBS_KEY_FILE" ]]; then
|
||||
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Error creating encryption key.')" 8 40
|
||||
return 1
|
||||
fi
|
||||
ENCRYPT_OPT="--keyfile $PBS_KEY_FILE"
|
||||
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Encryption key generated. Save it in a safe place!')" 10 60
|
||||
fi
|
||||
fi
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
msg_info2 "$(translate "Starting backup to PBS")"
|
||||
TOTAL_SIZE=$(du -cb "$@" | awk '/total$/ {print $1}')
|
||||
TOTAL_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE/1024/1024/1024}")
|
||||
echo -e
|
||||
echo -e "${BL}$(translate "PBS Repository:")${WHITE} $PBS_REPO${RESET}"
|
||||
echo -e "${BL}$(translate "Backup ID:")${WHITE} $HOSTNAME${RESET}"
|
||||
echo -e "${BL}$(translate "Encryption:")${WHITE} $([[ -n "$ENCRYPT_OPT" ]] && echo "Enabled" || echo "Disabled")${RESET}"
|
||||
echo -e "${BL}$(translate "Included directories:")${WHITE} $*${RESET}"
|
||||
echo -e "${BL}$(translate "Total size:")${WHITE} ${TOTAL_SIZE_GB} GB${RESET}"
|
||||
echo -e "${BOLD}${NEON_PURPLE_BLUE}-------------------------------${RESET}"
|
||||
|
||||
PBS_REPO_PASS=$(<"$PBS_PASS_FILE")
|
||||
|
||||
if $USE_ENCRYPTION && [[ -f "$PBS_ENCRYPTION_PASS_FILE" ]]; then
|
||||
PBS_KEY_PASS=$(<"$PBS_ENCRYPTION_PASS_FILE")
|
||||
expect -c "
|
||||
set timeout 300
|
||||
spawn proxmox-backup-client backup \"${PXAR_NAME}:$dir\" --repository \"$PBS_REPO\" $ENCRYPT_OPT --backup-type host --backup-id \"$SAFE_ID\" --backup-time \"$(date +%s)\"
|
||||
expect {
|
||||
-re \"Password for .*:\" {
|
||||
send \"$PBS_REPO_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
\"Encryption Key Password:\" {
|
||||
send \"$PBS_KEY_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
eof
|
||||
}
|
||||
"
|
||||
else
|
||||
|
||||
expect -c "
|
||||
set timeout 300
|
||||
spawn proxmox-backup-client backup \"${PXAR_NAME}:$dir\" --repository \"$PBS_REPO\" $ENCRYPT_OPT --backup-type host --backup-id \"$SAFE_ID\" --backup-time \"$(date +%s)\"
|
||||
expect {
|
||||
-re \"Password for .*:\" {
|
||||
send \"$PBS_REPO_PASS\r\"
|
||||
exp_continue
|
||||
}
|
||||
eof
|
||||
}
|
||||
"
|
||||
fi
|
||||
|
||||
COUNT=$((COUNT+1))
|
||||
done
|
||||
|
||||
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
|
||||
msg_ok "$(translate "Backup process finished.")"
|
||||
echo ""
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
|
||||
|
||||
}
|
||||
# ===============================
|
||||
|
||||
|
||||
|
||||
# ========== BORGBACKUP ==========
|
||||
backup_with_borg() {
|
||||
# local SRC="$1"
|
||||
local BORG_APPIMAGE="/usr/local/share/proxmenux/borg"
|
||||
local LOGFILE="/tmp/borg-backup.log"
|
||||
local DEST
|
||||
local TYPE
|
||||
local ENCRYPT_OPT=""
|
||||
local BORG_KEY
|
||||
|
||||
if [[ ! -x "$BORG_APPIMAGE" ]]; then
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "BorgBackup not found. Downloading AppImage...")"
|
||||
mkdir -p /usr/local/share/proxmenux
|
||||
wget -qO "$BORG_APPIMAGE" "https://github.com/borgbackup/borg/releases/download/1.2.8/borg-linux64"
|
||||
chmod +x "$BORG_APPIMAGE"
|
||||
msg_ok "$(translate "BorgBackup downloaded and ready.")"
|
||||
fi
|
||||
|
||||
|
||||
TYPE=$(dialog --backtitle "ProxMenux" --menu "$(translate 'Select Borg backup destination:')" 15 60 3 \
|
||||
"local" "$(translate 'Local directory')" \
|
||||
"usb" "$(translate 'Internal/External dedicated disk')" \
|
||||
"remote" "$(translate 'Remote server')" \
|
||||
3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [[ "$TYPE" == "local" ]]; then
|
||||
DEST=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter local directory for backup:')" 10 60 "/backup/borgbackup" 3>&1 1>&2 2>&3) || return 1
|
||||
mkdir -p "$DEST"
|
||||
elif [[ "$TYPE" == "usb" ]]; then
|
||||
|
||||
while true; do
|
||||
BASE_DEST=$(get_external_backup_mount_point)
|
||||
if [[ -z "$BASE_DEST" ]]; then
|
||||
dialog --backtitle "ProxMenux" --yesno "$(translate 'No external disk detected or mounted. Would you like to retry?')" 8 60
|
||||
[[ $? -eq 0 ]] && continue
|
||||
return 1
|
||||
fi
|
||||
|
||||
DEST="$BASE_DEST/borgbackup"
|
||||
mkdir -p "$DEST"
|
||||
|
||||
DISK_DEV=$(df "$BASE_DEST" | awk 'NR==2{print $1}')
|
||||
PKNAME=$(lsblk -no PKNAME "$DISK_DEV" 2>/dev/null)
|
||||
[[ -z "$PKNAME" ]] && PKNAME=$(basename "$DISK_DEV" | sed 's/[0-9]*$//')
|
||||
if [[ -n "$PKNAME" && -b /dev/$PKNAME ]]; then
|
||||
DISK_MODEL=$(lsblk -no MODEL "/dev/$PKNAME")
|
||||
else
|
||||
DISK_MODEL="(unknown)"
|
||||
fi
|
||||
FREE_SPACE=$(df -h "$BASE_DEST" | awk 'NR==2{print $4}')
|
||||
|
||||
dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Dedicated Backup Disk")" \
|
||||
--yesno "\n$(translate "Mount point:") $DEST\n\n\
|
||||
$(translate "Disk model:") $DISK_MODEL\n\
|
||||
$(translate "Available space:") $FREE_SPACE\n\n\
|
||||
$(translate "Use this disk for backup?")" 12 70
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
break
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
elif [[ "$TYPE" == "remote" ]]; then
|
||||
REMOTE_USER=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter SSH user for remote:')" 10 60 "root" 3>&1 1>&2 2>&3) || return 1
|
||||
REMOTE_HOST=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter SSH host:')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
|
||||
REMOTE_PATH=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter remote path:')" 10 60 "/backup/borgbackup" 3>&1 1>&2 2>&3) || return 1
|
||||
DEST="ssh://$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH"
|
||||
fi
|
||||
|
||||
|
||||
dialog --backtitle "ProxMenux" --yesno "$(translate 'Do you want to encrypt the backup?')" 8 60
|
||||
if [[ $? -eq 0 ]]; then
|
||||
BORG_KEY=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter Borg encryption passphrase (will be saved):')" 10 60 "" 3>&1 1>&2 2>&3) || return 1
|
||||
ENCRYPT_OPT="--encryption=repokey"
|
||||
export BORG_PASSPHRASE="$BORG_KEY"
|
||||
else
|
||||
ENCRYPT_OPT="--encryption=none"
|
||||
fi
|
||||
|
||||
if [[ "$TYPE" == "local" || "$TYPE" == "usb" ]]; then
|
||||
if [[ ! -f "$DEST/config" ]]; then
|
||||
"$BORG_APPIMAGE" init $ENCRYPT_OPT "$DEST"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "Failed to initialize Borg repo at") $DEST"
|
||||
sleep 5
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
dialog --backtitle "ProxMenux" --msgbox "$(translate 'Borg backup will start now. This may take a while.')" 8 40
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_info2 "$(translate "Starting backup with BorgBackup...")"
|
||||
echo -e
|
||||
|
||||
TOTAL_SIZE=$(du -cb "$@" | awk '/total$/ {print $1}')
|
||||
TOTAL_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE/1024/1024/1024}")
|
||||
|
||||
echo -e "${BL}$(translate "Included directories:")${WHITE} $*${RESET}"
|
||||
echo -e "${BL}$(translate "Total size:")${WHITE} ${TOTAL_SIZE_GB} GB${RESET}"
|
||||
|
||||
|
||||
# 6. Lanzar el backup y guardar log
|
||||
# "$BORG_APPIMAGE" create --progress "$DEST"::"root-$(hostname)-$(date +%Y%m%d_%H%M)" $SRC 2>&1 | tee "$LOGFILE"
|
||||
|
||||
"$BORG_APPIMAGE" create --progress "$DEST"::"root-$(hostname)-$(date +%Y%m%d_%H%M)" "$@" 2>&1 | tee "$LOGFILE"
|
||||
|
||||
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
|
||||
msg_ok "$(translate "Backup process finished.")"
|
||||
echo
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
}
|
||||
# ===============================
|
||||
|
||||
|
||||
|
||||
|
||||
# ========== LOCAL TAR ==========
|
||||
backup_to_local_tar() {
|
||||
# local SRC="$1"
|
||||
local TYPE
|
||||
local DEST
|
||||
local LOGFILE="/tmp/tar-backup.log"
|
||||
|
||||
|
||||
if ! command -v pv &>/dev/null; then
|
||||
apt-get update -qq && apt-get install -y pv >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
TYPE=$(dialog --backtitle "ProxMenux" --menu "$(translate 'Select backup destination:')" 15 60 2 \
|
||||
"local" "$(translate 'Local directory')" \
|
||||
"usb" "$(translate 'Internal/External dedicated disk')" \
|
||||
3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [[ "$TYPE" == "local" ]]; then
|
||||
DEST=$(dialog --backtitle "ProxMenux" --inputbox "$(translate 'Enter directory for backup:')" 10 60 "/backup" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
mkdir -p "$DEST"
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
while true; do
|
||||
DEST=$(get_external_backup_mount_point)
|
||||
if [[ -z "$DEST" ]]; then
|
||||
dialog --backtitle "ProxMenux" --yesno "No external disk detected or mounted. Would you like to retry?" 8 60
|
||||
[[ $? -eq 0 ]] && continue
|
||||
return 1
|
||||
fi
|
||||
|
||||
DISK_DEV=$(df "$DEST" | awk 'NR==2{print $1}')
|
||||
PKNAME=$(lsblk -no PKNAME "$DISK_DEV" 2>/dev/null)
|
||||
[[ -z "$PKNAME" ]] && PKNAME=$(basename "$DISK_DEV" | sed 's/[0-9]*$//')
|
||||
if [[ -n "$PKNAME" && -b /dev/$PKNAME ]]; then
|
||||
DISK_MODEL=$(lsblk -no MODEL "/dev/$PKNAME")
|
||||
else
|
||||
DISK_MODEL="(unknown)"
|
||||
fi
|
||||
FREE_SPACE=$(df -h "$DEST" | awk 'NR==2{print $4}')
|
||||
|
||||
|
||||
|
||||
dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Dedicated Backup Disk")" \
|
||||
--yesno "\n$(translate "Mount point:") $DEST\n\n\
|
||||
$(translate "Disk model:") $DISK_MODEL\n\
|
||||
$(translate "Available space:") $FREE_SPACE\n\n\
|
||||
$(translate "Use this disk for backup?")" 12 70
|
||||
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
mkdir -p "$DEST"
|
||||
break
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
|
||||
fi
|
||||
|
||||
|
||||
TAR_INPUT=""
|
||||
TOTAL_SIZE=0
|
||||
for src in $SRC; do
|
||||
sz=$(du -sb "$src" 2>/dev/null | awk '{print $1}')
|
||||
TOTAL_SIZE=$((TOTAL_SIZE + sz))
|
||||
TAR_INPUT="$TAR_INPUT $src"
|
||||
done
|
||||
|
||||
local FILENAME="root-$(hostname)-$(date +%Y%m%d_%H%M).tar.gz"
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_info2 "$(translate "Starting backup with tar...")"
|
||||
echo -e
|
||||
|
||||
|
||||
TOTAL_SIZE=$(du -cb "$@" | awk '/total$/ {print $1}')
|
||||
TOTAL_SIZE_GB=$(awk "BEGIN {printf \"%.2f\", $TOTAL_SIZE/1024/1024/1024}")
|
||||
|
||||
echo -e "${BL}$(translate "Included directories:")${WHITE} $*${RESET}"
|
||||
echo -e "${BL}$(translate "Total size:")${WHITE} ${TOTAL_SIZE_GB} GB${RESET}"
|
||||
|
||||
tar -cf - "$@" 2> >(grep -v "Removing leading \`/'" >&2) \
|
||||
| pv -s "$TOTAL_SIZE" \
|
||||
| gzip > "$DEST/$FILENAME"
|
||||
|
||||
|
||||
echo -ne "\033[1A\r\033[K"
|
||||
|
||||
echo -e "${BOLD}${NEON_PURPLE_BLUE}===============================${RESET}\n"
|
||||
msg_ok "$(translate "Backup process finished. Review log above or in /tmp/tar-backup.log")"
|
||||
echo
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
|
||||
}
|
||||
# ===============================
|
||||
|
||||
|
||||
host_backup_menu
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,433 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - Mount disk on Proxmox host for backups
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT
|
||||
# Version : 1.3-dialog
|
||||
# Last Updated: 13/12/2024
|
||||
# ==========================================================
|
||||
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
mount_disk_host_bk() {
|
||||
|
||||
|
||||
|
||||
get_disk_info() {
|
||||
local disk=$1
|
||||
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
|
||||
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
|
||||
echo "$MODEL" "$SIZE"
|
||||
}
|
||||
|
||||
|
||||
is_usb_disk() {
|
||||
local disk=$1
|
||||
local disk_name=$(basename "$disk")
|
||||
|
||||
|
||||
if readlink -f "/sys/block/$disk_name/device" 2>/dev/null | grep -q "usb"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
if udevadm info --query=property --name="$disk" 2>/dev/null | grep -q "ID_BUS=usb"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
is_system_disk() {
|
||||
local disk=$1
|
||||
local disk_name=$(basename "$disk")
|
||||
|
||||
|
||||
local system_mounts=$(df -h | grep -E '^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/|/boot|/usr|/var|/home)$' | awk '{print $1}')
|
||||
|
||||
|
||||
for mount_dev in $system_mounts; do
|
||||
|
||||
local mount_disk=""
|
||||
if [[ "$mount_dev" =~ ^/dev/mapper/ ]]; then
|
||||
|
||||
local vg_name=$(lvs --noheadings -o vg_name "$mount_dev" 2>/dev/null | xargs)
|
||||
if [[ -n "$vg_name" ]]; then
|
||||
local pvs_list=$(pvs --noheadings -o pv_name -S vg_name="$vg_name" 2>/dev/null | xargs)
|
||||
for pv in $pvs_list; do
|
||||
if [[ -n "$pv" && -e "$pv" ]]; then
|
||||
mount_disk=$(lsblk -no PKNAME "$pv" 2>/dev/null)
|
||||
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
elif [[ "$mount_dev" =~ ^/dev/[hsv]d[a-z][0-9]* || "$mount_dev" =~ ^/dev/nvme[0-9]+n[0-9]+p[0-9]+ ]]; then
|
||||
|
||||
mount_disk=$(lsblk -no PKNAME "$mount_dev" 2>/dev/null)
|
||||
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
local fs_type=$(lsblk -no FSTYPE "$disk" 2>/dev/null | head -1)
|
||||
if [[ "$fs_type" == "btrfs" ]]; then
|
||||
|
||||
local temp_mount=$(mktemp -d)
|
||||
if mount -o ro "$disk" "$temp_mount" 2>/dev/null; then
|
||||
|
||||
if btrfs subvolume list "$temp_mount" 2>/dev/null | grep -qE '(@|@home|@var|@boot|@root|root)'; then
|
||||
umount "$temp_mount" 2>/dev/null
|
||||
rmdir "$temp_mount" 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
umount "$temp_mount" 2>/dev/null
|
||||
fi
|
||||
rmdir "$temp_mount" 2>/dev/null
|
||||
|
||||
|
||||
while read -r part; do
|
||||
if [[ -n "$part" ]]; then
|
||||
local part_fs=$(lsblk -no FSTYPE "/dev/$part" 2>/dev/null)
|
||||
if [[ "$part_fs" == "btrfs" ]]; then
|
||||
local mount_point=$(lsblk -no MOUNTPOINT "/dev/$part" 2>/dev/null)
|
||||
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
|
||||
fi
|
||||
|
||||
|
||||
local disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null)
|
||||
local part_uuids=()
|
||||
while read -r part; do
|
||||
if [[ -n "$part" ]]; then
|
||||
local uuid=$(blkid -s UUID -o value "/dev/$part" 2>/dev/null)
|
||||
if [[ -n "$uuid" ]]; then
|
||||
part_uuids+=("$uuid")
|
||||
fi
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
|
||||
|
||||
|
||||
for uuid in "${part_uuids[@]}" "$disk_uuid"; do
|
||||
if [[ -n "$uuid" ]] && grep -q "UUID=$uuid" /etc/fstab; then
|
||||
local mount_point=$(grep "UUID=$uuid" /etc/fstab | awk '{print $2}')
|
||||
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if grep -q "$disk" /etc/fstab; then
|
||||
local mount_point=$(grep "$disk" /etc/fstab | awk '{print $2}')
|
||||
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
local disk_count=$(lsblk -dn -e 7,11 -o PATH | wc -l)
|
||||
if [[ "$disk_count" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
|
||||
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
|
||||
ZFS_DISKS=""
|
||||
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
|
||||
for entry in $ZFS_RAW; do
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
if [ -e "/dev/disk/by-id/$entry" ]; then
|
||||
path=$(readlink -f "/dev/disk/by-id/$entry")
|
||||
fi
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
|
||||
if [ -n "$path" ]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
if [ -n "$base_disk" ]; then
|
||||
ZFS_DISKS+="/dev/$base_disk"$'\n'
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
|
||||
LVM_DEVICES=$(
|
||||
pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') |
|
||||
while read -r dev; do
|
||||
[[ -n "$dev" && -e "$dev" ]] && readlink -f "$dev"
|
||||
done | sort -u
|
||||
)
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
while read -r DISK; do
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
INFO=($(get_disk_info "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
IS_SYSTEM=false
|
||||
IS_USB=false
|
||||
|
||||
|
||||
if is_system_disk "$DISK"; then
|
||||
IS_SYSTEM=true
|
||||
fi
|
||||
|
||||
|
||||
if is_usb_disk "$DISK"; then
|
||||
IS_USB=true
|
||||
fi
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
|
||||
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
REAL_PATH=""
|
||||
if [[ -n "$DISK" && -e "$DISK" ]]; then
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
fi
|
||||
if [[ -n "$REAL_PATH" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=""
|
||||
if [[ -n "$DISK" && -e "$DISK" ]]; then
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
fi
|
||||
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
else
|
||||
for SYMLINK in /dev/disk/by-id/*; do
|
||||
[[ -e "$SYMLINK" ]] || continue
|
||||
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
|
||||
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
|
||||
if grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
if $IS_ZFS; then SHOW_DISK=false; fi
|
||||
if $IS_MOUNTED; then SHOW_DISK=false; fi
|
||||
if $IS_SYSTEM; then SHOW_DISK=false; fi
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
|
||||
|
||||
if $IS_USB; then
|
||||
LABEL+=" USB"
|
||||
else
|
||||
LABEL+=" $(translate "Internal")"
|
||||
fi
|
||||
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "off")
|
||||
fi
|
||||
done < <(lsblk -dn -e 7,11 -o PATH)
|
||||
|
||||
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
|
||||
dialog --title "$(translate "Error")" --msgbox "$(translate "No available disks found on the host.")" 8 60
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Building the array for dialog (format: tag item on/off tag item on/off...)
|
||||
DLG_LIST=()
|
||||
for ((i=0; i<${#FREE_DISKS[@]}; i+=3)); do
|
||||
DLG_LIST+=("${FREE_DISKS[i]}" "${FREE_DISKS[i+1]}" "${FREE_DISKS[i+2]}")
|
||||
done
|
||||
|
||||
SELECTED=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Select Disk")" \
|
||||
--radiolist "\n$(translate "Select the disk you want to mount on the host:")" 20 90 10 \
|
||||
"${DLG_LIST[@]}" 2>&1 >/dev/tty)
|
||||
|
||||
if [ -z "$SELECTED" ]; then
|
||||
dialog --title "$(translate "Error")" --msgbox "$(translate "No disk was selected.")" 8 50
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# ------------------- Partitions and formatting ------------------------
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
|
||||
SKIP_FORMAT=false
|
||||
DEFAULT_MOUNT="/mnt/backup"
|
||||
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
|
||||
else
|
||||
dialog --title "$(translate "Unsupported Filesystem")" --yesno \
|
||||
"$(translate "The partition") $PARTITION $(translate "has an unsupported filesystem ($CURRENT_FS).\nDo you want to format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then exit 0; fi
|
||||
fi
|
||||
else
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$SELECTED" | xargs)
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
PARTITION="$SELECTED"
|
||||
|
||||
else
|
||||
dialog --title "$(translate "No Valid Partitions")" --yesno \
|
||||
"$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then exit 0; fi
|
||||
|
||||
echo -e "$(translate "Creating partition table and partition...")"
|
||||
parted -s "$SELECTED" mklabel gpt
|
||||
parted -s "$SELECTED" mkpart primary 0% 100%
|
||||
sleep 2
|
||||
partprobe "$SELECTED"
|
||||
sleep 2
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
else
|
||||
dialog --title "$(translate "Partition Error")" --msgbox \
|
||||
"$(translate "Failed to create partition on disk") $SELECTED." 8 70
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SKIP_FORMAT" != true ]; then
|
||||
FORMAT_TYPE=$(dialog --title "$(translate "Select Format Type")" --menu \
|
||||
"$(translate "Select the filesystem type for") $PARTITION:" 15 60 5 \
|
||||
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
|
||||
"xfs" "XFS" \
|
||||
"btrfs" "Btrfs" 2>&1 >/dev/tty)
|
||||
if [ -z "$FORMAT_TYPE" ]; then
|
||||
dialog --title "$(translate "Format Cancelled")" --msgbox \
|
||||
"$(translate "Format operation cancelled. The disk will not be added.")" 8 60
|
||||
exit 0
|
||||
fi
|
||||
|
||||
dialog --title "$(translate "WARNING")" --yesno \
|
||||
"$(translate "WARNING: This operation will FORMAT the disk") $PARTITION $(translate "with") $FORMAT_TYPE.\n\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\n\n$(translate "Are you sure you want to continue")" 15 70
|
||||
if [ $? -ne 0 ]; then exit 0; fi
|
||||
|
||||
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
|
||||
case "$FORMAT_TYPE" in
|
||||
"ext4") mkfs.ext4 -F "$PARTITION" ;;
|
||||
"xfs") mkfs.xfs -f "$PARTITION" ;;
|
||||
"btrfs") mkfs.btrfs -f "$PARTITION" ;;
|
||||
esac
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
cleanup
|
||||
dialog --title "$(translate "Format Failed")" --msgbox \
|
||||
"$(translate "Failed to format partition") $PARTITION $(translate "with") $FORMAT_TYPE." 12 70
|
||||
exit 1
|
||||
else
|
||||
|
||||
partprobe "$SELECTED"
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# ------------------- Mount point and permissions (modular, non-blocking) -------------------
|
||||
|
||||
|
||||
|
||||
MOUNT_POINT=$(dialog --clear --title "$(translate "Mount Point")" \
|
||||
--inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/backup):")" \
|
||||
10 60 "$DEFAULT_MOUNT" 2>&1 >/dev/tty)
|
||||
|
||||
if [ -z "$MOUNT_POINT" ]; then
|
||||
>&2 echo "$(translate "No mount point was specified.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
|
||||
UUID=$(blkid -s UUID -o value "$PARTITION")
|
||||
FS_TYPE=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
FSTAB_ENTRY="UUID=$UUID $MOUNT_POINT $FS_TYPE defaults 0 0"
|
||||
|
||||
if grep -q "UUID=$UUID" /etc/fstab; then
|
||||
sed -i "s|^.*UUID=$UUID.*|$FSTAB_ENTRY|" /etc/fstab
|
||||
else
|
||||
echo "$FSTAB_ENTRY" >> /etc/fstab
|
||||
fi
|
||||
|
||||
mount "$MOUNT_POINT" 2> >(grep -v "systemd still uses")
|
||||
if [ $? -eq 0 ]; then
|
||||
if ! getent group sharedfiles >/dev/null; then
|
||||
groupadd sharedfiles
|
||||
fi
|
||||
|
||||
chown root:sharedfiles "$MOUNT_POINT"
|
||||
chmod 2775 "$MOUNT_POINT"
|
||||
echo "$MOUNT_POINT" > /usr/local/share/proxmenux/last_backup_mount.txt
|
||||
|
||||
MOUNT_POINT=$(echo "$MOUNT_POINT" | head -n1 | tr -d '\r\n\t ')
|
||||
echo "$MOUNT_POINT"
|
||||
else
|
||||
>&2 echo "$(translate "Failed to mount the disk at") $MOUNT_POINT"
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
@@ -42,7 +42,7 @@ select_container() {
|
||||
fi
|
||||
|
||||
CONTAINER_ID=$(whiptail --title "$(translate 'Select Container')" \
|
||||
--menu "$(translate 'Select the LXC container:')" 15 60 8 $CONTAINERS 3>&1 1>&2 2>&3)
|
||||
--menu "$(translate 'Select the LXC container:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
msg_error "$(translate 'No container selected. Exiting.')"
|
||||
@@ -165,11 +165,13 @@ install_igpu_in_container() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
select_container
|
||||
show_proxmenux_logo
|
||||
configure_lxc_for_igpu
|
||||
install_igpu_in_container
|
||||
|
||||
|
||||
msg_success "$(translate 'iGPU configuration completed in container') $CONTAINER_ID."
|
||||
sleep 2
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
@@ -31,7 +31,7 @@ if [[ -f "$UTILS_FILE" ]]; then
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
show_proxmenux_logo
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,944 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenu - Network Management and Repair Tool
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 2.0
|
||||
# Last Updated: 07/01/2025
|
||||
# ==========================================================
|
||||
|
||||
# Description:
|
||||
# Advanced network management and troubleshooting tool for Proxmox VE.
|
||||
# Features include interface detection, bridge management, connectivity testing,
|
||||
# network diagnostics, configuration backup/restore, and automated repairs.
|
||||
|
||||
# Configuration ============================================
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
BACKUP_DIR="/var/backups/proxmenux"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
# Utility Functions
|
||||
create_backup_dir() {
|
||||
[ ! -d "$BACKUP_DIR" ] && mkdir -p "$BACKUP_DIR"
|
||||
}
|
||||
|
||||
backup_network_config() {
|
||||
create_backup_dir
|
||||
local timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
|
||||
local backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
|
||||
cp /etc/network/interfaces "$backup_file"
|
||||
echo "$backup_file"
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Network Detection Functions
|
||||
|
||||
detect_network_method() {
|
||||
# Detect Netplan
|
||||
if compgen -G "/etc/netplan/*.yaml" > /dev/null; then
|
||||
echo "netplan"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Detect systemd-networkd
|
||||
if systemctl is-active --quiet systemd-networkd 2>/dev/null; then
|
||||
echo "systemd-networkd"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Detect NetworkManager
|
||||
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
|
||||
echo "networkmanager"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Default: Debian/Proxmox classic
|
||||
echo "classic"
|
||||
}
|
||||
|
||||
|
||||
detect_physical_interfaces() {
|
||||
ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|bond|tap|tun|docker|br-)/ && $2 !~ /vmbr/ {print $2}' | sort
|
||||
}
|
||||
|
||||
detect_bridge_interfaces() {
|
||||
ip -o link show | awk -F': ' '$2 ~ /^vmbr/ {print $2}' | sort
|
||||
}
|
||||
|
||||
detect_all_interfaces() {
|
||||
ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|tap|tun)/ {print $2}' | sort
|
||||
}
|
||||
|
||||
get_interface_info() {
|
||||
local interface="$1"
|
||||
local info=""
|
||||
|
||||
# Get IP address
|
||||
local ip=$(ip -4 addr show "$interface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
|
||||
[ -z "$ip" ] && ip="$(translate "No IP")"
|
||||
|
||||
# Get status
|
||||
local status=$(ip link show "$interface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
|
||||
[ -z "$status" ] && status="UNKNOWN"
|
||||
|
||||
# Get MAC address
|
||||
local mac=$(ip link show "$interface" 2>/dev/null | grep -o "link/ether [a-f0-9:]*" | cut -d' ' -f2)
|
||||
[ -z "$mac" ] && mac="$(translate "No MAC")"
|
||||
|
||||
echo "$interface|$ip|$status|$mac"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Network Testing Functions
|
||||
test_connectivity() {
|
||||
local test_results=""
|
||||
local tests=(
|
||||
"8.8.8.8|Google DNS"
|
||||
"1.1.1.1|Cloudflare DNS"
|
||||
"$(ip route | grep default | awk '{print $3}' | head -1)|Gateway"
|
||||
)
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Test Connectivity")"
|
||||
test_results+="$(translate "Connectivity Test Results")\n"
|
||||
test_results+="$(printf '=%.0s' {1..35})\n\n"
|
||||
|
||||
for test in "${tests[@]}"; do
|
||||
IFS='|' read -r target name <<< "$test"
|
||||
if [ -n "$target" ] && [ "$target" != "" ]; then
|
||||
if ping -c 2 -W 3 "$target" >/dev/null 2>&1; then
|
||||
test_results+="✓ $name ($target): $(translate "OK")\n"
|
||||
else
|
||||
test_results+="✗ $name ($target): $(translate "FAILED")\n"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# DNS Resolution test
|
||||
if nslookup google.com >/dev/null 2>&1; then
|
||||
test_results+="✓ $(translate "DNS Resolution"): $(translate "OK")\n"
|
||||
else
|
||||
test_results+="✗ $(translate "DNS Resolution"): $(translate "FAILED")\n"
|
||||
fi
|
||||
cleanup
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Connectivity Test")" \
|
||||
--msgbox "$test_results" 15 60
|
||||
}
|
||||
|
||||
advanced_network_diagnostics() {
|
||||
|
||||
NETWORK_METHOD=$(detect_network_method)
|
||||
|
||||
if [[ "$NETWORK_METHOD" != "classic" ]]; then
|
||||
dialog --title "Unsupported Network Stack" \
|
||||
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Advanced Diagnostics")"
|
||||
sleep 1
|
||||
|
||||
local diag_info=""
|
||||
|
||||
diag_info+="$(translate "Advanced Network Diagnostics")\n"
|
||||
diag_info+="$(printf '=%.0s' {1..40})\n\n"
|
||||
|
||||
# Network statistics
|
||||
diag_info+="$(translate "Active Connections"): $(ss -tuln | wc -l)\n"
|
||||
diag_info+="$(translate "Listening Ports"): $(ss -tln | grep LISTEN | wc -l)\n"
|
||||
diag_info+="$(translate "Network Interfaces"): $(ip link show | grep -c "^[0-9]")\n\n"
|
||||
|
||||
# Check for common issues
|
||||
diag_info+="$(translate "Common Issues Check"):\n"
|
||||
|
||||
# Check if NetworkManager is running (shouldn't be on Proxmox)
|
||||
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
|
||||
diag_info+="⚠ $(translate "NetworkManager is running (may cause conflicts)")\n"
|
||||
|
||||
if dialog --title "$(translate "NetworkManager Detected")" \
|
||||
--yesno "$(translate "NetworkManager is running, which may conflict with Proxmox.")\n\n$(translate "Do you want to disable and remove it now?")" 10 70; then
|
||||
|
||||
dialog --infobox "$(translate "Disabling and removing NetworkManager...")" 6 60
|
||||
systemctl stop NetworkManager >/dev/null 2>&1
|
||||
systemctl disable NetworkManager >/dev/null 2>&1
|
||||
apt-get purge -y network-manager >/dev/null 2>&1
|
||||
|
||||
diag_info+="✓ $(translate "NetworkManager has been removed successfully")\n"
|
||||
else
|
||||
diag_info+="ℹ️ $(translate "User chose not to remove NetworkManager")\n"
|
||||
fi
|
||||
else
|
||||
diag_info+="✓ $(translate "NetworkManager not running")\n"
|
||||
fi
|
||||
|
||||
# Check for duplicate IPs
|
||||
local ips=($(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | sort | uniq -d))
|
||||
if [ ${#ips[@]} -gt 0 ]; then
|
||||
diag_info+="⚠ $(translate "Duplicate IP addresses found"): ${ips[*]}\n"
|
||||
else
|
||||
diag_info+="✓ $(translate "No duplicate IP addresses")\n"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Network Diagnostics")" \
|
||||
--msgbox "$diag_info" 18 70
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# SAFE Network Analysis Functions (NO AUTO-REPAIR)
|
||||
# ==========================================================
|
||||
|
||||
analyze_bridge_configuration() {
|
||||
|
||||
NETWORK_METHOD=$(detect_network_method)
|
||||
|
||||
if [[ "$NETWORK_METHOD" != "classic" ]]; then
|
||||
dialog --title "Unsupported Network Stack" \
|
||||
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
|
||||
exit 1
|
||||
fi
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Analyzing Bridge Configuration - READ ONLY MODE")"
|
||||
sleep 1
|
||||
|
||||
local physical_interfaces=($(detect_physical_interfaces))
|
||||
local bridges=($(detect_bridge_interfaces))
|
||||
local analysis_report=""
|
||||
local issues_found=0
|
||||
local suggestions=""
|
||||
|
||||
analysis_report+="🔍 $(translate "BRIDGE CONFIGURATION ANALYSIS")\n"
|
||||
analysis_report+="$(printf '=%.0s' {1..50})\n\n"
|
||||
cleanup
|
||||
if [ ${#bridges[@]} -eq 0 ]; then
|
||||
analysis_report+="ℹ️ $(translate "No bridges found in system")\n"
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Analysis")" --msgbox "$analysis_report" 10 60
|
||||
return
|
||||
fi
|
||||
|
||||
# Analyze each bridge
|
||||
for bridge in "${bridges[@]}"; do
|
||||
analysis_report+="🌉 $(translate "Bridge"): $bridge\n"
|
||||
|
||||
# Get current configuration
|
||||
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
|
||||
local bridge_ip=$(ip -4 addr show "$bridge" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
|
||||
local bridge_status=$(ip link show "$bridge" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
|
||||
|
||||
analysis_report+=" 📍 $(translate "Status"): ${bridge_status:-UNKNOWN}\n"
|
||||
analysis_report+=" 🌐 $(translate "IP"): ${bridge_ip:-$(translate "No IP assigned")}\n"
|
||||
analysis_report+=" 🔌 $(translate "Configured Ports"): ${current_ports:-$(translate "None")}\n"
|
||||
|
||||
if [ -n "$current_ports" ]; then
|
||||
local invalid_ports=""
|
||||
local valid_ports=""
|
||||
|
||||
# Check each configured port
|
||||
for port in $current_ports; do
|
||||
if ip link show "$port" >/dev/null 2>&1; then
|
||||
valid_ports+="$port "
|
||||
analysis_report+=" ✅ $(translate "Port") $port: $(translate "EXISTS")\n"
|
||||
else
|
||||
invalid_ports+="$port "
|
||||
analysis_report+=" ❌ $(translate "Port") $port: $(translate "NOT FOUND")\n"
|
||||
((issues_found++))
|
||||
fi
|
||||
done
|
||||
|
||||
# Generate suggestions for invalid ports
|
||||
if [ -n "$invalid_ports" ]; then
|
||||
suggestions+="🔧 $(translate "SUGGESTION FOR") $bridge:\n"
|
||||
if [ ${#physical_interfaces[@]} -gt 0 ]; then
|
||||
suggestions+=" $(translate "Replace invalid port(s)") '$invalid_ports' $(translate "with"): ${physical_interfaces[0]}\n"
|
||||
suggestions+=" $(translate "Command"): sed -i 's/bridge-ports.*/bridge-ports ${physical_interfaces[0]}/' /etc/network/interfaces\n"
|
||||
else
|
||||
suggestions+=" $(translate "Remove invalid port(s)") '$invalid_ports'\n"
|
||||
suggestions+=" $(translate "Command"): sed -i 's/bridge-ports.*/bridge-ports none/' /etc/network/interfaces\n"
|
||||
fi
|
||||
suggestions+="\n"
|
||||
fi
|
||||
else
|
||||
analysis_report+=" ⚠️ $(translate "No ports configured")\n"
|
||||
if [ ${#physical_interfaces[@]} -gt 0 ]; then
|
||||
suggestions+="🔧 $(translate "SUGGESTION FOR") $bridge:\n"
|
||||
suggestions+=" $(translate "Consider adding physical interface"): ${physical_interfaces[0]}\n"
|
||||
suggestions+=" $(translate "Command"): sed -i '/iface $bridge/a\\ bridge-ports ${physical_interfaces[0]}' /etc/network/interfaces\n\n"
|
||||
fi
|
||||
fi
|
||||
analysis_report+="\n"
|
||||
done
|
||||
|
||||
# Summary
|
||||
analysis_report+="📊 $(translate "ANALYSIS SUMMARY")\n"
|
||||
analysis_report+="$(printf '=%.0s' {1..25})\n"
|
||||
analysis_report+="$(translate "Bridges analyzed"): ${#bridges[@]}\n"
|
||||
analysis_report+="$(translate "Issues found"): $issues_found\n"
|
||||
|
||||
|
||||
local auto_only=$(grep "^auto" /etc/network/interfaces | awk '{print $2}' | while read i; do
|
||||
grep -q "^iface $i" /etc/network/interfaces || echo "$i"
|
||||
done)
|
||||
|
||||
if [ -n "$auto_only" ]; then
|
||||
analysis_report+="⚠️ $(translate "Interfaces defined with 'auto' but no 'iface' block"): $auto_only\n"
|
||||
((issues_found++))
|
||||
fi
|
||||
|
||||
analysis_report+="$(translate "Physical interfaces available"): ${#physical_interfaces[@]}\n\n"
|
||||
|
||||
if [ $issues_found -gt 0 ]; then
|
||||
analysis_report+="$suggestions"
|
||||
analysis_report+="⚠️ $(translate "IMPORTANT"): $(translate "No changes have been made to your system")\n"
|
||||
analysis_report+="$(translate "Use the Guided Repair option to fix issues safely")\n"
|
||||
else
|
||||
analysis_report+="✅ $(translate "No bridge configuration issues found")\n"
|
||||
fi
|
||||
|
||||
# Show analysis in scrollable dialog
|
||||
local temp_file=$(mktemp)
|
||||
echo -e "$analysis_report" > "$temp_file"
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Bridge Configuration Analysis")" \
|
||||
--textbox "$temp_file" 25 80
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Offer guided repair if issues found
|
||||
if [ $issues_found -gt 0 ]; then
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate "Guided Repair Available")" \
|
||||
--yesno "$(translate "Issues were found. Would you like to use the Guided Repair Assistant?")" 8 60; then
|
||||
guided_bridge_repair
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
guided_bridge_repair() {
|
||||
local step=1
|
||||
local total_steps=5
|
||||
|
||||
|
||||
local timestamp=$(date +"%Y%m%d_%H%M%S")
|
||||
local preview_backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
|
||||
|
||||
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Safety Backup")" \
|
||||
--yesno "$(translate "Before making any changes, we'll create a safety backup.")\n\n$(translate "Backup location"): $preview_backup_file\n\n$(translate "Continue?")" 12 70; then
|
||||
return
|
||||
fi
|
||||
((step++))
|
||||
|
||||
|
||||
show_proxmenux_logo
|
||||
local backup_file=$(backup_network_config)
|
||||
sleep 1
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Backup Created")" \
|
||||
--msgbox "$(translate "Safety backup created"): $backup_file\n\n$(translate "You can restore it anytime with"):\ncp $backup_file /etc/network/interfaces" 10 70
|
||||
|
||||
# Step 2: Show current configuration
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Current Configuration")" \
|
||||
--yesno "$(translate "Let's review your current network configuration.")\n\n$(translate "Would you like to see the current") /etc/network/interfaces $(translate "file?")" 10 70; then
|
||||
return
|
||||
fi
|
||||
((step++))
|
||||
|
||||
# Show current config
|
||||
local temp_config=$(mktemp)
|
||||
cat /etc/network/interfaces > "$temp_config"
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Current Network Configuration")" \
|
||||
--textbox "$temp_config" 20 80
|
||||
rm -f "$temp_config"
|
||||
|
||||
# Step 3: Identify specific changes needed
|
||||
local physical_interfaces=($(detect_physical_interfaces))
|
||||
local bridges=($(detect_bridge_interfaces))
|
||||
local changes_needed=""
|
||||
|
||||
for bridge in "${bridges[@]}"; do
|
||||
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
|
||||
|
||||
if [ -n "$current_ports" ]; then
|
||||
for port in $current_ports; do
|
||||
if ! ip link show "$port" >/dev/null 2>&1; then
|
||||
if [ ${#physical_interfaces[@]} -gt 0 ]; then
|
||||
changes_needed+="$(translate "Bridge") $bridge: $(translate "Replace") '$port' $(translate "with") '${physical_interfaces[0]}'\n"
|
||||
else
|
||||
changes_needed+="$(translate "Bridge") $bridge: $(translate "Remove invalid port") '$port'\n"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$changes_needed" ]; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "No Changes Needed")" \
|
||||
--msgbox "$(translate "After detailed analysis, no changes are needed.")" 8 50
|
||||
return
|
||||
fi
|
||||
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Proposed Changes")" \
|
||||
--yesno "$(translate "These are the changes that will be made"):\n\n$changes_needed\n$(translate "Do you want to proceed?")" 15 70; then
|
||||
return
|
||||
fi
|
||||
((step++))
|
||||
|
||||
# Step 4: Apply changes with verification
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Applying Changes")" \
|
||||
--infobox "$(translate "Applying changes safely...")\n\n$(translate "This may take a few seconds...")" 8 50
|
||||
|
||||
# Apply the changes
|
||||
for bridge in "${bridges[@]}"; do
|
||||
local current_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
|
||||
|
||||
if [ -n "$current_ports" ]; then
|
||||
local new_ports=""
|
||||
for port in $current_ports; do
|
||||
if ip link show "$port" >/dev/null 2>&1; then
|
||||
new_ports+="$port "
|
||||
fi
|
||||
done
|
||||
|
||||
# If no valid ports and we have physical interfaces, use the first one
|
||||
if [ -z "$new_ports" ] && [ ${#physical_interfaces[@]} -gt 0 ]; then
|
||||
new_ports="${physical_interfaces[0]}"
|
||||
fi
|
||||
|
||||
# Apply the change
|
||||
if [ "$new_ports" != "$current_ports" ]; then
|
||||
sed -i "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_ports/" /etc/network/interfaces
|
||||
fi
|
||||
fi
|
||||
done
|
||||
((step++))
|
||||
|
||||
# Step 5: Verification
|
||||
local verification_report=""
|
||||
verification_report+="✅ $(translate "CHANGES APPLIED SUCCESSFULLY")\n\n"
|
||||
verification_report+="$(translate "Verification"):\n"
|
||||
|
||||
for bridge in "${bridges[@]}"; do
|
||||
local new_ports=$(grep -A5 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep "bridge-ports" | cut -d' ' -f2-)
|
||||
verification_report+="$(translate "Bridge") $bridge: $new_ports\n"
|
||||
|
||||
# Verify each port exists
|
||||
for port in $new_ports; do
|
||||
if ip link show "$port" >/dev/null 2>&1; then
|
||||
verification_report+=" ✅ $port: $(translate "EXISTS")\n"
|
||||
else
|
||||
verification_report+=" ❌ $port: $(translate "NOT FOUND")\n"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
verification_report+="\n$(translate "Backup available at"): $backup_file\n"
|
||||
verification_report+="$(translate "To restore"): cp $backup_file /etc/network/interfaces"
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Repair Complete")" \
|
||||
--msgbox "$verification_report" 18 70
|
||||
|
||||
# Ask about network restart
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate "Network Restart")" \
|
||||
--yesno "$(translate "Changes have been applied to the configuration file.")\n\n$(translate "Do you want to restart the network service to apply changes?")\n\n$(translate "WARNING: This may cause a brief disconnection.")" 12 70; then
|
||||
|
||||
clear
|
||||
msg_info "$(translate "Restarting network service...")"
|
||||
|
||||
if systemctl restart networking; then
|
||||
msg_ok "$(translate "Network service restarted successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to restart network service")"
|
||||
msg_warn "$(translate "You can restore the backup with"): cp $backup_file /etc/network/interfaces"
|
||||
fi
|
||||
|
||||
msg_success "$(translate "Press ENTER to continue...")"
|
||||
read -r
|
||||
fi
|
||||
}
|
||||
|
||||
analyze_network_configuration() {
|
||||
|
||||
NETWORK_METHOD=$(detect_network_method)
|
||||
|
||||
if [[ "$NETWORK_METHOD" != "classic" ]]; then
|
||||
dialog --title "Unsupported Network Stack" \
|
||||
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
|
||||
exit 1
|
||||
fi
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Analyzing Network Configuration - READ ONLY MODE")"
|
||||
sleep 1
|
||||
|
||||
local configured_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
|
||||
local analysis_report=""
|
||||
local issues_found=0
|
||||
local suggestions=""
|
||||
|
||||
analysis_report+="🔍 $(translate "NETWORK CONFIGURATION ANALYSIS")\n"
|
||||
analysis_report+="$(printf '=%.0s' {1..50})\n\n"
|
||||
|
||||
cleanup
|
||||
if [ ${#configured_interfaces[@]} -eq 0 ]; then
|
||||
analysis_report+="ℹ️ $(translate "No network interfaces configured (besides loopback)")\n"
|
||||
dialog --title "$(translate "Configuration Analysis")" --msgbox "$analysis_report" 10 60
|
||||
return
|
||||
fi
|
||||
|
||||
analysis_report+="📋 $(translate "CONFIGURED INTERFACES")\n"
|
||||
analysis_report+="$(printf '=%.0s' {1..30})\n"
|
||||
|
||||
# Analyze each configured interface
|
||||
for iface in "${configured_interfaces[@]}"; do
|
||||
analysis_report+="🔌 $(translate "Interface"): $iface\n"
|
||||
|
||||
# Check if interface exists physically
|
||||
if ip link show "$iface" >/dev/null 2>&1; then
|
||||
local status=$(ip link show "$iface" 2>/dev/null | grep -o "state [A-Z]*" | cut -d' ' -f2)
|
||||
local ip=$(ip -4 addr show "$iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1)
|
||||
|
||||
analysis_report+=" ✅ $(translate "Status"): $(translate "EXISTS") ($status)\n"
|
||||
analysis_report+=" 🌐 $(translate "IP"): ${ip:-$(translate "No IP assigned")}\n"
|
||||
|
||||
# Check if it's a bridge or bond (these are virtual, so it's normal they exist)
|
||||
if [[ $iface =~ ^(vmbr|bond) ]]; then
|
||||
analysis_report+=" ℹ️ $(translate "Type"): $(translate "Virtual interface (normal)")\n"
|
||||
else
|
||||
analysis_report+=" ℹ️ $(translate "Type"): $(translate "Physical interface")\n"
|
||||
fi
|
||||
else
|
||||
analysis_report+=" ❌ $(translate "Status"): $(translate "NOT FOUND")\n"
|
||||
analysis_report+=" ⚠️ $(translate "Issue"): $(translate "Configured but doesn't exist")\n"
|
||||
((issues_found++))
|
||||
|
||||
# Only suggest removal for non-virtual interfaces
|
||||
if [[ ! $iface =~ ^(vmbr|bond) ]]; then
|
||||
suggestions+="🔧 $(translate "SUGGESTION FOR") $iface:\n"
|
||||
suggestions+=" $(translate "This interface is configured but doesn't exist physically")\n"
|
||||
suggestions+=" $(translate "Consider removing its configuration")\n"
|
||||
suggestions+=" $(translate "Command"): sed -i '/iface $iface/,/^$/d' /etc/network/interfaces\n\n"
|
||||
fi
|
||||
fi
|
||||
analysis_report+="\n"
|
||||
done
|
||||
|
||||
# Summary
|
||||
analysis_report+="📊 $(translate "ANALYSIS SUMMARY")\n"
|
||||
analysis_report+="$(printf '=%.0s' {1..25})\n"
|
||||
analysis_report+="$(translate "Interfaces configured"): ${#configured_interfaces[@]}\n"
|
||||
analysis_report+="$(translate "Issues found"): $issues_found\n\n"
|
||||
|
||||
if [ $issues_found -gt 0 ]; then
|
||||
analysis_report+="$suggestions"
|
||||
analysis_report+="⚠️ $(translate "IMPORTANT"): $(translate "No changes have been made to your system")\n"
|
||||
analysis_report+="$(translate "Use the Guided Cleanup option to fix issues safely")\n"
|
||||
else
|
||||
analysis_report+="✅ $(translate "No configuration issues found")\n"
|
||||
fi
|
||||
|
||||
# Show analysis in scrollable dialog
|
||||
local temp_file=$(mktemp)
|
||||
echo -e "$analysis_report" > "$temp_file"
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Network Configuration Analysis")" \
|
||||
--textbox "$temp_file" 25 80
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Offer guided cleanup if issues found
|
||||
if [ $issues_found -gt 0 ]; then
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate "Guided Cleanup Available")" \
|
||||
--yesno "$(translate "Issues were found. Would you like to use the Guided Cleanup Assistant?")" 8 60; then
|
||||
guided_configuration_cleanup
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
guided_configuration_cleanup() {
|
||||
local step=1
|
||||
local total_steps=5
|
||||
|
||||
local timestamp=$(date +"%Y%m%d_%H%M%S")
|
||||
local preview_backup_file="$BACKUP_DIR/interfaces_backup_$timestamp"
|
||||
|
||||
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Safety Backup")" \
|
||||
--yesno "$(translate "Before making any changes, we'll create a safety backup.")\n\n$(translate "Backup location"): $preview_backup_file\n\n$(translate "Continue?")" 12 70; then
|
||||
return
|
||||
fi
|
||||
((step++))
|
||||
|
||||
|
||||
show_proxmenux_logo
|
||||
local backup_file=$(backup_network_config)
|
||||
sleep 1
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Backup Created")" \
|
||||
--msgbox "$(translate "Safety backup created"): $backup_file\n\n$(translate "You can restore it anytime with"):\ncp $backup_file /etc/network/interfaces" 10 70
|
||||
|
||||
# Step 2: Identify interfaces to remove
|
||||
local configured_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
|
||||
local interfaces_to_remove=""
|
||||
local removal_list=""
|
||||
|
||||
for iface in "${configured_interfaces[@]}"; do
|
||||
if [[ ! $iface =~ ^(vmbr|bond) ]] && ! ip link show "$iface" >/dev/null 2>&1; then
|
||||
interfaces_to_remove+="$iface "
|
||||
removal_list+="❌ $iface: $(translate "Configured but doesn't exist")\n"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$interfaces_to_remove" ]; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "No Cleanup Needed")" \
|
||||
--msgbox "$(translate "After detailed analysis, no cleanup is needed.")" 8 50
|
||||
return
|
||||
fi
|
||||
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Interfaces to Remove")" \
|
||||
--yesno "$(translate "These interface configurations will be removed"):\n\n$removal_list\n$(translate "Do you want to proceed?")" 15 70; then
|
||||
return
|
||||
fi
|
||||
((step++))
|
||||
|
||||
# Step 3: Show what will be removed
|
||||
local temp_preview=$(mktemp)
|
||||
echo "$(translate "Configuration sections that will be REMOVED"):" > "$temp_preview"
|
||||
echo "=================================================" >> "$temp_preview"
|
||||
echo "" >> "$temp_preview"
|
||||
|
||||
for iface in $interfaces_to_remove; do
|
||||
echo "# Interface: $iface" >> "$temp_preview"
|
||||
sed -n "/^iface $iface/,/^$/p" /etc/network/interfaces >> "$temp_preview"
|
||||
echo "" >> "$temp_preview"
|
||||
done
|
||||
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Preview Changes")" \
|
||||
--yesno "$(translate "Review what will be removed"):\n\n$(translate "Press OK to see the preview, then confirm")" 10 60; then
|
||||
rm -f "$temp_preview"
|
||||
return
|
||||
fi
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Configuration to be Removed")" \
|
||||
--textbox "$temp_preview" 20 80
|
||||
rm -f "$temp_preview"
|
||||
|
||||
if ! dialog --backtitle "ProxMenux" --title "$(translate "Final Confirmation")" \
|
||||
--yesno "$(translate "Are you sure you want to remove these configurations?")" 8 60; then
|
||||
return
|
||||
fi
|
||||
((step++))
|
||||
|
||||
# Step 4: Apply changes
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Applying Changes")" \
|
||||
--infobox "$(translate "Removing invalid configurations...")\n\n$(translate "This may take a few seconds...")" 8 50
|
||||
|
||||
for iface in $interfaces_to_remove; do
|
||||
sed -i "/^iface $iface/,/^$/d" /etc/network/interfaces
|
||||
done
|
||||
((step++))
|
||||
|
||||
# Step 5: Verification
|
||||
local verification_report=""
|
||||
verification_report+="✅ $(translate "CLEANUP COMPLETED SUCCESSFULLY")\n\n"
|
||||
verification_report+="$(translate "Removed configurations for"):\n"
|
||||
|
||||
for iface in $interfaces_to_remove; do
|
||||
verification_report+="❌ $iface\n"
|
||||
done
|
||||
|
||||
verification_report+="\n$(translate "Verification"): $(translate "Checking remaining interfaces")\n"
|
||||
local remaining_interfaces=($(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo"))
|
||||
|
||||
for iface in "${remaining_interfaces[@]}"; do
|
||||
if ip link show "$iface" >/dev/null 2>&1; then
|
||||
verification_report+="✅ $iface: $(translate "OK")\n"
|
||||
else
|
||||
verification_report+="⚠️ $iface: $(translate "Still has issues")\n"
|
||||
fi
|
||||
done
|
||||
|
||||
verification_report+="\n$(translate "Backup available at"): $backup_file\n"
|
||||
verification_report+="$(translate "To restore"): cp $backup_file /etc/network/interfaces"
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Step") $step/$total_steps: $(translate "Cleanup Complete")" \
|
||||
--msgbox "$verification_report" 18 70
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Configuration Management
|
||||
show_network_config() {
|
||||
|
||||
NETWORK_METHOD=$(detect_network_method)
|
||||
|
||||
if [[ "$NETWORK_METHOD" != "classic" ]]; then
|
||||
dialog --title "Unsupported Network Stack" \
|
||||
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local config_content
|
||||
config_content=$(cat /etc/network/interfaces)
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
echo -e
|
||||
echo "========== $(translate "Network Configuration File") =========="
|
||||
echo
|
||||
cat /etc/network/interfaces
|
||||
echo
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
restore_network_backup() {
|
||||
|
||||
NETWORK_METHOD=$(detect_network_method)
|
||||
|
||||
if [[ "$NETWORK_METHOD" != "classic" ]]; then
|
||||
dialog --title "Unsupported Network Stack" \
|
||||
--msgbox "WARNING: This script only supports the classic Debian/Proxmox network configuration (/etc/network/interfaces).\n\nDetected: $NETWORK_METHOD.\n\nAborting for safety.\n\nPlease configure your network using your distribution's supported tools." 14 70
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local backups=($(ls -1 "$BACKUP_DIR"/interfaces_backup_* 2>/dev/null | sort -r))
|
||||
|
||||
if [ ${#backups[@]} -eq 0 ]; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "No Backups")" \
|
||||
--msgbox "\n$(translate "No network configuration backups found.")" 14 60
|
||||
return
|
||||
fi
|
||||
|
||||
local menu_items=()
|
||||
local counter=1
|
||||
|
||||
for backup in "${backups[@]}"; do
|
||||
local filename=$(basename "$backup")
|
||||
local timestamp=$(basename "$backup" | sed 's/interfaces_backup_//')
|
||||
menu_items+=("$counter" "$timestamp")
|
||||
((counter++))
|
||||
done
|
||||
|
||||
local selection=$(dialog --backtitle "ProxMenux" --title "$(translate "Restore Backup")" \
|
||||
--menu "$(translate "Select backup to restore:"):" 15 60 8 \
|
||||
"${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -n "$selection" ] && [ "$selection" -ge 1 ] && [ "$selection" -le ${#backups[@]} ]; then
|
||||
local selected_backup="${backups[$((selection-1))]}"
|
||||
|
||||
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate "Preview Backup")" \
|
||||
--yesno "\n$(translate "Do you want to view the selected backup before restoring?")" 8 60; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Backup Preview")" \
|
||||
--textbox "$selected_backup" 22 80
|
||||
fi
|
||||
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate "Confirm Restore")" \
|
||||
--yesno "\n$(translate "Are you sure you want to restore this backup?\nCurrent configuration will be overwritten.")\n\n$(translate "For your safety, a backup of the current configuration will be created automatically before restoring.")" 14 70; then
|
||||
|
||||
local pre_restore_backup=$(backup_network_config)
|
||||
cp "$selected_backup" /etc/network/interfaces
|
||||
|
||||
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Backup Restored")" \
|
||||
--msgbox "\n$(translate "Network configuration has been restored from backup.")" 8 60
|
||||
|
||||
|
||||
if dialog --backtitle "ProxMenux" --title "$(translate "Restart Network")" \
|
||||
--yesno "\n$(translate "Do you want to restart the network service now to apply changes?")" 8 60; then
|
||||
if systemctl restart networking; then
|
||||
dialog --backtitle "ProxMenux" --title "$(translate "Network Restarted")" \
|
||||
--msgbox "\n$(translate "Network service restarted successfully.")" 8 50
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Emergency System Repair Functions
|
||||
# ==========================================================
|
||||
|
||||
|
||||
emergency_proxmox_repair() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
echo "=========================================="
|
||||
echo " $(translate "EMERGENCY PROXMOX SYSTEM REPAIR")"
|
||||
echo "=========================================="
|
||||
echo
|
||||
|
||||
msg_warn "$(translate "This will reinstall core Proxmox packages and regenerate certificates")"
|
||||
echo "$(translate "This operation may take several minutes and requires internet connectivity.")"
|
||||
echo
|
||||
echo -n "$(translate "Do you want to continue?") (y/N): "
|
||||
read -r confirm
|
||||
|
||||
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||
msg_info2 "$(translate "Operation cancelled by user.")"
|
||||
return
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Starting Proxmox system repair...")"
|
||||
echo
|
||||
|
||||
# Step 1: Update package lists
|
||||
msg_success "$(translate "Step") 1/3: $(translate "Updating package lists...")"
|
||||
if apt-get update; then
|
||||
msg_ok "$(translate "Package lists updated successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists")"
|
||||
echo "$(translate "This might indicate network connectivity issues.")"
|
||||
echo
|
||||
echo "$(translate "Press ENTER to continue...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Step 2: Reinstall core Proxmox packages
|
||||
msg_success "$(translate "Step") 2/3: $(translate "Reinstalling core Proxmox packages...")"
|
||||
echo "$(translate "This may take several minutes...")"
|
||||
|
||||
if apt-get install --reinstall proxmox-widget-toolkit pve-manager -y; then
|
||||
msg_ok "$(translate "Core Proxmox packages reinstalled successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to reinstall Proxmox packages")"
|
||||
echo "$(translate "Check the error messages above for details.")"
|
||||
echo
|
||||
echo "$(translate "Press ENTER to continue...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Step 3: Regenerate certificates and restart services
|
||||
msg_success "$(translate "Step") 3/3: $(translate "Regenerating certificates and restarting services...")"
|
||||
|
||||
# Update certificates
|
||||
if command -v pvecm >/dev/null 2>&1; then
|
||||
msg_info "$(translate "Updating cluster certificates...")"
|
||||
if pvecm updatecerts -f; then
|
||||
msg_ok "$(translate "Cluster certificates updated")"
|
||||
else
|
||||
msg_warn "$(translate "Failed to update cluster certificates (might not be in a cluster)")"
|
||||
fi
|
||||
else
|
||||
msg_warn "$(translate "pvecm command not found (might not be in a cluster)")"
|
||||
fi
|
||||
|
||||
# Restart Proxmox services
|
||||
msg_success "$(translate "Restarting Proxmox services...")"
|
||||
local services_restarted=0
|
||||
local services_failed=0
|
||||
|
||||
for service in pveproxy pvedaemon; do
|
||||
if systemctl restart "$service"; then
|
||||
msg_ok " $service $(translate "restarted successfully")"
|
||||
((services_restarted++))
|
||||
else
|
||||
msg_error " $(translate "Failed to restart") $service"
|
||||
((services_failed++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "$(translate "REPAIR SUMMARY"):"
|
||||
echo "==============="
|
||||
echo " $(translate "Package lists"): $(translate "Updated")"
|
||||
echo " $(translate "Core packages"): $(translate "Reinstalled")"
|
||||
echo " $(translate "Services restarted"): $services_restarted"
|
||||
echo " $(translate "Services failed"): $services_failed"
|
||||
|
||||
if [ $services_failed -eq 0 ]; then
|
||||
msg_ok "$(translate "Proxmox system repair completed successfully!")"
|
||||
echo
|
||||
echo "$(translate "You should now be able to access the Proxmox web interface.")"
|
||||
echo "$(translate "Try accessing"): https://$(hostname -I | awk '{print $1}'):8006"
|
||||
else
|
||||
msg_warn "$(translate "Proxmox system repair completed with some issues.")"
|
||||
echo "$(translate "Check the service status manually if needed.")"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "$(translate "Press ENTER to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
|
||||
restart_network_service() {
|
||||
if dialog --title "$(translate "Restart Network")" \
|
||||
--yesno "$(translate "This will restart the network service and may cause a brief disconnection. Continue?")" 10 60; then
|
||||
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Restarting network service...")"
|
||||
|
||||
if systemctl restart networking; then
|
||||
msg_ok "$(translate "Network service restarted successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to restart network service")"
|
||||
msg_warn "$(translate "If you lose connectivity, you can restore from backup using the console.")"
|
||||
fi
|
||||
|
||||
msg_success "$(translate "Press ENTER to continue...")"
|
||||
read -r
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Main Menu
|
||||
show_main_menu() {
|
||||
while true; do
|
||||
local selection=$(dialog --clear \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate "Network Management - SAFE MODE")" \
|
||||
--menu "$(translate "Select an option:"):" 20 70 12 \
|
||||
"1" "$(translate "Test Connectivity")" \
|
||||
"2" "$(translate "Advanced Diagnostics")" \
|
||||
"3" "$(translate "Analyze Bridge Configuration")" \
|
||||
"4" "$(translate "Analyze Network Configuration")" \
|
||||
"5" "$(translate "Restart Network Service")" \
|
||||
"6" "$(translate "Show Network Config File")" \
|
||||
"7" "$(translate "Emergency Proxmox System Repair")" \
|
||||
"8" "$(translate "Restore Network Backup")" \
|
||||
"0" "$(translate "Exit")" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
case $selection in
|
||||
|
||||
1) test_connectivity ;;
|
||||
2) advanced_network_diagnostics ;;
|
||||
3) analyze_bridge_configuration ;;
|
||||
4) analyze_network_configuration ;;
|
||||
5) restart_network_service ;;
|
||||
6) show_network_config ;;
|
||||
7) emergency_proxmox_repair ;;
|
||||
8) restore_network_backup ;;
|
||||
0|"") exit ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
show_main_menu
|
||||
@@ -0,0 +1,335 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Common Functions for Proxmox VE Scripts
|
||||
# ==========================================================
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
get_pve_info() {
|
||||
local pve_full_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local pve_major=$(echo "$pve_full_version" | cut -d. -f1)
|
||||
local os_codename="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
|
||||
if [ -z "$os_codename" ]; then
|
||||
os_codename=$(lsb_release -cs 2>/dev/null)
|
||||
fi
|
||||
|
||||
|
||||
local target_codename
|
||||
if [ "$pve_major" -ge 9 ] 2>/dev/null; then
|
||||
target_codename="trixie"
|
||||
else
|
||||
target_codename="$os_codename"
|
||||
if [ -z "$target_codename" ]; then
|
||||
target_codename="bookworm"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$pve_full_version|$pve_major|$os_codename|$target_codename"
|
||||
}
|
||||
|
||||
|
||||
lvm_repair_check() {
|
||||
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
|
||||
|
||||
if ! command -v pvs >/dev/null 2>&1; then
|
||||
msg_info "$(translate "LVM tools not available, skipping LVM check")"
|
||||
return
|
||||
fi
|
||||
|
||||
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header" || true)
|
||||
if [ -z "$pvs_output" ]; then
|
||||
msg_ok "$(translate "No PVs with old headers found.")"
|
||||
return
|
||||
fi
|
||||
|
||||
declare -A vg_map
|
||||
while read -r line; do
|
||||
pv=$(echo "$line" | grep -o '/dev/[^ ]*' || true)
|
||||
if [ -n "$pv" ]; then
|
||||
vg=$(pvs -o vg_name --noheadings "$pv" 2>/dev/null | awk '{print $1}' || true)
|
||||
if [ -n "$vg" ]; then
|
||||
vg_map["$vg"]=1
|
||||
fi
|
||||
fi
|
||||
done <<< "$pvs_output"
|
||||
|
||||
for vg in "${!vg_map[@]}"; do
|
||||
msg_warn "$(translate "Old PV header(s) found in VG $vg. Updating metadata...")"
|
||||
vgck --updatemetadata "$vg" 2>/dev/null
|
||||
vgchange -ay "$vg" 2>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_warn "$(translate "Metadata update failed for VG $vg. Review manually.")"
|
||||
else
|
||||
msg_ok "$(translate "Metadata updated successfully for VG $vg")"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate "LVM PV headers check completed")"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
cleanup_duplicate_repos_pve9() {
|
||||
msg_info "$(translate "Cleaning up duplicate repositories...")"
|
||||
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
local temp_file=$(mktemp)
|
||||
local cleaned_count=0
|
||||
declare -A seen_repos
|
||||
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^deb ]]; then
|
||||
read -r _ url dist components <<< "$line"
|
||||
local key="${url}_${dist}"
|
||||
if [[ -v "seen_repos[$key]" ]]; then
|
||||
echo "# $line" >> "$temp_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
msg_info "$(translate "Commented duplicate: $url $dist")"
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
seen_repos[$key]="$components"
|
||||
fi
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$sources_file"
|
||||
|
||||
mv "$temp_file" "$sources_file"
|
||||
chmod 644 "$sources_file"
|
||||
|
||||
|
||||
if [ -f "/etc/apt/sources.list.d/proxmox.sources" ]; then
|
||||
|
||||
|
||||
|
||||
if grep -q "^deb.*download\.proxmox\.com" "$sources_file"; then
|
||||
sed -i '/^deb.*download\.proxmox\.com/s/^/# /' "$sources_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
|
||||
for list_file in /etc/apt/sources.list.d/pve-*.list; do
|
||||
if [ -f "$list_file" ] && [[ "$list_file" != "/etc/apt/sources.list.d/pve-enterprise.list" ]]; then
|
||||
if grep -q "^deb" "$list_file"; then
|
||||
sed -i 's/^deb/# deb/g' "$list_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -f "/etc/apt/sources.list.d/debian.sources" ]; then
|
||||
|
||||
if grep -q "^deb.*deb\.debian\.org" "$sources_file"; then
|
||||
sed -i '/^deb.*deb\.debian\.org/s/^/# /' "$sources_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
|
||||
fi
|
||||
|
||||
if grep -q "^deb.*security\.debian\.org" "$sources_file"; then
|
||||
sed -i '/^deb.*security\.debian\.org/s/^/# /' "$sources_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ -f "/etc/apt/sources.list.d/proxmox.sources" ]; then
|
||||
for old_file in /etc/apt/sources.list.d/pve-public-repo.list /etc/apt/sources.list.d/pve-install-repo.list; do
|
||||
if [ -f "$old_file" ]; then
|
||||
rm -f "$old_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ $cleaned_count -gt 0 ]; then
|
||||
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
|
||||
apt-get update > /dev/null 2>&1 || true
|
||||
else
|
||||
msg_ok "$(translate "No duplicate repositories found")"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
cleanup_duplicate_repos_pve9_() {
|
||||
msg_info "$(translate "Cleaning up duplicate repositories...")"
|
||||
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
local temp_file=$(mktemp)
|
||||
local cleaned_count=0
|
||||
declare -A seen_repos
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^deb ]]; then
|
||||
read -r _ url dist components <<< "$line"
|
||||
local key="${url}_${dist}"
|
||||
if [[ -v "seen_repos[$key]" ]]; then
|
||||
echo "# $line" >> "$temp_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
seen_repos[$key]="$components"
|
||||
fi
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$sources_file"
|
||||
|
||||
mv "$temp_file" "$sources_file"
|
||||
chmod 644 "$sources_file"
|
||||
|
||||
for src in proxmox debian ceph; do
|
||||
local sources_path="/etc/apt/sources.list.d/${src}.sources"
|
||||
if [ -f "$sources_path" ]; then
|
||||
case "$src" in
|
||||
proxmox)
|
||||
url_match="download.proxmox.com"
|
||||
;;
|
||||
debian)
|
||||
url_match="deb.debian.org"
|
||||
;;
|
||||
ceph)
|
||||
url_match="download.proxmox.com/ceph"
|
||||
;;
|
||||
*)
|
||||
url_match=""
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$url_match" ]]; then
|
||||
if grep -q "^deb.*$url_match" "$sources_file"; then
|
||||
sed -i "/^deb.*$url_match/s/^/# /" "$sources_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
for list_file in /etc/apt/sources.list.d/*.list; do
|
||||
[[ -f "$list_file" ]] || continue
|
||||
if grep -q "^deb.*$url_match" "$list_file"; then
|
||||
sed -i "/^deb.*$url_match/s/^/# /" "$list_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $cleaned_count -gt 0 ]; then
|
||||
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
|
||||
apt-get update > /dev/null 2>&1 || true
|
||||
else
|
||||
msg_ok "$(translate "No duplicate repositories found")"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cleanup_duplicate_repos_pve8() {
|
||||
msg_info "$(translate "Cleaning up duplicate repositories...")"
|
||||
|
||||
local cleaned_count=0
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
|
||||
|
||||
if [[ -f "$sources_file" ]]; then
|
||||
local temp_file
|
||||
temp_file=$(mktemp)
|
||||
declare -A seen_repos
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^[[:space:]]*deb ]]; then
|
||||
read -r _ url dist components <<< "$line"
|
||||
local key="${url}_${dist}"
|
||||
if [[ -v "seen_repos[$key]" ]]; then
|
||||
echo "# $line" >> "$temp_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
seen_repos[$key]="$components"
|
||||
fi
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$sources_file"
|
||||
|
||||
mv "$temp_file" "$sources_file"
|
||||
chmod 644 "$sources_file"
|
||||
fi
|
||||
|
||||
|
||||
local old_pve_files=(/etc/apt/sources.list.d/pve-*.list /etc/apt/sources.list.d/proxmox.list)
|
||||
|
||||
for file in "${old_pve_files[@]}"; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local base_name
|
||||
base_name=$(basename "$file" .list)
|
||||
local sources_equiv="/etc/apt/sources.list.d/${base_name}.sources"
|
||||
|
||||
if [[ -f "$sources_equiv" ]] && grep -q "^Enabled: *true" "$sources_equiv"; then
|
||||
msg_info "$(translate "Removing old repository file: $(basename "$file")")"
|
||||
rm -f "$file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [ "$cleaned_count" -gt 0 ]; then
|
||||
msg_ok "$(translate "Cleaned up $cleaned_count duplicate/old repositories")"
|
||||
apt-get update > /dev/null 2>&1 || true
|
||||
else
|
||||
msg_ok "$(translate "No duplicate repositories found")"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
cleanup_duplicate_repos() {
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
|
||||
if [[ -z "$pve_version" ]]; then
|
||||
msg_error "Unable to detect Proxmox version."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$pve_version" -ge 9 ]]; then
|
||||
cleanup_duplicate_repos_pve9
|
||||
else
|
||||
cleanup_duplicate_repos_pve8
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Remove Subscription Banner - Proxmox VE 8.4.9
|
||||
# ==========================================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
remove_subscription_banner_pve8() {
|
||||
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
local BACKUP_FILE="${JS_FILE}.bak.$(date +%F_%T)"
|
||||
|
||||
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
|
||||
local pve_major=$(echo "$pve_version" | cut -d. -f1)
|
||||
|
||||
if [[ "$pve_major" -ne 8 ]]; then
|
||||
msg_error "This script is only for Proxmox VE 8.x. Detected: $pve_version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "Detected Proxmox VE $pve_version - Applying safe JS patch..."
|
||||
|
||||
if [[ ! -f "$JS_FILE" ]]; then
|
||||
msg_error "JavaScript file not found: $JS_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$JS_FILE" "$BACKUP_FILE"
|
||||
|
||||
|
||||
sed -i "s/No valid subscription/Subscription active/g" "$JS_FILE"
|
||||
sed -i "s/Ext.Msg.WARNING/Ext.Msg.INFO/g" "$JS_FILE"
|
||||
sed -i "s/res.data.status.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
|
||||
|
||||
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
|
||||
|
||||
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
|
||||
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
|
||||
|
||||
|
||||
msg_ok "Subscription banner removed successfully."
|
||||
|
||||
register_tool "subscription_banner" true
|
||||
}
|
||||
|
||||
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
remove_subscription_banner_pve8
|
||||
fi
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Remove Subscription Banner - Proxmox VE 9.x ONLY
|
||||
# ==========================================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Tool registration system
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
remove_subscription_banner_pve9() {
|
||||
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
local MIN_JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js"
|
||||
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
|
||||
# Verify PVE 9.x
|
||||
local pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+' | head -1)
|
||||
local pve_major=$(echo "$pve_version" | cut -d. -f1)
|
||||
|
||||
if [ "$pve_major" -lt 9 ] 2>/dev/null; then
|
||||
msg_error "This script is for PVE 9.x only. Detected PVE $pve_version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "Detected Proxmox VE $pve_version - Applying PVE 9.x patches"
|
||||
|
||||
# Verify that the file exists
|
||||
if [ ! -f "$JS_FILE" ]; then
|
||||
msg_error "JavaScript file not found: $JS_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Create backup of original file
|
||||
local backup_file="${JS_FILE}.backup.pve9.$(date +%Y%m%d_%H%M%S)"
|
||||
cp "$JS_FILE" "$backup_file"
|
||||
|
||||
# Clean any existing problematic APT hooks
|
||||
for f in /etc/apt/apt.conf.d/*nag*; do
|
||||
[[ -e "$f" ]] && rm -f "$f"
|
||||
done
|
||||
|
||||
|
||||
# Main subscription check patches for PVE 9
|
||||
sed -i "s/res\.data\.status\.toLowerCase() !== 'active'/false/g" "$JS_FILE"
|
||||
sed -i "s/subscriptionActive: ''/subscriptionActive: true/g" "$JS_FILE"
|
||||
sed -i "s/title: gettext('No valid subscription')/title: gettext('Community Edition')/g" "$JS_FILE"
|
||||
|
||||
# Additional UX improvements for PVE 9
|
||||
sed -i "s/You do not have a valid subscription for this server/Community Edition - No subscription required/g" "$JS_FILE"
|
||||
sed -i "s/Enterprise repository needs valid subscription/Enterprise repository configured/g" "$JS_FILE"
|
||||
sed -i "s/icon: Ext\.Msg\.WARNING/icon: Ext.Msg.INFO/g" "$JS_FILE"
|
||||
|
||||
# Additional subscription patterns that may exist in PVE 9
|
||||
sed -i "s/subscription = !(/subscription = false \&\& (/g" "$JS_FILE"
|
||||
|
||||
# Remove compressed/minified files to force regeneration
|
||||
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
|
||||
[[ -f "$MIN_JS_FILE" ]] && rm -f "$MIN_JS_FILE"
|
||||
|
||||
# Clear various caches
|
||||
find /var/cache/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
find /var/lib/pve-manager/ -name "*.js*" -delete 2>/dev/null || true
|
||||
|
||||
# Create PVE 9.x specific APT hook
|
||||
[[ -f "$APT_HOOK" ]] && rm -f "$APT_HOOK"
|
||||
cat > "$APT_HOOK" << 'EOF'
|
||||
DPkg::Post-Invoke {
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/res\\.data\\.status\\.toLowerCase() !== '\''active'\''/false/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscriptionActive: '\'\'\''/subscriptionActive: true/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/title: gettext('\''No valid subscription'\'')/title: gettext('\''Community Edition'\'')/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"test -e /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js && sed -i 's/subscription = !(/subscription = false \\&\\& (/g' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js || true";
|
||||
"rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.min.js /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz || true";
|
||||
};
|
||||
EOF
|
||||
|
||||
chmod 644 "$APT_HOOK"
|
||||
|
||||
# Verify APT hook syntax
|
||||
if ! apt-config dump >/dev/null 2>&1; then
|
||||
msg_warn "APT hook has syntax issues, removing..."
|
||||
rm -f "$APT_HOOK"
|
||||
else
|
||||
msg_ok "APT hook created successfully"
|
||||
fi
|
||||
|
||||
|
||||
msg_ok "Subscription banner removed successfully for Proxmox VE $pve_version"
|
||||
msg_ok "Banner removal process completed"
|
||||
|
||||
|
||||
register_tool "subscription_banner" true
|
||||
}
|
||||
|
||||
|
||||
# Execute function if called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
remove_subscription_banner_pve9
|
||||
fi
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Proxmox VE Update Script
|
||||
# ==========================================================
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
download_common_functions() {
|
||||
if ! source <(curl -s "$REPO_URL/scripts/global/common-functions.sh"); then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_pve9() {
|
||||
local start_time=$(date +%s)
|
||||
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
|
||||
local changes_made=false
|
||||
local OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
local TARGET_CODENAME="trixie"
|
||||
|
||||
if [ -z "$OS_CODENAME" ]; then
|
||||
OS_CODENAME=$(lsb_release -cs 2>/dev/null || echo "trixie")
|
||||
fi
|
||||
|
||||
download_common_functions
|
||||
|
||||
|
||||
msg_info2 "$(translate "Detected: Proxmox VE 9.x (Current: $OS_CODENAME, Target: $TARGET_CODENAME)")"
|
||||
echo
|
||||
|
||||
local available_space=$(df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}')
|
||||
if [ "$available_space" -lt 1024 ]; then
|
||||
msg_error "$(translate "Insufficient disk space. Available: ${available_space}MB")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Cannot reach Proxmox repositories")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
disable_sources_repo() {
|
||||
local file="$1"
|
||||
if [[ -f "$file" ]]; then
|
||||
sed -i ':a;/^\n*$/{$d;N;ba}' "$file"
|
||||
|
||||
if grep -q "^Enabled:" "$file"; then
|
||||
sed -i 's/^Enabled:.*$/Enabled: false/' "$file"
|
||||
else
|
||||
echo "Enabled: false" >> "$file"
|
||||
fi
|
||||
|
||||
if ! grep -q "^Types: " "$file"; then
|
||||
msg_warn "$(translate "Malformed .sources file detected, removing: $(basename "$file")")"
|
||||
rm -f "$file"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if disable_sources_repo "/etc/apt/sources.list.d/pve-enterprise.sources"; then
|
||||
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
if disable_sources_repo "/etc/apt/sources.list.d/ceph.sources"; then
|
||||
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
for legacy_file in /etc/apt/sources.list.d/pve-public-repo.list \
|
||||
/etc/apt/sources.list.d/pve-install-repo.list \
|
||||
/etc/apt/sources.list.d/debian.list; do
|
||||
if [[ -f "$legacy_file" ]]; then
|
||||
rm -f "$legacy_file"
|
||||
msg_ok "$(translate "Removed legacy repository: $(basename "$legacy_file")")"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [[ -f /etc/apt/sources.list.d/debian.sources ]]; then
|
||||
rm -f /etc/apt/sources.list.d/debian.sources
|
||||
msg_ok "$(translate "Old debian.sources file removed to prevent duplication")"
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Creating Proxmox VE 9.x no-subscription repository...")"
|
||||
cat > /etc/apt/sources.list.d/proxmox.sources << EOF
|
||||
Enabled: true
|
||||
Types: deb
|
||||
URIs: http://download.proxmox.com/debian/pve
|
||||
Suites: ${TARGET_CODENAME}
|
||||
Components: pve-no-subscription
|
||||
Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
|
||||
EOF
|
||||
msg_ok "$(translate "Proxmox VE 9.x no-subscription repository created")"
|
||||
changes_made=true
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Creating Debian ${TARGET_CODENAME} sources file...")"
|
||||
cat > /etc/apt/sources.list.d/debian.sources << EOF
|
||||
Types: deb
|
||||
URIs: http://deb.debian.org/debian/
|
||||
Suites: ${TARGET_CODENAME} ${TARGET_CODENAME}-updates
|
||||
Components: main contrib non-free-firmware
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
|
||||
Types: deb
|
||||
URIs: http://security.debian.org/debian-security/
|
||||
Suites: ${TARGET_CODENAME}-security
|
||||
Components: main contrib non-free-firmware
|
||||
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
||||
EOF
|
||||
|
||||
|
||||
|
||||
msg_ok "$(translate "Debian repositories configured for $TARGET_CODENAME")"
|
||||
|
||||
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
|
||||
if [ ! -f "$firmware_conf" ]; then
|
||||
msg_info "$(translate "Disabling non-free firmware warnings...")"
|
||||
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
|
||||
msg_ok "$(translate "Non-free firmware warnings disabled")"
|
||||
fi
|
||||
|
||||
cleanup_duplicate_repos
|
||||
|
||||
update_output=$(apt-get update 2>&1)
|
||||
update_exit_code=$?
|
||||
|
||||
if [ $update_exit_code -eq 0 ]; then
|
||||
msg_ok "$(translate "Package lists updated successfully")"
|
||||
else
|
||||
if echo "$update_output" | grep -q "NO_PUBKEY\|GPG error"; then
|
||||
msg_info "$(translate "Fixing GPG key issues...")"
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $(echo "$update_output" | grep "NO_PUBKEY" | sed 's/.*NO_PUBKEY //' | head -1) 2>/dev/null
|
||||
if apt-get update > "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated after GPG fix")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
return 1
|
||||
fi
|
||||
elif echo "$update_output" | grep -q "404\|Failed to fetch"; then
|
||||
msg_warn "$(translate "Some repositories are not available, continuing with available ones...")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
echo "Error details: $update_output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
if apt policy 2>/dev/null | grep -q "${TARGET_CODENAME}.*pve-no-subscription"; then
|
||||
msg_ok "$(translate "Proxmox VE 9.x repositories verified")"
|
||||
else
|
||||
msg_warn "$(translate "Proxmox VE 9.x repositories verification inconclusive, continuing...")"
|
||||
fi
|
||||
|
||||
local current_pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local available_pve_version=$(apt-cache policy pve-manager 2>/dev/null | grep -oP 'Candidate: \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
|
||||
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
|
||||
|
||||
show_update_menu() {
|
||||
local current_version="$1"
|
||||
local target_version="$2"
|
||||
local upgradable_count="$3"
|
||||
local security_count="$4"
|
||||
|
||||
local menu_text="$(translate "System Update Information")\n\n"
|
||||
menu_text+="$(translate "Current PVE Version"): $current_version\n"
|
||||
if [ -n "$target_version" ] && [ "$target_version" != "$current_version" ]; then
|
||||
menu_text+="$(translate "Available PVE Version"): $target_version\n"
|
||||
fi
|
||||
menu_text+="\n$(translate "Package Updates Available"): $upgradable_count\n"
|
||||
menu_text+="$(translate "Security Updates"): $security_count\n\n"
|
||||
|
||||
if [ "$upgradable_count" -eq 0 ]; then
|
||||
menu_text+="$(translate "System is already up to date")"
|
||||
whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70
|
||||
return 2
|
||||
else
|
||||
menu_text+="$(translate "Do you want to proceed with the system update?")"
|
||||
if whiptail --title "$(translate "Proxmox Update")" --yesno "$menu_text" 18 70; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
show_update_menu "$current_pve_version" "$available_pve_version" "$upgradable" "$security_updates"
|
||||
MENU_RESULT=$?
|
||||
|
||||
if [[ $MENU_RESULT -eq 1 ]]; then
|
||||
msg_info2 "$(translate "Update cancelled by user")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
return 0
|
||||
elif [[ $MENU_RESULT -eq 2 ]]; then
|
||||
msg_ok "$(translate "System is already up to date. No update needed.")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Removing conflicting utilities...")"
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Conflicting utilities removed")"
|
||||
else
|
||||
msg_warn "$(translate "Some conflicting utilities may not have been removed")"
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Updating packages...")"
|
||||
apt-get install pv -y > /dev/null 2>&1
|
||||
msg_ok "$(translate "Packages updated successfully")"
|
||||
|
||||
tput sc
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y \
|
||||
-o Dpkg::Options::='--force-confdef' \
|
||||
-o Dpkg::Options::='--force-confold' \
|
||||
dist-upgrade 2>&1 | while IFS= read -r line; do
|
||||
|
||||
echo "$line" >> "$log_file"
|
||||
|
||||
if [[ "$line" =~ \[[#=\-]+\]\ *[0-9]{1,3}% ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^(Setting\ up|Unpacking|Preparing\ to\ unpack|Processing\ triggers\ for) ]]; then
|
||||
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ :]+).*/\2/')
|
||||
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
|
||||
|
||||
row=$(( $(tput lines) - 6 ))
|
||||
tput cup $row 0; printf "%s\n" "$(translate "Installing packages...")"
|
||||
tput cup $((row + 1)) 0; printf "%s\n" "──────────────────────────────────────────────"
|
||||
tput cup $((row + 2)) 0; printf "%s %s\n" "$(translate "Package:")" "$package_name"
|
||||
tput cup $((row + 3)) 0; printf "%s\n" "Progress: [ ] 0%"
|
||||
tput cup $((row + 4)) 0; printf "%s\n" "──────────────────────────────────────────────"
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
sleep 0.1
|
||||
progress=$((i * 10))
|
||||
tput cup $((row + 3)) 9
|
||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
tput rc
|
||||
tput ed
|
||||
|
||||
upgrade_exit_code=${PIPESTATUS[0]}
|
||||
|
||||
|
||||
|
||||
|
||||
if [ $upgrade_exit_code -eq 0 ]; then
|
||||
msg_ok "$(translate "System upgrade completed successfully")"
|
||||
else
|
||||
msg_error "$(translate "System upgrade failed. Check log: $log_file")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Installing essential Proxmox packages...")"
|
||||
local additional_packages="zfsutils-linux proxmox-backup-restore-image chrony"
|
||||
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install $additional_packages >> "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Essential Proxmox packages installed")"
|
||||
else
|
||||
msg_warn "$(translate "Some essential Proxmox packages may not have been installed")"
|
||||
fi
|
||||
|
||||
lvm_repair_check
|
||||
cleanup_duplicate_repos
|
||||
|
||||
msg_info "$(translate "Performing system cleanup...")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
local minutes=$((duration / 60))
|
||||
local seconds=$((duration % 60))
|
||||
|
||||
echo -e "${TAB}${BGN}$(translate "====== PVE UPDATE COMPLETED ======")${CL}"
|
||||
echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}"
|
||||
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}"
|
||||
echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}"
|
||||
echo -e "${TAB}${GN}🖥️ $(translate "Proxmox VE")${CL}: ${BL}$target_version (Debian $OS_CODENAME)${CL}"
|
||||
|
||||
msg_ok "$(translate "Proxmox VE 9.x configuration completed.")"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
update_pve9
|
||||
fi
|
||||
@@ -0,0 +1,281 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# Proxmox VE 8.x Update Script
|
||||
# ==========================================================
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
download_common_functions() {
|
||||
if ! source <(curl -s "$REPO_URL/scripts/global/common-functions.sh"); then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
update_pve8() {
|
||||
local start_time=$(date +%s)
|
||||
local log_file="/var/log/proxmox-update-$(date +%Y%m%d-%H%M%S).log"
|
||||
local changes_made=false
|
||||
local OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
|
||||
if [ -z "$OS_CODENAME" ]; then
|
||||
OS_CODENAME=$(lsb_release -cs 2>/dev/null || echo "bookworm")
|
||||
fi
|
||||
|
||||
download_common_functions
|
||||
|
||||
msg_info2 "$(translate "Detected: Proxmox VE 8.x (Debian $OS_CODENAME)")"
|
||||
echo
|
||||
|
||||
local available_space=$(df /var/cache/apt/archives | awk 'NR==2 {print int($4/1024)}')
|
||||
if [ "$available_space" -lt 1024 ]; then
|
||||
msg_error "$(translate "Insufficient disk space. Available: ${available_space}MB")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! ping -c 1 download.proxmox.com >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Cannot reach Proxmox repositories")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
|
||||
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
|
||||
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
|
||||
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
|
||||
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
|
||||
echo "deb http://download.proxmox.com/debian/pve $OS_CODENAME pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
|
||||
msg_ok "$(translate "Free public Proxmox repository enabled")"
|
||||
changes_made=true
|
||||
fi
|
||||
|
||||
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
cp "$sources_file" "${sources_file}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||
|
||||
if grep -q -E "(debian-security -security|debian main$|debian -updates)" "$sources_file"; then
|
||||
sed -i '/^deb.*debian-security -security/d' "$sources_file"
|
||||
sed -i '/^deb.*debian main$/d' "$sources_file"
|
||||
sed -i '/^deb.*debian -updates/d' "$sources_file"
|
||||
changes_made=true
|
||||
msg_ok "$(translate "Malformed repository entries cleaned")"
|
||||
fi
|
||||
|
||||
cat > "$sources_file" << EOF
|
||||
# Debian $OS_CODENAME repositories
|
||||
deb http://deb.debian.org/debian $OS_CODENAME main contrib non-free non-free-firmware
|
||||
deb http://deb.debian.org/debian $OS_CODENAME-updates main contrib non-free non-free-firmware
|
||||
deb http://security.debian.org/debian-security $OS_CODENAME-security main contrib non-free non-free-firmware
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "Debian repositories configured for $OS_CODENAME")"
|
||||
|
||||
local firmware_conf="/etc/apt/apt.conf.d/no-firmware-warnings.conf"
|
||||
if [ ! -f "$firmware_conf" ]; then
|
||||
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > "$firmware_conf"
|
||||
fi
|
||||
|
||||
cleanup_duplicate_repos
|
||||
|
||||
msg_info "$(translate "Updating package lists...")"
|
||||
if apt-get update > "$log_file" 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists. Check log: $log_file")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local current_pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local available_pve_version=$(apt-cache policy pve-manager 2>/dev/null | grep -oP 'Candidate: \K[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
||||
local upgradable=$(apt list --upgradable 2>/dev/null | grep -c "upgradable")
|
||||
local security_updates=$(apt list --upgradable 2>/dev/null | grep -c "security")
|
||||
|
||||
show_update_menu() {
|
||||
local current_version="$1"
|
||||
local target_version="$2"
|
||||
local upgradable_count="$3"
|
||||
local security_count="$4"
|
||||
|
||||
local menu_text="$(translate "System Update Information")\n\n"
|
||||
menu_text+="$(translate "Current PVE Version"): $current_version\n"
|
||||
if [ -n "$target_version" ] && [ "$target_version" != "$current_version" ]; then
|
||||
menu_text+="$(translate "Available PVE Version"): $target_version\n"
|
||||
fi
|
||||
menu_text+="\n$(translate "Package Updates Available"): $upgradable_count\n"
|
||||
menu_text+="$(translate "Security Updates"): $security_count\n\n"
|
||||
|
||||
if [ "$upgradable_count" -eq 0 ]; then
|
||||
menu_text+="$(translate "System is already up to date")"
|
||||
whiptail --title "$(translate "Update Status")" --msgbox "$menu_text" 15 70
|
||||
return 2
|
||||
else
|
||||
menu_text+="$(translate "Do you want to proceed with the system update?")"
|
||||
if whiptail --title "$(translate "Proxmox Update")" --yesno "$menu_text" 18 70; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
show_update_menu "$current_pve_version" "$available_pve_version" "$upgradable" "$security_updates"
|
||||
MENU_RESULT=$?
|
||||
|
||||
if [[ $MENU_RESULT -eq 1 ]]; then
|
||||
msg_info2 "$(translate "Update cancelled by user")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
return 0
|
||||
elif [[ $MENU_RESULT -eq 2 ]]; then
|
||||
msg_ok "$(translate "System is already up to date. No update needed.")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
local conflicting_packages=$(dpkg -l 2>/dev/null | grep -E "^ii.*(ntp|openntpd|systemd-timesyncd)" | awk '{print $2}')
|
||||
if [ -n "$conflicting_packages" ]; then
|
||||
msg_info "$(translate "Removing conflicting utilities...")"
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y purge $conflicting_packages >> "$log_file" 2>&1
|
||||
msg_ok "$(translate "Conflicting utilities removed")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Performing packages upgrade...")"
|
||||
apt-get install pv -y > /dev/null 2>&1
|
||||
total_packages=$(apt-get -s dist-upgrade | grep "^Inst" | wc -l)
|
||||
msg_ok "$(translate "Packages upgrade successfull")"
|
||||
|
||||
if [ "$total_packages" -eq 0 ]; then
|
||||
total_packages=1
|
||||
fi
|
||||
|
||||
tput civis
|
||||
tput sc
|
||||
|
||||
|
||||
(
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' dist-upgrade 2>&1 | \
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ]]; then
|
||||
|
||||
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ ]+).*/\2/')
|
||||
|
||||
|
||||
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
|
||||
|
||||
|
||||
tput rc
|
||||
tput ed
|
||||
|
||||
|
||||
row=$(( $(tput lines) - 6 ))
|
||||
tput cup $row 0; echo "$(translate "Installing packages...")"
|
||||
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
|
||||
tput cup $((row + 2)) 0; echo "Package: $package_name"
|
||||
tput cup $((row + 3)) 0; echo "Progress: [ ] 0%"
|
||||
tput cup $((row + 4)) 0; echo "──────────────────────────────────────────────"
|
||||
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
progress=$((i * 10))
|
||||
tput cup $((row + 3)) 9
|
||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||
|
||||
done
|
||||
fi
|
||||
done
|
||||
)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
tput rc
|
||||
tput ed
|
||||
msg_ok "$(translate "System upgrade completed")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
local essential_packages=("zfsutils-linux" "proxmox-backup-restore-image" "chrony")
|
||||
local missing_packages=()
|
||||
|
||||
for package in "${essential_packages[@]}"; do
|
||||
if ! dpkg -l 2>/dev/null | grep -q "^ii $package "; then
|
||||
missing_packages+=("$package")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_packages[@]} -gt 0 ]; then
|
||||
msg_info "$(translate "Installing essential Proxmox packages...")"
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y install "${missing_packages[@]}" >> "$log_file" 2>&1
|
||||
msg_ok "$(translate "Essential Proxmox packages installed")"
|
||||
fi
|
||||
|
||||
lvm_repair_check
|
||||
cleanup_duplicate_repos
|
||||
|
||||
msg_info "$(translate "Performing system cleanup...")"
|
||||
apt-get -y autoremove > /dev/null 2>&1 || true
|
||||
apt-get -y autoclean > /dev/null 2>&1 || true
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$((end_time - start_time))
|
||||
local minutes=$((duration / 60))
|
||||
local seconds=$((duration % 60))
|
||||
|
||||
echo -e "${TAB}${BGN}$(translate "====== PVE UPDATE COMPLETED ======")${CL}"
|
||||
echo -e "${TAB}${GN}⏱️ $(translate "Duration")${CL}: ${BL}${minutes}m ${seconds}s${CL}"
|
||||
echo -e "${TAB}${GN}📄 $(translate "Log file")${CL}: ${BL}$log_file${CL}"
|
||||
echo -e "${TAB}${GN}📦 $(translate "Packages upgraded")${CL}: ${BL}$upgradable${CL}"
|
||||
echo -e "${TAB}${GN}🖥️ $(translate "Proxmox VE")${CL}: ${BL}$target_version (Debian $OS_CODENAME)${CL}"
|
||||
|
||||
|
||||
|
||||
msg_ok "$(translate "Proxmox VE 8 system update completed successfully")"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
update_pve8
|
||||
fi
|
||||
+119
-167
@@ -6,15 +6,15 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.1
|
||||
# Last Updated: 29/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
|
||||
# making it easy to attach pre-existing disk files without manual configuration.
|
||||
#
|
||||
# Before running the script, ensure that disk images are available in /var/lib/vz/template/images/.
|
||||
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk) and lists the available files.
|
||||
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk, .raw) and lists the available files.
|
||||
#
|
||||
# Using an interactive menu, you can:
|
||||
# - Select a VM to attach the imported disk.
|
||||
@@ -32,211 +32,163 @@ BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
load_language
|
||||
initialize_cache
|
||||
show_proxmenux_logo
|
||||
# ==========================================================
|
||||
|
||||
# Path where disk images are stored
|
||||
IMAGES_DIR="/var/lib/vz/template/images/"
|
||||
# Configuration ============================================
|
||||
|
||||
|
||||
# Initial setup
|
||||
if [ ! -d "$IMAGES_DIR" ]; then
|
||||
msg_info "$(translate 'Creating images directory')"
|
||||
mkdir -p "$IMAGES_DIR"
|
||||
chmod 755 "$IMAGES_DIR"
|
||||
msg_ok "$(translate 'Images directory created:') $IMAGES_DIR"
|
||||
detect_image_dir() {
|
||||
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
|
||||
path=$(pvesm path "${store}:template" 2>/dev/null)
|
||||
if [[ -d "$path" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$path/*.$ext" > /dev/null; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
for sub in images iso; do
|
||||
dir="$path/$sub"
|
||||
if [[ -d "$dir" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$dir/*.$ext" > /dev/null; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
for fallback in /var/lib/vz/template/images /var/lib/vz/template/iso; do
|
||||
if [[ -d "$fallback" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$fallback/*.$ext" > /dev/null; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
IMAGES_DIR=$(detect_image_dir)
|
||||
if [[ -z "$IMAGES_DIR" ]]; then
|
||||
dialog --title "$(translate 'No Images Found')" \
|
||||
--msgbox "$(translate 'Could not find any directory containing disk images')\n\n$(translate 'Make sure there is at least one file with extension .img, .qcow2, .vmdk or .raw')" 15 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Check if there are any images in the directory
|
||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk)$")
|
||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
|
||||
if [ -z "$IMAGES" ]; then
|
||||
whiptail --title "$(translate 'No Images Found')" \
|
||||
--msgbox "$(translate 'No images available for import in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk')\n\n$(translate 'Please add some images and try again.')" 15 60
|
||||
exit 1
|
||||
dialog --title "$(translate 'No Disk Images Found')" \
|
||||
--msgbox "$(translate 'No compatible disk images found in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk, .raw')" 15 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Display initial message
|
||||
whiptail --title "$(translate 'Import Disk Image')" --msgbox "$(translate 'Make sure the disk images you want to import are located in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk.')" 15 60
|
||||
|
||||
|
||||
|
||||
# 1. Select VM
|
||||
# === Select VM
|
||||
msg_info "$(translate 'Getting VM list')"
|
||||
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
|
||||
if [ -z "$VM_LIST" ]; then
|
||||
msg_error "$(translate 'No VMs available in the system')"
|
||||
exit 1
|
||||
fi
|
||||
[[ -z "$VM_LIST" ]] && { msg_error "$(translate 'No VMs available in the system')"; exit 1; }
|
||||
msg_ok "$(translate 'VM list obtained')"
|
||||
|
||||
VMID=$(whiptail --title "$(translate 'Select VM')" --menu "$(translate 'Select the VM where you want to import the disk image:')" 15 60 8 $VM_LIST 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$VMID" ]; then
|
||||
# msg_error "$(translate 'No VM selected')"
|
||||
exit 1
|
||||
fi
|
||||
VMID=$(whiptail --title "$(translate 'Select VM')" \
|
||||
--menu "$(translate 'Select the VM where you want to import the disk image:')" 20 70 10 $VM_LIST 3>&1 1>&2 2>&3)
|
||||
[[ -z "$VMID" ]] && exit 1
|
||||
|
||||
|
||||
|
||||
# 2. Select storage volume
|
||||
|
||||
# === Select storage
|
||||
msg_info "$(translate 'Getting storage volumes')"
|
||||
STORAGE_LIST=$(pvesm status -content images | awk 'NR>1 {print $1}')
|
||||
if [ -z "$STORAGE_LIST" ]; then
|
||||
msg_error "$(translate 'No storage volumes available')"
|
||||
exit 1
|
||||
fi
|
||||
[[ -z "$STORAGE_LIST" ]] && { msg_error "$(translate 'No storage volumes available')"; exit 1; }
|
||||
msg_ok "$(translate 'Storage volumes obtained')"
|
||||
|
||||
# Create an array of storage options for whiptail
|
||||
STORAGE_OPTIONS=()
|
||||
while read -r storage; do
|
||||
STORAGE_OPTIONS+=("$storage" "")
|
||||
done <<< "$STORAGE_LIST"
|
||||
|
||||
STORAGE=$(whiptail --title "$(translate 'Select Storage')" --menu "$(translate 'Select the storage volume for disk import:')" 15 60 8 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$STORAGE" ]; then
|
||||
# msg_error "$(translate 'No storage selected')"
|
||||
exit 1
|
||||
fi
|
||||
while read -r storage; do STORAGE_OPTIONS+=("$storage" ""); done <<< "$STORAGE_LIST"
|
||||
STORAGE=$(whiptail --title "$(translate 'Select Storage')" \
|
||||
--menu "$(translate 'Select the storage volume for disk import:')" 20 70 10 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$STORAGE" ]] && exit 1
|
||||
|
||||
|
||||
|
||||
# 3. Select disk images
|
||||
msg_info "$(translate 'Scanning disk images')"
|
||||
if [ -z "$IMAGES" ]; then
|
||||
msg_warn "$(translate 'No compatible disk images found in') $IMAGES_DIR"
|
||||
exit 0
|
||||
fi
|
||||
msg_ok "$(translate 'Disk images found')"
|
||||
|
||||
# === Select images
|
||||
IMAGE_OPTIONS=()
|
||||
while read -r img; do
|
||||
IMAGE_OPTIONS+=("$img" "" "OFF")
|
||||
done <<< "$IMAGES"
|
||||
while read -r img; do IMAGE_OPTIONS+=("$img" "" "OFF"); done <<< "$IMAGES"
|
||||
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" \
|
||||
--checklist "$(translate 'Select the disk images to import:')" 20 70 12 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$SELECTED_IMAGES" ]] && exit 1
|
||||
|
||||
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" --checklist "$(translate 'Select the disk images to import:')" 20 60 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED_IMAGES" ]; then
|
||||
# msg_error "$(translate 'No images selected')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. Import each selected image
|
||||
# === Import each selected image
|
||||
for IMAGE in $SELECTED_IMAGES; do
|
||||
IMAGE=$(echo "$IMAGE" | tr -d '"')
|
||||
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
|
||||
"sata" "SATA" "scsi" "SCSI" "virtio" "VirtIO" "ide" "IDE" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$INTERFACE" ]] && { msg_error "$(translate 'No interface type selected for') $IMAGE"; continue; }
|
||||
|
||||
# Remove quotes from selected image
|
||||
IMAGE=$(echo "$IMAGE" | tr -d '"')
|
||||
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
||||
msg_info "$(translate 'Importing image:') $IMAGE"
|
||||
TEMP_DISK_FILE=$(mktemp)
|
||||
|
||||
# 5. Select interface type for each image
|
||||
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
|
||||
"sata" "SATA" \
|
||||
"scsi" "SCSI" \
|
||||
"virtio" "VirtIO" \
|
||||
"ide" "IDE" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$INTERFACE" ]; then
|
||||
msg_error "$(translate 'No interface type selected for') $IMAGE"
|
||||
continue
|
||||
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
|
||||
if [[ "$line" =~ transferred ]]; then
|
||||
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
||||
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
|
||||
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
||||
fi
|
||||
|
||||
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
||||
|
||||
# Show initial message
|
||||
msg_info "$(translate 'Importing image:')"
|
||||
|
||||
# Temporary file to capture the imported disk
|
||||
TEMP_DISK_FILE=$(mktemp)
|
||||
done
|
||||
echo -ne "\n"
|
||||
IMPORT_STATUS=${PIPESTATUS[0]}
|
||||
|
||||
|
||||
# Execute the command and process its output in real-time
|
||||
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
|
||||
if [[ "$line" =~ transferred ]]; then
|
||||
|
||||
# Extract the progress percentage
|
||||
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
||||
if [ "$IMPORT_STATUS" -eq 0 ]; then
|
||||
msg_ok "$(translate 'Image imported successfully')"
|
||||
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
|
||||
rm -f "$TEMP_DISK_FILE"
|
||||
|
||||
# Show progress with custom format without translation
|
||||
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||
if [ -n "$IMPORTED_DISK" ]; then
|
||||
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
|
||||
NEXT_SLOT=0
|
||||
[[ -n "$EXISTING_DISKS" ]] && NEXT_SLOT=$(( $(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//") + 1 ))
|
||||
|
||||
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
|
||||
SSD_OPTION=""
|
||||
if [ "$INTERFACE" != "virtio" ]; then
|
||||
whiptail --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60 && SSD_OPTION=",ssd=1"
|
||||
fi
|
||||
|
||||
# Extract the imported disk name and save it to the temporary file
|
||||
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
||||
fi
|
||||
done
|
||||
echo -ne "\n"
|
||||
|
||||
|
||||
IMPORT_STATUS=${PIPESTATUS[0]} # Capture the exit status of the main command
|
||||
|
||||
if [ $IMPORT_STATUS -eq 0 ]; then
|
||||
msg_ok "$(translate 'Image imported successfully')"
|
||||
|
||||
# Read the imported disk from the temporary file
|
||||
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
|
||||
rm -f "$TEMP_DISK_FILE" # Delete the temporary file
|
||||
|
||||
if [ -n "$IMPORTED_DISK" ]; then
|
||||
|
||||
# Find the next available disk slot
|
||||
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
|
||||
if [ -z "$EXISTING_DISKS" ]; then
|
||||
|
||||
# If there are no existing disks, start from 0
|
||||
NEXT_SLOT=0
|
||||
else
|
||||
# If there are existing disks, take the last one and add 1
|
||||
LAST_SLOT=$(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//")
|
||||
NEXT_SLOT=$((LAST_SLOT + 1))
|
||||
fi
|
||||
|
||||
|
||||
# Ask if SSD emulation is desired (only for non-VirtIO interfaces)
|
||||
if [ "$INTERFACE" != "virtio" ]; then
|
||||
if (whiptail --title "$(translate 'SSD Emulation')" --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60); then
|
||||
SSD_OPTION=",ssd=1"
|
||||
else
|
||||
SSD_OPTION=""
|
||||
fi
|
||||
else
|
||||
SSD_OPTION=""
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate 'Configuring disk')"
|
||||
|
||||
# Configure the disk in the VM
|
||||
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
|
||||
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
|
||||
|
||||
# Ask if the disk should be bootable
|
||||
if (whiptail --title "$(translate 'Make Bootable')" --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60); then
|
||||
msg_info "$(translate 'Configuring disk as bootable')"
|
||||
|
||||
if qm set "$VMID" --boot c --bootdisk ${INTERFACE}${NEXT_SLOT} &>/dev/null; then
|
||||
msg_ok "$(translate 'Disk configured as bootable')"
|
||||
else
|
||||
msg_error "$(translate 'Could not configure the disk as bootable')"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not find the imported disk')"
|
||||
fi
|
||||
msg_info "$(translate 'Configuring disk')"
|
||||
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
|
||||
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
|
||||
whiptail --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60 && {
|
||||
msg_info "$(translate 'Configuring disk as bootable')"
|
||||
if qm set "$VMID" --boot c --bootdisk ${INTERFACE}${NEXT_SLOT} &>/dev/null; then
|
||||
msg_ok "$(translate 'Disk configured as bootable')"
|
||||
else
|
||||
msg_error "$(translate 'Could not configure the disk as bootable')"
|
||||
fi
|
||||
}
|
||||
else
|
||||
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not import') $IMAGE"
|
||||
msg_error "$(translate 'Could not find the imported disk')"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not import') $IMAGE"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate 'All selected images have been processed')"
|
||||
sleep 2
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
@@ -4,10 +4,11 @@
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Revision : @Blaspt (USB passthrough via udev rule with persistent /dev/coral)
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.1
|
||||
# Last Updated: 16/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script automates the configuration and installation of
|
||||
@@ -17,13 +18,10 @@
|
||||
# - Installs necessary drivers inside the container
|
||||
# - Manages required system and container restarts
|
||||
#
|
||||
# The script aims to simplify the process of enabling
|
||||
# AI-powered video analysis capabilities in containers
|
||||
# LXC, leveraging hardware acceleration for
|
||||
# improved performance.
|
||||
# Supports Coral USB and Coral M.2 (PCIe) devices.
|
||||
# Includes USB passthrough enhancement using persistent udev alias (/dev/coral).
|
||||
# ==========================================================
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
@@ -38,10 +36,7 @@ initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Select LXC container
|
||||
select_container() {
|
||||
|
||||
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | xargs -n2)
|
||||
if [ -z "$CONTAINERS" ]; then
|
||||
msg_error "$(translate 'No containers available in Proxmox.')"
|
||||
@@ -49,7 +44,7 @@ select_container() {
|
||||
fi
|
||||
|
||||
CONTAINER_ID=$(whiptail --title "$(translate 'Select Container')" \
|
||||
--menu "$(translate 'Select the LXC container:')" 15 60 5 $CONTAINERS 3>&1 1>&2 2>&3)
|
||||
--menu "$(translate 'Select the LXC container:')" 20 70 10 $CONTAINERS 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
msg_error "$(translate 'No container selected. Exiting.')"
|
||||
@@ -64,15 +59,12 @@ select_container() {
|
||||
msg_ok "$(translate 'Container selected:') $CONTAINER_ID"
|
||||
}
|
||||
|
||||
|
||||
# Validate that the selected container is valid
|
||||
validate_container_id() {
|
||||
if [ -z "$CONTAINER_ID" ]; then
|
||||
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the container is running and stop it before configuration
|
||||
if pct status "$CONTAINER_ID" | grep -q "running"; then
|
||||
msg_info "$(translate 'Stopping the container before applying configuration...')"
|
||||
pct stop "$CONTAINER_ID"
|
||||
@@ -81,7 +73,48 @@ validate_container_id() {
|
||||
}
|
||||
|
||||
|
||||
# Configure LXC for Coral TPU and iGPU
|
||||
add_udev_rule_for_coral_usb_() {
|
||||
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
|
||||
RULE_CONTENT='SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess"'
|
||||
|
||||
if [[ ! -f "$RULE_FILE" ]] || ! grep -qF "$RULE_CONTENT" "$RULE_FILE"; then
|
||||
echo "$RULE_CONTENT" > "$RULE_FILE"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
msg_ok "$(translate 'Udev rule for Coral USB added and rules reloaded.')"
|
||||
else
|
||||
msg_ok "$(translate 'Udev rule for Coral USB already exists.')"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
add_udev_rule_for_coral_usb() {
|
||||
RULE_FILE="/etc/udev/rules.d/99-coral-usb.rules"
|
||||
RULE_CONTENT='# Coral USB Accelerator
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral"
|
||||
# Coral Dev Board / Mini PCIe
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="1a6e", ATTRS{idProduct}=="089a", MODE="0666", TAG+="uaccess", SYMLINK+="coral"'
|
||||
|
||||
if [[ ! -f "$RULE_FILE" ]] || ! grep -q "18d1.*9302\|1a6e.*089a" "$RULE_FILE"; then
|
||||
echo "$RULE_CONTENT" > "$RULE_FILE"
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices added and rules reloaded.')"
|
||||
else
|
||||
msg_ok "$(translate 'Udev rules for Coral USB devices already exist.')"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
add_mount_if_needed() {
|
||||
local DEVICE="$1"
|
||||
local DEST="$2"
|
||||
local CONFIG_FILE="$3"
|
||||
if [ -e "$DEVICE" ] && ! grep -q "lxc.mount.entry: $DEVICE" "$CONFIG_FILE"; then
|
||||
echo "lxc.mount.entry: $DEVICE $DEST none bind,optional,create=$( [ -c "$DEVICE" ] && echo file || echo dir )" >> "$CONFIG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
configure_lxc_hardware() {
|
||||
validate_container_id
|
||||
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
|
||||
@@ -90,6 +123,7 @@ configure_lxc_hardware() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Privileged container
|
||||
if grep -q "^unprivileged: 1" "$CONFIG_FILE"; then
|
||||
msg_info "$(translate 'The container is unprivileged. Changing to privileged...')"
|
||||
sed -i "s/^unprivileged: 1/unprivileged: 0/" "$CONFIG_FILE"
|
||||
@@ -102,51 +136,62 @@ configure_lxc_hardware() {
|
||||
else
|
||||
msg_ok "$(translate 'The container is already privileged.')"
|
||||
fi
|
||||
|
||||
|
||||
# Configure iGPU
|
||||
sed -i '/^dev[0-9]\+:/d' "$CONFIG_FILE"
|
||||
|
||||
# Enable nesting feature
|
||||
if ! grep -q "features: nesting=1" "$CONFIG_FILE"; then
|
||||
echo "features: nesting=1" >> "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# iGPU support
|
||||
if ! grep -q "c 226:0 rwm" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 226:0 rwm # iGPU" >> "$CONFIG_FILE"
|
||||
echo "lxc.cgroup2.devices.allow: c 226:128 rwm # iGPU" >> "$CONFIG_FILE"
|
||||
echo "lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir" >> "$CONFIG_FILE"
|
||||
echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
|
||||
add_mount_if_needed "/dev/dri" "dev/dri" "$CONFIG_FILE"
|
||||
add_mount_if_needed "/dev/dri/renderD128" "dev/dri/renderD128" "$CONFIG_FILE"
|
||||
add_mount_if_needed "/dev/dri/card0" "dev/dri/card0" "$CONFIG_FILE"
|
||||
|
||||
# Framebuffer support
|
||||
if ! grep -q "c 29:0 rwm # Framebuffer" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 29:0 rwm # Framebuffer" >> "$CONFIG_FILE"
|
||||
fi
|
||||
add_mount_if_needed "/dev/fb0" "dev/fb0" "$CONFIG_FILE"
|
||||
|
||||
if ! grep -q "lxc.mount.entry: /dev/fb0" "$CONFIG_FILE"; then
|
||||
echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
# Configure Coral TPU (USB and M.2)
|
||||
# ----------------------------------------------------------
|
||||
# Coral USB passthrough (via udev + /dev/coral)
|
||||
# ----------------------------------------------------------
|
||||
add_udev_rule_for_coral_usb
|
||||
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 189:\* rwm # Coral USB$" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 189:* rwm # Coral USB" >> "$CONFIG_FILE"
|
||||
fi
|
||||
add_mount_if_needed "/dev/coral" "dev/coral" "$CONFIG_FILE"
|
||||
|
||||
if ! grep -Pq "^lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir$" "$CONFIG_FILE"; then
|
||||
echo "lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir" >> "$CONFIG_FILE"
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Coral M.2 (PCIe) support
|
||||
# ----------------------------------------------------------
|
||||
if lspci | grep -iq "Global Unichip"; then
|
||||
if ! grep -Pq "^lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex$" "$CONFIG_FILE"; then
|
||||
echo "lxc.cgroup2.devices.allow: c 245:0 rwm # Coral M2 Apex" >> "$CONFIG_FILE"
|
||||
fi
|
||||
add_mount_if_needed "/dev/apex_0" "dev/apex_0" "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
if ! grep -Pq "^lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file$" "$CONFIG_FILE"; then
|
||||
echo "lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Coral TPU and iGPU configuration added to container') $CONTAINER_ID."
|
||||
}
|
||||
|
||||
|
||||
# Install Coral TPU drivers in the container
|
||||
install_coral_in_container() {
|
||||
|
||||
msg_info2 "$(translate 'Installing iGPU and Coral TPU drivers inside the container...')"
|
||||
tput sc
|
||||
LOG_FILE=$(mktemp)
|
||||
|
||||
|
||||
pct start "$CONTAINER_ID"
|
||||
|
||||
CORAL_M2=$(lspci | grep -i "Global Unichip")
|
||||
@@ -188,25 +233,24 @@ install_coral_in_container() {
|
||||
'" "$LOG_FILE"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
tput rc
|
||||
tput ed
|
||||
rm -f "$LOG_FILE"
|
||||
tput rc
|
||||
tput ed
|
||||
rm -f "$LOG_FILE"
|
||||
msg_ok "$(translate 'iGPU and Coral TPU drivers installed inside the container.')"
|
||||
else
|
||||
msg_error "$(translate 'Failed to install iGPU and Coral TPU drivers inside the container.')"
|
||||
cat "$LOG_FILE"
|
||||
cat "$LOG_FILE"
|
||||
rm -f "$LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
select_container
|
||||
select_container
|
||||
show_proxmenux_logo
|
||||
configure_lxc_hardware
|
||||
install_coral_in_container
|
||||
|
||||
|
||||
install_coral_in_container
|
||||
|
||||
msg_ok "$(translate 'Configuration completed.')"
|
||||
sleep 2
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
@@ -68,6 +68,7 @@ verify_and_add_repos() {
|
||||
|
||||
# Function to install Coral TPU drivers on the host
|
||||
install_coral_host() {
|
||||
show_proxmenux_logo
|
||||
verify_and_add_repos
|
||||
|
||||
apt-get install -y git devscripts dh-dkms dkms pve-headers-$(uname -r) >/dev/null 2>&1
|
||||
@@ -93,19 +94,23 @@ install_coral_host() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate 'Coral TPU drivers installed successfully on the host.')"
|
||||
msg_success "$(translate 'Coral TPU drivers installed successfully on the host.')"
|
||||
echo -e
|
||||
}
|
||||
|
||||
# Prompt for reboot after installation
|
||||
restart_prompt() {
|
||||
restart_prompt() {
|
||||
if whiptail --title "$(translate 'Coral TPU Installation')" --yesno "$(translate 'The installation requires a server restart to apply changes. Do you want to restart now?')" 10 70; then
|
||||
#echo -ne "\r${TAB}${YW}-$(translate 'Restarting the server...') ${CL}"
|
||||
msg_warn "$(translate 'Restarting the server...')"
|
||||
reboot
|
||||
else
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
fi
|
||||
}
|
||||
|
||||
# Main logic
|
||||
|
||||
pre_install_prompt
|
||||
install_coral_host
|
||||
restart_prompt
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script para instalar JDownloader en un contenedor LXC desde el host Proxmox
|
||||
# Autor: MacRimi
|
||||
|
||||
# Mostrar lista de CTs
|
||||
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
|
||||
if [ -z "$CT_LIST" ]; then
|
||||
whiptail --title "Error" --msgbox "No hay contenedores LXC disponibles en el sistema." 8 50
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Seleccionar CT
|
||||
CTID=$(whiptail --title "Instalación de JDownloader" --menu "Selecciona el contenedor donde instalar JDownloader:" 20 60 10 $CT_LIST 3>&1 1>&2 2>&3)
|
||||
if [ -z "$CTID" ]; then
|
||||
whiptail --title "Cancelado" --msgbox "No se ha seleccionado ningún contenedor." 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Solicitar email
|
||||
EMAIL=$(whiptail --title "Cuenta My JDownloader" --inputbox "Introduce tu correo electrónico para vincular JDownloader:" 10 60 3>&1 1>&2 2>&3)
|
||||
if [ -z "$EMAIL" ]; then
|
||||
whiptail --title "Error" --msgbox "No se ha introducido ningún correo." 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Solicitar contraseña con confirmación
|
||||
while true; do
|
||||
PASSWORD=$(whiptail --title "Cuenta My JDownloader" --passwordbox "Introduce tu contraseña de My JDownloader:" 10 60 3>&1 1>&2 2>&3)
|
||||
[ -z "$PASSWORD" ] && whiptail --title "Error" --msgbox "No se ha introducido ninguna contraseña." 8 40 && exit 1
|
||||
|
||||
CONFIRM=$(whiptail --title "Confirmación de contraseña" --passwordbox "Repite tu contraseña para confirmar:" 10 60 3>&1 1>&2 2>&3)
|
||||
[ "$PASSWORD" = "$CONFIRM" ] && break
|
||||
whiptail --title "Error" --msgbox "Las contraseñas no coinciden. Intenta de nuevo." 8 50
|
||||
done
|
||||
|
||||
# Confirmación final
|
||||
whiptail --title "Confirmar datos" --yesno "¿Deseas continuar con los siguientes datos?\n\nCorreo: $EMAIL\nContraseña: (oculta)\n\nEsta información se usará para vincular el contenedor con tu cuenta de My.JDownloader." 14 60
|
||||
[ $? -ne 0 ] && whiptail --title "Cancelado" --msgbox "Instalación cancelada por el usuario." 8 40 && exit 1
|
||||
|
||||
clear
|
||||
echo "🔍 Detectando sistema operativo dentro del CT $CTID..."
|
||||
OS_ID=$(pct exec "$CTID" -- awk -F= '/^ID=/{gsub("\"",""); print $2}' /etc/os-release)
|
||||
|
||||
echo "Sistema detectado: $OS_ID"
|
||||
echo "🧰 Preparando entorno..."
|
||||
|
||||
case "$OS_ID" in
|
||||
debian)
|
||||
# Repositorio adicional para Java 8
|
||||
pct exec "$CTID" -- wget -q http://www.mirbsd.org/~tg/Debs/sources.txt/wtf-bookworm.sources
|
||||
pct exec "$CTID" -- mv wtf-bookworm.sources /etc/apt/sources.list.d/
|
||||
pct exec "$CTID" -- apt update -y
|
||||
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
|
||||
JAVA_PATH="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"
|
||||
;;
|
||||
ubuntu)
|
||||
pct exec "$CTID" -- apt update -y
|
||||
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
|
||||
JAVA_PATH="/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"
|
||||
;;
|
||||
alpine)
|
||||
pct exec "$CTID" -- apk update
|
||||
pct exec "$CTID" -- apk add openjdk8 wget
|
||||
JAVA_PATH="/usr/lib/jvm/java-1.8-openjdk/bin/java"
|
||||
;;
|
||||
*)
|
||||
echo "❌ Sistema operativo no soportado: $OS_ID"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Crear carpeta de instalación
|
||||
pct exec "$CTID" -- mkdir -p /opt/jdownloader
|
||||
pct exec "$CTID" -- bash -c 'cd /opt/jdownloader && curl -O https://installer.jdownloader.org/JDownloader.jar'
|
||||
|
||||
|
||||
# Crear archivo de configuración JSON para My JDownloader
|
||||
pct exec "$CTID" -- bash -c "mkdir -p /opt/jdownloader/cfg && cat > /opt/jdownloader/cfg/org.jdownloader.api.myjdownloader.MyJDownloaderSettings.json" <<EOF
|
||||
{
|
||||
"email" : "$EMAIL",
|
||||
"password" : "$PASSWORD",
|
||||
"enabled" : true
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
# Crear servicio según sistema
|
||||
if [[ "$OS_ID" == "alpine" ]]; then
|
||||
# Servicio OpenRC para Alpine
|
||||
pct exec "$CTID" -- bash -c 'cat > /etc/init.d/jdownloader <<EOF
|
||||
#!/sbin/openrc-run
|
||||
|
||||
command="/usr/bin/java"
|
||||
command_args="-jar /opt/jdownloader/JDownloader.jar -norestart"
|
||||
pidfile="/var/run/jdownloader.pid"
|
||||
name="JDownloader"
|
||||
|
||||
depend() {
|
||||
need net
|
||||
}
|
||||
EOF'
|
||||
|
||||
pct exec "$CTID" -- chmod +x /etc/init.d/jdownloader
|
||||
pct exec "$CTID" -- rc-update add jdownloader default
|
||||
pct exec "$CTID" -- rc-service jdownloader start
|
||||
|
||||
else
|
||||
# Servicio systemd para Debian/Ubuntu
|
||||
pct exec "$CTID" -- bash -c 'cat > /etc/systemd/system/jdownloader.service <<EOF
|
||||
[Unit]
|
||||
Description=JDownloader
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/jdownloader
|
||||
ExecStart=/usr/bin/java -jar JDownloader.jar -norestart
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF'
|
||||
|
||||
pct exec "$CTID" -- systemctl daemon-reexec
|
||||
pct exec "$CTID" -- systemctl daemon-reload
|
||||
pct exec "$CTID" -- systemctl enable jdownloader
|
||||
pct exec "$CTID" -- systemctl start jdownloader
|
||||
fi
|
||||
|
||||
pct exec "$CTID" -- reboot
|
||||
|
||||
echo -e "\n\033[1;32m✅ JDownloader se ha instalado correctamente en el CT $CTID y está funcionando como servicio.\033[0m"
|
||||
echo -e "\n➡️ Accede a \033[1;34mhttps://my.jdownloader.org\033[0m con tu cuenta para gestionarlo.\n"
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script para instalar JDownloader en un contenedor LXC desde el host Proxmox
|
||||
# Autor: MacRimi
|
||||
|
||||
# Mostrar lista de CTs
|
||||
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
|
||||
if [ -z "$CT_LIST" ]; then
|
||||
whiptail --title "Error" --msgbox "No hay contenedores LXC disponibles en el sistema." 8 50
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Seleccionar CT
|
||||
CTID=$(whiptail --title "Instalación de JDownloader" --menu "Selecciona el contenedor donde instalar JDownloader:" 20 60 10 $CT_LIST 3>&1 1>&2 2>&3)
|
||||
if [ -z "$CTID" ]; then
|
||||
whiptail --title "Cancelado" --msgbox "No se ha seleccionado ningún contenedor." 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Solicitar email
|
||||
EMAIL=$(whiptail --title "Cuenta My JDownloader" --inputbox "Introduce tu correo electrónico para vincular JDownloader:" 10 60 3>&1 1>&2 2>&3)
|
||||
if [ -z "$EMAIL" ]; then
|
||||
whiptail --title "Error" --msgbox "No se ha introducido ningún correo." 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Solicitar contraseña
|
||||
while true; do
|
||||
PASSWORD=$(whiptail --title "Cuenta My JDownloader" --passwordbox "Introduce tu contraseña de My JDownloader:" 10 60 3>&1 1>&2 2>&3)
|
||||
if [ -z "$PASSWORD" ]; then
|
||||
whiptail --title "Error" --msgbox "No se ha introducido ninguna contraseña." 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONFIRM_PASSWORD=$(whiptail --title "Confirmación de contraseña" --passwordbox "Repite tu contraseña para confirmar:" 10 60 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ "$PASSWORD" = "$CONFIRM_PASSWORD" ]; then
|
||||
break
|
||||
else
|
||||
whiptail --title "Error" --msgbox "Las contraseñas no coinciden. Intenta de nuevo." 8 50
|
||||
fi
|
||||
done
|
||||
|
||||
# Confirmar datos
|
||||
whiptail --title "Confirmar datos" --yesno "¿Deseas continuar con los siguientes datos?\n\nCorreo: $EMAIL\nContraseña: (establecida)\n\nEsta información se usará para vincular el contenedor con tu cuenta de My.JDownloader." 14 60
|
||||
if [ $? -ne 0 ]; then
|
||||
whiptail --title "Cancelado" --msgbox "Instalación cancelada por el usuario." 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Instalando JDownloader en CT $CTID..."
|
||||
echo
|
||||
|
||||
# Añadir repositorio alternativo para Java 8 y actualizar
|
||||
pct exec "$CTID" -- wget -q http://www.mirbsd.org/~tg/Debs/sources.txt/wtf-bookworm.sources
|
||||
pct exec "$CTID" -- mv wtf-bookworm.sources /etc/apt/sources.list.d/
|
||||
pct exec "$CTID" -- apt update -y
|
||||
pct exec "$CTID" -- apt install -y openjdk-8-jdk wget
|
||||
|
||||
# Crear carpeta y descargar JDownloader
|
||||
pct exec "$CTID" -- mkdir -p /root/jdownloader
|
||||
pct exec "$CTID" -- bash -c "cd /root/jdownloader && wget -q http://installer.jdownloader.org/JDownloader.jar"
|
||||
|
||||
# Crear archivo de configuración JSON para My JDownloader
|
||||
pct exec "$CTID" -- bash -c "mkdir -p /root/jdownloader/cfg && cat > /root/jdownloader/cfg/org.jdownloader.api.myjdownloader.MyJDownloaderSettings.json" <<EOF
|
||||
|
||||
{
|
||||
"email" : "$EMAIL",
|
||||
"password" : "$PASSWORD",
|
||||
"enabled" : true
|
||||
}
|
||||
EOF
|
||||
|
||||
# Crear servicio systemd
|
||||
pct exec "$CTID" -- bash -c "cat > /etc/systemd/system/jdownloader.service <<EOF
|
||||
[Unit]
|
||||
Description=JDownloader Headless
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/root/jdownloader
|
||||
ExecStart=/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java -jar JDownloader.jar -norestart
|
||||
Restart=always
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF"
|
||||
|
||||
# Activar y arrancar servicio
|
||||
pct exec "$CTID" -- systemctl daemon-reexec
|
||||
pct exec "$CTID" -- systemctl daemon-reload
|
||||
pct exec "$CTID" -- systemctl enable jdownloader
|
||||
pct exec "$CTID" -- systemctl start jdownloader
|
||||
|
||||
echo -e "\n\033[1;32m✅ JDownloader se ha instalado y está funcionando como servicio en el CT $CTID.\033[0m"
|
||||
echo -e "\nPuedes acceder a \033[1;34mhttps://my.jdownloader.org\033[0m con tu cuenta para gestionarlo.\n"
|
||||
+224
-151
@@ -1,104 +1,179 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.1
|
||||
# Last Updated: 04/07/2025
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
CONFIG_FILE="$BASE_DIR/config.json"
|
||||
CACHE_FILE="$BASE_DIR/cache.json"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
|
||||
INSTALL_DIR="/usr/local/bin"
|
||||
MENU_SCRIPT="menu"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
detect_installation_type() {
|
||||
local has_venv=false
|
||||
local has_language=false
|
||||
|
||||
|
||||
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
has_venv=true
|
||||
fi
|
||||
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
local current_language=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
|
||||
if [[ -n "$current_language" && "$current_language" != "null" && "$current_language" != "empty" ]]; then
|
||||
has_language=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$has_venv" = true ] && [ "$has_language" = true ]; then
|
||||
echo "translation"
|
||||
else
|
||||
echo "normal"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
show_config_menu() {
|
||||
local install_type
|
||||
install_type=$(detect_installation_type)
|
||||
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Configuration Menu")" --menu "$(translate "Select an option:")" 20 70 8 \
|
||||
"1" "$(translate "Change Language")" \
|
||||
"2" "$(translate "Show Version Information")" \
|
||||
"3" "$(translate "Uninstall ProxMenux")" \
|
||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
local menu_options=()
|
||||
local option_actions=()
|
||||
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
if [ "$install_type" = "translation" ]; then
|
||||
menu_options+=("1" "$(translate "Change Language")")
|
||||
option_actions[1]="change_language"
|
||||
|
||||
menu_options+=("2" "$(translate "Show Version Information")")
|
||||
option_actions[2]="show_version_info"
|
||||
|
||||
menu_options+=("3" "$(translate "Uninstall ProxMenux")")
|
||||
option_actions[3]="uninstall_proxmenu"
|
||||
|
||||
menu_options+=("4" "$(translate "Return to Main Menu")")
|
||||
option_actions[4]="return_main"
|
||||
else
|
||||
|
||||
menu_options+=("1" "Show Version Information")
|
||||
option_actions[1]="show_version_info"
|
||||
|
||||
menu_options+=("2" "Uninstall ProxMenux")
|
||||
option_actions[2]="uninstall_proxmenu"
|
||||
|
||||
menu_options+=("3" "Return to Main Menu")
|
||||
option_actions[3]="return_main"
|
||||
fi
|
||||
|
||||
|
||||
OPTION=$(dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "$(translate "Configuration Menu")" \
|
||||
--menu "$(translate "Select an option:")" 20 70 10 \
|
||||
"${menu_options[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
|
||||
case "${option_actions[$OPTION]}" in
|
||||
"change_language")
|
||||
change_language
|
||||
;;
|
||||
2)
|
||||
"show_version_info")
|
||||
show_version_info
|
||||
;;
|
||||
3)
|
||||
"uninstall_proxmenu")
|
||||
uninstall_proxmenu
|
||||
;;
|
||||
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||
"return_main"|"")
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
change_language() {
|
||||
LANGUAGE=$(whiptail --title "$(translate "Change Language")" --menu "$(translate "Select a new language for the menu:")" 20 70 12 \
|
||||
"en" "$(translate "English (Recommended)")" \
|
||||
"es" "$(translate "Spanish")" \
|
||||
"fr" "$(translate "French")" \
|
||||
"de" "$(translate "German")" \
|
||||
"it" "$(translate "Italian")" \
|
||||
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$LANGUAGE" ]; then
|
||||
msg_error "$(translate "No language selected.")"
|
||||
local new_language
|
||||
new_language=$(dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "$(translate "Change Language")" \
|
||||
--menu "$(translate "Select a new language for the menu:")" 20 60 6 \
|
||||
"en" "$(translate "English")" \
|
||||
"es" "$(translate "Spanish")" \
|
||||
"fr" "$(translate "French")" \
|
||||
"de" "$(translate "German")" \
|
||||
"it" "$(translate "Italian")" \
|
||||
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$new_language" ]; then
|
||||
dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "$(translate "Language Change")" \
|
||||
--msgbox "\n\n$(translate "No language selected.")" 10 50
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
# Update only the language field in the config file
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
tmp=$(mktemp)
|
||||
jq --arg lang "$LANGUAGE" '.language = $lang' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
||||
jq --arg lang "$new_language" '.language = $lang' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
|
||||
else
|
||||
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
||||
echo "{\"language\": \"$new_language\"}" > "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "$(translate "Language Change")" \
|
||||
--msgbox "\n\n$(translate "Language changed to") $new_language" 10 50
|
||||
|
||||
|
||||
msg_ok "$(translate "Language changed to") $LANGUAGE"
|
||||
|
||||
# Reload the menu
|
||||
TMP_FILE=$(mktemp)
|
||||
curl -s "$REPO_URL/scripts/menus/config_menu.sh" > "$TMP_FILE"
|
||||
chmod +x "$TMP_FILE"
|
||||
|
||||
trap 'rm -f "$TMP_FILE"' EXIT
|
||||
|
||||
exec bash "$TMP_FILE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
show_version_info() {
|
||||
local version info_message temp_file
|
||||
version=$(<"$LOCAL_VERSION_FILE")
|
||||
|
||||
local version info_message install_type
|
||||
install_type=$(detect_installation_type)
|
||||
|
||||
if [ -f "$LOCAL_VERSION_FILE" ]; then
|
||||
version=$(<"$LOCAL_VERSION_FILE")
|
||||
else
|
||||
version="Unknown"
|
||||
fi
|
||||
|
||||
info_message+="$(translate "Current ProxMenux version:") $version\n\n"
|
||||
info_message+="$(translate "Installed components:")\n"
|
||||
|
||||
|
||||
info_message+="$(translate "Installation type:")\n"
|
||||
if [ "$install_type" = "translation" ]; then
|
||||
info_message+="✓ $(translate "Translation Version (Multi-language support)")\n"
|
||||
else
|
||||
info_message+="✓ $(translate "Normal Version (English only - Lightweight)")\n"
|
||||
fi
|
||||
info_message+="\n"
|
||||
|
||||
info_message+="$(translate "Installed components:")\n"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
while IFS=': ' read -r component value; do
|
||||
[ "$component" = "language" ] && continue
|
||||
@@ -121,134 +196,132 @@ show_version_info() {
|
||||
else
|
||||
info_message+="$(translate "No installation information available.")\n"
|
||||
fi
|
||||
|
||||
|
||||
info_message+="\n$(translate "ProxMenu files:")\n"
|
||||
[ -f "$INSTALL_DIR/$MENU_SCRIPT" ] && info_message+="✓ $MENU_SCRIPT → $INSTALL_DIR/$MENU_SCRIPT\n" || info_message+="✗ $MENU_SCRIPT\n"
|
||||
[ -f "$CACHE_FILE" ] && info_message+="✓ cache.json → $CACHE_FILE\n" || info_message+="✗ cache.json\n"
|
||||
[ -f "$UTILS_FILE" ] && info_message+="✓ utils.sh → $UTILS_FILE\n" || info_message+="✗ utils.sh\n"
|
||||
[ -f "$CONFIG_FILE" ] && info_message+="✓ config.json → $CONFIG_FILE\n" || info_message+="✗ config.json\n"
|
||||
[ -f "$LOCAL_VERSION_FILE" ] && info_message+="✓ version.txt → $LOCAL_VERSION_FILE\n" || info_message+="✗ version.txt\n"
|
||||
|
||||
|
||||
info_message+="\n$(translate "Virtual Environment:")\n"
|
||||
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
info_message+="✓ $(translate "Installed") → $VENV_PATH\n"
|
||||
[ -f "$VENV_PATH/bin/pip" ] && info_message+="✓ pip: $(translate "Installed") → $VENV_PATH/bin/pip\n" || info_message+="✗ pip: $(translate "Not installed")\n"
|
||||
if [ "$install_type" = "translation" ]; then
|
||||
[ -f "$CACHE_FILE" ] && info_message+="✓ cache.json → $CACHE_FILE\n" || info_message+="✗ cache.json\n"
|
||||
|
||||
info_message+="\n$(translate "Virtual Environment:")\n"
|
||||
if [ -d "$VENV_PATH" ] && [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
info_message+="✓ $(translate "Installed") → $VENV_PATH\n"
|
||||
[ -f "$VENV_PATH/bin/pip" ] && info_message+="✓ pip: $(translate "Installed") → $VENV_PATH/bin/pip\n" || info_message+="✗ pip: $(translate "Not installed")\n"
|
||||
else
|
||||
info_message+="✗ $(translate "Virtual Environment"): $(translate "Not installed")\n"
|
||||
info_message+="✗ pip: $(translate "Not installed")\n"
|
||||
fi
|
||||
|
||||
current_language=$(jq -r '.language // "en"' "$CONFIG_FILE")
|
||||
info_message+="\n$(translate "Current language:")\n$current_language\n"
|
||||
else
|
||||
info_message+="✗ $(translate "Virtual Environment"): $(translate "Not installed")\n"
|
||||
info_message+="✗ pip: $(translate "Not installed")\n"
|
||||
info_message+="\n$(translate "Language:")\nEnglish (Fixed)\n"
|
||||
fi
|
||||
|
||||
|
||||
current_language=$(jq -r '.language // "en"' "$CONFIG_FILE")
|
||||
info_message+="\n$(translate "Current language:")\n$current_language\n"
|
||||
|
||||
# Mostrar con dialog usando un archivo temporal
|
||||
if command -v dialog >/dev/null 2>&1; then
|
||||
tmpfile=$(mktemp)
|
||||
echo -e "$info_message" > "$tmpfile"
|
||||
dialog --title "$(translate "ProxMenux Information")" --clear --textbox "$tmpfile" 20 70
|
||||
rm -f "$tmpfile"
|
||||
fi
|
||||
#show_proxmenux_logo
|
||||
tmpfile=$(mktemp)
|
||||
echo -e "$info_message" > "$tmpfile"
|
||||
dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "$(translate "ProxMenux Information")" \
|
||||
--textbox "$tmpfile" 25 80
|
||||
rm -f "$tmpfile"
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
uninstall_proxmenu() {
|
||||
if ! whiptail --title "Uninstall ProxMenu" --yesno "$(translate "Are you sure you want to uninstall ProxMenu?")" 10 60; then
|
||||
local install_type
|
||||
install_type=$(detect_installation_type)
|
||||
|
||||
if ! dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "Uninstall ProxMenu" \
|
||||
--yesno "\n$(translate "Are you sure you want to uninstall ProxMenu?")" 8 60; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Show checklist for dependencies
|
||||
DEPS_TO_REMOVE=$(whiptail --title "Remove Dependencies" --checklist \
|
||||
"Select dependencies to remove:" 15 60 3 \
|
||||
"python3-venv" "Python virtual environment" OFF \
|
||||
"python3-pip" "Python package installer" OFF \
|
||||
"jq" "JSON processor" OFF \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
echo "Uninstalling ProxMenu..."
|
||||
local deps_to_remove=""
|
||||
|
||||
|
||||
# Remove googletrans if virtual environment exists
|
||||
if [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
echo "Removing googletrans..."
|
||||
source "$VENV_PATH/bin/activate"
|
||||
pip uninstall -y googletrans >/dev/null 2>&1
|
||||
deactivate
|
||||
fi
|
||||
|
||||
# Remove virtual environment
|
||||
if [ -d "$VENV_PATH" ]; then
|
||||
echo "Removing virtual environment..."
|
||||
rm -rf "$VENV_PATH"
|
||||
fi
|
||||
|
||||
# Remove selected dependencies
|
||||
if [ -n "$DEPS_TO_REMOVE" ]; then
|
||||
echo "Removing selected dependencies..."
|
||||
|
||||
# Remove quotes and process each package
|
||||
read -r -a DEPS_ARRAY <<< "$(echo "$DEPS_TO_REMOVE" | tr -d '"')"
|
||||
for dep in "${DEPS_ARRAY[@]}"; do
|
||||
echo "Removing $dep..."
|
||||
|
||||
# Mark package as auto-installed
|
||||
apt-mark auto "$dep" >/dev/null 2>&1
|
||||
|
||||
# Try to remove with apt-get
|
||||
if ! apt-get -y --purge autoremove "$dep" >/dev/null 2>&1; then
|
||||
echo "Failed to remove $dep with apt-get. Trying with dpkg..."
|
||||
if ! dpkg --purge "$dep" >/dev/null 2>&1; then
|
||||
echo "Failed to remove $dep with dpkg. Trying to force removal..."
|
||||
dpkg --force-all --purge "$dep" >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify if the package was actually removed
|
||||
if dpkg -l "$dep" 2>/dev/null | grep -q '^ii'; then
|
||||
echo "Warning: Failed to completely remove $dep. You may need to remove it manually."
|
||||
else
|
||||
echo "$dep successfully removed."
|
||||
fi
|
||||
done
|
||||
|
||||
# Run autoremove to clean up any leftover dependencies
|
||||
echo "Cleaning up unnecessary packages..."
|
||||
apt-get autoremove -y --purge >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
|
||||
# Restore original .bashrc if backup exists
|
||||
if [ -f /root/.bashrc.bak ]; then
|
||||
echo "$(translate "Restoring original .bashrc...")"
|
||||
mv /root/.bashrc.bak /root/.bashrc
|
||||
fi
|
||||
|
||||
# Restore original MOTD if backup exists
|
||||
if [ -f /etc/motd.bak ]; then
|
||||
echo "$(translate "Restoring original MOTD...")"
|
||||
mv /etc/motd.bak /etc/motd
|
||||
if [ "$install_type" = "translation" ]; then
|
||||
deps_to_remove=$(dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "Remove Dependencies" \
|
||||
--checklist "Select dependencies to remove:" 15 60 4 \
|
||||
"python3-venv" "Python virtual environment" OFF \
|
||||
"python3-pip" "Python package installer" OFF \
|
||||
"python3" "Python interpreter" OFF \
|
||||
"jq" "JSON processor" OFF \
|
||||
3>&1 1>&2 2>&3)
|
||||
else
|
||||
# Remove custom MOTD line if present
|
||||
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
|
||||
fi
|
||||
|
||||
|
||||
# Remove ProxMenu files
|
||||
rm -f "/usr/local/bin/menu"
|
||||
rm -rf "$BASE_DIR"
|
||||
|
||||
echo "ProxMenu has been uninstalled."
|
||||
|
||||
if [ -n "$DEPS_TO_REMOVE" ]; then
|
||||
echo "The following dependencies have been removed successfully: $DEPS_TO_REMOVE"
|
||||
deps_to_remove=$(dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "Remove Dependencies" \
|
||||
--checklist "Select dependencies to remove:" 12 60 2 \
|
||||
"dialog" "Interactive dialog boxes" OFF \
|
||||
"jq" "JSON processor" OFF \
|
||||
3>&1 1>&2 2>&3)
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "ProxMenux uninstallation complete. Thank you for using it!"
|
||||
echo
|
||||
|
||||
(
|
||||
echo "10" ; echo "Removing ProxMenu files..."
|
||||
sleep 1
|
||||
|
||||
|
||||
if [ -f "$VENV_PATH/bin/activate" ]; then
|
||||
echo "30" ; echo "Removing googletrans and virtual environment..."
|
||||
source "$VENV_PATH/bin/activate"
|
||||
pip uninstall -y googletrans >/dev/null 2>&1
|
||||
deactivate
|
||||
rm -rf "$VENV_PATH"
|
||||
fi
|
||||
|
||||
echo "50" ; echo "Removing ProxMenu files..."
|
||||
rm -f "$INSTALL_DIR/$MENU_SCRIPT"
|
||||
rm -rf "$BASE_DIR"
|
||||
|
||||
|
||||
if [ -n "$deps_to_remove" ]; then
|
||||
echo "70" ; echo "Removing selected dependencies..."
|
||||
read -r -a DEPS_ARRAY <<< "$(echo "$deps_to_remove" | tr -d '"')"
|
||||
for dep in "${DEPS_ARRAY[@]}"; do
|
||||
apt-mark auto "$dep" >/dev/null 2>&1
|
||||
apt-get -y --purge autoremove "$dep" >/dev/null 2>&1
|
||||
done
|
||||
apt-get autoremove -y --purge >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo "90" ; echo "Restoring system files..."
|
||||
|
||||
[ -f /root/.bashrc.bak ] && mv /root/.bashrc.bak /root/.bashrc
|
||||
if [ -f /etc/motd.bak ]; then
|
||||
mv /etc/motd.bak /etc/motd
|
||||
else
|
||||
sed -i '/This system is optimised by: ProxMenux/d' /etc/motd
|
||||
fi
|
||||
|
||||
echo "100" ; echo "Uninstallation complete!"
|
||||
sleep 1
|
||||
|
||||
) | dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "Uninstalling ProxMenux" \
|
||||
--gauge "Starting uninstallation..." 10 60 0
|
||||
|
||||
|
||||
local final_message="ProxMenux has been uninstalled successfully.\n\n"
|
||||
if [ -n "$deps_to_remove" ]; then
|
||||
final_message+="The following dependencies were removed:\n$deps_to_remove\n\n"
|
||||
fi
|
||||
final_message+="Thank you for using ProxMenux!"
|
||||
|
||||
dialog --clear --backtitle "ProxMenux Configuration" \
|
||||
--title "Uninstallation Complete" \
|
||||
--msgbox "$final_message" 12 60
|
||||
clear
|
||||
exit 0
|
||||
}
|
||||
|
||||
#show_proxmenux_logo
|
||||
show_config_menu
|
||||
# ==========================================================
|
||||
# Main execution
|
||||
show_config_menu
|
||||
@@ -67,8 +67,8 @@ function header_info() {
|
||||
# MAIN EXECUTION
|
||||
# ==========================================================
|
||||
|
||||
header_info
|
||||
echo -e "\n Loading..."
|
||||
#header_info
|
||||
#echo -e "\n Loading..."
|
||||
#sleep 1
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ function start_vm_configuration() {
|
||||
while true; do
|
||||
OS_TYPE=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Select System Type")" \
|
||||
--menu "\n$(translate "Choose the type of virtual system to install:")" 18 70 10 \
|
||||
--menu "\n$(translate "Choose the type of virtual system to install:")" 20 70 10 \
|
||||
1 "$(translate "Create") VM System NAS" \
|
||||
2 "$(translate "Create") VM System Windows" \
|
||||
3 "$(translate "Create") VM System Linux" \
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenuX - Virtual Machine Creator Script
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 07/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script is part of the central ProxMenux VM creation module. It allows users
|
||||
# to create virtual machines (VMs) in Proxmox VE using either default or advanced
|
||||
# configurations, streamlining the deployment of Linux, Windows, and other systems.
|
||||
#
|
||||
# Key features:
|
||||
# - Supports both virtual disk creation and physical disk passthrough.
|
||||
# - Automates CPU, RAM, BIOS, network and storage configuration.
|
||||
# - Provides a user-friendly menu to select OS type, ISO image and disk interface.
|
||||
# - Automatically generates a detailed and styled HTML description for each VM.
|
||||
#
|
||||
# All operations are designed to simplify and accelerate VM creation in a
|
||||
# consistent and maintainable way, using ProxMenux standards.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
VM_REPO="$REPO_URL/scripts/vm"
|
||||
ISO_REPO="$REPO_URL/scripts/vm"
|
||||
MENU_REPO="$REPO_URL/scripts/menus"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
|
||||
|
||||
source <(curl -s "$VM_REPO/vm_configurator.sh")
|
||||
source <(curl -s "$VM_REPO/disk_selector.sh")
|
||||
source <(curl -s "$VM_REPO/vm_creator.sh")
|
||||
|
||||
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
|
||||
function header_info() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}║${YWB} ProxMenux VM Creator ${BL}║${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}"
|
||||
echo -e
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# MAIN EXECUTION
|
||||
# ==========================================================
|
||||
|
||||
header_info
|
||||
echo -e "\n Loading..."
|
||||
sleep 1
|
||||
|
||||
|
||||
|
||||
|
||||
function start_vm_configuration() {
|
||||
|
||||
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
|
||||
if [[ -z "$HN" ]]; then
|
||||
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$HN" ]] && HN="custom-vm"
|
||||
fi
|
||||
|
||||
apply_default_vm_config
|
||||
else
|
||||
header_info
|
||||
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
|
||||
configure_vm_advanced "$OS_TYPE"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
while true; do
|
||||
OS_TYPE=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Select System Type")" \
|
||||
--menu "\n$(translate "Choose the type of virtual system to install:")" 18 70 10 \
|
||||
1 "$(translate "Create") VM System NAS" \
|
||||
2 "$(translate "Create") VM System Windows" \
|
||||
3 "$(translate "Create") VM System Linux" \
|
||||
4 "$(translate "Create") VM System macOS (OSX-PROXMOX)" \
|
||||
5 "$(translate "Create") VM System Others (based Linux)" \
|
||||
6 "$(translate "Return to Main Menu")" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
|
||||
[[ $? -ne 0 || "$OS_TYPE" == "6" ]] && exec bash <(curl -s "$MENU_REPO/main_menu.sh")
|
||||
|
||||
case "$OS_TYPE" in
|
||||
1)
|
||||
source <(curl -fsSL "$ISO_REPO/select_nas_iso.sh") && select_nas_iso || continue
|
||||
;;
|
||||
2)
|
||||
source <(curl -fsSL "$ISO_REPO/select_windows_iso.sh") && select_windows_iso || continue
|
||||
;;
|
||||
3)
|
||||
source <(curl -fsSL "$ISO_REPO/select_linux_iso.sh") && select_linux_iso || continue
|
||||
;;
|
||||
4)
|
||||
whiptail --title "OSX-PROXMOX" --yesno "$(translate "This is an external script that creates a macOS VM in Proxmox VE in just a few steps, whether you are using AMD or Intel hardware.")\n\n$(translate "The script clones the osx-proxmox.com repository and once the setup is complete, the server will automatically reboot.")\n\n$(translate "Make sure there are no critical services running as they will be interrupted. Ensure your server can be safely rebooted.")\n\n$(translate "Visit https://osx-proxmox.com for more information.")\n\n$(translate "Do you want to run the script now?")" 20 70
|
||||
if [[ $? -eq 0 ]]; then
|
||||
bash -c "$(curl -fsSL https://install.osx-proxmox.com)"
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
5)
|
||||
source <(curl -fsSL "$ISO_REPO/select_linux_iso.sh") && select_linux_other_scripts || continue
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
if ! confirm_vm_creation; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
start_vm_configuration || continue
|
||||
|
||||
|
||||
select_disk_type
|
||||
if [[ -z "$DISK_TYPE" ]]; then
|
||||
msg_error "$(translate "Disk type selection failed or cancelled")"
|
||||
continue
|
||||
fi
|
||||
|
||||
create_vm
|
||||
break
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
function start_vm_configuration() {
|
||||
|
||||
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
|
||||
if [[ -z "$HN" ]]; then
|
||||
HN=$(whiptail --inputbox "$(translate "Enter a name for the new virtual machine:")" 10 60 --title "VM Hostname" 3>&1 1>&2 2>&3)
|
||||
[[ -z "$HN" ]] && HN="custom-vm"
|
||||
fi
|
||||
|
||||
apply_default_vm_config
|
||||
else
|
||||
header_info
|
||||
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
|
||||
configure_vm_advanced "$OS_TYPE"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -25,43 +25,34 @@ initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "GPUs and Coral-TPU Menu")" --menu "$(translate "Select an option:")" 20 70 8 \
|
||||
"1" "$(translate "Add HW iGPU acceleration to an LXC")" \
|
||||
"2" "$(translate "Add Coral TPU to an LXC")" \
|
||||
"3" "$(translate "Install/Update Coral TPU on the Host")" \
|
||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "GPUs and Coral-TPU Menu")" \
|
||||
--menu "\n$(translate "Select an option:")" 20 70 8 \
|
||||
"1" "$(translate "Add HW iGPU acceleration to an LXC")" \
|
||||
"2" "$(translate "Add Coral TPU to an LXC")" \
|
||||
"3" "$(translate "Install/Update Coral TPU on the Host")" \
|
||||
"4" "$(translate "Return to Main Menu")" \
|
||||
2>&1 >/dev/tty)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
show_proxmenux_logo
|
||||
msg_info2 "$(translate "Running script:") $(translate " HW iGPU acceleration LXC")..."
|
||||
bash <(curl -s "$REPO_URL/scripts/configure_igpu_lxc.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_warn "$(translate "Operation cancelled.")"
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
show_proxmenux_logo
|
||||
msg_info2 "$(translate "Running script:") Coral TPU LXC..."
|
||||
bash <(curl -s "$REPO_URL/scripts/install_coral_lxc.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_warn "$(translate "Operation cancelled.")"
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
show_proxmenux_logo
|
||||
msg_info2 "$(translate "Running script:") $(translate "Install/Update") Coral..."
|
||||
bash <(curl -s "$REPO_URL/scripts/install_coral_pve.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_warn "$(translate "Operation cancelled.")"
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
;;
|
||||
|
||||
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
@@ -37,23 +37,24 @@ show_menu() {
|
||||
dialog --clear \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate "Main ProxMenux")" \
|
||||
--menu "$(translate "Select an option:")" 18 70 10 \
|
||||
--menu "$(translate "Select an option:")" 20 70 10 \
|
||||
1 "$(translate "Settings post-install Proxmox")" \
|
||||
2 "$(translate "Help and Info Commands")" \
|
||||
3 "$(translate "Hardware: GPUs and Coral-TPU")" \
|
||||
4 "$(translate "Create VM from template or script")" \
|
||||
5 "$(translate "Disk and Storage Manager")" \
|
||||
6 "$(translate "Essential Proxmox VE Helper-Scripts")" \
|
||||
7 "$(translate "Network")" \
|
||||
8 "$(translate "Settings")" \
|
||||
9 "$(translate "Exit")" 2>"$TEMP_FILE"
|
||||
6 "$(translate "Proxmox VE Helper Scripts")" \
|
||||
7 "$(translate "Network Management")" \
|
||||
8 "$(translate "Utilities and Tools")" \
|
||||
9 "$(translate "Settings")" \
|
||||
0 "$(translate "Exit")" 2>"$TEMP_FILE"
|
||||
|
||||
local EXIT_STATUS=$?
|
||||
|
||||
if [[ $EXIT_STATUS -ne 0 ]]; then
|
||||
# ESC pressed or Cancel
|
||||
clear
|
||||
msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"
|
||||
msg_ok "$(translate "Thank you for using ProxMenux. Goodbye!")"
|
||||
rm -f "$TEMP_FILE"
|
||||
exit 0
|
||||
fi
|
||||
@@ -67,9 +68,10 @@ show_menu() {
|
||||
4) exec bash <(curl -s "$REPO_URL/scripts/menus/create_vm_menu.sh") ;;
|
||||
5) exec bash <(curl -s "$REPO_URL/scripts/menus/storage_menu.sh") ;;
|
||||
6) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_Helper_Scripts.sh") ;;
|
||||
7) exec bash <(curl -s "$REPO_URL/scripts/repair_network.sh") ;;
|
||||
8) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;;
|
||||
9) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;;
|
||||
7) exec bash <(curl -s "$REPO_URL/scripts/menus/network_menu.sh") ;;
|
||||
8) exec bash <(curl -s "$REPO_URL/scripts/menus/utilities_menu.sh") ;;
|
||||
9) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;;
|
||||
0) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; rm -f "$TEMP_FILE"; exit 0 ;;
|
||||
*) msg_warn "$(translate "Invalid option")"; sleep 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
UTILS_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/utils.sh"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
CACHE_FILE="$BASE_DIR/cache.json"
|
||||
CONFIG_FILE="$BASE_DIR/config.json"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
|
||||
# Try to load utils.sh from GitHub
|
||||
if ! source <(curl -sSf "$UTILS_URL"); then
|
||||
echo "$(translate 'Error: Could not load utils.sh from') $UTILS_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Initialize cache
|
||||
initialize_cache() {
|
||||
if [ ! -f "$CACHE_FILE" ]; then
|
||||
echo "{}" > "$CACHE_FILE"
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
# Load language from JSON file
|
||||
load_language() {
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
LANGUAGE=$(jq -r '.language' "$CONFIG_FILE")
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Change language
|
||||
change_language() {
|
||||
LANGUAGE=$(whiptail --title "$(translate "Change Language")" --menu "$(translate "Select a new language for the menu:")" 20 60 12 \
|
||||
"en" "$(translate "English (Recommended)")" \
|
||||
"es" "$(translate "Spanish")" \
|
||||
"fr" "$(translate "French")" \
|
||||
"de" "$(translate "German")" \
|
||||
"it" "$(translate "Italian")" \
|
||||
"pt" "$(translate "Portuguese")" \
|
||||
"zh-cn" "$(translate "Simplified Chinese")" \
|
||||
"ja" "$(translate "Japanese")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$LANGUAGE" ]; then
|
||||
msg_error "$(translate "No language selected.")"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
||||
msg_ok "$(translate "Language changed to") $LANGUAGE"
|
||||
|
||||
# Descargar el script nuevamente
|
||||
TMP_FILE=$(mktemp)
|
||||
curl -s "$REPO_URL/scripts/menus/config_menu.sh" > "$TMP_FILE"
|
||||
chmod +x "$TMP_FILE"
|
||||
|
||||
# Programar la eliminación del archivo cuando termine el proceso
|
||||
trap 'rm -f "$TMP_FILE"' EXIT
|
||||
|
||||
exec bash "$TMP_FILE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Function to uninstall ProxMenu
|
||||
uninstall_proxmenu() {
|
||||
if whiptail --title "$(translate "Uninstall ProxMenu")" --yesno "$(translate "Are you sure you want to uninstall ProxMenu?")" 10 60; then
|
||||
msg_info "$(translate "Uninstalling ProxMenu...")"
|
||||
rm -rf "$BASE_DIR"
|
||||
rm -f "/usr/local/bin/menu.sh"
|
||||
msg_ok "$(translate "ProxMenu has been completely uninstalled.")"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to show version information
|
||||
show_version_info() {
|
||||
local version=$(cat "$LOCAL_VERSION_FILE" 2>/dev/null || echo "1.0.0")
|
||||
whiptail --title "$(translate "Version Information")" --msgbox "$(translate "Current ProxMenu version:") $version" 12 60
|
||||
}
|
||||
|
||||
# Show configuration menu
|
||||
show_config_menu() {
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Configuration Menu")" --menu "$(translate "Select an option:")" 15 60 4 \
|
||||
"1" "$(translate "Change Language")" \
|
||||
"2" "$(translate "Show Version Information")" \
|
||||
"3" "$(translate "Uninstall ProxMenu")" \
|
||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
change_language
|
||||
;;
|
||||
2)
|
||||
show_version_info
|
||||
;;
|
||||
3)
|
||||
uninstall_proxmenu
|
||||
;;
|
||||
4)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Show graphics and video menu
|
||||
show_graphics_menu() {
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "HW: GPUs and Coral")" --menu "$(translate "Select an option:")" 15 60 2 \
|
||||
"1" "IGPU/TPU" \
|
||||
"2" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
msg_info "$(translate "Running script") IGPU/TPU..."
|
||||
if bash <(curl -s "$REPO_URL/scripts/igpu_tpu.sh"); then
|
||||
msg_ok "$(translate "Script executed successfully.")"
|
||||
else
|
||||
msg_error "$(translate "Error executing script.")"
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
msg_error "$(translate "Invalid option.")"
|
||||
sleep 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Show storage menu
|
||||
show_storage_menu() {
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Disk and Storage Menu")" --menu "$(translate "Select an option:")" 15 60 3 \
|
||||
"1" "$(translate "Add Disk Passthrough to a VM")" \
|
||||
"2" "$(translate "Import Disk Image to a VM")" \
|
||||
"3" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
echo -e "\033[33m[INFO] $(translate "Running script:") $(translate "Disk Passthrough")...\033[0m"
|
||||
|
||||
bash <(curl -s "$REPO_URL/scripts/disk-passthrough.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_info "$(translate "Operation cancelled.")"
|
||||
sleep 2
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
echo -e "\033[33m[INFO] $(translate "Running script:") $(translate "Import Disk Image")...\033[0m"
|
||||
bash <(curl -s "$REPO_URL/scripts/import-disk-image.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_info "$(translate "Operation cancelled.")"
|
||||
sleep 2
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Show network menu
|
||||
show_network_menu() {
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Network Menu")" --menu "$(translate "Select an option:")" 15 60 2 \
|
||||
"1" "$(translate "Repair Network")" \
|
||||
"2" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
msg_info "$(translate "Running network repair...")"
|
||||
if bash <(curl -s "$REPO_URL/scripts/repair_network.sh"); then
|
||||
msg_ok "$(translate "Network repair completed.")"
|
||||
else
|
||||
msg_error "$(translate "Error in network repair.")"
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
return
|
||||
;;
|
||||
*)
|
||||
msg_error "$(translate "Invalid option.")"
|
||||
sleep 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Show main menu
|
||||
show_menu() {
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Main Menu")" --menu "$(translate "Select an option:")" 15 60 5 \
|
||||
"1" "$(translate "GPUs and Coral-TPU")" \
|
||||
"2" "$(translate "Hard Drives, Disk Images, and Storage")" \
|
||||
"3" "$(translate "Network")" \
|
||||
"4" "$(translate "Settings")" \
|
||||
"5" "$(translate "Exit")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
show_graphics_menu
|
||||
;;
|
||||
2)
|
||||
show_storage_menu
|
||||
;;
|
||||
3)
|
||||
show_network_menu
|
||||
;;
|
||||
4)
|
||||
show_config_menu
|
||||
;;
|
||||
5)
|
||||
clear
|
||||
msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
msg_error "$(translate "Invalid option.")"
|
||||
sleep 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Main flow
|
||||
initialize_cache
|
||||
load_language
|
||||
show_menu
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script serves as the main entry point for ProxMenux,
|
||||
# a menu-driven tool designed for Proxmox VE management.
|
||||
#
|
||||
# - Displays the ProxMenu logo on startup.
|
||||
# - Loads necessary configurations and language settings.
|
||||
# - Checks for available updates and installs them if confirmed.
|
||||
# - Downloads and executes the latest main menu script.
|
||||
#
|
||||
# Key Features:
|
||||
# - Ensures ProxMenu is always up-to-date by fetching the latest version.
|
||||
# - Uses whiptail for interactive menus and language selection.
|
||||
# - Loads utility functions and translation support.
|
||||
# - Maintains a cache system to improve performance.
|
||||
# - Executes the ProxMenux main menu dynamically from the repository.
|
||||
#
|
||||
# This script ensures a streamlined and automated experience
|
||||
# for managing Proxmox VE using ProxMenux.
|
||||
# ==========================================================
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
CONFIG_FILE="$BASE_DIR/config.json"
|
||||
CACHE_FILE="$BASE_DIR/cache.json"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
LOCAL_VERSION_FILE="$BASE_DIR/version.txt"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
# ==========================================================
|
||||
|
||||
show_proxmenu_logo
|
||||
|
||||
# Initialize language configuration
|
||||
initialize_config() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
LANGUAGE=$(whiptail --title "$(translate "Select Language")" --menu "$(translate "Choose a language for the menu:")" 20 60 12 \
|
||||
"en" "$(translate "English (Recommended)")" \
|
||||
"es" "$(translate "Spanish")" \
|
||||
"fr" "$(translate "French")" \
|
||||
"de" "$(translate "German")" \
|
||||
"it" "$(translate "Italian")" \
|
||||
"pt" "$(translate "Portuguese")" \
|
||||
"zh-cn" "$(translate "Simplified Chinese")" \
|
||||
"ja" "$(translate "Japanese")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$LANGUAGE" ]; then
|
||||
msg_error "$(translate "No language selected. Exiting.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
||||
msg_ok "$(translate "Initial language set to:") $LANGUAGE"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
|
||||
check_updates() {
|
||||
local INSTALL_SCRIPT="$BASE_DIR/install_proxmenux.sh"
|
||||
|
||||
# Fetch the remote version
|
||||
local REMOTE_VERSION
|
||||
REMOTE_VERSION=$(curl -fsSL "$REPO_URL/version.txt" | head -n 1)
|
||||
|
||||
# Exit silently if unable to fetch the remote version
|
||||
if [ -z "$REMOTE_VERSION" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Read the local version
|
||||
local LOCAL_VERSION
|
||||
LOCAL_VERSION=$(head -n 1 "$LOCAL_VERSION_FILE")
|
||||
|
||||
# If the local version matches the remote version, no update is needed
|
||||
[ "$LOCAL_VERSION" = "$REMOTE_VERSION" ] && return 0
|
||||
|
||||
# Prompt the user for update confirmation
|
||||
if whiptail --title "$(translate "Update Available")" \
|
||||
--yesno "$(translate "New version available") ($REMOTE_VERSION)\n\n$(translate "Do you want to update now?")" \
|
||||
10 60 --defaultno; then
|
||||
|
||||
|
||||
msg_warn "$(translate "Starting ProxMenu update...")"
|
||||
|
||||
# Download the installation script
|
||||
if wget -qO "$INSTALL_SCRIPT" "$REPO_URL/install_proxmenux.sh"; then
|
||||
chmod +x "$INSTALL_SCRIPT"
|
||||
|
||||
# Execute the script directly in the current environment
|
||||
source "$INSTALL_SCRIPT"
|
||||
|
||||
fi
|
||||
else
|
||||
msg_warn "$(translate "Update postponed. You can update later from the menu.")"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
main_menu() {
|
||||
exec bash <(curl -fsSL "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
}
|
||||
|
||||
|
||||
# Main flow
|
||||
initialize_config
|
||||
load_language
|
||||
initialize_cache
|
||||
check_updates
|
||||
main_menu
|
||||
@@ -6,11 +6,11 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.1
|
||||
# Last Updated: 04/06/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script provides a simple and efficient way to access and execute essential Proxmox VE scripts
|
||||
# This script provides a simple and efficient way to access and execute Proxmox VE scripts
|
||||
# from the Community Scripts project (https://community-scripts.github.io/ProxmoxVE/).
|
||||
#
|
||||
# It serves as a convenient tool to run key automation scripts that simplify system management,
|
||||
@@ -31,156 +31,297 @@ fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
#show_proxmenux_logo
|
||||
# ==========================================================
|
||||
|
||||
# Base URL community-scripts
|
||||
BASE_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc"
|
||||
BASE_URL2="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
|
||||
HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json"
|
||||
METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json"
|
||||
|
||||
for cmd in curl jq dialog; do
|
||||
if ! command -v "$cmd" >/dev/null; then
|
||||
echo "Missing required command: $cmd"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
CACHE_JSON=$(curl -s "$HELPERS_JSON_URL")
|
||||
META_JSON=$(curl -s "$METADATA_URL")
|
||||
|
||||
declare -A CATEGORY_NAMES
|
||||
while read -r id name; do
|
||||
CATEGORY_NAMES[$id]="$name"
|
||||
done < <(echo "$META_JSON" | jq -r '.categories[] | "\(.id)\t\(.name)"')
|
||||
|
||||
declare -A CATEGORY_COUNT
|
||||
for id in $(echo "$CACHE_JSON" | jq -r '.[].categories[]'); do
|
||||
((CATEGORY_COUNT[$id]++))
|
||||
done
|
||||
|
||||
get_type_label() {
|
||||
local type="$1"
|
||||
case "$type" in
|
||||
ct) echo $'\Z1LXC\Zn' ;;
|
||||
vm) echo $'\Z4VM\Zn' ;;
|
||||
pve) echo $'\Z3PVE\Zn' ;;
|
||||
addon) echo $'\Z2ADDON\Zn' ;;
|
||||
*) echo $'\Z7GEN\Zn' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
download_script() {
|
||||
local url="$1"
|
||||
local fallback_pve="${url/misc/tools\/pve}"
|
||||
local fallback_addon="${url/misc/tools\/addon}"
|
||||
local fallback_copydata="${url/misc/tools\/copy-data}"
|
||||
local url="$1"
|
||||
local fallback_pve="${url/misc\/tools\/pve}"
|
||||
local fallback_addon="${url/misc\/tools\/addon}"
|
||||
local fallback_copydata="${url/misc\/tools\/copy-data}"
|
||||
|
||||
if curl --silent --head --fail "$url" >/dev/null; then
|
||||
bash <(curl -s "$url")
|
||||
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
|
||||
bash <(curl -s "$fallback_pve")
|
||||
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
|
||||
bash <(curl -s "$fallback_addon")
|
||||
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
|
||||
bash <(curl -s "$fallback_copydata")
|
||||
else
|
||||
msg_error "$(translate 'Error: Failed to download the script.')\033[0m"
|
||||
msg_error "\n$(translate 'Tried URLs:')\n- $url\n- $fallback_pve\n- $fallback_addons\n- $fallback_copydata\n"
|
||||
if curl --silent --head --fail "$url" >/dev/null; then
|
||||
bash <(curl -s "$url")
|
||||
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
|
||||
bash <(curl -s "$fallback_pve")
|
||||
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
|
||||
bash <(curl -s "$fallback_addon")
|
||||
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
|
||||
bash <(curl -s "$fallback_copydata")
|
||||
else
|
||||
dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70
|
||||
fi
|
||||
}
|
||||
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
RETURN_TO_MAIN=false
|
||||
|
||||
format_credentials() {
|
||||
local script_info="$1"
|
||||
local credentials_info=""
|
||||
|
||||
local has_credentials
|
||||
has_credentials=$(echo "$script_info" | base64 --decode | jq -r 'has("default_credentials")')
|
||||
|
||||
if [[ "$has_credentials" == "true" ]]; then
|
||||
local username password
|
||||
username=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.username // empty')
|
||||
password=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.password // empty')
|
||||
|
||||
if [[ -n "$username" && -n "$password" ]]; then
|
||||
credentials_info="Username: $username | Password: $password"
|
||||
elif [[ -n "$username" ]]; then
|
||||
credentials_info="Username: $username"
|
||||
elif [[ -n "$password" ]]; then
|
||||
credentials_info="Password: $password"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$credentials_info"
|
||||
}
|
||||
|
||||
|
||||
run_script_by_slug() {
|
||||
local slug="$1"
|
||||
local script_info
|
||||
script_info=$(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64')
|
||||
|
||||
decode() {
|
||||
echo "$1" | base64 --decode | jq -r "$2"
|
||||
}
|
||||
|
||||
local name desc script_url notes
|
||||
name=$(decode "$script_info" ".name")
|
||||
desc=$(decode "$script_info" ".desc")
|
||||
script_url=$(decode "$script_info" ".script_url")
|
||||
notes=$(decode "$script_info" ".notes | join(\"\n\")")
|
||||
|
||||
|
||||
# Array with script names, URLs, categories, and descriptions
|
||||
scripts=(
|
||||
"Proxmox VE LXC IP-Tag|Containers|$BASE_URL/add-lxc-iptag.sh|Description:\n\nThis script automatically adds IP address as tags to LXC containers using a Systemd service.\n\nThe service also updates the tags if a LXC IP address is changed. Configuration: nano /opt/lxc-iptag/iptag.conf. iptag.service must be restarted after change.\n\n\The Proxmox Node must contain ipcalc and net-tools. apt-get install -y ipcalc net-tools"
|
||||
"Add Netbird to LXC|Networking|$BASE_URL/add-netbird-lxc.sh|Description:\n\nNetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.\n\nAfter the script finishes, reboot the LXC then run netbird up in the LXC console.\n\n\The script only works in Debian/Ubuntu, not in Alpine!"
|
||||
"Add Tailscale to LXC|Networking|$BASE_URL/add-tailscale-lxc.sh|Description:\n\nTailscale is a software-defined networking solution that enables secure communication between devices over the internet.\n\nIt creates a virtual private network (VPN) that enables devices to communicate with each other as if they were on the same local network.\n\n\After the script finishes, reboot the LXC then run tailscale up in the LXC console."
|
||||
"Proxmox VE LXC Cleaner|Maintenance|$BASE_URL/clean-lxcs.sh|Description:\n\nThis script provides options to delete logs and cache, and repopulate apt lists for Ubuntu and Debian systems."
|
||||
"Proxmox VE Host Backup|Security|$BASE_URL/host-backup.sh|Description:\n\nThis script serves as a versatile backup utility, enabling users to specify both the backup path and the directory they want to work in.\n\nThis flexibility empowers users to select the specific files and directories they wish to back up, making it compatible with a wide range of hosts, not limited to Proxmox.\n\nA backup is rendered ineffective when it remains stored on the host"
|
||||
"Add hardware Acceleration LXC|Containers|$BASE_URL/hw-acceleration.sh|Description:\n\nEnables hardware acceleration IGPU for LXC containers."
|
||||
"Proxmox Clean Orphaned LVM|Maintenance|$BASE_URL/clean-orphaned-lvm.sh|Description:\n\nThis script helps Proxmox users identify and remove orphaned LVM volumes that are no longer associated with any VM or LXC container.\n\nIt scans all LVM volumes, detects unused ones, and provides an interactive prompt to delete them safely.\n\nSystem-critical volumes like root, swap, and data are excluded to prevent accidental deletion."
|
||||
"Install Crowdsec|Security|$BASE_URL/crowdsec.sh|Description:\n\nCrowdSec is a free and open-source intrusion prevention system (IPS) designed to provide network security against malicious traffic.\n\nIt is a collaborative IPS that analyzes behaviors and responses to attacks by sharing signals across a community of users."
|
||||
"Proxmox VE LXC Filesystem Trim|Maintenance|$BASE_URL/fstrim.sh|Description:\n\nThis maintains SSD performance by managing unused blocks.\n\nThin-provisioned storage systems also require management to prevent unnecessary storage use.\n\nVMs automate fstrim, while LXC containers need manual or automated fstrim processes for optimal performance.\n\nThis is designed to work with SSDs on ext4 filesystems only."
|
||||
"Install Glances|Monitoring|$BASE_URL/glances.sh|Description:\n\nGlances is an open-source system cross-platform monitoring tool.\n\nIt allows real-time monitoring of various aspects of your system such as CPU, memory, disk, network usage etc."
|
||||
"Proxmox VE Kernel Clean|Maintenance|$BASE_URL/kernel-clean.sh|Description:\n\nCleaning unused kernel images is beneficial for reducing the length of the GRUB menu and freeing up disk space.\n\nBy removing old, unused kernels, the system is able to conserve disk space and streamline the boot process."
|
||||
"Proxmox VE Kernel Pin|System|$BASE_URL/kernel-pin.sh|Description:\n\nKernel Pin is an essential tool for effortlessly managing kernel pinning and unpinning."
|
||||
"Container LXC Deletion|Containers|$BASE_URL/lxc-delete.sh|Description:\n\nThis script helps manage and delete LXC containers on a Proxmox VE server.\n\nIt lists all available containers, allowing the user to select one or more for deletion through an interactive menu.\n\nRunning containers are automatically stopped before deletion, and the user is asked to confirm each action.\n\nThe script ensures a controlled and efficient container management process."
|
||||
"Proxmox VE Processor Microcode|System|$BASE_URL/microcode.sh|Description:\n\nProcessor Microcode is a layer of low-level software that runs on the processor and provides patches or updates to its firmware.\n\nMicrocode updates can fix hardware bugs, improve performance, and enhance security features of the processor."
|
||||
"Proxmox VE Netdata|Monitoring|$BASE_URL/netdata.sh|Description:\n\nNetdata is an open-source, real-time performance monitoring tool designed to provide insights into the performance and health of systems and applications.\n\nIt is often used by system administrators, DevOps professionals, and developers to monitor and troubleshoot issues on servers and other devices."
|
||||
"Install Olivetin|Applications|$BASE_URL/olivetin.sh|Description:\n\nOliveTin provides a secure and straightforward way to execute pre-determined shell commands through a web-based interface.\n\nConfiguration Path: /etc/OliveTin/config.yaml"
|
||||
"Proxmox VE Post Install|System|$BASE_URL/post-pve-install.sh|Description:\n\nThis script provides options for managing Proxmox VE repositories, including disabling the Enterprise Repo, adding or correcting PVE sources, enabling the No-Subscription Repo, adding the test Repo, disabling the subscription nag, updating Proxmox VE, and rebooting the system.\n\nExecute within the Proxmox shell.\n\n\It is recommended to answer yes (y) to all options presented during the process."
|
||||
"Proxmox VE CPU Scaling Governor|System|$BASE_URL/scaling-governor.sh|Description:\n\nThe CPU scaling governor determines how the CPU frequency is adjusted based on the workload, with the goal of either conserving power or improving performance.\n\nBy scaling the frequency up or down, the operating system can optimize the CPU usage and conserve energy when possible. Generic Scaling Governors."
|
||||
"Proxmox VE Cron LXC Updater|Maintenance|$BASE_URL/cron-update-lxcs.sh|Description:\n\nThis script will add/remove a crontab schedule that updates all LXCs every Sunday at midnight. To exclude LXCs from updating, edit the crontab using crontab -e and add CTID as shown in the example below:\n\n0 0 * * 0 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-lxcs-cron.sh)\" -s 103 111 >>/var/log/update-lxcs-cron.log 2>/dev/null"
|
||||
"Proxmox VE LXC Updater|Maintenance|$BASE_URL/update-lxcs.sh|Description:\n\nThis script has been created to simplify and speed up the process of updating all LXC containers across various Linux distributions, such as Ubuntu, Debian, Devuan, Alpine Linux, CentOS-Rocky-Alma, Fedora, and ArchLinux.\n\nDesigned to automatically skip templates and specific containers during the update, enhancing its convenience and usability."
|
||||
"Proxmox Backup Server|Security|$BASE_URL2/ct/proxmox-backup-server.sh|Description:\n\nProxmox Backup Server is an enterprise backup solution, for backing up and restoring VMs, containers, and physical hosts. By supporting incremental, fully deduplicated backups, Proxmox Backup Server significantly reduces network load and saves valuable storage space.\n\n\nSet a root password if using autologin. This will be the PBS password. passwd root"
|
||||
local notes_dialog=""
|
||||
if [[ -n "$notes" ]]; then
|
||||
while IFS= read -r line; do
|
||||
notes_dialog+="• $line\n"
|
||||
done <<< "$notes"
|
||||
notes_dialog="${notes_dialog%\\n}"
|
||||
fi
|
||||
|
||||
|
||||
)
|
||||
local credentials
|
||||
credentials=$(format_credentials "$script_info")
|
||||
|
||||
show_menu() {
|
||||
declare -A category_order
|
||||
category_order["System"]=1
|
||||
category_order["Maintenance"]=2
|
||||
category_order["Containers"]=3
|
||||
category_order["Applications"]=4
|
||||
category_order["Monitoring"]=5
|
||||
category_order["Networking"]=6
|
||||
category_order["Security"]=7
|
||||
|
||||
custom_sort() {
|
||||
while IFS='|' read -r name category url description; do
|
||||
category=$(echo "$category" | xargs)
|
||||
order=${category_order[$category]:-999}
|
||||
printf "%d|%s|%s|%s|%s\n" "$order" "$name" "$category" "$url" "$description"
|
||||
done | sort -n | cut -d'|' -f2-
|
||||
}
|
||||
local msg="\Zb\Z4Descripción:\Zn\n$desc"
|
||||
[[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog"
|
||||
[[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials"
|
||||
|
||||
dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85
|
||||
if [[ $? -eq 0 ]]; then
|
||||
download_script "$script_url"
|
||||
echo
|
||||
echo
|
||||
|
||||
if [[ -n "$desc" || -n "$notes" || -n "$credentials" ]]; then
|
||||
echo -e "$TAB\e[1;36mScript Information:\e[0m"
|
||||
|
||||
|
||||
|
||||
if [[ -n "$notes" ]]; then
|
||||
echo -e "$TAB\e[1;33mNotes:\e[0m"
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
echo -e "$TAB• $line"
|
||||
done <<< "$notes"
|
||||
echo
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "$credentials" ]]; then
|
||||
echo -e "$TAB\e[1;32mDefault Credentials:\e[0m"
|
||||
echo "$TAB$credentials"
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_success "Press Enter to return to the main menu..."
|
||||
read -r
|
||||
RETURN_TO_MAIN=true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
search_and_filter_scripts() {
|
||||
local search_term=""
|
||||
|
||||
while true; do
|
||||
search_term=$(dialog --inputbox "Enter search term (leave empty to show all scripts):" \
|
||||
8 65 "$search_term" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && return
|
||||
|
||||
local filtered_json
|
||||
if [[ -z "$search_term" ]]; then
|
||||
filtered_json="$CACHE_JSON"
|
||||
else
|
||||
local search_lower
|
||||
search_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]')
|
||||
filtered_json=$(echo "$CACHE_JSON" | jq --arg term "$search_lower" '
|
||||
[.[] | select(
|
||||
(.name | ascii_downcase | contains($term)) or
|
||||
(.desc | ascii_downcase | contains($term))
|
||||
)]')
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(echo "$filtered_json" | jq length)
|
||||
|
||||
if [[ $count -eq 0 ]]; then
|
||||
dialog --msgbox "No scripts found for: '$search_term'\n\nTry a different search term." 8 50
|
||||
continue
|
||||
fi
|
||||
|
||||
while true; do
|
||||
IFS=$'\n' sorted_scripts=($(printf "%s\n" "${scripts[@]}" | custom_sort))
|
||||
unset IFS
|
||||
|
||||
HEADER=$(printf " %-57s %-20s" "$(translate "Name")" "$(translate "Category")")
|
||||
|
||||
menu_items=()
|
||||
for script in "${sorted_scripts[@]}"; do
|
||||
IFS='|' read -r name category url description <<< "$script"
|
||||
translated_category=$(translate "$category")
|
||||
padded_name=$(printf "%-57s" "$name")
|
||||
menu_items+=("$padded_name" "$translated_category")
|
||||
done
|
||||
|
||||
menu_items+=("$(translate "Return to Main Menu")" "")
|
||||
|
||||
cleanup
|
||||
|
||||
script_selection=$(whiptail --title "$(translate "Essential Proxmox VE Helper-Scripts")" \
|
||||
--menu "\n$HEADER\n\n$(translate "Select a script to execute")" 25 78 16 \
|
||||
"${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -n "$script_selection" ]; then
|
||||
script_selection=$(echo "$script_selection" | xargs)
|
||||
if [ "$script_selection" = "$(translate "Return to Main Menu")" ]; then
|
||||
|
||||
|
||||
whiptail --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE")" 15 70
|
||||
|
||||
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
fi
|
||||
|
||||
for script in "${sorted_scripts[@]}"; do
|
||||
IFS='|' read -r name category url description <<< "$script"
|
||||
if [ "$name" = "$script_selection" ]; then
|
||||
selected_url="$url"
|
||||
selected_description=$(translate "$description")
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$selected_url" ]; then
|
||||
if whiptail --title "$(translate "Script Information")" \
|
||||
--yes-button "$(translate "Accept")" \
|
||||
--no-button "$(translate "Cancel")" \
|
||||
--yesno "$selected_description" 20 78; then
|
||||
#msg_info2 "$(translate "Executing script:") $script_selection"
|
||||
#sleep 2
|
||||
download_script "$selected_url"
|
||||
msg_ok "$(translate "Script completed.")"
|
||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
||||
read -r
|
||||
clear
|
||||
else
|
||||
msg_info2 "$(translate "Script execution cancelled.")"
|
||||
sleep 2
|
||||
fi
|
||||
else
|
||||
echo "$(translate "Error: Could not find the selected script URL.")"
|
||||
read -rp "$(translate "Press Enter to continue...")"
|
||||
fi
|
||||
else
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
fi
|
||||
declare -A index_to_slug
|
||||
local menu_items=()
|
||||
local i=1
|
||||
|
||||
while IFS=$'\t' read -r slug name type; do
|
||||
index_to_slug[$i]="$slug"
|
||||
local label
|
||||
label=$(get_type_label "$type")
|
||||
local padded_name
|
||||
padded_name=$(printf "%-42s" "$name")
|
||||
local entry="$padded_name $label"
|
||||
menu_items+=("$i" "$entry")
|
||||
((i++))
|
||||
done < <(echo "$filtered_json" | jq -r '
|
||||
sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||
|
||||
menu_items+=("" "")
|
||||
menu_items+=("new_search" "New Search")
|
||||
menu_items+=("show_all" "Show All Scripts")
|
||||
|
||||
local title="Search Results"
|
||||
if [[ -n "$search_term" ]]; then
|
||||
title="Search Results for: '$search_term' ($count found)"
|
||||
else
|
||||
title="All Available Scripts ($count total)"
|
||||
fi
|
||||
|
||||
local selected
|
||||
selected=$(dialog --colors --backtitle "ProxMenux" \
|
||||
--title "$title" \
|
||||
--menu "Select a script or action:" \
|
||||
22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$selected" in
|
||||
"new_search")
|
||||
break
|
||||
;;
|
||||
"show_all")
|
||||
search_term=""
|
||||
filtered_json="$CACHE_JSON"
|
||||
count=$(echo "$filtered_json" | jq length)
|
||||
continue
|
||||
;;
|
||||
"back"|"")
|
||||
return
|
||||
;;
|
||||
*)
|
||||
if [[ -n "${index_to_slug[$selected]}" ]]; then
|
||||
run_script_by_slug "${index_to_slug[$selected]}"
|
||||
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; }
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
while true; do
|
||||
MENU_ITEMS=()
|
||||
|
||||
MENU_ITEMS+=("search" "Search/Filter Scripts")
|
||||
MENU_ITEMS+=("" "")
|
||||
|
||||
for id in $(printf "%s\n" "${!CATEGORY_COUNT[@]}" | sort -n); do
|
||||
name="${CATEGORY_NAMES[$id]:-Category $id}"
|
||||
count="${CATEGORY_COUNT[$id]}"
|
||||
padded_name=$(printf "%-35s" "$name")
|
||||
padded_count=$(printf "(%2d)" "$count")
|
||||
MENU_ITEMS+=("$id" "$padded_name $padded_count")
|
||||
done
|
||||
|
||||
if [[ "$LANGUAGE" != "en" ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_lang "$(translate "Generating automatic translations...")"
|
||||
fi
|
||||
show_menu
|
||||
SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \
|
||||
"Select a category or search for scripts:" 20 70 14 \
|
||||
"${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || {
|
||||
dialog --clear --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "\n\n$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:")\n\nhttps://community-scripts.github.io/ProxmoxVE" 15 70
|
||||
#clear
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
}
|
||||
|
||||
if [[ "$SELECTED" == "search" ]]; then
|
||||
search_and_filter_scripts
|
||||
continue
|
||||
fi
|
||||
|
||||
while true; do
|
||||
declare -A INDEX_TO_SLUG
|
||||
SCRIPTS=()
|
||||
i=1
|
||||
while IFS=$'\t' read -r slug name type; do
|
||||
INDEX_TO_SLUG[$i]="$slug"
|
||||
label=$(get_type_label "$type")
|
||||
padded_name=$(printf "%-42s" "$name")
|
||||
entry="$padded_name $label"
|
||||
SCRIPTS+=("$i" "$entry")
|
||||
((i++))
|
||||
done < <(echo "$CACHE_JSON" | jq -r --argjson id "$SELECTED" \
|
||||
'[.[] | select(.categories | index($id)) | {slug, name, type}] | sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||
|
||||
SCRIPT_INDEX=$(dialog --colors --backtitle "ProxMenux" --title "Scripts in ${CATEGORY_NAMES[$SELECTED]}" --menu \
|
||||
"Choose a script to execute:" 20 70 14 \
|
||||
"${SCRIPTS[@]}" 3>&1 1>&2 2>&3) || break
|
||||
|
||||
SCRIPT_SELECTED="${INDEX_TO_SLUG[$SCRIPT_INDEX]}"
|
||||
run_script_by_slug "$SCRIPT_SELECTED"
|
||||
|
||||
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; break; }
|
||||
done
|
||||
done
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 24/02/2025
|
||||
# Version : 1.2
|
||||
# Last Updated: 06/07/2025
|
||||
# ==========================================================
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
@@ -19,21 +17,24 @@ VENV_PATH="/opt/googletrans-env"
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
#show_proxmenux_logo
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
confirm_and_run() {
|
||||
local name="$1"
|
||||
local command="$2"
|
||||
|
||||
if whiptail --title "$(translate "Confirmation")" \
|
||||
--yesno "$(translate "Do you want to run the post-installation script from") $name?" \
|
||||
10 70; then
|
||||
|
||||
dialog --clear --title "$(translate "Confirmation")" \
|
||||
--yesno "\n\n$(translate "Do you want to run the post-installation script from") $name?" 10 70
|
||||
|
||||
response=$?
|
||||
clear
|
||||
|
||||
if [ $response -eq 0 ]; then
|
||||
eval "$command"
|
||||
echo ""
|
||||
echo
|
||||
msg_success "$(translate 'Press ENTER to continue...')"
|
||||
read -r _
|
||||
else
|
||||
@@ -42,62 +43,149 @@ confirm_and_run() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
confirm_automated_script() {
|
||||
local script_info=""
|
||||
|
||||
|
||||
# Define scripts array
|
||||
scripts=(
|
||||
"Customizable script post-installation|ProxMenux|bash <(curl -s $REPO_URL/scripts/customizable_post_install.sh)"
|
||||
"Proxmox VE Post Install|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\""
|
||||
"xshok-proxmox Post install|fork xshok-proxmox|confirm_and_run \"Xshok\" \"wget https://raw.githubusercontent.com/MacRimi/xshok-proxmox/master/install-post.sh -c -O install-post.sh && bash install-post.sh && rm install-post.sh\""
|
||||
"Uninstall Tools|ProxMenux|bash <(curl -s $REPO_URL/scripts/uninstall-tools.sh)"
|
||||
script_info+="$(translate "This script will apply the following optimizations and advanced adjustments to your Proxmox VE server"):\n\n"
|
||||
script_info+="• $(translate "Configure") \Z4free repositories\Z0 $(translate "and upgrade the system (disables the enterprise repo)")\n"
|
||||
script_info+="• $(translate "Optionally remove") \Z4subscription banner\Z0 $(translate "from Proxmox web interface (you will be asked)")\n"
|
||||
script_info+="• $(translate "Optimize") \Z4memory\Z0, \Z4kernel\Z0, $(translate "and") \Z4network\Z0 $(translate "for better performance and stability")\n"
|
||||
script_info+="• $(translate "Install and configure") \Z4Log2RAM\Z0 $(translate "(only on SSD/NVMe) to protect your disk")\n"
|
||||
script_info+="• $(translate "Improve log rotation and limit log size to save space and extend disk life")\n"
|
||||
script_info+="• $(translate "Increase file and process limits for advanced workloads")\n"
|
||||
script_info+="• $(translate "Set up time synchronization and entropy generation")\n"
|
||||
script_info+="• $(translate "Add color prompts and useful aliases to the terminal environment")\n\n"
|
||||
|
||||
script_info+="\Zb$(translate "All changes are reversible using the ProxMenux uninstaller.")\Z0\n\n"
|
||||
script_info+="$(translate "Do you want to apply these optimizations now?")"
|
||||
|
||||
dialog --clear --colors \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate "Automated Post-Install Script")" \
|
||||
--yesno "$script_info" 22 80
|
||||
|
||||
local response=$?
|
||||
clear
|
||||
|
||||
if [ $response -eq 0 ]; then
|
||||
bash <(curl -s $REPO_URL/scripts/post_install/auto_post_install.sh)
|
||||
else
|
||||
msg_warn "$(translate "Cancelled by user.")"
|
||||
sleep 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
declare -a PROXMENUX_SCRIPTS=(
|
||||
"Customizable post-installation script|ProxMenux|bash <(curl -s $REPO_URL/scripts/post_install/customizable_post_install.sh)"
|
||||
"Automated post-installation script|ProxMenux|confirm_automated_script"
|
||||
"Uninstall optimizations|ProxMenux|bash <(curl -s $REPO_URL/scripts/post_install/uninstall-tools.sh)"
|
||||
)
|
||||
|
||||
|
||||
declare -a COMMUNITY_SCRIPTS=(
|
||||
"Proxmox VE Post Install|Helper-Scripts|bash -c \"\$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pve-install.sh); msg_success \\\"\$(translate 'Press ENTER to continue...')\\\"; read -r _\""
|
||||
"Xshok-proxmox Post install|fork xshok-proxmox|confirm_and_run \"Xshok\" \"wget https://raw.githubusercontent.com/MacRimi/xshok-proxmox/master/install-post.sh -c -O install-post.sh && bash install-post.sh && rm install-post.sh\""
|
||||
)
|
||||
|
||||
# ==========================================================
|
||||
|
||||
format_menu_item() {
|
||||
local description="$1"
|
||||
local source="$2"
|
||||
local total_width=62
|
||||
|
||||
|
||||
local desc_length=${#description}
|
||||
local source_length=${#source}
|
||||
local spaces_needed=$((total_width - desc_length - source_length))
|
||||
|
||||
|
||||
[ $spaces_needed -lt 3 ] && spaces_needed=3
|
||||
|
||||
|
||||
local spacing=""
|
||||
for ((i=0; i<spaces_needed; i++)); do
|
||||
spacing+=" "
|
||||
done
|
||||
|
||||
echo "${description}${spacing}${source}"
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
show_menu() {
|
||||
while true; do
|
||||
HEADER=$(printf " %-52s %-20s" "$(translate "Name")" "$(translate "Repository")")
|
||||
local menu_items=()
|
||||
|
||||
|
||||
menu_items=()
|
||||
for i in "${!scripts[@]}"; do
|
||||
IFS='|' read -r name repository command <<< "${scripts[$i]}"
|
||||
number=$((i+1))
|
||||
padded_option=$(printf "%2d %-50s" "$number" "$(translate "$name")")
|
||||
menu_items+=("$padded_option" "$repository")
|
||||
declare -A script_commands
|
||||
local counter=1
|
||||
|
||||
|
||||
for script in "${PROXMENUX_SCRIPTS[@]}"; do
|
||||
IFS='|' read -r name source command <<< "$script"
|
||||
local translated_name="$(translate "$name")"
|
||||
local formatted_item
|
||||
formatted_item=$(format_menu_item "$translated_name" "$source")
|
||||
menu_items+=("$counter" "$formatted_item")
|
||||
script_commands["$counter"]="$command"
|
||||
((counter++))
|
||||
done
|
||||
|
||||
menu_items+=("$(printf "%2d %-40s" "$((${#scripts[@]}+1))" "$(translate "Return to Main Menu")")" "")
|
||||
|
||||
cleanup
|
||||
|
||||
menu_items+=("" "")
|
||||
menu_items+=("-" "───────────────────── $(translate "Community Scripts") ──────────────────────")
|
||||
menu_items+=("" "")
|
||||
|
||||
script_selection=$(whiptail --title "$(translate "Post-Installation Scripts Menu")" \
|
||||
--menu "\n$HEADER" 20 78 $((${#scripts[@]}+1)) \
|
||||
"${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -n "$script_selection" ]; then
|
||||
selected_number=$(echo "$script_selection" | awk '{print $1}')
|
||||
|
||||
if [ "$selected_number" = "$((${#scripts[@]}+1))" ]; then
|
||||
#show_proxmenux_logo
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
fi
|
||||
for script in "${COMMUNITY_SCRIPTS[@]}"; do
|
||||
IFS='|' read -r name source command <<< "$script"
|
||||
local translated_name="$(translate "$name")"
|
||||
local formatted_item
|
||||
formatted_item=$(format_menu_item "$translated_name" "$source")
|
||||
menu_items+=("$counter" "$formatted_item")
|
||||
script_commands["$counter"]="$command"
|
||||
((counter++))
|
||||
done
|
||||
|
||||
|
||||
index=$((selected_number - 1))
|
||||
if [ $index -ge 0 ] && [ $index -lt ${#scripts[@]} ]; then
|
||||
IFS='|' read -r name repository command <<< "${scripts[$index]}"
|
||||
eval "$command"
|
||||
fi
|
||||
menu_items+=("" "")
|
||||
menu_items+=("0" "$(translate "Return to Main Menu")")
|
||||
|
||||
|
||||
else
|
||||
#show_proxmenux_logo
|
||||
exec 3>&1
|
||||
script_selection=$(dialog --clear \
|
||||
--backtitle "ProxMenux" \
|
||||
--title "$(translate "Post-Installation Scripts")" \
|
||||
--menu "\n$(translate "Select a post-installation script:"):\n" \
|
||||
22 78 15 \
|
||||
"${menu_items[@]}" 2>&1 1>&3)
|
||||
exit_status=$?
|
||||
exec 3>&-
|
||||
|
||||
|
||||
if [ $exit_status -ne 0 ] || [ "$script_selection" = "0" ]; then
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$script_selection" == "-" || "$script_selection" == "" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "${script_commands[$script_selection]}" ]]; then
|
||||
eval "${script_commands[$script_selection]}"
|
||||
else
|
||||
msg_error "$(translate "Invalid selection")"
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
clear
|
||||
if [[ "$LANGUAGE" != "en" ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_lang "$(translate "Generating automatic translations...")"
|
||||
fi
|
||||
# ==========================================================
|
||||
|
||||
show_menu
|
||||
show_menu
|
||||
|
||||
+1080
-26
File diff suppressed because it is too large
Load Diff
@@ -26,36 +26,32 @@ initialize_cache
|
||||
|
||||
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate "Disk and Storage Manager Menu")" --menu "$(translate "Select an option:")" 20 70 10 \
|
||||
"1" "$(translate "Add Disk Passthrough to a VM")" \
|
||||
"2" "$(translate "Add Disk") Passthrough $(translate "to a CT")" \
|
||||
"3" "$(translate "Import Disk Image to a VM")" \
|
||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Disk and Storage Manager Menu")" \
|
||||
--menu "\n$(translate "Select an option:")" 20 70 10 \
|
||||
"1" "$(translate "Add Disk") Passthrough $(translate "to a VM")" \
|
||||
"2" "$(translate "Add Disk") Passthrough $(translate "to a LXC")" \
|
||||
"3" "$(translate "Import Disk Image to a VM")" \
|
||||
"4" "$(translate "Return to Main Menu")" \
|
||||
2>&1 >/dev/tty)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
#show_proxmenux_logo
|
||||
msg_info2 "$(translate "Running script: Add Disk Passthrough to a VM")..."
|
||||
clear
|
||||
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough.sh")
|
||||
;;
|
||||
2)
|
||||
#show_proxmenux_logo
|
||||
msg_info2 "$(translate "Running script: Add Disk Passthrough to a CT")..."
|
||||
clear
|
||||
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough_ct.sh")
|
||||
;;
|
||||
3)
|
||||
#show_proxmenux_logo
|
||||
msg_info2 "$(translate "Running script: Import Disk Image to a VM")..."
|
||||
clear
|
||||
bash <(curl -s "$REPO_URL/scripts/storage/import-disk-image.sh")
|
||||
;;
|
||||
4)
|
||||
#show_proxmenux_logo
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
;;
|
||||
*)
|
||||
#show_proxmenux_logo
|
||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 02/07/2025
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
while true; do
|
||||
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Utilities Menu")" \
|
||||
--menu "\n$(translate "Select an option:")" 20 70 8 \
|
||||
"1" "$(translate "UUp Dump ISO creator Custom")" \
|
||||
"2" "$(translate "System Utilities Installer")" \
|
||||
"3" "$(translate "Proxmox System Update")" \
|
||||
"4" "$(translate "Return to Main Menu")" \
|
||||
2>&1 >/dev/tty)
|
||||
|
||||
case $OPTION in
|
||||
1)
|
||||
bash <(curl -s "$REPO_URL/scripts/utilities/uup_dump_iso_creator.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
bash <(curl -s "$REPO_URL/scripts/utilities/system_utils.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
;;
|
||||
3)
|
||||
proxmox_update_msg="\n"
|
||||
proxmox_update_msg+="$(translate "This script will update your Proxmox VE system with advanced options:")\n\n"
|
||||
proxmox_update_msg+="• $(translate "Repairs and optimizes repositories")\n"
|
||||
proxmox_update_msg+="• $(translate "Cleans duplicate or conflicting sources")\n"
|
||||
proxmox_update_msg+="• $(translate "Switches to the free no-subscription repository")\n"
|
||||
proxmox_update_msg+="• $(translate "Updates all Proxmox and Debian packages")\n"
|
||||
proxmox_update_msg+="• $(translate "Installs essential packages if missing")\n"
|
||||
proxmox_update_msg+="• $(translate "Checks for LVM and storage issues")\n"
|
||||
proxmox_update_msg+="• $(translate "Performs automatic cleanup after updating")\n\n"
|
||||
proxmox_update_msg+="$(translate "Do you want to proceed and run the Proxmox System Update?")"
|
||||
|
||||
dialog --colors --backtitle "ProxMenux" --title "$(translate "Proxmox System Update")" \
|
||||
--yesno "$proxmox_update_msg" 20 70
|
||||
|
||||
dialog_result=$?
|
||||
if [[ $dialog_result -eq 0 ]]; then
|
||||
bash <(curl -s "$REPO_URL/scripts/utilities/proxmox_update.sh")
|
||||
if [ $? -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||
esac
|
||||
done
|
||||
@@ -0,0 +1,827 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenux - Complete Post-Installation Script with Registration
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 06/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
#
|
||||
# The script performs system optimizations including:
|
||||
# - Repository configuration and system upgrades
|
||||
# - Subscription banner removal and UI enhancements
|
||||
# - Advanced memory management and kernel optimizations
|
||||
# - Network stack tuning and security hardening
|
||||
# - Storage optimizations including log2ram for SSD protection
|
||||
# - System limits increases and entropy generation improvements
|
||||
# - Journald and logrotate optimizations for better log management
|
||||
# - Security enhancements including RPC disabling and time synchronization
|
||||
# - Bash environment customization and system monitoring setup
|
||||
#
|
||||
# Key Features:
|
||||
# - Zero-interaction automation: Runs completely unattended
|
||||
# - Intelligent hardware detection: Automatically detects SSD/NVMe for log2ram
|
||||
# - RAM-aware configurations: Adjusts settings based on available system memory
|
||||
# - Comprehensive error handling: Robust installation with fallback mechanisms
|
||||
# - Registration system: Tracks installed optimizations for easy management
|
||||
# - Reboot management: Intelligently handles reboot requirements
|
||||
# - Translation support: Multi-language compatible through ProxMenux framework
|
||||
# - Rollback compatibility: All optimizations can be reversed using the uninstall script
|
||||
#
|
||||
# This script is based on the post-install script cutotomizable
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Global variables
|
||||
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) / 1024 / 1000))
|
||||
NECESSARY_REBOOT=0
|
||||
SCRIPT_TITLE="Customizable post-installation optimization script"
|
||||
|
||||
# ==========================================================
|
||||
# Tool registration system
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
|
||||
|
||||
check_extremeshok_warning() {
|
||||
local marker_file="/etc/extremeshok"
|
||||
|
||||
if [[ -f "$marker_file" ]]; then
|
||||
dialog --backtitle "ProxMenux" --title "xshok-proxmox Post-Install Detected" \
|
||||
--yesno "\n$(translate "It appears that you have already executed the xshok-proxmox post-install script on this system.")\n\n\
|
||||
$(translate "If you continue, some adjustments may be duplicated or conflict with those already made by xshok.")\n\n\
|
||||
$(translate "Do you want to continue anyway?")" 13 70
|
||||
|
||||
local response=$?
|
||||
if [[ $response -ne 0 ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_warn "$(translate "Action cancelled due to previous xshok-proxmox modifications.")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
apt_upgrade() {
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
|
||||
if [[ -z "$pve_version" ]]; then
|
||||
msg_error "Unable to detect Proxmox version."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$pve_version" -ge 9 ]]; then
|
||||
|
||||
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve.sh")
|
||||
else
|
||||
|
||||
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve8.sh")
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
remove_subscription_banner() {
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
|
||||
if [[ -z "$pve_version" ]]; then
|
||||
msg_error "Unable to detect Proxmox version."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$pve_version" -ge 9 ]]; then
|
||||
if ! whiptail --title "Proxmox VE 9.x Subscription Banner Removal" \
|
||||
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then
|
||||
msg_warn "Banner removal cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve9.sh")
|
||||
else
|
||||
if ! whiptail --title "Proxmox VE 8.x Subscription Banner Removal" \
|
||||
--yesno "Do you want to remove the Proxmox subscription banner from the web interface for PVE $pve_version?" 10 70; then
|
||||
msg_warn "Banner removal cancelled by user."
|
||||
return 1
|
||||
fi
|
||||
bash <(curl -fsSL "$REPO_URL/scripts/global/remove-banner-pve8.sh")
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
configure_time_sync() {
|
||||
msg_info "$(translate "Configuring system time settings...")"
|
||||
|
||||
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
|
||||
if [ -z "$this_ip" ]; then
|
||||
msg_warn "$(translate "Failed to obtain public IP address")"
|
||||
timezone="UTC"
|
||||
else
|
||||
|
||||
timezone=$(curl -s "https://ipapi.co/${this_ip}/timezone")
|
||||
if [ -z "$timezone" ]; then
|
||||
msg_warn "$(translate "Failed to determine timezone from IP address")"
|
||||
timezone="UTC"
|
||||
else
|
||||
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Enabling automatic time synchronization...")"
|
||||
if timedatectl set-ntp true; then
|
||||
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
|
||||
register_tool "time_sync" true
|
||||
else
|
||||
msg_error "$(translate "Failed to enable automatic time synchronization")"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
skip_apt_languages() {
|
||||
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
|
||||
local default_locale=""
|
||||
|
||||
if [ -f /etc/default/locale ]; then
|
||||
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
|
||||
elif [ -f /etc/environment ]; then
|
||||
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
|
||||
fi
|
||||
|
||||
default_locale="${default_locale:-en_US.UTF-8}"
|
||||
local normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
|
||||
|
||||
if ! locale -a | grep -qi "^$normalized_locale$"; then
|
||||
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
|
||||
echo "$default_locale UTF-8" >> /etc/locale.gen
|
||||
fi
|
||||
locale-gen "$default_locale" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99-disable-translations
|
||||
|
||||
msg_ok "$(translate "APT configured to skip additional languages")"
|
||||
register_tool "apt_languages" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_journald() {
|
||||
msg_info "$(translate "Limiting size and optimizing journald...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/systemd/journald.conf
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
SplitMode=none
|
||||
RateLimitInterval=0
|
||||
RateLimitIntervalSec=0
|
||||
RateLimitBurst=0
|
||||
ForwardToSyslog=no
|
||||
ForwardToWall=yes
|
||||
Seal=no
|
||||
Compress=yes
|
||||
SystemMaxUse=64M
|
||||
RuntimeMaxUse=60M
|
||||
MaxLevelStore=warning
|
||||
MaxLevelSyslog=warning
|
||||
MaxLevelKMsg=warning
|
||||
MaxLevelConsole=notice
|
||||
MaxLevelWall=crit
|
||||
EOF
|
||||
|
||||
systemctl restart systemd-journald.service > /dev/null 2>&1
|
||||
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
|
||||
journalctl --rotate > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Journald optimized - Max size: 64M")"
|
||||
register_tool "journald" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_logrotate() {
|
||||
msg_info "$(translate "Optimizing logrotate configuration...")"
|
||||
local logrotate_conf="/etc/logrotate.conf"
|
||||
local backup_conf="${logrotate_conf}.bak"
|
||||
|
||||
if ! grep -q "# ProxMenux optimized configuration" "$logrotate_conf"; then
|
||||
cp "$logrotate_conf" "$backup_conf"
|
||||
cat <<EOF > "$logrotate_conf"
|
||||
# ProxMenux optimized configuration
|
||||
daily
|
||||
su root adm
|
||||
rotate 7
|
||||
create
|
||||
compress
|
||||
size=10M
|
||||
delaycompress
|
||||
copytruncate
|
||||
include /etc/logrotate.d
|
||||
EOF
|
||||
systemctl restart logrotate > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Logrotate optimization completed")"
|
||||
register_tool "logrotate" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
increase_system_limits() {
|
||||
msg_info "$(translate "Increasing various system limits...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-maxwatches.conf << EOF
|
||||
# ProxMenux configuration
|
||||
fs.inotify.max_user_watches = 1048576
|
||||
fs.inotify.max_user_instances = 1048576
|
||||
fs.inotify.max_queued_events = 1048576
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/security/limits.d/99-limits.conf << EOF
|
||||
# ProxMenux configuration
|
||||
* soft nproc 1048576
|
||||
* hard nproc 1048576
|
||||
* soft nofile 1048576
|
||||
* hard nofile 1048576
|
||||
root soft nproc unlimited
|
||||
root hard nproc unlimited
|
||||
root soft nofile unlimited
|
||||
root hard nofile unlimited
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-maxkeys.conf << EOF
|
||||
# ProxMenux configuration
|
||||
kernel.keys.root_maxkeys=1000000
|
||||
kernel.keys.maxkeys=1000000
|
||||
EOF
|
||||
|
||||
|
||||
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
|
||||
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
|
||||
echo "DefaultLimitNOFILE=256000" >> "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
|
||||
if ! grep -q "^session required pam_limits.so" "$file"; then
|
||||
echo 'session required pam_limits.so' >> "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if ! grep -q "ulimit -n 256000" /root/.profile; then
|
||||
echo "ulimit -n 256000" >> /root/.profile
|
||||
fi
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-swap.conf << EOF
|
||||
# ProxMenux configuration
|
||||
vm.swappiness = 10
|
||||
vm.vfs_cache_pressure = 100
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-fs.conf << EOF
|
||||
# ProxMenux configuration
|
||||
fs.nr_open = 12000000
|
||||
fs.file-max = 9223372036854775807
|
||||
fs.aio-max-nr = 1048576
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "System limits increase completed.")"
|
||||
register_tool "system_limits" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
configure_entropy() {
|
||||
msg_info "$(translate "Configuring entropy generation to prevent slowdowns...")"
|
||||
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install haveged > /dev/null 2>&1
|
||||
|
||||
cat <<EOF > /etc/default/haveged
|
||||
# -w sets low entropy watermark (in bits)
|
||||
DAEMON_ARGS="-w 1024"
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload > /dev/null 2>&1
|
||||
systemctl enable haveged > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Entropy generation configuration completed")"
|
||||
register_tool "entropy" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_memory_settings() {
|
||||
msg_info "$(translate "Optimizing memory settings...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-memory.conf
|
||||
# Balanced Memory Optimization
|
||||
vm.swappiness = 10
|
||||
vm.dirty_ratio = 15
|
||||
vm.dirty_background_ratio = 5
|
||||
vm.overcommit_memory = 1
|
||||
vm.max_map_count = 65530
|
||||
EOF
|
||||
|
||||
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
|
||||
echo "vm.compaction_proactiveness = 20" >> /etc/sysctl.d/99-memory.conf
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Memory optimization completed.")"
|
||||
register_tool "memory_settings" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
configure_kernel_panic() {
|
||||
msg_info "$(translate "Configuring kernel panic behavior")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-kernelpanic.conf
|
||||
# Enable restart on kernel panic, kernel oops and hardlockup
|
||||
kernel.core_pattern = /var/crash/core.%t.%p
|
||||
kernel.panic = 10
|
||||
kernel.panic_on_oops = 1
|
||||
kernel.hardlockup_panic = 1
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "Kernel panic behavior configuration completed")"
|
||||
register_tool "kernel_panic" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
force_apt_ipv4() {
|
||||
msg_info "$(translate "Configuring APT to use IPv4...")"
|
||||
|
||||
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99-force-ipv4
|
||||
|
||||
msg_ok "$(translate "APT IPv4 configuration completed")"
|
||||
register_tool "apt_ipv4" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
apply_network_optimizations() {
|
||||
msg_info "$(translate "Optimizing network settings...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-network.conf
|
||||
net.core.netdev_max_backlog=8192
|
||||
net.core.optmem_max=8192
|
||||
net.core.rmem_max=16777216
|
||||
net.core.somaxconn=8151
|
||||
net.core.wmem_max=16777216
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.all.log_martians = 0
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.all.secure_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
net.ipv4.conf.default.log_martians = 0
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.default.secure_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
net.ipv4.ip_local_port_range=1024 65535
|
||||
net.ipv4.tcp_base_mss = 1024
|
||||
net.ipv4.tcp_challenge_ack_limit = 999999999
|
||||
net.ipv4.tcp_fin_timeout=10
|
||||
net.ipv4.tcp_keepalive_intvl=30
|
||||
net.ipv4.tcp_keepalive_probes=3
|
||||
net.ipv4.tcp_keepalive_time=240
|
||||
net.ipv4.tcp_limit_output_bytes=65536
|
||||
net.ipv4.tcp_max_syn_backlog=8192
|
||||
net.ipv4.tcp_max_tw_buckets = 1440000
|
||||
net.ipv4.tcp_mtu_probing = 1
|
||||
net.ipv4.tcp_rfc1337=1
|
||||
net.ipv4.tcp_rmem=8192 87380 16777216
|
||||
net.ipv4.tcp_sack=1
|
||||
net.ipv4.tcp_slow_start_after_idle=0
|
||||
net.ipv4.tcp_syn_retries=3
|
||||
net.ipv4.tcp_synack_retries = 2
|
||||
net.ipv4.tcp_tw_recycle = 0
|
||||
net.ipv4.tcp_tw_reuse = 0
|
||||
net.ipv4.tcp_wmem=8192 65536 16777216
|
||||
net.netfilter.nf_conntrack_generic_timeout = 60
|
||||
net.netfilter.nf_conntrack_helper=0
|
||||
net.netfilter.nf_conntrack_max = 524288
|
||||
net.netfilter.nf_conntrack_tcp_timeout_established = 28800
|
||||
net.unix.max_dgram_qlen = 4096
|
||||
EOF
|
||||
|
||||
sysctl --system > /dev/null 2>&1
|
||||
|
||||
local interfaces_file="/etc/network/interfaces"
|
||||
if ! grep -q 'source /etc/network/interfaces.d/*' "$interfaces_file"; then
|
||||
echo "source /etc/network/interfaces.d/*" >> "$interfaces_file"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Network optimization completed")"
|
||||
register_tool "network_optimization" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
disable_rpc() {
|
||||
msg_info "$(translate "Disabling portmapper/rpcbind for security...")"
|
||||
|
||||
systemctl disable rpcbind > /dev/null 2>&1
|
||||
systemctl stop rpcbind > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "portmapper/rpcbind has been disabled and removed")"
|
||||
register_tool "disable_rpc" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
customize_bashrc_() {
|
||||
msg_info "$(translate "Customizing bashrc for root user...")"
|
||||
local bashrc="/root/.bashrc"
|
||||
local bash_profile="/root/.bash_profile"
|
||||
|
||||
if [ ! -f "${bashrc}.bak" ]; then
|
||||
cp "$bashrc" "${bashrc}.bak"
|
||||
fi
|
||||
|
||||
|
||||
cat >> "$bashrc" << 'EOF'
|
||||
|
||||
# ProxMenux customizations
|
||||
export HISTTIMEFORMAT="%d/%m/%y %T "
|
||||
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]\W\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
|
||||
alias l='ls -CF'
|
||||
alias la='ls -A'
|
||||
alias ll='ls -alF'
|
||||
alias ls='ls --color=auto'
|
||||
alias grep='grep --color=auto'
|
||||
alias fgrep='fgrep --color=auto'
|
||||
alias egrep='egrep --color=auto'
|
||||
source /etc/profile.d/bash_completion.sh
|
||||
EOF
|
||||
|
||||
if ! grep -q "source /root/.bashrc" "$bash_profile"; then
|
||||
echo "source /root/.bashrc" >> "$bash_profile"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Bashrc customization completed")"
|
||||
register_tool "bashrc_custom" true
|
||||
}
|
||||
|
||||
|
||||
|
||||
customize_bashrc() {
|
||||
msg_info "$(translate "Customizing bashrc for root user...")"
|
||||
local bashrc="/root/.bashrc"
|
||||
local bash_profile="/root/.bash_profile"
|
||||
local marker_begin="# BEGIN PMX_CORE_BASHRC"
|
||||
local marker_end="# END PMX_CORE_BASHRC"
|
||||
|
||||
|
||||
[ -f "${bashrc}.bak" ] || cp "$bashrc" "${bashrc}.bak" > /dev/null 2>&1
|
||||
|
||||
|
||||
if grep -q "^${marker_begin}$" "$bashrc" 2>/dev/null; then
|
||||
sed -i "/^${marker_begin}$/,/^${marker_end}$/d" "$bashrc"
|
||||
fi
|
||||
|
||||
|
||||
cat >> "$bashrc" << 'EOF'
|
||||
${marker_begin}
|
||||
# ProxMenux core customizations
|
||||
export HISTTIMEFORMAT="%d/%m/%y %T "
|
||||
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]\W\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
|
||||
alias l='ls -CF'
|
||||
alias la='ls -A'
|
||||
alias ll='ls -alF'
|
||||
alias ls='ls --color=auto'
|
||||
alias grep='grep --color=auto'
|
||||
alias fgrep='fgrep --color=auto'
|
||||
alias egrep='egrep --color=auto'
|
||||
source /etc/profile.d/bash_completion.sh
|
||||
${marker_end}
|
||||
EOF
|
||||
|
||||
|
||||
if ! grep -q "source /root/.bashrc" "$bash_profile" 2>/dev/null; then
|
||||
echo "source /root/.bashrc" >> "$bash_profile" 2>/dev/null
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Bashrc customization completed")"
|
||||
register_tool "bashrc_custom" true
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
install_log2ram_auto() {
|
||||
|
||||
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
|
||||
|
||||
local is_ssd=false
|
||||
local pool disks disk byid_path dev rot
|
||||
|
||||
if grep -qE '^root=ZFS=' /etc/kernel/cmdline 2>/dev/null || mount | grep -q 'on / type zfs'; then
|
||||
|
||||
pool=$(zfs list -Ho name,mountpoint 2>/dev/null | awk '$2=="/"{print $1}' | cut -d/ -f1)
|
||||
disks=$(zpool status "$pool" 2>/dev/null | awk '/ONLINE/ && $1 !~ /:|mirror|raidz|log|spare|config|NAME|rpool|state/ {print $1}' | sort -u)
|
||||
|
||||
is_ssd=true
|
||||
for disk in $disks; do
|
||||
byid_path=$(readlink -f /dev/disk/by-id/*$disk* 2>/dev/null) || continue
|
||||
dev=$(basename "$byid_path" | sed -E 's|[0-9]+$||' | sed -E 's|p$||')
|
||||
rot=$(cat /sys/block/$dev/queue/rotational 2>/dev/null)
|
||||
[[ "$rot" != "0" ]] && is_ssd=false && break
|
||||
done
|
||||
else
|
||||
|
||||
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
|
||||
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null)
|
||||
SYSTEM_DISK=${SYSTEM_DISK:-sda}
|
||||
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
|
||||
is_ssd=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$is_ssd" == true ]]; then
|
||||
msg_ok "$(translate "System disk is SSD or M.2. Proceeding with Log2RAM setup.")"
|
||||
else
|
||||
msg_warn "$(translate "System disk is not SSD/M.2. Skipping Log2RAM installation.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1 && systemctl list-units --all | grep -q log2ram; then
|
||||
msg_ok "$(translate "Log2RAM is already installed and configured correctly.")"
|
||||
register_tool "log2ram" true
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Log2RAM proceeding with installation...")"
|
||||
|
||||
|
||||
if [[ -d /tmp/log2ram ]]; then
|
||||
rm -rf /tmp/log2ram
|
||||
fi
|
||||
|
||||
|
||||
[[ -f /etc/systemd/system/log2ram.service ]] && rm -f /etc/systemd/system/log2ram*
|
||||
[[ -f /etc/systemd/system/log2ram-daily.service ]] && rm -f /etc/systemd/system/log2ram-daily.*
|
||||
[[ -f /etc/cron.d/log2ram ]] && rm -f /etc/cron.d/log2ram*
|
||||
[[ -f /usr/sbin/log2ram ]] && rm -f /usr/sbin/log2ram
|
||||
[[ -f /etc/log2ram.conf ]] && rm -f /etc/log2ram.conf
|
||||
[[ -f /usr/local/bin/log2ram-check.sh ]] && rm -f /usr/local/bin/log2ram-check.sh
|
||||
[[ -d /var/log.hdd ]] && rm -rf /var/log.hdd
|
||||
|
||||
systemctl daemon-reexec >/dev/null 2>&1 || true
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
|
||||
|
||||
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
apt-get update -qq >/dev/null 2>&1
|
||||
apt-get install -y git >/dev/null 2>&1
|
||||
msg_ok "$(translate "Git installed successfully")"
|
||||
fi
|
||||
|
||||
if ! git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log; then
|
||||
msg_error "$(translate "Failed to clone log2ram repository. Check /tmp/log2ram_install.log")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd /tmp/log2ram || {
|
||||
msg_error "$(translate "Failed to access log2ram directory")"
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! bash install.sh >>/tmp/log2ram_install.log 2>&1; then
|
||||
msg_error "$(translate "Failed to run log2ram installer. Check /tmp/log2ram_install.log")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ -f /etc/log2ram.conf ]] && command -v log2ram >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "Log2RAM installed successfully")"
|
||||
else
|
||||
msg_error "$(translate "Log2RAM installation verification failed. Check /tmp/log2ram_install.log")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
RAM_SIZE_GB=$(free -g | awk '/^Mem:/{print $2}')
|
||||
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
|
||||
|
||||
if (( RAM_SIZE_GB <= 8 )); then
|
||||
LOG2RAM_SIZE="128M"
|
||||
CRON_HOURS=1
|
||||
elif (( RAM_SIZE_GB <= 16 )); then
|
||||
LOG2RAM_SIZE="256M"
|
||||
CRON_HOURS=3
|
||||
else
|
||||
LOG2RAM_SIZE="512M"
|
||||
CRON_HOURS=6
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "Log2RAM size set to:") $LOG2RAM_SIZE"
|
||||
|
||||
|
||||
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
|
||||
|
||||
|
||||
rm -f /etc/cron.hourly/log2ram
|
||||
echo "0 */$CRON_HOURS * * * root /usr/sbin/log2ram write" > /etc/cron.d/log2ram
|
||||
msg_ok "$(translate "log2ram write scheduled every") $CRON_HOURS $(translate "hour(s)")"
|
||||
|
||||
|
||||
cat << 'EOF' > /usr/local/bin/log2ram-check.sh
|
||||
#!/bin/bash
|
||||
CONF_FILE="/etc/log2ram.conf"
|
||||
LIMIT_KB=$(grep '^SIZE=' "$CONF_FILE" | cut -d'=' -f2 | tr -d 'M')000
|
||||
USED_KB=$(df /var/log --output=used | tail -1)
|
||||
THRESHOLD=$(( LIMIT_KB * 90 / 100 ))
|
||||
|
||||
if (( USED_KB > THRESHOLD )); then
|
||||
$(command -v log2ram) write
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x /usr/local/bin/log2ram-check.sh
|
||||
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh" > /etc/cron.d/log2ram-auto-sync
|
||||
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
|
||||
|
||||
register_tool "log2ram" true
|
||||
}
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
setup_persistent_network() {
|
||||
local LINK_DIR="/etc/systemd/network"
|
||||
local BACKUP_DIR="/etc/systemd/network/backup-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Setting up persistent network interfaces")"
|
||||
sleep 2
|
||||
|
||||
mkdir -p "$LINK_DIR"
|
||||
|
||||
if ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
cp "$LINK_DIR"/*.link "$BACKUP_DIR"/ 2>/dev/null || true
|
||||
fi
|
||||
|
||||
local count=0
|
||||
for iface in $(ls /sys/class/net/ | grep -vE "lo|docker|veth|br-|vmbr|tap|fwpr|fwln|virbr|bond|cilium|zt|wg"); do
|
||||
if [[ -e "/sys/class/net/$iface/device" ]] || [[ -e "/sys/class/net/$iface/phy80211" ]]; then
|
||||
local MAC=$(cat /sys/class/net/$iface/address 2>/dev/null)
|
||||
|
||||
if [[ "$MAC" =~ ^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$ ]]; then
|
||||
local LINK_FILE="$LINK_DIR/10-$iface.link"
|
||||
|
||||
cat > "$LINK_FILE" <<EOF
|
||||
[Match]
|
||||
MACAddress=$MAC
|
||||
|
||||
[Link]
|
||||
Name=$iface
|
||||
EOF
|
||||
chmod 644 "$LINK_FILE"
|
||||
((count++))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $count -gt 0 ]]; then
|
||||
msg_ok "$(translate "Created persistent names for") $count $(translate "interfaces")"
|
||||
msg_ok "$(translate "Changes will apply after reboot.")"
|
||||
else
|
||||
msg_warn "$(translate "No physical interfaces found")"
|
||||
fi
|
||||
register_tool "persistent_network" true
|
||||
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
run_complete_optimization() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "ProxMenux Optimization Post-Installation")"
|
||||
|
||||
ensure_tools_json
|
||||
|
||||
apt_upgrade
|
||||
remove_subscription_banner
|
||||
configure_time_sync
|
||||
skip_apt_languages
|
||||
optimize_journald
|
||||
optimize_logrotate
|
||||
increase_system_limits
|
||||
configure_entropy
|
||||
optimize_memory_settings
|
||||
configure_kernel_panic
|
||||
force_apt_ipv4
|
||||
apply_network_optimizations
|
||||
disable_rpc
|
||||
customize_bashrc
|
||||
install_log2ram_auto
|
||||
setup_persistent_network
|
||||
|
||||
|
||||
echo -e
|
||||
msg_success "$(translate "Complete post-installation optimization finished!")"
|
||||
|
||||
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
|
||||
whiptail --title "Reboot Required" \
|
||||
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
|
||||
if [[ $? -eq 0 ]]; then
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
msg_warn "$(translate "Rebooting the system...")"
|
||||
reboot
|
||||
else
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
msg_info2 "$(translate "You can reboot later manually.")"
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
check_extremeshok_warning
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
run_complete_optimization
|
||||
fi
|
||||
+1220
-557
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,654 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenux - Complete Uninstall Optimizations Script
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 06/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script provides a complete uninstallation and rollback system
|
||||
# for all post-installation optimizations applied by ProxMenux.
|
||||
#
|
||||
# It allows administrators to safely revert any changes made during the
|
||||
# optimization process, restoring the system to its original state.
|
||||
#
|
||||
# This ensures full control over system configurations and gives users
|
||||
# the confidence to apply, test, and undo ProxMenux enhancements as needed.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
RETURN_SCRIPT="$REPO_URL/scripts/menus/menu_post_install.sh"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
TOOLS_JSON="$BASE_DIR/installed_tools.json"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Tool registration system
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_fastfetch() {
|
||||
if ! command -v fastfetch &>/dev/null && [[ ! -f /usr/local/bin/fastfetch ]]; then
|
||||
msg_warn "$(translate "Fastfetch is not installed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Uninstalling Fastfetch...")"
|
||||
rm -f /usr/local/bin/fastfetch /usr/bin/fastfetch
|
||||
rm -rf "$HOME/.config/fastfetch"
|
||||
rm -rf /usr/local/share/fastfetch
|
||||
sed -i '/fastfetch/d' "$HOME/.bashrc" "$HOME/.profile" /etc/profile 2>/dev/null
|
||||
sed -i '/# BEGIN FASTFETCH/,/# END FASTFETCH/d' "$HOME/.bashrc"
|
||||
rm -f /etc/profile.d/fastfetch.sh /etc/update-motd.d/99-fastfetch
|
||||
dpkg -r fastfetch &>/dev/null
|
||||
|
||||
msg_ok "$(translate "Fastfetch removed from system")"
|
||||
register_tool "fastfetch" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_figurine_() {
|
||||
if ! command -v figurine &>/dev/null; then
|
||||
msg_warn "$(translate "Figurine is not installed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Uninstalling Figurine...")"
|
||||
rm -f /usr/local/bin/figurine
|
||||
rm -f /etc/profile.d/figurine.sh
|
||||
sed -i '/figurine/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
|
||||
|
||||
msg_ok "$(translate "Figurine removed from system")"
|
||||
register_tool "figurine" false
|
||||
}
|
||||
|
||||
|
||||
uninstall_figurine() {
|
||||
if ! command -v figurine &>/dev/null; then
|
||||
msg_warn "$(translate "Figurine is not installed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Uninstalling Figurine...")"
|
||||
rm -f /usr/local/bin/figurine
|
||||
rm -f /etc/profile.d/figurine.sh
|
||||
|
||||
sed -i '/lxcclean/d;/lxcupdate/d;/kernelclean/d;/cpugov/d;/updatecerts/d;/seqwrite/d;/seqread/d;/ranwrite/d;/ranread/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
|
||||
sed -i '/# ProxMenux Figurine aliases and tools/,+20d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
|
||||
sed -i '/# BEGIN PROXMENUX ALIASES/,/# END PROXMENUX ALIASES/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
|
||||
|
||||
msg_ok "$(translate "Figurine removed from system")"
|
||||
register_tool "figurine" false
|
||||
}
|
||||
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_kexec() {
|
||||
if ! dpkg -s kexec-tools >/dev/null 2>&1 && [ ! -f /etc/systemd/system/kexec-pve.service ]; then
|
||||
msg_warn "$(translate "kexec-tools is not installed or already removed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Uninstalling kexec-tools and removing custom service...")"
|
||||
systemctl disable --now kexec-pve.service &>/dev/null
|
||||
rm -f /etc/systemd/system/kexec-pve.service
|
||||
sed -i "/alias reboot-quick='systemctl kexec'/d" /root/.bash_profile
|
||||
apt-get purge -y kexec-tools >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "kexec-tools and related settings removed")"
|
||||
register_tool "kexec" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_apt_upgrade() {
|
||||
msg_info "$(translate "Restoring enterprise repositories...")"
|
||||
|
||||
# Re-enable enterprise repos
|
||||
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ]; then
|
||||
sed -i "s/^#deb/deb/g" /etc/apt/sources.list.d/pve-enterprise.list
|
||||
fi
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/ceph.list ]; then
|
||||
sed -i "s/^#deb/deb/g" /etc/apt/sources.list.d/ceph.list
|
||||
fi
|
||||
|
||||
# Remove public repo
|
||||
rm -f /etc/apt/sources.list.d/pve-public-repo.list
|
||||
|
||||
# Remove firmware warning config
|
||||
rm -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf
|
||||
|
||||
apt-get update > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Enterprise repositories restored")"
|
||||
register_tool "apt_upgrade" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_subscription_banner() {
|
||||
msg_info "$(translate "Restoring subscription banner...")"
|
||||
|
||||
# Remove APT hook
|
||||
rm -f /etc/apt/apt.conf.d/no-nag-script
|
||||
|
||||
# Reinstall proxmox-widget-toolkit to restore original
|
||||
apt --reinstall install proxmox-widget-toolkit -y >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Subscription banner restored")"
|
||||
register_tool "subscription_banner" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_time_sync() {
|
||||
msg_info "$(translate "Resetting time synchronization...")"
|
||||
|
||||
# Reset to UTC (safe default)
|
||||
timedatectl set-timezone UTC >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Time synchronization reset to UTC")"
|
||||
register_tool "time_sync" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_apt_languages() {
|
||||
msg_info "$(translate "Restoring APT language downloads...")"
|
||||
|
||||
# Remove the configuration that disables translations
|
||||
rm -f /etc/apt/apt.conf.d/99-disable-translations
|
||||
|
||||
msg_ok "$(translate "APT language downloads restored")"
|
||||
register_tool "apt_languages" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_journald() {
|
||||
msg_info "$(translate "Restoring default journald configuration...")"
|
||||
|
||||
# Restore default journald configuration
|
||||
cat > /etc/systemd/journald.conf << 'EOF'
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Entries in this file show the compile time defaults.
|
||||
# You can change settings by editing this file.
|
||||
# Defaults can be restored by simply deleting this file.
|
||||
#
|
||||
# See journald.conf(5) for details.
|
||||
|
||||
[Journal]
|
||||
#Storage=auto
|
||||
#Compress=yes
|
||||
#Seal=yes
|
||||
#SplitMode=uid
|
||||
#SyncIntervalSec=5m
|
||||
#RateLimitInterval=30s
|
||||
#RateLimitBurst=1000
|
||||
#SystemMaxUse=
|
||||
#SystemKeepFree=
|
||||
#SystemMaxFileSize=
|
||||
#RuntimeMaxUse=
|
||||
#RuntimeKeepFree=
|
||||
#RuntimeMaxFileSize=
|
||||
#MaxRetentionSec=
|
||||
#MaxFileSec=1month
|
||||
#ForwardToSyslog=yes
|
||||
#ForwardToKMsg=no
|
||||
#ForwardToConsole=no
|
||||
#ForwardToWall=yes
|
||||
#TTYPath=/dev/console
|
||||
#MaxLevelStore=debug
|
||||
#MaxLevelSyslog=debug
|
||||
#MaxLevelKMsg=notice
|
||||
#MaxLevelConsole=info
|
||||
#MaxLevelWall=emerg
|
||||
EOF
|
||||
|
||||
systemctl restart systemd-journald.service >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Default journald configuration restored")"
|
||||
register_tool "journald" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_logrotate() {
|
||||
msg_info "$(translate "Restoring original logrotate configuration...")"
|
||||
|
||||
# Restore from backup if it exists
|
||||
if [ -f /etc/logrotate.conf.bak ]; then
|
||||
mv /etc/logrotate.conf.bak /etc/logrotate.conf
|
||||
systemctl restart logrotate >/dev/null 2>&1
|
||||
msg_ok "$(translate "Original logrotate configuration restored")"
|
||||
else
|
||||
msg_warn "$(translate "No backup found, logrotate configuration not changed")"
|
||||
fi
|
||||
|
||||
register_tool "logrotate" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_system_limits() {
|
||||
msg_info "$(translate "Removing system limits optimizations...")"
|
||||
|
||||
# Remove ProxMenux sysctl configurations
|
||||
rm -f /etc/sysctl.d/99-maxwatches.conf
|
||||
rm -f /etc/sysctl.d/99-maxkeys.conf
|
||||
rm -f /etc/sysctl.d/99-swap.conf
|
||||
rm -f /etc/sysctl.d/99-fs.conf
|
||||
|
||||
# Remove ProxMenux limits configuration
|
||||
rm -f /etc/security/limits.d/99-limits.conf
|
||||
|
||||
# Remove systemd limits (restore defaults)
|
||||
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
|
||||
if [ -f "$file" ]; then
|
||||
sed -i '/^DefaultLimitNOFILE=256000/d' "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Remove PAM limits
|
||||
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
|
||||
if [ -f "$file" ]; then
|
||||
sed -i '/^session required pam_limits.so/d' "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Remove ulimit from profile
|
||||
if [ -f /root/.profile ]; then
|
||||
sed -i '/ulimit -n 256000/d' /root/.profile
|
||||
fi
|
||||
|
||||
# Reload sysctl
|
||||
sysctl --system >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "System limits optimizations removed")"
|
||||
register_tool "system_limits" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_entropy() {
|
||||
msg_info "$(translate "Removing entropy generation optimization...")"
|
||||
|
||||
# Stop and disable haveged
|
||||
systemctl stop haveged >/dev/null 2>&1
|
||||
systemctl disable haveged >/dev/null 2>&1
|
||||
|
||||
# Remove haveged package
|
||||
apt-get purge -y haveged >/dev/null 2>&1
|
||||
|
||||
# Remove configuration
|
||||
rm -f /etc/default/haveged
|
||||
|
||||
msg_ok "$(translate "Entropy generation optimization removed")"
|
||||
register_tool "entropy" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_memory_settings() {
|
||||
msg_info "$(translate "Removing memory optimizations...")"
|
||||
|
||||
# Remove ProxMenux memory configuration
|
||||
rm -f /etc/sysctl.d/99-memory.conf
|
||||
|
||||
# Reload sysctl
|
||||
sysctl --system >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Memory optimizations removed")"
|
||||
register_tool "memory_settings" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_kernel_panic() {
|
||||
msg_info "$(translate "Removing kernel panic configuration...")"
|
||||
|
||||
# Remove ProxMenux kernel panic configuration
|
||||
rm -f /etc/sysctl.d/99-kernelpanic.conf
|
||||
|
||||
# Reload sysctl
|
||||
sysctl --system >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Kernel panic configuration removed")"
|
||||
register_tool "kernel_panic" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_apt_ipv4() {
|
||||
msg_info "$(translate "Removing APT IPv4 configuration...")"
|
||||
|
||||
# Remove IPv4 force configuration
|
||||
rm -f /etc/apt/apt.conf.d/99-force-ipv4
|
||||
|
||||
msg_ok "$(translate "APT IPv4 configuration removed")"
|
||||
register_tool "apt_ipv4" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_network_optimization() {
|
||||
msg_info "$(translate "Removing network optimizations...")"
|
||||
|
||||
# Remove ProxMenux network configuration
|
||||
rm -f /etc/sysctl.d/99-network.conf
|
||||
|
||||
# Remove interfaces.d source line if we added it
|
||||
local interfaces_file="/etc/network/interfaces"
|
||||
if [ -f "$interfaces_file" ]; then
|
||||
# Only remove if it's the last line and looks like our addition
|
||||
if tail -1 "$interfaces_file" | grep -q "^source /etc/network/interfaces.d/\*$"; then
|
||||
sed -i '$d' "$interfaces_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Reload sysctl
|
||||
sysctl --system >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Network optimizations removed")"
|
||||
register_tool "network_optimization" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_disable_rpc() {
|
||||
msg_info "$(translate "Re-enabling RPC services...")"
|
||||
|
||||
# Re-enable and start rpcbind
|
||||
systemctl enable rpcbind >/dev/null 2>&1
|
||||
systemctl start rpcbind >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "RPC services re-enabled")"
|
||||
register_tool "disable_rpc" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_bashrc_custom() {
|
||||
msg_info "$(translate "Restoring original bashrc...")"
|
||||
|
||||
# Restore original bashrc from backup
|
||||
if [ -f /root/.bashrc.bak ]; then
|
||||
mv /root/.bashrc.bak /root/.bashrc
|
||||
msg_ok "$(translate "Original bashrc restored")"
|
||||
else
|
||||
# Remove ProxMenux customizations manually
|
||||
if [ -f /root/.bashrc ]; then
|
||||
# Remove our customization block
|
||||
sed -i '/# ProxMenux customizations/,/source \/etc\/profile\.d\/bash_completion\.sh/d' /root/.bashrc
|
||||
fi
|
||||
msg_ok "$(translate "ProxMenux customizations removed from bashrc")"
|
||||
fi
|
||||
|
||||
# Remove bash_profile source line if we added it
|
||||
if [ -f /root/.bash_profile ]; then
|
||||
sed -i '/source \/root\/\.bashrc/d' /root/.bash_profile
|
||||
fi
|
||||
|
||||
register_tool "bashrc_custom" false
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_log2ram() {
|
||||
if [[ ! -f /etc/log2ram.conf ]] && ! systemctl list-units --all | grep -q log2ram; then
|
||||
msg_warn "$(translate "log2ram is not installed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Uninstalling log2ram...")"
|
||||
|
||||
# Stop and disable services and timers
|
||||
systemctl stop log2ram >/dev/null 2>&1
|
||||
systemctl disable log2ram >/dev/null 2>&1
|
||||
systemctl stop log2ram-daily.timer >/dev/null 2>&1
|
||||
systemctl disable log2ram-daily.timer >/dev/null 2>&1
|
||||
|
||||
# Remove cron jobs
|
||||
rm -f /etc/cron.d/log2ram
|
||||
rm -f /etc/cron.d/log2ram-auto-sync
|
||||
|
||||
# Remove config and binaries
|
||||
rm -f /usr/local/bin/log2ram-check.sh
|
||||
rm -f /usr/sbin/log2ram
|
||||
rm -f /etc/log2ram.conf*
|
||||
rm -f /etc/systemd/system/log2ram.service
|
||||
rm -f /etc/systemd/system/log2ram-daily.timer
|
||||
rm -f /etc/systemd/system/log2ram-daily.service
|
||||
|
||||
# Clean up log2ram mount if active
|
||||
if [ -d /var/log.hdd ]; then
|
||||
if [ -d /var/log ] && mountpoint -q /var/log; then
|
||||
rsync -a /var/log/ /var/log.hdd/ >/dev/null 2>&1
|
||||
umount /var/log >/dev/null 2>&1
|
||||
fi
|
||||
rm -rf /var/log.hdd
|
||||
fi
|
||||
|
||||
systemctl daemon-reexec >/dev/null 2>&1
|
||||
systemctl daemon-reload >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "log2ram completely removed")"
|
||||
register_tool "log2ram" false
|
||||
}
|
||||
|
||||
|
||||
################################################################
|
||||
|
||||
uninstall_persistent_network() {
|
||||
local LINK_DIR="/etc/systemd/network"
|
||||
|
||||
msg_info "$(translate "Removing all .link files from") $LINK_DIR"
|
||||
sleep 2
|
||||
|
||||
if ! ls "$LINK_DIR"/*.link >/dev/null 2>&1; then
|
||||
msg_warn "$(translate "No .link files found in") $LINK_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$LINK_DIR"/*.link
|
||||
|
||||
msg_ok "$(translate "Removed all .link files from") $LINK_DIR"
|
||||
msg_info "$(translate "Interface names will return to default systemd behavior.")"
|
||||
register_tool "persistent_network" false
|
||||
}
|
||||
|
||||
|
||||
|
||||
################################################################
|
||||
|
||||
migrate_installed_tools() {
|
||||
if [[ -f "$TOOLS_JSON" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate 'Detecting previous optimizations...')"
|
||||
|
||||
echo "{}" > "$TOOLS_JSON"
|
||||
local updated=false
|
||||
|
||||
|
||||
|
||||
# APT configurations
|
||||
if [[ -f /etc/apt/apt.conf.d/99-force-ipv4 ]]; then
|
||||
jq '. + {"apt_ipv4": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
if [[ -f /etc/apt/apt.conf.d/99-disable-translations ]]; then
|
||||
jq '. + {"apt_languages": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
# System configurations
|
||||
if [[ -f /etc/sysctl.d/99-memory.conf ]]; then
|
||||
jq '. + {"memory_settings": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
if [[ -f /etc/sysctl.d/99-network.conf ]]; then
|
||||
jq '. + {"network_optimization": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
if [[ -f /etc/sysctl.d/99-kernelpanic.conf ]]; then
|
||||
jq '. + {"kernel_panic": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
if [[ -f /etc/security/limits.d/99-limits.conf ]]; then
|
||||
jq '. + {"system_limits": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
# Services
|
||||
if systemctl is-active --quiet log2ram 2>/dev/null; then
|
||||
jq '. + {"log2ram": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
if dpkg -l | grep -q haveged; then
|
||||
jq '. + {"entropy": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
# Bashrc customization
|
||||
if grep -q "# ProxMenux customizations" /root/.bashrc 2>/dev/null; then
|
||||
jq '. + {"bashrc_custom": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
# Subscription banner
|
||||
if [[ -f /etc/apt/apt.conf.d/no-nag-script ]]; then
|
||||
jq '. + {"subscription_banner": true}' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
updated=true
|
||||
fi
|
||||
|
||||
if [[ "$updated" == true ]]; then
|
||||
sleep 2
|
||||
msg_ok "$(translate 'Optimizations detected and ready to revert.')"
|
||||
sleep 1
|
||||
fi
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
show_uninstall_menu() {
|
||||
ensure_tools_json
|
||||
migrate_installed_tools
|
||||
|
||||
mapfile -t tools_installed < <(jq -r 'to_entries | map(select(.value==true)) | .[].key' "$TOOLS_JSON")
|
||||
|
||||
if [[ ${#tools_installed[@]} -eq 0 ]]; then
|
||||
dialog --backtitle "ProxMenux" --title "ProxMenux" \
|
||||
--msgbox "\n\n$(translate "No optimizations detected to uninstall.")" 10 60
|
||||
return 0
|
||||
fi
|
||||
|
||||
local menu_options=()
|
||||
for tool in "${tools_installed[@]}"; do
|
||||
case "$tool" in
|
||||
lvm_repair) desc="LVM PV Headers Repair";;
|
||||
repo_cleanup) desc="Repository Cleanup";;
|
||||
apt_upgrade) desc="APT Upgrade & Repository Config";;
|
||||
subscription_banner) desc="Subscription Banner Removal";;
|
||||
time_sync) desc="Time Synchronization";;
|
||||
apt_languages) desc="APT Language Skip";;
|
||||
journald) desc="Journald Optimization";;
|
||||
logrotate) desc="Logrotate Optimization";;
|
||||
system_limits) desc="System Limits Increase";;
|
||||
entropy) desc="Entropy Generation (haveged)";;
|
||||
memory_settings) desc="Memory Settings Optimization";;
|
||||
kernel_panic) desc="Kernel Panic Configuration";;
|
||||
apt_ipv4) desc="APT IPv4 Force";;
|
||||
kexec) desc="kexec for quick reboots";;
|
||||
network_optimization) desc="Network Optimizations";;
|
||||
disable_rpc) desc="RPC/rpcbind Disable";;
|
||||
bashrc_custom) desc="Bashrc Customization";;
|
||||
figurine) desc="Figurine";;
|
||||
fastfetch) desc="Fastfetch";;
|
||||
log2ram) desc="Log2ram (SSD Protection)";;
|
||||
persistent_network) desc="Setting persistent network interfaces";;
|
||||
*) desc="$tool";;
|
||||
esac
|
||||
menu_options+=("$tool" "$desc" "off")
|
||||
done
|
||||
|
||||
selected_tools=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Uninstall Optimizations")" \
|
||||
--checklist "$(translate "Select optimizations to uninstall:")" 20 70 12 \
|
||||
"${menu_options[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
local dialog_result=$?
|
||||
if [[ $dialog_result -ne 0 || -z "$selected_tools" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Show confirmation
|
||||
if ! dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Confirm Uninstallation")" \
|
||||
--yesno "\n\n$(translate "Are you sure you want to uninstall the selected optimizations.")" 10 60; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Execute uninstallations
|
||||
for tool in $selected_tools; do
|
||||
tool=$(echo "$tool" | tr -d '"')
|
||||
if declare -f "uninstall_$tool" > /dev/null 2>&1; then
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
"uninstall_$tool"
|
||||
else
|
||||
msg_warn "$(translate "No uninstaller found for:") $tool"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_success "$(translate "Selected optimizations have been uninstalled.")"
|
||||
msg_warn "$(translate "A system reboot is recommended to ensure all changes take effect.")"
|
||||
|
||||
if dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Reboot Recommended")" \
|
||||
--yesno "$(translate "Do you want to reboot now?")" 8 50; then
|
||||
reboot
|
||||
fi
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
show_uninstall_menu
|
||||
@@ -1,13 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.2
|
||||
# Last Updated: 30/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script allows users to assign physical disks to existing
|
||||
@@ -17,9 +16,9 @@
|
||||
# - Identifies and displays unassigned physical disks.
|
||||
# - Allows the user to select multiple disks and attach them to a CT.
|
||||
# - Configures the selected disks for the CT and verifies the assignment.
|
||||
# - Uses persistent device paths to avoid issues with device order changes.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
@@ -29,11 +28,69 @@ VENV_PATH="/opt/googletrans-env"
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Get OS codename for repository configuration
|
||||
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs )"
|
||||
|
||||
# ==========================================================
|
||||
|
||||
# Function to get persistent device path
|
||||
get_persistent_path() {
|
||||
local device="$1"
|
||||
local persistent_path=""
|
||||
|
||||
# Try by-id first (most reliable)
|
||||
for path in /dev/disk/by-id/*; do
|
||||
if [[ -e "$path" && "$(readlink -f "$path")" == "$device" ]]; then
|
||||
# Prefer ata- or scsi- over wwn- for readability
|
||||
if [[ "$path" =~ ata-|scsi- ]]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
elif [[ -z "$persistent_path" ]]; then
|
||||
persistent_path="$path"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Return the first found by-id path if any
|
||||
if [[ -n "$persistent_path" ]]; then
|
||||
echo "$persistent_path"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try by-path as fallback
|
||||
for path in /dev/disk/by-path/*; do
|
||||
if [[ -e "$path" && "$(readlink -f "$path")" == "$device" ]]; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Fallback to original device if no persistent path found
|
||||
msg_warn "$(translate "No persistent path found for") $device, $(translate "using direct path")"
|
||||
echo "$device"
|
||||
}
|
||||
|
||||
# Function to ensure repositories are properly configured
|
||||
ensure_repositories() {
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
local need_update=false
|
||||
|
||||
# Only verify the main repository with contrib and non-free
|
||||
if ! grep -q "deb.*${OS_CODENAME}.*main" "$sources_file"; then
|
||||
echo "deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
if [ "$need_update" = true ]; then
|
||||
apt update >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
get_disk_info() {
|
||||
local disk=$1
|
||||
@@ -42,7 +99,8 @@ get_disk_info() {
|
||||
echo "$MODEL" "$SIZE"
|
||||
}
|
||||
|
||||
|
||||
# Ensure repositories are configured
|
||||
ensure_repositories
|
||||
|
||||
CT_LIST=$(pct list | awk 'NR>1 {print $1, $3}')
|
||||
if [ -z "$CT_LIST" ]; then
|
||||
@@ -50,7 +108,6 @@ if [ -z "$CT_LIST" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
CTID=$(whiptail --title "$(translate "Select CT for destination disk")" --menu "$(translate "Select the CT to which you want to add disks:")" 15 60 8 $CT_LIST 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$CTID" ]; then
|
||||
@@ -60,11 +117,13 @@ fi
|
||||
|
||||
CTID=$(echo "$CTID" | tr -d '"')
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
msg_title "$(translate "Add Disk") Passthrough $(translate "to a LXC")"
|
||||
echo -e
|
||||
msg_ok "$(translate "CT selected successfully.")"
|
||||
|
||||
|
||||
|
||||
|
||||
CT_STATUS=$(pct status "$CTID" | awk '{print $2}')
|
||||
if [ "$CT_STATUS" != "running" ]; then
|
||||
msg_info "$(translate "Starting CT") $CTID..."
|
||||
@@ -72,20 +131,17 @@ if [ "$CT_STATUS" != "running" ]; then
|
||||
sleep 2
|
||||
if [ "$(pct status "$CTID" | awk '{print $2}')" != "running" ]; then
|
||||
msg_error "$(translate "Failed to start the CT.")"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "$(translate "CT started successfully.")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
CONF_FILE="/etc/pve/lxc/$CTID.conf"
|
||||
|
||||
if grep -q '^unprivileged: 1' "$CONF_FILE"; then
|
||||
if whiptail --title "$(translate "Privileged Container")" \
|
||||
--yesno "$(translate "The selected container is unprivileged. A privileged container is required for direct device passthrough.")\\n\\n$(translate "Do you want to convert it to a privileged container now?")" 12 70; then
|
||||
|
||||
|
||||
msg_info "$(translate "Stopping container") $CTID..."
|
||||
pct shutdown "$CTID" &>/dev/null
|
||||
for i in {1..10}; do
|
||||
@@ -94,20 +150,18 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [ "$(pct status "$CTID" | awk '{print $2}')" == "running" ]; then
|
||||
msg_error "$(translate "Failed to stop the container.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Container stopped.")"
|
||||
|
||||
|
||||
cp "$CONF_FILE" "$CONF_FILE.bak"
|
||||
sed -i '/^unprivileged: 1/d' "$CONF_FILE"
|
||||
echo "unprivileged: 0" >> "$CONF_FILE"
|
||||
|
||||
msg_ok "$(translate "Container successfully converted to privileged.")"
|
||||
|
||||
|
||||
msg_info "$(translate "Starting container") $CTID..."
|
||||
pct start "$CTID" &>/dev/null
|
||||
sleep 2
|
||||
@@ -116,7 +170,6 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; then
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "$(translate "Container started successfully.")"
|
||||
|
||||
else
|
||||
whiptail --title "$(translate "Aborted")" \
|
||||
--msgbox "$(translate "Operation cancelled. Cannot continue with an unprivileged container.")" 10 60
|
||||
@@ -124,16 +177,7 @@ if grep -q '^unprivileged: 1' "$CONF_FILE"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##########################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Detecting available disks...")"
|
||||
|
||||
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
|
||||
@@ -141,7 +185,6 @@ MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
|
||||
ZFS_DISKS=""
|
||||
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
|
||||
for entry in $ZFS_RAW; do
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
@@ -151,7 +194,7 @@ for entry in $ZFS_RAW; do
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$path" ]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
if [ -n "$base_disk" ]; then
|
||||
@@ -159,50 +202,43 @@ for entry in $ZFS_RAW; do
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
|
||||
is_disk_in_use() {
|
||||
local disk="$1"
|
||||
|
||||
while read -r part fstype; do
|
||||
case "$fstype" in
|
||||
zfs_member|linux_raid_member)
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
if echo "$MOUNTED_DISKS" | grep -q "/dev/$part"; then
|
||||
return 0
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2)
|
||||
|
||||
|
||||
if echo "$USED_DISKS" | grep -q "$disk" || echo "$ZFS_DISKS" | grep -q "$disk"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') | xargs -n1 readlink -f | sort -u)
|
||||
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
|
||||
|
||||
while read -r DISK; do
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
|
||||
INFO=($(get_disk_info "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
@@ -211,17 +247,16 @@ while read -r DISK; do
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
if echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
|
||||
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
else
|
||||
@@ -234,33 +269,38 @@ while read -r DISK; do
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
|
||||
if grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if $IS_ZFS; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
|
||||
if $IS_MOUNTED; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
|
||||
# Check if disk is already assigned to this CT using persistent paths
|
||||
if pct config "$CTID" | grep -vE '^\s*#|^description:' | grep -q "$DISK"; then
|
||||
SHOW_DISK=false
|
||||
else
|
||||
# Also check persistent paths
|
||||
PERSISTENT_DISK=$(get_persistent_path "$DISK")
|
||||
if [[ "$PERSISTENT_DISK" != "$DISK" ]] && pct config "$CTID" | grep -vE '^\s*#|^description:' | grep -q "$PERSISTENT_DISK"; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
|
||||
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF")
|
||||
fi
|
||||
@@ -275,19 +315,9 @@ fi
|
||||
|
||||
msg_ok "$(translate "Available disks detected.")"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
######################################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
|
||||
if [ $TOTAL_WIDTH -lt 50 ]; then
|
||||
TOTAL_WIDTH=50
|
||||
fi
|
||||
@@ -312,12 +342,11 @@ msg_info "$(translate "Processing selected disks...")"
|
||||
for DISK in $SELECTED; do
|
||||
DISK=$(echo "$DISK" | tr -d '"')
|
||||
DISK_INFO=$(get_disk_info "$DISK")
|
||||
|
||||
|
||||
ASSIGNED_TO=""
|
||||
RUNNING_CTS=""
|
||||
RUNNING_VMS=""
|
||||
|
||||
|
||||
|
||||
while read -r CT_ID CT_NAME; do
|
||||
if [[ "$CT_ID" =~ ^[0-9]+$ ]] && pct config "$CT_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="CT $CT_ID $CT_NAME\n"
|
||||
@@ -327,8 +356,7 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
fi
|
||||
done < <(pct list | awk 'NR>1 {print $1, $3}')
|
||||
|
||||
|
||||
|
||||
while read -r VM_ID VM_NAME; do
|
||||
if [[ "$VM_ID" =~ ^[0-9]+$ ]] && qm config "$VM_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="VM $VM_ID $VM_NAME\n"
|
||||
@@ -338,12 +366,12 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
fi
|
||||
done < <(qm list | awk 'NR>1 {print $1, $2}')
|
||||
|
||||
|
||||
if [ -n "$RUNNING_CTS" ] || [ -n "$RUNNING_VMS" ]; then
|
||||
ERROR_MESSAGES+="$(translate "The disk") $DISK_INFO $(translate "is in use by the following running VM(s) or CT(s):")\\n$RUNNING_CTS$RUNNING_VMS\\n\\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
cleanup
|
||||
whiptail --title "$(translate "Disk Already Assigned")" --yesno "$(translate "The disk") $DISK_INFO $(translate "is already assigned to the following VM(s) or CT(s):")\\n$ASSIGNED_TO\\n\\n$(translate "Do you want to continue anyway?")" 15 70
|
||||
@@ -354,39 +382,28 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if lsblk "$DISK" | grep -q "raid" || grep -q "${DISK##*/}" /proc/mdstat; then
|
||||
whiptail --title "$(translate "RAID Detected")" --msgbox "$(translate "The disk") $DISK_INFO $(translate "appears to be part of a") RAID. $(translate "For security reasons, the system cannot format it.")\\n\\n$(translate "If you are sure you want to use it, please remove the") RAID metadata $(translate "or format it manually using external tools.")\\n\\n$(translate "After that, run this script again to add it.")" 18 70
|
||||
clear
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
MOUNT_POINT=$(whiptail --title "$(translate "Mount Point")" --inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/disk_passthrough):")" 10 60 "/mnt/disk_passthrough" 3>&1 1>&2 2>&3)
|
||||
|
||||
|
||||
if [ -z "$MOUNT_POINT" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No mount point was specified.")" 8 40
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
msg_ok "$(translate "Mount point specified: $MOUNT_POINT")"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$DISK" | awk -v disk="$(basename "$DISK")" '$1 != disk {print $1; exit}')
|
||||
SKIP_FORMAT=false
|
||||
|
||||
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION."
|
||||
@@ -397,27 +414,24 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
fi
|
||||
else
|
||||
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$DISK" | xargs)
|
||||
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
PARTITION="$DISK"
|
||||
msg_ok "$(translate "Detected filesystem") $CURRENT_FS $(translate "directly on disk") $DISK."
|
||||
else
|
||||
|
||||
whiptail --title "$(translate "No Valid Partitions")" --yesno "$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
echo -e "$(translate "Creating partition table and partition...")"
|
||||
parted -s "$DISK" mklabel gpt
|
||||
parted -s "$DISK" mkpart primary 0% 100%
|
||||
sleep 2
|
||||
partprobe "$DISK"
|
||||
sleep 2
|
||||
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$DISK" | awk -v disk="$(basename "$DISK")" '$1 != disk {print $1; exit}')
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
@@ -427,28 +441,23 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [ "$SKIP_FORMAT" != true ]; then
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION. $(translate "Skipping format.")"
|
||||
else
|
||||
|
||||
FORMAT_TYPE=$(whiptail --title "$(translate "Select Format Type")" --menu "$(translate "Select the filesystem type for") $DISK_INFO:" 15 60 6 \
|
||||
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
|
||||
"xfs" "$(translate "XFS Filesystem")" \
|
||||
"btrfs" "$(translate "Btrfs Filesystem")" 3>&1 1>&2 2>&3)
|
||||
|
||||
|
||||
if [ -z "$FORMAT_TYPE" ]; then
|
||||
whiptail --title "$(translate "Format Cancelled")" --msgbox "$(translate "Format operation cancelled. The disk will not be added.")" 8 60
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
whiptail --title "$(translate "WARNING")" --yesno "$(translate "WARNING: This operation will FORMAT the disk") $DISK_INFO $(translate "with") $FORMAT_TYPE.\\n\\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\\n\\n$(translate "Are you sure you want to continue")" 15 70
|
||||
if [ $? -ne 0 ]; then
|
||||
whiptail --title "$(translate "Format Cancelled")" --msgbox "$(translate "Format operation cancelled. The disk will not be added.")" 8 60
|
||||
@@ -456,20 +465,15 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [ "$SKIP_FORMAT" != true ]; then
|
||||
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
|
||||
|
||||
case "$FORMAT_TYPE" in
|
||||
"ext4") mkfs.ext4 -F "$PARTITION" ;;
|
||||
"xfs") mkfs.xfs -f "$PARTITION" ;;
|
||||
"btrfs") mkfs.btrfs -f "$PARTITION" ;;
|
||||
esac
|
||||
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
whiptail --title "$(translate "Format Failed")" --msgbox "$(translate "Failed to format partition") $PARTITION $(translate "with") $FORMAT_TYPE.\\n\\n$(translate "The disk may be in use by the system or have hardware issues.")" 12 70
|
||||
continue
|
||||
@@ -479,35 +483,69 @@ for DISK in $SELECTED; do
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
INDEX=0
|
||||
while pct config "$CTID" | grep -q "mp${INDEX}:"; do
|
||||
((INDEX++))
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Determine the filesystem type for mount options
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
if [[ -n "$CURRENT_FS" ]]; then
|
||||
FORMAT_TYPE="$CURRENT_FS"
|
||||
fi
|
||||
|
||||
# Install filesystem tools in container if needed
|
||||
FS_PKG=""
|
||||
FS_BIN=""
|
||||
if [[ "$FORMAT_TYPE" == "xfs" ]]; then
|
||||
FS_PKG="xfsprogs"
|
||||
FS_BIN="mkfs.xfs"
|
||||
elif [[ "$FORMAT_TYPE" == "btrfs" ]]; then
|
||||
FS_PKG="btrfs-progs"
|
||||
FS_BIN="mkfs.btrfs"
|
||||
fi
|
||||
|
||||
if [[ -n "$FS_PKG" && -n "$FS_BIN" ]]; then
|
||||
if ! pct exec "$CTID" -- sh -c "command -v $FS_BIN >/dev/null 2>&1"; then
|
||||
msg_info "$(translate "Installing required tools for $FORMAT_TYPE in CT $CTID...")"
|
||||
if pct exec "$CTID" -- sh -c "[ -f /etc/alpine-release ]"; then
|
||||
pct exec "$CTID" -- sh -c "apk update >/dev/null && apk add --no-progress $FS_PKG >/dev/null"
|
||||
elif pct exec "$CTID" -- sh -c "[ -f /etc/os-release ] && (grep -qE 'debian|ubuntu' /etc/os-release)"; then
|
||||
pct exec "$CTID" -- sh -c "apt-get update -qq >/dev/null && apt-get install -y -qq $FS_PKG >/dev/null"
|
||||
fi
|
||||
msg_ok "$(translate "Required tools for $FORMAT_TYPE installed in CT $CTID.")"
|
||||
fi
|
||||
fi
|
||||
|
||||
##############################################################################
|
||||
|
||||
RESULT=$(pct set "$CTID" -mp${INDEX} "$PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
|
||||
|
||||
pct exec "$CTID" -- chmod -R 777 "$MOUNT_POINT"
|
||||
|
||||
# Get persistent path for the partition
|
||||
PERSISTENT_PARTITION=$(get_persistent_path "$PARTITION")
|
||||
|
||||
# Apply passthrough with persistent path
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
if [ "$CURRENT_FS" == "xfs" ] || [ "$FORMAT_TYPE" == "xfs" ]; then
|
||||
RESULT=$(pct set "$CTID" -mp${INDEX} "$PERSISTENT_PARTITION,mp=$MOUNT_POINT,backup=0,ro=0" 2>&1)
|
||||
else
|
||||
RESULT=$(pct set "$CTID" -mp${INDEX} "$PERSISTENT_PARTITION,mp=$MOUNT_POINT,backup=0,ro=0,acl=1" 2>&1)
|
||||
fi
|
||||
|
||||
# Adjust permissions inside the CT
|
||||
pct exec "$CTID" -- chmod -R 775 "$MOUNT_POINT" 2>/dev/null || true
|
||||
|
||||
# Show confirmation with persistent identifier
|
||||
msg_ok "$(translate "Assigned using") $PERSISTENT_PARTITION"
|
||||
##############################################################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
MESSAGE="$(translate "The disk") $DISK_INFO $(translate "has been successfully added to CT") $CTID $(translate "as a mount point at") $MOUNT_POINT."
|
||||
MESSAGE+="\\n$(translate "Using persistent path"): $PERSISTENT_PARTITION"
|
||||
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
MESSAGE+="\\n\\n$(translate "WARNING: This disk is also assigned to the following CT(s):")\\n$ASSIGNED_TO"
|
||||
MESSAGE+="\\n$(translate "Make sure not to start CTs that share this disk at the same time to avoid data corruption.")"
|
||||
fi
|
||||
|
||||
SUCCESS_MESSAGES+="$MESSAGE\\n\\n"
|
||||
((DISKS_ADDED++))
|
||||
else
|
||||
@@ -515,8 +553,6 @@ for DISK in $SELECTED; do
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
|
||||
msg_ok "$(translate "Disk processing completed.")"
|
||||
|
||||
if [ -n "$SUCCESS_MESSAGES" ]; then
|
||||
@@ -530,5 +566,4 @@ fi
|
||||
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# Version : 1.1
|
||||
# Last Updated: 29/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
|
||||
# making it easy to attach pre-existing disk files without manual configuration.
|
||||
#
|
||||
# Before running the script, ensure that disk images are available in /var/lib/vz/template/images/.
|
||||
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk) and lists the available files.
|
||||
# The script scans this directory for compatible formats (.img, .qcow2, .vmdk, .raw) and lists the available files.
|
||||
#
|
||||
# Using an interactive menu, you can:
|
||||
# - Select a VM to attach the imported disk.
|
||||
@@ -32,40 +32,65 @@ BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
# Path where disk images are stored
|
||||
IMAGES_DIR="/var/lib/vz/template/images/"
|
||||
# Configuration ============================================
|
||||
|
||||
|
||||
# Initial setup
|
||||
if [ ! -d "$IMAGES_DIR" ]; then
|
||||
msg_info "$(translate 'Creating images directory')"
|
||||
mkdir -p "$IMAGES_DIR"
|
||||
chmod 755 "$IMAGES_DIR"
|
||||
msg_ok "$(translate 'Images directory created:') $IMAGES_DIR"
|
||||
|
||||
detect_image_dir() {
|
||||
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
|
||||
path=$(pvesm path "${store}:template" 2>/dev/null)
|
||||
if [[ -d "$path" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$path/*.$ext" > /dev/null; then
|
||||
echo "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
for sub in images iso; do
|
||||
dir="$path/$sub"
|
||||
if [[ -d "$dir" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$dir/*.$ext" > /dev/null; then
|
||||
echo "$dir"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
for fallback in /var/lib/vz/template/images /var/lib/vz/template/iso; do
|
||||
if [[ -d "$fallback" ]]; then
|
||||
for ext in raw img qcow2 vmdk; do
|
||||
if compgen -G "$fallback/*.$ext" > /dev/null; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
IMAGES_DIR=$(detect_image_dir)
|
||||
if [[ -z "$IMAGES_DIR" ]]; then
|
||||
dialog --title "$(translate 'No Images Found')" \
|
||||
--msgbox "$(translate 'Could not find any directory containing disk images')\n\n$(translate 'Make sure there is at least one file with extension .img, .qcow2, .vmdk or .raw')" 15 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Check if there are any images in the directory
|
||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk)$")
|
||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
|
||||
if [ -z "$IMAGES" ]; then
|
||||
whiptail --title "$(translate 'No Images Found')" \
|
||||
--msgbox "$(translate 'No images available for import in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk')\n\n$(translate 'Please add some images and try again.')" 15 60
|
||||
exit 1
|
||||
dialog --title "$(translate 'No Disk Images Found')" \
|
||||
--msgbox "$(translate 'No compatible disk images found in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk, .raw')" 15 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Display initial message
|
||||
whiptail --title "$(translate 'Import Disk Image')" --msgbox "$(translate 'Make sure the disk images you want to import are located in:')\n\n$IMAGES_DIR\n\n$(translate 'Supported formats: .img, .qcow2, .vmdk.')" 15 60
|
||||
|
||||
|
||||
|
||||
# 1. Select VM
|
||||
msg_info "$(translate 'Getting VM list')"
|
||||
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
|
||||
@@ -78,7 +103,7 @@ msg_ok "$(translate 'VM list obtained')"
|
||||
VMID=$(whiptail --title "$(translate 'Select VM')" --menu "$(translate 'Select the VM where you want to import the disk image:')" 15 60 8 $VM_LIST 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$VMID" ]; then
|
||||
# msg_error "$(translate 'No VM selected')"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -93,7 +118,7 @@ if [ -z "$STORAGE_LIST" ]; then
|
||||
fi
|
||||
msg_ok "$(translate 'Storage volumes obtained')"
|
||||
|
||||
# Create an array of storage options for whiptail
|
||||
|
||||
STORAGE_OPTIONS=()
|
||||
while read -r storage; do
|
||||
STORAGE_OPTIONS+=("$storage" "")
|
||||
@@ -102,7 +127,7 @@ done <<< "$STORAGE_LIST"
|
||||
STORAGE=$(whiptail --title "$(translate 'Select Storage')" --menu "$(translate 'Select the storage volume for disk import:')" 15 60 8 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$STORAGE" ]; then
|
||||
# msg_error "$(translate 'No storage selected')"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -124,17 +149,19 @@ done <<< "$IMAGES"
|
||||
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" --checklist "$(translate 'Select the disk images to import:')" 20 60 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED_IMAGES" ]; then
|
||||
# msg_error "$(translate 'No images selected')"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# 4. Import each selected image
|
||||
for IMAGE in $SELECTED_IMAGES; do
|
||||
|
||||
# Remove quotes from selected image
|
||||
|
||||
IMAGE=$(echo "$IMAGE" | tr -d '"')
|
||||
|
||||
# 5. Select interface type for each image
|
||||
|
||||
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
|
||||
"sata" "SATA" \
|
||||
"scsi" "SCSI" \
|
||||
@@ -148,57 +175,63 @@ for IMAGE in $SELECTED_IMAGES; do
|
||||
|
||||
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
||||
|
||||
# Show initial message
|
||||
|
||||
msg_info "$(translate 'Importing image:')"
|
||||
|
||||
# Temporary file to capture the imported disk
|
||||
|
||||
TEMP_DISK_FILE=$(mktemp)
|
||||
|
||||
|
||||
# Execute the command and process its output in real-time
|
||||
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
|
||||
if [[ "$line" =~ transferred ]]; then
|
||||
|
||||
# Extract the progress percentage
|
||||
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
||||
|
||||
# Show progress with custom format without translation
|
||||
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||
|
||||
PERCENT=$(echo "$line" | grep -oP "\d+\.\d+(?=%)")
|
||||
|
||||
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
|
||||
|
||||
# Extract the imported disk name and save it to the temporary file
|
||||
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
||||
fi
|
||||
done
|
||||
echo -ne "\n"
|
||||
|
||||
|
||||
IMPORT_STATUS=${PIPESTATUS[0]} # Capture the exit status of the main command
|
||||
IMPORT_STATUS=${PIPESTATUS[0]}
|
||||
|
||||
if [ $IMPORT_STATUS -eq 0 ]; then
|
||||
msg_ok "$(translate 'Image imported successfully')"
|
||||
|
||||
# Read the imported disk from the temporary file
|
||||
|
||||
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
|
||||
rm -f "$TEMP_DISK_FILE" # Delete the temporary file
|
||||
rm -f "$TEMP_DISK_FILE"
|
||||
|
||||
|
||||
if [ -z "$IMPORTED_DISK" ]; then
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
|
||||
|
||||
if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then
|
||||
|
||||
UNUSED_LINE=$(qm config "$VMID" | grep -E '^unused[0-9]+:')
|
||||
IMPORTED_ID=$(echo "$UNUSED_LINE" | cut -d: -f1)
|
||||
IMPORTED_DISK=$(echo "$UNUSED_LINE" | cut -d: -f2- | xargs)
|
||||
else
|
||||
|
||||
IMPORTED_DISK=$(qm config "$VMID" | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f2- | xargs)
|
||||
IMPORTED_ID=$(qm config "$VMID" | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f1)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$IMPORTED_DISK" ]; then
|
||||
|
||||
# Find the next available disk slot
|
||||
|
||||
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
|
||||
if [ -z "$EXISTING_DISKS" ]; then
|
||||
|
||||
# If there are no existing disks, start from 0
|
||||
NEXT_SLOT=0
|
||||
else
|
||||
# If there are existing disks, take the last one and add 1
|
||||
LAST_SLOT=$(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//")
|
||||
NEXT_SLOT=$((LAST_SLOT + 1))
|
||||
fi
|
||||
|
||||
|
||||
# Ask if SSD emulation is desired (only for non-VirtIO interfaces)
|
||||
|
||||
if [ "$INTERFACE" != "virtio" ]; then
|
||||
if (whiptail --title "$(translate 'SSD Emulation')" --yesno "$(translate 'Do you want to use SSD emulation for this disk?')" 10 60); then
|
||||
SSD_OPTION=",ssd=1"
|
||||
@@ -209,14 +242,18 @@ for IMAGE in $SELECTED_IMAGES; do
|
||||
SSD_OPTION=""
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate 'Configuring disk')"
|
||||
|
||||
# Configure the disk in the VM
|
||||
|
||||
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
|
||||
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
|
||||
|
||||
# Ask if the disk should be bootable
|
||||
|
||||
if [[ -n "$IMPORTED_ID" ]]; then
|
||||
qm set "$VMID" -delete "$IMPORTED_ID" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
|
||||
if (whiptail --title "$(translate 'Make Bootable')" --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60); then
|
||||
msg_info "$(translate 'Configuring disk as bootable')"
|
||||
|
||||
@@ -228,14 +265,22 @@ for IMAGE in $SELECTED_IMAGES; do
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
|
||||
echo "DEBUG: Tried to configure: --${INTERFACE}${NEXT_SLOT} \"$IMPORTED_DISK${SSD_OPTION}\""
|
||||
echo "DEBUG: VM config after import:"
|
||||
qm config "$VMID" | grep -E "(unused|${INTERFACE})"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not find the imported disk')"
|
||||
echo "DEBUG: VM config after import:"
|
||||
qm config "$VMID"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate 'Could not import') $IMAGE"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
|
||||
msg_ok "$(translate 'All selected images have been processed')"
|
||||
sleep 2
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - Mount independent disk on Proxmox host
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT
|
||||
# Version : 1.3-dialog
|
||||
# Last Updated: 13/12/2024
|
||||
# ==========================================================
|
||||
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
mount_disk_host_bk() {
|
||||
|
||||
|
||||
|
||||
get_disk_info() {
|
||||
local disk=$1
|
||||
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
|
||||
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
|
||||
echo "$MODEL" "$SIZE"
|
||||
}
|
||||
|
||||
|
||||
is_usb_disk() {
|
||||
local disk=$1
|
||||
local disk_name=$(basename "$disk")
|
||||
|
||||
|
||||
if readlink -f "/sys/block/$disk_name/device" 2>/dev/null | grep -q "usb"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
if udevadm info --query=property --name="$disk" 2>/dev/null | grep -q "ID_BUS=usb"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
is_system_disk() {
|
||||
local disk=$1
|
||||
local disk_name=$(basename "$disk")
|
||||
|
||||
|
||||
local system_mounts=$(df -h | grep -E '^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(/|/boot|/usr|/var|/home)$' | awk '{print $1}')
|
||||
|
||||
|
||||
for mount_dev in $system_mounts; do
|
||||
|
||||
local mount_disk=""
|
||||
if [[ "$mount_dev" =~ ^/dev/mapper/ ]]; then
|
||||
|
||||
local vg_name=$(lvs --noheadings -o vg_name "$mount_dev" 2>/dev/null | xargs)
|
||||
if [[ -n "$vg_name" ]]; then
|
||||
local pvs_list=$(pvs --noheadings -o pv_name -S vg_name="$vg_name" 2>/dev/null | xargs)
|
||||
for pv in $pvs_list; do
|
||||
if [[ -n "$pv" && -e "$pv" ]]; then
|
||||
mount_disk=$(lsblk -no PKNAME "$pv" 2>/dev/null)
|
||||
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
elif [[ "$mount_dev" =~ ^/dev/[hsv]d[a-z][0-9]* || "$mount_dev" =~ ^/dev/nvme[0-9]+n[0-9]+p[0-9]+ ]]; then
|
||||
|
||||
mount_disk=$(lsblk -no PKNAME "$mount_dev" 2>/dev/null)
|
||||
if [[ -n "$mount_disk" && "/dev/$mount_disk" == "$disk" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
local fs_type=$(lsblk -no FSTYPE "$disk" 2>/dev/null | head -1)
|
||||
if [[ "$fs_type" == "btrfs" ]]; then
|
||||
|
||||
local temp_mount=$(mktemp -d)
|
||||
if mount -o ro "$disk" "$temp_mount" 2>/dev/null; then
|
||||
|
||||
if btrfs subvolume list "$temp_mount" 2>/dev/null | grep -qE '(@|@home|@var|@boot|@root|root)'; then
|
||||
umount "$temp_mount" 2>/dev/null
|
||||
rmdir "$temp_mount" 2>/dev/null
|
||||
return 0
|
||||
fi
|
||||
umount "$temp_mount" 2>/dev/null
|
||||
fi
|
||||
rmdir "$temp_mount" 2>/dev/null
|
||||
|
||||
|
||||
while read -r part; do
|
||||
if [[ -n "$part" ]]; then
|
||||
local part_fs=$(lsblk -no FSTYPE "/dev/$part" 2>/dev/null)
|
||||
if [[ "$part_fs" == "btrfs" ]]; then
|
||||
local mount_point=$(lsblk -no MOUNTPOINT "/dev/$part" 2>/dev/null)
|
||||
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
|
||||
fi
|
||||
|
||||
|
||||
local disk_uuid=$(blkid -s UUID -o value "$disk" 2>/dev/null)
|
||||
local part_uuids=()
|
||||
while read -r part; do
|
||||
if [[ -n "$part" ]]; then
|
||||
local uuid=$(blkid -s UUID -o value "/dev/$part" 2>/dev/null)
|
||||
if [[ -n "$uuid" ]]; then
|
||||
part_uuids+=("$uuid")
|
||||
fi
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME "$disk" | tail -n +2)
|
||||
|
||||
|
||||
for uuid in "${part_uuids[@]}" "$disk_uuid"; do
|
||||
if [[ -n "$uuid" ]] && grep -q "UUID=$uuid" /etc/fstab; then
|
||||
local mount_point=$(grep "UUID=$uuid" /etc/fstab | awk '{print $2}')
|
||||
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if grep -q "$disk" /etc/fstab; then
|
||||
local mount_point=$(grep "$disk" /etc/fstab | awk '{print $2}')
|
||||
if [[ "$mount_point" == "/" || "$mount_point" == "/boot" || "$mount_point" == "/home" || "$mount_point" == "/var" ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
local disk_count=$(lsblk -dn -e 7,11 -o PATH | wc -l)
|
||||
if [[ "$disk_count" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
msg_info "$(translate "Detecting available disks...")"
|
||||
|
||||
USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}')
|
||||
MOUNTED_DISKS=$(lsblk -ln -o NAME,MOUNTPOINT | awk '$2!="" {print "/dev/" $1}')
|
||||
|
||||
ZFS_DISKS=""
|
||||
ZFS_RAW=$(zpool list -v -H 2>/dev/null | awk '{print $1}' | grep -v '^NAME$' | grep -v '^-' | grep -v '^mirror')
|
||||
|
||||
for entry in $ZFS_RAW; do
|
||||
path=""
|
||||
if [[ "$entry" == wwn-* || "$entry" == ata-* ]]; then
|
||||
if [ -e "/dev/disk/by-id/$entry" ]; then
|
||||
path=$(readlink -f "/dev/disk/by-id/$entry")
|
||||
fi
|
||||
elif [[ "$entry" == /dev/* ]]; then
|
||||
path="$entry"
|
||||
fi
|
||||
|
||||
if [ -n "$path" ]; then
|
||||
base_disk=$(lsblk -no PKNAME "$path" 2>/dev/null)
|
||||
if [ -n "$base_disk" ]; then
|
||||
ZFS_DISKS+="/dev/$base_disk"$'\n'
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
ZFS_DISKS=$(echo "$ZFS_DISKS" | sort -u)
|
||||
|
||||
LVM_DEVICES=$(
|
||||
pvs --noheadings -o pv_name 2> >(grep -v 'File descriptor .* leaked') |
|
||||
while read -r dev; do
|
||||
[[ -n "$dev" && -e "$dev" ]] && readlink -f "$dev"
|
||||
done | sort -u
|
||||
)
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
while read -r DISK; do
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
INFO=($(get_disk_info "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
IS_SYSTEM=false
|
||||
IS_USB=false
|
||||
|
||||
|
||||
if is_system_disk "$DISK"; then
|
||||
IS_SYSTEM=true
|
||||
fi
|
||||
|
||||
|
||||
if is_usb_disk "$DISK"; then
|
||||
IS_USB=true
|
||||
fi
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$fstype" == "LVM2_member" ]] && IS_LVM=true
|
||||
if grep -q "/dev/$part" <<< "$MOUNTED_DISKS"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
REAL_PATH=""
|
||||
if [[ -n "$DISK" && -e "$DISK" ]]; then
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
fi
|
||||
if [[ -n "$REAL_PATH" ]] && echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=""
|
||||
if [[ -n "$DISK" && -e "$DISK" ]]; then
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
fi
|
||||
CONFIG_DATA=$(grep -vE '^\s*#' /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf 2>/dev/null)
|
||||
|
||||
if grep -Fq "$REAL_PATH" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
else
|
||||
for SYMLINK in /dev/disk/by-id/*; do
|
||||
[[ -e "$SYMLINK" ]] || continue
|
||||
if [[ "$(readlink -f "$SYMLINK")" == "$REAL_PATH" ]]; then
|
||||
if grep -Fq "$SYMLINK" <<< "$CONFIG_DATA"; then
|
||||
USED_BY="⚠ $(translate "In use")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
|
||||
if grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
if $IS_ZFS; then SHOW_DISK=false; fi
|
||||
if $IS_MOUNTED; then SHOW_DISK=false; fi
|
||||
if $IS_SYSTEM; then SHOW_DISK=false; fi
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ -n "$USED_BY" ]] && LABEL+=" [$USED_BY]"
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ RAID"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
|
||||
|
||||
if $IS_USB; then
|
||||
LABEL+=" USB"
|
||||
else
|
||||
LABEL+=" $(translate "Internal")"
|
||||
fi
|
||||
|
||||
DESCRIPTION=$(printf "%-30s %10s%s" "$MODEL" "$SIZE" "$LABEL")
|
||||
FREE_DISKS+=("$DISK" "$DESCRIPTION" "off")
|
||||
fi
|
||||
done < <(lsblk -dn -e 7,11 -o PATH)
|
||||
|
||||
if [ "${#FREE_DISKS[@]}" -eq 0 ]; then
|
||||
dialog --title "$(translate "Error")" --msgbox "$(translate "No available disks found on the host.")" 8 60
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Available disks detected.")"
|
||||
|
||||
# Building the array for dialog (format: tag item on/off tag item on/off...)
|
||||
DLG_LIST=()
|
||||
for ((i=0; i<${#FREE_DISKS[@]}; i+=3)); do
|
||||
DLG_LIST+=("${FREE_DISKS[i]}" "${FREE_DISKS[i+1]}" "${FREE_DISKS[i+2]}")
|
||||
done
|
||||
|
||||
SELECTED=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Select Disk")" \
|
||||
--radiolist "\n$(translate "Select the disk you want to mount on the host:")" 20 90 10 \
|
||||
"${DLG_LIST[@]}" 2>&1 >/dev/tty)
|
||||
|
||||
if [ -z "$SELECTED" ]; then
|
||||
dialog --title "$(translate "Error")" --msgbox "$(translate "No disk was selected.")" 8 50
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Disk selected successfully:") $SELECTED"
|
||||
|
||||
# ------------------- Partitions and formatting ------------------------
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
|
||||
SKIP_FORMAT=false
|
||||
DEFAULT_MOUNT="/mnt/backup"
|
||||
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
msg_ok "$(translate "Detected existing filesystem") $CURRENT_FS $(translate "on") $PARTITION."
|
||||
else
|
||||
dialog --title "$(translate "Unsupported Filesystem")" --yesno \
|
||||
"$(translate "The partition") $PARTITION $(translate "has an unsupported filesystem ($CURRENT_FS).\nDo you want to format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then exit 0; fi
|
||||
fi
|
||||
else
|
||||
CURRENT_FS=$(lsblk -no FSTYPE "$SELECTED" | xargs)
|
||||
if [[ "$CURRENT_FS" == "ext4" || "$CURRENT_FS" == "xfs" || "$CURRENT_FS" == "btrfs" ]]; then
|
||||
SKIP_FORMAT=true
|
||||
PARTITION="$SELECTED"
|
||||
msg_ok "$(translate "Detected filesystem") $CURRENT_FS $(translate "directly on disk") $SELECTED."
|
||||
else
|
||||
dialog --title "$(translate "No Valid Partitions")" --yesno \
|
||||
"$(translate "The disk has no partitions and no valid filesystem. Do you want to create a new partition and format it?")" 10 70
|
||||
if [ $? -ne 0 ]; then exit 0; fi
|
||||
|
||||
echo -e "$(translate "Creating partition table and partition...")"
|
||||
parted -s "$SELECTED" mklabel gpt
|
||||
parted -s "$SELECTED" mkpart primary 0% 100%
|
||||
sleep 2
|
||||
partprobe "$SELECTED"
|
||||
sleep 2
|
||||
|
||||
PARTITION=$(lsblk -rno NAME "$SELECTED" | awk -v disk="$(basename "$SELECTED")" '$1 != disk {print $1; exit}')
|
||||
if [ -n "$PARTITION" ]; then
|
||||
PARTITION="/dev/$PARTITION"
|
||||
else
|
||||
dialog --title "$(translate "Partition Error")" --msgbox \
|
||||
"$(translate "Failed to create partition on disk") $SELECTED." 8 70
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$SKIP_FORMAT" != true ]; then
|
||||
FORMAT_TYPE=$(dialog --title "$(translate "Select Format Type")" --menu \
|
||||
"$(translate "Select the filesystem type for") $PARTITION:" 15 60 5 \
|
||||
"ext4" "$(translate "Extended Filesystem 4 (recommended)")" \
|
||||
"xfs" "XFS" \
|
||||
"btrfs" "Btrfs" 2>&1 >/dev/tty)
|
||||
if [ -z "$FORMAT_TYPE" ]; then
|
||||
dialog --title "$(translate "Format Cancelled")" --msgbox \
|
||||
"$(translate "Format operation cancelled. The disk will not be added.")" 8 60
|
||||
exit 0
|
||||
fi
|
||||
|
||||
dialog --title "$(translate "WARNING")" --yesno \
|
||||
"$(translate "WARNING: This operation will FORMAT the disk") $PARTITION $(translate "with") $FORMAT_TYPE.\n\n$(translate "ALL DATA ON THIS DISK WILL BE PERMANENTLY LOST!")\n\n$(translate "Are you sure you want to continue")" 15 70
|
||||
if [ $? -ne 0 ]; then exit 0; fi
|
||||
|
||||
echo -e "$(translate "Formatting partition") $PARTITION $(translate "with") $FORMAT_TYPE..."
|
||||
case "$FORMAT_TYPE" in
|
||||
"ext4") mkfs.ext4 -F "$PARTITION" ;;
|
||||
"xfs") mkfs.xfs -f "$PARTITION" ;;
|
||||
"btrfs") mkfs.btrfs -f "$PARTITION" ;;
|
||||
esac
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
cleanup
|
||||
dialog --title "$(translate "Format Failed")" --msgbox \
|
||||
"$(translate "Failed to format partition") $PARTITION $(translate "with") $FORMAT_TYPE." 12 70
|
||||
exit 1
|
||||
else
|
||||
msg_ok "$(translate "Partition") $PARTITION $(translate "successfully formatted with") $FORMAT_TYPE."
|
||||
partprobe "$SELECTED"
|
||||
sleep 2
|
||||
fi
|
||||
fi
|
||||
|
||||
# ------------------- Mount point and permissions -------------------
|
||||
|
||||
MOUNT_POINT=$(dialog --title "$(translate "Mount Point")" \
|
||||
--inputbox "$(translate "Enter the mount point for the disk (e.g., /mnt/backup):")" \
|
||||
10 60 "$DEFAULT_MOUNT" 2>&1 >/dev/tty)
|
||||
if [ -z "$MOUNT_POINT" ]; then
|
||||
dialog --title "$(translate "Error")" --msgbox "$(translate "No mount point was specified.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Mount point specified:") $MOUNT_POINT"
|
||||
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
|
||||
UUID=$(blkid -s UUID -o value "$PARTITION")
|
||||
FS_TYPE=$(lsblk -no FSTYPE "$PARTITION" | xargs)
|
||||
FSTAB_ENTRY="UUID=$UUID $MOUNT_POINT $FS_TYPE defaults 0 0"
|
||||
|
||||
if grep -q "UUID=$UUID" /etc/fstab; then
|
||||
sed -i "s|^.*UUID=$UUID.*|$FSTAB_ENTRY|" /etc/fstab
|
||||
msg_ok "$(translate "fstab entry updated for") $UUID"
|
||||
else
|
||||
echo "$FSTAB_ENTRY" >> /etc/fstab
|
||||
msg_ok "$(translate "fstab entry added for") $UUID"
|
||||
fi
|
||||
|
||||
mount "$MOUNT_POINT" 2> >(grep -v "systemd still uses")
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
if ! getent group sharedfiles >/dev/null; then
|
||||
groupadd sharedfiles
|
||||
msg_ok "$(translate "Group 'sharedfiles' created")"
|
||||
else
|
||||
msg_ok "$(translate "Group 'sharedfiles' already exists")"
|
||||
fi
|
||||
|
||||
chown root:sharedfiles "$MOUNT_POINT"
|
||||
chmod 2775 "$MOUNT_POINT"
|
||||
|
||||
dialog --title "$(translate "Success")" --msgbox "$(translate "The disk has been successfully mounted at") $MOUNT_POINT" 8 60
|
||||
echo "$MOUNT_POINT" > /usr/local/share/proxmenux/last_backup_mount.txt
|
||||
msg_ok "$(translate "Disk mounted at") $MOUNT_POINT"
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
else
|
||||
dialog --title "$(translate "Mount Error")" --msgbox "$(translate "Failed to mount the disk at") $MOUNT_POINT" 8 60
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - UUP Dump ISO Creator
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 07/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script is part of the ProxMenux tools for Proxmox VE.
|
||||
# It allows downloading and converting official Windows ISO images
|
||||
# from UUP Dump using a shared link (with ID, pack, and edition).
|
||||
#
|
||||
# Key features:
|
||||
# - Automatically installs and verifies required dependencies (aria2c, cabextract, wimlib-imagex…)
|
||||
# - Downloads the selected Windows edition from UUP Dump using aria2
|
||||
# - Converts the downloaded files into a bootable ISO
|
||||
# - Stores the resulting ISO in the default template path (/var/lib/vz/template/iso)
|
||||
# - Provides a graphical prompt via whiptail for user-friendly usage
|
||||
#
|
||||
# This tool simplifies the creation of official Windows ISOs
|
||||
# for use in virtual machines within Proxmox VE.
|
||||
# ==========================================================
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
detect_iso_dir() {
|
||||
for store in $(pvesm status -content iso | awk 'NR>1 {print $1}'); do
|
||||
for ext in iso img; do
|
||||
volid=$(pvesm list "$store" --content iso | awk -v ext="$ext" 'NR>1 && $2 ~ ext {print $1; exit}')
|
||||
if [[ -n "$volid" ]]; then
|
||||
path=$(pvesm path "$volid" 2>/dev/null)
|
||||
dir=$(dirname "$path")
|
||||
[[ -d "$dir" ]] && echo "$dir" && return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [[ -d /var/lib/vz/template/iso ]]; then
|
||||
echo "/var/lib/vz/template/iso"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
function run_uupdump_creator() {
|
||||
|
||||
|
||||
local DEPS=(curl aria2 cabextract wimtools genisoimage chntpw)
|
||||
local CMDS=(curl aria2c cabextract wimlib-imagex genisoimage chntpw)
|
||||
local MISSING=()
|
||||
local FAILED=()
|
||||
|
||||
for i in "${!CMDS[@]}"; do
|
||||
if ! command -v "${CMDS[$i]}" &>/dev/null; then
|
||||
MISSING+=("${DEPS[$i]}")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
msg_info "$(translate "Installing dependencies: ${MISSING[*]}")"
|
||||
apt-get update -qq >/dev/null 2>&1
|
||||
if ! apt-get install -y "${MISSING[@]}" >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Failed to install: ${MISSING[*]}")"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
for i in "${!CMDS[@]}"; do
|
||||
if ! command -v "${CMDS[$i]}" &>/dev/null; then
|
||||
FAILED+=("${CMDS[$i]}")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
msg_ok "$(translate "All dependencies installed and verified.")"
|
||||
else
|
||||
msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
ISO_DIR=$(detect_iso_dir)
|
||||
if [[ -z "$ISO_DIR" ]]; then
|
||||
msg_error "$(translate "Could not determine a valid ISO storage directory.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
TMP_DIR=$(dialog --inputbox "Enter temporary folder path (default: /root/uup-temp):" 10 60 "/root/uup-temp" 3>&1 1>&2 2>&3)
|
||||
if [[ $? -ne 0 || -z "$TMP_DIR" ]]; then
|
||||
TMP_DIR="/root/uup-temp"
|
||||
fi
|
||||
|
||||
OUT_DIR="$ISO_DIR"
|
||||
CONVERTER="/root/uup-converter"
|
||||
|
||||
mkdir -p "$TMP_DIR" "$OUT_DIR"
|
||||
cd "$TMP_DIR" || exit 1
|
||||
|
||||
|
||||
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
|
||||
if [[ $? -ne 0 || -z "$UUP_URL" ]]; then
|
||||
msg_warn "$(translate "Cancelled by user or empty URL.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! "$UUP_URL" =~ id=.+\&pack=.+\&edition=.+ ]]; then
|
||||
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
|
||||
sleep 2
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
BUILD_ID=$(echo "$UUP_URL" | grep -oP 'id=\K[^&]+')
|
||||
LANG=$(echo "$UUP_URL" | grep -oP 'pack=\K[^&]+')
|
||||
EDITION=$(echo "$UUP_URL" | grep -oP 'edition=\K[^&]+')
|
||||
ARCH="amd64"
|
||||
|
||||
echo -e "\n${BGN}=============== UUP Dump Creator ===============${CL}"
|
||||
echo -e " ${BGN}🆔 ID:${CL} ${DGN}$BUILD_ID${CL}"
|
||||
echo -e " ${BGN}🌐 Language:${CL} ${DGN}$LANG${CL}"
|
||||
echo -e " ${BGN}💿 Edition:${CL} ${DGN}$EDITION${CL}"
|
||||
echo -e " ${BGN}🖥️ Architecture:${CL} ${DGN}$ARCH${CL}"
|
||||
echo -e "${BGN}===============================================${CL}\n"
|
||||
|
||||
|
||||
if [[ ! -f "$CONVERTER/convert.sh" ]]; then
|
||||
echo "📦 $(translate "Downloading UUP converter...")"
|
||||
mkdir -p "$CONVERTER"
|
||||
cd "$CONVERTER" || exit 1
|
||||
wget -q https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz -O converter.tar.gz
|
||||
tar -xzf converter.tar.gz --strip-components=1
|
||||
chmod +x convert.sh
|
||||
cd "$TMP_DIR" || exit 1
|
||||
fi
|
||||
|
||||
|
||||
cat > uup_download_linux.sh <<EOF
|
||||
#!/bin/bash
|
||||
mkdir -p files
|
||||
echo "https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz" > files/converter_multi
|
||||
|
||||
for prog in aria2c cabextract wimlib-imagex chntpw; do
|
||||
which \$prog &>/dev/null || { echo "\$prog not found."; exit 1; }
|
||||
done
|
||||
which genisoimage &>/dev/null || which mkisofs &>/dev/null || { echo "genisoimage/mkisofs not found."; exit 1; }
|
||||
|
||||
destDir="UUPs"
|
||||
tempScript="aria2_script.\$RANDOM.txt"
|
||||
|
||||
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \
|
||||
-x16 -s16 -j2 --allow-overwrite=true --auto-file-renaming=false -d"files" -i"files/converter_multi" || exit 1
|
||||
|
||||
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \
|
||||
-o"\$tempScript" --allow-overwrite=true --auto-file-renaming=false \
|
||||
"https://uupdump.net/get.php?id=$BUILD_ID&pack=$LANG&edition=$EDITION&aria2=2" || exit 1
|
||||
|
||||
grep '#UUPDUMP_ERROR:' "\$tempScript" && { echo "❌ Error generating UUP download list."; exit 1; }
|
||||
|
||||
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \
|
||||
-x16 -s16 -j5 -c -R -d"\$destDir" -i"\$tempScript" || exit 1
|
||||
EOF
|
||||
|
||||
chmod +x uup_download_linux.sh
|
||||
|
||||
|
||||
|
||||
# ==========================
|
||||
./uup_download_linux.sh
|
||||
# ==========================
|
||||
|
||||
|
||||
|
||||
UUP_FOLDER=$(find "$TMP_DIR" -type d -name "UUPs" | head -n1)
|
||||
[[ -z "$UUP_FOLDER" ]] && msg_error "$(translate "No UUP folder found.")" && exit 1
|
||||
|
||||
|
||||
echo -e "\n${GN}=======================================${CL}"
|
||||
echo -e " 💿 ${GN}Starting ISO conversion...${CL}"
|
||||
echo -e "${GN}=======================================${CL}\n"
|
||||
|
||||
"$CONVERTER/convert.sh" wim "$UUP_FOLDER" 1
|
||||
|
||||
|
||||
ISO_FILE=$(find "$TMP_DIR" "$CONVERTER" "$UUP_FOLDER" -maxdepth 1 -iname "*.iso" | head -n1)
|
||||
if [[ -f "$ISO_FILE" ]]; then
|
||||
mv "$ISO_FILE" "$OUT_DIR/"
|
||||
msg_ok "$(translate "ISO created successfully:") $OUT_DIR/$(basename "$ISO_FILE")"
|
||||
|
||||
|
||||
msg_ok "$(translate "Cleaning temporary files...")"
|
||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||
|
||||
export OS_TYPE="windows"
|
||||
export LANGUAGE=C
|
||||
export LANG=C
|
||||
export LC_ALL=C
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
else
|
||||
msg_warn "$(translate "No ISO was generated.")"
|
||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||
export LANGUAGE=C
|
||||
export LANG=C
|
||||
export LC_ALL=C
|
||||
load_language
|
||||
initialize_cache
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.1
|
||||
# Last Updated: 04/06/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script provides a simple and efficient way to access and execute Proxmox VE scripts
|
||||
# from the Community Scripts project (https://community-scripts.github.io/ProxmoxVE/).
|
||||
#
|
||||
# It serves as a convenient tool to run key automation scripts that simplify system management,
|
||||
# continuing the great work and legacy of tteck in making Proxmox VE more accessible.
|
||||
# A streamlined solution for executing must-have tools in Proxmox VE.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json"
|
||||
METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json"
|
||||
|
||||
for cmd in curl jq dialog; do
|
||||
if ! command -v "$cmd" >/dev/null; then
|
||||
echo "Missing required command: $cmd"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
CACHE_JSON=$(curl -s "$HELPERS_JSON_URL")
|
||||
META_JSON=$(curl -s "$METADATA_URL")
|
||||
|
||||
declare -A CATEGORY_NAMES
|
||||
while read -r id name; do
|
||||
CATEGORY_NAMES[$id]="$name"
|
||||
done < <(echo "$META_JSON" | jq -r '.categories[] | "\(.id)\t\(.name)"')
|
||||
|
||||
declare -A CATEGORY_COUNT
|
||||
for id in $(echo "$CACHE_JSON" | jq -r '.[].categories[]'); do
|
||||
((CATEGORY_COUNT[$id]++))
|
||||
done
|
||||
|
||||
get_type_label() {
|
||||
local type="$1"
|
||||
case "$type" in
|
||||
ct) echo $'\Z1LXC\Zn' ;;
|
||||
vm) echo $'\Z4VM\Zn' ;;
|
||||
pve) echo $'\Z3PVE\Zn' ;;
|
||||
addon) echo $'\Z2ADDON\Zn' ;;
|
||||
*) echo $'\Z7GEN\Zn' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
download_script() {
|
||||
local url="$1"
|
||||
local fallback_pve="${url/misc\/tools\/pve}"
|
||||
local fallback_addon="${url/misc\/tools\/addon}"
|
||||
local fallback_copydata="${url/misc\/tools\/copy-data}"
|
||||
|
||||
if curl --silent --head --fail "$url" >/dev/null; then
|
||||
bash <(curl -s "$url")
|
||||
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
|
||||
bash <(curl -s "$fallback_pve")
|
||||
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
|
||||
bash <(curl -s "$fallback_addon")
|
||||
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
|
||||
bash <(curl -s "$fallback_copydata")
|
||||
else
|
||||
dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70
|
||||
fi
|
||||
}
|
||||
|
||||
RETURN_TO_MAIN=false
|
||||
|
||||
format_credentials() {
|
||||
local script_info="$1"
|
||||
local credentials_info=""
|
||||
|
||||
local has_credentials
|
||||
has_credentials=$(echo "$script_info" | base64 --decode | jq -r 'has("default_credentials")')
|
||||
|
||||
if [[ "$has_credentials" == "true" ]]; then
|
||||
local username password
|
||||
username=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.username // empty')
|
||||
password=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.password // empty')
|
||||
|
||||
if [[ -n "$username" && -n "$password" ]]; then
|
||||
credentials_info="Username: $username | Password: $password"
|
||||
elif [[ -n "$username" ]]; then
|
||||
credentials_info="Username: $username"
|
||||
elif [[ -n "$password" ]]; then
|
||||
credentials_info="Password: $password"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$credentials_info"
|
||||
}
|
||||
|
||||
|
||||
run_script_by_slug() {
|
||||
local slug="$1"
|
||||
local script_info
|
||||
script_info=$(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64')
|
||||
|
||||
decode() {
|
||||
echo "$1" | base64 --decode | jq -r "$2"
|
||||
}
|
||||
|
||||
local name desc script_url notes
|
||||
name=$(decode "$script_info" ".name")
|
||||
desc=$(decode "$script_info" ".desc")
|
||||
script_url=$(decode "$script_info" ".script_url")
|
||||
notes=$(decode "$script_info" ".notes | join(\"\n\")")
|
||||
|
||||
|
||||
local notes_dialog=""
|
||||
if [[ -n "$notes" ]]; then
|
||||
while IFS= read -r line; do
|
||||
notes_dialog+="• $line\n"
|
||||
done <<< "$notes"
|
||||
notes_dialog="${notes_dialog%\\n}"
|
||||
fi
|
||||
|
||||
|
||||
local credentials
|
||||
credentials=$(format_credentials "$script_info")
|
||||
|
||||
|
||||
local msg="\Zb\Z4Descripción:\Zn\n$desc"
|
||||
[[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog"
|
||||
[[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials"
|
||||
|
||||
dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85
|
||||
if [[ $? -eq 0 ]]; then
|
||||
download_script "$script_url"
|
||||
echo
|
||||
echo
|
||||
|
||||
if [[ -n "$desc" || -n "$notes" || -n "$credentials" ]]; then
|
||||
echo -e "$TAB\e[1;36mScript Information:\e[0m"
|
||||
|
||||
|
||||
|
||||
if [[ -n "$notes" ]]; then
|
||||
echo -e "$TAB\e[1;33mNotes:\e[0m"
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
echo -e "$TAB• $line"
|
||||
done <<< "$notes"
|
||||
echo
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "$credentials" ]]; then
|
||||
echo -e "$TAB\e[1;32mDefault Credentials:\e[0m"
|
||||
echo "$TAB$credentials"
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_success "Press Enter to return to the main menu..."
|
||||
read -r
|
||||
RETURN_TO_MAIN=true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
search_and_filter_scripts() {
|
||||
local search_term=""
|
||||
|
||||
while true; do
|
||||
search_term=$(dialog --inputbox "Enter search term (leave empty to show all scripts):" \
|
||||
8 65 "$search_term" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && return
|
||||
|
||||
local filtered_json
|
||||
if [[ -z "$search_term" ]]; then
|
||||
filtered_json="$CACHE_JSON"
|
||||
else
|
||||
local search_lower
|
||||
search_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]')
|
||||
filtered_json=$(echo "$CACHE_JSON" | jq --arg term "$search_lower" '
|
||||
[.[] | select(
|
||||
(.name | ascii_downcase | contains($term)) or
|
||||
(.desc | ascii_downcase | contains($term))
|
||||
)]')
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(echo "$filtered_json" | jq length)
|
||||
|
||||
if [[ $count -eq 0 ]]; then
|
||||
dialog --msgbox "No scripts found for: '$search_term'\n\nTry a different search term." 8 50
|
||||
continue
|
||||
fi
|
||||
|
||||
while true; do
|
||||
declare -A index_to_slug
|
||||
local menu_items=()
|
||||
local i=1
|
||||
|
||||
while IFS=$'\t' read -r slug name type; do
|
||||
index_to_slug[$i]="$slug"
|
||||
local label
|
||||
label=$(get_type_label "$type")
|
||||
local padded_name
|
||||
padded_name=$(printf "%-42s" "$name")
|
||||
local entry="$padded_name $label"
|
||||
menu_items+=("$i" "$entry")
|
||||
((i++))
|
||||
done < <(echo "$filtered_json" | jq -r '
|
||||
sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||
|
||||
menu_items+=("" "")
|
||||
menu_items+=("new_search" "New Search")
|
||||
menu_items+=("show_all" "Show All Scripts")
|
||||
|
||||
local title="Search Results"
|
||||
if [[ -n "$search_term" ]]; then
|
||||
title="Search Results for: '$search_term' ($count found)"
|
||||
else
|
||||
title="All Available Scripts ($count total)"
|
||||
fi
|
||||
|
||||
local selected
|
||||
selected=$(dialog --colors --backtitle "ProxMenux" \
|
||||
--title "$title" \
|
||||
--menu "Select a script or action:" \
|
||||
22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$selected" in
|
||||
"new_search")
|
||||
break
|
||||
;;
|
||||
"show_all")
|
||||
search_term=""
|
||||
filtered_json="$CACHE_JSON"
|
||||
count=$(echo "$filtered_json" | jq length)
|
||||
continue
|
||||
;;
|
||||
"back"|"")
|
||||
return
|
||||
;;
|
||||
*)
|
||||
if [[ -n "${index_to_slug[$selected]}" ]]; then
|
||||
run_script_by_slug "${index_to_slug[$selected]}"
|
||||
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; }
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
while true; do
|
||||
MENU_ITEMS=()
|
||||
|
||||
MENU_ITEMS+=("search" "Search/Filter Scripts")
|
||||
MENU_ITEMS+=("" "")
|
||||
|
||||
for id in $(printf "%s\n" "${!CATEGORY_COUNT[@]}" | sort -n); do
|
||||
name="${CATEGORY_NAMES[$id]:-Category $id}"
|
||||
count="${CATEGORY_COUNT[$id]}"
|
||||
padded_name=$(printf "%-35s" "$name")
|
||||
padded_count=$(printf "(%2d)" "$count")
|
||||
MENU_ITEMS+=("$id" "$padded_name $padded_count")
|
||||
done
|
||||
|
||||
SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \
|
||||
"Select a category or search for scripts:" 20 70 14 \
|
||||
"${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || {
|
||||
dialog --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "\n\n$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:")\n\nhttps://community-scripts.github.io/ProxmoxVE" 15 70
|
||||
clear
|
||||
break
|
||||
}
|
||||
|
||||
if [[ "$SELECTED" == "search" ]]; then
|
||||
search_and_filter_scripts
|
||||
continue
|
||||
fi
|
||||
|
||||
while true; do
|
||||
declare -A INDEX_TO_SLUG
|
||||
SCRIPTS=()
|
||||
i=1
|
||||
while IFS=$'\t' read -r slug name type; do
|
||||
INDEX_TO_SLUG[$i]="$slug"
|
||||
label=$(get_type_label "$type")
|
||||
padded_name=$(printf "%-42s" "$name")
|
||||
entry="$padded_name $label"
|
||||
SCRIPTS+=("$i" "$entry")
|
||||
((i++))
|
||||
done < <(echo "$CACHE_JSON" | jq -r --argjson id "$SELECTED" \
|
||||
'[.[] | select(.categories | index($id)) | {slug, name, type}] | sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||
|
||||
SCRIPT_INDEX=$(dialog --colors --backtitle "ProxMenux" --title "Scripts in ${CATEGORY_NAMES[$SELECTED]}" --menu \
|
||||
"Choose a script to execute:" 20 70 14 \
|
||||
"${SCRIPTS[@]}" 3>&1 1>&2 2>&3) || break
|
||||
|
||||
SCRIPT_SELECTED="${INDEX_TO_SLUG[$SCRIPT_INDEX]}"
|
||||
run_script_by_slug "$SCRIPT_SELECTED"
|
||||
|
||||
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; break; }
|
||||
done
|
||||
done
|
||||
@@ -1,128 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - Uninstall Tools Menu for Proxmox
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 02/05/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script provides a dynamic menu for uninstalling optional tools
|
||||
# installed through ProxMenux on Proxmox Virtual Environment (VE).
|
||||
# ==========================================================
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
RETURN_SCRIPT="$REPO_URL/scripts/menus/menu_post_install.sh"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
uninstall_fastfetch() {
|
||||
if ! command -v fastfetch &>/dev/null; then
|
||||
msg_warn "$(translate "Fastfetch is not installed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Uninstalling Fastfetch...")"
|
||||
|
||||
rm -f /usr/local/bin/fastfetch /usr/bin/fastfetch
|
||||
rm -rf "$HOME/.config/fastfetch"
|
||||
rm -rf /usr/local/share/fastfetch
|
||||
sed -i '/fastfetch/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
|
||||
rm -f /etc/profile.d/fastfetch.sh /etc/update-motd.d/99-fastfetch
|
||||
dpkg -r fastfetch &>/dev/null
|
||||
|
||||
msg_ok "$(translate "Fastfetch removed from system")"
|
||||
msg_success "$(translate "You can reinstall it anytime from the post-installation script")"
|
||||
msg_success "$(translate "Press Enter to return...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
|
||||
uninstall_figurine() {
|
||||
if ! command -v figurine &>/dev/null; then
|
||||
msg_warn "$(translate "Figurine is not installed.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "Uninstalling Figurine...")"
|
||||
|
||||
rm -f /usr/local/bin/figurine
|
||||
rm -f /etc/profile.d/figurine.sh
|
||||
sed -i '/figurine/d' "$HOME/.bashrc" "$HOME/.profile" 2>/dev/null
|
||||
|
||||
msg_ok "$(translate "Figurine removed from system")"
|
||||
msg_success "$(translate "You can reinstall it anytime from the post-installation script")"
|
||||
msg_success "$(translate "Press ENTER to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
show_uninstall_menu() {
|
||||
local options=()
|
||||
local index=1
|
||||
declare -A uninstall_map
|
||||
|
||||
if command -v fastfetch >/dev/null 2>&1; then
|
||||
options+=("$index" "$(translate "Uninstall") Fastfetch")
|
||||
uninstall_map[$index]="uninstall_fastfetch"
|
||||
index=$((index + 1))
|
||||
fi
|
||||
|
||||
if command -v figurine >/dev/null 2>&1; then
|
||||
options+=("$index" "$(translate "Uninstall") Figurine")
|
||||
uninstall_map[$index]="uninstall_figurine"
|
||||
index=$((index + 1))
|
||||
fi
|
||||
|
||||
|
||||
if [ ${#options[@]} -eq 0 ]; then
|
||||
whiptail --title "ProxMenux" --msgbox "$(translate "No uninstallable tools detected.")" 10 60
|
||||
return_to_menu
|
||||
fi
|
||||
|
||||
local choice
|
||||
choice=$(whiptail --title "$(translate "Uninstall Tools")" \
|
||||
--menu "$(translate "Select a tool to uninstall:")" 15 60 6 \
|
||||
"${options[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
[ -z "$choice" ] && return_to_menu
|
||||
|
||||
local func="${uninstall_map[$choice]}"
|
||||
if [[ -n "$func" ]]; then
|
||||
"$func"
|
||||
fi
|
||||
|
||||
return_to_menu
|
||||
}
|
||||
|
||||
return_to_menu() {
|
||||
# Descargar temporalmente el script
|
||||
TEMP_SCRIPT=$(mktemp)
|
||||
if curl --fail -s -o "$TEMP_SCRIPT" "$RETURN_SCRIPT"; then
|
||||
bash "$TEMP_SCRIPT"
|
||||
rm -f "$TEMP_SCRIPT"
|
||||
else
|
||||
msg_error "$(translate "Error: Could not return to menu. URL returned 404.")"
|
||||
msg_info2 "$(translate "Please check the menu URL in the script.")"
|
||||
read -r
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
show_uninstall_menu
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 04/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script safely updates your Proxmox VE system and underlying Debian packages
|
||||
# through an interactive and automated process.
|
||||
#
|
||||
# Main features:
|
||||
# - Repairs and optimizes APT repositories (Proxmox & Debian)
|
||||
# - Removes duplicate or conflicting sources
|
||||
# - Switches to the recommended 'no-subscription' Proxmox repository
|
||||
# - Updates all Proxmox and Debian system packages
|
||||
# - Installs essential packages if missing (e.g., zfsutils, chrony)
|
||||
# - Checks for LVM and storage issues and repairs headers if needed
|
||||
# - Removes conflicting time sync packages automatically
|
||||
# - Performs a system cleanup after updating (autoremove, autoclean)
|
||||
# - Provides a summary and prompts for reboot if necessary
|
||||
#
|
||||
# The goal of this script is to simplify and secure the update process for Proxmox,
|
||||
# reduce manual intervention, and prevent common repository and package errors.
|
||||
# ==========================================================
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
apt_upgrade() {
|
||||
local pve_version
|
||||
pve_version=$(pveversion 2>/dev/null | grep -oP 'pve-manager/\K[0-9]+' | head -1)
|
||||
|
||||
if [[ -z "$pve_version" ]]; then
|
||||
msg_error "Unable to detect Proxmox version."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$pve_version" -ge 9 ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "Proxmox system update")"
|
||||
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve.sh")
|
||||
|
||||
else
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "Proxmox system update")"
|
||||
bash <(curl -fsSL "$REPO_URL/scripts/global/update-pve8.sh")
|
||||
|
||||
fi
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
check_reboot() {
|
||||
NECESSARY_REBOOT=0
|
||||
|
||||
if [ -f /var/run/reboot-required ]; then
|
||||
NECESSARY_REBOOT=1
|
||||
fi
|
||||
if grep -q "linux-image" "$log_file" 2>/dev/null; then
|
||||
NECESSARY_REBOOT=1
|
||||
fi
|
||||
|
||||
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
|
||||
if whiptail --title "$(translate "Reboot Required")" \
|
||||
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60; then
|
||||
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
|
||||
msg_warn "$(translate "Rebooting the system...")"
|
||||
reboot
|
||||
else
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
echo -e
|
||||
msg_info2 "$(translate "You can reboot later manually.")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
echo -e
|
||||
msg_ok "$(translate "All changes applied. No reboot required.")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
apt_upgrade
|
||||
check_reboot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenux - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.1
|
||||
# Last Updated: 30/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script provides an interactive system utilities installer with a
|
||||
# comprehensive dialog-based interface for Proxmox VE and Linux systems.
|
||||
# It simplifies the installation and management of essential command-line
|
||||
# tools and utilities commonly used in server environments.
|
||||
#
|
||||
# The script offers both individual utility selection and predefined groups
|
||||
# for different use cases, ensuring administrators can quickly set up their
|
||||
# preferred toolset without manual package management.
|
||||
#
|
||||
# Supported utility categories:
|
||||
# - Basic utilities: grc, htop, tree, curl, wget
|
||||
# - Development tools: git, vim, nano, dos2unix
|
||||
# - Compression tools: zip, unzip, rsync, cabextract
|
||||
# - Network tools: iperf3, nmap, tcpdump, nethogs, iptraf-ng, sshpass
|
||||
# - Analysis tools: jq, ncdu, iotop, btop, iftop
|
||||
# - System tools: mlocate, net-tools, ipset, msr-tools
|
||||
# - Virtualization tools: libguestfs-tools, wimtools, genisoimage, chntpw
|
||||
# - Download tools: axel, aria2
|
||||
#
|
||||
# The script automatically handles package name differences across distributions
|
||||
# and provides detailed feedback on installation success, warnings, and failures.
|
||||
# It includes built-in troubleshooting for common PATH and command availability
|
||||
# issues that may occur after package installation.
|
||||
#
|
||||
|
||||
# Configuration ============================================
|
||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs )"
|
||||
|
||||
# ==========================================================
|
||||
install_system_utils() {
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
|
||||
ensure_repositories() {
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
local need_update=false
|
||||
|
||||
|
||||
if [[ ! -f "$sources_file" ]]; then
|
||||
msg_warn "$(translate "sources.list not found, creating default Debian repository...")"
|
||||
cat > "$sources_file" << EOF
|
||||
# Default Debian ${OS_CODENAME} repository
|
||||
deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware
|
||||
EOF
|
||||
need_update=true
|
||||
else
|
||||
|
||||
if ! grep -q "deb.*${OS_CODENAME}.*main" "$sources_file"; then
|
||||
echo "deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$need_update" == true ]] || ! apt list --installed >/dev/null 2>&1; then
|
||||
msg_info "$(translate "Updating APT package lists...")"
|
||||
apt-get update -o Acquire::AllowInsecureRepositories=true >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_single_package() {
|
||||
local package="$1"
|
||||
local command_name="${2:-$package}"
|
||||
local description="$3"
|
||||
|
||||
msg_info "$(translate "Installing") $package ($description)..."
|
||||
local install_success=false
|
||||
|
||||
if apt install -y "$package" >/dev/null 2>&1; then
|
||||
install_success=true
|
||||
fi
|
||||
cleanup
|
||||
|
||||
if [ "$install_success" = true ]; then
|
||||
hash -r 2>/dev/null
|
||||
sleep 1
|
||||
if command_exists "$command_name"; then
|
||||
msg_ok "$package $(translate "installed correctly and available")"
|
||||
return 0
|
||||
else
|
||||
msg_warn "$package $(translate "installed but command not immediately available")"
|
||||
msg_info2 "$(translate "May need to restart terminal")"
|
||||
return 2
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate "Error installing") $package"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_main_utilities_menu() {
|
||||
local choice
|
||||
choice=$(dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "Utilities Installation Menu")" \
|
||||
--menu "$(translate "Select an option"):" 20 70 12 \
|
||||
"1" "$(translate "Custom selection")" \
|
||||
"2" "$(translate "Install ALL utilities")" \
|
||||
"3" "$(translate "Install basic utilities") (grc, htop, tree, curl, wget)" \
|
||||
"4" "$(translate "Install development tools") (git, vim, nano)" \
|
||||
"5" "$(translate "Install compression tools") (zip, unzip, rsync)" \
|
||||
"6" "$(translate "Install terminal multiplexers") (screen, tmux)" \
|
||||
"7" "$(translate "Install analysis tools") (jq, ncdu, iotop)" \
|
||||
"8" "$(translate "Install network tools") (nethogs, nmap, tcpdump, lsof)" \
|
||||
"9" "$(translate "Verify installations")" \
|
||||
"0" "$(translate "Return to main menu")" 2>&1 >/dev/tty)
|
||||
|
||||
echo "$choice"
|
||||
}
|
||||
|
||||
show_custom_selection() {
|
||||
local utilities=(
|
||||
"axel" "$(translate "Download accelerator")" "OFF"
|
||||
"dos2unix" "$(translate "Convert DOS/Unix text files")" "OFF"
|
||||
"grc" "$(translate "Generic log/command colorizer")" "OFF"
|
||||
"htop" "$(translate "Interactive process viewer")" "OFF"
|
||||
"btop" "$(translate "Modern resource monitor")" "OFF"
|
||||
"iftop" "$(translate "Real-time network usage")" "OFF"
|
||||
"iotop" "$(translate "Monitor disk I/O usage")" "OFF"
|
||||
"iperf3" "$(translate "Network performance testing")" "OFF"
|
||||
"ipset" "$(translate "Manage IP sets")" "OFF"
|
||||
"iptraf-ng" "$(translate "Network monitoring tool")" "OFF"
|
||||
"mlocate" "$(translate "Locate files quickly")" "OFF"
|
||||
"msr-tools" "$(translate "Access CPU MSRs")" "OFF"
|
||||
"net-tools" "$(translate "Legacy networking tools")" "OFF"
|
||||
"sshpass" "$(translate "Non-interactive SSH login")" "OFF"
|
||||
"tmux" "$(translate "Terminal multiplexer")" "OFF"
|
||||
"unzip" "$(translate "Extract ZIP files")" "OFF"
|
||||
"zip" "$(translate "Create ZIP files")" "OFF"
|
||||
"libguestfs-tools" "$(translate "VM disk utilities")" "OFF"
|
||||
"aria2" "$(translate "Multi-source downloader")" "OFF"
|
||||
"cabextract" "$(translate "Extract CAB files")" "OFF"
|
||||
"wimtools" "$(translate "Manage WIM images")" "OFF"
|
||||
"genisoimage" "$(translate "Create ISO images")" "OFF"
|
||||
"chntpw" "$(translate "Edit Windows registry/passwords")" "OFF"
|
||||
)
|
||||
|
||||
local selected
|
||||
selected=$(dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "Select utilities to install")" \
|
||||
--checklist "$(translate "Use SPACE to select/deselect, ENTER to confirm")" \
|
||||
25 80 20 "${utilities[@]}" 2>&1 >/dev/tty)
|
||||
|
||||
echo "$selected"
|
||||
}
|
||||
|
||||
install_utility_group() {
|
||||
local group_name="$1"
|
||||
shift
|
||||
local utilities=("$@")
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "Installing group"): $group_name"
|
||||
|
||||
|
||||
if ! ensure_repositories; then
|
||||
msg_error "$(translate "Failed to configure repositories. Installation aborted.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
local success=0
|
||||
local warning=0
|
||||
|
||||
declare -A package_to_command=(
|
||||
["mlocate"]="locate"
|
||||
["msr-tools"]="rdmsr"
|
||||
["net-tools"]="netstat"
|
||||
["libguestfs-tools"]="virt-filesystems"
|
||||
["aria2"]="aria2c"
|
||||
["wimtools"]="wimlib-imagex"
|
||||
)
|
||||
|
||||
for util_info in "${utilities[@]}"; do
|
||||
IFS=':' read -r package command description <<< "$util_info"
|
||||
local verify_command="${package_to_command[$package]:-$command}"
|
||||
install_single_package "$package" "$verify_command" "$description"
|
||||
local install_result=$?
|
||||
|
||||
case $install_result in
|
||||
0) success=$((success + 1)) ;;
|
||||
1) failed=$((failed + 1)) ;;
|
||||
2) warning=$((warning + 1)) ;;
|
||||
esac
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo
|
||||
msg_info2 "$(translate "Installation summary") - $group_name:"
|
||||
msg_ok "$(translate "Successful"): $success"
|
||||
[ $warning -gt 0 ] && msg_warn "$(translate "With warnings"): $warning"
|
||||
[ $failed -gt 0 ] && msg_error "$(translate "Failed"): $failed"
|
||||
|
||||
dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "Installation Complete")" \
|
||||
--msgbox "$(translate "Group"): $group_name\n$(translate "Successful"): $success\n$(translate "With warnings"): $warning\n$(translate "Failed"): $failed" 10 50
|
||||
}
|
||||
|
||||
install_selected_utilities() {
|
||||
local selected="$1"
|
||||
|
||||
if [ -z "$selected" ]; then
|
||||
dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "No Selection")" \
|
||||
--msgbox "$(translate "No utilities were selected")" 8 40
|
||||
return
|
||||
fi
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "Installing selected utilities")"
|
||||
|
||||
|
||||
if ! ensure_repositories; then
|
||||
msg_error "$(translate "Failed to configure repositories. Installation aborted.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local failed=0
|
||||
local success=0
|
||||
local warning=0
|
||||
local selected_array
|
||||
IFS=' ' read -ra selected_array <<< "$selected"
|
||||
|
||||
declare -A package_to_command=(
|
||||
["mlocate"]="locate"
|
||||
["msr-tools"]="rdmsr"
|
||||
["net-tools"]="netstat"
|
||||
["libguestfs-tools"]="virt-filesystems"
|
||||
["aria2"]="aria2c"
|
||||
["wimtools"]="wimlib-imagex"
|
||||
)
|
||||
|
||||
for util in "${selected_array[@]}"; do
|
||||
util=$(echo "$util" | tr -d '"')
|
||||
local verify_command="${package_to_command[$util]:-$util}"
|
||||
install_single_package "$util" "$verify_command" "$util"
|
||||
local install_result=$?
|
||||
|
||||
case $install_result in
|
||||
0) success=$((success + 1)) ;;
|
||||
1) failed=$((failed + 1)) ;;
|
||||
2) warning=$((warning + 1)) ;;
|
||||
esac
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ -f ~/.bashrc ]; then
|
||||
source ~/.bashrc >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
hash -r 2>/dev/null
|
||||
echo
|
||||
msg_info2 "$(translate "Installation summary"):"
|
||||
msg_ok "$(translate "Successful"): $success"
|
||||
[ $warning -gt 0 ] && msg_warn "$(translate "With warnings"): $warning"
|
||||
[ $failed -gt 0 ] && msg_error "$(translate "Failed"): $failed"
|
||||
|
||||
dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "Installation Complete")" \
|
||||
--msgbox "$(translate "Selected utilities installation completed")\n$(translate "Successful"): $success\n$(translate "With warnings"): $warning\n$(translate "Failed"): $failed" 12 60
|
||||
}
|
||||
|
||||
verify_installations() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Verifying all utilities status")..."
|
||||
|
||||
local utilities=(
|
||||
"axel:Download accelerator"
|
||||
"dialog:Console GUI dialogs"
|
||||
"dos2unix:Convert DOS/Unix text files"
|
||||
"grc:Generic log/command colorizer"
|
||||
"htop:Interactive process viewer"
|
||||
"btop:Modern resource monitor"
|
||||
"iftop:Real-time network usage"
|
||||
"iotop:Monitor disk I/O usage"
|
||||
"iperf3:Network performance testing"
|
||||
"ipset:Manage IP sets"
|
||||
"iptraf-ng:Network monitoring tool"
|
||||
"locate:Locate files quickly"
|
||||
"rdmsr:Access CPU MSRs"
|
||||
"netstat:Legacy networking tools"
|
||||
"sshpass:Non-interactive SSH login"
|
||||
"tmux:Terminal multiplexer"
|
||||
"unzip:Extract ZIP files"
|
||||
"zip:Create ZIP files"
|
||||
"virt-filesystems:VM disk utilities"
|
||||
"aria2c:Multi-source downloader"
|
||||
"cabextract:Extract CAB files"
|
||||
"wimlib-imagex:Manage WIM images"
|
||||
"genisoimage:Create ISO images"
|
||||
"chntpw:Edit Windows registry/passwords"
|
||||
)
|
||||
|
||||
local available=0
|
||||
local missing=0
|
||||
local status_text=""
|
||||
|
||||
for util in "${utilities[@]}"; do
|
||||
IFS=':' read -r cmd desc <<< "$util"
|
||||
if command_exists "$cmd"; then
|
||||
status_text+="\n✓ $cmd - $desc"
|
||||
available=$((available + 1))
|
||||
else
|
||||
status_text+="\n✗ $cmd - $desc"
|
||||
missing=$((missing + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
cleanup
|
||||
|
||||
local summary="$(translate "Total"): $((available + missing))\n$(translate "Available"): $available\n$(translate "Missing"): $missing"
|
||||
|
||||
dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "Utilities Verification")" \
|
||||
--msgbox "$summary$status_text" 25 80
|
||||
}
|
||||
|
||||
# Main menu loop
|
||||
while true; do
|
||||
choice=$(show_main_utilities_menu)
|
||||
case $choice in
|
||||
1)
|
||||
selected=$(show_custom_selection)
|
||||
install_selected_utilities "$selected"
|
||||
;;
|
||||
2)
|
||||
all_utils=(
|
||||
"axel:axel:Download accelerator"
|
||||
"dos2unix:dos2unix:Convert DOS/Unix text files"
|
||||
"grc:grc:Generic log/command colorizer"
|
||||
"htop:htop:Interactive process viewer"
|
||||
"btop:btop:Modern resource monitor"
|
||||
"iftop:iftop:Real-time network usage"
|
||||
"iotop:iotop:Monitor disk I/O usage"
|
||||
"iperf3:iperf3:Network performance testing"
|
||||
"ipset:ipset:Manage IP sets"
|
||||
"iptraf-ng:iptraf-ng:Network monitoring tool"
|
||||
"mlocate:locate:Locate files quickly"
|
||||
"msr-tools:rdmsr:Access CPU MSRs"
|
||||
"net-tools:netstat:Legacy networking tools"
|
||||
"sshpass:sshpass:Non-interactive SSH login"
|
||||
"tmux:tmux:Terminal multiplexer"
|
||||
"unzip:unzip:Extract ZIP files"
|
||||
"zip:zip:Create ZIP files"
|
||||
"libguestfs-tools:virt-filesystems:VM disk utilities"
|
||||
"aria2:aria2c:Multi-source downloader"
|
||||
"cabextract:cabextract:Extract CAB files"
|
||||
"wimtools:wimlib-imagex:Manage WIM images"
|
||||
"genisoimage:genisoimage:Create ISO images"
|
||||
"chntpw:chntpw:Edit Windows registry/passwords"
|
||||
)
|
||||
install_utility_group "$(translate "ALL Utilities")" "${all_utils[@]}"
|
||||
;;
|
||||
3)
|
||||
basic_utils=(
|
||||
"grc:grc:Generic Colouriser"
|
||||
"htop:htop:Process monitor"
|
||||
"tree:tree:Directory structure"
|
||||
"curl:curl:Data transfer"
|
||||
"wget:wget:Web downloader"
|
||||
)
|
||||
install_utility_group "$(translate "Basic Utilities")" "${basic_utils[@]}"
|
||||
;;
|
||||
4)
|
||||
dev_utils=(
|
||||
"git:git:Version control"
|
||||
"vim:vim:Advanced editor"
|
||||
"nano:nano:Simple editor"
|
||||
)
|
||||
install_utility_group "$(translate "Development Tools")" "${dev_utils[@]}"
|
||||
;;
|
||||
5)
|
||||
compress_utils=(
|
||||
"zip:zip:ZIP compressor"
|
||||
"unzip:unzip:ZIP extractor"
|
||||
"rsync:rsync:File synchronizer"
|
||||
)
|
||||
install_utility_group "$(translate "Compression Tools")" "${compress_utils[@]}"
|
||||
;;
|
||||
6)
|
||||
multiplex_utils=(
|
||||
"screen:screen:Terminal multiplexer"
|
||||
"tmux:tmux:Advanced multiplexer"
|
||||
)
|
||||
install_utility_group "$(translate "Terminal Multiplexers")" "${multiplex_utils[@]}"
|
||||
;;
|
||||
7)
|
||||
analysis_utils=(
|
||||
"jq:jq:JSON processor"
|
||||
"ncdu:ncdu:Disk analyzer"
|
||||
"iotop:iotop:I/O monitor"
|
||||
)
|
||||
install_utility_group "$(translate "Analysis Tools")" "${analysis_utils[@]}"
|
||||
;;
|
||||
8)
|
||||
network_utils=(
|
||||
"nethogs:nethogs:Network monitor"
|
||||
"nmap:nmap:Network scanner"
|
||||
"tcpdump:tcpdump:Packet analyzer"
|
||||
"lsof:lsof:Open files"
|
||||
)
|
||||
install_utility_group "$(translate "Network Tools")" "${network_utils[@]}"
|
||||
;;
|
||||
9)
|
||||
verify_installations
|
||||
;;
|
||||
0|"")
|
||||
break
|
||||
;;
|
||||
*)
|
||||
dialog --clear --backtitle "ProxMenux" \
|
||||
--title "$(translate "Invalid Option")" \
|
||||
--msgbox "$(translate "Please select a valid option")" 8 40
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
clear
|
||||
}
|
||||
|
||||
install_system_utils
|
||||
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - UUP Dump ISO Creator Custom
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 30/06/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script is part of the ProxMenux tools for Proxmox VE.
|
||||
# It allows downloading and converting official Windows ISO images
|
||||
# from UUP Dump using a shared link (with ID, pack, and edition).
|
||||
#
|
||||
# Key features:
|
||||
# - Automatically installs and verifies required dependencies (aria2c, cabextract, wimlib-imagex…)
|
||||
# - Downloads the selected Windows edition from UUP Dump using aria2
|
||||
# - Converts the downloaded files into a bootable ISO
|
||||
# - Stores the resulting ISO in the default template path (/var/lib/vz/template/iso)
|
||||
# - Provides a graphical prompt via whiptail for user-friendly usage
|
||||
#
|
||||
# This tool simplifies the creation of official Windows ISOs
|
||||
# for use in virtual machines within Proxmox VE.
|
||||
# ==========================================================
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# ==========================================================
|
||||
detect_iso_dir() {
|
||||
for store in $(pvesm status -content iso | awk 'NR>1 {print $1}'); do
|
||||
for ext in iso img; do
|
||||
volid=$(pvesm list "$store" --content iso | awk -v ext="$ext" 'NR>1 && $2 ~ ext {print $1; exit}')
|
||||
if [[ -n "$volid" ]]; then
|
||||
path=$(pvesm path "$volid" 2>/dev/null)
|
||||
dir=$(dirname "$path")
|
||||
[[ -d "$dir" ]] && echo "$dir" && return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [[ -d /var/lib/vz/template/iso ]]; then
|
||||
echo "/var/lib/vz/template/iso"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
function get_destination_path() {
|
||||
local default_path="$1"
|
||||
local user_path=""
|
||||
|
||||
while true; do
|
||||
|
||||
######################################
|
||||
|
||||
user_path=$(dialog --backtitle "ProxMenux" --inputbox "$(translate "Enter destination path for ISO file")" 10 80 "$default_path" 3>&1 1>&2 2>&3)
|
||||
|
||||
######################################
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ -z "$user_path" ]]; then
|
||||
user_path="$default_path"
|
||||
fi
|
||||
|
||||
|
||||
if [[ ! -d "$user_path" ]]; then
|
||||
if mkdir -p "$user_path" 2>/dev/null; then
|
||||
#msg_ok "$(translate "Directory created successfully:") $user_path"
|
||||
echo "$user_path"
|
||||
return 0
|
||||
else
|
||||
dialog --backtitle "ProxMenux" --msgbox "$(translate "Error: Cannot create directory") '$user_path'. $(translate "Please check permissions and try again.")" 8 60
|
||||
|
||||
continue
|
||||
fi
|
||||
else
|
||||
|
||||
if [[ -w "$user_path" ]]; then
|
||||
echo "$user_path"
|
||||
return 0
|
||||
else
|
||||
dialog --backtitle "ProxMenux" --msgbox "$(translate "Error: No write permissions in directory") '$user_path'. $(translate "Please choose another path.")" 8 60
|
||||
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function run_uupdump_creator() {
|
||||
local DEPS=(curl aria2 cabextract wimtools genisoimage chntpw)
|
||||
local CMDS=(curl aria2c cabextract wimlib-imagex genisoimage chntpw)
|
||||
local MISSING=()
|
||||
local FAILED=()
|
||||
|
||||
|
||||
for i in "${!CMDS[@]}"; do
|
||||
if ! command -v "${CMDS[$i]}" &>/dev/null; then
|
||||
MISSING+=("${DEPS[$i]}")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
msg_info "$(translate "Installing dependencies: ${MISSING[*]}")"
|
||||
apt-get update -qq >/dev/null 2>&1
|
||||
msg_ok "$(translate "All dependencies installed and verified.")"
|
||||
if ! apt-get install -y "${MISSING[@]}" >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Failed to install: ${MISSING[*]}")"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
for i in "${!CMDS[@]}"; do
|
||||
if ! command -v "${CMDS[$i]}" &>/dev/null; then
|
||||
FAILED+=("${CMDS[$i]}")
|
||||
fi
|
||||
done
|
||||
|
||||
#if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
# msg_ok "$(translate "All dependencies installed and verified.")"
|
||||
#else
|
||||
# msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
|
||||
# exit 1
|
||||
#fi
|
||||
|
||||
|
||||
ISO_DIR=$(detect_iso_dir)
|
||||
if [[ -z "$ISO_DIR" ]]; then
|
||||
msg_error "$(translate "Could not determine a valid ISO storage directory.")"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
######################################
|
||||
|
||||
DEFAULT_TMP="/root/uup-temp"
|
||||
USER_INPUT=$(dialog --backtitle "ProxMenux" --inputbox "Enter temporary folder path (default: $DEFAULT_TMP):" 10 60 "$DEFAULT_TMP" 3>&1 1>&2 2>&3)
|
||||
|
||||
######################################
|
||||
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
if [[ $? -ne 0 || -z "$USER_INPUT" ]]; then
|
||||
USER_INPUT="$DEFAULT_TMP"
|
||||
fi
|
||||
|
||||
|
||||
if [[ "$USER_INPUT" == "$DEFAULT_TMP" ]]; then
|
||||
TMP_DIR="$USER_INPUT"
|
||||
CLEAN_ALL=true
|
||||
else
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
RANDOM_ID=$(head /dev/urandom | tr -dc a-z0-9 | head -c 4)
|
||||
TMP_DIR="${USER_INPUT%/}/uup-session-${TIMESTAMP}-${RANDOM_ID}"
|
||||
CLEAN_ALL=false
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_DIR" || {
|
||||
msg_error "$(translate "Failed to create temporary directory:") $TMP_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
OUT_DIR=$(get_destination_path "$ISO_DIR")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
######################################
|
||||
|
||||
UUP_URL=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
|
||||
|
||||
######################################
|
||||
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
if [[ $? -ne 0 || -z "$UUP_URL" ]]; then
|
||||
msg_warn "$(translate "Cancelled by user or empty URL.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ ! "$UUP_URL" =~ id=.+\&pack=.+\&edition=.+ ]]; then
|
||||
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
|
||||
sleep 2
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
BUILD_ID=$(echo "$UUP_URL" | grep -oP 'id=\K[^&]+')
|
||||
LANG=$(echo "$UUP_URL" | grep -oP 'pack=\K[^&]+')
|
||||
EDITION=$(echo "$UUP_URL" | grep -oP 'edition=\K[^&]+')
|
||||
ARCH="amd64"
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
echo -e "\n${BGN}=============== UUP Dump Creator ===============${CL}"
|
||||
echo -e " ${BGN}🆔 ID:${CL} ${DGN}$BUILD_ID${CL}"
|
||||
echo -e " ${BGN}🌐 Language:${CL} ${DGN}$LANG${CL}"
|
||||
echo -e " ${BGN}💿 Edition:${CL} ${DGN}$EDITION${CL}"
|
||||
echo -e " ${BGN}🖥️ Architecture:${CL} ${DGN}$ARCH${CL}"
|
||||
echo -e " ${BGN}📁 Destination:${CL} ${DGN}$OUT_DIR${CL}"
|
||||
echo -e "${BGN}===============================================${CL}\n"
|
||||
|
||||
|
||||
CONVERTER="$TMP_DIR/converter"
|
||||
if [[ ! -f "$CONVERTER/convert.sh" ]]; then
|
||||
echo "📦 $(translate "Downloading UUP converter...")"
|
||||
mkdir -p "$CONVERTER"
|
||||
cd "$CONVERTER" || exit 1
|
||||
wget -q https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz -O converter.tar.gz
|
||||
tar -xzf converter.tar.gz --strip-components=1
|
||||
chmod +x convert.sh
|
||||
cd "$TMP_DIR" || exit 1
|
||||
fi
|
||||
|
||||
|
||||
cat > uup_download_linux.sh <<EOF
|
||||
#!/bin/bash
|
||||
mkdir -p files
|
||||
echo "https://git.uupdump.net/uup-dump/converter/archive/refs/heads/master.tar.gz" > files/converter_multi
|
||||
|
||||
for prog in aria2c cabextract wimlib-imagex chntpw; do
|
||||
which \$prog &>/dev/null || { echo "\$prog not found."; exit 1; }
|
||||
done
|
||||
|
||||
which genisoimage &>/dev/null || which mkisofs &>/dev/null || { echo "genisoimage/mkisofs not found."; exit 1; }
|
||||
|
||||
destDir="UUPs"
|
||||
tempScript="aria2_script.\$RANDOM.txt"
|
||||
|
||||
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
|
||||
-x16 -s16 -j2 --allow-overwrite=true --auto-file-renaming=false -d"files" -i"files/converter_multi" || exit 1
|
||||
|
||||
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
|
||||
-o"\$tempScript" --allow-overwrite=true --auto-file-renaming=false \\
|
||||
"https://uupdump.net/get.php?id=$BUILD_ID&pack=$LANG&edition=$EDITION&aria2=2" || exit 1
|
||||
|
||||
grep '#UUPDUMP_ERROR:' "\$tempScript" && { echo "❌ Error generating UUP download list."; exit 1; }
|
||||
|
||||
aria2c --no-conf --console-log-level=warn --log-level=info --log="aria2_download.log" \\
|
||||
-x16 -s16 -j5 -c -R -d"\$destDir" -i"\$tempScript" || exit 1
|
||||
EOF
|
||||
|
||||
chmod +x uup_download_linux.sh
|
||||
|
||||
|
||||
./uup_download_linux.sh
|
||||
|
||||
|
||||
UUP_FOLDER=$(find "$TMP_DIR" -type d -name "UUPs" | head -n1)
|
||||
[[ -z "$UUP_FOLDER" ]] && msg_error "$(translate "No UUP folder found.")" && exit 1
|
||||
|
||||
echo -e "\n${GN}=======================================${CL}"
|
||||
echo -e " 💿 ${GN}Starting ISO conversion...${CL}"
|
||||
echo -e "${GN}=======================================${CL}\n"
|
||||
|
||||
|
||||
"$CONVERTER/convert.sh" wim "$UUP_FOLDER" 1
|
||||
|
||||
|
||||
ISO_FILE=$(find "$TMP_DIR" "$CONVERTER" "$UUP_FOLDER" -maxdepth 1 -iname "*.iso" | head -n1)
|
||||
|
||||
if [[ -f "$ISO_FILE" ]]; then
|
||||
mv "$ISO_FILE" "$OUT_DIR/"
|
||||
msg_ok "$(translate "ISO created successfully:") $OUT_DIR/$(basename "$ISO_FILE")"
|
||||
msg_ok "$(translate "Cleaning temporary files...")"
|
||||
|
||||
if [[ "$CLEAN_ALL" == true ]]; then
|
||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||
else
|
||||
[[ -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
|
||||
[[ -d "$CONVERTER" ]] && rm -rf "$CONVERTER"
|
||||
fi
|
||||
|
||||
export OS_TYPE="windows"
|
||||
export LANGUAGE=C
|
||||
export LANG=C
|
||||
export LC_ALL=C
|
||||
load_language
|
||||
initialize_cache
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
else
|
||||
msg_warn "$(translate "No ISO was generated.")"
|
||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||
export LANGUAGE=C
|
||||
export LANG=C
|
||||
export LC_ALL=C
|
||||
load_language
|
||||
initialize_cache
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_uupdump_creator
|
||||
+32
-13
@@ -193,24 +193,36 @@ msg_error() {
|
||||
|
||||
# Initialize cache
|
||||
initialize_cache() {
|
||||
if [ ! -f "$CACHE_FILE" ]; then
|
||||
mkdir -p "$(dirname "$CACHE_FILE")"
|
||||
echo "{}" > "$CACHE_FILE"
|
||||
if [[ "$LANGUAGE" != "en" ]]; then
|
||||
if [ ! -f "$CACHE_FILE" ]; then
|
||||
mkdir -p "$(dirname "$CACHE_FILE")"
|
||||
echo "{}" > "$CACHE_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Load language
|
||||
load_language() {
|
||||
LANGUAGE="en"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
LANGUAGE=$(jq -r '.language' "$CONFIG_FILE")
|
||||
lang_candidate=$(jq -r '.language // empty' "$CONFIG_FILE" 2>/dev/null)
|
||||
if [[ -n "$lang_candidate" && "$lang_candidate" != "null" ]]; then
|
||||
LANGUAGE="$lang_candidate"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Translation with cache and predefined terms
|
||||
|
||||
|
||||
########################################################
|
||||
|
||||
|
||||
|
||||
translate() {
|
||||
local text="$1"
|
||||
local dest_lang="$LANGUAGE"
|
||||
|
||||
# If the language is English, return the original text without translating or caching
|
||||
|
||||
if [ "$dest_lang" = "en" ]; then
|
||||
echo "$text"
|
||||
return
|
||||
@@ -237,21 +249,23 @@ translate() {
|
||||
from googletrans import Translator
|
||||
import sys, json, re
|
||||
|
||||
def translate_text(text, dest_lang):
|
||||
def translate_text(text, dest_lang, context):
|
||||
translator = Translator()
|
||||
context = '$TRANSLATION_CONTEXT'
|
||||
try:
|
||||
full_text = context + ' ' + text
|
||||
result = translator.translate(full_text, dest=dest_lang).text
|
||||
# Remove context and any leading/trailing whitespace
|
||||
translated = re.sub(r'^.*?(Translate:|Traducir:|Traduire:|Übersetzen:|Tradurre:|Traduzir:|翻译:|翻訳:)', '', result, flags=re.IGNORECASE | re.DOTALL).strip()
|
||||
translated = re.sub(r'^.*?(Context:|Contexto:|Contexte:|Kontext:|Contesto:|上下文:|コンテキスト:).*?:', '', translated, flags=re.IGNORECASE | re.DOTALL).strip()
|
||||
return json.dumps({'success': True, 'text': translated})
|
||||
print(json.dumps({'success': True, 'text': translated}))
|
||||
except Exception as e:
|
||||
return json.dumps({'success': False, 'error': str(e)})
|
||||
print(json.dumps({'success': False, 'error': str(e)}))
|
||||
|
||||
print(translate_text('$text', '$dest_lang'))
|
||||
")
|
||||
translate_text(
|
||||
json.loads(sys.argv[1]),
|
||||
sys.argv[2],
|
||||
json.loads(sys.argv[3])
|
||||
)
|
||||
" "$(jq -Rn --arg t "$text" '$t')" "$dest_lang" "$(jq -Rn --arg ctx "$TRANSLATION_CONTEXT" '$ctx')")
|
||||
deactivate
|
||||
|
||||
local translation_result=$(echo "$translated" | jq -r '.')
|
||||
@@ -281,6 +295,11 @@ print(translate_text('$text', '$dest_lang'))
|
||||
|
||||
|
||||
|
||||
########################################################
|
||||
|
||||
|
||||
|
||||
|
||||
show_proxmenux_logo() {
|
||||
clear
|
||||
|
||||
|
||||
@@ -101,8 +101,12 @@ function select_linux_iso() {
|
||||
|
||||
function select_linux_iso_official() {
|
||||
DISTROS=(
|
||||
"Ubuntu 25.04|Desktop|ProxMenux|https://releases.ubuntu.com/25.04/ubuntu-25.04-desktop-amd64.iso"
|
||||
"Ubuntu 24.04|Desktop|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.2-desktop-amd64.iso"
|
||||
"Ubuntu 22.04|Desktop|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.5-desktop-amd64.iso"
|
||||
"Ubuntu 20.04|Desktop|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso"
|
||||
"Ubuntu 25.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/25.04/ubuntu-25.04-live-server-amd64.iso"
|
||||
"Ubuntu 24.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/24.04/ubuntu-24.04.2-live-server-amd64.iso"
|
||||
"Ubuntu 22.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso"
|
||||
"Ubuntu 20.04 Server|CLI|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso"
|
||||
"Debian 12|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-12.10.0-amd64-DVD-1.iso"
|
||||
@@ -110,6 +114,7 @@ function select_linux_iso_official() {
|
||||
"Debian 12 Netinst|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.10.0-amd64-netinst.iso"
|
||||
"Debian 11 Netinst|CLI|ProxMenux|https://cdimage.debian.org/cdimage/archive/11.11.0/amd64/iso-cd/debian-11.11.0-amd64-netinst.iso"
|
||||
"Fedora Workstation 42|Desktop|ProxMenux|https://download.fedoraproject.org/pub/fedora/linux/releases/42/Workstation/x86_64/iso/Fedora-Workstation-Live-42-1.1.x86_64.iso"
|
||||
"Arch Linux|CLI|ProxMenux|https://geo.mirror.pkgbuild.com/iso/2025.07.01/archlinux-2025.07.01-x86_64.iso"
|
||||
"Rocky Linux 9.5|Desktop|ProxMenux|https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.5-x86_64-dvd.iso"
|
||||
"Linux Mint 22.1|Desktop|ProxMenux|https://mirrors.edge.kernel.org/linuxmint/stable/22.1/linuxmint-22.1-cinnamon-64bit.iso"
|
||||
"openSUSE Leap 15.6|Desktop|ProxMenux|https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-DVD-x86_64-Media.iso"
|
||||
@@ -164,7 +169,8 @@ function select_linux_cloudinit() {
|
||||
"3" "Ubuntu 22.04 (Cloud-Init automated) │ Helper Scripts"
|
||||
"4" "Ubuntu 24.04 (Cloud-Init automated) │ Helper Scripts"
|
||||
"5" "Ubuntu 24.10 (Cloud-Init automated) │ Helper Scripts"
|
||||
"6" "$(translate "Return to Main Menu")"
|
||||
"6" "Ubuntu 25.04 (Cloud-Init automated) │ Helper Scripts"
|
||||
"7" "$(translate "Return to Main Menu")"
|
||||
)
|
||||
|
||||
local script_selection
|
||||
@@ -191,6 +197,12 @@ function select_linux_cloudinit() {
|
||||
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2410-vm.sh")
|
||||
;;
|
||||
6)
|
||||
bash <(curl -s "https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2504-vm.sh")
|
||||
echo -e
|
||||
echo -e "after installation, checkout:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/272"
|
||||
echo -e
|
||||
;;
|
||||
7)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
@@ -241,40 +253,53 @@ function select_linux_custom_iso() {
|
||||
|
||||
|
||||
function select_linux_other_scripts() {
|
||||
local OTHER_OPTIONS=(
|
||||
"1" "Home Assistant OS VM (HAOS) │ Helper Scripts"
|
||||
"2" "Docker VM (Debian + SSH + Docker) │ Helper Scripts"
|
||||
"3" "$(translate "Return to Main Menu")"
|
||||
)
|
||||
local OTHER_OPTIONS=(
|
||||
"1" "Home Assistant OS VM (HAOS) │ Helper Scripts"
|
||||
"2" "Docker VM (Debian + SSH + Docker) │ Helper Scripts"
|
||||
"3" "Nextcloud │ Helper Scripts"
|
||||
"4" "$(translate "Return to Main Menu")"
|
||||
)
|
||||
|
||||
local choice
|
||||
choice=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Other Prebuilt Linux VMs")" \
|
||||
--menu "\n$(translate "Select one of the ready-to-run Linux VMs:")" 18 70 10 \
|
||||
"${OTHER_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
local choice
|
||||
choice=$(dialog --backtitle "ProxMenux" \
|
||||
--title "$(translate "Other Prebuilt Linux VMs")" \
|
||||
--menu "\n$(translate "Select one of the ready-to-run Linux VMs:")" 18 70 10 \
|
||||
"${OTHER_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 || "$choice" == "3" ]]; then
|
||||
return 1
|
||||
fi
|
||||
if [[ $? -ne 0 || "$choice" == "4" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "$choice" in
|
||||
1)
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/haos-vm.sh)"
|
||||
;;
|
||||
2)
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/docker-vm.sh)"
|
||||
whiptail --title "Docker VM Info" \
|
||||
--msgbox "$(translate "Default Login Credentials:\n\nUsername: root\nPassword: docker")" 12 50
|
||||
;;
|
||||
esac
|
||||
case "$choice" in
|
||||
1)
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/haos-vm.sh)"
|
||||
;;
|
||||
2)
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/docker-vm.sh)"
|
||||
echo -e
|
||||
echo -e "${TAB}$(translate "Default Login Credentials:")"
|
||||
echo -e "${TAB}Username: root"
|
||||
echo -e "${TAB}Password: docker"
|
||||
echo -e
|
||||
;;
|
||||
3)
|
||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/nextcloud-vm.sh)"
|
||||
echo -e
|
||||
echo -e "${TAB}$(translate "You can use the following credentials to login to the Nextcloud vm:")"
|
||||
echo -e "${TAB}Username: admin"
|
||||
echo -e "${TAB}$(translate "This VM requires extra installation steps, see install guide at:\nhttps://github.com/community-scripts/ProxmoxVE/discussions/144")"
|
||||
echo -e
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
whiptail --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE")" 15 70
|
||||
whiptail --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE")" 15 70
|
||||
|
||||
return 1
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -44,9 +44,11 @@ function select_nas_iso() {
|
||||
"2" "TrueNAS SCALE VM (Fangtooth)"
|
||||
"3" "TrueNAS CORE VM (FreeBSD based)"
|
||||
"4" "OpenMediaVault VM (Debian based)"
|
||||
"5" "Rockstor VM (openSUSE based)"
|
||||
"6" "ZimaOS VM (R0GGER proxmox-zimaos)"
|
||||
"7" "$(translate "Return to Main Menu")"
|
||||
"5" "XigmaNAS VM (FreeBSD based)"
|
||||
"6" "Rockstor VM (openSUSE based)"
|
||||
"7" "ZimaOS VM (R0GGER proxmox-zimaos)"
|
||||
"8" "Umbrel OS VM (Helper Scripts)"
|
||||
"9" "$(translate "Return to Main Menu")"
|
||||
)
|
||||
|
||||
local NAS_TYPE
|
||||
@@ -87,18 +89,26 @@ function select_nas_iso() {
|
||||
HN="OpenMediaVault"
|
||||
;;
|
||||
5)
|
||||
ISO_NAME="XigmaNAS-13.3.0.5"
|
||||
ISO_URL="https://sourceforge.net/projects/xigmanas/files/XigmaNAS-13.3.0.5/13.3.0.5.10153/XigmaNAS-x64-LiveCD-13.3.0.5.10153.iso/download"
|
||||
ISO_FILE="XigmaNAS-x64-LiveCD-13.3.0.5.10153.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="XigmaNAS"
|
||||
;;
|
||||
6)
|
||||
ISO_NAME="Rockstor"
|
||||
ISO_URL="https://rockstor.com/downloads/installer/leap/15.6/x86_64/Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
ISO_FILE="Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
HN="Rockstor"
|
||||
;;
|
||||
6)
|
||||
7)
|
||||
HN="ZimaOS-VM"
|
||||
if ! confirm_vm_creation; then
|
||||
return 1
|
||||
fi
|
||||
bash -c "$(wget -qLO - https://raw.githubusercontent.com/R0GGER/proxmox-zimaos/refs/heads/main/zimaos_zimacube.sh)"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
@@ -107,8 +117,25 @@ function select_nas_iso() {
|
||||
|
||||
return 1
|
||||
;;
|
||||
8)
|
||||
HN="Umbrel OS"
|
||||
bash -c "$(wget -qLO - https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/umbrel-os-vm.sh)"
|
||||
echo -e
|
||||
echo -e "${TAB}$(translate "Default Login Credentials:")"
|
||||
echo -e "${TAB}Username: umbrel"
|
||||
echo -e "${TAB}Password: umbrel"
|
||||
echo -e "${TAB}$(translate "After logging in, run: ip a to obtain the IP address.\nThen, enter that IP address in your web browser like this:\n http://IP_ADDRESS\n\nThis will open the Umbral OS dashboard.")"
|
||||
echo -e
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
whiptail --title "Proxmox VE - Umbrel OS" \
|
||||
--msgbox "$(translate "Umbrel OS installer script by Helper Scripts\n\nVisit the GitHub repo to learn more, contribute, or support the project:\n\nhttps://community-scripts.github.io/ProxmoxVE/scripts?id=umbrel-os-vm")" 15 70
|
||||
|
||||
7)
|
||||
return 1
|
||||
;;
|
||||
|
||||
9)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -36,9 +36,6 @@ fi
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
@@ -78,12 +75,15 @@ function run_uupdump_creator() {
|
||||
done
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_info "$(translate "Installing dependencies: ${MISSING[*]}")"
|
||||
apt-get update -qq >/dev/null 2>&1
|
||||
if ! apt-get install -y "${MISSING[@]}" >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Failed to install: ${MISSING[*]}")"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "$(translate "All dependencies installed and verified.")"
|
||||
fi
|
||||
|
||||
for i in "${!CMDS[@]}"; do
|
||||
@@ -92,37 +92,84 @@ function run_uupdump_creator() {
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
msg_ok "$(translate "All dependencies installed and verified.")"
|
||||
else
|
||||
msg_error "$(translate "Missing commands after installation: ${FAILED[*]}")"
|
||||
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "Missing commands after installation:") ${FAILED[*]}"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
ISO_DIR=$(detect_iso_dir)
|
||||
if [[ -z "$ISO_DIR" ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "Could not determine a valid ISO storage directory.")"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
TMP_DIR="/root/uup-temp"
|
||||
OUT_DIR="$ISO_DIR"
|
||||
CONVERTER="/root/uup-converter"
|
||||
|
||||
mkdir -p "$TMP_DIR" "$OUT_DIR"
|
||||
cd "$TMP_DIR" || exit 1
|
||||
|
||||
DEFAULT_BASE="/root/uup-temp"
|
||||
BASE_INPUT=$(dialog --clear --inputbox "$(translate "Enter base folder for temporary files and converter (default:") $DEFAULT_BASE):" 10 60 "$DEFAULT_BASE" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 || -z "$BASE_INPUT" ]]; then
|
||||
BASE_INPUT="$DEFAULT_BASE"
|
||||
fi
|
||||
|
||||
|
||||
BASE_CLEAN="$(echo "$BASE_INPUT" | sed 's:[[:space:]]*$::' | sed 's:/*$::')"
|
||||
|
||||
|
||||
if [[ ! -d "$BASE_CLEAN" ]]; then
|
||||
if ! mkdir -p "$BASE_CLEAN"; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "The selected base folder does not exist and could not be created:") $BASE_CLEAN"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ ! -w "$BASE_CLEAN" ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "No write permissions on:") $BASE_CLEAN"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
TMP_DIR="$BASE_CLEAN/uup-temp"
|
||||
OUT_DIR="$ISO_DIR"
|
||||
CONVERTER="$BASE_CLEAN/uup-converter"
|
||||
|
||||
if ! mkdir -p "$TMP_DIR"; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "Could not create temporary directory:") $TMP_DIR"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! mkdir -p "$CONVERTER"; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "Could not create converter directory:") $CONVERTER"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$TMP_DIR" || { msg_error "$(translate "Failed to access:") $TMP_DIR"; exit 1; }
|
||||
|
||||
|
||||
|
||||
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
|
||||
if [[ $? -ne 0 || -z "$UUP_URL" ]]; then
|
||||
msg_warn "$(translate "Cancelled by user or empty URL.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! "$UUP_URL" =~ id=.+\&pack=.+\&edition=.+ ]]; then
|
||||
show_proxmenux_logo
|
||||
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
|
||||
sleep 2
|
||||
return 1
|
||||
@@ -134,6 +181,8 @@ LANG=$(echo "$UUP_URL" | grep -oP 'pack=\K[^&]+')
|
||||
EDITION=$(echo "$UUP_URL" | grep -oP 'edition=\K[^&]+')
|
||||
ARCH="amd64"
|
||||
|
||||
show_proxmenux_logo
|
||||
echo -e
|
||||
echo -e "\n${BGN}=============== UUP Dump Creator ===============${CL}"
|
||||
echo -e " ${BGN}🆔 ID:${CL} ${DGN}$BUILD_ID${CL}"
|
||||
echo -e " ${BGN}🌐 Language:${CL} ${DGN}$LANG${CL}"
|
||||
@@ -207,7 +256,12 @@ if [[ -f "$ISO_FILE" ]]; then
|
||||
|
||||
|
||||
msg_ok "$(translate "Cleaning temporary files...")"
|
||||
if [[ "$CLEAN_ALL" == true ]]; then
|
||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||
else
|
||||
[[ -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
|
||||
[[ -d "$CONVERTER" ]] && rm -rf "$CONVERTER"
|
||||
fi
|
||||
|
||||
export OS_TYPE="windows"
|
||||
export LANGUAGE=C
|
||||
@@ -218,15 +272,15 @@ if [[ -f "$ISO_FILE" ]]; then
|
||||
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
|
||||
else
|
||||
msg_warn "$(translate "No ISO was generated.")"
|
||||
|
||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||
export LANGUAGE=C
|
||||
export LANG=C
|
||||
export LC_ALL=C
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
return 1
|
||||
|
||||
@@ -95,6 +95,10 @@ function load_default_vm_config() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$os_type" == "1" && "$HN" == "OpenMediaVault" ]]; then
|
||||
BIOS_TYPE=" -bios seabios"
|
||||
fi
|
||||
|
||||
|
||||
[[ -z "$CORE_COUNT" ]] && CORE_COUNT="2"
|
||||
[[ -z "$RAM_SIZE" ]] && RAM_SIZE="4096"
|
||||
@@ -166,18 +170,37 @@ function configure_vm_advanced() {
|
||||
[[ "$MACHINE_TYPE" == "q35" ]] && MACHINE=" -machine q35" && FORMAT="" || MACHINE="" && FORMAT=",efitype=4m"
|
||||
|
||||
# BIOS
|
||||
BIOS=$(whiptail --backtitle "ProxMenux" --title "$(translate "BIOS Type")" \
|
||||
--radiolist "$(translate "Choose BIOS type")" 10 60 2 \
|
||||
"ovmf" "UEFI (OVMF)" ON \
|
||||
"seabios" "Legacy BIOS (SeaBIOS)" OFF 3>&1 1>&2 2>&3) || return 1
|
||||
BIOS_TYPE=" -bios $BIOS"
|
||||
if [[ "$HN" == "OpenMediaVault" ]]; then
|
||||
BIOS_TYPE=" -bios seabios"
|
||||
else
|
||||
BIOS=$(whiptail --backtitle "ProxMenux" --title "$(translate "BIOS Type")" \
|
||||
--radiolist "$(translate "Choose BIOS type")" 10 60 2 \
|
||||
"ovmf" "UEFI (OVMF)" ON \
|
||||
"seabios" "Legacy BIOS (SeaBIOS)" OFF 3>&1 1>&2 2>&3) || return 1
|
||||
BIOS_TYPE=" -bios $BIOS"
|
||||
fi
|
||||
|
||||
# CPU Type
|
||||
# CPU_CHOICE=$(whiptail --backtitle "ProxMenux" --title "$(translate "CPU Model")" \
|
||||
# --radiolist "$(translate "Select CPU model")" 10 60 2 \
|
||||
# "host" "Host (recommended)" ON \
|
||||
# "kvm64" "Generic KVM64" OFF 3>&1 1>&2 2>&3) || return 1
|
||||
# [[ "$CPU_CHOICE" == "host" ]] && CPU_TYPE=" -cpu host" || CPU_TYPE=" -cpu kvm64"
|
||||
|
||||
CPU_CHOICE=$(whiptail --backtitle "ProxMenux" --title "$(translate "CPU Model")" \
|
||||
--radiolist "$(translate "Select CPU model")" 10 60 2 \
|
||||
"host" "Host (recommended)" ON \
|
||||
"kvm64" "Generic KVM64" OFF 3>&1 1>&2 2>&3) || return 1
|
||||
[[ "$CPU_CHOICE" == "host" ]] && CPU_TYPE=" -cpu host" || CPU_TYPE=" -cpu kvm64"
|
||||
--radiolist "$(translate "Select CPU model")" 17 70 11 \
|
||||
"host" "Host (recommended)" ON \
|
||||
"kvm64" "Generic KVM64" OFF \
|
||||
"kvm32" "Generic KVM32" OFF \
|
||||
"qemu64" "QEMU 64-bit CPU" OFF \
|
||||
"qemu32" "QEMU 32-bit CPU" OFF \
|
||||
"max" "Expose all QEMU CPU features" OFF \
|
||||
"x86-64-v2" "Nehalem-class (x86-64-v2)" OFF \
|
||||
"x86-64-v2-AES" "Same as v2 but with AES" OFF \
|
||||
"x86-64-v3" "Haswell-class (x86-64-v3)" OFF \
|
||||
"x86-64-v4" "Skylake-class (x86-64-v4)" OFF 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
CPU_TYPE=" -cpu $CPU_CHOICE"
|
||||
|
||||
# Core Count
|
||||
CORE_COUNT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Number of CPU cores (default: 2)")" \
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.1.2
|
||||
1.1.4
|
||||
@@ -11,21 +11,17 @@ export default function CoralTPULXC() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-3xl font-bold mb-6">Enable Coral TPU in an LXC</h1>
|
||||
|
||||
|
||||
<p className="mb-4">
|
||||
This guide explains how to configure Google Coral TPU support for LXC containers in Proxmox VE using <strong>ProxMenux</strong>.
|
||||
Coral TPU provides dedicated AI acceleration, improving inference performance for machine learning applications. It is particularly useful for video surveillance applications with real-time video analysis, such as <a href='https://frigate.video/' target='_blank' className='text-blue-600 hover:underline'>Frigate</a> or <a href='https://www.ispyconnect.com' target='_blank' className='text-blue-600 hover:underline'>Agent DVR</a> or <a href='https://blueirissoftware.com/' target='_blank' className='text-blue-600 hover:underline'>Blue Iris</a> using <a href='https://www.codeproject.com/ai/index.aspx' target='_blank' className='text-blue-600 hover:underline'>CodeProject.AI</a>.
|
||||
</p>
|
||||
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
|
||||
<p className="mb-4">The script automates the following steps:</p>
|
||||
<ol className="list-decimal pl-6 space-y-2 mb-6">
|
||||
<li>Allows selection of an existing LXC container.</li>
|
||||
<li>Ensures the container is privileged for hardware access.</li>
|
||||
<li>Configures LXC parameters for Coral TPU and Intel iGPU.</li>
|
||||
<li>Installs required drivers and dependencies inside the container.</li>
|
||||
</ol>
|
||||
|
||||
<p className="mb-4">The script automates the complete configuration of Coral TPU support in LXC containers, including USB and M.2 variants. It applies Proxmox-specific container settings, manages device passthrough permissions, and installs required drivers both on the host and inside the container.</p>
|
||||
<p className="mb-4">The USB variant uses a persistent mapping based on <code>/dev/coral</code> via <code>udev</code> rules, avoiding reliance on dynamic USB paths like <code>/dev/bus/usb/*</code>. This ensures consistent device assignment across reboots and hardware reordering.</p>
|
||||
<p className="mb-4">The M.2 version is detected automatically and configured only if present.</p>
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
|
||||
<Steps>
|
||||
<Steps.Step title="Select an LXC Container">
|
||||
@@ -39,13 +35,47 @@ export default function CoralTPULXC() {
|
||||
<li>Sets device permissions for TPU and iGPU.</li>
|
||||
<li>Configures proper device mounts.</li>
|
||||
</ul>
|
||||
<CopyableCode
|
||||
code={`# Coral USB persistent passthrough example:
|
||||
/etc/udev/rules.d/99-coral-usb.rules
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", SYMLINK+="coral", MODE="0666"
|
||||
|
||||
# LXC config:
|
||||
lxc.cgroup2.devices.allow: c 189:* rwm
|
||||
lxc.mount.entry: /dev/coral dev/coral none bind,optional,create=file`}
|
||||
className="my-4"
|
||||
/>
|
||||
<CopyableCode
|
||||
code={`# Coral M.2 passthrough example (automatically added if detected):
|
||||
lxc.cgroup2.devices.allow: c 245:0 rwm
|
||||
lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file`}
|
||||
className="my-4"
|
||||
/>
|
||||
</Steps.Step>
|
||||
<Steps.Step title="Install Required Drivers">
|
||||
<p>The script installs the necessary components inside the container:</p>
|
||||
<ul className="list-disc pl-6 space-y-1 mt-2">
|
||||
<li>GPU drivers (va-driver-all, intel-opencl-icd).</li>
|
||||
<li>Coral TPU dependencies (Python, GPG keys, repository setup).</li>
|
||||
<li>Coral TPU drivers (USB and M.2 support).</li>
|
||||
<li>GPU drivers:</li>
|
||||
<ul className="list-disc pl-10">
|
||||
<li><code>va-driver-all</code></li>
|
||||
<li><code>ocl-icd-libopencl1</code></li>
|
||||
<li><code>intel-opencl-icd</code></li>
|
||||
<li><code>vainfo</code></li>
|
||||
<li><code>intel-gpu-tools</code></li>
|
||||
</ul>
|
||||
<li>Coral TPU dependencies:</li>
|
||||
<ul className="list-disc pl-10">
|
||||
<li><code>python3</code></li>
|
||||
<li><code>python3-pip</code></li>
|
||||
<li><code>python3-venv</code></li>
|
||||
<li><code>gnupg</code></li>
|
||||
<li><code>curl</code></li>
|
||||
</ul>
|
||||
<li>Coral TPU drivers:</li>
|
||||
<ul className="list-disc pl-10">
|
||||
<li><code>libedgetpu1-std</code> (standard performance)</li>
|
||||
<li><code>libedgetpu1-max</code> (maximum performance, optional)</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</Steps.Step>
|
||||
<Steps.Step title="Select Coral TPU Driver Version">
|
||||
@@ -56,7 +86,7 @@ export default function CoralTPULXC() {
|
||||
</ul>
|
||||
</Steps.Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Expected Results</h2>
|
||||
<ul className="list-disc pl-6 space-y-2 mb-6">
|
||||
<li>The selected container is correctly configured for TPU and iGPU usage.</li>
|
||||
@@ -64,14 +94,15 @@ export default function CoralTPULXC() {
|
||||
<li>The container will restart as needed during the process.</li>
|
||||
<li>After completion, applications inside the container can utilize Coral TPU acceleration.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Important Considerations</h2>
|
||||
<ul className="list-disc pl-6 space-y-2 mb-6">
|
||||
<li>The script supports both USB and M.2 Coral TPU devices.</li>
|
||||
<li>The Proxmox host must have the required Coral TPU and Intel GPU drivers installed.</li>
|
||||
<li>Additional application-specific configurations may be required inside the container.</li>
|
||||
<li>Coral USB passthrough uses a persistent device alias <code>/dev/coral</code> created by a udev rule. This improves stability and avoids issues with changing USB port identifiers.</li>
|
||||
<li>Coral M.2 devices are detected dynamically using <code>lspci</code> and configured only if present.</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,58 +10,79 @@ export default function InstallCoralTPUHost() {
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-3xl font-bold mb-6">Install Coral TPU on the Host</h1>
|
||||
|
||||
<p className="mb-4"><strong>Before using Coral TPU inside an LXC container, the drivers must first be installed on the Proxmox VE host. This script automates that process, ensuring the necessary setup is completed.</strong><br/><br/>
|
||||
This guide explains how to install and configure Google Coral TPU drivers on a Proxmox VE host using <strong>ProxMenux</strong>.
|
||||
This setup enables hardware acceleration for AI-based applications that leverage Coral TPU.
|
||||
|
||||
<p className="mb-4">
|
||||
<strong>Before using Coral TPU inside an LXC container, the drivers must first be installed on the Proxmox VE host. This script automates that process, ensuring the necessary setup is completed.</strong>
|
||||
<br /><br />
|
||||
This guide explains how to install and configure Google Coral TPU drivers on a Proxmox VE host using <strong>ProxMenux</strong>. This setup enables hardware acceleration for AI-based applications that leverage Coral TPU.
|
||||
</p>
|
||||
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
|
||||
<p className="mb-4">The script automates the following steps:</p>
|
||||
<ol className="list-decimal pl-6 space-y-2 mb-6">
|
||||
<li>Prompts for confirmation before proceeding with installation.</li>
|
||||
<li>Verifies and configures necessary repositories on the host.</li>
|
||||
<li>Installs required dependencies for driver compilation.</li>
|
||||
<li>Installs required build dependencies and kernel headers for driver compilation.</li>
|
||||
<li>Clones the Coral TPU driver repository and builds the drivers.</li>
|
||||
<li>Installs the compiled Coral TPU drivers.</li>
|
||||
<li>Prompts for a system restart to apply changes.</li>
|
||||
</ol>
|
||||
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
|
||||
<Steps>
|
||||
<Steps.Step title="Pre-Installation Confirmation">
|
||||
<p>The script prompts the user for confirmation before proceeding, as a system restart is required after installation.</p>
|
||||
</Steps.Step>
|
||||
|
||||
<Steps.Step title="Repository Configuration">
|
||||
<p>The script verifies and configures required repositories:</p>
|
||||
<ul className="list-disc pl-6 space-y-1 mt-2">
|
||||
<li>Adds the <strong>pve-no-subscription</strong> repository if not present.</li>
|
||||
<li>Adds <strong>non-free-firmware</strong> repositories for required packages.</li>
|
||||
<li>Runs an update to fetch the latest package lists.</li>
|
||||
<li>Runs <code>apt-get update</code> to fetch the latest package lists.</li>
|
||||
</ul>
|
||||
</Steps.Step>
|
||||
|
||||
<Steps.Step title="Driver Installation">
|
||||
<p>The script installs and compiles the required drivers:</p>
|
||||
<p>The script installs and compiles the required Coral TPU drivers:</p>
|
||||
<ul className="list-disc pl-6 space-y-1 mt-2">
|
||||
<li>Installs dependencies such as <strong>git, dkms, devscripts</strong>, and kernel headers.</li>
|
||||
<li>Clones the <strong>gasket-driver</strong> repository from Google.</li>
|
||||
<li>Builds the Coral TPU driver packages.</li>
|
||||
<li>Installs the compiled drivers on the host.</li>
|
||||
<li>Installs the following packages:</li>
|
||||
<ul className="list-disc pl-10">
|
||||
<li><code>git</code></li>
|
||||
<li><code>devscripts</code></li>
|
||||
<li><code>dh-dkms</code></li>
|
||||
<li><code>dkms</code></li>
|
||||
<li><code>pve-headers-$(uname -r)</code> (Proxmox kernel headers)</li>
|
||||
</ul>
|
||||
<li>Clones the Coral TPU driver source from:</li>
|
||||
<ul className="list-disc pl-10">
|
||||
<li><code>https://github.com/google/gasket-driver</code></li>
|
||||
</ul>
|
||||
<li>Builds the driver using <code>debuild</code> and installs it using <code>dpkg -i</code>.</li>
|
||||
</ul>
|
||||
|
||||
<CopyableCode
|
||||
code={`# Commands used to build and install Coral TPU driver on host
|
||||
apt install -y git devscripts dh-dkms dkms pve-headers-$(uname -r)
|
||||
git clone https://github.com/google/gasket-driver.git
|
||||
cd gasket-driver
|
||||
debuild -us -uc -tc -b
|
||||
dpkg -i ../gasket-dkms_*.deb`}
|
||||
className="my-4"
|
||||
/>
|
||||
</Steps.Step>
|
||||
|
||||
<Steps.Step title="Post-Installation Confirmation">
|
||||
<p>The script prompts the user to restart the server to apply the changes.</p>
|
||||
</Steps.Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Expected Results</h2>
|
||||
<ul className="list-disc pl-6 space-y-2 mb-6">
|
||||
<li>The Coral TPU drivers are installed successfully on the Proxmox VE host.</li>
|
||||
<li>Required repositories and dependencies are configured properly.</li>
|
||||
<li>A system restart is performed to complete the installation.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+74
-27
@@ -10,6 +10,31 @@ interface ChangelogEntry {
|
||||
title: string
|
||||
}
|
||||
|
||||
// Function to clean and format markdown content for RSS
|
||||
function formatContentForRSS(content: string): string {
|
||||
return (
|
||||
content
|
||||
// Convert ### headers to bold text
|
||||
.replace(/^### (.+)$/gm, "**$1**")
|
||||
// Convert ** bold ** to simple bold
|
||||
.replace(/\*\*(.*?)\*\*/g, "$1")
|
||||
// Clean code blocks - remove ``` and format nicely
|
||||
.replace(/```[\s\S]*?```/g, (match) => {
|
||||
const code = match.replace(/```/g, "").trim()
|
||||
return `\n${code}\n`
|
||||
})
|
||||
// Convert - bullet points to •
|
||||
.replace(/^- /gm, "• ")
|
||||
// Clean up multiple newlines
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
// Remove backslashes used for line breaks
|
||||
.replace(/\\\s*$/gm, "")
|
||||
// Clean up extra spaces
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
)
|
||||
}
|
||||
|
||||
async function parseChangelog(): Promise<ChangelogEntry[]> {
|
||||
try {
|
||||
const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md")
|
||||
@@ -21,45 +46,67 @@ async function parseChangelog(): Promise<ChangelogEntry[]> {
|
||||
const fileContents = fs.readFileSync(changelogPath, "utf8")
|
||||
const entries: ChangelogEntry[] = []
|
||||
|
||||
// Split by any heading (## or ###) to catch all changes, not just versions
|
||||
const sections = fileContents.split(/^(##\s+.*$)/gm).filter((section) => section.trim())
|
||||
// Split by ## headers (both versions and dates)
|
||||
const lines = fileContents.split("\n")
|
||||
let currentEntry: Partial<ChangelogEntry> | null = null
|
||||
let contentLines: string[] = []
|
||||
|
||||
for (let i = 0; i < sections.length - 1; i += 2) {
|
||||
const headerLine = sections[i]
|
||||
const content = sections[i + 1] || ""
|
||||
for (const line of lines) {
|
||||
// Check for version header: ## [1.1.1] - 2025-03-21
|
||||
const versionMatch = line.match(/^##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
|
||||
|
||||
// Check if it's a version header (## [version] - date)
|
||||
const versionMatch = headerLine.match(/##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
|
||||
// Check for date-only header: ## 2025-05-13
|
||||
const dateMatch = line.match(/^##\s+(\d{4}-\d{2}-\d{2})$/)
|
||||
|
||||
if (versionMatch) {
|
||||
const version = versionMatch[1]
|
||||
const date = versionMatch[2]
|
||||
if (versionMatch || dateMatch) {
|
||||
// Save previous entry if exists
|
||||
if (currentEntry && contentLines.length > 0) {
|
||||
const rawContent = contentLines.join("\n").trim()
|
||||
currentEntry.content = formatContentForRSS(rawContent)
|
||||
if (currentEntry.version && currentEntry.date && currentEntry.title) {
|
||||
entries.push(currentEntry as ChangelogEntry)
|
||||
}
|
||||
}
|
||||
|
||||
entries.push({
|
||||
version,
|
||||
date,
|
||||
content: content.trim(),
|
||||
url: `https://macrimi.github.io/ProxMenux/changelog#${version}`,
|
||||
title: `ProxMenux ${version}`,
|
||||
})
|
||||
} else {
|
||||
// Check for date-only headers (## 2025-05-13)
|
||||
const dateMatch = headerLine.match(/##\s+(\d{4}-\d{2}-\d{2})/)
|
||||
if (dateMatch) {
|
||||
// Start new entry
|
||||
if (versionMatch) {
|
||||
const version = versionMatch[1]
|
||||
const date = versionMatch[2]
|
||||
currentEntry = {
|
||||
version,
|
||||
date,
|
||||
url: `https://macrimi.github.io/ProxMenux/changelog#${version}`,
|
||||
title: `ProxMenux ${version}`,
|
||||
}
|
||||
} else if (dateMatch) {
|
||||
const date = dateMatch[1]
|
||||
|
||||
entries.push({
|
||||
currentEntry = {
|
||||
version: date,
|
||||
date,
|
||||
content: content.trim(),
|
||||
url: `https://macrimi.github.io/ProxMenux/changelog#${date}`,
|
||||
title: `ProxMenux Update ${date}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
contentLines = []
|
||||
} else if (currentEntry && line.trim()) {
|
||||
// Add content lines (skip empty lines at the beginning)
|
||||
if (contentLines.length > 0 || line.trim() !== "") {
|
||||
contentLines.push(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries.slice(0, 15) // Latest 15 entries
|
||||
// Don't forget the last entry
|
||||
if (currentEntry && contentLines.length > 0) {
|
||||
const rawContent = contentLines.join("\n").trim()
|
||||
currentEntry.content = formatContentForRSS(rawContent)
|
||||
if (currentEntry.version && currentEntry.date && currentEntry.title) {
|
||||
entries.push(currentEntry as ChangelogEntry)
|
||||
}
|
||||
}
|
||||
|
||||
return entries.slice(0, 20) // Latest 20 entries
|
||||
} catch (error) {
|
||||
console.error("Error parsing changelog:", error)
|
||||
return []
|
||||
@@ -87,7 +134,7 @@ export async function GET() {
|
||||
(entry) => `
|
||||
<item>
|
||||
<title>${entry.title}</title>
|
||||
<description><![CDATA[${entry.content.substring(0, 500)}${entry.content.length > 500 ? "..." : ""}]]></description>
|
||||
<description><![CDATA[${entry.content.length > 1000 ? entry.content.substring(0, 1000) + "..." : entry.content}]]></description>
|
||||
<link>${entry.url}</link>
|
||||
<guid isPermaLink="true">${entry.url}</guid>
|
||||
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
|
||||
|
||||
@@ -81,7 +81,6 @@ export const sidebarItems: MenuItem[] = [
|
||||
{
|
||||
title: "Network",
|
||||
submenu: [
|
||||
{ title: "Repair Network", href: "/docs/network/repair-network" },
|
||||
{ title: "Verify Network", href: "/docs/network/verify-network" },
|
||||
{ title: "Show IP Information", href: "/docs/network/show-ip-information" },
|
||||
],
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
Reference in New Issue
Block a user