From 366ccb7b9a9b2989b9526940955b58a9cfafa9f0 Mon Sep 17 00:00:00 2001 From: apulkin Date: Thu, 28 May 2026 11:58:28 +0300 Subject: [PATCH] add --- Docker/docker_debian_install.sh | 16 + Docker/metube/.docker-compose.yml.kate-swp | Bin 0 -> 197 bytes Docker/metube/docker-compose.yml | 10 + Docker/qbittorrent/docker-compose.yml | 18 + Docker/victoria-logs/docker-compose.yml | 17 + LXC/ARCH VM.sh | 602 +++++++++ LXC/Cloudflare-DDNS.sh | 40 + LXC/Element Synapse.sh | 75 ++ LXC/Grafana.sh | 68 + LXC/Homelable.sh | 90 ++ LXC/LXC Podman.sh | 45 + LXC/LXC_PMG_install.sh | 49 + LXC/Loki.sh | 103 ++ LXC/Mail-Archiver.sh | 89 ++ LXC/PostgreSQL.sh | 50 + LXC/Redis.sh | 56 + LXC/Technitium DNS.sh | 70 + LXC/Uptime Kuma.sh | 72 ++ LXC/VM Mikrotik RouterOS CHR.sh | 670 ++++++++++ LXC/VM OpenWrt.sh | 699 ++++++++++ LXC/VM Unifi OS Server.sh | 46 + LXC/Zerotier-One.sh | 58 + LXC/ddclient.sh | 53 + LXC/debian_lxc.sh | 1357 ++++++++++++++++++++ LXC/hev-socks5-server.sh | 61 + LXC/lxc_PDM_install.sh | 73 ++ Proxmox/PVE/PVE CPU Scaling Governor.sh | 72 ++ Proxmox/PVE/PVE Clean Orphaned LVM.sh | 99 ++ Proxmox/PVE/PVE Cron LXC Updater.sh | 355 +++++ Proxmox/PVE/PVE Host Backup.sh | 93 ++ Proxmox/PVE/PVE Kernel Clean.sh | 122 ++ Proxmox/PVE/PVE LXC Apps Update.sh | 680 ++++++++++ Proxmox/PVE/PVE LXC Cleaner.sh | 124 ++ Proxmox/PVE/PVE LXC Execute.sh | 94 ++ Proxmox/PVE/PVE LXC Filesystem Trim.sh | 197 +++ Proxmox/PVE/PVE LXC Tag.sh | 1140 ++++++++++++++++ Proxmox/PVE/PVE Netdata.sh | 171 +++ Proxmox/PVE/PVE Update Repositories.sh | 72 ++ Proxmox/pbs/PBS Processor Microcode.sh | 188 +++ Proxmox/pbs/conf.sh | 341 +++++ Proxmox/pbs/pbs_install._lxc.sh | 49 + Proxmox/pbs/pbs_update4.sh | 191 +++ 42 files changed, 8475 insertions(+) create mode 100644 Docker/docker_debian_install.sh create mode 100644 Docker/metube/.docker-compose.yml.kate-swp create mode 100644 Docker/metube/docker-compose.yml create mode 100644 Docker/qbittorrent/docker-compose.yml create mode 100644 Docker/victoria-logs/docker-compose.yml create mode 100644 LXC/ARCH VM.sh create mode 100644 LXC/Cloudflare-DDNS.sh create mode 100644 LXC/Element Synapse.sh create mode 100644 LXC/Grafana.sh create mode 100644 LXC/Homelable.sh create mode 100644 LXC/LXC Podman.sh create mode 100644 LXC/LXC_PMG_install.sh create mode 100644 LXC/Loki.sh create mode 100644 LXC/Mail-Archiver.sh create mode 100644 LXC/PostgreSQL.sh create mode 100644 LXC/Redis.sh create mode 100644 LXC/Technitium DNS.sh create mode 100644 LXC/Uptime Kuma.sh create mode 100644 LXC/VM Mikrotik RouterOS CHR.sh create mode 100644 LXC/VM OpenWrt.sh create mode 100644 LXC/VM Unifi OS Server.sh create mode 100644 LXC/Zerotier-One.sh create mode 100644 LXC/ddclient.sh create mode 100644 LXC/debian_lxc.sh create mode 100644 LXC/hev-socks5-server.sh create mode 100644 LXC/lxc_PDM_install.sh create mode 100644 Proxmox/PVE/PVE CPU Scaling Governor.sh create mode 100644 Proxmox/PVE/PVE Clean Orphaned LVM.sh create mode 100644 Proxmox/PVE/PVE Cron LXC Updater.sh create mode 100644 Proxmox/PVE/PVE Host Backup.sh create mode 100644 Proxmox/PVE/PVE Kernel Clean.sh create mode 100644 Proxmox/PVE/PVE LXC Apps Update.sh create mode 100644 Proxmox/PVE/PVE LXC Cleaner.sh create mode 100644 Proxmox/PVE/PVE LXC Execute.sh create mode 100644 Proxmox/PVE/PVE LXC Filesystem Trim.sh create mode 100644 Proxmox/PVE/PVE LXC Tag.sh create mode 100644 Proxmox/PVE/PVE Netdata.sh create mode 100644 Proxmox/PVE/PVE Update Repositories.sh create mode 100644 Proxmox/pbs/PBS Processor Microcode.sh create mode 100644 Proxmox/pbs/conf.sh create mode 100644 Proxmox/pbs/pbs_install._lxc.sh create mode 100644 Proxmox/pbs/pbs_update4.sh diff --git a/Docker/docker_debian_install.sh b/Docker/docker_debian_install.sh new file mode 100644 index 0000000..d8c38c1 --- /dev/null +++ b/Docker/docker_debian_install.sh @@ -0,0 +1,16 @@ +apt install sudo curl mc -y +sudo apt install ca-certificates curl -y +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc +sudo tee /etc/apt/sources.list.d/docker.sources <A2_aLdd|RWQ;sU|?Vn31 < | |/ / / / / +/_/ |_/_/ \___/_/ /_/ /_____/_/_/ /_/\__,_/_/|_| |___/_/ /_/ + +EOF +} +header_info +echo -e "\n Loading..." +GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//') +RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" +METHOD="" +NSAPP="archlinux-vm" +var_os="arch-linux" +var_version="n.d." + +YW=$(echo "\033[33m") +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +BGN=$(echo "\033[4;92m") +GN=$(echo "\033[1;92m") +DGN=$(echo "\033[32m") +CL=$(echo "\033[m") + +CL=$(echo "\033[m") +BOLD=$(echo "\033[1m") +BFR="\\r\\033[K" +HOLD=" " +TAB=" " + +CM="${TAB}✔️${TAB}${CL}" +CROSS="${TAB}✖️${TAB}${CL}" +INFO="${TAB}💡${TAB}${CL}" +OS="${TAB}🖥️${TAB}${CL}" +CONTAINERTYPE="${TAB}📦${TAB}${CL}" +DISKSIZE="${TAB}💾${TAB}${CL}" +CPUCORE="${TAB}🧠${TAB}${CL}" +RAMSIZE="${TAB}🛠️${TAB}${CL}" +CONTAINERID="${TAB}🆔${TAB}${CL}" +HOSTNAME="${TAB}🏠${TAB}${CL}" +BRIDGE="${TAB}🌉${TAB}${CL}" +GATEWAY="${TAB}🌐${TAB}${CL}" +DEFAULT="${TAB}⚙️${TAB}${CL}" +MACADDRESS="${TAB}🔗${TAB}${CL}" +VLANTAG="${TAB}🏷️${TAB}${CL}" +CREATING="${TAB}🚀${TAB}${CL}" +ADVANCED="${TAB}🧩${TAB}${CL}" +CLOUD="${TAB}☁️${TAB}${CL}" + +THIN="discard=on,ssd=1," +set -e +trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +trap cleanup EXIT +trap 'post_update_to_api "failed" "130"' SIGINT +trap 'post_update_to_api "failed" "143"' SIGTERM +trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP +function error_handler() { + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + post_update_to_api "failed" "${exit_code}" + echo -e "\n$error_message\n" + cleanup_vmid +} + +function get_valid_nextid() { + local try_id + try_id=$(pvesh get /cluster/nextid) + while true; do + if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then + try_id=$((try_id + 1)) + continue + fi + if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then + try_id=$((try_id + 1)) + continue + fi + break + done + echo "$try_id" +} + +function cleanup_vmid() { + if qm status $VMID &>/dev/null; then + qm stop $VMID &>/dev/null + qm destroy $VMID &>/dev/null + fi +} + +function cleanup() { + local exit_code=$? + popd >/dev/null + if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then + if [[ $exit_code -eq 0 ]]; then + post_update_to_api "done" "none" + else + post_update_to_api "failed" "$exit_code" + fi + fi + rm -rf $TEMP_DIR +} + +TEMP_DIR=$(mktemp -d) +pushd $TEMP_DIR >/dev/null +if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Arch Linux VM" --yesno "This will create a New Arch Linux VM. Proceed?" 10 58; then + : +else + header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit +fi + +function msg_info() { + local msg="$1" + echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" +} + +function msg_ok() { + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" +} + +function msg_error() { + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" +} + +function check_root() { + if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then + clear + msg_error "Please run this script as root." + echo -e "\nExiting..." + sleep 2 + exit + fi +} + +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.2 +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 105 + fi + return 0 + fi + + # Check for Proxmox VE 9.x: allow 9.0 and 9.2 + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 2)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.2" + exit 105 + fi + return 0 + fi + + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.2" + exit 105 +} + +function arch_check() { + if [ "$(dpkg --print-architecture)" != "amd64" ]; then + echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" + echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" + echo -e "Exiting..." + sleep 2 + exit + fi +} + +function ssh_check() { + if command -v pveversion >/dev/null 2>&1; then + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then + echo "you've been warned" + else + clear + exit + fi + fi + fi +} + +function exit-script() { + clear + echo -e "\n${CROSS}${RD}User exited script${CL}\n" + exit +} + +function default_settings() { + VMID=$(get_valid_nextid) + MACHINE="" + DISK_SIZE="4G" + DISK_CACHE="" + HN="arch-linux" + CPU_TYPE="" + CORE_COUNT="1" + RAM_SIZE="1024" + BRG="vmbr0" + MAC="$GEN_MAC" + VLAN="" + MTU="" + START_VM="yes" + METHOD="default" + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + echo -e "${CREATING}${BOLD}${DGN}Creating a Arch Linux VM using the above default settings${CL}" +} + +function advanced_settings() { + METHOD="advanced" + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 "$VMID" --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID=$(get_valid_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 2 + continue + fi + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break + else + exit-script + fi + done + + if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ + "i440fx" "Machine i440fx" ON \ + "q35" "Machine q35" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$MACH" = q35 ]; then + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + MACHINE=" -machine q35" + else + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + MACHINE="" + fi + else + exit-script + fi + + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') + if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then + DISK_SIZE="${DISK_SIZE}G" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + else + echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" + exit-script + fi + else + exit-script + fi + + if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "None (Default)" ON \ + "1" "Write Through" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$DISK_CACHE" = "1" ]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" + DISK_CACHE="cache=writethrough," + else + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + DISK_CACHE="" + fi + else + exit-script + fi + + if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 arch-linux --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VM_NAME" ]; then + HN="arch-linux" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + else + HN=$(echo "${VM_NAME,,}" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//') + if [ "$HN" != "${VM_NAME,,}" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58 + fi + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + fi + else + exit-script + fi + + if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "KVM64 (Default)" ON \ + "1" "Host" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$CPU_TYPE1" = "1" ]; then + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" + CPU_TYPE=" -cpu host" + else + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + CPU_TYPE="" + fi + else + exit-script + fi + + while true; do + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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"; fi + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58 + else + exit-script + fi + done + + while true; do + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$RAM_SIZE" ]; then RAM_SIZE="2048"; fi + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58 + else + exit-script + fi + done + + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + else + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + fi + else + exit-script + fi + + while true; do + if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" + break + fi + if [[ "$MAC1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then + MAC="$MAC1" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58 + else + exit-script + fi + done + + while true; do + if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + if [[ "$VLAN1" =~ ^[0-9]+$ ]] && [ "$VLAN1" -ge 1 ] && [ "$VLAN1" -le 4094 ]; then + VLAN=",tag=$VLAN1" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit-script + fi + done + + while true; do + if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + if [[ "$MTU1" =~ ^[0-9]+$ ]] && [ "$MTU1" -ge 576 ] && [ "$MTU1" -le 65520 ]; then + MTU=",mtu=$MTU1" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58 + else + exit-script + fi + done + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + START_VM="yes" + else + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}" + START_VM="no" + fi + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Arch Linux VM?" --no-button Do-Over 10 58); then + echo -e "${CREATING}${BOLD}${DGN}Creating a Arch Linux VM using the above advanced settings${CL}" + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +function start_script() { + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +check_root +arch_check +pve_check +ssh_check +start_script +post_to_api_vm + +msg_info "Validating Storage" +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 +elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} +else + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + done +fi +msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." +msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." +msg_info "Retrieving the URL for the Arch Linux .iso File" +URL=https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso +sleep 2 +msg_ok "${CL}${BL}${URL}${CL}" +curl -f#SL -o "$(basename "$URL")" "$URL" +echo -en "\e[1A\e[0K" +FILE=$(basename $URL) +msg_ok "Downloaded ${CL}${BL}${FILE}${CL}" + +STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}') +case $STORAGE_TYPE in +nfs | dir | cifs) + DISK_EXT=".qcow2" + DISK_REF="$VMID/" + DISK_IMPORT="--format qcow2" + THIN="" + ;; +btrfs) + DISK_EXT=".raw" + DISK_REF="$VMID/" + DISK_IMPORT="--format raw" + THIN="" + ;; +*) + DISK_EXT="" + DISK_REF="" + DISK_IMPORT="--format raw" + ;; +esac + +msg_info "Creating a Arch Linux VM" +qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \ + -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci + +if qm disk import --help >/dev/null 2>&1; then + IMPORT_CMD=(qm disk import) +else + IMPORT_CMD=(qm importdisk) +fi + +IMPORT_OUT="$("${IMPORT_CMD[@]}" "$VMID" "${FILE}" "$STORAGE" ${DISK_IMPORT:-} 2>&1 || true)" +DISK_REF_IMPORTED="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully imported disk '\([^']\+\)'.*/\1/p" | tr -d "\r\"'")" +[[ -z "$DISK_REF_IMPORTED" ]] && DISK_REF_IMPORTED="$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $1":"$5}' | sort | tail -n1)" +[[ -z "$DISK_REF_IMPORTED" ]] && { + msg_error "Unable to determine imported disk reference." + echo "$IMPORT_OUT" + exit 226 +} +msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})" + +qm set $VMID \ + -efidisk0 ${STORAGE}:0,efitype=4m \ + -scsi0 ${DISK_REF_IMPORTED},${DISK_CACHE}${THIN%,} \ + -ide2 ${STORAGE}:cloudinit \ + -boot order=scsi0 \ + -serial0 socket >/dev/null +DESCRIPTION=$( + cat < + + Logo + + +

Arch Linux VM

+ +

+ + spend Coffee + +

