diff --git a/oci/catalog.json b/oci/catalog.json new file mode 100644 index 00000000..84a66636 --- /dev/null +++ b/oci/catalog.json @@ -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"] + } + } + } +} diff --git a/scripts/auto_post_install.sh b/scripts/auto_post_install.sh deleted file mode 100644 index 6d48e448..00000000 --- a/scripts/auto_post_install.sh +++ /dev/null @@ -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 < /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 < "$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 < /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 < /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 < /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 < /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 \ No newline at end of file diff --git a/scripts/global/share-common.func b/scripts/global/share-common.func index 8b968f65..3433bcc7 100644 --- a/scripts/global/share-common.func +++ b/scripts/global/share-common.func @@ -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 diff --git a/scripts/security/fail2ban_installer.sh b/scripts/security/fail2ban_installer.sh new file mode 100644 index 00000000..e60eaf55 --- /dev/null +++ b/scripts/security/fail2ban_installer.sh @@ -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" </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:)? 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= 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 </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 diff --git a/scripts/security/lynis_installer.sh b/scripts/security/lynis_installer.sh new file mode 100644 index 00000000..e079904f --- /dev/null +++ b/scripts/security/lynis_installer.sh @@ -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 diff --git a/scripts/telegram-notifier.sh b/scripts/telegram-notifier.sh deleted file mode 100644 index d5ee8161..00000000 --- a/scripts/telegram-notifier.sh +++ /dev/null @@ -1,2316 +0,0 @@ -#!/bin/bash - - -# 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 - - -CONFIG_FILE="/etc/proxmox-telegram.conf" -PID_DIR="/var/run/proxmox-telegram" -WRAPPER_PATH="/usr/local/bin/telegram-notifier-wrapper.sh" -t() { translate "$1"; } -declare -A IFACE_DOWN -declare -A IFACE_DOWN_TIME -disk_full_detected=false -disk_nearly_full_detected=false -inode_full_detected=false -cpu_usage_history="" -last_cpu_sustained_notification=0 -last_swap_notification=0 - - - - -# ================================================== -# TELEGRAM -# ================================================== - -# Create configuration file if it doesn't exist -if [[ ! -f "$CONFIG_FILE" ]]; then - cat < "$CONFIG_FILE" -BOT_TOKEN="" -CHAT_ID="" -vm_start=0 -vm_shutdown=0 -vm_restart=0 -vm_fail=0 -update_available=0 -update_complete=0 -system_shutdown=0 -system_problem=0 -system_load_high=0 -kernel_panic=0 -disk_fail=0 -disk_full=0 -disk_io_error=0 -node_disconnect=0 -split_brain=0 -network_down=0 -network_saturation=0 -firewall_issue=0 -backup_complete=0 -backup_fail=0 -snapshot_complete=0 -snapshot_fail=0 -auth_fail=0 -ip_block=0 -user_permission_change=0 -cpu_high=0 -ram_high=0 -temp_high=0 -low_disk_space=0 -EOF - chmod 600 "$CONFIG_FILE" -fi - -source "$CONFIG_FILE" - - -######################################################################## - - -send_notification() { - local message="$1" - - - if [[ -z "$BOT_TOKEN" || -z "$CHAT_ID" ]]; then - return 1 - fi - - - curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \ - -d "chat_id=$CHAT_ID" \ - -d "text=$message" > /dev/null 2>&1 -} - - - -######################################################################### - - - -# Function to configure Telegram -configure_telegram() { - - [[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" - - - BOT_TOKEN=$(whiptail --title "$(translate "Telegram Configuration")" \ - --inputbox "$(translate "Enter your Telegram Bot Token:")" 10 70 "$BOT_TOKEN" 3>&1 1>&2 2>&3) - - if [[ $? -ne 0 ]]; then - return - fi - - - CHAT_ID=$(whiptail --title "$(translate "Telegram Configuration")" \ - --inputbox "$(translate "Enter your Telegram Chat ID:")" 10 70 "$CHAT_ID" 3>&1 1>&2 2>&3) - - if [[ $? -ne 0 ]]; then - return - fi - - # Save configuration to file - if [[ -n "$BOT_TOKEN" && -n "$CHAT_ID" ]]; then - - cp "$CONFIG_FILE" "${CONFIG_FILE}.bak" 2>/dev/null - - - if grep -q "^BOT_TOKEN=" "$CONFIG_FILE"; then - sed -i "s/^BOT_TOKEN=.*/BOT_TOKEN=\"$BOT_TOKEN\"/" "$CONFIG_FILE" - else - echo "BOT_TOKEN=\"$BOT_TOKEN\"" >> "$CONFIG_FILE" - fi - - if grep -q "^CHAT_ID=" "$CONFIG_FILE"; then - sed -i "s/^CHAT_ID=.*/CHAT_ID=\"$CHAT_ID\"/" "$CONFIG_FILE" - else - echo "CHAT_ID=\"$CHAT_ID\"" >> "$CONFIG_FILE" - fi - - - source "$CONFIG_FILE" - - # Test the configuration immediately - response=$(curl -s -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \ - -d "chat_id=$CHAT_ID" \ - -d "text=$(translate "Telegram is working correctly!")") - - if [[ "$response" =~ "ok\":true" ]]; then - whiptail --title "$(translate "Success")" \ - --msgbox "$(translate "Valid Telegram configuration. Notifications will be sent.")" 10 70 - else - whiptail --title "$(translate "Error")" \ - --msgbox "$(translate "Invalid Telegram configuration. Please verify the token and chat ID.")" 10 70 - fi - else - whiptail --title "$(translate "Error")" \ - --msgbox "$(translate "Incomplete Telegram configuration. Please provide both token and chat ID.")" 10 70 - fi -} - - - -# ================================================== - - - - - -# ================================================== -# NOTIFICATION CONFIGURATION -# ================================================== - - - -# Options for the menu -options=( - "VM and Container|$(t 'VM/Container Start')|vm_start" - "VM and Container|$(t 'VM/Container Shutdown')|vm_shutdown" - "VM and Container|$(t 'VM/Container Restart')|vm_restart" - "VM and Container|$(t 'VM/Container Start Failure')|vm_fail" - "System|$(t 'New update available')|update_available" - "System|$(t 'Update completed')|update_complete" - "System|$(t 'System shutdown')|system_shutdown" - "System|$(t 'System problem')|system_problem" - "System|$(t 'High system load')|system_load_high" - "Storage|$(t 'Disk failure')|disk_fail" - "Storage|$(t 'Storage full')|disk_full" - "Storage|$(t 'Read/Write issues')|disk_io_error" - "Cluster|$(t 'Node disconnected')|node_disconnect" - "Cluster|$(t 'Split-brain (quorum conflict)')|split_brain" - "Network|$(t 'Network interface down')|network_down" - "Network|$(t 'Network saturation')|network_saturation" - "Network|$(t 'Firewall issue')|firewall_issue" - "Backup and Snapshot|$(t 'Backup completed')|backup_complete" - "Backup and Snapshot|$(t 'Backup failed')|backup_fail" - "Backup and Snapshot|$(t 'Snapshot completed')|snapshot_complete" - "Backup and Snapshot|$(t 'Snapshot failed')|snapshot_fail" - "Security|$(t 'Failed authentication attempt')|auth_fail" - "Security|$(t 'Automatic IP blocks')|ip_block" - "Security|$(t 'User permission change')|user_permission_change" - "Resources|$(t 'High CPU usage')|cpu_high" - "Resources|$(t 'High RAM usage')|ram_high" - "Resources|$(t 'High system temperature')|temp_high" - "Resources|$(t 'Low disk space')|low_disk_space" -) - - - - - - -# Function to configure notifications -configure_notifications() { - - - IFS=$'\n' sorted_options=($(for option in "${options[@]}"; do - IFS='|' read -r category description var_name <<< "$option" - printf "%s|%s|%s\n" "$category" "$description" "$var_name" - done | sort -t'|' -k1,1 -k2,2)) - unset IFS - - - declare -A index_to_var - index=1 - - - menu_items=() - for option in "${sorted_options[@]}"; do - IFS='|' read -r category description var_name <<< "$option" - - - index_to_var["$index"]="$var_name" - - - formatted_item="$description" - current_length=${#formatted_item} - spaces_needed=$((50 - current_length)) - - for ((j = 0; j < spaces_needed; j++)); do - formatted_item+=" " - done - - formatted_item+="$category" - - - state="OFF" - [[ "$(eval echo \$$var_name)" -eq 1 ]] && state="ON" - - menu_items+=("$index" "$formatted_item" "$state") - ((index++)) - done - - # whiptail menu - selected_indices=$(whiptail --backtitle "ProxMenuX" --title "$(translate "Telegram Notification Configuration")" \ - --checklist --separate-output \ - "\n$(translate "Select the events you want to receive:")\n" \ - 30 100 20 \ - "${menu_items[@]}" \ - 3>&1 1>&2 2>&3) - - local result=$? - - - if [[ $result -eq 0 ]]; then - - cp "$CONFIG_FILE" "${CONFIG_FILE}.bak" 2>/dev/null - - for var_name in "${index_to_var[@]}"; do - sed -i "s/^$var_name=.*/$var_name=0/" "$CONFIG_FILE" - done - - for selected_index in $selected_indices; do - var_name="${index_to_var[$selected_index]}" - sed -i "s/^$var_name=.*/$var_name=1/" "$CONFIG_FILE" - done - - - source "$CONFIG_FILE" - - whiptail --backtitle "ProxMenuX" --title "$(translate "Success")" \ - --msgbox "$(translate "Configuration updated successfully.")" 10 70 - fi -} - - - - - -# ================================================== - - - - -# Function to get VM/CT name from its ID -get_vm_name() { - local vmid="$1" - local name="" - - if [[ -f "/etc/pve/qemu-server/$vmid.conf" ]]; then - name=$(grep -i "^name:" "/etc/pve/qemu-server/$vmid.conf" | cut -d ' ' -f2-) - elif [[ -f "/etc/pve/lxc/$vmid.conf" ]]; then - name=$(grep -i "^hostname:" "/etc/pve/lxc/$vmid.conf" | cut -d ' ' -f2-) - fi - - - if [[ -n "$name" ]]; then - echo "$name ($vmid)" - else - echo "$vmid" - fi -} - - -# ================================================== - - - - - -# ================================================== -# NOTIFICATION EVENTS -# ================================================== - - - - -# Function: capture events from journalctl -capture_journal_events() { - - - local processed_events_file="$PID_DIR/processed_events" - - - mkdir -p "$PID_DIR" 2>/dev/null - - - if [[ ! -f "$processed_events_file" ]]; then - touch "$processed_events_file" - fi - - - while true; do - - # Use tail for Proxmox tasks file - tail -F /var/log/pve/tasks/index 2>/dev/null | while read -r line; do - - event_id=$(echo "$line" | md5sum | cut -d' ' -f1) - - if grep -q "$event_id" "$processed_events_file" 2>/dev/null; then - continue - fi - - - echo "$event_id" >> "$processed_events_file" - - tail -n 1000 "$processed_events_file" > "${processed_events_file}.tmp" 2>/dev/null && mv "${processed_events_file}.tmp" "$processed_events_file" 2>/dev/null - - local event_processed=false - - - - - # ===== IMMEDIATE NOTIFICATION EVENTS ===== - - # VM or CT start failure (CRITICAL) - if [[ "$vm_fail" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # Detect VM errors - if [[ "$line" =~ "Failed to start VM" || "$line" =~ "qmstart" && "$line" =~ "err" || "$line" =~ "qmstart" && "$line" =~ "fail" ]]; then - VM_ID=$(echo "$line" | grep -oP '(VM |qmstart:)\K[0-9]+') - NAME=$(get_vm_name "$VM_ID") - send_notification "🚨 $(translate "CRITICAL: Failed to start VM:") $NAME" - event_processed=true - # Detect CT (LXC) errors - elif [[ "$line" =~ "Failed to start CT" || "$line" =~ "lxc-start" && "$line" =~ "err" || "$line" =~ "lxc-start" && "$line" =~ "fail" ]]; then - CT_ID=$(echo "$line" | grep -oP '(CT |lxc-start:)\K[0-9]+') - NAME=$(get_vm_name "$CT_ID") - send_notification "🚨 $(translate "CRITICAL: Failed to start Container:") $NAME" - event_processed=true - fi - fi - - - - # Disk I/O errors (CRITICAL) - if [[ "$disk_io_error" -eq 1 ]] && [[ "$event_processed" = false ]]; then - if [[ "$line" =~ "I/O error" || "$line" =~ "read error" || "$line" =~ "write error" || - "$line" =~ "blk_update_request" || "$line" =~ "buffer I/O error" || - "$line" =~ "medium error" || "$line" =~ "sense key: Medium Error" || - "$line" =~ "ata.*failed command" || "$line" =~ "SCSI error" ]]; then - - # Extract device name with improved pattern matching - DISK=$(echo "$line" | grep -oE "/dev/[a-zA-Z0-9]+" || - echo "$line" | grep -oE "sd[a-z][0-9]*" || - echo "$line" | grep -oE "nvme[0-9]+n[0-9]+" || - echo "unknown") - - # Try to extract error type - ERROR_TYPE="unknown" - if [[ "$line" =~ "read error" ]]; then - ERROR_TYPE="read" - elif [[ "$line" =~ "write error" ]]; then - ERROR_TYPE="write" - elif [[ "$line" =~ "medium error" || "$line" =~ "sense key: Medium Error" ]]; then - ERROR_TYPE="medium" - elif [[ "$line" =~ "timeout" ]]; then - ERROR_TYPE="timeout" - fi - - # Try to extract sector information if available - SECTOR=$(echo "$line" | grep -oP "sector [0-9]+" || echo "") - if [[ -n "$SECTOR" ]]; then - SECTOR=" ($SECTOR)" - fi - - # Send notification with enhanced information - send_notification "🚨 $(translate "CRITICAL: Disk ${ERROR_TYPE} error on:") $DISK$SECTOR" - - event_processed=true - fi - fi - - - - # Disk failure (CRITICAL) - if [[ "$disk_fail" -eq 1 ]] && [[ "$event_processed" = false ]]; then - if [[ "$line" =~ "disk failure" || "$line" =~ "hard drive failure" || - "$line" =~ "SMART error" || "$line" =~ "SMART failure" || - "$line" =~ "SMART Status BAD" || "$line" =~ "failed SMART" || - "$line" =~ "drive failure" || "$line" =~ "bad sectors" || - "$line" =~ "sector reallocation" || "$line" =~ "uncorrectable error" || - "$line" =~ "media error" || "$line" =~ "not responding" && "$line" =~ "disk" || - "$line" =~ "SSD life critical" || "$line" =~ "SSD wear" && "$line" =~ "critical" ]]; then - - # Extract device name with improved pattern matching - DISK=$(echo "$line" | grep -oE "/dev/[a-zA-Z0-9]+" || - echo "$line" | grep -oE "sd[a-z][0-9]*" || - echo "$line" | grep -oE "nvme[0-9]+n[0-9]+" || - echo "$line" | grep -oE "ata[0-9]+" || - echo "unknown") - - # Try to determine failure type - FAILURE_TYPE="hardware" - if [[ "$line" =~ "SMART" ]]; then - FAILURE_TYPE="SMART" - - # Try to extract SMART attribute if available - SMART_ATTR=$(echo "$line" | grep -oP "Attribute \K[^:]*" || - echo "$line" | grep -oP "SMART attribute \K[^:]*" || - echo "") - if [[ -n "$SMART_ATTR" ]]; then - SMART_ATTR=" (Attribute: $SMART_ATTR)" - fi - elif [[ "$line" =~ "bad sectors" || "$line" =~ "sector reallocation" ]]; then - FAILURE_TYPE="bad sectors" - elif [[ "$line" =~ "SSD" ]]; then - FAILURE_TYPE="SSD wear" - elif [[ "$line" =~ "not responding" ]]; then - FAILURE_TYPE="unresponsive" - fi - - # Send notification with enhanced information - send_notification "🚨 $(translate "CRITICAL: Disk failure detected") ($FAILURE_TYPE): $DISK$SMART_ATTR" - - event_processed=true - fi - fi - - - - # Snapshot failed (CRITICAL) - if [[ "$line" =~ "snapshot" ]] && [[ "$snapshot_fail" -eq 1 ]] && [[ "$line" =~ "error" || "$line" =~ "fail" || "$line" =~ "unable to" || "$line" =~ "cannot" ]] && [[ "$event_processed" = false ]]; then - - # Extract VM/CT ID - VM_ID=$(echo "$line" | grep -oP 'TASK \K[0-9]+') - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'VM \K[0-9]+') - fi - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'CT \K[0-9]+') - fi - - # Extract snapshot ID - SNAPSHOT_ID=$(echo "$line" | grep -oP 'snapshot \K[a-zA-Z0-9_-]+') - if [[ -z "$SNAPSHOT_ID" ]]; then - SNAPSHOT_ID=$(echo "$line" | grep -oP 'snap\K[a-zA-Z0-9_-]+') - fi - - - # Try to determine error reason - ERROR_REASON="" - if [[ "$line" =~ "no space" || "$line" =~ "space exhausted" || "$line" =~ "out of space" ]]; then - ERROR_REASON=" (No space left)" - elif [[ "$line" =~ "timeout" ]]; then - ERROR_REASON=" (Operation timed out)" - elif [[ "$line" =~ "already exists" ]]; then - ERROR_REASON=" (Snapshot already exists)" - elif [[ "$line" =~ "locked" || "$line" =~ "lock" ]]; then - ERROR_REASON=" (Resource locked)" - elif [[ "$line" =~ "permission" ]]; then - ERROR_REASON=" (Permission denied)" - elif [[ "$line" =~ "quorum" ]]; then - ERROR_REASON=" (Quorum error)" - fi - - # Format the notification message - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - if [[ -n "$SNAPSHOT_ID" ]]; then - send_notification "🚨 $(translate "CRITICAL: Snapshot failed for:") $NAME (ID: $SNAPSHOT_ID)$ERROR_REASON" - else - send_notification "🚨 $(translate "CRITICAL: Snapshot failed for:") $NAME$ERROR_REASON" - fi - else - if [[ -n "$SNAPSHOT_ID" ]]; then - send_notification "🚨 $(translate "CRITICAL: Snapshot failed") (ID: $SNAPSHOT_ID)$ERROR_REASON" - else - send_notification "🚨 $(translate "CRITICAL: Snapshot failed")$ERROR_REASON" - fi - fi - - event_processed=true - fi - - - - # Backup failed (CRITICAL) - if [[ "$backup_fail" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # Expanded pattern matching for backup failures - if [[ "$line" =~ "backup" && ("$line" =~ "error" || "$line" =~ "fail" || "$line" =~ "unable to" || "$line" =~ "cannot" || "$line" =~ "abort") ]]; then - - # Extract VM/CT ID - VM_ID=$(echo "$line" | grep -oP 'TASK \K[0-9]+') - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'VM \K[0-9]+') - fi - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'CT \K[0-9]+') - fi - - # Extract backup target - BACKUP_TARGET=$(echo "$line" | grep -oP 'to ["\047]?\K[a-zA-Z0-9_-]+') - if [[ -z "$BACKUP_TARGET" ]]; then - BACKUP_TARGET=$(echo "$line" | grep -oP 'storage ["\047]?\K[a-zA-Z0-9_-]+') - fi - - - # Try to determine error reason - ERROR_REASON="" - if [[ "$line" =~ "no space" || "$line" =~ "space exhausted" || "$line" =~ "out of space" ]]; then - ERROR_REASON=" (No space left)" - elif [[ "$line" =~ "timeout" ]]; then - ERROR_REASON=" (Operation timed out)" - elif [[ "$line" =~ "connection" && "$line" =~ "refused" ]]; then - ERROR_REASON=" (Connection refused)" - elif [[ "$line" =~ "network" ]]; then - ERROR_REASON=" (Network error)" - elif [[ "$line" =~ "permission" ]]; then - ERROR_REASON=" (Permission denied)" - elif [[ "$line" =~ "locked" || "$line" =~ "lock" ]]; then - ERROR_REASON=" (Resource locked)" - elif [[ "$line" =~ "quorum" ]]; then - ERROR_REASON=" (Quorum error)" - elif [[ "$line" =~ "already running" ]]; then - ERROR_REASON=" (Another backup is already running)" - fi - - # Format the notification message - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - if [[ -n "$BACKUP_TARGET" ]]; then - send_notification "🚨 $(translate "CRITICAL: Backup failed for:") $NAME (Target: $BACKUP_TARGET)$ERROR_REASON" - else - send_notification "🚨 $(translate "CRITICAL: Backup failed for:") $NAME$ERROR_REASON" - fi - else - if [[ -n "$BACKUP_TARGET" ]]; then - send_notification "🚨 $(translate "CRITICAL: Backup failed") (Target: $BACKUP_TARGET)$ERROR_REASON" - else - send_notification "🚨 $(translate "CRITICAL: Backup failed")$ERROR_REASON" - fi - fi - - event_processed=true - fi - fi - - - - # Failed authentication attempt (CRITICAL) - if [[ "$auth_fail" -eq 1 ]] && [[ "$event_processed" = false ]]; then - if [[ "$line" =~ "authentication failure" || "$line" =~ "auth fail" || "$line" =~ "login failed" || - "$line" =~ "Failed password" || "$line" =~ "Invalid user" || "$line" =~ "failed login" || - "$line" =~ "authentication error" || ( "$line" =~ "unauthorized" && "$line" =~ "access" ) ]]; then - - # Extract username - USER=$(echo "$line" | grep -oP 'user=\K[^ ]+') - if [[ -z "$USER" ]]; then - USER=$(echo "$line" | grep -oP 'user \K[^ ]+') - fi - if [[ -z "$USER" ]]; then - USER=$(echo "$line" | grep -oP 'for user \K[^ ]+') - fi - if [[ -z "$USER" ]]; then - USER=$(echo "$line" | grep -oP 'for invalid user \K[^ ]+') - fi - if [[ -z "$USER" ]]; then - USER=$(echo "$line" | grep -oP 'for \K[^ ]+' | grep -v "invalid") - fi - if [[ -z "$USER" ]]; then - USER="unknown" - fi - - # Extract IP address - IP=$(echo "$line" | grep -oP 'rhost=\K[^ ]+') - if [[ -z "$IP" ]]; then - IP=$(echo "$line" | grep -oP 'from \K[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+') - fi - if [[ -z "$IP" ]]; then - IP=$(echo "$line" | grep -oP 'from \K[0-9a-f:]+') - fi - if [[ -z "$IP" ]]; then - IP=$(echo "$line" | grep -oP 'IP: \K[^ ]+') - fi - if [[ -z "$IP" ]]; then - IP="unknown" - fi - - - # Try to determine authentication service - SERVICE="system" - if [[ "$line" =~ "sshd" ]]; then - SERVICE="SSH" - elif [[ "$line" =~ "pvedaemon" || "$line" =~ "pveproxy" ]]; then - SERVICE="Proxmox Web UI" - elif [[ "$line" =~ "nginx" || "$line" =~ "apache" ]]; then - SERVICE="Web Server" - elif [[ "$line" =~ "smtp" || "$line" =~ "mail" ]]; then - SERVICE="Mail" - elif [[ "$line" =~ "ftp" ]]; then - SERVICE="FTP" - fi - - # Try to extract authentication method if available - AUTH_METHOD="" - if [[ "$line" =~ "password" ]]; then - AUTH_METHOD=" (Password auth)" - elif [[ "$line" =~ "publickey" ]]; then - AUTH_METHOD=" (Public key auth)" - elif [[ "$line" =~ "keyboard-interactive" ]]; then - AUTH_METHOD=" (Interactive auth)" - elif [[ "$line" =~ "PAM" ]]; then - AUTH_METHOD=" (PAM auth)" - fi - - # Count failed attempts from this IP if possible - ATTEMPT_COUNT="" - if [[ -n "$IP" && "$IP" != "unknown" ]]; then - # Use journalctl to count recent failed attempts from this IP - if command -v journalctl &>/dev/null; then - COUNT=$(journalctl -q --since "1 hour ago" | grep -c "$IP") - if [[ $COUNT -gt 1 ]]; then - ATTEMPT_COUNT=" ($COUNT attempts in the last hour)" - fi - fi - fi - - # Send notification with enhanced information - send_notification "🚨 $(translate "CRITICAL: Failed authentication attempt:") $USER from $IP - $SERVICE$AUTH_METHOD$ATTEMPT_COUNT" - - event_processed=true - fi - fi - - - - # Firewall issue (CRITICAL) - if [[ "$line" =~ "firewall" && "$firewall_issue" -eq 1 && - ( "$line" =~ "error" || "$line" =~ "block" || "$line" =~ "reject" || - "$line" =~ "drop" || "$line" =~ "denied" || "$line" =~ "fail" || "$line" =~ "invalid" ) && - "$event_processed" = false ]]; then - - - # Try to determine the type of firewall issue - ISSUE_TYPE="issue" - if [[ "$line" =~ "error" ]]; then - ISSUE_TYPE="configuration error" - elif [[ "$line" =~ "block" || "$line" =~ "denied" ]]; then - ISSUE_TYPE="blocked connection" - elif [[ "$line" =~ "reject" ]]; then - ISSUE_TYPE="rejected connection" - elif [[ "$line" =~ "drop" ]]; then - ISSUE_TYPE="dropped packet" - elif [[ "$line" =~ "invalid" ]]; then - ISSUE_TYPE="invalid rule" - fi - - # Extract source IP - SRC_IP=$(echo "$line" | grep -oP 'SRC=\K[0-9.]+') - if [[ -z "$SRC_IP" ]]; then - SRC_IP=$(echo "$line" | grep -oP 'from \K[0-9.]+') - fi - if [[ -z "$SRC_IP" ]]; then - SRC_IP=$(echo "$line" | grep -oP 'source \K[0-9.]+') - fi - - # Extract destination IP - DST_IP=$(echo "$line" | grep -oP 'DST=\K[0-9.]+') - if [[ -z "$DST_IP" ]]; then - DST_IP=$(echo "$line" | grep -oP 'to \K[0-9.]+') - fi - if [[ -z "$DST_IP" ]]; then - DST_IP=$(echo "$line" | grep -oP 'destination \K[0-9.]+') - fi - - - # Try to extract port information if available - PORT_INFO="" - SRC_PORT=$(echo "$line" | grep -oP 'SPT=\K[0-9]+' || echo "") - DST_PORT=$(echo "$line" | grep -oP 'DPT=\K[0-9]+' || echo "") - if [[ -n "$SRC_PORT" && -n "$DST_PORT" ]]; then - PORT_INFO=" (Port $SRC_PORT → $DST_PORT)" - elif [[ -n "$DST_PORT" ]]; then - PORT_INFO=" (Port $DST_PORT)" - fi - - # Try to extract protocol if available - PROTO=$(echo "$line" | grep -oP 'PROTO=\K[A-Za-z]+' || - echo "$line" | grep -oP 'protocol \K[A-Za-z]+' || echo "") - if [[ -n "$PROTO" ]]; then - PROTO=" $PROTO" - fi - - # Try to extract interface if available - IFACE=$(echo "$line" | grep -oP 'IN=\K[^ ]+' || - echo "$line" | grep -oP 'OUT=\K[^ ]+' || - echo "$line" | grep -oP 'on \K[^ ]+' || echo "") - if [[ -n "$IFACE" ]]; then - IFACE=" on $IFACE" - fi - - # Format the notification message - if [[ -n "$SRC_IP" && -n "$DST_IP" ]]; then - send_notification "🚨 $(translate "CRITICAL: Firewall ${ISSUE_TYPE}:") $SRC_IP → $DST_IP$PORT_INFO$PROTO$IFACE" - elif [[ -n "$SRC_IP" ]]; then - send_notification "🚨 $(translate "CRITICAL: Firewall ${ISSUE_TYPE}:") from $SRC_IP$PORT_INFO$PROTO$IFACE" - elif [[ -n "$DST_IP" ]]; then - send_notification "🚨 $(translate "CRITICAL: Firewall ${ISSUE_TYPE}:") to $DST_IP$PORT_INFO$PROTO$IFACE" - else - # Extract a more concise message from the line - CONCISE_MSG=$(echo "$line" | sed -E 's/.*firewall[^:]*: ?//i' | cut -c 1-100) - send_notification "🚨 $(translate "CRITICAL: Firewall ${ISSUE_TYPE}:") $CONCISE_MSG" - fi - - event_processed=true - fi - - - - - # Network interface recovery handler - if [[ "$network_down" -eq 1 ]] && [[ "$event_processed" = false ]]; then - if [[ "$line" =~ (eth[0-9]+|eno[0-9]+|enp[0-9]+s[0-9]+|wlan[0-9]+) ]]; then - IFACE="${BASH_REMATCH[1]}" - - # Detect interface going down - if [[ "$line" =~ "link down" || "$line" =~ "disconnected" || "$line" =~ "no carrier" || "$line" =~ "failure" ]]; then - # Mark interface as down and store the timestamp - IFACE_DOWN["$IFACE"]=true - IFACE_DOWN_TIME["$IFACE"]="$(date +%s)" - fi - - # Detect interface recovery - if [[ "$line" =~ "link up" || "$line" =~ "activated" ]]; then - if [[ "${IFACE_DOWN[$IFACE]}" == true ]]; then - RESTORE_TIME=$(date +%s) - START_TIME=${IFACE_DOWN_TIME[$IFACE]} - DURATION=$((RESTORE_TIME - START_TIME)) - - # Check if this is the default route interface - PRIMARY="" - if ip route | grep -q "default.*$IFACE"; then - PRIMARY=" (PRIMARY INTERFACE)" - fi - - # Send notification after connection is restored - send_notification "$(translate '✅ Network connection was lost and has been restored on') $IFACE$PRIMARY. $(translate 'Downtime duration'): ${DURATION}s" - - # Clean up the interface state - unset IFACE_DOWN["$IFACE"] - unset IFACE_DOWN_TIME["$IFACE"] - event_processed=true - fi - fi - fi - fi - - - - - # Split-brain detected (CRITICAL) - if [[ "$split_brain" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # Expanded pattern matching for split-brain detection - if [[ "$line" =~ "Split-Brain" || "$line" =~ "split brain" || "$line" =~ "split-brain" || - ( "$line" =~ "fencing" && "$line" =~ "required" ) || - ( "$line" =~ "cluster" && "$line" =~ "partition" ) ]]; then - - - NODES=$(echo "$line" | grep -oP 'nodes: \K[^.]+') - if [[ -z "$NODES" ]]; then - NODES=$(echo "$line" | grep -oP 'between \K[^.]+') - fi - if [[ -n "$NODES" ]]; then - NODES=" (Affected nodes: $NODES)" - fi - - # Try to extract fence status if available - FENCE_INFO="" - if [[ "$line" =~ "fencing" ]]; then - if [[ "$line" =~ "successful" ]]; then - FENCE_INFO=" (Fencing successful)" - elif [[ "$line" =~ "failed" ]]; then - FENCE_INFO=" (Fencing failed)" - else - FENCE_INFO=" (Fencing required)" - fi - fi - - # Send notification with enhanced information - send_notification "🚨 $(translate "CRITICAL: Split-brain detected in cluster")$NODES$FENCE_INFO - $(translate "Manual intervention required!")" - - event_processed=true - fi - fi - - - - # Node disconnected from cluster (CRITICAL) - if [[ "$node_disconnect" -eq 1 ]] && [[ "$event_processed" = false ]]; then - - # Expanded pattern matching for node disconnection - if [[ ( "$line" =~ "quorum" && "$line" =~ "lost" ) || - ( "$line" =~ "node" && "$line" =~ "left" ) || - ( "$line" =~ "node" && "$line" =~ "offline" ) || - ( "$line" =~ "connection" && "$line" =~ "lost" && "$line" =~ "node" ) ]]; then - - - # Extract node name with improved pattern matching - NODE=$(echo "$line" | grep -oP 'node \K[^ ,.]+') - if [[ -z "$NODE" ]]; then - NODE=$(echo "$line" | grep -oP 'Node \K[^ ,.]+') - fi - if [[ -z "$NODE" ]]; then - NODE=$(echo "$line" | grep -oP 'from \K[^ ,.]+') - fi - if [[ -z "$NODE" ]]; then - NODE="unknown" - fi - - - # Try to determine if quorum is still valid - QUORUM_STATUS="" - if [[ "$line" =~ "quorum" ]]; then - if [[ "$line" =~ "lost" ]]; then - QUORUM_STATUS=" (Quorum lost)" - elif [[ "$line" =~ "still" && "$line" =~ "valid" ]]; then - QUORUM_STATUS=" (Quorum still valid)" - fi - fi - - # Try to extract remaining nodes count if available - REMAINING_COUNT=$(echo "$line" | grep -oP 'remaining nodes: \K[0-9]+') - if [[ -z "$REMAINING_COUNT" ]]; then - REMAINING_COUNT=$(echo "$line" | grep -oP 'nodes left: \K[0-9]+') - fi - if [[ -n "$REMAINING_COUNT" ]]; then - REMAINING=" ($REMAINING_COUNT nodes remaining)" - fi - - - # Try to determine if this is expected or unexpected - EXPECTED="" - if [[ "$line" =~ "shutdown" || "$line" =~ "maintenance" ]]; then - EXPECTED=" (Planned)" - else - EXPECTED=" (Unexpected)" - fi - - # Send notification with enhanced information - send_notification "🚨 $(translate "CRITICAL: Node disconnected from cluster:") $NODE$QUORUM_STATUS$REMAINING$EXPECTED" - - event_processed=true - fi - fi - - - - - # System shutdown (NON-CRITICAL - with interval) - if [[ "$system_shutdown" -eq 1 ]] && (( current_time - last_shutdown_notification > resource_interval )); then - if [[ "$line" =~ "systemd-journald" && "$line" =~ "Journal stopped" ]]; then - - # No hay razón específica, pero podemos indicar que se detuvo el journal (lo último antes del apagado) - send_notification "⚠️ $(translate "System is shutting down")" - last_shutdown_notification=$current_time - - # Log the event - logger -t proxmox-notify "System is shutting down (journal stopped)" - - event_processed=true - fi - fi - - - - # System problem (CRITICAL) - if [[ "$system_problem" -eq 1 ]] && [[ "$event_processed" = false ]]; then - if [[ "$line" =~ "kernel panic" || "$line" =~ "segfault" || "$line" =~ "Out of memory" || - "$line" =~ "BUG:" || "$line" =~ "Call Trace:" || - "$line" =~ "Failed to start" || "$line" =~ "Unit .* failed" || - "$line" =~ "Service .* exited with" ]]; then - - # Extract possible service name or component if available - COMPONENT=$(echo "$line" | grep -oP 'Failed to start \K[^:]+' || - echo "$line" | grep -oP 'Unit \K[^ ]+' || - echo "$line" | grep -oP 'Service \K[^ ]+' || echo "unknown") - - # Format and send the notification - send_notification "🚨 $(translate "CRITICAL: System problem detected") ($COMPONENT)" - logger -t proxmox-notify "CRITICAL: System problem detected ($COMPONENT)" - - event_processed=true - fi - fi - - - - # User permission change (NON-CRITICAL - with interval) - if [[ "$user_permission_change" -eq 1 ]] && (( current_time - last_user_permission_notification > resource_interval )); then - if [[ "$line" =~ "set permissions" || "$line" =~ "user added to group" || "$line" =~ "user removed from group" || - "$line" =~ "ACL updated" || "$line" =~ "Role assigned" || "$line" =~ "Changed user permissions" ]]; then - - # Try to extract username - USER=$(echo "$line" | grep -oP 'user \K[^ ]+' || - echo "$line" | grep -oP 'for user \K[^ ]+' || - echo "$line" | grep -oP 'User \K[^ ]+' || echo "unknown") - - # Try to detect change type - ACTION="Permission change" - if [[ "$line" =~ "added to group" ]]; then - ACTION="Added to group" - elif [[ "$line" =~ "removed from group" ]]; then - ACTION="Removed from group" - elif [[ "$line" =~ "Role assigned" ]]; then - ACTION="Role assigned" - elif [[ "$line" =~ "ACL updated" ]]; then - ACTION="ACL updated" - fi - - send_notification "🔐 $(translate "User permission changed:") $USER ($ACTION)" - logger -t proxmox-notify "User permission changed: $USER ($ACTION)" - last_user_permission_notification=$current_time - event_processed=true - fi - fi - - - - # Network saturation (NON-CRITICAL - with interval) - if [[ "$network_saturation" -eq 1 ]] && (( current_time - last_network_saturation_notification > resource_interval )); then - if command -v ip &>/dev/null; then - saturated_ifaces="" - - # Loop over interfaces and look for rx/tx errors/drops - while read -r line; do - iface=$(echo "$line" | awk -F: '{print $2}' | xargs) - stats=$(ip -s link show "$iface" | awk '/RX:|TX:/ {getline; print}') - - rx_errors=$(echo "$stats" | awk 'NR==1 {print $3}') - tx_errors=$(echo "$stats" | awk 'NR==2 {print $3}') - rx_dropped=$(echo "$stats" | awk 'NR==1 {print $4}') - tx_dropped=$(echo "$stats" | awk 'NR==2 {print $4}') - - if (( rx_errors > 100 || tx_errors > 100 || rx_dropped > 100 || tx_dropped > 100 )); then - saturated_ifaces+="$iface (RX errors: $rx_errors, TX errors: $tx_errors, RX dropped: $rx_dropped, TX dropped: $tx_dropped), " - fi - done < <(ip -o link show | awk -F': ' '{print $2}') - - # Clean and notify - if [[ -n "$saturated_ifaces" ]]; then - saturated_ifaces="${saturated_ifaces%, }" - send_notification "⚠️ $(translate "WARNING: Network saturation or errors detected on:") $saturated_ifaces" - logger -t proxmox-notify "WARNING: Network saturation on: $saturated_ifaces" - last_network_saturation_notification=$current_time - fi - fi - fi - - - - # Automatic IP blocks (NON-CRITICAL - with interval) - if [[ "$ip_block" -eq 1 ]] && (( current_time - last_ip_block_notification > resource_interval )); then - if [[ "$line" =~ "fail2ban" || "$line" =~ "Banned IP" || "$line" =~ "Blocking IP" || - "$line" =~ "DROP" && "$line" =~ "SRC=" || - "$line" =~ "REJECT" && "$line" =~ "SRC=" ]]; then - - # Try to extract IP address - IP=$(echo "$line" | grep -oP 'SRC=\K[0-9.]+' || - echo "$line" | grep -oP 'from \K[0-9.]+' || - echo "$line" | grep -oP 'Banned IP \K[0-9.]+' || - echo "$line" | grep -oP 'Blocking IP \K[0-9.]+' || echo "unknown") - - # Detect source (Fail2ban, Firewall, etc.) - SOURCE="Firewall" - if [[ "$line" =~ "fail2ban" ]]; then - SOURCE="Fail2ban" - elif [[ "$line" =~ "pve-firewall" ]]; then - SOURCE="PVE Firewall" - fi - - send_notification "🔒 $(translate "Automatic IP block detected") ($IP - $SOURCE)" - logger -t proxmox-notify "IP block detected: $IP ($SOURCE)" - last_ip_block_notification=$current_time - event_processed=true - fi - fi - - - - # ===== NON-CRITICAL EVENTS (IMMEDIATE) ===== - - # VM/CT start (NON-CRITICAL but immediate) - if [[ "$vm_start" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # VM start detection - if [[ "$line" =~ "qmstart" && ! "$line" =~ "err" && ! "$line" =~ "fail" ]]; then - VM_ID=$(echo "$line" | grep -oP 'qmstart:\K[0-9]+' || - echo "$line" | grep -oP 'VM \K[0-9]+' || echo "") - - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - - # Try to extract additional information - EXTRA_INFO="" - - # Check if this is a template - if [[ "$line" =~ "template" ]]; then - EXTRA_INFO=" (Template)" - fi - - # Check if this is a restore or clone operation - if [[ "$line" =~ "restore" ]]; then - EXTRA_INFO=" (Restored)" - elif [[ "$line" =~ "clone" ]]; then - EXTRA_INFO=" (Cloned)" - fi - - send_notification "✅ $(translate "VM started successfully:") $NAME$EXTRA_INFO" - event_processed=true - fi - # LXC container start detection - elif [[ "$line" =~ "lxc-start" && ! "$line" =~ "err" && ! "$line" =~ "fail" ]] || - [[ "$line" =~ "Starting CT" && ! "$line" =~ "err" && ! "$line" =~ "fail" ]]; then - - CT_ID=$(echo "$line" | grep -oP 'lxc-start:\K[0-9]+' || - echo "$line" | grep -oP 'CT \K[0-9]+' || echo "") - - if [[ -n "$CT_ID" ]]; then - NAME=$(get_vm_name "$CT_ID") - - # Try to extract additional information - EXTRA_INFO="" - - # Check if this is a template - if [[ "$line" =~ "template" ]]; then - EXTRA_INFO=" (Template)" - fi - - # Check if this is a restore or clone operation - if [[ "$line" =~ "restore" ]]; then - EXTRA_INFO=" (Restored)" - elif [[ "$line" =~ "clone" ]]; then - EXTRA_INFO=" (Cloned)" - fi - - send_notification "✅ $(translate "Container started successfully:") $NAME$EXTRA_INFO" - event_processed=true - fi - fi - fi - - - - # VM/CT shutdown (NON-CRITICAL but immediate) - if [[ "$vm_shutdown" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # VM shutdown detection - if [[ "$line" =~ "qmstop" && ! "$line" =~ "err" && ! "$line" =~ "fail" ]]; then - VM_ID=$(echo "$line" | grep -oP 'qmstop:\K[0-9]+' || - echo "$line" | grep -oP 'VM \K[0-9]+' || echo "") - - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - - # Try to determine shutdown type - SHUTDOWN_TYPE="" - if [[ "$line" =~ "force" || "$line" =~ "kill" ]]; then - SHUTDOWN_TYPE=" (Forced)" - elif [[ "$line" =~ "suspend" ]]; then - SHUTDOWN_TYPE=" (Suspended)" - elif [[ "$line" =~ "hibernate" ]]; then - SHUTDOWN_TYPE=" (Hibernated)" - elif [[ "$line" =~ "timeout" ]]; then - SHUTDOWN_TYPE=" (Timeout)" - elif [[ "$line" =~ "acpi" ]]; then - SHUTDOWN_TYPE=" (ACPI shutdown)" - fi - - send_notification "✅ $(translate "VM stopped successfully:") $NAME$SHUTDOWN_TYPE" - event_processed=true - fi - # LXC container shutdown detection - elif [[ "$line" =~ "lxc-stop" && ! "$line" =~ "err" && ! "$line" =~ "fail" ]] || - [[ "$line" =~ "Stopping CT" && ! "$line" =~ "err" && ! "$line" =~ "fail" ]]; then - - CT_ID=$(echo "$line" | grep -oP 'lxc-stop:\K[0-9]+' || - echo "$line" | grep -oP 'CT \K[0-9]+' || echo "") - - if [[ -n "$CT_ID" ]]; then - NAME=$(get_vm_name "$CT_ID") - - # Try to determine shutdown type - SHUTDOWN_TYPE="" - if [[ "$line" =~ "force" || "$line" =~ "kill" ]]; then - SHUTDOWN_TYPE=" (Forced)" - elif [[ "$line" =~ "timeout" ]]; then - SHUTDOWN_TYPE=" (Timeout)" - fi - - send_notification "✅ $(translate "Container stopped successfully:") $NAME$SHUTDOWN_TYPE" - event_processed=true - fi - fi - fi - - - - # VM/CT restart (NON-CRITICAL but immediate) - if [[ "$vm_restart" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # VM restart detection - if [[ ("$line" =~ "qmreset" || "$line" =~ "qmreboot") && ! "$line" =~ "err" && ! "$line" =~ "fail" ]]; then - VM_ID=$(echo "$line" | grep -oP '(qmreset|qmreboot):\K[0-9]+' || - echo "$line" | grep -oP 'VM \K[0-9]+' || echo "") - - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - - # Try to determine restart type - RESTART_TYPE="" - if [[ "$line" =~ "qmreset" ]]; then - RESTART_TYPE=" (Hard reset)" - elif [[ "$line" =~ "force" || "$line" =~ "kill" ]]; then - RESTART_TYPE=" (Forced)" - elif [[ "$line" =~ "timeout" ]]; then - RESTART_TYPE=" (After timeout)" - elif [[ "$line" =~ "acpi" ]]; then - RESTART_TYPE=" (ACPI restart)" - fi - - send_notification "✅ $(translate "VM restarted successfully:") $NAME$RESTART_TYPE" - event_processed=true - fi - # LXC container restart detection - elif [[ "$line" =~ "lxc-restart" || "$line" =~ "Restarting CT" || - ("$line" =~ "lxc-stop" && "$line" =~ "lxc-start" && "$line" =~ "restart") ]] && - [[ ! "$line" =~ "err" && ! "$line" =~ "fail" ]]; then - - CT_ID=$(echo "$line" | grep -oP 'lxc-restart:\K[0-9]+' || - echo "$line" | grep -oP 'CT \K[0-9]+' || - echo "$line" | grep -oP 'lxc-(stop|start):\K[0-9]+' || echo "") - - if [[ -n "$CT_ID" ]]; then - NAME=$(get_vm_name "$CT_ID") - - # Try to determine restart type - RESTART_TYPE="" - if [[ "$line" =~ "force" || "$line" =~ "kill" ]]; then - RESTART_TYPE=" (Forced)" - elif [[ "$line" =~ "timeout" ]]; then - RESTART_TYPE=" (After timeout)" - fi - - send_notification "✅ $(translate "Container restarted successfully:") $NAME$RESTART_TYPE" - event_processed=true - fi - fi - fi - - - - # Snapshot completed (NON-CRITICAL but immediate) - if [[ "$line" =~ "snapshot" ]] && [[ "$snapshot_complete" -eq 1 ]] && [[ ! "$line" =~ "error" ]] && [[ "$event_processed" = false ]]; then - - # Additional pattern matching for completed snapshots - if [[ "$line" =~ "complete" || "$line" =~ "finished" || "$line" =~ "success" || ! "$line" =~ "fail" && ! "$line" =~ "unable" ]]; then - - # Extract VM/CT ID with improved pattern matching - VM_ID=$(echo "$line" | grep -oP 'TASK \K[0-9]+') - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'VM \K[0-9]+') - fi - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'CT \K[0-9]+') - fi - - - # Try to extract snapshot name/ID if available - SNAPSHOT_NAME=$(echo "$line" | grep -oP 'snapshot \K[a-zA-Z0-9_-]+' || - echo "$line" | grep -oP 'snap\K[a-zA-Z0-9_-]+' || - echo "$line" | grep -oP 'name: \K[a-zA-Z0-9_-]+' || echo "") - - # Try to extract snapshot size if available - SNAPSHOT_SIZE=$(echo "$line" | grep -oP 'size: \K[0-9.]+[KMGT]B' || - echo "$line" | grep -oP '[0-9.]+[KMGT]B' || echo "") - - # Try to extract duration if available - DURATION=$(echo "$line" | grep -oP 'duration: \K[0-9.]+s' || - echo "$line" | grep -oP 'in \K[0-9.]+s' || - echo "$line" | grep -oP 'took \K[0-9.]+s' || echo "") - - # Format additional information - ADDITIONAL_INFO="" - if [[ -n "$SNAPSHOT_NAME" ]]; then - ADDITIONAL_INFO+=" (Name: $SNAPSHOT_NAME" - - if [[ -n "$SNAPSHOT_SIZE" ]]; then - ADDITIONAL_INFO+=", Size: $SNAPSHOT_SIZE" - fi - - if [[ -n "$DURATION" ]]; then - ADDITIONAL_INFO+=", Duration: $DURATION" - fi - - ADDITIONAL_INFO+=")" - elif [[ -n "$SNAPSHOT_SIZE" || -n "$DURATION" ]]; then - ADDITIONAL_INFO+=" (" - - if [[ -n "$SNAPSHOT_SIZE" ]]; then - ADDITIONAL_INFO+="Size: $SNAPSHOT_SIZE" - - if [[ -n "$DURATION" ]]; then - ADDITIONAL_INFO+=", " - fi - fi - - if [[ -n "$DURATION" ]]; then - ADDITIONAL_INFO+="Duration: $DURATION" - fi - - ADDITIONAL_INFO+=")" - fi - - # Try to determine snapshot type - SNAPSHOT_TYPE="" - if [[ "$line" =~ "memory" || "$line" =~ "ram" ]]; then - SNAPSHOT_TYPE=" (With RAM)" - elif [[ "$line" =~ "disk-only" ]]; then - SNAPSHOT_TYPE=" (Disk only)" - fi - - # Format the notification message - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - send_notification "✅ $(translate "Snapshot completed for:") $NAME$ADDITIONAL_INFO$SNAPSHOT_TYPE" - else - send_notification "✅ $(translate "Snapshot completed")$ADDITIONAL_INFO$SNAPSHOT_TYPE" - fi - - event_processed=true - fi - fi - - - - # Backup completed (NON-CRITICAL but immediate) - if [[ "$line" =~ "backup" && "$backup_complete" -eq 1 && - ( "$line" =~ "successful" || "$line" =~ "complete" || "$line" =~ "finished" || "$line" =~ "success" ) && - ! "$line" =~ "error" && ! "$line" =~ "fail" && - "$event_processed" = false ]]; then - - # Extract VM/CT ID - VM_ID=$(echo "$line" | grep -oP 'TASK \K[0-9]+') - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'VM \K[0-9]+') - fi - if [[ -z "$VM_ID" ]]; then - VM_ID=$(echo "$line" | grep -oP 'CT \K[0-9]+') - fi - - # Extract backup target - BACKUP_TARGET=$(echo "$line" | grep -oP 'to ["\047]?\K[a-zA-Z0-9_-]+') - if [[ -z "$BACKUP_TARGET" ]]; then - BACKUP_TARGET=$(echo "$line" | grep -oP 'storage ["\047]?\K[a-zA-Z0-9_-]+') - fi - if [[ -z "$BACKUP_TARGET" ]]; then - BACKUP_TARGET=$(echo "$line" | grep -oP 'target ["\047]?\K[a-zA-Z0-9_-]+') - fi - - - # Try to extract backup size if available - BACKUP_SIZE=$(echo "$line" | grep -oP 'size: \K[0-9.]+[KMGT]B' || - echo "$line" | grep -oP '[0-9.]+[KMGT]B' || echo "") - - # Try to extract duration if available - DURATION=$(echo "$line" | grep -oP 'duration: \K[0-9.]+s' || - echo "$line" | grep -oP 'in \K[0-9.]+s' || - echo "$line" | grep -oP 'took \K[0-9.]+s' || echo "") - - # Try to extract compression rate if available - COMPRESSION=$(echo "$line" | grep -oP 'compression: \K[0-9.]+%' || - echo "$line" | grep -oP 'compressed: \K[0-9.]+%' || echo "") - - # Format additional information - ADDITIONAL_INFO="" - if [[ -n "$BACKUP_TARGET" || -n "$BACKUP_SIZE" || -n "$DURATION" || -n "$COMPRESSION" ]]; then - ADDITIONAL_INFO+=" (" - - if [[ -n "$BACKUP_TARGET" ]]; then - ADDITIONAL_INFO+="Target: $BACKUP_TARGET" - - if [[ -n "$BACKUP_SIZE" || -n "$DURATION" || -n "$COMPRESSION" ]]; then - ADDITIONAL_INFO+=", " - fi - fi - - if [[ -n "$BACKUP_SIZE" ]]; then - ADDITIONAL_INFO+="Size: $BACKUP_SIZE" - - if [[ -n "$DURATION" || -n "$COMPRESSION" ]]; then - ADDITIONAL_INFO+=", " - fi - fi - - if [[ -n "$DURATION" ]]; then - ADDITIONAL_INFO+="Duration: $DURATION" - - if [[ -n "$COMPRESSION" ]]; then - ADDITIONAL_INFO+=", " - fi - fi - - if [[ -n "$COMPRESSION" ]]; then - ADDITIONAL_INFO+="Compression: $COMPRESSION" - fi - - ADDITIONAL_INFO+=")" - fi - - # Try to determine backup type - BACKUP_TYPE="" - if [[ "$line" =~ "incremental" ]]; then - BACKUP_TYPE=" (Incremental)" - elif [[ "$line" =~ "differential" ]]; then - BACKUP_TYPE=" (Differential)" - elif [[ "$line" =~ "full" ]]; then - BACKUP_TYPE=" (Full)" - fi - - # Format the notification message - if [[ -n "$VM_ID" ]]; then - NAME=$(get_vm_name "$VM_ID") - send_notification "✅ $(translate "Backup completed for:") $NAME$ADDITIONAL_INFO$BACKUP_TYPE" - else - send_notification "✅ $(translate "Backup completed")$ADDITIONAL_INFO$BACKUP_TYPE" - fi - - event_processed=true - fi - - - - # System update completed (NON-CRITICAL but immediate) - if [[ "$update_complete" -eq 1 ]] && [[ "$event_processed" = false ]]; then - # Match various patterns that indicate a completed update - if [[ "$line" =~ "update" && ("$line" =~ "complete" || "$line" =~ "finished" || "$line" =~ "done" || "$line" =~ "success") && - ! "$line" =~ "error" && ! "$line" =~ "fail" && ! "$line" =~ "unable" ]]; then - - # Try to determine what was updated - update_type="system" - if [[ "$line" =~ "proxmox" || "$line" =~ "pve" ]]; then - update_type="Proxmox VE" - elif [[ "$line" =~ "kernel" ]]; then - update_type="kernel" - elif [[ "$line" =~ "package" ]]; then - update_type="package" - fi - - # Try to extract version information if available - version_info="" - if [[ "$line" =~ "version" ]]; then - version=$(echo "$line" | grep -oP 'version \K[0-9.]+' || - echo "$line" | grep -oP 'to \K[0-9.]+' || echo "") - if [[ -n "$version" ]]; then - version_info=" ($(translate "version") $version)" - fi - fi - - # Try to extract package count if available - package_count="" - if [[ "$line" =~ "package" ]]; then - count=$(echo "$line" | grep -oP '([0-9]+) package' || echo "") - if [[ -n "$count" ]]; then - package_count=" ($count $(translate "packages"))" - fi - fi - - # Try to get a list of updated packages if available - package_list="" - if [[ -f /var/log/apt/history.log ]]; then - # Get the most recent upgrade entry - recent_upgrade=$(tac /var/log/apt/history.log | grep -m 1 -A 20 "Upgrade:" | grep -v "End-Date:" | grep "Upgrade:") - if [[ -n "$recent_upgrade" ]]; then - # Extract package names and versions - packages=$(echo "$recent_upgrade" | grep -oP '[a-zA-Z0-9.-]+:[a-zA-Z0-9]+ $$[^)]+$$' | head -n 5) - if [[ -n "$packages" ]]; then - package_list=" - $(translate "Updated packages:") $(echo "$packages" | tr '\n' ', ' | sed 's/,$//')" - - # If there are more packages, indicate this - total_packages=$(echo "$recent_upgrade" | grep -oP '[a-zA-Z0-9.-]+:[a-zA-Z0-9]+ $$[^)]+$$' | wc -l) - if [[ $total_packages -gt 5 ]]; then - package_list="$package_list, ... ($(translate "and") $((total_packages-5)) $(translate "more"))" - fi - fi - fi - fi - - # Check if a reboot is required - reboot_required="" - if [[ -f /var/run/reboot-required ]]; then - reboot_required=" - ⚠️ $(translate "System restart required to complete the update")" - fi - - # Format the notification message - send_notification "✅ $(translate "${update_type} update completed")${version_info}${package_count}${package_list}${reboot_required}" - - event_processed=true - - # Log the event - logger -t proxmox-notify "${update_type} update completed" - fi - fi - - done - - # Si llegamos aquí, es porque tail -F terminó inesperadamente - sleep 5 - done -} - - -# Function: capture direct system events -capture_direct_events() { - - # Variables to control notification frequency - local last_load_notification=0 - local last_temp_notification=0 - local last_disk_space_notification=0 - local last_cpu_notification=0 - local last_ram_notification=0 - local last_update_notification=0 - - - local resource_interval=900 # 15 minutes for resources - local update_interval=86400 # 24 hours for updates - - - local disk_full_detected=false - - while true; do - current_time=$(date +%s) - - # ===== CRITICAL IMMEDIATE NOTIFICATION EVENTS ===== - - # Disk full (CRITICAL - immediate) - if [[ "$disk_full" -eq 1 ]]; then - # Check for disks that are completely full (100%) - full_disks=$(df -h | awk '$5 == "100%" {print $1 " (100% full)"}') - - # Check for disks that are nearly full (>=95%) - nearly_full_disks=$(df -h | awk '$5 >= "95%" && $5 < "100%" {print $1 " (" $5 " full)"}') - - # Handle completely full disks - if [[ -n "$full_disks" && "$disk_full_detected" = false ]]; then - # Format the output for better readability - formatted_full_disks=$(echo "$full_disks" | tr '\n' ', ' | sed 's/,$//' | sed 's/,/, /g') - - send_notification "🚨 $(translate "CRITICAL: Storage completely full:") $formatted_full_disks" - disk_full_detected=true - - # Log the event - logger -t proxmox-notify "CRITICAL: Storage completely full: $formatted_full_disks" - elif [[ -z "$full_disks" ]]; then - disk_full_detected=false - fi - - # Handle nearly full disks (separate notification) - if [[ -n "$nearly_full_disks" && "$disk_nearly_full_detected" = false ]]; then - # Format the output for better readability - formatted_nearly_full_disks=$(echo "$nearly_full_disks" | tr '\n' ', ' | sed 's/,$//' | sed 's/,/, /g') - - send_notification "⚠️ $(translate "WARNING: Storage nearly full:") $formatted_nearly_full_disks" - disk_nearly_full_detected=true - - # Log the event - logger -t proxmox-notify "WARNING: Storage nearly full: $formatted_nearly_full_disks" - elif [[ -z "$nearly_full_disks" ]]; then - disk_nearly_full_detected=false - fi - - # Check for inode usage (sometimes disks can be full of inodes but not space) - full_inodes="" - while read -r filesystem inodes_used inodes_total iuse_percent mounted_on; do - # Skip if the line doesn't have a valid percentage - if ! [[ "$iuse_percent" =~ ^[0-9]+%$ ]]; then - continue - fi - - # Extract percentage number without the % sign - percent_num=${iuse_percent/\%/} - - # Skip if percentage is less than 95 - if [[ $percent_num -lt 95 ]]; then - continue - fi - - # Skip certain Proxmox-specific filesystems that normally show high inode usage - # but don't represent a real problem - if [[ "$filesystem" =~ ^/dev/mapper/pve- || - "$filesystem" =~ ^/dev/pve/ || - "$mounted_on" =~ ^/var/lib/vz/root/ || - "$mounted_on" =~ ^/etc/pve/ || - "$mounted_on" == "/var/lib/vz" && "$percent_num" -lt 98 ]]; then - continue - fi - - # Skip tmpfs and devtmpfs filesystems - if [[ "$filesystem" == "tmpfs" || "$filesystem" == "devtmpfs" ]]; then - continue - fi - - # Skip if the filesystem has very few total inodes (less than 1000) - # This helps avoid alerts on small or special filesystems - if [[ $inodes_total -lt 1000 ]]; then - continue - fi - - # Get a more user-friendly name for the filesystem - fs_name="$filesystem" - if [[ "$mounted_on" != "/" ]]; then - fs_name="$mounted_on ($filesystem)" - fi - - # Add to our list of filesystems with high inode usage - full_inodes+="$fs_name ($iuse_percent inodos usados, $inodes_used/$inodes_total), " - done < <(df -i | grep -v "Filesystem" | awk '{print $1, $3, $2, $5, $6}') - - # Remove trailing comma and space if any - full_inodes=${full_inodes%, } - - if [[ -n "$full_inodes" && "$inode_full_detected" = false ]]; then - send_notification "⚠️ $(translate "WARNING: Inode usage critical:") $full_inodes" - inode_full_detected=true - - # Log the event - logger -t proxmox-notify "WARNING: Inode usage critical: $full_inodes" - elif [[ -z "$full_inodes" ]]; then - inode_full_detected=false - fi - fi - - - - # ===== NON-CRITICAL EVENTS WITH INTERVAL ===== - - # High system load (NON-CRITICAL - with interval) - if [[ "$system_load_high" -eq 1 ]]; then - # Get current load averages (1, 5, 15 minutes) - load_1=$(awk '{print $1}' /proc/loadavg) - load_5=$(awk '{print $2}' /proc/loadavg) - load_15=$(awk '{print $3}' /proc/loadavg) - - # Get number of CPU cores - if [[ -f /proc/cpuinfo ]]; then - cpu_cores=$(grep -c "^processor" /proc/cpuinfo) - else - # Default to 1 if we can't determine - cpu_cores=1 - fi - - # Calculate thresholds based on number of cores - warning_threshold=$(echo "$cpu_cores * 0.8" | bc -l) - critical_threshold=$(echo "$cpu_cores * 1.5" | bc -l) - - # Format load averages for display - load_info="1m: $load_1, 5m: $load_5, 15m: $load_15" - - # Check if load exceeds critical threshold - if (( $(echo "$load_1 > $critical_threshold" | bc -l) )) && - (( current_time - last_load_notification > resource_interval )); then - - # Get top processes consuming CPU - if command -v top &>/dev/null; then - top_processes=$(top -b -n 1 | head -n 12 | tail -n 5 | awk '{print $NF " (" $9 "% CPU)"}' | tr '\n' ', ' | sed 's/,$//') - process_info=" $(translate "Top processes:") $top_processes" - else - process_info="" - fi - - # Get memory usage - if [[ -f /proc/meminfo ]]; then - mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print $2}') - mem_available=$(grep "MemAvailable" /proc/meminfo | awk '{print $2}') - mem_used_percent=$(echo "scale=1; 100 - ($mem_available * 100 / $mem_total)" | bc -l) - memory_info=" $(translate "Memory usage:") ${mem_used_percent}%" - else - memory_info="" - fi - - send_notification "🚨 $(translate "CRITICAL: Extremely high system load:") $load_info ($(translate "on") $cpu_cores $(translate "cores"))$memory_info$process_info" - last_load_notification=$current_time - - # Log the event - logger -t proxmox-notify "CRITICAL: Extremely high system load: $load_info" - - # Check if load exceeds warning threshold - elif (( $(echo "$load_1 > $warning_threshold" | bc -l) )) && - (( current_time - last_load_notification > resource_interval )); then - - # Get memory usage - if [[ -f /proc/meminfo ]]; then - mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print $2}') - mem_available=$(grep "MemAvailable" /proc/meminfo | awk '{print $2}') - mem_used_percent=$(echo "scale=1; 100 - ($mem_available * 100 / $mem_total)" | bc -l) - memory_info=" $(translate "Memory usage:") ${mem_used_percent}%" - else - memory_info="" - fi - - send_notification "⚠️ $(translate "WARNING: High system load:") $load_info ($(translate "on") $cpu_cores $(translate "cores"))$memory_info" - last_load_notification=$current_time - - # Log the event - logger -t proxmox-notify "WARNING: High system load: $load_info" - fi - fi - - - - # Available updates (NON-CRITICAL - with daily interval) - if [[ "$update_available" -eq 1 ]] && (( current_time - last_update_notification > update_interval )); then - # Update package lists quietly - apt-get update -qq &>/dev/null - - # Count total upgradable packages - updates=$(apt list --upgradable 2>/dev/null | grep -v "Listing..." | wc -l) - - # Check for security updates specifically - security_updates=$(apt list --upgradable 2>/dev/null | grep -i security | wc -l) - - # Check for Proxmox VE updates specifically - proxmox_updates=$(apt list --upgradable 2>/dev/null | grep -E "^(proxmox-ve|pve-manager|pve-kernel|pve-container|pve-firewall|pve-ha-manager|pve-docs|pve-qemu-kvm|pve-storage|pve-cluster|pve-gui|pve-headers|pve-firmware|pve-zsync|pve-guest-common)" | wc -l) - - # Get Proxmox version information - current_pve_version=$(pveversion -v 2>/dev/null | grep -oP "pve-manager/\K[0-9]+\.[0-9]+" || echo "unknown") - - # Check if there's a new major Proxmox version available - new_pve_version="" - if [[ $proxmox_updates -gt 0 ]]; then - new_version_check=$(apt list --upgradable 2>/dev/null | grep "^pve-manager/" | grep -oP "pve-manager/\K[0-9]+\.[0-9]+" || echo "") - if [[ -n "$new_version_check" && "$new_version_check" != "$current_pve_version" ]]; then - new_pve_version="$new_version_check" - fi - fi - - # Get list of specific packages that have updates - if [[ $updates -gt 0 ]]; then - # Get a list of all upgradable packages (limited to 10 to avoid too long messages) - package_list=$(apt list --upgradable 2>/dev/null | grep -v "Listing..." | head -n 10 | awk -F/ '{print $1}' | tr '\n' ', ' | sed 's/,$//') - - # If there are more than 10 packages, indicate this - if [[ $updates -gt 10 ]]; then - package_list="$package_list, ... ($(translate "and") $((updates-10)) $(translate "more"))" - fi - - # Format the notification message - update_msg="ℹ️ $(translate "Updates available:") $updates" - - if [[ $security_updates -gt 0 ]]; then - update_msg="$update_msg ($(translate "including") $security_updates $(translate "security updates"))" - fi - - - # If there's a new Proxmox version, highlight it - if [[ -n "$new_pve_version" ]]; then - update_msg="🔄 $(translate "NEW PROXMOX VERSION AVAILABLE:") $new_pve_version ($(translate "current:") $current_pve_version) - - $update_msg" - elif [[ $proxmox_updates -gt 0 ]]; then - update_msg="🔄 $(translate "Proxmox updates available") ($proxmox_updates $(translate "packages")) - - $update_msg" - fi - - send_notification "$update_msg" - last_update_notification=$current_time - - # Log the event - logger -t proxmox-notify "Updates available: $updates packages" - fi - fi - - - - # Low disk space (NON-CRITICAL - with interval) - if [[ "$low_disk_space" -eq 1 ]] && (( current_time - last_disk_space_notification > resource_interval )); then - # Check partitions with critical space (95-99% usage) - critical_space=$(df -h | awk '$5 ~ /9[5-9]%/ && $5 != "100%" {print $1 " (" $5 " full, " $4 " free)"}') - - # Check partitions with warning space (90-94% usage) - warning_space=$(df -h | awk '$5 ~ /9[0-4]%/ {print $1 " (" $5 " full, " $4 " free)"}') - - # Check partitions with attention space (85-89% usage) - attention_space=$(df -h | awk '$5 ~ /8[5-9]%/ {print $1 " (" $5 " full, " $4 " free)"}') - - # Format messages for better readability - if [[ -n "$critical_space" ]]; then - critical_space=$(echo "$critical_space" | tr '\n' ', ' | sed 's/,$//' | sed 's/,/, /g') - fi - - if [[ -n "$warning_space" ]]; then - warning_space=$(echo "$warning_space" | tr '\n' ', ' | sed 's/,$//' | sed 's/,/, /g') - fi - - if [[ -n "$attention_space" ]]; then - attention_space=$(echo "$attention_space" | tr '\n' ', ' | sed 's/,$//' | sed 's/,/, /g') - fi - - # Build notification message - disk_space_msg="" - - if [[ -n "$critical_space" ]]; then - disk_space_msg+="🚨 $(translate "CRITICAL: Very low disk space:") $critical_space" - fi - - if [[ -n "$warning_space" ]]; then - if [[ -n "$disk_space_msg" ]]; then - disk_space_msg+=" - - " - fi - disk_space_msg+="⚠️ $(translate "WARNING: Low disk space:") $warning_space" - fi - - if [[ -n "$attention_space" && -z "$critical_space" && -z "$warning_space" ]]; then - # Only show attention level if no higher alerts are present - disk_space_msg+="ℹ️ $(translate "ATTENTION: Disk space getting low:") $attention_space" - fi - - # Send notification if any space issues were detected - if [[ -n "$disk_space_msg" ]]; then - send_notification "$disk_space_msg" - last_disk_space_notification=$current_time - - # Log the event - logger -t proxmox-notify "Low disk space detected" - - # Suggest cleanup options for Proxmox - if [[ -d /var/lib/vz/dump || -d /var/lib/vz/template ]]; then - cleanup_msg="$(translate "TIP: Consider cleaning up old backups with:") 'rm -f /var/lib/vz/dump/vzdump-*.tar' $(translate "or old templates with:") 'rm -f /var/lib/vz/template/cache/*.tar.gz'" - send_notification "$cleanup_msg" - fi - fi - fi - - - - # High CPU usage (NON-CRITICAL - with interval) - if [[ "$cpu_high" -eq 1 ]] && (( current_time - last_cpu_notification > resource_interval )); then - # Get number of CPU cores - if [[ -f /proc/cpuinfo ]]; then - cpu_cores=$(grep -c "^processor" /proc/cpuinfo) - else - # Default to 1 if we can't determine - cpu_cores=1 - fi - - # Use mpstat if available, otherwise use top - if command -v mpstat &>/dev/null; then - cpu_usage=$(mpstat 1 1 | awk '/Average:/ {print 100 - $NF}') - else - cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}') - fi - - # Round to one decimal place - cpu_usage=$(printf "%.1f" $cpu_usage) - - # Get CPU temperature if available - cpu_temp="" - if command -v sensors &>/dev/null; then - # Try to get CPU temperature from sensors - cpu_temp=$(sensors | grep -i "core\|temp" | grep -oP '\+\K[0-9.]+°C' | sort -nr | head -n1) - elif [[ -f /sys/class/thermal/thermal_zone0/temp ]]; then - # Alternative method using sysfs - cpu_temp=$(echo "scale=1; $(cat /sys/class/thermal/thermal_zone0/temp) / 1000" | bc -l) - cpu_temp="${cpu_temp}°C" - fi - - # Add temperature info if available - temp_info="" - if [[ -n "$cpu_temp" ]]; then - temp_info=" ($(translate "Temperature:") $cpu_temp)" - fi - - # Get top CPU consuming processes - process_info="" - if command -v top &>/dev/null; then - top_processes=$(top -bn1 -o %CPU | head -n 12 | tail -n 5 | awk '{print $NF " (" $9 "%)"}' | tr '\n' ', ' | sed 's/,$//') - process_info=" - $(translate "Top processes:") $top_processes" - fi - - # Check for critical CPU usage (>95%) - if (( $(echo "$cpu_usage > 95" | bc -l) )); then - send_notification "🚨 $(translate "CRITICAL: Very high CPU usage:") ${cpu_usage}% ($(translate "on") $cpu_cores $(translate "cores"))${temp_info}${process_info}" - last_cpu_notification=$current_time - - # Log the event - logger -t proxmox-notify "CRITICAL: Very high CPU usage: ${cpu_usage}%" - - # Check for high CPU usage (>85%) - elif (( $(echo "$cpu_usage > 85" | bc -l) )); then - send_notification "⚠️ $(translate "WARNING: High CPU usage:") ${cpu_usage}% ($(translate "on") $cpu_cores $(translate "cores"))${temp_info}${process_info}" - last_cpu_notification=$current_time - - # Log the event - logger -t proxmox-notify "WARNING: High CPU usage: ${cpu_usage}%" - fi - - # Check for sustained moderate CPU usage (>70% for extended period) - # This requires tracking previous readings - if [[ -z "$cpu_usage_history" ]]; then - cpu_usage_history="$cpu_usage" - else - cpu_usage_history="$cpu_usage_history,$cpu_usage" - - # Keep only the last 5 readings - cpu_usage_history=$(echo "$cpu_usage_history" | awk -F, '{for(i=NF-4>1?NF-4:1; i<=NF; i++) printf("%s%s", $i, i==NF?"":",") }') - - # Calculate average of last readings - cpu_usage_avg=$(echo "$cpu_usage_history" | awk -F, '{sum=0; for(i=1; i<=NF; i++) sum+=$i; print sum/NF}') - - # If average is >70% and we haven't sent a notification recently - if (( $(echo "$cpu_usage_avg > 70" | bc -l) )) && - (( current_time - last_cpu_sustained_notification > resource_interval * 3 )); then - send_notification "ℹ️ $(translate "ATTENTION: Sustained CPU usage:") ${cpu_usage_avg}% $(translate "average over time") ($(translate "on") $cpu_cores $(translate "cores"))${temp_info}" - last_cpu_sustained_notification=$current_time - - # Log the event - logger -t proxmox-notify "ATTENTION: Sustained CPU usage: ${cpu_usage_avg}%" - fi - fi - fi - - - - # High RAM usage (NON-CRITICAL - with interval) - if [[ "$ram_high" -eq 1 ]] && (( current_time - last_ram_notification > resource_interval )); then - # Get detailed memory information - total_ram=$(free -m | awk '/Mem:/ {print $2}') - used_ram=$(free -m | awk '/Mem:/ {print $3}') - free_ram=$(free -m | awk '/Mem:/ {print $4}') - shared_ram=$(free -m | awk '/Mem:/ {print $5}') - cache_ram=$(free -m | awk '/Mem:/ {print $6}') - available_ram=$(free -m | awk '/Mem:/ {print $7}') - - # Calculate percentages - ram_usage=$(echo "scale=1; ($total_ram - $available_ram) * 100 / $total_ram" | bc -l) - ram_usage_no_cache=$(echo "scale=1; ($used_ram - $cache_ram) * 100 / $total_ram" | bc -l) - - # Get swap information - total_swap=$(free -m | awk '/Swap:/ {print $2}') - used_swap=$(free -m | awk '/Swap:/ {print $3}') - - # Calculate swap percentage if swap exists - swap_info="" - if [[ $total_swap -gt 0 ]]; then - swap_percent=$(echo "scale=1; $used_swap * 100 / $total_swap" | bc -l) - swap_info=", $(translate "Swap:") ${swap_percent}% (${used_swap}MB/${total_swap}MB)" - fi - - # Format memory values for display - ram_info="${ram_usage}% (${used_ram}MB/${total_ram}MB)" - ram_info_detailed="Used: ${used_ram}MB, Free: ${free_ram}MB, Cache: ${cache_ram}MB, Available: ${available_ram}MB" - - # Get top memory consuming processes - process_info="" - if command -v ps &>/dev/null; then - top_processes=$(ps aux --sort=-%mem | head -n 6 | tail -n 5 | awk '{print $11 " (" int($4) "%)"}' | tr '\n' ', ' | sed 's/,$//') - process_info=" - $(translate "Top processes:") $top_processes" - fi - - # Check for critical RAM usage (>95%) - if (( $(echo "$ram_usage > 95" | bc -l) )); then - send_notification "🚨 $(translate "CRITICAL: Very high RAM usage:") ${ram_info}${swap_info} - ${ram_info_detailed}${process_info}" - last_ram_notification=$current_time - - # Log the event - logger -t proxmox-notify "CRITICAL: Very high RAM usage: ${ram_usage}%" - - # Check for high RAM usage (>85%) - elif (( $(echo "$ram_usage > 85" | bc -l) )); then - send_notification "⚠️ $(translate "WARNING: High RAM usage:") ${ram_info}${swap_info} - ${ram_info_detailed}${process_info}" - last_ram_notification=$current_time - - # Log the event - logger -t proxmox-notify "WARNING: High RAM usage: ${ram_usage}%" - - # Check for high RAM usage excluding cache (>80%) - # This is important because Linux uses free RAM for cache, but can free it when needed - elif (( $(echo "$ram_usage_no_cache > 80" | bc -l) )); then - send_notification "ℹ️ $(translate "ATTENTION: High RAM usage (excluding cache):") ${ram_usage_no_cache}%${swap_info} - ${ram_info_detailed}${process_info}" - last_ram_notification=$current_time - - # Log the event - logger -t proxmox-notify "ATTENTION: High RAM usage (excluding cache): ${ram_usage_no_cache}%" - fi - - # Check for high swap usage if swap exists and is being used - if [[ $total_swap -gt 0 && $used_swap -gt 0 ]]; then - # Only alert on high swap if we haven't already alerted on RAM - if (( $(echo "$swap_percent > 50" | bc -l) )) && - (( $(echo "$ram_usage <= 85" | bc -l) )) && - (( current_time - last_swap_notification > resource_interval )); then - send_notification "⚠️ $(translate "WARNING: High swap usage:") ${swap_percent}% (${used_swap}MB/${total_swap}MB) - ${ram_info_detailed}${process_info}" - last_swap_notification=$current_time - - # Log the event - logger -t proxmox-notify "WARNING: High swap usage: ${swap_percent}%" - fi - fi - fi - - - - # High temperature (NON-CRITICAL - with interval) - if [[ "$temp_high" -eq 1 ]] && (( current_time - last_temp_notification > resource_interval )); then - # Initialize variables - temp_detected=false - max_temp=0 - temp_sources="" - - # Method 1: Use 'sensors' command if available - if command -v sensors &>/dev/null; then - # Update sensors database if needed - if [[ ! -f /var/run/proxmox-notify-sensors-updated ]]; then - sensors-detect --auto &>/dev/null || true - touch /var/run/proxmox-notify-sensors-updated - fi - - # Try to get CPU temperature from various patterns - cpu_temp=$(sensors | grep -E 'Package id 0:|Core [0-9]+:|CPU:|Tdie:|Tctl:' | grep -oP '\+\K[0-9.]+°C|[0-9.]+°C' | sed 's/°C//' | sort -nr | head -n1) - - if [[ -n "$cpu_temp" && "$cpu_temp" != "0" ]]; then - temp_detected=true - if (( $(echo "$cpu_temp > $max_temp" | bc -l) )); then - max_temp=$cpu_temp - temp_sources="CPU" - fi - fi - - # Try to get motherboard/system temperature - mb_temp=$(sensors | grep -E 'MB Temperature|System Temp|Board Temp|Motherboard' | grep -oP '\+\K[0-9.]+°C|[0-9.]+°C' | sed 's/°C//' | sort -nr | head -n1) - - if [[ -n "$mb_temp" && "$mb_temp" != "0" ]]; then - temp_detected=true - if (( $(echo "$mb_temp > $max_temp" | bc -l) )); then - max_temp=$mb_temp - temp_sources="Motherboard" - elif (( $(echo "$mb_temp == $max_temp" | bc -l) )); then - temp_sources="$temp_sources, Motherboard" - fi - fi - - # Try to get GPU temperature if available - gpu_temp=$(sensors | grep -E 'GPU|VGA' | grep -oP '\+\K[0-9.]+°C|[0-9.]+°C' | sed 's/°C//' | sort -nr | head -n1) - - if [[ -n "$gpu_temp" && "$gpu_temp" != "0" ]]; then - temp_detected=true - if (( $(echo "$gpu_temp > $max_temp" | bc -l) )); then - max_temp=$gpu_temp - temp_sources="GPU" - elif (( $(echo "$gpu_temp == $max_temp" | bc -l) )); then - temp_sources="$temp_sources, GPU" - fi - fi - - # Try to get disk temperature if available - disk_temp=$(sensors | grep -E 'Drive Temp|Disk Temp|Storage Temp' | grep -oP '\+\K[0-9.]+°C|[0-9.]+°C' | sed 's/°C//' | sort -nr | head -n1) - - if [[ -n "$disk_temp" && "$disk_temp" != "0" ]]; then - temp_detected=true - if (( $(echo "$disk_temp > $max_temp" | bc -l) )); then - max_temp=$disk_temp - temp_sources="Disk" - elif (( $(echo "$disk_temp == $max_temp" | bc -l) )); then - temp_sources="$temp_sources, Disk" - fi - fi - fi - - # Method 2: Use sysfs thermal zones if sensors not available or no temp detected - if ! $temp_detected && [[ -d /sys/class/thermal ]]; then - for zone in /sys/class/thermal/thermal_zone*/temp; do - if [[ -f "$zone" ]]; then - zone_temp=$(echo "scale=1; $(cat "$zone") / 1000" | bc -l) - - if [[ -n "$zone_temp" && "$zone_temp" != "0" ]]; then - temp_detected=true - if (( $(echo "$zone_temp > $max_temp" | bc -l) )); then - max_temp=$zone_temp - # Try to get zone type - zone_dir=$(dirname "$zone") - if [[ -f "$zone_dir/type" ]]; then - zone_type=$(cat "$zone_dir/type") - temp_sources="$zone_type" - else - temp_sources="Thermal Zone" - fi - fi - fi - fi - done - fi - - # Method 3: Use ipmitool if available and no temp detected yet - if ! $temp_detected && command -v ipmitool &>/dev/null; then - ipmi_temp=$(ipmitool sdr type temperature 2>/dev/null | grep -i -E 'CPU|System|Ambient|Inlet|Exhaust' | head -1 | awk '{print $4}') - - if [[ -n "$ipmi_temp" && "$ipmi_temp" != "0" ]]; then - temp_detected=true - max_temp=$ipmi_temp - temp_sources="IPMI" - fi - fi - - # Method 4: Use hddtemp for disk temperatures if available - if command -v hddtemp &>/dev/null; then - for disk in /dev/sd[a-z]; do - if [[ -b "$disk" ]]; then - disk_temp=$(hddtemp "$disk" 2>/dev/null | grep -oP '[0-9.]+°C' | sed 's/°C//') - - if [[ -n "$disk_temp" && "$disk_temp" != "0" ]]; then - temp_detected=true - if (( $(echo "$disk_temp > $max_temp" | bc -l) )); then - max_temp=$disk_temp - disk_name=$(basename "$disk") - temp_sources="Disk $disk_name" - elif (( $(echo "$disk_temp == $max_temp" | bc -l) )); then - disk_name=$(basename "$disk") - temp_sources="$temp_sources, Disk $disk_name" - fi - fi - fi - done - fi - - # If we detected a temperature, check against thresholds - if $temp_detected && [[ -n "$max_temp" && "$max_temp" != "0" ]]; then - # Critical temperature (>90°C) - if (( $(echo "$max_temp > 90" | bc -l) )); then - send_notification "🚨 $(translate "CRITICAL: Dangerously high temperature:") ${max_temp}°C (${temp_sources})" - last_temp_notification=$current_time - - # Log the event - logger -t proxmox-notify "CRITICAL: Dangerously high temperature: ${max_temp}°C (${temp_sources})" - - # High temperature (>80°C) - elif (( $(echo "$max_temp > 80" | bc -l) )); then - send_notification "⚠️ $(translate "WARNING: High temperature:") ${max_temp}°C (${temp_sources})" - last_temp_notification=$current_time - - # Log the event - logger -t proxmox-notify "WARNING: High temperature: ${max_temp}°C (${temp_sources})" - - # Elevated temperature (>70°C) - elif (( $(echo "$max_temp > 70" | bc -l) )); then - send_notification "ℹ️ $(translate "ATTENTION: Elevated temperature:") ${max_temp}°C (${temp_sources})" - last_temp_notification=$current_time - - # Log the event - logger -t proxmox-notify "ATTENTION: Elevated temperature: ${max_temp}°C (${temp_sources})" - fi - fi - fi - - # Pause between checks - sleep 30 - done -} - - - - - - - - - - -# Function to start the notification service -start_notification_service() { - if [[ ! -f /etc/systemd/system/proxmox-telegram.service ]]; then - install_systemd_service - fi - - if systemctl is-active --quiet proxmox-telegram.service; then - whiptail --title "$(translate "Information")" \ - --msgbox "$(translate "The notification service is already running.")" 10 70 - else - systemctl start proxmox-telegram.service - if systemctl is-active --quiet proxmox-telegram.service; then - whiptail --title "$(translate "Started")" \ - --msgbox "$(translate "The service has been started successfully.")" 10 70 - else - whiptail --title "$(translate "Error")" \ - --msgbox "$(translate "Could not start the notification service.")" 10 70 - fi - fi -} - -# Function to stop the service -stop_notification_service() { - if [[ -f /etc/systemd/system/proxmox-telegram.service ]]; then - if systemctl is-active --quiet proxmox-telegram.service; then - systemctl stop proxmox-telegram.service - sleep 2 - fi - - if ! systemctl is-active --quiet proxmox-telegram.service; then - whiptail --title "$(translate "Stopped")" \ - --msgbox "$(translate "The service has been stopped successfully.")" 10 70 - else - whiptail --title "$(translate "Error")" \ - --msgbox "$(translate "Could not stop the notification service.")" 10 70 - fi - else - whiptail --title "$(translate "Information")" \ - --msgbox "$(translate "The notification service is not installed yet.")" 10 70 - fi -} - -# Function to check service status -check_service_status() { - clear - if [[ -f /etc/systemd/system/proxmox-telegram.service ]]; then - systemctl status proxmox-telegram.service - else - echo "$(translate "The service is not installed.")" - fi - echo - msg_success "$(translate "Press Enter to return to the menu...")" - read -r -} - -# Function to remove the systemd service -remove_systemd_service() { - if [[ -f /etc/systemd/system/proxmox-telegram.service ]]; then - if systemctl is-active --quiet proxmox-telegram.service; then - systemctl stop proxmox-telegram.service - fi - systemctl disable proxmox-telegram.service - rm -f /etc/systemd/system/proxmox-telegram.service - systemctl daemon-reexec - whiptail --title "$(translate "Removed")" \ - --msgbox "$(translate "The service has been removed successfully. You can reinstall it from the menu if desired.")" 10 70 - else - whiptail --title "$(translate "Information")" \ - --msgbox "$(translate "The service does not exist, nothing to remove.")" 10 70 - fi -} - - - -# Functions required by systemd -start_silent() { - mkdir -p "$PID_DIR" - - capture_journal_events > /dev/null 2>&1 & - echo $! > "$PID_DIR/journal.pid" - journal_pid=$! - - capture_direct_events > /dev/null 2>&1 & - echo $! > "$PID_DIR/direct.pid" - direct_pid=$! - - echo $$ > "$PID_DIR/service.pid" - - # Wait for both processes to finish (keeps systemd service alive) - wait $journal_pid - wait $direct_pid - -} - -stop_silent() { - - send_notification "⚠️ $(translate "System is shutting down on") $(hostname) at $(date)" - - kill $(cat "$PID_DIR/journal.pid" 2>/dev/null) 2>/dev/null - kill $(cat "$PID_DIR/direct.pid" 2>/dev/null) 2>/dev/null - kill $(cat "$PID_DIR/service.pid" 2>/dev/null) 2>/dev/null - rm -f "$PID_DIR"/*.pid -} - - - - -# Function to install the service as a systemd service -install_systemd_service() { - mkdir -p "$PID_DIR" - - - cat > "$WRAPPER_PATH" < /etc/systemd/system/proxmox-telegram.service <&1 1>&2 2>&3) - - if [[ $? -ne 0 ]]; then - exit 0 - fi - - case "$OPTION" in - 1) configure_telegram ;; - 2) configure_notifications ;; - 3) start_notification_service ;; - 4) stop_notification_service ;; - 5) check_service_status ;; - 6) remove_systemd_service ;; - 7) exit 0 ;; - esac - done -} - -case "$1" in - start_silent) start_silent ;; - stop_silent) stop_silent ;; - *) main_menu ;; -esac \ No newline at end of file diff --git a/scripts/test/Iso.sh b/scripts/test/Iso.sh deleted file mode 100644 index da01242c..00000000 --- a/scripts/test/Iso.sh +++ /dev/null @@ -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 < 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 - -} diff --git a/scripts/test/debug_disks.sh b/scripts/test/debug_disks.sh deleted file mode 100644 index b9f57976..00000000 --- a/scripts/test/debug_disks.sh +++ /dev/null @@ -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 - diff --git a/scripts/test/debug_disks1.sh b/scripts/test/debug_disks1.sh deleted file mode 100644 index a1e57dd8..00000000 --- a/scripts/test/debug_disks1.sh +++ /dev/null @@ -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 diff --git a/scripts/test/helpers-menu.sh b/scripts/test/helpers-menu.sh deleted file mode 100644 index ef178602..00000000 --- a/scripts/test/helpers-menu.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/test/id.sh b/scripts/test/id.sh deleted file mode 100644 index 3e50844b..00000000 --- a/scripts/test/id.sh +++ /dev/null @@ -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 diff --git a/scripts/test/repair_network_safe.sh b/scripts/test/repair_network_safe.sh deleted file mode 100644 index e894d9f4..00000000 --- a/scripts/test/repair_network_safe.sh +++ /dev/null @@ -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 diff --git a/scripts/test/res.sh b/scripts/test/res.sh deleted file mode 100644 index 0c26548e..00000000 --- a/scripts/test/res.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/test/res2.sh b/scripts/test/res2.sh deleted file mode 100644 index bde8e420..00000000 --- a/scripts/test/res2.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/test/res3.sh b/scripts/test/res3.sh deleted file mode 100644 index 05bbdebd..00000000 --- a/scripts/test/res3.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/test/t2pbs.sh b/scripts/test/t2pbs.sh deleted file mode 100644 index 174981d3..00000000 --- a/scripts/test/t2pbs.sh +++ /dev/null @@ -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 diff --git a/scripts/test/tpbs.sh b/scripts/test/tpbs.sh deleted file mode 100644 index 2f72e0b2..00000000 --- a/scripts/test/tpbs.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/scripts/test/vm/create_vm.sh b/scripts/test/vm/create_vm.sh deleted file mode 100644 index 01344319..00000000 --- a/scripts/test/vm/create_vm.sh +++ /dev/null @@ -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 diff --git a/scripts/test/vm/disk_selector.sh b/scripts/test/vm/disk_selector.sh deleted file mode 100644 index 82cb0fb9..00000000 --- a/scripts/test/vm/disk_selector.sh +++ /dev/null @@ -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 - - -} -# ========================================================== \ No newline at end of file diff --git a/scripts/test/vm/guest_agent_config.sh b/scripts/test/vm/guest_agent_config.sh deleted file mode 100644 index 92cb0f87..00000000 --- a/scripts/test/vm/guest_agent_config.sh +++ /dev/null @@ -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 -} - diff --git a/scripts/test/vm/select_linux_iso.sh b/scripts/test/vm/select_linux_iso.sh deleted file mode 100644 index 5ca2e497..00000000 --- a/scripts/test/vm/select_linux_iso.sh +++ /dev/null @@ -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 - - -} \ No newline at end of file diff --git a/scripts/test/vm/select_nas_iso.sh b/scripts/test/vm/select_nas_iso.sh deleted file mode 100644 index 2c79c96c..00000000 --- a/scripts/test/vm/select_nas_iso.sh +++ /dev/null @@ -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 -} diff --git a/scripts/test/vm/select_windows_iso.sh b/scripts/test/vm/select_windows_iso.sh deleted file mode 100644 index e46c3197..00000000 --- a/scripts/test/vm/select_windows_iso.sh +++ /dev/null @@ -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 -} - diff --git a/scripts/test/vm/synology4.sh b/scripts/test/vm/synology4.sh deleted file mode 100644 index 2edba0fd..00000000 --- a/scripts/test/vm/synology4.sh +++ /dev/null @@ -1,1252 +0,0 @@ -#!/usr/bin/env bash - -# ========================================================== -# ProxMenuX - Synology DSM VM Creator Script -# ========================================================== -# Author : MacRimi -# Copyright : (c) 2024 MacRimi -# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE) -# Version : 1.0 -# Last Updated: 13/03/2025 -# ========================================================== -# Description: -# This script automates the creation and configuration of a Synology DSM -# (DiskStation Manager) virtual machine (VM) in Proxmox VE. It simplifies the -# setup process by allowing both default and advanced configuration options. -# -# The script automates the complete VM creation process, including loader -# download, disk configuration, and VM boot setup. -# -# **Credits** -# This script is an original idea but incorporates ideas and elements from -# a similar script by user **tim104979** from the ProxmoxVE branch: -# (https://raw.githubusercontent.com/tim104979/ProxmoxVE/refs/heads/main/vm/synology-vm.sh) -# -# Copyright (c) Proxmox VE Helper-Scripts Community -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# -# ========================================================== - - -# 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 -# ========================================================== - -GEN_MAC="02" -for i in {1..5}; do - BYTE=$(printf "%02X" $((RANDOM % 256))) - GEN_MAC="${GEN_MAC}:${BYTE}" -done - -NEXTID=$(pvesh get /cluster/nextid 2>/dev/null || echo "100") -NAME="Synology VM" -IMAGES_DIR="/var/lib/vz/template/iso" -ERROR_FLAG=false - - - - - -function exit_script() { - clear - if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then - start_script - else - clear - exit - fi -} - - -# Define the header_info function at the beginning of the script - -function header_info() { - clear - show_proxmenux_logo - echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}" - echo -e "${BL}║ ║${CL}" - echo -e "${BL}║${YWB} Synology VM Creator ${BL}║${CL}" - echo -e "${BL}║ ║${CL}" - echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}" - echo -e -} -# ========================================================== - - - - - - -# ========================================================== -# start Script -# ========================================================== -function start_script() { - if (whiptail --backtitle "ProxMenuX" --title "SETTINGS" --yesno "$(translate "Use Default Settings?")" --no-button Advanced 10 58); then - header_info - echo -e "${DEF}Using Default Settings${CL}" - default_settings - else - header_info - echo -e "${CUS}Using Advanced Settings${CL}" - advanced_settings - fi -} -# ========================================================== - - - - -# ========================================================== -# Default Settings -# ========================================================== -function default_settings() { - VMID="$NEXTID" - FORMAT="" - MACHINE=" -machine q35" - BIOS_TYPE=" -bios ovmf" - DISK_CACHE="" - HN="Synology-DSM" - CPU_TYPE=" -cpu host" - CORE_COUNT="2" - RAM_SIZE="4096" - BRG="vmbr0" - MAC="$GEN_MAC" - VLAN="" - MTU="" - SERIAL_PORT="socket" - START_VM="no" - - echo -e " ${TAB}${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}" - echo -e " ${TAB}${DGN}Using Machine Type: ${BGN}q35${CL}" - echo -e " ${TAB}${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}" - echo -e " ${TAB}${DGN}Using Hostname: ${BGN}${HN}${CL}" - echo -e " ${TAB}${DGN}Using CPU Model: ${BGN}Host${CL}" - echo -e " ${TAB}${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}" - echo -e " ${TAB}${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}" - echo -e " ${TAB}${DGN}Using Bridge: ${BGN}${BRG}${CL}" - echo -e " ${TAB}${DGN}Using MAC Address: ${BGN}${MAC}${CL}" - echo -e " ${TAB}${DGN}Using VLAN: ${BGN}Default${CL}" - echo -e " ${TAB}${DGN}Using Interface MTU Size: ${BGN}Default${CL}" - echo -e " ${TAB}${DGN}Configuring Serial Port: ${BGN}${SERIAL_PORT}${CL}" - echo -e " ${TAB}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}" - echo -e - echo -e "${DEF}Creating a $NAME using the above default settings${CL}" - - sleep 1 - select_disk_type -} -# ========================================================== - - - - - -# ========================================================== -# advanced Settings -# ========================================================== -function advanced_settings() { - # VM ID Selection - while true; do - if VMID=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Virtual Machine ID")" 8 58 $NEXTID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z "$VMID" ]; then - VMID="$NEXTID" - fi - if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then - echo -e "${CROSS}${RD} ID $VMID is already in use${CL}" - sleep 1 - continue - fi - echo -e "${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" - break - else - exit_script - fi - done - - # Machine Type Selection - if MACH=$(whiptail --backtitle "ProxMenuX" --title "$(translate "MACHINE TYPE")" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ - "q35" "Machine q35" ON \ - "i440fx" "Machine i440fx" OFF \ - 3>&1 1>&2 2>&3); then - if [ $MACH = q35 ]; then - echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}" - FORMAT="" - MACHINE=" -machine q35" - else - echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}" - FORMAT=",efitype=4m" - MACHINE="" - fi - else - exit_script - fi - - # BIOS Type Selection - if BIOS=$(whiptail --backtitle "ProxMenuX" --title "$(translate "BIOS TYPE")" --radiolist --cancel-button Exit-Script "Choose BIOS Type" 10 58 2 \ - "ovmf" "UEFI (OVMF)" ON \ - "seabios" "SeaBIOS (Legacy)" OFF \ - 3>&1 1>&2 2>&3); then - if [ "$BIOS" = "seabios" ]; then - echo -e "${DGN}Using BIOS Type: ${BGN}SeaBIOS${CL}" - BIOS_TYPE=" -bios seabios" - else - echo -e "${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}" - BIOS_TYPE=" -bios ovmf" - fi - else - exit_script - fi - - # Hostname Selection - if VM_NAME=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Hostname")" 8 58 Synology-DSM --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $VM_NAME ]; then - HN="Synology-DSM" - echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" - else - HN=$(echo ${VM_NAME,,} | tr -d ' ') - echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" - fi - else - exit_script - fi - - # CPU Type Selection - if CPU_TYPE1=$(whiptail --backtitle "ProxMenuX" --title "$(translate "CPU MODEL")" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ - "1" "Host" ON \ - "0" "KVM64" OFF \ - 3>&1 1>&2 2>&3); then - if [ $CPU_TYPE1 = "1" ]; then - echo -e "${DGN}Using CPU Model: ${BGN}Host${CL}" - CPU_TYPE=" -cpu host" - else - echo -e "${DGN}Using CPU Model: ${BGN}KVM64${CL}" - CPU_TYPE="" - fi - else - exit_script - fi - - # Core Count Selection - if CORE_COUNT=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate CPU Cores")" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $CORE_COUNT ]; then - CORE_COUNT="2" - echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" - else - echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" - fi - else - exit_script - fi - - # RAM Size Selection - if RAM_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate RAM in MiB")" 8 58 4096 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $RAM_SIZE ]; then - RAM_SIZE="4096" - echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" - else - echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" - fi - else - exit_script - fi - - # Bridge Selection - if BRG=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Bridge")" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $BRG ]; then - BRG="vmbr0" - echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}" - else - echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}" - fi - else - exit_script - fi - - # MAC Address Selection - if MAC1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a MAC Address")" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $MAC1 ]; then - MAC="$GEN_MAC" - echo -e "${DGN}Using MAC Address: ${BGN}$MAC${CL}" - else - MAC="$MAC1" - echo -e "${DGN}Using MAC Address: ${BGN}$MAC1${CL}" - fi - else - exit_script - fi - - # VLAN Selection - if VLAN1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Vlan(leave blank for default)")" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $VLAN1 ]; then - VLAN1="Default" - VLAN="" - echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}" - else - VLAN=",tag=$VLAN1" - echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}" - fi - else - exit_script - fi - - # MTU Selection - if MTU1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Interface MTU Size (leave blank for default)")" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $MTU1 ]; then - MTU1="Default" - MTU="" - echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" - else - MTU=",mtu=$MTU1" - echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" - fi - else - exit_script - fi - - - - # Confirmation - if (whiptail --backtitle "ProxMenuX" --title "$(translate "ADVANCED SETTINGS COMPLETE")" --yesno "Ready to create a $NAME?" --no-button Do-Over 10 58); then - echo -e - echo -e "${CUS}Creating a $NAME using the above advanced settings${CL}" - sleep 1 - select_disk_type - else - header_info - sleep 1 - echo -e "${CUS}Using Advanced Settings${CL}" - advanced_settings - fi -} -# ========================================================== - - - - - -# ========================================================== -# Select Disk -# ========================================================== -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) - - EXIT_STATUS=$? - - if [[ $EXIT_STATUS -ne 0 ]]; then - clear - header_info - msg_error "Operation cancelled by user. Returning to start scrip..." - sleep 2 - if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then - start_script - else - clear - exit - fi - fi - - 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_loader -} - -# ========================================================== - - - - - - -# ========================================================== -# 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 - - - select_loader -} -# ========================================================== - - - - - - -# ========================================================== -# Select Loader -# ========================================================== -function select_loader() { - # Ensure the images directory exists - if [ ! -d "$IMAGES_DIR" ]; then - msg_info "Creating images directory" - mkdir -p "$IMAGES_DIR" - chmod 755 "$IMAGES_DIR" - msg_ok "Images directory created: $IMAGES_DIR" - fi - - # Create the loader selection menu - LOADER_OPTION=$(whiptail --backtitle "ProxMenuX" --title "SELECT LOADER" --menu "$(translate "Choose a loader for Synology DSM:")" 15 70 4 \ - "1" "AuxXxilium Arc Loader" \ - "2" "RedPill Loader (RROrg - RR)" \ - "3" "TinyCore RedPill Loader (PeterSuh-Q3 M-shell)" \ - "4" "Custom Loader Image (from $IMAGES_DIR)" \ - 3>&1 1>&2 2>&3) - - if [ -z "$LOADER_OPTION" ]; then - exit_script - fi - - case $LOADER_OPTION in - 1) - LOADER_TYPE="arc" - LOADER_NAME="AuxXxilium Arc" - LOADER_URL="https://github.com/AuxXxilium/arc/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - download_loader - ;; - 2) - LOADER_TYPE="redpill" - LOADER_NAME="RedPill RR" - LOADER_URL="https://github.com/RROrg/rr/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - download_loader - ;; - 3) - LOADER_TYPE="tinycore" - LOADER_NAME="TinyCore RedPill M-shell" - LOADER_URL="https://github.com/PeterSuh-Q3/tinycore-redpill/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - download_loader - ;; - 4) - LOADER_TYPE="custom" - LOADER_NAME="Custom Image" - LOADER_URL="https://xpenology.com/forum/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - select_custom_image - ;; - esac -} - -function select_custom_image() { - # Check if there are any images in the directory - IMAGES=$(find "$IMAGES_DIR" -type f -name "*.img" -o -name "*.iso" -o -name "*.qcow2" -o -name "*.vmdk" | sort) - - if [ -z "$IMAGES" ]; then - whiptail --title "$(translate "No Images Found")" --msgbox "No compatible images found in $IMAGES_DIR\n\nSupported formats: .img, .iso, .qcow2, .vmdk\n\nPlease add some images and try again." 15 70 - select_loader - fi - - # Create an array of image options for whiptail - IMAGE_OPTIONS=() - - while read -r img; do - filename=$(basename "$img") - filesize=$(du -h "$img" | cut -f1) - IMAGE_OPTIONS+=("$img" "$filesize") - done <<< "$IMAGES" - - # Let the user select an image - LOADER_FILE=$(whiptail --backtitle "ProxMenuX" --title "SELECT CUSTOM IMAGE" --menu "$(translate "Choose a custom image:")" 20 70 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3) - - if [ -z "$LOADER_FILE" ]; then - msg_error "No custom image selected" - exit_script - fi - - echo -e "${DGN}${TAB}Using Custom Image: ${BGN}$(basename "$LOADER_FILE")${CL}" - FILE=$(basename "$LOADER_FILE") -} -# ========================================================== - - - - - - - -# ========================================================== -# Download Loader -# ========================================================== -function download_loader() { - - echo -e "${DGN}${TAB}Retrieving the URL for the ${BGN}$LOADER_NAME loader${CL}" - - if [[ "$LOADER_TYPE" == "arc" || "$LOADER_TYPE" == "redpill" ]] && ! command -v unzip &> /dev/null; then - msg_info "Installing unzip..." - apt-get update -qq && apt-get install -y unzip -qq >/dev/null 2>&1 - if ! command -v unzip &> /dev/null; then - msg_error "Failed to install unzip" - sleep 2 - return 1 - fi - msg_ok "Installed unzip successfully." - fi - - case $LOADER_TYPE in - arc) - curl -s https://api.github.com/repos/AuxXxilium/arc/releases/latest \ - | grep "browser_download_url.*\.img\.zip" \ - | cut -d '"' -f 4 \ - | xargs wget -q --show-progress -O "$IMAGES_DIR/arc.img.zip" - - if [ -f "$IMAGES_DIR/arc.img.zip" ]; then - cd "$IMAGES_DIR" - unzip -q arc.img.zip - rm arc.img.zip - FILE="arc.img" - LOADER_FILE="$IMAGES_DIR/$FILE" - cd - > /dev/null - else - msg_error "Failed to download $LOADER_NAME loader" - sleep 1 - select_loader - fi - ;; - - redpill) - curl -s https://api.github.com/repos/RROrg/rr/releases/latest \ - | grep "browser_download_url.*\.img\.zip" \ - | cut -d '"' -f 4 \ - | xargs wget -q --show-progress -O "$IMAGES_DIR/rr.img.zip" - - if [ -f "$IMAGES_DIR/rr.img.zip" ]; then - cd "$IMAGES_DIR" - msg_info "Unzipping $LOADER_NAME loader. Please wait..." - unzip -qo rr.img.zip - msg_ok "Unzipped $LOADER_NAME loader successfully." - rm -f rr.img.zip - FILE="rr.img" - LOADER_FILE="$IMAGES_DIR/$FILE" - cd - > /dev/null - fi - - ;; - - tinycore) - curl -s https://api.github.com/repos/PeterSuh-Q3/tinycore-redpill/releases/latest \ - | grep "browser_download_url.*tinycore-redpill.v.*img.gz" \ - | cut -d '"' -f 4 \ - | xargs wget -q --show-progress -O "$IMAGES_DIR/tinycore.img.gz" - - if [ -f "$IMAGES_DIR/tinycore.img.gz" ]; then - cd "$IMAGES_DIR" - - msg_info "Unzipping $LOADER_NAME loader. Please wait..." - gunzip -f tinycore.img.gz 2> /dev/null - msg_ok "Unzipped $LOADER_NAME loader successfully." - FILE="tinycore.img" - LOADER_FILE="$IMAGES_DIR/$FILE" - cd - > /dev/null - - else - msg_error "Failed to download $LOADER_NAME loader" - sleep 1 - select_loader - - fi - ;; - esac - - msg_ok "Downloaded ${CL}${BL}${FILE}${CL} to ${IMAGES_DIR}" -} -# ======================================================= - - - - - -# ========================================================== -# Select UEFI Storage -# ========================================================== -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)}') - - ITEM=" Type: $TYPE Free: $FREE" - OFFSET=2 - if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then - MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) - fi - - STORAGE_MENU+=("$TAG" "$ITEM" "OFF") - done < <(pvesm status -content images | awk 'NR>1') - - VALID=$(pvesm status -content images | awk 'NR>1') - if [ -z "$VALID" ]; then - msg_error "Unable to detect a valid storage location for EFI disk." - - elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then - STORAGE=${STORAGE_MENU[0]} - - else - kill $SPINNER_PID > /dev/null - while [ -z "${STORAGE:+x}" ]; do - STORAGE=$(whiptail --backtitle "ProxMenuX" --title "EFI Disk Storage" --radiolist \ - "$(translate "Choose the storage volume for the EFI disk (4MB):\n\nUse Spacebar to select.")" \ - 16 $(($MSG_MAX_LENGTH + 23)) 6 \ - "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit - - done - - fi - - echo "$STORAGE" -} -# ========================================================== - - - - - -# ========================================================== -# Select Storage Loader -# ========================================================== -function select_storage_volume() { - local vmid=$1 - local purpose=$2 - 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)}') - - ITEM=" Type: $TYPE Free: $FREE" - OFFSET=2 - if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then - MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) - fi - - STORAGE_MENU+=("$TAG" "$ITEM" "OFF") - done < <(pvesm status -content images | awk 'NR>1') - - VALID=$(pvesm status -content images | awk 'NR>1') - if [ -z "$VALID" ]; then - msg_error "Unable to detect a valid storage location." - exit 1 - elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then - STORAGE=${STORAGE_MENU[0]} - else - while [ -z "${STORAGE:+x}" ]; do - STORAGE=$(whiptail --backtitle "ProxMenuX" --title "Storage Pools" --radiolist \ - "$(translate "Choose the storage volume for $purpose:\n\nUse Spacebar to select.")" \ - 16 $(($MSG_MAX_LENGTH + 23)) 6 \ - "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit - done - fi - - echo "$STORAGE" -} - - - - - - -# ========================================================== -# Create VM -# ========================================================== -function create_vm() { - - # Create the VM - 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 -onboot 1 -ostype l26 -scsihw virtio-scsi-pci \ - -serial0 socket - msg_ok "Create a $NAME" - - - -# Check if UEFI (OVMF) is being used =================== - if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then - - msg_info "Configuring EFI disk" - EFI_STORAGE=$(select_efi_storage $VMID) - EFI_DISK_NAME="vm-${VMID}-disk-efivars" - - # Determine storage type and extension - 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 - - STORAGE_TYPE=$(pvesm status -storage "$EFI_STORAGE" | awk 'NR>1 {print $2}') - EFI_DISK_ID="efidisk0" - - if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then - - if qm set "$VMID" -$EFI_DISK_ID "$EFI_STORAGE:4,efitype=4m,format=raw,pre-enrolled-keys=0" >/dev/null 2>&1; then - msg_ok "EFI disk created (raw) and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}" - else - msg_error "Failed to configure EFI disk" - ERROR_FLAG=true - fi - else - - EFI_DISK_NAME="vm-${VMID}-disk-efivars" - EFI_DISK_EXT="" - EFI_DISK_REF="" - - if pvesm alloc "$EFI_STORAGE" "$VMID" "$EFI_DISK_NAME" 4M >/dev/null 2>&1; then - if qm set "$VMID" -$EFI_DISK_ID "$EFI_STORAGE:${EFI_DISK_NAME},pre-enrolled-keys=0" >/dev/null 2>&1; then - msg_ok "EFI disk created and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}" - else - msg_error "Failed to configure EFI disk" - ERROR_FLAG=true - fi - else - msg_error "Failed to create EFI disk" - ERROR_FLAG=true - fi - fi - - - fi -# ========================================================== - - -# Select storage volume for loader ======================= - - LOADER_STORAGE=$(select_storage_volume $VMID "loader disk") - - - #Run the command in the background and capture its PID - qm importdisk $VMID ${LOADER_FILE} $LOADER_STORAGE > /tmp/import_log_$VMID.txt 2>&1 & - import_pid=$! - - # Show a simple progress indicator - echo -n "Importing loader disk: " - while kill -0 $import_pid 2>/dev/null; do - echo -n "." - sleep 2.5 - done - - wait $import_pid - rm -f /tmp/import_log_$VMID.txt - - IMPORTED_DISK=$(qm config $VMID | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f1) - - # If the disk was not imported correctly, show an error message but continue - if [ -z "$IMPORTED_DISK" ]; then - msg_error "Loader import failed. No disk detected." - ERROR_FLAG=true - else - msg_ok "Loader imported successfully to ${CL}${BL}$LOADER_STORAGE${GN}${CL}" - fi - - - - - STORAGE_TYPE=$(pvesm status -storage "$LOADER_STORAGE" | awk 'NR>1 {print $2}') - - if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then - # Recuperar el disco importado como unused - IMPORTED_DISK=$(qm config $VMID | grep -oP 'unused\d+:\K.*') - if [[ -n "$IMPORTED_DISK" ]]; then - if qm set "$VMID" -ide0 "$IMPORTED_DISK" >/dev/null 2>&1; then - msg_ok "Configured loader disk as ide0" - # Eliminar entrada unused - UNUSED_ID=$(qm config $VMID | grep -oP '(unused\d+):' | cut -d: -f1) - qm set "$VMID" -delete "$UNUSED_ID" >/dev/null 2>&1 - else - msg_error "Failed to assign loader disk to ide0" - ERROR_FLAG=true - fi - else - msg_error "Loader import failed. No disk detected." - ERROR_FLAG=true - fi - else - # Para LVM/ZFS, usar el nombre fijo como hasta ahora - DISK_NAME="vm-${VMID}-disk-0" - if qm set "$VMID" -ide0 "$LOADER_STORAGE:${DISK_NAME}" >/dev/null 2>&1; then - msg_ok "Configured loader disk as ide0" - else - msg_error "Failed to assign loader disk" - ERROR_FLAG=true - fi - fi - - - result=$(qm set "$VMID" -boot order=ide0 2>&1) - if [[ $? -eq 0 ]]; then - msg_ok "Loader configured as boot device." - else - ERROR_FLAG=true - fi - -# ========================================================== - -if [ "$DISK_TYPE" = "virtual" ]; then - if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then - msg_error "No virtual disks configured." - exit_script - fi - - DISK_INFO="" - CONSOLE_DISK_INFO="" - - for i in "${!VIRTUAL_DISKS[@]}"; do - IFS=':' read -r STORAGE SIZE <<< "${VIRTUAL_DISKS[$i]}" - - STORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}') - case $STORAGE_TYPE in - nfs | dir) - DISK_EXT=".raw" - DISK_REF="$VMID/" - ;; - *) - DISK_EXT="" - DISK_REF="" - ;; - esac - - - DISK_NUM=$((i+1)) - DISK_NAME="vm-${VMID}-disk-${DISK_NUM}${DISK_EXT}" - - - # Create virtual disk - STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}') - SATA_ID="sata$i" - DISK_NUM=$((i+1)) - - if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then - # Método alternativo para BTRFS o similares → usa format=raw directamente - msg_info "Creating virtual disk (format=raw) for $STORAGE_TYPE..." - if ! qm set "$VMID" -$SATA_ID "$STORAGE:$SIZE,format=raw" >/dev/null 2>&1; then - msg_error "Failed to assign disk $DISK_NUM ($SATA_ID) on $STORAGE" - ERROR_FLAG=true - continue - fi - else - # Método estándar para LVM/ZFS - msg_info "Allocating virtual disk for $STORAGE_TYPE..." - if ! pvesm alloc "$STORAGE" "$VMID" "$DISK_NAME" "$SIZE"G >/dev/null 2>&1; then - msg_error "Failed to allocate virtual disk $DISK_NUM" - ERROR_FLAG=true - continue - fi - if ! qm set "$VMID" -$SATA_ID "$STORAGE:${DISK_REF}$DISK_NAME" >/dev/null 2>&1; then - msg_error "Failed to configure virtual disk as $SATA_ID" - ERROR_FLAG=true - continue - fi - fi - - msg_ok "Configured virtual disk as $SATA_ID, ${SIZE}GB on ${CL}${BL}$STORAGE${CL} ${GN}" - - - # Add information to the description - DISK_INFO="${DISK_INFO}

Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE}

" - CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE} ($SATA_ID)\n" - done - - - - # HTML description -HTML_DESC="
- - - - - -
-ProxMenux Logo - -

Synology DSM VM

-

Created with ProxMenuX

-

Loader: $LOADER_NAME

-
- -

-Docs -Code -Loader -Ko-fi -

- -
-${DISK_INFO} -
-
" - - msg_info "Setting VM description" - if ! qm set "$VMID" -description "$HTML_DESC" >/dev/null 2>&1; then - msg_error "Failed to set VM description" - exit_script - fi - msg_ok "Configured VM description" - - -else - - - # Configure multiple passthrough disks - DISK_INFO="" - CONSOLE_DISK_INFO="" - - for i in "${!PASSTHROUGH_DISKS[@]}"; do - DISK="${PASSTHROUGH_DISKS[$i]}" - DISK_MODEL=$(lsblk -ndo MODEL "$DISK" | xargs) - DISK_SIZE=$(lsblk -ndo SIZE "$DISK" | xargs) - DISK_ID="sata$i" - - - result=$(qm set $VMID -${DISK_ID} ${DISK} 2>&1) - if [[ $? -eq 0 ]]; then - msg_ok "Configured disk ${CL}${BL}($DISK_MODEL $DISK_SIZE)${CL}${GN} as $DISK_ID" - fi - # Add information to the description - DISK_INFO="${DISK_INFO}

