Compare commits

..
Author SHA1 Message Date
MickLesk dd38ed34e0 Let ArchiveBox 0.9 actually crawl, and serve its snapshots
Every extractor binary 0.9 cannot find aborts the crawl runner, and abx-plugins
keeps adding new ones, so a fixed apt list always falls behind while the service
user cannot run apt. archivebox install now runs as root with the service user's
home, the way upstream's own package does, and resolves every system dependency
itself; mercury stays disabled because npm refuses its git dependency. Snapshot
iframes are built from BASE_URL, so the update sets it when missing or when it is
a stale IP, and migrated 0.7 collections get their snapshots re-indexed.
2026-09-28 09:25:34 +02:00
MickLesk 049f2dd151 Name the admin account the way the docs do
createsuperuser was given admin, but the documented default user is archivebox -
which is what 0.7.4 produced, because its expect block sent an empty username and
Django then fell back to the OS user the command ran as.
2026-09-25 18:12:51 +02:00
MickLesk 57c299d068 Treat extractors as optional
@postlight/parser depends on a git URL and npm refuses to fetch those, so one
unreachable extractor aborted the whole install; readability and trafilatura cover
the same job and provision fine.
2026-09-25 16:14:52 +02:00
MickLesk 959a86983c Build the venv on the distro python and drop the obsolete extractors
A uv-managed interpreter lives where the service user cannot execute it, so every
archivebox call as that user died with 'Permission denied' while 0.9 drops privileges
on import; Debian 13 ships python3.13 in apt, so the venv now points at /usr/bin.
0.9 also provisions single-file and readability itself through abx-dl - the two npm
modules no longer exist - and it needs BASE_URL pinned plus tesseract, imagemagick,
ffmpeg, unzip and wget present, or archivebox install reaches for apt as a user that
has no root.
2026-09-25 14:30:30 +02:00
MickLesk eb1005e9b5 minor fixes 2026-09-25 13:43:44 +02:00
MickLesk 6a33fc45ad Let ArchiveBox reach 0.9 instead of staying on 0.7.4
uv pip install --system targets the container's Python 3.11, and every ArchiveBox
release from 0.9 on requires 3.13, so the resolver silently fell back to 0.7.4.
The package now lives in its own 3.13 venv, with the interpreter outside root's
home because ArchiveBox drops privileges while importing. The expect block is
gone: init and createsuperuser both run unattended now, and the server moved to
its new default port.
2026-09-25 11:30:16 +02:00
1759 changed files with 38491 additions and 16178 deletions

No files matched your search

