mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-05 20:03:48 +00:00
Beta Program Installer
This commit is contained in:
254
oci/catalog.json
Normal file
254
oci/catalog.json
Normal file
@@ -0,0 +1,254 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"last_updated": "2025-01-15T10:00:00Z",
|
||||
"apps": {
|
||||
"secure-gateway": {
|
||||
"id": "secure-gateway",
|
||||
"name": "Secure Gateway",
|
||||
"short_name": "VPN Gateway",
|
||||
"subtitle": "Tailscale VPN Gateway",
|
||||
"version": "1.0.0",
|
||||
"category": "security",
|
||||
"subcategory": "remote_access",
|
||||
"icon": "shield-check",
|
||||
"icon_type": "shield",
|
||||
"color": "#0EA5E9",
|
||||
|
||||
"summary": "Secure remote access without opening ports",
|
||||
"description": "Deploy a managed VPN gateway using Tailscale for zero-trust access to your Proxmox infrastructure. Access ProxMenux Monitor, Proxmox UI, VMs, and LXC containers from anywhere without exposing ports to the internet.",
|
||||
"documentation_url": "https://macrimi.github.io/ProxMenux/docs/secure-gateway",
|
||||
"code_url": "https://github.com/MacRimi/ProxMenux/tree/main/Scripts/oci",
|
||||
|
||||
"features": [
|
||||
"Zero-trust network access",
|
||||
"No port forwarding required",
|
||||
"End-to-end encryption",
|
||||
"Easy mobile access",
|
||||
"MagicDNS for easy hostname access",
|
||||
"Access control via Tailscale admin"
|
||||
],
|
||||
|
||||
"container": {
|
||||
"type": "lxc",
|
||||
"template": "alpine",
|
||||
"install_method": "apk",
|
||||
"packages": ["tailscale"],
|
||||
"services": ["tailscale"],
|
||||
"privileged": false,
|
||||
"memory": 256,
|
||||
"cores": 1,
|
||||
"disk_size": 2,
|
||||
"requires_ip_forward": true,
|
||||
"features": ["nesting=1"],
|
||||
"lxc_config": [
|
||||
"lxc.cgroup2.devices.allow: c 10:200 rwm",
|
||||
"lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file"
|
||||
]
|
||||
},
|
||||
|
||||
"volumes": {
|
||||
"state": {
|
||||
"container_path": "/var/lib/tailscale",
|
||||
"persistent": true,
|
||||
"description": "Tailscale state and keys"
|
||||
}
|
||||
},
|
||||
|
||||
"environment": [
|
||||
{
|
||||
"name": "TS_STATE_DIR",
|
||||
"value": "/var/lib/tailscale"
|
||||
},
|
||||
{
|
||||
"name": "TS_USERSPACE",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"name": "TS_AUTHKEY",
|
||||
"value": "$auth_key"
|
||||
},
|
||||
{
|
||||
"name": "TS_HOSTNAME",
|
||||
"value": "$hostname"
|
||||
},
|
||||
{
|
||||
"name": "TS_ROUTES",
|
||||
"value": "$advertise_routes"
|
||||
},
|
||||
{
|
||||
"name": "TS_EXTRA_ARGS",
|
||||
"value": "$extra_args"
|
||||
}
|
||||
],
|
||||
|
||||
"config_schema": {
|
||||
"auth_key": {
|
||||
"type": "password",
|
||||
"label": "Tailscale Auth Key",
|
||||
"description": "Pre-authentication key from Tailscale admin console. Generate one at the link below.",
|
||||
"placeholder": "tskey-auth-xxxxx",
|
||||
"required": true,
|
||||
"sensitive": true,
|
||||
"env_var": "TS_AUTHKEY",
|
||||
"help_url": "https://login.tailscale.com/admin/settings/keys",
|
||||
"help_text": "Generate Auth Key"
|
||||
},
|
||||
"hostname": {
|
||||
"type": "text",
|
||||
"label": "Device Hostname",
|
||||
"description": "Name shown in Tailscale admin console",
|
||||
"placeholder": "proxmox-gateway",
|
||||
"default": "proxmox-gateway",
|
||||
"required": false,
|
||||
"env_var": "TS_HOSTNAME",
|
||||
"validation": {
|
||||
"pattern": "^[a-zA-Z0-9-]+$",
|
||||
"max_length": 63,
|
||||
"message": "Only letters, numbers, and hyphens allowed"
|
||||
}
|
||||
},
|
||||
"access_mode": {
|
||||
"type": "select",
|
||||
"label": "Access Scope",
|
||||
"description": "What should be accessible through this gateway",
|
||||
"default": "host_only",
|
||||
"required": true,
|
||||
"options": [
|
||||
{
|
||||
"value": "host_only",
|
||||
"label": "Proxmox Only",
|
||||
"description": "Access only this Proxmox server (UI and ProxMenux Monitor)"
|
||||
},
|
||||
{
|
||||
"value": "proxmox_network",
|
||||
"label": "Full Local Network",
|
||||
"description": "Access all devices on your local network (NAS, printers, VMs, etc.)"
|
||||
},
|
||||
{
|
||||
"value": "custom",
|
||||
"label": "Custom Subnets",
|
||||
"description": "Select specific subnets to expose"
|
||||
}
|
||||
]
|
||||
},
|
||||
"advertise_routes": {
|
||||
"type": "networks",
|
||||
"label": "Advertised Networks",
|
||||
"description": "Select networks to make accessible through the VPN",
|
||||
"required": false,
|
||||
"depends_on": {
|
||||
"field": "access_mode",
|
||||
"values": ["custom"]
|
||||
},
|
||||
"env_var": "TS_ROUTES",
|
||||
"env_format": "csv"
|
||||
},
|
||||
"exit_node": {
|
||||
"type": "boolean",
|
||||
"label": "Exit Node",
|
||||
"description": "Use this gateway as your internet exit point when away from home. All your internet traffic will appear to come from your Proxmox server's IP address.",
|
||||
"default": false,
|
||||
"required": false,
|
||||
"flag": "--advertise-exit-node",
|
||||
"warning": "Requires approval in Tailscale Admin. When enabled on your device, ALL internet traffic routes through your Proxmox server."
|
||||
},
|
||||
"accept_routes": {
|
||||
"type": "boolean",
|
||||
"label": "Accept Routes",
|
||||
"description": "Allow this gateway to access networks advertised by OTHER Tailscale nodes in your tailnet. Useful if you have multiple Tailscale subnet routers.",
|
||||
"default": false,
|
||||
"required": false,
|
||||
"flag": "--accept-routes"
|
||||
}
|
||||
},
|
||||
|
||||
"healthcheck": {
|
||||
"command": ["tailscale", "status", "--json"],
|
||||
"interval_seconds": 30,
|
||||
"timeout_seconds": 10,
|
||||
"retries": 3,
|
||||
"healthy_condition": "BackendState == Running"
|
||||
},
|
||||
|
||||
"requirements": {
|
||||
"min_memory_mb": 64,
|
||||
"min_disk_mb": 100,
|
||||
"proxmox_min_version": "9.1",
|
||||
"checks": [
|
||||
{
|
||||
"type": "proxmox_version",
|
||||
"min": "9.1",
|
||||
"message": "OCI containers require Proxmox VE 9.1+"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"security_notes": [
|
||||
"Requires NET_ADMIN capability for VPN tunneling",
|
||||
"Uses /dev/net/tun for network virtualization",
|
||||
"Auth key is stored encrypted at rest",
|
||||
"No ports are opened on the host firewall",
|
||||
"All traffic is end-to-end encrypted"
|
||||
],
|
||||
|
||||
"ui": {
|
||||
"wizard_steps": [
|
||||
{
|
||||
"id": "intro",
|
||||
"title": "Secure Remote Access",
|
||||
"description": "Set up secure VPN access to your Proxmox server"
|
||||
},
|
||||
{
|
||||
"id": "auth",
|
||||
"title": "Tailscale Authentication",
|
||||
"description": "Connect to your Tailscale account",
|
||||
"fields": ["auth_key", "hostname"]
|
||||
},
|
||||
{
|
||||
"id": "access",
|
||||
"title": "Access Scope",
|
||||
"description": "Choose what to make accessible",
|
||||
"fields": ["access_mode", "advertise_routes"]
|
||||
},
|
||||
{
|
||||
"id": "options",
|
||||
"title": "Advanced Options",
|
||||
"description": "Additional configuration",
|
||||
"fields": ["exit_node", "accept_routes"]
|
||||
},
|
||||
{
|
||||
"id": "deploy",
|
||||
"title": "Deploy Gateway",
|
||||
"description": "Review and deploy"
|
||||
}
|
||||
],
|
||||
"show_in_sections": ["security"],
|
||||
"dashboard_widget": false,
|
||||
"status_indicators": {
|
||||
"running": {
|
||||
"color": "green",
|
||||
"icon": "check-circle",
|
||||
"label": "Connected"
|
||||
},
|
||||
"stopped": {
|
||||
"color": "yellow",
|
||||
"icon": "pause-circle",
|
||||
"label": "Stopped"
|
||||
},
|
||||
"error": {
|
||||
"color": "red",
|
||||
"icon": "x-circle",
|
||||
"label": "Error"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"metadata": {
|
||||
"author": "ProxMenux",
|
||||
"license": "MIT",
|
||||
"upstream": "https://tailscale.com",
|
||||
"tags": ["vpn", "remote-access", "tailscale", "zero-trust", "security"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,861 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==========================================================
|
||||
# ProxMenux - Complete Post-Installation Script with Registration
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 06/07/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
#
|
||||
# The script performs system optimizations including:
|
||||
# - Repository configuration and system upgrades
|
||||
# - Subscription banner removal and UI enhancements
|
||||
# - Advanced memory management and kernel optimizations
|
||||
# - Network stack tuning and security hardening
|
||||
# - Storage optimizations including log2ram for SSD protection
|
||||
# - System limits increases and entropy generation improvements
|
||||
# - Journald and logrotate optimizations for better log management
|
||||
# - Security enhancements including RPC disabling and time synchronization
|
||||
# - Bash environment customization and system monitoring setup
|
||||
#
|
||||
# Key Features:
|
||||
# - Zero-interaction automation: Runs completely unattended
|
||||
# - Intelligent hardware detection: Automatically detects SSD/NVMe for log2ram
|
||||
# - RAM-aware configurations: Adjusts settings based on available system memory
|
||||
# - Comprehensive error handling: Robust installation with fallback mechanisms
|
||||
# - Registration system: Tracks installed optimizations for easy management
|
||||
# - Reboot management: Intelligently handles reboot requirements
|
||||
# - Translation support: Multi-language compatible through ProxMenux framework
|
||||
# - Rollback compatibility: All optimizations can be reversed using the uninstall script
|
||||
#
|
||||
# This script is based on the post-install script customizable
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
TOOLS_JSON="/usr/local/share/proxmenux/installed_tools.json"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Global variables
|
||||
OS_CODENAME="$(grep "VERSION_CODENAME=" /etc/os-release | cut -d"=" -f 2 | xargs)"
|
||||
RAM_SIZE_GB=$(( $(vmstat -s | grep -i "total memory" | xargs | cut -d" " -f 1) / 1024 / 1000))
|
||||
NECESSARY_REBOOT=0
|
||||
SCRIPT_TITLE="Customizable post-installation optimization script"
|
||||
|
||||
# ==========================================================
|
||||
# Tool registration system
|
||||
ensure_tools_json() {
|
||||
[ -f "$TOOLS_JSON" ] || echo "{}" > "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
register_tool() {
|
||||
local tool="$1"
|
||||
local state="$2"
|
||||
ensure_tools_json
|
||||
jq --arg t "$tool" --argjson v "$state" '.[$t]=$v' "$TOOLS_JSON" > "$TOOLS_JSON.tmp" && mv "$TOOLS_JSON.tmp" "$TOOLS_JSON"
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
lvm_repair_check() {
|
||||
msg_info "$(translate "Checking and repairing old LVM PV headers (if needed)...")"
|
||||
pvs_output=$(LC_ALL=C pvs -v 2>&1 | grep "old PV header")
|
||||
if [ -z "$pvs_output" ]; then
|
||||
msg_ok "$(translate "No PVs with old headers found.")"
|
||||
register_tool "lvm_repair" true
|
||||
return
|
||||
fi
|
||||
|
||||
declare -A vg_map
|
||||
while read -r line; do
|
||||
pv=$(echo "$line" | grep -o '/dev/[^ ]*')
|
||||
vg=$(pvs -o vg_name --noheadings "$pv" | awk '{print $1}')
|
||||
if [ -n "$vg" ]; then
|
||||
vg_map["$vg"]=1
|
||||
fi
|
||||
done <<< "$pvs_output"
|
||||
|
||||
for vg in "${!vg_map[@]}"; do
|
||||
msg_warn "$(translate "Old PV header(s) found in VG $vg. Updating metadata...")"
|
||||
vgck --updatemetadata "$vg"
|
||||
vgchange -ay "$vg"
|
||||
if [ $? -ne 0 ]; then
|
||||
msg_warn "$(translate "Metadata update failed for VG $vg. Review manually.")"
|
||||
else
|
||||
msg_ok "$(translate "Metadata updated successfully for VG $vg")"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate "LVM PV headers check completed")"
|
||||
register_tool "lvm_repair" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
cleanup_duplicate_repos() {
|
||||
local sources_file="/etc/apt/sources.list"
|
||||
local temp_file=$(mktemp)
|
||||
local cleaned_count=0
|
||||
declare -A seen_repos
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ -z "$line" ]]; then
|
||||
echo "$line" >> "$temp_file"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$line" =~ ^deb ]]; then
|
||||
read -r _ url dist components <<< "$line"
|
||||
local key="${url}_${dist}"
|
||||
if [[ -v "seen_repos[$key]" ]]; then
|
||||
echo "# $line" >> "$temp_file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
seen_repos[$key]="$components"
|
||||
fi
|
||||
else
|
||||
echo "$line" >> "$temp_file"
|
||||
fi
|
||||
done < "$sources_file"
|
||||
|
||||
mv "$temp_file" "$sources_file"
|
||||
chmod 644 "$sources_file"
|
||||
|
||||
|
||||
local pve_files=(/etc/apt/sources.list.d/*proxmox*.list /etc/apt/sources.list.d/*pve*.list)
|
||||
local pve_content="deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription"
|
||||
local pve_public_repo="/etc/apt/sources.list.d/pve-public-repo.list"
|
||||
local pve_public_repo_exists=false
|
||||
|
||||
if [ -f "$pve_public_repo" ] && grep -q "^deb.*pve-no-subscription" "$pve_public_repo"; then
|
||||
pve_public_repo_exists=true
|
||||
fi
|
||||
|
||||
for file in "${pve_files[@]}"; do
|
||||
if [ -f "$file" ] && grep -q "^deb.*pve-no-subscription" "$file"; then
|
||||
if ! $pve_public_repo_exists && [[ "$file" == "$pve_public_repo" ]]; then
|
||||
sed -i 's/^# *deb/deb/' "$file"
|
||||
pve_public_repo_exists=true
|
||||
elif [[ "$file" != "$pve_public_repo" ]]; then
|
||||
sed -i 's/^deb/# deb/' "$file"
|
||||
cleaned_count=$((cleaned_count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
apt update
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
apt_upgrade() {
|
||||
|
||||
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/pve-enterprise.list ] && grep -q "^deb" /etc/apt/sources.list.d/pve-enterprise.list; then
|
||||
msg_info "$(translate "Disabling enterprise Proxmox repository...")"
|
||||
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/pve-enterprise.list
|
||||
msg_ok "$(translate "Enterprise Proxmox repository disabled")"
|
||||
fi
|
||||
|
||||
|
||||
if [ -f /etc/apt/sources.list.d/ceph.list ] && grep -q "^deb" /etc/apt/sources.list.d/ceph.list; then
|
||||
msg_info "$(translate "Disabling enterprise Proxmox Ceph repository...")"
|
||||
sed -i "s/^deb/#deb/g" /etc/apt/sources.list.d/ceph.list
|
||||
msg_ok "$(translate "Enterprise Proxmox Ceph repository disabled")"
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f /etc/apt/sources.list.d/pve-public-repo.list ] || ! grep -q "pve-no-subscription" /etc/apt/sources.list.d/pve-public-repo.list; then
|
||||
msg_info "$(translate "Enabling free public Proxmox repository...")"
|
||||
echo "deb http://download.proxmox.com/debian/pve ${OS_CODENAME} pve-no-subscription" > /etc/apt/sources.list.d/pve-public-repo.list
|
||||
msg_ok "$(translate "Free public Proxmox repository enabled")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
sources_file="/etc/apt/sources.list"
|
||||
need_update=false
|
||||
|
||||
|
||||
sed -i 's|ftp.es.debian.org|deb.debian.org|g' "$sources_file"
|
||||
|
||||
|
||||
if grep -q "^deb http://security.debian.org ${OS_CODENAME}-security main contrib" "$sources_file"; then
|
||||
sed -i "s|^deb http://security.debian.org ${OS_CODENAME}-security main contrib|deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware|" "$sources_file"
|
||||
msg_ok "$(translate "Replaced security repository with full version")"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
|
||||
if ! grep -q "deb http://security.debian.org/debian-security ${OS_CODENAME}-security" "$sources_file"; then
|
||||
echo "deb http://security.debian.org/debian-security ${OS_CODENAME}-security main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
|
||||
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME} " "$sources_file"; then
|
||||
echo "deb http://deb.debian.org/debian ${OS_CODENAME} main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
|
||||
if ! grep -q "deb http://deb.debian.org/debian ${OS_CODENAME}-updates" "$sources_file"; then
|
||||
echo "deb http://deb.debian.org/debian ${OS_CODENAME}-updates main contrib non-free non-free-firmware" >> "$sources_file"
|
||||
need_update=true
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Debian repositories configured correctly")"
|
||||
|
||||
# ===================================================
|
||||
|
||||
|
||||
if [ ! -f /etc/apt/apt.conf.d/no-bookworm-firmware.conf ]; then
|
||||
msg_info "$(translate "Disabling non-free firmware warnings...")"
|
||||
echo 'APT::Get::Update::SourceListWarnings::NonFreeFirmware "false";' > /etc/apt/apt.conf.d/no-bookworm-firmware.conf
|
||||
msg_ok "$(translate "Non-free firmware warnings disabled")"
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Updating package lists...")"
|
||||
if apt-get update > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Package lists updated")"
|
||||
else
|
||||
msg_error "$(translate "Failed to update package lists")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
msg_info "$(translate "Removing conflicting utilities...")"
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' purge ntp openntpd systemd-timesyncd > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Conflicting utilities removed")"
|
||||
else
|
||||
msg_error "$(translate "Failed to remove conflicting utilities")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Performing packages upgrade...")"
|
||||
apt-get install pv -y > /dev/null 2>&1
|
||||
total_packages=$(apt-get -s dist-upgrade | grep "^Inst" | wc -l)
|
||||
|
||||
if [ "$total_packages" -eq 0 ]; then
|
||||
total_packages=1
|
||||
fi
|
||||
msg_ok "$(translate "Packages upgrade successful")"
|
||||
tput civis
|
||||
tput sc
|
||||
|
||||
|
||||
(
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' dist-upgrade 2>&1 | \
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ]]; then
|
||||
|
||||
package_name=$(echo "$line" | sed -E 's/.*(Setting up|Unpacking|Preparing to unpack|Processing triggers for) ([^ ]+).*/\2/')
|
||||
|
||||
|
||||
[ -z "$package_name" ] && package_name="$(translate "Unknown")"
|
||||
|
||||
|
||||
tput rc
|
||||
tput ed
|
||||
|
||||
|
||||
row=$(( $(tput lines) - 6 ))
|
||||
tput cup $row 0; echo "$(translate "Installing packages...")"
|
||||
tput cup $((row + 1)) 0; echo "──────────────────────────────────────────────"
|
||||
tput cup $((row + 2)) 0; echo "Package: $package_name"
|
||||
tput cup $((row + 3)) 0; echo "Progress: [ ] 0%"
|
||||
tput cup $((row + 4)) 0; echo "──────────────────────────────────────────────"
|
||||
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
progress=$((i * 10))
|
||||
tput cup $((row + 3)) 9
|
||||
printf "[%-50s] %3d%%" "$(printf "#%.0s" $(seq 1 $((progress/2))))" "$progress"
|
||||
|
||||
done
|
||||
fi
|
||||
done
|
||||
)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
tput rc
|
||||
tput ed
|
||||
msg_ok "$(translate "System upgrade completed")"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Installing additional Proxmox packages...")"
|
||||
if /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install zfsutils-linux proxmox-backup-restore-image chrony > /dev/null 2>&1; then
|
||||
msg_ok "$(translate "Additional Proxmox packages installed")"
|
||||
else
|
||||
msg_error "$(translate "Failed to install additional Proxmox packages")"
|
||||
fi
|
||||
|
||||
lvm_repair_check
|
||||
|
||||
cleanup_duplicate_repos
|
||||
|
||||
msg_ok "$(translate "Proxmox update completed")"
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
remove_subscription_banner() {
|
||||
msg_info "$(translate "Removing Proxmox subscription nag banner...")"
|
||||
local JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
|
||||
local GZ_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz"
|
||||
local APT_HOOK="/etc/apt/apt.conf.d/no-nag-script"
|
||||
|
||||
if [[ ! -f "$APT_HOOK" ]]; then
|
||||
cat <<'EOF' > "$APT_HOOK"
|
||||
DPkg::Post-Invoke { "dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ $? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; rm -f /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js.gz; }; fi"; };
|
||||
EOF
|
||||
fi
|
||||
|
||||
if [[ -f "$JS_FILE" ]]; then
|
||||
sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/;s/Active/NoMoreNagging/}' "$JS_FILE"
|
||||
[[ -f "$GZ_FILE" ]] && rm -f "$GZ_FILE"
|
||||
touch "$JS_FILE"
|
||||
fi
|
||||
|
||||
apt --reinstall install proxmox-widget-toolkit -y > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Subscription nag banner removed successfully")"
|
||||
register_tool "subscription_banner" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
configure_time_sync() {
|
||||
msg_info "$(translate "Configuring system time settings...")"
|
||||
|
||||
this_ip=$(dig +short myip.opendns.com @resolver1.opendns.com)
|
||||
if [ -z "$this_ip" ]; then
|
||||
msg_warn "$(translate "Failed to obtain public IP address")"
|
||||
timezone="UTC"
|
||||
else
|
||||
|
||||
timezone=$(curl -s "https://ipapi.co/${this_ip}/timezone")
|
||||
if [ -z "$timezone" ]; then
|
||||
msg_warn "$(translate "Failed to determine timezone from IP address")"
|
||||
timezone="UTC"
|
||||
else
|
||||
msg_ok "$(translate "Found timezone $timezone for IP $this_ip")"
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Enabling automatic time synchronization...")"
|
||||
if timedatectl set-ntp true; then
|
||||
msg_ok "$(translate "Time settings configured - Timezone:") $timezone"
|
||||
register_tool "time_sync" true
|
||||
else
|
||||
msg_error "$(translate "Failed to enable automatic time synchronization")"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
skip_apt_languages() {
|
||||
msg_info "$(translate "Configuring APT to skip downloading additional languages...")"
|
||||
local default_locale=""
|
||||
|
||||
if [ -f /etc/default/locale ]; then
|
||||
default_locale=$(grep '^LANG=' /etc/default/locale | cut -d= -f2 | tr -d '"')
|
||||
elif [ -f /etc/environment ]; then
|
||||
default_locale=$(grep '^LANG=' /etc/environment | cut -d= -f2 | tr -d '"')
|
||||
fi
|
||||
|
||||
default_locale="${default_locale:-en_US.UTF-8}"
|
||||
local normalized_locale=$(echo "$default_locale" | tr 'A-Z' 'a-z' | sed 's/utf-8/utf8/;s/-/_/')
|
||||
|
||||
if ! locale -a | grep -qi "^$normalized_locale$"; then
|
||||
if ! grep -qE "^${default_locale}[[:space:]]+UTF-8" /etc/locale.gen; then
|
||||
echo "$default_locale UTF-8" >> /etc/locale.gen
|
||||
fi
|
||||
locale-gen "$default_locale" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/99-disable-translations
|
||||
|
||||
msg_ok "$(translate "APT configured to skip additional languages")"
|
||||
register_tool "apt_languages" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_journald() {
|
||||
msg_info "$(translate "Limiting size and optimizing journald...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/systemd/journald.conf
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
SplitMode=none
|
||||
RateLimitInterval=0
|
||||
RateLimitIntervalSec=0
|
||||
RateLimitBurst=0
|
||||
ForwardToSyslog=no
|
||||
ForwardToWall=yes
|
||||
Seal=no
|
||||
Compress=yes
|
||||
SystemMaxUse=64M
|
||||
RuntimeMaxUse=60M
|
||||
MaxLevelStore=warning
|
||||
MaxLevelSyslog=warning
|
||||
MaxLevelKMsg=warning
|
||||
MaxLevelConsole=notice
|
||||
MaxLevelWall=crit
|
||||
EOF
|
||||
|
||||
systemctl restart systemd-journald.service > /dev/null 2>&1
|
||||
journalctl --vacuum-size=64M --vacuum-time=1d > /dev/null 2>&1
|
||||
journalctl --rotate > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Journald optimized - Max size: 64M")"
|
||||
register_tool "journald" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_logrotate() {
|
||||
msg_info "$(translate "Optimizing logrotate configuration...")"
|
||||
local logrotate_conf="/etc/logrotate.conf"
|
||||
local backup_conf="${logrotate_conf}.bak"
|
||||
|
||||
if ! grep -q "# ProxMenux optimized configuration" "$logrotate_conf"; then
|
||||
cp "$logrotate_conf" "$backup_conf"
|
||||
cat <<EOF > "$logrotate_conf"
|
||||
# ProxMenux optimized configuration
|
||||
daily
|
||||
su root adm
|
||||
rotate 7
|
||||
create
|
||||
compress
|
||||
size=10M
|
||||
delaycompress
|
||||
copytruncate
|
||||
include /etc/logrotate.d
|
||||
EOF
|
||||
systemctl restart logrotate > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Logrotate optimization completed")"
|
||||
register_tool "logrotate" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
increase_system_limits() {
|
||||
msg_info "$(translate "Increasing various system limits...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-maxwatches.conf << EOF
|
||||
# ProxMenux configuration
|
||||
fs.inotify.max_user_watches = 1048576
|
||||
fs.inotify.max_user_instances = 1048576
|
||||
fs.inotify.max_queued_events = 1048576
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/security/limits.d/99-limits.conf << EOF
|
||||
# ProxMenux configuration
|
||||
* soft nproc 1048576
|
||||
* hard nproc 1048576
|
||||
* soft nofile 1048576
|
||||
* hard nofile 1048576
|
||||
root soft nproc unlimited
|
||||
root hard nproc unlimited
|
||||
root soft nofile unlimited
|
||||
root hard nofile unlimited
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-maxkeys.conf << EOF
|
||||
# ProxMenux configuration
|
||||
kernel.keys.root_maxkeys=1000000
|
||||
kernel.keys.maxkeys=1000000
|
||||
EOF
|
||||
|
||||
|
||||
for file in /etc/systemd/system.conf /etc/systemd/user.conf; do
|
||||
if ! grep -q "^DefaultLimitNOFILE=" "$file"; then
|
||||
echo "DefaultLimitNOFILE=256000" >> "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
for file in /etc/pam.d/common-session /etc/pam.d/runuser-l; do
|
||||
if ! grep -q "^session required pam_limits.so" "$file"; then
|
||||
echo 'session required pam_limits.so' >> "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if ! grep -q "ulimit -n 256000" /root/.profile; then
|
||||
echo "ulimit -n 256000" >> /root/.profile
|
||||
fi
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-swap.conf << EOF
|
||||
# ProxMenux configuration
|
||||
vm.swappiness = 10
|
||||
vm.vfs_cache_pressure = 100
|
||||
EOF
|
||||
|
||||
|
||||
cat > /etc/sysctl.d/99-fs.conf << EOF
|
||||
# ProxMenux configuration
|
||||
fs.nr_open = 12000000
|
||||
fs.file-max = 9223372036854775807
|
||||
fs.aio-max-nr = 1048576
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "System limits increase completed.")"
|
||||
register_tool "system_limits" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
configure_entropy() {
|
||||
msg_info "$(translate "Configuring entropy generation to prevent slowdowns...")"
|
||||
|
||||
/usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get -y -o Dpkg::Options::='--force-confdef' install haveged > /dev/null 2>&1
|
||||
|
||||
cat <<EOF > /etc/default/haveged
|
||||
# -w sets low entropy watermark (in bits)
|
||||
DAEMON_ARGS="-w 1024"
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload > /dev/null 2>&1
|
||||
systemctl enable haveged > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Entropy generation configuration completed")"
|
||||
register_tool "entropy" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
optimize_memory_settings() {
|
||||
msg_info "$(translate "Optimizing memory settings...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-memory.conf
|
||||
# Balanced Memory Optimization
|
||||
vm.swappiness = 10
|
||||
vm.dirty_ratio = 15
|
||||
vm.dirty_background_ratio = 5
|
||||
vm.overcommit_memory = 1
|
||||
vm.max_map_count = 65530
|
||||
EOF
|
||||
|
||||
if [ -f /proc/sys/vm/compaction_proactiveness ]; then
|
||||
echo "vm.compaction_proactiveness = 20" >> /etc/sysctl.d/99-memory.conf
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Memory optimization completed.")"
|
||||
register_tool "memory_settings" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
configure_kernel_panic() {
|
||||
msg_info "$(translate "Configuring kernel panic behavior")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-kernelpanic.conf
|
||||
# Enable restart on kernel panic, kernel oops and hardlockup
|
||||
kernel.core_pattern = /var/crash/core.%t.%p
|
||||
kernel.panic = 10
|
||||
kernel.panic_on_oops = 1
|
||||
kernel.hardlockup_panic = 1
|
||||
EOF
|
||||
|
||||
msg_ok "$(translate "Kernel panic behavior configuration completed")"
|
||||
register_tool "kernel_panic" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
force_apt_ipv4() {
|
||||
msg_info "$(translate "Configuring APT to use IPv4...")"
|
||||
|
||||
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99-force-ipv4
|
||||
|
||||
msg_ok "$(translate "APT IPv4 configuration completed")"
|
||||
register_tool "apt_ipv4" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
apply_network_optimizations() {
|
||||
msg_info "$(translate "Optimizing network settings...")"
|
||||
NECESSARY_REBOOT=1
|
||||
|
||||
cat <<EOF > /etc/sysctl.d/99-network.conf
|
||||
net.core.netdev_max_backlog=8192
|
||||
net.core.optmem_max=8192
|
||||
net.core.rmem_max=16777216
|
||||
net.core.somaxconn=8151
|
||||
net.core.wmem_max=16777216
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.all.log_martians = 0
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.all.secure_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
net.ipv4.conf.default.log_martians = 0
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.default.secure_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
net.ipv4.ip_local_port_range=1024 65535
|
||||
net.ipv4.tcp_base_mss = 1024
|
||||
net.ipv4.tcp_challenge_ack_limit = 999999999
|
||||
net.ipv4.tcp_fin_timeout=10
|
||||
net.ipv4.tcp_keepalive_intvl=30
|
||||
net.ipv4.tcp_keepalive_probes=3
|
||||
net.ipv4.tcp_keepalive_time=240
|
||||
net.ipv4.tcp_limit_output_bytes=65536
|
||||
net.ipv4.tcp_max_syn_backlog=8192
|
||||
net.ipv4.tcp_max_tw_buckets = 1440000
|
||||
net.ipv4.tcp_mtu_probing = 1
|
||||
net.ipv4.tcp_rfc1337=1
|
||||
net.ipv4.tcp_rmem=8192 87380 16777216
|
||||
net.ipv4.tcp_sack=1
|
||||
net.ipv4.tcp_slow_start_after_idle=0
|
||||
net.ipv4.tcp_syn_retries=3
|
||||
net.ipv4.tcp_synack_retries = 2
|
||||
net.ipv4.tcp_tw_recycle = 0
|
||||
net.ipv4.tcp_tw_reuse = 0
|
||||
net.ipv4.tcp_wmem=8192 65536 16777216
|
||||
net.netfilter.nf_conntrack_generic_timeout = 60
|
||||
net.netfilter.nf_conntrack_helper=0
|
||||
net.netfilter.nf_conntrack_max = 524288
|
||||
net.netfilter.nf_conntrack_tcp_timeout_established = 28800
|
||||
net.unix.max_dgram_qlen = 4096
|
||||
EOF
|
||||
|
||||
sysctl --system > /dev/null 2>&1
|
||||
|
||||
local interfaces_file="/etc/network/interfaces"
|
||||
if ! grep -q 'source /etc/network/interfaces.d/*' "$interfaces_file"; then
|
||||
echo "source /etc/network/interfaces.d/*" >> "$interfaces_file"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Network optimization completed")"
|
||||
register_tool "network_optimization" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
disable_rpc() {
|
||||
msg_info "$(translate "Disabling portmapper/rpcbind for security...")"
|
||||
|
||||
systemctl disable rpcbind > /dev/null 2>&1
|
||||
systemctl stop rpcbind > /dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "portmapper/rpcbind has been disabled and removed")"
|
||||
register_tool "disable_rpc" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
customize_bashrc() {
|
||||
msg_info "$(translate "Customizing bashrc for root user...")"
|
||||
local bashrc="/root/.bashrc"
|
||||
local bash_profile="/root/.bash_profile"
|
||||
|
||||
if [ ! -f "${bashrc}.bak" ]; then
|
||||
cp "$bashrc" "${bashrc}.bak"
|
||||
fi
|
||||
|
||||
|
||||
cat >> "$bashrc" << 'EOF'
|
||||
|
||||
# ProxMenux customizations
|
||||
export HISTTIMEFORMAT="%d/%m/%y %T "
|
||||
export PS1="\[\e[31m\][\[\e[m\]\[\e[38;5;172m\]\u\[\e[m\]@\[\e[38;5;153m\]\h\[\e[m\] \[\e[38;5;214m\]\W\[\e[m\]\[\e[31m\]]\[\e[m\]\\$ "
|
||||
alias l='ls -CF'
|
||||
alias la='ls -A'
|
||||
alias ll='ls -alF'
|
||||
alias ls='ls --color=auto'
|
||||
alias grep='grep --color=auto'
|
||||
alias fgrep='fgrep --color=auto'
|
||||
alias egrep='egrep --color=auto'
|
||||
source /etc/profile.d/bash_completion.sh
|
||||
EOF
|
||||
|
||||
if ! grep -q "source /root/.bashrc" "$bash_profile"; then
|
||||
echo "source /root/.bashrc" >> "$bash_profile"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Bashrc customization completed")"
|
||||
register_tool "bashrc_custom" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
install_log2ram_auto() {
|
||||
msg_info "$(translate "Checking if system disk is SSD or M.2...")"
|
||||
|
||||
ROOT_PART=$(lsblk -no NAME,MOUNTPOINT | grep ' /$' | awk '{print $1}')
|
||||
SYSTEM_DISK=$(lsblk -no PKNAME /dev/$ROOT_PART 2>/dev/null)
|
||||
SYSTEM_DISK=${SYSTEM_DISK:-sda}
|
||||
|
||||
if [[ "$SYSTEM_DISK" == nvme* || "$(cat /sys/block/$SYSTEM_DISK/queue/rotational 2>/dev/null)" == "0" ]]; then
|
||||
msg_ok "$(translate "System disk ($SYSTEM_DISK) is SSD or M.2. Proceeding with log2ram setup.")"
|
||||
else
|
||||
msg_warn "$(translate "System disk ($SYSTEM_DISK) is not SSD/M.2. Skipping log2ram installation.")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Clean up previous state
|
||||
rm -rf /tmp/log2ram
|
||||
rm -f /etc/systemd/system/log2ram*
|
||||
rm -f /etc/systemd/system/log2ram-daily.*
|
||||
rm -f /etc/cron.d/log2ram*
|
||||
rm -f /usr/sbin/log2ram
|
||||
rm -f /etc/log2ram.conf
|
||||
rm -f /usr/local/bin/log2ram-check.sh
|
||||
rm -rf /var/log.hdd
|
||||
systemctl daemon-reexec >/dev/null 2>&1
|
||||
systemctl daemon-reload >/dev/null 2>&1
|
||||
|
||||
msg_info "$(translate "Installing log2ram from GitHub...")"
|
||||
|
||||
git clone https://github.com/azlux/log2ram.git /tmp/log2ram >/dev/null 2>>/tmp/log2ram_install.log
|
||||
cd /tmp/log2ram || return 1
|
||||
bash install.sh >>/tmp/log2ram_install.log 2>&1
|
||||
|
||||
if [[ -f /etc/log2ram.conf ]] && systemctl list-units --all | grep -q log2ram; then
|
||||
msg_ok "$(translate "log2ram installed successfully")"
|
||||
else
|
||||
msg_error "$(translate "Failed to install log2ram. See /tmp/log2ram_install.log")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Detect RAM (in MB first for better accuracy)
|
||||
RAM_SIZE_MB=$(free -m | awk '/^Mem:/{print $2}')
|
||||
RAM_SIZE_GB=$((RAM_SIZE_MB / 1024))
|
||||
[[ -z "$RAM_SIZE_GB" || "$RAM_SIZE_GB" -eq 0 ]] && RAM_SIZE_GB=4
|
||||
|
||||
if (( RAM_SIZE_GB <= 8 )); then
|
||||
LOG2RAM_SIZE="128M"
|
||||
CRON_HOURS=1
|
||||
elif (( RAM_SIZE_GB <= 16 )); then
|
||||
LOG2RAM_SIZE="256M"
|
||||
CRON_HOURS=3
|
||||
else
|
||||
LOG2RAM_SIZE="512M"
|
||||
CRON_HOURS=6
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Detected RAM:") $RAM_SIZE_GB GB — $(translate "log2ram size set to:") $LOG2RAM_SIZE"
|
||||
|
||||
sed -i "s/^SIZE=.*/SIZE=$LOG2RAM_SIZE/" /etc/log2ram.conf
|
||||
rm -f /etc/cron.hourly/log2ram
|
||||
echo "0 */$CRON_HOURS * * * root /usr/sbin/log2ram write" > /etc/cron.d/log2ram
|
||||
msg_ok "$(translate "log2ram write scheduled every") $CRON_HOURS $(translate "hour(s)")"
|
||||
|
||||
cat << 'EOF' > /usr/local/bin/log2ram-check.sh
|
||||
#!/bin/bash
|
||||
CONF_FILE="/etc/log2ram.conf"
|
||||
SIZE_VALUE=$(grep '^SIZE=' "$CONF_FILE" | cut -d'=' -f2)
|
||||
# Convert to KB: handle M (megabytes) and G (gigabytes)
|
||||
if [[ "$SIZE_VALUE" == *"G" ]]; then
|
||||
LIMIT_KB=$(($(echo "$SIZE_VALUE" | tr -d 'G') * 1024 * 1024))
|
||||
else
|
||||
LIMIT_KB=$(($(echo "$SIZE_VALUE" | tr -d 'M') * 1024))
|
||||
fi
|
||||
USED_KB=$(df /var/log --output=used | tail -1)
|
||||
THRESHOLD=$(( LIMIT_KB * 90 / 100 ))
|
||||
if (( USED_KB > THRESHOLD )); then
|
||||
/usr/sbin/log2ram write
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x /usr/local/bin/log2ram-check.sh
|
||||
echo "*/5 * * * * root /usr/local/bin/log2ram-check.sh" > /etc/cron.d/log2ram-auto-sync
|
||||
msg_ok "$(translate "Auto-sync enabled when /var/log exceeds 90% of") $LOG2RAM_SIZE"
|
||||
|
||||
|
||||
register_tool "log2ram" true
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
run_complete_optimization() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "ProxMenux Optimization Post-Installation")"
|
||||
|
||||
ensure_tools_json
|
||||
|
||||
apt_upgrade
|
||||
remove_subscription_banner
|
||||
configure_time_sync
|
||||
skip_apt_languages
|
||||
optimize_journald
|
||||
optimize_logrotate
|
||||
increase_system_limits
|
||||
configure_entropy
|
||||
optimize_memory_settings
|
||||
configure_kernel_panic
|
||||
force_apt_ipv4
|
||||
apply_network_optimizations
|
||||
disable_rpc
|
||||
customize_bashrc
|
||||
install_log2ram_auto
|
||||
|
||||
|
||||
echo -e
|
||||
msg_success "$(translate "Complete post-installation optimization finished!")"
|
||||
|
||||
if [[ "$NECESSARY_REBOOT" -eq 1 ]]; then
|
||||
whiptail --title "Reboot Required" \
|
||||
--yesno "$(translate "Some changes require a reboot to take effect. Do you want to restart now?")" 10 60
|
||||
if [[ $? -eq 0 ]]; then
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
msg_warn "$(translate "Rebooting the system...")"
|
||||
reboot
|
||||
else
|
||||
msg_info "$(translate "Removing no longer required packages and purging old cached updates...")"
|
||||
apt-get -y autoremove >/dev/null 2>&1
|
||||
apt-get -y autoclean >/dev/null 2>&1
|
||||
msg_ok "$(translate "Cleanup finished")"
|
||||
msg_info2 "$(translate "You can reboot later manually.")"
|
||||
msg_success "$(translate "Press Enter to continue...")"
|
||||
read -r
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_success "$(translate "All changes applied. No reboot required.")"
|
||||
msg_success "$(translate "Press Enter to return to menu...")"
|
||||
read -r
|
||||
clear
|
||||
}
|
||||
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
run_complete_optimization
|
||||
fi
|
||||
@@ -1,8 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==========================================================
|
||||
# ProxMenux - Global Share Functions (reusable)
|
||||
# File: scripts/global/share_common.func
|
||||
# ==========================================================
|
||||
#!/bin/bash
|
||||
# ProxMenux - Shared Common Functions
|
||||
# ============================================
|
||||
# Author : MacRimi
|
||||
# License : MIT
|
||||
# Version : 1.0
|
||||
# Last Updated: 29/01/2026
|
||||
# ============================================
|
||||
# Common functions shared across multiple scripts
|
||||
|
||||
|
||||
|
||||
|
||||
465
scripts/security/fail2ban_installer.sh
Normal file
465
scripts/security/fail2ban_installer.sh
Normal file
@@ -0,0 +1,465 @@
|
||||
#!/bin/bash
|
||||
# ProxMenux - Fail2Ban Installer & Configurator
|
||||
# ============================================
|
||||
# Author : MacRimi
|
||||
# License : MIT
|
||||
# Version : 1.0
|
||||
# ============================================
|
||||
# Hybrid script: works from terminal (dialog) and web panel (ScriptTerminalModal)
|
||||
|
||||
SCRIPT_TITLE="Fail2Ban Installer for Proxmox VE"
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
COMPONENTS_STATUS_FILE="$BASE_DIR/components_status.json"
|
||||
|
||||
export BASE_DIR
|
||||
export COMPONENTS_STATUS_FILE
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
|
||||
echo "{}" > "$COMPONENTS_STATUS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Detection
|
||||
# ==========================================================
|
||||
detect_fail2ban() {
|
||||
FAIL2BAN_INSTALLED=false
|
||||
FAIL2BAN_VERSION=""
|
||||
FAIL2BAN_ACTIVE=false
|
||||
|
||||
if command -v fail2ban-client >/dev/null 2>&1; then
|
||||
FAIL2BAN_INSTALLED=true
|
||||
FAIL2BAN_VERSION=$(fail2ban-client --version 2>/dev/null | head -n1 | tr -d '[:space:]' || echo "unknown")
|
||||
|
||||
if systemctl is-active --quiet fail2ban 2>/dev/null; then
|
||||
FAIL2BAN_ACTIVE=true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Installation
|
||||
# ==========================================================
|
||||
install_fail2ban() {
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate "Installing and configuring Fail2Ban to protect Proxmox web interface and SSH...")"
|
||||
|
||||
# Ensure Debian repositories are available
|
||||
local deb_codename
|
||||
deb_codename=$(grep -oP '^VERSION_CODENAME=\K.*' /etc/os-release 2>/dev/null)
|
||||
|
||||
if ! grep -RqsE "debian.*(bookworm|trixie)" /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null; then
|
||||
msg_warn "$(translate "Debian repositories missing; creating default source file")"
|
||||
local src="/etc/apt/sources.list.d/debian.sources"
|
||||
cat > "$src" <<EOF
|
||||
Types: deb
|
||||
URIs: http://deb.debian.org/debian
|
||||
Suites: ${deb_codename} ${deb_codename}-updates
|
||||
Components: main contrib non-free non-free-firmware
|
||||
|
||||
Types: deb
|
||||
URIs: http://security.debian.org/debian-security
|
||||
Suites: ${deb_codename}-security
|
||||
Components: main contrib non-free non-free-firmware
|
||||
EOF
|
||||
msg_ok "$(translate "Debian repositories configured for ${deb_codename}")"
|
||||
fi
|
||||
|
||||
# Install Fail2Ban
|
||||
msg_info "$(translate "Installing Fail2Ban...")"
|
||||
if ! DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null 2>&1 || \
|
||||
! DEBIAN_FRONTEND=noninteractive apt-get install -y fail2ban >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Failed to install Fail2Ban")"
|
||||
return 1
|
||||
fi
|
||||
msg_ok "$(translate "Fail2Ban installed successfully")"
|
||||
|
||||
# ── Ensure journald stores auth-level messages ──
|
||||
# Proxmox sets MaxLevelStore=warning by default, which silently drops
|
||||
# info/notice messages. SSH auth failures (PAM) are logged at info/notice,
|
||||
# so Fail2Ban with backend=systemd will never see them.
|
||||
local journald_conf="/etc/systemd/journald.conf"
|
||||
local journald_changed=false
|
||||
|
||||
if [[ -f "$journald_conf" ]]; then
|
||||
local current_max
|
||||
current_max=$(grep -i '^MaxLevelStore=' "$journald_conf" 2>/dev/null | tail -1 | cut -d= -f2 | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# Levels that are too restrictive for auth logging (need at least info)
|
||||
case "$current_max" in
|
||||
emerg|alert|crit|err|warning)
|
||||
msg_warn "$(translate "journald MaxLevelStore is '${current_max}' - SSH auth failures are not being stored")"
|
||||
msg_info "$(translate "Updating journald to store info-level messages...")"
|
||||
|
||||
# Create a drop-in so we don't break other Proxmox settings
|
||||
mkdir -p /etc/systemd/journald.conf.d
|
||||
cat > /etc/systemd/journald.conf.d/proxmenux-loglevel.conf <<'JEOF'
|
||||
# ProxMenux: Allow auth/info messages so Fail2Ban can detect SSH failures
|
||||
# Proxmox default MaxLevelStore=warning drops PAM/SSH auth events
|
||||
[Journal]
|
||||
MaxLevelStore=info
|
||||
MaxLevelSyslog=info
|
||||
JEOF
|
||||
journald_changed=true
|
||||
msg_ok "$(translate "journald drop-in created: /etc/systemd/journald.conf.d/proxmenux-loglevel.conf")"
|
||||
;;
|
||||
*)
|
||||
msg_ok "$(translate "journald MaxLevelStore is adequate for auth logging")"
|
||||
;;
|
||||
esac
|
||||
|
||||
if $journald_changed; then
|
||||
systemctl restart systemd-journald
|
||||
sleep 1
|
||||
msg_ok "$(translate "journald restarted - auth messages will now be stored")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Journal-to-file logger services for Fail2Ban ──
|
||||
# Fail2Ban's systemd backend has a known issue: it cannot reliably read
|
||||
# journal entries in real-time from certain services (pvedaemon workers,
|
||||
# and intermittently sshd). The solution is to create small systemd services
|
||||
# that tail the journal and write to log files, then fail2ban monitors those
|
||||
# files with the reliable backend=auto.
|
||||
|
||||
# -- Proxmox UI auth logger (pvedaemon) --
|
||||
msg_info "$(translate "Creating Proxmox auth logger service...")"
|
||||
cat > /etc/systemd/system/proxmox-auth-logger.service <<'EOF'
|
||||
[Unit]
|
||||
Description=Proxmox Auth Logger for Fail2Ban
|
||||
Documentation=https://github.com/MacRimi/ProxMenux
|
||||
After=pvedaemon.service
|
||||
PartOf=fail2ban.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/bash -c 'journalctl -f _SYSTEMD_UNIT=pvedaemon.service -o short-iso --no-pager >> /var/log/proxmox-auth.log'
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
touch /var/log/proxmox-auth.log
|
||||
chmod 640 /var/log/proxmox-auth.log
|
||||
chown root:adm /var/log/proxmox-auth.log 2>/dev/null || true
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now proxmox-auth-logger.service >/dev/null 2>&1
|
||||
msg_ok "$(translate "Proxmox auth logger service created and started")"
|
||||
|
||||
# -- SSH auth logger --
|
||||
msg_info "$(translate "Creating SSH auth logger service...")"
|
||||
cat > /etc/systemd/system/ssh-auth-logger.service <<'EOF'
|
||||
[Unit]
|
||||
Description=SSH Auth Logger for Fail2Ban
|
||||
Documentation=https://github.com/MacRimi/ProxMenux
|
||||
After=ssh.service
|
||||
PartOf=fail2ban.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/bash -c 'journalctl -f _SYSTEMD_UNIT=ssh.service -o short-iso --no-pager >> /var/log/ssh-auth.log'
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
touch /var/log/ssh-auth.log
|
||||
chmod 640 /var/log/ssh-auth.log
|
||||
chown root:adm /var/log/ssh-auth.log 2>/dev/null || true
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now ssh-auth-logger.service >/dev/null 2>&1
|
||||
msg_ok "$(translate "SSH auth logger service created and started")"
|
||||
|
||||
# Configure Proxmox filter
|
||||
mkdir -p /etc/fail2ban/filter.d /etc/fail2ban/jail.d
|
||||
msg_info "$(translate "Configuring Proxmox filter...")"
|
||||
cat > /etc/fail2ban/filter.d/proxmox.conf <<'EOF'
|
||||
[Definition]
|
||||
# The proxmox-auth-logger service writes journal lines to /var/log/proxmox-auth.log
|
||||
# in short-iso format: 2026-02-10T19:36:08+01:00 host pvedaemon[PID]: message
|
||||
# Proxmox logs IPs as ::ffff:x.x.x.x (IPv4-mapped IPv6).
|
||||
failregex = authentication (failure|error); rhost=(::ffff:)?<HOST> user=.* msg=.*
|
||||
ignoreregex =
|
||||
datepattern = ^%%Y-%%m-%%dT%%H:%%M:%%S
|
||||
EOF
|
||||
msg_ok "$(translate "Proxmox filter configured")"
|
||||
|
||||
# Configure Proxmox jail (file-based backend)
|
||||
msg_info "$(translate "Configuring Proxmox jail...")"
|
||||
cat > /etc/fail2ban/jail.d/proxmox.conf <<'EOF'
|
||||
[proxmox]
|
||||
enabled = true
|
||||
port = 8006
|
||||
filter = proxmox
|
||||
backend = auto
|
||||
logpath = /var/log/proxmox-auth.log
|
||||
maxretry = 3
|
||||
bantime = 3600
|
||||
findtime = 600
|
||||
EOF
|
||||
msg_ok "$(translate "Proxmox jail configured")"
|
||||
|
||||
# Configure ProxMenux Monitor filter
|
||||
# This reads from a file written directly by the Flask app (not syslog/journal),
|
||||
# so it uses a datepattern that matches Python's logging format.
|
||||
msg_info "$(translate "Configuring ProxMenux Monitor filter...")"
|
||||
cat > /etc/fail2ban/filter.d/proxmenux.conf <<'EOF'
|
||||
[Definition]
|
||||
failregex = ^.*proxmenux-auth: authentication failure; rhost=<HOST> user=.*$
|
||||
ignoreregex =
|
||||
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S
|
||||
EOF
|
||||
msg_ok "$(translate "ProxMenux Monitor filter configured")"
|
||||
|
||||
# Configure ProxMenux Monitor jail (port 8008 + http/https for reverse proxy)
|
||||
# Uses backend=auto with logpath because the Flask app writes directly to this file.
|
||||
msg_info "$(translate "Configuring ProxMenux Monitor jail...")"
|
||||
cat > /etc/fail2ban/jail.d/proxmenux.conf <<'EOF'
|
||||
[proxmenux]
|
||||
enabled = true
|
||||
port = 8008,http,https
|
||||
filter = proxmenux
|
||||
backend = auto
|
||||
logpath = /var/log/proxmenux-auth.log
|
||||
maxretry = 3
|
||||
bantime = 3600
|
||||
findtime = 600
|
||||
EOF
|
||||
msg_ok "$(translate "ProxMenux Monitor jail configured")"
|
||||
|
||||
# Ensure ProxMenux auth log exists (Flask writes here directly)
|
||||
touch /var/log/proxmenux-auth.log
|
||||
chmod 640 /var/log/proxmenux-auth.log 2>/dev/null || true
|
||||
|
||||
# Detect firewall backend (nftables preferred, fallback to iptables)
|
||||
local ban_action="iptables-multiport"
|
||||
local ban_action_all="iptables-allports"
|
||||
if command -v nft >/dev/null 2>&1 && nft list ruleset >/dev/null 2>&1; then
|
||||
ban_action="nftables"
|
||||
ban_action_all="nftables[type=allports]"
|
||||
msg_ok "$(translate "Detected nftables - using nftables ban action")"
|
||||
else
|
||||
msg_info "$(translate "nftables not available - using iptables ban action")"
|
||||
fi
|
||||
|
||||
# Configure global settings and SSH jail
|
||||
msg_info "$(translate "Configuring global Fail2Ban settings and SSH jail...")"
|
||||
cat > /etc/fail2ban/jail.local <<EOF
|
||||
[DEFAULT]
|
||||
ignoreip = 127.0.0.1/8 ::1
|
||||
ignoreself = true
|
||||
bantime = 86400
|
||||
maxretry = 2
|
||||
findtime = 1800
|
||||
backend = auto
|
||||
banaction = ${ban_action}
|
||||
banaction_allports = ${ban_action_all}
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
filter = sshd[mode=aggressive]
|
||||
backend = auto
|
||||
logpath = /var/log/ssh-auth.log
|
||||
maxretry = 2
|
||||
findtime = 3600
|
||||
bantime = 32400
|
||||
EOF
|
||||
msg_ok "$(translate "Global settings and SSH jail configured")"
|
||||
|
||||
# ── SSH Hardening: MaxAuthTries ──
|
||||
# Lynis (SSH-7408) recommends MaxAuthTries=3. With fail2ban maxretry=2,
|
||||
# SSH will never reach 3 attempts, but setting it satisfies the audit
|
||||
# and adds defense-in-depth. Backup original value for clean restore.
|
||||
local sshd_config="/etc/ssh/sshd_config"
|
||||
if [[ -f "$sshd_config" ]]; then
|
||||
# Save original MaxAuthTries value for restore on uninstall
|
||||
local original_max_auth
|
||||
original_max_auth=$(grep -i '^MaxAuthTries' "$sshd_config" 2>/dev/null | awk '{print $2}' || echo "6")
|
||||
if [[ -z "$original_max_auth" ]]; then
|
||||
original_max_auth="6"
|
||||
fi
|
||||
|
||||
# Store original value in our config directory
|
||||
echo "$original_max_auth" > "${BASE_DIR}/sshd_maxauthtries_backup"
|
||||
|
||||
msg_info "$(translate "Hardening SSH: setting MaxAuthTries to 3...")"
|
||||
if grep -qi '^MaxAuthTries' "$sshd_config"; then
|
||||
sed -i 's/^MaxAuthTries.*/MaxAuthTries 3/' "$sshd_config"
|
||||
elif grep -qi '^#MaxAuthTries' "$sshd_config"; then
|
||||
sed -i 's/^#MaxAuthTries.*/MaxAuthTries 3/' "$sshd_config"
|
||||
else
|
||||
echo "MaxAuthTries 3" >> "$sshd_config"
|
||||
fi
|
||||
|
||||
# Reload SSH to apply the change (reload, not restart, to keep existing sessions)
|
||||
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
||||
msg_ok "$(translate "SSH MaxAuthTries set to 3 (original: ${original_max_auth})")"
|
||||
fi
|
||||
|
||||
# Enable and restart the service (restart ensures new jails are loaded
|
||||
# even if fail2ban was already running from a previous install)
|
||||
systemctl daemon-reload
|
||||
systemctl enable fail2ban >/dev/null 2>&1
|
||||
systemctl restart fail2ban >/dev/null 2>&1
|
||||
sleep 3
|
||||
|
||||
# Verify
|
||||
if systemctl is-active --quiet fail2ban; then
|
||||
msg_ok "$(translate "Fail2Ban is running correctly")"
|
||||
else
|
||||
msg_error "$(translate "Fail2Ban is NOT running!")"
|
||||
journalctl -u fail2ban --no-pager -n 10
|
||||
fi
|
||||
|
||||
if fail2ban-client ping >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "fail2ban-client successfully communicated with the server")"
|
||||
else
|
||||
msg_error "$(translate "fail2ban-client could not communicate with the server")"
|
||||
fi
|
||||
|
||||
update_component_status "fail2ban" "installed" "$(fail2ban-client --version 2>/dev/null | head -n1)" "security" '{}'
|
||||
|
||||
msg_success "$(translate "Fail2Ban installation and configuration completed successfully!")"
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Uninstall
|
||||
# ==========================================================
|
||||
uninstall_fail2ban() {
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate "Removing Fail2Ban...")"
|
||||
|
||||
systemctl stop fail2ban 2>/dev/null || true
|
||||
systemctl disable fail2ban 2>/dev/null || true
|
||||
|
||||
# Stop and remove the auth logger services
|
||||
systemctl stop proxmox-auth-logger.service 2>/dev/null || true
|
||||
systemctl disable proxmox-auth-logger.service 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/proxmox-auth-logger.service
|
||||
systemctl stop ssh-auth-logger.service 2>/dev/null || true
|
||||
systemctl disable ssh-auth-logger.service 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/ssh-auth-logger.service
|
||||
systemctl daemon-reload 2>/dev/null || true
|
||||
rm -f /var/log/proxmox-auth.log /var/log/ssh-auth.log
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get purge -y fail2ban >/dev/null 2>&1
|
||||
rm -f /etc/fail2ban/jail.d/proxmox.conf
|
||||
rm -f /etc/fail2ban/jail.d/proxmenux.conf
|
||||
rm -f /etc/fail2ban/filter.d/proxmox.conf
|
||||
rm -f /etc/fail2ban/filter.d/proxmenux.conf
|
||||
rm -f /etc/fail2ban/jail.local
|
||||
|
||||
# ── Restore SSH MaxAuthTries to original value ──
|
||||
local sshd_config="/etc/ssh/sshd_config"
|
||||
local backup_file="${BASE_DIR}/sshd_maxauthtries_backup"
|
||||
if [[ -f "$backup_file" && -f "$sshd_config" ]]; then
|
||||
local original_val
|
||||
original_val=$(cat "$backup_file" 2>/dev/null | tr -d '[:space:]')
|
||||
if [[ -n "$original_val" ]]; then
|
||||
msg_info "$(translate "Restoring SSH MaxAuthTries to ${original_val}...")"
|
||||
if grep -qi '^MaxAuthTries' "$sshd_config"; then
|
||||
sed -i "s/^MaxAuthTries.*/MaxAuthTries ${original_val}/" "$sshd_config"
|
||||
fi
|
||||
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
||||
msg_ok "$(translate "SSH MaxAuthTries restored to ${original_val}")"
|
||||
fi
|
||||
rm -f "$backup_file"
|
||||
fi
|
||||
|
||||
# Remove journald drop-in and restore original log level
|
||||
if [[ -f /etc/systemd/journald.conf.d/proxmenux-loglevel.conf ]]; then
|
||||
rm -f /etc/systemd/journald.conf.d/proxmenux-loglevel.conf
|
||||
systemctl restart systemd-journald 2>/dev/null || true
|
||||
msg_ok "$(translate "journald log level restored")"
|
||||
fi
|
||||
|
||||
update_component_status "fail2ban" "removed" "" "security" '{}'
|
||||
|
||||
msg_ok "$(translate "Fail2Ban has been removed")"
|
||||
msg_success "$(translate "Uninstallation completed. Press Enter to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Main
|
||||
# ==========================================================
|
||||
main() {
|
||||
detect_fail2ban
|
||||
|
||||
if $FAIL2BAN_INSTALLED; then
|
||||
# Already installed - show action menu
|
||||
local action_text
|
||||
action_text="\n$(translate 'Fail2Ban is currently installed.')\n"
|
||||
action_text+="$(translate 'Version:') $FAIL2BAN_VERSION\n"
|
||||
action_text+="$(translate 'Status:') $(if $FAIL2BAN_ACTIVE; then translate 'Active'; else translate 'Inactive'; fi)\n\n"
|
||||
action_text+="$(translate 'What would you like to do?')"
|
||||
|
||||
local ACTION
|
||||
ACTION=$(hybrid_menu "$(translate 'Fail2Ban Management')" "$action_text" 18 70 3 \
|
||||
"reinstall" "$(translate 'Reinstall and reconfigure Fail2Ban')" \
|
||||
"remove" "$(translate 'Uninstall Fail2Ban')" \
|
||||
"cancel" "$(translate 'Cancel')" \
|
||||
) || ACTION="cancel"
|
||||
|
||||
case "$ACTION" in
|
||||
reinstall)
|
||||
if hybrid_yesno "$(translate 'Reinstall Fail2Ban')" \
|
||||
"\n\n$(translate 'This will reinstall and reconfigure Fail2Ban with the default ProxMenux settings. Continue?')" 12 70; then
|
||||
install_fail2ban
|
||||
fi
|
||||
;;
|
||||
remove)
|
||||
if hybrid_yesno "$(translate 'Remove Fail2Ban')" \
|
||||
"\n\n$(translate 'This will completely remove Fail2Ban and its configuration. Continue?')" 12 70; then
|
||||
uninstall_fail2ban
|
||||
fi
|
||||
;;
|
||||
cancel|*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Not installed - confirm and install
|
||||
local info_text
|
||||
info_text="\n$(translate 'Fail2Ban is not installed on this system.')\n\n"
|
||||
info_text+="$(translate 'This will install and configure Fail2Ban with:')\n\n"
|
||||
info_text+=" - $(translate 'SSH protection (aggressive mode)') (max 2 $(translate 'retries'), 9h $(translate 'ban'))\n"
|
||||
info_text+=" - $(translate 'Proxmox web interface protection') ($(translate 'port') 8006, max 3 $(translate 'retries'), 1h $(translate 'ban'))\n"
|
||||
info_text+=" - $(translate 'ProxMenux Monitor protection') ($(translate 'port') 8008 + $(translate 'reverse proxy'), max 3 $(translate 'retries'), 1h $(translate 'ban'))\n"
|
||||
info_text+=" - $(translate 'Auto-detected firewall backend (nftables/iptables)')\n"
|
||||
info_text+=" - $(translate 'Adjusts journald log level if needed (Proxmox defaults may block auth logs)')\n"
|
||||
info_text+=" - $(translate 'SSH hardening: MaxAuthTries set to 3 (Lynis recommendation)')\n\n"
|
||||
info_text+="$(translate 'Do you want to proceed?')"
|
||||
|
||||
if hybrid_yesno "$(translate 'Install Fail2Ban')" "$info_text" 20 70; then
|
||||
install_fail2ban
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
main
|
||||
fi
|
||||
250
scripts/security/lynis_installer.sh
Normal file
250
scripts/security/lynis_installer.sh
Normal file
@@ -0,0 +1,250 @@
|
||||
#!/bin/bash
|
||||
# ProxMenux - Lynis Security Audit Tool Installer
|
||||
# ============================================
|
||||
# Author : MacRimi
|
||||
# License : MIT
|
||||
# Version : 1.0
|
||||
# ============================================
|
||||
# Hybrid script: works from terminal (dialog) and web panel (ScriptTerminalModal)
|
||||
|
||||
SCRIPT_TITLE="Lynis Security Audit Tool Installer"
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
COMPONENTS_STATUS_FILE="$BASE_DIR/components_status.json"
|
||||
|
||||
export BASE_DIR
|
||||
export COMPONENTS_STATUS_FILE
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
|
||||
echo "{}" > "$COMPONENTS_STATUS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Detection
|
||||
# ==========================================================
|
||||
detect_lynis() {
|
||||
LYNIS_INSTALLED=false
|
||||
LYNIS_VERSION=""
|
||||
LYNIS_CMD=""
|
||||
|
||||
for path in /usr/local/bin/lynis /opt/lynis/lynis /usr/bin/lynis; do
|
||||
if [[ -f "$path" ]] && [[ -x "$path" ]]; then
|
||||
LYNIS_CMD="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$LYNIS_CMD" ]]; then
|
||||
LYNIS_INSTALLED=true
|
||||
LYNIS_VERSION=$("$LYNIS_CMD" show version 2>/dev/null || echo "unknown")
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Installation
|
||||
# ==========================================================
|
||||
install_lynis() {
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate "Installing latest Lynis security scan tool...")"
|
||||
|
||||
# Install git if needed
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
msg_info "$(translate "Installing Git as a prerequisite...")"
|
||||
apt-get update -qq >/dev/null 2>&1
|
||||
apt-get install -y git >/dev/null 2>&1
|
||||
msg_ok "$(translate "Git installed")"
|
||||
fi
|
||||
|
||||
# Remove old installation if present
|
||||
if [[ -d /opt/lynis ]]; then
|
||||
msg_info "$(translate "Removing previous Lynis installation...")"
|
||||
rm -rf /opt/lynis >/dev/null 2>&1
|
||||
msg_ok "$(translate "Previous installation removed")"
|
||||
fi
|
||||
|
||||
# Clone from GitHub
|
||||
msg_info "$(translate "Cloning Lynis from GitHub...")"
|
||||
if git clone --quiet https://github.com/CISOfy/lynis.git /opt/lynis >/dev/null 2>&1; then
|
||||
# Create wrapper script
|
||||
cat << 'EOF' > /usr/local/bin/lynis
|
||||
#!/bin/bash
|
||||
cd /opt/lynis && ./lynis "$@"
|
||||
EOF
|
||||
chmod +x /usr/local/bin/lynis
|
||||
msg_ok "$(translate "Lynis installed successfully from GitHub")"
|
||||
else
|
||||
msg_error "$(translate "Failed to clone Lynis from GitHub")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify
|
||||
if /usr/local/bin/lynis show version >/dev/null 2>&1; then
|
||||
local version
|
||||
version=$(/usr/local/bin/lynis show version 2>/dev/null)
|
||||
update_component_status "lynis" "installed" "$version" "security" '{}'
|
||||
msg_ok "$(translate "Lynis version:") $version"
|
||||
msg_success "$(translate "Lynis is ready to use")"
|
||||
else
|
||||
msg_warn "$(translate "Lynis installation could not be verified")"
|
||||
fi
|
||||
|
||||
msg_info2 "$(translate "You can run a security audit with:")"
|
||||
echo -e " lynis audit system"
|
||||
echo ""
|
||||
msg_success "$(translate "Installation completed. Press Enter to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Update
|
||||
# ==========================================================
|
||||
update_lynis() {
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate "Updating Lynis to the latest version...")"
|
||||
|
||||
if [[ -d /opt/lynis/.git ]]; then
|
||||
cd /opt/lynis
|
||||
msg_info "$(translate "Pulling latest changes from GitHub...")"
|
||||
if git pull --quiet >/dev/null 2>&1; then
|
||||
local version
|
||||
version=$(/usr/local/bin/lynis show version 2>/dev/null)
|
||||
update_component_status "lynis" "installed" "$version" "security" '{}'
|
||||
msg_ok "$(translate "Lynis updated to version:") $version"
|
||||
else
|
||||
msg_error "$(translate "Failed to update Lynis")"
|
||||
fi
|
||||
else
|
||||
msg_warn "$(translate "Lynis was not installed from Git. Reinstalling...")"
|
||||
install_lynis
|
||||
return
|
||||
fi
|
||||
|
||||
msg_success "$(translate "Update completed. Press Enter to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Run Audit
|
||||
# ==========================================================
|
||||
run_audit() {
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate "Running Lynis security audit...")"
|
||||
echo ""
|
||||
|
||||
if [[ -z "$LYNIS_CMD" ]]; then
|
||||
msg_error "$(translate "Lynis command not found")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Run the audit
|
||||
"$LYNIS_CMD" audit system --no-colors 2>&1
|
||||
|
||||
echo ""
|
||||
msg_success "$(translate "Audit completed. Press Enter to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Uninstall
|
||||
# ==========================================================
|
||||
uninstall_lynis() {
|
||||
show_proxmenux_logo
|
||||
msg_title "$(translate "$SCRIPT_TITLE")"
|
||||
msg_info2 "$(translate "Removing Lynis...")"
|
||||
|
||||
rm -rf /opt/lynis 2>/dev/null
|
||||
rm -f /usr/local/bin/lynis 2>/dev/null
|
||||
|
||||
update_component_status "lynis" "removed" "" "security" '{}'
|
||||
|
||||
msg_ok "$(translate "Lynis has been removed")"
|
||||
msg_success "$(translate "Uninstallation completed. Press Enter to continue...")"
|
||||
read -r
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Main
|
||||
# ==========================================================
|
||||
main() {
|
||||
detect_lynis
|
||||
|
||||
if $LYNIS_INSTALLED; then
|
||||
# Already installed - show action menu
|
||||
local action_text
|
||||
action_text="\n$(translate 'Lynis is currently installed.')\n"
|
||||
action_text+="$(translate 'Version:') $LYNIS_VERSION\n\n"
|
||||
action_text+="$(translate 'What would you like to do?')"
|
||||
|
||||
local ACTION
|
||||
ACTION=$(hybrid_menu "$(translate 'Lynis Management')" "$action_text" 20 70 5 \
|
||||
"audit" "$(translate 'Run security audit now')" \
|
||||
"update" "$(translate 'Update Lynis to latest version')" \
|
||||
"reinstall" "$(translate 'Reinstall Lynis')" \
|
||||
"remove" "$(translate 'Uninstall Lynis')" \
|
||||
"cancel" "$(translate 'Cancel')" \
|
||||
) || ACTION="cancel"
|
||||
|
||||
case "$ACTION" in
|
||||
audit)
|
||||
run_audit
|
||||
;;
|
||||
update)
|
||||
update_lynis
|
||||
;;
|
||||
reinstall)
|
||||
if hybrid_yesno "$(translate 'Reinstall Lynis')" \
|
||||
"\n\n$(translate 'This will remove and reinstall Lynis from the latest GitHub source. Continue?')" 12 70; then
|
||||
install_lynis
|
||||
fi
|
||||
;;
|
||||
remove)
|
||||
if hybrid_yesno "$(translate 'Remove Lynis')" \
|
||||
"\n\n$(translate 'This will completely remove Lynis from the system. Continue?')" 12 70; then
|
||||
uninstall_lynis
|
||||
fi
|
||||
;;
|
||||
cancel|*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Not installed - confirm and install
|
||||
local info_text
|
||||
info_text="\n$(translate 'Lynis is not installed on this system.')\n\n"
|
||||
info_text+="$(translate 'Lynis is a security auditing tool that performs comprehensive system scans including:')\n\n"
|
||||
info_text+=" - $(translate 'System hardening scoring (0-100)')\n"
|
||||
info_text+=" - $(translate 'Vulnerability detection')\n"
|
||||
info_text+=" - $(translate 'Configuration analysis')\n"
|
||||
info_text+=" - $(translate 'Compliance checking (PCI-DSS, HIPAA, etc.)')\n\n"
|
||||
info_text+="$(translate 'It will be installed from the official GitHub repository.')\n\n"
|
||||
info_text+="$(translate 'Do you want to proceed?')"
|
||||
|
||||
if hybrid_yesno "$(translate 'Install Lynis')" "$info_text" 22 70; then
|
||||
install_lynis
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
main
|
||||
fi
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,240 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenux - UUP Dump ISO Creator
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/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
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
VMID="105"
|
||||
|
||||
echo -e "\n=== 📦 Discos físicos detectados (lsblk) ==="
|
||||
lsblk -dn -o PATH,TYPE | awk '$2 == "disk" {print $1}'
|
||||
|
||||
echo -e "\n=== 📌 Discos montados ==="
|
||||
mount | grep /dev/sd || echo "Ninguno"
|
||||
|
||||
echo -e "\n=== 🧱 Discos en uso por LVM (pvs) ==="
|
||||
pvs --noheadings -o pv_name | xargs -n1 readlink -f | sort -u || echo "Ninguno"
|
||||
|
||||
echo -e "\n=== ⚠ RAID activos (mdstat) ==="
|
||||
awk '/^md/ {for (i=4; i<=NF; i++) print $i}' /proc/mdstat || echo "Ninguno"
|
||||
|
||||
echo -e "\n=== 💻 Discos ya asignados a la VM ID $VMID ==="
|
||||
qm config "$VMID" | grep -E '^(scsi|sata|virtio|ide)[0-9]+:' | awk -F ':' '{print $2}' | cut -d',' -f1
|
||||
|
||||
echo -e "\n=== 🧪 FSTYPE de cada disco ==="
|
||||
for disk in $(lsblk -dn -o PATH,TYPE | awk '$2 == "disk" {print $1}'); do
|
||||
echo -e "\n→ $disk"
|
||||
lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2
|
||||
done
|
||||
|
||||
|
||||
|
||||
echo "📋 Analizando discos físicos..."
|
||||
ACTIVE_MD_DEVICES=$(awk '/^md/ {for (i=4; i<=NF; i++) print $i}' /proc/mdstat)
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name | xargs -n1 readlink -f | sed 's/ *$//' | sort -u)
|
||||
MOUNTED_DISKS=$(mount | grep /dev/sd | awk '{print $1}' | sort -u)
|
||||
|
||||
for DISK in $(lsblk -dn -o PATH,TYPE | awk '$2 == "disk" {print $1}'); do
|
||||
echo -e "\n🔍 Disco: $DISK"
|
||||
echo " - En LVM: $(echo "$LVM_DEVICES" | grep -Fxq "$DISK" && echo 'SÍ' || echo 'NO')"
|
||||
echo " - Es ZVOL (zd*): $( [[ $(basename "$DISK") == zd* ]] && echo 'SÍ' || echo 'NO')"
|
||||
echo " - Ya está en la VM: $(qm config "$VMID" | grep -q "$DISK" && echo 'SÍ' || echo 'NO')"
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_RAID_ACTIVE=false
|
||||
IS_ZFS=false
|
||||
|
||||
while read -r part fstype; do
|
||||
full_path="/dev/$part"
|
||||
real_path=$(readlink -f "$full_path")
|
||||
|
||||
[[ -z "$fstype" ]] && continue
|
||||
|
||||
echo " ➤ Partición: $part ($fstype)"
|
||||
if echo "$MOUNTED_DISKS" | grep -q "$full_path"; then
|
||||
echo " ⛔ Montado en el sistema"
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
if echo "$LVM_DEVICES" | grep -Fxq "$real_path"; then
|
||||
echo " ⛔ En uso por LVM"
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
if [[ "$fstype" == "linux_raid_member" ]]; then
|
||||
IS_RAID=true
|
||||
if echo "$ACTIVE_MD_DEVICES" | grep -q "$part"; then
|
||||
IS_RAID_ACTIVE=true
|
||||
echo " ⛔ RAID activo"
|
||||
else
|
||||
echo " ⚠ RAID pasivo"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$fstype" == "zfs_member" ]]; then
|
||||
IS_ZFS=true
|
||||
echo " ⛔ ZFS detectado"
|
||||
fi
|
||||
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$DISK" | tail -n +2)
|
||||
|
||||
echo "Resumen:"
|
||||
echo " - Montado: $IS_MOUNTED"
|
||||
echo " - RAID activo: $IS_RAID_ACTIVE"
|
||||
echo " - RAID pasivo: $IS_RAID"
|
||||
echo " - ZFS: $IS_ZFS"
|
||||
done
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
VMID="105"
|
||||
|
||||
echo -e "\n=== 📦 Discos físicos detectados (lsblk) ==="
|
||||
lsblk -dn -o PATH,TYPE | awk '$2 == "disk" {print $1}'
|
||||
|
||||
echo -e "\n=== 📌 Discos montados ==="
|
||||
mount | grep /dev/sd || echo "Ninguno"
|
||||
|
||||
echo -e "\n=== 🧱 Discos en uso por LVM (pvs) ==="
|
||||
pvs --noheadings -o pv_name | xargs -n1 readlink -f | sort -u || echo "Ninguno"
|
||||
|
||||
echo -e "\n=== ⚠ RAID activos (mdstat) ==="
|
||||
awk '/^md/ {for (i=4; i<=NF; i++) print $i}' /proc/mdstat || echo "Ninguno"
|
||||
|
||||
echo -e "\n=== 💻 Discos ya asignados a la VM ID $VMID ==="
|
||||
qm config "$VMID" | grep -E '^(scsi|sata|virtio|ide)[0-9]+:' | awk -F ':' '{print $2}' | cut -d',' -f1
|
||||
|
||||
echo -e "\n=== 🧪 FSTYPE de cada disco ==="
|
||||
for disk in $(lsblk -dn -o PATH,TYPE | awk '$2 == "disk" {print $1}'); do
|
||||
echo -e "\n→ $disk"
|
||||
lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2
|
||||
done
|
||||
@@ -1,327 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/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 ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
# ==========================================================
|
||||
|
||||
HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json"
|
||||
METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json"
|
||||
|
||||
for cmd in curl jq dialog; do
|
||||
if ! command -v "$cmd" >/dev/null; then
|
||||
echo "Missing required command: $cmd"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
CACHE_JSON=$(curl -s "$HELPERS_JSON_URL")
|
||||
META_JSON=$(curl -s "$METADATA_URL")
|
||||
|
||||
declare -A CATEGORY_NAMES
|
||||
while read -r id name; do
|
||||
CATEGORY_NAMES[$id]="$name"
|
||||
done < <(echo "$META_JSON" | jq -r '.categories[] | "\(.id)\t\(.name)"')
|
||||
|
||||
declare -A CATEGORY_COUNT
|
||||
for id in $(echo "$CACHE_JSON" | jq -r '.[].categories[]'); do
|
||||
((CATEGORY_COUNT[$id]++))
|
||||
done
|
||||
|
||||
get_type_label() {
|
||||
local type="$1"
|
||||
case "$type" in
|
||||
ct) echo $'\Z1LXC\Zn' ;;
|
||||
vm) echo $'\Z4VM\Zn' ;;
|
||||
pve) echo $'\Z3PVE\Zn' ;;
|
||||
addon) echo $'\Z2ADDON\Zn' ;;
|
||||
*) echo $'\Z7GEN\Zn' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
download_script() {
|
||||
local url="$1"
|
||||
local fallback_pve="${url/misc\/tools\/pve}"
|
||||
local fallback_addon="${url/misc\/tools\/addon}"
|
||||
local fallback_copydata="${url/misc\/tools\/copy-data}"
|
||||
|
||||
if curl --silent --head --fail "$url" >/dev/null; then
|
||||
bash <(curl -s "$url")
|
||||
elif curl --silent --head --fail "$fallback_pve" >/dev/null; then
|
||||
bash <(curl -s "$fallback_pve")
|
||||
elif curl --silent --head --fail "$fallback_addon" >/dev/null; then
|
||||
bash <(curl -s "$fallback_addon")
|
||||
elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then
|
||||
bash <(curl -s "$fallback_copydata")
|
||||
else
|
||||
dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70
|
||||
fi
|
||||
}
|
||||
|
||||
RETURN_TO_MAIN=false
|
||||
|
||||
format_credentials() {
|
||||
local script_info="$1"
|
||||
local credentials_info=""
|
||||
|
||||
local has_credentials
|
||||
has_credentials=$(echo "$script_info" | base64 --decode | jq -r 'has("default_credentials")')
|
||||
|
||||
if [[ "$has_credentials" == "true" ]]; then
|
||||
local username password
|
||||
username=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.username // empty')
|
||||
password=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.password // empty')
|
||||
|
||||
if [[ -n "$username" && -n "$password" ]]; then
|
||||
credentials_info="Username: $username | Password: $password"
|
||||
elif [[ -n "$username" ]]; then
|
||||
credentials_info="Username: $username"
|
||||
elif [[ -n "$password" ]]; then
|
||||
credentials_info="Password: $password"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$credentials_info"
|
||||
}
|
||||
|
||||
|
||||
run_script_by_slug() {
|
||||
local slug="$1"
|
||||
local script_info
|
||||
script_info=$(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64')
|
||||
|
||||
decode() {
|
||||
echo "$1" | base64 --decode | jq -r "$2"
|
||||
}
|
||||
|
||||
local name desc script_url notes
|
||||
name=$(decode "$script_info" ".name")
|
||||
desc=$(decode "$script_info" ".desc")
|
||||
script_url=$(decode "$script_info" ".script_url")
|
||||
notes=$(decode "$script_info" ".notes | join(\"\n\")")
|
||||
|
||||
|
||||
local notes_dialog=""
|
||||
if [[ -n "$notes" ]]; then
|
||||
while IFS= read -r line; do
|
||||
notes_dialog+="• $line\n"
|
||||
done <<< "$notes"
|
||||
notes_dialog="${notes_dialog%\\n}"
|
||||
fi
|
||||
|
||||
|
||||
local credentials
|
||||
credentials=$(format_credentials "$script_info")
|
||||
|
||||
|
||||
local msg="\Zb\Z4Descripción:\Zn\n$desc"
|
||||
[[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog"
|
||||
[[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials"
|
||||
|
||||
dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85
|
||||
if [[ $? -eq 0 ]]; then
|
||||
download_script "$script_url"
|
||||
echo
|
||||
echo
|
||||
|
||||
if [[ -n "$desc" || -n "$notes" || -n "$credentials" ]]; then
|
||||
echo -e "$TAB\e[1;36mScript Information:\e[0m"
|
||||
|
||||
|
||||
|
||||
if [[ -n "$notes" ]]; then
|
||||
echo -e "$TAB\e[1;33mNotes:\e[0m"
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
echo -e "$TAB• $line"
|
||||
done <<< "$notes"
|
||||
echo
|
||||
fi
|
||||
|
||||
|
||||
if [[ -n "$credentials" ]]; then
|
||||
echo -e "$TAB\e[1;32mDefault Credentials:\e[0m"
|
||||
echo "$TAB$credentials"
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
|
||||
msg_success "Press Enter to return to the main menu..."
|
||||
read -r
|
||||
RETURN_TO_MAIN=true
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
search_and_filter_scripts() {
|
||||
local search_term=""
|
||||
|
||||
while true; do
|
||||
search_term=$(dialog --inputbox "Enter search term (leave empty to show all scripts):" \
|
||||
8 65 "$search_term" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && return
|
||||
|
||||
local filtered_json
|
||||
if [[ -z "$search_term" ]]; then
|
||||
filtered_json="$CACHE_JSON"
|
||||
else
|
||||
local search_lower
|
||||
search_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]')
|
||||
filtered_json=$(echo "$CACHE_JSON" | jq --arg term "$search_lower" '
|
||||
[.[] | select(
|
||||
(.name | ascii_downcase | contains($term)) or
|
||||
(.desc | ascii_downcase | contains($term))
|
||||
)]')
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(echo "$filtered_json" | jq length)
|
||||
|
||||
if [[ $count -eq 0 ]]; then
|
||||
dialog --msgbox "No scripts found for: '$search_term'\n\nTry a different search term." 8 50
|
||||
continue
|
||||
fi
|
||||
|
||||
while true; do
|
||||
declare -A index_to_slug
|
||||
local menu_items=()
|
||||
local i=1
|
||||
|
||||
while IFS=$'\t' read -r slug name type; do
|
||||
index_to_slug[$i]="$slug"
|
||||
local label
|
||||
label=$(get_type_label "$type")
|
||||
local padded_name
|
||||
padded_name=$(printf "%-42s" "$name")
|
||||
local entry="$padded_name $label"
|
||||
menu_items+=("$i" "$entry")
|
||||
((i++))
|
||||
done < <(echo "$filtered_json" | jq -r '
|
||||
sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||
|
||||
menu_items+=("" "")
|
||||
menu_items+=("new_search" "New Search")
|
||||
menu_items+=("show_all" "Show All Scripts")
|
||||
|
||||
local title="Search Results"
|
||||
if [[ -n "$search_term" ]]; then
|
||||
title="Search Results for: '$search_term' ($count found)"
|
||||
else
|
||||
title="All Available Scripts ($count total)"
|
||||
fi
|
||||
|
||||
local selected
|
||||
selected=$(dialog --colors --backtitle "ProxMenux" \
|
||||
--title "$title" \
|
||||
--menu "Select a script or action:" \
|
||||
22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$selected" in
|
||||
"new_search")
|
||||
break
|
||||
;;
|
||||
"show_all")
|
||||
search_term=""
|
||||
filtered_json="$CACHE_JSON"
|
||||
count=$(echo "$filtered_json" | jq length)
|
||||
continue
|
||||
;;
|
||||
"back"|"")
|
||||
return
|
||||
;;
|
||||
*)
|
||||
if [[ -n "${index_to_slug[$selected]}" ]]; then
|
||||
run_script_by_slug "${index_to_slug[$selected]}"
|
||||
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; }
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
while true; do
|
||||
MENU_ITEMS=()
|
||||
|
||||
MENU_ITEMS+=("search" "Search/Filter Scripts")
|
||||
MENU_ITEMS+=("" "")
|
||||
|
||||
for id in $(printf "%s\n" "${!CATEGORY_COUNT[@]}" | sort -n); do
|
||||
name="${CATEGORY_NAMES[$id]:-Category $id}"
|
||||
count="${CATEGORY_COUNT[$id]}"
|
||||
padded_name=$(printf "%-35s" "$name")
|
||||
padded_count=$(printf "(%2d)" "$count")
|
||||
MENU_ITEMS+=("$id" "$padded_name $padded_count")
|
||||
done
|
||||
|
||||
SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \
|
||||
"Select a category or search for scripts:" 20 70 14 \
|
||||
"${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || {
|
||||
dialog --title "Proxmox VE Helper-Scripts" \
|
||||
--msgbox "\n\n$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:")\n\nhttps://community-scripts.github.io/ProxmoxVE" 15 70
|
||||
clear
|
||||
break
|
||||
}
|
||||
|
||||
if [[ "$SELECTED" == "search" ]]; then
|
||||
search_and_filter_scripts
|
||||
continue
|
||||
fi
|
||||
|
||||
while true; do
|
||||
declare -A INDEX_TO_SLUG
|
||||
SCRIPTS=()
|
||||
i=1
|
||||
while IFS=$'\t' read -r slug name type; do
|
||||
INDEX_TO_SLUG[$i]="$slug"
|
||||
label=$(get_type_label "$type")
|
||||
padded_name=$(printf "%-42s" "$name")
|
||||
entry="$padded_name $label"
|
||||
SCRIPTS+=("$i" "$entry")
|
||||
((i++))
|
||||
done < <(echo "$CACHE_JSON" | jq -r --argjson id "$SELECTED" \
|
||||
'[.[] | select(.categories | index($id)) | {slug, name, type}] | sort_by(.name)[] | [.slug, .name, .type] | @tsv')
|
||||
|
||||
SCRIPT_INDEX=$(dialog --colors --backtitle "ProxMenux" --title "Scripts in ${CATEGORY_NAMES[$SELECTED]}" --menu \
|
||||
"Choose a script to execute:" 20 70 14 \
|
||||
"${SCRIPTS[@]}" 3>&1 1>&2 2>&3) || break
|
||||
|
||||
SCRIPT_SELECTED="${INDEX_TO_SLUG[$SCRIPT_INDEX]}"
|
||||
run_script_by_slug "$SCRIPT_SELECTED"
|
||||
|
||||
[[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; break; }
|
||||
done
|
||||
done
|
||||
@@ -1,355 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
|
||||
# Version : 1.0
|
||||
# Last Updated: 28/01/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script allows users to assign physical disks to existing
|
||||
# Proxmox virtual machines (VMs) through an interactive menu.
|
||||
# - Detects the system disk and excludes it from selection.
|
||||
# - Lists all available VMs for the user to choose from.
|
||||
# - Identifies and displays unassigned physical disks.
|
||||
# - Allows the user to select multiple disks and attach them to a VM.
|
||||
# - Supports interface types: SATA, SCSI, VirtIO, and IDE.
|
||||
# - Ensures that disks are not already assigned to active VMs.
|
||||
# - Warns about disk sharing between multiple VMs to avoid data corruption.
|
||||
# - Configures the selected disks for the VM and verifies the assignment.
|
||||
#
|
||||
# The goal of this script is to simplify the process of assigning
|
||||
# physical disks to Proxmox VMs, reducing manual configurations
|
||||
# and preventing potential errors.
|
||||
# ==========================================================
|
||||
|
||||
|
||||
# Configuration ============================================
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
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
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
|
||||
# Function to get detailed disk information
|
||||
get_disk_info() {
|
||||
local disk=$1
|
||||
MODEL=$(lsblk -dn -o MODEL "$disk" | xargs)
|
||||
SIZE=$(lsblk -dn -o SIZE "$disk" | xargs)
|
||||
echo "$MODEL" "$SIZE"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# Display list of available VMs
|
||||
VM_LIST=$(qm list | awk 'NR>1 {print $1, $2}')
|
||||
if [ -z "$VM_LIST" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No VMs available in the system.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Select VM
|
||||
VMID=$(whiptail --title "$(translate "Select VM")" --menu "$(translate "Select the VM to which you want to add disks:")" 15 60 8 $VM_LIST 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$VMID" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No VM was selected.")" 8 40
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VMID=$(echo "$VMID" | tr -d '"')
|
||||
|
||||
|
||||
|
||||
#clear
|
||||
msg_ok "$(translate "VM selected successfully.")"
|
||||
|
||||
|
||||
VM_STATUS=$(qm status "$VMID" | awk '{print $2}')
|
||||
if [ "$VM_STATUS" == "running" ]; then
|
||||
whiptail --title "$(translate "Warning")" --msgbox "$(translate "The VM is powered on. Turn it off before adding disks.")" 12 60
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
##########################################
|
||||
|
||||
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)
|
||||
|
||||
|
||||
is_disk_in_use() {
|
||||
local disk="$1"
|
||||
|
||||
|
||||
while read -r part fstype; do
|
||||
case "$fstype" in
|
||||
zfs_member|linux_raid_member)
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
if echo "$MOUNTED_DISKS" | grep -q "/dev/$part"; then
|
||||
return 0
|
||||
fi
|
||||
done < <(lsblk -ln -o NAME,FSTYPE "$disk" | tail -n +2)
|
||||
|
||||
|
||||
if echo "$USED_DISKS" | grep -q "$disk" || echo "$ZFS_DISKS" | grep -q "$disk"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
FREE_DISKS=()
|
||||
|
||||
LVM_DEVICES=$(pvs --noheadings -o pv_name | xargs -n1 readlink -f | sort -u)
|
||||
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
|
||||
|
||||
while read -r DISK; do
|
||||
# Saltar ZVOLs
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
INFO=($(get_disk_info "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$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=$(readlink -f "$DISK")
|
||||
if echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
# RAID activo → no mostrar
|
||||
if $IS_RAID && grep -q "$DISK" <<< "$(cat /proc/mdstat)"; then
|
||||
if grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# ZFS no mostrar nunca
|
||||
if $IS_ZFS; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
# Si está montado → ocultar
|
||||
if $IS_MOUNTED; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
# Ya asignado a la VM actual
|
||||
if qm config "$VMID" | grep -q "$DISK"; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $SHOW_DISK; then
|
||||
[[ "$IS_RAID" == true ]] && LABEL+=" ⚠ with partitions"
|
||||
[[ "$IS_LVM" == true ]] && LABEL+=" ⚠ LVM"
|
||||
[[ "$IS_ZFS" == true ]] && LABEL+=" ⚠ ZFS"
|
||||
|
||||
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
|
||||
cleanup
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No disks available for this VM.")" 8 40
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Available disks detected.")"
|
||||
|
||||
|
||||
|
||||
######################################################
|
||||
|
||||
|
||||
|
||||
|
||||
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
|
||||
if [ $TOTAL_WIDTH -lt 70 ]; then
|
||||
TOTAL_WIDTH=70
|
||||
fi
|
||||
|
||||
|
||||
SELECTED=$(whiptail --title "$(translate "Select Disks")" --checklist \
|
||||
"$(translate "Select the disks you want to add:")" 20 $TOTAL_WIDTH 10 "${FREE_DISKS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No disks were selected.")" 10 $TOTAL_WIDTH
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Disks selected successfully.")"
|
||||
|
||||
|
||||
INTERFACE=$(whiptail --title "$(translate "Interface Type")" --menu "$(translate "Select the interface type for all disks:")" 15 40 4 \
|
||||
"sata" "$(translate "Add as SATA")" \
|
||||
"scsi" "$(translate "Add as SCSI")" \
|
||||
"virtio" "$(translate "Add as VirtIO")" \
|
||||
"ide" "$(translate "Add as IDE")" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$INTERFACE" ]; then
|
||||
whiptail --title "$(translate "Error")" --msgbox "$(translate "No interface type was selected for the disks.")" 8 40
|
||||
clear
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Interface type selected: $INTERFACE")"
|
||||
|
||||
DISKS_ADDED=0
|
||||
ERROR_MESSAGES=""
|
||||
SUCCESS_MESSAGES=""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
msg_info "$(translate "Processing selected disks...")"
|
||||
|
||||
for DISK in $SELECTED; do
|
||||
DISK=$(echo "$DISK" | tr -d '"')
|
||||
DISK_INFO=$(get_disk_info "$DISK")
|
||||
|
||||
ASSIGNED_TO=""
|
||||
RUNNING_VMS=""
|
||||
|
||||
while read -r VM_ID VM_NAME; do
|
||||
if [[ "$VM_ID" =~ ^[0-9]+$ ]] && qm config "$VM_ID" | grep -q "$DISK"; then
|
||||
ASSIGNED_TO+="$VM_ID $VM_NAME\n"
|
||||
VM_STATUS=$(qm status "$VM_ID" | awk '{print $2}')
|
||||
if [ "$VM_STATUS" == "running" ]; then
|
||||
RUNNING_VMS+="$VM_ID $VM_NAME\n"
|
||||
fi
|
||||
fi
|
||||
done < <(qm list | awk 'NR>1 {print $1, $2}')
|
||||
|
||||
if [ -n "$RUNNING_VMS" ]; then
|
||||
ERROR_MESSAGES+="$(translate "The disk") $DISK_INFO $(translate "is in use by the following running VM(s):")\\n$RUNNING_VMS\\n\\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
cleanup
|
||||
whiptail --title "$(translate "Disk Already Assigned")" --yesno "$(translate "The disk") $DISK_INFO $(translate "is already assigned to the following VM(s):")\\n$ASSIGNED_TO\\n\\n$(translate "Do you want to continue anyway?")" 15 70
|
||||
if [ $? -ne 0 ]; then
|
||||
sleep 1
|
||||
exec "$0"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
INDEX=0
|
||||
while qm config "$VMID" | grep -q "${INTERFACE}${INDEX}"; do
|
||||
((INDEX++))
|
||||
done
|
||||
|
||||
RESULT=$(qm set "$VMID" -${INTERFACE}${INDEX} "$DISK" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
MESSAGE="$(translate "The disk") $DISK_INFO $(translate "has been successfully added to VM") $VMID."
|
||||
if [ -n "$ASSIGNED_TO" ]; then
|
||||
MESSAGE+="\\n\\n$(translate "WARNING: This disk is also assigned to the following VM(s):")\\n$ASSIGNED_TO"
|
||||
MESSAGE+="\\n$(translate "Make sure not to start VMs that share this disk at the same time to avoid data corruption.")"
|
||||
fi
|
||||
SUCCESS_MESSAGES+="$MESSAGE\\n\\n"
|
||||
((DISKS_ADDED++))
|
||||
else
|
||||
ERROR_MESSAGES+="$(translate "Could not add disk") $DISK_INFO $(translate "to VM") $VMID.\\n$(translate "Error:") $RESULT\\n\\n"
|
||||
fi
|
||||
done
|
||||
|
||||
msg_ok "$(translate "Disk processing completed.")"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if [ -n "$SUCCESS_MESSAGES" ]; then
|
||||
MSG_LINES=$(echo "$SUCCESS_MESSAGES" | wc -l)
|
||||
whiptail --title "$(translate "Successful Operations")" --msgbox "$SUCCESS_MESSAGES" 16 70
|
||||
fi
|
||||
|
||||
if [ -n "$ERROR_MESSAGES" ]; then
|
||||
whiptail --title "$(translate "Warnings and Errors")" --msgbox "$ERROR_MESSAGES" 16 70
|
||||
fi
|
||||
|
||||
|
||||
|
||||
exit 0
|
||||
@@ -1,194 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==========================================================
|
||||
# ProxMenu - A menu-driven script for Proxmox VE management
|
||||
# ==========================================================
|
||||
# Author : MacRimi
|
||||
# Copyright : (c) 2024 MacRimi
|
||||
# License : MIT
|
||||
# Version : 1.1
|
||||
# Last Updated: 30/04/2025
|
||||
# ==========================================================
|
||||
# Description:
|
||||
# This script allows users to repair or verify network configuration in Proxmox.
|
||||
# It avoids making changes if the system is already connected to the internet.
|
||||
# ==========================================================
|
||||
|
||||
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
detect_physical_interfaces() {
|
||||
physical_interfaces=$(ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy|bond|tap|fw|vmbr|br)/ {print $2}')
|
||||
whiptail --title "$(translate 'Network Interfaces')" --msgbox "$physical_interfaces" 10 78
|
||||
}
|
||||
|
||||
get_relevant_interfaces() {
|
||||
echo $(ip -o link show | awk -F': ' '$2 !~ /^(lo|veth|dummy)/ {print $2}')
|
||||
}
|
||||
|
||||
check_and_fix_bridges() {
|
||||
local output=""
|
||||
output+="$(translate 'Checking bridges')\n\n"
|
||||
|
||||
bridges=$(grep "^auto vmbr" /etc/network/interfaces | awk '{print $2}')
|
||||
|
||||
for bridge in $bridges; do
|
||||
old_port=$(grep -A1 "iface $bridge" /etc/network/interfaces | grep "bridge-ports" | awk '{print $2}')
|
||||
|
||||
if ! ip link show "$old_port" &>/dev/null; then
|
||||
output+="$(translate 'Bridge port missing'): $bridge - $old_port\n"
|
||||
|
||||
new_port=$(whiptail --title "$(translate 'Missing Port Detected')" --menu "$(translate 'The bridge') $bridge $(translate 'is using a missing port') ($old_port).\n\n$(translate 'Select a replacement interface:')" 20 60 10 $(echo "$physical_interfaces" | tr ' ' '\n' | grep -v "vmbr" | awk '{print $1 " " $1}') 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -n "$new_port" ]; then
|
||||
sed -i "/iface $bridge/,/bridge-ports/ s/bridge-ports.*/bridge-ports $new_port/" /etc/network/interfaces
|
||||
output+="$(translate 'Bridge port updated'): $bridge - $old_port -> $new_port\n"
|
||||
else
|
||||
output+="$(translate 'No replacement selected. Skipping update for') $bridge\n"
|
||||
fi
|
||||
else
|
||||
output+="$(translate 'Bridge port OK'): $bridge - $old_port\n"
|
||||
fi
|
||||
done
|
||||
|
||||
whiptail --title "$(translate 'Checking Bridges')" --msgbox "$output" 20 78
|
||||
}
|
||||
|
||||
clean_nonexistent_interfaces() {
|
||||
local output=""
|
||||
output+="$(translate 'Cleaning interfaces')\n\n"
|
||||
configured_interfaces=$(grep "^iface" /etc/network/interfaces | awk '{print $2}' | grep -v "lo")
|
||||
for iface in $configured_interfaces; do
|
||||
if [[ ! $iface =~ ^(vmbr|bond) ]] && ! ip link show "$iface" &>/dev/null; then
|
||||
sed -i "/iface $iface/,/^$/d" /etc/network/interfaces
|
||||
output+="$(translate 'Interface removed'): $iface\n"
|
||||
fi
|
||||
done
|
||||
whiptail --title "$(translate 'Cleaning Interfaces')" --msgbox "$output" 15 78
|
||||
}
|
||||
|
||||
configure_physical_interfaces() {
|
||||
local output=""
|
||||
output+="$(translate 'Configuring interfaces')\n\n"
|
||||
for iface in $physical_interfaces; do
|
||||
if ! grep -q "iface $iface" /etc/network/interfaces; then
|
||||
echo -e "\niface $iface inet manual" >> /etc/network/interfaces
|
||||
output+="$(translate 'Interface added'): $iface\n"
|
||||
fi
|
||||
done
|
||||
whiptail --title "$(translate 'Configuring Interfaces')" --msgbox "$output" 15 78
|
||||
}
|
||||
|
||||
restart_networking() {
|
||||
if (whiptail --title "$(translate 'Restarting Network')" --yesno "$(translate 'Do you want to restart the network service?')" 10 60); then
|
||||
clear
|
||||
msg_info "$(translate 'The network service is about to restart. You may experience a brief disconnection.')"
|
||||
systemctl restart networking
|
||||
if [ $? -eq 0 ]; then
|
||||
msg_ok "$(translate 'Network service restarted successfully')"
|
||||
else
|
||||
msg_error "$(translate 'Failed to restart network service')"
|
||||
fi
|
||||
else
|
||||
msg_ok "$(translate 'Network restart canceled')"
|
||||
fi
|
||||
}
|
||||
|
||||
check_network_connectivity() {
|
||||
if ping -c 4 8.8.8.8 &> /dev/null; then
|
||||
msg_ok "$(translate 'Network connectivity OK')"
|
||||
return 0
|
||||
else
|
||||
msg_error "$(translate 'Network connectivity failed')"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_ip_info() {
|
||||
whiptail --title "$(translate 'IP Information')" --infobox "$(translate 'Gathering IP information...')" 8 78
|
||||
local ip_info=""
|
||||
ip_info+="$(translate 'IP Information')\n\n"
|
||||
|
||||
local interfaces=$(get_relevant_interfaces)
|
||||
|
||||
for interface in $interfaces; do
|
||||
local interface_ip=$(ip -4 addr show $interface 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
|
||||
if [ -n "$interface_ip" ]; then
|
||||
ip_info+="$interface: $interface_ip\n"
|
||||
else
|
||||
ip_info+="$interface: $(translate 'No IP assigned')\n"
|
||||
fi
|
||||
done
|
||||
|
||||
whiptail --title "$(translate 'Result')" --msgbox "${ip_info}\n\n$(translate 'IP information gathering completed')\n\n$(translate 'Press Enter to continue')" 20 78
|
||||
}
|
||||
|
||||
repair_network() {
|
||||
if check_network_connectivity; then
|
||||
msg_ok "$(translate 'Network is already working. No repair needed.')"
|
||||
whiptail --title "$(translate 'Network Status')" --msgbox "$(translate 'Network is already connected. No action will be taken.')" 10 78
|
||||
return
|
||||
fi
|
||||
|
||||
whiptail --title "$(translate 'Network Repair Started')" --infobox "$(translate 'Repairing network...')" 8 78
|
||||
echo -ne "${TAB}${YW}-$(translate 'Repairing network...') ${CL}"
|
||||
sleep 3
|
||||
detect_physical_interfaces
|
||||
clean_nonexistent_interfaces
|
||||
check_and_fix_bridges
|
||||
configure_physical_interfaces
|
||||
restart_networking
|
||||
|
||||
if check_network_connectivity; then
|
||||
show_ip_info
|
||||
msg_ok "$(translate 'Network repair completed successfully')"
|
||||
else
|
||||
msg_error "$(translate 'Network repair failed')"
|
||||
fi
|
||||
|
||||
whiptail --title "$(translate 'Result')" --msgbox "$(translate 'Repair process completed')\n\n$(translate 'Press Enter to continue')" 10 78
|
||||
}
|
||||
|
||||
verify_network() {
|
||||
whiptail --title "$(translate 'Network Verification Started')" --infobox "$(translate 'Verifying network...')" 8 78
|
||||
echo -ne "${TAB}${YW}-$(translate 'Verifying network...') ${CL}"
|
||||
detect_physical_interfaces
|
||||
show_ip_info
|
||||
if check_network_connectivity; then
|
||||
msg_ok "$(translate 'Network verification completed successfully')"
|
||||
else
|
||||
msg_error "$(translate 'Network verification failed')"
|
||||
fi
|
||||
whiptail --title "$(translate 'Result')" --msgbox "$(translate 'Verification process completed')\n\n$(translate 'Press Enter to continue')" 10 78
|
||||
}
|
||||
|
||||
show_main_menu() {
|
||||
while true; do
|
||||
OPTION=$(whiptail --title "$(translate 'Network Repair Menu')" --menu "$(translate 'Choose an option:')" 15 60 4 \
|
||||
"1" "$(translate 'Repair Network')" \
|
||||
"2" "$(translate 'Verify Network')" \
|
||||
"3" "$(translate 'Show IP Information')" \
|
||||
"4" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3)
|
||||
|
||||
case $OPTION in
|
||||
1) repair_network ;;
|
||||
2) verify_network ;;
|
||||
3) show_ip_info ;;
|
||||
4) exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh" ;;
|
||||
*) exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
show_main_menu
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================
|
||||
# ProxMenux - Menú de Restauración de Backups
|
||||
# ==============================================
|
||||
|
||||
BACKUP_DIR="/root/backups"
|
||||
PBS_REPO="root@pbs@192.168.100.10:host-backups"
|
||||
HOSTNAME=$(hostname)
|
||||
|
||||
main_menu() {
|
||||
OPTION=$(whiptail --title "Restaurar Backup del Host" --menu "¿Desde dónde quieres restaurar?" 15 60 5 \\
|
||||
"1" "Restaurar desde archivo local (.tar.gz)" \\
|
||||
"2" "Restaurar desde PBS (.pxar)" \\
|
||||
"3" "Salir" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$OPTION" in
|
||||
"1") restore_from_local ;;
|
||||
"2") restore_from_pbs ;;
|
||||
"3") clear; exit 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
restore_from_local() {
|
||||
mapfile -t TAR_FILES < <(find "$BACKUP_DIR" -name "*.tar.gz" 2>/dev/null)
|
||||
|
||||
if [ ${#TAR_FILES[@]} -eq 0 ]; then
|
||||
whiptail --msgbox "No se encontraron archivos .tar.gz en $BACKUP_DIR" 10 60
|
||||
return
|
||||
fi
|
||||
|
||||
MENU_ITEMS=()
|
||||
for f in "${TAR_FILES[@]}"; do
|
||||
MENU_ITEMS+=("$f" "")
|
||||
done
|
||||
|
||||
SELECTED_TAR=$(whiptail --title "Selecciona backup local" --menu "Elige el archivo a restaurar:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
mapfile -t FILE_CONTENT < <(tar -tzf "$SELECTED_TAR")
|
||||
MENU_CONTENT=()
|
||||
for item in "${FILE_CONTENT[@]}"; do
|
||||
MENU_CONTENT+=("$item" "OFF")
|
||||
done
|
||||
|
||||
SELECTED_DIRS=$(whiptail --title "Contenido del backup" --checklist "Selecciona qué restaurar (Espacio = seleccionar):" 20 80 15 \\
|
||||
"ALL" "Restaurar todo el contenido" OFF \\
|
||||
"${MENU_CONTENT[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
if echo "$SELECTED_DIRS" | grep -q "ALL"; then
|
||||
tar -xzf "$SELECTED_TAR" -C /
|
||||
whiptail --msgbox "Restauración completa realizada con éxito." 10 60
|
||||
else
|
||||
for item in $SELECTED_DIRS; do
|
||||
item_cleaned=$(echo "$item" | tr -d '"')
|
||||
tar -xzf "$SELECTED_TAR" -C / "$item_cleaned"
|
||||
done
|
||||
whiptail --msgbox "Restauración parcial realizada con éxito." 10 60
|
||||
fi
|
||||
}
|
||||
|
||||
restore_from_pbs() {
|
||||
mapfile -t BACKUPS < <(proxmox-backup-client list --repository "$PBS_REPO" | grep "$HOSTNAME" | awk '{print $3}')
|
||||
|
||||
if [ ${#BACKUPS[@]} -eq 0 ]; then
|
||||
whiptail --msgbox "No se encontraron backups de $HOSTNAME en PBS." 10 60
|
||||
return
|
||||
fi
|
||||
|
||||
MENU_ITEMS=()
|
||||
for backup in "${BACKUPS[@]}"; do
|
||||
MENU_ITEMS+=("$backup" "")
|
||||
done
|
||||
|
||||
SELECTED_BACKUP=$(whiptail --title "Seleccionar backup en PBS" --menu "Elige un snapshot para restaurar:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
mapfile -t FILES < <(proxmox-backup-client catalog --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP" | awk '{print $1}' | grep ".pxar")
|
||||
|
||||
if [ ${#FILES[@]} -eq 0 ]; then
|
||||
whiptail --msgbox "No se encontraron archivos .pxar en ese snapshot." 10 60
|
||||
return
|
||||
fi
|
||||
|
||||
FILE_OPTIONS=("ALL" "Restaurar todos los archivos" OFF)
|
||||
for file in "${FILES[@]}"; do
|
||||
FILE_OPTIONS+=("$file" "OFF")
|
||||
done
|
||||
|
||||
SELECTED_FILES=$(whiptail --title "Contenido del snapshot PBS" --checklist "Selecciona qué restaurar:" 20 80 15 "${FILE_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
RESTORE_DIR="/tmp/pbs-restore-${SELECTED_BACKUP}"
|
||||
mkdir -p "$RESTORE_DIR"
|
||||
|
||||
if echo "$SELECTED_FILES" | grep -q "ALL"; then
|
||||
for file in "${FILES[@]}"; do
|
||||
proxmox-backup-client restore "$file" "$RESTORE_DIR/$(basename "$file" .pxar)" --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP"
|
||||
done
|
||||
whiptail --msgbox "Restauración completa a $RESTORE_DIR." 10 60
|
||||
else
|
||||
for file in $SELECTED_FILES; do
|
||||
file_cleaned=$(echo "$file" | tr -d '"')
|
||||
proxmox-backup-client restore "$file_cleaned" "$RESTORE_DIR/$(basename "$file_cleaned" .pxar)" --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP"
|
||||
done
|
||||
whiptail --msgbox "Restauración parcial a $RESTORE_DIR." 10 60
|
||||
fi
|
||||
}
|
||||
|
||||
# Lanzar menú principal
|
||||
while true; do main_menu; done
|
||||
""")
|
||||
|
||||
unified_script_path = Path("/mnt/data/restore-unified-menu.sh")
|
||||
unified_script_path.write_text(unified_restore_script)
|
||||
unified_script_path.chmod(0o755)
|
||||
unified_script_path
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ========================================
|
||||
# ProxMenux - Menú completo de restauración
|
||||
# ========================================
|
||||
|
||||
BACKUP_DIR="/root/backups"
|
||||
PBS_REPO="root@pbs@192.168.100.10:host-backups"
|
||||
HOSTNAME=$(hostname)
|
||||
|
||||
main_menu() {
|
||||
OPTION=$(whiptail --title "Restaurar Backup del Host" --menu "Selecciona el origen del backup:" 15 60 4 \\
|
||||
"1" "Restaurar desde archivo local (.tar.gz)" \\
|
||||
"2" "Restaurar desde PBS (.pxar)" \\
|
||||
"3" "Salir" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$OPTION" in
|
||||
"1") local_restore_menu ;;
|
||||
"2") pbs_restore_menu ;;
|
||||
"3") clear; exit 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
local_restore_menu() {
|
||||
OPTION=$(whiptail --title "Restaurar desde archivo local" --menu "Selecciona el tipo de restauración:" 15 60 2 \\
|
||||
"1" "Restauración completa del sistema" \\
|
||||
"2" "Restauración manual (archivos o directorios)" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$OPTION" in
|
||||
"1") restore_local_full ;;
|
||||
"2") restore_local_manual ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pbs_restore_menu() {
|
||||
OPTION=$(whiptail --title "Restaurar desde PBS" --menu "Selecciona el tipo de restauración:" 15 60 2 \\
|
||||
"1" "Restauración completa del sistema" \\
|
||||
"2" "Restauración manual (archivos o directorios)" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$OPTION" in
|
||||
"1") restore_pbs_full ;;
|
||||
"2") restore_pbs_manual ;;
|
||||
esac
|
||||
}
|
||||
|
||||
restore_local_full() {
|
||||
mapfile -t TAR_FILES < <(find "$BACKUP_DIR" -name "*.tar.gz" 2>/dev/null)
|
||||
[[ ${#TAR_FILES[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron backups en $BACKUP_DIR" 10 60 && return
|
||||
|
||||
MENU_ITEMS=()
|
||||
for f in "${TAR_FILES[@]}"; do MENU_ITEMS+=("$f" ""); done
|
||||
|
||||
SELECTED=$(whiptail --title "Seleccionar backup" --menu "Elige el archivo para restaurar completamente:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
CONFIRM=$(whiptail --title "Confirmar restauración" --yesno "¿Deseas sobrescribir el sistema con este backup?" 10 60)
|
||||
[[ $? -ne 0 ]] && return
|
||||
|
||||
tar -xzf "$SELECTED" -C /
|
||||
whiptail --msgbox "Restauración completa realizada con éxito." 10 60
|
||||
}
|
||||
|
||||
restore_local_manual() {
|
||||
mapfile -t TAR_FILES < <(find "$BACKUP_DIR" -name "*.tar.gz" 2>/dev/null)
|
||||
[[ ${#TAR_FILES[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron backups en $BACKUP_DIR" 10 60 && return
|
||||
|
||||
MENU_ITEMS=()
|
||||
for f in "${TAR_FILES[@]}"; do MENU_ITEMS+=("$f" ""); done
|
||||
|
||||
SELECTED=$(whiptail --title "Seleccionar backup" --menu "Elige el archivo a examinar:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
mapfile -t CONTENT < <(tar -tzf "$SELECTED")
|
||||
MENU_CONTENT=()
|
||||
for item in "${CONTENT[@]}"; do MENU_CONTENT+=("$item" "OFF"); done
|
||||
|
||||
SELECTED_ITEMS=$(whiptail --title "Seleccionar contenido" --checklist "Selecciona qué restaurar:" 20 80 15 "${MENU_CONTENT[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
for item in $SELECTED_ITEMS; do
|
||||
CLEAN_ITEM=$(echo "$item" | tr -d '"')
|
||||
tar -xzf "$SELECTED" -C / "$CLEAN_ITEM"
|
||||
done
|
||||
|
||||
whiptail --msgbox "Restauración parcial realizada con éxito." 10 60
|
||||
}
|
||||
|
||||
restore_pbs_full() {
|
||||
mapfile -t BACKUPS < <(proxmox-backup-client list --repository "$PBS_REPO" | grep "$HOSTNAME" | awk '{print $3}')
|
||||
[[ ${#BACKUPS[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron backups en PBS." 10 60 && return
|
||||
|
||||
MENU_ITEMS=()
|
||||
for backup in "${BACKUPS[@]}"; do MENU_ITEMS+=("$backup" ""); done
|
||||
|
||||
SELECTED_BACKUP=$(whiptail --title "Snapshot PBS" --menu "Selecciona el snapshot para restaurar:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
mapfile -t FILES < <(proxmox-backup-client catalog --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP" | awk '{print $1}' | grep ".pxar")
|
||||
[[ ${#FILES[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron archivos .pxar." 10 60 && return
|
||||
|
||||
FILE_OPTIONS=()
|
||||
for file in "${FILES[@]}"; do FILE_OPTIONS+=("$file" "OFF"); done
|
||||
SELECTED_FILE=$(whiptail --title "Archivo .pxar" --radiolist "Selecciona el archivo para restaurar completamente:" 20 80 10 "${FILE_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
FILE_CLEAN=$(echo "$SELECTED_FILE" | tr -d '"')
|
||||
|
||||
CONFIRM=$(whiptail --title "Confirmar restauración" --yesno "¿Deseas sobrescribir el sistema con ${FILE_CLEAN}?" 10 70)
|
||||
[[ $? -ne 0 ]] && return
|
||||
|
||||
proxmox-backup-client restore "$FILE_CLEAN" / --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP"
|
||||
whiptail --msgbox "Restauración completa realizada con éxito." 10 60
|
||||
}
|
||||
|
||||
restore_pbs_manual() {
|
||||
mapfile -t BACKUPS < <(proxmox-backup-client list --repository "$PBS_REPO" | grep "$HOSTNAME" | awk '{print $3}')
|
||||
[[ ${#BACKUPS[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron backups en PBS." 10 60 && return
|
||||
|
||||
MENU_ITEMS=()
|
||||
for backup in "${BACKUPS[@]}"; do MENU_ITEMS+=("$backup" ""); done
|
||||
|
||||
SELECTED_BACKUP=$(whiptail --title "Snapshot PBS" --menu "Selecciona el snapshot para explorar:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
mapfile -t FILES < <(proxmox-backup-client catalog --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP" | awk '{print $1}' | grep ".pxar")
|
||||
[[ ${#FILES[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron archivos .pxar." 10 60 && return
|
||||
|
||||
FILE_OPTIONS=()
|
||||
for file in "${FILES[@]}"; do FILE_OPTIONS+=("$file" "OFF"); done
|
||||
SELECTED_FILE=$(whiptail --title "Archivo .pxar" --radiolist "Selecciona el archivo para restaurar parcialmente:" 20 80 10 "${FILE_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
FILE_CLEAN=$(echo "$SELECTED_FILE" | tr -d '"')
|
||||
|
||||
TMP_DIR="/tmp/restore-${RANDOM}"
|
||||
mkdir -p "$TMP_DIR"
|
||||
proxmox-backup-client restore "$FILE_CLEAN" "$TMP_DIR" --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP"
|
||||
|
||||
mapfile -t CONTENT < <(cd "$TMP_DIR" && find . -type f -o -type d)
|
||||
RESTORE_ITEMS=()
|
||||
for entry in "${CONTENT[@]}"; do RESTORE_ITEMS+=("$entry" "OFF"); done
|
||||
|
||||
SELECTED_ITEMS=$(whiptail --title "Contenido del backup" --checklist "Selecciona qué restaurar en el sistema:" 20 80 15 "${RESTORE_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
for item in $SELECTED_ITEMS; do
|
||||
CLEAN_ITEM=$(echo "$item" | tr -d '"')
|
||||
cp -r "$TMP_DIR/$CLEAN_ITEM" "/$CLEAN_ITEM"
|
||||
done
|
||||
|
||||
rm -rf "$TMP_DIR"
|
||||
whiptail --msgbox "Restauración parcial completada y archivos temporales eliminados." 10 60
|
||||
}
|
||||
|
||||
while true; do main_menu; done
|
||||
""")
|
||||
|
||||
script_path = Path("/mnt/data/proxmox-restore-menu.sh")
|
||||
script_path.write_text(full_menu_script)
|
||||
script_path.chmod(0o755)
|
||||
script_path
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ================================
|
||||
# ProxMenux - Restauración completa desde PBS con autoreparación
|
||||
# ================================
|
||||
|
||||
PBS_REPO="root@pbs@192.168.100.10:host-backups"
|
||||
HOSTNAME=$(hostname)
|
||||
|
||||
restore_pbs_full() {
|
||||
mapfile -t BACKUPS < <(proxmox-backup-client list --repository "$PBS_REPO" | grep "$HOSTNAME" | awk '{print $3}')
|
||||
[[ ${#BACKUPS[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron backups en PBS." 10 60 && return
|
||||
|
||||
MENU_ITEMS=()
|
||||
for backup in "${BACKUPS[@]}"; do MENU_ITEMS+=("$backup" ""); done
|
||||
|
||||
SELECTED_BACKUP=$(whiptail --title "Snapshot PBS" --menu "Selecciona el snapshot para restaurar:" 20 70 10 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
mapfile -t FILES < <(proxmox-backup-client catalog --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP" | awk '{print $1}' | grep ".pxar")
|
||||
[[ ${#FILES[@]} -eq 0 ]] && whiptail --msgbox "No se encontraron archivos .pxar." 10 60 && return
|
||||
|
||||
FILE_OPTIONS=()
|
||||
for file in "${FILES[@]}"; do FILE_OPTIONS+=("$file" "OFF"); done
|
||||
SELECTED_FILE=$(whiptail --title "Archivo .pxar" --radiolist "Selecciona el archivo para restaurar completamente:" 20 80 10 "${FILE_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
FILE_CLEAN=$(echo "$SELECTED_FILE" | tr -d '"')
|
||||
|
||||
CONFIRM=$(whiptail --title "Confirmar restauración" --yesno "¿Deseas sobrescribir el sistema con ${FILE_CLEAN}?\nEsto restaurará todos los archivos y puede requerir reinstalar GRUB y el kernel." 12 70)
|
||||
[[ $? -ne 0 ]] && return
|
||||
|
||||
# Restauración principal
|
||||
proxmox-backup-client restore "$FILE_CLEAN" / --repository "$PBS_REPO" --backup-id "$HOSTNAME" --backup-time "$SELECTED_BACKUP"
|
||||
RESTORE_STATUS=$?
|
||||
|
||||
if [ $RESTORE_STATUS -eq 0 ]; then
|
||||
whiptail --msgbox "Restauración completa realizada con éxito. Ahora se ejecutarán pasos de autoreparación (GRUB, kernel, DKMS)..." 10 70
|
||||
|
||||
# Reparación post-restauración
|
||||
{
|
||||
echo "[INFO] Reinstalando GRUB en /dev/sda..."
|
||||
grub-install /dev/sda && update-grub
|
||||
|
||||
echo "[INFO] Reinstalando kernel actual..."
|
||||
apt install --reinstall -y pve-kernel-$(uname -r)
|
||||
|
||||
echo "[INFO] Reconstruyendo módulos DKMS..."
|
||||
dkms autoinstall || true
|
||||
} >> /var/log/proxmox-restore.log 2>&1
|
||||
|
||||
whiptail --yesno "Restauración y autoreparación completadas.\n¿Deseas reiniciar ahora el sistema?" 10 60 && reboot
|
||||
else
|
||||
whiptail --msgbox "Error durante la restauración. Verifica los logs para más detalles." 10 60
|
||||
fi
|
||||
}
|
||||
|
||||
restore_pbs_full
|
||||
"""
|
||||
|
||||
pbs_autorepair_path.write_text(pbs_restore_autorepair_script)
|
||||
pbs_autorepair_path.chmod(0o755)
|
||||
pbs_autorepair_path
|
||||
@@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =======================================
|
||||
# ProxMenux - Backup Menu for Proxmox VE
|
||||
# =======================================
|
||||
|
||||
# CONFIGURACIÓN DINÁMICA
|
||||
# Solicitar datos de conexión a PBS por separado y construir el repositorio final
|
||||
PBS_REPO_FILE="/etc/proxmenux/pbs-repo.conf"
|
||||
|
||||
if [[ -f "$PBS_REPO_FILE" ]]; then
|
||||
PBS_REPO=$(tr -d '
|
||||
[:space:]' < "$PBS_REPO_FILE")
|
||||
else
|
||||
PBS_USER=$(whiptail --inputbox "Introduce el nombre de usuario para el PBS:" 10 60 "root" 3>&1 1>&2 2>&3) || exit
|
||||
PBS_HOST=$(whiptail --inputbox "Introduce la IP o nombre del host del PBS:" 10 60 "192.168.0.42" 3>&1 1>&2 2>&3) || exit
|
||||
PBS_DATASTORE=$(whiptail --inputbox "Introduce el nombre del datastore PBS:" 10 60 "t6pbs" 3>&1 1>&2 2>&3) || exit
|
||||
|
||||
PBS_REPO="${PBS_USER}@pam@${PBS_HOST}:${PBS_DATASTORE}"
|
||||
mkdir -p "$(dirname "$PBS_REPO_FILE")"
|
||||
echo "$PBS_REPO" > "$PBS_REPO_FILE"
|
||||
fi
|
||||
|
||||
HOSTNAME=$(hostname)
|
||||
TIMESTAMP=$(date +%Y-%m-%d_%H-%M)
|
||||
SNAPSHOT="${HOSTNAME}-${TIMESTAMP}"
|
||||
BACKUP_DIR="/var/backups/proxmox-host/tar"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
main_menu() {
|
||||
OPTION=$(whiptail --title "Proxmox Host Backup" --menu "Elige una opción de respaldo:" 20 78 10 \
|
||||
"1" "Backup rápido personalizado (tar.gz, local)" \
|
||||
"2" "Backup rápido personalizado (PBS)" \
|
||||
"3" "Backup completo del sistema (tar.gz, local)" \
|
||||
"4" "Backup completo del sistema (PBS)" \
|
||||
"5" "Backup mínimo automático (tar.gz, local)" \
|
||||
"6" "Salir" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$OPTION" in
|
||||
"1") backup_local_tar_checklist ;;
|
||||
"2") backup_modular_pbs_checklist ;;
|
||||
"3") backup_full_local_root ;;
|
||||
"4") backup_full_pbs_root ;;
|
||||
"5") backup_min_local_tar ;;
|
||||
"6") clear; exit 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
backup_local_tar_checklist() {
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
MENU_OPTIONS=("ALL" "Respaldar todos los directorios sugeridos" OFF)
|
||||
for name in "${!BACKUP_PATHS[@]}"; do
|
||||
MENU_OPTIONS+=("$name" "${BACKUP_PATHS[$name]}" OFF)
|
||||
done
|
||||
|
||||
CHOICES=$(whiptail --checklist "Selecciona los directorios a respaldar:" 20 78 12 "${MENU_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
SELECTED=()
|
||||
if echo "$CHOICES" | grep -q "ALL"; then
|
||||
for path in "${BACKUP_PATHS[@]}"; do SELECTED+=("$path"); done
|
||||
else
|
||||
for choice in $CHOICES; do
|
||||
key=$(echo "$choice" | tr -d '"')
|
||||
SELECTED+=("${BACKUP_PATHS[$key]}")
|
||||
done
|
||||
fi
|
||||
|
||||
BACKUP_FILE="${BACKUP_DIR}/${HOSTNAME}-local-backup-${TIMESTAMP}.tar.gz"
|
||||
tar --exclude="$BACKUP_FILE" -czf "$BACKUP_FILE" --absolute-names "${SELECTED[@]}" && \
|
||||
echo -e "\nBackup guardado en: $BACKUP_FILE" || \
|
||||
echo -e "\nError al crear el backup local."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_modular_pbs_checklist() {
|
||||
MENU_OPTIONS=("ALL" "Respaldar todos los directorios sugeridos" OFF)
|
||||
for name in "${!BACKUP_PATHS[@]}"; do
|
||||
MENU_OPTIONS+=("$name" "${BACKUP_PATHS[$name]}" OFF)
|
||||
done
|
||||
|
||||
CHOICES=$(whiptail --checklist "Selecciona qué enviar al PBS:" 20 78 12 "${MENU_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
SELECTED=()
|
||||
if echo "$CHOICES" | grep -q "ALL"; then
|
||||
for name in "${!BACKUP_PATHS[@]}"; do
|
||||
safe_name=$(echo "$name" | tr '.-' '_')
|
||||
SELECTED+=("${safe_name}.pxar:${BACKUP_PATHS[$name]}")
|
||||
done
|
||||
else
|
||||
for choice in $CHOICES; do
|
||||
key=$(echo "$choice" | tr -d '"')
|
||||
safe_key=$(echo "$key" | tr '.-' '_')
|
||||
SELECTED+=("${safe_key}.pxar:${BACKUP_PATHS[$key]}")
|
||||
done
|
||||
fi
|
||||
|
||||
for entry in "${SELECTED[@]}"; do
|
||||
if [[ "$entry" =~ ^[a-zA-Z0-9_-]+\.pxar:/.* ]]; then
|
||||
echo ">> Enviando: $entry"
|
||||
echo ">> REPO: '$PBS_REPO'"
|
||||
proxmox-backup-client backup "$entry" \
|
||||
--repository "$PBS_REPO" \
|
||||
--backup-type host \
|
||||
--backup-id "${HOSTNAME}-$(echo "$entry" | cut -d'.' -f1)" \
|
||||
--backup-time "$(date +%s)" \
|
||||
--incremental true
|
||||
else
|
||||
echo ">> Saltado (mal formado): $entry"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "\nBackup modular al PBS finalizado."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_full_local_root() {
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_FILE="${BACKUP_DIR}/${HOSTNAME}-full-backup-${TIMESTAMP}.tar.gz"
|
||||
echo "Creando backup completo local (excluyendo /proc, /sys, /dev, /run, /mnt, /tmp)..."
|
||||
tar --exclude="$BACKUP_DIR" --exclude=/proc --exclude=/sys --exclude=/dev --exclude=/run --exclude=/mnt --exclude=/tmp \
|
||||
-czf "$BACKUP_FILE" / && \
|
||||
echo -e "\nBackup completo guardado en: $BACKUP_FILE" || \
|
||||
echo -e "\nError durante el backup completo."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_full_pbs_root() {
|
||||
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)" && \
|
||||
echo -e "
|
||||
Backup completo al PBS finalizado correctamente." || \
|
||||
echo -e "
|
||||
Error durante el backup completo."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
echo -e "\nBackup completo al PBS finalizado correctamente." || \
|
||||
echo -e "\nError durante el backup completo."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_min_local_tar() {
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_FILE="${BACKUP_DIR}/${HOSTNAME}-minimal-${TIMESTAMP}.tar.gz"
|
||||
tar --exclude="$BACKUP_FILE" -czf "$BACKUP_FILE" --absolute-names /etc/pve /etc/network /var/lib/pve-cluster /root && \
|
||||
echo -e "\nBackup mínimo guardado en: $BACKUP_FILE" || \
|
||||
echo -e "\nError durante el backup mínimo."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
main_menu
|
||||
@@ -1,134 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# =======================================
|
||||
# ProxMenux - Backup Menu for Proxmox VE
|
||||
# =======================================
|
||||
|
||||
# CONFIGURACIÓN
|
||||
PBS_REPO="root@pbs@192.168.100.10:host-backups" # Cambiar IP/datastore si es necesario
|
||||
HOSTNAME=$(hostname)
|
||||
TIMESTAMP=$(date +%Y-%m-%d_%H-%M)
|
||||
SNAPSHOT="${HOSTNAME}-${TIMESTAMP}"
|
||||
|
||||
# LISTA DE DIRECTORIOS RECOMENDADOS
|
||||
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"
|
||||
)
|
||||
|
||||
main_menu() {
|
||||
OPTION=$(whiptail --title "Proxmox Host Backup" --menu "Elige una opción de respaldo:" 20 78 10 \
|
||||
"1" "Backup rápido personalizado (tar.gz, local)" \
|
||||
"2" "Backup completo del sistema (PBS, backup-client)" \
|
||||
"3" "Backup modular al PBS (checklist)" \
|
||||
"4" "Backup mínimo automático (tar.gz, local)" \
|
||||
"5" "Salir" 3>&1 1>&2 2>&3)
|
||||
|
||||
case "$OPTION" in
|
||||
"1") backup_local_tar_checklist ;;
|
||||
"2") backup_full_pbs_root ;;
|
||||
"3") backup_modular_pbs_checklist ;;
|
||||
"4") backup_min_local_tar ;;
|
||||
"5") clear; exit 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
backup_local_tar_checklist() {
|
||||
BACKUP_DIR=$(whiptail --inputbox "¿Dónde guardar el backup local? (por defecto /root/backups)" 10 60 3>&1 1>&2 2>&3)
|
||||
BACKUP_DIR="${BACKUP_DIR:-/root/backups}"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
MENU_OPTIONS=("ALL" "Respaldar todos los directorios sugeridos" OFF)
|
||||
for name in "${!BACKUP_PATHS[@]}"; do
|
||||
MENU_OPTIONS+=("$name" "${BACKUP_PATHS[$name]}" OFF)
|
||||
done
|
||||
|
||||
CHOICES=$(whiptail --checklist "Selecciona los directorios a respaldar:" 20 78 12 "${MENU_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
SELECTED=()
|
||||
if echo "$CHOICES" | grep -q "ALL"; then
|
||||
for path in "${BACKUP_PATHS[@]}"; do SELECTED+=("$path"); done
|
||||
else
|
||||
for choice in $CHOICES; do
|
||||
key=$(echo "$choice" | tr -d '"')
|
||||
SELECTED+=("${BACKUP_PATHS[$key]}")
|
||||
done
|
||||
fi
|
||||
|
||||
BACKUP_FILE="${BACKUP_DIR}/${HOSTNAME}-local-backup-${TIMESTAMP}.tar.gz"
|
||||
tar -czf "$BACKUP_FILE" --absolute-names "${SELECTED[@]}" && \
|
||||
echo -e "\\nBackup guardado en: $BACKUP_FILE" || \
|
||||
echo -e "\\nError al crear el backup local."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_full_pbs_root() {
|
||||
proxmox-backup-client backup \\
|
||||
--include-dev /boot/efi \\
|
||||
--include-dev /etc/pve \\
|
||||
root-${HOSTNAME}.pxar:/ \\
|
||||
--repository "$PBS_REPO" && \
|
||||
echo -e "\\nBackup completo al PBS finalizado correctamente." || \
|
||||
echo -e "\\nError durante el backup completo."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_modular_pbs_checklist() {
|
||||
MENU_OPTIONS=("ALL" "Respaldar todos los directorios sugeridos" OFF)
|
||||
for name in "${!BACKUP_PATHS[@]}"; do
|
||||
MENU_OPTIONS+=("$name" "${BACKUP_PATHS[$name]}" OFF)
|
||||
done
|
||||
|
||||
CHOICES=$(whiptail --checklist "Selecciona qué enviar al PBS:" 20 78 12 "${MENU_OPTIONS[@]}" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
SELECTED=()
|
||||
if echo "$CHOICES" | grep -q "ALL"; then
|
||||
for name in "${!BACKUP_PATHS[@]}"; do
|
||||
SELECTED+=("${name}.pxar:${BACKUP_PATHS[$name]}")
|
||||
done
|
||||
else
|
||||
for choice in $CHOICES; do
|
||||
key=$(echo "$choice" | tr -d '"')
|
||||
SELECTED+=("${key}.pxar:${BACKUP_PATHS[$key]}")
|
||||
done
|
||||
fi
|
||||
|
||||
for entry in "${SELECTED[@]}"; do
|
||||
proxmox-backup-client backup "$entry" \\
|
||||
--repository "$PBS_REPO" \\
|
||||
--backup-type host \\
|
||||
--backup-id "$HOSTNAME" \\
|
||||
--backup-time "$TIMESTAMP"
|
||||
done
|
||||
echo -e "\\nBackup modular al PBS finalizado."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
backup_min_local_tar() {
|
||||
BACKUP_DIR="/root/backups"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_FILE="${BACKUP_DIR}/${HOSTNAME}-minimal-${TIMESTAMP}.tar.gz"
|
||||
tar -czf "$BACKUP_FILE" --absolute-names \
|
||||
/etc/pve /etc/network /var/lib/pve-cluster /root && \
|
||||
echo -e "\\nBackup mínimo guardado en: $BACKUP_FILE" || \
|
||||
echo -e "\\nError durante el backup mínimo."
|
||||
read -p "Pulsa ENTER para continuar..."
|
||||
}
|
||||
|
||||
# Lanzar menú principal
|
||||
while true; do main_menu; done
|
||||
""")
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
backup_script_path = Path("/mnt/data/proxmox_host_backup_menu.sh")
|
||||
backup_script_path.write_text(menu_script)
|
||||
backup_script_path.chmod(0o755)
|
||||
backup_script_path
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ================================================
|
||||
# ProxMenux - Create VM Entry Point
|
||||
# ================================================
|
||||
# Author : MacRimi
|
||||
# ================================================
|
||||
|
||||
BASE_DIR="/usr/local/share/proxmenux"
|
||||
UTILS_FILE="$BASE_DIR/utils.sh"
|
||||
VENV_PATH="/opt/googletrans-env"
|
||||
VM_CONFIG="./vm_configurator.sh"
|
||||
DISK_SELECTOR="./disk_selector.sh"
|
||||
VM_CREATOR="./vm_creator.sh"
|
||||
#LINUX_ISO="./select_linux_iso.sh"
|
||||
GUEST_AGENT="./guest_agent_config.sh"
|
||||
|
||||
|
||||
if [[ -f "$UTILS_FILE" ]]; then
|
||||
source "$UTILS_FILE"
|
||||
fi
|
||||
|
||||
load_language
|
||||
initialize_cache
|
||||
|
||||
# Load modules
|
||||
[[ -f "$UTILS_FILE" ]] && source "$UTILS_FILE"
|
||||
[[ -f "$VM_CONFIG" ]] && source "$VM_CONFIG"
|
||||
[[ -f "$DISK_SELECTOR" ]] && source "$DISK_SELECTOR"
|
||||
[[ -f "$VM_CREATOR" ]] && source "$VM_CREATOR"
|
||||
[[ -f "$LINUX_ISO" ]] && source "$LINUX_ISO"
|
||||
[[ -f "$GUEST_AGENT" ]] && source "$GUEST_AGENT"
|
||||
|
||||
|
||||
|
||||
|
||||
function header_info() {
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}║${YWB} ProxMenux VM Creator ${BL}║${CL}"
|
||||
echo -e "${BL}║ ║${CL}"
|
||||
echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}"
|
||||
echo -e
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# MAIN EXECUTION
|
||||
# ==========================================================
|
||||
|
||||
header_info
|
||||
echo -e "\n Loading..."
|
||||
sleep 1
|
||||
|
||||
# Step 1 - Select OS Type
|
||||
OS_TYPE=$(whiptail --title "ProxMenux" --menu "$(translate "Select the type of system to install")" 15 60 4 \
|
||||
"nas" "$(translate "Create VM System NAS")" \
|
||||
"windows" "$(translate "Create VM System Windows")" \
|
||||
"linux" "$(translate "Create VM System Linux")" \
|
||||
"lite" "$(translate "Create VM System Others (based Linux)")" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ -z "$OS_TYPE" ]] && clear && exit
|
||||
|
||||
|
||||
if [[ "$OS_TYPE" == "nas" ]]; then
|
||||
header_info
|
||||
source ./select_nas_iso.sh
|
||||
select_nas_iso || exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Si es Windows, invocar selección de ISO
|
||||
if [[ "$OS_TYPE" == "windows" ]]; then
|
||||
header_info
|
||||
source ./select_windows_iso.sh
|
||||
select_windows_iso || exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Si es Linux, invocar selección de ISO
|
||||
if [[ "$OS_TYPE" == "linux" ]]; then
|
||||
header_info
|
||||
source ./select_linux_iso.sh
|
||||
select_linux_iso || exit 1
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Step 2 - Default or Advanced config
|
||||
if (whiptail --title "ProxMenux" --yesno "$(translate "Use Default Settings?")" --no-button "$(translate "Advanced")" 10 60); then
|
||||
header_info
|
||||
load_default_vm_config "$OS_TYPE"
|
||||
apply_default_vm_config
|
||||
else
|
||||
header_info
|
||||
echo -e "${CUS}$(translate "Using advanced configuration")${CL}"
|
||||
configure_vm_advanced "$OS_TYPE"
|
||||
fi
|
||||
|
||||
# Step 3 - Disk selection
|
||||
select_disk_type
|
||||
if [[ -z "$DISK_TYPE" ]]; then
|
||||
msg_error "$(translate "Disk type selection failed or cancelled")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 4 - Create VM
|
||||
create_vm
|
||||
|
||||
# Step 5 - Guest Agent integration
|
||||
configure_guest_agent
|
||||
@@ -1,280 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# Disk Selector Module - ProxMenux
|
||||
# ==========================================================
|
||||
# Reutiliza la lógica original de selección de discos
|
||||
# virtuales y físicos con integración de traducciones
|
||||
# ==========================================================
|
||||
|
||||
|
||||
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
|
||||
|
||||
function select_disk_type() {
|
||||
DISK_TYPE=$(whiptail --backtitle "ProxMenux" --title "DISK TYPE" --menu "$(translate "Choose disk type:")" 12 58 2 \
|
||||
"virtual" "$(translate "Create virtual disk")" \
|
||||
"passthrough" "$(translate "Use physical disk passthrough")" \
|
||||
--ok-button "Select" --cancel-button "Cancel" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ -z "$DISK_TYPE" ]] && return 1
|
||||
|
||||
if [[ "$DISK_TYPE" == "virtual" ]]; then
|
||||
select_virtual_disk
|
||||
else
|
||||
select_passthrough_disk
|
||||
fi
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
# Select Virtual Disks
|
||||
# ==========================================================
|
||||
function select_virtual_disk() {
|
||||
|
||||
VIRTUAL_DISKS=()
|
||||
|
||||
# Loop to add multiple disks
|
||||
local add_more_disks=true
|
||||
while $add_more_disks; do
|
||||
|
||||
msg_info "Detecting available storage volumes..."
|
||||
|
||||
# Get list of available storage
|
||||
STORAGE_MENU=()
|
||||
while read -r line; do
|
||||
TAG=$(echo $line | awk '{print $1}')
|
||||
TYPE=$(echo $line | awk '{print $2}')
|
||||
FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf( "%9sB", $6)}')
|
||||
ITEM=$(printf "%-15s %-10s %-15s" "$TAG" "$TYPE" "$FREE")
|
||||
STORAGE_MENU+=("$TAG" "$ITEM" "OFF")
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
# Check that storage is available
|
||||
VALID=$(pvesm status -content images | awk 'NR>1')
|
||||
if [ -z "$VALID" ]; then
|
||||
msg_error "Unable to detect a valid storage location."
|
||||
sleep 2
|
||||
select_disk_type
|
||||
fi
|
||||
|
||||
|
||||
# Select storage
|
||||
if [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
|
||||
STORAGE=${STORAGE_MENU[0]}
|
||||
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
|
||||
else
|
||||
|
||||
kill $SPINNER_PID > /dev/null
|
||||
STORAGE=$(whiptail --backtitle "ProxMenuX" --title "$(translate "Select Storage Volume")" --radiolist \
|
||||
"$(translate "Choose the storage volume for the virtual disk:\n")" 20 78 10 \
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$STORAGE" ]; then
|
||||
if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then
|
||||
msg_error "No storage selected. At least one disk is required."
|
||||
select_disk_type
|
||||
else
|
||||
add_more_disks=false
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
fi
|
||||
|
||||
# Request disk size
|
||||
DISK_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "System Disk Size (GB)")" 8 58 32 --title "VIRTUAL DISK" --cancel-button Cancel 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then
|
||||
msg_error "Disk size not specified. At least one disk is required."
|
||||
sleep 2
|
||||
select_disk_type
|
||||
|
||||
else
|
||||
add_more_disks=false
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$DISK_SIZE" ]; then
|
||||
DISK_SIZE="32"
|
||||
fi
|
||||
|
||||
# Store the configuration in the disk list
|
||||
VIRTUAL_DISKS+=("${STORAGE}:${DISK_SIZE}")
|
||||
|
||||
|
||||
# Ask if you want to create another disk
|
||||
if ! whiptail --backtitle "ProxMenuX" --title "$(translate "Add Another Disk")" \
|
||||
--yesno "$(translate "Do you want to add another virtual disk?")" 8 58; then
|
||||
add_more_disks=false
|
||||
fi
|
||||
done
|
||||
|
||||
# Show summary of the created disks
|
||||
if [ ${#VIRTUAL_DISKS[@]} -gt 0 ]; then
|
||||
|
||||
msg_ok "Virtual Disks Created:"
|
||||
for i in "${!VIRTUAL_DISKS[@]}"; do
|
||||
echo -e "${TAB}${BL}- Disk $((i+1)): ${VIRTUAL_DISKS[$i]}GB${CL}"
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
export VIRTUAL_DISKS
|
||||
|
||||
|
||||
}
|
||||
|
||||
# ==========================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Select Physical Disks
|
||||
# ==========================================================
|
||||
function select_passthrough_disk() {
|
||||
|
||||
msg_info "$(translate "Detecting available disks...")"
|
||||
|
||||
FREE_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') | xargs -n1 readlink -f | sort -u)
|
||||
|
||||
RAID_ACTIVE=$(grep -Po 'md\d+\s*:\s*active\s+raid[0-9]+' /proc/mdstat | awk '{print $1}' | sort -u)
|
||||
|
||||
while read -r DISK; do
|
||||
[[ "$DISK" =~ /dev/zd ]] && continue
|
||||
|
||||
INFO=($(lsblk -dn -o MODEL,SIZE "$DISK"))
|
||||
MODEL="${INFO[@]::${#INFO[@]}-1}"
|
||||
SIZE="${INFO[-1]}"
|
||||
LABEL=""
|
||||
SHOW_DISK=true
|
||||
|
||||
IS_MOUNTED=false
|
||||
IS_RAID=false
|
||||
IS_ZFS=false
|
||||
IS_LVM=false
|
||||
|
||||
while read -r part fstype; do
|
||||
[[ "$fstype" == "zfs_member" ]] && IS_ZFS=true
|
||||
[[ "$fstype" == "linux_raid_member" ]] && IS_RAID=true
|
||||
[[ "$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=$(readlink -f "$DISK")
|
||||
if echo "$LVM_DEVICES" | grep -qFx "$REAL_PATH"; then
|
||||
IS_MOUNTED=true
|
||||
fi
|
||||
|
||||
USED_BY=""
|
||||
REAL_PATH=$(readlink -f "$DISK")
|
||||
CONFIG_DATA=$(cat /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
|
||||
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)" && grep -q "active raid" /proc/mdstat; then
|
||||
SHOW_DISK=false
|
||||
fi
|
||||
|
||||
if $IS_ZFS || $IS_MOUNTED || [[ "$ZFS_DISKS" == *"$DISK"* ]]; 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"
|
||||
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
|
||||
cleanup
|
||||
whiptail --title "Error" --msgbox "$(translate "No disks available for this VM.")" 8 40
|
||||
select_disk_type
|
||||
return
|
||||
fi
|
||||
|
||||
MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1)
|
||||
TOTAL_WIDTH=$((MAX_WIDTH + 20))
|
||||
[ $TOTAL_WIDTH -lt 50 ] && TOTAL_WIDTH=50
|
||||
cleanup
|
||||
SELECTED_DISKS=$(whiptail --title "Select Disks" --checklist \
|
||||
"$(translate "Select the disks you want to use (use spacebar to select):")" 20 $TOTAL_WIDTH 10 \
|
||||
"${FREE_DISKS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [ -z "$SELECTED_DISKS" ]; then
|
||||
msg_error "Disk not specified. At least one disk is required."
|
||||
sleep 2
|
||||
select_disk_type
|
||||
return
|
||||
fi
|
||||
|
||||
|
||||
msg_ok "Disk passthrough selected:"
|
||||
PASSTHROUGH_DISKS=()
|
||||
for DISK in $(echo "$SELECTED_DISKS" | tr -d '"'); do
|
||||
DISK_INFO=$(lsblk -ndo MODEL,SIZE "$DISK" | xargs)
|
||||
echo -e "${TAB}${CL}${BL}- $DISK $DISK_INFO${GN}${CL}"
|
||||
PASSTHROUGH_DISKS+=("$DISK")
|
||||
done
|
||||
|
||||
|
||||
}
|
||||
# ==========================================================
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# Guest Agent Configurator - ProxMenux
|
||||
# ==========================================================
|
||||
# Añade soporte al QEMU Guest Agent y dispositivos útiles.
|
||||
# Se adapta según el sistema operativo.
|
||||
# ==========================================================
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
function configure_guest_agent() {
|
||||
if [[ -z "$VMID" ]]; then
|
||||
msg_error "$(translate "No VMID defined. Cannot apply guest agent config.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
msg_info "$(translate "Adding QEMU Guest Agent support...")"
|
||||
|
||||
# Habilitar el agente en la VM
|
||||
qm set "$VMID" -agent enabled=1 >/dev/null 2>&1
|
||||
|
||||
# Añadir canal de comunicación virtio
|
||||
qm set "$VMID" -chardev socket,id=qga0,path=/var/run/qemu-server/$VMID.qga,server=on,wait=off >/dev/null 2>&1
|
||||
qm set "$VMID" -device virtio-serial-pci -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 >/dev/null 2>&1
|
||||
|
||||
msg_ok "$(translate "Guest Agent configuration applied")"
|
||||
|
||||
if [[ "$OS_TYPE" == "windows" ]]; then
|
||||
echo -e "${YW}$(translate "Reminder: You must install the QEMU Guest Agent inside the Windows VM")${NC}"
|
||||
echo -e "${YW}$(translate "Tip: Also mount the VirtIO ISO for drivers and guest agent installer")${NC}"
|
||||
echo -e "${TAB}- https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/"
|
||||
elif [[ "$OS_TYPE" == "linux" || "$OS_TYPE" == "lite" ]]; then
|
||||
echo -e "${YW}$(translate "Tip: You can install the QEMU Guest Agent inside the VM with:")${NC}"
|
||||
echo -e "${TAB}apt install qemu-guest-agent -y && systemctl enable --now qemu-guest-agent"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================
|
||||
# ProxMenux - Linux ISO Selector (No download yet)
|
||||
# ==============================================================
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
function select_linux_iso() {
|
||||
|
||||
ISO_DIR="/var/lib/vz/template/iso"
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
|
||||
DISTROS=(
|
||||
"Ubuntu 22.04 LTS Desktop|Desktop|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso"
|
||||
"Ubuntu 20.04 LTS Desktop|Desktop|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-desktop-amd64.iso"
|
||||
"Ubuntu 22.04 LTS Server (CLI)|CLI|ProxMenux|https://releases.ubuntu.com/22.04/ubuntu-22.04.4-live-server-amd64.iso"
|
||||
"Ubuntu 20.04 LTS Server (CLI)|CLI|ProxMenux|https://releases.ubuntu.com/20.04/ubuntu-20.04.6-live-server-amd64.iso"
|
||||
"Debian 12 Gnome (Desktop)|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-dvd/debian-12.5.0-amd64-DVD-1.iso"
|
||||
"Debian 11 Gnome (Desktop)|Desktop|ProxMenux|https://cdimage.debian.org/debian-cd/11.9.0/amd64/iso-dvd/debian-11.9.0-amd64-DVD-1.iso"
|
||||
"Debian 12 Netinst (CLI)|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.5.0-amd64-netinst.iso"
|
||||
"Debian 11 Netinst (CLI)|CLI|ProxMenux|https://cdimage.debian.org/debian-cd/11.9.0/amd64/iso-cd/debian-11.9.0-amd64-netinst.iso"
|
||||
"Fedora Workstation 39|Desktop|ProxMenux|https://download.fedoraproject.org/pub/fedora/linux/releases/39/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-39-1.5.iso"
|
||||
"Fedora Workstation 38|Desktop|ProxMenux|https://download.fedoraproject.org/pub/fedora/linux/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso"
|
||||
"Rocky Linux 9.3 Gnome|Desktop|ProxMenux|https://download.rockylinux.org/pub/rocky/9.3/isos/x86_64/Rocky-9.3-x86_64-boot.iso"
|
||||
"Rocky Linux 8.9 Gnome|Desktop|ProxMenux|https://download.rockylinux.org/pub/rocky/8.9/isos/x86_64/Rocky-8.9-x86_64-boot.iso"
|
||||
"Linux Mint 21.3 Cinnamon|Desktop|ProxMenux|https://mirrors.edge.kernel.org/linuxmint/stable/21.3/linuxmint-21.3-cinnamon-64bit.iso"
|
||||
"Linux Mint 21.2 Cinnamon|Desktop|ProxMenux|https://mirrors.edge.kernel.org/linuxmint/stable/21.2/linuxmint-21.2-cinnamon-64bit.iso"
|
||||
"openSUSE Leap 15.5|Desktop|ProxMenux|https://download.opensuse.org/distribution/leap/15.5/iso/openSUSE-Leap-15.5-DVD-x86_64.iso"
|
||||
"openSUSE Leap 15.4|Desktop|ProxMenux|https://download.opensuse.org/distribution/leap/15.4/iso/openSUSE-Leap-15.4-DVD-x86_64.iso"
|
||||
"Alpine Linux 3.19|CLI|ProxMenux|https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-standard-3.19.1-x86_64.iso"
|
||||
"Kali Linux 2024.1|Desktop|ProxMenux|https://cdimage.kali.org/kali-2024.1/kali-linux-2024.1-installer-amd64.iso"
|
||||
"Manjaro 23.1 GNOME|Desktop|ProxMenux|https://download.manjaro.org/gnome/23.1/manjaro-gnome-23.1-231017-linux65.iso"
|
||||
"Arch Linux (automatizado)|Cloud-ini|Helper Scripts|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/archlinux-vm.sh"
|
||||
"Debian 12 (automatizado)|Cloud-ini|Helper Scripts|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/debian-vm.sh"
|
||||
"Ubuntu 22.04 (automatizado)|Cloud-ini|Helper Scripts|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2204-vm.sh"
|
||||
"Ubuntu 24.04 (automatizado)|Cloud-ini|Helper Scripts|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2404-vm.sh"
|
||||
"Ubuntu 24.10 (automatizado)|Cloud-ini|Helper Scripts|https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/ubuntu2410-vm.sh"
|
||||
)
|
||||
|
||||
MENU_OPTIONS=()
|
||||
INDEX=0
|
||||
for entry in "${DISTROS[@]}"; do
|
||||
IFS='|' read -r NAME TYPE SOURCE URL <<< "$entry"
|
||||
LINE=$(printf "%-35s │ %-10s │ %s" "$NAME" "$TYPE" "$SOURCE")
|
||||
MENU_OPTIONS+=("$INDEX" "$LINE")
|
||||
URLS[$INDEX]="$entry"
|
||||
((INDEX++))
|
||||
done
|
||||
|
||||
HEADER="%-41s │ %-10s │ %s"
|
||||
HEADER_TEXT=$(printf "$HEADER" " Versión" "Tipo" "Fuente")
|
||||
|
||||
CHOICE=$(whiptail --title "ProxMenux - Linux ISO" \
|
||||
--menu "$(translate "Select the Linux distribution to install"):\n\n$HEADER_TEXT" 20 80 10 \
|
||||
"${MENU_OPTIONS[@]}" \
|
||||
3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && echo "Cancelled" && exit 1
|
||||
|
||||
SELECTED="${URLS[$CHOICE]}"
|
||||
IFS='|' read -r ISO_NAME ISO_TYPE SOURCE ISO_URL <<< "$SELECTED"
|
||||
ISO_FILE=$(basename "$ISO_URL")
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
|
||||
# Exportar para que los use el script principal
|
||||
export ISO_NAME
|
||||
export ISO_TYPE
|
||||
export ISO_URL
|
||||
export ISO_FILE
|
||||
export ISO_PATH
|
||||
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================
|
||||
# ProxMenux - NAS ISO Selector
|
||||
# ==============================================================
|
||||
|
||||
# Configuracion Base
|
||||
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
|
||||
|
||||
ISO_DIR="/var/lib/vz/template/iso"
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
function select_nas_iso() {
|
||||
|
||||
NAS_OPTIONS=(
|
||||
"1" "$(translate "Synology DSM VM")"
|
||||
"2" "$(translate "TrueNAS SCALE VM 24.04.2.5") (Dragonfish)"
|
||||
"3" "$(translate "TrueNAS CORE VM (FreeBSD based)")"
|
||||
"4" "$(translate "OpenMediaVault VM (Debian based)")"
|
||||
"5" "$(translate "Rockstor VM (openSUSE based)")"
|
||||
)
|
||||
|
||||
NAS_TYPE=$(whiptail --title "ProxMenux - NAS Systems" --menu "$(translate "Select the NAS system to install")" 20 70 6 \
|
||||
"${NAS_OPTIONS[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && echo "Cancelled." && exit 1
|
||||
|
||||
case "$NAS_TYPE" in
|
||||
1)
|
||||
bash <(curl -s "https://raw.githubusercontent.com/MacRimi/ProxMenux/main/scripts/vm/synology.sh")
|
||||
exit 0
|
||||
;;
|
||||
2)
|
||||
ISO_NAME="TrueNAS SCALE 24.04.2.5 (Dragonfish)"
|
||||
ISO_URL="https://download.truenas.com/TrueNAS-SCALE-Dragonfish/24.04.2.5/TrueNAS-SCALE-24.04.2.5.iso"
|
||||
ISO_FILE="TrueNAS-SCALE-24.04.2.5.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
;;
|
||||
3)
|
||||
LATEST_ISO=$(wget -qO- https://download.freenas.org/latest/x64/ | grep -oP 'href="\K[^"]+\.iso' | head -n1)
|
||||
ISO_NAME="TrueNAS CORE (Latest)"
|
||||
ISO_URL="https://download.freenas.org/latest/x64/$LATEST_ISO"
|
||||
ISO_FILE=$(basename "$LATEST_ISO")
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
;;
|
||||
4)
|
||||
ISO_NAME="OpenMediaVault"
|
||||
ISO_URL="https://downloads.sourceforge.net/project/openmediavault/7.2.0/openmediavault_7.2.0-amd64.iso"
|
||||
ISO_FILE="openmediavault_7.2.0-amd64.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
;;
|
||||
5)
|
||||
ISO_NAME="Rockstor"
|
||||
ISO_URL="https://rockstor.com/downloads/installer/leap/15.6/x86_64/Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
ISO_FILE="Rockstor-Leap15.6-generic.x86_64-5.0.15-0.install.iso"
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
;;
|
||||
esac
|
||||
|
||||
export ISO_NAME
|
||||
export ISO_URL
|
||||
export ISO_FILE
|
||||
export ISO_PATH
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================
|
||||
# ProxMenux - Windows ISO Selector
|
||||
# ==============================================================
|
||||
|
||||
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
|
||||
|
||||
ISO_DIR="/var/lib/vz/template/iso"
|
||||
mkdir -p "$ISO_DIR"
|
||||
|
||||
|
||||
|
||||
|
||||
function select_windows_iso() {
|
||||
local CHOICE=$(whiptail --title "ProxMenux - Windows ISO" --menu "$(translate "Select how to provide the Windows ISO")" 15 60 2 \
|
||||
"1" "$(translate "Use existing ISO from storage")" \
|
||||
"2" "$(translate "Download ISO using UUP Dump")" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && msg_error "$(translate "Operation cancelled.")" && exit 1
|
||||
|
||||
case "$CHOICE" in
|
||||
1)
|
||||
select_existing_iso
|
||||
;;
|
||||
2)
|
||||
if [[ -f ./uupdump_creator.sh ]]; then
|
||||
source ./uupdump_creator.sh
|
||||
run_uupdump_creator || exit 1
|
||||
detect_latest_iso_created
|
||||
else
|
||||
msg_error "$(translate "UUP Dump script not found.")"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function select_existing_iso() {
|
||||
ISO_LIST=()
|
||||
while read -r line; do
|
||||
FILENAME=$(basename "$line")
|
||||
SIZE=$(du -h "$line" | cut -f1)
|
||||
ISO_LIST+=("$FILENAME" "$SIZE")
|
||||
done < <(find "$ISO_DIR" -type f -iname "*.iso" ! -iname "virtio*" | sort)
|
||||
|
||||
if [[ ${#ISO_LIST[@]} -eq 0 ]]; then
|
||||
msg_error "$(translate "No ISO images found in $ISO_DIR.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ISO_FILE=$(whiptail --title "ProxMenux - Windows ISO" --menu "$(translate "Choose a Windows ISO to use:")" 20 70 10 "${ISO_LIST[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ -z "$ISO_FILE" ]]; then
|
||||
msg_error "$(translate "No ISO selected.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ISO_PATH="$ISO_DIR/$ISO_FILE"
|
||||
ISO_NAME="$ISO_FILE"
|
||||
|
||||
export ISO_PATH ISO_FILE ISO_NAME
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function detect_latest_iso_created() {
|
||||
ISO_FILE=$(find "$ISO_DIR" -maxdepth 1 -type f -iname "*.iso" ! -iname "virtio*" -printf "%T@ %p\n" | sort -n | awk '{print $2}' | tail -n 1)
|
||||
|
||||
if [[ -z "$ISO_FILE" ]]; then
|
||||
msg_error "$(translate "No ISO file detected after UUP Dump process.")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ISO_NAME=$(basename "$ISO_FILE")
|
||||
ISO_PATH="$ISO_FILE"
|
||||
|
||||
export ISO_PATH ISO_FILE ISO_NAME
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,139 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==============================================================
|
||||
# ProxMenux - Windows ISO Creator from UUP Dump
|
||||
# ==============================================================
|
||||
|
||||
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
|
||||
|
||||
function run_uupdump_creator() {
|
||||
|
||||
clear
|
||||
show_proxmenux_logo
|
||||
|
||||
# Configuración de carpetas
|
||||
TMP_DIR="/root/uup-temp"
|
||||
OUT_DIR="/var/lib/vz/template/iso"
|
||||
CONVERTER="/root/uup-converter"
|
||||
|
||||
mkdir -p "$TMP_DIR" "$OUT_DIR"
|
||||
cd "$TMP_DIR" || exit 1
|
||||
|
||||
# Solicitar URL UUP Dump al usuario
|
||||
UUP_URL=$(whiptail --inputbox "$(translate "Paste the UUP Dump URL here")" 10 90 3>&1 1>&2 2>&3)
|
||||
[[ $? -ne 0 ]] && msg_error "$(translate "Cancelled by user.")" && exit 1
|
||||
|
||||
# Validar que la URL tenga los parámetros necesarios
|
||||
if [[ ! "$UUP_URL" =~ id=.+\&pack=.+\&edition=.+ ]]; then
|
||||
msg_error "$(translate "The URL does not contain the required parameters (id, pack, edition).")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extraer parámetros de la URL
|
||||
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"
|
||||
|
||||
# Descargar el conversor si no existe
|
||||
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
|
||||
|
||||
# Crear script de descarga uup_download_linux.sh
|
||||
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
|
||||
|
||||
# Ejecutar la descarga de archivos UUP
|
||||
./uup_download_linux.sh
|
||||
|
||||
# Buscar carpeta UUPs descargada
|
||||
UUP_FOLDER=$(find "$TMP_DIR" -type d -name "UUPs" | head -n1)
|
||||
[[ -z "$UUP_FOLDER" ]] && msg_error "$(translate "No UUP folder found.")" && exit 1
|
||||
|
||||
# Iniciar conversión a ISO
|
||||
echo -e "\n${GN}=======================================${CL}"
|
||||
echo -e " 💿 ${GN}Starting ISO conversion...${CL}"
|
||||
echo -e "${GN}=======================================${CL}\n"
|
||||
|
||||
"$CONVERTER/convert.sh" wim "$UUP_FOLDER" 1
|
||||
|
||||
# Buscar la ISO generada
|
||||
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")"
|
||||
|
||||
# Limpiar temporales
|
||||
msg_ok "$(translate "Cleaning temporary files...")"
|
||||
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
|
||||
else
|
||||
msg_warn "$(translate "No ISO was generated.")"
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ================================================
|
||||
# VM Configuration Module - ProxMenux
|
||||
# ================================================
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
function generate_mac() {
|
||||
local GEN_MAC="02"
|
||||
for i in {1..5}; do
|
||||
BYTE=$(printf "%02X" $((RANDOM % 256)))
|
||||
GEN_MAC="${GEN_MAC}:${BYTE}"
|
||||
done
|
||||
echo "$GEN_MAC"
|
||||
}
|
||||
|
||||
function load_default_vm_config() {
|
||||
local os_type="$1"
|
||||
|
||||
VMID=$(pvesh get /cluster/nextid 2>/dev/null || echo "100")
|
||||
MAC=$(generate_mac)
|
||||
|
||||
case "$os_type" in
|
||||
"nas")
|
||||
HN="synology-nas"
|
||||
CORE_COUNT="2"
|
||||
RAM_SIZE="8192"
|
||||
MACHINE=" -machine q35"
|
||||
BIOS_TYPE=" -bios ovmf"
|
||||
START_VM="no"
|
||||
;;
|
||||
"windows")
|
||||
HN="windows-vm"
|
||||
CORE_COUNT="4"
|
||||
RAM_SIZE="8192"
|
||||
MACHINE=" -machine q35"
|
||||
BIOS_TYPE=" -bios ovmf"
|
||||
START_VM="no"
|
||||
;;
|
||||
"linux")
|
||||
HN="linux-vm"
|
||||
CORE_COUNT="2"
|
||||
RAM_SIZE="4096"
|
||||
MACHINE=" -machine q35"
|
||||
BIOS_TYPE=" -bios ovmf"
|
||||
START_VM="no"
|
||||
;;
|
||||
"lite")
|
||||
HN="lite-vm"
|
||||
CORE_COUNT="1"
|
||||
RAM_SIZE="2048"
|
||||
MACHINE=""
|
||||
BIOS_TYPE=" -bios seabios"
|
||||
START_VM="no"
|
||||
;;
|
||||
*)
|
||||
HN="vm-proxmenux"
|
||||
CORE_COUNT="2"
|
||||
RAM_SIZE="2048"
|
||||
MACHINE=" -machine q35"
|
||||
BIOS_TYPE=" -bios ovmf"
|
||||
START_VM="no"
|
||||
;;
|
||||
esac
|
||||
|
||||
CPU_TYPE=" -cpu host"
|
||||
BRG="vmbr0"
|
||||
VLAN=""
|
||||
MTU=""
|
||||
SERIAL_PORT="socket"
|
||||
FORMAT=""
|
||||
DISK_CACHE=""
|
||||
}
|
||||
|
||||
function apply_default_vm_config() {
|
||||
echo -e "${DEF}$(translate "Applying default VM configuration")${CL}"
|
||||
echo -e "${DGN}$(translate "Virtual Machine ID")${CL}: ${BGN}$VMID${CL}"
|
||||
echo -e "${DGN}$(translate "Hostname")${CL}: ${BGN}$HN${CL}"
|
||||
echo -e "${DGN}$(translate "CPU Cores")${CL}: ${BGN}$CORE_COUNT${CL}"
|
||||
echo -e "${DGN}$(translate "RAM Size")${CL}: ${BGN}$RAM_SIZE MiB${CL}"
|
||||
echo -e "${DGN}$(translate "Machine Type")${CL}: ${BGN}${MACHINE/ -machine /}${CL}"
|
||||
echo -e "${DGN}$(translate "BIOS Type")${CL}: ${BGN}${BIOS_TYPE/ -bios /}${CL}"
|
||||
echo -e "${DGN}$(translate "CPU Model")${CL}: ${BGN}${CPU_TYPE/ -cpu /}${CL}"
|
||||
echo -e "${DGN}$(translate "Network Bridge")${CL}: ${BGN}$BRG${CL}"
|
||||
echo -e "${DGN}$(translate "MAC Address")${CL}: ${BGN}$MAC${CL}"
|
||||
echo -e "${DGN}$(translate "Start VM after creation")${CL}: ${BGN}$START_VM${CL}"
|
||||
echo -e
|
||||
}
|
||||
|
||||
|
||||
|
||||
function configure_vm_advanced() {
|
||||
# VMID
|
||||
while true; do
|
||||
VMID=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Set Virtual Machine ID")" 8 58 "$VMID" --title "VM ID" 3>&1 1>&2 2>&3) || return
|
||||
if [ -z "$VMID" ]; then continue; fi
|
||||
if qm status "$VMID" &>/dev/null || pct status "$VMID" &>/dev/null; then
|
||||
msg_error "$(translate "ID already in use. Please choose another.")"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Hostname
|
||||
HN=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Set Hostname")" 8 58 "$HN" --title "Hostname" 3>&1 1>&2 2>&3) || return
|
||||
[[ -z "$HN" ]] && HN="vm-proxmenux"
|
||||
|
||||
# Machine Type
|
||||
MACHINE_TYPE=$(whiptail --backtitle "ProxMenux" --title "$(translate "MACHINE TYPE")" --radiolist \
|
||||
"$(translate "Select machine type")" 10 60 2 \
|
||||
"q35" "QEMU q35" ON \
|
||||
"i440fx" "Legacy i440fx" OFF 3>&1 1>&2 2>&3) || return
|
||||
|
||||
if [ "$MACHINE_TYPE" = "q35" ]; then
|
||||
MACHINE=" -machine q35"
|
||||
FORMAT=""
|
||||
else
|
||||
MACHINE=""
|
||||
FORMAT=",efitype=4m"
|
||||
fi
|
||||
|
||||
# BIOS
|
||||
BIOS=$(whiptail --backtitle "ProxMenux" --title "$(translate "BIOS TYPE")" --radiolist \
|
||||
"$(translate "Choose BIOS type")" 10 60 2 \
|
||||
"ovmf" "UEFI (OVMF)" ON \
|
||||
"seabios" "Legacy BIOS (SeaBIOS)" OFF 3>&1 1>&2 2>&3) || return
|
||||
|
||||
BIOS_TYPE=" -bios $BIOS"
|
||||
|
||||
# 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
|
||||
|
||||
if [ "$CPU_CHOICE" = "host" ]; then
|
||||
CPU_TYPE=" -cpu host"
|
||||
else
|
||||
CPU_TYPE=" -cpu kvm64"
|
||||
fi
|
||||
|
||||
# Core Count
|
||||
CORE_COUNT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Number of CPU cores")" 8 58 "$CORE_COUNT" --title "CPU Cores" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
# RAM
|
||||
RAM_SIZE=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Amount of RAM in MiB")" 8 58 "$RAM_SIZE" --title "RAM" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
# Bridge
|
||||
BRG=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Set network bridge")" 8 58 "$BRG" --title "Network Bridge" 3>&1 1>&2 2>&3) || return
|
||||
|
||||
# MAC
|
||||
MAC_INPUT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Set MAC Address (leave empty for random)")" 8 58 "$MAC" --title "MAC Address" 3>&1 1>&2 2>&3) || return
|
||||
if [[ -z "$MAC_INPUT" ]]; then
|
||||
MAC=$(generate_mac)
|
||||
else
|
||||
MAC="$MAC_INPUT"
|
||||
fi
|
||||
|
||||
# VLAN
|
||||
VLAN_INPUT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Set VLAN Tag (leave empty for none)")" 8 58 --title "VLAN" 3>&1 1>&2 2>&3) || return
|
||||
VLAN=""
|
||||
[[ -n "$VLAN_INPUT" ]] && VLAN=",tag=$VLAN_INPUT"
|
||||
|
||||
# MTU
|
||||
MTU_INPUT=$(whiptail --backtitle "ProxMenux" --inputbox "$(translate "Set MTU size (leave empty for default)")" 8 58 --title "MTU" 3>&1 1>&2 2>&3) || return
|
||||
MTU=""
|
||||
[[ -n "$MTU_INPUT" ]] && MTU=",mtu=$MTU_INPUT"
|
||||
|
||||
# Start VM
|
||||
if (whiptail --backtitle "ProxMenux" --title "$(translate "START VM")" --yesno "$(translate "Start VM when finished?")" 10 60); then
|
||||
START_VM="yes"
|
||||
else
|
||||
START_VM="no"
|
||||
fi
|
||||
|
||||
msg_ok "$(translate "Advanced configuration completed.")"
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# ==========================================================
|
||||
# VM Creator Module - ProxMenux
|
||||
# ==========================================================
|
||||
# Este módulo recibe las variables globales y crea la VM
|
||||
# con su configuración, discos y descripción.
|
||||
# ==========================================================
|
||||
|
||||
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
|
||||
|
||||
# ==========================================================
|
||||
# Función para montar ISOs
|
||||
# ==========================================================
|
||||
function mount_iso_to_vm() {
|
||||
local vmid="$1"
|
||||
local iso_path="$2"
|
||||
local device="$3"
|
||||
|
||||
if [[ -f "$iso_path" ]]; then
|
||||
local iso_basename
|
||||
iso_basename=$(basename "$iso_path")
|
||||
qm set "$vmid" -$device "local:iso/$iso_basename,media=cdrom" >/dev/null 2>&1
|
||||
msg_ok "$(translate "Mounted ISO on device") $device → $iso_basename"
|
||||
else
|
||||
msg_warn "$(translate "ISO not found to mount on device") $device"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Select Interface Type
|
||||
# ==========================================================
|
||||
function select_interface_type() {
|
||||
INTERFACE_TYPE=$(whiptail --backtitle "ProxMenux" --title "$(translate "Select Disk Interface")" --radiolist \
|
||||
"$(translate "Select the bus type for the disks:")" 15 70 4 \
|
||||
"scsi" "$(translate "SCSI (recommended for Linux and Windows)")" ON \
|
||||
"sata" "$(translate "SATA (standard - high compatibility)")" OFF \
|
||||
"virtio" "$(translate "VirtIO (advanced - high performance)")" OFF \
|
||||
"ide" "IDE (legacy)" OFF \
|
||||
3>&1 1>&2 2>&3) || exit 1
|
||||
|
||||
case "$INTERFACE_TYPE" in
|
||||
"scsi"|"sata")
|
||||
DISCARD_OPTS=",discard=on,ssd=on"
|
||||
;;
|
||||
"virtio")
|
||||
DISCARD_OPTS=",discard=on"
|
||||
;;
|
||||
"ide")
|
||||
DISCARD_OPTS=""
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_ok "$(translate "Disk interface selected:") $INTERFACE_TYPE"
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Función principal para crear la VM
|
||||
# ==========================================================
|
||||
function create_vm() {
|
||||
local BOOT_ORDER=""
|
||||
local DISK_INFO=""
|
||||
local DISK_INDEX=0
|
||||
local ISO_DIR="/var/lib/vz/template/iso"
|
||||
|
||||
|
||||
# Descargar ISO si es necesario
|
||||
if [[ -n "$ISO_PATH" && -n "$ISO_URL" && ! -f "$ISO_PATH" ]]; then
|
||||
wget --no-verbose --show-progress -O "$ISO_PATH" "$ISO_URL"
|
||||
if [[ -f "$ISO_PATH" ]]; then
|
||||
msg_ok "$(translate "ISO image downloaded")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download ISO image")"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# Crear la VM base primero (mínima)
|
||||
qm create "$VMID" -agent 1${MACHINE} -tablet 0 -localtime 1${BIOS_TYPE}${CPU_TYPE} \
|
||||
-cores "$CORE_COUNT" -memory "$RAM_SIZE" -name "$HN" -tags proxmenux \
|
||||
-net0 "virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU" -ostype l26 \
|
||||
-scsihw virtio-scsi-pci -serial0 "$SERIAL_PORT"
|
||||
msg_ok "$(translate "Base VM created with ID") $VMID"
|
||||
|
||||
|
||||
# Crear disco EFI si corresponde
|
||||
if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then
|
||||
msg_info "$(translate "Configuring EFI disk")"
|
||||
EFI_STORAGE=$(select_efi_storage "$VMID")
|
||||
EFI_DISK_NAME="vm-${VMID}-disk-efivars"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$EFI_STORAGE" | awk 'NR>1 {print $2}')
|
||||
case "$STORAGE_TYPE" in
|
||||
nfs | dir)
|
||||
EFI_DISK_EXT=".raw"
|
||||
EFI_DISK_REF="$VMID/"
|
||||
;;
|
||||
*)
|
||||
EFI_DISK_EXT=""
|
||||
EFI_DISK_REF=""
|
||||
;;
|
||||
esac
|
||||
|
||||
if pvesm alloc "$EFI_STORAGE" "$VMID" "$EFI_DISK_NAME$EFI_DISK_EXT" 4M >/dev/null 2>&1; then
|
||||
if qm set "$VMID" -efidisk0 "$EFI_STORAGE:${EFI_DISK_REF}$EFI_DISK_NAME$EFI_DISK_EXT,pre-enrolled-keys=0" >/dev/null 2>&1; then
|
||||
msg_ok "$(translate "EFI disk created and configured on") $EFI_STORAGE"
|
||||
else
|
||||
msg_error "$(translate "Failed to configure EFI disk")"
|
||||
fi
|
||||
else
|
||||
msg_error "$(translate "Failed to create EFI disk")"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Crear discos virtuales o físicos con interfaz seleccionada
|
||||
# ==========================================================
|
||||
|
||||
# Primero seleccionar la interfaz
|
||||
select_interface_type
|
||||
|
||||
if [[ "$DISK_TYPE" == "virtual" && ${#VIRTUAL_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!VIRTUAL_DISKS[@]}"; do
|
||||
DISK_INDEX=$((i+1))
|
||||
IFS=':' read -r STORAGE SIZE <<< "${VIRTUAL_DISKS[$i]}"
|
||||
DISK_NAME="vm-${VMID}-disk-${DISK_INDEX}"
|
||||
SLOT_NAME="${INTERFACE_TYPE}${i}"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
|
||||
case "$STORAGE_TYPE" in
|
||||
dir|nfs)
|
||||
DISK_EXT=".raw"
|
||||
DISK_REF="$VMID/"
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
;;
|
||||
esac
|
||||
|
||||
if pvesm alloc "$STORAGE" "$VMID" "$DISK_NAME$DISK_EXT" "$SIZE"G >/dev/null 2>&1; then
|
||||
qm set "$VMID" -$SLOT_NAME "$STORAGE:${DISK_REF}${DISK_NAME}${DISK_EXT}${DISCARD_OPTS}" >/dev/null
|
||||
msg_ok "$(translate "Virtual disk") $DISK_INDEX ${SIZE}GB - $STORAGE ($SLOT_NAME)"
|
||||
DISK_INFO+="<p>Virtual Disk $DISK_INDEX: ${SIZE}GB ($STORAGE / $SLOT_NAME)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
else
|
||||
msg_error "$(translate "Failed to create disk") $DISK_INDEX"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "$DISK_TYPE" == "passthrough" && ${#PASSTHROUGH_DISKS[@]} -gt 0 ]]; then
|
||||
for i in "${!PASSTHROUGH_DISKS[@]}"; do
|
||||
SLOT_NAME="${INTERFACE_TYPE}${i}"
|
||||
DISK="${PASSTHROUGH_DISKS[$i]}"
|
||||
MODEL=$(lsblk -ndo MODEL "$DISK")
|
||||
SIZE=$(lsblk -ndo SIZE "$DISK")
|
||||
qm set "$VMID" -$SLOT_NAME "$DISK${DISCARD_OPTS}" >/dev/null 2>&1
|
||||
msg_ok "$(translate "Passthrough disk assigned") ($DISK → $SLOT_NAME)"
|
||||
DISK_INFO+="<p>Passthrough Disk $((i+1)): $DISK ($MODEL $SIZE)</p>"
|
||||
[[ -z "$BOOT_ORDER" ]] && BOOT_ORDER="$SLOT_NAME"
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
|
||||
|
||||
# Ahora montamos las ISOs
|
||||
if [[ -f "$ISO_PATH" ]]; then
|
||||
mount_iso_to_vm "$VMID" "$ISO_PATH" "ide2"
|
||||
fi
|
||||
|
||||
# Para Windows, preguntar y montar ISO VirtIO
|
||||
if [[ "$OS_TYPE" == "windows" ]]; then
|
||||
local VIRTIO_DIR="/var/lib/vz/template/iso"
|
||||
local VIRTIO_SELECTED=""
|
||||
local VIRTIO_DOWNLOAD_URL="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso"
|
||||
|
||||
while true; do
|
||||
VIRTIO_OPTION=$(whiptail --title "ProxMenux - VirtIO Drivers" --menu "$(translate "Select how to provide VirtIO drivers")" 15 70 2 \
|
||||
"1" "$(translate "Download latest VirtIO ISO automatically")" \
|
||||
"2" "$(translate "Use existing VirtIO ISO from storage")" 3>&1 1>&2 2>&3)
|
||||
|
||||
[[ $? -ne 0 ]] && msg_warn "$(translate "VirtIO ISO selection cancelled.")" && break
|
||||
|
||||
case "$VIRTIO_OPTION" in
|
||||
1)
|
||||
|
||||
if [[ -f "$VIRTIO_DIR/virtio-win.iso" ]]; then
|
||||
if whiptail --title "ProxMenux" --yesno "$(translate "A VirtIO ISO already exists. Do you want to overwrite it?")" 10 60; then
|
||||
wget -q --show-progress -O "$VIRTIO_DIR/virtio-win.iso" "$VIRTIO_DOWNLOAD_URL"
|
||||
if [[ -f "$VIRTIO_DIR/virtio-win.iso" ]]; then
|
||||
msg_ok "$(translate "VirtIO driver ISO downloaded successfully.")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download VirtIO driver ISO.")"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
wget -q --show-progress -O "$VIRTIO_DIR/virtio-win.iso" "$VIRTIO_DOWNLOAD_URL"
|
||||
if [[ -f "$VIRTIO_DIR/virtio-win.iso" ]]; then
|
||||
msg_ok "$(translate "VirtIO driver ISO downloaded successfully.")"
|
||||
else
|
||||
msg_error "$(translate "Failed to download VirtIO driver ISO.")"
|
||||
fi
|
||||
fi
|
||||
|
||||
VIRTIO_SELECTED="$VIRTIO_DIR/virtio-win.iso"
|
||||
;;
|
||||
2)
|
||||
|
||||
VIRTIO_LIST=()
|
||||
while read -r line; do
|
||||
FILENAME=$(basename "$line")
|
||||
SIZE=$(du -h "$line" | cut -f1)
|
||||
VIRTIO_LIST+=("$FILENAME" "$SIZE")
|
||||
done < <(find "$VIRTIO_DIR" -type f -iname "virtio*.iso" | sort)
|
||||
|
||||
if [[ ${#VIRTIO_LIST[@]} -eq 0 ]]; then
|
||||
msg_warn "$(translate "No VirtIO ISO found. Please download one.")"
|
||||
continue # Volver a preguntar
|
||||
fi
|
||||
|
||||
VIRTIO_FILE=$(whiptail --title "ProxMenux - VirtIO ISOs" --menu "$(translate "Select a VirtIO ISO to use:")" 20 70 10 "${VIRTIO_LIST[@]}" 3>&1 1>&2 2>&3)
|
||||
|
||||
if [[ -n "$VIRTIO_FILE" ]]; then
|
||||
VIRTIO_SELECTED="$VIRTIO_DIR/$VIRTIO_FILE"
|
||||
else
|
||||
msg_warn "$(translate "No VirtIO ISO selected. Please choose again.")"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -n "$VIRTIO_SELECTED" && -f "$VIRTIO_SELECTED" ]]; then
|
||||
mount_iso_to_vm "$VMID" "$VIRTIO_SELECTED" "ide3"
|
||||
else
|
||||
msg_warn "$(translate "VirtIO ISO not found after selection.")"
|
||||
fi
|
||||
|
||||
break
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Configurar el orden de arranque (primer disco, luego CD)
|
||||
local BOOT_FINAL="$BOOT_ORDER"
|
||||
[[ -f "$ISO_PATH" ]] && BOOT_FINAL="$BOOT_ORDER;ide2"
|
||||
qm set "$VMID" -boot order="$BOOT_FINAL" >/dev/null
|
||||
msg_ok "$(translate "Boot order set to") $BOOT_FINAL"
|
||||
|
||||
# Crear descripción
|
||||
local DESC="<div align='center'><h1>$HN</h1><p>Created with ProxMenux</p>$DISK_INFO</div>"
|
||||
qm set "$VMID" -description "$DESC" >/dev/null
|
||||
msg_ok "$(translate "VM description configured")"
|
||||
|
||||
# Arrancar la VM si corresponde
|
||||
if [[ "$START_VM" == "yes" ]]; then
|
||||
qm start "$VMID"
|
||||
msg_ok "$(translate "VM started")"
|
||||
fi
|
||||
|
||||
msg_success "$(translate "VM creation completed")"
|
||||
}
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Función select_efi_storage (no cambia)
|
||||
# ==========================================================
|
||||
function select_efi_storage() {
|
||||
local vmid=$1
|
||||
local STORAGE=""
|
||||
|
||||
STORAGE_MENU=()
|
||||
while read -r line; do
|
||||
TAG=$(echo "$line" | awk '{print $1}')
|
||||
TYPE=$(echo "$line" | awk '{printf "%-10s", $2}')
|
||||
FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf("%9sB", $6)}')
|
||||
STORAGE_MENU+=("$TAG" "Type: $TYPE Free: $FREE" "OFF")
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
if [ ${#STORAGE_MENU[@]} -eq 0 ]; then
|
||||
msg_error "$(translate "Unable to detect a valid storage location for EFI disk.")"
|
||||
exit 1
|
||||
elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
|
||||
STORAGE=${STORAGE_MENU[0]}
|
||||
else
|
||||
STORAGE=$(whiptail --backtitle "ProxMenux" --title "$(translate "EFI Disk Storage")" --radiolist \
|
||||
"$(translate "Choose the storage volume for the EFI disk (4MB):")" 16 70 6 \
|
||||
"${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit 1
|
||||
fi
|
||||
|
||||
echo "$STORAGE"
|
||||
}
|
||||
Reference in New Issue
Block a user