Passthrough Disk $((i+1)): $DISK ($DISK_MODEL $DISK_SIZE)

" - CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Passthrough Disk $((i+1)): $DISK ($DISK_MODEL $DISK_SIZE) (${DISK_ID})\n" - done - - - # HTML description -HTML_DESC="
- - - - - -
-ProxMenux Logo - -

Synology DSM VM

-

Created with ProxMenuX

-

Loader: $LOADER_NAME

-
- -

-Docs -Code -Loader -Ko-fi -

- -
-${DISK_INFO} -
-
" - - - result=$(qm set $VMID -description "$HTML_DESC" 2>&1) - if [[ $? -eq 0 ]]; then - msg_ok "Configured VM description" - fi - - -fi - - -if [ "$ERROR_FLAG" = true ]; then - msg_error "VM created with errors. Check configuration." -else -msg_success "$(translate "Completed Successfully!")" - -echo -e "${TAB}${GN}$(translate "Next Steps:")${CL}" -echo -e "${TAB}1. $(translate "Start the VM")" -echo -e "${TAB}2. $(translate "Open the VM console and wait for the loader to boot")" -echo -e "${TAB}3. $(translate "In the loader interface, follow the instructions to select your Synology model")" -echo -e "${TAB}4. $(translate "Complete the DSM installation wizard")" -echo -e "${TAB}5. $(translate "Find your device using https://finds.synology.com")" -echo -e - -msg_success "$(translate "Press Enter to return to the main menu...")" -read -r - -fi - -} - -# ========================================================== - - - -# ========================================================== -# Main execution -# ========================================================== -header_info -#echo -e "\n Loading..." -sleep 1 - -# Start script -if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then - start_script -else - clear - exit -fi - -# Create VM -create_vm - -# ========================================================== \ No newline at end of file diff --git a/scripts/test/vm/synology5.sh b/scripts/test/vm/synology5.sh deleted file mode 100644 index 5ebcf46d..00000000 --- a/scripts/test/vm/synology5.sh +++ /dev/null @@ -1,1252 +0,0 @@ -#!/usr/bin/env bash - -# ========================================================== -# ProxMenuX - Synology DSM VM Creator Script -# ========================================================== -# Author : MacRimi -# Copyright : (c) 2024 MacRimi -# License : (GPL-3.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE) -# Version : 1.0 -# Last Updated: 13/03/2025 -# ========================================================== -# Description: -# This script automates the creation and configuration of a Synology DSM -# (DiskStation Manager) virtual machine (VM) in Proxmox VE. It simplifies the -# setup process by allowing both default and advanced configuration options. -# -# The script automates the complete VM creation process, including loader -# download, disk configuration, and VM boot setup. -# -# **Credits** -# This script is an original idea but incorporates ideas and elements from -# a similar script by user **tim104979** from the ProxmoxVE branch: -# (https://raw.githubusercontent.com/tim104979/ProxmoxVE/refs/heads/main/vm/synology-vm.sh) -# -# Copyright (c) Proxmox VE Helper-Scripts Community -# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE -# -# ========================================================== - - -# 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 -# ========================================================== - -GEN_MAC="02" -for i in {1..5}; do - BYTE=$(printf "%02X" $((RANDOM % 256))) - GEN_MAC="${GEN_MAC}:${BYTE}" -done - -NEXTID=$(pvesh get /cluster/nextid 2>/dev/null || echo "100") -NAME="Synology VM" -IMAGES_DIR="/var/lib/vz/template/iso" -ERROR_FLAG=false - - - - - -function exit_script() { - clear - if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then - start_script - else - clear - exit - fi -} - - -# Define the header_info function at the beginning of the script - -function header_info() { - clear - show_proxmenux_logo - echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}" - echo -e "${BL}║ ║${CL}" - echo -e "${BL}║${YWB} Synology VM Creator ${BL}║${CL}" - echo -e "${BL}║ ║${CL}" - echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}" - echo -e -} -# ========================================================== - - - - - - -# ========================================================== -# start Script -# ========================================================== -function start_script() { - if (whiptail --backtitle "ProxMenuX" --title "SETTINGS" --yesno "$(translate "Use Default Settings?")" --no-button Advanced 10 58); then - header_info - echo -e "${DEF}Using Default Settings${CL}" - default_settings - else - header_info - echo -e "${CUS}Using Advanced Settings${CL}" - advanced_settings - fi -} -# ========================================================== - - - - -# ========================================================== -# Default Settings -# ========================================================== -function default_settings() { - VMID="$NEXTID" - FORMAT="" - MACHINE=" -machine q35" - BIOS_TYPE=" -bios ovmf" - DISK_CACHE="" - HN="Synology-DSM" - CPU_TYPE=" -cpu host" - CORE_COUNT="2" - RAM_SIZE="4096" - BRG="vmbr0" - MAC="$GEN_MAC" - VLAN="" - MTU="" - SERIAL_PORT="socket" - START_VM="no" - - echo -e " ${TAB}${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}" - echo -e " ${TAB}${DGN}Using Machine Type: ${BGN}q35${CL}" - echo -e " ${TAB}${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}" - echo -e " ${TAB}${DGN}Using Hostname: ${BGN}${HN}${CL}" - echo -e " ${TAB}${DGN}Using CPU Model: ${BGN}Host${CL}" - echo -e " ${TAB}${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}" - echo -e " ${TAB}${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}" - echo -e " ${TAB}${DGN}Using Bridge: ${BGN}${BRG}${CL}" - echo -e " ${TAB}${DGN}Using MAC Address: ${BGN}${MAC}${CL}" - echo -e " ${TAB}${DGN}Using VLAN: ${BGN}Default${CL}" - echo -e " ${TAB}${DGN}Using Interface MTU Size: ${BGN}Default${CL}" - echo -e " ${TAB}${DGN}Configuring Serial Port: ${BGN}${SERIAL_PORT}${CL}" - echo -e " ${TAB}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}" - echo -e - echo -e "${DEF}Creating a $NAME using the above default settings${CL}" - - sleep 1 - select_disk_type -} -# ========================================================== - - - - - -# ========================================================== -# advanced Settings -# ========================================================== -function advanced_settings() { - # VM ID Selection - while true; do - if VMID=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Virtual Machine ID")" 8 58 $NEXTID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z "$VMID" ]; then - VMID="$NEXTID" - fi - if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then - echo -e "${CROSS}${RD} ID $VMID is already in use${CL}" - sleep 1 - continue - fi - echo -e "${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" - break - else - exit_script - fi - done - - # Machine Type Selection - if MACH=$(whiptail --backtitle "ProxMenuX" --title "$(translate "MACHINE TYPE")" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ - "q35" "Machine q35" ON \ - "i440fx" "Machine i440fx" OFF \ - 3>&1 1>&2 2>&3); then - if [ $MACH = q35 ]; then - echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}" - FORMAT="" - MACHINE=" -machine q35" - else - echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}" - FORMAT=",efitype=4m" - MACHINE="" - fi - else - exit_script - fi - - # BIOS Type Selection - if BIOS=$(whiptail --backtitle "ProxMenuX" --title "$(translate "BIOS TYPE")" --radiolist --cancel-button Exit-Script "Choose BIOS Type" 10 58 2 \ - "ovmf" "UEFI (OVMF)" ON \ - "seabios" "SeaBIOS (Legacy)" OFF \ - 3>&1 1>&2 2>&3); then - if [ "$BIOS" = "seabios" ]; then - echo -e "${DGN}Using BIOS Type: ${BGN}SeaBIOS${CL}" - BIOS_TYPE=" -bios seabios" - else - echo -e "${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}" - BIOS_TYPE=" -bios ovmf" - fi - else - exit_script - fi - - # Hostname Selection - if VM_NAME=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Hostname")" 8 58 Synology-DSM --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $VM_NAME ]; then - HN="Synology-DSM" - echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" - else - HN=$(echo ${VM_NAME,,} | tr -d ' ') - echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" - fi - else - exit_script - fi - - # CPU Type Selection - if CPU_TYPE1=$(whiptail --backtitle "ProxMenuX" --title "$(translate "CPU MODEL")" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ - "1" "Host" ON \ - "0" "KVM64" OFF \ - 3>&1 1>&2 2>&3); then - if [ $CPU_TYPE1 = "1" ]; then - echo -e "${DGN}Using CPU Model: ${BGN}Host${CL}" - CPU_TYPE=" -cpu host" - else - echo -e "${DGN}Using CPU Model: ${BGN}KVM64${CL}" - CPU_TYPE="" - fi - else - exit_script - fi - - # Core Count Selection - if CORE_COUNT=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate CPU Cores")" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $CORE_COUNT ]; then - CORE_COUNT="2" - echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" - else - echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" - fi - else - exit_script - fi - - # RAM Size Selection - if RAM_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate RAM in MiB")" 8 58 4096 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $RAM_SIZE ]; then - RAM_SIZE="4096" - echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" - else - echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" - fi - else - exit_script - fi - - # Bridge Selection - if BRG=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Bridge")" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $BRG ]; then - BRG="vmbr0" - echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}" - else - echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}" - fi - else - exit_script - fi - - # MAC Address Selection - if MAC1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a MAC Address")" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $MAC1 ]; then - MAC="$GEN_MAC" - echo -e "${DGN}Using MAC Address: ${BGN}$MAC${CL}" - else - MAC="$MAC1" - echo -e "${DGN}Using MAC Address: ${BGN}$MAC1${CL}" - fi - else - exit_script - fi - - # VLAN Selection - if VLAN1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Vlan(leave blank for default)")" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $VLAN1 ]; then - VLAN1="Default" - VLAN="" - echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}" - else - VLAN=",tag=$VLAN1" - echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}" - fi - else - exit_script - fi - - # MTU Selection - if MTU1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Interface MTU Size (leave blank for default)")" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then - if [ -z $MTU1 ]; then - MTU1="Default" - MTU="" - echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" - else - MTU=",mtu=$MTU1" - echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" - fi - else - exit_script - fi - - - - # Confirmation - if (whiptail --backtitle "ProxMenuX" --title "$(translate "ADVANCED SETTINGS COMPLETE")" --yesno "Ready to create a $NAME?" --no-button Do-Over 10 58); then - echo -e - echo -e "${CUS}Creating a $NAME using the above advanced settings${CL}" - sleep 1 - select_disk_type - else - header_info - sleep 1 - echo -e "${CUS}Using Advanced Settings${CL}" - advanced_settings - fi -} -# ========================================================== - - - - - -# ========================================================== -# Select Disk -# ========================================================== -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) - - EXIT_STATUS=$? - - if [[ $EXIT_STATUS -ne 0 ]]; then - clear - header_info - msg_error "Operation cancelled by user. Returning to start scrip..." - sleep 2 - if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then - start_script - else - clear - exit - fi - fi - - 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_loader -} - -# ========================================================== - - - - - - -# ========================================================== -# 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 - - - select_loader -} -# ========================================================== - - - - - - -# ========================================================== -# Select Loader -# ========================================================== -function select_loader() { - # Ensure the images directory exists - if [ ! -d "$IMAGES_DIR" ]; then - msg_info "Creating images directory" - mkdir -p "$IMAGES_DIR" - chmod 755 "$IMAGES_DIR" - msg_ok "Images directory created: $IMAGES_DIR" - fi - - # Create the loader selection menu - LOADER_OPTION=$(whiptail --backtitle "ProxMenuX" --title "SELECT LOADER" --menu "$(translate "Choose a loader for Synology DSM:")" 15 70 4 \ - "1" "AuxXxilium Arc Loader" \ - "2" "RedPill Loader (RROrg - RR)" \ - "3" "TinyCore RedPill Loader (PeterSuh-Q3 M-shell)" \ - "4" "Custom Loader Image (from $IMAGES_DIR)" \ - 3>&1 1>&2 2>&3) - - if [ -z "$LOADER_OPTION" ]; then - exit_script - fi - - case $LOADER_OPTION in - 1) - LOADER_TYPE="arc" - LOADER_NAME="AuxXxilium Arc" - LOADER_URL="https://github.com/AuxXxilium/arc/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - download_loader - ;; - 2) - LOADER_TYPE="redpill" - LOADER_NAME="RedPill RR" - LOADER_URL="https://github.com/RROrg/rr/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - download_loader - ;; - 3) - LOADER_TYPE="tinycore" - LOADER_NAME="TinyCore RedPill M-shell" - LOADER_URL="https://github.com/PeterSuh-Q3/tinycore-redpill/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - download_loader - ;; - 4) - LOADER_TYPE="custom" - LOADER_NAME="Custom Image" - LOADER_URL="https://xpenology.com/forum/" - echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" - select_custom_image - ;; - esac -} - -function select_custom_image() { - # Check if there are any images in the directory - IMAGES=$(find "$IMAGES_DIR" -type f -name "*.img" -o -name "*.iso" -o -name "*.qcow2" -o -name "*.vmdk" | sort) - - if [ -z "$IMAGES" ]; then - whiptail --title "$(translate "No Images Found")" --msgbox "No compatible images found in $IMAGES_DIR\n\nSupported formats: .img, .iso, .qcow2, .vmdk\n\nPlease add some images and try again." 15 70 - select_loader - fi - - # Create an array of image options for whiptail - IMAGE_OPTIONS=() - - while read -r img; do - filename=$(basename "$img") - filesize=$(du -h "$img" | cut -f1) - IMAGE_OPTIONS+=("$img" "$filesize") - done <<< "$IMAGES" - - # Let the user select an image - LOADER_FILE=$(whiptail --backtitle "ProxMenuX" --title "SELECT CUSTOM IMAGE" --menu "$(translate "Choose a custom image:")" 20 70 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3) - - if [ -z "$LOADER_FILE" ]; then - msg_error "No custom image selected" - exit_script - fi - - echo -e "${DGN}${TAB}Using Custom Image: ${BGN}$(basename "$LOADER_FILE")${CL}" - FILE=$(basename "$LOADER_FILE") -} -# ========================================================== - - - - - - - -# ========================================================== -# Download Loader -# ========================================================== -function download_loader() { - - echo -e "${DGN}${TAB}Retrieving the URL for the ${BGN}$LOADER_NAME loader${CL}" - - if [[ "$LOADER_TYPE" == "arc" || "$LOADER_TYPE" == "redpill" ]] && ! command -v unzip &> /dev/null; then - msg_info "Installing unzip..." - apt-get update -qq && apt-get install -y unzip -qq >/dev/null 2>&1 - if ! command -v unzip &> /dev/null; then - msg_error "Failed to install unzip" - sleep 2 - return 1 - fi - msg_ok "Installed unzip successfully." - fi - - case $LOADER_TYPE in - arc) - curl -s https://api.github.com/repos/AuxXxilium/arc/releases/latest \ - | grep "browser_download_url.*\.img\.zip" \ - | cut -d '"' -f 4 \ - | xargs wget -q --show-progress -O "$IMAGES_DIR/arc.img.zip" - - if [ -f "$IMAGES_DIR/arc.img.zip" ]; then - cd "$IMAGES_DIR" - unzip -q arc.img.zip - rm arc.img.zip - FILE="arc.img" - LOADER_FILE="$IMAGES_DIR/$FILE" - cd - > /dev/null - else - msg_error "Failed to download $LOADER_NAME loader" - sleep 1 - select_loader - fi - ;; - - redpill) - curl -s https://api.github.com/repos/RROrg/rr/releases/latest \ - | grep "browser_download_url.*\.img\.zip" \ - | cut -d '"' -f 4 \ - | xargs wget -q --show-progress -O "$IMAGES_DIR/rr.img.zip" - - if [ -f "$IMAGES_DIR/rr.img.zip" ]; then - cd "$IMAGES_DIR" - msg_info "Unzipping $LOADER_NAME loader. Please wait..." - unzip -qo rr.img.zip - msg_ok "Unzipped $LOADER_NAME loader successfully." - rm -f rr.img.zip - FILE="rr.img" - LOADER_FILE="$IMAGES_DIR/$FILE" - cd - > /dev/null - fi - - ;; - - tinycore) - curl -s https://api.github.com/repos/PeterSuh-Q3/tinycore-redpill/releases/latest \ - | grep "browser_download_url.*tinycore-redpill.v.*img.gz" \ - | cut -d '"' -f 4 \ - | xargs wget -q --show-progress -O "$IMAGES_DIR/tinycore.img.gz" - - if [ -f "$IMAGES_DIR/tinycore.img.gz" ]; then - cd "$IMAGES_DIR" - - msg_info "Unzipping $LOADER_NAME loader. Please wait..." - gunzip -f tinycore.img.gz 2> /dev/null - msg_ok "Unzipped $LOADER_NAME loader successfully." - FILE="tinycore.img" - LOADER_FILE="$IMAGES_DIR/$FILE" - cd - > /dev/null - - else - msg_error "Failed to download $LOADER_NAME loader" - sleep 1 - select_loader - - fi - ;; - esac - - msg_ok "Downloaded ${CL}${BL}${FILE}${CL} to ${IMAGES_DIR}" -} -# ======================================================= - - - - - -# ========================================================== -# Select UEFI Storage -# ========================================================== -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)}') - - ITEM=" Type: $TYPE Free: $FREE" - OFFSET=2 - if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then - MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) - fi - - STORAGE_MENU+=("$TAG" "$ITEM" "OFF") - done < <(pvesm status -content images | awk 'NR>1') - - VALID=$(pvesm status -content images | awk 'NR>1') - if [ -z "$VALID" ]; then - msg_error "Unable to detect a valid storage location for EFI disk." - - elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then - STORAGE=${STORAGE_MENU[0]} - - else - kill $SPINNER_PID > /dev/null - while [ -z "${STORAGE:+x}" ]; do - STORAGE=$(whiptail --backtitle "ProxMenuX" --title "EFI Disk Storage" --radiolist \ - "$(translate "Choose the storage volume for the EFI disk (4MB):\n\nUse Spacebar to select.")" \ - 16 $(($MSG_MAX_LENGTH + 23)) 6 \ - "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit - - done - - fi - - echo "$STORAGE" -} -# ========================================================== - - - - - -# ========================================================== -# Select Storage Loader -# ========================================================== -function select_storage_volume() { - local vmid=$1 - local purpose=$2 - 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)}') - - ITEM=" Type: $TYPE Free: $FREE" - OFFSET=2 - if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then - MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) - fi - - STORAGE_MENU+=("$TAG" "$ITEM" "OFF") - done < <(pvesm status -content images | awk 'NR>1') - - VALID=$(pvesm status -content images | awk 'NR>1') - if [ -z "$VALID" ]; then - msg_error "Unable to detect a valid storage location." - exit 1 - elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then - STORAGE=${STORAGE_MENU[0]} - else - while [ -z "${STORAGE:+x}" ]; do - STORAGE=$(whiptail --backtitle "ProxMenuX" --title "Storage Pools" --radiolist \ - "$(translate "Choose the storage volume for $purpose:\n\nUse Spacebar to select.")" \ - 16 $(($MSG_MAX_LENGTH + 23)) 6 \ - "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit - done - fi - - echo "$STORAGE" -} - - - - - - -# ========================================================== -# Create VM -# ========================================================== -function create_vm() { - - # Create the VM - 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 -onboot 1 -ostype l26 -scsihw virtio-scsi-pci \ - -serial0 socket - msg_ok "Create a $NAME" - - - -# Check if UEFI (OVMF) is being used =================== - if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then - - msg_info "Configuring EFI disk" - EFI_STORAGE=$(select_efi_storage $VMID) - EFI_DISK_NAME="vm-${VMID}-disk-efivars" - - # Determine storage type and extension - 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 - - STORAGE_TYPE=$(pvesm status -storage "$EFI_STORAGE" | awk 'NR>1 {print $2}') - EFI_DISK_ID="efidisk0" - - if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then - - if qm set "$VMID" -$EFI_DISK_ID "$EFI_STORAGE:4,efitype=4m,format=raw,pre-enrolled-keys=0" >/dev/null 2>&1; then - msg_ok "EFI disk created (raw) and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}" - else - msg_error "Failed to configure EFI disk" - ERROR_FLAG=true - fi - else - - EFI_DISK_NAME="vm-${VMID}-disk-efivars" - EFI_DISK_EXT="" - EFI_DISK_REF="" - - if pvesm alloc "$EFI_STORAGE" "$VMID" "$EFI_DISK_NAME" 4M >/dev/null 2>&1; then - if qm set "$VMID" -$EFI_DISK_ID "$EFI_STORAGE:${EFI_DISK_NAME},pre-enrolled-keys=0" >/dev/null 2>&1; then - msg_ok "EFI disk created and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}" - else - msg_error "Failed to configure EFI disk" - ERROR_FLAG=true - fi - else - msg_error "Failed to create EFI disk" - ERROR_FLAG=true - fi - fi - - - fi -# ========================================================== - - -# Select storage volume for loader ======================= - - LOADER_STORAGE=$(select_storage_volume $VMID "loader disk") - - - #Run the command in the background and capture its PID - qm importdisk $VMID ${LOADER_FILE} $LOADER_STORAGE > /tmp/import_log_$VMID.txt 2>&1 & - import_pid=$! - - # Show a simple progress indicator - echo -n "Importing loader disk: " - while kill -0 $import_pid 2>/dev/null; do - echo -n "." - sleep 2.5 - done - - wait $import_pid - rm -f /tmp/import_log_$VMID.txt - - IMPORTED_DISK=$(qm config $VMID | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f1) - - # If the disk was not imported correctly, show an error message but continue - if [ -z "$IMPORTED_DISK" ]; then - msg_error "Loader import failed. No disk detected." - ERROR_FLAG=true - else - msg_ok "Loader imported successfully to ${CL}${BL}$LOADER_STORAGE${GN}${CL}" - fi - - - - - - STORAGE_TYPE=$(pvesm status -storage "$LOADER_STORAGE" | awk 'NR>1 {print $2}') - - if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then - - UNUSED_LINE=$(qm config "$VMID" | grep -E '^unused[0-9]+:') - IMPORTED_ID=$(echo "$UNUSED_LINE" | cut -d: -f1) - IMPORTED_REF=$(echo "$UNUSED_LINE" | cut -d: -f2- | xargs) - - if [[ -n "$IMPORTED_REF" && -n "$IMPORTED_ID" ]]; then - if qm set "$VMID" -ide0 "$IMPORTED_REF" >/dev/null 2>&1; then - msg_ok "Configured loader disk as ide0" - qm set "$VMID" -delete "$IMPORTED_ID" >/dev/null 2>&1 - else - msg_error "Failed to assign loader disk to ide0" - ERROR_FLAG=true - fi - else - msg_error "Loader import failed. No disk detected in config." - ERROR_FLAG=true - fi - else - - DISK_NAME="vm-${VMID}-disk-0" - if qm set "$VMID" -ide0 "$LOADER_STORAGE:${DISK_NAME}" >/dev/null 2>&1; then - msg_ok "Configured loader disk as ide0" - else - msg_error "Failed to assign loader disk" - ERROR_FLAG=true - fi - fi - - - - - result=$(qm set "$VMID" -boot order=ide0 2>&1) - if [[ $? -eq 0 ]]; then - msg_ok "Loader configured as boot device." - else - ERROR_FLAG=true - fi - -# ========================================================== - -if [ "$DISK_TYPE" = "virtual" ]; then - if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then - msg_error "No virtual disks configured." - exit_script - fi - - DISK_INFO="" - CONSOLE_DISK_INFO="" - - for i in "${!VIRTUAL_DISKS[@]}"; do - IFS=':' read -r STORAGE SIZE <<< "${VIRTUAL_DISKS[$i]}" - - STORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}') - case $STORAGE_TYPE in - nfs | dir) - DISK_EXT=".raw" - DISK_REF="$VMID/" - ;; - *) - DISK_EXT="" - DISK_REF="" - ;; - esac - - - DISK_NUM=$((i+1)) - DISK_NAME="vm-${VMID}-disk-${DISK_NUM}${DISK_EXT}" - SATA_ID="sata$i" - - # Create virtual disk - if [[ "$STORAGE_TYPE" == "btrfs" || "$STORAGE_TYPE" == "dir" || "$STORAGE_TYPE" == "nfs" ]]; then - - msg_info "Creating virtual disk (format=raw) for $STORAGE_TYPE..." - if ! qm set "$VMID" -$SATA_ID "$STORAGE:$SIZE,format=raw" >/dev/null 2>&1; then - msg_error "Failed to assign disk $DISK_NUM ($SATA_ID) on $STORAGE" - ERROR_FLAG=true - continue - fi - else - - msg_info "Allocating virtual disk for $STORAGE_TYPE..." - if ! pvesm alloc "$STORAGE" "$VMID" "$DISK_NAME" "$SIZE"G >/dev/null 2>&1; then - msg_error "Failed to allocate virtual disk $DISK_NUM" - ERROR_FLAG=true - continue - fi - if ! qm set "$VMID" -$SATA_ID "$STORAGE:${DISK_REF}$DISK_NAME" >/dev/null 2>&1; then - msg_error "Failed to configure virtual disk as $SATA_ID" - ERROR_FLAG=true - continue - fi - fi - - msg_ok "Configured virtual disk as $SATA_ID, ${SIZE}GB on ${CL}${BL}$STORAGE${CL} ${GN}" - - - # Add information to the description - DISK_INFO="${DISK_INFO}

Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE}

" - CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE} ($SATA_ID)\n" - done - - - - # HTML description -HTML_DESC="
- - - - - -
-ProxMenux Logo - -

Synology DSM VM

-

Created with ProxMenuX

-

Loader: $LOADER_NAME

-
- -

-Docs -Code -Loader -Ko-fi -

- -
-${DISK_INFO} -
-
" - - msg_info "Setting VM description" - if ! qm set "$VMID" -description "$HTML_DESC" >/dev/null 2>&1; then - msg_error "Failed to set VM description" - exit_script - fi - msg_ok "Configured VM description" - - -else - - - # Configure multiple passthrough disks - DISK_INFO="" - CONSOLE_DISK_INFO="" - - for i in "${!PASSTHROUGH_DISKS[@]}"; do - DISK="${PASSTHROUGH_DISKS[$i]}" - DISK_MODEL=$(lsblk -ndo MODEL "$DISK" | xargs) - DISK_SIZE=$(lsblk -ndo SIZE "$DISK" | xargs) - DISK_ID="sata$i" - - - result=$(qm set $VMID -${DISK_ID} ${DISK} 2>&1) - if [[ $? -eq 0 ]]; then - msg_ok "Configured disk ${CL}${BL}($DISK_MODEL $DISK_SIZE)${CL}${GN} as $DISK_ID" - fi - # Add information to the description - DISK_INFO="${DISK_INFO}