+ + + + GitHub + + + + Discussions + + + + Issues + + +EOF +) +qm set $VMID -description "$DESCRIPTION" >/dev/null +if [ -n "$DISK_SIZE" ]; then + msg_info "Resizing disk to $DISK_SIZE GB" + qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null +else + msg_info "Using default disk size of $DEFAULT_DISK_SIZE GB" + qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null +fi + +msg_ok "Created a Arch Linux VM ${CL}${BL}(${HN})" +if [ "$START_VM" == "yes" ]; then + msg_info "Starting Arch Linux VM" + qm start $VMID + msg_ok "Started Arch Linux VM" +fi +post_update_to_api "done" "none" + +msg_ok "Completed successfully!\n" diff --git a/LXC/Cloudflare-DDNS.sh b/LXC/Cloudflare-DDNS.sh new file mode 100644 index 0000000..0bfd4ff --- /dev/null +++ b/LXC/Cloudflare-DDNS.sh @@ -0,0 +1,40 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/cloudflare-ddns.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: edoardop13 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/favonia/cloudflare-ddns + +APP="Cloudflare-DDNS" +var_tags="${var_tags:-network}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-3}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /etc/systemd/system/cloudflare-ddns.service ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_error "There is no update function for ${APP}." + exit +} + +start +build_container +description +msg_ok "Completed successfully!\n" diff --git a/LXC/Element Synapse.sh b/LXC/Element Synapse.sh new file mode 100644 index 0000000..e50f870 --- /dev/null +++ b/LXC/Element Synapse.sh @@ -0,0 +1,75 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/elementsynapse.sh)" + +Port +:8008 + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: tremor021 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/element-hq/synapse + +APP="Element Synapse" +var_tags="${var_tags:-server}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-8}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /etc/matrix-synapse ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + NODE_VERSION="22" NODE_MODULE="yarn" setup_nodejs + + msg_info "Updating LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated LXC" + + if check_for_gh_release "synapse-admin" "etkecc/synapse-admin"; then + msg_info "Stopping Service" + systemctl stop synapse-admin + msg_ok "Stopped Service" + + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "synapse-admin" "etkecc/synapse-admin" "tarball" "latest" "/opt/synapse-admin" + + msg_info "Building Synapse-Admin" + cd /opt/synapse-admin + $STD yarn global add serve + $STD yarn install --ignore-engines + $STD yarn build + mv ./dist ../ && rm -rf * && mv ../dist ./ + msg_ok "Built Synapse-Admin" + + msg_info "Starting Service" + systemctl start synapse-admin + msg_ok "Started Service" + msg_ok "Updated Synapse-Admin to ${CHECK_UPDATE_RELEASE}" + fi + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8008${CL}" diff --git a/LXC/Grafana.sh b/LXC/Grafana.sh new file mode 100644 index 0000000..aea3fab --- /dev/null +++ b/LXC/Grafana.sh @@ -0,0 +1,68 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/grafana.sh)" + +User +admin +Pass +admin +Port +:3000 + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://grafana.com/ + +APP="Grafana" +var_tags="${var_tags:-monitoring;visualization}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if ! dpkg -s grafana >/dev/null 2>&1; then + msg_error "No ${APP} Installation Found!" + exit 233 + fi + + if [[ -f /etc/apt/sources.list.d/grafana.list ]] || [[ ! -f /etc/apt/sources.list.d/grafana.sources ]]; then + setup_deb822_repo \ + "grafana" \ + "https://apt.grafana.com/gpg.key" \ + "https://apt.grafana.com" \ + "stable" \ + "main" + fi + + msg_info "Updating Grafana LXC" + $STD apt update + $STD apt --only-upgrade install -y grafana + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}" + + diff --git a/LXC/Homelable.sh b/LXC/Homelable.sh new file mode 100644 index 0000000..2e783fc --- /dev/null +++ b/LXC/Homelable.sh @@ -0,0 +1,90 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/homelable.sh)" + +Install Profiles +Port +:3000 +Default credentials +User +admin +Pass +admin + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/Pouzor/homelable + +APP="Homelable" +var_tags="${var_tags:-monitoring;network;visualization}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-8}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -d /opt/homelable ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if check_for_gh_release "homelable" "Pouzor/homelable"; then + msg_info "Stopping Service" + systemctl stop homelable + msg_ok "Stopped Service" + + msg_info "Backing up Configuration and Data" + cp /opt/homelable/backend/.env /opt/homelable.env.bak + cp -r /opt/homelable/data /opt/homelable_data_bak + msg_ok "Backed up Configuration and Data" + + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "homelable" "Pouzor/homelable" "tarball" "latest" "/opt/homelable" + + msg_info "Updating Python Dependencies" + cd /opt/homelable/backend + $STD uv venv --clear /opt/homelable/backend/.venv + $STD uv pip install --python /opt/homelable/backend/.venv/bin/python -r requirements.txt + msg_ok "Updated Python Dependencies" + + msg_info "Rebuilding Frontend" + cd /opt/homelable/frontend + $STD npm ci + $STD npm run build + msg_ok "Rebuilt Frontend" + + msg_info "Restoring Configuration and Data" + cp /opt/homelable.env.bak /opt/homelable/backend/.env + cp -r /opt/homelable_data_bak/. /opt/homelable/data/ + rm -f /opt/homelable.env.bak + rm -rf /opt/homelable_data_bak + msg_ok "Restored Configuration and Data" + + msg_info "Starting Service" + systemctl start homelable + msg_ok "Started Service" + msg_ok "Updated successfully!" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}" diff --git a/LXC/LXC Podman.sh b/LXC/LXC Podman.sh new file mode 100644 index 0000000..f8d74a9 --- /dev/null +++ b/LXC/LXC Podman.sh @@ -0,0 +1,45 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/podman.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://podman.io/ + +APP="Podman" +var_tags="${var_tags:-container;kubernetes}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /etc/containers/registries.conf ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_info "Updating ${APP} LXC" + $STD apt update + $STD apt upgrade -y + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" diff --git a/LXC/LXC_PMG_install.sh b/LXC/LXC_PMG_install.sh new file mode 100644 index 0000000..eb7b4cc --- /dev/null +++ b/LXC/LXC_PMG_install.sh @@ -0,0 +1,49 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/proxmox-mail-gateway.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: thost96 (thost96) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.proxmox.com/en/products/proxmox-mail-gateway + +APP="Proxmox-Mail-Gateway" +var_tags="${var_tags:-mail}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-4096}" +var_disk="${var_disk:-10}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -e /usr/bin/pmgproxy ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + msg_info "Updating Proxmox-Mail-Gateway" + $STD apt update + $STD apt upgrade -y + msg_ok "Updated Proxmox-Mail-Gateway" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:8006/${CL}" diff --git a/LXC/Loki.sh b/LXC/Loki.sh new file mode 100644 index 0000000..bc3d6bc --- /dev/null +++ b/LXC/Loki.sh @@ -0,0 +1,103 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/loki.sh)" + +Port +:3100 +Debian: /etc/loki/config.yml | Alpine: /etc/loki/loki-local-config.yaml + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: hoholms +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/grafana/loki + +APP="Loki" +var_tags="${var_tags:-monitoring;logs}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if ! dpkg -s loki >/dev/null 2>&1; then + msg_error "No ${APP} Installation Found!" + exit 233 + fi + + CHOICE=$(msg_menu "Loki Update Options" \ + "1" "Update Loki & Promtail" \ + "2" "Allow 0.0.0.0 for listening" \ + "3" "Allow only ${LOCAL_IP} for listening") + + case $CHOICE in + 1) + msg_info "Stopping Loki" + systemctl stop loki + if systemctl is-active --quiet promtail 2>/dev/null || dpkg -s promtail >/dev/null 2>&1; then + systemctl stop promtail + fi + msg_ok "Stopped Loki" + + msg_info "Updating Loki" + $STD apt update + $STD apt install -y --only-upgrade loki + if dpkg -s promtail >/dev/null 2>&1; then + $STD apt install -y --only-upgrade promtail + fi + msg_ok "Updated Loki" + + msg_info "Starting Loki" + systemctl start loki + if dpkg -s promtail >/dev/null 2>&1; then + systemctl start promtail + fi + msg_ok "Started Loki" + msg_ok "Updated successfully!" + exit + ;; + 2) + msg_info "Configuring Loki to listen on 0.0.0.0" + sed -i 's/http_listen_address:.*/http_listen_address: 0.0.0.0/' /etc/loki/config.yml + sed -i 's/http_listen_port:.*/http_listen_port: 3100/' /etc/loki/config.yml + systemctl restart loki + msg_ok "Configured Loki to listen on 0.0.0.0" + exit + ;; + 3) + msg_info "Configuring Loki to listen on ${LOCAL_IP}" + sed -i "s/http_listen_address:.*/http_listen_address: $LOCAL_IP/" /etc/loki/config.yml + sed -i 's/http_listen_port:.*/http_listen_port: 3100/' /etc/loki/config.yml + systemctl restart loki + msg_ok "Configured Loki to listen on ${LOCAL_IP}" + exit + ;; + esac + exit 0 +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access loki using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3100${CL}\n" +if dpkg -s promtail >/dev/null 2>&1; then + echo -e "${INFO}${YW} Access promtail using the following URL:${CL}" + echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9080${CL}" +fi + diff --git a/LXC/Mail-Archiver.sh b/LXC/Mail-Archiver.sh new file mode 100644 index 0000000..dfa8a42 --- /dev/null +++ b/LXC/Mail-Archiver.sh @@ -0,0 +1,89 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/mail-archiver.sh)" + + +Default credentials +User +admin +Pass +secure123! + +:5000 + + + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: Slaviša Arežina (tremor021) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/s1t5/mail-archiver + +APP="Mail-Archiver" +var_tags="${var_tags:-mail-archiver}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-8}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /opt/mail-archiver ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + ensure_dependencies libgssapi-krb5-2 + + if check_for_gh_release "mail-archiver" "s1t5/mail-archiver"; then + msg_info "Stopping Mail-Archiver" + systemctl stop mail-archiver + msg_ok "Stopped Mail-Archiver" + + msg_info "Creating Backup" + cp /opt/mail-archiver/appsettings.json /opt/mail-archiver/.env /opt/ + [[ -d /opt/mail-archiver/DataProtection-Keys ]] && cp -r /opt/mail-archiver/DataProtection-Keys /opt + msg_ok "Created Backup" + + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "mail-archiver" "s1t5/mail-archiver" "tarball" + + msg_info "Updating Mail-Archiver" + mv /opt/mail-archiver /opt/mail-archiver-build + cd /opt/mail-archiver-build + $STD dotnet restore + $STD dotnet publish -c Release -o /opt/mail-archiver + rm -rf /opt/mail-archiver-build + msg_ok "Updated Mail-Archiver" + + msg_info "Restoring Backup" + cp /opt/appsettings.json /opt/.env /opt/mail-archiver + [[ -d /opt/DataProtection-Keys ]] && cp -r /opt/DataProtection-Keys /opt/mail-archiver/ + msg_ok "Restored Backup" + + msg_info "Starting Mail-Archiver" + systemctl start mail-archiver + msg_ok "Started Mail-Archiver" + msg_ok "Updated successfully!" + fi + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:5000${CL}" + diff --git a/LXC/PostgreSQL.sh b/LXC/PostgreSQL.sh new file mode 100644 index 0000000..01abd9e --- /dev/null +++ b/LXC/PostgreSQL.sh @@ -0,0 +1,50 @@ +Port:5432 + +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/postgresql.sh)" + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.postgresql.org/ + +APP="PostgreSQL" +var_tags="${var_tags:-database}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if ! command -v psql >/dev/null 2>&1; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_info "Updating ${APP} LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following IP:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}${IP}:5432${CL}" diff --git a/LXC/Redis.sh b/LXC/Redis.sh new file mode 100644 index 0000000..eb14ab3 --- /dev/null +++ b/LXC/Redis.sh @@ -0,0 +1,56 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/redis.sh)" + +Config paths + Debian /etc/redis/redis.conf | Alpine: /etc/redis.conf + + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://redis.io/ + +APP="Redis" +var_tags="${var_tags:-database}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /lib/systemd/system/redis-server.service ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + msg_info "Updating $APP LXC" + $STD apt update + $STD apt upgrade -y + msg_ok "Updated $APP LXC" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following IP:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}${IP}:6379${CL}" + + diff --git a/LXC/Technitium DNS.sh b/LXC/Technitium DNS.sh new file mode 100644 index 0000000..5f1d299 --- /dev/null +++ b/LXC/Technitium DNS.sh @@ -0,0 +1,70 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/technitiumdns.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://technitium.com/dns/ + +APP="Technitium DNS" +var_tags="${var_tags:-dns}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /etc/dns ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + if [[ -f /etc/systemd/system/dns.service ]]; then + mv /etc/systemd/system/dns.service /etc/systemd/system/technitium.service + systemctl daemon-reload + systemctl enable -q --now technitium + fi + if ! is_package_installed "aspnetcore-runtime-10.0"; then + $STD apt remove -y aspnetcore-runtime-8.0 aspnetcore-runtime-9.0 2>/dev/null || true + [ -f /etc/apt/sources.list.d/microsoft-prod.list ] && rm -f /etc/apt/sources.list.d/microsoft-prod.list + [ -f /usr/share/keyrings/microsoft-prod.gpg ] && rm -f /usr/share/keyrings/microsoft-prod.gpg + setup_deb822_repo \ + "microsoft" \ + "https://packages.microsoft.com/keys/microsoft-2025.asc" \ + "https://packages.microsoft.com/debian/13/prod/" \ + "trixie" \ + "main" + $STD apt install -y aspnetcore-runtime-10.0 + fi + + RELEASE=$(curl -fsSL https://technitium.com/dns/ | grep -oP 'Version \K[\d.]+') + if [[ ! -f ~/.technitium || ${RELEASE} != "$(cat ~/.technitium 2>/dev/null)" ]]; then + fetch_and_deploy_from_url "https://download.technitium.com/dns/DnsServerPortable.tar.gz" /opt/technitium/dns + echo "${RELEASE}" >~/.technitium + systemctl restart technitium + msg_ok "Updated successfully!" + else + msg_ok "No update required. Technitium DNS is already at v${RELEASE}." + fi + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:5380${CL}" diff --git a/LXC/Uptime Kuma.sh b/LXC/Uptime Kuma.sh new file mode 100644 index 0000000..25a5d35 --- /dev/null +++ b/LXC/Uptime Kuma.sh @@ -0,0 +1,72 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/uptimekuma.sh)" + +Port +:3001 + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://uptime.kuma.pet/ | Github: https://github.com/louislam/uptime-kuma + +APP="Uptime Kuma" +var_tags="${var_tags:-analytics;monitoring}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-1024}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /opt/uptime-kuma ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + NODE_VERSION="22" setup_nodejs + + ensure_dependencies chromium + if [[ ! -L /opt/uptime-kuma/chromium ]]; then + ln -s /usr/bin/chromium /opt/uptime-kuma/chromium + fi + + if check_for_gh_release "uptime-kuma" "louislam/uptime-kuma"; then + msg_info "Stopping Service" + systemctl stop uptime-kuma + msg_ok "Stopped Service" + + fetch_and_deploy_gh_release "uptime-kuma" "louislam/uptime-kuma" "tarball" + + msg_info "Updating Uptime Kuma" + cd /opt/uptime-kuma + $STD npm install --omit dev + $STD npm run download-dist + msg_ok "Updated Uptime Kuma" + + msg_info "Starting Service" + systemctl start uptime-kuma + msg_ok "Started Service" + msg_ok "Updated successfully!" + fi + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3001${CL}" diff --git a/LXC/VM Mikrotik RouterOS CHR.sh b/LXC/VM Mikrotik RouterOS CHR.sh new file mode 100644 index 0000000..2ad53d1 --- /dev/null +++ b/LXC/VM Mikrotik RouterOS CHR.sh @@ -0,0 +1,670 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/mikrotik-routeros.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) + +function header_info { + clear + cat <<"EOF" + __ ____ __ __ _ __ ____ __ ____ _____ ________ ______ + / |/ (_) /___________ / /_(_) /__ / __ \____ __ __/ /____ _____/ __ \/ ___/ / ____/ / / / __ \ + / /|_/ / / //_/ ___/ __ \/ __/ / //_/ / /_/ / __ \/ / / / __/ _ \/ ___/ / / /\__ \ / / / /_/ / /_/ / + / / / / / ,< / / / /_/ / /_/ / ,< / _, _/ /_/ / /_/ / /_/ __/ / / /_/ /___/ / / /___/ __ / _, _/ +/_/ /_/_/_/|_/_/ \____/\__/_/_/|_| /_/ |_|\____/\__,_/\__/\___/_/ \____//____/ \____/_/ /_/_/ |_| + +EOF +} +header_info +echo -e "Loading..." +GEN_MAC=$(echo '00 60 2f'$(od -An -N3 -t xC /dev/urandom) | sed -e 's/ /:/g' | tr '[:lower:]' '[:upper:]') +RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" +METHOD="" +NSAPP="mikrotik-routeros" +var_os="mikrotik" +var_version=" " +DISK_SIZE="1G" +YW=$(echo "\033[33m") +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +BGN=$(echo "\033[4;92m") +GN=$(echo "\033[1;92m") +DGN=$(echo "\033[32m") +CL=$(echo "\033[m") + +CL=$(echo "\033[m") +BOLD=$(echo "\033[1m") +BFR="\\r\\033[K" +HOLD=" " +TAB=" " + +CM="${TAB}✔️${TAB}${CL}" +CROSS="${TAB}✖️${TAB}${CL}" +INFO="${TAB}💡${TAB}${CL}" +OS="${TAB}🖥️${TAB}${CL}" +CONTAINERTYPE="${TAB}📦${TAB}${CL}" +DISKSIZE="${TAB}💾${TAB}${CL}" +CPUCORE="${TAB}🧠${TAB}${CL}" +RAMSIZE="${TAB}🛠️${TAB}${CL}" +CONTAINERID="${TAB}🆔${TAB}${CL}" +HOSTNAME="${TAB}🏠${TAB}${CL}" +BRIDGE="${TAB}🌉${TAB}${CL}" +GATEWAY="${TAB}🌐${TAB}${CL}" +DEFAULT="${TAB}⚙️${TAB}${CL}" +MACADDRESS="${TAB}🔗${TAB}${CL}" +VLANTAG="${TAB}🏷️${TAB}${CL}" +CREATING="${TAB}🚀${TAB}${CL}" +ADVANCED="${TAB}🧩${TAB}${CL}" +CLOUD="${TAB}☁️${TAB}${CL}" + +THIN="discard=on,ssd=1," +set -e +trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +trap cleanup EXIT +trap 'post_update_to_api "failed" "130"' SIGINT +trap 'post_update_to_api "failed" "143"' SIGTERM +trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP +function error_handler() { + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + post_update_to_api "failed" "${exit_code}" + echo -e "\n$error_message\n" + cleanup_vmid +} + +function get_valid_nextid() { + local try_id + try_id=$(pvesh get /cluster/nextid) + while true; do + if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then + try_id=$((try_id + 1)) + continue + fi + if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then + try_id=$((try_id + 1)) + continue + fi + break + done + echo "$try_id" +} + +function cleanup_vmid() { + if qm status $VMID &>/dev/null; then + qm stop $VMID &>/dev/null + qm destroy $VMID &>/dev/null + fi +} + +function cleanup() { + local exit_code=$? + popd >/dev/null + if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then + if [[ $exit_code -eq 0 ]]; then + post_update_to_api "done" "none" + else + post_update_to_api "failed" "$exit_code" + fi + fi + rm -rf $TEMP_DIR +} + +TEMP_DIR=$(mktemp -d) +pushd $TEMP_DIR >/dev/null +if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Mikrotik RouterOS CHR VM" --yesno "This will create a Mikrotik RouterOS CHR VM. Proceed?" 10 58; then + : +else + header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit +fi + +function msg_info() { + local msg="$1" + echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" +} + +function msg_ok() { + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" +} + +function msg_error() { + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" +} + +function check_root() { + if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then + clear + msg_error "Please run this script as root." + echo -e "\nExiting..." + sleep 2 + exit + fi +} + +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.2 +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 105 + fi + return 0 + fi + + # Check for Proxmox VE 9.x: allow 9.0 and 9.2 + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 2)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.2" + exit 105 + fi + return 0 + fi + + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.2" + exit 105 +} + +function arch_check() { + if [ "$(dpkg --print-architecture)" != "amd64" ]; then + echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" + echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" + echo -e "Exiting..." + sleep 2 + exit + fi +} + +function ssh_check() { + if command -v pveversion >/dev/null 2>&1; then + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then + echo "you've been warned" + else + clear + exit + fi + fi + fi +} + +function exit-script() { + clear + echo -e "\n${CROSS}${RD}User exited script${CL}\n" + exit +} + +function default_settings() { + VMID=$(get_valid_nextid) + FORMAT=",efitype=4m" + MACHINE="" + DISK_SIZE="8G" + DISK_CACHE="" + HN="mikrotik-routeros-chr" + CPU_TYPE="" + CORE_COUNT="2" + RAM_SIZE="512" + BRG="vmbr0" + MAC="$GEN_MAC" + VLAN="" + MTU="" + START_VM="yes" + CLOUD_INIT="no" + METHOD="default" + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + echo -e "${CREATING}${BOLD}${DGN}Creating a Mikrotik RouterOS VM using the above default settings${CL}" +} + +function get_mikrotik_version() { + local mode="$1" + local rss_url + local tree_name + + case "$mode" in + s) rss_url="https://cdn.mikrotik.com/routeros/latest-stable.rss" ;; + d) rss_url="https://cdn.mikrotik.com/routeros/latest-development.rss" ;; + l) rss_url="https://cdn.mikrotik.com/routeros/latest-long-term.rss" ;; + t) rss_url="https://cdn.mikrotik.com/routeros/latest-testing.rss" ;; + *) return 0 ;; + esac + + local rss_content + rss_content=$(curl -fsSL $rss_url 2>/dev/null) + if [ -n "$rss_content" ]; then + local version + version=$(echo "$rss_content" | grep -oP 'RouterOS \K[0-9.]+(?= \[)' 2>/dev/null || echo "$rss_content" | sed -n 's/.*<title>RouterOS \([0-9.]\+\) \[.*/\1/p' 2>/dev/null) + if [[ "$version" =~ ^[0-9]+\.[0-9]+ ]]; then + echo "$version" + return 0 + fi + fi + + case "$mode" in + s) tree_name="Stable release tree" ;; + d) tree_name="Development release tree" ;; + l) tree_name="Long-term release tree" ;; + t) tree_name="Testing release tree" ;; + esac + + local html + html=$(curl -fsSL "https://mikrotik.com/download/changelogs" 2>/dev/null) + if [ -n "$html" ]; then + local start_line + start_line=$(echo "$html" | grep -n "$tree_name" | cut -d: -f1 | head -n1) + if [[ "$start_line" =~ ^[0-9]+$ ]]; then + local line + line=$(echo "$html" | tail -n +"$start_line" | grep -m 1 -E "c-(stable|longTerm|testing|development)-v|RouterOS [0-9]+\.[0-9]+" 2>/dev/null) + + local version + version=$(echo "$line" | sed -n 's/.*c-[^"]*-v\([0-9_.a-zA-Z-]\+\).*/\1/p' | tr '_' '.' 2>/dev/null) + [ -z "$version" ] && version=$(echo "$line" | grep -oP 'RouterOS \K[0-9]+\.[0-9]+(\.[0-9]+)?' 2>/dev/null) + + if [[ "$version" =~ ^[0-9]+\.[0-9]+ ]]; then + echo "$version" + return 0 + fi + fi + fi + + for minor in $(seq 50 -1 15); do + local test_version="7.${minor}" + if curl -fsSL -I "https://download.mikrotik.com/routeros/${test_version}/chr-${test_version}.img.zip" 2>/dev/null | grep -q "200 OK"; then + echo "$test_version" + return 0 + fi + done + + return 0 +} + +function advanced_settings() { + METHOD="advanced" + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID=$(get_valid_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 2 + continue + fi + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break + else + exit-script + fi + done + + if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ + "i440fx" "Machine i440fx" ON \ + "q35" "Machine q35" OFF \ + 3>&1 1>&2 2>&3); then + if [ $MACH = q35 ]; then + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + FORMAT="" + MACHINE=" -machine q35" + else + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + FORMAT=",efitype=4m" + MACHINE="" + fi + else + exit-script + fi + + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') + if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then + DISK_SIZE="${DISK_SIZE}G" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + else + echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" + exit-script + fi + else + exit-script + fi + + if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "None (Default)" ON \ + "1" "Write Through" OFF \ + 3>&1 1>&2 2>&3); then + if [ $DISK_CACHE = "1" ]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" + DISK_CACHE="cache=writethrough," + else + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + DISK_CACHE="" + fi + else + exit-script + fi + + if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 mikrotik-routeros-chr --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VM_NAME ]; then + HN="mikrotik-routeros-chr" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + else + HN=$(echo "${VM_NAME,,}" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//') + if [ "$HN" != "${VM_NAME,,}" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58 + fi + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + fi + else + exit-script + fi + + if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "KVM64 (Default)" ON \ + "1" "Host" OFF \ + 3>&1 1>&2 2>&3); then + if [ $CPU_TYPE1 = "1" ]; then + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" + CPU_TYPE=" -cpu host" + else + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + CPU_TYPE="" + fi + else + exit-script + fi + + while true; do + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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"; fi + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58 + else + exit-script + fi + done + + while true; do + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$RAM_SIZE" ]; then RAM_SIZE="2048"; fi + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58 + else + exit-script + fi + done + + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + else + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + fi + else + exit-script + fi + + while true; do + if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" + break + fi + if [[ "$MAC1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then + MAC="$MAC1" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58 + else + exit-script + fi + done + + while true; do + if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + if [[ "$VLAN1" =~ ^[0-9]+$ ]] && [ "$VLAN1" -ge 1 ] && [ "$VLAN1" -le 4094 ]; then + VLAN=",tag=$VLAN1" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit-script + fi + done + + while true; do + if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + if [[ "$MTU1" =~ ^[0-9]+$ ]] && [ "$MTU1" -ge 576 ] && [ "$MTU1" -le 65520 ]; then + MTU=",mtu=$MTU1" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58 + else + exit-script + fi + done + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + START_VM="yes" + else + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}" + START_VM="no" + fi + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Mikrotik RouterOS CHR VM?" --no-button Do-Over 10 58); then + echo -e "${CREATING}${BOLD}${DGN}Creating a Mikrotik RouterOS CHR VM using the above advanced settings${CL}" + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +function start_script() { + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +check_root +arch_check +pve_check +ssh_check +start_script + +post_to_api_vm +msg_info "Validating Storage" +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 + echo -e "\n${RD}⚠ Unable to detect a valid storage location.${CL}" + echo -e "Exiting..." + exit +elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} +else + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for the Mikrotik RouterOS CHR VM?\n\n" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + done +fi +msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." +msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." +msg_info "Getting URL for Latest Mikrotik RouterOS CHR Disk Image" + +MIK_VER=$(get_mikrotik_version s) + +if [ -n "$MIK_VER" ]; then + msg_ok "Latest stable version: ${CL}${BL}$MIK_VER${CL}." +else + msg_error "Could not get latest version" + msg_ok "Defaulting to version 7.20" + MIK_VER="7.20" +fi + +URL=https://download.mikrotik.com/routeros/$MIK_VER/chr-$MIK_VER.img.zip + +sleep 2 +msg_ok "Downloading from URL: ${CL}${BL}${URL}${CL}" +curl -f#SL -o "$(basename "$URL")" "$URL" +echo -en "\e[1A\e[0K" +FILE=$(basename $URL) +msg_ok "Downloaded ${CL}${BL}$FILE${CL}" +msg_info "Extracting Mikrotik RouterOS CHR Disk Image" +gunzip -f -S .zip $FILE +STORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}') +case $STORAGE_TYPE in +nfs | dir) + DISK_EXT=".qcow2" + DISK_REF="$VMID/" + DISK_IMPORT="-format qcow2" + ;; +btrfs) + DISK_EXT=".raw" + DISK_REF="$VMID/" + DISK_IMPORT="-format raw" + ;; +zfspool) + DISK_EXT="" + DISK_REF="" + DISK_IMPORT="-format raw" + ;; +*) + DISK_EXT="" + DISK_REF="" + DISK_IMPORT="-format raw" + ;; +esac + +DISK_VAR="vm-${VMID}-disk-0${DISK_EXT:-}" +DISK_REF="${STORAGE}:${DISK_REF:-}${DISK_VAR:-}" + +msg_ok "Extracted Mikrotik RouterOS CHR Disk Image" +msg_info "Creating Mikrotik RouterOS CHR VM" +qm create $VMID -tablet 0 -localtime 1 -cores $CORE_COUNT -memory $RAM_SIZE -name $HN \ + -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU \ + -onboot 1 -ostype l26 -scsihw virtio-scsi-pci +qm importdisk $VMID ${FILE%.*} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null +qm set $VMID \ + -scsi0 "$DISK_REF" \ + -boot order=scsi0 >/dev/null + +DESCRIPTION=$( + cat <<EOF +<div align='center'> + <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'> + <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> + </a> + + <h2 style='font-size: 24px; margin: 20px 0;'>Mikrotik RouterOS CHR</h2> + + <p style='margin: 16px 0;'> + <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'> + <img src='https://img.shields.io/badge/☕-Buy us a coffee-blue' alt='spend Coffee' /> + </a> + </p> + + <span style='margin: 0 10px;'> + <i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a> + </span> +</div> +EOF +) +qm set $VMID -description "$DESCRIPTION" >/dev/null +if [ -n "$DISK_SIZE" ]; then + msg_info "Resizing disk to $DISK_SIZE GB" + qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null +else + msg_info "Using default disk size of $DEFAULT_DISK_SIZE GB" + qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null +fi + +msg_ok "Mikrotik RouterOS CHR VM ${CL}${BL}(${HN})" +if [ "$START_VM" == "yes" ]; then + msg_info "Starting Mikrotik RouterOS CHR VM" + qm start $VMID + msg_ok "Started Mikrotik RouterOS CHR VM" +fi +post_update_to_api "done" "none" +msg_ok "Completed successfully!\n" diff --git a/LXC/VM OpenWrt.sh b/LXC/VM OpenWrt.sh new file mode 100644 index 0000000..d0c3e28 --- /dev/null +++ b/LXC/VM OpenWrt.sh @@ -0,0 +1,699 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/openwrt-vm.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# Jon Spriggs (jontheniceguy) +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Based on work from https://i12bretro.github.io/tutorials/0405.html + +source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) + +function header_info { + clear + cat <<"EOF" + ____ _ __ __ + / __ \____ ___ ____| | / /____/ /_ + / / / / __ \/ _ \/ __ \ | /| / / ___/ __/ +/ /_/ / /_/ / __/ / / / |/ |/ / / / /_ +\____/ .___/\___/_/ /_/|__/|__/_/ \__/ + /_/ W I R E L E S S F R E E D O M + +EOF +} +header_info +echo -e "\n Loading..." +RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" +METHOD="" +NSAPP="openwrt-vm" +var_os="openwrt" +var_version=" " +DISK_SIZE="1G" +GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//') +GEN_MAC_LAN=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//') + +YW=$(echo "\033[33m") +BL=$(echo "\033[36m") +HA=$(echo "\033[1;34m") +RD=$(echo "\033[01;31m") +BGN=$(echo "\033[4;92m") +GN=$(echo "\033[1;92m") +DGN=$(echo "\033[32m") +CL=$(echo "\033[m") + +BOLD=$(echo "\033[1m") +BFR="\\r\\033[K" +HOLD=" " +TAB=" " + +CM="${TAB}✔️${TAB}${CL}" +CROSS="${TAB}✖️${TAB}${CL}" +INFO="${TAB}💡${TAB}${CL}" +OS="${TAB}🖥️${TAB}${CL}" +CONTAINERTYPE="${TAB}📦${TAB}${CL}" +DISKSIZE="${TAB}💾${TAB}${CL}" +CPUCORE="${TAB}🧠${TAB}${CL}" +RAMSIZE="${TAB}🛠️${TAB}${CL}" +CONTAINERID="${TAB}🆔${TAB}${CL}" +HOSTNAME="${TAB}🏠${TAB}${CL}" +BRIDGE="${TAB}🌉${TAB}${CL}" +GATEWAY="${TAB}🌐${TAB}${CL}" +DEFAULT="${TAB}⚙️${TAB}${CL}" +MACADDRESS="${TAB}🔗${TAB}${CL}" +VLANTAG="${TAB}🏷️${TAB}${CL}" +CREATING="${TAB}🚀${TAB}${CL}" +ADVANCED="${TAB}🧩${TAB}${CL}" +CLOUD="${TAB}☁️${TAB}${CL}" + +set -Eeo pipefail +trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +trap cleanup EXIT +trap 'post_update_to_api "failed" "130"' SIGINT +trap 'post_update_to_api "failed" "143"' SIGTERM +trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP +function error_handler() { + local exit_code="$?" + local line_number="$1" + local command="$2" + post_update_to_api "failed" "$exit_code" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + echo -e "\n$error_message\n" + cleanup_vmid +} + +function get_valid_nextid() { + local try_id + try_id=$(pvesh get /cluster/nextid) + while true; do + if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then + try_id=$((try_id + 1)) + continue + fi + if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then + try_id=$((try_id + 1)) + continue + fi + break + done + echo "$try_id" +} + +function cleanup_vmid() { + if qm status $VMID &>/dev/null; then + qm stop $VMID &>/dev/null + qm destroy $VMID &>/dev/null + fi +} + +function cleanup() { + local exit_code=$? + popd >/dev/null + if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then + if [[ $exit_code -eq 0 ]]; then + post_update_to_api "done" "none" + else + post_update_to_api "failed" "$exit_code" + fi + fi + rm -rf $TEMP_DIR +} + +TEMP_DIR=$(mktemp -d) +pushd $TEMP_DIR >/dev/null +function send_line_to_vm() { + echo -e "${DGN}Sending line: ${YW}$1${CL}" + for ((i = 0; i < ${#1}; i++)); do + character=${1:i:1} + case $character in + " ") character="spc" ;; + "-") character="minus" ;; + "=") character="equal" ;; + ",") character="comma" ;; + ".") character="dot" ;; + "/") character="slash" ;; + "'") character="apostrophe" ;; + ";") character="semicolon" ;; + '\') character="backslash" ;; + '`') character="grave_accent" ;; + "[") character="bracket_left" ;; + "]") character="bracket_right" ;; + "_") character="shift-minus" ;; + "+") character="shift-equal" ;; + "?") character="shift-slash" ;; + "<") character="shift-comma" ;; + ">") character="shift-dot" ;; + '"') character="shift-apostrophe" ;; + ":") character="shift-semicolon" ;; + "|") character="shift-backslash" ;; + "~") character="shift-grave_accent" ;; + "{") character="shift-bracket_left" ;; + "}") character="shift-bracket_right" ;; + "A") character="shift-a" ;; + "B") character="shift-b" ;; + "C") character="shift-c" ;; + "D") character="shift-d" ;; + "E") character="shift-e" ;; + "F") character="shift-f" ;; + "G") character="shift-g" ;; + "H") character="shift-h" ;; + "I") character="shift-i" ;; + "J") character="shift-j" ;; + "K") character="shift-k" ;; + "L") character="shift-l" ;; + "M") character="shift-m" ;; + "N") character="shift-n" ;; + "O") character="shift-o" ;; + "P") character="shift-p" ;; + "Q") character="shift-q" ;; + "R") character="shift-r" ;; + "S") character="shift-s" ;; + "T") character="shift-t" ;; + "U") character="shift-u" ;; + "V") character="shift-v" ;; + "W") character="shift-w" ;; + "X") character="shift=x" ;; + "Y") character="shift-y" ;; + "Z") character="shift-z" ;; + "!") character="shift-1" ;; + "@") character="shift-2" ;; + "#") character="shift-3" ;; + '$') character="shift-4" ;; + "%") character="shift-5" ;; + "^") character="shift-6" ;; + "&") character="shift-7" ;; + "*") character="shift-8" ;; + "(") character="shift-9" ;; + ")") character="shift-0" ;; + esac + qm sendkey $VMID "$character" + done + qm sendkey $VMID ret +} + +TEMP_DIR=$(mktemp -d) +pushd $TEMP_DIR >/dev/null + +if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "OpenWrt VM" --yesno "This will create a New OpenWrt VM. Proceed?" 10 58); then + : +else + header_info && echo -e "⚠ User exited script \n" && exit +fi + +function msg_info() { + local msg="$1" + echo -ne " ${HOLD} ${YW}${msg}..." +} + +function msg_ok() { + local msg="$1" + echo -e "${BFR} ${CM} ${GN}${msg}${CL}" +} + +function msg_error() { + local msg="$1" + echo -e "${BFR} ${CROSS} ${RD}${msg}${CL}" +} + +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.2 +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 105 + fi + return 0 + fi + + # Check for Proxmox VE 9.x: allow 9.0 and 9.2 + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 2)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.2" + exit 105 + fi + return 0 + fi + + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.2" + exit 105 +} + +function arch_check() { + if [ "$(dpkg --print-architecture)" != "amd64" ]; then + echo -e "\n ${CROSS} This script will not work with PiMox! \n" + echo -e "Exiting..." + sleep 2 + exit + fi +} + +function ssh_check() { + if command -v pveversion >/dev/null 2>&1; then + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then + echo "you've been warned" + else + clear + exit + fi + fi + fi +} + +function exit-script() { + clear + echo -e "⚠ User exited script \n" + exit +} + +function default_settings() { + VMID=$(get_valid_nextid) + HN="openwrt" + CORE_COUNT="1" + RAM_SIZE="256" + BRG="vmbr0" + LAN_BRG="vmbr0" + MAC=$GEN_MAC + LAN_MAC=$GEN_MAC_LAN + VLAN="" + LAN_VLAN="" + LAN_IP_ADDR="192.168.1.1" + LAN_NETMASK="255.255.255.0" + MTU="" + START_VM="yes" + METHOD="default" + DISK_SIZE="1G" + echo -e "${CONTAINERID}${BOLD}${DGN}VMID: ${BGN}${VMID}${CL}" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM: ${BGN}${RAM_SIZE}${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}WAN Bridge: ${BGN}${BRG}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}LAN Bridge: ${BGN}${LAN_BRG}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}WAN MAC: ${BGN}${MAC}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}LAN MAC: ${BGN}${LAN_MAC}${CL}" +} + +function advanced_settings() { + METHOD="advanced" + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID=$(get_valid_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 2 + continue + fi + echo -e "${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break + else + exit-script + fi + done + + if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 openwrt --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VM_NAME ]; then + HN="openwrt" + else + HN=$(echo "${VM_NAME,,}" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//') + if [ "$HN" != "${VM_NAME,,}" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58 + fi + fi + echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" + else + exit-script + fi + + while true; do + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 1 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$CORE_COUNT" ]; then CORE_COUNT="1"; fi + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 1)." 8 58 + else + exit-script + fi + done + + while true; do + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 256 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$RAM_SIZE" ]; then RAM_SIZE="256"; fi + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 256)." 8 58 + else + exit-script + fi + done + + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ + --inputbox "Set Disk Size in GiB (e.g., 1, 2, 4)" 8 58 "1" \ + --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then + DISK_SIZE="${DISK_SIZE}G" + fi + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + else + exit-script + fi + + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN Bridge" 8 58 vmbr0 --title "WAN BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $BRG ]; then + BRG="vmbr0" + fi + echo -e "${DGN}Using WAN Bridge: ${BGN}$BRG${CL}" + else + exit-script + fi + + if LAN_BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN Bridge" 8 58 vmbr0 --title "LAN BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $LAN_BRG ]; then + LAN_BRG="vmbr0" + fi + echo -e "${DGN}Using LAN Bridge: ${BGN}$LAN_BRG${CL}" + else + exit-script + fi + + if LAN_IP_ADDR=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a router IP" 8 58 $LAN_IP_ADDR --title "LAN IP ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $LAN_IP_ADDR ]; then + LAN_IP_ADDR="192.168.1.1" + fi + echo -e "${DGN}Using LAN IP ADDRESS: ${BGN}$LAN_IP_ADDR${CL}" + else + exit-script + fi + + if LAN_NETMASK=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a router netmask" 8 58 $LAN_NETMASK --title "LAN NETMASK" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $LAN_NETMASK ]; then + LAN_NETMASK="255.255.255.0" + fi + echo -e "${DGN}Using LAN NETMASK: ${BGN}$LAN_NETMASK${CL}" + else + exit-script + fi + + if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN MAC Address" 8 58 $GEN_MAC --title "WAN MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $MAC1 ]; then + MAC="$GEN_MAC" + else + MAC="$MAC1" + fi + echo -e "${DGN}Using WAN MAC Address: ${BGN}$MAC${CL}" + else + exit-script + fi + + if MAC2=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN MAC Address" 8 58 $GEN_MAC_LAN --title "LAN MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $MAC2 ]; then + LAN_MAC="$GEN_MAC_LAN" + else + LAN_MAC="$MAC2" + fi + echo -e "${DGN}Using LAN MAC Address: ${BGN}$LAN_MAC${CL}" + else + exit-script + fi + + while true; do + if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a WAN Vlan (leave blank for default)" 8 58 --title "WAN VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VLAN1" ]; then + VLAN1="Default" + VLAN="" + echo -e "${DGN}Using WAN Vlan: ${BGN}$VLAN1${CL}" + break + fi + if [[ "$VLAN1" =~ ^[0-9]+$ ]] && [ "$VLAN1" -ge 1 ] && [ "$VLAN1" -le 4094 ]; then + VLAN=",tag=$VLAN1" + echo -e "${DGN}Using WAN Vlan: ${BGN}$VLAN1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit-script + fi + done + + while true; do + if VLAN2=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a LAN Vlan" 8 58 999 --title "LAN VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VLAN2" ]; then + VLAN2="Default" + LAN_VLAN="" + echo -e "${DGN}Using LAN Vlan: ${BGN}$VLAN2${CL}" + break + fi + if [[ "$VLAN2" =~ ^[0-9]+$ ]] && [ "$VLAN2" -ge 1 ] && [ "$VLAN2" -le 4094 ]; then + LAN_VLAN=",tag=$VLAN2" + echo -e "${DGN}Using LAN Vlan: ${BGN}$VLAN2${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit-script + fi + done + + while true; do + if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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}" + break + fi + if [[ "$MTU1" =~ ^[0-9]+$ ]] && [ "$MTU1" -ge 576 ] && [ "$MTU1" -le 65520 ]; then + MTU=",mtu=$MTU1" + echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58 + else + exit-script + fi + done + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then + START_VM="yes" + else + START_VM="no" + fi + echo -e "${DGN}Start VM when completed: ${BGN}$START_VM${CL}" + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create OpenWrt VM?" --no-button Do-Over 10 58); then + echo -e "${RD}Creating a OpenWrt VM using the above advanced settings${CL}" + else + header_info + echo -e "${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +function start_script() { + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then + header_info + echo -e "${BL}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +arch_check +pve_check +ssh_check +start_script +post_to_api_vm + +msg_info "Validating Storage" +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 + echo -e "\n${RD}⚠ Unable to detect a valid storage location.${CL}" + echo -e "Exiting..." + exit +elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} +else + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for the OpenWrt VM?\n\n" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + done +fi +msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." +msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." +msg_info "Getting URL for OpenWrt Disk Image" + +response=$(curl -fsSL https://openwrt.org) +stableversion=$(echo "$response" | sed -n 's/.*Current stable release - OpenWrt \([0-9.]\+\).*/\1/p' | head -n 1) +URL="https://downloads.openwrt.org/releases/$stableversion/targets/x86/64/openwrt-$stableversion-x86-64-generic-ext4-combined.img.gz" + +msg_ok "${CL}${BL}${URL}${CL}" +curl -f#SL -o "$(basename "$URL")" "$URL" +FILE=$(basename "$URL") +msg_ok "Downloaded ${CL}${BL}$FILE${CL}" + +gunzip -f "$FILE" >/dev/null 2>&1 || true +FILE="${FILE%.*}" +msg_ok "Extracted OpenWrt Disk Image ${CL}${BL}$FILE${CL}" + +msg_info "Creating OpenWrt VM" +qm create $VMID -cores $CORE_COUNT -memory $RAM_SIZE -name $HN \ + -onboot 1 -ostype l26 -scsihw virtio-scsi-pci --tablet 0 +if [[ "$(pvesm status | awk -v s=$STORAGE '$1==s {print $2}')" == "dir" ]]; then + qm set $VMID -efidisk0 ${STORAGE}:0,efitype=4m,size=4M +else + pvesm alloc $STORAGE $VMID vm-$VMID-disk-0 4M >/dev/null + qm set $VMID -efidisk0 ${STORAGE}:vm-$VMID-disk-0,efitype=4m,size=4M +fi + +IMPORT_OUT="$(qm importdisk $VMID $FILE $STORAGE --format raw 2>&1 || true)" +DISK_REF="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully imported disk '\([^']\+\)'.*/\1/p")" + +if [[ -z "$DISK_REF" ]]; then + DISK_REF="$(pvesm list "$STORAGE" | awk -v id="$VMID" '$1 ~ ("vm-"id"-disk-") {print $1}' | sort | tail -n1)" +fi + +if [[ -z "$DISK_REF" ]]; then + msg_error "Unable to determine imported disk reference." + echo "$IMPORT_OUT" + exit 226 +fi + +qm set $VMID \ + -efidisk0 ${STORAGE}:0,efitype=4m,size=4M \ + -scsi0 ${DISK_REF} \ + -boot order=scsi0 \ + -tags community-script >/dev/null +msg_ok "Attached disk" + +msg_info "Resizing disk to ${DISK_SIZE}" +qm disk resize "$VMID" scsi0 "${DISK_SIZE}" >/dev/null +msg_ok "Resized disk to ${DISK_SIZE}" + +DESCRIPTION=$( + cat <<EOF +<div align='center'> + <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'> + <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> + </a> + + <h2 style='font-size: 24px; margin: 20px 0;'>OpenWrt VM</h2> + + <p style='margin: 16px 0;'> + <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'> + <img src='https://img.shields.io/badge/☕-Buy us a coffee-blue' alt='spend Coffee' /> + </a> + </p> + + <span style='margin: 0 10px;'> + <i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a> + </span> +</div> +EOF +) +qm set $VMID -description "$DESCRIPTION" >/dev/null + +msg_ok "Created OpenWrt VM ${CL}${BL}(${HN})" +msg_info "OpenWrt is being started in order to configure the network interfaces." +qm start $VMID +sleep 15 +msg_info "Waiting for OpenWrt to boot..." +for i in {1..30}; do + if qm status "$VMID" | grep -q "running"; then + sleep 5 + msg_ok "OpenWrt is running" + break + fi + sleep 1 +done + +msg_ok "Network interfaces are being configured as OpenWrt initiates." + +if qm status "$VMID" | grep -q "running"; then + send_line_to_vm "" + send_line_to_vm "uci delete network.@device[0]" + send_line_to_vm "uci set network.wan=interface" + send_line_to_vm "uci set network.wan.device=eth1" + send_line_to_vm "uci set network.wan.proto=dhcp" + send_line_to_vm "uci delete network.lan" + send_line_to_vm "uci set network.lan=interface" + send_line_to_vm "uci set network.lan.device=eth0" + send_line_to_vm "uci set network.lan.proto=static" + send_line_to_vm "uci set network.lan.ipaddr=${LAN_IP_ADDR}" + send_line_to_vm "uci set network.lan.netmask=${LAN_NETMASK}" + send_line_to_vm "uci commit" + send_line_to_vm "poweroff" + msg_ok "Network interfaces configured in OpenWrt" +else + msg_error "VM is not running" + exit 226 +fi + +msg_info "Waiting for OpenWrt to shut down..." +until qm status "$VMID" | grep -q "stopped"; do + sleep 2 +done +msg_ok "OpenWrt has shut down" + +msg_info "Adding bridge interfaces on Proxmox side" +qm set $VMID \ + -net0 virtio,bridge=${LAN_BRG},macaddr=${LAN_MAC}${LAN_VLAN}${MTU} \ + -net1 virtio,bridge=${BRG},macaddr=${MAC}${VLAN}${MTU} >/dev/null +msg_ok "Bridge interfaces added" + +if [ "$START_VM" = "yes" ]; then + msg_info "Starting OpenWrt VM" + qm start $VMID + msg_ok "Started OpenWrt VM" +fi + +VLAN_FINISH="" +if [ -z "$VLAN" ] && [ "$VLAN2" != "999" ]; then + VLAN_FINISH=" Please remember to adjust the VLAN tags to suit your network." +fi +post_update_to_api "done" "none" +msg_ok "Completed Successfully!${VLAN_FINISH:+\n$VLAN_FINISH}" diff --git a/LXC/VM Unifi OS Server.sh b/LXC/VM Unifi OS Server.sh new file mode 100644 index 0000000..8eec00b --- /dev/null +++ b/LXC/VM Unifi OS Server.sh @@ -0,0 +1,46 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/unifi-os-server.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://ui.com/ + +APP="UniFi-OS-Server" +var_tags="${var_tags:-network}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-4096}" +var_disk="${var_disk:-20}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-0}" +var_tun="${var_tun:-yes}" +var_nesting="${var_nesting:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /usr/local/sbin/unifi-os-server.bin && ! -d /data/unifi ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_custom "🚀" "${GN}" "The app offers a built-in updater. Please use it." + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:11443${CL}" diff --git a/LXC/Zerotier-One.sh b/LXC/Zerotier-One.sh new file mode 100644 index 0000000..5342e53 --- /dev/null +++ b/LXC/Zerotier-One.sh @@ -0,0 +1,58 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/zerotier-one.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: tremor021 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.zerotier.com/ + +APP="Zerotier-One" +var_tags="${var_tags:-networking}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -f /usr/sbin/zerotier-one ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + msg_info "Stopping Service" + systemctl stop zerotier-one + msg_ok "Stopping Service" + + msg_info "Updating Zerotier-One" + $STD apt update + $STD apt upgrade -y + msg_ok "Updated Zerotier-One" + + msg_info "Starting Service" + systemctl start zerotier-one + msg_ok "Started Service" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following IP:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:3443${CL}" diff --git a/LXC/ddclient.sh b/LXC/ddclient.sh new file mode 100644 index 0000000..a3aa50f --- /dev/null +++ b/LXC/ddclient.sh @@ -0,0 +1,53 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/ddclient.sh)" + + +ddclient — это клиент на Perl, используемый для обновления динамических DNS-записей для учетных записей у широкого круга поставщиков услуг динамического DNS. Он поддерживает множество протоколов и поставщиков, позволяя автоматически обновлять IP-адреса для ваших доменных имен. + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: mitchscobell +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://ddclient.net/ + +APP="ddclient" +var_tags="${var_tags:-network}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -f /etc/ddclient.conf ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + msg_info "Updating ddclient" + $STD apt update + $STD apt install --only-upgrade -y ddclient + $STD systemctl restart ddclient + msg_ok "Updated ddclient" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" diff --git a/LXC/debian_lxc.sh b/LXC/debian_lxc.sh new file mode 100644 index 0000000..ffffd21 --- /dev/null +++ b/LXC/debian_lxc.sh @@ -0,0 +1,1357 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)" + + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.debian.org/ + +APP="Debian" +var_tags="${var_tags:-os}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /var ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_info "Updating $APP LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated $APP LXC" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" + + + +# Debian 12 +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/debian-vm.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) + +function header_info { + clear + cat <<"EOF" + ____ __ _ ______ + / __ \___ / /_ (_)___ _____ < /__ \ + / / / / _ \/ __ \/ / __ `/ __ \ / /__/ / + / /_/ / __/ /_/ / / /_/ / / / / / // __/ +/_____/\___/_.___/_/\__,_/_/ /_/ /_//____/ + +EOF +} +header_info +echo -e "\n Loading..." +GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//') +RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" +METHOD="" +NSAPP="debian-vm" +var_os="debian" +var_version="12" + +YW=$(echo "\033[33m") +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +BGN=$(echo "\033[4;92m") +GN=$(echo "\033[1;92m") +DGN=$(echo "\033[32m") +CL=$(echo "\033[m") + +CL=$(echo "\033[m") +BOLD=$(echo "\033[1m") +BFR="\\r\\033[K" +HOLD=" " +TAB=" " + +CM="${TAB}✔️${TAB}${CL}" +CROSS="${TAB}✖️${TAB}${CL}" +INFO="${TAB}💡${TAB}${CL}" +OS="${TAB}🖥️${TAB}${CL}" +CONTAINERTYPE="${TAB}📦${TAB}${CL}" +DISKSIZE="${TAB}💾${TAB}${CL}" +CPUCORE="${TAB}🧠${TAB}${CL}" +RAMSIZE="${TAB}🛠️${TAB}${CL}" +CONTAINERID="${TAB}🆔${TAB}${CL}" +HOSTNAME="${TAB}🏠${TAB}${CL}" +BRIDGE="${TAB}🌉${TAB}${CL}" +GATEWAY="${TAB}🌐${TAB}${CL}" +DEFAULT="${TAB}⚙️${TAB}${CL}" +MACADDRESS="${TAB}🔗${TAB}${CL}" +VLANTAG="${TAB}🏷️${TAB}${CL}" +CREATING="${TAB}🚀${TAB}${CL}" +ADVANCED="${TAB}🧩${TAB}${CL}" +CLOUD="${TAB}☁️${TAB}${CL}" + +THIN="discard=on,ssd=1," +set -e +trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +trap cleanup EXIT +trap 'post_update_to_api "failed" "130"' SIGINT +trap 'post_update_to_api "failed" "143"' SIGTERM +trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP +function error_handler() { + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + post_update_to_api "failed" "${exit_code}" + echo -e "\n$error_message\n" + cleanup_vmid +} + +function get_valid_nextid() { + local try_id + try_id=$(pvesh get /cluster/nextid) + while true; do + if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then + try_id=$((try_id + 1)) + continue + fi + if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then + try_id=$((try_id + 1)) + continue + fi + break + done + echo "$try_id" +} + +function cleanup_vmid() { + if qm status $VMID &>/dev/null; then + qm stop $VMID &>/dev/null + qm destroy $VMID &>/dev/null + fi +} + +function cleanup() { + local exit_code=$? + popd >/dev/null + if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then + if [[ $exit_code -eq 0 ]]; then + post_update_to_api "done" "none" + else + post_update_to_api "failed" "$exit_code" + fi + fi + rm -rf $TEMP_DIR +} + +TEMP_DIR=$(mktemp -d) +pushd $TEMP_DIR >/dev/null +if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Debian 12 VM" --yesno "This will create a New Debian 12 VM. Proceed?" 10 58; then + : +else + header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit +fi + +function msg_info() { + local msg="$1" + echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" +} + +function msg_ok() { + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" +} + +function msg_error() { + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" +} + +function check_root() { + if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then + clear + msg_error "Please run this script as root." + echo -e "\nExiting..." + sleep 2 + exit + fi +} + +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.2 +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 105 + fi + return 0 + fi + + # Check for Proxmox VE 9.x: allow 9.0 and 9.2 + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 2)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.2" + exit 105 + fi + return 0 + fi + + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.2" + exit 105 +} + +function arch_check() { + if [ "$(dpkg --print-architecture)" != "amd64" ]; then + echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" + echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" + echo -e "Exiting..." + sleep 2 + exit + fi +} + +function ssh_check() { + if command -v pveversion >/dev/null 2>&1; then + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then + echo "you've been warned" + else + clear + exit + fi + fi + fi +} + +function exit-script() { + clear + echo -e "\n${CROSS}${RD}User exited script${CL}\n" + exit +} + +function default_settings() { + VMID=$(get_valid_nextid) + FORMAT=",efitype=4m" + MACHINE="" + DISK_SIZE="8G" + DISK_CACHE="" + HN="debian" + CPU_TYPE="" + CORE_COUNT="2" + RAM_SIZE="2048" + BRG="vmbr0" + MAC="$GEN_MAC" + VLAN="" + MTU="" + START_VM="yes" + CLOUD_INIT="no" + METHOD="default" + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" + echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}" + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 12 VM using the above default settings${CL}" +} + +function advanced_settings() { + METHOD="advanced" + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID=$(get_valid_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 2 + continue + fi + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break + else + exit-script + fi + done + + if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ + "i440fx" "Machine i440fx" ON \ + "q35" "Machine q35" OFF \ + 3>&1 1>&2 2>&3); then + if [ $MACH = q35 ]; then + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + FORMAT="" + MACHINE=" -machine q35" + else + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + FORMAT=",efitype=4m" + MACHINE="" + fi + else + exit-script + fi + + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') + if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then + DISK_SIZE="${DISK_SIZE}G" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + else + echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" + exit-script + fi + else + exit-script + fi + + if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "None (Default)" ON \ + "1" "Write Through" OFF \ + 3>&1 1>&2 2>&3); then + if [ $DISK_CACHE = "1" ]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" + DISK_CACHE="cache=writethrough," + else + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + DISK_CACHE="" + fi + else + exit-script + fi + + if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 debian --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VM_NAME ]; then + HN="debian" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + else + HN=$(echo "${VM_NAME,,}" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//') + if [ "$HN" != "${VM_NAME,,}" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58 + fi + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + fi + else + exit-script + fi + + if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "KVM64 (Default)" ON \ + "1" "Host" OFF \ + 3>&1 1>&2 2>&3); then + if [ $CPU_TYPE1 = "1" ]; then + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" + CPU_TYPE=" -cpu host" + else + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + CPU_TYPE="" + fi + else + exit-script + fi + + while true; do + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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"; fi + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58 + else + exit-script + fi + done + + while true; do + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$RAM_SIZE" ]; then RAM_SIZE="2048"; fi + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58 + else + exit-script + fi + done + + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + else + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + fi + else + exit-script + fi + + while true; do + if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" + break + fi + if [[ "$MAC1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then + MAC="$MAC1" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58 + else + exit-script + fi + done + + while true; do + if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + if [[ "$VLAN1" =~ ^[0-9]+$ ]] && [ "$VLAN1" -ge 1 ] && [ "$VLAN1" -le 4094 ]; then + VLAN=",tag=$VLAN1" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit-script + fi + done + + while true; do + if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + if [[ "$MTU1" =~ ^[0-9]+$ ]] && [ "$MTU1" -ge 576 ] && [ "$MTU1" -le 65520 ]; then + MTU=",mtu=$MTU1" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58 + else + exit-script + fi + done + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" --yesno "Configure the VM with Cloud-init?" --defaultno 10 58); then + echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}yes${CL}" + CLOUD_INIT="yes" + else + echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}" + CLOUD_INIT="no" + fi + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + START_VM="yes" + else + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}" + START_VM="no" + fi + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Debian 12 VM?" --no-button Do-Over 10 58); then + echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 12 VM using the above advanced settings${CL}" + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +function start_script() { + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +check_root +arch_check +pve_check +ssh_check +start_script + +post_to_api_vm + +msg_info "Validating Storage" +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 +elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} +else + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + done +fi +msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." +msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." +msg_info "Retrieving the URL for the Debian 12 Qcow2 Disk Image" +if [ "$CLOUD_INIT" == "yes" ]; then + URL=https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2 +else + URL=https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-amd64.qcow2 +fi +sleep 2 +msg_ok "${CL}${BL}${URL}${CL}" +curl -f#SL -o "$(basename "$URL")" "$URL" +echo -en "\e[1A\e[0K" +FILE=$(basename $URL) +msg_ok "Downloaded ${CL}${BL}${FILE}${CL}" + +STORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}') +case $STORAGE_TYPE in +nfs | dir) + DISK_EXT=".qcow2" + DISK_REF="$VMID/" + DISK_IMPORT="-format qcow2" + THIN="" + ;; +btrfs) + DISK_EXT=".raw" + DISK_REF="$VMID/" + DISK_IMPORT="-format raw" + FORMAT=",efitype=4m" + THIN="" + ;; +*) + DISK_EXT="" + DISK_REF="" + DISK_IMPORT="-format raw" + ;; +esac +for i in {0,1}; do + disk="DISK$i" + eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-} + eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk} +done + +msg_info "Creating a Debian 12 VM" +qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \ + -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci +pvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null +qm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null +if [ "$CLOUD_INIT" == "yes" ]; then + qm set $VMID \ + -efidisk0 ${DISK0_REF}${FORMAT} \ + -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \ + -scsi1 ${STORAGE}:cloudinit \ + -boot order=scsi0 \ + -serial0 socket >/dev/null +else + qm set $VMID \ + -efidisk0 ${DISK0_REF}${FORMAT} \ + -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \ + -boot order=scsi0 \ + -serial0 socket >/dev/null +fi +DESCRIPTION=$( + cat <<EOF +<div align='center'> + <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'> + <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> + </a> + + <h2 style='font-size: 24px; margin: 20px 0;'>Debian VM</h2> + + <p style='margin: 16px 0;'> + <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'> + <img src='https://img.shields.io/badge/☕-Buy us a coffee-blue' alt='spend Coffee' /> + </a> + </p> + + <span style='margin: 0 10px;'> + <i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a> + </span> +</div> +EOF +) +qm set "$VMID" -description "$DESCRIPTION" >/dev/null +if [ -n "$DISK_SIZE" ]; then + msg_info "Resizing disk to $DISK_SIZE GB" + qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null +else + msg_info "Using default disk size of $DEFAULT_DISK_SIZE GB" + qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null +fi + +msg_ok "Created a Debian 12 VM ${CL}${BL}(${HN})" +if [ "$START_VM" == "yes" ]; then + msg_info "Starting Debian 12 VM" + qm start $VMID + msg_ok "Started Debian 12 VM" +fi + +msg_ok "Completed successfully!\n" +echo "More Info at https://github.com/community-scripts/ProxmoxVE/discussions/836" + + + + + +# Debian 13 +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/vm/debian-13-vm.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) + +function header_info { + clear + cat <<"EOF" + ____ __ _ ________ + / __ \___ / /_ (_)___ _____ < /__ / + / / / / _ \/ __ \/ / __ `/ __ \ / / /_ < + / /_/ / __/ /_/ / / /_/ / / / / / /___/ / +/_____/\___/_.___/_/\__,_/_/ /_/ /_//____/ + (Trixie) +EOF +} +header_info +echo -e "\n Loading..." +GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//') +RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" +METHOD="" +NSAPP="debian-13-vm" +var_os="debian" +var_version="13" + +YW=$(echo "\033[33m") +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +BGN=$(echo "\033[4;92m") +GN=$(echo "\033[1;92m") +DGN=$(echo "\033[32m") +CL=$(echo "\033[m") + +CL=$(echo "\033[m") +BOLD=$(echo "\033[1m") +BFR="\\r\\033[K" +HOLD=" " +TAB=" " + +CM="${TAB}✔️${TAB}${CL}" +CROSS="${TAB}✖️${TAB}${CL}" +INFO="${TAB}💡${TAB}${CL}" +OS="${TAB}🖥️${TAB}${CL}" +CONTAINERTYPE="${TAB}📦${TAB}${CL}" +DISKSIZE="${TAB}💾${TAB}${CL}" +CPUCORE="${TAB}🧠${TAB}${CL}" +RAMSIZE="${TAB}🛠️${TAB}${CL}" +CONTAINERID="${TAB}🆔${TAB}${CL}" +HOSTNAME="${TAB}🏠${TAB}${CL}" +BRIDGE="${TAB}🌉${TAB}${CL}" +GATEWAY="${TAB}🌐${TAB}${CL}" +DEFAULT="${TAB}⚙️${TAB}${CL}" +MACADDRESS="${TAB}🔗${TAB}${CL}" +VLANTAG="${TAB}🏷️${TAB}${CL}" +CREATING="${TAB}🚀${TAB}${CL}" +ADVANCED="${TAB}🧩${TAB}${CL}" +CLOUD="${TAB}☁️${TAB}${CL}" + +THIN="discard=on,ssd=1," +set -e +trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +trap cleanup EXIT +trap 'post_update_to_api "failed" "130"' SIGINT +trap 'post_update_to_api "failed" "143"' SIGTERM +trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP +function error_handler() { + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + post_update_to_api "failed" "${exit_code}" + echo -e "\n$error_message\n" + cleanup_vmid +} + +function get_valid_nextid() { + local try_id + try_id=$(pvesh get /cluster/nextid) + while true; do + if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then + try_id=$((try_id + 1)) + continue + fi + if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then + try_id=$((try_id + 1)) + continue + fi + break + done + echo "$try_id" +} + +function cleanup_vmid() { + if qm status $VMID &>/dev/null; then + qm stop $VMID &>/dev/null + qm destroy $VMID &>/dev/null + fi +} + +function cleanup() { + local exit_code=$? + popd >/dev/null + if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then + if [[ $exit_code -eq 0 ]]; then + post_update_to_api "done" "none" + else + post_update_to_api "failed" "$exit_code" + fi + fi + rm -rf $TEMP_DIR +} + +TEMP_DIR=$(mktemp -d) +pushd $TEMP_DIR >/dev/null +if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Debian 13 VM" --yesno "This will create a New Debian 13 VM. Proceed?" 10 58; then + : +else + header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit +fi + +function msg_info() { + local msg="$1" + echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}" +} + +function msg_ok() { + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" +} + +function msg_error() { + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" +} + +function check_root() { + if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then + clear + msg_error "Please run this script as root." + echo -e "\nExiting..." + sleep 2 + exit + fi +} + +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +# Supported: Proxmox VE 8.0.x – 8.9.x, 9.0 and 9.2 +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 105 + fi + return 0 + fi + + # Check for Proxmox VE 9.x: allow 9.0 and 9.2 + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 2)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 9.0 – 9.2" + exit 105 + fi + return 0 + fi + + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0 – 9.2" + exit 105 +} + +function arch_check() { + if [ "$(dpkg --print-architecture)" != "amd64" ]; then + echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" + echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" + echo -e "Exiting..." + sleep 2 + exit + fi +} + +function ssh_check() { + if command -v pveversion >/dev/null 2>&1; then + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then + echo "you've been warned" + else + clear + exit + fi + fi + fi +} + +function exit-script() { + clear + echo -e "\n${CROSS}${RD}User exited script${CL}\n" + exit +} + +function select_cloud_init() { + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" \ + --yesno "Enable Cloud-Init for VM configuration?\n\nCloud-Init allows automatic configuration of:\n- User accounts and passwords\n- SSH keys\n- Network settings (DHCP/Static)\n- DNS configuration\n\nYou can also configure these settings later in Proxmox UI.\n\nNote: Without Cloud-Init, the nocloud image will be used with console auto-login." --defaultno 18 68); then + CLOUD_INIT="yes" + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}yes${CL}" + else + CLOUD_INIT="no" + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}no${CL}" + fi +} + +function default_settings() { + VMID=$(get_valid_nextid) + FORMAT=",efitype=4m" + MACHINE="" + DISK_SIZE="8G" + DISK_CACHE="" + HN="debian" + CPU_TYPE="" + CORE_COUNT="2" + RAM_SIZE="2048" + BRG="vmbr0" + MAC="$GEN_MAC" + VLAN="" + MTU="" + START_VM="yes" + METHOD="default" + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" + select_cloud_init + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above default settings${CL}" +} + +function advanced_settings() { + METHOD="advanced" + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) + while true; do + if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 "$VMID" --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID=$(get_valid_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 2 + continue + fi + echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break + else + exit-script + fi + done + + if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ + "i440fx" "Machine i440fx" ON \ + "q35" "Machine q35" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$MACH" = q35 ]; then + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + FORMAT="" + MACHINE=" -machine q35" + else + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}" + FORMAT=",efitype=4m" + MACHINE="" + fi + else + exit-script + fi + + if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') + if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then + DISK_SIZE="${DISK_SIZE}G" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" + else + echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" + exit-script + fi + else + exit-script + fi + + if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "None (Default)" ON \ + "1" "Write Through" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$DISK_CACHE" = "1" ]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" + DISK_CACHE="cache=writethrough," + else + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" + DISK_CACHE="" + fi + else + exit-script + fi + + if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 debian --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VM_NAME" ]; then + HN="debian" + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + else + HN=$(echo "${VM_NAME,,}" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//') + if [ "$HN" != "${VM_NAME,,}" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58 + fi + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + fi + else + exit-script + fi + + if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "0" "KVM64 (Default)" ON \ + "1" "Host" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$CPU_TYPE1" = "1" ]; then + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" + CPU_TYPE=" -cpu host" + else + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + CPU_TYPE="" + fi + else + exit-script + fi + + while true; do + if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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"; fi + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58 + else + exit-script + fi + done + + while true; do + if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$RAM_SIZE" ]; then RAM_SIZE="2048"; fi + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58 + else + exit-script + fi + done + + if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + else + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + fi + else + exit-script + fi + + while true; do + if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}" + break + fi + if [[ "$MAC1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then + MAC="$MAC1" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58 + else + exit-script + fi + done + + while true; do + if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + if [[ "$VLAN1" =~ ^[0-9]+$ ]] && [ "$VLAN1" -ge 1 ] && [ "$VLAN1" -le 4094 ]; then + VLAN=",tag=$VLAN1" + echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58 + else + exit-script + fi + done + + while true; do + if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "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 "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + if [[ "$MTU1" =~ ^[0-9]+$ ]] && [ "$MTU1" -ge 576 ] && [ "$MTU1" -le 65520 ]; then + MTU=",mtu=$MTU1" + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + break + fi + whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58 + else + exit-script + fi + done + + select_cloud_init + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" + START_VM="yes" + else + echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}no${CL}" + START_VM="no" + fi + + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Debian 13 VM?" --no-button Do-Over 10 58); then + echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above advanced settings${CL}" + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +function start_script() { + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "Use Default Settings?" --no-button Advanced 10 58); then + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings${CL}" + advanced_settings + fi +} + +check_root +arch_check +pve_check +ssh_check +start_script + +post_to_api_vm + +msg_info "Validating Storage" +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 +elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} +else + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ + "Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + done +fi +msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." +msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." + +# ============================================================================== +# PREREQUISITES +# ============================================================================== +if ! command -v virt-customize &>/dev/null; then + msg_info "Installing libguestfs-tools" + apt-get update >/dev/null 2>&1 + apt-get install -y libguestfs-tools >/dev/null 2>&1 + msg_ok "Installed libguestfs-tools" +fi + +msg_info "Retrieving the URL for the Debian 13 Qcow2 Disk Image" +if [ "$CLOUD_INIT" == "yes" ]; then + URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2 +else + URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2 +fi +sleep 2 +msg_ok "${CL}${BL}${URL}${CL}" +curl -f#SL -o "$(basename "$URL")" "$URL" +echo -en "\e[1A\e[0K" +FILE=$(basename $URL) +msg_ok "Downloaded ${CL}${BL}${FILE}${CL}" + +# ============================================================================== +# IMAGE CUSTOMIZATION +# ============================================================================== +msg_info "Customizing ${FILE} image" + +WORK_FILE=$(mktemp --suffix=.qcow2) +cp "$FILE" "$WORK_FILE" + +# Set hostname +virt-customize -q -a "$WORK_FILE" --hostname "${HN}" >/dev/null 2>&1 + +# Prepare for unique machine-id on first boot +virt-customize -q -a "$WORK_FILE" --run-command "truncate -s 0 /etc/machine-id" >/dev/null 2>&1 +virt-customize -q -a "$WORK_FILE" --run-command "rm -f /var/lib/dbus/machine-id" >/dev/null 2>&1 + +# Disable systemd-firstboot to prevent interactive prompts blocking the console +virt-customize -q -a "$WORK_FILE" --run-command "systemctl disable systemd-firstboot.service 2>/dev/null; rm -f /etc/systemd/system/sysinit.target.wants/systemd-firstboot.service; ln -sf /dev/null /etc/systemd/system/systemd-firstboot.service" >/dev/null 2>&1 || true + +# Pre-seed firstboot settings so it won't prompt even if triggered +virt-customize -q -a "$WORK_FILE" --run-command "echo 'Etc/UTC' > /etc/timezone && ln -sf /usr/share/zoneinfo/Etc/UTC /etc/localtime" >/dev/null 2>&1 || true +virt-customize -q -a "$WORK_FILE" --run-command "touch /etc/locale.conf" >/dev/null 2>&1 || true + +if [ "$CLOUD_INIT" == "yes" ]; then + # Cloud-Init handles SSH and login + virt-customize -q -a "$WORK_FILE" --run-command "sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config" >/dev/null 2>&1 || true + virt-customize -q -a "$WORK_FILE" --run-command "sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config" >/dev/null 2>&1 || true +else + # Configure auto-login on serial console (ttyS0) and virtual console (tty1) + virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d" >/dev/null 2>&1 || true + virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf << EOF +[Service] +ExecStart= +ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM +EOF' >/dev/null 2>&1 || true + virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/systemd/system/getty@tty1.service.d" >/dev/null 2>&1 || true + virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF +[Service] +ExecStart= +ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM +EOF' >/dev/null 2>&1 || true +fi + +msg_ok "Customized image" + +STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}') +case $STORAGE_TYPE in +nfs | dir) + DISK_EXT=".qcow2" + DISK_REF="$VMID/" + DISK_IMPORT="-format qcow2" + THIN="" + ;; +btrfs) + DISK_EXT=".raw" + DISK_REF="$VMID/" + DISK_IMPORT="-format raw" + FORMAT=",efitype=4m" + THIN="" + ;; +*) + DISK_EXT="" + DISK_REF="" + DISK_IMPORT="-format raw" + ;; +esac +for i in {0,1}; do + disk="DISK$i" + eval DISK"${i}"=vm-"${VMID}"-disk-"${i}"${DISK_EXT:-} + eval DISK"${i}"_REF="${STORAGE}":"${DISK_REF:-}"${!disk} +done + +msg_info "Creating a Debian 13 VM" +qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \ + -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci +pvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null +qm importdisk $VMID ${WORK_FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null +if [ "$CLOUD_INIT" == "yes" ]; then + qm set $VMID \ + -efidisk0 ${DISK0_REF}${FORMAT} \ + -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \ + -scsi1 ${STORAGE}:cloudinit \ + -boot order=scsi0 \ + -serial0 socket >/dev/null +else + qm set $VMID \ + -efidisk0 ${DISK0_REF}${FORMAT} \ + -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \ + -boot order=scsi0 \ + -serial0 socket >/dev/null +fi + +# Clean up work file +rm -f "$WORK_FILE" + +DESCRIPTION=$( + cat <<EOF +<div align='center'> + <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'> + <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> + </a> + + <h2 style='font-size: 24px; margin: 20px 0;'>Debian VM</h2> + + <p style='margin: 16px 0;'> + <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'> + <img src='https://img.shields.io/badge/☕-Buy us a coffee-blue' alt='spend Coffee' /> + </a> + </p> + + <span style='margin: 0 10px;'> + <i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a> + </span> + <span style='margin: 0 10px;'> + <i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i> + <a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a> + </span> +</div> +EOF +) +qm set $VMID -description "$DESCRIPTION" >/dev/null +if [ -n "$DISK_SIZE" ]; then + msg_info "Resizing disk to $DISK_SIZE GB" + qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null +else + msg_info "Using default disk size of $DEFAULT_DISK_SIZE GB" + qm resize $VMID scsi0 ${DEFAULT_DISK_SIZE} >/dev/null +fi + +msg_ok "Created a Debian 13 VM ${CL}${BL}(${HN})" +if [ "$START_VM" == "yes" ]; then + msg_info "Starting Debian 13 VM" + qm start $VMID + msg_ok "Started Debian 13 VM" +fi + +msg_ok "Completed successfully!\n" +echo "More Info at https://github.com/community-scripts/ProxmoxVE/discussions/836" + + + diff --git a/LXC/hev-socks5-server.sh b/LXC/hev-socks5-server.sh new file mode 100644 index 0000000..d8e9bc0 --- /dev/null +++ b/LXC/hev-socks5-server.sh @@ -0,0 +1,61 @@ +:1080 + +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/hev-socks5-server.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: miviro +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/heiher/hev-socks5-server + +APP="hev-socks5-server" +var_tags="${var_tags:-proxy;socks5}" +var_cpu="${var_cpu:-1}" +var_ram="${var_ram:-512}" +var_disk="${var_disk:-2}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + if [[ ! -f /opt/${APP} ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + + if check_for_gh_release "hev-socks5-server" "heiher/hev-socks5-server"; then + msg_info "Stopping Service" + systemctl stop hev-socks5-server + msg_ok "Stopped Service" + + fetch_and_deploy_gh_release "hev-socks5-server" "heiher/hev-socks5-server" "singlefile" "latest" "/opt" "hev-socks5-server-linux-x86_64" + + msg_info "Starting Service" + systemctl start hev-socks5-server + msg_ok "Started Service" + msg_ok "Updated successfully!" + fi + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it with a SOCKS5 client using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}${IP}:1080${CL}" +echo -e "${INFO}${YW} and the credentials stored at /root/hev.creds${CL}" diff --git a/LXC/lxc_PDM_install.sh b/LXC/lxc_PDM_install.sh new file mode 100644 index 0000000..b3600f2 --- /dev/null +++ b/LXC/lxc_PDM_install.sh @@ -0,0 +1,73 @@ +#pve +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/proxmox-datacenter-manager.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 community-scripts ORG +# Author: CrazyWolf13 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: Proxmox Server Solution GmbH + +APP="Proxmox-Datacenter-Manager" +var_tags="${var_tags:-datacenter}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-10}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -e /usr/sbin/proxmox-datacenter-manager-admin ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if grep -q 'Debian GNU/Linux 12' /etc/os-release && [ -f /etc/apt/sources.list.d/proxmox-release-bookworm.list ] && [ -f /etc/apt/sources.list.d/pdm-test.list ]; then + msg_info "Updating outdated outdated source formats" + echo "deb [signed-by=/usr/share/keyrings/proxmox-archive-keyring.gpg] http://download.proxmox.com/debian/pdm bookworm pdm-test" >/etc/apt/sources.list.d/pdm-test.list + curl -fsSL https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg -o /usr/share/keyrings/proxmox-archive-keyring.gpg + rm -f /etc/apt/keyrings/proxmox-release-bookworm.gpg /etc/apt/sources.list.d/proxmox-release-bookworm.list + $STD apt update + msg_ok "Updated old sources" + fi + + if grep -q 'Debian GNU/Linux 13' /etc/os-release; then + if [ -f "/etc/apt/sources.list.d/pdm-test.sources" ]; then + if ! grep -qx "Enabled: false" "/etc/apt/sources.list.d/pdm-test.sources"; then + echo "Enabled: false" >> "/etc/apt/sources.list.d/pdm-test.sources" + setup_deb822_repo \ + "pdm" \ + "https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg" \ + "http://download.proxmox.com/debian/pdm" \ + "trixie" \ + "pdm-no-subscription" + fi + fi + fi + + msg_info "Updating $APP LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated $APP LXC" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:8443${CL}" diff --git a/Proxmox/PVE/PVE CPU Scaling Governor.sh b/Proxmox/PVE/PVE CPU Scaling Governor.sh new file mode 100644 index 0000000..5105ac3 --- /dev/null +++ b/Proxmox/PVE/PVE CPU Scaling Governor.sh @@ -0,0 +1,72 @@ + +Регулятор масштабирования ЦП определяет, как регулируется частота ЦП в зависимости от рабочей нагрузки, с целью либо экономии энергии, либо повышения производительности. Изменяя частоту в большую или меньшую сторону, операционная система может оптимизировать использование ЦП и, по возможности, экономить энергию. Типичные регуляторы масштабирования + +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/scaling-governor.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +set -e + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "scaling-governor" "pve" + +header_info() { + clear + cat <<EOF + ________ __ __ _____ + / ___/ _ \/ / / / / ___/__ _ _____ _______ ___ _______ +/ /__/ ___/ /_/ / / (_ / _ \ |/ / -_) __/ _ \/ _ \/ __(_-< +\___/_/ \____/ \___/\___/___/\__/_/ /_//_/\___/_/ /___/ +EOF +} +header_info +whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU Scaling Governors" --yesno "View/Change CPU Scaling Governors. Proceed?" 10 58 +current_governor=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) +GOVERNORS_MENU=() +MSG_MAX_LENGTH=0 +while read -r TAG ITEM; do + OFFSET=2 + ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET + GOVERNORS_MENU+=("$TAG" "$ITEM " "OFF") +done < <(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors | tr ' ' '\n' | grep -v "$current_governor") +scaling_governor=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Current CPU Scaling Governor is set to $current_governor" --checklist "\nSelect the Scaling Governor to use:\n" 16 $((MSG_MAX_LENGTH + 58)) 6 "${GOVERNORS_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"') +[ -z "$scaling_governor" ] && { + whiptail --backtitle "Proxmox VE Helper Scripts" --title "No CPU Scaling Governor Selected" --msgbox "It appears that no CPU Scaling Governor was selected" 10 68 + clear + exit +} +echo "${scaling_governor}" | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor >/dev/null +current_governor=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor) +whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox --title "Current CPU Scaling Governor" "\nCurrent CPU Scaling Governor has been set to $current_governor\n" 10 60 +CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU Scaling Governor" --menu "This will establish a crontab to maintain the CPU Scaling Governor configuration across reboots.\n \nSetup a crontab?" 14 68 2 \ + "yes" " " \ + "no" " " 3>&2 2>&1 1>&3) + +case $CHOICE in +yes) + set +e + NEW_CRONTAB_COMMAND="(sleep 60 && echo \"$current_governor\" | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor)" + EXISTING_CRONTAB=$(crontab -l 2>/dev/null) + if [[ -n "$EXISTING_CRONTAB" ]]; then + TEMP_CRONTAB_FILE=$(mktemp) + echo "$EXISTING_CRONTAB" | grep -v "@reboot (sleep 60 && echo*" >"$TEMP_CRONTAB_FILE" + crontab "$TEMP_CRONTAB_FILE" + rm "$TEMP_CRONTAB_FILE" + fi + ( + crontab -l 2>/dev/null + echo "@reboot $NEW_CRONTAB_COMMAND" + ) | crontab - + echo -e "\nCrontab Set (use 'crontab -e' to check)" + ;; +no) + echo -e "\n\033[31mNOTE: Settings return to default after reboot\033[m\n" + ;; +esac +echo -e "Current CPU Scaling Governor is set to \033[36m$current_governor\033[m\n" diff --git a/Proxmox/PVE/PVE Clean Orphaned LVM.sh b/Proxmox/PVE/PVE Clean Orphaned LVM.sh new file mode 100644 index 0000000..94bb788 --- /dev/null +++ b/Proxmox/PVE/PVE Clean Orphaned LVM.sh @@ -0,0 +1,99 @@ + +# Этот скрипт помогает пользователям Proxmox выявлять и удалять "осиротевшие" тома LVM, +# которые больше не связаны ни с одной виртуальной машиной или контейнером LXC. +# Он сканирует все тома LVM, обнаруживает неиспользуемые и предоставляет интерактивное приглашение для их безопасного +# удаления. Критически важные для системы тома, такие как корневой раздел, раздел подкачки и раздел +# данных, исключаются, чтобы предотвратить случайное удаление. + + +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/clean-orphaned-lvm.sh)" + + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info { + clear + cat <<"EOF" + ____ ________ ____ __ __ __ _ ____ ___ + / __ \_________ _ ______ ___ ____ _ __ / ____/ /__ ____ _____ / __ \_________ / /_ ____ _____ ___ ____/ / / /| | / / |/ /____ + / /_/ / ___/ __ \| |/_/ __ `__ \/ __ \| |/_/ / / / / _ \/ __ `/ __ \ / / / / ___/ __ \/ __ \/ __ `/ __ \/ _ \/ __ / / / | | / / /|_/ / ___/ + / ____/ / / /_/ /> </ / / / / / /_/ /> < / /___/ / __/ /_/ / / / / / /_/ / / / /_/ / / / / /_/ / / / / __/ /_/ / / /__| |/ / / / (__ ) +/_/ /_/ \____/_/|_/_/ /_/ /_/\____/_/|_| \____/_/\___/\__,_/_/ /_/ \____/_/ / .___/_/ /_/\__,_/_/ /_/\___/\__,_/ /_____/___/_/ /_/____/ + /_/ +EOF +} + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "clean-orphaned-lvm" "pve" + +# Function to check for orphaned LVM volumes +function find_orphaned_lvm { + echo -e "\n🔍 Scanning for orphaned LVM volumes...\n" + + orphaned_volumes=() + while read -r lv vg size seg_type; do + # Exclude system-critical LVs and Ceph OSDs + if [[ "$lv" == "data" || "$lv" == "root" || "$lv" == "swap" || "$lv" =~ ^osd-block- ]]; then + continue + fi + + # Exclude thin pools (any name) + if [[ "$seg_type" == "thin-pool" ]]; then + continue + fi + + container_id=$(echo "$lv" | grep -oE "[0-9]+" | head -1) + # Check if the ID exists as a VM or LXC container on any cluster node + if compgen -G "/etc/pve/nodes/*/lxc/${container_id}.conf" >/dev/null 2>&1 || + compgen -G "/etc/pve/nodes/*/qemu-server/${container_id}.conf" >/dev/null 2>&1; then + continue + fi + + orphaned_volumes+=("$lv" "$vg" "$size") + done < <(lvs --noheadings -o lv_name,vg_name,lv_size,seg_type --separator ' ' 2>/dev/null | awk '{print $1, $2, $3, $4}') + + # Display orphaned volumes + echo -e "❗ The following orphaned LVM volumes were found:\n" + printf "%-25s %-10s %-10s\n" "LV Name" "VG" "Size" + printf "%-25s %-10s %-10s\n" "-------------------------" "----------" "----------" + + for ((i = 0; i < ${#orphaned_volumes[@]}; i += 3)); do + printf "%-25s %-10s %-10s\n" "${orphaned_volumes[i]}" "${orphaned_volumes[i + 1]}" "${orphaned_volumes[i + 2]}" + done + echo "" +} + +# Function to delete selected volumes +function delete_orphaned_lvm { + for ((i = 0; i < ${#orphaned_volumes[@]}; i += 3)); do + lv="${orphaned_volumes[i]}" + vg="${orphaned_volumes[i + 1]}" + size="${orphaned_volumes[i + 2]}" + + read -p "❓ Do you want to delete $lv (VG: $vg, Size: $size)? [y/N]: " confirm + if [[ "$confirm" =~ ^[Yy]$ ]]; then + echo -e "🗑️ Deleting $lv from $vg..." + lvremove -f "$vg/$lv" + if [ $? -eq 0 ]; then + echo -e "✅ Successfully deleted $lv.\n" + else + echo -e "❌ Failed to delete $lv.\n" + fi + else + echo -e "⚠️ Skipping $lv.\n" + fi + done +} + +# Run script +header_info +find_orphaned_lvm +delete_orphaned_lvm + +echo -e "✅ Cleanup process completed!\n" diff --git a/Proxmox/PVE/PVE Cron LXC Updater.sh b/Proxmox/PVE/PVE Cron LXC Updater.sh new file mode 100644 index 0000000..37b1bea --- /dev/null +++ b/Proxmox/PVE/PVE Cron LXC Updater.sh @@ -0,0 +1,355 @@ +# Этот скрипт добавит/удалит расписание crontab, которое обновляет операционную систему всех LXC каждое воскресенье в полночь. + +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# +# This script manages a local cron job for automatic LXC container OS updates. +# The update script is downloaded once, displayed for review, and installed +# locally. Cron runs the local copy — no remote code execution at runtime. +# +# bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/cron-update-lxcs.sh)" + +set -euo pipefail + +REPO_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main" +SCRIPT_URL="${REPO_URL}/tools/pve/update-lxcs-cron.sh" +LOCAL_SCRIPT="/usr/local/bin/update-lxcs.sh" +CONF_FILE="/etc/update-lxcs.conf" +LOG_FILE="/var/log/update-lxcs-cron.log" +CRON_ENTRY="0 0 * * 0 ${LOCAL_SCRIPT} >>${LOG_FILE} 2>&1" + +clear +cat <<"EOF" + ______ __ __ __ __ __ _ ________ + / ____/________ ____ / / / /___ ____/ /___ _/ /____ / / | |/ / ____/____ + / / / ___/ __ \/ __ \ / / / / __ \/ __ / __ `/ __/ _ \ / / | / / / ___/ +/ /___/ / / /_/ / / / / / /_/ / /_/ / /_/ / /_/ / /_/ __/ / /___/ / /___(__ ) +\____/_/ \____/_/ /_/ \____/ .___/\__,_/\__,_/\__/\___/ /_____/_/|_\____/____/ + /_/ +EOF + +info() { echo -e "\n \e[36m[Info]\e[0m $1"; } +ok() { echo -e " \e[32m[OK]\e[0m $1"; } +err() { echo -e " \e[31m[Error]\e[0m $1" >&2; } + +confirm() { + local prompt="${1:-Proceed?}" + while true; do + read -rp " ${prompt} (y/n): " yn + case $yn in + [Yy]*) return 0 ;; + [Nn]*) return 1 ;; + *) echo " Please answer yes or no." ;; + esac + done +} + +download_script() { + local tmp + tmp=$(mktemp) + if ! curl -fsSL -o "$tmp" "$SCRIPT_URL"; then + err "Failed to download script from:\n ${SCRIPT_URL}" + rm -f "$tmp" + return 1 + fi + echo "$tmp" +} + +review_script() { + local file="$1" + local hash + hash=$(sha256sum "$file" | awk '{print $1}') + echo "" + echo -e " \e[1;33m─── Script Content ───────────────────────────────────────────\e[0m" + cat "$file" + echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m" + echo -e " \e[36mSHA256:\e[0m ${hash}" + echo -e " \e[36mSource:\e[0m ${SCRIPT_URL}" + echo "" +} + +remove_legacy_cron() { + if crontab -l -u root 2>/dev/null | grep -q "update-lxcs-cron.sh"; then + (crontab -l -u root 2>/dev/null | grep -v "update-lxcs-cron.sh") | crontab -u root - + ok "Removed legacy curl-based cron entry" + fi +} + +add() { + info "Downloading update script..." + local tmp + tmp=$(download_script) || exit 1 + + local hash + hash=$(sha256sum "$tmp" | awk '{print $1}') + echo "" + echo -e " \e[1;33m─── Installation Summary ─────────────────────────────────────\e[0m" + echo -e " \e[36mSource:\e[0m ${SCRIPT_URL}" + echo -e " \e[36mSHA256:\e[0m ${hash}" + echo -e " \e[36mInstall to:\e[0m ${LOCAL_SCRIPT}" + echo -e " \e[36mConfig:\e[0m ${CONF_FILE}" + echo -e " \e[36mLog file:\e[0m ${LOG_FILE}" + echo -e " \e[36mCron schedule:\e[0m Every Sunday at midnight (0 0 * * 0)" + echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m" + echo "" + + if confirm "Review script content before installing?"; then + review_script "$tmp" + fi + + if ! confirm "Install this script and activate cron schedule?"; then + rm -f "$tmp" + echo " Aborted." + exit 0 + fi + + remove_legacy_cron + + install -m 0755 "$tmp" "$LOCAL_SCRIPT" + rm -f "$tmp" + ok "Installed script to ${LOCAL_SCRIPT}" + + if [[ ! -f "$CONF_FILE" ]]; then + cat >"$CONF_FILE" <<'CONF' +# Configuration for automatic LXC container OS updates. +# Add container IDs to exclude from updates (comma-separated): +# EXCLUDE=100,101,102 +EXCLUDE= +CONF + ok "Created config ${CONF_FILE}" + fi + + ( + crontab -l -u root 2>/dev/null | grep -v "${LOCAL_SCRIPT}" || true + echo "${CRON_ENTRY}" + ) | crontab -u root - + ok "Added cron schedule: Every Sunday at midnight" + echo "" + echo -e " \e[36mLocal script:\e[0m ${LOCAL_SCRIPT}" + echo -e " \e[36mConfig:\e[0m ${CONF_FILE}" + echo -e " \e[36mLog file:\e[0m ${LOG_FILE}" + echo "" +} + +remove() { + if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then + (crontab -l -u root 2>/dev/null | grep -v "${LOCAL_SCRIPT}") | crontab -u root - + ok "Removed cron schedule" + fi + remove_legacy_cron + [[ -f "$LOCAL_SCRIPT" ]] && rm -f "$LOCAL_SCRIPT" && ok "Removed ${LOCAL_SCRIPT}" + [[ -f "$LOG_FILE" ]] && rm -f "$LOG_FILE" && ok "Removed ${LOG_FILE}" + echo -e "\n Cron Update LXCs has been fully removed." + echo -e " \e[90mNote: ${CONF_FILE} was kept (remove manually if desired).\e[0m" +} + +update_script() { + if [[ ! -f "$LOCAL_SCRIPT" ]]; then + err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first." + exit 1 + fi + + info "Downloading latest version..." + local tmp + tmp=$(download_script) || exit 1 + + if command -v diff &>/dev/null; then + local changes + changes=$(diff --color=auto "$LOCAL_SCRIPT" "$tmp" 2>/dev/null || true) + if [[ -z "$changes" ]]; then + ok "Script is already up-to-date (no changes)." + rm -f "$tmp" + return + fi + echo "" + echo -e " \e[1;33m─── Changes ──────────────────────────────────────────────────\e[0m" + echo "$changes" + echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m" + else + review_script "$tmp" + fi + + local new_hash old_hash + new_hash=$(sha256sum "$tmp" | awk '{print $1}') + old_hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}') + echo -e " \e[36mCurrent SHA256:\e[0m ${old_hash}" + echo -e " \e[36mNew SHA256:\e[0m ${new_hash}" + echo "" + + if ! confirm "Apply update?"; then + rm -f "$tmp" + echo " Aborted." + return + fi + + install -m 0755 "$tmp" "$LOCAL_SCRIPT" + rm -f "$tmp" + ok "Updated ${LOCAL_SCRIPT}" +} + +view_script() { + if [[ ! -f "$LOCAL_SCRIPT" ]]; then + err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first." + exit 1 + fi + + local view_choice + view_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "View Script" --menu "What do you want to view?" 12 60 3 \ + "Worker" "Installed update script (${LOCAL_SCRIPT##*/})" \ + "Cron" "Cron schedule & configuration" \ + "Both" "Show everything" \ + 3>&1 1>&2 2>&3) || return 0 + + case "$view_choice" in + "Worker") view_worker_script ;; + "Cron") view_cron_config ;; + "Both") view_cron_config && echo "" && view_worker_script ;; + esac +} + +view_worker_script() { + local hash + hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}') + echo "" + echo -e " \e[1;33m─── ${LOCAL_SCRIPT} ───\e[0m" + cat "$LOCAL_SCRIPT" + echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m" + echo -e " \e[36mSHA256:\e[0m ${hash}" + echo -e " \e[36mInstalled:\e[0m $(stat -c '%y' "$LOCAL_SCRIPT" 2>/dev/null | cut -d. -f1)" + echo "" +} + +view_cron_config() { + echo "" + echo -e " \e[1;33m─── Cron Configuration ───────────────────────────────────────\e[0m" + if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then + local entry + entry=$(crontab -l -u root 2>/dev/null | grep "${LOCAL_SCRIPT}") + echo -e " \e[36mCron entry:\e[0m ${entry}" + local schedule + schedule=$(echo "$entry" | awk '{print $1,$2,$3,$4,$5}') + echo -e " \e[36mSchedule:\e[0m ${schedule} ($(cron_to_human "$schedule"))" + else + echo -e " \e[31mCron:\e[0m Not configured" + fi + if [[ -f "$CONF_FILE" ]]; then + echo -e " \e[36mConfig file:\e[0m ${CONF_FILE}" + local excludes + excludes=$(grep -oP '^\s*EXCLUDE\s*=\s*\K.*' "$CONF_FILE" 2>/dev/null || true) + echo -e " \e[36mExcluded:\e[0m ${excludes:-(none)}" + echo "" + echo -e " \e[90m--- ${CONF_FILE} ---\e[0m" + cat "$CONF_FILE" + else + echo -e " \e[36mConfig file:\e[0m (not created yet)" + fi + if [[ -f "$LOG_FILE" ]]; then + local log_size + log_size=$(du -h "$LOG_FILE" | awk '{print $1}') + echo -e " \e[36mLog file:\e[0m ${LOG_FILE} (${log_size})" + fi + echo -e " \e[1;33m──────────────────────────────────────────────────────────────\e[0m" + echo "" +} + +cron_to_human() { + local schedule="$1" + case "$schedule" in + "0 0 * * 0") echo "Every Sunday at midnight" ;; + "0 0 * * *") echo "Daily at midnight" ;; + "0 * * * *") echo "Every hour" ;; + *) echo "Custom schedule" ;; + esac +} + +show_status() { + echo "" + if [[ -f "$LOCAL_SCRIPT" ]]; then + local hash + hash=$(sha256sum "$LOCAL_SCRIPT" | awk '{print $1}') + ok "Script installed: ${LOCAL_SCRIPT}" + echo -e " \e[36mSHA256:\e[0m ${hash}" + echo -e " \e[36mInstalled:\e[0m $(stat -c '%y' "$LOCAL_SCRIPT" 2>/dev/null | cut -d. -f1)" + else + err "Script not installed" + fi + + if crontab -l -u root 2>/dev/null | grep -q "${LOCAL_SCRIPT}"; then + local schedule + schedule=$(crontab -l -u root 2>/dev/null | grep "${LOCAL_SCRIPT}" | awk '{print $1,$2,$3,$4,$5}') + ok "Cron active: ${schedule}" + else + err "Cron not configured" + fi + + if [[ -f "$CONF_FILE" ]]; then + local excludes + excludes=$(grep -oP '^\s*EXCLUDE\s*=\s*\K.*' "$CONF_FILE" 2>/dev/null || echo "(none)") + echo -e " \e[36mExcluded:\e[0m ${excludes:-"(none)"}" + fi + + if [[ -f "$LOG_FILE" ]]; then + local log_size last_run + log_size=$(du -h "$LOG_FILE" | awk '{print $1}') + last_run=$(grep -oP '^\s+\K\w.*' "$LOG_FILE" | tail -1) + echo -e " \e[36mLog file:\e[0m ${LOG_FILE} (${log_size})" + [[ -n "${last_run:-}" ]] && echo -e " \e[36mLast run:\e[0m ${last_run}" + else + echo -e " \e[36mLog file:\e[0m (no runs yet)" + fi + echo "" +} + +run_now() { + if [[ ! -f "$LOCAL_SCRIPT" ]]; then + err "No local script found at ${LOCAL_SCRIPT}. Use 'Add' first." + exit 1 + fi + info "Running update script now..." + bash "$LOCAL_SCRIPT" | tee -a "$LOG_FILE" + ok "Run completed. Log appended to ${LOG_FILE}" +} + +rotate_log() { + if [[ ! -f "$LOG_FILE" ]]; then + info "No log file to rotate." + return + fi + local log_size + log_size=$(stat -c '%s' "$LOG_FILE" 2>/dev/null || echo 0) + local log_size_h + log_size_h=$(du -h "$LOG_FILE" | awk '{print $1}') + if confirm "Rotate log file? (current size: ${log_size_h})"; then + mv "$LOG_FILE" "${LOG_FILE}.old" + ok "Rotated: ${LOG_FILE} → ${LOG_FILE}.old" + fi +} + +OPTIONS=( + Add "Download, review & install cron schedule" + Remove "Remove cron schedule & local script" + Update "Update local script from repository" + Status "Show installation status & last run" + Run "Run update script now (manual trigger)" + View "View cron config & installed script" + Rotate "Rotate log file" +) + +CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Cron Update LXCs" --menu "Select an option:" 16 68 7 \ + "${OPTIONS[@]}" 3>&1 1>&2 2>&3) || exit 0 + +case $CHOICE in +"Add") add ;; +"Remove") remove ;; +"Update") update_script ;; +"Status") show_status ;; +"Run") run_now ;; +"View") view_script ;; +"Rotate") rotate_log ;; +esac diff --git a/Proxmox/PVE/PVE Host Backup.sh b/Proxmox/PVE/PVE Host Backup.sh new file mode 100644 index 0000000..eec5885 --- /dev/null +++ b/Proxmox/PVE/PVE Host Backup.sh @@ -0,0 +1,93 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/host-backup.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info { + clear + cat <<"EOF" + __ __ __ ___ __ + / // /__ ___ / /_ / _ )___ _____/ /____ _____ + / _ / _ \(_-</ __/ / _ / _ `/ __/ '_/ // / _ \ +/_//_/\___/___/\__/ /____/\_,_/\__/_/\_\\_,_/ .__/ + /_/ +EOF +} + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "host-backup" "pve" + +# Function to perform backup +function perform_backup { + local BACKUP_PATH + local DIR + local DIR_DASH + local BACKUP_FILE + local selected_directories=() + + # Get backup path from user + BACKUP_PATH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "\nDefaults to /root/\ne.g. /mnt/backups/" 11 68 --title "Directory to backup to:" 3>&1 1>&2 2>&3) || return + + # Default to /root/ if no input + BACKUP_PATH="${BACKUP_PATH:-/root/}" + + # Get directory to work in from user + DIR=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "\nDefaults to /etc/\ne.g. /root/, /var/lib/pve-cluster/ etc." 11 68 --title "Directory to work in:" 3>&1 1>&2 2>&3) || return + + # Default to /etc/ if no input + DIR="${DIR:-/etc/}" + + DIR_DASH=$(echo "$DIR" | tr '/' '-') + BACKUP_FILE="$(hostname)${DIR_DASH}backup" + + # Build a list of directories for backup + local CTID_MENU=() + CTID_MENU=("ALL" "Backup all folders" "OFF") + while read -r dir; do + CTID_MENU+=("$(basename "$dir")" "$dir " "OFF") + done < <(ls -d "${DIR}"*) + + # Allow the user to select directories + local HOST_BACKUP + while [ -z "${HOST_BACKUP:+x}" ]; do + HOST_BACKUP=$(whiptail --backtitle "Proxmox VE Host Backup" --title "Working in the ${DIR} directory " --checklist \ + "\nSelect what files/directories to backup:\n" 16 $(((${#DIRNAME} + 2) + 88)) 6 "${CTID_MENU[@]}" 3>&1 1>&2 2>&3) || return + + for selected_dir in ${HOST_BACKUP//\"/}; do + if [[ "$selected_dir" == "ALL" ]]; then + # if ALL was chosen, secure all folders + selected_directories=("${DIR}"*/) + break + else + selected_directories+=("${DIR}$selected_dir") + fi + done + done + + # Perform the backup + header_info + echo -e "This will create a backup in\e[1;33m $BACKUP_PATH \e[0mfor these files and directories\e[1;33m ${selected_directories[*]} \e[0m" + read -p "Press ENTER to continue..." + header_info + echo "Working..." + tar -czf "$BACKUP_PATH$BACKUP_FILE-$(date +%Y_%m_%dT%H_%M).tar.gz" --absolute-names "${selected_directories[@]}" + header_info + echo -e "\nFinished" + echo -e "\e[1;33m \nA backup is rendered ineffective when it remains stored on the host.\n \e[0m" + sleep 2 +} + +# Main script execution loop +while true; do + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE Host Backup" --yesno "This will create backups for particular files and directories located within a designated directory. Proceed?" 10 88); then + perform_backup + else + break + fi +done diff --git a/Proxmox/PVE/PVE Kernel Clean.sh b/Proxmox/PVE/PVE Kernel Clean.sh new file mode 100644 index 0000000..26f288f --- /dev/null +++ b/Proxmox/PVE/PVE Kernel Clean.sh @@ -0,0 +1,122 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/kernel-clean.sh)" + +#!/usr/bin/env bash +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info { + clear + cat <<"EOF" + __ __ __ ________ + / //_/__ _________ ___ / / / ____/ /__ ____ _____ + / ,< / _ \/ ___/ __ \/ _ \/ / / / / / _ \/ __ `/ __ \ + / /| / __/ / / / / / __/ / / /___/ / __/ /_/ / / / / +/_/ |_\___/_/ /_/ /_/\___/_/ \____/_/\___/\__,_/_/ /_/ + +EOF +} + +# Color variables +YW="\033[33m" +GN="\033[1;92m" +RD="\033[01;31m" +CL="\033[m" + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "kernel-clean" "pve" + +# Detect current kernel +current_kernel=$(uname -r) +# Only list fully-installed (ii) versioned kernel packages; the pattern +# proxmox-kernel-X.Y.Z matches versioned kernels while excluding the +# two-segment meta-packages (proxmox-kernel-X.Y) and proxmox-kernel-helper. +available_kernels=$(dpkg --list | + awk '/^ii/ {print $2}' | + grep -E '^proxmox-kernel-[0-9]+\.[0-9]+\.[0-9]' | + grep -v "$current_kernel" | + sort -V) + +header_info + +if [ -z "$available_kernels" ]; then + echo -e "${GN}No old kernels detected. Current kernel: ${current_kernel}${CL}" + exit 0 +fi + +echo -e "${GN}Currently running kernel: ${current_kernel}${CL}" +echo -e "${YW}Available kernels for removal:${CL}" +echo "$available_kernels" | nl -w 2 -s '. ' + +echo -e "\n${YW}Select kernels to remove (e.g. 1,3 or 1-5 or 1-3,7):${CL}" +read -r selected + +# Parse selection: supports single indices, ranges (e.g., 1-5), and combinations (e.g., 1,3-5,7) +selected_indices=() +IFS=',' read -r -a tokens <<<"$selected" +for token in "${tokens[@]}"; do + if [[ "$token" =~ ^([0-9]+)-([0-9]+)$ ]]; then + for ((i = BASH_REMATCH[1]; i <= BASH_REMATCH[2]; i++)); do + selected_indices+=("$i") + done + else + selected_indices+=("$token") + fi +done + +kernels_to_remove=() +for index in "${selected_indices[@]}"; do + kernel=$(echo "$available_kernels" | sed -n "${index}p") + if [ -n "$kernel" ]; then + kernels_to_remove+=("$kernel") + fi +done + +if [ ${#kernels_to_remove[@]} -eq 0 ]; then + echo -e "${RD}No valid selection made. Exiting.${CL}" + exit 0 +fi + +# Confirm removal +echo -e "${YW}Kernels to be removed:${CL}" +printf "%s\n" "${kernels_to_remove[@]}" +read -rp "Proceed with removal? (y/n): " confirm +if [[ "$confirm" != "y" ]]; then + echo -e "${RD}Aborted.${CL}" + exit 0 +fi + +# Remove kernels +for kernel in "${kernels_to_remove[@]}"; do + echo -e "${YW}Removing $kernel...${CL}" + # Derive the major.minor meta-package name (e.g. proxmox-kernel-6.14) + # from a versioned name like proxmox-kernel-6.14.11-9-pve-signed. + minor_version=$(echo "$kernel" | sed -E 's/^proxmox-kernel-([0-9]+\.[0-9]+)\..*/\1/') + meta="proxmox-kernel-${minor_version}" + pkgs_to_remove=("$kernel") + # Include the meta-package in the purge when it is installed and when + # no other versioned kernel of the same minor version will remain + # (the running kernel keeps it alive if it shares the same minor). + if dpkg -l "$meta" 2>/dev/null | grep -q '^ii'; then + remaining=$(dpkg --list | + awk '/^ii/ {print $2}' | + grep -E "^proxmox-kernel-${minor_version}\." | + grep -v "^${kernel}$" | + wc -l) + if [ "$remaining" -eq 0 ]; then + pkgs_to_remove+=("$meta") + fi + fi + if apt-get purge -y "${pkgs_to_remove[@]}" >/dev/null 2>&1; then + echo -e "${GN}Successfully removed: ${pkgs_to_remove[*]}${CL}" + else + echo -e "${RD}Failed to remove: ${pkgs_to_remove[*]}. Check dependencies.${CL}" + fi +done + +# Clean up and update GRUB +echo -e "${YW}Cleaning up...${CL}" +apt-get autoremove -y >/dev/null 2>&1 && update-grub >/dev/null 2>&1 +echo -e "${GN}Cleanup and GRUB update complete.${CL}" diff --git a/Proxmox/PVE/PVE LXC Apps Update.sh b/Proxmox/PVE/PVE LXC Apps Update.sh new file mode 100644 index 0000000..bbf6306 --- /dev/null +++ b/Proxmox/PVE/PVE LXC Apps Update.sh @@ -0,0 +1,680 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-apps.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: BvdBerg01 | Co-Author: remz1337 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "update-apps" "pve" + +# ============================================================================= +# CONFIGURATION VARIABLES +# Set these variables to skip interactive prompts (Whiptail dialogs) +# ============================================================================= +# var_backup: Enable/disable backup before update +# Options: "yes" | "no" | "" (empty = interactive prompt) +var_backup="${var_backup:-}" + +# var_backup_storage: Storage location for backups (only used if var_backup=yes) +# Options: Storage name from /etc/pve/storage.cfg (e.g., "local", "nas-backup") +# Leave empty for interactive selection +var_backup_storage="${var_backup_storage:-}" + +# var_container: Which containers to update +# Options: +# - "all" : All containers with community-scripts tags +# - "all_running" : Only running containers with community-scripts tags +# - "all_stopped" : Only stopped containers with community-scripts tags +# - "101,102,109" : Comma-separated list of specific container IDs +# - "" : Interactive selection via Whiptail +var_container="${var_container:-}" + +# var_unattended: Run updates without user interaction inside containers +# Options: "yes" | "no" | "" (empty = interactive prompt) +var_unattended="${var_unattended:-}" + +# var_skip_confirm: Skip initial confirmation dialog +# Options: "yes" | "no" (default: no) +var_skip_confirm="${var_skip_confirm:-no}" + +# var_auto_reboot: Automatically reboot containers that require it after update +# Options: "yes" | "no" | "" (empty = interactive prompt) +var_auto_reboot="${var_auto_reboot:-}" + +# var_continue_on_error: Continue updating remaining containers if one update fails +# Options: "yes" | "no" (default: no = stop on first error) +# Note: containers with backups always attempt restore on failure regardless of this setting +var_continue_on_error="${var_continue_on_error:-no}" + +# var_dry_run: Check for available updates without applying them +# Options: "yes" | "no" (default: no) +# Output: lists each container with current vs. latest version +# Note: requires the container to be running; does not modify any container +var_dry_run="${var_dry_run:-no}" + +# var_tags: Optionally override the tags used for auto-detection +# Options: "community-script|proxmox-helper-scripts" (default) +var_tags="${var_tags:-community-script|proxmox-helper-scripts}" +# ============================================================================= +# JSON CONFIG EXPORT +# Run with --export-config to output current configuration as JSON +# ============================================================================= + +function export_config_json() { + cat <<EOF +{ + "var_backup": "${var_backup}", + "var_backup_storage": "${var_backup_storage}", + "var_container": "${var_container}", + "var_unattended": "${var_unattended}", + "var_skip_confirm": "${var_skip_confirm}", + "var_auto_reboot": "${var_auto_reboot}", + "var_continue_on_error": "${var_continue_on_error}", + "var_dry_run": "${var_dry_run}", + "var_tags": "${var_tags}" +} +EOF +} + +function print_usage() { + cat <<EOF +Usage: $(basename "$0") [OPTIONS] + +Update LXC containers created with community-scripts. + +Options: + --help Show this help message + --export-config Export current configuration as JSON + +Environment Variables: + var_backup Enable backup before update (yes/no) + var_backup_storage Storage location for backups + var_container Container selection (all/all_running/all_stopped/101,102,...) + var_unattended Run updates unattended (yes/no) + var_skip_confirm Skip initial confirmation (yes/no) + var_auto_reboot Auto-reboot containers if required (yes/no) + var_continue_on_error Continue to next container on update failure (yes/no) + var_dry_run Check for updates without applying them (yes/no) + var_tags Optionally override auto-detection tags ("prod|smb|community-script") + +Examples: + # Run interactively + $(basename "$0") + + # Update all running containers unattended with backup + var_backup=yes var_backup_storage=local var_container=all_running var_unattended=yes var_skip_confirm=yes $(basename "$0") + + # Update specific containers without backup + var_backup=no var_container=101,102,105 var_unattended=yes var_skip_confirm=yes $(basename "$0") + + # Unattended cron-style: skip confirm, continue on error, no backup + var_backup=no var_container=all_running var_unattended=yes var_skip_confirm=yes var_continue_on_error=yes $(basename "$0") + + # Dry-run: show available updates for all running containers without applying + var_container=all_running var_skip_confirm=yes var_dry_run=yes $(basename "$0") + + # Export current configuration + $(basename "$0") --export-config +EOF +} + +# Handle command line arguments +case "${1:-}" in +--help | -h) + print_usage + exit 0 + ;; +--export-config) + export_config_json + exit 0 + ;; +esac + +# ============================================================================= + +function header_info { + clear + cat <<"EOF" + __ _ ________ __ __ __ __ + / / | |/ / ____/ / / / /___ ____/ /___ _/ /____ + / / | / / / / / / __ \/ __ / __ `/ __/ _ \ + / /___/ / /___ / /_/ / /_/ / /_/ / /_/ / /_/ __/ +/_____/_/|_\____/ \____/ .___/\__,_/\__,_/\__/\___/ + /_/ +EOF +} + +function detect_service() { + pushd $(mktemp -d) >/dev/null + pct pull "$1" /usr/bin/update update 2>/dev/null + service=$(cat update | sed 's|.*/ct/||g' | sed 's|\.sh).*||g') + popd >/dev/null +} + +function dry_run_container() { + local container="$1" + local service="$2" + + # Extract app name and source repo directly from check_for_gh_release call in the ct script + # Pattern: check_for_gh_release "appname" "owner/repo" + local check_line app_name app_lc source_repo + check_line=$(echo "$script" | grep -m1 'check_for_gh_release') + + if [[ -z "$check_line" ]]; then + echo -e "${YW}[DRY-RUN]${CL} Container $container ($service): no check_for_gh_release found — skipping" + DRY_RUN_RESULT="no check_for_gh_release found — skipping" + return + fi + + app_name=$(echo "$check_line" | cut -d'"' -f2) + source_repo=$(echo "$check_line" | cut -d'"' -f4) + app_lc=$(echo "${app_name,,}" | tr -d ' ') + + if [[ -z "$source_repo" || "$source_repo" != *"/"* ]]; then + echo -e "${YW}[DRY-RUN]${CL} Container $container ($service): cannot parse source repo — skipping" + DRY_RUN_RESULT="cannot parse source repo — skipping" + return + fi + + # Read installed version from container (stored by check_for_gh_release as ~/.<appname>) + local current_version + current_version=$(pct exec "$container" -- bash -c "cat \$HOME/.${app_lc} 2>/dev/null" 2>/dev/null || true) + current_version="${current_version#v}" + + # Query latest release from GitHub API + local latest_version + latest_version=$(curl -sSL --max-time 10 \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "https://api.github.com/repos/${source_repo}/releases/latest" 2>/dev/null | + grep '"tag_name"' | head -1 | cut -d'"' -f4 | sed 's/^v//') + + if [[ -z "$latest_version" ]]; then + echo -e "${YW}[DRY-RUN]${CL} Container $container ($service): cannot fetch latest version from $source_repo" + DRY_RUN_RESULT="cannot fetch latest version from $source_repo" + return + fi + + if [[ -z "$current_version" ]]; then + echo -e "${BL}[DRY-RUN]${CL} Container $container ($service): installed version unknown, latest: ${latest_version} (${source_repo})" + DRY_RUN_RESULT="version unknown — latest: ${latest_version}" + elif [[ "$current_version" == "$latest_version" ]]; then + echo -e "${GN}[DRY-RUN]${CL} Container $container ($service): up to date (${current_version})" + DRY_RUN_RESULT="up to date (${current_version})" + else + echo -e "${YW}[DRY-RUN]${CL} Container $container ($service): update available ${current_version} → ${latest_version}" + DRY_RUN_RESULT="update available ${current_version} → ${latest_version}" + fi +} + +function backup_container() { + msg_info "Creating backup for container $1" + vzdump $1 --compress zstd --storage $STORAGE_CHOICE -notes-template "{{guestname}} - community-scripts backup updater" >/dev/null 2>&1 + status=$? + + if [ $status -eq 0 ]; then + msg_ok "Backup created" + else + msg_error "Backup failed for container $1" + exit 235 + fi +} + +function get_backup_storages() { + STORAGES=$(awk ' +/^[a-z]+:/ { + if (name != "") { + if (has_backup || (!has_content && type == "dir")) print name + } + split($0, a, ":") + type = a[1] + name = a[2] + gsub(/^[ \t]+|[ \t]+$/, "", name) + has_content = 0 + has_backup = 0 +} +/^[ \t]*content/ { + has_content = 1 + if ($0 ~ /backup/) has_backup = 1 +} +END { + if (name != "") { + if (has_backup || (!has_content && type == "dir")) print name + } +} +' /etc/pve/storage.cfg) +} + +# Structured result tracking for the final summary report +# Each entry: "CTID|service|STATUS|details" +declare -a UPDATE_RESULTS=() +function log_result() { + # log_result <ctid> <service> <STATUS> <details> + UPDATE_RESULTS+=("${1}|${2}|${3}|${4}") +} + +header_info + +# ============================================================================= +# LOGGING SETUP +# Key events are written directly to a timestamped log file under +# /usr/local/community-scripts/update_apps/ — this avoids any stdout +# redirection that would break interactive spinners or whiptail dialogs. +# The full summary table is appended at the end of the run. +# ============================================================================= +LOG_DIR="/usr/local/community-scripts/update_apps" +mkdir -p "$LOG_DIR" +LOG_FILE="${LOG_DIR}/$(date '+%Y%m%d_%H%M%S').log" +echo "Update started: $(date '+%Y-%m-%d %H:%M:%S')" >"$LOG_FILE" + +function log_write() { + echo "[$(date '+%H:%M:%S')] $*" >>"$LOG_FILE" +} + +# Skip confirmation if var_skip_confirm is set to yes +if [[ "$var_skip_confirm" != "yes" ]]; then + whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC App Update" --yesno "This will update apps in LXCs installed by Helper-Scripts. Proceed?" 10 58 || exit +fi + +tags_formatted="${var_tags//|/, }" +msg_info "Loading all possible LXC containers from Proxmox VE with tags: ${tags_formatted}. This may take a few seconds..." +NODE=$(hostname) +containers=$(pct list | tail -n +2 | awk '{print $0 " " $4}') + +if [ -z "$containers" ]; then + whiptail --title "LXC Container Update" --msgbox "No LXC containers available!" 10 60 + exit 234 +fi + +menu_items=() +FORMAT="%-10s %-15s %-10s" +TAGS="${var_tags:-community-script|proxmox-helper-scripts}" + +while read -r container; do + container_id=$(echo $container | awk '{print $1}') + container_name=$(echo $container | awk '{print $2}') + container_status=$(echo $container | awk '{print $3}') + formatted_line=$(printf "$FORMAT" "$container_name" "$container_status") + if pct config "$container_id" | grep -qE "[^-][; ](${TAGS}).*"; then + menu_items+=("$container_id" "$formatted_line" "OFF") + fi +done <<<"$containers" +msg_ok "Loaded $((${#menu_items[@]} / 3)) containers" + +# Determine container selection based on var_container +if [[ -n "$var_container" ]]; then + case "$var_container" in + all) + # Select all containers with matching tags + CHOICE="" + for ((i = 0; i < ${#menu_items[@]}; i += 3)); do + CHOICE="$CHOICE ${menu_items[$i]}" + done + CHOICE=$(echo "$CHOICE" | xargs) + ;; + all_running) + # Select only running containers with matching tags + CHOICE="" + for ((i = 0; i < ${#menu_items[@]}; i += 3)); do + cid="${menu_items[$i]}" + if pct status "$cid" 2>/dev/null | grep -q "running"; then + CHOICE="$CHOICE $cid" + fi + done + CHOICE=$(echo "$CHOICE" | xargs) + ;; + all_stopped) + # Select only stopped containers with matching tags + CHOICE="" + for ((i = 0; i < ${#menu_items[@]}; i += 3)); do + cid="${menu_items[$i]}" + if pct status "$cid" 2>/dev/null | grep -q "stopped"; then + CHOICE="$CHOICE $cid" + fi + done + CHOICE=$(echo "$CHOICE" | xargs) + ;; + *) + # Assume comma-separated list of container IDs + CHOICE=$(echo "$var_container" | tr ',' ' ') + ;; + esac + + if [[ -z "$CHOICE" ]]; then + msg_error "No containers matched the selection criteria: $var_container ${var_tags:-community-script|proxmox-helper-scripts}" + exit 234 + fi + msg_ok "Selected containers: $CHOICE" +else + CHOICE=$(whiptail --title "LXC Container Update" \ + --checklist "Select LXC containers to update:" 25 60 13 \ + "${menu_items[@]}" 3>&2 2>&1 1>&3 | tr -d '"') + + if [ -z "$CHOICE" ]; then + whiptail --title "LXC Container Update" \ + --msgbox "No containers selected!" 10 60 + exit 0 + fi +fi + +header_info + +# Determine backup choice based on var_backup +# Dry-run never needs a backup — skip the prompt entirely +if [[ "$var_dry_run" == "yes" ]]; then + BACKUP_CHOICE="no" +elif [[ -n "$var_backup" ]]; then + BACKUP_CHOICE="$var_backup" +else + BACKUP_CHOICE="no" + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Update" --yesno "Do you want to backup your containers before update?" 10 58); then + BACKUP_CHOICE="yes" + fi +fi + +# Determine unattended update based on var_unattended +# Dry-run never executes updates — skip the prompt entirely +if [[ "$var_dry_run" == "yes" ]]; then + UNATTENDED_UPDATE="no" +elif [[ -n "$var_unattended" ]]; then + UNATTENDED_UPDATE="$var_unattended" +else + UNATTENDED_UPDATE="no" + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "LXC Container Update" --yesno "Run updates unattended?" 10 58); then + UNATTENDED_UPDATE="yes" + fi +fi + +if [ "$BACKUP_CHOICE" == "yes" ]; then + get_backup_storages + + if [ -z "$STORAGES" ]; then + msg_error "No storage with 'backup' support found!" + exit 119 + fi + + # Determine storage based on var_backup_storage + if [[ -n "$var_backup_storage" ]]; then + # Validate that the specified storage exists and supports backups + if echo "$STORAGES" | grep -qw "$var_backup_storage"; then + STORAGE_CHOICE="$var_backup_storage" + msg_ok "Using backup storage: $STORAGE_CHOICE" + else + msg_error "Specified backup storage '$var_backup_storage' not found or doesn't support backups!" + msg_info "Available storages: $(echo $STORAGES | tr '\n' ' ')" + exit 119 + fi + else + MENU_ITEMS=() + for STORAGE in $STORAGES; do + MENU_ITEMS+=("$STORAGE" "") + done + + STORAGE_CHOICE=$(whiptail --title "Select storage device" --menu "Select a storage device (Only storage devices with 'backup' support are listed):" 15 50 5 "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) + + if [ -z "$STORAGE_CHOICE" ]; then + msg_error "No storage selected!" + exit 0 + fi + fi +fi + +UPDATE_CMD="update;" +if [ "$UNATTENDED_UPDATE" == "yes" ]; then + UPDATE_CMD="export PHS_SILENT=1;update;" +fi + +containers_needing_reboot=() +for container in $CHOICE; do + echo -e "${BL}[INFO]${CL} Updating container $container" + log_write "Container $container: starting" + + if [ "$BACKUP_CHOICE" == "yes" ]; then + backup_container $container + fi + + os=$(pct config "$container" | awk '/^ostype/ {print $2}') + status=$(pct status $container) + template=$(pct config $container | grep -q "template:" && echo "true" || echo "false") + if [ "$template" == "false" ] && [ "$status" == "status: stopped" ]; then + echo -e "${BL}[Info]${GN} Starting${BL} $container ${CL} \n" + pct start $container + echo -e "${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL} \n" + sleep 5 + fi + + #1) Detect service using the service name in the update command + detect_service $container + + #1.1) If update script not detected, return + if [ -z "${service}" ]; then + echo -e "${YW}[WARN]${CL} Update script not found. Skipping to next container" + log_result "$container" "(unknown)" "SKIPPED" "No update script found in container" + log_write "Container $container: SKIPPED — no update script found" + continue + else + echo -e "${BL}[INFO]${CL} Detected service: ${GN}${service}${CL}" + log_write "Container $container: detected service '$service'" + fi + + #2) Extract service build/update resource requirements from config/installation file + script=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${service}.sh) + + #2.1) Check if the script downloaded successfully + if [ $? -ne 0 ]; then + echo -e "${RD}[ERROR]${CL} Issue while downloading install script." + echo -e "${YW}[WARN]${CL} Unable to assess build resource requirements. Proceeding with current resources." + fi + + config=$(pct config "$container") + build_cpu=$(echo "$script" | { grep -m 1 "var_cpu" || test $? = 1; } | sed 's|.*=||g' | sed 's|"||g' | sed 's|.*var_cpu:-||g' | sed 's|}||g') + build_ram=$(echo "$script" | { grep -m 1 "var_ram" || test $? = 1; } | sed 's|.*=||g' | sed 's|"||g' | sed 's|.*var_ram:-||g' | sed 's|}||g') + run_cpu=$(echo "$script" | { grep -m 1 "pct set \$CTID -cores" || test $? = 1; } | sed 's|.*cores ||g') + run_ram=$(echo "$script" | { grep -m 1 "pct set \$CTID -memory" || test $? = 1; } | sed 's|.*memory ||g') + current_cpu=$(echo "$config" | grep -m 1 "cores:" | sed 's|cores: ||g') + current_ram=$(echo "$config" | grep -m 1 "memory:" | sed 's|memory: ||g') + + #Test if all values are valid (>0) + if [ -z "${run_cpu}" ] || [ "$run_cpu" -le 0 ]; then + #echo "No valid value found for run_cpu. Assuming same as current configuration." + run_cpu=$current_cpu + fi + + if [ -z "${run_ram}" ] || [ "$run_ram" -le 0 ]; then + #echo "No valid value found for run_ram. Assuming same as current configuration." + run_ram=$current_ram + fi + + if [ -z "${build_cpu}" ] || [ "$build_cpu" -le 0 ]; then + #echo "No valid value found for build_cpu. Assuming same as current configuration." + build_cpu=$current_cpu + fi + + if [ -z "${build_ram}" ] || [ "$build_ram" -le 0 ]; then + #echo "No valid value found for build_ram. Assuming same as current configuration." + build_ram=$current_ram + fi + + UPDATE_BUILD_RESOURCES=0 + if [ "$build_cpu" -gt "$run_cpu" ] || [ "$build_ram" -gt "$run_ram" ]; then + UPDATE_BUILD_RESOURCES=1 + fi + + #3) if build resources are different than run resources, then: + if [ "$UPDATE_BUILD_RESOURCES" -eq "1" ] && [[ "$var_dry_run" != "yes" ]]; then + pct set "$container" --cores "$build_cpu" --memory "$build_ram" + fi + + #3.5) Dry-run: report update availability without applying + if [[ "$var_dry_run" == "yes" ]]; then + DRY_RUN_RESULT="" + dry_run_container "$container" "$service" + log_result "$container" "$service" "DRY-RUN" "${DRY_RUN_RESULT:-version check only}" + log_write "Container $container ($service): DRY-RUN — ${DRY_RUN_RESULT:-version check only}" + continue + fi + + #4) Update service, using the update command + # Prepend a no-op 'clear' wrapper to PATH so update scripts calling clear + # don't fail without a TTY — works for all shells incl. ash (no export -f) + SETUP_CMD="mkdir -p /tmp/.nc; printf '#!/bin/sh\n:\n' > /tmp/.nc/clear; chmod +x /tmp/.nc/clear; export PATH=/tmp/.nc:\$PATH; export TERM=dumb; " + case "$os" in + alpine) pct exec "$container" -- ash -c "${SETUP_CMD}${UPDATE_CMD}" ;; + archlinux) pct exec "$container" -- bash -c "${SETUP_CMD}${UPDATE_CMD}" ;; + fedora | rocky | centos | alma) pct exec "$container" -- bash -c "${SETUP_CMD}${UPDATE_CMD}" ;; + ubuntu | debian | devuan) pct exec "$container" -- bash -c "${SETUP_CMD}${UPDATE_CMD}" ;; + opensuse) pct exec "$container" -- bash -c "${SETUP_CMD}${UPDATE_CMD}" ;; + esac + exit_code=$? + + #5) if build resources are different than run resources, then: + if [ "$UPDATE_BUILD_RESOURCES" -eq "1" ]; then + pct set "$container" --cores "$run_cpu" --memory "$run_ram" + fi + + if pct exec "$container" -- [ -e "/var/run/reboot-required" ]; then + # Get the container's hostname and add it to the list + container_hostname=$(pct exec "$container" hostname) + containers_needing_reboot+=("$container ($container_hostname)") + fi + + if [ "$template" == "false" ] && [ "$status" == "status: stopped" ]; then + echo -e "${BL}[Info]${GN} Shutting down${BL} $container ${CL} \n" + pct shutdown $container &>/dev/null & + fi + + if [ $exit_code -eq 0 ]; then + msg_ok "Updated container $container" + log_result "$container" "$service" "OK" "Updated successfully" + log_write "Container $container ($service): OK" + elif [ $exit_code -eq 75 ]; then + echo -e "${YW}[WARN]${CL} Container $container skipped (requires interactive mode)" + log_result "$container" "$service" "SKIPPED" "Requires interactive mode (exit 75)" + log_write "Container $container ($service): SKIPPED — requires interactive mode" + elif [ $exit_code -eq 113 ]; then + echo -e "${YW}[WARN]${CL} Container $container skipped (under-provisioned: increase CPU/RAM to match template)" + log_result "$container" "$service" "SKIPPED" "Under-provisioned — increase CPU/RAM to match template" + log_write "Container $container ($service): SKIPPED — under-provisioned" + elif [ $exit_code -eq 114 ]; then + echo -e "${YW}[WARN]${CL} Container $container skipped (storage critically low on /boot)" + log_result "$container" "$service" "SKIPPED" "Storage critically low on /boot (>80%)" + log_write "Container $container ($service): SKIPPED — storage critically low on /boot" + elif [ "$BACKUP_CHOICE" == "yes" ]; then + msg_error "Update failed for container $container (exit code: $exit_code) — attempting restore" + log_write "Container $container ($service): FAILED (exit $exit_code) — attempting restore" + msg_info "Restoring LXC $container from backup ($STORAGE_CHOICE)" + pct stop $container + LXC_STORAGE=$(pct config $container | awk -F '[:,]' '/rootfs/ {print $2}') + BACKUP_ENTRY=$(pvesm list "$STORAGE_CHOICE" 2>/dev/null | awk -v ctid="$container" '$1 ~ "vzdump-lxc-"ctid"-" || $1 ~ "/ct/"ctid"/" {print $1}' | sort -r | head -n1) + if [ -z "$BACKUP_ENTRY" ]; then + msg_error "No backup found in storage $STORAGE_CHOICE for container $container" + log_result "$container" "$service" "FAILED" "Update failed (exit $exit_code) — no backup found for restore" + log_write "Container $container ($service): FAILED — no backup found for restore" + exit 235 + fi + msg_info "Restoring from: $BACKUP_ENTRY" + pct restore $container "$BACKUP_ENTRY" --storage $LXC_STORAGE --force >/dev/null 2>&1 + restorestatus=$? + if [ $restorestatus -eq 0 ]; then + pct start $container + msg_ok "Container $container successfully restored from backup" + log_result "$container" "$service" "RESTORED" "Update failed (exit $exit_code) — restored from backup" + log_write "Container $container ($service): RESTORED from $BACKUP_ENTRY" + else + msg_error "Restore failed for container $container" + log_result "$container" "$service" "FAILED" "Update failed (exit $exit_code) — restore also failed" + log_write "Container $container ($service): FAILED — restore also failed" + exit 235 + fi + else + msg_error "Update failed for container $container (exit code: $exit_code)" + log_result "$container" "$service" "FAILED" "Exit code $exit_code" + log_write "Container $container ($service): FAILED (exit $exit_code)" + if [[ "$var_continue_on_error" == "yes" ]]; then + echo -e "${YW}[WARN]${CL} Continuing to next container (var_continue_on_error=yes)" + continue + else + exit "$exit_code" + fi + fi +done + +wait +header_info +if [[ "$var_dry_run" == "yes" ]]; then + echo -e "${GN}Dry-run complete. No containers were modified.${CL}\n" +else + echo -e "${GN}The process is complete, and the containers have been successfully updated.${CL}\n" +fi + +# ============================================================================= +# SUMMARY REPORT +# ============================================================================= +if [ "${#UPDATE_RESULTS[@]}" -gt 0 ]; then + SEPARATOR="━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + HEADER=$(printf " %-8s %-22s %-10s %s" "CTID" "Service" "Status" "Details") + + # terminal output (with colours) + echo "" + echo "$SEPARATOR" + echo "$HEADER" + echo "$SEPARATOR" + for entry in "${UPDATE_RESULTS[@]}"; do + IFS='|' read -r _ctid _svc _status _details <<<"$entry" + case "$_status" in + OK) _color="${GN}" ;; + FAILED) _color="${RD}" ;; + RESTORED) _color="${YW}" ;; + *) _color="${YW}" ;; + esac + printf " %-8s %-22s ${_color}%-10s${CL} %s\n" "$_ctid" "$_svc" "$_status" "$_details" + done + echo "$SEPARATOR" + echo "" + echo "Full log: $LOG_FILE" + echo "" + + # append plain-text summary to log file + { + echo "" + echo "Update finished: $(date '+%Y-%m-%d %H:%M:%S')" + echo "$SEPARATOR" + echo "$HEADER" + echo "$SEPARATOR" + for entry in "${UPDATE_RESULTS[@]}"; do + IFS='|' read -r _ctid _svc _status _details <<<"$entry" + printf " %-8s %-22s %-10s %s\n" "$_ctid" "$_svc" "$_status" "$_details" + done + echo "$SEPARATOR" + } >>"$LOG_FILE" +fi +if [ "${#containers_needing_reboot[@]}" -gt 0 ]; then + echo -e "${RD}The following containers require a reboot:${CL}" + for container_name in "${containers_needing_reboot[@]}"; do + echo "$container_name" + done + + # Determine reboot choice based on var_auto_reboot + REBOOT_CHOICE="no" + if [[ -n "$var_auto_reboot" ]]; then + REBOOT_CHOICE="$var_auto_reboot" + else + echo -ne "${INFO} Do you wish to reboot these containers? <yes/No> " + read -r prompt + if [[ ${prompt,,} =~ ^(yes)$ ]]; then + REBOOT_CHOICE="yes" + fi + fi + + if [[ "$REBOOT_CHOICE" == "yes" ]]; then + echo -e "${CROSS}${HOLD} ${YWB}Rebooting containers.${CL}" + for container_name in "${containers_needing_reboot[@]}"; do + container=$(echo $container_name | cut -d " " -f 1) + pct reboot ${container} + done + fi +fi diff --git a/Proxmox/PVE/PVE LXC Cleaner.sh b/Proxmox/PVE/PVE LXC Cleaner.sh new file mode 100644 index 0000000..17a1fcc --- /dev/null +++ b/Proxmox/PVE/PVE LXC Cleaner.sh @@ -0,0 +1,124 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/clean-lxcs.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info() { + clear + cat <<"EOF" + ________ __ _ ________ + / ____/ /__ ____ _____ / / | |/ / ____/ + / / / / _ \/ __ `/ __ \ / / | / / +/ /___/ / __/ /_/ / / / / / /___/ / /___ +\____/_/\___/\__,_/_/ /_/ /_____/_/|_\____/ + +EOF +} + +set -eEuo pipefail +BL="\033[36m" +RD="\033[01;31m" +CM='\xE2\x9C\x94\033' +GN="\033[1;92m" +CL="\033[m" + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "clean-lxcs" "pve" + +header_info +echo "Loading..." + +whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE LXC Updater" --yesno "This will clean logs, cache and update package lists on selected LXC Containers. Proceed?" 10 58 + +NODE=$(hostname) +EXCLUDE_MENU=() +MSG_MAX_LENGTH=0 + +while read -r TAG ITEM; do + OFFSET=2 + ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET + EXCLUDE_MENU+=("$TAG" "$ITEM " "OFF") +done < <(pct list | awk 'NR>1') + +excluded_containers=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --checklist "\nSelect containers to skip from cleaning:\n" \ + 16 $((MSG_MAX_LENGTH + 23)) 6 "${EXCLUDE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"') + +if [ $? -ne 0 ]; then + exit +fi + +function run_lxc_clean() { + local container=$1 + header_info + name=$(pct exec "$container" hostname) + + pct exec "$container" -- bash -c ' + BL="\033[36m"; GN="\033[1;92m"; CL="\033[m" + name=$(hostname) + if [ -e /etc/alpine-release ]; then + echo -e "${BL}[Info]${GN} Cleaning $name (Alpine)${CL}\n" + apk cache clean + find /var/log -type f -delete 2>/dev/null + find /tmp -mindepth 1 -delete 2>/dev/null + apk update + elif [ -e /etc/redhat-release ]; then + echo -e "${BL}[Info]${GN} Cleaning $name (CentOS)${CL}\n" + yum clean all + find /var/log -type f -delete 2>/dev/null + find /tmp -mindepth 1 -delete 2>/dev/null + yum update + yum upgrade -y + else + echo -e "${BL}[Info]${GN} Cleaning $name (Debian/Ubuntu)${CL}\n" + find /var/cache -type f -delete 2>/dev/null + find /var/log -type f -delete 2>/dev/null + find /tmp -mindepth 1 -delete 2>/dev/null + apt -y --purge autoremove + apt -y autoclean + rm -rf /var/lib/apt/lists/* + apt update + fi + ' +} + +for container in $(pct list | awk '{if(NR>1) print $1}'); do + if [[ " ${excluded_containers[@]} " =~ " $container " ]]; then + header_info + echo -e "${BL}[Info]${GN} Skipping ${BL}$container${CL}" + sleep 1 + continue + fi + + os=$(pct config "$container" | awk '/^ostype/ {print $2}') + # Supported: debian, ubuntu, alpine, centos + if [ "$os" != "debian" ] && [ "$os" != "ubuntu" ] && [ "$os" != "alpine" ] && [ "$os" != "centos" ]; then + header_info + echo -e "${BL}[Info]${GN} Skipping ${RD}$container is not Debian, Ubuntu, Alpine or Red Hat Compatible${CL} \n" + sleep 1 + continue + fi + + status=$(pct status "$container") + template=$(pct config "$container" | grep -q "template:" && echo "true" || echo "false") + + if [ "$template" == "false" ] && [ "$status" == "status: stopped" ]; then + echo -e "${BL}[Info]${GN} Starting${BL} $container ${CL} \n" + pct start "$container" + echo -e "${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL} \n" + sleep 5 + run_lxc_clean "$container" + echo -e "${BL}[Info]${GN} Shutting down${BL} $container ${CL} \n" + pct shutdown "$container" & + elif [ "$status" == "status: running" ]; then + run_lxc_clean "$container" + fi +done + +wait +header_info +echo -e "${GN} Finished, Selected Containers Cleaned. ${CL} \n" diff --git a/Proxmox/PVE/PVE LXC Execute.sh b/Proxmox/PVE/PVE LXC Execute.sh new file mode 100644 index 0000000..187bbb9 --- /dev/null +++ b/Proxmox/PVE/PVE LXC Execute.sh @@ -0,0 +1,94 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/execute.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: jeroenzwart +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info() { + clear + cat <<"EOF" + ______ __ __ _ ________ + / ____/ _____ _______ __/ /____ / / | |/ / ____/ + / __/ | |/_/ _ \/ ___/ / / / __/ _ \ / / | / / + / /____> </ __/ /__/ /_/ / /_/ __/ / /___/ / /___ +/_____/_/|_|\___/\___/\__,_/\__/\___/ /_____/_/|_\____/ + +EOF +} +set -eEuo pipefail +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +CM='\xE2\x9C\x94\033' +GN=$(echo "\033[1;92m") +CL=$(echo "\033[m") + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "execute-lxcs" "pve" + +header_info +echo "Loading..." +whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE LXC Execute" --yesno "This will execute a command inside selected LXC Containers. Proceed?" 10 58 +NODE=$(hostname) +EXCLUDE_MENU=() +MSG_MAX_LENGTH=0 +while read -r TAG ITEM; do + OFFSET=2 + ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET + EXCLUDE_MENU+=("$TAG" "$ITEM " "OFF") +done < <(pct list | awk 'NR>1') +excluded_containers=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --checklist "\nSelect containers to skip from executing:\n" \ + 16 $((MSG_MAX_LENGTH + 23)) 6 "${EXCLUDE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"') + +if [ $? -ne 0 ]; then + exit +fi + +read -r -p "Enter here command for inside the containers: " custom_command + +header_info +echo "One moment please...\n" + +function execute_in() { + container=$1 + name=$(pct exec "$container" hostname) + echo -e "${BL}[Info]${GN} Execute inside${BL} ${name}${GN} with output: ${CL}" + if ! pct exec "$container" -- bash -c "command ${custom_command} >/dev/null 2>&1"; then + echo -e "${BL}[Info]${GN} Skipping ${name} ${RD}$container has no command: ${custom_command}" + else + pct exec "$container" -- bash -c "${custom_command}" | tee + fi +} + +for container in $(pct list | awk '{if(NR>1) print $1}'); do + if [[ " ${excluded_containers[@]} " =~ " $container " ]]; then + echo -e "${BL}[Info]${GN} Skipping ${BL}$container${CL}" + else + os=$(pct config "$container" | awk '/^ostype/ {print $2}') + if [ "$os" != "debian" ] && [ "$os" != "ubuntu" ]; then + echo -e "${BL}[Info]${GN} Skipping ${name} ${RD}$container is not Debian or Ubuntu ${CL}" + continue + fi + + status=$(pct status "$container") + template=$(pct config "$container" | grep -q "template:" && echo "true" || echo "false") + if [ "$template" == "false" ] && [ "$status" == "status: stopped" ]; then + echo -e "${BL}[Info]${GN} Starting${BL} $container ${CL}" + pct start "$container" + echo -e "${BL}[Info]${GN} Waiting For${BL} $container${CL}${GN} To Start ${CL}" + sleep 5 + execute_in "$container" + echo -e "${BL}[Info]${GN} Shutting down${BL} $container ${CL}" + pct shutdown "$container" & + elif [ "$status" == "status: running" ]; then + execute_in "$container" + fi + fi +done + +wait + +echo -e "${GN} Finished, execute command inside selected containers. ${CL} \n" diff --git a/Proxmox/PVE/PVE LXC Filesystem Trim.sh b/Proxmox/PVE/PVE LXC Filesystem Trim.sh new file mode 100644 index 0000000..88fd4f9 --- /dev/null +++ b/Proxmox/PVE/PVE LXC Filesystem Trim.sh @@ -0,0 +1,197 @@ +# Это позволяет поддерживать производительность SSD за счет управления неиспользуемыми блоками. Системы хранения с тонким выделением ресурсов также требуют управления для предотвращения ненужного использования хранилища. Виртуальные машины автоматизируют fstrim, в то время как контейнеры LXC требуют ручных или автоматизированных процессов fstrim для оптимальной производительности. + +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/fstrim.sh)" + +#!/usr/bin/env bash + +set -eEuo pipefail + +function header_info() { + clear + cat <<"EOF" + _______ __ __ ______ _ + / ____(_) /__ _______ _______/ /____ ____ ___ /_ __/____(_)___ ___ + / /_ / / / _ \/ ___/ / / / ___/ __/ _ \/ __ `__ \ / / / ___/ / __ `__ \ + / __/ / / / __(__ ) /_/ (__ ) /_/ __/ / / / / / / / / / / / / / / / / +/_/ /_/_/\___/____/\__, /____/\__/\___/_/ /_/ /_/ /_/ /_/ /_/_/ /_/ /_/ + /____/ +EOF +} + +BL="\033[36m" +RD="\033[01;31m" +GN="\033[1;92m" +CL="\033[m" + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "fstrim" "pve" + +LOGFILE="/var/log/fstrim.log" +touch "$LOGFILE" +chmod 600 "$LOGFILE" +echo -e "\n----- $(date '+%Y-%m-%d %H:%M:%S') | fstrim Run by $(whoami) on $(hostname) -----" >>"$LOGFILE" + +header_info +echo "Loading..." + +whiptail --backtitle "Proxmox VE Helper Scripts" \ + --title "About fstrim (LXC)" \ + --msgbox "The 'fstrim' command releases unused blocks back to the storage device. This only makes sense for containers on SSD, NVMe, Thin-LVM, or storage with discard/TRIM support.\n\nIf your root filesystem or container disks are on classic HDDs, thick LVM, or unsupported storage types, running fstrim will have no effect.\n\nRecommended:\n- Use fstrim only on SSD, NVMe, or thin-provisioned storage with discard enabled.\n- For ZFS, ensure 'autotrim=on' is set on your pool.\n" 16 88 + +ROOT_FS=$(df -Th "/" | awk 'NR==2 {print $2}') +if [ "$ROOT_FS" != "ext4" ]; then + whiptail --backtitle "Proxmox VE Helper Scripts" \ + --title "Warning" \ + --yesno "Root filesystem is not ext4 ($ROOT_FS).\nContinue anyway?" 12 80 || exit 0 +fi + +NODE=$(hostname) +EXCLUDE_MENU=() +STOPPED_MENU=() +MAX_NAME_LEN=0 +MAX_STAT_LEN=0 + +# Build arrays with one pct list +mapfile -t CTLINES < <(pct list | awk 'NR>1') + +for LINE in "${CTLINES[@]}"; do + CTID=$(awk '{print $1}' <<<"$LINE") + STATUS=$(awk '{print $2}' <<<"$LINE") + NAME=$(awk '{print $3}' <<<"$LINE") + ((${#NAME} > MAX_NAME_LEN)) && MAX_NAME_LEN=${#NAME} + ((${#STATUS} > MAX_STAT_LEN)) && MAX_STAT_LEN=${#STATUS} +done + +FMT="%-${MAX_NAME_LEN}s | %-${MAX_STAT_LEN}s" + +for LINE in "${CTLINES[@]}"; do + CTID=$(awk '{print $1}' <<<"$LINE") + STATUS=$(awk '{print $2}' <<<"$LINE") + NAME=$(awk '{print $3}' <<<"$LINE") + DESC=$(printf "$FMT" "$NAME" "$STATUS") + EXCLUDE_MENU+=("$CTID" "$DESC" "OFF") + if [[ "$STATUS" == "stopped" ]]; then + STOPPED_MENU+=("$CTID" "$DESC" "OFF") + fi +done + +excluded_containers_raw=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ + --title "Containers on $NODE" \ + --checklist "\nSelect containers to skip from trimming:\n" \ + 20 $((MAX_NAME_LEN + MAX_STAT_LEN + 20)) 12 "${EXCLUDE_MENU[@]}" 3>&1 1>&2 2>&3) +[ $? -ne 0 ] && exit +read -ra EXCLUDED <<<$(echo "$excluded_containers_raw" | tr -d '"') + +TO_START=() +if [ ${#STOPPED_MENU[@]} -gt 0 ]; then + for ((i = 0; i < ${#STOPPED_MENU[@]}; i += 3)); do + CTID="${STOPPED_MENU[i]}" + DESC="${STOPPED_MENU[i + 1]}" + if [[ " ${EXCLUDED[*]} " =~ " $CTID " ]]; then + continue + fi + header_info + echo -e "${BL}[Info]${GN} Container $CTID ($DESC) is currently stopped.${CL}" + read -rp "Temporarily start for fstrim? [y/N]: " answer + if [[ "$answer" =~ ^[Yy]$ ]]; then + TO_START+=("$CTID") + fi + done +fi + +declare -A WAS_STOPPED +for ct in "${TO_START[@]}"; do + WAS_STOPPED["$ct"]=1 +done + +function trim_container() { + local container="$1" + local name="$2" + header_info + echo -e "${BL}[Info]${GN} Trimming ${BL}$container${CL} \n" + + local before_trim after_trim + local lv_name="vm-${container}-disk-0" + if lvs --noheadings -o lv_name 2>/dev/null | grep -qw "$lv_name"; then + before_trim=$(lvs --noheadings -o lv_name,data_percent 2>/dev/null | awk -v ctid="$lv_name" '$1 == ctid {gsub(/%/, "", $2); print $2}') + [[ -n "$before_trim" ]] && echo -e "${RD}Data before trim $before_trim%${CL}" || echo -e "${RD}Data before trim: not available${CL}" + else + before_trim="" + echo -e "${RD}Data before trim: not available (non-LVM storage)${CL}" + fi + + local fstrim_output + fstrim_output=$(pct fstrim "$container" 2>&1) + if echo "$fstrim_output" | grep -qi "not supported"; then + echo -e "${RD}fstrim isnt supported on this storage!${CL}" + elif echo "$fstrim_output" | grep -Eq '([0-9]+(\.[0-9]+)?\s*[KMGT]?B)'; then + echo -e "${GN}fstrim result: $fstrim_output${CL}" + else + echo -e "${RD}fstrim result: $fstrim_output${CL}" + fi + + if lvs --noheadings -o lv_name 2>/dev/null | grep -qw "$lv_name"; then + after_trim=$(lvs --noheadings -o lv_name,data_percent 2>/dev/null | awk -v ctid="$lv_name" '$1 == ctid {gsub(/%/, "", $2); print $2}') + [[ -n "$after_trim" ]] && echo -e "${GN}Data after trim $after_trim%${CL}" || echo -e "${GN}Data after trim: not available${CL}" + else + after_trim="" + echo -e "${GN}Data after trim: not available (non-LVM storage)${CL}" + fi + + # Logging + echo "$(date '+%Y-%m-%d %H:%M:%S') | CTID=$container | Name=$name | Before=${before_trim:-N/A}% | After=${after_trim:-N/A}% | fstrim: $fstrim_output" >>"$LOGFILE" + sleep 0.5 +} + +for LINE in "${CTLINES[@]}"; do + CTID=$(awk '{print $1}' <<<"$LINE") + STATUS=$(awk '{print $2}' <<<"$LINE") + NAME=$(awk '{print $3}' <<<"$LINE") + if [[ " ${EXCLUDED[*]} " =~ " $CTID " ]]; then + header_info + echo -e "${BL}[Info]${GN} Skipping $CTID ($NAME, excluded)${CL}" + sleep 0.5 + continue + fi + if pct config "$CTID" | grep -q "template:"; then + header_info + echo -e "${BL}[Info]${GN} Skipping $CTID ($NAME, template)${CL}\n" + sleep 0.5 + continue + fi + if [[ "$STATUS" != "running" ]]; then + if [[ -n "${WAS_STOPPED[$CTID]:-}" ]]; then + header_info + echo -e "${BL}[Info]${GN} Starting $CTID ($NAME) for trim...${CL}" + pct start "$CTID" + sleep 2 + else + header_info + echo -e "${BL}[Info]${GN} Skipping $CTID ($NAME, not running, not selected)${CL}" + sleep 0.5 + continue + fi + fi + + trim_container "$CTID" "$NAME" + + if [[ -n "${WAS_STOPPED[$CTID]:-}" ]]; then + read -rp "Stop LXC $CTID ($NAME) again after trim? [Y/n]: " answer + if [[ ! "$answer" =~ ^[Nn]$ ]]; then + header_info + echo -e "${BL}[Info]${GN} Stopping $CTID ($NAME) again...${CL}" + pct stop "$CTID" + sleep 1 + else + header_info + echo -e "${BL}[Info]${GN} Leaving $CTID ($NAME) running as requested.${CL}" + sleep 1 + fi + fi +done + +header_info +echo -e "${GN}Finished, LXC Containers Trimmed.${CL} \n" +echo -e "${BL}If you want to see the complete log: cat $LOGFILE${CL}" +exit 0 diff --git a/Proxmox/PVE/PVE LXC Tag.sh b/Proxmox/PVE/PVE LXC Tag.sh new file mode 100644 index 0000000..a2ed53e --- /dev/null +++ b/Proxmox/PVE/PVE LXC Tag.sh @@ -0,0 +1,1140 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/add-iptag.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (Canbiz) && Desert_Gamer +# License: MIT + +function header_info { + clear + cat <<"EOF" + ___ ____ _____ +|_ _| _ \ _ |_ _|_ _ __ _ + | || |_) (_) | |/ _` |/ _` | + | || __/ _ | | (_| | (_| | +|___|_| (_) |_|\__,_|\__, | + |___/ +EOF +} + +clear +header_info +APP="IP-Tag" +hostname=$(hostname) + +# Color variables +YW=$(echo "\033[33m") +GN=$(echo "\033[1;92m") +RD=$(echo "\033[01;31m") +CL=$(echo "\033[m") +BFR="\\r\\033[K" +HOLD=" " +CM="${GN}✓${CL} " +CROSS="${RD}✗${CL} " + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "add-iptag" "pve" + +# Stop any running spinner +stop_spinner() { + if [ -n "$SPINNER_PID" ] && kill -0 "$SPINNER_PID" 2>/dev/null; then + kill -TERM "$SPINNER_PID" 2>/dev/null + wait "$SPINNER_PID" 2>/dev/null + fi + SPINNER_PID="" + printf "\e[?25h\r" +} + +# Error handler for displaying error messages +error_handler() { + stop_spinner + local exit_code="$?" + local line_number="$1" + local command="$2" + local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}" + echo -e "\n$error_message\n" +} + +# Spinner for progress indication +spinner() { + local msg="$1" + local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏') + local spin_i=0 + local interval=0.1 + + trap 'exit 0' TERM INT + printf "\e[?25l" 2>/dev/null + + while true; do + printf "\r%s ${YW}%s${CL}" "${frames[spin_i]}" "$msg" 2>/dev/null || exit 0 + spin_i=$(((spin_i + 1) % ${#frames[@]})) + sleep "$interval" || exit 0 + done +} + +# Info message +msg_info() { + local msg="$1" + stop_spinner + spinner "$msg" & + SPINNER_PID=$! + disown $SPINNER_PID 2>/dev/null +} + +# Success message +msg_ok() { + stop_spinner + local msg="$1" + echo -e "${BFR}${CM}${GN}${msg}${CL}" +} + +# Error message +msg_error() { + stop_spinner + local msg="$1" + echo -e "${BFR}${CROSS}${RD}${msg}${CL}" +} + +# Migrate configuration from old path to new +migrate_config() { + local old_config="/opt/lxc-iptag" + local new_config="/opt/iptag/iptag.conf" + + if [[ -f "$old_config" ]]; then + msg_info "Migrating configuration from old path" + if cp "$old_config" "$new_config" &>/dev/null; then + rm -rf "$old_config" &>/dev/null + msg_ok "Configuration migrated and old config removed" + else + msg_error "Failed to migrate configuration" + fi + fi +} + +# Update existing installation +update_installation() { + msg_info "Updating IP-Tag Scripts" + systemctl stop iptag.service &>/dev/null + msg_ok "Stopped IP-Tag service" + + # Create directory if it doesn't exist + if [[ ! -d "/opt/iptag" ]]; then + mkdir -p /opt/iptag + fi + + # Create new config file (check if exists and ask user) + if [[ -f "/opt/iptag/iptag.conf" ]]; then + echo -e "\n${YW}Configuration file already exists.${CL}" + echo -e "${YW}Note: No critical changes were made in this version.${CL}" + while true; do + read -p "Do you want to replace it with defaults? (y/n): " yn + case $yn in + [Yy]*) + interactive_config_setup + msg_info "Replacing configuration file" + generate_config >/opt/iptag/iptag.conf + msg_ok "Configuration file replaced with defaults" + break + ;; + [Nn]*) + echo -e "${GN}✓ Keeping existing configuration file${CL}" + break + ;; + *) + echo -e "${RD}Please answer yes or no.${CL}" + ;; + esac + done + else + interactive_config_setup + msg_info "Creating new configuration file" + generate_config >/opt/iptag/iptag.conf + msg_ok "Created new configuration file at /opt/iptag/iptag.conf" + fi + + # Update main script + msg_info "Updating main script" + generate_main_script >/opt/iptag/iptag + chmod +x /opt/iptag/iptag + msg_ok "Updated main script" + + # Update service file + msg_info "Updating service file" + generate_service >/lib/systemd/system/iptag.service + msg_ok "Updated service file" + + msg_info "Creating manual run command" + cat <<'EOF' >/usr/local/bin/iptag-run +#!/usr/bin/env bash +CONFIG_FILE="/opt/iptag/iptag.conf" +SCRIPT_FILE="/opt/iptag/iptag" +if [[ ! -f "$SCRIPT_FILE" ]]; then + echo "✗ Main script not found: $SCRIPT_FILE" + exit 1 +fi +export FORCE_SINGLE_RUN=true +exec "$SCRIPT_FILE" +EOF + chmod +x /usr/local/bin/iptag-run + msg_ok "Created iptag-run executable - You can execute this manually by entering “iptag-run” in the Proxmox host, so the script is executed by hand." + + msg_info "Restarting service" + systemctl daemon-reload &>/dev/null + systemctl enable -q --now iptag.service &>/dev/null + msg_ok "Updated IP-Tag Scripts" + + # Show configuration information after update + show_post_install_info +} + +# Install only command without service +install_command_only() { + msg_info "Installing IP-Tag Command Only" + + # Create directory if it doesn't exist + if [[ ! -d "/opt/iptag" ]]; then + mkdir -p /opt/iptag + fi + + # Migrate config if needed + migrate_config + + # Interactive configuration setup + if [[ ! -f /opt/iptag/iptag.conf ]]; then + interactive_config_setup_command + msg_info "Setup Configuration" + generate_config >/opt/iptag/iptag.conf + msg_ok "Created configuration file at /opt/iptag/iptag.conf" + else + stop_spinner + echo -e "\n${YW}Configuration file already exists.${CL}" + read -p "Do you want to reconfigure tag format? (y/n): " reconfigure + case $reconfigure in + [Yy]*) + interactive_config_setup_command + msg_info "Updating Configuration" + generate_config >/opt/iptag/iptag.conf + msg_ok "Updated configuration file" + ;; + *) + msg_ok "Keeping existing configuration file" + ;; + esac + fi + + # Setup main script + msg_info "Setup Main Script" + generate_main_script >/opt/iptag/iptag + chmod +x /opt/iptag/iptag + msg_ok "Created main script" + + # Create manual run command + msg_info "Creating iptag-run command" + cat <<'EOF' >/usr/local/bin/iptag-run +#!/usr/bin/env bash +CONFIG_FILE="/opt/iptag/iptag.conf" +SCRIPT_FILE="/opt/iptag/iptag" +if [[ ! -f "$SCRIPT_FILE" ]]; then + echo "✗ Main script not found: $SCRIPT_FILE" + exit 1 +fi +export FORCE_SINGLE_RUN=true +exec "$SCRIPT_FILE" +EOF + chmod +x /usr/local/bin/iptag-run + msg_ok "Created iptag-run command" + + msg_ok "IP-Tag Command installed successfully! Use 'iptag-run' to run manually." +} + +# Show post-installation information +show_post_install_info() { + stop_spinner + echo -e "\n${YW}=== Next Steps ===${CL}" + + # Show usage information + if command -v iptag-run >/dev/null 2>&1; then + echo -e "${YW}Run IP tagging manually: ${GN}iptag-run${CL}" + echo -e "${YW}Add to cron for scheduled execution if needed${CL}" + echo -e "" + fi + + echo -e "${RD}IMPORTANT: Configure your network subnets!${CL}" + echo -e "" + echo -e "${YW}Configuration file: ${GN}/opt/iptag/iptag.conf${CL}" + echo -e "" + echo -e "${YW}Edit CIDR_LIST with your actual subnets:${CL}" + echo -e "${GN}nano /opt/iptag/iptag.conf${CL} ${YW}or${CL} ${GN}vim /opt/iptag/iptag.conf${CL}" + echo -e "" + echo -e "${YW}Example configuration:${CL}" + echo -e "${GN}CIDR_LIST=(${CL}" + echo -e "${GN} 192.168.1.0/24 # Your actual subnet${CL}" + echo -e "${GN} 10.10.0.0/16 # Another subnet${CL}" + echo -e "${GN})${CL}" + echo -e "" +} + +# Interactive configuration setup for command-only (TAG_FORMAT only) +interactive_config_setup_command() { + echo -e "\n${YW}=== Configuration Setup ===${CL}" + + # TAG_FORMAT configuration + echo -e "\n${YW}Select tag format:${CL}" + echo -e "${GN}1)${CL} last_two_octets - Show last two octets (e.g., 0.100) [Default]" + echo -e "${GN}2)${CL} last_octet - Show only last octet (e.g., 100)" + echo -e "${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)" + + while true; do + read -p "Enter your choice (1-3) [1]: " tag_choice + case ${tag_choice:-1} in + 1) + TAG_FORMAT="last_two_octets" + echo -e "${GN}✓ Selected: last_two_octets${CL}" + break + ;; + 2) + TAG_FORMAT="last_octet" + echo -e "${GN}✓ Selected: last_octet${CL}" + break + ;; + 3) + TAG_FORMAT="full" + echo -e "${GN}✓ Selected: full${CL}" + break + ;; + *) + echo -e "${RD}Please enter 1, 2, or 3.${CL}" + ;; + esac + done + + # Set default LOOP_INTERVAL for command mode + LOOP_INTERVAL=300 +} + +# Interactive configuration setup for service (TAG_FORMAT + LOOP_INTERVAL) +interactive_config_setup() { + echo -e "\n${YW}=== Configuration Setup ===${CL}" + + # TAG_FORMAT configuration + echo -e "\n${YW}Select tag format:${CL}" + echo -e "${GN}1)${CL} last_two_octets - Show last two octets (e.g., 0.100) [Default]" + echo -e "${GN}2)${CL} last_octet - Show only last octet (e.g., 100)" + echo -e "${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)" + + while true; do + read -p "Enter your choice (1-3) [1]: " tag_choice + case ${tag_choice:-1} in + 1) + TAG_FORMAT="last_two_octets" + echo -e "${GN}✓ Selected: last_two_octets${CL}" + break + ;; + 2) + TAG_FORMAT="last_octet" + echo -e "${GN}✓ Selected: last_octet${CL}" + break + ;; + 3) + TAG_FORMAT="full" + echo -e "${GN}✓ Selected: full${CL}" + break + ;; + *) + echo -e "${RD}Please enter 1, 2, or 3.${CL}" + ;; + esac + done + + # LOOP_INTERVAL configuration + echo -e "\n${YW}Set check interval (in seconds):${CL}" + echo -e "${YW}Default: 300 seconds (5 minutes)${CL}" + echo -e "${YW}Recommended range: 300-3600 seconds${CL}" + + while true; do + read -p "Enter interval in seconds [300]: " interval_input + interval_input=${interval_input:-300} + + if [[ $interval_input =~ ^[0-9]+$ ]] && [ $interval_input -ge 300 ] && [ $interval_input -le 7200 ]; then + LOOP_INTERVAL=$interval_input + echo -e "${GN}✓ Selected: ${LOOP_INTERVAL} seconds${CL}" + break + else + echo -e "${RD}Please enter a valid number between 300 and 7200 seconds.${CL}" + fi + done +} + +# Generate configuration file content +generate_config() { + cat <<EOF +# Configuration file for IP tagging + +# List of allowed CIDRs +CIDR_LIST=( + 192.168.0.0/16 + 10.0.0.0/8 + 100.64.0.0/10 +) + +# Tag format options: +# - "full": full IP address (e.g., 192.168.0.100) +# - "last_octet": only the last octet (e.g., 100) +# - "last_two_octets": last two octets (e.g., 0.100) +TAG_FORMAT="${TAG_FORMAT:-last_two_octets}" + + +# Check interval (in seconds) +LOOP_INTERVAL=${LOOP_INTERVAL:-300} + +# Debug settings (set to true to enable debugging) +DEBUG=false +EOF +} + +# Generate systemd service file content +generate_service() { + cat <<EOF +[Unit] +Description=IP-Tag service +After=network.target + +[Service] +Type=simple +ExecStart=/opt/iptag/iptag +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF +} + +# Generate main script content +generate_main_script() { + cat <<'EOF' +#!/bin/bash +# =============== CONFIGURATION =============== # +readonly CONFIG_FILE="/opt/iptag/iptag.conf" +readonly DEFAULT_TAG_FORMAT="full" +readonly DEFAULT_CHECK_INTERVAL=60 + +# Load the configuration file if it exists +if [ -f "$CONFIG_FILE" ]; then + # shellcheck source=./iptag.conf + source "$CONFIG_FILE" +fi + +# Set default DEBUG value if not defined +DEBUG=${DEBUG:-false} + +# Debug logging function +debug_log() { + if [[ "$DEBUG" == "true" || "$DEBUG" == "1" ]]; then + echo "[DEBUG] $*" >&2 + fi +} + +# Color constants +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[0;33m' +readonly BLUE='\033[0;34m' +readonly PURPLE='\033[0;35m' +readonly CYAN='\033[0;36m' +readonly WHITE='\033[1;37m' +readonly GRAY='\033[0;37m' +readonly NC='\033[0m' # No Color + +# Logging functions with colors +log_success() { + echo -e "${GREEN}✓${NC} $*" +} + +log_info() { + echo -e "${BLUE}ℹ${NC} $*" +} + +log_warning() { + echo -e "${YELLOW}⚠${NC} $*" +} + +log_error() { + echo -e "${RED}✗${NC} $*" +} + +log_change() { + echo -e "${CYAN}~${NC} $*" +} + +log_unchanged() { + echo -e "${GRAY}=${NC} $*" +} + +# Check if IP is in CIDR +ip_in_cidr() { + local ip="$1" cidr="$2" + debug_log "ip_in_cidr: checking '$ip' against '$cidr'" + + # Manual CIDR check - более надёжный метод + debug_log "ip_in_cidr: using manual check (bypassing ipcalc)" + local network prefix + IFS='/' read -r network prefix <<< "$cidr" + + # Convert IP and network to integers for comparison + local ip_int net_int mask + IFS='.' read -r a b c d <<< "$ip" + ip_int=$(( (a << 24) + (b << 16) + (c << 8) + d )) + + IFS='.' read -r a b c d <<< "$network" + net_int=$(( (a << 24) + (b << 16) + (c << 8) + d )) + + # Create subnet mask + mask=$(( 0xFFFFFFFF << (32 - prefix) )) + + # Apply mask and compare + local ip_masked=$((ip_int & mask)) + local net_masked=$((net_int & mask)) + + debug_log "ip_in_cidr: IP=$ip ($ip_int), Network=$network ($net_int), Prefix=$prefix" + debug_log "ip_in_cidr: Mask=$mask (hex: $(printf '0x%08x' $mask))" + debug_log "ip_in_cidr: IP&Mask=$ip_masked ($(printf '%d.%d.%d.%d' $((ip_masked>>24&255)) $((ip_masked>>16&255)) $((ip_masked>>8&255)) $((ip_masked&255))))" + debug_log "ip_in_cidr: Net&Mask=$net_masked ($(printf '%d.%d.%d.%d' $((net_masked>>24&255)) $((net_masked>>16&255)) $((net_masked>>8&255)) $((net_masked&255))))" + + if (( ip_masked == net_masked )); then + debug_log "ip_in_cidr: manual check PASSED - IP is in CIDR" + return 0 + else + debug_log "ip_in_cidr: manual check FAILED - IP is NOT in CIDR" + return 1 + fi +} + +# Format IP address according to the configuration +format_ip_tag() { + local ip="$1" + [[ -z "$ip" ]] && return + local format="${TAG_FORMAT:-$DEFAULT_TAG_FORMAT}" + case "$format" in + "last_octet") echo "${ip##*.}" ;; + "last_two_octets") echo "${ip#*.*.}" ;; + *) echo "$ip" ;; + esac +} + + +# Check if IP is in any CIDRs +ip_in_cidrs() { + local ip="$1" cidrs="$2" + [[ -z "$cidrs" ]] && return 1 + local IFS=' ' + debug_log "Checking IP '$ip' against CIDRs: '$cidrs'" + for cidr in $cidrs; do + debug_log "Testing IP '$ip' against CIDR '$cidr'" + if ip_in_cidr "$ip" "$cidr"; then + debug_log "IP '$ip' matches CIDR '$cidr' - PASSED" + return 0 + else + debug_log "IP '$ip' does not match CIDR '$cidr'" + fi + done + debug_log "IP '$ip' failed all CIDR checks" + return 1 +} + +# Check if IP is valid +is_valid_ipv4() { + local ip="$1" + [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1 + + local IFS='.' parts + read -ra parts <<< "$ip" + for part in "${parts[@]}"; do + (( part >= 0 && part <= 255 )) || return 1 + done + return 0 +} + +# Get VM IPs using improved methods +get_vm_ips() { + local vmid=$1 ips="" + local vm_config="/etc/pve/qemu-server/${vmid}.conf" + [[ ! -f "$vm_config" ]] && return + + debug_log "vm $vmid: starting IP detection" + + # Check if VM is running first + local vm_status="" + if command -v qm >/dev/null 2>&1; then + vm_status=$(qm status "$vmid" 2>/dev/null | awk '{print $2}') + fi + + if [[ "$vm_status" != "running" ]]; then + debug_log "vm $vmid: not running (status: $vm_status)" + return + fi + + # Get MAC addresses from config + local mac_addresses=$(grep -E "^net[0-9]+:" "$vm_config" | grep -oE "([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}" | head -3) + debug_log "vm $vmid: found MACs: $mac_addresses" + + # Method 1: QM guest agent (most reliable for current IP) + if command -v qm >/dev/null 2>&1; then + debug_log "vm $vmid: trying qm guest agent first" + local qm_ips=$(timeout 8 qm guest cmd "$vmid" network-get-interfaces 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v "127.0.0.1" | head -3) + for qm_ip in $qm_ips; do + if [[ "$qm_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $qm_ip via qm guest cmd" + ips+="$qm_ip " + fi + done + fi + + # Method 2: Fresh ARP table lookup (force refresh) + if [[ -n "$mac_addresses" ]]; then + debug_log "vm $vmid: refreshing ARP table and checking" + # Try to refresh ARP table by pinging network ranges + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + + # First check current ARP table + local current_ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + + # If found in ARP, verify it's still valid by trying to ping + if [[ -n "$current_ip" && "$current_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $current_ip in ARP table for MAC $mac_lower, verifying..." + # Quick ping test to verify IP is still active + if timeout 2 ping -c 1 "$current_ip" >/dev/null 2>&1; then + debug_log "vm $vmid: verified IP $current_ip is active via ping" + ips+="$current_ip " + else + debug_log "vm $vmid: IP $current_ip failed ping verification, removing from ARP" + # Remove stale ARP entry + ip neighbor del "$current_ip" dev $(ip route get "$current_ip" 2>/dev/null | grep -oE 'dev [^ ]+' | cut -d' ' -f2) 2>/dev/null || true + fi + fi + done + fi + + # Method 3: DHCP leases (backup method) + if [[ -z "$ips" ]]; then + debug_log "vm $vmid: checking DHCP leases as fallback" + for mac in $mac_addresses; do + local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]') + for dhcp_file in "/var/lib/dhcp/dhcpd.leases" "/var/lib/dhcpcd5/dhcpcd.leases"; do + if [[ -f "$dhcp_file" ]]; then + # Look for most recent lease for this MAC + local dhcp_ip=$(tac "$dhcp_file" 2>/dev/null | grep -A 10 "ethernet $mac_lower" | grep "binding state active" -A 5 | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}" | head -1) + if [[ -n "$dhcp_ip" && "$dhcp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "vm $vmid: found IP $dhcp_ip via DHCP leases" + # Verify this IP responds + if timeout 2 ping -c 1 "$dhcp_ip" >/dev/null 2>&1; then + debug_log "vm $vmid: verified DHCP IP $dhcp_ip is active" + ips+="$dhcp_ip " + break 2 + else + debug_log "vm $vmid: DHCP IP $dhcp_ip failed ping test" + fi + fi + fi + done + done + fi + + # Remove duplicates and clean up + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + + debug_log "vm $vmid: final IPs: '$unique_ips'" + echo "$unique_ips" +} + +# Cache for configs to avoid repeated reads +declare -A CONFIG_CACHE +declare -A IP_CACHE + +# Update tags for container or VM +update_tags() { + local type="$1" vmid="$2" + local current_ips_full + local current_tags_raw="" + + # Get IPs with caching + local cache_key="${type}_${vmid}" + if [[ -n "${IP_CACHE[$cache_key]:-}" ]]; then + current_ips_full="${IP_CACHE[$cache_key]}" + debug_log "$type $vmid: using cached IPs" + else + if [[ "$type" == "lxc" ]]; then + current_ips_full=$(get_lxc_ips "${vmid}") + else + current_ips_full=$(get_vm_ips "${vmid}") + fi + IP_CACHE[$cache_key]="$current_ips_full" + fi + + # Get current tags (optimized file reading) + if [[ "$type" == "lxc" ]]; then + local config_file="/etc/pve/lxc/${vmid}.conf" + if [[ -f "$config_file" ]]; then + current_tags_raw=$(grep "^tags:" "$config_file" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//') + fi + else + local vm_config="/etc/pve/qemu-server/${vmid}.conf" + if [[ -f "$vm_config" ]]; then + current_tags_raw=$(grep "^tags:" "$vm_config" 2>/dev/null | cut -d: -f2 | sed 's/^[[:space:]]*//') + fi + fi + + local current_tags=() next_tags=() current_ip_tags=() + if [[ -n "$current_tags_raw" ]]; then + mapfile -t current_tags < <(echo "$current_tags_raw" | sed 's/;/\n/g') + fi + + # Separate IP/numeric and user tags + for tag in "${current_tags[@]}"; do + if is_valid_ipv4 "${tag}" || [[ "$tag" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then + current_ip_tags+=("${tag}") + else + next_tags+=("${tag}") + fi + done + + # Generate new IP tags from current IPs + local formatted_ips=() + debug_log "$type $vmid current_ips_full: '$current_ips_full'" + debug_log "$type $vmid CIDR_LIST: ${CIDR_LIST[*]}" + for ip in $current_ips_full; do + [[ -z "$ip" ]] && continue + debug_log "$type $vmid processing IP: '$ip'" + if is_valid_ipv4 "$ip"; then + debug_log "$type $vmid IP '$ip' is valid" + if ip_in_cidrs "$ip" "${CIDR_LIST[*]}"; then + debug_log "$type $vmid IP '$ip' passed CIDR check" + local formatted_ip=$(format_ip_tag "$ip") + debug_log "$type $vmid formatted '$ip' -> '$formatted_ip'" + [[ -n "$formatted_ip" ]] && formatted_ips+=("$formatted_ip") + else + debug_log "$type $vmid IP '$ip' failed CIDR check" + fi + else + debug_log "$type $vmid IP '$ip' is invalid" + fi + done + debug_log "$type $vmid final formatted_ips: ${formatted_ips[*]}" + + # If LXC and no IPs detected, do not touch tags at all + if [[ "$type" == "lxc" && ${#formatted_ips[@]} -eq 0 ]]; then + log_unchanged "LXC ${GRAY}${vmid}${NC}: No IP detected, tags unchanged" + return + fi + + # Prepend new IP tags to the beginning of the tag list + local final_tags=() + for new_ip in "${formatted_ips[@]}"; do + final_tags+=("$new_ip") + done + for tag in "${next_tags[@]}"; do + final_tags+=("$tag") + done + next_tags=("${final_tags[@]}") + + # Update tags if there are changes + local old_tags_str=$(IFS=';'; echo "${current_tags[*]}") + local new_tags_str=$(IFS=';'; echo "${next_tags[*]}") + + debug_log "$type $vmid old_tags: '$old_tags_str'" + debug_log "$type $vmid new_tags: '$new_tags_str'" + debug_log "$type $vmid tags_equal: $([[ "$old_tags_str" == "$new_tags_str" ]] && echo true || echo false)" + + if [[ "$old_tags_str" != "$new_tags_str" ]]; then + # Determine what changed + local old_ip_tags_count=${#current_ip_tags[@]} + local new_ip_tags_count=${#formatted_ips[@]} + + # Build detailed change message + local change_details="" + + if [[ $old_ip_tags_count -eq 0 ]]; then + change_details="added ${new_ip_tags_count} IP tag(s): [${GREEN}${formatted_ips[*]}${NC}]" + else + # Compare old and new IP tags + local added_tags=() removed_tags=() common_tags=() + + # Find removed tags + for old_tag in "${current_ip_tags[@]}"; do + local found=false + for new_tag in "${formatted_ips[@]}"; do + if [[ "$old_tag" == "$new_tag" ]]; then + found=true + break + fi + done + if [[ "$found" == false ]]; then + removed_tags+=("$old_tag") + else + common_tags+=("$old_tag") + fi + done + + # Find added tags + for new_tag in "${formatted_ips[@]}"; do + local found=false + for old_tag in "${current_ip_tags[@]}"; do + if [[ "$new_tag" == "$old_tag" ]]; then + found=true + break + fi + done + if [[ "$found" == false ]]; then + added_tags+=("$new_tag") + fi + done + + # Build change message + local change_parts=() + if [[ ${#added_tags[@]} -gt 0 ]]; then + change_parts+=("added [${GREEN}${added_tags[*]}${NC}]") + fi + if [[ ${#removed_tags[@]} -gt 0 ]]; then + change_parts+=("removed [${YELLOW}${removed_tags[*]}${NC}]") + fi + if [[ ${#common_tags[@]} -gt 0 ]]; then + change_parts+=("kept [${GRAY}${common_tags[*]}${NC}]") + fi + + change_details=$(IFS=', '; echo "${change_parts[*]}") + fi + + log_change "${type^^} ${CYAN}${vmid}${NC}: ${change_details}" + + if [[ "$type" == "lxc" ]]; then + pct set "${vmid}" -tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null + else + qm set "${vmid}" --tags "$(IFS=';'; echo "${next_tags[*]}")" &>/dev/null + fi + else + # Tags unchanged + local ip_count=${#formatted_ips[@]} + local status_msg="" + + if [[ $ip_count -eq 0 ]]; then + status_msg="No IPs detected" + elif [[ $ip_count -eq 1 ]]; then + status_msg="IP tag [${GRAY}${formatted_ips[0]}${NC}] unchanged" + else + status_msg="${ip_count} IP tags [${GRAY}${formatted_ips[*]}${NC}] unchanged" + fi + + log_unchanged "${type^^} ${GRAY}${vmid}${NC}: ${status_msg}" + fi +} + +# Update all instances of specified type +update_all_tags() { + local type="$1" vmids count=0 + + # Get list of all containers/VMs + if [[ "$type" == "lxc" ]]; then + vmids=($(pct list 2>/dev/null | grep -v VMID | awk '{print $1}')) + else + # More efficient: direct file listing instead of ls+sed + vmids=() + for conf in /etc/pve/qemu-server/*.conf; do + [[ -f "$conf" ]] || continue + local basename="${conf##*/}" + vmids+=("${basename%.conf}") + done + fi + + count=${#vmids[@]} + [[ $count -eq 0 ]] && return + + # Display processing header with color + if [[ "$type" == "lxc" ]]; then + log_info "Processing ${WHITE}${count}${NC} LXC container(s)" + else + log_info "Processing ${WHITE}${count}${NC} virtual machine(s)" + fi + + # Process each VM/LXC container + local processed=0 + for vmid in "${vmids[@]}"; do + update_tags "$type" "$vmid" + ((processed++)) + done + + # Add completion message + if [[ "$type" == "lxc" ]]; then + log_success "Completed processing LXC containers" + else + log_success "Completed processing virtual machines" + fi +} + +# Main check function +check() { + local start_time=$(date +%s) + + log_info "Starting periodic check" + + # Clear caches before each run + CONFIG_CACHE=() + IP_CACHE=() + + # Update LXC containers + update_all_tags "lxc" + + # Update VMs + update_all_tags "vm" + + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + log_success "Check completed in ${WHITE}${duration}${NC} seconds" +} + +# Main loop +main() { + # Display startup message + echo -e "\n${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + log_success "IP-Tag service started successfully" + echo -e "${BLUE}ℹ${NC} Loop interval: ${WHITE}${LOOP_INTERVAL:-300}${NC} seconds" + echo -e "${BLUE}ℹ${NC} Debug mode: ${WHITE}${DEBUG:-false}${NC}" + echo -e "${BLUE}ℹ${NC} Tag format: ${WHITE}${TAG_FORMAT:-full}${NC}" + echo -e "${BLUE}ℹ${NC} Allowed CIDRs: ${WHITE}${CIDR_LIST[*]}${NC}" + echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n" + + if [[ "$FORCE_SINGLE_RUN" == "true" ]]; then + check + exit 0 + fi + + while true; do + check + sleep "${LOOP_INTERVAL:-300}" + done +} + + + + + +# Simple LXC IP detection +get_lxc_ips() { + local vmid=$1 + + debug_log "lxc $vmid: starting IP detection" + + # Check if LXC is running + local lxc_status=$(pct status "${vmid}" 2>/dev/null | awk '{print $2}') + if [[ "$lxc_status" != "running" ]]; then + debug_log "lxc $vmid: not running (status: $lxc_status)" + return + fi + + local ips="" + + # Method 1: Check Proxmox config for ALL static IPs (multiple interfaces) + local pve_lxc_config="/etc/pve/lxc/${vmid}.conf" + if [[ -f "$pve_lxc_config" ]]; then + local static_ips=$(grep -E "^net[0-9]+:" "$pve_lxc_config" 2>/dev/null | grep -oE 'ip=([0-9]{1,3}\.){3}[0-9]{1,3}' | cut -d'=' -f2) + if [[ -n "$static_ips" ]]; then + while IFS= read -r ip; do + if [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "lxc $vmid: found static IP $ip in config" + ips="${ips}${ips:+ }${ip}" + fi + done <<< "$static_ips" + fi + fi + + # Method 2: ARP table lookup for ALL MAC addresses if no static IPs found + if [[ -z "$ips" && -f "$pve_lxc_config" ]]; then + local mac_addrs=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" | cut -d'=' -f2) + if [[ -n "$mac_addrs" ]]; then + while IFS= read -r mac_addr; do + [[ -z "$mac_addr" ]] && continue + local arp_ip=$(ip neighbor show | grep -i "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1) + if [[ -n "$arp_ip" && "$arp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + debug_log "lxc $vmid: found IP $arp_ip via ARP table for MAC $mac_addr" + ips="${ips}${ips:+ }${arp_ip}" + fi + done <<< "$mac_addrs" + fi + fi + + # Method 3: Direct container command to get ALL IPs if previous methods failed + if [[ -z "$ips" ]]; then + local container_ips=$(timeout 5s pct exec "$vmid" -- ip -4 addr show 2>/dev/null | grep -oE 'inet ([0-9]{1,3}\.){3}[0-9]{1,3}' | awk '{print $2}' | grep -v '127.0.0.1') + if [[ -n "$container_ips" ]]; then + while IFS= read -r ip; do + if is_valid_ipv4 "$ip"; then + debug_log "lxc $vmid: found IP $ip via pct exec" + ips="${ips}${ips:+ }${ip}" + fi + done <<< "$container_ips" + fi + fi + + # Remove duplicates and clean up + local unique_ips=$(echo "$ips" | tr ' ' '\n' | sort -u | tr '\n' ' ') + unique_ips="${unique_ips% }" + + debug_log "lxc $vmid: final IPs: '$unique_ips'" + echo "$unique_ips" +} + +main +EOF +} + +# Choose installation/update mode +echo -e "\n${YW}Choose action:${CL}" +echo -e "${GN}1)${CL} Install with automatic service (recommended)" +echo -e "${GN}2)${CL} Install command only (manual execution)" +echo -e "${GN}3)${CL} Update existing installation" +echo -e "${RD}4)${CL} Cancel" + +while true; do + read -p "Enter your choice (1-4): " choice + case $choice in + 1) + INSTALL_MODE="service" + echo -e "${GN}✓ Selected: Service installation${CL}" + break + ;; + 2) + INSTALL_MODE="command" + echo -e "${GN}✓ Selected: Command-only installation${CL}" + break + ;; + 3) + echo -e "${GN}✓ Selected: Update installation${CL}" + update_installation + exit 0 + ;; + 4) + msg_error "Action cancelled." + exit 0 + ;; + *) + msg_error "Please enter 1, 2, 3, or 4." + ;; + esac +done + +echo -e "\n${YW}This will install ${APP} on ${hostname} in $INSTALL_MODE mode.${CL}" +while true; do + read -p "Proceed? (y/n): " yn + case $yn in + [Yy]*) + break + ;; + [Nn]*) + msg_error "Installation cancelled." + exit + ;; + *) + msg_error "Please answer yes or no." + ;; + esac +done + +if ! pveversion | grep -Eq "pve-manager/(8\.[0-4]|9\.[0-9]+)(\.[0-9]+)*"; then + msg_error "This version of Proxmox Virtual Environment is not supported" + msg_error "⚠ Requires Proxmox Virtual Environment Version 8.0–8.4 or 9.x." + msg_error "Exiting..." + sleep 2 + exit +fi + +msg_info "Installing Dependencies" +apt-get update &>/dev/null +apt-get install -y ipcalc net-tools &>/dev/null +msg_ok "Installed Dependencies" + +# Execute installation based on selected mode +if [[ "$INSTALL_MODE" == "service" ]]; then + # Full service installation + msg_info "Setting up IP-Tag Scripts" + mkdir -p /opt/iptag + msg_ok "Setup IP-Tag Scripts" + + # Migrate config if needed + migrate_config + + # Interactive configuration setup + if [[ ! -f /opt/iptag/iptag.conf ]]; then + interactive_config_setup + msg_info "Setup Default Config" + generate_config >/opt/iptag/iptag.conf + msg_ok "Setup default config" + else + stop_spinner + echo -e "\n${YW}Configuration file already exists.${CL}" + read -p "Do you want to reconfigure tag format and loop interval? (y/n): " reconfigure + case $reconfigure in + [Yy]*) + interactive_config_setup + msg_info "Updating Configuration" + generate_config >/opt/iptag/iptag.conf + msg_ok "Updated configuration file" + ;; + *) + msg_ok "Keeping existing configuration file" + ;; + esac + fi + + msg_info "Setup Main Function" + generate_main_script >/opt/iptag/iptag + chmod +x /opt/iptag/iptag + msg_ok "Setup Main Function" + + msg_info "Creating Service" + generate_service >/lib/systemd/system/iptag.service + msg_ok "Created Service" + + msg_info "Starting Service" + systemctl daemon-reload &>/dev/null + systemctl enable -q --now iptag.service &>/dev/null + msg_ok "Started Service" + + msg_info "Creating manual run command" + cat <<'EOF' >/usr/local/bin/iptag-run +#!/usr/bin/env bash +CONFIG_FILE="/opt/iptag/iptag.conf" +SCRIPT_FILE="/opt/iptag/iptag" +if [[ ! -f "$SCRIPT_FILE" ]]; then + echo "✗ Main script not found: $SCRIPT_FILE" + exit 1 +fi +export FORCE_SINGLE_RUN=true +exec "$SCRIPT_FILE" +EOF + chmod +x /usr/local/bin/iptag-run + msg_ok "Created iptag-run command" + + echo -e "\n${GN}${APP} service installation completed successfully! ${CL}" + echo -e "${YW}The service is now running automatically.${CL}" + echo -e "${YW}You can also run it manually with: ${GN}iptag-run${CL}\n" + + # Show configuration information + show_post_install_info + +elif [[ "$INSTALL_MODE" == "command" ]]; then + # Command-only installation + install_command_only + + stop_spinner + echo -e "\n${GN}${APP} command installation completed successfully! ${CL}" + + # Show configuration information + show_post_install_info +fi + +# Clean up any running spinner and exit +stop_spinner +exit 0 diff --git a/Proxmox/PVE/PVE Netdata.sh b/Proxmox/PVE/PVE Netdata.sh new file mode 100644 index 0000000..8b30afe --- /dev/null +++ b/Proxmox/PVE/PVE Netdata.sh @@ -0,0 +1,171 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/netdata.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.netdata.cloud/ | Github: https://github.com/netdata/netdata + +function header_info { + clear + cat <<"EOF" + _ __ __ ____ __ + / | / /__ / /_/ __ \____ _/ /_____ _ + / |/ / _ \/ __/ / / / __ `/ __/ __ `/ + / /| / __/ /_/ /_/ / /_/ / /_/ /_/ / +/_/ |_/\___/\__/_____/\__,_/\__/\__,_/ + +EOF +} + +YW=$(echo "\033[33m") +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +GN=$(echo "\033[1;92m") +CL=$(echo "\033[m") +BFR="\\r\\033[K" +HOLD="-" +CM="${GN}✓${CL}" +silent() { "$@" >/dev/null 2>&1; } + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "netdata" "addon" + +set -e +header_info +echo "Loading..." +function msg_info() { + local msg="$1" + echo -ne " ${HOLD} ${YW}${msg}..." +} + +function msg_ok() { + local msg="$1" + echo -e "${BFR} ${CM} ${GN}${msg}${CL}" +} + +function msg_error() { echo -e "${RD}✗ $1${CL}"; } + +# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported. +# Supported: Proxmox VE 8.0.x – 8.9.x and 9.0–9.x +pve_check() { + local PVE_VER + PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + + # Check for Proxmox VE 8.x: allow 8.0–8.9 + if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then + local MINOR="${BASH_REMATCH[1]}" + if ((MINOR < 0 || MINOR > 9)); then + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported: Proxmox VE version 8.0 – 8.9" + exit 105 + fi + return 0 + fi + + # Check for Proxmox VE 9.x: allow 9.0–9.x + if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then + return 0 + fi + + # All other unsupported versions + msg_error "This version of Proxmox VE is not supported." + msg_error "Supported versions: Proxmox VE 8.0 – 8.9 or 9.0–9.x" + exit 105 +} + +detect_codename() { + source /etc/os-release + if [[ "$ID" != "debian" ]]; then + msg_error "Unsupported base OS: $ID (only Proxmox VE / Debian supported)." + exit 238 + fi + CODENAME="${VERSION_CODENAME:-}" + if [[ -z "$CODENAME" ]]; then + msg_error "Could not detect Debian codename." + exit 71 + fi + echo "$CODENAME" +} + +get_latest_repo_pkg() { + local REPO_URL=$1 + curl -fsSL "$REPO_URL" | + grep -oP 'netdata-repo_[^"]+all\.deb' | + sort -V | + tail -n1 +} + +install() { + header_info + while true; do + read -p "Are you sure you want to install NetData on Proxmox VE host. Proceed(y/n)? " yn + case $yn in + [Yy]*) break ;; + [Nn]*) exit ;; + *) echo "Please answer yes or no." ;; + esac + done + + read -r -p "Verbose mode? <y/N> " prompt + [[ ${prompt,,} =~ ^(y|yes)$ ]] && STD="" || STD="silent" + + CODENAME=$(detect_codename) + REPO_URL="https://repo.netdata.cloud/repos/repoconfig/debian/${CODENAME}/" + + msg_info "Setting up repository" + $STD apt-get install -y debian-keyring + PKG=$(get_latest_repo_pkg "$REPO_URL") + if [[ -z "$PKG" ]]; then + msg_error "Could not find netdata-repo package for Debian $CODENAME" + exit 237 + fi + curl -fsSL "${REPO_URL}${PKG}" -o "$PKG" + $STD dpkg -i "$PKG" + rm -f "$PKG" + msg_ok "Set up repository" + + msg_info "Installing Netdata" + $STD apt-get update + $STD apt-get install -y netdata + msg_ok "Installed Netdata" + msg_ok "Completed successfully!\n" + echo -e "\n Netdata should be reachable at${BL} http://$(hostname -I | awk '{print $1}'):19999 ${CL}\n" +} + +uninstall() { + header_info + read -r -p "Verbose mode? <y/N> " prompt + [[ ${prompt,,} =~ ^(y|yes)$ ]] && STD="" || STD="silent" + + msg_info "Uninstalling Netdata" + systemctl stop netdata || true + rm -rf /var/log/netdata /var/lib/netdata /var/cache/netdata /etc/netdata/go.d + rm -rf /etc/apt/trusted.gpg.d/netdata-archive-keyring.gpg /etc/apt/sources.list.d/netdata.list + $STD apt-get remove --purge -y netdata netdata-repo + systemctl daemon-reload + $STD apt autoremove -y + $STD userdel netdata || true + msg_ok "Uninstalled Netdata" + msg_ok "Completed successfully!\n" +} + +header_info +pve_check + +OPTIONS=(Install "Install NetData on Proxmox VE" + Uninstall "Uninstall NetData from Proxmox VE") + +CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "NetData" \ + --menu "Select an option:" 10 58 2 "${OPTIONS[@]}" 3>&1 1>&2 2>&3) + +case $CHOICE in +"Install") install ;; +"Uninstall") uninstall ;; +*) + echo "Exiting..." + exit 0 + ;; +esac diff --git a/Proxmox/PVE/PVE Update Repositories.sh b/Proxmox/PVE/PVE Update Repositories.sh new file mode 100644 index 0000000..c82bb7c --- /dev/null +++ b/Proxmox/PVE/PVE Update Repositories.sh @@ -0,0 +1,72 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/update-repo.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 tteck +# Author: MickLesk +# License: MIT +# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info { + clear + cat <<"EOF" + __ __ __ __ ____ + / / / /___ ____/ /___ _/ /____ / __ \___ ____ ____ + / / / / __ \/ __ / __ `/ __/ _ \ / /_/ / _ \/ __ \/ __ \ +/ /_/ / /_/ / /_/ / /_/ / /_/ __/ / _, _/ __/ /_/ / /_/ / +\____/ .___/\__,_/\__,_/\__/\___/ /_/ |_|\___/ .___/\____/ + /_/ /_/ +EOF +} + +set -eEuo pipefail +BL=$(echo "\033[36m") +RD=$(echo "\033[01;31m") +GN=$(echo "\033[1;92m") +CL=$(echo "\033[m") + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "update-repo" "pve" + +header_info +echo "Loading..." +NODE=$(hostname) + +function update_container() { + container=$1 + os=$(pct config "$container" | awk '/^ostype/ {print $2}') + + if [[ "$os" == "ubuntu" || "$os" == "debian" ]]; then + echo -e "${BL}[Info]${GN} Checking /usr/bin/update in ${BL}$container${CL} (OS: ${GN}$os${CL})" + + if pct exec "$container" -- [ -e /usr/bin/update ]; then + if pct exec "$container" -- grep -q "community-scripts/ProxmoxVE" /usr/bin/update; then + echo -e "${RD}[No Change]${CL} /usr/bin/update is already up to date in ${BL}$container${CL}.\n" + elif pct exec "$container" -- grep -q -v "tteck" /usr/bin/update; then + echo -e "${RD}[Warning]${CL} /usr/bin/update in ${BL}$container${CL} contains a different entry (${RD}tteck${CL}). No changes made.\n" + else + pct exec "$container" -- bash -c "sed -i 's/tteck\\/Proxmox/community-scripts\\/ProxmoxVE/g' /usr/bin/update" + + if pct exec "$container" -- grep -q "community-scripts/ProxmoxVE" /usr/bin/update; then + echo -e "${GN}[Success]${CL} /usr/bin/update updated in ${BL}$container${CL}.\n" + else + echo -e "${RD}[Error]${CL} /usr/bin/update in ${BL}$container${CL} could not be updated properly.\n" + fi + fi + else + echo -e "${RD}[Error]${CL} /usr/bin/update not found in container ${BL}$container${CL}.\n" + fi + else + echo -e "${BL}[Info]${GN} Skipping ${BL}$container${CL} (not Debian/Ubuntu)\n" + fi +} + +header_info +for container in $(pct list | awk '{if(NR>1) print $1}'); do + update_container "$container" +done + +header_info +echo -e "${GN}The process is complete. The repositories have been switched to community-scripts/ProxmoxVE.${CL}\n" diff --git a/Proxmox/pbs/PBS Processor Microcode.sh b/Proxmox/pbs/PBS Processor Microcode.sh new file mode 100644 index 0000000..8a38fd5 --- /dev/null +++ b/Proxmox/pbs/PBS Processor Microcode.sh @@ -0,0 +1,188 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/pbs-microcode.sh)" + + +# Микрокод процессора — это низкоуровневый программный слой, работающий на процессоре и предоставляющий исправления или обновления для его микропрограммы. Обновления микрокода могут исправлять аппаратные ошибки, +# повышать производительность и улучшать функции безопасности процессора. Этот скрипт адаптирован для среды Proxmox Backup Server и будет работать только на физических системах. При запуске в виртуализированной среде скрипт завершит работу. +# Обратите внимание, что механизмы обновления микропрограммы, такие как Intel Management Engine (ME) или AMD Platform Security Processor (PSP), могут различаться в зависимости от вашего процессора и его реализации. Пожалуйста, обратитесь к +# документации вашего процессора, чтобы проверить, можно ли применить обновления микропрограммы через операционную систему. + + + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: DonPablo1010 | Co-Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +function header_info { + clear + cat <<"EOF" + ____ __ ____ __ + / __ \_________ ________ ______________ _____ / |/ (_)_____________ _________ ____/ /__ + / /_/ / ___/ __ \/ ___/ _ \/ ___/ ___/ __ \/ ___/ / /|_/ / / ___/ ___/ __ \/ ___/ __ \/ __ / _ \ + / ____/ / / /_/ / /__/ __(__ |__ ) /_/ / / / / / / / /__/ / / /_/ / /__/ /_/ / /_/ / __/ +/_/ /_/ \____/\___/\___/____/____/\____/_/ /_/ /_/_/\___/_/ \____/\___/\____/\__,_/\___/ + + Proxmox Backup Server Processor Microcode Updater +EOF +} + +# Color definitions +RD=$(echo "\033[01;31m") +YW=$(echo "\033[33m") +GN=$(echo "\033[1;92m") +CL=$(echo "\033[m") +BFR="\\r\\033[K" +HOLD="-" +CM="${GN}✓${CL}" +CROSS="${RD}✗${CL}" + +msg_info() { echo -ne " ${HOLD} ${YW}$1..."; } + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "pbs-microcode" "pve" +msg_ok() { echo -e "${BFR} ${CM} ${GN}$1${CL}"; } +msg_error() { echo -e "${BFR} ${CROSS} ${RD}$1${CL}"; } + +header_info + +# Check if running on bare metal using systemd-detect-virt. +virt=$(systemd-detect-virt) +if [ "$virt" != "none" ]; then + msg_error "This script must be run on bare metal. Detected virtual environment: $virt" + exit 232 +fi + +# Attempt to obtain the current loaded microcode revision +current_microcode=$(journalctl -k | grep -i 'microcode: Current revision:' | grep -oP 'Current revision: \K0x[0-9a-f]+') +[ -z "$current_microcode" ] && current_microcode="Not found." + +intel() { + if ! dpkg -s iucode-tool >/dev/null 2>&1; then + msg_info "Installing iucode-tool (Intel microcode updater)" + apt-get install -y iucode-tool &>/dev/null + msg_ok "Installed iucode-tool" + else + msg_ok "Intel iucode-tool is already installed" + sleep 1 + fi + + intel_microcode=$(curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/" | grep -o 'href="[^"]*amd64.deb"' | sed 's/href="//;s/"//') + [ -z "$intel_microcode" ] && { + whiptail --backtitle "Proxmox Backup Server Helper Scripts" --title "No Microcode Found" --msgbox "No microcode packages were found.\nTry again later." 10 68 + msg_info "Exiting" + sleep 1 + msg_ok "Done" + exit + } + + MICROCODE_MENU=() + MSG_MAX_LENGTH=0 + + while read -r TAG ITEM; do + OFFSET=2 + ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET)) + MICROCODE_MENU+=("$TAG" "$ITEM " "OFF") + done < <(echo "$intel_microcode") + + microcode=$(whiptail --backtitle "Proxmox Backup Server Helper Scripts" \ + --title "Current Microcode Revision: ${current_microcode}" \ + --radiolist "\nSelect a microcode package to install:\n" \ + 16 $((MSG_MAX_LENGTH + 58)) 6 "${MICROCODE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"') + + [ -z "$microcode" ] && { + whiptail --backtitle "Proxmox Backup Server Helper Scripts" --title "No Microcode Selected" --msgbox "No microcode package was selected." 10 68 + msg_info "Exiting" + sleep 1 + msg_ok "Done" + exit + } + + msg_info "Downloading Intel processor microcode package $microcode" + curl -fsSL "http://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/$microcode" -o $(basename "http://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/$microcode") + msg_ok "Downloaded Intel processor microcode package $microcode" + + msg_info "Installing $microcode (this might take a while)" + dpkg -i $microcode &>/dev/null + msg_ok "Installed $microcode" + + msg_info "Cleaning up" + rm $microcode + msg_ok "Clean up complete" + echo -e "\nA system reboot is required to apply the changes.\n" +} + +amd() { + amd_microcode=$(curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/" | grep -o 'href="[^"]*amd64.deb"' | sed 's/href="//;s/"//') + + [ -z "$amd_microcode" ] && { + whiptail --backtitle "Proxmox Backup Server Helper Scripts" --title "No Microcode Found" --msgbox "No microcode packages were found.\nTry again later." 10 68 + msg_info "Exiting" + sleep 1 + msg_ok "Done" + exit + } + + MICROCODE_MENU=() + MSG_MAX_LENGTH=0 + + while read -r TAG ITEM; do + OFFSET=2 + ((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + OFFSET)) + MICROCODE_MENU+=("$TAG" "$ITEM " "OFF") + done < <(echo "$amd_microcode") + + microcode=$(whiptail --backtitle "Proxmox Backup Server Helper Scripts" \ + --title "Current Microcode Revision: ${current_microcode}" \ + --radiolist "\nSelect a microcode package to install:\n" \ + 16 $((MSG_MAX_LENGTH + 58)) 6 "${MICROCODE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"') + + [ -z "$microcode" ] && { + whiptail --backtitle "Proxmox Backup Server Helper Scripts" --title "No Microcode Selected" --msgbox "No microcode package was selected." 10 68 + msg_info "Exiting" + sleep 1 + msg_ok "Done" + exit + } + + msg_info "Downloading AMD processor microcode package $microcode" + curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode" -o $(basename "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode") + msg_ok "Downloaded AMD processor microcode package $microcode" + + msg_info "Installing $microcode (this might take a while)" + dpkg -i $microcode &>/dev/null + msg_ok "Installed $microcode" + + msg_info "Cleaning up" + rm $microcode + msg_ok "Clean up complete" + echo -e "\nA system reboot is required to apply the changes.\n" +} + +# Check if this is a Proxmox Backup Server by verifying the presence of the datastore config. +if [ ! -f /etc/proxmox-backup/user.cfg ]; then + header_info + msg_error "Proxmox Backup Server not detected!" + exit +fi + +whiptail --backtitle "Proxmox Backup Server Helper Scripts" \ + --title "Proxmox Backup Server Processor Microcode" \ + --yesno "This script searches for CPU microcode packages and offers the option to install them.\nProceed?" 10 68 + +msg_info "Checking CPU vendor" +cpu=$(lscpu | grep -oP 'Vendor ID:\s*\K\S+' | head -n 1) +if [ "$cpu" == "GenuineIntel" ]; then + msg_ok "${cpu} detected" + sleep 1 + intel +elif [ "$cpu" == "AuthenticAMD" ]; then + msg_ok "${cpu} detected" + sleep 1 + amd +else + msg_error "CPU vendor ${cpu} is not supported" + exit +fi diff --git a/Proxmox/pbs/conf.sh b/Proxmox/pbs/conf.sh new file mode 100644 index 0000000..297f957 --- /dev/null +++ b/Proxmox/pbs/conf.sh @@ -0,0 +1,341 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/post-pbs-install.sh)" + + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: tteck (tteckster) | MickLesk (CanbiZ) | thost96 +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +header_info() { + clear + cat <<"EOF" + ____ ____ _____ ____ __ ____ __ ____ + / __ \/ __ ) ___/ / __ \____ _____/ /_ / _/___ _____/ /_____ _/ / / + / /_/ / __ \__ \ / /_/ / __ \/ ___/ __/ / // __ \/ ___/ __/ __ `/ / / + / ____/ /_/ /__/ / / ____/ /_/ (__ ) /_ _/ // / / (__ ) /_/ /_/ / / / +/_/ /_____/____/ /_/ \____/____/\__/ /___/_/ /_/____/\__/\__,_/_/_/ + +EOF +} + +RD=$(echo "\033[01;31m") +YW=$(echo "\033[33m") +GN=$(echo "\033[1;92m") +CL=$(echo "\033[m") +BFR="\\r\\033[K" +HOLD="-" +CM="${GN}✓${CL}" +CROSS="${RD}✗${CL}" + +set -euo pipefail +shopt -s inherit_errexit nullglob + +msg_info() { echo -ne " ${HOLD} ${YW}$1..."; } +msg_ok() { echo -e "${BFR} ${CM} ${GN}$1${CL}"; } +msg_error() { echo -e "${BFR} ${CROSS} ${RD}$1${CL}"; } + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "post-pbs-install" "pve" + +# ---- helpers ---- +get_pbs_codename() { + awk -F'=' '/^VERSION_CODENAME=/{print $2}' /etc/os-release +} + +repo_state_list() { + local repo="$1" + local file="" + local state="missing" + for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do + [[ -f "$f" ]] || continue + if grep -q "$repo" "$f"; then + file="$f" + if grep -qE "^[^#].*${repo}" "$f"; then + state="active" + elif grep -qE "^#.*${repo}" "$f"; then + state="disabled" + fi + break + fi + done + echo "$state $file" +} + +component_exists_in_sources() { + local component="$1" + grep -h -E "^[^#]*Components:[^#]*\b${component}\b" /etc/apt/sources.list.d/*.sources 2>/dev/null | grep -q . +} + +require_whiptail() { + if ! command -v whiptail >/dev/null 2>&1; then + msg_error "Missing dependency: whiptail" + echo -e "Install it first (e.g. apt update && apt install -y whiptail), then re-run this script." + exit 127 + fi +} + +# ---- main ---- +main() { + header_info + echo -e "\nThis script will Perform Post Install Routines.\n" + while true; do + read -rp "Start the Proxmox Backup Server Post Install Script (y/n)? " yn + case $yn in + [Yy]*) break ;; + [Nn]*) + clear + exit + ;; + *) echo "Please answer yes or no." ;; + esac + done + + if command -v pveversion >/dev/null 2>&1; then + echo -e "\n🛑 PVE Detected, Wrong Script!\n" + exit 232 + fi + + local CODENAME + CODENAME="$(get_pbs_codename)" + + case "$CODENAME" in + bookworm) + require_whiptail + start_routines_3 + ;; + trixie) + require_whiptail + start_routines_4 + ;; + *) + msg_error "Unsupported Debian codename: $CODENAME" + echo -e "Supported: bookworm (PBS 3.x) and trixie (PBS 4.x)" + exit 105 + ;; + esac +} + +# ---- PBS 3.x (Bookworm) ---- +start_routines_3() { + header_info + local VERSION="bookworm" + + # --- Debian sources --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS SOURCES" --menu \ + "Correct Debian sources for Proxmox Backup Server 3.x?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Correcting Debian Sources" + cat <<EOF >/etc/apt/sources.list +deb http://deb.debian.org/debian ${VERSION} main contrib +deb http://deb.debian.org/debian ${VERSION}-updates main contrib +deb http://security.debian.org/debian-security ${VERSION}-security main contrib +EOF + msg_ok "Corrected Debian Sources" + ;; + no) msg_error "Selected no to Correcting Debian Sources" ;; + esac + + # --- Enterprise repo --- + read -r state file <<<"$(repo_state_list pbs-enterprise)" + case $state in + active) + sed -i "s/^[^#].*pbs-enterprise/# &/" "$file" + msg_ok "Disabled 'pbs-enterprise' repository" + ;; + disabled) msg_ok "'pbs-enterprise' already disabled" ;; + missing) + cat >/etc/apt/sources.list.d/pbs-enterprise.list <<EOF +# deb https://enterprise.proxmox.com/debian/pbs ${VERSION} pbs-enterprise +EOF + msg_ok "Added 'pbs-enterprise' repository (disabled)" + ;; + esac + + # --- No-subscription repo --- + read -r state file <<<"$(repo_state_list pbs-no-subscription)" + if [[ "$state" == "missing" ]]; then + cat >/etc/apt/sources.list.d/pbs-install-repo.list <<EOF +deb http://download.proxmox.com/debian/pbs ${VERSION} pbs-no-subscription +EOF + msg_ok "Enabled 'pbs-no-subscription' repository" + else + msg_ok "'pbs-no-subscription' repository already present" + fi + + # --- Test repo (legacy name pbstest) --- + read -r state file <<<"$(repo_state_list pbstest)" + if [[ "$state" == "missing" ]]; then + cat >/etc/apt/sources.list.d/pbstest-for-beta.list <<EOF +# deb http://download.proxmox.com/debian/pbs ${VERSION} pbstest +EOF + msg_ok "Added 'pbstest' repository (disabled)" + else + msg_ok "'pbstest' repository already exists" + fi + + post_routines_common +} + +# ---- PBS 4.x (Trixie, deb822) ---- +start_routines_4() { + header_info + local VERSION="trixie" + + # --- Debian sources (deb822) --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS SOURCES" --menu \ + "Correct Debian sources for Proxmox Backup Server 4.x (deb822)?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Correcting Debian Sources (deb822)" + rm -f /etc/apt/sources.list.d/*.list + if [ -f /etc/apt/sources.list ]; then + sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list + fi + cat >/etc/apt/sources.list.d/debian.sources <<EOF +Types: deb +URIs: http://deb.debian.org/debian/ +Suites: trixie trixie-updates +Components: main contrib non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb +URIs: http://security.debian.org/debian-security/ +Suites: trixie-security +Components: main contrib non-free-firmware +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg +EOF + msg_ok "Corrected Debian Sources" + ;; + no) msg_error "Selected no to Correcting Debian Sources" ;; + esac + + # --- Enterprise repo --- + if component_exists_in_sources "pbs-enterprise"; then + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS Enterprise Repository" --menu \ + "Enterprise repository detected. + +You normally need a valid subscription for this. +Disable it (recommended)?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Disabling 'pbs-enterprise' repository" + # Use Enabled: false instead of commenting to avoid malformed entry + if grep -q "^Enabled:" /etc/apt/sources.list.d/pbs-enterprise.sources 2>/dev/null; then + sed -i 's/^Enabled:.*/Enabled: false/' /etc/apt/sources.list.d/pbs-enterprise.sources + else + echo "Enabled: false" >>/etc/apt/sources.list.d/pbs-enterprise.sources + fi + msg_ok "Disabled 'pbs-enterprise' repository" + ;; + no) + msg_error "Keeping 'pbs-enterprise' active (subscription required!)" + ;; + esac + else + cat >/etc/apt/sources.list.d/pbs-enterprise.sources <<EOF +Types: deb +URIs: https://enterprise.proxmox.com/debian/pbs +Suites: trixie +Components: pbs-enterprise +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +Enabled: false +EOF + msg_ok "Added 'pbs-enterprise' repository (disabled)" + fi + + # --- No-subscription repo --- + if ! component_exists_in_sources "pbs-no-subscription"; then + cat >/etc/apt/sources.list.d/proxmox.sources <<EOF +Types: deb +URIs: http://download.proxmox.com/debian/pbs +Suites: trixie +Components: pbs-no-subscription +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +EOF + msg_ok "Added 'pbs-no-subscription' repository" + else + msg_ok "'pbs-no-subscription' repository already present" + fi + + # --- Test repo (pbs-test, renamed) --- + if ! component_exists_in_sources "pbs-test"; then + cat >/etc/apt/sources.list.d/pbs-test.sources <<EOF +Types: deb +URIs: http://download.proxmox.com/debian/pbs +Suites: trixie +Components: pbs-test +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +Enabled: false +EOF + msg_ok "Added 'pbs-test' repository (disabled)" + else + msg_ok "'pbs-test' repository already present" + fi + + post_routines_common +} + +# ---- Shared routines ---- +post_routines_common() { + # Subscription nag + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SUBSCRIPTION NAG" --menu \ + "Disable subscription nag in PBS UI?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + whiptail --backtitle "Proxmox VE Helper Scripts" --msgbox \ + "Supporting the software's development team is essential.\nPlease consider buying a subscription." 10 58 + msg_info "Disabling subscription nag" + echo "DPkg::Post-Invoke { \"if [ -s /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js ] && ! grep -q -F 'NoMoreNagging' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; then sed -i '/data\\.status/{s/\\!//;s/active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; fi\" };" >/etc/apt/apt.conf.d/no-nag-script + msg_ok "Disabled subscription nag (clear browser cache!)" + ;; + no) + msg_error "Selected no to Disabling subscription nag" + rm -f /etc/apt/apt.conf.d/no-nag-script 2>/dev/null + ;; + esac + apt --reinstall install proxmox-widget-toolkit &>/dev/null || msg_error "Widget toolkit reinstall failed" + + # Update + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "UPDATE" --menu \ + "Update Proxmox Backup Server now?" 11 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Updating Proxmox Backup Server (Patience)" + apt update &>/dev/null || msg_error "apt update failed" + apt -y dist-upgrade &>/dev/null || msg_error "apt dist-upgrade failed" + msg_ok "Updated Proxmox Backup Server" + ;; + no) msg_error "Selected no to updating Proxmox Backup Server" ;; + esac + + # Reminder + whiptail --backtitle "Proxmox VE Helper Scripts" --title "Post-Install Reminder" --msgbox \ + "IMPORTANT: + +Please run this script on every PBS node individually if you have multiple nodes. + +After completing these steps, it is strongly recommended to REBOOT your node. + +After the upgrade or post-install routines, always clear your browser cache or perform a hard reload (Ctrl+Shift+R) before using the PBS Web UI to avoid UI display issues." 20 80 + + # Reboot + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "REBOOT" --menu \ + "Reboot Proxmox Backup Server now? (recommended)" 11 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Rebooting PBS" + sleep 2 + msg_ok "Completed Post Install Routines" + reboot + ;; + no) + msg_error "Selected no to Reboot (Reboot recommended)" + msg_ok "Completed Post Install Routines" + ;; + esac +} + +main diff --git a/Proxmox/pbs/pbs_install._lxc.sh b/Proxmox/pbs/pbs_install._lxc.sh new file mode 100644 index 0000000..2f2b24c --- /dev/null +++ b/Proxmox/pbs/pbs_install._lxc.sh @@ -0,0 +1,49 @@ +#pve +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/proxmox-backup-server.sh)" + +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2026 tteck +# Author: tteck (tteckster) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://www.proxmox.com/en/proxmox-backup-server + +APP="Proxmox-Backup-Server" +var_tags="${var_tags:-backup}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-10}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_arm64="${var_arm64:-no}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -e /usr/sbin/proxmox-backup-manager ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + msg_info "Updating $APP LXC" + $STD apt update + $STD apt -y upgrade + msg_ok "Updated $APP LXC" + msg_ok "Updated successfully!" + exit +} + +start +build_container +description + +msg_ok "Completed successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:8007${CL}" diff --git a/Proxmox/pbs/pbs_update4.sh b/Proxmox/pbs/pbs_update4.sh new file mode 100644 index 0000000..cc1b635 --- /dev/null +++ b/Proxmox/pbs/pbs_update4.sh @@ -0,0 +1,191 @@ +bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/pve/pbs4-upgrade.sh)" + +#!/usr/bin/env bash + +# Copyright (c) 2021-2026 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE + +header_info() { + clear + cat <<"EOF" + ____ ____ _____ __ __ __ __ __ + / __ \/ __ ) ___// // / / / / /___ ____ __________ _____/ /__ + / /_/ / __ \__ \/ // /_ / / / / __ \/ __ `/ ___/ __ `/ __ / _ \ + / ____/ /_/ /__/ /__ __/ / /_/ / /_/ / /_/ / / / /_/ / /_/ / __/ +/_/ /_____/____/ /_/ \____/ .___/\__, /_/ \__,_/\__,_/\___/ + /_/ /____/ +EOF +} + +RD=$(echo "\033[01;31m") +YW=$(echo "\033[33m") +GN=$(echo "\033[1;92m") +CL=$(echo "\033[m") +BFR="\\r\\033[K" +HOLD="-" +CM="${GN}✓${CL}" +CROSS="${RD}✗${CL}" + +set -euo pipefail +shopt -s inherit_errexit nullglob + +msg_info() { echo -ne " ${HOLD} ${YW}$1..."; } +msg_ok() { echo -e "${BFR} ${CM} ${GN}$1${CL}"; } +msg_error() { echo -e "${BFR} ${CROSS} ${RD}$1${CL}"; } + +# Telemetry +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true +declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "pbs4-upgrade" "pve" + +start_routines() { + header_info + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS 3 BACKUP" --menu \ + "\nMake a backup of /etc/proxmox-backup to ensure recovery in worst case?" 14 58 2 \ + "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Backing up Proxmox Backup Server 3" + tar czf "pbs3-etc-backup-$(date -I).tar.gz" -C "/etc" "proxmox-backup" + msg_ok "Backed up Proxmox Backup Server 3" + ;; + no) msg_error "Selected no to Backup" ;; + esac + + # --- Debian 13 Sources --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS 4 SOURCES" --menu \ + "Switch to Debian 13 (Trixie) sources for PBS 4?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Switching to Debian 13 (Trixie) Sources" + rm -f /etc/apt/sources.list.d/*.list + if [ -f /etc/apt/sources.list ]; then + sed -i '/proxmox/d;/bookworm/d' /etc/apt/sources.list + fi + cat >/etc/apt/sources.list.d/debian.sources <<EOF +Types: deb +URIs: http://deb.debian.org/debian +Suites: trixie +Components: main contrib +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb +URIs: http://security.debian.org/debian-security +Suites: trixie-security +Components: main contrib +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg + +Types: deb +URIs: http://deb.debian.org/debian +Suites: trixie-updates +Components: main contrib +Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg +EOF + msg_ok "Configured Debian 13 (Trixie) Sources" + ;; + no) msg_error "Selected no to Sources update" ;; + esac + + # --- Enterprise Repo --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS4-ENTERPRISE" --menu \ + "Add 'pbs-enterprise' repository (for subscription users)?" 14 58 2 "yes" " " "no" " " \ + 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Adding 'pbs-enterprise' repository" + cat >/etc/apt/sources.list.d/pbs-enterprise.sources <<EOF +Types: deb +URIs: https://enterprise.proxmox.com/debian/pbs +Suites: trixie +Components: pbs-enterprise +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +EOF + msg_ok "Added 'pbs-enterprise' repository" + ;; + no) msg_error "Skipped enterprise repo" ;; + esac + + # --- No-Subscription Repo --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS4-NO-SUBSCRIPTION" --menu \ + "Enable 'pbs-no-subscription' repository?" 14 58 2 "yes" " " "no" " " \ + 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Adding 'pbs-no-subscription' repository" + cat >/etc/apt/sources.list.d/proxmox.sources <<EOF +Types: deb +URIs: http://download.proxmox.com/debian/pbs +Suites: trixie +Components: pbs-no-subscription +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +EOF + msg_ok "Added 'pbs-no-subscription' repository" + ;; + no) msg_error "Skipped no-subscription repo" ;; + esac + + # --- Test Repo --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS4 TEST" --menu \ + "Add 'pbs-test' repository (disabled by default)?" 14 58 2 "yes" " " "no" " " \ + 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Adding 'pbs-test' repository (disabled)" + cat >/etc/apt/sources.list.d/pbs-test.sources <<EOF +# Types: deb +# URIs: http://download.proxmox.com/debian/pbs +# Suites: trixie +# Components: pbs-test +# Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg +EOF + msg_ok "Added 'pbs-test' repository" + ;; + no) msg_error "Skipped test repo" ;; + esac + + # --- Upgrade --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS 4 UPGRADE" --menu \ + "\nUpgrade to Proxmox Backup Server 4 now?" 11 58 2 "yes" " " "no" " " \ + 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Upgrading to Proxmox Backup Server 4 (Patience)" + apt update + DEBIAN_FRONTEND=noninteractive apt -o Dpkg::Options::="--force-confold" dist-upgrade -y + msg_ok "System upgraded to PBS 4" + ;; + no) msg_error "Selected no to upgrade" ;; + esac + + # --- Reboot --- + CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "REBOOT" --menu \ + "\nReboot Proxmox Backup Server 4 now? (recommended)" 11 58 2 "yes" " " "no" " " \ + 3>&2 2>&1 1>&3) + case $CHOICE in + yes) + msg_info "Rebooting PBS 4" + sleep 2 + msg_ok "Upgrade Complete" + reboot + ;; + no) + msg_error "Selected no to Reboot (Reboot recommended)" + msg_ok "Upgrade Complete" + ;; + esac +} + +header_info +while true; do + read -rp "Start the Upgrade to Proxmox Backup Server 4 Script (y/n)? " yn + case $yn in + [Yy]*) break ;; + [Nn]*) + clear + exit + ;; + *) echo "Please answer yes or no." ;; + esac +done + +start_routines