diff --git a/.github/DISCUSSION_TEMPLATE/request-script.yml b/.github/DISCUSSION_TEMPLATE/request-script.yml
index 64d694c97..a2cf93136 100644
--- a/.github/DISCUSSION_TEMPLATE/request-script.yml
+++ b/.github/DISCUSSION_TEMPLATE/request-script.yml
@@ -12,13 +12,17 @@ body:
Requests may be closed if the application is out of scope, abandoned, too new, not publicly verifiable, or not suitable for a reliable Proxmox VE Helper-Scripts integration.
General requirements:
- - The application should be self-hosted.
- - The project should have an official public source repository.
- - The project should provide official releases, tags, or release tarballs.
- - The project should be actively maintained.
- - The project should generally have at least 1,000 stars or a comparable public adoption signal.
- - The latest official release or tag should not be older than 6 months.
- - The project itself should be at least 6 months old.
+ - The application must be self-hosted.
+ - The project must have an official public source repository.
+ - The project must provide official releases, tags, or release tarballs.
+ - The project must be actively maintained.
+ - The official source repository must have at least 1,000 stars.
+ - The latest official release or tag must not be older than 6 months.
+ - The project itself must be at least 6 months old.
+
+ Projects that do not meet these requirements may be closed without further evaluation.
+
+ Exceptions to the 1,000-star requirement are rare and require a clearly verifiable, significant public adoption signal.
- type: input
id: application-name
@@ -47,6 +51,35 @@ body:
validations:
required: true
+ - type: markdown
+ attributes:
+ value: |
+ ## ⚠️ Project Eligibility
+
+ Before continuing, verify that the requested project meets the minimum requirements below.
+
+ **Projects with fewer than 1,000 stars are generally not eligible for a script request.**
+
+ Exceptions are only considered where there is a clearly verifiable, significant public adoption signal.
+
+ - type: input
+ id: repository-stars
+ attributes:
+ label: Repository Stars
+ description: Enter the current number of stars of the official source repository.
+ placeholder: "e.g., 15,000"
+ validations:
+ required: true
+
+ - type: checkboxes
+ id: minimum-stars
+ attributes:
+ label: Minimum Adoption Requirement
+ description: Confirm that you have verified the project's public adoption.
+ options:
+ - label: The official source repository has at least 1,000 stars.
+ required: true
+
- type: textarea
id: app-description
attributes:
diff --git a/.github/changelogs/2026/07.md b/.github/changelogs/2026/07.md
index 4c20ca3e6..2ecd58f89 100644
--- a/.github/changelogs/2026/07.md
+++ b/.github/changelogs/2026/07.md
@@ -1,3 +1,94 @@
+## 2026-07-11
+
+### 🆕 New Scripts
+
+ - LocalAGI ([#15687](https://github.com/community-scripts/ProxmoxVE/pull/15687))
+
+### 🚀 Updated Scripts
+
+ - fix(adventurelog): allow pnpm build scripts so install/update doesn't abort [@TowyTowy](https://github.com/TowyTowy) ([#15681](https://github.com/community-scripts/ProxmoxVE/pull/15681))
+
+ - #### 🐞 Bug Fixes
+
+ - fix(fileflows): install .NET 10 ASP.NET Core Runtime to match current release [@TowyTowy](https://github.com/TowyTowy) ([#15702](https://github.com/community-scripts/ProxmoxVE/pull/15702))
+ - Fix spacing on VLAN Input Box in haos-vm.sh [@pumrum](https://github.com/pumrum) ([#15696](https://github.com/community-scripts/ProxmoxVE/pull/15696))
+
+### 💾 Core
+
+ - #### ✨ New Features
+
+ - [tools.func]: Add function to handle deployment via GitLab release tags [@tremor021](https://github.com/tremor021) ([#15641](https://github.com/community-scripts/ProxmoxVE/pull/15641))
+
+## 2026-07-10
+
+### 🆕 New Scripts
+
+ - Squid ([#15605](https://github.com/community-scripts/ProxmoxVE/pull/15605))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - Adapt to new artifact filename format for pocket id [@wollew](https://github.com/wollew) ([#15689](https://github.com/community-scripts/ProxmoxVE/pull/15689))
+ - Fireshare: Fix for install and upgrade to v1.7.3 [@tremor021](https://github.com/tremor021) ([#15673](https://github.com/community-scripts/ProxmoxVE/pull/15673))
+ - Endurain: Fix update procedure [@tremor021](https://github.com/tremor021) ([#15674](https://github.com/community-scripts/ProxmoxVE/pull/15674))
+
+## 2026-07-09
+
+### 🚀 Updated Scripts
+
+ - fix(pihole): repair Unbound DNS-over-TLS (DoT) forwarding config [@TowyTowy](https://github.com/TowyTowy) ([#15654](https://github.com/community-scripts/ProxmoxVE/pull/15654))
+
+## 2026-07-08
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - Opencloud: Bump version to 7.2.1 [@vhsdream](https://github.com/vhsdream) ([#15655](https://github.com/community-scripts/ProxmoxVE/pull/15655))
+ - BabyBuddy: Harden update script [@MickLesk](https://github.com/MickLesk) ([#15642](https://github.com/community-scripts/ProxmoxVE/pull/15642))
+
+## 2026-07-07
+
+### 🆕 New Scripts
+
+ - Forgejo-Runner ([#15046](https://github.com/community-scripts/ProxmoxVE/pull/15046))
+
+## 2026-07-06
+
+### 🚀 Updated Scripts
+
+ - Fix alignment in various ct end messages [@tremor021](https://github.com/tremor021) ([#15632](https://github.com/community-scripts/ProxmoxVE/pull/15632))
+- Immich: Update libvips to 8.18.4 [@vhsdream](https://github.com/vhsdream) ([#15619](https://github.com/community-scripts/ProxmoxVE/pull/15619))
+
+ - #### 🐞 Bug Fixes
+
+ - Wizarr: Build JS and CSS static assets [@vhsdream](https://github.com/vhsdream) ([#15634](https://github.com/community-scripts/ProxmoxVE/pull/15634))
+ - RustDesk Server: Update URL format in rustdeskserver.sh [@tremor021](https://github.com/tremor021) ([#15626](https://github.com/community-scripts/ProxmoxVE/pull/15626))
+ - attempt to port docker-vm to support arm64 [@asylumexp](https://github.com/asylumexp) ([#15611](https://github.com/community-scripts/ProxmoxVE/pull/15611))
+ - fix(plane): don't clobber global app var, breaking /usr/bin/update [@asylumexp](https://github.com/asylumexp) ([#15612](https://github.com/community-scripts/ProxmoxVE/pull/15612))
+
+ - #### 🔧 Refactor
+
+ - cliproxyapi: point setup message at /management.html [@austinpilz](https://github.com/austinpilz) ([#15628](https://github.com/community-scripts/ProxmoxVE/pull/15628))
+
+### 🗑️ Deleted Scripts
+
+ - Remove: FlowiseAI [@MickLesk](https://github.com/MickLesk) ([#15624](https://github.com/community-scripts/ProxmoxVE/pull/15624))
+
+## 2026-07-05
+
+### 🆕 New Scripts
+
+ - excalidash ([#15604](https://github.com/community-scripts/ProxmoxVE/pull/15604))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - fix: homarr: cli [@CrazyWolf13](https://github.com/CrazyWolf13) ([#15603](https://github.com/community-scripts/ProxmoxVE/pull/15603))
+ - immich: vacuum smart_search/face_search before VectorChord bump [@irishpadres](https://github.com/irishpadres) ([#15607](https://github.com/community-scripts/ProxmoxVE/pull/15607))
+
## 2026-07-04
### 🚀 Updated Scripts
diff --git a/.github/workflows/close-invalid-pr-template.yml b/.github/workflows/close-invalid-pr-template.yml
new file mode 100644
index 000000000..ce4c9a1bb
--- /dev/null
+++ b/.github/workflows/close-invalid-pr-template.yml
@@ -0,0 +1,163 @@
+name: Close PRs Missing Template
+
+on:
+ pull_request_target:
+ branches: ["main"]
+ types: [opened, edited, reopened, synchronize, labeled]
+
+jobs:
+ validate-pr-template:
+ if: github.repository == 'community-scripts/ProxmoxVE'
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ issues: write
+ contents: read
+ steps:
+ - name: Close PR if it does not follow the PR template
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const pr = context.payload.pull_request;
+ const prNumber = pr.number;
+ const author = pr.user.login;
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+
+ const allowedBots = [
+ "push-app-to-main[bot]",
+ "push-app-to-main",
+ "community-scripts-pr-app",
+ "github-actions[bot]",
+ "dependabot[bot]",
+ ];
+
+ if (allowedBots.includes(author) || author.endsWith("[bot]")) {
+ core.info(`PR #${prNumber} by bot "${author}" — skipping template validation.`);
+ return;
+ }
+
+ const association = pr.author_association;
+ const exemptAssociations = ["OWNER", "MEMBER", "COLLABORATOR"];
+ if (exemptAssociations.includes(association)) {
+ core.info(`PR #${prNumber} by ${association} "${author}" — skipping template validation.`);
+ return;
+ }
+
+ const labels = pr.labels.map((label) => label.name);
+ const skipLabels = ["automated pr", "keep open"];
+
+ if (skipLabels.some((label) => labels.includes(label))) {
+ core.info(`PR #${prNumber} has a skip label (${labels.join(", ")}) — skipping template validation.`);
+ return;
+ }
+
+ if (pr.draft) {
+ core.info(`PR #${prNumber} is a draft — skipping template validation.`);
+ return;
+ }
+
+ const body = pr.body || "";
+ const failures = [];
+
+ const requiredSections = [
+ "## ✍️ Description",
+ "## ✅ Prerequisites",
+ "## 🛠️ Type of Change",
+ ];
+
+ for (const section of requiredSections) {
+ if (!body.includes(section)) {
+ failures.push(`Missing required section: \`${section}\``);
+ }
+ }
+
+ const descriptionMatch = body.match(
+ /## ✍️ Description\s*\n+([\s\S]*?)(?=\n## )/i
+ );
+ const description = (descriptionMatch?.[1] || "").trim();
+ if (!description) {
+ failures.push("The **Description** section is empty.");
+ }
+
+ const prerequisiteCheckboxes = [
+ "**Self-review completed**",
+ "**Tested thoroughly**",
+ "**No security risks**",
+ ];
+
+ for (const checkbox of prerequisiteCheckboxes) {
+ const escaped = checkbox.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1");
+ const regex = new RegExp(`- \\[(x|X)\\]\\s*${escaped}`, "i");
+ if (!regex.test(body)) {
+ failures.push(`Prerequisite not checked: ${checkbox}`);
+ }
+ }
+
+ const typeOfChangeCheckboxes = [
+ "🐞 **Bug fix**",
+ "✨ **New feature**",
+ "💥 **Breaking change**",
+ "🆕 **New script**",
+ "🌍 **Website update**",
+ "🔧 **Refactoring / Code Cleanup**",
+ "📝 **Documentation update**",
+ ];
+
+ const hasTypeChecked = typeOfChangeCheckboxes.some((checkbox) => {
+ const escaped = checkbox.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1");
+ const regex = new RegExp(`- \\[(x|X)\\]\\s*${escaped}`, "i");
+ return regex.test(body);
+ });
+
+ if (!hasTypeChecked) {
+ failures.push("At least one **Type of Change** checkbox must be checked.");
+ }
+
+ if (failures.length === 0) {
+ core.info(`PR #${prNumber} follows the PR template.`);
+ return;
+ }
+
+ 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 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");
+
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number: prNumber,
+ body: comment,
+ });
+
+ 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"],
+ });
diff --git a/.github/workflows/delete-merged-branches.yml b/.github/workflows/delete-merged-branches.yml
index aafaf2b44..09a9f8ab1 100644
--- a/.github/workflows/delete-merged-branches.yml
+++ b/.github/workflows/delete-merged-branches.yml
@@ -91,6 +91,30 @@ jobs:
let skipped = 0;
for (const branch of candidates) {
+ // A branch name can be reused after an earlier PR was merged. Never delete a
+ // branch while it is the head of a current open PR, even if it is also a
+ // candidate from an older merged PR.
+ try {
+ const { data: openPrs } = await github.rest.pulls.list({
+ owner,
+ repo,
+ state: "open",
+ head: `${owner}:${branch}`,
+ per_page: 1,
+ });
+
+ if (openPrs.length > 0) {
+ console.log(`Skipped "${branch}" (head of open PR #${openPrs[0].number})`);
+ skipped++;
+ continue;
+ }
+ } catch (error) {
+ // Do not risk deleting a branch if GitHub cannot confirm it has no open PR.
+ console.log(`Failed to check open PRs for "${branch}": ${error.message}`);
+ skipped++;
+ continue;
+ }
+
// Confirm the branch still exists and isn't protected.
let branchData;
try {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2dabf3bbe..ab484bb31 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -74,6 +74,9 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
+
+
+
@@ -87,7 +90,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
-July (4 entries)
+July (11 entries)
[View July 2026 Changelog](.github/changelogs/2026/07.md)
@@ -499,6 +502,208 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
+## 2026-07-17
+
+### 🆕 New Scripts
+
+ - Invidious ([#15824](https://github.com/community-scripts/ProxmoxVE/pull/15824))
+- OxiCloud ([#15823](https://github.com/community-scripts/ProxmoxVE/pull/15823))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - webtrees: initialize database schema before admin user creation [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15837](https://github.com/community-scripts/ProxmoxVE/pull/15837))
+ - Fix DocuSeal missing Leptonica deps on install and update [@Copilot](https://github.com/Copilot) ([#15858](https://github.com/community-scripts/ProxmoxVE/pull/15858))
+ - apache-guacamole: detect installed extensions during update [@TowyTowy](https://github.com/TowyTowy) ([#15841](https://github.com/community-scripts/ProxmoxVE/pull/15841))
+ - CLIProxyAPI: fix update deleting config.yaml [@austinpilz](https://github.com/austinpilz) ([#15834](https://github.com/community-scripts/ProxmoxVE/pull/15834))
+ - esphome: install libusb-1.0-0 for ESP-IDF native builds [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15838](https://github.com/community-scripts/ProxmoxVE/pull/15838))
+
+ - #### ✨ New Features
+
+ - AFFiNE: Bump to 0.27.0 [@MickLesk](https://github.com/MickLesk) ([#15848](https://github.com/community-scripts/ProxmoxVE/pull/15848))
+ - n8n: unpin / use latest release [@MickLesk](https://github.com/MickLesk) ([#15817](https://github.com/community-scripts/ProxmoxVE/pull/15817))
+ - Pin Opencloud to v7.3.0 [@vhsdream](https://github.com/vhsdream) ([#15826](https://github.com/community-scripts/ProxmoxVE/pull/15826))
+
+ - #### 🔧 Refactor
+
+ - SFTPGo: Update APT Repo & Re-Enable Script [@MickLesk](https://github.com/MickLesk) ([#15829](https://github.com/community-scripts/ProxmoxVE/pull/15829))
+
+### 💾 Core
+
+ - #### ✨ New Features
+
+ - tools.func: enhance rbenv with profile updates / bundle in bashrc [@MickLesk](https://github.com/MickLesk) ([#15822](https://github.com/community-scripts/ProxmoxVE/pull/15822))
+ - feat(build.func): notify users when already on a pinned script version [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15819](https://github.com/community-scripts/ProxmoxVE/pull/15819))
+
+ - #### 💥 Breaking Changes
+
+ - MongoDB: Implement kernel version check and patch [@MickLesk](https://github.com/MickLesk) ([#15821](https://github.com/community-scripts/ProxmoxVE/pull/15821))
+
+### 🧰 Tools
+
+ - #### ✨ New Features
+
+ - update-lxc: autoremove and autoclean after apt full-upgrade [@soupy-boy](https://github.com/soupy-boy) ([#15831](https://github.com/community-scripts/ProxmoxVE/pull/15831))
+
+## 2026-07-16
+
+### 🆕 New Scripts
+
+ - Sync-In ([#15812](https://github.com/community-scripts/ProxmoxVE/pull/15812))
+- Beaverhabits ([#15813](https://github.com/community-scripts/ProxmoxVE/pull/15813))
+- Notediscovery ([#15811](https://github.com/community-scripts/ProxmoxVE/pull/15811))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - Pin Immich to v3.0.3 [@vhsdream](https://github.com/vhsdream) ([#15790](https://github.com/community-scripts/ProxmoxVE/pull/15790))
+
+## 2026-07-15
+
+### 🆕 New Scripts
+
+ - Nexterm ([#15688](https://github.com/community-scripts/ProxmoxVE/pull/15688))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - 2fauth: minor fixes for 8.0.0 [@MickLesk](https://github.com/MickLesk) ([#15795](https://github.com/community-scripts/ProxmoxVE/pull/15795))
+ - SnapOtter: refactor update process to prebuild [@MickLesk](https://github.com/MickLesk) ([#15797](https://github.com/community-scripts/ProxmoxVE/pull/15797))
+
+### 💾 Core
+
+ - #### 🔧 Refactor
+
+ - tools.func: default Docker setup to official repo [@MickLesk](https://github.com/MickLesk) ([#15794](https://github.com/community-scripts/ProxmoxVE/pull/15794))
+
+## 2026-07-14
+
+### 🆕 New Scripts
+
+ - Grav ([#15773](https://github.com/community-scripts/ProxmoxVE/pull/15773))
+- Yuvomi ([#15772](https://github.com/community-scripts/ProxmoxVE/pull/15772))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - Lychee: Preserve uploads and ownership during update [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15768](https://github.com/community-scripts/ProxmoxVE/pull/15768))
+ - Wanderer: Clean deploy and install plugins for v0.20.0 update [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15759](https://github.com/community-scripts/ProxmoxVE/pull/15759))
+ - FileFlows: Handle update API 401, force update, and Node install [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15766](https://github.com/community-scripts/ProxmoxVE/pull/15766))
+ - BirdNET-Go: Match new upstream release asset naming [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15758](https://github.com/community-scripts/ProxmoxVE/pull/15758))
+ - [Upstream Fix] Immich: Fix loader priority [@vhsdream](https://github.com/vhsdream) ([#15755](https://github.com/community-scripts/ProxmoxVE/pull/15755))
+
+ - #### ✨ New Features
+
+ - Bump OpenCloud version to v7.2.2 [@MickLesk](https://github.com/MickLesk) ([#15769](https://github.com/community-scripts/ProxmoxVE/pull/15769))
+ - Silverbullet: Add optional Runtime API install via Chromium [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15761](https://github.com/community-scripts/ProxmoxVE/pull/15761))
+
+ - #### 💥 Breaking Changes
+
+ - Pangolin: Bump to 1.20.0 | BREAKING: Switch to PostgreSQL [@MickLesk](https://github.com/MickLesk) ([#15682](https://github.com/community-scripts/ProxmoxVE/pull/15682))
+
+ - #### 🔧 Refactor
+
+ - AFFiNE: Pin to v0.26.3 [@MickLesk](https://github.com/MickLesk) ([#15782](https://github.com/community-scripts/ProxmoxVE/pull/15782))
+
+## 2026-07-13
+
+### 🆕 New Scripts
+
+ - LeafWiki ([#15748](https://github.com/community-scripts/ProxmoxVE/pull/15748))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - fix(hyperion): keep service running after container reboot [@TowyTowy](https://github.com/TowyTowy) ([#15653](https://github.com/community-scripts/ProxmoxVE/pull/15653))
+ - Change sign-in URL to admin URL in affine.sh [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15741](https://github.com/community-scripts/ProxmoxVE/pull/15741))
+ - immich: use actual PostgreSQL version for VectorChord package lookup [@mnavon](https://github.com/mnavon) ([#15705](https://github.com/community-scripts/ProxmoxVE/pull/15705))
+ - fix storyteller release selection for stable web tags [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15736](https://github.com/community-scripts/ProxmoxVE/pull/15736))
+ - Docmost: Fix update procedure [@MickLesk](https://github.com/MickLesk) ([#15732](https://github.com/community-scripts/ProxmoxVE/pull/15732))
+ - fix(shinobi): remove obsolete --unsafe-perm npm flag [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15730](https://github.com/community-scripts/ProxmoxVE/pull/15730))
+
+ - #### 💥 Breaking Changes
+
+ - reitti: update to v5 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#15635](https://github.com/community-scripts/ProxmoxVE/pull/15635))
+
+### 💾 Core
+
+ - #### 🐞 Bug Fixes
+
+ - fix(build.func): parse script status without jq dependency [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15729](https://github.com/community-scripts/ProxmoxVE/pull/15729))
+
+ - #### 🔧 Refactor
+
+ - tools.func: some improvements (sql injection / command injection / guard) [@MickLesk](https://github.com/MickLesk) ([#15661](https://github.com/community-scripts/ProxmoxVE/pull/15661))
+
+## 2026-07-12
+
+### 🆕 New Scripts
+
+ - AFFiNE ([#15690](https://github.com/community-scripts/ProxmoxVE/pull/15690))
+
+### 🚀 Updated Scripts
+
+ - Immich: Bump version to 3.0.2 [@vhsdream](https://github.com/vhsdream) ([#15668](https://github.com/community-scripts/ProxmoxVE/pull/15668))
+
+### ❔ Uncategorized
+
+ - fix(immich): correct Python indentation error in ct/immich.sh heredoc patch [@Copilot](https://github.com/Copilot) ([#15723](https://github.com/community-scripts/ProxmoxVE/pull/15723))
+
+## 2026-07-11
+
+### 🆕 New Scripts
+
+ - LocalAGI ([#15687](https://github.com/community-scripts/ProxmoxVE/pull/15687))
+
+### 🚀 Updated Scripts
+
+ - fix(adventurelog): allow pnpm build scripts so install/update doesn't abort [@TowyTowy](https://github.com/TowyTowy) ([#15681](https://github.com/community-scripts/ProxmoxVE/pull/15681))
+
+ - #### 🐞 Bug Fixes
+
+ - fix(fileflows): install .NET 10 ASP.NET Core Runtime to match current release [@TowyTowy](https://github.com/TowyTowy) ([#15702](https://github.com/community-scripts/ProxmoxVE/pull/15702))
+ - Fix spacing on VLAN Input Box in haos-vm.sh [@pumrum](https://github.com/pumrum) ([#15696](https://github.com/community-scripts/ProxmoxVE/pull/15696))
+
+### 💾 Core
+
+ - #### ✨ New Features
+
+ - [tools.func]: Add function to handle deployment via GitLab release tags [@tremor021](https://github.com/tremor021) ([#15641](https://github.com/community-scripts/ProxmoxVE/pull/15641))
+
+## 2026-07-10
+
+### 🆕 New Scripts
+
+ - Squid ([#15605](https://github.com/community-scripts/ProxmoxVE/pull/15605))
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - Adapt to new artifact filename format for pocket id [@wollew](https://github.com/wollew) ([#15689](https://github.com/community-scripts/ProxmoxVE/pull/15689))
+ - Fireshare: Fix for install and upgrade to v1.7.3 [@tremor021](https://github.com/tremor021) ([#15673](https://github.com/community-scripts/ProxmoxVE/pull/15673))
+ - Endurain: Fix update procedure [@tremor021](https://github.com/tremor021) ([#15674](https://github.com/community-scripts/ProxmoxVE/pull/15674))
+
+## 2026-07-09
+
+### 🚀 Updated Scripts
+
+ - fix(pihole): repair Unbound DNS-over-TLS (DoT) forwarding config [@TowyTowy](https://github.com/TowyTowy) ([#15654](https://github.com/community-scripts/ProxmoxVE/pull/15654))
+
+## 2026-07-08
+
+### 🚀 Updated Scripts
+
+ - #### 🐞 Bug Fixes
+
+ - Opencloud: Bump version to 7.2.1 [@vhsdream](https://github.com/vhsdream) ([#15655](https://github.com/community-scripts/ProxmoxVE/pull/15655))
+ - BabyBuddy: Harden update script [@MickLesk](https://github.com/MickLesk) ([#15642](https://github.com/community-scripts/ProxmoxVE/pull/15642))
+
## 2026-07-07
### 🆕 New Scripts
@@ -1054,135 +1259,4 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### ✨ New Features
- - [core] Implement backup and restore functions [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15067](https://github.com/community-scripts/ProxmoxVE/pull/15067))
-
-## 2026-06-11
-
-### 🆕 New Scripts
-
- - Clickhouse ([#15045](https://github.com/community-scripts/ProxmoxVE/pull/15045))
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - Manyfold: add new dependency [@MickLesk](https://github.com/MickLesk) ([#15040](https://github.com/community-scripts/ProxmoxVE/pull/15040))
- - OpenArchiver: switch Rebuild Function [@MickLesk](https://github.com/MickLesk) ([#15042](https://github.com/community-scripts/ProxmoxVE/pull/15042))
- - CLIProxyAPI: Save management password to creds file [@tremor021](https://github.com/tremor021) ([#15051](https://github.com/community-scripts/ProxmoxVE/pull/15051))
- - Jotty: Fix wrong path test in config restore [@vhsdream](https://github.com/vhsdream) ([#15038](https://github.com/community-scripts/ProxmoxVE/pull/15038))
- - Fix for cross-seed after node upgrade [@TorinFrancis](https://github.com/TorinFrancis) ([#15025](https://github.com/community-scripts/ProxmoxVE/pull/15025))
-
- - #### 🔧 Refactor
-
- - Alpine-Nextcloud: Upgrade PHP and dependencies in installation script [@MickLesk](https://github.com/MickLesk) ([#15039](https://github.com/community-scripts/ProxmoxVE/pull/15039))
- - [arm64] porting stage 1: set script arm64 statuses to yes [@asylumexp](https://github.com/asylumexp) ([#15052](https://github.com/community-scripts/ProxmoxVE/pull/15052))
-
-### 💾 Core
-
- - #### ✨ New Features
-
- - misc scripts: add support for arm64 [@asylumexp](https://github.com/asylumexp) ([#12639](https://github.com/community-scripts/ProxmoxVE/pull/12639))
-
- - #### 🔧 Refactor
-
- - [arm64] remove logic for custom debian arm64 template [@asylumexp](https://github.com/asylumexp) ([#15050](https://github.com/community-scripts/ProxmoxVE/pull/15050))
-
-### 📚 Documentation
-
- - (github): Revise script request template [@MickLesk](https://github.com/MickLesk) ([#15058](https://github.com/community-scripts/ProxmoxVE/pull/15058))
-
-## 2026-06-10
-
-### 🆕 New Scripts
-
- - Baserow ([#14968](https://github.com/community-scripts/ProxmoxVE/pull/14968))
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - Koillection: Fix update procedure [@tremor021](https://github.com/tremor021) ([#15033](https://github.com/community-scripts/ProxmoxVE/pull/15033))
-
-## 2026-06-09
-
-### 🆕 New Scripts
-
- - paperclip ([#14990](https://github.com/community-scripts/ProxmoxVE/pull/14990))
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - endurain: Install pytz package during backend setup [@MickLesk](https://github.com/MickLesk) ([#15014](https://github.com/community-scripts/ProxmoxVE/pull/15014))
-
- - #### 🔧 Refactor
-
- - Refactor: Proxmox Backup Server - use deb822 [@MickLesk](https://github.com/MickLesk) ([#15013](https://github.com/community-scripts/ProxmoxVE/pull/15013))
-
-## 2026-06-08
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - security: Fix HTTP to HTTPS for all package and repository downloads [@MickLesk](https://github.com/MickLesk) ([#15009](https://github.com/community-scripts/ProxmoxVE/pull/15009))
- - homelable: preserve MCP server config across updates [@ferr079](https://github.com/ferr079) ([#14996](https://github.com/community-scripts/ProxmoxVE/pull/14996))
- - changedetection: migrate Python install to uv venv [@ferr079](https://github.com/ferr079) ([#14995](https://github.com/community-scripts/ProxmoxVE/pull/14995))
-
- - #### 🔧 Refactor
-
- - Update Flowwiseai to node 24 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14999](https://github.com/community-scripts/ProxmoxVE/pull/14999))
-
-### 🧰 Tools
-
- - #### 🐞 Bug Fixes
-
- - security: Fix MITM RCE vulnerability in microcode scripts (CVE) [@MickLesk](https://github.com/MickLesk) ([#15007](https://github.com/community-scripts/ProxmoxVE/pull/15007))
-
-## 2026-06-07
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - Immich: use actual installed PostgreSQL version for vchord package [@MickLesk](https://github.com/MickLesk) ([#14989](https://github.com/community-scripts/ProxmoxVE/pull/14989))
-
- - #### 🔧 Refactor
-
- - Navidrome: remove genereic filebrowser addon setup [@MickLesk](https://github.com/MickLesk) ([#14991](https://github.com/community-scripts/ProxmoxVE/pull/14991))
-
-## 2026-06-06
-
-### 🆕 New Scripts
-
- - Spliit ([#14966](https://github.com/community-scripts/ProxmoxVE/pull/14966))
-- Tolgee ([#14965](https://github.com/community-scripts/ProxmoxVE/pull/14965))
-- XYOps ([#14967](https://github.com/community-scripts/ProxmoxVE/pull/14967))
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - Photoprism: Allow env variables with spaces [@Badintral](https://github.com/Badintral) ([#14969](https://github.com/community-scripts/ProxmoxVE/pull/14969))
-
-## 2026-06-05
-
-### 🆕 New Scripts
-
- - MatterJS-Server ([#14951](https://github.com/community-scripts/ProxmoxVE/pull/14951))
-- CyberChef ([#14952](https://github.com/community-scripts/ProxmoxVE/pull/14952))
-
-### 🚀 Updated Scripts
-
- - #### 🐞 Bug Fixes
-
- - Jackett: Create missing .env file [@tremor021](https://github.com/tremor021) ([#14959](https://github.com/community-scripts/ProxmoxVE/pull/14959))
- - OpenThread-BR: use systemd instead of init.d [@tomfrenzel](https://github.com/tomfrenzel) ([#14942](https://github.com/community-scripts/ProxmoxVE/pull/14942))
-
- - #### ✨ New Features
-
- - AMD IGPU support [@Learath](https://github.com/Learath) ([#14944](https://github.com/community-scripts/ProxmoxVE/pull/14944))
-
- - #### 💥 Breaking Changes
-
- - update authentik to 2026.5.2 [@thieneret](https://github.com/thieneret) ([#14846](https://github.com/community-scripts/ProxmoxVE/pull/14846))
\ No newline at end of file
+ - [core] Implement backup and restore functions [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15067](https://github.com/community-scripts/ProxmoxVE/pull/15067))
\ No newline at end of file
diff --git a/ct/2fauth.sh b/ct/2fauth.sh
index 506f140d8..5b5dc3eb7 100644
--- a/ct/2fauth.sh
+++ b/ct/2fauth.sh
@@ -60,6 +60,7 @@ function update_script() {
php artisan 2fauth:install
chown -R www-data: /opt/2fauth
chmod -R 755 /opt/2fauth
+ $STD php artisan 2fauth:fix-passport-key-permissions
$STD systemctl restart php8.4-fpm
$STD systemctl restart nginx
msg_ok "Configured 2FAuth"
diff --git a/ct/adventurelog.sh b/ct/adventurelog.sh
index 0da4c09c0..98327747e 100644
--- a/ct/adventurelog.sh
+++ b/ct/adventurelog.sh
@@ -60,6 +60,7 @@ function update_script() {
$STD .venv/bin/python -m manage migrate
cd /opt/adventurelog/frontend
+ grep -q "^dangerouslyAllowAllBuilds:" ./pnpm-workspace.yaml 2>/dev/null || echo "dangerouslyAllowAllBuilds: true" >>./pnpm-workspace.yaml
$STD pnpm i
$STD pnpm build
msg_ok "Updated AdventureLog"
diff --git a/ct/affine.sh b/ct/affine.sh
new file mode 100644
index 000000000..1480c2a99
--- /dev/null
+++ b/ct/affine.sh
@@ -0,0 +1,131 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/toeverything/AFFiNE
+
+APP="AFFiNE"
+var_tags="${var_tags:-knowledge;notes;workspace}"
+var_cpu="${var_cpu:-4}"
+var_ram="${var_ram:-8192}"
+var_disk="${var_disk:-20}"
+var_os="${var_os:-debian}"
+var_version="${var_version:-13}"
+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
+
+ if [[ ! -d /opt/affine ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ RELEASE="v0.27.0"
+ if check_for_gh_release "affine_app" "toeverything/AFFiNE" "${RELEASE}" "each release is tested individually before the version is updated. Please do not open issues for this"; then
+ msg_info "Stopping Services"
+ systemctl stop affine-web affine-worker
+ msg_ok "Stopped Services"
+
+ ensure_dependencies cmake
+
+ create_backup /root/.affine/config /root/.affine/storage
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "affine_app" "toeverything/AFFiNE" "tarball" "${RELEASE}" "/opt/affine"
+
+ msg_info "Rebuilding Application (Patience ~25 mins, don't close the console!)"
+ cd /opt/affine
+ source /root/.profile
+ export PATH="/root/.cargo/bin:/root/.rbenv/shims:$PATH"
+
+ set -a && source /opt/affine/.env && set +a
+
+ export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
+ export VITE_CORE_COMMIT_SHA=$(cat ~/.affine_app)
+
+ # Initialize git repo (required for build process)
+ export HUSKY=0
+ $STD git init -q
+ $STD git config user.email "build@local"
+ $STD git config user.name "Build"
+ $STD git add -A
+ $STD git commit -q -m "update" --no-verify --allow-empty
+
+ # Force Turbo to run sequentially
+ mkdir -p /opt/affine/.turbo
+ cat </opt/affine/.turbo/config.json
+{
+ "concurrency": 1
+}
+TURBO
+
+ $STD corepack enable
+ $STD corepack prepare yarn@4.13.0 --activate
+ $STD yarn config set enableTelemetry 0
+
+ export NODE_OPTIONS="--max-old-space-size=2048"
+ $STD yarn install
+ $STD npm install -g typescript
+
+ $STD yarn affine @affine/native build
+ $STD yarn affine @affine/server-native build
+
+ # Create architecture-specific symlinks
+ ln -sf /opt/affine/packages/backend/native/server-native.node \
+ /opt/affine/packages/backend/native/server-native.x64.node
+ ln -sf /opt/affine/packages/backend/native/server-native.node \
+ /opt/affine/packages/backend/native/server-native.arm64.node
+ ln -sf /opt/affine/packages/backend/native/server-native.node \
+ /opt/affine/packages/backend/native/server-native.armv7.node
+
+ $STD yarn affine init
+ $STD yarn affine build -p @affine/reader
+ $STD yarn affine build -p @affine/server
+
+ export NODE_OPTIONS="--max-old-space-size=4096"
+ $STD yarn affine build -p @affine/web
+ $STD yarn affine build -p @affine/admin
+
+ # Copy web assets
+ mkdir -p /opt/affine/packages/backend/server/static
+ cp -r /opt/affine/packages/frontend/apps/web/dist/* /opt/affine/packages/backend/server/static/
+ mkdir -p /opt/affine/packages/backend/server/static/admin
+ cp -r /opt/affine/packages/frontend/admin/dist/* /opt/affine/packages/backend/server/static/admin/
+
+ # Mobile manifest placeholder
+ mkdir -p /opt/affine/packages/backend/server/static/mobile
+ echo '{"publicPath":"/","js":[],"css":[],"gitHash":"","description":""}' \
+ >/opt/affine/packages/backend/server/static/mobile/assets-manifest.json
+
+ # Run migrations
+ cd /opt/affine/packages/backend/server
+ set -a && source /opt/affine/.env && set +a
+ $STD node ./scripts/self-host-predeploy.js
+
+ restore_backup
+
+ msg_info "Starting Services"
+ systemctl start affine-web affine-worker
+ 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}:3010/admin${CL}"
diff --git a/ct/apache-guacamole.sh b/ct/apache-guacamole.sh
index bbd9673cc..89f1b973b 100644
--- a/ct/apache-guacamole.sh
+++ b/ct/apache-guacamole.sh
@@ -146,7 +146,7 @@ function update_script() {
# Check and upgrade optional extensions
# TOTP Extension
- if [[ -f /etc/guacamole/extensions/guacamole-auth-totp-*.jar ]]; then
+ if compgen -G "/etc/guacamole/extensions/guacamole-auth-totp-*.jar" >/dev/null; then
msg_info "Updating TOTP Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-totp-*.jar
curl_download "/tmp/guacamole-auth-totp.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-totp-${LATEST_SERVER}.tar.gz"
@@ -158,7 +158,7 @@ function update_script() {
fi
# DUO Extension
- if [[ -f /etc/guacamole/extensions/guacamole-auth-duo-*.jar ]]; then
+ if compgen -G "/etc/guacamole/extensions/guacamole-auth-duo-*.jar" >/dev/null; then
msg_info "Updating DUO Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-duo-*.jar
curl_download "/tmp/guacamole-auth-duo.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-duo-${LATEST_SERVER}.tar.gz"
@@ -170,7 +170,7 @@ function update_script() {
fi
# LDAP Extension
- if [[ -f /etc/guacamole/extensions/guacamole-auth-ldap-*.jar ]]; then
+ if compgen -G "/etc/guacamole/extensions/guacamole-auth-ldap-*.jar" >/dev/null; then
msg_info "Updating LDAP Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-ldap-*.jar
curl_download "/tmp/guacamole-auth-ldap.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-ldap-${LATEST_SERVER}.tar.gz"
@@ -182,7 +182,7 @@ function update_script() {
fi
# Quick Connect Extension
- if [[ -f /etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar ]]; then
+ if compgen -G "/etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar" >/dev/null; then
msg_info "Updating Quick Connect Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar
curl_download "/tmp/guacamole-auth-quickconnect.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-quickconnect-${LATEST_SERVER}.tar.gz"
@@ -194,7 +194,7 @@ function update_script() {
fi
# History Recording Storage Extension
- if [[ -f /etc/guacamole/extensions/guacamole-history-recording-storage-*.jar ]]; then
+ if compgen -G "/etc/guacamole/extensions/guacamole-history-recording-storage-*.jar" >/dev/null; then
msg_info "Updating History Recording Storage Extension"
rm -f /etc/guacamole/extensions/guacamole-history-recording-storage-*.jar
curl_download "/tmp/guacamole-history-recording-storage.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-history-recording-storage-${LATEST_SERVER}.tar.gz"
diff --git a/ct/babybuddy.sh b/ct/babybuddy.sh
index 29fc7703a..b718a42ef 100644
--- a/ct/babybuddy.sh
+++ b/ct/babybuddy.sh
@@ -40,7 +40,8 @@ function update_script() {
create_backup /opt/babybuddy/babybuddy/settings/production.py
msg_info "Cleaning old files"
- find . -mindepth 1 -maxdepth 1 ! -name '.venv' -exec rm -rf {} +
+ cd /opt/babybuddy || exit
+ find . -mindepth 1 -maxdepth 1 ! -name '.venv' -exec rm -rf -- {} +
msg_ok "Cleaned old files"
fetch_and_deploy_gh_release "babybuddy" "babybuddy/babybuddy" "tarball"
@@ -51,6 +52,7 @@ function update_script() {
source .venv/bin/activate
$STD uv pip install -r requirements.txt
export DJANGO_SETTINGS_MODULE=babybuddy.settings.production
+ $STD python manage.py makemigrations
$STD python manage.py migrate
msg_ok "Updated ${APP}"
diff --git a/ct/beaverhabits.sh b/ct/beaverhabits.sh
new file mode 100644
index 000000000..168be42d4
--- /dev/null
+++ b/ct/beaverhabits.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/daya0576/beaverhabits
+
+APP="BeaverHabits"
+var_tags="${var_tags:-habits;tracking;productivity}"
+var_cpu="${var_cpu:-2}"
+var_ram="${var_ram:-1024}"
+var_disk="${var_disk:-4}"
+var_os="${var_os:-debian}"
+var_version="${var_version:-13}"
+var_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/beaverhabits ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "beaverhabits" "daya0576/beaverhabits"; then
+ msg_info "Stopping Service"
+ systemctl stop beaverhabits
+ msg_ok "Stopped Service"
+
+ create_backup /opt/beaverhabits/.user
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "beaverhabits" "daya0576/beaverhabits" "tarball"
+
+ msg_info "Syncing Dependencies"
+ cd /opt/beaverhabits
+ $STD uv sync --no-dev
+ msg_ok "Synced Dependencies"
+
+ restore_backup
+
+ msg_info "Starting Service"
+ systemctl start beaverhabits
+ 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/register{CL}"
diff --git a/ct/birdnet-go.sh b/ct/birdnet-go.sh
index 39db9ca1f..6319219f9 100644
--- a/ct/birdnet-go.sh
+++ b/ct/birdnet-go.sh
@@ -37,7 +37,7 @@ function update_script() {
systemctl stop birdnet
msg_ok "Stopped Service"
- fetch_and_deploy_gh_release "birdnet" "tphakala/birdnet-go" "prebuild" "latest" "/opt/birdnet" "birdnet-go-linux-$(arch_resolve).tar.gz"
+ fetch_and_deploy_gh_release "birdnet" "tphakala/birdnet-go" "prebuild" "latest" "/opt/birdnet" "birdnet-go-linux-$(arch_resolve)*.tar.gz"
msg_info "Deploying Binary"
cp /opt/birdnet/birdnet-go /usr/local/bin/birdnet-go
diff --git a/ct/cliproxyapi.sh b/ct/cliproxyapi.sh
index 9eb49a1a6..40f9f2b1f 100644
--- a/ct/cliproxyapi.sh
+++ b/ct/cliproxyapi.sh
@@ -36,8 +36,12 @@ function update_script() {
systemctl stop cliproxyapi
msg_ok "Stopped CLIProxyAPI"
+ create_backup /opt/cliproxyapi/config.yaml
+
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "cliproxyapi" "router-for-me/CLIProxyAPI" "prebuild" "latest" "/opt/cliproxyapi" "CLIProxyAPI_*_linux_$(arch_resolve "amd64" "aarch64").tar.gz"
+ restore_backup
+
msg_info "Starting CLIProxyAPI"
systemctl start cliproxyapi
msg_ok "Started CLIProxyAPI"
diff --git a/ct/docmost.sh b/ct/docmost.sh
index c3392c585..1c6271c98 100644
--- a/ct/docmost.sh
+++ b/ct/docmost.sh
@@ -39,7 +39,6 @@ function update_script() {
create_backup /opt/docmost/.env \
/opt/docmost/data
-
fetch_and_deploy_gh_release "docmost" "docmost/docmost" "tarball"
restore_backup
@@ -54,9 +53,11 @@ function update_script() {
sed -i '/^@Module({$/i @Global()' /opt/docmost/apps/server/src/core/core.module.ts
fi
+ msg_info "Configuring Docmost"
+ cd /opt/docmost
$STD pnpm install --force
$STD pnpm build
- msg_ok "Updated ${APP}"
+ msg_ok "Configured Docmost"
msg_info "Starting Service"
systemctl start docmost
diff --git a/ct/docuseal.sh b/ct/docuseal.sh
index d856d1694..9fbdca313 100644
--- a/ct/docuseal.sh
+++ b/ct/docuseal.sh
@@ -35,6 +35,8 @@ function update_script() {
systemctl stop docuseal docuseal-sidekiq
msg_ok "Stopped Services"
+ ensure_dependencies libleptonica-dev libleptonica6
+
create_backup /opt/docuseal/.env \
/opt/docuseal/data
diff --git a/ct/endurain.sh b/ct/endurain.sh
index 934532aea..cdfd6eb0a 100644
--- a/ct/endurain.sh
+++ b/ct/endurain.sh
@@ -34,31 +34,22 @@ function update_script() {
systemctl stop endurain
msg_ok "Stopped Service"
- msg_info "Creating Backup"
- cp /opt/endurain/.env /opt/endurain.env
- cp /opt/endurain/frontend/dist/env.js /opt/endurain.env.js
- msg_ok "Created Backup"
-
+ create_backup /opt/endurain/.env /opt/endurain/frontend/dist/env.js
CLEAN_INSTALL=1 fetch_and_deploy_codeberg_release "endurain" "endurain-project/endurain" "tarball" "latest" "/opt/endurain"
msg_info "Preparing Update"
cd /opt/endurain
- rm -rf \
- /opt/endurain/{docs,example.env,screenshot_01.png} \
- /opt/endurain/docker* \
- /opt/endurain/*.yml
- cp /opt/endurain.env /opt/endurain/.env
- rm /opt/endurain.env
+ rm -rf /opt/endurain/{docs,example.env,screenshot_01.png} /opt/endurain/docker* /opt/endurain/*.yml
msg_ok "Prepared Update"
msg_info "Updating Frontend"
cd /opt/endurain/frontend
$STD npm ci
$STD npm run build
- cp /opt/endurain.env.js /opt/endurain/frontend/dist/env.js
- rm /opt/endurain.env.js
msg_ok "Updated Frontend"
+ restore_backup
+
msg_info "Updating Backend"
cd /opt/endurain/backend
UV_VERSION=$(grep -Po 'required-version\s*=\s*"\K[^"]+' pyproject.toml 2>/dev/null || echo "0.11.18")
diff --git a/ct/esphome.sh b/ct/esphome.sh
index 4dcf1ea06..086be0e83 100644
--- a/ct/esphome.sh
+++ b/ct/esphome.sh
@@ -28,6 +28,7 @@ function update_script() {
msg_error "No ${APP} Installation Found!"
exit
fi
+ ensure_dependencies libusb-1.0-0
msg_info "Stopping Service"
systemctl stop esphome-device-builder 2>/dev/null || true
diff --git a/ct/fileflows.sh b/ct/fileflows.sh
index 1f33d2dd0..3f72cef69 100644
--- a/ct/fileflows.sh
+++ b/ct/fileflows.sh
@@ -31,28 +31,83 @@ function update_script() {
exit
fi
- update_available=$(curl -fsSL -X 'GET' "http://localhost:19200/api/status/update-available" -H 'accept: application/json' | jq .UpdateAvailable)
- if [[ "${update_available}" == "true" ]]; then
- msg_info "Stopping Service"
- systemctl --all stop 'fileflows*'
- msg_info "Stopped Service"
+ local proceed=false
- msg_info "Creating Backup"
- ls /opt/*.tar.gz &>/dev/null && rm -f /opt/*.tar.gz
- backup_filename="/opt/${APP}_backup_$(date +%F).tar.gz"
- tar -czf "$backup_filename" -C /opt/fileflows Data
- msg_ok "Backup Created"
-
- fetch_and_deploy_from_url "https://fileflows.com/downloads/zip" "/opt/fileflows"
-
- msg_info "Starting Service"
- systemctl --all start 'fileflows*'
- msg_ok "Started Service"
- msg_ok "Updated successfully!"
+ if systemctl list-unit-files 'fileflows.service' --no-legend 2>/dev/null | grep -q '^fileflows\.service'; then
+ tmp=$(mktemp)
+ http_code=$(curl -sSL -X 'GET' "http://localhost:19200/api/status/update-available" -H 'accept: application/json' -o "$tmp" -w '%{http_code}' 2>/dev/null) || http_code="000"
+ if [[ "$http_code" == "200" ]]; then
+ update_available=$(jq -r '.UpdateAvailable // false' "$tmp" 2>/dev/null)
+ rm -f "$tmp"
+ if [[ "${update_available}" == "true" ]]; then
+ proceed=true
+ else
+ msg_ok "No update required. ${APP} is already at latest version"
+ exit
+ fi
+ else
+ rm -f "$tmp"
+ if [[ "$http_code" == "401" ]]; then
+ msg_warn "Could not check for updates: API returned 401 (security may be enabled)."
+ else
+ msg_warn "Could not check for updates: API unreachable (HTTP ${http_code})."
+ fi
+ if [[ "${FORCE_UPDATE:-}" == "1" ]]; then
+ proceed=true
+ else
+ read -r -p "${TAB3}Force update without version check? [y/N]: " CONFIRM
+ if [[ "$CONFIRM" =~ ^([yY][eE][sS]|[yY])$ ]]; then
+ proceed=true
+ else
+ msg_error "Update aborted."
+ exit
+ fi
+ fi
+ fi
else
- msg_ok "No update required. ${APP} is already at latest version"
+ proceed=true
fi
+ if [[ "$proceed" != "true" ]]; then
+ exit
+ fi
+
+ msg_info "Stopping Service"
+ systemctl --all stop 'fileflows*'
+ msg_ok "Stopped Service"
+
+ msg_info "Creating Backup"
+ ls /opt/*.tar.gz &>/dev/null && rm -f /opt/*.tar.gz
+ backup_filename="/opt/${APP}_backup_$(date +%F).tar.gz"
+ tar -czf "$backup_filename" -C /opt/fileflows Data
+ msg_ok "Backup Created"
+
+ msg_info "Ensuring ASP.NET Core Runtime"
+ if [[ "$(arch_resolve)" == "arm64" ]]; then
+ if [[ ! -x /usr/lib/dotnet10/dotnet ]]; then
+ curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
+ $STD bash /tmp/dotnet-install.sh --channel 10.0 --runtime aspnetcore --install-dir /usr/lib/dotnet10
+ ln -sf /usr/lib/dotnet10/dotnet /usr/bin/dotnet
+ rm -f /tmp/dotnet-install.sh
+ fi
+ elif ! is_package_installed "aspnetcore-runtime-10.0"; then
+ $STD apt remove -y aspnetcore-runtime-8.0 aspnetcore-runtime-9.0 2>/dev/null || true
+ setup_deb822_repo \
+ "microsoft" \
+ "https://packages.microsoft.com/keys/microsoft-2025.asc" \
+ "https://packages.microsoft.com/debian/13/prod/" \
+ "trixie"
+ $STD apt install -y aspnetcore-runtime-10.0
+ fi
+ msg_ok "Ensured ASP.NET Core Runtime"
+
+ fetch_and_deploy_from_url "https://fileflows.com/downloads/zip" "/opt/fileflows"
+
+ msg_info "Starting Service"
+ systemctl --all start 'fileflows*'
+ msg_ok "Started Service"
+ msg_ok "Updated successfully!"
+
exit
}
diff --git a/ct/fireshare.sh b/ct/fireshare.sh
index 296cb6c01..f10be013b 100644
--- a/ct/fireshare.sh
+++ b/ct/fireshare.sh
@@ -35,12 +35,22 @@ function update_script() {
systemctl stop fireshare
msg_ok "Stopped Service"
- mv /opt/fireshare/fireshare.env /opt
+ create_backup /opt/fireshare/fireshare.env
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "fireshare" "ShaneIsrael/fireshare" "tarball"
- mv /opt/fireshare.env /opt/fireshare
+ restore_backup
rm -f /usr/local/bin/fireshare
- msg_info "Updating Fireshare"
+ if ! grep -q "__FIRESHARE_PORT__" /etc/nginx/nginx.conf; then
+ cp /opt/fireshare/app/nginx/prod.conf /etc/nginx/nginx.conf
+ sed -i 's|root /processed/|root /opt/fireshare-processed/|g' /etc/nginx/nginx.conf
+ sed -i 's/^user[[:space:]]\+nginx;/user root;/' /etc/nginx/nginx.conf
+ sed -i 's|root[[:space:]]\+/app/build;|root /opt/fireshare/app/client/build;|' /etc/nginx/nginx.conf
+ sed -i 's/__FIRESHARE_PORT__/80/g' /etc/nginx/nginx.conf
+ cp /opt/fireshare/app/nginx/error.html /etc/nginx/
+ cp /opt/fireshare/app/nginx/api_unavailable.html /etc/nginx/
+ fi
+ msg_info "Configuring Fireshare"
+
cd /opt/fireshare
$STD uv venv --clear
$STD .venv/bin/python -m ensurepip --upgrade
@@ -53,13 +63,10 @@ function update_script() {
export VIDEO_DIRECTORY=/opt/fireshare-videos
export PROCESSED_DIRECTORY=/opt/fireshare-processed
$STD uv run flask db upgrade
-
- msg_info "Building Fireshare Client"
cd /opt/fireshare/app/client
$STD npm install
$STD npm run build
- msg_ok "Built Fireshare Client"
- msg_ok "Updated Fireshare"
+ msg_ok "Configured Fireshare"
msg_info "Starting Service"
systemctl start fireshare
diff --git a/ct/grav.sh b/ct/grav.sh
new file mode 100644
index 000000000..9271a029e
--- /dev/null
+++ b/ct/grav.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: Raffaele (rafspiny)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://getgrav.org/
+
+APP="Grav"
+var_tags="${var_tags:-cms}"
+var_cpu="${var_cpu:-1}"
+var_ram="${var_ram:-2048}"
+var_disk="${var_disk:-8}"
+var_os="${var_os:-debian}"
+var_version="${var_version:-13}"
+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
+
+ if [[ ! -d "/opt/grav" ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "grav" "getgrav/grav"; then
+ msg_info "Creating Backup"
+ cd /opt/grav
+ bin/grav backup -nq
+ msg_ok "Backup Created"
+ bin/gpm self-upgrade -y
+ cd -
+ chown -R www-data:www-data /opt/grav
+ msg_ok "Update Successful"
+ 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}:80${CL}"
diff --git a/ct/headers/affine b/ct/headers/affine
new file mode 100644
index 000000000..0e1d759f0
--- /dev/null
+++ b/ct/headers/affine
@@ -0,0 +1,6 @@
+ ___ _____________ _ ________
+ / | / ____/ ____(_) | / / ____/
+ / /| | / /_ / /_ / / |/ / __/
+ / ___ |/ __/ / __/ / / /| / /___
+/_/ |_/_/ /_/ /_/_/ |_/_____/
+
diff --git a/ct/headers/beaverhabits b/ct/headers/beaverhabits
new file mode 100644
index 000000000..0a11d4242
--- /dev/null
+++ b/ct/headers/beaverhabits
@@ -0,0 +1,6 @@
+ ____ __ __ __ _ __
+ / __ )___ ____ __ _____ _____/ / / /___ _/ /_ (_) /______
+ / __ / _ \/ __ `/ | / / _ \/ ___/ /_/ / __ `/ __ \/ / __/ ___/
+ / /_/ / __/ /_/ /| |/ / __/ / / __ / /_/ / /_/ / / /_(__ )
+/_____/\___/\__,_/ |___/\___/_/ /_/ /_/\__,_/_.___/_/\__/____/
+
diff --git a/ct/headers/grav b/ct/headers/grav
new file mode 100644
index 000000000..3e041ab1e
--- /dev/null
+++ b/ct/headers/grav
@@ -0,0 +1,6 @@
+ ______
+ / ____/________ __ __
+ / / __/ ___/ __ `/ | / /
+/ /_/ / / / /_/ /| |/ /
+\____/_/ \__,_/ |___/
+
diff --git a/ct/headers/invidious b/ct/headers/invidious
new file mode 100644
index 000000000..c858744dd
--- /dev/null
+++ b/ct/headers/invidious
@@ -0,0 +1,6 @@
+ ____ _ ___
+ / _/___ _ __(_)___/ (_)___ __ _______
+ / // __ \ | / / / __ / / __ \/ / / / ___/
+ _/ // / / / |/ / / /_/ / / /_/ / /_/ (__ )
+/___/_/ /_/|___/_/\__,_/_/\____/\__,_/____/
+
diff --git a/ct/headers/leafwiki b/ct/headers/leafwiki
new file mode 100644
index 000000000..dcfacbddf
--- /dev/null
+++ b/ct/headers/leafwiki
@@ -0,0 +1,6 @@
+ __ _____ ___ __ _
+ / / ___ ____ _/ __/ | / (_) /__(_)
+ / / / _ \/ __ `/ /_ | | /| / / / //_/ /
+ / /___/ __/ /_/ / __/ | |/ |/ / / ,< / /
+/_____/\___/\__,_/_/ |__/|__/_/_/|_/_/
+
diff --git a/ct/headers/localagi b/ct/headers/localagi
new file mode 100644
index 000000000..c47da9aac
--- /dev/null
+++ b/ct/headers/localagi
@@ -0,0 +1,6 @@
+ __ _____ __________
+ / / ____ _________ _/ / | / ____/ _/
+ / / / __ \/ ___/ __ `/ / /| |/ / __ / /
+ / /___/ /_/ / /__/ /_/ / / ___ / /_/ // /
+/_____/\____/\___/\__,_/_/_/ |_\____/___/
+
diff --git a/ct/headers/nexterm b/ct/headers/nexterm
new file mode 100644
index 000000000..7d52bc561
--- /dev/null
+++ b/ct/headers/nexterm
@@ -0,0 +1,6 @@
+ _ __ __
+ / | / /__ _ __/ /____ _________ ___
+ / |/ / _ \| |/_/ __/ _ \/ ___/ __ `__ \
+ / /| / __/> /_/ __/ / / / / / / /
+/_/ |_/\___/_/|_|\__/\___/_/ /_/ /_/ /_/
+
diff --git a/ct/headers/notediscovery b/ct/headers/notediscovery
new file mode 100644
index 000000000..df51d962d
--- /dev/null
+++ b/ct/headers/notediscovery
@@ -0,0 +1,6 @@
+ _ __ __ ____ _
+ / | / /___ / /____ / __ \(_)_____________ _ _____ _______ __
+ / |/ / __ \/ __/ _ \/ / / / / ___/ ___/ __ \ | / / _ \/ ___/ / / /
+ / /| / /_/ / /_/ __/ /_/ / (__ ) /__/ /_/ / |/ / __/ / / /_/ /
+/_/ |_/\____/\__/\___/_____/_/____/\___/\____/|___/\___/_/ \__, /
+ /____/
diff --git a/ct/headers/oxicloud b/ct/headers/oxicloud
new file mode 100644
index 000000000..b48c6ee29
--- /dev/null
+++ b/ct/headers/oxicloud
@@ -0,0 +1,6 @@
+ ____ _ ________ __
+ / __ \_ __(_) ____/ /___ __ ______/ /
+ / / / / |/_/ / / / / __ \/ / / / __ /
+/ /_/ /> / /___/ / /_/ / /_/ / /_/ /
+\____/_/|_/_/\____/_/\____/\__,_/\__,_/
+
diff --git a/ct/headers/squid b/ct/headers/squid
new file mode 100644
index 000000000..3d826ef75
--- /dev/null
+++ b/ct/headers/squid
@@ -0,0 +1,6 @@
+ _____ _ __
+ / ___/____ ___ __(_)___/ /
+ \__ \/ __ `/ / / / / __ /
+ ___/ / /_/ / /_/ / / /_/ /
+/____/\__, /\__,_/_/\__,_/
+ /_/
diff --git a/ct/headers/sync-in b/ct/headers/sync-in
new file mode 100644
index 000000000..f5ae41c66
--- /dev/null
+++ b/ct/headers/sync-in
@@ -0,0 +1,6 @@
+ _____ _
+ / ___/__ ______ _____ (_)___
+ \__ \/ / / / __ \/ ___/_____/ / __ \
+ ___/ / /_/ / / / / /__/_____/ / / / /
+/____/\__, /_/ /_/\___/ /_/_/ /_/
+ /____/
diff --git a/ct/headers/yuvomi b/ct/headers/yuvomi
new file mode 100644
index 000000000..3247d3dcc
--- /dev/null
+++ b/ct/headers/yuvomi
@@ -0,0 +1,6 @@
+__ __ _
+\ \/ /_ ___ ______ ____ ___ (_)
+ \ / / / / | / / __ \/ __ `__ \/ /
+ / / /_/ /| |/ / /_/ / / / / / / /
+/_/\__,_/ |___/\____/_/ /_/ /_/_/
+
diff --git a/ct/immich.sh b/ct/immich.sh
index 48ae7ceda..1ea66f467 100644
--- a/ct/immich.sh
+++ b/ct/immich.sh
@@ -110,7 +110,7 @@ EOF
msg_ok "Image-processing libraries up to date"
fi
- RELEASE="v3.0.1"
+ RELEASE="v3.0.3"
if check_for_gh_release "Immich" "immich-app/immich" "${RELEASE}" "each release is tested individually before the version is updated. Please do not open issues for this"; then
if [[ $(cat ~/.immich) > "2.5.1" ]]; then
msg_info "Enabling Maintenance Mode"
@@ -124,7 +124,9 @@ EOF
systemctl stop immich-web
systemctl stop immich-ml
msg_ok "Stopped Services"
- VCHORD_RELEASE="1.0.0"
+ VCHORD_RELEASE="1.1.1"
+ PG_VERSION=$(ls /etc/postgresql/ 2>/dev/null | sort -V | tail -1)
+ PG_VERSION=${PG_VERSION:-16}
[[ -f ~/.vchord_version ]] && mv ~/.vchord_version ~/.vectorchord
if check_for_gh_release "VectorChord" "tensorchord/VectorChord" "${VCHORD_RELEASE}" "updated together with Immich after testing"; then
# dead tuples in smart_search/face_search make the REINDEX below fail with
@@ -132,7 +134,7 @@ EOF
# while still on the old extension version, a post-upgrade vacuum errors instead
$STD sudo -u postgres psql -d immich -c "VACUUM (ANALYZE) smart_search;"
$STD sudo -u postgres psql -d immich -c "VACUUM (ANALYZE) face_search;"
- fetch_and_deploy_gh_release "VectorChord" "tensorchord/VectorChord" "binary" "${VCHORD_RELEASE}" "/tmp" "postgresql-16-vchord_*_$(arch_resolve).deb"
+ fetch_and_deploy_gh_release "VectorChord" "tensorchord/VectorChord" "binary" "${VCHORD_RELEASE}" "/tmp" "postgresql-${PG_VERSION}-vchord_*_$(arch_resolve).deb"
systemctl restart postgresql
$STD sudo -u postgres psql -d immich -c "ALTER EXTENSION vector UPDATE;"
$STD sudo -u postgres psql -d immich -c "ALTER EXTENSION vchord UPDATE;"
@@ -328,6 +330,27 @@ EOF
systemctl daemon-reload
fi
+ # MickLesk temporary patch for HEIC thumbnail gen
+ msg_info "Patching media.repository.js"
+ MEDIA_REPO_JS="/opt/immich/app/dist/repositories/media.repository.js"
+ if [[ -f "$MEDIA_REPO_JS" ]]; then
+ python3 - <<'PY'
+from pathlib import Path
+p = Path('/opt/immich/app/dist/repositories/media.repository.js')
+s = p.read_text()
+old = "(0, sharp_1.default)(input).metadata()"
+new = "(0, sharp_1.default)(input, { unlimited: true, limitInputPixels: false }).metadata()"
+if new in s:
+ print('hotfix already there')
+elif old in s:
+ p.write_text(s.replace(old, new, 1))
+ print('hotfix applied')
+else:
+ print('pattern not found, skipped')
+PY
+ fi
+ msg_ok "Patched media.repository.js"
+
# chown excluding upload dir contents (may be a mount with restricted permissions)
chown immich:immich "$INSTALL_DIR"
find "$INSTALL_DIR" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +
@@ -478,6 +501,7 @@ function compile_libvips() {
$STD git clone https://github.com/libvips/libvips.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBVIPS_REVISION"
+ $STD git apply "$BASE_DIR"/server/sources/libvips-patches/0001-put-other-loaders-ahead-of-dcrawload.patch
$STD meson setup build --buildtype=release --libdir=lib -Dintrospection=disabled -Dtiff=disabled
cd build
$STD ninja install
diff --git a/ct/invidious.sh b/ct/invidious.sh
new file mode 100644
index 000000000..b3b68a632
--- /dev/null
+++ b/ct/invidious.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: vhsdream
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/iv-org/invidious
+
+APP="Invidious"
+var_tags="${var_tags:-streaming}"
+var_cpu="${var_cpu:-2}"
+var_ram="${var_ram:-4096}"
+var_disk="${var_disk:-20}"
+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/invidious ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "Invidious" "iv-org/invidious"; then
+ msg_info "Stopping services"
+ $STD systemctl stop invidious-companion invidious
+ msg_ok "Stopped services"
+
+ create_backup /opt/invidious/config/config.yml
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Invidious" "iv-org/invidious" "tarball" "latest" "/opt/invidious"
+ if check_for_gh_release "Invidious-Companion" "iv-org/invidious-companion"; then
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Invidious-Companion" "iv-org/invidious-companion" "prebuild" "latest" "/opt/invidious-companion" "invidious_companion-$(arch_resolve x86_64 aarch64)-unknown-linux-gnu.tar.gz"
+ fi
+
+ msg_info "Rebuilding Invidious"
+ cd /opt/invidious
+ INVIDIOUS_VERSION="$(cat ~/.invidious 2>/dev/null || echo "unknown")"
+ INVIDIOUS_VERSION="${INVIDIOUS_VERSION#v}"
+ sed -i \
+ -e "s~^\(\s*CURRENT_BRANCH\s*=\).*~\1 \"master\"~" \
+ -e "s~^\(\s*CURRENT_COMMIT\s*=\).*~\1 \"\"~" \
+ -e "s~^\(\s*CURRENT_VERSION\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
+ -e "s~^\(\s*CURRENT_TAG\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
+ -e "s~^\(\s*ASSET_COMMIT\s*=\).*~\1 \"\"~" \
+ src/invidious.cr
+ $STD make
+ msg_ok "Rebuilt Invidious"
+
+ restore_backup
+
+ msg_info "Starting services"
+ $STD systemctl start invidious invidious-companion
+ msg_ok "Started services"
+ msg_ok "Updated successfully!"
+ fi
+ exit
+}
+
+start
+build_container
+description
+
+msg_ok "Completed successfully!\n"
+echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
+echo -e "${INFO}${YW}Access it using the following URL:${CL}"
+echo -e "${GATEWAY}${BGN}http://${IP}:3000${CL}"
diff --git a/ct/leafwiki.sh b/ct/leafwiki.sh
new file mode 100644
index 000000000..20633cb57
--- /dev/null
+++ b/ct/leafwiki.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/perber/leafwiki
+
+APP="LeafWiki"
+var_tags="${var_tags:-wiki;markdown;notes}"
+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 /usr/local/bin/leafwiki ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "leafwiki" "perber/leafwiki"; then
+ msg_info "Stopping Service"
+ systemctl stop leafwiki
+ msg_ok "Stopped Service"
+
+ create_backup /opt/leafwiki/data
+ fetch_and_deploy_gh_release "leafwiki" "perber/leafwiki" "singlefile" "latest" "/usr/local/bin" "leafwiki-v*-linux-$(arch_resolve)"
+ restore_backup
+
+ msg_info "Starting Service"
+ systemctl start leafwiki
+ 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}"
diff --git a/ct/localagi.sh b/ct/localagi.sh
new file mode 100644
index 000000000..b2f9fbe91
--- /dev/null
+++ b/ct/localagi.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: BillyOutlast
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/mudler/LocalAGI
+
+APP="LocalAGI"
+var_tags="${var_tags:-ai}"
+var_cpu="${var_cpu:-2}"
+var_ram="${var_ram:-4096}"
+var_disk="${var_disk:-20}"
+var_os="${var_os:-debian}"
+var_version="${var_version:-13}"
+var_arm64="${var_arm64:-no}"
+var_unprivileged="${var_unprivileged:-1}"
+var_gpu="${var_gpu:-no}"
+
+header_info "$APP"
+variables
+color
+catch_errors
+
+function update_script() {
+ header_info
+ check_container_storage
+ check_container_resources
+
+ if [[ ! -d /opt/localagi ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "localagi" "mudler/LocalAGI"; then
+ msg_info "Stopping Service"
+ systemctl stop localagi
+ msg_ok "Stopped Service"
+
+ create_backup /opt/localagi/.env
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "localagi" "mudler/LocalAGI" "tarball" "latest" "/opt/localagi"
+ restore_backup
+
+ msg_info "Building LocalAGI"
+ cd /opt/localagi/webui/react-ui
+ $STD bun install
+ $STD bun run build
+ cd /opt/localagi
+ $STD go build -o /usr/local/bin/localagi
+ msg_ok "Updated LocalAGI successfully"
+
+ msg_info "Starting Service"
+ systemctl start localagi
+ msg_ok "Started Service"
+ msg_ok "Updated successfully!"
+ exit
+ fi
+ exit
+}
+
+start
+build_container
+description
+
+msg_ok "Completed successfully!\n"
+echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
+echo -e "${INFO}${YW}Access it using the following URL:${CL}"
+echo -e "${GATEWAY}${BGN}http://${IP}:3000${CL}"
diff --git a/ct/lychee.sh b/ct/lychee.sh
index 887d1f3ac..7adfff87b 100644
--- a/ct/lychee.sh
+++ b/ct/lychee.sh
@@ -31,33 +31,38 @@ function update_script() {
fi
if check_for_gh_release "lychee" "LycheeOrg/Lychee"; then
+ PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
+
msg_info "Stopping Services"
- systemctl stop caddy
+ systemctl stop caddy php${PHP_VER}-fpm
msg_ok "Stopped Services"
- msg_info "Backing up Data"
- cp /opt/lychee/.env /opt/lychee.env.bak
- cp -r /opt/lychee/storage /opt/lychee_storage_backup
- msg_ok "Backed up Data"
+ create_backup /opt/lychee/.env \
+ /opt/lychee/storage \
+ /opt/lychee/public/uploads \
+ /opt/lychee/public/dist
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "lychee" "LycheeOrg/Lychee" "prebuild" "latest" "/opt/lychee" "Lychee.zip"
- msg_info "Restoring Data"
- cp /opt/lychee.env.bak /opt/lychee/.env
- rm -f /opt/lychee.env.bak
- cp -r /opt/lychee_storage_backup/. /opt/lychee/storage
- rm -rf /opt/lychee_storage_backup
- msg_ok "Restored Data"
+ restore_backup
msg_info "Updating Application"
cd /opt/lychee
$STD php artisan migrate --force
+ $STD php artisan config:clear
+ $STD php artisan cache:clear
$STD php artisan optimize:clear
- chmod -R 775 /opt/lychee/storage /opt/lychee/bootstrap/cache
+ $STD php artisan optimize
+ chown -R www-data:www-data /opt/lychee
+ chmod -R 775 /opt/lychee/storage /opt/lychee/bootstrap/cache \
+ /opt/lychee/public/dist /opt/lychee/public/uploads
+ if [[ "${VERBOSE:-no}" = "yes" ]]; then
+ php artisan lychee:diagnostics || true
+ fi
msg_ok "Updated Application"
msg_info "Starting Services"
- systemctl start caddy
+ systemctl start caddy php${PHP_VER}-fpm
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
diff --git a/ct/n8n.sh b/ct/n8n.sh
index 8f9cb483b..658a19b6c 100644
--- a/ct/n8n.sh
+++ b/ct/n8n.sh
@@ -45,7 +45,7 @@ EOF
systemctl daemon-reload
fi
- $STD npm install -g n8n@2.27.5
+ $STD npm install -g n8n@latest
systemctl restart n8n
msg_ok "Updated n8n"
msg_ok "Updated successfully!"
diff --git a/ct/nexterm.sh b/ct/nexterm.sh
new file mode 100644
index 000000000..dd67c4697
--- /dev/null
+++ b/ct/nexterm.sh
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: Mathias Wagner (gnmyt)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://nexterm.dev/
+
+APP="Nexterm"
+var_tags="${var_tags:-server-management}"
+var_cpu="${var_cpu:-2}"
+var_ram="${var_ram:-2048}"
+var_disk="${var_disk:-6}"
+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/nexterm/server/nexterm-server ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "nexterm-engine" "gnmyt/Nexterm"; then
+ msg_info "Stopping nexterm-engine"
+ systemctl stop nexterm-engine
+ msg_ok "Stopped nexterm-engine"
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "nexterm-engine" "gnmyt/Nexterm" "prebuild" "latest" "/opt/nexterm/engine" "nexterm-engine-linux-$(arch_resolve "x64" "arm64").tar.gz"
+
+ msg_info "Starting nexterm-engine"
+ systemctl start nexterm-engine
+ msg_ok "Started nexterm-engine"
+ fi
+
+ if check_for_gh_release "nexterm-server" "gnmyt/Nexterm"; then
+ msg_info "Stopping nexterm-server"
+ systemctl stop nexterm-server
+ msg_ok "Stopped nexterm-server"
+
+ fetch_and_deploy_gh_release "nexterm-server" "gnmyt/Nexterm" "singlefile" "latest" "/opt/nexterm/server" "nexterm-server-linux-$(arch_resolve "x64" "arm64")"
+
+ msg_info "Starting nexterm-server"
+ systemctl start nexterm-server
+ msg_ok "Started nexterm-server"
+ 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}:6989${CL}"
diff --git a/ct/notediscovery.sh b/ct/notediscovery.sh
new file mode 100644
index 000000000..3b15fd0c7
--- /dev/null
+++ b/ct/notediscovery.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/gamosoft/NoteDiscovery
+
+APP="NoteDiscovery"
+var_tags="${var_tags:-notes;wiki;knowledge-base}"
+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 [[ ! -d /opt/notediscovery ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "notediscovery" "gamosoft/NoteDiscovery"; then
+ msg_info "Stopping Service"
+ systemctl stop notediscovery
+ msg_ok "Stopped Service"
+
+ create_backup /opt/notediscovery/data /opt/notediscovery/config.yaml
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "notediscovery" "gamosoft/NoteDiscovery" "tarball"
+
+ msg_info "Syncing Dependencies"
+ cd /opt/notediscovery
+ $STD uv sync --no-dev
+ msg_ok "Synced Dependencies"
+
+ restore_backup
+
+ msg_info "Starting Service"
+ systemctl start notediscovery
+ 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}:8000${CL}"
diff --git a/ct/opencloud.sh b/ct/opencloud.sh
index b64dc34d0..51485fec3 100644
--- a/ct/opencloud.sh
+++ b/ct/opencloud.sh
@@ -30,7 +30,7 @@ function update_script() {
exit
fi
- RELEASE="v7.2.0"
+ RELEASE="v7.3.0"
if check_for_gh_release "OpenCloud" "opencloud-eu/opencloud" "${RELEASE}" "each release is tested individually before the version is updated. Please do not open issues for this"; then
msg_info "Stopping services"
systemctl stop opencloud opencloud-wopi
diff --git a/ct/oxicloud.sh b/ct/oxicloud.sh
new file mode 100644
index 000000000..bee864391
--- /dev/null
+++ b/ct/oxicloud.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: vhsdream
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/DioCrafts/OxiCloud
+
+APP="OxiCloud"
+var_tags="${var_tags:-files;documents}"
+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_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/oxicloud ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "OxiCloud" "DioCrafts/OxiCloud"; then
+ msg_info "Stopping OxiCloud"
+ systemctl stop oxicloud
+ msg_ok "Stopped OxiCloud"
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "OxiCloud" "DioCrafts/OxiCloud" "tarball" "latest" "/opt/oxicloud"
+ TOOLCHAIN="$(grep -oP 'FROM\s+rust:\K[0-9]+\.[0-9]+(\.[0-9]+)?' /opt/oxicloud/Dockerfile | head -1)"
+ RUST_TOOLCHAIN="${TOOLCHAIN:-stable}" setup_rust
+
+ msg_info "Building Frontend SPA"
+ cd /opt/oxicloud/frontend
+ $STD npm ci
+ $STD npm run build
+ msg_ok "Built Frontend SPA"
+
+ msg_info "Updating OxiCloud (Patience)"
+ set -a
+ source /etc/oxicloud/.env
+ set +a
+ cd /opt/oxicloud
+ export DATABASE_URL
+ export RUSTFLAGS="-C target-cpu=native"
+ RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
+ CARGO_JOBS=$((RAM_MB / 2560))
+ [[ $CARGO_JOBS -lt 1 ]] && CARGO_JOBS=1
+ [[ $CARGO_JOBS -gt $(nproc) ]] && CARGO_JOBS=$(nproc)
+ $STD cargo build --release -j "$CARGO_JOBS" --bin oxicloud --bin migrate-nfc-filenames
+ mv target/release/oxicloud /usr/local/bin/oxicloud
+ mv target/release/migrate-nfc-filenames /usr/local/bin/migrate-nfc-filenames
+ chmod +x /usr/local/bin/oxicloud /usr/local/bin/migrate-nfc-filenames
+ rm -f /usr/bin/oxicloud
+ rm -rf /opt/oxicloud/static
+ mv /opt/oxicloud/static-dist /opt/oxicloud/static
+ rm -rf /opt/oxicloud/target /opt/oxicloud/frontend/node_modules
+ msg_ok "Updated OxiCloud"
+
+ msg_info "Starting OxiCloud"
+ systemctl start oxicloud
+ msg_ok "Started OxiCloud"
+ 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}:8086${CL}"
diff --git a/ct/pangolin.sh b/ct/pangolin.sh
index 08d604277..fe5b787a9 100644
--- a/ct/pangolin.sh
+++ b/ct/pangolin.sh
@@ -6,7 +6,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
# Source: https://pangolin.net/ | Github: https://github.com/fosrl/pangolin
APP="Pangolin"
-PANGOLIN_VERSION="${PANGOLIN_VERSION:-1.18.4}"
+PANGOLIN_VERSION="${PANGOLIN_VERSION:-1.20.0}"
var_tags="${var_tags:-proxy}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
@@ -33,6 +33,15 @@ function update_script() {
ensure_dependencies build-essential python3
+ if ! command -v psql &>/dev/null; then
+ msg_error "This installation uses SQLite and cannot be upgraded to Pangolin ${PANGOLIN_VERSION}."
+ echo -e "${INFO}${YW}Starting with Pangolin 1.20.0, PostgreSQL is required as the database backend.${CL}"
+ echo -e "${INFO}${YW}An automatic migration of your existing SQLite data is not supported.${CL}"
+ echo -e "${INFO}${YW}Please create a new LXC with the Pangolin install script, which sets up PostgreSQL automatically.${CL}"
+ echo -e "${INFO}${YW}Your current data is preserved in this container and can be manually migrated if needed.${CL}"
+ exit 1
+ fi
+
NODE_VERSION="24" setup_nodejs
if check_for_gh_release "pangolin" "fosrl/pangolin" "$PANGOLIN_VERSION" "Pinned to a tested release because Pangolin's schema changes have repeatedly broken unattended updates. To try a newer version at your own risk, run: 'export PANGOLIN_VERSION=' and re-run update. If it breaks, please open an issue at https://github.com/community-scripts/ProxmoxVE/issues with the error log."; then
@@ -41,13 +50,8 @@ function update_script() {
systemctl stop gerbil
msg_info "Service stopped"
- msg_info "Creating backup"
- tar -czf /opt/pangolin_config_backup.tar.gz -C /opt/pangolin config
- if [[ -f /opt/pangolin/config/db/db.sqlite ]]; then
- cp -a /opt/pangolin/config/db/db.sqlite \
- "/opt/pangolin/config/db/db.sqlite.pre-${PANGOLIN_VERSION}-$(date +%Y%m%d-%H%M%S).bak"
- fi
- msg_ok "Created backup"
+ DB_URL=$(sed -n 's/.*connection_string: "\(.*\)".*/\1/p' /opt/pangolin/config/config.yml)
+ create_backup /opt/pangolin/config
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "pangolin" "fosrl/pangolin" "tarball" "$PANGOLIN_VERSION"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "gerbil" "fosrl/gerbil" "singlefile" "latest" "/usr/bin" "gerbil_linux_$(arch_resolve)"
@@ -55,23 +59,21 @@ function update_script() {
msg_info "Updating Pangolin"
cd /opt/pangolin
$STD npm ci
- $STD npm run set:sqlite
+ $STD npm run set:pg
$STD npm run set:oss
rm -rf server/private
- $STD npm run db:generate
+ DATABASE_URL="$DB_URL" $STD npm run db:generate
$STD npm run build
$STD npm run build:cli
cp -R .next/standalone ./
+ cp -r server/migrations ./dist/init
chmod +x ./dist/cli.mjs
cp server/db/names.json ./dist/names.json
cp server/db/ios_models.json ./dist/ios_models.json
cp server/db/mac_models.json ./dist/mac_models.json
msg_ok "Updated Pangolin"
- msg_info "Restoring config"
- tar -xzf /opt/pangolin_config_backup.tar.gz -C /opt/pangolin --overwrite
- rm -f /opt/pangolin_config_backup.tar.gz
- msg_ok "Restored config"
+ restore_backup
if ! grep -q '^ExecStartPre=/usr/bin/node dist/migrations.mjs' /etc/systemd/system/pangolin.service 2>/dev/null; then
msg_info "Adding migration step to pangolin.service"
@@ -82,13 +84,8 @@ function update_script() {
msg_info "Running database migrations"
cd /opt/pangolin
- SQLITE_DB="/opt/pangolin/config/db/db.sqlite"
- if [[ -f "$SQLITE_DB" ]]; then
- if ! sqlite3 "$SQLITE_DB" ".tables" 2>/dev/null | tr ' ' '\n' | grep -qx "statusHistory"; then
- sqlite3 "$SQLITE_DB" "DELETE FROM versionMigrations;" 2>/dev/null || true
- fi
- fi
ENVIRONMENT=prod $STD node dist/migrations.mjs
+
msg_ok "Ran database migrations"
msg_info "Updating Badger plugin version"
@@ -112,4 +109,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}https://${CL}"
+echo -e "${GATEWAY}${BGN}https:// or http://${IP}:3002${CL}"
diff --git a/ct/pocketid.sh b/ct/pocketid.sh
index 6153b40c0..9ffc54426 100755
--- a/ct/pocketid.sh
+++ b/ct/pocketid.sh
@@ -71,7 +71,7 @@ function update_script() {
cp /opt/pocket-id/.env /opt/env
fi
- fetch_and_deploy_gh_release "pocket-id" "pocket-id/pocket-id" "singlefile" "latest" "/opt/pocket-id/" "pocket-id-linux-$(arch_resolve)"
+ fetch_and_deploy_gh_release "pocket-id" "pocket-id/pocket-id" "singlefile" "latest" "/opt/pocket-id/" "pocket-id_linux_$(arch_resolve)"
mv /opt/env /opt/pocket-id/.env
msg_info "Starting Service"
diff --git a/ct/reitti.sh b/ct/reitti.sh
index f160176d4..5980e6ae7 100644
--- a/ct/reitti.sh
+++ b/ct/reitti.sh
@@ -106,13 +106,6 @@ spring.servlet.multipart.max-file-size=5GB
spring.servlet.multipart.max-request-size=5GB
server.tomcat.max-part-count=100
-# Rqueue configuration
-rqueue.web.enable=false
-rqueue.job.enabled=false
-rqueue.message.durability.in-terminal-state=0
-rqueue.key.prefix=\${spring.cache.redis.key-prefix}
-rqueue.message.converter.provider.class=com.dedicatedcode.reitti.config.RQueueCustomMessageConverter
-
# Application-specific settings
reitti.server.advertise-uri=
@@ -168,6 +161,81 @@ PROPEOF
msg_ok "Rewrote application.properties (backup: application.properties.bak)"
fi
+ # Migrate v4 -> v5: Remove Rqueue configuration (replaced by Quartz Scheduler)
+ if grep -q "^rqueue\." /opt/reitti/application.properties 2>/dev/null; then
+ msg_info "Migrating to v5: Removing Rqueue configuration"
+ sed -i '/^# Rqueue configuration$/d; /^rqueue\./d' /opt/reitti/application.properties
+ msg_ok "Removed Rqueue configuration"
+ fi
+
+ # Migrate v4 -> v5: Update application.properties and nginx tile cache for v5 compatibility
+ if grep -q "^reitti\.process-data\.schedule=" /opt/reitti/application.properties 2>/dev/null; then
+ msg_info "Migrating to v5: Updating application.properties"
+ sed -i '/^reitti\.process-data\.schedule=/d' /opt/reitti/application.properties
+ sed -i 's/^reitti\.import\.processing-idle-start-time=.*/reitti.import.grace-time-seconds=30/' /opt/reitti/application.properties
+ sed -i 's/^spring\.datasource\.hikari\.maximum-pool-size=20$/spring.datasource.hikari.maximum-pool-size=30/' /opt/reitti/application.properties
+ grep -q "devices" /opt/reitti/application.properties || \
+ sed -i 's/^spring\.cache\.cache-names=\(.*\)$/spring.cache.cache-names=\1,devices,mapStyles,mapStyleJson/' /opt/reitti/application.properties
+ grep -q "org.quartz.core.ErrorLogger" /opt/reitti/application.properties || \
+ sed -i '/^logging\.level\.com\.dedicatedcode\.reitti=/a logging.level.org.quartz.core.ErrorLogger=FATAL' /opt/reitti/application.properties
+ grep -q "^spring.servlet.multipart.resolve-lazily=" /opt/reitti/application.properties || \
+ sed -i '/^spring\.servlet\.multipart\.max-request-size=/a spring.servlet.multipart.resolve-lazily=true' /opt/reitti/application.properties
+ grep -q "^spring.mvc.async.request-timeout=" /opt/reitti/application.properties || \
+ echo "spring.mvc.async.request-timeout=600000" >>/opt/reitti/application.properties
+ if ! grep -q "^spring.quartz" /opt/reitti/application.properties; then
+ cat >>/opt/reitti/application.properties <<'QUARTZEOF'
+
+# Quartz Scheduler configuration
+spring.quartz.job-store-type=jdbc
+spring.quartz.jdbc.initialize-schema=never
+spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
+spring.quartz.properties.org.quartz.jobStore.isClustered=false
+spring.quartz.properties.org.quartz.jobStore.tablePrefix=qrtz_
+spring.quartz.properties.org.quartz.threadPool.threadCount=5
+QUARTZEOF
+ fi
+ grep -q "^reitti.import.staging.cleanup.cron=" /opt/reitti/application.properties || \
+ echo "reitti.import.staging.cleanup.cron=0 0 4 * * *" >>/opt/reitti/application.properties
+ grep -q "^reitti.batching.max-batch-size=" /opt/reitti/application.properties || \
+ printf "reitti.batching.max-batch-size=100\nreitti.batching.max-wait-time=5\n" >>/opt/reitti/application.properties
+ grep -q "^reitti.jobs.cleanup.cron=" /opt/reitti/application.properties || \
+ printf "reitti.jobs.cleanup.cron=0 0 4 * * ?\nreitti.jobs.cleanup.max-age-hours=24\n" >>/opt/reitti/application.properties
+ grep -q "^reitti.db-janitor.schedule=" /opt/reitti/application.properties || \
+ echo "reitti.db-janitor.schedule=0 0 4 * * ?" >>/opt/reitti/application.properties
+ msg_ok "Updated application.properties for v5"
+
+ if [[ -f /etc/nginx/nginx.conf ]]; then
+ msg_info "Migrating to v5: Updating nginx tile cache configuration"
+ cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.v5
+ cat >/etc/nginx/nginx.conf <<'NGINXEOF'
+user www-data;
+
+events {
+ worker_connections 1024;
+}
+http {
+ resolver 1.1.1.1 8.8.8.8 valid=30s ipv6=off;
+ proxy_cache_path /var/cache/nginx/tiles levels=1:2 keys_zone=tiles:10m max_size=1g inactive=30d use_temp_path=off;
+ server {
+ listen 80;
+ location /custom/ {
+ set $upstream_url $http_x_reitti_upstream_url;
+ proxy_pass $upstream_url;
+ proxy_set_header Host $proxy_host;
+ proxy_set_header User-Agent "Reitti/1.0";
+ proxy_cache tiles;
+ proxy_cache_key $upstream_url;
+ proxy_cache_valid 200 30d;
+ proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
+ }
+ }
+}
+NGINXEOF
+ systemctl reload nginx
+ msg_ok "Updated nginx tile cache configuration"
+ fi
+ fi
+
if check_for_gh_release "reitti" "dedicatedcode/reitti"; then
msg_info "Stopping Service"
systemctl stop reitti
@@ -179,10 +247,12 @@ PROPEOF
USE_ORIGINAL_FILENAME="true" fetch_and_deploy_gh_release "reitti" "dedicatedcode/reitti" "singlefile" "latest" "/opt/reitti" "reitti-app.jar"
mv /opt/reitti/reitti-*.jar /opt/reitti/reitti.jar
+ msg_warn "v5 runs a one-time database migration on first start (GPS points → device table). This may take several minutes on large datasets — do not interrupt the container."
msg_info "Starting Service"
systemctl start reitti
msg_ok "Started Service"
msg_ok "Updated successfully!"
+ msg_warn "Post-upgrade: Verify each API token has a Device assigned in Settings → API Tokens. Tokens without a device cannot ingest location data in v5."
fi
exit
}
diff --git a/ct/sftpgo.sh b/ct/sftpgo.sh
index d96f013d2..5c6a79adf 100644
--- a/ct/sftpgo.sh
+++ b/ct/sftpgo.sh
@@ -28,6 +28,12 @@ function update_script() {
msg_error "No ${APP} Installation Found!"
exit
fi
+
+ setup_deb822_repo \
+ "sftpgo" \
+ "https://oss.sftpgo.com/apt/gpg.key" \
+ "https://oss.sftpgo.com/apt" \
+ "trixie"
msg_info "Updating SFTPGo"
$STD apt update
diff --git a/ct/snapotter.sh b/ct/snapotter.sh
index 2ebea2959..ce2323f8f 100644
--- a/ct/snapotter.sh
+++ b/ct/snapotter.sh
@@ -30,19 +30,63 @@ function update_script() {
exit
fi
- if check_for_gh_release "snapotter" "snapotter-hq/SnapOtter"; then
+ NEEDS_V2_MIGRATION=false
+ grep -q '^DB_PATH=' /opt/snapotter_data/.env 2>/dev/null && NEEDS_V2_MIGRATION=true
+ UPDATE_AVAILABLE=false
+ check_for_gh_release "snapotter" "snapotter-hq/SnapOtter" && UPDATE_AVAILABLE=true
+
+ if [[ "$NEEDS_V2_MIGRATION" == true || "$UPDATE_AVAILABLE" == true ]]; then
msg_info "Stopping Service"
systemctl stop snapotter
msg_ok "Stopped Service"
- CLEAN_INSTALL=1 fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "tarball"
+ PG_VERSION="17" setup_postgresql
+ if ! sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'snapotter'" | grep -qx '1'; then
+ PG_DB_NAME="snapotter" PG_DB_USER="snapotter" setup_postgresql_db
+ else
+ PG_DB_NAME="snapotter"
+ PG_DB_USER="snapotter"
+ PG_DB_PASS=$(sed -n 's|^DATABASE_URL=postgres://snapotter:\([^@]*\)@.*|\1|p' /opt/snapotter_data/.env | head -n1)
+ if [[ -z "$PG_DB_PASS" ]]; then
+ msg_error "SnapOtter's PostgreSQL database exists, but its password is not available in /opt/snapotter_data/.env"
+ exit 1
+ fi
+ fi
+
+ msg_info "Installing Redis"
+ $STD apt install -y redis-server
+ if grep -q '^appendonly ' /etc/redis/redis.conf; then
+ sed -i 's/^appendonly .*/appendonly yes/' /etc/redis/redis.conf
+ else
+ echo 'appendonly yes' >>/etc/redis/redis.conf
+ fi
+ $STD systemctl enable --now redis-server
+ msg_ok "Installed Redis"
+
+ msg_info "Migrating SnapOtter Configuration"
+ sed -i '/^DB_PATH=/d; /^DATABASE_URL=/d; /^REDIS_URL=/d; /^SQLITE_MIGRATE_PATH=/d' /opt/snapotter_data/.env
+ cat <>/opt/snapotter_data/.env
+DATABASE_URL=postgres://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}
+REDIS_URL=redis://127.0.0.1:6379
+EOF
+ if [[ -f /opt/snapotter_data/snapotter.db ]]; then
+ echo 'SQLITE_MIGRATE_PATH=/opt/snapotter_data/snapotter.db' >>/opt/snapotter_data/.env
+ fi
+ if ! grep -q '^Requires=postgresql.service redis-server.service$' /etc/systemd/system/snapotter.service; then
+ sed -i '/^After=/c\After=network-online.target postgresql.service redis-server.service' /etc/systemd/system/snapotter.service
+ sed -i '/^\[Unit\]/a Wants=network-online.target\nRequires=postgresql.service redis-server.service' /etc/systemd/system/snapotter.service
+ fi
+ systemctl daemon-reload
+ msg_ok "Migrated SnapOtter Configuration"
+
+ if [[ "$UPDATE_AVAILABLE" == true ]]; then
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "prebuild" "latest" "/opt/snapotter" "snapotter-*-linux-amd64.tar.gz"
+ fi
msg_info "Updating SnapOtter"
- cd /opt/snapotter
- $STD npm pkg delete scripts.prepare
- $STD pnpm install --frozen-lockfile
- $STD pnpm --filter @snapotter/web build
- sed -i 's/mediapipe==0.10.21/mediapipe>=0.10.21/' /opt/snapotter/docker/feature-manifest.json
+ $STD uv python install 3.11
+ $STD uv venv --seed --python 3.11 /opt/snapotter_data/ai/venv
+ ln -sfn /opt/snapotter /app
msg_ok "Updated SnapOtter"
msg_info "Starting Service"
diff --git a/ct/squid.sh b/ct/squid.sh
new file mode 100644
index 000000000..1698fc395
--- /dev/null
+++ b/ct/squid.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: 007hacky007
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://www.squid-cache.org/
+
+APP="Squid"
+var_tags="${var_tags:-proxy}"
+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 /etc/squid/squid.conf ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+ msg_info "Updating Squid"
+ $STD apt update
+ $STD apt upgrade -y
+ msg_ok "Updated Squid"
+
+ msg_info "Validating Squid Configuration"
+ $STD squid -k parse
+ msg_ok "Validated Squid Configuration"
+
+ msg_info "Restarting Squid"
+ systemctl restart squid
+ msg_ok "Restarted Squid"
+ 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} Proxy endpoint:${CL}"
+echo -e "${TAB}${GATEWAY}${BGN}${IP}:3128${CL}"
diff --git a/ct/storyteller.sh b/ct/storyteller.sh
index df2f915c9..0e49e0196 100644
--- a/ct/storyteller.sh
+++ b/ct/storyteller.sh
@@ -32,7 +32,7 @@ function update_script() {
NODE_VERSION="24" NODE_MODULE="corepack,yarn" setup_nodejs
- if check_for_gl_release "storyteller" "storyteller-platform/storyteller"; then
+ if check_for_gl_release "storyteller" "storyteller-platform/storyteller" "" "" "web-v2"; then
msg_info "Stopping Service"
systemctl stop storyteller
msg_ok "Stopped Service"
@@ -41,7 +41,7 @@ function update_script() {
cp /opt/storyteller/.env /opt/storyteller_env.bak
msg_ok "Backed up Data"
- CLEAN_INSTALL=1 fetch_and_deploy_gl_release "storyteller" "storyteller-platform/storyteller" "tarball" "latest" "/opt/storyteller"
+ CLEAN_INSTALL=1 fetch_and_deploy_gl_release "storyteller" "storyteller-platform/storyteller" "tarball" "latest" "/opt/storyteller" "" "web-v2"
msg_info "Restoring Configuration"
mv /opt/storyteller_env.bak /opt/storyteller/.env
diff --git a/ct/sync-in.sh b/ct/sync-in.sh
new file mode 100644
index 000000000..4b43c6dd4
--- /dev/null
+++ b/ct/sync-in.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/Sync-in/server
+
+APP="Sync-in"
+var_tags="${var_tags:-files;sync;collaboration}"
+var_cpu="${var_cpu:-2}"
+var_ram="${var_ram:-2048}"
+var_disk="${var_disk:-20}"
+var_os="${var_os:-debian}"
+var_version="${var_version:-13}"
+var_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/sync-in/node_modules/@sync-in ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "sync-in" "Sync-in/server"; then
+ msg_info "Stopping Service"
+ systemctl stop sync-in
+ msg_ok "Stopped Service"
+
+ msg_info "Updating Sync-in"
+ $STD npm install --prefix /opt/sync-in "@sync-in/server@${CHECK_UPDATE_RELEASE#v}"
+ msg_ok "Updated Sync-in"
+
+ msg_info "Running Database Migrations"
+ cd /opt/sync-in
+ $STD npx sync-in-server migrate-db
+ msg_ok "Ran Database Migrations"
+
+ VERSION=$(node -pe "require('/opt/sync-in/node_modules/@sync-in/server/package.json').version" 2>/dev/null || echo "")
+ [[ -n "$VERSION" ]] && echo "$VERSION" >~/.sync-in
+
+ msg_info "Starting Service"
+ systemctl start sync-in
+ 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}"
diff --git a/ct/wanderer.sh b/ct/wanderer.sh
index 432963379..bb0ec71cd 100644
--- a/ct/wanderer.sh
+++ b/ct/wanderer.sh
@@ -30,12 +30,14 @@ function update_script() {
exit
fi
- if check_for_gh_release "wanderer" "Flomp/wanderer"; then
+ if check_for_gh_release "wanderer" "open-wanderer/wanderer"; then
msg_info "Stopping service"
systemctl stop wanderer-web
msg_ok "Stopped service"
- fetch_and_deploy_gh_release "wanderer" "open-wanderer/wanderer" "tarball" "latest" "/opt/wanderer/source"
+ create_backup /opt/wanderer/source/search
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wanderer" "open-wanderer/wanderer" "tarball" "latest" "/opt/wanderer/source"
+ restore_backup
msg_info "Updating wanderer"
cd /opt/wanderer/source/db
@@ -44,6 +46,13 @@ function update_script() {
cd /opt/wanderer/source/web
$STD npm ci
$STD npm run build
+ mkdir -p /opt/wanderer/data/plugins
+ [[ -e /data/plugins ]] || ln -sfn /opt/wanderer/data/plugins /data/plugins
+ msg_info "Installing wanderer plugins"
+ for plugin in hammerhead komoot strava; do
+ fetch_and_deploy_gh_release "wanderer-plugin-${plugin}" "open-wanderer/wanderer" "prebuild" "${CHECK_UPDATE_RELEASE:-latest}" "/opt/wanderer/data/plugins" "wanderer-plugin-${plugin}.tar.gz" || msg_warn "Failed to install wanderer plugin: ${plugin}"
+ done
+ msg_ok "Installed wanderer plugins"
msg_ok "Updated wanderer"
msg_info "Starting service"
diff --git a/ct/yuvomi.sh b/ct/yuvomi.sh
new file mode 100644
index 000000000..625a0aa61
--- /dev/null
+++ b/ct/yuvomi.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env bash
+source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/ulsklyc/yuvomi
+
+APP="Yuvomi"
+var_tags="${var_tags:-family;planner;calendar}"
+var_cpu="${var_cpu:-2}"
+var_ram="${var_ram:-1024}"
+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/yuvomi ]]; then
+ msg_error "No ${APP} Installation Found!"
+ exit
+ fi
+
+ if check_for_gh_release "yuvomi" "ulsklyc/yuvomi"; then
+ msg_info "Stopping Service"
+ systemctl stop yuvomi
+ msg_ok "Stopped Service"
+
+ create_backup /opt/yuvomi/data /opt/yuvomi/.env
+
+ CLEAN_INSTALL=1 fetch_and_deploy_gh_release "yuvomi" "ulsklyc/yuvomi" "tarball"
+
+ msg_info "Installing Node.js Dependencies"
+ cd /opt/yuvomi
+ $STD npm ci --omit=dev
+ msg_ok "Installed Node.js Dependencies"
+
+ restore_backup
+
+ msg_info "Starting Service"
+ systemctl start yuvomi
+ 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}:3000${CL}"
diff --git a/install/2fauth-install.sh b/install/2fauth-install.sh
index 386b5c3de..61c8377ae 100644
--- a/install/2fauth-install.sh
+++ b/install/2fauth-install.sh
@@ -43,6 +43,7 @@ $STD php artisan migrate:refresh
$STD php artisan passport:install -q -n
$STD php artisan storage:link
$STD php artisan config:cache
+$STD php artisan 2fauth:fix-passport-key-permissions
chown -R www-data: /opt/2fauth
chmod -R 755 /opt/2fauth
msg_ok "Setup 2fauth"
diff --git a/install/adventurelog-install.sh b/install/adventurelog-install.sh
index 10e664bc6..b480da0d9 100644
--- a/install/adventurelog-install.sh
+++ b/install/adventurelog-install.sh
@@ -72,6 +72,7 @@ BODY_SIZE_LIMIT=Infinity
ORIGIN='http://$LOCAL_IP:3000'
EOF
cd /opt/adventurelog/frontend
+grep -q "^dangerouslyAllowAllBuilds:" ./pnpm-workspace.yaml 2>/dev/null || echo "dangerouslyAllowAllBuilds: true" >>./pnpm-workspace.yaml
$STD pnpm i
$STD pnpm build
msg_ok "Installed AdventureLog"
diff --git a/install/affine-install.sh b/install/affine-install.sh
new file mode 100644
index 000000000..50144d4e0
--- /dev/null
+++ b/install/affine-install.sh
@@ -0,0 +1,217 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/toeverything/AFFiNE
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Installing Dependencies"
+$STD apt install -y \
+ build-essential \
+ git \
+ pkg-config \
+ openssl \
+ libssl-dev \
+ libjemalloc2 \
+ redis-server \
+ nginx \
+ cmake
+msg_ok "Installed Dependencies"
+
+PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
+PG_DB_NAME="affine" PG_DB_USER="affine" setup_postgresql_db
+NODE_VERSION="22" setup_nodejs
+setup_rust
+
+fetch_and_deploy_gh_release "affine_app" "toeverything/AFFiNE" "tarball" "v0.27.0" "/opt/affine"
+
+msg_info "Setting up Directories"
+rm -rf /root/.affine
+mkdir -p /root/.affine/{storage,config}
+msg_ok "Set up Directories"
+
+msg_info "Configuring Environment"
+SECRET_KEY=$(openssl rand -hex 32)
+cat </opt/affine/.env
+NODE_ENV=production
+AFFINE_SERVER_PORT=3010
+AFFINE_SERVER_HOST=${LOCAL_IP}
+AFFINE_SERVER_EXTERNAL_URL=http://${LOCAL_IP}
+DATABASE_URL=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
+REDIS_SERVER_HOST=localhost
+REDIS_SERVER_PORT=6379
+AFFINE_INDEXER_ENABLED=false
+SECRET_KEY=${SECRET_KEY}
+EOF
+msg_ok "Configured Environment"
+
+msg_info "Building AFFiNE (Patience)"
+cd /opt/affine
+source /root/.profile
+export PATH="/root/.cargo/bin:$PATH"
+export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
+export VITE_CORE_COMMIT_SHA=$(cat ~/.affine_app)
+# # Initialize git repo (required for build process)
+export HUSKY=0
+$STD git init -q
+$STD git config user.email "build@local"
+$STD git config user.name "Build"
+$STD git add -A
+$STD git commit -q -m "update" --no-verify --allow-empty
+mkdir -p /opt/affine/.turbo
+cat </opt/affine/.turbo/config.json
+{
+ "concurrency": 1
+}
+TURBO
+$STD corepack enable
+$STD corepack prepare yarn@4.13.0 --activate
+$STD yarn config set enableTelemetry 0
+export NODE_OPTIONS="--max-old-space-size=4096"
+export TSC_COMPILE_ON_ERROR=true
+$STD yarn install
+$STD npm install -g typescript
+$STD yarn affine @affine/native build
+$STD yarn affine @affine/server-native build
+
+# Create architecture-specific symlinks for server-native
+ln -sf /opt/affine/packages/backend/native/server-native.node \
+ /opt/affine/packages/backend/native/server-native.x64.node
+ln -sf /opt/affine/packages/backend/native/server-native.node \
+ /opt/affine/packages/backend/native/server-native.arm64.node
+ln -sf /opt/affine/packages/backend/native/server-native.node \
+ /opt/affine/packages/backend/native/server-native.armv7.node
+
+$STD yarn affine init
+$STD yarn affine build -p @affine/reader
+$STD yarn affine build -p @affine/server
+export NODE_OPTIONS="--max-old-space-size=4096"
+$STD yarn affine build -p @affine/web
+$STD yarn affine build -p @affine/admin
+mkdir -p /opt/affine/packages/backend/server/static
+cp -r /opt/affine/packages/frontend/apps/web/dist/* /opt/affine/packages/backend/server/static/
+mkdir -p /opt/affine/packages/backend/server/static/admin
+cp -r /opt/affine/packages/frontend/admin/dist/* /opt/affine/packages/backend/server/static/admin/
+# Create empty mobile manifest (server expects it but we don't build mobile)
+mkdir -p /opt/affine/packages/backend/server/static/mobile
+cat <<'MANIFEST' >/opt/affine/packages/backend/server/static/mobile/assets-manifest.json
+{"publicPath":"/","js":[],"css":[],"gitHash":"","description":""}
+MANIFEST
+msg_ok "Built AFFiNE"
+
+msg_info "Running Initial Migration"
+cd /opt/affine/packages/backend/server
+set -a && source /opt/affine/.env && set +a
+$STD node ./scripts/self-host-predeploy.js
+msg_ok "Ran Initial Migration"
+
+msg_info "Creating Services"
+cat </etc/systemd/system/affine-web.service
+[Unit]
+Description=AFFiNE Web Server
+After=network.target postgresql.service redis-server.service
+Requires=postgresql.service redis-server.service
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/affine/packages/backend/server
+EnvironmentFile=/opt/affine/.env
+Environment=LD_PRELOAD=libjemalloc.so.2
+Environment=NODE_OPTIONS=--max-old-space-size=1024
+ExecStart=/usr/bin/node ./dist/main.js
+Restart=always
+RestartSec=10
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+cat </etc/systemd/system/affine-worker.service
+[Unit]
+Description=AFFiNE Background Worker
+After=network.target postgresql.service redis-server.service
+Requires=postgresql.service redis-server.service
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/affine/packages/backend/server
+EnvironmentFile=/opt/affine/.env
+Environment=LD_PRELOAD=libjemalloc.so.2
+Environment=NODE_OPTIONS=--max-old-space-size=1024
+ExecStart=/usr/bin/node ./dist/main.js --worker
+Restart=always
+RestartSec=10
+
+[Install]
+WantedBy=multi-user.target
+EOF
+
+systemctl enable -q --now redis-server affine-web affine-worker
+msg_ok "Created Services"
+
+msg_info "Creating Admin User"
+ADMIN_PASS=$(openssl rand -base64 12)
+for i in {1..30}; do
+ if curl -s http://localhost:3010/info >/dev/null 2>&1; then
+ break
+ fi
+ sleep 2
+done
+# Create admin via API
+ADMIN_RESPONSE=$(curl -s -X POST http://localhost:3010/api/setup/create-admin-user \
+ -H "Content-Type: application/json" \
+ -d "{\"email\":\"admin@affine.local\",\"password\":\"${ADMIN_PASS}\"}")
+if echo "$ADMIN_RESPONSE" | grep -q '"id"'; then
+ {
+ echo "AFFiNE Credentials"
+ echo "=================="
+ echo "Email: admin@affine.local"
+ echo "Password: ${ADMIN_PASS}"
+ } >~/affine.creds
+ msg_ok "Created Admin User"
+else
+ msg_warn "Admin creation skipped (may already exist)"
+fi
+
+msg_info "Configuring Nginx"
+cat </etc/nginx/sites-available/affine.conf
+upstream affine_backend {
+ server 127.0.0.1:3010;
+}
+
+server {
+ listen 80;
+ server_name _;
+
+ client_max_body_size 100M;
+
+ location / {
+ proxy_pass http://affine_backend;
+ proxy_http_version 1.1;
+ proxy_set_header Host \$host;
+ proxy_set_header X-Real-IP \$remote_addr;
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto \$scheme;
+ proxy_set_header Upgrade \$http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_redirect off;
+ proxy_buffering off;
+ }
+}
+EOF
+ln -sf /etc/nginx/sites-available/affine.conf /etc/nginx/sites-enabled/
+rm -f /etc/nginx/sites-enabled/default
+systemctl enable -q --now nginx
+msg_ok "Configured Nginx"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/beaverhabits-install.sh b/install/beaverhabits-install.sh
new file mode 100644
index 000000000..f58f4003a
--- /dev/null
+++ b/install/beaverhabits-install.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/daya0576/beaverhabits
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+PYTHON_VERSION="3.14" setup_uv
+
+fetch_and_deploy_gh_release "beaverhabits" "daya0576/beaverhabits" "tarball"
+
+msg_info "Installing Dependencies"
+cd /opt/beaverhabits
+$STD uv sync --no-dev
+msg_ok "Installed Dependencies"
+
+msg_info "Configuring BeaverHabits"
+mkdir -p /opt/beaverhabits/.user
+msg_ok "Configured BeaverHabits"
+
+msg_info "Creating Service"
+cat </etc/systemd/system/beaverhabits.service
+[Unit]
+Description=BeaverHabits Habit Tracker
+After=network.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/beaverhabits
+Environment=HABITS_STORAGE=USER_DISK
+Environment=NICEGUI_STORAGE_PATH=/opt/beaverhabits/.user/.nicegui
+ExecStart=/opt/beaverhabits/.venv/bin/gunicorn beaverhabits.main:app --bind 0.0.0.0:8080 -w 1 -k uvicorn_worker.UvicornWorker --max-requests 10000 --log-level info
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now beaverhabits
+msg_ok "Created Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/birdnet-go-install.sh b/install/birdnet-go-install.sh
index b3dfcab8e..df0a96058 100644
--- a/install/birdnet-go-install.sh
+++ b/install/birdnet-go-install.sh
@@ -21,7 +21,7 @@ $STD apt install -y \
ffmpeg
msg_ok "Installed Dependencies"
-fetch_and_deploy_gh_release "birdnet" "tphakala/birdnet-go" "prebuild" "latest" "/opt/birdnet" "birdnet-go-linux-$(arch_resolve).tar.gz"
+fetch_and_deploy_gh_release "birdnet" "tphakala/birdnet-go" "prebuild" "latest" "/opt/birdnet" "birdnet-go-linux-$(arch_resolve)*.tar.gz"
msg_info "Setting up BirdNET-Go"
cp /opt/birdnet/birdnet-go /usr/local/bin/birdnet-go
diff --git a/install/docuseal-install.sh b/install/docuseal-install.sh
index 82c89bfc6..e502256e3 100644
--- a/install/docuseal-install.sh
+++ b/install/docuseal-install.sh
@@ -23,6 +23,8 @@ $STD apt install -y \
libreadline-dev \
zlib1g-dev \
libffi-dev \
+ libleptonica-dev \
+ libleptonica6 \
libvips42 \
libvips-dev \
libheif1 \
diff --git a/install/esphome-install.sh b/install/esphome-install.sh
index 1b5b7367d..02c46b155 100644
--- a/install/esphome-install.sh
+++ b/install/esphome-install.sh
@@ -14,7 +14,8 @@ network_check
update_os
msg_info "Installing Dependencies"
-$STD apt install -y git
+$STD apt install -y git \
+ libusb-1.0-0
msg_ok "Installed Dependencies"
PYTHON_VERSION="3.12" setup_uv
diff --git a/install/fileflows-install.sh b/install/fileflows-install.sh
index af93424b6..14efb6582 100644
--- a/install/fileflows-install.sh
+++ b/install/fileflows-install.sh
@@ -26,8 +26,8 @@ msg_info "Installing ASP.NET Core Runtime"
if [[ "$(arch_resolve)" == "arm64" ]]; then
# packages.microsoft.com only ships amd64 debs for Debian; use dotnet-install on arm64
curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh
- $STD bash /tmp/dotnet-install.sh --channel 8.0 --runtime aspnetcore --install-dir /usr/lib/dotnet8
- ln -sf /usr/lib/dotnet8/dotnet /usr/bin/dotnet
+ $STD bash /tmp/dotnet-install.sh --channel 10.0 --runtime aspnetcore --install-dir /usr/lib/dotnet10
+ ln -sf /usr/lib/dotnet10/dotnet /usr/bin/dotnet
rm -f /tmp/dotnet-install.sh
else
setup_deb822_repo \
@@ -35,7 +35,7 @@ else
"https://packages.microsoft.com/keys/microsoft-2025.asc" \
"https://packages.microsoft.com/debian/13/prod/" \
"trixie"
- $STD apt install -y aspnetcore-runtime-8.0
+ $STD apt install -y aspnetcore-runtime-10.0
fi
msg_ok "Installed ASP.NET Core Runtime"
@@ -55,9 +55,12 @@ if [[ "$install_server" =~ ^[Ss]$ ]]; then
msg_ok "Installed FileFlows Server"
else
msg_info "Installing FileFlows Node"
+ read -r -p "${TAB3}Enter FileFlows Server URL (e.g. http://192.168.1.10:19200): " server_url
+ while [[ -z "${server_url// /}" ]]; do
+ read -r -p "${TAB3}Enter FileFlows Server URL (e.g. http://192.168.1.10:19200): " server_url
+ done
cd /opt/fileflows/Node
- $STD dotnet FileFlows.Node.dll
- $STD dotnet FileFlows.Node.dll --systemd install --root true
+ $STD dotnet FileFlows.Node.dll --server "$server_url" --systemd install --root true
systemctl enable -q --now fileflows-node
msg_ok "Installed FileFlows Node"
fi
diff --git a/install/fireshare-install.sh b/install/fireshare-install.sh
index 982679c6e..311ba230b 100644
--- a/install/fireshare-install.sh
+++ b/install/fireshare-install.sh
@@ -141,6 +141,9 @@ cp /opt/fireshare/app/nginx/prod.conf /etc/nginx/nginx.conf
sed -i 's|root /processed/|root /opt/fireshare-processed/|g' /etc/nginx/nginx.conf
sed -i 's/^user[[:space:]]\+nginx;/user root;/' /etc/nginx/nginx.conf
sed -i 's|root[[:space:]]\+/app/build;|root /opt/fireshare/app/client/build;|' /etc/nginx/nginx.conf
+sed -i 's/__FIRESHARE_PORT__/80/g' /etc/nginx/nginx.conf
+cp /opt/fireshare/app/nginx/error.html /etc/nginx/
+cp /opt/fireshare/app/nginx/api_unavailable.html /etc/nginx/
systemctl start nginx
cat <~/fireshare.creds
diff --git a/install/grav-install.sh b/install/grav-install.sh
new file mode 100644
index 000000000..8b3c396cf
--- /dev/null
+++ b/install/grav-install.sh
@@ -0,0 +1,111 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: Raffaele (rafspiny)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://getgrav.org/
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Installing Dependencies"
+$STD apt install -y \
+ nginx \
+ logrotate
+msg_ok "Installed Dependencies"
+
+PHP_FPM="YES" setup_php
+
+fetch_and_deploy_gh_release "grav" "getgrav/grav" "prebuild" "latest" "/opt/grav" "grav-admin-v*zip"
+chown -R www-data:www-data /opt/grav
+
+msg_info "Configuring Nginx"
+PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
+PHP_FPM_SOCK=$(find /run/php -maxdepth 1 -name "php*-fpm.sock" -type s | sort -V | tail -1)
+unlink /etc/nginx/sites-enabled/default
+rm -f /etc/nginx/sites-available/default
+cat </etc/nginx/sites-available/grav
+server {
+ listen 80;
+ server_name _;
+ root /opt/grav;
+ index index.html index.htm index.php;
+
+ location / {
+ try_files \$uri \$uri/ /index.html /index.htm /index.php\$is_args\$args;
+ }
+
+ ## Begin - Security
+ location ~* /(\.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
+ location ~* /(system|vendor)/.*\.(txt|xml|md|html|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
+ location ~* /user/.*\.(txt|md|json|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
+ location ~ /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) { return 403; }
+ ## End - Security
+
+ ## Begin - API
+ location ^~ /api/ {
+ try_files \$uri \$uri/ /index.php\$is_args\$args;
+ }
+ ## End - API
+
+ # deny all direct access to these sensitive user folders, whatever the file type
+ location ~* /user/(accounts|config|env)/.*$ { return 403; }
+ # allow public media uploads under user/data to be served directly;
+ # this must come before the user/data deny so it wins the match
+ location ~* /user/data/.*\.(jpe?g|png|gif|webp|avif|bmp|ico|mp4|webm|ogg|ogv|mov|mp3|wav|m4a|flac|pdf)$ { try_files \$uri =404; }
+ # deny everything else under user/data
+ location ~* /user/data/.*$ { return 403; }
+
+
+ ## Begin - Caching
+ location ~* ^/forms-basic-captcha-image.jpg$ {
+ try_files \$uri \$uri/ /index.php\$is_args\$args;
+ }
+
+ location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
+ expires 30d;
+ add_header Vary Accept-Encoding;
+ log_not_found off;
+ }
+
+ location ~* ^.+\.(?:css|cur|js|jpe?g|gif|htc|ico|png|html|xml|otf|ttf|eot|woff|woff2|svg)$ {
+ access_log off;
+ expires 30d;
+ add_header Cache-Control public;
+ tcp_nodelay off;
+ open_file_cache max=3000 inactive=120s;
+ open_file_cache_valid 45s;
+ open_file_cache_min_uses 2;
+ open_file_cache_errors off;
+ }
+ ## End - Caching
+
+ location ~ ^(.+\.php)(.*)$ {
+ fastcgi_split_path_info ^(.+\.php)(.*)$;
+ if (!-f \$document_root\$fastcgi_script_name) { return 404; }
+ fastcgi_pass unix:${PHP_FPM_SOCK};
+ fastcgi_index index.php;
+ include /etc/nginx/fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
+ }
+
+ location ~ /\.ht {
+ deny all;
+ }
+}
+EOF
+ln -sf /etc/nginx/sites-available/grav /etc/nginx/sites-enabled/grav
+systemctl enable -q --now php${PHP_VER}-fpm
+$STD nginx -t
+systemctl enable -q --now nginx
+$STD nginx -s reload
+msg_ok "Configured Nginx"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/hyperion-install.sh b/install/hyperion-install.sh
index db5bf301f..b58798f96 100644
--- a/install/hyperion-install.sh
+++ b/install/hyperion-install.sh
@@ -24,6 +24,12 @@ msg_ok "Set up Hyperion repository"
msg_info "Installing Hyperion"
$STD apt install -y hyperion
+mkdir -p /etc/systemd/system/hyperion@.service.d
+cat </etc/systemd/system/hyperion@.service.d/override.conf
+[Unit]
+Requisite=
+EOF
+systemctl daemon-reload
systemctl enable -q --now hyperion@root
msg_ok "Installed Hyperion"
diff --git a/install/immich-install.sh b/install/immich-install.sh
index fcaad3e0b..688ae657e 100644
--- a/install/immich-install.sh
+++ b/install/immich-install.sh
@@ -162,7 +162,7 @@ PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
ACTUAL_PG_VERSION=$(ls /etc/postgresql/ 2>/dev/null | sort -V | tail -1)
ACTUAL_PG_VERSION=${ACTUAL_PG_VERSION:-16}
-VCHORD_RELEASE="1.0.0"
+VCHORD_RELEASE="1.1.1"
fetch_and_deploy_gh_release "VectorChord" "tensorchord/VectorChord" "binary" "${VCHORD_RELEASE}" "/tmp" "postgresql-${ACTUAL_PG_VERSION}-vchord_*_$(arch_resolve).deb"
sed -i "s/^#shared_preload.*/shared_preload_libraries = 'vchord.so'/" /etc/postgresql/${ACTUAL_PG_VERSION}/main/postgresql.conf
@@ -286,6 +286,7 @@ LIBVIPS_REVISION="e01a4797cabe77d457fdfa7d776b7a7e7ca6d6a7"
$STD git clone https://github.com/libvips/libvips.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBVIPS_REVISION"
+$STD git apply "$BASE_DIR"/server/sources/libvips-patches/0001-put-other-loaders-ahead-of-dcrawload.patch
$STD meson setup build --buildtype=release --libdir=lib -Dintrospection=disabled -Dtiff=disabled
cd build
$STD ninja install
@@ -311,7 +312,7 @@ ML_DIR="${APP_DIR}/machine-learning"
GEO_DIR="${INSTALL_DIR}/geodata"
mkdir -p {"${APP_DIR}","${UPLOAD_DIR}","${GEO_DIR}","${INSTALL_DIR}"/cache}
-fetch_and_deploy_gh_release "Immich" "immich-app/immich" "tarball" "v3.0.1" "$SRC_DIR"
+fetch_and_deploy_gh_release "Immich" "immich-app/immich" "tarball" "v3.0.3" "$SRC_DIR"
PNPM_VERSION="$(jq -r '.packageManager | split("@")[1] | split("+")[0]' ${SRC_DIR}/package.json)"
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
NODE_VERSION="24" NODE_MODULE="corepack" setup_nodejs
@@ -437,6 +438,27 @@ cd "$INSTALL_DIR"
ln -s "$GEO_DIR" "$APP_DIR"
msg_ok "Installed GeoNames data"
+# MickLesk temporary patch for HEIC thumbnail gen
+msg_info "Patching media.repository.js"
+MEDIA_REPO_JS="/opt/immich/app/dist/repositories/media.repository.js"
+if [[ -f "$MEDIA_REPO_JS" ]]; then
+ python3 - <<'PY'
+from pathlib import Path
+p = Path('/opt/immich/app/dist/repositories/media.repository.js')
+s = p.read_text()
+old = "(0, sharp_1.default)(input).metadata()"
+new = "(0, sharp_1.default)(input, { unlimited: true, limitInputPixels: false }).metadata()"
+if new in s:
+ print('hotfix already there')
+elif old in s:
+ p.write_text(s.replace(old, new, 1))
+ print('hotfix applied')
+else:
+ print('pattern not found, skipped')
+PY
+fi
+msg_ok "Patched media.repository.js"
+
mkdir -p /var/log/immich
touch /var/log/immich/{web.log,ml.log}
msg_ok "Installed Immich"
diff --git a/install/invidious-install.sh b/install/invidious-install.sh
new file mode 100644
index 000000000..798b09e92
--- /dev/null
+++ b/install/invidious-install.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: vhsdream
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/iv-org/invidious
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Installing Dependencies"
+$STD apt install -y \
+ build-essential \
+ git \
+ pkg-config \
+ libssl-dev \
+ libxml2-dev \
+ libyaml-dev \
+ libgmp-dev \
+ libreadline-dev \
+ librsvg2-bin \
+ libsqlite3-dev \
+ zlib1g-dev \
+ libpcre2-dev \
+ libevent-dev \
+ fonts-open-sans
+msg_ok "Installed Dependencies"
+
+if [[ "$(arch_resolve amd64 arm64)" == "amd64" ]]; then
+ setup_deb822_repo "crystal" "https://download.opensuse.org/repositories/devel:/languages:/crystal/Debian_13/Release.key" "https://download.opensuse.org/repositories/devel:/languages:/crystal/Debian_13/" "./"
+ $STD apt install -y crystal
+else
+ fetch_and_deploy_gh_release "Crystal" "crystal-lang/crystal" "prebuild" "latest" "/opt/crystal" "crystal-*-linux-aarch64-bundled.tar.gz"
+ ln -sf /opt/crystal/bin/crystal /usr/local/bin/crystal
+ ln -sf /opt/crystal/bin/shards /usr/local/bin/shards
+fi
+
+PG_VERSION="17" setup_postgresql
+PG_DB_NAME="invidious" PG_DB_USER="invidious" setup_postgresql_db
+fetch_and_deploy_gh_release "Invidious" "iv-org/invidious" "tarball" "latest" "/opt/invidious"
+fetch_and_deploy_gh_release "Invidious Companion" "iv-org/invidious-companion" "prebuild" "latest" "/opt/invidious-companion" "invidious_companion-$(arch_resolve x86_64 aarch64)-unknown-linux-gnu.tar.gz"
+
+msg_info "Building Invidious"
+cd /opt/invidious
+INVIDIOUS_VERSION="$(cat ~/.invidious 2>/dev/null || echo "unknown")"
+INVIDIOUS_VERSION="${INVIDIOUS_VERSION#v}"
+sed -i \
+ -e "s~^\(\s*CURRENT_BRANCH\s*=\).*~\1 \"master\"~" \
+ -e "s~^\(\s*CURRENT_COMMIT\s*=\).*~\1 \"\"~" \
+ -e "s~^\(\s*CURRENT_VERSION\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
+ -e "s~^\(\s*CURRENT_TAG\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
+ -e "s~^\(\s*ASSET_COMMIT\s*=\).*~\1 \"\"~" \
+ src/invidious.cr
+$STD make
+msg_ok "Built Invidious"
+
+msg_info "Configuring Invidious"
+SECRET_KEY="$(openssl rand -hex 8)"
+HMAC_KEY="$(openssl rand -hex 32)"
+sed -e '\~^db:~,\~dbname:~d' \
+ -e "s~^#database_.*~database_url: postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}~" \
+ -e 's~^#check_tables.*~check_tables: true~' \
+ -e 's~^#invidious_companion:~invidious_companion:~' \
+ -e 's~^# - private_~ - private_~' \
+ -e "s~^#invidious_companion_key:.*~invidious_companion_key: \"${SECRET_KEY}\"~" \
+ -e "s~^hmac_key:.*~hmac_key: \"${HMAC_KEY}\"~" \
+ /opt/invidious/config/config.example.yml >/opt/invidious/config/config.yml
+chmod 600 /opt/invidious/config/config.yml
+
+cat </etc/logrotate.d/invidious.logrotate
+/opt/invidious/invidious.log {
+ rotate 4
+ weekly
+ notifempty
+ missingok
+ compress
+ minsize 1048576
+}
+EOF
+chmod 0644 /etc/logrotate.d/invidious.logrotate
+msg_ok "Configured Invidious"
+
+msg_info "Migrating database"
+$STD ./invidious --migrate
+msg_ok "Migrated database"
+
+msg_info "Configuring services"
+sed -e 's|^User=invidious|User=root|' \
+ -e 's|^Group=invidious|Group=root|' \
+ -e 's|/home/invidious/invidious|/opt/invidious|g' \
+ /opt/invidious/invidious.service >/etc/systemd/system/invidious.service
+mkdir -p /var/tmp/youtubei.js
+cat </etc/systemd/system/invidious-companion.service
+[Unit]
+Description=Invidious Companion
+After=network.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/invidious-companion
+Environment=SERVER_SECRET_KEY=${SECRET_KEY}
+Environment=CACHE_DIRECTORY=/var/tmp/youtubei.js
+ExecStart=/opt/invidious-companion/invidious_companion
+Restart=always
+RestartSec=2s
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl -q enable --now invidious invidious-companion
+msg_ok "Configured services"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/leafwiki-install.sh b/install/leafwiki-install.sh
new file mode 100644
index 000000000..228ffc702
--- /dev/null
+++ b/install/leafwiki-install.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/perber/leafwiki
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+fetch_and_deploy_gh_release "leafwiki" "perber/leafwiki" "singlefile" "latest" "/usr/local/bin" "leafwiki-v*-linux-$(arch_resolve)"
+
+msg_info "Configuring LeafWiki"
+mkdir -p /opt/leafwiki/data
+mkdir -p /etc/leafwiki
+JWT_SECRET=$(openssl rand -hex 32)
+ADMIN_PASS=$(openssl rand -base64 12 | tr -dc 'a-zA-Z0-9' | head -c12)
+cat </etc/leafwiki/.env
+LEAFWIKI_DATA_DIR=/opt/leafwiki/data
+LEAFWIKI_HOST=0.0.0.0
+LEAFWIKI_PORT=8080
+LEAFWIKI_JWT_SECRET=${JWT_SECRET}
+LEAFWIKI_ADMIN_PASSWORD=${ADMIN_PASS}
+LEAFWIKI_ALLOW_INSECURE=true
+EOF
+msg_ok "Configured LeafWiki"
+
+msg_info "Creating Service"
+cat </etc/systemd/system/leafwiki.service
+[Unit]
+Description=LeafWiki
+After=network.target
+
+[Service]
+Type=simple
+User=root
+EnvironmentFile=/etc/leafwiki/.env
+ExecStart=/usr/local/bin/leafwiki
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now leafwiki
+msg_ok "Created Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/localagi-install.sh b/install/localagi-install.sh
new file mode 100644
index 000000000..3589d1b8b
--- /dev/null
+++ b/install/localagi-install.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: BillyOutlast
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/mudler/LocalAGI
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+APP="LocalAGI"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Installing Dependencies"
+$STD apt install -y build-essential
+msg_ok "Installed Dependencies"
+
+NODE_VERSION="24" setup_nodejs
+setup_go
+
+msg_info "Installing Bun"
+export BUN_INSTALL="/root/.bun"
+curl -fsSL https://bun.sh/install | $STD bash
+ln -sf /root/.bun/bin/bun /usr/local/bin/bun
+ln -sf /root/.bun/bin/bunx /usr/local/bin/bunx
+msg_ok "Installed Bun"
+
+fetch_and_deploy_gh_release "localagi" "mudler/LocalAGI" "tarball" "latest" "/opt/localagi"
+
+msg_info "Configuring LocalAGI"
+mkdir -p /opt/localagi/pool
+cat <<'EOF' >/opt/localagi/.env
+LOCALAGI_MODEL=gemma-3-4b-it-qat
+LOCALAGI_MULTIMODAL_MODEL=moondream2-20250414
+LOCALAGI_IMAGE_MODEL=sd-1.5-ggml
+LOCALAGI_LLM_API_URL=http://127.0.0.1:11434/v1
+LOCALAGI_STATE_DIR=/opt/localagi/pool
+EOF
+msg_ok "Configured LocalAGI"
+
+msg_info "Setting up LocalAGI"
+cd /opt/localagi/webui/react-ui
+$STD bun install
+$STD bun run build
+cd /opt/localagi
+$STD go build -o /usr/local/bin/localagi
+msg_ok "Set up LocalAGI"
+
+msg_info "Creating LocalAGI systemd service"
+cat </etc/systemd/system/localagi.service
+[Unit]
+Description=LocalAGI
+After=network.target
+
+[Service]
+User=root
+Type=simple
+EnvironmentFile=/opt/localagi/.env
+
+WorkingDirectory=/opt/localagi
+ExecStart=/usr/local/bin/localagi
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now localagi
+msg_ok "Created LocalAGI systemd service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/n8n-install.sh b/install/n8n-install.sh
index 8ddf37d0b..c6f0d421a 100644
--- a/install/n8n-install.sh
+++ b/install/n8n-install.sh
@@ -16,7 +16,6 @@ update_os
msg_info "Installing Dependencies"
$STD apt install -y \
build-essential \
- python3 \
python3-setuptools \
graphicsmagick
msg_ok "Installed Dependencies"
@@ -24,7 +23,7 @@ msg_ok "Installed Dependencies"
NODE_VERSION="24" setup_nodejs
msg_info "Installing n8n (Patience)"
-$STD npm install -g n8n@2.27.5
+$STD npm install -g n8n@latest
msg_ok "Installed n8n"
msg_info "Creating Service"
diff --git a/install/nexterm-install.sh b/install/nexterm-install.sh
new file mode 100644
index 000000000..1ef0ecfe8
--- /dev/null
+++ b/install/nexterm-install.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: Mathias Wagner (gnmyt)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://nexterm.dev/
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+fetch_and_deploy_gh_release "nexterm-engine" "gnmyt/Nexterm" "prebuild" "latest" "/opt/nexterm/engine" "nexterm-engine-linux-$(arch_resolve "x64" "arm64").tar.gz"
+fetch_and_deploy_gh_release "nexterm-server" "gnmyt/Nexterm" "singlefile" "latest" "/opt/nexterm/server" "nexterm-server-linux-$(arch_resolve "x64" "arm64")"
+
+msg_info "Configuring Nexterm"
+LOCAL_ENGINE_TOKEN=$(tr -d '-' /etc/nexterm-engine/config.yaml
+server_host: "127.0.0.1"
+server_port: 7800
+registration_token: "${LOCAL_ENGINE_TOKEN}"
+tls: false
+EOF
+cat </etc/nexterm-server/server.env
+NODE_ENV=production
+SERVER_PORT=6989
+LOCAL_ENGINE_TOKEN=${LOCAL_ENGINE_TOKEN}
+ENCRYPTION_KEY=${ENCRYPTION_KEY}
+EOF
+chmod 0640 /etc/nexterm-engine/config.yaml /etc/nexterm-server/server.env
+msg_ok "Configured Nexterm"
+
+msg_info "Creating Services"
+cat </etc/systemd/system/nexterm-server.service
+[Unit]
+Description=Nexterm Server
+Documentation=https://docs.nexterm.dev/
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/nexterm/data
+EnvironmentFile=/etc/nexterm-server/server.env
+ExecStart=/opt/nexterm/server/nexterm-server
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+cat </etc/systemd/system/nexterm-engine.service
+[Unit]
+Description=Nexterm Engine
+Documentation=https://docs.nexterm.dev/
+After=network-online.target nexterm-server.service
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/etc/nexterm-engine
+Environment=FREERDP_EXTENSION_PATH=/opt/nexterm/engine/lib/freerdp2
+Environment=LD_LIBRARY_PATH=/opt/nexterm/engine/lib
+ExecStart=/opt/nexterm/engine/nexterm-engine
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now nexterm-server
+sleep 5
+systemctl enable -q --now nexterm-engine
+msg_ok "Created Services"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/notediscovery-install.sh b/install/notediscovery-install.sh
new file mode 100644
index 000000000..26687f8c6
--- /dev/null
+++ b/install/notediscovery-install.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/gamosoft/NoteDiscovery
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+setup_uv
+
+fetch_and_deploy_gh_release "notediscovery" "gamosoft/NoteDiscovery" "tarball"
+
+msg_info "Installing Dependencies"
+cd /opt/notediscovery
+$STD uv sync --no-dev
+msg_ok "Installed Dependencies"
+
+msg_info "Configuring NoteDiscovery"
+mkdir -p /opt/notediscovery/data
+msg_ok "Configured NoteDiscovery"
+
+msg_info "Creating Service"
+cat </etc/systemd/system/notediscovery.service
+[Unit]
+Description=NoteDiscovery Knowledge Base
+After=network.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/notediscovery
+ExecStart=/opt/notediscovery/.venv/bin/python /opt/notediscovery/run.py
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now notediscovery
+msg_ok "Created Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/opencloud-install.sh b/install/opencloud-install.sh
index 608440062..d0cb26994 100644
--- a/install/opencloud-install.sh
+++ b/install/opencloud-install.sh
@@ -64,7 +64,7 @@ $STD sudo -u cool coolconfig set-admin-password --user=admin --password="$COOLPA
echo "$COOLPASS" >~/.coolpass
msg_ok "Installed Collabora Online"
-fetch_and_deploy_gh_release "OpenCloud" "opencloud-eu/opencloud" "singlefile" "v7.2.0" "/usr/bin" "opencloud-*-linux-$(arch_resolve)"
+fetch_and_deploy_gh_release "OpenCloud" "opencloud-eu/opencloud" "singlefile" "v7.3.0" "/usr/bin" "opencloud-*-linux-$(arch_resolve)"
mv /usr/bin/OpenCloud /usr/bin/opencloud
msg_info "Configuring OpenCloud"
diff --git a/install/oxicloud-install.sh b/install/oxicloud-install.sh
new file mode 100644
index 000000000..1b80f103c
--- /dev/null
+++ b/install/oxicloud-install.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: vhsdream
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/DioCrafts/OxiCloud
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Installing Dependencies"
+$STD apt install -y build-essential
+msg_ok "Installed Dependencies"
+
+NODE_VERSION="24" setup_nodejs
+PG_VERSION="17" setup_postgresql
+PG_DB_NAME="oxicloud" PG_DB_USER="oxicloud" setup_postgresql_db
+fetch_and_deploy_gh_release "OxiCloud" "DioCrafts/OxiCloud" "tarball" "latest" "/opt/oxicloud"
+TOOLCHAIN="$(grep -oP 'FROM\s+rust:\K[0-9]+\.[0-9]+(\.[0-9]+)?' /opt/oxicloud/Dockerfile | head -1)"
+RUST_TOOLCHAIN="${TOOLCHAIN:-stable}" setup_rust
+
+msg_info "Building Frontend SPA"
+cd /opt/oxicloud/frontend
+$STD npm ci
+$STD npm run build
+msg_ok "Built Frontend SPA"
+
+msg_info "Building OxiCloud (Patience)"
+cd /opt/oxicloud
+export DATABASE_URL="postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}"
+export RUSTFLAGS="-C target-cpu=native"
+RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
+CARGO_JOBS=$((RAM_MB / 2560))
+[[ $CARGO_JOBS -lt 1 ]] && CARGO_JOBS=1
+[[ $CARGO_JOBS -gt $(nproc) ]] && CARGO_JOBS=$(nproc)
+$STD cargo build --release -j "$CARGO_JOBS" --bin oxicloud --bin migrate-nfc-filenames
+mv target/release/oxicloud /usr/local/bin/oxicloud
+mv target/release/migrate-nfc-filenames /usr/local/bin/migrate-nfc-filenames
+rm -rf /opt/oxicloud/static
+mv /opt/oxicloud/static-dist /opt/oxicloud/static
+rm -rf /opt/oxicloud/target /opt/oxicloud/frontend/node_modules
+msg_ok "Built OxiCloud"
+
+msg_info "Configuring OxiCloud"
+mkdir -p {/mnt/oxicloud,/etc/oxicloud}
+sed -e 's|OXICLOUD_STORAGE_PATH=.*|OXICLOUD_STORAGE_PATH=/mnt/oxicloud|' \
+ -e 's|OXICLOUD_SERVER_HOST=.*|OXICLOUD_SERVER_HOST=0.0.0.0|' \
+ -e 's|OXICLOUD_STATIC_PATH=.*|OXICLOUD_STATIC_PATH=/opt/oxicloud/static|' \
+ -e "s|^#OXICLOUD_BASE_URL=.*|OXICLOUD_BASE_URL=http://${LOCAL_IP}:8086|" \
+ -e "s|OXICLOUD_DB_CONNECTION_STRING=.*|OXICLOUD_DB_CONNECTION_STRING=${DATABASE_URL}|" \
+ -e "s|^DATABASE_URL=.*|DATABASE_URL=${DATABASE_URL}|" \
+ -e "s|^#OXICLOUD_JWT_SECRET=.*|OXICLOUD_JWT_SECRET=$(openssl rand -hex 32)|" \
+ /opt/oxicloud/example.env >/etc/oxicloud/.env
+chmod 600 /etc/oxicloud/.env
+msg_ok "Configured OxiCloud"
+
+msg_info "Creating OxiCloud Service"
+cat </etc/systemd/system/oxicloud.service
+[Unit]
+Description=OxiCloud Service
+After=network.target postgresql.service
+Requires=postgresql.service
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/oxicloud
+EnvironmentFile=/etc/oxicloud/.env
+ExecStart=/usr/local/bin/oxicloud
+Restart=always
+RestartSec=5
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now oxicloud
+msg_ok "Created OxiCloud Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/pangolin-install.sh b/install/pangolin-install.sh
index ab3fc5095..0a8f008af 100644
--- a/install/pangolin-install.sh
+++ b/install/pangolin-install.sh
@@ -16,18 +16,19 @@ update_os
msg_info "Installing Dependencies"
$STD apt install -y \
build-essential \
- python3 \
- sqlite3 \
iptables
msg_ok "Installed Dependencies"
NODE_VERSION="24" setup_nodejs
-PANGOLIN_VERSION="${PANGOLIN_VERSION:-1.18.4}"
+PG_VERSION="17" setup_postgresql
+PG_DB_NAME="pangolin" PG_DB_USER="pangolin" setup_postgresql_db
+PANGOLIN_VERSION="${PANGOLIN_VERSION:-1.20.0}"
fetch_and_deploy_gh_release "pangolin" "fosrl/pangolin" "tarball" "$PANGOLIN_VERSION"
fetch_and_deploy_gh_release "gerbil" "fosrl/gerbil" "singlefile" "latest" "/usr/bin" "gerbil_linux_$(arch_resolve)"
fetch_and_deploy_gh_release "traefik" "traefik/traefik" "prebuild" "latest" "/usr/bin" "traefik_v*_linux_$(arch_resolve).tar.gz"
read -rp "${TAB3}Enter your Pangolin URL (ex: https://pangolin.example.com): " pango_url
+[[ "$pango_url" != https://* && "$pango_url" != http://* ]] && pango_url="https://${pango_url}"
read -rp "${TAB3}Enter your email address: " pango_email
msg_info "Setup Pangolin"
@@ -36,13 +37,14 @@ BADGER_VERSION=$(get_latest_github_release "fosrl/badger" "false")
cd /opt/pangolin
mkdir -p /opt/pangolin/config/{traefik,db,letsencrypt,logs}
$STD npm ci
-$STD npm run set:sqlite
+$STD npm run set:pg
$STD npm run set:oss
rm -rf server/private
-$STD npm run db:generate
+DATABASE_URL="postgresql://pangolin:${PG_DB_PASS}@localhost:5432/pangolin" $STD npm run db:generate
$STD npm run build
$STD npm run build:cli
cp -R .next/standalone ./
+cp -r server/migrations ./dist/init
cat </usr/local/bin/pangctl
#!/bin/sh
@@ -74,6 +76,9 @@ flags:
require_email_verification: false
disable_signup_without_invite: false
disable_user_create_org: false
+
+postgres:
+ connection_string: "postgresql://pangolin:${PG_DB_PASS}@localhost:5432/pangolin"
EOF
cat </opt/pangolin/config/traefik/traefik_config.yml
@@ -181,7 +186,8 @@ http:
servers:
- url: "http://$LOCAL_IP:3000"
EOF
-$STD npm run db:push
+export ENVIRONMENT=prod
+$STD node dist/migrations.mjs
. /etc/os-release
if [ "$VERSION_CODENAME" = "trixie" ]; then
@@ -197,7 +203,8 @@ msg_info "Creating Services"
cat </etc/systemd/system/pangolin.service
[Unit]
Description=Pangolin Service
-After=network.target
+After=network.target postgresql.service
+Wants=postgresql.service
[Service]
Type=simple
diff --git a/install/pihole-install.sh b/install/pihole-install.sh
index c88a7c997..547d79f23 100644
--- a/install/pihole-install.sh
+++ b/install/pihole-install.sh
@@ -119,7 +119,8 @@ edns-packet-max=1232
EOF
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
- cat </etc/unbound/unbound.conf.d/pi-hole.conf
+ cat <>/etc/unbound/unbound.conf.d/pi-hole.conf
+server:
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
forward-zone:
name: "."
diff --git a/install/pocketid-install.sh b/install/pocketid-install.sh
index 47f945d3b..11b8fa591 100644
--- a/install/pocketid-install.sh
+++ b/install/pocketid-install.sh
@@ -14,7 +14,7 @@ network_check
update_os
read -r -p "${TAB3}What public URL do you want to use (e.g. pocketid.mydomain.com)? " public_url
-fetch_and_deploy_gh_release "pocket-id" "pocket-id/pocket-id" "singlefile" "latest" "/opt/pocket-id/" "pocket-id-linux-$(arch_resolve)"
+fetch_and_deploy_gh_release "pocket-id" "pocket-id/pocket-id" "singlefile" "latest" "/opt/pocket-id/" "pocket-id_linux_$(arch_resolve)"
msg_info "Configuring Pocket ID"
ENCRYPTION_KEY=$(openssl rand -base64 32)
diff --git a/install/reitti-install.sh b/install/reitti-install.sh
index 2810b1495..512b0cd13 100644
--- a/install/reitti-install.sh
+++ b/install/reitti-install.sh
@@ -30,27 +30,30 @@ mv /opt/reitti/reitti-*.jar /opt/reitti/reitti.jar
msg_info "Installing Nginx Tile Cache"
mkdir -p /var/cache/nginx/tiles
-cat </etc/nginx/nginx.conf
+cat <<'NGINXEOF' >/etc/nginx/nginx.conf
user www-data;
events {
worker_connections 1024;
}
http {
+ resolver 1.1.1.1 8.8.8.8 valid=30s ipv6=off;
proxy_cache_path /var/cache/nginx/tiles levels=1:2 keys_zone=tiles:10m max_size=1g inactive=30d use_temp_path=off;
server {
listen 80;
- location / {
- proxy_pass https://tile.openstreetmap.org/;
- proxy_set_header Host tile.openstreetmap.org;
+ location /custom/ {
+ set $upstream_url $http_x_reitti_upstream_url;
+ proxy_pass $upstream_url;
+ proxy_set_header Host $proxy_host;
proxy_set_header User-Agent "Reitti/1.0";
proxy_cache tiles;
+ proxy_cache_key $upstream_url;
proxy_cache_valid 200 30d;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
}
}
}
-EOF
+NGINXEOF
chown -R www-data:www-data /var/cache/nginx
chmod -R 750 /var/cache/nginx
systemctl restart nginx
@@ -71,6 +74,7 @@ server.compression.mime-types=text/plain,application/json
logging.level.root=INFO
logging.level.org.hibernate.engine.jdbc.spi.SqlExceptionHelper=FATAL
logging.level.com.dedicatedcode.reitti=INFO
+logging.level.org.quartz.core.ErrorLogger=FATAL
# Internationalization
spring.messages.basename=messages
@@ -82,7 +86,7 @@ spring.messages.fallback-to-system-locale=false
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/$PG_DB_NAME
spring.datasource.username=$PG_DB_USER
spring.datasource.password=$PG_DB_PASS
-spring.datasource.hikari.maximum-pool-size=20
+spring.datasource.hikari.maximum-pool-size=30
# Redis configuration
spring.data.redis.host=127.0.0.1
@@ -92,20 +96,23 @@ spring.data.redis.password=
spring.data.redis.database=0
spring.cache.redis.key-prefix=
-spring.cache.cache-names=processed-visits,significant-places,users,magic-links,configurations,transport-mode-configs,avatarThumbnails,avatarData,user-settings
+spring.cache.cache-names=processed-visits,significant-places,users,magic-links,configurations,transport-mode-configs,avatarThumbnails,avatarData,user-settings,devices,mapStyles,mapStyleJson
spring.cache.redis.time-to-live=1d
# Upload configuration
spring.servlet.multipart.max-file-size=5GB
spring.servlet.multipart.max-request-size=5GB
+spring.servlet.multipart.resolve-lazily=true
server.tomcat.max-part-count=100
+spring.mvc.async.request-timeout=600000
-# Rqueue configuration
-rqueue.web.enable=false
-rqueue.job.enabled=false
-rqueue.message.durability.in-terminal-state=0
-rqueue.key.prefix=\${spring.cache.redis.key-prefix}
-rqueue.message.converter.provider.class=com.dedicatedcode.reitti.config.RQueueCustomMessageConverter
+# Quartz Scheduler configuration
+spring.quartz.job-store-type=jdbc
+spring.quartz.jdbc.initialize-schema=never
+spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
+spring.quartz.properties.org.quartz.jobStore.isClustered=false
+spring.quartz.properties.org.quartz.jobStore.tablePrefix=qrtz_
+spring.quartz.properties.org.quartz.threadPool.threadCount=5
# Application-specific settings
reitti.server.advertise-uri=
@@ -117,18 +124,25 @@ reitti.security.oidc.enabled=false
reitti.security.oidc.registration.enabled=false
reitti.import.batch-size=10000
-reitti.import.processing-idle-start-time=10
+reitti.import.grace-time-seconds=30
+reitti.import.staging.cleanup.cron=0 0 4 * * *
+
+reitti.batching.max-batch-size=100
+reitti.batching.max-wait-time=5
reitti.geo-point-filter.max-speed-kmh=1000
reitti.geo-point-filter.max-accuracy-meters=100
reitti.geo-point-filter.history-lookback-hours=24
reitti.geo-point-filter.window-size=50
-reitti.process-data.schedule=0 */10 * * * *
reitti.process-data.refresh-views.schedule=0 0 4 * * *
reitti.imports.schedule=0 5/10 * * * *
reitti.imports.owntracks-recorder.schedule=\${reitti.imports.schedule}
+reitti.jobs.cleanup.cron=0 0 4 * * ?
+reitti.jobs.cleanup.max-age-hours=24
+reitti.db-janitor.schedule=0 0 4 * * ?
+
# Geocoding service configuration
reitti.geocoding.max-errors=10
reitti.geocoding.photon.base-url=
diff --git a/install/sftpgo-install.sh b/install/sftpgo-install.sh
index d27d4f761..5547d2e59 100644
--- a/install/sftpgo-install.sh
+++ b/install/sftpgo-install.sh
@@ -19,8 +19,8 @@ msg_ok "Installed Dependencies"
setup_deb822_repo \
"sftpgo" \
- "https://ftp.osuosl.org/pub/sftpgo/apt/gpg.key" \
- "https://ftp.osuosl.org/pub/sftpgo/apt" \
+ "https://oss.sftpgo.com/apt/gpg.key" \
+ "https://oss.sftpgo.com/apt" \
"trixie"
msg_info "Installing SFTPGo"
diff --git a/install/shinobi-install.sh b/install/shinobi-install.sh
index 383325016..6107c6488 100644
--- a/install/shinobi-install.sh
+++ b/install/shinobi-install.sh
@@ -54,7 +54,7 @@ cronKey=$(head -c 1024 /etc/systemd/system/silverbullet.service
[Unit]
@@ -25,6 +36,7 @@ After=syslog.target network.target
[Service]
User=root
Type=simple
+${RUNTIME_API_ENV}
ExecStart=/opt/silverbullet/bin/silverbullet --hostname 0.0.0.0 --port 3000 /opt/silverbullet/space
WorkingDirectory=/opt/silverbullet
Restart=on-failure
diff --git a/install/snapotter-install.sh b/install/snapotter-install.sh
index 7878c441a..b8330b7ca 100644
--- a/install/snapotter-install.sh
+++ b/install/snapotter-install.sh
@@ -39,6 +39,19 @@ msg_ok "Installed Dependencies"
PYTHON_VERSION="3.11" setup_uv
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
+PG_VERSION="17" setup_postgresql
+PG_DB_NAME="snapotter" PG_DB_USER="snapotter" setup_postgresql_db
+
+msg_info "Installing Redis"
+$STD apt install -y redis-server
+if grep -q '^appendonly ' /etc/redis/redis.conf; then
+ sed -i 's/^appendonly .*/appendonly yes/' /etc/redis/redis.conf
+else
+ echo 'appendonly yes' >>/etc/redis/redis.conf
+fi
+$STD systemctl enable --now redis-server
+msg_ok "Installed Redis"
+
fetch_and_deploy_gh_release "caire" "esimov/caire" "prebuild" "latest" "/usr/local/bin" "caire-*-linux-amd64.tar.gz"
fetch_and_deploy_gh_release "snapotter" "snapotter-hq/SnapOtter" "prebuild" "latest" "/opt/snapotter" "snapotter-*-linux-amd64.tar.gz"
@@ -61,7 +74,8 @@ mkdir -p /tmp/snapotter-workspace
cat </opt/snapotter_data/.env
PORT=1349
NODE_ENV=production
-DB_PATH=/opt/snapotter_data/snapotter.db
+DATABASE_URL=postgres://${PG_DB_USER}:${PG_DB_PASS}@127.0.0.1:5432/${PG_DB_NAME}
+REDIS_URL=redis://127.0.0.1:6379
WORKSPACE_PATH=/tmp/snapotter-workspace
FILES_STORAGE_PATH=/opt/snapotter_data/files
PYTHON_VENV_PATH=/opt/snapotter_data/ai/venv
@@ -85,7 +99,9 @@ PNPM_BIN="$(command -v pnpm)"
cat </etc/systemd/system/snapotter.service
[Unit]
Description=SnapOtter Service
-After=network.target
+Wants=network-online.target
+After=network-online.target postgresql.service redis-server.service
+Requires=postgresql.service redis-server.service
[Service]
Type=simple
@@ -99,6 +115,7 @@ RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
+systemctl daemon-reload
systemctl enable -q --now snapotter
msg_ok "Created Service"
diff --git a/install/squid-install.sh b/install/squid-install.sh
new file mode 100644
index 000000000..aa0110820
--- /dev/null
+++ b/install/squid-install.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: 007hacky007
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://www.squid-cache.org/
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Configuring Squid"
+mkdir -p /etc/squid
+cat </etc/squid/squid.conf
+acl localnet src 0.0.0.1-0.255.255.255
+acl localnet src 10.0.0.0/8
+acl localnet src 100.64.0.0/10
+acl localnet src 169.254.0.0/16
+acl localnet src 172.16.0.0/12
+acl localnet src 192.168.0.0/16
+acl localnet src fc00::/7
+acl localnet src fe80::/10
+
+acl SSL_ports port 443
+acl Safe_ports port 80
+acl Safe_ports port 21
+acl Safe_ports port 443
+acl Safe_ports port 70
+acl Safe_ports port 210
+acl Safe_ports port 1025-65535
+acl Safe_ports port 280
+acl Safe_ports port 488
+acl Safe_ports port 591
+acl Safe_ports port 777
+acl CONNECT method CONNECT
+
+http_access deny !Safe_ports
+http_access deny CONNECT !SSL_ports
+http_access allow localhost manager
+http_access deny manager
+
+auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwords
+auth_param basic realm proxy
+acl authenticated proxy_auth REQUIRED
+http_access allow authenticated
+http_access deny all
+
+http_port 3128
+
+coredump_dir /var/spool/squid
+
+refresh_pattern ^ftp: 1440 20% 10080
+refresh_pattern ^gopher: 1440 0% 1440
+refresh_pattern -i (/cgi-bin/|\\?) 0 0% 0
+refresh_pattern . 0 20% 4320
+
+# Privacy / hardening
+httpd_suppress_version_string on
+visible_hostname $(hostname)
+forwarded_for delete
+request_header_access X-Forwarded-For deny all
+EOF
+msg_ok "Configured Squid"
+
+msg_info "Installing Dependencies"
+$STD apt install -y \
+ squid \
+ apache2-utils
+msg_ok "Installed Dependencies"
+
+msg_info "Configuring Squid Authentication"
+touch /etc/squid/passwords
+chown proxy:proxy /etc/squid/passwords
+chmod 640 /etc/squid/passwords
+$STD squid -k parse
+msg_ok "Configured Squid Authentication"
+
+msg_info "Starting Service"
+systemctl enable -q --now squid
+msg_ok "Started Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/storyteller-install.sh b/install/storyteller-install.sh
index b3b343da6..1df32ec27 100644
--- a/install/storyteller-install.sh
+++ b/install/storyteller-install.sh
@@ -28,7 +28,7 @@ NODE_VERSION="24" NODE_MODULE="corepack,yarn" setup_nodejs
fetch_and_deploy_gh_release "readium" "readium/cli" "prebuild" "latest" "/opt/readium" "readium_linux_$(arch_resolve "x86_64" "arm64").tar.gz"
ln -sf /opt/readium/readium /usr/local/bin/readium
-fetch_and_deploy_gl_release "storyteller" "storyteller-platform/storyteller" "tarball" "latest" "/opt/storyteller"
+fetch_and_deploy_gl_release "storyteller" "storyteller-platform/storyteller" "tarball" "latest" "/opt/storyteller" "" "web-v2"
msg_info "Setting up Storyteller"
cd /opt/storyteller
diff --git a/install/sync-in-install.sh b/install/sync-in-install.sh
new file mode 100644
index 000000000..0550fecca
--- /dev/null
+++ b/install/sync-in-install.sh
@@ -0,0 +1,82 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/Sync-in/server
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+NODE_VERSION="22" setup_nodejs
+setup_mariadb
+MARIADB_DB_NAME="sync_in" MARIADB_DB_USER="sync_in" setup_mariadb_db
+
+msg_info "Installing Sync-in"
+mkdir -p /opt/sync-in/data
+$STD npm install --prefix /opt/sync-in @sync-in/server
+msg_ok "Installed Sync-in"
+
+msg_info "Configuring Sync-in"
+ENCRYPT_KEY=$(openssl rand -hex 32)
+ACCESS_SECRET=$(openssl rand -hex 32)
+REFRESH_SECRET=$(openssl rand -hex 32)
+cat </opt/sync-in/environment.yaml
+server:
+ port: 8080
+mysql:
+ url: 'mysql://${MARIADB_DB_USER}:${MARIADB_DB_PASS}@localhost:3306/${MARIADB_DB_NAME}'
+auth:
+ encryptionKey: '${ENCRYPT_KEY}'
+ token:
+ access:
+ secret: '${ACCESS_SECRET}'
+ refresh:
+ secret: '${REFRESH_SECRET}'
+applications:
+ files:
+ dataPath: '/opt/sync-in/data'
+EOF
+msg_ok "Configured Sync-in"
+
+msg_info "Running Database Migrations"
+cd /opt/sync-in
+$STD npx sync-in-server migrate-db
+msg_ok "Ran Database Migrations"
+
+msg_info "Creating Admin User"
+cd /opt/sync-in
+$STD npx sync-in-server create-user
+msg_ok "Created Admin User"
+
+VERSION=$(node -pe "require('/opt/sync-in/node_modules/@sync-in/server/package.json').version" 2>/dev/null || echo "")
+[[ -n "$VERSION" ]] && echo "$VERSION" >~/.sync-in
+
+msg_info "Creating Service"
+cat </etc/systemd/system/sync-in.service
+[Unit]
+Description=Sync-in Server
+After=network.target mariadb.service
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/sync-in
+ExecStart=/opt/sync-in/node_modules/.bin/sync-in-server start
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now sync-in
+msg_ok "Created Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/install/wanderer-install.sh b/install/wanderer-install.sh
index 3cebc9fef..6a8571bbf 100644
--- a/install/wanderer-install.sh
+++ b/install/wanderer-install.sh
@@ -20,7 +20,8 @@ if [[ "$(arch_resolve)" == "arm64" ]]; then
else
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary" "latest" "/opt/wanderer/source/search"
fi
-mkdir -p /opt/wanderer/{source,data/pb_data,data/meili_data}
+mkdir -p /opt/wanderer/{source,data/pb_data,data/meili_data,data/plugins}
+[[ -e /data/plugins ]] || ln -sfn /opt/wanderer/data/plugins /data/plugins
fetch_and_deploy_gh_release "wanderer" "open-wanderer/wanderer" "tarball" "latest" "/opt/wanderer/source"
msg_info "Installing wanderer (patience)"
@@ -32,6 +33,12 @@ $STD npm ci
$STD npm run build
msg_ok "Installed wanderer"
+msg_info "Installing wanderer plugins"
+for plugin in hammerhead komoot strava; do
+ fetch_and_deploy_gh_release "wanderer-plugin-${plugin}" "open-wanderer/wanderer" "prebuild" "latest" "/opt/wanderer/data/plugins" "wanderer-plugin-${plugin}.tar.gz" || msg_warn "Failed to install wanderer plugin: ${plugin}"
+done
+msg_ok "Installed wanderer plugins"
+
msg_info "Creating Service"
MEILI_KEY=$(openssl rand -hex 32)
POCKETBASE_KEY=$(openssl rand -hex 16)
diff --git a/install/webtrees-install.sh b/install/webtrees-install.sh
index c2be2e79e..69b735e46 100644
--- a/install/webtrees-install.sh
+++ b/install/webtrees-install.sh
@@ -47,6 +47,8 @@ msg_ok "Configured Caddy"
msg_info "Automating Webtrees Setup"
cd /opt/webtrees
+mkdir -p /opt/webtrees/data
+chown -R www-data:www-data /opt/webtrees/data
WT_ADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c15)
$STD sudo -u www-data php /opt/webtrees/index.php config-ini \
--dbhost=127.0.0.1 \
@@ -56,6 +58,15 @@ $STD sudo -u www-data php /opt/webtrees/index.php config-ini \
--dbname=webtrees \
--tblpfx=wt_ \
--base-url="http://${LOCAL_IP}"
+msg_info "Initializing Webtrees database schema"
+for i in {1..15}; do
+ if curl -sf "http://127.0.0.1/" >/dev/null 2>&1; then
+ break
+ fi
+ sleep 2
+done
+$STD mariadb -u webtrees -p"${MARIADB_DB_PASS}" -h 127.0.0.1 webtrees -e "SHOW TABLES LIKE 'wt_user';" | grep -q wt_user
+msg_ok "Initialized Webtrees database schema"
$STD sudo -u www-data php /opt/webtrees/index.php user Admin \
--create \
--real-name="Administrator" \
diff --git a/install/yuvomi-install.sh b/install/yuvomi-install.sh
new file mode 100644
index 000000000..521049586
--- /dev/null
+++ b/install/yuvomi-install.sh
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+
+# Copyright (c) 2021-2026 community-scripts ORG
+# Author: MickLesk (CanbiZ)
+# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
+# Source: https://github.com/ulsklyc/yuvomi
+
+source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
+color
+verb_ip6
+catch_errors
+setting_up_container
+network_check
+update_os
+
+msg_info "Installing Dependencies"
+$STD apt install -y \
+ python3 \
+ make \
+ g++ \
+ libsqlcipher-dev
+msg_ok "Installed Dependencies"
+
+NODE_VERSION="22" setup_nodejs
+
+fetch_and_deploy_gh_release "yuvomi" "ulsklyc/yuvomi" "tarball"
+
+msg_info "Installing Node.js Dependencies"
+cd /opt/yuvomi
+$STD npm ci --omit=dev
+msg_ok "Installed Node.js Dependencies"
+
+msg_info "Configuring Yuvomi"
+mkdir -p /opt/yuvomi/data /opt/yuvomi/backups
+SESSION_SECRET=$(openssl rand -hex 32)
+DB_ENCRYPT_KEY=$(openssl rand -hex 32)
+cat </opt/yuvomi/.env
+PORT=3000
+NODE_ENV=production
+DB_PATH=/opt/yuvomi/data/yuvomi.db
+DB_ENCRYPTION_KEY=${DB_ENCRYPT_KEY}
+SESSION_SECRET=${SESSION_SECRET}
+RATE_LIMIT_WINDOW_MS=60000
+RATE_LIMIT_MAX_ATTEMPTS=5
+RATE_LIMIT_BLOCK_DURATION_MS=900000
+EOF
+msg_ok "Configured Yuvomi"
+
+msg_info "Creating Service"
+cat </etc/systemd/system/yuvomi.service
+[Unit]
+Description=Yuvomi Family Planner
+After=network.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/yuvomi
+EnvironmentFile=/opt/yuvomi/.env
+ExecStart=/usr/bin/node server/index.js
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl enable -q --now yuvomi
+msg_ok "Created Service"
+
+motd_ssh
+customize
+cleanup_lxc
diff --git a/misc/build.func b/misc/build.func
index bf74bd3b6..53509780f 100644
--- a/misc/build.func
+++ b/misc/build.func
@@ -3753,30 +3753,32 @@ run_addon_updates() {
}
runtime_script_status_guard() {
+ local mode="${1:-}"
local script_slug="${SCRIPT_SLUG:-${NSAPP:-}}"
script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
[[ -z "$script_slug" ]] && return 0
- local api_url="https://db.community-scripts.org/api/collections/script_scripts/records?filter=(slug='${script_slug}')&perPage=1&fields=slug,is_disabled,is_deleted,disable_message,deleted_message"
+ local api_url="https://db.community-scripts.org/api/collections/script_scripts/records?filter=(slug='${script_slug}')&perPage=1&fields=slug,is_disabled,is_deleted,disable_message,deleted_message,pinned_version,pin_reason"
local response
if ! response=$(curl -fsSL --connect-timeout 2 --max-time 3 "$api_url" 2>/dev/null); then
msg_warn "Script status check is unavailable. Continuing without status verification."
return 0
fi
- if ! command -v jq >/dev/null 2>&1; then
- msg_warn "Missing jq for script status check. Continuing without status verification."
+ local is_deleted is_disabled deleted_message disable_message pinned_version pin_reason info_url
+ if printf '%s' "$response" | grep -qE '"items":[[:space:]]*\[[[:space:]]*\]'; then
return 0
fi
- local has_record is_deleted is_disabled deleted_message disable_message info_url
- has_record=$(printf '%s' "$response" | jq -r '.items | length')
- [[ "$has_record" == "0" ]] && return 0
-
- is_deleted=$(printf '%s' "$response" | jq -r '.items[0].is_deleted // false')
- is_disabled=$(printf '%s' "$response" | jq -r '.items[0].is_disabled // false')
- deleted_message=$(printf '%s' "$response" | jq -r '.items[0].deleted_message // ""')
- disable_message=$(printf '%s' "$response" | jq -r '.items[0].disable_message // ""')
+ # PocketBase returns a flat, fixed-field JSON blob; sed is enough here (no jq needed).
+ is_deleted=$(printf '%s' "$response" | sed -n 's/.*"is_deleted"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p' | head -1)
+ is_disabled=$(printf '%s' "$response" | sed -n 's/.*"is_disabled"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p' | head -1)
+ deleted_message=$(printf '%s' "$response" | sed -n 's/.*"deleted_message"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
+ disable_message=$(printf '%s' "$response" | sed -n 's/.*"disable_message"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
+ pinned_version=$(printf '%s' "$response" | sed -n 's/.*"pinned_version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
+ pin_reason=$(printf '%s' "$response" | sed -n 's/.*"pin_reason"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
+ is_deleted=${is_deleted:-false}
+ is_disabled=${is_disabled:-false}
info_url="https://community-scripts.org/scripts/${script_slug}"
if [[ "$is_deleted" == "true" ]]; then
@@ -3806,6 +3808,26 @@ runtime_script_status_guard() {
return 1
fi
+ if [[ "$mode" == "update" && -n "$pinned_version" && -n "${NSAPP:-}" ]]; then
+ local current_file="$HOME/.${NSAPP}"
+ if [[ -f "$current_file" ]]; then
+ local installed pinned_clean installed_clean
+ installed="$(<"$current_file")"
+ pinned_clean="$pinned_version"
+ installed_clean="$installed"
+ [[ "$pinned_clean" =~ ^v[0-9] ]] && pinned_clean="${pinned_clean:1}"
+ [[ "$installed_clean" =~ ^v[0-9] ]] && installed_clean="${installed_clean:1}"
+ if [[ "$installed_clean" == "$pinned_clean" ]]; then
+ if [[ -n "$pin_reason" ]]; then
+ msg_info "You are already on the pinned version (${pinned_version}). ${pin_reason}"
+ else
+ msg_info "You are already on the pinned version (${pinned_version}). No newer update is offered intentionally — please do not open an issue unless you see an actual error."
+ fi
+ msg_info "More info: ${info_url}"
+ fi
+ fi
+ fi
+
return 0
}
@@ -3821,7 +3843,7 @@ runtime_script_status_guard() {
start() {
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
if command -v pveversion >/dev/null 2>&1; then
- runtime_script_status_guard || return 0
+ runtime_script_status_guard install || return 0
install_script || return 0
return 0
elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then
@@ -3829,7 +3851,7 @@ start() {
set_std_mode
ensure_profile_loaded
get_lxc_ip
- runtime_script_status_guard || return 0
+ runtime_script_status_guard update || return 0
update_script
run_addon_updates
update_motd_ip
@@ -3840,7 +3862,7 @@ start() {
set_std_mode
ensure_profile_loaded
get_lxc_ip
- runtime_script_status_guard || return 0
+ runtime_script_status_guard update || return 0
update_script
run_addon_updates
update_motd_ip
@@ -3870,7 +3892,7 @@ start() {
esac
ensure_profile_loaded
get_lxc_ip
- runtime_script_status_guard || return 0
+ runtime_script_status_guard update || return 0
update_script
run_addon_updates
update_motd_ip
diff --git a/misc/tools.func b/misc/tools.func
index e8e18f4f3..adb7a5ac4 100644
--- a/misc/tools.func
+++ b/misc/tools.func
@@ -36,6 +36,10 @@
#
# ==============================================================================
+# Guard against double-sourcing (core.func uses the same pattern)
+[[ -n "${_TOOLS_FUNC_LOADED:-}" ]] && return 0
+_TOOLS_FUNC_LOADED=1
+
# ------------------------------------------------------------------------------
# Debug helper - outputs to stderr when TOOLS_DEBUG is enabled
# Usage: debug_log "message"
@@ -91,16 +95,17 @@ curl_with_retry() {
while [[ $attempt -le $retries ]]; do
debug_log "curl attempt $attempt/$retries: $url"
- local curl_cmd="curl -fsSL --connect-timeout $connect_timeout --max-time $timeout"
- [[ -n "$extra_opts" ]] && curl_cmd="$curl_cmd $extra_opts"
+ # Build curl command as array to avoid command injection via extra_opts
+ local -a curl_args=(curl -fsSL --connect-timeout "$connect_timeout" --max-time "$timeout")
+ [[ -n "$extra_opts" ]] && read -ra _extra <<<"$extra_opts" && curl_args+=("${_extra[@]}")
if [[ "$output" == "-" ]]; then
- if $curl_cmd "$url"; then
+ if "${curl_args[@]}" "$url"; then
success=true
break
fi
else
- if $curl_cmd -o "$output" "$url"; then
+ if "${curl_args[@]}" -o "$output" "$url"; then
success=true
break
fi
@@ -153,15 +158,16 @@ curl_api_with_retry() {
while [[ $attempt -le $retries ]]; do
debug_log "curl API attempt $attempt/$retries: $url"
- local curl_cmd="curl -fsSL --connect-timeout $connect_timeout --max-time $timeout -w '%{http_code}'"
- [[ -n "$extra_opts" ]] && curl_cmd="$curl_cmd $extra_opts"
+ # Build curl command as array to avoid command injection via extra_opts
+ local -a curl_args=(curl -fsSL --connect-timeout "$connect_timeout" --max-time "$timeout" -w '%{http_code}')
+ [[ -n "$extra_opts" ]] && read -ra _extra <<<"$extra_opts" && curl_args+=("${_extra[@]}")
if [[ -n "$body_file" ]]; then
- http_code=$($curl_cmd -o "$body_file" "$url" 2>/dev/null) || true
+ http_code=$("${curl_args[@]}" -o "$body_file" "$url" 2>/dev/null) || true
else
# Capture body and http_code separately
local tmp_body="/tmp/curl_api_body_$$"
- http_code=$($curl_cmd -o "$tmp_body" "$url" 2>/dev/null) || true
+ http_code=$("${curl_args[@]}" -o "$tmp_body" "$url" 2>/dev/null) || true
if [[ -f "$tmp_body" ]]; then
cat "$tmp_body"
rm -f "$tmp_body"
@@ -304,7 +310,10 @@ edit_yaml_config() {
return 1
fi
- sed -i "s|^\([[:space:]]*${key}[[:space:]]*:\).*|\1 ${value}|" "$file"
+ # Escape sed metacharacters in value (| and &) to prevent injection
+ local escaped_value="${value//|/\\|}"
+ escaped_value="${escaped_value//&/\\&}"
+ sed -i "s|^\([[:space:]]*${key}[[:space:]]*:\).*|\1 ${escaped_value}|" "$file"
}
# ------------------------------------------------------------------------------
@@ -1687,10 +1696,6 @@ is_ubuntu() {
[[ "$(get_os_info id)" == "ubuntu" ]]
}
-is_alpine() {
- [[ "$(get_os_info id)" == "alpine" ]]
-}
-
# ------------------------------------------------------------------------------
# Get Debian/Ubuntu major version
# ------------------------------------------------------------------------------
@@ -2454,8 +2459,10 @@ start_timer() {
end_timer() {
local start_time="$1"
local label="${2:-Operation}"
- local end_time=$(date +%s)
+ local end_time
+ end_time=$(date +%s)
local duration=$((end_time - start_time))
+ echo "${label} took ${duration}s"
}
# ------------------------------------------------------------------------------
@@ -2559,6 +2566,199 @@ fetch_and_deploy_gh_tag() {
return 0
}
+# ------------------------------------------------------------------------------
+# Get the latest GitLab repository tag matching a glob pattern.
+#
+# Description:
+# - Queries the GitLab repository tags API (up to 100 tags per page)
+# - Filters tag names against a shell glob pattern (e.g. "web-v*" or "mobile-v*")
+# - Always excludes pre-release tags (those containing alpha, beta, or rc)
+# - Sorts matching tags with `sort -V` and returns the highest one
+# - Supports GITLAB_TOKEN for private/rate-limited projects
+#
+# Usage:
+# get_latest_gl_tag "owner/repo" "web-v*"
+# get_latest_gl_tag "owner/repo" "mobile-v*"
+# get_latest_gl_tag "owner/repo" # returns newest tag (no filter)
+#
+# Arguments:
+# $1 - GitLab repo path (namespace/project, e.g. "mygroup/myapp")
+# $2 - Optional glob pattern to filter tag names (e.g. "web-v*")
+#
+# Returns:
+# Latest matching tag name on stdout, or non-zero on failure
+# ------------------------------------------------------------------------------
+get_latest_gl_tag() {
+ local repo="$1"
+ local pattern="${2:-}"
+
+ local repo_encoded
+ repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')
+
+ local api_base="https://gitlab.com/api/v4/projects/${repo_encoded}/repository/tags"
+ local api_timeout="--connect-timeout 10 --max-time 60"
+
+ local header=()
+ [[ -n "${GITLAB_TOKEN:-}" ]] && header=(-H "PRIVATE-TOKEN: $GITLAB_TOKEN")
+
+ # If a pattern is given, pass it as a regex search to reduce server-side results.
+ # GitLab ?search= supports anchored regex; convert leading glob prefix to regex anchor.
+ local search_param=""
+ if [[ -n "$pattern" ]]; then
+ # Strip trailing wildcard for the search hint (server-side prefix filter).
+ local prefix="${pattern%%\**}"
+ [[ -n "$prefix" ]] && search_param="?search=^${prefix}&per_page=100" || search_param="?per_page=100"
+ else
+ search_param="?per_page=100"
+ fi
+
+ local temp_file
+ temp_file=$(mktemp) || return 1
+
+ local http_code
+ http_code=$(curl $api_timeout -sSL -w "%{http_code}" -o "$temp_file" \
+ "${header[@]}" "${api_base}${search_param}" 2>/dev/null) || true
+
+ if [[ "$http_code" != "200" ]]; then
+ rm -f "$temp_file"
+ msg_error "GitLab tags API returned HTTP $http_code for $repo"
+ return 22
+ fi
+
+ local tag=""
+ if [[ -n "$pattern" ]]; then
+ # Client-side glob filter + pre-release exclusion, then version-sort to pick the highest match.
+ tag=$(jq -r '.[].name' "$temp_file" 2>/dev/null | while IFS= read -r t; do
+ case "$t" in
+ *alpha* | *beta* | *rc*) continue ;;
+ $pattern) echo "$t" ;;
+ esac
+ done | sort -V | tail -n1)
+ else
+ # No pattern: skip pre-release tags, take the first remaining (newest) one.
+ tag=$(jq -r '.[].name' "$temp_file" 2>/dev/null |
+ grep -Eiv '(alpha|beta|rc)' |
+ head -n1)
+ fi
+
+ rm -f "$temp_file"
+
+ if [[ -z "$tag" ]]; then
+ msg_error "No tags matching '${pattern:-*}' found for ${repo}"
+ return 250
+ fi
+
+ echo "$tag"
+}
+
+# ------------------------------------------------------------------------------
+# Fetches and deploys a GitLab tag-based source tarball.
+#
+# Description:
+# - Resolves the latest tag matching the given glob pattern via get_latest_gl_tag
+# (or uses the exact tag if one is provided instead of "latest")
+# - Downloads the GitLab source tarball for that tag
+# - Extracts it to the target directory
+# - Writes the resolved tag to ~/. for update-checking
+#
+# Usage:
+# fetch_and_deploy_gl_tag "myapp" "mygroup/myrepo" "web-v*"
+# fetch_and_deploy_gl_tag "myapp" "mygroup/myrepo" "mobile-v*" "/opt/myapp"
+# fetch_and_deploy_gl_tag "myapp" "mygroup/myrepo" "v*" # any v-tag
+# fetch_and_deploy_gl_tag "myapp" "mygroup/myrepo" "web-v3.0*" # narrow version range
+#
+# Arguments:
+# $1 - App name (used for version file ~/. and lowercase tarball name)
+# $2 - GitLab repo path (namespace/project, e.g. "mygroup/myapp")
+# $3 - Tag pattern: glob (e.g. "web-v*") or exact tag (e.g. "web-v3.0.0").
+# Use "latest" to fetch the single newest tag with no pattern filter.
+# $4 - Target directory (default: /opt/$app)
+#
+# Notes:
+# - Supports CLEAN_INSTALL=1 to wipe target before extracting
+# - Supports GITLAB_TOKEN for private/rate-limited projects
+# - For repos that only publish tags, not formal GitLab Releases
+# (use fetch_and_deploy_gl_release for proper Releases with assets)
+# ------------------------------------------------------------------------------
+fetch_and_deploy_gl_tag() {
+ local app="$1"
+ local repo="$2"
+ local tag_pattern="${3:-latest}"
+ local target="${4:-/opt/$app}"
+
+ local app_lc=""
+ app_lc="$(echo "${app,,}" | tr -d ' ')"
+ local version_file="$HOME/.${app_lc}"
+
+ local api_timeout="--connect-timeout 10 --max-time 60"
+ local download_timeout="--connect-timeout 15 --max-time 900"
+
+ local header=()
+ [[ -n "${GITLAB_TOKEN:-}" ]] && header=(-H "PRIVATE-TOKEN: $GITLAB_TOKEN")
+
+ # Resolve the tag: if caller passed a glob/latest, query the API.
+ # If caller passed an exact tag (no wildcards), use it directly.
+ local resolved_tag="$tag_pattern"
+ if [[ "$tag_pattern" == "latest" || "$tag_pattern" == *"*"* || "$tag_pattern" == *"?"* ]]; then
+ local glob_arg=""
+ [[ "$tag_pattern" != "latest" ]] && glob_arg="$tag_pattern"
+ resolved_tag=$(get_latest_gl_tag "$repo" "$glob_arg") || {
+ msg_error "Failed to determine latest tag matching '${tag_pattern}' for ${repo}"
+ return 250
+ }
+ fi
+
+ local current_version=""
+ [[ -f "$version_file" ]] && current_version=$(<"$version_file")
+
+ if [[ "$current_version" == "$resolved_tag" ]]; then
+ msg_ok "$app is already up-to-date ($resolved_tag)"
+ return 0
+ fi
+
+ local repo_encoded
+ repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')
+
+ # GitLab source tarball URL (no release needed, works for any tag).
+ local version_safe="${resolved_tag//\//-}"
+ local tarball_url="https://gitlab.com/${repo}/-/archive/${resolved_tag}/${app_lc}-${version_safe}.tar.gz"
+
+ local tmpdir
+ tmpdir=$(mktemp -d) || return 1
+ local filename="${app_lc}-${version_safe}.tar.gz"
+
+ msg_info "Fetching GitLab tag: ${app} (${resolved_tag})"
+
+ curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$tarball_url" || {
+ msg_error "Download failed: $tarball_url"
+ rm -rf "$tmpdir"
+ return 7
+ }
+
+ mkdir -p "$target"
+ if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
+ rm -rf "${target:?}/"*
+ fi
+
+ tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
+ msg_error "Failed to extract tarball"
+ rm -rf "$tmpdir"
+ return 251
+ }
+
+ local unpack_dir
+ unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
+
+ shopt -s dotglob nullglob
+ cp -r "$unpack_dir"/* "$target/"
+ shopt -u dotglob nullglob
+
+ rm -rf "$tmpdir"
+ echo "$resolved_tag" >"$version_file"
+ msg_ok "Deployed ${app} ${resolved_tag} to ${target}"
+ return 0
+}
+
# ------------------------------------------------------------------------------
# Checks for new GitHub tag (for repos without releases).
#
@@ -2633,6 +2833,7 @@ check_for_gh_release() {
local source="$2"
local pinned_version_in="${3:-}" # optional
local pin_reason="${4:-}" # optional reason shown to user
+ local tag_prefix="${5:-}" # optional tag prefix filter (e.g. web-v2)
local app_lc=""
app_lc="$(echo "${app,,}" | tr -d ' ')"
local current_file="$HOME/.${app_lc}"
@@ -2688,7 +2889,7 @@ check_for_gh_release() {
rm -f "$gh_check_json"
fi
- if [[ -z "$pinned_version_in" ]]; then
+ if [[ -z "$pinned_version_in" && -z "$tag_prefix" ]]; then
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gh_check_json" \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
@@ -2716,7 +2917,7 @@ check_for_gh_release() {
rm -f "$gh_check_json"
fi
- # If no releases yet (pinned version OR /latest failed), fetch up to 100
+ # If no releases yet (pinned version, tag prefix, OR /latest failed), fetch up to 100
if [[ -z "$releases_json" ]]; then
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o "$gh_check_json" \
-H 'Accept: application/vnd.github+json' \
@@ -2754,9 +2955,18 @@ check_for_gh_release() {
rm -f "$gh_check_json"
fi
- mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<"$releases_json")
+ if [[ -n "$tag_prefix" ]]; then
+ mapfile -t raw_tags < <(jq -r --arg p "$tag_prefix" \
+ '.[] | select(.draft==false and .prerelease==false) | select(.tag_name | startswith($p)) | .tag_name' <<<"$releases_json")
+ else
+ mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<"$releases_json")
+ fi
if ((${#raw_tags[@]} == 0)); then
- msg_error "No stable releases found for ${app}"
+ if [[ -n "$tag_prefix" ]]; then
+ msg_error "No stable releases matching prefix '${tag_prefix}' found for ${app}"
+ else
+ msg_error "No stable releases found for ${app}"
+ fi
return 250
fi
@@ -3760,6 +3970,7 @@ fetch_and_deploy_gh_release() {
local version="${var_appversion:-${4:-latest}}"
local target="${5:-/opt/$app}"
local asset_pattern="${6:-}"
+ local tag_prefix="${7:-}"
# Validate app name to prevent /root/. directory issues
if [[ -z "$app" ]]; then
@@ -3789,7 +4000,13 @@ fetch_and_deploy_gh_release() {
TOOLS_GH_REL_JSON="$gh_rel_json"
local api_url="https://api.github.com/repos/$repo/releases"
- [[ "$version" != "latest" ]] && api_url="$api_url/tags/$version" || api_url="$api_url/latest"
+ if [[ "$version" != "latest" ]]; then
+ api_url="$api_url/tags/$version"
+ elif [[ -n "$tag_prefix" ]]; then
+ api_url="$api_url?per_page=100"
+ else
+ api_url="$api_url/latest"
+ fi
local header=()
[[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
@@ -3852,6 +4069,14 @@ fetch_and_deploy_gh_release() {
local json tag_name
json=$(<"$gh_rel_json")
+ if [[ "$version" == "latest" && -n "$tag_prefix" ]]; then
+ json=$(echo "$json" | jq --arg p "$tag_prefix" \
+ '[.[] | select(.draft==false and .prerelease==false) | select(.tag_name | startswith($p))][0] // empty')
+ if [[ -z "$json" || "$json" == "null" ]]; then
+ msg_error "No stable release matching prefix '${tag_prefix}' found for $repo on GitHub"
+ return 1
+ fi
+ fi
tag_name=$(echo "$json" | jq -r '.tag_name // .name // empty')
# Only strip leading 'v' when followed by a digit (e.g. v1.2.3), not words like "version/..."
[[ "$tag_name" =~ ^v[0-9] ]] && version="${tag_name:1}" || version="$tag_name"
@@ -4438,20 +4663,20 @@ setup_composer() {
# - Cleans up legacy repository files
#
# Usage:
-# setup_docker # Uses distro package (recommended)
-# USE_DOCKER_REPO=true setup_docker # Uses official Docker repo
+# setup_docker # Uses official Docker repo (recommended)
+# USE_DOCKER_REPO=false setup_docker # Uses distro docker.io package
# DOCKER_PORTAINER="true" setup_docker
# DOCKER_LOG_DRIVER="json-file" setup_docker
#
# Variables:
-# USE_DOCKER_REPO - Set to "true" to use official Docker repository
-# (default: false, uses distro docker.io package)
+# USE_DOCKER_REPO - Set to "false" to use distro docker.io package
+# (default: true, uses official Docker repository)
# DOCKER_PORTAINER - Install Portainer CE (optional, "true" to enable)
# DOCKER_LOG_DRIVER - Log driver (optional, default: "journald")
# DOCKER_SKIP_UPDATES - Skip container update check (optional, "true" to skip)
#
# Features:
-# - Uses stable distro packages by default
+# - Uses official Docker repository by default
# - Migrates from get.docker.com to repository-based installation
# - Updates Docker Engine if newer version available
# - Interactive per-container update prompt (Y/N, 60 s auto-no)
@@ -4467,7 +4692,7 @@ _docker_is_noninteractive() {
setup_docker() {
local docker_installed=false
local portainer_installed=false
- local USE_DOCKER_REPO="${USE_DOCKER_REPO:-false}"
+ local USE_DOCKER_REPO="${USE_DOCKER_REPO:-true}"
# Check if Docker is already installed
if command -v docker &>/dev/null; then
@@ -4482,7 +4707,7 @@ setup_docker() {
msg_info "Portainer container detected"
fi
- # Scenario 1: Use distro repository (default, most stable)
+ # Scenario 1: Use distro repository (opt-out via USE_DOCKER_REPO=false)
if [[ "$USE_DOCKER_REPO" != "true" && "$USE_DOCKER_REPO" != "TRUE" && "$USE_DOCKER_REPO" != "1" ]]; then
# Install or upgrade Docker from distro repo
@@ -6746,9 +6971,11 @@ setup_mariadb_db() {
msg_info "Setting up MariaDB Database"
- $STD mariadb -u root -e "CREATE DATABASE \`$MARIADB_DB_NAME\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
- $STD mariadb -u root -e "CREATE USER '$MARIADB_DB_USER'@'localhost' IDENTIFIED BY '$MARIADB_DB_PASS';"
- $STD mariadb -u root -e "GRANT ALL ON \`$MARIADB_DB_NAME\`.* TO '$MARIADB_DB_USER'@'localhost';"
+ # Use --defaults-extra-file to pass credentials safely and escape identifiers
+ # to prevent SQL injection via DB name / user / password
+ $STD mariadb -u root -e "CREATE DATABASE \`${MARIADB_DB_NAME//\`/\`\`}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
+ $STD mariadb -u root -e "CREATE USER '${MARIADB_DB_USER//\'/\'\'}'@'localhost' IDENTIFIED BY '${MARIADB_DB_PASS//\'/\'\'}';"
+ $STD mariadb -u root -e "GRANT ALL ON \`${MARIADB_DB_NAME//\`/\`\`}\`.* TO '${MARIADB_DB_USER//\'/\'\'}'@'localhost';"
# Optional extra grants
if [[ -n "${MARIADB_DB_EXTRA_GRANTS:-}" ]]; then
@@ -7204,6 +7431,18 @@ setup_mongodb() {
mkdir -p /var/lib/mongodb
chown -R mongodb:mongodb /var/lib/mongodb
+ local KERNEL_VERSION MONGO_MAJOR
+ KERNEL_VERSION=$(uname -r | cut -d- -f1)
+ MONGO_MAJOR="${MONGO_VERSION%%.*}"
+ if ((MONGO_MAJOR >= 8)) && [[ "$(printf '%s\n' "6.19" "$KERNEL_VERSION" | sort -V | head -n1)" == "6.19" ]]; then
+ mkdir -p /etc/systemd/system/mongod.service.d
+ cat </etc/systemd/system/mongod.service.d/rseq.conf
+[Service]
+Environment=GLIBC_TUNABLES=glibc.pthread.rseq=1
+EOF
+ systemctl daemon-reload
+ fi
+
$STD systemctl enable mongod || {
msg_warn "Failed to enable mongod service"
}
@@ -8378,8 +8617,14 @@ setup_postgresql_db() {
fi
msg_info "Setting up PostgreSQL Database"
- $STD sudo -u postgres psql -c "CREATE ROLE $PG_DB_USER WITH LOGIN PASSWORD '$PG_DB_PASS';"
- $STD sudo -u postgres psql -c "CREATE DATABASE $PG_DB_NAME WITH OWNER $PG_DB_USER ENCODING 'UTF8' TEMPLATE template0;"
+ # Escape single quotes in identifiers to prevent SQL injection
+ local _pg_user_escaped _pg_pass_escaped _pg_db_escaped
+ _pg_user_escaped="${PG_DB_USER//\'/\'\'}"
+ _pg_pass_escaped="${PG_DB_PASS//\'/\'\'}"
+ _pg_db_escaped="${PG_DB_NAME//\'/\'\'}"
+
+ $STD sudo -u postgres psql -c "CREATE ROLE $_pg_user_escaped WITH LOGIN PASSWORD '$_pg_pass_escaped';"
+ $STD sudo -u postgres psql -c "CREATE DATABASE $_pg_db_escaped WITH OWNER $_pg_user_escaped ENCODING 'UTF8' TEMPLATE template0;"
# Configure pg_cron database BEFORE creating the extension (must be set before pg_cron loads)
if [[ -n "${PG_DB_EXTENSIONS:-}" ]] && [[ ",${PG_DB_EXTENSIONS//[[:space:]]/}," == *",pg_cron,"* ]]; then
@@ -8444,6 +8689,7 @@ setup_postgresql_db() {
export PG_DB_USER
export PG_DB_PASS
}
+
# ------------------------------------------------------------------------------
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
#
@@ -8463,8 +8709,26 @@ setup_ruby() {
local RBENV_DIR="$HOME/.rbenv"
local RBENV_BIN="$RBENV_DIR/bin/rbenv"
local PROFILE_FILE="$HOME/.profile"
+ local BASH_PROFILE_FILE="$HOME/.bash_profile"
+ local BASHRC_FILE="$HOME/.bashrc"
local TMP_DIR=$(mktemp -d)
+ if ! grep -q 'rbenv init' "$PROFILE_FILE" 2>/dev/null; then
+ cat <<'EOF' >>"$PROFILE_FILE"
+export PATH="$HOME/.rbenv/bin:$PATH"
+eval "$(rbenv init -)"
+EOF
+ fi
+ if ! grep -q '.rbenv/shims' "$PROFILE_FILE" 2>/dev/null; then
+ echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$PROFILE_FILE"
+ fi
+ if [[ -f "$BASH_PROFILE_FILE" ]] && ! grep -q '.rbenv/shims' "$BASH_PROFILE_FILE"; then
+ echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$BASH_PROFILE_FILE"
+ fi
+ if [[ -f "$BASHRC_FILE" ]] && ! grep -q '.rbenv/shims' "$BASHRC_FILE"; then
+ echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$BASHRC_FILE"
+ fi
+
# Get currently installed Ruby version
local CURRENT_RUBY_VERSION=""
if [[ -x "$RBENV_BIN" ]]; then
@@ -9276,6 +9540,7 @@ check_for_gl_release() {
local source="$2"
local pinned_version_in="${3:-}" # optional
local pin_reason="${4:-}" # optional reason shown to user
+ local tag_prefix="${5:-}" # optional tag prefix filter (e.g. web-v2)
local app_lc="${app,,}"
local current_file="$HOME/.${app_lc}"
@@ -9352,9 +9617,18 @@ check_for_gl_release() {
rm -f "$gl_check_json"
fi
- mapfile -t raw_tags < <(jq -r '.[] | .tag_name' <<<"$releases_json")
+ if [[ -n "$tag_prefix" ]]; then
+ mapfile -t raw_tags < <(jq -r --arg p "$tag_prefix" \
+ '.[] | select(.tag_name | startswith($p)) | .tag_name' <<<"$releases_json")
+ else
+ mapfile -t raw_tags < <(jq -r '.[] | .tag_name' <<<"$releases_json")
+ fi
if ((${#raw_tags[@]} == 0)); then
- msg_error "No releases found for ${app} on GitLab"
+ if [[ -n "$tag_prefix" ]]; then
+ msg_error "No releases matching prefix '${tag_prefix}' found for ${app} on GitLab"
+ else
+ msg_error "No releases found for ${app} on GitLab"
+ fi
return 250
fi
@@ -9549,6 +9823,7 @@ fetch_and_deploy_gl_release() {
local version="${var_appversion:-${4:-latest}}"
local target="${5:-/opt/$app}"
local asset_pattern="${6:-}"
+ local tag_prefix="${7:-}"
if [[ -z "$app" ]]; then
app="${repo##*/}"
@@ -9580,6 +9855,8 @@ fetch_and_deploy_gl_release() {
local api_url
if [[ "$version" != "latest" ]]; then
api_url="$api_base/$version"
+ elif [[ -n "$tag_prefix" ]]; then
+ api_url="$api_base?per_page=100&order_by=released_at&sort=desc"
else
api_url="$api_base?per_page=1&order_by=released_at&sort=desc"
fi
@@ -9634,9 +9911,18 @@ fetch_and_deploy_gl_release() {
json=$(<"$gl_rel_json")
if [[ "$version" == "latest" ]]; then
- json=$(echo "$json" | jq '.[0] // empty')
+ if [[ -n "$tag_prefix" ]]; then
+ json=$(echo "$json" | jq --arg p "$tag_prefix" \
+ '[.[] | select(.tag_name | startswith($p))][0] // empty')
+ else
+ json=$(echo "$json" | jq '.[0] // empty')
+ fi
if [[ -z "$json" || "$json" == "null" ]]; then
- msg_error "No releases found for $repo on GitLab"
+ if [[ -n "$tag_prefix" ]]; then
+ msg_error "No release matching prefix '${tag_prefix}' found for $repo on GitLab"
+ else
+ msg_error "No releases found for $repo on GitLab"
+ fi
return 1
fi
fi
diff --git a/tools/pve/update-lxcs.sh b/tools/pve/update-lxcs.sh
index 52e9d2528..e083a4fc2 100644
--- a/tools/pve/update-lxcs.sh
+++ b/tools/pve/update-lxcs.sh
@@ -78,7 +78,7 @@ function update_container() {
alpine) pct exec "$container" -- ash -c "apk -U upgrade" ;;
archlinux) pct exec "$container" -- bash -c "pacman -Syyu --noconfirm" ;;
fedora | rocky | centos | alma) pct exec "$container" -- bash -c "dnf -y update && dnf -y upgrade" ;;
- ubuntu | debian | devuan) pct exec "$container" -- bash -c "apt-get update 2>/dev/null | grep 'packages.*upgraded'; apt list --upgradable 2>/dev/null | cat && apt-get -yq dist-upgrade 2>&1; rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED || true" ;;
+ ubuntu | debian | devuan) pct exec "$container" -- bash -c "apt-get update 2>/dev/null | grep 'packages.*upgraded'; apt list --upgradable 2>/dev/null | cat && apt-get -yq dist-upgrade 2>&1; apt-get -yq autoremove 2>&1; apt-get -yq autoclean 2>&1; rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED || true" ;;
opensuse) pct exec "$container" -- bash -c "zypper ref && zypper --non-interactive dup" ;;
esac
}
diff --git a/vm/haos-vm.sh b/vm/haos-vm.sh
index 85d582170..df5189614 100644
--- a/vm/haos-vm.sh
+++ b/vm/haos-vm.sh
@@ -475,7 +475,7 @@ function advanced_settings() {
done
while true; do
- if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
+ if VLAN1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set a Vlan (leave blank for default)" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then
if [ -z "$VLAN1" ]; then
VLAN1="Default"
VLAN=""