Passthrough Disk $((i+1)): $DISK ($DISK_MODEL $DISK_SIZE)

" - CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Passthrough Disk $((i+1)): $DISK ($DISK_MODEL $DISK_SIZE) (${DISK_ID})\n" - done - - - # HTML description -HTML_DESC="
- - - - - -
-ProxMenux Logo - -

Synology DSM VM

-

Created with ProxMenuX

-

Loader: $LOADER_NAME

-
- -

-Docs -Code -Loader -Ko-fi -

- -
-${DISK_INFO} -
-
" - - - result=$(qm set $VMID -description "$HTML_DESC" 2>&1) - if [[ $? -eq 0 ]]; then - msg_ok "Configured VM description" - fi - - -fi - - -if [ "$ERROR_FLAG" = true ]; then - msg_error "VM created with errors. Check configuration." -else -msg_success "$(translate "Completed Successfully!")" - -echo -e "${TAB}${GN}$(translate "Next Steps:")${CL}" -echo -e "${TAB}1. $(translate "Start the VM")" -echo -e "${TAB}2. $(translate "Open the VM console and wait for the loader to boot")" -echo -e "${TAB}3. $(translate "In the loader interface, follow the instructions to select your Synology model")" -echo -e "${TAB}4. $(translate "Complete the DSM installation wizard")" -echo -e "${TAB}5. $(translate "Find your device using https://finds.synology.com")" -echo -e - -msg_success "$(translate "Press Enter to return to the main menu...")" -read -r - -fi - -} - -# ========================================================== - - - -# ========================================================== -# Main execution -# ========================================================== -header_info -#echo -e "\n Loading..." -sleep 1 - -# Start script -if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then - start_script -else - clear - exit -fi - -# Create VM -create_vm - -# ========================================================== \ No newline at end of file diff --git a/scripts/test/vm/uupdump_creator.sh b/scripts/test/vm/uupdump_creator.sh deleted file mode 100644 index 731fe28c..00000000 --- a/scripts/test/vm/uupdump_creator.sh +++ /dev/null @@ -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 < 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 - -} \ No newline at end of file diff --git a/scripts/test/vm/vm_configurator.sh b/scripts/test/vm/vm_configurator.sh deleted file mode 100644 index 88a9761a..00000000 --- a/scripts/test/vm/vm_configurator.sh +++ /dev/null @@ -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.")" -} diff --git a/scripts/test/vm/vm_creator.sh b/scripts/test/vm/vm_creator.sh deleted file mode 100644 index 63959c5d..00000000 --- a/scripts/test/vm/vm_creator.sh +++ /dev/null @@ -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+="

Virtual Disk $DISK_INDEX: ${SIZE}GB ($STORAGE / $SLOT_NAME)

" - [[ -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+="

Passthrough Disk $((i+1)): $DISK ($MODEL $SIZE)

" - [[ -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="

$HN

Created with ProxMenux

$DISK_INFO
" - 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" -}