Compare commits

..

22 Commits

Author SHA1 Message Date
github-actions[bot] 66f5215c8c Update CHANGELOG.md 2026-06-25 12:06:42 +00:00
push-app-to-main[bot] 075a691198 Pinchflat (#15367) 2026-06-25 14:06:24 +02:00
community-scripts-pr-app[bot] fbd083c9ae Update CHANGELOG.md (#15389)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-25 11:13:58 +00:00
CanbiZ (MickLesk) edf167025f VM-Core: Update some Functions (#15113) 2026-06-25 13:13:29 +02:00
community-scripts-pr-app[bot] 3abbefe3b0 Update CHANGELOG.md (#15387)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 22:28:32 +00:00
Michel Roegl-Brunner c356c3827a delete wger (#15380) 2026-06-25 00:28:07 +02:00
community-scripts-pr-app[bot] 6f2d3da61f Update CHANGELOG.md (#15385)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 21:49:18 +00:00
MickLesk f7acb76e80 fix typo 2026-06-24 23:44:26 +02:00
community-scripts-pr-app[bot] b2ecd86698 Update CHANGELOG.md (#15381)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 21:32:52 +00:00
Michel Roegl-Brunner df10a5ea53 Delete ghost (#15377)
* ghost.sh

* ghost-install.sh

* ghost löschen
2026-06-24 23:32:29 +02:00
community-scripts-pr-app[bot] 3a65047ff9 Update CHANGELOG.md (#15379)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 21:30:04 +00:00
push-app-to-main[bot] 5b01ff81e8 SnapOtter (#15368) 2026-06-24 23:29:42 +02:00
community-scripts-pr-app[bot] 4e6cb56f06 Update CHANGELOG.md (#15378)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 21:22:09 +00:00
CanbiZ (MickLesk) 470c0672fb watcharr: Increase default RAM allocation from 1024 to 2048 (#15370) 2026-06-24 23:21:44 +02:00
community-scripts-pr-app[bot] 62b7080477 Update CHANGELOG.md (#15376)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 21:21:36 +00:00
CanbiZ (MickLesk) e193adad5a core: add SDN vnet selection in advanced install (#15366)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 23:21:12 +02:00
community-scripts-pr-app[bot] aeb8dba809 Update CHANGELOG.md (#15375)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 21:16:00 +00:00
CanbiZ (MickLesk) 3fc7008bbe Refactor LibreNMS: replace old install and update variant with tarball approach (#15369) 2026-06-24 23:15:36 +02:00
community-scripts-pr-app[bot] f71005cc48 Update CHANGELOG.md (#15364)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 04:58:53 +00:00
CanbiZ (MickLesk) 0b94d4f54a core: add var_http_proxy and var_http_no_proxy support (#15225)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 06:58:32 +02:00
community-scripts-pr-app[bot] 2acf3ba19e Update CHANGELOG.md (#15363)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 01:37:44 +00:00
Marvin 5670e3721d enabling rewirte module in apache (#15360)
The rewrite module is essential for beautiful domains like "https://myexample.com/problematicSite". With the currently provided setup these domains end up in a 404, as apache is not rewriting the domain.
2026-06-24 11:37:18 +10:00
22 changed files with 1239 additions and 485 deletions
+41
View File
@@ -486,6 +486,47 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details> </details>
## 2026-06-25
### 🆕 New Scripts
- Pinchflat ([#15367](https://github.com/community-scripts/ProxmoxVE/pull/15367))
### 💾 Core
- #### ✨ New Features
- VM-Core: Update some Functions [@MickLesk](https://github.com/MickLesk) ([#15113](https://github.com/community-scripts/ProxmoxVE/pull/15113))
## 2026-06-24
### 🆕 New Scripts
- SnapOtter ([#15368](https://github.com/community-scripts/ProxmoxVE/pull/15368))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- enabling rewirte module in apache [@d3v3lop3rDE](https://github.com/d3v3lop3rDE) ([#15360](https://github.com/community-scripts/ProxmoxVE/pull/15360))
- watcharr: Increase default RAM allocation from 1024 to 2048 [@MickLesk](https://github.com/MickLesk) ([#15370](https://github.com/community-scripts/ProxmoxVE/pull/15370))
- #### 🔧 Refactor
- Refactor LibreNMS: replace old install and update variant with tarball approach [@MickLesk](https://github.com/MickLesk) ([#15369](https://github.com/community-scripts/ProxmoxVE/pull/15369))
### 🗑️ Deleted Scripts
- delete wger [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15380](https://github.com/community-scripts/ProxmoxVE/pull/15380))
- Delete ghost [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15377](https://github.com/community-scripts/ProxmoxVE/pull/15377))
### 💾 Core
- #### ✨ New Features
- core: add SDN vnet selection in advanced install [@MickLesk](https://github.com/MickLesk) ([#15366](https://github.com/community-scripts/ProxmoxVE/pull/15366))
- core: add var_http_proxy and var_http_no_proxy support [@MickLesk](https://github.com/MickLesk) ([#15225](https://github.com/community-scripts/ProxmoxVE/pull/15225))
## 2026-06-23 ## 2026-06-23
### 🚀 Updated Scripts ### 🚀 Updated Scripts
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: fabrice1236
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://ghost.org/ | Github: https://github.com/TryGhost/Ghost
APP="Ghost"
var_tags="${var_tags:-cms;blog}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-5}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
setup_mariadb
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
ensure_dependencies git
msg_info "Updating Ghost"
if command -v ghost &>/dev/null; then
current_version=$(ghost version | grep 'Ghost-CLI version' | awk '{print $3}')
latest_version=$(npm show ghost-cli version)
if [ "$current_version" != "$latest_version" ]; then
msg_info "Updating ${APP} from version v${current_version} to v${latest_version}"
$STD npm install -g ghost-cli@latest
msg_ok "Updated successfully!"
else
msg_ok "${APP} is already at v${current_version}"
fi
else
msg_error "No ${APP} Installation Found!"
exit
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 "${GATEWAY}${BGN}http://${IP}:2368${CL}"
-6
View File
@@ -1,6 +0,0 @@
________ __
/ ____/ /_ ____ _____/ /_
/ / __/ __ \/ __ \/ ___/ __/
/ /_/ / / / / /_/ (__ ) /_
\____/_/ /_/\____/____/\__/
+6
View File
@@ -0,0 +1,6 @@
____ _ __ ______ __
/ __ \(_)___ _____/ /_ / __/ /___ _/ /_
/ /_/ / / __ \/ ___/ __ \/ /_/ / __ `/ __/
/ ____/ / / / / /__/ / / / __/ / /_/ / /_
/_/ /_/_/ /_/\___/_/ /_/_/ /_/\__,_/\__/
+6
View File
@@ -0,0 +1,6 @@
_____ ____ __ __
/ ___/____ ____ _____ / __ \/ /_/ /____ _____
\__ \/ __ \/ __ `/ __ \/ / / / __/ __/ _ \/ ___/
___/ / / / / /_/ / /_/ / /_/ / /_/ /_/ __/ /
/____/_/ /_/\__,_/ .___/\____/\__/\__/\___/_/
/_/
-6
View File
@@ -1,6 +0,0 @@
_ ______ ____ _____
| | /| / / __ `/ _ \/ ___/
| |/ |/ / /_/ / __/ /
|__/|__/\__, /\___/_/
/____/
+25 -17
View File
@@ -24,27 +24,35 @@ function update_script() {
header_info header_info
check_container_storage check_container_storage
check_container_resources check_container_resources
if [ ! -d /opt/librenms ]; then if [[ ! -d /opt/librenms ]]; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
setup_mariadb if check_for_gh_release "librenms" "librenms/librenms"; then
ensure_dependencies git msg_info "Stopping Services"
if [[ ! -d /opt/librenms/.git ]]; then systemctl stop php8.4-fpm librenms-scheduler.timer
msg_info "Initializing LibreNMS git metadata" msg_ok "Stopped Services"
LIBRENMS_VERSION=$(cat ~/.librenms 2>/dev/null)
cd /opt/librenms create_backup /opt/librenms/.env /opt/librenms/config.php /opt/librenms/rrd
git init -q CLEAN_INSTALL=1 fetch_and_deploy_gh_release "librenms" "librenms/librenms" "tarball"
git remote add origin https://github.com/librenms/librenms.git restore_backup
git fetch --depth 1 origin "refs/tags/v${LIBRENMS_VERSION}" 2>/dev/null ||
git fetch --depth 1 origin "refs/tags/${LIBRENMS_VERSION}" 2>/dev/null || true msg_info "Updating LibreNMS"
git checkout -qf FETCH_HEAD 2>/dev/null || true mkdir -p /opt/librenms/{rrd,logs,bootstrap/cache,storage}
chown -R librenms:librenms .git chown -R librenms:librenms /opt/librenms
msg_ok "Initialized LibreNMS git metadata" chmod 771 /opt/librenms
chmod -R ug=rwX /opt/librenms/bootstrap/cache /opt/librenms/storage /opt/librenms/logs /opt/librenms/rrd
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && uv venv --clear .venv && source .venv/bin/activate && uv pip install -r requirements.txt"
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev"
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && php8.4 artisan optimize:clear"
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && php8.4 artisan migrate --force --isolated"
msg_ok "Updated LibreNMS"
msg_info "Starting Services"
systemctl start php8.4-fpm librenms-scheduler.timer
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi fi
msg_info "Updating LibreNMS"
$STD su - librenms -s /bin/bash -c 'cd /opt/librenms && ./daily.sh'
msg_ok "Updated LibreNMS"
exit exit
} }
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: nnsense
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/kieraneglin/pinchflat
APP="Pinchflat"
var_tags="${var_tags:-media;youtube;downloader}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_gpu="${var_gpu:-yes}"
var_arm64="${var_arm64:-yes}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/pinchflat/app ]]; then
msg_error "No ${APP} installation found."
exit 1
fi
if check_for_gh_release "pinchflat" "kieraneglin/pinchflat"; then
msg_info "Stopping Service"
systemctl stop pinchflat
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "pinchflat" "kieraneglin/pinchflat" "tarball" "latest" "/opt/pinchflat-src"
msg_info "Building Pinchflat"
cd /opt/pinchflat-src
export MIX_ENV=prod
export ERL_FLAGS="+JPperf true"
$STD mix deps.get --only prod
$STD mix deps.compile
$STD yarn --cwd assets install
$STD mix assets.deploy
$STD mix compile
$STD mix release --overwrite
rm -rf /opt/pinchflat/app
cp -r _build/prod/rel/pinchflat /opt/pinchflat/app
msg_ok "Built Pinchflat"
msg_info "Starting Service"
systemctl start pinchflat
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8945${CL}"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://snapotter.com
APP="SnapOtter"
var_tags="${var_tags:-media;image}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_arm64="${var_arm64:-no}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/snapotter ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "snapotter" "snapotter-hq/SnapOtter"; then
msg_info "Stopping Service"
systemctl stop snapotter
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "tarball"
msg_info "Updating SnapOtter"
cd /opt/snapotter
$STD npm pkg delete scripts.prepare
$STD pnpm install --frozen-lockfile
$STD pnpm --filter @snapotter/web build
sed -i 's/mediapipe==0.10.21/mediapipe>=0.10.21/' /opt/snapotter/docker/feature-manifest.json
msg_ok "Updated SnapOtter"
msg_info "Starting Service"
systemctl start snapotter
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:1349${CL}"
+1 -1
View File
@@ -8,7 +8,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
APP="Watcharr" APP="Watcharr"
var_tags="${var_tags:-media}" var_tags="${var_tags:-media}"
var_cpu="${var_cpu:-1}" var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}" var_ram="${var_ram:-2048}"
var_disk="${var_disk:-4}" var_disk="${var_disk:-4}"
var_os="${var_os:-debian}" var_os="${var_os:-debian}"
var_version="${var_version:-13}" var_version="${var_version:-13}"
-78
View File
@@ -1,78 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/wger-project/wger
APP="wger"
var_tags="${var_tags:-management;fitness}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
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/wger ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "wger" "wger-project/wger"; then
msg_info "Stopping Service"
systemctl stop redis-server nginx celery celery-beat wger
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp -r /opt/wger/media /opt/wger_media_backup
cp /opt/wger/.env /opt/wger_env_backup
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wger" "wger-project/wger" "tarball"
msg_info "Restoring Data"
cp -r /opt/wger_media_backup/. /opt/wger/media
cp /opt/wger_env_backup /opt/wger/.env
rm -rf /opt/wger_media_backup /opt/wger_env_backup
msg_ok "Restored Data"
msg_info "Updating wger"
cd /opt/wger
set -a && source /opt/wger/.env && set +a
export DJANGO_SETTINGS_MODULE=settings.main
$STD uv pip install .
$STD npm install
$STD npm run build:css:sass
$STD uv run python manage.py migrate
$STD uv run python manage.py collectstatic --no-input
msg_ok "Updated wger"
msg_info "Starting Services"
systemctl start redis-server nginx celery celery-beat wger
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 "${GATEWAY}${BGN}http://${IP}:3000${CL}"
-45
View File
@@ -1,45 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: fabrice1236
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://ghost.org/ | Github: https://github.com/TryGhost/Ghost
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 \
nginx \
ca-certificates \
libjemalloc2 \
git
msg_ok "Installed Dependencies"
setup_mariadb
MARIADB_DB_NAME="ghost" MARIADB_DB_USER="ghostuser" setup_mariadb_db
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
msg_info "Installing Ghost CLI"
$STD npm install ghost-cli@latest -g
msg_ok "Installed Ghost CLI"
msg_info "Creating Service"
$STD adduser --disabled-password --gecos "Ghost user" ghost-user
$STD usermod -aG sudo ghost-user
echo "ghost-user ALL=(ALL) NOPASSWD:ALL" | tee /etc/sudoers.d/ghost-user
mkdir -p /var/www/ghost
chown -R ghost-user:ghost-user /var/www/ghost
chmod 775 /var/www/ghost
$STD sudo -u ghost-user -H sh -c "cd /var/www/ghost && ghost install --db=mysql --dbhost=localhost --dbuser=$MARIADB_DB_USER --dbpass=$MARIADB_DB_PASS --dbname=$MARIADB_DB_NAME --url=http://localhost:2368 --no-prompt --no-setup-nginx --no-setup-ssl --no-setup-mysql --enable --start --ip 0.0.0.0"
rm /etc/sudoers.d/ghost-user
msg_ok "Creating Service"
motd_ssh
customize
cleanup_lxc
-11
View File
@@ -17,7 +17,6 @@ msg_info "Installing Dependencies"
$STD apt install -y \ $STD apt install -y \
acl \ acl \
fping \ fping \
git \
graphviz \ graphviz \
imagemagick \ imagemagick \
mtr-tiny \ mtr-tiny \
@@ -65,16 +64,6 @@ EOF
chown -R librenms:librenms /opt/librenms chown -R librenms:librenms /opt/librenms
chmod 771 /opt/librenms chmod 771 /opt/librenms
chmod -R ug=rwX /opt/librenms/bootstrap/cache /opt/librenms/storage /opt/librenms/logs /opt/librenms/rrd chmod -R ug=rwX /opt/librenms/bootstrap/cache /opt/librenms/storage /opt/librenms/logs /opt/librenms/rrd
if [[ ! -d /opt/librenms/.git ]]; then
LIBRENMS_VERSION=$(cat ~/.librenms 2>/dev/null)
cd /opt/librenms
git init -q
git remote add origin https://github.com/librenms/librenms.git
git fetch --depth 1 origin "refs/tags/v${LIBRENMS_VERSION}" 2>/dev/null ||
git fetch --depth 1 origin "refs/tags/${LIBRENMS_VERSION}" 2>/dev/null || true
git checkout -qf FETCH_HEAD 2>/dev/null || true
chown -R librenms:librenms .git
fi
msg_ok "Configured LibreNMS" msg_ok "Configured LibreNMS"
msg_info "Configure MariaDB" msg_info "Configure MariaDB"
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: nnsense
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/kieraneglin/pinchflat
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 \
build-essential \
elixir \
erlang-dev \
erlang-inets \
erlang-os-mon \
erlang-runtime-tools \
erlang-syntax-tools \
erlang-xmerl \
git \
libsqlite3-dev \
locales \
openssh-client \
openssl \
pipx \
pkg-config \
procps \
python3-mutagen \
zip
msg_ok "Installed Dependencies"
NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs
FFMPEG_TYPE="binary" setup_ffmpeg
setup_hwaccel
fetch_and_deploy_gh_release "deno" "denoland/deno" "prebuild" "latest" "/usr/local/bin" "deno-$(arch_resolve "x86_64" "aarch64")-unknown-linux-gnu.zip"
fetch_and_deploy_gh_release "yt-dlp" "yt-dlp/yt-dlp" "singlefile" "latest" "/usr/local/bin" "yt-dlp_$(arch_resolve "linux" "linux_aarch64")"
msg_info "Installing Apprise"
export PIPX_HOME=/opt/pipx
export PIPX_BIN_DIR=/usr/local/bin
$STD pipx install apprise
msg_ok "Installed Apprise"
fetch_and_deploy_gh_release "pinchflat" "kieraneglin/pinchflat" "tarball" "latest" "/opt/pinchflat-src"
msg_info "Configuring Pinchflat"
CONFIG_PATH="/opt/pinchflat/config"
DOWNLOADS_PATH="/opt/pinchflat/downloads"
mkdir -p \
/etc/elixir_tzdata_data \
/etc/yt-dlp/plugins \
/opt/pinchflat/app \
"$CONFIG_PATH/db" \
"$CONFIG_PATH/extras" \
"$CONFIG_PATH/logs" \
"$CONFIG_PATH/metadata" \
"$DOWNLOADS_PATH"
ln -sfn "$CONFIG_PATH" /config
ln -sfn "$DOWNLOADS_PATH" /downloads
chmod ugo+rw /etc/elixir_tzdata_data /etc/yt-dlp /etc/yt-dlp/plugins
cat <<EOF >/opt/pinchflat/.env
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=en_US.UTF-8
MIX_ENV=prod
PHX_SERVER=true
PORT=8945
RUN_CONTEXT=selfhosted
CONFIG_PATH=${CONFIG_PATH}
MEDIA_PATH=${DOWNLOADS_PATH}
TZ_DATA_PATH=/etc/elixir_tzdata_data
SECRET_KEY_BASE=$(openssl rand -base64 48)
EOF
msg_ok "Configured Pinchflat"
msg_info "Building Pinchflat"
cd /opt/pinchflat-src
export MIX_ENV=prod
export ERL_FLAGS="+JPperf true"
$STD mix local.hex --force
$STD mix local.rebar --force
$STD mix deps.get --only prod
$STD mix deps.compile
$STD yarn --cwd assets install
$STD mix assets.deploy
$STD mix compile
$STD mix release --overwrite
rm -rf /opt/pinchflat/app
cp -r _build/prod/rel/pinchflat /opt/pinchflat/app
msg_ok "Built Pinchflat"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/pinchflat.service
[Unit]
Description=Pinchflat
After=network.target
[Service]
Type=simple
EnvironmentFile=/opt/pinchflat/.env
WorkingDirectory=/opt/pinchflat/app
UMask=0022
ExecStartPre=/opt/pinchflat/app/bin/check_file_permissions
ExecStartPre=/opt/pinchflat/app/bin/migrate
ExecStart=/opt/pinchflat/app/bin/pinchflat start
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now pinchflat
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://snapotter.com
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 \
imagemagick \
ghostscript \
potrace \
libopenjp2-tools \
libegl1 \
libwayland-client0 \
libwayland-cursor0 \
libwayland-egl1 \
libxkbcommon0 \
libxkbcommon-x11-0 \
libxcursor1 \
python3 \
python3-dev \
gcc \
g++
msg_ok "Installed Dependencies"
PYTHON_VERSION="3.11" setup_uv
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
fetch_and_deploy_gh_release "caire" "esimov/caire" "prebuild" "latest" "/usr/local/bin" "caire-*-linux-amd64.tar.gz"
fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "prebuild" "latest" "/opt/snapotter" "snapotter-*-linux-amd64.tar.gz"
msg_info "Setting up Python Environment"
mkdir -p /opt/snapotter_data/ai/models/rembg
$STD uv python install 3.11
$STD uv venv --seed --python 3.11 /opt/snapotter_data/ai/venv
#if [[ -f /opt/snapotter/packages/ai/python/requirements.txt ]]; then
# $STD uv pip install \
# --python /opt/snapotter_data/ai/venv/bin/python \
# -r /opt/snapotter/packages/ai/python/requirements.txt
#fi
ln -sfn /opt/snapotter /app
msg_ok "Set up Python Environment"
msg_info "Configuring SnapOtter"
mkdir -p /opt/snapotter_data/files
mkdir -p /tmp/snapotter-workspace
cat <<EOF >/opt/snapotter_data/.env
PORT=1349
NODE_ENV=production
DB_PATH=/opt/snapotter_data/snapotter.db
WORKSPACE_PATH=/tmp/snapotter-workspace
FILES_STORAGE_PATH=/opt/snapotter_data/files
PYTHON_VENV_PATH=/opt/snapotter_data/ai/venv
MODELS_PATH=/opt/snapotter_data/ai/models
DATA_DIR=/opt/snapotter_data
FEATURE_MANIFEST_PATH=/opt/snapotter/docker/feature-manifest.json
U2NET_HOME=/opt/snapotter_data/ai/models/rembg
AUTH_ENABLED=true
DEFAULT_USERNAME=admin
DEFAULT_PASSWORD=admin
LOG_LEVEL=info
TRUST_PROXY=true
FILE_MAX_AGE_HOURS=72
CLEANUP_INTERVAL_MINUTES=60
ANALYTICS_ENABLED=false
EOF
msg_ok "Configured SnapOtter"
msg_info "Creating Service"
PNPM_BIN="$(command -v pnpm)"
cat <<EOF >/etc/systemd/system/snapotter.service
[Unit]
Description=SnapOtter Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/snapotter
EnvironmentFile=/opt/snapotter_data/.env
ExecStart=${PNPM_BIN} --filter @snapotter/api run start
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now snapotter
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
-182
View File
@@ -1,182 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/wger-project/wger
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 \
build-essential \
nginx \
redis-server \
libpq-dev
msg_ok "Installed Dependencies"
NODE_VERSION="22" NODE_MODULE="sass" setup_nodejs
setup_uv
PG_VERSION="16" setup_postgresql
PG_DB_NAME="wger" PG_DB_USER="wger" setup_postgresql_db
fetch_and_deploy_gh_release "wger" "wger-project/wger" "tarball"
msg_info "Setting up wger"
mkdir -p /opt/wger/{static,media}
chmod o+w /opt/wger/media
cd /opt/wger
$STD npm install
$STD npm run build:css:sass
$STD uv venv
$STD uv pip install . --group docker
SECRET_KEY=$(openssl rand -base64 40)
cat <<EOF >/opt/wger/.env
DJANGO_SETTINGS_MODULE=settings.main
PYTHONPATH=/opt/wger
DJANGO_DB_ENGINE=django.db.backends.postgresql
DJANGO_DB_DATABASE=${PG_DB_NAME}
DJANGO_DB_USER=${PG_DB_USER}
DJANGO_DB_PASSWORD=${PG_DB_PASS}
DJANGO_DB_HOST=localhost
DJANGO_DB_PORT=5432
DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
DJANGO_MEDIA_ROOT=/opt/wger/media
DJANGO_STATIC_ROOT=/opt/wger/static
DJANGO_STATIC_URL=/static/
ALLOWED_HOSTS=${LOCAL_IP},localhost,127.0.0.1
CSRF_TRUSTED_ORIGINS=http://${LOCAL_IP}:3000
USE_X_FORWARDED_HOST=True
SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,http
DJANGO_CACHE_BACKEND=django_redis.cache.RedisCache
DJANGO_CACHE_LOCATION=redis://127.0.0.1:6379/1
DJANGO_CACHE_TIMEOUT=300
DJANGO_CACHE_CLIENT_CLASS=django_redis.client.DefaultClient
AXES_CACHE_ALIAS=default
USE_CELERY=True
CELERY_BROKER=redis://127.0.0.1:6379/2
CELERY_BACKEND=redis://127.0.0.1:6379/2
SITE_URL=http://${LOCAL_IP}:3000
SECRET_KEY=${SECRET_KEY}
EOF
set -a && source /opt/wger/.env && set +a
$STD uv run wger bootstrap
$STD uv run python manage.py collectstatic --no-input
cat <<EOF | uv run python manage.py shell
from django.contrib.auth import get_user_model
User = get_user_model()
user, created = User.objects.get_or_create(
username="admin",
defaults={"email": "admin@localhost"},
)
if created:
user.set_password("${PG_DB_PASS}")
user.is_superuser = True
user.is_staff = True
user.save()
EOF
msg_ok "Set up wger"
msg_info "Creating Config and Services"
cat <<EOF >/etc/systemd/system/wger.service
[Unit]
Description=wger Gunicorn
After=network.target
[Service]
User=root
WorkingDirectory=/opt/wger
EnvironmentFile=/opt/wger/.env
ExecStart=/opt/wger/.venv/bin/gunicorn \
--bind 127.0.0.1:8000 \
--workers 3 \
--threads 2 \
--timeout 120 \
wger.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/celery.service
[Unit]
Description=wger Celery Worker
After=network.target redis-server.service
Requires=redis-server.service
[Service]
WorkingDirectory=/opt/wger
EnvironmentFile=/opt/wger/.env
ExecStart=/opt/wger/.venv/bin/celery -A wger worker -l info
Restart=always
[Install]
WantedBy=multi-user.target
EOF
mkdir -p /var/lib/wger/celery
chmod 700 /var/lib/wger/celery
cat <<EOF >/etc/systemd/system/celery-beat.service
[Unit]
Description=wger Celery Beat
After=network.target redis-server.service
Requires=redis-server.service
[Service]
WorkingDirectory=/opt/wger
EnvironmentFile=/opt/wger/.env
ExecStart=/opt/wger/.venv/bin/celery -A wger beat -l info \
--schedule /var/lib/wger/celery/celerybeat-schedule
Restart=always
[Install]
WantedBy=multi-user.target
EOF
cat <<'EOF' >/etc/nginx/sites-available/wger
server {
listen 3000;
server_name _;
client_max_body_size 20M;
location /static/ {
alias /opt/wger/static/;
expires 30d;
}
location /media/ {
alias /opt/wger/media/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
EOF
$STD rm -f /etc/nginx/sites-enabled/default
$STD ln -sf /etc/nginx/sites-available/wger /etc/nginx/sites-enabled/wger
systemctl enable -q --now redis-server nginx wger celery celery-beat
systemctl restart nginx
msg_ok "Created Config and Services"
motd_ssh
customize
cleanup_lxc
+1
View File
@@ -47,6 +47,7 @@ cat <<EOF >/etc/apache2/sites-available/wordpress.conf
EOF EOF
$STD a2ensite wordpress.conf $STD a2ensite wordpress.conf
$STD a2dissite 000-default.conf $STD a2dissite 000-default.conf
$STD a2enmod rewrite
systemctl reload apache2 systemctl reload apache2
msg_ok "Created Services" msg_ok "Created Services"
+2 -1
View File
@@ -138,6 +138,7 @@ network_check() {
# This function updates the Container OS by running apk upgrade with mirror fallback # This function updates the Container OS by running apk upgrade with mirror fallback
update_os() { update_os() {
msg_info "Updating Container OS" msg_info "Updating Container OS"
configure_http_proxy
if ! $STD apk -U upgrade; then if ! $STD apk -U upgrade; then
msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." msg_warn "apk update failed (dl-cdn.alpinelinux.org), trying alternate mirrors..."
local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" local alpine_mirrors="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org"
@@ -243,7 +244,7 @@ EOF
msg_ok "Customized Container" msg_ok "Customized Container"
fi fi
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/'"${app}"'.sh)"' >/usr/bin/update
chmod +x /usr/bin/update chmod +x /usr/bin/update
post_progress_to_api post_progress_to_api
} }
+198 -34
View File
@@ -519,6 +519,19 @@ validate_bridge() {
return 0 return 0
} }
# ------------------------------------------------------------------------------
# validate_sdn_vnet()
#
# - Validates that an SDN vnet exists in the cluster config
# ------------------------------------------------------------------------------
validate_sdn_vnet() {
local vnet="$1"
[[ -z "$vnet" ]] && return 1
[[ -f /etc/pve/sdn/vnets.cfg ]] && grep -qE "^vnet:[[:space:]]*${vnet}([[:space:]]|$)" /etc/pve/sdn/vnets.cfg && return 0
command -v pvesh &>/dev/null && pvesh get "/cluster/sdn/vnets/${vnet}" &>/dev/null && return 0
return 1
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# validate_gateway_in_subnet() # validate_gateway_in_subnet()
# #
@@ -964,6 +977,7 @@ base_settings() {
HN="$requested_hostname" HN="$requested_hostname"
BRG=${var_brg:-"vmbr0"} BRG=${var_brg:-"vmbr0"}
SDN_VNET=${var_sdn_vnet:-""}
NET=${var_net:-"dhcp"} NET=${var_net:-"dhcp"}
# Resolve IP range if NET contains a range (e.g., 192.168.1.100/24-192.168.1.200/24) # Resolve IP range if NET contains a range (e.g., 192.168.1.100/24-192.168.1.200/24)
@@ -1018,6 +1032,9 @@ base_settings() {
fi fi
fi fi
HTTP_PROXY="${var_http_proxy:-}"
HTTP_NO_PROXY="${var_http_no_proxy:-}"
MTU=${var_mtu:-""} MTU=${var_mtu:-""}
_sd_val="${var_searchdomain:-""}" _sd_val="${var_searchdomain:-""}"
[[ -n "$_sd_val" ]] && SD="-searchdomain=$_sd_val" || SD="" [[ -n "$_sd_val" ]] && SD="-searchdomain=$_sd_val" || SD=""
@@ -1071,11 +1088,11 @@ load_vars_file() {
# Allowed var_* keys # Allowed var_* keys
local VAR_WHITELIST=( local VAR_WHITELIST=(
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_http_no_proxy var_http_proxy var_keyctl
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
var_post_install var_post_install var_sdn_vnet
) )
# Whitelist check helper # Whitelist check helper
@@ -1250,6 +1267,24 @@ load_vars_file() {
continue continue
fi fi
;; ;;
var_sdn_vnet)
if [[ -n "$var_val" ]] && ! validate_sdn_vnet "$var_val"; then
msg_warn "SDN vnet '$var_val' from $file not found, ignoring"
continue
fi
;;
var_http_proxy)
if [[ -n "$var_val" ]] && ! [[ "$var_val" =~ ^https?://[^[:space:]]+(:[0-9]+)?/?$ ]]; then
msg_warn "Invalid HTTP proxy URL '$var_val' in $file, ignoring"
continue
fi
;;
var_http_no_proxy)
if [[ -n "$var_val" ]] && [[ ! "$var_val" =~ ^[a-zA-Z0-9.*_:/-]+(,[a-zA-Z0-9.*_:/-]+)*$ ]]; then
msg_warn "Invalid no_proxy value '$var_val' in $file, ignoring"
continue
fi
;;
var_container_storage | var_template_storage) var_container_storage | var_template_storage)
# Validate that the storage exists and is active on the current node # Validate that the storage exists and is active on the current node
local _storage_status local _storage_status
@@ -1289,11 +1324,11 @@ default_var_settings() {
# Allowed var_* keys (alphabetically sorted) # Allowed var_* keys (alphabetically sorted)
# Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique) # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)
local VAR_WHITELIST=( local VAR_WHITELIST=(
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_http_no_proxy var_http_proxy var_keyctl
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
var_post_install var_post_install var_sdn_vnet
) )
# Snapshot: environment variables (highest precedence) # Snapshot: environment variables (highest precedence)
@@ -1369,6 +1404,10 @@ var_ssh=no
# var_apt_cacher_ip=http://proxy.local # var_apt_cacher_ip=http://proxy.local
# var_apt_cacher_ip=https://proxy.local:443 # var_apt_cacher_ip=https://proxy.local:443
# HTTP/HTTPS proxy (optional - for networks requiring a proxy)
# var_http_proxy=http://proxy.local:8080
# var_http_no_proxy=localhost,127.0.0.1,.local
# Features/Tags/verbosity # Features/Tags/verbosity
var_fuse=no var_fuse=no
var_tun=no var_tun=no
@@ -1468,11 +1507,11 @@ get_app_defaults_path() {
if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then 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) # Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)
declare -ag VAR_WHITELIST=( declare -ag VAR_WHITELIST=(
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_http_no_proxy var_http_proxy var_keyctl
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
var_post_install var_post_install var_sdn_vnet
) )
fi fi
@@ -1616,6 +1655,8 @@ _build_current_app_vars_tmp() {
_ssh_auth="${SSH_AUTHORIZED_KEY:-}" _ssh_auth="${SSH_AUTHORIZED_KEY:-}"
_apt_cacher="${APT_CACHER:-}" _apt_cacher="${APT_CACHER:-}"
_apt_cacher_ip="${APT_CACHER_IP:-}" _apt_cacher_ip="${APT_CACHER_IP:-}"
_http_proxy="${HTTP_PROXY:-${var_http_proxy:-}}"
_http_no_proxy="${HTTP_NO_PROXY:-${var_http_no_proxy:-}}"
_fuse="${ENABLE_FUSE:-no}" _fuse="${ENABLE_FUSE:-no}"
_tun="${ENABLE_TUN:-no}" _tun="${ENABLE_TUN:-no}"
_gpu="${ENABLE_GPU:-no}" _gpu="${ENABLE_GPU:-no}"
@@ -1667,6 +1708,8 @@ _build_current_app_vars_tmp() {
[ -n "$_apt_cacher" ] && echo "var_apt_cacher=$(_sanitize_value "$_apt_cacher")" [ -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 "$_apt_cacher_ip" ] && echo "var_apt_cacher_ip=$(_sanitize_value "$_apt_cacher_ip")"
[ -n "$_http_proxy" ] && echo "var_http_proxy=$(_sanitize_value "$_http_proxy")"
[ -n "$_http_no_proxy" ] && echo "var_http_no_proxy=$(_sanitize_value "$_http_no_proxy")"
[ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")" [ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")"
[ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")" [ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")"
@@ -1682,6 +1725,7 @@ _build_current_app_vars_tmp() {
[ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")" [ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")"
[ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")" [ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")"
[ -n "${var_sdn_vnet:-}" ] && echo "var_sdn_vnet=$(_sanitize_value "${var_sdn_vnet}")"
[ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")" [ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")"
[ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")" [ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")"
@@ -1830,7 +1874,7 @@ advanced_settings() {
TAGS="community-script${var_tags:+;${var_tags}}" TAGS="community-script${var_tags:+;${var_tags}}"
fi fi
local STEP=1 local STEP=1
local MAX_STEP=29 local MAX_STEP=30
# Store values for back navigation - inherit from var_* app defaults # Store values for back navigation - inherit from var_* app defaults
local _ct_type="${var_unprivileged:-1}" local _ct_type="${var_unprivileged:-1}"
@@ -1842,6 +1886,7 @@ advanced_settings() {
local _core_count="${var_cpu:-1}" local _core_count="${var_cpu:-1}"
local _ram_size="${var_ram:-1024}" local _ram_size="${var_ram:-1024}"
local _bridge="${var_brg:-vmbr0}" local _bridge="${var_brg:-vmbr0}"
local _sdn_vnet="${var_sdn_vnet:-}"
local _net="${var_net:-dhcp}" local _net="${var_net:-dhcp}"
local _gate="${var_gateway:-}" local _gate="${var_gateway:-}"
local _ipv6_method="${var_ipv6_method:-auto}" local _ipv6_method="${var_ipv6_method:-auto}"
@@ -1849,6 +1894,8 @@ advanced_settings() {
local _ipv6_gate="" local _ipv6_gate=""
local _apt_cacher="${var_apt_cacher:-no}" local _apt_cacher="${var_apt_cacher:-no}"
local _apt_cacher_ip="${var_apt_cacher_ip:-}" local _apt_cacher_ip="${var_apt_cacher_ip:-}"
local _http_proxy="${var_http_proxy:-}"
local _http_no_proxy="${var_http_no_proxy:-}"
local _mtu="${var_mtu:-}" local _mtu="${var_mtu:-}"
local _sd="${var_searchdomain:-}" local _sd="${var_searchdomain:-}"
local _ns="${var_ns:-}" local _ns="${var_ns:-}"
@@ -1921,6 +1968,11 @@ advanced_settings() {
fi fi
done <<<"$BRIDGES" done <<<"$BRIDGES"
fi fi
if [[ -f /etc/pve/sdn/vnets.cfg ]]; then
while IFS= read -r vnet; do
[[ -n "$vnet" ]] && BRIDGE_MENU_OPTIONS+=("sdn:${vnet}" "[SDN] ${vnet}")
done < <(awk '/^vnet:/{print $2}' /etc/pve/sdn/vnets.cfg 2>/dev/null)
fi
} }
_detect_bridges _detect_bridges
@@ -2153,8 +2205,18 @@ advanced_settings() {
if [[ "$bridge_test" == "__other__" || "$bridge_test" == -* ]]; then if [[ "$bridge_test" == "__other__" || "$bridge_test" == -* ]]; then
continue continue
fi fi
if validate_bridge "$bridge_test"; then if [[ "$bridge_test" == sdn:* ]]; then
local vnet_test="${bridge_test#sdn:}"
if validate_sdn_vnet "$vnet_test"; then
_sdn_vnet="$vnet_test"
_bridge="${var_brg:-vmbr0}"
((STEP++))
else
whiptail --msgbox "SDN vnet '$vnet_test' is not configured on this cluster." 8 58
fi
elif validate_bridge "$bridge_test"; then
_bridge="$bridge_test" _bridge="$bridge_test"
_sdn_vnet=""
((STEP++)) ((STEP++))
else else
whiptail --msgbox "Bridge '$bridge_test' is not available or not active." 8 58 whiptail --msgbox "Bridge '$bridge_test' is not available or not active." 8 58
@@ -2626,9 +2688,46 @@ advanced_settings() {
;; ;;
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
# STEP 24: Container Timezone # STEP 24: HTTP/HTTPS Proxy
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
24) 24)
local http_proxy_default_flag="--defaultno"
[[ -n "$_http_proxy" ]] && http_proxy_default_flag=""
if whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "HTTP/HTTPS PROXY" \
--ok-button "Next" --cancel-button "Back" \
$http_proxy_default_flag \
--yesno "\nUse HTTP/HTTPS proxy?\n\nRequired when internet access must go through a proxy server.\n(e.g. corporate networks)\n\n(App default: ${var_http_proxy:-none})" 14 62; then
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "HTTP PROXY URL" \
--inputbox "\nEnter HTTP proxy URL:\n(e.g. http://proxy.local:8080)" 12 62 "$_http_proxy" \
3>&1 1>&2 2>&3); then
_http_proxy="$result"
local _no_proxy_default="${_http_no_proxy:-localhost,127.0.0.1,.local}"
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "NO_PROXY EXCEPTIONS" \
--inputbox "\nEnter NO_PROXY exceptions (comma-separated):\nLeave empty for defaults." 12 62 "$_no_proxy_default" \
3>&1 1>&2 2>&3); then
_http_no_proxy="$result"
fi
fi
else
if [ $? -eq 1 ]; then
_http_proxy=""
_http_no_proxy=""
else
((STEP--))
continue
fi
fi
((STEP++))
;;
# ═══════════════════════════════════════════════════════════════════════════
# STEP 25: Container Timezone
# ═══════════════════════════════════════════════════════════════════════════
25)
local tz_hint="$_ct_timezone" local tz_hint="$_ct_timezone"
[[ -z "$tz_hint" ]] && tz_hint="(empty - will use host timezone)" [[ -z "$tz_hint" ]] && tz_hint="(empty - will use host timezone)"
@@ -2651,9 +2750,9 @@ advanced_settings() {
;; ;;
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
# STEP 25: Container Protection # STEP 26: Container Protection
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
25) 26)
local protect_default_flag="--defaultno" local protect_default_flag="--defaultno"
[[ "$_protect_ct" == "yes" || "$_protect_ct" == "1" ]] && protect_default_flag="" [[ "$_protect_ct" == "yes" || "$_protect_ct" == "1" ]] && protect_default_flag=""
@@ -2675,9 +2774,9 @@ advanced_settings() {
;; ;;
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
# STEP 26: Device Node Creation (mknod) # STEP 27: Device Node Creation (mknod)
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
26) 27)
local mknod_default_flag="--defaultno" local mknod_default_flag="--defaultno"
[[ "$_enable_mknod" == "1" ]] && mknod_default_flag="" [[ "$_enable_mknod" == "1" ]] && mknod_default_flag=""
@@ -2699,9 +2798,9 @@ advanced_settings() {
;; ;;
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
# STEP 27: Mount Filesystems # STEP 28: Mount Filesystems
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
27) 28)
local mount_hint="" local mount_hint=""
[[ -n "$_mount_fs" ]] && mount_hint="$_mount_fs" || mount_hint="(none)" [[ -n "$_mount_fs" ]] && mount_hint="$_mount_fs" || mount_hint="(none)"
@@ -2722,9 +2821,9 @@ advanced_settings() {
;; ;;
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
# STEP 28: Optional host-side post-install hook (path on the Proxmox HOST) # STEP 29: Optional host-side post-install hook (path on the Proxmox HOST)
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
28) 29)
local _hook_prompt="Optional: absolute path to a *.sh file ON THE PROXMOX HOST. local _hook_prompt="Optional: absolute path to a *.sh file ON THE PROXMOX HOST.
It runs as root on the HOST (NOT in the LXC) after the container It runs as root on the HOST (NOT in the LXC) after the container
@@ -2774,9 +2873,9 @@ Leave empty to skip."
;; ;;
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
# STEP 29: Verbose Mode & Confirmation # STEP 30: Verbose Mode & Confirmation
# ═══════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════
29) 30)
local verbose_default_flag="--defaultno" local verbose_default_flag="--defaultno"
[[ "$_verbose" == "yes" ]] && verbose_default_flag="" [[ "$_verbose" == "yes" ]] && verbose_default_flag=""
@@ -2804,6 +2903,7 @@ Leave empty to skip."
local tz_display="${_ct_timezone:-Host TZ}" local tz_display="${_ct_timezone:-Host TZ}"
local apt_display="${_apt_cacher:-no}" local apt_display="${_apt_cacher:-no}"
[[ "$_apt_cacher" == "yes" && -n "$_apt_cacher_ip" ]] && apt_display="$_apt_cacher_ip" [[ "$_apt_cacher" == "yes" && -n "$_apt_cacher_ip" ]] && apt_display="$_apt_cacher_ip"
local http_proxy_display="${_http_proxy:-(none)}"
local post_install_display="${_post_install:-(none)}" local post_install_display="${_post_install:-(none)}"
local post_install_warn="" local post_install_warn=""
@@ -2833,6 +2933,7 @@ Features:
Advanced: Advanced:
Timezone: $tz_display Timezone: $tz_display
APT Cacher: $apt_display APT Cacher: $apt_display
HTTP Proxy: $http_proxy_display
Verbose: $_verbose Verbose: $_verbose
Post-Install Script: ${post_install_display}${post_install_warn}" Post-Install Script: ${post_install_display}${post_install_warn}"
@@ -2876,6 +2977,8 @@ Advanced:
CT_TIMEZONE="$_ct_timezone" CT_TIMEZONE="$_ct_timezone"
APT_CACHER="$_apt_cacher" APT_CACHER="$_apt_cacher"
APT_CACHER_IP="$_apt_cacher_ip" APT_CACHER_IP="$_apt_cacher_ip"
HTTP_PROXY="$_http_proxy"
HTTP_NO_PROXY="$_http_no_proxy"
VERBOSE="$_verbose" VERBOSE="$_verbose"
var_post_install="$_post_install" var_post_install="$_post_install"
@@ -2891,6 +2994,9 @@ Advanced:
var_timezone="$_ct_timezone" var_timezone="$_ct_timezone"
var_apt_cacher="$_apt_cacher" var_apt_cacher="$_apt_cacher"
var_apt_cacher_ip="$_apt_cacher_ip" var_apt_cacher_ip="$_apt_cacher_ip"
var_sdn_vnet="$_sdn_vnet"
var_http_proxy="$_http_proxy"
var_http_no_proxy="$_http_no_proxy"
# Format optional values # Format optional values
[[ -n "$_mtu" ]] && MTU=",mtu=$_mtu" || MTU="" [[ -n "$_mtu" ]] && MTU=",mtu=$_mtu" || MTU=""
@@ -2928,6 +3034,7 @@ Advanced:
[[ "${PROTECT_CT:-no}" == "yes" || "${PROTECT_CT:-no}" == "1" ]] && echo -e "${CONTAINERTYPE}${BOLD}${DGN}Protection: ${BGN}Enabled${CL}" [[ "${PROTECT_CT:-no}" == "yes" || "${PROTECT_CT:-no}" == "1" ]] && echo -e "${CONTAINERTYPE}${BOLD}${DGN}Protection: ${BGN}Enabled${CL}"
[[ -n "${CT_TIMEZONE:-}" ]] && echo -e "${INFO}${BOLD}${DGN}Timezone: ${BGN}$CT_TIMEZONE${CL}" [[ -n "${CT_TIMEZONE:-}" ]] && echo -e "${INFO}${BOLD}${DGN}Timezone: ${BGN}$CT_TIMEZONE${CL}"
[[ "$APT_CACHER" == "yes" ]] && echo -e "${INFO}${BOLD}${DGN}APT Cacher: ${BGN}$APT_CACHER_IP${CL}" [[ "$APT_CACHER" == "yes" ]] && echo -e "${INFO}${BOLD}${DGN}APT Cacher: ${BGN}$APT_CACHER_IP${CL}"
[[ -n "${HTTP_PROXY:-}" ]] && echo -e "${INFO}${BOLD}${DGN}HTTP Proxy: ${BGN}$HTTP_PROXY${CL}"
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}" echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
echo -e "${CREATING}${BOLD}${RD}Creating an LXC of ${APP} using the above advanced settings${CL}" echo -e "${CREATING}${BOLD}${RD}Creating an LXC of ${APP} using the above advanced settings${CL}"
@@ -3645,7 +3752,6 @@ run_addon_updates() {
done done
} }
runtime_script_status_guard() { runtime_script_status_guard() {
local script_slug="${SCRIPT_SLUG:-${NSAPP:-}}" local script_slug="${SCRIPT_SLUG:-${NSAPP:-}}"
script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')" script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
@@ -3765,6 +3871,47 @@ start() {
# SECTION 8: CONTAINER CREATION & DEPLOYMENT # SECTION 8: CONTAINER CREATION & DEPLOYMENT
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------
# _apply_http_proxy_in_container()
#
# - Writes persistent proxy settings into the LXC before base package install
# - Ensures apt/apk/curl work behind corporate proxies during early setup
# ------------------------------------------------------------------------------
_apply_http_proxy_in_container() {
local proxy="${HTTP_PROXY:-}"
local noproxy="${HTTP_NO_PROXY:-}"
[[ -z "$proxy" || -z "${CTID:-}" ]] && return 0
[[ -z "$noproxy" ]] && noproxy="localhost,127.0.0.1,.local"
msg_info "Applying HTTP proxy in container"
local tmpf
tmpf="$(mktemp)"
cat >"$tmpf" <<EOF
export http_proxy="$proxy"
export https_proxy="$proxy"
export HTTP_PROXY="$proxy"
export HTTPS_PROXY="$proxy"
export no_proxy="$noproxy"
export NO_PROXY="$noproxy"
EOF
pct push "$CTID" "$tmpf" /etc/profile.d/90-http-proxy.sh >/dev/null 2>&1 || {
rm -f "$tmpf"
msg_warn "Failed to push HTTP proxy profile into container"
return 0
}
pct exec "$CTID" -- chmod 644 /etc/profile.d/90-http-proxy.sh >/dev/null 2>&1 || true
if [[ "$var_os" != "alpine" && "${APT_CACHER:-}" != "yes" ]]; then
cat >"$tmpf" <<EOF
Acquire::http::Proxy "$proxy";
Acquire::https::Proxy "$proxy";
EOF
pct push "$CTID" "$tmpf" /etc/apt/apt.conf.d/95http-proxy >/dev/null 2>&1 || true
fi
rm -f "$tmpf"
msg_ok "Applied HTTP proxy in container"
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# build_container() # build_container()
# #
@@ -3782,6 +3929,9 @@ build_container() {
# if [ "$VERBOSE" == "yes" ]; then set -x; fi # if [ "$VERBOSE" == "yes" ]; then set -x; fi
NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}" NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}"
if [[ -n "${var_sdn_vnet:-${SDN_VNET:-}}" ]]; then
NET_STRING="-net0 name=eth0,vnet=${var_sdn_vnet:-$SDN_VNET}"
fi
# MAC # MAC
if [[ -n "$MAC" ]]; then if [[ -n "$MAC" ]]; then
@@ -3891,6 +4041,12 @@ build_container() {
export SESSION_ID="$SESSION_ID" export SESSION_ID="$SESSION_ID"
export CACHER="$APT_CACHER" export CACHER="$APT_CACHER"
export CACHER_IP="$APT_CACHER_IP" export CACHER_IP="$APT_CACHER_IP"
if [[ -n "${HTTP_PROXY:-}" ]]; then
export HTTP_PROXY HTTPS_PROXY="$HTTP_PROXY" http_proxy="$HTTP_PROXY" https_proxy="$HTTP_PROXY"
export NO_PROXY="${HTTP_NO_PROXY:-}" no_proxy="${HTTP_NO_PROXY:-}"
export var_http_proxy="$HTTP_PROXY"
export var_http_no_proxy="${HTTP_NO_PROXY:-}"
fi
export tz="$timezone" export tz="$timezone"
export APPLICATION="$APP" export APPLICATION="$APP"
export app="$NSAPP" export app="$NSAPP"
@@ -4375,6 +4531,8 @@ EOF
local install_exit_code=0 local install_exit_code=0
_apply_http_proxy_in_container
# Continue with standard container setup # Continue with standard container setup
if [ "$var_os" == "alpine" ]; then if [ "$var_os" == "alpine" ]; then
sleep 3 sleep 3
@@ -4382,10 +4540,13 @@ EOF
https://dl-cdn.alpinelinux.org/alpine/latest-stable/main https://dl-cdn.alpinelinux.org/alpine/latest-stable/main
https://dl-cdn.alpinelinux.org/alpine/latest-stable/community https://dl-cdn.alpinelinux.org/alpine/latest-stable/community
EOF' EOF'
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq" >>"$BUILD_LOG" 2>&1 || { pct exec "$CTID" -- ash -c 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; apk add bash newt curl openssh nano mc ncurses jq' >>"$BUILD_LOG" 2>&1 || {
msg_warn "apk install failed (dl-cdn.alpinelinux.org), trying alternate mirrors..." msg_warn "apk install failed (dl-cdn.alpinelinux.org), trying alternate mirrors..."
local alpine_exit=0 local alpine_exit=0
pct exec "$CTID" -- ash -c ' pct exec "$CTID" -- ash -c '
set -a
[ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh
set +a
ALPINE_MIRRORS="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org" ALPINE_MIRRORS="mirror.init7.net ftp.halifax.rwth-aachen.de mirrors.edge.kernel.org alpine.mirror.wearetriple.com mirror.leaseweb.com uk.alpinelinux.org dl-2.alpinelinux.org dl-4.alpinelinux.org"
for m in $(printf "%s\n" $ALPINE_MIRRORS | shuf); do for m in $(printf "%s\n" $ALPINE_MIRRORS | shuf); do
if wget -q --spider --timeout=2 "http://$m/alpine/latest-stable/main/" 2>/dev/null; then if wget -q --spider --timeout=2 "http://$m/alpine/latest-stable/main/" 2>/dev/null; then
@@ -4444,13 +4605,16 @@ EOF
pct exec "$CTID" -- bash -c "echo -e 'nameserver 8.8.8.8\nnameserver 1.1.1.1' >/etc/resolv.conf" pct exec "$CTID" -- bash -c "echo -e 'nameserver 8.8.8.8\nnameserver 1.1.1.1' >/etc/resolv.conf"
fi fi
pct exec "$CTID" -- bash -c "apt-get update 2>&1 && apt-get install -y ${_base_pkgs} 2>&1" >>"$BUILD_LOG" 2>&1 || { pct exec "$CTID" -- bash -c "set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; apt-get update 2>&1 && apt-get install -y ${_base_pkgs} 2>&1" >>"$BUILD_LOG" 2>&1 || {
local failed_mirror local failed_mirror
failed_mirror=$(pct exec "$CTID" -- bash -c "grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null" 2>/dev/null || echo "unknown") failed_mirror=$(pct exec "$CTID" -- bash -c "grep -m1 -oP '(?<=URIs: https?://)[^/]+' /etc/apt/sources.list.d/debian.sources 2>/dev/null || grep -m1 -oP '(?<=deb https?://)[^/]+' /etc/apt/sources.list 2>/dev/null" 2>/dev/null || echo "unknown")
msg_warn "apt-get update failed (${failed_mirror})." msg_warn "apt-get update failed (${failed_mirror})."
msg_custom "️" "${YW}" "Probing alternate mirrors (this can take 1-2 minutes on network issues)" msg_custom "️" "${YW}" "Probing alternate mirrors (this can take 1-2 minutes on network issues)"
local mirror_exit=0 local mirror_exit=0
pct exec "$CTID" -- env APT_BASE="$_base_pkgs" bash -c ' pct exec "$CTID" -- env APT_BASE="$_base_pkgs" bash -c '
set -a
[ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh
set +a
DISTRO=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian") DISTRO=$(. /etc/os-release 2>/dev/null && echo "$ID" || echo "debian")
echo " Mirror fallback for distro: $DISTRO" echo " Mirror fallback for distro: $DISTRO"
@@ -5472,10 +5636,10 @@ check_storage_health() {
[[ -z "$total_kb" || "$total_kb" == "0" || -z "$avail_kb" ]] && return 0 [[ -z "$total_kb" || "$total_kb" == "0" || -z "$avail_kb" ]] && return 0
used_pct=$(( (total_kb - avail_kb) * 100 / total_kb )) used_pct=$(((total_kb - avail_kb) * 100 / total_kb))
free_gb_fmt=$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f "$avail_kb" 2>/dev/null || echo "${avail_kb}KB") free_gb_fmt=$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f "$avail_kb" 2>/dev/null || echo "${avail_kb}KB")
if (( used_pct >= 95 )); then if ((used_pct >= 95)); then
msg_warn "Storage '${storage}' (${storage_type}) is ${used_pct}% full (${free_gb_fmt} free)" msg_warn "Storage '${storage}' (${storage_type}) is ${used_pct}% full (${free_gb_fmt} free)"
if ! is_unattended && command -v whiptail >/dev/null 2>&1 && [[ -t 0 ]]; then if ! is_unattended && command -v whiptail >/dev/null 2>&1 && [[ -t 0 ]]; then
if ! whiptail --backtitle "Proxmox VE Helper Scripts" \ if ! whiptail --backtitle "Proxmox VE Helper Scripts" \
@@ -5484,11 +5648,11 @@ check_storage_health() {
msg_error "Installation cancelled storage nearly full" msg_error "Installation cancelled storage nearly full"
exit 214 exit 214
fi fi
elif (( used_pct >= 98 )); then elif ((used_pct >= 98)); then
msg_error "Storage '${storage}' is ${used_pct}% full refusing install in unattended mode" msg_error "Storage '${storage}' is ${used_pct}% full refusing install in unattended mode"
exit 214 exit 214
fi fi
elif (( used_pct >= 85 )); then elif ((used_pct >= 85)); then
msg_warn "Storage '${storage}' (${storage_type}) is ${used_pct}% full (${free_gb_fmt} free)" msg_warn "Storage '${storage}' (${storage_type}) is ${used_pct}% full (${free_gb_fmt} free)"
fi fi
@@ -5827,17 +5991,17 @@ create_lxc_container() {
# Maps OS type + version to the release variant name used by ARM64 template sources. # Maps OS type + version to the release variant name used by ARM64 template sources.
arm64_template_variant() { arm64_template_variant() {
case "$1:$2" in case "$1:$2" in
debian:12) echo "bookworm" ;; debian:12) echo "bookworm" ;;
debian:13) echo "trixie" ;; debian:13) echo "trixie" ;;
debian:) echo "$DEBIAN_DEFAULT_CODENAME" ;; debian:) echo "$DEBIAN_DEFAULT_CODENAME" ;;
ubuntu:24.04) echo "noble" ;; ubuntu:24.04) echo "noble" ;;
ubuntu:26.04) echo "questing" ;; ubuntu:26.04) echo "questing" ;;
ubuntu:) echo "$UBUNTU_DEFAULT_CODENAME" ;; ubuntu:) echo "$UBUNTU_DEFAULT_CODENAME" ;;
alpine:*) echo "${2:-$ALPINE_DEFAULT_VERSION}" ;; alpine:*) echo "${2:-$ALPINE_DEFAULT_VERSION}" ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
+47
View File
@@ -970,6 +970,53 @@ is_verbose_mode() {
[[ "$verbose" != "no" ]] [[ "$verbose" != "no" ]]
} }
# ------------------------------------------------------------------------------
# configure_http_proxy()
#
# - Applies var_http_proxy / var_http_no_proxy inside the container
# - Persists proxy env for login shells, systemd, and package managers
# - Skips APT proxy config when APT Cacher-NG is active
# ------------------------------------------------------------------------------
configure_http_proxy() {
local proxy="${HTTP_PROXY:-${http_proxy:-${var_http_proxy:-}}}"
local noproxy="${HTTP_NO_PROXY:-${no_proxy:-${var_http_no_proxy:-}}}"
[[ -z "$proxy" ]] && return 0
[[ -z "$noproxy" ]] && noproxy="localhost,127.0.0.1,.local"
msg_info "Configuring HTTP proxy"
cat >/etc/profile.d/90-http-proxy.sh <<EOF
export http_proxy="$proxy"
export https_proxy="$proxy"
export HTTP_PROXY="$proxy"
export HTTPS_PROXY="$proxy"
export no_proxy="$noproxy"
export NO_PROXY="$noproxy"
EOF
chmod 644 /etc/profile.d/90-http-proxy.sh
if ! grep -q '^http_proxy=' /etc/environment 2>/dev/null; then
cat >>/etc/environment <<EOF
http_proxy=$proxy
https_proxy=$proxy
HTTP_PROXY=$proxy
HTTPS_PROXY=$proxy
no_proxy=$noproxy
NO_PROXY=$noproxy
EOF
fi
if [[ "${CACHER:-}" != "yes" && -d /etc/apt/apt.conf.d ]]; then
cat >/etc/apt/apt.conf.d/95http-proxy <<EOF
Acquire::http::Proxy "$proxy";
Acquire::https::Proxy "$proxy";
EOF
fi
export http_proxy="$proxy" https_proxy="$proxy" HTTP_PROXY="$proxy" HTTPS_PROXY="$proxy"
export no_proxy="$noproxy" NO_PROXY="$noproxy"
msg_ok "Configured HTTP proxy"
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# is_unattended() # is_unattended()
# #
+3 -2
View File
@@ -388,7 +388,8 @@ apt_update_safe() {
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
update_os() { update_os() {
msg_info "Updating Container OS" msg_info "Updating Container OS"
if [[ "$CACHER" == "yes" ]]; then configure_http_proxy
if [[ "$CACHER" == "yes" && -z "${HTTP_PROXY:-${http_proxy:-}}" ]]; then
echo 'Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";' >/etc/apt/apt.conf.d/00aptproxy echo 'Acquire::http::Proxy-Auto-Detect "/usr/local/bin/apt-proxy-detect.sh";' >/etc/apt/apt.conf.d/00aptproxy
local _proxy_raw="${CACHER_IP}" local _proxy_raw="${CACHER_IP}"
local _proxy_host _proxy_port _proxy_url local _proxy_host _proxy_port _proxy_url
@@ -509,7 +510,7 @@ EOF
systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')" systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')"
msg_ok "Customized Container" msg_ok "Customized Container"
fi fi
echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update echo 'set -a; [ -f /etc/profile.d/90-http-proxy.sh ] && . /etc/profile.d/90-http-proxy.sh; set +a; bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/'"${app}"'.sh)"' >/usr/bin/update
chmod +x /usr/bin/update chmod +x /usr/bin/update
if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then
+549 -45
View File
@@ -1,5 +1,5 @@
# Copyright (c) 2021-2026 community-scripts ORG # Copyright (c) 2021-2026 community-scripts ORG
# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/LICENSE # License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
set -euo pipefail set -euo pipefail
SPINNER_PID="" SPINNER_PID=""
@@ -14,9 +14,18 @@ declare -A MSG_INFO_SHOWN
[[ -n "${_CORE_FUNC_LOADED:-}" ]] && return [[ -n "${_CORE_FUNC_LOADED:-}" ]] && return
_CORE_FUNC_LOADED=1 _CORE_FUNC_LOADED=1
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main}"
load_api_functions() {
if ! declare -f post_to_api_vm >/dev/null 2>&1; then
source /dev/stdin <<<$(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/api.func")
fi
}
load_functions() { load_functions() {
[[ -n "${__FUNCTIONS_LOADED:-}" ]] && return [[ -n "${__FUNCTIONS_LOADED:-}" ]] && return
__FUNCTIONS_LOADED=1 __FUNCTIONS_LOADED=1
load_api_functions
color color
formatting formatting
icons icons
@@ -31,18 +40,24 @@ load_functions() {
arch_check arch_check
} }
load_cloud_init_functions() {
if ! declare -f setup_cloud_init >/dev/null 2>&1; then
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/cloud-init.func") 2>/dev/null || true
fi
}
# Function to download & save header files # Function to download & save header files
get_header() { get_header() {
local app_name=$(echo "${APP,,}" | tr ' ' '-') local app_name=$(echo "${APP,,}" | tr ' ' '-')
local app_type=${APP_TYPE:-vm} local app_type=${APP_TYPE:-vm}
local header_url="https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/${app_type}/headers/${app_name}" local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}"
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}" local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
mkdir -p "$(dirname "$local_header_path")" mkdir -p "$(dirname "$local_header_path")"
if [ ! -s "$local_header_path" ]; then if [ ! -s "$local_header_path" ]; then
if ! curl -fsSL "$header_url" -o "$local_header_path"; then if ! curl -fsSL "$header_url" -o "$local_header_path"; then
return 250 return 1
fi fi
fi fi
@@ -98,6 +113,7 @@ icons() {
DNSOK="✔️ " DNSOK="✔️ "
DNSFAIL="${TAB}✖️${TAB}" DNSFAIL="${TAB}✖️${TAB}"
INFO="${TAB}💡${TAB}${CL}" INFO="${TAB}💡${TAB}${CL}"
CLOUD="${TAB}☁️${TAB}${CL}"
OS="${TAB}🖥️${TAB}${CL}" OS="${TAB}🖥️${TAB}${CL}"
OSVERSION="${TAB}🌟${TAB}${CL}" OSVERSION="${TAB}🌟${TAB}${CL}"
CONTAINERTYPE="${TAB}📦${TAB}${CL}" CONTAINERTYPE="${TAB}📦${TAB}${CL}"
@@ -188,18 +204,32 @@ silent() {
trap 'error_handler' ERR trap 'error_handler' ERR
if [[ $rc -ne 0 ]]; then if [[ $rc -ne 0 ]]; then
# Return instead of exit so that callers can use `$STD cmd || true` # Source explain_exit_code if needed
# When no || is used, set -e + ERR trap catches it via error_handler() if ! declare -f explain_exit_code >/dev/null 2>&1; then
export _SILENT_FAILED_RC="$rc" source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/error_handler.func") 2>/dev/null || true
export _SILENT_FAILED_CMD="$cmd" fi
export _SILENT_FAILED_LINE="$caller_line"
export _SILENT_FAILED_LOG="$logfile"
return "$rc" local explanation=""
if declare -f explain_exit_code >/dev/null 2>&1; then
explanation="$(explain_exit_code "$rc")"
fi
printf "\e[?25h"
if [[ -n "$explanation" ]]; then
msg_error "in line ${caller_line}: exit code ${rc} (${explanation})"
else
msg_error "in line ${caller_line}: exit code ${rc}"
fi
msg_custom "→" "${YWB}" "${cmd}"
if [[ -s "$logfile" ]]; then
echo -e "\n${TAB}--- Last 20 lines of log ---"
tail -n 20 "$logfile"
echo -e "${TAB}----------------------------\n"
fi
exit "$rc"
fi fi
# Clear stale flags on success
unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE _SILENT_FAILED_LOG 2>/dev/null || true
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -230,7 +260,7 @@ curl_handler() {
if [[ -z "$url" ]]; then if [[ -z "$url" ]]; then
msg_error "no valid url or option entered for curl_handler" msg_error "no valid url or option entered for curl_handler"
exit 64 exit 1
fi fi
$STD msg_info "Fetching: $url" $STD msg_info "Fetching: $url"
@@ -259,7 +289,7 @@ curl_handler() {
rm -f /tmp/curl_error.log rm -f /tmp/curl_error.log
fi fi
__curl_err_handler "$exit_code" "$url" "$curl_stderr" __curl_err_handler "$exit_code" "$url" "$curl_stderr"
exit "$exit_code" exit 1 # hard exit if exit_code is not 0
fi fi
$STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2 $STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2
@@ -302,7 +332,7 @@ __curl_err_handler() {
esac esac
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2 [[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
exit "$exit_code" exit 1
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -317,7 +347,7 @@ shell_check() {
msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell." msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
echo -e "\nExiting..." echo -e "\nExiting..."
sleep 2 sleep 2
exit 103 exit
fi fi
} }
@@ -338,11 +368,11 @@ clear_line() {
# #
# - Determines if script should run in verbose mode # - Determines if script should run in verbose mode
# - Checks VERBOSE and var_verbose variables # - Checks VERBOSE and var_verbose variables
# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info() # - Also returns true if not running in TTY (pipe/redirect scenario)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
is_verbose_mode() { is_verbose_mode() {
local verbose="${VERBOSE:-${var_verbose:-no}}" local verbose="${VERBOSE:-${var_verbose:-no}}"
[[ "$verbose" != "no" ]] [[ "$verbose" != "no" || ! -t 2 ]]
} }
### dev spinner ### ### dev spinner ###
@@ -481,6 +511,20 @@ msg_debug() {
fi fi
} }
error_handler() {
local exit_code="$?"
local line_number="${1:-unknown}"
local command="${2:-unknown}"
if declare -f post_update_to_api >/dev/null 2>&1; then
post_update_to_api "failed" "$exit_code"
fi
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
echo -e "\n$error_message\n"
cleanup_vmid
}
# Displays error message and immediately terminates script # Displays error message and immediately terminates script
fatal() { fatal() {
msg_error "$1" msg_error "$1"
@@ -516,9 +560,13 @@ cleanup_vmid() {
cleanup() { cleanup() {
local exit_code=$? local exit_code=$?
stop_spinner
if [[ "$(dirs -p | wc -l)" -gt 1 ]]; then if [[ "$(dirs -p | wc -l)" -gt 1 ]]; then
popd >/dev/null || true popd >/dev/null || true
fi fi
if [[ -n "${TEMP_DIR:-}" && -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
fi
# Report final telemetry status if post_to_api_vm was called but no update was sent # Report final telemetry status if post_to_api_vm was called but no update was sent
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
if declare -f post_update_to_api >/dev/null 2>&1; then if declare -f post_update_to_api >/dev/null 2>&1; then
@@ -538,18 +586,37 @@ check_root() {
msg_error "Please run this script as root." msg_error "Please run this script as root."
echo -e "\nExiting..." echo -e "\nExiting..."
sleep 2 sleep 2
exit 104 exit
fi fi
} }
pve_check() { pve_check() {
if ! pveversion | grep -Eq "pve-manager/(8\.[1-4]|9\.[0-2])(\.[0-9]+)*"; then local pve_ver
msg_error "This version of Proxmox Virtual Environment is not supported" pve_ver="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
echo -e "Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.2."
echo -e "Exiting..." if [[ "$pve_ver" =~ ^8\.([0-9]+) ]]; then
sleep 2 local minor="${BASH_REMATCH[1]}"
exit 105 if ((minor < 0 || minor > 9)); then
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 8.0 8.9"
exit 105
fi
return 0
fi fi
if [[ "$pve_ver" =~ ^9\.([0-9]+) ]]; then
local minor="${BASH_REMATCH[1]}"
if ((minor < 0 || minor > 2)); then
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 9.0 9.2"
exit 105
fi
return 0
fi
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported versions: Proxmox VE 8.0 8.9 or 9.0 9.2"
exit 105
} }
arch_check() { arch_check() {
@@ -558,50 +625,487 @@ arch_check() {
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..." echo -e "Exiting..."
sleep 2 sleep 2
exit 106 exit
fi
}
ssh_check() {
if command -v pveversion >/dev/null 2>&1 && [ -n "${SSH_CLIENT:-}" ]; then
if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then
:
else
clear
exit
fi
fi fi
} }
exit_script() { exit_script() {
clear clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n" echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit 0 exit
}
sanitize_vm_hostname() {
local hostname="${1,,}"
hostname=$(echo "$hostname" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')
echo "${hostname:0:63}"
}
vm_confirm_new_vm() {
local title="$1"
local message="$2"
local height="${3:-10}"
local width="${4:-58}"
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" --yesno "$message" "$height" "$width"
}
vm_choose_settings_mode() {
local message="${1:-Use Default Settings?}"
local height="${2:-10}"
local width="${3:-58}"
whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "$message" --no-button Advanced "$height" "$width"
}
vm_confirm_advanced_settings() {
local message="$1"
local height="${2:-10}"
local width="${3:-58}"
whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "$message" --no-button Do-Over "$height" "$width"
}
vm_prompt_vmid() {
local default_vmid="${1:-$(get_valid_nextid)}"
while true; do
if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 "$default_vmid" --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$VMID" ]; then
VMID=$(get_valid_nextid)
fi
if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then
echo -e "${CROSS}${RD} ID $VMID is already in use${CL}"
sleep 2
continue
fi
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}"
break
else
exit_script
fi
done
}
vm_apply_machine_type() {
local machine_type="${1:-i440fx}"
if [ "$machine_type" = "q35" ]; then
MACHINE_TYPE="q35"
FORMAT=""
MACHINE=" -machine q35"
else
MACHINE_TYPE="i440fx"
FORMAT=",efitype=4m"
MACHINE=""
fi
}
vm_machine_type_label() {
case "${1:-i440fx}" in
q35)
echo "Q35 (Modern)"
;;
*)
echo "i440fx"
;;
esac
}
vm_prompt_machine_type() {
local default_machine="${1:-i440fx}"
local i440fx_default="ON"
local q35_default="OFF"
local machine_choice
if [ "$default_machine" = "q35" ]; then
i440fx_default="OFF"
q35_default="ON"
fi
if machine_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \
"i440fx" "Machine i440fx" "$i440fx_default" \
"q35" "Machine q35" "$q35_default" \
3>&1 1>&2 2>&3); then
vm_apply_machine_type "$machine_choice"
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$(vm_machine_type_label "$MACHINE_TYPE")${CL}"
else
exit_script
fi
}
vm_prompt_cloud_init() {
local default_user="${1:-root}"
USE_CLOUD_INIT="no"
load_cloud_init_functions
if ! declare -f configure_cloud_init_interactive >/dev/null 2>&1; then
echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}unavailable${CL}"
return 1
fi
configure_cloud_init_interactive "$default_user" || true
USE_CLOUD_INIT="${CLOUDINIT_ENABLE:-no}"
echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}${USE_CLOUD_INIT}${CL}"
if [ "$USE_CLOUD_INIT" = "yes" ] && declare -f configure_cloudinit_ssh_keys >/dev/null 2>&1; then
configure_cloudinit_ssh_keys || true
fi
return 0
}
vm_prompt_disk_size() {
local default_size="${1:-8G}"
local prompt_message="${2:-Set Disk Size in GiB (e.g., 10, 20)}"
if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "$prompt_message" 8 58 "$default_size" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ')
if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then
DISK_SIZE="${DISK_SIZE}G"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
else
echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}"
exit_script
fi
else
exit_script
fi
}
vm_prompt_disk_cache() {
local default_cache="${1:-none}"
local none_default="ON"
local write_default="OFF"
local cache_choice
if [ "$default_cache" = "writethrough" ]; then
none_default="OFF"
write_default="ON"
fi
if cache_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
"0" "None (Default)" "$none_default" \
"1" "Write Through" "$write_default" \
3>&1 1>&2 2>&3); then
if [ "$cache_choice" = "1" ]; then
DISK_CACHE="cache=writethrough,"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}"
else
DISK_CACHE=""
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}"
fi
else
exit_script
fi
}
vm_prompt_hostname() {
local default_hostname="${1:-vm}"
local adjusted_hostname
local input_hostname
if input_hostname=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$default_hostname" --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$input_hostname" ]; then
HN="$default_hostname"
else
adjusted_hostname=$(sanitize_vm_hostname "$input_hostname")
HN="${adjusted_hostname:-$default_hostname}"
if [ "$HN" != "${input_hostname,,}" ]; then
whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58
fi
fi
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
else
exit_script
fi
}
vm_prompt_cpu_model() {
local default_model="${1:-kvm64}"
local kvm_default="ON"
local host_default="OFF"
local cpu_choice
if [ "$default_model" = "host" ]; then
kvm_default="OFF"
host_default="ON"
fi
if cpu_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
"0" "KVM64 (Default)" "$kvm_default" \
"1" "Host" "$host_default" \
3>&1 1>&2 2>&3); then
if [ "$cpu_choice" = "1" ]; then
CPU_TYPE=" -cpu host"
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}"
else
CPU_TYPE=""
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}"
fi
else
exit_script
fi
}
vm_prompt_cpu_cores() {
local default_cores="${1:-2}"
while true; do
if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 "$default_cores" --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$CORE_COUNT" ]; then
CORE_COUNT="$default_cores"
fi
if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58
else
exit_script
fi
done
}
vm_prompt_ram() {
local default_ram="${1:-2048}"
while true; do
if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 "$default_ram" --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$RAM_SIZE" ]; then
RAM_SIZE="$default_ram"
fi
if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58
else
exit_script
fi
done
}
vm_prompt_bridge() {
local default_bridge="${1:-vmbr0}"
if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 "$default_bridge" --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$BRG" ]; then
BRG="$default_bridge"
fi
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
else
exit_script
fi
}
vm_prompt_mac() {
local default_mac="${1:-$GEN_MAC}"
local input_mac
while true; do
if input_mac=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 "$default_mac" --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$input_mac" ]; then
MAC="$default_mac"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
break
fi
if [[ "$input_mac" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
MAC="$input_mac"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58
else
exit_script
fi
done
}
vm_prompt_vlan() {
local default_vlan="${1:-}"
local input_vlan
while true; do
if input_vlan=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan (leave blank for default)" 8 58 "$default_vlan" --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$input_vlan" ]; then
VLAN=""
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}"
break
fi
if [[ "$input_vlan" =~ ^[0-9]+$ ]] && [ "$input_vlan" -ge 1 ] && [ "$input_vlan" -le 4094 ]; then
VLAN=",tag=$input_vlan"
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$input_vlan${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58
else
exit_script
fi
done
}
vm_prompt_mtu() {
local default_mtu="${1:-}"
local input_mtu
while true; do
if input_mtu=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 "$default_mtu" --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$input_mtu" ]; then
MTU=""
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}"
break
fi
if [[ "$input_mtu" =~ ^[0-9]+$ ]] && [ "$input_mtu" -ge 576 ] && [ "$input_mtu" -le 65520 ]; then
MTU=",mtu=$input_mtu"
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$input_mtu${CL}"
break
fi
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58
else
exit_script
fi
done
}
vm_prompt_start_vm() {
local default_start="${1:-yes}"
local default_flag=()
if [ "$default_start" = "no" ]; then
default_flag=(--defaultno)
fi
if whiptail --backtitle "Proxmox VE Helper Scripts" "${default_flag[@]}" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58; then
START_VM="yes"
else
START_VM="no"
fi
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}"
}
vm_apply_storage_layout() {
local storage_type="$1"
case $storage_type in
nfs | dir | cifs)
DISK_EXT=".qcow2"
DISK_REF="$VMID/"
DISK_IMPORT_FORMAT="qcow2"
THIN=""
;;
btrfs)
DISK_EXT=".raw"
DISK_REF="$VMID/"
DISK_IMPORT_FORMAT="raw"
FORMAT=",efitype=4m"
THIN=""
;;
*)
DISK_EXT=""
DISK_REF=""
DISK_IMPORT_FORMAT="raw"
;;
esac
}
vm_select_storage() {
local hostname="${1:-${HN:-vm}}"
local storage_menu=()
local msg_max_length=0
local line tag type free item
local offset=2
local valid_storage
msg_info "Validating Storage"
while read -r line; do
tag=$(echo "$line" | awk '{print $1}')
type=$(echo "$line" | awk '{printf "%-10s", $2}')
free=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}')
item=" Type: $type Free: $free "
if [[ $((${#item} + offset)) -gt $msg_max_length ]]; then
msg_max_length=$((${#item} + offset))
fi
storage_menu+=("$tag" "$item" "OFF")
done < <(pvesm status -content images | awk 'NR>1')
valid_storage=$(pvesm status -content images | awk 'NR>1')
if [ -z "$valid_storage" ]; then
msg_error "Unable to detect a valid storage location."
exit
elif [ $((${#storage_menu[@]} / 3)) -eq 1 ]; then
STORAGE=${storage_menu[0]}
else
if [ -n "${SPINNER_PID:-}" ] && ps -p "$SPINNER_PID" >/dev/null 2>&1; then
kill "$SPINNER_PID" >/dev/null 2>&1 || true
SPINNER_ACTIVE=0
printf "\r\e[2K" >&2
fi
while [ -z "${STORAGE:+x}" ]; do
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
"Which storage pool would you like to use for ${hostname}?\nTo make a selection, use the Spacebar.\n" \
16 $(($msg_max_length + 23)) 6 \
"${storage_menu[@]}" 3>&1 1>&2 2>&3)
done
fi
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
vm_apply_storage_layout "$STORAGE_TYPE"
}
vm_define_disk_references() {
local disk_count="${1:-2}"
local i disk_name
for ((i = 0; i < disk_count; i++)); do
disk_name="vm-${VMID}-disk-${i}${DISK_EXT:-}"
printf -v "DISK${i}" '%s' "$disk_name"
printf -v "DISK${i}_REF" '%s' "${STORAGE}:${DISK_REF:-}${disk_name}"
done
} }
check_hostname_conflict() { check_hostname_conflict() {
local hostname="$1" local hostname="$1"
if qm list | awk '{print $2}' | grep -qx "$hostname"; then if qm list | awk '{print $2}' | grep -qx "$hostname"; then
msg_error "Hostname $hostname already in use by another VM." msg_error "Hostname $hostname already in use by another VM."
exit 206 exit 1
fi fi
} }
set_description() { set_description() {
local app_name script_slug script_url donate_url local description_title="${APP:-${NSAPP} VM}"
app_name=$(echo "${APP,,}" | tr ' ' '-')
script_slug="${SCRIPT_SLUG:-${app_name}}"
script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
script_url="https://community-scripts.org/scripts/${script_slug}"
donate_url="https://community-scripts.org/donate"
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'> <a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>
<h2 style='font-size: 24px; margin: 20px 0;'>${NSAPP} VM</h2> <h2 style='font-size: 24px; margin: 20px 0;'>${description_title}</h2>
<p style='margin: 16px 0;'> <p style='margin: 16px 0;'>
<a href='${donate_url}' target='_blank' rel='noopener noreferrer'> <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/❤️-Sponsoring%20%26%20Donations-FF5E5B' alt='Sponsoring and donations' /> <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
</a>
</p>
<p style='margin: 12px 0;'>
<a href='${script_url}' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/📦-Open%20Script%20Page-00617f' alt='Open script page' />
</a> </a>
</p> </p>