Compare commits

..

1 Commits

Author SHA1 Message Date
CanbiZ (MickLesk)
e3446b6367 fix(homepage): preserve config directory during updates
Fixes #10985 - The update script was deleting user config files due to
CLEAN_INSTALL=1 flag. Now backs up and restores both .env and config/
directory to preserve user configurations.
2026-01-20 13:06:52 +01:00
7 changed files with 268 additions and 792 deletions

View File

@@ -10,27 +10,8 @@
> [!CAUTION]
Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit the project's popularity for potentially malicious purposes.
## 2026-01-21
## 2026-01-20
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- dolibarr: switch mirror [@MickLesk](https://github.com/MickLesk) ([#11004](https://github.com/community-scripts/ProxmoxVE/pull/11004))
- checkmk: reordner base function [@MickLesk](https://github.com/MickLesk) ([#10990](https://github.com/community-scripts/ProxmoxVE/pull/10990))
- Homepage: preserve config directory during updates [@MickLesk](https://github.com/MickLesk) ([#10993](https://github.com/community-scripts/ProxmoxVE/pull/10993))
- DiscoPanel: add go for update build process [@miausalvaje](https://github.com/miausalvaje) ([#10991](https://github.com/community-scripts/ProxmoxVE/pull/10991))
### 💾 Core
- #### ✨ New Features
- core: add retry logic for template lock in LXC container creation [@MickLesk](https://github.com/MickLesk) ([#11002](https://github.com/community-scripts/ProxmoxVE/pull/11002))
- core: implement ensure_profile_loaded function [@MickLesk](https://github.com/MickLesk) ([#10999](https://github.com/community-scripts/ProxmoxVE/pull/10999))
- core: add input validations for several functions [@MickLesk](https://github.com/MickLesk) ([#10995](https://github.com/community-scripts/ProxmoxVE/pull/10995))
## 2026-01-19
### 🆕 New Scripts

View File

@@ -54,14 +54,9 @@ function update_script() {
cd /opt/discopanel/web/discopanel
$STD npm install
$STD npm run build
msg_ok "Built Web Interface"
setup_go
msg_info "Building DiscoPanel"
cd /opt/discopanel
$STD go build -o discopanel cmd/discopanel/main.go
msg_ok "Built DiscoPanel"
msg_ok "Setup DiscoPanel"
msg_info "Restoring Data"
mkdir -p /opt/discopanel/data

View File

@@ -1,199 +1,4 @@
[
{
"name": "semaphoreui/semaphore",
"version": "v2.17.0-rc5",
"date": "2026-01-20T23:36:16Z"
},
{
"name": "ZoeyVid/NPMplus",
"version": "2026-01-20-r2",
"date": "2026-01-20T22:28:54Z"
},
{
"name": "esphome/esphome",
"version": "2025.12.7",
"date": "2026-01-17T03:49:29Z"
},
{
"name": "chrisbenincasa/tunarr",
"version": "v1.2.0-dev.1",
"date": "2026-01-20T21:46:14Z"
},
{
"name": "SigNoz/signoz",
"version": "v0.108.0-rc.1",
"date": "2026-01-20T20:46:43Z"
},
{
"name": "ollama/ollama",
"version": "v0.14.3-rc2",
"date": "2026-01-19T20:48:34Z"
},
{
"name": "rcourtman/Pulse",
"version": "v5.0.17",
"date": "2026-01-20T19:07:30Z"
},
{
"name": "firefly-iii/firefly-iii",
"version": "v6.4.16",
"date": "2026-01-17T07:54:15Z"
},
{
"name": "netbox-community/netbox",
"version": "v4.5.1",
"date": "2026-01-20T19:45:05Z"
},
{
"name": "Infisical/infisical",
"version": "v0.155.7",
"date": "2026-01-20T19:42:20Z"
},
{
"name": "seerr-team/seerr",
"version": "preview-availability-sync-fix",
"date": "2026-01-20T19:12:59Z"
},
{
"name": "nickheyer/discopanel",
"version": "v1.0.24",
"date": "2026-01-20T18:31:20Z"
},
{
"name": "mysql/mysql-server",
"version": "mysql-cluster-8.0.45",
"date": "2026-01-20T18:27:03Z"
},
{
"name": "element-hq/synapse",
"version": "v1.145.0",
"date": "2026-01-13T16:49:51Z"
},
{
"name": "BerriAI/litellm",
"version": "v1.81.0.rc.1",
"date": "2026-01-20T16:55:00Z"
},
{
"name": "chrisvel/tududi",
"version": "v0.88.4",
"date": "2026-01-20T16:02:12Z"
},
{
"name": "gtsteffaniak/filebrowser",
"version": "v1.1.1-stable",
"date": "2026-01-12T23:25:10Z"
},
{
"name": "mattermost/mattermost",
"version": "@mattermost/client@11.3.0",
"date": "2026-01-20T15:26:31Z"
},
{
"name": "theonedev/onedev",
"version": "v14.0.8",
"date": "2026-01-20T15:10:44Z"
},
{
"name": "metabase/metabase",
"version": "v0.57.10",
"date": "2026-01-20T14:55:34Z"
},
{
"name": "meilisearch/meilisearch",
"version": "latest",
"date": "2026-01-20T14:25:19Z"
},
{
"name": "sysadminsmedia/homebox",
"version": "v0.23.0-rc.1",
"date": "2026-01-20T14:19:56Z"
},
{
"name": "apache/tomcat",
"version": "9.0.114",
"date": "2026-01-20T14:13:00Z"
},
{
"name": "n8n-io/n8n",
"version": "n8n@2.3.6",
"date": "2026-01-16T15:00:42Z"
},
{
"name": "jenkinsci/jenkins",
"version": "jenkins-2.547",
"date": "2026-01-20T13:46:00Z"
},
{
"name": "docker/compose",
"version": "v5.0.2",
"date": "2026-01-20T12:57:53Z"
},
{
"name": "dgtlmoon/changedetection.io",
"version": "0.52.8",
"date": "2026-01-20T12:36:05Z"
},
{
"name": "lazy-media/Reactive-Resume",
"version": "v1.2.7",
"date": "2026-01-20T11:59:40Z"
},
{
"name": "cloudflare/cloudflared",
"version": "2026.1.1",
"date": "2026-01-20T11:22:06Z"
},
{
"name": "passbolt/passbolt_api",
"version": "v5.9.0-test.1",
"date": "2026-01-20T10:34:53Z"
},
{
"name": "Luligu/matterbridge",
"version": "3.5.0",
"date": "2026-01-20T08:53:52Z"
},
{
"name": "nzbgetcom/nzbget",
"version": "v25.4",
"date": "2025-10-09T10:27:01Z"
},
{
"name": "openobserve/openobserve",
"version": "v0.50.0",
"date": "2026-01-20T07:34:31Z"
},
{
"name": "HydroshieldMKII/Guardian",
"version": "v1.3.4",
"date": "2026-01-20T06:20:36Z"
},
{
"name": "morpheus65535/bazarr",
"version": "v1.5.4",
"date": "2026-01-04T22:41:00Z"
},
{
"name": "Comfy-Org/ComfyUI",
"version": "v0.10.0",
"date": "2026-01-20T03:40:18Z"
},
{
"name": "diced/zipline",
"version": "v4.4.1",
"date": "2026-01-20T01:29:01Z"
},
{
"name": "jeedom/core",
"version": "4.5.2",
"date": "2026-01-20T00:27:06Z"
},
{
"name": "steveiliop56/tinyauth",
"version": "v4.1.0",
"date": "2025-11-23T12:13:34Z"
},
{
"name": "binwiederhier/ntfy",
"version": "v2.16.0",
@@ -205,14 +10,24 @@
"date": "2026-01-19T20:51:19Z"
},
{
"name": "livebook-dev/livebook",
"version": "nightly",
"date": "2026-01-19T18:03:38Z"
"name": "ollama/ollama",
"version": "v0.14.3-rc2",
"date": "2026-01-19T20:48:34Z"
},
{
"name": "keycloak/keycloak",
"version": "26.4.8",
"date": "2026-01-15T13:52:29Z"
"name": "firefly-iii/firefly-iii",
"version": "v6.4.16",
"date": "2026-01-17T07:54:15Z"
},
{
"name": "metabase/metabase",
"version": "v0.58.x",
"date": "2026-01-19T18:21:04Z"
},
{
"name": "apache/tomcat",
"version": "11.0.16",
"date": "2026-01-19T18:15:05Z"
},
{
"name": "paperless-ngx/paperless-ngx",
@@ -229,6 +44,16 @@
"version": "server-v3.5.2",
"date": "2025-12-19T21:28:55Z"
},
{
"name": "meilisearch/meilisearch",
"version": "prototype-v1.33.0-hannoy-better-linear-scanning.4",
"date": "2026-01-19T16:37:18Z"
},
{
"name": "Infisical/infisical",
"version": "v0.155.6",
"date": "2026-01-19T16:35:03Z"
},
{
"name": "bunkerity/bunkerweb",
"version": "v1.6.7",
@@ -244,6 +69,11 @@
"version": "pmm-6401-v1.134.0",
"date": "2026-01-19T13:31:08Z"
},
{
"name": "keycloak/keycloak",
"version": "26.4.8",
"date": "2026-01-15T13:52:29Z"
},
{
"name": "crowdsecurity/crowdsec",
"version": "v1.7.4",
@@ -264,6 +94,16 @@
"version": "v1.9.14",
"date": "2026-01-19T09:16:56Z"
},
{
"name": "dgtlmoon/changedetection.io",
"version": "0.52.7",
"date": "2026-01-19T08:38:05Z"
},
{
"name": "morpheus65535/bazarr",
"version": "v1.5.4",
"date": "2026-01-04T22:41:00Z"
},
{
"name": "Jackett/Jackett",
"version": "v0.24.887",
@@ -274,6 +114,11 @@
"version": "v1.9.0",
"date": "2026-01-19T05:46:09Z"
},
{
"name": "nickheyer/discopanel",
"version": "v1.0.23",
"date": "2026-01-19T05:14:43Z"
},
{
"name": "9001/copyparty",
"version": "v1.20.2",
@@ -289,6 +134,16 @@
"version": "2.1.0rc1",
"date": "2026-01-19T00:48:21Z"
},
{
"name": "steveiliop56/tinyauth",
"version": "v4.1.0",
"date": "2025-11-23T12:13:34Z"
},
{
"name": "jeedom/core",
"version": "4.5.2",
"date": "2026-01-19T00:27:05Z"
},
{
"name": "Kareadita/Kavita",
"version": "v0.8.9.1",
@@ -309,6 +164,11 @@
"version": "v2.5.0",
"date": "2026-01-18T22:16:38Z"
},
{
"name": "seerr-team/seerr",
"version": "preview-remonitor-sonarr-episodes",
"date": "2026-01-18T20:33:38Z"
},
{
"name": "fccview/jotty",
"version": "1.18.0",
@@ -334,6 +194,11 @@
"version": "2.4",
"date": "2026-01-18T12:12:02Z"
},
{
"name": "BerriAI/litellm",
"version": "v1.81.0-nightly",
"date": "2026-01-18T04:06:15Z"
},
{
"name": "hyperion-project/hyperion.ng",
"version": "2.1.1",
@@ -344,6 +209,16 @@
"version": "v7.14.2",
"date": "2026-01-18T00:26:09Z"
},
{
"name": "chrisbenincasa/tunarr",
"version": "v1.1.1",
"date": "2026-01-17T22:35:09Z"
},
{
"name": "ZoeyVid/NPMplus",
"version": "2026-01-17-r3",
"date": "2026-01-17T21:45:17Z"
},
{
"name": "outline/outline",
"version": "v1.3.0",
@@ -369,6 +244,11 @@
"version": "v6.13.6",
"date": "2025-11-04T13:35:35Z"
},
{
"name": "esphome/esphome",
"version": "2025.12.7",
"date": "2026-01-17T03:49:29Z"
},
{
"name": "coder/code-server",
"version": "v4.108.1",
@@ -389,6 +269,21 @@
"version": "v1.50.1",
"date": "2026-01-16T19:27:38Z"
},
{
"name": "livebook-dev/livebook",
"version": "v0.18.3",
"date": "2026-01-14T21:50:55Z"
},
{
"name": "cloudflare/cloudflared",
"version": "2026.1.0",
"date": "2026-01-16T17:48:15Z"
},
{
"name": "n8n-io/n8n",
"version": "n8n@2.3.6",
"date": "2026-01-16T15:00:42Z"
},
{
"name": "emqx/emqx",
"version": "6.0.2",
@@ -424,6 +319,11 @@
"version": "v0.13.6",
"date": "2026-01-15T23:34:51Z"
},
{
"name": "semaphoreui/semaphore",
"version": "v2.17.0-rc3",
"date": "2026-01-15T21:30:26Z"
},
{
"name": "azukaar/Cosmos-Server",
"version": "v0.20.0",
@@ -439,6 +339,11 @@
"version": "v2.3.0",
"date": "2026-01-15T19:29:02Z"
},
{
"name": "Comfy-Org/ComfyUI",
"version": "v0.9.2",
"date": "2026-01-15T17:55:40Z"
},
{
"name": "zwave-js/zwave-js-ui",
"version": "v11.10.1",
@@ -454,11 +359,26 @@
"version": "0.24.3",
"date": "2026-01-15T14:40:15Z"
},
{
"name": "openobserve/openobserve",
"version": "v0.50.0-rc3",
"date": "2026-01-15T13:57:37Z"
},
{
"name": "readeck/readeck",
"version": "0.21.6",
"date": "2026-01-15T11:18:58Z"
},
{
"name": "mattermost/mattermost",
"version": "v10.11.10",
"date": "2026-01-15T10:36:07Z"
},
{
"name": "SigNoz/signoz",
"version": "v0.107.0",
"date": "2026-01-15T06:50:08Z"
},
{
"name": "zitadel/zitadel",
"version": "v4.9.1",
@@ -554,6 +474,11 @@
"version": "v0.5.5",
"date": "2026-01-13T17:03:32Z"
},
{
"name": "element-hq/synapse",
"version": "v1.145.0",
"date": "2026-01-13T16:49:51Z"
},
{
"name": "LimeSurvey/LimeSurvey",
"version": "6.16.3+251215",
@@ -564,6 +489,16 @@
"version": "v0.16.6",
"date": "2026-01-13T10:28:14Z"
},
{
"name": "jenkinsci/jenkins",
"version": "jenkins-2.546",
"date": "2026-01-13T10:08:09Z"
},
{
"name": "Luligu/matterbridge",
"version": "3.4.7",
"date": "2026-01-13T07:28:02Z"
},
{
"name": "henrygd/beszel",
"version": "v0.18.2",
@@ -579,6 +514,11 @@
"version": "26.1.1",
"date": "2026-01-12T23:26:02Z"
},
{
"name": "gtsteffaniak/filebrowser",
"version": "v1.1.1-stable",
"date": "2026-01-12T23:25:10Z"
},
{
"name": "influxdata/influxdb",
"version": "v2.8.0",
@@ -594,6 +534,11 @@
"version": "v1.7.10",
"date": "2026-01-12T20:50:50Z"
},
{
"name": "rcourtman/Pulse",
"version": "v5.0.16",
"date": "2026-01-12T20:18:34Z"
},
{
"name": "release-argus/Argus",
"version": "0.29.2",
@@ -679,6 +624,11 @@
"version": "v0.14.1",
"date": "2024-08-29T22:32:51Z"
},
{
"name": "theonedev/onedev",
"version": "v14.0.7",
"date": "2026-01-10T10:31:47Z"
},
{
"name": "Kozea/Radicale",
"version": "v3.6.0",
@@ -809,6 +759,11 @@
"version": "2.46.0",
"date": "2026-01-07T00:19:31Z"
},
{
"name": "netbox-community/netbox",
"version": "v4.5.0",
"date": "2026-01-06T21:14:27Z"
},
{
"name": "caddyserver/caddy",
"version": "v2.10.2",
@@ -919,6 +874,16 @@
"version": "v4.7.0",
"date": "2025-12-27T20:37:54Z"
},
{
"name": "sysadminsmedia/homebox",
"version": "v0.22.3",
"date": "2025-12-26T22:31:20Z"
},
{
"name": "HydroshieldMKII/Guardian",
"version": "v1.3.3",
"date": "2025-12-25T20:19:52Z"
},
{
"name": "matze/wastebin",
"version": "3.4.0",
@@ -954,6 +919,11 @@
"version": "v5.7",
"date": "2025-12-23T14:53:51Z"
},
{
"name": "nzbgetcom/nzbget",
"version": "v25.4",
"date": "2025-10-09T10:27:01Z"
},
{
"name": "itsmng/itsm-ng",
"version": "v1.6.11",
@@ -974,6 +944,16 @@
"version": "v4.0.16.2944",
"date": "2025-11-05T01:56:48Z"
},
{
"name": "chrisvel/tududi",
"version": "v0.88.2",
"date": "2025-12-22T14:36:59Z"
},
{
"name": "passbolt/passbolt_api",
"version": "v5.8.0",
"date": "2025-12-22T10:12:48Z"
},
{
"name": "benjaminjonard/koillection",
"version": "1.7.1",
@@ -1029,6 +1009,11 @@
"version": "v1.25.3",
"date": "2025-12-18T18:11:48Z"
},
{
"name": "docker/compose",
"version": "v5.0.1",
"date": "2025-12-18T14:22:38Z"
},
{
"name": "juanfont/headscale",
"version": "v0.27.1",
@@ -1074,6 +1059,11 @@
"version": "v1.13.2",
"date": "2025-12-13T22:59:03Z"
},
{
"name": "diced/zipline",
"version": "v4.4.0",
"date": "2025-12-13T22:49:07Z"
},
{
"name": "WGDashboard/WGDashboard",
"version": "v4.3.1",
@@ -1434,6 +1424,11 @@
"version": "v2.2.2",
"date": "2025-10-06T21:31:07Z"
},
{
"name": "mysql/mysql-server",
"version": "mysql-cluster-7.6.36",
"date": "2025-10-06T15:19:49Z"
},
{
"name": "jordan-dalby/ByteStash",
"version": "v1.5.9",
@@ -1449,6 +1444,11 @@
"version": "v1.11.1",
"date": "2025-09-30T00:24:16Z"
},
{
"name": "lazy-media/Reactive-Resume",
"version": "v1.2.6",
"date": "2025-09-28T18:09:21Z"
},
{
"name": "Pf2eToolsOrg/Pf2eTools",
"version": "v0.10.1",

View File

@@ -21,6 +21,9 @@ rm -rf /opt/checkmk.deb
echo "${RELEASE}" >"/opt/checkmk_version.txt"
msg_ok "Installed Checkmk"
motd_ssh
customize
msg_info "Creating Service"
SITE_NAME="monitoring"
$STD omd create "$SITE_NAME"
@@ -39,5 +42,3 @@ $STD omd start "$SITE_NAME"
msg_ok "Created Service"
cleanup_lxc
motd_ssh
customize

View File

@@ -34,7 +34,7 @@ msg_info "Setup Dolibarr"
BASE="https://sourceforge.net/projects/dolibarr/files/Dolibarr%20installer%20for%20Debian-Ubuntu%20(DoliDeb)/"
RELEASE=$(curl -fsSL "$BASE" | grep -oP '(?<=/Dolibarr%20installer%20for%20Debian-Ubuntu%20%28DoliDeb%29/)\d+(\.\d+)+(?=/)' | sort -V | tail -n1)
FILE=$(curl -fsSL "${BASE}${RELEASE}/" | grep -oP 'dolibarr_[^"]+_all.deb' | head -n1)
curl -fsSL "https://altushost-swe.dl.sourceforge.net/project/dolibarr/Dolibarr%20installer%20for%20Debian-Ubuntu%20(DoliDeb)/${RELEASE}/${FILE}?viasf=1" -o ""$FILE""
curl -fsSL "https://netcologne.dl.sourceforge.net/project/dolibarr/Dolibarr%20installer%20for%20Debian-Ubuntu%20(DoliDeb)/${RELEASE}/${FILE}?viasf=1" -o ""$FILE""
echo "dolibarr dolibarr/reconfigure-webserver multiselect apache2" | debconf-set-selections
$STD apt-get install ./$FILE -y
$STD apt install -f

View File

@@ -357,268 +357,6 @@ validate_hostname() {
return 0
}
# ------------------------------------------------------------------------------
# validate_mac_address()
#
# - Validates MAC address format (XX:XX:XX:XX:XX:XX)
# - Empty value is allowed (auto-generated)
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_mac_address() {
local mac="$1"
[[ -z "$mac" ]] && return 0
if [[ ! "$mac" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_vlan_tag()
#
# - Validates VLAN tag (1-4094)
# - Empty value is allowed (no VLAN)
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_vlan_tag() {
local vlan="$1"
[[ -z "$vlan" ]] && return 0
if ! [[ "$vlan" =~ ^[0-9]+$ ]] || ((vlan < 1 || vlan > 4094)); then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_mtu()
#
# - Validates MTU size (576-65535, common values: 1500, 9000)
# - Empty value is allowed (default 1500)
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_mtu() {
local mtu="$1"
[[ -z "$mtu" ]] && return 0
if ! [[ "$mtu" =~ ^[0-9]+$ ]] || ((mtu < 576 || mtu > 65535)); then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_ipv6_address()
#
# - Validates IPv6 address with optional CIDR notation
# - Supports compressed (::) and full notation
# - Empty value is allowed
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_ipv6_address() {
local ipv6="$1"
[[ -z "$ipv6" ]] && return 0
# Extract address and CIDR
local addr="${ipv6%%/*}"
local cidr="${ipv6##*/}"
# Validate CIDR if present (1-128)
if [[ "$ipv6" == */* ]]; then
if ! [[ "$cidr" =~ ^[0-9]+$ ]] || ((cidr < 1 || cidr > 128)); then
return 1
fi
fi
# Basic IPv6 validation - check for valid characters and structure
# Must contain only hex digits and colons
if [[ ! "$addr" =~ ^[0-9a-fA-F:]+$ ]]; then
return 1
fi
# Must contain at least one colon
if [[ ! "$addr" == *:* ]]; then
return 1
fi
# Check for valid double-colon usage (only one :: allowed)
if [[ "$addr" == *::*::* ]]; then
return 1
fi
# Check that no segment exceeds 4 hex chars
local IFS=':'
local -a segments
read -ra segments <<< "$addr"
for seg in "${segments[@]}"; do
if [[ ${#seg} -gt 4 ]]; then
return 1
fi
done
return 0
}
# ------------------------------------------------------------------------------
# validate_bridge()
#
# - Validates that network bridge exists and is active
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_bridge() {
local bridge="$1"
[[ -z "$bridge" ]] && return 1
# Check if bridge interface exists
if ! ip link show "$bridge" &>/dev/null; then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_gateway_in_subnet()
#
# - Validates that gateway IP is in the same subnet as static IP
# - Arguments: static_ip (with CIDR), gateway_ip
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_gateway_in_subnet() {
local static_ip="$1"
local gateway="$2"
[[ -z "$static_ip" || -z "$gateway" ]] && return 0
# Extract IP and CIDR
local ip="${static_ip%%/*}"
local cidr="${static_ip##*/}"
# Convert CIDR to netmask bits
local mask=$((0xFFFFFFFF << (32 - cidr) & 0xFFFFFFFF))
# Convert IPs to integers
local IFS='.'
read -r i1 i2 i3 i4 <<< "$ip"
read -r g1 g2 g3 g4 <<< "$gateway"
local ip_int=$(( (i1 << 24) + (i2 << 16) + (i3 << 8) + i4 ))
local gw_int=$(( (g1 << 24) + (g2 << 16) + (g3 << 8) + g4 ))
# Check if both are in same network
if (( (ip_int & mask) != (gw_int & mask) )); then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_ip_address()
#
# - Validates IPv4 address with CIDR notation
# - Checks each octet is 0-255
# - Checks CIDR is 1-32
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_ip_address() {
local ip="$1"
[[ -z "$ip" ]] && return 1
# Check format with CIDR
if [[ ! "$ip" =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/([0-9]{1,2})$ ]]; then
return 1
fi
local o1="${BASH_REMATCH[1]}"
local o2="${BASH_REMATCH[2]}"
local o3="${BASH_REMATCH[3]}"
local o4="${BASH_REMATCH[4]}"
local cidr="${BASH_REMATCH[5]}"
# Validate octets (0-255)
for octet in "$o1" "$o2" "$o3" "$o4"; do
if ((octet > 255)); then
return 1
fi
done
# Validate CIDR (1-32)
if ((cidr < 1 || cidr > 32)); then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_gateway_ip()
#
# - Validates gateway IPv4 address (without CIDR)
# - Checks each octet is 0-255
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_gateway_ip() {
local ip="$1"
[[ -z "$ip" ]] && return 0
# Check format without CIDR
if [[ ! "$ip" =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]]; then
return 1
fi
local o1="${BASH_REMATCH[1]}"
local o2="${BASH_REMATCH[2]}"
local o3="${BASH_REMATCH[3]}"
local o4="${BASH_REMATCH[4]}"
# Validate octets (0-255)
for octet in "$o1" "$o2" "$o3" "$o4"; do
if ((octet > 255)); then
return 1
fi
done
return 0
}
# ------------------------------------------------------------------------------
# validate_timezone()
#
# - Validates timezone string against system zoneinfo
# - Empty value or "host" is allowed
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_timezone() {
local tz="$1"
[[ -z "$tz" || "$tz" == "host" ]] && return 0
# Check if timezone file exists
if [[ ! -f "/usr/share/zoneinfo/$tz" ]]; then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# validate_tags()
#
# - Validates Proxmox tags format
# - Only alphanumeric, hyphens, underscores, and semicolons allowed
# - Empty value is allowed
# - Returns 0 if valid, 1 if invalid
# ------------------------------------------------------------------------------
validate_tags() {
local tags="$1"
[[ -z "$tags" ]] && return 0
# Tags can only contain alphanumeric, -, _, and ; (separator)
if [[ ! "$tags" =~ ^[a-zA-Z0-9_\;-]+$ ]]; then
return 1
fi
return 0
}
# ------------------------------------------------------------------------------
# find_host_ssh_keys()
#
@@ -1034,119 +772,6 @@ load_vars_file() {
# Trim trailing whitespace
var_val="${var_val%"${var_val##*[![:space:]]}"}"
# Validate values before setting (skip empty values - they use defaults)
if [[ -n "$var_val" ]]; then
case "$var_key" in
var_mac)
if ! validate_mac_address "$var_val"; then
msg_warn "Invalid MAC address '$var_val' in $file, ignoring"
continue
fi
;;
var_vlan)
if ! validate_vlan_tag "$var_val"; then
msg_warn "Invalid VLAN tag '$var_val' in $file (must be 1-4094), ignoring"
continue
fi
;;
var_mtu)
if ! validate_mtu "$var_val"; then
msg_warn "Invalid MTU '$var_val' in $file (must be 576-65535), ignoring"
continue
fi
;;
var_tags)
if ! validate_tags "$var_val"; then
msg_warn "Invalid tags '$var_val' in $file (alphanumeric, -, _, ; only), ignoring"
continue
fi
;;
var_timezone)
if ! validate_timezone "$var_val"; then
msg_warn "Invalid timezone '$var_val' in $file, ignoring"
continue
fi
;;
var_brg)
if ! validate_bridge "$var_val"; then
msg_warn "Bridge '$var_val' not found in $file, ignoring"
continue
fi
;;
var_gateway)
if ! validate_gateway_ip "$var_val"; then
msg_warn "Invalid gateway IP '$var_val' in $file, ignoring"
continue
fi
;;
var_hostname)
if ! validate_hostname "$var_val"; then
msg_warn "Invalid hostname '$var_val' in $file, ignoring"
continue
fi
;;
var_cpu)
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 1 || var_val > 128)); then
msg_warn "Invalid CPU count '$var_val' in $file (must be 1-128), ignoring"
continue
fi
;;
var_ram)
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 256)); then
msg_warn "Invalid RAM '$var_val' in $file (must be >= 256 MiB), ignoring"
continue
fi
;;
var_disk)
if ! [[ "$var_val" =~ ^[0-9]+$ ]] || ((var_val < 1)); then
msg_warn "Invalid disk size '$var_val' in $file (must be >= 1 GB), ignoring"
continue
fi
;;
var_unprivileged)
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
msg_warn "Invalid unprivileged value '$var_val' in $file (must be 0 or 1), ignoring"
continue
fi
;;
var_nesting)
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
msg_warn "Invalid nesting value '$var_val' in $file (must be 0 or 1), ignoring"
continue
fi
;;
var_keyctl)
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
msg_warn "Invalid keyctl value '$var_val' in $file (must be 0 or 1), ignoring"
continue
fi
;;
var_net)
# var_net can be: dhcp, static IP/CIDR, or IP range
if [[ "$var_val" != "dhcp" ]]; then
if is_ip_range "$var_val"; then
: # IP range is valid, will be resolved at runtime
elif ! validate_ip_address "$var_val"; then
msg_warn "Invalid network '$var_val' in $file (must be dhcp or IP/CIDR), ignoring"
continue
fi
fi
;;
var_fuse|var_tun|var_gpu|var_ssh|var_verbose|var_protection)
if [[ "$var_val" != "yes" && "$var_val" != "no" ]]; then
msg_warn "Invalid boolean '$var_val' for $var_key in $file (must be yes/no), ignoring"
continue
fi
;;
var_ipv6_method)
if [[ "$var_val" != "auto" && "$var_val" != "dhcp" && "$var_val" != "static" && "$var_val" != "none" ]]; then
msg_warn "Invalid IPv6 method '$var_val' in $file (must be auto/dhcp/static/none), ignoring"
continue
fi
;;
esac
fi
# Set variable: force mode overrides existing, otherwise only set if empty
if [[ "$force" == "yes" ]]; then
export "${var_key}=${var_val}"
@@ -1987,14 +1612,8 @@ advanced_settings() {
# ═══════════════════════════════════════════════════════════════════════════
8)
if [[ ${#BRIDGE_MENU_OPTIONS[@]} -eq 0 ]]; then
# Validate default bridge exists
if validate_bridge "vmbr0"; then
_bridge="vmbr0"
((STEP++))
else
whiptail --msgbox "Default bridge 'vmbr0' not found!\n\nPlease configure a network bridge in Proxmox first." 10 58
exit 1
fi
_bridge="vmbr0"
((STEP++))
else
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "NETWORK BRIDGE" \
@@ -2002,13 +1621,8 @@ advanced_settings() {
--menu "\nSelect network bridge:" 16 58 6 \
"${BRIDGE_MENU_OPTIONS[@]}" \
3>&1 1>&2 2>&3); then
local bridge_test="${result:-vmbr0}"
if validate_bridge "$bridge_test"; then
_bridge="$bridge_test"
((STEP++))
else
whiptail --msgbox "Bridge '$bridge_test' is not available or not active." 8 58
fi
_bridge="${result:-vmbr0}"
((STEP++))
else
((STEP--))
fi
@@ -2036,7 +1650,7 @@ advanced_settings() {
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nEnter Static IPv4 CIDR Address\n(e.g. 192.168.1.100/24)" 12 58 "" \
3>&1 1>&2 2>&3); then
if validate_ip_address "$static_ip"; then
if [[ "$static_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
# Get gateway
local gateway_ip
if gateway_ip=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
@@ -2044,21 +1658,16 @@ advanced_settings() {
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nEnter Gateway IP address" 10 58 "" \
3>&1 1>&2 2>&3); then
if validate_gateway_ip "$gateway_ip"; then
# Validate gateway is in same subnet
if validate_gateway_in_subnet "$static_ip" "$gateway_ip"; then
_net="$static_ip"
_gate=",gw=$gateway_ip"
((STEP++))
else
whiptail --msgbox "Gateway is not in the same subnet as the static IP.\n\nStatic IP: $static_ip\nGateway: $gateway_ip" 10 58
fi
if [[ "$gateway_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
_net="$static_ip"
_gate=",gw=$gateway_ip"
((STEP++))
else
whiptail --msgbox "Invalid Gateway IP format.\n\nEach octet must be 0-255.\nExample: 192.168.1.1" 10 58
whiptail --msgbox "Invalid Gateway IP format." 8 58
fi
fi
else
whiptail --msgbox "Invalid IPv4 CIDR format.\n\nEach octet must be 0-255.\nCIDR must be 1-32.\nExample: 192.168.1.100/24" 12 58
whiptail --msgbox "Invalid IPv4 CIDR format.\nExample: 192.168.1.100/24" 8 58
fi
fi
elif [[ "$result" == "range" ]]; then
@@ -2082,17 +1691,12 @@ advanced_settings() {
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nFound free IP: $NET_RESOLVED\n\nEnter Gateway IP address" 12 58 "" \
3>&1 1>&2 2>&3); then
if validate_gateway_ip "$gateway_ip"; then
# Validate gateway is in same subnet
if validate_gateway_in_subnet "$NET_RESOLVED" "$gateway_ip"; then
_net="$NET_RESOLVED"
_gate=",gw=$gateway_ip"
((STEP++))
else
whiptail --msgbox "Gateway is not in the same subnet as the IP.\n\nIP: $NET_RESOLVED\nGateway: $gateway_ip" 10 58
fi
if [[ "$gateway_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
_net="$NET_RESOLVED"
_gate=",gw=$gateway_ip"
((STEP++))
else
whiptail --msgbox "Invalid Gateway IP format.\n\nEach octet must be 0-255.\nExample: 192.168.1.1" 10 58
whiptail --msgbox "Invalid Gateway IP format." 8 58
fi
fi
else
@@ -2135,33 +1739,16 @@ advanced_settings() {
--title "STATIC IPv6 ADDRESS" \
--inputbox "\nEnter IPv6 CIDR address\n(e.g. 2001:db8::1/64)" 12 58 "" \
3>&1 1>&2 2>&3); then
if validate_ipv6_address "$ipv6_addr"; then
if [[ "$ipv6_addr" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+(/[0-9]{1,3})$ ]]; then
_ipv6_addr="$ipv6_addr"
# Optional gateway - loop until valid or empty
local ipv6_gw_valid=false
while [[ "$ipv6_gw_valid" == "false" ]]; do
local ipv6_gw
ipv6_gw=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "IPv6 GATEWAY" \
--inputbox "\nEnter IPv6 gateway (optional, leave blank for none)" 10 58 "" \
3>&1 1>&2 2>&3) || true
# Validate gateway if provided
if [[ -n "$ipv6_gw" ]]; then
if validate_ipv6_address "$ipv6_gw"; then
_ipv6_gate="$ipv6_gw"
ipv6_gw_valid=true
((STEP++))
else
whiptail --msgbox "Invalid IPv6 gateway format.\n\nExample: 2001:db8::1" 8 58
fi
else
_ipv6_gate=""
ipv6_gw_valid=true
((STEP++))
fi
done
# Optional gateway
_ipv6_gate=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "IPv6 GATEWAY" \
--inputbox "\nEnter IPv6 gateway (optional, leave blank for none)" 10 58 "" \
3>&1 1>&2 2>&3) || true
((STEP++))
else
whiptail --msgbox "Invalid IPv6 CIDR format.\n\nExample: 2001:db8::1/64\nCIDR must be 1-128." 10 58
whiptail --msgbox "Invalid IPv6 CIDR format." 8 58
fi
fi
;;
@@ -2194,14 +1781,10 @@ advanced_settings() {
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "MTU SIZE" \
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nSet Interface MTU Size\n(leave blank for default 1500, common values: 1500, 9000)" 12 62 "" \
--inputbox "\nSet Interface MTU Size\n(leave blank for default 1500)" 12 58 "" \
3>&1 1>&2 2>&3); then
if validate_mtu "$result"; then
_mtu="$result"
((STEP++))
else
whiptail --msgbox "Invalid MTU size.\n\nMTU must be between 576 and 65535.\nCommon values: 1500 (default), 9000 (jumbo frames)" 10 58
fi
_mtu="$result"
((STEP++))
else
((STEP--))
fi
@@ -2246,14 +1829,10 @@ advanced_settings() {
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "MAC ADDRESS" \
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nSet MAC Address\n(leave blank for auto-generated, format: XX:XX:XX:XX:XX:XX)" 12 62 "" \
--inputbox "\nSet MAC Address\n(leave blank for auto-generated)" 12 58 "" \
3>&1 1>&2 2>&3); then
if validate_mac_address "$result"; then
_mac="$result"
((STEP++))
else
whiptail --msgbox "Invalid MAC address format.\n\nRequired format: XX:XX:XX:XX:XX:XX\nExample: 02:00:00:00:00:01" 10 58
fi
_mac="$result"
((STEP++))
else
((STEP--))
fi
@@ -2266,14 +1845,10 @@ advanced_settings() {
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "VLAN TAG" \
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nSet VLAN Tag (1-4094)\n(leave blank for no VLAN)" 12 58 "" \
--inputbox "\nSet VLAN Tag\n(leave blank for no VLAN)" 12 58 "" \
3>&1 1>&2 2>&3); then
if validate_vlan_tag "$result"; then
_vlan="$result"
((STEP++))
else
whiptail --msgbox "Invalid VLAN tag.\n\nVLAN must be a number between 1 and 4094." 8 58
fi
_vlan="$result"
((STEP++))
else
((STEP--))
fi
@@ -2286,16 +1861,11 @@ advanced_settings() {
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "CONTAINER TAGS" \
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nSet Custom Tags (semicolon-separated)\n(alphanumeric, hyphens, underscores only)" 12 58 "$_tags" \
--inputbox "\nSet Custom Tags (semicolon-separated)\n(remove all for no tags)" 12 58 "$_tags" \
3>&1 1>&2 2>&3); then
local tags_test="${result:-}"
tags_test=$(echo "$tags_test" | tr -d '[:space:]')
if validate_tags "$tags_test"; then
_tags="$tags_test"
((STEP++))
else
whiptail --msgbox "Invalid tag format.\n\nTags can only contain:\n- Letters (a-z, A-Z)\n- Numbers (0-9)\n- Hyphens (-)\n- Underscores (_)\n- Semicolons (;) as separator" 14 58
fi
_tags="${result:-;}"
_tags=$(echo "$_tags" | tr -d '[:space:]')
((STEP++))
else
((STEP--))
fi
@@ -2474,14 +2044,9 @@ advanced_settings() {
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nSet container timezone.\n\nExamples: Europe/Berlin, America/New_York, Asia/Tokyo\n\nHost timezone: ${_host_timezone:-unknown}\n\nLeave empty to inherit from host." 16 62 "$_ct_timezone" \
3>&1 1>&2 2>&3); then
local tz_test="$result"
[[ "${tz_test:-}" == Etc/* ]] && tz_test="host" # pct doesn't accept Etc/* zones
if validate_timezone "$tz_test"; then
_ct_timezone="$tz_test"
((STEP++))
else
whiptail --msgbox "Invalid timezone: '$result'\n\nTimezone must exist in /usr/share/zoneinfo/\n\nExamples:\n- Europe/Berlin\n- America/New_York\n- Asia/Tokyo\n- UTC" 14 58
fi
_ct_timezone="$result"
[[ "${_ct_timezone:-}" == Etc/* ]] && _ct_timezone="host" # pct doesn't accept Etc/* zones
((STEP++))
else
((STEP--))
fi
@@ -3278,7 +2843,6 @@ start() {
elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then
VERBOSE="no"
set_std_mode
ensure_profile_loaded
update_script
cleanup_lxc
else
@@ -3304,7 +2868,6 @@ start() {
exit
;;
esac
ensure_profile_loaded
update_script
cleanup_lxc
fi
@@ -4743,88 +4306,50 @@ create_lxc_container() {
-rootfs $CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}"
fi
# Lock by template file (avoid concurrent template downloads/validation)
# Lock by template file (avoid concurrent downloads/creates)
lockfile="/tmp/template.${TEMPLATE}.lock"
# Cleanup stale lock files (older than 1 hour - likely from crashed processes)
if [[ -f "$lockfile" ]]; then
local lock_age=$(($(date +%s) - $(stat -c %Y "$lockfile" 2>/dev/null || echo 0)))
if [[ $lock_age -gt 3600 ]]; then
msg_warn "Removing stale template lock file (age: ${lock_age}s)"
rm -f "$lockfile"
fi
fi
exec 9>"$lockfile" || {
msg_error "Failed to create lock file '$lockfile'."
exit 200
}
# Retry logic for template lock (another container creation may be running)
local lock_attempts=0
local max_lock_attempts=10
local lock_wait_time=30
while ! flock -w "$lock_wait_time" 9; do
lock_attempts=$((lock_attempts + 1))
if [[ $lock_attempts -ge $max_lock_attempts ]]; then
msg_error "Timeout while waiting for template lock after ${max_lock_attempts} attempts."
msg_custom "💡" "${YW}" "Another container creation may be stuck. Check running processes or remove: $lockfile"
exit 211
fi
msg_custom "⏳" "${YW}" "Another container is being created with this template. Waiting... (attempt ${lock_attempts}/${max_lock_attempts})"
done
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"
# Validate template before pct create (while holding lock)
if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH" 2>/dev/null || echo 0)" -lt 1000000 ]]; then
msg_info "Template file missing or too small downloading"
rm -f "$TEMPLATE_PATH"
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1
msg_ok "Template downloaded"
elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
if [[ -n "$ONLINE_TEMPLATE" ]]; then
msg_info "Template appears corrupted re-downloading"
rm -f "$TEMPLATE_PATH"
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1
msg_ok "Template re-downloaded"
else
msg_warn "Template appears corrupted, but no online version exists. Skipping re-download."
fi
fi
# Release lock after template validation - pct create has its own internal locking
exec 9>&-
msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS"
msg_debug "Logfile: $LOGFILE"
# 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}. Checking error..."
msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Validating template..."
# Check if template issue - retry with fresh download
if grep -qiE 'unable to open|corrupt|invalid' "$LOGFILE"; then
msg_info "Template may be corrupted re-downloading"
# 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" >/dev/null 2>&1
msg_ok "Template re-downloaded"
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"
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_ok "Trying local storage fallback"
msg_info "Downloading template to local"
msg_info "Downloading template to local..."
pveam download local "$TEMPLATE" >/dev/null 2>&1
msg_ok "Template downloaded to local"
else
msg_ok "Trying local storage fallback"
fi
if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
# Local fallback also failed - check for LXC stack version issue

View File

@@ -38,6 +38,8 @@ load_functions() {
icons
default_vars
set_std_mode
# Note: get_lxc_ip() is NOT called here automatically
# Call it explicitly when you need LOCAL_IP variable
}
# ------------------------------------------------------------------------------
@@ -127,34 +129,6 @@ icons() {
HOURGLASS="${TAB}${TAB}"
}
# ------------------------------------------------------------------------------
# ensure_profile_loaded()
#
# - Sources /etc/profile.d/*.sh scripts if not already loaded
# - Fixes PATH issues when running via pct enter/exec (non-login shells)
# - Safe to call multiple times (uses guard variable)
# - Should be called in update_script() or any script running inside LXC
# ------------------------------------------------------------------------------
ensure_profile_loaded() {
# Skip if already loaded or running on Proxmox host
[[ -n "${_PROFILE_LOADED:-}" ]] && return
command -v pveversion &>/dev/null && return
# Source all profile.d scripts to ensure PATH is complete
if [[ -d /etc/profile.d ]]; then
for script in /etc/profile.d/*.sh; do
[[ -r "$script" ]] && source "$script"
done
fi
# Also ensure /usr/local/bin is in PATH (common install location)
if [[ ":$PATH:" != *":/usr/local/bin:"* ]]; then
export PATH="/usr/local/bin:$PATH"
fi
export _PROFILE_LOADED=1
}
# ------------------------------------------------------------------------------
# default_vars()
#