Compare commits

..

34 Commits

Author SHA1 Message Date
github-actions[bot]
9a7a32400f Update CHANGELOG.md 2026-04-14 19:22:55 +00:00
community-scripts-pr-app[bot]
18e09c26d9 Update CHANGELOG.md (#13750)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 19:22:37 +00:00
CanbiZ (MickLesk)
209f92bf0f fix(lyrion): correct service name and version file in update script (#13734) 2026-04-14 21:22:32 +02:00
CanbiZ (MickLesk)
a17ba89e7b Mealie: support v3.15+ Nuxt 4 migration (#13731) 2026-04-14 21:22:11 +02:00
community-scripts-pr-app[bot]
08ee4699df Update CHANGELOG.md (#13749)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 19:19:40 +00:00
Chris
a49d9c5713 Immich: Pin photo-processing library revisions (#13748) 2026-04-14 21:19:16 +02:00
community-scripts-pr-app[bot]
18fd0f2393 Update CHANGELOG.md (#13746)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 18:20:04 +00:00
Slaviša Arežina
9b35339cbe Changedetection: move env vars from service file to .env (#13732) 2026-04-14 20:19:37 +02:00
CanbiZ (MickLesk)
a9c2307b0e Enhance PocketBase bot commands & revalidation
Add frontend revalidation and richer command handling for the PocketBase GitHub bot. Key changes:

- Expose FRONTEND_URL and REVALIDATE_SECRET to workflow env and add a best-effort revalidate() helper to ping the frontend after edits.
- Introduce shared parsing/helpers: parseKVPairs, parseTokens, readJsonBlob, formatNotesList, formatMethodsList, and other utilities to centralize logic.
- Add an "info" subcommand to display script details, links, credentials, install methods and notes.
- Improve note handling (add/edit/remove) to use shared parsers and call revalidate after updates; tweak messages and reactions.
- Expand install method management: support add/remove/edit operations, new method fields (cpu, ram, hdd, os, version, config_path, script), validation of unknown fields, and better formatting. Persist install_methods_json as JSON (not stringified JSON) when PATCHing.
- Replace ad-hoc field parsers with the shared key=value parser for field updates and SET handling; call revalidate after SET/field patches.
- Update help text and minor message wording/formatting.
- In push-json-to-pocketbase workflow, remove writing config_path from pushed payload.

These changes aim to make the bot more robust, easier to extend, and ensure frontend caches are refreshed after data changes.
2026-04-14 15:37:50 +02:00
community-scripts-pr-app[bot]
0e809c6ee9 Update CHANGELOG.md (#13742)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 13:12:46 +00:00
Slaviša Arežina
64d000b73a Nginx fixes (#13741) 2026-04-14 15:12:18 +02:00
community-scripts-pr-app[bot]
65353d01e1 Update CHANGELOG.md (#13740)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 11:41:29 +00:00
Copilot
683be87e9e Zerobyte: add git to dependencies to fix bun install failure (#13721)
* Initial plan

* fix(zerobyte): add git to dependencies to fix bun install postinstall script

Agent-Logs-Url: https://github.com/community-scripts/ProxmoxVE/sessions/d5a3e428-515e-4b91-817e-0ff6d05ef24c

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

* fix(zerobyte): add ensure_dependencies git in update_script

Agent-Logs-Url: https://github.com/community-scripts/ProxmoxVE/sessions/b61bb87d-1c0e-4f75-a16a-c1f6f2143e49

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>
2026-04-14 13:41:05 +02:00
community-scripts-pr-app[bot]
5894734857 Update CHANGELOG.md (#13733)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 08:51:37 +00:00
Alexander Stein
c881811499 alpine-nextcloud-install: do not use deprecated nginx config (#13726)
Co-authored-by: Alexander Stein <alexander.stein@mailbox.org>
2026-04-14 10:51:07 +02:00
community-scripts-pr-app[bot]
33716c92e5 Update CHANGELOG.md (#13725)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 21:29:28 +00:00
Copilot
dda2ea811f fix(mealie): pin version to v3.14.0 in install and update scripts (#13724)
Agent-Logs-Url: https://github.com/community-scripts/ProxmoxVE/sessions/8cc1f756-6838-4392-9069-ba53921b4e38

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>
2026-04-13 23:29:01 +02:00
community-scripts-pr-app[bot]
392ff1f575 Update CHANGELOG.md (#13717)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 19:20:27 +00:00
Chris
af9bf93707 Immich: Pin version to 2.7.5 (#13715) 2026-04-13 21:20:02 +02:00
community-scripts-pr-app[bot]
cfe5e7baa7 Update CHANGELOG.md (#13716)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 19:02:54 +00:00
CanbiZ (MickLesk)
1d609976d3 Slskd: Remove stale Soularr lock file on startup and redirect logs to stderr (#13669)
* Slskd: Remove stale Soularr lock file on startup and redirect logs to stderr

* fix(slskd): inline LOCK_FILE variable
2026-04-13 21:02:29 +02:00
CanbiZ (MickLesk)
041de06a4d fix(opnsense-vm): fix grep pipefail crash with single bridge & ambiguous redirect
- Add || true to grep -v in default_settings and advanced_settings to
  prevent pipefail exit code 1 when only one bridge exists
- Change 1>&/dev/null to &>/dev/null for pvesm alloc and qm importdisk
  to fix ambiguous bash redirect syntax
2026-04-13 16:31:36 +02:00
community-scripts-pr-app[bot]
f0bfec1b59 Update CHANGELOG.md (#13713)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 14:15:58 +00:00
CanbiZ (MickLesk)
83ef4a5857 refactor(bytestash): auto backup/restore data on update (#13707)
Remove manual backup prompt. Automatically back up and restore
the data directory (/opt/bytestash/data or legacy /opt/data)
during clean installs to prevent data loss.
2026-04-13 16:15:28 +02:00
community-scripts-pr-app[bot]
0baafa3993 Update CHANGELOG.md (#13711)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 14:00:11 +00:00
CanbiZ (MickLesk)
e55fe43e2d core: remove unused TEMP_DIR mktemp leak in build_container / clean sonarqube (#13708)
* fix(core): remove unused TEMP_DIR mktemp leak in build_container

The build_container() function created a temp directory via mktemp -d and
pushd into it, but never popd or rm -rf. The directory was not used for
anything — FUNCTIONS_FILE_PATH is downloaded into a variable, not a file.

Remove the mktemp -d and pushd entirely to eliminate the leak.

* fix(sonarqube): clean up temp file after zip extraction

The SonarQube update function (ct/sonarqube.sh) never deleted the
downloaded zip file (~200-500 MB) from /tmp after extraction. On LXC
containers with 4-8 GB disks, this accumulates with every update and
can eventually fill the disk.

Also add explicit cleanup in the install script instead of relying
solely on cleanup_lxc() pattern matching.
2026-04-13 15:59:42 +02:00
community-scripts-pr-app[bot]
482f579dc0 Update CHANGELOG.md (#13710)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 13:56:13 +00:00
Copilot
2652ae6c95 Bambuddy: preserve database and archive on update (#13706)
* Initial plan

* fix(bambuddy): backup and restore database and archive directory on update

Agent-Logs-Url: https://github.com/community-scripts/ProxmoxVE/sessions/17f6ab31-077b-4215-bc88-eac3187aab0b

Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: MickLesk <47820557+MickLesk@users.noreply.github.com>
2026-04-13 15:55:46 +02:00
community-scripts-pr-app[bot]
e9f802dd29 Update CHANGELOG.md (#13699)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-13 06:16:11 +00:00
Chris
60957a8eee OpenCloud: pin version to 6.0.0 (#13691) 2026-04-13 08:15:41 +02:00
community-scripts-pr-app[bot]
0bf87f6fcc Update CHANGELOG.md (#13696)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-12 19:33:38 +00:00
CanbiZ (MickLesk)
3dd3040010 IronClaw: Install keychain dependencies and launch in a DBus session (#13692) 2026-04-12 21:33:17 +02:00
community-scripts-pr-app[bot]
6a86f52c0e Update CHANGELOG.md (#13695)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-12 19:33:13 +00:00
CanbiZ (MickLesk)
346dfd8bf7 Alpine-Wakapi: Remove container checks in update_script function (#13694) 2026-04-12 21:32:51 +02:00
26 changed files with 474 additions and 248 deletions

474
.github/workflows/pocketbase-bot.yml generated vendored
View File

@@ -31,6 +31,8 @@ jobs:
ACTOR: ${{ github.event.comment.user.login }}
ACTOR_ASSOCIATION: ${{ github.event.comment.author_association }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FRONTEND_URL: ${{ secrets.FRONTEND_URL }}
REVALIDATE_SECRET: ${{ secrets.REVALIDATE_SECRET }}
run: |
node << 'ENDSCRIPT'
(async function () {
@@ -113,7 +115,6 @@ jobs:
}
// ── Permission check ───────────────────────────────────────────────
// author_association: OWNER = repo/org owner, MEMBER = org member (includes Contributors team)
const association = process.env.ACTOR_ASSOCIATION;
if (association !== 'OWNER' && association !== 'MEMBER') {
await addReaction('-1');
@@ -128,18 +129,11 @@ jobs:
await addReaction('eyes');
// ── Parse command ──────────────────────────────────────────────────
// Formats (first line of comment):
// /pocketbase <slug> field=value [field=value ...] ← field updates (simple values)
// /pocketbase <slug> set <field> ← value from code block below
// /pocketbase <slug> note list|add|edit|remove ... ← note management
// /pocketbase <slug> method list ← list install methods
// /pocketbase <slug> method <type> cpu=N ram=N hdd=N ← edit install method resources
const commentBody = process.env.COMMENT_BODY || '';
const lines = commentBody.trim().split('\n');
const firstLine = lines[0].trim();
const withoutCmd = firstLine.replace(/^\/pocketbase\s+/, '').trim();
// Extract code block content from comment body (```...``` or ```lang\n...```)
function extractCodeBlock(body) {
const m = body.match(/```[^\n]*\n([\s\S]*?)```/);
return m ? m[1].trim() : null;
@@ -147,6 +141,8 @@ jobs:
const codeBlockValue = extractCodeBlock(commentBody);
const HELP_TEXT =
'**Show current state:**\n' +
'```\n/pocketbase <slug> info\n```\n\n' +
'**Field update (simple):** `/pocketbase <slug> field=value [field=value ...]`\n\n' +
'**Field update (HTML/multiline) — value from code block:**\n' +
'````\n' +
@@ -162,12 +158,16 @@ jobs:
'/pocketbase <slug> note edit <type> "<old text>" "<new text>"\n' +
'/pocketbase <slug> note remove <type> "<text>"\n' +
'```\n\n' +
'**Install method resources:**\n' +
'**Install method management:**\n' +
'```\n' +
'/pocketbase <slug> method list\n' +
'/pocketbase <slug> method <type> hdd=10\n' +
'/pocketbase <slug> method <type> cpu=4 ram=2048 hdd=20\n' +
'```\n\n' +
'/pocketbase <slug> method <type> config_path="/opt/app/.env"\n' +
'/pocketbase <slug> method <type> os=debian version=13\n' +
'/pocketbase <slug> method add <type> cpu=2 ram=2048 hdd=8 os=debian version=13\n' +
'/pocketbase <slug> method remove <type>\n' +
'```\n' +
'Method fields: `cpu` `ram` `hdd` `os` `version` `config_path` `script`\n\n' +
'**Editable fields:** `name` `description` `logo` `documentation` `website` `project_url` `github` ' +
'`config_path` `port` `default_user` `default_passwd` ' +
'`updateable` `privileged` `has_arm` `is_dev` ' +
@@ -189,8 +189,7 @@ jobs:
process.exit(0);
}
// ── Allowed fields and their types ─────────────────────────────────
// ── PocketBase: authenticate (shared by all paths) ─────────────────
// ── PocketBase: authenticate ───────────────────────────────────────
const raw = process.env.POCKETBASE_URL.replace(/\/$/, '');
const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api';
const coll = process.env.POCKETBASE_COLLECTION;
@@ -210,7 +209,7 @@ jobs:
}
const token = JSON.parse(authRes.body).token;
// ── PocketBase: find record by slug (shared by all paths) ──────────
// ── PocketBase: find record by slug ────────────────────────────────
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
const filter = "(slug='" + slug.replace(/'/g, "''") + "')";
const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {
@@ -228,57 +227,164 @@ jobs:
process.exit(0);
}
// ── Shared helpers ─────────────────────────────────────────────────
// Key=value parser: handles unquoted and "quoted" values
function parseKVPairs(str) {
const fields = {};
let pos = 0;
while (pos < str.length) {
while (pos < str.length && /\s/.test(str[pos])) pos++;
if (pos >= str.length) break;
let keyStart = pos;
while (pos < str.length && str[pos] !== '=' && !/\s/.test(str[pos])) pos++;
const key = str.substring(keyStart, pos).trim();
if (!key || pos >= str.length || str[pos] !== '=') { pos++; continue; }
pos++;
let value;
if (pos < str.length && str[pos] === '"') {
pos++;
let valStart = pos;
while (pos < str.length && str[pos] !== '"') {
if (str[pos] === '\\') pos++;
pos++;
}
value = str.substring(valStart, pos).replace(/\\"/g, '"');
if (pos < str.length) pos++;
} else {
let valStart = pos;
while (pos < str.length && !/\s/.test(str[pos])) pos++;
value = str.substring(valStart, pos);
}
fields[key] = value;
}
return fields;
}
// Token parser for note commands: unquoted-word OR "quoted string"
function parseTokens(str) {
const tokens = [];
let pos = 0;
while (pos < str.length) {
while (pos < str.length && /\s/.test(str[pos])) pos++;
if (pos >= str.length) break;
if (str[pos] === '"') {
pos++;
let start = pos;
while (pos < str.length && str[pos] !== '"') {
if (str[pos] === '\\') pos++;
pos++;
}
tokens.push(str.substring(start, pos).replace(/\\"/g, '"'));
if (pos < str.length) pos++;
} else {
let start = pos;
while (pos < str.length && !/\s/.test(str[pos])) pos++;
tokens.push(str.substring(start, pos));
}
}
return tokens;
}
// Read JSON blob from record (handles parsed objects and strings)
function readJsonBlob(val) {
if (Array.isArray(val)) return val;
try { return JSON.parse(val || '[]'); } catch (e) { return []; }
}
// Frontend cache revalidation (silent, best-effort)
async function revalidate(s) {
const frontendUrl = process.env.FRONTEND_URL;
const secret = process.env.REVALIDATE_SECRET;
if (!frontendUrl || !secret) return;
try {
await request(frontendUrl.replace(/\/$/, '') + '/api/revalidate', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + secret, 'Content-Type': 'application/json' },
body: JSON.stringify({ tags: ['scripts', 'script-' + s] })
});
} catch (e) { console.warn('Revalidation skipped:', e.message); }
}
// Format notes list for display
function formatNotesList(arr) {
if (arr.length === 0) return '*None*';
return arr.map(function (n, i) {
return (i + 1) + '. **`' + (n.type || '?') + '`**: ' + (n.text || '');
}).join('\n');
}
// Format install methods list for display
function formatMethodsList(arr) {
if (arr.length === 0) return '*None*';
return arr.map(function (im, i) {
const r = im.resources || {};
const parts = [
(r.os || '?') + ' ' + (r.version || '?'),
(r.cpu != null ? r.cpu : '?') + 'C / ' + (r.ram != null ? r.ram : '?') + ' MB / ' + (r.hdd != null ? r.hdd : '?') + ' GB'
];
if (im.config_path) parts.push('config: `' + im.config_path + '`');
if (im.script) parts.push('script: `' + im.script + '`');
return (i + 1) + '. **`' + (im.type || '?') + '`** — ' + parts.join(', ');
}).join('\n');
}
// ── Route: dispatch to subcommand handler ──────────────────────────
const infoMatch = rest.match(/^info$/i);
const noteMatch = rest.match(/^note\s+(list|add|edit|remove)\b/i);
const methodMatch = rest.match(/^method\b/i);
const setMatch = rest.match(/^set\s+(\S+)/i);
if (noteMatch) {
// ── NOTE SUBCOMMAND (reads/writes notes_json on script record) ────
if (infoMatch) {
// ── INFO SUBCOMMAND ──────────────────────────────────────────────
const notesArr = readJsonBlob(record.notes_json);
const methodsArr = readJsonBlob(record.install_methods_json);
const out = [];
out.push(' **PocketBase Bot**: Info for **`' + slug + '`**\n');
out.push('**Basic info:**');
out.push('- **Name:** ' + (record.name || '—'));
out.push('- **Slug:** `' + slug + '`');
out.push('- **Port:** ' + (record.port != null ? '`' + record.port + '`' : '—'));
out.push('- **Updateable:** ' + (record.updateable ? 'Yes' : 'No'));
out.push('- **Privileged:** ' + (record.privileged ? 'Yes' : 'No'));
out.push('- **ARM:** ' + (record.has_arm ? 'Yes' : 'No'));
if (record.is_dev) out.push('- **Dev:** Yes');
if (record.is_disabled) out.push('- **Disabled:** Yes' + (record.disable_message ? ' — ' + record.disable_message : ''));
if (record.is_deleted) out.push('- **Deleted:** Yes' + (record.deleted_message ? ' — ' + record.deleted_message : ''));
out.push('');
out.push('**Links:**');
out.push('- **Website:** ' + (record.website || '—'));
out.push('- **Docs:** ' + (record.documentation || '—'));
out.push('- **Logo:** ' + (record.logo ? '[link](' + record.logo + ')' : '—'));
out.push('- **GitHub:** ' + (record.github || '—'));
if (record.config_path) out.push('- **Config:** `' + record.config_path + '`');
out.push('');
out.push('**Credentials:**');
out.push('- **User:** ' + (record.default_user || '—'));
out.push('- **Password:** ' + (record.default_passwd ? '*(set)*' : '—'));
out.push('');
out.push('**Install methods** (' + methodsArr.length + '):');
out.push(formatMethodsList(methodsArr));
out.push('');
out.push('**Notes** (' + notesArr.length + '):');
out.push(formatNotesList(notesArr));
await addReaction('+1');
await postComment(out.join('\n'));
} else if (noteMatch) {
// ── NOTE SUBCOMMAND ──────────────────────────────────────────────
const noteAction = noteMatch[1].toLowerCase();
const noteArgsStr = rest.substring(noteMatch[0].length).trim();
let notesArr = readJsonBlob(record.notes_json);
// Parse notes_json from the already-fetched script record
// PocketBase may return JSON fields as already-parsed objects
let notesArr = [];
try {
const rawNotes = record.notes_json;
notesArr = Array.isArray(rawNotes) ? rawNotes : JSON.parse(rawNotes || '[]');
} catch (e) { notesArr = []; }
// Token parser: unquoted-word OR "quoted string" (supports \" escapes)
function parseNoteTokens(str) {
const tokens = [];
let pos = 0;
while (pos < str.length) {
while (pos < str.length && /\s/.test(str[pos])) pos++;
if (pos >= str.length) break;
if (str[pos] === '"') {
pos++;
let start = pos;
while (pos < str.length && str[pos] !== '"') {
if (str[pos] === '\\') pos++;
pos++;
}
tokens.push(str.substring(start, pos).replace(/\\"/g, '"'));
if (pos < str.length) pos++;
} else {
let start = pos;
while (pos < str.length && !/\s/.test(str[pos])) pos++;
tokens.push(str.substring(start, pos));
}
}
return tokens;
}
function formatNotesList(arr) {
if (arr.length === 0) return '*None*';
return arr.map(function (n, i) {
return (i + 1) + '. **`' + (n.type || '?') + '`**: ' + (n.text || '');
}).join('\n');
}
async function patchNotesJson(arr) {
async function patchNotes(arr) {
const res = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
@@ -286,7 +392,7 @@ jobs:
});
if (!res.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to update `notes_json`:\n```\n' + res.body + '\n```');
await postComment('❌ **PocketBase Bot**: Failed to update notes:\n```\n' + res.body + '\n```');
process.exit(1);
}
}
@@ -299,7 +405,7 @@ jobs:
);
} else if (noteAction === 'add') {
const tokens = parseNoteTokens(noteArgsStr);
const tokens = parseTokens(noteArgsStr);
if (tokens.length < 2) {
await addReaction('-1');
await postComment(
@@ -311,7 +417,8 @@ jobs:
const noteType = tokens[0].toLowerCase();
const noteText = tokens.slice(1).join(' ');
notesArr.push({ type: noteType, text: noteText });
await patchNotesJson(notesArr);
await patchNotes(notesArr);
await revalidate(slug);
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Added note to **`' + slug + '`**\n\n' +
@@ -321,7 +428,7 @@ jobs:
);
} else if (noteAction === 'edit') {
const tokens = parseNoteTokens(noteArgsStr);
const tokens = parseTokens(noteArgsStr);
if (tokens.length < 3) {
await addReaction('-1');
await postComment(
@@ -346,7 +453,8 @@ jobs:
process.exit(0);
}
notesArr[idx].text = newText;
await patchNotesJson(notesArr);
await patchNotes(notesArr);
await revalidate(slug);
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Edited note in **`' + slug + '`**\n\n' +
@@ -357,7 +465,7 @@ jobs:
);
} else if (noteAction === 'remove') {
const tokens = parseNoteTokens(noteArgsStr);
const tokens = parseTokens(noteArgsStr);
if (tokens.length < 2) {
await addReaction('-1');
await postComment(
@@ -381,7 +489,8 @@ jobs:
);
process.exit(0);
}
await patchNotesJson(notesArr);
await patchNotes(notesArr);
await revalidate(slug);
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Removed note from **`' + slug + '`**\n\n' +
@@ -392,36 +501,36 @@ jobs:
}
} else if (methodMatch) {
// ── METHOD SUBCOMMAND (reads/writes install_methods_json on script record) ──
// ── METHOD SUBCOMMAND ────────────────────────────────────────────
const methodArgs = rest.replace(/^method\s*/i, '').trim();
const methodListMode = !methodArgs || methodArgs.toLowerCase() === 'list';
let methodsArr = readJsonBlob(record.install_methods_json);
// Parse install_methods_json from the already-fetched script record
// PocketBase may return JSON fields as already-parsed objects
let methodsArr = [];
try {
const rawMethods = record.install_methods_json;
methodsArr = Array.isArray(rawMethods) ? rawMethods : JSON.parse(rawMethods || '[]');
} catch (e) { methodsArr = []; }
// Method field classification
const RESOURCE_KEYS = { cpu: 'number', ram: 'number', hdd: 'number', os: 'string', version: 'string' };
const METHOD_KEYS = { config_path: 'string', script: 'string' };
const ALL_METHOD_KEYS = Object.assign({}, RESOURCE_KEYS, METHOD_KEYS);
function formatMethodsList(arr) {
if (arr.length === 0) return '*None*';
return arr.map(function (im, i) {
const r = im.resources || {};
return (i + 1) + '. **`' + (im.type || '?') + '`** — CPU: `' + (r.cpu != null ? r.cpu : '?') +
'` · RAM: `' + (r.ram != null ? r.ram : '?') + ' MB` · HDD: `' + (r.hdd != null ? r.hdd : '?') + ' GB`';
}).join('\n');
function applyMethodChanges(method, parsed) {
if (!method.resources) method.resources = {};
for (const [k, v] of Object.entries(parsed)) {
if (RESOURCE_KEYS[k]) {
method.resources[k] = RESOURCE_KEYS[k] === 'number' ? parseInt(v, 10) : v;
} else if (METHOD_KEYS[k]) {
method[k] = v === '' ? null : v;
}
}
}
async function patchInstallMethodsJson(arr) {
async function patchMethods(arr) {
const res = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ install_methods_json: JSON.stringify(arr) })
body: JSON.stringify({ install_methods_json: arr })
});
if (!res.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to update `install_methods_json`:\n```\n' + res.body + '\n```');
await postComment('❌ **PocketBase Bot**: Failed to update install methods:\n```\n' + res.body + '\n```');
process.exit(1);
}
}
@@ -432,70 +541,122 @@ jobs:
' **PocketBase Bot**: Install methods for **`' + slug + '`** (' + methodsArr.length + ' total)\n\n' +
formatMethodsList(methodsArr)
);
} else {
// Parse: <type> cpu=N ram=N hdd=N
const methodParts = methodArgs.match(/^(\S+)\s+(.+)$/);
if (!methodParts) {
await addReaction('-1');
// Check for add / remove sub-actions
const addMatch = methodArgs.match(/^add\s+(\S+)(?:\s+(.+))?$/i);
const removeMatch = methodArgs.match(/^remove\s+(\S+)$/i);
if (addMatch) {
// ── METHOD ADD ───────────────────────────────────────────────
const newType = addMatch[1];
if (methodsArr.some(function (im) { return (im.type || '').toLowerCase() === newType.toLowerCase(); })) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Install method `' + newType + '` already exists for `' + slug + '`.\n\nUse `/pocketbase ' + slug + ' method list` to see all methods.');
process.exit(0);
}
const newMethod = { type: newType, resources: { cpu: 1, ram: 512, hdd: 4, os: 'debian', version: '13' } };
if (addMatch[2]) {
const parsed = parseKVPairs(addMatch[2]);
const unknown = Object.keys(parsed).filter(function (k) { return !ALL_METHOD_KEYS[k]; });
if (unknown.length > 0) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Unknown method field(s): `' + unknown.join('`, `') + '`\n\n**Allowed:** `' + Object.keys(ALL_METHOD_KEYS).join('`, `') + '`');
process.exit(0);
}
applyMethodChanges(newMethod, parsed);
}
methodsArr.push(newMethod);
await patchMethods(methodsArr);
await revalidate(slug);
await addReaction('+1');
await postComment(
' **PocketBase Bot**: Invalid `method` syntax.\n\n' +
'**Usage:**\n```\n/pocketbase ' + slug + ' method list\n/pocketbase ' + slug + ' method <type> hdd=10\n/pocketbase ' + slug + ' method <type> cpu=4 ram=2048 hdd=20\n```'
' **PocketBase Bot**: Added install method **`' + newType + '`** to **`' + slug + '`**\n\n' +
formatMethodsList([newMethod]) + '\n\n' +
'*Executed by @' + actor + '*'
);
process.exit(0);
}
const targetType = methodParts[1].toLowerCase();
const resourcesStr = methodParts[2];
// Parse resource fields (only cpu/ram/hdd allowed)
const RESOURCE_FIELDS = { cpu: true, ram: true, hdd: true };
const resourceChanges = {};
const rePairs = /([a-z]+)=(\d+)/gi;
let m;
while ((m = rePairs.exec(resourcesStr)) !== null) {
const key = m[1].toLowerCase();
if (RESOURCE_FIELDS[key]) resourceChanges[key] = parseInt(m[2], 10);
}
if (Object.keys(resourceChanges).length === 0) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: No valid resource fields found. Use `cpu=N`, `ram=N`, `hdd=N`.');
process.exit(0);
}
// Find matching method by type name (case-insensitive)
const idx = methodsArr.findIndex(function (im) {
return (im.type || '').toLowerCase() === targetType;
});
if (idx === -1) {
await addReaction('-1');
const availableTypes = methodsArr.map(function (im) { return im.type || '?'; });
} else if (removeMatch) {
// ── METHOD REMOVE ────────────────────────────────────────────
const removeType = removeMatch[1].toLowerCase();
const removed = methodsArr.filter(function (im) { return (im.type || '').toLowerCase() === removeType; });
if (removed.length === 0) {
await addReaction('-1');
const available = methodsArr.map(function (im) { return im.type || '?'; });
await postComment('❌ **PocketBase Bot**: No install method `' + removeType + '` found.\n\n**Available:** `' + (available.length ? available.join('`, `') : '(none)') + '`');
process.exit(0);
}
methodsArr = methodsArr.filter(function (im) { return (im.type || '').toLowerCase() !== removeType; });
await patchMethods(methodsArr);
await revalidate(slug);
await addReaction('+1');
await postComment(
' **PocketBase Bot**: No install method with type `' + targetType + '` found for `' + slug + '`.\n\n' +
'**Available types:** `' + (availableTypes.length ? availableTypes.join('`, `') : '(none)') + '`\n\n' +
'Use `/pocketbase ' + slug + ' method list` to see all methods.'
' **PocketBase Bot**: Removed install method **`' + removed[0].type + '`** from **`' + slug + '`**\n\n' +
'*Executed by @' + actor + '*'
);
} else {
// ── METHOD EDIT ──────────────────────────────────────────────
const editParts = methodArgs.match(/^(\S+)\s+(.+)$/);
if (!editParts) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: Invalid `method` syntax.\n\n' +
'**Usage:**\n```\n/pocketbase ' + slug + ' method list\n' +
'/pocketbase ' + slug + ' method <type> cpu=4 ram=2048 hdd=20\n' +
'/pocketbase ' + slug + ' method <type> config_path="/opt/app/.env"\n' +
'/pocketbase ' + slug + ' method add <type> cpu=2 ram=2048 hdd=8\n' +
'/pocketbase ' + slug + ' method remove <type>\n```'
);
process.exit(0);
}
const targetType = editParts[1].toLowerCase();
const parsed = parseKVPairs(editParts[2]);
const unknown = Object.keys(parsed).filter(function (k) { return !ALL_METHOD_KEYS[k]; });
if (unknown.length > 0) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Unknown method field(s): `' + unknown.join('`, `') + '`\n\n**Allowed:** `' + Object.keys(ALL_METHOD_KEYS).join('`, `') + '`');
process.exit(0);
}
if (Object.keys(parsed).length === 0) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: No valid `key=value` pairs found.\n\n**Allowed:** `' + Object.keys(ALL_METHOD_KEYS).join('`, `') + '`');
process.exit(0);
}
const idx = methodsArr.findIndex(function (im) { return (im.type || '').toLowerCase() === targetType; });
if (idx === -1) {
await addReaction('-1');
const available = methodsArr.map(function (im) { return im.type || '?'; });
await postComment(
'❌ **PocketBase Bot**: No install method `' + targetType + '` found for `' + slug + '`.\n\n' +
'**Available:** `' + (available.length ? available.join('`, `') : '(none)') + '`\n\n' +
'Use `/pocketbase ' + slug + ' method list` to see all methods.'
);
process.exit(0);
}
applyMethodChanges(methodsArr[idx], parsed);
await patchMethods(methodsArr);
await revalidate(slug);
const changesLines = Object.entries(parsed)
.map(function ([k, v]) {
const unit = k === 'ram' ? ' MB' : k === 'hdd' ? ' GB' : '';
return '- `' + k + '` → `' + v + unit + '`';
}).join('\n');
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Updated install method **`' + methodsArr[idx].type + '`** for **`' + slug + '`**\n\n' +
'**Changes applied:**\n' + changesLines + '\n\n' +
'*Executed by @' + actor + '*'
);
process.exit(0);
}
if (!methodsArr[idx].resources) methodsArr[idx].resources = {};
if (resourceChanges.cpu != null) methodsArr[idx].resources.cpu = resourceChanges.cpu;
if (resourceChanges.ram != null) methodsArr[idx].resources.ram = resourceChanges.ram;
if (resourceChanges.hdd != null) methodsArr[idx].resources.hdd = resourceChanges.hdd;
await patchInstallMethodsJson(methodsArr);
const changesLines = Object.entries(resourceChanges)
.map(function ([k, v]) { return '- `' + k + '` → `' + v + (k === 'ram' ? ' MB' : k === 'hdd' ? ' GB' : '') + '`'; })
.join('\n');
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Updated install method **`' + methodsArr[idx].type + '`** for **`' + slug + '`**\n\n' +
'**Changes applied:**\n' + changesLines + '\n\n' +
'*Executed by @' + actor + '*'
);
}
} else if (setMatch) {
// ── SET SUBCOMMAND (multi-line / HTML / special chars via code block) ──
// ── SET SUBCOMMAND (value from code block) ───────────────────────
const fieldName = setMatch[1].toLowerCase();
const SET_ALLOWED = {
name: 'string', description: 'string', logo: 'string',
@@ -531,6 +692,7 @@ jobs:
await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\n```\n' + setPatchRes.body + '\n```');
process.exit(1);
}
await revalidate(slug);
const preview = codeBlockValue.length > 300 ? codeBlockValue.substring(0, 300) + '…' : codeBlockValue;
await addReaction('+1');
await postComment(
@@ -541,11 +703,6 @@ jobs:
} else {
// ── FIELD=VALUE PATH ─────────────────────────────────────────────
const fieldsStr = rest;
// Skipped: slug, script_created/updated, created (auto), categories/
// install_methods/notes/type (relations), github_data/install_methods_json/
// notes_json (auto-generated), execute_in (select relation), last_update_commit (auto)
const ALLOWED_FIELDS = {
name: 'string',
description: 'string',
@@ -568,39 +725,7 @@ jobs:
deleted_message: 'string',
};
// Field=value parser (handles quoted values and empty=null)
function parseFields(str) {
const fields = {};
let pos = 0;
while (pos < str.length) {
while (pos < str.length && /\s/.test(str[pos])) pos++;
if (pos >= str.length) break;
let keyStart = pos;
while (pos < str.length && str[pos] !== '=' && !/\s/.test(str[pos])) pos++;
const key = str.substring(keyStart, pos).trim();
if (!key || pos >= str.length || str[pos] !== '=') { pos++; continue; }
pos++;
let value;
if (str[pos] === '"') {
pos++;
let valStart = pos;
while (pos < str.length && str[pos] !== '"') {
if (str[pos] === '\\') pos++;
pos++;
}
value = str.substring(valStart, pos).replace(/\\"/g, '"');
if (pos < str.length) pos++;
} else {
let valStart = pos;
while (pos < str.length && !/\s/.test(str[pos])) pos++;
value = str.substring(valStart, pos);
}
fields[key] = value;
}
return fields;
}
const parsedFields = parseFields(fieldsStr);
const parsedFields = parseKVPairs(rest);
const unknownFields = Object.keys(parsedFields).filter(function (f) { return !ALLOWED_FIELDS[f]; });
if (unknownFields.length > 0) {
@@ -655,6 +780,7 @@ jobs:
await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\n```\n' + patchRes.body + '\n```');
process.exit(1);
}
await revalidate(slug);
await addReaction('+1');
const changesLines = Object.entries(payload)
.map(function ([k, v]) { return '- `' + k + '` → `' + JSON.stringify(v) + '`'; })

View File

@@ -170,7 +170,6 @@ jobs:
website: data.website,
logo: data.logo,
description: data.description,
config_path: data.config_path,
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user || null,
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd || null,
is_dev: false

View File

@@ -442,12 +442,61 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-04-14
### 🚀 Updated Scripts
- Immich: Pin photo-processing library revisions [@vhsdream](https://github.com/vhsdream) ([#13748](https://github.com/community-scripts/ProxmoxVE/pull/13748))
- #### 🐞 Bug Fixes
- BentoPDF: Nginx fixes [@tremor021](https://github.com/tremor021) ([#13741](https://github.com/community-scripts/ProxmoxVE/pull/13741))
- Zerobyte: add git to dependencies to fix bun install failure [@Copilot](https://github.com/Copilot) ([#13721](https://github.com/community-scripts/ProxmoxVE/pull/13721))
- alpine-nextcloud-install: do not use deprecated nginx config [@AlexanderStein](https://github.com/AlexanderStein) ([#13726](https://github.com/community-scripts/ProxmoxVE/pull/13726))
- #### ✨ New Features
- Mealie: support v3.15+ Nuxt 4 migration [@MickLesk](https://github.com/MickLesk) ([#13731](https://github.com/community-scripts/ProxmoxVE/pull/13731))
- #### 🔧 Refactor
- Lyrion: correct service name and version file in update script [@MickLesk](https://github.com/MickLesk) ([#13734](https://github.com/community-scripts/ProxmoxVE/pull/13734))
- Changedetection: move env vars from service file to .env [@tremor021](https://github.com/tremor021) ([#13732](https://github.com/community-scripts/ProxmoxVE/pull/13732))
## 2026-04-13
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Slskd: Remove stale Soularr lock file on startup and redirect logs to stderr [@MickLesk](https://github.com/MickLesk) ([#13669](https://github.com/community-scripts/ProxmoxVE/pull/13669))
- Bambuddy: preserve database and archive on update [@Copilot](https://github.com/Copilot) ([#13706](https://github.com/community-scripts/ProxmoxVE/pull/13706))
- #### ✨ New Features
- Immich: Pin version to 2.7.5 [@vhsdream](https://github.com/vhsdream) ([#13715](https://github.com/community-scripts/ProxmoxVE/pull/13715))
- Bytestash: auto backup/restore data on update [@MickLesk](https://github.com/MickLesk) ([#13707](https://github.com/community-scripts/ProxmoxVE/pull/13707))
- OpenCloud: pin version to 6.0.0 [@vhsdream](https://github.com/vhsdream) ([#13691](https://github.com/community-scripts/ProxmoxVE/pull/13691))
- #### 💥 Breaking Changes
- Mealie: pin version to v3.14.0 in install and update scripts [@Copilot](https://github.com/Copilot) ([#13724](https://github.com/community-scripts/ProxmoxVE/pull/13724))
- #### 🔧 Refactor
- core: remove unused TEMP_DIR mktemp leak in build_container / clean sonarqube [@MickLesk](https://github.com/MickLesk) ([#13708](https://github.com/community-scripts/ProxmoxVE/pull/13708))
## 2026-04-12
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Alpine-Wakapi: Remove container checks in update_script function [@MickLesk](https://github.com/MickLesk) ([#13694](https://github.com/community-scripts/ProxmoxVE/pull/13694))
- #### 🔧 Refactor
- IronClaw: Install keychain dependencies and launch in a DBus session [@MickLesk](https://github.com/MickLesk) ([#13692](https://github.com/community-scripts/ProxmoxVE/pull/13692))
- MeTube: Allow pnpm build scripts to fix ERR_PNPM_IGNORED_BUILDS [@MickLesk](https://github.com/MickLesk) ([#13668](https://github.com/community-scripts/ProxmoxVE/pull/13668))
## 2026-04-11

View File

@@ -22,7 +22,6 @@ catch_errors
function update_script() {
header_info
check_container_resources
if [[ ! -d /opt/wakapi ]]; then
msg_error "No ${APP} Installation Found!"
exit

View File

@@ -39,6 +39,9 @@ function update_script() {
msg_info "Backing up Configuration and Data"
cp /opt/bambuddy/.env /opt/bambuddy.env.bak
cp -r /opt/bambuddy/data /opt/bambuddy_data_bak
[[ -f /opt/bambuddy/bambuddy.db ]] && cp /opt/bambuddy/bambuddy.db /opt/bambuddy.db.bak
[[ -f /opt/bambuddy/bambutrack.db ]] && cp /opt/bambuddy/bambutrack.db /opt/bambutrack.db.bak
[[ -d /opt/bambuddy/archive ]] && cp -r /opt/bambuddy/archive /opt/bambuddy_archive_bak
msg_ok "Backed up Configuration and Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "bambuddy" "maziggy/bambuddy" "tarball" "latest" "/opt/bambuddy"
@@ -59,8 +62,14 @@ function update_script() {
mkdir -p /opt/bambuddy/data
cp /opt/bambuddy.env.bak /opt/bambuddy/.env
cp -r /opt/bambuddy_data_bak/. /opt/bambuddy/data/
rm -f /opt/bambuddy.env.bak
rm -rf /opt/bambuddy_data_bak
[[ -f /opt/bambuddy.db.bak ]] && cp /opt/bambuddy.db.bak /opt/bambuddy/bambuddy.db
[[ -f /opt/bambutrack.db.bak ]] && cp /opt/bambutrack.db.bak /opt/bambuddy/bambutrack.db
if [[ -d /opt/bambuddy_archive_bak ]]; then
mkdir -p /opt/bambuddy/archive
cp -r /opt/bambuddy_archive_bak/. /opt/bambuddy/archive/
fi
rm -f /opt/bambuddy.env.bak /opt/bambuddy.db.bak /opt/bambutrack.db.bak
rm -rf /opt/bambuddy_data_bak /opt/bambuddy_archive_bak
msg_ok "Restored Configuration and Data"
msg_info "Starting Service"

View File

@@ -29,28 +29,41 @@ function update_script() {
exit
fi
if check_for_gh_release "bytestash" "jordan-dalby/ByteStash"; then
read -rp "${TAB3}Did you make a backup via application WebUI? (y/n): " backuped
if [[ "$backuped" =~ ^[Yy]$ ]]; then
msg_info "Stopping Services"
systemctl stop bytestash-backend bytestash-frontend
msg_ok "Services Stopped"
msg_info "Stopping Services"
systemctl stop bytestash-backend bytestash-frontend
msg_ok "Services Stopped"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "bytestash" "jordan-dalby/ByteStash" "tarball"
msg_info "Configuring ByteStash"
cd /opt/bytestash/server
$STD npm install
cd /opt/bytestash/client
$STD npm install
msg_ok "Updated ByteStash"
msg_info "Starting Services"
systemctl start bytestash-backend bytestash-frontend
msg_ok "Started Services"
else
msg_error "PLEASE MAKE A BACKUP FIRST!"
exit
msg_info "Backing up data"
tmp_dir="/opt/bytestash-data-backup"
mkdir -p "$tmp_dir"
if [[ -d /opt/bytestash/data ]]; then
cp -r /opt/bytestash/data "$tmp_dir"/data
elif [[ -d /opt/data ]]; then
cp -r /opt/data "$tmp_dir"/data
fi
msg_ok "Data backed up"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "bytestash" "jordan-dalby/ByteStash" "tarball"
msg_info "Restoring data"
if [[ -d "$tmp_dir"/data ]]; then
mkdir -p /opt/bytestash/data
cp -r "$tmp_dir"/data/* /opt/bytestash/data/
rm -rf "$tmp_dir"
fi
msg_ok "Data restored"
msg_info "Configuring ByteStash"
cd /opt/bytestash/server
$STD npm install
cd /opt/bytestash/client
$STD npm install
msg_ok "Updated ByteStash"
msg_info "Starting Services"
systemctl start bytestash-backend bytestash-frontend
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit

View File

@@ -109,7 +109,7 @@ EOF
msg_ok "Image-processing libraries up to date"
fi
RELEASE="v2.7.4"
RELEASE="v2.7.5"
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"
@@ -309,7 +309,8 @@ function compile_libjxl() {
SOURCE=${SOURCE_DIR}/libjxl
JPEGLI_LIBJPEG_LIBRARY_SOVERSION="62"
JPEGLI_LIBJPEG_LIBRARY_VERSION="62.3.0"
: "${LIBJXL_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libjxl.json)}"
LIBJXL_REVISION="794a5dcf0d54f9f0b20d288a12e87afb91d20dfc"
# : "${LIBJXL_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libjxl.json)}"
if [[ "$LIBJXL_REVISION" != "$(grep 'libjxl' ~/.immich_library_revisions | awk '{print $2}')" ]]; then
msg_info "Recompiling libjxl"
[[ -d "$SOURCE" ]] && rm -rf "$SOURCE"
@@ -353,7 +354,8 @@ function compile_libjxl() {
function compile_libheif() {
SOURCE=${SOURCE_DIR}/libheif
ensure_dependencies libaom-dev
: "${LIBHEIF_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libheif.json)}"
LIBHEIF_REVISION="35dad50a9145332a7bfdf1ff6aef6801fb613d68"
# : "${LIBHEIF_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libheif.json)}"
if [[ "${update:-}" ]] || [[ "$LIBHEIF_REVISION" != "$(grep 'libheif' ~/.immich_library_revisions | awk '{print $2}')" ]]; then
msg_info "Recompiling libheif"
[[ -d "$SOURCE" ]] && rm -rf "$SOURCE"
@@ -384,7 +386,8 @@ function compile_libheif() {
function compile_libraw() {
SOURCE=${SOURCE_DIR}/libraw
: "${LIBRAW_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libraw.json)}"
LIBRAW_REVISION="0b56545a4f828743f28a4345cdfdd4c49f9f9a2a"
# : "${LIBRAW_REVISION:=$(jq -cr '.revision' "$BASE_DIR"/server/sources/libraw.json)}"
if [[ "$LIBRAW_REVISION" != "$(grep 'libraw' ~/.immich_library_revisions | awk '{print $2}')" ]]; then
msg_info "Recompiling libraw"
[[ -d "$SOURCE" ]] && rm -rf "$SOURCE"

View File

@@ -30,16 +30,16 @@ function update_script() {
exit
fi
DEB_URL=$(curl -s 'https://lyrion.org/getting-started/' | grep -oP '<a\s[^>]*href="\K[^"]*amd64\.deb(?="[^>]*>)' | head -n 1)
DEB_URL=$(curl_with_retry 'https://lyrion.org/getting-started/' | grep -oP '<a\s[^>]*href="\K[^"]*amd64\.deb(?="[^>]*>)' | head -n 1)
RELEASE=$(echo "$DEB_URL" | grep -oP 'lyrionmusicserver_\K[0-9.]+(?=_amd64\.deb)')
DEB_FILE="/tmp/lyrionmusicserver_${RELEASE}_amd64.deb"
if [[ ! -f /opt/lyrion_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/lyrion_version.txt)" ]]; then
msg_info "Updating $APP to ${RELEASE}"
curl -fsSL -o "$DEB_FILE" "$DEB_URL"
curl_with_retry "$DEB_URL" "$DEB_FILE"
$STD apt install "$DEB_FILE" -y
systemctl restart lyrion
$STD rm -f "$DEB_FILE"
echo "${RELEASE}" >/opt/${APP}_version.txt
systemctl restart lyrionmusicserver
rm -f "$DEB_FILE"
echo "${RELEASE}" >/opt/lyrion_version.txt
msg_ok "Updated $APP to ${RELEASE}"
msg_ok "Updated successfully!"
else

View File

@@ -40,7 +40,7 @@ function update_script() {
cp -f /opt/mealie/mealie.env /opt/mealie.env
msg_ok "Backup completed"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball" "latest" "/opt/mealie"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball"
msg_info "Installing Python Dependencies with uv"
cd /opt/mealie
@@ -49,9 +49,10 @@ function update_script() {
msg_info "Building Frontend"
MEALIE_VERSION=$(<$HOME/.mealie)
$STD sed -i "s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g" /opt/mealie/frontend/pages/admin/site-settings.vue
$STD sed -i "s|value: data.buildId,|value: \"v${MEALIE_VERSION}\",|g" /opt/mealie/frontend/pages/admin/site-settings.vue
$STD sed -i "s|value: data.production ? i18n.t(\"about.production\") : i18n.t(\"about.development\"),|value: \"bare-metal\",|g" /opt/mealie/frontend/pages/admin/site-settings.vue
SITE_SETTINGS=$(find /opt/mealie/frontend -name "site-settings.vue" -path "*/admin/*" | head -1)
$STD sed -i "s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g" "$SITE_SETTINGS"
$STD sed -i "s|value: data.buildId,|value: \"v${MEALIE_VERSION}\",|g" "$SITE_SETTINGS"
$STD sed -i "s|value: data.production ? i18n.t(\"about.production\") : i18n.t(\"about.development\"),|value: \"bare-metal\",|g" "$SITE_SETTINGS"
export NUXT_TELEMETRY_DISABLED=1
cd /opt/mealie/frontend
$STD yarn install --prefer-offline --frozen-lockfile --non-interactive --production=false --network-timeout 1000000
@@ -97,4 +98,3 @@ msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9000${CL}"

View File

@@ -29,7 +29,7 @@ function update_script() {
exit
fi
RELEASE="v5.2.0"
RELEASE="v6.0.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

View File

@@ -43,6 +43,7 @@ function update_script() {
RELEASE=$(get_latest_github_release "SonarSource/sonarqube")
curl -fsSL "https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${RELEASE}.zip" -o $temp_file
unzip -q "$temp_file" -d /opt
rm -f "$temp_file"
mv /opt/sonarqube-${RELEASE} /opt/sonarqube
echo "${RELEASE}" > ~/.sonarqube
msg_ok "Updated SonarQube"

View File

@@ -38,6 +38,7 @@ function update_script() {
cp /opt/zerobyte/.env /opt/zerobyte.env.bak
msg_ok "Backed up Configuration"
ensure_dependencies git
NODE_VERSION="24" setup_nodejs
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "zerobyte" "nicotsx/zerobyte" "tarball"

View File

@@ -14,7 +14,7 @@ network_check
update_os
msg_info "Installing Dependencies"
$STD apk add openssl
$STD apk add openssl dbus gnome-keyring
msg_ok "Installed Dependencies"
msg_info "Installing PostgreSQL"
@@ -57,7 +57,8 @@ cat <<EOF >/etc/init.d/ironclaw
name="IronClaw"
description="IronClaw AI Agent"
command="/usr/local/bin/ironclaw"
command="/usr/bin/dbus-run-session"
command_args="/usr/local/bin/ironclaw"
command_background=true
pidfile="/run/ironclaw.pid"
directory="/root"

View File

@@ -102,8 +102,9 @@ server {
fastcgi_read_timeout 120s;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name localhost;
root /usr/share/webapps/nextcloud;
index index.php index.html index.htm;

View File

@@ -38,9 +38,9 @@ msg_ok "Setup BentoPDF"
msg_info "Creating Service"
CERT_CN="$(hostname -I | awk '{print $1}')"
$STD openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \
-keyout /etc/ssl/private/bentopdf-selfsigned.key \
-out /etc/ssl/certs/bentopdf-selfsigned.crt \
-subj "/CN=${CERT_CN}"
-keyout /etc/ssl/private/bentopdf-selfsigned.key \
-out /etc/ssl/certs/bentopdf-selfsigned.crt \
-subj "/CN=${CERT_CN}"
cat <<'EOF' >/etc/nginx/sites-available/bentopdf
server {
@@ -102,6 +102,10 @@ server {
EOF
rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/bentopdf
systemctl stop nginx
systemctl disable -q nginx
sed -i '/application\/rss+xml/a\ application\/javascript mjs;' /etc/nginx/mime.types
cat <<'EOF' >/etc/systemd/system/bentopdf.service
[Unit]
Description=BentoPDF Service
@@ -116,7 +120,6 @@ Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now bentopdf
msg_ok "Created & started service"

View File

@@ -56,6 +56,10 @@ NODE_VERSION="24" setup_nodejs
msg_info "Installing Change Detection"
mkdir /opt/changedetection
$STD pip3 install changedetection.io
cat <<EOF >/opt/changedetection/.env
WEBDRIVER_URL=http://127.0.0.1:4444/wd/hub
PLAYWRIGHT_DRIVER_URL=ws://localhost:3000/chrome?launch=eyJkZWZhdWx0Vmlld3BvcnQiOnsiaGVpZ2h0Ijo3MjAsIndpZHRoIjoxMjgwfSwiaGVhZGxlc3MiOmZhbHNlLCJzdGVhbHRoIjp0cnVlfQ==&blockAds=true
EOF
msg_ok "Installed Change Detection"
msg_info "Installing Browserless & Playwright"
@@ -112,12 +116,13 @@ Description=Change Detection
After=network-online.target
After=network.target browserless.service
Wants=browserless.service
[Service]
Type=simple
EnvironmentFile=/opt/changedetection/.env
WorkingDirectory=/opt/changedetection
Environment=WEBDRIVER_URL=http://127.0.0.1:4444/wd/hub
Environment=PLAYWRIGHT_DRIVER_URL=ws://localhost:3000/chrome?launch=eyJkZWZhdWx0Vmlld3BvcnQiOnsiaGVpZ2h0Ijo3MjAsIndpZHRoIjoxMjgwfSwiaGVhZGxlc3MiOmZhbHNlLCJzdGVhbHRoIjp0cnVlfQ==&blockAds=true
ExecStart=changedetection.io -d /opt/changedetection -p 5000
[Install]
WantedBy=multi-user.target
EOF
@@ -126,15 +131,16 @@ cat <<EOF >/etc/systemd/system/browserless.service
[Unit]
Description=browserless service
After=network.target
[Service]
Environment=CONNECTION_TIMEOUT=60000
WorkingDirectory=/opt/browserless
ExecStart=/opt/browserless/scripts/start.sh
SyslogIdentifier=browserless
[Install]
WantedBy=default.target
EOF
systemctl enable -q --now browserless
systemctl enable -q --now changedetection
msg_ok "Created Services"

View File

@@ -175,7 +175,8 @@ cd "$STAGING_DIR"
SOURCE=${SOURCE_DIR}/libjxl
JPEGLI_LIBJPEG_LIBRARY_SOVERSION="62"
JPEGLI_LIBJPEG_LIBRARY_VERSION="62.3.0"
: "${LIBJXL_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libjxl.json)}"
LIBJXL_REVISION="794a5dcf0d54f9f0b20d288a12e87afb91d20dfc"
# : "${LIBJXL_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libjxl.json)}"
$STD git clone https://github.com/libjxl/libjxl.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBJXL_REVISION"
@@ -212,7 +213,8 @@ msg_ok "(1/5) Compiled libjxl"
msg_info "(2/5) Compiling libheif"
SOURCE=${SOURCE_DIR}/libheif
: "${LIBHEIF_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libheif.json)}"
LIBHEIF_REVISION="35dad50a9145332a7bfdf1ff6aef6801fb613d68"
# : "${LIBHEIF_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libheif.json)}"
$STD git clone https://github.com/strukturag/libheif.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBHEIF_REVISION"
@@ -237,7 +239,8 @@ msg_ok "(2/5) Compiled libheif"
msg_info "(3/5) Compiling libraw"
SOURCE=${SOURCE_DIR}/libraw
: "${LIBRAW_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libraw.json)}"
LIBRAW_REVISION="0b56545a4f828743f28a4345cdfdd4c49f9f9a2a"
# : "${LIBRAW_REVISION:=$(jq -cr '.revision' $BASE_DIR/server/sources/libraw.json)}"
$STD git clone https://github.com/LibRaw/LibRaw.git "$SOURCE"
cd "$SOURCE"
$STD git reset --hard "$LIBRAW_REVISION"
@@ -295,7 +298,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" "v2.7.4" "$SRC_DIR"
fetch_and_deploy_gh_release "Immich" "immich-app/immich" "tarball" "v2.7.5" "$SRC_DIR"
PNPM_VERSION="$(jq -r '.packageManager | split("@")[1] | split("+")[0]' ${SRC_DIR}/package.json)"
NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs

View File

@@ -13,6 +13,13 @@ setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y \
dbus-user-session \
gnome-keyring \
libsecret-tools
msg_ok "Installed Dependencies"
PG_VERSION="17" PG_MODULES="pgvector" setup_postgresql
PG_DB_NAME="ironclaw" PG_DB_USER="ironclaw" PG_DB_EXTENSIONS="vector" setup_postgresql_db
@@ -46,7 +53,8 @@ After=network.target postgresql.service
Type=simple
User=root
WorkingDirectory=/root
ExecStart=/usr/local/bin/ironclaw
EnvironmentFile=/root/.ironclaw/.env
ExecStart=/usr/bin/dbus-run-session /usr/local/bin/ironclaw
Restart=on-failure
RestartSec=5

View File

@@ -14,10 +14,10 @@ network_check
update_os
msg_info "Setup Lyrion Music Server"
DEB_URL=$(curl -fsSL 'https://lyrion.org/getting-started/' | grep -oP '<a\s[^>]*href="\K[^"]*amd64\.deb(?="[^>]*>)' | head -n 1)
DEB_URL=$(curl_with_retry 'https://lyrion.org/getting-started/' | grep -oP '<a\s[^>]*href="\K[^"]*amd64\.deb(?="[^>]*>)' | head -n 1)
RELEASE=$(echo "$DEB_URL" | grep -oP 'lyrionmusicserver_\K[0-9.]+(?=_amd64\.deb)')
DEB_FILE="/tmp/lyrionmusicserver_${RELEASE}_amd64.deb"
curl -fsSL -o "$DEB_FILE" "$DEB_URL"
curl_with_retry "$DEB_URL" "$DEB_FILE"
$STD apt install "$DEB_FILE" -y
rm -f "$DEB_FILE"
echo "${RELEASE}" >"/opt/lyrion_version.txt"

View File

@@ -30,7 +30,7 @@ msg_ok "Installed Dependencies"
PYTHON_VERSION="3.12" setup_uv
PG_VERSION="16" setup_postgresql
NODE_MODULE="yarn" NODE_VERSION="24" setup_nodejs
fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball" "latest" "/opt/mealie"
fetch_and_deploy_gh_release "mealie" "mealie-recipes/mealie" "tarball"
PG_DB_NAME="mealie_db" PG_DB_USER="mealie_user" PG_DB_GRANT_SUPERUSER="true" setup_postgresql_db
msg_info "Installing Python Dependencies with uv"
@@ -42,9 +42,10 @@ msg_info "Building Frontend"
MEALIE_VERSION=$(<$HOME/.mealie)
export NUXT_TELEMETRY_DISABLED=1
cd /opt/mealie/frontend
$STD sed -i "s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g" /opt/mealie/frontend/pages/admin/site-settings.vue
$STD sed -i "s|value: data.buildId,|value: \"v${MEALIE_VERSION}\",|g" /opt/mealie/frontend/pages/admin/site-settings.vue
$STD sed -i "s|value: data.production ? i18n.t(\"about.production\") : i18n.t(\"about.development\"),|value: \"bare-metal\",|g" /opt/mealie/frontend/pages/admin/site-settings.vue
SITE_SETTINGS=$(find /opt/mealie/frontend -name "site-settings.vue" -path "*/admin/*" | head -1)
$STD sed -i "s|https://github.com/mealie-recipes/mealie/commit/|https://github.com/mealie-recipes/mealie/releases/tag/|g" "$SITE_SETTINGS"
$STD sed -i "s|value: data.buildId,|value: \"v${MEALIE_VERSION}\",|g" "$SITE_SETTINGS"
$STD sed -i "s|value: data.production ? i18n.t(\"about.production\") : i18n.t(\"about.development\"),|value: \"bare-metal\",|g" "$SITE_SETTINGS"
$STD yarn install --prefer-offline --frozen-lockfile --non-interactive --production=false --network-timeout 1000000
$STD yarn generate
msg_ok "Built Frontend"

View File

@@ -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" "v5.2.0" "/usr/bin" "opencloud-*-linux-amd64"
fetch_and_deploy_gh_release "OpenCloud" "opencloud-eu/opencloud" "singlefile" "v6.0.0" "/usr/bin" "opencloud-*-linux-amd64"
mv /usr/bin/OpenCloud /usr/bin/opencloud
msg_info "Configuring OpenCloud"

View File

@@ -48,12 +48,15 @@ if [[ ${soularr,,} =~ ^(y|yes)$ ]]; then
#!/usr/bin/env bash
if ps aux | grep "[s]oularr.py" >/dev/null; then
echo "Soularr is already running. Exiting..."
echo "Soularr is already running. Exiting..." >&2
exit 1
else
source /opt/soularr/venv/bin/activate
uv run python3 -u /opt/soularr/soularr.py --config-dir /opt/soularr
fi
# Remove stale lock file from previous ungraceful exit
rm -f "/opt/soularr/.soularr.lock"
source /opt/soularr/venv/bin/activate
uv run python3 -u /opt/soularr/soularr.py --config-dir /opt/soularr 2>&1
EOF
chmod +x /opt/soularr/run.sh
deactivate

View File

@@ -21,6 +21,7 @@ temp_file=$(mktemp)
RELEASE=$(get_latest_github_release "SonarSource/sonarqube")
curl -fsSL "https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-${RELEASE}.zip" -o $temp_file
unzip -q "$temp_file" -d /opt
rm -f "$temp_file"
mv /opt/sonarqube-* /opt/sonarqube
$STD useradd -r -m -U -d /opt/sonarqube -s /bin/bash sonarqube
chown -R sonarqube:sonarqube /opt/sonarqube

View File

@@ -18,6 +18,7 @@ echo "davfs2 davfs2/suid_file boolean false" | debconf-set-selections
$STD apt-get install -y \
bzip2 \
fuse3 \
git \
sshfs \
davfs2 \
openssh-client

View File

@@ -3214,8 +3214,8 @@ check_container_resources() {
# - Warns if usage >80% and asks user confirmation before proceeding
# ------------------------------------------------------------------------------
check_container_storage() {
total_size=$(df -P /boot | awk 'NR==2 {print $2}')
local used_size=$(df -P /boot | awk 'NR==2 {print $3}')
total_size=$(df /boot --output=size | tail -n 1)
local used_size=$(df /boot --output=used | tail -n 1)
usage=$((100 * used_size / total_size))
if ((usage > 80)); then
msg_warn "Storage is dangerously low (${usage}% used on /boot)"
@@ -3613,8 +3613,6 @@ build_container() {
fi
# Build PCT_OPTIONS as string for export
TEMP_DIR=$(mktemp -d)
pushd "$TEMP_DIR" >/dev/null
local _func_url
if [ "$var_os" == "alpine" ]; then
_func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func"

View File

@@ -317,7 +317,7 @@ function default_settings() {
# Determine available network modes based on bridge count
local DEFAULT_WAN_BRG
DEFAULT_WAN_BRG=$(echo "$AVAILABLE_BRIDGES" | grep -v "^${BRG}$" | head -n1)
DEFAULT_WAN_BRG=$(echo "$AVAILABLE_BRIDGES" | grep -v "^${BRG}$" | head -n1 || true)
if [ "$BRIDGE_COUNT" -ge 2 ]; then
# Multiple bridges available - offer dual or single mode
@@ -509,7 +509,7 @@ function advanced_settings() {
# Build WAN bridge selection from available bridges (excluding LAN bridge)
local WAN_BRIDGES
WAN_BRIDGES=$(get_available_bridges | grep -v "^${BRG}$")
WAN_BRIDGES=$(get_available_bridges | grep -v "^${BRG}$" || true)
if [ -z "$WAN_BRIDGES" ]; then
msg_error "No additional bridge available for WAN. Only '${BRG}' exists."
msg_error "Create a second bridge (e.g. vmbr1) in Proxmox network config first."
@@ -738,8 +738,8 @@ done
msg_info "Creating a OPNsense VM"
qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \
-name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
pvesm alloc $STORAGE $VMID $DISK0 4M 1>&/dev/null
qm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} 1>&/dev/null
pvesm alloc $STORAGE $VMID $DISK0 4M &>/dev/null
qm importdisk $VMID ${FILE} $STORAGE ${DISK_IMPORT:-} &>/dev/null
qm set $VMID \
-efidisk0 ${DISK0_REF}${FORMAT} \
-scsi0 ${DISK1_REF},${DISK_CACHE}${THIN}size=2G \