mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-02-14 17:23:25 +01:00
Compare commits
81 Commits
api_refact
...
github-act
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b72bc0ec3 | ||
|
|
b3e3ed5fb3 | ||
|
|
7b767ff58b | ||
|
|
79baf4360e | ||
|
|
773f3f67b8 | ||
|
|
9a95d81f17 | ||
|
|
ed9a6d9d4b | ||
|
|
c6005af29d | ||
|
|
911f533e6a | ||
|
|
cecadf5681 | ||
|
|
f762155870 | ||
|
|
867d02d969 | ||
|
|
1a9bbdd6e7 | ||
|
|
3e0db150bd | ||
|
|
6b3653627c | ||
|
|
733ad75dc1 | ||
|
|
60aaaab3e7 | ||
|
|
1f735cb31f | ||
|
|
adcbe8dae2 | ||
|
|
bba520dbbf | ||
|
|
b43963d352 | ||
|
|
0957a23366 | ||
|
|
9f3588dd8d | ||
|
|
f23414a1a8 | ||
|
|
2a8bb76dcf | ||
|
|
bf85ef2a8b | ||
|
|
cc89cdbab1 | ||
|
|
d6f3f03f8a | ||
|
|
55e35d7f11 | ||
|
|
3b9f8d4a93 | ||
|
|
6c5377adec | ||
|
|
eeb349346b | ||
|
|
d271c16799 | ||
|
|
4774c54861 | ||
|
|
4bf63bae35 | ||
|
|
f2b7c9638d | ||
|
|
551f89e46f | ||
|
|
4f571a1eb6 | ||
|
|
3156e8e363 | ||
|
|
60ebdc97a5 | ||
|
|
20ec369338 | ||
|
|
4907a906c3 | ||
|
|
27e3a4301e | ||
|
|
43fb75f2b4 | ||
|
|
899d0e4baa | ||
|
|
85584b105d | ||
|
|
3fe6f50414 | ||
|
|
724a066aed | ||
|
|
cd6e8ecbbe | ||
|
|
8083c0c0e1 | ||
|
|
29836f35ed | ||
|
|
17d3d4297c | ||
|
|
2b921736e6 | ||
|
|
ddabe81dd8 | ||
|
|
19c5671d3f | ||
|
|
2326520d17 | ||
|
|
7964d39e32 | ||
|
|
f7cf7c8adc | ||
|
|
744191cb84 | ||
|
|
291ed4c5ad | ||
|
|
f9612c5aba | ||
|
|
403a839ac0 | ||
|
|
41c89413ef | ||
|
|
fa11528a7b | ||
|
|
2a03c86384 | ||
|
|
57b4e10b93 | ||
|
|
4b22c7cc2d | ||
|
|
79fd0d1dda | ||
|
|
280778d53b | ||
|
|
1c3a3107f1 | ||
|
|
ee2c3a20ee | ||
|
|
5ee4f4e34b | ||
|
|
4b0e893bf1 | ||
|
|
3676157a7c | ||
|
|
e437e50882 | ||
|
|
6e9a94b46d | ||
|
|
137ae6775e | ||
|
|
dd8c998d43 | ||
|
|
b215bac01d | ||
|
|
c3cd9df12f | ||
|
|
406d53ea2f |
90
CHANGELOG.md
90
CHANGELOG.md
@@ -401,8 +401,98 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-02-14
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: overwriteable app version [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11753](https://github.com/community-scripts/ProxmoxVE/pull/11753))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: validate container IDs cluster-wide across all nodes [@MickLesk](https://github.com/MickLesk) ([#11906](https://github.com/community-scripts/ProxmoxVE/pull/11906))
|
||||
- core: improve error reporting with structured error strings and better categorization + output formatting [@MickLesk](https://github.com/MickLesk) ([#11907](https://github.com/community-scripts/ProxmoxVE/pull/11907))
|
||||
- core: unified logging system with combined logs [@MickLesk](https://github.com/MickLesk) ([#11761](https://github.com/community-scripts/ProxmoxVE/pull/11761))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- lxc-updater: add patchmon aware [@failure101](https://github.com/failure101) ([#11905](https://github.com/community-scripts/ProxmoxVE/pull/11905))
|
||||
|
||||
### ❔ Uncategorized
|
||||
|
||||
- Disable UniFi script - APT packages no longer available [@Copilot](https://github.com/Copilot) ([#11898](https://github.com/community-scripts/ProxmoxVE/pull/11898))
|
||||
|
||||
## 2026-02-13
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- OpenWebUI: pin numba constraint [@MickLesk](https://github.com/MickLesk) ([#11874](https://github.com/community-scripts/ProxmoxVE/pull/11874))
|
||||
- Planka: add migrate step to update function [@ZimmermannLeon](https://github.com/ZimmermannLeon) ([#11877](https://github.com/community-scripts/ProxmoxVE/pull/11877))
|
||||
- Pangolin: switch sqlite-specific back to generic [@MickLesk](https://github.com/MickLesk) ([#11868](https://github.com/community-scripts/ProxmoxVE/pull/11868))
|
||||
- [Hotfix] Jotty: Copy contents of config backup into /opt/jotty/config [@vhsdream](https://github.com/vhsdream) ([#11864](https://github.com/community-scripts/ProxmoxVE/pull/11864))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor: Radicale [@vhsdream](https://github.com/vhsdream) ([#11850](https://github.com/community-scripts/ProxmoxVE/pull/11850))
|
||||
- chore(donetick): add config entry for v0.1.73 [@tomfrenzel](https://github.com/tomfrenzel) ([#11872](https://github.com/community-scripts/ProxmoxVE/pull/11872))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- core: retry reporting with fallback payloads [@MickLesk](https://github.com/MickLesk) ([#11885](https://github.com/community-scripts/ProxmoxVE/pull/11885))
|
||||
|
||||
### 📡 API
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- error-handler: Implement json_escape and enhance error handling [@MickLesk](https://github.com/MickLesk) ([#11875](https://github.com/community-scripts/ProxmoxVE/pull/11875))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- #### 📝 Script Information
|
||||
|
||||
- SQLServer-2025: add PVE9/Kernel 6.x incompatibility warning [@MickLesk](https://github.com/MickLesk) ([#11829](https://github.com/community-scripts/ProxmoxVE/pull/11829))
|
||||
|
||||
## 2026-02-12
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- EMQX: increase disk to 6GB and add optional MQ disable prompt [@MickLesk](https://github.com/MickLesk) ([#11844](https://github.com/community-scripts/ProxmoxVE/pull/11844))
|
||||
- Increased the Grafana container default disk size. [@shtefko](https://github.com/shtefko) ([#11840](https://github.com/community-scripts/ProxmoxVE/pull/11840))
|
||||
- Pangolin: Update database generation command in install script [@tremor021](https://github.com/tremor021) ([#11825](https://github.com/community-scripts/ProxmoxVE/pull/11825))
|
||||
- Deluge: add python3-setuptools as dep [@MickLesk](https://github.com/MickLesk) ([#11833](https://github.com/community-scripts/ProxmoxVE/pull/11833))
|
||||
- Dispatcharr: migrate to uv sync [@MickLesk](https://github.com/MickLesk) ([#11831](https://github.com/community-scripts/ProxmoxVE/pull/11831))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Archlinux-VM: fix LVM/LVM-thin storage and improve error reporting | VM's add correct exit_code for analytics [@MickLesk](https://github.com/MickLesk) ([#11842](https://github.com/community-scripts/ProxmoxVE/pull/11842))
|
||||
- Debian13-VM: Optimize First Boot & add noCloud/Cloud Selection [@MickLesk](https://github.com/MickLesk) ([#11810](https://github.com/community-scripts/ProxmoxVE/pull/11810))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- tools.func: auto-detect binary vs armored GPG keys in setup_deb822_repo [@MickLesk](https://github.com/MickLesk) ([#11841](https://github.com/community-scripts/ProxmoxVE/pull/11841))
|
||||
- core: remove old Go API and extend misc/api.func with new backend [@MickLesk](https://github.com/MickLesk) ([#11822](https://github.com/community-scripts/ProxmoxVE/pull/11822))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- error_handler: prevent stuck 'installing' status [@MickLesk](https://github.com/MickLesk) ([#11845](https://github.com/community-scripts/ProxmoxVE/pull/11845))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Tailscale: fix DNS check and keyrings directory issues [@MickLesk](https://github.com/MickLesk) ([#11837](https://github.com/community-scripts/ProxmoxVE/pull/11837))
|
||||
|
||||
## 2026-02-11
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
@@ -9,7 +9,7 @@ APP="Alpine-Grafana"
|
||||
var_tags="${var_tags:-alpine;monitoring}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-256}"
|
||||
var_disk="${var_disk:-1}"
|
||||
var_disk="${var_disk:-2}"
|
||||
var_os="${var_os:-alpine}"
|
||||
var_version="${var_version:-3.23}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
@@ -28,6 +28,7 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
msg_info "Updating Deluge"
|
||||
ensure_dependencies python3-setuptools
|
||||
$STD apt update
|
||||
$STD pip3 install deluge[all] --upgrade
|
||||
msg_ok "Updated Deluge"
|
||||
|
||||
@@ -104,7 +104,7 @@ function update_script() {
|
||||
cd /opt/dispatcharr
|
||||
rm -rf .venv
|
||||
$STD uv venv --clear
|
||||
$STD uv pip install -r requirements.txt --index-strategy unsafe-best-match
|
||||
$STD uv sync
|
||||
$STD uv pip install gunicorn gevent celery redis daphne
|
||||
msg_ok "Updated Dispatcharr Backend"
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ function update_script() {
|
||||
|
||||
msg_info "Restoring Configurations"
|
||||
mv /opt/selfhosted.yaml /opt/donetick/config
|
||||
sed -i '/capacitor:\/\/localhost/d' /opt/donetick/config/selfhosted.yaml
|
||||
grep -q 'http://localhost"$' /opt/donetick/config/selfhosted.yaml || sed -i '/https:\/\/localhost"$/a\ - "http://localhost"' /opt/donetick/config/selfhosted.yaml
|
||||
grep -q 'capacitor://localhost' /opt/donetick/config/selfhosted.yaml || sed -i '/http:\/\/localhost"$/a\ - "capacitor://localhost"' /opt/donetick/config/selfhosted.yaml
|
||||
mv /opt/donetick.db /opt/donetick
|
||||
msg_ok "Restored Configurations"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ APP="EMQX"
|
||||
var_tags="${var_tags:-mqtt}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-1024}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_disk="${var_disk:-6}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
@@ -9,7 +9,7 @@ APP="Grafana"
|
||||
var_tags="${var_tags:-monitoring;visualization}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-512}"
|
||||
var_disk="${var_disk:-2}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
@@ -46,7 +46,7 @@ function update_script() {
|
||||
msg_info "Restoring configuration & data"
|
||||
mv /opt/app.env /opt/jotty/.env
|
||||
[[ -d /opt/data ]] && mv /opt/data /opt/jotty/data
|
||||
[[ -d /opt/jotty/config ]] && mv /opt/config/* /opt/jotty/config
|
||||
[[ -d /opt/jotty/config ]] && cp -a /opt/config/* /opt/jotty/config && rm -rf /opt/config
|
||||
msg_ok "Restored configuration & data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
|
||||
@@ -44,7 +44,7 @@ function update_script() {
|
||||
|
||||
msg_info "Installing uv-based Open-WebUI"
|
||||
PYTHON_VERSION="3.12" setup_uv
|
||||
$STD uv tool install --python 3.12 open-webui[all]
|
||||
$STD uv tool install --python 3.12 --constraint <(echo "numba>=0.60") open-webui[all]
|
||||
msg_ok "Installed uv-based Open-WebUI"
|
||||
|
||||
msg_info "Restoring data"
|
||||
@@ -126,7 +126,7 @@ EOF
|
||||
|
||||
msg_info "Updating Open WebUI via uv"
|
||||
PYTHON_VERSION="3.12" setup_uv
|
||||
$STD uv tool upgrade --python 3.12 open-webui[all]
|
||||
$STD uv tool install --force --python 3.12 --constraint <(echo "numba>=0.60") open-webui[all]
|
||||
systemctl restart open-webui
|
||||
msg_ok "Updated Open WebUI"
|
||||
msg_ok "Updated successfully!"
|
||||
|
||||
@@ -51,7 +51,7 @@ function update_script() {
|
||||
$STD npm run db:generate
|
||||
$STD npm run build
|
||||
$STD npm run build:cli
|
||||
$STD npm run db:sqlite:push
|
||||
$STD npm run db:push
|
||||
cp -R .next/standalone ./
|
||||
chmod +x ./dist/cli.mjs
|
||||
cp server/db/names.json ./dist/names.json
|
||||
|
||||
@@ -61,6 +61,12 @@ function update_script() {
|
||||
rm -rf "$BK"
|
||||
msg_ok "Restored data"
|
||||
|
||||
msg_ok "Migrate Database"
|
||||
cd /opt/planka
|
||||
$STD npm run db:upgrade
|
||||
$STD npm run db:migrate
|
||||
msg_ok "Migrated Database"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start planka
|
||||
msg_ok "Started Service"
|
||||
|
||||
@@ -28,16 +28,55 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
$STD python3 -m venv /opt/radicale
|
||||
source /opt/radicale/bin/activate
|
||||
$STD python3 -m pip install --upgrade https://github.com/Kozea/Radicale/archive/master.tar.gz
|
||||
msg_ok "Updated ${APP}"
|
||||
if check_for_gh_release "Radicale" "Kozea/Radicale"; then
|
||||
msg_info "Stopping service"
|
||||
systemctl stop radicale
|
||||
msg_ok "Stopped service"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl enable -q --now radicale
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
msg_info "Backing up users file"
|
||||
cp /opt/radicale/users /opt/radicale_users_backup
|
||||
msg_ok "Backed up users file"
|
||||
|
||||
PYTHON_VERSION="3.13" setup_uv
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Radicale" "Kozea/Radicale" "tarball" "latest" "/opt/radicale"
|
||||
|
||||
msg_info "Restoring users file"
|
||||
rm -f /opt/radicale/users
|
||||
mv /opt/radicale_users_backup /opt/radicale/users
|
||||
msg_ok "Restored users file"
|
||||
|
||||
if grep -q 'start.sh' /etc/systemd/system/radicale.service; then
|
||||
sed -i -e '/^Description/i[Unit]' \
|
||||
-e '\|^ExecStart|iWorkingDirectory=/opt/radicale' \
|
||||
-e 's|^ExecStart=.*|ExecStart=/usr/local/bin/uv run -m radicale --config /etc/radicale/config|' /etc/systemd/system/radicale.service
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
if [[ ! -f /etc/radicale/config ]]; then
|
||||
msg_info "Migrating to config file (/etc/radicale/config)"
|
||||
mkdir -p /etc/radicale
|
||||
cat <<EOF >/etc/radicale/config
|
||||
[server]
|
||||
hosts = 0.0.0.0:5232
|
||||
|
||||
[auth]
|
||||
type = htpasswd
|
||||
htpasswd_filename = /opt/radicale/users
|
||||
htpasswd_encryption = sha512
|
||||
|
||||
[storage]
|
||||
type = multifilesystem
|
||||
filesystem_folder = /var/lib/radicale/collections
|
||||
|
||||
[web]
|
||||
type = internal
|
||||
EOF
|
||||
msg_ok "Migrated to config (/etc/radicale/config)"
|
||||
fi
|
||||
msg_info "Starting service"
|
||||
systemctl start radicale
|
||||
msg_ok "Started service"
|
||||
msg_ok "Updated Successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 1024,
|
||||
"hdd": 4,
|
||||
"hdd": 6,
|
||||
"os": "debian",
|
||||
"version": "13"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-02-12T06:25:40Z",
|
||||
"generated": "2026-02-14T12:08:41Z",
|
||||
"versions": [
|
||||
{
|
||||
"slug": "2fauth",
|
||||
@@ -67,9 +67,9 @@
|
||||
{
|
||||
"slug": "autobrr",
|
||||
"repo": "autobrr/autobrr",
|
||||
"version": "v1.72.1",
|
||||
"version": "v1.73.0",
|
||||
"pinned": false,
|
||||
"date": "2026-01-30T12:57:58Z"
|
||||
"date": "2026-02-13T16:37:28Z"
|
||||
},
|
||||
{
|
||||
"slug": "autocaliweb",
|
||||
@@ -193,16 +193,16 @@
|
||||
{
|
||||
"slug": "cleanuparr",
|
||||
"repo": "Cleanuparr/Cleanuparr",
|
||||
"version": "v2.5.1",
|
||||
"version": "v2.6.1",
|
||||
"pinned": false,
|
||||
"date": "2026-01-11T00:46:17Z"
|
||||
"date": "2026-02-13T10:00:19Z"
|
||||
},
|
||||
{
|
||||
"slug": "cloudreve",
|
||||
"repo": "cloudreve/cloudreve",
|
||||
"version": "4.13.0",
|
||||
"version": "4.14.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-05T12:53:24Z"
|
||||
"date": "2026-02-14T06:05:06Z"
|
||||
},
|
||||
{
|
||||
"slug": "comfyui",
|
||||
@@ -284,9 +284,9 @@
|
||||
{
|
||||
"slug": "domain-locker",
|
||||
"repo": "Lissy93/domain-locker",
|
||||
"version": "v0.1.3",
|
||||
"version": "v0.1.4",
|
||||
"pinned": false,
|
||||
"date": "2026-02-11T10:03:32Z"
|
||||
"date": "2026-02-14T07:41:29Z"
|
||||
},
|
||||
{
|
||||
"slug": "domain-monitor",
|
||||
@@ -298,9 +298,9 @@
|
||||
{
|
||||
"slug": "donetick",
|
||||
"repo": "donetick/donetick",
|
||||
"version": "v0.1.71",
|
||||
"version": "v0.1.73",
|
||||
"pinned": false,
|
||||
"date": "2026-02-11T06:01:13Z"
|
||||
"date": "2026-02-12T23:42:30Z"
|
||||
},
|
||||
{
|
||||
"slug": "drawio",
|
||||
@@ -354,9 +354,9 @@
|
||||
{
|
||||
"slug": "firefly",
|
||||
"repo": "firefly-iii/firefly-iii",
|
||||
"version": "v6.4.18",
|
||||
"version": "v6.4.19",
|
||||
"pinned": false,
|
||||
"date": "2026-02-08T07:28:00Z"
|
||||
"date": "2026-02-14T11:55:40Z"
|
||||
},
|
||||
{
|
||||
"slug": "fladder",
|
||||
@@ -403,9 +403,9 @@
|
||||
{
|
||||
"slug": "ghostfolio",
|
||||
"repo": "ghostfolio/ghostfolio",
|
||||
"version": "2.237.0",
|
||||
"version": "2.238.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-08T13:59:53Z"
|
||||
"date": "2026-02-12T18:28:55Z"
|
||||
},
|
||||
{
|
||||
"slug": "gitea",
|
||||
@@ -445,9 +445,9 @@
|
||||
{
|
||||
"slug": "gotify",
|
||||
"repo": "gotify/server",
|
||||
"version": "v2.8.0",
|
||||
"version": "v2.9.0",
|
||||
"pinned": false,
|
||||
"date": "2026-01-02T11:56:16Z"
|
||||
"date": "2026-02-13T15:22:31Z"
|
||||
},
|
||||
{
|
||||
"slug": "grist",
|
||||
@@ -508,9 +508,9 @@
|
||||
{
|
||||
"slug": "homarr",
|
||||
"repo": "homarr-labs/homarr",
|
||||
"version": "v1.53.0",
|
||||
"version": "v1.53.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-06T19:42:58Z"
|
||||
"date": "2026-02-13T19:47:11Z"
|
||||
},
|
||||
{
|
||||
"slug": "homebox",
|
||||
@@ -543,9 +543,9 @@
|
||||
{
|
||||
"slug": "huntarr",
|
||||
"repo": "plexguide/Huntarr.io",
|
||||
"version": "9.2.3",
|
||||
"version": "9.2.4.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-07T04:44:20Z"
|
||||
"date": "2026-02-12T22:17:47Z"
|
||||
},
|
||||
{
|
||||
"slug": "immich-public-proxy",
|
||||
@@ -571,16 +571,16 @@
|
||||
{
|
||||
"slug": "invoiceninja",
|
||||
"repo": "invoiceninja/invoiceninja",
|
||||
"version": "v5.12.57",
|
||||
"version": "v5.12.59",
|
||||
"pinned": false,
|
||||
"date": "2026-02-11T23:08:56Z"
|
||||
"date": "2026-02-13T02:26:13Z"
|
||||
},
|
||||
{
|
||||
"slug": "jackett",
|
||||
"repo": "Jackett/Jackett",
|
||||
"version": "v0.24.1098",
|
||||
"version": "v0.24.1109",
|
||||
"pinned": false,
|
||||
"date": "2026-02-12T05:56:25Z"
|
||||
"date": "2026-02-14T05:54:26Z"
|
||||
},
|
||||
{
|
||||
"slug": "jellystat",
|
||||
@@ -599,9 +599,9 @@
|
||||
{
|
||||
"slug": "jotty",
|
||||
"repo": "fccview/jotty",
|
||||
"version": "1.19.1",
|
||||
"version": "1.20.0",
|
||||
"pinned": false,
|
||||
"date": "2026-01-26T21:30:39Z"
|
||||
"date": "2026-02-12T09:23:30Z"
|
||||
},
|
||||
{
|
||||
"slug": "kapowarr",
|
||||
@@ -823,16 +823,16 @@
|
||||
{
|
||||
"slug": "metube",
|
||||
"repo": "alexta69/metube",
|
||||
"version": "2026.02.08",
|
||||
"version": "2026.02.14",
|
||||
"pinned": false,
|
||||
"date": "2026-02-08T17:01:37Z"
|
||||
"date": "2026-02-14T07:49:11Z"
|
||||
},
|
||||
{
|
||||
"slug": "miniflux",
|
||||
"repo": "miniflux/v2",
|
||||
"version": "2.2.16",
|
||||
"version": "2.2.17",
|
||||
"pinned": false,
|
||||
"date": "2026-01-07T03:26:27Z"
|
||||
"date": "2026-02-13T20:30:17Z"
|
||||
},
|
||||
{
|
||||
"slug": "monica",
|
||||
@@ -998,9 +998,9 @@
|
||||
{
|
||||
"slug": "pangolin",
|
||||
"repo": "fosrl/pangolin",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"pinned": false,
|
||||
"date": "2026-02-12T06:10:19Z"
|
||||
"date": "2026-02-13T23:01:29Z"
|
||||
},
|
||||
{
|
||||
"slug": "paperless-ai",
|
||||
@@ -1026,9 +1026,9 @@
|
||||
{
|
||||
"slug": "patchmon",
|
||||
"repo": "PatchMon/PatchMon",
|
||||
"version": "v1.3.7",
|
||||
"version": "v1.4.0",
|
||||
"pinned": false,
|
||||
"date": "2025-12-25T11:08:14Z"
|
||||
"date": "2026-02-13T10:39:03Z"
|
||||
},
|
||||
{
|
||||
"slug": "paymenter",
|
||||
@@ -1096,9 +1096,9 @@
|
||||
{
|
||||
"slug": "pocketbase",
|
||||
"repo": "pocketbase/pocketbase",
|
||||
"version": "v0.36.2",
|
||||
"version": "v0.36.3",
|
||||
"pinned": false,
|
||||
"date": "2026-02-01T08:12:42Z"
|
||||
"date": "2026-02-13T18:38:58Z"
|
||||
},
|
||||
{
|
||||
"slug": "pocketid",
|
||||
@@ -1219,6 +1219,13 @@
|
||||
"pinned": false,
|
||||
"date": "2025-11-16T22:39:01Z"
|
||||
},
|
||||
{
|
||||
"slug": "radicale",
|
||||
"repo": "Kozea/Radicale",
|
||||
"version": "v3.6.0",
|
||||
"pinned": false,
|
||||
"date": "2026-01-10T06:56:46Z"
|
||||
},
|
||||
{
|
||||
"slug": "rclone",
|
||||
"repo": "rclone/rclone",
|
||||
@@ -1292,9 +1299,9 @@
|
||||
{
|
||||
"slug": "scraparr",
|
||||
"repo": "thecfu/scraparr",
|
||||
"version": "v3.0.1",
|
||||
"version": "v3.0.3",
|
||||
"pinned": false,
|
||||
"date": "2026-02-11T17:42:23Z"
|
||||
"date": "2026-02-12T14:20:56Z"
|
||||
},
|
||||
{
|
||||
"slug": "seelf",
|
||||
@@ -1306,9 +1313,9 @@
|
||||
{
|
||||
"slug": "semaphore",
|
||||
"repo": "semaphoreui/semaphore",
|
||||
"version": "v2.16.51",
|
||||
"version": "v2.17.0",
|
||||
"pinned": false,
|
||||
"date": "2026-01-12T16:26:38Z"
|
||||
"date": "2026-02-13T21:08:30Z"
|
||||
},
|
||||
{
|
||||
"slug": "shelfmark",
|
||||
@@ -1404,9 +1411,9 @@
|
||||
{
|
||||
"slug": "tandoor",
|
||||
"repo": "TandoorRecipes/recipes",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-08T13:23:02Z"
|
||||
"date": "2026-02-13T15:57:27Z"
|
||||
},
|
||||
{
|
||||
"slug": "tasmoadmin",
|
||||
@@ -1432,9 +1439,9 @@
|
||||
{
|
||||
"slug": "termix",
|
||||
"repo": "Termix-SSH/Termix",
|
||||
"version": "release-1.11.0-tag",
|
||||
"version": "release-1.11.1-tag",
|
||||
"pinned": false,
|
||||
"date": "2026-01-25T02:09:52Z"
|
||||
"date": "2026-02-13T04:49:16Z"
|
||||
},
|
||||
{
|
||||
"slug": "the-lounge",
|
||||
@@ -1460,9 +1467,9 @@
|
||||
{
|
||||
"slug": "tianji",
|
||||
"repo": "msgbyte/tianji",
|
||||
"version": "v1.31.10",
|
||||
"version": "v1.31.13",
|
||||
"pinned": false,
|
||||
"date": "2026-02-04T17:21:04Z"
|
||||
"date": "2026-02-13T16:30:09Z"
|
||||
},
|
||||
{
|
||||
"slug": "traccar",
|
||||
@@ -1509,9 +1516,9 @@
|
||||
{
|
||||
"slug": "tududi",
|
||||
"repo": "chrisvel/tududi",
|
||||
"version": "v0.88.4",
|
||||
"version": "v0.88.5",
|
||||
"pinned": false,
|
||||
"date": "2026-01-20T15:11:58Z"
|
||||
"date": "2026-02-13T13:54:14Z"
|
||||
},
|
||||
{
|
||||
"slug": "tunarr",
|
||||
@@ -1551,16 +1558,16 @@
|
||||
{
|
||||
"slug": "upsnap",
|
||||
"repo": "seriousm4x/UpSnap",
|
||||
"version": "5.2.7",
|
||||
"version": "5.2.8",
|
||||
"pinned": false,
|
||||
"date": "2026-01-07T23:48:00Z"
|
||||
"date": "2026-02-13T00:02:37Z"
|
||||
},
|
||||
{
|
||||
"slug": "uptimekuma",
|
||||
"repo": "louislam/uptime-kuma",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"pinned": false,
|
||||
"date": "2026-02-07T02:31:49Z"
|
||||
"date": "2026-02-13T16:07:33Z"
|
||||
},
|
||||
{
|
||||
"slug": "vaultwarden",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 512,
|
||||
"hdd": 2,
|
||||
"hdd": 4,
|
||||
"os": "debian",
|
||||
"version": "13"
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 256,
|
||||
"hdd": 1,
|
||||
"hdd": 2,
|
||||
"os": "alpine",
|
||||
"version": "3.23"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"documentation": "https://radicale.org/master.html#documentation-1",
|
||||
"website": "https://radicale.org/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/radicale.webp",
|
||||
"config_path": "/etc/radicale/config or ~/.config/radicale/config",
|
||||
"config_path": "/etc/radicale/config",
|
||||
"description": "Radicale is a small but powerful CalDAV (calendars, to-do lists) and CardDAV (contacts)",
|
||||
"install_methods": [
|
||||
{
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "SQL Server (2025) SQLPAL is incompatible with Proxmox VE 9 (Kernel 6.12+) in LXC containers. Use a VM instead or the SQL-Server 2022 LXC.",
|
||||
"type": "warning"
|
||||
},
|
||||
{
|
||||
"text": "If you choose not to run the installation setup, execute: `/opt/mssql/bin/mssql-conf setup` in LXC shell.",
|
||||
"type": "info"
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/ubiquiti-unifi.webp",
|
||||
"config_path": "",
|
||||
"description": "UniFi Network Server is a software that helps manage and monitor UniFi networks (Wi-Fi, Ethernet, etc.) by providing an intuitive user interface and advanced features. It allows network administrators to configure, monitor, and upgrade network devices, as well as view network statistics, client devices, and historical events. The aim of the application is to make the management of UniFi networks easier and more efficient.",
|
||||
"disable": true,
|
||||
"disable_description": "This script is disabled because UniFi no longer delivers APT packages for Debian systems. The installation relies on APT repositories that are no longer maintained or available. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11876",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
|
||||
@@ -16,7 +16,8 @@ update_os
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
python3-pip \
|
||||
python3-libtorrent
|
||||
python3-libtorrent \
|
||||
python3-setuptools
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
msg_info "Installing Deluge"
|
||||
|
||||
@@ -37,7 +37,7 @@ fetch_and_deploy_gh_release "dispatcharr" "Dispatcharr/Dispatcharr" "tarball"
|
||||
msg_info "Installing Python Dependencies with uv"
|
||||
cd /opt/dispatcharr
|
||||
$STD uv venv --clear
|
||||
$STD uv pip install -r requirements.txt --index-strategy unsafe-best-match
|
||||
$STD uv sync
|
||||
$STD uv pip install gunicorn gevent celery redis daphne
|
||||
msg_ok "Installed Python Dependencies"
|
||||
|
||||
|
||||
@@ -38,6 +38,18 @@ rm -f "$DEB_FILE"
|
||||
echo "$LATEST_VERSION" >~/.emqx
|
||||
msg_ok "Installed EMQX"
|
||||
|
||||
read -r -p "${TAB3}Would you like to disable the EMQX MQ feature? (reduces disk/CPU usage) <y/N> " prompt
|
||||
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||
msg_info "Disabling EMQX MQ feature"
|
||||
mkdir -p /etc/emqx
|
||||
if ! grep -q "^mq.enable" /etc/emqx/emqx.conf 2>/dev/null; then
|
||||
echo "mq.enable = false" >>/etc/emqx/emqx.conf
|
||||
else
|
||||
sed -i 's/^mq.enable.*/mq.enable = false/' /etc/emqx/emqx.conf
|
||||
fi
|
||||
msg_ok "Disabled EMQX MQ feature"
|
||||
fi
|
||||
|
||||
msg_info "Starting EMQX service"
|
||||
$STD systemctl enable -q --now emqx
|
||||
msg_ok "Enabled EMQX service"
|
||||
|
||||
@@ -24,7 +24,7 @@ setup_hwaccel
|
||||
PYTHON_VERSION="3.12" setup_uv
|
||||
|
||||
msg_info "Installing Open WebUI"
|
||||
$STD uv tool install --python 3.12 open-webui[all]
|
||||
$STD uv tool install --python 3.12 --constraint <(echo "numba>=0.60") open-webui[all]
|
||||
msg_ok "Installed Open WebUI"
|
||||
|
||||
read -r -p "${TAB3}Would you like to add Ollama? <y/N> " prompt
|
||||
|
||||
@@ -178,7 +178,7 @@ http:
|
||||
servers:
|
||||
- url: "http://$LOCAL_IP:3000"
|
||||
EOF
|
||||
$STD npm run db:sqlite:push
|
||||
$STD npm run db:push
|
||||
|
||||
. /etc/os-release
|
||||
if [ "$VERSION_CODENAME" = "trixie" ]; then
|
||||
|
||||
@@ -14,42 +14,51 @@ network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
apache2-utils \
|
||||
python3-pip \
|
||||
python3-venv
|
||||
$STD apt install -y apache2-utils
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PYTHON_VERSION="3.13" setup_uv
|
||||
fetch_and_deploy_gh_release "Radicale" "Kozea/Radicale" "tarball" "latest" "/opt/radicale"
|
||||
|
||||
msg_info "Setting up Radicale"
|
||||
python3 -m venv /opt/radicale
|
||||
source /opt/radicale/bin/activate
|
||||
$STD python3 -m pip install --upgrade https://github.com/Kozea/Radicale/archive/master.tar.gz
|
||||
cd /opt/radicale
|
||||
RNDPASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
|
||||
$STD htpasswd -c -b -5 /opt/radicale/users admin $RNDPASS
|
||||
$STD htpasswd -c -b -5 /opt/radicale/users admin "$RNDPASS"
|
||||
{
|
||||
echo "Radicale Credentials"
|
||||
echo "Admin User: admin"
|
||||
echo "Admin Password: $RNDPASS"
|
||||
} >>~/radicale.creds
|
||||
msg_ok "Done setting up Radicale"
|
||||
|
||||
msg_info "Setup Service"
|
||||
mkdir -p /etc/radicale
|
||||
cat <<EOF >/etc/radicale/config
|
||||
[server]
|
||||
hosts = 0.0.0.0:5232
|
||||
|
||||
cat <<EOF >/opt/radicale/start.sh
|
||||
#!/usr/bin/env bash
|
||||
source /opt/radicale/bin/activate
|
||||
python3 -m radicale --storage-filesystem-folder=/var/lib/radicale/collections --hosts 0.0.0.0:5232 --auth-type htpasswd --auth-htpasswd-filename /opt/radicale/users --auth-htpasswd-encryption sha512
|
||||
[auth]
|
||||
type = htpasswd
|
||||
htpasswd_filename = /opt/radicale/users
|
||||
htpasswd_encryption = sha512
|
||||
|
||||
[storage]
|
||||
type = multifilesystem
|
||||
filesystem_folder = /var/lib/radicale/collections
|
||||
|
||||
[web]
|
||||
type = internal
|
||||
EOF
|
||||
msg_ok "Set up Radicale"
|
||||
|
||||
chmod +x /opt/radicale/start.sh
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/radicale.service
|
||||
[Unit]
|
||||
Description=A simple CalDAV (calendar) and CardDAV (contact) server
|
||||
After=network.target
|
||||
Requires=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/radicale/start.sh
|
||||
WorkingDirectory=/opt/radicale
|
||||
ExecStart=/usr/local/bin/uv run -m radicale --config /etc/radicale/config
|
||||
Restart=on-failure
|
||||
# User=radicale
|
||||
# Deny other users access to the calendar data
|
||||
|
||||
@@ -15,16 +15,18 @@ update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y apt-transport-https
|
||||
curl -fsSL "https://dl.ui.com/unifi/unifi-repo.gpg" -o "/usr/share/keyrings/unifi-repo.gpg"
|
||||
cat <<EOF | sudo tee /etc/apt/sources.list.d/100-ubnt-unifi.sources >/dev/null
|
||||
Types: deb
|
||||
URIs: https://www.ui.com/downloads/unifi/debian
|
||||
Suites: stable
|
||||
Components: ubiquiti
|
||||
Architectures: amd64
|
||||
Signed-By: /usr/share/keyrings/unifi-repo.gpg
|
||||
EOF
|
||||
$STD apt update
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
setup_deb822_repo \
|
||||
"unifi" \
|
||||
"https://dl.ui.com/unifi/unifi-repo.gpg" \
|
||||
"https://www.ui.com/downloads/unifi/debian" \
|
||||
"stable" \
|
||||
"ubiquiti" \
|
||||
"amd64"
|
||||
|
||||
JAVA_VERSION="21" setup_java
|
||||
|
||||
if lscpu | grep -q 'avx'; then
|
||||
|
||||
296
misc/api.func
296
misc/api.func
@@ -153,7 +153,7 @@ explain_exit_code() {
|
||||
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||
127) echo "Command not found" ;;
|
||||
128) echo "Invalid argument to exit" ;;
|
||||
130) echo "Terminated by Ctrl+C (SIGINT)" ;;
|
||||
130) echo "Aborted by user (SIGINT)" ;;
|
||||
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
||||
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
||||
139) echo "Segmentation fault (core dumped)" ;;
|
||||
@@ -233,6 +233,91 @@ explain_exit_code() {
|
||||
esac
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# json_escape()
|
||||
#
|
||||
# - Escapes a string for safe JSON embedding
|
||||
# - Strips ANSI escape sequences and non-printable control characters
|
||||
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
|
||||
# ------------------------------------------------------------------------------
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
# Strip ANSI escape sequences (color codes etc.)
|
||||
s=$(printf '%s' "$s" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g')
|
||||
s=${s//\\/\\\\}
|
||||
s=${s//"/\\"/}
|
||||
s=${s//$'\n'/\\n}
|
||||
s=${s//$'\r'/}
|
||||
s=${s//$'\t'/\\t}
|
||||
# Remove any remaining control characters (0x00-0x1F except those already handled)
|
||||
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037')
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# get_error_text()
|
||||
#
|
||||
# - Returns last 20 lines of the active log (INSTALL_LOG or BUILD_LOG)
|
||||
# - Falls back to combined log or BUILD_LOG if primary is not accessible
|
||||
# - Handles container paths that don't exist on the host
|
||||
# ------------------------------------------------------------------------------
|
||||
get_error_text() {
|
||||
local logfile=""
|
||||
if declare -f get_active_logfile >/dev/null 2>&1; then
|
||||
logfile=$(get_active_logfile)
|
||||
elif [[ -n "${INSTALL_LOG:-}" ]]; then
|
||||
logfile="$INSTALL_LOG"
|
||||
elif [[ -n "${BUILD_LOG:-}" ]]; then
|
||||
logfile="$BUILD_LOG"
|
||||
fi
|
||||
|
||||
# If logfile is inside container (e.g. /root/.install-*), try the host copy
|
||||
if [[ -n "$logfile" && ! -s "$logfile" ]]; then
|
||||
# Try combined log: /tmp/<app>-<CTID>-<SESSION_ID>.log
|
||||
if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]]; then
|
||||
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
|
||||
if [[ -s "$combined_log" ]]; then
|
||||
logfile="$combined_log"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Also try BUILD_LOG as fallback if primary log is empty/missing
|
||||
if [[ -z "$logfile" || ! -s "$logfile" ]] && [[ -n "${BUILD_LOG:-}" && -s "${BUILD_LOG}" ]]; then
|
||||
logfile="$BUILD_LOG"
|
||||
fi
|
||||
|
||||
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
||||
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# build_error_string()
|
||||
#
|
||||
# - Builds a structured error string for telemetry reporting
|
||||
# - Format: "exit_code=<N> | <explanation>\n---\n<last 20 log lines>"
|
||||
# - If no log lines available, returns just the explanation
|
||||
# - Arguments:
|
||||
# * $1: exit_code (numeric)
|
||||
# * $2: log_text (optional, output from get_error_text)
|
||||
# - Returns structured error string via stdout
|
||||
# ------------------------------------------------------------------------------
|
||||
build_error_string() {
|
||||
local exit_code="${1:-1}"
|
||||
local log_text="${2:-}"
|
||||
local explanation
|
||||
explanation=$(explain_exit_code "$exit_code")
|
||||
|
||||
if [[ -n "$log_text" ]]; then
|
||||
# Structured format: header + separator + log lines
|
||||
printf 'exit_code=%s | %s\n---\n%s' "$exit_code" "$explanation" "$log_text"
|
||||
else
|
||||
# No log available - just the explanation with exit code
|
||||
printf 'exit_code=%s | %s' "$exit_code" "$explanation"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# SECTION 2: TELEMETRY FUNCTIONS
|
||||
# ==============================================================================
|
||||
@@ -353,6 +438,9 @@ detect_ram() {
|
||||
# - Never blocks or fails script execution
|
||||
# ------------------------------------------------------------------------------
|
||||
post_to_api() {
|
||||
# Prevent duplicate submissions (post_to_api is called from multiple places)
|
||||
[[ "${POST_TO_API_DONE:-}" == "true" ]] && return 0
|
||||
|
||||
# Silent fail - telemetry should never break scripts
|
||||
command -v curl &>/dev/null || {
|
||||
[[ "${DEV_MODE:-}" == "true" ]] && echo "[DEBUG] curl not found, skipping" >&2
|
||||
@@ -382,7 +470,8 @@ post_to_api() {
|
||||
detect_gpu
|
||||
fi
|
||||
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
||||
local gpu_model="${GPU_MODEL:-}"
|
||||
local gpu_model
|
||||
gpu_model=$(json_escape "${GPU_MODEL:-}")
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
||||
|
||||
# Detect CPU if not already set
|
||||
@@ -390,7 +479,8 @@ post_to_api() {
|
||||
detect_cpu
|
||||
fi
|
||||
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
||||
local cpu_model="${CPU_MODEL:-}"
|
||||
local cpu_model
|
||||
cpu_model=$(json_escape "${CPU_MODEL:-}")
|
||||
|
||||
# Detect RAM if not already set
|
||||
if [[ -z "${RAM_SPEED:-}" ]]; then
|
||||
@@ -440,6 +530,8 @@ EOF
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" &>/dev/null || true
|
||||
fi
|
||||
|
||||
POST_TO_API_DONE=true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -451,6 +543,7 @@ EOF
|
||||
# * ct_type=2 (VM instead of LXC)
|
||||
# * type="vm"
|
||||
# * Disk size without 'G' suffix
|
||||
# - Includes hardware detection: CPU, GPU, RAM speed
|
||||
# - Only executes if DIAGNOSTICS=yes and RANDOM_UUID is set
|
||||
# - Never blocks or fails script execution
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -473,6 +566,29 @@ post_to_api_vm() {
|
||||
pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true
|
||||
fi
|
||||
|
||||
# Detect GPU if not already set
|
||||
if [[ -z "${GPU_VENDOR:-}" ]]; then
|
||||
detect_gpu
|
||||
fi
|
||||
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
||||
local gpu_model
|
||||
gpu_model=$(json_escape "${GPU_MODEL:-}")
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
||||
|
||||
# Detect CPU if not already set
|
||||
if [[ -z "${CPU_VENDOR:-}" ]]; then
|
||||
detect_cpu
|
||||
fi
|
||||
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
||||
local cpu_model
|
||||
cpu_model=$(json_escape "${CPU_MODEL:-}")
|
||||
|
||||
# Detect RAM if not already set
|
||||
if [[ -z "${RAM_SPEED:-}" ]]; then
|
||||
detect_ram
|
||||
fi
|
||||
local ram_speed="${RAM_SPEED:-}"
|
||||
|
||||
# Remove 'G' suffix from disk size
|
||||
local DISK_SIZE_API="${DISK_SIZE%G}"
|
||||
|
||||
@@ -492,6 +608,12 @@ post_to_api_vm() {
|
||||
"os_version": "${var_version:-}",
|
||||
"pve_version": "${pve_version}",
|
||||
"method": "${METHOD:-default}",
|
||||
"cpu_vendor": "${cpu_vendor}",
|
||||
"cpu_model": "${cpu_model}",
|
||||
"gpu_vendor": "${gpu_vendor}",
|
||||
"gpu_model": "${gpu_model}",
|
||||
"gpu_passthrough": "${gpu_passthrough}",
|
||||
"ram_speed": "${ram_speed}",
|
||||
"repo_source": "${REPO_SOURCE}"
|
||||
}
|
||||
EOF
|
||||
@@ -522,9 +644,12 @@ post_update_to_api() {
|
||||
# Silent fail - telemetry should never break scripts
|
||||
command -v curl &>/dev/null || return 0
|
||||
|
||||
# Prevent duplicate submissions
|
||||
# Support "force" mode (3rd arg) to bypass duplicate check for retries after cleanup
|
||||
local force="${3:-}"
|
||||
POST_UPDATE_DONE=${POST_UPDATE_DONE:-false}
|
||||
[[ "$POST_UPDATE_DONE" == "true" ]] && return 0
|
||||
if [[ "$POST_UPDATE_DONE" == "true" && "$force" != "force" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
[[ "${DIAGNOSTICS:-no}" == "no" ]] && return 0
|
||||
[[ -z "${RANDOM_UUID:-}" ]] && return 0
|
||||
@@ -535,12 +660,14 @@ post_update_to_api() {
|
||||
|
||||
# Get GPU info (if detected)
|
||||
local gpu_vendor="${GPU_VENDOR:-unknown}"
|
||||
local gpu_model="${GPU_MODEL:-}"
|
||||
local gpu_model
|
||||
gpu_model=$(json_escape "${GPU_MODEL:-}")
|
||||
local gpu_passthrough="${GPU_PASSTHROUGH:-unknown}"
|
||||
|
||||
# Get CPU info (if detected)
|
||||
local cpu_vendor="${CPU_VENDOR:-unknown}"
|
||||
local cpu_model="${CPU_MODEL:-}"
|
||||
local cpu_model
|
||||
cpu_model=$(json_escape "${CPU_MODEL:-}")
|
||||
|
||||
# Get RAM info (if detected)
|
||||
local ram_speed="${RAM_SPEED:-}"
|
||||
@@ -562,13 +689,20 @@ post_update_to_api() {
|
||||
esac
|
||||
|
||||
# For failed/unknown status, resolve exit code and error description
|
||||
local short_error=""
|
||||
if [[ "$pb_status" == "failed" ]] || [[ "$pb_status" == "unknown" ]]; then
|
||||
if [[ "$raw_exit_code" =~ ^[0-9]+$ ]]; then
|
||||
exit_code="$raw_exit_code"
|
||||
else
|
||||
exit_code=1
|
||||
fi
|
||||
error=$(explain_exit_code "$exit_code")
|
||||
# Get log lines and build structured error string
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
fi
|
||||
@@ -585,8 +719,9 @@ post_update_to_api() {
|
||||
pve_version=$(pveversion 2>/dev/null | awk -F'[/ ]' '{print $2}') || true
|
||||
fi
|
||||
|
||||
# Full payload including all fields - allows record creation if initial call failed
|
||||
# The Go service will find the record by random_id and PATCH, or create if not found
|
||||
local http_code=""
|
||||
|
||||
# ── Attempt 1: Full payload with complete error text ──
|
||||
local JSON_PAYLOAD
|
||||
JSON_PAYLOAD=$(
|
||||
cat <<EOF
|
||||
@@ -618,11 +753,80 @@ post_update_to_api() {
|
||||
EOF
|
||||
)
|
||||
|
||||
# Fire-and-forget: never block, never fail
|
||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
||||
|
||||
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
||||
POST_UPDATE_DONE=true
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Attempt 2: Short error text (no full log) ──
|
||||
sleep 1
|
||||
local RETRY_PAYLOAD
|
||||
RETRY_PAYLOAD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"random_id": "${RANDOM_UUID}",
|
||||
"type": "${TELEMETRY_TYPE:-lxc}",
|
||||
"nsapp": "${NSAPP:-unknown}",
|
||||
"status": "${pb_status}",
|
||||
"ct_type": ${CT_TYPE:-1},
|
||||
"disk_size": ${DISK_SIZE:-0},
|
||||
"core_count": ${CORE_COUNT:-0},
|
||||
"ram_size": ${RAM_SIZE:-0},
|
||||
"os_type": "${var_os:-}",
|
||||
"os_version": "${var_version:-}",
|
||||
"pve_version": "${pve_version}",
|
||||
"method": "${METHOD:-default}",
|
||||
"exit_code": ${exit_code},
|
||||
"error": "${short_error}",
|
||||
"error_category": "${error_category}",
|
||||
"install_duration": ${duration},
|
||||
"cpu_vendor": "${cpu_vendor}",
|
||||
"cpu_model": "${cpu_model}",
|
||||
"gpu_vendor": "${gpu_vendor}",
|
||||
"gpu_model": "${gpu_model}",
|
||||
"gpu_passthrough": "${gpu_passthrough}",
|
||||
"ram_speed": "${ram_speed}",
|
||||
"repo_source": "${REPO_SOURCE}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
http_code=$(curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$RETRY_PAYLOAD" -o /dev/null 2>/dev/null) || http_code="000"
|
||||
|
||||
if [[ "$http_code" =~ ^2[0-9]{2}$ ]]; then
|
||||
POST_UPDATE_DONE=true
|
||||
return 0
|
||||
fi
|
||||
|
||||
# ── Attempt 3: Minimal payload (bare minimum to set status) ──
|
||||
sleep 2
|
||||
local MINIMAL_PAYLOAD
|
||||
MINIMAL_PAYLOAD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"random_id": "${RANDOM_UUID}",
|
||||
"type": "${TELEMETRY_TYPE:-lxc}",
|
||||
"nsapp": "${NSAPP:-unknown}",
|
||||
"status": "${pb_status}",
|
||||
"exit_code": ${exit_code},
|
||||
"error": "${short_error}",
|
||||
"error_category": "${error_category}",
|
||||
"install_duration": ${duration}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
curl -sS -w "%{http_code}" -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" -o /dev/null 2>&1 || true
|
||||
-d "$MINIMAL_PAYLOAD" -o /dev/null 2>/dev/null || true
|
||||
|
||||
# Tried 3 times - mark as done regardless to prevent infinite loops
|
||||
POST_UPDATE_DONE=true
|
||||
}
|
||||
|
||||
@@ -640,28 +844,52 @@ EOF
|
||||
categorize_error() {
|
||||
local code="$1"
|
||||
case "$code" in
|
||||
# Network errors
|
||||
6 | 7 | 22 | 28 | 35) echo "network" ;;
|
||||
# Network errors (curl/wget)
|
||||
6 | 7 | 22 | 35) echo "network" ;;
|
||||
|
||||
# Storage errors
|
||||
214 | 217 | 219) echo "storage" ;;
|
||||
# Timeout errors
|
||||
28 | 124 | 211) echo "timeout" ;;
|
||||
|
||||
# Dependency/Package errors
|
||||
100 | 101 | 102 | 127 | 160 | 161 | 162) echo "dependency" ;;
|
||||
# Storage errors (Proxmox storage)
|
||||
214 | 217 | 219 | 224) echo "storage" ;;
|
||||
|
||||
# Dependency/Package errors (APT, DPKG, pip, commands)
|
||||
100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo "dependency" ;;
|
||||
|
||||
# Permission errors
|
||||
126 | 152) echo "permission" ;;
|
||||
|
||||
# Timeout errors
|
||||
124 | 28 | 211) echo "timeout" ;;
|
||||
# Configuration errors (Proxmox config, invalid args)
|
||||
128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||
|
||||
# Configuration errors
|
||||
203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||
# Proxmox container/template errors
|
||||
200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo "proxmox" ;;
|
||||
|
||||
# Resource errors (OOM, etc)
|
||||
137 | 134) echo "resource" ;;
|
||||
# Service/Systemd errors
|
||||
150 | 151 | 153 | 154) echo "service" ;;
|
||||
|
||||
# Default
|
||||
# Database errors (PostgreSQL, MySQL, MongoDB)
|
||||
170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo "database" ;;
|
||||
|
||||
# Node.js / JavaScript runtime errors
|
||||
243 | 245 | 246 | 247 | 248 | 249) echo "runtime" ;;
|
||||
|
||||
# Python environment errors
|
||||
# (already covered: 160-162 under dependency)
|
||||
|
||||
# Aborted by user
|
||||
130) echo "aborted" ;;
|
||||
|
||||
# Resource errors (OOM, SIGKILL, SIGABRT)
|
||||
134 | 137) echo "resource" ;;
|
||||
|
||||
# Signal/Process errors (SIGTERM, SIGPIPE, SIGSEGV)
|
||||
139 | 141 | 143) echo "signal" ;;
|
||||
|
||||
# Shell errors (general error, syntax error)
|
||||
1 | 2) echo "shell" ;;
|
||||
|
||||
# Default - truly unknown
|
||||
*) echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
@@ -722,7 +950,11 @@ post_tool_to_api() {
|
||||
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||
error=$(explain_exit_code "$exit_code")
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
fi
|
||||
|
||||
@@ -783,7 +1015,11 @@ post_addon_to_api() {
|
||||
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||
error=$(explain_exit_code "$exit_code")
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
fi
|
||||
|
||||
@@ -876,7 +1112,11 @@ post_update_to_api_extended() {
|
||||
else
|
||||
exit_code=1
|
||||
fi
|
||||
error=$(explain_exit_code "$exit_code")
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
fi
|
||||
|
||||
524
misc/build.func
524
misc/build.func
@@ -38,15 +38,16 @@
|
||||
# - Captures app-declared resource defaults (CPU, RAM, Disk)
|
||||
# ------------------------------------------------------------------------------
|
||||
variables() {
|
||||
NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
|
||||
var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP.
|
||||
INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern.
|
||||
PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase
|
||||
DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call.
|
||||
METHOD="default" # sets the METHOD variable to "default", used for the API call.
|
||||
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable.
|
||||
SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files
|
||||
BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log
|
||||
NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
|
||||
var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP.
|
||||
INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern.
|
||||
PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase
|
||||
DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call.
|
||||
METHOD="default" # sets the METHOD variable to "default", used for the API call.
|
||||
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable.
|
||||
SESSION_ID="${RANDOM_UUID:0:8}" # Short session ID (first 8 chars of UUID) for log files
|
||||
BUILD_LOG="/tmp/create-lxc-${SESSION_ID}.log" # Host-side container creation log
|
||||
combined_log="/tmp/install-${SESSION_ID}-combined.log" # Combined log (build + install) for failed installations
|
||||
CTTYPE="${CTTYPE:-${CT_TYPE:-1}}"
|
||||
|
||||
# Parse dev_mode early
|
||||
@@ -217,7 +218,7 @@ update_motd_ip() {
|
||||
local current_os="$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')"
|
||||
local current_hostname="$(hostname)"
|
||||
local current_ip="$(hostname -I | awk '{print $1}')"
|
||||
|
||||
|
||||
# Update only if values actually changed
|
||||
if ! grep -q "OS:.*$current_os" "$PROFILE_FILE" 2>/dev/null; then
|
||||
sed -i "s|OS:.*|OS: \${GN}$current_os\${CL}\\\"|" "$PROFILE_FILE"
|
||||
@@ -276,8 +277,9 @@ install_ssh_keys_into_ct() {
|
||||
# ------------------------------------------------------------------------------
|
||||
# validate_container_id()
|
||||
#
|
||||
# - Validates if a container ID is available for use
|
||||
# - Checks if ID is already used by VM or LXC container
|
||||
# - Validates if a container ID is available for use (CLUSTER-WIDE)
|
||||
# - Checks cluster resources via pvesh for VMs/CTs on ALL nodes
|
||||
# - Falls back to local config file check if pvesh unavailable
|
||||
# - Checks if ID is used in LVM logical volumes
|
||||
# - Returns 0 if ID is available, 1 if already in use
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -289,11 +291,35 @@ validate_container_id() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if config file exists for VM or LXC
|
||||
# CLUSTER-WIDE CHECK: Query all VMs/CTs across all nodes
|
||||
# This catches IDs used on other nodes in the cluster
|
||||
# NOTE: Works on single-node too - Proxmox always has internal cluster structure
|
||||
# Falls back gracefully if pvesh unavailable or returns empty
|
||||
if command -v pvesh &>/dev/null; then
|
||||
local cluster_ids
|
||||
cluster_ids=$(pvesh get /cluster/resources --type vm --output-format json 2>/dev/null |
|
||||
grep -oP '"vmid":\s*\K[0-9]+' 2>/dev/null || true)
|
||||
if [[ -n "$cluster_ids" ]] && echo "$cluster_ids" | grep -qw "$ctid"; then
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# LOCAL FALLBACK: Check if config file exists for VM or LXC
|
||||
# This handles edge cases where pvesh might not return all info
|
||||
if [[ -f "/etc/pve/qemu-server/${ctid}.conf" ]] || [[ -f "/etc/pve/lxc/${ctid}.conf" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check ALL nodes in cluster for config files (handles pmxcfs sync delays)
|
||||
# NOTE: On single-node, /etc/pve/nodes/ contains just the one node - still works
|
||||
if [[ -d "/etc/pve/nodes" ]]; then
|
||||
for node_dir in /etc/pve/nodes/*/; do
|
||||
if [[ -f "${node_dir}qemu-server/${ctid}.conf" ]] || [[ -f "${node_dir}lxc/${ctid}.conf" ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Check if ID is used in LVM logical volumes
|
||||
if lvs --noheadings -o lv_name 2>/dev/null | grep -qE "(^|[-_])${ctid}($|[-_])"; then
|
||||
return 1
|
||||
@@ -305,63 +331,30 @@ validate_container_id() {
|
||||
# ------------------------------------------------------------------------------
|
||||
# get_valid_container_id()
|
||||
#
|
||||
# - Returns a valid, unused container ID
|
||||
# - Returns a valid, unused container ID (CLUSTER-AWARE)
|
||||
# - Uses pvesh /cluster/nextid as starting point (already cluster-aware)
|
||||
# - If provided ID is valid, returns it
|
||||
# - Otherwise increments from suggested ID until a free one is found
|
||||
# - Otherwise increments until a free one is found across entire cluster
|
||||
# - Calls validate_container_id() to check availability
|
||||
# ------------------------------------------------------------------------------
|
||||
get_valid_container_id() {
|
||||
local suggested_id="${1:-$(pvesh get /cluster/nextid)}"
|
||||
|
||||
while ! validate_container_id "$suggested_id"; do
|
||||
suggested_id=$((suggested_id + 1))
|
||||
done
|
||||
|
||||
echo "$suggested_id"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# validate_container_id()
|
||||
#
|
||||
# - Validates if a container ID is available for use
|
||||
# - Checks if ID is already used by VM or LXC container
|
||||
# - Checks if ID is used in LVM logical volumes
|
||||
# - Returns 0 if ID is available, 1 if already in use
|
||||
# ------------------------------------------------------------------------------
|
||||
validate_container_id() {
|
||||
local ctid="$1"
|
||||
|
||||
# Check if ID is numeric
|
||||
if ! [[ "$ctid" =~ ^[0-9]+$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if config file exists for VM or LXC
|
||||
if [[ -f "/etc/pve/qemu-server/${ctid}.conf" ]] || [[ -f "/etc/pve/lxc/${ctid}.conf" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if ID is used in LVM logical volumes
|
||||
if lvs --noheadings -o lv_name 2>/dev/null | grep -qE "(^|[-_])${ctid}($|[-_])"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# get_valid_container_id()
|
||||
#
|
||||
# - Returns a valid, unused container ID
|
||||
# - If provided ID is valid, returns it
|
||||
# - Otherwise increments from suggested ID until a free one is found
|
||||
# - Calls validate_container_id() to check availability
|
||||
# ------------------------------------------------------------------------------
|
||||
get_valid_container_id() {
|
||||
local suggested_id="${1:-$(pvesh get /cluster/nextid)}"
|
||||
local suggested_id="${1:-$(pvesh get /cluster/nextid 2>/dev/null || echo 100)}"
|
||||
|
||||
# Ensure we have a valid starting ID
|
||||
if ! [[ "$suggested_id" =~ ^[0-9]+$ ]]; then
|
||||
suggested_id=$(pvesh get /cluster/nextid 2>/dev/null || echo 100)
|
||||
fi
|
||||
|
||||
local max_attempts=1000
|
||||
local attempts=0
|
||||
|
||||
while ! validate_container_id "$suggested_id"; do
|
||||
suggested_id=$((suggested_id + 1))
|
||||
attempts=$((attempts + 1))
|
||||
if [[ $attempts -ge $max_attempts ]]; then
|
||||
msg_error "Could not find available container ID after $max_attempts attempts"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$suggested_id"
|
||||
@@ -385,7 +378,7 @@ validate_hostname() {
|
||||
|
||||
# Split by dots and validate each label
|
||||
local IFS='.'
|
||||
read -ra labels <<< "$hostname"
|
||||
read -ra labels <<<"$hostname"
|
||||
for label in "${labels[@]}"; do
|
||||
# Each label: 1-63 chars, alphanumeric, hyphens allowed (not at start/end)
|
||||
if [[ -z "$label" ]] || [[ ${#label} -gt 63 ]]; then
|
||||
@@ -489,7 +482,7 @@ validate_ipv6_address() {
|
||||
# Check that no segment exceeds 4 hex chars
|
||||
local IFS=':'
|
||||
local -a segments
|
||||
read -ra segments <<< "$addr"
|
||||
read -ra segments <<<"$addr"
|
||||
for seg in "${segments[@]}"; do
|
||||
if [[ ${#seg} -gt 4 ]]; then
|
||||
return 1
|
||||
@@ -539,14 +532,14 @@ validate_gateway_in_subnet() {
|
||||
|
||||
# Convert IPs to integers
|
||||
local IFS='.'
|
||||
read -r i1 i2 i3 i4 <<< "$ip"
|
||||
read -r g1 g2 g3 g4 <<< "$gateway"
|
||||
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 ))
|
||||
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
|
||||
if (((ip_int & mask) != (gw_int & mask))); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -1079,117 +1072,117 @@ load_vars_file() {
|
||||
# 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"
|
||||
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
|
||||
# Warn about potential issues with systemd-based OS when nesting is disabled via vars file
|
||||
if [[ "$var_val" == "0" && "${var_os:-debian}" != "alpine" ]]; then
|
||||
msg_warn "Nesting disabled in $file - modern systemd-based distributions may require nesting for proper operation"
|
||||
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
|
||||
;;
|
||||
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
|
||||
# Warn about potential issues with systemd-based OS when nesting is disabled via vars file
|
||||
if [[ "$var_val" == "0" && "${var_os:-debian}" != "alpine" ]]; then
|
||||
msg_warn "Nesting disabled in $file - modern systemd-based distributions may require nesting for proper operation"
|
||||
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
|
||||
;;
|
||||
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
|
||||
|
||||
@@ -2764,6 +2757,26 @@ Advanced:
|
||||
[[ "$APT_CACHER" == "yes" ]] && echo -e "${INFO}${BOLD}${DGN}APT Cacher: ${BGN}$APT_CACHER_IP${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}"
|
||||
|
||||
# Log settings to file
|
||||
log_section "CONTAINER SETTINGS (ADVANCED) - ${APP}"
|
||||
log_msg "Application: ${APP}"
|
||||
log_msg "PVE Version: ${PVEVERSION} (Kernel: ${KERNEL_VERSION})"
|
||||
log_msg "Operating System: $var_os ($var_version)"
|
||||
log_msg "Container Type: $([ "$CT_TYPE" == "1" ] && echo "Unprivileged" || echo "Privileged")"
|
||||
log_msg "Container ID: $CT_ID"
|
||||
log_msg "Hostname: $HN"
|
||||
log_msg "Disk Size: ${DISK_SIZE} GB"
|
||||
log_msg "CPU Cores: $CORE_COUNT"
|
||||
log_msg "RAM Size: ${RAM_SIZE} MiB"
|
||||
log_msg "Bridge: $BRG"
|
||||
log_msg "IPv4: $NET"
|
||||
log_msg "IPv6: $IPV6_METHOD"
|
||||
log_msg "FUSE Support: ${ENABLE_FUSE:-no}"
|
||||
log_msg "Nesting: $([ "${ENABLE_NESTING:-1}" == "1" ] && echo "Enabled" || echo "Disabled")"
|
||||
log_msg "GPU Passthrough: ${ENABLE_GPU:-no}"
|
||||
log_msg "Verbose Mode: $VERBOSE"
|
||||
log_msg "Session ID: ${SESSION_ID}"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
@@ -2871,6 +2884,7 @@ diagnostics_menu() {
|
||||
# - Prints summary of default values (ID, OS, type, disk, RAM, CPU, etc.)
|
||||
# - Uses icons and formatting for readability
|
||||
# - Convert CT_TYPE to description
|
||||
# - Also logs settings to log file for debugging
|
||||
# ------------------------------------------------------------------------------
|
||||
echo_default() {
|
||||
CT_TYPE_DESC="Unprivileged"
|
||||
@@ -2892,6 +2906,20 @@ echo_default() {
|
||||
fi
|
||||
echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}"
|
||||
echo -e " "
|
||||
|
||||
# Log settings to file
|
||||
log_section "CONTAINER SETTINGS - ${APP}"
|
||||
log_msg "Application: ${APP}"
|
||||
log_msg "PVE Version: ${PVEVERSION} (Kernel: ${KERNEL_VERSION})"
|
||||
log_msg "Container ID: ${CT_ID}"
|
||||
log_msg "Operating System: $var_os ($var_version)"
|
||||
log_msg "Container Type: $CT_TYPE_DESC"
|
||||
log_msg "Disk Size: ${DISK_SIZE} GB"
|
||||
log_msg "CPU Cores: ${CORE_COUNT}"
|
||||
log_msg "RAM Size: ${RAM_SIZE} MiB"
|
||||
[[ -n "${var_gpu:-}" && "${var_gpu}" == "yes" ]] && log_msg "GPU Passthrough: Enabled"
|
||||
[[ "$VERBOSE" == "yes" ]] && log_msg "Verbose Mode: Enabled"
|
||||
log_msg "Session ID: ${SESSION_ID}"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -4046,28 +4074,58 @@ EOF'
|
||||
if [[ $install_exit_code -ne 0 ]]; then
|
||||
msg_error "Installation failed in container ${CTID} (exit code: ${install_exit_code})"
|
||||
|
||||
# Report failure to telemetry API
|
||||
post_update_to_api "failed" "$install_exit_code"
|
||||
|
||||
# Copy both logs from container before potential deletion
|
||||
# Copy install log from container BEFORE API call so get_error_text() can read it
|
||||
local build_log_copied=false
|
||||
local install_log_copied=false
|
||||
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
|
||||
|
||||
if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
|
||||
# Copy BUILD_LOG (creation log) if it exists
|
||||
# Create combined log with header
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "COMBINED INSTALLATION LOG - ${APP:-LXC}"
|
||||
echo "Container ID: ${CTID}"
|
||||
echo "Session ID: ${SESSION_ID}"
|
||||
echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "================================================================================"
|
||||
echo ""
|
||||
} >"$combined_log"
|
||||
|
||||
# Append BUILD_LOG (host-side creation log) if it exists
|
||||
if [[ -f "${BUILD_LOG}" ]]; then
|
||||
cp "${BUILD_LOG}" "/tmp/create-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null && build_log_copied=true
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 1: CONTAINER CREATION (Host)"
|
||||
echo "================================================================================"
|
||||
cat "${BUILD_LOG}"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
build_log_copied=true
|
||||
fi
|
||||
|
||||
# Copy INSTALL_LOG from container
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "/tmp/install-lxc-${CTID}-${SESSION_ID}.log" 2>/dev/null; then
|
||||
# Copy and append INSTALL_LOG from container
|
||||
local temp_install_log="/tmp/.install-temp-${SESSION_ID}.log"
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_install_log" 2>/dev/null; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 2: APPLICATION INSTALLATION (Container)"
|
||||
echo "================================================================================"
|
||||
cat "$temp_install_log"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
rm -f "$temp_install_log"
|
||||
install_log_copied=true
|
||||
# Point INSTALL_LOG to combined log so get_error_text() finds it
|
||||
INSTALL_LOG="$combined_log"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Show available logs
|
||||
echo ""
|
||||
[[ "$build_log_copied" == true ]] && echo -e "${GN}✔${CL} Container creation log: ${BL}/tmp/create-lxc-${CTID}-${SESSION_ID}.log${CL}"
|
||||
[[ "$install_log_copied" == true ]] && echo -e "${GN}✔${CL} Installation log: ${BL}/tmp/install-lxc-${CTID}-${SESSION_ID}.log${CL}"
|
||||
# Report failure to telemetry API (now with log available on host)
|
||||
post_update_to_api "failed" "$install_exit_code"
|
||||
|
||||
# Show combined log location
|
||||
if [[ -n "$CTID" && -n "${SESSION_ID:-}" ]]; then
|
||||
msg_custom "📋" "${YW}" "Installation log: ${combined_log}"
|
||||
fi
|
||||
|
||||
# Dev mode: Keep container or open breakpoint shell
|
||||
@@ -4090,19 +4148,21 @@ EOF'
|
||||
exit $install_exit_code
|
||||
fi
|
||||
|
||||
# Prompt user for cleanup with 60s timeout (plain echo - no msg_info to avoid spinner)
|
||||
# Prompt user for cleanup with 60s timeout
|
||||
echo ""
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
echo -en "${TAB}❓${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
|
||||
if read -t 60 -r response; then
|
||||
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||
# Remove container
|
||||
echo -e "\n${TAB}${HOLD}${YW}Removing container ${CTID}${CL}"
|
||||
echo ""
|
||||
msg_info "Removing container ${CTID}"
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
|
||||
msg_ok "Container ${CTID} removed"
|
||||
elif [[ "$response" =~ ^[Nn]$ ]]; then
|
||||
echo -e "\n${TAB}${YW}Container ${CTID} kept for debugging${CL}"
|
||||
echo ""
|
||||
msg_warn "Container ${CTID} kept for debugging"
|
||||
|
||||
# Dev mode: Setup MOTD/SSH for debugging access to broken container
|
||||
if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then
|
||||
@@ -4118,13 +4178,17 @@ EOF'
|
||||
fi
|
||||
else
|
||||
# Timeout - auto-remove
|
||||
echo -e "\n${YW}No response - auto-removing container${CL}"
|
||||
echo -e "${TAB}${HOLD}${YW}Removing container ${CTID}${CL}"
|
||||
echo ""
|
||||
msg_info "No response - removing container ${CTID}"
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${BFR}${CM}${GN}Container ${CTID} removed${CL}"
|
||||
msg_ok "Container ${CTID} removed"
|
||||
fi
|
||||
|
||||
# Force one final status update attempt after cleanup
|
||||
# This ensures status is updated even if the first attempt failed (e.g., HTTP 400)
|
||||
post_update_to_api "failed" "$install_exit_code" "force"
|
||||
|
||||
exit $install_exit_code
|
||||
fi
|
||||
}
|
||||
@@ -5128,6 +5192,61 @@ EOF
|
||||
# SECTION 10: ERROR HANDLING & EXIT TRAPS
|
||||
# ==============================================================================
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ensure_log_on_host()
|
||||
#
|
||||
# - Ensures INSTALL_LOG points to a readable file on the host
|
||||
# - If INSTALL_LOG points to a container path (e.g. /root/.install-*),
|
||||
# tries to pull it from the container and create a combined log
|
||||
# - This allows get_error_text() to find actual error output for telemetry
|
||||
# ------------------------------------------------------------------------------
|
||||
ensure_log_on_host() {
|
||||
# Already readable on host? Nothing to do.
|
||||
[[ -n "${INSTALL_LOG:-}" && -s "${INSTALL_LOG}" ]] && return 0
|
||||
|
||||
# Try pulling from container and creating combined log
|
||||
if [[ -n "${CTID:-}" && -n "${SESSION_ID:-}" ]] && command -v pct &>/dev/null; then
|
||||
local combined_log="/tmp/${NSAPP:-lxc}-${CTID}-${SESSION_ID}.log"
|
||||
if [[ ! -s "$combined_log" ]]; then
|
||||
# Create combined log
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "COMBINED INSTALLATION LOG - ${APP:-LXC}"
|
||||
echo "Container ID: ${CTID}"
|
||||
echo "Session ID: ${SESSION_ID}"
|
||||
echo "Timestamp: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo "================================================================================"
|
||||
echo ""
|
||||
} >"$combined_log" 2>/dev/null || return 0
|
||||
# Append BUILD_LOG if it exists
|
||||
if [[ -f "${BUILD_LOG:-}" ]]; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 1: CONTAINER CREATION (Host)"
|
||||
echo "================================================================================"
|
||||
cat "${BUILD_LOG}"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
fi
|
||||
# Pull INSTALL_LOG from container
|
||||
local temp_log="/tmp/.install-temp-${SESSION_ID}.log"
|
||||
if pct pull "$CTID" "/root/.install-${SESSION_ID}.log" "$temp_log" 2>/dev/null; then
|
||||
{
|
||||
echo "================================================================================"
|
||||
echo "PHASE 2: APPLICATION INSTALLATION (Container)"
|
||||
echo "================================================================================"
|
||||
cat "$temp_log"
|
||||
echo ""
|
||||
} >>"$combined_log"
|
||||
rm -f "$temp_log"
|
||||
fi
|
||||
fi
|
||||
if [[ -s "$combined_log" ]]; then
|
||||
INSTALL_LOG="$combined_log"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# api_exit_script()
|
||||
#
|
||||
@@ -5140,6 +5259,7 @@ EOF
|
||||
api_exit_script() {
|
||||
exit_code=$?
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
ensure_log_on_host
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
fi
|
||||
}
|
||||
@@ -5147,6 +5267,6 @@ api_exit_script() {
|
||||
if command -v pveversion >/dev/null 2>&1; then
|
||||
trap 'api_exit_script' EXIT
|
||||
fi
|
||||
trap 'post_update_to_api "failed" "$?"' ERR
|
||||
trap 'post_update_to_api "failed" "130"' SIGINT
|
||||
trap 'post_update_to_api "failed" "143"' SIGTERM
|
||||
trap 'ensure_log_on_host; post_update_to_api "failed" "$?"' ERR
|
||||
trap 'ensure_log_on_host; post_update_to_api "failed" "130"' SIGINT
|
||||
trap 'ensure_log_on_host; post_update_to_api "failed" "143"' SIGTERM
|
||||
|
||||
652
misc/core.func
652
misc/core.func
@@ -115,7 +115,7 @@ icons() {
|
||||
BRIDGE="${TAB}🌉${TAB}${CL}"
|
||||
NETWORK="${TAB}📡${TAB}${CL}"
|
||||
GATEWAY="${TAB}🌐${TAB}${CL}"
|
||||
DISABLEIPV6="${TAB}🚫${TAB}${CL}"
|
||||
ICON_DISABLEIPV6="${TAB}🚫${TAB}${CL}"
|
||||
DEFAULT="${TAB}⚙️${TAB}${CL}"
|
||||
MACADDRESS="${TAB}🔗${TAB}${CL}"
|
||||
VLANTAG="${TAB}🏷️${TAB}${CL}"
|
||||
@@ -413,6 +413,69 @@ get_active_logfile() {
|
||||
# Legacy compatibility: SILENT_LOGFILE points to active log
|
||||
SILENT_LOGFILE="$(get_active_logfile)"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# strip_ansi()
|
||||
#
|
||||
# - Removes ANSI escape sequences from input text
|
||||
# - Used to clean colored output for log files
|
||||
# - Handles both piped input and arguments
|
||||
# ------------------------------------------------------------------------------
|
||||
strip_ansi() {
|
||||
if [[ $# -gt 0 ]]; then
|
||||
echo -e "$*" | sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||
else
|
||||
sed 's/\x1b\[[0-9;]*m//g; s/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# log_msg()
|
||||
#
|
||||
# - Writes message to active log file without ANSI codes
|
||||
# - Adds timestamp prefix for log correlation
|
||||
# - Creates log file if it doesn't exist
|
||||
# - Arguments: message text (can include ANSI codes, will be stripped)
|
||||
# ------------------------------------------------------------------------------
|
||||
log_msg() {
|
||||
local msg="$*"
|
||||
local logfile
|
||||
logfile="$(get_active_logfile)"
|
||||
|
||||
[[ -z "$msg" ]] && return
|
||||
[[ -z "$logfile" ]] && return
|
||||
|
||||
# Ensure log directory exists
|
||||
mkdir -p "$(dirname "$logfile")" 2>/dev/null || true
|
||||
|
||||
# Strip ANSI codes and write with timestamp
|
||||
local clean_msg
|
||||
clean_msg=$(strip_ansi "$msg")
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $clean_msg" >>"$logfile"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# log_section()
|
||||
#
|
||||
# - Writes a section header to the log file
|
||||
# - Used for separating different phases of installation
|
||||
# - Arguments: section name
|
||||
# ------------------------------------------------------------------------------
|
||||
log_section() {
|
||||
local section="$1"
|
||||
local logfile
|
||||
logfile="$(get_active_logfile)"
|
||||
|
||||
[[ -z "$logfile" ]] && return
|
||||
mkdir -p "$(dirname "$logfile")" 2>/dev/null || true
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "================================================================================"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $section"
|
||||
echo "================================================================================"
|
||||
} >>"$logfile"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# silent()
|
||||
#
|
||||
@@ -459,15 +522,9 @@ silent() {
|
||||
msg_custom "→" "${YWB}" "${cmd}"
|
||||
|
||||
if [[ -s "$logfile" ]]; then
|
||||
local log_lines=$(wc -l <"$logfile")
|
||||
echo "--- Last 10 lines of silent log ---"
|
||||
echo -e "\n${TAB}--- Last 10 lines of log ---"
|
||||
tail -n 10 "$logfile"
|
||||
echo "-----------------------------------"
|
||||
|
||||
# Show how to view full log if there are more lines
|
||||
if [[ $log_lines -gt 10 ]]; then
|
||||
msg_custom "📋" "${YW}" "View full log (${log_lines} lines): ${logfile}"
|
||||
fi
|
||||
echo -e "${TAB}-----------------------------------\n"
|
||||
fi
|
||||
|
||||
exit "$rc"
|
||||
@@ -488,7 +545,7 @@ spinner() {
|
||||
local i=0
|
||||
while true; do
|
||||
local index=$((i++ % ${#chars[@]}))
|
||||
printf "\r\033[2K%s %b" "${CS_YWB}${TAB}${chars[$index]}${TAB}${CS_CL}" "${CS_YWB}${msg}${CS_CL}"
|
||||
printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${msg}${CS_CL}"
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
@@ -555,6 +612,9 @@ msg_info() {
|
||||
[[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return
|
||||
MSG_INFO_SHOWN["$msg"]=1
|
||||
|
||||
# Log to file
|
||||
log_msg "[INFO] $msg"
|
||||
|
||||
stop_spinner
|
||||
SPINNER_MSG="$msg"
|
||||
|
||||
@@ -598,7 +658,10 @@ msg_ok() {
|
||||
stop_spinner
|
||||
clear_line
|
||||
echo -e "$CM ${GN}${msg}${CL}"
|
||||
unset MSG_INFO_SHOWN["$msg"]
|
||||
log_msg "[OK] $msg"
|
||||
local sanitized_msg
|
||||
sanitized_msg=$(printf '%s' "$msg" | sed 's/\x1b\[[0-9;]*m//g; s/[^a-zA-Z0-9_]/_/g')
|
||||
unset 'MSG_INFO_SHOWN['"$sanitized_msg"']' 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -613,6 +676,7 @@ msg_error() {
|
||||
stop_spinner
|
||||
local msg="$1"
|
||||
echo -e "${BFR:-}${CROSS:-✖️} ${RD}${msg}${CL}" >&2
|
||||
log_msg "[ERROR] $msg"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -627,6 +691,7 @@ msg_warn() {
|
||||
stop_spinner
|
||||
local msg="$1"
|
||||
echo -e "${BFR:-}${INFO:-ℹ️} ${YWB}${msg}${CL}" >&2
|
||||
log_msg "[WARN] $msg"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -644,6 +709,7 @@ msg_custom() {
|
||||
[[ -z "$msg" ]] && return
|
||||
stop_spinner
|
||||
echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}"
|
||||
log_msg "$msg"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -808,6 +874,562 @@ is_verbose_mode() {
|
||||
[[ "$verbose" != "no" || ! -t 2 ]]
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# is_unattended()
|
||||
#
|
||||
# - Detects if script is running in unattended/non-interactive mode
|
||||
# - Checks MODE variable first (primary method)
|
||||
# - Falls back to legacy flags (PHS_SILENT, var_unattended)
|
||||
# - Returns 0 (true) if unattended, 1 (false) otherwise
|
||||
# - Used by prompt functions to auto-apply defaults
|
||||
#
|
||||
# Modes that are unattended:
|
||||
# - default (1) : Use script defaults, no prompts
|
||||
# - mydefaults (3) : Use user's default.vars, no prompts
|
||||
# - appdefaults (4) : Use app-specific defaults, no prompts
|
||||
#
|
||||
# Modes that are interactive:
|
||||
# - advanced (2) : Full wizard with all options
|
||||
#
|
||||
# Note: Even in advanced mode, install scripts run unattended because
|
||||
# all values are already collected during the wizard phase.
|
||||
# ------------------------------------------------------------------------------
|
||||
is_unattended() {
|
||||
# Primary: Check MODE variable (case-insensitive)
|
||||
local mode="${MODE:-${mode:-}}"
|
||||
mode="${mode,,}" # lowercase
|
||||
|
||||
case "$mode" in
|
||||
default | 1)
|
||||
return 0
|
||||
;;
|
||||
mydefaults | userdefaults | 3)
|
||||
return 0
|
||||
;;
|
||||
appdefaults | 4)
|
||||
return 0
|
||||
;;
|
||||
advanced | 2)
|
||||
# Advanced mode is interactive ONLY during wizard
|
||||
# Inside container (install scripts), it should be unattended
|
||||
# Check if we're inside a container (no pveversion command)
|
||||
if ! command -v pveversion &>/dev/null; then
|
||||
# We're inside the container - all values already collected
|
||||
return 0
|
||||
fi
|
||||
# On host during wizard - interactive
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Legacy fallbacks for compatibility
|
||||
[[ "${PHS_SILENT:-0}" == "1" ]] && return 0
|
||||
[[ "${var_unattended:-}" =~ ^(yes|true|1)$ ]] && return 0
|
||||
[[ "${UNATTENDED:-}" =~ ^(yes|true|1)$ ]] && return 0
|
||||
|
||||
# No TTY available = unattended
|
||||
[[ ! -t 0 ]] && return 0
|
||||
|
||||
# Default: interactive
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# show_missing_values_warning()
|
||||
#
|
||||
# - Displays a summary of required values that used fallback defaults
|
||||
# - Should be called at the end of install scripts
|
||||
# - Only shows warning if MISSING_REQUIRED_VALUES array has entries
|
||||
# - Provides clear guidance on what needs manual configuration
|
||||
#
|
||||
# Global:
|
||||
# MISSING_REQUIRED_VALUES - Array of variable names that need configuration
|
||||
#
|
||||
# Example:
|
||||
# # At end of install script:
|
||||
# show_missing_values_warning
|
||||
# ------------------------------------------------------------------------------
|
||||
show_missing_values_warning() {
|
||||
if [[ ${#MISSING_REQUIRED_VALUES[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo -e "${YW}╔════════════════════════════════════════════════════════════╗${CL}"
|
||||
echo -e "${YW}║ ⚠️ MANUAL CONFIGURATION REQUIRED ║${CL}"
|
||||
echo -e "${YW}╠════════════════════════════════════════════════════════════╣${CL}"
|
||||
echo -e "${YW}║ The following values were not provided and need to be ║${CL}"
|
||||
echo -e "${YW}║ configured manually for the service to work properly: ║${CL}"
|
||||
echo -e "${YW}╟────────────────────────────────────────────────────────────╢${CL}"
|
||||
for val in "${MISSING_REQUIRED_VALUES[@]}"; do
|
||||
printf "${YW}║${CL} • %-56s ${YW}║${CL}\n" "$val"
|
||||
done
|
||||
echo -e "${YW}╟────────────────────────────────────────────────────────────╢${CL}"
|
||||
echo -e "${YW}║ Check the service configuration files or environment ║${CL}"
|
||||
echo -e "${YW}║ variables and update the placeholder values. ║${CL}"
|
||||
echo -e "${YW}╚════════════════════════════════════════════════════════════╝${CL}"
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_confirm()
|
||||
#
|
||||
# - Prompts user for yes/no confirmation with timeout and unattended support
|
||||
# - In unattended mode: immediately returns default value
|
||||
# - In interactive mode: waits for user input with configurable timeout
|
||||
# - After timeout: auto-applies default value
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default value: "y" or "n" (optional, default: "n")
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
#
|
||||
# Returns:
|
||||
# 0 - User confirmed (yes)
|
||||
# 1 - User declined (no) or timeout with default "n"
|
||||
#
|
||||
# Example:
|
||||
# if prompt_confirm "Proceed with installation?" "y" 30; then
|
||||
# echo "Installing..."
|
||||
# fi
|
||||
#
|
||||
# # Unattended: prompt_confirm will use default without waiting
|
||||
# var_unattended=yes
|
||||
# prompt_confirm "Delete files?" "n" && echo "Deleting" || echo "Skipped"
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_confirm() {
|
||||
local message="${1:-Confirm?}"
|
||||
local default="${2:-n}"
|
||||
local timeout="${3:-60}"
|
||||
local response
|
||||
|
||||
# Normalize default to lowercase
|
||||
default="${default,,}"
|
||||
[[ "$default" != "y" ]] && default="n"
|
||||
|
||||
# Build prompt hint
|
||||
local hint
|
||||
if [[ "$default" == "y" ]]; then
|
||||
hint="[Y/n]"
|
||||
else
|
||||
hint="[y/N]"
|
||||
fi
|
||||
|
||||
# Unattended mode: apply default immediately
|
||||
if is_unattended; then
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
# Not a TTY, use default
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Interactive prompt with timeout
|
||||
echo -en "${YW}${message} ${hint} (auto-${default} in ${timeout}s): ${CL}"
|
||||
|
||||
if read -t "$timeout" -r response; then
|
||||
# User provided input
|
||||
response="${response,,}" # lowercase
|
||||
case "$response" in
|
||||
y | yes)
|
||||
return 0
|
||||
;;
|
||||
n | no)
|
||||
return 1
|
||||
;;
|
||||
"")
|
||||
# Empty response, use default
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Invalid input, use default
|
||||
echo -e "${YW}Invalid response, using default: ${default}${CL}"
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" # Newline after timeout
|
||||
echo -e "${YW}Timeout - auto-selecting: ${default}${CL}"
|
||||
if [[ "$default" == "y" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_input()
|
||||
#
|
||||
# - Prompts user for text input with timeout and unattended support
|
||||
# - In unattended mode: immediately returns default value
|
||||
# - In interactive mode: waits for user input with configurable timeout
|
||||
# - After timeout: auto-applies default value
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default value (optional, default: "")
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
#
|
||||
# Output:
|
||||
# Prints the user input or default value to stdout
|
||||
#
|
||||
# Example:
|
||||
# username=$(prompt_input "Enter username:" "admin" 30)
|
||||
# echo "Using username: $username"
|
||||
#
|
||||
# # With validation
|
||||
# while true; do
|
||||
# port=$(prompt_input "Enter port:" "8080" 30)
|
||||
# [[ "$port" =~ ^[0-9]+$ ]] && break
|
||||
# echo "Invalid port number"
|
||||
# done
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_input() {
|
||||
local message="${1:-Enter value:}"
|
||||
local default="${2:-}"
|
||||
local timeout="${3:-60}"
|
||||
local response
|
||||
|
||||
# Build display default hint
|
||||
local hint=""
|
||||
[[ -n "$default" ]] && hint=" (default: ${default})"
|
||||
|
||||
# Unattended mode: return default immediately
|
||||
if is_unattended; then
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
# Not a TTY, use default
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Interactive prompt with timeout
|
||||
echo -en "${YW}${message}${hint} (auto-default in ${timeout}s): ${CL}" >&2
|
||||
|
||||
if read -t "$timeout" -r response; then
|
||||
# User provided input (or pressed Enter for empty)
|
||||
if [[ -n "$response" ]]; then
|
||||
echo "$response"
|
||||
else
|
||||
echo "$default"
|
||||
fi
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" >&2 # Newline after timeout
|
||||
echo -e "${YW}Timeout - using default: ${default}${CL}" >&2
|
||||
echo "$default"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_input_required()
|
||||
#
|
||||
# - Prompts user for REQUIRED text input with fallback support
|
||||
# - In unattended mode: Uses fallback value if no env var set (with warning)
|
||||
# - In interactive mode: loops until user provides non-empty input
|
||||
# - Tracks missing required values for end-of-script summary
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Fallback/example value for unattended mode (optional)
|
||||
# $3 - Timeout in seconds (optional, default: 120)
|
||||
# $4 - Environment variable name hint for error messages (optional)
|
||||
#
|
||||
# Output:
|
||||
# Prints the user input or fallback value to stdout
|
||||
#
|
||||
# Returns:
|
||||
# 0 - Success (value provided or fallback used)
|
||||
# 1 - Failed (interactive timeout without input)
|
||||
#
|
||||
# Global:
|
||||
# MISSING_REQUIRED_VALUES - Array tracking fields that used fallbacks
|
||||
#
|
||||
# Example:
|
||||
# # With fallback - script continues even in unattended mode
|
||||
# token=$(prompt_input_required "Enter API Token:" "YOUR_TOKEN_HERE" 60 "var_api_token")
|
||||
#
|
||||
# # Check at end of script if any values need manual configuration
|
||||
# if [[ ${#MISSING_REQUIRED_VALUES[@]} -gt 0 ]]; then
|
||||
# msg_warn "Please configure: ${MISSING_REQUIRED_VALUES[*]}"
|
||||
# fi
|
||||
# ------------------------------------------------------------------------------
|
||||
# Global array to track missing required values
|
||||
declare -g -a MISSING_REQUIRED_VALUES=()
|
||||
|
||||
prompt_input_required() {
|
||||
local message="${1:-Enter required value:}"
|
||||
local fallback="${2:-CHANGE_ME}"
|
||||
local timeout="${3:-120}"
|
||||
local env_var_hint="${4:-}"
|
||||
local response=""
|
||||
|
||||
# Check if value is already set via environment variable (if hint provided)
|
||||
if [[ -n "$env_var_hint" ]]; then
|
||||
local env_value="${!env_var_hint:-}"
|
||||
if [[ -n "$env_value" ]]; then
|
||||
echo "$env_value"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Unattended mode: use fallback with warning
|
||||
if is_unattended; then
|
||||
if [[ -n "$env_var_hint" ]]; then
|
||||
echo -e "${YW}⚠ Required value '${env_var_hint}' not set - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("$env_var_hint")
|
||||
else
|
||||
echo -e "${YW}⚠ Required value not provided - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("(unnamed)")
|
||||
fi
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo -e "${YW}⚠ Not interactive - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("${env_var_hint:-unnamed}")
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Interactive prompt - loop until non-empty input or use fallback on timeout
|
||||
local attempts=0
|
||||
while [[ -z "$response" ]]; do
|
||||
attempts=$((attempts + 1))
|
||||
|
||||
if [[ $attempts -gt 3 ]]; then
|
||||
echo -e "${YW}Too many empty inputs - using fallback: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("${env_var_hint:-manual_input}")
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -en "${YW}${message} (required, timeout ${timeout}s): ${CL}" >&2
|
||||
|
||||
if read -t "$timeout" -r response; then
|
||||
if [[ -z "$response" ]]; then
|
||||
echo -e "${YW}This field is required. Please enter a value. (attempt ${attempts}/3)${CL}" >&2
|
||||
fi
|
||||
else
|
||||
# Timeout occurred - use fallback
|
||||
echo "" >&2
|
||||
echo -e "${YW}Timeout - using fallback value: ${fallback}${CL}" >&2
|
||||
MISSING_REQUIRED_VALUES+=("${env_var_hint:-timeout}")
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_select()
|
||||
#
|
||||
# - Prompts user to select from a list of options with timeout support
|
||||
# - In unattended mode: immediately returns default selection
|
||||
# - In interactive mode: displays numbered menu and waits for choice
|
||||
# - After timeout: auto-applies default selection
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default option number, 1-based (optional, default: 1)
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
# $4+ - Options to display (required, at least 2)
|
||||
#
|
||||
# Output:
|
||||
# Prints the selected option value to stdout
|
||||
#
|
||||
# Returns:
|
||||
# 0 - Success
|
||||
# 1 - No options provided or invalid state
|
||||
#
|
||||
# Example:
|
||||
# choice=$(prompt_select "Select database:" 1 30 "PostgreSQL" "MySQL" "SQLite")
|
||||
# echo "Selected: $choice"
|
||||
#
|
||||
# # With array
|
||||
# options=("Option A" "Option B" "Option C")
|
||||
# selected=$(prompt_select "Choose:" 2 60 "${options[@]}")
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_select() {
|
||||
local message="${1:-Select option:}"
|
||||
local default="${2:-1}"
|
||||
local timeout="${3:-60}"
|
||||
shift 3
|
||||
|
||||
local options=("$@")
|
||||
local num_options=${#options[@]}
|
||||
|
||||
# Validate options
|
||||
if [[ $num_options -eq 0 ]]; then
|
||||
echo "" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate default
|
||||
if [[ ! "$default" =~ ^[0-9]+$ ]] || [[ "$default" -lt 1 ]] || [[ "$default" -gt "$num_options" ]]; then
|
||||
default=1
|
||||
fi
|
||||
|
||||
# Unattended mode: return default immediately
|
||||
if is_unattended; then
|
||||
echo "${options[$((default - 1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "${options[$((default - 1))]}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Display menu
|
||||
echo -e "${YW}${message}${CL}" >&2
|
||||
local i
|
||||
for i in "${!options[@]}"; do
|
||||
local num=$((i + 1))
|
||||
if [[ $num -eq $default ]]; then
|
||||
echo -e " ${GN}${num})${CL} ${options[$i]} ${YW}(default)${CL}" >&2
|
||||
else
|
||||
echo -e " ${GN}${num})${CL} ${options[$i]}" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
# Interactive prompt with timeout
|
||||
echo -en "${YW}Select [1-${num_options}] (auto-select ${default} in ${timeout}s): ${CL}" >&2
|
||||
|
||||
local response
|
||||
if read -t "$timeout" -r response; then
|
||||
if [[ -z "$response" ]]; then
|
||||
# Empty response, use default
|
||||
echo "${options[$((default - 1))]}"
|
||||
elif [[ "$response" =~ ^[0-9]+$ ]] && [[ "$response" -ge 1 ]] && [[ "$response" -le "$num_options" ]]; then
|
||||
# Valid selection
|
||||
echo "${options[$((response - 1))]}"
|
||||
else
|
||||
# Invalid input, use default
|
||||
echo -e "${YW}Invalid selection, using default: ${options[$((default - 1))]}${CL}" >&2
|
||||
echo "${options[$((default - 1))]}"
|
||||
fi
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" >&2 # Newline after timeout
|
||||
echo -e "${YW}Timeout - auto-selecting: ${options[$((default - 1))]}${CL}" >&2
|
||||
echo "${options[$((default - 1))]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# prompt_password()
|
||||
#
|
||||
# - Prompts user for password input with hidden characters
|
||||
# - In unattended mode: returns default or generates random password
|
||||
# - Supports auto-generation of secure passwords
|
||||
# - After timeout: generates random password if allowed
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - Prompt message (required)
|
||||
# $2 - Default value or "generate" for auto-generation (optional)
|
||||
# $3 - Timeout in seconds (optional, default: 60)
|
||||
# $4 - Minimum length for validation (optional, default: 0 = no minimum)
|
||||
#
|
||||
# Output:
|
||||
# Prints the password to stdout
|
||||
#
|
||||
# Example:
|
||||
# password=$(prompt_password "Enter password:" "generate" 30 8)
|
||||
# echo "Password set"
|
||||
#
|
||||
# # Require user input (no default)
|
||||
# db_pass=$(prompt_password "Database password:" "" 60 12)
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_password() {
|
||||
local message="${1:-Enter password:}"
|
||||
local default="${2:-}"
|
||||
local timeout="${3:-60}"
|
||||
local min_length="${4:-0}"
|
||||
local response
|
||||
|
||||
# Generate random password if requested
|
||||
local generated=""
|
||||
if [[ "$default" == "generate" ]]; then
|
||||
generated=$(openssl rand -base64 16 2>/dev/null | tr -dc 'a-zA-Z0-9' | head -c 16)
|
||||
[[ -z "$generated" ]] && generated=$(head /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 16)
|
||||
default="$generated"
|
||||
fi
|
||||
|
||||
# Unattended mode: return default immediately
|
||||
if is_unattended; then
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if running in a TTY
|
||||
if [[ ! -t 0 ]]; then
|
||||
echo "$default"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Build hint
|
||||
local hint=""
|
||||
if [[ -n "$generated" ]]; then
|
||||
hint=" (Enter for auto-generated)"
|
||||
elif [[ -n "$default" ]]; then
|
||||
hint=" (Enter for default)"
|
||||
fi
|
||||
[[ "$min_length" -gt 0 ]] && hint="${hint} [min ${min_length} chars]"
|
||||
|
||||
# Interactive prompt with timeout (silent input)
|
||||
echo -en "${YW}${message}${hint} (timeout ${timeout}s): ${CL}" >&2
|
||||
|
||||
if read -t "$timeout" -rs response; then
|
||||
echo "" >&2 # Newline after hidden input
|
||||
if [[ -n "$response" ]]; then
|
||||
# Validate minimum length
|
||||
if [[ "$min_length" -gt 0 ]] && [[ ${#response} -lt "$min_length" ]]; then
|
||||
echo -e "${YW}Password too short (min ${min_length}), using default${CL}" >&2
|
||||
echo "$default"
|
||||
else
|
||||
echo "$response"
|
||||
fi
|
||||
else
|
||||
echo "$default"
|
||||
fi
|
||||
else
|
||||
# Timeout occurred
|
||||
echo "" >&2 # Newline after timeout
|
||||
echo -e "${YW}Timeout - using generated password${CL}" >&2
|
||||
echo "$default"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# SECTION 6: CLEANUP & MAINTENANCE
|
||||
# ==============================================================================
|
||||
@@ -896,15 +1518,13 @@ check_or_create_swap() {
|
||||
|
||||
msg_error "No active swap detected"
|
||||
|
||||
read -p "Do you want to create a swap file? [y/N]: " create_swap
|
||||
create_swap="${create_swap,,}" # to lowercase
|
||||
|
||||
if [[ "$create_swap" != "y" && "$create_swap" != "yes" ]]; then
|
||||
if ! prompt_confirm "Do you want to create a swap file?" "n" 60; then
|
||||
msg_info "Skipping swap file creation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
read -p "Enter swap size in MB (e.g., 2048 for 2GB): " swap_size_mb
|
||||
local swap_size_mb
|
||||
swap_size_mb=$(prompt_input "Enter swap size in MB (e.g., 2048 for 2GB):" "2048" 60)
|
||||
if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then
|
||||
msg_error "Invalid size input. Aborting."
|
||||
return 1
|
||||
|
||||
@@ -175,9 +175,9 @@ error_handler() {
|
||||
fi
|
||||
|
||||
if [[ -n "$active_log" && -s "$active_log" ]]; then
|
||||
echo "--- Last 20 lines of silent log ---"
|
||||
echo -e "\n${TAB}--- Last 20 lines of log ---"
|
||||
tail -n 20 "$active_log"
|
||||
echo "-----------------------------------"
|
||||
echo -e "${TAB}-----------------------------------\n"
|
||||
|
||||
# Detect context: Container (INSTALL_LOG set + /root exists) vs Host (BUILD_LOG)
|
||||
if [[ -n "${INSTALL_LOG:-}" && -d /root ]]; then
|
||||
@@ -204,23 +204,56 @@ error_handler() {
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
if declare -f msg_custom >/dev/null 2>&1; then
|
||||
echo -en "${TAB}❓${TAB}${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
else
|
||||
echo -en "${YW}Remove broken container ${CTID}? (Y/n) [auto-remove in 60s]: ${CL}"
|
||||
fi
|
||||
|
||||
if read -t 60 -r response; then
|
||||
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
|
||||
echo -e "\n${YW}Removing container ${CTID}${CL}"
|
||||
echo ""
|
||||
if declare -f msg_info >/dev/null 2>&1; then
|
||||
msg_info "Removing container ${CTID}"
|
||||
else
|
||||
echo -e "${YW}Removing container ${CTID}${CL}"
|
||||
fi
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
if declare -f msg_ok >/dev/null 2>&1; then
|
||||
msg_ok "Container ${CTID} removed"
|
||||
else
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
fi
|
||||
elif [[ "$response" =~ ^[Nn]$ ]]; then
|
||||
echo -e "\n${YW}Container ${CTID} kept for debugging${CL}"
|
||||
echo ""
|
||||
if declare -f msg_warn >/dev/null 2>&1; then
|
||||
msg_warn "Container ${CTID} kept for debugging"
|
||||
else
|
||||
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Timeout - auto-remove
|
||||
echo -e "\n${YW}No response - auto-removing container${CL}"
|
||||
echo ""
|
||||
if declare -f msg_info >/dev/null 2>&1; then
|
||||
msg_info "No response - removing container ${CTID}"
|
||||
else
|
||||
echo -e "${YW}No response - removing container ${CTID}${CL}"
|
||||
fi
|
||||
pct stop "$CTID" &>/dev/null || true
|
||||
pct destroy "$CTID" &>/dev/null || true
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
if declare -f msg_ok >/dev/null 2>&1; then
|
||||
msg_ok "Container ${CTID} removed"
|
||||
else
|
||||
echo -e "${GN}✔${CL} Container ${CTID} removed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Force one final status update attempt after cleanup
|
||||
# This ensures status is updated even if the first attempt failed (e.g., HTTP 400)
|
||||
if declare -f post_update_to_api &>/dev/null; then
|
||||
post_update_to_api "failed" "$exit_code" "force"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -243,6 +276,22 @@ error_handler() {
|
||||
# ------------------------------------------------------------------------------
|
||||
on_exit() {
|
||||
local exit_code=$?
|
||||
# Report orphaned "installing" records to telemetry API
|
||||
# Catches ALL exit paths: errors (non-zero), signals, AND clean exits where
|
||||
# post_to_api was called ("installing" sent) but post_update_to_api was never called
|
||||
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
# Ensure log is accessible on host before reporting
|
||||
if declare -f ensure_log_on_host >/dev/null 2>&1; then
|
||||
ensure_log_on_host
|
||||
fi
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
else
|
||||
post_update_to_api "failed" "1"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||||
exit "$exit_code"
|
||||
}
|
||||
@@ -255,6 +304,14 @@ on_exit() {
|
||||
# - Exits with code 130 (128 + SIGINT=2)
|
||||
# ------------------------------------------------------------------------------
|
||||
on_interrupt() {
|
||||
# Ensure log is accessible on host before reporting
|
||||
if declare -f ensure_log_on_host >/dev/null 2>&1; then
|
||||
ensure_log_on_host
|
||||
fi
|
||||
# Report interruption to telemetry API (prevents stuck "installing" records)
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
post_update_to_api "failed" "130"
|
||||
fi
|
||||
if declare -f msg_error >/dev/null 2>&1; then
|
||||
msg_error "Interrupted by user (SIGINT)"
|
||||
else
|
||||
@@ -272,6 +329,14 @@ on_interrupt() {
|
||||
# - Triggered by external process termination
|
||||
# ------------------------------------------------------------------------------
|
||||
on_terminate() {
|
||||
# Ensure log is accessible on host before reporting
|
||||
if declare -f ensure_log_on_host >/dev/null 2>&1; then
|
||||
ensure_log_on_host
|
||||
fi
|
||||
# Report termination to telemetry API (prevents stuck "installing" records)
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
post_update_to_api "failed" "143"
|
||||
fi
|
||||
if declare -f msg_error >/dev/null 2>&1; then
|
||||
msg_error "Terminated by signal (SIGTERM)"
|
||||
else
|
||||
|
||||
@@ -465,6 +465,7 @@ manage_tool_repository() {
|
||||
msg_error "Failed to download MongoDB GPG key"
|
||||
return 1
|
||||
fi
|
||||
chmod 644 "/etc/apt/keyrings/mongodb-server-${version}.gpg"
|
||||
|
||||
# Setup repository
|
||||
local distro_codename
|
||||
@@ -1294,12 +1295,33 @@ setup_deb822_repo() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Import GPG
|
||||
curl -fsSL "$gpg_url" | gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" || {
|
||||
msg_error "Failed to import GPG key for ${name}"
|
||||
# Import GPG key (auto-detect binary vs ASCII-armored format)
|
||||
local tmp_gpg
|
||||
tmp_gpg=$(mktemp) || return 1
|
||||
curl -fsSL "$gpg_url" -o "$tmp_gpg" || {
|
||||
msg_error "Failed to download GPG key for ${name}"
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
|
||||
if grep -q "BEGIN PGP" "$tmp_gpg" 2>/dev/null; then
|
||||
# ASCII-armored — dearmor to binary
|
||||
gpg --dearmor --yes -o "/etc/apt/keyrings/${name}.gpg" < "$tmp_gpg" || {
|
||||
msg_error "Failed to dearmor GPG key for ${name}"
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
# Already in binary GPG format — copy directly
|
||||
cp "$tmp_gpg" "/etc/apt/keyrings/${name}.gpg" || {
|
||||
msg_error "Failed to install GPG key for ${name}"
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
rm -f "$tmp_gpg"
|
||||
chmod 644 "/etc/apt/keyrings/${name}.gpg"
|
||||
|
||||
# Write deb822
|
||||
{
|
||||
echo "Types: deb"
|
||||
@@ -1891,7 +1913,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
local app="$1"
|
||||
local repo="$2"
|
||||
local mode="${3:-tarball}" # tarball | binary | prebuild | singlefile | tag
|
||||
local version="${4:-latest}"
|
||||
local version="${var_appversion:-${4:-latest}}"
|
||||
local target="${5:-/opt/$app}"
|
||||
local asset_pattern="${6:-}"
|
||||
|
||||
@@ -2421,7 +2443,7 @@ function fetch_and_deploy_gh_release() {
|
||||
local app="$1"
|
||||
local repo="$2"
|
||||
local mode="${3:-tarball}" # tarball | binary | prebuild | singlefile
|
||||
local version="${4:-latest}"
|
||||
local version="${var_appversion:-${4:-latest}}"
|
||||
local target="${5:-/opt/$app}"
|
||||
local asset_pattern="${6:-}"
|
||||
|
||||
|
||||
@@ -207,15 +207,9 @@ silent() {
|
||||
msg_custom "→" "${YWB}" "${cmd}"
|
||||
|
||||
if [[ -s "$logfile" ]]; then
|
||||
local log_lines=$(wc -l <"$logfile")
|
||||
echo "--- Last 10 lines of log ---"
|
||||
echo -e "\n${TAB}--- Last 10 lines of log ---"
|
||||
tail -n 10 "$logfile"
|
||||
echo "----------------------------"
|
||||
|
||||
# Show how to view full log if there are more lines
|
||||
if [[ $log_lines -gt 10 ]]; then
|
||||
msg_custom "📋" "${YW}" "View full log (${log_lines} lines): ${logfile}"
|
||||
fi
|
||||
echo -e "${TAB}----------------------------\n"
|
||||
fi
|
||||
|
||||
exit "$rc"
|
||||
|
||||
@@ -75,14 +75,37 @@ pct exec "$CTID" -- bash -c '
|
||||
set -e
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ID=$(grep "^ID=" /etc/os-release | cut -d"=" -f2)
|
||||
VER=$(grep "^VERSION_CODENAME=" /etc/os-release | cut -d"=" -f2)
|
||||
# Source os-release properly (handles quoted values)
|
||||
source /etc/os-release
|
||||
|
||||
# fallback if DNS is poisoned or blocked
|
||||
# Fallback if DNS is poisoned or blocked
|
||||
ORIG_RESOLV="/etc/resolv.conf"
|
||||
BACKUP_RESOLV="/tmp/resolv.conf.backup"
|
||||
|
||||
if ! dig +short pkgs.tailscale.com | grep -qvE "^127\.|^0\.0\.0\.0$"; then
|
||||
# Check DNS resolution using multiple methods (dig may not be installed)
|
||||
dns_check_failed=true
|
||||
if command -v dig &>/dev/null; then
|
||||
if dig +short pkgs.tailscale.com 2>/dev/null | grep -qvE "^127\.|^0\.0\.0\.0$|^$"; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
elif command -v host &>/dev/null; then
|
||||
if host pkgs.tailscale.com 2>/dev/null | grep -q "has address"; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
elif command -v nslookup &>/dev/null; then
|
||||
if nslookup pkgs.tailscale.com 2>/dev/null | grep -q "Address:"; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
elif command -v getent &>/dev/null; then
|
||||
if getent hosts pkgs.tailscale.com &>/dev/null; then
|
||||
dns_check_failed=false
|
||||
fi
|
||||
else
|
||||
# No DNS tools available, try curl directly and assume DNS works
|
||||
dns_check_failed=false
|
||||
fi
|
||||
|
||||
if $dns_check_failed; then
|
||||
echo "[INFO] DNS resolution for pkgs.tailscale.com failed (blocked or redirected)."
|
||||
echo "[INFO] Temporarily overriding /etc/resolv.conf with Cloudflare DNS (1.1.1.1)"
|
||||
cp "$ORIG_RESOLV" "$BACKUP_RESOLV"
|
||||
@@ -92,17 +115,22 @@ fi
|
||||
if ! command -v curl &>/dev/null; then
|
||||
echo "[INFO] curl not found, installing..."
|
||||
apt-get update -qq
|
||||
apt-get install -y curl >/dev/null
|
||||
apt update -qq
|
||||
apt install -y curl >/dev/null
|
||||
fi
|
||||
|
||||
curl -fsSL https://pkgs.tailscale.com/stable/${ID}/${VER}.noarmor.gpg \
|
||||
# Ensure keyrings directory exists
|
||||
mkdir -p /usr/share/keyrings
|
||||
|
||||
curl -fsSL "https://pkgs.tailscale.com/stable/${ID}/${VERSION_CODENAME}.noarmor.gpg" \
|
||||
| tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
|
||||
|
||||
echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/${ID} ${VER} main" \
|
||||
echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/${ID} ${VERSION_CODENAME} main" \
|
||||
>/etc/apt/sources.list.d/tailscale.list
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y tailscale >/dev/null
|
||||
apt update -qq
|
||||
apt install -y tailscale >/dev/null
|
||||
|
||||
if [[ -f /tmp/resolv.conf.backup ]]; then
|
||||
echo "[INFO] Restoring original /etc/resolv.conf"
|
||||
|
||||
@@ -110,6 +110,11 @@ for container in $(pct list | awk '{if(NR>1) print $1}'); do
|
||||
container_hostname=$(pct exec "$container" hostname)
|
||||
containers_needing_reboot+=("$container ($container_hostname)")
|
||||
fi
|
||||
# check if patchmon agent is present in container and run a report if found
|
||||
if pct exec "$container" -- [ -e "/usr/local/bin/patchmon-agent" ]; then
|
||||
echo -e "${BL}[Info]${GN} patchmon-agent found in ${BL} $container ${CL}, triggering report. \n"
|
||||
pct exec "$container" -- "/usr/local/bin/patchmon-agent" "report"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
wait
|
||||
|
||||
@@ -70,7 +70,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -203,7 +203,6 @@ function exit-script() {
|
||||
|
||||
function default_settings() {
|
||||
VMID=$(get_valid_nextid)
|
||||
FORMAT=",efitype=4m"
|
||||
MACHINE=""
|
||||
DISK_SIZE="4G"
|
||||
DISK_CACHE=""
|
||||
@@ -259,11 +258,9 @@ function advanced_settings() {
|
||||
3>&1 1>&2 2>&3); then
|
||||
if [ "$MACH" = q35 ]; then
|
||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}"
|
||||
FORMAT=""
|
||||
MACHINE=" -machine q35"
|
||||
else
|
||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}"
|
||||
FORMAT=",efitype=4m"
|
||||
MACHINE=""
|
||||
fi
|
||||
else
|
||||
@@ -476,31 +473,45 @@ case $STORAGE_TYPE in
|
||||
nfs | dir | cifs)
|
||||
DISK_EXT=".qcow2"
|
||||
DISK_REF="$VMID/"
|
||||
DISK_IMPORT="-format qcow2"
|
||||
DISK_IMPORT="--format qcow2"
|
||||
THIN=""
|
||||
;;
|
||||
btrfs)
|
||||
DISK_EXT=".raw"
|
||||
DISK_REF="$VMID/"
|
||||
DISK_IMPORT="-format raw"
|
||||
FORMAT=",efitype=4m"
|
||||
DISK_IMPORT="--format raw"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="--format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
eval DISK"${i}"=vm-"${VMID}"-disk-"${i}"${DISK_EXT:-}
|
||||
eval DISK"${i}"_REF="${STORAGE}":"${DISK_REF:-}"${!disk}
|
||||
done
|
||||
|
||||
msg_info "Creating a Arch Linux VM"
|
||||
qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \
|
||||
-name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
|
||||
pvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null
|
||||
qm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null
|
||||
|
||||
if qm disk import --help >/dev/null 2>&1; then
|
||||
IMPORT_CMD=(qm disk import)
|
||||
else
|
||||
IMPORT_CMD=(qm importdisk)
|
||||
fi
|
||||
|
||||
IMPORT_OUT="$("${IMPORT_CMD[@]}" "$VMID" "${FILE}" "$STORAGE" ${DISK_IMPORT:-} 2>&1 || true)"
|
||||
DISK_REF_IMPORTED="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully imported disk '\([^']\+\)'.*/\1/p" | tr -d "\r\"'")"
|
||||
[[ -z "$DISK_REF_IMPORTED" ]] && DISK_REF_IMPORTED="$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $1":"$5}' | sort | tail -n1)"
|
||||
[[ -z "$DISK_REF_IMPORTED" ]] && {
|
||||
msg_error "Unable to determine imported disk reference."
|
||||
echo "$IMPORT_OUT"
|
||||
exit 1
|
||||
}
|
||||
msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})"
|
||||
|
||||
qm set $VMID \
|
||||
-efidisk0 ${DISK0_REF}${FORMAT} \
|
||||
-scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \
|
||||
-efidisk0 ${STORAGE}:0,efitype=4m \
|
||||
-scsi0 ${DISK_REF_IMPORTED},${DISK_CACHE}${THIN%,} \
|
||||
-ide2 ${STORAGE}:cloudinit \
|
||||
-boot order=scsi0 \
|
||||
-serial0 socket >/dev/null
|
||||
|
||||
@@ -70,7 +70,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -201,6 +201,17 @@ function exit-script() {
|
||||
exit
|
||||
}
|
||||
|
||||
function select_cloud_init() {
|
||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" \
|
||||
--yesno "Enable Cloud-Init for VM configuration?\n\nCloud-Init allows automatic configuration of:\n- User accounts and passwords\n- SSH keys\n- Network settings (DHCP/Static)\n- DNS configuration\n\nYou can also configure these settings later in Proxmox UI.\n\nNote: Without Cloud-Init, the nocloud image will be used with console auto-login." --defaultno 18 68); then
|
||||
CLOUD_INIT="yes"
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}yes${CL}"
|
||||
else
|
||||
CLOUD_INIT="no"
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}no${CL}"
|
||||
fi
|
||||
}
|
||||
|
||||
function default_settings() {
|
||||
VMID=$(get_valid_nextid)
|
||||
FORMAT=",efitype=4m"
|
||||
@@ -216,7 +227,6 @@ function default_settings() {
|
||||
VLAN=""
|
||||
MTU=""
|
||||
START_VM="yes"
|
||||
CLOUD_INIT="no"
|
||||
METHOD="default"
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}"
|
||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}"
|
||||
@@ -230,7 +240,7 @@ function default_settings() {
|
||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}${MAC}${CL}"
|
||||
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}"
|
||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}"
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}"
|
||||
select_cloud_init
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}"
|
||||
echo -e "${CREATING}${BOLD}${DGN}Creating a Debian 13 VM using the above default settings${CL}"
|
||||
}
|
||||
@@ -400,13 +410,7 @@ function advanced_settings() {
|
||||
exit-script
|
||||
fi
|
||||
|
||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" --yesno "Configure the VM with Cloud-init?" --defaultno 10 58); then
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}yes${CL}"
|
||||
CLOUD_INIT="yes"
|
||||
else
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Configure Cloud-init: ${BGN}no${CL}"
|
||||
CLOUD_INIT="no"
|
||||
fi
|
||||
select_cloud_init
|
||||
|
||||
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}"
|
||||
@@ -473,6 +477,17 @@ else
|
||||
fi
|
||||
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
|
||||
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
|
||||
|
||||
# ==============================================================================
|
||||
# PREREQUISITES
|
||||
# ==============================================================================
|
||||
if ! command -v virt-customize &>/dev/null; then
|
||||
msg_info "Installing libguestfs-tools"
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y libguestfs-tools >/dev/null 2>&1
|
||||
msg_ok "Installed libguestfs-tools"
|
||||
fi
|
||||
|
||||
msg_info "Retrieving the URL for the Debian 13 Qcow2 Disk Image"
|
||||
if [ "$CLOUD_INIT" == "yes" ]; then
|
||||
URL=https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2
|
||||
@@ -486,6 +501,50 @@ echo -en "\e[1A\e[0K"
|
||||
FILE=$(basename $URL)
|
||||
msg_ok "Downloaded ${CL}${BL}${FILE}${CL}"
|
||||
|
||||
# ==============================================================================
|
||||
# IMAGE CUSTOMIZATION
|
||||
# ==============================================================================
|
||||
msg_info "Customizing ${FILE} image"
|
||||
|
||||
WORK_FILE=$(mktemp --suffix=.qcow2)
|
||||
cp "$FILE" "$WORK_FILE"
|
||||
|
||||
# Set hostname
|
||||
virt-customize -q -a "$WORK_FILE" --hostname "${HN}" >/dev/null 2>&1
|
||||
|
||||
# Prepare for unique machine-id on first boot
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "truncate -s 0 /etc/machine-id" >/dev/null 2>&1
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "rm -f /var/lib/dbus/machine-id" >/dev/null 2>&1
|
||||
|
||||
# Disable systemd-firstboot to prevent interactive prompts blocking the console
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "systemctl disable systemd-firstboot.service 2>/dev/null; rm -f /etc/systemd/system/sysinit.target.wants/systemd-firstboot.service; ln -sf /dev/null /etc/systemd/system/systemd-firstboot.service" >/dev/null 2>&1 || true
|
||||
|
||||
# Pre-seed firstboot settings so it won't prompt even if triggered
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "echo 'Etc/UTC' > /etc/timezone && ln -sf /usr/share/zoneinfo/Etc/UTC /etc/localtime" >/dev/null 2>&1 || true
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "touch /etc/locale.conf" >/dev/null 2>&1 || true
|
||||
|
||||
if [ "$CLOUD_INIT" == "yes" ]; then
|
||||
# Cloud-Init handles SSH and login
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config" >/dev/null 2>&1 || true
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config" >/dev/null 2>&1 || true
|
||||
else
|
||||
# Configure auto-login on serial console (ttyS0) and virtual console (tty1)
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d" >/dev/null 2>&1 || true
|
||||
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf << EOF
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM
|
||||
EOF' >/dev/null 2>&1 || true
|
||||
virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/systemd/system/getty@tty1.service.d" >/dev/null 2>&1 || true
|
||||
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM
|
||||
EOF' >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
msg_ok "Customized image"
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
|
||||
case $STORAGE_TYPE in
|
||||
nfs | dir)
|
||||
@@ -501,6 +560,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
@@ -512,7 +576,7 @@ msg_info "Creating a Debian 13 VM"
|
||||
qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \
|
||||
-name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
|
||||
pvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null
|
||||
qm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null
|
||||
qm importdisk $VMID ${WORK_FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null
|
||||
if [ "$CLOUD_INIT" == "yes" ]; then
|
||||
qm set $VMID \
|
||||
-efidisk0 ${DISK0_REF}${FORMAT} \
|
||||
@@ -527,6 +591,10 @@ else
|
||||
-boot order=scsi0 \
|
||||
-serial0 socket >/dev/null
|
||||
fi
|
||||
|
||||
# Clean up work file
|
||||
rm -f "$WORK_FILE"
|
||||
|
||||
DESCRIPTION=$(
|
||||
cat <<EOF
|
||||
<div align='center'>
|
||||
|
||||
@@ -70,7 +70,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -501,6 +501,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -45,7 +45,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -566,6 +566,11 @@ zfspool)
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
|
||||
DISK_VAR="vm-${VMID}-disk-0${DISK_EXT:-}"
|
||||
|
||||
@@ -70,7 +70,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -487,6 +487,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1,2}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -74,7 +74,7 @@ function error_handler() {
|
||||
local exit_code="$?"
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
post_update_to_api "failed" "$command"
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
|
||||
@@ -48,7 +48,7 @@ function error_handler() {
|
||||
local exit_code="$?"
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
post_update_to_api "failed" "$command"
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
@@ -619,6 +619,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -71,7 +71,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -500,6 +500,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1,2}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -79,7 +79,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
@@ -402,6 +402,11 @@ nfs | dir)
|
||||
DISK_REF="$VMID/"
|
||||
DISK_IMPORT="-format qcow2"
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -66,7 +66,7 @@ function error_handler() {
|
||||
local exit_code="$?"
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
post_update_to_api "failed" "$command"
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
@@ -482,6 +482,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -69,7 +69,7 @@ function error_handler() {
|
||||
local exit_code="$?"
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
post_update_to_api "failed" "$command"
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
@@ -484,6 +484,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -68,7 +68,7 @@ function error_handler() {
|
||||
local exit_code="$?"
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
post_update_to_api "failed" "$command"
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
@@ -483,6 +483,11 @@ btrfs)
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT="-format raw"
|
||||
;;
|
||||
esac
|
||||
for i in {0,1}; do
|
||||
disk="DISK$i"
|
||||
|
||||
@@ -69,7 +69,7 @@ function error_handler() {
|
||||
local line_number="$1"
|
||||
local command="$2"
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
post_update_to_api "failed" "${command}"
|
||||
post_update_to_api "failed" "${exit_code}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user