Compare commits

...

16 Commits

Author SHA1 Message Date
Sam Heinz 0d7d3bfacf change libpcre3-dev to libpcre2-dev 2026-06-28 21:30:00 +10:00
Sam Heinz 81812089f6 hardcode openresty version and build it from website release 2026-06-28 21:28:46 +10:00
MickLesk 5bea573281 Improve storage prep & mounting in host-migrate
Add interactive storage preparation and safer mount handling for host-migrate.

- Track and clean up on-demand mounts via TEMP_MOUNTS and extend cleanup handler.
- Add helpers: _new_mountpoint, _offer_fstab, mount_existing_fs, format_and_mount, create_lv_and_mount to mount, format, or create LVs and optionally persist to /etc/fstab.
- Enhance browse_mounts to list unmounted block devices and LVM VGs, offer mount/format/LV creation, and return prepared mount via BROWSE_RESULT.
- Integrate prepared target into choose_location and do_export; show free-space warning before export.
- Improve vzdump output detection to pick the newest non-log file.
- Minor UX/message tweaks and quoting fixes for backup filenames when restoring storage.cfg and /etc/hosts.

These changes let users pick or prepare target storage (mount existing FS, format disks, create LVs) interactively and ensures temporary mounts are cleaned up.
2026-06-28 11:24:31 +02:00
MickLesk 5603b69b81 Add host-migrate.sh Proxmox VE tool
Add tools/pve/host-migrate.sh — an interactive Proxmox VE host migration utility. The script (whiptail UI) can export host configuration, /etc tarball, SSH keys, APT state and LXC/QEMU guests (vzdump or config-only) into a timestamped bundle, with optional on-demand NFS mounting. It also supports importing bundles to restore guests and selective host components (storage, users, SSH, APT, hosts, network, hostname) with explicit warnings for dangerous operations (network/hostname). Implements preflight checks, manifest creation, storage mapping checks, cleanup trap for NFS, and integrates helper functions loaded from the project's core scripts.
2026-06-28 11:17:47 +02:00
community-scripts-pr-app[bot] d756c83b62 Update CHANGELOG.md (#15451)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-28 00:22:50 +00:00
community-scripts-pr-app[bot] e36e0bbe3f Archive old changelog entries (#15450)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-28 00:22:24 +00:00
community-scripts-pr-app[bot] de0408e12f Update CHANGELOG.md (#15445)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 21:28:03 +00:00
CanbiZ (MickLesk) b5361e5278 tool: add disk-health tool (SMART + NVMe) (#15417) 2026-06-27 23:27:43 +02:00
community-scripts-pr-app[bot] 4f9f556a93 Update CHANGELOG.md (#15444)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 19:55:59 +00:00
TN 4b23f2c72c Storyteller: bump Node.js version to Node 24 (#15439)
* storyteller: bump Node.js version

* storyteller: update: node_version

---------

Co-authored-by: Tobias <96661824+CrazyWolf13@users.noreply.github.com>
2026-06-27 21:55:37 +02:00
community-scripts-pr-app[bot] 2ded16ed4b Update CHANGELOG.md (#15441)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 19:07:29 +00:00
Sam Heinz e1f61aeeb9 Deluge openssl fix (#15435) 2026-06-27 21:07:03 +02:00
community-scripts-pr-app[bot] fb9f5a0047 Update CHANGELOG.md (#15438)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 14:51:17 +00:00
Sam Heinz 17b5f8c10c fix command syntax in tunarr.sh (#15434)
* fix command syntax in tunarr.sh

* Update tunarr.sh
2026-06-27 16:50:56 +02:00
community-scripts-pr-app[bot] 8b99d3b1cc Update CHANGELOG.md (#15430)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-27 02:52:41 +00:00
Copilot 039470965b fix(endurain): replace Poetry/uv-pip backend setup with uv sync --frozen --no-dev (#15429)
* Initial plan

* fix: remove exclude-newer constraint from pyproject.toml before uv pip install in endurain

* fix(endurain): replace poetry+uv pip with uv sync --frozen --no-dev

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-27 12:52:18 +10:00
13 changed files with 1324 additions and 189 deletions
+204
View File
@@ -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
+28 -161
View File
@@ -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,6 +489,29 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 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
- #### 🐞 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
@@ -1107,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))
+2 -2
View File
@@ -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 -4
View File
@@ -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"
+5 -3
View File
@@ -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]
+2
View File
@@ -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
+2 -7
View File
@@ -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
View File
@@ -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"
+1 -8
View File
@@ -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"
+4 -2
View File
@@ -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
+1 -1
View File
@@ -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
+156
View File
@@ -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"
+917
View File
@@ -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