mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-06-28 18:24:56 +02:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d7d3bfacf | |||
| 81812089f6 | |||
| 5bea573281 | |||
| 5603b69b81 | |||
| d756c83b62 | |||
| e36e0bbe3f | |||
| de0408e12f | |||
| b5361e5278 | |||
| 4f9f556a93 | |||
| 4b23f2c72c | |||
| 2ded16ed4b | |||
| e1f61aeeb9 | |||
| fb9f5a0047 | |||
| 17b5f8c10c | |||
| 8b99d3b1cc | |||
| 039470965b | |||
| 8e4a9829cf | |||
| f1eee5f5ed | |||
| fb14e6ae8b | |||
| 4142c5c2d3 | |||
| d85914530f | |||
| 3d9e67292b | |||
| ae60dc138b | |||
| 14a85a8591 | |||
| 1be64c1994 | |||
| 340695b9bd | |||
| ba31c925e3 | |||
| 868b405082 | |||
| 60266b9c17 | |||
| ea8b87fd7f | |||
| 58145d5bd3 | |||
| 9fbe2de1cb | |||
| c918dee5fe | |||
| 0774772b87 | |||
| dc26b8358e | |||
| 01a6c1ddec | |||
| 3e544b750d | |||
| 681924cb1a | |||
| 6ea04b7602 | |||
| b2d20799d8 | |||
| 324fa33d8c | |||
| 5aed3bdde5 | |||
| b3cef14a35 | |||
| 10c8ad6f28 | |||
| 37bcddd847 | |||
| 075a691198 | |||
| fbd083c9ae | |||
| edf167025f | |||
| 3abbefe3b0 | |||
| c356c3827a | |||
| 6f2d3da61f | |||
| f7acb76e80 | |||
| b2ecd86698 | |||
| df10a5ea53 | |||
| 3a65047ff9 | |||
| 5b01ff81e8 | |||
| 4e6cb56f06 | |||
| 470c0672fb | |||
| 62b7080477 | |||
| e193adad5a | |||
| aeb8dba809 | |||
| 3fc7008bbe |
Generated
+204
@@ -1,3 +1,207 @@
|
||||
## 2026-06-27
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- tool: add disk-health tool (SMART + NVMe) [@MickLesk](https://github.com/MickLesk) ([#15417](https://github.com/community-scripts/ProxmoxVE/pull/15417))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Deluge openssl fix [@asylumexp](https://github.com/asylumexp) ([#15435](https://github.com/community-scripts/ProxmoxVE/pull/15435))
|
||||
- fix command syntax in tunarr.sh [@asylumexp](https://github.com/asylumexp) ([#15434](https://github.com/community-scripts/ProxmoxVE/pull/15434))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Storyteller: bump Node.js version to Node 24 [@thinusn](https://github.com/thinusn) ([#15439](https://github.com/community-scripts/ProxmoxVE/pull/15439))
|
||||
|
||||
### ❔ Uncategorized
|
||||
|
||||
- fix(endurain): replace Poetry/uv-pip backend setup with uv sync --frozen --no-dev [@Copilot](https://github.com/Copilot) ([#15429](https://github.com/community-scripts/ProxmoxVE/pull/15429))
|
||||
|
||||
## 2026-06-26
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- Termix: Update Nginx configuration file paths [@xyzulu](https://github.com/xyzulu) ([#15397](https://github.com/community-scripts/ProxmoxVE/pull/15397))
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Docuseal: use real SECRET_KEY_BASE for db:migrate on update [@MickLesk](https://github.com/MickLesk) ([#15411](https://github.com/community-scripts/ProxmoxVE/pull/15411))
|
||||
- bun: correct install for degoog [@MickLesk](https://github.com/MickLesk) ([#15412](https://github.com/community-scripts/ProxmoxVE/pull/15412))
|
||||
- fix databasus update/install errors [@asylumexp](https://github.com/asylumexp) ([#15403](https://github.com/community-scripts/ProxmoxVE/pull/15403))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- tools.func: fix setup_docker - don't abort update on docker pull failure [@MickLesk](https://github.com/MickLesk) ([#15410](https://github.com/community-scripts/ProxmoxVE/pull/15410))
|
||||
- fix(build.func): set /dev/kfd GID in fix_gpu_gids for AMD ROCm [@jamiej](https://github.com/jamiej) ([#15401](https://github.com/community-scripts/ProxmoxVE/pull/15401))
|
||||
- fix alpine mktmp error [@asylumexp](https://github.com/asylumexp) ([#15398](https://github.com/community-scripts/ProxmoxVE/pull/15398))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor: reduce IP-Tag resource usage and clean up ShellCheck findings [@MickLesk](https://github.com/MickLesk) ([#15418](https://github.com/community-scripts/ProxmoxVE/pull/15418))
|
||||
- QoL: kernel-clean: Validate kernel selection input [@MickLesk](https://github.com/MickLesk) ([#15414](https://github.com/community-scripts/ProxmoxVE/pull/15414))
|
||||
- QoL: clean-lxcs exclude matching and set -e cancel handling [@MickLesk](https://github.com/MickLesk) ([#15413](https://github.com/community-scripts/ProxmoxVE/pull/15413))
|
||||
- QoL: Harden microcode download/install in microcode and pbs-microcode [@MickLesk](https://github.com/MickLesk) ([#15415](https://github.com/community-scripts/ProxmoxVE/pull/15415))
|
||||
- QoL: scaling-governor extend selection and guard missing cpufreq [@MickLesk](https://github.com/MickLesk) ([#15416](https://github.com/community-scripts/ProxmoxVE/pull/15416))
|
||||
|
||||
## 2026-06-25
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Pinchflat ([#15367](https://github.com/community-scripts/ProxmoxVE/pull/15367))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- persist gramps-web configuration file after update [@operfesium](https://github.com/operfesium) ([#15394](https://github.com/community-scripts/ProxmoxVE/pull/15394))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- VM-Core: Update some Functions [@MickLesk](https://github.com/MickLesk) ([#15113](https://github.com/community-scripts/ProxmoxVE/pull/15113))
|
||||
|
||||
## 2026-06-24
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- SnapOtter ([#15368](https://github.com/community-scripts/ProxmoxVE/pull/15368))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- enabling rewirte module in apache [@d3v3lop3rDE](https://github.com/d3v3lop3rDE) ([#15360](https://github.com/community-scripts/ProxmoxVE/pull/15360))
|
||||
- watcharr: Increase default RAM allocation from 1024 to 2048 [@MickLesk](https://github.com/MickLesk) ([#15370](https://github.com/community-scripts/ProxmoxVE/pull/15370))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor LibreNMS: replace old install and update variant with tarball approach [@MickLesk](https://github.com/MickLesk) ([#15369](https://github.com/community-scripts/ProxmoxVE/pull/15369))
|
||||
|
||||
### 🗑️ Deleted Scripts
|
||||
|
||||
- delete wger [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15380](https://github.com/community-scripts/ProxmoxVE/pull/15380))
|
||||
- Delete ghost [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15377](https://github.com/community-scripts/ProxmoxVE/pull/15377))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: add SDN vnet selection in advanced install [@MickLesk](https://github.com/MickLesk) ([#15366](https://github.com/community-scripts/ProxmoxVE/pull/15366))
|
||||
- core: add var_http_proxy and var_http_no_proxy support [@MickLesk](https://github.com/MickLesk) ([#15225](https://github.com/community-scripts/ProxmoxVE/pull/15225))
|
||||
|
||||
## 2026-06-23
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- update jdk when updating crafty-controller [@asylumexp](https://github.com/asylumexp) ([#15349](https://github.com/community-scripts/ProxmoxVE/pull/15349))
|
||||
- fix docker update function [@asylumexp](https://github.com/asylumexp) ([#15353](https://github.com/community-scripts/ProxmoxVE/pull/15353))
|
||||
- LibreNMS: run daily.sh as librenms user with git available [@MickLesk](https://github.com/MickLesk) ([#15314](https://github.com/community-scripts/ProxmoxVE/pull/15314))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- termix - patch tmp nginx behaviour to match the install script [@xyzulu](https://github.com/xyzulu) ([#15283](https://github.com/community-scripts/ProxmoxVE/pull/15283))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- Fix syntax error in build function [@l0caldadmin](https://github.com/l0caldadmin) ([#15337](https://github.com/community-scripts/ProxmoxVE/pull/15337))
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- fix: close lxc build function [@ServerBP](https://github.com/ServerBP) ([#15343](https://github.com/community-scripts/ProxmoxVE/pull/15343))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- [arm64] port pve scripts to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15288](https://github.com/community-scripts/ProxmoxVE/pull/15288))
|
||||
|
||||
### ❔ Uncategorized
|
||||
|
||||
- fix(build.func): remove duplicate if statement causing syntax error on container creation [@Copilot](https://github.com/Copilot) ([#15338](https://github.com/community-scripts/ProxmoxVE/pull/15338))
|
||||
|
||||
## 2026-06-22
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Postiz ([#15048](https://github.com/community-scripts/ProxmoxVE/pull/15048))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- invoiceshelf: use pnpm instead of yarn for frontend build [@MickLesk](https://github.com/MickLesk) ([#15312](https://github.com/community-scripts/ProxmoxVE/pull/15312))
|
||||
- VictoriaMetrics: resolve architecture before jq asset filter [@MickLesk](https://github.com/MickLesk) ([#15316](https://github.com/community-scripts/ProxmoxVE/pull/15316))
|
||||
- Endurain: pin uv to the version required by the project [@MickLesk](https://github.com/MickLesk) ([#15313](https://github.com/community-scripts/ProxmoxVE/pull/15313))
|
||||
- add proxy headers to dispatcharr from #15143 [@asylumexp](https://github.com/asylumexp) ([#15293](https://github.com/community-scripts/ProxmoxVE/pull/15293))
|
||||
- Fix-15015: check correct path for certbot [@galz55](https://github.com/galz55) ([#15034](https://github.com/community-scripts/ProxmoxVE/pull/15034))
|
||||
- fix(romm): resolve 403 Forbidden error on nginx mod_zip installation [@hug-efrei](https://github.com/hug-efrei) ([#15134](https://github.com/community-scripts/ProxmoxVE/pull/15134))
|
||||
- Degoog: Fix valkey url in update; set mandatory settings password [@vhsdream](https://github.com/vhsdream) ([#15300](https://github.com/community-scripts/ProxmoxVE/pull/15300))
|
||||
- [arm64] fix update functions to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15290](https://github.com/community-scripts/ProxmoxVE/pull/15290))
|
||||
- Fix typo in victoriametrics [@asylumexp](https://github.com/asylumexp) ([#15289](https://github.com/community-scripts/ProxmoxVE/pull/15289))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- update: esphome to install and run ESPHome Device Builder [@jesserockz](https://github.com/jesserockz) ([#15195](https://github.com/community-scripts/ProxmoxVE/pull/15195))
|
||||
- [arm64] Port scripts between warracker-zwavejsui to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15291](https://github.com/community-scripts/ProxmoxVE/pull/15291))
|
||||
- [arm64] Port scripts between thingsboard & wanderer to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15286](https://github.com/community-scripts/ProxmoxVE/pull/15286))
|
||||
- [arm64] Port scripts between snowshare & thelounge to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15280](https://github.com/community-scripts/ProxmoxVE/pull/15280))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- tools.func: refresh ruby-build when requested version is missing [@MickLesk](https://github.com/MickLesk) ([#15315](https://github.com/community-scripts/ProxmoxVE/pull/15315))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: add pre-install storage health checks [@MickLesk](https://github.com/MickLesk) ([#15226](https://github.com/community-scripts/ProxmoxVE/pull/15226))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- core:: skip LXC stack upgrade prompt in unattended mode [@MickLesk](https://github.com/MickLesk) ([#15319](https://github.com/community-scripts/ProxmoxVE/pull/15319))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- update-apps: sanitize service detection and fail on invalid names [@MickLesk](https://github.com/MickLesk) ([#15318](https://github.com/community-scripts/ProxmoxVE/pull/15318))
|
||||
|
||||
## 2026-06-21
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Matomo: flatten nested deploy layout after update [@MickLesk](https://github.com/MickLesk) ([#15267](https://github.com/community-scripts/ProxmoxVE/pull/15267))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- [arm64] Port scripts between qdrant & snipeit to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15274](https://github.com/community-scripts/ProxmoxVE/pull/15274))
|
||||
- [arm64] Port scripts between nodered & paperlessngx to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15255](https://github.com/community-scripts/ProxmoxVE/pull/15255))
|
||||
- [arm64] port scripts titled between papra and qbittorrent to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15258](https://github.com/community-scripts/ProxmoxVE/pull/15258))
|
||||
- [arm64] Port scripts between mediamtx-nocodb to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15254](https://github.com/community-scripts/ProxmoxVE/pull/15254))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- tools.func: centralize Node.js corepack and npm handling in `setup_nodejs()` [@MickLesk](https://github.com/MickLesk) ([#15268](https://github.com/community-scripts/ProxmoxVE/pull/15268))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- tools.func: APT install and deb822 repo reliability [@MickLesk](https://github.com/MickLesk) ([#15272](https://github.com/community-scripts/ProxmoxVE/pull/15272))
|
||||
- tools.func: prevent MySQL data loss and fix repo version matching [@MickLesk](https://github.com/MickLesk) ([#15271](https://github.com/community-scripts/ProxmoxVE/pull/15271))
|
||||
- tools.func: runtime hardening for API helpers and Docker/MeiliSearch [@MickLesk](https://github.com/MickLesk) ([#15273](https://github.com/community-scripts/ProxmoxVE/pull/15273))
|
||||
|
||||
## 2026-06-20
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
+95
-163
@@ -68,6 +68,9 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -81,7 +84,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
|
||||
<details>
|
||||
<summary><h4>June (20 entries)</h4></summary>
|
||||
<summary><h4>June (27 entries)</h4></summary>
|
||||
|
||||
[View June 2026 Changelog](.github/changelogs/2026/06.md)
|
||||
|
||||
@@ -486,16 +489,104 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-06-24
|
||||
## 2026-06-28
|
||||
|
||||
## 2026-06-27
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- tool: add disk-health tool (SMART + NVMe) [@MickLesk](https://github.com/MickLesk) ([#15417](https://github.com/community-scripts/ProxmoxVE/pull/15417))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- enabling rewirte module in apache [@d3v3lop3rDE](https://github.com/d3v3lop3rDE) ([#15360](https://github.com/community-scripts/ProxmoxVE/pull/15360))
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Deluge openssl fix [@asylumexp](https://github.com/asylumexp) ([#15435](https://github.com/community-scripts/ProxmoxVE/pull/15435))
|
||||
- fix command syntax in tunarr.sh [@asylumexp](https://github.com/asylumexp) ([#15434](https://github.com/community-scripts/ProxmoxVE/pull/15434))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Storyteller: bump Node.js version to Node 24 [@thinusn](https://github.com/thinusn) ([#15439](https://github.com/community-scripts/ProxmoxVE/pull/15439))
|
||||
|
||||
### ❔ Uncategorized
|
||||
|
||||
- fix(endurain): replace Poetry/uv-pip backend setup with uv sync --frozen --no-dev [@Copilot](https://github.com/Copilot) ([#15429](https://github.com/community-scripts/ProxmoxVE/pull/15429))
|
||||
|
||||
## 2026-06-26
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- Termix: Update Nginx configuration file paths [@xyzulu](https://github.com/xyzulu) ([#15397](https://github.com/community-scripts/ProxmoxVE/pull/15397))
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Docuseal: use real SECRET_KEY_BASE for db:migrate on update [@MickLesk](https://github.com/MickLesk) ([#15411](https://github.com/community-scripts/ProxmoxVE/pull/15411))
|
||||
- bun: correct install for degoog [@MickLesk](https://github.com/MickLesk) ([#15412](https://github.com/community-scripts/ProxmoxVE/pull/15412))
|
||||
- fix databasus update/install errors [@asylumexp](https://github.com/asylumexp) ([#15403](https://github.com/community-scripts/ProxmoxVE/pull/15403))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- tools.func: fix setup_docker - don't abort update on docker pull failure [@MickLesk](https://github.com/MickLesk) ([#15410](https://github.com/community-scripts/ProxmoxVE/pull/15410))
|
||||
- fix(build.func): set /dev/kfd GID in fix_gpu_gids for AMD ROCm [@jamiej](https://github.com/jamiej) ([#15401](https://github.com/community-scripts/ProxmoxVE/pull/15401))
|
||||
- fix alpine mktmp error [@asylumexp](https://github.com/asylumexp) ([#15398](https://github.com/community-scripts/ProxmoxVE/pull/15398))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor: reduce IP-Tag resource usage and clean up ShellCheck findings [@MickLesk](https://github.com/MickLesk) ([#15418](https://github.com/community-scripts/ProxmoxVE/pull/15418))
|
||||
- QoL: kernel-clean: Validate kernel selection input [@MickLesk](https://github.com/MickLesk) ([#15414](https://github.com/community-scripts/ProxmoxVE/pull/15414))
|
||||
- QoL: clean-lxcs exclude matching and set -e cancel handling [@MickLesk](https://github.com/MickLesk) ([#15413](https://github.com/community-scripts/ProxmoxVE/pull/15413))
|
||||
- QoL: Harden microcode download/install in microcode and pbs-microcode [@MickLesk](https://github.com/MickLesk) ([#15415](https://github.com/community-scripts/ProxmoxVE/pull/15415))
|
||||
- QoL: scaling-governor extend selection and guard missing cpufreq [@MickLesk](https://github.com/MickLesk) ([#15416](https://github.com/community-scripts/ProxmoxVE/pull/15416))
|
||||
|
||||
## 2026-06-25
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Pinchflat ([#15367](https://github.com/community-scripts/ProxmoxVE/pull/15367))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- persist gramps-web configuration file after update [@operfesium](https://github.com/operfesium) ([#15394](https://github.com/community-scripts/ProxmoxVE/pull/15394))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- VM-Core: Update some Functions [@MickLesk](https://github.com/MickLesk) ([#15113](https://github.com/community-scripts/ProxmoxVE/pull/15113))
|
||||
|
||||
## 2026-06-24
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- SnapOtter ([#15368](https://github.com/community-scripts/ProxmoxVE/pull/15368))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- enabling rewirte module in apache [@d3v3lop3rDE](https://github.com/d3v3lop3rDE) ([#15360](https://github.com/community-scripts/ProxmoxVE/pull/15360))
|
||||
- watcharr: Increase default RAM allocation from 1024 to 2048 [@MickLesk](https://github.com/MickLesk) ([#15370](https://github.com/community-scripts/ProxmoxVE/pull/15370))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor LibreNMS: replace old install and update variant with tarball approach [@MickLesk](https://github.com/MickLesk) ([#15369](https://github.com/community-scripts/ProxmoxVE/pull/15369))
|
||||
|
||||
### 🗑️ Deleted Scripts
|
||||
|
||||
- delete wger [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15380](https://github.com/community-scripts/ProxmoxVE/pull/15380))
|
||||
- Delete ghost [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15377](https://github.com/community-scripts/ProxmoxVE/pull/15377))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- core: add SDN vnet selection in advanced install [@MickLesk](https://github.com/MickLesk) ([#15366](https://github.com/community-scripts/ProxmoxVE/pull/15366))
|
||||
- core: add var_http_proxy and var_http_no_proxy support [@MickLesk](https://github.com/MickLesk) ([#15225](https://github.com/community-scripts/ProxmoxVE/pull/15225))
|
||||
|
||||
## 2026-06-23
|
||||
@@ -1042,163 +1133,4 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Sure: Remove `$STD` for `systemctl enable -q` [@tremor021](https://github.com/tremor021) ([#14801](https://github.com/community-scripts/ProxmoxVE/pull/14801))
|
||||
|
||||
## 2026-05-28
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- RomM: remove nginx default.conf during installation [@MickLesk](https://github.com/MickLesk) ([#14766](https://github.com/community-scripts/ProxmoxVE/pull/14766))
|
||||
- Open-Archiver: replace pnpm approve-builds --yes with --all [@MickLesk](https://github.com/MickLesk) ([#14765](https://github.com/community-scripts/ProxmoxVE/pull/14765))
|
||||
- fix(hermesagent): set npm_config_yes=true to suppress interactive pro… [@steveonjava](https://github.com/steveonjava) ([#14763](https://github.com/community-scripts/ProxmoxVE/pull/14763))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Yamtrack: migrate to uv [@MickLesk](https://github.com/MickLesk) ([#14767](https://github.com/community-scripts/ProxmoxVE/pull/14767))
|
||||
|
||||
### ❔ Uncategorized
|
||||
|
||||
- chore(ct): sync adventurelog defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#14772](https://github.com/community-scripts/ProxmoxVE/pull/14772))
|
||||
|
||||
## 2026-05-27
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- MusicSeerr ([#14746](https://github.com/community-scripts/ProxmoxVE/pull/14746))
|
||||
- Hermes Agent ([#14751](https://github.com/community-scripts/ProxmoxVE/pull/14751))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- grist: restore install:ee step [@paulfitz](https://github.com/paulfitz) ([#14759](https://github.com/community-scripts/ProxmoxVE/pull/14759))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- [tools.func]: `setup_gs()` fix getting dotted release format [@tremor021](https://github.com/tremor021) ([#14745](https://github.com/community-scripts/ProxmoxVE/pull/14745))
|
||||
|
||||
## 2026-05-26
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Add directory creation to Profilarr update script [@ryansully](https://github.com/ryansully) ([#14740](https://github.com/community-scripts/ProxmoxVE/pull/14740))
|
||||
- profilarr: Fix ARCH assignment in profilarr.sh to support Profilarr build usage [@mpeleshenko](https://github.com/mpeleshenko) ([#14709](https://github.com/community-scripts/ProxmoxVE/pull/14709))
|
||||
- Jackett: Remove quotes in Service File [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14729](https://github.com/community-scripts/ProxmoxVE/pull/14729))
|
||||
- Open-archiver: approve pnpm build scripts and run build:oss without subshell [@MickLesk](https://github.com/MickLesk) ([#14711](https://github.com/community-scripts/ProxmoxVE/pull/14711))
|
||||
- Docuseal: read Ruby version from Gemfile, upgrade on update if needed [@MickLesk](https://github.com/MickLesk) ([#14715](https://github.com/community-scripts/ProxmoxVE/pull/14715))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Birdnet-GO: install libonnxruntime.so from release tarball [@MickLesk](https://github.com/MickLesk) ([#14716](https://github.com/community-scripts/ProxmoxVE/pull/14716))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- tools.func: better error diagnostics, consistent OS detection, setup function ordering [@MickLesk](https://github.com/MickLesk) ([#14692](https://github.com/community-scripts/ProxmoxVE/pull/14692))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- IPTag-Tool: use qm set for VM tags to handle snapshot sections crrectly [@MickLesk](https://github.com/MickLesk) ([#14713](https://github.com/community-scripts/ProxmoxVE/pull/14713))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Netdata: extend PVE version support to 9.x [@MickLesk](https://github.com/MickLesk) ([#14714](https://github.com/community-scripts/ProxmoxVE/pull/14714))
|
||||
|
||||
## 2026-05-25
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- karakeep: fix: pip config [@CrazyWolf13](https://github.com/CrazyWolf13) ([#14703](https://github.com/community-scripts/ProxmoxVE/pull/14703))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- tools.func: replace raw GitHub API curl calls with get_latest_github_release [@MickLesk](https://github.com/MickLesk) ([#14690](https://github.com/community-scripts/ProxmoxVE/pull/14690))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Kernel-Clean: detect meta-packages and fix silent removal failures [@MickLesk](https://github.com/MickLesk) ([#14674](https://github.com/community-scripts/ProxmoxVE/pull/14674))
|
||||
|
||||
## 2026-05-24
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- RomM: add installation steps for Nginx mod_zip module [@MickLesk](https://github.com/MickLesk) ([#14678](https://github.com/community-scripts/ProxmoxVE/pull/14678))
|
||||
- ISponsorblockTV: detect CPU capabilities to select compatible binary [@MickLesk](https://github.com/MickLesk) ([#14677](https://github.com/community-scripts/ProxmoxVE/pull/14677))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Refactor: MQTT [@tremor021](https://github.com/tremor021) ([#14673](https://github.com/community-scripts/ProxmoxVE/pull/14673))
|
||||
|
||||
## 2026-05-23
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- IronClaw: Extra configuration during install to ensure Web Gateway can run [@SystemIdleProcess](https://github.com/SystemIdleProcess) ([#14635](https://github.com/community-scripts/ProxmoxVE/pull/14635))
|
||||
- Tunarr: fix path to backup during update [@SystemIdleProcess](https://github.com/SystemIdleProcess) ([#14655](https://github.com/community-scripts/ProxmoxVE/pull/14655))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- wealthfolio: add: prebuild [@CrazyWolf13](https://github.com/CrazyWolf13) ([#14658](https://github.com/community-scripts/ProxmoxVE/pull/14658))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- kernel-clean: support range syntax in selection prompt [@djhojd](https://github.com/djhojd) ([#14656](https://github.com/community-scripts/ProxmoxVE/pull/14656))
|
||||
|
||||
## 2026-05-22
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- bitfocus-companion ([#14603](https://github.com/community-scripts/ProxmoxVE/pull/14603))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- fix(the-lounge): install Node.js 22 before deb package [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14648](https://github.com/community-scripts/ProxmoxVE/pull/14648))
|
||||
- Docmost: Fix duplicate STORAGE_DRIVER [@MickLesk](https://github.com/MickLesk) ([#14645](https://github.com/community-scripts/ProxmoxVE/pull/14645))
|
||||
- Profilarr: pin Deno version to v2.7.5 [@MickLesk](https://github.com/MickLesk) ([#14632](https://github.com/community-scripts/ProxmoxVE/pull/14632))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- add: karakeep cli wrapper [@CrazyWolf13](https://github.com/CrazyWolf13) ([#14618](https://github.com/community-scripts/ProxmoxVE/pull/14618))
|
||||
|
||||
- #### 💥 Breaking Changes
|
||||
|
||||
- OpenCloud: v7.0.0 changes [@vhsdream](https://github.com/vhsdream) ([#14650](https://github.com/community-scripts/ProxmoxVE/pull/14650))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- workflows: update workflows, templates to support arm64. [@asylumexp](https://github.com/asylumexp) ([#14653](https://github.com/community-scripts/ProxmoxVE/pull/14653))
|
||||
- SoulSync: setup Node v22 and build WebUI [@MickLesk](https://github.com/MickLesk) ([#14639](https://github.com/community-scripts/ProxmoxVE/pull/14639))
|
||||
- Refactor: Dispatcharr [@MickLesk](https://github.com/MickLesk) ([#14313](https://github.com/community-scripts/ProxmoxVE/pull/14313))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- fix: make LXC banner OS detection dynamic via /etc/os-release [@atahan99](https://github.com/atahan99) ([#14269](https://github.com/community-scripts/ProxmoxVE/pull/14269))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- core: suppress MOTD for non-interactive shells [@MickLesk](https://github.com/MickLesk) ([#14638](https://github.com/community-scripts/ProxmoxVE/pull/14638))
|
||||
- Sure: Remove `$STD` for `systemctl enable -q` [@tremor021](https://github.com/tremor021) ([#14801](https://github.com/community-scripts/ProxmoxVE/pull/14801))
|
||||
+3
-3
@@ -52,11 +52,11 @@ function update_script() {
|
||||
[[ "$MONGO_ARCH" == "arm64" ]] && MONGO_DIST="ubuntu2204"
|
||||
fetch_and_deploy_from_url "https://fastdl.mongodb.org/tools/db/mongodb-database-tools-${MONGO_DIST}-${MONGO_ARCH}-100.16.1.deb"
|
||||
fi
|
||||
ensure_dependencies mariadb-client
|
||||
mkdir -p /usr/local/mariadb-{10.6,12.1}/bin /usr/local/mysql-{5.7,8.0,8.4,9}/bin /usr/local/mongodb-database-tools/bin
|
||||
[[ -f /usr/bin/mongodump ]] && ln -sf /usr/bin/mongodump /usr/local/mongodb-database-tools/bin/mongodump
|
||||
[[ -f /usr/bin/mongorestore ]] && ln -sf /usr/bin/mongorestore /usr/local/mongodb-database-tools/bin/mongorestore
|
||||
# Create MariaDB and MySQL client symlinks for compatibility
|
||||
ensure_dependencies mariadb-client
|
||||
mkdir -p /usr/local/mariadb-{10.6,12.1}/bin /usr/local/mysql-{5.7,8.0,8.4,9}/bin /usr/local/mongodb-database-tools/bin
|
||||
for dir in /usr/local/mariadb-{10.6,12.1}/bin; do
|
||||
ln -sf /usr/bin/mariadb-dump "$dir/mariadb-dump"
|
||||
ln -sf /usr/bin/mariadb "$dir/mariadb"
|
||||
@@ -79,7 +79,7 @@ function update_script() {
|
||||
cd /opt/databasus/backend
|
||||
$STD go mod download
|
||||
$STD /root/go/bin/swag init -g cmd/main.go -o swagger
|
||||
$STD env CGO_ENABLED=0 GOOS=linux GOARCH=$(arch_resolve) go build -o databasus ./cmd/main.go
|
||||
$STD env CGO_ENABLED=0 GOOS=linux GOARCH=$(arch_resolve) go build -o databasus ./cmd
|
||||
mv /opt/databasus/backend/databasus /opt/databasus/databasus
|
||||
mkdir -p /opt/databasus/ui/build
|
||||
cp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ function update_script() {
|
||||
create_backup /opt/degoog/.env \
|
||||
/opt/degoog/data
|
||||
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
if [[ ! -x /root/.bun/bin/bun ]]; then
|
||||
msg_info "Installing Bun"
|
||||
export BUN_INSTALL="/root/.bun"
|
||||
curl -fsSL https://bun.sh/install | $STD bash
|
||||
@@ -52,7 +52,7 @@ function update_script() {
|
||||
msg_ok "Updated Valkey"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "degoog" "fccview/degoog" "prebuild" "latest" "/opt/degoog" "degoog_*_prebuild.tar.gz"
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "curl-impersonate" "lexiforest/curl-impersonate" "prebuild" "latest" "/usr/local/bin" "curl-impersonate-v*.$(uname -m)-linux-gnu.tar.gz"
|
||||
fetch_and_deploy_gh_release "curl-impersonate" "lexiforest/curl-impersonate" "prebuild" "latest" "/usr/local/bin" "curl-impersonate-v*.$(uname -m)-linux-gnu.tar.gz"
|
||||
|
||||
restore_backup
|
||||
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-yes}"
|
||||
var_arm64="${var_arm64:-no}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
@@ -31,7 +31,7 @@ function update_script() {
|
||||
msg_info "Updating Deluge"
|
||||
ensure_dependencies python3-setuptools
|
||||
$STD apt update
|
||||
$STD pip3 install deluge[all] --upgrade
|
||||
$STD pip3 install deluge[all] "pyopenssl<25" --upgrade
|
||||
msg_ok "Updated Deluge"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ function update_script() {
|
||||
eval "$(rbenv init - bash)" 2>/dev/null || true
|
||||
export RAILS_ENV=production
|
||||
export NODE_ENV=production
|
||||
export SECRET_KEY_BASE_DUMMY=1
|
||||
mkdir -p /opt/docuseal/tmp
|
||||
set -a
|
||||
source /opt/docuseal/.env
|
||||
set +a
|
||||
|
||||
+1
-4
@@ -63,10 +63,7 @@ function update_script() {
|
||||
cd /opt/endurain/backend
|
||||
UV_VERSION=$(grep -Po 'required-version\s*=\s*"\K[^"]+' pyproject.toml 2>/dev/null || echo "0.11.18")
|
||||
UV_VERSION="$UV_VERSION" setup_uv
|
||||
$STD poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
$STD uv venv --clear
|
||||
$STD uv pip install -r requirements.txt
|
||||
$STD uv pip install pytz
|
||||
$STD uv sync --frozen --no-dev
|
||||
msg_ok "Backend Updated"
|
||||
|
||||
msg_info "Starting Service"
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: fabrice1236
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://ghost.org/ | Github: https://github.com/TryGhost/Ghost
|
||||
|
||||
APP="Ghost"
|
||||
var_tags="${var_tags:-cms;blog}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-1024}"
|
||||
var_disk="${var_disk:-5}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-yes}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
setup_mariadb
|
||||
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
|
||||
ensure_dependencies git
|
||||
|
||||
msg_info "Updating Ghost"
|
||||
if command -v ghost &>/dev/null; then
|
||||
current_version=$(ghost version | grep 'Ghost-CLI version' | awk '{print $3}')
|
||||
latest_version=$(npm show ghost-cli version)
|
||||
if [ "$current_version" != "$latest_version" ]; then
|
||||
msg_info "Updating ${APP} from version v${current_version} to v${latest_version}"
|
||||
$STD npm install -g ghost-cli@latest
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "${APP} is already at v${current_version}"
|
||||
fi
|
||||
else
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
|
||||
echo -e "${GATEWAY}${BGN}http://${IP}:2368${CL}"
|
||||
@@ -85,8 +85,13 @@ function update_script() {
|
||||
cd /opt/gramps-web/frontend
|
||||
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
create_backup /opt/gramps-web/frontend/dist/config.js
|
||||
|
||||
$STD npm install
|
||||
$STD npm run build
|
||||
|
||||
restore_backup
|
||||
|
||||
msg_ok "Updated Gramps Web Frontend"
|
||||
|
||||
msg_info "Starting Service"
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
________ __
|
||||
/ ____/ /_ ____ _____/ /_
|
||||
/ / __/ __ \/ __ \/ ___/ __/
|
||||
/ /_/ / / / / /_/ (__ ) /_
|
||||
\____/_/ /_/\____/____/\__/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
____ _ __ ______ __
|
||||
/ __ \(_)___ _____/ /_ / __/ /___ _/ /_
|
||||
/ /_/ / / __ \/ ___/ __ \/ /_/ / __ `/ __/
|
||||
/ ____/ / / / / /__/ / / / __/ / /_/ / /_
|
||||
/_/ /_/_/ /_/\___/_/ /_/_/ /_/\__,_/\__/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
_____ ____ __ __
|
||||
/ ___/____ ____ _____ / __ \/ /_/ /____ _____
|
||||
\__ \/ __ \/ __ `/ __ \/ / / / __/ __/ _ \/ ___/
|
||||
___/ / / / / /_/ / /_/ / /_/ / /_/ /_/ __/ /
|
||||
/____/_/ /_/\__,_/ .___/\____/\__/\__/\___/_/
|
||||
/_/
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
_ ______ ____ _____
|
||||
| | /| / / __ `/ _ \/ ___/
|
||||
| |/ |/ / /_/ / __/ /
|
||||
|__/|__/\__, /\___/_/
|
||||
/____/
|
||||
+25
-17
@@ -24,27 +24,35 @@ function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
if [ ! -d /opt/librenms ]; then
|
||||
if [[ ! -d /opt/librenms ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
setup_mariadb
|
||||
ensure_dependencies git
|
||||
if [[ ! -d /opt/librenms/.git ]]; then
|
||||
msg_info "Initializing LibreNMS git metadata"
|
||||
LIBRENMS_VERSION=$(cat ~/.librenms 2>/dev/null)
|
||||
cd /opt/librenms
|
||||
git init -q
|
||||
git remote add origin https://github.com/librenms/librenms.git
|
||||
git fetch --depth 1 origin "refs/tags/v${LIBRENMS_VERSION}" 2>/dev/null ||
|
||||
git fetch --depth 1 origin "refs/tags/${LIBRENMS_VERSION}" 2>/dev/null || true
|
||||
git checkout -qf FETCH_HEAD 2>/dev/null || true
|
||||
chown -R librenms:librenms .git
|
||||
msg_ok "Initialized LibreNMS git metadata"
|
||||
if check_for_gh_release "librenms" "librenms/librenms"; then
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop php8.4-fpm librenms-scheduler.timer
|
||||
msg_ok "Stopped Services"
|
||||
|
||||
create_backup /opt/librenms/.env /opt/librenms/config.php /opt/librenms/rrd
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "librenms" "librenms/librenms" "tarball"
|
||||
restore_backup
|
||||
|
||||
msg_info "Updating LibreNMS"
|
||||
mkdir -p /opt/librenms/{rrd,logs,bootstrap/cache,storage}
|
||||
chown -R librenms:librenms /opt/librenms
|
||||
chmod 771 /opt/librenms
|
||||
chmod -R ug=rwX /opt/librenms/bootstrap/cache /opt/librenms/storage /opt/librenms/logs /opt/librenms/rrd
|
||||
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && uv venv --clear .venv && source .venv/bin/activate && uv pip install -r requirements.txt"
|
||||
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev"
|
||||
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && php8.4 artisan optimize:clear"
|
||||
$STD su - librenms -s /bin/bash -c "cd /opt/librenms && php8.4 artisan migrate --force --isolated"
|
||||
msg_ok "Updated LibreNMS"
|
||||
|
||||
msg_info "Starting Services"
|
||||
systemctl start php8.4-fpm librenms-scheduler.timer
|
||||
msg_ok "Started Services"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
msg_info "Updating LibreNMS"
|
||||
$STD su - librenms -s /bin/bash -c 'cd /opt/librenms && ./daily.sh'
|
||||
msg_ok "Updated LibreNMS"
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-12}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-yes}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
@@ -60,8 +60,9 @@ function update_script() {
|
||||
fi
|
||||
$STD apt install -y build-essential "$pcre_pkg" libssl-dev zlib1g-dev
|
||||
|
||||
if check_for_gh_release "openresty" "openresty/openresty"; then
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "openresty" "openresty/openresty" "prebuild" "${CHECK_UPDATE_RELEASE}" "/opt/openresty" "openresty-*.tar.gz"
|
||||
OPENRESTY_VERSION="1.29.2.5"
|
||||
if [[ "$(cat ~/.openresty 2>/dev/null)" != "$OPENRESTY_VERSION" ]]; then
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_from_url "https://openresty.org/download/openresty-${OPENRESTY_VERSION}.tar.gz" "/opt/openresty"
|
||||
|
||||
msg_info "Building OpenResty"
|
||||
cd /opt/openresty
|
||||
@@ -77,6 +78,7 @@ function update_script() {
|
||||
--with-stream_ssl_module
|
||||
$STD make -j"$(nproc)"
|
||||
$STD make install
|
||||
echo "${OPENRESTY_VERSION}" >~/.openresty
|
||||
rm -rf /opt/openresty
|
||||
cat <<'EOF' >/lib/systemd/system/openresty.service
|
||||
[Unit]
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: nnsense
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/kieraneglin/pinchflat
|
||||
|
||||
APP="Pinchflat"
|
||||
var_tags="${var_tags:-media;youtube;downloader}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
var_gpu="${var_gpu:-yes}"
|
||||
var_arm64="${var_arm64:-yes}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/pinchflat/app ]]; then
|
||||
msg_error "No ${APP} installation found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if check_for_gh_release "pinchflat" "kieraneglin/pinchflat"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop pinchflat
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "pinchflat" "kieraneglin/pinchflat" "tarball" "latest" "/opt/pinchflat-src"
|
||||
|
||||
msg_info "Building Pinchflat"
|
||||
cd /opt/pinchflat-src
|
||||
export MIX_ENV=prod
|
||||
export ERL_FLAGS="+JPperf true"
|
||||
$STD mix deps.get --only prod
|
||||
$STD mix deps.compile
|
||||
$STD yarn --cwd assets install
|
||||
$STD mix assets.deploy
|
||||
$STD mix compile
|
||||
$STD mix release --overwrite
|
||||
rm -rf /opt/pinchflat/app
|
||||
cp -r _build/prod/rel/pinchflat /opt/pinchflat/app
|
||||
msg_ok "Built Pinchflat"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start pinchflat
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
msg_ok "Completed Successfully!\n"
|
||||
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8945${CL}"
|
||||
+1
-1
@@ -8,7 +8,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
|
||||
APP="Seerr"
|
||||
var_tags="${var_tags:-media}"
|
||||
var_cpu="${var_cpu:-4}"
|
||||
var_ram="${var_ram:-6144}"
|
||||
var_ram="${var_ram:-4096}"
|
||||
var_disk="${var_disk:-12}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://snapotter.com
|
||||
|
||||
APP="SnapOtter"
|
||||
var_tags="${var_tags:-media;image}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-20}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
var_arm64="${var_arm64:-no}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/snapotter ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "snapotter" "snapotter-hq/SnapOtter"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop snapotter
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "tarball"
|
||||
|
||||
msg_info "Updating SnapOtter"
|
||||
cd /opt/snapotter
|
||||
$STD npm pkg delete scripts.prepare
|
||||
$STD pnpm install --frozen-lockfile
|
||||
$STD pnpm --filter @snapotter/web build
|
||||
sed -i 's/mediapipe==0.10.21/mediapipe>=0.10.21/' /opt/snapotter/docker/feature-manifest.json
|
||||
msg_ok "Updated SnapOtter"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start snapotter
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:1349${CL}"
|
||||
@@ -30,6 +30,8 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
NODE_VERSION="24" NODE_MODULE="corepack,yarn" setup_nodejs
|
||||
|
||||
if check_for_gl_release "storyteller" "storyteller-platform/storyteller"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop storyteller
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ EOF
|
||||
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
|
||||
curl -fsSL "https://raw.githubusercontent.com/Termix-SSH/Termix/main/docker/nginx.conf" -o /etc/nginx/nginx.conf
|
||||
sed -i '/^master_process/d' /etc/nginx/nginx.conf
|
||||
sed -i 's|pid /tmp/nginx/nginx.pid;|pid /run/nginx.pid;|' /etc/nginx/nginx.conf
|
||||
sed -i '/^pid \/app\/nginx/d' /etc/nginx/nginx.conf
|
||||
sed -i 's|error_log /tmp/nginx/error.log|error_log /var/log/nginx/error.log|' /etc/nginx/nginx.conf
|
||||
sed -i 's|access_log /tmp/nginx/access.log|access_log /var/log/nginx/access.log|' /etc/nginx/nginx.conf
|
||||
sed -i 's|/app/html|/opt/termix/html|g' /etc/nginx/nginx.conf
|
||||
|
||||
+2
-7
@@ -33,17 +33,12 @@ function update_script() {
|
||||
systemctl stop tunarr
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Creating Backup"
|
||||
if [ -d "/root/.local/share/tunarr" ]; then
|
||||
tar -czf "/opt/${APP}_backup_$(date +%F).tar.gz" /root/.local/share/tunarr $STD
|
||||
msg_ok "Backup Created"
|
||||
else
|
||||
msg_error "Backup failed: /root/.local/share/tunarr does not exist"
|
||||
fi
|
||||
create_backup /root/.local/share/tunarr
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "tunarr" "chrisbenincasa/tunarr" "prebuild" "latest" "/opt/tunarr" "*linux-$(arch_resolve "x64" "arm64").tar.gz"
|
||||
cd /opt/tunarr
|
||||
mv tunarr* tunarr
|
||||
restore_backup
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start tunarr
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
|
||||
APP="Watcharr"
|
||||
var_tags="${var_tags:-media}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-1024}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Slaviša Arežina (tremor021)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/wger-project/wger
|
||||
|
||||
APP="wger"
|
||||
var_tags="${var_tags:-management;fitness}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-yes}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/wger ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "wger" "wger-project/wger"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop redis-server nginx celery celery-beat wger
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/wger/media /opt/wger_media_backup
|
||||
cp /opt/wger/.env /opt/wger_env_backup
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wger" "wger-project/wger" "tarball"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/wger_media_backup/. /opt/wger/media
|
||||
cp /opt/wger_env_backup /opt/wger/.env
|
||||
rm -rf /opt/wger_media_backup /opt/wger_env_backup
|
||||
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Updating wger"
|
||||
cd /opt/wger
|
||||
set -a && source /opt/wger/.env && set +a
|
||||
export DJANGO_SETTINGS_MODULE=settings.main
|
||||
$STD uv pip install .
|
||||
$STD npm install
|
||||
$STD npm run build:css:sass
|
||||
$STD uv run python manage.py migrate
|
||||
$STD uv run python manage.py collectstatic --no-input
|
||||
msg_ok "Updated wger"
|
||||
|
||||
msg_info "Starting Services"
|
||||
systemctl start redis-server nginx celery celery-beat wger
|
||||
msg_ok "Started Services"
|
||||
msg_ok "Updated Successfully"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
|
||||
echo -e "${GATEWAY}${BGN}http://${IP}:3000${CL}"
|
||||
@@ -33,7 +33,7 @@ done
|
||||
# Install MongoDB Database Tools via direct .deb (no APT repo for Debian 13)
|
||||
[[ "$(get_os_info id)" == "ubuntu" ]] && MONGO_DIST="ubuntu2204" || MONGO_DIST="debian12"
|
||||
# MongoDB only publishes arm64 builds for Ubuntu
|
||||
[[ "$MONGO_ARCH" == "arm64" ]] && MONGO_DIST="ubuntu2204"
|
||||
[[ "$(arch_resolve "x86_64" "arm64")" == "arm64" ]] && MONGO_DIST="ubuntu2204"
|
||||
MONGO_VERSION=$(get_latest_gh_tag "mongodb/mongo-tools" "100." || echo "100.16.1")
|
||||
fetch_and_deploy_from_url "https://fastdl.mongodb.org/tools/db/mongodb-database-tools-${MONGO_DIST}-$(arch_resolve "x86_64" "arm64")-${MONGO_VERSION}.deb" ""
|
||||
mkdir -p /usr/local/mongodb-database-tools/bin
|
||||
@@ -65,7 +65,7 @@ $STD go mod tidy
|
||||
$STD go mod download
|
||||
$STD go install github.com/swaggo/swag/cmd/swag@latest
|
||||
$STD /root/go/bin/swag init -g cmd/main.go -o swagger
|
||||
$STD env CGO_ENABLED=0 GOOS=linux GOARCH=$(arch_resolve) go build -o databasus ./cmd/main.go
|
||||
$STD env CGO_ENABLED=0 GOOS=linux GOARCH=$(arch_resolve) go build -o databasus ./cmd
|
||||
mv /opt/databasus/backend/databasus /opt/databasus/databasus
|
||||
mkdir -p /databasus-data/{pgdata,temp,backups,data,logs}
|
||||
mkdir -p /opt/databasus/ui/build
|
||||
|
||||
@@ -26,7 +26,7 @@ cat >~/.config/pip/pip.conf <<EOF
|
||||
[global]
|
||||
break-system-packages = true
|
||||
EOF
|
||||
$STD pip install deluge[all]
|
||||
$STD pip install deluge[all] "pyopenssl<25"
|
||||
msg_ok "Installed Deluge"
|
||||
|
||||
msg_info "Creating Service"
|
||||
|
||||
@@ -83,14 +83,7 @@ msg_info "Setting up Backend"
|
||||
cd /opt/endurain/backend
|
||||
UV_VERSION=$(grep -Po 'required-version\s*=\s*"\K[^"]+' pyproject.toml 2>/dev/null || echo "0.11.18")
|
||||
UV_VERSION="$UV_VERSION" setup_uv
|
||||
$STD uv tool install poetry
|
||||
$STD uv tool update-shell
|
||||
export PATH="/root/.local/bin:$PATH"
|
||||
$STD poetry self add poetry-plugin-export
|
||||
$STD poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
$STD uv venv --clear
|
||||
$STD uv pip install -r requirements.txt
|
||||
$STD uv pip install pytz
|
||||
$STD uv sync --frozen --no-dev
|
||||
msg_ok "Setup Backend"
|
||||
|
||||
msg_info "Creating Service"
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: fabrice1236
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://ghost.org/ | Github: https://github.com/TryGhost/Ghost
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
nginx \
|
||||
ca-certificates \
|
||||
libjemalloc2 \
|
||||
git
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
setup_mariadb
|
||||
MARIADB_DB_NAME="ghost" MARIADB_DB_USER="ghostuser" setup_mariadb_db
|
||||
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
|
||||
|
||||
msg_info "Installing Ghost CLI"
|
||||
$STD npm install ghost-cli@latest -g
|
||||
msg_ok "Installed Ghost CLI"
|
||||
|
||||
msg_info "Creating Service"
|
||||
$STD adduser --disabled-password --gecos "Ghost user" ghost-user
|
||||
$STD usermod -aG sudo ghost-user
|
||||
echo "ghost-user ALL=(ALL) NOPASSWD:ALL" | tee /etc/sudoers.d/ghost-user
|
||||
mkdir -p /var/www/ghost
|
||||
chown -R ghost-user:ghost-user /var/www/ghost
|
||||
chmod 775 /var/www/ghost
|
||||
$STD sudo -u ghost-user -H sh -c "cd /var/www/ghost && ghost install --db=mysql --dbhost=localhost --dbuser=$MARIADB_DB_USER --dbpass=$MARIADB_DB_PASS --dbname=$MARIADB_DB_NAME --url=http://localhost:2368 --no-prompt --no-setup-nginx --no-setup-ssl --no-setup-mysql --enable --start --ip 0.0.0.0"
|
||||
rm /etc/sudoers.d/ghost-user
|
||||
msg_ok "Creating Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -17,7 +17,6 @@ msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
acl \
|
||||
fping \
|
||||
git \
|
||||
graphviz \
|
||||
imagemagick \
|
||||
mtr-tiny \
|
||||
@@ -65,16 +64,6 @@ EOF
|
||||
chown -R librenms:librenms /opt/librenms
|
||||
chmod 771 /opt/librenms
|
||||
chmod -R ug=rwX /opt/librenms/bootstrap/cache /opt/librenms/storage /opt/librenms/logs /opt/librenms/rrd
|
||||
if [[ ! -d /opt/librenms/.git ]]; then
|
||||
LIBRENMS_VERSION=$(cat ~/.librenms 2>/dev/null)
|
||||
cd /opt/librenms
|
||||
git init -q
|
||||
git remote add origin https://github.com/librenms/librenms.git
|
||||
git fetch --depth 1 origin "refs/tags/v${LIBRENMS_VERSION}" 2>/dev/null ||
|
||||
git fetch --depth 1 origin "refs/tags/${LIBRENMS_VERSION}" 2>/dev/null || true
|
||||
git checkout -qf FETCH_HEAD 2>/dev/null || true
|
||||
chown -R librenms:librenms .git
|
||||
fi
|
||||
msg_ok "Configured LibreNMS"
|
||||
|
||||
msg_info "Configure MariaDB"
|
||||
|
||||
@@ -18,7 +18,7 @@ $STD apt install -y \
|
||||
apache2-utils \
|
||||
logrotate \
|
||||
build-essential \
|
||||
libpcre3-dev \
|
||||
libpcre2-dev \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
git \
|
||||
@@ -36,7 +36,8 @@ $STD /opt/certbot/bin/pip install certbot certbot-dns-cloudflare
|
||||
ln -sf /opt/certbot/bin/certbot /usr/local/bin/certbot
|
||||
msg_ok "Set up Certbot"
|
||||
|
||||
fetch_and_deploy_gh_release "openresty" "openresty/openresty" "prebuild" "latest" "/opt/openresty" "openresty-*.tar.gz"
|
||||
OPENRESTY_VERSION="1.29.2.5"
|
||||
fetch_and_deploy_from_url "https://openresty.org/download/openresty-${OPENRESTY_VERSION}.tar.gz" "/opt/openresty"
|
||||
|
||||
msg_info "Building OpenResty"
|
||||
cd /opt/openresty
|
||||
@@ -52,6 +53,7 @@ $STD ./configure \
|
||||
--with-stream_ssl_module
|
||||
$STD make -j"$(nproc)"
|
||||
$STD make install
|
||||
echo "${OPENRESTY_VERSION}" >~/.openresty
|
||||
rm -rf /opt/openresty
|
||||
|
||||
cat <<'EOF' >/lib/systemd/system/openresty.service
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: nnsense
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/kieraneglin/pinchflat
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
build-essential \
|
||||
elixir \
|
||||
erlang-dev \
|
||||
erlang-inets \
|
||||
erlang-os-mon \
|
||||
erlang-runtime-tools \
|
||||
erlang-syntax-tools \
|
||||
erlang-xmerl \
|
||||
git \
|
||||
libsqlite3-dev \
|
||||
locales \
|
||||
openssh-client \
|
||||
openssl \
|
||||
pipx \
|
||||
pkg-config \
|
||||
procps \
|
||||
python3-mutagen \
|
||||
zip
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs
|
||||
FFMPEG_TYPE="binary" setup_ffmpeg
|
||||
setup_hwaccel
|
||||
fetch_and_deploy_gh_release "deno" "denoland/deno" "prebuild" "latest" "/usr/local/bin" "deno-$(arch_resolve "x86_64" "aarch64")-unknown-linux-gnu.zip"
|
||||
fetch_and_deploy_gh_release "yt-dlp" "yt-dlp/yt-dlp" "singlefile" "latest" "/usr/local/bin" "yt-dlp_$(arch_resolve "linux" "linux_aarch64")"
|
||||
|
||||
msg_info "Installing Apprise"
|
||||
export PIPX_HOME=/opt/pipx
|
||||
export PIPX_BIN_DIR=/usr/local/bin
|
||||
$STD pipx install apprise
|
||||
msg_ok "Installed Apprise"
|
||||
|
||||
fetch_and_deploy_gh_release "pinchflat" "kieraneglin/pinchflat" "tarball" "latest" "/opt/pinchflat-src"
|
||||
|
||||
msg_info "Configuring Pinchflat"
|
||||
CONFIG_PATH="/opt/pinchflat/config"
|
||||
DOWNLOADS_PATH="/opt/pinchflat/downloads"
|
||||
mkdir -p \
|
||||
/etc/elixir_tzdata_data \
|
||||
/etc/yt-dlp/plugins \
|
||||
/opt/pinchflat/app \
|
||||
"$CONFIG_PATH/db" \
|
||||
"$CONFIG_PATH/extras" \
|
||||
"$CONFIG_PATH/logs" \
|
||||
"$CONFIG_PATH/metadata" \
|
||||
"$DOWNLOADS_PATH"
|
||||
ln -sfn "$CONFIG_PATH" /config
|
||||
ln -sfn "$DOWNLOADS_PATH" /downloads
|
||||
chmod ugo+rw /etc/elixir_tzdata_data /etc/yt-dlp /etc/yt-dlp/plugins
|
||||
|
||||
cat <<EOF >/opt/pinchflat/.env
|
||||
LANG=en_US.UTF-8
|
||||
LANGUAGE=en_US:en
|
||||
LC_ALL=en_US.UTF-8
|
||||
MIX_ENV=prod
|
||||
PHX_SERVER=true
|
||||
PORT=8945
|
||||
RUN_CONTEXT=selfhosted
|
||||
CONFIG_PATH=${CONFIG_PATH}
|
||||
MEDIA_PATH=${DOWNLOADS_PATH}
|
||||
TZ_DATA_PATH=/etc/elixir_tzdata_data
|
||||
SECRET_KEY_BASE=$(openssl rand -base64 48)
|
||||
EOF
|
||||
msg_ok "Configured Pinchflat"
|
||||
|
||||
msg_info "Building Pinchflat"
|
||||
cd /opt/pinchflat-src
|
||||
export MIX_ENV=prod
|
||||
export ERL_FLAGS="+JPperf true"
|
||||
$STD mix local.hex --force
|
||||
$STD mix local.rebar --force
|
||||
$STD mix deps.get --only prod
|
||||
$STD mix deps.compile
|
||||
$STD yarn --cwd assets install
|
||||
$STD mix assets.deploy
|
||||
$STD mix compile
|
||||
$STD mix release --overwrite
|
||||
rm -rf /opt/pinchflat/app
|
||||
cp -r _build/prod/rel/pinchflat /opt/pinchflat/app
|
||||
msg_ok "Built Pinchflat"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/pinchflat.service
|
||||
[Unit]
|
||||
Description=Pinchflat
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/opt/pinchflat/.env
|
||||
WorkingDirectory=/opt/pinchflat/app
|
||||
UMask=0022
|
||||
ExecStartPre=/opt/pinchflat/app/bin/check_file_permissions
|
||||
ExecStartPre=/opt/pinchflat/app/bin/migrate
|
||||
ExecStart=/opt/pinchflat/app/bin/pinchflat start
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now pinchflat
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://snapotter.com
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
imagemagick \
|
||||
ghostscript \
|
||||
potrace \
|
||||
libopenjp2-tools \
|
||||
libegl1 \
|
||||
libwayland-client0 \
|
||||
libwayland-cursor0 \
|
||||
libwayland-egl1 \
|
||||
libxkbcommon0 \
|
||||
libxkbcommon-x11-0 \
|
||||
libxcursor1 \
|
||||
python3 \
|
||||
python3-dev \
|
||||
gcc \
|
||||
g++
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PYTHON_VERSION="3.11" setup_uv
|
||||
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
|
||||
fetch_and_deploy_gh_release "caire" "esimov/caire" "prebuild" "latest" "/usr/local/bin" "caire-*-linux-amd64.tar.gz"
|
||||
fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "prebuild" "latest" "/opt/snapotter" "snapotter-*-linux-amd64.tar.gz"
|
||||
|
||||
msg_info "Setting up Python Environment"
|
||||
mkdir -p /opt/snapotter_data/ai/models/rembg
|
||||
$STD uv python install 3.11
|
||||
$STD uv venv --seed --python 3.11 /opt/snapotter_data/ai/venv
|
||||
#if [[ -f /opt/snapotter/packages/ai/python/requirements.txt ]]; then
|
||||
# $STD uv pip install \
|
||||
# --python /opt/snapotter_data/ai/venv/bin/python \
|
||||
# -r /opt/snapotter/packages/ai/python/requirements.txt
|
||||
#fi
|
||||
ln -sfn /opt/snapotter /app
|
||||
msg_ok "Set up Python Environment"
|
||||
|
||||
msg_info "Configuring SnapOtter"
|
||||
mkdir -p /opt/snapotter_data/files
|
||||
mkdir -p /tmp/snapotter-workspace
|
||||
|
||||
cat <<EOF >/opt/snapotter_data/.env
|
||||
PORT=1349
|
||||
NODE_ENV=production
|
||||
DB_PATH=/opt/snapotter_data/snapotter.db
|
||||
WORKSPACE_PATH=/tmp/snapotter-workspace
|
||||
FILES_STORAGE_PATH=/opt/snapotter_data/files
|
||||
PYTHON_VENV_PATH=/opt/snapotter_data/ai/venv
|
||||
MODELS_PATH=/opt/snapotter_data/ai/models
|
||||
DATA_DIR=/opt/snapotter_data
|
||||
FEATURE_MANIFEST_PATH=/opt/snapotter/docker/feature-manifest.json
|
||||
U2NET_HOME=/opt/snapotter_data/ai/models/rembg
|
||||
AUTH_ENABLED=true
|
||||
DEFAULT_USERNAME=admin
|
||||
DEFAULT_PASSWORD=admin
|
||||
LOG_LEVEL=info
|
||||
TRUST_PROXY=true
|
||||
FILE_MAX_AGE_HOURS=72
|
||||
CLEANUP_INTERVAL_MINUTES=60
|
||||
ANALYTICS_ENABLED=false
|
||||
EOF
|
||||
msg_ok "Configured SnapOtter"
|
||||
|
||||
msg_info "Creating Service"
|
||||
PNPM_BIN="$(command -v pnpm)"
|
||||
cat <<EOF >/etc/systemd/system/snapotter.service
|
||||
[Unit]
|
||||
Description=SnapOtter Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/snapotter
|
||||
EnvironmentFile=/opt/snapotter_data/.env
|
||||
ExecStart=${PNPM_BIN} --filter @snapotter/api run start
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now snapotter
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -24,7 +24,7 @@ $STD apt install -y \
|
||||
ffmpeg
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="22" NODE_MODULE="corepack,yarn" setup_nodejs
|
||||
NODE_VERSION="24" NODE_MODULE="corepack,yarn" setup_nodejs
|
||||
|
||||
fetch_and_deploy_gh_release "readium" "readium/cli" "prebuild" "latest" "/opt/readium" "readium_linux_$(arch_resolve "x86_64" "arm64").tar.gz"
|
||||
ln -sf /opt/readium/readium /usr/local/bin/readium
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Slaviša Arežina (tremor021)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/wger-project/wger
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
build-essential \
|
||||
nginx \
|
||||
redis-server \
|
||||
libpq-dev
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="22" NODE_MODULE="sass" setup_nodejs
|
||||
setup_uv
|
||||
PG_VERSION="16" setup_postgresql
|
||||
PG_DB_NAME="wger" PG_DB_USER="wger" setup_postgresql_db
|
||||
fetch_and_deploy_gh_release "wger" "wger-project/wger" "tarball"
|
||||
|
||||
msg_info "Setting up wger"
|
||||
mkdir -p /opt/wger/{static,media}
|
||||
chmod o+w /opt/wger/media
|
||||
cd /opt/wger
|
||||
|
||||
$STD npm install
|
||||
$STD npm run build:css:sass
|
||||
$STD uv venv
|
||||
$STD uv pip install . --group docker
|
||||
SECRET_KEY=$(openssl rand -base64 40)
|
||||
cat <<EOF >/opt/wger/.env
|
||||
DJANGO_SETTINGS_MODULE=settings.main
|
||||
PYTHONPATH=/opt/wger
|
||||
|
||||
DJANGO_DB_ENGINE=django.db.backends.postgresql
|
||||
DJANGO_DB_DATABASE=${PG_DB_NAME}
|
||||
DJANGO_DB_USER=${PG_DB_USER}
|
||||
DJANGO_DB_PASSWORD=${PG_DB_PASS}
|
||||
DJANGO_DB_HOST=localhost
|
||||
DJANGO_DB_PORT=5432
|
||||
DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
|
||||
|
||||
DJANGO_MEDIA_ROOT=/opt/wger/media
|
||||
DJANGO_STATIC_ROOT=/opt/wger/static
|
||||
DJANGO_STATIC_URL=/static/
|
||||
|
||||
ALLOWED_HOSTS=${LOCAL_IP},localhost,127.0.0.1
|
||||
CSRF_TRUSTED_ORIGINS=http://${LOCAL_IP}:3000
|
||||
|
||||
USE_X_FORWARDED_HOST=True
|
||||
SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,http
|
||||
|
||||
DJANGO_CACHE_BACKEND=django_redis.cache.RedisCache
|
||||
DJANGO_CACHE_LOCATION=redis://127.0.0.1:6379/1
|
||||
DJANGO_CACHE_TIMEOUT=300
|
||||
DJANGO_CACHE_CLIENT_CLASS=django_redis.client.DefaultClient
|
||||
AXES_CACHE_ALIAS=default
|
||||
|
||||
USE_CELERY=True
|
||||
CELERY_BROKER=redis://127.0.0.1:6379/2
|
||||
CELERY_BACKEND=redis://127.0.0.1:6379/2
|
||||
|
||||
SITE_URL=http://${LOCAL_IP}:3000
|
||||
SECRET_KEY=${SECRET_KEY}
|
||||
EOF
|
||||
set -a && source /opt/wger/.env && set +a
|
||||
$STD uv run wger bootstrap
|
||||
$STD uv run python manage.py collectstatic --no-input
|
||||
cat <<EOF | uv run python manage.py shell
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
|
||||
user, created = User.objects.get_or_create(
|
||||
username="admin",
|
||||
defaults={"email": "admin@localhost"},
|
||||
)
|
||||
|
||||
if created:
|
||||
user.set_password("${PG_DB_PASS}")
|
||||
user.is_superuser = True
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
EOF
|
||||
msg_ok "Set up wger"
|
||||
msg_info "Creating Config and Services"
|
||||
cat <<EOF >/etc/systemd/system/wger.service
|
||||
[Unit]
|
||||
Description=wger Gunicorn
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=root
|
||||
WorkingDirectory=/opt/wger
|
||||
EnvironmentFile=/opt/wger/.env
|
||||
ExecStart=/opt/wger/.venv/bin/gunicorn \
|
||||
--bind 127.0.0.1:8000 \
|
||||
--workers 3 \
|
||||
--threads 2 \
|
||||
--timeout 120 \
|
||||
wger.wsgi:application
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
cat <<EOF >/etc/systemd/system/celery.service
|
||||
[Unit]
|
||||
Description=wger Celery Worker
|
||||
After=network.target redis-server.service
|
||||
Requires=redis-server.service
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/opt/wger
|
||||
EnvironmentFile=/opt/wger/.env
|
||||
ExecStart=/opt/wger/.venv/bin/celery -A wger worker -l info
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
mkdir -p /var/lib/wger/celery
|
||||
chmod 700 /var/lib/wger/celery
|
||||
cat <<EOF >/etc/systemd/system/celery-beat.service
|
||||
[Unit]
|
||||
Description=wger Celery Beat
|
||||
After=network.target redis-server.service
|
||||
Requires=redis-server.service
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/opt/wger
|
||||
EnvironmentFile=/opt/wger/.env
|
||||
ExecStart=/opt/wger/.venv/bin/celery -A wger beat -l info \
|
||||
--schedule /var/lib/wger/celery/celerybeat-schedule
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
cat <<'EOF' >/etc/nginx/sites-available/wger
|
||||
server {
|
||||
listen 3000;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 20M;
|
||||
|
||||
location /static/ {
|
||||
alias /opt/wger/static/;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
location /media/ {
|
||||
alias /opt/wger/media/;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_redirect off;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
$STD rm -f /etc/nginx/sites-enabled/default
|
||||
$STD ln -sf /etc/nginx/sites-available/wger /etc/nginx/sites-enabled/wger
|
||||
systemctl enable -q --now redis-server nginx wger celery celery-beat
|
||||
systemctl restart nginx
|
||||
msg_ok "Created Config and Services"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
+62
-17
@@ -519,6 +519,19 @@ validate_bridge() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# validate_sdn_vnet()
|
||||
#
|
||||
# - Validates that an SDN vnet exists in the cluster config
|
||||
# ------------------------------------------------------------------------------
|
||||
validate_sdn_vnet() {
|
||||
local vnet="$1"
|
||||
[[ -z "$vnet" ]] && return 1
|
||||
[[ -f /etc/pve/sdn/vnets.cfg ]] && grep -qE "^vnet:[[:space:]]*${vnet}([[:space:]]|$)" /etc/pve/sdn/vnets.cfg && return 0
|
||||
command -v pvesh &>/dev/null && pvesh get "/cluster/sdn/vnets/${vnet}" &>/dev/null && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# validate_gateway_in_subnet()
|
||||
#
|
||||
@@ -964,6 +977,7 @@ base_settings() {
|
||||
HN="$requested_hostname"
|
||||
|
||||
BRG=${var_brg:-"vmbr0"}
|
||||
SDN_VNET=${var_sdn_vnet:-""}
|
||||
NET=${var_net:-"dhcp"}
|
||||
|
||||
# Resolve IP range if NET contains a range (e.g., 192.168.1.100/24-192.168.1.200/24)
|
||||
@@ -1078,7 +1092,7 @@ load_vars_file() {
|
||||
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
|
||||
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
|
||||
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
|
||||
var_post_install
|
||||
var_post_install var_sdn_vnet
|
||||
)
|
||||
|
||||
# Whitelist check helper
|
||||
@@ -1253,6 +1267,12 @@ load_vars_file() {
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_sdn_vnet)
|
||||
if [[ -n "$var_val" ]] && ! validate_sdn_vnet "$var_val"; then
|
||||
msg_warn "SDN vnet '$var_val' from $file not found, ignoring"
|
||||
continue
|
||||
fi
|
||||
;;
|
||||
var_http_proxy)
|
||||
if [[ -n "$var_val" ]] && ! [[ "$var_val" =~ ^https?://[^[:space:]]+(:[0-9]+)?/?$ ]]; then
|
||||
msg_warn "Invalid HTTP proxy URL '$var_val' in $file, ignoring"
|
||||
@@ -1308,7 +1328,7 @@ default_var_settings() {
|
||||
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
|
||||
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
|
||||
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
|
||||
var_post_install
|
||||
var_post_install var_sdn_vnet
|
||||
)
|
||||
|
||||
# Snapshot: environment variables (highest precedence)
|
||||
@@ -1491,7 +1511,7 @@ if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then
|
||||
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
|
||||
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
|
||||
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
|
||||
var_post_install
|
||||
var_post_install var_sdn_vnet
|
||||
)
|
||||
fi
|
||||
|
||||
@@ -1705,6 +1725,7 @@ _build_current_app_vars_tmp() {
|
||||
|
||||
[ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")"
|
||||
[ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")"
|
||||
[ -n "${var_sdn_vnet:-}" ] && echo "var_sdn_vnet=$(_sanitize_value "${var_sdn_vnet}")"
|
||||
|
||||
[ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")"
|
||||
[ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")"
|
||||
@@ -1865,6 +1886,7 @@ advanced_settings() {
|
||||
local _core_count="${var_cpu:-1}"
|
||||
local _ram_size="${var_ram:-1024}"
|
||||
local _bridge="${var_brg:-vmbr0}"
|
||||
local _sdn_vnet="${var_sdn_vnet:-}"
|
||||
local _net="${var_net:-dhcp}"
|
||||
local _gate="${var_gateway:-}"
|
||||
local _ipv6_method="${var_ipv6_method:-auto}"
|
||||
@@ -1946,6 +1968,11 @@ advanced_settings() {
|
||||
fi
|
||||
done <<<"$BRIDGES"
|
||||
fi
|
||||
if [[ -f /etc/pve/sdn/vnets.cfg ]]; then
|
||||
while IFS= read -r vnet; do
|
||||
[[ -n "$vnet" ]] && BRIDGE_MENU_OPTIONS+=("sdn:${vnet}" "[SDN] ${vnet}")
|
||||
done < <(awk '/^vnet:/{print $2}' /etc/pve/sdn/vnets.cfg 2>/dev/null)
|
||||
fi
|
||||
}
|
||||
_detect_bridges
|
||||
|
||||
@@ -2178,8 +2205,18 @@ advanced_settings() {
|
||||
if [[ "$bridge_test" == "__other__" || "$bridge_test" == -* ]]; then
|
||||
continue
|
||||
fi
|
||||
if validate_bridge "$bridge_test"; then
|
||||
if [[ "$bridge_test" == sdn:* ]]; then
|
||||
local vnet_test="${bridge_test#sdn:}"
|
||||
if validate_sdn_vnet "$vnet_test"; then
|
||||
_sdn_vnet="$vnet_test"
|
||||
_bridge="${var_brg:-vmbr0}"
|
||||
((STEP++))
|
||||
else
|
||||
whiptail --msgbox "SDN vnet '$vnet_test' is not configured on this cluster." 8 58
|
||||
fi
|
||||
elif validate_bridge "$bridge_test"; then
|
||||
_bridge="$bridge_test"
|
||||
_sdn_vnet=""
|
||||
((STEP++))
|
||||
else
|
||||
whiptail --msgbox "Bridge '$bridge_test' is not available or not active." 8 58
|
||||
@@ -2957,6 +2994,7 @@ Advanced:
|
||||
var_timezone="$_ct_timezone"
|
||||
var_apt_cacher="$_apt_cacher"
|
||||
var_apt_cacher_ip="$_apt_cacher_ip"
|
||||
var_sdn_vnet="$_sdn_vnet"
|
||||
var_http_proxy="$_http_proxy"
|
||||
var_http_no_proxy="$_http_no_proxy"
|
||||
|
||||
@@ -3714,7 +3752,6 @@ run_addon_updates() {
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
runtime_script_status_guard() {
|
||||
local script_slug="${SCRIPT_SLUG:-${NSAPP:-}}"
|
||||
script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
|
||||
@@ -3892,6 +3929,9 @@ build_container() {
|
||||
# if [ "$VERBOSE" == "yes" ]; then set -x; fi
|
||||
|
||||
NET_STRING="-net0 name=eth0,bridge=${BRG:-vmbr0}"
|
||||
if [[ -n "${var_sdn_vnet:-${SDN_VNET:-}}" ]]; then
|
||||
NET_STRING="-net0 name=eth0,vnet=${var_sdn_vnet:-$SDN_VNET}"
|
||||
fi
|
||||
|
||||
# MAC
|
||||
if [[ -n "$MAC" ]]; then
|
||||
@@ -5390,6 +5430,7 @@ fix_gpu_gids() {
|
||||
# Update dev entries with correct GIDs
|
||||
sed -i.bak -E "s|(dev[0-9]+: /dev/dri/renderD[0-9]+),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG"
|
||||
sed -i -E "s|(dev[0-9]+: /dev/dri/card[0-9]+),gid=[0-9]+|\1,gid=${video_gid}|g" "$LXC_CONFIG"
|
||||
sed -i -E "s|(dev[0-9]+: /dev/kfd),gid=[0-9]+|\1,gid=${render_gid}|g" "$LXC_CONFIG"
|
||||
|
||||
# Restart container
|
||||
pct start "$CTID" >/dev/null 2>&1
|
||||
@@ -5411,6 +5452,10 @@ fix_gpu_gids() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
if [ -e /dev/kfd ]; then
|
||||
chgrp ${render_gid} /dev/kfd 2>/dev/null || true
|
||||
chmod 660 /dev/kfd 2>/dev/null || true
|
||||
fi
|
||||
" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
@@ -5596,10 +5641,10 @@ check_storage_health() {
|
||||
|
||||
[[ -z "$total_kb" || "$total_kb" == "0" || -z "$avail_kb" ]] && return 0
|
||||
|
||||
used_pct=$(( (total_kb - avail_kb) * 100 / total_kb ))
|
||||
used_pct=$(((total_kb - avail_kb) * 100 / total_kb))
|
||||
free_gb_fmt=$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f "$avail_kb" 2>/dev/null || echo "${avail_kb}KB")
|
||||
|
||||
if (( used_pct >= 95 )); then
|
||||
if ((used_pct >= 95)); then
|
||||
msg_warn "Storage '${storage}' (${storage_type}) is ${used_pct}% full (${free_gb_fmt} free)"
|
||||
if ! is_unattended && command -v whiptail >/dev/null 2>&1 && [[ -t 0 ]]; then
|
||||
if ! whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||
@@ -5608,11 +5653,11 @@ check_storage_health() {
|
||||
msg_error "Installation cancelled – storage nearly full"
|
||||
exit 214
|
||||
fi
|
||||
elif (( used_pct >= 98 )); then
|
||||
elif ((used_pct >= 98)); then
|
||||
msg_error "Storage '${storage}' is ${used_pct}% full – refusing install in unattended mode"
|
||||
exit 214
|
||||
fi
|
||||
elif (( used_pct >= 85 )); then
|
||||
elif ((used_pct >= 85)); then
|
||||
msg_warn "Storage '${storage}' (${storage_type}) is ${used_pct}% full (${free_gb_fmt} free)"
|
||||
fi
|
||||
|
||||
@@ -5951,17 +5996,17 @@ create_lxc_container() {
|
||||
# Maps OS type + version to the release variant name used by ARM64 template sources.
|
||||
arm64_template_variant() {
|
||||
case "$1:$2" in
|
||||
debian:12) echo "bookworm" ;;
|
||||
debian:13) echo "trixie" ;;
|
||||
debian:) echo "$DEBIAN_DEFAULT_CODENAME" ;;
|
||||
debian:12) echo "bookworm" ;;
|
||||
debian:13) echo "trixie" ;;
|
||||
debian:) echo "$DEBIAN_DEFAULT_CODENAME" ;;
|
||||
|
||||
ubuntu:24.04) echo "noble" ;;
|
||||
ubuntu:26.04) echo "questing" ;;
|
||||
ubuntu:) echo "$UBUNTU_DEFAULT_CODENAME" ;;
|
||||
ubuntu:24.04) echo "noble" ;;
|
||||
ubuntu:26.04) echo "questing" ;;
|
||||
ubuntu:) echo "$UBUNTU_DEFAULT_CODENAME" ;;
|
||||
|
||||
alpine:*) echo "${2:-$ALPINE_DEFAULT_VERSION}" ;;
|
||||
alpine:*) echo "${2:-$ALPINE_DEFAULT_VERSION}" ;;
|
||||
|
||||
*) return 1 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -2607,7 +2607,7 @@ check_for_gh_release() {
|
||||
ensure_dependencies jq
|
||||
|
||||
local gh_check_json
|
||||
gh_check_json=$(mktemp /tmp/tools-gh-check-XXXXXX.json) || return 7
|
||||
gh_check_json=$(mktemp /tmp/tools-gh-check-XXXXXX) || return 73
|
||||
trap 'rm -f "$gh_check_json"' RETURN
|
||||
|
||||
# Build auth header if token is available
|
||||
@@ -3257,7 +3257,7 @@ fetch_and_deploy_codeberg_release() {
|
||||
fi
|
||||
|
||||
local codeberg_rel_json
|
||||
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX.json) || return 7
|
||||
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX) || return 73
|
||||
trap 'rm -f "$codeberg_rel_json"' RETURN
|
||||
|
||||
local attempt=0 success=false resp http_code
|
||||
@@ -3744,7 +3744,7 @@ fetch_and_deploy_gh_release() {
|
||||
rm -f "$TOOLS_GH_REL_JSON"
|
||||
fi
|
||||
local gh_rel_json
|
||||
gh_rel_json=$(mktemp /tmp/tools-gh-rel-XXXXXX.json) || return 7
|
||||
gh_rel_json=$(mktemp /tmp/tools-gh-rel-XXXXXX) || return 73
|
||||
TOOLS_GH_REL_JSON="$gh_rel_json"
|
||||
|
||||
local api_url="https://api.github.com/repos/$repo/releases"
|
||||
@@ -4614,11 +4614,11 @@ EOF
|
||||
local image=$(echo "$container" | awk '{print $2}')
|
||||
local current_digest=$(docker inspect "$name" --format='{{.Image}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12)
|
||||
|
||||
# Pull latest image digest
|
||||
docker pull "$image" >/dev/null 2>&1
|
||||
# Pull latest image digest (ignore failures, e.g. local-only images or registry/permission issues)
|
||||
docker pull "$image" >/dev/null 2>&1 || true
|
||||
local latest_digest=$(docker inspect "$image" --format='{{.Id}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12)
|
||||
|
||||
if [ "$current_digest" != "$latest_digest" ]; then
|
||||
if [ -n "$latest_digest" ] && [ "$current_digest" != "$latest_digest" ]; then
|
||||
containers_with_updates+=("$name")
|
||||
container_info+=("${index}) ${name} (${image})")
|
||||
((index++))
|
||||
@@ -7561,8 +7561,8 @@ setup_nodejs() {
|
||||
}
|
||||
|
||||
# Install global Node modules
|
||||
if [[ -n "$NODE_MODULE" ]] || (( node_major >= 25 )); then
|
||||
if (( node_major >= 25 )) && [[ ",${NODE_MODULE}," != *",corepack,"* ]] && [[ "$NODE_MODULE" != corepack* ]]; then
|
||||
if [[ -n "$NODE_MODULE" ]] || ((node_major >= 25)); then
|
||||
if ((node_major >= 25)) && [[ ",${NODE_MODULE}," != *",corepack,"* ]] && [[ "$NODE_MODULE" != corepack* ]]; then
|
||||
NODE_MODULE="${NODE_MODULE:+$NODE_MODULE,}corepack"
|
||||
fi
|
||||
|
||||
@@ -7624,12 +7624,12 @@ setup_nodejs() {
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if (( failed_modules > 0 )); then
|
||||
if ((failed_modules > 0)); then
|
||||
msg_warn "$failed_modules Node.js module(s) failed: $NODE_MODULE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$NODE_COREPACK_ENABLE" == "1" ]] && (( wants_corepack )) && command -v corepack >/dev/null 2>&1; then
|
||||
if [[ "$NODE_COREPACK_ENABLE" == "1" ]] && ((wants_corepack)) && command -v corepack >/dev/null 2>&1; then
|
||||
msg_info "Enabling corepack"
|
||||
if $STD corepack enable 2>/dev/null; then
|
||||
msg_ok "Enabled corepack"
|
||||
@@ -9172,7 +9172,7 @@ check_for_gl_release() {
|
||||
ensure_dependencies jq
|
||||
|
||||
local gl_check_json
|
||||
gl_check_json=$(mktemp /tmp/tools-gl-check-XXXXXX.json) || return 7
|
||||
gl_check_json=$(mktemp /tmp/tools-gl-check-XXXXXX) || return 73
|
||||
trap 'rm -f "$gl_check_json"' RETURN
|
||||
|
||||
local repo_encoded
|
||||
@@ -9452,7 +9452,7 @@ fetch_and_deploy_gl_release() {
|
||||
ensure_dependencies jq
|
||||
|
||||
local gl_rel_json
|
||||
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX.json) || return 7
|
||||
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX) || return 73
|
||||
trap 'rm -f "$gl_rel_json"' RETURN
|
||||
|
||||
local repo_encoded
|
||||
@@ -9902,7 +9902,7 @@ setup_nltk() {
|
||||
fi
|
||||
|
||||
mkdir -p "${target_dir}/${subdir}"
|
||||
tmp_zip=$(mktemp --suffix=.zip)
|
||||
tmp_zip=$(mktemp)
|
||||
|
||||
if CURL_TIMEOUT=120 curl_with_retry "$pkg_url" "$tmp_zip"; then
|
||||
if [[ "$do_unzip" == "1" ]]; then
|
||||
|
||||
+549
-45
@@ -1,5 +1,5 @@
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/LICENSE
|
||||
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
||||
|
||||
set -euo pipefail
|
||||
SPINNER_PID=""
|
||||
@@ -14,9 +14,18 @@ declare -A MSG_INFO_SHOWN
|
||||
[[ -n "${_CORE_FUNC_LOADED:-}" ]] && return
|
||||
_CORE_FUNC_LOADED=1
|
||||
|
||||
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main}"
|
||||
|
||||
load_api_functions() {
|
||||
if ! declare -f post_to_api_vm >/dev/null 2>&1; then
|
||||
source /dev/stdin <<<$(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/api.func")
|
||||
fi
|
||||
}
|
||||
|
||||
load_functions() {
|
||||
[[ -n "${__FUNCTIONS_LOADED:-}" ]] && return
|
||||
__FUNCTIONS_LOADED=1
|
||||
load_api_functions
|
||||
color
|
||||
formatting
|
||||
icons
|
||||
@@ -31,18 +40,24 @@ load_functions() {
|
||||
arch_check
|
||||
}
|
||||
|
||||
load_cloud_init_functions() {
|
||||
if ! declare -f setup_cloud_init >/dev/null 2>&1; then
|
||||
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/cloud-init.func") 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to download & save header files
|
||||
get_header() {
|
||||
local app_name=$(echo "${APP,,}" | tr ' ' '-')
|
||||
local app_type=${APP_TYPE:-vm}
|
||||
local header_url="https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/${app_type}/headers/${app_name}"
|
||||
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}"
|
||||
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
|
||||
|
||||
mkdir -p "$(dirname "$local_header_path")"
|
||||
|
||||
if [ ! -s "$local_header_path" ]; then
|
||||
if ! curl -fsSL "$header_url" -o "$local_header_path"; then
|
||||
return 250
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -98,6 +113,7 @@ icons() {
|
||||
DNSOK="✔️ "
|
||||
DNSFAIL="${TAB}✖️${TAB}"
|
||||
INFO="${TAB}💡${TAB}${CL}"
|
||||
CLOUD="${TAB}☁️${TAB}${CL}"
|
||||
OS="${TAB}🖥️${TAB}${CL}"
|
||||
OSVERSION="${TAB}🌟${TAB}${CL}"
|
||||
CONTAINERTYPE="${TAB}📦${TAB}${CL}"
|
||||
@@ -188,18 +204,32 @@ silent() {
|
||||
trap 'error_handler' ERR
|
||||
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
# Return instead of exit so that callers can use `$STD cmd || true`
|
||||
# When no || is used, set -e + ERR trap catches it via error_handler()
|
||||
export _SILENT_FAILED_RC="$rc"
|
||||
export _SILENT_FAILED_CMD="$cmd"
|
||||
export _SILENT_FAILED_LINE="$caller_line"
|
||||
export _SILENT_FAILED_LOG="$logfile"
|
||||
# Source explain_exit_code if needed
|
||||
if ! declare -f explain_exit_code >/dev/null 2>&1; then
|
||||
source <(curl -fsSL "$COMMUNITY_SCRIPTS_URL/misc/error_handler.func") 2>/dev/null || true
|
||||
fi
|
||||
|
||||
return "$rc"
|
||||
local explanation=""
|
||||
if declare -f explain_exit_code >/dev/null 2>&1; then
|
||||
explanation="$(explain_exit_code "$rc")"
|
||||
fi
|
||||
|
||||
printf "\e[?25h"
|
||||
if [[ -n "$explanation" ]]; then
|
||||
msg_error "in line ${caller_line}: exit code ${rc} (${explanation})"
|
||||
else
|
||||
msg_error "in line ${caller_line}: exit code ${rc}"
|
||||
fi
|
||||
msg_custom "→" "${YWB}" "${cmd}"
|
||||
|
||||
if [[ -s "$logfile" ]]; then
|
||||
echo -e "\n${TAB}--- Last 20 lines of log ---"
|
||||
tail -n 20 "$logfile"
|
||||
echo -e "${TAB}----------------------------\n"
|
||||
fi
|
||||
|
||||
exit "$rc"
|
||||
fi
|
||||
|
||||
# Clear stale flags on success
|
||||
unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE _SILENT_FAILED_LOG 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -230,7 +260,7 @@ curl_handler() {
|
||||
|
||||
if [[ -z "$url" ]]; then
|
||||
msg_error "no valid url or option entered for curl_handler"
|
||||
exit 64
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$STD msg_info "Fetching: $url"
|
||||
@@ -259,7 +289,7 @@ curl_handler() {
|
||||
rm -f /tmp/curl_error.log
|
||||
fi
|
||||
__curl_err_handler "$exit_code" "$url" "$curl_stderr"
|
||||
exit "$exit_code"
|
||||
exit 1 # hard exit if exit_code is not 0
|
||||
fi
|
||||
|
||||
$STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2
|
||||
@@ -302,7 +332,7 @@ __curl_err_handler() {
|
||||
esac
|
||||
|
||||
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
|
||||
exit "$exit_code"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -317,7 +347,7 @@ shell_check() {
|
||||
msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
|
||||
echo -e "\nExiting..."
|
||||
sleep 2
|
||||
exit 103
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -338,11 +368,11 @@ clear_line() {
|
||||
#
|
||||
# - Determines if script should run in verbose mode
|
||||
# - Checks VERBOSE and var_verbose variables
|
||||
# - Note: Non-TTY (pipe) scenarios are handled separately in msg_info()
|
||||
# - Also returns true if not running in TTY (pipe/redirect scenario)
|
||||
# ------------------------------------------------------------------------------
|
||||
is_verbose_mode() {
|
||||
local verbose="${VERBOSE:-${var_verbose:-no}}"
|
||||
[[ "$verbose" != "no" ]]
|
||||
[[ "$verbose" != "no" || ! -t 2 ]]
|
||||
}
|
||||
|
||||
### dev spinner ###
|
||||
@@ -481,6 +511,20 @@ msg_debug() {
|
||||
fi
|
||||
}
|
||||
|
||||
error_handler() {
|
||||
local exit_code="$?"
|
||||
local line_number="${1:-unknown}"
|
||||
local command="${2:-unknown}"
|
||||
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
post_update_to_api "failed" "$exit_code"
|
||||
fi
|
||||
|
||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
||||
echo -e "\n$error_message\n"
|
||||
cleanup_vmid
|
||||
}
|
||||
|
||||
# Displays error message and immediately terminates script
|
||||
fatal() {
|
||||
msg_error "$1"
|
||||
@@ -516,9 +560,13 @@ cleanup_vmid() {
|
||||
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
stop_spinner
|
||||
if [[ "$(dirs -p | wc -l)" -gt 1 ]]; then
|
||||
popd >/dev/null || true
|
||||
fi
|
||||
if [[ -n "${TEMP_DIR:-}" && -d "$TEMP_DIR" ]]; then
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
# Report final telemetry status if post_to_api_vm was called but no update was sent
|
||||
if [[ "${POST_TO_API_DONE:-}" == "true" && "${POST_UPDATE_DONE:-}" != "true" ]]; then
|
||||
if declare -f post_update_to_api >/dev/null 2>&1; then
|
||||
@@ -538,18 +586,37 @@ check_root() {
|
||||
msg_error "Please run this script as root."
|
||||
echo -e "\nExiting..."
|
||||
sleep 2
|
||||
exit 104
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
pve_check() {
|
||||
if ! pveversion | grep -Eq "pve-manager/(8\.[1-4]|9\.[0-2])(\.[0-9]+)*"; then
|
||||
msg_error "This version of Proxmox Virtual Environment is not supported"
|
||||
echo -e "Requires Proxmox Virtual Environment Version 8.1 - 8.4 or 9.0 - 9.2."
|
||||
echo -e "Exiting..."
|
||||
sleep 2
|
||||
exit 105
|
||||
local pve_ver
|
||||
pve_ver="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
|
||||
|
||||
if [[ "$pve_ver" =~ ^8\.([0-9]+) ]]; then
|
||||
local minor="${BASH_REMATCH[1]}"
|
||||
if ((minor < 0 || minor > 9)); then
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported: Proxmox VE version 8.0 – 8.9"
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$pve_ver" =~ ^9\.([0-9]+) ]]; then
|
||||
local minor="${BASH_REMATCH[1]}"
|
||||
if ((minor < 0 || minor > 2)); then
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported: Proxmox VE version 9.0 – 9.2"
|
||||
exit 105
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_error "This version of Proxmox VE is not supported."
|
||||
msg_error "Supported versions: Proxmox VE 8.0 – 8.9 or 9.0 – 9.2"
|
||||
exit 105
|
||||
}
|
||||
|
||||
arch_check() {
|
||||
@@ -558,50 +625,487 @@ arch_check() {
|
||||
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
|
||||
echo -e "Exiting..."
|
||||
sleep 2
|
||||
exit 106
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
ssh_check() {
|
||||
if command -v pveversion >/dev/null 2>&1 && [ -n "${SSH_CLIENT:-}" ]; then
|
||||
if whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's suggested to use the Proxmox shell instead of SSH, since SSH can create issues while gathering variables. Would you like to proceed with using SSH?" 10 62; then
|
||||
:
|
||||
else
|
||||
clear
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
exit_script() {
|
||||
clear
|
||||
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
||||
exit 0
|
||||
exit
|
||||
}
|
||||
|
||||
sanitize_vm_hostname() {
|
||||
local hostname="${1,,}"
|
||||
hostname=$(echo "$hostname" | tr -cs 'a-z0-9-' '-' | sed 's/^-//;s/-$//')
|
||||
echo "${hostname:0:63}"
|
||||
}
|
||||
|
||||
vm_confirm_new_vm() {
|
||||
local title="$1"
|
||||
local message="$2"
|
||||
local height="${3:-10}"
|
||||
local width="${4:-58}"
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "$title" --yesno "$message" "$height" "$width"
|
||||
}
|
||||
|
||||
vm_choose_settings_mode() {
|
||||
local message="${1:-Use Default Settings?}"
|
||||
local height="${2:-10}"
|
||||
local width="${3:-58}"
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "SETTINGS" --yesno "$message" --no-button Advanced "$height" "$width"
|
||||
}
|
||||
|
||||
vm_confirm_advanced_settings() {
|
||||
local message="$1"
|
||||
local height="${2:-10}"
|
||||
local width="${3:-58}"
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "$message" --no-button Do-Over "$height" "$width"
|
||||
}
|
||||
|
||||
vm_prompt_vmid() {
|
||||
local default_vmid="${1:-$(get_valid_nextid)}"
|
||||
|
||||
while true; do
|
||||
if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 "$default_vmid" --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$VMID" ]; then
|
||||
VMID=$(get_valid_nextid)
|
||||
fi
|
||||
if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then
|
||||
echo -e "${CROSS}${RD} ID $VMID is already in use${CL}"
|
||||
sleep 2
|
||||
continue
|
||||
fi
|
||||
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}"
|
||||
break
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
vm_apply_machine_type() {
|
||||
local machine_type="${1:-i440fx}"
|
||||
|
||||
if [ "$machine_type" = "q35" ]; then
|
||||
MACHINE_TYPE="q35"
|
||||
FORMAT=""
|
||||
MACHINE=" -machine q35"
|
||||
else
|
||||
MACHINE_TYPE="i440fx"
|
||||
FORMAT=",efitype=4m"
|
||||
MACHINE=""
|
||||
fi
|
||||
}
|
||||
|
||||
vm_machine_type_label() {
|
||||
case "${1:-i440fx}" in
|
||||
q35)
|
||||
echo "Q35 (Modern)"
|
||||
;;
|
||||
*)
|
||||
echo "i440fx"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
vm_prompt_machine_type() {
|
||||
local default_machine="${1:-i440fx}"
|
||||
local i440fx_default="ON"
|
||||
local q35_default="OFF"
|
||||
local machine_choice
|
||||
|
||||
if [ "$default_machine" = "q35" ]; then
|
||||
i440fx_default="OFF"
|
||||
q35_default="ON"
|
||||
fi
|
||||
|
||||
if machine_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \
|
||||
"i440fx" "Machine i440fx" "$i440fx_default" \
|
||||
"q35" "Machine q35" "$q35_default" \
|
||||
3>&1 1>&2 2>&3); then
|
||||
vm_apply_machine_type "$machine_choice"
|
||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$(vm_machine_type_label "$MACHINE_TYPE")${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
}
|
||||
|
||||
vm_prompt_cloud_init() {
|
||||
local default_user="${1:-root}"
|
||||
|
||||
USE_CLOUD_INIT="no"
|
||||
load_cloud_init_functions
|
||||
|
||||
if ! declare -f configure_cloud_init_interactive >/dev/null 2>&1; then
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}unavailable${CL}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
configure_cloud_init_interactive "$default_user" || true
|
||||
USE_CLOUD_INIT="${CLOUDINIT_ENABLE:-no}"
|
||||
echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}${USE_CLOUD_INIT}${CL}"
|
||||
|
||||
if [ "$USE_CLOUD_INIT" = "yes" ] && declare -f configure_cloudinit_ssh_keys >/dev/null 2>&1; then
|
||||
configure_cloudinit_ssh_keys || true
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
vm_prompt_disk_size() {
|
||||
local default_size="${1:-8G}"
|
||||
local prompt_message="${2:-Set Disk Size in GiB (e.g., 10, 20)}"
|
||||
|
||||
if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "$prompt_message" 8 58 "$default_size" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ')
|
||||
if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then
|
||||
DISK_SIZE="${DISK_SIZE}G"
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
|
||||
elif [[ "$DISK_SIZE" =~ ^[0-9]+G$ ]]; then
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
|
||||
else
|
||||
echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}"
|
||||
exit_script
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
}
|
||||
|
||||
vm_prompt_disk_cache() {
|
||||
local default_cache="${1:-none}"
|
||||
local none_default="ON"
|
||||
local write_default="OFF"
|
||||
local cache_choice
|
||||
|
||||
if [ "$default_cache" = "writethrough" ]; then
|
||||
none_default="OFF"
|
||||
write_default="ON"
|
||||
fi
|
||||
|
||||
if cache_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
|
||||
"0" "None (Default)" "$none_default" \
|
||||
"1" "Write Through" "$write_default" \
|
||||
3>&1 1>&2 2>&3); then
|
||||
if [ "$cache_choice" = "1" ]; then
|
||||
DISK_CACHE="cache=writethrough,"
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}"
|
||||
else
|
||||
DISK_CACHE=""
|
||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
}
|
||||
|
||||
vm_prompt_hostname() {
|
||||
local default_hostname="${1:-vm}"
|
||||
local adjusted_hostname
|
||||
local input_hostname
|
||||
|
||||
if input_hostname=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$default_hostname" --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$input_hostname" ]; then
|
||||
HN="$default_hostname"
|
||||
else
|
||||
adjusted_hostname=$(sanitize_vm_hostname "$input_hostname")
|
||||
HN="${adjusted_hostname:-$default_hostname}"
|
||||
if [ "$HN" != "${input_hostname,,}" ]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "HOSTNAME ADJUSTED" --msgbox "Invalid characters detected. Hostname has been adjusted to:\n\n $HN" 10 58
|
||||
fi
|
||||
fi
|
||||
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
}
|
||||
|
||||
vm_prompt_cpu_model() {
|
||||
local default_model="${1:-kvm64}"
|
||||
local kvm_default="ON"
|
||||
local host_default="OFF"
|
||||
local cpu_choice
|
||||
|
||||
if [ "$default_model" = "host" ]; then
|
||||
kvm_default="OFF"
|
||||
host_default="ON"
|
||||
fi
|
||||
|
||||
if cpu_choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
|
||||
"0" "KVM64 (Default)" "$kvm_default" \
|
||||
"1" "Host" "$host_default" \
|
||||
3>&1 1>&2 2>&3); then
|
||||
if [ "$cpu_choice" = "1" ]; then
|
||||
CPU_TYPE=" -cpu host"
|
||||
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}"
|
||||
else
|
||||
CPU_TYPE=""
|
||||
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}"
|
||||
fi
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
}
|
||||
|
||||
vm_prompt_cpu_cores() {
|
||||
local default_cores="${1:-2}"
|
||||
|
||||
while true; do
|
||||
if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 "$default_cores" --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$CORE_COUNT" ]; then
|
||||
CORE_COUNT="$default_cores"
|
||||
fi
|
||||
if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
|
||||
break
|
||||
fi
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "CPU Cores must be a positive integer (e.g., 2)." 8 58
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
vm_prompt_ram() {
|
||||
local default_ram="${1:-2048}"
|
||||
|
||||
while true; do
|
||||
if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 "$default_ram" --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$RAM_SIZE" ]; then
|
||||
RAM_SIZE="$default_ram"
|
||||
fi
|
||||
if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then
|
||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}"
|
||||
break
|
||||
fi
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "RAM Size must be a positive integer in MiB (e.g., 2048)." 8 58
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
vm_prompt_bridge() {
|
||||
local default_bridge="${1:-vmbr0}"
|
||||
|
||||
if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 "$default_bridge" --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$BRG" ]; then
|
||||
BRG="$default_bridge"
|
||||
fi
|
||||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
}
|
||||
|
||||
vm_prompt_mac() {
|
||||
local default_mac="${1:-$GEN_MAC}"
|
||||
local input_mac
|
||||
|
||||
while true; do
|
||||
if input_mac=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 "$default_mac" --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$input_mac" ]; then
|
||||
MAC="$default_mac"
|
||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
|
||||
break
|
||||
fi
|
||||
if [[ "$input_mac" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
|
||||
MAC="$input_mac"
|
||||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
|
||||
break
|
||||
fi
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "Invalid MAC address format. Use XX:XX:XX:XX:XX:XX (e.g., AA:BB:CC:DD:EE:FF)." 8 58
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
vm_prompt_vlan() {
|
||||
local default_vlan="${1:-}"
|
||||
local input_vlan
|
||||
|
||||
while true; do
|
||||
if input_vlan=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan (leave blank for default)" 8 58 "$default_vlan" --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$input_vlan" ]; then
|
||||
VLAN=""
|
||||
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}"
|
||||
break
|
||||
fi
|
||||
if [[ "$input_vlan" =~ ^[0-9]+$ ]] && [ "$input_vlan" -ge 1 ] && [ "$input_vlan" -le 4094 ]; then
|
||||
VLAN=",tag=$input_vlan"
|
||||
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$input_vlan${CL}"
|
||||
break
|
||||
fi
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
vm_prompt_mtu() {
|
||||
local default_mtu="${1:-}"
|
||||
local input_mtu
|
||||
|
||||
while true; do
|
||||
if input_mtu=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 "$default_mtu" --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
|
||||
if [ -z "$input_mtu" ]; then
|
||||
MTU=""
|
||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}"
|
||||
break
|
||||
fi
|
||||
if [[ "$input_mtu" =~ ^[0-9]+$ ]] && [ "$input_mtu" -ge 576 ] && [ "$input_mtu" -le 65520 ]; then
|
||||
MTU=",mtu=$input_mtu"
|
||||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$input_mtu${CL}"
|
||||
break
|
||||
fi
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID INPUT" --msgbox "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58
|
||||
else
|
||||
exit_script
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
vm_prompt_start_vm() {
|
||||
local default_start="${1:-yes}"
|
||||
local default_flag=()
|
||||
|
||||
if [ "$default_start" = "no" ]; then
|
||||
default_flag=(--defaultno)
|
||||
fi
|
||||
|
||||
if whiptail --backtitle "Proxmox VE Helper Scripts" "${default_flag[@]}" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58; then
|
||||
START_VM="yes"
|
||||
else
|
||||
START_VM="no"
|
||||
fi
|
||||
|
||||
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}${START_VM}${CL}"
|
||||
}
|
||||
|
||||
vm_apply_storage_layout() {
|
||||
local storage_type="$1"
|
||||
|
||||
case $storage_type in
|
||||
nfs | dir | cifs)
|
||||
DISK_EXT=".qcow2"
|
||||
DISK_REF="$VMID/"
|
||||
DISK_IMPORT_FORMAT="qcow2"
|
||||
THIN=""
|
||||
;;
|
||||
btrfs)
|
||||
DISK_EXT=".raw"
|
||||
DISK_REF="$VMID/"
|
||||
DISK_IMPORT_FORMAT="raw"
|
||||
FORMAT=",efitype=4m"
|
||||
THIN=""
|
||||
;;
|
||||
*)
|
||||
DISK_EXT=""
|
||||
DISK_REF=""
|
||||
DISK_IMPORT_FORMAT="raw"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
vm_select_storage() {
|
||||
local hostname="${1:-${HN:-vm}}"
|
||||
local storage_menu=()
|
||||
local msg_max_length=0
|
||||
local line tag type free item
|
||||
local offset=2
|
||||
local valid_storage
|
||||
|
||||
msg_info "Validating Storage"
|
||||
|
||||
while read -r line; do
|
||||
tag=$(echo "$line" | awk '{print $1}')
|
||||
type=$(echo "$line" | awk '{printf "%-10s", $2}')
|
||||
free=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}')
|
||||
item=" Type: $type Free: $free "
|
||||
if [[ $((${#item} + offset)) -gt $msg_max_length ]]; then
|
||||
msg_max_length=$((${#item} + offset))
|
||||
fi
|
||||
storage_menu+=("$tag" "$item" "OFF")
|
||||
done < <(pvesm status -content images | awk 'NR>1')
|
||||
|
||||
valid_storage=$(pvesm status -content images | awk 'NR>1')
|
||||
if [ -z "$valid_storage" ]; then
|
||||
msg_error "Unable to detect a valid storage location."
|
||||
exit
|
||||
elif [ $((${#storage_menu[@]} / 3)) -eq 1 ]; then
|
||||
STORAGE=${storage_menu[0]}
|
||||
else
|
||||
if [ -n "${SPINNER_PID:-}" ] && ps -p "$SPINNER_PID" >/dev/null 2>&1; then
|
||||
kill "$SPINNER_PID" >/dev/null 2>&1 || true
|
||||
SPINNER_ACTIVE=0
|
||||
printf "\r\e[2K" >&2
|
||||
fi
|
||||
while [ -z "${STORAGE:+x}" ]; do
|
||||
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
|
||||
"Which storage pool would you like to use for ${hostname}?\nTo make a selection, use the Spacebar.\n" \
|
||||
16 $(($msg_max_length + 23)) 6 \
|
||||
"${storage_menu[@]}" 3>&1 1>&2 2>&3)
|
||||
done
|
||||
fi
|
||||
|
||||
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
|
||||
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
|
||||
|
||||
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
|
||||
vm_apply_storage_layout "$STORAGE_TYPE"
|
||||
}
|
||||
|
||||
vm_define_disk_references() {
|
||||
local disk_count="${1:-2}"
|
||||
local i disk_name
|
||||
|
||||
for ((i = 0; i < disk_count; i++)); do
|
||||
disk_name="vm-${VMID}-disk-${i}${DISK_EXT:-}"
|
||||
printf -v "DISK${i}" '%s' "$disk_name"
|
||||
printf -v "DISK${i}_REF" '%s' "${STORAGE}:${DISK_REF:-}${disk_name}"
|
||||
done
|
||||
}
|
||||
|
||||
check_hostname_conflict() {
|
||||
local hostname="$1"
|
||||
if qm list | awk '{print $2}' | grep -qx "$hostname"; then
|
||||
msg_error "Hostname $hostname already in use by another VM."
|
||||
exit 206
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
set_description() {
|
||||
local app_name script_slug script_url donate_url
|
||||
app_name=$(echo "${APP,,}" | tr ' ' '-')
|
||||
script_slug="${SCRIPT_SLUG:-${app_name}}"
|
||||
script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
|
||||
script_url="https://community-scripts.org/scripts/${script_slug}"
|
||||
donate_url="https://community-scripts.org/donate"
|
||||
local description_title="${APP:-${NSAPP} VM}"
|
||||
|
||||
DESCRIPTION=$(
|
||||
cat <<EOF
|
||||
<div align='center'>
|
||||
<a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
|
||||
<a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
|
||||
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
|
||||
</a>
|
||||
|
||||
<h2 style='font-size: 24px; margin: 20px 0;'>${NSAPP} VM</h2>
|
||||
<h2 style='font-size: 24px; margin: 20px 0;'>${description_title}</h2>
|
||||
|
||||
<p style='margin: 16px 0;'>
|
||||
<a href='${donate_url}' target='_blank' rel='noopener noreferrer'>
|
||||
<img src='https://img.shields.io/badge/❤️-Sponsoring%20%26%20Donations-FF5E5B' alt='Sponsoring and donations' />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p style='margin: 12px 0;'>
|
||||
<a href='${script_url}' target='_blank' rel='noopener noreferrer'>
|
||||
<img src='https://img.shields.io/badge/📦-Open%20Script%20Page-00617f' alt='Open script page' />
|
||||
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
|
||||
<img src='https://img.shields.io/badge/☕-Buy us a coffee-blue' alt='spend Coffee' />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
+72
-32
@@ -22,10 +22,10 @@ APP="IP-Tag"
|
||||
hostname=$(hostname)
|
||||
|
||||
# Color variables
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
CL=$(echo "\033[m")
|
||||
YW="\033[33m"
|
||||
GN="\033[1;92m"
|
||||
RD="\033[01;31m"
|
||||
CL="\033[m"
|
||||
BFR="\\r\\033[K"
|
||||
HOLD=" "
|
||||
CM="${GN}✓${CL} "
|
||||
@@ -127,7 +127,7 @@ update_installation() {
|
||||
echo -e "\n${YW}Configuration file already exists.${CL}"
|
||||
echo -e "${YW}Note: No critical changes were made in this version.${CL}"
|
||||
while true; do
|
||||
read -p "Do you want to replace it with defaults? (y/n): " yn
|
||||
read -rp "Do you want to replace it with defaults? (y/n): " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
interactive_config_setup
|
||||
@@ -176,7 +176,7 @@ export FORCE_SINGLE_RUN=true
|
||||
exec "$SCRIPT_FILE"
|
||||
EOF
|
||||
chmod +x /usr/local/bin/iptag-run
|
||||
msg_ok "Created iptag-run executable - You can execute this manually by entering “iptag-run” in the Proxmox host, so the script is executed by hand."
|
||||
msg_ok "Created iptag-run executable - You can execute this manually by entering 'iptag-run' in the Proxmox host, so the script is executed by hand."
|
||||
|
||||
msg_info "Restarting service"
|
||||
systemctl daemon-reload &>/dev/null
|
||||
@@ -208,7 +208,7 @@ install_command_only() {
|
||||
else
|
||||
stop_spinner
|
||||
echo -e "\n${YW}Configuration file already exists.${CL}"
|
||||
read -p "Do you want to reconfigure tag format? (y/n): " reconfigure
|
||||
read -rp "Do you want to reconfigure tag format? (y/n): " reconfigure
|
||||
case $reconfigure in
|
||||
[Yy]*)
|
||||
interactive_config_setup_command
|
||||
@@ -285,7 +285,7 @@ interactive_config_setup_command() {
|
||||
echo -e "${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)"
|
||||
|
||||
while true; do
|
||||
read -p "Enter your choice (1-3) [1]: " tag_choice
|
||||
read -rp "Enter your choice (1-3) [1]: " tag_choice
|
||||
case ${tag_choice:-1} in
|
||||
1)
|
||||
TAG_FORMAT="last_two_octets"
|
||||
@@ -323,7 +323,7 @@ interactive_config_setup() {
|
||||
echo -e "${GN}3)${CL} full - Show full IP address (e.g., 192.168.0.100)"
|
||||
|
||||
while true; do
|
||||
read -p "Enter your choice (1-3) [1]: " tag_choice
|
||||
read -rp "Enter your choice (1-3) [1]: " tag_choice
|
||||
case ${tag_choice:-1} in
|
||||
1)
|
||||
TAG_FORMAT="last_two_octets"
|
||||
@@ -352,7 +352,7 @@ interactive_config_setup() {
|
||||
echo -e "${YW}Recommended range: 300-3600 seconds${CL}"
|
||||
|
||||
while true; do
|
||||
read -p "Enter interval in seconds [300]: " interval_input
|
||||
read -rp "Enter interval in seconds [300]: " interval_input
|
||||
interval_input=${interval_input:-300}
|
||||
|
||||
if [[ $interval_input =~ ^[0-9]+$ ]] && [ $interval_input -ge 300 ] && [ $interval_input -le 7200 ]; then
|
||||
@@ -563,9 +563,10 @@ get_vm_ips() {
|
||||
|
||||
debug_log "vm $vmid: starting IP detection"
|
||||
|
||||
# Check if VM is running first
|
||||
local vm_status=""
|
||||
if command -v qm >/dev/null 2>&1; then
|
||||
# Check if VM is running first (status comes from the cached `qm list`,
|
||||
# falling back to `qm status` only when called outside the normal cycle).
|
||||
local vm_status="${STATUS_CACHE[vm_${vmid}]:-}"
|
||||
if [[ -z "$vm_status" ]] && command -v qm >/dev/null 2>&1; then
|
||||
vm_status=$(qm status "$vmid" 2>/dev/null | awk '{print $2}')
|
||||
fi
|
||||
|
||||
@@ -578,33 +579,43 @@ get_vm_ips() {
|
||||
local mac_addresses=$(grep -E "^net[0-9]+:" "$vm_config" | grep -oE "([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}" | head -3)
|
||||
debug_log "vm $vmid: found MACs: $mac_addresses"
|
||||
|
||||
# Method 1: QM guest agent (most reliable for current IP)
|
||||
if command -v qm >/dev/null 2>&1; then
|
||||
debug_log "vm $vmid: trying qm guest agent first"
|
||||
local qm_ips=$(timeout 8 qm guest cmd "$vmid" network-get-interfaces 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v "127.0.0.1" | head -3)
|
||||
# Method 1: QEMU guest agent (most reliable for current IP). Only query it
|
||||
# when the agent is actually enabled in the VM config, otherwise the call
|
||||
# blocks until the timeout on every VM without an agent.
|
||||
local agent_enabled=0
|
||||
if [[ "$(grep -E '^agent:' "$vm_config" 2>/dev/null)" =~ (^agent:[[:space:]]*1|enabled=1) ]]; then
|
||||
agent_enabled=1
|
||||
fi
|
||||
if [[ "$agent_enabled" == "1" ]] && command -v qm >/dev/null 2>&1; then
|
||||
debug_log "vm $vmid: querying guest agent"
|
||||
local qm_ips=$(timeout 5 qm guest cmd "$vmid" network-get-interfaces 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | grep -v "127.0.0.1" | head -3)
|
||||
for qm_ip in $qm_ips; do
|
||||
if [[ "$qm_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
debug_log "vm $vmid: found IP $qm_ip via qm guest cmd"
|
||||
ips+="$qm_ip "
|
||||
fi
|
||||
done
|
||||
else
|
||||
debug_log "vm $vmid: guest agent not enabled, skipping qm guest cmd"
|
||||
fi
|
||||
|
||||
# Method 2: Fresh ARP table lookup (force refresh)
|
||||
if [[ -n "$mac_addresses" ]]; then
|
||||
debug_log "vm $vmid: refreshing ARP table and checking"
|
||||
# Try to refresh ARP table by pinging network ranges
|
||||
# Method 2: ARP table lookup (only if the guest agent gave us nothing).
|
||||
if [[ -z "$ips" && -n "$mac_addresses" ]]; then
|
||||
debug_log "vm $vmid: checking ARP table"
|
||||
# Snapshot the neighbor table once instead of per MAC
|
||||
local neigh_table
|
||||
neigh_table=$(ip neighbor show 2>/dev/null)
|
||||
for mac in $mac_addresses; do
|
||||
local mac_lower=$(echo "$mac" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
# First check current ARP table
|
||||
local current_ip=$(ip neighbor show | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
|
||||
# Check current ARP table
|
||||
local current_ip=$(echo "$neigh_table" | grep "$mac_lower" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
|
||||
|
||||
# If found in ARP, verify it's still valid by trying to ping
|
||||
if [[ -n "$current_ip" && "$current_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
debug_log "vm $vmid: found IP $current_ip in ARP table for MAC $mac_lower, verifying..."
|
||||
# Quick ping test to verify IP is still active
|
||||
if timeout 2 ping -c 1 "$current_ip" >/dev/null 2>&1; then
|
||||
if timeout 1 ping -c 1 -W 1 "$current_ip" >/dev/null 2>&1; then
|
||||
debug_log "vm $vmid: verified IP $current_ip is active via ping"
|
||||
ips+="$current_ip "
|
||||
else
|
||||
@@ -628,7 +639,7 @@ get_vm_ips() {
|
||||
if [[ -n "$dhcp_ip" && "$dhcp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
debug_log "vm $vmid: found IP $dhcp_ip via DHCP leases"
|
||||
# Verify this IP responds
|
||||
if timeout 2 ping -c 1 "$dhcp_ip" >/dev/null 2>&1; then
|
||||
if timeout 1 ping -c 1 -W 1 "$dhcp_ip" >/dev/null 2>&1; then
|
||||
debug_log "vm $vmid: verified DHCP IP $dhcp_ip is active"
|
||||
ips+="$dhcp_ip "
|
||||
break 2
|
||||
@@ -652,6 +663,9 @@ get_vm_ips() {
|
||||
# Cache for configs to avoid repeated reads
|
||||
declare -A CONFIG_CACHE
|
||||
declare -A IP_CACHE
|
||||
# Status cache populated once per check from `pct list` / `qm list` to avoid
|
||||
# spawning an expensive `pct status` / `qm status` (Perl) per guest each cycle.
|
||||
declare -A STATUS_CACHE
|
||||
|
||||
# Update tags for container or VM
|
||||
update_tags() {
|
||||
@@ -836,7 +850,16 @@ update_all_tags() {
|
||||
|
||||
# Get list of all containers/VMs
|
||||
if [[ "$type" == "lxc" ]]; then
|
||||
vmids=($(pct list 2>/dev/null | grep -v VMID | awk '{print $1}'))
|
||||
# A single `pct list` call yields both the VMID list and the running
|
||||
# status, so we never need a per-container `pct status` afterwards.
|
||||
local pct_list_output
|
||||
pct_list_output=$(pct list 2>/dev/null)
|
||||
vmids=($(echo "$pct_list_output" | awk 'NR>1 {print $1}'))
|
||||
local _vmid _status _rest
|
||||
while read -r _vmid _status _rest; do
|
||||
[[ "$_vmid" == "VMID" || -z "$_vmid" ]] && continue
|
||||
STATUS_CACHE["lxc_${_vmid}"]="$_status"
|
||||
done <<<"$pct_list_output"
|
||||
else
|
||||
# More efficient: direct file listing instead of ls+sed
|
||||
vmids=()
|
||||
@@ -845,6 +868,15 @@ update_all_tags() {
|
||||
local basename="${conf##*/}"
|
||||
vmids+=("${basename%.conf}")
|
||||
done
|
||||
# A single `qm list` call yields the status for all VMs, avoiding a
|
||||
# per-VM `qm status`.
|
||||
if command -v qm >/dev/null 2>&1; then
|
||||
local _vmid _name _status _rest
|
||||
while read -r _vmid _name _status _rest; do
|
||||
[[ "$_vmid" == "VMID" || -z "$_vmid" ]] && continue
|
||||
STATUS_CACHE["vm_${_vmid}"]="$_status"
|
||||
done <<<"$(qm list 2>/dev/null)"
|
||||
fi
|
||||
fi
|
||||
|
||||
count=${#vmids[@]}
|
||||
@@ -881,6 +913,7 @@ check() {
|
||||
# Clear caches before each run
|
||||
CONFIG_CACHE=()
|
||||
IP_CACHE=()
|
||||
STATUS_CACHE=()
|
||||
|
||||
# Update LXC containers
|
||||
update_all_tags "lxc"
|
||||
@@ -925,8 +958,12 @@ get_lxc_ips() {
|
||||
|
||||
debug_log "lxc $vmid: starting IP detection"
|
||||
|
||||
# Check if LXC is running
|
||||
local lxc_status=$(pct status "${vmid}" 2>/dev/null | awk '{print $2}')
|
||||
# Check if LXC is running (status comes from the cached `pct list`,
|
||||
# falling back to `pct status` only when called outside the normal cycle).
|
||||
local lxc_status="${STATUS_CACHE[lxc_${vmid}]:-}"
|
||||
if [[ -z "$lxc_status" ]]; then
|
||||
lxc_status=$(pct status "${vmid}" 2>/dev/null | awk '{print $2}')
|
||||
fi
|
||||
if [[ "$lxc_status" != "running" ]]; then
|
||||
debug_log "lxc $vmid: not running (status: $lxc_status)"
|
||||
return
|
||||
@@ -952,9 +989,12 @@ get_lxc_ips() {
|
||||
if [[ -z "$ips" && -f "$pve_lxc_config" ]]; then
|
||||
local mac_addrs=$(grep -Eo 'hwaddr=([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}' "$pve_lxc_config" | cut -d'=' -f2)
|
||||
if [[ -n "$mac_addrs" ]]; then
|
||||
# Snapshot the neighbor table once instead of per MAC
|
||||
local neigh_table
|
||||
neigh_table=$(ip neighbor show 2>/dev/null)
|
||||
while IFS= read -r mac_addr; do
|
||||
[[ -z "$mac_addr" ]] && continue
|
||||
local arp_ip=$(ip neighbor show | grep -i "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
|
||||
local arp_ip=$(echo "$neigh_table" | grep -i "$mac_addr" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -1)
|
||||
if [[ -n "$arp_ip" && "$arp_ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||||
debug_log "lxc $vmid: found IP $arp_ip via ARP table for MAC $mac_addr"
|
||||
ips="${ips}${ips:+ }${arp_ip}"
|
||||
@@ -996,7 +1036,7 @@ echo -e "${GN}3)${CL} Update existing installation"
|
||||
echo -e "${RD}4)${CL} Cancel"
|
||||
|
||||
while true; do
|
||||
read -p "Enter your choice (1-4): " choice
|
||||
read -rp "Enter your choice (1-4): " choice
|
||||
case $choice in
|
||||
1)
|
||||
INSTALL_MODE="service"
|
||||
@@ -1025,7 +1065,7 @@ done
|
||||
|
||||
echo -e "\n${YW}This will install ${APP} on ${hostname} in $INSTALL_MODE mode.${CL}"
|
||||
while true; do
|
||||
read -p "Proceed? (y/n): " yn
|
||||
read -rp "Proceed? (y/n): " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
break
|
||||
@@ -1072,7 +1112,7 @@ if [[ "$INSTALL_MODE" == "service" ]]; then
|
||||
else
|
||||
stop_spinner
|
||||
echo -e "\n${YW}Configuration file already exists.${CL}"
|
||||
read -p "Do you want to reconfigure tag format and loop interval? (y/n): " reconfigure
|
||||
read -rp "Do you want to reconfigure tag format and loop interval? (y/n): " reconfigure
|
||||
case $reconfigure in
|
||||
[Yy]*)
|
||||
interactive_config_setup
|
||||
|
||||
+15
-8
@@ -30,7 +30,7 @@ declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "clean-lxcs" "
|
||||
header_info
|
||||
echo "Loading..."
|
||||
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE LXC Updater" --yesno "This will clean logs, cache and update package lists on selected LXC Containers. Proceed?" 10 58
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "Proxmox VE LXC Updater" --yesno "This will clean logs, cache and update package lists on selected LXC Containers. Proceed?" 10 58 || exit 0
|
||||
|
||||
NODE=$(hostname)
|
||||
EXCLUDE_MENU=()
|
||||
@@ -42,17 +42,17 @@ while read -r TAG ITEM; do
|
||||
EXCLUDE_MENU+=("$TAG" "$ITEM " "OFF")
|
||||
done < <(pct list | awk 'NR>1')
|
||||
|
||||
excluded_containers=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --checklist "\nSelect containers to skip from cleaning:\n" \
|
||||
16 $((MSG_MAX_LENGTH + 23)) 6 "${EXCLUDE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"')
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exit
|
||||
# Capture the selection; abort cleanly if the user cancels the dialog
|
||||
# (set -e would otherwise terminate on the failing command substitution).
|
||||
if ! excluded_containers=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Containers on $NODE" --checklist "\nSelect containers to skip from cleaning:\n" \
|
||||
16 $((MSG_MAX_LENGTH + 23)) 6 "${EXCLUDE_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"'); then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
function run_lxc_clean() {
|
||||
local container=$1
|
||||
header_info
|
||||
name=$(pct exec "$container" hostname)
|
||||
name=$(pct exec "$container" -- hostname)
|
||||
|
||||
pct exec "$container" -- bash -c '
|
||||
BL="\033[36m"; GN="\033[1;92m"; CL="\033[m"
|
||||
@@ -84,7 +84,14 @@ function run_lxc_clean() {
|
||||
}
|
||||
|
||||
for container in $(pct list | awk '{if(NR>1) print $1}'); do
|
||||
if [[ " ${excluded_containers[@]} " =~ " $container " ]]; then
|
||||
excluded=0
|
||||
for ex in $excluded_containers; do
|
||||
if [[ "$ex" == "$container" ]]; then
|
||||
excluded=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$excluded" -eq 1 ]]; then
|
||||
header_info
|
||||
echo -e "${BL}[Info]${GN} Skipping ${BL}$container${CL}"
|
||||
sleep 1
|
||||
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT
|
||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
load_functions
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "disk-health" "pve"
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
____ _ __ __ __ ____ __
|
||||
/ __ \(_)____/ /__ / / / /__ ____ _/ / /_/ /_
|
||||
/ / / / / ___/ //_/ / /_/ / _ \/ __ `/ / __/ __ \
|
||||
/ /_/ / (__ ) ,< / __ / __/ /_/ / / /_/ / / /
|
||||
/_____/_/____/_/|_| /_/ /_/\___/\__,_/_/\__/_/ /_/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
header_info
|
||||
|
||||
# Must run as root (SMART access requires it)
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
msg_error "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pveversion >/dev/null 2>&1; then
|
||||
msg_error "No Proxmox VE detected!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install required tooling on demand
|
||||
if ! command -v smartctl >/dev/null 2>&1; then
|
||||
msg_info "Installing smartmontools"
|
||||
apt-get update &>/dev/null
|
||||
if apt-get install -y smartmontools &>/dev/null; then
|
||||
msg_ok "Installed smartmontools"
|
||||
else
|
||||
msg_error "Failed to install smartmontools"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if ! command -v nvme >/dev/null 2>&1; then
|
||||
msg_info "Installing nvme-cli"
|
||||
if apt-get install -y nvme-cli &>/dev/null; then
|
||||
msg_ok "Installed nvme-cli"
|
||||
else
|
||||
msg_error "nvme-cli not available (NVMe details limited)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect physical disks (exclude loop, zram and device-mapper devices)
|
||||
mapfile -t DISKS < <(lsblk -dn -o NAME,TYPE | awk '$2=="disk"{print $1}' | grep -vE '^(loop|zram|dm-)' | sort)
|
||||
|
||||
if [ "${#DISKS[@]}" -eq 0 ]; then
|
||||
msg_error "No physical disks found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Pull a single attribute value out of "smartctl -A" output by attribute name
|
||||
sata_attr() {
|
||||
local output="$1" name="$2"
|
||||
echo "$output" | awk -v n="$name" '$2==n {print $10; exit}'
|
||||
}
|
||||
|
||||
report_disk() {
|
||||
local dev="$1"
|
||||
local path="/dev/${dev}"
|
||||
local model size health
|
||||
model=$(lsblk -dn -o MODEL "$path" 2>/dev/null | sed 's/[[:space:]]*$//')
|
||||
size=$(lsblk -dn -o SIZE "$path" 2>/dev/null | tr -d ' ')
|
||||
|
||||
echo -e "\n${BL}======================================================${CL}"
|
||||
echo -e "${GN}${path}${CL} ${YW}${size:-?}${CL} ${model:-Unknown model}"
|
||||
echo -e "${BL}======================================================${CL}"
|
||||
|
||||
# Overall SMART health verdict
|
||||
health=$(smartctl -H "$path" 2>/dev/null | grep -iE "SMART overall-health|SMART Health Status" | sed 's/.*: *//')
|
||||
if [ -z "$health" ]; then
|
||||
echo -e " Health: ${YW}SMART not available for this device${CL}"
|
||||
elif echo "$health" | grep -qiE "PASSED|OK"; then
|
||||
echo -e " Health: ${GN}${health}${CL}"
|
||||
else
|
||||
echo -e " Health: ${RD}${health}${CL}"
|
||||
fi
|
||||
|
||||
if [[ "$dev" == nvme* ]]; then
|
||||
local a
|
||||
a=$(smartctl -A "$path" 2>/dev/null)
|
||||
echo "$a" | grep -iE "Temperature:|Available Spare:|Percentage Used:|Data Units Written:|Power On Hours:|Unsafe Shutdowns:|Media and Data Integrity Errors:" |
|
||||
sed 's/^/ /'
|
||||
else
|
||||
local a poh temp realloc pending offline crc wear
|
||||
a=$(smartctl -A "$path" 2>/dev/null)
|
||||
poh=$(sata_attr "$a" "Power_On_Hours")
|
||||
temp=$(sata_attr "$a" "Temperature_Celsius")
|
||||
realloc=$(sata_attr "$a" "Reallocated_Sector_Ct")
|
||||
pending=$(sata_attr "$a" "Current_Pending_Sector")
|
||||
offline=$(sata_attr "$a" "Offline_Uncorrectable")
|
||||
crc=$(sata_attr "$a" "UDMA_CRC_Error_Count")
|
||||
wear=$(sata_attr "$a" "Wear_Leveling_Count")
|
||||
[ -z "$wear" ] && wear=$(sata_attr "$a" "Media_Wearout_Indicator")
|
||||
|
||||
[ -n "$temp" ] && echo -e " Temperature: ${temp} C"
|
||||
[ -n "$poh" ] && echo -e " Power On Hours: ${poh}"
|
||||
[ -n "$wear" ] && echo -e " Wear Leveling/Wearout: ${wear}"
|
||||
print_attr() {
|
||||
local label="$1" val="$2"
|
||||
[ -z "$val" ] && return
|
||||
if [ "$val" -gt 0 ] 2>/dev/null; then
|
||||
echo -e " ${label} ${RD}${val}${CL}"
|
||||
else
|
||||
echo -e " ${label} ${GN}${val}${CL}"
|
||||
fi
|
||||
}
|
||||
print_attr "Reallocated Sectors: " "$realloc"
|
||||
print_attr "Pending Sectors: " "$pending"
|
||||
print_attr "Offline Uncorrectable:" "$offline"
|
||||
print_attr "UDMA CRC Errors: " "$crc"
|
||||
fi
|
||||
}
|
||||
|
||||
header_info
|
||||
echo -e "${YW}Scanning ${#DISKS[@]} disk(s) for SMART health...${CL}"
|
||||
for d in "${DISKS[@]}"; do
|
||||
report_disk "$d"
|
||||
done
|
||||
echo
|
||||
|
||||
# Offer an optional, non-destructive short self-test
|
||||
if whiptail --backtitle "Proxmox VE Helper Scripts" --title "SMART Self-Test" \
|
||||
--yesno "Health report complete.\n\nDo you want to start a non-destructive SHORT SMART self-test on a disk?\n\n(The test runs in the background; check results later with: smartctl -a /dev/XXX)" 14 70; then
|
||||
TEST_MENU=()
|
||||
for d in "${DISKS[@]}"; do
|
||||
TEST_MENU+=("$d" "/dev/$d" "OFF")
|
||||
done
|
||||
sel=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Select Disk for Short Self-Test" \
|
||||
--radiolist "\nSelect a disk:\n" 16 60 6 "${TEST_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"')
|
||||
if [ -n "$sel" ]; then
|
||||
msg_info "Starting short self-test on /dev/$sel"
|
||||
if smartctl -t short "/dev/$sel" &>/dev/null; then
|
||||
msg_ok "Short self-test started on /dev/$sel"
|
||||
echo -e "${YW}Check progress/result with: ${GN}smartctl -a /dev/$sel${CL}"
|
||||
else
|
||||
msg_error "Could not start self-test on /dev/$sel"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "\n${GN}Disk health check complete.${CL}\n"
|
||||
@@ -0,0 +1,917 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT
|
||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
load_functions
|
||||
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "host-migrate" "pve"
|
||||
|
||||
BACKTITLE="Proxmox VE Helper Scripts - Host Migrate"
|
||||
BUNDLE_PREFIX="pve-migrate"
|
||||
NFS_MOUNTPOINT=""
|
||||
NFS_MOUNTED=0
|
||||
# Mountpoints this script created on demand and should clean up on exit
|
||||
# (only those the user did NOT choose to make persistent via fstab).
|
||||
TEMP_MOUNTS=()
|
||||
# Result holders for the storage picker / disk preparation helpers.
|
||||
BROWSE_RESULT=""
|
||||
PREPARED_MP=""
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
__ __ __ __ ___ _ __
|
||||
/ / / /___ _____/ /_ / |/ /(_)____ _____ ____ / /_ ___
|
||||
/ /_/ / __ \/ ___/ __/ / /|_/ // // __ `/ ___/ __ `/ __// _ \
|
||||
/ __ / /_/ (__ ) /_ / / / // // /_/ / / / /_/ / /_ / __/
|
||||
/_/ /_/\____/____/\__/ /_/ /_//_/ \__, /_/ \__,_/\__/ \___/
|
||||
/____/ EXPORT / IMPORT
|
||||
EOF
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Pre-flight checks
|
||||
# ----------------------------------------------------------------------------
|
||||
header_info
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
msg_error "This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v pveversion >/dev/null 2>&1; then
|
||||
msg_error "No Proxmox VE detected!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# Cleanup handler: unmount things we mounted on demand (NFS + non-persistent
|
||||
# disk mounts). Disk DATA is never touched here, we only unmount.
|
||||
function cleanup {
|
||||
if [ "$NFS_MOUNTED" -eq 1 ] && mountpoint -q "$NFS_MOUNTPOINT"; then
|
||||
umount "$NFS_MOUNTPOINT" 2>/dev/null && rmdir "$NFS_MOUNTPOINT" 2>/dev/null
|
||||
fi
|
||||
local mp
|
||||
for mp in "${TEMP_MOUNTS[@]}"; do
|
||||
if mountpoint -q "$mp"; then
|
||||
umount "$mp" 2>/dev/null && rmdir "$mp" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Convert a size string like "37.9G" / "931.51g" to a human label as-is.
|
||||
# Echo a fresh, unique mountpoint path under /mnt for a given label.
|
||||
function _new_mountpoint {
|
||||
local base="/mnt/${1}"
|
||||
local mp="$base"
|
||||
local n=1
|
||||
while [ -e "$mp" ] && ! { [ -d "$mp" ] && [ -z "$(ls -A "$mp" 2>/dev/null)" ]; }; do
|
||||
mp="${base}-${n}"
|
||||
n=$((n + 1))
|
||||
done
|
||||
echo "$mp"
|
||||
}
|
||||
|
||||
# Offer to persist a mount in /etc/fstab. $1=device(or UUID source) $2=mountpoint $3=fstype
|
||||
function _offer_fstab {
|
||||
local dev="$1" mp="$2" fstype="$3"
|
||||
local uuid
|
||||
uuid=$(blkid -s UUID -o value "$dev" 2>/dev/null)
|
||||
if whiptail --backtitle "$BACKTITLE" --yesno \
|
||||
"Make this mount permanent (survives reboot) by adding it to /etc/fstab?\n\n${dev} -> ${mp}" 11 72; then
|
||||
if [ -n "$uuid" ]; then
|
||||
echo "UUID=${uuid} ${mp} ${fstype} defaults 0 2" >>/etc/fstab
|
||||
else
|
||||
echo "${dev} ${mp} ${fstype} defaults 0 2" >>/etc/fstab
|
||||
fi
|
||||
msg_ok "Added to /etc/fstab"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Mount an existing filesystem on a device. Sets PREPARED_MP on success.
|
||||
function mount_existing_fs {
|
||||
local dev="$1" fstype="$2"
|
||||
local mp
|
||||
PREPARED_MP=""
|
||||
mp=$(_new_mountpoint "$(basename "$dev")")
|
||||
mkdir -p "$mp"
|
||||
msg_info "Mounting ${dev}"
|
||||
if mount "$dev" "$mp" 2>/tmp/host-migrate-mount.log; then
|
||||
msg_ok "Mounted ${dev} at ${mp}"
|
||||
if _offer_fstab "$dev" "$mp" "${fstype:-auto}"; then :; else TEMP_MOUNTS+=("$mp"); fi
|
||||
PREPARED_MP="$mp"
|
||||
return 0
|
||||
fi
|
||||
msg_error "Could not mount ${dev} (see /tmp/host-migrate-mount.log)"
|
||||
rmdir "$mp" 2>/dev/null
|
||||
return 1
|
||||
}
|
||||
|
||||
# Format a raw/empty device with ext4 and mount it. DESTRUCTIVE.
|
||||
function format_and_mount {
|
||||
local dev="$1"
|
||||
local confirm
|
||||
confirm=$(whiptail --backtitle "$BACKTITLE" --title "!! DESTRUCTIVE - FORMAT !!" --inputbox \
|
||||
"\nThis will ERASE ALL DATA on:\n ${dev}\n\nand create a fresh ext4 filesystem.\n\nType exactly FORMAT to proceed:" 14 72 \
|
||||
3>&1 1>&2 2>&3) || return 1
|
||||
[ "$confirm" != "FORMAT" ] && {
|
||||
msg_warn "Confirmation mismatch - nothing changed"
|
||||
sleep 2
|
||||
return 1
|
||||
}
|
||||
msg_info "Creating ext4 on ${dev}"
|
||||
if ! mkfs.ext4 -F "$dev" &>/tmp/host-migrate-mkfs.log; then
|
||||
msg_error "mkfs.ext4 failed (see /tmp/host-migrate-mkfs.log)"
|
||||
return 1
|
||||
fi
|
||||
msg_ok "Formatted ${dev}"
|
||||
mount_existing_fs "$dev" "ext4"
|
||||
}
|
||||
|
||||
# Create a logical volume in a VG with free space, format ext4 and mount it.
|
||||
function create_lv_and_mount {
|
||||
local vg="$1" vgfree="$2"
|
||||
local lvname size
|
||||
lvname=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nName for the new logical volume in VG '${vg}':" 10 64 \
|
||||
"backup" --title "New LV name" 3>&1 1>&2 2>&3) || return 1
|
||||
lvname="${lvname:-backup}"
|
||||
size=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nSize for /dev/${vg}/${lvname}\n(${vgfree} free).\nUse e.g. 500G, or leave empty for ALL free space:" 12 68 \
|
||||
--title "LV size" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
msg_info "Creating LV ${lvname} in ${vg}"
|
||||
if [ -z "$size" ]; then
|
||||
lvcreate -l 100%FREE -n "$lvname" "$vg" &>/tmp/host-migrate-lv.log || {
|
||||
msg_error "lvcreate failed (see /tmp/host-migrate-lv.log)"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
lvcreate -L "$size" -n "$lvname" "$vg" &>/tmp/host-migrate-lv.log || {
|
||||
msg_error "lvcreate failed (see /tmp/host-migrate-lv.log)"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
msg_ok "Created /dev/${vg}/${lvname}"
|
||||
local dev="/dev/${vg}/${lvname}"
|
||||
msg_info "Creating ext4 on ${dev}"
|
||||
mkfs.ext4 -F "$dev" &>/tmp/host-migrate-mkfs.log || {
|
||||
msg_error "mkfs.ext4 failed (see /tmp/host-migrate-mkfs.log)"
|
||||
return 1
|
||||
}
|
||||
msg_ok "Formatted ${dev}"
|
||||
mount_existing_fs "$dev" "ext4"
|
||||
}
|
||||
|
||||
# Show currently mounted filesystems (real storage only) and let the user pick
|
||||
# one. Echoes the chosen mountpoint on stdout. Returns non-zero on cancel.
|
||||
function browse_mounts {
|
||||
local menu=() target fstype size avail source
|
||||
local -A seen=()
|
||||
# Pseudo / non-storage filesystems we never want as a backup target.
|
||||
local exclude='tmpfs|devtmpfs|squashfs|overlay|fuse|fuse.lxcfs|cgroup|cgroup2|mqueue|pstore|bpf|debugfs|tracefs|configfs|sysfs|proc|autofs|ramfs|efivarfs|fusectl|securityfs|binfmt_misc|hugetlbfs|rpc_pipefs|devpts'
|
||||
|
||||
if command -v findmnt >/dev/null 2>&1; then
|
||||
while IFS=$'\t' read -r target source fstype size avail; do
|
||||
[ -z "$target" ] && continue
|
||||
seen["$target"]=1
|
||||
local note=""
|
||||
[ "$target" = "/" ] && note=" <-- SYSTEM ROOT, use a subfolder!"
|
||||
menu+=("$target" "${fstype} | ${avail:-?} free of ${size:-?} | ${source}${note}")
|
||||
done < <(findmnt -rnD -o TARGET,SOURCE,FSTYPE,SIZE,AVAIL 2>/dev/null |
|
||||
awk -v ex="$exclude" 'BEGIN{OFS="\t"} $3 !~ ("^(" ex ")$") && $1 !~ "^/(proc|sys|dev|run|boot)(/|$)" && $1 != "/etc/pve" && !s[$1]++ {print $1,$2,$3,$4,$5}')
|
||||
else
|
||||
while read -r source target fstype _; do
|
||||
[[ "$fstype" =~ ^($exclude)$ ]] && continue
|
||||
[[ "$target" =~ ^/(proc|sys|dev|run|boot)(/|$) ]] && continue
|
||||
[ "$target" = "/etc/pve" ] && continue
|
||||
seen["$target"]=1
|
||||
menu+=("$target" "${fstype} | ${source}")
|
||||
done < <(awk '{print $1, $2, $3}' /proc/mounts)
|
||||
fi
|
||||
|
||||
# Append Proxmox directory-type storages (path + free space). These often live
|
||||
# on an existing filesystem (e.g. /mnt/backup on root) and would otherwise be
|
||||
# invisible to a pure mount listing.
|
||||
if command -v pvesm >/dev/null 2>&1; then
|
||||
local sid savail spath
|
||||
while IFS=$'\t' read -r sid savail; do
|
||||
[ -z "$sid" ] && continue
|
||||
spath=$(awk -v id="$sid" '
|
||||
$1=="dir:" && $2==id {f=1; next}
|
||||
f && $1=="path" {print $2; exit}
|
||||
f && /^[^[:space:]]/ {exit}' /etc/pve/storage.cfg 2>/dev/null)
|
||||
[ -n "$spath" ] && [ -d "$spath" ] && [ -z "${seen[$spath]:-}" ] || continue
|
||||
seen["$spath"]=1
|
||||
menu+=("$spath" "pve-storage: ${sid} | ${savail} free")
|
||||
done < <(pvesm status 2>/dev/null | awk 'NR>1 && $3=="active" && $2=="dir" {printf "%s\t%.1fG\n", $1, $6/1048576}')
|
||||
fi
|
||||
|
||||
# --- Unmounted block devices: offer mount / format -----------------------
|
||||
# Columns: NAME TYPE FSTYPE MOUNTPOINT SIZE TRAN
|
||||
local name dtype dfs dmnt dsize dtran dev
|
||||
while read -r name dtype dfs dmnt dsize dtran; do
|
||||
[ -z "$name" ] && continue
|
||||
[[ "$name" =~ ^(loop|zram|sr|fd) ]] && continue
|
||||
[ -n "$dmnt" ] && continue # already mounted
|
||||
[ "$dtype" = "disk" ] || [ "$dtype" = "part" ] || continue
|
||||
dev="/dev/${name}"
|
||||
# skip if device or any child is mounted (system disks)
|
||||
if lsblk -rno MOUNTPOINT "$dev" 2>/dev/null | grep -q .; then continue; fi
|
||||
case "$dfs" in
|
||||
LVM2_member | swap | crypto_LUKS) continue ;; # handled via VG / not a target
|
||||
"")
|
||||
# Only offer to format reasonably sized devices (GiB/TiB), skip tiny ones.
|
||||
[[ "$dsize" =~ [GT]$ ]] || continue
|
||||
menu+=("FORMAT:${dev}" "[format] empty ${dtype} ${name} (${dsize}, ${dtran:-?}) - ERASES DATA")
|
||||
;;
|
||||
ext2 | ext3 | ext4 | xfs | btrfs | vfat | exfat | ntfs)
|
||||
menu+=("MOUNT:${dev}" "[mount] ${dfs} on ${name} (${dsize}, ${dtran:-?})")
|
||||
;;
|
||||
esac
|
||||
done < <(lsblk -rno NAME,TYPE,FSTYPE,MOUNTPOINT,SIZE,TRAN 2>/dev/null)
|
||||
|
||||
# --- Volume groups with free space: offer to create an LV ----------------
|
||||
if command -v vgs >/dev/null 2>&1; then
|
||||
local vg vgfree
|
||||
while read -r vg vgfree; do
|
||||
[ -z "$vg" ] && continue
|
||||
# only offer if there is meaningful free space (> 1 GiB)
|
||||
awk -v f="$vgfree" 'BEGIN{exit !(f+0 > 1)}' || continue
|
||||
menu+=("LV:${vg}" "[lvm] create volume in VG '${vg}' (${vgfree}G free)")
|
||||
done < <(vgs --noheadings --nosuffix --units g -o vg_name,vg_free 2>/dev/null | awk '{print $1, $2}')
|
||||
fi
|
||||
|
||||
if [ "${#menu[@]}" -eq 0 ]; then
|
||||
msg_error "No usable target found. Attach an SSD/USB/NFS first or use the NFS option."
|
||||
sleep 3
|
||||
return 1
|
||||
fi
|
||||
|
||||
local picked
|
||||
picked=$(whiptail --backtitle "$BACKTITLE" --title "Select / Prepare Target Storage" --menu \
|
||||
"\nPick a ready location, or prepare a disk/LVM ([mount]/[format]/[lvm]):" 24 112 14 "${menu[@]}" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
BROWSE_RESULT=""
|
||||
case "$picked" in
|
||||
MOUNT:*)
|
||||
dev="${picked#MOUNT:}"
|
||||
mount_existing_fs "$dev" "$(lsblk -rno FSTYPE "$dev" 2>/dev/null | head -n1)" || return 1
|
||||
BROWSE_RESULT="$PREPARED_MP"
|
||||
;;
|
||||
FORMAT:*)
|
||||
format_and_mount "${picked#FORMAT:}" || return 1
|
||||
BROWSE_RESULT="$PREPARED_MP"
|
||||
;;
|
||||
LV:*)
|
||||
vg="${picked#LV:}"
|
||||
vgfree=$(vgs --noheadings --nosuffix --units g -o vg_free "$vg" 2>/dev/null | awk '{print $1}')
|
||||
create_lv_and_mount "$vg" "$vgfree" || return 1
|
||||
BROWSE_RESULT="$PREPARED_MP"
|
||||
;;
|
||||
*)
|
||||
BROWSE_RESULT="$picked"
|
||||
;;
|
||||
esac
|
||||
[ -n "$BROWSE_RESULT" ] || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Ask the user whether the destination/source is a local path or an NFS share.
|
||||
# Sets global variable BASE_DIR to a usable directory.
|
||||
function choose_location {
|
||||
local prompt_title="$1"
|
||||
local default_path="$2"
|
||||
local choice
|
||||
|
||||
choice=$(whiptail --backtitle "$BACKTITLE" --title "$prompt_title" --menu \
|
||||
"\nWhere is the migration bundle located?" 16 74 4 \
|
||||
"browse" "Pick / prepare storage (mounts, disks, LVM)" \
|
||||
"local" "Type a local path manually (SSD, USB, mount)" \
|
||||
"nfs" "NFS share (mount on demand)" \
|
||||
3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [ "$choice" = "browse" ]; then
|
||||
local picked sub
|
||||
browse_mounts || return 1
|
||||
picked="$BROWSE_RESULT"
|
||||
[ -z "$picked" ] && return 1
|
||||
sub=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nOptional subfolder under:\n${picked}\n\nLeave empty to use it directly." 12 70 \
|
||||
--title "Subfolder" 3>&1 1>&2 2>&3) || return 1
|
||||
BASE_DIR="${picked%/}${sub:+/${sub#/}}"
|
||||
if [ ! -d "$BASE_DIR" ]; then
|
||||
mkdir -p "$BASE_DIR" || {
|
||||
msg_error "Could not create '$BASE_DIR'"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
return 0
|
||||
elif [ "$choice" = "nfs" ]; then
|
||||
local nfs_server nfs_export
|
||||
nfs_server=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nNFS server (IP or hostname):\ne.g. 192.168.1.10" 11 68 \
|
||||
--title "NFS Server" 3>&1 1>&2 2>&3) || return 1
|
||||
nfs_export=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nExported path on the NFS server:\ne.g. /volume1/proxmox-backups" 11 68 \
|
||||
--title "NFS Export Path" 3>&1 1>&2 2>&3) || return 1
|
||||
|
||||
if [ -z "$nfs_server" ] || [ -z "$nfs_export" ]; then
|
||||
msg_error "NFS server and export path are required."
|
||||
sleep 2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! command -v mount.nfs >/dev/null 2>&1; then
|
||||
msg_info "Installing nfs-common"
|
||||
apt-get update &>/dev/null
|
||||
apt-get install -y nfs-common &>/dev/null || {
|
||||
msg_error "Failed to install nfs-common"
|
||||
return 1
|
||||
}
|
||||
msg_ok "Installed nfs-common"
|
||||
fi
|
||||
|
||||
NFS_MOUNTPOINT="/mnt/${BUNDLE_PREFIX}-nfs-$$"
|
||||
mkdir -p "$NFS_MOUNTPOINT"
|
||||
msg_info "Mounting ${nfs_server}:${nfs_export}"
|
||||
if mount -t nfs "${nfs_server}:${nfs_export}" "$NFS_MOUNTPOINT" 2>/dev/null; then
|
||||
NFS_MOUNTED=1
|
||||
msg_ok "Mounted NFS share at ${NFS_MOUNTPOINT}"
|
||||
BASE_DIR="$NFS_MOUNTPOINT"
|
||||
else
|
||||
msg_error "Could not mount ${nfs_server}:${nfs_export}"
|
||||
rmdir "$NFS_MOUNTPOINT" 2>/dev/null
|
||||
sleep 2
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
local path
|
||||
path=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nLocal directory (must already exist / be mounted):" 11 68 \
|
||||
--title "Local Path" "$default_path" 3>&1 1>&2 2>&3) || return 1
|
||||
path="${path:-$default_path}"
|
||||
if [ ! -d "$path" ]; then
|
||||
if whiptail --backtitle "$BACKTITLE" --yesno "Directory '$path' does not exist. Create it?" 9 68; then
|
||||
mkdir -p "$path" || {
|
||||
msg_error "Could not create '$path'"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
BASE_DIR="$path"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Collect VM and CT lists into global arrays.
|
||||
# GUEST_ROWS entries: "type|id|name|status"
|
||||
function collect_guests {
|
||||
GUEST_ROWS=()
|
||||
if command -v qm >/dev/null 2>&1; then
|
||||
while read -r id name status _; do
|
||||
[ -z "$id" ] && continue
|
||||
GUEST_ROWS+=("vm|${id}|${name}|${status}")
|
||||
done < <(qm list 2>/dev/null | awk 'NR>1 {print $1, $2, $3}')
|
||||
fi
|
||||
if command -v pct >/dev/null 2>&1; then
|
||||
while read -r id status name _; do
|
||||
[ -z "$id" ] && continue
|
||||
GUEST_ROWS+=("ct|${id}|${name}|${status}")
|
||||
done < <(pct list 2>/dev/null | awk 'NR>1 {print $1, $2, $3}')
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# EXPORT
|
||||
# ----------------------------------------------------------------------------
|
||||
function do_export {
|
||||
choose_location "Export Destination" "/mnt/" || return
|
||||
|
||||
# Free-space awareness: show target capacity and warn when it's tight.
|
||||
local avail_h avail_g
|
||||
avail_h=$(df -h --output=avail "$BASE_DIR" 2>/dev/null | tail -n1 | tr -d ' ')
|
||||
avail_g=$(df -BG --output=avail "$BASE_DIR" 2>/dev/null | tail -n1 | tr -dc '0-9')
|
||||
if [ -n "$avail_g" ]; then
|
||||
if [ "$avail_g" -lt 10 ]; then
|
||||
if ! whiptail --backtitle "$BACKTITLE" --title "Low Disk Space" --yesno \
|
||||
"Target '${BASE_DIR}' has only ${avail_h:-?} free.\n\nA full export with vzdump can easily exceed this.\nConsider a larger disk/LVM target.\n\nContinue anyway?" 13 74; then
|
||||
return
|
||||
fi
|
||||
else
|
||||
whiptail --backtitle "$BACKTITLE" --title "Target Space" --msgbox \
|
||||
"Target: ${BASE_DIR}\nFree space: ${avail_h:-?}" 9 70
|
||||
fi
|
||||
fi
|
||||
|
||||
local bundle="${BASE_DIR%/}/${BUNDLE_PREFIX}-$(hostname)-$(date +%Y_%m_%dT%H_%M)"
|
||||
mkdir -p "$bundle/host" "$bundle/guests" || {
|
||||
msg_error "Could not create bundle directory"
|
||||
return
|
||||
}
|
||||
|
||||
# --- Component selection -------------------------------------------------
|
||||
local components
|
||||
components=$(whiptail --backtitle "$BACKTITLE" --title "Export Components" --checklist \
|
||||
"\nSelect what to export into the bundle:" 18 78 7 \
|
||||
"hostcfg" "Host configs (/etc/pve, network, storage, users, fw)" ON \
|
||||
"etc" "Full /etc tarball (extra safety net)" OFF \
|
||||
"ssh" "SSH host keys + /root/.ssh" ON \
|
||||
"apt" "APT sources + installed package list" ON \
|
||||
"guests" "LXC / VM guests" ON \
|
||||
3>&1 1>&2 2>&3) || return
|
||||
|
||||
components="${components//\"/}"
|
||||
|
||||
# --- Host config ---------------------------------------------------------
|
||||
if [[ "$components" == *hostcfg* ]]; then
|
||||
msg_info "Collecting host configuration"
|
||||
mkdir -p "$bundle/host/etc-pve" "$bundle/host/network"
|
||||
# pmxcfs content is readable as normal files
|
||||
[ -d /etc/pve ] && cp -a /etc/pve/. "$bundle/host/etc-pve/" 2>/dev/null
|
||||
[ -f /etc/network/interfaces ] && cp -a /etc/network/interfaces "$bundle/host/network/"
|
||||
[ -d /etc/network/interfaces.d ] && cp -a /etc/network/interfaces.d "$bundle/host/network/" 2>/dev/null
|
||||
[ -f /etc/hostname ] && cp -a /etc/hostname "$bundle/host/"
|
||||
[ -f /etc/hosts ] && cp -a /etc/hosts "$bundle/host/"
|
||||
[ -f /etc/resolv.conf ] && cp -a /etc/resolv.conf "$bundle/host/" 2>/dev/null
|
||||
msg_ok "Collected host configuration"
|
||||
fi
|
||||
|
||||
if [[ "$components" == *etc* ]]; then
|
||||
msg_info "Creating /etc tarball"
|
||||
tar -czf "$bundle/host/etc-full.tar.gz" --absolute-names /etc 2>/dev/null
|
||||
msg_ok "Created /etc tarball"
|
||||
fi
|
||||
|
||||
if [[ "$components" == *ssh* ]]; then
|
||||
msg_info "Collecting SSH keys"
|
||||
mkdir -p "$bundle/host/ssh"
|
||||
cp -a /etc/ssh "$bundle/host/ssh/etc-ssh" 2>/dev/null
|
||||
[ -d /root/.ssh ] && cp -a /root/.ssh "$bundle/host/ssh/root-ssh" 2>/dev/null
|
||||
msg_ok "Collected SSH keys"
|
||||
fi
|
||||
|
||||
if [[ "$components" == *apt* ]]; then
|
||||
msg_info "Collecting APT state"
|
||||
mkdir -p "$bundle/host/apt"
|
||||
cp -a /etc/apt/sources.list "$bundle/host/apt/" 2>/dev/null
|
||||
cp -a /etc/apt/sources.list.d "$bundle/host/apt/" 2>/dev/null
|
||||
dpkg --get-selections >"$bundle/host/apt/packages.selections" 2>/dev/null
|
||||
msg_ok "Collected APT state"
|
||||
fi
|
||||
|
||||
# --- Guests --------------------------------------------------------------
|
||||
local guest_method="" guest_mode="snapshot"
|
||||
: >"$bundle/guests.tsv"
|
||||
if [[ "$components" == *guests* ]]; then
|
||||
collect_guests
|
||||
if [ "${#GUEST_ROWS[@]}" -eq 0 ]; then
|
||||
msg_warn "No guests found on this host"
|
||||
else
|
||||
# method selection
|
||||
guest_method=$(whiptail --backtitle "$BACKTITLE" --title "Guest Export Method" --menu \
|
||||
"\nHow should guests be exported?" 14 78 2 \
|
||||
"vzdump" "Full portable backup incl. disks (vzdump)" \
|
||||
"config" "Configs only (no disk data)" \
|
||||
3>&1 1>&2 2>&3) || guest_method=""
|
||||
|
||||
if [ -n "$guest_method" ]; then
|
||||
# build checklist
|
||||
local menu=() row type id name status
|
||||
for row in "${GUEST_ROWS[@]}"; do
|
||||
IFS='|' read -r type id name status <<<"$row"
|
||||
menu+=("${type}:${id}" "${name} (${status})" ON)
|
||||
done
|
||||
|
||||
local selected
|
||||
selected=$(whiptail --backtitle "$BACKTITLE" --title "Select Guests" --checklist \
|
||||
"\nSelect guests to export:" 20 78 10 "${menu[@]}" 3>&1 1>&2 2>&3) || selected=""
|
||||
selected="${selected//\"/}"
|
||||
|
||||
if [ "$guest_method" = "vzdump" ]; then
|
||||
guest_mode=$(whiptail --backtitle "$BACKTITLE" --title "vzdump Mode" --menu \
|
||||
"\nBackup mode for running guests:" 14 78 3 \
|
||||
"snapshot" "Live snapshot (recommended)" \
|
||||
"suspend" "Suspend guest during backup" \
|
||||
"stop" "Stop guest during backup (most consistent)" \
|
||||
3>&1 1>&2 2>&3) || guest_mode="snapshot"
|
||||
fi
|
||||
|
||||
local sel type id name status confs
|
||||
for sel in $selected; do
|
||||
type="${sel%%:*}"
|
||||
id="${sel##*:}"
|
||||
# resolve name from rows
|
||||
name=""
|
||||
for row in "${GUEST_ROWS[@]}"; do
|
||||
IFS='|' read -r r_type r_id r_name r_status <<<"$row"
|
||||
if [ "$r_type" = "$type" ] && [ "$r_id" = "$id" ]; then name="$r_name"; fi
|
||||
done
|
||||
|
||||
if [ "$guest_method" = "vzdump" ]; then
|
||||
msg_info "vzdump ${type} ${id} (${name})"
|
||||
if vzdump "$id" --dumpdir "$bundle/guests" --mode "$guest_mode" --compress zstd &>>"$bundle/guests/vzdump.log"; then
|
||||
local file newest=""
|
||||
for file in "$bundle/guests"/vzdump-*-"${id}"-*; do
|
||||
[ -f "$file" ] || continue
|
||||
case "$file" in *.log) continue ;; esac
|
||||
[ -z "$newest" ] || [ "$file" -nt "$newest" ] && newest="$file"
|
||||
done
|
||||
file="$(basename "$newest")"
|
||||
echo -e "${type}\t${id}\t${name}\tvzdump\t${file}" >>"$bundle/guests.tsv"
|
||||
msg_ok "vzdump ${type} ${id} -> ${file}"
|
||||
else
|
||||
msg_error "vzdump failed for ${type} ${id} (see vzdump.log)"
|
||||
fi
|
||||
else
|
||||
# config-only
|
||||
mkdir -p "$bundle/guests/config"
|
||||
if [ "$type" = "vm" ]; then
|
||||
confs="/etc/pve/qemu-server/${id}.conf"
|
||||
else
|
||||
confs="/etc/pve/lxc/${id}.conf"
|
||||
fi
|
||||
if [ -f "$confs" ]; then
|
||||
cp -a "$confs" "$bundle/guests/config/${type}-${id}.conf"
|
||||
echo -e "${type}\t${id}\t${name}\tconfig\t${type}-${id}.conf" >>"$bundle/guests.tsv"
|
||||
msg_ok "Saved config for ${type} ${id}"
|
||||
else
|
||||
msg_error "Config not found: ${confs}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Manifest ------------------------------------------------------------
|
||||
{
|
||||
echo "EXPORT_HOSTNAME=\"$(hostname)\""
|
||||
echo "EXPORT_DATE=\"$(date -Iseconds)\""
|
||||
echo "EXPORT_PVE_VERSION=\"$(pveversion | head -n1)\""
|
||||
echo "EXPORT_ARCH=\"$(uname -m)\""
|
||||
echo "EXPORT_COMPONENTS=\"${components}\""
|
||||
echo "EXPORT_GUEST_METHOD=\"${guest_method}\""
|
||||
} >"$bundle/manifest.env"
|
||||
|
||||
ip -br link >"$bundle/host/network-links.info" 2>/dev/null
|
||||
ip -br addr >"$bundle/host/network-addr.info" 2>/dev/null
|
||||
pvesm status >"$bundle/host/storage.info" 2>/dev/null
|
||||
|
||||
header_info
|
||||
msg_ok "Export finished"
|
||||
echo -e "\nBundle: \e[1;33m${bundle}\e[0m\n"
|
||||
if [ "$NFS_MOUNTED" -eq 1 ]; then
|
||||
echo -e "Copied to NFS share (will be unmounted on exit).\n"
|
||||
fi
|
||||
read -rp "Press ENTER to return to the menu..."
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# IMPORT
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# Let the user pick a bundle directory inside BASE_DIR.
|
||||
function pick_bundle {
|
||||
local menu=() d count=0
|
||||
while IFS= read -r d; do
|
||||
[ -f "$d/manifest.env" ] || continue
|
||||
menu+=("$d" " ")
|
||||
count=$((count + 1))
|
||||
done < <(find "$BASE_DIR" -maxdepth 2 -type d -name "${BUNDLE_PREFIX}-*" 2>/dev/null | sort)
|
||||
|
||||
if [ "$count" -eq 0 ]; then
|
||||
msg_error "No migration bundles found under ${BASE_DIR}"
|
||||
sleep 2
|
||||
return 1
|
||||
fi
|
||||
|
||||
BUNDLE=$(whiptail --backtitle "$BACKTITLE" --title "Select Bundle" --menu \
|
||||
"\nFound migration bundles:" 20 100 10 "${menu[@]}" 3>&1 1>&2 2>&3) || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function next_free_id {
|
||||
if command -v pvesh >/dev/null 2>&1; then
|
||||
pvesh get /cluster/nextid 2>/dev/null && return
|
||||
fi
|
||||
echo "999"
|
||||
}
|
||||
|
||||
function pick_storage {
|
||||
local content="$1" menu=() store type
|
||||
while read -r store type _; do
|
||||
[ -z "$store" ] && continue
|
||||
menu+=("$store" "$type")
|
||||
done < <(pvesm status ${content:+-content "$content"} 2>/dev/null | awk 'NR>1 {print $1, $2}')
|
||||
if [ "${#menu[@]}" -eq 0 ]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
whiptail --backtitle "$BACKTITLE" --title "Target Storage" --menu \
|
||||
"\nSelect target storage:" 18 70 8 "${menu[@]}" 3>&1 1>&2 2>&3
|
||||
}
|
||||
|
||||
function import_guests {
|
||||
local bundle="$1"
|
||||
[ -f "$bundle/guests.tsv" ] || {
|
||||
msg_warn "No guests in this bundle"
|
||||
sleep 2
|
||||
return
|
||||
}
|
||||
[ -s "$bundle/guests.tsv" ] || {
|
||||
msg_warn "No guests in this bundle"
|
||||
sleep 2
|
||||
return
|
||||
}
|
||||
|
||||
local menu=() type id name method file
|
||||
while IFS=$'\t' read -r type id name method file; do
|
||||
[ -z "$type" ] && continue
|
||||
menu+=("${type}:${id}" "${name} [${method}]" ON)
|
||||
done <"$bundle/guests.tsv"
|
||||
|
||||
local selected
|
||||
selected=$(whiptail --backtitle "$BACKTITLE" --title "Restore Guests" --checklist \
|
||||
"\nSelect guests to restore:" 20 78 10 "${menu[@]}" 3>&1 1>&2 2>&3) || return
|
||||
selected="${selected//\"/}"
|
||||
[ -z "$selected" ] && return
|
||||
|
||||
local default_storage
|
||||
default_storage=$(pick_storage "") || default_storage=""
|
||||
|
||||
local sel target_type target_id
|
||||
for sel in $selected; do
|
||||
target_type="${sel%%:*}"
|
||||
local src_id="${sel##*:}"
|
||||
# find matching line
|
||||
while IFS=$'\t' read -r type id name method file; do
|
||||
[ "$type" = "$target_type" ] && [ "$id" = "$src_id" ] || continue
|
||||
|
||||
target_id="$src_id"
|
||||
# conflict check
|
||||
local exists=0
|
||||
if [ "$type" = "vm" ]; then
|
||||
qm config "$target_id" &>/dev/null && exists=1
|
||||
else
|
||||
pct config "$target_id" &>/dev/null && exists=1
|
||||
fi
|
||||
if [ "$exists" -eq 1 ]; then
|
||||
local suggested
|
||||
suggested=$(next_free_id)
|
||||
target_id=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nID ${src_id} already exists on this host.\nEnter a new ID for ${name}:" 11 68 \
|
||||
"$suggested" --title "ID Conflict" 3>&1 1>&2 2>&3) || continue
|
||||
fi
|
||||
|
||||
if [ "$method" = "vzdump" ]; then
|
||||
local archive="$bundle/guests/$file"
|
||||
if [ ! -f "$archive" ]; then
|
||||
msg_error "Archive missing: $file"
|
||||
continue
|
||||
fi
|
||||
msg_info "Restoring ${type} ${src_id} -> ${target_id}"
|
||||
if [ "$type" = "vm" ]; then
|
||||
if qmrestore "$archive" "$target_id" ${default_storage:+--storage "$default_storage"} &>/tmp/host-migrate-restore.log; then
|
||||
msg_ok "Restored VM ${target_id}"
|
||||
else
|
||||
msg_error "qmrestore failed for ${src_id} (see /tmp/host-migrate-restore.log)"
|
||||
fi
|
||||
else
|
||||
if pct restore "$target_id" "$archive" ${default_storage:+--storage "$default_storage"} &>/tmp/host-migrate-restore.log; then
|
||||
msg_ok "Restored CT ${target_id}"
|
||||
else
|
||||
msg_error "pct restore failed for ${src_id} (see /tmp/host-migrate-restore.log)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# config-only
|
||||
local conf="$bundle/guests/config/$file"
|
||||
if [ ! -f "$conf" ]; then
|
||||
msg_error "Config missing: $file"
|
||||
continue
|
||||
fi
|
||||
local dest
|
||||
if [ "$type" = "vm" ]; then
|
||||
dest="/etc/pve/qemu-server/${target_id}.conf"
|
||||
else
|
||||
dest="/etc/pve/lxc/${target_id}.conf"
|
||||
fi
|
||||
if [ -f "$dest" ]; then
|
||||
msg_error "Target config already exists: $dest"
|
||||
continue
|
||||
fi
|
||||
cp "$conf" "$dest"
|
||||
msg_ok "Restored config ${type} ${target_id} (disks must exist separately!)"
|
||||
fi
|
||||
done <"$bundle/guests.tsv"
|
||||
done
|
||||
read -rp "Press ENTER to continue..."
|
||||
}
|
||||
|
||||
function import_hostcfg {
|
||||
local bundle="$1"
|
||||
[ -d "$bundle/host" ] || {
|
||||
msg_warn "No host configs in this bundle"
|
||||
sleep 2
|
||||
return
|
||||
}
|
||||
|
||||
local sel
|
||||
sel=$(whiptail --backtitle "$BACKTITLE" --title "Restore Host Configuration" --checklist \
|
||||
"\nSelect host components to restore.\nNETWORK and HOSTNAME are DANGEROUS - read the warnings!" 20 82 8 \
|
||||
"storage" "storage.cfg (storage definitions)" OFF \
|
||||
"users" "user.cfg / firewall (PVE users + ACLs)" OFF \
|
||||
"ssh" "SSH host keys + /root/.ssh" OFF \
|
||||
"apt" "APT sources + package selections" OFF \
|
||||
"hosts" "/etc/hosts" OFF \
|
||||
"network" "/etc/network/interfaces (!! DANGER !!)" OFF \
|
||||
"hostname" "hostname (!! DANGER !!)" OFF \
|
||||
3>&1 1>&2 2>&3) || return
|
||||
sel="${sel//\"/}"
|
||||
[ -z "$sel" ] && return
|
||||
|
||||
if [[ "$sel" == *storage* ]]; then
|
||||
if [ -f "$bundle/host/etc-pve/storage.cfg" ]; then
|
||||
cp /etc/pve/storage.cfg "/etc/pve/storage.cfg.bak.$(date +%s)" 2>/dev/null
|
||||
cp "$bundle/host/etc-pve/storage.cfg" /etc/pve/storage.cfg
|
||||
msg_ok "Restored storage.cfg (review with: pvesm status)"
|
||||
else
|
||||
msg_error "storage.cfg not in bundle"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$sel" == *users* ]]; then
|
||||
[ -f "$bundle/host/etc-pve/user.cfg" ] && cp "$bundle/host/etc-pve/user.cfg" /etc/pve/user.cfg && msg_ok "Restored user.cfg"
|
||||
[ -d "$bundle/host/etc-pve/firewall" ] && cp -a "$bundle/host/etc-pve/firewall/." /etc/pve/firewall/ 2>/dev/null && msg_ok "Restored firewall rules"
|
||||
fi
|
||||
|
||||
if [[ "$sel" == *ssh* ]]; then
|
||||
[ -d "$bundle/host/ssh/etc-ssh" ] && cp -a "$bundle/host/ssh/etc-ssh/." /etc/ssh/ 2>/dev/null && msg_ok "Restored /etc/ssh"
|
||||
[ -d "$bundle/host/ssh/root-ssh" ] && cp -a "$bundle/host/ssh/root-ssh/." /root/.ssh/ 2>/dev/null && chmod 700 /root/.ssh && msg_ok "Restored /root/.ssh"
|
||||
fi
|
||||
|
||||
if [[ "$sel" == *apt* ]]; then
|
||||
[ -f "$bundle/host/apt/sources.list" ] && cp "$bundle/host/apt/sources.list" /etc/apt/sources.list && msg_ok "Restored sources.list"
|
||||
[ -d "$bundle/host/apt/sources.list.d" ] && cp -a "$bundle/host/apt/sources.list.d/." /etc/apt/sources.list.d/ 2>/dev/null && msg_ok "Restored sources.list.d"
|
||||
msg_warn "Package selections saved as reference: apt/packages.selections (not auto-installed)"
|
||||
fi
|
||||
|
||||
if [[ "$sel" == *hosts* ]]; then
|
||||
[ -f "$bundle/host/hosts" ] && cp /etc/hosts "/etc/hosts.bak.$(date +%s)" && cp "$bundle/host/hosts" /etc/hosts && msg_ok "Restored /etc/hosts"
|
||||
fi
|
||||
|
||||
if [[ "$sel" == *network* ]]; then
|
||||
import_network "$bundle"
|
||||
fi
|
||||
|
||||
if [[ "$sel" == *hostname* ]]; then
|
||||
import_hostname "$bundle"
|
||||
fi
|
||||
|
||||
read -rp "Press ENTER to continue..."
|
||||
}
|
||||
|
||||
# Dangerous: applying a foreign network config can disconnect the host.
|
||||
function import_network {
|
||||
local bundle="$1"
|
||||
local src="$bundle/host/network/interfaces"
|
||||
[ -f "$src" ] || {
|
||||
msg_error "No interfaces file in bundle"
|
||||
return
|
||||
}
|
||||
|
||||
local target_nics source_nics
|
||||
target_nics=$(ip -br link 2>/dev/null | awk '{print $1}' | grep -vE '^(lo|vmbr|tap|veth|fwbr|fwln|fwpr|bond)' | tr '\n' ' ')
|
||||
source_nics=$(grep -oE 'en[a-z0-9]+|eth[0-9]+' "$src" 2>/dev/null | sort -u | tr '\n' ' ')
|
||||
|
||||
whiptail --backtitle "$BACKTITLE" --title "!! NETWORK WARNING !!" --scrolltext --msgbox \
|
||||
"Applying the source network config can leave THIS host without network access if NIC names differ.\n\nSource NICs referenced:\n ${source_nics:-none detected}\n\nNICs on THIS host:\n ${target_nics:-none detected}\n\nThe current /etc/network/interfaces will be backed up.\nChanges require a reboot (no auto ifreload)." 22 82
|
||||
|
||||
local mode
|
||||
mode=$(whiptail --backtitle "$BACKTITLE" --title "Network Import Mode" --menu \
|
||||
"\nHow do you want to handle the network config?" 15 80 2 \
|
||||
"template" "Save as /root/migrate-interfaces.template (safe, recommended)" \
|
||||
"apply" "Overwrite /etc/network/interfaces (DANGEROUS)" \
|
||||
3>&1 1>&2 2>&3) || return
|
||||
|
||||
if [ "$mode" = "template" ]; then
|
||||
cp "$src" /root/migrate-interfaces.template
|
||||
msg_ok "Saved to /root/migrate-interfaces.template (apply manually after review)"
|
||||
return
|
||||
fi
|
||||
|
||||
local confirm
|
||||
confirm=$(whiptail --backtitle "$BACKTITLE" --inputbox \
|
||||
"\nType exactly APPLY-NETWORK to overwrite /etc/network/interfaces:" 11 70 \
|
||||
--title "Final Confirmation" 3>&1 1>&2 2>&3) || return
|
||||
if [ "$confirm" != "APPLY-NETWORK" ]; then
|
||||
msg_warn "Confirmation mismatch - network NOT changed"
|
||||
return
|
||||
fi
|
||||
cp /etc/network/interfaces "/etc/network/interfaces.bak.$(date +%s)"
|
||||
cp "$src" /etc/network/interfaces
|
||||
msg_ok "Overwrote /etc/network/interfaces (backup created)"
|
||||
msg_warn "Verify NIC names, then reboot. You may lose connectivity!"
|
||||
}
|
||||
|
||||
# Dangerous: hostname change affects /etc/pve/nodes/<name> layout.
|
||||
function import_hostname {
|
||||
local bundle="$1"
|
||||
local src="$bundle/host/hostname"
|
||||
[ -f "$src" ] || {
|
||||
msg_error "No hostname file in bundle"
|
||||
return
|
||||
}
|
||||
local new_name
|
||||
new_name=$(tr -d '[:space:]' <"$src")
|
||||
|
||||
whiptail --backtitle "$BACKTITLE" --title "!! HOSTNAME WARNING !!" --scrolltext --msgbox \
|
||||
"Changing the hostname to '${new_name}' affects:\n - /etc/pve/nodes/<name>/ (node-specific guest configs)\n - storage ownership and certificates\n\nRecommended only on a FRESH target before restoring guests.\nA reboot is required afterwards." 18 80
|
||||
|
||||
if ! whiptail --backtitle "$BACKTITLE" --yesno "Set hostname to '${new_name}' now?" 9 70; then
|
||||
return
|
||||
fi
|
||||
cp /etc/hostname "/etc/hostname.bak.$(date +%s)"
|
||||
echo "$new_name" >/etc/hostname
|
||||
if command -v hostnamectl >/dev/null 2>&1; then
|
||||
hostnamectl set-hostname "$new_name" 2>/dev/null
|
||||
fi
|
||||
msg_ok "Hostname set to ${new_name} (reboot required)"
|
||||
msg_warn "Node-specific configs under /etc/pve/nodes may need manual migration"
|
||||
}
|
||||
|
||||
function do_import {
|
||||
choose_location "Import Source" "/mnt/" || return
|
||||
pick_bundle || return
|
||||
|
||||
# Show manifest summary
|
||||
# shellcheck disable=SC1090
|
||||
source "$BUNDLE/manifest.env" 2>/dev/null
|
||||
whiptail --backtitle "$BACKTITLE" --title "Bundle Information" --scrolltext --msgbox \
|
||||
"Origin host : ${EXPORT_HOSTNAME:-?}\nExported : ${EXPORT_DATE:-?}\nPVE version : ${EXPORT_PVE_VERSION:-?}\nArch : ${EXPORT_ARCH:-?}\nComponents : ${EXPORT_COMPONENTS:-?}\nGuest method: ${EXPORT_GUEST_METHOD:-?}\n\nThis (target) host: $(hostname) / $(pveversion | head -n1)" 18 82
|
||||
|
||||
# Preflight: storage comparison
|
||||
if [ -f "$BUNDLE/host/storage.info" ]; then
|
||||
local src_stores cur_stores missing=""
|
||||
src_stores=$(awk 'NR>1 {print $1}' "$BUNDLE/host/storage.info" 2>/dev/null)
|
||||
cur_stores=$(pvesm status 2>/dev/null | awk 'NR>1 {print $1}')
|
||||
local s
|
||||
for s in $src_stores; do
|
||||
grep -qx "$s" <<<"$cur_stores" || missing+="$s "
|
||||
done
|
||||
if [ -n "$missing" ]; then
|
||||
whiptail --backtitle "$BACKTITLE" --title "Storage Preflight" --msgbox \
|
||||
"These storages from the source are MISSING on this host:\n\n ${missing}\n\nRestores using them may fail. Create them first or pick a different target storage during restore." 14 78
|
||||
fi
|
||||
fi
|
||||
|
||||
while true; do
|
||||
local choice
|
||||
choice=$(whiptail --backtitle "$BACKTITLE" --title "Import Menu" --menu \
|
||||
"\nBundle: $(basename "$BUNDLE")" 15 78 4 \
|
||||
"guests" "Restore LXC / VM guests" \
|
||||
"hostcfg" "Restore host configuration (selective)" \
|
||||
"back" "Back to main menu" \
|
||||
3>&1 1>&2 2>&3) || break
|
||||
case "$choice" in
|
||||
guests) import_guests "$BUNDLE" ;;
|
||||
hostcfg) import_hostcfg "$BUNDLE" ;;
|
||||
back) break ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Main menu
|
||||
# ----------------------------------------------------------------------------
|
||||
while true; do
|
||||
header_info
|
||||
ACTION=$(whiptail --backtitle "$BACKTITLE" --title "Proxmox VE Host Migrate" --menu \
|
||||
"\nExport this host or import a bundle onto a new host." 15 78 3 \
|
||||
"export" "Export host + guests to a bundle (mount/SSD/NFS)" \
|
||||
"import" "Import a bundle onto THIS host" \
|
||||
"quit" "Exit" \
|
||||
3>&1 1>&2 2>&3) || break
|
||||
|
||||
case "$ACTION" in
|
||||
export) do_export ;;
|
||||
import) do_import ;;
|
||||
quit) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
cleanup
|
||||
clear
|
||||
@@ -55,12 +55,23 @@ read -r selected
|
||||
selected_indices=()
|
||||
IFS=',' read -r -a tokens <<<"$selected"
|
||||
for token in "${tokens[@]}"; do
|
||||
# Strip surrounding whitespace and skip empty tokens
|
||||
token="${token//[[:space:]]/}"
|
||||
[ -z "$token" ] && continue
|
||||
if [[ "$token" =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||||
for ((i = BASH_REMATCH[1]; i <= BASH_REMATCH[2]; i++)); do
|
||||
start=${BASH_REMATCH[1]}
|
||||
end=${BASH_REMATCH[2]}
|
||||
if ((start > end)); then
|
||||
echo -e "${RD}Ignoring invalid range '${token}' (start greater than end).${CL}"
|
||||
continue
|
||||
fi
|
||||
for ((i = start; i <= end; i++)); do
|
||||
selected_indices+=("$i")
|
||||
done
|
||||
else
|
||||
elif [[ "$token" =~ ^[0-9]+$ ]]; then
|
||||
selected_indices+=("$token")
|
||||
else
|
||||
echo -e "${RD}Ignoring invalid selection '${token}'.${CL}"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -101,8 +112,7 @@ for kernel in "${kernels_to_remove[@]}"; do
|
||||
remaining=$(dpkg --list |
|
||||
awk '/^ii/ {print $2}' |
|
||||
grep -E "^proxmox-kernel-${minor_version}\." |
|
||||
grep -v "^${kernel}$" |
|
||||
wc -l)
|
||||
grep -cv "^${kernel}$")
|
||||
if [ "$remaining" -eq 0 ]; then
|
||||
pkgs_to_remove+=("$meta")
|
||||
fi
|
||||
|
||||
+11
-11
@@ -16,10 +16,10 @@ function header_info {
|
||||
EOF
|
||||
}
|
||||
|
||||
RD=$(echo "\033[01;31m")
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
CL=$(echo "\033[m")
|
||||
RD="\033[01;31m"
|
||||
YW="\033[33m"
|
||||
GN="\033[1;92m"
|
||||
CL="\033[m"
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
CM="${GN}✓${CL}"
|
||||
@@ -47,7 +47,7 @@ intel() {
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
intel_microcode=$(curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode//" | grep -o 'href="[^"]*amd64.deb"' | sed 's/href="//;s/"//')
|
||||
intel_microcode=$(curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/i/intel-microcode/" | grep -o 'href="[^"]*amd64.deb"' | sed 's/href="//;s/"//')
|
||||
[ -z "$intel_microcode" ] && {
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "No Microcode Found" --msgbox "It appears there were no microcode packages found\n Try again later." 10 68
|
||||
msg_info "Exiting"
|
||||
@@ -80,17 +80,17 @@ intel() {
|
||||
msg_ok "Downloaded the Intel Processor Microcode Package $microcode"
|
||||
|
||||
msg_info "Installing $microcode (Patience)"
|
||||
dpkg -i $microcode &>/dev/null
|
||||
dpkg -i "$microcode" &>/dev/null
|
||||
msg_ok "Installed $microcode"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm $microcode
|
||||
rm -f "$microcode"
|
||||
msg_ok "Cleaned"
|
||||
echo -e "\nIn order to apply the changes, a system reboot will be necessary.\n"
|
||||
}
|
||||
|
||||
amd() {
|
||||
amd_microcode=$(curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode///" | grep -o 'href="[^"]*amd64.deb"' | sed 's/href="//;s/"//')
|
||||
amd_microcode=$(curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/" | grep -o 'href="[^"]*amd64.deb"' | sed 's/href="//;s/"//')
|
||||
|
||||
[ -z "$amd_microcode" ] && {
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "No Microcode Found" --msgbox "It appears there were no microcode packages found\n Try again later." 10 68
|
||||
@@ -120,15 +120,15 @@ amd() {
|
||||
}
|
||||
|
||||
msg_info "Downloading the AMD Processor Microcode Package $microcode"
|
||||
curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode" -o $(basename "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode")
|
||||
curl -fsSL --proto '=https' "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode" -o "$microcode"
|
||||
msg_ok "Downloaded the AMD Processor Microcode Package $microcode"
|
||||
|
||||
msg_info "Installing $microcode (Patience)"
|
||||
dpkg -i $microcode &>/dev/null
|
||||
dpkg -i "$microcode" &>/dev/null
|
||||
msg_ok "Installed $microcode"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm $microcode
|
||||
rm -f "$microcode"
|
||||
msg_ok "Cleaned"
|
||||
echo -e "\nIn order to apply the changes, a system reboot will be necessary.\n"
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ EOF
|
||||
}
|
||||
|
||||
# Color definitions
|
||||
RD=$(echo "\033[01;31m")
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
CL=$(echo "\033[m")
|
||||
RD="\033[01;31m"
|
||||
YW="\033[33m"
|
||||
GN="\033[1;92m"
|
||||
CL="\033[m"
|
||||
BFR="\\r\\033[K"
|
||||
HOLD="-"
|
||||
CM="${GN}✓${CL}"
|
||||
@@ -94,11 +94,11 @@ intel() {
|
||||
msg_ok "Downloaded Intel processor microcode package $microcode"
|
||||
|
||||
msg_info "Installing $microcode (this might take a while)"
|
||||
dpkg -i $microcode &>/dev/null
|
||||
dpkg -i "$microcode" &>/dev/null
|
||||
msg_ok "Installed $microcode"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm $microcode
|
||||
rm -f "$microcode"
|
||||
msg_ok "Clean up complete"
|
||||
echo -e "\nA system reboot is required to apply the changes.\n"
|
||||
}
|
||||
@@ -137,15 +137,15 @@ amd() {
|
||||
}
|
||||
|
||||
msg_info "Downloading AMD processor microcode package $microcode"
|
||||
curl -fsSL "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode" -o $(basename "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode")
|
||||
curl -fsSL --proto '=https' "https://ftp.debian.org/debian/pool/non-free-firmware/a/amd64-microcode/$microcode" -o "$microcode"
|
||||
msg_ok "Downloaded AMD processor microcode package $microcode"
|
||||
|
||||
msg_info "Installing $microcode (this might take a while)"
|
||||
dpkg -i $microcode &>/dev/null
|
||||
dpkg -i "$microcode" &>/dev/null
|
||||
msg_ok "Installed $microcode"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm $microcode
|
||||
rm -f "$microcode"
|
||||
msg_ok "Clean up complete"
|
||||
echo -e "\nA system reboot is required to apply the changes.\n"
|
||||
}
|
||||
|
||||
@@ -20,16 +20,26 @@ header_info() {
|
||||
EOF
|
||||
}
|
||||
header_info
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU Scaling Governors" --yesno "View/Change CPU Scaling Governors. Proceed?" 10 58
|
||||
current_governor=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU Scaling Governors" --yesno "View/Change CPU Scaling Governors. Proceed?" 10 58 || exit 0
|
||||
|
||||
GOV_BASE="/sys/devices/system/cpu/cpu0/cpufreq"
|
||||
if [[ ! -r "$GOV_BASE/scaling_governor" || ! -r "$GOV_BASE/scaling_available_governors" ]]; then
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU Scaling Not Available" \
|
||||
--msgbox "CPU frequency scaling is not available on this system.\n\nThis is normal when no cpufreq driver is active (e.g. CPU power management handled by the BIOS, or certain virtualized hosts)." 12 70
|
||||
clear
|
||||
exit 0
|
||||
fi
|
||||
|
||||
current_governor=$(cat "$GOV_BASE/scaling_governor")
|
||||
GOVERNORS_MENU=()
|
||||
MSG_MAX_LENGTH=0
|
||||
while read -r TAG ITEM; do
|
||||
OFFSET=2
|
||||
((${#ITEM} + OFFSET > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=${#ITEM}+OFFSET
|
||||
GOVERNORS_MENU+=("$TAG" "$ITEM " "OFF")
|
||||
done < <(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors | tr ' ' '\n' | grep -v "$current_governor")
|
||||
scaling_governor=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Current CPU Scaling Governor is set to $current_governor" --checklist "\nSelect the Scaling Governor to use:\n" 16 $((MSG_MAX_LENGTH + 58)) 6 "${GOVERNORS_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"')
|
||||
done < <(tr ' ' '\n' <"$GOV_BASE/scaling_available_governors" | sed '/^$/d' | grep -vxF "$current_governor")
|
||||
# A radiolist is used on purpose: only a single governor can be active at a time.
|
||||
scaling_governor=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Current CPU Scaling Governor is set to $current_governor" --radiolist "\nSelect the Scaling Governor to use:\n" 16 $((MSG_MAX_LENGTH + 58)) 6 "${GOVERNORS_MENU[@]}" 3>&1 1>&2 2>&3 | tr -d '"')
|
||||
[ -z "$scaling_governor" ] && {
|
||||
whiptail --backtitle "Proxmox VE Helper Scripts" --title "No CPU Scaling Governor Selected" --msgbox "It appears that no CPU Scaling Governor was selected" 10 68
|
||||
clear
|
||||
@@ -49,7 +59,7 @@ yes)
|
||||
EXISTING_CRONTAB=$(crontab -l 2>/dev/null)
|
||||
if [[ -n "$EXISTING_CRONTAB" ]]; then
|
||||
TEMP_CRONTAB_FILE=$(mktemp)
|
||||
echo "$EXISTING_CRONTAB" | grep -v "@reboot (sleep 60 && echo*" >"$TEMP_CRONTAB_FILE"
|
||||
echo "$EXISTING_CRONTAB" | grep -vF "@reboot (sleep 60 && echo" >"$TEMP_CRONTAB_FILE"
|
||||
crontab "$TEMP_CRONTAB_FILE"
|
||||
rm "$TEMP_CRONTAB_FILE"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user