+5
View File
@@ -7,6 +7,11 @@
*.func linguist-detectable=true
*.install linguist-language=Shell
# ---------------------------------------
# Exclude header art from stats
# ---------------------------------------
ct/headers/* linguist-documentation
# ---------------------------------------
# Exclude documentation from stats
# ---------------------------------------
+2 -18
View File
@@ -58,9 +58,9 @@ body:
Before continuing, verify that the requested project meets the minimum requirements below.
**Projects with fewer than 1,000 stars, or younger than 6 months, are generally not eligible for a script request.**
**Projects with fewer than 1,000 stars are generally not eligible for a script request.**
An automated check verifies the star count and project age for GitHub-hosted projects. If the project doesn't meet these requirements, fill in **Justification for an exception** below — without one, the request is closed automatically. With one, it's flagged for manual review instead. Exceptions are rare and require a clearly verifiable, significant public adoption signal.
Exceptions are only considered where there is a clearly verifiable, significant public adoption signal.
- type: input
id: repository-stars
@@ -134,22 +134,6 @@ body:
- label: I understand that requests outside the project scope may be closed without an extensive explanation.
required: true
- type: textarea
id: justification
attributes:
label: Justification for an exception
description: >-
Only needed if the project has fewer than 1,000 stars or is younger than 6 months (e.g. it has no GitHub
presence, so stars don't apply). Explain the significant public adoption signal. Leave blank if the project
meets the normal requirements — requests that don't meet the requirements and have no justification here are
closed automatically.
placeholder: |
Example:
No public GitHub repository, but the project has 50k+ active installs reported by its own telemetry
dashboard and is covered by several established self-hosting publications.
validations:
required: false
- type: markdown
attributes:
value: |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-15
View File
@@ -1,15 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title desc">
<title id="title">Community Scripts - CS Amber</title>
<desc id="desc">A continuous ivory C interlocks with a warm orange S on charcoal. Both letterforms use rounded, folded geometry.</desc>
<rect width="512" height="512" rx="112" fill="#29262b"/>
<defs>
<mask id="weave-clearance" maskUnits="userSpaceOnUse" x="0" y="0" width="512" height="512">
<rect width="512" height="512" fill="white"/>
<path d="M390 142H322L270 172V220L322 250H348L392 276V338L338 370H284" fill="none" stroke="black" stroke-width="72" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
</defs>
<g fill="none" stroke-width="56" stroke-linecap="round" stroke-linejoin="round">
<path d="M254 142H186L118 182V330L186 370H254" stroke="#f5f4eb" mask="url(#weave-clearance)"/>
<path d="M390 142H322L270 172V220L322 250H348L392 276V338L338 370H284" stroke="#f29a67"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 997 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

+19 -3
View File
@@ -10,7 +10,9 @@
],
"excludeGlobs": [
"**/headers/**",
"tools/headers/**"
"ct/headers/**",
"tools/headers/**",
"vm/headers/**"
]
}
],
@@ -46,7 +48,9 @@
],
"excludeGlobs": [
"**/headers/**",
"tools/headers/**"
"ct/headers/**",
"tools/headers/**",
"vm/headers/**"
]
}
],
@@ -77,6 +81,17 @@
"excludeGlobs": []
}
],
"core": [
{
"fileStatus": null,
"includeGlobs": [
"misc/*.func"
],
"excludeGlobs": [
"misc/api.func"
]
}
],
"documentation": [
{
"fileStatus": null,
@@ -103,7 +118,8 @@
{
"fileStatus": null,
"includeGlobs": [
"api/**"
"api/**",
"misc/api.func"
],
"excludeGlobs": []
}
-230
View File
@@ -1,233 +1,3 @@
## 2026-09-30
### 🆕 New Scripts
- Shoko ([#17617](https://github.com/community-scripts/ProxmoxVE/pull/17617))
- SIlo ([#17614](https://github.com/community-scripts/ProxmoxVE/pull/17614))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(blocky): restart on failure and wait for network-online [@haplo](https://github.com/haplo) ([#17601](https://github.com/community-scripts/ProxmoxVE/pull/17601))
- SparkyFitness: update sed replacement [@tomfrenzel](https://github.com/tomfrenzel) ([#17604](https://github.com/community-scripts/ProxmoxVE/pull/17604))
### 💾 Core
- vm: autologin on the serial console for images without cloud-init [@MickLesk](https://github.com/MickLesk) ([core#80](https://github.com/community-scripts/core/pull/80))
- download_gpg_key: make dearmored keyrings world-readable [@MickLesk](https://github.com/MickLesk) ([core#79](https://github.com/community-scripts/core/pull/79))
## 2026-09-29
### 🆕 New Scripts
- Anki Sync Server ([#17416](https://github.com/community-scripts/ProxmoxVE/pull/17416))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(kaneo): build with Vite+ instead of Turbo [@tinsever](https://github.com/tinsever) ([#17599](https://github.com/community-scripts/ProxmoxVE/pull/17599))
- tianji: raise the Node heap for the static build on install [@MickLesk](https://github.com/MickLesk) ([#17582](https://github.com/community-scripts/ProxmoxVE/pull/17582))
- fix(romm): snapshot Redis hourly like the upstream image [@DenislavDenev](https://github.com/DenislavDenev) ([#17562](https://github.com/community-scripts/ProxmoxVE/pull/17562))
- checkmate: bump Node.js from 22 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17554](https://github.com/community-scripts/ProxmoxVE/pull/17554))
- #### 🔧 Refactor
- Drop the scripts base pin from every ct/ script [@MickLesk](https://github.com/MickLesk) ([#17585](https://github.com/community-scripts/ProxmoxVE/pull/17585))
- odoo: move to Debian 13 and stay on the installed major on update [@MickLesk](https://github.com/MickLesk) ([#17581](https://github.com/community-scripts/ProxmoxVE/pull/17581))
- manyfold: use upstream f3d for headless renders on amd64 [@MickLesk](https://github.com/MickLesk) ([#17583](https://github.com/community-scripts/ProxmoxVE/pull/17583))
### 💾 Core
- Default the scripts base to ProxmoxVE [@MickLesk](https://github.com/MickLesk) ([core#76](https://github.com/community-scripts/core/pull/76))
### 🧰 Tools
- #### 🐞 Bug Fixes
- all-templates: handle the arch column of pveam available [@MickLesk](https://github.com/MickLesk) ([#17579](https://github.com/community-scripts/ProxmoxVE/pull/17579))
### 📂 Github
- Skip automated core pull requests in the changelog [@MickLesk](https://github.com/MickLesk) ([#17589](https://github.com/community-scripts/ProxmoxVE/pull/17589))
- Post a test command for vm/, tools/ and turnkey/ changes too [@MickLesk](https://github.com/MickLesk) ([#17587](https://github.com/community-scripts/ProxmoxVE/pull/17587))
- Remove the PocketBase AI bot [@MickLesk](https://github.com/MickLesk) ([#17591](https://github.com/community-scripts/ProxmoxVE/pull/17591))
## 2026-09-28
### 🚀 Updated Scripts
- Immich: Pin version to 3.2.4 [@vhsdream](https://github.com/vhsdream) ([#17564](https://github.com/community-scripts/ProxmoxVE/pull/17564))
- #### 🐞 Bug Fixes
- fix(autocaliweb): wire .env into all service units, not just autocali… [@bobartlett](https://github.com/bobartlett) ([#17561](https://github.com/community-scripts/ProxmoxVE/pull/17561))
- Zipline: Change cp command [@Mraedis](https://github.com/Mraedis) ([#17559](https://github.com/community-scripts/ProxmoxVE/pull/17559))
- Borg-UI: start through upstream's start.sh so migrations run [@MickLesk](https://github.com/MickLesk) ([#17563](https://github.com/community-scripts/ProxmoxVE/pull/17563))
- linkding: serve favicons and preview images [@MickLesk](https://github.com/MickLesk) ([#17557](https://github.com/community-scripts/ProxmoxVE/pull/17557))
- SparkyFitness: Fill in the nginx resolver 1.7.3 added [@MickLesk](https://github.com/MickLesk) ([#17556](https://github.com/community-scripts/ProxmoxVE/pull/17556))
- #### 🔧 Refactor
- Plane: Replace MinIO installation with Silo [@MickLesk](https://github.com/MickLesk) ([#17347](https://github.com/community-scripts/ProxmoxVE/pull/17347))
## 2026-09-27
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(aurral): bump to Node 26 and bypass strict engine pin [@JodyVanden](https://github.com/JodyVanden) ([#17543](https://github.com/community-scripts/ProxmoxVE/pull/17543))
- Tracearr: fetch map tiles on install/update [@durzo](https://github.com/durzo) ([#17544](https://github.com/community-scripts/ProxmoxVE/pull/17544))
- fix(autocaliweb): define INSTALL_DIR before first use [@bobartlett](https://github.com/bobartlett) ([#17545](https://github.com/community-scripts/ProxmoxVE/pull/17545))
## 2026-09-26
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Manyfold: make /opt/manyfold_data writable for the manyfold user [@sergstepanenko](https://github.com/sergstepanenko) ([#17534](https://github.com/community-scripts/ProxmoxVE/pull/17534))
- steamcmd: update itself before installing Satisfactory [@MickLesk](https://github.com/MickLesk) ([#17526](https://github.com/community-scripts/ProxmoxVE/pull/17526))
- AudioMuse-AI: build noavx2 environment on Python 3.11 [@MickLesk](https://github.com/MickLesk) ([#17528](https://github.com/community-scripts/ProxmoxVE/pull/17528))
- LobeHub: pin lobehub/ui at 5.49 while LobeHub's stable release needs it [@MickLesk](https://github.com/MickLesk) ([#17531](https://github.com/community-scripts/ProxmoxVE/pull/17531))
- Keep Trilium updates on the server releases [@MickLesk](https://github.com/MickLesk) ([#17525](https://github.com/community-scripts/ProxmoxVE/pull/17525))
- NginxProxyManager: generate admin config and repair certbot's venv on update [@MickLesk](https://github.com/MickLesk) ([#17523](https://github.com/community-scripts/ProxmoxVE/pull/17523))
- Komodo: add KOMODO_HOST | fix broken spinner [@MickLesk](https://github.com/MickLesk) ([#17481](https://github.com/community-scripts/ProxmoxVE/pull/17481))
- romm: run rq cron and a scans worker for RomM 5.3 [@davidbb](https://github.com/davidbb) ([#17502](https://github.com/community-scripts/ProxmoxVE/pull/17502))
- #### 💥 Breaking Changes
- Semaphore: fix BoltDB migration by pinning 2.18.30 [@MickLesk](https://github.com/MickLesk) ([#17439](https://github.com/community-scripts/ProxmoxVE/pull/17439))
### 💾 Core
- Keep a dash-led bridge comment out of whiptail's options [@MickLesk](https://github.com/MickLesk) ([core#74](https://github.com/community-scripts/core/pull/74))
## 2026-09-25
### 🆕 New Scripts
- RustFS ([#17499](https://github.com/community-scripts/ProxmoxVE/pull/17499))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Ignis: take the Obsidian version from release tag [@MickLesk](https://github.com/MickLesk) ([#17503](https://github.com/community-scripts/ProxmoxVE/pull/17503))
- #### 🔧 Refactor
- Refactor: Umbrel OS VM [@MickLesk](https://github.com/MickLesk) ([#17505](https://github.com/community-scripts/ProxmoxVE/pull/17505))
- Docker-LXC: remove Portainer installation from install [@MickLesk](https://github.com/MickLesk) ([#17493](https://github.com/community-scripts/ProxmoxVE/pull/17493))
### 💾 Core
- Incus: make prompts visible for end-user [@MickLesk](https://github.com/MickLesk) ([core#73](https://github.com/community-scripts/core/pull/73))
- incus: do not let an install script block on an invisible prompt [@MickLesk](https://github.com/MickLesk) ([core#71](https://github.com/community-scripts/core/pull/71))
- tel: report progress from installs only, not updates [@MickLesk](https://github.com/MickLesk) ([core#72](https://github.com/community-scripts/core/pull/72))
## 2026-09-24
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Declare the RomM filesystem structure required since 5.3.0 [@MickLesk](https://github.com/MickLesk) ([#17480](https://github.com/community-scripts/ProxmoxVE/pull/17480))
- general: use apt_update_safe instead of aborting on a failed apt update [@MickLesk](https://github.com/MickLesk) ([#17462](https://github.com/community-scripts/ProxmoxVE/pull/17462))
- #### 🔧 Refactor
- Journiv: Serve Node Frontend and add HTTP Auth .env [@MickLesk](https://github.com/MickLesk) ([#17479](https://github.com/community-scripts/ProxmoxVE/pull/17479))
### 💾 Core
- core: accept an off-subnet gateway for /31 and /32 [@MickLesk](https://github.com/MickLesk) ([core#70](https://github.com/community-scripts/core/pull/70))
## 2026-09-23
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(signoz): install the histogramQuantile ClickHouse function [@maksimtech](https://github.com/maksimtech) ([#17386](https://github.com/community-scripts/ProxmoxVE/pull/17386))
- omniroute: bump to 4 GB for the npm install [@MickLesk](https://github.com/MickLesk) ([#17461](https://github.com/community-scripts/ProxmoxVE/pull/17461))
- Run Borg-UI with a single gunicorn worker [@MickLesk](https://github.com/MickLesk) ([#17445](https://github.com/community-scripts/ProxmoxVE/pull/17445))
- immich: keep geodata linked and the build deps present on update [@MickLesk](https://github.com/MickLesk) ([#17438](https://github.com/community-scripts/ProxmoxVE/pull/17438))
- #### 🔧 Refactor
- Refactor: Scanopy [@MickLesk](https://github.com/MickLesk) ([#16797](https://github.com/community-scripts/ProxmoxVE/pull/16797))
### 💾 Core
- stop a partial find from aborting the caller under pipefail [@MickLesk](https://github.com/MickLesk) ([core#68](https://github.com/community-scripts/core/pull/68))
- core: give RHEL-family containers an ostype PVE accepts [@MickLesk](https://github.com/MickLesk) ([core#67](https://github.com/community-scripts/core/pull/67))
- core: do not abort on a failed apt update [@MickLesk](https://github.com/MickLesk) ([core#66](https://github.com/community-scripts/core/pull/66))
- VM's: report a finished VM as done, not aborted [@MickLesk](https://github.com/MickLesk) ([core#65](https://github.com/community-scripts/core/pull/65))
- VM-Core: decide console autologin from the image, not USE_CLOUD_INIT [@MickLesk](https://github.com/MickLesk) ([core#63](https://github.com/community-scripts/core/pull/63))
- VM-Core: report failed image steps and restore tty1 autologin without cloud-init [@MickLesk](https://github.com/MickLesk) ([core#62](https://github.com/community-scripts/core/pull/62))
- tools: raise pnpm/yarn fetch timeout and lower network concurrency [@MickLesk](https://github.com/MickLesk) ([core#59](https://github.com/community-scripts/core/pull/59))
- VM-Core: stop discarding cloud-init error messages [@MickLesk](https://github.com/MickLesk) ([core#60](https://github.com/community-scripts/core/pull/60))
## 2026-09-22
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(invoiceshelf): re-link storage during update [@owenvoke](https://github.com/owenvoke) ([#17431](https://github.com/community-scripts/ProxmoxVE/pull/17431))
- fix(alpine-it-tools): write the version file after a successful update [@Houtek](https://github.com/Houtek) ([#17421](https://github.com/community-scripts/ProxmoxVE/pull/17421))
- Umami: Align pnpm with engines.pnpm on update [@ReneNulschDE](https://github.com/ReneNulschDE) ([#17404](https://github.com/community-scripts/ProxmoxVE/pull/17404))
- #### 🔧 Refactor
- several scripts: let uv see the project before syncing it | refactor some scripts that use uv [@MickLesk](https://github.com/MickLesk) ([#17436](https://github.com/community-scripts/ProxmoxVE/pull/17436))
### 💾 Core
- setup_uv: honour a project's required-version pin [@MickLesk](https://github.com/MickLesk) ([core#58](https://github.com/community-scripts/core/pull/58))
- fix(motd): repair legacy 00_lxc-details.sh header and dead color placeholders [@BadFlo](https://github.com/BadFlo) ([core#53](https://github.com/community-scripts/core/pull/53))
- meilisearch: take the raw binary, not the .deb [@MickLesk](https://github.com/MickLesk) ([core#57](https://github.com/community-scripts/core/pull/57))
### 📚 Documentation
- github: Stop generating header filenames with a newline in them [@MickLesk](https://github.com/MickLesk) ([#17435](https://github.com/community-scripts/ProxmoxVE/pull/17435))
### ❔ Uncategorized
- chore(ct): sync cloudflare-ddns defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17446](https://github.com/community-scripts/ProxmoxVE/pull/17446))
## 2026-09-21
### 🆕 New Scripts
- AudioMuse-AI ([#17415](https://github.com/community-scripts/ProxmoxVE/pull/17415))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(portabase): meet the default admin password rules [@maksimtech](https://github.com/maksimtech) ([#17384](https://github.com/community-scripts/ProxmoxVE/pull/17384))
- truenas-vm: strip the G before the storage allocation [@MickLesk](https://github.com/MickLesk) ([#17407](https://github.com/community-scripts/ProxmoxVE/pull/17407))
- Librenms - Fix local snmpd community string setup [@dopch](https://github.com/dopch) ([#17363](https://github.com/community-scripts/ProxmoxVE/pull/17363))
- fix(iventoy): preserve release data and migrate legacy service launchers [@Msprg](https://github.com/Msprg) ([#17161](https://github.com/community-scripts/ProxmoxVE/pull/17161))
- xyops: bump Node.js from 22 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17398](https://github.com/community-scripts/ProxmoxVE/pull/17398))
### 💾 Core
- Generate app headers [@github-actions[bot]](https://github.com/github-actions[bot]) ([core#56](https://github.com/community-scripts/core/pull/56))
- github-api: take the release the maintainer marked as latest [@MickLesk](https://github.com/MickLesk) ([core#55](https://github.com/community-scripts/core/pull/55))
- general: prune old Rust toolchains [@MickLesk](https://github.com/MickLesk) ([core#52](https://github.com/community-scripts/core/pull/52))
- Ask when the preselected storage cannot hold the content [@MickLesk](https://github.com/MickLesk) ([core#51](https://github.com/community-scripts/core/pull/51))
## 2026-09-20
### 🆕 New Scripts
- Your-Spotify ([#17376](https://github.com/community-scripts/ProxmoxVE/pull/17376))
## 2026-09-19
### 🆕 New Scripts
-802
View File
@@ -1,802 +0,0 @@
## 2026-10-31
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Docker VM: take the imported disk from vm_import_disk [@MickLesk](https://github.com/MickLesk) ([#18341](https://github.com/community-scripts/ProxmoxVE/pull/18341))
- Twenty: move to Node.js 26 [@MickLesk](https://github.com/MickLesk) ([#18337](https://github.com/community-scripts/ProxmoxVE/pull/18337))
- Norish: fetch Obscura from the repo Norish pins [@MickLesk](https://github.com/MickLesk) ([#18338](https://github.com/community-scripts/ProxmoxVE/pull/18338))
- HAOS VM: take the imported disk from vm_import_disk [@MickLesk](https://github.com/MickLesk) ([#18342](https://github.com/community-scripts/ProxmoxVE/pull/18342))
- Aurral: build the pnpm workspace and run backend/server.ts [@MickLesk](https://github.com/MickLesk) ([#18340](https://github.com/community-scripts/ProxmoxVE/pull/18340))
- PS5-MQTT: build the yarn workspace and run the packaged server [@MickLesk](https://github.com/MickLesk) ([#18339](https://github.com/community-scripts/ProxmoxVE/pull/18339))
- Jellyseerr, Overseerr, Paperless-ngx, Vikunja: skip migration prompts when unattended [@florianpunz](https://github.com/florianpunz) ([#18321](https://github.com/community-scripts/ProxmoxVE/pull/18321))
- Discourse: track the Ruby version Discourse requires [@MickLesk](https://github.com/MickLesk) ([#18329](https://github.com/community-scripts/ProxmoxVE/pull/18329))
- #### ✨ New Features
- MatterJS-Server: Add time sync [@tomfrenzel](https://github.com/tomfrenzel) ([#18330](https://github.com/community-scripts/ProxmoxVE/pull/18330))
- #### 🔧 Refactor
- Debian VM: keep a static IP without a snippets storage [@MickLesk](https://github.com/MickLesk) ([#18346](https://github.com/community-scripts/ProxmoxVE/pull/18346))
- Ubuntu VM: check for a snippets storage before building a static-IP VM [@MickLesk](https://github.com/MickLesk) ([#18347](https://github.com/community-scripts/ProxmoxVE/pull/18347))
### 💾 Core
- feat: add setup_java_memory: size Java services from the CT's RAM on Proxmox [@MickLesk](https://github.com/MickLesk) ([core#181](https://github.com/community-scripts/core/pull/181))
- analytics: name the Proxmox tasks when a container stops mid-install [@MickLesk](https://github.com/MickLesk) ([core#184](https://github.com/community-scripts/core/pull/184))
- PVE: do not abort when the spinner PID file cannot be written [@MickLesk](https://github.com/MickLesk) ([core#183](https://github.com/community-scripts/core/pull/183))
### 📂 Github
- Drop the legacy engine copies from misc/ [@MickLesk](https://github.com/MickLesk) ([#18294](https://github.com/community-scripts/ProxmoxVE/pull/18294))
## 2026-10-30
### 🆕 New Scripts
- Reconya ([#18325](https://github.com/community-scripts/ProxmoxVE/pull/18325))
- Blinko ([#18324](https://github.com/community-scripts/ProxmoxVE/pull/18324))
- ChartDB ([#18323](https://github.com/community-scripts/ProxmoxVE/pull/18323))
## 2026-10-29
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Karakeep: fix monolith release asset pattern [@MohamedBassem](https://github.com/MohamedBassem) ([#18317](https://github.com/community-scripts/ProxmoxVE/pull/18317))
- Vikunja: fold the package's service block into the config's service section [@MickLesk](https://github.com/MickLesk) ([#18311](https://github.com/community-scripts/ProxmoxVE/pull/18311))
- Stirling-PDF: size JVM heap from container memory, not host RAM [@aashishvanand](https://github.com/aashishvanand) ([#18292](https://github.com/community-scripts/ProxmoxVE/pull/18292))
- fix(authentik): migrate systemd units when still on pre-2026.8 paths [@CervezaStallone](https://github.com/CervezaStallone) ([#18307](https://github.com/community-scripts/ProxmoxVE/pull/18307))
- fix(dawarich): serve .mjs MapLibre modules as JavaScript [@ricbaral](https://github.com/ricbaral) ([#18304](https://github.com/community-scripts/ProxmoxVE/pull/18304))
- #### ✨ New Features
- VictoriaLogs: enable syslog ingestion on TCP/UDP 514 [@opastorello](https://github.com/opastorello) ([#18300](https://github.com/community-scripts/ProxmoxVE/pull/18300))
### 💾 Core
- feat: Performance - load host data in the background and stop reporting failed pre-checks [@MickLesk](https://github.com/MickLesk) ([core#179](https://github.com/community-scripts/core/pull/179))
### 📚 Documentation
- gh: let the bot set oidc_url and license [@MickLesk](https://github.com/MickLesk) ([#18313](https://github.com/community-scripts/ProxmoxVE/pull/18313))
## 2026-10-28
### 🆕 New Scripts
- VictoriaLogs ([#18282](https://github.com/community-scripts/ProxmoxVE/pull/18282))
- Deemix ([#18272](https://github.com/community-scripts/ProxmoxVE/pull/18272))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(vm/opnsense): increase startup wait from 90 to 180 seconds [@MTlyx](https://github.com/MTlyx) ([#18086](https://github.com/community-scripts/ProxmoxVE/pull/18086))
- apache-tomcat: restore the update backup from its real store [@mazelado](https://github.com/mazelado) ([#18145](https://github.com/community-scripts/ProxmoxVE/pull/18145))
- AdventureLog: clean-install on update and drop the DRF <3.15 pin [@angusmaul](https://github.com/angusmaul) ([#18281](https://github.com/community-scripts/ProxmoxVE/pull/18281))
- #### 🔧 Refactor
- Refactor: ownCloud VM [@MickLesk](https://github.com/MickLesk) ([#18295](https://github.com/community-scripts/ProxmoxVE/pull/18295))
### 💾 Core
- Keep the console hint out of the default output [@MickLesk](https://github.com/MickLesk) ([core#176](https://github.com/community-scripts/core/pull/176))
### 📚 Documentation
- Move a promoted script's telemetry history to ProxmoxVE [@MickLesk](https://github.com/MickLesk) ([#18271](https://github.com/community-scripts/ProxmoxVE/pull/18271))
### ❔ Uncategorized
- chore(ct): sync tdarr defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18289](https://github.com/community-scripts/ProxmoxVE/pull/18289))
## 2026-10-27
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Zabbix: leave out the NVIDIA agent2 plugin that breaks the all-plugins install [@MickLesk](https://github.com/MickLesk) ([#18262](https://github.com/community-scripts/ProxmoxVE/pull/18262))
- Omada: detect latest version from product download page [@ChrisKappe](https://github.com/ChrisKappe) ([#18248](https://github.com/community-scripts/ProxmoxVE/pull/18248))
- LanguageTool: count ngram prompt retries without tripping errexit [@MickLesk](https://github.com/MickLesk) ([#18258](https://github.com/community-scripts/ProxmoxVE/pull/18258))
- Overseerr: point fresh installs to Seerr [@MickLesk](https://github.com/MickLesk) ([#18259](https://github.com/community-scripts/ProxmoxVE/pull/18259))
- stalwart: pin to v0.16.27 until v1 migration is supported [@MickLesk](https://github.com/MickLesk) ([#18252](https://github.com/community-scripts/ProxmoxVE/pull/18252))
- #### ✨ New Features
- TurnKey: offer only current appliances, stop on arm64 [@MickLesk](https://github.com/MickLesk) ([#18265](https://github.com/community-scripts/ProxmoxVE/pull/18265))
- #### 🔧 Refactor
- NPMplus: let the ACME e-mail stay empty again, Let's Encrypt does not need one [@MickLesk](https://github.com/MickLesk) ([#18263](https://github.com/community-scripts/ProxmoxVE/pull/18263))
### 🗑️ Deleted Scripts
- Remove: Readarr [@MickLesk](https://github.com/MickLesk) ([#18264](https://github.com/community-scripts/ProxmoxVE/pull/18264))
### 💾 Core
- Say the same thing on both hosts while a container is created [@MickLesk](https://github.com/MickLesk) ([core#173](https://github.com/community-scripts/core/pull/173))
- Skip the IPv6 sysctl when the host kernel has no IPv6 [@MickLesk](https://github.com/MickLesk) ([core#171](https://github.com/community-scripts/core/pull/171))
- Stop reporting a closed console as a user abort, and shield the host's own work [@MickLesk](https://github.com/MickLesk) ([core#172](https://github.com/community-scripts/core/pull/172))
## 2026-10-26
### 🆕 New Scripts
- Vane ([#18168](https://github.com/community-scripts/ProxmoxVE/pull/18168))
- HomeTube ([#18172](https://github.com/community-scripts/ProxmoxVE/pull/18172))
- Honcho ([#18169](https://github.com/community-scripts/ProxmoxVE/pull/18169))
- YTPTube ([#18174](https://github.com/community-scripts/ProxmoxVE/pull/18174))
- Stump ([#18173](https://github.com/community-scripts/ProxmoxVE/pull/18173))
- Casdoor ([#18170](https://github.com/community-scripts/ProxmoxVE/pull/18170))
- Streamystats ([#18171](https://github.com/community-scripts/ProxmoxVE/pull/18171))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: lock only this platform when mise installs the plugin tools [@MickLesk](https://github.com/MickLesk) ([#18241](https://github.com/community-scripts/ProxmoxVE/pull/18241))
- Fix service name case for Splunkd [@CerberusStyle](https://github.com/CerberusStyle) ([#18238](https://github.com/community-scripts/ProxmoxVE/pull/18238))
- VMs: pass the machine type, CPU, disk cache and discard settings to qm [@MickLesk](https://github.com/MickLesk) ([#18232](https://github.com/community-scripts/ProxmoxVE/pull/18232))
- excalidash: bump Node.js from 20 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18213](https://github.com/community-scripts/ProxmoxVE/pull/18213))
- readmeabook: bump Node.js from 20 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18217](https://github.com/community-scripts/ProxmoxVE/pull/18217))
- homepage: bump Node.js from 22 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18214](https://github.com/community-scripts/ProxmoxVE/pull/18214))
- remove old -install.sh (verdaccio) [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18218](https://github.com/community-scripts/ProxmoxVE/pull/18218))
- meshcentral: bump Node.js from 22 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18215](https://github.com/community-scripts/ProxmoxVE/pull/18215))
- #### ✨ New Features
- MeshCentral: update the npm package, not just the OS [@MickLesk](https://github.com/MickLesk) ([#18243](https://github.com/community-scripts/ProxmoxVE/pull/18243))
### 💾 Core
- feat: let an install survive a closed console / dont sighup installs [@MickLesk](https://github.com/MickLesk) ([core#167](https://github.com/community-scripts/core/pull/167))
- feat: create containers from the linuxcontainers.org catalog when Proxmox has no template [@MickLesk](https://github.com/MickLesk) ([core#25](https://github.com/community-scripts/core/pull/25))
### 📚 Documentation
- Ref: Docs / Readme [@MickLesk](https://github.com/MickLesk) ([#18233](https://github.com/community-scripts/ProxmoxVE/pull/18233))
## 2026-10-25
### 🆕 New Scripts
- Dreeve ([#18167](https://github.com/community-scripts/ProxmoxVE/pull/18167))
- TaskView ([#18166](https://github.com/community-scripts/ProxmoxVE/pull/18166))
- EmDash ([#18165](https://github.com/community-scripts/ProxmoxVE/pull/18165))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(mealie): install ffmpeg for video recipe import [@clartek](https://github.com/clartek) ([#18210](https://github.com/community-scripts/ProxmoxVE/pull/18210))
- Termix: step through 2.9.2 when updating from 2.8 or older [@MickLesk](https://github.com/MickLesk) ([#18158](https://github.com/community-scripts/ProxmoxVE/pull/18158))
- Element Synapse: remove the registration line by its text, not line 34 [@MickLesk](https://github.com/MickLesk) ([#18201](https://github.com/community-scripts/ProxmoxVE/pull/18201))
- Element Synapse: drop the ::1 listener so a container without IPv6 starts [@MickLesk](https://github.com/MickLesk) ([#18180](https://github.com/community-scripts/ProxmoxVE/pull/18180))
- Heimdall: generate the app key without the production prompt [@MickLesk](https://github.com/MickLesk) ([#18156](https://github.com/community-scripts/ProxmoxVE/pull/18156))
- Frigate: fetch the Intel graphics key with retries before building the media driver [@MickLesk](https://github.com/MickLesk) ([#18192](https://github.com/community-scripts/ProxmoxVE/pull/18192))
- Pterodactyl Panel: default the admin's first and last name when left empty [@MickLesk](https://github.com/MickLesk) ([#18177](https://github.com/community-scripts/ProxmoxVE/pull/18177))
- Jellyseerr: point fresh installs to Seerr instead of a missing install script [@MickLesk](https://github.com/MickLesk) ([#18183](https://github.com/community-scripts/ProxmoxVE/pull/18183))
- Pi-hole: download the installer before running it [@MickLesk](https://github.com/MickLesk) ([#18182](https://github.com/community-scripts/ProxmoxVE/pull/18182))
- OmniTools: give the build a git commit to read [@MickLesk](https://github.com/MickLesk) ([#18181](https://github.com/community-scripts/ProxmoxVE/pull/18181))
- iVentoy: stop early in an unprivileged container [@MickLesk](https://github.com/MickLesk) ([#18179](https://github.com/community-scripts/ProxmoxVE/pull/18179))
- Immich: stop early on Debian 12 instead of failing at apt [@MickLesk](https://github.com/MickLesk) ([#18178](https://github.com/community-scripts/ProxmoxVE/pull/18178))
- paperless-ngx: fix missing index folder [@Segelzwerg](https://github.com/Segelzwerg) ([#18142](https://github.com/community-scripts/ProxmoxVE/pull/18142))
- Anytype Server: keep the .env across updates and recreate a lost one [@MickLesk](https://github.com/MickLesk) ([#18155](https://github.com/community-scripts/ProxmoxVE/pull/18155))
- changedetection: build Browserless from its lockfile on update [@MickLesk](https://github.com/MickLesk) ([#18157](https://github.com/community-scripts/ProxmoxVE/pull/18157))
- Zitadel: identify the machine by hostname when it has no private IPv4 [@MickLesk](https://github.com/MickLesk) ([#18139](https://github.com/community-scripts/ProxmoxVE/pull/18139))
- fix(bar-assistant): publish starter media catalog so new bars provision [@och29](https://github.com/och29) ([#18130](https://github.com/community-scripts/ProxmoxVE/pull/18130))
- #### ✨ New Features
- Gramps-Web: run the Celery task queue with Redis [@MickLesk](https://github.com/MickLesk) ([#18141](https://github.com/community-scripts/ProxmoxVE/pull/18141))
- #### 💥 Breaking Changes
- Hermes Agent: install, update and run everything as the hermes user [@MickLesk](https://github.com/MickLesk) ([#18203](https://github.com/community-scripts/ProxmoxVE/pull/18203))
- tubearchivist: fix: prepare tubearchivist for v0.6.0 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#17867](https://github.com/community-scripts/ProxmoxVE/pull/17867))
- #### 🔧 Refactor
- TREK: build 5.0 with 4 GB RAM and without dependency install scripts [@MickLesk](https://github.com/MickLesk) ([#18194](https://github.com/community-scripts/ProxmoxVE/pull/18194))
### 💾 Core
- alpine: fix uv release url [@mfelipe](https://github.com/mfelipe) ([core#168](https://github.com/community-scripts/core/pull/168))
- Set npm's script and git policy for root too, so --prefix keeps it [@MickLesk](https://github.com/MickLesk) ([core#170](https://github.com/community-scripts/core/pull/170))
- Upgrade Node.js in place when an app package depends on it [@MickLesk](https://github.com/MickLesk) ([core#169](https://github.com/community-scripts/core/pull/169))
### 🧰 Tools
- #### ✨ New Features
- post-pmg-install: hide the subscription nag in the PMG 9 mobile quarantine [@MickLesk](https://github.com/MickLesk) ([#18140](https://github.com/community-scripts/ProxmoxVE/pull/18140))
- post-pve-install: hide the mobile subscription nag in every UI language [@MickLesk](https://github.com/MickLesk) ([#18143](https://github.com/community-scripts/ProxmoxVE/pull/18143))
## 2026-10-24
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- patchmon: avoids systemd environment secret leaks via D-Bus/subprocesses. [@rhclayto](https://github.com/rhclayto) ([#18074](https://github.com/community-scripts/ProxmoxVE/pull/18074))
- fix: Linkwarden - clear Yarn berry cache after install and update- #18115 [@bferd](https://github.com/bferd) ([#18116](https://github.com/community-scripts/ProxmoxVE/pull/18116))
- Borg-UI: recreate the borg venvs with --clear [@MickLesk](https://github.com/MickLesk) ([#18118](https://github.com/community-scripts/ProxmoxVE/pull/18118))
- SnapOtter: recreate the AI venv with --clear [@MickLesk](https://github.com/MickLesk) ([#18119](https://github.com/community-scripts/ProxmoxVE/pull/18119))
- Update authentik version to 2026.8.5 [@thieneret](https://github.com/thieneret) ([#18125](https://github.com/community-scripts/ProxmoxVE/pull/18125))
- #### ✨ New Features
- Frigate: bump to v0.18.2 [@MickLesk](https://github.com/MickLesk) ([#18120](https://github.com/community-scripts/ProxmoxVE/pull/18120))
- #### 🔧 Refactor
- install: answer every prompt from the environment, survive a closed stdin [@MickLesk](https://github.com/MickLesk) ([#18121](https://github.com/community-scripts/ProxmoxVE/pull/18121))
### 🧰 Tools
- #### 🐞 Bug Fixes
- fix(addons): use catch_errors so the update marker is cleared [@mazelado](https://github.com/mazelado) ([#18128](https://github.com/community-scripts/ProxmoxVE/pull/18128))
## 2026-10-23
### 🚀 Updated Scripts
- postgresql: default the version prompt and match Alpine's packages [@MickLesk](https://github.com/MickLesk) ([#18093](https://github.com/community-scripts/ProxmoxVE/pull/18093))
- Storyteller: build the runtime like upstream's Dockerfile [@MickLesk](https://github.com/MickLesk) ([#18083](https://github.com/community-scripts/ProxmoxVE/pull/18083))
- #### 🐞 Bug Fixes
- proxcenter: skip the type check and lower the build heap [@MickLesk](https://github.com/MickLesk) ([#18090](https://github.com/community-scripts/ProxmoxVE/pull/18090))
- hermesagent: retry the installer download, fall back to GitHub [@MickLesk](https://github.com/MickLesk) ([#18091](https://github.com/community-scripts/ProxmoxVE/pull/18091))
- zabbix: use the arm64 repository for 7.0 [@MickLesk](https://github.com/MickLesk) ([#18089](https://github.com/community-scripts/ProxmoxVE/pull/18089))
- kasm: survive a failed release lookup and answer the installer with flags [@MickLesk](https://github.com/MickLesk) ([#18092](https://github.com/community-scripts/ProxmoxVE/pull/18092))
- #### ✨ New Features
- InvoiceShelf: run the Laravel scheduler [@MickLesk](https://github.com/MickLesk) ([#18079](https://github.com/community-scripts/ProxmoxVE/pull/18079))
- #### 🔧 Refactor
- podman-homeassistant: require Debian 13 [@MickLesk](https://github.com/MickLesk) ([#18095](https://github.com/community-scripts/ProxmoxVE/pull/18095))
- apache-guacamole: require Debian 13 [@MickLesk](https://github.com/MickLesk) ([#18094](https://github.com/community-scripts/ProxmoxVE/pull/18094))
- unifi-os-server: require Debian 13 [@MickLesk](https://github.com/MickLesk) ([#18096](https://github.com/community-scripts/ProxmoxVE/pull/18096))
- openwebui: drop the Intel oneAPI Base Toolkit [@MickLesk](https://github.com/MickLesk) ([#18098](https://github.com/community-scripts/ProxmoxVE/pull/18098))
- ollama: drop the Intel oneAPI Base Toolkit [@MickLesk](https://github.com/MickLesk) ([#18097](https://github.com/community-scripts/ProxmoxVE/pull/18097))
- tdarr: do not let a failed or slow updater block the service start [@MickLesk](https://github.com/MickLesk) ([#18100](https://github.com/community-scripts/ProxmoxVE/pull/18100))
- immich: install mise from its GitHub release, retry jellyfin-ffmpeg, default the ML prompt [@MickLesk](https://github.com/MickLesk) ([#18099](https://github.com/community-scripts/ProxmoxVE/pull/18099))
- Refactor: pimox-haos-vm [@MickLesk](https://github.com/MickLesk) ([#18077](https://github.com/community-scripts/ProxmoxVE/pull/18077))
### 💾 Core
- Telemetry: explain and categorize by the command that failed [@MickLesk](https://github.com/MickLesk) ([core#166](https://github.com/community-scripts/core/pull/166))
- error-handler: report a handled silent failure only on its own line [@MickLesk](https://github.com/MickLesk) ([core#165](https://github.com/community-scripts/core/pull/165))
- VM silent: return the failure instead of exiting [@MickLesk](https://github.com/MickLesk) ([core#164](https://github.com/community-scripts/core/pull/164))
- error-handler: keep the start explanation and the final IP lookup from failing the run [@MickLesk](https://github.com/MickLesk) ([core#163](https://github.com/community-scripts/core/pull/163))
- error output: report a failure inside a subshell once [@MickLesk](https://github.com/MickLesk) ([core#162](https://github.com/community-scripts/core/pull/162))
- general: allow Composer as root in every script [@MickLesk](https://github.com/MickLesk) ([core#161](https://github.com/community-scripts/core/pull/161))
- lxc-updater: skip the resource check when the container cannot read its RAM [@MickLesk](https://github.com/MickLesk) ([core#160](https://github.com/community-scripts/core/pull/160))
- vm-core: let vm_timezone_default survive a host without /etc/timezone [@MickLesk](https://github.com/MickLesk) ([core#159](https://github.com/community-scripts/core/pull/159))
## 2026-10-22
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Zabbix: keep unattended updates on the installed series [@florianpunz](https://github.com/florianpunz) ([#18051](https://github.com/community-scripts/ProxmoxVE/pull/18051))
- #### 🔧 Refactor
- Refactor: Cryptpad [@MickLesk](https://github.com/MickLesk) ([#18065](https://github.com/community-scripts/ProxmoxVE/pull/18065))
### 💾 Core
- Incus: drive a remote Incus server from the client's default remote [@MickLesk](https://github.com/MickLesk) ([core#154](https://github.com/community-scripts/core/pull/154))
- Deploy the release again when an update is redone [@MickLesk](https://github.com/MickLesk) ([core#158](https://github.com/community-scripts/core/pull/158))
### ❔ Uncategorized
- chore(ct): sync sparkyfitness defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18061](https://github.com/community-scripts/ProxmoxVE/pull/18061))
## 2026-10-21
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: bump release version to v3.3.5 (CVE) [@CerberusStyle](https://github.com/CerberusStyle) ([#18057](https://github.com/community-scripts/ProxmoxVE/pull/18057))
- DroppedNeedle: pass release version to the app via COMMIT_TAG [@MickLesk](https://github.com/MickLesk) ([#18042](https://github.com/community-scripts/ProxmoxVE/pull/18042))
### 💾 Core
- Keep the version an update started from across a redo [@MickLesk](https://github.com/MickLesk) ([core#156](https://github.com/community-scripts/core/pull/156))
- Redo an update whose run did not finish after the deploy [@MickLesk](https://github.com/MickLesk) ([core#155](https://github.com/community-scripts/core/pull/155))
## 2026-10-20
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Plane: give Silo its own HOME [@MickLesk](https://github.com/MickLesk) ([#18028](https://github.com/community-scripts/ProxmoxVE/pull/18028))
- Immich: Pin version to 3.3.4 [@vhsdream](https://github.com/vhsdream) ([#18036](https://github.com/community-scripts/ProxmoxVE/pull/18036))
- InvenTree: run apt under systemd so postinst can finish [@MickLesk](https://github.com/MickLesk) ([#18029](https://github.com/community-scripts/ProxmoxVE/pull/18029))
- Profilarr: pass the deno pin as the version [@MickLesk](https://github.com/MickLesk) ([#18025](https://github.com/community-scripts/ProxmoxVE/pull/18025))
- Silo: keep the version file out of ~/.silo [@MickLesk](https://github.com/MickLesk) ([#18030](https://github.com/community-scripts/ProxmoxVE/pull/18030))
- InvenTree: move to go.packager.io [@MickLesk](https://github.com/MickLesk) ([#18015](https://github.com/community-scripts/ProxmoxVE/pull/18015))
- Zitadel: run init and setup in the foreground [@MickLesk](https://github.com/MickLesk) ([#18016](https://github.com/community-scripts/ProxmoxVE/pull/18016))
- #### ✨ New Features
- Zitadel: Support v1 and v2 Login [@MickLesk](https://github.com/MickLesk) ([#18031](https://github.com/community-scripts/ProxmoxVE/pull/18031))
- #### 🔧 Refactor
- Refactor: OpenWebUI (remove numba pin) [@MickLesk](https://github.com/MickLesk) ([#18026](https://github.com/community-scripts/ProxmoxVE/pull/18026))
### 💾 Core
- Only treat an NVIDIA GPU as present when its device nodes are [@MickLesk](https://github.com/MickLesk) ([core#153](https://github.com/community-scripts/core/pull/153))
## 2026-10-19
### 🆕 New Scripts
- Calagopus ([#18012](https://github.com/community-scripts/ProxmoxVE/pull/18012))
- Netronome ([#18011](https://github.com/community-scripts/ProxmoxVE/pull/18011))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- immich: record library revisions when the line is missing [@mazelado](https://github.com/mazelado) ([#17990](https://github.com/community-scripts/ProxmoxVE/pull/17990))
- Update authentik 2026.8.4 [@thieneret](https://github.com/thieneret) ([#18004](https://github.com/community-scripts/ProxmoxVE/pull/18004))
- #### ✨ New Features
- feat(mealie): add reverse proxy auth settings to mealie.env [@bferd](https://github.com/bferd) ([#18006](https://github.com/community-scripts/ProxmoxVE/pull/18006))
## 2026-10-18
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Donetick: create selfhosted.yaml from the example [@MickLesk](https://github.com/MickLesk) ([#17992](https://github.com/community-scripts/ProxmoxVE/pull/17992))
- Wiki.js: only back up db.sqlite on SQLite installs [@MickLesk](https://github.com/MickLesk) ([#17993](https://github.com/community-scripts/ProxmoxVE/pull/17993))
- FlareSolverr: pass Chrome an en-US Accept-Language [@MickLesk](https://github.com/MickLesk) ([#17995](https://github.com/community-scripts/ProxmoxVE/pull/17995))
- 2FAuth: install the locked dependencies [@MickLesk](https://github.com/MickLesk) ([#17996](https://github.com/community-scripts/ProxmoxVE/pull/17996))
- Tracearr: drop the pnpm store after the update build [@gioxx](https://github.com/gioxx) ([#17869](https://github.com/community-scripts/ProxmoxVE/pull/17869))
- vaultwarden: build before stopping the service [@mazelado](https://github.com/mazelado) ([#17983](https://github.com/community-scripts/ProxmoxVE/pull/17983))
- #### 💥 Breaking Changes
- fix: geopulse 2.0.0 upgrade support (nginx .mjs, pg18, jwt cleanup) [@CrazyWolf13](https://github.com/CrazyWolf13) ([#17978](https://github.com/community-scripts/ProxmoxVE/pull/17978))
### 💾 Core
- Let get_current_ip reach its IPv6 fallback [@MickLesk](https://github.com/MickLesk) ([core#151](https://github.com/community-scripts/core/pull/151))
- Drop apt autoclean before apt clean [@MickLesk](https://github.com/MickLesk) ([core#152](https://github.com/community-scripts/core/pull/152))
- Silence apt's script warning in $STD [@MickLesk](https://github.com/MickLesk) ([core#150](https://github.com/community-scripts/core/pull/150))
## 2026-10-17
### 🆕 New Scripts
- ProxCenter ([#17961](https://github.com/community-scripts/ProxmoxVE/pull/17961))
- Listenarr ([#17963](https://github.com/community-scripts/ProxmoxVE/pull/17963))
- Youtarr ([#17962](https://github.com/community-scripts/ProxmoxVE/pull/17962))
- Norish ([#17964](https://github.com/community-scripts/ProxmoxVE/pull/17964))
### 🚀 Updated Scripts
- #### 🔧 Refactor
- Sonarr: stay on the newest v4 (pin) [@MickLesk](https://github.com/MickLesk) ([#17958](https://github.com/community-scripts/ProxmoxVE/pull/17958))
## 2026-10-16
### 🆕 New Scripts
- Downtify ([#17959](https://github.com/community-scripts/ProxmoxVE/pull/17959))
- openGym ([#17960](https://github.com/community-scripts/ProxmoxVE/pull/17960))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Pelican Panel: pass the URL to p:environment:setup [@MickLesk](https://github.com/MickLesk) ([#17948](https://github.com/community-scripts/ProxmoxVE/pull/17948))
- OneDev: use the onedev.server unit of 16.9 [@MickLesk](https://github.com/MickLesk) ([#17949](https://github.com/community-scripts/ProxmoxVE/pull/17949))
- ReadMeABook: start the scheduler with the service [@MickLesk](https://github.com/MickLesk) ([#17950](https://github.com/community-scripts/ProxmoxVE/pull/17950))
### 💾 Core
- Clean up after installs and updates again [@MickLesk](https://github.com/MickLesk) ([core#147](https://github.com/community-scripts/core/pull/147))
- Fix apt_conffile_report exiting 1 when uutils comm sees locale-sorted input [@BadFlo](https://github.com/BadFlo) ([core#146](https://github.com/community-scripts/core/pull/146))
## 2026-10-15
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(apprise-api): copy static assets into the served directory [@brokentokin](https://github.com/brokentokin) ([#17940](https://github.com/community-scripts/ProxmoxVE/pull/17940))
- Bytestash: modify backup process | Update Resources [@MickLesk](https://github.com/MickLesk) ([#17935](https://github.com/community-scripts/ProxmoxVE/pull/17935))
- Zabbix: Add '-y' flag to apt install commands [@MickLesk](https://github.com/MickLesk) ([#17933](https://github.com/community-scripts/ProxmoxVE/pull/17933))
- karakeep: bump Node.js from 22 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17087](https://github.com/community-scripts/ProxmoxVE/pull/17087))
### 💾 Core
- Keep VM release lookups working without system.func [@MickLesk](https://github.com/MickLesk) ([core#144](https://github.com/community-scripts/core/pull/144))
- Stop calling exit 1 "Operation not permitted" at several steps [@MickLesk](https://github.com/MickLesk) ([core#140](https://github.com/community-scripts/core/pull/140))
## 2026-10-14
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Paperclip: fall back to a non-frozen install when the release lockfile is stale [@MickLesk](https://github.com/MickLesk) ([#17922](https://github.com/community-scripts/ProxmoxVE/pull/17922))
- #### 🔧 Refactor
- Immich: rebuild the image libraries when the revision record is missing [@MickLesk](https://github.com/MickLesk) ([#17919](https://github.com/community-scripts/ProxmoxVE/pull/17919))
### 💾 Core
- Show what the failing command printed, and keep verbose output in the log [@MickLesk](https://github.com/MickLesk) ([core#139](https://github.com/community-scripts/core/pull/139))
- Install jq before the release lookup and show what a non-JSON answer was [@MickLesk](https://github.com/MickLesk) ([core#138](https://github.com/community-scripts/core/pull/138))
- Refresh the NodeSource key before a same-major Node.js update [@MickLesk](https://github.com/MickLesk) ([core#137](https://github.com/community-scripts/core/pull/137))
- github: say why a forge could not be reached instead of 'connection failed' [@MickLesk](https://github.com/MickLesk) ([core#136](https://github.com/community-scripts/core/pull/136))
## 2026-10-13
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Fix URL for downloading OneDev latest release [@ryansully](https://github.com/ryansully) ([#17915](https://github.com/community-scripts/ProxmoxVE/pull/17915))
- LocalAI: pin the CPU backends when the container has no GPU runtime [@MickLesk](https://github.com/MickLesk) ([#17909](https://github.com/community-scripts/ProxmoxVE/pull/17909))
- [Gluetun] Update GitHub repository source install and update. [@CerberusStyle](https://github.com/CerberusStyle) ([#17899](https://github.com/community-scripts/ProxmoxVE/pull/17899))
- Immich: Bump to 3.3.1 and restore (failed) geodata/countryInfo.txt on update [@MickLesk](https://github.com/MickLesk) ([#17894](https://github.com/community-scripts/ProxmoxVE/pull/17894))
- WikiJS: use v2 Repo until v3 is stable [@MickLesk](https://github.com/MickLesk) ([#17895](https://github.com/community-scripts/ProxmoxVE/pull/17895))
- #### 💥 Breaking Changes
- zammad: Debian 13, new apt repo, Elasticsearch 8 [@durzo](https://github.com/durzo) ([#17836](https://github.com/community-scripts/ProxmoxVE/pull/17836))
### 💾 Core
- Keep the version key parseable by jq 1.6 [@MickLesk](https://github.com/MickLesk) ([core#134](https://github.com/community-scripts/core/pull/134))
## 2026-10-12
### 🆕 New Scripts
- WatchState ([#17881](https://github.com/community-scripts/ProxmoxVE/pull/17881))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- chore(ct): sync wazuh defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17889](https://github.com/community-scripts/ProxmoxVE/pull/17889))
- Wanderer: bring back the wanderer-pb wrapper [@MickLesk](https://github.com/MickLesk) ([#17874](https://github.com/community-scripts/ProxmoxVE/pull/17874))
- Tracearr: install only the server's production dependencies [@MickLesk](https://github.com/MickLesk) ([#17873](https://github.com/community-scripts/ProxmoxVE/pull/17873))
- Paperless-ngx: QoL restart crashed services | patch regex [@MickLesk](https://github.com/MickLesk) ([#17812](https://github.com/community-scripts/ProxmoxVE/pull/17812))
- Splunk Enterprise: start the service as the splunk user [@rcastley](https://github.com/rcastley) ([#17816](https://github.com/community-scripts/ProxmoxVE/pull/17816))
### 💾 Core
- Draw a Community Scripts banner while a script has no header of its own [@MickLesk](https://github.com/MickLesk) ([core#133](https://github.com/community-scripts/core/pull/133))
- Order pre-release labels when comparing versions [@MickLesk](https://github.com/MickLesk) ([core#132](https://github.com/community-scripts/core/pull/132))
- gh: curl_download: wait between attempts [@MickLesk](https://github.com/MickLesk) ([core#131](https://github.com/community-scripts/core/pull/131))
### 🧰 Tools
- #### ✨ New Features
- Immich Public Proxy: optional visitor upload service (v4 Release) [@MickLesk](https://github.com/MickLesk) ([#17875](https://github.com/community-scripts/ProxmoxVE/pull/17875))
## 2026-10-11
### 🆕 New Scripts
- Cinephage ([#17852](https://github.com/community-scripts/ProxmoxVE/pull/17852))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: build libvips with JPEG 2000 support [@MickLesk](https://github.com/MickLesk) ([#17864](https://github.com/community-scripts/ProxmoxVE/pull/17864))
- hermesagent: match the relaunched root gateway regardless of interpreter [@MickLesk](https://github.com/MickLesk) ([#17847](https://github.com/community-scripts/ProxmoxVE/pull/17847))
- Radicale: install the bcrypt and argon2 extras [@MickLesk](https://github.com/MickLesk) ([#17813](https://github.com/community-scripts/ProxmoxVE/pull/17813))
- fix(zigbee2mqtt): preserve backup stream and prevent filename collisions [@petermnt](https://github.com/petermnt) ([#17751](https://github.com/community-scripts/ProxmoxVE/pull/17751))
- #### ✨ New Features
- QoL: let var_* answer the optional add-on prompts [@MickLesk](https://github.com/MickLesk) ([#17858](https://github.com/community-scripts/ProxmoxVE/pull/17858))
- QoL: Confirm third-party installers through confirm_external_installer [@MickLesk](https://github.com/MickLesk) ([#17857](https://github.com/community-scripts/ProxmoxVE/pull/17857))
- docker: take the lxcfs toggle, TCP socket and Compose choice from app variables [@MickLesk](https://github.com/MickLesk) ([#17850](https://github.com/community-scripts/ProxmoxVE/pull/17850))
- #### 🔧 Refactor
- RustDesk Server: run the official server, make the web UI optional [@MickLesk](https://github.com/MickLesk) ([#17854](https://github.com/community-scripts/ProxmoxVE/pull/17854))
- Caddy: install the .deb from GitHub instead of the Cloudsmith repo [@MickLesk](https://github.com/MickLesk) ([#17862](https://github.com/community-scripts/ProxmoxVE/pull/17862))
### 💾 Core
- Add confirm_external_installer [@MickLesk](https://github.com/MickLesk) ([core#130](https://github.com/community-scripts/core/pull/130))
- setup_docker: make LXC resource limits visible to nested containers [@andrebrait](https://github.com/andrebrait) ([core#9](https://github.com/community-scripts/core/pull/9))
- Incus: fix ENABLE_FUSE / TUN vars [@MickLesk](https://github.com/MickLesk) ([core#127](https://github.com/community-scripts/core/pull/127))
- Resolve a uv required-version range to the newest release inside it [@MickLesk](https://github.com/MickLesk) ([core#126](https://github.com/community-scripts/core/pull/126))
- Upgrade MongoDB in place when an app package depends on it [@MickLesk](https://github.com/MickLesk) ([core#128](https://github.com/community-scripts/core/pull/128))
## 2026-10-10
### 🆕 New Scripts
- Weblate ([#17837](https://github.com/community-scripts/ProxmoxVE/pull/17837))
- CertMate ([#17803](https://github.com/community-scripts/ProxmoxVE/pull/17803))
- AnythingLLM ([#17802](https://github.com/community-scripts/ProxmoxVE/pull/17802))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- update endurain repo from codeberg to gh [@johanngrobe](https://github.com/johanngrobe) ([#17831](https://github.com/community-scripts/ProxmoxVE/pull/17831))
- Immich: download countryInfo.txt into the geodata directory on update [@claytonfaria](https://github.com/claytonfaria) ([#17823](https://github.com/community-scripts/ProxmoxVE/pull/17823))
- paperclip: keep root and skip /opt/paperclip-data chown when PAPERCLIP_HOME is custom [@austinpilz](https://github.com/austinpilz) ([#17825](https://github.com/community-scripts/ProxmoxVE/pull/17825))
## 2026-10-09
### 🆕 New Scripts
- FoldingAtHome ([#17799](https://github.com/community-scripts/ProxmoxVE/pull/17799))
- LocalAI ([#17801](https://github.com/community-scripts/ProxmoxVE/pull/17801))
- Tvheadend ([#17800](https://github.com/community-scripts/ProxmoxVE/pull/17800))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- OpenCloud: fix Collabora embedding with Collabora 26.04.4 [@SimKaiLong](https://github.com/SimKaiLong) ([#17810](https://github.com/community-scripts/ProxmoxVE/pull/17810))
- #### 🔧 Refactor
- Immich: survive an interrupted update and a failing ML build [@MickLesk](https://github.com/MickLesk) ([#17798](https://github.com/community-scripts/ProxmoxVE/pull/17798))
### 💾 Core
- check_for_release: compare against the release marked as latest [@MickLesk](https://github.com/MickLesk) ([core#125](https://github.com/community-scripts/core/pull/125))
### 🧰 Tools
- #### ✨ New Features
- FileBrowser Quantum: migrate the config and database to v2 [@MickLesk](https://github.com/MickLesk) ([#17815](https://github.com/community-scripts/ProxmoxVE/pull/17815))
## 2026-10-08
### 🆕 New Scripts
- Unifi-OS-Server VM ([#17752](https://github.com/community-scripts/ProxmoxVE/pull/17752))
- ZimaOS VM ([#17778](https://github.com/community-scripts/ProxmoxVE/pull/17778))
### 🚀 Updated Scripts
- Revert "Immich: Pin to v3.3.0" [@MickLesk](https://github.com/MickLesk) ([#17767](https://github.com/community-scripts/ProxmoxVE/pull/17767))
- #### 🐞 Bug Fixes
- Homarr: replace the musl better-sqlite3 that v2.3.0 ships for Debian [@MickLesk](https://github.com/MickLesk) ([#17789](https://github.com/community-scripts/ProxmoxVE/pull/17789))
- CLIProxyAPI: keep plugins and data across updates [@MickLesk](https://github.com/MickLesk) ([#17790](https://github.com/community-scripts/ProxmoxVE/pull/17790))
- Kima-Hub: install the Python services into a uv venv [@MickLesk](https://github.com/MickLesk) ([#17791](https://github.com/community-scripts/ProxmoxVE/pull/17791))
- #### ✨ New Features
- Immich: Pin to 3.3.0 / Update ML Python to 3.13 [@MickLesk](https://github.com/MickLesk) ([#17770](https://github.com/community-scripts/ProxmoxVE/pull/17770))
- #### 🔧 Refactor
- Refactor: OPNsense VM [@MickLesk](https://github.com/MickLesk) ([#17785](https://github.com/community-scripts/ProxmoxVE/pull/17785))
- Refactor: Nextcloud VM (Turnkey) [@MickLesk](https://github.com/MickLesk) ([#17787](https://github.com/community-scripts/ProxmoxVE/pull/17787))
- Refactor: OpenWrt VM [@MickLesk](https://github.com/MickLesk) ([#17774](https://github.com/community-scripts/ProxmoxVE/pull/17774))
- Refactor: Ubuntu VM [@MickLesk](https://github.com/MickLesk) ([#17776](https://github.com/community-scripts/ProxmoxVE/pull/17776))
### 💾 Core
- Tolerate a missing datacenter.cfg in vm_keyboard_default [@MickLesk](https://github.com/MickLesk) ([core#124](https://github.com/community-scripts/core/pull/124))
- Take the ISO storage from the disk pool when it can hold ISOs [@MickLesk](https://github.com/MickLesk) ([core#122](https://github.com/community-scripts/core/pull/122))
- Shared VM helpers: disk import, releases, checksums, first boot, IP by MAC [@MickLesk](https://github.com/MickLesk) ([core#117](https://github.com/community-scripts/core/pull/117))
- tools: forge truncated release list [@MickLesk](https://github.com/MickLesk) ([core#118](https://github.com/community-scripts/core/pull/118))
- Remember a kept cached image until the upstream changes [@MickLesk](https://github.com/MickLesk) ([core#119](https://github.com/community-scripts/core/pull/119))
- Choose the VM keyboard layout and carry the host's timezone into prepared images [@MickLesk](https://github.com/MickLesk) ([core#120](https://github.com/community-scripts/core/pull/120))
- VM's: run first-boot units without debconf dialogs [@MickLesk](https://github.com/MickLesk) ([core#121](https://github.com/community-scripts/core/pull/121))
## 2026-10-07
### 🆕 New Scripts
- Fedora VM ([#17747](https://github.com/community-scripts/ProxmoxVE/pull/17747))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- AudioMuse-AI: replace the venv on update without asking [@MickLesk](https://github.com/MickLesk) ([#17738](https://github.com/community-scripts/ProxmoxVE/pull/17738))
- paperclip: run as a dedicated non-root user so Claude Code can skip permissions [@samvandenbossche](https://github.com/samvandenbossche) ([#17707](https://github.com/community-scripts/ProxmoxVE/pull/17707))
- Thingsboard: update Java version from 17 to 25 [@CerberusStyle](https://github.com/CerberusStyle) ([#17733](https://github.com/community-scripts/ProxmoxVE/pull/17733))
- #### ✨ New Features
- Immich: Pin to v3.3.0 [@vhsdream](https://github.com/vhsdream) ([#17759](https://github.com/community-scripts/ProxmoxVE/pull/17759))
- #### 💥 Breaking Changes
- OpenCloud: bump to v8.1.0, rebuild search index on upgrade [@SimKaiLong](https://github.com/SimKaiLong) ([#17710](https://github.com/community-scripts/ProxmoxVE/pull/17710))
- #### 🔧 Refactor
- Pangolin: Unpin Release, stable enough, Bump to latest [@MickLesk](https://github.com/MickLesk) ([#17740](https://github.com/community-scripts/ProxmoxVE/pull/17740))
- Refactor: Archlinux-VM [@MickLesk](https://github.com/MickLesk) ([#17741](https://github.com/community-scripts/ProxmoxVE/pull/17741))
- Refactor: MikroTik RouterOS [@MickLesk](https://github.com/MickLesk) ([#17748](https://github.com/community-scripts/ProxmoxVE/pull/17748))
### 💾 Core
- Fix Fedora and BLS cloud image console setup [@MickLesk](https://github.com/MickLesk) ([core#116](https://github.com/community-scripts/core/pull/116))
- Keep setup_nodejs alive when the caller's directory is gone [@MickLesk](https://github.com/MickLesk) ([core#114](https://github.com/community-scripts/core/pull/114))
## 2026-10-06
### 🆕 New Scripts
- Pricebuddy ([#17708](https://github.com/community-scripts/ProxmoxVE/pull/17708))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Webtrees: stop serving data/ and the source folders directly [@MickLesk](https://github.com/MickLesk) ([#17724](https://github.com/community-scripts/ProxmoxVE/pull/17724))
### 💾 Core
- Incus: check the architecture on Incus hosts too [@MickLesk](https://github.com/MickLesk) ([core#113](https://github.com/community-scripts/core/pull/113))
- Incus: Pass the app's var_* settings into Incus containers [@MickLesk](https://github.com/MickLesk) ([core#112](https://github.com/community-scripts/core/pull/112))
- Incus: stub storage_content_check on Incus [@MickLesk](https://github.com/MickLesk) ([core#104](https://github.com/community-scripts/core/pull/104))
- Incus: map Proxmox template versions to Incus image names [@MickLesk](https://github.com/MickLesk) ([core#111](https://github.com/community-scripts/core/pull/111))
- Incus: read storage names from .vars files [@MickLesk](https://github.com/MickLesk) ([core#110](https://github.com/community-scripts/core/pull/110))
- Incus: show the spinner inside containers again [@MickLesk](https://github.com/MickLesk) ([core#109](https://github.com/community-scripts/core/pull/109))
- Incus: set the hostname with sh, so it works before bash is installed [@MickLesk](https://github.com/MickLesk) ([core#102](https://github.com/community-scripts/core/pull/102))
- Incus: reserve a plain address on Incus, leave the gateway to the network [@MickLesk](https://github.com/MickLesk) ([core#101](https://github.com/community-scripts/core/pull/101))
- Incus: Wait for the network on Alpine OS too [@MickLesk](https://github.com/MickLesk) ([core#105](https://github.com/community-scripts/core/pull/105))
- Incus: do not fail the build when hostname -I is missing [@MickLesk](https://github.com/MickLesk) ([core#103](https://github.com/community-scripts/core/pull/103))
- Incus: Fuse always in unprivileged Containers, attach TUN only when container lacks it [@MickLesk](https://github.com/MickLesk) ([core#108](https://github.com/community-scripts/core/pull/108))
- Incus: accept mode=generated [@MickLesk](https://github.com/MickLesk) ([core#106](https://github.com/community-scripts/core/pull/106))
- Incus: pass the full install environment into Incus containers (quoted) [@MickLesk](https://github.com/MickLesk) ([core#107](https://github.com/community-scripts/core/pull/107))
## 2026-10-05
### 🚀 Updated Scripts
- Point to DevScripts, the renamed ProxmoxVED [@MickLesk](https://github.com/MickLesk) ([#17694](https://github.com/community-scripts/ProxmoxVE/pull/17694))
- #### 🐞 Bug Fixes
- wanderer: set the proxy secret and install plugins in their own directories [@MickLesk](https://github.com/MickLesk) ([#17698](https://github.com/community-scripts/ProxmoxVE/pull/17698))
- discourse: serve stylesheets and repair the update [@MickLesk](https://github.com/MickLesk) ([#17697](https://github.com/community-scripts/ProxmoxVE/pull/17697))
- wikijs: bump Node.js from 24 to 26 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17689](https://github.com/community-scripts/ProxmoxVE/pull/17689))
- jotty: bump Node.js from 22 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#17688](https://github.com/community-scripts/ProxmoxVE/pull/17688))
### 💾 Core
- Read releases from github.com when the API is rate limited, resume stalled downloads [@MickLesk](https://github.com/MickLesk) ([core#99](https://github.com/community-scripts/core/pull/99))
- Check for template and container storage before the settings menu [@MickLesk](https://github.com/MickLesk) ([core#98](https://github.com/community-scripts/core/pull/98))
- Check /etc/pve before the settings menu [@MickLesk](https://github.com/MickLesk) ([core#97](https://github.com/community-scripts/core/pull/97))
- Check container settings before pct create rejects them [@MickLesk](https://github.com/MickLesk) ([core#96](https://github.com/community-scripts/core/pull/96))
- PVE: Smart Diagnosis - say why a container would not start [@MickLesk](https://github.com/MickLesk) ([core#95](https://github.com/community-scripts/core/pull/95))
- Retry template downloads that pveam reports as done but are not [@MickLesk](https://github.com/MickLesk) ([core#94](https://github.com/community-scripts/core/pull/94))
- Check the OS release against pve-container before creating anything [@MickLesk](https://github.com/MickLesk) ([core#93](https://github.com/community-scripts/core/pull/93))
- Rename "ProxmoxVED"-Links to "DevScripts" [@MickLesk](https://github.com/MickLesk) ([core#91](https://github.com/community-scripts/core/pull/91))
### 🧰 Tools
- #### 🐞 Bug Fixes
- monitor-all: read container IPs from the host side [@jasonobrien](https://github.com/jasonobrien) ([#17686](https://github.com/community-scripts/ProxmoxVE/pull/17686))
## 2026-10-04
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- immich: read the Intel runtime from the pinned release [@MickLesk](https://github.com/MickLesk) ([#17677](https://github.com/community-scripts/ProxmoxVE/pull/17677))
### 💾 Core
- better explain unsupported version & upgrade path for pve [@MickLesk](https://github.com/MickLesk) ([core#86](https://github.com/community-scripts/core/pull/86))
- Quote PostgreSQL identifiers and drop spaces from the creds file name [@MickLesk](https://github.com/MickLesk) ([core#89](https://github.com/community-scripts/core/pull/89))
- Find the default Rust toolchain in current rustup output [@MickLesk](https://github.com/MickLesk) ([core#88](https://github.com/community-scripts/core/pull/88))
## 2026-10-03
### 🆕 New Scripts
- Keeper.sh ([#17659](https://github.com/community-scripts/ProxmoxVE/pull/17659))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- hermesagent: download the installer before running it [@MickLesk](https://github.com/MickLesk) ([#17666](https://github.com/community-scripts/ProxmoxVE/pull/17666))
- frigate: raise the Node heap for the web build [@MickLesk](https://github.com/MickLesk) ([#17667](https://github.com/community-scripts/ProxmoxVE/pull/17667))
- Tracearr: workaround for failing map tiles download and data preservation [@durzo](https://github.com/durzo) ([#17661](https://github.com/community-scripts/ProxmoxVE/pull/17661))
- fix(nginxproxymanager): clean yarn cache after builds to prevent disk growth [@PsycoStea](https://github.com/PsycoStea) ([#17653](https://github.com/community-scripts/ProxmoxVE/pull/17653))
- #### 🔧 Refactor
- Several Scripts: refactor msg_ blocks, generate passwords with helper, add CLEAN_INSTALL_KEEP [@MickLesk](https://github.com/MickLesk) ([#17655](https://github.com/community-scripts/ProxmoxVE/pull/17655))
### 💾 Core
- detect_repo_source: read the license header and fall back to ProxmoxVE [@MickLesk](https://github.com/MickLesk) ([core#85](https://github.com/community-scripts/core/pull/85))
## 2026-10-02
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- zerobyte: upgrade Bun on update [@MickLesk](https://github.com/MickLesk) ([#17646](https://github.com/community-scripts/ProxmoxVE/pull/17646))
- sure: keep Active Storage uploads across updates [@MickLesk](https://github.com/MickLesk) ([#17648](https://github.com/community-scripts/ProxmoxVE/pull/17648))
- twenty: run upstreams upgrade command instead of the removed setup-db.ts [@MickLesk](https://github.com/MickLesk) ([#17647](https://github.com/community-scripts/ProxmoxVE/pull/17647))
- #### 🔧 Refactor
- NPM: Refactor / Fix npm openresty user and certbot version [@MickLesk](https://github.com/MickLesk) ([#17649](https://github.com/community-scripts/ProxmoxVE/pull/17649))
- several scripts: use the release helpers instead of hand-rolled version checks and downloads [@MickLesk](https://github.com/MickLesk) ([#17625](https://github.com/community-scripts/ProxmoxVE/pull/17625))
### 💾 Core
- QoL: message blocks, clean-install keep list, password helper, network limits [@MickLesk](https://github.com/MickLesk) ([core#61](https://github.com/community-scripts/core/pull/61))
## 2026-10-01
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- romm: keep the asset links in sync with ROMM_BASE_PATH [@MickLesk](https://github.com/MickLesk) ([#17610](https://github.com/community-scripts/ProxmoxVE/pull/17610))
- teslamate: build from the elixir directory since 4.3.0 [@MickLesk](https://github.com/MickLesk) ([#17612](https://github.com/community-scripts/ProxmoxVE/pull/17612))
- #### ✨ New Features
- feat(glance): add alpine os [@skilletfun](https://github.com/skilletfun) ([#17605](https://github.com/community-scripts/ProxmoxVE/pull/17605))
- feat(blocky): add alpine os [@skilletfun](https://github.com/skilletfun) ([#17637](https://github.com/community-scripts/ProxmoxVE/pull/17637))
- #### 🔧 Refactor
- several scripts: apt instead of apt-get, cleanup_lxc, nginx_enable_site, drop needless daemon-reloads [@MickLesk](https://github.com/MickLesk) ([#17624](https://github.com/community-scripts/ProxmoxVE/pull/17624))
- several scripts: use setup_postgresql_db / setup_mariadb_db instead of hand-written SQL [@MickLesk](https://github.com/MickLesk) ([#17622](https://github.com/community-scripts/ProxmoxVE/pull/17622))
### 💾 Core
- Offer to raise the kernel key limits instead of only naming them [@MickLesk](https://github.com/MickLesk) ([core#81](https://github.com/community-scripts/core/pull/81))
### 🧰 Tools
- #### 🔧 Refactor
- tools, turnkey: load core instead of the legacy misc functions [@MickLesk](https://github.com/MickLesk) ([#17621](https://github.com/community-scripts/ProxmoxVE/pull/17621))
### 📚 Documentation
- chore: drop the local header generation; headers come from core [@MickLesk](https://github.com/MickLesk) ([#17623](https://github.com/community-scripts/ProxmoxVE/pull/17623))
-187
View File
@@ -1,187 +0,0 @@
## 2026-11-07
### 🆕 New Scripts
- EspoCRM ([#18519](https://github.com/community-scripts/ProxmoxVE/pull/18519))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- RomM: emit player isolation headers from the serving location [@adotagarwal](https://github.com/adotagarwal) ([#18511](https://github.com/community-scripts/ProxmoxVE/pull/18511))
- #### 💥 Breaking Changes
- fix: gotenberg: update to v9 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#18491](https://github.com/community-scripts/ProxmoxVE/pull/18491))
### 💾 Core
- nvidia: close the hardware acceleration step when no GPU is usable | pin the whole NVIDIA userspace to the host's driver version [@MickLesk](https://github.com/MickLesk) ([core#215](https://github.com/community-scripts/core/pull/215))
- fix: stage release downloads on disk, not in /tmp [@rcourtman](https://github.com/rcourtman) ([core#218](https://github.com/community-scripts/core/pull/218))
- Minor fixes: restart php-fpm during sigpipe, Install jq for forge commands [@MickLesk](https://github.com/MickLesk) ([core#213](https://github.com/community-scripts/core/pull/213))
- incus: name the project restriction that refuses an Incus launch [@MickLesk](https://github.com/MickLesk) ([core#214](https://github.com/community-scripts/core/pull/214))
## 2026-11-06
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- UmbrelOS-VM: report runs as ProxmoxVE, not DevScripts [@MickLesk](https://github.com/MickLesk) ([#18497](https://github.com/community-scripts/ProxmoxVE/pull/18497))
- Caddy: skip the xCaddy test build below 2 GB RAM [@MickLesk](https://github.com/MickLesk) ([#18472](https://github.com/community-scripts/ProxmoxVE/pull/18472))
- Thingsboard: keep the package defaults in thingsboard.conf [@MickLesk](https://github.com/MickLesk) ([#18499](https://github.com/community-scripts/ProxmoxVE/pull/18499))
- Nexterm: keep the extracted app out of the /tmp cleanup [@MickLesk](https://github.com/MickLesk) ([#18501](https://github.com/community-scripts/ProxmoxVE/pull/18501))
- PLANKA: run the service with node instead of nodemon [@MickLesk](https://github.com/MickLesk) ([#18503](https://github.com/community-scripts/ProxmoxVE/pull/18503))
- MeTube/Yubal: install Deno without its shell setup [@MickLesk](https://github.com/MickLesk) ([#18406](https://github.com/community-scripts/ProxmoxVE/pull/18406))
- OPNsense VM: size the temp space check to the unpacked image | say why the EFI disk could not be allocated [@MickLesk](https://github.com/MickLesk) ([#18404](https://github.com/community-scripts/ProxmoxVE/pull/18404))
- #### 🔧 Refactor
- Technitium DNS: read the version from its GitHub releases [@MickLesk](https://github.com/MickLesk) ([#18425](https://github.com/community-scripts/ProxmoxVE/pull/18425))
### 💾 Core
- fix: spot same-filesystem bind mounts on Alpine [@rcourtman](https://github.com/rcourtman) ([core#212](https://github.com/community-scripts/core/pull/212))
- fix: report a container that went down during setup, keep finished installs [@MickLesk](https://github.com/MickLesk) ([core#211](https://github.com/community-scripts/core/pull/211))
### 📚 Documentation
- ci: reject DevScripts URLs in scripts [@MickLesk](https://github.com/MickLesk) ([#18498](https://github.com/community-scripts/ProxmoxVE/pull/18498))
## 2026-11-05
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- OpenWRT-VM: leave the NIC untagged on a bridge without a port [@MickLesk](https://github.com/MickLesk) ([#18473](https://github.com/community-scripts/ProxmoxVE/pull/18473))
- Podman: skip Portainer where Podman has no Quadlet [@MickLesk](https://github.com/MickLesk) ([#18469](https://github.com/community-scripts/ProxmoxVE/pull/18469))
- Planka: change npm start argument to --production [@CerberusStyle](https://github.com/CerberusStyle) ([#18478](https://github.com/community-scripts/ProxmoxVE/pull/18478))
- OPNsense VM: wait for the FreeBSD login prompt instead of 180 seconds [@MickLesk](https://github.com/MickLesk) ([#18467](https://github.com/community-scripts/ProxmoxVE/pull/18467))
- #### 🔧 Refactor
- truenas-VM: let a failed ISO lookup reach its own error [@MickLesk](https://github.com/MickLesk) ([#18476](https://github.com/community-scripts/ProxmoxVE/pull/18476))
- proxmox-mail-gateway: take the repo suite from the container [@MickLesk](https://github.com/MickLesk) ([#18475](https://github.com/community-scripts/ProxmoxVE/pull/18475))
- Immich: Bump to 3.3.6 and minor improvement [@MickLesk](https://github.com/MickLesk) ([#18474](https://github.com/community-scripts/ProxmoxVE/pull/18474))
- Tududi: run 2.0 on PostgreSQL, keep SQLite installs on 1.15.0 [@MickLesk](https://github.com/MickLesk) ([#18465](https://github.com/community-scripts/ProxmoxVE/pull/18465))
### 💾 Core
- fix: keep the existing install until the new archive has extracted [@rcourtman](https://github.com/rcourtman) ([core#201](https://github.com/community-scripts/core/pull/201))
- fix: keep the install when a clone or an Alpine extract fails [@rcourtman](https://github.com/rcourtman) ([core#210](https://github.com/community-scripts/core/pull/210))
### 🧰 Tools
- #### 🐞 Bug Fixes
- Arcane: write both env names, stop shipping the unused .env [@MickLesk](https://github.com/MickLesk) ([#18466](https://github.com/community-scripts/ProxmoxVE/pull/18466))
## 2026-11-04
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Teleport: Poll readyz instead of fixed 10 second sleep. [@cthach](https://github.com/cthach) ([#18461](https://github.com/community-scripts/ProxmoxVE/pull/18461))
- n8n: install past its unsatisfiable canvas dependencies [@MickLesk](https://github.com/MickLesk) ([#18439](https://github.com/community-scripts/ProxmoxVE/pull/18439))
- Teable: add Redis cache [@MickLesk](https://github.com/MickLesk) ([#18450](https://github.com/community-scripts/ProxmoxVE/pull/18450))
### 💾 Core
- feat: say why download_gpg_key failed [@MickLesk](https://github.com/MickLesk) ([core#208](https://github.com/community-scripts/core/pull/208))
- feat: read the MOTD's OS version at login [@MickLesk](https://github.com/MickLesk) ([core#207](https://github.com/community-scripts/core/pull/207))
- feat: skip screen clear when there is no terminal | pin only the scripts own app with var_appversion [@MickLesk](https://github.com/MickLesk) ([core#203](https://github.com/community-scripts/core/pull/203))
## 2026-11-03
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Fedora VM: let the engine install libguestfs-tools [@MickLesk](https://github.com/MickLesk) ([#18422](https://github.com/community-scripts/ProxmoxVE/pull/18422))
- Cinephage: run the camoufox binary the package now ships [@MickLesk](https://github.com/MickLesk) ([#18426](https://github.com/community-scripts/ProxmoxVE/pull/18426))
- AlmaLinux VM: report failures through the engine's error handler [@MickLesk](https://github.com/MickLesk) ([#18418](https://github.com/community-scripts/ProxmoxVE/pull/18418))
- WireGuard: give the Debian container 1 GB for WGDashboard [@MickLesk](https://github.com/MickLesk) ([#18419](https://github.com/community-scripts/ProxmoxVE/pull/18419))
- zabbix: use os specific apt packages [@MickLesk](https://github.com/MickLesk) ([#18417](https://github.com/community-scripts/ProxmoxVE/pull/18417))
- Discourse: build libvips 8.18 for its image worker [@MickLesk](https://github.com/MickLesk) ([#18430](https://github.com/community-scripts/ProxmoxVE/pull/18430))
- Wazuh: show the installer's own error when it fails [@MickLesk](https://github.com/MickLesk) ([#18428](https://github.com/community-scripts/ProxmoxVE/pull/18428))
- Tracearr: move to Node.js 26 [@connorgallopo](https://github.com/connorgallopo) ([#18393](https://github.com/community-scripts/ProxmoxVE/pull/18393))
- #### ✨ New Features
- Wiki.js: install 3.x [@MickLesk](https://github.com/MickLesk) ([#18382](https://github.com/community-scripts/ProxmoxVE/pull/18382))
- #### 🔧 Refactor
- OpenWrt VM: read the stable release from downloads.openwrt.org [@MickLesk](https://github.com/MickLesk) ([#18411](https://github.com/community-scripts/ProxmoxVE/pull/18411))
- Immich: fetch VectorChord from its new repo, skip Java, get GeoNames first [@MickLesk](https://github.com/MickLesk) ([#18397](https://github.com/community-scripts/ProxmoxVE/pull/18397))
### 💾 Core
- fix: put the reason for a VM failure into its trace [@MickLesk](https://github.com/MickLesk) ([core#200](https://github.com/community-scripts/core/pull/200))
- fix: check the bridge before creating a container, and its state after start [@MickLesk](https://github.com/MickLesk) ([core#199](https://github.com/community-scripts/core/pull/199))
- ref: offer to remove only the container this run created [@MickLesk](https://github.com/MickLesk) ([core#198](https://github.com/community-scripts/core/pull/198))
- feat: report a cancelled tool dialog as an abort and name the failing command [@MickLesk](https://github.com/MickLesk) ([core#197](https://github.com/community-scripts/core/pull/197))
- feat: check KVM and the host's threads before building a VM [@MickLesk](https://github.com/MickLesk) ([core#196](https://github.com/community-scripts/core/pull/196))
- ref: give a host tool one log file for the whole run [@MickLesk](https://github.com/MickLesk) ([core#194](https://github.com/community-scripts/core/pull/194))
- fix: keep a piped stdin when output moves to the log after a hangup [@MickLesk](https://github.com/MickLesk) ([core#193](https://github.com/community-scripts/core/pull/193))
- fix: pve-LXC: say when the container went away during its setup [@MickLesk](https://github.com/MickLesk) ([core#192](https://github.com/community-scripts/core/pull/192))
- fix: treat Esc in a VM wizard dialog as leaving the script [@MickLesk](https://github.com/MickLesk) ([core#190](https://github.com/community-scripts/core/pull/190))
- ref: count a container stopped during a detached install as an abort [@MickLesk](https://github.com/MickLesk) ([core#187](https://github.com/community-scripts/core/pull/187))
- feat: find the assets of a renamed GitHub repo without the API [@MickLesk](https://github.com/MickLesk) ([core#188](https://github.com/community-scripts/core/pull/188))
- fix: restore npm when an interrupted update left it behind [@MickLesk](https://github.com/MickLesk) ([core#189](https://github.com/community-scripts/core/pull/189))
- feat: add var_cmode to set the container console mode [@MickLesk](https://github.com/MickLesk) ([core#180](https://github.com/community-scripts/core/pull/180))
### 🧰 Tools
- #### 🐞 Bug Fixes
- all-templates: answering No or Cancel is not a failure [@MickLesk](https://github.com/MickLesk) ([#18423](https://github.com/community-scripts/ProxmoxVE/pull/18423))
- homebrew: skip service accounts when picking the user, survive a failed apt update [@MickLesk](https://github.com/MickLesk) ([#18424](https://github.com/community-scripts/ProxmoxVE/pull/18424))
- add-tailscale-lxc: start a stopped container and check its OS first [@MickLesk](https://github.com/MickLesk) ([#18421](https://github.com/community-scripts/ProxmoxVE/pull/18421))
- netdata: survive a failed apt update, leave quietly on Cancel [@MickLesk](https://github.com/MickLesk) ([#18427](https://github.com/community-scripts/ProxmoxVE/pull/18427))
- Privilege-Converter: offer only container-capable storages as the converter's restore target [@MickLesk](https://github.com/MickLesk) ([#18415](https://github.com/community-scripts/ProxmoxVE/pull/18415))
- let post-pve-install go on when legacy sources are kept [@MickLesk](https://github.com/MickLesk) ([#18403](https://github.com/community-scripts/ProxmoxVE/pull/18403))
- #### 🔧 Refactor
- update-apps: say why an backup or restore failed [@MickLesk](https://github.com/MickLesk) ([#18405](https://github.com/community-scripts/ProxmoxVE/pull/18405))
- Keep host tools running when clear does not know the terminal [@MickLesk](https://github.com/MickLesk) ([#18412](https://github.com/community-scripts/ProxmoxVE/pull/18412))
- Refuse to run the PBS 4 upgrade on a Proxmox VE host [@MickLesk](https://github.com/MickLesk) ([#18416](https://github.com/community-scripts/ProxmoxVE/pull/18416))
- fstrim: keep trimming when pct fstrim fails on one container [@MickLesk](https://github.com/MickLesk) ([#18413](https://github.com/community-scripts/ProxmoxVE/pull/18413))
- execute-lxc: do not abort on a container that is not Debian or Ubuntu [@MickLesk](https://github.com/MickLesk) ([#18414](https://github.com/community-scripts/ProxmoxVE/pull/18414))
### 📚 Documentation
- github: flag instead of closing on first PR template miss; add script-request eligibility check [@MickLesk](https://github.com/MickLesk) ([#18384](https://github.com/community-scripts/ProxmoxVE/pull/18384))
## 2026-11-02
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- FileFlows: replace the program folders on update [@MickLesk](https://github.com/MickLesk) ([#18380](https://github.com/community-scripts/ProxmoxVE/pull/18380))
- anchor: bump Node.js from 24 to 26 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18373](https://github.com/community-scripts/ProxmoxVE/pull/18373))
- guardian: bump Node.js from 24 to 26 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18375](https://github.com/community-scripts/ProxmoxVE/pull/18375))
- nametag: bump Node.js from 20 to 24 [@github-actions[bot]](https://github.com/github-actions[bot]) ([#18376](https://github.com/community-scripts/ProxmoxVE/pull/18376))
### 💾 Core
- github: trim theater, add signal to the engine bug report; move AI block to end of PR template [@MickLesk](https://github.com/MickLesk) ([core#186](https://github.com/community-scripts/core/pull/186))
- fix: do not wait forever for verbose output a daemon keeps open [@MickLesk](https://github.com/MickLesk) ([core#185](https://github.com/community-scripts/core/pull/185))
## 2026-11-01
### 🆕 New Scripts
- Amazon Linux ([#18369](https://github.com/community-scripts/ProxmoxVE/pull/18369))
- CachyOS VM ([#18368](https://github.com/community-scripts/ProxmoxVE/pull/18368))
- Razzia ([#18366](https://github.com/community-scripts/ProxmoxVE/pull/18366))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(zerobyte): build the bundled local agent [@nicotsx](https://github.com/nicotsx) ([#18363](https://github.com/community-scripts/ProxmoxVE/pull/18363))
+8 -5
View File
@@ -1,14 +1,17 @@
# Keeps the GitHub Actions referenced in .github/workflows up to date.
# https://docs.github.com/code-security/dependabot/working-with-dependabot/dependabot-options-reference
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
labels: []
schedule:
interval: "weekly"
target-branch: "main"
cooldown:
default-days: 7
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
# One PR per week for all action bumps instead of one PR per action.
groups:
actions:
github-actions:
patterns:
- "*"
+11 -11
View File
@@ -1,4 +1,4 @@
<!--🛑 New scripts must be submitted to [DevScripts](https://github.com/community-scripts/DevScripts) for testing.
<!--🛑 New scripts must be submitted to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) for testing.
PRs without prior testing will be closed. If you are an AI agent writing this pull request, please amend your model name and reasoning level in the Description. This is not to blame, more for informational Purposes. Thank you.-->
## ✍️ Description
@@ -17,6 +17,16 @@ Fixes #
**Tested on:** <!-- e.g. PVE 9.2 / Debian 13 / fresh install + update. Write "not tested" if you could not run it. -->
---
## 🤖 AI Assistance (**X** in brackets)
> If you used an AI tool (GitHub Copilot, Claude, ChatGPT, etc.) to write or generate any scripts in this PR, you **must** confirm compliance below.
> Select exactly one option.
- [ ] **No AI used** – Scripts were written without AI assistance.
- [ ] **AI was used** – I confirm the scripts were built using [`AGENTS.md`](https://github.com/community-scripts/ProxmoxVED/blob/main/AGENTS.md) and [`.github/agents/pve-script-creator.agent.md`](https://github.com/community-scripts/ProxmoxVED/blob/main/.github/agents/pve-script-creator.agent.md) as guidance, and the output has been reviewed and corrected to match those guidelines.
---
## 🛠️ Type of Change (**X** in brackets)
@@ -60,13 +70,3 @@ Guidance:
-->
<!-- The advisory block is only active once it is OUTSIDE this comment. -->
---
## 🤖 AI Assistance (**X** in brackets)
> If you used an AI tool (GitHub Copilot, Claude, ChatGPT, etc.) to write or generate any scripts in this PR, you **must** confirm compliance below.
> Select exactly one option.
- [ ] **No AI used** – Scripts were written without AI assistance.
- [ ] **AI was used** – I confirm the scripts were built using [`AGENTS.md`](https://github.com/community-scripts/DevScripts/blob/main/AGENTS.md) and [`.github/agents/pve-script-creator.agent.md`](https://github.com/community-scripts/DevScripts/blob/main/.github/agents/pve-script-creator.agent.md) as guidance, and the output has been reviewed and corrected to match those guidelines.
+119
View File
@@ -0,0 +1,119 @@
name: Auto Update .app-files
on:
push:
branches:
- main
paths:
- "ct/**.sh"
- "tools/**.sh"
- "vm/**.sh"
workflow_dispatch:
jobs:
update-app-files:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.GHAPP_PR_ID }}
private-key: ${{ secrets.GHAPP_PR_PRIVATE_KEY }}
- name: Generate a token for PR approval and merge
id: generate-token-merge
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.GHAPP_MERGEBOT_ID }}
private-key: ${{ secrets.GHAPP_MERGEBOT_PRIVATE_KEY }}
# Step 1: Checkout repository
- name: Checkout repository
uses: actions/checkout@v7
# Step 2: Disable file mode changes detection
- name: Disable file mode changes
run: git config core.fileMode false
# Step 3: Set up Git user for committing changes
- name: Set up Git
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Step 4: Install figlet
- name: Install figlet
run: sudo apt-get install -y figlet
# Step 5: Run the updated generate-app-files.sh script
- name: Run generate-app-files.sh
run: |
chmod +x .github/workflows/scripts/generate-app-headers.sh
.github/workflows/scripts/generate-app-headers.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Step 6: Check if there are any changes
- name: Check if there are any changes
run: |
echo "Checking for changes..."
git add -A # Untracked Dateien aufnehmen
git status
if git diff --cached --quiet; then
echo "No changes detected."
echo "changed=false" >> "$GITHUB_ENV"
else
echo "Changes detected:"
git diff --stat --cached
echo "changed=true" >> "$GITHUB_ENV"
fi
# Step 7: Commit and create PR if changes exist
- name: Commit and create PR if changes exist
if: env.changed == 'true'
run: |
git commit -m "Update .app files"
git checkout -b pr-update-app-files
git push origin pr-update-app-files --force
gh pr create --title "[core] update .app files" \
--body "This PR is auto-generated by a GitHub Action to update the .app files." \
--head pr-update-app-files \
--base main \
--label "automated pr"
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
- name: Approve pull request
if: env.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "pr-update-app-files" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi
- name: Approve pull request and merge
if: env.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}
run: |
git config --global user.name "github-actions-automege[bot]"
git config --global user.email "github-actions-automege[bot]@users.noreply.github.com"
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash --admin
fi
# Step 8: Output success message when no changes
- name: No changes detected
if: env.changed == 'false'
run: echo "No changes to commit. Workflow completed successfully."
+2 -2
View File
@@ -126,7 +126,7 @@ jobs:
try {
const { data: relatedIssues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: "DevScripts",
repo: "ProxmoxVED",
state: "all",
labels: ["Started Migration To ProxmoxVE"]
});
@@ -249,7 +249,7 @@ jobs:
if (new Date(pr.merged_at) <= latestDateInChangelog) continue;
const rawLabels = pr.labels.map(label => label.name.toLowerCase());
if (rawLabels.some(l => ["invalid", "wontdo", "wontfix", "duplicate", process.env.AUTOMATED_PR_LABEL].includes(l))) continue;
if (rawLabels.some(l => ["invalid", "wontdo", "wontfix", "duplicate"].includes(l))) continue;
if (!coreCategory) {
console.log(`No Core category configured; skipping core#${pr.number}`);
+1 -1
View File
@@ -491,7 +491,7 @@ jobs:
bash -c "\$(curl -fsSL "\$COMMUNITY_SCRIPTS_URL/ct/${slug}.sh")"
\`\`\`
Both lines are needed — without \`COMMUNITY_SCRIPTS_URL\` the engine takes the \`install/\` script from \`main\`.
Both lines are needed — each script pins \`_CS_DEFAULT_URL\` to \`main\`, and that pin is what fills \`COMMUNITY_SCRIPTS_URL\` when it is unset.
If the bump is wrong — upstream ships a newer Node than the app needs, or this script is deliberately held back — **close this PR**. It gets recorded on the drift report and this exact bump is not proposed again.
PR_EOF
-27
View File
@@ -1,27 +0,0 @@
name: Check Script Repo URL
on:
pull_request:
branches: ["main"]
paths:
- "ct/**"
- "install/**"
- "vm/**"
- "tools/**"
permissions:
contents: read
jobs:
check:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Reject DevScripts URLs
run: |
if grep -rnE 'community-scripts/(ProxmoxVED|DevScripts)/' ct install vm tools; then
echo "::error::These lines point at DevScripts, so telemetry files the runs there. Use community-scripts/ProxmoxVE."
exit 1
fi
+40 -44
View File
@@ -1,4 +1,4 @@
name: Flag PRs Missing Template
name: Close PRs Missing Template
on:
pull_request_target:
@@ -14,12 +14,10 @@ jobs:
issues: write
contents: read
steps:
- name: Check PR against the template, comment and label instead of closing
- name: Close PR if it does not follow the PR template
uses: actions/github-script@v9
with:
script: |
const MARKER = "<!-- pr-template-check -->";
const LABELS = ["missing pr template", "stale"];
const pr = context.payload.pull_request;
const prNumber = pr.number;
const author = pr.user.login;
@@ -46,11 +44,11 @@ jobs:
return;
}
const prLabels = pr.labels.map((label) => label.name);
const labels = pr.labels.map((label) => label.name);
const skipLabels = ["automated pr", "keep-open"];
if (skipLabels.some((label) => prLabels.includes(label))) {
core.info(`PR #${prNumber} has a skip label (${prLabels.join(", ")}) — skipping template validation.`);
if (skipLabels.some((label) => labels.includes(label))) {
core.info(`PR #${prNumber} has a skip label (${labels.join(", ")}) — skipping template validation.`);
return;
}
@@ -116,52 +114,50 @@ jobs:
failures.push("At least one **Type of Change** checkbox must be checked.");
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100
});
const mine = comments.find(c => c.body && c.body.includes(MARKER));
if (failures.length === 0) {
core.info(`PR #${prNumber} follows the PR template.`);
if (mine && !mine.body.startsWith(MARKER + "\n✅")) {
await github.rest.issues.updateComment({
owner, repo, comment_id: mine.id,
body: MARKER + "\n✅ **Template check passed.** Thanks for fixing the description — the `stale` mark has been removed.",
});
}
for (const name of LABELS) {
if (prLabels.includes(name)) {
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name });
}
}
return;
}
core.info(`PR #${prNumber} — template validation failed, flagging instead of closing.`);
core.info(`Closing PR #${prNumber} — template validation failed.`);
const templateUrl =
"https://github.com/community-scripts/ProxmoxVE/blob/main/.github/pull_request_template.md";
const failureList = failures.map((item) => `- ${item}`).join("\n");
const text =
MARKER + "\n" +
`👋 Hi @${author},\n\n` +
`This pull request does not follow the [PR template](${templateUrl}) yet:\n\n` +
failureList + "\n\n" +
`**Edit the PR description** to fix it — no new PR needed. This check runs again on every edit and clears itself once everything is in place.\n\n` +
`Until then the PR is marked \`stale\` and will be closed in **7 days**.\n\n` +
`Maintainers can add the \`keep-open\` label to exempt a PR from this check.\n\n` +
`Thank you for contributing! 🙏`;
const comment = [
`👋 Hi @${author},`,
``,
`This pull request was closed because it does not follow the [PR template](${templateUrl}).`,
``,
`Please fix the following and open a new PR (or reopen this one after updating the description):`,
``,
failureList,
``,
`> Use the template sections, fill in the description, check all prerequisite boxes, and select at least one type of change.`,
``,
`Maintainers can add the \`keep-open\` label to exempt a PR from this check.`,
``,
`Thank you for contributing! 🙏`,
].join("\n");
if (mine) {
if (mine.body !== text) {
await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body: text });
}
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: text });
}
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: comment,
});
const toAdd = LABELS.filter(name => !prLabels.includes(name));
if (toAdd.length > 0) {
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: toAdd });
}
await github.rest.pulls.update({
owner,
repo,
pull_number: prNumber,
state: "closed",
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: ["missing pr template"],
});
+2 -2
View File
@@ -86,10 +86,10 @@ jobs:
``,
`However, **new scripts must first be submitted to our development repository** for testing and review before they can be merged here.`,
``,
`> 🛑 New scripts must be submitted to [**DevScripts**](https://github.com/community-scripts/DevScripts) for testing.`,
`> 🛑 New scripts must be submitted to [**ProxmoxVED**](https://github.com/community-scripts/ProxmoxVED) for testing.`,
`> PRs without prior testing will be closed.`,
``,
`Please open your PR at **https://github.com/community-scripts/DevScripts** instead.`,
`Please open your PR at **https://github.com/community-scripts/ProxmoxVED** instead.`,
`Once your script has been tested and approved there, it will be pushed to this repository automatically.`,
``,
`This PR will now be closed. Thank you for understanding! 🙏`,
+5 -22
View File
@@ -12,7 +12,7 @@ jobs:
if: github.event.pull_request.merged == true && github.repository == 'community-scripts/ProxmoxVE'
runs-on: self-hosted
env:
DEV_REPO: community-scripts/DevScripts
DEV_REPO: community-scripts/ProxmoxVED
steps:
- name: Checkout target repo (merge commit)
@@ -60,7 +60,7 @@ jobs:
echo "count=$(printf '%s' "$slugs" | wc -w | tr -d '[:space:]')" >> "$GITHUB_OUTPUT"
echo "Slugs in this PR: ${slugs:-<none>}"
- name: Resolve the matching DevScripts issue
- name: Resolve the matching ProxmoxVED issue
id: find_issue
shell: bash
env:
@@ -71,7 +71,7 @@ jobs:
# creates, so migration PRs carry an exact back-reference. Guessing from
# titles or file names is never needed for them.
matched=$(printf '%s' "$PR_BODY" \
| grep -oE 'community-scripts/(ProxmoxVED|DevScripts)#[0-9]+' \
| grep -oE 'community-scripts/ProxmoxVED#[0-9]+' \
| sed 's/.*#//' || true)
if [ -n "$matched" ]; then
@@ -96,7 +96,7 @@ jobs:
[ -n "$row" ] || continue
num=$(printf '%s' "$row" | jq -r '.number')
# Same slug derivation the DevScripts workflows use, so both
# Same slug derivation the ProxmoxVED workflows use, so both
# sides agree on what "alpine-cinny" means.
name=$(printf '%s' "$row" | jq -r '.body // ""' \
| sed -n '/^###[[:space:]]*Name of the Script/,/^###/p' \
@@ -124,7 +124,7 @@ jobs:
echo "issue_numbers=$matched" >> "$GITHUB_OUTPUT"
echo "Closing: ${matched:-<none>}"
- name: Comment on and close matching DevScripts issues
- name: Comment on and close matching ProxmoxVED issues
if: steps.find_issue.outputs.issue_numbers != ''
shell: bash
env:
@@ -258,20 +258,3 @@ jobs:
})().catch(e => { console.error(e); process.exit(1); });
ENDSCRIPT
shell: bash
- name: Move the scripts' telemetry to ProxmoxVE
if: ${{ !cancelled() && steps.find_issue.outputs.issue_numbers != '' && steps.get_slugs.outputs.count != '0' }}
shell: bash
env:
TELEMETRY_ADMIN_TOKEN: ${{ secrets.TELEMETRY_ADMIN_TOKEN }}
run: |
if [ -z "$TELEMETRY_ADMIN_TOKEN" ]; then
echo "TELEMETRY_ADMIN_TOKEN is not set, skipping"
exit 0
fi
body=$(jq -cn --arg s "$(cat pocketbase_slugs.txt)" \
'{slugs: ($s | split(" ") | map(select(. != ""))), from: "DevScripts", to: "ProxmoxVE"}')
curl -fsS -X POST "https://telemetry.community-scripts.org/api/admin/migrate-script" \
-H "Authorization: Bearer ${TELEMETRY_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d "$body" || echo "::warning::Moving the telemetry history to ProxmoxVE failed"
+3 -2
View File
@@ -267,14 +267,15 @@ jobs:
const stubPath = path.join('ct', slug + '.sh');
const content = [
"#!/usr/bin/env bash",
"source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/core/main/core/core.func)",
"source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)",
"# Copyright (c) 2021-2026 community-scripts ORG",
"# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE",
"",
`APP="${appName.replace(/"/g, '\\"')}"`,
"",
'header_info "$APP"',
'variables',
'color',
'header_info',
"",
'msg_error "This script is no longer available in community-scripts."',
`msg_error "${deletedMessage.replace(/"/g, '\\"')}"`,
+626
View File
@@ -0,0 +1,626 @@
name: PocketBase AI Bot
# Natural-language companion to pocketbase-bot.yml.
# Mention the bot in plain English, e.g.:
# @pocketbase-bot change RAM to 4096 on zigbee2mqtt
# @pocketbase-bot disable script Nextcloud because upstream is broken
# The bot parses the request with GitHub Models, replies with the exact change(s)
# it understood, and only applies them after you reply "@pocketbase-bot confirm".
# The slash-command bot (/pocketbase ...) is unaffected; triggers do not overlap.
on:
issue_comment:
types: [created]
permissions:
models: read # lets the built-in GITHUB_TOKEN call GitHub Models inference
contents: write # built-in token opens the CT-defaults sync PR (like the slash bot)
pull-requests: write
jobs:
ai-bot:
runs-on: self-hosted
# Broad gate; the script does precise keyword + self-author checks.
if: contains(github.event.comment.body, '@pocketbase-bot')
steps:
- name: Mint GitHub App token (bot identity)
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ vars.GHAPP_PBBOT_ID }}
private-key: ${{ secrets.GHAPP_PBBOT_PRIVATE_KEY }}
- name: Run PocketBase AI bot
env:
# GitHub REST as the bot identity
GH_APP_TOKEN: ${{ steps.app-token.outputs.token }}
PB_BOT_APP_ID: ${{ vars.GHAPP_PBBOT_ID }}
# GitHub Models inference uses the built-in token (needs models: read)
MODELS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Built-in token for git/PR ops (CT-defaults sync), mirroring the slash bot
GH_DEFAULT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AI_MODEL: openai/gpt-4o
# PocketBase
POCKETBASE_URL: ${{ vars.POCKETBASE_URL }}
POCKETBASE_COLLECTION: ${{ vars.POCKETBASE_COLLECTION }}
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
FRONTEND_URL: ${{ vars.FRONTEND_URL }}
REVALIDATE_SECRET: ${{ secrets.FRONTEND_INGEST_SECRET }}
# Event context
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_ID: ${{ github.event.comment.id }}
COMMENT_AUTHOR_TYPE: ${{ github.event.comment.user.type }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
ACTOR: ${{ github.event.comment.user.login }}
ACTOR_ASSOCIATION: ${{ github.event.comment.author_association }}
run: |
node << 'ENDSCRIPT'
(async function () {
const https = require('https');
const http = require('http');
const url = require('url');
// ── HTTP helper with redirect following ────────────────────────────
function request(fullUrl, opts, redirectCount) {
redirectCount = redirectCount || 0;
return new Promise(function (resolve, reject) {
const u = url.parse(fullUrl);
const isHttps = u.protocol === 'https:';
const body = opts.body;
const options = {
hostname: u.hostname,
port: u.port || (isHttps ? 443 : 80),
path: u.path,
method: opts.method || 'GET',
headers: opts.headers || {}
};
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
const lib = isHttps ? https : http;
const req = lib.request(options, function (res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
const redirectUrl = url.resolve(fullUrl, res.headers.location);
res.resume();
resolve(request(redirectUrl, opts, redirectCount + 1));
return;
}
let data = '';
res.on('data', function (chunk) { data += chunk; });
res.on('end', function () {
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
// ── GitHub REST (as the bot app) ───────────────────────────────────
const owner = process.env.REPO_OWNER;
const repo = process.env.REPO_NAME;
const issueNumber = parseInt(process.env.ISSUE_NUMBER, 10);
const commentId = parseInt(process.env.COMMENT_ID, 10);
const actor = process.env.ACTOR;
function ghRequest(path, method, body) {
const headers = {
'Authorization': 'Bearer ' + process.env.GH_APP_TOKEN,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'PocketBase-AI-Bot'
};
const bodyStr = body ? JSON.stringify(body) : undefined;
if (bodyStr) headers['Content-Type'] = 'application/json';
return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });
}
// Same as ghRequest but authenticated with the built-in GITHUB_TOKEN.
// Used for the CT-defaults sync branch/PR (the App token lacks contents:write).
function ghDefault(path, method, body) {
const headers = {
'Authorization': 'Bearer ' + process.env.GH_DEFAULT_TOKEN,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'PocketBase-AI-Bot'
};
const bodyStr = body ? JSON.stringify(body) : undefined;
if (bodyStr) headers['Content-Type'] = 'application/json';
return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });
}
async function addReaction(content) {
try {
await ghRequest('/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions', 'POST', { content });
} catch (e) { console.warn('Could not add reaction:', e.message); }
}
async function postComment(text) {
const res = await ghRequest('/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments', 'POST', { body: text });
if (!res.ok) console.warn('Could not post comment:', res.body);
return res.ok ? JSON.parse(res.body) : null;
}
async function updateComment(id, text) {
const res = await ghRequest('/repos/' + owner + '/' + repo + '/issues/comments/' + id, 'PATCH', { body: text });
if (!res.ok) console.warn('Could not update comment:', res.body);
}
async function listIssueComments() {
const all = [];
let page = 1;
while (page <= 10) {
const res = await ghRequest('/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments?per_page=100&page=' + page);
if (!res.ok) break;
const batch = JSON.parse(res.body);
all.push.apply(all, batch);
if (batch.length < 100) break;
page++;
}
return all;
}
// ── 1. Self-trigger guard (App-token comments DO re-fire this event) ─
if (process.env.COMMENT_AUTHOR_TYPE === 'Bot') {
console.log('Comment authored by a bot — skipping to avoid loops.');
return;
}
// ── 2. Permission gate (mirrors the slash bot) ─────────────────────
const association = process.env.ACTOR_ASSOCIATION;
if (association !== 'OWNER' && association !== 'MEMBER') {
await addReaction('-1');
await postComment(
'❌ **PocketBase AI Bot**: @' + actor + ' is not authorized to use this command.\n' +
'Only org members (Contributors team) can use `@pocketbase-bot`.'
);
return;
}
// ── 3. Extract the instruction after the @pocketbase-bot handle ────
const commentBody = process.env.COMMENT_BODY || '';
const handleMatch = commentBody.match(/@pocketbase-bot(\[bot\])?/i);
if (!handleMatch) {
console.log('No @pocketbase-bot handle found — ignoring.');
return;
}
const instruction = commentBody.slice(handleMatch.index + handleMatch[0].length).trim();
if (!instruction) {
await addReaction('-1');
await postComment(
'ℹ️ **PocketBase AI Bot**: Tell me what to do, e.g.\n' +
'`@pocketbase-bot change RAM to 4096 on zigbee2mqtt` or `@pocketbase-bot disable script Nextcloud`.'
);
return;
}
// ── PocketBase auth + low-level helpers ────────────────────────────
const pbRaw = process.env.POCKETBASE_URL.replace(/\/$/, '');
const apiBase = /\/api$/i.test(pbRaw) ? pbRaw : pbRaw + '/api';
const coll = process.env.POCKETBASE_COLLECTION;
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
async function pbAuth() {
const res = await request(apiBase + '/collections/users/auth-with-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identity: process.env.POCKETBASE_ADMIN_EMAIL, password: process.env.POCKETBASE_ADMIN_PASSWORD })
});
if (!res.ok) throw new Error('PocketBase auth failed: ' + res.body);
return JSON.parse(res.body).token;
}
async function pbFindRecord(token, slug) {
const filter = "(slug='" + String(slug).replace(/'/g, "''") + "')";
const res = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', { headers: { 'Authorization': token } });
const list = JSON.parse(res.body);
return list.items && list.items[0];
}
async function pbPatch(token, id, payload) {
return request(recordsUrl + '/' + id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
function readJsonBlob(val) {
if (Array.isArray(val)) return val;
try { return JSON.parse(val || '[]'); } catch (e) { return []; }
}
async function revalidate(s) {
const frontendUrl = process.env.FRONTEND_URL;
const secret = process.env.REVALIDATE_SECRET;
if (!frontendUrl || !secret) return;
try {
await request(frontendUrl.replace(/\/$/, '') + '/api/revalidate', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + secret, 'Content-Type': 'application/json' },
body: JSON.stringify({ tags: ['scripts', 'script-' + s] })
});
} catch (e) { console.warn('Revalidation skipped:', e.message); }
}
// ── CT-defaults sync PR (copied from slash bot) ────────────────────
function encodeContentPath(filePath) { return filePath.split('/').map(encodeURIComponent).join('/'); }
function decodeGitHubContent(content) { return Buffer.from((content || '').replace(/\n/g, ''), 'base64').toString('utf8'); }
function sanitizeBranchPart(value) {
return (value || '').toLowerCase().replace(/[^a-z0-9._/-]+/g, '-').replace(/\/+/g, '/').replace(/^-+|-+$/g, '');
}
function applyCtDefaultChanges(scriptText, varChanges) {
let nextText = scriptText;
const updatedVars = [], unchangedVars = [];
for (const [varName, rawValue] of Object.entries(varChanges)) {
const newValue = String(rawValue);
const pattern = new RegExp('(^\\s*' + varName + '="\\$\\{' + varName + ':-)([^"}]*)(\\}"\\s*$)', 'm');
const match = nextText.match(pattern);
if (!match) continue;
if (match[2] === newValue) { unchangedVars.push(varName); continue; }
nextText = nextText.replace(pattern, '$1' + newValue + '$3');
updatedVars.push(varName);
}
return { nextText, updatedVars, unchangedVars };
}
async function ensureBranch(defaultBranch, branchName) {
const ghRequest = ghDefault; // git ops run as the built-in token
const branchRefRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/ref/heads/' + encodeURIComponent(branchName));
if (branchRefRes.ok) return;
const defaultRefRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/ref/heads/' + encodeURIComponent(defaultBranch));
if (!defaultRefRes.ok) throw new Error('Could not read default branch ref: ' + defaultRefRes.body);
const defaultRef = JSON.parse(defaultRefRes.body);
const createBranchRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/refs', 'POST', { ref: 'refs/heads/' + branchName, sha: defaultRef.object.sha });
if (!createBranchRes.ok) throw new Error('Could not create branch: ' + createBranchRes.body);
}
async function upsertCtDefaultsPr(slugValue, varChanges) {
const ghRequest = ghDefault; // contents/PR ops run as the built-in token
const wantedEntries = Object.entries(varChanges || {}).filter(function ([, v]) { return v !== undefined && v !== null && String(v) !== ''; });
if (wantedEntries.length === 0) return { status: 'skipped', reason: 'No mapped CT defaults changed.' };
const repoRes = await ghRequest('/repos/' + owner + '/' + repo);
if (!repoRes.ok) throw new Error('Could not read repository metadata: ' + repoRes.body);
const defaultBranch = JSON.parse(repoRes.body).default_branch;
const ctPath = 'ct/' + slugValue + '.sh';
const encodedCtPath = encodeContentPath(ctPath);
const defaultFileRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath + '?ref=' + encodeURIComponent(defaultBranch));
if (defaultFileRes.statusCode === 404) return { status: 'skipped', reason: 'No matching CT file found at `' + ctPath + '`.' };
if (!defaultFileRes.ok) throw new Error('Could not read CT file from default branch: ' + defaultFileRes.body);
const branchName = 'pocketbase-sync/' + sanitizeBranchPart(slugValue || 'unknown');
await ensureBranch(defaultBranch, branchName);
const branchFileRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath + '?ref=' + encodeURIComponent(branchName));
if (!branchFileRes.ok) throw new Error('Could not read CT file from sync branch: ' + branchFileRes.body);
const branchFile = JSON.parse(branchFileRes.body);
const currentBranchText = decodeGitHubContent(branchFile.content);
const updateResult = applyCtDefaultChanges(currentBranchText, Object.fromEntries(wantedEntries));
if (updateResult.updatedVars.length === 0) return { status: 'skipped', reason: 'CT defaults already up to date.' };
const putRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath, 'PUT', {
message: 'chore(ct): sync ' + slugValue + ' defaults from PocketBase',
content: Buffer.from(updateResult.nextText, 'utf8').toString('base64'),
sha: branchFile.sha,
branch: branchName
});
if (!putRes.ok) throw new Error('Could not update CT file: ' + putRes.body);
const openPrRes = await ghRequest('/repos/' + owner + '/' + repo + '/pulls?state=open&head=' + encodeURIComponent(owner + ':' + branchName) + '&base=' + encodeURIComponent(defaultBranch));
if (!openPrRes.ok) throw new Error('Could not query existing PRs: ' + openPrRes.body);
const openPrs = JSON.parse(openPrRes.body);
if (openPrs.length > 0) return { status: 'updated', prUrl: openPrs[0].html_url, updatedVars: updateResult.updatedVars };
const createPrRes = await ghRequest('/repos/' + owner + '/' + repo + '/pulls', 'POST', {
title: 'chore(ct): sync ' + slugValue + ' defaults with PocketBase',
body: '## Summary\n- Sync default CT variables for `' + slugValue + '` after an `@pocketbase-bot` update.\n- Updated vars: `' + updateResult.updatedVars.join('`, `') + '`.\n\n## Source\n- Triggered by @' + actor + ' via PocketBase AI bot.\n',
head: branchName,
base: defaultBranch
});
if (!createPrRes.ok) throw new Error('Could not create PR: ' + createPrRes.body);
return { status: 'created', prUrl: JSON.parse(createPrRes.body).html_url, updatedVars: updateResult.updatedVars };
}
// ── Allow-lists (mirror the slash bot) ─────────────────────────────
const ALLOWED_FIELDS = {
name: 'string', description: 'string', logo: 'string', documentation: 'string',
website: 'string', project_url: 'string', repository: 'string', config_path: 'string',
tags: 'string', port: 'number', default_user: 'nullable_string', default_passwd: 'nullable_string',
unprivileged: 'number', updateable: 'boolean', privileged: 'boolean',
architectures: 'select_list', platforms: 'select_list',
is_dev: 'boolean', is_disabled: 'boolean', disable_message: 'string',
is_deleted: 'boolean', deleted_message: 'string'
};
const SELECT_VALUES = {
architectures: ['amd64', 'arm64'],
platforms: ['pve', 'incus'],
};
const FIELD_TO_CT_VAR = { tags: 'var_tags', unprivileged: 'var_unprivileged' };
const RESOURCE_KEYS = { cpu: 'number', ram: 'number', hdd: 'number', os: 'string', version: 'string' };
const METHOD_KEYS = { config_path: 'string', script: 'string' };
const ALL_METHOD_KEYS = Object.assign({}, RESOURCE_KEYS, METHOD_KEYS);
const RESOURCE_TO_CT_VAR = { cpu: 'var_cpu', ram: 'var_ram', hdd: 'var_disk', os: 'var_os', version: 'var_version' };
function castFieldValue(key, rawVal) {
const type = ALLOWED_FIELDS[key];
if (!type) return { error: 'Unknown field `' + key + '`' };
if (type === 'boolean') {
if (rawVal === true || rawVal === 'true') return { value: true };
if (rawVal === false || rawVal === 'false') return { value: false };
return { error: '`' + key + '` must be true/false' };
}
if (type === 'number') {
const n = parseInt(rawVal, 10);
if (isNaN(n)) return { error: '`' + key + '` must be a number' };
return { value: n };
}
if (type === 'select_list') {
// architectures/platforms are multi-selects over a closed set,
// so an unknown value is rejected rather than stored. The model
// may hand back an array or a comma-separated string.
const allowed = SELECT_VALUES[key] || [];
const raw = Array.isArray(rawVal) ? rawVal : String(rawVal == null ? '' : rawVal).split(',');
const values = raw.map(function (v) { return String(v).trim(); }).filter(Boolean);
const bad = values.filter(function (v) { return allowed.indexOf(v) === -1; });
if (bad.length > 0) return { error: '`' + key + '` accepts ' + allowed.join(', ') + ' — got: ' + bad.join(', ') };
return { value: values };
}
if (type === 'nullable_string') return { value: rawVal === '' || rawVal == null ? null : String(rawVal) };
return { value: String(rawVal) };
}
// ── Operation validation (used at propose AND confirm time) ────────
// Never trust raw operations: enforce the field/op allow-lists and
// re-cast values. Returns only well-formed, allowed operations.
function sanitizeOperations(ops) {
const validOps = [], problems = [];
for (const op of (Array.isArray(ops) ? ops : [])) {
if (op && op.kind === 'field') {
const cast = castFieldValue(op.field, op.value);
if (cast.error) { problems.push(cast.error); continue; }
validOps.push({ kind: 'field', field: op.field, value: cast.value });
} else if (op && op.kind === 'note' && ['add', 'edit', 'remove'].includes(op.action)) {
validOps.push({ kind: 'note', action: op.action, type: String(op.type || ''), text: op.text, newText: op.newText });
} else if (op && op.kind === 'method' && ['add', 'edit', 'remove'].includes(op.action)) {
const changes = {};
for (const [k, v] of Object.entries(op.changes || {})) { if (ALL_METHOD_KEYS[k]) changes[k] = v; }
validOps.push({ kind: 'method', action: op.action, type: String(op.type || 'default'), changes });
} else {
problems.push('Unsupported operation: `' + JSON.stringify(op) + '`');
}
}
return { validOps, problems };
}
// ── Executor: apply a validated {slug, operations} set ─────────────
async function applyOperations(action) {
const token = await pbAuth();
const record = await pbFindRecord(token, action.slug);
if (!record) return { ok: false, summary: '❌ No PocketBase record for slug `' + action.slug + '`.' };
const fieldPayload = {};
let notesArr = readJsonBlob(record.notes);
let methodsArr = readJsonBlob(record.install_methods);
let notesChanged = false, methodsChanged = false;
const ctChanges = {};
const lines = [];
for (const op of action.operations) {
if (op.kind === 'field') {
const cast = castFieldValue(op.field, op.value);
if (cast.error) { lines.push('- ⚠️ skipped field: ' + cast.error); continue; }
fieldPayload[op.field] = cast.value;
if (FIELD_TO_CT_VAR[op.field]) ctChanges[FIELD_TO_CT_VAR[op.field]] = cast.value;
lines.push('- `' + op.field + '` → `' + JSON.stringify(cast.value) + '`');
} else if (op.kind === 'note') {
const type = String(op.type || '').toLowerCase();
if (op.action === 'add') {
notesArr.push({ type, text: String(op.text || '') });
notesChanged = true; lines.push('- note add `' + type + '`: ' + op.text);
} else if (op.action === 'remove') {
const before = notesArr.length;
notesArr = notesArr.filter(function (n) { return !(String(n.type).toLowerCase() === type && n.text === op.text); });
if (notesArr.length !== before) { notesChanged = true; lines.push('- note remove `' + type + '`: ' + op.text); }
else lines.push('- ⚠️ note remove: no `' + type + '` note matched');
} else if (op.action === 'edit') {
const idx = notesArr.findIndex(function (n) { return String(n.type).toLowerCase() === type && n.text === op.text; });
if (idx !== -1) { notesArr[idx].text = String(op.newText || ''); notesChanged = true; lines.push('- note edit `' + type + '`'); }
else lines.push('- ⚠️ note edit: no `' + type + '` note matched');
}
} else if (op.kind === 'method') {
const type = String(op.type || '').toLowerCase();
const changes = op.changes || {};
if (op.action === 'remove') {
const before = methodsArr.length;
methodsArr = methodsArr.filter(function (im) { return String(im.type || '').toLowerCase() !== type; });
if (methodsArr.length !== before) { methodsChanged = true; lines.push('- method remove `' + type + '`'); }
else lines.push('- ⚠️ method remove: `' + type + '` not found');
} else {
let method = methodsArr.find(function (im) { return String(im.type || '').toLowerCase() === type; });
if (!method && op.action === 'add') { method = { type, resources: { cpu: 1, ram: 512, hdd: 4, os: 'debian', version: '13' } }; methodsArr.push(method); }
if (!method) { lines.push('- ⚠️ method edit: `' + type + '` not found'); continue; }
if (!method.resources) method.resources = {};
for (const [k, v] of Object.entries(changes)) {
if (RESOURCE_KEYS[k]) {
method.resources[k] = RESOURCE_KEYS[k] === 'number' ? parseInt(v, 10) : String(v);
if (RESOURCE_TO_CT_VAR[k]) ctChanges[RESOURCE_TO_CT_VAR[k]] = method.resources[k];
} else if (METHOD_KEYS[k]) {
method[k] = v === '' ? null : String(v);
}
}
methodsChanged = true;
lines.push('- method `' + (op.action === 'add' ? 'add' : 'edit') + '` `' + type + '`: ' + JSON.stringify(changes));
}
}
}
if (Object.keys(fieldPayload).length) {
const r = await pbPatch(token, record.id, fieldPayload);
if (!r.ok) return { ok: false, summary: '❌ Field update failed:\n```\n' + r.body + '\n```' };
}
if (notesChanged) {
const r = await pbPatch(token, record.id, { notes: notesArr });
if (!r.ok) return { ok: false, summary: '❌ Notes update failed:\n```\n' + r.body + '\n```' };
}
if (methodsChanged) {
const r = await pbPatch(token, record.id, { install_methods: methodsArr });
if (!r.ok) return { ok: false, summary: '❌ Install-method update failed:\n```\n' + r.body + '\n```' };
}
await revalidate(action.slug);
let ctNote = '';
if (Object.keys(ctChanges).length) {
try {
const sync = await upsertCtDefaultsPr(action.slug, ctChanges);
if (sync.status === 'created') ctNote = '\n\n**CT sync PR:** ' + sync.prUrl;
else if (sync.status === 'updated') ctNote = '\n\n**CT sync PR updated:** ' + sync.prUrl;
else if (sync.status === 'skipped') ctNote = '\n\n**CT sync skipped:** ' + sync.reason;
} catch (e) { ctNote = '\n\n**CT sync failed:** ' + e.message; }
}
return { ok: true, summary: lines.join('\n') + ctNote };
}
// ── 4. Confirm branch ──────────────────────────────────────────────
const PENDING_RE = /<!--\s*pocketbase-pending:\s*([A-Za-z0-9+/=]+)\s*-->/;
const isConfirm = /^(confirm|yes|apply|do it|y)\b/i.test(instruction);
if (isConfirm) {
const comments = await listIssueComments();
const appId = String(process.env.PB_BOT_APP_ID || '');
let pending = null, pendingComment = null;
for (let i = comments.length - 1; i >= 0; i--) {
const c = comments[i];
// Only trust a marker in a comment THIS bot app authored — otherwise a
// user could hand-craft a forged pocketbase-pending marker and confirm it.
const byBotApp = c.user && c.user.type === 'Bot' &&
c.performed_via_github_app && String(c.performed_via_github_app.id) === appId;
if (!byBotApp) continue;
const m = c.body && c.body.match(PENDING_RE);
if (m) { pending = m[1]; pendingComment = c; break; }
}
if (!pending) {
await addReaction('confused');
await postComment('🤔 **PocketBase AI Bot**: I have no pending change to confirm in this thread.');
return;
}
let action;
try { action = JSON.parse(Buffer.from(pending, 'base64').toString('utf8')); }
catch (e) { await postComment('❌ **PocketBase AI Bot**: Could not decode the pending change.'); return; }
// Re-validate the decoded operations before applying (defense-in-depth).
const recheck = sanitizeOperations(action.operations);
if (!action.slug || recheck.validOps.length === 0) {
await addReaction('-1');
await postComment('❌ **PocketBase AI Bot**: The pending change is no longer valid. Please restate the request.');
return;
}
action.operations = recheck.validOps;
let result;
try { result = await applyOperations(action); }
catch (e) { await addReaction('-1'); await postComment('❌ **PocketBase AI Bot**: ' + e.message); return; }
if (!result.ok) { await addReaction('-1'); await postComment(result.summary); return; }
await updateComment(pendingComment.id, pendingComment.body.replace(PENDING_RE, '<!-- pocketbase-applied -->'));
await addReaction('+1');
await postComment(
'✅ **PocketBase AI Bot**: Applied to **`' + action.slug + '`**\n\n' + result.summary +
'\n\n*Confirmed by @' + actor + '*'
);
return;
}
// ── 5. New request: acknowledge, fetch script list, call the model ─
await addReaction('eyes');
const token0 = await pbAuth();
const scripts = [];
let page = 1;
while (page <= 5) {
const res = await request(recordsUrl + '?fields=slug,name&perPage=500&page=' + page, { headers: { 'Authorization': token0 } });
if (!res.ok) break;
const data = JSON.parse(res.body);
(data.items || []).forEach(function (it) { if (it.slug) scripts.push({ slug: it.slug, name: it.name || it.slug }); });
if (!data.items || data.items.length < 500) break;
page++;
}
const SYSTEM_PROMPT =
'You translate a maintainer\'s natural-language request into a STRICT JSON change-set for a ' +
'script catalog (PocketBase). Respond with a SINGLE JSON object and nothing else.\n\n' +
'Schema:\n' +
'{\n' +
' "slug": string|null, // MUST be one of the known slugs below, chosen from the request\n' +
' "operations": [ ... ], // [] if you cannot determine concrete changes\n' +
' "human_summary": string, // short plain-English description\n' +
' "clarification": string|null // a question to ask if ambiguous/unsupported; else null\n' +
'}\n\n' +
'Operation kinds:\n' +
'- {"kind":"field","field":<one of: ' + Object.keys(ALLOWED_FIELDS).join(', ') + '>,"value":<bool|number|string>}\n' +
' (booleans true/false; "is_disabled"/"is_deleted"/"is_dev" are booleans; "port"/"unprivileged" are numbers; rest strings.)\n' +
'- {"kind":"note","action":"add"|"edit"|"remove","type":string,"text":string,"newText":string?}\n' +
'- {"kind":"method","action":"add"|"edit"|"remove","type":string,"changes":{cpu?:number,ram?:number,hdd?:number,os?:string,version?:string,config_path?:string,script?:string}}\n' +
' (RAM/HDD are in MB/GB; method "type" defaults to "default" if the user does not name one.)\n\n' +
'Rules:\n' +
'- Only use fields/operations listed above. If the request needs something else, set clarification and operations=[].\n' +
'- "disable"/"enable" map to is_disabled true/false. If disabling and the user gave a reason, also set disable_message.\n' +
'- Resolve the target script to a slug from the list. If you cannot confidently match exactly one, set slug=null and ask via clarification.\n\n' +
'Known scripts (slug — name):\n' +
scripts.map(function (s) { return s.slug + ' — ' + s.name; }).join('\n');
const modelRes = await request('https://models.github.ai/inference/chat/completions', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + process.env.MODELS_TOKEN, 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
model: process.env.AI_MODEL || 'openai/gpt-4o',
temperature: 0.1,
response_format: { type: 'json_object' },
messages: [{ role: 'system', content: SYSTEM_PROMPT }, { role: 'user', content: instruction }]
})
});
if (!modelRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase AI Bot**: Model request failed (' + modelRes.statusCode + ').\n```\n' + modelRes.body.slice(0, 500) + '\n```');
return;
}
let parsed;
try {
const content = JSON.parse(modelRes.body).choices[0].message.content;
const cleaned = content.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/i, '').trim();
parsed = JSON.parse(cleaned);
} catch (e) {
await addReaction('-1');
await postComment('❌ **PocketBase AI Bot**: Could not parse the model response. Please rephrase.');
return;
}
// ── 6. Validate ────────────────────────────────────────────────────
const knownSlugs = new Set(scripts.map(function (s) { return s.slug; }));
const problems = [];
if (parsed.clarification) problems.push(parsed.clarification);
if (!parsed.slug || !knownSlugs.has(parsed.slug)) problems.push('I could not match the request to a known script.');
const sanitized = sanitizeOperations(parsed.operations);
const validOps = sanitized.validOps;
problems.push.apply(problems, sanitized.problems);
if (validOps.length === 0) problems.push('No concrete, supported change was found.');
if (problems.length) {
await addReaction('confused');
await postComment(
'🤔 **PocketBase AI Bot**: I need a bit more to act on that.\n\n- ' + problems.join('\n- ') +
'\n\nTry naming the script and the exact change, e.g. `@pocketbase-bot set RAM to 4096 on zigbee2mqtt`.'
);
return;
}
// ── 7. Propose (do NOT apply yet) ──────────────────────────────────
const action = { slug: parsed.slug, operations: validOps };
const bullets = validOps.map(function (op) {
if (op.kind === 'field') return '- `' + op.field + '` → `' + JSON.stringify(op.value) + '`';
if (op.kind === 'note') return '- note ' + op.action + ' `' + op.type + '`' + (op.text ? ': ' + op.text : '');
return '- method ' + op.action + ' `' + op.type + '`: ' + JSON.stringify(op.changes);
}).join('\n');
const marker = '<!-- pocketbase-pending: ' + Buffer.from(JSON.stringify(action), 'utf8').toString('base64') + ' -->';
await addReaction('+1');
await postComment(
'🤖 **PocketBase AI Bot** — please confirm\n\n' +
(parsed.human_summary ? '> ' + parsed.human_summary + '\n\n' : '') +
'**Target:** `' + action.slug + '`\n**Proposed changes:**\n' + bullets + '\n\n' +
'Reply **`@pocketbase-bot confirm`** to apply, or restate the request to adjust.\n' + marker
);
})().catch(function (e) {
console.error('Fatal error:', e && (e.message || e));
process.exit(1);
});
ENDSCRIPT
-5
View File
@@ -343,7 +343,6 @@ jobs:
'`cpu` `ram` `hdd` `os` `version` also work without `method` — they go to the\n' +
'default install method, e.g. `/pocketbase <slug> hdd=25`\n\n' +
'**Editable fields:** `name` `description` `logo` `documentation` `website` `project_url` `repository` ' +
'`oidc_url` `license` ' +
'`config_path` `port` `default_user` `default_passwd` ' +
'`updateable` `privileged` `is_dev` `has_arm` ' +
'`architectures` (amd64,arm64) `platforms` (pve,incus) ' +
@@ -545,7 +544,6 @@ jobs:
out.push('- **Port:** ' + (record.port != null ? '`' + record.port + '`' : '—'));
out.push('- **Updateable:** ' + (record.updateable ? 'Yes' : 'No'));
out.push('- **Privileged:** ' + (record.privileged ? 'Yes' : 'No'));
out.push('- **License:** ' + (record.license || '—'));
out.push('- **Architectures:** ' + ((record.architectures || []).join(', ') || 'amd64'));
out.push('- **Platforms:** ' + ((record.platforms || []).join(', ') || 'pve'));
if (record.is_dev) out.push('- **Dev:** Yes');
@@ -556,7 +554,6 @@ jobs:
out.push('**Links:**');
out.push('- **Website:** ' + (record.website || '—'));
out.push('- **Docs:** ' + (record.documentation || '—'));
out.push('- **OIDC docs:** ' + (record.oidc_url || '—'));
out.push('- **Logo:** ' + (record.logo ? '[link](' + record.logo + ')' : '—'));
out.push('- **GitHub:** ' + (record.github || '—'));
if (record.config_path) out.push('- **Config:** `' + record.config_path + '`');
@@ -988,8 +985,6 @@ jobs:
logo: 'string',
documentation: 'string',
website: 'string',
oidc_url: 'string',
license: 'string',
project_url: 'string',
repository: 'string',
config_path: 'string',
-475
View File
@@ -1,475 +0,0 @@
name: PR script test
# Builds the ct/ scripts a pull request touches on a real Proxmox host and
# reports the outcome on the PR, as a check run and as one comment kept up to
# date.
#
# A self-hosted runner that executes pull request code as root is only
# acceptable if a human decides what runs on it. Nothing here starts on a push:
# someone with write access comments `/test` (optionally followed by a full SHA
# to pin, and the ct/ paths to build). Only a commit that existed when the
# comment was written gets built: a push after it is never covered, not even
# one that lands in the seconds before this job runs.
#
# gate ubuntu-latest who asked, which scripts, which commit
# test proxmox-test build + checks, one matrix job per script/OS
# report ubuntu-latest check run + PR comment
#
# The test job gets a read-only token and never sees a secret: the host it runs
# on executes untrusted code and has to be treated as compromised.
#
# This file is identical in DevScripts and ProxmoxVE; github.repository decides
# which URLs get rewritten and which container ID range is used.
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr:
description: "Pull request number"
required: true
type: number
sha:
description: "Pin a commit (full 40-character SHA); empty = the latest, if nothing was pushed since"
required: false
type: string
scripts:
description: "Space-separated ct/ paths (empty = everything the PR touches)"
required: false
type: string
permissions: {}
jobs:
gate:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.issue.pull_request && startsWith(github.event.comment.body, '/test'))
runs-on: ubuntu-latest
permissions:
actions: read # when the run was dispatched, for a /test without SHA
checks: write
contents: read
issues: write
pull-requests: write
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
pr: ${{ steps.plan.outputs.pr }}
repo: ${{ steps.plan.outputs.repo }}
sha: ${{ steps.plan.outputs.sha }}
check_id: ${{ steps.plan.outputs.check_id }}
actor: ${{ steps.plan.outputs.actor }}
steps:
- id: plan
uses: actions/github-script@v8
with:
script: |
const { owner, repo } = context.repo;
const MAX = 10;
core.setOutput('matrix', '[]');
let prNumber, requested = '';
if (context.eventName === 'workflow_dispatch') {
prNumber = Number(context.payload.inputs.pr);
requested = `${context.payload.inputs.sha || ''} ${context.payload.inputs.scripts || ''}`;
} else {
prNumber = context.payload.issue.number;
const body = context.payload.comment.body.trim();
if (!/^\/test(\s|$)/.test(body)) return;
// author_association hides private org members, so ask for the permission itself.
const user = context.payload.comment.user.login;
const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: user });
if (!['admin', 'maintain', 'write'].includes(perm.permission)) {
core.notice(`${user} has ${perm.permission} access; /test needs write`);
return;
}
await github.rest.reactions.createForIssueComment({
owner, repo, comment_id: context.payload.comment.id, content: 'rocket',
});
requested = body.split('\n')[0].slice('/test'.length);
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
if (pr.state !== 'open' || !pr.head.repo) { core.notice('PR is closed or its fork is gone'); return; }
const head = pr.head.repo;
const MARK = '<!-- pr-script-test -->';
const sticky = async body => {
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: prNumber });
// Only our own comment: anyone can paste the marker into theirs.
const mine = comments.find(c => c.body.includes(MARK) && c.user.type === 'Bot');
if (mine) await github.rest.issues.updateComment({ owner, repo, comment_id: mine.id, body });
else await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
};
// Which commit the approval covers. Reading pr.head.sha alone would
// build whatever was pushed between the comment and this job
// starting, i.e. code nobody looked at. Two ways to be sure:
//
// /test <sha> that exact commit, and only while it is the head. A
// full SHA: the author writes both commits, so a short
// prefix is a quick birthday collision away.
// /test the latest commit, if nothing reached the fork after
// the comment. pushed_at is set by GitHub on every push
// and read after the PR, so a push in between shows up.
// Branches of this repository belong to people with
// write access, who could build anything anyway.
const tokens = requested.split(/\s+/).filter(Boolean);
const isSha = t => /^[0-9a-f]{7,40}$/.test(t);
const approved = tokens.filter(isSha);
let entries = tokens.filter(t => !isSha(t));
const refuse = async (why, retry) => {
await sticky([MARK, '### Proxmox script test', '', `⚠️ ${why} Nothing was built.`, '', retry].join('\n'));
core.notice(why);
};
if (approved.length > 1) {
await refuse('`/test` takes at most one commit.', 'Comment `/test` to build the latest commit.');
return;
}
if (approved.length === 1 && approved[0] !== pr.head.sha) {
await refuse(
approved[0].length !== 40 && pr.head.sha.startsWith(approved[0])
? 'A short SHA is not enough, it has to be all 40 characters.'
: `\`${approved[0]}\` is not the head of this PR (any more).`,
'Comment `/test` to build the latest commit.');
return;
}
if (approved.length === 0 && head.full_name !== `${owner}/${repo}`) {
const askedAt = context.eventName === 'workflow_dispatch'
? (await github.rest.actions.getWorkflowRun({ owner, repo, run_id: context.runId })).data.created_at
: context.payload.comment.created_at;
const { data: fork } = await github.rest.repos.get({ owner: head.owner.login, repo: head.name });
if (new Date(fork.pushed_at) >= new Date(askedAt)) {
await refuse(
`Something was pushed to ${head.full_name} after the \`/test\`, so it is not certain which commit was meant.`,
'Comment `/test` again once you have looked at the latest commit.');
return;
}
}
if (entries.some(e => !/^ct\/[A-Za-z0-9._-]+\.sh$/.test(e))) {
core.setFailed(`Only ct/<name>.sh paths can be requested, got: ${requested}`);
return;
}
if (entries.length === 0) {
const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: prNumber });
const set = new Set();
for (const f of files) {
if (f.status === 'removed') continue;
let m;
if ((m = f.filename.match(/^install\/([A-Za-z0-9._-]+)-install\.sh$/))) set.add(`ct/${m[1]}.sh`);
else if (/^ct\/[A-Za-z0-9._-]+\.sh$/.test(f.filename)) set.add(f.filename);
}
entries = [...set].sort();
}
const include = [];
for (const script of entries.slice(0, MAX)) {
let text;
try {
const res = await github.rest.repos.getContent({ owner: head.owner.login, repo: head.name, path: script, ref: pr.head.sha });
text = Buffer.from(res.data.content || '', 'base64').toString('utf8');
} catch (e) { continue; } // install script without a ct/ entry point
// Scripts that ask for the OS get built once per OS.
const oses = /msg_menu\s+"Choose the container OS"/.test(text) ? ['debian', 'alpine'] : [''];
const name = script.slice(3, -3);
for (const os of oses) include.push({ script, os, name: os ? `${name}-${os}` : name });
}
if (entries.length > MAX) core.warning(`Only the first ${MAX} of ${entries.length} scripts are built`);
if (include.length === 0) { core.notice('No ct/ script to build'); return; }
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
const { data: check } = await github.rest.checks.create({
owner, repo, name: 'Proxmox script test', head_sha: pr.head.sha, status: 'in_progress',
details_url: runUrl,
output: { title: `Building ${include.length} on Proxmox`, summary: include.map(i => `- \`${i.script}\` ${i.os}`).join('\n') },
});
core.setOutput('matrix', JSON.stringify(include));
core.setOutput('pr', String(prNumber));
core.setOutput('repo', head.full_name);
core.setOutput('sha', pr.head.sha);
core.setOutput('check_id', String(check.id));
// Who asked: the /test commenter, or whoever dispatched the run.
core.setOutput('actor', context.actor);
await sticky([
MARK, '### Proxmox script test', '',
`⏳ Building ${include.length} at ${pr.head.sha.slice(0, 7)} — [watch it live](${runUrl}) (open the \`test\` job)`, '',
...include.map(i => `- \`${i.script}\`${i.os ? ` (${i.os})` : ''}`),
].join('\n'));
test:
needs: gate
if: needs.gate.outputs.matrix != '[]' && needs.gate.outputs.matrix != ''
# Only `proxmox-test`, never `self-hosted`: the runner is registered without
# default labels so that no other workflow can be scheduled onto it.
runs-on: proxmox-test
permissions:
contents: read
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.gate.outputs.matrix) }}
name: test (${{ matrix.name }})
steps:
# The default branch, never the PR: the harness itself must be trusted.
- uses: actions/checkout@v5
with:
sparse-checkout: .github/workflows/scripts
persist-credentials: false
- name: Build and check ${{ matrix.script }}
env:
PR_REPO: ${{ needs.gate.outputs.repo }}
PR_SHA: ${{ needs.gate.outputs.sha }}
SCRIPT: ${{ matrix.script }}
OS: ${{ matrix.os }}
UPSTREAM: ${{ github.repository }}
CTID_BASE: ${{ github.repository == 'community-scripts/ProxmoxVE' && '9050' || '9000' }}
OUT_DIR: ${{ runner.temp }}/result
run: bash .github/workflows/scripts/pve-run-script.sh
- if: always()
uses: actions/upload-artifact@v4
with:
name: result-${{ matrix.name }}
path: ${{ runner.temp }}/result
if-no-files-found: ignore
retention-days: 14
report:
needs: [gate, test]
if: always() && needs.gate.result == 'success' && needs.gate.outputs.matrix != '[]' && needs.gate.outputs.matrix != ''
runs-on: ubuntu-latest
permissions:
actions: read
checks: write
contents: write # the ci-screenshots branch only; main is protected
issues: write
pull-requests: write
steps:
- uses: actions/download-artifact@v5
with:
pattern: result-*
path: results
- uses: actions/github-script@v8
env:
MATRIX: ${{ needs.gate.outputs.matrix }}
PR: ${{ needs.gate.outputs.pr }}
SHA: ${{ needs.gate.outputs.sha }}
CHECK_ID: ${{ needs.gate.outputs.check_id }}
TEST_RESULT: ${{ needs.test.result }}
ACTOR: ${{ needs.gate.outputs.actor }}
with:
script: |
const fs = require('fs');
const { owner, repo } = context.repo;
const runUrl = `${context.serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;
const matrix = JSON.parse(process.env.MATRIX);
// download-artifact unpacks a single artifact straight into results/
// and several into results/<name>/, so look for every result.json
// and match it by what it says it is.
const path = require('path');
const found = new Map();
const walk = dir => {
if (!fs.existsSync(dir)) return;
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walk(p);
else if (e.name === 'result.json') {
try {
const r = JSON.parse(fs.readFileSync(p, 'utf8'));
// Written on the host that ran the PR's code: anything but a
// known status is an error, never something new.
if (!['pass', 'fail', 'unsupported', 'error'].includes(r.status)) r.status = 'error';
const log = path.join(dir, 'install.log');
r.install_log = fs.existsSync(log) ? fs.readFileSync(log, 'utf8') : '';
const shot = path.join(dir, 'screenshot.png');
r.shot_file = fs.existsSync(shot) ? shot : '';
found.set(`${r.script}|${r.os}`, r);
} catch (err) { core.warning(`unreadable ${p}: ${err.message}`); }
}
}
};
walk('results');
// A job that died before writing result.json still gets a row.
const rows = matrix.map(m => ({ ...(found.get(`${m.script}|${m.os || 'default'}`) || {
script: m.script, os: m.os || 'default',
status: process.env.TEST_RESULT === 'cancelled' ? 'cancelled' : 'error',
step: 'no result (runner or job failure)', duration: 0, log_tail: '', install_log: '', shot_file: '',
}), name: m.name }));
// Screenshots go to an orphan branch, because a comment can only show
// an image that has a URL. One file per PR and script, overwritten on
// every run; the link names the commit, so no cache shows an old one.
// The PNG comes from the host that ran the PR's code: check it again.
const BRANCH = 'ci-screenshots';
const prNum = Number(process.env.PR);
const isPng = buf => buf.length > 8 && buf.length < 3000000 &&
buf.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
const ensureBranch = async () => {
try { await github.rest.git.getRef({ owner, repo, ref: `heads/${BRANCH}` }); return; }
catch (e) { if (e.status !== 404) throw e; }
const { data: blob } = await github.rest.git.createBlob({ owner, repo, encoding: 'utf-8',
content: 'Screenshots taken by pr-script-test.yml for the PR comments. Not code; never merge this branch.\n' });
const { data: tree } = await github.rest.git.createTree({ owner, repo, tree: [{ path: 'README.md', mode: '100644', type: 'blob', sha: blob.sha }] });
const { data: commit } = await github.rest.git.createCommit({ owner, repo, message: 'Start ci-screenshots', tree: tree.sha, parents: [] });
await github.rest.git.createRef({ owner, repo, ref: `refs/heads/${BRANCH}`, sha: commit.sha });
};
for (const r of rows.filter(r => r.shot_file)) {
const buf = fs.readFileSync(r.shot_file);
if (!isPng(buf)) { core.warning(`${r.script}: screenshot is not a plausible PNG, skipped`); continue; }
const file = `pr-${prNum}/${r.name}.png`;
for (let attempt = 1; attempt <= 3 && !r.shot_url; attempt++) {
try {
await ensureBranch();
let sha;
try { sha = (await github.rest.repos.getContent({ owner, repo, path: file, ref: BRANCH })).data.sha; }
catch (e) { if (e.status !== 404) throw e; }
const { data } = await github.rest.repos.createOrUpdateFileContents({
owner, repo, branch: BRANCH, path: file, sha, content: buf.toString('base64'),
message: `Screenshot: ${r.script} (${r.os}) for #${prNum} at ${process.env.SHA.slice(0, 7)}`,
});
r.shot_url = `https://raw.githubusercontent.com/${owner}/${repo}/${data.commit.sha}/${file}`;
} catch (e) {
// Two reports at once race on the branch head; try again.
core.warning(`${r.script}: screenshot upload attempt ${attempt} failed: ${e.message}`);
await new Promise(res => setTimeout(res, 2000 * attempt));
}
}
}
// unsupported: the script needs a CPU feature the test host lacks
// (AVX2 and the like). Not the PR's fault, so not a failure.
const icon = { pass: '✅', fail: '❌', error: '⚠️', cancelled: '⏹️', unsupported: '🖥️' };
const dur = s => `${Math.floor(s / 60)}m${String(s % 60).padStart(2, '0')}s`;
// Text from the build lands outside code blocks here: no table breaks,
// no code spans, and no @mention of a stranger (zero-width space).
const clean = s => String(s || '').replace(/[|\r\n]/g, ' ').replace(/`/g, "'").replace(/@/g, '@​').slice(0, 200);
const fence = s => String(s || '').replace(/~~~~/g, '~~~');
// fail: the app broke, the PR's business. error/cancelled: our test
// setup did, not the PR's. unsupported: the host's CPU is too old.
const count = s => rows.filter(r => r.status === s).length;
const passed = count('pass');
const untestable = count('unsupported');
const broken = count('fail');
const setupTrouble = count('error') + count('cancelled');
const allPass = passed === rows.length;
const cancelled = rows.every(r => r.status === 'cancelled');
const of = `**${passed}/${rows.length} passed**`;
const headline = broken
? `❌ ${of}`
: setupTrouble
? `⚠️ ${of}, ${setupTrouble} could not be tested because of a problem with the test setup (not the PR)`
: untestable
? `🖥️ ${of}, ${untestable} need${untestable === 1 ? 's' : ''} a newer CPU than the test host has (nothing failed)`
: `✅ ${of}`;
const table = [
'| Script | OS | Result | Duration | Detail |',
'|---|---|---|---|---|',
...rows.map(r => `| \`${r.script}\` | ${r.os} | ${icon[r.status] || '❔'} | ${dur(r.duration || 0)} | ${r.status === 'pass' ? (r.url ? `${clean(r.url)} → ${r.http_code}` : 'no URL to check') : clean(r.step)} |`),
].join('\n');
// A comment holds 65536 characters. Each script gets an equal share
// and a log that does not fit loses its beginning: the end is where
// a failure shows.
const budget = Math.floor(52000 / rows.length);
const tail = (text, max) => {
text = fence(text).trimEnd();
if (text.length <= max) return text;
const kept = text.slice(text.length - max);
const cut = text.slice(0, text.length - max).split('\n').length;
return `… ${cut} earlier lines cut, the full log is in the run artifacts\n${kept.slice(kept.indexOf('\n') + 1)}`;
};
const details = (summaryLine, text, max) => [
`<details><summary>${summaryLine}</summary>`, '',
'~~~~text', tail(text, max), '~~~~', '</details>',
].join('\n');
const logs = rows.map(r => {
const parts = [];
const failed = r.status !== 'pass';
const buildShare = failed && r.log_tail ? Math.min(r.log_tail.length, Math.floor(budget / 3)) : 0;
if (buildShare) parts.push(details(`${icon[r.status] || '❔'} ${r.script} (${r.os}) — build log`, r.log_tail, buildShare));
if (r.install_log) {
const lines = r.install_log.trimEnd().split('\n').length;
parts.push(details(`📄 ${r.script} (${r.os}) — install log (${lines} lines)`, r.install_log, budget - buildShare - 300));
} else if (!buildShare) {
parts.push(`<sub>${r.script} (${r.os}): no install log was captured</sub>`);
}
return parts.join('\n\n');
});
const shots = rows.filter(r => r.shot_url).map(r => [
`<details open><summary>📸 ${r.script} (${r.os}) — ${clean(r.url)}</summary>`, '',
`<img src="${r.shot_url}" width="640" alt="Screenshot of ${r.script}">`, '', '</details>',
].join('\n'));
const summary = [
`${headline} at \`${process.env.SHA.slice(0, 7)}\``, '',
table, '',
...shots, '',
...logs, '',
`Full logs: [workflow run](${runUrl}) (artifacts \`result-*\`). Comment \`/test\` to build again.`,
].join('\n');
await github.rest.checks.update({
owner, repo, check_run_id: Number(process.env.CHECK_ID), status: 'completed',
// Untestable here is "test it by hand", not a pass: action_required,
// because a required check would count neutral as passed.
conclusion: cancelled ? 'cancelled' : allPass ? 'success' : broken ? 'failure' : 'action_required',
output: { title: `${passed}/${rows.length} passed`, summary: summary.slice(0, 65000) },
});
// The result pings whoever started the test. A mention added by
// editing a comment does not reliably notify, so the old comment
// goes and the result is posted as a new one, at the end of the
// thread where it is looked for.
// Whoever started the test and the PR's author, once each; no bots.
const pr = Number(process.env.PR);
const { data: prInfo } = await github.rest.pulls.get({ owner, repo, pull_number: pr });
const author = prInfo.user && prInfo.user.type === 'User' ? prInfo.user.login : '';
const login = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;
const ping = [...new Set([process.env.ACTOR || '', author])].filter(l => login.test(l)).map(l => `@${l} `).join('');
const MARK = '<!-- pr-script-test -->';
const body = `${MARK}\n### Proxmox script test\n\n${ping}${summary}`.slice(0, 65000);
const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: pr });
for (const c of comments.filter(c => c.body.includes(MARK) && c.user.type === 'Bot')) {
await github.rest.issues.deleteComment({ owner, repo, comment_id: c.id });
}
const { data: posted } = await github.rest.issues.createComment({ owner, repo, issue_number: pr, body });
// A real failure requests changes on the tested commit. Untestable
// (CPU), runner errors and cancelled runs are not the PR's fault and
// leave the review state alone. Our previous "changes requested" is
// always dismissed first: a pass lifts it, a new failure replaces it.
const REVIEW_MARK = '<!-- pr-script-test-review -->';
const failedRows = cancelled ? [] : rows.filter(r => r.status === 'fail');
const sha7 = process.env.SHA.slice(0, 7);
try {
const reviews = await github.paginate(github.rest.pulls.listReviews, { owner, repo, pull_number: pr });
for (const v of reviews.filter(v => v.user && v.user.type === 'Bot' && v.state === 'CHANGES_REQUESTED' && (v.body || '').includes(REVIEW_MARK))) {
await github.rest.pulls.dismissReview({
owner, repo, pull_number: pr, review_id: v.id,
message: failedRows.length ? 'Replaced by a newer Proxmox script test.' : `Proxmox script test passed at ${sha7}.`,
});
}
if (failedRows.length) {
await github.rest.pulls.createReview({
owner, repo, pull_number: pr, commit_id: process.env.SHA, event: 'REQUEST_CHANGES',
body: [
REVIEW_MARK, `**Proxmox script test failed** at \`${sha7}\`:`, '',
...failedRows.map(r => `- \`${r.script}\` (${r.os}): ${clean(r.step)}`), '',
`Logs and details: [test result](${posted.html_url}). Push a fix, then ask for \`/test\` again.`,
].join('\n'),
});
}
} catch (e) {
core.warning(`Could not update the review state: ${e.message}`);
}
+7 -63
View File
@@ -2,8 +2,7 @@ name: PR test command
# Posts a ready-to-run test command for reviewers.
# It uses this PR’s script branch with the production engine, since both resolve
# independently. ct/ scripts need community-scripts/core; vm/, tools/ and
# turnkey/ scripts run straight from the branch.
# independently. Only scripts using community-scripts/core are supported.
#
# pull_request_target allows comments on fork PRs. No PR code is checked out or
# executed; API inputs are validated and the comment is assembled in JavaScript.
@@ -15,9 +14,6 @@ on:
paths:
- "ct/**"
- "install/**"
- "vm/**"
- "tools/**"
- "turnkey/**"
jobs:
comment:
@@ -54,43 +50,14 @@ jobs:
// ct/foo.sh and install/foo-install.sh are the same app. A removed
// file has nothing left to run.
const apps = new Map(); // slug -> {ct, install}
const others = [];
for (const f of files) {
if (f.status === 'removed') continue;
let m = f.filename.match(/^ct\/([a-z0-9][a-z0-9._-]*)\.sh$/);
if (m) { apps.set(m[1], { ...apps.get(m[1]), ct: true }); continue; }
m = f.filename.match(/^install\/([a-z0-9][a-z0-9._-]*)-install\.sh$/);
if (m) { apps.set(m[1], { ...apps.get(m[1]), install: true }); continue; }
if (/^(vm|tools|turnkey)\/[A-Za-z0-9._\/-]+\.sh$/.test(f.filename)) others.push(f.filename);
if (m) apps.set(m[1], { ...apps.get(m[1]), install: true });
}
if (apps.size === 0 && others.length === 0) return;
others.sort();
// Where a tool runs is read from the script itself; it is never run.
const HOST = /(^|[\s;&|(`$])(pct|qm|pveam|pvesm|pvesh|pveversion)\s/m;
async function runsOn(path) {
if (path.startsWith('vm/')) return 'pve';
const appliance = (path.split('/').pop().match(/(?:^|-)(pbs|pmg|pdm)(?=[0-9-]|\.sh$)/) || [])[1];
if (path.startsWith('tools/pve/')) return appliance || 'pve';
let text = '';
try {
const res = await github.rest.repos.getContent({
owner: head.owner.login, repo: head.name, path, ref: pr.head.sha,
});
text = Buffer.from(res.data.content || '', 'base64').toString('utf8');
} catch (e) {}
if (HOST.test(text.replace(/^\s*#.*$/gm, '').replace(/command -v \S+/g, ''))) return 'pve';
if (appliance) return appliance;
return path.startsWith('tools/addon/') ? 'guest' : 'unknown';
}
const WHERE = {
pve: 'in the Proxmox VE shell',
guest: 'inside the LXC or VM that should get it',
pbs: 'in the Proxmox Backup Server shell',
pmg: 'in the Proxmox Mail Gateway shell',
pdm: 'in the Proxmox Datacenter Manager shell',
unknown: 'wherever the script is meant to run',
};
if (apps.size === 0) return;
// Read the ct script at the PR head to see which engine it loads.
// Read only -- it is never sourced or run.
@@ -144,9 +111,10 @@ jobs:
);
}
lines.push(
'Both lines are needed. Without `COMMUNITY_SCRIPTS_URL` the engine falls back to',
'`main`, so curling the branch URL on its own gives you the `ct/` script from',
'this PR and the `install/` script from `main`. Frequently the one you meant to test.',
'Both lines are needed. Each script pins `_CS_DEFAULT_URL` to `main`, and that',
'pin is what fills `COMMUNITY_SCRIPTS_URL` when the variable is unset — so',
'curling the branch URL on its own gives you the `ct/` script from this PR and',
'the `install/` script from `main`. Frequently the one you meant to test.',
'',
'The same command works on an Incus host: the engine detects the platform and',
'loads the matching backend, while the scripts still come from this branch.',
@@ -165,30 +133,6 @@ jobs:
);
}
if (others.length > 0) {
const shown = others.slice(0, MAX_APPS);
lines.push(...(ready.length > 0 ? ['', '---', ''] : ['### Try this branch', '']));
for (const path of shown) {
lines.push(
`\`${path}\`, run ${WHERE[await runsOn(path)]}:`,
'```bash',
`bash -c "$(curl -fsSL "${base}/${path}")"`,
'```',
'',
);
}
if (others.length > shown.length) {
lines.push(
`${others.length - shown.length} more script(s) changed; same command, different path.`,
'',
);
}
lines.push(
'Only the script itself comes from this branch. The engine it loads still',
'comes from community-scripts/core on `main`.',
);
}
if (legacy.length > 0) {
lines.push(
'',
-141
View File
@@ -1,141 +0,0 @@
name: Request Script Eligibility Check
# category.slug is the same string GitHub uses in the "new discussion" URL
# (?category=request-script, linked from every repo's issue chooser), so this
# guard matches the live category without needing its numeric id.
on:
discussion:
types: [created, edited, reopened]
jobs:
check-eligibility:
if: github.repository == 'community-scripts/ProxmoxVE' && github.event.discussion.category.slug == 'request-script'
runs-on: ubuntu-latest
permissions:
discussions: write
issues: write
contents: read
steps:
- name: Verify stars/age, flag or close
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const discussion = context.payload.discussion;
if (!discussion || discussion.state === "closed") {
core.info("Discussion missing or already closed — skipping.");
return;
}
const body = discussion.body || "";
const lines = body.split("\n");
const fields = {};
let current = null;
for (const line of lines) {
const m = line.match(/^###\s+(.+?)\s*$/);
if (m) {
current = m[1].trim();
fields[current] = [];
} else if (current) {
fields[current].push(line);
}
}
function fieldText(label) {
const raw = (fields[label] || []).join("\n").trim();
return raw === "_No response_" ? "" : raw;
}
const sourceRepo = fieldText("Source Repository");
const justification = fieldText("Justification for an exception");
let eligible = false;
let verified = false;
const ghMatch = sourceRepo.match(/^https?:\/\/(?:www\.)?github\.com\/([^\/\s]+)\/([^\/\s#?]+?)(?:\.git)?\/?(?:[#?].*)?$/i);
if (ghMatch) {
const [, repoOwner, repoName] = ghMatch;
try {
const { data: repoData } = await github.rest.repos.get({ owner: repoOwner, repo: repoName });
const stars = repoData.stargazers_count;
const createdAt = new Date(repoData.created_at);
const now = new Date();
const ageMonths =
(now.getFullYear() - createdAt.getFullYear()) * 12 +
(now.getMonth() - createdAt.getMonth()) -
(now.getDate() < createdAt.getDate() ? 1 : 0);
eligible = stars >= 1000 && ageMonths >= 6;
verified = true;
core.info(`Verified ${repoOwner}/${repoName}: ${stars} stars, ${ageMonths} months old, eligible=${eligible}`);
} catch (err) {
core.warning(`Could not verify ${repoOwner}/${repoName}: ${err.message} — treating as unverifiable.`);
}
} else {
core.info("Source Repository is not a github.com URL — cannot auto-verify, treating as unverifiable.");
}
if (eligible) {
core.info(`Discussion #${discussion.number} meets the requirements — no action.`);
return;
}
const labels = (discussion.labels || []).map(l => l.name);
const reasonLine = verified
? "the source repository does not meet the 1,000-star / 6-month minimum adoption requirement"
: "the source repository's star count and age could not be verified automatically";
if (justification) {
if (labels.includes("investigation")) {
core.info(`Discussion #${discussion.number} already flagged for investigation — skipping.`);
return;
}
try {
await github.rest.issues.createLabel({
owner, repo, name: "investigation",
color: "fbca04",
description: "Below the normal script-request threshold; a justification was given and needs maintainer review.",
});
} catch (err) {
if (err.status !== 422) throw err;
}
const labelQuery = await github.graphql(
`query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { label(name: "investigation") { id } } }`,
{ owner, repo }
);
const labelNodeId = labelQuery.repository.label.id;
await github.graphql(
`mutation($labelableId: ID!, $labelIds: [ID!]!) { addLabelsToLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) { clientMutationId } }`,
{ labelableId: discussion.node_id, labelIds: [labelNodeId] }
);
await github.graphql(
`mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { comment { id } } }`,
{
discussionId: discussion.node_id,
body:
`Thanks for the justification — ${reasonLine}, so this has been flagged \`investigation\` for a maintainer ` +
`to review manually rather than closed automatically.`,
}
);
} else {
await github.graphql(
`mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { comment { id } } }`,
{
discussionId: discussion.node_id,
body:
`This request doesn't meet the minimum requirements (1,000 stars, project at least 6 months old) — ` +
`${reasonLine}, and no justification was given. Closing automatically.\n\n` +
`If you believe this project qualifies for an exception, edit this discussion to fill in ` +
`**Justification for an exception** and reopen it — it will then be flagged for manual review instead of closed again.`,
}
);
await github.graphql(
`mutation($discussionId: ID!) { closeDiscussion(input: { discussionId: $discussionId, reason: OUTDATED }) { discussion { id } } }`,
{ discussionId: discussion.node_id }
);
}
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Function for generating Figlet headers
generate_headers() {
local base_dir=$1
local target_subdir=$2
local search_pattern=$3
local headers_dir="${base_dir}/headers"
mkdir -p "$headers_dir"
rm -f "$headers_dir"/*
# Recursive or non-recursive search
if [[ "$search_pattern" == "**" ]]; then
shopt -s globstar nullglob
file_list=("${base_dir}"/**/*.sh)
shopt -u globstar
else
file_list=("${base_dir}"/*.sh)
fi
for script in "${file_list[@]}"; do
[[ -f "$script" ]] || continue
# -m1: a script that sets APP twice (the second carrying ${var_version})
# otherwise yields both lines, and the filename gets a newline in it.
app_name=$(grep -m1 -oP '^APP="\K[^"]+' "$script" 2>/dev/null)
if [[ -n "$app_name" ]]; then
# core.func/vm-core.func look the header up as "${APP,,}" without spaces,
# so the generated file has to be named after APP - not after the script.
output_file="${headers_dir}/$(echo "${app_name,,}" | tr -d ' ')"
figlet_output=$(figlet -w 500 -f slant "$app_name")
if [[ -n "$figlet_output" ]]; then
echo "$figlet_output" >"$output_file"
echo "Generated: $output_file"
else
echo "Figlet failed for $app_name in $script"
fi
else
echo "No APP name found in $script, skipping."
fi
done
}
# ct
generate_headers "./ct" "headers" "*"
# tools (addon, pve, ...)
generate_headers "./tools" "headers" "**"
# vm
generate_headers "./vm" "headers" "*"
echo "Completed processing all sections."
-203
View File
@@ -1,203 +0,0 @@
#!/usr/bin/env bash
# One-time setup of the Proxmox test host behind pr-script-test.yml.
# Safe to re-run. Run as root on the test host (a disposable PVE, never a
# production node: it builds pull request code as root).
#
# pve-ci-host-setup.sh host only
# pve-ci-host-setup.sh <owner/repo> <token> host + a runner for that repo
#
# The token is a runner *registration* token (valid one hour):
# gh api -X POST repos/<owner/repo>/actions/runners/registration-token --jq .token
set -euo pipefail
RUNNER_VERSION="${RUNNER_VERSION:-2.338.2}"
SERVE_ROOT=/srv/ci
SERVE_PORT=18080
UPLINK="${UPLINK:-vmbr0}"
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq jq curl python3 dnsmasq iptables chromium fonts-dejavu fonts-noto-color-emoji >/dev/null
# Screenshots of the app under test are taken as this user, never as root.
id ci-shot &>/dev/null || useradd --system --create-home --shell /usr/sbin/nologin ci-shot
# The page Chromium renders is served by code under test, and a redirect or an
# <img> could point it at this host (the PVE UI on :8006) or the lab network;
# the screenshot would then publish that on the PR. So ci-shot may reach the
# test containers, DNS and the public internet (apps load fonts and scripts
# from CDNs), and nothing private.
cat >/usr/local/sbin/ci-shot-firewall <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
uid=$(id -u ci-shot)
for fw in iptables ip6tables; do
$fw -w -N CI_SHOT 2>/dev/null || $fw -w -F CI_SHOT
$fw -w -C OUTPUT -m owner --uid-owner "$uid" -j CI_SHOT 2>/dev/null ||
$fw -w -I OUTPUT -m owner --uid-owner "$uid" -j CI_SHOT
done
iptables -w -A CI_SHOT -d 10.99.0.1 -j REJECT
iptables -w -A CI_SHOT -d 10.99.0.0/24 -j ACCEPT
for ns in $(awk '/^nameserver/ && $2 !~ /:/ {print $2}' /etc/resolv.conf); do
iptables -w -A CI_SHOT -d "$ns" -p udp --dport 53 -j ACCEPT
iptables -w -A CI_SHOT -d "$ns" -p tcp --dport 53 -j ACCEPT
done
for net in 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/3; do
iptables -w -A CI_SHOT -d "$net" -j REJECT
done
for net in ::1/128 fc00::/7 fe80::/10 ff00::/8; do
ip6tables -w -A CI_SHOT -d "$net" -j REJECT
done
EOF
chmod +x /usr/local/sbin/ci-shot-firewall
cat >/etc/systemd/system/ci-shot-firewall.service <<'EOF'
[Unit]
Description=Keep the screenshot browser away from this host and the lab network
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/ci-shot-firewall
[Install]
WantedBy=multi-user.target
EOF
# Containers get their own NATed bridge instead of the lab network: no DHCP
# lease surprises (a lab lease once handed a container the host's own address),
# and an app listening on 8080 can never be confused with this host.
cat >/etc/network/interfaces.d/ci-nat <<EOF
auto vmbr1
iface vmbr1 inet static
address 10.99.0.1/24
bridge-ports none
bridge-stp off
bridge-fd 0
post-up sysctl -qw net.ipv4.ip_forward=1
post-up iptables -t nat -C POSTROUTING -s 10.99.0.0/24 -o ${UPLINK} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s 10.99.0.0/24 -o ${UPLINK} -j MASQUERADE
post-down iptables -t nat -D POSTROUTING -s 10.99.0.0/24 -o ${UPLINK} -j MASQUERADE || true
EOF
ifreload -a
cat >/etc/dnsmasq.d/ci.conf <<'EOF'
interface=vmbr1
bind-interfaces
except-interface=lo
dhcp-range=10.99.0.100,10.99.0.250,255.255.255.0,2h
dhcp-option=option:router,10.99.0.1
dhcp-option=option:dns-server,10.99.0.1
no-resolv
EOF
# Upstream = whatever this host resolves with. Debian's dnsmasq would otherwise
# ask resolvconf, which has nothing to offer on a PVE host.
awk '/^nameserver/ {print "server=" $2}' /etc/resolv.conf >>/etc/dnsmasq.d/ci.conf
systemctl enable dnsmasq >/dev/null
systemctl restart dnsmasq
# A PVE ISO install writes /etc/timezone; a cloud-image install does not, and
# the engine's pct_timezone aborts without it.
[[ -s /etc/timezone ]] || {
timedatectl set-timezone Europe/Vienna
echo Europe/Vienna >/etc/timezone
}
# Containers live on the root filesystem; there is no LVM in a cloud-image PVE.
pvesm set local --content iso,vztmpl,backup,rootdir,images
# Templates are fetched once here rather than on every first build.
pveam update >/dev/null
for t in $(pveam available --section system | awk '/_amd64\./ && /debian-13-standard|alpine-3\.2[0-9]-default|ubuntu-24\.04-standard/ {print $2}'); do
pveam list local | grep -q "$t" || pveam download local "$t" >/dev/null
done
# The rewritten PR trees, served to the engine and to the containers.
mkdir -p "$SERVE_ROOT"
cat >/etc/systemd/system/ci-serve.service <<EOF
[Unit]
Description=Serve pull request trees to script builds
After=network-online.target
[Service]
ExecStart=/usr/bin/python3 -m http.server ${SERVE_PORT} --bind 10.99.0.1 --directory ${SERVE_ROOT}
DynamicUser=yes
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Leftovers of a job that was killed before its cleanup trap ran.
cat >/usr/local/sbin/ci-sweep <<'EOF'
#!/usr/bin/env bash
# Destroy CI containers (9000-9099) that no running job holds, and stale trees.
for id in $(pct list | awk 'NR>1 && $1>=9000 && $1<=9099 {print $1}'); do
[[ -e "/run/ci-jobs/$id" ]] && continue
pct stop "$id" --skiplock 1 &>/dev/null || true
pct destroy "$id" --purge 1 --force 1 &>/dev/null && echo "swept CT $id"
done
find /srv/ci -mindepth 1 -maxdepth 1 -type d -mmin +180 -exec rm -rf {} +
# Engine logs that a killed job never got to remove.
find /tmp -maxdepth 1 -type f \( -name 'create-lxc-*.log' -o -name 'pct_create_*.log' \
-o -regex '/tmp/.*-9[0-9][0-9][0-9]-[0-9a-f]+\.log' \) -mmin +180 -delete
find /var/log/community-scripts -type f -mtime +2 -delete 2>/dev/null
find /opt/actions-runner/*/_diag -type f -mtime +14 -delete 2>/dev/null
true
EOF
chmod +x /usr/local/sbin/ci-sweep
cat >/etc/systemd/system/ci-sweep.service <<'EOF'
[Unit]
Description=Remove leftover CI containers
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ci-sweep
EOF
cat >/etc/systemd/system/ci-sweep.timer <<'EOF'
[Unit]
Description=Remove leftover CI containers every 3 hours
[Timer]
OnCalendar=0/3:30
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now ci-serve.service ci-sweep.timer ci-shot-firewall.service >/dev/null
systemctl restart ci-serve.service ci-shot-firewall.service
# --- Runner (one per repository) ----------------------------------------------
if [[ $# -eq 2 ]]; then
repo="$1"
token="$2"
[[ "$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "bad repo" >&2; exit 2; }
dir="/opt/actions-runner/${repo#*/}"
mkdir -p "$dir"
cd "$dir"
if [[ ! -x ./config.sh ]]; then
curl -fsSL "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" | tar -xz
./bin/installdependencies.sh >/dev/null
fi
# A registered runner is reconfigured from scratch: stop and remove the old
# registration (the token works for removal too).
if [[ -f .runner ]]; then
./svc.sh stop >/dev/null 2>&1 || true
./svc.sh uninstall >/dev/null 2>&1 || true
RUNNER_ALLOW_RUNASROOT=1 ./config.sh remove --token "$token" >/dev/null 2>&1 || true
fi
# Root, because pct needs it; the host is disposable by design.
# --no-default-labels: without it the runner also answers to `self-hosted`,
# and any other workflow with `runs-on: self-hosted` (and its secrets) could
# land on this host.
RUNNER_ALLOW_RUNASROOT=1 ./config.sh --unattended --replace --no-default-labels \
--url "https://github.com/${repo}" --token "$token" \
--name "pve-ci-${repo#*/}" --labels proxmox-test --work _work
./svc.sh install root >/dev/null
./svc.sh start >/dev/null
echo "runner for ${repo} registered"
fi
echo "test host ready: ${SERVE_ROOT} on :${SERVE_PORT}, sweeper every 3h"
-419
View File
@@ -1,419 +0,0 @@
#!/usr/bin/env bash
# Build one ct/ script from a pull request on this Proxmox host, check that the
# container really works, destroy it, and leave a result.json behind.
#
# Runs on the self-hosted runner, which *is* the disposable test host. The
# harness comes from the default branch; only the script under test comes from
# the pull request, pinned to the commit a maintainer approved.
#
# The engine hardcodes raw.githubusercontent.com/<upstream>/main for every fetch
# (ct -> build.func -> install script -> install.func -> tools.func). Pointing it
# at a fork branch therefore means rewriting those URLs: the PR tree is unpacked
# under SERVE_ROOT, every upstream main URL in it is replaced by this host's own
# HTTP server, and the containers fetch from there. Engine changes in the PR are
# tested along with the script, which is what a reviewer wants anyway.
set -uo pipefail
: "${PR_REPO:?}" "${PR_SHA:?}" "${SCRIPT:?}" "${UPSTREAM:?}" "${CTID_BASE:?}" "${OUT_DIR:?}"
OS="${OS:-}"
CTID_COUNT="${CTID_COUNT:-50}"
SERVE_ROOT="${SERVE_ROOT:-/srv/ci}"
SERVE_URL="${SERVE_URL:-http://10.99.0.1:18080}"
BRIDGE="${BRIDGE:-vmbr1}"
BUILD_TIMEOUT="${BUILD_TIMEOUT:-45m}"
URL_WAIT="${URL_WAIT:-120}"
# Every value below reaches a path, a URL or a command line.
[[ "$PR_REPO" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "bad PR_REPO" >&2; exit 2; }
[[ "$UPSTREAM" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || { echo "bad UPSTREAM" >&2; exit 2; }
[[ "$PR_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "bad PR_SHA" >&2; exit 2; }
[[ "$SCRIPT" =~ ^ct/[A-Za-z0-9._-]+\.sh$ ]] || { echo "bad SCRIPT" >&2; exit 2; }
[[ -z "$OS" || "$OS" =~ ^(debian|ubuntu|alpine)$ ]] || { echo "bad OS" >&2; exit 2; }
[[ "$CTID_BASE" =~ ^[0-9]+$ ]] || { echo "bad CTID_BASE" >&2; exit 2; }
mkdir -p "$OUT_DIR"
RAW_LOG="$OUT_DIR/build.raw.log"
LOG="$OUT_DIR/build.log"
RESULT="$OUT_DIR/result.json"
INSTALL_LOG_OUT="$OUT_DIR/install.log"
app=$(basename "$SCRIPT" .sh)
job="${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}-${app}-${OS:-default}"
tree="$SERVE_ROOT/$job"
base="$SERVE_URL/$job"
started=$(date +%s)
status="fail"
step=""
exit_code=""
ctid=""
ct_ip=""
url=""
http_code=""
failed_units=""
strip_ansi() {
sed -E 's/\x1B\[[0-9;?]*[A-Za-z]//g; s/\x1B[()][A-Z0-9]//g; s/\r/\n/g' | grep -vE '^\s*$'
}
# The install script's own output: everything $STD hides from the build log.
# Both engines write it inside the container as /root/.install-<session>.log;
# dev_mode=keep makes sure a failed container is still there to read it from.
# If it is gone anyway, the copy the engine leaves on the host is the fallback.
collect_install_log() {
[[ -n "$ctid" ]] || return 0
# Exactly one build ran in this container, so the newest install log is ours.
if pct status "$ctid" &>/dev/null &&
pct exec "$ctid" -- sh -c 'cat "$(ls -t /root/.install-*.log 2>/dev/null | head -n1)"' 2>/dev/null |
strip_ansi >"$INSTALL_LOG_OUT" && [[ -s "$INSTALL_LOG_OUT" ]]; then
return 0
fi
# The engine names its host-side files after the session: pct_create_<ctid>_<date>_<session>.log
local sid f
sid=$(find /tmp -maxdepth 1 -name "pct_create_${ctid}_*.log" -newer "$RAW_LOG.start" -printf '%f\n' 2>/dev/null |
sed -E 's/.*_([0-9a-f]+)\.log$/\1/' | head -n1)
[[ -n "$sid" ]] || { rm -f "$INSTALL_LOG_OUT"; return 0; }
for f in /tmp/*-"${ctid}"-"${sid}".log /var/log/community-scripts/install-"${sid}"-combined-*.log; do
[[ -s "$f" ]] && { strip_ansi <"$f" >"$INSTALL_LOG_OUT"; return 0; }
done
rm -f "$INSTALL_LOG_OUT"
}
# "Requires AVX2" and friends: the script may be fine, the test host's CPU is
# not (Xeon E5-2650, Sandy Bridge: AVX, no AVX2). Shown as untestable rather
# than as a failure of the PR -- but the log is written by the code under test,
# so its word is not enough. Only the engine's error lines count, only when the
# build or install itself failed, and the feature they name must really be
# missing from this host's /proc/cpuinfo. Claiming a feature the host has
# changes nothing.
CPU_RE='(requir|need)[a-z]*[^.]{0,60}(AVX(2|-?512)?|SSE4[._]?2|x86-64-v[234])\b|\b(AVX(2|-?512)?|x86-64-v[234])\b[^.]{0,60}(not (supported|available)|does not have|is missing|unsupported)'
classify_cpu_failure() {
local line feature flags
line=$(strip_ansi <"$RAW_LOG" | grep -E '✖|\[ERROR\]' | grep -m1 -iE "$CPU_RE" |
sed -E 's/^\s*(✖️?|\[[^]]*\]|\s)*//' | cut -c1-200)
[[ -n "$line" ]] || return 0
# The CPU flag that would have to be missing for the message to be true.
case "${line,,}" in
*avx-512* | *avx512* | *x86-64-v4*) feature=avx512f ;;
*avx2* | *x86-64-v3*) feature=avx2 ;;
*sse4.2* | *sse4_2* | *x86-64-v2*) feature=sse4_2 ;;
*) return 0 ;;
esac
flags=" $(grep -m1 '^flags' /proc/cpuinfo | cut -d: -f2) "
[[ "$flags" == *" ${feature} "* ]] && return 0
status="unsupported"
step="needs a newer CPU than the test host has (no ${feature}): ${line}"
}
finish() {
[[ -f "$RAW_LOG" ]] && strip_ansi <"$RAW_LOG" >"$LOG"
[[ -f "$RAW_LOG" ]] && collect_install_log
jq -n \
--arg script "$SCRIPT" --arg os "${OS:-default}" --arg status "$status" \
--arg step "$step" --arg exit_code "$exit_code" --arg sha "$PR_SHA" \
--arg ctid "$ctid" --arg url "$url" --arg http_code "$http_code" \
--arg failed_units "$failed_units" \
--argjson duration "$(($(date +%s) - started))" \
--arg tail "$([[ -f "$LOG" ]] && tail -n 60 "$LOG")" \
'{script: $script, os: $os, status: $status, step: $step, exit_code: $exit_code,
sha: $sha, ctid: $ctid, url: $url, http_code: $http_code,
failed_units: $failed_units, duration: $duration, log_tail: $tail}' >"$RESULT"
echo "::group::result"
jq 'del(.log_tail)' "$RESULT"
echo "::endgroup::"
}
# --- Live output ------------------------------------------------------------------
# The job log on GitHub streams while the job runs, so mirroring the build there
# is all "live" takes. Two streams: the engine's own output, and the install
# log inside the container, which holds everything $STD hides (apt, npm, ...).
#
# Both are written by the code under test. A line starting with `::` would be
# read by the runner as a workflow command, so command processing is switched
# off for as long as they run, with a token the build cannot know.
stream_token=""
log_pid=""
install_pid=""
# Colours stay, cursor movement and spinner redraws go.
live_filter() {
sed -u -E 's/\r/\n/g; s/\x1B\[[0-9;?]*[ABCDEFGHJKSTfhilnsu]//g' | sed -u -E '/^\s*$/d'
}
start_streams() {
stream_token=$(head -c 16 /dev/urandom | od -An -tx1 | tr -d ' \n')
echo "::stop-commands::${stream_token}"
tail -n +1 -F --pid="$build_pid" "$RAW_LOG" 2>/dev/null > >(live_filter) &
log_pid=$!
# setsid: its own process group, so stop_streams can take down the
# pct exec / tail pair inside it in one go.
setsid bash -c '
for _ in $(seq 1 900); do
f=$(pct exec "$1" -- sh -c "ls -t /root/.install-*.log 2>/dev/null | head -n1" 2>/dev/null)
if [ -n "$f" ]; then
echo "──────── install log of CT $1 (live) ────────"
exec pct exec "$1" -- tail -n +1 -F "$f"
fi
sleep 2
done' _ "$ctid" 2>/dev/null > >(live_filter | sed -u 's/^/ │ /') &
install_pid=$!
}
stop_streams() {
[[ -n "$stream_token" ]] || return 0
[[ -n "$install_pid" ]] && kill -TERM -- "-$install_pid" &>/dev/null
[[ -n "$log_pid" ]] && kill -TERM "$log_pid" &>/dev/null
wait "$log_pid" "$install_pid" &>/dev/null
sleep 1 # let the filters flush
install_pid=""
log_pid=""
echo "::${stream_token}::"
stream_token=""
}
build_pid=""
cleanup() {
stop_streams
if [[ -n "$build_pid" ]]; then
pkill -TERM -P "$build_pid" &>/dev/null || true
kill -TERM "$build_pid" &>/dev/null || true
sleep 2
fi
if [[ -n "$ctid" ]]; then
if pct status "$ctid" &>/dev/null; then
pct stop "$ctid" --skiplock 1 &>/dev/null || true
pct destroy "$ctid" --purge 1 --force 1 &>/dev/null || true
fi
rm -f "/run/ci-jobs/$ctid"
# What the engine leaves on the host for this container and its session.
local f sid
for f in /tmp/pct_create_"${ctid}"_*.log; do
[[ -f "$f" && "$f" -nt "$RAW_LOG.start" ]] || continue
sid=$(sed -E 's/.*_([0-9a-f]+)\.log$/\1/' <<<"$f")
rm -f -- "$f" /tmp/create-lxc-"${sid}".log /tmp/*-"${ctid}"-"${sid}".log
done
fi
rm -f "$RAW_LOG.start"
rm -rf -- "$tree"
}
trap cleanup EXIT
# A cancelled job sends TERM; without this the EXIT trap would not run.
trap 'exit 143' TERM
trap 'exit 130' INT
# fail <reason> [fail|unsupported]: only the build/install branches pass the
# second argument, after classify_cpu_failure has checked the host.
#
# The exit code says whether *this job* worked, not whether the app did. A
# script that fails to build is a result: it is written down, reported on the
# PR (check, comment, review) and the job exits 0 -- a red run per broken app
# would only be notification noise. Only trouble with the test setup itself
# (setup_error) fails the job, because that is ours to look at.
fail() {
step="$1"
status="${2:-fail}"
[[ "$status" == "unsupported" ]] || status="fail"
finish
echo "::warning title=${SCRIPT}::${step}"
exit 0
}
setup_error() {
step="$1"
status="error"
finish
echo "::error title=${SCRIPT}::test setup: ${step}"
exit 1
}
# --- Reserve a container ID ---------------------------------------------------
# A lock file per ID, not just "first free one", because the engine only creates
# the container minutes later and two jobs would otherwise pick the same ID.
mkdir -p /run/ci-jobs
exec 9>/run/ci-jobs/.lock
flock 9
for ((id = CTID_BASE; id < CTID_BASE + CTID_COUNT; id++)); do
[[ -e "/run/ci-jobs/$id" ]] && continue
pvesh get /cluster/nextid --vmid "$id" &>/dev/null || continue
echo "$job" >"/run/ci-jobs/$id"
ctid=$id
break
done
flock -u 9
exec 9>&-
[[ -n "$ctid" ]] || setup_error "no free container ID in ${CTID_BASE}-$((CTID_BASE + CTID_COUNT - 1))"
# --- Fetch the approved commit and point it at ourselves ------------------------
mkdir -p "$tree"
curl -fsSL --retry 3 "https://codeload.github.com/${PR_REPO}/tar.gz/${PR_SHA}" |
tar -xz -C "$tree" --strip-components=1 || setup_error "could not download ${PR_REPO}@${PR_SHA:0:7}"
[[ -f "$tree/$SCRIPT" ]] || fail "${SCRIPT} does not exist at ${PR_SHA:0:7}"
upstream_re="https://raw\.githubusercontent\.com/${UPSTREAM//./\\.}/(refs/heads/)?main"
grep -rlE "$upstream_re" "$tree" --include='*.sh' --include='*.func' |
xargs -r sed -i -E "s#${upstream_re}#${base}#g"
# --- Build ----------------------------------------------------------------------
echo "Building ${SCRIPT} (${OS:-default OS}) as CT ${ctid} from ${PR_REPO}@${PR_SHA:0:7}"
hostname_safe="ci-$(tr -c 'a-z0-9-\n' '-' <<<"${app,,}" | cut -c1-50)"
build_env=(
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/root TERM=dumb LANG=C.UTF-8
mode=default MODE=default PHS_SILENT=1 DIAGNOSTICS=no
var_ctid="$ctid" var_hostname="$hostname_safe"
var_container_storage=local var_template_storage=local var_brg="$BRIDGE"
"dev_mode=timing,keep" COMMUNITY_SCRIPTS_URL="$base"
)
touch "$RAW_LOG.start"
[[ -n "$OS" ]] && build_env+=(var_os="$OS")
# --- Answers for the script's questions ------------------------------------------
# Nobody types into CI, so anything the install asks has to be answered up front.
# AGENTS.md rule 24: a script reads var_<x> first and prompts only when it is
# unset (or uses prompt_input_required ... "var_x"). Every such variable gets a
# CI value here. The names come from json/<app>.json app_vars (DevScripts) and
# from the read/prompt_ lines of the install script (both repos). A bare `read`
# that ignores its variable still fails, and the report says why.
ENGINE_VARS=" var_ctid var_hostname var_os var_version var_cpu var_ram var_disk var_brg var_net var_container_storage var_template_storage var_unprivileged var_tags var_arm64 var_testurl var_verbose var_timezone var_http_proxy var_http_no_proxy var_appversion "
host_app=$(tr -c 'a-z0-9-\n' '-' <<<"${app,,}")
ci_value() {
case "${1,,}" in
*pass* | *secret* | *token* | *key*) head -c 32 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | cut -c1-20 ;;
*email* | *mail*) echo "ci@example.test" ;;
*url* | *endpoint*) echo "https://ci-${host_app}.example.test" ;;
*host* | *domain* | *fqdn*) echo "ci-${host_app}.example.test" ;;
*user*) echo "ciadmin" ;;
*) echo "ci-test" ;;
esac
}
declare -A answers=()
json="$tree/json/${app}.json"
if [[ -f "$json" ]]; then
while IFS=$'\t' read -r name default; do
answers[$name]="$default"
done < <(jq -r '(if type == "array" then .[0] else . end).app_vars // [] | .[] |
select(.name | test("^var_[a-z0-9_]{1,40}$")) | [.name, (.default // "" | tostring)] | @tsv' "$json" 2>/dev/null)
fi
# The engine names the install script after APP (lower case, no spaces), which
# need not match the ct/ file name: ct/bluesky-pds.sh -> blueskypds-install.sh.
nsapp=$(grep -m1 -oE '^APP="[^"]+"' "$tree/$SCRIPT" | cut -d'"' -f2 | tr -d ' ' | tr '[:upper:]' '[:lower:]')
for f in "$tree/install/${app}-install.sh" "$tree/install/${nsapp}-install.sh" "$tree/$SCRIPT"; do
[[ -f "$f" ]] || continue
while read -r name; do
[[ -v "answers[$name]" ]] || answers[$name]=""
done < <(grep -E '\bread\b|prompt_(input|input_required|confirm|select|password)\b' "$f" | grep -oE '\bvar_[a-z0-9_]{1,40}\b' | sort -u)
done
for name in "${!answers[@]}"; do
[[ "$ENGINE_VARS" == *" $name "* ]] && continue
value="${answers[$name]}"
[[ -n "$value" ]] || value=$(ci_value "$name")
build_env+=("$name=$value")
case "${name,,}" in
*pass* | *secret* | *token* | *key*) echo "Pre-answered ${name}=(random)" ;;
*) echo "Pre-answered ${name}=${value}" ;;
esac
done
# In the background so that a TERM from a cancelled job is handled at once:
# bash defers traps until a foreground command returns.
env -i "${build_env[@]}" timeout --kill-after=1m "$BUILD_TIMEOUT" \
bash -c 'bash -c "$(curl -fsSL "$1")"' _ "$base/$SCRIPT" </dev/null >"$RAW_LOG" 2>&1 &
build_pid=$!
start_streams
wait "$build_pid"
exit_code=$?
build_pid=""
stop_streams
if ((exit_code == 124 || exit_code == 137)); then
fail "build timed out after ${BUILD_TIMEOUT}"
fi
# The engine marks the step that broke with a cross. Its closing "Installation
# failed in container" line is generic, so the cross before it is the useful one.
failure_line() {
local line
line=$(strip_ansi <"$RAW_LOG" | grep -E '✖|\[ERROR\]|error:' | grep -v 'Installation failed in container' |
tail -n 1 | sed -E 's/^\s*(✖️?|\s)*//' | cut -c1-200)
# A prompt nobody can answer: say what to do instead.
# First, because the report keeps 200 characters.
if [[ "$line" == *"read exited with status"* || "$line" == *"executing command read "* ]]; then
local asked
asked=$(grep -oE '\bvar_[a-z0-9_]{1,40}\b' <<<"$line" | tail -n 1)
line="unanswerable prompt (AGENTS.md rule 24): ask only when ${asked:-var_<x>} is unset, or use prompt_input_required. ${line}"
fi
echo "$line"
}
if ((exit_code != 0)); then
last=$(failure_line)
step="build exited ${exit_code}${last:+: $last}"
status="fail"
classify_cpu_failure
fail "$step" "$status"
fi
# With dev_mode=keep (needed to read the install log) the engine carries on
# after a failed install and still prints "Completed Successfully", exit 0.
install_exit=$(strip_ansi <"$RAW_LOG" | grep -oE 'Installation failed in container [0-9]+ \(exit code: [0-9]+\)' |
grep -oE '[0-9]+\)$' | tr -d ')' | tail -n 1)
if [[ -n "$install_exit" ]]; then
last=$(failure_line)
step="install script exited ${install_exit}${last:+: $last}"
status="fail"
classify_cpu_failure
fail "$step" "$status"
fi
grep -qi 'completed successfully' "$RAW_LOG" || fail "build exited 0 but never reported success"
# --- Checks ---------------------------------------------------------------------
pct status "$ctid" 2>/dev/null | grep -q running || fail "container ${ctid} is not running"
ct_ip=$(pct exec "$ctid" -- sh -c "ip -4 -o addr show dev eth0 | awk '{print \$4}' | cut -d/ -f1" 2>/dev/null | head -n1)
if pct exec "$ctid" -- sh -c 'command -v systemctl' &>/dev/null; then
sleep 10
failed_units=$(pct exec "$ctid" -- systemctl --failed --no-legend --plain 2>/dev/null | awk '{print $1}' | paste -sd, -)
elif pct exec "$ctid" -- sh -c 'command -v rc-status' &>/dev/null; then
failed_units=$(pct exec "$ctid" -- rc-status --crashed 2>/dev/null | awk '{print $1}' | paste -sd, -)
fi
[[ -z "$failed_units" ]] || fail "failed services: ${failed_units}"
# What the app's page looks like, for the PR comment. Chromium runs as its own
# unprivileged user with its sandbox on: the page is served by code under test.
# ci-shot-firewall (pve-ci-host-setup.sh) keeps that user off this host and the
# lab network; without it there is no screenshot rather than an unguarded one.
take_screenshot() {
command -v chromium >/dev/null && id ci-shot &>/dev/null || return 0
iptables -w -S CI_SHOT 2>/dev/null | grep -q -- '-d 10.0.0.0/8 .*REJECT' || return 0
local dir
dir=$(mktemp -d)
chown ci-shot "$dir"
runuser -u ci-shot -- timeout 60 chromium --headless=new --disable-gpu --hide-scrollbars \
--ignore-certificate-errors --no-first-run --window-size=1280,800 --virtual-time-budget=10000 \
--user-data-dir="$dir/profile" --screenshot="$dir/shot.png" "$url" &>/dev/null
# Only a plausible PNG leaves this function.
if [[ -s "$dir/shot.png" && $(stat -c %s "$dir/shot.png") -lt 3000000 ]] &&
[[ "$(head -c 8 "$dir/shot.png" | od -An -tx1 | tr -d ' \n')" == "89504e470d0a1a0a" ]]; then
cp "$dir/shot.png" "$OUT_DIR/screenshot.png"
fi
rm -rf -- "$dir"
}
# The URL the script tells the user to open, e.g. http://${IP}:1337/admin, has
# to answer 200 (after redirects, so a jump to /login is fine). A script that
# prints no URL has no web UI to check.
pattern=$(grep -oE 'https?://\$\{IP\}(:[0-9]+)?(/[A-Za-z0-9._~/?=&%-]*)?' "$tree/$SCRIPT" | head -n1)
if [[ -n "$pattern" && -n "$ct_ip" ]]; then
url="${pattern/\$\{IP\}/$ct_ip}"
for ((waited = 0; waited < URL_WAIT; waited += 5)); do
http_code=$(curl -sk -L --max-redirs 5 -o /dev/null -w '%{http_code}' --max-time 10 "$url" || true)
[[ "$http_code" =~ ^2[0-9][0-9]$ ]] && break
sleep 5
done
# Also when the check fails: an error page explains more than a status code.
[[ "$http_code" =~ ^[1-5][0-9][0-9]$ ]] && take_screenshot
[[ "$http_code" =~ ^2[0-9][0-9]$ ]] || fail "${url} answered ${http_code/#000/nothing} instead of 200 within ${URL_WAIT}s"
fi
status="pass"
finish
+73 -95
View File
@@ -22,112 +22,90 @@ jobs:
const now = new Date();
const owner = context.repo.owner;
const repo = context.repo.repo;
const TEMPLATE_LABEL = "missing pr template";
const MAINTAINERS = ["OWNER", "MEMBER", "COLLABORATOR"];
const days = d => (now - d) / (1000 * 60 * 60 * 24);
const staleNotice = author =>
`@${author} This PR has been marked as stale. It will be closed if no new commits are added in 7 days.`;
// Only fires for a maintainer's label: labels set with GITHUB_TOKEN raise no event.
// --- When stale label is added, comment immediately ---
if (context.eventName === "pull_request_target" && context.payload.action === "labeled") {
const pr = context.payload.pull_request;
const labels = (pr.labels || []).map(l => l.name);
if (context.payload.label?.name === "stale" && !labels.includes(TEMPLATE_LABEL)) {
const label = context.payload.label?.name;
if (label === "stale") {
const author = context.payload.pull_request.user.login;
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body: staleNotice(pr.user.login)
owner,
repo,
issue_number: context.payload.pull_request.number,
body: `@${author} This PR has been marked as stale. It will be closed if no new commits are added in 7 days.`
});
}
return;
}
const prs = await github.paginate(github.rest.pulls.list, { owner, repo, state: "open", per_page: 100 });
// --- Scheduled run: check all stale PRs ---
const { data: prs } = await github.rest.pulls.list({
owner,
repo,
state: "open",
per_page: 100
});
for (const pr of prs) {
const labels = pr.labels.map(l => l.name);
const author = pr.user.login;
const hasStale = labels.includes("stale");
if (labels.includes("keep-open")) {
if (hasStale) {
await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: "stale" });
}
continue;
}
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100
const hasStale = pr.labels.some(l => l.name === "stale");
if (!hasStale) continue;
// Get timeline events to find when stale label was added
const { data: events } = await github.rest.issues.listEvents({
owner,
repo,
issue_number: pr.number,
per_page: 100
});
const commits = await github.paginate(github.rest.pulls.listCommits, {
owner, repo, pull_number: pr.number, per_page: 100
});
const lastCommitDate = commits.length > 0
? new Date(commits[commits.length - 1].commit.author.date)
: new Date(pr.created_at);
const humanComments = comments.filter(c => c.user?.type !== "Bot");
// -------------------------------------------------------
// Auto-label PRs with no activity in the last 14 days,
// but ONLY if someone other than the author has engaged.
// -------------------------------------------------------
if (!hasStale) {
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner, repo, pull_number: pr.number, per_page: 100
});
const engaged =
reviews.some(r => r.user?.type !== "Bot" && r.user?.login !== author) ||
humanComments.some(c => c.user?.login !== author);
if (!engaged) continue;
const lastCommentDate = humanComments.length > 0
? new Date(humanComments[humanComments.length - 1].created_at)
: null;
const lastActivityDate = lastCommentDate && lastCommentDate > lastCommitDate
? lastCommentDate
: lastCommitDate;
if (days(lastActivityDate) > 14) {
await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: ["stale"] });
await github.rest.issues.createComment({ owner, repo, issue_number: pr.number, body: staleNotice(author) });
}
continue;
}
// -------------------------------------------------------
// Manage already-stale PRs
// -------------------------------------------------------
const events = await github.paginate(github.rest.issues.listEvents, {
owner, repo, issue_number: pr.number, per_page: 100
});
const staleDates = events
// Find the most recent time the stale label was added
const staleLabelEvents = events
.filter(e => e.event === "labeled" && e.label?.name === "stale")
.map(e => new Date(e.created_at));
if (staleDates.length === 0) continue;
const staleLabelDate = new Date(Math.max(...staleDates));
const isTemplateIssue = labels.includes(TEMPLATE_LABEL);
// A push ends an inactivity mark; a template mark only ends via close-invalid-pr-template.yml's re-check.
if (!isTemplateIssue && lastCommitDate > staleLabelDate) {
await github.rest.issues.removeLabel({ owner, repo, issue_number: pr.number, name: "stale" });
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (staleLabelEvents.length === 0) continue;
const staleLabelDate = new Date(staleLabelEvents[0].created_at);
const daysSinceStale = (now - staleLabelDate) / (1000 * 60 * 60 * 24);
// Check for new commits since stale label was added
const { data: commits } = await github.rest.pulls.listCommits({
owner,
repo,
pull_number: pr.number
});
const lastCommitDate = new Date(commits[commits.length - 1].commit.author.date);
const author = pr.user.login;
// If there are new commits after the stale label, remove it
if (lastCommitDate > staleLabelDate) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pr.number,
name: "stale"
});
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number,
owner,
repo,
issue_number: pr.number,
body: `@${author} Recent activity detected. Removing stale label.`
});
continue;
}
// A maintainer comment after the mark restarts the 7 days.
const maintainerDates = humanComments
.filter(c => MAINTAINERS.includes(c.author_association) && c.user?.login !== author)
.map(c => new Date(c.created_at))
.filter(d => d > staleLabelDate);
const clockStart = maintainerDates.length > 0 ? new Date(Math.max(...maintainerDates)) : staleLabelDate;
if (days(clockStart) <= 7) continue;
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number,
body: isTemplateIssue
? `@${author} Closing: the PR template requirements listed above were still missing 7 days later. Once they are in place, please open a new pull request.`
: `@${author} Closing stale PR due to inactivity (no commits for 7 days after stale label).`
});
await github.rest.pulls.update({ owner, repo, pull_number: pr.number, state: "closed" });
// If 7 days have passed since stale label, close the PR
else if (daysSinceStale > 7) {
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
state: "closed"
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: `@${author} Closing stale PR due to inactivity (no commits for 7 days after stale label).`
});
}
}
+2 -2
View File
@@ -5,8 +5,8 @@ name: Sync ct/install to Incus
#
# The actual mirroring + transformation lives in community-scripts/Incus
# (.github/workflows/sync-scripts.yml): it rewrites each script's bootstrap line
# to load the engine from community-scripts/core (ct scripts here load its PVE
# backend; Incus needs the Incus one), so a plain 1:1 copy would break every Incus
# to load the engine from community-scripts/core (ProxmoxVE's misc/build.func is
# a monolith with no Incus backend), so a plain 1:1 copy would break every Incus
# script. We therefore only fire a repository_dispatch and let that single
# source of truth do the work (and auto-approve + merge its own PR).
#
+639 -753
View File
File diff suppressed because it is too large. Load diff
+7 -6
View File
@@ -9,13 +9,13 @@ For detailed coding standards and full documentation, visit **[community-scripts
## How Can I Help?
> [!IMPORTANT]
> **New scripts** must always be submitted to [DevScripts](https://github.com/community-scripts/DevScripts) first — not to this repository.
> **New scripts** must always be submitted to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) first — not to this repository.
> PRs with new scripts opened directly against ProxmoxVE **will be closed without review**.
> **Bug fixes, improvements, and features for existing scripts** go here (ProxmoxVE).
| I want to… | Where to go |
| :------------------------------------------ | :------------------------------------------------------------------------------------------- |
| **Add a brand-new script** | [DevScripts](https://github.com/community-scripts/DevScripts) — testing repo for new scripts |
| **Add a brand-new script** | [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) — testing repo for new scripts |
| **Fix a bug or improve an existing script** | This repo (ProxmoxVE) — open a PR here |
| **Add a feature to an existing script** | This repo (ProxmoxVE) — open a PR here |
| Report a bug or broken script | [Open an Issue](https://github.com/community-scripts/ProxmoxVE/issues) |
@@ -55,13 +55,13 @@ Use existing scripts in [`ct/`](ct/) and [`install/`](install/) as reference. Fu
New scripts are **not accepted directly in this repository**. The workflow is:
1. Fork [DevScripts](https://github.com/community-scripts/DevScripts) and clone it
1. Fork [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) and clone it
2. Create a branch: `git switch -c feat/myapp`
3. Write your two script files:
- `ct/myapp.sh`
- `install/myapp-install.sh`
4. Test thoroughly in DevScripts — run the script against a real Proxmox instance
5. Open a PR in **DevScripts** for review and testing
4. Test thoroughly in ProxmoxVED — run the script against a real Proxmox instance
5. Open a PR in **ProxmoxVED** for review and testing
6. Once accepted and verified there, the script will be promoted to ProxmoxVE by maintainers
Follow the coding standards at [community-scripts.org/docs/contribution](https://community-scripts.org/docs/contribution).
@@ -191,6 +191,7 @@ create` still ran and the container was still built.
## Notes
- **Website metadata** (name, description, logo, tags) is managed via the website — use the "Report Issue" link on any script page to request changes. Do not submit metadata changes via repo files.
- **JSON files** in `json/` define script properties used by the website. See existing files for structure reference.
- Keep PRs small and focused. One fix or feature per PR is ideal.
- PRs with **new scripts** opened against ProxmoxVE will be closed — submit them to [DevScripts](https://github.com/community-scripts/DevScripts) instead.
- PRs with **new scripts** opened against ProxmoxVE will be closed — submit them to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) instead.
- PRs that fail CI checks will not be merged.
+71 -128
View File
@@ -1,17 +1,16 @@
<div align="center">
<a href=".github/assets/community-scripts.svg"><img src=".github/assets/community-scripts.png" width="112" height="112" alt="Community Scripts — Proxmox VE logo" /></a>
<!-- Editable artwork: .github/assets/community-scripts.svg; PNG export: 512 × 512. -->
<img src="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png" height="112px" alt="Proxmox VE Helper-Scripts Logo" />
<h1>Community Scripts — Proxmox VE</h1>
<p><strong>One-command installations for services, containers and VMs on Proxmox VE</strong><br/>
<h1>Proxmox VE Helper-Scripts</h1>
<p><strong>One-command installations for services, containers, and VMs on Proxmox VE</strong><br/>
A community project — built on the foundation of <a href="https://github.com/tteck">@tteck</a>'s original work</p>
<p>
<a href="https://community-scripts.org"><img src="https://img.shields.io/badge/Website-community--scripts.org-4c9b3f?style=flat-square" alt="Website" /></a>
<a href="https://discord.gg/3AnUqsXnmK"><img src="https://img.shields.io/badge/Discord-Join_us-7289da?style=flat-square&logo=discord&logoColor=white" alt="Discord" /></a>
<a href="https://github.com/community-scripts/ProxmoxVE/stargazers"><img src="https://img.shields.io/github/stars/community-scripts/ProxmoxVE?style=flat-square&label=Stars&color=f5a623" alt="Stars" /></a>
<a href="https://github.com/community-scripts/ProxmoxVE/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/Changelog-view-6c5ce7?style=flat-square" alt="Changelog" /></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=flat-square" alt="License" /></a>
<a href="https://community-scripts.org"><img src="https://img.shields.io/badge/Website-community--scripts.org-4c9b3f?style=flat-square" /></a>
<a href="https://discord.gg/3AnUqsXnmK"><img src="https://img.shields.io/badge/Discord-Join_us-7289da?style=flat-square&logo=discord&logoColor=white" /></a>
<a href="https://github.com/community-scripts/ProxmoxVE/stargazers"><img src="https://img.shields.io/github/stars/community-scripts/ProxmoxVE?style=flat-square&label=Stars&color=f5a623" /></a>
<a href="https://github.com/community-scripts/ProxmoxVE/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/Changelog-view-6c5ce7?style=flat-square" /></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=flat-square" /></a>
</p>
</div>
@@ -19,146 +18,94 @@
## What is this?
**The canonical application scripts.** More than 700 of them, covering home
automation, media, networking, databases, monitoring, security and developer
tooling. Paste one command into your Proxmox shell and the container or VM is
built, configured and running.
**Simplify your Proxmox VE setup with community-driven automation scripts.**
A script only describes *what* to install. Creating the container, asking the
questions and handling the errors is the job of the shared engine in
[core](https://github.com/community-scripts/core), which every script loads at
start-up. The same scripts run unchanged on an Incus host through
[the Incus repository](https://github.com/community-scripts/Incus).
Install and configure popular self-hosted services with a single command — no manual package hunting, no config file archaeology. Paste a command into your Proxmox shell, answer a few prompts, and your container or VM is up and running.
Ready-made commands for every application are on
[community-scripts.org](https://community-scripts.org).
---
## Getting started
1. Go to **[community-scripts.org](https://community-scripts.org)**
2. Search for the service you want — Home Assistant, Nginx Proxy Manager, Jellyfin
3. Copy the one-line install command from the script page
4. Open your **Proxmox shell** and paste it
5. Choose **Default** or **Advanced** and follow the prompts
Each script page lists what the container includes, the default resources and
the post-install notes.
> These commands run as root on your host. Every script is readable in this
> repository, and each script page links to its source — read it before you
> paste it, as you would with any installer.
---
## How scripts work
**Default** picks sensible resources and asks only what it has to. Most installs
finish in a few minutes.
**Advanced** hands you container settings, networking, storage backend and
application options before anything is created.
Every container gets an `update` command. Type it in the container console:
```bash
update
```
It updates the installed application and reaches settings that would otherwise
mean editing config files by hand. VM scripts have no built-in updater — use the
guest's own package manager.
The collection covers hundreds of services across categories like home automation, media servers, networking tools, databases, monitoring stacks, and more.
---
## Requirements
| Component | Details |
| --------- | ------- |
| **Proxmox VE** | 8.0 – 8.4, or 9.0 – 9.2 |
| **Access** | Root shell on the Proxmox host |
| **Network** | Internet access during installation |
Virtual machine scripts need the same versions as containers.
| Component | Details |
| -------------- | ------------------------------------------------ |
| **Proxmox VE** | Version 8.4, 9.0, 9.1, or 9.2 |
| **Host OS** | Proxmox VE (Debian-based) |
| **Access** | Root shell access on the Proxmox host |
| **Network** | Internet connection required during installation |
---
## Repository map
## Getting Started
| Repository | Contains |
| ---------- | -------- |
| [core](https://github.com/community-scripts/core) | The engine — container and VM creation, the wizard, error handling |
| **ProxmoxVE** (this repo) | Canonical application scripts, plus `tools/pve/`, `vm/` and `turnkey/` |
| [Incus](https://github.com/community-scripts/Incus) | The same scripts on an Incus host, plus Incus VMs and host tooling |
| [DevScripts](https://github.com/community-scripts/DevScripts) | Where new scripts are tested before they land here |
The fastest way to find and run scripts:
1. Go to **[community-scripts.org](https://community-scripts.org)**
2. Search for the service you want (e.g. "Home Assistant", "Nginx Proxy Manager", "Jellyfin")
3. Copy the one-line install command from the script page
4. Open your **Proxmox Shell** and paste it
5. Choose between **Default** or **Advanced** setup and follow the prompts
Each script page documents what the container includes, default resource allocation, and post-install notes.
---
## Layout
## How Scripts Work
| Folder | Contents |
| ------ | -------- |
| `ct/` | Application scripts — the command you paste |
| `install/` | What each script installs inside the container |
| `vm/` | Virtual machine scripts |
| `tools/pve/` | Host tooling: post-install, hardware passthrough, maintenance |
| `turnkey/` | TurnKey Linux appliance helper |
Every script follows the same pattern:
Application metadata — name, description, logo, tags — is managed on the website,
not in this repository. Use the **Report Issue** link on any script page to
request a change.
**Default mode** — Picks sensible resource defaults (CPU, RAM, storage) and asks only the minimum required questions. Most installs finish in under five minutes.
**Advanced mode** — Gives you full control over container settings, networking, storage backends, and application-level configuration before anything is installed.
After installation, each container ships with a **post-install helper** accessible from the Proxmox shell. It handles common tasks like:
- Applying updates to the installed service
- Changing application settings without manually editing config files
- Basic troubleshooting and log access
---
## Testing a fork or branch
## What's Included
Engine and scripts resolve independently, so either can point somewhere else.
Put the variable on the command itself — nothing needs exporting.
The repository covers a wide range of categories. A few examples:
To run your own copy of the scripts:
| Category | Examples |
| --------------- | --------------------------------------------------- |
| Home Automation | Home Assistant, Zigbee2MQTT, ESPHome, Node-RED |
| Media | Jellyfin, Plex, Radarr, Sonarr, Immich |
| Networking | AdGuard Home, Nginx Proxy Manager, Pi-hole, Traefik |
| Monitoring | Grafana, Prometheus, Uptime Kuma, Netdata |
| Databases | PostgreSQL, MariaDB, Redis, InfluxDB |
| Security | Vaultwarden, CrowdSec, Authentik |
| Dev & Tools | Gitea, Portainer, VS Code Server, n8n |
```bash
COMMUNITY_SCRIPTS_URL=https://raw.githubusercontent.com/YOU/ProxmoxVE/my-branch \
bash -c "$(curl -fsSL https://raw.githubusercontent.com/YOU/ProxmoxVE/my-branch/ct/debian.sh)"
```
A `ct/` script fetched with `curl` cannot tell the engine where it came from, so
without that variable it would look for its `install/` half upstream.
Only when you are changing the engine itself:
```bash
COMMUNITY_SCRIPTS_CORE_URL=https://raw.githubusercontent.com/YOU/core/my-branch \
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/debian.sh)"
```
A local checkout of core wins over both — point at it with
`COMMUNITY_SCRIPTS_CORE_DIR`.
> Browse the full list at **[community-scripts.org/categories](https://community-scripts.org/categories)** — new scripts are added regularly.
---
## Contributing
This project runs on community contributions. New scripts, fixes, bug reports —
all of it helps.
This project runs on community contributions. Whether you want to write new scripts, improve existing ones, or just report a bug — every bit helps.
| I want to… | Go here |
| ---------- | ------- |
| Add a **new** script | [DevScripts](https://github.com/community-scripts/DevScripts) — new scripts are tested there first |
| Fix or improve an **existing** script | [Contributing Guidelines](CONTRIBUTING.md) — open a PR here |
| Change how containers are built | [core](https://github.com/community-scripts/core) |
| Report a bug or a broken script | [Issues](https://github.com/community-scripts/ProxmoxVE/issues) |
| Request a script or a feature | [Discussions](https://github.com/community-scripts/ProxmoxVE/discussions) |
| Report a security vulnerability | [Security Policy](SECURITY.md) |
| Ask a question | [Discord](https://discord.gg/3AnUqsXnmK) |
### Where to start
| I want to… | Go here |
| ------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Add a **new** script | [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED) — new scripts are tested here first |
| Fix or improve an **existing** script | [Contributing Guidelines](CONTRIBUTING.md) — open a PR in this repo |
| Report a bug or broken script | [Issues](https://github.com/community-scripts/ProxmoxVE/issues) |
| Request a new script or feature | [Discussions](https://github.com/community-scripts/ProxmoxVE/discussions) |
| Report a security vulnerability | [Security Policy](SECURITY.md) |
| Get help or chat with other users | [Discord](https://discord.gg/3AnUqsXnmK) |
### Before you open a PR
- **New scripts go to [DevScripts](https://github.com/community-scripts/DevScripts), not here.** PRs adding new scripts to this repository will be closed.
- Keep a PR to one fix or one feature.
- A bug that also happens on Incus is an application or engine bug — say so in the report.
- Read the [Contributing Guidelines](CONTRIBUTING.md) before your first PR.
- **New scripts go to [ProxmoxVED](https://github.com/community-scripts/ProxmoxVED), not here.** PRs with new scripts opened directly against this repo will be closed.
- Bug fixes and improvements to existing scripts belong in this repo — read the [Contributing Guidelines](CONTRIBUTING.md) first.
- Keep PRs focused. One fix or feature per PR.
- Document what your script installs and any non-obvious decisions in the corresponding JSON metadata file.
---
@@ -190,8 +137,6 @@ all of it helps.
<sub><b>CrazyWolf13</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/tremor021">
<img src="https://github.com/tremor021.png" width="80" height="80" style="border-radius:50%" alt="tremor021" /><br/>
@@ -210,7 +155,6 @@ all of it helps.
<sub><b>asylumexp</b></sub>
</a>
</td>
<td></td>
</tr>
</table>
@@ -250,11 +194,9 @@ all of it helps.
## Support the Project
This project is maintained by volunteers. Infrastructure costs come out of
pocket and the work happens in people's spare time.
This project is maintained by volunteers. All infrastructure costs come out of pocket, and the work is done in people's spare time.
**30% of all donations are forwarded directly to cancer research and hospice
care** — a cause that was important to tteck.
**30% of all donations are forwarded directly to cancer research and hospice care** — a cause that was important to tteck.
<div align="center">
<a href="https://ko-fi.com/community_scripts">
@@ -270,13 +212,14 @@ care** — a cause that was important to tteck.
## License
MIT — free to use, modify and redistribute, for personal and commercial
purposes. See [LICENSE](LICENSE).
This project is licensed under the [MIT License](LICENSE) — free to use, modify, and redistribute for personal and commercial purposes.
See the full license text in [LICENSE](LICENSE).
---
<div align="center">
<sub>Built on the foundation of <a href="https://github.com/tteck">tteck</a>'s original work · <a href="https://github.com/tteck/Proxmox">Original Repository</a></sub><br/>
<sub>Maintained and expanded by the community · In memory of tteck</sub><br/>
<sub><i>This is an independent community project. <b>Proxmox</b>® is a registered trademark of <a href="https://www.proxmox.com/en/about/company">Proxmox Server Solutions GmbH</a>.</i></sub>
<sub><i>Proxmox® is a registered trademark of <a href="https://www.proxmox.com/en/about/company">Proxmox Server Solutions GmbH</a></i></sub>
</div>
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+4 -2
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
@@ -37,9 +38,9 @@ function update_script() {
msg_ok "Services Stopped"
create_backup /opt/adventurelog/backend/server/.env \
/opt/adventurelog/frontend/.env
/opt/adventurelog/backend/server/media
CLEAN_INSTALL=1 CLEAN_INSTALL_KEEP="backend/server/media" fetch_and_deploy_gh_release "adventurelog" "seanmorley15/adventurelog" "tarball"
fetch_and_deploy_gh_release "adventurelog" "seanmorley15/adventurelog" "tarball"
PYTHON_VERSION="3.13" setup_uv
msg_info "Ensuring PostgreSQL Extensions"
@@ -56,6 +57,7 @@ function update_script() {
fi
$STD .venv/bin/python -m pip install --upgrade pip
$STD .venv/bin/python -m pip install -r requirements.txt
$STD .venv/bin/python -m pip install 'djangorestframework<3.15'
$STD .venv/bin/python -m manage collectstatic --noinput
$STD .venv/bin/python -m manage migrate
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+3 -2
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -36,9 +37,9 @@ function update_script() {
systemctl stop airtrail
msg_ok "Stopped Service"
create_backup /opt/airtrail/.env
create_backup /opt/airtrail/.env /opt/airtrail/uploads
CLEAN_INSTALL=1 CLEAN_INSTALL_KEEP="uploads" fetch_and_deploy_gh_release "airtrail" "johanohly/AirTrail" "tarball" "latest" "/opt/airtrail"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "airtrail" "johanohly/AirTrail" "tarball" "latest" "/opt/airtrail"
restore_backup
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+4 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
@@ -48,7 +49,9 @@ function update_script() {
exit
fi
echo -e "${INFO}${YW}Please paste your SSH public key (e.g., ssh-rsa AAAAB3... user@host):${CL}"
msg_info "Setting up SSH Public Key for backup user"
msg_info "Please paste your SSH public key (e.g., ssh-rsa AAAAB3... user@host): \n"
read -p "Key: " SSH_PUBLIC_KEY
echo
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+14 -6
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -29,13 +30,20 @@ function update_script() {
exit
fi
if [[ -f /opt/${APP}_version.txt ]]; then
mv /opt/"${APP}_version.txt" ~/.it-tools
fi
if check_for_gh_release "it-tools" "sharevb/it-tools"; then
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "it-tools" "sharevb/it-tools" "prebuild" "latest" "/usr/share/nginx/html" "it-tools-*.zip"
RELEASE=$(curl -fsSL https://api.github.com/repos/sharevb/it-tools/releases/latest | grep '"tag_name":' | cut -d '"' -f4)
if [ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ] || [ ! -f /opt/${APP}_version.txt ]; then
msg_info "Updating ${APP} LXC"
curl -fsSL "https://github.com/sharevb/it-tools/releases/download/${RELEASE}/it-tools-${RELEASE#v}.zip" -o it-tools.zip
mkdir -p /usr/share/nginx/html
rm -rf /usr/share/nginx/html/*
$STD unzip it-tools.zip -d /tmp
cp -r /tmp/dist/* /usr/share/nginx/html
rm -rf /tmp/dist
rm -f it-tools.zip
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
exit 0
}
+3 -2
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -31,7 +32,7 @@ function update_script() {
msg_warn "⚠️ ${APP} has been migrated to an addon script."
echo ""
echo -e "${INFO}${YW}This is a one-time migration. After this, you can update ${APP} anytime with:${CL}"
msg_info "This is a one-time migration. After this, you can update ${APP} anytime with:"
echo -e "${TAB}${TAB}${GN}update_komodo${CL} or ${GN}bash <(curl -fsSL ${ADDON_SCRIPT})${CL}"
echo ""
read -r -p "${TAB}Migrate update function now? [y/N]: " CONFIRM
@@ -62,7 +63,7 @@ MIGRATION_EOF
ln -sf /usr/bin/update /usr/bin/update_komodo 2>/dev/null || true
msg_ok "Migration complete"
echo -e "${INFO}${YW}Running addon update${CL}"
msg_info "Running addon update"
type=update bash <(curl -fsSL "${ADDON_SCRIPT}")
exit
}
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+17 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
@@ -29,7 +30,8 @@ function update_script() {
exit
fi
if check_for_gh_release "wakapi" "muety/wakapi"; then
RELEASE=$(curl -s https://api.github.com/repos/muety/wakapi/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
if [ "${RELEASE}" != "$(cat ~/.wakapi 2>/dev/null)" ] || [ ! -f ~/.wakapi ]; then
msg_info "Stopping Wakapi Service"
$STD rc-service wakapi stop
msg_ok "Stopped Wakapi Service"
@@ -38,14 +40,26 @@ function update_script() {
$STD apk -U upgrade
msg_ok "Updated Wakapi LXC"
create_backup /opt/wakapi/config.yml /opt/wakapi/wakapi_db.db
msg_info "Creating backup"
mkdir -p /opt/wakapi-backup
cp /opt/wakapi/config.yml /opt/wakapi/wakapi_db.db /opt/wakapi-backup/
msg_ok "Created backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wakapi" "muety/wakapi" "prebuild" "latest" "/opt/wakapi" "wakapi_linux_$(arch_resolve).zip"
restore_backup
msg_info "Configuring Wakapi"
cd /opt/wakapi
cp /opt/wakapi-backup/config.yml /opt/wakapi/
cp /opt/wakapi-backup/wakapi_db.db /opt/wakapi/
rm -rf /opt/wakapi-backup
msg_ok "Configured Wakapi"
msg_info "Starting Service"
$STD rc-service wakapi start
msg_ok "Started Service"
msg_ok "Updated successfully"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
exit 0
}
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
-41
View File
@@ -1,41 +0,0 @@
#!/usr/bin/env bash
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/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://aws.amazon.com/linux/
APP="Amazon Linux"
var_tags="${var_tags:-os}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-amazonlinux}"
var_version="${var_version:-2023}"
var_arm64="${var_arm64:-no}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
msg_info "Updating Amazon Linux LXC"
$STD dnf -y upgrade
msg_ok "Updated Amazon Linux LXC"
cleanup_lxc
exit
}
start
build_container
description
msg_ok "Completed successfully!"
msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!"
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -39,7 +40,6 @@ function update_script() {
create_backup /opt/anchor/.env
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "anchor" "ZhFahim/anchor" "tarball"
NODE_VERSION="26" NODE_MODULE="pnpm" setup_nodejs
msg_info "Building Server"
cd /opt/anchor/server
-65
View File
@@ -1,65 +0,0 @@
#!/usr/bin/env bash
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/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://docs.ankiweb.net/sync-server.html
APP="Anki-Sync-Server"
var_tags="${var_tags:-anki;flashcards;sync}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /opt/anki-sync-server/.env ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "anki-sync-server" "ankitects/anki"; then
msg_info "Stopping Service"
systemctl stop anki-sync-server
msg_ok "Stopped Service"
create_backup /opt/anki-sync-server/.env /opt/anki-sync-server/data
msg_info "Updating Anki Sync Server"
$STD uv pip install --python /opt/anki-sync-server/venv/bin/python "anki==$(get_latest_github_release "ankitects/anki")"
cat <<EOF >~/.anki-sync-server
$(get_latest_github_release "ankitects/anki")
EOF
msg_ok "Updated Anki Sync Server"
restore_backup
msg_info "Starting Service"
systemctl start anki-sync-server
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 "${GATEWAY}${BGN}http://${IP}:8080${CL}"
-78
View File
@@ -1,78 +0,0 @@
#!/usr/bin/env bash
# Engine comes from community-scripts/core; this repo only ships the scripts.
# A local core checkout wins (COMMUNITY_SCRIPTS_CORE_DIR, else a sibling ../core),
# so a fork or branch of core can be tested without editing this file.
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/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/Mintplex-Labs/anything-llm
APP="AnythingLLM"
var_tags="${var_tags:-ai;rag}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-6144}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_gpu="${var_gpu:-yes}"
#var_arm64="${var_arm64:-no}" # unset = ask the user; set yes/no only when verified
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/anythingllm ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "anythingllm" "Mintplex-Labs/anything-llm"; then
msg_info "Stopping Services"
systemctl stop anythingllm anythingllm-collector
msg_ok "Stopped Services"
create_backup /opt/anythingllm/server/.env /opt/anythingllm/collector/.env /opt/anythingllm/frontend/.env
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "anythingllm" "Mintplex-Labs/anything-llm" "tarball"
restore_backup
msg_info "Building AnythingLLM (Patience)"
cd /opt/anythingllm
export PUPPETEER_SKIP_DOWNLOAD=true
export NODE_OPTIONS="--max-old-space-size=3072"
$STD yarn setup
cd /opt/anythingllm/frontend
$STD yarn build
rm -rf /opt/anythingllm/server/public
cp -R /opt/anythingllm/frontend/dist /opt/anythingllm/server/public
cd /opt/anythingllm/server
$STD npx prisma generate --schema=./prisma/schema.prisma
$STD npx prisma migrate deploy --schema=./prisma/schema.prisma
msg_ok "Built AnythingLLM"
msg_info "Starting Services"
systemctl start anythingllm anythingllm-collector
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:3001${CL}"
+2 -13
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
@@ -37,25 +38,13 @@ function update_script() {
systemctl stop anytype
msg_ok "Stopped Service"
create_backup /opt/anytype/data /opt/anytype/.env
create_backup /opt/anytype/data
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "anytype" "grishy/any-sync-bundle" "prebuild" "latest" "/opt/anytype" "any-sync-bundle_*_linux_$(arch_resolve).tar.gz"
chmod +x /opt/anytype/any-sync-bundle
restore_backup
if [[ ! -f /opt/anytype/.env ]]; then
cat <<EOF >/opt/anytype/.env
ANY_SYNC_BUNDLE_CONFIG=/opt/anytype/data/bundle-config.yml
ANY_SYNC_BUNDLE_CLIENT_CONFIG=/opt/anytype/data/client-config.yml
ANY_SYNC_BUNDLE_INIT_STORAGE=/opt/anytype/data/storage/
ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS=$(hostname -I | awk '{print $1}')
ANY_SYNC_BUNDLE_INIT_MONGO_URI=mongodb://127.0.0.1:27017/
ANY_SYNC_BUNDLE_INIT_REDIS_URI=redis://127.0.0.1:6379/
ANY_SYNC_BUNDLE_LOG_LEVEL=info
EOF
fi
msg_info "Starting Service"
systemctl start anytype
msg_ok "Started Service"
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+2 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
@@ -31,7 +32,7 @@ function update_script() {
fi
msg_info "Updating Apache CouchDB"
apt_update_safe
$STD apt install -y --only-upgrade couchdb
$STD apt-get install -y --only-upgrade couchdb
msg_ok "Updated Apache CouchDB"
msg_ok "Updated successfully!"
exit
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+23 -19
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -51,28 +52,18 @@ function update_script() {
exit
fi
msg_info "Downloading Tomcat ${LATEST_VERSION}"
TOMCAT_URL="https://dlcdn.apache.org/tomcat/tomcat-${TOMCAT_MAJOR}/v${LATEST_VERSION}/bin/apache-tomcat-${LATEST_VERSION}.tar.gz"
curl -fsSL "$TOMCAT_URL" -o /tmp/tomcat-update.tar.gz
msg_ok "Downloaded Tomcat ${LATEST_VERSION}"
# Keep only the jars in lib that the new release does not ship (JDBC drivers
# and the like). ecj and jakartaee-migration are Tomcat's own and change name
# with each version, so they are never carried over.
TOMCAT_FILES=$(tar -tzf /tmp/tomcat-update.tar.gz)
BACKUP_PATHS=("$TOMCAT_DIR/conf")
[[ -d $TOMCAT_DIR/webapps ]] && BACKUP_PATHS+=("$TOMCAT_DIR/webapps")
for jar in "$TOMCAT_DIR"/lib/*.jar; do
[[ -f $jar ]] || continue
case "${jar##*/}" in ecj-*.jar | jakartaee-migration-*.jar) continue ;; esac
grep -qxF "apache-tomcat-${LATEST_VERSION}/lib/${jar##*/}" <<<"$TOMCAT_FILES" || BACKUP_PATHS+=("$jar")
done
msg_info "Stopping Tomcat service"
systemctl stop tomcat
msg_ok "Stopped Tomcat service"
create_backup "${BACKUP_PATHS[@]}"
create_backup $TOMCAT_DIR/conf
[[ -d $TOMCAT_DIR/webapps ]] && create_backup $TOMCAT_DIR/webapps
[[ -d $TOMCAT_DIR/lib ]] && create_backup $TOMCAT_DIR/lib
msg_info "Downloading Tomcat ${LATEST_VERSION}"
TOMCAT_URL="https://dlcdn.apache.org/tomcat/tomcat-${TOMCAT_MAJOR}/v${LATEST_VERSION}/bin/apache-tomcat-${LATEST_VERSION}.tar.gz"
curl -fsSL "$TOMCAT_URL" -o /tmp/tomcat-update.tar.gz
msg_ok "Downloaded Tomcat ${LATEST_VERSION}"
msg_info "Installing update"
rm -rf "${TOMCAT_DIR:?}"/*
@@ -80,8 +71,21 @@ function update_script() {
rm -f /tmp/tomcat-update.tar.gz
msg_ok "Installed update"
restore_backup
msg_info "Restoring configuration and applications"
cp -a "$BACKUP_DIR/conf"/* "$TOMCAT_DIR/conf/"
cp -a "$BACKUP_DIR/webapps"/* "$TOMCAT_DIR/webapps/" 2>/dev/null || true
if [[ -d "$BACKUP_DIR/lib" ]]; then
for jar in "$BACKUP_DIR/lib"/*.jar; do
[[ -f $jar ]] || continue
jar_name=$(basename "$jar")
if [[ ! -f "$TOMCAT_DIR/lib/$jar_name" ]]; then
cp "$jar" "$TOMCAT_DIR/lib/"
fi
done
fi
rm -rf "$BACKUP_DIR"
chown -R root:root "$TOMCAT_DIR"
msg_ok "Restored configuration and applications"
msg_info "Starting Tomcat service"
systemctl start tomcat
+2 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -46,7 +47,7 @@ function update_script() {
$STD uv venv /opt/apprise/.venv
$STD uv pip install -r requirements.txt gunicorn supervisor -p /opt/apprise/.venv/bin/python
ln -sf /opt/apprise/.venv/bin/supervisord /opt/apprise/.venv/bin/gunicorn /usr/local/bin/
cp -fr apprise_api/static/. /usr/share/nginx/html/s/
cp -fr apprise_api/static /usr/share/nginx/html/s/
mv apprise_api/ webapp
touch /etc/nginx/server-override.conf
touch /etc/nginx/location-override.conf
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+62 -12
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
@@ -12,7 +13,7 @@ var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-12}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
@@ -30,26 +31,75 @@ function update_script() {
exit
fi
NODE_VERSION="22" NODE_MODULE="@postlight/parser@latest,single-file-cli@latest" setup_nodejs
PYTHON_VERSION="3.13" setup_uv
ensure_dependencies chromium
setup_uv
msg_info "Stopping Service"
systemctl stop archivebox
msg_ok "Stopped Service"
msg_info "Upgrading Playwright"
$STD uv pip install playwright --system --break-system-packages
$STD playwright install-deps chromium
msg_ok "Upgraded Playwright"
local migrate=0 py port base runtime
if [[ ! -x /opt/archivebox/venv/bin/archivebox ]]; then
migrate=1
msg_info "Moving ArchiveBox to its own Python 3.13 environment"
py="/usr/bin/python3.13"
[[ -x "$py" ]] || ensure_dependencies python3.13 || true
if [[ ! -x "$py" ]]; then
$STD uv python install --install-dir /opt/archivebox/python 3.13
py="$(UV_PYTHON_INSTALL_DIR=/opt/archivebox/python uv python find 3.13)"
fi
$STD uv venv --python "$py" /opt/archivebox/venv
msg_ok "Moved ArchiveBox to its own Python 3.13 environment"
fi
port="$(awk -F: '/^ExecStart=/ {print $NF}' /etc/systemd/system/archivebox.service 2>/dev/null)"
[[ "$port" =~ ^[0-9]+$ ]] || port=5797
cat <<EOF >/etc/systemd/system/archivebox.service
[Unit]
Description=ArchiveBox Server
After=network.target
[Service]
User=archivebox
WorkingDirectory=/opt/archivebox/data
ExecStartPre=/opt/archivebox/venv/bin/archivebox init
ExecStart=/opt/archivebox/venv/bin/archivebox server 0.0.0.0:${port}
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
msg_info "Updating ArchiveBox"
$STD uv pip install --python /opt/archivebox/venv/bin/python --upgrade archivebox
ln -sf /opt/archivebox/venv/bin/archivebox /usr/local/bin/archivebox
mkdir -p /home/archivebox/.config
chown -R archivebox:archivebox /opt/archivebox /home/archivebox
cd /opt/archivebox/data
$STD uv pip install --system --break-system-packages --upgrade --no-reinstall archivebox
sudo -u archivebox archivebox init
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox init
base="$(grep -oP '^BASE_URL\s*=\s*\K\S+' ArchiveBox.conf 2>/dev/null || true)"
if [[ -z "$base" || "$base" =~ ^https?://[0-9.]+(:[0-9]+)?/?$ ]]; then
base="http://$(hostname -I | awk '{print $1}'):${port}"
fi
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox config --set "BASE_URL=${base}" MERCURY_ENABLED=False
msg_ok "Updated ArchiveBox"
msg_info "Installing Extractors (Patience)"
runtime="/tmp/archivebox-runtime-$(id -u archivebox)"
install -d -m 700 -o archivebox -g archivebox "$runtime"
if $STD env HOME=/home/archivebox USER=archivebox LOGNAME=archivebox XDG_CONFIG_HOME=/home/archivebox/.config XDG_RUNTIME_DIR="$runtime" \
/opt/archivebox/venv/bin/archivebox install; then
msg_ok "Installed Extractors"
else
msg_warn "Some extractors failed to install - crawls stop until the next update succeeds"
fi
if [[ "$migrate" == 1 ]]; then
msg_info "Migrating Existing Snapshots (Patience)"
$STD sudo -u archivebox /opt/archivebox/venv/bin/archivebox update --rescan --migrate-only --index-only
msg_ok "Migrated Existing Snapshots"
fi
msg_info "Starting Service"
systemctl start archivebox
msg_ok "Started Service"
@@ -64,4 +114,4 @@ description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:8000/admin/login${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:5797/admin/${CL}"
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+2 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -40,7 +41,6 @@ function update_script() {
AUDIOMUSE_BACKEND="$(cat /opt/audiomuse-ai_data/.backend 2>/dev/null || echo cpu)"
REQ_COMMON="common.txt"
PY_VERSION="3.12"
case "$AUDIOMUSE_BACKEND" in
gpu)
REQ_ACCEL="gpu.txt"
@@ -49,7 +49,6 @@ function update_script() {
cpu-noavx2)
REQ_COMMON="common-noavx2.txt"
REQ_ACCEL="cpu-noavx2.txt"
PY_VERSION="3.11"
;;
*)
REQ_ACCEL="cpu.txt"
@@ -58,7 +57,7 @@ function update_script() {
msg_info "Updating Python Environment (${AUDIOMUSE_BACKEND})"
cd /opt/audiomuse-ai
$STD uv venv --clear --seed --python "$PY_VERSION" /opt/audiomuse-ai/.venv
$STD uv venv --seed --python 3.12 /opt/audiomuse-ai/.venv
$STD uv pip install --python /opt/audiomuse-ai/.venv \
-r "/opt/audiomuse-ai/requirements/${REQ_COMMON}" \
-r "/opt/audiomuse-ai/requirements/${REQ_ACCEL}"
+5 -6
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -37,16 +38,16 @@ function update_script() {
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "aurral" "lklynet/aurral" "tarball"
NODE_VERSION="26" NODE_MODULE="pnpm" setup_nodejs
ensure_dependencies git
NODE_VERSION="22" setup_nodejs
msg_info "Updating Aurral"
cd /opt/aurral
export VITE_APP_VERSION="$(cat ~/.aurral)"
export VITE_GITHUB_REPO="lklynet/aurral"
export VITE_RELEASE_CHANNEL="stable"
$STD pnpm install --frozen-lockfile --config.engine-strict=false --filter aurral-frontend --filter aurral-backend
$STD pnpm --filter aurral-frontend run build
$STD npm ci --workspace frontend --include-workspace-root=false
$STD npm run build --workspace frontend
$STD npm ci --workspace backend --omit=dev --include=optional --include-workspace-root=false
mkdir -p /opt/aurral_data
cat <<EOF >/opt/aurral/aurral.env
NODE_ENV=production
@@ -54,8 +55,6 @@ PORT=3001
AURRAL_DATA_DIR=/opt/aurral_data
APP_VERSION=$(cat ~/.aurral)
EOF
sed -i 's#backend/server.js#backend/server.ts#' /etc/systemd/system/aurral.service
systemctl daemon-reload
msg_ok "Updated Aurral"
msg_info "Starting Service"
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -47,7 +48,7 @@ function update_script() {
RUST_PROFILE="minimal" RUST_TOOLCHAIN="stable" setup_rust
setup_yq
AUTHENTIK_VERSION="version/2026.8.5"
AUTHENTIK_VERSION="version/2026.8.3"
# Source: https://github.com/goauthentik/fips/blob/main/Makefile#L26
XMLSEC_VERSION="1.3.12"
@@ -136,6 +137,7 @@ function update_script() {
export UV_COMPILE_BYTECODE="1"
export UV_LINK_MODE="copy"
export UV_NATIVE_TLS="1"
export UV_HTTP_TIMEOUT="300"
export RUSTUP_PERMIT_COPY_RENAME="true"
export UV_PYTHON_INSTALL_DIR="/usr/local/bin"
cd /opt/authentik
@@ -155,8 +157,7 @@ function update_script() {
rm -r /opt/authentik/blueprints
$STD find /opt/authentik-data -path '*/lost+found' -prune -o -exec chown authentik:authentik {} +
# Also catch installs whose version marker already reads 2026.8.x but whose units were never migrated
if [[ $MAJOR == 2026 && $MINOR -lt 8 ]] || grep -q "ExecStart=/opt/authentik/authentik-server" /etc/systemd/system/authentik-server.service; then
if [[ $MAJOR == 2026 && $MINOR -lt 8 ]]; then
msg_info "Updating Worker and Server config (from $MAJOR.$MINOR)"
cat <<EOF >>/etc/default/authentik-server
RUST_BACKTRACE=full
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+2 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -58,7 +59,7 @@ function update_script() {
mkdir -p "$INSTALL_DIR"/metadata_temp
$STD tar -xf ~/autocaliweb_bkp.tar --directory /
KEPUB_VERSION="$(/usr/bin/kepubify --version)"
CALIBRE_RELEASE="$(get_latest_github_release "kovidgoyal/calibre")"
CALIBRE_RELEASE="$(curl -s https://api.github.com/repos/kovidgoyal/calibre/releases/latest | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4)"
echo "${KEPUB_VERSION#v}" >"$INSTALL_DIR"/KEPUBIFY_RELEASE
echo "${CALIBRE_RELEASE#v}" >/"$INSTALL_DIR"/CALIBRE_RELEASE
sed 's/^/v/' ~/.autocaliweb >"$INSTALL_DIR"/ACW_RELEASE
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1 -8
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -46,19 +47,11 @@ function update_script() {
restore_backup
msg_info "Refreshing Bar-Assistant data pack"
rm -rf /opt/bar-assistant/resources/data
mkdir -p /opt/bar-assistant/resources/data
curl -fsSL https://github.com/bar-assistant/data/archive/refs/heads/v5.tar.gz | tar -xz --strip-components=1 -C /opt/bar-assistant/resources/data
chown -R www-data:www-data /opt/bar-assistant/resources/data
msg_ok "Refreshed Bar-Assistant data pack"
msg_info "Configuring Bar-Assistant"
cd /opt/bar-assistant
$STD composer install --no-interaction
$STD php artisan migrate --force
$STD php artisan storage:link
$STD php artisan starter-media:publish
$STD php artisan bar:setup-meilisearch
$STD php artisan scout:sync-index-settings
$STD php artisan config:cache
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+2 -1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -33,7 +34,7 @@ function update_script() {
if check_for_gh_release "beszel" "henrygd/beszel"; then
msg_info "Stopping Service"
systemctl stop beszel-hub
msg_ok "Stopped Service"
msg_info "Stopped Service"
msg_info "Updating Beszel"
$STD /opt/beszel/beszel update
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+20 -18
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -75,7 +76,8 @@ update_deb_based() {
msg_info "Configuring Bitmagnet"
cd /opt/bitmagnet
$STD go build -ldflags "-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=v$(cat ~/.bitmagnet)"
VREL=v$(curl -fsSL https://api.github.com/repos/bitmagnet-io/bitmagnet/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
$STD go build -ldflags "-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=$VREL"
chmod +x bitmagnet
msg_ok "Configured Bitmagnet"
@@ -91,10 +93,8 @@ update_alpine() {
msg_error "No ${APP} Installation Found!"
exit
fi
if [[ -f /opt/bitmagnet_version.txt ]]; then
mv /opt/bitmagnet_version.txt ~/.bitmagnet
fi
if check_for_gh_release "bitmagnet" "bitmagnet-io/bitmagnet"; then
RELEASE=$(curl -fsSL https://api.github.com/repos/bitmagnet-io/bitmagnet/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4) }')
if [ "${RELEASE}" != "$(cat /opt/bitmagnet_version.txt)" ] || [ ! -f /opt/bitmagnet_version.txt ]; then
msg_info "Backing up database"
rm -f /tmp/backup.sql
$STD sudo -u postgres pg_dump \
@@ -120,25 +120,27 @@ update_alpine() {
mv /tmp/backup.sql /opt/
msg_ok "Database backed up"
msg_info "Stopping Service"
msg_info "Updating ${APP} from $(cat /opt/bitmagnet_version.txt) to ${RELEASE}"
$STD apk -U upgrade
$STD service bitmagnet stop
msg_ok "Stopped Service"
create_backup /opt/bitmagnet/.env /opt/bitmagnet/config.yml
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "bitmagnet" "bitmagnet-io/bitmagnet" "tarball"
restore_backup
msg_info "Building ${APP}"
[ -f /opt/bitmagnet/.env ] && cp /opt/bitmagnet/.env /opt/
[ -f /opt/bitmagnet/config.yml ] && cp /opt/bitmagnet/config.yml /opt/
rm -rf /opt/bitmagnet/*
temp_file=$(mktemp)
curl -fsSL "https://github.com/bitmagnet-io/bitmagnet/archive/refs/tags/v${RELEASE}.tar.gz" -o "$temp_file"
tar zxf "$temp_file" --strip-components=1 -C /opt/bitmagnet
cd /opt/bitmagnet
$STD go build -ldflags "-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=v$(cat ~/.bitmagnet)"
VREL=v$RELEASE
$STD go build -ldflags "-s -w -X github.com/bitmagnet-io/bitmagnet/internal/version.GitTag=$VREL"
chmod +x bitmagnet
msg_ok "Built ${APP}"
msg_info "Starting Service"
[ -f "/opt/.env" ] && cp "/opt/.env" /opt/bitmagnet/
[ -f "/opt/config.yml" ] && cp "/opt/config.yml" /opt/bitmagnet/
rm -f "$temp_file"
echo "${RELEASE}" >/opt/bitmagnet_version.txt
$STD service bitmagnet start
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
}
-68
View File
@@ -1,68 +0,0 @@
#!/usr/bin/env bash
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/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/blinkospace/blinko
APP="Blinko"
var_tags="${var_tags:-notes;ai;knowledge}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-6144}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/blinko ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "blinko" "blinkospace/blinko"; then
msg_info "Stopping Blinko"
systemctl stop blinko
msg_ok "Stopped Blinko"
NODE_VERSION="24" NODE_MODULE="bun" setup_nodejs
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "blinko" "blinkospace/blinko" "tarball"
msg_info "Building Blinko"
cd /opt/blinko
$STD bun install
$STD bunx prisma generate
$STD bun run build:web
$STD bun run build:seed
mv dist/public server/public
cp -r server/lute.min.js server/vditor dist/
ln -s /opt/blinko_data/storage /opt/blinko/.blinko
msg_ok "Built Blinko"
msg_info "Starting Blinko"
systemctl start blinko
msg_ok "Started Blinko"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:1111${CL}"
+9 -45
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
@@ -9,30 +10,22 @@ source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_
APP="Blocky"
var_tags="${var_tags:-adblock}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
if [[ -z "${var_os:-}" ]] && command -v pveversion >/dev/null 2>&1; then
var_os=$(msg_menu "Choose the container OS" \
"debian" "Debian 13" \
"alpine" "Alpine 3.24 (smaller footprint)")
fi
if [[ "${var_os:-}" == "alpine" ]]; then
var_ram="${var_ram:-256}"
var_disk="${var_disk:-2}"
var_version="${var_version:-3.24}"
else
var_ram="${var_ram:-512}"
var_disk="${var_disk:-2}"
var_version="${var_version:-13}"
fi
header_info "$APP"
variables
color
catch_errors
update_deb_based() {
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/blocky ]]; then
msg_error "No ${APP} Installation Found!"
exit
@@ -54,35 +47,6 @@ update_deb_based() {
exit
}
update_alpine() {
if [[ ! -d /opt/blocky ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "blocky" "0xERR0R/blocky"; then
msg_info "Stopping Service"
rc-service blocky stop
msg_ok "Stopped Service"
create_backup /opt/blocky/config.yml
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "blocky" "0xERR0R/blocky" "prebuild" "latest" "/opt/blocky" "blocky_*_Linux_$(arch_resolve "x86_64" "arm64").tar.gz"
restore_backup
msg_info "Starting Service"
rc-service blocky start
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
function update_script() {
header_info
check_container_storage
check_container_resources
run_os_update
}
start
build_container
description
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+2 -6
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -48,12 +49,7 @@ function update_script() {
$STD corepack prepare "pnpm@${PNPM_VERSION}" --activate
$STD pnpm install --frozen-lockfile
# vite's bundler (rolldown) crashes at random on some builds, rolldown#10860.
for attempt in 1 2 3; do
$STD pnpm --filter client run build-only && break
[[ $attempt -eq 3 ]] && { msg_error "Client build failed three times"; exit 1; }
msg_warn "Client build failed (attempt $attempt), retrying"
done
$STD pnpm --filter client run build-only
$STD pnpm --filter server run build
cp -r /opt/bookorbit/client/dist /opt/bookorbit/server/public
mkdir -p /opt/bookorbit/server/migrations
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+12 -33
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -31,9 +32,15 @@ function update_script() {
exit
fi
local changed=0
if grep -q -- '--workers 2' /etc/systemd/system/borg-ui.service 2>/dev/null; then
msg_info "Reducing Service to a single worker"
sed -i 's/--workers 2/--workers 1/' /etc/systemd/system/borg-ui.service
systemctl daemon-reload
systemctl try-restart borg-ui
msg_ok "Reduced Service to a single worker"
fi
if check_for_gh_release "borg-ui" "karanhudia/borg-ui"; then
changed=1
msg_info "Stopping Service"
systemctl stop borg-ui
msg_ok "Stopped Service"
@@ -69,7 +76,7 @@ function update_script() {
if [[ "$(cat /opt/borg1-venv/.pinned_version 2>/dev/null)" != "$BORG1_VERSION" ]]; then
msg_info "Installing Borg ${BORG1_VERSION} (Patience)"
$STD uv venv --clear --python "$BORG_PYTHON" /opt/borg1-venv
$STD uv venv --python "$BORG_PYTHON" /opt/borg1-venv
$STD uv pip install --python /opt/borg1-venv pyfuse3 "borgbackup==${BORG1_VERSION}"
echo "$BORG1_VERSION" >/opt/borg1-venv/.pinned_version
ln -sf /opt/borg1-venv/bin/borg /usr/local/bin/borg
@@ -78,7 +85,7 @@ function update_script() {
if [[ "$(cat /opt/borg2-venv/.pinned_version 2>/dev/null)" != "${BORG2_VERSION}-${BORGSTORE_VERSION}" ]]; then
msg_info "Installing Borg ${BORG2_VERSION} (Patience)"
$STD uv venv --clear --python "$BORG_PYTHON" /opt/borg2-venv
$STD uv venv --python "$BORG_PYTHON" /opt/borg2-venv
$STD uv pip install --python /opt/borg2-venv pyfuse3 "borgbackup==${BORG2_VERSION}" "borgstore[rclone,sftp,rest,s3,blake3]==${BORGSTORE_VERSION}"
echo "${BORG2_VERSION}-${BORGSTORE_VERSION}" >/opt/borg2-venv/.pinned_version
ln -sf /opt/borg2-venv/bin/borg /usr/local/bin/borg2
@@ -103,37 +110,9 @@ function update_script() {
$STD uv venv --python "$BORG_PYTHON" /opt/borg-ui/.venv
$STD uv pip install --python /opt/borg-ui/.venv -r requirements.txt
msg_ok "Updated Python Environment"
fi
if [[ -f /opt/borg-ui/packaging/native/start.sh ]] && ! grep -q 'packaging/native/start.sh' /etc/systemd/system/borg-ui.service; then
msg_info "Switching Service to the upstream start script"
cat <<EOF >/etc/systemd/system/borg-ui.service
[Unit]
Description=Borg-UI
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/borg-ui
EnvironmentFile=/opt/borg-ui/.env
Environment=BORG_UI_VENV=/opt/borg-ui/.venv
ExecStart=/bin/bash /opt/borg-ui/packaging/native/start.sh
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
msg_ok "Switched Service to the upstream start script"
changed=1
fi
if [[ "$changed" == 1 ]]; then
msg_info "Starting Service"
systemctl restart borg-ui
systemctl start borg-ui
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
+6 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 community-scripts ORG
@@ -9,8 +10,8 @@ source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_
APP="ByteStash"
var_tags="${var_tags:-code}"
var_disk="${var_disk:-4}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
@@ -35,8 +36,9 @@ function update_script() {
systemctl stop bytestash-backend bytestash-frontend
msg_ok "Services Stopped"
[[ -d /opt/bytestash/data ]] && create_backup /opt/bytestash/data
[[ -d /opt/data ]] && create_backup /opt/data
CLEAN_INSTALL=1 CLEAN_INSTALL_KEEP="data" fetch_and_deploy_gh_release "bytestash" "jordan-dalby/ByteStash" "tarball"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "bytestash" "jordan-dalby/ByteStash" "tarball"
restore_backup
msg_info "Configuring ByteStash"
@@ -49,6 +51,7 @@ function update_script() {
msg_info "Starting Services"
systemctl start bytestash-backend bytestash-frontend
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
+4 -14
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
_CS_DEFAULT_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main"
_cs_boot="${COMMUNITY_SCRIPTS_CORE_DIR:-$(dirname "${BASH_SOURCE[0]}")/../../core}/core/build.func"
source "$_cs_boot" 2>/dev/null || source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/core/build.func")
# Copyright (c) 2021-2026 tteck
@@ -38,30 +39,19 @@ update_deb_based() {
exit
fi
cleanup_old_repo_files "caddy"
msg_info "Updating Caddy LXC"
apt_update_safe
$STD apt upgrade -y
msg_ok "Updated Caddy LXC"
[[ -f ~/.caddy ]] || dpkg-query -W -f='${Version}' caddy >~/.caddy 2>/dev/null || true
if check_for_gh_release "caddy" "caddyserver/caddy"; then
DPKG_FORCE_CONFOLD=1 fetch_and_deploy_gh_release "caddy" "caddyserver/caddy" "binary" "latest" "/opt/caddy" "caddy_*_linux_$(arch_resolve).deb"
systemctl restart caddy
fi
if command -v xcaddy >/dev/null 2>&1; then
if check_for_gh_release "xcaddy" "caddyserver/xcaddy"; then
setup_go
fetch_and_deploy_gh_release "xcaddy" "caddyserver/xcaddy" "binary"
if (($(awk '/^MemTotal:/ {print int($2 / 1024)}' /proc/meminfo) < 1900)); then
msg_warn "xCaddy updated; its test build was skipped because linking Caddy needs about 2 GB RAM"
else
msg_info "Updating xCaddy"
$STD xcaddy build
msg_ok "Updated xCaddy"
fi
msg_info "Updating xCaddy"
$STD xcaddy build
msg_ok "Updated xCaddy"
fi
fi
msg_ok "Updated successfully!"
Loaded 100 of 1759 files, more files were not shown because too many files have changed in this diff. Show more