Compare commits

..

119 Commits
v1.1.2 ... main

Author SHA1 Message Date
ProxMenuxBot
5c13d124de Update helpers_cache.json 2025-06-28 01:03:36 +00:00
ProxMenuxBot
4f94e21ea8 Update helpers_cache.json 2025-06-27 18:18:47 +00:00
ProxMenuxBot
7498aab76a Update helpers_cache.json 2025-06-26 18:20:04 +00:00
MacRimi
e65ac4ac8b
Update uupdump_creator.sh 2025-06-25 20:23:45 +02:00
MacRimi
df2ec69448
Create Iso.sh 2025-06-25 20:23:31 +02:00
ProxMenuxBot
6fe474e494 Update helpers_cache.json 2025-06-25 12:27:42 +00:00
ProxMenuxBot
6408477939 Update helpers_cache.json 2025-06-24 18:18:58 +00:00
ProxMenuxBot
5a20a4260b Update helpers_cache.json 2025-06-24 12:27:48 +00:00
ProxMenuxBot
b604b81f37 Update helpers_cache.json 2025-06-24 01:06:00 +00:00
MacRimi
40e36b3203
Update customizable_post_install.sh 2025-06-21 19:09:10 +02:00
MacRimi
e098b63beb
Update customizable_post_install.sh 2025-06-21 09:18:58 +02:00
ProxMenuxBot
56e99bc6ba Update helpers_cache.json 2025-06-19 18:18:49 +00:00
MacRimi
0abc42bf2c
Update uupdump_creator.sh 2025-06-19 19:46:33 +02:00
ProxMenuxBot
f648eba8dd Update helpers_cache.json 2025-06-19 12:52:20 +00:00
ProxMenuxBot
20648b479f Update helpers_cache.json 2025-06-19 06:20:00 +00:00
ProxMenuxBot
ef82bac8fc Update helpers_cache.json 2025-06-18 18:19:19 +00:00
MacRimi
28e330520b update menu backup 2025-06-18 18:56:11 +02:00
MacRimi
b481bb08cc Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-17 19:52:39 +02:00
MacRimi
506d7fff22 Create backup_host.sh 2025-06-17 19:52:36 +02:00
ProxMenuxBot
da3b42b6ac Update helpers_cache.json 2025-06-17 12:27:56 +00:00
ProxMenuxBot
eea50c23b5 Update helpers_cache.json 2025-06-16 18:19:26 +00:00
ProxMenuxBot
b1f5860335 Update helpers_cache.json 2025-06-16 06:21:42 +00:00
ProxMenuxBot
958c567b6b Update helpers_cache.json 2025-06-16 01:07:19 +00:00
MacRimi
b443f278da Update mount_disk_host_bk.sh 2025-06-15 11:58:17 +02:00
MacRimi
f5ae187012 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-15 11:48:40 +02:00
MacRimi
6e48279c8a Update mount_disk_host_bk.sh 2025-06-15 11:48:30 +02:00
MacRimi
61976e8c13
Update mount_disk_host_bk.sh 2025-06-15 11:47:28 +02:00
MacRimi
2009b0ff7e Update mount_disk_host_bk.sh 2025-06-15 11:35:57 +02:00
MacRimi
60dd0d45b9 Update mount to host 2025-06-14 20:17:51 +02:00
MacRimi
a7422e4c1e Update mount_disk_host_bk.sh 2025-06-14 16:19:06 +02:00
MacRimi
ffd79d2404 Update mount_disk_host_bk.sh 2025-06-14 16:17:43 +02:00
MacRimi
7363a461de Update mount_disk_host_bk.sh 2025-06-14 15:58:49 +02:00
MacRimi
f7325f030c
Rename Mount_disk_host_bk.sh to mount_disk_host_bk.sh 2025-06-14 11:59:25 +02:00
MacRimi
2668e5d8b2 Create Mount_disk_host_bk.sh 2025-06-14 11:52:50 +02:00
ProxMenuxBot
f09e3ffcdb Update helpers_cache.json 2025-06-13 01:05:40 +00:00
ProxMenuxBot
de4bba2e85 Update helpers_cache.json 2025-06-12 18:18:59 +00:00
ProxMenuxBot
bb50ecc86c Update helpers_cache.json 2025-06-10 18:18:50 +00:00
MacRimi
2191fe4cdd Update customizable_post_install.sh 2025-06-10 19:58:34 +02:00
MacRimi
321e0b2331 Update customizable_post_install.sh 2025-06-10 19:54:31 +02:00
MacRimi
f4611280a7
Update menu_Helper_Scripts.sh 2025-06-10 14:34:30 +02:00
MacRimi
ed3f2415bb
Update hw_grafics_menu.sh 2025-06-10 14:29:34 +02:00
MacRimi
495bc24b2f
Update install_coral_pve.sh 2025-06-10 14:27:09 +02:00
MacRimi
397c84cacb
Update install_coral_lxc.sh 2025-06-10 14:24:30 +02:00
MacRimi
f04fb7e756
Update configure_igpu_lxc.sh 2025-06-10 14:18:05 +02:00
MacRimi
dcbed8b173
Update configure_igpu_lxc.sh 2025-06-10 14:03:58 +02:00
MacRimi
3e5e79ba18
Update config_menu.sh 2025-06-10 13:57:07 +02:00
MacRimi
ddaee77b59
Update config_menu.sh 2025-06-10 13:55:10 +02:00
MacRimi
2d5a08a921
Update create_vm_menu.sh 2025-06-10 13:52:30 +02:00
MacRimi
240a325ef1
Update create_vm_menu_.sh 2025-06-10 13:48:09 +02:00
MacRimi
663a0f15df
Update cache.json 2025-06-10 13:30:03 +02:00
MacRimi
cb7afac17b
Update main_menu.sh 2025-06-10 13:28:06 +02:00
MacRimi
b04710cf50
Update cache.json 2025-06-10 13:24:49 +02:00
MacRimi
ce3fd894ae
Update storage_menu.sh 2025-06-10 12:56:05 +02:00
MacRimi
fd11f4e866
Update create_vm_menu.sh 2025-06-10 12:48:26 +02:00
MacRimi
5422af1e82
Update main_menu.sh 2025-06-10 12:45:17 +02:00
MacRimi
444002b006
Update storage_menu.sh 2025-06-10 12:44:49 +02:00
MacRimi
f01c474536
Actualizar storage_menu.sh 2025-06-10 11:41:07 +02:00
ProxMenuxBot
696b42666f Update helpers_cache.json 2025-06-10 01:06:21 +00:00
ProxMenuxBot
84190e0806 Update helpers_cache.json 2025-06-09 01:08:35 +00:00
ProxMenuxBot
80253426b7 Update helpers_cache.json 2025-06-07 01:03:54 +00:00
MacRimi
26ccc63c96
Actualizar CHANGELOG.md 2025-06-06 19:19:11 +02:00
MacRimi
1124ac41f9 Update CHANGELOG.md 2025-06-06 18:29:10 +02:00
MacRimi
d534d8b25c Update CHANGELOG.md 2025-06-06 18:28:26 +02:00
MacRimi
618afaacd4 add images 2025-06-06 17:54:44 +02:00
MacRimi
53b6ce56bf Update menu_Helper_Scripts.sh 2025-06-06 17:42:52 +02:00
MacRimi
8257c7d7e4 Update menu_Helper_Scripts.sh 2025-06-06 17:40:53 +02:00
MacRimi
769416f474 Update menu_Helper_Scripts.sh 2025-06-06 17:34:51 +02:00
MacRimi
f978e5d261 Update main_menu.sh 2025-06-06 17:30:24 +02:00
MacRimi
9d3660a1e2 update menu 2025-06-06 17:29:06 +02:00
MacRimi
f2637aad46 Update menu 2025-06-06 17:24:56 +02:00
MacRimi
371e8a9570 Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-06 17:11:59 +02:00
MacRimi
56987fe7a0 Update customizable_post_install.sh 2025-06-06 17:11:57 +02:00
ProxMenuxBot
dfe5138cad Update helpers_cache.json 2025-06-06 12:25:55 +00:00
ProxMenuxBot
90a2d83670 Update helpers_cache.json 2025-06-06 01:04:20 +00:00
MacRimi
8c8981ea9f Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-06 00:02:47 +02:00
MacRimi
de49d67361 Update helpers-menu.sh 2025-06-06 00:02:43 +02:00
ProxMenuxBot
5826c383b7 Update helpers_cache.json 2025-06-05 12:27:18 +00:00
ProxMenuxBot
24962f44e1 Update helpers_cache.json 2025-06-04 21:26:29 +00:00
MacRimi
9f2fc40c76
Update generate_helpers_cache.py 2025-06-04 23:25:29 +02:00
MacRimi
b8bdcf4c71 Update helpers-menu.sh 2025-06-04 20:50:01 +02:00
MacRimi
c9c6dc7666 Update helpers-menu.sh 2025-06-04 20:47:11 +02:00
MacRimi
9f686b91a2 Update helpers-menu.sh 2025-06-04 20:45:05 +02:00
MacRimi
9e879d6582 Update helpers-menu.sh 2025-06-04 20:42:23 +02:00
MacRimi
2fc1df729b Merge branch 'main' of https://github.com/MacRimi/ProxMenux 2025-06-04 20:39:57 +02:00
MacRimi
6586b9746a Create helpers-menu.sh 2025-06-04 20:39:50 +02:00
ProxMenuxBot
37d3ba3bc1 Update helpers_cache.json 2025-06-04 18:19:08 +00:00
ProxMenuxBot
1126860834 Update helpers_cache.json 2025-06-03 18:42:23 +00:00
MacRimi
c26141bc5d
Update generate_helpers_cache.py 2025-06-03 20:41:20 +02:00
ProxMenuxBot
3d6cfb44bb Update helpers_cache.json 2025-06-03 17:41:05 +00:00
MacRimi
4b1bdc55f1
Update generate_helpers_cache.py 2025-06-03 19:40:01 +02:00
ProxMenuxBot
c2bdd5f0bb Update helpers_cache.json 2025-06-03 16:44:28 +00:00
MacRimi
c22544672c
Update update-helpers-cache.yml 2025-06-03 18:42:14 +02:00
MacRimi
78064cc07c
Update generate_helpers_cache.py 2025-06-03 18:41:49 +02:00
MacRimi
84f5897e38
Update generate_helpers_cache.py 2025-06-03 18:37:36 +02:00
ProxMenuxBot
9044f13d2b 🔄 Update helpers_cache.json 2025-06-03 09:13:31 +00:00
MacRimi
cf9ee44970
Actualizar update-helpers-cache.yml 2025-06-03 11:11:24 +02:00
MacRimi
0cf5830671
Actualizar generate_helpers_cache.py 2025-06-03 11:09:29 +02:00
MacRimi
f25d8aec3c
Crear update-helpers-cache.yml 2025-06-03 11:00:47 +02:00
MacRimi
4ecb3f9943
Crear generate_helpers_cache.py 2025-06-03 10:58:58 +02:00
MacRimi
3dcb521422
Update import-disk-image.sh 2025-05-30 18:25:10 +02:00
MacRimi
de4db1de9a
Update import-disk-image.sh 2025-05-30 15:11:18 +02:00
MacRimi
a6c2b958a2
Update import-disk-image.sh 2025-05-30 14:56:57 +02:00
MacRimi
f721d9d774
Update README.md 2025-05-30 11:12:45 +02:00
MacRimi
3a8c1c3fd9
Update README.md 2025-05-30 11:08:59 +02:00
MacRimi
891c70dd4c
Update CODE_OF_CONDUCT.md 2025-05-30 11:07:26 +02:00
MacRimi
f2b99722e3
Create SECURITY.md 2025-05-30 11:03:54 +02:00
MacRimi
32204d3e17 Update vm_configurator.sh 2025-05-29 22:18:52 +02:00
MacRimi
112dfe08b3 Update vm_configurator.sh 2025-05-29 22:07:42 +02:00
MacRimi
4c3736fad7 Update storage_menu.sh 2025-05-29 21:50:48 +02:00
MacRimi
69a5b76e97 Update menu import image to vm 2025-05-29 21:49:21 +02:00
MacRimi
f941207699 Update import-disk-image.sh 2025-05-29 21:43:39 +02:00
MacRimi
084a8956ca Update menu_post_install.sh 2025-05-28 23:33:59 +02:00
MacRimi
571b5270a2 Update menus dialog 2025-05-28 23:26:58 +02:00
MacRimi
dcae6e1cd0 Update RSS page 2025-05-28 17:24:11 +02:00
MacRimi
a7d84d27fd Update page.tsx 2025-05-28 17:16:06 +02:00
MacRimi
9fba81f51d Update CHANGELOG.md 2025-05-27 20:48:54 +02:00
MacRimi
35e399dbaf Update storage_menu.sh 2025-05-27 20:32:30 +02:00
MacRimi
c3fc013002 Update storage_menu.sh 2025-05-27 20:30:17 +02:00
MacRimi
a34fc6eaa4 Update menus 2025-05-27 19:58:03 +02:00
38 changed files with 11452 additions and 745 deletions

View 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.")

View 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

View File

@ -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
Its a cleaner, faster, and more functional way to access community scripts in Proxmox.
![Helper Scripts Menu](https://macrimi.github.io/ProxMenux/menu-helpers-script.png)
- **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.
![CPU Config](https://macrimi.github.io/ProxMenux/vm/config-cpu.png)
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

View File

@ -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
View 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!

View File

@ -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

File diff suppressed because it is too large Load Diff

4
menu
View File

@ -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")" \

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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" \

View File

@ -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

View File

@ -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

View File

@ -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"

View 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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View 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
View 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
}

View 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

View File

@ -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

View File

@ -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)")" \

View File

@ -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>
) )
} }

View File

@ -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>
) )
} }

View File

@ -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>

View File

@ -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" },
], ],

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB