From bd25fb79ddfee74c56a4310fc1b70ef6c57e36be Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 26 Oct 2025 16:19:38 +0530 Subject: [PATCH 01/54] feat(ente): install and configure ente cli, dynamically export backend urls for frontend --- install/ente-install.sh | 61 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index 8a4b06ab4..22b4e1381 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -44,6 +44,30 @@ $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC';" } >>~/ente.creds msg_ok "Set up PostgreSQL" +# Download Ente cli +msg_info "Downloading Ente CLI" +$STD mkdir -p /opt/ente/cli/dist +fetch_and_deploy_gh_release "ente" "ente-io/ente" "prebuild" "cli-v0.2.3" "/opt/ente/cli/dist" "ente-cli-v0.2.3-linux-amd64.tar.gz" +$STD chmod +x /opt/ente/cli/dist/ente +msg_ok "Downloaded Ente CLI" + +msg_info "Configuring Ente CLI" +$STD export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt +$STD export PATH="/opt/ente/cli/dist:$PATH" +cat <>~/.bashrc +export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt +export PATH="/opt/ente/cli/dist:$PATH" +EOF +msg_ok "Exported Ente CLI paths" + +cat <~/.ente/config.yaml +endpoint: + api: http://localhost:8080 +EOF +msg_ok "Created Ente CLI config.yaml" + +msg_ok "Configured Ente CLI" + msg_info "Building Museum (server)" cd /opt/ente/server $STD corepack enable @@ -101,11 +125,42 @@ jwt: EOF msg_ok "Created museum.yaml" +# Prompt for backend URL +read -r -p "Enter the public URL for Ente backend (e.g., https://api.ente.yourdomain.com or http://192.168.1.100:8080) leave empty to use container IP: " backend_url +if [[ -z "$backend_url" ]]; then + # Default to local IP if user doesn't provide one + LOCAL_IP=$(hostname -I | awk '{print $1}') + ENTE_BACKEND_URL="http://$LOCAL_IP:8080" + msg_info "No URL provided, using local IP: $ENTE_BACKEND_URL" +else + ENTE_BACKEND_URL="$backend_url" + msg_info "Using provided URL: $ENTE_BACKEND_URL" +fi + +# Prompt for albums URL +read -r -p "Enter the public URL for Ente albums (e.g., https://albums.ente.yourdomain.com or http://192.168.1.100:3002) leave empty to use container IP: " albums_url +if [[ -z "$albums_url" ]]; then + LOCAL_IP=$(hostname -I | awk '{print $1}') + ENTE_ALBUMS_URL="http://$LOCAL_IP:3002" + msg_info "No URL provided, using local IP: $ENTE_ALBUMS_URL" +else + ENTE_ALBUMS_URL="$albums_url" + msg_info "Using provided URL: $ENTE_ALBUMS_URL" +fi + +export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL +export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL + +# save to bashrc +cat <>~/.bashrc +export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL +export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL +EOF +msg_ok "Saved to bashrc" + msg_info "Building Web Applications" cd /opt/ente/web $STD yarn install -export NEXT_PUBLIC_ENTE_ENDPOINT=http://localhost:8080 -export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=http://localhost:3002 $STD yarn build $STD yarn build:accounts $STD yarn build:auth @@ -172,3 +227,5 @@ msg_info "Cleaning up" $STD apt-get -y autoremove $STD apt-get -y autoclean msg_ok "Cleaned" + +msg_info "If you want to use the Ente CLI, please follow the instructions at https://ente.io/help/self-hosting/administration/cli" From 54bf19ce9f7a7350badecd3118e81d04f45569ac Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 26 Oct 2025 16:44:46 +0530 Subject: [PATCH 02/54] temp: update build.func to use forked install script --- ct/ente.sh | 2 +- misc/build.func | 2 +- misc/install.func | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ct/ente.sh b/ct/ente.sh index 5733886ca..31d6858ae 100644 --- a/ct/ente.sh +++ b/ct/ente.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) +source <(curl -fsSL https://raw.githubusercontent.com/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: MickLesk (CanbiZ) # License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE diff --git a/misc/build.func b/misc/build.func index f8fcaa5fb..2881bb5f9 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2514,7 +2514,7 @@ EOF' install_ssh_keys_into_ct # Run application installer - 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/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/install/${var_install}.sh)"; then exit $? fi } diff --git a/misc/install.func b/misc/install.func index f741b921d..71edf7ebf 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/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/misc/core.func) +source <(curl -fsSL https://raw.githubusercontent.com/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/misc/error_handler.func) load_functions catch_errors From 9ee64e97f1c68cf304dc35b2c24d48b094bebfed Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 26 Oct 2025 16:57:12 +0530 Subject: [PATCH 03/54] fix(ente): cli config.yml creation error --- install/ente-install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index 22b4e1381..bcb490c33 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -59,7 +59,8 @@ export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt export PATH="/opt/ente/cli/dist:$PATH" EOF msg_ok "Exported Ente CLI paths" - +$STD mkdir -p ~/.ente +$STD touch ~/.ente/config.yaml cat <~/.ente/config.yaml endpoint: api: http://localhost:8080 From eb401a3e4277daaf00d66d23a43b00513d0d851e Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 26 Oct 2025 17:07:45 +0530 Subject: [PATCH 04/54] fix(ente): errors --- install/ente-install.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index bcb490c33..e885c676b 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -59,7 +59,6 @@ export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt export PATH="/opt/ente/cli/dist:$PATH" EOF msg_ok "Exported Ente CLI paths" -$STD mkdir -p ~/.ente $STD touch ~/.ente/config.yaml cat <~/.ente/config.yaml endpoint: From a4d5b5a21f5a890f241c06214766cbee0faed17e Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 26 Oct 2025 17:19:44 +0530 Subject: [PATCH 05/54] fix(ente): remove unexpected .ente txt file being created during installation --- install/ente-install.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index e885c676b..3a6d157ac 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -59,7 +59,9 @@ export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt export PATH="/opt/ente/cli/dist:$PATH" EOF msg_ok "Exported Ente CLI paths" -$STD touch ~/.ente/config.yaml +# remove .ente txt file +$STD rm ~/.ente +$STD mkdir -p ~/.ente cat <~/.ente/config.yaml endpoint: api: http://localhost:8080 From 3489dfb8934c9f91e3d91fcc350422b13c724a82 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sat, 22 Nov 2025 22:04:50 +0530 Subject: [PATCH 06/54] chore: clean logs --- ct/ente.sh | 2 +- install/ente-install.sh | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ct/ente.sh b/ct/ente.sh index 31d6858ae..126f673dd 100644 --- a/ct/ente.sh +++ b/ct/ente.sh @@ -39,4 +39,4 @@ build_container description msg_ok "Completed Successfully!" -msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!" +msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!\nIf you want to use the Ente CLI to add/whitelist admins, please follow the instructions at https://ente.io/help/self-hosting/administration/cli" diff --git a/install/ente-install.sh b/install/ente-install.sh index 3a6d157ac..7af4f173c 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -133,10 +133,10 @@ if [[ -z "$backend_url" ]]; then # Default to local IP if user doesn't provide one LOCAL_IP=$(hostname -I | awk '{print $1}') ENTE_BACKEND_URL="http://$LOCAL_IP:8080" - msg_info "No URL provided, using local IP: $ENTE_BACKEND_URL" + msg_info "No URL provided, using local IP: $ENTE_BACKEND_URL\n" else ENTE_BACKEND_URL="$backend_url" - msg_info "Using provided URL: $ENTE_BACKEND_URL" + msg_info "Using provided URL: $ENTE_BACKEND_URL\n" fi # Prompt for albums URL @@ -144,10 +144,10 @@ read -r -p "Enter the public URL for Ente albums (e.g., https://albums.ente.your if [[ -z "$albums_url" ]]; then LOCAL_IP=$(hostname -I | awk '{print $1}') ENTE_ALBUMS_URL="http://$LOCAL_IP:3002" - msg_info "No URL provided, using local IP: $ENTE_ALBUMS_URL" + msg_info "No URL provided, using local IP: $ENTE_ALBUMS_URL\n" else ENTE_ALBUMS_URL="$albums_url" - msg_info "Using provided URL: $ENTE_ALBUMS_URL" + msg_info "Using provided URL: $ENTE_ALBUMS_URL\n" fi export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL @@ -160,7 +160,7 @@ export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL EOF msg_ok "Saved to bashrc" -msg_info "Building Web Applications" +msg_info "Building Web Applications\n" cd /opt/ente/web $STD yarn install $STD yarn build @@ -229,5 +229,3 @@ msg_info "Cleaning up" $STD apt-get -y autoremove $STD apt-get -y autoclean msg_ok "Cleaned" - -msg_info "If you want to use the Ente CLI, please follow the instructions at https://ente.io/help/self-hosting/administration/cli" From 9478807cc25f4069e71446955719c12db4b878dc Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sat, 22 Nov 2025 22:50:19 +0530 Subject: [PATCH 07/54] feat: add ente.json for FE --- frontend/public/json/ente.json | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 frontend/public/json/ente.json diff --git a/frontend/public/json/ente.json b/frontend/public/json/ente.json new file mode 100644 index 000000000..378d3e2db --- /dev/null +++ b/frontend/public/json/ente.json @@ -0,0 +1,44 @@ +{ + "name": "Ente", + "slug": "ente", + "categories": [ + 20 + ], + "date_created": "2025-11-22", + "type": "ct", + "updateable": false, + "privileged": false, + "config_path": "/opt", + "interface_port": 3000, + "documentation": "https://github.com/ente-io/ente", + "website": "https://ente.io/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ente-photos.svg", + "description": "Ente is a service that provides a fully open source, end-to-end encrypted platform for you to store your data in the cloud without needing to trust the service provider. On top of this platform, we have built two apps so far: Ente Photos (an alternative to Apple and Google Photos) and Ente Auth (a 2FA alternative to the deprecated Authy).", + "install_methods": [ + { + "type": "default", + "script": "ct/ente.sh", + "resources": { + "cpu": 4, + "ram": 4096, + "hdd": 10, + "os": "debian", + "version": "12" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [ + { + "text": "Please use `journalctl -u ente-museum.service -n 10` to read logs for the signup verification code", + "type": "info" + }, + { + "text": "To see Museium config: `cat /opt/ente/server/museum.yaml`", + "type": "info" + } + ] +} From b948cae78651fe26ebc4da873252dccf62aa6307 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 18:13:52 +0530 Subject: [PATCH 08/54] Revert "temp: update build.func to use forked install script" This reverts commit 54bf19ce9f7a7350badecd3118e81d04f45569ac. --- ct/ente.sh | 2 +- misc/build.func | 2 +- misc/install.func | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ct/ente.sh b/ct/ente.sh index 126f673dd..14aeae342 100644 --- a/ct/ente.sh +++ b/ct/ente.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -source <(curl -fsSL https://raw.githubusercontent.com/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/misc/build.func) +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: MickLesk (CanbiZ) # License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE diff --git a/misc/build.func b/misc/build.func index 2881bb5f9..f8fcaa5fb 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2514,7 +2514,7 @@ EOF' install_ssh_keys_into_ct # Run application installer - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/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 71edf7ebf..f741b921d 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/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/misc/core.func) -source <(curl -fsSL https://raw.githubusercontent.com/dramikei/ProxmoxVED/refs/heads/dramikei/ente-enhancement/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 From fb84ff1110c9b70592eadbea8b07f22960a4032d Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 18:35:07 +0530 Subject: [PATCH 09/54] fix: dont save NEXT vars in bashrc, add url prompt in rebuild script --- install/ente-install.sh | 45 ++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index bddfc1b31..c8e27bbcf 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -185,13 +185,6 @@ fi export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL -# save to bashrc -cat <>~/.bashrc -export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL -export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL -EOF -msg_ok "Saved to bashrc" - msg_info "Building Web Applications\n" cd /opt/ente/web $STD yarn install @@ -208,13 +201,36 @@ cp -r apps/cast/out /var/www/ente/apps/cast # Save build configuration for future rebuilds cat </opt/ente/rebuild-frontend.sh #!/usr/bin/env bash -# Rebuild Ente frontend with current IP -CONTAINER_IP=\$(hostname -I | awk '{print \$1}') -echo "Building frontend with IP: \$CONTAINER_IP" +# Rebuild Ente frontend +# Prompt for backend URL +read -r -p "Enter the public URL for Ente backend (e.g., https://api.ente.yourdomain.com or http://192.168.1.100:8080) leave empty to use container IP: " backend_url +if [[ -z "\$backend_url" ]]; then + # Default to local IP if user doesn't provide one + LOCAL_IP=$(hostname -I | awk '{print $1}') + ENTE_BACKEND_URL="http://\$LOCAL_IP:8080" + echo "No URL provided, using local IP: \$ENTE_BACKEND_URL\n" +else + ENTE_BACKEND_URL="\$backend_url" + echo "Using provided URL: \$ENTE_BACKEND_URL\n" +fi + +# Prompt for albums URL +read -r -p "Enter the public URL for Ente albums (e.g., https://albums.ente.yourdomain.com or http://192.168.1.100:3002) leave empty to use container IP: " albums_url +if [[ -z "\$albums_url" ]]; then + LOCAL_IP=\$(hostname -I | awk '{print $1}') + ENTE_ALBUMS_URL="http://\$LOCAL_IP:3002" + echo "No URL provided, using local IP: \$ENTE_ALBUMS_URL\n" +else + ENTE_ALBUMS_URL="\$albums_url" + echo "Using provided URL: \$ENTE_ALBUMS_URL\n" +fi + +export NEXT_PUBLIC_ENTE_ENDPOINT=\$ENTE_BACKEND_URL +export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=\$ENTE_ALBUMS_URL + +echo "Building Web Applications\n" + cd /opt/ente/web -export -=http://\${CONTAINER_IP}:8080 -export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=http://\${CONTAINER_IP}:3002 yarn build yarn build:accounts yarn build:auth @@ -368,8 +384,9 @@ echo -e "━━━━━━━━━━━━━━━━━━━━━━━ echo -e "\n${BL}Access URLs:${CL}" echo -e " Photos: http://${CONTAINER_IP}:3000" echo -e " Accounts: http://${CONTAINER_IP}:3001" +echo -e " Albums: ${ENTE_ALBUMS_URL}" echo -e " Auth: http://${CONTAINER_IP}:3003" -echo -e " API: http://${CONTAINER_IP}:8080" +echo -e " API: ${ENTE_BACKEND_URL}" echo -e "\n${YW}⚠️ Important Post-Installation Steps:${CL}" echo -e "\n${BL}1. Create your first account:${CL}" echo -e " • Open http://${CONTAINER_IP}:3000 in your browser" From 92c827ed9a9a9bd0513da35c76a40bb00b663dbf Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 18:39:42 +0530 Subject: [PATCH 10/54] feat: move add/whitelist admins message to website --- ct/ente.sh | 2 +- frontend/public/json/ente.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ct/ente.sh b/ct/ente.sh index 14aeae342..5733886ca 100644 --- a/ct/ente.sh +++ b/ct/ente.sh @@ -39,4 +39,4 @@ build_container description msg_ok "Completed Successfully!" -msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!\nIf you want to use the Ente CLI to add/whitelist admins, please follow the instructions at https://ente.io/help/self-hosting/administration/cli" +msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!" diff --git a/frontend/public/json/ente.json b/frontend/public/json/ente.json index 378d3e2db..794580914 100644 --- a/frontend/public/json/ente.json +++ b/frontend/public/json/ente.json @@ -36,6 +36,10 @@ "text": "Please use `journalctl -u ente-museum.service -n 10` to read logs for the signup verification code", "type": "info" }, + { + "text": "If you want to use the Ente CLI to add/whitelist admins, please follow the instructions at https://ente.io/help/self-hosting/administration/cli", + "type": "info" + }, { "text": "To see Museium config: `cat /opt/ente/server/museum.yaml`", "type": "info" From 4509406b0c623c0dc0f238ba8a2a14e5b847e304 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 18:45:07 +0530 Subject: [PATCH 11/54] fix: each msg_info must have msg_ok --- install/ente-install.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index c8e27bbcf..b1f2cb8ab 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -80,16 +80,12 @@ cat <>~/.bashrc export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt export PATH="/opt/ente/cli/dist:$PATH" EOF -msg_ok "Exported Ente CLI paths" -# remove .ente txt file $STD rm ~/.ente $STD mkdir -p ~/.ente cat <~/.ente/config.yaml endpoint: api: http://localhost:8080 EOF -msg_ok "Created Ente CLI config.yaml" - msg_ok "Configured Ente CLI" msg_info "Building Museum (server)" @@ -165,10 +161,12 @@ if [[ -z "$backend_url" ]]; then # Default to local IP if user doesn't provide one LOCAL_IP=$(hostname -I | awk '{print $1}') ENTE_BACKEND_URL="http://$LOCAL_IP:8080" - msg_info "No URL provided, using local IP: $ENTE_BACKEND_URL\n" + msg_info "No URL provided" + msg_ok "using local IP: $ENTE_BACKEND_URL\n" else ENTE_BACKEND_URL="$backend_url" - msg_info "Using provided URL: $ENTE_BACKEND_URL\n" + msg_info "URL provided" + msg_ok "Using provided URL: $ENTE_BACKEND_URL\n" fi # Prompt for albums URL @@ -176,10 +174,12 @@ read -r -p "Enter the public URL for Ente albums (e.g., https://albums.ente.your if [[ -z "$albums_url" ]]; then LOCAL_IP=$(hostname -I | awk '{print $1}') ENTE_ALBUMS_URL="http://$LOCAL_IP:3002" - msg_info "No URL provided, using local IP: $ENTE_ALBUMS_URL\n" + msg_info "No URL provided" + msg_ok "using local IP: $ENTE_ALBUMS_URL\n" else ENTE_ALBUMS_URL="$albums_url" - msg_info "Using provided URL: $ENTE_ALBUMS_URL\n" + msg_info "URL provided" + msg_ok "Using provided URL: $ENTE_ALBUMS_URL\n" fi export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL From 755b17d5ec17672148fc26e4081144ae2ec92f2e Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 18:47:48 +0530 Subject: [PATCH 12/54] chore: rm comments --- install/ente-install.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index b1f2cb8ab..e64c7ee63 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -66,7 +66,6 @@ $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC';" } >>~/ente.creds msg_ok "Set up PostgreSQL" -# Download Ente cli msg_info "Downloading Ente CLI" $STD mkdir -p /opt/ente/cli/dist fetch_and_deploy_gh_release "ente" "ente-io/ente" "prebuild" "cli-v0.2.3" "/opt/ente/cli/dist" "ente-cli-v0.2.3-linux-amd64.tar.gz" @@ -155,10 +154,8 @@ jwt: EOF msg_ok "Created museum.yaml" -# Prompt for backend URL read -r -p "Enter the public URL for Ente backend (e.g., https://api.ente.yourdomain.com or http://192.168.1.100:8080) leave empty to use container IP: " backend_url if [[ -z "$backend_url" ]]; then - # Default to local IP if user doesn't provide one LOCAL_IP=$(hostname -I | awk '{print $1}') ENTE_BACKEND_URL="http://$LOCAL_IP:8080" msg_info "No URL provided" @@ -169,7 +166,6 @@ else msg_ok "Using provided URL: $ENTE_BACKEND_URL\n" fi -# Prompt for albums URL read -r -p "Enter the public URL for Ente albums (e.g., https://albums.ente.yourdomain.com or http://192.168.1.100:3002) leave empty to use container IP: " albums_url if [[ -z "$albums_url" ]]; then LOCAL_IP=$(hostname -I | awk '{print $1}') @@ -205,7 +201,6 @@ cat </opt/ente/rebuild-frontend.sh # Prompt for backend URL read -r -p "Enter the public URL for Ente backend (e.g., https://api.ente.yourdomain.com or http://192.168.1.100:8080) leave empty to use container IP: " backend_url if [[ -z "\$backend_url" ]]; then - # Default to local IP if user doesn't provide one LOCAL_IP=$(hostname -I | awk '{print $1}') ENTE_BACKEND_URL="http://\$LOCAL_IP:8080" echo "No URL provided, using local IP: \$ENTE_BACKEND_URL\n" From d0846b5c3bfb6732e9a8b65ed453a92795b6c7b8 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 18:52:27 +0530 Subject: [PATCH 13/54] fix: source bashrc after writing to it instead of exporting again in shell --- install/ente-install.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index e64c7ee63..d26c9dd6e 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -73,12 +73,11 @@ $STD chmod +x /opt/ente/cli/dist/ente msg_ok "Downloaded Ente CLI" msg_info "Configuring Ente CLI" -$STD export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt -$STD export PATH="/opt/ente/cli/dist:$PATH" cat <>~/.bashrc export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt export PATH="/opt/ente/cli/dist:$PATH" EOF +$STD source ~/.bashrc $STD rm ~/.ente $STD mkdir -p ~/.ente cat <~/.ente/config.yaml From bf38c3b91913ba767c3fe8198614fd0632de5981 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 19:00:35 +0530 Subject: [PATCH 14/54] fix: rename ente cli appName and dont delete the versioning file --- install/ente-install.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index d26c9dd6e..76750de1b 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -68,7 +68,7 @@ msg_ok "Set up PostgreSQL" msg_info "Downloading Ente CLI" $STD mkdir -p /opt/ente/cli/dist -fetch_and_deploy_gh_release "ente" "ente-io/ente" "prebuild" "cli-v0.2.3" "/opt/ente/cli/dist" "ente-cli-v0.2.3-linux-amd64.tar.gz" +fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "cli-v0.2.3" "/opt/ente/cli/dist" "ente-cli-v0.2.3-linux-amd64.tar.gz" $STD chmod +x /opt/ente/cli/dist/ente msg_ok "Downloaded Ente CLI" @@ -78,7 +78,6 @@ export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt export PATH="/opt/ente/cli/dist:$PATH" EOF $STD source ~/.bashrc -$STD rm ~/.ente $STD mkdir -p ~/.ente cat <~/.ente/config.yaml endpoint: From 8a0f2cdeaf8cb6b501c2f14eb3000dffdd47fc87 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 19:20:55 +0530 Subject: [PATCH 15/54] fix: ente-cli installation --- install/ente-install.sh | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index 76750de1b..ef1d08581 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -28,8 +28,21 @@ PG_VERSION="17" setup_postgresql setup_go NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs ENTE_CLI_VERSION=$(curl -s https://api.github.com/repos/ente-io/ente/releases | jq -r '[.[] | select(.tag_name | startswith("cli-v"))][0].tag_name') -fetch_and_deploy_gh_release "ente" "ente-io/ente" "tarball" "latest" "/opt/ente" -fetch_and_deploy_gh_release "ente" "ente-io/ente" "tarball" "$ENTE_CLI_VERSION" "/usr/local/bin/ente" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" +fetch_and_deploy_gh_release "ente-server" "ente-io/ente" "tarball" "latest" "/opt/ente" +fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "tarball" "$ENTE_CLI_VERSION" "/usr/local/bin/ente" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" + +$STD mkdir -p /opt/ente/cli +msg_info "Configuring Ente CLI" +cat <>~/.bashrc +export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/secrets.txt +EOF +$STD source ~/.bashrc +$STD mkdir -p ~/.ente +cat <~/.ente/config.yaml +endpoint: + api: http://localhost:8080 +EOF +msg_ok "Configured Ente CLI" msg_info "Setting up PostgreSQL" DB_NAME="ente_db" @@ -66,25 +79,6 @@ $STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC';" } >>~/ente.creds msg_ok "Set up PostgreSQL" -msg_info "Downloading Ente CLI" -$STD mkdir -p /opt/ente/cli/dist -fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "cli-v0.2.3" "/opt/ente/cli/dist" "ente-cli-v0.2.3-linux-amd64.tar.gz" -$STD chmod +x /opt/ente/cli/dist/ente -msg_ok "Downloaded Ente CLI" - -msg_info "Configuring Ente CLI" -cat <>~/.bashrc -export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/dist/secrets.txt -export PATH="/opt/ente/cli/dist:$PATH" -EOF -$STD source ~/.bashrc -$STD mkdir -p ~/.ente -cat <~/.ente/config.yaml -endpoint: - api: http://localhost:8080 -EOF -msg_ok "Configured Ente CLI" - msg_info "Building Museum (server)" cd /opt/ente/server $STD corepack enable From 42fbb303d065b5900d62c8d0ba9653a0f04f3b4c Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 19:30:14 +0530 Subject: [PATCH 16/54] fix: log --- install/ente-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index ef1d08581..e30856389 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -173,7 +173,7 @@ fi export NEXT_PUBLIC_ENTE_ENDPOINT=$ENTE_BACKEND_URL export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL -msg_info "Building Web Applications\n" +msg_info "Building Web Applications" cd /opt/ente/web $STD yarn install $STD yarn build From 5ebb77750ac15a9cff040610ddca637fd84e74a7 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 20:35:39 +0530 Subject: [PATCH 17/54] fix: cli install --- install/ente-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index e30856389..f75bbedff 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -29,7 +29,7 @@ setup_go NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs ENTE_CLI_VERSION=$(curl -s https://api.github.com/repos/ente-io/ente/releases | jq -r '[.[] | select(.tag_name | startswith("cli-v"))][0].tag_name') fetch_and_deploy_gh_release "ente-server" "ente-io/ente" "tarball" "latest" "/opt/ente" -fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "tarball" "$ENTE_CLI_VERSION" "/usr/local/bin/ente" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" +fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "$ENTE_CLI_VERSION" "/usr/local/bin/ente" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" $STD mkdir -p /opt/ente/cli msg_info "Configuring Ente CLI" From 2a523605854a1bae5da4ee82b1d01e5bb5d4677d Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 21:21:09 +0530 Subject: [PATCH 18/54] feat: export binaries and scripts in /usr/local/bin --- install/ente-install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index f75bbedff..a52ca8eca 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -29,12 +29,13 @@ setup_go NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs ENTE_CLI_VERSION=$(curl -s https://api.github.com/repos/ente-io/ente/releases | jq -r '[.[] | select(.tag_name | startswith("cli-v"))][0].tag_name') fetch_and_deploy_gh_release "ente-server" "ente-io/ente" "tarball" "latest" "/opt/ente" -fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "$ENTE_CLI_VERSION" "/usr/local/bin/ente" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" +fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "$ENTE_CLI_VERSION" "/usr/local/bin" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" $STD mkdir -p /opt/ente/cli msg_info "Configuring Ente CLI" cat <>~/.bashrc export ENTE_CLI_SECRETS_PATH=/opt/ente/cli/secrets.txt +export PATH="/usr/local/bin:$PATH" EOF $STD source ~/.bashrc $STD mkdir -p ~/.ente From 7f2bcf85434eda11e9033297fffe55e680f6e960 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 21:32:46 +0530 Subject: [PATCH 19/54] fix: ente cli installation (again) --- install/ente-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/ente-install.sh b/install/ente-install.sh index a52ca8eca..f2edeb064 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -29,7 +29,7 @@ setup_go NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs ENTE_CLI_VERSION=$(curl -s https://api.github.com/repos/ente-io/ente/releases | jq -r '[.[] | select(.tag_name | startswith("cli-v"))][0].tag_name') fetch_and_deploy_gh_release "ente-server" "ente-io/ente" "tarball" "latest" "/opt/ente" -fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "$ENTE_CLI_VERSION" "/usr/local/bin" "ente-cli-$ENTE_CLI_VERSION-linux-amd64.tar.gz" +fetch_and_deploy_gh_release "ente-cli" "ente-io/ente" "prebuild" "$ENTE_CLI_VERSION" "/usr/local/bin" "ente-$ENTE_CLI_VERSION-linux-amd64.tar.gz" $STD mkdir -p /opt/ente/cli msg_info "Configuring Ente CLI" From 8e8e14dbd005a6ca2b4cf4d8ab63cfef286f6a13 Mon Sep 17 00:00:00 2001 From: Raghav Vashisht Date: Sun, 23 Nov 2025 21:24:40 +0530 Subject: [PATCH 20/54] feat: suppress corepack download prompt --- install/ente-install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install/ente-install.sh b/install/ente-install.sh index f2edeb064..9a4fbdee6 100644 --- a/install/ente-install.sh +++ b/install/ente-install.sh @@ -176,6 +176,7 @@ export NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT=$ENTE_ALBUMS_URL msg_info "Building Web Applications" cd /opt/ente/web +export COREPACK_ENABLE_DOWNLOAD_PROMPT=0 $STD yarn install $STD yarn build $STD yarn build:accounts From 6a2928ca479979dcb3f3022b56f5a25a30691b57 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:19:34 +0100 Subject: [PATCH 21/54] Update build.func --- misc/build.func | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/misc/build.func b/misc/build.func index d5dd0ef06..794f138ee 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2832,16 +2832,16 @@ EOF' echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" # Dev mode: Setup MOTD/SSH for debugging access to broken container - if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then - echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" - if pct exec "$CTID" -- bash -c " - source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/install.func) - declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true - " >/dev/null 2>&1; then - local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) - echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" - fi - fi + # if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then + # echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" + # if pct exec "$CTID" -- bash -c " + # source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/install.func) + # declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true + # " >/dev/null 2>&1; then + # local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) + # echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" + # fi + # fi fi else # Timeout - auto-remove From f866c1cbef8a6e16f30797c29871697c76eb1eb9 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:34:35 +0100 Subject: [PATCH 22/54] Add build copy.func and update build.func Added new 'build copy.func' script for LXC container build and configuration in Proxmox VE, including variable initialization, storage management, resource allocation, and advanced interactive configuration. Updated 'build.func' to support new features and improved logic for container setup, storage selection, and app-specific defaults management. --- misc/build copy.func | 3783 ++++++++++++++++++++++++++++++++++++++++++ misc/build.func | 268 +-- 2 files changed, 3918 insertions(+), 133 deletions(-) create mode 100644 misc/build copy.func diff --git a/misc/build copy.func b/misc/build copy.func new file mode 100644 index 000000000..794f138ee --- /dev/null +++ b/misc/build copy.func @@ -0,0 +1,3783 @@ +#!/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/branch/main/LICENSE +# Revision: 1 + +# ============================================================================== +# BUILD.FUNC - LXC CONTAINER BUILD & CONFIGURATION +# ============================================================================== +# +# This file provides the main build functions for creating and configuring +# LXC containers in Proxmox VE. It handles: +# +# - Variable initialization and defaults +# - Container creation and resource allocation +# - Storage selection and management +# - Advanced configuration and customization +# - User interaction menus and prompts +# +# Usage: +# - Sourced automatically by CT creation scripts +# - Requires core.func and error_handler.func to be loaded first +# +# ============================================================================== + +# ============================================================================== +# SECTION 1: INITIALIZATION & CORE VARIABLES +# ============================================================================== + +# ------------------------------------------------------------------------------ +# variables() +# +# - Initializes core variables for container creation +# - Normalizes application name (NSAPP = lowercase, no spaces) +# - Builds installer filename (var_install) +# - Defines regex patterns for validation +# - Fetches Proxmox hostname and version +# - Generates unique session ID for tracking and logging +# - Captures app-declared resource defaults (CPU, RAM, Disk) +# ------------------------------------------------------------------------------ +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. + SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files + BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log + CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" + + # Parse dev_mode early + parse_dev_mode + + # Setup persistent log directory if logs mode active + if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then + mkdir -p /var/log/community-scripts + BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" + fi + + # Get Proxmox VE version and kernel version + if command -v pveversion >/dev/null 2>&1; then + PVEVERSION="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" + else + PVEVERSION="N/A" + fi + KERNEL_VERSION=$(uname -r) + + # Capture app-declared defaults (for precedence logic) + # These values are set by the app script BEFORE default.vars is loaded + # If app declares higher values than default.vars, app values take precedence + if [[ -n "${var_cpu:-}" && "${var_cpu}" =~ ^[0-9]+$ ]]; then + export APP_DEFAULT_CPU="${var_cpu}" + fi + if [[ -n "${var_ram:-}" && "${var_ram}" =~ ^[0-9]+$ ]]; then + export APP_DEFAULT_RAM="${var_ram}" + fi + if [[ -n "${var_disk:-}" && "${var_disk}" =~ ^[0-9]+$ ]]; then + export APP_DEFAULT_DISK="${var_disk}" + fi +} + +# ----------------------------------------------------------------------------- +# 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 + +# ============================================================================== +# SECTION 2: PRE-FLIGHT CHECKS & SYSTEM VALIDATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# 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 + + # Silent success - only show errors if they exist +} + +# ============================================================================== +# SECTION 3: CONTAINER SETUP UTILITIES +# ============================================================================== + +# ------------------------------------------------------------------------------ +# get_current_ip() +# +# - Returns current container IP depending on OS type +# - Debian/Ubuntu: uses `hostname -I` +# - Alpine: parses eth0 via `ip -4 addr` +# - Returns "Unknown" if OS type cannot be determined +# ------------------------------------------------------------------------------ +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 +} + +# ------------------------------------------------------------------------------ +# 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 +} + +# ------------------------------------------------------------------------------ +# 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[*]}" + ) +} + +# ============================================================================== +# SECTION 4: STORAGE & RESOURCE MANAGEMENT +# ============================================================================== + +# ------------------------------------------------------------------------------ +# _write_storage_to_vars() +# +# - Writes storage selection to vars file +# - Removes old entries (commented and uncommented) to avoid duplicates +# - Arguments: vars_file, key (var_container_storage/var_template_storage), value +# ------------------------------------------------------------------------------ +_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 + + # Silent operation - no output message +} + +# ============================================================================== +# SECTION 5: CONFIGURATION & DEFAULTS MANAGEMENT +# ============================================================================== + +# ------------------------------------------------------------------------------ +# base_settings() +# +# - Defines all base/default variables for container creation +# - Reads from environment variables (var_*) +# - Provides fallback defaults for OS type/version +# - App-specific values take precedence when they are HIGHER (for CPU, RAM, DISK) +# - Sets up container type, resources, network, SSH, features, and tags +# ------------------------------------------------------------------------------ +base_settings() { + # Default Settings + CT_TYPE=${var_unprivileged:-"1"} + + # Resource allocation: App defaults take precedence if HIGHER + # Compare app-declared values (saved in APP_DEFAULT_*) with current var_* values + local final_disk="${var_disk:-4}" + local final_cpu="${var_cpu:-1}" + local final_ram="${var_ram:-1024}" + + # If app declared higher values, use those instead + if [[ -n "${APP_DEFAULT_DISK:-}" && "${APP_DEFAULT_DISK}" =~ ^[0-9]+$ ]]; then + if [[ "${APP_DEFAULT_DISK}" -gt "${final_disk}" ]]; then + final_disk="${APP_DEFAULT_DISK}" + fi + fi + + if [[ -n "${APP_DEFAULT_CPU:-}" && "${APP_DEFAULT_CPU}" =~ ^[0-9]+$ ]]; then + if [[ "${APP_DEFAULT_CPU}" -gt "${final_cpu}" ]]; then + final_cpu="${APP_DEFAULT_CPU}" + fi + fi + + if [[ -n "${APP_DEFAULT_RAM:-}" && "${APP_DEFAULT_RAM}" =~ ^[0-9]+$ ]]; then + if [[ "${APP_DEFAULT_RAM}" -gt "${final_ram}" ]]; then + final_ram="${APP_DEFAULT_RAM}" + fi + fi + + DISK_SIZE="${final_disk}" + CORE_COUNT="${final_cpu}" + RAM_SIZE="${final_ram}" + 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:-""} + + # Runtime check: Verify APT cacher is reachable if configured + if [[ -n "$APT_CACHER_IP" && "$APT_CACHER" == "yes" ]]; then + if ! curl -s --connect-timeout 2 "http://${APT_CACHER_IP}:3142" >/dev/null 2>&1; then + msg_warn "APT Cacher configured but not reachable at ${APT_CACHER_IP}:3142" + msg_custom "⚠️" "${YW}" "Disabling APT Cacher for this installation" + APT_CACHER="" + APT_CACHER_IP="" + else + msg_ok "APT Cacher verified at ${APT_CACHER_IP}:3142" + fi + fi + + 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}"} + ENABLE_NESTING=${var_nesting:-"${1:-1}"} + ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} + ALLOW_MOUNT_FS=${var_mount_fs:-""} + ENABLE_MKNOD=${var_mknod:-"${1:-0}"} + PROTECT_CT=${var_protection:-"${1:-no}"} + CT_TIMEZONE=${var_timezone:-""} + + # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) + # This must happen here before any usage of these variables + case "${ENABLE_NESTING,,}" in + yes | true) ENABLE_NESTING="1" ;; + no | false) ENABLE_NESTING="0" ;; + esac + case "${ENABLE_KEYCTL,,}" in + yes | true) ENABLE_KEYCTL="1" ;; + no | false) ENABLE_KEYCTL="0" ;; + esac + case "${ENABLE_MKNOD,,}" in + yes | true) ENABLE_MKNOD="1" ;; + no | false) ENABLE_MKNOD="0" ;; + esac + case "${ENABLE_FUSE,,}" in + yes | true) ENABLE_FUSE="1" ;; + no | false) ENABLE_FUSE="0" ;; + esac + case "${PROTECT_CT,,}" in + yes | true) PROTECT_CT="1" ;; + no | false) PROTECT_CT="0" ;; + esac + + # 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 +} + +# ------------------------------------------------------------------------------ +# 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) + # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) + local VAR_WHITELIST=( + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_keyctl + var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu + var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone 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" + # Silent creation - no msg_info output + 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_vlan= +# var_mtu= +# var_mac= +# var_ns= + +# SSH +var_ssh=no +# var_ssh_authorized_key= + +# APT cacher (optional - with example) +# var_apt_cacher=yes +# var_apt_cacher_ip=192.168.1.10 + +# Features/Tags/verbosity +var_fuse=no +var_tun=no + +# Advanced Settings (Proxmox-official features) +var_nesting=1 # Allow nesting (required for Docker/LXC in CT) +var_keyctl=0 # Allow keyctl() - needed for Docker (systemd-networkd workaround) +var_mknod=0 # Allow device node creation (requires kernel 5.3+, experimental) +var_mount_fs= # Allow specific filesystems: nfs,fuse,ext4,etc (leave empty for defaults) +var_protection=no # Prevent accidental deletion of container +var_timezone= # Container timezone (e.g. Europe/Berlin, leave empty for host timezone) +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" + # Silent creation - no output message + } + + # 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 + # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) + declare -ag VAR_WHITELIST=( + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse + var_gateway var_hostname var_ipv6_method 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 + +# Note: _is_whitelisted_key() is defined above in default_var_settings section + +_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 +# Note: Main _load_vars_file() with full validation is defined in default_var_settings section +# This simplified version is used specifically for diff operations via _VARS_IN array +declare -A _VARS_IN +_load_vars_file_to_map() { + local file="$1" + [ -f "$file" ] || return 0 + _VARS_IN=() # Clear array + 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 + _VARS_IN["$key"]="$val" + fi + ;; + esac + done <"$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}" + _nesting="${ENABLE_NESTING:-1}" + _keyctl="${ENABLE_KEYCTL:-0}" + _mknod="${ENABLE_MKNOD:-0}" + _mount_fs="${ALLOW_MOUNT_FS:-}" + _protect="${PROTECT_CT:-no}" + _timezone="${CT_TIMEZONE:-}" + _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")" + # var_ipv6_static removed - static IPs are unique, can't be default + + [ -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 "$_nesting" ] && echo "var_nesting=$(_sanitize_value "$_nesting")" + [ -n "$_keyctl" ] && echo "var_keyctl=$(_sanitize_value "$_keyctl")" + [ -n "$_mknod" ] && echo "var_mknod=$(_sanitize_value "$_mknod")" + [ -n "$_mount_fs" ] && echo "var_mount_fs=$(_sanitize_value "$_mount_fs")" + [ -n "$_protect" ] && echo "var_protection=$(_sanitize_value "$_protect")" + [ -n "$_timezone" ] && echo "var_timezone=$(_sanitize_value "$_timezone")" + [ -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_custom "ℹ️" "${BL}" "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_custom "🚫" "${YW}" "Canceled. No changes to app defaults." + break + ;; + esac + done + + rm -f "$new_tmp" "$diff_tmp" +} + +ensure_storage_selection_for_vars_file() { + local vf="$1" + + # Read stored values (if any) + local tpl ct + tpl=$(grep -E '^var_template_storage=' "$vf" | cut -d= -f2-) + ct=$(grep -E '^var_container_storage=' "$vf" | cut -d= -f2-) + + if [[ -n "$tpl" && -n "$ct" ]]; then + TEMPLATE_STORAGE="$tpl" + CONTAINER_STORAGE="$ct" + return 0 + fi + + choose_and_set_storage_for_file "$vf" template + choose_and_set_storage_for_file "$vf" container + + # Silent operation - no output message +} + +ensure_global_default_vars_file() { + local vars_path="/usr/local/community-scripts/default.vars" + if [[ ! -f "$vars_path" ]]; then + mkdir -p "$(dirname "$vars_path")" + touch "$vars_path" + fi + echo "$vars_path" +} + +# ============================================================================== +# SECTION 6: ADVANCED INTERACTIVE CONFIGURATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# advanced_settings() +# +# - Interactive whiptail menu for comprehensive container configuration +# - Allows user to customize: +# * Container type (privileged/unprivileged) +# * Root password +# * Container ID (CTID) +# * Hostname +# * Resources (disk size, CPU cores, RAM) +# * Network (IPv4/IPv6, gateway, DNS, MAC, VLAN, MTU) +# * SSH settings and key injection +# * Advanced features (FUSE, TUN, keyctl) +# * Tags for organization +# * Verbose/debug mode +# - Loops until user confirms or cancels +# - Validates all input and shows current selections +# ------------------------------------------------------------------------------ +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 "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}" + 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 "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}" + 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 "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" + fi + else + exit_script + fi + echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}" + + 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 + # Build bridge menu with descriptions + BRIDGE_MENU_OPTIONS=() + while IFS= read -r bridge; do + if [[ -n "$bridge" ]]; then + # Get description from Proxmox built-in method - find comment for this specific bridge + description=$(grep -A 10 "iface $bridge" /etc/network/interfaces | grep '^#' | head -n1 | sed 's/^#\s*//') + if [[ -n "$description" ]]; then + BRIDGE_MENU_OPTIONS+=("$bridge" "${description}") + else + BRIDGE_MENU_OPTIONS+=("$bridge" " ") + fi + fi + done <<<"$BRIDGES" + + BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --menu "Select network bridge: " 18 55 6 "${BRIDGE_MENU_OPTIONS[@]}" 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 + + configure_ssh_settings + export SSH_KEYS_FILE + echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" + + # Advanced Settings - Proxmox Features + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS" --yesno "Configure Advanced Proxmox Features?" 10 58); then + # keyctl: for Docker support + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable keyctl()" --yesno "Allow keyctl() system calls?\n\nNeeded for: Docker inside container, systemd-networkd\nDefault: No (not needed for most applications)" 10 58); then + ENABLE_KEYCTL="1" + else + ENABLE_KEYCTL="0" + fi + echo -e "${SEARCH}${BOLD}${DGN}Allow keyctl(): ${BGN}$ENABLE_KEYCTL${CL}" + + # mknod: device node creation + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable mknod()" --yesno "Allow device node creation?\n\nNeeded for: Complex device management (experimental, kernel 5.3+)\nDefault: No (rarely needed)" 10 58); then + ENABLE_MKNOD="1" + else + ENABLE_MKNOD="0" + fi + echo -e "${SEARCH}${BOLD}${DGN}Allow mknod(): ${BGN}$ENABLE_MKNOD${CL}" + + # mount: specific filesystems + if MOUNT_FS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allow specific filesystems (e.g., nfs,fuse,ext4)\nLeave blank for defaults" 8 58 "$ALLOW_MOUNT_FS" --title "Mount Filesystems" 3>&1 1>&2 2>&3); then + ALLOW_MOUNT_FS="$MOUNT_FS" + [ -z "$ALLOW_MOUNT_FS" ] && ALLOW_MOUNT_FS="(defaults)" + else + exit_script + fi + echo -e "${SEARCH}${BOLD}${DGN}Mount Filesystems: ${BGN}$ALLOW_MOUNT_FS${CL}" + + # Container protection + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Protection Flag" --yesno "Prevent accidental deletion?\n\nIf enabled, container cannot be deleted or its disk modified\nDefault: No" 10 58); then + PROTECT_CT="yes" + else + PROTECT_CT="no" + fi + echo -e "${SEARCH}${BOLD}${DGN}Container Protection: ${BGN}$PROTECT_CT${CL}" + + # Container timezone + if CT_TIMEZONE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set container timezone (e.g., Europe/Berlin)\nLeave blank to use host timezone" 8 58 "$CT_TIMEZONE" --title "Container Timezone" 3>&1 1>&2 2>&3); then + [ -z "$CT_TIMEZONE" ] && CT_TIMEZONE="(host)" + else + exit_script + fi + echo -e "${SEARCH}${BOLD}${DGN}Container Timezone: ${BGN}$CT_TIMEZONE${CL}" + fi + + 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 "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}" + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" + advanced_settings + fi +} + +# ============================================================================== +# SECTION 7: USER INTERFACE & DIAGNOSTICS +# ============================================================================== + +# ------------------------------------------------------------------------------ +# 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 +# - Creates file if missing with default DIAGNOSTICS=yes +# - Reads current diagnostics setting from file +# - Sets global DIAGNOSTICS variable for API telemetry opt-in/out +# ------------------------------------------------------------------------------ +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: +#"disk_size" +#"core_count" +#"ram_size" +#"os_type" +#"os_version" +#"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: +#"disk_size" +#"core_count" +#"ram_size" +#"os_type" +#"os_version" +#"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 +} + +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 +} + +# ------------------------------------------------------------------------------ +# 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 "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}" + 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 " " +} + +# ------------------------------------------------------------------------------ +# 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) + + # Get timezone using timedatectl (Debian 13+ compatible) + # Fallback to /etc/timezone for older systems + if command -v timedatectl >/dev/null 2>&1; then + timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC") + elif [ -f /etc/timezone ]; then + timezone=$(cat /etc/timezone) + else + timezone="UTC" + fi + + # Show APP Header + header_info + + # --- Support CLI argument as direct preset (default, advanced, …) --- + CHOICE="${mode:-${1:-}}" + + # If no CLI argument → show whiptail menu + # Build menu dynamically based on available options + local appdefaults_option="" + local settings_option="" + local menu_items=( + "1" "Default Install" + "2" "Advanced Install" + "3" "My Defaults" + ) + + if [ -f "$(get_app_defaults_path)" ]; then + appdefaults_option="4" + menu_items+=("4" "App Defaults for ${APP}") + settings_option="5" + menu_items+=("5" "Settings") + else + settings_option="4" + menu_items+=("4" "Settings") + fi + + if [ -z "$CHOICE" ]; then + + 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 + + APPDEFAULTS_OPTION="$appdefaults_option" + SETTINGS_OPTION="$settings_option" + + # --- Main case --- + local defaults_target="" + local run_maybe_offer="no" + 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 + defaults_target="$(ensure_global_default_vars_file)" + ;; + 2 | advanced | ADVANCED) + header_info + + echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Install on node $PVEHOST_NAME${CL}" + echo -e "${INFO}${BOLD}${DGN}PVE Version ${PVEVERSION} (Kernel: ${KERNEL_VERSION})${CL}" + METHOD="advanced" + base_settings + advanced_settings + defaults_target="$(ensure_global_default_vars_file)" + run_maybe_offer="yes" + ;; + 3 | mydefaults | MYDEFAULTS) + default_var_settings || { + msg_error "Failed to apply default.vars" + exit 1 + } + defaults_target="/usr/local/community-scripts/default.vars" + ;; + "$APPDEFAULTS_OPTION" | 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 + defaults_target="$(get_app_defaults_path)" + else + msg_error "No App Defaults available for ${APP}" + exit 1 + fi + ;; + "$SETTINGS_OPTION" | settings | SETTINGS) + settings_menu + defaults_target="" + ;; + *) + echo -e "${CROSS}${RD}Invalid option: $CHOICE${CL}" + exit 1 + ;; + esac + + if [[ -n "$defaults_target" ]]; then + ensure_storage_selection_for_vars_file "$defaults_target" + fi + + if [[ "$run_maybe_offer" == "yes" ]]; then + maybe_offer_save_app_defaults + fi +} + +edit_default_storage() { + local vf="/usr/local/community-scripts/default.vars" + + # Ensure file exists + if [[ ! -f "$vf" ]]; then + mkdir -p "$(dirname "$vf")" + touch "$vf" + fi + + # Let ensure_storage_selection_for_vars_file handle everything + ensure_storage_selection_for_vars_file "$vf" +} + +settings_menu() { + while true; do + local settings_items=( + "1" "Manage API-Diagnostic Setting" + "2" "Edit Default.vars" + ) + if [ -f "$(get_app_defaults_path)" ]; then + settings_items+=("3" "Edit App.vars for ${APP}") + settings_items+=("4" "Exit") + else + settings_items+=("3" "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 +} + +# ------------------------------------------------------------------------------ +# 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[@]}" +} + +configure_ssh_settings() { + 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[@]}" + local default_key_count="$COUNT" + + local ssh_key_mode + if [[ "$default_key_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 (${default_key_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) + local selection + selection=$(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 $selection; do + tag="${tag%\"}" + tag="${tag#\"}" + local line + 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) + local glob_path + 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 + local folder_selection + folder_selection=$(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 $folder_selection; do + tag="${tag%\"}" + tag="${tag#\"}" + local line + 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 + + if [[ -s "$SSH_KEYS_FILE" ]]; then + sort -u -o "$SSH_KEYS_FILE" "$SSH_KEYS_FILE" + printf '\n' >>"$SSH_KEYS_FILE" + fi + + 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 +} + +# ------------------------------------------------------------------------------ +# 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 +} + +# ============================================================================== +# SECTION 8: CONTAINER CREATION & DEPLOYMENT +# ============================================================================== + +# ------------------------------------------------------------------------------ +# build_container() +# +# - Main function for creating and configuring LXC container +# - Builds network configuration string (IP, gateway, VLAN, MTU, MAC, IPv6) +# - Creates container via pct create with all specified settings +# - Applies features: FUSE, TUN, keyctl, VAAPI passthrough +# - Starts container and waits for network connectivity +# - Installs base packages (curl, sudo, etc.) +# - Injects SSH keys if configured +# - Executes -install.sh inside container +# - Posts installation telemetry to API if diagnostics enabled +# ------------------------------------------------------------------------------ +build_container() { + # if [ "$VERBOSE" == "yes" ]; then set -x; fi + + NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}" + + # MAC + if [[ -n "$MAC" ]]; then + case "$MAC" in + ,hwaddr=*) NET_STRING+="$MAC" ;; + *) NET_STRING+=",hwaddr=$MAC" ;; + esac + fi + + # IP (immer zwingend, Standard dhcp) + NET_STRING+=",ip=${NET:-dhcp}" + + # Gateway + if [[ -n "$GATE" ]]; then + case "$GATE" in + ,gw=*) NET_STRING+="$GATE" ;; + *) NET_STRING+=",gw=$GATE" ;; + esac + fi + + # VLAN + if [[ -n "$VLAN" ]]; then + case "$VLAN" in + ,tag=*) NET_STRING+="$VLAN" ;; + *) NET_STRING+=",tag=$VLAN" ;; + esac + fi + + # MTU + if [[ -n "$MTU" ]]; then + case "$MTU" in + ,mtu=*) NET_STRING+="$MTU" ;; + *) NET_STRING+=",mtu=$MTU" ;; + esac + fi + + # IPv6 Handling + 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 + + # Build FEATURES array with advanced settings + # Note: All feature flags are already normalized to 0/1 in default_settings() + # Proxmox requires each feature as a separate parameter, not comma-separated string + FEATURES_ARRAY=() + FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + + # keyctl: needed for Docker inside containers (systemd-networkd workaround) + # Typically needed for unprivileged containers with Docker + if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then + FEATURES_ARRAY+=("keyctl=1") + fi + + # mknod: allow device node creation (requires kernel 5.3+, experimental) + if [ "$ENABLE_MKNOD" == "1" ]; then + FEATURES_ARRAY+=("mknod=1") + fi + + # FUSE: required for rclone, mergerfs, AppImage, etc. + if [ "$ENABLE_FUSE" == "1" ]; then + FEATURES_ARRAY+=("fuse=1") + fi + + # mount: allow specific filesystems (e.g., nfs, ext4, etc.) + # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) + if [ -n "$ALLOW_MOUNT_FS" ]; then + # Replace commas with semicolons for proper pct syntax + ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" + FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + 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 SESSION_ID="$SESSION_ID" + export BUILD_LOG="$BUILD_LOG" + export INSTALL_LOG="/root/.install-${SESSION_ID}.log" + export dev_mode="${dev_mode:-}" + export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" + export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" + export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" + export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" + export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" + export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" + export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" + export CACHER="$APT_CACHER" + export CACHER_IP="$APT_CACHER_IP" + export tz="$timezone" + 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 ENABLE_NESTING="$ENABLE_NESTING" + export ENABLE_KEYCTL="$ENABLE_KEYCTL" + export ENABLE_MKNOD="$ENABLE_MKNOD" + export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" + export PROTECT_CT="$PROTECT_CT" + export CT_TIMEZONE="$CT_TIMEZONE" + export PCT_OSTYPE="$var_os" + export PCT_OSVERSION="$var_version" + export PCT_DISK_SIZE="$DISK_SIZE" + + # Build PCT_OPTIONS array (not string) for proper parameter handling + PCT_OPTIONS=() + + # Add features - each as separate -features parameter + for feature in "${FEATURES_ARRAY[@]}"; do + PCT_OPTIONS+=("-features" "$feature") + done + + PCT_OPTIONS+=("-hostname" "$HN") + PCT_OPTIONS+=("-tags" "$TAGS") + + if [ -n "$SD" ]; then + PCT_OPTIONS+=($SD) # Storage device flags (already formatted) + fi + + if [ -n "$NS" ]; then + PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) + fi + + # Network configuration (single string with all network parameters) + PCT_OPTIONS+=($NET_STRING) + + PCT_OPTIONS+=("-onboot" "1") + PCT_OPTIONS+=("-cores" "$CORE_COUNT") + PCT_OPTIONS+=("-memory" "$RAM_SIZE") + PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") + + # Protection flag + if [ "$PROTECT_CT" == "1" ]; then + PCT_OPTIONS+=("-protection" "1") + fi + + # Timezone flag + if [ -n "$CT_TIMEZONE" ]; then + PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + fi + + # Password flag (already formatted as "-password xxx") + if [ -n "$PW" ]; then + PCT_OPTIONS+=($PW) + fi + + export PCT_OPTIONS + export TEMPLATE_STORAGE="${var_template_storage:-}" + export CONTAINER_STORAGE="${var_container_storage:-}" + create_lxc_container || exit $? + + LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" + + # ============================================================================ + # GPU/USB PASSTHROUGH CONFIGURATION + # ============================================================================ + + # List of applications that benefit from GPU acceleration + GPU_APPS=( + "immich" "channels" "emby" "ersatztv" "frigate" + "jellyfin" "plex" "scrypted" "tdarr" "unmanic" + "ollama" "fileflows" "open-webui" "tunarr" "debian" + "handbrake" "sunshine" "moonlight" "kodi" "stremio" + "viseron" + ) + + # Check if app needs GPU + is_gpu_app() { + local app="${1,,}" + for gpu_app in "${GPU_APPS[@]}"; do + [[ "$app" == "${gpu_app,,}" ]] && return 0 + done + return 1 + } + + # Detect all available GPU devices + detect_gpu_devices() { + INTEL_DEVICES=() + AMD_DEVICES=() + NVIDIA_DEVICES=() + + # Store PCI info to avoid multiple calls + local pci_vga_info=$(lspci -nn 2>/dev/null | grep -E "VGA|Display|3D") + + # Check for Intel GPU - look for Intel vendor ID [8086] + if echo "$pci_vga_info" | grep -q "\[8086:"; then + msg_custom "🎮" "${BL}" "Detected Intel GPU" + if [[ -d /dev/dri ]]; then + for d in /dev/dri/renderD* /dev/dri/card*; do + [[ -e "$d" ]] && INTEL_DEVICES+=("$d") + done + fi + fi + + # Check for AMD GPU - look for AMD vendor IDs [1002] (AMD/ATI) or [1022] (AMD) + if echo "$pci_vga_info" | grep -qE "\[1002:|\[1022:"; then + msg_custom "🎮" "${RD}" "Detected AMD GPU" + if [[ -d /dev/dri ]]; then + # Only add if not already claimed by Intel + if [[ ${#INTEL_DEVICES[@]} -eq 0 ]]; then + for d in /dev/dri/renderD* /dev/dri/card*; do + [[ -e "$d" ]] && AMD_DEVICES+=("$d") + done + fi + fi + fi + + # Check for NVIDIA GPU - look for NVIDIA vendor ID [10de] + if echo "$pci_vga_info" | grep -q "\[10de:"; then + msg_custom "🎮" "${GN}" "Detected NVIDIA GPU" + + # Simple passthrough - just bind /dev/nvidia* devices if they exist + for d in /dev/nvidia* /dev/nvidiactl /dev/nvidia-modeset /dev/nvidia-uvm /dev/nvidia-uvm-tools; do + [[ -e "$d" ]] && NVIDIA_DEVICES+=("$d") + done + + if [[ ${#NVIDIA_DEVICES[@]} -gt 0 ]]; then + msg_custom "🎮" "${GN}" "Found ${#NVIDIA_DEVICES[@]} NVIDIA device(s) for passthrough" + else + msg_warn "NVIDIA GPU detected via PCI but no /dev/nvidia* devices found" + msg_custom "ℹ️" "${YW}" "Skipping NVIDIA passthrough (host drivers may not be loaded)" + fi + fi + + # Debug output + msg_debug "Intel devices: ${INTEL_DEVICES[*]}" + msg_debug "AMD devices: ${AMD_DEVICES[*]}" + msg_debug "NVIDIA devices: ${NVIDIA_DEVICES[*]}" + } + + # Configure USB passthrough for privileged containers + configure_usb_passthrough() { + if [[ "$CT_TYPE" != "0" ]]; then + return 0 + fi + + msg_info "Configuring automatic USB passthrough (privileged container)" + cat <>"$LXC_CONFIG" +# Automatic USB passthrough (privileged container) +lxc.cgroup2.devices.allow: a +lxc.cap.drop: +lxc.cgroup2.devices.allow: c 188:* rwm +lxc.cgroup2.devices.allow: c 189:* rwm +lxc.mount.entry: /dev/serial/by-id dev/serial/by-id none bind,optional,create=dir +lxc.mount.entry: /dev/ttyUSB0 dev/ttyUSB0 none bind,optional,create=file +lxc.mount.entry: /dev/ttyUSB1 dev/ttyUSB1 none bind,optional,create=file +lxc.mount.entry: /dev/ttyACM0 dev/ttyACM0 none bind,optional,create=file +lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create=file +EOF + msg_ok "USB passthrough configured" + } + + # Configure GPU passthrough + configure_gpu_passthrough() { + # Skip if not a GPU app and not privileged + if [[ "$CT_TYPE" != "0" ]] && ! is_gpu_app "$APP"; then + return 0 + fi + + detect_gpu_devices + + # Count available GPU types + local gpu_count=0 + local available_gpus=() + + if [[ ${#INTEL_DEVICES[@]} -gt 0 ]]; then + available_gpus+=("INTEL") + gpu_count=$((gpu_count + 1)) + fi + + if [[ ${#AMD_DEVICES[@]} -gt 0 ]]; then + available_gpus+=("AMD") + gpu_count=$((gpu_count + 1)) + fi + + if [[ ${#NVIDIA_DEVICES[@]} -gt 0 ]]; then + available_gpus+=("NVIDIA") + gpu_count=$((gpu_count + 1)) + fi + + if [[ $gpu_count -eq 0 ]]; then + msg_custom "ℹ️" "${YW}" "No GPU devices found for passthrough" + return 0 + fi + + local selected_gpu="" + + if [[ $gpu_count -eq 1 ]]; then + # Automatic selection for single GPU + selected_gpu="${available_gpus[0]}" + msg_custom "⚙️" "${GN}" "Automatically configuring ${selected_gpu} GPU passthrough" + else + # Multiple GPUs - ask user + echo -e "\n${INFO} Multiple GPU types detected:" + for gpu in "${available_gpus[@]}"; do + echo " - $gpu" + done + read -rp "Which GPU type to passthrough? (${available_gpus[*]}): " selected_gpu + selected_gpu="${selected_gpu^^}" + + # Validate selection + local valid=0 + for gpu in "${available_gpus[@]}"; do + [[ "$selected_gpu" == "$gpu" ]] && valid=1 + done + + if [[ $valid -eq 0 ]]; then + msg_warn "Invalid selection. Skipping GPU passthrough." + return 0 + fi + fi + + # Apply passthrough configuration based on selection + local dev_idx=0 + + case "$selected_gpu" in + INTEL | AMD) + local devices=() + [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}") + [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}") + + # Use pct set to add devices with proper dev0/dev1 format + # GIDs will be detected and set after container starts + local dev_index=0 + for dev in "${devices[@]}"; do + # Add to config using pct set (will be visible in GUI) + echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" + dev_index=$((dev_index + 1)) + done + + export GPU_TYPE="$selected_gpu" + msg_ok "${selected_gpu} GPU passthrough configured (${#devices[@]} devices)" + ;; + + NVIDIA) + if [[ ${#NVIDIA_DEVICES[@]} -eq 0 ]]; then + msg_warn "No NVIDIA devices available for passthrough" + return 0 + fi + + # Use pct set for NVIDIA devices + local dev_index=0 + for dev in "${NVIDIA_DEVICES[@]}"; do + echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" + dev_index=$((dev_index + 1)) + done + + export GPU_TYPE="NVIDIA" + msg_ok "NVIDIA GPU passthrough configured (${#NVIDIA_DEVICES[@]} devices) - install drivers in container if needed" + ;; + esac + } + + # Additional device passthrough + configure_additional_devices() { + # 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 + + # Coral TPU passthrough + if [[ -e /dev/apex_0 ]]; then + msg_custom "🔌" "${BL}" "Detected Coral TPU - configuring passthrough" + echo "lxc.mount.entry: /dev/apex_0 dev/apex_0 none bind,optional,create=file" >>"$LXC_CONFIG" + fi + } + + # Execute pre-start configurations + configure_usb_passthrough + configure_gpu_passthrough + configure_additional_devices + + # ============================================================================ + # START CONTAINER AND INSTALL USERLAND + # ============================================================================ + + msg_info "Starting LXC Container" + pct start "$CTID" + + # Wait for container to be 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 + + # Wait for network (skip for Alpine initially) + if [ "$var_os" != "alpine" ]; then + msg_info "Waiting for network in LXC container" + + # 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 + + # Simple connectivity check - just verify IP is assigned + msg_ok "Network configured (IP: $ip_in_lxc)" + fi + # Function to get correct GID inside container + get_container_gid() { + local group="$1" + local gid=$(pct exec "$CTID" -- getent group "$group" 2>/dev/null | cut -d: -f3) + echo "${gid:-44}" # Default to 44 if not found + } + + fix_gpu_gids + + # Continue with standard container setup + msg_info "Customizing LXC Container" + + # # Install GPU userland if configured + # if [[ "${ENABLE_VAAPI:-0}" == "1" ]]; then + # install_gpu_userland "VAAPI" + # fi + + # if [[ "${ENABLE_NVIDIA:-0}" == "1" ]]; then + # install_gpu_userland "NVIDIA" + # fi + + # Continue with standard container setup + 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 + # Set timezone using symlink (Debian 13+ compatible) + # Create /etc/timezone for backwards compatibility with older scripts + pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true" + 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 + install_ssh_keys_into_ct + + # Run application installer + # NOTE: We disable error handling here because: + # 1. Container errors are caught by error_handler INSIDE container + # 2. Container creates flag file with exit code + # 3. We read flag file and handle cleanup manually below + # 4. We DON'T want host error_handler to fire for lxc-attach command itself + + set +Eeuo pipefail # Disable ALL error handling temporarily + trap - ERR # Remove ERR trap completely + + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" + local lxc_exit=$? + + set -Eeuo pipefail # Re-enable error handling + trap 'error_handler' ERR # Restore ERR trap + + # Check for error flag file in container (more reliable than lxc-attach exit code) + local install_exit_code=0 + if [[ -n "${SESSION_ID:-}" ]]; then + local error_flag="/root/.install-${SESSION_ID}.failed" + if pct exec "$CTID" -- test -f "$error_flag" 2>/dev/null; then + install_exit_code=$(pct exec "$CTID" -- cat "$error_flag" 2>/dev/null || echo "1") + pct exec "$CTID" -- rm -f "$error_flag" 2>/dev/null || true + fi + fi + + # Fallback to lxc-attach exit code if no flag file + if [[ $install_exit_code -eq 0 && $lxc_exit -ne 0 ]]; then + install_exit_code=$lxc_exit + fi + + # Installation failed? + if [[ $install_exit_code -ne 0 ]]; then + msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})" + + # Copy both logs from container before potential deletion + local build_log_copied=false + local install_log_copied=false + + if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then + # Copy BUILD_LOG (creation log) if it exists + if [[ -f "${BUILD_LOG}" ]]; then + cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true + fi + + # Copy INSTALL_LOG from container + if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then + install_log_copied=true + fi + + # Show available logs + echo "" + [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + fi + + # Dev mode: Keep container or open breakpoint shell + if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then + msg_dev "Keep mode active - container ${CTID} preserved" + return 0 + elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then + msg_dev "Breakpoint mode - opening shell in container ${CTID}" + echo -e "${YW}Type 'exit' to return to host${CL}" + pct enter "$CTID" + echo "" + echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" + if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + msg_ok "Container ${CTID} removed" + else + msg_dev "Container ${CTID} kept for debugging" + fi + exit $install_exit_code + fi + + # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner) + echo "" + echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" + + if read -t 60 -r response; then + if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then + # Remove container + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + elif [[ "$response" =~ ^[Nn]$ ]]; then + echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" + + # Dev mode: Setup MOTD/SSH for debugging access to broken container + # if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then + # echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" + # if pct exec "$CTID" -- bash -c " + # source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/install.func) + # declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true + # " >/dev/null 2>&1; then + # local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) + # echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" + # fi + # fi + fi + else + # Timeout - auto-remove + echo -e "\n${YW}No response - auto-removing container${CL}" + echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + fi + + exit $install_exit_code + 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_custom "ℹ️" "${BL}" "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 +} + +fix_gpu_gids() { + if [[ -z "${GPU_TYPE:-}" ]]; then + return 0 + fi + + msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" + + # Get actual GIDs from container + local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") + local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") + + # Create groups if they don't exist + if [[ -z "$video_gid" ]]; then + pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1 + video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") + [[ -z "$video_gid" ]] && video_gid="44" + fi + + if [[ -z "$render_gid" ]]; then + pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1 + render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") + [[ -z "$render_gid" ]] && render_gid="104" + fi + + # Stop container to update config + pct stop "$CTID" >/dev/null 2>&1 + sleep 1 + + # Update dev entries with correct GIDs + sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG" + sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG" + + # Restart container + pct start "$CTID" >/dev/null 2>&1 + sleep 2 + + msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})" + + # For privileged containers: also fix permissions inside container + if [[ "$CT_TYPE" == "0" ]]; then + pct exec "$CTID" -- bash -c " + if [ -d /dev/dri ]; then + for dev in /dev/dri/*; do + if [ -e \"\$dev\" ]; then + if [[ \"\$dev\" =~ renderD ]]; then + chgrp ${render_gid} \"\$dev\" 2>/dev/null || true + else + chgrp ${video_gid} \"\$dev\" 2>/dev/null || true + fi + chmod 660 \"\$dev\" 2>/dev/null || true + fi + done + fi + " >/dev/null 2>&1 + fi +} + +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 $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; 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 0 + 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 [[ -z "${var_template_storage:-}" ]]; then + 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 + 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 + if [[ -z "${var_container_storage:-}" ]]; then + 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]" + fi + fi + 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'" + + # Build regex patterns outside awk/grep for clarity + SEARCH_PATTERN="^${TEMPLATE_SEARCH}" + + #echo "[DEBUG] TEMPLATE_SEARCH='$TEMPLATE_SEARCH'" + #echo "[DEBUG] SEARCH_PATTERN='$SEARCH_PATTERN'" + #echo "[DEBUG] TEMPLATE_PATTERN='$TEMPLATE_PATTERN'" + + mapfile -t LOCAL_TEMPLATES < <( + pveam list "$TEMPLATE_STORAGE" 2>/dev/null | + awk -v search="${SEARCH_PATTERN}" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {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)." + + msg_ok "Template search completed" + + #echo "[DEBUG] pveam available output (first 5 lines with .tar files):" + #pveam available -section system 2>/dev/null | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | head -5 | sed 's/^/ /' + + set +u + mapfile -t ONLINE_TEMPLATES < <(pveam available -section system 2>/dev/null | grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | awk '{print $2}' | grep -E "${SEARCH_PATTERN}.*${TEMPLATE_PATTERN}" | sort -t - -k 2 -V 2>/dev/null || true) + #echo "[DEBUG] After filtering: ${#ONLINE_TEMPLATES[@]} online templates found" + set -u + + ONLINE_TEMPLATE="" + [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}" + + #msg_debug "SEARCH_PATTERN='${SEARCH_PATTERN}' TEMPLATE_PATTERN='${TEMPLATE_PATTERN}'" + #msg_debug "Found ${#LOCAL_TEMPLATES[@]} local templates, ${#ONLINE_TEMPLATES[@]} online templates" + if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then + #msg_debug "First 3 online templates:" + count=0 + for idx in "${!ONLINE_TEMPLATES[@]}"; do + #msg_debug " [$idx]: ${ONLINE_TEMPLATES[$idx]}" + ((count++)) + [[ $count -ge 3 ]] && break + done + fi + #msg_debug "ONLINE_TEMPLATE='$ONLINE_TEMPLATE'" + + if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then + TEMPLATE="${LOCAL_TEMPLATES[-1]}" + TEMPLATE_SOURCE="local" + else + TEMPLATE="$ONLINE_TEMPLATE" + TEMPLATE_SOURCE="online" + fi + + # If still no template, try to find alternatives + if [[ -z "$TEMPLATE" ]]; then + echo "" + echo "[DEBUG] No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}, searching for alternatives..." + + # Get all available versions for this OS type + mapfile -t AVAILABLE_VERSIONS < <( + pveam available -section system 2>/dev/null | + grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | + awk -F'\t' '{print $1}' | + grep "^${PCT_OSTYPE}-" | + sed -E "s/.*${PCT_OSTYPE}-([0-9]+(\.[0-9]+)?).*/\1/" | + sort -u -V 2>/dev/null + ) + + if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then + echo "" + echo "${BL}Available ${PCT_OSTYPE} versions:${CL}" + for i in "${!AVAILABLE_VERSIONS[@]}"; do + echo " [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}" + done + echo "" + read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or press Enter to cancel: " choice + + if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#AVAILABLE_VERSIONS[@]} ]]; then + PCT_OSVERSION="${AVAILABLE_VERSIONS[$((choice - 1))]}" + TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION}" + SEARCH_PATTERN="^${TEMPLATE_SEARCH}-" + + #echo "[DEBUG] Retrying with version: $PCT_OSVERSION" + + mapfile -t ONLINE_TEMPLATES < <( + pveam available -section system 2>/dev/null | + grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | + awk -F'\t' '{print $1}' | + grep -E "${SEARCH_PATTERN}.*${TEMPLATE_PATTERN}" | + sort -t - -k 2 -V 2>/dev/null || true + ) + + if [[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]]; then + TEMPLATE="${ONLINE_TEMPLATES[-1]}" + TEMPLATE_SOURCE="online" + #echo "[DEBUG] Found alternative: $TEMPLATE" + else + msg_error "No templates available for ${PCT_OSTYPE} ${PCT_OSVERSION}" + exit 225 + fi + else + msg_custom "🚫" "${YW}" "Installation cancelled" + exit 0 + fi + else + msg_error "No ${PCT_OSTYPE} templates available at all" + exit 225 + fi + fi + + #echo "[DEBUG] Selected TEMPLATE='$TEMPLATE' SOURCE='$TEMPLATE_SOURCE'" + #msg_debug "Selected TEMPLATE='$TEMPLATE' SOURCE='$TEMPLATE_SOURCE'" + + 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 + + # If we still don't have a path but have a valid template name, construct it + if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then + TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" + fi + + [[ -n "$TEMPLATE_PATH" ]] || { + if [[ -z "$TEMPLATE" ]]; then + msg_error "Template ${PCT_OSTYPE} ${PCT_OSVERSION} not available" + + # Get available versions + mapfile -t AVAILABLE_VERSIONS < <( + pveam available -section system 2>/dev/null | + grep "^${PCT_OSTYPE}-" | + sed -E 's/.*'"${PCT_OSTYPE}"'-([0-9]+\.[0-9]+).*/\1/' | + grep -E '^[0-9]+\.[0-9]+$' | + sort -u -V 2>/dev/null || sort -u + ) + + if [[ ${#AVAILABLE_VERSIONS[@]} -gt 0 ]]; then + echo -e "\n${BL}Available versions:${CL}" + for i in "${!AVAILABLE_VERSIONS[@]}"; do + echo " [$((i + 1))] ${AVAILABLE_VERSIONS[$i]}" + done + + echo "" + read -p "Select version [1-${#AVAILABLE_VERSIONS[@]}] or Enter to exit: " choice + + if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#AVAILABLE_VERSIONS[@]} ]]; then + export var_version="${AVAILABLE_VERSIONS[$((choice - 1))]}" + export PCT_OSVERSION="$var_version" + msg_ok "Switched to ${PCT_OSTYPE} ${var_version}" + + # Retry template search with new version + TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}" + SEARCH_PATTERN="^${TEMPLATE_SEARCH}-" + + mapfile -t LOCAL_TEMPLATES < <( + pveam list "$TEMPLATE_STORAGE" 2>/dev/null | + awk -v search="${SEARCH_PATTERN}" -v pattern="${TEMPLATE_PATTERN}" '$1 ~ search && $1 ~ pattern {print $1}' | + sed 's|.*/||' | sort -t - -k 2 -V + ) + mapfile -t ONLINE_TEMPLATES < <( + pveam available -section system 2>/dev/null | + grep -E '\.(tar\.zst|tar\.xz|tar\.gz)$' | + awk -F'\t' '{print $1}' | + grep -E "${SEARCH_PATTERN}.*${TEMPLATE_PATTERN}" | + sort -t - -k 2 -V 2>/dev/null || true + ) + 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 + + # If we still don't have a path but have a valid template name, construct it + if [[ -z "$TEMPLATE_PATH" && -n "$TEMPLATE" ]]; then + TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE" + fi + + [[ -n "$TEMPLATE_PATH" ]] || { + msg_error "Template still not found after version change" + exit 220 + } + else + msg_custom "🚫" "${YW}" "Installation cancelled" + exit 1 + fi + else + msg_error "No ${PCT_OSTYPE} templates available" + exit 220 + fi + fi + } + + # Validate that we found a template + if [[ -z "$TEMPLATE" ]]; then + msg_error "No template found for ${PCT_OSTYPE} ${PCT_OSVERSION}" + msg_custom "ℹ️" "${YW}" "Please check:" + msg_custom " •" "${YW}" "Is pveam catalog available? (run: pveam available -section system)" + msg_custom " •" "${YW}" "Does the template exist for your OS version?" + exit 225 + fi + + 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_custom "ℹ️" "${BL}" "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}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.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_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating 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 not already on local + if [[ "$TEMPLATE_STORAGE" != "local" ]]; then + msg_info "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 + # Local fallback also failed - check for LXC stack version issue + 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." + offer_lxc_stack_upgrade_and_maybe_retry "yes" + rc=$? + case $rc in + 0) : ;; # success - container created, continue + 2) + echo "Upgrade was declined. Please update and re-run: + apt update && apt install --only-upgrade pve-container lxc-pve" + exit 231 + ;; + 3) + echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" + exit 231 + ;; + esac + else + msg_error "Container creation failed. 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 + pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + set +x + fi + exit 209 + fi + else + msg_ok "Container successfully created using local fallback." + fi + else + # Already on local storage and still failed - check LXC stack version + 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." + offer_lxc_stack_upgrade_and_maybe_retry "yes" + rc=$? + case $rc in + 0) : ;; # success - container created, continue + 2) + echo "Upgrade was declined. Please update and re-run: + apt update && apt install --only-upgrade pve-container lxc-pve" + exit 231 + ;; + 3) + echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" + exit 231 + ;; + esac + else + msg_error "Container creation failed. 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 + pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + set +x + fi + exit 209 + fi + fi + else + msg_ok "Container successfully created after template repair." + 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." + + # Report container creation to API + post_to_api +} + +# ============================================================================== +# SECTION 9: POST-INSTALLATION & FINALIZATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# description() +# +# - Sets container description with formatted HTML content +# - Includes: +# * Community-Scripts logo +# * Application name +# * Links to GitHub, Discussions, Issues +# * Ko-fi donation badge +# - Restarts ping-instances.service if present (monitoring) +# - Posts final "done" status to API telemetry +# ------------------------------------------------------------------------------ +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" +} + +# ============================================================================== +# SECTION 10: ERROR HANDLING & EXIT TRAPS +# ============================================================================== + +# ------------------------------------------------------------------------------ +# api_exit_script() +# +# - Exit trap handler for reporting to API telemetry +# - Captures exit code and reports to API using centralized error descriptions +# - Uses explain_exit_code() from error_handler.func for consistent error messages +# - Posts failure status with exit code to API (error description added automatically) +# - Only executes on non-zero exit codes +# ------------------------------------------------------------------------------ +api_exit_script() { + exit_code=$? + if [ $exit_code -ne 0 ]; then + post_update_to_api "failed" "$exit_code" + 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/build.func b/misc/build.func index 794f138ee..ca6336147 100644 --- a/misc/build.func +++ b/misc/build.func @@ -542,29 +542,6 @@ base_settings() { PROTECT_CT=${var_protection:-"${1:-no}"} CT_TIMEZONE=${var_timezone:-""} - # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) - # This must happen here before any usage of these variables - case "${ENABLE_NESTING,,}" in - yes | true) ENABLE_NESTING="1" ;; - no | false) ENABLE_NESTING="0" ;; - esac - case "${ENABLE_KEYCTL,,}" in - yes | true) ENABLE_KEYCTL="1" ;; - no | false) ENABLE_KEYCTL="0" ;; - esac - case "${ENABLE_MKNOD,,}" in - yes | true) ENABLE_MKNOD="1" ;; - no | false) ENABLE_MKNOD="0" ;; - esac - case "${ENABLE_FUSE,,}" in - yes | true) ENABLE_FUSE="1" ;; - no | false) ENABLE_FUSE="0" ;; - esac - case "${PROTECT_CT,,}" in - yes | true) PROTECT_CT="1" ;; - no | false) PROTECT_CT="0" ;; - esac - # 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" @@ -1795,16 +1772,7 @@ install_script() { fi NEXTID=$(pvesh get /cluster/nextid) - - # Get timezone using timedatectl (Debian 13+ compatible) - # Fallback to /etc/timezone for older systems - if command -v timedatectl >/dev/null 2>&1; then - timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC") - elif [ -f /etc/timezone ]; then - timezone=$(cat /etc/timezone) - else - timezone="UTC" - fi + timezone=$(cat /etc/timezone) # Show APP Header header_info @@ -2298,26 +2266,24 @@ build_container() { none) ;; esac - # Build FEATURES array with advanced settings - # Note: All feature flags are already normalized to 0/1 in default_settings() - # Proxmox requires each feature as a separate parameter, not comma-separated string - FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + # Build FEATURES string with advanced settings + # Start with nesting (almost always enabled for Proxmox CTs) + FEATURES="nesting=${ENABLE_NESTING}" # keyctl: needed for Docker inside containers (systemd-networkd workaround) # Typically needed for unprivileged containers with Docker if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES_ARRAY+=("keyctl=1") + FEATURES="$FEATURES,keyctl=1" fi # mknod: allow device node creation (requires kernel 5.3+, experimental) if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES_ARRAY+=("mknod=1") + FEATURES="$FEATURES,mknod=1" fi # FUSE: required for rclone, mergerfs, AppImage, etc. - if [ "$ENABLE_FUSE" == "1" ]; then - FEATURES_ARRAY+=("fuse=1") + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES="$FEATURES,fuse=1" fi # mount: allow specific filesystems (e.g., nfs, ext4, etc.) @@ -2325,7 +2291,7 @@ build_container() { if [ -n "$ALLOW_MOUNT_FS" ]; then # Replace commas with semicolons for proper pct syntax ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + FEATURES="$FEATURES,mount=$ALLOW_MOUNT_FS_FORMATTED" fi TEMP_DIR=$(mktemp -d) @@ -2370,50 +2336,33 @@ build_container() { export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - - # Build PCT_OPTIONS array (not string) for proper parameter handling - PCT_OPTIONS=() - - # Add features - each as separate -features parameter - for feature in "${FEATURES_ARRAY[@]}"; do - PCT_OPTIONS+=("-features" "$feature") - done - - PCT_OPTIONS+=("-hostname" "$HN") - PCT_OPTIONS+=("-tags" "$TAGS") - - if [ -n "$SD" ]; then - PCT_OPTIONS+=($SD) # Storage device flags (already formatted) + # Build protection flag if enabled + _PROT_FLAG="" + if [ "$PROTECT_CT" == "yes" ]; then + _PROT_FLAG="-protection 1" fi - if [ -n "$NS" ]; then - PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - fi - - # Network configuration (single string with all network parameters) - PCT_OPTIONS+=($NET_STRING) - - PCT_OPTIONS+=("-onboot" "1") - PCT_OPTIONS+=("-cores" "$CORE_COUNT") - PCT_OPTIONS+=("-memory" "$RAM_SIZE") - PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - - # Protection flag - if [ "$PROTECT_CT" == "1" ]; then - PCT_OPTIONS+=("-protection" "1") - fi - - # Timezone flag + # Build timezone flag if set + _TZ_FLAG="" if [ -n "$CT_TIMEZONE" ]; then - PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + _TZ_FLAG="-timezone $CT_TIMEZONE" fi - # Password flag (already formatted as "-password xxx") - if [ -n "$PW" ]; then - PCT_OPTIONS+=($PW) - fi - - export PCT_OPTIONS + export PCT_OPTIONS=" + -features '$FEATURES' + -hostname $HN + -tags $TAGS + $SD + $NS + $NET_STRING + -onboot 1 + -cores $CORE_COUNT + -memory $RAM_SIZE + -unprivileged $CT_TYPE + $_PROT_FLAG + $_TZ_FLAG + $PW +" export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" create_lxc_container || exit $? @@ -2588,13 +2537,20 @@ EOF [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}") [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}") - # Use pct set to add devices with proper dev0/dev1 format - # GIDs will be detected and set after container starts - local dev_index=0 + # Add lxc.mount.entry for each device for dev in "${devices[@]}"; do - # Add to config using pct set (will be visible in GUI) - echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" - dev_index=$((dev_index + 1)) + echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" + + if [[ "$CT_TYPE" == "0" ]]; then + # Privileged container - also add cgroup allows + local major minor + major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") + minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") + + if [[ "$major" != "0" && "$minor" != "0" ]]; then + echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" + fi + fi done export GPU_TYPE="$selected_gpu" @@ -2607,11 +2563,20 @@ EOF return 0 fi - # Use pct set for NVIDIA devices - local dev_index=0 + # Add lxc.mount.entry for each NVIDIA device for dev in "${NVIDIA_DEVICES[@]}"; do - echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" - dev_index=$((dev_index + 1)) + echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" + + if [[ "$CT_TYPE" == "0" ]]; then + # Privileged container - also add cgroup allows + local major minor + major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") + minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") + + if [[ "$major" != "0" && "$minor" != "0" ]]; then + echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" + fi + fi done export GPU_TYPE="NVIDIA" @@ -2723,9 +2688,7 @@ EOF' fi if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then - # Set timezone using symlink (Debian 13+ compatible) - # Create /etc/timezone for backwards compatibility with older scripts - pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true" + 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 @@ -2741,6 +2704,21 @@ EOF' # Install SSH keys install_ssh_keys_into_ct + # Dev mode: Setup MOTD/SSH AFTER network is ready and before installation + # This ensures the container is fully booted and accessible via SSH + if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then + msg_dev "Setting up MOTD and SSH for debugging access" + pct exec "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" <<'MOTD_SETUP' + # Only run motd_ssh function if it exists + if declare -f motd_ssh >/dev/null 2>&1; then + motd_ssh + else + msg_warn "motd_ssh function not found in ${var_install}.sh" + fi +MOTD_SETUP + msg_dev "MOTD/SSH ready - container accessible via SSH (IP: $ip_in_lxc)" + fi + # Run application installer # NOTE: We disable error handling here because: # 1. Container errors are caught by error_handler INSIDE container @@ -2793,8 +2771,8 @@ EOF' # Show available logs echo "" - [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" - [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ $build_log_copied == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ $install_log_copied == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" fi # Dev mode: Keep container or open breakpoint shell @@ -2830,18 +2808,6 @@ EOF' echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" elif [[ "$response" =~ ^[Nn]$ ]]; then echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" - - # Dev mode: Setup MOTD/SSH for debugging access to broken container - # if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then - # echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" - # if pct exec "$CTID" -- bash -c " - # source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/install.func) - # declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true - # " >/dev/null 2>&1; then - # local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) - # echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" - # fi - # fi fi else # Timeout - auto-remove @@ -2934,40 +2900,79 @@ fix_gpu_gids() { return 0 fi + # Silent operation to avoid spinner conflicts msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" - # Get actual GIDs from container + # Ermittle die tatsächlichen GIDs aus dem Container local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - # Create groups if they don't exist + # Fallbacks wenn Gruppen nicht existieren if [[ -z "$video_gid" ]]; then - pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1 + # Versuche die video Gruppe zu erstellen + pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") - [[ -z "$video_gid" ]] && video_gid="44" + [[ -z "$video_gid" ]] && video_gid="44" # Ultimate fallback fi if [[ -z "$render_gid" ]]; then - pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1 + # Versuche die render Gruppe zu erstellen + pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - [[ -z "$render_gid" ]] && render_gid="104" + [[ -z "$render_gid" ]] && render_gid="104" # Ultimate fallback fi - # Stop container to update config - pct stop "$CTID" >/dev/null 2>&1 - sleep 1 + msg_custom "ℹ️" "${DGN}" "Container GIDs detected - video:${video_gid}, render:${render_gid}" - # Update dev entries with correct GIDs - sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG" - sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG" + # Prüfe ob die GIDs von den Defaults abweichen + local need_update=0 + if [[ "$video_gid" != "44" ]] || [[ "$render_gid" != "104" ]]; then + need_update=1 + fi - # Restart container - pct start "$CTID" >/dev/null 2>&1 - sleep 2 + if [[ $need_update -eq 1 ]]; then + msg_custom "🔄" "${YW}" "Updating device GIDs in container config" - msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})" + # Stoppe Container für Config-Update + pct stop "$CTID" >/dev/null 2>&1 - # For privileged containers: also fix permissions inside container + # Update die dev Einträge mit korrekten GIDs + # Backup der Config + cp "$LXC_CONFIG" "${LXC_CONFIG}.bak" + + # Parse und update jeden dev Eintrag + while IFS= read -r line; do + if [[ "$line" =~ ^dev[0-9]+: ]]; then + # Extract device path + local device_path=$(echo "$line" | sed -E 's/^dev[0-9]+: ([^,]+).*/\1/') + local dev_num=$(echo "$line" | sed -E 's/^(dev[0-9]+):.*/\1/') + + if [[ "$device_path" =~ renderD ]]; then + # RenderD device - use render GID + echo "${dev_num}: ${device_path},gid=${render_gid}" + elif [[ "$device_path" =~ card ]]; then + # Card device - use video GID + echo "${dev_num}: ${device_path},gid=${video_gid}" + else + # Keep original line + echo "$line" + fi + else + # Keep non-dev lines + echo "$line" + fi + done <"$LXC_CONFIG" >"${LXC_CONFIG}.new" + + mv "${LXC_CONFIG}.new" "$LXC_CONFIG" + + # Starte Container wieder + pct start "$CTID" >/dev/null 2>&1 + sleep 3 + + msg_ok "Device GIDs updated successfully" + else + msg_ok "Device GIDs are already correct" + fi if [[ "$CT_TYPE" == "0" ]]; then pct exec "$CTID" -- bash -c " if [ -d /dev/dri ]; then @@ -3133,7 +3138,7 @@ create_lxc_container() { case "${_ans,,}" in y | yes) msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)" - if $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; then + 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" @@ -3578,7 +3583,7 @@ create_lxc_container() { exit 211 } - LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" + LOGFILE="/tmp/pct_create_${CTID}.log" msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" msg_debug "Logfile: $LOGFILE" @@ -3635,7 +3640,7 @@ create_lxc_container() { msg_error "Container creation failed. 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 @@ -3667,7 +3672,7 @@ create_lxc_container() { msg_error "Container creation failed. 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 @@ -3691,9 +3696,6 @@ create_lxc_container() { } msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." - - # Report container creation to API - post_to_api } # ============================================================================== From 8740271cd9a1390cd0e25075439373ad50a7f5e3 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:48:53 +0100 Subject: [PATCH 23/54] Update build.func --- misc/build.func | 138 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 95 insertions(+), 43 deletions(-) diff --git a/misc/build.func b/misc/build.func index ca6336147..462aa6ec9 100644 --- a/misc/build.func +++ b/misc/build.func @@ -542,6 +542,29 @@ base_settings() { PROTECT_CT=${var_protection:-"${1:-no}"} CT_TIMEZONE=${var_timezone:-""} + # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) + # This must happen here before any usage of these variables + case "${ENABLE_NESTING,,}" in + yes | true) ENABLE_NESTING="1" ;; + no | false) ENABLE_NESTING="0" ;; + esac + case "${ENABLE_KEYCTL,,}" in + yes | true) ENABLE_KEYCTL="1" ;; + no | false) ENABLE_KEYCTL="0" ;; + esac + case "${ENABLE_MKNOD,,}" in + yes | true) ENABLE_MKNOD="1" ;; + no | false) ENABLE_MKNOD="0" ;; + esac + case "${ENABLE_FUSE,,}" in + yes | true) ENABLE_FUSE="1" ;; + no | false) ENABLE_FUSE="0" ;; + esac + case "${PROTECT_CT,,}" in + yes | true) PROTECT_CT="1" ;; + no | false) PROTECT_CT="0" ;; + esac + # 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" @@ -1772,7 +1795,16 @@ install_script() { fi NEXTID=$(pvesh get /cluster/nextid) - timezone=$(cat /etc/timezone) + + # Get timezone using timedatectl (Debian 13+ compatible) + # Fallback to /etc/timezone for older systems + if command -v timedatectl >/dev/null 2>&1; then + timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC") + elif [ -f /etc/timezone ]; then + timezone=$(cat /etc/timezone) + else + timezone="UTC" + fi # Show APP Header header_info @@ -2266,24 +2298,26 @@ build_container() { none) ;; esac - # Build FEATURES string with advanced settings - # Start with nesting (almost always enabled for Proxmox CTs) - FEATURES="nesting=${ENABLE_NESTING}" + # Build FEATURES array with advanced settings + # Note: All feature flags are already normalized to 0/1 in default_settings() + # Proxmox requires each feature as a separate parameter, not comma-separated string + FEATURES_ARRAY=() + FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") # keyctl: needed for Docker inside containers (systemd-networkd workaround) # Typically needed for unprivileged containers with Docker if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES="$FEATURES,keyctl=1" + FEATURES_ARRAY+=("keyctl=1") fi # mknod: allow device node creation (requires kernel 5.3+, experimental) if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES="$FEATURES,mknod=1" + FEATURES_ARRAY+=("mknod=1") fi # FUSE: required for rclone, mergerfs, AppImage, etc. - if [ "$ENABLE_FUSE" == "yes" ]; then - FEATURES="$FEATURES,fuse=1" + if [ "$ENABLE_FUSE" == "1" ]; then + FEATURES_ARRAY+=("fuse=1") fi # mount: allow specific filesystems (e.g., nfs, ext4, etc.) @@ -2291,7 +2325,7 @@ build_container() { if [ -n "$ALLOW_MOUNT_FS" ]; then # Replace commas with semicolons for proper pct syntax ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES="$FEATURES,mount=$ALLOW_MOUNT_FS_FORMATTED" + FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") fi TEMP_DIR=$(mktemp -d) @@ -2336,33 +2370,50 @@ build_container() { export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - # Build protection flag if enabled - _PROT_FLAG="" - if [ "$PROTECT_CT" == "yes" ]; then - _PROT_FLAG="-protection 1" + + # Build PCT_OPTIONS array (not string) for proper parameter handling + PCT_OPTIONS=() + + # Add features - each as separate -features parameter + for feature in "${FEATURES_ARRAY[@]}"; do + PCT_OPTIONS+=("-features" "$feature") + done + + PCT_OPTIONS+=("-hostname" "$HN") + PCT_OPTIONS+=("-tags" "$TAGS") + + if [ -n "$SD" ]; then + PCT_OPTIONS+=($SD) # Storage device flags (already formatted) fi - # Build timezone flag if set - _TZ_FLAG="" + if [ -n "$NS" ]; then + PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) + fi + + # Network configuration (single string with all network parameters) + PCT_OPTIONS+=($NET_STRING) + + PCT_OPTIONS+=("-onboot" "1") + PCT_OPTIONS+=("-cores" "$CORE_COUNT") + PCT_OPTIONS+=("-memory" "$RAM_SIZE") + PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") + + # Protection flag + if [ "$PROTECT_CT" == "1" ]; then + PCT_OPTIONS+=("-protection" "1") + fi + + # Timezone flag if [ -n "$CT_TIMEZONE" ]; then - _TZ_FLAG="-timezone $CT_TIMEZONE" + PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") fi - export PCT_OPTIONS=" - -features '$FEATURES' - -hostname $HN - -tags $TAGS - $SD - $NS - $NET_STRING - -onboot 1 - -cores $CORE_COUNT - -memory $RAM_SIZE - -unprivileged $CT_TYPE - $_PROT_FLAG - $_TZ_FLAG - $PW -" + # Password flag (already formatted as "-password xxx") + if [ -n "$PW" ]; then + PCT_OPTIONS+=($PW) + fi + + export PCT_OPTIONS export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" create_lxc_container || exit $? @@ -2688,7 +2739,9 @@ EOF' 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" + # Set timezone using symlink (Debian 13+ compatible) + # Create /etc/timezone for backwards compatibility with older scripts + pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true" else msg_warn "Skipping timezone setup – zone '$tz' not found in container" fi @@ -2900,31 +2953,30 @@ fix_gpu_gids() { return 0 fi - # Silent operation to avoid spinner conflicts msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" - # Ermittle die tatsächlichen GIDs aus dem Container + # Get actual GIDs from container local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - # Fallbacks wenn Gruppen nicht existieren + # Create groups if they don't exist if [[ -z "$video_gid" ]]; then - # Versuche die video Gruppe zu erstellen - pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" + pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1 video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") - [[ -z "$video_gid" ]] && video_gid="44" # Ultimate fallback + [[ -z "$video_gid" ]] && video_gid="44" fi if [[ -z "$render_gid" ]]; then - # Versuche die render Gruppe zu erstellen - pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" + pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1 render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - [[ -z "$render_gid" ]] && render_gid="104" # Ultimate fallback + [[ -z "$render_gid" ]] && render_gid="104" fi - msg_custom "ℹ️" "${DGN}" "Container GIDs detected - video:${video_gid}, render:${render_gid}" + # Stop container to update config + pct stop "$CTID" >/dev/null 2>&1 + sleep 1 - # Prüfe ob die GIDs von den Defaults abweichen + # Check if GIDs differ from defaults local need_update=0 if [[ "$video_gid" != "44" ]] || [[ "$render_gid" != "104" ]]; then need_update=1 From 532fa094f31f32b39d266fa41847f32f4489f246 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:11:13 +0100 Subject: [PATCH 24/54] Refactor GPU passthrough and device config handling Switches device configuration to use pct set format for GPU passthrough, simplifying config and improving GUI visibility. Updates GID handling logic for devices, streamlines fix_gpu_gids function, and improves container creation logging and API reporting. Removes commented-out MOTD/SSH debug setup from build copy.func. --- misc/build copy.func | 12 ----- misc/build.func | 103 +++++++++++-------------------------------- 2 files changed, 26 insertions(+), 89 deletions(-) diff --git a/misc/build copy.func b/misc/build copy.func index 794f138ee..568881307 100644 --- a/misc/build copy.func +++ b/misc/build copy.func @@ -2830,18 +2830,6 @@ EOF' echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" elif [[ "$response" =~ ^[Nn]$ ]]; then echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" - - # Dev mode: Setup MOTD/SSH for debugging access to broken container - # if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then - # echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" - # if pct exec "$CTID" -- bash -c " - # source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/install.func) - # declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true - # " >/dev/null 2>&1; then - # local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) - # echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" - # fi - # fi fi else # Timeout - auto-remove diff --git a/misc/build.func b/misc/build.func index 462aa6ec9..564f4f02e 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2588,20 +2588,13 @@ EOF [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}") [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}") - # Add lxc.mount.entry for each device + # Use pct set to add devices with proper dev0/dev1 format + # GIDs will be detected and set after container starts + local dev_index=0 for dev in "${devices[@]}"; do - echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" - - if [[ "$CT_TYPE" == "0" ]]; then - # Privileged container - also add cgroup allows - local major minor - major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") - minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") - - if [[ "$major" != "0" && "$minor" != "0" ]]; then - echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" - fi - fi + # Add to config using pct set (will be visible in GUI) + echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" + dev_index=$((dev_index + 1)) done export GPU_TYPE="$selected_gpu" @@ -2614,20 +2607,11 @@ EOF return 0 fi - # Add lxc.mount.entry for each NVIDIA device + # Use pct set for NVIDIA devices + local dev_index=0 for dev in "${NVIDIA_DEVICES[@]}"; do - echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" - - if [[ "$CT_TYPE" == "0" ]]; then - # Privileged container - also add cgroup allows - local major minor - major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") - minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") - - if [[ "$major" != "0" && "$minor" != "0" ]]; then - echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" - fi - fi + echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" + dev_index=$((dev_index + 1)) done export GPU_TYPE="NVIDIA" @@ -2976,55 +2960,17 @@ fix_gpu_gids() { pct stop "$CTID" >/dev/null 2>&1 sleep 1 - # Check if GIDs differ from defaults - local need_update=0 - if [[ "$video_gid" != "44" ]] || [[ "$render_gid" != "104" ]]; then - need_update=1 - fi + # Update dev entries with correct GIDs + sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG" + sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG" - if [[ $need_update -eq 1 ]]; then - msg_custom "🔄" "${YW}" "Updating device GIDs in container config" + # Restart container + pct start "$CTID" >/dev/null 2>&1 + sleep 2 - # Stoppe Container für Config-Update - pct stop "$CTID" >/dev/null 2>&1 - - # Update die dev Einträge mit korrekten GIDs - # Backup der Config - cp "$LXC_CONFIG" "${LXC_CONFIG}.bak" - - # Parse und update jeden dev Eintrag - while IFS= read -r line; do - if [[ "$line" =~ ^dev[0-9]+: ]]; then - # Extract device path - local device_path=$(echo "$line" | sed -E 's/^dev[0-9]+: ([^,]+).*/\1/') - local dev_num=$(echo "$line" | sed -E 's/^(dev[0-9]+):.*/\1/') - - if [[ "$device_path" =~ renderD ]]; then - # RenderD device - use render GID - echo "${dev_num}: ${device_path},gid=${render_gid}" - elif [[ "$device_path" =~ card ]]; then - # Card device - use video GID - echo "${dev_num}: ${device_path},gid=${video_gid}" - else - # Keep original line - echo "$line" - fi - else - # Keep non-dev lines - echo "$line" - fi - done <"$LXC_CONFIG" >"${LXC_CONFIG}.new" - - mv "${LXC_CONFIG}.new" "$LXC_CONFIG" - - # Starte Container wieder - pct start "$CTID" >/dev/null 2>&1 - sleep 3 - - msg_ok "Device GIDs updated successfully" - else - msg_ok "Device GIDs are already correct" - fi + msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})" + + # For privileged containers: also fix permissions inside container if [[ "$CT_TYPE" == "0" ]]; then pct exec "$CTID" -- bash -c " if [ -d /dev/dri ]; then @@ -3190,7 +3136,7 @@ create_lxc_container() { 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 + if $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; then msg_ok "LXC stack upgraded." if [[ "$do_retry" == "yes" ]]; then msg_info "Retrying container creation after upgrade" @@ -3635,7 +3581,7 @@ create_lxc_container() { exit 211 } - LOGFILE="/tmp/pct_create_${CTID}.log" + LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" msg_debug "Logfile: $LOGFILE" @@ -3692,7 +3638,7 @@ create_lxc_container() { msg_error "Container creation failed. 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" + pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 @@ -3724,7 +3670,7 @@ create_lxc_container() { msg_error "Container creation failed. 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" + pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 @@ -3748,6 +3694,9 @@ create_lxc_container() { } msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." + + # Report container creation to API + post_to_api } # ============================================================================== From 3a4d938fac7cb8be7d3957715add22a40182bdda Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:28:56 +0100 Subject: [PATCH 25/54] Update install.func --- misc/install.func | 263 +++++++++++++++++++++++----------------------- 1 file changed, 132 insertions(+), 131 deletions(-) diff --git a/misc/install.func b/misc/install.func index 97bd19b8c..81a6f8a30 100644 --- a/misc/install.func +++ b/misc/install.func @@ -30,19 +30,19 @@ # Ensure INSTALL_LOG is set (exported from build.func, but fallback if missing) if [[ -z "${INSTALL_LOG:-}" ]]; then - INSTALL_LOG="/root/.install-${SESSION_ID:-unknown}.log" + INSTALL_LOG="/root/.install-${SESSION_ID:-unknown}.log" fi # Dev mode: Persistent logs directory if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - INSTALL_LOG="/var/log/community-scripts/install-${SESSION_ID:-unknown}-$(date +%Y%m%d_%H%M%S).log" + mkdir -p /var/log/community-scripts + INSTALL_LOG="/var/log/community-scripts/install-${SESSION_ID:-unknown}-$(date +%Y%m%d_%H%M%S).log" fi 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 + 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) @@ -64,12 +64,12 @@ parse_dev_mode # - Sets verbose mode via set_std_mode() # ------------------------------------------------------------------------------ verb_ip6() { - set_std_mode # Set STD mode based on VERBOSE + 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 + if [ "$DISABLEIPV6" == "yes" ]; then + echo "net.ipv6.conf.all.disable_ipv6 = 1" >>/etc/sysctl.conf + $STD sysctl -p + fi } # ------------------------------------------------------------------------------ @@ -82,24 +82,24 @@ verb_ip6() { # - Exits with error if network unavailable after retries # ------------------------------------------------------------------------------ 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 + msg_info "Setting up Container OS" + for ((i = RETRY_NUM; i > 0; i--)); do + if [ "$(hostname -I)" != "" ]; then + break 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)" + 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)" } # ------------------------------------------------------------------------------ @@ -114,65 +114,65 @@ setting_up_container() { # - Uses fatal() on DNS resolution failure for critical hosts # ------------------------------------------------------------------------------ network_check() { - set +e - trap - ERR - ipv4_connected=false - ipv6_connected=false - sleep 1 + 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 - ipv4_connected=true - ipv4_status="${GN}✔${CL} IPv4" + # 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 + ipv4_connected=true + ipv4_status="${GN}✔${CL} IPv4" + else + ipv4_status="${RD}✖${CL} IPv4" + 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 + ipv6_connected=true + ipv6_status="${GN}✔${CL} IPv6" + else + ipv6_status="${RD}✖${CL} IPv6" + fi + + # Show combined status + msg_ok "Internet: ${ipv4_status} ${ipv6_status}" + + # 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 - ipv4_status="${RD}✖${CL} IPv4" + echo -e "${NETWORK}Check Network Settings" + exit 1 fi + 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 - ipv6_connected=true - ipv6_status="${GN}✔${CL} IPv6" + # 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 - ipv6_status="${RD}✖${CL} IPv6" + GIT_STATUS+=" $HOST:($DNSOK)" fi + done - # Show combined status - msg_ok "Internet: ${ipv4_status} ${ipv6_status}" + if [[ "$DNS_FAILED" == true ]]; then + fatal "$GIT_STATUS" + else + msg_ok "$GIT_STATUS" + 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 + set -e + trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } # ============================================================================== @@ -189,10 +189,10 @@ network_check() { # - Uses $STD wrapper to suppress output unless VERBOSE=yes # ------------------------------------------------------------------------------ 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 + 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" @@ -200,13 +200,13 @@ 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) + 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) } # ============================================================================== @@ -228,32 +228,32 @@ EOF # - Configures TERM environment variable for better terminal support # ------------------------------------------------------------------------------ motd_ssh() { - grep -qxF "export TERM='xterm-256color'" /root/.bashrc || echo "export TERM='xterm-256color'" >>/root/.bashrc + 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 + 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" + 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/* + 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 + if [[ "${SSH_ROOT}" == "yes" ]]; then + sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/g" /etc/ssh/sshd_config + systemctl restart sshd + fi } # ============================================================================== @@ -270,24 +270,25 @@ motd_ssh() { # - Sets proper permissions on SSH directories and key files # ------------------------------------------------------------------------------ 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 + if [[ -z "${PASSWORD:-}" ]]; then + msg_info "Configuring autologin for root" + passwd -d root >/dev/null 2>&1 || true + 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 - $STD systemctl daemon-reload || true - 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 + $STD systemctl daemon-reload || true + 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 645b532e17f3fbcd24b74459468e7c097a1b0a6a Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:43:32 +0100 Subject: [PATCH 26/54] Update install.func --- misc/install.func | 4 ---- 1 file changed, 4 deletions(-) diff --git a/misc/install.func b/misc/install.func index 81a6f8a30..e97a49bcf 100644 --- a/misc/install.func +++ b/misc/install.func @@ -256,10 +256,6 @@ motd_ssh() { fi } -# ============================================================================== -# SECTION 5: CONTAINER CUSTOMIZATION -# ============================================================================== - # ------------------------------------------------------------------------------ # customize() # From 1f6034a15bcc9278828180eac3a2be14f3e6440f Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:19:40 +0100 Subject: [PATCH 27/54] Update install.func --- misc/install.func | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/misc/install.func b/misc/install.func index e97a49bcf..8548d34fc 100644 --- a/misc/install.func +++ b/misc/install.func @@ -268,19 +268,39 @@ motd_ssh() { customize() { if [[ -z "${PASSWORD:-}" ]]; then msg_info "Configuring autologin for root" + + # Enable root account (remove password lock) passwd -d root >/dev/null 2>&1 || true + + # Create getty override for container-getty@1.service GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" mkdir -p "$(dirname "$GETTY_OVERRIDE")" - cat <"$GETTY_OVERRIDE" + cat <<'EOF' >"$GETTY_OVERRIDE" [Service] ExecStart= -ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM +ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 linux EOF - $STD systemctl daemon-reload || true - msg_ok "Customized Container" + + # Create getty override for console-getty.service + CONSOLE_OVERRIDE="/etc/systemd/system/console-getty.service.d/override.conf" + mkdir -p "$(dirname "$CONSOLE_OVERRIDE")" + cat <<'EOF' >"$CONSOLE_OVERRIDE" +[Service] +ExecStart= +ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud console 115200,38400,9600 linux +EOF + + # Reload systemd daemon + $STD systemctl daemon-reload + + msg_ok "Autologin configured" fi + + msg_ok "Customized Container" + 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 From cb2c3fab969fcc8cf1ed913c6deca9754c7da146 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:22:04 +0100 Subject: [PATCH 28/54] Update install.func --- misc/install.func | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/misc/install.func b/misc/install.func index 8548d34fc..9ea4a0fab 100644 --- a/misc/install.func +++ b/misc/install.func @@ -274,24 +274,16 @@ customize() { # Create getty override for container-getty@1.service GETTY_OVERRIDE="/etc/systemd/system/container-getty@1.service.d/override.conf" - mkdir -p "$(dirname "$GETTY_OVERRIDE")" - cat <<'EOF' >"$GETTY_OVERRIDE" + mkdir -p $(dirname $GETTY_OVERRIDE) + cat <$GETTY_OVERRIDE [Service] ExecStart= -ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 linux +ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM EOF - # Create getty override for console-getty.service - CONSOLE_OVERRIDE="/etc/systemd/system/console-getty.service.d/override.conf" - mkdir -p "$(dirname "$CONSOLE_OVERRIDE")" - cat <<'EOF' >"$CONSOLE_OVERRIDE" -[Service] -ExecStart= -ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud console 115200,38400,9600 linux -EOF - - # Reload systemd daemon + # Reload and restart getty service $STD systemctl daemon-reload + $STD systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') || true msg_ok "Autologin configured" fi From 7455f8195cffd1fd06482cc3ba52dfce72b31c80 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:35:10 +0100 Subject: [PATCH 29/54] Update build.func --- misc/build.func | 499 +++++++++++++----------------------------------- 1 file changed, 131 insertions(+), 368 deletions(-) diff --git a/misc/build.func b/misc/build.func index 564f4f02e..b0b0404cd 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,17 +47,7 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log - CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" - - # Parse dev_mode early - parse_dev_mode - - # Setup persistent log directory if logs mode active - if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" - fi + CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"} # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then @@ -535,35 +525,6 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} - ENABLE_NESTING=${var_nesting:-"${1:-1}"} - ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} - ALLOW_MOUNT_FS=${var_mount_fs:-""} - ENABLE_MKNOD=${var_mknod:-"${1:-0}"} - PROTECT_CT=${var_protection:-"${1:-no}"} - CT_TIMEZONE=${var_timezone:-""} - - # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) - # This must happen here before any usage of these variables - case "${ENABLE_NESTING,,}" in - yes | true) ENABLE_NESTING="1" ;; - no | false) ENABLE_NESTING="0" ;; - esac - case "${ENABLE_KEYCTL,,}" in - yes | true) ENABLE_KEYCTL="1" ;; - no | false) ENABLE_KEYCTL="0" ;; - esac - case "${ENABLE_MKNOD,,}" in - yes | true) ENABLE_MKNOD="1" ;; - no | false) ENABLE_MKNOD="0" ;; - esac - case "${ENABLE_FUSE,,}" in - yes | true) ENABLE_FUSE="1" ;; - no | false) ENABLE_FUSE="0" ;; - esac - case "${PROTECT_CT,,}" in - yes | true) PROTECT_CT="1" ;; - no | false) PROTECT_CT="0" ;; - esac # 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 @@ -587,9 +548,9 @@ default_var_settings() { # Allowed var_* keys (alphabetically sorted) # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) local VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_keyctl - var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu - var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse + var_gateway var_hostname var_ipv6_method 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 ) @@ -667,14 +628,6 @@ var_ssh=no # Features/Tags/verbosity var_fuse=no var_tun=no - -# Advanced Settings (Proxmox-official features) -var_nesting=1 # Allow nesting (required for Docker/LXC in CT) -var_keyctl=0 # Allow keyctl() - needed for Docker (systemd-networkd workaround) -var_mknod=0 # Allow device node creation (requires kernel 5.3+, experimental) -var_mount_fs= # Allow specific filesystems: nfs,fuse,ext4,etc (leave empty for defaults) -var_protection=no # Prevent accidental deletion of container -var_timezone= # Container timezone (e.g. Europe/Berlin, leave empty for host timezone) var_tags=community-script var_verbose=no @@ -941,12 +894,6 @@ _build_current_app_vars_tmp() { _apt_cacher_ip="${APT_CACHER_IP:-}" _fuse="${ENABLE_FUSE:-no}" _tun="${ENABLE_TUN:-no}" - _nesting="${ENABLE_NESTING:-1}" - _keyctl="${ENABLE_KEYCTL:-0}" - _mknod="${ENABLE_MKNOD:-0}" - _mount_fs="${ALLOW_MOUNT_FS:-}" - _protect="${PROTECT_CT:-no}" - _timezone="${CT_TIMEZONE:-}" _tags="${TAGS:-}" _verbose="${VERBOSE:-no}" @@ -990,12 +937,6 @@ _build_current_app_vars_tmp() { [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")" [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")" - [ -n "$_nesting" ] && echo "var_nesting=$(_sanitize_value "$_nesting")" - [ -n "$_keyctl" ] && echo "var_keyctl=$(_sanitize_value "$_keyctl")" - [ -n "$_mknod" ] && echo "var_mknod=$(_sanitize_value "$_mknod")" - [ -n "$_mount_fs" ] && echo "var_mount_fs=$(_sanitize_value "$_mount_fs")" - [ -n "$_protect" ] && echo "var_protection=$(_sanitize_value "$_protect")" - [ -n "$_timezone" ] && echo "var_timezone=$(_sanitize_value "$_timezone")" [ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")" [ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")" @@ -1578,51 +1519,6 @@ advanced_settings() { configure_ssh_settings export SSH_KEYS_FILE echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - - # Advanced Settings - Proxmox Features - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS" --yesno "Configure Advanced Proxmox Features?" 10 58); then - # keyctl: for Docker support - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable keyctl()" --yesno "Allow keyctl() system calls?\n\nNeeded for: Docker inside container, systemd-networkd\nDefault: No (not needed for most applications)" 10 58); then - ENABLE_KEYCTL="1" - else - ENABLE_KEYCTL="0" - fi - echo -e "${SEARCH}${BOLD}${DGN}Allow keyctl(): ${BGN}$ENABLE_KEYCTL${CL}" - - # mknod: device node creation - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable mknod()" --yesno "Allow device node creation?\n\nNeeded for: Complex device management (experimental, kernel 5.3+)\nDefault: No (rarely needed)" 10 58); then - ENABLE_MKNOD="1" - else - ENABLE_MKNOD="0" - fi - echo -e "${SEARCH}${BOLD}${DGN}Allow mknod(): ${BGN}$ENABLE_MKNOD${CL}" - - # mount: specific filesystems - if MOUNT_FS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allow specific filesystems (e.g., nfs,fuse,ext4)\nLeave blank for defaults" 8 58 "$ALLOW_MOUNT_FS" --title "Mount Filesystems" 3>&1 1>&2 2>&3); then - ALLOW_MOUNT_FS="$MOUNT_FS" - [ -z "$ALLOW_MOUNT_FS" ] && ALLOW_MOUNT_FS="(defaults)" - else - exit_script - fi - echo -e "${SEARCH}${BOLD}${DGN}Mount Filesystems: ${BGN}$ALLOW_MOUNT_FS${CL}" - - # Container protection - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Protection Flag" --yesno "Prevent accidental deletion?\n\nIf enabled, container cannot be deleted or its disk modified\nDefault: No" 10 58); then - PROTECT_CT="yes" - else - PROTECT_CT="no" - fi - echo -e "${SEARCH}${BOLD}${DGN}Container Protection: ${BGN}$PROTECT_CT${CL}" - - # Container timezone - if CT_TIMEZONE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set container timezone (e.g., Europe/Berlin)\nLeave blank to use host timezone" 8 58 "$CT_TIMEZONE" --title "Container Timezone" 3>&1 1>&2 2>&3); then - [ -z "$CT_TIMEZONE" ] && CT_TIMEZONE="(host)" - else - exit_script - fi - echo -e "${SEARCH}${BOLD}${DGN}Container Timezone: ${BGN}$CT_TIMEZONE${CL}" - fi - 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 @@ -1795,16 +1691,7 @@ install_script() { fi NEXTID=$(pvesh get /cluster/nextid) - - # Get timezone using timedatectl (Debian 13+ compatible) - # Fallback to /etc/timezone for older systems - if command -v timedatectl >/dev/null 2>&1; then - timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC") - elif [ -f /etc/timezone ]; then - timezone=$(cat /etc/timezone) - else - timezone="UTC" - fi + timezone=$(cat /etc/timezone) # Show APP Header header_info @@ -1932,12 +1819,13 @@ settings_menu() { 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+=("3" "Edit App.vars for ${APP}") - settings_items+=("4" "Exit") + settings_items+=("4" "Edit App.vars for ${APP}") + settings_items+=("5" "Exit") else - settings_items+=("3" "Exit") + settings_items+=("4" "Exit") fi local choice @@ -2298,34 +2186,14 @@ build_container() { none) ;; esac - # Build FEATURES array with advanced settings - # Note: All feature flags are already normalized to 0/1 in default_settings() - # Proxmox requires each feature as a separate parameter, not comma-separated string - FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") - - # keyctl: needed for Docker inside containers (systemd-networkd workaround) - # Typically needed for unprivileged containers with Docker - if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES_ARRAY+=("keyctl=1") + if [ "$CT_TYPE" == "1" ]; then + FEATURES="keyctl=1,nesting=1" + else + FEATURES="nesting=1" fi - # mknod: allow device node creation (requires kernel 5.3+, experimental) - if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES_ARRAY+=("mknod=1") - fi - - # FUSE: required for rclone, mergerfs, AppImage, etc. - if [ "$ENABLE_FUSE" == "1" ]; then - FEATURES_ARRAY+=("fuse=1") - fi - - # mount: allow specific filesystems (e.g., nfs, ext4, etc.) - # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) - if [ -n "$ALLOW_MOUNT_FS" ]; then - # Replace commas with semicolons for proper pct syntax - ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES="$FEATURES,fuse=1" fi TEMP_DIR=$(mktemp -d) @@ -2338,16 +2206,6 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" - export BUILD_LOG="$BUILD_LOG" - export INSTALL_LOG="/root/.install-${SESSION_ID}.log" - export dev_mode="${dev_mode:-}" - export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" - export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" - export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" - export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" - export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" - export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" - export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" @@ -2361,59 +2219,22 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" - export ENABLE_NESTING="$ENABLE_NESTING" - export ENABLE_KEYCTL="$ENABLE_KEYCTL" - export ENABLE_MKNOD="$ENABLE_MKNOD" - export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" - export PROTECT_CT="$PROTECT_CT" - export CT_TIMEZONE="$CT_TIMEZONE" export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - - # Build PCT_OPTIONS array (not string) for proper parameter handling - PCT_OPTIONS=() - - # Add features - each as separate -features parameter - for feature in "${FEATURES_ARRAY[@]}"; do - PCT_OPTIONS+=("-features" "$feature") - done - - PCT_OPTIONS+=("-hostname" "$HN") - PCT_OPTIONS+=("-tags" "$TAGS") - - if [ -n "$SD" ]; then - PCT_OPTIONS+=($SD) # Storage device flags (already formatted) - fi - - if [ -n "$NS" ]; then - PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - fi - - # Network configuration (single string with all network parameters) - PCT_OPTIONS+=($NET_STRING) - - PCT_OPTIONS+=("-onboot" "1") - PCT_OPTIONS+=("-cores" "$CORE_COUNT") - PCT_OPTIONS+=("-memory" "$RAM_SIZE") - PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - - # Protection flag - if [ "$PROTECT_CT" == "1" ]; then - PCT_OPTIONS+=("-protection" "1") - fi - - # Timezone flag - if [ -n "$CT_TIMEZONE" ]; then - PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") - fi - - # Password flag (already formatted as "-password xxx") - if [ -n "$PW" ]; then - PCT_OPTIONS+=($PW) - fi - - export PCT_OPTIONS + 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 $? @@ -2588,13 +2409,20 @@ EOF [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}") [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}") - # Use pct set to add devices with proper dev0/dev1 format - # GIDs will be detected and set after container starts - local dev_index=0 + # Add lxc.mount.entry for each device for dev in "${devices[@]}"; do - # Add to config using pct set (will be visible in GUI) - echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" - dev_index=$((dev_index + 1)) + echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" + + if [[ "$CT_TYPE" == "0" ]]; then + # Privileged container - also add cgroup allows + local major minor + major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") + minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") + + if [[ "$major" != "0" && "$minor" != "0" ]]; then + echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" + fi + fi done export GPU_TYPE="$selected_gpu" @@ -2607,11 +2435,20 @@ EOF return 0 fi - # Use pct set for NVIDIA devices - local dev_index=0 + # Add lxc.mount.entry for each NVIDIA device for dev in "${NVIDIA_DEVICES[@]}"; do - echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" - dev_index=$((dev_index + 1)) + echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" + + if [[ "$CT_TYPE" == "0" ]]; then + # Privileged container - also add cgroup allows + local major minor + major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") + minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") + + if [[ "$major" != "0" && "$minor" != "0" ]]; then + echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" + fi + fi done export GPU_TYPE="NVIDIA" @@ -2723,9 +2560,7 @@ EOF' fi if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then - # Set timezone using symlink (Debian 13+ compatible) - # Create /etc/timezone for backwards compatibility with older scripts - pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true" + 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 @@ -2741,121 +2576,14 @@ EOF' # Install SSH keys install_ssh_keys_into_ct - # Dev mode: Setup MOTD/SSH AFTER network is ready and before installation - # This ensures the container is fully booted and accessible via SSH - if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then - msg_dev "Setting up MOTD and SSH for debugging access" - pct exec "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" <<'MOTD_SETUP' - # Only run motd_ssh function if it exists - if declare -f motd_ssh >/dev/null 2>&1; then - motd_ssh - else - msg_warn "motd_ssh function not found in ${var_install}.sh" - fi -MOTD_SETUP - msg_dev "MOTD/SSH ready - container accessible via SSH (IP: $ip_in_lxc)" - fi - # Run application installer - # NOTE: We disable error handling here because: - # 1. Container errors are caught by error_handler INSIDE container - # 2. Container creates flag file with exit code - # 3. We read flag file and handle cleanup manually below - # 4. We DON'T want host error_handler to fire for lxc-attach command itself - - set +Eeuo pipefail # Disable ALL error handling temporarily - trap - ERR # Remove ERR trap completely - - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" - local lxc_exit=$? - - set -Eeuo pipefail # Re-enable error handling - trap 'error_handler' ERR # Restore ERR trap - - # Check for error flag file in container (more reliable than lxc-attach exit code) - local install_exit_code=0 - if [[ -n "${SESSION_ID:-}" ]]; then - local error_flag="/root/.install-${SESSION_ID}.failed" - if pct exec "$CTID" -- test -f "$error_flag" 2>/dev/null; then - install_exit_code=$(pct exec "$CTID" -- cat "$error_flag" 2>/dev/null || echo "1") - pct exec "$CTID" -- rm -f "$error_flag" 2>/dev/null || true - fi - fi - - # Fallback to lxc-attach exit code if no flag file - if [[ $install_exit_code -eq 0 && $lxc_exit -ne 0 ]]; then - install_exit_code=$lxc_exit - fi - - # Installation failed? - if [[ $install_exit_code -ne 0 ]]; then - msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})" - - # Copy both logs from container before potential deletion - local build_log_copied=false - local install_log_copied=false - + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then + local exit_code=$? + # Try to copy installation log from container before exiting if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then - # Copy BUILD_LOG (creation log) if it exists - if [[ -f "${BUILD_LOG}" ]]; then - cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true - fi - - # Copy INSTALL_LOG from container - if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then - install_log_copied=true - fi - - # Show available logs - echo "" - [[ $build_log_copied == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" - [[ $install_log_copied == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-${SESSION_ID}.log" 2>/dev/null || true fi - - # Dev mode: Keep container or open breakpoint shell - if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then - msg_dev "Keep mode active - container ${CTID} preserved" - return 0 - elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then - msg_dev "Breakpoint mode - opening shell in container ${CTID}" - echo -e "${YW}Type 'exit' to return to host${CL}" - pct enter "$CTID" - echo "" - echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" - if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - msg_ok "Container ${CTID} removed" - else - msg_dev "Container ${CTID} kept for debugging" - fi - exit $install_exit_code - fi - - # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner) - echo "" - echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" - - if read -t 60 -r response; then - if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then - # Remove container - echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" - elif [[ "$response" =~ ^[Nn]$ ]]; then - echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" - fi - else - # Timeout - auto-remove - echo -e "\n${YW}No response - auto-removing container${CL}" - echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" - fi - - exit $install_exit_code + exit $exit_code fi } @@ -2937,40 +2665,79 @@ fix_gpu_gids() { return 0 fi + # Silent operation to avoid spinner conflicts msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" - # Get actual GIDs from container + # Ermittle die tatsächlichen GIDs aus dem Container local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - # Create groups if they don't exist + # Fallbacks wenn Gruppen nicht existieren if [[ -z "$video_gid" ]]; then - pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1 + # Versuche die video Gruppe zu erstellen + pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") - [[ -z "$video_gid" ]] && video_gid="44" + [[ -z "$video_gid" ]] && video_gid="44" # Ultimate fallback fi if [[ -z "$render_gid" ]]; then - pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1 + # Versuche die render Gruppe zu erstellen + pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - [[ -z "$render_gid" ]] && render_gid="104" + [[ -z "$render_gid" ]] && render_gid="104" # Ultimate fallback fi - # Stop container to update config - pct stop "$CTID" >/dev/null 2>&1 - sleep 1 + msg_custom "ℹ️" "${DGN}" "Container GIDs detected - video:${video_gid}, render:${render_gid}" - # Update dev entries with correct GIDs - sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG" - sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG" + # Prüfe ob die GIDs von den Defaults abweichen + local need_update=0 + if [[ "$video_gid" != "44" ]] || [[ "$render_gid" != "104" ]]; then + need_update=1 + fi - # Restart container - pct start "$CTID" >/dev/null 2>&1 - sleep 2 + if [[ $need_update -eq 1 ]]; then + msg_custom "🔄" "${YW}" "Updating device GIDs in container config" - msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})" - - # For privileged containers: also fix permissions inside container + # Stoppe Container für Config-Update + pct stop "$CTID" >/dev/null 2>&1 + + # Update die dev Einträge mit korrekten GIDs + # Backup der Config + cp "$LXC_CONFIG" "${LXC_CONFIG}.bak" + + # Parse und update jeden dev Eintrag + while IFS= read -r line; do + if [[ "$line" =~ ^dev[0-9]+: ]]; then + # Extract device path + local device_path=$(echo "$line" | sed -E 's/^dev[0-9]+: ([^,]+).*/\1/') + local dev_num=$(echo "$line" | sed -E 's/^(dev[0-9]+):.*/\1/') + + if [[ "$device_path" =~ renderD ]]; then + # RenderD device - use render GID + echo "${dev_num}: ${device_path},gid=${render_gid}" + elif [[ "$device_path" =~ card ]]; then + # Card device - use video GID + echo "${dev_num}: ${device_path},gid=${video_gid}" + else + # Keep original line + echo "$line" + fi + else + # Keep non-dev lines + echo "$line" + fi + done <"$LXC_CONFIG" >"${LXC_CONFIG}.new" + + mv "${LXC_CONFIG}.new" "$LXC_CONFIG" + + # Starte Container wieder + pct start "$CTID" >/dev/null 2>&1 + sleep 3 + + msg_ok "Device GIDs updated successfully" + else + msg_ok "Device GIDs are already correct" + fi if [[ "$CT_TYPE" == "0" ]]; then pct exec "$CTID" -- bash -c " if [ -d /dev/dri ]; then @@ -3136,7 +2903,7 @@ create_lxc_container() { case "${_ans,,}" in y | yes) msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)" - if $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; then + 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" @@ -3581,13 +3348,13 @@ create_lxc_container() { exit 211 } - LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" + 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_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating template..." + 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 @@ -3606,16 +3373,18 @@ create_lxc_container() { # Retry after repair if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then - # Fallback to local storage if not already on local + # Fallback to local storage if [[ "$TEMPLATE_STORAGE" != "local" ]]; then - msg_info "Retrying container creation with fallback to local storage..." + 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 - # Local fallback also failed - check for LXC stack version issue + 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." @@ -3635,19 +3404,18 @@ create_lxc_container() { ;; esac else - msg_error "Container creation failed. See $LOGFILE" + 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 fi - else - msg_ok "Container successfully created using local fallback." fi else - # Already on local storage and still failed - check LXC stack version + 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." @@ -3670,14 +3438,12 @@ create_lxc_container() { msg_error "Container creation failed. 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + 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_ok "Container successfully created after template repair." fi fi @@ -3694,9 +3460,6 @@ create_lxc_container() { } msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." - - # Report container creation to API - post_to_api } # ============================================================================== From e147c848bb1a5ace434d181ba6058f0a762fd275 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:35:48 +0100 Subject: [PATCH 30/54] Update install.func --- misc/install.func | 57 +++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 41 deletions(-) diff --git a/misc/install.func b/misc/install.func index 9ea4a0fab..e9e319576 100644 --- a/misc/install.func +++ b/misc/install.func @@ -28,17 +28,6 @@ # SECTION 1: INITIALIZATION # ============================================================================== -# Ensure INSTALL_LOG is set (exported from build.func, but fallback if missing) -if [[ -z "${INSTALL_LOG:-}" ]]; then - INSTALL_LOG="/root/.install-${SESSION_ID:-unknown}.log" -fi - -# Dev mode: Persistent logs directory -if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - INSTALL_LOG="/var/log/community-scripts/install-${SESSION_ID:-unknown}-$(date +%Y%m%d_%H%M%S).log" -fi - 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 @@ -49,9 +38,6 @@ source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxV load_functions catch_errors -# Re-parse dev_mode in container context (flags exported from host) -parse_dev_mode - # ============================================================================== # SECTION 2: NETWORK & CONNECTIVITY # ============================================================================== @@ -122,23 +108,20 @@ network_check() { # 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 - ipv4_status="${GN}✔${CL} IPv4" else - ipv4_status="${RD}✖${CL} IPv4" + 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 - ipv6_status="${GN}✔${CL} IPv6" else - ipv6_status="${RD}✖${CL} IPv6" + msg_error "IPv6 Internet Not Connected" fi - # Show combined status - msg_ok "Internet: ${ipv4_status} ${ipv6_status}" - # 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 @@ -256,6 +239,10 @@ motd_ssh() { fi } +# ============================================================================== +# SECTION 5: CONTAINER CUSTOMIZATION +# ============================================================================== + # ------------------------------------------------------------------------------ # customize() # @@ -266,33 +253,21 @@ motd_ssh() { # - Sets proper permissions on SSH directories and key files # ------------------------------------------------------------------------------ customize() { - if [[ -z "${PASSWORD:-}" ]]; then - msg_info "Configuring autologin for root" - - # Enable root account (remove password lock) - passwd -d root >/dev/null 2>&1 || true - - # Create getty override for container-getty@1.service + 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 + [Service] + ExecStart= + ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM EOF - - # Reload and restart getty service - $STD systemctl daemon-reload - $STD systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') || true - - msg_ok "Autologin configured" + systemctl daemon-reload + systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') + msg_ok "Customized Container" fi - - msg_ok "Customized Container" - 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 From bea8814994b4de78e6d5cffd8bb92ab57da3f9d9 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:42:00 +0100 Subject: [PATCH 31/54] reverted --- misc/{build copy.func => build copy 2.func} | 482 +++++-------------- misc/build.func | 499 +++++++++++++++----- misc/install copy.func | 277 +++++++++++ misc/install.func | 57 ++- 4 files changed, 816 insertions(+), 499 deletions(-) rename misc/{build copy.func => build copy 2.func} (88%) create mode 100644 misc/install copy.func diff --git a/misc/build copy.func b/misc/build copy 2.func similarity index 88% rename from misc/build copy.func rename to misc/build copy 2.func index 568881307..b0b0404cd 100644 --- a/misc/build copy.func +++ b/misc/build copy 2.func @@ -47,17 +47,7 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log - CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" - - # Parse dev_mode early - parse_dev_mode - - # Setup persistent log directory if logs mode active - if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" - fi + CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"} # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then @@ -535,35 +525,6 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} - ENABLE_NESTING=${var_nesting:-"${1:-1}"} - ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} - ALLOW_MOUNT_FS=${var_mount_fs:-""} - ENABLE_MKNOD=${var_mknod:-"${1:-0}"} - PROTECT_CT=${var_protection:-"${1:-no}"} - CT_TIMEZONE=${var_timezone:-""} - - # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) - # This must happen here before any usage of these variables - case "${ENABLE_NESTING,,}" in - yes | true) ENABLE_NESTING="1" ;; - no | false) ENABLE_NESTING="0" ;; - esac - case "${ENABLE_KEYCTL,,}" in - yes | true) ENABLE_KEYCTL="1" ;; - no | false) ENABLE_KEYCTL="0" ;; - esac - case "${ENABLE_MKNOD,,}" in - yes | true) ENABLE_MKNOD="1" ;; - no | false) ENABLE_MKNOD="0" ;; - esac - case "${ENABLE_FUSE,,}" in - yes | true) ENABLE_FUSE="1" ;; - no | false) ENABLE_FUSE="0" ;; - esac - case "${PROTECT_CT,,}" in - yes | true) PROTECT_CT="1" ;; - no | false) PROTECT_CT="0" ;; - esac # 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 @@ -587,9 +548,9 @@ default_var_settings() { # Allowed var_* keys (alphabetically sorted) # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) local VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_keyctl - var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu - var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse + var_gateway var_hostname var_ipv6_method 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 ) @@ -667,14 +628,6 @@ var_ssh=no # Features/Tags/verbosity var_fuse=no var_tun=no - -# Advanced Settings (Proxmox-official features) -var_nesting=1 # Allow nesting (required for Docker/LXC in CT) -var_keyctl=0 # Allow keyctl() - needed for Docker (systemd-networkd workaround) -var_mknod=0 # Allow device node creation (requires kernel 5.3+, experimental) -var_mount_fs= # Allow specific filesystems: nfs,fuse,ext4,etc (leave empty for defaults) -var_protection=no # Prevent accidental deletion of container -var_timezone= # Container timezone (e.g. Europe/Berlin, leave empty for host timezone) var_tags=community-script var_verbose=no @@ -941,12 +894,6 @@ _build_current_app_vars_tmp() { _apt_cacher_ip="${APT_CACHER_IP:-}" _fuse="${ENABLE_FUSE:-no}" _tun="${ENABLE_TUN:-no}" - _nesting="${ENABLE_NESTING:-1}" - _keyctl="${ENABLE_KEYCTL:-0}" - _mknod="${ENABLE_MKNOD:-0}" - _mount_fs="${ALLOW_MOUNT_FS:-}" - _protect="${PROTECT_CT:-no}" - _timezone="${CT_TIMEZONE:-}" _tags="${TAGS:-}" _verbose="${VERBOSE:-no}" @@ -990,12 +937,6 @@ _build_current_app_vars_tmp() { [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")" [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")" - [ -n "$_nesting" ] && echo "var_nesting=$(_sanitize_value "$_nesting")" - [ -n "$_keyctl" ] && echo "var_keyctl=$(_sanitize_value "$_keyctl")" - [ -n "$_mknod" ] && echo "var_mknod=$(_sanitize_value "$_mknod")" - [ -n "$_mount_fs" ] && echo "var_mount_fs=$(_sanitize_value "$_mount_fs")" - [ -n "$_protect" ] && echo "var_protection=$(_sanitize_value "$_protect")" - [ -n "$_timezone" ] && echo "var_timezone=$(_sanitize_value "$_timezone")" [ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")" [ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")" @@ -1578,51 +1519,6 @@ advanced_settings() { configure_ssh_settings export SSH_KEYS_FILE echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" - - # Advanced Settings - Proxmox Features - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS" --yesno "Configure Advanced Proxmox Features?" 10 58); then - # keyctl: for Docker support - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable keyctl()" --yesno "Allow keyctl() system calls?\n\nNeeded for: Docker inside container, systemd-networkd\nDefault: No (not needed for most applications)" 10 58); then - ENABLE_KEYCTL="1" - else - ENABLE_KEYCTL="0" - fi - echo -e "${SEARCH}${BOLD}${DGN}Allow keyctl(): ${BGN}$ENABLE_KEYCTL${CL}" - - # mknod: device node creation - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable mknod()" --yesno "Allow device node creation?\n\nNeeded for: Complex device management (experimental, kernel 5.3+)\nDefault: No (rarely needed)" 10 58); then - ENABLE_MKNOD="1" - else - ENABLE_MKNOD="0" - fi - echo -e "${SEARCH}${BOLD}${DGN}Allow mknod(): ${BGN}$ENABLE_MKNOD${CL}" - - # mount: specific filesystems - if MOUNT_FS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allow specific filesystems (e.g., nfs,fuse,ext4)\nLeave blank for defaults" 8 58 "$ALLOW_MOUNT_FS" --title "Mount Filesystems" 3>&1 1>&2 2>&3); then - ALLOW_MOUNT_FS="$MOUNT_FS" - [ -z "$ALLOW_MOUNT_FS" ] && ALLOW_MOUNT_FS="(defaults)" - else - exit_script - fi - echo -e "${SEARCH}${BOLD}${DGN}Mount Filesystems: ${BGN}$ALLOW_MOUNT_FS${CL}" - - # Container protection - if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Protection Flag" --yesno "Prevent accidental deletion?\n\nIf enabled, container cannot be deleted or its disk modified\nDefault: No" 10 58); then - PROTECT_CT="yes" - else - PROTECT_CT="no" - fi - echo -e "${SEARCH}${BOLD}${DGN}Container Protection: ${BGN}$PROTECT_CT${CL}" - - # Container timezone - if CT_TIMEZONE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set container timezone (e.g., Europe/Berlin)\nLeave blank to use host timezone" 8 58 "$CT_TIMEZONE" --title "Container Timezone" 3>&1 1>&2 2>&3); then - [ -z "$CT_TIMEZONE" ] && CT_TIMEZONE="(host)" - else - exit_script - fi - echo -e "${SEARCH}${BOLD}${DGN}Container Timezone: ${BGN}$CT_TIMEZONE${CL}" - fi - 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 @@ -1795,16 +1691,7 @@ install_script() { fi NEXTID=$(pvesh get /cluster/nextid) - - # Get timezone using timedatectl (Debian 13+ compatible) - # Fallback to /etc/timezone for older systems - if command -v timedatectl >/dev/null 2>&1; then - timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC") - elif [ -f /etc/timezone ]; then - timezone=$(cat /etc/timezone) - else - timezone="UTC" - fi + timezone=$(cat /etc/timezone) # Show APP Header header_info @@ -1932,12 +1819,13 @@ settings_menu() { 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+=("3" "Edit App.vars for ${APP}") - settings_items+=("4" "Exit") + settings_items+=("4" "Edit App.vars for ${APP}") + settings_items+=("5" "Exit") else - settings_items+=("3" "Exit") + settings_items+=("4" "Exit") fi local choice @@ -2298,34 +2186,14 @@ build_container() { none) ;; esac - # Build FEATURES array with advanced settings - # Note: All feature flags are already normalized to 0/1 in default_settings() - # Proxmox requires each feature as a separate parameter, not comma-separated string - FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") - - # keyctl: needed for Docker inside containers (systemd-networkd workaround) - # Typically needed for unprivileged containers with Docker - if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES_ARRAY+=("keyctl=1") + if [ "$CT_TYPE" == "1" ]; then + FEATURES="keyctl=1,nesting=1" + else + FEATURES="nesting=1" fi - # mknod: allow device node creation (requires kernel 5.3+, experimental) - if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES_ARRAY+=("mknod=1") - fi - - # FUSE: required for rclone, mergerfs, AppImage, etc. - if [ "$ENABLE_FUSE" == "1" ]; then - FEATURES_ARRAY+=("fuse=1") - fi - - # mount: allow specific filesystems (e.g., nfs, ext4, etc.) - # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) - if [ -n "$ALLOW_MOUNT_FS" ]; then - # Replace commas with semicolons for proper pct syntax - ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES="$FEATURES,fuse=1" fi TEMP_DIR=$(mktemp -d) @@ -2338,16 +2206,6 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" - export BUILD_LOG="$BUILD_LOG" - export INSTALL_LOG="/root/.install-${SESSION_ID}.log" - export dev_mode="${dev_mode:-}" - export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" - export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" - export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" - export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" - export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" - export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" - export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" @@ -2361,59 +2219,22 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" - export ENABLE_NESTING="$ENABLE_NESTING" - export ENABLE_KEYCTL="$ENABLE_KEYCTL" - export ENABLE_MKNOD="$ENABLE_MKNOD" - export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" - export PROTECT_CT="$PROTECT_CT" - export CT_TIMEZONE="$CT_TIMEZONE" export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - - # Build PCT_OPTIONS array (not string) for proper parameter handling - PCT_OPTIONS=() - - # Add features - each as separate -features parameter - for feature in "${FEATURES_ARRAY[@]}"; do - PCT_OPTIONS+=("-features" "$feature") - done - - PCT_OPTIONS+=("-hostname" "$HN") - PCT_OPTIONS+=("-tags" "$TAGS") - - if [ -n "$SD" ]; then - PCT_OPTIONS+=($SD) # Storage device flags (already formatted) - fi - - if [ -n "$NS" ]; then - PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - fi - - # Network configuration (single string with all network parameters) - PCT_OPTIONS+=($NET_STRING) - - PCT_OPTIONS+=("-onboot" "1") - PCT_OPTIONS+=("-cores" "$CORE_COUNT") - PCT_OPTIONS+=("-memory" "$RAM_SIZE") - PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - - # Protection flag - if [ "$PROTECT_CT" == "1" ]; then - PCT_OPTIONS+=("-protection" "1") - fi - - # Timezone flag - if [ -n "$CT_TIMEZONE" ]; then - PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") - fi - - # Password flag (already formatted as "-password xxx") - if [ -n "$PW" ]; then - PCT_OPTIONS+=($PW) - fi - - export PCT_OPTIONS + 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 $? @@ -2588,13 +2409,20 @@ EOF [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}") [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}") - # Use pct set to add devices with proper dev0/dev1 format - # GIDs will be detected and set after container starts - local dev_index=0 + # Add lxc.mount.entry for each device for dev in "${devices[@]}"; do - # Add to config using pct set (will be visible in GUI) - echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" - dev_index=$((dev_index + 1)) + echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" + + if [[ "$CT_TYPE" == "0" ]]; then + # Privileged container - also add cgroup allows + local major minor + major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") + minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") + + if [[ "$major" != "0" && "$minor" != "0" ]]; then + echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" + fi + fi done export GPU_TYPE="$selected_gpu" @@ -2607,11 +2435,20 @@ EOF return 0 fi - # Use pct set for NVIDIA devices - local dev_index=0 + # Add lxc.mount.entry for each NVIDIA device for dev in "${NVIDIA_DEVICES[@]}"; do - echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" - dev_index=$((dev_index + 1)) + echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" + + if [[ "$CT_TYPE" == "0" ]]; then + # Privileged container - also add cgroup allows + local major minor + major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") + minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") + + if [[ "$major" != "0" && "$minor" != "0" ]]; then + echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" + fi + fi done export GPU_TYPE="NVIDIA" @@ -2723,9 +2560,7 @@ EOF' fi if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then - # Set timezone using symlink (Debian 13+ compatible) - # Create /etc/timezone for backwards compatibility with older scripts - pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true" + 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 @@ -2742,105 +2577,13 @@ EOF' install_ssh_keys_into_ct # Run application installer - # NOTE: We disable error handling here because: - # 1. Container errors are caught by error_handler INSIDE container - # 2. Container creates flag file with exit code - # 3. We read flag file and handle cleanup manually below - # 4. We DON'T want host error_handler to fire for lxc-attach command itself - - set +Eeuo pipefail # Disable ALL error handling temporarily - trap - ERR # Remove ERR trap completely - - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" - local lxc_exit=$? - - set -Eeuo pipefail # Re-enable error handling - trap 'error_handler' ERR # Restore ERR trap - - # Check for error flag file in container (more reliable than lxc-attach exit code) - local install_exit_code=0 - if [[ -n "${SESSION_ID:-}" ]]; then - local error_flag="/root/.install-${SESSION_ID}.failed" - if pct exec "$CTID" -- test -f "$error_flag" 2>/dev/null; then - install_exit_code=$(pct exec "$CTID" -- cat "$error_flag" 2>/dev/null || echo "1") - pct exec "$CTID" -- rm -f "$error_flag" 2>/dev/null || true - fi - fi - - # Fallback to lxc-attach exit code if no flag file - if [[ $install_exit_code -eq 0 && $lxc_exit -ne 0 ]]; then - install_exit_code=$lxc_exit - fi - - # Installation failed? - if [[ $install_exit_code -ne 0 ]]; then - msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})" - - # Copy both logs from container before potential deletion - local build_log_copied=false - local install_log_copied=false - + if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then + local exit_code=$? + # Try to copy installation log from container before exiting if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then - # Copy BUILD_LOG (creation log) if it exists - if [[ -f "${BUILD_LOG}" ]]; then - cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true - fi - - # Copy INSTALL_LOG from container - if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then - install_log_copied=true - fi - - # Show available logs - echo "" - [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" - [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-${SESSION_ID}.log" 2>/dev/null || true fi - - # Dev mode: Keep container or open breakpoint shell - if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then - msg_dev "Keep mode active - container ${CTID} preserved" - return 0 - elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then - msg_dev "Breakpoint mode - opening shell in container ${CTID}" - echo -e "${YW}Type 'exit' to return to host${CL}" - pct enter "$CTID" - echo "" - echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" - if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - msg_ok "Container ${CTID} removed" - else - msg_dev "Container ${CTID} kept for debugging" - fi - exit $install_exit_code - fi - - # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner) - echo "" - echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" - - if read -t 60 -r response; then - if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then - # Remove container - echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" - elif [[ "$response" =~ ^[Nn]$ ]]; then - echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" - fi - else - # Timeout - auto-remove - echo -e "\n${YW}No response - auto-removing container${CL}" - echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" - fi - - exit $install_exit_code + exit $exit_code fi } @@ -2922,40 +2665,79 @@ fix_gpu_gids() { return 0 fi + # Silent operation to avoid spinner conflicts msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" - # Get actual GIDs from container + # Ermittle die tatsächlichen GIDs aus dem Container local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - # Create groups if they don't exist + # Fallbacks wenn Gruppen nicht existieren if [[ -z "$video_gid" ]]; then - pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1 + # Versuche die video Gruppe zu erstellen + pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") - [[ -z "$video_gid" ]] && video_gid="44" + [[ -z "$video_gid" ]] && video_gid="44" # Ultimate fallback fi if [[ -z "$render_gid" ]]; then - pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1 + # Versuche die render Gruppe zu erstellen + pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - [[ -z "$render_gid" ]] && render_gid="104" + [[ -z "$render_gid" ]] && render_gid="104" # Ultimate fallback fi - # Stop container to update config - pct stop "$CTID" >/dev/null 2>&1 - sleep 1 + msg_custom "ℹ️" "${DGN}" "Container GIDs detected - video:${video_gid}, render:${render_gid}" - # Update dev entries with correct GIDs - sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG" - sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG" + # Prüfe ob die GIDs von den Defaults abweichen + local need_update=0 + if [[ "$video_gid" != "44" ]] || [[ "$render_gid" != "104" ]]; then + need_update=1 + fi - # Restart container - pct start "$CTID" >/dev/null 2>&1 - sleep 2 + if [[ $need_update -eq 1 ]]; then + msg_custom "🔄" "${YW}" "Updating device GIDs in container config" - msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})" + # Stoppe Container für Config-Update + pct stop "$CTID" >/dev/null 2>&1 - # For privileged containers: also fix permissions inside container + # Update die dev Einträge mit korrekten GIDs + # Backup der Config + cp "$LXC_CONFIG" "${LXC_CONFIG}.bak" + + # Parse und update jeden dev Eintrag + while IFS= read -r line; do + if [[ "$line" =~ ^dev[0-9]+: ]]; then + # Extract device path + local device_path=$(echo "$line" | sed -E 's/^dev[0-9]+: ([^,]+).*/\1/') + local dev_num=$(echo "$line" | sed -E 's/^(dev[0-9]+):.*/\1/') + + if [[ "$device_path" =~ renderD ]]; then + # RenderD device - use render GID + echo "${dev_num}: ${device_path},gid=${render_gid}" + elif [[ "$device_path" =~ card ]]; then + # Card device - use video GID + echo "${dev_num}: ${device_path},gid=${video_gid}" + else + # Keep original line + echo "$line" + fi + else + # Keep non-dev lines + echo "$line" + fi + done <"$LXC_CONFIG" >"${LXC_CONFIG}.new" + + mv "${LXC_CONFIG}.new" "$LXC_CONFIG" + + # Starte Container wieder + pct start "$CTID" >/dev/null 2>&1 + sleep 3 + + msg_ok "Device GIDs updated successfully" + else + msg_ok "Device GIDs are already correct" + fi if [[ "$CT_TYPE" == "0" ]]; then pct exec "$CTID" -- bash -c " if [ -d /dev/dri ]; then @@ -3121,7 +2903,7 @@ create_lxc_container() { case "${_ans,,}" in y | yes) msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)" - if $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; then + 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" @@ -3566,13 +3348,13 @@ create_lxc_container() { exit 211 } - LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" + 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_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating template..." + 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 @@ -3591,16 +3373,18 @@ create_lxc_container() { # Retry after repair if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then - # Fallback to local storage if not already on local + # Fallback to local storage if [[ "$TEMPLATE_STORAGE" != "local" ]]; then - msg_info "Retrying container creation with fallback to local storage..." + 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 - # Local fallback also failed - check for LXC stack version issue + 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." @@ -3620,19 +3404,18 @@ create_lxc_container() { ;; esac else - msg_error "Container creation failed. See $LOGFILE" + 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 fi - else - msg_ok "Container successfully created using local fallback." fi else - # Already on local storage and still failed - check LXC stack version + 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." @@ -3655,14 +3438,12 @@ create_lxc_container() { msg_error "Container creation failed. 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + 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_ok "Container successfully created after template repair." fi fi @@ -3679,9 +3460,6 @@ create_lxc_container() { } msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." - - # Report container creation to API - post_to_api } # ============================================================================== diff --git a/misc/build.func b/misc/build.func index b0b0404cd..733a4215f 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,7 +47,17 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"} + BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log + CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" + + # Parse dev_mode early + parse_dev_mode + + # Setup persistent log directory if logs mode active + if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then + mkdir -p /var/log/community-scripts + BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" + fi # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then @@ -525,6 +535,35 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} + ENABLE_NESTING=${var_nesting:-"${1:-1}"} + ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} + ALLOW_MOUNT_FS=${var_mount_fs:-""} + ENABLE_MKNOD=${var_mknod:-"${1:-0}"} + PROTECT_CT=${var_protection:-"${1:-no}"} + CT_TIMEZONE=${var_timezone:-""} + + # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) + # This must happen here before any usage of these variables + case "${ENABLE_NESTING,,}" in + yes | true) ENABLE_NESTING="1" ;; + no | false) ENABLE_NESTING="0" ;; + esac + case "${ENABLE_KEYCTL,,}" in + yes | true) ENABLE_KEYCTL="1" ;; + no | false) ENABLE_KEYCTL="0" ;; + esac + case "${ENABLE_MKNOD,,}" in + yes | true) ENABLE_MKNOD="1" ;; + no | false) ENABLE_MKNOD="0" ;; + esac + case "${ENABLE_FUSE,,}" in + yes | true) ENABLE_FUSE="1" ;; + no | false) ENABLE_FUSE="0" ;; + esac + case "${PROTECT_CT,,}" in + yes | true) PROTECT_CT="1" ;; + no | false) PROTECT_CT="0" ;; + esac # 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 @@ -548,9 +587,9 @@ default_var_settings() { # Allowed var_* keys (alphabetically sorted) # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) local VAR_WHITELIST=( - var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse - var_gateway var_hostname var_ipv6_method var_mac var_mtu - var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged + var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_keyctl + var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu + var_net var_nesting var_ns var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage ) @@ -628,6 +667,14 @@ var_ssh=no # Features/Tags/verbosity var_fuse=no var_tun=no + +# Advanced Settings (Proxmox-official features) +var_nesting=1 # Allow nesting (required for Docker/LXC in CT) +var_keyctl=0 # Allow keyctl() - needed for Docker (systemd-networkd workaround) +var_mknod=0 # Allow device node creation (requires kernel 5.3+, experimental) +var_mount_fs= # Allow specific filesystems: nfs,fuse,ext4,etc (leave empty for defaults) +var_protection=no # Prevent accidental deletion of container +var_timezone= # Container timezone (e.g. Europe/Berlin, leave empty for host timezone) var_tags=community-script var_verbose=no @@ -894,6 +941,12 @@ _build_current_app_vars_tmp() { _apt_cacher_ip="${APT_CACHER_IP:-}" _fuse="${ENABLE_FUSE:-no}" _tun="${ENABLE_TUN:-no}" + _nesting="${ENABLE_NESTING:-1}" + _keyctl="${ENABLE_KEYCTL:-0}" + _mknod="${ENABLE_MKNOD:-0}" + _mount_fs="${ALLOW_MOUNT_FS:-}" + _protect="${PROTECT_CT:-no}" + _timezone="${CT_TIMEZONE:-}" _tags="${TAGS:-}" _verbose="${VERBOSE:-no}" @@ -937,6 +990,12 @@ _build_current_app_vars_tmp() { [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")" [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")" + [ -n "$_nesting" ] && echo "var_nesting=$(_sanitize_value "$_nesting")" + [ -n "$_keyctl" ] && echo "var_keyctl=$(_sanitize_value "$_keyctl")" + [ -n "$_mknod" ] && echo "var_mknod=$(_sanitize_value "$_mknod")" + [ -n "$_mount_fs" ] && echo "var_mount_fs=$(_sanitize_value "$_mount_fs")" + [ -n "$_protect" ] && echo "var_protection=$(_sanitize_value "$_protect")" + [ -n "$_timezone" ] && echo "var_timezone=$(_sanitize_value "$_timezone")" [ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")" [ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")" @@ -1519,6 +1578,51 @@ advanced_settings() { configure_ssh_settings export SSH_KEYS_FILE echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" + + # Advanced Settings - Proxmox Features + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS" --yesno "Configure Advanced Proxmox Features?" 10 58); then + # keyctl: for Docker support + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable keyctl()" --yesno "Allow keyctl() system calls?\n\nNeeded for: Docker inside container, systemd-networkd\nDefault: No (not needed for most applications)" 10 58); then + ENABLE_KEYCTL="1" + else + ENABLE_KEYCTL="0" + fi + echo -e "${SEARCH}${BOLD}${DGN}Allow keyctl(): ${BGN}$ENABLE_KEYCTL${CL}" + + # mknod: device node creation + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Enable mknod()" --yesno "Allow device node creation?\n\nNeeded for: Complex device management (experimental, kernel 5.3+)\nDefault: No (rarely needed)" 10 58); then + ENABLE_MKNOD="1" + else + ENABLE_MKNOD="0" + fi + echo -e "${SEARCH}${BOLD}${DGN}Allow mknod(): ${BGN}$ENABLE_MKNOD${CL}" + + # mount: specific filesystems + if MOUNT_FS=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allow specific filesystems (e.g., nfs,fuse,ext4)\nLeave blank for defaults" 8 58 "$ALLOW_MOUNT_FS" --title "Mount Filesystems" 3>&1 1>&2 2>&3); then + ALLOW_MOUNT_FS="$MOUNT_FS" + [ -z "$ALLOW_MOUNT_FS" ] && ALLOW_MOUNT_FS="(defaults)" + else + exit_script + fi + echo -e "${SEARCH}${BOLD}${DGN}Mount Filesystems: ${BGN}$ALLOW_MOUNT_FS${CL}" + + # Container protection + if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "Protection Flag" --yesno "Prevent accidental deletion?\n\nIf enabled, container cannot be deleted or its disk modified\nDefault: No" 10 58); then + PROTECT_CT="yes" + else + PROTECT_CT="no" + fi + echo -e "${SEARCH}${BOLD}${DGN}Container Protection: ${BGN}$PROTECT_CT${CL}" + + # Container timezone + if CT_TIMEZONE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set container timezone (e.g., Europe/Berlin)\nLeave blank to use host timezone" 8 58 "$CT_TIMEZONE" --title "Container Timezone" 3>&1 1>&2 2>&3); then + [ -z "$CT_TIMEZONE" ] && CT_TIMEZONE="(host)" + else + exit_script + fi + echo -e "${SEARCH}${BOLD}${DGN}Container Timezone: ${BGN}$CT_TIMEZONE${CL}" + fi + 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 @@ -1691,7 +1795,16 @@ install_script() { fi NEXTID=$(pvesh get /cluster/nextid) - timezone=$(cat /etc/timezone) + + # Get timezone using timedatectl (Debian 13+ compatible) + # Fallback to /etc/timezone for older systems + if command -v timedatectl >/dev/null 2>&1; then + timezone=$(timedatectl show --value --property=Timezone 2>/dev/null || echo "UTC") + elif [ -f /etc/timezone ]; then + timezone=$(cat /etc/timezone) + else + timezone="UTC" + fi # Show APP Header header_info @@ -1819,13 +1932,12 @@ settings_menu() { 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+=("3" "Edit App.vars for ${APP}") settings_items+=("4" "Exit") + else + settings_items+=("3" "Exit") fi local choice @@ -2186,14 +2298,34 @@ build_container() { none) ;; esac - if [ "$CT_TYPE" == "1" ]; then - FEATURES="keyctl=1,nesting=1" - else - FEATURES="nesting=1" + # Build FEATURES array with advanced settings + # Note: All feature flags are already normalized to 0/1 in default_settings() + # Proxmox requires each feature as a separate parameter, not comma-separated string + FEATURES_ARRAY=() + FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + + # keyctl: needed for Docker inside containers (systemd-networkd workaround) + # Typically needed for unprivileged containers with Docker + if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then + FEATURES_ARRAY+=("keyctl=1") fi - if [ "$ENABLE_FUSE" == "yes" ]; then - FEATURES="$FEATURES,fuse=1" + # mknod: allow device node creation (requires kernel 5.3+, experimental) + if [ "$ENABLE_MKNOD" == "1" ]; then + FEATURES_ARRAY+=("mknod=1") + fi + + # FUSE: required for rclone, mergerfs, AppImage, etc. + if [ "$ENABLE_FUSE" == "1" ]; then + FEATURES_ARRAY+=("fuse=1") + fi + + # mount: allow specific filesystems (e.g., nfs, ext4, etc.) + # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) + if [ -n "$ALLOW_MOUNT_FS" ]; then + # Replace commas with semicolons for proper pct syntax + ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" + FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") fi TEMP_DIR=$(mktemp -d) @@ -2206,6 +2338,16 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" + export BUILD_LOG="$BUILD_LOG" + export INSTALL_LOG="/root/.install-${SESSION_ID}.log" + export dev_mode="${dev_mode:-}" + export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" + export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" + export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" + export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" + export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" + export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" + export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" @@ -2219,22 +2361,59 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" + export ENABLE_NESTING="$ENABLE_NESTING" + export ENABLE_KEYCTL="$ENABLE_KEYCTL" + export ENABLE_MKNOD="$ENABLE_MKNOD" + export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" + export PROTECT_CT="$PROTECT_CT" + export CT_TIMEZONE="$CT_TIMEZONE" 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 -" + + # Build PCT_OPTIONS array (not string) for proper parameter handling + PCT_OPTIONS=() + + # Add features - each as separate -features parameter + for feature in "${FEATURES_ARRAY[@]}"; do + PCT_OPTIONS+=("-features" "$feature") + done + + PCT_OPTIONS+=("-hostname" "$HN") + PCT_OPTIONS+=("-tags" "$TAGS") + + if [ -n "$SD" ]; then + PCT_OPTIONS+=($SD) # Storage device flags (already formatted) + fi + + if [ -n "$NS" ]; then + PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) + fi + + # Network configuration (single string with all network parameters) + PCT_OPTIONS+=($NET_STRING) + + PCT_OPTIONS+=("-onboot" "1") + PCT_OPTIONS+=("-cores" "$CORE_COUNT") + PCT_OPTIONS+=("-memory" "$RAM_SIZE") + PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") + + # Protection flag + if [ "$PROTECT_CT" == "1" ]; then + PCT_OPTIONS+=("-protection" "1") + fi + + # Timezone flag + if [ -n "$CT_TIMEZONE" ]; then + PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + fi + + # Password flag (already formatted as "-password xxx") + if [ -n "$PW" ]; then + PCT_OPTIONS+=($PW) + fi + + export PCT_OPTIONS export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" create_lxc_container || exit $? @@ -2409,20 +2588,13 @@ EOF [[ "$selected_gpu" == "INTEL" ]] && devices=("${INTEL_DEVICES[@]}") [[ "$selected_gpu" == "AMD" ]] && devices=("${AMD_DEVICES[@]}") - # Add lxc.mount.entry for each device + # Use pct set to add devices with proper dev0/dev1 format + # GIDs will be detected and set after container starts + local dev_index=0 for dev in "${devices[@]}"; do - echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" - - if [[ "$CT_TYPE" == "0" ]]; then - # Privileged container - also add cgroup allows - local major minor - major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") - minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") - - if [[ "$major" != "0" && "$minor" != "0" ]]; then - echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" - fi - fi + # Add to config using pct set (will be visible in GUI) + echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" + dev_index=$((dev_index + 1)) done export GPU_TYPE="$selected_gpu" @@ -2435,20 +2607,11 @@ EOF return 0 fi - # Add lxc.mount.entry for each NVIDIA device + # Use pct set for NVIDIA devices + local dev_index=0 for dev in "${NVIDIA_DEVICES[@]}"; do - echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" - - if [[ "$CT_TYPE" == "0" ]]; then - # Privileged container - also add cgroup allows - local major minor - major=$(stat -c '%t' "$dev" 2>/dev/null || echo "0") - minor=$(stat -c '%T' "$dev" 2>/dev/null || echo "0") - - if [[ "$major" != "0" && "$minor" != "0" ]]; then - echo "lxc.cgroup2.devices.allow: c $((0x$major)):$((0x$minor)) rwm" >>"$LXC_CONFIG" - fi - fi + echo "dev${dev_index}: ${dev},gid=44" >>"$LXC_CONFIG" + dev_index=$((dev_index + 1)) done export GPU_TYPE="NVIDIA" @@ -2560,7 +2723,9 @@ EOF' 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" + # Set timezone using symlink (Debian 13+ compatible) + # Create /etc/timezone for backwards compatibility with older scripts + pct exec "$CTID" -- bash -c "tz='$tz'; ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime && echo \"\$tz\" >/etc/timezone || true" else msg_warn "Skipping timezone setup – zone '$tz' not found in container" fi @@ -2576,14 +2741,121 @@ EOF' # Install SSH keys install_ssh_keys_into_ct + # Dev mode: Setup MOTD/SSH AFTER network is ready and before installation + # This ensures the container is fully booted and accessible via SSH + if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then + msg_dev "Setting up MOTD and SSH for debugging access" + pct exec "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" <<'MOTD_SETUP' + # Only run motd_ssh function if it exists + if declare -f motd_ssh >/dev/null 2>&1; then + motd_ssh + else + msg_warn "motd_ssh function not found in ${var_install}.sh" + fi +MOTD_SETUP + msg_dev "MOTD/SSH ready - container accessible via SSH (IP: $ip_in_lxc)" + fi + # Run application installer - if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then - local exit_code=$? - # Try to copy installation log from container before exiting - if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then - pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-${SESSION_ID}.log" 2>/dev/null || true + # NOTE: We disable error handling here because: + # 1. Container errors are caught by error_handler INSIDE container + # 2. Container creates flag file with exit code + # 3. We read flag file and handle cleanup manually below + # 4. We DON'T want host error_handler to fire for lxc-attach command itself + + set +Eeuo pipefail # Disable ALL error handling temporarily + trap - ERR # Remove ERR trap completely + + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" + local lxc_exit=$? + + set -Eeuo pipefail # Re-enable error handling + trap 'error_handler' ERR # Restore ERR trap + + # Check for error flag file in container (more reliable than lxc-attach exit code) + local install_exit_code=0 + if [[ -n "${SESSION_ID:-}" ]]; then + local error_flag="/root/.install-${SESSION_ID}.failed" + if pct exec "$CTID" -- test -f "$error_flag" 2>/dev/null; then + install_exit_code=$(pct exec "$CTID" -- cat "$error_flag" 2>/dev/null || echo "1") + pct exec "$CTID" -- rm -f "$error_flag" 2>/dev/null || true fi - exit $exit_code + fi + + # Fallback to lxc-attach exit code if no flag file + if [[ $install_exit_code -eq 0 && $lxc_exit -ne 0 ]]; then + install_exit_code=$lxc_exit + fi + + # Installation failed? + if [[ $install_exit_code -ne 0 ]]; then + msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})" + + # Copy both logs from container before potential deletion + local build_log_copied=false + local install_log_copied=false + + if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then + # Copy BUILD_LOG (creation log) if it exists + if [[ -f "${BUILD_LOG}" ]]; then + cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true + fi + + # Copy INSTALL_LOG from container + if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then + install_log_copied=true + fi + + # Show available logs + echo "" + [[ $build_log_copied == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ $install_log_copied == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + fi + + # Dev mode: Keep container or open breakpoint shell + if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then + msg_dev "Keep mode active - container ${CTID} preserved" + return 0 + elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then + msg_dev "Breakpoint mode - opening shell in container ${CTID}" + echo -e "${YW}Type 'exit' to return to host${CL}" + pct enter "$CTID" + echo "" + echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" + if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + msg_ok "Container ${CTID} removed" + else + msg_dev "Container ${CTID} kept for debugging" + fi + exit $install_exit_code + fi + + # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner) + echo "" + echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}" + + if read -t 60 -r response; then + if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then + # Remove container + echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + elif [[ "$response" =~ ^[Nn]$ ]]; then + echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" + fi + else + # Timeout - auto-remove + echo -e "\n${YW}No response - auto-removing container${CL}" + echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}" + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" + fi + + exit $install_exit_code fi } @@ -2665,79 +2937,40 @@ fix_gpu_gids() { return 0 fi - # Silent operation to avoid spinner conflicts msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" - # Ermittle die tatsächlichen GIDs aus dem Container + # Get actual GIDs from container local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") local render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - # Fallbacks wenn Gruppen nicht existieren + # Create groups if they don't exist if [[ -z "$video_gid" ]]; then - # Versuche die video Gruppe zu erstellen - pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" + pct exec "$CTID" -- sh -c "groupadd -r video 2>/dev/null || true" >/dev/null 2>&1 video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") - [[ -z "$video_gid" ]] && video_gid="44" # Ultimate fallback + [[ -z "$video_gid" ]] && video_gid="44" fi if [[ -z "$render_gid" ]]; then - # Versuche die render Gruppe zu erstellen - pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" + pct exec "$CTID" -- sh -c "groupadd -r render 2>/dev/null || true" >/dev/null 2>&1 render_gid=$(pct exec "$CTID" -- sh -c "getent group render 2>/dev/null | cut -d: -f3") - [[ -z "$render_gid" ]] && render_gid="104" # Ultimate fallback + [[ -z "$render_gid" ]] && render_gid="104" fi - msg_custom "ℹ️" "${DGN}" "Container GIDs detected - video:${video_gid}, render:${render_gid}" + # Stop container to update config + pct stop "$CTID" >/dev/null 2>&1 + sleep 1 - # Prüfe ob die GIDs von den Defaults abweichen - local need_update=0 - if [[ "$video_gid" != "44" ]] || [[ "$render_gid" != "104" ]]; then - need_update=1 - fi + # Update dev entries with correct GIDs + sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG" + sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG" - if [[ $need_update -eq 1 ]]; then - msg_custom "🔄" "${YW}" "Updating device GIDs in container config" + # Restart container + pct start "$CTID" >/dev/null 2>&1 + sleep 2 - # Stoppe Container für Config-Update - pct stop "$CTID" >/dev/null 2>&1 + msg_ok "GPU passthrough configured (video:${video_gid}, render:${render_gid})" - # Update die dev Einträge mit korrekten GIDs - # Backup der Config - cp "$LXC_CONFIG" "${LXC_CONFIG}.bak" - - # Parse und update jeden dev Eintrag - while IFS= read -r line; do - if [[ "$line" =~ ^dev[0-9]+: ]]; then - # Extract device path - local device_path=$(echo "$line" | sed -E 's/^dev[0-9]+: ([^,]+).*/\1/') - local dev_num=$(echo "$line" | sed -E 's/^(dev[0-9]+):.*/\1/') - - if [[ "$device_path" =~ renderD ]]; then - # RenderD device - use render GID - echo "${dev_num}: ${device_path},gid=${render_gid}" - elif [[ "$device_path" =~ card ]]; then - # Card device - use video GID - echo "${dev_num}: ${device_path},gid=${video_gid}" - else - # Keep original line - echo "$line" - fi - else - # Keep non-dev lines - echo "$line" - fi - done <"$LXC_CONFIG" >"${LXC_CONFIG}.new" - - mv "${LXC_CONFIG}.new" "$LXC_CONFIG" - - # Starte Container wieder - pct start "$CTID" >/dev/null 2>&1 - sleep 3 - - msg_ok "Device GIDs updated successfully" - else - msg_ok "Device GIDs are already correct" - fi + # For privileged containers: also fix permissions inside container if [[ "$CT_TYPE" == "0" ]]; then pct exec "$CTID" -- bash -c " if [ -d /dev/dri ]; then @@ -2903,7 +3136,7 @@ create_lxc_container() { 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 + if $STD apt-get update && $STD apt-get install -y --only-upgrade pve-container lxc-pve; then msg_ok "LXC stack upgraded." if [[ "$do_retry" == "yes" ]]; then msg_info "Retrying container creation after upgrade" @@ -3348,13 +3581,13 @@ create_lxc_container() { exit 211 } - LOGFILE="/tmp/pct_create_${CTID}.log" + LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.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..." + msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating template..." # Validate template file if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then @@ -3373,18 +3606,16 @@ create_lxc_container() { # Retry after repair if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then - # Fallback to local storage + # Fallback to local storage if not already on local if [[ "$TEMPLATE_STORAGE" != "local" ]]; then - msg_warn "Retrying container creation with fallback to local storage..." + msg_info "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 ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then + # Local fallback also failed - check for LXC stack version issue if grep -qiE 'unsupported .* version' "$LOGFILE"; then echo echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template." @@ -3404,18 +3635,19 @@ create_lxc_container() { ;; esac else - msg_error "Container creation failed even with local fallback. See $LOGFILE" + msg_error "Container creation failed. 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" + pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 fi + else + msg_ok "Container successfully created using local fallback." fi else - msg_error "Container creation failed on local storage. See $LOGFILE" - # --- Dynamic stack upgrade + auto-retry on the well-known error pattern --- + # Already on local storage and still failed - check LXC stack version if grep -qiE 'unsupported .* version' "$LOGFILE"; then echo echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template." @@ -3438,12 +3670,14 @@ create_lxc_container() { msg_error "Container creation failed. 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" + pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 fi fi + else + msg_ok "Container successfully created after template repair." fi fi @@ -3460,6 +3694,9 @@ create_lxc_container() { } msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created." + + # Report container creation to API + post_to_api } # ============================================================================== diff --git a/misc/install copy.func b/misc/install copy.func new file mode 100644 index 000000000..e9e319576 --- /dev/null +++ b/misc/install copy.func @@ -0,0 +1,277 @@ +# 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 + +# ============================================================================== +# INSTALL.FUNC - CONTAINER INSTALLATION & SETUP +# ============================================================================== +# +# This file provides installation functions executed inside LXC containers +# after creation. Handles: +# +# - Network connectivity verification (IPv4/IPv6) +# - OS updates and package installation +# - DNS resolution checks +# - MOTD and SSH configuration +# - Container customization and auto-login +# +# Usage: +# - Sourced by -install.sh scripts +# - Executes via pct exec inside container +# - Requires internet connectivity +# +# ============================================================================== + +# ============================================================================== +# SECTION 1: INITIALIZATION +# ============================================================================== + +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 + +# ============================================================================== +# SECTION 2: NETWORK & CONNECTIVITY +# ============================================================================== + +# ------------------------------------------------------------------------------ +# verb_ip6() +# +# - Configures IPv6 based on DISABLEIPV6 variable +# - If DISABLEIPV6=yes: disables IPv6 via sysctl +# - Sets verbose mode via set_std_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 +} + +# ------------------------------------------------------------------------------ +# setting_up_container() +# +# - Verifies network connectivity via hostname -I +# - Retries up to RETRY_NUM times with RETRY_EVERY seconds delay +# - Removes Python EXTERNALLY-MANAGED restrictions +# - Disables systemd-networkd-wait-online.service for faster boot +# - Exits with error if network unavailable after retries +# ------------------------------------------------------------------------------ +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)" +} + +# ------------------------------------------------------------------------------ +# network_check() +# +# - Comprehensive network connectivity check for IPv4 and IPv6 +# - Tests connectivity to multiple DNS servers: +# * IPv4: 1.1.1.1 (Cloudflare), 8.8.8.8 (Google), 9.9.9.9 (Quad9) +# * IPv6: 2606:4700:4700::1111, 2001:4860:4860::8888, 2620:fe::fe +# - Verifies DNS resolution for GitHub and Community-Scripts domains +# - Prompts user to continue if no internet detected +# - Uses fatal() on DNS resolution failure for critical hosts +# ------------------------------------------------------------------------------ +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 +} + +# ============================================================================== +# SECTION 3: OS UPDATE & PACKAGE MANAGEMENT +# ============================================================================== + +# ------------------------------------------------------------------------------ +# update_os() +# +# - Updates container OS via apt-get update and dist-upgrade +# - Configures APT cacher proxy if CACHER=yes (accelerates package downloads) +# - Removes Python EXTERNALLY-MANAGED restrictions for pip +# - Sources tools.func for additional setup functions after update +# - Uses $STD wrapper to suppress output unless VERBOSE=yes +# ------------------------------------------------------------------------------ +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) +} + +# ============================================================================== +# SECTION 4: MOTD & SSH CONFIGURATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# motd_ssh() +# +# - Configures Message of the Day (MOTD) with container information +# - Creates /etc/profile.d/00_lxc-details.sh with: +# * Application name +# * Warning banner (DEV repository) +# * OS name and version +# * Hostname and IP address +# * GitHub repository link +# - Disables executable flag on /etc/update-motd.d/* scripts +# - Enables root SSH access if SSH_ROOT=yes +# - Configures TERM environment variable for better terminal support +# ------------------------------------------------------------------------------ +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 +} + +# ============================================================================== +# SECTION 5: CONTAINER CUSTOMIZATION +# ============================================================================== + +# ------------------------------------------------------------------------------ +# customize() +# +# - Customizes container for passwordless root login if PASSWORD is empty +# - Configures getty for auto-login via /etc/systemd/system/container-getty@1.service.d/override.conf +# - Creates /usr/bin/update script for easy application updates +# - Injects SSH authorized keys if SSH_AUTHORIZED_KEY variable is set +# - Sets proper permissions on SSH directories and key files +# ------------------------------------------------------------------------------ +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 +} diff --git a/misc/install.func b/misc/install.func index e9e319576..9ea4a0fab 100644 --- a/misc/install.func +++ b/misc/install.func @@ -28,6 +28,17 @@ # SECTION 1: INITIALIZATION # ============================================================================== +# Ensure INSTALL_LOG is set (exported from build.func, but fallback if missing) +if [[ -z "${INSTALL_LOG:-}" ]]; then + INSTALL_LOG="/root/.install-${SESSION_ID:-unknown}.log" +fi + +# Dev mode: Persistent logs directory +if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then + mkdir -p /var/log/community-scripts + INSTALL_LOG="/var/log/community-scripts/install-${SESSION_ID:-unknown}-$(date +%Y%m%d_%H%M%S).log" +fi + 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 @@ -38,6 +49,9 @@ source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxV load_functions catch_errors +# Re-parse dev_mode in container context (flags exported from host) +parse_dev_mode + # ============================================================================== # SECTION 2: NETWORK & CONNECTIVITY # ============================================================================== @@ -108,20 +122,23 @@ network_check() { # 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 + ipv4_status="${GN}✔${CL} IPv4" else - msg_error "IPv4 Internet Not Connected" + ipv4_status="${RD}✖${CL} IPv4" 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 + ipv6_status="${GN}✔${CL} IPv6" else - msg_error "IPv6 Internet Not Connected" + ipv6_status="${RD}✖${CL} IPv6" fi + # Show combined status + msg_ok "Internet: ${ipv4_status} ${ipv6_status}" + # 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 @@ -239,10 +256,6 @@ motd_ssh() { fi } -# ============================================================================== -# SECTION 5: CONTAINER CUSTOMIZATION -# ============================================================================== - # ------------------------------------------------------------------------------ # customize() # @@ -253,21 +266,33 @@ motd_ssh() { # - Sets proper permissions on SSH directories and key files # ------------------------------------------------------------------------------ customize() { - if [[ "$PASSWORD" == "" ]]; then - msg_info "Customizing Container" + if [[ -z "${PASSWORD:-}" ]]; then + msg_info "Configuring autologin for root" + + # Enable root account (remove password lock) + passwd -d root >/dev/null 2>&1 || true + + # Create getty override for container-getty@1.service 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 +[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" + + # Reload and restart getty service + $STD systemctl daemon-reload + $STD systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') || true + + msg_ok "Autologin configured" fi + + msg_ok "Customized Container" + 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 From 806bb715a9667a9047fba1ebe34cada48d95d2e0 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:54:20 +0100 Subject: [PATCH 32/54] Remove dev mode and log handling from build/install Cleaned up dev mode and persistent log directory logic from build.func and install.func. Network connectivity status messages are now more direct, and unnecessary exports and re-parsing of dev_mode have been removed for clarity and maintainability. --- misc/build.func | 22 +--------------------- misc/install.func | 25 ++++--------------------- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/misc/build.func b/misc/build.func index 733a4215f..4cf7e2156 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,17 +47,7 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log - CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" - - # Parse dev_mode early - parse_dev_mode - - # Setup persistent log directory if logs mode active - if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" - fi + CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"} # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then @@ -2338,16 +2328,6 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" - export BUILD_LOG="$BUILD_LOG" - export INSTALL_LOG="/root/.install-${SESSION_ID}.log" - export dev_mode="${dev_mode:-}" - export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" - export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" - export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" - export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" - export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" - export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" - export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" diff --git a/misc/install.func b/misc/install.func index 9ea4a0fab..5b13bab62 100644 --- a/misc/install.func +++ b/misc/install.func @@ -28,17 +28,6 @@ # SECTION 1: INITIALIZATION # ============================================================================== -# Ensure INSTALL_LOG is set (exported from build.func, but fallback if missing) -if [[ -z "${INSTALL_LOG:-}" ]]; then - INSTALL_LOG="/root/.install-${SESSION_ID:-unknown}.log" -fi - -# Dev mode: Persistent logs directory -if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - INSTALL_LOG="/var/log/community-scripts/install-${SESSION_ID:-unknown}-$(date +%Y%m%d_%H%M%S).log" -fi - 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 @@ -49,9 +38,6 @@ source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxV load_functions catch_errors -# Re-parse dev_mode in container context (flags exported from host) -parse_dev_mode - # ============================================================================== # SECTION 2: NETWORK & CONNECTIVITY # ============================================================================== @@ -122,23 +108,20 @@ network_check() { # 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 - ipv4_status="${GN}✔${CL} IPv4" else - ipv4_status="${RD}✖${CL} IPv4" + 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 - ipv6_status="${GN}✔${CL} IPv6" else - ipv6_status="${RD}✖${CL} IPv6" + msg_error "IPv6 Internet Not Connected" fi - # Show combined status - msg_ok "Internet: ${ipv4_status} ${ipv6_status}" - # 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 From f32ab7876f262aa7c3ec713365a05090a7af92a5 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:13:13 +0100 Subject: [PATCH 33/54] Update install.func --- misc/install.func | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/misc/install.func b/misc/install.func index 5b13bab62..e9e319576 100644 --- a/misc/install.func +++ b/misc/install.func @@ -239,6 +239,10 @@ motd_ssh() { fi } +# ============================================================================== +# SECTION 5: CONTAINER CUSTOMIZATION +# ============================================================================== + # ------------------------------------------------------------------------------ # customize() # @@ -249,33 +253,21 @@ motd_ssh() { # - Sets proper permissions on SSH directories and key files # ------------------------------------------------------------------------------ customize() { - if [[ -z "${PASSWORD:-}" ]]; then - msg_info "Configuring autologin for root" - - # Enable root account (remove password lock) - passwd -d root >/dev/null 2>&1 || true - - # Create getty override for container-getty@1.service + 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 + [Service] + ExecStart= + ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 \$TERM EOF - - # Reload and restart getty service - $STD systemctl daemon-reload - $STD systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') || true - - msg_ok "Autologin configured" + systemctl daemon-reload + systemctl restart $(basename $(dirname $GETTY_OVERRIDE) | sed 's/\.d//') + msg_ok "Customized Container" fi - - msg_ok "Customized Container" - 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 From b87558b415b33343ef6e6fd135feee240706a038 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:14:17 +0100 Subject: [PATCH 34/54] Update build.func --- misc/build.func | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/build.func b/misc/build.func index 4cf7e2156..d9894c01e 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,7 +47,7 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"} + CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then From 894449182d93bf144c93b4d33e78c1d1443101af Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:34:01 +0100 Subject: [PATCH 35/54] Debugcontainer build options handling Changed PCT_OPTIONS from an array to a string for proper export and compatibility with Proxmox pct. Features are now passed as a comma-separated string. Also updated install.func to use a literal heredoc for systemd override configuration. --- misc/build.func | 55 +++++++++++++++++++++-------------------------- misc/install.func | 8 +++---- 2 files changed, 28 insertions(+), 35 deletions(-) diff --git a/misc/build.func b/misc/build.func index d9894c01e..c0203b225 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2351,46 +2351,39 @@ build_container() { export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - # Build PCT_OPTIONS array (not string) for proper parameter handling - PCT_OPTIONS=() + # Build FEATURES string from array + # Proxmox pct expects comma-separated features: -features nesting=1,keyctl=1,fuse=1 + FEATURES=$( + IFS=, + echo "${FEATURES_ARRAY[*]}" + ) - # Add features - each as separate -features parameter - for feature in "${FEATURES_ARRAY[@]}"; do - PCT_OPTIONS+=("-features" "$feature") - done + # Build PCT_OPTIONS as string (must be string for export to work) + PCT_OPTIONS="-features $FEATURES +-hostname $HN +-tags $TAGS +$SD +$NS +$NET_STRING +-onboot 1 +-cores $CORE_COUNT +-memory $RAM_SIZE +-unprivileged $CT_TYPE" - PCT_OPTIONS+=("-hostname" "$HN") - PCT_OPTIONS+=("-tags" "$TAGS") - - if [ -n "$SD" ]; then - PCT_OPTIONS+=($SD) # Storage device flags (already formatted) - fi - - if [ -n "$NS" ]; then - PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - fi - - # Network configuration (single string with all network parameters) - PCT_OPTIONS+=($NET_STRING) - - PCT_OPTIONS+=("-onboot" "1") - PCT_OPTIONS+=("-cores" "$CORE_COUNT") - PCT_OPTIONS+=("-memory" "$RAM_SIZE") - PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - - # Protection flag + # Add optional flags if [ "$PROTECT_CT" == "1" ]; then - PCT_OPTIONS+=("-protection" "1") + PCT_OPTIONS="$PCT_OPTIONS +-protection 1" fi - # Timezone flag if [ -n "$CT_TIMEZONE" ]; then - PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + PCT_OPTIONS="$PCT_OPTIONS +-timezone $CT_TIMEZONE" fi - # Password flag (already formatted as "-password xxx") if [ -n "$PW" ]; then - PCT_OPTIONS+=($PW) + PCT_OPTIONS="$PCT_OPTIONS +$PW" fi export PCT_OPTIONS diff --git a/misc/install.func b/misc/install.func index e9e319576..7c3f601d4 100644 --- a/misc/install.func +++ b/misc/install.func @@ -257,10 +257,10 @@ customize() { 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 + cat <<'EOF' >$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//') From fa82374abb329cc08a7bb44702df07d86d6c659c Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:42:54 +0100 Subject: [PATCH 36/54] Revert --- misc/build.func | 116 ++++++++---------------------------------------- 1 file changed, 19 insertions(+), 97 deletions(-) diff --git a/misc/build.func b/misc/build.func index c0203b225..39144e3f3 100644 --- a/misc/build.func +++ b/misc/build.func @@ -525,35 +525,6 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} - ENABLE_NESTING=${var_nesting:-"${1:-1}"} - ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} - ALLOW_MOUNT_FS=${var_mount_fs:-""} - ENABLE_MKNOD=${var_mknod:-"${1:-0}"} - PROTECT_CT=${var_protection:-"${1:-no}"} - CT_TIMEZONE=${var_timezone:-""} - - # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) - # This must happen here before any usage of these variables - case "${ENABLE_NESTING,,}" in - yes | true) ENABLE_NESTING="1" ;; - no | false) ENABLE_NESTING="0" ;; - esac - case "${ENABLE_KEYCTL,,}" in - yes | true) ENABLE_KEYCTL="1" ;; - no | false) ENABLE_KEYCTL="0" ;; - esac - case "${ENABLE_MKNOD,,}" in - yes | true) ENABLE_MKNOD="1" ;; - no | false) ENABLE_MKNOD="0" ;; - esac - case "${ENABLE_FUSE,,}" in - yes | true) ENABLE_FUSE="1" ;; - no | false) ENABLE_FUSE="0" ;; - esac - case "${PROTECT_CT,,}" in - yes | true) PROTECT_CT="1" ;; - no | false) PROTECT_CT="0" ;; - esac # 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 @@ -2288,34 +2259,14 @@ build_container() { none) ;; esac - # Build FEATURES array with advanced settings - # Note: All feature flags are already normalized to 0/1 in default_settings() - # Proxmox requires each feature as a separate parameter, not comma-separated string - FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") - - # keyctl: needed for Docker inside containers (systemd-networkd workaround) - # Typically needed for unprivileged containers with Docker - if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES_ARRAY+=("keyctl=1") + if [ "$CT_TYPE" == "1" ]; then + FEATURES="keyctl=1,nesting=1" + else + FEATURES="nesting=1" fi - # mknod: allow device node creation (requires kernel 5.3+, experimental) - if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES_ARRAY+=("mknod=1") - fi - - # FUSE: required for rclone, mergerfs, AppImage, etc. - if [ "$ENABLE_FUSE" == "1" ]; then - FEATURES_ARRAY+=("fuse=1") - fi - - # mount: allow specific filesystems (e.g., nfs, ext4, etc.) - # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) - if [ -n "$ALLOW_MOUNT_FS" ]; then - # Replace commas with semicolons for proper pct syntax - ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES="$FEATURES,fuse=1" fi TEMP_DIR=$(mktemp -d) @@ -2341,51 +2292,22 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" - export ENABLE_NESTING="$ENABLE_NESTING" - export ENABLE_KEYCTL="$ENABLE_KEYCTL" - export ENABLE_MKNOD="$ENABLE_MKNOD" - export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" - export PROTECT_CT="$PROTECT_CT" - export CT_TIMEZONE="$CT_TIMEZONE" export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - - # Build FEATURES string from array - # Proxmox pct expects comma-separated features: -features nesting=1,keyctl=1,fuse=1 - FEATURES=$( - IFS=, - echo "${FEATURES_ARRAY[*]}" - ) - - # Build PCT_OPTIONS as string (must be string for export to work) - PCT_OPTIONS="-features $FEATURES --hostname $HN --tags $TAGS -$SD -$NS -$NET_STRING --onboot 1 --cores $CORE_COUNT --memory $RAM_SIZE --unprivileged $CT_TYPE" - - # Add optional flags - if [ "$PROTECT_CT" == "1" ]; then - PCT_OPTIONS="$PCT_OPTIONS --protection 1" - fi - - if [ -n "$CT_TIMEZONE" ]; then - PCT_OPTIONS="$PCT_OPTIONS --timezone $CT_TIMEZONE" - fi - - if [ -n "$PW" ]; then - PCT_OPTIONS="$PCT_OPTIONS -$PW" - fi - + 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 PCT_OPTIONS export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" From f008d4bbf9dfef94dcfdd9ebaa6b03d967e6fe4a Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:53:48 +0100 Subject: [PATCH 37/54] Comment out problematic feature and dev mode code Commented out sections related to advanced feature flags, array-based FEATURES and PCT_OPTIONS, and dev mode exports that were causing compatibility and autologin issues. Added notes and TODOs for future integration, and moved MOTD/SSH setup for debugging to only run when containers are kept for troubleshooting. --- misc/build.func | 212 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 172 insertions(+), 40 deletions(-) diff --git a/misc/build.func b/misc/build.func index 39144e3f3..2e1dcd8c5 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,8 +47,18 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files + BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" + # Parse dev_mode early + parse_dev_mode + + # Setup persistent log directory if logs mode active + if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then + mkdir -p /var/log/community-scripts + BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" + fi + # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then PVEVERSION="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" @@ -525,6 +535,40 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} + # PROBLEMATIC: Extra feature variables not in working version - Comment out for now + # TODO: These need proper integration with string-based FEATURES export (not array) + # ENABLE_NESTING=${var_nesting:-"${1:-1}"} + # ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} + # ALLOW_MOUNT_FS=${var_mount_fs:-""} + # ENABLE_MKNOD=${var_mknod:-"${1:-0}"} + # PROTECT_CT=${var_protection:-"${1:-no}"} + # CT_TIMEZONE=${var_timezone:-""} + + # PROBLEMATIC: Feature normalization breaks compatibility + # TODO: Working version expects ENABLE_FUSE="yes" but this converts to "1" + # If implementing, must update all checks throughout codebase to handle numeric values + # # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) + # # This must happen here before any usage of these variables + # case "${ENABLE_NESTING,,}" in + # yes | true) ENABLE_NESTING="1" ;; + # no | false) ENABLE_NESTING="0" ;; + # esac + # case "${ENABLE_KEYCTL,,}" in + # yes | true) ENABLE_KEYCTL="1" ;; + # no | false) ENABLE_KEYCTL="0" ;; + # esac + # case "${ENABLE_MKNOD,,}" in + # yes | true) ENABLE_MKNOD="1" ;; + # no | false) ENABLE_MKNOD="0" ;; + # esac + # case "${ENABLE_FUSE,,}" in + # yes | true) ENABLE_FUSE="1" ;; + # no | false) ENABLE_FUSE="0" ;; + # esac + # case "${PROTECT_CT,,}" in + # yes | true) PROTECT_CT="1" ;; + # no | false) PROTECT_CT="0" ;; + # esac # 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 @@ -2259,15 +2303,48 @@ build_container() { 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 + # PROBLEMATIC: FEATURES_ARRAY system - ROOT CAUSE OF AUTOLOGIN FAILURE + # TODO: Bash arrays cannot be exported to child processes! + # When PCT_OPTIONS was built from this array, exports failed silently + # This caused container creation to succeed but configuration to fail (black console) + # SOLUTION: Use string-based FEATURES like working version: + # 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 + # # Build FEATURES array with advanced settings + # # Note: All feature flags are already normalized to 0/1 in default_settings() + # # Proxmox requires each feature as a separate parameter, not comma-separated string + # FEATURES_ARRAY=() + # FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + # + # # keyctl: needed for Docker inside containers (systemd-networkd workaround) + # # Typically needed for unprivileged containers with Docker + # if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then + # FEATURES_ARRAY+=("keyctl=1") + # fi + # + # # mknod: allow device node creation (requires kernel 5.3+, experimental) + # if [ "$ENABLE_MKNOD" == "1" ]; then + # FEATURES_ARRAY+=("mknod=1") + # fi + # + # # FUSE: required for rclone, mergerfs, AppImage, etc. + # if [ "$ENABLE_FUSE" == "1" ]; then + # FEATURES_ARRAY+=("fuse=1") + # fi + # + # # mount: allow specific filesystems (e.g., nfs, ext4, etc.) + # # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) + # if [ -n "$ALLOW_MOUNT_FS" ]; then + # # Replace commas with semicolons for proper pct syntax + # ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" + # FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + # fi TEMP_DIR=$(mktemp -d) pushd "$TEMP_DIR" >/dev/null @@ -2279,6 +2356,19 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" + # PROBLEMATIC: DEV_MODE exports not in working version + # TODO: These were causing autologin issues by interfering with getty service + # If implementing DEV_MODE, must ensure it doesn't modify container startup + # export BUILD_LOG="$BUILD_LOG" + # export INSTALL_LOG="/root/.install-${SESSION_ID}.log" + # export dev_mode="${dev_mode:-}" + # export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" + # export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" + # export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" + # export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" + # export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" + # export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" + # export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" @@ -2292,23 +2382,68 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" + # PROBLEMATIC: Extra exports for features not in working version + # TODO: These variables don't exist in working version (see above where commented out) + # export ENABLE_NESTING="$ENABLE_NESTING" + # export ENABLE_KEYCTL="$ENABLE_KEYCTL" + # export ENABLE_MKNOD="$ENABLE_MKNOD" + # export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" + # export PROTECT_CT="$PROTECT_CT" + # export CT_TIMEZONE="$CT_TIMEZONE" 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 PCT_OPTIONS + + # PROBLEMATIC: PCT_OPTIONS as array cannot be exported + # TODO: Working version uses string with newlines (see create_lxc_container function) + # # Build PCT_OPTIONS array (not string) for proper parameter handling + # PCT_OPTIONS=() + + # PROBLEMATIC: Loop building PCT_OPTIONS from FEATURES_ARRAY + # TODO: Since both are commented out above, this loop has no effect + # # Add features - each as separate -features parameter + # for feature in "${FEATURES_ARRAY[@]}"; do + # PCT_OPTIONS+=("-features" "$feature") + # done + + # PROBLEMATIC: All PCT_OPTIONS array operations + # TODO: Working version uses string-based PCT_OPTIONS built in create_lxc_container + # The export below is the CRITICAL FAILURE POINT - Bash cannot export arrays! + # # PCT_OPTIONS+=("-hostname" "$HN") + # PCT_OPTIONS+=("-tags" "$TAGS") + # + # if [ -n "$SD" ]; then + # PCT_OPTIONS+=($SD) # Storage device flags (already formatted) + # fi + # + # if [ -n "$NS" ]; then + # PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) + # fi + # + # # Network configuration (single string with all network parameters) + # PCT_OPTIONS+=($NET_STRING) + # + # PCT_OPTIONS+=("-onboot" "1") + # PCT_OPTIONS+=("-cores" "$CORE_COUNT") + # PCT_OPTIONS+=("-memory" "$RAM_SIZE") + # PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") + # + # # Protection flag + # if [ "$PROTECT_CT" == "1" ]; then + # PCT_OPTIONS+=("-protection" "1") + # fi + # + # # Timezone flag + # if [ -n "$CT_TIMEZONE" ]; then + # PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + # fi + # + # # Password flag (already formatted as "-password xxx") + # if [ -n "$PW" ]; then + # PCT_OPTIONS+=($PW) + # fi + # + # export PCT_OPTIONS export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" create_lxc_container || exit $? @@ -2636,21 +2771,6 @@ EOF' # Install SSH keys install_ssh_keys_into_ct - # Dev mode: Setup MOTD/SSH AFTER network is ready and before installation - # This ensures the container is fully booted and accessible via SSH - if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then - msg_dev "Setting up MOTD and SSH for debugging access" - pct exec "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)" <<'MOTD_SETUP' - # Only run motd_ssh function if it exists - if declare -f motd_ssh >/dev/null 2>&1; then - motd_ssh - else - msg_warn "motd_ssh function not found in ${var_install}.sh" - fi -MOTD_SETUP - msg_dev "MOTD/SSH ready - container accessible via SSH (IP: $ip_in_lxc)" - fi - # Run application installer # NOTE: We disable error handling here because: # 1. Container errors are caught by error_handler INSIDE container @@ -2703,8 +2823,8 @@ MOTD_SETUP # Show available logs echo "" - [[ $build_log_copied == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" - [[ $install_log_copied == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" fi # Dev mode: Keep container or open breakpoint shell @@ -2740,6 +2860,18 @@ MOTD_SETUP echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}" elif [[ "$response" =~ ^[Nn]$ ]]; then echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}" + + # Dev mode: Setup MOTD/SSH for debugging access to broken container + if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then + echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" + if pct exec "$CTID" -- bash -c " + source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/install.func) + declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true + " >/dev/null 2>&1; then + local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) + echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}" + fi + fi fi else # Timeout - auto-remove From b529d45db07268051a509fdb452f62fd9baa17ab Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:54:42 +0100 Subject: [PATCH 38/54] Update build.func --- misc/build.func | 54 ++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/misc/build.func b/misc/build.func index 2e1dcd8c5..81a8b0189 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,17 +47,23 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log + # PROBLEMATIC: BUILD_LOG and DEV_MODE initialization + # TODO: Working version doesn't have BUILD_LOG or DEV_MODE + # BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" + # PROBLEMATIC: parse_dev_mode function call (function doesn't exist) + # TODO: Comment out until DEV_MODE is properly implemented # Parse dev_mode early - parse_dev_mode + # parse_dev_mode - # Setup persistent log directory if logs mode active - if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - mkdir -p /var/log/community-scripts - BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" - fi + # PROBLEMATIC: DEV_MODE_LOGS directory setup + # TODO: Working version doesn't use persistent logs + # # Setup persistent log directory if logs mode active + # if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then + # mkdir -p /var/log/community-scripts + # BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" + # fi # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then @@ -2806,22 +2812,24 @@ EOF' if [[ $install_exit_code -ne 0 ]]; then msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})" - # Copy both logs from container before potential deletion - local build_log_copied=false - local install_log_copied=false - - if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then - # Copy BUILD_LOG (creation log) if it exists - if [[ -f "${BUILD_LOG}" ]]; then - cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true - fi - - # Copy INSTALL_LOG from container - if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then - install_log_copied=true - fi - - # Show available logs + # PROBLEMATIC: BUILD_LOG copy logic + # TODO: Working version doesn't use BUILD_LOG + # # Copy both logs from container before potential deletion + # local build_log_copied=false + # local install_log_copied=false + # + # if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then + # # Copy BUILD_LOG (creation log) if it exists + # if [[ -f "${BUILD_LOG}" ]]; then + # cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true + # fi + # + # # Copy INSTALL_LOG from container + # if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then + # install_log_copied=true + # fi + # + # # Show available logs echo "" [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" From 4613021586a4d528fc4d2bdb84c88cabdbb65699 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 13:55:21 +0100 Subject: [PATCH 39/54] Update build.func --- misc/build.func | 50 ++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/misc/build.func b/misc/build.func index 81a8b0189..a7bab07f4 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2830,30 +2830,34 @@ EOF' # fi # # # Show available logs - echo "" - [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" - [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" - fi + # echo "" + # [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" + # [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + # fi - # Dev mode: Keep container or open breakpoint shell - if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then - msg_dev "Keep mode active - container ${CTID} preserved" - return 0 - elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then - msg_dev "Breakpoint mode - opening shell in container ${CTID}" - echo -e "${YW}Type 'exit' to return to host${CL}" - pct enter "$CTID" - echo "" - echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" - if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then - pct stop "$CTID" &>/dev/null || true - pct destroy "$CTID" &>/dev/null || true - msg_ok "Container ${CTID} removed" - else - msg_dev "Container ${CTID} kept for debugging" - fi - exit $install_exit_code - fi + # PROBLEMATIC: DEV_MODE keep/breakpoint logic + # TODO: Working version doesn't have DEV_MODE + # # Dev mode: Keep container or open breakpoint shell + # if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then + # msg_dev "Keep mode active - container ${CTID} preserved" + # return 0 + # elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then + # msg_dev "Breakpoint mode - opening shell in container ${CTID}" + # echo -e "${YW}Type 'exit' to return to host${CL}" + # pct enter "$CTID" + # echo "" + # echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" + # if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then + # pct stop "$CTID" &>/dev/null || true + # pct destroy "$CTID" &>/dev/null || true + # msg_ok "Container ${CTID} removed" + # else + # msg_dev "Container ${CTID} kept for debugging" + # fi + # exit $install_exit_code + # fi + + # Working version: Simple cleanup on failure # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner) echo "" From ced117328249fe4344a0189051b437e823a50782 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:00:17 +0100 Subject: [PATCH 40/54] Update build.func --- misc/build.func | 360 +++++++++++++++++++++--------------------------- 1 file changed, 159 insertions(+), 201 deletions(-) diff --git a/misc/build.func b/misc/build.func index a7bab07f4..d5dd0ef06 100644 --- a/misc/build.func +++ b/misc/build.func @@ -47,23 +47,17 @@ variables() { 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. SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files - # PROBLEMATIC: BUILD_LOG and DEV_MODE initialization - # TODO: Working version doesn't have BUILD_LOG or DEV_MODE - # BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log + BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log CTTYPE="${CTTYPE:-${CT_TYPE:-1}}" - # PROBLEMATIC: parse_dev_mode function call (function doesn't exist) - # TODO: Comment out until DEV_MODE is properly implemented # Parse dev_mode early - # parse_dev_mode + parse_dev_mode - # PROBLEMATIC: DEV_MODE_LOGS directory setup - # TODO: Working version doesn't use persistent logs - # # Setup persistent log directory if logs mode active - # if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then - # mkdir -p /var/log/community-scripts - # BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" - # fi + # Setup persistent log directory if logs mode active + if [[ "${DEV_MODE_LOGS:-false}" == "true" ]]; then + mkdir -p /var/log/community-scripts + BUILD_LOG="/var/log/community-scripts/create-lxc-${SESSION_ID}-$(date +%Y%m%d_%H%M%S).log" + fi # Get Proxmox VE version and kernel version if command -v pveversion >/dev/null 2>&1; then @@ -541,40 +535,35 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} - # PROBLEMATIC: Extra feature variables not in working version - Comment out for now - # TODO: These need proper integration with string-based FEATURES export (not array) - # ENABLE_NESTING=${var_nesting:-"${1:-1}"} - # ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} - # ALLOW_MOUNT_FS=${var_mount_fs:-""} - # ENABLE_MKNOD=${var_mknod:-"${1:-0}"} - # PROTECT_CT=${var_protection:-"${1:-no}"} - # CT_TIMEZONE=${var_timezone:-""} + ENABLE_NESTING=${var_nesting:-"${1:-1}"} + ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} + ALLOW_MOUNT_FS=${var_mount_fs:-""} + ENABLE_MKNOD=${var_mknod:-"${1:-0}"} + PROTECT_CT=${var_protection:-"${1:-no}"} + CT_TIMEZONE=${var_timezone:-""} - # PROBLEMATIC: Feature normalization breaks compatibility - # TODO: Working version expects ENABLE_FUSE="yes" but this converts to "1" - # If implementing, must update all checks throughout codebase to handle numeric values - # # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) - # # This must happen here before any usage of these variables - # case "${ENABLE_NESTING,,}" in - # yes | true) ENABLE_NESTING="1" ;; - # no | false) ENABLE_NESTING="0" ;; - # esac - # case "${ENABLE_KEYCTL,,}" in - # yes | true) ENABLE_KEYCTL="1" ;; - # no | false) ENABLE_KEYCTL="0" ;; - # esac - # case "${ENABLE_MKNOD,,}" in - # yes | true) ENABLE_MKNOD="1" ;; - # no | false) ENABLE_MKNOD="0" ;; - # esac - # case "${ENABLE_FUSE,,}" in - # yes | true) ENABLE_FUSE="1" ;; - # no | false) ENABLE_FUSE="0" ;; - # esac - # case "${PROTECT_CT,,}" in - # yes | true) PROTECT_CT="1" ;; - # no | false) PROTECT_CT="0" ;; - # esac + # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) + # This must happen here before any usage of these variables + case "${ENABLE_NESTING,,}" in + yes | true) ENABLE_NESTING="1" ;; + no | false) ENABLE_NESTING="0" ;; + esac + case "${ENABLE_KEYCTL,,}" in + yes | true) ENABLE_KEYCTL="1" ;; + no | false) ENABLE_KEYCTL="0" ;; + esac + case "${ENABLE_MKNOD,,}" in + yes | true) ENABLE_MKNOD="1" ;; + no | false) ENABLE_MKNOD="0" ;; + esac + case "${ENABLE_FUSE,,}" in + yes | true) ENABLE_FUSE="1" ;; + no | false) ENABLE_FUSE="0" ;; + esac + case "${PROTECT_CT,,}" in + yes | true) PROTECT_CT="1" ;; + no | false) PROTECT_CT="0" ;; + esac # 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 @@ -2309,48 +2298,35 @@ build_container() { none) ;; esac - # PROBLEMATIC: FEATURES_ARRAY system - ROOT CAUSE OF AUTOLOGIN FAILURE - # TODO: Bash arrays cannot be exported to child processes! - # When PCT_OPTIONS was built from this array, exports failed silently - # This caused container creation to succeed but configuration to fail (black console) - # SOLUTION: Use string-based FEATURES like working version: - # 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 - # # Build FEATURES array with advanced settings - # # Note: All feature flags are already normalized to 0/1 in default_settings() - # # Proxmox requires each feature as a separate parameter, not comma-separated string - # FEATURES_ARRAY=() - # FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") - # - # # keyctl: needed for Docker inside containers (systemd-networkd workaround) - # # Typically needed for unprivileged containers with Docker - # if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - # FEATURES_ARRAY+=("keyctl=1") - # fi - # - # # mknod: allow device node creation (requires kernel 5.3+, experimental) - # if [ "$ENABLE_MKNOD" == "1" ]; then - # FEATURES_ARRAY+=("mknod=1") - # fi - # - # # FUSE: required for rclone, mergerfs, AppImage, etc. - # if [ "$ENABLE_FUSE" == "1" ]; then - # FEATURES_ARRAY+=("fuse=1") - # fi - # - # # mount: allow specific filesystems (e.g., nfs, ext4, etc.) - # # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) - # if [ -n "$ALLOW_MOUNT_FS" ]; then - # # Replace commas with semicolons for proper pct syntax - # ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - # FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") - # fi + # Build FEATURES array with advanced settings + # Note: All feature flags are already normalized to 0/1 in default_settings() + # Proxmox requires each feature as a separate parameter, not comma-separated string + FEATURES_ARRAY=() + FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + + # keyctl: needed for Docker inside containers (systemd-networkd workaround) + # Typically needed for unprivileged containers with Docker + if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then + FEATURES_ARRAY+=("keyctl=1") + fi + + # mknod: allow device node creation (requires kernel 5.3+, experimental) + if [ "$ENABLE_MKNOD" == "1" ]; then + FEATURES_ARRAY+=("mknod=1") + fi + + # FUSE: required for rclone, mergerfs, AppImage, etc. + if [ "$ENABLE_FUSE" == "1" ]; then + FEATURES_ARRAY+=("fuse=1") + fi + + # mount: allow specific filesystems (e.g., nfs, ext4, etc.) + # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) + if [ -n "$ALLOW_MOUNT_FS" ]; then + # Replace commas with semicolons for proper pct syntax + ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" + FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + fi TEMP_DIR=$(mktemp -d) pushd "$TEMP_DIR" >/dev/null @@ -2362,19 +2338,16 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" - # PROBLEMATIC: DEV_MODE exports not in working version - # TODO: These were causing autologin issues by interfering with getty service - # If implementing DEV_MODE, must ensure it doesn't modify container startup - # export BUILD_LOG="$BUILD_LOG" - # export INSTALL_LOG="/root/.install-${SESSION_ID}.log" - # export dev_mode="${dev_mode:-}" - # export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" - # export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" - # export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" - # export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" - # export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" - # export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" - # export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" + export BUILD_LOG="$BUILD_LOG" + export INSTALL_LOG="/root/.install-${SESSION_ID}.log" + export dev_mode="${dev_mode:-}" + export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" + export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" + export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" + export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" + export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" + export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" + export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" @@ -2388,68 +2361,59 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" - # PROBLEMATIC: Extra exports for features not in working version - # TODO: These variables don't exist in working version (see above where commented out) - # export ENABLE_NESTING="$ENABLE_NESTING" - # export ENABLE_KEYCTL="$ENABLE_KEYCTL" - # export ENABLE_MKNOD="$ENABLE_MKNOD" - # export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" - # export PROTECT_CT="$PROTECT_CT" - # export CT_TIMEZONE="$CT_TIMEZONE" + export ENABLE_NESTING="$ENABLE_NESTING" + export ENABLE_KEYCTL="$ENABLE_KEYCTL" + export ENABLE_MKNOD="$ENABLE_MKNOD" + export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" + export PROTECT_CT="$PROTECT_CT" + export CT_TIMEZONE="$CT_TIMEZONE" export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - # PROBLEMATIC: PCT_OPTIONS as array cannot be exported - # TODO: Working version uses string with newlines (see create_lxc_container function) - # # Build PCT_OPTIONS array (not string) for proper parameter handling - # PCT_OPTIONS=() + # Build PCT_OPTIONS array (not string) for proper parameter handling + PCT_OPTIONS=() - # PROBLEMATIC: Loop building PCT_OPTIONS from FEATURES_ARRAY - # TODO: Since both are commented out above, this loop has no effect - # # Add features - each as separate -features parameter - # for feature in "${FEATURES_ARRAY[@]}"; do - # PCT_OPTIONS+=("-features" "$feature") - # done + # Add features - each as separate -features parameter + for feature in "${FEATURES_ARRAY[@]}"; do + PCT_OPTIONS+=("-features" "$feature") + done - # PROBLEMATIC: All PCT_OPTIONS array operations - # TODO: Working version uses string-based PCT_OPTIONS built in create_lxc_container - # The export below is the CRITICAL FAILURE POINT - Bash cannot export arrays! - # # PCT_OPTIONS+=("-hostname" "$HN") - # PCT_OPTIONS+=("-tags" "$TAGS") - # - # if [ -n "$SD" ]; then - # PCT_OPTIONS+=($SD) # Storage device flags (already formatted) - # fi - # - # if [ -n "$NS" ]; then - # PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - # fi - # - # # Network configuration (single string with all network parameters) - # PCT_OPTIONS+=($NET_STRING) - # - # PCT_OPTIONS+=("-onboot" "1") - # PCT_OPTIONS+=("-cores" "$CORE_COUNT") - # PCT_OPTIONS+=("-memory" "$RAM_SIZE") - # PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - # - # # Protection flag - # if [ "$PROTECT_CT" == "1" ]; then - # PCT_OPTIONS+=("-protection" "1") - # fi - # - # # Timezone flag - # if [ -n "$CT_TIMEZONE" ]; then - # PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") - # fi - # - # # Password flag (already formatted as "-password xxx") - # if [ -n "$PW" ]; then - # PCT_OPTIONS+=($PW) - # fi - # - # export PCT_OPTIONS + PCT_OPTIONS+=("-hostname" "$HN") + PCT_OPTIONS+=("-tags" "$TAGS") + + if [ -n "$SD" ]; then + PCT_OPTIONS+=($SD) # Storage device flags (already formatted) + fi + + if [ -n "$NS" ]; then + PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) + fi + + # Network configuration (single string with all network parameters) + PCT_OPTIONS+=($NET_STRING) + + PCT_OPTIONS+=("-onboot" "1") + PCT_OPTIONS+=("-cores" "$CORE_COUNT") + PCT_OPTIONS+=("-memory" "$RAM_SIZE") + PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") + + # Protection flag + if [ "$PROTECT_CT" == "1" ]; then + PCT_OPTIONS+=("-protection" "1") + fi + + # Timezone flag + if [ -n "$CT_TIMEZONE" ]; then + PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + fi + + # Password flag (already formatted as "-password xxx") + if [ -n "$PW" ]; then + PCT_OPTIONS+=($PW) + fi + + export PCT_OPTIONS export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" create_lxc_container || exit $? @@ -2812,52 +2776,46 @@ EOF' if [[ $install_exit_code -ne 0 ]]; then msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})" - # PROBLEMATIC: BUILD_LOG copy logic - # TODO: Working version doesn't use BUILD_LOG - # # Copy both logs from container before potential deletion - # local build_log_copied=false - # local install_log_copied=false - # - # if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then - # # Copy BUILD_LOG (creation log) if it exists - # if [[ -f "${BUILD_LOG}" ]]; then - # cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true - # fi - # - # # Copy INSTALL_LOG from container - # if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then - # install_log_copied=true - # fi - # - # # Show available logs - # echo "" - # [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" - # [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" - # fi + # Copy both logs from container before potential deletion + local build_log_copied=false + local install_log_copied=false - # PROBLEMATIC: DEV_MODE keep/breakpoint logic - # TODO: Working version doesn't have DEV_MODE - # # Dev mode: Keep container or open breakpoint shell - # if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then - # msg_dev "Keep mode active - container ${CTID} preserved" - # return 0 - # elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then - # msg_dev "Breakpoint mode - opening shell in container ${CTID}" - # echo -e "${YW}Type 'exit' to return to host${CL}" - # pct enter "$CTID" - # echo "" - # echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" - # if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then - # pct stop "$CTID" &>/dev/null || true - # pct destroy "$CTID" &>/dev/null || true - # msg_ok "Container ${CTID} removed" - # else - # msg_dev "Container ${CTID} kept for debugging" - # fi - # exit $install_exit_code - # fi + if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then + # Copy BUILD_LOG (creation log) if it exists + if [[ -f "${BUILD_LOG}" ]]; then + cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true + fi - # Working version: Simple cleanup on failure + # Copy INSTALL_LOG from container + if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then + install_log_copied=true + fi + + # Show available logs + echo "" + [[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}" + [[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}" + fi + + # Dev mode: Keep container or open breakpoint shell + if [[ "${DEV_MODE_KEEP:-false}" == "true" ]]; then + msg_dev "Keep mode active - container ${CTID} preserved" + return 0 + elif [[ "${DEV_MODE_BREAKPOINT:-false}" == "true" ]]; then + msg_dev "Breakpoint mode - opening shell in container ${CTID}" + echo -e "${YW}Type 'exit' to return to host${CL}" + pct enter "$CTID" + echo "" + echo -en "${YW}Container ${CTID} still running. Remove now? (y/N): ${CL}" + if read -r response && [[ "$response" =~ ^[Yy]$ ]]; then + pct stop "$CTID" &>/dev/null || true + pct destroy "$CTID" &>/dev/null || true + msg_ok "Container ${CTID} removed" + else + msg_dev "Container ${CTID} kept for debugging" + fi + exit $install_exit_code + fi # Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner) echo "" From 3a7104589166344858dc82398f77810cf4d1add4 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:09:29 +0100 Subject: [PATCH 41/54] Update build.func --- misc/build.func | 165 ++++++++++++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 60 deletions(-) diff --git a/misc/build.func b/misc/build.func index d5dd0ef06..ba8069147 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2338,16 +2338,6 @@ build_container() { export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" - export BUILD_LOG="$BUILD_LOG" - export INSTALL_LOG="/root/.install-${SESSION_ID}.log" - export dev_mode="${dev_mode:-}" - export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" - export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" - export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" - export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" - export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" - export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" - export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" export CACHER="$APT_CACHER" export CACHER_IP="$APT_CACHER_IP" export tz="$timezone" @@ -2361,65 +2351,120 @@ build_container() { export CTTYPE="$CT_TYPE" export ENABLE_FUSE="$ENABLE_FUSE" export ENABLE_TUN="$ENABLE_TUN" - export ENABLE_NESTING="$ENABLE_NESTING" - export ENABLE_KEYCTL="$ENABLE_KEYCTL" - export ENABLE_MKNOD="$ENABLE_MKNOD" - export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" - export PROTECT_CT="$PROTECT_CT" - export CT_TIMEZONE="$CT_TIMEZONE" export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - - # Build PCT_OPTIONS array (not string) for proper parameter handling - PCT_OPTIONS=() - - # Add features - each as separate -features parameter - for feature in "${FEATURES_ARRAY[@]}"; do - PCT_OPTIONS+=("-features" "$feature") - done - - PCT_OPTIONS+=("-hostname" "$HN") - PCT_OPTIONS+=("-tags" "$TAGS") - - if [ -n "$SD" ]; then - PCT_OPTIONS+=($SD) # Storage device flags (already formatted) - fi - - if [ -n "$NS" ]; then - PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - fi - - # Network configuration (single string with all network parameters) - PCT_OPTIONS+=($NET_STRING) - - PCT_OPTIONS+=("-onboot" "1") - PCT_OPTIONS+=("-cores" "$CORE_COUNT") - PCT_OPTIONS+=("-memory" "$RAM_SIZE") - PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - - # Protection flag - if [ "$PROTECT_CT" == "1" ]; then - PCT_OPTIONS+=("-protection" "1") - fi - - # Timezone flag - if [ -n "$CT_TIMEZONE" ]; then - PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") - fi - - # Password flag (already formatted as "-password xxx") - if [ -n "$PW" ]; then - PCT_OPTIONS+=($PW) - fi - - export PCT_OPTIONS + 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" + # 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 SESSION_ID="$SESSION_ID" + # export BUILD_LOG="$BUILD_LOG" + # export INSTALL_LOG="/root/.install-${SESSION_ID}.log" + # export dev_mode="${dev_mode:-}" + # export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" + # export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" + # export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" + # export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" + # export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" + # export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" + # export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" + # export CACHER="$APT_CACHER" + # export CACHER_IP="$APT_CACHER_IP" + # export tz="$timezone" + # 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 ENABLE_NESTING="$ENABLE_NESTING" + # export ENABLE_KEYCTL="$ENABLE_KEYCTL" + # export ENABLE_MKNOD="$ENABLE_MKNOD" + # export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" + # export PROTECT_CT="$PROTECT_CT" + # export CT_TIMEZONE="$CT_TIMEZONE" + # export PCT_OSTYPE="$var_os" + # export PCT_OSVERSION="$var_version" + # export PCT_DISK_SIZE="$DISK_SIZE" + + # # Build PCT_OPTIONS array (not string) for proper parameter handling + # PCT_OPTIONS=() + + # # Add features - each as separate -features parameter + # for feature in "${FEATURES_ARRAY[@]}"; do + # PCT_OPTIONS+=("-features" "$feature") + # done + + # PCT_OPTIONS+=("-hostname" "$HN") + # PCT_OPTIONS+=("-tags" "$TAGS") + + # if [ -n "$SD" ]; then + # PCT_OPTIONS+=($SD) # Storage device flags (already formatted) + # fi + + # if [ -n "$NS" ]; then + # PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) + # fi + + # # Network configuration (single string with all network parameters) + # PCT_OPTIONS+=($NET_STRING) + + # PCT_OPTIONS+=("-onboot" "1") + # PCT_OPTIONS+=("-cores" "$CORE_COUNT") + # PCT_OPTIONS+=("-memory" "$RAM_SIZE") + # PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") + + # # Protection flag + # if [ "$PROTECT_CT" == "1" ]; then + # PCT_OPTIONS+=("-protection" "1") + # fi + + # # Timezone flag + # if [ -n "$CT_TIMEZONE" ]; then + # PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") + # fi + + # # Password flag (already formatted as "-password xxx") + # if [ -n "$PW" ]; then + # PCT_OPTIONS+=($PW) + # fi + + # export PCT_OPTIONS + # export TEMPLATE_STORAGE="${var_template_storage:-}" + # export CONTAINER_STORAGE="${var_container_storage:-}" + # create_lxc_container || exit $? + + # LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" + # ============================================================================ # GPU/USB PASSTHROUGH CONFIGURATION # ============================================================================ From 16c65ae73a9bd76604b67dde3cee73e8b2bc5df7 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:17:52 +0100 Subject: [PATCH 42/54] Update build.func --- misc/build.func | 87 ++++++++++++------------------------------------- 1 file changed, 21 insertions(+), 66 deletions(-) diff --git a/misc/build.func b/misc/build.func index ba8069147..b2766761f 100644 --- a/misc/build.func +++ b/misc/build.func @@ -535,35 +535,6 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} - ENABLE_NESTING=${var_nesting:-"${1:-1}"} - ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} - ALLOW_MOUNT_FS=${var_mount_fs:-""} - ENABLE_MKNOD=${var_mknod:-"${1:-0}"} - PROTECT_CT=${var_protection:-"${1:-no}"} - CT_TIMEZONE=${var_timezone:-""} - - # Normalize feature flags to 0/1 immediately (pct requires numeric values, not yes/no) - # This must happen here before any usage of these variables - case "${ENABLE_NESTING,,}" in - yes | true) ENABLE_NESTING="1" ;; - no | false) ENABLE_NESTING="0" ;; - esac - case "${ENABLE_KEYCTL,,}" in - yes | true) ENABLE_KEYCTL="1" ;; - no | false) ENABLE_KEYCTL="0" ;; - esac - case "${ENABLE_MKNOD,,}" in - yes | true) ENABLE_MKNOD="1" ;; - no | false) ENABLE_MKNOD="0" ;; - esac - case "${ENABLE_FUSE,,}" in - yes | true) ENABLE_FUSE="1" ;; - no | false) ENABLE_FUSE="0" ;; - esac - case "${PROTECT_CT,,}" in - yes | true) PROTECT_CT="1" ;; - no | false) PROTECT_CT="0" ;; - esac # 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 @@ -2298,34 +2269,15 @@ build_container() { none) ;; esac - # Build FEATURES array with advanced settings - # Note: All feature flags are already normalized to 0/1 in default_settings() - # Proxmox requires each feature as a separate parameter, not comma-separated string - FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") - - # keyctl: needed for Docker inside containers (systemd-networkd workaround) - # Typically needed for unprivileged containers with Docker - if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES_ARRAY+=("keyctl=1") + # Build FEATURES string (working version - simple and reliable) + if [ "$CT_TYPE" == "1" ]; then + FEATURES="keyctl=1,nesting=1" + else + FEATURES="nesting=1" fi - # mknod: allow device node creation (requires kernel 5.3+, experimental) - if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES_ARRAY+=("mknod=1") - fi - - # FUSE: required for rclone, mergerfs, AppImage, etc. - if [ "$ENABLE_FUSE" == "1" ]; then - FEATURES_ARRAY+=("fuse=1") - fi - - # mount: allow specific filesystems (e.g., nfs, ext4, etc.) - # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated, not comma!) - if [ -n "$ALLOW_MOUNT_FS" ]; then - # Replace commas with semicolons for proper pct syntax - ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES="$FEATURES,fuse=1" fi TEMP_DIR=$(mktemp -d) @@ -3182,7 +3134,7 @@ create_lxc_container() { 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 + if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then msg_ok "Container created successfully after upgrade." return 0 else @@ -3608,9 +3560,12 @@ create_lxc_container() { 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}") + # PCT_OPTIONS is now a string (exported from build_container) + # Add rootfs if not already specified + if [[ ! "$PCT_OPTIONS" =~ "-rootfs" ]]; then + PCT_OPTIONS="$PCT_OPTIONS + -rootfs $CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}" + fi # Lock by template file (avoid concurrent downloads/creates) lockfile="/tmp/template.${TEMPLATE}.lock" @@ -3624,11 +3579,11 @@ create_lxc_container() { } LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" - msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" + 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 + # First attempt (PCT_OPTIONS is a multi-line string, use it directly) + if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating template..." # Validate template file @@ -3647,7 +3602,7 @@ create_lxc_container() { fi # Retry after repair - if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then + if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then # Fallback to local storage if not already on local if [[ "$TEMPLATE_STORAGE" != "local" ]]; then msg_info "Retrying container creation with fallback to local storage..." @@ -3656,7 +3611,7 @@ create_lxc_container() { 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 + if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then # Local fallback also failed - check for LXC stack version issue if grep -qiE 'unsupported .* version' "$LOGFILE"; then echo @@ -3680,7 +3635,7 @@ create_lxc_container() { msg_error "Container creation failed. 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 @@ -3712,7 +3667,7 @@ create_lxc_container() { msg_error "Container creation failed. 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 - pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" 2>&1 | tee -a "$LOGFILE" + pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE" set +x fi exit 209 From a47bc24568b917efa8347a4179205d870c80f6f3 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:35:02 +0100 Subject: [PATCH 43/54] Refactor PCT Options --- misc/build.func | 219 +++++++++++++++++++++++++----------------------- 1 file changed, 112 insertions(+), 107 deletions(-) diff --git a/misc/build.func b/misc/build.func index b2766761f..c1e7cc4d6 100644 --- a/misc/build.func +++ b/misc/build.func @@ -535,6 +535,26 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} + ENABLE_NESTING=${var_nesting:-"${1:-1}"} + ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} + ALLOW_MOUNT_FS=${var_mount_fs:-""} + ENABLE_MKNOD=${var_mknod:-"${1:-0}"} + PROTECT_CT=${var_protection:-"${1:-no}"} + CT_TIMEZONE=${var_timezone:-""} + + # Normalize numeric feature flags (keep ENABLE_FUSE as yes/no for compatibility) + case "${ENABLE_NESTING,,}" in + yes | true) ENABLE_NESTING="1" ;; + no | false) ENABLE_NESTING="0" ;; + esac + case "${ENABLE_KEYCTL,,}" in + yes | true) ENABLE_KEYCTL="1" ;; + no | false) ENABLE_KEYCTL="0" ;; + esac + case "${ENABLE_MKNOD,,}" in + yes | true) ENABLE_MKNOD="1" ;; + no | false) ENABLE_MKNOD="0" ;; + esac # 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 @@ -2269,17 +2289,35 @@ build_container() { none) ;; esac - # Build FEATURES string (working version - simple and reliable) - if [ "$CT_TYPE" == "1" ]; then - FEATURES="keyctl=1,nesting=1" - else - FEATURES="nesting=1" + # Build FEATURES_ARRAY with all requested features + FEATURES_ARRAY=() + FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + + # keyctl: needed for Docker inside containers (systemd-networkd workaround) + if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then + FEATURES_ARRAY+=("keyctl=1") fi - if [ "$ENABLE_FUSE" == "yes" ]; then - FEATURES="$FEATURES,fuse=1" + # mknod: allow device node creation (requires kernel 5.3+) + if [ "$ENABLE_MKNOD" == "1" ]; then + FEATURES_ARRAY+=("mknod=1") fi + # FUSE: required for rclone, mergerfs, AppImage, etc. + if [ "$ENABLE_FUSE" == "yes" ]; then + FEATURES_ARRAY+=("fuse=1") + fi + + # mount: allow specific filesystems (e.g., nfs, ext4) + # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated!) + if [ -n "$ALLOW_MOUNT_FS" ]; then + ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" + FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") + fi + + # NEW IMPLEMENTATION (Fixed): Build PCT_OPTIONS properly + # Key insight: Bash cannot export arrays, so we build the options as a string + TEMP_DIR=$(mktemp -d) pushd "$TEMP_DIR" >/dev/null if [ "$var_os" == "alpine" ]; then @@ -2287,6 +2325,8 @@ build_container() { else export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)" fi + + # Core exports for install.func export DIAGNOSTICS="$DIAGNOSTICS" export RANDOM_UUID="$RANDOM_UUID" export SESSION_ID="$SESSION_ID" @@ -2306,117 +2346,82 @@ build_container() { export PCT_OSTYPE="$var_os" export PCT_OSVERSION="$var_version" export PCT_DISK_SIZE="$DISK_SIZE" - export PCT_OPTIONS=" - -features $FEATURES + + # DEV_MODE exports (optional, for debugging) + export BUILD_LOG="$BUILD_LOG" + export INSTALL_LOG="/root/.install-${SESSION_ID}.log" + export dev_mode="${dev_mode:-}" + export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" + export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" + export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" + export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" + export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" + export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" + export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" + + # Build PCT_OPTIONS as multi-line string (arrays cannot be exported!) + # Use FEATURES_ARRAY to build the FEATURES string + FEATURES_STRING="" + for feature in "${FEATURES_ARRAY[@]}"; do + if [ -z "$FEATURES_STRING" ]; then + FEATURES_STRING="$feature" + else + FEATURES_STRING="$FEATURES_STRING,$feature" + fi + done + + # Start building PCT_OPTIONS as a string + PCT_OPTIONS_STRING=" -features $FEATURES_STRING -hostname $HN - -tags $TAGS - $SD - $NS + -tags $TAGS" + + # Add storage if specified + if [ -n "$SD" ]; then + PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING + $SD" + fi + + # Add nameserver if specified + if [ -n "$NS" ]; then + PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING + $NS" + fi + + # Network configuration + PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING $NET_STRING -onboot 1 -cores $CORE_COUNT -memory $RAM_SIZE - -unprivileged $CT_TYPE - $PW -" + -unprivileged $CT_TYPE" + + # Protection flag (if var_protection was set) + if [ "${PROTECT_CT:-}" == "1" ] || [ "${PROTECT_CT:-}" == "yes" ]; then + PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING + -protection 1" + fi + + # Timezone flag (if var_timezone was set) + if [ -n "${CT_TIMEZONE:-}" ]; then + PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING + -timezone $CT_TIMEZONE" + fi + + # Password (already formatted) + if [ -n "$PW" ]; then + PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING + $PW" + fi + + # Export as string (this works, unlike arrays!) + export PCT_OPTIONS="$PCT_OPTIONS_STRING" export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" + create_lxc_container || exit $? LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - # 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 SESSION_ID="$SESSION_ID" - # export BUILD_LOG="$BUILD_LOG" - # export INSTALL_LOG="/root/.install-${SESSION_ID}.log" - # export dev_mode="${dev_mode:-}" - # export DEV_MODE_MOTD="${DEV_MODE_MOTD:-false}" - # export DEV_MODE_KEEP="${DEV_MODE_KEEP:-false}" - # export DEV_MODE_TRACE="${DEV_MODE_TRACE:-false}" - # export DEV_MODE_PAUSE="${DEV_MODE_PAUSE:-false}" - # export DEV_MODE_BREAKPOINT="${DEV_MODE_BREAKPOINT:-false}" - # export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" - # export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" - # export CACHER="$APT_CACHER" - # export CACHER_IP="$APT_CACHER_IP" - # export tz="$timezone" - # 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 ENABLE_NESTING="$ENABLE_NESTING" - # export ENABLE_KEYCTL="$ENABLE_KEYCTL" - # export ENABLE_MKNOD="$ENABLE_MKNOD" - # export ALLOW_MOUNT_FS="$ALLOW_MOUNT_FS" - # export PROTECT_CT="$PROTECT_CT" - # export CT_TIMEZONE="$CT_TIMEZONE" - # export PCT_OSTYPE="$var_os" - # export PCT_OSVERSION="$var_version" - # export PCT_DISK_SIZE="$DISK_SIZE" - - # # Build PCT_OPTIONS array (not string) for proper parameter handling - # PCT_OPTIONS=() - - # # Add features - each as separate -features parameter - # for feature in "${FEATURES_ARRAY[@]}"; do - # PCT_OPTIONS+=("-features" "$feature") - # done - - # PCT_OPTIONS+=("-hostname" "$HN") - # PCT_OPTIONS+=("-tags" "$TAGS") - - # if [ -n "$SD" ]; then - # PCT_OPTIONS+=($SD) # Storage device flags (already formatted) - # fi - - # if [ -n "$NS" ]; then - # PCT_OPTIONS+=($NS) # Nameserver flags (already formatted) - # fi - - # # Network configuration (single string with all network parameters) - # PCT_OPTIONS+=($NET_STRING) - - # PCT_OPTIONS+=("-onboot" "1") - # PCT_OPTIONS+=("-cores" "$CORE_COUNT") - # PCT_OPTIONS+=("-memory" "$RAM_SIZE") - # PCT_OPTIONS+=("-unprivileged" "$CT_TYPE") - - # # Protection flag - # if [ "$PROTECT_CT" == "1" ]; then - # PCT_OPTIONS+=("-protection" "1") - # fi - - # # Timezone flag - # if [ -n "$CT_TIMEZONE" ]; then - # PCT_OPTIONS+=("-timezone" "$CT_TIMEZONE") - # fi - - # # Password flag (already formatted as "-password xxx") - # if [ -n "$PW" ]; then - # PCT_OPTIONS+=($PW) - # fi - - # export PCT_OPTIONS - # export TEMPLATE_STORAGE="${var_template_storage:-}" - # export CONTAINER_STORAGE="${var_container_storage:-}" - # create_lxc_container || exit $? - - # LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" - # ============================================================================ # GPU/USB PASSTHROUGH CONFIGURATION # ============================================================================ From 222bdedc25b9d4bc9a7ce014a486884746b8eb73 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:43:57 +0100 Subject: [PATCH 44/54] Update build.func --- misc/build.func | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/build.func b/misc/build.func index c1e7cc4d6..cdfec736d 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2559,7 +2559,7 @@ EOF if [[ $gpu_count -eq 1 ]]; then # Automatic selection for single GPU selected_gpu="${available_gpus[0]}" - msg_custom "⚙️" "${GN}" "Automatically configuring ${selected_gpu} GPU passthrough" + msg_ok "Automatically configuring ${selected_gpu} GPU passthrough" else # Multiple GPUs - ask user echo -e "\n${INFO} Multiple GPU types detected:" @@ -2936,7 +2936,7 @@ fix_gpu_gids() { return 0 fi - msg_custom "🔧" "${BL}" "Detecting and setting correct GPU group IDs" + msg_info "Detecting and setting correct GPU group IDs" # Get actual GIDs from container local video_gid=$(pct exec "$CTID" -- sh -c "getent group video 2>/dev/null | cut -d: -f3") From 99ac1ac90898a301fd2487ccddbd563255f1f104 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:47:24 +0100 Subject: [PATCH 45/54] Update build.func --- misc/build.func | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/misc/build.func b/misc/build.func index cdfec736d..7300ab2c6 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2290,8 +2290,15 @@ build_container() { esac # Build FEATURES_ARRAY with all requested features + # Note: Set safe defaults for all feature flags to prevent undefined variables + : "${ENABLE_NESTING:=1}" + : "${ENABLE_KEYCTL:=0}" + : "${ENABLE_MKNOD:=0}" + : "${ENABLE_FUSE:=no}" + : "${ALLOW_MOUNT_FS:=}" + FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=${ENABLE_NESTING}") + FEATURES_ARRAY+=("nesting=$ENABLE_NESTING") # keyctl: needed for Docker inside containers (systemd-networkd workaround) if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then @@ -2315,6 +2322,12 @@ build_container() { FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") fi + # DEBUG: Show built FEATURES_ARRAY + echo "[DEBUG] FEATURES_ARRAY built with ${#FEATURES_ARRAY[@]} elements:" + for i in "${!FEATURES_ARRAY[@]}"; do + echo " [$i] = ${FEATURES_ARRAY[$i]}" + done + # NEW IMPLEMENTATION (Fixed): Build PCT_OPTIONS properly # Key insight: Bash cannot export arrays, so we build the options as a string @@ -2370,6 +2383,9 @@ build_container() { fi done + # DEBUG: Show converted FEATURES_STRING + echo "[DEBUG] FEATURES_STRING (comma-separated): $FEATURES_STRING" + # Start building PCT_OPTIONS as a string PCT_OPTIONS_STRING=" -features $FEATURES_STRING -hostname $HN @@ -2418,6 +2434,11 @@ build_container() { export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" + # DEBUG: Show final PCT_OPTIONS being exported + echo "[DEBUG] PCT_OPTIONS to be exported:" + echo "$PCT_OPTIONS" | sed 's/^/ /' + echo "[DEBUG] Calling create_lxc_container..." + create_lxc_container || exit $? LXC_CONFIG="/etc/pve/lxc/${CTID}.conf" @@ -3584,6 +3605,17 @@ create_lxc_container() { } LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" + + # DEBUG: Show the actual command that will be executed + echo "[DEBUG] ===== PCT CREATE COMMAND DETAILS =====" + echo "[DEBUG] CTID: $CTID" + echo "[DEBUG] Template: ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" + echo "[DEBUG] PCT_OPTIONS (will be word-split):" + echo "$PCT_OPTIONS" | sed 's/^/ /' + echo "[DEBUG] Full command line:" + echo " pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS" + echo "[DEBUG] ========================================" + msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS" msg_debug "Logfile: $LOGFILE" From ab4f4c117d712ddcc6e833e67aedaea82617d9e7 Mon Sep 17 00:00:00 2001 From: tremor021 Date: Mon, 24 Nov 2025 14:56:21 +0100 Subject: [PATCH 46/54] Joplin test --- ct/joplin-server.sh | 61 +++++++++++++++++++++ install/joplin-server-install.sh | 94 ++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 ct/joplin-server.sh create mode 100644 install/joplin-server-install.sh diff --git a/ct/joplin-server.sh b/ct/joplin-server.sh new file mode 100644 index 000000000..388f9fb43 --- /dev/null +++ b/ct/joplin-server.sh @@ -0,0 +1,61 @@ +#!/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: Slaviša Arežina (tremor021) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://joplinapp.org/ + +APP="Joplin-Server" +var_tags="${var_tags:-notes}" +var_cpu="${var_cpu:-2}" +var_ram="${var_ram:-4096}" +var_disk="${var_disk:-20}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /opt/joplin-server ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + + if check_for_gh_release "joplin-server" "laurent22/joplin"; then + msg_info "Stopping Services" + systemctl stop joplin-server + msg_ok "Stopped Services" + + fetch_and_deploy_gh_release "joplin-server" "laurent22/joplin" "tarball" "latest" + + msg_info "Updating Joplin-Server" + cd /opt/joplin-server + sed -i "/onenote-converter/d" packages/lib/package.json + $STD yarn config set --home enableTelemetry 0 + export BUILD_SEQUENCIAL=1 + $STD yarn install --inline-builds + msg_ok "Updated Joplin-Server" + + msg_info "Starting Services" + systemctl start joplin-server + msg_ok "Started Services" + msg_ok "Updated Successfully!" + fi + exit +} + +start +build_container +description + +msg_ok "Completed Successfully!\n" +echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}" +echo -e "${INFO}${YW} Access it using the following URL:${CL}" +echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:22300${CL}" diff --git a/install/joplin-server-install.sh b/install/joplin-server-install.sh new file mode 100644 index 000000000..35d85fbf2 --- /dev/null +++ b/install/joplin-server-install.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: Slaviša Arežina (tremor021) +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# Source: https://joplinapp.org/ + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +msg_info "Installing Dependencies" +$STD apt install -y \ + git \ + rsync +msg_ok "Installed Dependencies" + +PG_VERSION="17" setup_postgresql +NODE_VERSION=24 NODE_MODULE="yarn,npm,pm2" setup_nodejs +mkdir -p /opt/pm2 +export PM2_HOME=/opt/pm2 +$STD pm2 install pm2-logrotate +$STD pm2 set pm2-logrotate:max_size 100MB +$STD pm2 set pm2-logrotate:retain 5 +$STD pm2 set pm2-logrotate:compress tr + +msg_info "Setting up PostgreSQL Database" +DB_NAME=joplin +DB_USER=joplin +DB_PASS="$(openssl rand -base64 18 | cut -c1-13)" +$STD sudo -u postgres psql -c "CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';" +$STD sudo -u postgres psql -c "CREATE DATABASE $DB_NAME WITH OWNER $DB_USER ENCODING 'UTF8' TEMPLATE template0;" +$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET client_encoding TO 'utf8';" +$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET default_transaction_isolation TO 'read committed';" +$STD sudo -u postgres psql -c "ALTER ROLE $DB_USER SET timezone TO 'UTC'" +{ + echo "Joplin-Credentials" + echo "Joplin Database User: $DB_USER" + echo "Joplin Database Password: $DB_PASS" + echo "Joplin Database Name: $DB_NAME" +} >>~/joplin.creds +msg_ok "Set up PostgreSQL Database" + +fetch_and_deploy_gh_release "joplin-server" "laurent22/joplin" "tarball" "latest" + +msg_info "Setting up Joplin Server (Patience)" +LOCAL_IP=$(hostname -I | awk '{print $1}') +cd /opt/joplin-server +sed -i "/onenote-converter/d" packages/lib/package.json +$STD yarn config set --home enableTelemetry 0 +export BUILD_SEQUENCIAL=1 +$STD yarn install --inline-builds + +cat </opt/joplin-server/.env +PM2_HOME=/opt/pm2 +NODE_ENV=production +APP_BASE_URL=http://$LOCAL_IP:22300 +APP_PORT=22300 +DB_CLIENT=pg +POSTGRES_PASSWORD=$DB_PASS +POSTGRES_DATABASE=$DB_NAME +POSTGRES_USER=$DB_USER +POSTGRES_PORT=5432 +POSTGRES_HOST=localhost +EOF +msg_ok "Setup Joplin Server" + +msg_info "Setting up Service" +cat </etc/systemd/system/joplin-server.service +[Unit] +Description=Joplin Server Service +After=network.target + +[Service] +Type=simple +WorkingDirectory=/opt/joplin-server/packages/server +EnvironmentFile=/opt/joplin-server/.env +ExecStart=/usr/bin/yarn start-prod +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF +systemctl enable -q --now joplin-server +msg_ok "Service Setup" + +motd_ssh +customize +cleanup_lxc From 8ef47507f83014e3bef33560651b6d017a2a8227 Mon Sep 17 00:00:00 2001 From: tremor021 Date: Mon, 24 Nov 2025 14:57:12 +0100 Subject: [PATCH 47/54] VE>VED --- ct/joplin-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ct/joplin-server.sh b/ct/joplin-server.sh index 388f9fb43..d7824f8ba 100644 --- a/ct/joplin-server.sh +++ b/ct/joplin-server.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/community-scripts/ProxmoxVED/main/misc/build.func) # Copyright (c) 2021-2025 community-scripts ORG # Author: Slaviša Arežina (tremor021) # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE From 3fdb2d79c647bde2d5fecc70e45d08ac2ee2d425 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:59:26 +0100 Subject: [PATCH 48/54] Normalize feature flag handling in build.func Updated feature flag normalization to support both yes/no and 0/1 formats for ENABLE_NESTING, ENABLE_KEYCTL, and ENABLE_MKNOD. ENABLE_FUSE remains as yes/no for backward compatibility. --- misc/build.func | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/misc/build.func b/misc/build.func index 7300ab2c6..928d16238 100644 --- a/misc/build.func +++ b/misc/build.func @@ -542,18 +542,19 @@ base_settings() { PROTECT_CT=${var_protection:-"${1:-no}"} CT_TIMEZONE=${var_timezone:-""} - # Normalize numeric feature flags (keep ENABLE_FUSE as yes/no for compatibility) + # Normalize numeric feature flags (handle both yes/no and 0/1 formats) + # Keep ENABLE_FUSE as yes/no for backward compatibility case "${ENABLE_NESTING,,}" in - yes | true) ENABLE_NESTING="1" ;; - no | false) ENABLE_NESTING="0" ;; + yes | true | 1) ENABLE_NESTING="1" ;; + no | false | 0) ENABLE_NESTING="0" ;; esac case "${ENABLE_KEYCTL,,}" in - yes | true) ENABLE_KEYCTL="1" ;; - no | false) ENABLE_KEYCTL="0" ;; + yes | true | 1) ENABLE_KEYCTL="1" ;; + no | false | 0) ENABLE_KEYCTL="0" ;; esac case "${ENABLE_MKNOD,,}" in - yes | true) ENABLE_MKNOD="1" ;; - no | false) ENABLE_MKNOD="0" ;; + yes | true | 1) ENABLE_MKNOD="1" ;; + no | false | 0) ENABLE_MKNOD="0" ;; esac # 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 From dbaa41584b0609c3da9f2abca7d1d2a76afdd90e Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:04:09 +0100 Subject: [PATCH 49/54] remove portainer --- vm/docker-vm.sh | 74 ++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/vm/docker-vm.sh b/vm/docker-vm.sh index c7f2b285c..68dc581e2 100644 --- a/vm/docker-vm.sh +++ b/vm/docker-vm.sh @@ -29,7 +29,7 @@ var_os="debian" var_version="13" DISK_SIZE="10G" USE_CLOUD_INIT="no" -INSTALL_PORTAINER="no" +# INSTALL_PORTAINER="no" OS_TYPE="" OS_VERSION="" @@ -284,16 +284,16 @@ function select_cloud_init() { fi } -function select_portainer() { - if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "PORTAINER" \ - --yesno "Install Portainer for Docker management?\n\nPortainer is a lightweight management UI for Docker.\n\nAccess after installation:\n• HTTP: http://:9000\n• HTTPS: https://:9443" 14 68); then - INSTALL_PORTAINER="yes" - echo -e "${ADVANCED}${BOLD}${DGN}Portainer: ${BGN}yes${CL}" - else - INSTALL_PORTAINER="no" - echo -e "${ADVANCED}${BOLD}${DGN}Portainer: ${BGN}no${CL}" - fi -} +# function select_portainer() { +# if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "PORTAINER" \ +# --yesno "Install Portainer for Docker management?\n\nPortainer is a lightweight management UI for Docker.\n\nAccess after installation:\n• HTTP: http://:9000\n• HTTPS: https://:9443" 14 68); then +# INSTALL_PORTAINER="yes" +# echo -e "${ADVANCED}${BOLD}${DGN}Portainer: ${BGN}yes${CL}" +# else +# INSTALL_PORTAINER="no" +# echo -e "${ADVANCED}${BOLD}${DGN}Portainer: ${BGN}no${CL}" +# fi +# } function get_image_url() { local arch=$(dpkg --print-architecture) @@ -323,7 +323,7 @@ function default_settings() { select_cloud_init # Portainer Selection - ALWAYS ask - select_portainer + # select_portainer # Set defaults for other settings VMID=$(get_valid_nextid) @@ -367,7 +367,7 @@ function advanced_settings() { select_cloud_init # Portainer Selection - ALWAYS ask (at the beginning) - select_portainer + # select_portainer METHOD="advanced" [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) @@ -703,7 +703,7 @@ for i in {1..10}; do done # Install Portainer if requested -INSTALL_PORTAINER_PLACEHOLDER +# INSTALL_PORTAINER_PLACEHOLDER # Create completion flag echo \"[\\$(date)] Docker installation completed successfully\" @@ -711,20 +711,20 @@ touch /root/.docker-installed INSTALLEOF" >/dev/null # Add Portainer installation script if requested -if [ "$INSTALL_PORTAINER" = "yes" ]; then - virt-customize -q -a "${FILE}" --run-command "cat > /root/install-portainer.sh << 'PORTAINEREOF' -#!/bin/bash -exec >> /var/log/install-docker.log 2>&1 -echo \"[\\$(date)] Installing Portainer\" -docker volume create portainer_data -docker run -d -p 9000:9000 -p 9443:9443 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest -echo \"[\\$(date)] Portainer installed and started\" -PORTAINEREOF" >/dev/null - virt-customize -q -a "${FILE}" --run-command "chmod +x /root/install-portainer.sh" >/dev/null - virt-customize -q -a "${FILE}" --run-command "sed -i 's|INSTALL_PORTAINER_PLACEHOLDER|/root/install-portainer.sh|' /root/install-docker.sh" >/dev/null -else - virt-customize -q -a "${FILE}" --run-command "sed -i 's|INSTALL_PORTAINER_PLACEHOLDER|echo \"[\\\\\\$(date)] Skipping Portainer installation\"|' /root/install-docker.sh" >/dev/null -fi +# if [ "$INSTALL_PORTAINER" = "yes" ]; then +# virt-customize -q -a "${FILE}" --run-command "cat > /root/install-portainer.sh << 'PORTAINEREOF' +# #!/bin/bash +# exec >> /var/log/install-docker.log 2>&1 +# echo \"[\\$(date)] Installing Portainer\" +# docker volume create portainer_data +# docker run -d -p 9000:9000 -p 9443:9443 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest +# echo \"[\\$(date)] Portainer installed and started\" +# PORTAINEREOF" >/dev/null +# virt-customize -q -a "${FILE}" --run-command "chmod +x /root/install-portainer.sh" >/dev/null +# virt-customize -q -a "${FILE}" --run-command "sed -i 's|INSTALL_PORTAINER_PLACEHOLDER|/root/install-portainer.sh|' /root/install-docker.sh" >/dev/null +# else +# virt-customize -q -a "${FILE}" --run-command "sed -i 's|INSTALL_PORTAINER_PLACEHOLDER|echo \"[\\\\\\$(date)] Skipping Portainer installation\"|' /root/install-docker.sh" >/dev/null +# fi virt-customize -q -a "${FILE}" --run-command "chmod +x /root/install-docker.sh" >/dev/null @@ -899,15 +899,15 @@ else echo -e "${TAB}${DGN}Docker: ${BGN}Latest (via get.docker.com)${CL}" fi -if [ "$INSTALL_PORTAINER" = "yes" ]; then - if [ -n "$VM_IP" ]; then - echo -e "${TAB}${DGN}Portainer: ${BGN}https://${VM_IP}:9443${CL}" - else - echo -e "${TAB}${DGN}Portainer: ${BGN}Will be accessible at https://:9443${CL}" - echo -e "${TAB}${YW}⚠️ Wait 2-3 minutes after boot for installation to complete${CL}" - echo -e "${TAB}${YW}⚠️ Get IP with: ${BL}qm guest cmd ${VMID} network-get-interfaces${CL}" - fi -fi +# if [ "$INSTALL_PORTAINER" = "yes" ]; then +# if [ -n "$VM_IP" ]; then +# echo -e "${TAB}${DGN}Portainer: ${BGN}https://${VM_IP}:9443${CL}" +# else +# echo -e "${TAB}${DGN}Portainer: ${BGN}Will be accessible at https://:9443${CL}" +# echo -e "${TAB}${YW}⚠️ Wait 2-3 minutes after boot for installation to complete${CL}" +# echo -e "${TAB}${YW}⚠️ Get IP with: ${BL}qm guest cmd ${VMID} network-get-interfaces${CL}" +# fi +# fi if [ "$USE_CLOUD_INIT" = "yes" ]; then display_cloud_init_info "$VMID" "$HN" fi From b2abad19ba329bc0521317daa2c533ce2cb0e78d Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:07:27 +0100 Subject: [PATCH 50/54] Update build.func --- misc/build.func | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/misc/build.func b/misc/build.func index 928d16238..37b3af27c 100644 --- a/misc/build.func +++ b/misc/build.func @@ -535,27 +535,6 @@ base_settings() { TAGS="community-script,${var_tags:-}" ENABLE_FUSE=${var_fuse:-"${1:-no}"} ENABLE_TUN=${var_tun:-"${1:-no}"} - ENABLE_NESTING=${var_nesting:-"${1:-1}"} - ENABLE_KEYCTL=${var_keyctl:-"${1:-0}"} - ALLOW_MOUNT_FS=${var_mount_fs:-""} - ENABLE_MKNOD=${var_mknod:-"${1:-0}"} - PROTECT_CT=${var_protection:-"${1:-no}"} - CT_TIMEZONE=${var_timezone:-""} - - # Normalize numeric feature flags (handle both yes/no and 0/1 formats) - # Keep ENABLE_FUSE as yes/no for backward compatibility - case "${ENABLE_NESTING,,}" in - yes | true | 1) ENABLE_NESTING="1" ;; - no | false | 0) ENABLE_NESTING="0" ;; - esac - case "${ENABLE_KEYCTL,,}" in - yes | true | 1) ENABLE_KEYCTL="1" ;; - no | false | 0) ENABLE_KEYCTL="0" ;; - esac - case "${ENABLE_MKNOD,,}" in - yes | true | 1) ENABLE_MKNOD="1" ;; - no | false | 0) ENABLE_MKNOD="0" ;; - esac # 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 @@ -2373,22 +2352,8 @@ build_container() { export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}" export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}" - # Build PCT_OPTIONS as multi-line string (arrays cannot be exported!) - # Use FEATURES_ARRAY to build the FEATURES string - FEATURES_STRING="" - for feature in "${FEATURES_ARRAY[@]}"; do - if [ -z "$FEATURES_STRING" ]; then - FEATURES_STRING="$feature" - else - FEATURES_STRING="$FEATURES_STRING,$feature" - fi - done - - # DEBUG: Show converted FEATURES_STRING - echo "[DEBUG] FEATURES_STRING (comma-separated): $FEATURES_STRING" - - # Start building PCT_OPTIONS as a string - PCT_OPTIONS_STRING=" -features $FEATURES_STRING + # Build PCT_OPTIONS as multi-line string + PCT_OPTIONS_STRING=" -features $FEATURES -hostname $HN -tags $TAGS" From fac32e6840db9c39d6a22a0bac5f57260115d86a Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:10:49 +0100 Subject: [PATCH 51/54] Update build.func --- misc/build.func | 42 +++++++----------------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/misc/build.func b/misc/build.func index 37b3af27c..0c66b3ac0 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2269,45 +2269,17 @@ build_container() { none) ;; esac - # Build FEATURES_ARRAY with all requested features - # Note: Set safe defaults for all feature flags to prevent undefined variables - : "${ENABLE_NESTING:=1}" - : "${ENABLE_KEYCTL:=0}" - : "${ENABLE_MKNOD:=0}" - : "${ENABLE_FUSE:=no}" - : "${ALLOW_MOUNT_FS:=}" - - FEATURES_ARRAY=() - FEATURES_ARRAY+=("nesting=$ENABLE_NESTING") - - # keyctl: needed for Docker inside containers (systemd-networkd workaround) - if [ "$CT_TYPE" == "1" ] || [ "$ENABLE_KEYCTL" == "1" ]; then - FEATURES_ARRAY+=("keyctl=1") + # Build FEATURES string (simple working version) + if [ "$CT_TYPE" == "1" ]; then + FEATURES="keyctl=1,nesting=1" + else + FEATURES="nesting=1" fi - - # mknod: allow device node creation (requires kernel 5.3+) - if [ "$ENABLE_MKNOD" == "1" ]; then - FEATURES_ARRAY+=("mknod=1") - fi - - # FUSE: required for rclone, mergerfs, AppImage, etc. + if [ "$ENABLE_FUSE" == "yes" ]; then - FEATURES_ARRAY+=("fuse=1") + FEATURES="$FEATURES,fuse=1" fi - # mount: allow specific filesystems (e.g., nfs, ext4) - # Format: mount=fstype1;fstype2;fstype3 (semicolon-separated!) - if [ -n "$ALLOW_MOUNT_FS" ]; then - ALLOW_MOUNT_FS_FORMATTED="${ALLOW_MOUNT_FS//,/;}" - FEATURES_ARRAY+=("mount=$ALLOW_MOUNT_FS_FORMATTED") - fi - - # DEBUG: Show built FEATURES_ARRAY - echo "[DEBUG] FEATURES_ARRAY built with ${#FEATURES_ARRAY[@]} elements:" - for i in "${!FEATURES_ARRAY[@]}"; do - echo " [$i] = ${FEATURES_ARRAY[$i]}" - done - # NEW IMPLEMENTATION (Fixed): Build PCT_OPTIONS properly # Key insight: Bash cannot export arrays, so we build the options as a string From 1eb8111a286daa4435b8e7528e661497997aeb69 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:39:34 +0100 Subject: [PATCH 52/54] remove debug output --- misc/build.func | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/misc/build.func b/misc/build.func index 0c66b3ac0..4ef714bd4 100644 --- a/misc/build.func +++ b/misc/build.func @@ -2275,7 +2275,7 @@ build_container() { else FEATURES="nesting=1" fi - + if [ "$ENABLE_FUSE" == "yes" ]; then FEATURES="$FEATURES,fuse=1" fi @@ -2372,10 +2372,10 @@ build_container() { export TEMPLATE_STORAGE="${var_template_storage:-}" export CONTAINER_STORAGE="${var_container_storage:-}" - # DEBUG: Show final PCT_OPTIONS being exported - echo "[DEBUG] PCT_OPTIONS to be exported:" - echo "$PCT_OPTIONS" | sed 's/^/ /' - echo "[DEBUG] Calling create_lxc_container..." + # # DEBUG: Show final PCT_OPTIONS being exported + # echo "[DEBUG] PCT_OPTIONS to be exported:" + # echo "$PCT_OPTIONS" | sed 's/^/ /' + # echo "[DEBUG] Calling create_lxc_container..." create_lxc_container || exit $? @@ -3544,15 +3544,15 @@ create_lxc_container() { LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log" - # DEBUG: Show the actual command that will be executed - echo "[DEBUG] ===== PCT CREATE COMMAND DETAILS =====" - echo "[DEBUG] CTID: $CTID" - echo "[DEBUG] Template: ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" - echo "[DEBUG] PCT_OPTIONS (will be word-split):" - echo "$PCT_OPTIONS" | sed 's/^/ /' - echo "[DEBUG] Full command line:" - echo " pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS" - echo "[DEBUG] ========================================" + # # DEBUG: Show the actual command that will be executed + # echo "[DEBUG] ===== PCT CREATE COMMAND DETAILS =====" + # echo "[DEBUG] CTID: $CTID" + # echo "[DEBUG] Template: ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" + # echo "[DEBUG] PCT_OPTIONS (will be word-split):" + # echo "$PCT_OPTIONS" | sed 's/^/ /' + # echo "[DEBUG] Full command line:" + # echo " pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS" + # echo "[DEBUG] ========================================" msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS" msg_debug "Logfile: $LOGFILE" From 585a0e17bb1006219b3e369a618d21157dce8ebe Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:33:52 +0100 Subject: [PATCH 53/54] add qdrant --- ct/qdrant.sh | 45 ++++++++++++++++++++++ frontend/public/json/qdrant.json | 35 +++++++++++++++++ install/qdrant-install.sh | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 ct/qdrant.sh create mode 100644 frontend/public/json/qdrant.json create mode 100644 install/qdrant-install.sh diff --git a/ct/qdrant.sh b/ct/qdrant.sh new file mode 100644 index 000000000..dc8852c7b --- /dev/null +++ b/ct/qdrant.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func) +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Source: https://github.com/qdrant/qdrant + +APP="Qdrant" +var_tags="${var_tags:-}" +var_cpu="${var_cpu:-4}" +var_ram="${var_ram:-8192}" +var_disk="${var_disk:-20}" +var_os="${var_os:-debian}" +var_version="${var_version:-13}" +var_unprivileged="${var_unprivileged:-1}" + +header_info "$APP" +variables +color +catch_errors + +function update_script() { + header_info + check_container_storage + check_container_resources + if [[ ! -d /var/lib/qdrant ]]; then + msg_error "No ${APP} Installation Found!" + exit + fi + if check_for_gh_release "qdrant" "qdrant/qdrant"; then + fetch_and_deploy_gh_release "qdrant" "qdrant/qdrant" "binary" "latest" "/usr/bin/qdrant" + chown -R root:root /var/lib/qdrant + chmod -R 755 /var/lib/qdrant + 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}:6333${CL}" diff --git a/frontend/public/json/qdrant.json b/frontend/public/json/qdrant.json new file mode 100644 index 000000000..01e5d558c --- /dev/null +++ b/frontend/public/json/qdrant.json @@ -0,0 +1,35 @@ +{ + "name": "Qdrant", + "slug": "qdrant", + "categories": [ + 20 + ], + "date_created": "2025-11-25", + "type": "ct", + "updateable": false, + "privileged": false, + "config_path": "/etc/qdrant/config.yaml", + "interface_port": 6333, + "documentation": "https://github.com/qdrant/qdrant", + "website": "https://qdrant.tech/", + "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/qdrant.webp", + "description": "Qdrant is a vector search engine and vector database that allows you to store, search, and manage high-dimensional vectors efficiently.", + "install_methods": [ + { + "type": "default", + "script": "ct/qdrant.sh", + "resources": { + "cpu": 1, + "ram": 1024, + "hdd": 5, + "os": "debian", + "version": "13" + } + } + ], + "default_credentials": { + "username": null, + "password": null + }, + "notes": [] +} diff --git a/install/qdrant-install.sh b/install/qdrant-install.sh new file mode 100644 index 000000000..4d6ed15f1 --- /dev/null +++ b/install/qdrant-install.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# Copyright (c) 2021-2025 community-scripts ORG +# Author: MickLesk (CanbiZ) +# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE +# Source: https://github.com/qdrant/qdrant + +source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" +color +verb_ip6 +catch_errors +setting_up_container +network_check +update_os + +fetch_and_deploy_gh_release "qdrant" "qdrant/qdrant" "binary" "latest" "/usr/bin/qdrant" + +msg_info "Creating Qdrant Configuration" +mkdir -p /var/lib/qdrant/storage +mkdir -p /var/lib/qdrant/snapshots +mkdir -p /etc/qdrant +chown -R root:root /var/lib/qdrant +chmod -R 755 /var/lib/qdrant + +cat >/etc/qdrant/config.yaml </etc/systemd/system/qdrant.service < Date: Tue, 25 Nov 2025 08:34:09 +0000 Subject: [PATCH 54/54] Update .app files --- ct/headers/asterisk | 6 ------ ct/headers/domain-locker | 6 ------ ct/headers/joplin-server | 6 ++++++ ct/headers/kanba | 6 ------ ct/headers/librenms | 6 ------ ct/headers/mealie | 6 ++++++ ct/headers/omada | 6 ------ ct/headers/passbolt | 6 ------ ct/headers/qdrant | 6 ++++++ ct/headers/upgopher | 6 ------ 10 files changed, 18 insertions(+), 42 deletions(-) delete mode 100644 ct/headers/asterisk delete mode 100644 ct/headers/domain-locker create mode 100644 ct/headers/joplin-server delete mode 100644 ct/headers/kanba delete mode 100644 ct/headers/librenms create mode 100644 ct/headers/mealie delete mode 100644 ct/headers/omada delete mode 100644 ct/headers/passbolt create mode 100644 ct/headers/qdrant delete mode 100644 ct/headers/upgopher diff --git a/ct/headers/asterisk b/ct/headers/asterisk deleted file mode 100644 index ed4356862..000000000 --- a/ct/headers/asterisk +++ /dev/null @@ -1,6 +0,0 @@ - ___ __ _ __ - / | _____/ /____ _____(_)____/ /__ - / /| | / ___/ __/ _ \/ ___/ / ___/ //_/ - / ___ |(__ ) /_/ __/ / / (__ ) ,< -/_/ |_/____/\__/\___/_/ /_/____/_/|_| - diff --git a/ct/headers/domain-locker b/ct/headers/domain-locker deleted file mode 100644 index 0ab1a9b74..000000000 --- a/ct/headers/domain-locker +++ /dev/null @@ -1,6 +0,0 @@ - ____ _ __ __ - / __ \____ ____ ___ ____ _(_)___ / / ____ _____/ /_____ _____ - / / / / __ \/ __ `__ \/ __ `/ / __ \______/ / / __ \/ ___/ //_/ _ \/ ___/ - / /_/ / /_/ / / / / / / /_/ / / / / /_____/ /___/ /_/ / /__/ ,< / __/ / -/_____/\____/_/ /_/ /_/\__,_/_/_/ /_/ /_____/\____/\___/_/|_|\___/_/ - diff --git a/ct/headers/joplin-server b/ct/headers/joplin-server new file mode 100644 index 000000000..68b32c8fa --- /dev/null +++ b/ct/headers/joplin-server @@ -0,0 +1,6 @@ + __ ___ _____ + / /___ ____ / (_)___ / ___/___ ______ _____ _____ + __ / / __ \/ __ \/ / / __ \______\__ \/ _ \/ ___/ | / / _ \/ ___/ +/ /_/ / /_/ / /_/ / / / / / /_____/__/ / __/ / | |/ / __/ / +\____/\____/ .___/_/_/_/ /_/ /____/\___/_/ |___/\___/_/ + /_/ diff --git a/ct/headers/kanba b/ct/headers/kanba deleted file mode 100644 index 40c351f8b..000000000 --- a/ct/headers/kanba +++ /dev/null @@ -1,6 +0,0 @@ - __ __ __ - / //_/___ _____ / /_ ____ _ - / ,< / __ `/ __ \/ __ \/ __ `/ - / /| / /_/ / / / / /_/ / /_/ / -/_/ |_\__,_/_/ /_/_.___/\__,_/ - diff --git a/ct/headers/librenms b/ct/headers/librenms deleted file mode 100644 index cf3dd4e4f..000000000 --- a/ct/headers/librenms +++ /dev/null @@ -1,6 +0,0 @@ - __ _ __ - / / (_) /_ ________ ____ ____ ___ _____ - / / / / __ \/ ___/ _ \/ __ \/ __ `__ \/ ___/ - / /___/ / /_/ / / / __/ / / / / / / / (__ ) -/_____/_/_.___/_/ \___/_/ /_/_/ /_/ /_/____/ - diff --git a/ct/headers/mealie b/ct/headers/mealie new file mode 100644 index 000000000..a5d36d54c --- /dev/null +++ b/ct/headers/mealie @@ -0,0 +1,6 @@ + __ ___ ___ + / |/ /__ ____ _/ (_)__ + / /|_/ / _ \/ __ `/ / / _ \ + / / / / __/ /_/ / / / __/ +/_/ /_/\___/\__,_/_/_/\___/ + diff --git a/ct/headers/omada b/ct/headers/omada deleted file mode 100644 index 3629b79d1..000000000 --- a/ct/headers/omada +++ /dev/null @@ -1,6 +0,0 @@ - ____ __ - / __ \____ ___ ____ _____/ /___ _ - / / / / __ `__ \/ __ `/ __ / __ `/ -/ /_/ / / / / / / /_/ / /_/ / /_/ / -\____/_/ /_/ /_/\__,_/\__,_/\__,_/ - diff --git a/ct/headers/passbolt b/ct/headers/passbolt deleted file mode 100644 index 91f0ab71d..000000000 --- a/ct/headers/passbolt +++ /dev/null @@ -1,6 +0,0 @@ - ____ __ ____ - / __ \____ ___________/ /_ ____ / / /_ - / /_/ / __ `/ ___/ ___/ __ \/ __ \/ / __/ - / ____/ /_/ (__ |__ ) /_/ / /_/ / / /_ -/_/ \__,_/____/____/_.___/\____/_/\__/ - diff --git a/ct/headers/qdrant b/ct/headers/qdrant new file mode 100644 index 000000000..f6a89dbc3 --- /dev/null +++ b/ct/headers/qdrant @@ -0,0 +1,6 @@ + ____ __ __ + / __ \____/ /________ _____ / /_ + / / / / __ / ___/ __ `/ __ \/ __/ +/ /_/ / /_/ / / / /_/ / / / / /_ +\___\_\__,_/_/ \__,_/_/ /_/\__/ + diff --git a/ct/headers/upgopher b/ct/headers/upgopher deleted file mode 100644 index e1126d09c..000000000 --- a/ct/headers/upgopher +++ /dev/null @@ -1,6 +0,0 @@ - __ __ __ - / / / /___ ____ _____ ____ / /_ ___ _____ - / / / / __ \/ __ `/ __ \/ __ \/ __ \/ _ \/ ___/ -/ /_/ / /_/ / /_/ / /_/ / /_/ / / / / __/ / -\____/ .___/\__, /\____/ .___/_/ /_/\___/_/ - /_/ /____/ /_/