From f5affeda71e106951526bd8dddfca2e32770d947 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:46:17 -0400 Subject: [PATCH 01/41] Init app install + json + default ct update --- ct/guardian.sh | 77 ++++++++++++++++++++++ frontend/public/json/guardian.json | 33 ++++++++++ install/guardian-install.sh | 101 +++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 ct/guardian.sh create mode 100644 frontend/public/json/guardian.json create mode 100644 install/guardian-install.sh diff --git a/ct/guardian.sh b/ct/guardian.sh new file mode 100644 index 00000000..42aaa0bc --- /dev/null +++ b/ct/guardian.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: HydroshieldMKII +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/HydroshieldMKII/Guardian + +APP="Guardian" +var_tags="${var_tags:-media;monitoring}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-2048}" +var_disk="${var_disk:-4}" +var_os="${var_os:-debian}" +var_version="${var_version:-12}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + + # Check if installation is present | -f for file, -d for folder + if [[ ! -d "/opt/${APP}" ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + # Crawling the new version and checking whether an update is required + RELEASE=$(curl -fsSL [RELEASE_URL] | [PARSE_RELEASE_COMMAND]) + if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then + # Stopping Services + msg_info "Stopping $APP" + systemctl stop [SERVICE_NAME] + msg_ok "Stopped $APP" + + # Creating Backup + msg_info "Creating Backup" + tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" [IMPORTANT_PATHS] + msg_ok "Backup Created" + + # Execute Update + msg_info "Updating $APP to v${RELEASE}" + [UPDATE_COMMANDS] + msg_ok "Updated $APP to v${RELEASE}" + + # Starting Services + msg_info "Starting $APP" + systemctl start [SERVICE_NAME] + msg_ok "Started $APP" + + # Cleaning up + msg_info "Cleaning Up" + rm -rf [TEMP_FILES] + msg_ok "Cleanup Completed" + + # Last Action + echo "${RELEASE}" >/opt/${APP}_version.txt + msg_ok "Update Successful" + else + msg_ok "No update required. ${APP} 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}:[PORT]${CL}" diff --git a/frontend/public/json/guardian.json b/frontend/public/json/guardian.json new file mode 100644 index 00000000..4bc9f6dc --- /dev/null +++ b/frontend/public/json/guardian.json @@ -0,0 +1,33 @@ +{ + "name": "Guardian", + "slug": "guardian", + "categories": [13], + "date_created": "2025-09-22", + "type": "ct", + "updateable": true, + "privileged": false, + "interface_port": 3000, + "documentation": null, + "config_path": "", + "website": "https://github.com/HydroshieldMKII/Guardian", + "logo": null, + "description": "A tool to manage devices access to your Plex server with a nice Dashboard ", + "install_methods": [ + { + "type": "default", + "script": "ct/guardian.sh", + "resources": { + "cpu": 4, + "ram": 2048, + "hdd": 6, + "os": "Debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/install/guardian-install.sh b/install/guardian-install.sh new file mode 100644 index 00000000..77d73eed --- /dev/null +++ b/install/guardian-install.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: HydroshieldMKII +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://github.com/HydroshieldMKII/Guardian + +# Import Functions und Setup +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +# Installing Dependencies +msg_info "Installing Dependencies" +$STD apt-get install -y \ + git \ + nodejs \ + npm \ + sqlite +msg_ok "Installed Dependencies" + +# Setup App +msg_info "Setup ${APPLICATION}" +RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') +curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" +unzip -q "${RELEASE}.zip" +mv "${APPLICATION}-${RELEASE}/" "/opt/${APPLICATION}" +# +# +# +echo "${RELEASE}" >/opt/"${APPLICATION}"_version.txt +msg_ok "Setup ${APPLICATION}" + +# ===== Build Backend ===== +msg_info "Building backend" +cd /opt/${APPLICATION}/backend +npm ci +npm run build +msg_ok "Built backend" + +# ===== Build Frontend ===== +msg_info "Building frontend" +cd /opt/${APPLICATION}/frontend +npm ci +npm run build +msg_ok "Built frontend" + +# ===== Backend Service ===== +msg_info "Creating Backend Service" +cat </etc/systemd/system/guardian-backend.service +[Unit] +Description=Guardian Backend +After=network.target + +[Service] +WorkingDirectory=/opt/Guardian/backend +Environment=NODE_ENV=development +ExecStart=/usr/bin/node dist/main.js +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now guardian-backend +msg_ok "Created Backend Service" + +# ===== Frontend Service ===== +msg_info "Creating Frontend Service" +cat </etc/systemd/system/guardian-frontend.service +[Unit] +Description=Guardian Frontend +After=guardian-backend.service network.target +Wants=guardian-backend.service + +[Service] +WorkingDirectory=/opt/Guardian/frontend +Environment=NODE_ENV=development +ExecStart=/usr/bin/npm run start +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now guardian-frontend +msg_ok "Created Frontend Service" + +motd_ssh +customize + +# Cleanup +msg_info "Cleaning up" +rm -f "${RELEASE}".zip +$STD apt-get -y autoremove +$STD apt-get -y autoclean +msg_ok "Cleaned" From 552959a39bdc793b30a14746809f40f6f2f1164e Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:36:17 -0400 Subject: [PATCH 02/41] Update Guardian installation and update scripts to use new repository paths and improve dependency management --- ct/guardian.sh | 34 ++++++++++++++++++++++++---------- install/guardian-install.sh | 19 +++++++++---------- misc/build.func | 2 +- misc/install.func | 10 +++++----- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 42aaa0bc..09c774cc 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: HydroshieldMKII # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE @@ -31,35 +31,49 @@ function update_script() { fi # Crawling the new version and checking whether an update is required - RELEASE=$(curl -fsSL [RELEASE_URL] | [PARSE_RELEASE_COMMAND]) + RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then # Stopping Services msg_info "Stopping $APP" - systemctl stop [SERVICE_NAME] + systemctl stop guardian-backend guardian-frontend msg_ok "Stopped $APP" # Creating Backup msg_info "Creating Backup" - tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" [IMPORTANT_PATHS] + tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" /opt/Guardian msg_ok "Backup Created" # Execute Update msg_info "Updating $APP to v${RELEASE}" - [UPDATE_COMMANDS] + cd /tmp + curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" + unzip -q "${RELEASE}.zip" + rm -rf /opt/Guardian + mv "Guardian-${RELEASE}/" "/opt/Guardian" + + # Build Backend + cd /opt/Guardian/backend + npm ci + npm run build + + # Build Frontend + cd /opt/Guardian/frontend + npm ci + npm run build + + echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Updated $APP to v${RELEASE}" # Starting Services msg_info "Starting $APP" - systemctl start [SERVICE_NAME] + systemctl start guardian-backend guardian-frontend msg_ok "Started $APP" # Cleaning up msg_info "Cleaning Up" - rm -rf [TEMP_FILES] + rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${RELEASE}" msg_ok "Cleanup Completed" - # Last Action - echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Update Successful" else msg_ok "No update required. ${APP} is already at v${RELEASE}" @@ -74,4 +88,4 @@ 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}:[PORT]${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}" diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 77d73eed..69eeecf3 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -20,31 +20,30 @@ $STD apt-get install -y \ git \ nodejs \ npm \ - sqlite + sqlite3 \ + unzip \ + curl msg_ok "Installed Dependencies" # Setup App -msg_info "Setup ${APPLICATION}" +msg_info "Setup Guardian" RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" unzip -q "${RELEASE}.zip" -mv "${APPLICATION}-${RELEASE}/" "/opt/${APPLICATION}" -# -# -# -echo "${RELEASE}" >/opt/"${APPLICATION}"_version.txt -msg_ok "Setup ${APPLICATION}" +mv "Guardian-${RELEASE}/" "/opt/Guardian" +echo "${RELEASE}" >/opt/Guardian_version.txt +msg_ok "Setup Guardian" # ===== Build Backend ===== msg_info "Building backend" -cd /opt/${APPLICATION}/backend +cd /opt/Guardian/backend npm ci npm run build msg_ok "Built backend" # ===== Build Frontend ===== msg_info "Building frontend" -cd /opt/${APPLICATION}/frontend +cd /opt/Guardian/frontend npm ci npm run build msg_ok "Built frontend" diff --git a/misc/build.func b/misc/build.func index d6b0af72..92e9c719 100644 --- a/misc/build.func +++ b/misc/build.func @@ -79,7 +79,7 @@ variables() { # [Dd]*) # for file in core.func error_handler.func tools.func; do # local_path="$FUNC_DIR/$file" -# url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/$file" +# url="https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/$file" # remote_tmp="$(mktemp)" # curl -fsSL "$url" -o "$remote_tmp" || continue diff --git a/misc/install.func b/misc/install.func index f741b921..2ebc8e2e 100644 --- a/misc/install.func +++ b/misc/install.func @@ -9,8 +9,8 @@ if ! command -v curl >/dev/null 2>&1; then apt-get update >/dev/null 2>&1 apt-get install -y curl >/dev/null 2>&1 fi -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors @@ -32,7 +32,7 @@ verb_ip6() { # # This function handles errors # error_handler() { -# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func) +# source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) # local exit_code="$1" # local line_number="$2" # local command="${3:-}" @@ -147,7 +147,7 @@ EOF $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings @@ -195,7 +195,7 @@ EOF systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update + echo "bash -c \"\$(curl -fsSL https://github.com/HydroshieldMKII/ProxmoxVED/raw/add-guardian-app/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh From e3718d5c63bcb7a320d1243c59611bcc1a5559f9 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:36:26 -0400 Subject: [PATCH 03/41] Refactor update_script function to streamline update process and ensure version tracking --- ct/guardian.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 09c774cc..b3c4634b 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -50,17 +50,17 @@ function update_script() { unzip -q "${RELEASE}.zip" rm -rf /opt/Guardian mv "Guardian-${RELEASE}/" "/opt/Guardian" - + # Build Backend cd /opt/Guardian/backend npm ci npm run build - + # Build Frontend cd /opt/Guardian/frontend npm ci npm run build - + echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Updated $APP to v${RELEASE}" From bf38566ff8f96e80d17f8e952f4802e527b7b8a4 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:47:15 -0400 Subject: [PATCH 04/41] Update script sources to use new repository paths for improved functionality --- misc/build.func | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/misc/build.func b/misc/build.func index 92e9c719..7169b354 100644 --- a/misc/build.func +++ b/misc/build.func @@ -128,17 +128,17 @@ variables() { # - Initialize error traps after loading # ------------------------------------------------------------------------------ -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) if command -v curl >/dev/null 2>&1; then - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via curl" elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) + source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via wget" @@ -2062,7 +2062,7 @@ ssh_discover_default_files() { # - Otherwise: shows update/setting menu # ------------------------------------------------------------------------------ start() { - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) if command -v pveversion >/dev/null 2>&1; then install_script || return 0 return 0 @@ -2141,9 +2141,9 @@ build_container() { TEMP_DIR=$(mktemp -d) pushd "$TEMP_DIR" >/dev/null if [ "$var_os" == "alpine" ]; then - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/alpine-install.func)" + export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/alpine-install.func)" else - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)" + export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/install.func)" fi export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" @@ -2183,7 +2183,7 @@ build_container() { LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/passthrough.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/passthrough.func) select_hw_passthrough "$CTID" "$CT_TYPE" "$APP" # TUN device passthrough @@ -2295,7 +2295,7 @@ EOF' fi msg_ok "Customized LXC Container" install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/install/${var_install}.sh)"; then exit $? fi } From c520602fcbe7946dc5eb65ae9d88969daf5fa064 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:51:54 -0400 Subject: [PATCH 05/41] Strip 'v' prefix from RELEASE in Guardian installation scripts for consistent folder naming --- ct/guardian.sh | 4 +++- install/guardian-install.sh | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index b3c4634b..140a472a 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -49,7 +49,9 @@ function update_script() { curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" unzip -q "${RELEASE}.zip" rm -rf /opt/Guardian - mv "Guardian-${RELEASE}/" "/opt/Guardian" + # Strip 'v' prefix from RELEASE for folder name + FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') + mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" # Build Backend cd /opt/Guardian/backend diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 69eeecf3..51b67744 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -30,7 +30,9 @@ msg_info "Setup Guardian" RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" unzip -q "${RELEASE}.zip" -mv "Guardian-${RELEASE}/" "/opt/Guardian" +# Strip 'v' prefix from RELEASE for folder name +FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') +mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" echo "${RELEASE}" >/opt/Guardian_version.txt msg_ok "Setup Guardian" From f48609011b1937def38128e18a654bfb4f04e51d Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:11:18 -0400 Subject: [PATCH 06/41] Refactor frontend setup in guardian-install.sh to streamline installation process and update service configuration --- install/guardian-install.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 51b67744..c13645ee 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -43,12 +43,11 @@ npm ci npm run build msg_ok "Built backend" -# ===== Build Frontend ===== -msg_info "Building frontend" +# ===== Install Frontend Dependencies ===== +msg_info "Installing frontend dependencies" cd /opt/Guardian/frontend npm ci -npm run build -msg_ok "Built frontend" +msg_ok "Installed frontend dependencies" # ===== Backend Service ===== msg_info "Creating Backend Service" @@ -81,7 +80,8 @@ Wants=guardian-backend.service [Service] WorkingDirectory=/opt/Guardian/frontend Environment=NODE_ENV=development -ExecStart=/usr/bin/npm run start +Environment=PORT=3000 +ExecStart=/usr/bin/npm run dev Restart=always RestartSec=3 From c9f81a23c5e038b099e0aeab52bb0c1460171b9f Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:29:39 -0400 Subject: [PATCH 07/41] Enhance database backup and restore functionality during Guardian updates --- ct/guardian.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 140a472a..0028bf3e 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -9,7 +9,7 @@ APP="Guardian" var_tags="${var_tags:-media;monitoring}" var_cpu="${var_cpu:-2}" var_ram="${var_ram:-2048}" -var_disk="${var_disk:-4}" +var_disk="${var_disk:-6}" var_os="${var_os:-debian}" var_version="${var_version:-12}" var_unprivileged="${var_unprivileged:-1}" @@ -43,6 +43,13 @@ function update_script() { tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" /opt/Guardian msg_ok "Backup Created" + # Preserve Database + msg_info "Preserving Database" + if [[ -f "/opt/Guardian/backend/plex-guard.db" ]]; then + cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" + msg_ok "Database backed up" + fi + # Execute Update msg_info "Updating $APP to v${RELEASE}" cd /tmp @@ -53,6 +60,14 @@ function update_script() { FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" + # Restore Database + if [[ -f "/tmp/plex-guard.db.backup" ]]; then + msg_info "Restoring Database" + cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" + rm "/tmp/plex-guard.db.backup" + msg_ok "Database restored" + fi + # Build Backend cd /opt/Guardian/backend npm ci @@ -73,7 +88,7 @@ function update_script() { # Cleaning up msg_info "Cleaning Up" - rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${RELEASE}" + rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${FOLDER_NAME}" /tmp/plex-guard.db.backup msg_ok "Cleanup Completed" msg_ok "Update Successful" From 7f876d101d30e77d26480636e06a4daaa86fa4c3 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:42:50 -0400 Subject: [PATCH 08/41] Refactor update_script and installation process for Guardian to enhance backup handling, streamline frontend build, and improve service configurations --- ct/guardian.sh | 22 ++++++---------------- install/guardian-install.sh | 20 ++++++++------------ 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 0028bf3e..2a9a8c15 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -24,43 +24,37 @@ function update_script() { check_container_storage check_container_resources - # Check if installation is present | -f for file, -d for folder if [[ ! -d "/opt/${APP}" ]]; then msg_error "No ${APP} Installation Found!" exit fi - # Crawling the new version and checking whether an update is required + RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then - # Stopping Services + msg_info "Stopping $APP" systemctl stop guardian-backend guardian-frontend msg_ok "Stopped $APP" - # Creating Backup - msg_info "Creating Backup" - tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" /opt/Guardian - msg_ok "Backup Created" - # Preserve Database msg_info "Preserving Database" if [[ -f "/opt/Guardian/backend/plex-guard.db" ]]; then cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" msg_ok "Database backed up" fi - # Execute Update + msg_info "Updating $APP to v${RELEASE}" cd /tmp + curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" unzip -q "${RELEASE}.zip" rm -rf /opt/Guardian - # Strip 'v' prefix from RELEASE for folder name + FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" - # Restore Database if [[ -f "/tmp/plex-guard.db.backup" ]]; then msg_info "Restoring Database" cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" @@ -68,25 +62,21 @@ function update_script() { msg_ok "Database restored" fi - # Build Backend cd /opt/Guardian/backend npm ci npm run build - # Build Frontend cd /opt/Guardian/frontend npm ci - npm run build + NODE_ENV=development npm run build echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Updated $APP to v${RELEASE}" - # Starting Services msg_info "Starting $APP" systemctl start guardian-backend guardian-frontend msg_ok "Started $APP" - # Cleaning up msg_info "Cleaning Up" rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${FOLDER_NAME}" /tmp/plex-guard.db.backup msg_ok "Cleanup Completed" diff --git a/install/guardian-install.sh b/install/guardian-install.sh index c13645ee..06d547b0 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -5,7 +5,6 @@ # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # Source: https://github.com/HydroshieldMKII/Guardian -# Import Functions und Setup source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" color verb_ip6 @@ -14,7 +13,6 @@ setting_up_container network_check update_os -# Installing Dependencies msg_info "Installing Dependencies" $STD apt-get install -y \ git \ @@ -25,31 +23,30 @@ $STD apt-get install -y \ curl msg_ok "Installed Dependencies" -# Setup App msg_info "Setup Guardian" RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" unzip -q "${RELEASE}.zip" -# Strip 'v' prefix from RELEASE for folder name + FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" + echo "${RELEASE}" >/opt/Guardian_version.txt msg_ok "Setup Guardian" -# ===== Build Backend ===== + msg_info "Building backend" cd /opt/Guardian/backend npm ci npm run build msg_ok "Built backend" -# ===== Install Frontend Dependencies ===== -msg_info "Installing frontend dependencies" +msg_info "Building frontend" cd /opt/Guardian/frontend npm ci -msg_ok "Installed frontend dependencies" +NODE_ENV=development npm run build +msg_ok "Built frontend" -# ===== Backend Service ===== msg_info "Creating Backend Service" cat </etc/systemd/system/guardian-backend.service [Unit] @@ -69,7 +66,6 @@ EOF systemctl enable -q --now guardian-backend msg_ok "Created Backend Service" -# ===== Frontend Service ===== msg_info "Creating Frontend Service" cat </etc/systemd/system/guardian-frontend.service [Unit] @@ -79,9 +75,9 @@ Wants=guardian-backend.service [Service] WorkingDirectory=/opt/Guardian/frontend -Environment=NODE_ENV=development +Environment=NODE_ENV=production Environment=PORT=3000 -ExecStart=/usr/bin/npm run dev +ExecStart=/usr/bin/npm run start Restart=always RestartSec=3 From 8076c313dfaf46c863480ba54141f5e19c168d70 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:43:27 -0400 Subject: [PATCH 09/41] Remove redundant cleanup comment from guardian-install.sh --- install/guardian-install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 06d547b0..0edaa9f8 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -90,7 +90,6 @@ msg_ok "Created Frontend Service" motd_ssh customize -# Cleanup msg_info "Cleaning up" rm -f "${RELEASE}".zip $STD apt-get -y autoremove From 467a81b1e8ed325b215daca77d1c3b25942d6455 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:56:23 -0400 Subject: [PATCH 10/41] Refactor Guardian installation and update process to utilize Docker Compose, streamline dependency installation, and enhance service management --- ct/guardian.sh | 45 ++++------------ install/guardian-install.sh | 101 ++++++++++++++++-------------------- 2 files changed, 55 insertions(+), 91 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 2a9a8c15..fda14f81 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -34,56 +34,33 @@ function update_script() { if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then msg_info "Stopping $APP" - systemctl stop guardian-backend guardian-frontend + cd /opt/Guardian + docker compose down msg_ok "Stopped $APP" - - msg_info "Preserving Database" - if [[ -f "/opt/Guardian/backend/plex-guard.db" ]]; then - cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" - msg_ok "Database backed up" - fi - - msg_info "Updating $APP to v${RELEASE}" - cd /tmp + # Download new docker-compose.yml + curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.yml" - curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" - unzip -q "${RELEASE}.zip" - rm -rf /opt/Guardian - - FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') - mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" - - if [[ -f "/tmp/plex-guard.db.backup" ]]; then - msg_info "Restoring Database" - cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" - rm "/tmp/plex-guard.db.backup" - msg_ok "Database restored" - fi - - cd /opt/Guardian/backend - npm ci - npm run build - - cd /opt/Guardian/frontend - npm ci - NODE_ENV=development npm run build + # Pull new Docker images + docker compose pull echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Updated $APP to v${RELEASE}" msg_info "Starting $APP" - systemctl start guardian-backend guardian-frontend + cd /opt/Guardian + docker compose up -d msg_ok "Started $APP" msg_info "Cleaning Up" - rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${FOLDER_NAME}" /tmp/plex-guard.db.backup + rm -f /tmp/plex-guard.db.backup + docker system prune -f msg_ok "Cleanup Completed" msg_ok "Update Successful" else - msg_ok "No update required. ${APP} is already at v${RELEASE}" + msg_ok "No update required. ${APP} is already at ${RELEASE}" fi exit } diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 0edaa9f8..620f907e 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -15,83 +15,70 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ - git \ - nodejs \ - npm \ - sqlite3 \ - unzip \ - curl + curl \ + ca-certificates \ + gnupg \ + lsb-release msg_ok "Installed Dependencies" -msg_info "Setup Guardian" -RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') -curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" -unzip -q "${RELEASE}.zip" +# Install Docker +msg_info "Installing Docker" +curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +$STD apt-get update +$STD apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +systemctl enable --now docker +msg_ok "Installed Docker" -FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') -mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" +# Setup Guardian +msg_info "Setting up Guardian" +mkdir -p /opt/Guardian +cd /opt/Guardian + +# Download docker-compose.yml from repository +RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') +curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.yml" + +# Create data directory for persistent storage +mkdir -p data echo "${RELEASE}" >/opt/Guardian_version.txt msg_ok "Setup Guardian" +# Start Guardian with Docker Compose +msg_info "Starting Guardian" +cd /opt/Guardian +docker compose up -d +msg_ok "Started Guardian" -msg_info "Building backend" -cd /opt/Guardian/backend -npm ci -npm run build -msg_ok "Built backend" - -msg_info "Building frontend" -cd /opt/Guardian/frontend -npm ci -NODE_ENV=development npm run build -msg_ok "Built frontend" - -msg_info "Creating Backend Service" -cat </etc/systemd/system/guardian-backend.service +# Create systemd service to manage Docker Compose +msg_info "Creating Guardian Service" +cat </etc/systemd/system/guardian.service [Unit] -Description=Guardian Backend -After=network.target +Description=Guardian Docker Compose +Requires=docker.service +After=docker.service +Wants=network-online.target +After=network-online.target [Service] -WorkingDirectory=/opt/Guardian/backend -Environment=NODE_ENV=development -ExecStart=/usr/bin/node dist/main.js -Restart=always -RestartSec=3 +Type=oneshot +RemainAfterExit=true +WorkingDirectory=/opt/Guardian +ExecStart=/usr/bin/docker compose up -d +ExecStop=/usr/bin/docker compose down +TimeoutStartSec=0 [Install] WantedBy=multi-user.target EOF -systemctl enable -q --now guardian-backend -msg_ok "Created Backend Service" - -msg_info "Creating Frontend Service" -cat </etc/systemd/system/guardian-frontend.service -[Unit] -Description=Guardian Frontend -After=guardian-backend.service network.target -Wants=guardian-backend.service - -[Service] -WorkingDirectory=/opt/Guardian/frontend -Environment=NODE_ENV=production -Environment=PORT=3000 -ExecStart=/usr/bin/npm run start -Restart=always -RestartSec=3 - -[Install] -WantedBy=multi-user.target -EOF -systemctl enable -q --now guardian-frontend -msg_ok "Created Frontend Service" +systemctl enable guardian +msg_ok "Created Guardian Service" motd_ssh customize msg_info "Cleaning up" -rm -f "${RELEASE}".zip $STD apt-get -y autoremove $STD apt-get -y autoclean msg_ok "Cleaned" From 544150a57a53d9401bf5fd4c5a40b66c8590c903 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:59:05 -0400 Subject: [PATCH 11/41] Update docker-compose.yml download link to use the example file for Guardian setup --- install/guardian-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 620f907e..6786666a 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -37,7 +37,7 @@ cd /opt/Guardian # Download docker-compose.yml from repository RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') -curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.yml" +curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.example.yml" # Create data directory for persistent storage mkdir -p data From e48f2c35a8bc971db5a0eca9dff6b36f1d15b63e Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:10:18 -0400 Subject: [PATCH 12/41] Update Guardian installation and update scripts to use example docker-compose.yml and remove unnecessary comments --- ct/guardian.sh | 5 +---- install/guardian-install.sh | 8 -------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index fda14f81..d1da2113 100644 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -39,10 +39,8 @@ function update_script() { msg_ok "Stopped $APP" msg_info "Updating $APP to v${RELEASE}" - # Download new docker-compose.yml - curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.yml" + curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.example.yml" - # Pull new Docker images docker compose pull echo "${RELEASE}" >/opt/${APP}_version.txt @@ -54,7 +52,6 @@ function update_script() { msg_ok "Started $APP" msg_info "Cleaning Up" - rm -f /tmp/plex-guard.db.backup docker system prune -f msg_ok "Cleanup Completed" diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 6786666a..4640ed06 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -21,7 +21,6 @@ $STD apt-get install -y \ lsb-release msg_ok "Installed Dependencies" -# Install Docker msg_info "Installing Docker" curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null @@ -30,28 +29,21 @@ $STD apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plu systemctl enable --now docker msg_ok "Installed Docker" -# Setup Guardian msg_info "Setting up Guardian" mkdir -p /opt/Guardian cd /opt/Guardian -# Download docker-compose.yml from repository RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.example.yml" -# Create data directory for persistent storage -mkdir -p data - echo "${RELEASE}" >/opt/Guardian_version.txt msg_ok "Setup Guardian" -# Start Guardian with Docker Compose msg_info "Starting Guardian" cd /opt/Guardian docker compose up -d msg_ok "Started Guardian" -# Create systemd service to manage Docker Compose msg_info "Creating Guardian Service" cat </etc/systemd/system/guardian.service [Unit] From a4420750bdb1dbd02503b83c739554e9d3842b71 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:20:09 -0400 Subject: [PATCH 13/41] Update URLs in build.func and install.func to use community-scripts repository --- misc/build.func | 22 +++++++++++----------- misc/install.func | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/misc/build.func b/misc/build.func index 7169b354..d6b0af72 100644 --- a/misc/build.func +++ b/misc/build.func @@ -79,7 +79,7 @@ variables() { # [Dd]*) # for file in core.func error_handler.func tools.func; do # local_path="$FUNC_DIR/$file" -# url="https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/$file" +# url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/$file" # remote_tmp="$(mktemp)" # curl -fsSL "$url" -o "$remote_tmp" || continue @@ -128,17 +128,17 @@ variables() { # - Initialize error traps after loading # ------------------------------------------------------------------------------ -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) if command -v curl >/dev/null 2>&1; then - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via curl" elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) - source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) + source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) + source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via wget" @@ -2062,7 +2062,7 @@ ssh_discover_default_files() { # - Otherwise: shows update/setting menu # ------------------------------------------------------------------------------ start() { - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) if command -v pveversion >/dev/null 2>&1; then install_script || return 0 return 0 @@ -2141,9 +2141,9 @@ build_container() { TEMP_DIR=$(mktemp -d) pushd "$TEMP_DIR" >/dev/null if [ "$var_os" == "alpine" ]; then - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/alpine-install.func)" + export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/alpine-install.func)" else - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/install.func)" + export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)" fi export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" @@ -2183,7 +2183,7 @@ build_container() { LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/passthrough.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/passthrough.func) select_hw_passthrough "$CTID" "$CT_TYPE" "$APP" # TUN device passthrough @@ -2295,7 +2295,7 @@ EOF' fi msg_ok "Customized LXC Container" install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/install/${var_install}.sh)"; then + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then exit $? fi } diff --git a/misc/install.func b/misc/install.func index 2ebc8e2e..f741b921 100644 --- a/misc/install.func +++ b/misc/install.func @@ -9,8 +9,8 @@ if ! command -v curl >/dev/null 2>&1; then apt-get update >/dev/null 2>&1 apt-get install -y curl >/dev/null 2>&1 fi -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) load_functions catch_errors @@ -32,7 +32,7 @@ verb_ip6() { # # This function handles errors # error_handler() { -# source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) +# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func) # local exit_code="$1" # local line_number="$2" # local command="${3:-}" @@ -147,7 +147,7 @@ EOF $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings @@ -195,7 +195,7 @@ EOF systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://github.com/HydroshieldMKII/ProxmoxVED/raw/add-guardian-app/ct/${app}.sh)\"" >/usr/bin/update + echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh From 385931094c2a5d923b06113420cb5d3555788f3e Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:38:39 -0400 Subject: [PATCH 14/41] Refactor Guardian installation script to remove Docker installation, streamline dependency setup, and enhance service management for backend and frontend --- install/guardian-install.sh | 93 +++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 4640ed06..0edaa9f8 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -15,62 +15,83 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ - curl \ - ca-certificates \ - gnupg \ - lsb-release + git \ + nodejs \ + npm \ + sqlite3 \ + unzip \ + curl msg_ok "Installed Dependencies" -msg_info "Installing Docker" -curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null -$STD apt-get update -$STD apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin -systemctl enable --now docker -msg_ok "Installed Docker" - -msg_info "Setting up Guardian" -mkdir -p /opt/Guardian -cd /opt/Guardian - +msg_info "Setup Guardian" RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') -curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.example.yml" +curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" +unzip -q "${RELEASE}.zip" + +FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') +mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" echo "${RELEASE}" >/opt/Guardian_version.txt msg_ok "Setup Guardian" -msg_info "Starting Guardian" -cd /opt/Guardian -docker compose up -d -msg_ok "Started Guardian" -msg_info "Creating Guardian Service" -cat </etc/systemd/system/guardian.service +msg_info "Building backend" +cd /opt/Guardian/backend +npm ci +npm run build +msg_ok "Built backend" + +msg_info "Building frontend" +cd /opt/Guardian/frontend +npm ci +NODE_ENV=development npm run build +msg_ok "Built frontend" + +msg_info "Creating Backend Service" +cat </etc/systemd/system/guardian-backend.service [Unit] -Description=Guardian Docker Compose -Requires=docker.service -After=docker.service -Wants=network-online.target -After=network-online.target +Description=Guardian Backend +After=network.target [Service] -Type=oneshot -RemainAfterExit=true -WorkingDirectory=/opt/Guardian -ExecStart=/usr/bin/docker compose up -d -ExecStop=/usr/bin/docker compose down -TimeoutStartSec=0 +WorkingDirectory=/opt/Guardian/backend +Environment=NODE_ENV=development +ExecStart=/usr/bin/node dist/main.js +Restart=always +RestartSec=3 [Install] WantedBy=multi-user.target EOF -systemctl enable guardian -msg_ok "Created Guardian Service" +systemctl enable -q --now guardian-backend +msg_ok "Created Backend Service" + +msg_info "Creating Frontend Service" +cat </etc/systemd/system/guardian-frontend.service +[Unit] +Description=Guardian Frontend +After=guardian-backend.service network.target +Wants=guardian-backend.service + +[Service] +WorkingDirectory=/opt/Guardian/frontend +Environment=NODE_ENV=production +Environment=PORT=3000 +ExecStart=/usr/bin/npm run start +Restart=always +RestartSec=3 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now guardian-frontend +msg_ok "Created Frontend Service" motd_ssh customize msg_info "Cleaning up" +rm -f "${RELEASE}".zip $STD apt-get -y autoremove $STD apt-get -y autoclean msg_ok "Cleaned" From d30d51209137079c3851383db52edac5cde8be86 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:40:35 -0400 Subject: [PATCH 15/41] Update installation scripts to use HydroshieldMKII repository for Guardian app setup --- misc/build.func | 2 +- misc/install.func | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/build.func b/misc/build.func index d6b0af72..689ef4e6 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2295,7 +2295,7 @@ EOF' fi msg_ok "Customized LXC Container" install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/install/${var_install}.sh)"; then exit $? fi } diff --git a/misc/install.func b/misc/install.func index f741b921..030031e9 100644 --- a/misc/install.func +++ b/misc/install.func @@ -195,7 +195,7 @@ EOF systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update + echo "bash -c \"\$(curl -fsSL https://github.com/HydroshieldMKII/ProxmoxVED/raw/add-guardian-app/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh From f6a8eed717c801259022867c99f872573a9a0b6a Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:04:50 -0400 Subject: [PATCH 16/41] Reduce CPU allocation to 2 for Guardian installation and update environment variables for production deployment --- frontend/public/json/guardian.json | 2 +- install/guardian-install.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/public/json/guardian.json b/frontend/public/json/guardian.json index 4bc9f6dc..57381efd 100644 --- a/frontend/public/json/guardian.json +++ b/frontend/public/json/guardian.json @@ -17,7 +17,7 @@ "type": "default", "script": "ct/guardian.sh", "resources": { - "cpu": 4, + "cpu": 2, "ram": 2048, "hdd": 6, "os": "Debian", diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 0edaa9f8..ba902914 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -44,7 +44,7 @@ msg_ok "Built backend" msg_info "Building frontend" cd /opt/Guardian/frontend npm ci -NODE_ENV=development npm run build +npm run build msg_ok "Built frontend" msg_info "Creating Backend Service" @@ -55,7 +55,7 @@ After=network.target [Service] WorkingDirectory=/opt/Guardian/backend -Environment=NODE_ENV=development +Environment=NODE_ENV=production ExecStart=/usr/bin/node dist/main.js Restart=always RestartSec=3 @@ -76,6 +76,7 @@ Wants=guardian-backend.service [Service] WorkingDirectory=/opt/Guardian/frontend Environment=NODE_ENV=production +Environment=DEPLOYMENT_MODE=standalone Environment=PORT=3000 ExecStart=/usr/bin/npm run start Restart=always From fa4e953f72e401dfdc912e9eb8843bc77ee51ca0 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:12:48 -0400 Subject: [PATCH 17/41] Update guardian-install.sh to set DEPLOYMENT_MODE for frontend build --- ct/guardian.sh | 0 install/guardian-install.sh | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 ct/guardian.sh diff --git a/ct/guardian.sh b/ct/guardian.sh old mode 100644 new mode 100755 diff --git a/install/guardian-install.sh b/install/guardian-install.sh index ba902914..fd697cf1 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -44,7 +44,7 @@ msg_ok "Built backend" msg_info "Building frontend" cd /opt/Guardian/frontend npm ci -npm run build +DEPLOYMENT_MODE=standalone npm run build msg_ok "Built frontend" msg_info "Creating Backend Service" From a533865a80261af359da4b840a544af9f81a0031 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:13:02 -0400 Subject: [PATCH 18/41] Remove DEPLOYMENT_MODE environment variable from frontend service configuration --- install/guardian-install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index fd697cf1..747c6de8 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -76,7 +76,6 @@ Wants=guardian-backend.service [Service] WorkingDirectory=/opt/Guardian/frontend Environment=NODE_ENV=production -Environment=DEPLOYMENT_MODE=standalone Environment=PORT=3000 ExecStart=/usr/bin/npm run start Restart=always From a4cc08a194c775180ee05be9e9ae7f017b06e6f3 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:13:29 -0400 Subject: [PATCH 19/41] Remove NODE_ENV and PORT environment variables from frontend and backend service configurations --- install/guardian-install.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 747c6de8..876260cc 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -55,7 +55,6 @@ After=network.target [Service] WorkingDirectory=/opt/Guardian/backend -Environment=NODE_ENV=production ExecStart=/usr/bin/node dist/main.js Restart=always RestartSec=3 @@ -75,8 +74,6 @@ Wants=guardian-backend.service [Service] WorkingDirectory=/opt/Guardian/frontend -Environment=NODE_ENV=production -Environment=PORT=3000 ExecStart=/usr/bin/npm run start Restart=always RestartSec=3 From e9d9d03aa8b24634fad91241cf32c0fd75e1e61f Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:15:40 -0400 Subject: [PATCH 20/41] Refactor guardian-install.sh to streamline dependency installation by removing npm and ensuring proper package installation order --- install/guardian-install.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 876260cc..eb314f21 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -16,11 +16,14 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ git \ - nodejs \ - npm \ sqlite3 \ unzip \ - curl + curl \ + ca-certificates \ + gnupg + +curl -fsSL https://deb.nodesource.com/setup_24.x | bash - +$STD apt-get install -y nodejs msg_ok "Installed Dependencies" msg_info "Setup Guardian" From b4646fbd78a52193f61ca50559c1ca76891f2a84 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:25:41 -0400 Subject: [PATCH 21/41] Refactor guardian-install.sh to simplify dependency installation and ensure DEPLOYMENT_MODE is set for frontend service --- install/guardian-install.sh | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index eb314f21..ad623502 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -16,14 +16,11 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ git \ + nodejs \ + npm \ sqlite3 \ unzip \ - curl \ - ca-certificates \ - gnupg - -curl -fsSL https://deb.nodesource.com/setup_24.x | bash - -$STD apt-get install -y nodejs + curl msg_ok "Installed Dependencies" msg_info "Setup Guardian" @@ -77,6 +74,7 @@ Wants=guardian-backend.service [Service] WorkingDirectory=/opt/Guardian/frontend +Environment=DEPLOYMENT_MODE=standalone ExecStart=/usr/bin/npm run start Restart=always RestartSec=3 From aa2649a9337e6cb08d4fd5363024b730b2016316 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:34:25 -0400 Subject: [PATCH 22/41] Refactor guardian-install.sh to remove npm installation and ensure proper dependency setup --- install/guardian-install.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index ad623502..de05a8e8 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -16,11 +16,15 @@ update_os msg_info "Installing Dependencies" $STD apt-get install -y \ git \ - nodejs \ - npm \ sqlite3 \ unzip \ - curl + curl \ + ca-certificates \ + gnupg + +curl -fsSL https://deb.nodesource.com/setup_24.x | bash - +$STD apt-get install -y nodejs + msg_ok "Installed Dependencies" msg_info "Setup Guardian" From e8e630c8417dd64e135af736b6927ca06e73ead3 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 17:41:22 -0400 Subject: [PATCH 23/41] Refactor update_script function to replace Docker commands with systemctl for service management and enhance database backup/restore process --- ct/guardian.sh | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index d1da2113..4b67897b 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -34,30 +34,56 @@ function update_script() { if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then msg_info "Stopping $APP" - cd /opt/Guardian - docker compose down + systemctl stop guardian-backend guardian-frontend msg_ok "Stopped $APP" - msg_info "Updating $APP to v${RELEASE}" - curl -fsSL -o docker-compose.yml "https://raw.githubusercontent.com/HydroshieldMKII/Guardian/main/docker-compose.example.yml" - docker compose pull + msg_info "Saving Database" + if [[ -f "/opt/Guardian/backend/plex-guard.db" ]]; then + cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" + msg_ok "Database backed up" + fi + + + msg_info "Updating $APP to v${RELEASE}" + cd /tmp + + curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" + unzip -q "${RELEASE}.zip" + rm -rf /opt/Guardian + + FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') + mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" + + if [[ -f "/tmp/plex-guard.db.backup" ]]; then + msg_info "Restoring Database" + cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" + rm "/tmp/plex-guard.db.backup" + msg_ok "Database restored" + fi + + cd /opt/Guardian/backend + npm ci + npm run build + + cd /opt/Guardian/frontend + npm ci + DEPLOYMENT_MODE=standalone npm run build echo "${RELEASE}" >/opt/${APP}_version.txt msg_ok "Updated $APP to v${RELEASE}" msg_info "Starting $APP" - cd /opt/Guardian - docker compose up -d + systemctl start guardian-backend guardian-frontend msg_ok "Started $APP" msg_info "Cleaning Up" - docker system prune -f + rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${FOLDER_NAME}" /tmp/plex-guard.db.backup msg_ok "Cleanup Completed" msg_ok "Update Successful" else - msg_ok "No update required. ${APP} is already at ${RELEASE}" + msg_ok "No update required. ${APP} is already at v${RELEASE}" fi exit } From 462c71c4578de32614332c5533b89b6b45dae34d Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 18:04:40 -0400 Subject: [PATCH 24/41] Update script to source functions from new repository location for consistency --- misc/build.func | 14 +++++++------- misc/install.func | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/misc/build.func b/misc/build.func index 689ef4e6..fd9f0092 100644 --- a/misc/build.func +++ b/misc/build.func @@ -128,17 +128,17 @@ variables() { # - Initialize error traps after loading # ------------------------------------------------------------------------------ -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) if command -v curl >/dev/null 2>&1; then - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via curl" elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) + source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via wget" @@ -2062,7 +2062,7 @@ ssh_discover_default_files() { # - Otherwise: shows update/setting menu # ------------------------------------------------------------------------------ start() { - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) if command -v pveversion >/dev/null 2>&1; then install_script || return 0 return 0 @@ -2183,7 +2183,7 @@ build_container() { LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/passthrough.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/passthrough.func) select_hw_passthrough "$CTID" "$CT_TYPE" "$APP" # TUN device passthrough diff --git a/misc/install.func b/misc/install.func index 030031e9..c63ad7a0 100644 --- a/misc/install.func +++ b/misc/install.func @@ -9,8 +9,8 @@ if ! command -v curl >/dev/null 2>&1; then apt-get update >/dev/null 2>&1 apt-get install -y curl >/dev/null 2>&1 fi -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors @@ -147,7 +147,7 @@ EOF $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings From 9b1c5d4af004ac54e139bc04ae910ffcd4229f5e Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 18:11:59 -0400 Subject: [PATCH 25/41] Fix update message to display version without 'v' prefix --- ct/guardian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 4b67897b..d14a2981 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -83,7 +83,7 @@ function update_script() { msg_ok "Update Successful" else - msg_ok "No update required. ${APP} is already at v${RELEASE}" + msg_ok "No update required. ${APP} is already at ${RELEASE}" fi exit } From 12659a9357fc49e34dd9fd19447ec7ad84f5c1ff Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 18:13:20 -0400 Subject: [PATCH 26/41] Fix update message to display version without 'v' prefix --- ct/guardian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index d14a2981..a1078f54 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -45,7 +45,7 @@ function update_script() { fi - msg_info "Updating $APP to v${RELEASE}" + msg_info "Updating $APP to ${RELEASE}" cd /tmp curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" From d8b1ee6c73d040eee55bd7d0b8ee451ce3d8884b Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 18:15:22 -0400 Subject: [PATCH 27/41] Update guardian.sh to source build functions from the correct repository location --- ct/guardian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index a1078f54..07203be2 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: HydroshieldMKII # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE From d7a1ccd59fd9ecb02c389c13c113852535951b00 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Tue, 30 Sep 2025 18:20:46 -0400 Subject: [PATCH 28/41] Update build and install scripts to source functions from the main community repository --- misc/build.func | 16 ++++++++-------- misc/install.func | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/misc/build.func b/misc/build.func index fd9f0092..5ada1a46 100644 --- a/misc/build.func +++ b/misc/build.func @@ -128,17 +128,17 @@ variables() { # - Initialize error traps after loading # ------------------------------------------------------------------------------ -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) if command -v curl >/dev/null 2>&1; then - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via curl" elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) - source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) + source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) + source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via wget" @@ -2062,7 +2062,7 @@ ssh_discover_default_files() { # - Otherwise: shows update/setting menu # ------------------------------------------------------------------------------ start() { - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) if command -v pveversion >/dev/null 2>&1; then install_script || return 0 return 0 @@ -2183,7 +2183,7 @@ build_container() { LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/passthrough.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/passthrough.func) select_hw_passthrough "$CTID" "$CT_TYPE" "$APP" # TUN device passthrough @@ -2295,7 +2295,7 @@ EOF' fi msg_ok "Customized LXC Container" install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/install/${var_install}.sh)"; then + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/install/${var_install}.sh)"; then exit $? fi } diff --git a/misc/install.func b/misc/install.func index c63ad7a0..f741b921 100644 --- a/misc/install.func +++ b/misc/install.func @@ -9,8 +9,8 @@ if ! command -v curl >/dev/null 2>&1; then apt-get update >/dev/null 2>&1 apt-get install -y curl >/dev/null 2>&1 fi -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) load_functions catch_errors @@ -147,7 +147,7 @@ EOF $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings @@ -195,7 +195,7 @@ EOF systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://github.com/HydroshieldMKII/ProxmoxVED/raw/add-guardian-app/ct/${app}.sh)\"" >/usr/bin/update + echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh From 64d22d0048e59dcad7f1300ecb2cc26196d53a0a Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:23:48 -0400 Subject: [PATCH 29/41] Update script URL for LXC container installation --- misc/build.func | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/build.func b/misc/build.func index 5ada1a46..d6b0af72 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2295,7 +2295,7 @@ EOF' fi msg_ok "Customized LXC Container" install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/install/${var_install}.sh)"; then + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then exit $? fi } From 9798c7d6c95637da431c0589bd1f8dc40b63ca1c Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:52:27 -0400 Subject: [PATCH 30/41] Updated install and update script for PR request. Update the json for the interface --- ct/guardian.sh | 81 ++++++++++++------------------ frontend/public/json/guardian.json | 6 +-- install/guardian-install.sh | 53 ++++++------------- 3 files changed, 50 insertions(+), 90 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 07203be2..1017b2c1 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -11,7 +11,7 @@ var_cpu="${var_cpu:-2}" var_ram="${var_ram:-2048}" var_disk="${var_disk:-6}" var_os="${var_os:-debian}" -var_version="${var_version:-12}" +var_version="${var_version:-13}" var_unprivileged="${var_unprivileged:-1}" header_info "$APP" @@ -20,72 +20,55 @@ color catch_errors function update_script() { - header_info - check_container_storage - check_container_resources +header_info +check_container_storage +check_container_resources - if [[ ! -d "/opt/${APP}" ]]; then +if [[ ! -d "/opt/guardian" ]] ; then msg_error "No ${APP} Installation Found!" exit - fi +fi - - RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') - if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f /opt/${APP}_version.txt ]]; then - - msg_info "Stopping $APP" +if check_for_gh_release "guardian" "HydroshieldMKII/Guardian" ; then + msg_info "Stopping Services" systemctl stop guardian-backend guardian-frontend - msg_ok "Stopped $APP" + msg_ok "Stopped Services" - msg_info "Saving Database" - if [[ -f "/opt/Guardian/backend/plex-guard.db" ]]; then - cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" - msg_ok "Database backed up" + if [[ -f "/opt/Guardian/backend/plex-guard.db" ]] ; then + msg_info "Saving Database" + cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" + msg_ok "Database backed up" fi + cp /opt/guardian/.env /opt + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "latest" "/opt/guardian" + mv /opt/.env /opt/guardian - msg_info "Updating $APP to ${RELEASE}" - cd /tmp - - curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" - unzip -q "${RELEASE}.zip" - rm -rf /opt/Guardian - - FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') - mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" - - if [[ -f "/tmp/plex-guard.db.backup" ]]; then - msg_info "Restoring Database" - cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" - rm "/tmp/plex-guard.db.backup" - msg_ok "Database restored" + if [[ -f "/tmp/plex-guard.db.backup" ]] ; then + msg_info "Restoring Database" + cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" + rm "/tmp/plex-guard.db.backup" + msg_ok "Database restored" fi + msg_info "Updating Guardian" cd /opt/Guardian/backend - npm ci - npm run build + $STD npm ci + $STD npm run build cd /opt/Guardian/frontend - npm ci - DEPLOYMENT_MODE=standalone npm run build + $STD npm ci + $STD DEPLOYMENT_MODE = standalone npm run build - echo "${RELEASE}" >/opt/${APP}_version.txt - msg_ok "Updated $APP to v${RELEASE}" + msg_ok "Updated Guardian" - msg_info "Starting $APP" + msg_info "Starting Services" systemctl start guardian-backend guardian-frontend - msg_ok "Started $APP" - - msg_info "Cleaning Up" - rm -rf /tmp/"${RELEASE}.zip" /tmp/"Guardian-${FOLDER_NAME}" /tmp/plex-guard.db.backup - msg_ok "Cleanup Completed" - - msg_ok "Update Successful" - else - msg_ok "No update required. ${APP} is already at ${RELEASE}" - fi - exit + msg_ok "Started Services" + msg_ok "Updated Successfully" +fi +exit } start diff --git a/frontend/public/json/guardian.json b/frontend/public/json/guardian.json index 57381efd..97568819 100644 --- a/frontend/public/json/guardian.json +++ b/frontend/public/json/guardian.json @@ -8,10 +8,10 @@ "privileged": false, "interface_port": 3000, "documentation": null, - "config_path": "", + "config_path": "/opt/guardian/.env", "website": "https://github.com/HydroshieldMKII/Guardian", "logo": null, - "description": "A tool to manage devices access to your Plex server with a nice Dashboard ", + "description": "Guardian is a lightweight companion app for Plex that lets you monitor, approve or block devices in real time. It helps you enforce per-user or global policies, stop unwanted sessions automatically and grant temporary access - all through a simple web interface.", "install_methods": [ { "type": "default", @@ -21,7 +21,7 @@ "ram": 2048, "hdd": 6, "os": "Debian", - "version": "12" + "version": "13" } } ], diff --git a/install/guardian-install.sh b/install/guardian-install.sh index de05a8e8..0a5cb5d6 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -14,86 +14,63 @@ network_check update_os msg_info "Installing Dependencies" -$STD apt-get install -y \ - git \ - sqlite3 \ - unzip \ - curl \ - ca-certificates \ - gnupg - -curl -fsSL https://deb.nodesource.com/setup_24.x | bash - -$STD apt-get install -y nodejs - +$STD apt install -y \ + sqlite3 +NODE_VERSION="24" setup_nodejs msg_ok "Installed Dependencies" msg_info "Setup Guardian" -RELEASE=$(curl -fsSL https://api.github.com/repos/HydroshieldMKII/Guardian/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') -curl -fsSL -o "${RELEASE}.zip" "https://github.com/HydroshieldMKII/Guardian/archive/refs/tags/${RELEASE}.zip" -unzip -q "${RELEASE}.zip" - -FOLDER_NAME=$(echo "${RELEASE}" | sed 's/^v//') -mv "Guardian-${FOLDER_NAME}/" "/opt/Guardian" - -echo "${RELEASE}" >/opt/Guardian_version.txt -msg_ok "Setup Guardian" +fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "latest" "/opt/guardian" msg_info "Building backend" cd /opt/Guardian/backend -npm ci -npm run build +$STD npm ci +$STD npm run build msg_ok "Built backend" msg_info "Building frontend" cd /opt/Guardian/frontend -npm ci -DEPLOYMENT_MODE=standalone npm run build +$STD npm ci +$STD DEPLOYMENT_MODE=standalone npm run build msg_ok "Built frontend" -msg_info "Creating Backend Service" +msg_info "Creating Services" cat </etc/systemd/system/guardian-backend.service [Unit] Description=Guardian Backend After=network.target - [Service] WorkingDirectory=/opt/Guardian/backend ExecStart=/usr/bin/node dist/main.js Restart=always RestartSec=3 - [Install] WantedBy=multi-user.target EOF -systemctl enable -q --now guardian-backend -msg_ok "Created Backend Service" -msg_info "Creating Frontend Service" cat </etc/systemd/system/guardian-frontend.service [Unit] Description=Guardian Frontend After=guardian-backend.service network.target Wants=guardian-backend.service - [Service] WorkingDirectory=/opt/Guardian/frontend Environment=DEPLOYMENT_MODE=standalone ExecStart=/usr/bin/npm run start Restart=always RestartSec=3 - [Install] WantedBy=multi-user.target EOF + +systemctl enable -q --now guardian-backend systemctl enable -q --now guardian-frontend -msg_ok "Created Frontend Service" +msg_ok "Created Services" motd_ssh -customize -msg_info "Cleaning up" -rm -f "${RELEASE}".zip -$STD apt-get -y autoremove -$STD apt-get -y autoclean +apt -y autoremove +apt -y autoclean +apt -y clean msg_ok "Cleaned" From ed6f915b9fe9a27e61b8bbdf962c4ea9735b5118 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Wed, 1 Oct 2025 09:06:58 -0400 Subject: [PATCH 31/41] temporary edit build and install.func for testing --- ct/guardian.sh | 2 +- misc/build.func | 12 ++++++------ misc/install.func | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 1017b2c1..ed8a561c 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: HydroshieldMKII # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE diff --git a/misc/build.func b/misc/build.func index d6b0af72..d0c9988a 100644 --- a/misc/build.func +++ b/misc/build.func @@ -128,17 +128,17 @@ variables() { # - Initialize error traps after loading # ------------------------------------------------------------------------------ -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) if command -v curl >/dev/null 2>&1; then - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via curl" elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) - source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) + source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors #echo "(build.func) Loaded core.func via wget" @@ -2295,7 +2295,7 @@ EOF' fi msg_ok "Customized LXC Container" install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/install/${var_install}.sh)"; then exit $? fi } diff --git a/misc/install.func b/misc/install.func index f741b921..bfa55d45 100644 --- a/misc/install.func +++ b/misc/install.func @@ -9,8 +9,8 @@ if ! command -v curl >/dev/null 2>&1; then apt-get update >/dev/null 2>&1 apt-get install -y curl >/dev/null 2>&1 fi -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) load_functions catch_errors @@ -147,7 +147,7 @@ EOF $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED msg_ok "Updated Container OS" - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) + source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) } # This function modifies the message of the day (motd) and SSH settings @@ -195,7 +195,7 @@ EOF systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update + echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/ct/${app}.sh)\"" >/usr/bin/update chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then mkdir -p /root/.ssh From 7a8f1aa0ff8d0959fd88ec1d4f53b38c03464dcd Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Wed, 1 Oct 2025 09:10:20 -0400 Subject: [PATCH 32/41] Fix case sensitivity in Guardian paths across scripts --- ct/guardian.sh | 12 ++++++------ install/guardian-install.sh | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index ed8a561c..4358f287 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -35,9 +35,9 @@ if check_for_gh_release "guardian" "HydroshieldMKII/Guardian" ; then msg_ok "Stopped Services" - if [[ -f "/opt/Guardian/backend/plex-guard.db" ]] ; then + if [[ -f "/opt/guardian/backend/plex-guard.db" ]] ; then msg_info "Saving Database" - cp "/opt/Guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" + cp "/opt/guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" msg_ok "Database backed up" fi @@ -47,19 +47,19 @@ if check_for_gh_release "guardian" "HydroshieldMKII/Guardian" ; then if [[ -f "/tmp/plex-guard.db.backup" ]] ; then msg_info "Restoring Database" - cp "/tmp/plex-guard.db.backup" "/opt/Guardian/backend/plex-guard.db" + cp "/tmp/plex-guard.db.backup" "/opt/guardian/backend/plex-guard.db" rm "/tmp/plex-guard.db.backup" msg_ok "Database restored" fi msg_info "Updating Guardian" - cd /opt/Guardian/backend + cd /opt/guardian/backend $STD npm ci $STD npm run build - cd /opt/Guardian/frontend + cd /opt/guardian/frontend $STD npm ci - $STD DEPLOYMENT_MODE = standalone npm run build + $STD DEPLOYMENT_MODE=standalone npm run build msg_ok "Updated Guardian" diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 0a5cb5d6..c6a745e5 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -24,13 +24,13 @@ fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "lat msg_info "Building backend" -cd /opt/Guardian/backend +cd /opt/guardian/backend $STD npm ci $STD npm run build msg_ok "Built backend" msg_info "Building frontend" -cd /opt/Guardian/frontend +cd /opt/guardian/frontend $STD npm ci $STD DEPLOYMENT_MODE=standalone npm run build msg_ok "Built frontend" @@ -41,7 +41,7 @@ cat </etc/systemd/system/guardian-backend.service Description=Guardian Backend After=network.target [Service] -WorkingDirectory=/opt/Guardian/backend +WorkingDirectory=/opt/guardian/backend ExecStart=/usr/bin/node dist/main.js Restart=always RestartSec=3 @@ -55,7 +55,7 @@ Description=Guardian Frontend After=guardian-backend.service network.target Wants=guardian-backend.service [Service] -WorkingDirectory=/opt/Guardian/frontend +WorkingDirectory=/opt/guardian/frontend Environment=DEPLOYMENT_MODE=standalone ExecStart=/usr/bin/npm run start Restart=always From 8d44bed6c597de409cd607777298c508f0c260d1 Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Wed, 1 Oct 2025 09:35:51 -0400 Subject: [PATCH 33/41] Refactor frontend build process to export DEPLOYMENT_MODE before running npm build --- install/guardian-install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index c6a745e5..19c97ffe 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -32,7 +32,8 @@ msg_ok "Built backend" msg_info "Building frontend" cd /opt/guardian/frontend $STD npm ci -$STD DEPLOYMENT_MODE=standalone npm run build +export DEPLOYMENT_MODE=standalone +$STD npm run build msg_ok "Built frontend" msg_info "Creating Services" From 18c3b2979d1a571f0a5ae72a9bc77e310ce7354c Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Wed, 1 Oct 2025 09:36:16 -0400 Subject: [PATCH 34/41] Refactor build messages in guardian-install.sh for consistency --- install/guardian-install.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 19c97ffe..6b3766ee 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -23,18 +23,16 @@ msg_info "Setup Guardian" fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "latest" "/opt/guardian" -msg_info "Building backend" +msg_info "Building App" cd /opt/guardian/backend $STD npm ci $STD npm run build -msg_ok "Built backend" -msg_info "Building frontend" cd /opt/guardian/frontend $STD npm ci export DEPLOYMENT_MODE=standalone $STD npm run build -msg_ok "Built frontend" +msg_ok "Built App" msg_info "Creating Services" cat </etc/systemd/system/guardian-backend.service From 660d8a21cbd4ee687a660b55b162d7010b3e9675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slavi=C5=A1a=20Are=C5=BEina?= <58952836+tremor021@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:39:34 +0200 Subject: [PATCH 35/41] Update guardian.sh --- ct/guardian.sh | 64 ++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index 4358f287..c33a5c06 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -25,48 +25,46 @@ check_container_storage check_container_resources if [[ ! -d "/opt/guardian" ]] ; then - msg_error "No ${APP} Installation Found!" - exit + msg_error "No ${APP} Installation Found!" + exit fi if check_for_gh_release "guardian" "HydroshieldMKII/Guardian" ; then - msg_info "Stopping Services" - systemctl stop guardian-backend guardian-frontend - msg_ok "Stopped Services" + msg_info "Stopping Services" + systemctl stop guardian-backend guardian-frontend + msg_ok "Stopped Services" + if [[ -f "/opt/guardian/backend/plex-guard.db" ]] ; then + msg_info "Saving Database" + cp "/opt/guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" + msg_ok "Database backed up" + fi - if [[ -f "/opt/guardian/backend/plex-guard.db" ]] ; then - msg_info "Saving Database" - cp "/opt/guardian/backend/plex-guard.db" "/tmp/plex-guard.db.backup" - msg_ok "Database backed up" - fi + cp /opt/guardian/.env /opt + CLEAN_INSTALL=1 fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "latest" "/opt/guardian" + mv /opt/.env /opt/guardian - cp /opt/guardian/.env /opt - CLEAN_INSTALL=1 fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "latest" "/opt/guardian" - mv /opt/.env /opt/guardian + if [[ -f "/tmp/plex-guard.db.backup" ]] ; then + msg_info "Restoring Database" + cp "/tmp/plex-guard.db.backup" "/opt/guardian/backend/plex-guard.db" + rm "/tmp/plex-guard.db.backup" + msg_ok "Database restored" + fi - if [[ -f "/tmp/plex-guard.db.backup" ]] ; then - msg_info "Restoring Database" - cp "/tmp/plex-guard.db.backup" "/opt/guardian/backend/plex-guard.db" - rm "/tmp/plex-guard.db.backup" - msg_ok "Database restored" - fi + msg_info "Updating Guardian" + cd /opt/guardian/backend + $STD npm ci + $STD npm run build - msg_info "Updating Guardian" - cd /opt/guardian/backend - $STD npm ci - $STD npm run build + cd /opt/guardian/frontend + $STD npm ci + $STD DEPLOYMENT_MODE=standalone npm run build + msg_ok "Updated Guardian" - cd /opt/guardian/frontend - $STD npm ci - $STD DEPLOYMENT_MODE=standalone npm run build - - msg_ok "Updated Guardian" - - msg_info "Starting Services" - systemctl start guardian-backend guardian-frontend - msg_ok "Started Services" - msg_ok "Updated Successfully" + msg_info "Starting Services" + systemctl start guardian-backend guardian-frontend + msg_ok "Started Services" + msg_ok "Updated Successfully" fi exit } From 07cc74b074a9dd6ff9503a31b33d770ea22a542d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slavi=C5=A1a=20Are=C5=BEina?= <58952836+tremor021@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:41:31 +0200 Subject: [PATCH 36/41] Update guardian.json --- frontend/public/json/guardian.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/public/json/guardian.json b/frontend/public/json/guardian.json index 97568819..b7c6761d 100644 --- a/frontend/public/json/guardian.json +++ b/frontend/public/json/guardian.json @@ -1,13 +1,15 @@ { "name": "Guardian", "slug": "guardian", - "categories": [13], + "categories": [ + 13 + ], "date_created": "2025-09-22", "type": "ct", "updateable": true, "privileged": false, "interface_port": 3000, - "documentation": null, + "documentation": "https://github.com/HydroshieldMKII/Guardian/blob/main/README.md", "config_path": "/opt/guardian/.env", "website": "https://github.com/HydroshieldMKII/Guardian", "logo": null, From e7a9bc04c683c072829498bf051a58a577f1698a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slavi=C5=A1a=20Are=C5=BEina?= <58952836+tremor021@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:44:13 +0200 Subject: [PATCH 37/41] Update guardian-install.sh --- install/guardian-install.sh | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/install/guardian-install.sh b/install/guardian-install.sh index 6b3766ee..b982bf3c 100644 --- a/install/guardian-install.sh +++ b/install/guardian-install.sh @@ -14,31 +14,28 @@ network_check update_os msg_info "Installing Dependencies" -$STD apt install -y \ - sqlite3 -NODE_VERSION="24" setup_nodejs +$STD apt install -y sqlite3 msg_ok "Installed Dependencies" -msg_info "Setup Guardian" +NODE_VERSION="24" setup_nodejs fetch_and_deploy_gh_release "guardian" "HydroshieldMKII/Guardian" "tarball" "latest" "/opt/guardian" - -msg_info "Building App" +msg_info "Configuring ${APPLICATION}" cd /opt/guardian/backend $STD npm ci $STD npm run build - cd /opt/guardian/frontend $STD npm ci export DEPLOYMENT_MODE=standalone $STD npm run build -msg_ok "Built App" +msg_ok "Configured {APPLICATION}" -msg_info "Creating Services" +msg_info "Creating Service" cat </etc/systemd/system/guardian-backend.service [Unit] Description=Guardian Backend After=network.target + [Service] WorkingDirectory=/opt/guardian/backend ExecStart=/usr/bin/node dist/main.js @@ -53,6 +50,7 @@ cat </etc/systemd/system/guardian-frontend.service Description=Guardian Frontend After=guardian-backend.service network.target Wants=guardian-backend.service + [Service] WorkingDirectory=/opt/guardian/frontend Environment=DEPLOYMENT_MODE=standalone @@ -65,9 +63,10 @@ EOF systemctl enable -q --now guardian-backend systemctl enable -q --now guardian-frontend -msg_ok "Created Services" +msg_ok "Created Service" motd_ssh +customize apt -y autoremove apt -y autoclean From b074b25daa2930a621873fcecce42adfea2d918a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slavi=C5=A1a=20Are=C5=BEina?= <58952836+tremor021@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:44:47 +0200 Subject: [PATCH 38/41] Delete misc/install.func --- misc/install.func | 206 ---------------------------------------------- 1 file changed, 206 deletions(-) delete mode 100644 misc/install.func diff --git a/misc/install.func b/misc/install.func deleted file mode 100644 index bfa55d45..00000000 --- a/misc/install.func +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (c) 2021-2025 tteck -# Author: tteck (tteckster) -# Co-Author: MickLesk -# Co-Author: michelroegl-brunner -# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE - -if ! command -v curl >/dev/null 2>&1; then - printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 - apt-get update >/dev/null 2>&1 - apt-get install -y curl >/dev/null 2>&1 -fi -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) -load_functions -catch_errors - -# This function enables IPv6 if it's not disabled and sets verbose mode -verb_ip6() { - set_std_mode # Set STD mode based on VERBOSE - - if [ "$DISABLEIPV6" == "yes" ]; then - echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf - $STD sysctl -p - fi -} - -# # This function sets error handling options and defines the error_handler function to handle errors -# catch_errors() { -# set -Eeuo pipefail -# trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -# } - -# # This function handles errors -# error_handler() { -# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func) -# local exit_code="$1" -# local line_number="$2" -# local command="${3:-}" - -# if [[ "$exit_code" -eq 0 ]]; then -# return 0 -# fi - -# printf "\e[?25h" -# echo -e "\n${RD}[ERROR]${CL} in line ${RD}${line_number}${CL}: exit code ${RD}${exit_code}${CL}: while executing command ${YW}${command}${CL}\n" -# exit "$exit_code" -#} - -# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection -setting_up_container() { - msg_info "Setting up Container OS" - for ((i = RETRY_NUM; i > 0; i--)); do - if [ "$(hostname -I)" != "" ]; then - break - fi - echo 1>&2 -en "${CROSS}${RD} No Network! " - sleep $RETRY_EVERY - done - if [ "$(hostname -I)" = "" ]; then - echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" - echo -e "${NETWORK}Check Network Settings" - exit 1 - fi - rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED - systemctl disable -q --now systemd-networkd-wait-online.service - msg_ok "Set up Container OS" - #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" - msg_ok "Network Connected: ${BL}$(hostname -I)" -} - -# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected -network_check() { - set +e - trap - ERR - ipv4_connected=false - ipv6_connected=false - sleep 1 - - # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. - if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then - msg_ok "IPv4 Internet Connected" - ipv4_connected=true - else - msg_error "IPv4 Internet Not Connected" - fi - - # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. - if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then - msg_ok "IPv6 Internet Connected" - ipv6_connected=true - else - msg_error "IPv6 Internet Not Connected" - fi - - # If both IPv4 and IPv6 checks fail, prompt the user - if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then - read -r -p "No Internet detected, would you like to continue anyway? " prompt - if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then - echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" - else - echo -e "${NETWORK}Check Network Settings" - exit 1 - fi - fi - - # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) - GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") - GIT_STATUS="Git DNS:" - DNS_FAILED=false - - for HOST in "${GIT_HOSTS[@]}"; do - RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) - if [[ -z "$RESOLVEDIP" ]]; then - GIT_STATUS+="$HOST:($DNSFAIL)" - DNS_FAILED=true - else - GIT_STATUS+=" $HOST:($DNSOK)" - fi - done - - if [[ "$DNS_FAILED" == true ]]; then - fatal "$GIT_STATUS" - else - msg_ok "$GIT_STATUS" - fi - - set -e - trap 'error_handler $LINENO "$BASH_COMMAND"' ERR -} - -# This function updates the Container OS by running apt-get update and upgrade -update_os() { - msg_info "Updating Container OS" - if [[ "$CACHER" == "yes" ]]; then - echo "Acquire::http::Proxy-Auto-Detect \"/usr/local/bin/apt-proxy-detect.sh\";" >/etc/apt/apt.conf.d/00aptproxy - cat <<'EOF' >/usr/local/bin/apt-proxy-detect.sh -#!/bin/bash -if nc -w1 -z "${CACHER_IP}" 3142; then - echo -n "http://${CACHER_IP}:3142" -else - echo -n "DIRECT" -fi -EOF - chmod +x /usr/local/bin/apt-proxy-detect.sh - fi - $STD apt-get update - $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade - rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED - msg_ok "Updated Container OS" - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/tools.func) -} - -# This function modifies the message of the day (motd) and SSH settings -motd_ssh() { - grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc - - if [ -f "/etc/os-release" ]; then - OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"') - elif [ -f "/etc/debian_version" ]; then - OS_NAME="Debian" - OS_VERSION=$(cat /etc/debian_version) - fi - - PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" - echo "echo -e \"\"" >"$PROFILE_FILE" - echo -e "echo -e \"${BOLD}${YW}${APPLICATION} LXC Container - DEV Repository${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${RD}WARNING: This is a DEVELOPMENT version (ProxmoxVED). Do NOT use in production!${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE" - echo -e "echo -e \"${YW} Repository: ${GN}https://github.com/community-scripts/ProxmoxVED${CL}\"" >>"$PROFILE_FILE" - echo "echo \"\"" >>"$PROFILE_FILE" - - chmod -x /etc/update-motd.d/* - - if [[ "${SSH_ROOT}" == "yes" ]]; then - sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config - systemctl restart sshd - fi -} - -# This function customizes the container by modifying the getty service and enabling auto-login for the root user -customize() { - if [[ "$PASSWORD" == "" ]]; then - msg_info "Customizing Container" - GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" - mkdir -p $(dirname $GETTY_OVERRIDE) - cat <$GETTY_OVERRIDE - [Service] - ExecStart= - ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM -EOF - systemctl daemon-reload - systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') - msg_ok "Customized Container" - fi - echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/ct/${app}.sh)\"" >/usr/bin/update - chmod +x /usr/bin/update - if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then - mkdir -p /root/.ssh - echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys - chmod 700 /root/.ssh - chmod 600 /root/.ssh/authorized_keys - fi -} From 1a6c5dbf7629be311803cfa1382c5791767d7678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Slavi=C5=A1a=20Are=C5=BEina?= <58952836+tremor021@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:45:17 +0200 Subject: [PATCH 39/41] Delete misc/build.func --- misc/build.func | 2981 ----------------------------------------------- 1 file changed, 2981 deletions(-) delete mode 100644 misc/build.func diff --git a/misc/build.func b/misc/build.func deleted file mode 100644 index d0c9988a..00000000 --- a/misc/build.func +++ /dev/null @@ -1,2981 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) 2021-2025 community-scripts ORG -# Author: tteck (tteckster) | MickLesk | michelroegl-brunner -# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE -# Revision: 1 - -# ------------------------------------------------------------------------------ -# variables() -# -# - Normalize application name (NSAPP = lowercase, no spaces) -# - Build installer filename (var_install) -# - Define regex for integer validation -# - Fetch hostname of Proxmox node -# - Set default values for diagnostics/method -# - Generate random UUID for tracking -# ------------------------------------------------------------------------------ -variables() { - NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces. - var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP. - INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern. - PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase - DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call. - METHOD="default" # sets the METHOD variable to "default", used for the API call. - RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable. - #CT_TYPE=${var_unprivileged:-$CT_TYPE} -} - -# ----------------------------------------------------------------------------- -# Community-Scripts bootstrap loader -# - Always sources build.func from remote -# - Updates local core files only if build.func changed -# - Local cache: /usr/local/community-scripts/core -# ----------------------------------------------------------------------------- - -# FUNC_DIR="/usr/local/community-scripts/core" -# mkdir -p "$FUNC_DIR" - -# BUILD_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func" -# BUILD_REV="$FUNC_DIR/build.rev" -# DEVMODE="${DEVMODE:-no}" - -# # --- Step 1: fetch build.func content once, compute hash --- -# build_content="$(curl -fsSL "$BUILD_URL")" || { -# echo "❌ Failed to fetch build.func" -# exit 1 -# } - -# newhash=$(printf "%s" "$build_content" | sha256sum | awk '{print $1}') -# oldhash=$(cat "$BUILD_REV" 2>/dev/null || echo "") - -# # --- Step 2: if build.func changed, offer update for core files --- -# if [ "$newhash" != "$oldhash" ]; then -# echo "⚠️ build.func changed!" - -# while true; do -# read -rp "Refresh local core files? [y/N/diff]: " ans -# case "$ans" in -# [Yy]*) -# echo "$newhash" >"$BUILD_REV" - -# update_func_file() { -# local file="$1" -# local url="https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/$file" -# local local_path="$FUNC_DIR/$file" - -# echo "⬇️ Downloading $file ..." -# curl -fsSL "$url" -o "$local_path" || { -# echo "❌ Failed to fetch $file" -# exit 1 -# } -# echo "✔️ Updated $file" -# } - -# update_func_file core.func -# update_func_file error_handler.func -# update_func_file tools.func -# break -# ;; -# [Dd]*) -# for file in core.func error_handler.func tools.func; do -# local_path="$FUNC_DIR/$file" -# url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/$file" -# remote_tmp="$(mktemp)" - -# curl -fsSL "$url" -o "$remote_tmp" || continue - -# if [ -f "$local_path" ]; then -# echo "🔍 Diff for $file:" -# diff -u "$local_path" "$remote_tmp" || echo "(no differences)" -# else -# echo "📦 New file $file will be installed" -# fi - -# rm -f "$remote_tmp" -# done -# ;; -# *) -# echo "❌ Skipped updating local core files" -# break -# ;; -# esac -# done -# else -# if [ "$DEVMODE" != "yes" ]; then -# echo "✔️ build.func unchanged → using existing local core files" -# fi -# fi - -# if [ -n "${_COMMUNITY_SCRIPTS_LOADER:-}" ]; then -# return 0 2>/dev/null || exit 0 -# fi -# _COMMUNITY_SCRIPTS_LOADER=1 - -# # --- Step 3: always source local versions of the core files --- -# source "$FUNC_DIR/core.func" -# source "$FUNC_DIR/error_handler.func" -# source "$FUNC_DIR/tools.func" - -# # --- Step 4: finally, source build.func directly from memory --- -# # (no tmp file needed) -# source <(printf "%s" "$build_content") - -# ------------------------------------------------------------------------------ -# Load core + error handler functions from community-scripts repo -# -# - Prefer curl if available, fallback to wget -# - Load: core.func, error_handler.func, api.func -# - Initialize error traps after loading -# ------------------------------------------------------------------------------ - -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/api.func) - -if command -v curl >/dev/null 2>&1; then - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) - source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) - load_functions - catch_errors - #echo "(build.func) Loaded core.func via curl" -elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/core.func) - source <(wget -qO- https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/error_handler.func) - load_functions - catch_errors - #echo "(build.func) Loaded core.func via wget" -fi - -# ------------------------------------------------------------------------------ -# maxkeys_check() -# -# - Reads kernel keyring limits (maxkeys, maxbytes) -# - Checks current usage for LXC user (UID 100000) -# - Warns if usage is close to limits and suggests sysctl tuning -# - Exits if thresholds are exceeded -# - https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html -# ------------------------------------------------------------------------------ - -maxkeys_check() { - # Read kernel parameters - per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0) - per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0) - - # Exit if kernel parameters are unavailable - if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then - echo -e "${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}" - exit 1 - fi - - # Fetch key usage for user ID 100000 (typical for containers) - used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0) - used_lxc_bytes=$(awk '/100000:/ {split($5, a, "/"); print a[1]}' /proc/key-users 2>/dev/null || echo 0) - - # Calculate thresholds and suggested new limits - threshold_keys=$((per_user_maxkeys - 100)) - threshold_bytes=$((per_user_maxbytes - 1000)) - new_limit_keys=$((per_user_maxkeys * 2)) - new_limit_bytes=$((per_user_maxbytes * 2)) - - # Check if key or byte usage is near limits - failure=0 - if [[ "$used_lxc_keys" -gt "$threshold_keys" ]]; then - echo -e "${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}" - echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}." - failure=1 - fi - if [[ "$used_lxc_bytes" -gt "$threshold_bytes" ]]; then - echo -e "${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}" - echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}." - failure=1 - fi - - # Provide next steps if issues are detected - if [[ "$failure" -eq 1 ]]; then - echo -e "${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}" - exit 1 - fi - - echo -e "${CM}${GN} All kernel key limits are within safe thresholds.${CL}" -} - -# ------------------------------------------------------------------------------ -# get_current_ip() -# -# - Returns current container IP depending on OS type -# - Debian/Ubuntu: uses `hostname -I` -# - Alpine: parses eth0 via `ip -4 addr` -# ------------------------------------------------------------------------------ -get_current_ip() { - if [ -f /etc/os-release ]; then - # Check for Debian/Ubuntu (uses hostname -I) - if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then - CURRENT_IP=$(hostname -I | awk '{print $1}') - # Check for Alpine (uses ip command) - elif grep -q 'ID=alpine' /etc/os-release; then - CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1) - else - CURRENT_IP="Unknown" - fi - fi - echo "$CURRENT_IP" -} - -# ------------------------------------------------------------------------------ -# update_motd_ip() -# -# - Updates /etc/motd with current container IP -# - Removes old IP entries to avoid duplicates -# ------------------------------------------------------------------------------ -update_motd_ip() { - MOTD_FILE="/etc/motd" - - if [ -f "$MOTD_FILE" ]; then - # Remove existing IP Address lines to prevent duplication - sed -i '/IP Address:/d' "$MOTD_FILE" - - IP=$(get_current_ip) - # Add the new IP address - echo -e "${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}" >>"$MOTD_FILE" - fi -} - -# ------------------------------------------------------------------------------ -# ssh_check() -# -# - Detects if script is running over SSH -# - Warns user and recommends using Proxmox shell -# - User can choose to continue or abort -# ------------------------------------------------------------------------------ -ssh_check() { - if [ -n "${SSH_CLIENT:+x}" ]; then - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's advisable to utilize the Proxmox shell rather than SSH, as there may be potential complications with variable retrieval. Proceed using SSH?" 10 72; then - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Proceed using SSH" "You've chosen to proceed using SSH. If any issues arise, please run the script in the Proxmox shell before creating a repository issue." 10 72 - else - clear - echo "Exiting due to SSH usage. Please consider using the Proxmox shell." - exit - fi - fi -} - -# ------------------------------------------------------------------------------ -# install_ssh_keys_into_ct() -# -# - Installs SSH keys into container root account if SSH is enabled -# - Uses pct push or direct input to authorized_keys -# - Falls back to warning if no keys provided -# ------------------------------------------------------------------------------ -install_ssh_keys_into_ct() { - [[ "$SSH" != "yes" ]] && return 0 - - if [[ -n "$SSH_KEYS_FILE" && -s "$SSH_KEYS_FILE" ]]; then - msg_info "Installing selected SSH keys into CT ${CTID}" - pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || { - msg_error "prepare /root/.ssh failed" - return 1 - } - pct push "$CTID" "$SSH_KEYS_FILE" /root/.ssh/authorized_keys >/dev/null 2>&1 || - pct exec "$CTID" -- sh -c "cat > /root/.ssh/authorized_keys" <"$SSH_KEYS_FILE" || { - msg_error "write authorized_keys failed" - return 1 - } - pct exec "$CTID" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true - msg_ok "Installed SSH keys into CT ${CTID}" - return 0 - fi - - # Fallback: nichts ausgewählt - msg_warn "No SSH keys to install (skipping)." - return 0 -} - -# ------------------------------------------------------------------------------ -# base_settings() -# -# - Defines all base/default variables for container creation -# - Reads from environment variables (var_*) -# - Provides fallback defaults for OS type/version -# ------------------------------------------------------------------------------ -base_settings() { - # Default Settings - CT_TYPE=${var_unprivileged:-"1"} - DISK_SIZE=${var_disk:-"4"} - CORE_COUNT=${var_cpu:-"1"} - RAM_SIZE=${var_ram:-"1024"} - VERBOSE=${var_verbose:-"${1:-no}"} - PW=${var_pw:-""} - CT_ID=${var_ctid:-$NEXTID} - HN=${var_hostname:-$NSAPP} - BRG=${var_brg:-"vmbr0"} - NET=${var_net:-"dhcp"} - IPV6_METHOD=${var_ipv6_method:-"none"} - IPV6_STATIC=${var_ipv6_static:-""} - GATE=${var_gateway:-""} - APT_CACHER=${var_apt_cacher:-""} - APT_CACHER_IP=${var_apt_cacher_ip:-""} - MTU=${var_mtu:-""} - SD=${var_storage:-""} - NS=${var_ns:-""} - MAC=${var_mac:-""} - VLAN=${var_vlan:-""} - SSH=${var_ssh:-"no"} - SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-""} - UDHCPC_FIX=${var_udhcpc_fix:-""} - TAGS="community-script,${var_tags:-}" - ENABLE_FUSE=${var_fuse:-"${1:-no}"} - ENABLE_TUN=${var_tun:-"${1:-no}"} - - # Since these 2 are only defined outside of default_settings function, we add a temporary fallback. TODO: To align everything, we should add these as constant variables (e.g. OSTYPE and OSVERSION), but that would currently require updating the default_settings function for all existing scripts - if [ -z "$var_os" ]; then - var_os="debian" - fi - if [ -z "$var_version" ]; then - var_version="12" - fi -} - -# ------------------------------------------------------------------------------ -# echo_default() -# -# - Prints summary of default values (ID, OS, type, disk, RAM, CPU, etc.) -# - Uses icons and formatting for readability -# - Convert CT_TYPE to description -# ------------------------------------------------------------------------------ -echo_default() { - CT_TYPE_DESC="Unprivileged" - if [ "$CT_TYPE" -eq 0 ]; then - CT_TYPE_DESC="Privileged" - fi - - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}" - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" - if [ "$VERBOSE" == "yes" ]; then - echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}" - fi - echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}" - echo -e " " -} - -# ------------------------------------------------------------------------------ -# exit_script() -# -# - Called when user cancels an action -# - Clears screen and exits gracefully -# ------------------------------------------------------------------------------ -exit_script() { - clear - echo -e "\n${CROSS}${RD}User exited script${CL}\n" - exit -} - -# ------------------------------------------------------------------------------ -# find_host_ssh_keys() -# -# - Scans system for available SSH keys -# - Supports defaults (~/.ssh, /etc/ssh/authorized_keys) -# - Returns list of files containing valid SSH public keys -# - Sets FOUND_HOST_KEY_COUNT to number of keys found -# ------------------------------------------------------------------------------ -find_host_ssh_keys() { - local re='(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))' - local -a files=() cand=() - local g="${var_ssh_import_glob:-}" - local total=0 f base c - - shopt -s nullglob - if [[ -n "$g" ]]; then - for pat in $g; do cand+=($pat); done - else - cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2) - cand+=(/root/.ssh/*.pub) - cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*) - fi - shopt -u nullglob - - for f in "${cand[@]}"; do - [[ -f "$f" && -r "$f" ]] || continue - base="$(basename -- "$f")" - case "$base" in - known_hosts | known_hosts.* | config) continue ;; - id_*) [[ "$f" != *.pub ]] && continue ;; - esac - - # CRLF safe check for host keys - c=$(tr -d '\r' <"$f" | awk ' - /^[[:space:]]*#/ {next} - /^[[:space:]]*$/ {next} - {print} - ' | grep -E -c '"$re"' || true) - - if ((c > 0)); then - files+=("$f") - total=$((total + c)) - fi - done - - # Fallback to /root/.ssh/authorized_keys - if ((${#files[@]} == 0)) && [[ -r /root/.ssh/authorized_keys ]]; then - if grep -E -q "$re" /root/.ssh/authorized_keys; then - files+=(/root/.ssh/authorized_keys) - total=$((total + $(grep -E -c "$re" /root/.ssh/authorized_keys || echo 0))) - fi - fi - - FOUND_HOST_KEY_COUNT="$total" - ( - IFS=: - echo "${files[*]}" - ) -} - -# ------------------------------------------------------------------------------ -# advanced_settings() -# -# - Interactive whiptail menu for advanced configuration -# - Lets user set container type, password, CT ID, hostname, disk, CPU, RAM -# - Supports IPv4/IPv6, DNS, MAC, VLAN, tags, SSH keys, FUSE, verbose mode -# - Ends with confirmation or re-entry if cancelled -# ------------------------------------------------------------------------------ -advanced_settings() { - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Here is an instructional tip:" "To make a selection, use the Spacebar." 8 58 - # Setting Default Tag for Advanced Settings - TAGS="community-script;${var_tags:-}" - CT_DEFAULT_TYPE="${CT_TYPE}" - CT_TYPE="" - while [ -z "$CT_TYPE" ]; do - if [ "$CT_DEFAULT_TYPE" == "1" ]; then - if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \ - "1" "Unprivileged" ON \ - "0" "Privileged" OFF \ - 3>&1 1>&2 2>&3); then - if [ -n "$CT_TYPE" ]; then - CT_TYPE_DESC="Unprivileged" - if [ "$CT_TYPE" -eq 0 ]; then - CT_TYPE_DESC="Privileged" - fi - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os | ${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - fi - else - exit_script - fi - fi - if [ "$CT_DEFAULT_TYPE" == "0" ]; then - if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \ - "1" "Unprivileged" OFF \ - "0" "Privileged" ON \ - 3>&1 1>&2 2>&3); then - if [ -n "$CT_TYPE" ]; then - CT_TYPE_DESC="Unprivileged" - if [ "$CT_TYPE" -eq 0 ]; then - CT_TYPE_DESC="Privileged" - fi - echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" - echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" - fi - else - exit_script - fi - fi - done - - while true; do - if PW1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nSet Root Password (needed for root ssh access)" 9 58 --title "PASSWORD (leave blank for automatic login)" 3>&1 1>&2 2>&3); then - # Empty = Autologin - if [[ -z "$PW1" ]]; then - PW="" - PW1="Automatic Login" - echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}" - break - fi - - # Invalid: contains spaces - if [[ "$PW1" == *" "* ]]; then - whiptail --msgbox "Password cannot contain spaces." 8 58 - continue - fi - - # Invalid: too short - if ((${#PW1} < 5)); then - whiptail --msgbox "Password must be at least 5 characters." 8 58 - continue - fi - - # Confirm password - if PW2=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nVerify Root Password" 9 58 --title "PASSWORD VERIFICATION" 3>&1 1>&2 2>&3); then - if [[ "$PW1" == "$PW2" ]]; then - PW="-password $PW1" - echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}" - break - else - whiptail --msgbox "Passwords do not match. Please try again." 8 58 - fi - else - exit_script - fi - else - exit_script - fi - done - - if CT_ID=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Container ID" 8 58 "$NEXTID" --title "CONTAINER ID" 3>&1 1>&2 2>&3); then - if [ -z "$CT_ID" ]; then - CT_ID="$NEXTID" - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - else - echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" - fi - else - exit_script - fi - - while true; do - if CT_NAME=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then - if [ -z "$CT_NAME" ]; then - HN="$NSAPP" - else - HN=$(echo "${CT_NAME,,}" | tr -d ' ') - fi - # Hostname validate (RFC 1123) - if [[ "$HN" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then - echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" - break - else - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --msgbox "❌ Invalid hostname: '$HN'\n\nOnly lowercase letters, digits and hyphens (-) are allowed.\nUnderscores (_) or other characters are not permitted!" 10 70 - fi - else - exit_script - fi - done - - while true; do - DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GB" 8 58 "$var_disk" --title "DISK SIZE" 3>&1 1>&2 2>&3) || exit_script - - if [ -z "$DISK_SIZE" ]; then - DISK_SIZE="$var_disk" - fi - - if [[ "$DISK_SIZE" =~ ^[1-9][0-9]*$ ]]; then - echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" - break - else - whiptail --msgbox "Disk size must be a positive integer!" 8 58 - fi - done - - while true; do - CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ - --inputbox "Allocate CPU Cores" 8 58 "$var_cpu" --title "CORE COUNT" 3>&1 1>&2 2>&3) || exit_script - - if [ -z "$CORE_COUNT" ]; then - CORE_COUNT="$var_cpu" - fi - - if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then - echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" - break - else - whiptail --msgbox "CPU core count must be a positive integer!" 8 58 - fi - done - - while true; do - RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ - --inputbox "Allocate RAM in MiB" 8 58 "$var_ram" --title "RAM" 3>&1 1>&2 2>&3) || exit_script - - if [ -z "$RAM_SIZE" ]; then - RAM_SIZE="$var_ram" - fi - - if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then - echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" - break - else - whiptail --msgbox "RAM size must be a positive integer!" 8 58 - fi - done - - IFACE_FILEPATH_LIST="/etc/network/interfaces"$'\n'$(find "/etc/network/interfaces.d/" -type f) - BRIDGES="" - OLD_IFS=$IFS - IFS=$'\n' - for iface_filepath in ${IFACE_FILEPATH_LIST}; do - - iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX') - (grep -Pn '^\s*iface' "${iface_filepath}" | cut -d':' -f1 && wc -l "${iface_filepath}" | cut -d' ' -f1) | awk 'FNR==1 {line=$0; next} {print line":"$0-1; line=$0}' >"${iface_indexes_tmpfile}" || true - - if [ -f "${iface_indexes_tmpfile}" ]; then - - while read -r pair; do - start=$(echo "${pair}" | cut -d':' -f1) - end=$(echo "${pair}" | cut -d':' -f2) - - if awk "NR >= ${start} && NR <= ${end}" "${iface_filepath}" | grep -qP '^\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\s+OVSBridge)\b'; then - iface_name=$(sed "${start}q;d" "${iface_filepath}" | awk '{print $2}') - BRIDGES="${iface_name}"$'\n'"${BRIDGES}" - fi - - done <"${iface_indexes_tmpfile}" - rm -f "${iface_indexes_tmpfile}" - fi - - done - IFS=$OLD_IFS - BRIDGES=$(echo "$BRIDGES" | grep -v '^\s*$' | sort | uniq) - if [[ -z "$BRIDGES" ]]; then - BRG="vmbr0" - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - else - BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --menu "Select network bridge:" 15 40 6 $(echo "$BRIDGES" | awk '{print $0, "Bridge"}') 3>&1 1>&2 2>&3) - if [[ -z "$BRG" ]]; then - exit_script - else - echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" - fi - fi - - # IPv4 methods: dhcp, static, none - while true; do - IPV4_METHOD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --title "IPv4 Address Management" \ - --menu "Select IPv4 Address Assignment Method:" 12 60 2 \ - "dhcp" "Automatic (DHCP, recommended)" \ - "static" "Static (manual entry)" \ - 3>&1 1>&2 2>&3) - - exit_status=$? - if [ $exit_status -ne 0 ]; then - exit_script - fi - - case "$IPV4_METHOD" in - dhcp) - NET="dhcp" - GATE="" - echo -e "${NETWORK}${BOLD}${DGN}IPv4: DHCP${CL}" - break - ;; - static) - # Static: call and validate CIDR address - while true; do - NET=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --inputbox "Enter Static IPv4 CIDR Address (e.g. 192.168.100.50/24)" 8 58 "" \ - --title "IPv4 ADDRESS" 3>&1 1>&2 2>&3) - if [ -z "$NET" ]; then - whiptail --msgbox "IPv4 address must not be empty." 8 58 - continue - elif [[ "$NET" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then - echo -e "${NETWORK}${BOLD}${DGN}IPv4 Address: ${BGN}$NET${CL}" - break - else - whiptail --msgbox "$NET is not a valid IPv4 CIDR address. Please enter a correct value!" 8 58 - fi - done - - # call and validate Gateway - while true; do - GATE1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --inputbox "Enter Gateway IP address for static IPv4" 8 58 "" \ - --title "Gateway IP" 3>&1 1>&2 2>&3) - if [ -z "$GATE1" ]; then - whiptail --msgbox "Gateway IP address cannot be empty." 8 58 - elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then - whiptail --msgbox "Invalid Gateway IP address format." 8 58 - else - GATE=",gw=$GATE1" - echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}" - break - fi - done - break - ;; - esac - done - - # IPv6 Address Management selection - while true; do - IPV6_METHOD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --menu \ - "Select IPv6 Address Management Type:" 15 58 4 \ - "auto" "SLAAC/AUTO (recommended, default)" \ - "dhcp" "DHCPv6" \ - "static" "Static (manual entry)" \ - "none" "Disabled" \ - --default-item "auto" 3>&1 1>&2 2>&3) - [ $? -ne 0 ] && exit_script - - case "$IPV6_METHOD" in - auto) - echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}SLAAC/AUTO${CL}" - IPV6_ADDR="" - IPV6_GATE="" - break - ;; - dhcp) - echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}DHCPv6${CL}" - IPV6_ADDR="dhcp" - IPV6_GATE="" - break - ;; - static) - # Ask for static IPv6 address (CIDR notation, e.g., 2001:db8::1234/64) - while true; do - IPV6_ADDR=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox \ - "Set a static IPv6 CIDR address (e.g., 2001:db8::1234/64)" 8 58 "" \ - --title "IPv6 STATIC ADDRESS" 3>&1 1>&2 2>&3) || exit_script - if [[ "$IPV6_ADDR" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+(/[0-9]{1,3})$ ]]; then - echo -e "${NETWORK}${BOLD}${DGN}IPv6 Address: ${BGN}$IPV6_ADDR${CL}" - break - else - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox \ - "$IPV6_ADDR is an invalid IPv6 CIDR address. Please enter a valid IPv6 CIDR address (e.g., 2001:db8::1234/64)" 8 58 - fi - done - # Optional: ask for IPv6 gateway for static config - while true; do - IPV6_GATE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox \ - "Enter IPv6 gateway address (optional, leave blank for none)" 8 58 "" --title "IPv6 GATEWAY" 3>&1 1>&2 2>&3) - if [ -z "$IPV6_GATE" ]; then - IPV6_GATE="" - break - elif [[ "$IPV6_GATE" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+$ ]]; then - break - else - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox \ - "Invalid IPv6 gateway format." 8 58 - fi - done - break - ;; - none) - echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}Disabled${CL}" - IPV6_ADDR="none" - IPV6_GATE="" - break - ;; - *) - exit_script - ;; - esac - done - - if [ "$var_os" == "alpine" ]; then - APT_CACHER="" - APT_CACHER_IP="" - else - if APT_CACHER_IP=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 --title "APT-Cacher IP" 3>&1 1>&2 2>&3); then - APT_CACHER="${APT_CACHER_IP:+yes}" - echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}" - else - exit_script - fi - fi - - # if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "IPv6" --yesno "Disable IPv6?" 10 58); then - # DISABLEIP6="yes" - # else - # DISABLEIP6="no" - # fi - # echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}$DISABLEIP6${CL}" - - if MTU1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])" 8 58 --title "MTU SIZE" 3>&1 1>&2 2>&3); then - if [ -z "$MTU1" ]; then - MTU1="Default" - MTU="" - else - MTU=",mtu=$MTU1" - fi - echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" - else - exit_script - fi - - if SD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 --title "DNS Search Domain" 3>&1 1>&2 2>&3); then - if [ -z "$SD" ]; then - SX=Host - SD="" - else - SX=$SD - SD="-searchdomain=$SD" - fi - echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}" - else - exit_script - fi - - if NX=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 --title "DNS SERVER IP" 3>&1 1>&2 2>&3); then - if [ -z "$NX" ]; then - NX=Host - NS="" - else - NS="-nameserver=$NX" - fi - echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}" - else - exit_script - fi - - if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ "$NX" != "Host" ]; then - UDHCPC_FIX="yes" - else - UDHCPC_FIX="no" - fi - export UDHCPC_FIX - - if MAC1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a MAC Address(leave blank for generated MAC)" 8 58 --title "MAC ADDRESS" 3>&1 1>&2 2>&3); then - if [ -z "$MAC1" ]; then - MAC1="Default" - MAC="" - else - MAC=",hwaddr=$MAC1" - echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" - fi - else - exit_script - fi - - if VLAN1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for no VLAN)" 8 58 --title "VLAN" 3>&1 1>&2 2>&3); then - if [ -z "$VLAN1" ]; then - VLAN1="Default" - VLAN="" - else - VLAN=",tag=$VLAN1" - fi - echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}" - else - exit_script - fi - - if ADV_TAGS=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Custom Tags?[If you remove all, there will be no tags!]" 8 58 "${TAGS}" --title "Advanced Tags" 3>&1 1>&2 2>&3); then - if [ -n "${ADV_TAGS}" ]; then - ADV_TAGS=$(echo "$ADV_TAGS" | tr -d '[:space:]') - TAGS="${ADV_TAGS}" - else - TAGS=";" - fi - echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}" - else - exit_script - fi - - # --- SSH key provisioning (one dialog) --- - SSH_KEYS_FILE="$(mktemp)" - : >"$SSH_KEYS_FILE" - - IFS=$'\0' read -r -d '' -a _def_files < <(ssh_discover_default_files && printf '\0') - ssh_build_choices_from_files "${_def_files[@]}" - DEF_KEYS_COUNT="$COUNT" - - if [[ "$DEF_KEYS_COUNT" -gt 0 ]]; then - SSH_KEY_MODE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SSH KEY SOURCE" --menu \ - "Provision SSH keys for root:" 14 72 4 \ - "found" "Select from detected keys (${DEF_KEYS_COUNT})" \ - "manual" "Paste a single public key" \ - "folder" "Scan another folder (path or glob)" \ - "none" "No keys" 3>&1 1>&2 2>&3) || exit_script - else - SSH_KEY_MODE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SSH KEY SOURCE" --menu \ - "No host keys detected; choose manual/none:" 12 72 2 \ - "manual" "Paste a single public key" \ - "none" "No keys" 3>&1 1>&2 2>&3) || exit_script - fi - - case "$SSH_KEY_MODE" in - found) - SEL=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SELECT HOST KEYS" \ - --checklist "Select one or more keys to import:" 20 140 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script - for tag in $SEL; do - tag="${tag%\"}" - tag="${tag#\"}" - line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-) - [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE" - done - ;; - manual) - SSH_AUTHORIZED_KEY="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --inputbox "Paste one SSH public key line (ssh-ed25519/ssh-rsa/...)" 10 72 --title "SSH Public Key" 3>&1 1>&2 2>&3)" - [[ -n "$SSH_AUTHORIZED_KEY" ]] && printf '%s\n' "$SSH_AUTHORIZED_KEY" >>"$SSH_KEYS_FILE" - ;; - folder) - GLOB_PATH="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --inputbox "Enter a folder or glob to scan (e.g. /root/.ssh/*.pub)" 10 72 --title "Scan Folder/Glob" 3>&1 1>&2 2>&3)" - if [[ -n "$GLOB_PATH" ]]; then - shopt -s nullglob - read -r -a _scan_files <<<"$GLOB_PATH" - shopt -u nullglob - if [[ "${#_scan_files[@]}" -gt 0 ]]; then - ssh_build_choices_from_files "${_scan_files[@]}" - if [[ "$COUNT" -gt 0 ]]; then - SEL=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SELECT FOLDER KEYS" \ - --checklist "Select key(s) to import:" 20 78 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script - for tag in $SEL; do - tag="${tag%\"}" - tag="${tag#\"}" - line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-) - [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE" - done - else - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "No keys found in: $GLOB_PATH" 8 60 - fi - else - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "Path/glob returned no files." 8 60 - fi - fi - ;; - none) : ;; - esac - - # Dedupe + clean EOF - if [[ -s "$SSH_KEYS_FILE" ]]; then - sort -u -o "$SSH_KEYS_FILE" "$SSH_KEYS_FILE" - printf '\n' >>"$SSH_KEYS_FILE" - fi - - # SSH activate, if keys found or password set - if [[ -s "$SSH_KEYS_FILE" || "$PW" == -password* ]]; then - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH ACCESS" --yesno "Enable root SSH access?" 10 58); then - SSH="yes" - else - SSH="no" - fi - else - SSH="no" - fi - echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - - export SSH_KEYS_FILE - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "FUSE Support" --yesno "Enable FUSE support?\nRequired for tools like rclone, mergerfs, AppImage, etc." 10 58); then - ENABLE_FUSE="yes" - else - ENABLE_FUSE="no" - fi - echo -e "${FUSE}${BOLD}${DGN}Enable FUSE Support: ${BGN}$ENABLE_FUSE${CL}" - - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then - VERBOSE="yes" - else - VERBOSE="no" - fi - echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}" - - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then - echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above advanced settings${CL}" - else - clear - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" - advanced_settings - fi -} - -# ------------------------------------------------------------------------------ -# diagnostics_check() -# -# - Ensures diagnostics config file exists at /usr/local/community-scripts/diagnostics -# - Asks user whether to send anonymous diagnostic data -# - Saves DIAGNOSTICS=yes/no in the config file -# ------------------------------------------------------------------------------ -diagnostics_check() { - if ! [ -d "/usr/local/community-scripts" ]; then - mkdir -p /usr/local/community-scripts - fi - - if ! [ -f "/usr/local/community-scripts/diagnostics" ]; then - if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS" --yesno "Send Diagnostics of LXC Installation?\n\n(This only transmits data without user data, just RAM, CPU, LXC name, ...)" 10 58); then - cat </usr/local/community-scripts/diagnostics -DIAGNOSTICS=yes - -#This file is used to store the diagnostics settings for the Community-Scripts API. -#https://github.com/community-scripts/ProxmoxVED/discussions/1836 -#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes. -#You can review the data at https://community-scripts.github.io/ProxmoxVE/data -#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will disable the diagnostics feature. -#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will enable the diagnostics feature. -#The following information will be sent: -#"ct_type" -#"disk_size" -#"core_count" -#"ram_size" -#"os_type" -#"os_version" -#"disableip6" -#"nsapp" -#"method" -#"pve_version" -#"status" -#If you have any concerns, please review the source code at /misc/build.func -EOF - DIAGNOSTICS="yes" - else - cat </usr/local/community-scripts/diagnostics -DIAGNOSTICS=no - -#This file is used to store the diagnostics settings for the Community-Scripts API. -#https://github.com/community-scripts/ProxmoxVED/discussions/1836 -#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes. -#You can review the data at https://community-scripts.github.io/ProxmoxVE/data -#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will disable the diagnostics feature. -#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue. -#This will enable the diagnostics feature. -#The following information will be sent: -#"ct_type" -#"disk_size" -#"core_count" -#"ram_size" -#"os_type" -#"os_version" -#"disableip6" -#"nsapp" -#"method" -#"pve_version" -#"status" -#If you have any concerns, please review the source code at /misc/build.func -EOF - DIAGNOSTICS="no" - fi - else - DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' /usr/local/community-scripts/diagnostics) - - fi - -} - -# ------------------------------------------------------------------------------ -# default_var_settings -# -# - Ensures /usr/local/community-scripts/default.vars exists (creates if missing) -# - Loads var_* values from default.vars (safe parser, no source/eval) -# - Precedence: ENV var_* > default.vars > built-in defaults -# - Maps var_verbose → VERBOSE -# - Calls base_settings "$VERBOSE" and echo_default -# ------------------------------------------------------------------------------ -default_var_settings() { - # Allowed var_* keys (alphabetically sorted) - local VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_ctid var_disk var_fuse - var_gateway var_hostname var_ipv6_method var_ipv6_static var_mac var_mtu - var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged - var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage - ) - - # Snapshot: environment variables (highest precedence) - declare -A _HARD_ENV=() - local _k - for _k in "${VAR_WHITELIST[@]}"; do - if printenv "$_k" >/dev/null 2>&1; then _HARD_ENV["$_k"]=1; fi - done - - # Find default.vars location - local _find_default_vars - _find_default_vars() { - local f - for f in \ - /usr/local/community-scripts/default.vars \ - "$HOME/.config/community-scripts/default.vars" \ - "./default.vars"; do - [ -f "$f" ] && { - echo "$f" - return 0 - } - done - return 1 - } - # Allow override of storages via env (for non-interactive use cases) - [ -n "${var_template_storage:-}" ] && TEMPLATE_STORAGE="$var_template_storage" - [ -n "${var_container_storage:-}" ] && CONTAINER_STORAGE="$var_container_storage" - - # Create once, with storages already selected, no var_ctid/var_hostname lines - local _ensure_default_vars - _ensure_default_vars() { - _find_default_vars >/dev/null 2>&1 && return 0 - - local canonical="/usr/local/community-scripts/default.vars" - msg_info "No default.vars found. Creating ${canonical}" - mkdir -p /usr/local/community-scripts - - # Pick storages before writing the file (always ask unless only one) - # Create a minimal temp file to write into - : >"$canonical" - - # Base content (no var_ctid / var_hostname here) - cat >"$canonical" <<'EOF' -# Community-Scripts defaults (var_* only). Lines starting with # are comments. -# Precedence: ENV var_* > default.vars > built-ins. -# Keep keys alphabetically sorted. - -# Container type -var_unprivileged=1 - -# Resources -var_cpu=1 -var_disk=4 -var_ram=1024 - -# Network -var_brg=vmbr0 -var_net=dhcp -var_ipv6_method=none -# var_gateway= -# var_ipv6_static= -# var_vlan= -# var_mtu= -# var_mac= -# var_ns= - -# SSH -var_ssh=no -# var_ssh_authorized_key= - -# APT cacher (optional) -# var_apt_cacher=yes -# var_apt_cacher_ip=192.168.1.10 - -# Features/Tags/verbosity -var_fuse=no -var_tun=no -var_tags=community-script -var_verbose=no - -# Security (root PW) – empty => autologin -# var_pw= -EOF - - # Now choose storages (always prompt unless just one exists) - choose_and_set_storage_for_file "$canonical" template - choose_and_set_storage_for_file "$canonical" container - - chmod 0644 "$canonical" - msg_ok "Created ${canonical}" - } - - # Whitelist check - local _is_whitelisted_key - _is_whitelisted_key() { - local k="$1" - local w - for w in "${VAR_WHITELIST[@]}"; do [ "$k" = "$w" ] && return 0; done - return 1 - } - - # Safe parser for KEY=VALUE lines - local _load_vars_file - _load_vars_file() { - local file="$1" - [ -f "$file" ] || return 0 - msg_info "Loading defaults from ${file}" - local line key val - while IFS= read -r line || [ -n "$line" ]; do - line="${line#"${line%%[![:space:]]*}"}" - line="${line%"${line##*[![:space:]]}"}" - [[ -z "$line" || "$line" == \#* ]] && continue - if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then - local var_key="${BASH_REMATCH[1]}" - local var_val="${BASH_REMATCH[2]}" - - [[ "$var_key" != var_* ]] && continue - _is_whitelisted_key "$var_key" || { - msg_debug "Ignore non-whitelisted ${var_key}" - continue - } - - # Strip quotes - if [[ "$var_val" =~ ^\"(.*)\"$ ]]; then - var_val="${BASH_REMATCH[1]}" - elif [[ "$var_val" =~ ^\'(.*)\'$ ]]; then - var_val="${BASH_REMATCH[1]}" - fi - - # Unsafe characters - case $var_val in - \"*\") - var_val=${var_val#\"} - var_val=${var_val%\"} - ;; - \'*\') - var_val=${var_val#\'} - var_val=${var_val%\'} - ;; - esac # Hard env wins - [[ -n "${_HARD_ENV[$var_key]:-}" ]] && continue - # Set only if not already exported - [[ -z "${!var_key+x}" ]] && export "${var_key}=${var_val}" - else - msg_warn "Malformed line in ${file}: ${line}" - fi - done <"$file" - msg_ok "Loaded ${file}" - } - - # 1) Ensure file exists - _ensure_default_vars - - # 2) Load file - local dv - dv="$(_find_default_vars)" || { - msg_error "default.vars not found after ensure step" - return 1 - } - _load_vars_file "$dv" - - # 3) Map var_verbose → VERBOSE - if [[ -n "${var_verbose:-}" ]]; then - case "${var_verbose,,}" in 1 | yes | true | on) VERBOSE="yes" ;; 0 | no | false | off) VERBOSE="no" ;; *) VERBOSE="${var_verbose}" ;; esac - else - VERBOSE="no" - fi - - # 4) Apply base settings and show summary - METHOD="mydefaults-global" - base_settings "$VERBOSE" - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using My Defaults (default.vars) on node $PVEHOST_NAME${CL}" - echo_default -} - -# ------------------------------------------------------------------------------ -# get_app_defaults_path() -# -# - Returns full path for app-specific defaults file -# - Example: /usr/local/community-scripts/defaults/.vars -# ------------------------------------------------------------------------------ - -get_app_defaults_path() { - local n="${NSAPP:-${APP,,}}" - echo "/usr/local/community-scripts/defaults/${n}.vars" -} - -# ------------------------------------------------------------------------------ -# maybe_offer_save_app_defaults -# -# - Called after advanced_settings returned with fully chosen values. -# - If no .vars exists, offers to persist current advanced settings -# into /usr/local/community-scripts/defaults/.vars -# - Only writes whitelisted var_* keys. -# - Extracts raw values from flags like ",gw=..." ",mtu=..." etc. -# ------------------------------------------------------------------------------ -if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then - declare -ag VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_ctid var_disk var_fuse - var_gateway var_hostname var_ipv6_method var_ipv6_static var_mac var_mtu - var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged - var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage - ) -fi - -_is_whitelisted_key() { - local k="$1" - local w - for w in "${VAR_WHITELIST[@]}"; do [[ "$k" == "$w" ]] && return 0; done - return 1 -} - -_sanitize_value() { - # Disallow Command-Substitution / Shell-Meta - case "$1" in - *'$('* | *'`'* | *';'* | *'&'* | *'<('*) - echo "" - return 0 - ;; - esac - echo "$1" -} - -# Map-Parser: read var_* from file into _VARS_IN associative array -declare -A _VARS_IN -_load_vars_file() { - local file="$1" - [ -f "$file" ] || return 0 - msg_info "Loading defaults from ${file}" - local line key val - while IFS= read -r line || [ -n "$line" ]; do - line="${line#"${line%%[![:space:]]*}"}" - line="${line%"${line##*[![:space:]]}"}" - [ -z "$line" ] && continue - case "$line" in - \#*) continue ;; - esac - key=$(printf "%s" "$line" | cut -d= -f1) - val=$(printf "%s" "$line" | cut -d= -f2-) - case "$key" in - var_*) - if _is_whitelisted_key "$key"; then - [ -z "${!key+x}" ] && export "$key=$val" - fi - ;; - esac - done <"$file" - msg_ok "Loaded ${file}" -} - -# Diff function for two var_* files -> produces human-readable diff list for $1 (old) vs $2 (new) -_build_vars_diff() { - local oldf="$1" newf="$2" - local k - local -A OLD=() NEW=() - _load_vars_file_to_map "$oldf" - for k in "${!_VARS_IN[@]}"; do OLD["$k"]="${_VARS_IN[$k]}"; done - _load_vars_file_to_map "$newf" - for k in "${!_VARS_IN[@]}"; do NEW["$k"]="${_VARS_IN[$k]}"; done - - local out - out+="# Diff for ${APP} (${NSAPP})\n" - out+="# Old: ${oldf}\n# New: ${newf}\n\n" - - local found_change=0 - - # Changed & Removed - for k in "${!OLD[@]}"; do - if [[ -v NEW["$k"] ]]; then - if [[ "${OLD[$k]}" != "${NEW[$k]}" ]]; then - out+="~ ${k}\n - old: ${OLD[$k]}\n + new: ${NEW[$k]}\n" - found_change=1 - fi - else - out+="- ${k}\n - old: ${OLD[$k]}\n" - found_change=1 - fi - done - - # Added - for k in "${!NEW[@]}"; do - if [[ ! -v OLD["$k"] ]]; then - out+="+ ${k}\n + new: ${NEW[$k]}\n" - found_change=1 - fi - done - - if [[ $found_change -eq 0 ]]; then - out+="(No differences)\n" - fi - - printf "%b" "$out" -} - -# Build a temporary .vars file from current advanced settings -_build_current_app_vars_tmp() { - tmpf="$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)" - - # NET/GW - _net="${NET:-}" - _gate="" - case "${GATE:-}" in - ,gw=*) _gate=$(echo "$GATE" | sed 's/^,gw=//') ;; - esac - - # IPv6 - _ipv6_method="${IPV6_METHOD:-auto}" - _ipv6_static="" - _ipv6_gateway="" - if [ "$_ipv6_method" = "static" ]; then - _ipv6_static="${IPV6_ADDR:-}" - _ipv6_gateway="${IPV6_GATE:-}" - fi - - # MTU/VLAN/MAC - _mtu="" - _vlan="" - _mac="" - case "${MTU:-}" in - ,mtu=*) _mtu=$(echo "$MTU" | sed 's/^,mtu=//') ;; - esac - case "${VLAN:-}" in - ,tag=*) _vlan=$(echo "$VLAN" | sed 's/^,tag=//') ;; - esac - case "${MAC:-}" in - ,hwaddr=*) _mac=$(echo "$MAC" | sed 's/^,hwaddr=//') ;; - esac - - # DNS / Searchdomain - _ns="" - _searchdomain="" - case "${NS:-}" in - -nameserver=*) _ns=$(echo "$NS" | sed 's/^-nameserver=//') ;; - esac - case "${SD:-}" in - -searchdomain=*) _searchdomain=$(echo "$SD" | sed 's/^-searchdomain=//') ;; - esac - - # SSH / APT / Features - _ssh="${SSH:-no}" - _ssh_auth="${SSH_AUTHORIZED_KEY:-}" - _apt_cacher="${APT_CACHER:-}" - _apt_cacher_ip="${APT_CACHER_IP:-}" - _fuse="${ENABLE_FUSE:-no}" - _tun="${ENABLE_TUN:-no}" - _tags="${TAGS:-}" - _verbose="${VERBOSE:-no}" - - # Type / Resources / Identity - _unpriv="${CT_TYPE:-1}" - _cpu="${CORE_COUNT:-1}" - _ram="${RAM_SIZE:-1024}" - _disk="${DISK_SIZE:-4}" - _hostname="${HN:-$NSAPP}" - - # Storage - _tpl_storage="${TEMPLATE_STORAGE:-${var_template_storage:-}}" - _ct_storage="${CONTAINER_STORAGE:-${var_container_storage:-}}" - - { - echo "# App-specific defaults for ${APP} (${NSAPP})" - echo "# Generated on $(date -u '+%Y-%m-%dT%H:%M:%SZ')" - echo - - echo "var_unprivileged=$(_sanitize_value "$_unpriv")" - echo "var_cpu=$(_sanitize_value "$_cpu")" - echo "var_ram=$(_sanitize_value "$_ram")" - echo "var_disk=$(_sanitize_value "$_disk")" - - [ -n "${BRG:-}" ] && echo "var_brg=$(_sanitize_value "$BRG")" - [ -n "$_net" ] && echo "var_net=$(_sanitize_value "$_net")" - [ -n "$_gate" ] && echo "var_gateway=$(_sanitize_value "$_gate")" - [ -n "$_mtu" ] && echo "var_mtu=$(_sanitize_value "$_mtu")" - [ -n "$_vlan" ] && echo "var_vlan=$(_sanitize_value "$_vlan")" - [ -n "$_mac" ] && echo "var_mac=$(_sanitize_value "$_mac")" - [ -n "$_ns" ] && echo "var_ns=$(_sanitize_value "$_ns")" - - [ -n "$_ipv6_method" ] && echo "var_ipv6_method=$(_sanitize_value "$_ipv6_method")" - [ -n "$_ipv6_static" ] && echo "var_ipv6_static=$(_sanitize_value "$_ipv6_static")" - - [ -n "$_ssh" ] && echo "var_ssh=$(_sanitize_value "$_ssh")" - [ -n "$_ssh_auth" ] && echo "var_ssh_authorized_key=$(_sanitize_value "$_ssh_auth")" - - [ -n "$_apt_cacher" ] && echo "var_apt_cacher=$(_sanitize_value "$_apt_cacher")" - [ -n "$_apt_cacher_ip" ] && echo "var_apt_cacher_ip=$(_sanitize_value "$_apt_cacher_ip")" - - [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")" - [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")" - [ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")" - [ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")" - - [ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")" - [ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")" - - [ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")" - [ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")" - } >"$tmpf" - - echo "$tmpf" -} - -# ------------------------------------------------------------------------------ -# maybe_offer_save_app_defaults() -# -# - Called after advanced_settings() -# - Offers to save current values as app defaults if not existing -# - If file exists: shows diff and allows Update, Keep, View Diff, or Cancel -# ------------------------------------------------------------------------------ -maybe_offer_save_app_defaults() { - local app_vars_path - app_vars_path="$(get_app_defaults_path)" - - # always build from current settings - local new_tmp diff_tmp - new_tmp="$(_build_current_app_vars_tmp)" - diff_tmp="$(mktemp -p /tmp "${NSAPP:-app}.vars.diff.XXXXXX")" - - # 1) if no file → offer to create - if [[ ! -f "$app_vars_path" ]]; then - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --yesno "Save these advanced settings as defaults for ${APP}?\n\nThis will create:\n${app_vars_path}" 12 72; then - mkdir -p "$(dirname "$app_vars_path")" - install -m 0644 "$new_tmp" "$app_vars_path" - msg_ok "Saved app defaults: ${app_vars_path}" - fi - rm -f "$new_tmp" "$diff_tmp" - return 0 - fi - - # 2) if file exists → build diff - _build_vars_diff "$app_vars_path" "$new_tmp" >"$diff_tmp" - - # if no differences → do nothing - if grep -q "^(No differences)$" "$diff_tmp"; then - rm -f "$new_tmp" "$diff_tmp" - return 0 - fi - - # 3) if file exists → show menu with default selection "Update Defaults" - local app_vars_file - app_vars_file="$(basename "$app_vars_path")" - - while true; do - local sel - sel="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --title "APP DEFAULTS – ${APP}" \ - --menu "Differences detected. What do you want to do?" 20 78 10 \ - "Update Defaults" "Write new values to ${app_vars_file}" \ - "Keep Current" "Keep existing defaults (no changes)" \ - "View Diff" "Show a detailed diff" \ - "Cancel" "Abort without changes" \ - --default-item "Update Defaults" \ - 3>&1 1>&2 2>&3)" || { sel="Cancel"; } - - case "$sel" in - "Update Defaults") - install -m 0644 "$new_tmp" "$app_vars_path" - msg_ok "Updated app defaults: ${app_vars_path}" - break - ;; - "Keep Current") - msg_info "Keeping current app defaults: ${app_vars_path}" - break - ;; - "View Diff") - whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --title "Diff – ${APP}" \ - --scrolltext --textbox "$diff_tmp" 25 100 - ;; - "Cancel" | *) - msg_info "Canceled. No changes to app defaults." - break - ;; - esac - done - - rm -f "$new_tmp" "$diff_tmp" -} - -ensure_storage_selection_for_vars_file() { - vf="$1" - - # Read stored values (if any) - tpl=$(grep -E '^var_template_storage=' "$vf" | cut -d= -f2-) - ct=$(grep -E '^var_container_storage=' "$vf" | cut -d= -f2-) - - # Template storage - if [ -n "$tpl" ]; then - TEMPLATE_STORAGE="$tpl" - else - choose_and_set_storage_for_file "$vf" template - fi - - # Container storage - if [ -n "$ct" ]; then - CONTAINER_STORAGE="$ct" - else - choose_and_set_storage_for_file "$vf" container - fi - - echo_storage_summary_from_file "$vf" -} - -diagnostics_menu() { - if [ "${DIAGNOSTICS:-no}" = "yes" ]; then - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --title "DIAGNOSTIC SETTINGS" \ - --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \ - --yes-button "No" --no-button "Back"; then - DIAGNOSTICS="no" - sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics - whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58 - fi - else - if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --title "DIAGNOSTIC SETTINGS" \ - --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \ - --yes-button "Yes" --no-button "Back"; then - DIAGNOSTICS="yes" - sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics - whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58 - fi - fi -} - -# ------------------------------------------------------------------------------ -# install_script() -# -# - Main entrypoint for installation mode -# - Runs safety checks (pve_check, root_check, maxkeys_check, diagnostics_check) -# - Builds interactive menu (Default, Verbose, Advanced, My Defaults, App Defaults, Diagnostics, Storage, Exit) -# - Applies chosen settings and triggers container build -# ------------------------------------------------------------------------------ -install_script() { - pve_check - shell_check - root_check - arch_check - ssh_check - maxkeys_check - diagnostics_check - - if systemctl is-active -q ping-instances.service; then - systemctl -q stop ping-instances.service - fi - - NEXTID=$(pvesh get /cluster/nextid) - timezone=$(cat /etc/timezone) - header_info - - # --- Support CLI argument as direct preset (default, advanced, …) --- - CHOICE="${mode:-${1:-}}" - - # If no CLI argument → show whiptail menu - if [ -z "$CHOICE" ]; then - local menu_items=( - "1" "Default Install" - "2" "Advanced Install" - "3" "My Defaults" - ) - - if [ -f "$(get_app_defaults_path)" ]; then - menu_items+=("4" "App Defaults for ${APP}") - menu_items+=("5" "Settings") - else - menu_items+=("4" "Settings") - fi - - TMP_CHOICE=$(whiptail \ - --backtitle "Proxmox VE Helper Scripts" \ - --title "Community-Scripts Options" \ - --ok-button "Select" --cancel-button "Exit Script" \ - --notags \ - --menu "\nChoose an option:\n Use TAB or Arrow keys to navigate, ENTER to select.\n" \ - 20 60 9 \ - "${menu_items[@]}" \ - --default-item "1" \ - 3>&1 1>&2 2>&3) || exit_script - CHOICE="$TMP_CHOICE" - fi - - # --- Main case --- - case "$CHOICE" in - 1 | default | DEFAULT) - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}" - VERBOSE="no" - METHOD="default" - base_settings "$VERBOSE" - echo_default - ensure_storage_selection_for_vars_file "/usr/local/community-scripts/default.vars" - ;; - 2 | advanced | ADVANCED) - header_info - echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Install on node $PVEHOST_NAME${CL}" - METHOD="advanced" - base_settings - advanced_settings - ensure_storage_selection_for_vars_file "/usr/local/community-scripts/default.vars" - maybe_offer_save_app_defaults - ;; - 3 | mydefaults | MYDEFAULTS) - default_var_settings || { - msg_error "Failed to apply default.vars" - exit 1 - } - ensure_storage_selection_for_vars_file "/usr/local/community-scripts/default.vars" - ;; - 4 | appdefaults | APPDEFAULTS) - if [ -f "$(get_app_defaults_path)" ]; then - header_info - echo -e "${DEFAULT}${BOLD}${BL}Using App Defaults for ${APP} on node $PVEHOST_NAME${CL}" - METHOD="appdefaults" - base_settings - _load_vars_file "$(get_app_defaults_path)" - echo_default - ensure_storage_selection_for_vars_file "$(get_app_defaults_path)" - else - msg_error "No App Defaults available for ${APP}" - exit 1 - fi - ;; - 5 | settings | SETTINGS) - settings_menu - ;; - *) - echo -e "${CROSS}${RD}Invalid option: $CHOICE${CL}" - exit 1 - ;; - esac -} - -# ------------------------------------------------------------------------------ -# check_storage_or_prompt() -# -# - Validates container/template storage -# - If invalid or missing, prompts user to select new storage -# - Updates vars file accordingly -# ------------------------------------------------------------------------------ -# check_storage_or_prompt() { -# local vars_file="$1" -# local changed=0 - -# if [ ! -f "$vars_file" ]; then -# msg_warn "No vars file found at $vars_file" -# return 0 -# fi - -# # Helper: validate storage ID -# _validate_storage() { -# local s="$1" -# [ -n "$s" ] || return 1 -# pvesm status -content images | awk 'NR>1 {print $1}' | grep -qx "$s" -# } - -# # Load current values (empty if not set) -# local ct_store tpl_store -# ct_store=$(awk -F= '/^var_container_storage=/ {print $2; exit}' "$vars_file") -# tpl_store=$(awk -F= '/^var_template_storage=/ {print $2; exit}' "$vars_file") - -# # Container storage -# if ! _validate_storage "$ct_store"; then -# local new_ct -# new_ct=$(pvesm status -content images | awk 'NR>1 {print $1" "$2" "$6}') -# new_ct=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Select Container Storage" \ -# --menu "Choose container storage:" 20 60 10 $new_ct 3>&1 1>&2 2>&3) || return 0 -# if [ -n "$new_ct" ]; then -# sed -i "/^var_container_storage=/d" "$vars_file" -# echo "var_container_storage=$new_ct" >>"$vars_file" -# changed=1 -# msg_ok "Updated container storage in $vars_file → $new_ct" -# fi -# fi - -# # Template storage -# if ! _validate_storage "$tpl_store"; then -# local new_tpl -# new_tpl=$(pvesm status -content vztmpl | awk 'NR>1 {print $1" "$2" "$6}') -# new_tpl=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Select Template Storage" \ -# --menu "Choose template storage:" 20 60 10 $new_tpl 3>&1 1>&2 2>&3) || return 0 -# if [ -n "$new_tpl" ]; then -# sed -i "/^var_template_storage=/d" "$vars_file" -# echo "var_template_storage=$new_tpl" >>"$vars_file" -# changed=1 -# msg_ok "Updated template storage in $vars_file → $new_tpl" -# fi -# fi - -# # Always succeed (no aborts from here) -# return 0 -# } - -# # ------------------------------------------------------------------------------ -# # storage_settings_menu() -# # -# # - Menu for managing storage defaults -# # - Options: update My Defaults or App Defaults storage -# # ------------------------------------------------------------------------------ -# storage_settings_menu() { -# local vars_file="/usr/local/community-scripts/default.vars" - -# check_storage_or_prompt "$vars_file" -# _echo_storage_summary "$vars_file" - -# # Always ask user if they want to update, even if values are valid -# if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "STORAGE SETTINGS" \ -# --yesno "Do you want to update the storage defaults?\n\nCurrent values will be kept unless you select new ones." 12 72; then - -# # container storage selection -# local new_ct -# new_ct=$(pvesm status -content images | awk 'NR>1 {print $1" "$2" "$6}') -# new_ct=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Select Container Storage" \ -# --menu "Choose container storage:" 20 60 10 $new_ct 3>&1 1>&2 2>&3) || true -# if [ -n "$new_ct" ]; then -# sed -i '/^var_container_storage=/d' "$vars_file" -# echo "var_container_storage=$new_ct" >>"$vars_file" -# msg_ok "Updated container storage → $new_ct" -# fi - -# # template storage selection -# local new_tpl -# new_tpl=$(pvesm status -content vztmpl | awk 'NR>1 {print $1" "$2" "$6}') -# new_tpl=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ -# --title "Select Template Storage" \ -# --menu "Choose template storage:" 20 60 10 $new_tpl 3>&1 1>&2 2>&3) || true -# if [ -n "$new_tpl" ]; then -# sed -i '/^var_template_storage=/d' "$vars_file" -# echo "var_template_storage=$new_tpl" >>"$vars_file" -# msg_ok "Updated template storage → $new_tpl" -# fi -# fi -# } - -edit_default_storage() { - local vf="/usr/local/community-scripts/default.vars" - - # make sure file exists - if [ ! -f "$vf" ]; then - msg_info "No default.vars found, creating $vf" - mkdir -p /usr/local/community-scripts - touch "$vf" - fi - - # reuse the same Whiptail selection we already have - ensure_storage_selection_for_vars_file "$vf" -} - -settings_menu() { - while true; do - local settings_items=( - "1" "Manage API-Diagnostic Setting" - "2" "Edit Default.vars" - "3" "Edit Default Storage" - ) - if [ -f "$(get_app_defaults_path)" ]; then - settings_items+=("4" "Edit App.vars for ${APP}") - settings_items+=("5" "Exit") - else - settings_items+=("4" "Exit") - fi - - local choice - choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ - --title "Community-Scripts SETTINGS Menu" \ - --ok-button "OK" --cancel-button "Back" \ - --menu "\n\nChoose a settings option:\n\nUse TAB or Arrow keys to navigate, ENTER to select." 20 60 9 \ - "${settings_items[@]}" \ - 3>&1 1>&2 2>&3) || break - - case "$choice" in - 1) diagnostics_menu ;; - 2) ${EDITOR:-nano} /usr/local/community-scripts/default.vars ;; - 3) edit_default_storage ;; - 4) - if [ -f "$(get_app_defaults_path)" ]; then - ${EDITOR:-nano} "$(get_app_defaults_path)" - else - exit_script - fi - ;; - 5) exit_script ;; - esac - done -} - -# ===== Unified storage selection & writing to vars files ===== -_write_storage_to_vars() { - # $1 = vars_file, $2 = key (var_container_storage / var_template_storage), $3 = value - local vf="$1" key="$2" val="$3" - # remove uncommented and commented versions to avoid duplicates - sed -i "/^[#[:space:]]*${key}=/d" "$vf" - echo "${key}=${val}" >>"$vf" -} - -choose_and_set_storage_for_file() { - # $1 = vars_file, $2 = class ('container'|'template') - local vf="$1" class="$2" key="" current="" - case "$class" in - container) key="var_container_storage" ;; - template) key="var_template_storage" ;; - *) - msg_error "Unknown storage class: $class" - return 1 - ;; - esac - - current=$(awk -F= -v k="^${key}=" '$0 ~ k {print $2; exit}' "$vf") - - # If only one storage exists for the content type, auto-pick. Else always ask (your wish #4). - local content="rootdir" - [[ "$class" == "template" ]] && content="vztmpl" - local count - count=$(pvesm status -content "$content" | awk 'NR>1{print $1}' | wc -l) - - if [[ "$count" -eq 1 ]]; then - STORAGE_RESULT=$(pvesm status -content "$content" | awk 'NR>1{print $1; exit}') - STORAGE_INFO="" - else - # If the current value is preselectable, we could show it, but per your requirement we always offer selection - select_storage "$class" || return 1 - fi - - _write_storage_to_vars "$vf" "$key" "$STORAGE_RESULT" - - # Keep environment in sync for later steps (e.g. app-default save) - if [[ "$class" == "container" ]]; then - export var_container_storage="$STORAGE_RESULT" - export CONTAINER_STORAGE="$STORAGE_RESULT" - else - export var_template_storage="$STORAGE_RESULT" - export TEMPLATE_STORAGE="$STORAGE_RESULT" - fi - - msg_ok "Updated ${key} → ${STORAGE_RESULT}" -} - -echo_storage_summary_from_file() { - local vars_file="$1" - local ct_store tpl_store - ct_store=$(awk -F= '/^var_container_storage=/ {print $2; exit}' "$vars_file") - tpl_store=$(awk -F= '/^var_template_storage=/ {print $2; exit}' "$vars_file") - if [ -n "$tpl" ]; then - TEMPLATE_STORAGE="$tpl" - else - choose_and_set_storage_for_file "$vf" template - fi - - if [ -n "$ct" ]; then - CONTAINER_STORAGE="$ct" - else - choose_and_set_storage_for_file "$vf" container - fi -} - -# ------------------------------------------------------------------------------ -# check_container_resources() -# -# - Compares host RAM/CPU with required values -# - Warns if under-provisioned and asks user to continue or abort -# ------------------------------------------------------------------------------ -check_container_resources() { - current_ram=$(free -m | awk 'NR==2{print $2}') - current_cpu=$(nproc) - - if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then - echo -e "\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}" - echo -e "${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\n" - echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? " - read -r prompt - if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then - echo -e "${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}" - exit 1 - fi - else - echo -e "" - fi -} - -# ------------------------------------------------------------------------------ -# check_container_storage() -# -# - Checks /boot partition usage -# - Warns if usage >80% and asks user confirmation before proceeding -# ------------------------------------------------------------------------------ -check_container_storage() { - total_size=$(df /boot --output=size | tail -n 1) - local used_size=$(df /boot --output=used | tail -n 1) - usage=$((100 * used_size / total_size)) - if ((usage > 80)); then - echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}" - echo -ne "Continue anyway? " - read -r prompt - if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then - echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}" - exit 1 - fi - fi -} - -# ------------------------------------------------------------------------------ -# ssh_extract_keys_from_file() -# -# - Extracts valid SSH public keys from given file -# - Supports RSA, Ed25519, ECDSA and filters out comments/invalid lines -# ------------------------------------------------------------------------------ -ssh_extract_keys_from_file() { - local f="$1" - [[ -r "$f" ]] || return 0 - tr -d '\r' <"$f" | awk ' - /^[[:space:]]*#/ {next} - /^[[:space:]]*$/ {next} - # nackt: typ base64 [comment] - /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/ {print; next} - # mit Optionen: finde ab erstem Key-Typ - { - match($0, /(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/) - if (RSTART>0) { print substr($0, RSTART) } - } - ' -} - -# ------------------------------------------------------------------------------ -# ssh_build_choices_from_files() -# -# - Builds interactive whiptail checklist of available SSH keys -# - Generates fingerprint, type and comment for each key -# ------------------------------------------------------------------------------ -ssh_build_choices_from_files() { - local -a files=("$@") - CHOICES=() - COUNT=0 - MAPFILE="$(mktemp)" - local id key typ fp cmt base ln=0 - - for f in "${files[@]}"; do - [[ -f "$f" && -r "$f" ]] || continue - base="$(basename -- "$f")" - case "$base" in - known_hosts | known_hosts.* | config) continue ;; - id_*) [[ "$f" != *.pub ]] && continue ;; - esac - - # map every key in file - while IFS= read -r key; do - [[ -n "$key" ]] || continue - - typ="" - fp="" - cmt="" - # Only the pure key part (without options) is already included in ‘key’. - read -r _typ _b64 _cmt <<<"$key" - typ="${_typ:-key}" - cmt="${_cmt:-}" - # Fingerprint via ssh-keygen (if available) - if command -v ssh-keygen >/dev/null 2>&1; then - fp="$(printf '%s\n' "$key" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')" - fi - # Label shorten - [[ ${#cmt} -gt 40 ]] && cmt="${cmt:0:37}..." - - ln=$((ln + 1)) - COUNT=$((COUNT + 1)) - id="K${COUNT}" - echo "${id}|${key}" >>"$MAPFILE" - CHOICES+=("$id" "[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}" "OFF") - done < <(ssh_extract_keys_from_file "$f") - done -} - -# ------------------------------------------------------------------------------ -# ssh_discover_default_files() -# -# - Scans standard paths for SSH keys -# - Includes ~/.ssh/*.pub, /etc/ssh/authorized_keys, etc. -# ------------------------------------------------------------------------------ -ssh_discover_default_files() { - local -a cand=() - shopt -s nullglob - cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2) - cand+=(/root/.ssh/*.pub) - cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*) - shopt -u nullglob - printf '%s\0' "${cand[@]}" -} - -# ------------------------------------------------------------------------------ -# start() -# -# - Entry point of script -# - On Proxmox host: calls install_script -# - In silent mode: runs update_script -# - Otherwise: shows update/setting menu -# ------------------------------------------------------------------------------ -start() { - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) - if command -v pveversion >/dev/null 2>&1; then - install_script || return 0 - return 0 - elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then - VERBOSE="no" - set_std_mode - update_script - else - CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \ - "Support/Update functions for ${APP} LXC. Choose an option:" \ - 12 60 3 \ - "1" "YES (Silent Mode)" \ - "2" "YES (Verbose Mode)" \ - "3" "NO (Cancel Update)" --nocancel --default-item "1" 3>&1 1>&2 2>&3) - - case "$CHOICE" in - 1) - VERBOSE="no" - set_std_mode - ;; - 2) - VERBOSE="yes" - set_std_mode - ;; - 3) - clear - exit_script - exit - ;; - esac - update_script - fi -} - -# ------------------------------------------------------------------------------ -# build_container() -# -# - Creates and configures the LXC container -# - Builds network string and applies features (FUSE, TUN, VAAPI passthrough) -# - Starts container and waits for network connectivity -# - Installs base packages, SSH keys, and runs -install.sh -# ------------------------------------------------------------------------------ -build_container() { - # if [ "$VERBOSE" == "yes" ]; then set -x; fi - - NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}" - NET_STRING+="${MAC:+,hwaddr=$MAC}" - NET_STRING+=",ip=${NET:-dhcp}" - NET_STRING+="${GATE:+,gw=$GATE}" - NET_STRING+="${VLAN:+,tag=$VLAN}" - NET_STRING+="${MTU:+,mtu=$MTU}" - case "$IPV6_METHOD" in - auto) NET_STRING="$NET_STRING,ip6=auto" ;; - dhcp) NET_STRING="$NET_STRING,ip6=dhcp" ;; - static) - NET_STRING="$NET_STRING,ip6=$IPV6_ADDR" - [ -n "$IPV6_GATE" ] && NET_STRING="$NET_STRING,gw6=$IPV6_GATE" - ;; - none) ;; - esac - - if [ "$CT_TYPE" == "1" ]; then - FEATURES="keyctl=1,nesting=1" - else - FEATURES="nesting=1" - fi - - if [ "$ENABLE_FUSE" == "yes" ]; then - FEATURES="$FEATURES,fuse=1" - fi - - #if [[ $DIAGNOSTICS == "yes" ]]; then - # post_to_api - #fi - - TEMP_DIR=$(mktemp -d) - pushd "$TEMP_DIR" >/dev/null - if [ "$var_os" == "alpine" ]; then - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/alpine-install.func)" - else - export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)" - fi - export DIAGNOSTICS="$DIAGNOSTICS" - export RANDOM_UUID="$RANDOM_UUID" - export CACHER="$APT_CACHER" - export CACHER_IP="$APT_CACHER_IP" - export tz="$timezone" - #export DISABLEIPV6="$DISABLEIP6" - export APPLICATION="$APP" - export app="$NSAPP" - export PASSWORD="$PW" - export VERBOSE="$VERBOSE" - export SSH_ROOT="${SSH}" - export SSH_AUTHORIZED_KEY - export CTID="$CT_ID" - export CTTYPE="$CT_TYPE" - export ENABLE_FUSE="$ENABLE_FUSE" - export ENABLE_TUN="$ENABLE_TUN" - export PCT_OSTYPE="$var_os" - export PCT_OSVERSION="${var_version%%.*}" - export PCT_DISK_SIZE="$DISK_SIZE" - export PCT_OPTIONS=" - -features $FEATURES - -hostname $HN - -tags $TAGS - $SD - $NS - $NET_STRING - -onboot 1 - -cores $CORE_COUNT - -memory $RAM_SIZE - -unprivileged $CT_TYPE - $PW -" - export TEMPLATE_STORAGE="${var_template_storage:-}" - export CONTAINER_STORAGE="${var_container_storage:-}" - create_lxc_container || exit $? - - LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - - source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/passthrough.func) - select_hw_passthrough "$CTID" "$CT_TYPE" "$APP" - - # TUN device passthrough - if [ "$ENABLE_TUN" == "yes" ]; then - cat <>"$LXC_CONFIG" -lxc.cgroup2.devices.allow: c 10:200 rwm -lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file -EOF - fi - - # This starts the container and executes -install.sh - msg_info "Starting LXC Container" - pct start "$CTID" - - # wait for status 'running' - for i in {1..10}; do - if pct status "$CTID" | grep -q "status: running"; then - msg_ok "Started LXC Container" - break - fi - sleep 1 - if [ "$i" -eq 10 ]; then - msg_error "LXC Container did not reach running state" - exit 1 - fi - done - - if [ "$var_os" != "alpine" ]; then - msg_info "Waiting for network in LXC container" - - # --- Step 1: Wait for IP --- - for i in {1..20}; do - ip_in_lxc=$(pct exec "$CTID" -- ip -4 addr show dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1) - [ -n "$ip_in_lxc" ] && break - sleep 1 - done - - if [ -z "$ip_in_lxc" ]; then - msg_error "No IP assigned to CT $CTID after 20s" - exit 1 - fi - - # --- Step 2: Try to reach gateway --- - gw_ok=0 - for i in {1..10}; do - if pct exec "$CTID" -- ping -c1 -W1 "$GATEWAY" >/dev/null 2>&1; then - gw_ok=1 - break - fi - sleep 1 - done - - if [ "$gw_ok" -eq 1 ]; then - msg_ok "CT $CTID gateway $GATEWAY reachable (IP $ip_in_lxc)" - else - msg_warn "CT $CTID has IP $ip_in_lxc but gateway $GATEWAY did not reply" - fi - - # --- Step 3: DNS / Internet check --- - if pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1; then - msg_ok "Network in LXC is reachable (DNS OK, IP $ip_in_lxc)" - else - msg_warn "Network reachable (IP $ip_in_lxc), but DNS failed – applying fallback resolv.conf" - pct exec "$CTID" -- bash -c 'echo "nameserver 1.1.1.1" > /etc/resolv.conf && echo "nameserver 8.8.8.8" >> /etc/resolv.conf' - if pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1; then - msg_ok "Network reachable after DNS fallback" - else - msg_error "Still no DNS/network in LXC! Aborting customization." - exit_script - fi - fi - fi - - msg_info "Customizing LXC Container" - #if [ "$var_os" != "alpine" ]; then - # vaapi_inside_setup "$CTID" "$CT_TYPE" "$APP" - # nvidia_inside_setup "$CTID" "$CT_TYPE" "$APP" - #fi - if [ "$var_os" == "alpine" ]; then - sleep 3 - pct exec "$CTID" -- /bin/sh -c 'cat </etc/apk/repositories -http://dl-cdn.alpinelinux.org/alpine/latest-stable/main -http://dl-cdn.alpinelinux.org/alpine/latest-stable/community -EOF' - pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq >/dev/null" - else - sleep 3 - - pct exec "$CTID" -- bash -c "sed -i '/$LANG/ s/^# //' /etc/locale.gen" - pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \ - echo LANG=\$locale_line >/etc/default/locale && \ - locale-gen >/dev/null && \ - export LANG=\$locale_line" - - if [[ -z "${tz:-}" ]]; then - tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Etc/UTC") - fi - - if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then - pct exec "$CTID" -- bash -c "tz='$tz'; echo \"\$tz\" >/etc/timezone && ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime" - else - msg_warn "Skipping timezone setup – zone '$tz' not found in container" - fi - - pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 jq >/dev/null" || { - msg_error "apt-get base packages installation failed" - exit 1 - } - fi - msg_ok "Customized LXC Container" - install_ssh_keys_into_ct - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/install/${var_install}.sh)"; then - exit $? - fi -} - -destroy_lxc() { - if [[ -z "$CT_ID" ]]; then - msg_error "No CT_ID found. Nothing to remove." - return 1 - fi - - # Abbruch bei Ctrl-C / Ctrl-D / ESC - trap 'echo; msg_error "Aborted by user (SIGINT/SIGQUIT)"; return 130' INT QUIT - - local prompt - if ! read -rp "Remove this Container? " prompt; then - # read gibt != 0 zurück bei Ctrl-D/ESC - msg_error "Aborted input (Ctrl-D/ESC)" - return 130 - fi - - case "${prompt,,}" in - y | yes) - if pct stop "$CT_ID" &>/dev/null && pct destroy "$CT_ID" &>/dev/null; then - msg_ok "Removed Container $CT_ID" - else - msg_error "Failed to remove Container $CT_ID" - return 1 - fi - ;; - "" | n | no) - msg_info "Container was not removed." - ;; - *) - msg_warn "Invalid response. Container was not removed." - ;; - esac -} - -# ------------------------------------------------------------------------------ -# Storage discovery / selection helpers -# ------------------------------------------------------------------------------ -# ===== Storage discovery / selection helpers (ported from create_lxc.sh) ===== -resolve_storage_preselect() { - local class="$1" preselect="$2" required_content="" - case "$class" in - template) required_content="vztmpl" ;; - container) required_content="rootdir" ;; - *) return 1 ;; - esac - [[ -z "$preselect" ]] && return 1 - if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then - msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)" - return 1 - fi - - local line total used free - line="$(pvesm status | awk -v s="$preselect" 'NR>1 && $1==s {print $0}')" - if [[ -z "$line" ]]; then - STORAGE_INFO="n/a" - else - total="$(awk '{print $4}' <<<"$line")" - used="$(awk '{print $5}' <<<"$line")" - free="$(awk '{print $6}' <<<"$line")" - local total_h used_h free_h - if command -v numfmt >/dev/null 2>&1; then - total_h="$(numfmt --to=iec --suffix=B --format %.1f "$total" 2>/dev/null || echo "$total")" - used_h="$(numfmt --to=iec --suffix=B --format %.1f "$used" 2>/dev/null || echo "$used")" - free_h="$(numfmt --to=iec --suffix=B --format %.1f "$free" 2>/dev/null || echo "$free")" - STORAGE_INFO="Free: ${free_h} Used: ${used_h}" - else - STORAGE_INFO="Free: ${free} Used: ${used}" - fi - fi - STORAGE_RESULT="$preselect" - return 0 -} - -check_storage_support() { - local CONTENT="$1" VALID=0 - while IFS= read -r line; do - local STORAGE_NAME - STORAGE_NAME=$(awk '{print $1}' <<<"$line") - [[ -n "$STORAGE_NAME" ]] && VALID=1 - done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1') - [[ $VALID -eq 1 ]] -} - -select_storage() { - local CLASS=$1 CONTENT CONTENT_LABEL - case $CLASS in - container) - CONTENT='rootdir' - CONTENT_LABEL='Container' - ;; - template) - CONTENT='vztmpl' - CONTENT_LABEL='Container template' - ;; - iso) - CONTENT='iso' - CONTENT_LABEL='ISO image' - ;; - images) - CONTENT='images' - CONTENT_LABEL='VM Disk image' - ;; - backup) - CONTENT='backup' - CONTENT_LABEL='Backup' - ;; - snippets) - CONTENT='snippets' - CONTENT_LABEL='Snippets' - ;; - *) - msg_error "Invalid storage class '$CLASS'" - return 1 - ;; - esac - - declare -A STORAGE_MAP - local -a MENU=() - local COL_WIDTH=0 - - while read -r TAG TYPE _ TOTAL USED FREE _; do - [[ -n "$TAG" && -n "$TYPE" ]] || continue - local DISPLAY="${TAG} (${TYPE})" - local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED") - local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE") - local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B" - STORAGE_MAP["$DISPLAY"]="$TAG" - MENU+=("$DISPLAY" "$INFO" "OFF") - ((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY} - done < <(pvesm status -content "$CONTENT" | awk 'NR>1') - - if [[ ${#MENU[@]} -eq 0 ]]; then - msg_error "No storage found for content type '$CONTENT'." - return 2 - fi - - if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then - STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}" - STORAGE_INFO="${MENU[1]}" - return 0 - fi - - local WIDTH=$((COL_WIDTH + 42)) - while true; do - local DISPLAY_SELECTED - DISPLAY_SELECTED=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ - --title "Storage Pools" \ - --radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ - 16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { exit_script; } - - DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<"$DISPLAY_SELECTED") - if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then - whiptail --msgbox "No valid storage selected. Please try again." 8 58 - continue - fi - STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}" - for ((i = 0; i < ${#MENU[@]}; i += 3)); do - if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then - STORAGE_INFO="${MENU[$i + 1]}" - break - fi - done - return 0 - done -} - -create_lxc_container() { - # ------------------------------------------------------------------------------ - # Optional verbose mode (debug tracing) - # ------------------------------------------------------------------------------ - if [[ "${CREATE_LXC_VERBOSE:-no}" == "yes" ]]; then set -x; fi - - # ------------------------------------------------------------------------------ - # Helpers (dynamic versioning / template parsing) - # ------------------------------------------------------------------------------ - pkg_ver() { dpkg-query -W -f='${Version}\n' "$1" 2>/dev/null || echo ""; } - pkg_cand() { apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}'; } - - ver_ge() { dpkg --compare-versions "$1" ge "$2"; } - ver_gt() { dpkg --compare-versions "$1" gt "$2"; } - ver_lt() { dpkg --compare-versions "$1" lt "$2"; } - - # Extract Debian OS minor from template name: debian-13-standard_13.1-1_amd64.tar.zst => "13.1" - parse_template_osver() { sed -n 's/.*_\([0-9][0-9]*\(\.[0-9]\+\)\?\)-.*/\1/p' <<<"$1"; } - - # Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create - # Returns: - # 0 = no upgrade needed - # 1 = upgraded (and if do_retry=yes and retry succeeded, creation done) - # 2 = user declined - # 3 = upgrade attempted but failed OR retry failed - offer_lxc_stack_upgrade_and_maybe_retry() { - local do_retry="${1:-no}" # yes|no - local _pvec_i _pvec_c _lxcp_i _lxcp_c need=0 - - _pvec_i="$(pkg_ver pve-container)" - _lxcp_i="$(pkg_ver lxc-pve)" - _pvec_c="$(pkg_cand pve-container)" - _lxcp_c="$(pkg_cand lxc-pve)" - - if [[ -n "$_pvec_c" && "$_pvec_c" != "none" ]]; then - ver_gt "$_pvec_c" "${_pvec_i:-0}" && need=1 - fi - if [[ -n "$_lxcp_c" && "$_lxcp_c" != "none" ]]; then - ver_gt "$_lxcp_c" "${_lxcp_i:-0}" && need=1 - fi - if [[ $need -eq 0 ]]; then - msg_debug "No newer candidate for pve-container/lxc-pve (installed=$_pvec_i/$_lxcp_i, cand=$_pvec_c/$_lxcp_c)" - return 0 - fi - - echo - echo "An update for the Proxmox LXC stack is available:" - echo " pve-container: installed=${_pvec_i:-n/a} candidate=${_pvec_c:-n/a}" - echo " lxc-pve : installed=${_lxcp_i:-n/a} candidate=${_lxcp_c:-n/a}" - echo - read -rp "Do you want to upgrade now? [y/N] " _ans - case "${_ans,,}" in - y | yes) - msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)" - if apt-get update -qq >/dev/null && apt-get install -y --only-upgrade pve-container lxc-pve >/dev/null; then - msg_ok "LXC stack upgraded." - if [[ "$do_retry" == "yes" ]]; then - msg_info "Retrying container creation after upgrade" - if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then - msg_ok "Container created successfully after upgrade." - return 1 - else - msg_error "pct create still failed after upgrade. See $LOGFILE" - return 3 - fi - fi - return 1 - else - msg_error "Upgrade failed. Please check APT output." - return 3 - fi - ;; - *) return 2 ;; - esac - } - - # ------------------------------------------------------------------------------ - # Required input variables - # ------------------------------------------------------------------------------ - [[ "${CTID:-}" ]] || { - msg_error "You need to set 'CTID' variable." - exit 203 - } - [[ "${PCT_OSTYPE:-}" ]] || { - msg_error "You need to set 'PCT_OSTYPE' variable." - exit 204 - } - - msg_debug "CTID=$CTID" - msg_debug "PCT_OSTYPE=$PCT_OSTYPE" - msg_debug "PCT_OSVERSION=${PCT_OSVERSION:-default}" - - # ID checks - [[ "$CTID" -ge 100 ]] || { - msg_error "ID cannot be less than 100." - exit 205 - } - if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then - echo -e "ID '$CTID' is already in use." - unset CTID - msg_error "Cannot use ID that is already in use." - exit 206 - fi - - # Storage capability check - check_storage_support "rootdir" || { - msg_error "No valid storage found for 'rootdir' [Container]" - exit 1 - } - check_storage_support "vztmpl" || { - msg_error "No valid storage found for 'vztmpl' [Template]" - exit 1 - } - - # Template storage selection - if resolve_storage_preselect template "${TEMPLATE_STORAGE:-}"; then - TEMPLATE_STORAGE="$STORAGE_RESULT" - TEMPLATE_STORAGE_INFO="$STORAGE_INFO" - msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]" - else - while true; do - if select_storage template; then - TEMPLATE_STORAGE="$STORAGE_RESULT" - TEMPLATE_STORAGE_INFO="$STORAGE_INFO" - msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]" - break - fi - done - fi - - # Container storage selection - if resolve_storage_preselect container "${CONTAINER_STORAGE:-}"; then - CONTAINER_STORAGE="$STORAGE_RESULT" - CONTAINER_STORAGE_INFO="$STORAGE_INFO" - msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]" - else - while true; do - if select_storage container; then - CONTAINER_STORAGE="$STORAGE_RESULT" - CONTAINER_STORAGE_INFO="$STORAGE_INFO" - msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]" - break - fi - done - fi - - # Validate content types - msg_info "Validating content types of storage '$CONTAINER_STORAGE'" - STORAGE_CONTENT=$(grep -A4 -E "^(zfspool|dir|lvmthin|lvm): $CONTAINER_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs) - msg_debug "Storage '$CONTAINER_STORAGE' has content types: $STORAGE_CONTENT" - grep -qw "rootdir" <<<"$STORAGE_CONTENT" || { - msg_error "Storage '$CONTAINER_STORAGE' does not support 'rootdir'. Cannot create LXC." - exit 217 - } - $STD msg_ok "Storage '$CONTAINER_STORAGE' supports 'rootdir'" - - msg_info "Validating content types of template storage '$TEMPLATE_STORAGE'" - TEMPLATE_CONTENT=$(grep -A4 -E "^[^:]+: $TEMPLATE_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs) - msg_debug "Template storage '$TEMPLATE_STORAGE' has content types: $TEMPLATE_CONTENT" - if ! grep -qw "vztmpl" <<<"$TEMPLATE_CONTENT"; then - msg_warn "Template storage '$TEMPLATE_STORAGE' does not declare 'vztmpl'. This may cause pct create to fail." - else - $STD msg_ok "Template storage '$TEMPLATE_STORAGE' supports 'vztmpl'" - fi - - # Free space check - STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }') - REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024)) - [[ "$STORAGE_FREE" -ge "$REQUIRED_KB" ]] || { - msg_error "Not enough space on '$CONTAINER_STORAGE'. Needed: ${PCT_DISK_SIZE:-8}G." - exit 214 - } - - # Cluster quorum (if cluster) - if [[ -f /etc/pve/corosync.conf ]]; then - msg_info "Checking cluster quorum" - if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then - msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)." - exit 210 - fi - msg_ok "Cluster is quorate" - fi - - # ------------------------------------------------------------------------------ - # Template discovery & validation - # ------------------------------------------------------------------------------ - TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}" - case "$PCT_OSTYPE" in - debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;; - alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;; - *) TEMPLATE_PATTERN="" ;; - esac - - msg_info "Searching for template '$TEMPLATE_SEARCH'" - - mapfile -t LOCAL_TEMPLATES < <( - pveam list "$TEMPLATE_STORAGE" 2>/dev/null | - awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '$1 ~ s && $1 ~ p {print $1}' | - sed 's|.*/||' | sort -t - -k 2 -V - ) - - pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)." - mapfile -t ONLINE_TEMPLATES < <( - pveam available -section system 2>/dev/null | - sed -n "s/.*\($TEMPLATE_SEARCH.*$TEMPLATE_PATTERN.*\)/\1/p" | - sort -t - -k 2 -V - ) - ONLINE_TEMPLATE="" - [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" - - if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then - TEMPLATE="${LOCAL_TEMPLATES[-1]}" - TEMPLATE_SOURCE="local" - else - TEMPLATE="$ONLINE_TEMPLATE" - TEMPLATE_SOURCE="online" - fi - - TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)" - if [[ -z "$TEMPLATE_PATH" ]]; then - TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg) - [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE" - fi - [[ -n "$TEMPLATE_PATH" ]] || { - msg_error "Unable to resolve template path for $TEMPLATE_STORAGE. Check storage type and permissions." - exit 220 - } - - msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]" - msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH" - - NEED_DOWNLOAD=0 - if [[ ! -f "$TEMPLATE_PATH" ]]; then - msg_info "Template not present locally – will download." - NEED_DOWNLOAD=1 - elif [[ ! -r "$TEMPLATE_PATH" ]]; then - msg_error "Template file exists but is not readable – check permissions." - exit 221 - elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then - if [[ -n "$ONLINE_TEMPLATE" ]]; then - msg_warn "Template file too small (<1MB) – re-downloading." - NEED_DOWNLOAD=1 - else - msg_warn "Template looks too small, but no online version exists. Keeping local file." - fi - elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then - if [[ -n "$ONLINE_TEMPLATE" ]]; then - msg_warn "Template appears corrupted – re-downloading." - NEED_DOWNLOAD=1 - else - msg_warn "Template appears corrupted, but no online version exists. Keeping local file." - fi - else - $STD msg_ok "Template $TEMPLATE is present and valid." - fi - - if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then - msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)" - if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then - TEMPLATE="$ONLINE_TEMPLATE" - NEED_DOWNLOAD=1 - else - msg_info "Continuing with local template $TEMPLATE" - fi - fi - - if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then - [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH" - for attempt in {1..3}; do - msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE" - if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then - msg_ok "Template download successful." - break - fi - if [[ $attempt -eq 3 ]]; then - msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" - exit 222 - fi - sleep $((attempt * 5)) - done - fi - - if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then - msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download." - exit 223 - fi - - # ------------------------------------------------------------------------------ - # Dynamic preflight for Debian 13.x: offer upgrade if available (no hard mins) - # ------------------------------------------------------------------------------ - if [[ "$PCT_OSTYPE" == "debian" ]]; then - OSVER="$(parse_template_osver "$TEMPLATE")" - if [[ -n "$OSVER" ]]; then - # Proactive, aber ohne Abbruch – nur Angebot - offer_lxc_stack_upgrade_and_maybe_retry "no" || true - fi - fi - - # ------------------------------------------------------------------------------ - # Create LXC Container - # ------------------------------------------------------------------------------ - msg_info "Creating LXC container" - - # Ensure subuid/subgid entries exist - grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid - grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid - - # Assemble pct options - PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}}) - [[ " ${PCT_OPTIONS[*]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}") - - # Lock by template file (avoid concurrent downloads/creates) - lockfile="/tmp/template.${TEMPLATE}.lock" - exec 9>"$lockfile" || { - msg_error "Failed to create lock file '$lockfile'." - exit 200 - } - flock -w 60 9 || { - msg_error "Timeout while waiting for template lock." - exit 211 - } - - LOGFILE="/tmp/pct_create_${CTID}.log" - msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" - msg_debug "Logfile: $LOGFILE" - - # First attempt - if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >"$LOGFILE" 2>&1; then - msg_error "Container creation failed on ${TEMPLATE_STORAGE}. Checking template..." - - # Validate template file - if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then - msg_warn "Template file too small or missing – re-downloading." - rm -f "$TEMPLATE_PATH" - pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" - elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then - if [[ -n "$ONLINE_TEMPLATE" ]]; then - msg_warn "Template appears corrupted – re-downloading." - rm -f "$TEMPLATE_PATH" - pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" - else - msg_warn "Template appears corrupted, but no online version exists. Skipping re-download." - fi - fi - - # Retry after repair - if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then - # Fallback to local storage - if [[ "$TEMPLATE_STORAGE" != "local" ]]; then - msg_warn "Retrying container creation with fallback to local storage..." - LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" - if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then - msg_info "Downloading template to local..." - pveam download local "$TEMPLATE" >/dev/null 2>&1 - fi - if pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then - msg_ok "Container successfully created using local fallback." - else - # --- Dynamic stack upgrade + auto-retry on the well-known error pattern --- - if grep -qiE 'unsupported .* version' "$LOGFILE"; then - echo - echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template." - echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically." - if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then - : # success after retry - else - rc=$? - case $rc in - 2) echo "Upgrade was declined. Please update and re-run: - apt update && apt install --only-upgrade pve-container lxc-pve" ;; - 3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;; - esac - exit 231 - fi - else - msg_error "Container creation failed even with local fallback. See $LOGFILE" - if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then - set -x - bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" - set +x - fi - exit 209 - fi - fi - else - msg_error "Container creation failed on local storage. See $LOGFILE" - # --- Dynamic stack upgrade + auto-retry on the well-known error pattern --- - if grep -qiE 'unsupported .* version' "$LOGFILE"; then - echo - echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template." - echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically." - if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then - : # success after retry - else - rc=$? - case $rc in - 2) echo "Upgrade was declined. Please update and re-run: - apt update && apt install --only-upgrade pve-container lxc-pve" ;; - 3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;; - esac - exit 231 - fi - else - if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then - set -x - bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" - set +x - fi - exit 209 - fi - fi - fi - fi - - # Verify container exists - pct list | awk '{print $1}' | grep -qx "$CTID" || { - msg_error "Container ID $CTID not listed in 'pct list'. See $LOGFILE" - exit 215 - } - - # Verify config rootfs - grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf" || { - msg_error "RootFS entry missing in container config. See $LOGFILE" - exit 216 - } - - msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." -} - -# ------------------------------------------------------------------------------ -# description() -# -# - Sets container description with HTML content (logo, links, badges) -# - Restarts ping-instances.service if present -# - Posts status "done" to API -# ------------------------------------------------------------------------------ -description() { - IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1) - - # Generate LXC Description - DESCRIPTION=$( - cat < - - Logo - - -

