Compare commits

..

1 Commits

Author SHA1 Message Date
MickLesk
78b4e71ce9 fix(2fauth): export PHP_VERSION for nginx config
The PHP_VERSION variable was only available within the setup_php
function call scope. By setting it separately before the function
call, it remains available for the nginx configuration heredoc.

Fixes #11439
2026-02-01 20:49:02 +01:00
100 changed files with 1724 additions and 4660 deletions

310
.github/changelogs/2026/01.md generated vendored
View File

@@ -1,156 +1,14 @@
## 2026-01-31 ## 2026-01-27
### 🆕 New Scripts
- shelfmark ([#11371](https://github.com/community-scripts/ProxmoxVE/pull/11371))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix: yubal: add git [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11394](https://github.com/community-scripts/ProxmoxVE/pull/11394))
## 2026-01-30
### 🆕 New Scripts
- languagetool ([#11370](https://github.com/community-scripts/ProxmoxVE/pull/11370))
- Ampache ([#11369](https://github.com/community-scripts/ProxmoxVE/pull/11369))
### 🚀 Updated Scripts
- #### 🔧 Refactor
- Refactor: remove redundant PHP_MODULE entries in several scripts [@MickLesk](https://github.com/MickLesk) ([#11362](https://github.com/community-scripts/ProxmoxVE/pull/11362))
- Refactor: Koillection [@MickLesk](https://github.com/MickLesk) ([#11361](https://github.com/community-scripts/ProxmoxVE/pull/11361))
### 💾 Core
- #### 🐞 Bug Fixes
- core: meilisearch - add data migration for version upgrades [@MickLesk](https://github.com/MickLesk) ([#11356](https://github.com/community-scripts/ProxmoxVE/pull/11356))
- #### ✨ New Features
- [tools] Add `fetch_and_deploy_from_url()` [@tremor021](https://github.com/tremor021) ([#11376](https://github.com/community-scripts/ProxmoxVE/pull/11376))
- core: php - improve module handling and prevent installation failures [@MickLesk](https://github.com/MickLesk) ([#11358](https://github.com/community-scripts/ProxmoxVE/pull/11358))
## 2026-01-29
### 🆕 New Scripts
- Alpine-Valkey [@MickLesk](https://github.com/MickLesk) ([#11320](https://github.com/community-scripts/ProxmoxVE/pull/11320))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: Pin version to 2.5.2 [@vhsdream](https://github.com/vhsdream) ([#11335](https://github.com/community-scripts/ProxmoxVE/pull/11335))
- Kollection: Update to php 8.5 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11315](https://github.com/community-scripts/ProxmoxVE/pull/11315))
- Notifiarr: change installation check from apt to systemd service [@MickLesk](https://github.com/MickLesk) ([#11319](https://github.com/community-scripts/ProxmoxVE/pull/11319))
- #### ✨ New Features
- [FEAT] Immich: Enable Maintenance Mode before update [@vhsdream](https://github.com/vhsdream) ([#11342](https://github.com/community-scripts/ProxmoxVE/pull/11342))
- jellyfin: add logrotate instead of reducing log level [@MickLesk](https://github.com/MickLesk) ([#11326](https://github.com/community-scripts/ProxmoxVE/pull/11326))
- core: Add config file handling options | Fix Vikunja update with interactive overwrite [@MickLesk](https://github.com/MickLesk) ([#11317](https://github.com/community-scripts/ProxmoxVE/pull/11317))
- Immich: v2.5.0 [@vhsdream](https://github.com/vhsdream) ([#11240](https://github.com/community-scripts/ProxmoxVE/pull/11240))
- #### 💥 Breaking Changes
- fix: vikunja v1 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11308](https://github.com/community-scripts/ProxmoxVE/pull/11308))
- #### 🔧 Refactor
- Refactor: Byparr [@vhsdream](https://github.com/vhsdream) ([#11338](https://github.com/community-scripts/ProxmoxVE/pull/11338))
- cloudflare: Remove deprecated DNS-over-HTTPS proxy option [@MickLesk](https://github.com/MickLesk) ([#11068](https://github.com/community-scripts/ProxmoxVE/pull/11068))
### 💾 Core
- #### 🐞 Bug Fixes
- build.func: Replace storage variable with searchdomain variable [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11322](https://github.com/community-scripts/ProxmoxVE/pull/11322))
### 📂 Github
- Add workflow to lock closed issues [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11316](https://github.com/community-scripts/ProxmoxVE/pull/11316))
## 2026-01-28
### 🆕 New Scripts
- nodecast-tv ([#11287](https://github.com/community-scripts/ProxmoxVE/pull/11287))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Ubuntu 25.04 VM - Change default start from yes to no [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11292](https://github.com/community-scripts/ProxmoxVE/pull/11292))
- #### ✨ New Features
- various scripts: use setup_meilisearch function [@MickLesk](https://github.com/MickLesk) ([#11259](https://github.com/community-scripts/ProxmoxVE/pull/11259))
- #### 🔧 Refactor
- Refactor: NPMPlus / Default Login [@MickLesk](https://github.com/MickLesk) ([#11262](https://github.com/community-scripts/ProxmoxVE/pull/11262))
### 💾 Core
- #### 🐞 Bug Fixes
- core: sed patch for ram [@lavacano](https://github.com/lavacano) ([#11285](https://github.com/community-scripts/ProxmoxVE/pull/11285))
- Fix installer loop caused by invalid whiptail menu separator [@Mesteriis](https://github.com/Mesteriis) ([#11237](https://github.com/community-scripts/ProxmoxVE/pull/11237))
- core: fix Debian 13 LXC template root ownership bug [@MickLesk](https://github.com/MickLesk) ([#11277](https://github.com/community-scripts/ProxmoxVE/pull/11277))
- tools.func: prevent systemd-tmpfiles failure in unprivileged LXC during deb install [@MickLesk](https://github.com/MickLesk) ([#11271](https://github.com/community-scripts/ProxmoxVE/pull/11271))
- tools.func: fix php "wait_for" hint [@MickLesk](https://github.com/MickLesk) ([#11254](https://github.com/community-scripts/ProxmoxVE/pull/11254))
- #### ✨ New Features
- core: update dynamic values in LXC profile on update_motd_ip [@MickLesk](https://github.com/MickLesk) ([#11268](https://github.com/community-scripts/ProxmoxVE/pull/11268))
- tools.func: add new function - setup_meilisearch [@MickLesk](https://github.com/MickLesk) ([#11258](https://github.com/community-scripts/ProxmoxVE/pull/11258))
### 📂 Github
- github: add GitHub-based versions.json updater [@MickLesk](https://github.com/MickLesk) ([#10021](https://github.com/community-scripts/ProxmoxVE/pull/10021))
### 🌐 Website
- #### ✨ New Features
- Frontend: use github-versions.json for version display [@MickLesk](https://github.com/MickLesk) ([#11281](https://github.com/community-scripts/ProxmoxVE/pull/11281))
- #### 📝 Script Information
- fix: homarr: conf location [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11253](https://github.com/community-scripts/ProxmoxVE/pull/11253))
## 2026-01-27
### 🚀 Updated Scripts ### 🚀 Updated Scripts
- #### 🐞 Bug Fixes - #### 🐞 Bug Fixes
- [FIX] Jotty: backup and restore custom config [@vhsdream](https://github.com/vhsdream) ([#11212](https://github.com/community-scripts/ProxmoxVE/pull/11212)) - [FIX] Jotty: backup and restore custom config [@vhsdream](https://github.com/vhsdream) ([#11212](https://github.com/community-scripts/ProxmoxVE/pull/11212))
- Immich: update libraw [@vhsdream](https://github.com/vhsdream) ([#11233](https://github.com/community-scripts/ProxmoxVE/pull/11233))
- #### ✨ New Features
- grist: enable optional enterprise features toggle [@MickLesk](https://github.com/MickLesk) ([#11239](https://github.com/community-scripts/ProxmoxVE/pull/11239))
- #### 🔧 Refactor
- Termix: use nginx.conf from upstream repo [@MickLesk](https://github.com/MickLesk) ([#11228](https://github.com/community-scripts/ProxmoxVE/pull/11228))
### 💾 Core
- #### ✨ New Features
- feat: add NVIDIA driver install prompt for GPU-enabled containers [@devdecrux](https://github.com/devdecrux) ([#11184](https://github.com/community-scripts/ProxmoxVE/pull/11184))
### 📚 Documentation ### 📚 Documentation
- doc setup_deb822_repo arg order [@chrnie](https://github.com/chrnie) ([#11215](https://github.com/community-scripts/ProxmoxVE/pull/11215)) - doc setup_deb822_repo arg order [@chrnie](https://github.com/chrnie) ([#11215](https://github.com/community-scripts/ProxmoxVE/pull/11215))
- changelog: archive old entries to year/month files [@MickLesk](https://github.com/MickLesk) ([#11225](https://github.com/community-scripts/ProxmoxVE/pull/11225))
## 2026-01-26 ## 2026-01-26
@@ -242,7 +100,7 @@
### 🆕 New Scripts ### 🆕 New Scripts
- Tracearr ([#11079](https://github.com/community-scripts/ProxmoxVE/pull/11079)) - Tracearr ([#11079](https://github.com/community-scripts/ProxmoxVE/pull/11079))
- Dawarich ([#11075](https://github.com/community-scripts/ProxmoxVE/pull/11075)) - Dawarich ([#11075](https://github.com/community-scripts/ProxmoxVE/pull/11075))
### 🚀 Updated Scripts ### 🚀 Updated Scripts
@@ -392,7 +250,7 @@
### 🆕 New Scripts ### 🆕 New Scripts
- Termix ([#10887](https://github.com/community-scripts/ProxmoxVE/pull/10887)) - Termix ([#10887](https://github.com/community-scripts/ProxmoxVE/pull/10887))
- ThingsBoard ([#10904](https://github.com/community-scripts/ProxmoxVE/pull/10904)) - ThingsBoard ([#10904](https://github.com/community-scripts/ProxmoxVE/pull/10904))
### 🚀 Updated Scripts ### 🚀 Updated Scripts
@@ -461,7 +319,7 @@
### 🆕 New Scripts ### 🆕 New Scripts
- Flatnotes ([#10857](https://github.com/community-scripts/ProxmoxVE/pull/10857)) - Flatnotes ([#10857](https://github.com/community-scripts/ProxmoxVE/pull/10857))
- Unifi OS Server ([#10856](https://github.com/community-scripts/ProxmoxVE/pull/10856)) - Unifi OS Server ([#10856](https://github.com/community-scripts/ProxmoxVE/pull/10856))
### 🚀 Updated Scripts ### 🚀 Updated Scripts
@@ -527,7 +385,7 @@
### 🆕 New Scripts ### 🆕 New Scripts
- Investbrain ([#10774](https://github.com/community-scripts/ProxmoxVE/pull/10774)) - Investbrain ([#10774](https://github.com/community-scripts/ProxmoxVE/pull/10774))
- Fladder ([#10768](https://github.com/community-scripts/ProxmoxVE/pull/10768)) - Fladder ([#10768](https://github.com/community-scripts/ProxmoxVE/pull/10768))
### 🚀 Updated Scripts ### 🚀 Updated Scripts
@@ -716,7 +574,7 @@
### 📚 Documentation ### 📚 Documentation
- [gh] New Script template update [@tremor021](https://github.com/tremor021) ([#10607](https://github.com/community-scripts/ProxmoxVE/pull/10607)) - [gh] New Script template update [@tremor021](https://github.com/tremor021) ([#10607](https://github.com/community-scripts/ProxmoxVE/pull/10607))
- chore: bump copyright to 2026 - happy new year [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10585](https://github.com/community-scripts/ProxmoxVE/pull/10585)) - chore: bump copyright to 2026 - happy new year [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10585](https://github.com/community-scripts/ProxmoxVE/pull/10585))
### 📂 Github ### 📂 Github
@@ -797,7 +655,7 @@
### ❔ Uncategorized ### ❔ Uncategorized
- Wireguard: Update WGDashboard notes URL to the new link [@tremor021](https://github.com/tremor021) ([#10496](https://github.com/community-scripts/ProxmoxVE/pull/10496)) - Wireguard: Update WGDashboard notes URL to the new link [@tremor021](https://github.com/tremor021) ([#10496](https://github.com/community-scripts/ProxmoxVE/pull/10496))
- InvoiceNinja: Update database credentias information [@tremor021](https://github.com/tremor021) ([#10497](https://github.com/community-scripts/ProxmoxVE/pull/10497)) - InvoiceNinja: Update database credentias information [@tremor021](https://github.com/tremor021) ([#10497](https://github.com/community-scripts/ProxmoxVE/pull/10497))
## 2026-01-02 ## 2026-01-02
@@ -826,3 +684,157 @@
- #### 🐞 Bug Fixes - #### 🐞 Bug Fixes
- Fix MariaDB runtime directory persistence on container reboot [@Copilot](https://github.com/Copilot) ([#10468](https://github.com/community-scripts/ProxmoxVE/pull/10468)) - Fix MariaDB runtime directory persistence on container reboot [@Copilot](https://github.com/Copilot) ([#10468](https://github.com/community-scripts/ProxmoxVE/pull/10468))
## 2026-01-31
### 🆕 New Scripts
- shelfmark ([#11371](https://github.com/community-scripts/ProxmoxVE/pull/11371))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix: yubal: add git [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11394](https://github.com/community-scripts/ProxmoxVE/pull/11394))
## 2026-01-30
### 🆕 New Scripts
- languagetool ([#11370](https://github.com/community-scripts/ProxmoxVE/pull/11370))
- Ampache ([#11369](https://github.com/community-scripts/ProxmoxVE/pull/11369))
### 🚀 Updated Scripts
- #### 🔧 Refactor
- Refactor: remove redundant PHP_MODULE entries in several scripts [@MickLesk](https://github.com/MickLesk) ([#11362](https://github.com/community-scripts/ProxmoxVE/pull/11362))
- Refactor: Koillection [@MickLesk](https://github.com/MickLesk) ([#11361](https://github.com/community-scripts/ProxmoxVE/pull/11361))
### 💾 Core
- #### 🐞 Bug Fixes
- core: meilisearch - add data migration for version upgrades [@MickLesk](https://github.com/MickLesk) ([#11356](https://github.com/community-scripts/ProxmoxVE/pull/11356))
- #### ✨ New Features
- [tools] Add `fetch_and_deploy_from_url()` [@tremor021](https://github.com/tremor021) ([#11376](https://github.com/community-scripts/ProxmoxVE/pull/11376))
- core: php - improve module handling and prevent installation failures [@MickLesk](https://github.com/MickLesk) ([#11358](https://github.com/community-scripts/ProxmoxVE/pull/11358))
## 2026-01-29
### 🆕 New Scripts
- Alpine-Valkey [@MickLesk](https://github.com/MickLesk) ([#11320](https://github.com/community-scripts/ProxmoxVE/pull/11320))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: Pin version to 2.5.2 [@vhsdream](https://github.com/vhsdream) ([#11335](https://github.com/community-scripts/ProxmoxVE/pull/11335))
- Kollection: Update to php 8.5 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11315](https://github.com/community-scripts/ProxmoxVE/pull/11315))
- Notifiarr: change installation check from apt to systemd service [@MickLesk](https://github.com/MickLesk) ([#11319](https://github.com/community-scripts/ProxmoxVE/pull/11319))
- #### ✨ New Features
- [FEAT] Immich: Enable Maintenance Mode before update [@vhsdream](https://github.com/vhsdream) ([#11342](https://github.com/community-scripts/ProxmoxVE/pull/11342))
- jellyfin: add logrotate instead of reducing log level [@MickLesk](https://github.com/MickLesk) ([#11326](https://github.com/community-scripts/ProxmoxVE/pull/11326))
- core: Add config file handling options | Fix Vikunja update with interactive overwrite [@MickLesk](https://github.com/MickLesk) ([#11317](https://github.com/community-scripts/ProxmoxVE/pull/11317))
- Immich: v2.5.0 [@vhsdream](https://github.com/vhsdream) ([#11240](https://github.com/community-scripts/ProxmoxVE/pull/11240))
- #### 💥 Breaking Changes
- fix: vikunja v1 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11308](https://github.com/community-scripts/ProxmoxVE/pull/11308))
- #### 🔧 Refactor
- Refactor: Byparr [@vhsdream](https://github.com/vhsdream) ([#11338](https://github.com/community-scripts/ProxmoxVE/pull/11338))
- cloudflare: Remove deprecated DNS-over-HTTPS proxy option [@MickLesk](https://github.com/MickLesk) ([#11068](https://github.com/community-scripts/ProxmoxVE/pull/11068))
### 💾 Core
- #### 🐞 Bug Fixes
- build.func: Replace storage variable with searchdomain variable [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11322](https://github.com/community-scripts/ProxmoxVE/pull/11322))
### 📂 Github
- Add workflow to lock closed issues [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11316](https://github.com/community-scripts/ProxmoxVE/pull/11316))
## 2026-01-28
### 🆕 New Scripts
- nodecast-tv ([#11287](https://github.com/community-scripts/ProxmoxVE/pull/11287))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Ubuntu 25.04 VM - Change default start from yes to no [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11292](https://github.com/community-scripts/ProxmoxVE/pull/11292))
- #### ✨ New Features
- various scripts: use setup_meilisearch function [@MickLesk](https://github.com/MickLesk) ([#11259](https://github.com/community-scripts/ProxmoxVE/pull/11259))
- #### 🔧 Refactor
- Refactor: NPMPlus / Default Login [@MickLesk](https://github.com/MickLesk) ([#11262](https://github.com/community-scripts/ProxmoxVE/pull/11262))
### 💾 Core
- #### 🐞 Bug Fixes
- core: sed patch for ram [@lavacano](https://github.com/lavacano) ([#11285](https://github.com/community-scripts/ProxmoxVE/pull/11285))
- Fix installer loop caused by invalid whiptail menu separator [@Mesteriis](https://github.com/Mesteriis) ([#11237](https://github.com/community-scripts/ProxmoxVE/pull/11237))
- core: fix Debian 13 LXC template root ownership bug [@MickLesk](https://github.com/MickLesk) ([#11277](https://github.com/community-scripts/ProxmoxVE/pull/11277))
- tools.func: prevent systemd-tmpfiles failure in unprivileged LXC during deb install [@MickLesk](https://github.com/MickLesk) ([#11271](https://github.com/community-scripts/ProxmoxVE/pull/11271))
- tools.func: fix php "wait_for" hint [@MickLesk](https://github.com/MickLesk) ([#11254](https://github.com/community-scripts/ProxmoxVE/pull/11254))
- #### ✨ New Features
- core: update dynamic values in LXC profile on update_motd_ip [@MickLesk](https://github.com/MickLesk) ([#11268](https://github.com/community-scripts/ProxmoxVE/pull/11268))
- tools.func: add new function - setup_meilisearch [@MickLesk](https://github.com/MickLesk) ([#11258](https://github.com/community-scripts/ProxmoxVE/pull/11258))
### 📂 Github
- github: add GitHub-based versions.json updater [@MickLesk](https://github.com/MickLesk) ([#10021](https://github.com/community-scripts/ProxmoxVE/pull/10021))
### 🌐 Website
- #### ✨ New Features
- Frontend: use github-versions.json for version display [@MickLesk](https://github.com/MickLesk) ([#11281](https://github.com/community-scripts/ProxmoxVE/pull/11281))
- #### 📝 Script Information
- fix: homarr: conf location [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11253](https://github.com/community-scripts/ProxmoxVE/pull/11253))
## 2026-01-27
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: update libraw [@vhsdream](https://github.com/vhsdream) ([#11233](https://github.com/community-scripts/ProxmoxVE/pull/11233))
- #### ✨ New Features
- grist: enable optional enterprise features toggle [@MickLesk](https://github.com/MickLesk) ([#11239](https://github.com/community-scripts/ProxmoxVE/pull/11239))
- #### 🔧 Refactor
- Termix: use nginx.conf from upstream repo [@MickLesk](https://github.com/MickLesk) ([#11228](https://github.com/community-scripts/ProxmoxVE/pull/11228))
### 💾 Core
- #### ✨ New Features
- feat: add NVIDIA driver install prompt for GPU-enabled containers [@devdecrux](https://github.com/devdecrux) ([#11184](https://github.com/community-scripts/ProxmoxVE/pull/11184))
### 📚 Documentation
- doc setup_deb822_repo arg order [@chrnie](https://github.com/chrnie) ([#11215](https://github.com/community-scripts/ProxmoxVE/pull/11215))
- changelog: archive old entries to year/month files [@MickLesk](https://github.com/MickLesk) ([#11225](https://github.com/community-scripts/ProxmoxVE/pull/11225))

121
.github/changelogs/2026/02.md generated vendored
View File

@@ -1,121 +0,0 @@
## 2026-02-04
### 🆕 New Scripts
- Wishlist ([#11527](https://github.com/community-scripts/ProxmoxVE/pull/11527))
- WriteFreely ([#11524](https://github.com/community-scripts/ProxmoxVE/pull/11524))
### 💾 Core
- #### ✨ New Features
- core: create vm-core.func from dev [@MickLesk](https://github.com/MickLesk) ([#11528](https://github.com/community-scripts/ProxmoxVE/pull/11528))
### 🌐 Website
- #### 🐞 Bug Fixes
- fix(frontend): implement weighted search scoring for command menu [@ls-root](https://github.com/ls-root) ([#11534](https://github.com/community-scripts/ProxmoxVE/pull/11534))
## 2026-02-03
### 🆕 New Scripts
- Wealthfolio ([#11511](https://github.com/community-scripts/ProxmoxVE/pull/11511))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- [FIX] Shelfmark: unpin Chromium version [@vhsdream](https://github.com/vhsdream) ([#11505](https://github.com/community-scripts/ProxmoxVE/pull/11505))
- #### ✨ New Features
- [FEAT] Scanopy: automatically update integrated daemon [@vhsdream](https://github.com/vhsdream) ([#11506](https://github.com/community-scripts/ProxmoxVE/pull/11506))
### 💾 Core
- #### 🐞 Bug Fixes
- [FIX] tools.func: trim spaces in app_lc when checking for gh release [@vhsdream](https://github.com/vhsdream) ([#11512](https://github.com/community-scripts/ProxmoxVE/pull/11512))
### 🌐 Website
- #### 🐞 Bug Fixes
- fix(frontend): decouple table pagination from summary fetching [@ls-root](https://github.com/ls-root) ([#11495](https://github.com/community-scripts/ProxmoxVE/pull/11495))
## 2026-02-02
### 🆕 New Scripts
- rustypaste | Alpine-rustypaste ([#11457](https://github.com/community-scripts/ProxmoxVE/pull/11457))
- KitchenOwl ([#11453](https://github.com/community-scripts/ProxmoxVE/pull/11453))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Grist: Update dependencies [@tremor021](https://github.com/tremor021) ([#11489](https://github.com/community-scripts/ProxmoxVE/pull/11489))
- Allow "downgrade" of libigdgmm12 [@vhsdream](https://github.com/vhsdream) ([#11478](https://github.com/community-scripts/ProxmoxVE/pull/11478))
- Disable NPM install and update due to OpenResty SHA-1 signature issues [@MickLesk](https://github.com/MickLesk) ([#11471](https://github.com/community-scripts/ProxmoxVE/pull/11471))
- #### ✨ New Features
- Refactor: Forgejo & readeck - migrate to codeberg functions [@MickLesk](https://github.com/MickLesk) ([#11460](https://github.com/community-scripts/ProxmoxVE/pull/11460))
- #### 💥 Breaking Changes
- [FIX] Scanopy: remove daemon build [@vhsdream](https://github.com/vhsdream) ([#11444](https://github.com/community-scripts/ProxmoxVE/pull/11444))
- #### 🔧 Refactor
- Refactor: Vaultwarden [@MickLesk](https://github.com/MickLesk) ([#11445](https://github.com/community-scripts/ProxmoxVE/pull/11445))
- various scripts: use ensure_dependencies instead of apt [@MickLesk](https://github.com/MickLesk) ([#11463](https://github.com/community-scripts/ProxmoxVE/pull/11463))
### 🌐 Website
- cleanup(frontend): remove unused /category-view route [@ls-root](https://github.com/ls-root) ([#11461](https://github.com/community-scripts/ProxmoxVE/pull/11461))
- #### ✨ New Features
- feat(frontend): preview tab [@ls-root](https://github.com/ls-root) ([#11475](https://github.com/community-scripts/ProxmoxVE/pull/11475))
## 2026-02-01
### 🚀 Updated Scripts
- fix headers [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11422](https://github.com/community-scripts/ProxmoxVE/pull/11422))
- #### 🐞 Bug Fixes
- 2fauth: export PHP_VERSION for nginx config [@MickLesk](https://github.com/MickLesk) ([#11441](https://github.com/community-scripts/ProxmoxVE/pull/11441))
- Prometheus Paperless NGX Exporter: Set correct binary path in systemd unit file [@andygrunwald](https://github.com/andygrunwald) ([#11438](https://github.com/community-scripts/ProxmoxVE/pull/11438))
- tracearr: install/update new prestart script from upstream [@durzo](https://github.com/durzo) ([#11433](https://github.com/community-scripts/ProxmoxVE/pull/11433))
- n8n: Fix dependencies [@tremor021](https://github.com/tremor021) ([#11429](https://github.com/community-scripts/ProxmoxVE/pull/11429))
- [Hotfix] Bunkerweb update [@vhsdream](https://github.com/vhsdream) ([#11402](https://github.com/community-scripts/ProxmoxVE/pull/11402))
- [Hotfix] Immich: revert healthcheck feature [@vhsdream](https://github.com/vhsdream) ([#11427](https://github.com/community-scripts/ProxmoxVE/pull/11427))
- #### ✨ New Features
- tools.func: add codeberg functions & autocaliweb: migrate from GitHub to Codeberg [@MickLesk](https://github.com/MickLesk) ([#11440](https://github.com/community-scripts/ProxmoxVE/pull/11440))
- Immich Refactor #2 [@vhsdream](https://github.com/vhsdream) ([#11375](https://github.com/community-scripts/ProxmoxVE/pull/11375))
- #### 🔧 Refactor
- WordPress: Refactor [@tremor021](https://github.com/tremor021) ([#11408](https://github.com/community-scripts/ProxmoxVE/pull/11408))
- Refactor: Whisparr [@tremor021](https://github.com/tremor021) ([#11411](https://github.com/community-scripts/ProxmoxVE/pull/11411))
### 💾 Core
- #### ✨ New Features
- [tools]: Update `fetch_and_deply_from_url()` [@tremor021](https://github.com/tremor021) ([#11410](https://github.com/community-scripts/ProxmoxVE/pull/11410))
### 🌐 Website
- feat(frontend): implement UX refinements and syntax highlighting [@ls-root](https://github.com/ls-root) ([#11423](https://github.com/community-scripts/ProxmoxVE/pull/11423))
- #### ✨ New Features
- feat(frontend): add contribution CTA to empty search state [@ls-root](https://github.com/ls-root) ([#11412](https://github.com/community-scripts/ProxmoxVE/pull/11412))

View File

@@ -54,9 +54,8 @@ jobs:
console.log(`Cutoff date: ${cutoffDate.toISOString().split('T')[0]}`); console.log(`Cutoff date: ${cutoffDate.toISOString().split('T')[0]}`);
// Read changelog and normalize line endings // Read changelog
let content = await fs.readFile(CHANGELOG_PATH, 'utf-8'); const content = await fs.readFile(CHANGELOG_PATH, 'utf-8');
content = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
const lines = content.split('\n'); const lines = content.split('\n');
// Parse entries // Parse entries
@@ -67,39 +66,9 @@ jobs:
let currentDate = null; let currentDate = null;
let currentContent = []; let currentContent = [];
let inHeader = true; let inHeader = true;
let inOldHistory = false;
let historyDetailsDepth = 0;
for (let i = 0; i < lines.length; i++) { for (const line of lines) {
const line = lines[i];
const match = line.match(datePattern); const match = line.match(datePattern);
// Detect the start of History section: <details> followed by line with 📜 History
if (inHeader && !inOldHistory && line.trim() === '<details>') {
// Look ahead to see if this is the History section
const nextLine = lines[i + 1] || '';
if (nextLine.includes('📜 History')) {
inOldHistory = true;
historyDetailsDepth = 1;
continue;
}
}
// Track nested details tags to find the end of History section
if (inOldHistory) {
if (line.trim() === '<details>') {
historyDetailsDepth++;
}
if (line.trim() === '</details>') {
historyDetailsDepth--;
if (historyDetailsDepth === 0) {
// We've closed the main History details tag
inOldHistory = false;
}
}
continue;
}
if (match) { if (match) {
inHeader = false; inHeader = false;
@@ -179,55 +148,30 @@ jobs:
let existingContent = ''; let existingContent = '';
try { try {
existingContent = await fs.readFile(monthPath, 'utf-8'); existingContent = await fs.readFile(monthPath, 'utf-8');
// Normalize line endings to prevent regex issues
existingContent = existingContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
} catch (e) { } catch (e) {
// File doesn't exist // File doesn't exist
} }
// Parse existing entries into a Map (date -> content) for deduplication // Merge new entries with existing (avoid duplicates)
const allEntries = new Map(); const existingDates = new Set();
const existingDatePattern = /^## (\d{4}-\d{2}-\d{2})$/gm;
// Helper function to parse entries from content let match;
const parseEntries = (content) => { while ((match = existingDatePattern.exec(existingContent)) !== null) {
const entries = new Map(); existingDates.add(match[1]);
const parts = content.split(/(?=^## \d{4}-\d{2}-\d{2}$)/m);
for (const part of parts) {
const trimmed = part.trim();
if (!trimmed) continue;
const dateMatch = trimmed.match(/^## (\d{4}-\d{2}-\d{2})/);
if (dateMatch) {
entries.set(dateMatch[1], trimmed);
}
}
return entries;
};
// Parse existing content
if (existingContent) {
const existingEntries = parseEntries(existingContent);
for (const [date, content] of existingEntries) {
allEntries.set(date, content);
}
} }
// Add new entries (existing entries take precedence to avoid overwriting) const newEntries = archiveData[year][month].filter(entry => {
let addedCount = 0;
for (const entry of archiveData[year][month]) {
const dateMatch = entry.match(/^## (\d{4}-\d{2}-\d{2})/); const dateMatch = entry.match(/^## (\d{4}-\d{2}-\d{2})/);
if (dateMatch && !allEntries.has(dateMatch[1])) { return dateMatch && !existingDates.has(dateMatch[1]);
allEntries.set(dateMatch[1], entry.trim()); });
addedCount++;
}
}
// Sort entries by date (newest first) and write if (newEntries.length > 0) {
const sortedDates = [...allEntries.keys()].sort().reverse(); const allContent = existingContent
const sortedContent = sortedDates.map(date => allEntries.get(date)).join('\n\n'); ? existingContent + '\n\n' + newEntries.join('\n\n')
: newEntries.join('\n\n');
if (addedCount > 0 || !existingContent) { await fs.writeFile(monthPath, allContent, 'utf-8');
await fs.writeFile(monthPath, sortedContent + '\n', 'utf-8'); console.log(`Updated: ${monthPath} (+${newEntries.length} entries)`);
console.log(`Updated: ${monthPath} (${allEntries.size} total entries, +${addedCount} new)`);
} }
} }
} }
@@ -274,8 +218,7 @@ jobs:
// Count entries in month file // Count entries in month file
let entryCount = 0; let entryCount = 0;
try { try {
let monthContent = await fs.readFile(monthPath, 'utf-8'); const monthContent = await fs.readFile(monthPath, 'utf-8');
monthContent = monthContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
entryCount = (monthContent.match(/^## \d{4}-\d{2}-\d{2}$/gm) || []).length; entryCount = (monthContent.match(/^## \d{4}-\d{2}-\d{2}$/gm) || []).length;
} catch (e) { } catch (e) {
entryCount = (archiveData[year]?.[month] || []).length; entryCount = (archiveData[year]?.[month] || []).length;

81
.github/workflows/lock-issue.yaml generated vendored
View File

@@ -18,6 +18,7 @@ jobs:
with: with:
script: | script: |
const daysBeforeLock = 3; const daysBeforeLock = 3;
const cutoffDate = new Date('2026-01-27T00:00:00Z');
const lockDate = new Date(); const lockDate = new Date();
lockDate.setDate(lockDate.getDate() - daysBeforeLock); lockDate.setDate(lockDate.getDate() - daysBeforeLock);
@@ -28,50 +29,48 @@ jobs:
/dependabot/i /dependabot/i
]; ];
// Search for closed, unlocked issues older than 3 days (paginated, oldest first) // Search for closed, unlocked issues older than 3 days
let page = 1; const issues = await github.rest.search.issuesAndPullRequests({
let totalLocked = 0; q: `repo:${context.repo.owner}/${context.repo.repo} is:closed is:unlocked updated:<${lockDate.toISOString().split('T')[0]}`,
per_page: 50
});
while (true) { console.log(`Found ${issues.data.items.length} issues/PRs to process`);
const issues = await github.rest.search.issuesAndPullRequests({
q: `repo:${context.repo.owner}/${context.repo.repo} is:closed is:unlocked updated:<${lockDate.toISOString().split('T')[0]}`,
sort: 'updated',
order: 'asc',
per_page: 100,
page: page
});
if (issues.data.items.length === 0) break; for (const item of issues.data.items) {
// Skip excluded items
console.log(`Page ${page}: ${issues.data.items.length} items (total available: ${issues.data.total_count})`); const shouldExclude = excludePatterns.some(pattern => pattern.test(item.title));
if (shouldExclude) {
for (const item of issues.data.items) { console.log(`Skipped #${item.number}: "${item.title}" (matches exclude pattern)`);
// Skip excluded items continue;
const shouldExclude = excludePatterns.some(pattern => pattern.test(item.title));
if (shouldExclude) {
console.log(`Skipped #${item.number}: "${item.title}" (matches exclude pattern)`);
continue;
}
try {
// Lock the issue/PR silently
await github.rest.issues.lock({
...context.repo,
issue_number: item.number,
lock_reason: 'resolved'
});
totalLocked++;
console.log(`Locked #${item.number} (${item.pull_request ? 'PR' : 'Issue'})`);
} catch (error) {
console.log(`Failed to lock #${item.number}: ${error.message}`);
}
} }
page++; const createdAt = new Date(item.created_at);
const isNew = createdAt >= cutoffDate;
// GitHub search API limit: max 10000 results (100 pages * 100) - temporarily increased try {
if (page > 100) break; // Add comment only for new issues (created after 2026-01-27)
if (isNew) {
const comment = item.pull_request
? 'This pull request has been automatically locked. Please open a new issue for related bugs.'
: 'This issue has been automatically locked. Please open a new issue for related bugs and reference this issue if needed.';
await github.rest.issues.createComment({
...context.repo,
issue_number: item.number,
body: comment
});
}
// Lock the issue/PR
await github.rest.issues.lock({
...context.repo,
issue_number: item.number,
lock_reason: 'resolved'
});
console.log(`Locked #${item.number} (${item.pull_request ? 'PR' : 'Issue'})`);
} catch (error) {
console.log(`Failed to lock #${item.number}: ${error.message}`);
}
} }
console.log(`Total locked: ${totalLocked} issues/PRs`);

View File

@@ -21,14 +21,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
<details> <details>
<summary><h4>February (4 entries)</h4></summary> <summary><h4>January - 27 entries</h4></summary>
[View February 2026 Changelog](.github/changelogs/2026/02.md)
</details>
<details>
<summary><h4>January (31 entries)</h4></summary>
[View January 2026 Changelog](.github/changelogs/2026/01.md) [View January 2026 Changelog](.github/changelogs/2026/01.md)
@@ -398,131 +391,386 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details> </details>
## 2026-02-05
### 🆕 New Scripts
- New: SQL-Server 2025 | Refactor SQL-Server 2022 [@MickLesk](https://github.com/MickLesk) ([#11546](https://github.com/community-scripts/ProxmoxVE/pull/11546)) <details>
<summary><h2>📜 History</h2></summary>
### 🚀 Updated Scripts
- #### ✨ New Features <details>
<summary><h3>2026</h3></summary>
- Refactor: Docker-VM (Multi-OS / Cloud-Init / Stabilization) [@MickLesk](https://github.com/MickLesk) ([#9047](https://github.com/community-scripts/ProxmoxVE/pull/9047))
### 💾 Core <details>
<summary><h4>January (31 entries)</h4></summary>
- #### ✨ New Features [View January 2026 Changelog](.github/changelogs/2026/01.md)
- cloud-init: add interactive SSH key discovery and selection [@MickLesk](https://github.com/MickLesk) ([#11547](https://github.com/community-scripts/ProxmoxVE/pull/11547)) </details>
### 🌐 Website </details>
- #### 🐞 Bug Fixes <details>
<summary><h3>2025</h3></summary>
- fix(frontend): theme respective syntax highlighting [@ls-root](https://github.com/ls-root) ([#11565](https://github.com/community-scripts/ProxmoxVE/pull/11565))
## 2026-02-04 <details>
<summary><h4>December (31 entries)</h4></summary>
### 🆕 New Scripts [View December 2025 Changelog](.github/changelogs/2025/12.md)
- Wishlist ([#11527](https://github.com/community-scripts/ProxmoxVE/pull/11527)) </details>
- WriteFreely ([#11524](https://github.com/community-scripts/ProxmoxVE/pull/11524))
### 🚀 Updated Scripts <details>
<summary><h4>November (29 entries)</h4></summary>
- Add log directory and permissions for koillection [@shineangelic](https://github.com/shineangelic) ([#11553](https://github.com/community-scripts/ProxmoxVE/pull/11553)) [View November 2025 Changelog](.github/changelogs/2025/11.md)
- #### 🐞 Bug Fixes </details>
- [FIX] Scanopy: ensure Scanopy Daemon update [@vhsdream](https://github.com/vhsdream) ([#11541](https://github.com/community-scripts/ProxmoxVE/pull/11541)) <details>
- Immich: pin version to 2.5.3 [@vhsdream](https://github.com/vhsdream) ([#11515](https://github.com/community-scripts/ProxmoxVE/pull/11515)) <summary><h4>October (30 entries)</h4></summary>
### 💾 Core [View October 2025 Changelog](.github/changelogs/2025/10.md)
- #### ✨ New Features </details>
- core: create vm-core.func from dev [@MickLesk](https://github.com/MickLesk) ([#11528](https://github.com/community-scripts/ProxmoxVE/pull/11528)) <details>
<summary><h4>September (29 entries)</h4></summary>
### 🧰 Tools [View September 2025 Changelog](.github/changelogs/2025/09.md)
- #### ✨ New Features </details>
- [ADDON] Immich Public Proxy addon [@vhsdream](https://github.com/vhsdream) ([#11518](https://github.com/community-scripts/ProxmoxVE/pull/11518)) <details>
<summary><h4>August (30 entries)</h4></summary>
### 🌐 Website [View August 2025 Changelog](.github/changelogs/2025/08.md)
- #### 🐞 Bug Fixes </details>
- fix(frontend): implement weighted search scoring for command menu [@ls-root](https://github.com/ls-root) ([#11534](https://github.com/community-scripts/ProxmoxVE/pull/11534)) <details>
<summary><h4>July (29 entries)</h4></summary>
### ❔ Uncategorized [View July 2025 Changelog](.github/changelogs/2025/07.md)
- [FIX] Immich Public Proxy docs link [@vhsdream](https://github.com/vhsdream) ([#11543](https://github.com/community-scripts/ProxmoxVE/pull/11543)) </details>
## 2026-02-03 <details>
<summary><h4>June (29 entries)</h4></summary>
### 🆕 New Scripts [View June 2025 Changelog](.github/changelogs/2025/06.md)
- Wealthfolio ([#11511](https://github.com/community-scripts/ProxmoxVE/pull/11511)) </details>
### 🚀 Updated Scripts <details>
<summary><h4>May (30 entries)</h4></summary>
- #### 🐞 Bug Fixes [View May 2025 Changelog](.github/changelogs/2025/05.md)
- [FIX] Shelfmark: unpin Chromium version [@vhsdream](https://github.com/vhsdream) ([#11505](https://github.com/community-scripts/ProxmoxVE/pull/11505)) </details>
- #### ✨ New Features <details>
<summary><h4>April (25 entries)</h4></summary>
- [FEAT] Scanopy: automatically update integrated daemon [@vhsdream](https://github.com/vhsdream) ([#11506](https://github.com/community-scripts/ProxmoxVE/pull/11506)) [View April 2025 Changelog](.github/changelogs/2025/04.md)
### 💾 Core </details>
- #### 🐞 Bug Fixes <details>
<summary><h4>March (30 entries)</h4></summary>
- [FIX] tools.func: trim spaces in app_lc when checking for gh release [@vhsdream](https://github.com/vhsdream) ([#11512](https://github.com/community-scripts/ProxmoxVE/pull/11512)) [View March 2025 Changelog](.github/changelogs/2025/03.md)
### 🌐 Website </details>
- #### 🐞 Bug Fixes <details>
<summary><h4>February (26 entries)</h4></summary>
- fix(frontend): decouple table pagination from summary fetching [@ls-root](https://github.com/ls-root) ([#11495](https://github.com/community-scripts/ProxmoxVE/pull/11495)) [View February 2025 Changelog](.github/changelogs/2025/02.md)
## 2026-02-02 </details>
### 🆕 New Scripts <details>
<summary><h4>January (27 entries)</h4></summary>
- rustypaste | Alpine-rustypaste ([#11457](https://github.com/community-scripts/ProxmoxVE/pull/11457)) [View January 2025 Changelog](.github/changelogs/2025/01.md)
- KitchenOwl ([#11453](https://github.com/community-scripts/ProxmoxVE/pull/11453))
### 🚀 Updated Scripts </details>
- #### 🐞 Bug Fixes </details>
- Grist: Update dependencies [@tremor021](https://github.com/tremor021) ([#11489](https://github.com/community-scripts/ProxmoxVE/pull/11489)) <details>
- Allow "downgrade" of libigdgmm12 [@vhsdream](https://github.com/vhsdream) ([#11478](https://github.com/community-scripts/ProxmoxVE/pull/11478)) <summary><h3>2024</h3></summary>
- Disable NPM install and update due to OpenResty SHA-1 signature issues [@MickLesk](https://github.com/MickLesk) ([#11471](https://github.com/community-scripts/ProxmoxVE/pull/11471))
- #### ✨ New Features
- Refactor: Forgejo & readeck - migrate to codeberg functions [@MickLesk](https://github.com/MickLesk) ([#11460](https://github.com/community-scripts/ProxmoxVE/pull/11460)) <details>
<summary><h4>December (22 entries)</h4></summary>
- #### 💥 Breaking Changes [View December 2024 Changelog](.github/changelogs/2024/12.md)
- [FIX] Scanopy: remove daemon build [@vhsdream](https://github.com/vhsdream) ([#11444](https://github.com/community-scripts/ProxmoxVE/pull/11444)) </details>
- #### 🔧 Refactor <details>
<summary><h4>November (15 entries)</h4></summary>
- Refactor: Vaultwarden [@MickLesk](https://github.com/MickLesk) ([#11445](https://github.com/community-scripts/ProxmoxVE/pull/11445)) [View November 2024 Changelog](.github/changelogs/2024/11.md)
- various scripts: use ensure_dependencies instead of apt [@MickLesk](https://github.com/MickLesk) ([#11463](https://github.com/community-scripts/ProxmoxVE/pull/11463))
### 🌐 Website </details>
- cleanup(frontend): remove unused /category-view route [@ls-root](https://github.com/ls-root) ([#11461](https://github.com/community-scripts/ProxmoxVE/pull/11461)) <details>
<summary><h4>October (9 entries)</h4></summary>
- #### ✨ New Features [View October 2024 Changelog](.github/changelogs/2024/10.md)
- feat(frontend): preview tab [@ls-root](https://github.com/ls-root) ([#11475](https://github.com/community-scripts/ProxmoxVE/pull/11475)) </details>
<details>
<summary><h4>September (1 entries)</h4></summary>
[View September 2024 Changelog](.github/changelogs/2024/09.md)
</details>
<details>
<summary><h4>August (2 entries)</h4></summary>
[View August 2024 Changelog](.github/changelogs/2024/08.md)
</details>
<details>
<summary><h4>July (0 entries)</h4></summary>
[View July 2024 Changelog](.github/changelogs/2024/07.md)
</details>
<details>
<summary><h4>June (8 entries)</h4></summary>
[View June 2024 Changelog](.github/changelogs/2024/06.md)
</details>
<details>
<summary><h4>May (16 entries)</h4></summary>
[View May 2024 Changelog](.github/changelogs/2024/05.md)
</details>
<details>
<summary><h4>April (14 entries)</h4></summary>
[View April 2024 Changelog](.github/changelogs/2024/04.md)
</details>
<details>
<summary><h4>March (5 entries)</h4></summary>
[View March 2024 Changelog](.github/changelogs/2024/03.md)
</details>
<details>
<summary><h4>February (9 entries)</h4></summary>
[View February 2024 Changelog](.github/changelogs/2024/02.md)
</details>
<details>
<summary><h4>January (9 entries)</h4></summary>
[View January 2024 Changelog](.github/changelogs/2024/01.md)
</details>
</details>
<details>
<summary><h3>2023</h3></summary>
<details>
<summary><h4>December (3 entries)</h4></summary>
[View December 2023 Changelog](.github/changelogs/2023/12.md)
</details>
<details>
<summary><h4>November (3 entries)</h4></summary>
[View November 2023 Changelog](.github/changelogs/2023/11.md)
</details>
<details>
<summary><h4>October (7 entries)</h4></summary>
[View October 2023 Changelog](.github/changelogs/2023/10.md)
</details>
<details>
<summary><h4>September (10 entries)</h4></summary>
[View September 2023 Changelog](.github/changelogs/2023/09.md)
</details>
<details>
<summary><h4>August (7 entries)</h4></summary>
[View August 2023 Changelog](.github/changelogs/2023/08.md)
</details>
<details>
<summary><h4>July (5 entries)</h4></summary>
[View July 2023 Changelog](.github/changelogs/2023/07.md)
</details>
<details>
<summary><h4>June (5 entries)</h4></summary>
[View June 2023 Changelog](.github/changelogs/2023/06.md)
</details>
<details>
<summary><h4>May (8 entries)</h4></summary>
[View May 2023 Changelog](.github/changelogs/2023/05.md)
</details>
<details>
<summary><h4>April (8 entries)</h4></summary>
[View April 2023 Changelog](.github/changelogs/2023/04.md)
</details>
<details>
<summary><h4>March (8 entries)</h4></summary>
[View March 2023 Changelog](.github/changelogs/2023/03.md)
</details>
<details>
<summary><h4>February (6 entries)</h4></summary>
[View February 2023 Changelog](.github/changelogs/2023/02.md)
</details>
<details>
<summary><h4>January (15 entries)</h4></summary>
[View January 2023 Changelog](.github/changelogs/2023/01.md)
</details>
</details>
<details>
<summary><h3>2022</h3></summary>
<details>
<summary><h4>December (7 entries)</h4></summary>
[View December 2022 Changelog](.github/changelogs/2022/12.md)
</details>
<details>
<summary><h4>November (7 entries)</h4></summary>
[View November 2022 Changelog](.github/changelogs/2022/11.md)
</details>
<details>
<summary><h4>October (2 entries)</h4></summary>
[View October 2022 Changelog](.github/changelogs/2022/10.md)
</details>
<details>
<summary><h4>September (9 entries)</h4></summary>
[View September 2022 Changelog](.github/changelogs/2022/09.md)
</details>
<details>
<summary><h4>August (7 entries)</h4></summary>
[View August 2022 Changelog](.github/changelogs/2022/08.md)
</details>
<details>
<summary><h4>July (10 entries)</h4></summary>
[View July 2022 Changelog](.github/changelogs/2022/07.md)
</details>
<details>
<summary><h4>June (1 entries)</h4></summary>
[View June 2022 Changelog](.github/changelogs/2022/06.md)
</details>
<details>
<summary><h4>May (8 entries)</h4></summary>
[View May 2022 Changelog](.github/changelogs/2022/05.md)
</details>
<details>
<summary><h4>April (13 entries)</h4></summary>
[View April 2022 Changelog](.github/changelogs/2022/04.md)
</details>
<details>
<summary><h4>March (20 entries)</h4></summary>
[View March 2022 Changelog](.github/changelogs/2022/03.md)
</details>
<details>
<summary><h4>February (15 entries)</h4></summary>
[View February 2022 Changelog](.github/changelogs/2022/02.md)
</details>
<details>
<summary><h4>January (3 entries)</h4></summary>
[View January 2022 Changelog](.github/changelogs/2022/01.md)
</details>
</details>
</details>
## 2026-02-01 ## 2026-02-01
@@ -532,16 +780,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🐞 Bug Fixes - #### 🐞 Bug Fixes
- 2fauth: export PHP_VERSION for nginx config [@MickLesk](https://github.com/MickLesk) ([#11441](https://github.com/community-scripts/ProxmoxVE/pull/11441))
- Prometheus Paperless NGX Exporter: Set correct binary path in systemd unit file [@andygrunwald](https://github.com/andygrunwald) ([#11438](https://github.com/community-scripts/ProxmoxVE/pull/11438))
- tracearr: install/update new prestart script from upstream [@durzo](https://github.com/durzo) ([#11433](https://github.com/community-scripts/ProxmoxVE/pull/11433))
- n8n: Fix dependencies [@tremor021](https://github.com/tremor021) ([#11429](https://github.com/community-scripts/ProxmoxVE/pull/11429)) - n8n: Fix dependencies [@tremor021](https://github.com/tremor021) ([#11429](https://github.com/community-scripts/ProxmoxVE/pull/11429))
- [Hotfix] Bunkerweb update [@vhsdream](https://github.com/vhsdream) ([#11402](https://github.com/community-scripts/ProxmoxVE/pull/11402)) - [Hotfix] Bunkerweb update [@vhsdream](https://github.com/vhsdream) ([#11402](https://github.com/community-scripts/ProxmoxVE/pull/11402))
- [Hotfix] Immich: revert healthcheck feature [@vhsdream](https://github.com/vhsdream) ([#11427](https://github.com/community-scripts/ProxmoxVE/pull/11427)) - [Hotfix] Immich: revert healthcheck feature [@vhsdream](https://github.com/vhsdream) ([#11427](https://github.com/community-scripts/ProxmoxVE/pull/11427))
- #### ✨ New Features - #### ✨ New Features
- tools.func: add codeberg functions & autocaliweb: migrate from GitHub to Codeberg [@MickLesk](https://github.com/MickLesk) ([#11440](https://github.com/community-scripts/ProxmoxVE/pull/11440))
- Immich Refactor #2 [@vhsdream](https://github.com/vhsdream) ([#11375](https://github.com/community-scripts/ProxmoxVE/pull/11375)) - Immich Refactor #2 [@vhsdream](https://github.com/vhsdream) ([#11375](https://github.com/community-scripts/ProxmoxVE/pull/11375))
- #### 🔧 Refactor - #### 🔧 Refactor
@@ -1313,3 +1557,64 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🔧 Refactor - #### 🔧 Refactor
- Refactor: IP-Tag (Multiple IP / Performance / Execution Time) [@MickLesk](https://github.com/MickLesk) ([#10558](https://github.com/community-scripts/ProxmoxVE/pull/10558)) - Refactor: IP-Tag (Multiple IP / Performance / Execution Time) [@MickLesk](https://github.com/MickLesk) ([#10558](https://github.com/community-scripts/ProxmoxVE/pull/10558))
## 2026-01-04
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- PocketID: Update PocketID for 2.x [@tremor021](https://github.com/tremor021) ([#10506](https://github.com/community-scripts/ProxmoxVE/pull/10506))
- fix: reitti: nginx [@CrazyWolf13](https://github.com/CrazyWolf13) ([#10511](https://github.com/community-scripts/ProxmoxVE/pull/10511))
- MagicMirror: bump to nodejs 24 [@MickLesk](https://github.com/MickLesk) ([#10534](https://github.com/community-scripts/ProxmoxVE/pull/10534))
- #### 🔧 Refactor
- Refactor: SFTPGo [@tremor021](https://github.com/tremor021) ([#10518](https://github.com/community-scripts/ProxmoxVE/pull/10518))
- Refactor: Pelican Wings [@tremor021](https://github.com/tremor021) ([#10517](https://github.com/community-scripts/ProxmoxVE/pull/10517))
- Refactor: Pelican Panel [@tremor021](https://github.com/tremor021) ([#10516](https://github.com/community-scripts/ProxmoxVE/pull/10516))
- Refactor: Audiobookshelf [@tremor021](https://github.com/tremor021) ([#10519](https://github.com/community-scripts/ProxmoxVE/pull/10519))
### 💾 Core
- #### 🐞 Bug Fixes
- Export IPV6_METHOD to trigger verb_ip6() function [@remz1337](https://github.com/remz1337) ([#10538](https://github.com/community-scripts/ProxmoxVE/pull/10538))
### 🌐 Website
- #### 📝 Script Information
- Prowlarr: Update config_path [@tremor021](https://github.com/tremor021) ([#10504](https://github.com/community-scripts/ProxmoxVE/pull/10504))
## 2026-01-03
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Fix ownership and permissions for InvoiceNinja setup [@twinzdragonz](https://github.com/twinzdragonz) ([#10298](https://github.com/community-scripts/ProxmoxVE/pull/10298))
- Fix headscale Caddyfile to pass non-API URLs [@IlyaSemenov](https://github.com/IlyaSemenov) ([#10493](https://github.com/community-scripts/ProxmoxVE/pull/10493))
### 💾 Core
- #### 🔧 Refactor
- [core]: Preserve log files [@tremor021](https://github.com/tremor021) ([#10509](https://github.com/community-scripts/ProxmoxVE/pull/10509))
### ❔ Uncategorized
- Wireguard: Update WGDashboard notes URL to the new link [@tremor021](https://github.com/tremor021) ([#10496](https://github.com/community-scripts/ProxmoxVE/pull/10496))
- InvoiceNinja: Update database credentias information [@tremor021](https://github.com/tremor021) ([#10497](https://github.com/community-scripts/ProxmoxVE/pull/10497))
## 2026-01-02
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Fix Intel Level Zero package conflict on Debian 13 [@Copilot](https://github.com/Copilot) ([#10467](https://github.com/community-scripts/ProxmoxVE/pull/10467))
### ❔ Uncategorized
- Extend guidance for changing the immich upload location for #10447 [@jshprentz](https://github.com/jshprentz) ([#10475](https://github.com/community-scripts/ProxmoxVE/pull/10475))

View File

@@ -27,7 +27,10 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
ensure_dependencies memcached libmemcached-tools if ! command -v memcached >/dev/null 2>&1; then
$STD apt update
$STD apt install -y memcached libmemcached-tools
fi
if check_for_gh_release "adventurelog" "seanmorley15/adventurelog"; then if check_for_gh_release "adventurelog" "seanmorley15/adventurelog"; then
msg_info "Stopping Services" msg_info "Stopping Services"
systemctl stop adventurelog-backend systemctl stop adventurelog-backend

View File

@@ -1,51 +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: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/orhun/rustypaste
APP="Alpine-RustyPaste"
var_tags="${var_tags:-alpine;pastebin;storage}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-256}"
var_disk="${var_disk:-4}"
var_os="${var_os:-alpine}"
var_version="${var_version:-3.23}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if ! apk info -e rustypaste >/dev/null 2>&1; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating RustyPaste"
$STD apk update
$STD apk upgrade rustypaste --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
msg_ok "Updated RustyPaste"
msg_info "Restarting Services"
$STD rc-service rustypaste restart
msg_ok "Restarted Services"
msg_ok "Updated successfully!"
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}:8000${CL}"

View File

@@ -31,7 +31,11 @@ function update_script() {
NODE_VERSION="22" NODE_MODULE="@postlight/parser@latest,single-file-cli@latest" setup_nodejs NODE_VERSION="22" NODE_MODULE="@postlight/parser@latest,single-file-cli@latest" setup_nodejs
PYTHON_VERSION="3.13" setup_uv PYTHON_VERSION="3.13" setup_uv
ensure_dependencies chromium if ! dpkg -l | grep -q "^ii chromium "; then
msg_info "Installing System Dependencies"
$STD apt-get install -y chromium
msg_ok "Installed System Dependencies"
fi
msg_info "Stopping Service" msg_info "Stopping Service"
systemctl stop archivebox systemctl stop archivebox

View File

@@ -3,7 +3,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
# Copyright (c) 2021-2026 community-scripts ORG # Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream # Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://codeberg.org/gelbphoenix/autocaliweb # Source: https://github.com/gelbphoenix/autocaliweb
APP="Autocaliweb" APP="Autocaliweb"
var_tags="${var_tags:-ebooks}" var_tags="${var_tags:-ebooks}"
@@ -30,8 +30,8 @@ function update_script() {
setup_uv setup_uv
RELEASE=$(get_latest_codeberg_release "gelbphoenix/autocaliweb") RELEASE=$(get_latest_github_release "gelbphoenix/autocaliweb")
if check_for_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb"; then if check_for_gh_release "autocaliweb" "gelbphoenix/autocaliweb"; then
msg_info "Stopping Services" msg_info "Stopping Services"
systemctl stop autocaliweb metadata-change-detector acw-ingest-service acw-auto-zipper systemctl stop autocaliweb metadata-change-detector acw-ingest-service acw-auto-zipper
msg_ok "Stopped Services" msg_ok "Stopped Services"
@@ -39,7 +39,7 @@ function update_script() {
INSTALL_DIR="/opt/autocaliweb" INSTALL_DIR="/opt/autocaliweb"
export VIRTUAL_ENV="${INSTALL_DIR}/venv" export VIRTUAL_ENV="${INSTALL_DIR}/venv"
$STD tar -cf ~/autocaliweb_bkp.tar "$INSTALL_DIR"/{metadata_change_logs,dirs.json,.env,scripts/ingest_watcher.sh,scripts/auto_zipper_wrapper.sh,scripts/metadata_change_detector_wrapper.sh} $STD tar -cf ~/autocaliweb_bkp.tar "$INSTALL_DIR"/{metadata_change_logs,dirs.json,.env,scripts/ingest_watcher.sh,scripts/auto_zipper_wrapper.sh,scripts/metadata_change_detector_wrapper.sh}
fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb" fetch_and_deploy_gh_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb"
msg_info "Updating Autocaliweb" msg_info "Updating Autocaliweb"
cd "$INSTALL_DIR" cd "$INSTALL_DIR"

View File

@@ -29,7 +29,12 @@ function update_script() {
exit exit
fi fi
ensure_dependencies libjpeg-dev if ! dpkg -s libjpeg-dev >/dev/null 2>&1; then
msg_info "Installing Dependencies"
$STD apt-get update
$STD apt-get install -y libjpeg-dev
msg_ok "Updated Dependencies"
fi
NODE_VERSION="24" setup_nodejs NODE_VERSION="24" setup_nodejs

View File

@@ -34,7 +34,12 @@ function update_script() {
systemctl stop commafeed systemctl stop commafeed
msg_ok "Stopped Service" msg_ok "Stopped Service"
ensure_dependencies rsync if ! [[ $(dpkg -s rsync 2>/dev/null) ]]; then
msg_info "Installing Dependencies"
$STD apt update
$STD apt install -y rsync
msg_ok "Installed Dependencies"
fi
if [ -d /opt/commafeed/data ] && [ "$(ls -A /opt/commafeed/data)" ]; then if [ -d /opt/commafeed/data ] && [ "$(ls -A /opt/commafeed/data)" ]; then
msg_info "Backing up existing data" msg_info "Backing up existing data"

View File

@@ -44,7 +44,10 @@ function update_script() {
NODE_VERSION="22" setup_nodejs NODE_VERSION="22" setup_nodejs
if check_for_gh_release "cronicle" "jhuckaby/Cronicle"; then if check_for_gh_release "cronicle" "jhuckaby/Cronicle"; then
msg_info "Installing Dependencies" msg_info "Installing Dependencies"
ensure_dependencies git build-essential ca-certificates $STD apt install -y \
git \
build-essential \
ca-certificates
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
NODE_VERSION="22" setup_nodejs NODE_VERSION="22" setup_nodejs

View File

@@ -38,7 +38,9 @@ function update_script() {
systemctl reload nginx systemctl reload nginx
fi fi
ensure_dependencies vlc-bin vlc-plugin-base if ! dpkg -s vlc-bin vlc-plugin-base &>/dev/null; then
$STD apt update && $STD apt install -y vlc-bin vlc-plugin-base
fi
if check_for_gh_release "Dispatcharr" "Dispatcharr/Dispatcharr"; then if check_for_gh_release "Dispatcharr" "Dispatcharr/Dispatcharr"; then
msg_info "Stopping Services" msg_info "Stopping Services"

View File

@@ -27,28 +27,35 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
if check_for_codeberg_release "forgejo" "forgejo/forgejo"; then msg_info "Stopping Service"
msg_info "Stopping Service" systemctl stop forgejo
systemctl stop forgejo msg_ok "Stopped Service"
msg_ok "Stopped Service"
fetch_and_deploy_codeberg_release "forgejo" "forgejo/forgejo" "singlefile" "latest" "/opt/forgejo" "forgejo-*-linux-amd64" msg_info "Updating ${APP}"
ln -sf /opt/forgejo/forgejo /usr/local/bin/forgejo RELEASE=$(curl -fsSL https://codeberg.org/api/v1/repos/forgejo/forgejo/releases/latest | grep -oP '"tag_name":\s*"\K[^"]+' | sed 's/^v//')
curl -fsSL "https://codeberg.org/forgejo/forgejo/releases/download/v${RELEASE}/forgejo-${RELEASE}-linux-amd64" -o "forgejo-$RELEASE-linux-amd64"
rm -rf /opt/forgejo/*
cp -r forgejo-$RELEASE-linux-amd64 /opt/forgejo/forgejo-$RELEASE-linux-amd64
chmod +x /opt/forgejo/forgejo-$RELEASE-linux-amd64
ln -sf /opt/forgejo/forgejo-$RELEASE-linux-amd64 /usr/local/bin/forgejo
msg_ok "Updated ${APP}"
if grep -q "GITEA_WORK_DIR" /etc/systemd/system/forgejo.service; then msg_info "Cleaning"
msg_info "Updating Service File" rm -rf forgejo-$RELEASE-linux-amd64
sed -i "s/GITEA_WORK_DIR/FORGEJO_WORK_DIR/g" /etc/systemd/system/forgejo.service msg_ok "Cleaned"
systemctl daemon-reload
msg_ok "Updated Service File"
fi
msg_info "Starting Service" # Fix env var from older version of community script
systemctl start forgejo if grep -q "GITEA_WORK_DIR" /etc/systemd/system/forgejo.service; then
msg_ok "Started Service" msg_info "Updating Service File"
msg_ok "Updated successfully!" sed -i "s/GITEA_WORK_DIR/FORGEJO_WORK_DIR/g" /etc/systemd/system/forgejo.service
else systemctl daemon-reload
msg_ok "No update required. ${APP} is already at the latest version." msg_ok "Updated Service File"
fi fi
msg_info "Starting Service"
systemctl start forgejo
msg_ok "Started Service"
msg_ok "Updated successfully!"
exit exit
} }

View File

@@ -43,7 +43,9 @@ function update_script() {
msg_error "Project directory does not exist: $PROJECT_DIR" msg_error "Project directory does not exist: $PROJECT_DIR"
exit exit
fi fi
ensure_dependencies git if ! command -v git &>/dev/null; then
$STD apt install -y git
fi
msg_info "Stopping service $SERVICE_NAME" msg_info "Stopping service $SERVICE_NAME"
systemctl stop "$SERVICE_NAME" systemctl stop "$SERVICE_NAME"

View File

@@ -45,7 +45,7 @@ function update_script() {
curl -fsSL "https://packages.graylog2.org/repo/packages/graylog-7.0-repository_latest.deb" -o "graylog-7.0-repository_latest.deb" curl -fsSL "https://packages.graylog2.org/repo/packages/graylog-7.0-repository_latest.deb" -o "graylog-7.0-repository_latest.deb"
$STD dpkg -i graylog-7.0-repository_latest.deb $STD dpkg -i graylog-7.0-repository_latest.deb
$STD apt update $STD apt update
ensure_dependencies graylog-server graylog-datanode $STD apt install -y graylog-server graylog-datanode
rm -f graylog-7.0-repository_latest.deb rm -f graylog-7.0-repository_latest.deb
msg_ok "Updated Graylog" msg_ok "Updated Graylog"
elif dpkg --compare-versions "$CURRENT_VERSION" ge "7.0"; then elif dpkg --compare-versions "$CURRENT_VERSION" ge "7.0"; then

View File

@@ -29,8 +29,6 @@ function update_script() {
exit exit
fi fi
ensure_dependencies git
if check_for_gh_release "grist" "gristlabs/grist-core"; then if check_for_gh_release "grist" "gristlabs/grist-core"; then
msg_info "Stopping Service" msg_info "Stopping Service"
systemctl stop grist systemctl stop grist

View File

@@ -1,6 +0,0 @@
___ __ _ ____ __ ____ __
/ | / /___ (_)___ ___ / __ \__ _______/ /___ __/ __ \____ ______/ /____
/ /| | / / __ \/ / __ \/ _ \______/ /_/ / / / / ___/ __/ / / / /_/ / __ `/ ___/ __/ _ \
/ ___ |/ / /_/ / / / / / __/_____/ _, _/ /_/ (__ ) /_/ /_/ / ____/ /_/ (__ ) /_/ __/
/_/ |_/_/ .___/_/_/ /_/\___/ /_/ |_|\__,_/____/\__/\__, /_/ \__,_/____/\__/\___/
/_/ /____/

View File

@@ -1,6 +0,0 @@
__ __ _ __ __ ____ __
/ //_/(_) /______/ /_ ___ ____ / __ \_ __/ /
/ ,< / / __/ ___/ __ \/ _ \/ __ \/ / / / | /| / / /
/ /| |/ / /_/ /__/ / / / __/ / / / /_/ /| |/ |/ / /
/_/ |_/_/\__/\___/_/ /_/\___/_/ /_/\____/ |__/|__/_/

View File

@@ -1,6 +0,0 @@
__ __
_______ _______/ /___ ______ ____ ______/ /____
/ ___/ / / / ___/ __/ / / / __ \/ __ `/ ___/ __/ _ \
/ / / /_/ (__ ) /_/ /_/ / /_/ / /_/ (__ ) /_/ __/
/_/ \__,_/____/\__/\__, / .___/\__,_/____/\__/\___/
/____/_/

View File

@@ -1,6 +0,0 @@
_____ ____ __ _____ ___ ____ ___ ______
/ ___// __ \ / / / ___/___ ______ _____ _____ |__ \ / __ \__ \ / ____/
\__ \/ / / / / / \__ \/ _ \/ ___/ | / / _ \/ ___/ __/ // / / /_/ //___ \
___/ / /_/ / / /___ ___/ / __/ / | |/ / __/ / / __// /_/ / __/____/ /
/____/\___\_\/_____/ /____/\___/_/ |___/\___/_/ /____/\____/____/_____/

View File

@@ -1,6 +0,0 @@
_ __ ____ __ ____ ___
| | / /__ ____ _/ / /_/ /_ / __/___ / (_)___
| | /| / / _ \/ __ `/ / __/ __ \/ /_/ __ \/ / / __ \
| |/ |/ / __/ /_/ / / /_/ / / / __/ /_/ / / / /_/ /
|__/|__/\___/\__,_/_/\__/_/ /_/_/ \____/_/_/\____/

View File

@@ -1,6 +0,0 @@
_ ___ __ ___ __
| | / (_)____/ /_ / (_)____/ /_
| | /| / / / ___/ __ \/ / / ___/ __/
| |/ |/ / (__ ) / / / / (__ ) /_
|__/|__/_/____/_/ /_/_/_/____/\__/

View File

@@ -1,6 +0,0 @@
_ __ _ __ ______ __
| | / /____(_) /____ / ____/_______ ___ / /_ __
| | /| / / ___/ / __/ _ \/ /_ / ___/ _ \/ _ \/ / / / /
| |/ |/ / / / / /_/ __/ __/ / / / __/ __/ / /_/ /
|__/|__/_/ /_/\__/\___/_/ /_/ \___/\___/_/\__, /
/____/

View File

@@ -30,7 +30,14 @@ function update_script() {
get_lxc_ip get_lxc_ip
NODE_VERSION="22" NODE_MODULE="pnpm@latest" setup_nodejs NODE_VERSION="22" NODE_MODULE="pnpm@latest" setup_nodejs
ensure_dependencies jq if ! command -v jq &>/dev/null; then
$STD msg_info "Installing jq..."
$STD apt-get update -qq &>/dev/null
$STD apt-get install -y jq &>/dev/null || {
msg_error "Failed to install jq"
exit
}
fi
if check_for_gh_release "homepage" "gethomepage/homepage"; then if check_for_gh_release "homepage" "gethomepage/homepage"; then
msg_info "Stopping service" msg_info "Stopping service"

View File

@@ -36,6 +36,10 @@ function update_script() {
exit exit
fi fi
setup_uv
PNPM_VERSION="$(curl -fsSL "https://raw.githubusercontent.com/immich-app/immich/refs/heads/main/package.json" | jq -r '.packageManager | split("@")[1]')"
NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs
if [[ ! -f /etc/apt/preferences.d/preferences ]]; then if [[ ! -f /etc/apt/preferences.d/preferences ]]; then
msg_info "Adding Debian Testing repo" msg_info "Adding Debian Testing repo"
sed -i 's/ trixie-updates/ trixie-updates testing/g' /etc/apt/sources.list.d/debian.sources sed -i 's/ trixie-updates/ trixie-updates testing/g' /etc/apt/sources.list.d/debian.sources
@@ -63,7 +67,8 @@ EOF
msg_info "Installing Mise" msg_info "Installing Mise"
curl -fSs https://mise.jdx.dev/gpg-key.pub | tee /etc/apt/keyrings/mise-archive-keyring.pub 1>/dev/null curl -fSs https://mise.jdx.dev/gpg-key.pub | tee /etc/apt/keyrings/mise-archive-keyring.pub 1>/dev/null
echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=amd64] https://mise.jdx.dev/deb stable main" >/etc/apt/sources.list.d/mise.list echo "deb [signed-by=/etc/apt/keyrings/mise-archive-keyring.pub arch=amd64] https://mise.jdx.dev/deb stable main" >/etc/apt/sources.list.d/mise.list
ensure_dependencies mise $STD apt update
$STD apt install -y mise
msg_ok "Installed Mise" msg_ok "Installed Mise"
fi fi
@@ -84,7 +89,7 @@ EOF
curl -fsSLO "$url" curl -fsSLO "$url"
done done
$STD apt-mark unhold libigdgmm12 $STD apt-mark unhold libigdgmm12
$STD apt install -y --allow-downgrades ./libigdgmm12*.deb $STD apt install -y ./libigdgmm12*.deb
rm ./libigdgmm12*.deb rm ./libigdgmm12*.deb
$STD apt install -y ./*.deb $STD apt install -y ./*.deb
rm ./*.deb rm ./*.deb
@@ -105,7 +110,7 @@ EOF
msg_ok "Image-processing libraries up to date" msg_ok "Image-processing libraries up to date"
fi fi
RELEASE="2.5.3" RELEASE="2.5.2"
if check_for_gh_release "immich" "immich-app/immich" "${RELEASE}"; then if check_for_gh_release "immich" "immich-app/immich" "${RELEASE}"; then
if [[ $(cat ~/.immich) > "2.5.1" ]]; then if [[ $(cat ~/.immich) > "2.5.1" ]]; then
msg_info "Enabling Maintenance Mode" msg_info "Enabling Maintenance Mode"
@@ -129,7 +134,9 @@ EOF
$STD sudo -u postgres psql -d immich -c "REINDEX INDEX face_index;" $STD sudo -u postgres psql -d immich -c "REINDEX INDEX face_index;"
$STD sudo -u postgres psql -d immich -c "REINDEX INDEX clip_index;" $STD sudo -u postgres psql -d immich -c "REINDEX INDEX clip_index;"
fi fi
ensure_dependencies ccache if ! dpkg -l | grep -q ccache; then
$STD apt install -yqq ccache
fi
INSTALL_DIR="/opt/${APP}" INSTALL_DIR="/opt/${APP}"
UPLOAD_DIR="$(sed -n '/^IMMICH_MEDIA_LOCATION/s/[^=]*=//p' /opt/immich/.env)" UPLOAD_DIR="$(sed -n '/^IMMICH_MEDIA_LOCATION/s/[^=]*=//p' /opt/immich/.env)"
@@ -160,10 +167,7 @@ EOF
rm -rf "${APP_DIR:?}"/* rm -rf "${APP_DIR:?}"/*
) )
setup_uv
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "immich" "immich-app/immich" "tarball" "v${RELEASE}" "$SRC_DIR" CLEAN_INSTALL=1 fetch_and_deploy_gh_release "immich" "immich-app/immich" "tarball" "v${RELEASE}" "$SRC_DIR"
PNPM_VERSION="$(jq -r '.packageManager | split("@")[1]' ${SRC_DIR}/package.json)"
NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs
msg_info "Updating Immich web and microservices" msg_info "Updating Immich web and microservices"
cd "$SRC_DIR"/server cd "$SRC_DIR"/server
@@ -300,7 +304,10 @@ function compile_libjxl() {
function compile_libheif() { function compile_libheif() {
SOURCE=${SOURCE_DIR}/libheif SOURCE=${SOURCE_DIR}/libheif
ensure_dependencies libaom-dev if ! dpkg -l | grep -q libaom; then
$STD apt install -y libaom-dev
local update="required"
fi
: "${LIBHEIF_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libheif.json)}" : "${LIBHEIF_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libheif.json)}"
if [[ "${update:-}" ]] || [[ "$LIBHEIF_REVISION" != "$(grep 'libheif' ~/.immich_library_revisions | awk '{print $2}')" ]]; then if [[ "${update:-}" ]] || [[ "$LIBHEIF_REVISION" != "$(grep 'libheif' ~/.immich_library_revisions | awk '{print $2}')" ]]; then
msg_info "Recompiling libheif" msg_info "Recompiling libheif"

View File

@@ -40,7 +40,9 @@ function update_script() {
fi fi
msg_info "Updating Jellyfin" msg_info "Updating Jellyfin"
ensure_dependencies libjemalloc2 if ! dpkg -s libjemalloc2 >/dev/null 2>&1; then
$STD apt install -y libjemalloc2
fi
if [[ ! -f /usr/lib/libjemalloc.so ]]; then if [[ ! -f /usr/lib/libjemalloc.so ]]; then
ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so ln -sf /usr/lib/x86_64-linux-gnu/libjemalloc.so.2 /usr/lib/libjemalloc.so
fi fi

View File

@@ -38,7 +38,7 @@ function update_script() {
msg_ok "Updated yt-dlp" msg_ok "Updated yt-dlp"
msg_info "Prepare update" msg_info "Prepare update"
ensure_dependencies graphicsmagick ghostscript $STD apt install -y graphicsmagick ghostscript
if [[ -f /opt/karakeep/.env ]] && [[ ! -f /etc/karakeep/karakeep.env ]]; then if [[ -f /opt/karakeep/.env ]] && [[ ! -f /etc/karakeep/karakeep.env ]]; then
mkdir -p /etc/karakeep mkdir -p /etc/karakeep
mv /opt/karakeep/.env /etc/karakeep/karakeep.env mv /opt/karakeep/.env /etc/karakeep/karakeep.env

View File

@@ -23,7 +23,9 @@ function update_script() {
header_info header_info
check_container_storage check_container_storage
check_container_resources check_container_resources
ensure_dependencies lsb-release if ! command -v lsb_release; then
apt install -y lsb-release
fi
if [[ ! -d /opt/kimai ]]; then if [[ ! -d /opt/kimai ]]; then
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit

View File

@@ -1,79 +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: snazzybean
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/TomBursch/kitchenowl
APP="KitchenOwl"
var_tags="${var_tags:-food;recipes}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-6}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/kitchenowl ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "kitchenowl" "TomBursch/kitchenowl"; then
msg_info "Stopping Service"
systemctl stop kitchenowl
msg_ok "Stopped Service"
msg_info "Creating Backup"
mkdir -p /opt/kitchenowl_backup
cp -r /opt/kitchenowl/data /opt/kitchenowl_backup/
cp -f /opt/kitchenowl/kitchenowl.env /opt/kitchenowl_backup/
msg_ok "Created Backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "kitchenowl" "TomBursch/kitchenowl" "tarball" "latest" "/opt/kitchenowl"
rm -rf /opt/kitchenowl/web
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "kitchenowl-web" "TomBursch/kitchenowl" "prebuild" "latest" "/opt/kitchenowl/web" "kitchenowl_Web.tar.gz"
msg_info "Restoring data"
sed -i 's/default=True/default=False/' /opt/kitchenowl/backend/wsgi.py
cp -r /opt/kitchenowl_backup/data /opt/kitchenowl/
cp -f /opt/kitchenowl_backup/kitchenowl.env /opt/kitchenowl/
rm -rf /opt/kitchenowl_backup
msg_ok "Restored data"
msg_info "Updating KitchenOwl"
cd /opt/kitchenowl/backend
$STD uv sync --frozen
cd /opt/kitchenowl/backend
set -a
source /opt/kitchenowl/kitchenowl.env
set +a
$STD uv run flask db upgrade
msg_ok "Updated KitchenOwl"
msg_info "Starting Service"
systemctl start kitchenowl
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}:80${CL}"

View File

@@ -59,8 +59,6 @@ function update_script() {
$STD yarn install $STD yarn install
$STD yarn build $STD yarn build
mkdir -p /opt/koillection/public/uploads mkdir -p /opt/koillection/public/uploads
mkdir -p /opt/koillection/var/log
chown -R www-data:www-data /opt/koillection/var/log
chown -R www-data:www-data /opt/koillection/public/uploads chown -R www-data:www-data /opt/koillection/public/uploads
rm -r /opt/koillection-backup rm -r /opt/koillection-backup

View File

@@ -27,8 +27,9 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
JAVA_VERSION="21" setup_java if ! dpkg -l | grep -q temurin-21-jre; then
JAVA_VERSION="21" setup_java
fi
msg_info "Updating ${APP}" msg_info "Updating ${APP}"
$STD apt update $STD apt update
$STD apt -y upgrade $STD apt -y upgrade

View File

@@ -36,7 +36,12 @@ function update_script() {
NODE_VERSION="24" setup_nodejs NODE_VERSION="24" setup_nodejs
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy" CLEAN_INSTALL=1 fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy"
ensure_dependencies pkg-config libssl-dev if ! dpkg -l | grep -q "pkg-config"; then
$STD apt install -y pkg-config
fi
if ! dpkg -l | grep -q "libssl-dev"; then
$STD apt install -y libssl-dev
fi
TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')" TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')"
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust RUST_TOOLCHAIN=$TOOLCHAIN setup_rust

View File

@@ -28,12 +28,6 @@ function update_script() {
exit exit
fi fi
msg_error "This script is currently disabled due to an external issue with the OpenResty APT repository."
msg_error "The repository's GPG key uses SHA-1 signatures, which are no longer accepted by Debian as of February 1, 2026."
msg_error "The issue is tracked in openresty/openresty#1097"
msg_error "For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11406"
exit 1
if [[ $(grep -E '^VERSION_ID=' /etc/os-release) == *"12"* ]]; then if [[ $(grep -E '^VERSION_ID=' /etc/os-release) == *"12"* ]]; then
msg_error "Wrong Debian version detected!" msg_error "Wrong Debian version detected!"
msg_error "Please create a snapshot first. You must upgrade your LXC to Debian Trixie before updating. Visit: https://github.com/community-scripts/ProxmoxVE/discussions/7489" msg_error "Please create a snapshot first. You must upgrade your LXC to Debian Trixie before updating. Visit: https://github.com/community-scripts/ProxmoxVE/discussions/7489"

View File

@@ -28,8 +28,8 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
ensure_dependencies python3-lxml
if ! [[ $(dpkg -s python3-lxml-html-clean 2>/dev/null) ]]; then if ! [[ $(dpkg -s python3-lxml-html-clean 2>/dev/null) ]]; then
$STD apt install python3-lxml
curl -fsSL "http://archive.ubuntu.com/ubuntu/pool/universe/l/lxml-html-clean/python3-lxml-html-clean_0.1.1-1_all.deb" -o /opt/python3-lxml-html-clean.deb curl -fsSL "http://archive.ubuntu.com/ubuntu/pool/universe/l/lxml-html-clean/python3-lxml-html-clean_0.1.1-1_all.deb" -o /opt/python3-lxml-html-clean.deb
$STD dpkg -i /opt/python3-lxml-html-clean.deb $STD dpkg -i /opt/python3-lxml-html-clean.deb
rm -f /opt/python3-lxml-html-clean.deb rm -f /opt/python3-lxml-html-clean.deb

View File

@@ -32,7 +32,11 @@ function update_script() {
if [[ ! -f /opt/Ollama_version.txt ]]; then if [[ ! -f /opt/Ollama_version.txt ]]; then
touch /opt/Ollama_version.txt touch /opt/Ollama_version.txt
fi fi
ensure_dependencies zstd if ! command -v zstd &>/dev/null; then
msg_info "Installing zstd"
$STD apt install -y zstd
msg_ok "Installed zstd"
fi
msg_info "Stopping Services" msg_info "Stopping Services"
systemctl stop ollama systemctl stop ollama
msg_ok "Services Stopped" msg_ok "Services Stopped"

View File

@@ -92,7 +92,11 @@ EOF
OLLAMA_VERSION=$(ollama -v | awk '{print $NF}') OLLAMA_VERSION=$(ollama -v | awk '{print $NF}')
RELEASE=$(curl -s https://api.github.com/repos/ollama/ollama/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4)}') RELEASE=$(curl -s https://api.github.com/repos/ollama/ollama/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4)}')
if [ "$OLLAMA_VERSION" != "$RELEASE" ]; then if [ "$OLLAMA_VERSION" != "$RELEASE" ]; then
ensure_dependencies zstd if ! command -v zstd &>/dev/null; then
msg_info "Installing zstd"
$STD apt install -y zstd
msg_ok "Installed zstd"
fi
msg_info "Ollama update available: v$OLLAMA_VERSION -> v$RELEASE" msg_info "Ollama update available: v$OLLAMA_VERSION -> v$RELEASE"
msg_info "Downloading Ollama v$RELEASE \n" msg_info "Downloading Ollama v$RELEASE \n"
curl -fS#LO https://github.com/ollama/ollama/releases/download/v${RELEASE}/ollama-linux-amd64.tar.zst curl -fS#LO https://github.com/ollama/ollama/releases/download/v${RELEASE}/ollama-linux-amd64.tar.zst

View File

@@ -69,7 +69,7 @@ function update_script() {
if [ "$VERSION_CODENAME" = "bookworm" ]; then if [ "$VERSION_CODENAME" = "bookworm" ]; then
setup_gs setup_gs
else else
ensure_dependencies ghostscript $STD apt install -y ghostscript
fi fi
msg_info "Updating Paperless-ngx" msg_info "Updating Paperless-ngx"
@@ -145,7 +145,7 @@ function update_script() {
setup_gs setup_gs
else else
msg_info "Installing Ghostscript" msg_info "Installing Ghostscript"
ensure_dependencies ghostscript $STD apt install -y ghostscript
msg_ok "Installed Ghostscript" msg_ok "Installed Ghostscript"
fi fi

View File

@@ -45,7 +45,7 @@ function update_script() {
LIBHEIF_URL=$(curl -fsSL "https://dl.photoprism.app/dist/libheif/" | grep -oP "libheif-bookworm-amd64-v[0-9\.]+\.tar\.gz" | sort -V | tail -n 1) LIBHEIF_URL=$(curl -fsSL "https://dl.photoprism.app/dist/libheif/" | grep -oP "libheif-bookworm-amd64-v[0-9\.]+\.tar\.gz" | sort -V | tail -n 1)
if [[ "${LIBHEIF_URL}" != "$(cat ~/.photoprism_libheif 2>/dev/null)" ]] || [[ ! -f ~/.photoprism_libheif ]]; then if [[ "${LIBHEIF_URL}" != "$(cat ~/.photoprism_libheif 2>/dev/null)" ]] || [[ ! -f ~/.photoprism_libheif ]]; then
msg_info "Updating PhotoPrism LibHeif" msg_info "Updating PhotoPrism LibHeif"
ensure_dependencies libvips42 $STD apt install -y libvips42
curl -fsSL "https://dl.photoprism.app/dist/libheif/$LIBHEIF_URL" -o /tmp/libheif.tar.gz curl -fsSL "https://dl.photoprism.app/dist/libheif/$LIBHEIF_URL" -o /tmp/libheif.tar.gz
tar -xzf /tmp/libheif.tar.gz -C /usr/local tar -xzf /tmp/libheif.tar.gz -C /usr/local
ldconfig ldconfig

View File

@@ -41,7 +41,7 @@ function update_script() {
cp -R /opt/rdtc-backup/appsettings.json /opt/rdtc/ cp -R /opt/rdtc-backup/appsettings.json /opt/rdtc/
if dpkg-query -W dotnet-sdk-8.0 >/dev/null 2>&1; then if dpkg-query -W dotnet-sdk-8.0 >/dev/null 2>&1; then
$STD apt remove --purge -y dotnet-sdk-8.0 $STD apt remove --purge -y dotnet-sdk-8.0
ensure_dependencies aspnetcore-runtime-9.0 $STD apt install -y aspnetcore-runtime-9.0
fi fi
rm -rf /opt/rdtc-backup rm -rf /opt/rdtc-backup

View File

@@ -27,20 +27,22 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
if check_for_codeberg_release "readeck" "readeck/readeck"; then msg_info "Stopping Service"
msg_info "Stopping Service" systemctl stop readeck
systemctl stop readeck msg_ok "Stopped Service"
msg_ok "Stopped Service"
fetch_and_deploy_codeberg_release "readeck" "readeck/readeck" "singlefile" "latest" "/opt/readeck" "readeck-*-linux-amd64" msg_info "Updating Readeck"
LATEST=$(curl -fsSL https://codeberg.org/readeck/readeck/releases/ | grep -oP '/releases/tag/\K\d+\.\d+\.\d+' | head -1)
rm -rf /opt/readeck/readeck
cd /opt/readeck
curl -fsSL "https://codeberg.org/readeck/readeck/releases/download/${LATEST}/readeck-${LATEST}-linux-amd64" -o "readeck"
chmod a+x readeck
msg_ok "Updated Readeck"
msg_info "Starting Service" msg_info "Starting Service"
systemctl start readeck systemctl start readeck
msg_ok "Started Service" msg_ok "Started Service"
msg_ok "Updated successfully!" msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at the latest version."
fi
exit exit
} }

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env bash
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: GoldenSpringness
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/orhun/rustypaste
APP="rustypaste"
var_tags="${var_tags:-pastebin;storage}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /opt/rustypaste/rustypaste ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "rustypaste" "orhun/rustypaste"; then
msg_info "Stopping Services"
systemctl stop rustypaste
msg_ok "Stopped Services"
msg_info "Creating Backup"
tar -czf "/opt/rustypaste_backup_$(date +%F).tar.gz" /opt/rustypaste/upload 2>/dev/null || true
cp /opt/rustypaste/config.toml /tmp/rustypaste_config.toml.bak
msg_ok "Backup Created"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "rustypaste" "orhun/rustypaste" "prebuild" "latest" "/opt/rustypaste" "*x86_64-unknown-linux-gnu.tar.gz"
msg_info "Restoring Data"
mv /tmp/rustypaste_config.toml.bak /opt/rustypaste/config.toml
tar -xzf "/opt/rustypaste_backup_$(date +%F).tar.gz" -C /opt/rustypaste/upload 2>/dev/null || true
rm -rf /opt/rustypaste_backup_$(date +%F).tar.gz
msg_ok "Restored Data"
msg_info "Starting Services"
systemctl start rustypaste
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
if check_for_gh_release "rustypaste-cli" "orhun/rustypaste-cli"; then
fetch_and_deploy_gh_release "rustypaste-cli" "orhun/rustypaste-cli" "prebuild" "latest" "/usr/local/bin" "*x86_64-unknown-linux-gnu.tar.gz"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}rustypaste setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}"

View File

@@ -29,7 +29,7 @@ function update_script() {
exit exit
fi fi
if check_for_gh_release "Scanopy" "scanopy/scanopy"; then if check_for_gh_release "scanopy" "scanopy/scanopy"; then
msg_info "Stopping services" msg_info "Stopping services"
systemctl stop scanopy-server systemctl stop scanopy-server
[[ -f /etc/systemd/system/scanopy-daemon.service ]] && systemctl stop scanopy-daemon [[ -f /etc/systemd/system/scanopy-daemon.service ]] && systemctl stop scanopy-daemon
@@ -40,9 +40,14 @@ function update_script() {
[[ -f /opt/scanopy/oidc.toml ]] && cp /opt/scanopy/oidc.toml /opt/scanopy.oidc.toml [[ -f /opt/scanopy/oidc.toml ]] && cp /opt/scanopy/oidc.toml /opt/scanopy.oidc.toml
msg_ok "Backed up configurations" msg_ok "Backed up configurations"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy" CLEAN_INSTALL=1 fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy"
ensure_dependencies pkg-config libssl-dev if ! dpkg -l | grep -q "pkg-config"; then
$STD apt install -y pkg-config
fi
if ! dpkg -l | grep -q "libssl-dev"; then
$STD apt install -y libssl-dev
fi
TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')" TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')"
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust RUST_TOOLCHAIN=$TOOLCHAIN setup_rust
@@ -61,22 +66,16 @@ function update_script() {
$STD npm run build $STD npm run build
msg_ok "Created frontend UI" msg_ok "Created frontend UI"
msg_info "Building Scanopy Server (patience)" msg_info "Building scanopy-server (patience)"
cd /opt/scanopy/backend cd /opt/scanopy/backend
$STD cargo build --release --bin server $STD cargo build --release --bin server
mv ./target/release/server /usr/bin/scanopy-server mv ./target/release/server /usr/bin/scanopy-server
msg_ok "Built Scanopy Server" msg_ok "Built scanopy-server"
if [[ -f /etc/systemd/system/scanopy-daemon.service ]]; then msg_info "Building scanopy-daemon"
fetch_and_deploy_gh_release "Scanopy Daemon" "scanopy/scanopy" "singlefile" "latest" "/usr/local/bin" "scanopy-daemon-linux-amd64" $STD cargo build --release --bin daemon
mv "/usr/local/bin/Scanopy Daemon" /usr/local/bin/scanopy-daemon cp ./target/release/daemon /usr/bin/scanopy-daemon
rm -f /usr/bin/scanopy-daemon ~/configure_daemon.sh msg_ok "Built scanopy-daemon"
sed -i -e 's|usr/bin|usr/local/bin|' \
-e 's/push/daemon_poll/' \
-e 's/pull/server_poll/' /etc/systemd/system/scanopy-daemon.service
systemctl daemon-reload
msg_ok "Updated Scanopy Daemon"
fi
msg_info "Starting services" msg_info "Starting services"
systemctl start scanopy-server systemctl start scanopy-server

View File

@@ -27,8 +27,7 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
msg_info "Updating SQL Server 2022" msg_info "Updating ${APP} LXC"
rm -f /etc/profile.d/debuginfod.sh /etc/profile.d/debuginfod.csh
$STD apt update $STD apt update
$STD apt -y upgrade $STD apt -y upgrade
msg_ok "Updated successfully!" msg_ok "Updated successfully!"

View File

@@ -1,45 +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: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://www.microsoft.com/en-us/sql-server/sql-server-2025
APP="SQL Server 2025"
var_tags="${var_tags:-sql;database}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-10}"
var_os="${var_os:-ubuntu}"
var_version="${var_version:-24.04}"
var_unprivileged="${var_unprivileged:-0}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/mssql ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating SQL Server 2025"
rm -f /etc/profile.d/debuginfod.sh /etc/profile.d/debuginfod.csh
$STD apt update
$STD apt -y upgrade
msg_ok "Updated successfully!"
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 IP:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}${IP}:1433${CL}"

View File

@@ -30,55 +30,6 @@ function update_script() {
NODE_VERSION="24" setup_nodejs NODE_VERSION="24" setup_nodejs
msg_info "Updating prestart script"
cat <<EOF >/data/tracearr/prestart.sh
#!/usr/bin/env bash
# =============================================================================
# Tune PostgreSQL for available resources (runs every startup)
# =============================================================================
# timescaledb-tune automatically optimizes PostgreSQL settings based on
# available RAM and CPU. Safe to run repeatedly - recalculates if resources change.
if command -v timescaledb-tune &> /dev/null; then
total_ram_kb=\$(grep MemTotal /proc/meminfo | awk '{print \$2}')
ram_for_tsdb=\$((total_ram_kb / 1024 / 2))
timescaledb-tune -yes -memory "\$ram_for_tsdb"MB --quiet 2>/dev/null \
|| echo "Warning: timescaledb-tune failed (non-fatal)"
fi
# =============================================================================
# Ensure required PostgreSQL settings for Tracearr
# =============================================================================
pg_config_file="/etc/postgresql/18/main/postgresql.conf"
if [ -f \$pg_config_file ]; then
# Ensure max_tuples_decompressed_per_dml_transaction is set
if grep -q "^timescaledb\.max_tuples_decompressed_per_dml_transaction" \$pg_config_file; then
# Setting exists (uncommented) - update if not 0
current_value=\$(grep "^timescaledb\.max_tuples_decompressed_per_dml_transaction" \$pg_config_file | grep -oE '[0-9]+' | head -1)
if [ -n "\$current_value" ] && [ "\$current_value" -ne 0 ]; then
sed -i "s/^timescaledb\.max_tuples_decompressed_per_dml_transaction.*/timescaledb.max_tuples_decompressed_per_dml_transaction = 0/" \$pg_config_file
fi
elif ! grep -q "^timescaledb\.max_tuples_decompressed_per_dml_transaction" \$pg_config_file; then
echo "" >> \$pg_config_file
echo "# Allow unlimited tuple decompression for migrations on compressed hypertables" >> \$pg_config_file
echo "timescaledb.max_tuples_decompressed_per_dml_transaction = 0" >> \$pg_config_file
fi
# Ensure max_locks_per_transaction is set (for existing databases)
if grep -q "^max_locks_per_transaction" \$pg_config_file; then
# Setting exists (uncommented) - update if below 4096
current_value=\$(grep "^max_locks_per_transaction" \$pg_config_file | grep -oE '[0-9]+' | head -1)
if [ -n "\$current_value" ] && [ "\$current_value" -lt 4096 ]; then
sed -i "s/^max_locks_per_transaction.*/max_locks_per_transaction = 4096/" \$pg_config_file
fi
elif ! grep -q "^max_locks_per_transaction" \$pg_config_file; then
echo "" >> \$pg_config_file
echo "# Increase lock table size for TimescaleDB hypertables with many chunks" >> \$pg_config_file
echo "max_locks_per_transaction = 4096" >> \$pg_config_file
fi
fi
systemctl restart postgresql
EOF
chmod +x /data/tracearr/prestart.sh
msg_ok "Updated prestart script"
if check_for_gh_release "tracearr" "connorgallopo/Tracearr"; then if check_for_gh_release "tracearr" "connorgallopo/Tracearr"; then
msg_info "Stopping Services" msg_info "Stopping Services"
systemctl stop tracearr postgresql redis systemctl stop tracearr postgresql redis
@@ -123,15 +74,10 @@ EOF
chown -R tracearr:tracearr /data/tracearr chown -R tracearr:tracearr /data/tracearr
msg_ok "Configured Tracearr" msg_ok "Configured Tracearr"
msg_info "Starting services" msg_info "Starting Services"
systemctl start postgresql redis tracearr systemctl start postgresql redis tracearr
msg_ok "Started services" msg_ok "Started Services"
msg_ok "Updated successfully!" msg_ok "Updated successfully!"
else
# no new release, just restart service to apply prestart changes
msg_info "Restarting service"
systemctl restart tracearr
msg_ok "Restarted service"
fi fi
exit exit
} }

View File

@@ -30,7 +30,8 @@ function update_script() {
fi fi
msg_info "Updating ${APP}" msg_info "Updating ${APP}"
ensure_dependencies twingate-connector $STD apt update
$STD apt install -yq twingate-connector
$STD systemctl restart twingate-connector $STD systemctl restart twingate-connector
msg_ok "Updated successfully!" msg_ok "Updated successfully!"
exit exit

View File

@@ -32,7 +32,7 @@ function update_script() {
msg_info "Updating ${APP}" msg_info "Updating ${APP}"
$STD apt update --allow-releaseinfo-change $STD apt update --allow-releaseinfo-change
ensure_dependencies unifi $STD apt install -y unifi
msg_ok "Updated successfully!" msg_ok "Updated successfully!"
exit exit
} }

View File

@@ -30,9 +30,12 @@ function update_script() {
NODE_VERSION="22" setup_nodejs NODE_VERSION="22" setup_nodejs
ensure_dependencies chromium if ! dpkg -s chromium >/dev/null 2>&1; then
if [[ ! -L /opt/uptime-kuma/chromium ]]; then msg_info "Installing Chromium"
$STD apt update
$STD apt install -y chromium
ln -s /usr/bin/chromium /opt/uptime-kuma/chromium ln -s /usr/bin/chromium /opt/uptime-kuma/chromium
msg_ok "Installed Chromium"
fi fi
if check_for_gh_release "uptime-kuma" "louislam/uptime-kuma"; then if check_for_gh_release "uptime-kuma" "louislam/uptime-kuma"; then

View File

@@ -28,8 +28,12 @@ function update_script() {
exit exit
fi fi
VAULT=$(get_latest_github_release "dani-garcia/vaultwarden") VAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest |
WVRELEASE=$(get_latest_github_release "dani-garcia/bw_web_builds") grep "tag_name" |
awk '{print substr($2, 2, length($2)-3) }')
WVRELEASE=$(curl -fsSL https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest |
grep "tag_name" |
awk '{print substr($2, 2, length($2)-3) }')
UPD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SUPPORT" --radiolist --cancel-button Exit-Script "Spacebar = Select" 11 58 3 \ UPD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SUPPORT" --radiolist --cancel-button Exit-Script "Spacebar = Select" 11 58 3 \
"1" "VaultWarden $VAULT" ON \ "1" "VaultWarden $VAULT" ON \
@@ -38,70 +42,57 @@ function update_script() {
3>&1 1>&2 2>&3) 3>&1 1>&2 2>&3)
if [ "$UPD" == "1" ]; then if [ "$UPD" == "1" ]; then
if check_for_gh_release "vaultwarden" "dani-garcia/vaultwarden"; then msg_info "Stopping Service"
msg_info "Stopping Service" systemctl stop vaultwarden
systemctl stop vaultwarden msg_ok "Stopped Service"
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src" msg_info "Updating VaultWarden to $VAULT (Patience)"
cd ~ && rm -rf vaultwarden
msg_info "Updating VaultWarden to $VAULT (Patience)" $STD git clone https://github.com/dani-garcia/vaultwarden
cd /tmp/vaultwarden-src cd vaultwarden
$STD cargo build --features "sqlite,mysql,postgresql" --release $STD cargo build --features "sqlite,mysql,postgresql" --release
if [[ -f /usr/bin/vaultwarden ]]; then DIR=/usr/bin/vaultwarden
cp target/release/vaultwarden /usr/bin/ if [ -d "$DIR" ]; then
else cp target/release/vaultwarden /usr/bin/
cp target/release/vaultwarden /opt/vaultwarden/bin/
fi
cd ~ && rm -rf /tmp/vaultwarden-src
msg_ok "Updated VaultWarden to ${VAULT}"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
else else
msg_ok "VaultWarden is already up-to-date" cp target/release/vaultwarden /opt/vaultwarden/bin/
fi fi
cd ~ && rm -rf vaultwarden
msg_ok "Updated VaultWarden"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
exit exit
fi fi
if [ "$UPD" == "2" ]; then if [ "$UPD" == "2" ]; then
if check_for_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds"; then msg_info "Stopping Service"
msg_info "Stopping Service" systemctl stop vaultwarden
systemctl stop vaultwarden msg_ok "Stopped Service"
msg_ok "Stopped Service"
msg_info "Updating Web-Vault to $WVRELEASE" msg_info "Updating Web-Vault to $WVRELEASE"
rm -rf /opt/vaultwarden/web-vault $STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WVRELEASE"/bw_web_"$WVRELEASE".tar.gz
mkdir -p /opt/vaultwarden/web-vault $STD tar -zxf bw_web_"$WVRELEASE".tar.gz -C /opt/vaultwarden/
rm bw_web_"$WVRELEASE".tar.gz
msg_ok "Updated Web-Vault"
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden/web-vault" "bw_web_*.tar.gz" msg_info "Starting Service"
systemctl start vaultwarden
chown -R root:root /opt/vaultwarden/web-vault/ msg_ok "Started Service"
msg_ok "Updated Web-Vault to ${WVRELEASE}" msg_ok "Updated successfully!"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "Web-Vault is already up-to-date"
fi
exit exit
fi fi
if [ "$UPD" == "3" ]; then if [ "$UPD" == "3" ]; then
if NEWTOKEN=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "Set the ADMIN_TOKEN" 10 58 3>&1 1>&2 2>&3); then if NEWTOKEN=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "Set the ADMIN_TOKEN" 10 58 3>&1 1>&2 2>&3); then
if [[ -z "$NEWTOKEN" ]]; then exit; fi if [[ -z "$NEWTOKEN" ]]; then exit; fi
ensure_dependencies argon2 if ! command -v argon2 >/dev/null 2>&1; then $STD apt-get install -y argon2; fi
TOKEN=$(echo -n "${NEWTOKEN}" | argon2 "$(openssl rand -base64 32)" -t 2 -m 16 -p 4 -l 64 -e) TOKEN=$(echo -n "${NEWTOKEN}" | argon2 "$(openssl rand -base64 32)" -t 2 -m 16 -p 4 -l 64 -e)
sed -i "s|ADMIN_TOKEN=.*|ADMIN_TOKEN='${TOKEN}'|" /opt/vaultwarden/.env sed -i "s|ADMIN_TOKEN=.*|ADMIN_TOKEN='${TOKEN}'|" /opt/vaultwarden/.env
if [[ -f /opt/vaultwarden/data/config.json ]]; then if [[ -f /opt/vaultwarden/data/config.json ]]; then
sed -i "s|\"admin_token\":.*|\"admin_token\": \"${TOKEN}\"|" /opt/vaultwarden/data/config.json sed -i "s|\"admin_token\":.*|\"admin_token\": \"${TOKEN}\"|" /opt/vaultwarden/data/config.json
fi fi
systemctl restart vaultwarden systemctl restart vaultwarden
msg_ok "Admin token updated"
fi fi
exit exit
fi fi

View File

@@ -27,7 +27,10 @@ function update_script() {
msg_error "No ${APP} Installation Found!" msg_error "No ${APP} Installation Found!"
exit exit
fi fi
ensure_dependencies zstd if ! [[ $(dpkg -s zstd 2>/dev/null) ]]; then
$STD apt update
$STD apt install -y zstd
fi
RELEASE=$(curl -fsSL https://api.github.com/repos/matze/wastebin/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }') RELEASE=$(curl -fsSL https://api.github.com/repos/matze/wastebin/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
# Dirty-Fix 03/2025 for missing APP_version.txt on old installations, set to pre-latest release # Dirty-Fix 03/2025 for missing APP_version.txt on old installations, set to pre-latest release
msg_info "Running Migration" msg_info "Running Migration"

View File

@@ -1,86 +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: CrazyWolf13
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://wealthfolio.app/
APP="Wealthfolio"
var_tags="${var_tags:-finance;portfolio}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-10}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/wealthfolio ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "wealthfolio" "afadil/wealthfolio"; then
msg_info "Stopping Service"
systemctl stop wealthfolio
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp -r /opt/wealthfolio_data /opt/wealthfolio_data_backup
cp /opt/wealthfolio/.env /opt/wealthfolio_env_backup
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wealthfolio" "afadil/wealthfolio" "tarball"
msg_info "Building Frontend (patience)"
cd /opt/wealthfolio
$STD pnpm install --frozen-lockfile
$STD pnpm tsc
$STD pnpm vite build
msg_ok "Built Frontend"
msg_info "Building Backend (patience)"
cd /opt/wealthfolio/src-server
source ~/.cargo/env
$STD cargo build --release --manifest-path Cargo.toml
cp /opt/wealthfolio/src-server/target/release/wealthfolio-server /usr/local/bin/wealthfolio-server
chmod +x /usr/local/bin/wealthfolio-server
msg_ok "Built Backend"
msg_info "Restoring Data"
cp -r /opt/wealthfolio_data_backup/. /opt/wealthfolio_data
cp /opt/wealthfolio_env_backup /opt/wealthfolio/.env
rm -rf /opt/wealthfolio_data_backup /opt/wealthfolio_env_backup
msg_ok "Restored Data"
msg_info "Cleaning Up"
rm -rf /opt/wealthfolio/src-server/target
rm -rf /root/.cargo/registry
rm -rf /opt/wealthfolio/node_modules
msg_ok "Cleaned Up"
msg_info "Starting Service"
systemctl start wealthfolio
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}:8080${CL}"

View File

@@ -1,82 +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: Dunky13
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/cmintey/wishlist
APP="Wishlist"
var_tags="${var_tags:-sharing}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-5}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/wishlist ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "wishlist" "cmintey/wishlist"; then
NODE_VERSION="24" NODE_MODULE="pnpm" setup_nodejs
msg_info "Stopping Service"
systemctl stop wishlist
msg_ok "Stopped Service"
msg_info "Creating Backup"
mkdir -p /opt/wishlist-backup
cp /opt/wishlist/.env /opt/wishlist-backup/.env
cp -a /opt/wishlist/uploads /opt/wishlist-backup
cp -a /opt/wishlist/data /opt/wishlist-backup
msg_ok "Created Backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wishlist" "cmintey/wishlist" "tarball"
LATEST_APP_VERSION=$(get_latest_github_release "cmintey/wishlist")
msg_info "Updating Wishlist"
cd /opt/wishlist
$STD pnpm install
$STD pnpm svelte-kit sync
$STD pnpm prisma generate
sed -i 's|/usr/src/app/|/opt/wishlist/|g' $(grep -rl '/usr/src/app/' /opt/wishlist)
export VERSION="v${LATEST_APP_VERSION}"
export SHA="v${LATEST_APP_VERSION}"
$STD pnpm run build
$STD pnpm prune --prod
chmod +x /opt/wishlist/entrypoint.sh
msg_info "Restoring Backup"
cp /opt/wishlist-backup/.env /opt/wishlist/.env
cp -a /opt/wishlist-backup/uploads /opt/wishlist
cp -a /opt/wishlist-backup/data /opt/wishlist
rm -rf /opt/wishlist-backup
msg_ok "Restored Backup"
msg_ok "Updated Wishlist"
msg_info "Starting Service"
systemctl start wishlist
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}:3280${CL}"

View File

@@ -1,72 +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: StellaeAlis
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/writefreely/writefreely
APP="WriteFreely"
var_tags="${var_tags:-writing}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
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/writefreely ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "writefreely" "writefreely/writefreely"; then
msg_info "Stopping Services"
systemctl stop writefreely
msg_ok "Stopped Services"
msg_info "Creating Backup"
mkdir -p /tmp/writefreely_backup
cp /opt/writefreely/keys /tmp/writefreely_backup/ 2>/dev/null
cp /opt/writefreely/config.ini /tmp/writefreely_backup/ 2>/dev/null
msg_ok "Created Backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "writefreely" "writefreely/writefreely" "prebuild" "latest" "/opt/writefreely" "writefreely_*_linux_amd64.tar.gz"
msg_info "Restoring Data"
cp /tmp/writefreely_backup/config.ini /opt/writefreely/ 2>/dev/null
cp /tmp/writefreely_backup/keys/* /opt/writefreely/keys/ 2>/dev/null
rm -rf /tmp/writefreely_backup
msg_ok "Restored Data"
msg_info "Running Post-Update Tasks"
cd /opt/writefreely
$STD ./writefreely db migrate
ln -s /opt/writefreely/writefreely /usr/local/bin/writefreely
msg_ok "Ran Post-Update Tasks"
msg_info "Starting Services"
systemctl start writefreely
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 "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"

View File

@@ -9,9 +9,9 @@
"updateable": true, "updateable": true,
"privileged": false, "privileged": false,
"interface_port": 8083, "interface_port": 8083,
"documentation": "https://codeberg.org/gelbphoenix/autocaliweb/wiki", "documentation": "https://github.com/gelbphoenix/autocaliweb/wiki",
"config_path": "/etc/autocaliweb", "config_path": "/etc/autocaliweb",
"website": "https://codeberg.org/gelbphoenix/autocaliweb", "website": "https://github.com/gelbphoenix/autocaliweb",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/autocaliweb.webp", "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/autocaliweb.webp",
"description": "A modern web management system for eBooks, eComics and PDFs", "description": "A modern web management system for eBooks, eComics and PDFs",
"install_methods": [ "install_methods": [

View File

@@ -1,5 +1,5 @@
{ {
"generated": "2026-02-05T06:22:20Z", "generated": "2026-02-01T18:07:47Z",
"versions": [ "versions": [
{ {
"slug": "2fauth", "slug": "2fauth",
@@ -95,9 +95,9 @@
{ {
"slug": "bar-assistant", "slug": "bar-assistant",
"repo": "karlomikus/bar-assistant", "repo": "karlomikus/bar-assistant",
"version": "v5.13.1", "version": "v5.13.0",
"pinned": false, "pinned": false,
"date": "2026-02-02T18:47:43Z" "date": "2026-02-01T15:49:21Z"
}, },
{ {
"slug": "bazarr", "slug": "bazarr",
@@ -109,16 +109,16 @@
{ {
"slug": "bentopdf", "slug": "bentopdf",
"repo": "alam00000/bentopdf", "repo": "alam00000/bentopdf",
"version": "v2.1.0", "version": "v2.0.0",
"pinned": false, "pinned": false,
"date": "2026-02-02T14:30:55Z" "date": "2026-01-31T10:13:47Z"
}, },
{ {
"slug": "beszel", "slug": "beszel",
"repo": "henrygd/beszel", "repo": "henrygd/beszel",
"version": "v0.18.3", "version": "v0.18.2",
"pinned": false, "pinned": false,
"date": "2026-02-01T19:02:42Z" "date": "2026-01-12T23:58:00Z"
}, },
{ {
"slug": "bitmagnet", "slug": "bitmagnet",
@@ -158,9 +158,9 @@
{ {
"slug": "bytestash", "slug": "bytestash",
"repo": "jordan-dalby/ByteStash", "repo": "jordan-dalby/ByteStash",
"version": "v1.5.11", "version": "v1.5.10",
"pinned": false, "pinned": false,
"date": "2026-02-03T22:12:19Z" "date": "2026-01-26T14:07:59Z"
}, },
{ {
"slug": "caddy", "slug": "caddy",
@@ -186,9 +186,9 @@
{ {
"slug": "comfyui", "slug": "comfyui",
"repo": "comfyanonymous/ComfyUI", "repo": "comfyanonymous/ComfyUI",
"version": "v0.12.2", "version": "v0.11.1",
"pinned": false, "pinned": false,
"date": "2026-02-04T06:09:31Z" "date": "2026-01-29T07:52:21Z"
}, },
{ {
"slug": "commafeed", "slug": "commafeed",
@@ -221,9 +221,9 @@
{ {
"slug": "cronicle", "slug": "cronicle",
"repo": "jhuckaby/Cronicle", "repo": "jhuckaby/Cronicle",
"version": "v0.9.103", "version": "v0.9.102",
"pinned": false, "pinned": false,
"date": "2026-02-05T03:09:04Z" "date": "2025-12-19T03:45:13Z"
}, },
{ {
"slug": "cryptpad", "slug": "cryptpad",
@@ -242,9 +242,9 @@
{ {
"slug": "discopanel", "slug": "discopanel",
"repo": "nickheyer/discopanel", "repo": "nickheyer/discopanel",
"version": "v1.0.35", "version": "v1.0.32",
"pinned": false, "pinned": false,
"date": "2026-02-02T05:20:12Z" "date": "2026-01-31T22:24:53Z"
}, },
{ {
"slug": "dispatcharr", "slug": "dispatcharr",
@@ -256,9 +256,9 @@
{ {
"slug": "docmost", "slug": "docmost",
"repo": "docmost/docmost", "repo": "docmost/docmost",
"version": "v0.25.1", "version": "v0.24.1",
"pinned": false, "pinned": false,
"date": "2026-02-04T15:19:51Z" "date": "2025-12-14T13:49:16Z"
}, },
{ {
"slug": "domain-locker", "slug": "domain-locker",
@@ -291,9 +291,9 @@
{ {
"slug": "elementsynapse", "slug": "elementsynapse",
"repo": "etkecc/synapse-admin", "repo": "etkecc/synapse-admin",
"version": "v0.11.1-etke53", "version": "v0.11.1-etke52",
"pinned": false, "pinned": false,
"date": "2026-02-03T20:38:15Z" "date": "2026-01-09T08:41:29Z"
}, },
{ {
"slug": "emby", "slug": "emby",
@@ -312,9 +312,9 @@
{ {
"slug": "ersatztv", "slug": "ersatztv",
"repo": "ErsatzTV/ErsatzTV", "repo": "ErsatzTV/ErsatzTV",
"version": "v26.2.0", "version": "v26.1.1",
"pinned": false, "pinned": false,
"date": "2026-02-02T20:54:26Z" "date": "2026-01-08T22:02:15Z"
}, },
{ {
"slug": "excalidraw", "slug": "excalidraw",
@@ -375,9 +375,9 @@
{ {
"slug": "ghostfolio", "slug": "ghostfolio",
"repo": "ghostfolio/ghostfolio", "repo": "ghostfolio/ghostfolio",
"version": "2.235.0", "version": "2.234.0",
"pinned": false, "pinned": false,
"date": "2026-02-03T19:27:17Z" "date": "2026-01-30T19:00:22Z"
}, },
{ {
"slug": "gitea", "slug": "gitea",
@@ -445,9 +445,9 @@
{ {
"slug": "headscale", "slug": "headscale",
"repo": "juanfont/headscale", "repo": "juanfont/headscale",
"version": "v0.28.0", "version": "v0.27.1",
"pinned": false, "pinned": false,
"date": "2026-02-04T20:40:23Z" "date": "2025-11-11T19:32:29Z"
}, },
{ {
"slug": "healthchecks", "slug": "healthchecks",
@@ -487,16 +487,16 @@
{ {
"slug": "homebox", "slug": "homebox",
"repo": "sysadminsmedia/homebox", "repo": "sysadminsmedia/homebox",
"version": "v0.23.1", "version": "v0.23.0",
"pinned": false, "pinned": false,
"date": "2026-02-01T22:53:32Z" "date": "2026-01-30T17:41:01Z"
}, },
{ {
"slug": "homepage", "slug": "homepage",
"repo": "gethomepage/homepage", "repo": "gethomepage/homepage",
"version": "v1.10.0", "version": "v1.9.0",
"pinned": false, "pinned": false,
"date": "2026-02-05T06:17:32Z" "date": "2026-01-19T05:46:09Z"
}, },
{ {
"slug": "homer", "slug": "homer",
@@ -515,9 +515,9 @@
{ {
"slug": "huntarr", "slug": "huntarr",
"repo": "plexguide/Huntarr.io", "repo": "plexguide/Huntarr.io",
"version": "9.2.0", "version": "9.1.5",
"pinned": false, "pinned": false,
"date": "2026-02-05T04:18:08Z" "date": "2026-01-31T22:55:29Z"
}, },
{ {
"slug": "inspircd", "slug": "inspircd",
@@ -536,16 +536,16 @@
{ {
"slug": "invoiceninja", "slug": "invoiceninja",
"repo": "invoiceninja/invoiceninja", "repo": "invoiceninja/invoiceninja",
"version": "v5.12.55", "version": "v5.12.52",
"pinned": false, "pinned": false,
"date": "2026-02-05T01:06:15Z" "date": "2026-02-01T02:08:10Z"
}, },
{ {
"slug": "jackett", "slug": "jackett",
"repo": "Jackett/Jackett", "repo": "Jackett/Jackett",
"version": "v0.24.1032", "version": "v0.24.1003",
"pinned": false, "pinned": false,
"date": "2026-02-05T05:55:27Z" "date": "2026-02-01T05:55:30Z"
}, },
{ {
"slug": "joplin-server", "slug": "joplin-server",
@@ -596,13 +596,6 @@
"pinned": false, "pinned": false,
"date": "2026-01-31T18:10:59Z" "date": "2026-01-31T18:10:59Z"
}, },
{
"slug": "kitchenowl",
"repo": "TomBursch/kitchenowl",
"version": "v0.7.6",
"pinned": false,
"date": "2026-01-24T01:21:14Z"
},
{ {
"slug": "koel", "slug": "koel",
"repo": "koel/koel", "repo": "koel/koel",
@@ -669,9 +662,9 @@
{ {
"slug": "libretranslate", "slug": "libretranslate",
"repo": "LibreTranslate/LibreTranslate", "repo": "LibreTranslate/LibreTranslate",
"version": "v1.8.4", "version": "v1.8.3",
"pinned": false, "pinned": false,
"date": "2026-02-02T17:45:16Z" "date": "2025-12-04T21:07:00Z"
}, },
{ {
"slug": "lidarr", "slug": "lidarr",
@@ -746,9 +739,9 @@
{ {
"slug": "mealie", "slug": "mealie",
"repo": "mealie-recipes/mealie", "repo": "mealie-recipes/mealie",
"version": "v3.10.2", "version": "v3.9.2",
"pinned": false, "pinned": false,
"date": "2026-02-04T23:32:32Z" "date": "2026-01-02T19:40:09Z"
}, },
{ {
"slug": "mediamanager", "slug": "mediamanager",
@@ -767,9 +760,9 @@
{ {
"slug": "meilisearch", "slug": "meilisearch",
"repo": "riccox/meilisearch-ui", "repo": "riccox/meilisearch-ui",
"version": "v0.15.1", "version": "v0.15.0",
"pinned": false, "pinned": false,
"date": "2026-02-04T03:56:59Z" "date": "2026-01-29T03:54:27Z"
}, },
{ {
"slug": "memos", "slug": "memos",
@@ -781,9 +774,9 @@
{ {
"slug": "metube", "slug": "metube",
"repo": "alexta69/metube", "repo": "alexta69/metube",
"version": "2026.02.04", "version": "2026.02.01",
"pinned": false, "pinned": false,
"date": "2026-02-04T20:01:18Z" "date": "2026-02-01T00:20:00Z"
}, },
{ {
"slug": "miniflux", "slug": "miniflux",
@@ -823,16 +816,16 @@
{ {
"slug": "navidrome", "slug": "navidrome",
"repo": "navidrome/navidrome", "repo": "navidrome/navidrome",
"version": "v0.60.0", "version": "v0.59.0",
"pinned": false, "pinned": false,
"date": "2026-02-03T18:57:04Z" "date": "2025-12-06T18:08:42Z"
}, },
{ {
"slug": "netbox", "slug": "netbox",
"repo": "netbox-community/netbox", "repo": "netbox-community/netbox",
"version": "v4.5.2", "version": "v4.5.1",
"pinned": false, "pinned": false,
"date": "2026-02-03T13:54:26Z" "date": "2026-01-20T19:45:05Z"
}, },
{ {
"slug": "nocodb", "slug": "nocodb",
@@ -879,9 +872,9 @@
{ {
"slug": "opengist", "slug": "opengist",
"repo": "thomiceli/opengist", "repo": "thomiceli/opengist",
"version": "v1.12.1", "version": "v1.12.0",
"pinned": false, "pinned": false,
"date": "2026-02-03T09:00:43Z" "date": "2026-01-27T15:31:57Z"
}, },
{ {
"slug": "ots", "slug": "ots",
@@ -1054,9 +1047,9 @@
{ {
"slug": "prometheus-alertmanager", "slug": "prometheus-alertmanager",
"repo": "prometheus/alertmanager", "repo": "prometheus/alertmanager",
"version": "v0.31.0", "version": "v0.30.1",
"pinned": false, "pinned": false,
"date": "2026-02-02T13:34:15Z" "date": "2026-01-12T23:30:06Z"
}, },
{ {
"slug": "prometheus-blackbox-exporter", "slug": "prometheus-blackbox-exporter",
@@ -1096,9 +1089,9 @@
{ {
"slug": "pulse", "slug": "pulse",
"repo": "rcourtman/Pulse", "repo": "rcourtman/Pulse",
"version": "v5.1.2", "version": "v5.0.17",
"pinned": false, "pinned": false,
"date": "2026-02-05T00:18:57Z" "date": "2026-01-20T19:07:30Z"
}, },
{ {
"slug": "pve-scripts-local", "slug": "pve-scripts-local",
@@ -1184,13 +1177,6 @@
"pinned": false, "pinned": false,
"date": "2026-01-12T05:38:30Z" "date": "2026-01-12T05:38:30Z"
}, },
{
"slug": "rustypaste",
"repo": "orhun/rustypaste",
"version": "v0.16.1",
"pinned": false,
"date": "2025-03-21T20:44:47Z"
},
{ {
"slug": "sabnzbd", "slug": "sabnzbd",
"repo": "sabnzbd/sabnzbd", "repo": "sabnzbd/sabnzbd",
@@ -1201,9 +1187,9 @@
{ {
"slug": "scanopy", "slug": "scanopy",
"repo": "scanopy/scanopy", "repo": "scanopy/scanopy",
"version": "v0.14.3", "version": "v0.14.0",
"pinned": false, "pinned": false,
"date": "2026-02-04T01:41:01Z" "date": "2026-02-01T17:02:37Z"
}, },
{ {
"slug": "scraparr", "slug": "scraparr",
@@ -1271,16 +1257,16 @@
{ {
"slug": "speedtest-tracker", "slug": "speedtest-tracker",
"repo": "alexjustesen/speedtest-tracker", "repo": "alexjustesen/speedtest-tracker",
"version": "v1.13.8", "version": "v1.13.5",
"pinned": false, "pinned": false,
"date": "2026-02-04T19:24:23Z" "date": "2026-01-08T22:35:28Z"
}, },
{ {
"slug": "spoolman", "slug": "spoolman",
"repo": "Donkie/Spoolman", "repo": "Donkie/Spoolman",
"version": "v0.23.1", "version": "v0.23.0",
"pinned": false, "pinned": false,
"date": "2026-02-03T19:03:55Z" "date": "2026-01-23T20:42:34Z"
}, },
{ {
"slug": "sportarr", "slug": "sportarr",
@@ -1292,9 +1278,9 @@
{ {
"slug": "stirling-pdf", "slug": "stirling-pdf",
"repo": "Stirling-Tools/Stirling-PDF", "repo": "Stirling-Tools/Stirling-PDF",
"version": "v2.4.4", "version": "v2.4.3",
"pinned": false, "pinned": false,
"date": "2026-02-05T00:15:53Z" "date": "2026-01-31T22:19:14Z"
}, },
{ {
"slug": "streamlink-webui", "slug": "streamlink-webui",
@@ -1355,9 +1341,9 @@
{ {
"slug": "thingsboard", "slug": "thingsboard",
"repo": "thingsboard/thingsboard", "repo": "thingsboard/thingsboard",
"version": "v4.3.0.1", "version": "v4.3",
"pinned": false, "pinned": false,
"date": "2026-02-03T12:39:14Z" "date": "2026-01-20T14:27:07Z"
}, },
{ {
"slug": "threadfin", "slug": "threadfin",
@@ -1369,9 +1355,9 @@
{ {
"slug": "tianji", "slug": "tianji",
"repo": "msgbyte/tianji", "repo": "msgbyte/tianji",
"version": "v1.31.10", "version": "v1.31.9",
"pinned": false, "pinned": false,
"date": "2026-02-04T17:21:04Z" "date": "2026-01-31T18:22:40Z"
}, },
{ {
"slug": "traccar", "slug": "traccar",
@@ -1411,9 +1397,9 @@
{ {
"slug": "trip", "slug": "trip",
"repo": "itskovacs/TRIP", "repo": "itskovacs/TRIP",
"version": "1.38.1", "version": "1.38.0",
"pinned": false, "pinned": false,
"date": "2026-02-04T18:10:15Z" "date": "2026-01-31T15:56:30Z"
}, },
{ {
"slug": "tududi", "slug": "tududi",
@@ -1425,9 +1411,9 @@
{ {
"slug": "tunarr", "slug": "tunarr",
"repo": "chrisbenincasa/tunarr", "repo": "chrisbenincasa/tunarr",
"version": "v1.1.12", "version": "v1.1.11",
"pinned": false, "pinned": false,
"date": "2026-02-03T20:19:00Z" "date": "2026-01-30T22:34:30Z"
}, },
{ {
"slug": "uhf", "slug": "uhf",
@@ -1471,19 +1457,12 @@
"pinned": false, "pinned": false,
"date": "2025-10-22T17:03:54Z" "date": "2025-10-22T17:03:54Z"
}, },
{
"slug": "vaultwarden",
"repo": "dani-garcia/vaultwarden",
"version": "1.35.2",
"pinned": false,
"date": "2026-01-09T18:37:04Z"
},
{ {
"slug": "victoriametrics", "slug": "victoriametrics",
"repo": "VictoriaMetrics/VictoriaMetrics", "repo": "VictoriaMetrics/VictoriaMetrics",
"version": "v1.135.0", "version": "v1.134.0",
"pinned": false, "pinned": false,
"date": "2026-02-02T14:20:15Z" "date": "2026-01-19T13:29:43Z"
}, },
{ {
"slug": "vikunja", "slug": "vikunja",
@@ -1509,9 +1488,9 @@
{ {
"slug": "wanderer", "slug": "wanderer",
"repo": "meilisearch/meilisearch", "repo": "meilisearch/meilisearch",
"version": "v1.35.0", "version": "v1.34.3",
"pinned": false, "pinned": false,
"date": "2026-02-02T09:57:03Z" "date": "2026-01-28T17:52:24Z"
}, },
{ {
"slug": "warracker", "slug": "warracker",
@@ -1541,13 +1520,6 @@
"pinned": false, "pinned": false,
"date": "2025-12-31T16:53:34Z" "date": "2025-12-31T16:53:34Z"
}, },
{
"slug": "wealthfolio",
"repo": "afadil/wealthfolio",
"version": "v2.1.0",
"pinned": false,
"date": "2025-12-01T21:57:36Z"
},
{ {
"slug": "web-check", "slug": "web-check",
"repo": "CrazyWolf13/web-check", "repo": "CrazyWolf13/web-check",
@@ -1562,13 +1534,6 @@
"pinned": false, "pinned": false,
"date": "2026-01-08T09:50:00Z" "date": "2026-01-08T09:50:00Z"
}, },
{
"slug": "wishlist",
"repo": "cmintey/wishlist",
"version": "v0.59.0",
"pinned": false,
"date": "2026-01-19T16:42:14Z"
},
{ {
"slug": "wizarr", "slug": "wizarr",
"repo": "wizarrrr/wizarr", "repo": "wizarrrr/wizarr",
@@ -1576,13 +1541,6 @@
"pinned": false, "pinned": false,
"date": "2025-12-09T14:30:23Z" "date": "2025-12-09T14:30:23Z"
}, },
{
"slug": "writefreely",
"repo": "writefreely/writefreely",
"version": "v0.16.0",
"pinned": false,
"date": "2025-08-29T19:30:02Z"
},
{ {
"slug": "yt-dlp-webui", "slug": "yt-dlp-webui",
"repo": "marcopiovanello/yt-dlp-web-ui", "repo": "marcopiovanello/yt-dlp-web-ui",
@@ -1600,9 +1558,9 @@
{ {
"slug": "zigbee2mqtt", "slug": "zigbee2mqtt",
"repo": "Koenkk/zigbee2mqtt", "repo": "Koenkk/zigbee2mqtt",
"version": "2.8.0", "version": "2.7.2",
"pinned": false, "pinned": false,
"date": "2026-02-01T19:27:25Z" "date": "2026-01-01T13:43:47Z"
}, },
{ {
"slug": "zipline", "slug": "zipline",
@@ -1628,9 +1586,9 @@
{ {
"slug": "zwave-js-ui", "slug": "zwave-js-ui",
"repo": "zwave-js/zwave-js-ui", "repo": "zwave-js/zwave-js-ui",
"version": "v11.11.0", "version": "v11.10.1",
"pinned": false, "pinned": false,
"date": "2026-02-03T13:13:05Z" "date": "2026-01-15T15:58:06Z"
} }
] ]
} }

View File

@@ -1,44 +0,0 @@
{
"name": "Immich Public Proxy",
"slug": "immich-public-proxy",
"categories": [
21
],
"date_created": "2026-02-04",
"type": "addon",
"updateable": true,
"privileged": false,
"interface_port": 3000,
"documentation": "https://github.com/alangrainger/immich-public-proxy/tree/main/docs",
"website": "https://github.com/alangrainger/immich-public-proxy",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/immich-public-proxy.webp",
"config_path": "/opt/immich-proxy/app/.env",
"description": "Share your Immich photos and albums in a safe way without exposing your Immich instance to the public.",
"install_methods": [
{
"type": "default",
"script": "tools/addon/immich-public-proxy.sh",
"resources": {
"cpu": null,
"ram": null,
"hdd": null,
"os": null,
"version": null
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "Requires Node.js 24+",
"type": "info"
},
{
"text": "Update with: update_immich-public-proxy",
"type": "info"
}
]
}

View File

@@ -1,35 +0,0 @@
{
"name": "KitchenOwl",
"slug": "kitchenowl",
"categories": [
13
],
"date_created": "2026-02-02",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 80,
"documentation": "https://docs.kitchenowl.org/",
"website": "https://kitchenowl.org/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp",
"config_path": "/opt/kitchenowl/kitchenowl.env",
"description": "KitchenOwl is a smart self-hosted grocery list and recipe manager with real-time synchronization, recipe management, meal planning, and expense tracking.",
"install_methods": [
{
"type": "default",
"script": "ct/kitchenowl.sh",
"resources": {
"cpu": 1,
"ram": 2048,
"hdd": 6,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -13,8 +13,6 @@
"website": "https://nginxproxymanager.com/", "website": "https://nginxproxymanager.com/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/nginx-proxy-manager.webp", "logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/nginx-proxy-manager.webp",
"config_path": "", "config_path": "",
"disable": true,
"disable_description": "This script is temporarily disabled due to an external issue with the OpenResty APT repository. The repository's GPG key uses SHA-1 signatures, which are no longer accepted by Debian as of February 1, 2026. This causes installation to fail with APT errors. The issue is tracked in openresty/openresty#1097. A workaround exists but requires manual configuration. The script will be re-enabled once OpenResty updates their repository signing key. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11406",
"description": "Nginx Proxy Manager is a tool that provides a web-based interface to manage Nginx reverse proxies. It enables users to easily and securely expose their services to the internet by providing features such as HTTPS encryption, domain mapping, and access control. It eliminates the need for manual configuration of Nginx reverse proxies, making it easy for users to quickly and securely expose their services to the public.", "description": "Nginx Proxy Manager is a tool that provides a web-based interface to manage Nginx reverse proxies. It enables users to easily and securely expose their services to the internet by providing features such as HTTPS encryption, domain mapping, and access control. It eliminates the need for manual configuration of Nginx reverse proxies, making it easy for users to quickly and securely expose their services to the public.",
"install_methods": [ "install_methods": [
{ {

View File

@@ -1,51 +0,0 @@
{
"name": "RustyPaste",
"slug": "rustypaste",
"categories": [
11
],
"date_created": "2026-02-02",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 8000,
"documentation": "https://github.com/orhun/rustypaste",
"config_path": "/opt/rustypaste/config.toml",
"website": "https://github.com/orhun/rustypaste",
"logo": "https://github.com/orhun/rustypaste/raw/master/img/rustypaste_logo.png",
"description": "Rustypaste is a minimal file upload/pastebin service.",
"install_methods": [
{
"type": "default",
"script": "ct/rustypaste.sh",
"resources": {
"cpu": 1,
"ram": 1024,
"hdd": 20,
"os": "Debian",
"version": "13"
}
},
{
"type": "alpine",
"script": "ct/alpine-rustypaste.sh",
"resources": {
"cpu": 1,
"ram": 256,
"hdd": 4,
"os": "Alpine",
"version": "3.23"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "When updating the script it will backup the whole project including all the uploaded files, make sure to extract it to a safe location or remove",
"type": "info"
}
]
}

View File

@@ -37,7 +37,7 @@
"type": "info" "type": "info"
}, },
{ {
"text": "The integrated daemon config is located at `/root/.config/daemon/`", "text": "The integrated daemon config is located at `/root/.config/daemon/config.json`",
"type": "info" "type": "info"
} }
] ]

View File

@@ -1,52 +0,0 @@
{
"name": "SQL Server 2025",
"slug": "sqlserver2025",
"categories": [
8
],
"date_created": "2026-02-05",
"type": "ct",
"updateable": true,
"privileged": true,
"interface_port": 1433,
"documentation": "https://learn.microsoft.com/en-us/sql/sql-server/?view=sql-server-ver17",
"website": "https://www.microsoft.com/en-us/sql-server/sql-server-2025",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/microsoft-sql-server.webp",
"config_path": "",
"description": "Script to automatically set up a SQL Server 2025 installation with Ubuntu 24.04 support.",
"install_methods": [
{
"type": "default",
"script": "ct/sqlserver2025.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 10,
"os": "Ubuntu",
"version": "24.04"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "If you choose not to run the installation setup, execute: `/opt/mssql/bin/mssql-conf setup` in LXC shell.",
"type": "info"
},
{
"text": "You can setup the admin account 'SA' during installation",
"type": "info"
},
{
"text": "Make sure you disable the SA account if you intend to use this in production!",
"type": "warning"
},
{
"text": "Ubuntu 24.04 support requires SQL Server 2025 CU1 or later",
"type": "info"
}
]
}

View File

@@ -1,40 +0,0 @@
{
"name": "Wealthfolio",
"slug": "wealthfolio",
"categories": [
23
],
"date_created": "2026-02-03",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 8080,
"documentation": "https://wealthfolio.app/docs/introduction/",
"config_path": "/opt/wealthfolio/.env",
"website": "https://wealthfolio.app/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/wealthfolio.webp",
"description": "Wealthfolio is a beautiful, privacy-focused investment tracker with local data storage. Track your portfolio across multiple accounts and asset types with detailed performance analytics, goal planning, and multi-currency support.",
"install_methods": [
{
"type": "default",
"script": "ct/wealthfolio.sh",
"resources": {
"cpu": 4,
"ram": 4096,
"hdd": 10,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": "See ~/wealthfolio.creds"
},
"notes": [
{
"text": "Login password is stored in ~/wealthfolio.creds",
"type": "info"
}
]
}

View File

@@ -1,35 +0,0 @@
{
"name": "Wishlist",
"slug": "wishlist",
"categories": [
12
],
"date_created": "2026-02-04",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 3280,
"documentation": "https://github.com/cmintey/wishlist/blob/main/README.md#getting-started",
"config_path": "/opt/wishlist/.env",
"website": "https://github.com/cmintey/wishlist",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/cmintey-wishlist.webp",
"description": "Wishlist is a self-hosted wishlist application that you can share with your friends and family. You no longer have to wonder what to get your family for the holidays, simply check their wishlist and claim any available item!",
"install_methods": [
{
"type": "default",
"script": "ct/wishlist.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 5,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -1,40 +0,0 @@
{
"name": "WriteFreely",
"slug": "writefreely",
"categories": [
12
],
"date_created": "2026-02-04",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 80,
"documentation": "https://writefreely.org/docs",
"config_path": "/opt/writefreely/config.ini",
"website": "https://writefreely.org/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/writefreely-light.webp",
"description": "WriteFreely is free and open source software for easily publishing writing on the web with support for the ActivityPub protocol. Use it to start a personal blog — or an entire community.",
"install_methods": [
{
"type": "default",
"script": "ct/writefreely.sh",
"resources": {
"cpu": 2,
"ram": 1024,
"hdd": 4,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "After installation execute `writefreely user create --admin <username>:<password>` to create your user.",
"type": "info"
}
]
}

View File

@@ -0,0 +1,320 @@
"use client";
import { ChevronLeft, ChevronRight } from "lucide-react";
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import type { Category } from "@/lib/types";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
const defaultLogo = "/default-logo.png"; // Fallback logo path
const MAX_DESCRIPTION_LENGTH = 100; // Set max length for description
const MAX_LOGOS = 5; // Max logos to display at once
function formattedBadge(type: string) {
switch (type) {
case "vm":
return <Badge className="text-blue-500/75 border-blue-500/75 badge">VM</Badge>;
case "ct":
return <Badge className="text-yellow-500/75 border-yellow-500/75 badge">LXC</Badge>;
case "pve":
return <Badge className="text-orange-500/75 border-orange-500/75 badge">PVE</Badge>;
case "addon":
return <Badge className="text-green-500/75 border-green-500/75 badge">ADDON</Badge>;
}
return null;
}
function CategoryView() {
const [categories, setCategories] = useState<Category[]>([]);
const [selectedCategoryIndex, setSelectedCategoryIndex] = useState<number | null>(null);
const [currentScripts, setCurrentScripts] = useState<any[]>([]);
const [logoIndices, setLogoIndices] = useState<{ [key: string]: number }>({});
const router = useRouter();
useEffect(() => {
const fetchCategories = async () => {
try {
// eslint-disable-next-line node/no-process-env
const basePath = process.env.NODE_ENV === "production" ? "/ProxmoxVE" : "";
const response = await fetch(`${basePath}/api/categories`);
if (!response.ok) {
throw new Error("Failed to fetch categories");
}
const data = await response.json();
setCategories(data);
// Initialize logo indices
const initialLogoIndices: { [key: string]: number } = {};
data.forEach((category: any) => {
initialLogoIndices[category.name] = 0;
});
setLogoIndices(initialLogoIndices);
}
catch (error) {
console.error("Error fetching categories:", error);
}
};
fetchCategories();
}, []);
const handleCategoryClick = (index: number) => {
setSelectedCategoryIndex(index);
setCurrentScripts(categories[index]?.scripts || []); // Update scripts for the selected category
};
const handleBackClick = () => {
setSelectedCategoryIndex(null);
setCurrentScripts([]); // Clear scripts when going back
};
const handleScriptClick = (scriptSlug: string) => {
// Include category context when navigating to scripts
const categoryName = selectedCategoryIndex !== null ? categories[selectedCategoryIndex]?.name : null;
const queryParams = new URLSearchParams({ id: scriptSlug });
if (categoryName) {
queryParams.append("category", categoryName);
}
router.push(`/scripts?${queryParams.toString()}`);
};
const navigateCategory = (direction: "prev" | "next") => {
if (selectedCategoryIndex !== null) {
const newIndex
= direction === "prev"
? (selectedCategoryIndex - 1 + categories.length) % categories.length
: (selectedCategoryIndex + 1) % categories.length;
setSelectedCategoryIndex(newIndex);
setCurrentScripts(categories[newIndex]?.scripts || []); // Update scripts for the new category
}
};
const switchLogos = (categoryName: string, direction: "prev" | "next") => {
setLogoIndices((prev) => {
const currentIndex = prev[categoryName] || 0;
const category = categories.find(cat => cat.name === categoryName);
if (!category || !category.scripts)
return prev;
const totalLogos = category.scripts.length;
const newIndex
= direction === "prev"
? (currentIndex - MAX_LOGOS + totalLogos) % totalLogos
: (currentIndex + MAX_LOGOS) % totalLogos;
return { ...prev, [categoryName]: newIndex };
});
};
const truncateDescription = (text: string) => {
return text.length > MAX_DESCRIPTION_LENGTH ? `${text.slice(0, MAX_DESCRIPTION_LENGTH)}...` : text;
};
const renderResources = (script: any) => {
const cpu = script.install_methods[0]?.resources.cpu;
const ram = script.install_methods[0]?.resources.ram;
const hdd = script.install_methods[0]?.resources.hdd;
const resourceParts = [];
if (cpu) {
resourceParts.push(
<span key="cpu">
<b>CPU:</b>
{" "}
{cpu}
vCPU
</span>,
);
}
if (ram) {
resourceParts.push(
<span key="ram">
<b>RAM:</b>
{" "}
{ram}
MB
</span>,
);
}
if (hdd) {
resourceParts.push(
<span key="hdd">
<b>HDD:</b>
{" "}
{hdd}
GB
</span>,
);
}
return resourceParts.length > 0
? (
<div className="text-sm text-gray-400">
{resourceParts.map((part, index) => (
<React.Fragment key={index}>
{part}
{index < resourceParts.length - 1 && " | "}
</React.Fragment>
))}
</div>
)
: null;
};
return (
<div className="p-6 mt-20">
{categories.length === 0 && (
<p className="text-center text-gray-500">No categories available. Please check the API endpoint.</p>
)}
{selectedCategoryIndex !== null
? (
<div>
{/* Header with Navigation */}
<div className="flex items-center justify-between mb-6">
<Button
variant="ghost"
onClick={() => navigateCategory("prev")}
className="p-2 transition-transform duration-300 hover:scale-105"
>
<ChevronLeft className="h-6 w-6" />
</Button>
<h2 className="text-3xl font-semibold transition-opacity duration-300 hover:opacity-90">
{categories[selectedCategoryIndex].name}
</h2>
<Button
variant="ghost"
onClick={() => navigateCategory("next")}
className="p-2 transition-transform duration-300 hover:scale-105"
>
<ChevronRight className="h-6 w-6" />
</Button>
</div>
{/* Scripts Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{currentScripts
.sort((a, b) => a.name.localeCompare(b.name))
.map(script => (
<Card
key={script.name}
className="p-4 cursor-pointer hover:shadow-md transition-shadow duration-300"
onClick={() => handleScriptClick(script.slug)}
>
<CardContent className="flex flex-col gap-4">
<h3 className="text-lg font-bold script-text text-center hover:text-blue-600 transition-colors duration-300">
{script.name}
</h3>
<img
src={script.logo || defaultLogo}
alt={script.name || "Script logo"}
className="h-12 w-12 object-contain mx-auto"
/>
<p className="text-sm text-gray-500 text-center">
<b>Created at:</b>
{" "}
{script.date_created || "No date available"}
</p>
<p
className="text-sm text-gray-700 hover:text-gray-900 text-center transition-colors duration-300"
title={script.description || "No description available."}
>
{truncateDescription(script.description || "No description available.")}
</p>
{renderResources(script)}
</CardContent>
</Card>
))}
</div>
{/* Back to Categories Button */}
<div className="mt-8 text-center">
<Button
variant="default"
onClick={handleBackClick}
className="px-6 py-2 text-white bg-blue-600 hover:bg-blue-700 rounded-lg shadow-md transition-transform duration-300 hover:scale-105"
>
Back to Categories
</Button>
</div>
</div>
)
: (
<div>
{/* Categories Grid */}
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-semibold mb-4">Categories</h1>
<p className="text-sm text-gray-500">
{categories.reduce((total, category) => total + (category.scripts?.length || 0), 0)}
{" "}
Total scripts
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
{categories.map((category, index) => (
<Card
key={category.name}
onClick={() => handleCategoryClick(index)}
className="cursor-pointer hover:shadow-lg flex flex-col items-center justify-center py-6 transition-shadow duration-300"
>
<CardContent className="flex flex-col items-center">
<h3 className="text-xl font-bold mb-4 category-title transition-colors duration-300 hover:text-blue-600">
{category.name}
</h3>
<div className="flex justify-center items-center gap-2 mb-4">
<Button
variant="ghost"
onClick={(e) => {
e.stopPropagation();
switchLogos(category.name, "prev");
}}
className="p-1 transition-transform duration-300 hover:scale-110"
>
<ChevronLeft className="h-4 w-4" />
</Button>
{category.scripts
&& category.scripts
.slice(logoIndices[category.name] || 0, (logoIndices[category.name] || 0) + MAX_LOGOS)
.map((script, i) => (
<div key={i} className="flex flex-col items-center">
<img
src={script.logo || defaultLogo}
alt={script.name || "Script logo"}
title={script.name}
className="h-8 w-8 object-contain cursor-pointer"
onClick={(e) => {
e.stopPropagation();
handleScriptClick(script.slug);
}}
/>
{formattedBadge(script.type)}
</div>
))}
<Button
variant="ghost"
onClick={(e) => {
e.stopPropagation();
switchLogos(category.name, "next");
}}
className="p-1 transition-transform duration-300 hover:scale-110"
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
<p className="text-sm text-gray-400 text-center">
{(category as any).description || "No description available."}
</p>
</CardContent>
</Card>
))}
</div>
</div>
)}
</div>
);
}
export default CategoryView;

View File

@@ -94,8 +94,7 @@ const chartConfigApps = {
export default function DataPage() { export default function DataPage() {
const [data, setData] = useState<DataModel[]>([]); const [data, setData] = useState<DataModel[]>([]);
const [summary, setSummary] = useState<SummaryData | null>(null); const [summary, setSummary] = useState<SummaryData | null>(null);
const [summaryLoading, setSummaryLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [dataLoading, setDataLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(25); const [itemsPerPage, setItemsPerPage] = useState(25);
@@ -106,40 +105,35 @@ export default function DataPage() {
const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }); const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
// Fetch summary only once on mount
useEffect(() => { useEffect(() => {
const fetchSummary = async () => { const fetchData = async () => {
setLoading(true);
try { try {
const summaryRes = await fetch("https://api.htl-braunau.at/data/summary"); const [summaryRes, dataRes] = await Promise.all([
fetch("https://api.htl-braunau.at/data/summary"),
fetch(
`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${
itemsPerPage === 0 ? "" : itemsPerPage
}`,
),
]);
if (!summaryRes.ok) { if (!summaryRes.ok) {
throw new Error(`Failed to fetch summary: ${summaryRes.statusText}`); throw new Error(`Failed to fetch summary: ${summaryRes.statusText}`);
} }
const summaryData: SummaryData = await summaryRes.json();
setSummary(summaryData);
} catch (err) {
setError((err as Error).message);
} finally {
setSummaryLoading(false);
}
};
fetchSummary();
}, []);
useEffect(() => {
const fetchData = async () => {
setDataLoading(true);
try {
const dataRes = await fetch(`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage}`);
if (!dataRes.ok) { if (!dataRes.ok) {
throw new Error(`Failed to fetch data: ${dataRes.statusText}`); throw new Error(`Failed to fetch data: ${dataRes.statusText}`);
} }
const summaryData: SummaryData = await summaryRes.json();
const pageData: DataModel[] = await dataRes.json(); const pageData: DataModel[] = await dataRes.json();
setSummary(summaryData);
setData(pageData); setData(pageData);
} catch (err) { } catch (err) {
setError((err as Error).message); setError((err as Error).message);
} finally { } finally {
setDataLoading(false); setLoading(false);
} }
}; };
@@ -312,7 +306,7 @@ export default function DataPage() {
</CardHeader> </CardHeader>
<CardContent className="pl-2"> <CardContent className="pl-2">
<div className="h-[300px] w-full"> <div className="h-[300px] w-full">
{summaryLoading ? ( {loading ? (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div> </div>
@@ -417,7 +411,7 @@ export default function DataPage() {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{dataLoading ? ( {loading ? (
<TableRow> <TableRow>
<TableCell colSpan={8} className="h-24 text-center"> <TableCell colSpan={8} className="h-24 text-center">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
@@ -484,7 +478,7 @@ export default function DataPage() {
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))} onClick={() => setCurrentPage((prev) => Math.max(prev - 1, 1))}
disabled={currentPage === 1 || dataLoading} disabled={currentPage === 1 || loading}
> >
<ChevronLeft className="mr-2 h-4 w-4" /> <ChevronLeft className="mr-2 h-4 w-4" />
Previous Previous
@@ -494,7 +488,7 @@ export default function DataPage() {
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setCurrentPage((prev) => prev + 1)} onClick={() => setCurrentPage((prev) => prev + 1)}
disabled={dataLoading || sortedData.length < itemsPerPage} disabled={loading || sortedData.length < itemsPerPage}
> >
Next Next
<ChevronRight className="ml-2 h-4 w-4" /> <ChevronRight className="ml-2 h-4 w-4" />

View File

@@ -105,7 +105,7 @@ function Note({
const addNote = useCallback(() => { const addNote = useCallback(() => {
setScript({ setScript({
...script, ...script,
notes: [...script.notes, { text: "", type: "info" }], notes: [...script.notes, { text: "", type: "" }],
}); });
}, [script, setScript]); }, [script, setScript]);

View File

@@ -1,5 +1,4 @@
import { z } from "zod"; import { z } from "zod";
import { AlertColors } from "@/config/site-config";
export const InstallMethodSchema = z.object({ export const InstallMethodSchema = z.object({
type: z.enum(["default", "alpine"], { type: z.enum(["default", "alpine"], {
@@ -17,9 +16,7 @@ export const InstallMethodSchema = z.object({
const NoteSchema = z.object({ const NoteSchema = z.object({
text: z.string().min(1, "Note text cannot be empty"), text: z.string().min(1, "Note text cannot be empty"),
type: z.enum(Object.keys(AlertColors) as [keyof typeof AlertColors, ...(keyof typeof AlertColors)[]], { type: z.string().min(1, "Note type cannot be empty"),
message: `Type must be one of: ${Object.keys(AlertColors).join(", ")}`,
}),
}); });
export const ScriptSchema = z.object({ export const ScriptSchema = z.object({
@@ -45,7 +42,7 @@ export const ScriptSchema = z.object({
username: z.string().nullable(), username: z.string().nullable(),
password: z.string().nullable(), password: z.string().nullable(),
}), }),
notes: z.array(NoteSchema).optional().default([]), notes: z.array(NoteSchema),
}).refine((data) => { }).refine((data) => {
if (data.disable === true && !data.disable_description) { if (data.disable === true && !data.disable_description) {
return false; return false;

View File

@@ -18,7 +18,6 @@ import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { fetchCategories } from "@/lib/data"; import { fetchCategories } from "@/lib/data";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -29,10 +28,8 @@ import { ScriptSchema } from "./_schemas/schemas";
import Categories from "./_components/categories"; import Categories from "./_components/categories";
import Note from "./_components/note"; import Note from "./_components/note";
import { githubGist, nord } from "react-syntax-highlighter/dist/esm/styles/hljs"; import { nord } from "react-syntax-highlighter/dist/esm/styles/hljs";
import SyntaxHighlighter from "react-syntax-highlighter"; import SyntaxHighlighter from "react-syntax-highlighter";
import { ScriptItem } from "../scripts/_components/script-item";
import { useTheme } from "next-themes";
const initialScript: Script = { const initialScript: Script = {
name: "", name: "",
@@ -59,12 +56,10 @@ const initialScript: Script = {
}; };
export default function JSONGenerator() { export default function JSONGenerator() {
const { theme } = useTheme();
const [script, setScript] = useState<Script>(initialScript); const [script, setScript] = useState<Script>(initialScript);
const [isCopied, setIsCopied] = useState(false); const [isCopied, setIsCopied] = useState(false);
const [isValid, setIsValid] = useState(false); const [isValid, setIsValid] = useState(false);
const [categories, setCategories] = useState<Category[]>([]); const [categories, setCategories] = useState<Category[]>([]);
const [currentTab, setCurrentTab] = useState<"json" | "preview">("json");
const [zodErrors, setZodErrors] = useState<z.ZodError | null>(null); const [zodErrors, setZodErrors] = useState<z.ZodError | null>(null);
useEffect(() => { useEffect(() => {
@@ -73,13 +68,6 @@ export default function JSONGenerator() {
.catch((error) => console.error("Error fetching categories:", error)); .catch((error) => console.error("Error fetching categories:", error));
}, []); }, []);
useEffect(() => {
if (!isValid && currentTab === "preview") {
setCurrentTab("json");
toast.error("Switched to JSON tab due to invalid configuration.");
}
}, [isValid, currentTab]);
const updateScript = useCallback((key: keyof Script, value: Script[keyof Script]) => { const updateScript = useCallback((key: keyof Script, value: Script[keyof Script]) => {
setScript((prev) => { setScript((prev) => {
const updated = { ...prev, [key]: value }; const updated = { ...prev, [key]: value };
@@ -208,7 +196,7 @@ export default function JSONGenerator() {
<Input <Input
placeholder="Path to config file" placeholder="Path to config file"
value={script.config_path || ""} value={script.config_path || ""}
onChange={(e) => updateScript("config_path", e.target.value || "")} onChange={(e) => updateScript("config_path", e.target.value || null)}
/> />
</div> </div>
<div> <div>
@@ -335,41 +323,25 @@ export default function JSONGenerator() {
</form> </form>
</div> </div>
<div className="w-1/2 p-4 bg-background overflow-y-auto"> <div className="w-1/2 p-4 bg-background overflow-y-auto">
<Tabs {validationAlert}
defaultValue="json" <div className="relative">
className="w-full" <div className="absolute right-2 top-2 flex gap-1">
onValueChange={(value) => setCurrentTab(value as "json" | "preview")} <Button size="icon" variant="outline" onClick={handleCopy}>
value={currentTab} {isCopied ? <Check className="h-4 w-4" /> : <Clipboard className="h-4 w-4" />}
> </Button>
<TabsList className="grid w-full grid-cols-2"> <Button size="icon" variant="outline" onClick={handleDownload}>
<TabsTrigger value="json">JSON</TabsTrigger> <Download className="h-4 w-4" />
<TabsTrigger disabled={!isValid} value="preview">Preview</TabsTrigger> </Button>
</TabsList> </div>
<TabsContent value="json" className="h-full w-full">
{validationAlert}
<div className="relative">
<div className="absolute right-2 top-2 flex gap-1">
<Button size="icon" variant="outline" onClick={handleCopy}>
{isCopied ? <Check className="h-4 w-4" /> : <Clipboard className="h-4 w-4" />}
</Button>
<Button size="icon" variant="outline" onClick={handleDownload}>
<Download className="h-4 w-4" />
</Button>
</div>
<SyntaxHighlighter <SyntaxHighlighter
language="json" language="json"
style={theme === "light" ? githubGist : nord} style={nord}
className="mt-4 p-4 bg-secondary rounded shadow overflow-x-scroll" className="mt-4 p-4 bg-secondary rounded shadow overflow-x-scroll"
> >
{JSON.stringify(script, null, 2)} {JSON.stringify(script, null, 2)}
</SyntaxHighlighter> </SyntaxHighlighter>
</div> </div>
</TabsContent>
<TabsContent value="preview" className="h-full w-full">
<ScriptItem item={script} />
</TabsContent>
</Tabs>
</div> </div>
</div> </div>
); );

View File

@@ -4,8 +4,7 @@ import { X, HelpCircle } from "lucide-react";
import { Suspense } from "react"; import { Suspense } from "react";
import Image from "next/image"; import Image from "next/image";
import type { AppVersion } from "@/lib/types"; import type { AppVersion, Script } from "@/lib/types";
import type { Script } from "@/app/json-editor/_schemas/schemas";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
@@ -27,6 +26,7 @@ import Alerts from "./script-items/alerts";
type ScriptItemProps = { type ScriptItemProps = {
item: Script; item: Script;
setSelectedScript: (script: string | null) => void;
}; };
function ScriptHeader({ item }: { item: Script }) { function ScriptHeader({ item }: { item: Script }) {
@@ -135,10 +135,25 @@ function VersionInfo({ item }: { item: Script }) {
); );
} }
export function ScriptItem({ item }: ScriptItemProps) { export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {
const closeScript = () => {
window.history.pushState({}, document.title, window.location.pathname);
setSelectedScript(null);
};
return ( return (
<div className="w-full mx-auto"> <div className="w-full mx-auto">
<div className="flex w-full flex-col"> <div className="flex w-full flex-col">
<div className="mb-3 flex items-center justify-between">
<h2 className="text-2xl font-semibold tracking-tight text-foreground/90">Selected Script</h2>
<button
onClick={closeScript}
className="rounded-full p-2 text-muted-foreground hover:bg-card/50 transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="rounded-xl border border-border bg-accent/30 backdrop-blur-sm shadow-sm"> <div className="rounded-xl border border-border bg-accent/30 backdrop-blur-sm shadow-sm">
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<Suspense fallback={<div className="animate-pulse h-32 bg-accent/20 rounded-xl" />}> <Suspense fallback={<div className="animate-pulse h-32 bg-accent/20 rounded-xl" />}>
@@ -147,7 +162,7 @@ export function ScriptItem({ item }: ScriptItemProps) {
{item.disable && item.disable_description && ( {item.disable && item.disable_description && (
<DisableDescription item={item} /> <DisableDescription item={item} />
)} ) }
{!item.disable && ( {!item.disable && (
<> <>

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect, useState } from "react";
import { Loader2, X } from "lucide-react"; import { Loader2 } from "lucide-react";
import { useQueryState } from "nuqs"; import { useQueryState } from "nuqs";
import type { Category, Script } from "@/lib/types"; import type { Category, Script } from "@/lib/types";
@@ -20,11 +20,6 @@ function ScriptContent() {
const [item, setItem] = useState<Script>(); const [item, setItem] = useState<Script>();
const [latestPage, setLatestPage] = useState(1); const [latestPage, setLatestPage] = useState(1);
const closeScript = () => {
window.history.pushState({}, document.title, window.location.pathname);
setSelectedScript(null);
};
useEffect(() => { useEffect(() => {
if (selectedScript && links.length > 0) { if (selectedScript && links.length > 0) {
const script = links const script = links
@@ -58,18 +53,7 @@ function ScriptContent() {
<div className="px-4 w-full sm:max-w-[calc(100%-350px-16px)]"> <div className="px-4 w-full sm:max-w-[calc(100%-350px-16px)]">
{selectedScript && item {selectedScript && item
? ( ? (
<div className="flex w-full flex-col"> <ScriptItem item={item} setSelectedScript={setSelectedScript} />
<div className="mb-3 flex items-center justify-between">
<h2 className="text-2xl font-semibold tracking-tight text-foreground/90">Selected Script</h2>
<button
onClick={closeScript}
className="rounded-full p-2 text-muted-foreground hover:bg-card/50 transition-colors"
>
<X className="h-5 w-5" />
</button>
</div>
<ScriptItem item={item} />
</div>
) )
: ( : (
<div className="flex w-full flex-col gap-5"> <div className="flex w-full flex-col gap-5">

View File

@@ -23,34 +23,6 @@ import { Button } from "./ui/button";
import { Badge } from "./ui/badge"; import { Badge } from "./ui/badge";
import Link from "next/link"; import Link from "next/link";
function search(scripts: Script[], query: string): Script[] {
const queryLower = query.toLowerCase().trim();
const searchWords = queryLower.split(/\s+/).filter(Boolean);
return scripts
.map(script => {
const nameLower = script.name.toLowerCase();
const descriptionLower = (script.description || "").toLowerCase();
let score = 0;
for (const word of searchWords) {
if (nameLower.includes(word)) {
score += 10;
}
if (descriptionLower.includes(word)) {
score += 5;
}
}
return { script, score };
})
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, 20)
.map(({ script }) => script);
}
export function formattedBadge(type: string) { export function formattedBadge(type: string) {
switch (type) { switch (type) {
case "vm": case "vm":
@@ -79,11 +51,9 @@ function getRandomScript(categories: Category[], previouslySelected: Set<string>
} }
function CommandMenu() { function CommandMenu() {
const [query, setQuery] = React.useState("");
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [links, setLinks] = React.useState<Category[]>([]); const [links, setLinks] = React.useState<Category[]>([]);
const [isLoading, setIsLoading] = React.useState(false); const [isLoading, setIsLoading] = React.useState(false);
const [results, setResults] = React.useState<Script[]>([]);
const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set()); const [selectedScripts, setSelectedScripts] = React.useState<Set<string>>(new Set());
const router = useRouter(); const router = useRouter();
@@ -100,27 +70,6 @@ function CommandMenu() {
}); });
}; };
React.useEffect(() => {
if (query.trim() === "") {
fetchSortedCategories();
}
else {
const scriptMap = new Map<string, Script>();
for (const category of links) {
for (const script of category.scripts || []) {
if (!scriptMap.has(script.slug)) {
scriptMap.set(script.slug, script);
}
}
}
const uniqueScripts = Array.from(scriptMap.values());
const filteredResults = search(uniqueScripts, query);
setResults(filteredResults);
}
}, [query]);
React.useEffect(() => { React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "k" && (e.metaKey || e.ctrlKey)) { if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
@@ -248,20 +197,20 @@ function CommandMenu() {
<CommandDialog <CommandDialog
open={open} open={open}
onOpenChange={(open) => { onOpenChange={setOpen}
setOpen(open); filter={(value: string, search: string) => {
if (open) { const searchLower = search.toLowerCase().trim();
setQuery(""); if (!searchLower)
setResults([]); return 1;
} const valueLower = value.toLowerCase();
const searchWords = searchLower.split(/\s+/).filter(Boolean);
// All search words must appear somewhere in the value (name + description)
const allWordsMatch = searchWords.every((word: string) => valueLower.includes(word));
return allWordsMatch ? 1 : 0;
}} }}
> >
<DialogTitle className="sr-only">Search scripts</DialogTitle> <DialogTitle className="sr-only">Search scripts</DialogTitle>
<CommandInput <CommandInput placeholder="Search for a script..." />
placeholder="Search for a script..."
onValueChange={setQuery}
value={query}
/>
<CommandList> <CommandList>
<CommandEmpty> <CommandEmpty>
{isLoading ? ( {isLoading ? (
@@ -284,10 +233,9 @@ function CommandMenu() {
</div> </div>
)} )}
</CommandEmpty> </CommandEmpty>
{Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
{results.length > 0 ? ( <CommandGroup key={`category:${categoryName}`} heading={categoryName}>
<CommandGroup heading="Search Results"> {scripts.map(script => (
{results.map(script => (
<CommandItem <CommandItem
key={`script:${script.slug}`} key={`script:${script.slug}`}
value={`${script.name} ${script.type} ${script.description || ""}`} value={`${script.name} ${script.type} ${script.description || ""}`}
@@ -320,44 +268,7 @@ function CommandMenu() {
</CommandItem> </CommandItem>
))} ))}
</CommandGroup> </CommandGroup>
) : ( // When no search results, show all scripts grouped by category ))}
Object.entries(uniqueScriptsByCategory).map(([categoryName, scripts]) => (
<CommandGroup key={`category:${categoryName}`} heading={categoryName}>
{scripts.map(script => (
<CommandItem
key={`script:${script.slug}`}
value={`${script.name} ${script.type} ${script.description || ""}`}
onSelect={() => {
setOpen(false);
router.push(`/scripts?id=${script.slug}`);
}}
tabIndex={0}
aria-label={`Open script ${script.name}`}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
setOpen(false);
router.push(`/scripts?id=${script.slug}`);
}
}}
>
<div className="flex gap-2" onClick={() => setOpen(false)}>
<Image
src={script.logo || `/${basePath}/logo.png`}
onError={e => ((e.currentTarget as HTMLImageElement).src = `/${basePath}/logo.png`)}
unoptimized
width={16}
height={16}
alt=""
className="h-5 w-5"
/>
<span>{script.name}</span>
<span>{formattedBadge(script.type)}</span>
</div>
</CommandItem>
))}
</CommandGroup>
))
)}
</CommandList> </CommandList>
</CommandDialog> </CommandDialog>
</> </>

View File

@@ -119,6 +119,7 @@ function MobileSidebar() {
<p className="text-sm font-medium">Last Viewed</p> <p className="text-sm font-medium">Last Viewed</p>
<ScriptItem <ScriptItem
item={lastViewedScript} item={lastViewedScript}
setSelectedScript={isOnScriptsPage ? setSelectedScript : setTempSelectedScript}
/> />
</div> </div>
) )
@@ -130,4 +131,3 @@ function MobileSidebar() {
} }
export default MobileSidebar; export default MobileSidebar;

View File

@@ -5,7 +5,7 @@ export type Script = {
slug: string; slug: string;
categories: number[]; categories: number[];
date_created: string; date_created: string;
type: "vm" | "ct" | "pve" | "addon" | "turnkey"; type: "vm" | "ct" | "pve" | "addon";
updateable: boolean; updateable: boolean;
privileged: boolean; privileged: boolean;
interface_port: number | null; interface_port: number | null;
@@ -31,10 +31,12 @@ export type Script = {
username: string | null; username: string | null;
password: string | null; password: string | null;
}; };
notes: { notes: [
text: string; {
type: keyof typeof AlertColors; text: string;
}[]; type: keyof typeof AlertColors;
},
];
}; };
export type Category = { export type Category = {

View File

@@ -1,55 +0,0 @@
#!/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://github.com/orhun/rustypaste
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing RustyPaste"
$STD apk add --no-cache rustypaste --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
msg_ok "Installed RustyPaste"
msg_info "Configuring RustyPaste"
mkdir -p /var/lib/rustypaste
sed -i 's|^address = ".*"|address = "0.0.0.0:8000"|' /etc/rustypaste/config.toml
msg_ok "Configured RustyPaste"
msg_info "Creating Service"
cat <<'EOF' >/etc/init.d/rustypaste
#!/sbin/openrc-run
name="rustypaste"
description="RustyPaste - A minimal file upload/pastebin service"
command="/usr/bin/rustypaste"
command_args=""
command_user="root"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
directory="/var/lib/rustypaste"
depend() {
need net
after firewall
}
start_pre() {
export CONFIG=/etc/rustypaste/config.toml
checkpath --directory --owner root:root --mode 0755 /var/lib/rustypaste
}
EOF
chmod +x /etc/init.d/rustypaste
$STD rc-update add rustypaste default
$STD rc-service rustypaste start
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -3,7 +3,7 @@
# Copyright (c) 2021-2026 community-scripts ORG # Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream # Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://codeberg.org/gelbphoenix/autocaliweb # Source: https://github.com/gelbphoenix/autocaliweb
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH" source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color color
@@ -56,7 +56,7 @@ msg_ok "Installed Calibre"
setup_uv setup_uv
fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb" fetch_and_deploy_gh_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb"
msg_info "Configuring Autocaliweb" msg_info "Configuring Autocaliweb"
INSTALL_DIR="/opt/autocaliweb" INSTALL_DIR="/opt/autocaliweb"
@@ -111,8 +111,8 @@ msg_info "Initializing databases"
KEPUBIFY_PATH=$(command -v kepubify 2>/dev/null || echo "/usr/bin/kepubify") KEPUBIFY_PATH=$(command -v kepubify 2>/dev/null || echo "/usr/bin/kepubify")
EBOOK_CONVERT_PATH=$(command -v ebook-convert 2>/dev/null || echo "/usr/bin/ebook-convert") EBOOK_CONVERT_PATH=$(command -v ebook-convert 2>/dev/null || echo "/usr/bin/ebook-convert")
CALIBRE_BIN_DIR=$(dirname "$EBOOK_CONVERT_PATH") CALIBRE_BIN_DIR=$(dirname "$EBOOK_CONVERT_PATH")
curl -fsSL https://codeberg.org/gelbphoenix/autocaliweb/raw/branch/main/library/metadata.db -o "$CALIBRE_LIB_DIR"/metadata.db curl -fsSL https://github.com/gelbphoenix/autocaliweb/raw/refs/heads/main/library/metadata.db -o "$CALIBRE_LIB_DIR"/metadata.db
curl -fsSL https://codeberg.org/gelbphoenix/autocaliweb/raw/branch/main/library/app.db -o "$CONFIG_DIR"/app.db curl -fsSL https://github.com/gelbphoenix/autocaliweb/raw/refs/heads/main/library/app.db -o "$CONFIG_DIR"/app.db
sqlite3 "$CONFIG_DIR/app.db" <<EOS sqlite3 "$CONFIG_DIR/app.db" <<EOS
UPDATE settings SET UPDATE settings SET
config_kepubifypath='$KEPUBIFY_PATH', config_kepubifypath='$KEPUBIFY_PATH',

View File

@@ -14,13 +14,17 @@ network_check
update_os update_os
msg_info "Installing Dependencies" msg_info "Installing Dependencies"
$STD apt install -y \ $STD apt-get install -y git
git \ $STD apt-get install -y git-lfs
git-lfs
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
fetch_and_deploy_codeberg_release "forgejo" "forgejo/forgejo" "singlefile" "latest" "/opt/forgejo" "forgejo-*-linux-amd64" msg_info "Installing Forgejo"
ln -sf /opt/forgejo/forgejo /usr/local/bin/forgejo mkdir -p /opt/forgejo
RELEASE=$(curl -fsSL https://codeberg.org/api/v1/repos/forgejo/forgejo/releases/latest | grep -oP '"tag_name":\s*"\K[^"]+' | sed 's/^v//')
curl -fsSL "https://codeberg.org/forgejo/forgejo/releases/download/v${RELEASE}/forgejo-${RELEASE}-linux-amd64" -o "/opt/forgejo/forgejo-$RELEASE-linux-amd64"
chmod +x /opt/forgejo/forgejo-$RELEASE-linux-amd64
ln -sf /opt/forgejo/forgejo-$RELEASE-linux-amd64 /usr/local/bin/forgejo
msg_ok "Installed Forgejo"
msg_info "Setting up Forgejo" msg_info "Setting up Forgejo"
$STD adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git $STD adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git

View File

@@ -17,8 +17,7 @@ msg_info "Installing Dependencies"
$STD apt install -y \ $STD apt install -y \
make \ make \
ca-certificates \ ca-certificates \
python3-venv \ python3-venv
git
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
NODE_VERSION="22" NODE_MODULE="yarn@latest" setup_nodejs NODE_VERSION="22" NODE_MODULE="yarn@latest" setup_nodejs
fetch_and_deploy_gh_release "grist" "gristlabs/grist-core" "tarball" fetch_and_deploy_gh_release "grist" "gristlabs/grist-core" "tarball"

View File

@@ -13,44 +13,44 @@ setting_up_container
network_check network_check
update_os update_os
if [ -d /dev/dri ]; then echo ""
echo "" echo ""
echo "" echo -e "🤖 ${BL}Immich Machine Learning Options${CL}"
echo -e "🤖 ${BL}Immich Machine Learning Options${CL}" echo "─────────────────────────────────────────"
echo "─────────────────────────────────────────" echo "Please choose your machine-learning type:"
echo "Please choose your machine-learning type:" echo ""
echo "" echo " 1) CPU only (default)"
echo " 1) CPU only (default)" echo " 2) Intel OpenVINO (requires GPU passthrough)"
echo " 2) Intel OpenVINO (requires GPU passthrough)" echo ""
echo ""
read -r -p "${TAB3}Select machine-learning type [1]: " ML_TYPE read -r -p "${TAB3}Select machine-learning type [1]: " ML_TYPE
ML_TYPE="${ML_TYPE:-1}" ML_TYPE="${ML_TYPE:-1}"
if [[ "$ML_TYPE" == "2" ]]; then if [[ "$ML_TYPE" == "2" ]]; then
msg_info "Installing OpenVINO dependencies" msg_info "Installing OpenVINO dependencies"
touch ~/.openvino touch ~/.openvino
$STD apt install -y --no-install-recommends patchelf $STD apt install -y --no-install-recommends patchelf
tmp_dir=$(mktemp -d) tmp_dir=$(mktemp -d)
$STD pushd "$tmp_dir" $STD pushd "$tmp_dir"
curl -fsSLO https://raw.githubusercontent.com/immich-app/base-images/refs/heads/main/server/Dockerfile curl -fsSLO https://raw.githubusercontent.com/immich-app/base-images/refs/heads/main/server/Dockerfile
readarray -t INTEL_URLS < <( readarray -t INTEL_URLS < <(
sed -n "/intel-[igc|opencl]/p" ./Dockerfile | awk '{print $2}' sed -n "/intel-[igc|opencl]/p" ./Dockerfile | awk '{print $2}'
sed -n "/libigdgmm12/p" ./Dockerfile | awk '{print $3}' sed -n "/libigdgmm12/p" ./Dockerfile | awk '{print $3}'
) )
for url in "${INTEL_URLS[@]}"; do for url in "${INTEL_URLS[@]}"; do
curl -fsSLO "$url" curl -fsSLO "$url"
done done
$STD apt install -y ./libigdgmm12*.deb $STD apt install -y ./libigdgmm12*.deb
rm ./libigdgmm12*.deb rm ./libigdgmm12*.deb
$STD apt install -y ./*.deb $STD apt install -y ./*.deb
$STD apt-mark hold libigdgmm12 $STD apt-mark hold libigdgmm12
$STD popd $STD popd
rm -rf "$tmp_dir" rm -rf "$tmp_dir"
dpkg-query -W -f='${Version}\n' intel-opencl-icd >~/.intel_version dpkg-query -W -f='${Version}\n' intel-opencl-icd >~/.intel_version
msg_ok "Installed OpenVINO dependencies" msg_ok "Installed OpenVINO dependencies"
fi
fi fi
setup_uv
msg_info "Installing dependencies" msg_info "Installing dependencies"
$STD apt install --no-install-recommends -y \ $STD apt install --no-install-recommends -y \
git \ git \
@@ -144,7 +144,8 @@ msg_info "Installing packages from Debian Testing repo"
$STD apt install -t testing --no-install-recommends -yqq libmimalloc3 libde265-dev $STD apt install -t testing --no-install-recommends -yqq libmimalloc3 libde265-dev
msg_ok "Installed packages from Debian Testing repo" msg_ok "Installed packages from Debian Testing repo"
setup_uv PNPM_VERSION="$(curl -fsSL "https://raw.githubusercontent.com/immich-app/immich/refs/heads/main/package.json" | jq -r '.packageManager | split("@")[1]')"
NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
VCHORD_RELEASE="0.5.3" VCHORD_RELEASE="0.5.3"
@@ -289,9 +290,7 @@ ML_DIR="${APP_DIR}/machine-learning"
GEO_DIR="${INSTALL_DIR}/geodata" GEO_DIR="${INSTALL_DIR}/geodata"
mkdir -p {"${APP_DIR}","${UPLOAD_DIR}","${GEO_DIR}","${INSTALL_DIR}"/cache} mkdir -p {"${APP_DIR}","${UPLOAD_DIR}","${GEO_DIR}","${INSTALL_DIR}"/cache}
fetch_and_deploy_gh_release "immich" "immich-app/immich" "tarball" "v2.5.3" "$SRC_DIR" fetch_and_deploy_gh_release "immich" "immich-app/immich" "tarball" "v2.5.2" "$SRC_DIR"
PNPM_VERSION="$(jq -r '.packageManager | split("@")[1]' ${SRC_DIR}/package.json)"
NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs
msg_info "Installing Immich (patience)" msg_info "Installing Immich (patience)"

View File

@@ -1,145 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: snazzybean
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/TomBursch/kitchenowl
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 \
build-essential \
gfortran \
pkg-config \
ninja-build \
autoconf \
automake \
libpq-dev \
libffi-dev \
libssl-dev \
libpcre2-dev \
libre2-dev \
libxml2-dev \
libxslt-dev \
libopenblas-dev \
liblapack-dev \
zlib1g-dev \
libjpeg62-turbo-dev \
libsqlite3-dev \
libexpat1-dev \
libicu-dev
msg_ok "Installed Dependencies"
PYTHON_VERSION="3.14" setup_uv
fetch_and_deploy_gh_release "kitchenowl" "TomBursch/kitchenowl" "tarball" "latest" "/opt/kitchenowl"
rm -rf /opt/kitchenowl/web
fetch_and_deploy_gh_release "kitchenowl-web" "TomBursch/kitchenowl" "prebuild" "latest" "/opt/kitchenowl/web" "kitchenowl_Web.tar.gz"
msg_info "Setting up KitchenOwl"
cd /opt/kitchenowl/backend
$STD uv sync --no-dev
sed -i 's/default=True/default=False/' /opt/kitchenowl/backend/wsgi.py
mkdir -p /nltk_data
$STD uv run python -m nltk.downloader -d /nltk_data averaged_perceptron_tagger_eng
JWT_SECRET=$(openssl rand -hex 32)
mkdir -p /opt/kitchenowl/data
cat <<EOF >/opt/kitchenowl/kitchenowl.env
STORAGE_PATH=/opt/kitchenowl/data
JWT_SECRET_KEY=${JWT_SECRET}
NLTK_DATA=/nltk_data
FRONT_URL=http://${LOCAL_IP}
FLASK_APP=wsgi.py
FLASK_ENV=production
EOF
set -a
source /opt/kitchenowl/kitchenowl.env
set +a
$STD uv run flask db upgrade
msg_ok "Set up KitchenOwl"
msg_info "Creating Systemd Service"
cat <<EOF >/etc/systemd/system/kitchenowl.service
[Unit]
Description=KitchenOwl Backend
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kitchenowl/backend
EnvironmentFile=/opt/kitchenowl/kitchenowl.env
ExecStart=/usr/local/bin/uv run wsgi.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now kitchenowl
msg_ok "Created and Started Service"
msg_info "Configuring Nginx"
rm -f /etc/nginx/sites-enabled/default
cat <<'EOF' >/etc/nginx/sites-available/kitchenowl.conf
server {
listen 80;
server_name _;
root /opt/kitchenowl/web;
index index.html;
client_max_body_size 100M;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /socket.io {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# WebSocket Timeouts - allow long-lived connections
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
EOF
ln -sf /etc/nginx/sites-available/kitchenowl.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
$STD systemctl reload nginx
msg_ok "Configured Nginx"
motd_ssh
customize
cleanup_lxc

View File

@@ -31,7 +31,7 @@ After=network-online.target
User=root User=root
Restart=always Restart=always
Type=simple Type=simple
ExecStart=/usr/bin/prometheus-paperless-exporter \ ExecStart=/usr/local/bin/prometheus-paperless-exporter \
--paperless_url=http://paperless.example.org \ --paperless_url=http://paperless.example.org \
--paperless_auth_token_file=/etc/prometheus-paperless-ngx-exporter/paperless_auth_token_file --paperless_auth_token_file=/etc/prometheus-paperless-ngx-exporter/paperless_auth_token_file
ExecReload=/bin/kill -HUP \$MAINPID ExecReload=/bin/kill -HUP \$MAINPID

View File

@@ -13,7 +13,13 @@ setting_up_container
network_check network_check
update_os update_os
fetch_and_deploy_codeberg_release "readeck" "readeck/readeck" "singlefile" "latest" "/opt/readeck" "readeck-*-linux-amd64" msg_info "Installing Readeck"
LATEST=$(curl -fsSL https://codeberg.org/readeck/readeck/releases/ | grep -oP '/releases/tag/\K\d+\.\d+\.\d+' | head -1)
mkdir -p /opt/readeck
cd /opt/readeck
curl -fsSL "https://codeberg.org/readeck/readeck/releases/download/${LATEST}/readeck-${LATEST}-linux-amd64" -o "readeck"
chmod a+x readeck
msg_ok "Installed Readeck"
msg_info "Creating Service" msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/readeck.service cat <<EOF >/etc/systemd/system/readeck.service

View File

@@ -1,43 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: GoldenSpringness | MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/orhun/rustypaste
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
fetch_and_deploy_gh_release "rustypaste" "orhun/rustypaste" "prebuild" "latest" "/opt/rustypaste" "*x86_64-unknown-linux-gnu.tar.gz"
fetch_and_deploy_gh_release "rustypaste-cli" "orhun/rustypaste-cli" "prebuild" "latest" "/usr/local/bin" "*x86_64-unknown-linux-gnu.tar.gz"
msg_info "Setting up RustyPaste"
cd /opt/rustypaste
sed -i 's|^address = ".*"|address = "0.0.0.0:8000"|' config.toml
msg_ok "Set up RustyPaste"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/rustypaste.service
[Unit]
Description=rustypaste Service
After=network.target
[Service]
WorkingDirectory=/opt/rustypaste
ExecStart=/opt/rustypaste/rustypaste
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now rustypaste
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -23,7 +23,7 @@ msg_ok "Installed Dependencies"
PG_VERSION=17 setup_postgresql PG_VERSION=17 setup_postgresql
NODE_VERSION="24" setup_nodejs NODE_VERSION="24" setup_nodejs
PG_DB_NAME="scanopy_db" PG_DB_USER="scanopy" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db PG_DB_NAME="scanopy_db" PG_DB_USER="scanopy" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
fetch_and_deploy_gh_release "Scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy" fetch_and_deploy_gh_release "scanopy" "scanopy/scanopy" "tarball" "latest" "/opt/scanopy"
TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')" TOOLCHAIN="$(grep "channel" /opt/scanopy/backend/rust-toolchain.toml | awk -F\" '{print $2}')"
RUST_TOOLCHAIN=$TOOLCHAIN setup_rust RUST_TOOLCHAIN=$TOOLCHAIN setup_rust
@@ -35,40 +35,45 @@ $STD npm ci --no-fund --no-audit
$STD npm run build $STD npm run build
msg_ok "Created frontend UI" msg_ok "Created frontend UI"
msg_info "Building Scanopy Server (patience)" msg_info "Building scanopy-server (patience)"
cd /opt/scanopy/backend cd /opt/scanopy/backend
$STD cargo build --release --bin server $STD cargo build --release --bin server
mv ./target/release/server /usr/bin/scanopy-server mv ./target/release/server /usr/bin/scanopy-server
msg_ok "Built Scanopy Server" msg_ok "Built scanopy-server"
msg_info "Building scanopy-daemon"
$STD cargo build --release --bin daemon
cp ./target/release/daemon /usr/bin/scanopy-daemon
msg_ok "Built scanopy-daemon"
msg_info "Configuring server for first-run" msg_info "Configuring server for first-run"
cat <<EOF >/opt/scanopy/.env cat <<EOF >/opt/scanopy/.env
### - SERVER ### - SERVER
SCANOPY_DATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME scanopy_DATABASE_URL=postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME
SCANOPY_WEB_EXTERNAL_PATH="/opt/scanopy/ui/build" scanopy_WEB_EXTERNAL_PATH="/opt/scanopy/ui/build"
SCANOPY_PUBLIC_URL=http://${LOCAL_IP}:60072 scanopy_PUBLIC_URL=http://${LOCAL_IP}:60072
SCANOPY_SERVER_PORT=60072 scanopy_SERVER_PORT=60072
SCANOPY_LOG_LEVEL=info scanopy_LOG_LEVEL=info
SCANOPY_INTEGRATED_DAEMON_URL=http://127.0.0.1:60073 scanopy_INTEGRATED_DAEMON_URL=http://127.0.0.1:60073
## - uncomment to disable signups ## - uncomment to disable signups
# SCANOPY_DISABLE_REGISTRATION=true # scanopy_DISABLE_REGISTRATION=true
## - uncomment when using TLS ## - uncomment when using TLS
# SCANOPY_USE_SECURE_SESSION_COOKIES=true # scanopy_USE_SECURE_SESSION_COOKIES=true
## - see https://github.com/imbolc/axum-client-ip?tab=readme-ov-file#configurable-vs-specific-extractors ## - see https://github.com/imbolc/axum-client-ip?tab=readme-ov-file#configurable-vs-specific-extractors
## - before uncommenting the below ## - before uncommenting the below
# SCANOPY_CLIENT_IP_SOURCE= # scanopy_CLIENT_IP_SOURCE=
### - SMTP (password reset and notifications - optional) ### - SMTP (password reset and notifications - optional)
# SCANOPY_SMTP_RELAY=smtp.gmail.com:587 # scanopy_SMTP_RELAY=smtp.gmail.com:587
# SCANOPY_SMTP_USERNAME=your-email@gmail.com # scanopy_SMTP_USERNAME=your-email@gmail.com
# SCANOPY_SMTP_PASSWORD=your-app-password # scanopy_SMTP_PASSWORD=your-app-password
# SCANOPY_SMTP_EMAIL=scanopy@yourdomain.tld # scanopy_SMTP_EMAIL=scanopy@yourdomain.tld
### - INTEGRATED DAEMON ### - INTEGRATED DAEMON
SCANOPY_SERVER_URL=http://127.0.0.1:60072 scanopy_SERVER_URL=http://127.0.0.1:60072
SCANOPY_BIND_ADDRESS=0.0.0.0 scanopy_BIND_ADDRESS=0.0.0.0
SCANOPY_NAME="scanopy-daemon" scanopy_NAME="scanopy-daemon"
SCANOPY_HEARTBEAT_INTERVAL=30 scanopy_HEARTBEAT_INTERVAL=30
### - see https://github.com/scanopy/scanopy/blob/main/docs/CONFIGURATION.md for more options ### - see https://github.com/scanopy/scanopy/blob/main/docs/CONFIGURATION.md for more options
EOF EOF

View File

@@ -105,13 +105,14 @@ elif [[ "$DEPLOYMENT_TYPE" == "4" ]]; then
sed -i '/_BYPASS=/s/true/false/' /etc/shelfmark/.env sed -i '/_BYPASS=/s/true/false/' /etc/shelfmark/.env
else else
DEPLOYMENT_TYPE="1" DEPLOYMENT_TYPE="1"
CHROME_VERSION=$(curl -fsSL https://raw.githubusercontent.com/calibrain/shelfmark/refs/heads/main/Dockerfile | sed -n '/chromium=/s/[^=]*=//p' | awk '{print $1}')
msg_info "Installing internal bypasser dependencies" msg_info "Installing internal bypasser dependencies"
$STD apt install -y --no-install-recommends \ $STD apt install -y --no-install-recommends \
xvfb \ xvfb \
ffmpeg \ ffmpeg \
chromium-common \ chromium-common=${CHROME_VERSION} \
chromium \ chromium=${CHROME_VERSION} \
chromium-driver \ chromium-driver=${CHROME_VERSION} \
python3-tk python3-tk
msg_ok "Installed internal bypasser dependencies" msg_ok "Installed internal bypasser dependencies"
fi fi

View File

@@ -14,32 +14,24 @@ network_check
update_os update_os
msg_info "Installing Dependencies" msg_info "Installing Dependencies"
$STD apt install -y coreutils $STD apt install -y \
coreutils
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
msg_info "Setting up SQL Server 2022 Repository" msg_info "Setup SQL Server 2022"
setup_deb822_repo \ curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc >/dev/null
"mssql-server-2022" \ curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list | tee /etc/apt/sources.list.d/mssql-server-2022.list >/dev/null
"https://packages.microsoft.com/keys/microsoft.asc" \ $STD apt-get update -y
"https://packages.microsoft.com/ubuntu/22.04/mssql-server-2022" \ $STD apt-get install -y mssql-server
"./" \ msg_ok "Setup Server 2022"
""
msg_ok "Repository configured"
msg_info "Installing SQL Server 2022"
$STD apt install -y mssql-server
msg_ok "Installed SQL Server 2022"
msg_info "Installing SQL Server Tools" msg_info "Installing SQL Server Tools"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
export ACCEPT_EULA=Y export ACCEPT_EULA=Y
setup_deb822_repo \ curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | tee /etc/apt/trusted.gpg.d/microsoft.asc >/dev/null
"mssql-release" \ curl -fsSL https://packages.microsoft.com/config/ubuntu/22.04/prod.list | tee /etc/apt/sources.list.d/mssql-release.list >/dev/null
"https://packages.microsoft.com/keys/microsoft.asc" \ $STD apt-get update
"https://packages.microsoft.com/ubuntu/22.04/prod" \ $STD apt-get install -y -qq \
"jammy" \
"main"
$STD apt-get install -y \
mssql-tools18 \ mssql-tools18 \
unixodbc-dev unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >>~/.bash_profile echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >>~/.bash_profile
@@ -57,11 +49,6 @@ msg_info "Start Service"
systemctl enable -q --now mssql-server systemctl enable -q --now mssql-server
msg_ok "Service started" msg_ok "Service started"
msg_info "Cleaning up"
rm -f /etc/profile.d/debuginfod.sh
rm -f /etc/profile.d/debuginfod.csh
msg_ok "Cleaned up"
motd_ssh motd_ssh
customize customize
cleanup_lxc cleanup_lxc

View File

@@ -1,66 +0,0 @@
#!/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://www.microsoft.com/en-us/sql-server/sql-server-2025
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 coreutils
msg_ok "Installed Dependencies"
msg_info "Setting up SQL Server 2025 Repository"
setup_deb822_repo \
"mssql-server-2025" \
"https://packages.microsoft.com/keys/microsoft.asc" \
"https://packages.microsoft.com/ubuntu/24.04/mssql-server-2025" \
"./" \
""
msg_ok "Repository configured"
msg_info "Installing SQL Server 2025"
$STD apt install -y mssql-server
msg_ok "Installed SQL Server 2025"
msg_info "Installing SQL Server Tools"
export DEBIAN_FRONTEND=noninteractive
export ACCEPT_EULA=Y
setup_deb822_repo \
"mssql-release" \
"https://packages.microsoft.com/keys/microsoft.asc" \
"https://packages.microsoft.com/ubuntu/24.04/prod" \
"noble" \
"main"
$STD apt-get install -y \
mssql-tools18 \
unixodbc-dev
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >>~/.bash_profile
source ~/.bash_profile
msg_ok "Installed SQL Server Tools"
read -r -p "${TAB3}Do you want to run the SQL Server setup now? (Later is also possible) <y/N>" prompt
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
/opt/mssql/bin/mssql-conf setup
else
msg_ok "Skipping SQL Server setup. You can run it later with '/opt/mssql/bin/mssql-conf setup'."
fi
msg_info "Starting SQL Server Service"
systemctl enable -q --now mssql-server
msg_ok "Service started"
msg_info "Cleaning up"
rm -f /etc/profile.d/debuginfod.sh /etc/profile.d/debuginfod.csh
msg_ok "Cleaned up"
motd_ssh
customize
cleanup_lxc

View File

@@ -109,34 +109,18 @@ if command -v timescaledb-tune &> /dev/null; then
|| echo "Warning: timescaledb-tune failed (non-fatal)" || echo "Warning: timescaledb-tune failed (non-fatal)"
fi fi
# ============================================================================= # =============================================================================
# Ensure required PostgreSQL settings for Tracearr # Ensure TimescaleDB decompression limit is set (for existing databases)
# ============================================================================= # =============================================================================
# This setting allows migrations to modify compressed hypertable data.
# Without it, bulk UPDATEs on compressed sessions will fail with
# "tuple decompression limit exceeded" errors.
pg_config_file="/etc/postgresql/18/main/postgresql.conf" pg_config_file="/etc/postgresql/18/main/postgresql.conf"
if [ -f \$pg_config_file ]; then if [ -f \$pg_config_file ]; then
# Ensure max_tuples_decompressed_per_dml_transaction is set if ! grep -q "max_tuples_decompressed_per_dml_transaction" \$pg_config_file; then
if grep -q "^timescaledb\.max_tuples_decompressed_per_dml_transaction" \$pg_config_file; then
# Setting exists (uncommented) - update if not 0
current_value=\$(grep "^timescaledb\.max_tuples_decompressed_per_dml_transaction" \$pg_config_file | grep -oE '[0-9]+' | head -1)
if [ -n "\$current_value" ] && [ "\$current_value" -ne 0 ]; then
sed -i "s/^timescaledb\.max_tuples_decompressed_per_dml_transaction.*/timescaledb.max_tuples_decompressed_per_dml_transaction = 0/" \$pg_config_file
fi
elif ! grep -q "^timescaledb\.max_tuples_decompressed_per_dml_transaction" \$pg_config_file; then
echo "" >> \$pg_config_file echo "" >> \$pg_config_file
echo "# Allow unlimited tuple decompression for migrations on compressed hypertables" >> \$pg_config_file echo "# Allow unlimited tuple decompression for migrations on compressed hypertables" >> \$pg_config_file
echo "timescaledb.max_tuples_decompressed_per_dml_transaction = 0" >> \$pg_config_file echo "timescaledb.max_tuples_decompressed_per_dml_transaction = 0" >> \$pg_config_file
fi fi
# Ensure max_locks_per_transaction is set (for existing databases)
if grep -q "^max_locks_per_transaction" \$pg_config_file; then
# Setting exists (uncommented) - update if below 4096
current_value=\$(grep "^max_locks_per_transaction" \$pg_config_file | grep -oE '[0-9]+' | head -1)
if [ -n "\$current_value" ] && [ "\$current_value" -lt 4096 ]; then
sed -i "s/^max_locks_per_transaction.*/max_locks_per_transaction = 4096/" \$pg_config_file
fi
elif ! grep -q "^max_locks_per_transaction" \$pg_config_file; then
echo "" >> \$pg_config_file
echo "# Increase lock table size for TimescaleDB hypertables with many chunks" >> \$pg_config_file
echo "max_locks_per_transaction = 4096" >> \$pg_config_file
fi
fi fi
systemctl restart postgresql systemctl restart postgresql
EOF EOF

View File

@@ -14,7 +14,7 @@ network_check
update_os update_os
msg_info "Installing Dependencies" msg_info "Installing Dependencies"
$STD apt install -y \ $STD apt install -y git \
build-essential \ build-essential \
pkgconf \ pkgconf \
libssl-dev \ libssl-dev \
@@ -24,25 +24,34 @@ $STD apt install -y \
ssl-cert ssl-cert
msg_ok "Installed Dependencies" msg_ok "Installed Dependencies"
setup_rust WEBVAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src" VAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
msg_info "Building Vaultwarden (Patience)" msg_info "Installing Rust"
cd /tmp/vaultwarden-src curl -fsSL https://sh.rustup.rs -o rustup-init.sh
$STD bash rustup-init.sh -y --profile minimal
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>~/.bashrc
export PATH="$HOME/.cargo/bin:$PATH"
rm rustup-init.sh
msg_ok "Installed Rust"
msg_info "Building Vaultwarden ${VAULT} (Patience)"
$STD git clone https://github.com/dani-garcia/vaultwarden
cd vaultwarden
$STD cargo build --features "sqlite,mysql,postgresql" --release $STD cargo build --features "sqlite,mysql,postgresql" --release
msg_ok "Built Vaultwarden" msg_ok "Built Vaultwarden ${VAULT}"
msg_info "Setting up Vaultwarden"
$STD addgroup --system vaultwarden $STD addgroup --system vaultwarden
$STD adduser --system --home /opt/vaultwarden --shell /usr/sbin/nologin --no-create-home --gecos 'vaultwarden' --ingroup vaultwarden --disabled-login --disabled-password vaultwarden $STD adduser --system --home /opt/vaultwarden --shell /usr/sbin/nologin --no-create-home --gecos 'vaultwarden' --ingroup vaultwarden --disabled-login --disabled-password vaultwarden
mkdir -p /opt/vaultwarden/{bin,data,web-vault} mkdir -p /opt/vaultwarden/bin
mkdir -p /opt/vaultwarden/data
cp target/release/vaultwarden /opt/vaultwarden/bin/ cp target/release/vaultwarden /opt/vaultwarden/bin/
cd ~ && rm -rf /tmp/vaultwarden-src
msg_ok "Set up Vaultwarden"
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden/web-vault" "bw_web_*.tar.gz" msg_info "Downloading Web-Vault ${WEBVAULT}"
$STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WEBVAULT"/bw_web_"$WEBVAULT".tar.gz
$STD tar -xzf bw_web_"$WEBVAULT".tar.gz -C /opt/vaultwarden/
msg_ok "Downloaded Web-Vault ${WEBVAULT}"
msg_info "Configuring Vaultwarden"
cat <<EOF >/opt/vaultwarden/.env cat <<EOF >/opt/vaultwarden/.env
ADMIN_TOKEN='' ADMIN_TOKEN=''
ROCKET_ADDRESS=0.0.0.0 ROCKET_ADDRESS=0.0.0.0
@@ -52,23 +61,22 @@ DATABASE_MAX_CONNS=10
WEB_VAULT_FOLDER=/opt/vaultwarden/web-vault WEB_VAULT_FOLDER=/opt/vaultwarden/web-vault
WEB_VAULT_ENABLED=true WEB_VAULT_ENABLED=true
EOF EOF
mv /etc/ssl/certs/ssl-cert-snakeoil.pem /opt/vaultwarden/ mv /etc/ssl/certs/ssl-cert-snakeoil.pem /opt/vaultwarden/
mv /etc/ssl/private/ssl-cert-snakeoil.key /opt/vaultwarden/ mv /etc/ssl/private/ssl-cert-snakeoil.key /opt/vaultwarden/
msg_info "Creating Service"
chown -R vaultwarden:vaultwarden /opt/vaultwarden/ chown -R vaultwarden:vaultwarden /opt/vaultwarden/
chown root:root /opt/vaultwarden/bin/vaultwarden chown root:root /opt/vaultwarden/bin/vaultwarden
chmod +x /opt/vaultwarden/bin/vaultwarden chmod +x /opt/vaultwarden/bin/vaultwarden
chown -R root:root /opt/vaultwarden/web-vault/ chown -R root:root /opt/vaultwarden/web-vault/
chmod +r /opt/vaultwarden/.env chmod +r /opt/vaultwarden/.env
msg_ok "Configured Vaultwarden"
msg_info "Creating Service" service_path="/etc/systemd/system/vaultwarden.service"
cat <<EOF >/etc/systemd/system/vaultwarden.service echo "[Unit]
[Unit]
Description=Bitwarden Server (Powered by Vaultwarden) Description=Bitwarden Server (Powered by Vaultwarden)
Documentation=https://github.com/dani-garcia/vaultwarden Documentation=https://github.com/dani-garcia/vaultwarden
After=network.target After=network.target
[Service] [Service]
User=vaultwarden User=vaultwarden
Group=vaultwarden Group=vaultwarden
@@ -91,11 +99,10 @@ LockPersonality=yes
WorkingDirectory=/opt/vaultwarden WorkingDirectory=/opt/vaultwarden
ReadWriteDirectories=/opt/vaultwarden/data ReadWriteDirectories=/opt/vaultwarden/data
AmbientCapabilities=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target" >$service_path
EOF systemctl daemon-reload
systemctl enable -q --now vaultwarden $STD systemctl enable --now vaultwarden
msg_ok "Created Service" msg_ok "Created Service"
motd_ssh motd_ssh

View File

@@ -1,89 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: CrazyWolf13
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://wealthfolio.app/
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 \
pkg-config \
libssl-dev \
build-essential \
libsqlite3-dev \
argon2
msg_ok "Installed Dependencies"
setup_rust
NODE_MODULE="pnpm" setup_nodejs
fetch_and_deploy_gh_release "wealthfolio" "afadil/wealthfolio" "tarball"
msg_info "Building Frontend (patience)"
cd /opt/wealthfolio
$STD pnpm install --frozen-lockfile
$STD pnpm tsc
$STD pnpm vite build
msg_ok "Built Frontend"
msg_info "Building Backend (patience)"
cd /opt/wealthfolio/src-server
$STD cargo build --release --manifest-path Cargo.toml
cp /opt/wealthfolio/src-server/target/release/wealthfolio-server /usr/local/bin/wealthfolio-server
chmod +x /usr/local/bin/wealthfolio-server
msg_ok "Built Backend"
msg_info "Configuring Wealthfolio"
mkdir -p /opt/wealthfolio_data
SECRET_KEY=$(openssl rand -base64 32)
WF_PASSWORD=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-16)
WF_PASSWORD_HASH=$(echo -n "$WF_PASSWORD" | argon2 "$(openssl rand -base64 16)" -id -e)
cat <<EOF >/opt/wealthfolio/.env
WF_LISTEN_ADDR=0.0.0.0:8080
WF_DB_PATH=/opt/wealthfolio_data/wealthfolio.db
WF_SECRET_KEY=${SECRET_KEY}
WF_AUTH_PASSWORD_HASH=${WF_PASSWORD_HASH}
WF_STATIC_DIR=/opt/wealthfolio/dist
WF_CORS_ALLOW_ORIGINS=*
WF_REQUEST_TIMEOUT_MS=30000
EOF
echo "WF_PASSWORD=${WF_PASSWORD}" >~/wealthfolio.creds
msg_ok "Configured Wealthfolio"
msg_info "Cleaning Up"
rm -rf /opt/wealthfolio/src-server/target
rm -rf /root/.cargo/registry
rm -rf /opt/wealthfolio/node_modules
msg_ok "Cleaned Up"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/wealthfolio.service
[Unit]
Description=Wealthfolio Investment Tracker
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/wealthfolio
EnvironmentFile=/opt/wealthfolio/.env
ExecStart=/usr/local/bin/wealthfolio-server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now wealthfolio
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,66 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Dunky13
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/cmintey/wishlist
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 \
openssl \
caddy
msg_ok "Installed dependencies"
NODE_VERSION="24" NODE_MODULE="pnpm" setup_nodejs
fetch_and_deploy_gh_release "wishlist" "cmintey/wishlist" "tarball"
LATEST_APP_VERSION=$(get_latest_github_release "cmintey/wishlist")
msg_info "Installing Wishlist"
cd /opt/wishlist
cp .env.example .env
sed -i "s|^ORIGIN=.*|ORIGIN=http://${LOCAL_IP}:3280|" /opt/wishlist/.env
echo "" >>/opt/wishlist/.env
echo "NODE_ENV=production" >>/opt/wishlist/.env
$STD pnpm install
$STD pnpm svelte-kit sync
$STD pnpm prisma generate
sed -i 's|/usr/src/app/|/opt/wishlist/|g' $(grep -rl '/usr/src/app/' /opt/wishlist)
export VERSION="v${LATEST_APP_VERSION}"
export SHA="v${LATEST_APP_VERSION}"
$STD pnpm run build
$STD pnpm prune --prod
chmod +x /opt/wishlist/entrypoint.sh
mkdir -p /opt/wishlist/uploads
mkdir -p /opt/wishlist/data
msg_ok "Installed Wishlist"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/wishlist.service
[Unit]
Description=Wishlist Service
After=network.target
[Service]
WorkingDirectory=/opt/wishlist
EnvironmentFile=/opt/wishlist/.env
ExecStart=/usr/bin/env sh -c './entrypoint.sh'
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now wishlist
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: StellaeAlis
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/writefreely/writefreely
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 crudini
msg_ok "Installed Dependencies"
setup_mariadb
MARIADB_DB_NAME="writefreely" MARIADB_DB_USER="writefreely" setup_mariadb_db
fetch_and_deploy_gh_release "writefreely" "writefreely/writefreely" "prebuild" "latest" "/opt/writefreely" "writefreely_*_linux_amd64.tar.gz"
msg_info "Setting up WriteFreely"
cd /opt/writefreely
$STD ./writefreely config generate
$STD ./writefreely keys generate
msg_ok "Setup WriteFreely"
msg_info "Configuring WriteFreely"
$STD crudini --set config.ini server port 80
$STD crudini --set config.ini server bind $LOCAL_IP
$STD crudini --set config.ini database username $MARIADB_DB_USER
$STD crudini --set config.ini database password $MARIADB_DB_PASS
$STD crudini --set config.ini database database $MARIADB_DB_NAME
$STD crudini --set config.ini app host http://$LOCAL_IP:80
$STD ./writefreely db init
ln -s /opt/writefreely/writefreely /usr/local/bin/writefreely
msg_ok "Configured WriteFreely"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/writefreely.service
[Unit]
Description=WriteFreely Service
After=syslog.target network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/writefreely
ExecStart=/opt/writefreely/writefreely
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now writefreely
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -28,210 +28,13 @@
# ============================================================================== # ==============================================================================
# These can be overridden before sourcing this library # These can be overridden before sourcing this library
# Disable 'unbound variable' errors for this library (restored at end)
_OLD_SET_STATE=$(set +o | grep -E 'set -(e|u|o)')
set +u
CLOUDINIT_DEFAULT_USER="${CLOUDINIT_DEFAULT_USER:-root}" CLOUDINIT_DEFAULT_USER="${CLOUDINIT_DEFAULT_USER:-root}"
CLOUDINIT_DNS_SERVERS="${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}" CLOUDINIT_DNS_SERVERS="${CLOUDINIT_DNS_SERVERS:-1.1.1.1 8.8.8.8}"
CLOUDINIT_SEARCH_DOMAIN="${CLOUDINIT_SEARCH_DOMAIN:-local}" CLOUDINIT_SEARCH_DOMAIN="${CLOUDINIT_SEARCH_DOMAIN:-local}"
CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-}" # Empty by default - user must explicitly provide keys CLOUDINIT_SSH_KEYS="${CLOUDINIT_SSH_KEYS:-/root/.ssh/authorized_keys}"
# ============================================================================== # ==============================================================================
# SECTION 2: SSH KEY DISCOVERY AND SELECTION # SECTION 2: HELPER FUNCTIONS
# ==============================================================================
# ------------------------------------------------------------------------------
# _ci_ssh_extract_keys_from_file - Extracts valid SSH public keys from a file
# ------------------------------------------------------------------------------
function _ci_ssh_extract_keys_from_file() {
local file="$1"
[[ -f "$file" && -r "$file" ]] || return 0
grep -E '^(ssh-(rsa|ed25519|dss|ecdsa)|ecdsa-sha2-)' "$file" 2>/dev/null || true
}
# ------------------------------------------------------------------------------
# _ci_ssh_discover_files - Scans standard paths for SSH keys
# ------------------------------------------------------------------------------
function _ci_ssh_discover_files() {
local -a cand=()
shopt -s nullglob
cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)
cand+=(/root/.ssh/*.pub)
cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)
shopt -u nullglob
printf '%s\0' "${cand[@]}"
}
# ------------------------------------------------------------------------------
# _ci_ssh_build_choices - Builds whiptail checklist from SSH key files
#
# Sets: CI_SSH_CHOICES (array), CI_SSH_COUNT (int), CI_SSH_MAPFILE (path)
# ------------------------------------------------------------------------------
function _ci_ssh_build_choices() {
local -a files=("$@")
CI_SSH_CHOICES=()
CI_SSH_COUNT=0
CI_SSH_MAPFILE="$(mktemp)"
local id key typ fp cmt base
for f in "${files[@]}"; do
[[ -f "$f" && -r "$f" ]] || continue
base="$(basename -- "$f")"
# Skip known_hosts and private keys
case "$base" in
known_hosts | known_hosts.* | config) continue ;;
id_*) [[ "$f" != *.pub ]] && continue ;;
esac
while IFS= read -r key; do
[[ -n "$key" ]] || continue
typ=""
fp=""
cmt=""
read -r _typ _b64 _cmt <<<"$key"
typ="${_typ:-key}"
cmt="${_cmt:-}"
# Get fingerprint via ssh-keygen if available
if command -v ssh-keygen >/dev/null 2>&1; then
fp="$(printf '%s\n' "$key" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')"
fi
# Shorten long comments
[[ ${#cmt} -gt 40 ]] && cmt="${cmt:0:37}..."
CI_SSH_COUNT=$((CI_SSH_COUNT + 1))
id="K${CI_SSH_COUNT}"
echo "${id}|${key}" >>"$CI_SSH_MAPFILE"
CI_SSH_CHOICES+=("$id" "[$typ] ${fp:+$fp }${cmt:+$cmt }${base}" "OFF")
done < <(_ci_ssh_extract_keys_from_file "$f")
done
}
# ------------------------------------------------------------------------------
# configure_cloudinit_ssh_keys - Interactive SSH key selection for Cloud-Init
#
# Usage: configure_cloudinit_ssh_keys
# Sets: CLOUDINIT_SSH_KEYS (path to temporary file with selected keys)
# ------------------------------------------------------------------------------
function configure_cloudinit_ssh_keys() {
local backtitle="Proxmox VE Helper Scripts"
local ssh_key_mode
# Create temp file for selected keys
CLOUDINIT_SSH_KEYS_TEMP="$(mktemp)"
: >"$CLOUDINIT_SSH_KEYS_TEMP"
# Discover keys and build choices
IFS=$'\0' read -r -d '' -a _def_files < <(_ci_ssh_discover_files && printf '\0')
_ci_ssh_build_choices "${_def_files[@]}"
local default_key_count="$CI_SSH_COUNT"
if [[ "$default_key_count" -gt 0 ]]; then
ssh_key_mode=$(whiptail --backtitle "$backtitle" --title "SSH KEY SOURCE" --menu \
"Provision SSH keys for Cloud-Init VM:" 14 72 4 \
"found" "Select from detected keys (${default_key_count})" \
"manual" "Paste a single public key" \
"folder" "Scan another folder (path or glob)" \
"none" "No SSH keys (password auth only)" 3>&1 1>&2 2>&3) || return 1
else
ssh_key_mode=$(whiptail --backtitle "$backtitle" --title "SSH KEY SOURCE" --menu \
"No host keys detected. Choose:" 12 72 3 \
"manual" "Paste a single public key" \
"folder" "Scan another folder (path or glob)" \
"none" "No SSH keys (password auth only)" 3>&1 1>&2 2>&3) || return 1
fi
case "$ssh_key_mode" in
found)
# Show checklist with individual keys
local selection
selection=$(whiptail --backtitle "$backtitle" --title "SELECT SSH KEYS" \
--checklist "Select one or more keys to import:" 20 140 10 "${CI_SSH_CHOICES[@]}" 3>&1 1>&2 2>&3) || return 1
for tag in $selection; do
tag="${tag%\"}"
tag="${tag#\"}"
local line
line=$(grep -E "^${tag}\|" "$CI_SSH_MAPFILE" | head -n1 | cut -d'|' -f2-)
[[ -n "$line" ]] && printf '%s\n' "$line" >>"$CLOUDINIT_SSH_KEYS_TEMP"
done
local imported
imported=$(wc -l <"$CLOUDINIT_SSH_KEYS_TEMP")
echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}${imported} key(s) selected${CL}"
;;
manual)
local pubkey
pubkey=$(whiptail --backtitle "$backtitle" --title "PASTE SSH PUBLIC KEY" \
--inputbox "Paste your SSH public key (ssh-rsa, ssh-ed25519, etc.):" 10 76 3>&1 1>&2 2>&3) || return 1
if [[ -n "$pubkey" ]]; then
echo "$pubkey" >"$CLOUDINIT_SSH_KEYS_TEMP"
echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}1 key added manually${CL}"
else
echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}none (empty input)${CL}"
CLOUDINIT_SSH_KEYS=""
rm -f "$CLOUDINIT_SSH_KEYS_TEMP" "$CI_SSH_MAPFILE" 2>/dev/null
return 0
fi
;;
folder)
local glob_path
glob_path=$(whiptail --backtitle "$backtitle" --title "SCAN FOLDER/GLOB" \
--inputbox "Enter a folder or glob to scan (e.g. /root/.ssh/*.pub):" 10 72 3>&1 1>&2 2>&3) || return 1
if [[ -n "$glob_path" ]]; then
shopt -s nullglob
local -a _scan_files=($glob_path)
shopt -u nullglob
if [[ "${#_scan_files[@]}" -gt 0 ]]; then
_ci_ssh_build_choices "${_scan_files[@]}"
if [[ "$CI_SSH_COUNT" -gt 0 ]]; then
local folder_selection
folder_selection=$(whiptail --backtitle "$backtitle" --title "SELECT FOLDER KEYS" \
--checklist "Select key(s) to import:" 20 140 10 "${CI_SSH_CHOICES[@]}" 3>&1 1>&2 2>&3) || return 1
for tag in $folder_selection; do
tag="${tag%\"}"
tag="${tag#\"}"
local line
line=$(grep -E "^${tag}\|" "$CI_SSH_MAPFILE" | head -n1 | cut -d'|' -f2-)
[[ -n "$line" ]] && printf '%s\n' "$line" >>"$CLOUDINIT_SSH_KEYS_TEMP"
done
local imported
imported=$(wc -l <"$CLOUDINIT_SSH_KEYS_TEMP")
echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}${imported} key(s) from folder${CL}"
else
whiptail --backtitle "$backtitle" --msgbox "No keys found in: $glob_path" 8 60
fi
else
whiptail --backtitle "$backtitle" --msgbox "Path/glob returned no files." 8 60
fi
fi
;;
none | *)
echo -e "${ROOTSSH:- 🔑 }${BOLD}${DGN}SSH Keys: ${BGN}none (password auth only)${CL}"
CLOUDINIT_SSH_KEYS=""
rm -f "$CLOUDINIT_SSH_KEYS_TEMP" "$CI_SSH_MAPFILE" 2>/dev/null
return 0
;;
esac
# Cleanup mapfile
rm -f "$CI_SSH_MAPFILE" 2>/dev/null
# Set the variable for setup_cloud_init to use
if [[ -s "$CLOUDINIT_SSH_KEYS_TEMP" ]]; then
CLOUDINIT_SSH_KEYS="$CLOUDINIT_SSH_KEYS_TEMP"
else
CLOUDINIT_SSH_KEYS=""
rm -f "$CLOUDINIT_SSH_KEYS_TEMP"
fi
return 0
}
# ==============================================================================
# SECTION 3: HELPER FUNCTIONS
# ============================================================================== # ==============================================================================
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -341,10 +144,9 @@ function setup_cloud_init() {
local cipassword=$(openssl rand -base64 16) local cipassword=$(openssl rand -base64 16)
qm set "$vmid" --cipassword "$cipassword" >/dev/null qm set "$vmid" --cipassword "$cipassword" >/dev/null
# Add SSH keys only if explicitly provided (not auto-imported from host) # Add SSH keys if available
if [ -n "${CLOUDINIT_SSH_KEYS:-}" ] && [ -f "$CLOUDINIT_SSH_KEYS" ]; then if [ -f "$CLOUDINIT_SSH_KEYS" ]; then
qm set "$vmid" --sshkeys "$CLOUDINIT_SSH_KEYS" >/dev/null 2>&1 || true qm set "$vmid" --sshkeys "$CLOUDINIT_SSH_KEYS" >/dev/null 2>&1 || true
_ci_msg_info "SSH keys imported from: $CLOUDINIT_SSH_KEYS"
fi fi
# Configure network # Configure network
@@ -657,11 +459,6 @@ export -f wait_for_cloud_init 2>/dev/null || true
export -f validate_ip_cidr 2>/dev/null || true export -f validate_ip_cidr 2>/dev/null || true
export -f validate_ip 2>/dev/null || true export -f validate_ip 2>/dev/null || true
# Restore previous shell options if they were saved
if [ -n "${_OLD_SET_STATE:-}" ]; then
eval "$_OLD_SET_STATE"
fi
# ============================================================================== # ==============================================================================
# SECTION 7: EXAMPLES & DOCUMENTATION # SECTION 7: EXAMPLES & DOCUMENTATION
# ============================================================================== # ==============================================================================

View File

@@ -821,54 +821,6 @@ github_api_call() {
return 1 return 1
} }
# ------------------------------------------------------------------------------
# Codeberg API call with retry logic
# ------------------------------------------------------------------------------
codeberg_api_call() {
local url="$1"
local output_file="${2:-/dev/stdout}"
local max_retries=3
local retry_delay=2
for attempt in $(seq 1 $max_retries); do
local http_code
http_code=$(curl -fsSL -w "%{http_code}" -o "$output_file" \
-H "Accept: application/json" \
"$url" 2>/dev/null || echo "000")
case "$http_code" in
200)
return 0
;;
403)
# Rate limit - retry
if [[ $attempt -lt $max_retries ]]; then
msg_warn "Codeberg API rate limit, waiting ${retry_delay}s... (attempt $attempt/$max_retries)"
sleep "$retry_delay"
retry_delay=$((retry_delay * 2))
continue
fi
msg_error "Codeberg API rate limit exceeded."
return 1
;;
404)
msg_error "Codeberg API endpoint not found: $url"
return 1
;;
*)
if [[ $attempt -lt $max_retries ]]; then
sleep "$retry_delay"
continue
fi
msg_error "Codeberg API call failed with HTTP $http_code"
return 1
;;
esac
done
return 1
}
should_upgrade() { should_upgrade() {
local current="$1" local current="$1"
local target="$2" local target="$2"
@@ -1433,37 +1385,6 @@ get_latest_github_release() {
echo "$version" echo "$version"
} }
# ------------------------------------------------------------------------------
# Get latest Codeberg release version
# ------------------------------------------------------------------------------
get_latest_codeberg_release() {
local repo="$1"
local strip_v="${2:-true}"
local temp_file=$(mktemp)
# Codeberg API: get all releases and pick the first non-draft/non-prerelease
if ! codeberg_api_call "https://codeberg.org/api/v1/repos/${repo}/releases" "$temp_file"; then
rm -f "$temp_file"
return 1
fi
local version
# Codeberg uses same JSON structure but releases endpoint returns array
version=$(jq -r '[.[] | select(.draft==false and .prerelease==false)][0].tag_name // empty' "$temp_file")
if [[ "$strip_v" == "true" ]]; then
version="${version#v}"
fi
rm -f "$temp_file"
if [[ -z "$version" ]]; then
return 1
fi
echo "$version"
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Debug logging (only if DEBUG=1) # Debug logging (only if DEBUG=1)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -1531,8 +1452,7 @@ check_for_gh_release() {
local app="$1" local app="$1"
local source="$2" local source="$2"
local pinned_version_in="${3:-}" # optional local pinned_version_in="${3:-}" # optional
local app_lc="" local app_lc="${app,,}"
app_lc="$(echo "${app,,}" | tr -d ' ')"
local current_file="$HOME/.${app_lc}" local current_file="$HOME/.${app_lc}"
msg_info "Checking for update: ${app}" msg_info "Checking for update: ${app}"
@@ -1639,119 +1559,6 @@ check_for_gh_release() {
return 1 return 1
} }
# ------------------------------------------------------------------------------
# Checks for new Codeberg release (latest tag).
#
# Description:
# - Queries the Codeberg API for the latest release tag
# - Compares it to a local cached version (~/.<app>)
# - If newer, sets global CHECK_UPDATE_RELEASE and returns 0
#
# Usage:
# if check_for_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" [optional] "v0.11.3"; then
# # trigger update...
# fi
# exit 0
# } (end of update_script not from the function)
#
# Notes:
# - Requires `jq` (auto-installed if missing)
# - Does not modify anything, only checks version state
# - Does not support pre-releases
# ------------------------------------------------------------------------------
check_for_codeberg_release() {
local app="$1"
local source="$2"
local pinned_version_in="${3:-}" # optional
local app_lc="${app,,}"
local current_file="$HOME/.${app_lc}"
msg_info "Checking for update: ${app}"
# DNS check
if ! getent hosts codeberg.org >/dev/null 2>&1; then
msg_error "Network error: cannot resolve codeberg.org"
return 1
fi
ensure_dependencies jq
# Fetch releases from Codeberg API
local releases_json=""
releases_json=$(curl -fsSL --max-time 20 \
-H 'Accept: application/json' \
"https://codeberg.org/api/v1/repos/${source}/releases" 2>/dev/null) || {
msg_error "Unable to fetch releases for ${app}"
return 1
}
mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<"$releases_json")
if ((${#raw_tags[@]} == 0)); then
msg_error "No stable releases found for ${app}"
return 1
fi
local clean_tags=()
for t in "${raw_tags[@]}"; do
clean_tags+=("${t#v}")
done
local latest_raw="${raw_tags[0]}"
local latest_clean="${clean_tags[0]}"
# current installed (stored without v)
local current=""
if [[ -f "$current_file" ]]; then
current="$(<"$current_file")"
else
# Migration: search for any /opt/*_version.txt
local legacy_files
mapfile -t legacy_files < <(find /opt -maxdepth 1 -type f -name "*_version.txt" 2>/dev/null)
if ((${#legacy_files[@]} == 1)); then
current="$(<"${legacy_files[0]}")"
echo "${current#v}" >"$current_file"
rm -f "${legacy_files[0]}"
fi
fi
current="${current#v}"
# Pinned version handling
if [[ -n "$pinned_version_in" ]]; then
local pin_clean="${pinned_version_in#v}"
local match_raw=""
for i in "${!clean_tags[@]}"; do
if [[ "${clean_tags[$i]}" == "$pin_clean" ]]; then
match_raw="${raw_tags[$i]}"
break
fi
done
if [[ -z "$match_raw" ]]; then
msg_error "Pinned version ${pinned_version_in} not found upstream"
return 1
fi
if [[ "$current" != "$pin_clean" ]]; then
CHECK_UPDATE_RELEASE="$match_raw"
msg_ok "Update available: ${app} ${current:-not installed}${pin_clean}"
return 0
fi
msg_ok "No update available: ${app} is already on pinned version (${current})"
return 1
fi
# No pinning → use latest
if [[ -z "$current" || "$current" != "$latest_clean" ]]; then
CHECK_UPDATE_RELEASE="$latest_raw"
msg_ok "Update available: ${app} ${current:-not installed}${latest_clean}"
return 0
fi
msg_ok "No update available: ${app} (${latest_clean})"
return 1
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Creates and installs self-signed certificates. # Creates and installs self-signed certificates.
# #
@@ -1841,440 +1648,6 @@ function ensure_usr_local_bin_persist() {
fi fi
} }
# ------------------------------------------------------------------------------
# Downloads and deploys latest Codeberg release (source, binary, tarball, asset).
#
# Description:
# - Fetches latest release metadata from Codeberg API
# - Supports the following modes:
# - tarball: Source code tarball (default if omitted)
# - source: Alias for tarball (same behavior)
# - binary: .deb package install (arch-dependent)
# - prebuild: Prebuilt .tar.gz archive (e.g. Go binaries)
# - singlefile: Standalone binary (no archive, direct chmod +x install)
# - tag: Direct tag download (bypasses Release API)
# - Handles download, extraction/installation and version tracking in ~/.<app>
#
# Parameters:
# $1 APP - Application name (used for install path and version file)
# $2 REPO - Codeberg repository in form user/repo
# $3 MODE - Release type:
# tarball → source tarball (.tar.gz)
# binary → .deb file (auto-arch matched)
# prebuild → prebuilt archive (e.g. tar.gz)
# singlefile→ standalone binary (chmod +x)
# tag → direct tag (bypasses Release API)
# $4 VERSION - Optional release tag (default: latest)
# $5 TARGET_DIR - Optional install path (default: /opt/<app>)
# $6 ASSET_FILENAME - Required for:
# - prebuild → archive filename or pattern
# - singlefile→ binary filename or pattern
#
# Examples:
# # 1. Minimal: Fetch and deploy source tarball
# fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb"
#
# # 2. Binary install via .deb asset (architecture auto-detected)
# fetch_and_deploy_codeberg_release "myapp" "myuser/myapp" "binary"
#
# # 3. Prebuilt archive (.tar.gz) with asset filename match
# fetch_and_deploy_codeberg_release "myapp" "myuser/myapp" "prebuild" "latest" "/opt/myapp" "myapp_Linux_x86_64.tar.gz"
#
# # 4. Single binary (chmod +x)
# fetch_and_deploy_codeberg_release "myapp" "myuser/myapp" "singlefile" "v1.0.0" "/opt/myapp" "myapp-linux-amd64"
#
# # 5. Explicit tag version
# fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tag" "v0.11.3" "/opt/autocaliweb"
# ------------------------------------------------------------------------------
function fetch_and_deploy_codeberg_release() {
local app="$1"
local repo="$2"
local mode="${3:-tarball}" # tarball | binary | prebuild | singlefile | tag
local version="${4:-latest}"
local target="${5:-/opt/$app}"
local asset_pattern="${6:-}"
local app_lc=$(echo "${app,,}" | tr -d ' ')
local version_file="$HOME/.${app_lc}"
local api_timeout="--connect-timeout 10 --max-time 60"
local download_timeout="--connect-timeout 15 --max-time 900"
local current_version=""
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
ensure_dependencies jq
### Tag Mode (bypass Release API) ###
if [[ "$mode" == "tag" ]]; then
if [[ "$version" == "latest" ]]; then
msg_error "Mode 'tag' requires explicit version (not 'latest')"
return 1
fi
local tag_name="$version"
[[ "$tag_name" =~ ^v ]] && version="${tag_name:1}" || version="$tag_name"
if [[ "$current_version" == "$version" ]]; then
$STD msg_ok "$app is already up-to-date (v$version)"
return 0
fi
# DNS check
if ! getent hosts "codeberg.org" &>/dev/null; then
msg_error "DNS resolution failed for codeberg.org check /etc/resolv.conf or networking"
return 1
fi
local tmpdir
tmpdir=$(mktemp -d) || return 1
msg_info "Fetching Codeberg tag: $app ($tag_name)"
local safe_version="${version//@/_}"
safe_version="${safe_version//\//_}"
local filename="${app_lc}-${safe_version}.tar.gz"
local download_success=false
# Codeberg archive URL format: https://codeberg.org/{owner}/{repo}/archive/{tag}.tar.gz
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
if curl $download_timeout -fsSL -o "$tmpdir/$filename" "$archive_url"; then
download_success=true
fi
if [[ "$download_success" != "true" ]]; then
msg_error "Download failed for $app ($tag_name)"
rm -rf "$tmpdir"
return 1
fi
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 1
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
return 0
fi
# Codeberg API: https://codeberg.org/api/v1/repos/{owner}/{repo}/releases
local api_url="https://codeberg.org/api/v1/repos/$repo/releases"
if [[ "$version" != "latest" ]]; then
# Get release by tag: /repos/{owner}/{repo}/releases/tags/{tag}
api_url="https://codeberg.org/api/v1/repos/$repo/releases/tags/$version"
fi
# dns pre check
if ! getent hosts "codeberg.org" &>/dev/null; then
msg_error "DNS resolution failed for codeberg.org check /etc/resolv.conf or networking"
return 1
fi
local max_retries=3 retry_delay=2 attempt=1 success=false resp http_code
while ((attempt <= max_retries)); do
resp=$(curl $api_timeout -fsSL -w "%{http_code}" -o /tmp/codeberg_rel.json "$api_url") && success=true && break
sleep "$retry_delay"
((attempt++))
done
if ! $success; then
msg_error "Failed to fetch release metadata from $api_url after $max_retries attempts"
return 1
fi
http_code="${resp:(-3)}"
[[ "$http_code" != "200" ]] && {
msg_error "Codeberg API returned HTTP $http_code"
return 1
}
local json tag_name
json=$(</tmp/codeberg_rel.json)
# For "latest", the API returns an array - take the first (most recent) release
if [[ "$version" == "latest" ]]; then
json=$(echo "$json" | jq '.[0]')
fi
tag_name=$(echo "$json" | jq -r '.tag_name // .name // empty')
[[ "$tag_name" =~ ^v ]] && version="${tag_name:1}" || version="$tag_name"
if [[ "$current_version" == "$version" ]]; then
$STD msg_ok "$app is already up-to-date (v$version)"
return 0
fi
local tmpdir
tmpdir=$(mktemp -d) || return 1
local filename="" url=""
msg_info "Fetching Codeberg release: $app ($version)"
### Tarball Mode ###
if [[ "$mode" == "tarball" || "$mode" == "source" ]]; then
local safe_version="${version//@/_}"
safe_version="${safe_version//\//_}"
filename="${app_lc}-${safe_version}.tar.gz"
local download_success=false
# Codeberg archive URL format
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
if curl $download_timeout -fsSL -o "$tmpdir/$filename" "$archive_url"; then
download_success=true
fi
if [[ "$download_success" != "true" ]]; then
msg_error "Download failed for $app ($tag_name)"
rm -rf "$tmpdir"
return 1
fi
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 1
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
### Binary Mode ###
elif [[ "$mode" == "binary" ]]; then
local arch
arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
[[ "$arch" == "x86_64" ]] && arch="amd64"
[[ "$arch" == "aarch64" ]] && arch="arm64"
local assets url_match=""
# Codeberg assets are in .assets[].browser_download_url
assets=$(echo "$json" | jq -r '.assets[].browser_download_url')
# If explicit filename pattern is provided, match that first
if [[ -n "$asset_pattern" ]]; then
for u in $assets; do
case "${u##*/}" in
$asset_pattern)
url_match="$u"
break
;;
esac
done
fi
# Fall back to architecture heuristic
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
fi
done
fi
# Fallback: any .deb file
if [[ -z "$url_match" ]]; then
for u in $assets; do
[[ "$u" =~ \.deb$ ]] && url_match="$u" && break
done
fi
if [[ -z "$url_match" ]]; then
msg_error "No suitable .deb asset found for $app"
rm -rf "$tmpdir"
return 1
fi
filename="${url_match##*/}"
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 1
}
chmod 644 "$tmpdir/$filename"
$STD apt install -y "$tmpdir/$filename" || {
$STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
rm -rf "$tmpdir"
return 1
}
}
### Prebuild Mode ###
elif [[ "$mode" == "prebuild" ]]; then
local pattern="${6%\"}"
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
local asset_url=""
for u in $(echo "$json" | jq -r '.assets[].browser_download_url'); do
filename_candidate="${u##*/}"
case "$filename_candidate" in
$pattern)
asset_url="$u"
break
;;
esac
done
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
local unpack_tmp
unpack_tmp=$(mktemp -d)
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
if [[ "$filename" == *.zip ]]; then
ensure_dependencies unzip
unzip -q "$tmpdir/$filename" -d "$unpack_tmp" || {
msg_error "Failed to extract ZIP archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
elif [[ "$filename" == *.tar.* || "$filename" == *.tgz ]]; then
tar --no-same-owner -xf "$tmpdir/$filename" -C "$unpack_tmp" || {
msg_error "Failed to extract TAR archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Unsupported archive format: $filename"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
local top_dirs
top_dirs=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1 -type d | wc -l)
local top_entries inner_dir
top_entries=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1)
if [[ "$(echo "$top_entries" | wc -l)" -eq 1 && -d "$top_entries" ]]; then
inner_dir="$top_entries"
shopt -s dotglob nullglob
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$target/" || {
msg_error "Failed to copy contents from $inner_dir to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Inner directory is empty: $inner_dir"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
shopt -u dotglob nullglob
else
shopt -s dotglob nullglob
if compgen -G "$unpack_tmp/*" >/dev/null; then
cp -r "$unpack_tmp"/* "$target/" || {
msg_error "Failed to copy contents to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Unpacked archive is empty"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
shopt -u dotglob nullglob
fi
### Singlefile Mode ###
elif [[ "$mode" == "singlefile" ]]; then
local pattern="${6%\"}"
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
local asset_url=""
for u in $(echo "$json" | jq -r '.assets[].browser_download_url'); do
filename_candidate="${u##*/}"
case "$filename_candidate" in
$pattern)
asset_url="$u"
break
;;
esac
done
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
mkdir -p "$target"
local use_filename="${USE_ORIGINAL_FILENAME:-false}"
local target_file="$app"
[[ "$use_filename" == "true" ]] && target_file="$filename"
curl $download_timeout -fsSL -o "$target/$target_file" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
if [[ "$target_file" != *.jar && -f "$target/$target_file" ]]; then
chmod +x "$target/$target_file"
fi
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 1
fi
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Downloads and deploys latest GitHub release (source, binary, tarball, asset). # Downloads and deploys latest GitHub release (source, binary, tarball, asset).
# #

View File

@@ -1,620 +0,0 @@
# Copyright (c) 2021-2026 community-scripts ORG
# License: MIT | https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/LICENSE
set -euo pipefail
SPINNER_PID=""
SPINNER_ACTIVE=0
SPINNER_MSG=""
declare -A MSG_INFO_SHOWN
# ------------------------------------------------------------------------------
# Loads core utility groups once (colors, formatting, icons, defaults).
# ------------------------------------------------------------------------------
[[ -n "${_CORE_FUNC_LOADED:-}" ]] && return
_CORE_FUNC_LOADED=1
load_functions() {
[[ -n "${__FUNCTIONS_LOADED:-}" ]] && return
__FUNCTIONS_LOADED=1
color
formatting
icons
default_vars
set_std_mode
shell_check
get_valid_nextid
cleanup_vmid
cleanup
check_root
pve_check
arch_check
}
# 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 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 1
fi
fi
cat "$local_header_path" 2>/dev/null || true
}
header_info() {
local app_name=$(echo "${APP,,}" | tr ' ' '-')
local header_content
header_content=$(get_header "$app_name") || header_content=""
clear
local term_width
term_width=$(tput cols 2>/dev/null || echo 120)
if [ -n "$header_content" ]; then
echo "$header_content"
fi
}
# ------------------------------------------------------------------------------
# Sets ANSI color codes used for styled terminal output.
# ------------------------------------------------------------------------------
color() {
YW=$(echo "\033[33m")
YWB=$(echo "\033[93m")
BL=$(echo "\033[36m")
RD=$(echo "\033[01;31m")
BGN=$(echo "\033[4;92m")
GN=$(echo "\033[1;92m")
DGN=$(echo "\033[32m")
CL=$(echo "\033[m")
}
# ------------------------------------------------------------------------------
# Defines formatting helpers like tab, bold, and line reset sequences.
# ------------------------------------------------------------------------------
formatting() {
BFR="\\r\\033[K"
BOLD=$(echo "\033[1m")
HOLD=" "
TAB=" "
TAB3=" "
}
# ------------------------------------------------------------------------------
# Sets symbolic icons used throughout user feedback and prompts.
# ------------------------------------------------------------------------------
icons() {
CM="${TAB}✔️${TAB}"
CROSS="${TAB}✖️${TAB}"
DNSOK="✔️ "
DNSFAIL="${TAB}✖️${TAB}"
INFO="${TAB}💡${TAB}${CL}"
OS="${TAB}🖥️${TAB}${CL}"
OSVERSION="${TAB}🌟${TAB}${CL}"
CONTAINERTYPE="${TAB}📦${TAB}${CL}"
DISKSIZE="${TAB}💾${TAB}${CL}"
CPUCORE="${TAB}🧠${TAB}${CL}"
RAMSIZE="${TAB}🛠️${TAB}${CL}"
SEARCH="${TAB}🔍${TAB}${CL}"
VERBOSE_CROPPED="🔍${TAB}"
VERIFYPW="${TAB}🔐${TAB}${CL}"
CONTAINERID="${TAB}🆔${TAB}${CL}"
HOSTNAME="${TAB}🏠${TAB}${CL}"
BRIDGE="${TAB}🌉${TAB}${CL}"
NETWORK="${TAB}📡${TAB}${CL}"
GATEWAY="${TAB}🌐${TAB}${CL}"
DISABLEIPV6="${TAB}🚫${TAB}${CL}"
ICON_DISABLEIPV6="${TAB}🚫${TAB}${CL}"
DEFAULT="${TAB}⚙️${TAB}${CL}"
MACADDRESS="${TAB}🔗${TAB}${CL}"
VLANTAG="${TAB}🏷️${TAB}${CL}"
ROOTSSH="${TAB}🔑${TAB}${CL}"
CREATING="${TAB}🚀${TAB}${CL}"
ADVANCED="${TAB}🧩${TAB}${CL}"
FUSE="${TAB}🗂️${TAB}${CL}"
GPU="${TAB}🎮${TAB}${CL}"
HOURGLASS="${TAB}${TAB}"
}
# ------------------------------------------------------------------------------
# Sets default verbose mode for script and os execution.
# ------------------------------------------------------------------------------
set_std_mode() {
if [ "${VERBOSE:-no}" = "yes" ]; then
STD=""
else
STD="silent"
fi
}
# ------------------------------------------------------------------------------
# default_vars()
#
# - Sets default retry and wait variables used for system actions
# - RETRY_NUM: Maximum number of retry attempts (default: 10)
# - RETRY_EVERY: Seconds to wait between retries (default: 3)
# ------------------------------------------------------------------------------
default_vars() {
RETRY_NUM=10
RETRY_EVERY=3
i=$RETRY_NUM
}
# ------------------------------------------------------------------------------
# get_active_logfile()
#
# - Returns the appropriate log file based on execution context
# - BUILD_LOG: Host operations (VM creation)
# - Fallback to /tmp/build-<timestamp>.log if not set
# ------------------------------------------------------------------------------
get_active_logfile() {
if [[ -n "${BUILD_LOG:-}" ]]; then
echo "$BUILD_LOG"
else
# Fallback for legacy scripts
echo "/tmp/build-$(date +%Y%m%d_%H%M%S).log"
fi
}
# ------------------------------------------------------------------------------
# silent()
#
# - Executes command with output redirected to active log file
# - On error: displays last 10 lines of log and exits with original exit code
# - Temporarily disables error trap to capture exit code correctly
# - Sources explain_exit_code() for detailed error messages
# ------------------------------------------------------------------------------
silent() {
local cmd="$*"
local caller_line="${BASH_LINENO[0]:-unknown}"
local logfile="$(get_active_logfile)"
set +Eeuo pipefail
trap - ERR
"$@" >>"$logfile" 2>&1
local rc=$?
set -Eeuo pipefail
trap 'error_handler' ERR
if [[ $rc -ne 0 ]]; then
# Source explain_exit_code if needed
if ! declare -f explain_exit_code >/dev/null 2>&1; then
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/error_handler.func) 2>/dev/null || true
fi
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
local log_lines=$(wc -l <"$logfile")
echo "--- Last 10 lines of log ---"
tail -n 10 "$logfile"
echo "----------------------------"
# Show how to view full log if there are more lines
if [[ $log_lines -gt 10 ]]; then
msg_custom "📋" "${YW}" "View full log (${log_lines} lines): ${logfile}"
fi
fi
exit "$rc"
fi
}
# ------------------------------------------------------------------------------
# Performs a curl request with retry logic and inline feedback.
# ------------------------------------------------------------------------------
run_curl() {
if [ "$VERB" = "no" ]; then
curl "$@" >/dev/null 2>>/tmp/curl_error.log
else
curl "$@" 2>>/tmp/curl_error.log
fi
}
curl_handler() {
local args=()
local url=""
local max_retries=0 delay=2 attempt=1
local exit_code has_output_file=false
for arg in "$@"; do
if [[ "$arg" != -* && -z "$url" ]]; then
url="$arg"
fi
[[ "$arg" == "-o" || "$arg" == --output ]] && has_output_file=true
args+=("$arg")
done
if [[ -z "$url" ]]; then
msg_error "no valid url or option entered for curl_handler"
exit 1
fi
$STD msg_info "Fetching: $url"
while :; do
if $has_output_file; then
$STD run_curl "${args[@]}"
exit_code=$?
else
$STD result=$(run_curl "${args[@]}")
exit_code=$?
fi
if [[ $exit_code -eq 0 ]]; then
stop_spinner
msg_ok "Fetched: $url"
$has_output_file || printf '%s' "$result"
return 0
fi
if ((attempt >= max_retries)); then
stop_spinner
if [ -s /tmp/curl_error.log ]; then
local curl_stderr
curl_stderr=$(</tmp/curl_error.log)
rm -f /tmp/curl_error.log
fi
__curl_err_handler "$exit_code" "$url" "$curl_stderr"
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
sleep "$delay"
((attempt++))
done
}
# ------------------------------------------------------------------------------
# Handles specific curl error codes and displays descriptive messages.
# ------------------------------------------------------------------------------
__curl_err_handler() {
local exit_code="$1"
local target="$2"
local curl_msg="$3"
case $exit_code in
1) msg_error "Unsupported protocol: $target" ;;
2) msg_error "Curl init failed: $target" ;;
3) msg_error "Malformed URL: $target" ;;
5) msg_error "Proxy resolution failed: $target" ;;
6) msg_error "Host resolution failed: $target" ;;
7) msg_error "Connection failed: $target" ;;
9) msg_error "Access denied: $target" ;;
18) msg_error "Partial file transfer: $target" ;;
22) msg_error "HTTP error (e.g. 400/404): $target" ;;
23) msg_error "Write error on local system: $target" ;;
26) msg_error "Read error from local file: $target" ;;
28) msg_error "Timeout: $target" ;;
35) msg_error "SSL connect error: $target" ;;
47) msg_error "Too many redirects: $target" ;;
51) msg_error "SSL cert verify failed: $target" ;;
52) msg_error "Empty server response: $target" ;;
55) msg_error "Send error: $target" ;;
56) msg_error "Receive error: $target" ;;
60) msg_error "SSL CA not trusted: $target" ;;
67) msg_error "Login denied by server: $target" ;;
78) msg_error "Remote file not found (404): $target" ;;
*) msg_error "Curl failed with code $exit_code: $target" ;;
esac
[[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
exit 1
}
# ------------------------------------------------------------------------------
# shell_check()
#
# - Verifies that the script is running under Bash shell
# - Exits with error message if different shell is detected
# ------------------------------------------------------------------------------
shell_check() {
if [[ "$(ps -p $$ -o comm=)" != "bash" ]]; then
clear
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
fi
}
# ------------------------------------------------------------------------------
# clear_line()
#
# - Clears current terminal line using tput or ANSI escape codes
# - Moves cursor to beginning of line (carriage return)
# - Fallback to ANSI codes if tput not available
# ------------------------------------------------------------------------------
clear_line() {
tput cr 2>/dev/null || echo -en "\r"
tput el 2>/dev/null || echo -en "\033[K"
}
# ------------------------------------------------------------------------------
# is_verbose_mode()
#
# - Determines if script should run in verbose mode
# - Checks VERBOSE and var_verbose variables
# - Also returns true if not running in TTY (pipe/redirect scenario)
# ------------------------------------------------------------------------------
is_verbose_mode() {
local verbose="${VERBOSE:-${var_verbose:-no}}"
[[ "$verbose" != "no" || ! -t 2 ]]
}
### dev spinner ###
SPINNER_ACTIVE=0
SPINNER_PID=""
SPINNER_MSG=""
declare -A MSG_INFO_SHOWN=()
# Trap cleanup on various signals
trap 'cleanup_spinner' EXIT INT TERM HUP
# Cleans up spinner process on exit
cleanup_spinner() {
stop_spinner
# Additional cleanup if needed
}
start_spinner() {
local msg="${1:-Processing...}"
local frames=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
local spin_i=0
local interval=0.1
# Set message and clear current line
SPINNER_MSG="$msg"
printf "\r\e[2K" >&2
# Stop any existing spinner
stop_spinner
# Set active flag
SPINNER_ACTIVE=1
# Start spinner in background
{
while [[ "$SPINNER_ACTIVE" -eq 1 ]]; do
printf "\r\e[2K%s %b" "${TAB}${frames[spin_i]}${TAB}" "${YW}${SPINNER_MSG}${CL}" >&2
spin_i=$(((spin_i + 1) % ${#frames[@]}))
sleep "$interval"
done
} &
SPINNER_PID=$!
# Disown to prevent getting "Terminated" messages
disown "$SPINNER_PID" 2>/dev/null || true
}
stop_spinner() {
# Check if spinner is active and PID exists
if [[ "$SPINNER_ACTIVE" -eq 1 ]] && [[ -n "${SPINNER_PID}" ]]; then
SPINNER_ACTIVE=0
if kill -0 "$SPINNER_PID" 2>/dev/null; then
kill "$SPINNER_PID" 2>/dev/null
# Give it a moment to terminate
sleep 0.1
# Force kill if still running
if kill -0 "$SPINNER_PID" 2>/dev/null; then
kill -9 "$SPINNER_PID" 2>/dev/null
fi
# Wait for process but ignore errors
wait "$SPINNER_PID" 2>/dev/null || true
fi
# Clear spinner line
printf "\r\e[2K" >&2
SPINNER_PID=""
fi
}
spinner_guard() {
# Safely stop spinner if it's running
if [[ "$SPINNER_ACTIVE" -eq 1 ]] && [[ -n "${SPINNER_PID}" ]]; then
stop_spinner
fi
}
msg_info() {
local msg="${1:-Information message}"
# Only show each message once unless reset
if [[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]]; then
return
fi
MSG_INFO_SHOWN["$msg"]=1
spinner_guard
start_spinner "$msg"
}
msg_ok() {
local msg="${1:-Operation completed successfully}"
stop_spinner
printf "\r\e[2K%s %b\n" "${CM}" "${GN}${msg}${CL}" >&2
# Remove from shown messages to allow it to be shown again
local sanitized_msg
sanitized_msg=$(printf '%s' "$msg" | sed 's/\x1b\[[0-9;]*m//g; s/[^a-zA-Z0-9_]/_/g')
unset 'MSG_INFO_SHOWN['"$sanitized_msg"']' 2>/dev/null || true
}
msg_error() {
local msg="${1:-An error occurred}"
stop_spinner
printf "\r\e[2K%s %b\n" "${CROSS}" "${RD}${msg}${CL}" >&2
}
msg_warn() {
stop_spinner
local msg="$1"
echo -e "${BFR:-}${INFO:-} ${YWB}${msg}${CL}" >&2
}
# Helper function to display a message with custom symbol and color
msg_custom() {
local symbol="${1:-*}"
local color="${2:-$CL}"
local msg="${3:-Custom message}"
[[ -z "$msg" ]] && return
stop_spinner
printf "\r\e[2K%s %b\n" "$symbol" "${color}${msg}${CL}" >&2
}
# ------------------------------------------------------------------------------
# msg_debug()
#
# - Displays debug message with timestamp when var_full_verbose=1
# - Automatically enables var_verbose if not already set
# - Uses bright yellow color for debug output
# ------------------------------------------------------------------------------
msg_debug() {
if [[ "${var_full_verbose:-0}" == "1" ]]; then
[[ "${var_verbose:-0}" != "1" ]] && var_verbose=1
echo -e "${YWB}[$(date '+%F %T')] [DEBUG]${CL} $*"
fi
}
# Displays error message and immediately terminates script
fatal() {
msg_error "$1"
kill -INT $$
}
get_valid_nextid() {
local try_id
try_id=$(pvesh get /cluster/nextid)
while true; do
if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then
try_id=$((try_id + 1))
continue
fi
if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then
try_id=$((try_id + 1))
continue
fi
break
done
echo "$try_id"
}
cleanup_vmid() {
if [[ -z "${VMID:-}" ]]; then
return
fi
if qm status "$VMID" &>/dev/null; then
qm stop "$VMID" &>/dev/null
qm destroy "$VMID" &>/dev/null
fi
}
cleanup() {
if [[ "$(dirs -p | wc -l)" -gt 1 ]]; then
popd >/dev/null || true
fi
}
check_root() {
if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then
clear
msg_error "Please run this script as root."
echo -e "\nExiting..."
sleep 2
exit
fi
}
pve_check() {
if ! pveversion | grep -Eq "pve-manager/(8\.[1-4]|9\.[0-1])(\.[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.1."
echo -e "Exiting..."
sleep 2
exit
fi
}
arch_check() {
if [ "$(dpkg --print-architecture)" != "amd64" ]; then
echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n"
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..."
sleep 2
exit
fi
}
exit_script() {
clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit
}
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 1
fi
}
set_description() {
DESCRIPTION=$(
cat <<EOF
<div align='center'>
<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>
<p style='margin: 16px 0;'>
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
</a>
</p>
<span style='margin: 0 10px;'>
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
</span>
</div>
EOF
)
qm set "$VMID" -description "$DESCRIPTION" >/dev/null
}

View File

@@ -1,264 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/alangrainger/immich-public-proxy
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
# Enable error handling
set -Eeuo pipefail
trap 'error_handler' ERR
# ==============================================================================
# CONFIGURATION
# ==============================================================================
APP="Immich Public Proxy"
APP_TYPE="addon"
INSTALL_PATH="/opt/immich-proxy"
CONFIG_PATH="/opt/immich-proxy/app"
DEFAULT_PORT=3000
# Initialize all core functions (colors, formatting, icons, STD mode)
load_functions
# ==============================================================================
# HEADER
# ==============================================================================
function header_info {
clear
cat <<"EOF"
____ _ __ ____
/ _/___ ___ ____ ___ (_)____/ /_ / __ \_________ _ ____ __
/ // __ `__ \/ __ `__ \/ / ___/ __ \______/ /_/ / ___/ __ \| |/_/ / / /
_/ // / / / / / / / / / / / /__/ / / /_____/ ____/ / / /_/ /> </ /_/ /
/___/_/ /_/ /_/_/ /_/ /_/_/\___/_/ /_/ /_/ /_/ \____/_/|_|\__, /
/____/
EOF
}
# ==============================================================================
# OS DETECTION
# ==============================================================================
if [[ -f "/etc/alpine-release" ]]; then
msg_error "Alpine is not supported for ${APP}. Use Debian."
exit 1
elif [[ -f "/etc/debian_version" ]]; then
OS="Debian"
SERVICE_PATH="/etc/systemd/system/immich-proxy.service"
else
echo -e "${CROSS} Unsupported OS detected. Exiting."
exit 1
fi
# ==============================================================================
# UNINSTALL
# ==============================================================================
function uninstall() {
msg_info "Uninstalling ${APP}"
systemctl disable --now immich-proxy.service &>/dev/null || true
rm -f "$SERVICE_PATH"
rm -rf "$INSTALL_PATH"
rm -f "/usr/local/bin/update_immich-public-proxy"
rm -f "$HOME/.immichpublicproxy"
msg_ok "${APP} has been uninstalled"
}
# ==============================================================================
# UPDATE
# ==============================================================================
function update() {
if check_for_gh_release "Immich Public Proxy" "alangrainger/immich-public-proxy"; then
msg_info "Stopping service"
systemctl stop immich-proxy.service &>/dev/null || true
msg_ok "Stopped service"
msg_info "Backing up configuration"
cp "$CONFIG_PATH"/.env /tmp/ipp.env.bak 2>/dev/null || true
cp "$CONFIG_PATH"/config.json /tmp/ipp.config.json.bak 2>/dev/null || true
msg_ok "Backed up configuration"
NODE_VERSION="24" setup_nodejs
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Immich Public Proxy" "alangrainger/immich-public-proxy" "tarball" "latest" "$INSTALL_PATH"
msg_info "Restoring configuration"
cp /tmp/ipp.env.bak "$CONFIG_PATH"/.env 2>/dev/null || true
cp /tmp/ipp.config.json.bak "$CONFIG_PATH"/config.json 2>/dev/null || true
rm -f /tmp/ipp.*.bak
msg_ok "Restored configuration"
msg_info "Installing dependencies"
cd "$CONFIG_PATH"
$STD npm install
msg_ok "Installed dependencies"
msg_info "Building ${APP}"
$STD npm run build
msg_ok "Built ${APP}"
msg_info "Starting service"
systemctl start immich-proxy
msg_ok "Started service"
msg_ok "Updated successfully"
exit
fi
}
# ==============================================================================
# INSTALL
# ==============================================================================
function install() {
NODE_VERSION="24" setup_nodejs
# Force fresh download by removing version cache
rm -f "$HOME/.immichpublicproxy"
fetch_and_deploy_gh_release "Immich Public Proxy" "alangrainger/immich-public-proxy" "tarball" "latest" "$INSTALL_PATH"
msg_info "Installing dependencies"
cd "$CONFIG_PATH"
$STD npm install
msg_ok "Installed dependencies"
msg_info "Building ${APP}"
$STD npm run build
msg_ok "Built ${APP}"
MAX_ATTEMPTS=3
attempt=0
while true; do
attempt=$((attempt + 1))
read -rp "${TAB3}Enter your LOCAL Immich IP or domain (ex. 192.168.1.100 or immich.local.lan): " DOMAIN
if [[ -z "$DOMAIN" ]]; then
if ((attempt >= MAX_ATTEMPTS)); then
DOMAIN="${LOCAL_IP:-localhost}"
msg_warn "Using fallback: $DOMAIN"
break
fi
msg_warn "Domain cannot be empty! (Attempt $attempt/$MAX_ATTEMPTS)"
elif [[ "$DOMAIN" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
valid_ip=true
IFS='.' read -ra octets <<<"$DOMAIN"
for octet in "${octets[@]}"; do
if ((octet > 255)); then
valid_ip=false
break
fi
done
if $valid_ip; then
break
else
msg_warn "Invalid IP address!"
fi
elif [[ "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$ || "$DOMAIN" == "localhost" ]]; then
break
else
msg_warn "Invalid domain format!"
fi
done
msg_info "Creating configuration"
cat <<EOF >"$CONFIG_PATH"/.env
NODE_ENV=production
IMMICH_URL=http://${DOMAIN}:2283
EOF
chmod 600 "$CONFIG_PATH"/.env
msg_ok "Created configuration"
msg_info "Creating service"
cat <<EOF >"$SERVICE_PATH"
[Unit]
Description=Immich Public Proxy
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=${INSTALL_PATH}
EnvironmentFile=${CONFIG_PATH}/.env
ExecStart=/usr/bin/node ${INSTALL_PATH}/app/server.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now immich-proxy
msg_ok "Created and started service"
# Create update script (simple wrapper that calls this addon with type=update)
msg_info "Creating update script"
cat <<'UPDATEEOF' >/usr/local/bin/update_immich-public-proxy
#!/usr/bin/env bash
# Immich Public Proxy Update Script
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/immich-public-proxy.sh)"
UPDATEEOF
chmod +x /usr/local/bin/update_immich-public-proxy
msg_ok "Created update script (/usr/local/bin/update_immich-public-proxy)"
echo ""
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
echo ""
msg_warn "Additional configuration is available at '/opt/immich-proxy/app/config.json'"
}
# ==============================================================================
# MAIN
# ==============================================================================
# Handle type=update (called from update script)
if [[ "${type:-}" == "update" ]]; then
header_info
if [[ -d "$INSTALL_PATH" && -f "$SERVICE_PATH" ]]; then
update
else
msg_error "${APP} is not installed. Nothing to update."
exit 1
fi
exit 0
fi
header_info
get_lxc_ip
# Check if already installed
if [[ -d "$INSTALL_PATH" && -f "$SERVICE_PATH" ]]; then
msg_warn "${APP} is already installed."
echo ""
echo -n "${TAB}Uninstall ${APP}? (y/N): "
read -r uninstall_prompt
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
uninstall
exit 0
fi
echo -n "${TAB}Update ${APP}? (y/N): "
read -r update_prompt
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
update
exit 0
fi
msg_warn "No action selected. Exiting."
exit 0
fi
# Fresh installation
msg_warn "${APP} is not installed."
echo ""
echo -e "${TAB}${INFO} This will install:"
echo -e "${TAB} - Node.js 24"
echo -e "${TAB} - Immich Public Proxy"
echo ""
echo -n "${TAB}Install ${APP}? (y/N): "
read -r install_prompt
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
install
else
msg_warn "Installation cancelled. Exiting."
exit 0
fi

View File

@@ -1,6 +0,0 @@
____ _ __ ____ __ ___ ____
/ _/___ ___ ____ ___ (_)____/ /_ / __ \__ __/ /_ / (_)____ / __ \_________ _ ____ __
/ // __ `__ \/ __ `__ \/ / ___/ __ \ / /_/ / / / / __ \/ / / ___/ / /_/ / ___/ __ \| |/_/ / / /
_/ // / / / / / / / / / / / /__/ / / / / ____/ /_/ / /_/ / / / /__ / ____/ / / /_/ /> </ /_/ /
/___/_/ /_/ /_/_/ /_/ /_/_/\___/_/ /_/ /_/ \__,_/_.___/_/_/\___/ /_/ /_/ \____/_/|_|\__, /
/____/

View File

@@ -1,45 +1,71 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG # Copyright (c) 2021-2026 community-scripts ORG
# Author: thost96 (thost96) | michelroegl-brunner | MickLesk # Author: thost96 (thost96) | Co-Author: michelroegl-brunner
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# ============================================================================== source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)
# Docker VM - Creates a Docker-ready Virtual Machine
# ==============================================================================
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/api.func) 2>/dev/null function header_info() {
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/vm-core.func) 2>/dev/null clear
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVE/raw/branch/main/misc/cloud-init.func) 2>/dev/null || true cat <<"EOF"
load_functions ____ __ _ ____ ___
/ __ \____ _____/ /_____ _____ | | / / |/ /
# ============================================================================== / / / / __ \/ ___/ //_/ _ \/ ___/ | | / / /|_/ /
# SCRIPT VARIABLES / /_/ / /_/ / /__/ ,< / __/ / | |/ / / / /
# ============================================================================== /_____/\____/\___/_/|_|\___/_/ |___/_/ /_/
APP="Docker"
APP_TYPE="vm"
NSAPP="docker-vm"
var_os="debian"
var_version="13"
EOF
}
header_info
echo -e "\n Loading..."
GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//') GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
METHOD="" METHOD=""
NSAPP="docker-vm"
var_os="debian"
var_version="12"
DISK_SIZE="10G" DISK_SIZE="10G"
USE_CLOUD_INIT="no"
OS_TYPE=""
OS_VERSION=""
THIN="discard=on,ssd=1,"
# ============================================================================== YW=$(echo "\033[33m")
# ERROR HANDLING & CLEANUP BL=$(echo "\033[36m")
# ============================================================================== RD=$(echo "\033[01;31m")
BGN=$(echo "\033[4;92m")
GN=$(echo "\033[1;92m")
DGN=$(echo "\033[32m")
CL=$(echo "\033[m")
CL=$(echo "\033[m")
BOLD=$(echo "\033[1m")
BFR="\\r\\033[K"
HOLD=" "
TAB=" "
CM="${TAB}✔️${TAB}${CL}"
CROSS="${TAB}✖️${TAB}${CL}"
INFO="${TAB}💡${TAB}${CL}"
OS="${TAB}🖥️${TAB}${CL}"
CONTAINERTYPE="${TAB}📦${TAB}${CL}"
DISKSIZE="${TAB}💾${TAB}${CL}"
CPUCORE="${TAB}🧠${TAB}${CL}"
RAMSIZE="${TAB}🛠️${TAB}${CL}"
CONTAINERID="${TAB}🆔${TAB}${CL}"
HOSTNAME="${TAB}🏠${TAB}${CL}"
BRIDGE="${TAB}🌉${TAB}${CL}"
GATEWAY="${TAB}🌐${TAB}${CL}"
DEFAULT="${TAB}⚙️${TAB}${CL}"
MACADDRESS="${TAB}🔗${TAB}${CL}"
VLANTAG="${TAB}🏷️${TAB}${CL}"
CREATING="${TAB}🚀${TAB}${CL}"
ADVANCED="${TAB}🧩${TAB}${CL}"
CLOUD="${TAB}☁️${TAB}${CL}"
THIN="discard=on,ssd=1,"
set -e set -e
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
trap cleanup EXIT trap cleanup EXIT
trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
function error_handler() { function error_handler() {
local exit_code="$?" local exit_code="$?"
local line_number="$1" local line_number="$1"
@@ -50,96 +76,140 @@ function error_handler() {
cleanup_vmid cleanup_vmid
} }
# ============================================================================== function get_valid_nextid() {
# OS SELECTION FUNCTIONS local try_id
# ============================================================================== try_id=$(pvesh get /cluster/nextid)
function select_os() { while true; do
if OS_CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SELECT OS" --radiolist \ if [ -f "/etc/pve/qemu-server/${try_id}.conf" ] || [ -f "/etc/pve/lxc/${try_id}.conf" ]; then
"Choose Operating System for Docker VM" 14 68 4 \ try_id=$((try_id + 1))
"debian13" "Debian 13 (Trixie) - Latest" ON \ continue
"debian12" "Debian 12 (Bookworm) - Stable" OFF \
"ubuntu2404" "Ubuntu 24.04 LTS (Noble)" OFF \
"ubuntu2204" "Ubuntu 22.04 LTS (Jammy)" OFF \
3>&1 1>&2 2>&3); then
case $OS_CHOICE in
debian13)
OS_TYPE="debian"
OS_VERSION="13"
OS_CODENAME="trixie"
OS_DISPLAY="Debian 13 (Trixie)"
;;
debian12)
OS_TYPE="debian"
OS_VERSION="12"
OS_CODENAME="bookworm"
OS_DISPLAY="Debian 12 (Bookworm)"
;;
ubuntu2404)
OS_TYPE="ubuntu"
OS_VERSION="24.04"
OS_CODENAME="noble"
OS_DISPLAY="Ubuntu 24.04 LTS"
;;
ubuntu2204)
OS_TYPE="ubuntu"
OS_VERSION="22.04"
OS_CODENAME="jammy"
OS_DISPLAY="Ubuntu 22.04 LTS"
;;
esac
echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}${OS_DISPLAY}${CL}"
else
exit_script
fi
}
function select_cloud_init() {
if [ "$OS_TYPE" = "ubuntu" ]; then
USE_CLOUD_INIT="yes"
echo -e "${CLOUD:-${TAB}☁️${TAB}${CL}}${BOLD}${DGN}Cloud-Init: ${BGN}yes (Ubuntu requires Cloud-Init)${CL}"
return
fi
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" \
--yesno "Enable Cloud-Init for VM configuration?\n\nCloud-Init allows automatic configuration of:\n- User accounts and passwords\n- SSH keys\n- Network settings (DHCP/Static)\n- DNS configuration\n\nYou can also configure these settings later in Proxmox UI.\n\nNote: Debian without Cloud-Init will use nocloud image with console auto-login." 18 68); then
USE_CLOUD_INIT="yes"
echo -e "${CLOUD:-${TAB}☁️${TAB}${CL}}${BOLD}${DGN}Cloud-Init: ${BGN}yes${CL}"
else
USE_CLOUD_INIT="no"
echo -e "${CLOUD:-${TAB}☁️${TAB}${CL}}${BOLD}${DGN}Cloud-Init: ${BGN}no${CL}"
fi
}
function get_image_url() {
local arch=$(dpkg --print-architecture)
case $OS_TYPE in
debian)
if [ "$USE_CLOUD_INIT" = "yes" ]; then
echo "https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-generic-${arch}.qcow2"
else
echo "https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-nocloud-${arch}.qcow2"
fi fi
;; if lvs --noheadings -o lv_name | grep -qE "(^|[-_])${try_id}($|[-_])"; then
ubuntu) try_id=$((try_id + 1))
echo "https://cloud-images.ubuntu.com/${OS_CODENAME}/current/${OS_CODENAME}-server-cloudimg-${arch}.img" continue
;; fi
esac break
done
echo "$try_id"
} }
# ============================================================================== function cleanup_vmid() {
# SETTINGS FUNCTIONS if qm status $VMID &>/dev/null; then
# ============================================================================== qm stop $VMID &>/dev/null
function default_settings() { qm destroy $VMID &>/dev/null
select_os fi
select_cloud_init }
function cleanup() {
popd >/dev/null
post_update_to_api "done" "none"
rm -rf $TEMP_DIR
}
TEMP_DIR=$(mktemp -d)
pushd $TEMP_DIR >/dev/null
if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Docker VM" --yesno "This will create a New Docker VM. Proceed?" 10 58; then
:
else
header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit
fi
function msg_info() {
local msg="$1"
echo -ne "${TAB}${YW}${HOLD}${msg}${HOLD}"
}
function msg_ok() {
local msg="$1"
echo -e "${BFR}${CM}${GN}${msg}${CL}"
}
function msg_error() {
local msg="$1"
echo -e "${BFR}${CROSS}${RD}${msg}${CL}"
}
function check_root() {
if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then
clear
msg_error "Please run this script as root."
echo -e "\nExiting..."
sleep 2
exit
fi
}
# This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.
# Supported: Proxmox VE 8.0.x 8.9.x, 9.0 and 9.1
pve_check() {
local PVE_VER
PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
# Check for Proxmox VE 8.x: allow 8.08.9
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 1
fi
return 0
fi
# Check for Proxmox VE 9.x: allow 9.0 and 9.1
if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then
local MINOR="${BASH_REMATCH[1]}"
if ((MINOR < 0 || MINOR > 1)); then
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported: Proxmox VE version 9.0 9.1"
exit 1
fi
return 0
fi
# All other unsupported versions
msg_error "This version of Proxmox VE is not supported."
msg_error "Supported versions: Proxmox VE 8.0 8.x or 9.0 9.1"
exit 1
}
function arch_check() {
if [ "$(dpkg --print-architecture)" != "amd64" ]; then
echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n"
echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n"
echo -e "Exiting..."
sleep 2
exit
fi
}
function ssh_check() {
if command -v pveversion >/dev/null 2>&1; then
if [ -n "${SSH_CLIENT:+x}" ]; 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
echo "you've been warned"
else
clear
exit
fi
fi
fi
}
function exit-script() {
clear
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
exit
}
function default_settings() {
VMID=$(get_valid_nextid) VMID=$(get_valid_nextid)
FORMAT="" FORMAT=",efitype=4m"
MACHINE=" -machine q35" MACHINE=""
DISK_CACHE="" DISK_CACHE=""
DISK_SIZE="10G" DISK_SIZE="10G"
HN="docker" HN="docker"
CPU_TYPE=" -cpu host" CPU_TYPE=""
CORE_COUNT="2" CORE_COUNT="2"
RAM_SIZE="4096" RAM_SIZE="4096"
BRG="vmbr0" BRG="vmbr0"
@@ -148,13 +218,12 @@ function default_settings() {
MTU="" MTU=""
START_VM="yes" START_VM="yes"
METHOD="default" METHOD="default"
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}"
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}Q35 (Modern)${CL}" echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}"
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}"
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}"
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}"
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}"
@@ -162,22 +231,12 @@ function default_settings() {
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}"
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}"
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}"
echo -e "${CREATING}${BOLD}${DGN}Creating a Docker VM using the above settings${CL}" echo -e "${CREATING}${BOLD}${DGN}Creating a Docker VM using the above default settings${CL}"
} }
function advanced_settings() { function advanced_settings() {
select_os
select_cloud_init
# SSH Key selection for Cloud-Init VMs
if [ "$USE_CLOUD_INIT" = "yes" ]; then
configure_cloudinit_ssh_keys || true
fi
METHOD="advanced" METHOD="advanced"
[ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid)
# VM ID
while true; do while true; do
if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$VMID" ]; then if [ -z "$VMID" ]; then
@@ -191,29 +250,27 @@ function advanced_settings() {
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}$VMID${CL}"
break break
else else
exit_script exit-script
fi fi
done done
# Machine Type
if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \
"q35" "Q35 (Modern, PCIe)" ON \ "i440fx" "Machine i440fx" ON \
"i440fx" "i440fx (Legacy, PCI)" OFF \ "q35" "Machine q35" OFF \
3>&1 1>&2 2>&3); then 3>&1 1>&2 2>&3); then
if [ $MACH = q35 ]; then if [ $MACH = q35 ]; then
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}Q35 (Modern)${CL}" echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}"
FORMAT="" FORMAT=""
MACHINE=" -machine q35" MACHINE=" -machine q35"
else else
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx (Legacy)${CL}" echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}$MACH${CL}"
FORMAT=",efitype=4m" FORMAT=",efitype=4m"
MACHINE="" MACHINE=""
fi fi
else else
exit_script exit-script
fi fi
# Disk Size
if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GiB (e.g., 10, 20)" 8 58 "$DISK_SIZE" --title "DISK SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ') DISK_SIZE=$(echo "$DISK_SIZE" | tr -d ' ')
if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then if [[ "$DISK_SIZE" =~ ^[0-9]+$ ]]; then
@@ -223,13 +280,12 @@ function advanced_settings() {
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}" echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}$DISK_SIZE${CL}"
else else
echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}" echo -e "${DISKSIZE}${BOLD}${RD}Invalid Disk Size. Please use a number (e.g., 10 or 10G).${CL}"
exit_script exit-script
fi fi
else else
exit_script exit-script
fi fi
# Disk Cache
if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
"0" "None (Default)" ON \ "0" "None (Default)" ON \
"1" "Write Through" OFF \ "1" "Write Through" OFF \
@@ -242,25 +298,24 @@ function advanced_settings() {
DISK_CACHE="" DISK_CACHE=""
fi fi
else else
exit_script exit-script
fi fi
# Hostname
if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 docker --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if VM_NAME=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 docker --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $VM_NAME ]; then if [ -z $VM_NAME ]; then
HN="docker" HN="docker"
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
else else
HN=$(echo ${VM_NAME,,} | tr -d ' ') HN=$(echo ${VM_NAME,,} | tr -d ' ')
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
fi fi
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
else else
exit_script exit-script
fi fi
# CPU Model
if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
"1" "Host (Recommended)" ON \ "0" "KVM64 (Default)" ON \
"0" "KVM64" OFF \ "1" "Host" OFF \
3>&1 1>&2 2>&3); then 3>&1 1>&2 2>&3); then
if [ $CPU_TYPE1 = "1" ]; then if [ $CPU_TYPE1 = "1" ]; then
echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}"
@@ -270,78 +325,80 @@ function advanced_settings() {
CPU_TYPE="" CPU_TYPE=""
fi fi
else else
exit_script exit-script
fi fi
# CPU Cores
if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate CPU Cores" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $CORE_COUNT ]; then if [ -z $CORE_COUNT ]; then
CORE_COUNT="2" CORE_COUNT="2"
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
else
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
fi fi
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
else else
exit_script exit-script
fi fi
# RAM Size if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 2048 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Allocate RAM in MiB" 8 58 4096 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $RAM_SIZE ]; then if [ -z $RAM_SIZE ]; then
RAM_SIZE="4096" RAM_SIZE="2048"
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}"
else
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}"
fi fi
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}$RAM_SIZE${CL}"
else else
exit_script exit-script
fi fi
# Bridge
if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Bridge" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $BRG ]; then if [ -z $BRG ]; then
BRG="vmbr0" BRG="vmbr0"
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
else
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
fi fi
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
else else
exit_script exit-script
fi fi
# MAC Address
if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if MAC1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a MAC Address" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $MAC1 ]; then if [ -z $MAC1 ]; then
MAC="$GEN_MAC" MAC="$GEN_MAC"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
else else
MAC="$MAC1" MAC="$MAC1"
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}"
fi fi
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC${CL}"
else else
exit_script exit-script
fi fi
# VLAN if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan (leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $VLAN1 ]; then if [ -z $VLAN1 ]; then
VLAN1="Default" VLAN1="Default"
VLAN="" VLAN=""
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}"
else else
VLAN=",tag=$VLAN1" VLAN=",tag=$VLAN1"
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}"
fi fi
echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}$VLAN1${CL}"
else else
exit_script exit-script
fi fi
# MTU
if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then if MTU1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default)" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z $MTU1 ]; then if [ -z $MTU1 ]; then
MTU1="Default" MTU1="Default"
MTU="" MTU=""
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
else else
MTU=",mtu=$MTU1" MTU=",mtu=$MTU1"
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
fi fi
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
else else
exit_script exit-script
fi fi
# Start VM
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "START VIRTUAL MACHINE" --yesno "Start VM when completed?" 10 58); then
echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}"
START_VM="yes" START_VM="yes"
@@ -350,7 +407,6 @@ function advanced_settings() {
START_VM="no" START_VM="no"
fi fi
# Confirm
if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Docker VM?" --no-button Do-Over 10 58); then if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create a Docker VM?" --no-button Do-Over 10 58); then
echo -e "${CREATING}${BOLD}${DGN}Creating a Docker VM using the above advanced settings${CL}" echo -e "${CREATING}${BOLD}${DGN}Creating a Docker VM using the above advanced settings${CL}"
else else
@@ -371,28 +427,13 @@ function start_script() {
advanced_settings advanced_settings
fi fi
} }
# ==============================================================================
# MAIN EXECUTION
# ==============================================================================
header_info
check_root check_root
arch_check arch_check
pve_check pve_check
ssh_check
if whiptail --backtitle "Proxmox VE Helper Scripts" --title "Docker VM" --yesno "This will create a New Docker VM. Proceed?" 10 58; then
:
else
header_info && echo -e "${CROSS}${RD}User exited script${CL}\n" && exit
fi
start_script start_script
post_to_api_vm post_to_api_vm
# ==============================================================================
# STORAGE SELECTION
# ==============================================================================
msg_info "Validating Storage" msg_info "Validating Storage"
while read -r line; do while read -r line; do
TAG=$(echo $line | awk '{print $1}') TAG=$(echo $line | awk '{print $1}')
@@ -405,7 +446,6 @@ while read -r line; do
fi fi
STORAGE_MENU+=("$TAG" "$ITEM" "OFF") STORAGE_MENU+=("$TAG" "$ITEM" "OFF")
done < <(pvesm status -content images | awk 'NR>1') done < <(pvesm status -content images | awk 'NR>1')
VALID=$(pvesm status -content images | awk 'NR>1') VALID=$(pvesm status -content images | awk 'NR>1')
if [ -z "$VALID" ]; then if [ -z "$VALID" ]; then
msg_error "Unable to detect a valid storage location." msg_error "Unable to detect a valid storage location."
@@ -413,8 +453,6 @@ if [ -z "$VALID" ]; then
elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then
STORAGE=${STORAGE_MENU[0]} STORAGE=${STORAGE_MENU[0]}
else else
if [ -n "$SPINNER_PID" ] && ps -p $SPINNER_PID >/dev/null; then kill $SPINNER_PID >/dev/null; fi
printf "\e[?25h"
while [ -z "${STORAGE:+x}" ]; do while [ -z "${STORAGE:+x}" ]; do
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
"Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \ "Which storage pool would you like to use for ${HN}?\nTo make a selection, use the Spacebar.\n" \
@@ -424,288 +462,112 @@ else
fi fi
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
msg_info "Retrieving the URL for the Debian 12 Qcow2 Disk Image"
# ============================================================================== URL="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-nocloud-$(dpkg --print-architecture).qcow2"
# PREREQUISITES sleep 2
# ==============================================================================
if ! command -v virt-customize &>/dev/null; then
msg_info "Installing libguestfs-tools"
apt-get -qq update >/dev/null
apt-get -qq install libguestfs-tools lsb-release -y >/dev/null
apt-get -qq install dhcpcd-base -y >/dev/null 2>&1 || true
msg_ok "Installed libguestfs-tools"
fi
# ==============================================================================
# IMAGE DOWNLOAD
# ==============================================================================
msg_info "Retrieving the URL for the ${OS_DISPLAY} Qcow2 Disk Image"
URL=$(get_image_url)
CACHE_DIR="/var/lib/vz/template/cache"
CACHE_FILE="$CACHE_DIR/$(basename "$URL")"
mkdir -p "$CACHE_DIR"
msg_ok "${CL}${BL}${URL}${CL}" msg_ok "${CL}${BL}${URL}${CL}"
curl -f#SL -o "$(basename "$URL")" "$URL"
echo -en "\e[1A\e[0K"
FILE=$(basename $URL)
msg_ok "Downloaded ${CL}${BL}${FILE}${CL}"
if [[ ! -s "$CACHE_FILE" ]]; then
curl -f#SL -o "$CACHE_FILE" "$URL"
echo -en "\e[1A\e[0K"
msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}"
else
msg_ok "Using cached image ${CL}${BL}$(basename "$CACHE_FILE")${CL}"
fi
# ==============================================================================
# STORAGE TYPE DETECTION
# ==============================================================================
STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}') STORAGE_TYPE=$(pvesm status -storage "$STORAGE" | awk 'NR>1 {print $2}')
case $STORAGE_TYPE in case $STORAGE_TYPE in
nfs | dir) nfs | dir)
DISK_EXT=".qcow2" DISK_EXT=".qcow2"
DISK_REF="$VMID/" DISK_REF="$VMID/"
DISK_IMPORT="--format qcow2" DISK_IMPORT="-format qcow2"
THIN="" THIN=""
;; ;;
btrfs) btrfs)
DISK_EXT=".raw" DISK_EXT=".raw"
DISK_REF="$VMID/" DISK_REF="$VMID/"
DISK_IMPORT="--format raw" DISK_IMPORT="-format raw"
FORMAT=",efitype=4m" FORMAT=",efitype=4m"
THIN="" THIN=""
;; ;;
*)
DISK_EXT=""
DISK_REF=""
DISK_IMPORT="--format raw"
;;
esac esac
for i in {0,1}; do
# ============================================================================== disk="DISK$i"
# IMAGE CUSTOMIZATION WITH DOCKER eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}
# ============================================================================== eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}
msg_info "Preparing ${OS_DISPLAY} image with Docker"
WORK_FILE=$(mktemp --suffix=.qcow2)
cp "$CACHE_FILE" "$WORK_FILE"
export LIBGUESTFS_BACKEND_SETTINGS=dns=8.8.8.8,1.1.1.1
DOCKER_PREINSTALLED="no"
# Install qemu-guest-agent and Docker during image customization
msg_info "Installing base packages in image"
if virt-customize -a "$WORK_FILE" --install qemu-guest-agent,curl,ca-certificates >/dev/null 2>&1; then
msg_ok "Installed base packages"
msg_info "Installing Docker (this may take 2-5 minutes)"
if virt-customize -q -a "$WORK_FILE" --run-command "curl -fsSL https://get.docker.com | sh" >/dev/null 2>&1 &&
virt-customize -q -a "$WORK_FILE" --run-command "systemctl enable docker" >/dev/null 2>&1; then
msg_ok "Installed Docker"
msg_info "Configuring Docker daemon"
# Optimize Docker daemon configuration
virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/docker" >/dev/null 2>&1
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/docker/daemon.json << EOF
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF' >/dev/null 2>&1
DOCKER_PREINSTALLED="yes"
msg_ok "Configured Docker daemon"
else
msg_ok "Docker will be installed on first boot"
fi
else
msg_ok "Packages will be installed on first boot"
fi
msg_info "Finalizing image (hostname, SSH config)"
# Set hostname and prepare for unique machine-id
virt-customize -q -a "$WORK_FILE" --hostname "${HN}" >/dev/null 2>&1
virt-customize -q -a "$WORK_FILE" --run-command "truncate -s 0 /etc/machine-id" >/dev/null 2>&1
virt-customize -q -a "$WORK_FILE" --run-command "rm -f /var/lib/dbus/machine-id" >/dev/null 2>&1
# Configure SSH for Cloud-Init
if [ "$USE_CLOUD_INIT" = "yes" ]; then
virt-customize -q -a "$WORK_FILE" --run-command "sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config" >/dev/null 2>&1 || true
virt-customize -q -a "$WORK_FILE" --run-command "sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config" >/dev/null 2>&1 || true
else
# Configure auto-login for nocloud images (no Cloud-Init)
virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/systemd/system/serial-getty@ttyS0.service.d" >/dev/null 2>&1 || true
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/serial-getty@ttyS0.service.d/autologin.conf << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM
EOF' >/dev/null 2>&1 || true
virt-customize -q -a "$WORK_FILE" --run-command "mkdir -p /etc/systemd/system/getty@tty1.service.d" >/dev/null 2>&1 || true
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM
EOF' >/dev/null 2>&1 || true
fi
msg_ok "Finalized image"
# Create first-boot Docker install script (fallback if virt-customize failed)
if [ "$DOCKER_PREINSTALLED" = "no" ]; then
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /root/install-docker.sh << "DOCKERSCRIPT"
#!/bin/bash
exec > /var/log/install-docker.log 2>&1
echo "[$(date)] Starting Docker installation"
for i in {1..30}; do
ping -c 1 8.8.8.8 >/dev/null 2>&1 && break
sleep 2
done done
apt-get update if ! command -v virt-customize &>/dev/null; then
apt-get install -y qemu-guest-agent curl ca-certificates msg_info "Installing Pre-Requisite libguestfs-tools onto Host"
curl -fsSL https://get.docker.com | sh apt-get -qq update >/dev/null
systemctl enable docker apt-get -qq install libguestfs-tools lsb-release -y >/dev/null
systemctl start docker # Workaround for Proxmox VE 9.0 libguestfs issue
apt-get -qq install dhcpcd-base -y >/dev/null 2>&1 || true
mkdir -p /etc/docker msg_ok "Installed libguestfs-tools successfully"
cat > /etc/docker/daemon.json << DAEMON
{
"storage-driver": "overlay2",
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
DAEMON
systemctl restart docker
touch /root/.docker-installed
echo "[$(date)] Docker installation completed"
DOCKERSCRIPT
chmod +x /root/install-docker.sh' >/dev/null 2>&1
virt-customize -q -a "$WORK_FILE" --run-command 'cat > /etc/systemd/system/install-docker.service << "DOCKERSERVICE"
[Unit]
Description=Install Docker on First Boot
After=network-online.target
Wants=network-online.target
ConditionPathExists=!/root/.docker-installed
[Service]
Type=oneshot
ExecStart=/root/install-docker.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
DOCKERSERVICE
systemctl enable install-docker.service' >/dev/null 2>&1
fi fi
# Resize disk to target size msg_info "Adding Docker and Docker Compose Plugin to Debian 12 Qcow2 Disk Image"
msg_info "Resizing disk image to ${DISK_SIZE}" virt-customize -q -a "${FILE}" --install qemu-guest-agent,apt-transport-https,ca-certificates,curl,gnupg,software-properties-common,lsb-release >/dev/null &&
qemu-img resize "$WORK_FILE" "${DISK_SIZE}" >/dev/null 2>&1 virt-customize -q -a "${FILE}" --run-command "mkdir -p /etc/apt/keyrings && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg" >/dev/null &&
msg_ok "Resized disk image" virt-customize -q -a "${FILE}" --run-command "echo 'deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable' > /etc/apt/sources.list.d/docker.list" >/dev/null &&
virt-customize -q -a "${FILE}" --run-command "apt-get update -qq && apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin" >/dev/null &&
virt-customize -q -a "${FILE}" --run-command "systemctl enable docker" >/dev/null &&
virt-customize -q -a "${FILE}" --hostname "${HN}" >/dev/null &&
virt-customize -q -a "${FILE}" --run-command "echo -n > /etc/machine-id" >/dev/null
msg_ok "Added Docker and Docker Compose Plugin to Debian 12 Qcow2 Disk Image successfully"
# ============================================================================== msg_info "Expanding root partition to use full disk space"
# VM CREATION qemu-img create -f qcow2 expanded.qcow2 ${DISK_SIZE} >/dev/null 2>&1
# ============================================================================== virt-resize --expand /dev/sda1 ${FILE} expanded.qcow2 >/dev/null 2>&1
msg_info "Creating Docker VM shell" mv expanded.qcow2 ${FILE} >/dev/null 2>&1
msg_ok "Expanded image to full size"
msg_info "Creating a Docker VM"
qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \ qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \
-name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci >/dev/null -name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
pvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null
msg_ok "Created VM shell" qm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null
qm set $VMID \
# ============================================================================== -efidisk0 ${DISK0_REF}${FORMAT} \
# DISK IMPORT -scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=${DISK_SIZE} \
# ============================================================================== -boot order=scsi0 \
msg_info "Importing disk into storage ($STORAGE)" -serial0 socket >/dev/null
qm resize $VMID scsi0 8G >/dev/null
if qm disk import --help >/dev/null 2>&1; then
IMPORT_CMD=(qm disk import)
else
IMPORT_CMD=(qm importdisk)
fi
IMPORT_OUT="$("${IMPORT_CMD[@]}" "$VMID" "$WORK_FILE" "$STORAGE" ${DISK_IMPORT:-} 2>&1 || true)"
DISK_REF_IMPORTED="$(printf '%s\n' "$IMPORT_OUT" | sed -n "s/.*successfully imported disk '\([^']\+\)'.*/\1/p" | tr -d "\r\"'")"
[[ -z "$DISK_REF_IMPORTED" ]] && DISK_REF_IMPORTED="$(pvesm list "$STORAGE" | awk -v id="$VMID" '$5 ~ ("vm-"id"-disk-") {print $1":"$5}' | sort | tail -n1)"
[[ -z "$DISK_REF_IMPORTED" ]] && {
msg_error "Unable to determine imported disk reference."
echo "$IMPORT_OUT"
exit 1
}
msg_ok "Imported disk (${CL}${BL}${DISK_REF_IMPORTED}${CL})"
# Clean up work file
rm -f "$WORK_FILE"
# ==============================================================================
# VM CONFIGURATION
# ==============================================================================
msg_info "Attaching EFI and root disk"
qm set "$VMID" \
--efidisk0 "${STORAGE}:0,efitype=4m" \
--scsi0 "${DISK_REF_IMPORTED},${DISK_CACHE}${THIN%,}" \
--boot order=scsi0 \
--serial0 socket >/dev/null
qm set $VMID --agent enabled=1 >/dev/null qm set $VMID --agent enabled=1 >/dev/null
msg_ok "Attached EFI and root disk" DESCRIPTION=$(
cat <<EOF
<div align='center'>
<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>
# Set VM description <h2 style='font-size: 24px; margin: 20px 0;'>Docker VM</h2>
set_description
# Cloud-Init configuration <p style='margin: 16px 0;'>
if [ "$USE_CLOUD_INIT" = "yes" ]; then <a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
msg_info "Configuring Cloud-Init" <img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
setup_cloud_init "$VMID" "$STORAGE" "$HN" "yes" </a>
msg_ok "Cloud-Init configured" </p>
fi
# Start VM <span style='margin: 0 10px;'>
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
</span>
</div>
EOF
)
qm set $VMID -description "$DESCRIPTION" >/dev/null
msg_ok "Created a Docker VM ${CL}${BL}(${HN})"
if [ "$START_VM" == "yes" ]; then if [ "$START_VM" == "yes" ]; then
msg_info "Starting Docker VM" msg_info "Starting Docker VM"
qm start $VMID >/dev/null 2>&1 qm start $VMID
msg_ok "Started Docker VM" msg_ok "Started Docker VM"
fi fi
# ==============================================================================
# FINAL OUTPUT
# ==============================================================================
VM_IP=""
if [ "$START_VM" == "yes" ]; then
set +e
for i in {1..10}; do
VM_IP=$(qm guest cmd "$VMID" network-get-interfaces 2>/dev/null |
jq -r '.[] | select(.name != "lo") | ."ip-addresses"[]? | select(."ip-address-type" == "ipv4") | ."ip-address"' 2>/dev/null |
grep -v "^127\." | head -1) || true
[ -n "$VM_IP" ] && break
sleep 3
done
set -e
fi
echo -e "\n${INFO}${BOLD}${GN}Docker VM Configuration Summary:${CL}"
echo -e "${TAB}${DGN}VM ID: ${BGN}${VMID}${CL}"
echo -e "${TAB}${DGN}Hostname: ${BGN}${HN}${CL}"
echo -e "${TAB}${DGN}OS: ${BGN}${OS_DISPLAY}${CL}"
[ -n "$VM_IP" ] && echo -e "${TAB}${DGN}IP Address: ${BGN}${VM_IP}${CL}"
if [ "$DOCKER_PREINSTALLED" = "yes" ]; then
echo -e "${TAB}${DGN}Docker: ${BGN}Pre-installed (via get.docker.com)${CL}"
else
echo -e "${TAB}${DGN}Docker: ${BGN}Installing on first boot${CL}"
echo -e "${TAB}${YW}⚠️ Wait 2-3 minutes for installation to complete${CL}"
echo -e "${TAB}${YW}⚠️ Check progress: ${BL}cat /var/log/install-docker.log${CL}"
fi
if [ "$USE_CLOUD_INIT" = "yes" ]; then
display_cloud_init_info "$VMID" "$HN" 2>/dev/null || true
fi
post_update_to_api "done" "none" post_update_to_api "done" "none"
msg_ok "Completed successfully!\n" msg_ok "Completed successfully!\n"

View File

@@ -1,6 +0,0 @@
____ __
/ __ \____ _____/ /_____ _____
/ / / / __ \/ ___/ //_/ _ \/ ___/
/ /_/ / /_/ / /__/ ,< / __/ /
/_____/\____/\___/_/|_|\___/_/