mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-06-28 12:16:53 +00:00
Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
76
.github/scripts/generate_helpers_cache.py
vendored
Normal file
76
.github/scripts/generate_helpers_cache.py
vendored
Normal file
@ -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.")
|
38
.github/workflows/update-helpers-cache.yml
vendored
Normal file
38
.github/workflows/update-helpers-cache.yml
vendored
Normal file
@ -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
|
99
CHANGELOG.md
99
CHANGELOG.md
@ -1,3 +1,102 @@
|
|||||||
|
## 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
|
## 2025-05-13
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -29,6 +29,7 @@ Instead, please report it privately via email:
|
|||||||
|
|
||||||
📧 proxmenux@macrimi.pro
|
📧 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
|
## 🤝 3. Community Guidelines
|
||||||
|
|
||||||
|
41
SECURITY.md
Normal file
41
SECURITY.md
Normal file
@ -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!
|
@ -223,12 +223,12 @@
|
|||||||
"it": "Hardware: GPUs e Coral-TPU",
|
"it": "Hardware: GPUs e Coral-TPU",
|
||||||
"pt": "Hardware: GPUs e Coral-TPU"
|
"pt": "Hardware: GPUs e Coral-TPU"
|
||||||
},
|
},
|
||||||
"Essential Proxmox VE Helper-Scripts": {
|
"Proxmox VE Helper Scripts": {
|
||||||
"es": "Esenciales Proxmox VE Helper-Scripts",
|
"es": "Proxmox VE Helper Scripts",
|
||||||
"fr": "Essentiels Proxmox VE Helper-Scripts",
|
"fr": "Proxmox VE Helper Scripts",
|
||||||
"de": "Essentielle Proxmox VE Helper-Scriptse",
|
"de": "Proxmox VE Helper Scriptse",
|
||||||
"it": "Essenziali Proxmox VE Helper-Scripts",
|
"it": "Proxmox VE Helper Scripts",
|
||||||
"pt": "Essenciais Proxmox VE Helper-Scripts"
|
"pt": "Proxmox VE Helper Scripts"
|
||||||
},
|
},
|
||||||
"Generating automatic translations...": {
|
"Generating automatic translations...": {
|
||||||
"es": "Generando traducciones...",
|
"es": "Generando traducciones...",
|
||||||
|
5087
json/helpers_cache.json
Normal file
5087
json/helpers_cache.json
Normal file
File diff suppressed because it is too large
Load Diff
4
menu
4
menu
@ -49,9 +49,11 @@ fi
|
|||||||
|
|
||||||
# Initialize language configuration
|
# Initialize language configuration
|
||||||
initialize_config() {
|
initialize_config() {
|
||||||
show_proxmenux_logo
|
|
||||||
# Check if config file exists and has language field
|
# Check if config file exists and has language field
|
||||||
if [ ! -f "$CONFIG_FILE" ] || [ -z "$(jq -r '.language // empty' "$CONFIG_FILE")" ]; then
|
if [ ! -f "$CONFIG_FILE" ] || [ -z "$(jq -r '.language // empty' "$CONFIG_FILE")" ]; then
|
||||||
|
show_proxmenux_logo
|
||||||
|
sleep 2
|
||||||
LANGUAGE=$(whiptail --title "$(translate "Select Language")" --menu "$(translate "Choose a language for the menu:")" 20 60 12 \
|
LANGUAGE=$(whiptail --title "$(translate "Select Language")" --menu "$(translate "Choose a language for the menu:")" 20 60 12 \
|
||||||
"en" "$(translate "English (Recommended)")" \
|
"en" "$(translate "English (Recommended)")" \
|
||||||
"es" "$(translate "Spanish")" \
|
"es" "$(translate "Spanish")" \
|
||||||
|
917
scripts/backup_restore/backup_host.sh
Normal file
917
scripts/backup_restore/backup_host.sh
Normal file
@ -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
|
1061
scripts/backup_restore/backup_host2.sh
Normal file
1061
scripts/backup_restore/backup_host2.sh
Normal file
File diff suppressed because it is too large
Load Diff
1294
scripts/backup_restore/backup_host3.sh
Normal file
1294
scripts/backup_restore/backup_host3.sh
Normal file
File diff suppressed because it is too large
Load Diff
433
scripts/backup_restore/mount_disk_host_bk.sh
Normal file
433
scripts/backup_restore/mount_disk_host_bk.sh
Normal file
@ -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
|
||||||
|
|
||||||
|
}
|
@ -165,8 +165,8 @@ install_igpu_in_container() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
select_container
|
select_container
|
||||||
|
show_proxmenux_logo
|
||||||
configure_lxc_for_igpu
|
configure_lxc_for_igpu
|
||||||
install_igpu_in_container
|
install_igpu_in_container
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ apt_upgrade() {
|
|||||||
progress=$((i * 10))
|
progress=$((i * 10))
|
||||||
tput cup $((row + 3)) 9
|
tput cup $((row + 3)) 9
|
||||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||||
sleep 0.2
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -292,51 +292,7 @@ apt_upgrade() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
msg_info "$(translate "Updating PVE application manager, patience...")"
|
|
||||||
total_steps=$(pveam update 2>&1 | grep -E "^(Downloading|Importing)" | wc -l)
|
|
||||||
[ $total_steps -eq 0 ] && total_steps=1
|
|
||||||
|
|
||||||
tput sc
|
|
||||||
|
|
||||||
(
|
|
||||||
pveam update 2>&1 | while IFS= read -r line; do
|
|
||||||
if [[ $line == "Downloading"* ]] || [[ $line == "Importing"* ]]; then
|
|
||||||
|
|
||||||
file_name=$(echo "$line" | sed -E 's/.* (Downloading|Importing) ([^ ]+).*/\2/')
|
|
||||||
|
|
||||||
|
|
||||||
[ -z "$file_name" ] && file_name="$(translate "Unknown")"
|
|
||||||
|
|
||||||
|
|
||||||
tput rc
|
|
||||||
tput ed
|
|
||||||
|
|
||||||
|
|
||||||
row=$(( $(tput lines) - 6 ))
|
|
||||||
tput cup $row 0; echo "$(translate "Updating PVE application manager...")"
|
|
||||||
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
|
|
||||||
tput cup $((row + 2)) 0; echo "Downloading: $file_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"
|
|
||||||
sleep 0.2
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
tput rc
|
|
||||||
tput ed
|
|
||||||
msg_ok "$(translate "PVE application manager updated")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
tput cnorm
|
|
||||||
|
|
||||||
|
|
||||||
# Install additional Proxmox packages
|
# Install additional Proxmox packages
|
||||||
@ -591,7 +547,7 @@ fs.aio-max-nr = 1048576"
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
skip_apt_languages() {
|
skip_apt_languages_() {
|
||||||
msg_info2 "$(translate "Configuring APT to skip downloading additional languages")"
|
msg_info2 "$(translate "Configuring APT to skip downloading additional languages")"
|
||||||
|
|
||||||
local config_file="/etc/apt/apt.conf.d/99-disable-translations"
|
local config_file="/etc/apt/apt.conf.d/99-disable-translations"
|
||||||
@ -610,6 +566,43 @@ skip_apt_languages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
skip_apt_languages() {
|
||||||
|
msg_info2 "$(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)
|
||||||
|
elif [ -f /etc/environment ]; then
|
||||||
|
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2)
|
||||||
|
fi
|
||||||
|
|
||||||
|
default_locale="${default_locale:-en_US.UTF-8}"
|
||||||
|
|
||||||
|
if ! locale -a | grep -qi "^${default_locale//-/_}$"; then
|
||||||
|
msg_info "$(translate "Generating missing locale:") $default_locale"
|
||||||
|
echo "$default_locale UTF-8" >> /etc/locale.gen
|
||||||
|
locale-gen "$default_locale"
|
||||||
|
msg_ok "$(translate "Locale generated")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local config_file="/etc/apt/apt.conf.d/99-disable-translations"
|
||||||
|
local config_content="Acquire::Languages \"none\";"
|
||||||
|
|
||||||
|
msg_info "$(translate "Setting APT language configuration...")"
|
||||||
|
|
||||||
|
if [ -f "$config_file" ] && grep -q "$config_content" "$config_file"; then
|
||||||
|
msg_ok "$(translate "APT language configuration updated")"
|
||||||
|
else
|
||||||
|
echo -e "$config_content\n" > "$config_file"
|
||||||
|
msg_ok "$(translate "APT language configuration updated")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_success "$(translate "APT configured to skip downloading additional languages")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
@ -742,7 +735,6 @@ packages_list=(
|
|||||||
progress=$((i * 10))
|
progress=$((i * 10))
|
||||||
tput cup $((row + 3)) 9
|
tput cup $((row + 3)) 9
|
||||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||||
sleep 0.2
|
|
||||||
done
|
done
|
||||||
|
|
||||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install "$package" > /dev/null 2>&1
|
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install "$package" > /dev/null 2>&1
|
||||||
@ -1718,7 +1710,7 @@ configure_ksmtuned() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
enable_vfio_iommu() {
|
enable_vfio_iommu_() {
|
||||||
msg_info2 "$(translate "Enabling IOMMU and configuring VFIO for PCI passthrough...")"
|
msg_info2 "$(translate "Enabling IOMMU and configuring VFIO for PCI passthrough...")"
|
||||||
NECESSARY_REBOOT=1
|
NECESSARY_REBOOT=1
|
||||||
|
|
||||||
@ -1818,6 +1810,92 @@ enable_vfio_iommu() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enable_vfio_iommu() {
|
||||||
|
msg_info2 "$(translate "Enabling IOMMU and configuring VFIO for PCI passthrough...")"
|
||||||
|
NECESSARY_REBOOT=1
|
||||||
|
|
||||||
|
# Detect if system uses ZFS/systemd-boot (Proxmox)
|
||||||
|
local uses_zfs=false
|
||||||
|
local cmdline_file="/etc/kernel/cmdline"
|
||||||
|
if [[ -f "$cmdline_file" ]] && grep -qE 'root=ZFS=|root=ZFS/' "$cmdline_file"; then
|
||||||
|
uses_zfs=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Enable IOMMU
|
||||||
|
local cpu_info=$(cat /proc/cpuinfo)
|
||||||
|
local grub_file="/etc/default/grub"
|
||||||
|
local iommu_param=""
|
||||||
|
local additional_params="pcie_acs_override=downstream,multifunction nofb nomodeset video=vesafb:off,efifb:off"
|
||||||
|
|
||||||
|
if [[ "$cpu_info" == *"GenuineIntel"* ]]; then
|
||||||
|
msg_info "$(translate "Detected Intel CPU")"
|
||||||
|
iommu_param="intel_iommu=on"
|
||||||
|
elif [[ "$cpu_info" == *"AuthenticAMD"* ]]; then
|
||||||
|
msg_info "$(translate "Detected AMD CPU")"
|
||||||
|
iommu_param="amd_iommu=on"
|
||||||
|
else
|
||||||
|
msg_warning "$(translate "Unknown CPU type. IOMMU might not be properly enabled.")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$uses_zfs" == true ]]; then
|
||||||
|
# --- SYSTEMD-BOOT: /etc/kernel/cmdline ---
|
||||||
|
if grep -q "$iommu_param" "$cmdline_file"; then
|
||||||
|
msg_ok "$(translate "IOMMU already configured in /etc/kernel/cmdline")"
|
||||||
|
else
|
||||||
|
cp "$cmdline_file" "${cmdline_file}.bak"
|
||||||
|
# sed -i "s|\"$| $iommu_param iommu=pt|" "$cmdline_file"
|
||||||
|
sed -i "s|\s*$| $iommu_param iommu=pt|" "$cmdline_file"
|
||||||
|
msg_ok "$(translate "IOMMU parameters added to /etc/kernel/cmdline")"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# --- GRUB ---
|
||||||
|
if grep -q "$iommu_param" "$grub_file"; then
|
||||||
|
msg_ok "$(translate "IOMMU already enabled in GRUB configuration")"
|
||||||
|
else
|
||||||
|
cp "$grub_file" "${grub_file}.bak"
|
||||||
|
sed -i "/GRUB_CMDLINE_LINUX_DEFAULT=/ s|\"$| $iommu_param iommu=pt\"|" "$grub_file"
|
||||||
|
msg_ok "$(translate "IOMMU enabled in GRUB configuration")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure VFIO modules (avoid duplicates)
|
||||||
|
local modules_file="/etc/modules"
|
||||||
|
msg_info "$(translate "Checking VFIO modules...")"
|
||||||
|
local vfio_modules=("vfio" "vfio_iommu_type1" "vfio_pci" "vfio_virqfd")
|
||||||
|
for module in "${vfio_modules[@]}"; do
|
||||||
|
grep -q "^$module" "$modules_file" || echo "$module" >> "$modules_file"
|
||||||
|
done
|
||||||
|
msg_ok "$(translate "VFIO modules configured.")"
|
||||||
|
|
||||||
|
# Blacklist conflicting drivers (avoid duplicates)
|
||||||
|
local blacklist_file="/etc/modprobe.d/blacklist.conf"
|
||||||
|
msg_info "$(translate "Checking conflicting drivers blacklist...")"
|
||||||
|
touch "$blacklist_file"
|
||||||
|
local blacklist_drivers=("nouveau" "lbm-nouveau" "amdgpu" "radeon" "nvidia" "nvidiafb")
|
||||||
|
for driver in "${blacklist_drivers[@]}"; do
|
||||||
|
grep -q "^blacklist $driver" "$blacklist_file" || echo "blacklist $driver" >> "$blacklist_file"
|
||||||
|
done
|
||||||
|
if ! grep -q "options nouveau modeset=0" "$blacklist_file"; then
|
||||||
|
echo "options nouveau modeset=0" >> "$blacklist_file"
|
||||||
|
fi
|
||||||
|
msg_ok "$(translate "Conflicting drivers blacklisted successfully.")"
|
||||||
|
|
||||||
|
# Propagate the settings
|
||||||
|
msg_info "$(translate "Updating initramfs, GRUB, and EFI boot, patience...")"
|
||||||
|
update-initramfs -u -k all > /dev/null 2>&1
|
||||||
|
if [[ "$uses_zfs" == true ]]; then
|
||||||
|
proxmox-boot-tool refresh > /dev/null 2>&1
|
||||||
|
else
|
||||||
|
update-grub > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_success "$(translate "IOMMU and VFIO setup completed")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
@ -1960,8 +2038,53 @@ EOF
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
remove_subscription_banner() {
|
remove_subscription_banner() {
|
||||||
|
|
||||||
|
msg_info2 "$(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
|
||||||
|
msg_ok "$(translate "APT hook for nag removal created")"
|
||||||
|
else
|
||||||
|
msg_info "$(translate "APT hook for nag removal already exists")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [[ -f "$JS_FILE" ]]; then
|
||||||
|
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
|
||||||
|
|
||||||
|
if [[ -f "$GZ_FILE" ]]; then
|
||||||
|
rm -f "$GZ_FILE"
|
||||||
|
msg_info "$(translate "Deleted proxmoxlib.js.gz to force browser refresh")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$JS_FILE"
|
||||||
|
msg_ok "$(translate "Patched proxmoxlib.js (banner should disappear after browser refresh)")"
|
||||||
|
else
|
||||||
|
msg_error "$(translate "proxmoxlib.js not found. Cannot patch subscription banner.")"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
|
||||||
|
|
||||||
|
msg_success "$(translate "Subscription nag banner removed.")"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
remove_subscription_banner_() {
|
||||||
msg_info2 "$(translate "Checking Proxmox subscription banner and nag status...")"
|
msg_info2 "$(translate "Checking Proxmox subscription banner and nag status...")"
|
||||||
|
|
||||||
local proxmox_js="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
local proxmox_js="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||||
@ -2476,6 +2599,24 @@ EOF
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
update_pve_appliance_manager() {
|
||||||
|
msg_info "$(translate "Updating PVE application manager...")"
|
||||||
|
if pveam update > /dev/null 2>&1; then
|
||||||
|
msg_ok "$(translate "PVE application manager updated")"
|
||||||
|
else
|
||||||
|
msg_warn "$(translate "No updates or failed to fetch templates")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Auxiliary help functions
|
# Auxiliary help functions
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
@ -2608,20 +2749,17 @@ lvm_repair_check() {
|
|||||||
|
|
||||||
# Main menu function
|
# Main menu function
|
||||||
main_menu() {
|
main_menu() {
|
||||||
local HEADER=$(printf " %-56s %10s" "$(translate "Description")" "Category")
|
local HEADER
|
||||||
|
if [[ "$LANGUAGE" == "es" ]]; then
|
||||||
|
HEADER="Seleccione las opciones a configurar:\n\n Descripción | Categoría"
|
||||||
|
else
|
||||||
|
HEADER="$(translate "Choose options to configure:")\n\n Description | Category"
|
||||||
|
fi
|
||||||
|
|
||||||
declare -A category_order=(
|
declare -A category_order=(
|
||||||
["Basic Settings"]=1
|
["Basic Settings"]=1 ["System"]=2 ["Hardware"]=3 ["Virtualization"]=4
|
||||||
["System"]=2
|
["Network"]=5 ["Storage"]=6 ["Security"]=7 ["Customization"]=8
|
||||||
["Hardware"]=3
|
["Monitoring"]=9 ["Performance"]=10 ["Optional"]=11
|
||||||
["Virtualization"]=4
|
|
||||||
["Network"]=5
|
|
||||||
["Storage"]=6
|
|
||||||
["Security"]=7
|
|
||||||
["Customization"]=8
|
|
||||||
["Monitoring"]=9
|
|
||||||
["Performance"]=10
|
|
||||||
["Optional"]=11
|
|
||||||
)
|
)
|
||||||
|
|
||||||
local options=(
|
local options=(
|
||||||
@ -2657,6 +2795,7 @@ main_menu() {
|
|||||||
"Monitoring|Install OVH Real Time Monitoring|OVHRTM"
|
"Monitoring|Install OVH Real Time Monitoring|OVHRTM"
|
||||||
"Performance|Use pigz for faster gzip compression|PIGZ"
|
"Performance|Use pigz for faster gzip compression|PIGZ"
|
||||||
"Optional|Install and configure Fastfetch|FASTFETCH"
|
"Optional|Install and configure Fastfetch|FASTFETCH"
|
||||||
|
"Optional|Update Proxmox VE Appliance Manager|PVEAM"
|
||||||
"Optional|Add latest Ceph support|CEPH"
|
"Optional|Add latest Ceph support|CEPH"
|
||||||
"Optional|Add Proxmox testing repository|REPOTEST"
|
"Optional|Add Proxmox testing repository|REPOTEST"
|
||||||
"Optional|Enable High Availability services|ENABLE_HA"
|
"Optional|Enable High Availability services|ENABLE_HA"
|
||||||
@ -2669,168 +2808,187 @@ main_menu() {
|
|||||||
done | sort -n | cut -d'|' -f2-))
|
done | sort -n | cut -d'|' -f2-))
|
||||||
unset IFS
|
unset IFS
|
||||||
|
|
||||||
local total_width=65
|
local max_desc_length=0
|
||||||
local max_desc_width=50
|
local temp_descriptions=()
|
||||||
local category_width=15
|
|
||||||
local category_position=$((total_width - category_width))
|
for option in "${sorted_options[@]}"; do
|
||||||
|
IFS='|' read -r category description function_name <<< "$option"
|
||||||
|
local desc_translated="$(translate "$description")"
|
||||||
|
temp_descriptions+=("$desc_translated")
|
||||||
|
|
||||||
|
local desc_length=${#desc_translated}
|
||||||
|
if [ $desc_length -gt $max_desc_length ]; then
|
||||||
|
max_desc_length=$desc_length
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $max_desc_length -gt 50 ]; then
|
||||||
|
max_desc_length=50
|
||||||
|
fi
|
||||||
|
|
||||||
local menu_items=()
|
local checklist_items=()
|
||||||
local i=1
|
local i=1
|
||||||
|
local desc_index=0
|
||||||
local previous_category=""
|
local previous_category=""
|
||||||
|
|
||||||
for option in "${sorted_options[@]}"; do
|
for option in "${sorted_options[@]}"; do
|
||||||
IFS='|' read -r category description function_name <<< "$option"
|
IFS='|' read -r category description function_name <<< "$option"
|
||||||
translated_description="$(translate "$description")"
|
|
||||||
|
|
||||||
|
|
||||||
local max_cut=$((category_position - 3))
|
|
||||||
[[ "$max_cut" -lt 10 ]] && max_cut=10
|
|
||||||
if [[ ${#translated_description} -gt $max_cut ]]; then
|
|
||||||
translated_description="${translated_description:0:$((max_cut - 3))}..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [[ "$category" != "$previous_category" && "$category" == "Optional" && -n "$previous_category" ]]; then
|
if [[ "$category" != "$previous_category" && "$category" == "Optional" && -n "$previous_category" ]]; then
|
||||||
menu_items+=("" "================================================================" "")
|
checklist_items+=("" "==============================================================" "")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
local desc_translated="${temp_descriptions[$desc_index]}"
|
||||||
|
desc_index=$((desc_index + 1))
|
||||||
|
|
||||||
|
|
||||||
|
if [ ${#desc_translated} -gt $max_desc_length ]; then
|
||||||
|
desc_translated="${desc_translated:0:$((max_desc_length-3))}..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
local line="$translated_description"
|
local spaces_needed=$((max_desc_length - ${#desc_translated}))
|
||||||
local spaces_needed=$((category_position - ${#translated_description}))
|
local padding=""
|
||||||
for ((j = 0; j < spaces_needed; j++)); do
|
for ((j=0; j<spaces_needed; j++)); do
|
||||||
line+=" "
|
padding+=" "
|
||||||
done
|
done
|
||||||
line+="$category"
|
|
||||||
|
local line="${desc_translated}${padding} | ${category}"
|
||||||
|
|
||||||
menu_items+=("$i" "$line" "OFF")
|
checklist_items+=("$i" "$line" "off")
|
||||||
i=$((i + 1))
|
i=$((i + 1))
|
||||||
previous_category="$category"
|
previous_category="$category"
|
||||||
done
|
done
|
||||||
|
|
||||||
cleanup
|
exec 3>&1
|
||||||
|
selected_indices=$(dialog --clear \
|
||||||
|
--backtitle "ProxMenux" \
|
||||||
|
--title "$(translate "Post-Installation Options")" \
|
||||||
|
--checklist "$HEADER" 22 80 15 \
|
||||||
|
"${checklist_items[@]}" \
|
||||||
|
2>&1 1>&3)
|
||||||
|
|
||||||
local selected_indices=$(whiptail --title "$(translate "ProxMenux Custom Script for Post-Installation")" \
|
local dialog_exit=$?
|
||||||
--checklist --separate-output \
|
exec 3>&-
|
||||||
"\n$HEADER\n\n$(translate "Choose options to configure:")\n$(translate "Use [SPACE] to select/deselect and [ENTER] to confirm:")" \
|
|
||||||
20 82 12 \
|
|
||||||
"${menu_items[@]}" \
|
|
||||||
3>&1 1>&2 2>&3)
|
|
||||||
|
|
||||||
|
if [[ $dialog_exit -ne 0 || -z "$selected_indices" ]]; then
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "User cancelled. Exiting."
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
IFS=$'\n' read -d '' -r -a selected_options <<< "$selected_indices"
|
|
||||||
declare -A selected_functions
|
|
||||||
|
|
||||||
if [ -n "$selected_indices" ]; then
|
|
||||||
show_proxmenux_logo
|
|
||||||
msg_title "$SCRIPT_TITLE"
|
|
||||||
|
|
||||||
for index in "${selected_options[@]}"; do
|
|
||||||
option=${sorted_options[$((index - 1))]}
|
|
||||||
IFS='|' read -r category description function_name <<< "$option"
|
|
||||||
selected_functions[$function_name]=1
|
|
||||||
|
|
||||||
|
|
||||||
[[ "$function_name" == "FASTFETCH" ]] && selected_functions[MOTD]=0
|
declare -A selected_functions
|
||||||
done
|
read -ra indices_array <<< "$selected_indices"
|
||||||
|
|
||||||
for index in "${!sorted_options[@]}"; do
|
|
||||||
option=${sorted_options[$index]}
|
|
||||||
IFS='|' read -r category description function_name <<< "$option"
|
|
||||||
if [[ ${selected_functions[$function_name]} -eq 1 ]]; then
|
|
||||||
case $function_name in
|
|
||||||
APTUPGRADE) apt_upgrade ;;
|
|
||||||
TIMESYNC) configure_time_sync ;;
|
|
||||||
NOAPTLANG) skip_apt_languages ;;
|
|
||||||
UTILS) install_system_utils ;;
|
|
||||||
JOURNALD) optimize_journald ;;
|
|
||||||
LOGROTATE) optimize_logrotate ;;
|
|
||||||
LIMITS) increase_system_limits ;;
|
|
||||||
ENTROPY) configure_entropy ;;
|
|
||||||
MEMORYFIXES) optimize_memory_settings ;;
|
|
||||||
KEXEC) enable_kexec ;;
|
|
||||||
KERNELPANIC) configure_kernel_panic ;;
|
|
||||||
KERNELHEADERS) install_kernel_headers ;;
|
|
||||||
AMDFIXES) apply_amd_fixes ;;
|
|
||||||
GUESTAGENT) install_guest_agent ;;
|
|
||||||
VFIO_IOMMU) enable_vfio_iommu ;;
|
|
||||||
KSMTUNED) configure_ksmtuned ;;
|
|
||||||
APTIPV4) force_apt_ipv4 ;;
|
|
||||||
NET) apply_network_optimizations ;;
|
|
||||||
OPENVSWITCH) install_openvswitch ;;
|
|
||||||
TCPFASTOPEN) enable_tcp_fast_open ;;
|
|
||||||
ZFSARC) optimize_zfs_arc ;;
|
|
||||||
ZFSAUTOSNAPSHOT) install_zfs_auto_snapshot ;;
|
|
||||||
VZDUMP) optimize_vzdump ;;
|
|
||||||
DISABLERPC) disable_rpc ;;
|
|
||||||
FAIL2BAN) install_fail2ban ;;
|
|
||||||
LYNIS) install_lynis ;;
|
|
||||||
BASHRC) customize_bashrc ;;
|
|
||||||
MOTD) setup_motd ;;
|
|
||||||
NOSUBBANNER) remove_subscription_banner ;;
|
|
||||||
OVHRTM) install_ovh_rtm ;;
|
|
||||||
PIGZ) configure_pigz ;;
|
|
||||||
FASTFETCH) configure_fastfetch ;;
|
|
||||||
CEPH) install_ceph ;;
|
|
||||||
REPOTEST) add_repo_test ;;
|
|
||||||
ENABLE_HA) enable_ha ;;
|
|
||||||
FIGURINE) configure_figurine ;;
|
|
||||||
*) echo "Option $function_name not implemented yet" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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...")"
|
|
||||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' autoremove >/dev/null 2>&1
|
|
||||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' 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...")"
|
|
||||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' autoremove >/dev/null 2>&1
|
|
||||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' 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
|
|
||||||
|
|
||||||
|
for index in "${indices_array[@]}"; do
|
||||||
|
if [[ -z "$index" ]] || ! [[ "$index" =~ ^[0-9]+$ ]]; then
|
||||||
|
continue
|
||||||
fi
|
fi
|
||||||
msg_success "$(translate "All changes applied. No reboot required.")"
|
|
||||||
msg_success "$(translate "Press Enter to return to menu...")"
|
|
||||||
read -r
|
|
||||||
|
|
||||||
else
|
local item_index=$(( (index - 1) * 3 + 1 ))
|
||||||
exit 0
|
if [[ $item_index -lt ${#checklist_items[@]} ]]; then
|
||||||
fi
|
local selected_line="${checklist_items[$item_index]}"
|
||||||
|
if [[ "$selected_line" =~ ^.*(\-\-\-|===+).*$ ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
option=${sorted_options[$((index - 1))]}
|
||||||
|
IFS='|' read -r _ description function_name <<< "$option"
|
||||||
|
selected_functions[$function_name]=1
|
||||||
|
[[ "$function_name" == "FASTFETCH" ]] && selected_functions[MOTD]=0
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
clear
|
||||||
|
show_proxmenux_logo
|
||||||
|
msg_title "$SCRIPT_TITLE"
|
||||||
|
|
||||||
|
for option in "${sorted_options[@]}"; do
|
||||||
|
IFS='|' read -r _ description function_name <<< "$option"
|
||||||
|
if [[ ${selected_functions[$function_name]} -eq 1 ]]; then
|
||||||
|
case $function_name in
|
||||||
|
APTUPGRADE) apt_upgrade ;;
|
||||||
|
TIMESYNC) configure_time_sync ;;
|
||||||
|
NOAPTLANG) skip_apt_languages ;;
|
||||||
|
UTILS) install_system_utils ;;
|
||||||
|
JOURNALD) optimize_journald ;;
|
||||||
|
LOGROTATE) optimize_logrotate ;;
|
||||||
|
LIMITS) increase_system_limits ;;
|
||||||
|
ENTROPY) configure_entropy ;;
|
||||||
|
MEMORYFIXES) optimize_memory_settings ;;
|
||||||
|
KEXEC) enable_kexec ;;
|
||||||
|
KERNELPANIC) configure_kernel_panic ;;
|
||||||
|
KERNELHEADERS) install_kernel_headers ;;
|
||||||
|
AMDFIXES) apply_amd_fixes ;;
|
||||||
|
GUESTAGENT) install_guest_agent ;;
|
||||||
|
VFIO_IOMMU) enable_vfio_iommu ;;
|
||||||
|
KSMTUNED) configure_ksmtuned ;;
|
||||||
|
APTIPV4) force_apt_ipv4 ;;
|
||||||
|
NET) apply_network_optimizations ;;
|
||||||
|
OPENVSWITCH) install_openvswitch ;;
|
||||||
|
TCPFASTOPEN) enable_tcp_fast_open ;;
|
||||||
|
ZFSARC) optimize_zfs_arc ;;
|
||||||
|
ZFSAUTOSNAPSHOT) install_zfs_auto_snapshot ;;
|
||||||
|
VZDUMP) optimize_vzdump ;;
|
||||||
|
DISABLERPC) disable_rpc ;;
|
||||||
|
FAIL2BAN) install_fail2ban ;;
|
||||||
|
LYNIS) install_lynis ;;
|
||||||
|
BASHRC) customize_bashrc ;;
|
||||||
|
MOTD) setup_motd ;;
|
||||||
|
NOSUBBANNER) remove_subscription_banner ;;
|
||||||
|
OVHRTM) install_ovh_rtm ;;
|
||||||
|
PIGZ) configure_pigz ;;
|
||||||
|
FASTFETCH) configure_fastfetch ;;
|
||||||
|
CEPH) install_ceph ;;
|
||||||
|
REPOTEST) add_repo_test ;;
|
||||||
|
ENABLE_HA) enable_ha ;;
|
||||||
|
FIGURINE) configure_figurine ;;
|
||||||
|
PVEAM) update_pve_appliance_manager ;;
|
||||||
|
*) echo "Option $function_name not implemented yet" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
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 [[ "$LANGUAGE" != "en" ]]; then
|
|
||||||
show_proxmenux_logo
|
|
||||||
msg_lang "$(translate "Generating automatic translations...")"
|
|
||||||
fi
|
|
||||||
main_menu
|
main_menu
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
# Author : MacRimi
|
# Author : MacRimi
|
||||||
# Copyright : (c) 2024 MacRimi
|
# Copyright : (c) 2024 MacRimi
|
||||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||||
# Version : 1.0
|
# Version : 1.1
|
||||||
# Last Updated: 28/01/2025
|
# Last Updated: 29/05/2025
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Description:
|
# Description:
|
||||||
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
|
# 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.
|
# 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/.
|
# 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:
|
# Using an interactive menu, you can:
|
||||||
# - Select a VM to attach the imported disk.
|
# - Select a VM to attach the imported disk.
|
||||||
@ -32,211 +32,163 @@ BASE_DIR="/usr/local/share/proxmenux"
|
|||||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||||
VENV_PATH="/opt/googletrans-env"
|
VENV_PATH="/opt/googletrans-env"
|
||||||
|
|
||||||
if [[ -f "$UTILS_FILE" ]]; then
|
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||||
source "$UTILS_FILE"
|
|
||||||
fi
|
|
||||||
load_language
|
load_language
|
||||||
initialize_cache
|
initialize_cache
|
||||||
show_proxmenux_logo
|
# Configuration ============================================
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
# Path where disk images are stored
|
|
||||||
IMAGES_DIR="/var/lib/vz/template/images/"
|
|
||||||
|
|
||||||
|
|
||||||
# Initial setup
|
detect_image_dir() {
|
||||||
if [ ! -d "$IMAGES_DIR" ]; then
|
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
|
||||||
msg_info "$(translate 'Creating images directory')"
|
path=$(pvesm path "${store}:template" 2>/dev/null)
|
||||||
mkdir -p "$IMAGES_DIR"
|
if [[ -d "$path" ]]; then
|
||||||
chmod 755 "$IMAGES_DIR"
|
for ext in raw img qcow2 vmdk; do
|
||||||
msg_ok "$(translate 'Images directory created:') $IMAGES_DIR"
|
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
|
fi
|
||||||
|
|
||||||
|
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
|
||||||
# Check if there are any images in the directory
|
|
||||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk)$")
|
|
||||||
if [ -z "$IMAGES" ]; then
|
if [ -z "$IMAGES" ]; then
|
||||||
whiptail --title "$(translate 'No Images Found')" \
|
dialog --title "$(translate 'No Disk 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
|
--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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Display initial message
|
# === Select VM
|
||||||
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')"
|
msg_info "$(translate 'Getting VM list')"
|
||||||
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
|
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
|
||||||
if [ -z "$VM_LIST" ]; then
|
[[ -z "$VM_LIST" ]] && { msg_error "$(translate 'No VMs available in the system')"; exit 1; }
|
||||||
msg_error "$(translate 'No VMs available in the system')"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
msg_ok "$(translate 'VM list obtained')"
|
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)
|
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)
|
||||||
if [ -z "$VMID" ]; then
|
[[ -z "$VMID" ]] && exit 1
|
||||||
# msg_error "$(translate 'No VM selected')"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 2. Select storage volume
|
|
||||||
|
# === Select storage
|
||||||
msg_info "$(translate 'Getting storage volumes')"
|
msg_info "$(translate 'Getting storage volumes')"
|
||||||
STORAGE_LIST=$(pvesm status -content images | awk 'NR>1 {print $1}')
|
STORAGE_LIST=$(pvesm status -content images | awk 'NR>1 {print $1}')
|
||||||
if [ -z "$STORAGE_LIST" ]; then
|
[[ -z "$STORAGE_LIST" ]] && { msg_error "$(translate 'No storage volumes available')"; exit 1; }
|
||||||
msg_error "$(translate 'No storage volumes available')"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
msg_ok "$(translate 'Storage volumes obtained')"
|
msg_ok "$(translate 'Storage volumes obtained')"
|
||||||
|
|
||||||
# Create an array of storage options for whiptail
|
|
||||||
STORAGE_OPTIONS=()
|
STORAGE_OPTIONS=()
|
||||||
while read -r storage; do
|
while read -r storage; do STORAGE_OPTIONS+=("$storage" ""); done <<< "$STORAGE_LIST"
|
||||||
STORAGE_OPTIONS+=("$storage" "")
|
STORAGE=$(whiptail --title "$(translate 'Select Storage')" \
|
||||||
done <<< "$STORAGE_LIST"
|
--menu "$(translate 'Select the storage volume for disk import:')" 20 70 10 "${STORAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||||
|
[[ -z "$STORAGE" ]] && exit 1
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 3. Select disk images
|
# === Select 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')"
|
|
||||||
|
|
||||||
IMAGE_OPTIONS=()
|
IMAGE_OPTIONS=()
|
||||||
while read -r img; do
|
while read -r img; do IMAGE_OPTIONS+=("$img" "" "OFF"); done <<< "$IMAGES"
|
||||||
IMAGE_OPTIONS+=("$img" "" "OFF")
|
SELECTED_IMAGES=$(whiptail --title "$(translate 'Select Disk Images')" \
|
||||||
done <<< "$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
|
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
|
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
||||||
IMAGE=$(echo "$IMAGE" | tr -d '"')
|
msg_info "$(translate 'Importing image:') $IMAGE"
|
||||||
|
TEMP_DISK_FILE=$(mktemp)
|
||||||
|
|
||||||
# 5. Select interface type for each image
|
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
|
||||||
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
|
if [[ "$line" =~ transferred ]]; then
|
||||||
"sata" "SATA" \
|
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
||||||
"scsi" "SCSI" \
|
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||||
"virtio" "VirtIO" \
|
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
|
||||||
"ide" "IDE" 3>&1 1>&2 2>&3)
|
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
||||||
|
|
||||||
if [ -z "$INTERFACE" ]; then
|
|
||||||
msg_error "$(translate 'No interface type selected for') $IMAGE"
|
|
||||||
continue
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
echo -ne "\n"
|
||||||
|
IMPORT_STATUS=${PIPESTATUS[0]}
|
||||||
# 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
|
if [ "$IMPORT_STATUS" -eq 0 ]; then
|
||||||
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
msg_ok "$(translate 'Image imported successfully')"
|
||||||
|
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
|
||||||
|
rm -f "$TEMP_DISK_FILE"
|
||||||
|
|
||||||
# Show progress with custom format without translation
|
if [ -n "$IMPORTED_DISK" ]; then
|
||||||
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
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
|
msg_info "$(translate 'Configuring disk')"
|
||||||
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
if qm set "$VMID" --${INTERFACE}${NEXT_SLOT} "$IMPORTED_DISK${SSD_OPTION}" &>/dev/null; then
|
||||||
fi
|
msg_ok "$(translate 'Image') $IMAGE $(translate 'configured as') ${INTERFACE}${NEXT_SLOT}"
|
||||||
done
|
whiptail --yesno "$(translate 'Do you want to make this disk bootable?')" 10 60 && {
|
||||||
echo -ne "\n"
|
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')"
|
||||||
IMPORT_STATUS=${PIPESTATUS[0]} # Capture the exit status of the main command
|
else
|
||||||
|
msg_error "$(translate 'Could not configure the disk as bootable')"
|
||||||
if [ $IMPORT_STATUS -eq 0 ]; then
|
fi
|
||||||
msg_ok "$(translate 'Image imported successfully')"
|
}
|
||||||
|
else
|
||||||
# Read the imported disk from the temporary file
|
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
|
||||||
IMPORTED_DISK=$(cat "$TEMP_DISK_FILE")
|
fi
|
||||||
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
|
|
||||||
else
|
else
|
||||||
msg_error "$(translate 'Could not import') $IMAGE"
|
msg_error "$(translate 'Could not find the imported disk')"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
msg_error "$(translate 'Could not import') $IMAGE"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
msg_ok "$(translate 'All selected images have been processed')"
|
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
|
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Author : MacRimi
|
# Author : MacRimi
|
||||||
|
# Revision : @Blaspt (USB passthrough via udev rule with persistent /dev/coral)
|
||||||
# Copyright : (c) 2024 MacRimi
|
# Copyright : (c) 2024 MacRimi
|
||||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||||
# Version : 1.0
|
# Version : 1.1
|
||||||
# Last Updated: 28/01/2025
|
# Last Updated: 16/05/2025
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Description:
|
# Description:
|
||||||
# This script automates the configuration and installation of
|
# This script automates the configuration and installation of
|
||||||
@ -17,13 +18,10 @@
|
|||||||
# - Installs necessary drivers inside the container
|
# - Installs necessary drivers inside the container
|
||||||
# - Manages required system and container restarts
|
# - Manages required system and container restarts
|
||||||
#
|
#
|
||||||
# The script aims to simplify the process of enabling
|
# Supports Coral USB and Coral M.2 (PCIe) devices.
|
||||||
# AI-powered video analysis capabilities in containers
|
# Includes USB passthrough enhancement using persistent udev alias (/dev/coral).
|
||||||
# LXC, leveraging hardware acceleration for
|
|
||||||
# improved performance.
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# Configuration ============================================
|
|
||||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||||
BASE_DIR="/usr/local/share/proxmenux"
|
BASE_DIR="/usr/local/share/proxmenux"
|
||||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||||
@ -38,10 +36,7 @@ initialize_cache
|
|||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
|
|
||||||
# Select LXC container
|
|
||||||
select_container() {
|
select_container() {
|
||||||
|
|
||||||
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | xargs -n2)
|
CONTAINERS=$(pct list | awk 'NR>1 {print $1, $3}' | xargs -n2)
|
||||||
if [ -z "$CONTAINERS" ]; then
|
if [ -z "$CONTAINERS" ]; then
|
||||||
msg_error "$(translate 'No containers available in Proxmox.')"
|
msg_error "$(translate 'No containers available in Proxmox.')"
|
||||||
@ -64,15 +59,12 @@ select_container() {
|
|||||||
msg_ok "$(translate 'Container selected:') $CONTAINER_ID"
|
msg_ok "$(translate 'Container selected:') $CONTAINER_ID"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Validate that the selected container is valid
|
|
||||||
validate_container_id() {
|
validate_container_id() {
|
||||||
if [ -z "$CONTAINER_ID" ]; then
|
if [ -z "$CONTAINER_ID" ]; then
|
||||||
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
|
msg_error "$(translate 'Container ID not defined. Make sure to select a container first.')"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if the container is running and stop it before configuration
|
|
||||||
if pct status "$CONTAINER_ID" | grep -q "running"; then
|
if pct status "$CONTAINER_ID" | grep -q "running"; then
|
||||||
msg_info "$(translate 'Stopping the container before applying configuration...')"
|
msg_info "$(translate 'Stopping the container before applying configuration...')"
|
||||||
pct stop "$CONTAINER_ID"
|
pct stop "$CONTAINER_ID"
|
||||||
@ -80,8 +72,20 @@ validate_container_id() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Añadir regla udev para Coral USB para persistencia de permisos
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
# Configure LXC for Coral TPU and iGPU
|
|
||||||
configure_lxc_hardware() {
|
configure_lxc_hardware() {
|
||||||
validate_container_id
|
validate_container_id
|
||||||
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
|
CONFIG_FILE="/etc/pve/lxc/${CONTAINER_ID}.conf"
|
||||||
@ -90,6 +94,7 @@ configure_lxc_hardware() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Privileged container
|
||||||
if grep -q "^unprivileged: 1" "$CONFIG_FILE"; then
|
if grep -q "^unprivileged: 1" "$CONFIG_FILE"; then
|
||||||
msg_info "$(translate 'The container is unprivileged. Changing to privileged...')"
|
msg_info "$(translate 'The container is unprivileged. Changing to privileged...')"
|
||||||
sed -i "s/^unprivileged: 1/unprivileged: 0/" "$CONFIG_FILE"
|
sed -i "s/^unprivileged: 1/unprivileged: 0/" "$CONFIG_FILE"
|
||||||
@ -103,11 +108,12 @@ configure_lxc_hardware() {
|
|||||||
msg_ok "$(translate 'The container is already privileged.')"
|
msg_ok "$(translate 'The container is already privileged.')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Configure iGPU
|
# Enable nesting feature
|
||||||
if ! grep -q "features: nesting=1" "$CONFIG_FILE"; then
|
if ! grep -q "features: nesting=1" "$CONFIG_FILE"; then
|
||||||
echo "features: nesting=1" >> "$CONFIG_FILE"
|
echo "features: nesting=1" >> "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# iGPU support
|
||||||
if ! grep -q "c 226:0 rwm" "$CONFIG_FILE"; then
|
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:0 rwm # iGPU" >> "$CONFIG_FILE"
|
||||||
echo "lxc.cgroup2.devices.allow: c 226:128 rwm # iGPU" >> "$CONFIG_FILE"
|
echo "lxc.cgroup2.devices.allow: c 226:128 rwm # iGPU" >> "$CONFIG_FILE"
|
||||||
@ -115,6 +121,7 @@ configure_lxc_hardware() {
|
|||||||
echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >> "$CONFIG_FILE"
|
echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Framebuffer support
|
||||||
if ! grep -q "c 29:0 rwm # Framebuffer" "$CONFIG_FILE"; then
|
if ! grep -q "c 29:0 rwm # Framebuffer" "$CONFIG_FILE"; then
|
||||||
echo "lxc.cgroup2.devices.allow: c 29:0 rwm # Framebuffer" >> "$CONFIG_FILE"
|
echo "lxc.cgroup2.devices.allow: c 29:0 rwm # Framebuffer" >> "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
@ -123,30 +130,37 @@ configure_lxc_hardware() {
|
|||||||
echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >> "$CONFIG_FILE"
|
echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||||
fi
|
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
|
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"
|
echo "lxc.cgroup2.devices.allow: c 189:* rwm # Coral USB" >> "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
|
if ! grep -Pq "^lxc.mount.entry: /dev/coral dev/coral none bind,optional,create=file$" "$CONFIG_FILE"; then
|
||||||
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/coral dev/coral none bind,optional,create=file" >> "$CONFIG_FILE"
|
||||||
echo "lxc.mount.entry: /dev/bus/usb dev/bus/usb none bind,optional,create=dir" >> "$CONFIG_FILE"
|
|
||||||
fi
|
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"
|
# 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
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
msg_ok "$(translate 'Coral TPU and iGPU configuration added to container') $CONTAINER_ID."
|
msg_ok "$(translate 'Coral TPU and iGPU configuration added to container') $CONTAINER_ID."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Install Coral TPU drivers in the container
|
|
||||||
install_coral_in_container() {
|
install_coral_in_container() {
|
||||||
|
|
||||||
msg_info2 "$(translate 'Installing iGPU and Coral TPU drivers inside the container...')"
|
msg_info2 "$(translate 'Installing iGPU and Coral TPU drivers inside the container...')"
|
||||||
tput sc
|
tput sc
|
||||||
LOG_FILE=$(mktemp)
|
LOG_FILE=$(mktemp)
|
||||||
|
|
||||||
pct start "$CONTAINER_ID"
|
pct start "$CONTAINER_ID"
|
||||||
|
|
||||||
CORAL_M2=$(lspci | grep -i "Global Unichip")
|
CORAL_M2=$(lspci | grep -i "Global Unichip")
|
||||||
@ -188,25 +202,22 @@ install_coral_in_container() {
|
|||||||
'" "$LOG_FILE"
|
'" "$LOG_FILE"
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
tput rc
|
tput rc
|
||||||
tput ed
|
tput ed
|
||||||
rm -f "$LOG_FILE"
|
rm -f "$LOG_FILE"
|
||||||
msg_ok "$(translate 'iGPU and Coral TPU drivers installed inside the container.')"
|
msg_ok "$(translate 'iGPU and Coral TPU drivers installed inside the container.')"
|
||||||
else
|
else
|
||||||
msg_error "$(translate 'Failed to install iGPU and Coral TPU drivers inside the container.')"
|
msg_error "$(translate 'Failed to install iGPU and Coral TPU drivers inside the container.')"
|
||||||
cat "$LOG_FILE"
|
cat "$LOG_FILE"
|
||||||
rm -f "$LOG_FILE"
|
rm -f "$LOG_FILE"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select_container
|
||||||
|
show_proxmenux_logo
|
||||||
select_container
|
|
||||||
configure_lxc_hardware
|
configure_lxc_hardware
|
||||||
install_coral_in_container
|
install_coral_in_container
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
msg_ok "$(translate 'Configuration completed.')"
|
msg_ok "$(translate 'Configuration completed.')"
|
||||||
sleep 2
|
sleep 2
|
||||||
|
@ -68,6 +68,7 @@ verify_and_add_repos() {
|
|||||||
|
|
||||||
# Function to install Coral TPU drivers on the host
|
# Function to install Coral TPU drivers on the host
|
||||||
install_coral_host() {
|
install_coral_host() {
|
||||||
|
show_proxmenux_logo
|
||||||
verify_and_add_repos
|
verify_and_add_repos
|
||||||
|
|
||||||
apt-get install -y git devscripts dh-dkms dkms pve-headers-$(uname -r) >/dev/null 2>&1
|
apt-get install -y git devscripts dh-dkms dkms pve-headers-$(uname -r) >/dev/null 2>&1
|
||||||
|
@ -64,6 +64,7 @@ change_language() {
|
|||||||
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
|
"pt" "$(translate "Portuguese")" 3>&1 1>&2 2>&3)
|
||||||
|
|
||||||
if [ -z "$LANGUAGE" ]; then
|
if [ -z "$LANGUAGE" ]; then
|
||||||
|
clear
|
||||||
msg_error "$(translate "No language selected.")"
|
msg_error "$(translate "No language selected.")"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@ -75,7 +76,7 @@ change_language() {
|
|||||||
else
|
else
|
||||||
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
echo "{\"language\": \"$LANGUAGE\"}" > "$CONFIG_FILE"
|
||||||
fi
|
fi
|
||||||
|
clear
|
||||||
msg_ok "$(translate "Language changed to") $LANGUAGE"
|
msg_ok "$(translate "Language changed to") $LANGUAGE"
|
||||||
|
|
||||||
# Reload the menu
|
# Reload the menu
|
||||||
|
@ -67,8 +67,8 @@ function header_info() {
|
|||||||
# MAIN EXECUTION
|
# MAIN EXECUTION
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
header_info
|
#header_info
|
||||||
echo -e "\n Loading..."
|
#echo -e "\n Loading..."
|
||||||
#sleep 1
|
#sleep 1
|
||||||
|
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ function start_vm_configuration() {
|
|||||||
while true; do
|
while true; do
|
||||||
OS_TYPE=$(dialog --backtitle "ProxMenux" \
|
OS_TYPE=$(dialog --backtitle "ProxMenux" \
|
||||||
--title "$(translate "Select System Type")" \
|
--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" \
|
1 "$(translate "Create") VM System NAS" \
|
||||||
2 "$(translate "Create") VM System Windows" \
|
2 "$(translate "Create") VM System Windows" \
|
||||||
3 "$(translate "Create") VM System Linux" \
|
3 "$(translate "Create") VM System Linux" \
|
||||||
|
@ -67,9 +67,9 @@ function header_info() {
|
|||||||
# MAIN EXECUTION
|
# MAIN EXECUTION
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
header_info
|
#header_info
|
||||||
echo -e "\n Loading..."
|
#echo -e "\n Loading..."
|
||||||
sleep 1
|
#sleep 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,43 +25,43 @@ initialize_cache
|
|||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
OPTION=$(whiptail --title "$(translate "GPUs and Coral-TPU Menu")" --menu "$(translate "Select an option:")" 20 70 8 \
|
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "GPUs and Coral-TPU Menu")" \
|
||||||
"1" "$(translate "Add HW iGPU acceleration to an LXC")" \
|
--menu "\n$(translate "Select an option:")" 20 70 8 \
|
||||||
"2" "$(translate "Add Coral TPU to an LXC")" \
|
"1" "$(translate "Add HW iGPU acceleration to an LXC")" \
|
||||||
"3" "$(translate "Install/Update Coral TPU on the Host")" \
|
"2" "$(translate "Add Coral TPU to an LXC")" \
|
||||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
"3" "$(translate "Install/Update Coral TPU on the Host")" \
|
||||||
|
"4" "$(translate "Return to Main Menu")" \
|
||||||
|
2>&1 >/dev/tty)
|
||||||
|
|
||||||
case $OPTION in
|
case $OPTION in
|
||||||
1)
|
1)
|
||||||
show_proxmenux_logo
|
|
||||||
msg_info2 "$(translate "Running script:") $(translate " HW iGPU acceleration LXC")..."
|
|
||||||
bash <(curl -s "$REPO_URL/scripts/configure_igpu_lxc.sh")
|
bash <(curl -s "$REPO_URL/scripts/configure_igpu_lxc.sh")
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
|
clear
|
||||||
|
show_proxmenux_logo
|
||||||
msg_warn "$(translate "Operation cancelled.")"
|
msg_warn "$(translate "Operation cancelled.")"
|
||||||
sleep 2
|
sleep 2
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
show_proxmenux_logo
|
|
||||||
msg_info2 "$(translate "Running script:") Coral TPU LXC..."
|
|
||||||
bash <(curl -s "$REPO_URL/scripts/install_coral_lxc.sh")
|
bash <(curl -s "$REPO_URL/scripts/install_coral_lxc.sh")
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
|
clear
|
||||||
|
show_proxmenux_logo
|
||||||
msg_warn "$(translate "Operation cancelled.")"
|
msg_warn "$(translate "Operation cancelled.")"
|
||||||
sleep 2
|
sleep 2
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
show_proxmenux_logo
|
|
||||||
msg_info2 "$(translate "Running script:") $(translate "Install/Update") Coral..."
|
|
||||||
bash <(curl -s "$REPO_URL/scripts/install_coral_pve.sh")
|
bash <(curl -s "$REPO_URL/scripts/install_coral_pve.sh")
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
|
clear
|
||||||
|
show_proxmenux_logo
|
||||||
msg_warn "$(translate "Operation cancelled.")"
|
msg_warn "$(translate "Operation cancelled.")"
|
||||||
sleep 2
|
sleep 2
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
4) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||||
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
*) exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -37,13 +37,13 @@ show_menu() {
|
|||||||
dialog --clear \
|
dialog --clear \
|
||||||
--backtitle "ProxMenux" \
|
--backtitle "ProxMenux" \
|
||||||
--title "$(translate "Main 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")" \
|
1 "$(translate "Settings post-install Proxmox")" \
|
||||||
2 "$(translate "Help and Info Commands")" \
|
2 "$(translate "Help and Info Commands")" \
|
||||||
3 "$(translate "Hardware: GPUs and Coral-TPU")" \
|
3 "$(translate "Hardware: GPUs and Coral-TPU")" \
|
||||||
4 "$(translate "Create VM from template or script")" \
|
4 "$(translate "Create VM from template or script")" \
|
||||||
5 "$(translate "Disk and Storage Manager")" \
|
5 "$(translate "Disk and Storage Manager")" \
|
||||||
6 "$(translate "Essential Proxmox VE Helper-Scripts")" \
|
6 "$(translate "Proxmox VE Helper Scripts")" \
|
||||||
7 "$(translate "Network")" \
|
7 "$(translate "Network")" \
|
||||||
8 "$(translate "Settings")" \
|
8 "$(translate "Settings")" \
|
||||||
9 "$(translate "Exit")" 2>"$TEMP_FILE"
|
9 "$(translate "Exit")" 2>"$TEMP_FILE"
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
# Author : MacRimi
|
# Author : MacRimi
|
||||||
# Copyright : (c) 2024 MacRimi
|
# Copyright : (c) 2024 MacRimi
|
||||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||||
# Version : 1.0
|
# Version : 1.1
|
||||||
# Last Updated: 28/01/2025
|
# Last Updated: 04/06/2025
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Description:
|
# 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/).
|
# 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,
|
# It serves as a convenient tool to run key automation scripts that simplify system management,
|
||||||
@ -31,156 +31,297 @@ fi
|
|||||||
|
|
||||||
load_language
|
load_language
|
||||||
initialize_cache
|
initialize_cache
|
||||||
#show_proxmenux_logo
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# Base URL community-scripts
|
HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json"
|
||||||
BASE_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc"
|
METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json"
|
||||||
BASE_URL2="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
|
|
||||||
|
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() {
|
download_script() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
local fallback_pve="${url/misc/tools\/pve}"
|
local fallback_pve="${url/misc\/tools\/pve}"
|
||||||
local fallback_addon="${url/misc/tools\/addon}"
|
local fallback_addon="${url/misc\/tools\/addon}"
|
||||||
local fallback_copydata="${url/misc/tools\/copy-data}"
|
local fallback_copydata="${url/misc\/tools\/copy-data}"
|
||||||
|
|
||||||
if curl --silent --head --fail "$url" >/dev/null; then
|
if curl --silent --head --fail "$url" >/dev/null; then
|
||||||
bash <(curl -s "$url")
|
bash <(curl -s "$url")
|
||||||
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
|
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
|
||||||
bash <(curl -s "$fallback_pve")
|
bash <(curl -s "$fallback_pve")
|
||||||
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
|
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
|
||||||
bash <(curl -s "$fallback_addon")
|
bash <(curl -s "$fallback_addon")
|
||||||
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
|
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
|
||||||
bash <(curl -s "$fallback_copydata")
|
bash <(curl -s "$fallback_copydata")
|
||||||
else
|
else
|
||||||
msg_error "$(translate 'Error: Failed to download the script.')\033[0m"
|
dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70
|
||||||
msg_error "\n$(translate 'Tried URLs:')\n- $url\n- $fallback_pve\n- $fallback_addons\n- $fallback_copydata\n"
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
msg_success "$(translate "Press Enter to return to menu...")"
|
RETURN_TO_MAIN=false
|
||||||
read -r
|
|
||||||
|
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
|
||||||
|
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
|
local notes_dialog=""
|
||||||
scripts=(
|
if [[ -n "$notes" ]]; then
|
||||||
"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"
|
while IFS= read -r line; do
|
||||||
"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!"
|
notes_dialog+="• $line\n"
|
||||||
"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."
|
done <<< "$notes"
|
||||||
"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."
|
notes_dialog="${notes_dialog%\\n}"
|
||||||
"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"
|
fi
|
||||||
"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 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() {
|
local msg="\Zb\Z4Descripción:\Zn\n$desc"
|
||||||
while IFS='|' read -r name category url description; do
|
[[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog"
|
||||||
category=$(echo "$category" | xargs)
|
[[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials"
|
||||||
order=${category_order[$category]:-999}
|
|
||||||
printf "%d|%s|%s|%s|%s\n" "$order" "$name" "$category" "$url" "$description"
|
dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85
|
||||||
done | sort -n | cut -d'|' -f2-
|
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
|
while true; do
|
||||||
IFS=$'\n' sorted_scripts=($(printf "%s\n" "${scripts[@]}" | custom_sort))
|
declare -A index_to_slug
|
||||||
unset IFS
|
local menu_items=()
|
||||||
|
local i=1
|
||||||
HEADER=$(printf " %-57s %-20s" "$(translate "Name")" "$(translate "Category")")
|
|
||||||
|
while IFS=$'\t' read -r slug name type; do
|
||||||
menu_items=()
|
index_to_slug[$i]="$slug"
|
||||||
for script in "${sorted_scripts[@]}"; do
|
local label
|
||||||
IFS='|' read -r name category url description <<< "$script"
|
label=$(get_type_label "$type")
|
||||||
translated_category=$(translate "$category")
|
local padded_name
|
||||||
padded_name=$(printf "%-57s" "$name")
|
padded_name=$(printf "%-42s" "$name")
|
||||||
menu_items+=("$padded_name" "$translated_category")
|
local entry="$padded_name $label"
|
||||||
done
|
menu_items+=("$i" "$entry")
|
||||||
|
((i++))
|
||||||
menu_items+=("$(translate "Return to Main Menu")" "")
|
done < <(echo "$filtered_json" | jq -r '
|
||||||
|
sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||||
cleanup
|
|
||||||
|
menu_items+=("" "")
|
||||||
script_selection=$(whiptail --title "$(translate "Essential Proxmox VE Helper-Scripts")" \
|
menu_items+=("new_search" "New Search")
|
||||||
--menu "\n$HEADER\n\n$(translate "Select a script to execute")" 25 78 16 \
|
menu_items+=("show_all" "Show All Scripts")
|
||||||
"${menu_items[@]}" 3>&1 1>&2 2>&3)
|
|
||||||
|
local title="Search Results"
|
||||||
if [ -n "$script_selection" ]; then
|
if [[ -n "$search_term" ]]; then
|
||||||
script_selection=$(echo "$script_selection" | xargs)
|
title="Search Results for: '$search_term' ($count found)"
|
||||||
if [ "$script_selection" = "$(translate "Return to Main Menu")" ]; then
|
else
|
||||||
|
title="All Available Scripts ($count total)"
|
||||||
|
fi
|
||||||
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
|
local selected
|
||||||
|
selected=$(dialog --colors --backtitle "ProxMenux" \
|
||||||
|
--title "$title" \
|
||||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
--menu "Select a script or action:" \
|
||||||
fi
|
22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||||
|
|
||||||
for script in "${sorted_scripts[@]}"; do
|
if [[ $? -ne 0 ]]; then
|
||||||
IFS='|' read -r name category url description <<< "$script"
|
return
|
||||||
if [ "$name" = "$script_selection" ]; then
|
fi
|
||||||
selected_url="$url"
|
|
||||||
selected_description=$(translate "$description")
|
case "$selected" in
|
||||||
break
|
"new_search")
|
||||||
fi
|
break
|
||||||
done
|
;;
|
||||||
|
"show_all")
|
||||||
if [ -n "$selected_url" ]; then
|
search_term=""
|
||||||
if whiptail --title "$(translate "Script Information")" \
|
filtered_json="$CACHE_JSON"
|
||||||
--yes-button "$(translate "Accept")" \
|
count=$(echo "$filtered_json" | jq length)
|
||||||
--no-button "$(translate "Cancel")" \
|
continue
|
||||||
--yesno "$selected_description" 20 78; then
|
;;
|
||||||
#msg_info2 "$(translate "Executing script:") $script_selection"
|
"back"|"")
|
||||||
#sleep 2
|
return
|
||||||
download_script "$selected_url"
|
;;
|
||||||
msg_ok "$(translate "Script completed.")"
|
*)
|
||||||
msg_success "$(translate "Press Enter to return to the main menu...")"
|
if [[ -n "${index_to_slug[$selected]}" ]]; then
|
||||||
read -r
|
run_script_by_slug "${index_to_slug[$selected]}"
|
||||||
clear
|
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; }
|
||||||
else
|
fi
|
||||||
msg_info2 "$(translate "Script execution cancelled.")"
|
;;
|
||||||
sleep 2
|
esac
|
||||||
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
|
|
||||||
done
|
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
|
SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \
|
||||||
show_proxmenux_logo
|
"Select a category or search for scripts:" 20 70 14 \
|
||||||
msg_lang "$(translate "Generating automatic translations...")"
|
"${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || {
|
||||||
fi
|
dialog --clear --title "Proxmox VE Helper-Scripts" \
|
||||||
show_menu
|
--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
|
||||||
|
186
scripts/menus/menu_Helper_Scripts_bak.sh
Normal file
186
scripts/menus/menu_Helper_Scripts_bak.sh
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
#!/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 provides a simple and efficient way to access and execute essential 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
|
||||||
|
#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"
|
||||||
|
|
||||||
|
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
|
||||||
|
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"
|
||||||
|
|
||||||
|
msg_success "$(translate "Press Enter to return to menu...")"
|
||||||
|
read -r
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
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-
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "$LANGUAGE" != "en" ]]; then
|
||||||
|
show_proxmenux_logo
|
||||||
|
msg_lang "$(translate "Generating automatic translations...")"
|
||||||
|
fi
|
||||||
|
show_menu
|
@ -6,11 +6,10 @@
|
|||||||
# Author : MacRimi
|
# Author : MacRimi
|
||||||
# Copyright : (c) 2024 MacRimi
|
# Copyright : (c) 2024 MacRimi
|
||||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||||
# Version : 1.0
|
# Version : 1.1
|
||||||
# Last Updated: 24/02/2025
|
# Last Updated: 28/05/2025
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
# Configuration ============================================
|
|
||||||
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main"
|
||||||
BASE_DIR="/usr/local/share/proxmenux"
|
BASE_DIR="/usr/local/share/proxmenux"
|
||||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||||
@ -19,21 +18,24 @@ VENV_PATH="/opt/googletrans-env"
|
|||||||
if [[ -f "$UTILS_FILE" ]]; then
|
if [[ -f "$UTILS_FILE" ]]; then
|
||||||
source "$UTILS_FILE"
|
source "$UTILS_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
load_language
|
load_language
|
||||||
initialize_cache
|
initialize_cache
|
||||||
#show_proxmenux_logo
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
confirm_and_run() {
|
confirm_and_run() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
local command="$2"
|
local command="$2"
|
||||||
|
|
||||||
if whiptail --title "$(translate "Confirmation")" \
|
dialog --clear --title "$(translate "Confirmation")" \
|
||||||
--yesno "$(translate "Do you want to run the post-installation script from") $name?" \
|
--yesno "\n\n$(translate "Do you want to run the post-installation script from") $name?" 10 70
|
||||||
10 70; then
|
response=$?
|
||||||
|
clear
|
||||||
|
|
||||||
|
if [ $response -eq 0 ]; then
|
||||||
eval "$command"
|
eval "$command"
|
||||||
echo ""
|
echo
|
||||||
msg_success "$(translate 'Press ENTER to continue...')"
|
msg_success "$(translate 'Press ENTER to continue...')"
|
||||||
read -r _
|
read -r _
|
||||||
else
|
else
|
||||||
@ -42,62 +44,75 @@ confirm_and_run() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scripts_es=(
|
||||||
|
"Script post-install personalizable |ProxMenux|bash <(curl -s $REPO_URL/scripts/customizable_post_install.sh)"
|
||||||
|
"Script post-install Proxmox VE |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 _\""
|
||||||
|
"Script post-iinstall xshok-proxmox|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\""
|
||||||
|
"Desinstalar herramientas|ProxMenux|bash <(curl -s $REPO_URL/scripts/uninstall-tools.sh)"
|
||||||
|
)
|
||||||
|
|
||||||
|
scripts_all_langs=(
|
||||||
# Define scripts array
|
|
||||||
scripts=(
|
|
||||||
"Customizable script post-installation|ProxMenux|bash <(curl -s $REPO_URL/scripts/customizable_post_install.sh)"
|
"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 _\""
|
"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\""
|
"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)"
|
"Uninstall Tools|ProxMenux|bash <(curl -s $REPO_URL/scripts/uninstall-tools.sh)"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
show_menu() {
|
show_menu() {
|
||||||
while true; do
|
while true; do
|
||||||
HEADER=$(printf " %-52s %-20s" "$(translate "Name")" "$(translate "Repository")")
|
local HEADER
|
||||||
|
local current_scripts=()
|
||||||
|
|
||||||
|
if [[ "$LANGUAGE" == "es" ]]; then
|
||||||
|
HEADER="\n Seleccione un script post-instalación:\n\n Descripción │ Fuente"
|
||||||
|
current_scripts=("${scripts_es[@]}")
|
||||||
|
else
|
||||||
|
HEADER="\n$(translate " Select a post-installation script:")\n\n Description │ Source"
|
||||||
|
current_scripts=("${scripts_all_langs[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
menu_items=()
|
menu_items=()
|
||||||
for i in "${!scripts[@]}"; do
|
|
||||||
IFS='|' read -r name repository command <<< "${scripts[$i]}"
|
for i in "${!current_scripts[@]}"; do
|
||||||
|
IFS='|' read -r name repository command <<< "${current_scripts[$i]}"
|
||||||
number=$((i+1))
|
number=$((i+1))
|
||||||
padded_option=$(printf "%2d %-50s" "$number" "$(translate "$name")")
|
local display_name="$name"
|
||||||
menu_items+=("$padded_option" "$repository")
|
[[ "$LANGUAGE" != "es" ]] && display_name="$(translate "$name")"
|
||||||
|
formatted_line=$(printf "%-47s │ %s" "$display_name" "$repository")
|
||||||
|
menu_items+=("$number" "$formatted_line")
|
||||||
done
|
done
|
||||||
|
|
||||||
menu_items+=("$(printf "%2d %-40s" "$((${#scripts[@]}+1))" "$(translate "Return to Main Menu")")" "")
|
menu_items+=("$(( ${#current_scripts[@]}+1 ))" "$(translate "Return to Main Menu")")
|
||||||
|
|
||||||
|
exec 3>&1
|
||||||
|
script_selection=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Post-Installation Scripts Menu")" \
|
||||||
|
--menu "$HEADER" 20 78 $((${#menu_items[@]}/2)) \
|
||||||
|
"${menu_items[@]}" 2>&1 1>&3)
|
||||||
|
exit_status=$?
|
||||||
|
exec 3>&-
|
||||||
|
|
||||||
|
|
||||||
cleanup
|
|
||||||
|
|
||||||
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
|
if [ $exit_status -ne 0 ]; 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
|
|
||||||
|
|
||||||
index=$((selected_number - 1))
|
|
||||||
if [ $index -ge 0 ] && [ $index -lt ${#scripts[@]} ]; then
|
|
||||||
IFS='|' read -r name repository command <<< "${scripts[$index]}"
|
|
||||||
eval "$command"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
#show_proxmenux_logo
|
|
||||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$script_selection" = "$(( ${#current_scripts[@]} + 1 ))" ]; then
|
||||||
|
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||||
|
fi
|
||||||
|
|
||||||
|
index=$((script_selection - 1))
|
||||||
|
if [ $index -ge 0 ] && [ $index -lt ${#current_scripts[@]} ]; then
|
||||||
|
IFS='|' read -r _ _ command <<< "${current_scripts[$index]}"
|
||||||
|
eval "$command"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
clear
|
|
||||||
if [[ "$LANGUAGE" != "en" ]]; then
|
|
||||||
show_proxmenux_logo
|
|
||||||
msg_lang "$(translate "Generating automatic translations...")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
#if [[ "$LANGUAGE" != "en" ]]; then
|
||||||
|
# show_proxmenux_logo
|
||||||
|
# msg_lang "$(translate "Generating automatic translations...")"
|
||||||
|
#fi
|
||||||
|
cleanup
|
||||||
show_menu
|
show_menu
|
@ -26,36 +26,32 @@ initialize_cache
|
|||||||
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
OPTION=$(whiptail --title "$(translate "Disk and Storage Manager Menu")" --menu "$(translate "Select an option:")" 20 70 10 \
|
OPTION=$(dialog --clear --backtitle "ProxMenux" --title "$(translate "Disk and Storage Manager Menu")" \
|
||||||
"1" "$(translate "Add Disk Passthrough to a VM")" \
|
--menu "\n$(translate "Select an option:")" 20 70 10 \
|
||||||
"2" "$(translate "Add Disk") Passthrough $(translate "to a CT")" \
|
"1" "$(translate "Add Disk") Passthrough $(translate "to a VM")" \
|
||||||
"3" "$(translate "Import Disk Image to a VM")" \
|
"2" "$(translate "Add Disk") Passthrough $(translate "to a LXC")" \
|
||||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
"3" "$(translate "Import Disk Image to a VM")" \
|
||||||
|
"4" "$(translate "Return to Main Menu")" \
|
||||||
|
2>&1 >/dev/tty)
|
||||||
|
|
||||||
case $OPTION in
|
case $OPTION in
|
||||||
1)
|
1)
|
||||||
#show_proxmenux_logo
|
clear
|
||||||
msg_info2 "$(translate "Running script: Add Disk Passthrough to a VM")..."
|
|
||||||
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough.sh")
|
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough.sh")
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
#show_proxmenux_logo
|
clear
|
||||||
msg_info2 "$(translate "Running script: Add Disk Passthrough to a CT")..."
|
|
||||||
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough_ct.sh")
|
bash <(curl -s "$REPO_URL/scripts/storage/disk-passthrough_ct.sh")
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
#show_proxmenux_logo
|
clear
|
||||||
msg_info2 "$(translate "Running script: Import Disk Image to a VM")..."
|
|
||||||
bash <(curl -s "$REPO_URL/scripts/storage/import-disk-image.sh")
|
bash <(curl -s "$REPO_URL/scripts/storage/import-disk-image.sh")
|
||||||
;;
|
;;
|
||||||
4)
|
4)
|
||||||
#show_proxmenux_logo
|
|
||||||
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
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")
|
exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh")
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
# Author : MacRimi
|
# Author : MacRimi
|
||||||
# Copyright : (c) 2024 MacRimi
|
# Copyright : (c) 2024 MacRimi
|
||||||
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE)
|
||||||
# Version : 1.0
|
# Version : 1.1
|
||||||
# Last Updated: 28/01/2025
|
# Last Updated: 29/05/2025
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Description:
|
# Description:
|
||||||
# This script automates the process of importing disk images into Proxmox VE virtual machines (VMs),
|
# 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.
|
# 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/.
|
# 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:
|
# Using an interactive menu, you can:
|
||||||
# - Select a VM to attach the imported disk.
|
# - Select a VM to attach the imported disk.
|
||||||
@ -32,40 +32,65 @@ BASE_DIR="/usr/local/share/proxmenux"
|
|||||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||||
VENV_PATH="/opt/googletrans-env"
|
VENV_PATH="/opt/googletrans-env"
|
||||||
|
|
||||||
if [[ -f "$UTILS_FILE" ]]; then
|
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||||
source "$UTILS_FILE"
|
|
||||||
fi
|
|
||||||
load_language
|
load_language
|
||||||
initialize_cache
|
initialize_cache
|
||||||
# ==========================================================
|
# Configuration ============================================
|
||||||
|
|
||||||
# Path where disk images are stored
|
|
||||||
IMAGES_DIR="/var/lib/vz/template/images/"
|
|
||||||
|
|
||||||
|
|
||||||
# Initial setup
|
|
||||||
if [ ! -d "$IMAGES_DIR" ]; then
|
detect_image_dir() {
|
||||||
msg_info "$(translate 'Creating images directory')"
|
for store in $(pvesm status -content images | awk 'NR>1 {print $1}'); do
|
||||||
mkdir -p "$IMAGES_DIR"
|
path=$(pvesm path "${store}:template" 2>/dev/null)
|
||||||
chmod 755 "$IMAGES_DIR"
|
if [[ -d "$path" ]]; then
|
||||||
msg_ok "$(translate 'Images directory created:') $IMAGES_DIR"
|
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
|
fi
|
||||||
|
|
||||||
|
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk|raw)$")
|
||||||
# Check if there are any images in the directory
|
|
||||||
IMAGES=$(ls -A "$IMAGES_DIR" | grep -E "\.(img|qcow2|vmdk)$")
|
|
||||||
if [ -z "$IMAGES" ]; then
|
if [ -z "$IMAGES" ]; then
|
||||||
whiptail --title "$(translate 'No Images Found')" \
|
dialog --title "$(translate 'No Disk 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
|
--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
|
exit 1
|
||||||
fi
|
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
|
# 1. Select VM
|
||||||
msg_info "$(translate 'Getting VM list')"
|
msg_info "$(translate 'Getting VM list')"
|
||||||
VM_LIST=$(qm list | awk 'NR>1 {print $1" "$2}')
|
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)
|
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
|
if [ -z "$VMID" ]; then
|
||||||
# msg_error "$(translate 'No VM selected')"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -93,7 +118,7 @@ if [ -z "$STORAGE_LIST" ]; then
|
|||||||
fi
|
fi
|
||||||
msg_ok "$(translate 'Storage volumes obtained')"
|
msg_ok "$(translate 'Storage volumes obtained')"
|
||||||
|
|
||||||
# Create an array of storage options for whiptail
|
|
||||||
STORAGE_OPTIONS=()
|
STORAGE_OPTIONS=()
|
||||||
while read -r storage; do
|
while read -r storage; do
|
||||||
STORAGE_OPTIONS+=("$storage" "")
|
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)
|
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
|
if [ -z "$STORAGE" ]; then
|
||||||
# msg_error "$(translate 'No storage selected')"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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)
|
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
|
if [ -z "$SELECTED_IMAGES" ]; then
|
||||||
# msg_error "$(translate 'No images selected')"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 4. Import each selected image
|
# 4. Import each selected image
|
||||||
for IMAGE in $SELECTED_IMAGES; do
|
for IMAGE in $SELECTED_IMAGES; do
|
||||||
|
|
||||||
# Remove quotes from selected image
|
|
||||||
IMAGE=$(echo "$IMAGE" | tr -d '"')
|
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 \
|
INTERFACE=$(whiptail --title "$(translate 'Interface Type')" --menu "$(translate 'Select the interface type for the image:') $IMAGE" 15 40 4 \
|
||||||
"sata" "SATA" \
|
"sata" "SATA" \
|
||||||
"scsi" "SCSI" \
|
"scsi" "SCSI" \
|
||||||
@ -148,57 +175,63 @@ for IMAGE in $SELECTED_IMAGES; do
|
|||||||
|
|
||||||
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
FULL_PATH="$IMAGES_DIR/$IMAGE"
|
||||||
|
|
||||||
# Show initial message
|
|
||||||
msg_info "$(translate 'Importing image:')"
|
msg_info "$(translate 'Importing image:')"
|
||||||
|
|
||||||
# Temporary file to capture the imported disk
|
|
||||||
TEMP_DISK_FILE=$(mktemp)
|
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
|
qm importdisk "$VMID" "$FULL_PATH" "$STORAGE" 2>&1 | while read -r line; do
|
||||||
if [[ "$line" =~ transferred ]]; then
|
if [[ "$line" =~ transferred ]]; then
|
||||||
|
|
||||||
# Extract the progress percentage
|
PERCENT=$(echo "$line" | grep -oP "\d+\.\d+(?=%)")
|
||||||
PERCENT=$(echo "$line" | grep -oP "\(\d+\.\d+%\)" | tr -d '()%')
|
|
||||||
|
echo -ne "\r${TAB}${BL}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
||||||
# Show progress with custom format without translation
|
|
||||||
echo -ne "\r${TAB}${YW}-$(translate 'Importing image:') $IMAGE-${CL} ${PERCENT}%"
|
|
||||||
|
|
||||||
elif [[ "$line" =~ successfully\ imported\ disk ]]; then
|
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"
|
echo "$line" | grep -oP "(?<=successfully imported disk ').*(?=')" > "$TEMP_DISK_FILE"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo -ne "\n"
|
echo -ne "\n"
|
||||||
|
|
||||||
|
IMPORT_STATUS=${PIPESTATUS[0]}
|
||||||
IMPORT_STATUS=${PIPESTATUS[0]} # Capture the exit status of the main command
|
|
||||||
|
|
||||||
if [ $IMPORT_STATUS -eq 0 ]; then
|
if [ $IMPORT_STATUS -eq 0 ]; then
|
||||||
msg_ok "$(translate 'Image imported successfully')"
|
msg_ok "$(translate 'Image imported successfully')"
|
||||||
|
|
||||||
# Read the imported disk from the temporary file
|
|
||||||
IMPORTED_DISK=$(cat "$TEMP_DISK_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
|
if [ -n "$IMPORTED_DISK" ]; then
|
||||||
|
|
||||||
# Find the next available disk slot
|
|
||||||
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
|
EXISTING_DISKS=$(qm config "$VMID" | grep -oP "${INTERFACE}\d+" | sort -n)
|
||||||
if [ -z "$EXISTING_DISKS" ]; then
|
if [ -z "$EXISTING_DISKS" ]; then
|
||||||
|
|
||||||
# If there are no existing disks, start from 0
|
|
||||||
NEXT_SLOT=0
|
NEXT_SLOT=0
|
||||||
else
|
else
|
||||||
# If there are existing disks, take the last one and add 1
|
|
||||||
LAST_SLOT=$(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//")
|
LAST_SLOT=$(echo "$EXISTING_DISKS" | tail -n1 | sed "s/${INTERFACE}//")
|
||||||
NEXT_SLOT=$((LAST_SLOT + 1))
|
NEXT_SLOT=$((LAST_SLOT + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
# Ask if SSD emulation is desired (only for non-VirtIO interfaces)
|
|
||||||
if [ "$INTERFACE" != "virtio" ]; then
|
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
|
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"
|
SSD_OPTION=",ssd=1"
|
||||||
@ -209,14 +242,18 @@ for IMAGE in $SELECTED_IMAGES; do
|
|||||||
SSD_OPTION=""
|
SSD_OPTION=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
msg_info "$(translate 'Configuring disk')"
|
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
|
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}"
|
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
|
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')"
|
msg_info "$(translate 'Configuring disk as bootable')"
|
||||||
|
|
||||||
@ -228,14 +265,22 @@ for IMAGE in $SELECTED_IMAGES; do
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
msg_error "$(translate 'Could not configure disk') ${INTERFACE}${NEXT_SLOT} $(translate 'for VM') $VMID"
|
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
|
fi
|
||||||
else
|
else
|
||||||
msg_error "$(translate 'Could not find the imported disk')"
|
msg_error "$(translate 'Could not find the imported disk')"
|
||||||
|
echo "DEBUG: VM config after import:"
|
||||||
|
qm config "$VMID"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
msg_error "$(translate 'Could not import') $IMAGE"
|
msg_error "$(translate 'Could not import') $IMAGE"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
msg_ok "$(translate 'All selected images have been processed')"
|
msg_ok "$(translate 'All selected images have been processed')"
|
||||||
sleep 2
|
msg_success "$(translate "Press Enter to return to menu...")"
|
||||||
|
read -r
|
||||||
|
446
scripts/storage/mount_disk_host_bk.sh
Normal file
446
scripts/storage/mount_disk_host_bk.sh
Normal file
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
240
scripts/test/Iso.sh
Normal file
240
scripts/test/Iso.sh
Normal file
@ -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
|
||||||
|
|
||||||
|
}
|
327
scripts/test/helpers-menu.sh
Normal file
327
scripts/test/helpers-menu.sh
Normal file
@ -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
|
@ -106,14 +106,35 @@ if [[ -z "$ISO_DIR" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
mkdir -p "$ISO_DIR"
|
mkdir -p "$ISO_DIR"
|
||||||
|
|
||||||
TMP_DIR="/root/uup-temp"
|
|
||||||
OUT_DIR="$ISO_DIR"
|
|
||||||
CONVERTER="/root/uup-converter"
|
|
||||||
|
|
||||||
mkdir -p "$TMP_DIR" "$OUT_DIR"
|
DEFAULT_TMP="/root/uup-temp"
|
||||||
cd "$TMP_DIR" || exit 1
|
USER_INPUT=$(dialog --inputbox "Enter temporary folder path (default: $DEFAULT_TMP):" 10 60 "$DEFAULT_TMP" 3>&1 1>&2 2>&3)
|
||||||
|
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=$(detect_iso_dir)
|
||||||
|
[[ -z "$OUT_DIR" ]] && msg_error "$(translate "Could not determine a valid ISO directory.")" && exit 1
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
|
||||||
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
|
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
|
||||||
@ -207,7 +228,12 @@ if [[ -f "$ISO_FILE" ]]; then
|
|||||||
|
|
||||||
|
|
||||||
msg_ok "$(translate "Cleaning temporary files...")"
|
msg_ok "$(translate "Cleaning temporary files...")"
|
||||||
|
if [[ "$CLEAN_ALL" == true ]]; then
|
||||||
rm -rf "$TMP_DIR" "$CONVERTER"
|
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||||
|
else
|
||||||
|
[[ -d "$TMP_DIR" ]] && rm -rf "$TMP_DIR"
|
||||||
|
[[ -d "$CONVERTER" ]] && rm -rf "$CONVERTER"
|
||||||
|
fi
|
||||||
|
|
||||||
export OS_TYPE="windows"
|
export OS_TYPE="windows"
|
||||||
export LANGUAGE=C
|
export LANGUAGE=C
|
||||||
@ -218,15 +244,15 @@ if [[ -f "$ISO_FILE" ]]; then
|
|||||||
|
|
||||||
msg_success "$(translate "Press Enter to return to menu...")"
|
msg_success "$(translate "Press Enter to return to menu...")"
|
||||||
read -r
|
read -r
|
||||||
|
|
||||||
else
|
else
|
||||||
msg_warn "$(translate "No ISO was generated.")"
|
msg_warn "$(translate "No ISO was generated.")"
|
||||||
|
rm -rf "$TMP_DIR" "$CONVERTER"
|
||||||
export LANGUAGE=C
|
export LANGUAGE=C
|
||||||
export LANG=C
|
export LANG=C
|
||||||
export LC_ALL=C
|
export LC_ALL=C
|
||||||
load_language
|
load_language
|
||||||
initialize_cache
|
initialize_cache
|
||||||
|
|
||||||
msg_success "$(translate "Press Enter to return to menu...")"
|
msg_success "$(translate "Press Enter to return to menu...")"
|
||||||
read -r
|
read -r
|
||||||
return 1
|
return 1
|
||||||
|
@ -173,11 +173,26 @@ function configure_vm_advanced() {
|
|||||||
BIOS_TYPE=" -bios $BIOS"
|
BIOS_TYPE=" -bios $BIOS"
|
||||||
|
|
||||||
# CPU Type
|
# 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")" \
|
CPU_CHOICE=$(whiptail --backtitle "ProxMenux" --title "$(translate "CPU Model")" \
|
||||||
--radiolist "$(translate "Select CPU model")" 10 60 2 \
|
--radiolist "$(translate "Select CPU model")" 17 70 11 \
|
||||||
"host" "Host (recommended)" ON \
|
"host" "Host (recommended)" ON \
|
||||||
"kvm64" "Generic KVM64" OFF 3>&1 1>&2 2>&3) || return 1
|
"kvm64" "Generic KVM64" OFF \
|
||||||
[[ "$CPU_CHOICE" == "host" ]] && CPU_TYPE=" -cpu host" || CPU_TYPE=" -cpu kvm64"
|
"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
|
||||||
CORE_COUNT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Number of CPU cores (default: 2)")" \
|
CORE_COUNT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Number of CPU cores (default: 2)")" \
|
||||||
|
@ -11,21 +11,17 @@ export default function CoralTPULXC() {
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h1 className="text-3xl font-bold mb-6">Enable Coral TPU in an LXC</h1>
|
<h1 className="text-3xl font-bold mb-6">Enable Coral TPU in an LXC</h1>
|
||||||
|
|
||||||
<p className="mb-4">
|
<p className="mb-4">
|
||||||
This guide explains how to configure Google Coral TPU support for LXC containers in Proxmox VE using <strong>ProxMenux</strong>.
|
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>.
|
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>
|
</p>
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
|
||||||
<p className="mb-4">The script automates the following steps:</p>
|
<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>
|
||||||
<ol className="list-decimal pl-6 space-y-2 mb-6">
|
<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>
|
||||||
<li>Allows selection of an existing LXC container.</li>
|
<p className="mb-4">The M.2 version is detected automatically and configured only if present.</p>
|
||||||
<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>
|
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
|
||||||
<Steps>
|
<Steps>
|
||||||
<Steps.Step title="Select an LXC Container">
|
<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>Sets device permissions for TPU and iGPU.</li>
|
||||||
<li>Configures proper device mounts.</li>
|
<li>Configures proper device mounts.</li>
|
||||||
</ul>
|
</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>
|
||||||
<Steps.Step title="Install Required Drivers">
|
<Steps.Step title="Install Required Drivers">
|
||||||
<p>The script installs the necessary components inside the container:</p>
|
<p>The script installs the necessary components inside the container:</p>
|
||||||
<ul className="list-disc pl-6 space-y-1 mt-2">
|
<ul className="list-disc pl-6 space-y-1 mt-2">
|
||||||
<li>GPU drivers (va-driver-all, intel-opencl-icd).</li>
|
<li>GPU drivers:</li>
|
||||||
<li>Coral TPU dependencies (Python, GPG keys, repository setup).</li>
|
<ul className="list-disc pl-10">
|
||||||
<li>Coral TPU drivers (USB and M.2 support).</li>
|
<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>
|
</ul>
|
||||||
</Steps.Step>
|
</Steps.Step>
|
||||||
<Steps.Step title="Select Coral TPU Driver Version">
|
<Steps.Step title="Select Coral TPU Driver Version">
|
||||||
@ -56,7 +86,7 @@ export default function CoralTPULXC() {
|
|||||||
</ul>
|
</ul>
|
||||||
</Steps.Step>
|
</Steps.Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Expected Results</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Expected Results</h2>
|
||||||
<ul className="list-disc pl-6 space-y-2 mb-6">
|
<ul className="list-disc pl-6 space-y-2 mb-6">
|
||||||
<li>The selected container is correctly configured for TPU and iGPU usage.</li>
|
<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>The container will restart as needed during the process.</li>
|
||||||
<li>After completion, applications inside the container can utilize Coral TPU acceleration.</li>
|
<li>After completion, applications inside the container can utilize Coral TPU acceleration.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Important Considerations</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Important Considerations</h2>
|
||||||
<ul className="list-disc pl-6 space-y-2 mb-6">
|
<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 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>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>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>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,58 +10,79 @@ export default function InstallCoralTPUHost() {
|
|||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto">
|
<div className="max-w-3xl mx-auto">
|
||||||
<h1 className="text-3xl font-bold mb-6">Install Coral TPU on the Host</h1>
|
<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/>
|
<p className="mb-4">
|
||||||
This guide explains how to install and configure Google Coral TPU drivers on a Proxmox VE host using <strong>ProxMenux</strong>.
|
<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>
|
||||||
This setup enables hardware acceleration for AI-based applications that leverage Coral TPU.
|
<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>
|
</p>
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Overview</h2>
|
||||||
<p className="mb-4">The script automates the following steps:</p>
|
<p className="mb-4">The script automates the following steps:</p>
|
||||||
<ol className="list-decimal pl-6 space-y-2 mb-6">
|
<ol className="list-decimal pl-6 space-y-2 mb-6">
|
||||||
<li>Prompts for confirmation before proceeding with installation.</li>
|
<li>Prompts for confirmation before proceeding with installation.</li>
|
||||||
<li>Verifies and configures necessary repositories on the host.</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>Clones the Coral TPU driver repository and builds the drivers.</li>
|
||||||
<li>Installs the compiled Coral TPU drivers.</li>
|
<li>Installs the compiled Coral TPU drivers.</li>
|
||||||
<li>Prompts for a system restart to apply changes.</li>
|
<li>Prompts for a system restart to apply changes.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Implementation Steps</h2>
|
||||||
<Steps>
|
<Steps>
|
||||||
<Steps.Step title="Pre-Installation Confirmation">
|
<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>
|
<p>The script prompts the user for confirmation before proceeding, as a system restart is required after installation.</p>
|
||||||
</Steps.Step>
|
</Steps.Step>
|
||||||
|
|
||||||
<Steps.Step title="Repository Configuration">
|
<Steps.Step title="Repository Configuration">
|
||||||
<p>The script verifies and configures required repositories:</p>
|
<p>The script verifies and configures required repositories:</p>
|
||||||
<ul className="list-disc pl-6 space-y-1 mt-2">
|
<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 the <strong>pve-no-subscription</strong> repository if not present.</li>
|
||||||
<li>Adds <strong>non-free-firmware</strong> repositories for required packages.</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>
|
</ul>
|
||||||
</Steps.Step>
|
</Steps.Step>
|
||||||
|
|
||||||
<Steps.Step title="Driver Installation">
|
<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">
|
<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>Installs the following packages:</li>
|
||||||
<li>Clones the <strong>gasket-driver</strong> repository from Google.</li>
|
<ul className="list-disc pl-10">
|
||||||
<li>Builds the Coral TPU driver packages.</li>
|
<li><code>git</code></li>
|
||||||
<li>Installs the compiled drivers on the host.</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>
|
</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>
|
||||||
|
|
||||||
<Steps.Step title="Post-Installation Confirmation">
|
<Steps.Step title="Post-Installation Confirmation">
|
||||||
<p>The script prompts the user to restart the server to apply the changes.</p>
|
<p>The script prompts the user to restart the server to apply the changes.</p>
|
||||||
</Steps.Step>
|
</Steps.Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
<h2 className="text-2xl font-semibold mt-8 mb-4">Expected Results</h2>
|
<h2 className="text-2xl font-semibold mt-8 mb-4">Expected Results</h2>
|
||||||
<ul className="list-disc pl-6 space-y-2 mb-6">
|
<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>The Coral TPU drivers are installed successfully on the Proxmox VE host.</li>
|
||||||
<li>Required repositories and dependencies are configured properly.</li>
|
<li>Required repositories and dependencies are configured properly.</li>
|
||||||
<li>A system restart is performed to complete the installation.</li>
|
<li>A system restart is performed to complete the installation.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,31 @@ interface ChangelogEntry {
|
|||||||
title: string
|
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[]> {
|
async function parseChangelog(): Promise<ChangelogEntry[]> {
|
||||||
try {
|
try {
|
||||||
const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md")
|
const changelogPath = path.join(process.cwd(), "..", "CHANGELOG.md")
|
||||||
@ -21,45 +46,67 @@ async function parseChangelog(): Promise<ChangelogEntry[]> {
|
|||||||
const fileContents = fs.readFileSync(changelogPath, "utf8")
|
const fileContents = fs.readFileSync(changelogPath, "utf8")
|
||||||
const entries: ChangelogEntry[] = []
|
const entries: ChangelogEntry[] = []
|
||||||
|
|
||||||
// Split by any heading (## or ###) to catch all changes, not just versions
|
// Split by ## headers (both versions and dates)
|
||||||
const sections = fileContents.split(/^(##\s+.*$)/gm).filter((section) => section.trim())
|
const lines = fileContents.split("\n")
|
||||||
|
let currentEntry: Partial<ChangelogEntry> | null = null
|
||||||
|
let contentLines: string[] = []
|
||||||
|
|
||||||
for (let i = 0; i < sections.length - 1; i += 2) {
|
for (const line of lines) {
|
||||||
const headerLine = sections[i]
|
// Check for version header: ## [1.1.1] - 2025-03-21
|
||||||
const content = sections[i + 1] || ""
|
const versionMatch = line.match(/^##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
|
||||||
|
|
||||||
// Check if it's a version header (## [version] - date)
|
// Check for date-only header: ## 2025-05-13
|
||||||
const versionMatch = headerLine.match(/##\s+\[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/)
|
const dateMatch = line.match(/^##\s+(\d{4}-\d{2}-\d{2})$/)
|
||||||
|
|
||||||
if (versionMatch) {
|
if (versionMatch || dateMatch) {
|
||||||
const version = versionMatch[1]
|
// Save previous entry if exists
|
||||||
const date = versionMatch[2]
|
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({
|
// Start new entry
|
||||||
version,
|
if (versionMatch) {
|
||||||
date,
|
const version = versionMatch[1]
|
||||||
content: content.trim(),
|
const date = versionMatch[2]
|
||||||
url: `https://macrimi.github.io/ProxMenux/changelog#${version}`,
|
currentEntry = {
|
||||||
title: `ProxMenux ${version}`,
|
version,
|
||||||
})
|
date,
|
||||||
} else {
|
url: `https://macrimi.github.io/ProxMenux/changelog#${version}`,
|
||||||
// Check for date-only headers (## 2025-05-13)
|
title: `ProxMenux ${version}`,
|
||||||
const dateMatch = headerLine.match(/##\s+(\d{4}-\d{2}-\d{2})/)
|
}
|
||||||
if (dateMatch) {
|
} else if (dateMatch) {
|
||||||
const date = dateMatch[1]
|
const date = dateMatch[1]
|
||||||
|
currentEntry = {
|
||||||
entries.push({
|
|
||||||
version: date,
|
version: date,
|
||||||
date,
|
date,
|
||||||
content: content.trim(),
|
|
||||||
url: `https://macrimi.github.io/ProxMenux/changelog#${date}`,
|
url: `https://macrimi.github.io/ProxMenux/changelog#${date}`,
|
||||||
title: `ProxMenux Update ${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) {
|
} catch (error) {
|
||||||
console.error("Error parsing changelog:", error)
|
console.error("Error parsing changelog:", error)
|
||||||
return []
|
return []
|
||||||
@ -87,7 +134,7 @@ export async function GET() {
|
|||||||
(entry) => `
|
(entry) => `
|
||||||
<item>
|
<item>
|
||||||
<title>${entry.title}</title>
|
<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>
|
<link>${entry.url}</link>
|
||||||
<guid isPermaLink="true">${entry.url}</guid>
|
<guid isPermaLink="true">${entry.url}</guid>
|
||||||
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
|
<pubDate>${new Date(entry.date).toUTCString()}</pubDate>
|
||||||
|
@ -81,7 +81,6 @@ export const sidebarItems: MenuItem[] = [
|
|||||||
{
|
{
|
||||||
title: "Network",
|
title: "Network",
|
||||||
submenu: [
|
submenu: [
|
||||||
{ title: "Repair Network", href: "/docs/network/repair-network" },
|
|
||||||
{ title: "Verify Network", href: "/docs/network/verify-network" },
|
{ title: "Verify Network", href: "/docs/network/verify-network" },
|
||||||
{ title: "Show IP Information", href: "/docs/network/show-ip-information" },
|
{ title: "Show IP Information", href: "/docs/network/show-ip-information" },
|
||||||
],
|
],
|
||||||
|
BIN
web/public/menu-helpers-script.png
Normal file
BIN
web/public/menu-helpers-script.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
BIN
web/public/vm/config-cpu.png
Normal file
BIN
web/public/vm/config-cpu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
Loading…
x
Reference in New Issue
Block a user