${APP} LXC

- -

- - spend Coffee - -

- - - - GitHub - - - - Discussions - - - - Issues - - -EOF - ) - pct set "$CTID" -description "$DESCRIPTION" - - if [[ -f /etc/systemd/system/ping-instances.service ]]; then - systemctl start ping-instances.service - fi - - post_update_to_api "done" "none" -} - -# ------------------------------------------------------------------------------ -# api_exit_script() -# -# - Exit trap handler -# - Reports exit codes to API with detailed reason -# - Handles known codes (100–209) and maps them to errors -# ------------------------------------------------------------------------------ -api_exit_script() { - exit_code=$? - if [ $exit_code -ne 0 ]; then - case $exit_code in - 100) post_update_to_api "failed" "100: Unexpected error in create_lxc.sh" ;; - 101) post_update_to_api "failed" "101: No network connection detected in create_lxc.sh" ;; - 200) post_update_to_api "failed" "200: LXC creation failed in create_lxc.sh" ;; - 201) post_update_to_api "failed" "201: Invalid Storage class in create_lxc.sh" ;; - 202) post_update_to_api "failed" "202: User aborted menu in create_lxc.sh" ;; - 203) post_update_to_api "failed" "203: CTID not set in create_lxc.sh" ;; - 204) post_update_to_api "failed" "204: PCT_OSTYPE not set in create_lxc.sh" ;; - 205) post_update_to_api "failed" "205: CTID cannot be less than 100 in create_lxc.sh" ;; - 206) post_update_to_api "failed" "206: CTID already in use in create_lxc.sh" ;; - 207) post_update_to_api "failed" "207: Template not found in create_lxc.sh" ;; - 208) post_update_to_api "failed" "208: Error downloading template in create_lxc.sh" ;; - 209) post_update_to_api "failed" "209: Container creation failed, but template is intact in create_lxc.sh" ;; - *) post_update_to_api "failed" "Unknown error, exit code: $exit_code in create_lxc.sh" ;; - esac - fi -} - -if command -v pveversion >/dev/null 2>&1; then - trap 'api_exit_script' EXIT -fi -trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR -trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT -trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM From 8137614d6b8687b6de5edeb2d0a19f26ef7ef93b Mon Sep 17 00:00:00 2001 From: HydroshieldMKII Date: Wed, 1 Oct 2025 10:32:31 -0400 Subject: [PATCH 40/41] Reverted changes in build and install --- misc/build.func | 2981 +++++++++++++++++++++++++++++++++++++++++++++ misc/install.func | 206 ++++ 2 files changed, 3187 insertions(+) create mode 100644 misc/build.func create mode 100644 misc/install.func diff --git a/misc/build.func b/misc/build.func new file mode 100644 index 00000000..d6b0af72 --- /dev/null +++ b/misc/build.func @@ -0,0 +1,2981 @@ +#!/usr/bin/env bash +# Copyright (c) 2021-2025 community-scripts ORG +# Author: tteck (tteckster) | MickLesk | michelroegl-brunner +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Revision: 1 + +# ------------------------------------------------------------------------------ +# variables() +# +# - Normalize application name (NSAPP = lowercase, no spaces) +# - Build installer filename (var_install) +# - Define regex for integer validation +# - Fetch hostname of Proxmox node +# - Set default values for diagnostics/method +# - Generate random UUID for tracking +# ------------------------------------------------------------------------------ +variables() { + NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces. + var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP. + INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern. + PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase + DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call. + METHOD="default" # sets the METHOD variable to "default", used for the API call. + RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable. + #CT_TYPE=${var_unprivileged:-$CT_TYPE} +} + +# ----------------------------------------------------------------------------- +# Community-Scripts bootstrap loader +# - Always sources build.func from remote +# - Updates local core files only if build.func changed +# - Local cache: /usr/local/community-scripts/core +# ----------------------------------------------------------------------------- + +# FUNC_DIR="/usr/local/community-scripts/core" +# mkdir -p "$FUNC_DIR" + +# BUILD_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func" +# BUILD_REV="$FUNC_DIR/build.rev" +# DEVMODE="${DEVMODE:-no}" + +# # --- Step 1: fetch build.func content once, compute hash --- +# build_content="$(curl -fsSL "$BUILD_URL")" || { +# echo "❌ Failed to fetch build.func" +# exit 1 +# } + +# newhash=$(printf "%s" "$build_content" | sha256sum | awk '{print $1}') +# oldhash=$(cat "$BUILD_REV" 2>/dev/null || echo "") + +# # --- Step 2: if build.func changed, offer update for core files --- +# if [ "$newhash" != "$oldhash" ]; then +# echo "⚠️ build.func changed!" + +# while true; do +# read -rp "Refresh local core files? [y/N/diff]: " ans +# case "$ans" in +# [Yy]*) +# echo "$newhash" >"$BUILD_REV" + +# update_func_file() { +# local file="$1" +# local url="https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/$file" +# local local_path="$FUNC_DIR/$file" + +# echo "⬇️ Downloading $file ..." +# curl -fsSL "$url" -o "$local_path" || { +# echo "❌ Failed to fetch $file" +# exit 1 +# } +# echo "✔️ Updated $file" +# } + +# update_func_file core.func +# update_func_file error_handler.func +# update_func_file tools.func +# break +# ;; +# [Dd]*) +# for file in core.func error_handler.func tools.func; do +# local_path="$FUNC_DIR/$file" +# url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/$file" +# remote_tmp="$(mktemp)" + +# curl -fsSL "$url" -o "$remote_tmp" || continue + +# if [ -f "$local_path" ]; then +# echo "🔍 Diff for $file:" +# diff -u "$local_path" "$remote_tmp" || echo "(no differences)" +# else +# echo "📦 New file $file will be installed" +# fi + +# rm -f "$remote_tmp" +# done +# ;; +# *) +# echo "❌ Skipped updating local core files" +# break +# ;; +# esac +# done +# else +# if [ "$DEVMODE" != "yes" ]; then +# echo "✔️ build.func unchanged → using existing local core files" +# fi +# fi + +# if [ -n "${_COMMUNITY_SCRIPTS_LOADER:-}" ]; then +# return 0 2>/dev/null || exit 0 +# fi +# _COMMUNITY_SCRIPTS_LOADER=1 + +# # --- Step 3: always source local versions of the core files --- +# source "$FUNC_DIR/core.func" +# source "$FUNC_DIR/error_handler.func" +# source "$FUNC_DIR/tools.func" + +# # --- Step 4: finally, source build.func directly from memory --- +# # (no tmp file needed) +# source <(printf "%s" "$build_content") + +# ------------------------------------------------------------------------------ +# Load core + error handler functions from community-scripts repo +# +# - Prefer curl if available, fallback to wget +# - Load: core.func, error_handler.func, api.func +# - Initialize error traps after loading +# ------------------------------------------------------------------------------ + +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func) + +if command -v curl >/dev/null 2>&1; then + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + load_functions + catch_errors + #echo "(build.func) Loaded core.func via curl" +elif command -v wget >/dev/null 2>&1; then + source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) + source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) + load_functions + catch_errors + #echo "(build.func) Loaded core.func via wget" +fi + +# ------------------------------------------------------------------------------ +# maxkeys_check() +# +# - Reads kernel keyring limits (maxkeys, maxbytes) +# - Checks current usage for LXC user (UID 100000) +# - Warns if usage is close to limits and suggests sysctl tuning +# - Exits if thresholds are exceeded +# - https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html +# ------------------------------------------------------------------------------ + +maxkeys_check() { + # Read kernel parameters + per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0) + per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0) + + # Exit if kernel parameters are unavailable + if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then + echo -e "${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}" + exit 1 + fi + + # Fetch key usage for user ID 100000 (typical for containers) + used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0) + used_lxc_bytes=$(awk '/100000:/ {split($5, a, "/"); print a[1]}' /proc/key-users 2>/dev/null || echo 0) + + # Calculate thresholds and suggested new limits + threshold_keys=$((per_user_maxkeys - 100)) + threshold_bytes=$((per_user_maxbytes - 1000)) + new_limit_keys=$((per_user_maxkeys * 2)) + new_limit_bytes=$((per_user_maxbytes * 2)) + + # Check if key or byte usage is near limits + failure=0 + if [[ "$used_lxc_keys" -gt "$threshold_keys" ]]; then + echo -e "${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}" + echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}." + failure=1 + fi + if [[ "$used_lxc_bytes" -gt "$threshold_bytes" ]]; then + echo -e "${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}" + echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}." + failure=1 + fi + + # Provide next steps if issues are detected + if [[ "$failure" -eq 1 ]]; then + echo -e "${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}" + exit 1 + fi + + echo -e "${CM}${GN} All kernel key limits are within safe thresholds.${CL}" +} + +# ------------------------------------------------------------------------------ +# get_current_ip() +# +# - Returns current container IP depending on OS type +# - Debian/Ubuntu: uses `hostname -I` +# - Alpine: parses eth0 via `ip -4 addr` +# ------------------------------------------------------------------------------ +get_current_ip() { + if [ -f /etc/os-release ]; then + # Check for Debian/Ubuntu (uses hostname -I) + if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then + CURRENT_IP=$(hostname -I | awk '{print $1}') + # Check for Alpine (uses ip command) + elif grep -q 'ID=alpine' /etc/os-release; then + CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1) + else + CURRENT_IP="Unknown" + fi + fi + echo "$CURRENT_IP" +} + +# ------------------------------------------------------------------------------ +# update_motd_ip() +# +# - Updates /etc/motd with current container IP +# - Removes old IP entries to avoid duplicates +# ------------------------------------------------------------------------------ +update_motd_ip() { + MOTD_FILE="/etc/motd" + + if [ -f "$MOTD_FILE" ]; then + # Remove existing IP Address lines to prevent duplication + sed -i '/IP Address:/d' "$MOTD_FILE" + + IP=$(get_current_ip) + # Add the new IP address + echo -e "${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}" >>"$MOTD_FILE" + fi +} + +# ------------------------------------------------------------------------------ +# ssh_check() +# +# - Detects if script is running over SSH +# - Warns user and recommends using Proxmox shell +# - User can choose to continue or abort +# ------------------------------------------------------------------------------ +ssh_check() { + if [ -n "${SSH_CLIENT:+x}" ]; then + if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's advisable to utilize the Proxmox shell rather than SSH, as there may be potential complications with variable retrieval. Proceed using SSH?" 10 72; then + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Proceed using SSH" "You've chosen to proceed using SSH. If any issues arise, please run the script in the Proxmox shell before creating a repository issue." 10 72 + else + clear + echo "Exiting due to SSH usage. Please consider using the Proxmox shell." + exit + fi + fi +} + +# ------------------------------------------------------------------------------ +# install_ssh_keys_into_ct() +# +# - Installs SSH keys into container root account if SSH is enabled +# - Uses pct push or direct input to authorized_keys +# - Falls back to warning if no keys provided +# ------------------------------------------------------------------------------ +install_ssh_keys_into_ct() { + [[ "$SSH" != "yes" ]] && return 0 + + if [[ -n "$SSH_KEYS_FILE" && -s "$SSH_KEYS_FILE" ]]; then + msg_info "Installing selected SSH keys into CT ${CTID}" + pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || { + msg_error "prepare /root/.ssh failed" + return 1 + } + pct push "$CTID" "$SSH_KEYS_FILE" /root/.ssh/authorized_keys >/dev/null 2>&1 || + pct exec "$CTID" -- sh -c "cat > /root/.ssh/authorized_keys" <"$SSH_KEYS_FILE" || { + msg_error "write authorized_keys failed" + return 1 + } + pct exec "$CTID" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true + msg_ok "Installed SSH keys into CT ${CTID}" + return 0 + fi + + # Fallback: nichts ausgewählt + msg_warn "No SSH keys to install (skipping)." + return 0 +} + +# ------------------------------------------------------------------------------ +# base_settings() +# +# - Defines all base/default variables for container creation +# - Reads from environment variables (var_*) +# - Provides fallback defaults for OS type/version +# ------------------------------------------------------------------------------ +base_settings() { + # Default Settings + CT_TYPE=${var_unprivileged:-"1"} + DISK_SIZE=${var_disk:-"4"} + CORE_COUNT=${var_cpu:-"1"} + RAM_SIZE=${var_ram:-"1024"} + VERBOSE=${var_verbose:-"${1:-no}"} + PW=${var_pw:-""} + CT_ID=${var_ctid:-$NEXTID} + HN=${var_hostname:-$NSAPP} + BRG=${var_brg:-"vmbr0"} + NET=${var_net:-"dhcp"} + IPV6_METHOD=${var_ipv6_method:-"none"} + IPV6_STATIC=${var_ipv6_static:-""} + GATE=${var_gateway:-""} + APT_CACHER=${var_apt_cacher:-""} + APT_CACHER_IP=${var_apt_cacher_ip:-""} + MTU=${var_mtu:-""} + SD=${var_storage:-""} + NS=${var_ns:-""} + MAC=${var_mac:-""} + VLAN=${var_vlan:-""} + SSH=${var_ssh:-"no"} + SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-""} + UDHCPC_FIX=${var_udhcpc_fix:-""} + TAGS="community-script,${var_tags:-}" + ENABLE_FUSE=${var_fuse:-"${1:-no}"} + ENABLE_TUN=${var_tun:-"${1:-no}"} + + # Since these 2 are only defined outside of default_settings function, we add a temporary fallback. TODO: To align everything, we should add these as constant variables (e.g. OSTYPE and OSVERSION), but that would currently require updating the default_settings function for all existing scripts + if [ -z "$var_os" ]; then + var_os="debian" + fi + if [ -z "$var_version" ]; then + var_version="12" + fi +} + +# ------------------------------------------------------------------------------ +# echo_default() +# +# - Prints summary of default values (ID, OS, type, disk, RAM, CPU, etc.) +# - Uses icons and formatting for readability +# - Convert CT_TYPE to description +# ------------------------------------------------------------------------------ +echo_default() { + CT_TYPE_DESC="Unprivileged" + if [ "$CT_TYPE" -eq 0 ]; then + CT_TYPE_DESC="Privileged" + fi + + echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}" + echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" + if [ "$VERBOSE" == "yes" ]; then + echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}" + fi + echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}" + echo -e " " +} + +# ------------------------------------------------------------------------------ +# exit_script() +# +# - Called when user cancels an action +# - Clears screen and exits gracefully +# ------------------------------------------------------------------------------ +exit_script() { + clear + echo -e "\n${CROSS}${RD}User exited script${CL}\n" + exit +} + +# ------------------------------------------------------------------------------ +# find_host_ssh_keys() +# +# - Scans system for available SSH keys +# - Supports defaults (~/.ssh, /etc/ssh/authorized_keys) +# - Returns list of files containing valid SSH public keys +# - Sets FOUND_HOST_KEY_COUNT to number of keys found +# ------------------------------------------------------------------------------ +find_host_ssh_keys() { + local re='(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))' + local -a files=() cand=() + local g="${var_ssh_import_glob:-}" + local total=0 f base c + + shopt -s nullglob + if [[ -n "$g" ]]; then + for pat in $g; do cand+=($pat); done + else + cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2) + cand+=(/root/.ssh/*.pub) + cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*) + fi + shopt -u nullglob + + for f in "${cand[@]}"; do + [[ -f "$f" && -r "$f" ]] || continue + base="$(basename -- "$f")" + case "$base" in + known_hosts | known_hosts.* | config) continue ;; + id_*) [[ "$f" != *.pub ]] && continue ;; + esac + + # CRLF safe check for host keys + c=$(tr -d '\r' <"$f" | awk ' + /^[[:space:]]*#/ {next} + /^[[:space:]]*$/ {next} + {print} + ' | grep -E -c '"$re"' || true) + + if ((c > 0)); then + files+=("$f") + total=$((total + c)) + fi + done + + # Fallback to /root/.ssh/authorized_keys + if ((${#files[@]} == 0)) && [[ -r /root/.ssh/authorized_keys ]]; then + if grep -E -q "$re" /root/.ssh/authorized_keys; then + files+=(/root/.ssh/authorized_keys) + total=$((total + $(grep -E -c "$re" /root/.ssh/authorized_keys || echo 0))) + fi + fi + + FOUND_HOST_KEY_COUNT="$total" + ( + IFS=: + echo "${files[*]}" + ) +} + +# ------------------------------------------------------------------------------ +# advanced_settings() +# +# - Interactive whiptail menu for advanced configuration +# - Lets user set container type, password, CT ID, hostname, disk, CPU, RAM +# - Supports IPv4/IPv6, DNS, MAC, VLAN, tags, SSH keys, FUSE, verbose mode +# - Ends with confirmation or re-entry if cancelled +# ------------------------------------------------------------------------------ +advanced_settings() { + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Here is an instructional tip:" "To make a selection, use the Spacebar." 8 58 + # Setting Default Tag for Advanced Settings + TAGS="community-script;${var_tags:-}" + CT_DEFAULT_TYPE="${CT_TYPE}" + CT_TYPE="" + while [ -z "$CT_TYPE" ]; do + if [ "$CT_DEFAULT_TYPE" == "1" ]; then + if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \ + "1" "Unprivileged" ON \ + "0" "Privileged" OFF \ + 3>&1 1>&2 2>&3); then + if [ -n "$CT_TYPE" ]; then + CT_TYPE_DESC="Unprivileged" + if [ "$CT_TYPE" -eq 0 ]; then + CT_TYPE_DESC="Privileged" + fi + echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os | ${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" + fi + else + exit_script + fi + fi + if [ "$CT_DEFAULT_TYPE" == "0" ]; then + if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \ + "1" "Unprivileged" OFF \ + "0" "Privileged" ON \ + 3>&1 1>&2 2>&3); then + if [ -n "$CT_TYPE" ]; then + CT_TYPE_DESC="Unprivileged" + if [ "$CT_TYPE" -eq 0 ]; then + CT_TYPE_DESC="Privileged" + fi + echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}" + echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}" + fi + else + exit_script + fi + fi + done + + while true; do + if PW1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nSet Root Password (needed for root ssh access)" 9 58 --title "PASSWORD (leave blank for automatic login)" 3>&1 1>&2 2>&3); then + # Empty = Autologin + if [[ -z "$PW1" ]]; then + PW="" + PW1="Automatic Login" + echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}" + break + fi + + # Invalid: contains spaces + if [[ "$PW1" == *" "* ]]; then + whiptail --msgbox "Password cannot contain spaces." 8 58 + continue + fi + + # Invalid: too short + if ((${#PW1} < 5)); then + whiptail --msgbox "Password must be at least 5 characters." 8 58 + continue + fi + + # Confirm password + if PW2=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nVerify Root Password" 9 58 --title "PASSWORD VERIFICATION" 3>&1 1>&2 2>&3); then + if [[ "$PW1" == "$PW2" ]]; then + PW="-password $PW1" + echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}" + break + else + whiptail --msgbox "Passwords do not match. Please try again." 8 58 + fi + else + exit_script + fi + else + exit_script + fi + done + + if CT_ID=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Container ID" 8 58 "$NEXTID" --title "CONTAINER ID" 3>&1 1>&2 2>&3); then + if [ -z "$CT_ID" ]; then + CT_ID="$NEXTID" + echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" + else + echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" + fi + else + exit_script + fi + + while true; do + if CT_NAME=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then + if [ -z "$CT_NAME" ]; then + HN="$NSAPP" + else + HN=$(echo "${CT_NAME,,}" | tr -d ' ') + fi + # Hostname validate (RFC 1123) + if [[ "$HN" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then + echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}" + break + else + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --msgbox "❌ Invalid hostname: '$HN'\n\nOnly lowercase letters, digits and hyphens (-) are allowed.\nUnderscores (_) or other characters are not permitted!" 10 70 + fi + else + exit_script + fi + done + + while true; do + DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GB" 8 58 "$var_disk" --title "DISK SIZE" 3>&1 1>&2 2>&3) || exit_script + + if [ -z "$DISK_SIZE" ]; then + DISK_SIZE="$var_disk" + fi + + if [[ "$DISK_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}" + break + else + whiptail --msgbox "Disk size must be a positive integer!" 8 58 + fi + done + + while true; do + CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ + --inputbox "Allocate CPU Cores" 8 58 "$var_cpu" --title "CORE COUNT" 3>&1 1>&2 2>&3) || exit_script + + if [ -z "$CORE_COUNT" ]; then + CORE_COUNT="$var_cpu" + fi + + if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}" + break + else + whiptail --msgbox "CPU core count must be a positive integer!" 8 58 + fi + done + + while true; do + RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ + --inputbox "Allocate RAM in MiB" 8 58 "$var_ram" --title "RAM" 3>&1 1>&2 2>&3) || exit_script + + if [ -z "$RAM_SIZE" ]; then + RAM_SIZE="$var_ram" + fi + + if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then + echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}" + break + else + whiptail --msgbox "RAM size must be a positive integer!" 8 58 + fi + done + + IFACE_FILEPATH_LIST="/etc/network/interfaces"$'\n'$(find "/etc/network/interfaces.d/" -type f) + BRIDGES="" + OLD_IFS=$IFS + IFS=$'\n' + for iface_filepath in ${IFACE_FILEPATH_LIST}; do + + iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX') + (grep -Pn '^\s*iface' "${iface_filepath}" | cut -d':' -f1 && wc -l "${iface_filepath}" | cut -d' ' -f1) | awk 'FNR==1 {line=$0; next} {print line":"$0-1; line=$0}' >"${iface_indexes_tmpfile}" || true + + if [ -f "${iface_indexes_tmpfile}" ]; then + + while read -r pair; do + start=$(echo "${pair}" | cut -d':' -f1) + end=$(echo "${pair}" | cut -d':' -f2) + + if awk "NR >= ${start} && NR <= ${end}" "${iface_filepath}" | grep -qP '^\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\s+OVSBridge)\b'; then + iface_name=$(sed "${start}q;d" "${iface_filepath}" | awk '{print $2}') + BRIDGES="${iface_name}"$'\n'"${BRIDGES}" + fi + + done <"${iface_indexes_tmpfile}" + rm -f "${iface_indexes_tmpfile}" + fi + + done + IFS=$OLD_IFS + BRIDGES=$(echo "$BRIDGES" | grep -v '^\s*$' | sort | uniq) + if [[ -z "$BRIDGES" ]]; then + BRG="vmbr0" + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + else + BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --menu "Select network bridge:" 15 40 6 $(echo "$BRIDGES" | awk '{print $0, "Bridge"}') 3>&1 1>&2 2>&3) + if [[ -z "$BRG" ]]; then + exit_script + else + echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}" + fi + fi + + # IPv4 methods: dhcp, static, none + while true; do + IPV4_METHOD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "IPv4 Address Management" \ + --menu "Select IPv4 Address Assignment Method:" 12 60 2 \ + "dhcp" "Automatic (DHCP, recommended)" \ + "static" "Static (manual entry)" \ + 3>&1 1>&2 2>&3) + + exit_status=$? + if [ $exit_status -ne 0 ]; then + exit_script + fi + + case "$IPV4_METHOD" in + dhcp) + NET="dhcp" + GATE="" + echo -e "${NETWORK}${BOLD}${DGN}IPv4: DHCP${CL}" + break + ;; + static) + # Static: call and validate CIDR address + while true; do + NET=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --inputbox "Enter Static IPv4 CIDR Address (e.g. 192.168.100.50/24)" 8 58 "" \ + --title "IPv4 ADDRESS" 3>&1 1>&2 2>&3) + if [ -z "$NET" ]; then + whiptail --msgbox "IPv4 address must not be empty." 8 58 + continue + elif [[ "$NET" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then + echo -e "${NETWORK}${BOLD}${DGN}IPv4 Address: ${BGN}$NET${CL}" + break + else + whiptail --msgbox "$NET is not a valid IPv4 CIDR address. Please enter a correct value!" 8 58 + fi + done + + # call and validate Gateway + while true; do + GATE1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --inputbox "Enter Gateway IP address for static IPv4" 8 58 "" \ + --title "Gateway IP" 3>&1 1>&2 2>&3) + if [ -z "$GATE1" ]; then + whiptail --msgbox "Gateway IP address cannot be empty." 8 58 + elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then + whiptail --msgbox "Invalid Gateway IP address format." 8 58 + else + GATE=",gw=$GATE1" + echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}" + break + fi + done + break + ;; + esac + done + + # IPv6 Address Management selection + while true; do + IPV6_METHOD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --menu \ + "Select IPv6 Address Management Type:" 15 58 4 \ + "auto" "SLAAC/AUTO (recommended, default)" \ + "dhcp" "DHCPv6" \ + "static" "Static (manual entry)" \ + "none" "Disabled" \ + --default-item "auto" 3>&1 1>&2 2>&3) + [ $? -ne 0 ] && exit_script + + case "$IPV6_METHOD" in + auto) + echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}SLAAC/AUTO${CL}" + IPV6_ADDR="" + IPV6_GATE="" + break + ;; + dhcp) + echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}DHCPv6${CL}" + IPV6_ADDR="dhcp" + IPV6_GATE="" + break + ;; + static) + # Ask for static IPv6 address (CIDR notation, e.g., 2001:db8::1234/64) + while true; do + IPV6_ADDR=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox \ + "Set a static IPv6 CIDR address (e.g., 2001:db8::1234/64)" 8 58 "" \ + --title "IPv6 STATIC ADDRESS" 3>&1 1>&2 2>&3) || exit_script + if [[ "$IPV6_ADDR" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+(/[0-9]{1,3})$ ]]; then + echo -e "${NETWORK}${BOLD}${DGN}IPv6 Address: ${BGN}$IPV6_ADDR${CL}" + break + else + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox \ + "$IPV6_ADDR is an invalid IPv6 CIDR address. Please enter a valid IPv6 CIDR address (e.g., 2001:db8::1234/64)" 8 58 + fi + done + # Optional: ask for IPv6 gateway for static config + while true; do + IPV6_GATE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox \ + "Enter IPv6 gateway address (optional, leave blank for none)" 8 58 "" --title "IPv6 GATEWAY" 3>&1 1>&2 2>&3) + if [ -z "$IPV6_GATE" ]; then + IPV6_GATE="" + break + elif [[ "$IPV6_GATE" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+$ ]]; then + break + else + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox \ + "Invalid IPv6 gateway format." 8 58 + fi + done + break + ;; + none) + echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}Disabled${CL}" + IPV6_ADDR="none" + IPV6_GATE="" + break + ;; + *) + exit_script + ;; + esac + done + + if [ "$var_os" == "alpine" ]; then + APT_CACHER="" + APT_CACHER_IP="" + else + if APT_CACHER_IP=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 --title "APT-Cacher IP" 3>&1 1>&2 2>&3); then + APT_CACHER="${APT_CACHER_IP:+yes}" + echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}" + else + exit_script + fi + fi + + # if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "IPv6" --yesno "Disable IPv6?" 10 58); then + # DISABLEIP6="yes" + # else + # DISABLEIP6="no" + # fi + # echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}$DISABLEIP6${CL}" + + if MTU1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])" 8 58 --title "MTU SIZE" 3>&1 1>&2 2>&3); then + if [ -z "$MTU1" ]; then + MTU1="Default" + MTU="" + else + MTU=",mtu=$MTU1" + fi + echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}" + else + exit_script + fi + + if SD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 --title "DNS Search Domain" 3>&1 1>&2 2>&3); then + if [ -z "$SD" ]; then + SX=Host + SD="" + else + SX=$SD + SD="-searchdomain=$SD" + fi + echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}" + else + exit_script + fi + + if NX=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 --title "DNS SERVER IP" 3>&1 1>&2 2>&3); then + if [ -z "$NX" ]; then + NX=Host + NS="" + else + NS="-nameserver=$NX" + fi + echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}" + else + exit_script + fi + + if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ "$NX" != "Host" ]; then + UDHCPC_FIX="yes" + else + UDHCPC_FIX="no" + fi + export UDHCPC_FIX + + if MAC1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a MAC Address(leave blank for generated MAC)" 8 58 --title "MAC ADDRESS" 3>&1 1>&2 2>&3); then + if [ -z "$MAC1" ]; then + MAC1="Default" + MAC="" + else + MAC=",hwaddr=$MAC1" + echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}" + fi + else + exit_script + fi + + if VLAN1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for no VLAN)" 8 58 --title "VLAN" 3>&1 1>&2 2>&3); then + if [ -z "$VLAN1" ]; then + VLAN1="Default" + VLAN="" + else + VLAN=",tag=$VLAN1" + fi + echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}" + else + exit_script + fi + + if ADV_TAGS=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Custom Tags?[If you remove all, there will be no tags!]" 8 58 "${TAGS}" --title "Advanced Tags" 3>&1 1>&2 2>&3); then + if [ -n "${ADV_TAGS}" ]; then + ADV_TAGS=$(echo "$ADV_TAGS" | tr -d '[:space:]') + TAGS="${ADV_TAGS}" + else + TAGS=";" + fi + echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}" + else + exit_script + fi + + # --- SSH key provisioning (one dialog) --- + SSH_KEYS_FILE="$(mktemp)" + : >"$SSH_KEYS_FILE" + + IFS=$'\0' read -r -d '' -a _def_files < <(ssh_discover_default_files && printf '\0') + ssh_build_choices_from_files "${_def_files[@]}" + DEF_KEYS_COUNT="$COUNT" + + if [[ "$DEF_KEYS_COUNT" -gt 0 ]]; then + SSH_KEY_MODE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SSH KEY SOURCE" --menu \ + "Provision SSH keys for root:" 14 72 4 \ + "found" "Select from detected keys (${DEF_KEYS_COUNT})" \ + "manual" "Paste a single public key" \ + "folder" "Scan another folder (path or glob)" \ + "none" "No keys" 3>&1 1>&2 2>&3) || exit_script + else + SSH_KEY_MODE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SSH KEY SOURCE" --menu \ + "No host keys detected; choose manual/none:" 12 72 2 \ + "manual" "Paste a single public key" \ + "none" "No keys" 3>&1 1>&2 2>&3) || exit_script + fi + + case "$SSH_KEY_MODE" in + found) + SEL=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SELECT HOST KEYS" \ + --checklist "Select one or more keys to import:" 20 140 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script + for tag in $SEL; do + tag="${tag%\"}" + tag="${tag#\"}" + line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-) + [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE" + done + ;; + manual) + SSH_AUTHORIZED_KEY="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --inputbox "Paste one SSH public key line (ssh-ed25519/ssh-rsa/...)" 10 72 --title "SSH Public Key" 3>&1 1>&2 2>&3)" + [[ -n "$SSH_AUTHORIZED_KEY" ]] && printf '%s\n' "$SSH_AUTHORIZED_KEY" >>"$SSH_KEYS_FILE" + ;; + folder) + GLOB_PATH="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --inputbox "Enter a folder or glob to scan (e.g. /root/.ssh/*.pub)" 10 72 --title "Scan Folder/Glob" 3>&1 1>&2 2>&3)" + if [[ -n "$GLOB_PATH" ]]; then + shopt -s nullglob + read -r -a _scan_files <<<"$GLOB_PATH" + shopt -u nullglob + if [[ "${#_scan_files[@]}" -gt 0 ]]; then + ssh_build_choices_from_files "${_scan_files[@]}" + if [[ "$COUNT" -gt 0 ]]; then + SEL=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SELECT FOLDER KEYS" \ + --checklist "Select key(s) to import:" 20 78 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script + for tag in $SEL; do + tag="${tag%\"}" + tag="${tag#\"}" + line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-) + [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE" + done + else + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "No keys found in: $GLOB_PATH" 8 60 + fi + else + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "Path/glob returned no files." 8 60 + fi + fi + ;; + none) : ;; + esac + + # Dedupe + clean EOF + if [[ -s "$SSH_KEYS_FILE" ]]; then + sort -u -o "$SSH_KEYS_FILE" "$SSH_KEYS_FILE" + printf '\n' >>"$SSH_KEYS_FILE" + fi + + # SSH activate, if keys found or password set + if [[ -s "$SSH_KEYS_FILE" || "$PW" == -password* ]]; then + if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH ACCESS" --yesno "Enable root SSH access?" 10 58); then + SSH="yes" + else + SSH="no" + fi + else + SSH="no" + fi + echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" + + export SSH_KEYS_FILE + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "FUSE Support" --yesno "Enable FUSE support?\nRequired for tools like rclone, mergerfs, AppImage, etc." 10 58); then + ENABLE_FUSE="yes" + else + ENABLE_FUSE="no" + fi + echo -e "${FUSE}${BOLD}${DGN}Enable FUSE Support: ${BGN}$ENABLE_FUSE${CL}" + + if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then + VERBOSE="yes" + else + VERBOSE="no" + fi + echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}" + + if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then + echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above advanced settings${CL}" + else + clear + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" + advanced_settings + fi +} + +# ------------------------------------------------------------------------------ +# diagnostics_check() +# +# - Ensures diagnostics config file exists at /usr/local/community-scripts/diagnostics +# - Asks user whether to send anonymous diagnostic data +# - Saves DIAGNOSTICS=yes/no in the config file +# ------------------------------------------------------------------------------ +diagnostics_check() { + if ! [ -d "/usr/local/community-scripts" ]; then + mkdir -p /usr/local/community-scripts + fi + + if ! [ -f "/usr/local/community-scripts/diagnostics" ]; then + if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS" --yesno "Send Diagnostics of LXC Installation?\n\n(This only transmits data without user data, just RAM, CPU, LXC name, ...)" 10 58); then + cat </usr/local/community-scripts/diagnostics +DIAGNOSTICS=yes + +#This file is used to store the diagnostics settings for the Community-Scripts API. +#https://github.com/community-scripts/ProxmoxVED/discussions/1836 +#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes. +#You can review the data at https://community-scripts.github.io/ProxmoxVE/data +#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue. +#This will disable the diagnostics feature. +#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue. +#This will enable the diagnostics feature. +#The following information will be sent: +#"ct_type" +#"disk_size" +#"core_count" +#"ram_size" +#"os_type" +#"os_version" +#"disableip6" +#"nsapp" +#"method" +#"pve_version" +#"status" +#If you have any concerns, please review the source code at /misc/build.func +EOF + DIAGNOSTICS="yes" + else + cat </usr/local/community-scripts/diagnostics +DIAGNOSTICS=no + +#This file is used to store the diagnostics settings for the Community-Scripts API. +#https://github.com/community-scripts/ProxmoxVED/discussions/1836 +#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes. +#You can review the data at https://community-scripts.github.io/ProxmoxVE/data +#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue. +#This will disable the diagnostics feature. +#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue. +#This will enable the diagnostics feature. +#The following information will be sent: +#"ct_type" +#"disk_size" +#"core_count" +#"ram_size" +#"os_type" +#"os_version" +#"disableip6" +#"nsapp" +#"method" +#"pve_version" +#"status" +#If you have any concerns, please review the source code at /misc/build.func +EOF + DIAGNOSTICS="no" + fi + else + DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' /usr/local/community-scripts/diagnostics) + + fi + +} + +# ------------------------------------------------------------------------------ +# default_var_settings +# +# - Ensures /usr/local/community-scripts/default.vars exists (creates if missing) +# - Loads var_* values from default.vars (safe parser, no source/eval) +# - Precedence: ENV var_* > default.vars > built-in defaults +# - Maps var_verbose → VERBOSE +# - Calls base_settings "$VERBOSE" and echo_default +# ------------------------------------------------------------------------------ +default_var_settings() { + # Allowed var_* keys (alphabetically sorted) + local VAR_WHITELIST=( + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_ctid var_disk var_fuse + var_gateway var_hostname var_ipv6_method var_ipv6_static var_mac var_mtu + var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged + var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage + ) + + # Snapshot: environment variables (highest precedence) + declare -A _HARD_ENV=() + local _k + for _k in "${VAR_WHITELIST[@]}"; do + if printenv "$_k" >/dev/null 2>&1; then _HARD_ENV["$_k"]=1; fi + done + + # Find default.vars location + local _find_default_vars + _find_default_vars() { + local f + for f in \ + /usr/local/community-scripts/default.vars \ + "$HOME/.config/community-scripts/default.vars" \ + "./default.vars"; do + [ -f "$f" ] && { + echo "$f" + return 0 + } + done + return 1 + } + # Allow override of storages via env (for non-interactive use cases) + [ -n "${var_template_storage:-}" ] && TEMPLATE_STORAGE="$var_template_storage" + [ -n "${var_container_storage:-}" ] && CONTAINER_STORAGE="$var_container_storage" + + # Create once, with storages already selected, no var_ctid/var_hostname lines + local _ensure_default_vars + _ensure_default_vars() { + _find_default_vars >/dev/null 2>&1 && return 0 + + local canonical="/usr/local/community-scripts/default.vars" + msg_info "No default.vars found. Creating ${canonical}" + mkdir -p /usr/local/community-scripts + + # Pick storages before writing the file (always ask unless only one) + # Create a minimal temp file to write into + : >"$canonical" + + # Base content (no var_ctid / var_hostname here) + cat >"$canonical" <<'EOF' +# Community-Scripts defaults (var_* only). Lines starting with # are comments. +# Precedence: ENV var_* > default.vars > built-ins. +# Keep keys alphabetically sorted. + +# Container type +var_unprivileged=1 + +# Resources +var_cpu=1 +var_disk=4 +var_ram=1024 + +# Network +var_brg=vmbr0 +var_net=dhcp +var_ipv6_method=none +# var_gateway= +# var_ipv6_static= +# var_vlan= +# var_mtu= +# var_mac= +# var_ns= + +# SSH +var_ssh=no +# var_ssh_authorized_key= + +# APT cacher (optional) +# var_apt_cacher=yes +# var_apt_cacher_ip=192.168.1.10 + +# Features/Tags/verbosity +var_fuse=no +var_tun=no +var_tags=community-script +var_verbose=no + +# Security (root PW) – empty => autologin +# var_pw= +EOF + + # Now choose storages (always prompt unless just one exists) + choose_and_set_storage_for_file "$canonical" template + choose_and_set_storage_for_file "$canonical" container + + chmod 0644 "$canonical" + msg_ok "Created ${canonical}" + } + + # Whitelist check + local _is_whitelisted_key + _is_whitelisted_key() { + local k="$1" + local w + for w in "${VAR_WHITELIST[@]}"; do [ "$k" = "$w" ] && return 0; done + return 1 + } + + # Safe parser for KEY=VALUE lines + local _load_vars_file + _load_vars_file() { + local file="$1" + [ -f "$file" ] || return 0 + msg_info "Loading defaults from ${file}" + local line key val + while IFS= read -r line || [ -n "$line" ]; do + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [[ -z "$line" || "$line" == \#* ]] && continue + if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then + local var_key="${BASH_REMATCH[1]}" + local var_val="${BASH_REMATCH[2]}" + + [[ "$var_key" != var_* ]] && continue + _is_whitelisted_key "$var_key" || { + msg_debug "Ignore non-whitelisted ${var_key}" + continue + } + + # Strip quotes + if [[ "$var_val" =~ ^\"(.*)\"$ ]]; then + var_val="${BASH_REMATCH[1]}" + elif [[ "$var_val" =~ ^\'(.*)\'$ ]]; then + var_val="${BASH_REMATCH[1]}" + fi + + # Unsafe characters + case $var_val in + \"*\") + var_val=${var_val#\"} + var_val=${var_val%\"} + ;; + \'*\') + var_val=${var_val#\'} + var_val=${var_val%\'} + ;; + esac # Hard env wins + [[ -n "${_HARD_ENV[$var_key]:-}" ]] && continue + # Set only if not already exported + [[ -z "${!var_key+x}" ]] && export "${var_key}=${var_val}" + else + msg_warn "Malformed line in ${file}: ${line}" + fi + done <"$file" + msg_ok "Loaded ${file}" + } + + # 1) Ensure file exists + _ensure_default_vars + + # 2) Load file + local dv + dv="$(_find_default_vars)" || { + msg_error "default.vars not found after ensure step" + return 1 + } + _load_vars_file "$dv" + + # 3) Map var_verbose → VERBOSE + if [[ -n "${var_verbose:-}" ]]; then + case "${var_verbose,,}" in 1 | yes | true | on) VERBOSE="yes" ;; 0 | no | false | off) VERBOSE="no" ;; *) VERBOSE="${var_verbose}" ;; esac + else + VERBOSE="no" + fi + + # 4) Apply base settings and show summary + METHOD="mydefaults-global" + base_settings "$VERBOSE" + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using My Defaults (default.vars) on node $PVEHOST_NAME${CL}" + echo_default +} + +# ------------------------------------------------------------------------------ +# get_app_defaults_path() +# +# - Returns full path for app-specific defaults file +# - Example: /usr/local/community-scripts/defaults/.vars +# ------------------------------------------------------------------------------ + +get_app_defaults_path() { + local n="${NSAPP:-${APP,,}}" + echo "/usr/local/community-scripts/defaults/${n}.vars" +} + +# ------------------------------------------------------------------------------ +# maybe_offer_save_app_defaults +# +# - Called after advanced_settings returned with fully chosen values. +# - If no .vars exists, offers to persist current advanced settings +# into /usr/local/community-scripts/defaults/.vars +# - Only writes whitelisted var_* keys. +# - Extracts raw values from flags like ",gw=..." ",mtu=..." etc. +# ------------------------------------------------------------------------------ +if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then + declare -ag VAR_WHITELIST=( + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_ctid var_disk var_fuse + var_gateway var_hostname var_ipv6_method var_ipv6_static var_mac var_mtu + var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged + var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage + ) +fi + +_is_whitelisted_key() { + local k="$1" + local w + for w in "${VAR_WHITELIST[@]}"; do [[ "$k" == "$w" ]] && return 0; done + return 1 +} + +_sanitize_value() { + # Disallow Command-Substitution / Shell-Meta + case "$1" in + *'$('* | *'`'* | *';'* | *'&'* | *'<('*) + echo "" + return 0 + ;; + esac + echo "$1" +} + +# Map-Parser: read var_* from file into _VARS_IN associative array +declare -A _VARS_IN +_load_vars_file() { + local file="$1" + [ -f "$file" ] || return 0 + msg_info "Loading defaults from ${file}" + local line key val + while IFS= read -r line || [ -n "$line" ]; do + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [ -z "$line" ] && continue + case "$line" in + \#*) continue ;; + esac + key=$(printf "%s" "$line" | cut -d= -f1) + val=$(printf "%s" "$line" | cut -d= -f2-) + case "$key" in + var_*) + if _is_whitelisted_key "$key"; then + [ -z "${!key+x}" ] && export "$key=$val" + fi + ;; + esac + done <"$file" + msg_ok "Loaded ${file}" +} + +# Diff function for two var_* files -> produces human-readable diff list for $1 (old) vs $2 (new) +_build_vars_diff() { + local oldf="$1" newf="$2" + local k + local -A OLD=() NEW=() + _load_vars_file_to_map "$oldf" + for k in "${!_VARS_IN[@]}"; do OLD["$k"]="${_VARS_IN[$k]}"; done + _load_vars_file_to_map "$newf" + for k in "${!_VARS_IN[@]}"; do NEW["$k"]="${_VARS_IN[$k]}"; done + + local out + out+="# Diff for ${APP} (${NSAPP})\n" + out+="# Old: ${oldf}\n# New: ${newf}\n\n" + + local found_change=0 + + # Changed & Removed + for k in "${!OLD[@]}"; do + if [[ -v NEW["$k"] ]]; then + if [[ "${OLD[$k]}" != "${NEW[$k]}" ]]; then + out+="~ ${k}\n - old: ${OLD[$k]}\n + new: ${NEW[$k]}\n" + found_change=1 + fi + else + out+="- ${k}\n - old: ${OLD[$k]}\n" + found_change=1 + fi + done + + # Added + for k in "${!NEW[@]}"; do + if [[ ! -v OLD["$k"] ]]; then + out+="+ ${k}\n + new: ${NEW[$k]}\n" + found_change=1 + fi + done + + if [[ $found_change -eq 0 ]]; then + out+="(No differences)\n" + fi + + printf "%b" "$out" +} + +# Build a temporary .vars file from current advanced settings +_build_current_app_vars_tmp() { + tmpf="$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)" + + # NET/GW + _net="${NET:-}" + _gate="" + case "${GATE:-}" in + ,gw=*) _gate=$(echo "$GATE" | sed 's/^,gw=//') ;; + esac + + # IPv6 + _ipv6_method="${IPV6_METHOD:-auto}" + _ipv6_static="" + _ipv6_gateway="" + if [ "$_ipv6_method" = "static" ]; then + _ipv6_static="${IPV6_ADDR:-}" + _ipv6_gateway="${IPV6_GATE:-}" + fi + + # MTU/VLAN/MAC + _mtu="" + _vlan="" + _mac="" + case "${MTU:-}" in + ,mtu=*) _mtu=$(echo "$MTU" | sed 's/^,mtu=//') ;; + esac + case "${VLAN:-}" in + ,tag=*) _vlan=$(echo "$VLAN" | sed 's/^,tag=//') ;; + esac + case "${MAC:-}" in + ,hwaddr=*) _mac=$(echo "$MAC" | sed 's/^,hwaddr=//') ;; + esac + + # DNS / Searchdomain + _ns="" + _searchdomain="" + case "${NS:-}" in + -nameserver=*) _ns=$(echo "$NS" | sed 's/^-nameserver=//') ;; + esac + case "${SD:-}" in + -searchdomain=*) _searchdomain=$(echo "$SD" | sed 's/^-searchdomain=//') ;; + esac + + # SSH / APT / Features + _ssh="${SSH:-no}" + _ssh_auth="${SSH_AUTHORIZED_KEY:-}" + _apt_cacher="${APT_CACHER:-}" + _apt_cacher_ip="${APT_CACHER_IP:-}" + _fuse="${ENABLE_FUSE:-no}" + _tun="${ENABLE_TUN:-no}" + _tags="${TAGS:-}" + _verbose="${VERBOSE:-no}" + + # Type / Resources / Identity + _unpriv="${CT_TYPE:-1}" + _cpu="${CORE_COUNT:-1}" + _ram="${RAM_SIZE:-1024}" + _disk="${DISK_SIZE:-4}" + _hostname="${HN:-$NSAPP}" + + # Storage + _tpl_storage="${TEMPLATE_STORAGE:-${var_template_storage:-}}" + _ct_storage="${CONTAINER_STORAGE:-${var_container_storage:-}}" + + { + echo "# App-specific defaults for ${APP} (${NSAPP})" + echo "# Generated on $(date -u '+%Y-%m-%dT%H:%M:%SZ')" + echo + + echo "var_unprivileged=$(_sanitize_value "$_unpriv")" + echo "var_cpu=$(_sanitize_value "$_cpu")" + echo "var_ram=$(_sanitize_value "$_ram")" + echo "var_disk=$(_sanitize_value "$_disk")" + + [ -n "${BRG:-}" ] && echo "var_brg=$(_sanitize_value "$BRG")" + [ -n "$_net" ] && echo "var_net=$(_sanitize_value "$_net")" + [ -n "$_gate" ] && echo "var_gateway=$(_sanitize_value "$_gate")" + [ -n "$_mtu" ] && echo "var_mtu=$(_sanitize_value "$_mtu")" + [ -n "$_vlan" ] && echo "var_vlan=$(_sanitize_value "$_vlan")" + [ -n "$_mac" ] && echo "var_mac=$(_sanitize_value "$_mac")" + [ -n "$_ns" ] && echo "var_ns=$(_sanitize_value "$_ns")" + + [ -n "$_ipv6_method" ] && echo "var_ipv6_method=$(_sanitize_value "$_ipv6_method")" + [ -n "$_ipv6_static" ] && echo "var_ipv6_static=$(_sanitize_value "$_ipv6_static")" + + [ -n "$_ssh" ] && echo "var_ssh=$(_sanitize_value "$_ssh")" + [ -n "$_ssh_auth" ] && echo "var_ssh_authorized_key=$(_sanitize_value "$_ssh_auth")" + + [ -n "$_apt_cacher" ] && echo "var_apt_cacher=$(_sanitize_value "$_apt_cacher")" + [ -n "$_apt_cacher_ip" ] && echo "var_apt_cacher_ip=$(_sanitize_value "$_apt_cacher_ip")" + + [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")" + [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")" + [ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")" + [ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")" + + [ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")" + [ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")" + + [ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")" + [ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")" + } >"$tmpf" + + echo "$tmpf" +} + +# ------------------------------------------------------------------------------ +# maybe_offer_save_app_defaults() +# +# - Called after advanced_settings() +# - Offers to save current values as app defaults if not existing +# - If file exists: shows diff and allows Update, Keep, View Diff, or Cancel +# ------------------------------------------------------------------------------ +maybe_offer_save_app_defaults() { + local app_vars_path + app_vars_path="$(get_app_defaults_path)" + + # always build from current settings + local new_tmp diff_tmp + new_tmp="$(_build_current_app_vars_tmp)" + diff_tmp="$(mktemp -p /tmp "${NSAPP:-app}.vars.diff.XXXXXX")" + + # 1) if no file → offer to create + if [[ ! -f "$app_vars_path" ]]; then + if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --yesno "Save these advanced settings as defaults for ${APP}?\n\nThis will create:\n${app_vars_path}" 12 72; then + mkdir -p "$(dirname "$app_vars_path")" + install -m 0644 "$new_tmp" "$app_vars_path" + msg_ok "Saved app defaults: ${app_vars_path}" + fi + rm -f "$new_tmp" "$diff_tmp" + return 0 + fi + + # 2) if file exists → build diff + _build_vars_diff "$app_vars_path" "$new_tmp" >"$diff_tmp" + + # if no differences → do nothing + if grep -q "^(No differences)$" "$diff_tmp"; then + rm -f "$new_tmp" "$diff_tmp" + return 0 + fi + + # 3) if file exists → show menu with default selection "Update Defaults" + local app_vars_file + app_vars_file="$(basename "$app_vars_path")" + + while true; do + local sel + sel="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "APP DEFAULTS – ${APP}" \ + --menu "Differences detected. What do you want to do?" 20 78 10 \ + "Update Defaults" "Write new values to ${app_vars_file}" \ + "Keep Current" "Keep existing defaults (no changes)" \ + "View Diff" "Show a detailed diff" \ + "Cancel" "Abort without changes" \ + --default-item "Update Defaults" \ + 3>&1 1>&2 2>&3)" || { sel="Cancel"; } + + case "$sel" in + "Update Defaults") + install -m 0644 "$new_tmp" "$app_vars_path" + msg_ok "Updated app defaults: ${app_vars_path}" + break + ;; + "Keep Current") + msg_info "Keeping current app defaults: ${app_vars_path}" + break + ;; + "View Diff") + whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "Diff – ${APP}" \ + --scrolltext --textbox "$diff_tmp" 25 100 + ;; + "Cancel" | *) + msg_info "Canceled. No changes to app defaults." + break + ;; + esac + done + + rm -f "$new_tmp" "$diff_tmp" +} + +ensure_storage_selection_for_vars_file() { + vf="$1" + + # Read stored values (if any) + tpl=$(grep -E '^var_template_storage=' "$vf" | cut -d= -f2-) + ct=$(grep -E '^var_container_storage=' "$vf" | cut -d= -f2-) + + # Template storage + if [ -n "$tpl" ]; then + TEMPLATE_STORAGE="$tpl" + else + choose_and_set_storage_for_file "$vf" template + fi + + # Container storage + if [ -n "$ct" ]; then + CONTAINER_STORAGE="$ct" + else + choose_and_set_storage_for_file "$vf" container + fi + + echo_storage_summary_from_file "$vf" +} + +diagnostics_menu() { + if [ "${DIAGNOSTICS:-no}" = "yes" ]; then + if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "DIAGNOSTIC SETTINGS" \ + --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \ + --yes-button "No" --no-button "Back"; then + DIAGNOSTICS="no" + sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics + whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58 + fi + else + if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "DIAGNOSTIC SETTINGS" \ + --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \ + --yes-button "Yes" --no-button "Back"; then + DIAGNOSTICS="yes" + sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics + whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58 + fi + fi +} + +# ------------------------------------------------------------------------------ +# install_script() +# +# - Main entrypoint for installation mode +# - Runs safety checks (pve_check, root_check, maxkeys_check, diagnostics_check) +# - Builds interactive menu (Default, Verbose, Advanced, My Defaults, App Defaults, Diagnostics, Storage, Exit) +# - Applies chosen settings and triggers container build +# ------------------------------------------------------------------------------ +install_script() { + pve_check + shell_check + root_check + arch_check + ssh_check + maxkeys_check + diagnostics_check + + if systemctl is-active -q ping-instances.service; then + systemctl -q stop ping-instances.service + fi + + NEXTID=$(pvesh get /cluster/nextid) + timezone=$(cat /etc/timezone) + header_info + + # --- Support CLI argument as direct preset (default, advanced, …) --- + CHOICE="${mode:-${1:-}}" + + # If no CLI argument → show whiptail menu + if [ -z "$CHOICE" ]; then + local menu_items=( + "1" "Default Install" + "2" "Advanced Install" + "3" "My Defaults" + ) + + if [ -f "$(get_app_defaults_path)" ]; then + menu_items+=("4" "App Defaults for ${APP}") + menu_items+=("5" "Settings") + else + menu_items+=("4" "Settings") + fi + + TMP_CHOICE=$(whiptail \ + --backtitle "Proxmox VE Helper Scripts" \ + --title "Community-Scripts Options" \ + --ok-button "Select" --cancel-button "Exit Script" \ + --notags \ + --menu "\nChoose an option:\n Use TAB or Arrow keys to navigate, ENTER to select.\n" \ + 20 60 9 \ + "${menu_items[@]}" \ + --default-item "1" \ + 3>&1 1>&2 2>&3) || exit_script + CHOICE="$TMP_CHOICE" + fi + + # --- Main case --- + case "$CHOICE" in + 1 | default | DEFAULT) + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}" + VERBOSE="no" + METHOD="default" + base_settings "$VERBOSE" + echo_default + ensure_storage_selection_for_vars_file "/usr/local/community-scripts/default.vars" + ;; + 2 | advanced | ADVANCED) + header_info + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Install on node $PVEHOST_NAME${CL}" + METHOD="advanced" + base_settings + advanced_settings + ensure_storage_selection_for_vars_file "/usr/local/community-scripts/default.vars" + maybe_offer_save_app_defaults + ;; + 3 | mydefaults | MYDEFAULTS) + default_var_settings || { + msg_error "Failed to apply default.vars" + exit 1 + } + ensure_storage_selection_for_vars_file "/usr/local/community-scripts/default.vars" + ;; + 4 | appdefaults | APPDEFAULTS) + if [ -f "$(get_app_defaults_path)" ]; then + header_info + echo -e "${DEFAULT}${BOLD}${BL}Using App Defaults for ${APP} on node $PVEHOST_NAME${CL}" + METHOD="appdefaults" + base_settings + _load_vars_file "$(get_app_defaults_path)" + echo_default + ensure_storage_selection_for_vars_file "$(get_app_defaults_path)" + else + msg_error "No App Defaults available for ${APP}" + exit 1 + fi + ;; + 5 | settings | SETTINGS) + settings_menu + ;; + *) + echo -e "${CROSS}${RD}Invalid option: $CHOICE${CL}" + exit 1 + ;; + esac +} + +# ------------------------------------------------------------------------------ +# check_storage_or_prompt() +# +# - Validates container/template storage +# - If invalid or missing, prompts user to select new storage +# - Updates vars file accordingly +# ------------------------------------------------------------------------------ +# check_storage_or_prompt() { +# local vars_file="$1" +# local changed=0 + +# if [ ! -f "$vars_file" ]; then +# msg_warn "No vars file found at $vars_file" +# return 0 +# fi + +# # Helper: validate storage ID +# _validate_storage() { +# local s="$1" +# [ -n "$s" ] || return 1 +# pvesm status -content images | awk 'NR>1 {print $1}' | grep -qx "$s" +# } + +# # Load current values (empty if not set) +# local ct_store tpl_store +# ct_store=$(awk -F= '/^var_container_storage=/ {print $2; exit}' "$vars_file") +# tpl_store=$(awk -F= '/^var_template_storage=/ {print $2; exit}' "$vars_file") + +# # Container storage +# if ! _validate_storage "$ct_store"; then +# local new_ct +# new_ct=$(pvesm status -content images | awk 'NR>1 {print $1" "$2" "$6}') +# new_ct=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ +# --title "Select Container Storage" \ +# --menu "Choose container storage:" 20 60 10 $new_ct 3>&1 1>&2 2>&3) || return 0 +# if [ -n "$new_ct" ]; then +# sed -i "/^var_container_storage=/d" "$vars_file" +# echo "var_container_storage=$new_ct" >>"$vars_file" +# changed=1 +# msg_ok "Updated container storage in $vars_file → $new_ct" +# fi +# fi + +# # Template storage +# if ! _validate_storage "$tpl_store"; then +# local new_tpl +# new_tpl=$(pvesm status -content vztmpl | awk 'NR>1 {print $1" "$2" "$6}') +# new_tpl=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ +# --title "Select Template Storage" \ +# --menu "Choose template storage:" 20 60 10 $new_tpl 3>&1 1>&2 2>&3) || return 0 +# if [ -n "$new_tpl" ]; then +# sed -i "/^var_template_storage=/d" "$vars_file" +# echo "var_template_storage=$new_tpl" >>"$vars_file" +# changed=1 +# msg_ok "Updated template storage in $vars_file → $new_tpl" +# fi +# fi + +# # Always succeed (no aborts from here) +# return 0 +# } + +# # ------------------------------------------------------------------------------ +# # storage_settings_menu() +# # +# # - Menu for managing storage defaults +# # - Options: update My Defaults or App Defaults storage +# # ------------------------------------------------------------------------------ +# storage_settings_menu() { +# local vars_file="/usr/local/community-scripts/default.vars" + +# check_storage_or_prompt "$vars_file" +# _echo_storage_summary "$vars_file" + +# # Always ask user if they want to update, even if values are valid +# if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ +# --title "STORAGE SETTINGS" \ +# --yesno "Do you want to update the storage defaults?\n\nCurrent values will be kept unless you select new ones." 12 72; then + +# # container storage selection +# local new_ct +# new_ct=$(pvesm status -content images | awk 'NR>1 {print $1" "$2" "$6}') +# new_ct=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ +# --title "Select Container Storage" \ +# --menu "Choose container storage:" 20 60 10 $new_ct 3>&1 1>&2 2>&3) || true +# if [ -n "$new_ct" ]; then +# sed -i '/^var_container_storage=/d' "$vars_file" +# echo "var_container_storage=$new_ct" >>"$vars_file" +# msg_ok "Updated container storage → $new_ct" +# fi + +# # template storage selection +# local new_tpl +# new_tpl=$(pvesm status -content vztmpl | awk 'NR>1 {print $1" "$2" "$6}') +# new_tpl=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ +# --title "Select Template Storage" \ +# --menu "Choose template storage:" 20 60 10 $new_tpl 3>&1 1>&2 2>&3) || true +# if [ -n "$new_tpl" ]; then +# sed -i '/^var_template_storage=/d' "$vars_file" +# echo "var_template_storage=$new_tpl" >>"$vars_file" +# msg_ok "Updated template storage → $new_tpl" +# fi +# fi +# } + +edit_default_storage() { + local vf="/usr/local/community-scripts/default.vars" + + # make sure file exists + if [ ! -f "$vf" ]; then + msg_info "No default.vars found, creating $vf" + mkdir -p /usr/local/community-scripts + touch "$vf" + fi + + # reuse the same Whiptail selection we already have + ensure_storage_selection_for_vars_file "$vf" +} + +settings_menu() { + while true; do + local settings_items=( + "1" "Manage API-Diagnostic Setting" + "2" "Edit Default.vars" + "3" "Edit Default Storage" + ) + if [ -f "$(get_app_defaults_path)" ]; then + settings_items+=("4" "Edit App.vars for ${APP}") + settings_items+=("5" "Exit") + else + settings_items+=("4" "Exit") + fi + + local choice + choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" \ + --title "Community-Scripts SETTINGS Menu" \ + --ok-button "OK" --cancel-button "Back" \ + --menu "\n\nChoose a settings option:\n\nUse TAB or Arrow keys to navigate, ENTER to select." 20 60 9 \ + "${settings_items[@]}" \ + 3>&1 1>&2 2>&3) || break + + case "$choice" in + 1) diagnostics_menu ;; + 2) ${EDITOR:-nano} /usr/local/community-scripts/default.vars ;; + 3) edit_default_storage ;; + 4) + if [ -f "$(get_app_defaults_path)" ]; then + ${EDITOR:-nano} "$(get_app_defaults_path)" + else + exit_script + fi + ;; + 5) exit_script ;; + esac + done +} + +# ===== Unified storage selection & writing to vars files ===== +_write_storage_to_vars() { + # $1 = vars_file, $2 = key (var_container_storage / var_template_storage), $3 = value + local vf="$1" key="$2" val="$3" + # remove uncommented and commented versions to avoid duplicates + sed -i "/^[#[:space:]]*${key}=/d" "$vf" + echo "${key}=${val}" >>"$vf" +} + +choose_and_set_storage_for_file() { + # $1 = vars_file, $2 = class ('container'|'template') + local vf="$1" class="$2" key="" current="" + case "$class" in + container) key="var_container_storage" ;; + template) key="var_template_storage" ;; + *) + msg_error "Unknown storage class: $class" + return 1 + ;; + esac + + current=$(awk -F= -v k="^${key}=" '$0 ~ k {print $2; exit}' "$vf") + + # If only one storage exists for the content type, auto-pick. Else always ask (your wish #4). + local content="rootdir" + [[ "$class" == "template" ]] && content="vztmpl" + local count + count=$(pvesm status -content "$content" | awk 'NR>1{print $1}' | wc -l) + + if [[ "$count" -eq 1 ]]; then + STORAGE_RESULT=$(pvesm status -content "$content" | awk 'NR>1{print $1; exit}') + STORAGE_INFO="" + else + # If the current value is preselectable, we could show it, but per your requirement we always offer selection + select_storage "$class" || return 1 + fi + + _write_storage_to_vars "$vf" "$key" "$STORAGE_RESULT" + + # Keep environment in sync for later steps (e.g. app-default save) + if [[ "$class" == "container" ]]; then + export var_container_storage="$STORAGE_RESULT" + export CONTAINER_STORAGE="$STORAGE_RESULT" + else + export var_template_storage="$STORAGE_RESULT" + export TEMPLATE_STORAGE="$STORAGE_RESULT" + fi + + msg_ok "Updated ${key} → ${STORAGE_RESULT}" +} + +echo_storage_summary_from_file() { + local vars_file="$1" + local ct_store tpl_store + ct_store=$(awk -F= '/^var_container_storage=/ {print $2; exit}' "$vars_file") + tpl_store=$(awk -F= '/^var_template_storage=/ {print $2; exit}' "$vars_file") + if [ -n "$tpl" ]; then + TEMPLATE_STORAGE="$tpl" + else + choose_and_set_storage_for_file "$vf" template + fi + + if [ -n "$ct" ]; then + CONTAINER_STORAGE="$ct" + else + choose_and_set_storage_for_file "$vf" container + fi +} + +# ------------------------------------------------------------------------------ +# check_container_resources() +# +# - Compares host RAM/CPU with required values +# - Warns if under-provisioned and asks user to continue or abort +# ------------------------------------------------------------------------------ +check_container_resources() { + current_ram=$(free -m | awk 'NR==2{print $2}') + current_cpu=$(nproc) + + if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then + echo -e "\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}" + echo -e "${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\n" + echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? " + read -r prompt + if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then + echo -e "${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}" + exit 1 + fi + else + echo -e "" + fi +} + +# ------------------------------------------------------------------------------ +# check_container_storage() +# +# - Checks /boot partition usage +# - Warns if usage >80% and asks user confirmation before proceeding +# ------------------------------------------------------------------------------ +check_container_storage() { + total_size=$(df /boot --output=size | tail -n 1) + local used_size=$(df /boot --output=used | tail -n 1) + usage=$((100 * used_size / total_size)) + if ((usage > 80)); then + echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}" + echo -ne "Continue anyway? " + read -r prompt + if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then + echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}" + exit 1 + fi + fi +} + +# ------------------------------------------------------------------------------ +# ssh_extract_keys_from_file() +# +# - Extracts valid SSH public keys from given file +# - Supports RSA, Ed25519, ECDSA and filters out comments/invalid lines +# ------------------------------------------------------------------------------ +ssh_extract_keys_from_file() { + local f="$1" + [[ -r "$f" ]] || return 0 + tr -d '\r' <"$f" | awk ' + /^[[:space:]]*#/ {next} + /^[[:space:]]*$/ {next} + # nackt: typ base64 [comment] + /^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/ {print; next} + # mit Optionen: finde ab erstem Key-Typ + { + match($0, /(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/) + if (RSTART>0) { print substr($0, RSTART) } + } + ' +} + +# ------------------------------------------------------------------------------ +# ssh_build_choices_from_files() +# +# - Builds interactive whiptail checklist of available SSH keys +# - Generates fingerprint, type and comment for each key +# ------------------------------------------------------------------------------ +ssh_build_choices_from_files() { + local -a files=("$@") + CHOICES=() + COUNT=0 + MAPFILE="$(mktemp)" + local id key typ fp cmt base ln=0 + + for f in "${files[@]}"; do + [[ -f "$f" && -r "$f" ]] || continue + base="$(basename -- "$f")" + case "$base" in + known_hosts | known_hosts.* | config) continue ;; + id_*) [[ "$f" != *.pub ]] && continue ;; + esac + + # map every key in file + while IFS= read -r key; do + [[ -n "$key" ]] || continue + + typ="" + fp="" + cmt="" + # Only the pure key part (without options) is already included in ‘key’. + read -r _typ _b64 _cmt <<<"$key" + typ="${_typ:-key}" + cmt="${_cmt:-}" + # Fingerprint via ssh-keygen (if available) + if command -v ssh-keygen >/dev/null 2>&1; then + fp="$(printf '%s\n' "$key" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')" + fi + # Label shorten + [[ ${#cmt} -gt 40 ]] && cmt="${cmt:0:37}..." + + ln=$((ln + 1)) + COUNT=$((COUNT + 1)) + id="K${COUNT}" + echo "${id}|${key}" >>"$MAPFILE" + CHOICES+=("$id" "[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}" "OFF") + done < <(ssh_extract_keys_from_file "$f") + done +} + +# ------------------------------------------------------------------------------ +# ssh_discover_default_files() +# +# - Scans standard paths for SSH keys +# - Includes ~/.ssh/*.pub, /etc/ssh/authorized_keys, etc. +# ------------------------------------------------------------------------------ +ssh_discover_default_files() { + local -a cand=() + shopt -s nullglob + cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2) + cand+=(/root/.ssh/*.pub) + cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*) + shopt -u nullglob + printf '%s\0' "${cand[@]}" +} + +# ------------------------------------------------------------------------------ +# start() +# +# - Entry point of script +# - On Proxmox host: calls install_script +# - In silent mode: runs update_script +# - Otherwise: shows update/setting menu +# ------------------------------------------------------------------------------ +start() { + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) + if command -v pveversion >/dev/null 2>&1; then + install_script || return 0 + return 0 + elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then + VERBOSE="no" + set_std_mode + update_script + else + CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \ + "Support/Update functions for ${APP} LXC. Choose an option:" \ + 12 60 3 \ + "1" "YES (Silent Mode)" \ + "2" "YES (Verbose Mode)" \ + "3" "NO (Cancel Update)" --nocancel --default-item "1" 3>&1 1>&2 2>&3) + + case "$CHOICE" in + 1) + VERBOSE="no" + set_std_mode + ;; + 2) + VERBOSE="yes" + set_std_mode + ;; + 3) + clear + exit_script + exit + ;; + esac + update_script + fi +} + +# ------------------------------------------------------------------------------ +# build_container() +# +# - Creates and configures the LXC container +# - Builds network string and applies features (FUSE, TUN, VAAPI passthrough) +# - Starts container and waits for network connectivity +# - Installs base packages, SSH keys, and runs -install.sh +# ------------------------------------------------------------------------------ +build_container() { + # if [ "$VERBOSE" == "yes" ]; then set -x; fi + + NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}" + NET_STRING+="${MAC:+,hwaddr=$MAC}" + NET_STRING+=",ip=${NET:-dhcp}" + NET_STRING+="${GATE:+,gw=$GATE}" + NET_STRING+="${VLAN:+,tag=$VLAN}" + NET_STRING+="${MTU:+,mtu=$MTU}" + case "$IPV6_METHOD" in + auto) NET_STRING="$NET_STRING,ip6=auto" ;; + dhcp) NET_STRING="$NET_STRING,ip6=dhcp" ;; + static) + NET_STRING="$NET_STRING,ip6=$IPV6_ADDR" + [ -n "$IPV6_GATE" ] && NET_STRING="$NET_STRING,gw6=$IPV6_GATE" + ;; + none) ;; + esac + + if [ "$CT_TYPE" == "1" ]; then + FEATURES="keyctl=1,nesting=1" + else + FEATURES="nesting=1" + fi + + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES="$FEATURES,fuse=1" + fi + + #if [[ $DIAGNOSTICS == "yes" ]]; then + # post_to_api + #fi + + TEMP_DIR=$(mktemp -d) + pushd "$TEMP_DIR" >/dev/null + if [ "$var_os" == "alpine" ]; then + export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/alpine-install.func)" + else + export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)" + fi + export DIAGNOSTICS="$DIAGNOSTICS" + export RANDOM_UUID="$RANDOM_UUID" + export CACHER="$APT_CACHER" + export CACHER_IP="$APT_CACHER_IP" + export tz="$timezone" + #export DISABLEIPV6="$DISABLEIP6" + export APPLICATION="$APP" + export app="$NSAPP" + export PASSWORD="$PW" + export VERBOSE="$VERBOSE" + export SSH_ROOT="${SSH}" + export SSH_AUTHORIZED_KEY + export CTID="$CT_ID" + export CTTYPE="$CT_TYPE" + export ENABLE_FUSE="$ENABLE_FUSE" + export ENABLE_TUN="$ENABLE_TUN" + export PCT_OSTYPE="$var_os" + export PCT_OSVERSION="${var_version%%.*}" + export PCT_DISK_SIZE="$DISK_SIZE" + export PCT_OPTIONS=" + -features $FEATURES + -hostname $HN + -tags $TAGS + $SD + $NS + $NET_STRING + -onboot 1 + -cores $CORE_COUNT + -memory $RAM_SIZE + -unprivileged $CT_TYPE + $PW +" + export TEMPLATE_STORAGE="${var_template_storage:-}" + export CONTAINER_STORAGE="${var_container_storage:-}" + create_lxc_container || exit $? + + LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" + + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/passthrough.func) + select_hw_passthrough "$CTID" "$CT_TYPE" "$APP" + + # TUN device passthrough + if [ "$ENABLE_TUN" == "yes" ]; then + cat <>"$LXC_CONFIG" +lxc.cgroup2.devices.allow: c 10:200 rwm +lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file +EOF + fi + + # This starts the container and executes -install.sh + msg_info "Starting LXC Container" + pct start "$CTID" + + # wait for status 'running' + for i in {1..10}; do + if pct status "$CTID" | grep -q "status: running"; then + msg_ok "Started LXC Container" + break + fi + sleep 1 + if [ "$i" -eq 10 ]; then + msg_error "LXC Container did not reach running state" + exit 1 + fi + done + + if [ "$var_os" != "alpine" ]; then + msg_info "Waiting for network in LXC container" + + # --- Step 1: Wait for IP --- + for i in {1..20}; do + ip_in_lxc=$(pct exec "$CTID" -- ip -4 addr show dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1) + [ -n "$ip_in_lxc" ] && break + sleep 1 + done + + if [ -z "$ip_in_lxc" ]; then + msg_error "No IP assigned to CT $CTID after 20s" + exit 1 + fi + + # --- Step 2: Try to reach gateway --- + gw_ok=0 + for i in {1..10}; do + if pct exec "$CTID" -- ping -c1 -W1 "$GATEWAY" >/dev/null 2>&1; then + gw_ok=1 + break + fi + sleep 1 + done + + if [ "$gw_ok" -eq 1 ]; then + msg_ok "CT $CTID gateway $GATEWAY reachable (IP $ip_in_lxc)" + else + msg_warn "CT $CTID has IP $ip_in_lxc but gateway $GATEWAY did not reply" + fi + + # --- Step 3: DNS / Internet check --- + if pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1; then + msg_ok "Network in LXC is reachable (DNS OK, IP $ip_in_lxc)" + else + msg_warn "Network reachable (IP $ip_in_lxc), but DNS failed – applying fallback resolv.conf" + pct exec "$CTID" -- bash -c 'echo "nameserver 1.1.1.1" > /etc/resolv.conf && echo "nameserver 8.8.8.8" >> /etc/resolv.conf' + if pct exec "$CTID" -- getent hosts deb.debian.org >/dev/null 2>&1; then + msg_ok "Network reachable after DNS fallback" + else + msg_error "Still no DNS/network in LXC! Aborting customization." + exit_script + fi + fi + fi + + msg_info "Customizing LXC Container" + #if [ "$var_os" != "alpine" ]; then + # vaapi_inside_setup "$CTID" "$CT_TYPE" "$APP" + # nvidia_inside_setup "$CTID" "$CT_TYPE" "$APP" + #fi + if [ "$var_os" == "alpine" ]; then + sleep 3 + pct exec "$CTID" -- /bin/sh -c 'cat </etc/apk/repositories +http://dl-cdn.alpinelinux.org/alpine/latest-stable/main +http://dl-cdn.alpinelinux.org/alpine/latest-stable/community +EOF' + pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq >/dev/null" + else + sleep 3 + + pct exec "$CTID" -- bash -c "sed -i '/$LANG/ s/^# //' /etc/locale.gen" + pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \ + echo LANG=\$locale_line >/etc/default/locale && \ + locale-gen >/dev/null && \ + export LANG=\$locale_line" + + if [[ -z "${tz:-}" ]]; then + tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Etc/UTC") + fi + + if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then + pct exec "$CTID" -- bash -c "tz='$tz'; echo \"\$tz\" >/etc/timezone && ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime" + else + msg_warn "Skipping timezone setup – zone '$tz' not found in container" + fi + + pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 jq >/dev/null" || { + msg_error "apt-get base packages installation failed" + exit 1 + } + fi + msg_ok "Customized LXC Container" + install_ssh_keys_into_ct + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then + exit $? + fi +} + +destroy_lxc() { + if [[ -z "$CT_ID" ]]; then + msg_error "No CT_ID found. Nothing to remove." + return 1 + fi + + # Abbruch bei Ctrl-C / Ctrl-D / ESC + trap 'echo; msg_error "Aborted by user (SIGINT/SIGQUIT)"; return 130' INT QUIT + + local prompt + if ! read -rp "Remove this Container? " prompt; then + # read gibt != 0 zurück bei Ctrl-D/ESC + msg_error "Aborted input (Ctrl-D/ESC)" + return 130 + fi + + case "${prompt,,}" in + y | yes) + if pct stop "$CT_ID" &>/dev/null && pct destroy "$CT_ID" &>/dev/null; then + msg_ok "Removed Container $CT_ID" + else + msg_error "Failed to remove Container $CT_ID" + return 1 + fi + ;; + "" | n | no) + msg_info "Container was not removed." + ;; + *) + msg_warn "Invalid response. Container was not removed." + ;; + esac +} + +# ------------------------------------------------------------------------------ +# Storage discovery / selection helpers +# ------------------------------------------------------------------------------ +# ===== Storage discovery / selection helpers (ported from create_lxc.sh) ===== +resolve_storage_preselect() { + local class="$1" preselect="$2" required_content="" + case "$class" in + template) required_content="vztmpl" ;; + container) required_content="rootdir" ;; + *) return 1 ;; + esac + [[ -z "$preselect" ]] && return 1 + if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then + msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)" + return 1 + fi + + local line total used free + line="$(pvesm status | awk -v s="$preselect" 'NR>1 && $1==s {print $0}')" + if [[ -z "$line" ]]; then + STORAGE_INFO="n/a" + else + total="$(awk '{print $4}' <<<"$line")" + used="$(awk '{print $5}' <<<"$line")" + free="$(awk '{print $6}' <<<"$line")" + local total_h used_h free_h + if command -v numfmt >/dev/null 2>&1; then + total_h="$(numfmt --to=iec --suffix=B --format %.1f "$total" 2>/dev/null || echo "$total")" + used_h="$(numfmt --to=iec --suffix=B --format %.1f "$used" 2>/dev/null || echo "$used")" + free_h="$(numfmt --to=iec --suffix=B --format %.1f "$free" 2>/dev/null || echo "$free")" + STORAGE_INFO="Free: ${free_h} Used: ${used_h}" + else + STORAGE_INFO="Free: ${free} Used: ${used}" + fi + fi + STORAGE_RESULT="$preselect" + return 0 +} + +check_storage_support() { + local CONTENT="$1" VALID=0 + while IFS= read -r line; do + local STORAGE_NAME + STORAGE_NAME=$(awk '{print $1}' <<<"$line") + [[ -n "$STORAGE_NAME" ]] && VALID=1 + done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1') + [[ $VALID -eq 1 ]] +} + +select_storage() { + local CLASS=$1 CONTENT CONTENT_LABEL + case $CLASS in + container) + CONTENT='rootdir' + CONTENT_LABEL='Container' + ;; + template) + CONTENT='vztmpl' + CONTENT_LABEL='Container template' + ;; + iso) + CONTENT='iso' + CONTENT_LABEL='ISO image' + ;; + images) + CONTENT='images' + CONTENT_LABEL='VM Disk image' + ;; + backup) + CONTENT='backup' + CONTENT_LABEL='Backup' + ;; + snippets) + CONTENT='snippets' + CONTENT_LABEL='Snippets' + ;; + *) + msg_error "Invalid storage class '$CLASS'" + return 1 + ;; + esac + + declare -A STORAGE_MAP + local -a MENU=() + local COL_WIDTH=0 + + while read -r TAG TYPE _ TOTAL USED FREE _; do + [[ -n "$TAG" && -n "$TYPE" ]] || continue + local DISPLAY="${TAG} (${TYPE})" + local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED") + local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE") + local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B" + STORAGE_MAP["$DISPLAY"]="$TAG" + MENU+=("$DISPLAY" "$INFO" "OFF") + ((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY} + done < <(pvesm status -content "$CONTENT" | awk 'NR>1') + + if [[ ${#MENU[@]} -eq 0 ]]; then + msg_error "No storage found for content type '$CONTENT'." + return 2 + fi + + if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then + STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}" + STORAGE_INFO="${MENU[1]}" + return 0 + fi + + local WIDTH=$((COL_WIDTH + 42)) + while true; do + local DISPLAY_SELECTED + DISPLAY_SELECTED=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ + --title "Storage Pools" \ + --radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ + 16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { exit_script; } + + DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<"$DISPLAY_SELECTED") + if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then + whiptail --msgbox "No valid storage selected. Please try again." 8 58 + continue + fi + STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}" + for ((i = 0; i < ${#MENU[@]}; i += 3)); do + if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then + STORAGE_INFO="${MENU[$i + 1]}" + break + fi + done + return 0 + done +} + +create_lxc_container() { + # ------------------------------------------------------------------------------ + # Optional verbose mode (debug tracing) + # ------------------------------------------------------------------------------ + if [[ "${CREATE_LXC_VERBOSE:-no}" == "yes" ]]; then set -x; fi + + # ------------------------------------------------------------------------------ + # Helpers (dynamic versioning / template parsing) + # ------------------------------------------------------------------------------ + pkg_ver() { dpkg-query -W -f='${Version}\n' "$1" 2>/dev/null || echo ""; } + pkg_cand() { apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}'; } + + ver_ge() { dpkg --compare-versions "$1" ge "$2"; } + ver_gt() { dpkg --compare-versions "$1" gt "$2"; } + ver_lt() { dpkg --compare-versions "$1" lt "$2"; } + + # Extract Debian OS minor from template name: debian-13-standard_13.1-1_amd64.tar.zst => "13.1" + parse_template_osver() { sed -n 's/.*_\([0-9][0-9]*\(\.[0-9]\+\)\?\)-.*/\1/p' <<<"$1"; } + + # Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create + # Returns: + # 0 = no upgrade needed + # 1 = upgraded (and if do_retry=yes and retry succeeded, creation done) + # 2 = user declined + # 3 = upgrade attempted but failed OR retry failed + offer_lxc_stack_upgrade_and_maybe_retry() { + local do_retry="${1:-no}" # yes|no + local _pvec_i _pvec_c _lxcp_i _lxcp_c need=0 + + _pvec_i="$(pkg_ver pve-container)" + _lxcp_i="$(pkg_ver lxc-pve)" + _pvec_c="$(pkg_cand pve-container)" + _lxcp_c="$(pkg_cand lxc-pve)" + + if [[ -n "$_pvec_c" && "$_pvec_c" != "none" ]]; then + ver_gt "$_pvec_c" "${_pvec_i:-0}" && need=1 + fi + if [[ -n "$_lxcp_c" && "$_lxcp_c" != "none" ]]; then + ver_gt "$_lxcp_c" "${_lxcp_i:-0}" && need=1 + fi + if [[ $need -eq 0 ]]; then + msg_debug "No newer candidate for pve-container/lxc-pve (installed=$_pvec_i/$_lxcp_i, cand=$_pvec_c/$_lxcp_c)" + return 0 + fi + + echo + echo "An update for the Proxmox LXC stack is available:" + echo " pve-container: installed=${_pvec_i:-n/a} candidate=${_pvec_c:-n/a}" + echo " lxc-pve : installed=${_lxcp_i:-n/a} candidate=${_lxcp_c:-n/a}" + echo + read -rp "Do you want to upgrade now? [y/N] " _ans + case "${_ans,,}" in + y | yes) + msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)" + if apt-get update -qq >/dev/null && apt-get install -y --only-upgrade pve-container lxc-pve >/dev/null; then + msg_ok "LXC stack upgraded." + if [[ "$do_retry" == "yes" ]]; then + msg_info "Retrying container creation after upgrade" + if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then + msg_ok "Container created successfully after upgrade." + return 1 + else + msg_error "pct create still failed after upgrade. See $LOGFILE" + return 3 + fi + fi + return 1 + else + msg_error "Upgrade failed. Please check APT output." + return 3 + fi + ;; + *) return 2 ;; + esac + } + + # ------------------------------------------------------------------------------ + # Required input variables + # ------------------------------------------------------------------------------ + [[ "${CTID:-}" ]] || { + msg_error "You need to set 'CTID' variable." + exit 203 + } + [[ "${PCT_OSTYPE:-}" ]] || { + msg_error "You need to set 'PCT_OSTYPE' variable." + exit 204 + } + + msg_debug "CTID=$CTID" + msg_debug "PCT_OSTYPE=$PCT_OSTYPE" + msg_debug "PCT_OSVERSION=${PCT_OSVERSION:-default}" + + # ID checks + [[ "$CTID" -ge 100 ]] || { + msg_error "ID cannot be less than 100." + exit 205 + } + if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then + echo -e "ID '$CTID' is already in use." + unset CTID + msg_error "Cannot use ID that is already in use." + exit 206 + fi + + # Storage capability check + check_storage_support "rootdir" || { + msg_error "No valid storage found for 'rootdir' [Container]" + exit 1 + } + check_storage_support "vztmpl" || { + msg_error "No valid storage found for 'vztmpl' [Template]" + exit 1 + } + + # Template storage selection + if resolve_storage_preselect template "${TEMPLATE_STORAGE:-}"; then + TEMPLATE_STORAGE="$STORAGE_RESULT" + TEMPLATE_STORAGE_INFO="$STORAGE_INFO" + msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]" + else + while true; do + if select_storage template; then + TEMPLATE_STORAGE="$STORAGE_RESULT" + TEMPLATE_STORAGE_INFO="$STORAGE_INFO" + msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]" + break + fi + done + fi + + # Container storage selection + if resolve_storage_preselect container "${CONTAINER_STORAGE:-}"; then + CONTAINER_STORAGE="$STORAGE_RESULT" + CONTAINER_STORAGE_INFO="$STORAGE_INFO" + msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]" + else + while true; do + if select_storage container; then + CONTAINER_STORAGE="$STORAGE_RESULT" + CONTAINER_STORAGE_INFO="$STORAGE_INFO" + msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]" + break + fi + done + fi + + # Validate content types + msg_info "Validating content types of storage '$CONTAINER_STORAGE'" + STORAGE_CONTENT=$(grep -A4 -E "^(zfspool|dir|lvmthin|lvm): $CONTAINER_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs) + msg_debug "Storage '$CONTAINER_STORAGE' has content types: $STORAGE_CONTENT" + grep -qw "rootdir" <<<"$STORAGE_CONTENT" || { + msg_error "Storage '$CONTAINER_STORAGE' does not support 'rootdir'. Cannot create LXC." + exit 217 + } + $STD msg_ok "Storage '$CONTAINER_STORAGE' supports 'rootdir'" + + msg_info "Validating content types of template storage '$TEMPLATE_STORAGE'" + TEMPLATE_CONTENT=$(grep -A4 -E "^[^:]+: $TEMPLATE_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs) + msg_debug "Template storage '$TEMPLATE_STORAGE' has content types: $TEMPLATE_CONTENT" + if ! grep -qw "vztmpl" <<<"$TEMPLATE_CONTENT"; then + msg_warn "Template storage '$TEMPLATE_STORAGE' does not declare 'vztmpl'. This may cause pct create to fail." + else + $STD msg_ok "Template storage '$TEMPLATE_STORAGE' supports 'vztmpl'" + fi + + # Free space check + STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }') + REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024)) + [[ "$STORAGE_FREE" -ge "$REQUIRED_KB" ]] || { + msg_error "Not enough space on '$CONTAINER_STORAGE'. Needed: ${PCT_DISK_SIZE:-8}G." + exit 214 + } + + # Cluster quorum (if cluster) + if [[ -f /etc/pve/corosync.conf ]]; then + msg_info "Checking cluster quorum" + if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then + msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)." + exit 210 + fi + msg_ok "Cluster is quorate" + fi + + # ------------------------------------------------------------------------------ + # Template discovery & validation + # ------------------------------------------------------------------------------ + TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}" + case "$PCT_OSTYPE" in + debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;; + alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;; + *) TEMPLATE_PATTERN="" ;; + esac + + msg_info "Searching for template '$TEMPLATE_SEARCH'" + + mapfile -t LOCAL_TEMPLATES < <( + pveam list "$TEMPLATE_STORAGE" 2>/dev/null | + awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '$1 ~ s && $1 ~ p {print $1}' | + sed 's|.*/||' | sort -t - -k 2 -V + ) + + pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)." + mapfile -t ONLINE_TEMPLATES < <( + pveam available -section system 2>/dev/null | + sed -n "s/.*\($TEMPLATE_SEARCH.*$TEMPLATE_PATTERN.*\)/\1/p" | + sort -t - -k 2 -V + ) + ONLINE_TEMPLATE="" + [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" + + if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then + TEMPLATE="${LOCAL_TEMPLATES[-1]}" + TEMPLATE_SOURCE="local" + else + TEMPLATE="$ONLINE_TEMPLATE" + TEMPLATE_SOURCE="online" + fi + + TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)" + if [[ -z "$TEMPLATE_PATH" ]]; then + TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg) + [[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE" + fi + [[ -n "$TEMPLATE_PATH" ]] || { + msg_error "Unable to resolve template path for $TEMPLATE_STORAGE. Check storage type and permissions." + exit 220 + } + + msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]" + msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH" + + NEED_DOWNLOAD=0 + if [[ ! -f "$TEMPLATE_PATH" ]]; then + msg_info "Template not present locally – will download." + NEED_DOWNLOAD=1 + elif [[ ! -r "$TEMPLATE_PATH" ]]; then + msg_error "Template file exists but is not readable – check permissions." + exit 221 + elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then + if [[ -n "$ONLINE_TEMPLATE" ]]; then + msg_warn "Template file too small (<1MB) – re-downloading." + NEED_DOWNLOAD=1 + else + msg_warn "Template looks too small, but no online version exists. Keeping local file." + fi + elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then + if [[ -n "$ONLINE_TEMPLATE" ]]; then + msg_warn "Template appears corrupted – re-downloading." + NEED_DOWNLOAD=1 + else + msg_warn "Template appears corrupted, but no online version exists. Keeping local file." + fi + else + $STD msg_ok "Template $TEMPLATE is present and valid." + fi + + if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then + msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)" + if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then + TEMPLATE="$ONLINE_TEMPLATE" + NEED_DOWNLOAD=1 + else + msg_info "Continuing with local template $TEMPLATE" + fi + fi + + if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then + [[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH" + for attempt in {1..3}; do + msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE" + if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then + msg_ok "Template download successful." + break + fi + if [[ $attempt -eq 3 ]]; then + msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE" + exit 222 + fi + sleep $((attempt * 5)) + done + fi + + if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then + msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download." + exit 223 + fi + + # ------------------------------------------------------------------------------ + # Dynamic preflight for Debian 13.x: offer upgrade if available (no hard mins) + # ------------------------------------------------------------------------------ + if [[ "$PCT_OSTYPE" == "debian" ]]; then + OSVER="$(parse_template_osver "$TEMPLATE")" + if [[ -n "$OSVER" ]]; then + # Proactive, aber ohne Abbruch – nur Angebot + offer_lxc_stack_upgrade_and_maybe_retry "no" || true + fi + fi + + # ------------------------------------------------------------------------------ + # Create LXC Container + # ------------------------------------------------------------------------------ + msg_info "Creating LXC container" + + # Ensure subuid/subgid entries exist + grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid + grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid + + # Assemble pct options + PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}}) + [[ " ${PCT_OPTIONS[*]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}") + + # Lock by template file (avoid concurrent downloads/creates) + lockfile="/tmp/template.${TEMPLATE}.lock" + exec 9>"$lockfile" || { + msg_error "Failed to create lock file '$lockfile'." + exit 200 + } + flock -w 60 9 || { + msg_error "Timeout while waiting for template lock." + exit 211 + } + + LOGFILE="/tmp/pct_create_${CTID}.log" + msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" + msg_debug "Logfile: $LOGFILE" + + # First attempt + if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >"$LOGFILE" 2>&1; then + msg_error "Container creation failed on ${TEMPLATE_STORAGE}. Checking template..." + + # Validate template file + if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then + msg_warn "Template file too small or missing – re-downloading." + rm -f "$TEMPLATE_PATH" + pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" + elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then + if [[ -n "$ONLINE_TEMPLATE" ]]; then + msg_warn "Template appears corrupted – re-downloading." + rm -f "$TEMPLATE_PATH" + pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" + else + msg_warn "Template appears corrupted, but no online version exists. Skipping re-download." + fi + fi + + # Retry after repair + if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then + # Fallback to local storage + if [[ "$TEMPLATE_STORAGE" != "local" ]]; then + msg_warn "Retrying container creation with fallback to local storage..." + LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" + if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then + msg_info "Downloading template to local..." + pveam download local "$TEMPLATE" >/dev/null 2>&1 + fi + if pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then + msg_ok "Container successfully created using local fallback." + else + # --- Dynamic stack upgrade + auto-retry on the well-known error pattern --- + if grep -qiE 'unsupported .* version' "$LOGFILE"; then + echo + echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template." + echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically." + if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then + : # success after retry + else + rc=$? + case $rc in + 2) echo "Upgrade was declined. Please update and re-run: + apt update && apt install --only-upgrade pve-container lxc-pve" ;; + 3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;; + esac + exit 231 + fi + else + msg_error "Container creation failed even with local fallback. See $LOGFILE" + if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then + set -x + bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" + set +x + fi + exit 209 + fi + fi + else + msg_error "Container creation failed on local storage. See $LOGFILE" + # --- Dynamic stack upgrade + auto-retry on the well-known error pattern --- + if grep -qiE 'unsupported .* version' "$LOGFILE"; then + echo + echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template." + echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically." + if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then + : # success after retry + else + rc=$? + case $rc in + 2) echo "Upgrade was declined. Please update and re-run: + apt update && apt install --only-upgrade pve-container lxc-pve" ;; + 3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;; + esac + exit 231 + fi + else + if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then + set -x + bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" + set +x + fi + exit 209 + fi + fi + fi + fi + + # Verify container exists + pct list | awk '{print $1}' | grep -qx "$CTID" || { + msg_error "Container ID $CTID not listed in 'pct list'. See $LOGFILE" + exit 215 + } + + # Verify config rootfs + grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf" || { + msg_error "RootFS entry missing in container config. See $LOGFILE" + exit 216 + } + + msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." +} + +# ------------------------------------------------------------------------------ +# description() +# +# - Sets container description with HTML content (logo, links, badges) +# - Restarts ping-instances.service if present +# - Posts status "done" to API +# ------------------------------------------------------------------------------ +description() { + IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1) + + # Generate LXC Description + DESCRIPTION=$( + cat < + + Logo + + +

${APP} LXC

+ +

+ + spend Coffee + +

+ + + + GitHub + + + + Discussions + + + + Issues + + +EOF + ) + pct set "$CTID" -description "$DESCRIPTION" + + if [[ -f /etc/systemd/system/ping-instances.service ]]; then + systemctl start ping-instances.service + fi + + post_update_to_api "done" "none" +} + +# ------------------------------------------------------------------------------ +# api_exit_script() +# +# - Exit trap handler +# - Reports exit codes to API with detailed reason +# - Handles known codes (100–209) and maps them to errors +# ------------------------------------------------------------------------------ +api_exit_script() { + exit_code=$? + if [ $exit_code -ne 0 ]; then + case $exit_code in + 100) post_update_to_api "failed" "100: Unexpected error in create_lxc.sh" ;; + 101) post_update_to_api "failed" "101: No network connection detected in create_lxc.sh" ;; + 200) post_update_to_api "failed" "200: LXC creation failed in create_lxc.sh" ;; + 201) post_update_to_api "failed" "201: Invalid Storage class in create_lxc.sh" ;; + 202) post_update_to_api "failed" "202: User aborted menu in create_lxc.sh" ;; + 203) post_update_to_api "failed" "203: CTID not set in create_lxc.sh" ;; + 204) post_update_to_api "failed" "204: PCT_OSTYPE not set in create_lxc.sh" ;; + 205) post_update_to_api "failed" "205: CTID cannot be less than 100 in create_lxc.sh" ;; + 206) post_update_to_api "failed" "206: CTID already in use in create_lxc.sh" ;; + 207) post_update_to_api "failed" "207: Template not found in create_lxc.sh" ;; + 208) post_update_to_api "failed" "208: Error downloading template in create_lxc.sh" ;; + 209) post_update_to_api "failed" "209: Container creation failed, but template is intact in create_lxc.sh" ;; + *) post_update_to_api "failed" "Unknown error, exit code: $exit_code in create_lxc.sh" ;; + esac + fi +} + +if command -v pveversion >/dev/null 2>&1; then + trap 'api_exit_script' EXIT +fi +trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR +trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT +trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM diff --git a/misc/install.func b/misc/install.func new file mode 100644 index 00000000..f741b921 --- /dev/null +++ b/misc/install.func @@ -0,0 +1,206 @@ +# Copyright (c) 2021-2025 tteck +# Author: tteck (tteckster) +# Co-Author: MickLesk +# Co-Author: michelroegl-brunner +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE + +if ! command -v curl >/dev/null 2>&1; then + printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2 + apt-get update >/dev/null 2>&1 + apt-get install -y curl >/dev/null 2>&1 +fi +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func) +load_functions +catch_errors + +# This function enables IPv6 if it's not disabled and sets verbose mode +verb_ip6() { + set_std_mode # Set STD mode based on VERBOSE + + if [ "$DISABLEIPV6" == "yes" ]; then + echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf + $STD sysctl -p + fi +} + +# # This function sets error handling options and defines the error_handler function to handle errors +# catch_errors() { +# set -Eeuo pipefail +# trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +# } + +# # This function handles errors +# error_handler() { +# source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func) +# local exit_code="$1" +# local line_number="$2" +# local command="${3:-}" + +# if [[ "$exit_code" -eq 0 ]]; then +# return 0 +# fi + +# printf "\e[?25h" +# echo -e "\n${RD}[ERROR]${CL} in line ${RD}${line_number}${CL}: exit code ${RD}${exit_code}${CL}: while executing command ${YW}${command}${CL}\n" +# exit "$exit_code" +#} + +# This function sets up the Container OS by generating the locale, setting the timezone, and checking the network connection +setting_up_container() { + msg_info "Setting up Container OS" + for ((i = RETRY_NUM; i > 0; i--)); do + if [ "$(hostname -I)" != "" ]; then + break + fi + echo 1>&2 -en "${CROSS}${RD} No Network! " + sleep $RETRY_EVERY + done + if [ "$(hostname -I)" = "" ]; then + echo 1>&2 -e "\n${CROSS}${RD} No Network After $RETRY_NUM Tries${CL}" + echo -e "${NETWORK}Check Network Settings" + exit 1 + fi + rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED + systemctl disable -q --now systemd-networkd-wait-online.service + msg_ok "Set up Container OS" + #msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)" + msg_ok "Network Connected: ${BL}$(hostname -I)" +} + +# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected +network_check() { + set +e + trap - ERR + ipv4_connected=false + ipv6_connected=false + sleep 1 + + # Check IPv4 connectivity to Google, Cloudflare & Quad9 DNS servers. + if ping -c 1 -W 1 1.1.1.1 &>/dev/null || ping -c 1 -W 1 8.8.8.8 &>/dev/null || ping -c 1 -W 1 9.9.9.9 &>/dev/null; then + msg_ok "IPv4 Internet Connected" + ipv4_connected=true + else + msg_error "IPv4 Internet Not Connected" + fi + + # Check IPv6 connectivity to Google, Cloudflare & Quad9 DNS servers. + if ping6 -c 1 -W 1 2606:4700:4700::1111 &>/dev/null || ping6 -c 1 -W 1 2001:4860:4860::8888 &>/dev/null || ping6 -c 1 -W 1 2620:fe::fe &>/dev/null; then + msg_ok "IPv6 Internet Connected" + ipv6_connected=true + else + msg_error "IPv6 Internet Not Connected" + fi + + # If both IPv4 and IPv6 checks fail, prompt the user + if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then + read -r -p "No Internet detected, would you like to continue anyway? " prompt + if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then + echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" + else + echo -e "${NETWORK}Check Network Settings" + exit 1 + fi + fi + + # DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6) + GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org") + GIT_STATUS="Git DNS:" + DNS_FAILED=false + + for HOST in "${GIT_HOSTS[@]}"; do + RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1) + if [[ -z "$RESOLVEDIP" ]]; then + GIT_STATUS+="$HOST:($DNSFAIL)" + DNS_FAILED=true + else + GIT_STATUS+=" $HOST:($DNSOK)" + fi + done + + if [[ "$DNS_FAILED" == true ]]; then + fatal "$GIT_STATUS" + else + msg_ok "$GIT_STATUS" + fi + + set -e + trap 'error_handler $LINENO "$BASH_COMMAND"' ERR +} + +# This function updates the Container OS by running apt-get update and upgrade +update_os() { + msg_info "Updating Container OS" + if [[ "$CACHER" == "yes" ]]; then + echo "Acquire::http::Proxy-Auto-Detect \"/usr/local/bin/apt-proxy-detect.sh\";" >/etc/apt/apt.conf.d/00aptproxy + cat <<'EOF' >/usr/local/bin/apt-proxy-detect.sh +#!/bin/bash +if nc -w1 -z "${CACHER_IP}" 3142; then + echo -n "http://${CACHER_IP}:3142" +else + echo -n "DIRECT" +fi +EOF + chmod +x /usr/local/bin/apt-proxy-detect.sh + fi + $STD apt-get update + $STD apt-get -o Dpkg::Options::="--force-confold" -y dist-upgrade + rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED + msg_ok "Updated Container OS" + source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func) +} + +# This function modifies the message of the day (motd) and SSH settings +motd_ssh() { + grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc + + if [ -f "/etc/os-release" ]; then + OS_NAME=$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') + OS_VERSION=$(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"') + elif [ -f "/etc/debian_version" ]; then + OS_NAME="Debian" + OS_VERSION=$(cat /etc/debian_version) + fi + + PROFILE_FILE="/etc/profile.d/00_lxc-details.sh" + echo "echo -e \"\"" >"$PROFILE_FILE" + echo -e "echo -e \"${BOLD}${YW}${APPLICATION} LXC Container - DEV Repository${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${RD}WARNING: This is a DEVELOPMENT version (ProxmoxVED). Do NOT use in production!${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${YW} OS: ${GN}${OS_NAME} - Version: ${OS_VERSION}${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${YW} Hostname: ${GN}\$(hostname)${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${YW} IP Address: ${GN}\$(hostname -I | awk '{print \$1}')${CL}\"" >>"$PROFILE_FILE" + echo -e "echo -e \"${YW} Repository: ${GN}https://github.com/community-scripts/ProxmoxVED${CL}\"" >>"$PROFILE_FILE" + echo "echo \"\"" >>"$PROFILE_FILE" + + chmod -x /etc/update-motd.d/* + + if [[ "${SSH_ROOT}" == "yes" ]]; then + sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config + systemctl restart sshd + fi +} + +# This function customizes the container by modifying the getty service and enabling auto-login for the root user +customize() { + if [[ "$PASSWORD" == "" ]]; then + msg_info "Customizing Container" + GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" + mkdir -p $(dirname $GETTY_OVERRIDE) + cat <$GETTY_OVERRIDE + [Service] + ExecStart= + ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM +EOF + systemctl daemon-reload + systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') + msg_ok "Customized Container" + fi + echo "bash -c \"\$(curl -fsSL https://github.com/community-scripts/ProxmoxVED/raw/main/ct/${app}.sh)\"" >/usr/bin/update + chmod +x /usr/bin/update + if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then + mkdir -p /root/.ssh + echo "${SSH_AUTHORIZED_KEY}" >/root/.ssh/authorized_keys + chmod 700 /root/.ssh + chmod 600 /root/.ssh/authorized_keys + fi +} From d6b981a1b2f0bb44bfc9014dc731176160b3b482 Mon Sep 17 00:00:00 2001 From: Vincent <114195376+HydroshieldMKII@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:33:56 -0400 Subject: [PATCH 41/41] Update guardian.sh to source build.func from the correct repository --- ct/guardian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ct/guardian.sh b/ct/guardian.sh index c33a5c06..41fbaf30 100755 --- a/ct/guardian.sh +++ b/ct/guardian.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/HydroshieldMKII/ProxmoxVED/refs/heads/add-guardian-app/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: HydroshieldMKII # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE