Compare commits

..

2 Commits

Author SHA1 Message Date
MickLesk
dc49400904 fix(fileflows): use fileflows.com header for node version check
Replace GitHub API call (repo does not exist) with a HEAD request
to the fileflows.com download URL. The Content-Disposition header
contains the versioned filename, from which the release number is
parsed and compared against the local version file.

If the header is not available the node update proceeds unconditionally,
since the user explicitly invoked the update command.
2026-05-28 07:40:37 +02:00
MickLesk
db5cbf74c7 fix(fileflows): handle node-only update separately from server
The update_script always called the server API at localhost:19200
which does not exist on node-only containers, causing a connection
error (exit code 7) or 401 on auth-hardened servers.

Fix: detect node vs server via systemctl is-enabled, then:
- Node: use GitHub releases API to compare versions and update
- Server: keep existing local API behaviour

Fixes #14622
2026-05-28 07:38:19 +02:00
54 changed files with 315 additions and 1464 deletions

129
.github/changelogs/2026/05.md generated vendored
View File

@@ -1,132 +1,3 @@
## 2026-05-30
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Flatnotes: fix empty package name in pyproject.toml [@MickLesk](https://github.com/MickLesk) ([#14814](https://github.com/community-scripts/ProxmoxVE/pull/14814))
## 2026-05-29
### 🆕 New Scripts
- Kan ([#14776](https://github.com/community-scripts/ProxmoxVE/pull/14776))
- Dynacat ([#14777](https://github.com/community-scripts/ProxmoxVE/pull/14777))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Fix lobehub docker path [@dannyyy](https://github.com/dannyyy) ([#14793](https://github.com/community-scripts/ProxmoxVE/pull/14793))
- karakeep: add more hdd space [@MickLesk](https://github.com/MickLesk) ([#14797](https://github.com/community-scripts/ProxmoxVE/pull/14797))
- Grist: Revert installation of EE [@tremor021](https://github.com/tremor021) ([#14784](https://github.com/community-scripts/ProxmoxVE/pull/14784))
- #### 🔧 Refactor
- Sure: Remove `$STD` for `systemctl enable -q` [@tremor021](https://github.com/tremor021) ([#14801](https://github.com/community-scripts/ProxmoxVE/pull/14801))
## 2026-05-28
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- RomM: remove nginx default.conf during installation [@MickLesk](https://github.com/MickLesk) ([#14766](https://github.com/community-scripts/ProxmoxVE/pull/14766))
- Open-Archiver: replace pnpm approve-builds --yes with --all [@MickLesk](https://github.com/MickLesk) ([#14765](https://github.com/community-scripts/ProxmoxVE/pull/14765))
- fix(hermesagent): set npm_config_yes=true to suppress interactive pro… [@steveonjava](https://github.com/steveonjava) ([#14763](https://github.com/community-scripts/ProxmoxVE/pull/14763))
- #### 🔧 Refactor
- Yamtrack: migrate to uv [@MickLesk](https://github.com/MickLesk) ([#14767](https://github.com/community-scripts/ProxmoxVE/pull/14767))
### ❔ Uncategorized
- chore(ct): sync adventurelog defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#14772](https://github.com/community-scripts/ProxmoxVE/pull/14772))
## 2026-05-27
### 🆕 New Scripts
- MusicSeerr ([#14746](https://github.com/community-scripts/ProxmoxVE/pull/14746))
- Hermes Agent ([#14751](https://github.com/community-scripts/ProxmoxVE/pull/14751))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- grist: restore install:ee step [@paulfitz](https://github.com/paulfitz) ([#14759](https://github.com/community-scripts/ProxmoxVE/pull/14759))
### 💾 Core
- #### 🐞 Bug Fixes
- [tools.func]: `setup_gs()` fix getting dotted release format [@tremor021](https://github.com/tremor021) ([#14745](https://github.com/community-scripts/ProxmoxVE/pull/14745))
## 2026-05-26
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Add directory creation to Profilarr update script [@ryansully](https://github.com/ryansully) ([#14740](https://github.com/community-scripts/ProxmoxVE/pull/14740))
- profilarr: Fix ARCH assignment in profilarr.sh to support Profilarr build usage [@mpeleshenko](https://github.com/mpeleshenko) ([#14709](https://github.com/community-scripts/ProxmoxVE/pull/14709))
- Jackett: Remove quotes in Service File [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14729](https://github.com/community-scripts/ProxmoxVE/pull/14729))
- Open-archiver: approve pnpm build scripts and run build:oss without subshell [@MickLesk](https://github.com/MickLesk) ([#14711](https://github.com/community-scripts/ProxmoxVE/pull/14711))
- Docuseal: read Ruby version from Gemfile, upgrade on update if needed [@MickLesk](https://github.com/MickLesk) ([#14715](https://github.com/community-scripts/ProxmoxVE/pull/14715))
- #### ✨ New Features
- Birdnet-GO: install libonnxruntime.so from release tarball [@MickLesk](https://github.com/MickLesk) ([#14716](https://github.com/community-scripts/ProxmoxVE/pull/14716))
### 💾 Core
- #### ✨ New Features
- tools.func: better error diagnostics, consistent OS detection, setup function ordering [@MickLesk](https://github.com/MickLesk) ([#14692](https://github.com/community-scripts/ProxmoxVE/pull/14692))
### 🧰 Tools
- #### 🐞 Bug Fixes
- IPTag-Tool: use qm set for VM tags to handle snapshot sections crrectly [@MickLesk](https://github.com/MickLesk) ([#14713](https://github.com/community-scripts/ProxmoxVE/pull/14713))
- #### ✨ New Features
- Netdata: extend PVE version support to 9.x [@MickLesk](https://github.com/MickLesk) ([#14714](https://github.com/community-scripts/ProxmoxVE/pull/14714))
## 2026-05-25
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- karakeep: fix: pip config [@CrazyWolf13](https://github.com/CrazyWolf13) ([#14703](https://github.com/community-scripts/ProxmoxVE/pull/14703))
### 💾 Core
- #### ✨ New Features
- tools.func: replace raw GitHub API curl calls with get_latest_github_release [@MickLesk](https://github.com/MickLesk) ([#14690](https://github.com/community-scripts/ProxmoxVE/pull/14690))
### 🧰 Tools
- #### 🔧 Refactor
- Kernel-Clean: detect meta-packages and fix silent removal failures [@MickLesk](https://github.com/MickLesk) ([#14674](https://github.com/community-scripts/ProxmoxVE/pull/14674))
## 2026-05-24
### 🚀 Updated Scripts
- #### ✨ New Features
- RomM: add installation steps for Nginx mod_zip module [@MickLesk](https://github.com/MickLesk) ([#14678](https://github.com/community-scripts/ProxmoxVE/pull/14678))
- ISponsorblockTV: detect CPU capabilities to select compatible binary [@MickLesk](https://github.com/MickLesk) ([#14677](https://github.com/community-scripts/ProxmoxVE/pull/14677))
- #### 🔧 Refactor
- Refactor: MQTT [@tremor021](https://github.com/tremor021) ([#14673](https://github.com/community-scripts/ProxmoxVE/pull/14673))
## 2026-05-23
### 🚀 Updated Scripts

View File

@@ -336,18 +336,14 @@ jobs:
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
drift_count=$((drift_count + 1))
elif [[ -n "$upstream_major" && "$our_version" != "$upstream_major" ]]; then
if (( our_version < upstream_major )); then
# Check if engines.node is a minimum constraint that our version satisfies
if [[ -z "$DF_NODE_MAJOR" && "$ENGINES_IS_MINIMUM" == "true" ]] && \
version_satisfies_engines "$our_version" "$ENGINES_MIN_MAJOR" "$ENGINES_IS_MINIMUM"; then
status="✅ (engines: $ENGINES_NODE_RAW — ours: $our_version satisfies)"
else
status="🔸 Drift → upstream=$upstream_major ($upstream_hint)"
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
drift_count=$((drift_count + 1))
fi
# Check if engines.node is a minimum constraint that our version satisfies
if [[ -z "$DF_NODE_MAJOR" && "$ENGINES_IS_MINIMUM" == "true" ]] && \
version_satisfies_engines "$our_version" "$ENGINES_MIN_MAJOR" "$ENGINES_IS_MINIMUM"; then
status="✅ (engines: $ENGINES_NODE_RAW — ours: $our_version satisfies)"
else
status="✅ Ahead of upstream ($upstream_major via $upstream_hint)"
status="🔸 Drift → upstream=$upstream_major ($upstream_hint)"
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
drift_count=$((drift_count + 1))
fi
fi

View File

@@ -3,7 +3,7 @@ name: Close Unauthorized New Script PRs
on:
pull_request_target:
branches: ["main"]
types: [opened, labeled, reopened, synchronize]
types: [opened, labeled]
jobs:
check-new-script:
@@ -24,6 +24,13 @@ jobs:
const owner = context.repo.owner;
const repo = context.repo.repo;
// --- Only act on PRs with the "new script" label ---
const labels = pr.labels.map(l => l.name);
if (!labels.includes("new script")) {
core.info(`PR #${prNumber} does not have "new script" label — skipping.`);
return;
}
// --- Allow our bots ---
const allowedBots = [
"push-app-to-main[bot]",
@@ -35,40 +42,38 @@ jobs:
return;
}
// --- Exempt contributors via author_association ---
// OWNER/MEMBER/COLLABORATOR are trusted; CONTRIBUTOR ("has merged before")
// and NONE are not — their new-script PRs are still closed.
const association = pr.author_association;
const exempt = ["OWNER", "MEMBER", "COLLABORATOR"];
if (exempt.includes(association)) {
core.info(`PR #${prNumber} by ${association} "${author}" — skipping.`);
return;
}
// --- Check if author is a member of the contributor team ---
const teamSlug = "contributor";
let isMember = false;
// --- Detect a new-script PR: "new script" label OR a newly-added
// script file under ct/ install/ turnkey/ vm/ (mirrors
// autolabeler-config.json). Removes the label-timing dependency. ---
const labels = pr.labels.map(l => l.name);
const hasNewScriptLabel = labels.includes("new script");
const scriptPrefixes = ["ct/", "install/", "turnkey/", "vm/"];
let hasAddedScriptFile = false;
try {
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
const { status } = await github.rest.teams.getMembershipForUserInOrg({
org: owner,
team_slug: teamSlug,
username: author,
});
hasAddedScriptFile = files.some(
f => f.status === "added" && scriptPrefixes.some(p => f.filename.startsWith(p))
);
// status 200 means the user is a member (active or pending)
isMember = true;
} catch (error) {
core.warning(`Could not list files for PR #${prNumber}: ${error.message}`);
if (error.status === 404) {
isMember = false;
} else {
core.warning(`Could not check team membership for ${author}: ${error.message}`);
// Fallback: check org membership
try {
await github.rest.orgs.checkMembershipForUser({
org: owner,
username: author,
});
isMember = true;
} catch {
isMember = false;
}
}
}
if (!hasNewScriptLabel && !hasAddedScriptFile) {
core.info(`PR #${prNumber} is not a new-script submission (no label, no added script file) — skipping.`);
if (isMember) {
core.info(`PR #${prNumber} by contributor "${author}" — skipping.`);
return;
}

View File

@@ -56,57 +56,46 @@ jobs:
echo "$slugs" > pocketbase_slugs.txt
echo "count=$(echo $slugs | wc -w)" >> "$GITHUB_OUTPUT"
- name: Find matching issues in ProxmoxVED by slug
- name: Search for Issues with Similar Titles
id: find_issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ ! -s pocketbase_slugs.txt ]]; then
echo "No slugs derived from PR — nothing to match."
echo "issue_numbers=" >> "$GITHUB_OUTPUT"
exit 0
fi
slugs=$(cat pocketbase_slugs.txt)
issues=$(gh issue list --repo community-scripts/ProxmoxVED --json number,title --jq '.[] | {number, title}')
# Normalize: lowercase, strip spaces and hyphens (same shape as the slug derivation)
norm() { echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]]//g; s/-//g'; }
best_match_score=0
best_match_number=0
issues=$(gh issue list --repo community-scripts/ProxmoxVED --state open --limit 1000 --json number,title,body)
for issue in $(echo "$issues" | jq -r '. | @base64'); do
_jq() {
echo ${issue} | base64 --decode | jq -r ${1}
}
matched=""
for slug in $slugs; do
nslug=$(norm "$slug")
[[ -z "$nslug" ]] && continue
while IFS= read -r row; do
num=$(echo "$row" | jq -r '.number')
ntitle=$(norm "$(echo "$row" | jq -r '.title')")
body=$(echo "$row" | jq -r '.body // ""' | tr '[:upper:]' '[:lower:]')
# Match when the issue title contains the slug, or the body mentions it verbatim
if [[ "$ntitle" == *"$nslug"* ]] || [[ "$body" == *"$slug"* ]]; then
matched="$matched $num"
fi
done < <(echo "$issues" | jq -c '.[]')
issue_title=$(_jq '.title' | tr '[:upper:]' '[:lower:]' | sed 's/ //g' | sed 's/-//g')
issue_number=$(_jq '.number')
match_score=$(echo "$title" | grep -o "$issue_title" | wc -l)
if [ "$match_score" -gt "$best_match_score" ]; then
best_match_score=$match_score
best_match_number=$issue_number
fi
done
matched=$(echo $matched | xargs -n1 2>/dev/null | sort -un | tr '\n' ' ')
if [[ -z "$matched" ]]; then
echo "No matching ProxmoxVED issues found for slugs: $slugs"
echo "issue_numbers=" >> "$GITHUB_OUTPUT"
if [ "$best_match_number" != "0" ]; then
echo "issue_number=$best_match_number" >> $GITHUB_ENV
else
echo "No matching issue found."
exit 0
fi
echo "Matched ProxmoxVED issues: $matched"
echo "issue_numbers=$matched" >> "$GITHUB_OUTPUT"
- name: Comment on and close matching ProxmoxVED issues
if: steps.find_issue.outputs.issue_numbers != ''
- name: Comment on the Best-Matching Issue and Close It
if: env.issue_number != ''
env:
GH_TOKEN: ${{ secrets.PAT_MICHEL }}
run: |
for issue_number in ${{ steps.find_issue.outputs.issue_numbers }}; do
echo "Closing ProxmoxVED issue #$issue_number"
gh issue comment "$issue_number" --repo community-scripts/ProxmoxVED --body "Merged with #${{ github.event.pull_request.number }} in ProxmoxVE"
gh issue close "$issue_number" --repo community-scripts/ProxmoxVED
done
gh issue comment $issue_number --repo community-scripts/ProxmoxVED --body "Merged with #${{ github.event.pull_request.number }} in ProxmoxVE"
gh issue close $issue_number --repo community-scripts/ProxmoxVED
- name: Set is_dev to false in PocketBase
if: steps.get_slugs.outputs.count != '0'

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

@@ -17,7 +17,7 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const daysBeforeLock = 7;
const daysBeforeLock = 3;
const lockDate = new Date();
lockDate.setDate(lockDate.getDate() - daysBeforeLock);
@@ -28,7 +28,7 @@ jobs:
/dependabot/i
];
// Search for closed, unlocked issues older than 7 days (paginated, oldest first)
// Search for closed, unlocked issues older than 3 days (paginated, oldest first)
let page = 1;
let totalLocked = 0;

View File

@@ -1,610 +0,0 @@
name: PocketBase AI Bot
# Natural-language companion to pocketbase-bot.yml.
# Mention the bot in plain English, e.g.:
# @pocketbase-bot change RAM to 4096 on zigbee2mqtt
# @pocketbase-bot disable script Nextcloud because upstream is broken
# The bot parses the request with GitHub Models, replies with the exact change(s)
# it understood, and only applies them after you reply "@pocketbase-bot confirm".
# The slash-command bot (/pocketbase ...) is unaffected; triggers do not overlap.
on:
issue_comment:
types: [created]
permissions:
models: read # lets the built-in GITHUB_TOKEN call GitHub Models inference
contents: write # built-in token opens the CT-defaults sync PR (like the slash bot)
pull-requests: write
jobs:
ai-bot:
runs-on: self-hosted
# Broad gate; the script does precise keyword + self-author checks.
if: contains(github.event.comment.body, '@pocketbase-bot')
steps:
- name: Mint GitHub App token (bot identity)
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PB_BOT_APP_ID }}
private-key: ${{ secrets.PB_BOT_APP_PRIVATE_KEY }}
- name: Run PocketBase AI bot
env:
# GitHub REST as the bot identity
GH_APP_TOKEN: ${{ steps.app-token.outputs.token }}
PB_BOT_APP_ID: ${{ secrets.PB_BOT_APP_ID }}
# GitHub Models inference uses the built-in token (needs models: read)
MODELS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Built-in token for git/PR ops (CT-defaults sync), mirroring the slash bot
GH_DEFAULT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AI_MODEL: openai/gpt-4o
# PocketBase
POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}
POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
FRONTEND_URL: ${{ secrets.FRONTEND_URL }}
REVALIDATE_SECRET: ${{ secrets.REVALIDATE_SECRET }}
# Event context
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_ID: ${{ github.event.comment.id }}
COMMENT_AUTHOR_TYPE: ${{ github.event.comment.user.type }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
ACTOR: ${{ github.event.comment.user.login }}
ACTOR_ASSOCIATION: ${{ github.event.comment.author_association }}
run: |
node << 'ENDSCRIPT'
(async function () {
const https = require('https');
const http = require('http');
const url = require('url');
// ── HTTP helper with redirect following ────────────────────────────
function request(fullUrl, opts, redirectCount) {
redirectCount = redirectCount || 0;
return new Promise(function (resolve, reject) {
const u = url.parse(fullUrl);
const isHttps = u.protocol === 'https:';
const body = opts.body;
const options = {
hostname: u.hostname,
port: u.port || (isHttps ? 443 : 80),
path: u.path,
method: opts.method || 'GET',
headers: opts.headers || {}
};
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
const lib = isHttps ? https : http;
const req = lib.request(options, function (res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
const redirectUrl = url.resolve(fullUrl, res.headers.location);
res.resume();
resolve(request(redirectUrl, opts, redirectCount + 1));
return;
}
let data = '';
res.on('data', function (chunk) { data += chunk; });
res.on('end', function () {
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
// ── GitHub REST (as the bot app) ───────────────────────────────────
const owner = process.env.REPO_OWNER;
const repo = process.env.REPO_NAME;
const issueNumber = parseInt(process.env.ISSUE_NUMBER, 10);
const commentId = parseInt(process.env.COMMENT_ID, 10);
const actor = process.env.ACTOR;
function ghRequest(path, method, body) {
const headers = {
'Authorization': 'Bearer ' + process.env.GH_APP_TOKEN,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'PocketBase-AI-Bot'
};
const bodyStr = body ? JSON.stringify(body) : undefined;
if (bodyStr) headers['Content-Type'] = 'application/json';
return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });
}
// Same as ghRequest but authenticated with the built-in GITHUB_TOKEN.
// Used for the CT-defaults sync branch/PR (the App token lacks contents:write).
function ghDefault(path, method, body) {
const headers = {
'Authorization': 'Bearer ' + process.env.GH_DEFAULT_TOKEN,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'PocketBase-AI-Bot'
};
const bodyStr = body ? JSON.stringify(body) : undefined;
if (bodyStr) headers['Content-Type'] = 'application/json';
return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });
}
async function addReaction(content) {
try {
await ghRequest('/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions', 'POST', { content });
} catch (e) { console.warn('Could not add reaction:', e.message); }
}
async function postComment(text) {
const res = await ghRequest('/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments', 'POST', { body: text });
if (!res.ok) console.warn('Could not post comment:', res.body);
return res.ok ? JSON.parse(res.body) : null;
}
async function updateComment(id, text) {
const res = await ghRequest('/repos/' + owner + '/' + repo + '/issues/comments/' + id, 'PATCH', { body: text });
if (!res.ok) console.warn('Could not update comment:', res.body);
}
async function listIssueComments() {
const all = [];
let page = 1;
while (page <= 10) {
const res = await ghRequest('/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments?per_page=100&page=' + page);
if (!res.ok) break;
const batch = JSON.parse(res.body);
all.push.apply(all, batch);
if (batch.length < 100) break;
page++;
}
return all;
}
// ── 1. Self-trigger guard (App-token comments DO re-fire this event) ─
if (process.env.COMMENT_AUTHOR_TYPE === 'Bot') {
console.log('Comment authored by a bot — skipping to avoid loops.');
return;
}
// ── 2. Permission gate (mirrors the slash bot) ─────────────────────
const association = process.env.ACTOR_ASSOCIATION;
if (association !== 'OWNER' && association !== 'MEMBER') {
await addReaction('-1');
await postComment(
'❌ **PocketBase AI Bot**: @' + actor + ' is not authorized to use this command.\n' +
'Only org members (Contributors team) can use `@pocketbase-bot`.'
);
return;
}
// ── 3. Extract the instruction after the @pocketbase-bot handle ────
const commentBody = process.env.COMMENT_BODY || '';
const handleMatch = commentBody.match(/@pocketbase-bot(\[bot\])?/i);
if (!handleMatch) {
console.log('No @pocketbase-bot handle found — ignoring.');
return;
}
const instruction = commentBody.slice(handleMatch.index + handleMatch[0].length).trim();
if (!instruction) {
await addReaction('-1');
await postComment(
' **PocketBase AI Bot**: Tell me what to do, e.g.\n' +
'`@pocketbase-bot change RAM to 4096 on zigbee2mqtt` or `@pocketbase-bot disable script Nextcloud`.'
);
return;
}
// ── PocketBase auth + low-level helpers ────────────────────────────
const pbRaw = process.env.POCKETBASE_URL.replace(/\/$/, '');
const apiBase = /\/api$/i.test(pbRaw) ? pbRaw : pbRaw + '/api';
const coll = process.env.POCKETBASE_COLLECTION;
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
async function pbAuth() {
const res = await request(apiBase + '/collections/users/auth-with-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identity: process.env.POCKETBASE_ADMIN_EMAIL, password: process.env.POCKETBASE_ADMIN_PASSWORD })
});
if (!res.ok) throw new Error('PocketBase auth failed: ' + res.body);
return JSON.parse(res.body).token;
}
async function pbFindRecord(token, slug) {
const filter = "(slug='" + String(slug).replace(/'/g, "''") + "')";
const res = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', { headers: { 'Authorization': token } });
const list = JSON.parse(res.body);
return list.items && list.items[0];
}
async function pbPatch(token, id, payload) {
return request(recordsUrl + '/' + id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
function readJsonBlob(val) {
if (Array.isArray(val)) return val;
try { return JSON.parse(val || '[]'); } catch (e) { return []; }
}
async function revalidate(s) {
const frontendUrl = process.env.FRONTEND_URL;
const secret = process.env.REVALIDATE_SECRET;
if (!frontendUrl || !secret) return;
try {
await request(frontendUrl.replace(/\/$/, '') + '/api/revalidate', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + secret, 'Content-Type': 'application/json' },
body: JSON.stringify({ tags: ['scripts', 'script-' + s] })
});
} catch (e) { console.warn('Revalidation skipped:', e.message); }
}
// ── CT-defaults sync PR (copied from slash bot) ────────────────────
function encodeContentPath(filePath) { return filePath.split('/').map(encodeURIComponent).join('/'); }
function decodeGitHubContent(content) { return Buffer.from((content || '').replace(/\n/g, ''), 'base64').toString('utf8'); }
function sanitizeBranchPart(value) {
return (value || '').toLowerCase().replace(/[^a-z0-9._/-]+/g, '-').replace(/\/+/g, '/').replace(/^-+|-+$/g, '');
}
function applyCtDefaultChanges(scriptText, varChanges) {
let nextText = scriptText;
const updatedVars = [], unchangedVars = [];
for (const [varName, rawValue] of Object.entries(varChanges)) {
const newValue = String(rawValue);
const pattern = new RegExp('(^\\s*' + varName + '="\\$\\{' + varName + ':-)([^"}]*)(\\}"\\s*$)', 'm');
const match = nextText.match(pattern);
if (!match) continue;
if (match[2] === newValue) { unchangedVars.push(varName); continue; }
nextText = nextText.replace(pattern, '$1' + newValue + '$3');
updatedVars.push(varName);
}
return { nextText, updatedVars, unchangedVars };
}
async function ensureBranch(defaultBranch, branchName) {
const ghRequest = ghDefault; // git ops run as the built-in token
const branchRefRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/ref/heads/' + encodeURIComponent(branchName));
if (branchRefRes.ok) return;
const defaultRefRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/ref/heads/' + encodeURIComponent(defaultBranch));
if (!defaultRefRes.ok) throw new Error('Could not read default branch ref: ' + defaultRefRes.body);
const defaultRef = JSON.parse(defaultRefRes.body);
const createBranchRes = await ghRequest('/repos/' + owner + '/' + repo + '/git/refs', 'POST', { ref: 'refs/heads/' + branchName, sha: defaultRef.object.sha });
if (!createBranchRes.ok) throw new Error('Could not create branch: ' + createBranchRes.body);
}
async function upsertCtDefaultsPr(slugValue, varChanges) {
const ghRequest = ghDefault; // contents/PR ops run as the built-in token
const wantedEntries = Object.entries(varChanges || {}).filter(function ([, v]) { return v !== undefined && v !== null && String(v) !== ''; });
if (wantedEntries.length === 0) return { status: 'skipped', reason: 'No mapped CT defaults changed.' };
const repoRes = await ghRequest('/repos/' + owner + '/' + repo);
if (!repoRes.ok) throw new Error('Could not read repository metadata: ' + repoRes.body);
const defaultBranch = JSON.parse(repoRes.body).default_branch;
const ctPath = 'ct/' + slugValue + '.sh';
const encodedCtPath = encodeContentPath(ctPath);
const defaultFileRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath + '?ref=' + encodeURIComponent(defaultBranch));
if (defaultFileRes.statusCode === 404) return { status: 'skipped', reason: 'No matching CT file found at `' + ctPath + '`.' };
if (!defaultFileRes.ok) throw new Error('Could not read CT file from default branch: ' + defaultFileRes.body);
const branchName = 'pocketbase-sync/' + sanitizeBranchPart(slugValue || 'unknown');
await ensureBranch(defaultBranch, branchName);
const branchFileRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath + '?ref=' + encodeURIComponent(branchName));
if (!branchFileRes.ok) throw new Error('Could not read CT file from sync branch: ' + branchFileRes.body);
const branchFile = JSON.parse(branchFileRes.body);
const currentBranchText = decodeGitHubContent(branchFile.content);
const updateResult = applyCtDefaultChanges(currentBranchText, Object.fromEntries(wantedEntries));
if (updateResult.updatedVars.length === 0) return { status: 'skipped', reason: 'CT defaults already up to date.' };
const putRes = await ghRequest('/repos/' + owner + '/' + repo + '/contents/' + encodedCtPath, 'PUT', {
message: 'chore(ct): sync ' + slugValue + ' defaults from PocketBase',
content: Buffer.from(updateResult.nextText, 'utf8').toString('base64'),
sha: branchFile.sha,
branch: branchName
});
if (!putRes.ok) throw new Error('Could not update CT file: ' + putRes.body);
const openPrRes = await ghRequest('/repos/' + owner + '/' + repo + '/pulls?state=open&head=' + encodeURIComponent(owner + ':' + branchName) + '&base=' + encodeURIComponent(defaultBranch));
if (!openPrRes.ok) throw new Error('Could not query existing PRs: ' + openPrRes.body);
const openPrs = JSON.parse(openPrRes.body);
if (openPrs.length > 0) return { status: 'updated', prUrl: openPrs[0].html_url, updatedVars: updateResult.updatedVars };
const createPrRes = await ghRequest('/repos/' + owner + '/' + repo + '/pulls', 'POST', {
title: 'chore(ct): sync ' + slugValue + ' defaults with PocketBase',
body: '## Summary\n- Sync default CT variables for `' + slugValue + '` after an `@pocketbase-bot` update.\n- Updated vars: `' + updateResult.updatedVars.join('`, `') + '`.\n\n## Source\n- Triggered by @' + actor + ' via PocketBase AI bot.\n',
head: branchName,
base: defaultBranch
});
if (!createPrRes.ok) throw new Error('Could not create PR: ' + createPrRes.body);
return { status: 'created', prUrl: JSON.parse(createPrRes.body).html_url, updatedVars: updateResult.updatedVars };
}
// ── Allow-lists (mirror the slash bot) ─────────────────────────────
const ALLOWED_FIELDS = {
name: 'string', description: 'string', logo: 'string', documentation: 'string',
website: 'string', project_url: 'string', github: 'string', config_path: 'string',
tags: 'string', port: 'number', default_user: 'nullable_string', default_passwd: 'nullable_string',
unprivileged: 'number', updateable: 'boolean', privileged: 'boolean', has_arm: 'boolean',
is_dev: 'boolean', is_disabled: 'boolean', disable_message: 'string',
is_deleted: 'boolean', deleted_message: 'string'
};
const FIELD_TO_CT_VAR = { tags: 'var_tags', unprivileged: 'var_unprivileged' };
const RESOURCE_KEYS = { cpu: 'number', ram: 'number', hdd: 'number', os: 'string', version: 'string' };
const METHOD_KEYS = { config_path: 'string', script: 'string' };
const ALL_METHOD_KEYS = Object.assign({}, RESOURCE_KEYS, METHOD_KEYS);
const RESOURCE_TO_CT_VAR = { cpu: 'var_cpu', ram: 'var_ram', hdd: 'var_disk', os: 'var_os', version: 'var_version' };
function castFieldValue(key, rawVal) {
const type = ALLOWED_FIELDS[key];
if (!type) return { error: 'Unknown field `' + key + '`' };
if (type === 'boolean') {
if (rawVal === true || rawVal === 'true') return { value: true };
if (rawVal === false || rawVal === 'false') return { value: false };
return { error: '`' + key + '` must be true/false' };
}
if (type === 'number') {
const n = parseInt(rawVal, 10);
if (isNaN(n)) return { error: '`' + key + '` must be a number' };
return { value: n };
}
if (type === 'nullable_string') return { value: rawVal === '' || rawVal == null ? null : String(rawVal) };
return { value: String(rawVal) };
}
// ── Operation validation (used at propose AND confirm time) ────────
// Never trust raw operations: enforce the field/op allow-lists and
// re-cast values. Returns only well-formed, allowed operations.
function sanitizeOperations(ops) {
const validOps = [], problems = [];
for (const op of (Array.isArray(ops) ? ops : [])) {
if (op && op.kind === 'field') {
const cast = castFieldValue(op.field, op.value);
if (cast.error) { problems.push(cast.error); continue; }
validOps.push({ kind: 'field', field: op.field, value: cast.value });
} else if (op && op.kind === 'note' && ['add', 'edit', 'remove'].includes(op.action)) {
validOps.push({ kind: 'note', action: op.action, type: String(op.type || ''), text: op.text, newText: op.newText });
} else if (op && op.kind === 'method' && ['add', 'edit', 'remove'].includes(op.action)) {
const changes = {};
for (const [k, v] of Object.entries(op.changes || {})) { if (ALL_METHOD_KEYS[k]) changes[k] = v; }
validOps.push({ kind: 'method', action: op.action, type: String(op.type || 'default'), changes });
} else {
problems.push('Unsupported operation: `' + JSON.stringify(op) + '`');
}
}
return { validOps, problems };
}
// ── Executor: apply a validated {slug, operations} set ─────────────
async function applyOperations(action) {
const token = await pbAuth();
const record = await pbFindRecord(token, action.slug);
if (!record) return { ok: false, summary: '❌ No PocketBase record for slug `' + action.slug + '`.' };
const fieldPayload = {};
let notesArr = readJsonBlob(record.notes);
let methodsArr = readJsonBlob(record.install_methods);
let notesChanged = false, methodsChanged = false;
const ctChanges = {};
const lines = [];
for (const op of action.operations) {
if (op.kind === 'field') {
const cast = castFieldValue(op.field, op.value);
if (cast.error) { lines.push('- ⚠️ skipped field: ' + cast.error); continue; }
fieldPayload[op.field] = cast.value;
if (FIELD_TO_CT_VAR[op.field]) ctChanges[FIELD_TO_CT_VAR[op.field]] = cast.value;
lines.push('- `' + op.field + '` → `' + JSON.stringify(cast.value) + '`');
} else if (op.kind === 'note') {
const type = String(op.type || '').toLowerCase();
if (op.action === 'add') {
notesArr.push({ type, text: String(op.text || '') });
notesChanged = true; lines.push('- note add `' + type + '`: ' + op.text);
} else if (op.action === 'remove') {
const before = notesArr.length;
notesArr = notesArr.filter(function (n) { return !(String(n.type).toLowerCase() === type && n.text === op.text); });
if (notesArr.length !== before) { notesChanged = true; lines.push('- note remove `' + type + '`: ' + op.text); }
else lines.push('- ⚠️ note remove: no `' + type + '` note matched');
} else if (op.action === 'edit') {
const idx = notesArr.findIndex(function (n) { return String(n.type).toLowerCase() === type && n.text === op.text; });
if (idx !== -1) { notesArr[idx].text = String(op.newText || ''); notesChanged = true; lines.push('- note edit `' + type + '`'); }
else lines.push('- ⚠️ note edit: no `' + type + '` note matched');
}
} else if (op.kind === 'method') {
const type = String(op.type || '').toLowerCase();
const changes = op.changes || {};
if (op.action === 'remove') {
const before = methodsArr.length;
methodsArr = methodsArr.filter(function (im) { return String(im.type || '').toLowerCase() !== type; });
if (methodsArr.length !== before) { methodsChanged = true; lines.push('- method remove `' + type + '`'); }
else lines.push('- ⚠️ method remove: `' + type + '` not found');
} else {
let method = methodsArr.find(function (im) { return String(im.type || '').toLowerCase() === type; });
if (!method && op.action === 'add') { method = { type, resources: { cpu: 1, ram: 512, hdd: 4, os: 'debian', version: '13' } }; methodsArr.push(method); }
if (!method) { lines.push('- ⚠️ method edit: `' + type + '` not found'); continue; }
if (!method.resources) method.resources = {};
for (const [k, v] of Object.entries(changes)) {
if (RESOURCE_KEYS[k]) {
method.resources[k] = RESOURCE_KEYS[k] === 'number' ? parseInt(v, 10) : String(v);
if (RESOURCE_TO_CT_VAR[k]) ctChanges[RESOURCE_TO_CT_VAR[k]] = method.resources[k];
} else if (METHOD_KEYS[k]) {
method[k] = v === '' ? null : String(v);
}
}
methodsChanged = true;
lines.push('- method `' + (op.action === 'add' ? 'add' : 'edit') + '` `' + type + '`: ' + JSON.stringify(changes));
}
}
}
if (Object.keys(fieldPayload).length) {
const r = await pbPatch(token, record.id, fieldPayload);
if (!r.ok) return { ok: false, summary: '❌ Field update failed:\n```\n' + r.body + '\n```' };
}
if (notesChanged) {
const r = await pbPatch(token, record.id, { notes: notesArr });
if (!r.ok) return { ok: false, summary: '❌ Notes update failed:\n```\n' + r.body + '\n```' };
}
if (methodsChanged) {
const r = await pbPatch(token, record.id, { install_methods: methodsArr });
if (!r.ok) return { ok: false, summary: '❌ Install-method update failed:\n```\n' + r.body + '\n```' };
}
await revalidate(action.slug);
let ctNote = '';
if (Object.keys(ctChanges).length) {
try {
const sync = await upsertCtDefaultsPr(action.slug, ctChanges);
if (sync.status === 'created') ctNote = '\n\n**CT sync PR:** ' + sync.prUrl;
else if (sync.status === 'updated') ctNote = '\n\n**CT sync PR updated:** ' + sync.prUrl;
else if (sync.status === 'skipped') ctNote = '\n\n**CT sync skipped:** ' + sync.reason;
} catch (e) { ctNote = '\n\n**CT sync failed:** ' + e.message; }
}
return { ok: true, summary: lines.join('\n') + ctNote };
}
// ── 4. Confirm branch ──────────────────────────────────────────────
const PENDING_RE = /<!--\s*pocketbase-pending:\s*([A-Za-z0-9+/=]+)\s*-->/;
const isConfirm = /^(confirm|yes|apply|do it|y)\b/i.test(instruction);
if (isConfirm) {
const comments = await listIssueComments();
const appId = String(process.env.PB_BOT_APP_ID || '');
let pending = null, pendingComment = null;
for (let i = comments.length - 1; i >= 0; i--) {
const c = comments[i];
// Only trust a marker in a comment THIS bot app authored — otherwise a
// user could hand-craft a forged pocketbase-pending marker and confirm it.
const byBotApp = c.user && c.user.type === 'Bot' &&
c.performed_via_github_app && String(c.performed_via_github_app.id) === appId;
if (!byBotApp) continue;
const m = c.body && c.body.match(PENDING_RE);
if (m) { pending = m[1]; pendingComment = c; break; }
}
if (!pending) {
await addReaction('confused');
await postComment('🤔 **PocketBase AI Bot**: I have no pending change to confirm in this thread.');
return;
}
let action;
try { action = JSON.parse(Buffer.from(pending, 'base64').toString('utf8')); }
catch (e) { await postComment('❌ **PocketBase AI Bot**: Could not decode the pending change.'); return; }
// Re-validate the decoded operations before applying (defense-in-depth).
const recheck = sanitizeOperations(action.operations);
if (!action.slug || recheck.validOps.length === 0) {
await addReaction('-1');
await postComment('❌ **PocketBase AI Bot**: The pending change is no longer valid. Please restate the request.');
return;
}
action.operations = recheck.validOps;
let result;
try { result = await applyOperations(action); }
catch (e) { await addReaction('-1'); await postComment('❌ **PocketBase AI Bot**: ' + e.message); return; }
if (!result.ok) { await addReaction('-1'); await postComment(result.summary); return; }
await updateComment(pendingComment.id, pendingComment.body.replace(PENDING_RE, '<!-- pocketbase-applied -->'));
await addReaction('+1');
await postComment(
'✅ **PocketBase AI Bot**: Applied to **`' + action.slug + '`**\n\n' + result.summary +
'\n\n*Confirmed by @' + actor + '*'
);
return;
}
// ── 5. New request: acknowledge, fetch script list, call the model ─
await addReaction('eyes');
const token0 = await pbAuth();
const scripts = [];
let page = 1;
while (page <= 5) {
const res = await request(recordsUrl + '?fields=slug,name&perPage=500&page=' + page, { headers: { 'Authorization': token0 } });
if (!res.ok) break;
const data = JSON.parse(res.body);
(data.items || []).forEach(function (it) { if (it.slug) scripts.push({ slug: it.slug, name: it.name || it.slug }); });
if (!data.items || data.items.length < 500) break;
page++;
}
const SYSTEM_PROMPT =
'You translate a maintainer\'s natural-language request into a STRICT JSON change-set for a ' +
'script catalog (PocketBase). Respond with a SINGLE JSON object and nothing else.\n\n' +
'Schema:\n' +
'{\n' +
' "slug": string|null, // MUST be one of the known slugs below, chosen from the request\n' +
' "operations": [ ... ], // [] if you cannot determine concrete changes\n' +
' "human_summary": string, // short plain-English description\n' +
' "clarification": string|null // a question to ask if ambiguous/unsupported; else null\n' +
'}\n\n' +
'Operation kinds:\n' +
'- {"kind":"field","field":<one of: ' + Object.keys(ALLOWED_FIELDS).join(', ') + '>,"value":<bool|number|string>}\n' +
' (booleans true/false; "is_disabled"/"is_deleted"/"is_dev" are booleans; "port"/"unprivileged" are numbers; rest strings.)\n' +
'- {"kind":"note","action":"add"|"edit"|"remove","type":string,"text":string,"newText":string?}\n' +
'- {"kind":"method","action":"add"|"edit"|"remove","type":string,"changes":{cpu?:number,ram?:number,hdd?:number,os?:string,version?:string,config_path?:string,script?:string}}\n' +
' (RAM/HDD are in MB/GB; method "type" defaults to "default" if the user does not name one.)\n\n' +
'Rules:\n' +
'- Only use fields/operations listed above. If the request needs something else, set clarification and operations=[].\n' +
'- "disable"/"enable" map to is_disabled true/false. If disabling and the user gave a reason, also set disable_message.\n' +
'- Resolve the target script to a slug from the list. If you cannot confidently match exactly one, set slug=null and ask via clarification.\n\n' +
'Known scripts (slug — name):\n' +
scripts.map(function (s) { return s.slug + ' — ' + s.name; }).join('\n');
const modelRes = await request('https://models.github.ai/inference/chat/completions', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + process.env.MODELS_TOKEN, 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
model: process.env.AI_MODEL || 'openai/gpt-4o',
temperature: 0.1,
response_format: { type: 'json_object' },
messages: [{ role: 'system', content: SYSTEM_PROMPT }, { role: 'user', content: instruction }]
})
});
if (!modelRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase AI Bot**: Model request failed (' + modelRes.statusCode + ').\n```\n' + modelRes.body.slice(0, 500) + '\n```');
return;
}
let parsed;
try {
const content = JSON.parse(modelRes.body).choices[0].message.content;
const cleaned = content.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/i, '').trim();
parsed = JSON.parse(cleaned);
} catch (e) {
await addReaction('-1');
await postComment('❌ **PocketBase AI Bot**: Could not parse the model response. Please rephrase.');
return;
}
// ── 6. Validate ────────────────────────────────────────────────────
const knownSlugs = new Set(scripts.map(function (s) { return s.slug; }));
const problems = [];
if (parsed.clarification) problems.push(parsed.clarification);
if (!parsed.slug || !knownSlugs.has(parsed.slug)) problems.push('I could not match the request to a known script.');
const sanitized = sanitizeOperations(parsed.operations);
const validOps = sanitized.validOps;
problems.push.apply(problems, sanitized.problems);
if (validOps.length === 0) problems.push('No concrete, supported change was found.');
if (problems.length) {
await addReaction('confused');
await postComment(
'🤔 **PocketBase AI Bot**: I need a bit more to act on that.\n\n- ' + problems.join('\n- ') +
'\n\nTry naming the script and the exact change, e.g. `@pocketbase-bot set RAM to 4096 on zigbee2mqtt`.'
);
return;
}
// ── 7. Propose (do NOT apply yet) ──────────────────────────────────
const action = { slug: parsed.slug, operations: validOps };
const bullets = validOps.map(function (op) {
if (op.kind === 'field') return '- `' + op.field + '` → `' + JSON.stringify(op.value) + '`';
if (op.kind === 'note') return '- note ' + op.action + ' `' + op.type + '`' + (op.text ? ': ' + op.text : '');
return '- method ' + op.action + ' `' + op.type + '`: ' + JSON.stringify(op.changes);
}).join('\n');
const marker = '<!-- pocketbase-pending: ' + Buffer.from(JSON.stringify(action), 'utf8').toString('base64') + ' -->';
await addReaction('+1');
await postComment(
'🤖 **PocketBase AI Bot** — please confirm\n\n' +
(parsed.human_summary ? '> ' + parsed.human_summary + '\n\n' : '') +
'**Target:** `' + action.slug + '`\n**Proposed changes:**\n' + bullets + '\n\n' +
'Reply **`@pocketbase-bot confirm`** to apply, or restate the request to adjust.\n' + marker
);
})().catch(function (e) {
console.error('Fatal error:', e && (e.message || e));
process.exit(1);
});
ENDSCRIPT

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

@@ -13,8 +13,8 @@ jobs:
pocketbase-bot:
runs-on: self-hosted
# Act on comments that contain a /pocketbase command line (precise line check happens in-script)
if: contains(github.event.comment.body, '/pocketbase')
# Only act on /pocketbase commands
if: startsWith(github.event.comment.body, '/pocketbase')
steps:
- name: Execute PocketBase bot command
@@ -257,22 +257,6 @@ jobs:
if (!res.ok) console.warn('Could not post comment:', res.body);
}
// ── Locate the command line ────────────────────────────────────────
// Accept /pocketbase at the start of ANY line (leading whitespace ok),
// so the command works even when preceded by other text. Mid-sentence
// mentions and blockquoted ("> ...") examples are ignored.
const commentBody = process.env.COMMENT_BODY || '';
const cmdLine = commentBody
.split('\n')
.map(l => l.trim())
.find(l => l.startsWith('/pocketbase'));
if (!cmdLine) {
console.log('No /pocketbase command line found — ignoring comment.');
process.exit(0);
}
const withoutCmd = cmdLine.replace(/^\/pocketbase\s*/, '').trim();
// ── Permission check ───────────────────────────────────────────────
const association = process.env.ACTOR_ASSOCIATION;
if (association !== 'OWNER' && association !== 'MEMBER') {
@@ -288,6 +272,10 @@ jobs:
await addReaction('eyes');
// ── Parse command ──────────────────────────────────────────────────
const commentBody = process.env.COMMENT_BODY || '';
const lines = commentBody.trim().split('\n');
const firstLine = lines[0].trim();
const withoutCmd = firstLine.replace(/^\/pocketbase\s+/, '').trim();
function extractCodeBlock(body) {
const m = body.match(/```[^\n]*\n([\s\S]*?)```/);

View File

@@ -56,9 +56,6 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
@@ -72,7 +69,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
<details>
<summary><h4>May (30 entries)</h4></summary>
<summary><h4>May (23 entries)</h4></summary>
[View May 2026 Changelog](.github/changelogs/2026/05.md)
@@ -470,117 +467,14 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-06-02
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- infisical: fix update abort due to creds field mismatch (#14868) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14870](https://github.com/community-scripts/ProxmoxVE/pull/14870))
- #### ✨ New Features
- feat(degoog): enable default valkey cache integration [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14871](https://github.com/community-scripts/ProxmoxVE/pull/14871))
- #### 🔧 Refactor
- chore: bump Node version in selected scripts [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14873](https://github.com/community-scripts/ProxmoxVE/pull/14873))
### 💾 Core
- #### ✨ New Features
- tools.func: add support for Rust installation profile in setup_rust [@MickLesk](https://github.com/MickLesk) ([#14872](https://github.com/community-scripts/ProxmoxVE/pull/14872))
### 📂 Github
- fix(workflow): only flag node drift when local is behind upstream [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14874](https://github.com/community-scripts/ProxmoxVE/pull/14874))
## 2026-06-01
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- fix(dispatcharr): forward nginx port for M3U URLs on new installs [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14862](https://github.com/community-scripts/ProxmoxVE/pull/14862))
- Set environment paths in service for apprise-api-install.sh [@SystemIdleProcess](https://github.com/SystemIdleProcess) ([#14805](https://github.com/community-scripts/ProxmoxVE/pull/14805))
- fix(fireshare): rebuild client on update to fix nginx 500 [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14848](https://github.com/community-scripts/ProxmoxVE/pull/14848))
- Fix Kan build failure (TS7016 nodemailer) [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14856](https://github.com/community-scripts/ProxmoxVE/pull/14856))
- fix(firefly): set Data Importer APP_URL for subdirectory install [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14847](https://github.com/community-scripts/ProxmoxVE/pull/14847))
- kan: extend fetch_and_deploy_gh_tag to use 'latest' tag [@MickLesk](https://github.com/MickLesk) ([#14853](https://github.com/community-scripts/ProxmoxVE/pull/14853))
- Glance: preserve glance.yml across updates [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#14845](https://github.com/community-scripts/ProxmoxVE/pull/14845))
- NginxProxymanager: set Certbot version in npm.service environment variable (2.15.0) [@MickLesk](https://github.com/MickLesk) ([#14843](https://github.com/community-scripts/ProxmoxVE/pull/14843))
- [FileFlows] Fix service handling by using systemctl --all with quoted glob [@adrianmusante](https://github.com/adrianmusante) ([#14838](https://github.com/community-scripts/ProxmoxVE/pull/14838))
- #### ✨ New Features
- Kometa: also update Quickstart in update_script [@MickLesk](https://github.com/MickLesk) ([#14529](https://github.com/community-scripts/ProxmoxVE/pull/14529))
## 2026-05-31
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Manyfold: regenerate Rails credentials on update to fix encryption mimatch [@MickLesk](https://github.com/MickLesk) ([#14817](https://github.com/community-scripts/ProxmoxVE/pull/14817))
- OpenThread-BR: use correct ipv6 configuration [@tomfrenzel](https://github.com/tomfrenzel) ([#14829](https://github.com/community-scripts/ProxmoxVE/pull/14829))
- #### 🔧 Refactor
- Webtrees: use PHP CLI for initial setup instead of curl to setup wizard [@MickLesk](https://github.com/MickLesk) ([#14818](https://github.com/community-scripts/ProxmoxVE/pull/14818))
- Kima-Hub: use curl_with_retry for ML model downloads to fix possible timeout issues [@MickLesk](https://github.com/MickLesk) ([#14816](https://github.com/community-scripts/ProxmoxVE/pull/14816))
### 🧰 Tools
- #### 🔧 Refactor
- PBS4-Upgrade: update current PBS3 packages before switching to Trixie repos [@MickLesk](https://github.com/MickLesk) ([#14815](https://github.com/community-scripts/ProxmoxVE/pull/14815))
## 2026-05-30
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Flatnotes: fix empty package name in pyproject.toml [@MickLesk](https://github.com/MickLesk) ([#14814](https://github.com/community-scripts/ProxmoxVE/pull/14814))
## 2026-05-29
### 🆕 New Scripts
- Kan ([#14776](https://github.com/community-scripts/ProxmoxVE/pull/14776))
- Dynacat ([#14777](https://github.com/community-scripts/ProxmoxVE/pull/14777))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Fix lobehub docker path [@dannyyy](https://github.com/dannyyy) ([#14793](https://github.com/community-scripts/ProxmoxVE/pull/14793))
- karakeep: add more hdd space [@MickLesk](https://github.com/MickLesk) ([#14797](https://github.com/community-scripts/ProxmoxVE/pull/14797))
- Grist: Revert installation of EE [@tremor021](https://github.com/tremor021) ([#14784](https://github.com/community-scripts/ProxmoxVE/pull/14784))
- #### 🔧 Refactor
- Sure: Remove `$STD` for `systemctl enable -q` [@tremor021](https://github.com/tremor021) ([#14801](https://github.com/community-scripts/ProxmoxVE/pull/14801))
## 2026-05-28
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- RomM: remove nginx default.conf during installation [@MickLesk](https://github.com/MickLesk) ([#14766](https://github.com/community-scripts/ProxmoxVE/pull/14766))
- Open-Archiver: replace pnpm approve-builds --yes with --all [@MickLesk](https://github.com/MickLesk) ([#14765](https://github.com/community-scripts/ProxmoxVE/pull/14765))
- fix(hermesagent): set npm_config_yes=true to suppress interactive pro… [@steveonjava](https://github.com/steveonjava) ([#14763](https://github.com/community-scripts/ProxmoxVE/pull/14763))
- #### 🔧 Refactor
- Yamtrack: migrate to uv [@MickLesk](https://github.com/MickLesk) ([#14767](https://github.com/community-scripts/ProxmoxVE/pull/14767))
### ❔ Uncategorized
- chore(ct): sync adventurelog defaults with PocketBase [@github-actions[bot]](https://github.com/github-actions[bot]) ([#14772](https://github.com/community-scripts/ProxmoxVE/pull/14772))
## 2026-05-27
### 🆕 New Scripts
@@ -1097,4 +991,142 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🔧 Refactor
- Mail-Archiver: update dependencies [@tremor021](https://github.com/tremor021) ([#14152](https://github.com/community-scripts/ProxmoxVE/pull/14152))
- Mail-Archiver: update dependencies [@tremor021](https://github.com/tremor021) ([#14152](https://github.com/community-scripts/ProxmoxVE/pull/14152))
## 2026-04-30
### 🆕 New Scripts
- Nagios ([#14126](https://github.com/community-scripts/ProxmoxVE/pull/14126))
- Neko ([#14121](https://github.com/community-scripts/ProxmoxVE/pull/14121))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- alpine-docker: install openssl as core dependency | alpine-komodo: check & install openssl if missing [@MickLesk](https://github.com/MickLesk) ([#14134](https://github.com/community-scripts/ProxmoxVE/pull/14134))
- endurain: update source references to Codeberg [@MickLesk](https://github.com/MickLesk) ([#14128](https://github.com/community-scripts/ProxmoxVE/pull/14128))
### 💾 Core
- #### 🔧 Refactor
- tools.func: Manage minor versions for MongoDB 8.x [@tremor021](https://github.com/tremor021) ([#14131](https://github.com/community-scripts/ProxmoxVE/pull/14131))
## 2026-04-29
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- GrayLog: MongoDB update to 8.2.x [@tremor021](https://github.com/tremor021) ([#14114](https://github.com/community-scripts/ProxmoxVE/pull/14114))
- Graylog: Better information in the log file [@tremor021](https://github.com/tremor021) ([#14110](https://github.com/community-scripts/ProxmoxVE/pull/14110))
- #### 🔧 Refactor
- Refactor: checkMK [@MickLesk](https://github.com/MickLesk) ([#14105](https://github.com/community-scripts/ProxmoxVE/pull/14105))
- PatchMon: Unpin release [@tremor021](https://github.com/tremor021) ([#14097](https://github.com/community-scripts/ProxmoxVE/pull/14097))
### 💾 Core
- #### 🔧 Refactor
- core: add guidance when storage lacks rootdir support [@MickLesk](https://github.com/MickLesk) ([#14108](https://github.com/community-scripts/ProxmoxVE/pull/14108))
## 2026-04-28
### 🆕 New Scripts
- StoryBook ([#14081](https://github.com/community-scripts/ProxmoxVE/pull/14081))
- CoreDNS ([#14082](https://github.com/community-scripts/ProxmoxVE/pull/14082))
### 🚀 Updated Scripts
- Fix Dawarich Install/Update [@Jerry1098](https://github.com/Jerry1098) ([#14078](https://github.com/community-scripts/ProxmoxVE/pull/14078))
- #### ✨ New Features
- PatchMon Version 2.0.2 Script update [@9technologygroup](https://github.com/9technologygroup) ([#14095](https://github.com/community-scripts/ProxmoxVE/pull/14095))
## 2026-04-27
### 🚀 Updated Scripts
- Add pamUsername column to userOrgs table [@JVKeller](https://github.com/JVKeller) ([#14075](https://github.com/community-scripts/ProxmoxVE/pull/14075))
- #### 🐞 Bug Fixes
- Dawarich: run db:migrate before assets:precompile [@MickLesk](https://github.com/MickLesk) ([#14051](https://github.com/community-scripts/ProxmoxVE/pull/14051))
- TechnitiumDNS: always install .NET 10 if not already present [@MickLesk](https://github.com/MickLesk) ([#14049](https://github.com/community-scripts/ProxmoxVE/pull/14049))
- #### 💥 Breaking Changes
- PatchMon: v2.0.0 migration [@vhsdream](https://github.com/vhsdream) ([#14015](https://github.com/community-scripts/ProxmoxVE/pull/14015))
### 💾 Core
- #### 🔧 Refactor
- Update build.func - fixed spelling mistake [@m1ckywill](https://github.com/m1ckywill) ([#14047](https://github.com/community-scripts/ProxmoxVE/pull/14047))
### 🧰 Tools
- #### 🐞 Bug Fixes
- update-lxcs/apps: avoid pct exec on containers mid-shutdown [@MickLesk](https://github.com/MickLesk) ([#14050](https://github.com/community-scripts/ProxmoxVE/pull/14050))
- #### ✨ New Features
- Add patchmon-agent report execution in update script [@heinemannj](https://github.com/heinemannj) ([#14054](https://github.com/community-scripts/ProxmoxVE/pull/14054))
## 2026-04-26
### 🆕 New Scripts
- TREK ([#14017](https://github.com/community-scripts/ProxmoxVE/pull/14017))
### 🚀 Updated Scripts
- fix(2fauth): handle stale backup directory on update [@omertahaoztop](https://github.com/omertahaoztop) ([#14018](https://github.com/community-scripts/ProxmoxVE/pull/14018))
- #### 🐞 Bug Fixes
- Increase Frigate default CPU cores from 4 to 8 [@MickLesk](https://github.com/MickLesk) ([#14039](https://github.com/community-scripts/ProxmoxVE/pull/14039))
- Technitium DNS: Ensure directories exist before running service [@tremor021](https://github.com/tremor021) ([#14030](https://github.com/community-scripts/ProxmoxVE/pull/14030))
### 💾 Core
- #### 🐞 Bug Fixes
- core: Correct deb822 repository flat path detection [@MickLesk](https://github.com/MickLesk) ([#14037](https://github.com/community-scripts/ProxmoxVE/pull/14037))
## 2026-04-25
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- VictoriaMetrics: Stop vmagent/vmalert before update [@irishpadres](https://github.com/irishpadres) ([#14016](https://github.com/community-scripts/ProxmoxVE/pull/14016))
- Domain-Monitor: start apache2 after stop instead of reload [@omertahaoztop](https://github.com/omertahaoztop) ([#14019](https://github.com/community-scripts/ProxmoxVE/pull/14019))
- Transmute: Fix ffmpeg detection [@tremor021](https://github.com/tremor021) ([#14008](https://github.com/community-scripts/ProxmoxVE/pull/14008))
- #### 🔧 Refactor
- Refactor: Technitium DNS [@tremor021](https://github.com/tremor021) ([#14013](https://github.com/community-scripts/ProxmoxVE/pull/14013))
## 2026-04-24
### 🆕 New Scripts
- Apprise-API ([#13934](https://github.com/community-scripts/ProxmoxVE/pull/13934))
- fireshare ([#13995](https://github.com/community-scripts/ProxmoxVE/pull/13995))
- Transmute ([#13935](https://github.com/community-scripts/ProxmoxVE/pull/13935))
- Jitsi-Meet ([#13897](https://github.com/community-scripts/ProxmoxVE/pull/13897))
### 🚀 Updated Scripts
- Update wger.sh [@Soppster1029](https://github.com/Soppster1029) ([#13977](https://github.com/community-scripts/ProxmoxVE/pull/13977))
- #### 🔧 Refactor
- Refactor: Ghostfolio [@MickLesk](https://github.com/MickLesk) ([#13990](https://github.com/community-scripts/ProxmoxVE/pull/13990))

View File

@@ -9,7 +9,7 @@ APP="AdventureLog"
var_tags="${var_tags:-traveling}"
var_disk="${var_disk:-7}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_ram="${var_ram:-2048}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-no}"

View File

@@ -25,7 +25,7 @@ function update_script() {
check_container_storage
check_container_resources
NODE_VERSION="26" setup_nodejs
NODE_VERSION="24" setup_nodejs
ensure_dependencies build-essential
if command -v cross-seed &>/dev/null; then

View File

@@ -49,21 +49,11 @@ function update_script() {
msg_ok "Installed Bun"
fi
msg_info "Updating Valkey"
ensure_dependencies valkey
msg_ok "Updated Valkey"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "degoog" "fccview/degoog" "prebuild" "latest" "/opt/degoog" "degoog_*_prebuild.tar.gz"
msg_info "Restoring Configuration & Data"
[[ -f /opt/degoog.env.bak ]] && mv /opt/degoog.env.bak /opt/degoog/.env
[[ -d /opt/degoog_data_backup ]] && mv /opt/degoog_data_backup /opt/degoog/data
if [[ -f /opt/degoog/.env ]]; then
grep -q "^DEGOOG_VALKEY_URL=" /opt/degoog/.env && sed -i "s|^DEGOOG_VALKEY_URL=.*|DEGOOG_VALKEY_URL=redis://valkey:6379|" /opt/degoog/.env || echo "DEGOOG_VALKEY_URL=redis://valkey:6379" >>/opt/degoog/.env
grep -q "^DEGOOG_CACHE_MAX_ENTRIES=" /opt/degoog/.env && sed -i "s|^DEGOOG_CACHE_MAX_ENTRIES=.*|DEGOOG_CACHE_MAX_ENTRIES=1000|" /opt/degoog/.env || echo "DEGOOG_CACHE_MAX_ENTRIES=1000" >>/opt/degoog/.env
grep -q "^DEGOOG_CACHE_TTL_MS=" /opt/degoog/.env && sed -i "s|^DEGOOG_CACHE_TTL_MS=.*|DEGOOG_CACHE_TTL_MS=43200000|" /opt/degoog/.env || echo "DEGOOG_CACHE_TTL_MS=43200000" >>/opt/degoog/.env
fi
msg_ok "Restored Configuration & Data"
msg_info "Starting Service"

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/Panonim/dynacat
APP="Dynacat"
var_tags="${var_tags:-dashboard;homepage;monitoring}"
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:-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 [[ ! -f /opt/dynacat/dynacat ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "dynacat" "Panonim/dynacat"; then
msg_info "Stopping Service"
systemctl stop dynacat
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp -r /opt/dynacat/config /opt/dynacat_config_backup
cp -r /opt/dynacat/assets /opt/dynacat_assets_backup
cp -r /opt/dynacat/data /opt/dynacat_data_backup
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "dynacat" "Panonim/dynacat" "prebuild" "latest" "/opt/dynacat" "dynacat-linux-amd64.tar.gz"
msg_info "Restoring Data"
cp -r /opt/dynacat_config_backup/. /opt/dynacat/config
cp -r /opt/dynacat_assets_backup/. /opt/dynacat/assets
cp -r /opt/dynacat_data_backup/. /opt/dynacat/data
rm -rf /opt/dynacat_config_backup /opt/dynacat_assets_backup /opt/dynacat_data_backup
chmod +x /opt/dynacat/dynacat
msg_ok "Restored Data"
msg_info "Starting Service"
systemctl start dynacat
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}"

View File

@@ -31,11 +31,22 @@ 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
CURRENT=$(cat /opt/fileflows/version 2>/dev/null | tr -d '[:space:]')
# Extract latest version from the Content-Disposition header of the download URL
RELEASE=$(curl -fsI "https://fileflows.com/downloads/zip" 2>/dev/null \
| grep -i "content-disposition" \
| grep -oP '[\d]+\.[\d]+\.[\d]+\.[\d]+' \
| head -1)
if systemctl is-enabled fileflows-node &>/dev/null && ! systemctl is-enabled fileflows &>/dev/null; then
# Node-only installation: no local server API available
if [[ -n "$RELEASE" && "$CURRENT" == "$RELEASE" ]]; then
msg_ok "No update required. ${APP} Node is already at v${CURRENT}"
exit
fi
msg_info "Stopping Service"
systemctl --all stop 'fileflows*'
msg_info "Stopped Service"
systemctl stop fileflows-node
msg_ok "Stopped Service"
msg_info "Creating Backup"
ls /opt/*.tar.gz &>/dev/null && rm -f /opt/*.tar.gz
@@ -46,11 +57,32 @@ function update_script() {
fetch_and_deploy_from_url "https://fileflows.com/downloads/zip" "/opt/fileflows"
msg_info "Starting Service"
systemctl --all start 'fileflows*'
systemctl start fileflows-node
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at latest version"
# Server installation: use the local API
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 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"
fetch_and_deploy_from_url "https://fileflows.com/downloads/zip" "/opt/fileflows"
msg_info "Starting Service"
systemctl start fileflows*
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at latest version"
fi
fi
exit

View File

@@ -53,12 +53,6 @@ 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_info "Starting Service"

View File

@@ -47,7 +47,6 @@ function update_script() {
$STD npm run build
cd /opt/flatnotes
rm -f uv.lock
sed -i 's/^name = ""$/name = "flatnotes"/' pyproject.toml
$STD /usr/local/bin/uvx migrate-to-uv
$STD /usr/local/bin/uv sync
msg_ok "Updated Flatnotes"

View File

@@ -34,20 +34,8 @@ function update_script() {
systemctl stop glance
msg_ok "Stopped Service"
if [[ -f /opt/glance/glance.yml ]]; then
msg_info "Backing up glance.yml"
cp /opt/glance/glance.yml /tmp/glance.yml.bak
msg_ok "Backed up glance.yml"
fi
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "glance" "glanceapp/glance" "prebuild" "latest" "/opt/glance" "glance-linux-amd64.tar.gz"
if [[ -f /tmp/glance.yml.bak ]]; then
msg_info "Restoring glance.yml"
mv /tmp/glance.yml.bak /opt/glance/glance.yml
msg_ok "Restored glance.yml"
fi
msg_info "Starting Service"
systemctl start glance
msg_ok "Started Service"

View File

@@ -54,6 +54,7 @@ function update_script() {
[[ -f /opt/grist_bak/landing.db ]] && cp /opt/grist_bak/landing.db /opt/grist/landing.db
cd /opt/grist
$STD yarn install
$STD yarn run install:ee -y
$STD yarn run build:prod
$STD yarn run install:python
msg_ok "Updated Grist"

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ function update_script() {
msg_info "Creating backup"
[[ -f /opt/infisical_backup.sql ]] && rm -f /opt/infisical_backup.sql
DB_PASS=$(grep -Po '(?<=^Password:\s).*' ~/infisical.creds | head -n1)
DB_PASS=$(grep -Po '(?<=^Database Password:\s).*' ~/infisical.creds | head -n1)
PGPASSWORD=$DB_PASS pg_dump -U infisical -h localhost -d infisical_db > /opt/infisical_backup.sql
msg_ok "Created backup"

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/kanbn/kan
APP="Kan"
var_tags="${var_tags:-project-management;kanban}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-12}"
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/kan ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_tag "kan" "kanbn/kan"; then
msg_info "Stopping Service"
systemctl stop kan
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp /opt/kan/.env /opt/kan.env.bak
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_tag "kan" "kanbn/kan" "latest"
msg_info "Restoring Configuration"
cp /opt/kan.env.bak /opt/kan/.env
rm -f /opt/kan.env.bak
msg_ok "Restored Configuration"
msg_info "Building Application"
cd /opt/kan
set -a && source /opt/kan/.env && set +a
export NEXT_PUBLIC_USE_STANDALONE_OUTPUT=true
$STD pnpm install --ignore-scripts --prod=false
export CI=true
find /opt/kan/packages /opt/kan/apps -name 'tsconfig.json' -exec sed -i 's|"@kan/tsconfig/|"../../tooling/typescript/|g' {} +
$STD pnpm build --filter=@kan/web
unset NEXT_PUBLIC_USE_STANDALONE_OUTPUT CI
msg_ok "Built Application"
msg_info "Setting up Standalone"
mkdir -p /opt/kan/apps/web/.next/standalone/apps/web/.next/static
cp -r /opt/kan/apps/web/.next/static/* /opt/kan/apps/web/.next/standalone/apps/web/.next/static/
cp -r /opt/kan/apps/web/public /opt/kan/apps/web/.next/standalone/apps/web/public
msg_ok "Set up Standalone"
msg_info "Running Database Migrations"
cd /opt/kan/packages/db
$STD pnpm exec drizzle-kit migrate
msg_ok "Ran Database Migrations"
msg_info "Starting Service"
systemctl start kan
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"

View File

@@ -9,7 +9,7 @@ APP="karakeep"
var_tags="${var_tags:-bookmark}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-15}"
var_disk="${var_disk:-10}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-no}"

View File

@@ -53,7 +53,7 @@ function update_script() {
[[ -s /opt/koillection/.env.local && -n "$(tail -c 1 /opt/koillection/.env.local)" ]] && echo "" >>/opt/koillection/.env.local
echo 'APP_RUNTIME="Symfony\Component\Runtime\SymfonyRuntime"' >>/opt/koillection/.env.local
fi
NODE_VERSION="26" NODE_MODULE="yarn" setup_nodejs
export COMPOSER_ALLOW_SUPERUSER=1
export APP_RUNTIME='Symfony\Component\Runtime\SymfonyRuntime'
$STD composer install --no-dev -o --no-interaction --classmap-authoritative

View File

@@ -32,7 +32,6 @@ function update_script() {
if check_for_gh_release "kometa" "Kometa-Team/Kometa"; then
msg_info "Stopping Service"
systemctl stop kometa
[[ -d "/opt/kometa-quickstart" ]] && systemctl stop kometa-quickstart
msg_ok "Stopped Service"
msg_info "Backing up data"
@@ -43,7 +42,7 @@ function update_script() {
fetch_and_deploy_gh_release "kometa" "Kometa-Team/Kometa" "tarball"
msg_info "Updating Kometa"
cd /opt/kometa
cd /opt/kometa
$STD uv pip install -r requirements.txt --system
mkdir -p config/assets
cp /opt/config.yml config/config.yml
@@ -51,28 +50,9 @@ function update_script() {
msg_info "Starting Service"
systemctl start kometa
[[ -d "/opt/kometa-quickstart" ]] && systemctl start kometa-quickstart
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
if [[ -d "/opt/kometa-quickstart" ]] && check_for_gh_release "kometa-quickstart" "Kometa-Team/Quickstart"; then
msg_info "Stopping Quickstart Service"
systemctl stop kometa-quickstart
msg_ok "Stopped Quickstart Service"
fetch_and_deploy_gh_release "kometa-quickstart" "Kometa-Team/Quickstart" "tarball"
msg_info "Updating Kometa Quickstart"
cd /opt/kometa-quickstart
$STD uv pip install -r requirements.txt -p /opt/kometa-quickstart/.venv/bin/python
msg_ok "Updated Kometa Quickstart"
msg_info "Starting Quickstart Service"
systemctl start kometa-quickstart
msg_ok "Started Quickstart Service"
msg_ok "Updated Quickstart successfully!"
fi
exit
}

View File

@@ -54,13 +54,6 @@ function update_script() {
unset NODE_OPTIONS
msg_ok "Built Application"
msg_info "Setting Up Standalone"
cp -r /opt/lobehub/.next/static /opt/lobehub/.next/standalone/.next/static
cp -r /opt/lobehub/public /opt/lobehub/.next/standalone/public
cp -r /opt/lobehub/scripts/migrateServerDB/* /opt/lobehub/.next/standalone/
cp -r /opt/lobehub/packages/database/migrations /opt/lobehub/.next/standalone/migrations
msg_ok "Set Up Standalone"
msg_info "Running Database Migrations"
cd /opt/lobehub
set -a && source /opt/lobehub/.env && set +a

View File

@@ -40,6 +40,8 @@ function update_script() {
CURRENT_VERSION=$(grep -oP 'APP_VERSION=\K[^ ]+' /opt/manyfold/.env || echo "unknown")
cp -r /opt/manyfold/app/storage /opt/manyfold_storage_backup 2>/dev/null || true
cp -r /opt/manyfold/app/tmp /opt/manyfold_tmp_backup 2>/dev/null || true
cp /opt/manyfold/app/config/credentials.yml.enc /opt/manyfold_credentials.yml.enc 2>/dev/null || true
cp /opt/manyfold/app/config/master.key /opt/manyfold_master.key 2>/dev/null || true
$STD tar -czf "/opt/manyfold_${CURRENT_VERSION}_backup.tar.gz" -C /opt/manyfold app
msg_ok "Backed up Data"
@@ -55,12 +57,14 @@ function update_script() {
RUBY_VERSION=${RUBY_INSTALL_VERSION} RUBY_INSTALL_RAILS="true" HOME=/home/manyfold setup_ruby
msg_info "Restoring Data"
rm -rf /opt/manyfold/app/{storage,tmp}
rm -rf /opt/manyfold/app/{storage,tmp,config/credentials.yml.enc,config/master.key}
cp -r /opt/manyfold_storage_backup /opt/manyfold/app/storage 2>/dev/null || true
cp -r /opt/manyfold_tmp_backup /opt/manyfold/app/tmp 2>/dev/null || true
cp /opt/manyfold_credentials.yml.enc /opt/manyfold/app/config/credentials.yml.enc 2>/dev/null || true
cp /opt/manyfold_master.key /opt/manyfold/app/config/master.key 2>/dev/null || true
chown -R manyfold:manyfold {/home/manyfold,/opt/manyfold}
chown -R manyfold:manyfold /opt/manyfold/app/storage /opt/manyfold/app/tmp /opt/manyfold/app/config
rm -rf /opt/manyfold_storage_backup /opt/manyfold_tmp_backup
rm -rf /opt/manyfold_storage_backup /opt/manyfold_tmp_backup /opt/manyfold_credentials.yml.enc /opt/manyfold_master.key
msg_ok "Restored Data"
msg_info "Installing Manyfold"
@@ -76,8 +80,6 @@ function update_script() {
bundle install
corepack prepare '"$YARN_VERSION"' --activate
corepack use '"$YARN_VERSION"'
rm -f config/credentials.yml.enc config/master.key
EDITOR=/bin/true bin/rails credentials:edit
bin/rails db:migrate
bin/rails assets:precompile
'

View File

@@ -42,7 +42,7 @@ function update_script() {
PYTHON_VERSION="3.13" setup_uv
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "musicseerr" "HabiRabbu/Musicseerr" "tarball"
NODE_VERSION="25" NODE_MODULE="pnpm@10.33.0" setup_nodejs
NODE_VERSION="22" NODE_MODULE="pnpm@10.33.0" setup_nodejs
msg_info "Building Frontend"
cd /opt/musicseerr/frontend

View File

@@ -216,12 +216,6 @@ EOF
msg_ok "Initialized Backend"
msg_info "Starting Services"
CERTBOT_VER=$(/opt/certbot/bin/certbot --version 2>&1 | awk '{print $NF}')
if grep -q "Environment=CERTBOT_VERSION" /lib/systemd/system/npm.service; then
sed -i "s|Environment=CERTBOT_VERSION=.*|Environment=CERTBOT_VERSION=${CERTBOT_VER}|" /lib/systemd/system/npm.service
else
sed -i "/Environment=NODE_ENV=production/a Environment=CERTBOT_VERSION=${CERTBOT_VER}" /lib/systemd/system/npm.service
fi
sed -i 's/user npm/user root/g; s/^pid/#pid/g' /usr/local/openresty/nginx/conf/nginx.conf
sed -r -i 's/^([[:space:]]*)su npm npm/\1#su npm npm/g;' /etc/logrotate.d/nginx-proxy-manager
systemctl daemon-reload

View File

@@ -70,27 +70,6 @@ function update_script() {
$STD ninja install
msg_ok "Rebuilt OpenThread Border Router"
if ! grep -q "net.ipv6.conf.all.accept_ra=2" /etc/sysctl.d/99-otbr.conf; then
msg_info "Configuring Network"
cat <<EOF >/etc/sysctl.d/99-otbr.conf
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.accept_ra=2
net.ipv6.conf.all.accept_ra_rtr_pref=1
net.ipv6.conf.all.accept_ra_rt_info_max_plen=64
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.default.accept_ra=2
net.ipv6.conf.default.accept_ra_rtr_pref=1
net.ipv6.conf.default.accept_ra_rt_info_max_plen=64
net.ipv6.conf.eth0.forwarding=1
net.ipv6.conf.eth0.accept_ra=2
net.ipv6.conf.eth0.accept_ra_rtr_pref=1
net.ipv6.conf.eth0.accept_ra_rt_info_max_plen=64
net.ipv4.ip_forward=1
EOF
$STD sysctl -p /etc/sysctl.d/99-otbr.conf
msg_ok "Configured Network"
fi
msg_info "Starting Services"
systemctl start otbr-agent
systemctl start otbr-web

View File

@@ -30,7 +30,7 @@ function update_script() {
exit
fi
NODE_VERSION="24" setup_nodejs
NODE_VERSION="22" setup_nodejs
if check_for_gh_release "soulsync" "Nezreka/SoulSync"; then
msg_info "Stopping Service"

View File

@@ -81,7 +81,7 @@ EOF
msg_ok "Updated Sure"
msg_info "Starting Services"
systemctl start sure sure-worker
$STD systemctl start sure sure-worker
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi

View File

@@ -44,7 +44,8 @@ function update_script() {
msg_info "Installing Python Dependencies"
cd /opt/yamtrack
$STD uv sync --locked
$STD uv venv --clear .venv
$STD uv pip install --no-cache-dir -r requirements.txt
msg_ok "Installed Python Dependencies"
msg_info "Restoring Data"

View File

@@ -52,9 +52,6 @@ After=network-online.target
Type=simple
WorkingDirectory=/opt/apprise
ExecStart=/opt/apprise/webapp/supervisord-startup
Environment=APPRISE_CONFIG_DIR=/config
Environment=APPRISE_ATTACH_DIR=/attach
Environment=APPRISE_PLUGIN_PATHS=/plugin
Restart=always
RestartSec=30

View File

@@ -17,7 +17,7 @@ msg_info "Installing Dependencies"
$STD apt install -y build-essential
msg_ok "Installed Dependencies"
NODE_VERSION="26" setup_nodejs
NODE_VERSION="24" setup_nodejs
msg_info "Setup Cross-Seed"
$STD npm install cross-seed@latest -g

View File

@@ -16,8 +16,7 @@ update_os
msg_info "Installing Dependencies"
$STD apt install -y \
git \
unzip \
valkey
unzip
msg_ok "Installed Dependencies"
msg_info "Installing Bun"
@@ -39,9 +38,6 @@ DEGOOG_PLUGINS_DIR=/opt/degoog/data/plugins
DEGOOG_THEMES_DIR=/opt/degoog/data/themes
DEGOOG_ALIASES_FILE=/opt/degoog/data/aliases.json
DEGOOG_PLUGIN_SETTINGS_FILE=/opt/degoog/data/plugin-settings.json
DEGOOG_VALKEY_URL=redis://valkey:6379
DEGOOG_CACHE_MAX_ENTRIES=1000
DEGOOG_CACHE_TTL_MS=43200000
# DEGOOG_SETTINGS_PASSWORDS=changeme
# DEGOOG_PUBLIC_INSTANCE=false
# LOGGER=debug
@@ -66,16 +62,11 @@ EOF
fi
msg_ok "Set up degoog"
msg_info "Starting Valkey Service"
systemctl enable -q --now valkey-server
msg_ok "Started Valkey Service"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/degoog.service
[Unit]
Description=degoog
After=network.target valkey-server.service
Wants=valkey-server.service
After=network.target
[Service]
Type=simple

View File

@@ -121,7 +121,6 @@ server {
# All other requests proxy to uWSGI
location / {
include proxy_params;
proxy_set_header X-Forwarded-Port \$server_port;
proxy_pass http://127.0.0.1:5656;
}
}

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/Panonim/dynacat
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
fetch_and_deploy_gh_release "dynacat" "Panonim/dynacat" "prebuild" "latest" "/opt/dynacat" "dynacat-linux-amd64.tar.gz"
msg_info "Setting up Dynacat"
mkdir -p /opt/dynacat/config /opt/dynacat/assets /opt/dynacat/data
chmod +x /opt/dynacat/dynacat
cat <<EOF >/opt/dynacat/config/dynacat.yml
server:
host: 0.0.0.0
port: 8080
assets-path: /opt/dynacat/assets
db-path: /opt/dynacat/data/dynacat.db
pages:
- name: Home
columns:
- size: small
widgets:
- type: calendar
- type: clock
- size: full
widgets:
- type: search
search-engine: duckduckgo
- type: monitor
title: Services
sites:
- title: Dynacat
url: http://127.0.0.1:8080
update-interval: 5m
EOF
msg_ok "Set up Dynacat"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/dynacat.service
[Unit]
Description=Dynacat Dashboard
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/dynacat
ExecStart=/opt/dynacat/dynacat -config /opt/dynacat/config/dynacat.yml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now dynacat
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -37,13 +37,7 @@ msg_ok "Configured Firefly III"
msg_info "Configuring Data Importer"
cp /opt/firefly/dataimporter/.env.example /opt/firefly/dataimporter/.env
sed -i \
-e "s#FIREFLY_III_URL=#FIREFLY_III_URL=http://${LOCAL_IP}#g" \
-e "s|^APP_URL=.*|APP_URL=http://${LOCAL_IP}/dataimporter|" \
-e "s|^ASSET_URL=.*|ASSET_URL=/dataimporter|" \
/opt/firefly/dataimporter/.env
cd /opt/firefly/dataimporter
$STD php artisan config:clear
sed -i "s#FIREFLY_III_URL=#FIREFLY_III_URL=http://${LOCAL_IP}#g" /opt/firefly/dataimporter/.env
chown -R www-data:www-data /opt/firefly
msg_ok "Configured Data Importer"

View File

@@ -19,7 +19,6 @@ NODE_VERSION="22" setup_nodejs
msg_info "Setting up Flatnotes"
cd /opt/flatnotes
sed -i 's/^name = ""$/name = "flatnotes"/' pyproject.toml
$STD /usr/local/bin/uvx migrate-to-uv
$STD /usr/local/bin/uv sync
mkdir -p /opt/flatnotes/data

View File

@@ -28,6 +28,7 @@ export CYPRESS_INSTALL_BINARY=0
export NODE_OPTIONS="--max-old-space-size=2048"
cd /opt/grist
$STD yarn install
$STD yarn run install:ee -y
$STD yarn run build:prod
$STD yarn run install:python
cat <<EOF >/opt/grist/.env

View File

@@ -1,90 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/kanbn/kan
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
msg_ok "Installed Dependencies"
PG_VERSION="16" setup_postgresql
PG_DB_NAME="kan" PG_DB_USER="kan" setup_postgresql_db
NODE_VERSION="20" NODE_MODULE="pnpm" setup_nodejs
fetch_and_deploy_gh_tag "kan" "kanbn/kan" "latest"
msg_info "Configuring Application"
AUTH_SECRET=$(openssl rand -base64 32)
cat <<EOF >/opt/kan/.env
NEXT_PUBLIC_BASE_URL=http://${LOCAL_IP}:3000
BETTER_AUTH_SECRET=${AUTH_SECRET}
BETTER_AUTH_TRUSTED_ORIGINS=http://${LOCAL_IP}:3000
POSTGRES_URL=postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}
NEXT_PUBLIC_ALLOW_CREDENTIALS=true
TRELLO_APP_API_KEY=
TRELLO_APP_API_SECRET=
HOSTNAME=0.0.0.0
PORT=3000
NODE_ENV=production
EOF
msg_ok "Configured Application"
msg_info "Building Application"
cd /opt/kan
set -a && source /opt/kan/.env && set +a
export NEXT_PUBLIC_USE_STANDALONE_OUTPUT=true NEXT_PUBLIC_BASE_URL BETTER_AUTH_TRUSTED_ORIGINS NEXT_PUBLIC_ALLOW_CREDENTIALS BETTER_AUTH_SECRET
$STD pnpm install --ignore-scripts --prod=false
export CI=true
find /opt/kan/packages /opt/kan/apps -name 'tsconfig.json' -exec sed -i 's|"@kan/tsconfig/|"../../tooling/typescript/|g' {} +
$STD pnpm build --filter=@kan/web
unset NEXT_PUBLIC_USE_STANDALONE_OUTPUT CI
msg_ok "Built Application"
msg_info "Setting up Standalone"
mkdir -p /opt/kan/apps/web/.next/standalone/apps/web/.next/static
cp -r /opt/kan/apps/web/.next/static/* /opt/kan/apps/web/.next/standalone/apps/web/.next/static/
cp -r /opt/kan/apps/web/public /opt/kan/apps/web/.next/standalone/apps/web/public
msg_ok "Set up Standalone"
msg_info "Running Database Migrations"
cd /opt/kan/packages/db
POSTGRES_URL="postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}" $STD pnpm exec drizzle-kit migrate
msg_ok "Ran Database Migrations"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/kan.service
[Unit]
Description=Kan Board
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kan/apps/web/.next/standalone
EnvironmentFile=/opt/kan/.env
ExecStart=/usr/bin/node apps/web/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now kan
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -56,20 +56,20 @@ msg_ok "Installed Python Dependencies"
msg_info "Downloading Essentia ML Models"
mkdir -p /opt/kima-hub/models
cd /opt/kima-hub/models
curl_with_retry "https://essentia.upf.edu/models/autotagging/msd/msd-musicnn-1.pb" "msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_happy/mood_happy-msd-musicnn-1.pb" "mood_happy-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_sad/mood_sad-msd-musicnn-1.pb" "mood_sad-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_relaxed/mood_relaxed-msd-musicnn-1.pb" "mood_relaxed-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_aggressive/mood_aggressive-msd-musicnn-1.pb" "mood_aggressive-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_party/mood_party-msd-musicnn-1.pb" "mood_party-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_acoustic/mood_acoustic-msd-musicnn-1.pb" "mood_acoustic-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/mood_electronic/mood_electronic-msd-musicnn-1.pb" "mood_electronic-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/danceability/danceability-msd-musicnn-1.pb" "danceability-msd-musicnn-1.pb"
curl_with_retry "https://essentia.upf.edu/models/classification-heads/voice_instrumental/voice_instrumental-msd-musicnn-1.pb" "voice_instrumental-msd-musicnn-1.pb"
curl -fsSL -o msd-musicnn-1.pb "https://essentia.upf.edu/models/autotagging/msd/msd-musicnn-1.pb"
curl -fsSL -o mood_happy-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_happy/mood_happy-msd-musicnn-1.pb"
curl -fsSL -o mood_sad-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_sad/mood_sad-msd-musicnn-1.pb"
curl -fsSL -o mood_relaxed-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_relaxed/mood_relaxed-msd-musicnn-1.pb"
curl -fsSL -o mood_aggressive-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_aggressive/mood_aggressive-msd-musicnn-1.pb"
curl -fsSL -o mood_party-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_party/mood_party-msd-musicnn-1.pb"
curl -fsSL -o mood_acoustic-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_acoustic/mood_acoustic-msd-musicnn-1.pb"
curl -fsSL -o mood_electronic-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/mood_electronic/mood_electronic-msd-musicnn-1.pb"
curl -fsSL -o danceability-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/danceability/danceability-msd-musicnn-1.pb"
curl -fsSL -o voice_instrumental-msd-musicnn-1.pb "https://essentia.upf.edu/models/classification-heads/voice_instrumental/voice_instrumental-msd-musicnn-1.pb"
msg_ok "Downloaded Essentia ML Models"
msg_info "Downloading CLAP Model"
CURL_TIMEOUT=600 curl_with_retry "https://huggingface.co/lukewys/laion_clap/resolve/main/music_audioset_epoch_15_esc_90.14.pt" "/opt/kima-hub/models/music_audioset_epoch_15_esc_90.14.pt"
curl -fsSL -o /opt/kima-hub/models/music_audioset_epoch_15_esc_90.14.pt "https://huggingface.co/lukewys/laion_clap/resolve/main/music_audioset_epoch_15_esc_90.14.pt"
msg_ok "Downloaded CLAP Model"
msg_info "Building Backend"

View File

@@ -13,7 +13,7 @@ setting_up_container
network_check
update_os
NODE_VERSION="26" NODE_MODULE="yarn" setup_nodejs
NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs
PG_VERSION="16" setup_postgresql
PHP_VERSION="8.5" PHP_APACHE="YES" setup_php
setup_composer

View File

@@ -15,7 +15,7 @@ update_os
PYTHON_VERSION="3.13" setup_uv
fetch_and_deploy_gh_release "musicseerr" "HabiRabbu/Musicseerr" "tarball"
NODE_VERSION="25" NODE_MODULE="pnpm@10.33.0" setup_nodejs
NODE_VERSION="22" NODE_MODULE="pnpm@10.33.0" setup_nodejs
msg_info "Building Frontend"
cd /opt/musicseerr/frontend

View File

@@ -160,8 +160,7 @@ $STD yarn install --network-timeout 600000
msg_ok "Initialized Backend"
msg_info "Creating Service"
CERTBOT_VER=$(/opt/certbot/bin/certbot --version 2>&1 | awk '{print $NF}')
cat <<EOF >/lib/systemd/system/npm.service
cat <<'EOF' >/lib/systemd/system/npm.service
[Unit]
Description=Nginx Proxy Manager
After=network.target
@@ -170,7 +169,6 @@ Wants=openresty.service
[Service]
Type=simple
Environment=NODE_ENV=production
Environment=CERTBOT_VERSION=${CERTBOT_VER}
ExecStartPre=-mkdir -p /tmp/nginx/body /data/letsencrypt-acme-challenge
ExecStart=/usr/bin/node index.js --abort_on_uncaught_exception --max_old_space_size=250
WorkingDirectory=/app

View File

@@ -46,7 +46,7 @@ sed -i "s|^ENCRYPTION_KEY=.*|ENCRYPTION_KEY=$SECRET_KEY|g" /opt/openarchiver/.en
sed -i "s|^TIKA_URL=.*|TIKA_URL=|g" /opt/openarchiver/.env
sed -i "s|^ORIGIN=.*|ORIGIN=http://$LOCAL_IP:3000|g" /opt/openarchiver/.env
$STD pnpm install --shamefully-hoist --frozen-lockfile --prod=false
$STD pnpm approve-builds
$STD pnpm approve-builds --yes
$STD pnpm run build:oss
$STD pnpm db:migrate
msg_ok "Setup Open Archiver"

View File

@@ -67,17 +67,6 @@ msg_ok "Built OpenThread Border Router"
msg_info "Configuring Network"
cat <<EOF >/etc/sysctl.d/99-otbr.conf
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.all.accept_ra=2
net.ipv6.conf.all.accept_ra_rtr_pref=1
net.ipv6.conf.all.accept_ra_rt_info_max_plen=64
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.default.accept_ra=2
net.ipv6.conf.default.accept_ra_rtr_pref=1
net.ipv6.conf.default.accept_ra_rt_info_max_plen=64
net.ipv6.conf.eth0.forwarding=1
net.ipv6.conf.eth0.accept_ra=2
net.ipv6.conf.eth0.accept_ra_rtr_pref=1
net.ipv6.conf.eth0.accept_ra_rt_info_max_plen=64
net.ipv4.ip_forward=1
EOF
$STD sysctl -p /etc/sysctl.d/99-otbr.conf

View File

@@ -265,7 +265,6 @@ EOF
sed -i "s|alias /var/lib/romm/library/;|alias ${ROMM_BASE}/library/;|" /etc/nginx/sites-available/romm
rm -f /etc/nginx/sites-enabled/default
rm -f /etc/nginx/conf.d/default.conf
ln -sf /etc/nginx/sites-available/romm /etc/nginx/sites-enabled/romm
systemctl restart nginx
systemctl enable -q --now nginx

View File

@@ -23,7 +23,7 @@ $STD apt install -y \
msg_ok "Installed Dependencies"
UV_PYTHON="3.11" setup_uv
NODE_VERSION="24" setup_nodejs
NODE_VERSION="22" setup_nodejs
fetch_and_deploy_gh_release "soulsync" "Nezreka/SoulSync" "tarball"

View File

@@ -103,7 +103,7 @@ StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now sure sure-worker
$STD systemctl enable -q --now sure sure-worker
msg_ok "Created Services"
motd_ssh

View File

@@ -41,27 +41,29 @@ cat <<EOF >/etc/caddy/Caddyfile
}
EOF
usermod -aG www-data caddy
systemctl enable -q --now php${PHP_VER}-fpm
systemctl restart caddy
msg_ok "Configured Caddy"
systemctl enable -q --now php${PHP_VER}-fpm
systemctl restart caddy
msg_info "Automating Webtrees Setup"
cd /opt/webtrees
sleep 5
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 \
--dbport=3306 \
--dbuser=webtrees \
--dbpass="${MARIADB_DB_PASS}" \
--dbname=webtrees \
--tblpfx=wt_ \
--base-url="http://${LOCAL_IP}"
$STD sudo -u www-data php /opt/webtrees/index.php user Admin \
--create \
--real-name="Administrator" \
--email="admin@example.com" \
--password="${WT_ADMIN_PASS}"
$STD sudo -u www-data php /opt/webtrees/index.php user-setting Admin canadmin 1
curl -sS -X POST "http://127.0.0.1/" \
-d "step=6" \
--data-urlencode "baseurl=http://${LOCAL_IP}" \
-d "lang=en-US" \
-d "dbtype=mysql" \
-d "dbhost=127.0.0.1" \
-d "dbport=3306" \
-d "dbuser=webtrees" \
--data-urlencode "dbpass=${MARIADB_DB_PASS}" \
-d "dbname=webtrees" \
-d "tblpfx=wt_" \
-d "wtname=Administrator" \
-d "wtuser=Admin" \
--data-urlencode "wtpass=${WT_ADMIN_PASS}" \
-d "wtemail=admin@example.com" >/dev/null
cat <<EOF >>~/webtrees.creds

View File

@@ -27,7 +27,8 @@ fetch_and_deploy_gh_release "yamtrack" "FuzzyGrim/Yamtrack" "tarball"
msg_info "Installing Python Dependencies"
cd /opt/yamtrack
$STD uv sync --locked
$STD uv venv .venv
$STD uv pip install --no-cache-dir -r requirements.txt
msg_ok "Installed Python Dependencies"
msg_info "Configuring Yamtrack"

View File

@@ -8227,13 +8227,11 @@ setup_ruby() {
#
# Variables:
# RUST_TOOLCHAIN - Rust toolchain to install (default: stable)
# RUST_PROFILE - Rust installation profile (default: default, e.g. minimal)
# RUST_CRATES - Comma-separated list of crates (e.g. "cargo-edit,wasm-pack@0.12.1")
# ------------------------------------------------------------------------------
setup_rust() {
local RUST_TOOLCHAIN="${RUST_TOOLCHAIN:-stable}"
local RUST_PROFILE="${RUST_PROFILE:-default}"
local RUST_CRATES="${RUST_CRATES:-}"
local CARGO_BIN="${HOME}/.cargo/bin"
@@ -8245,8 +8243,8 @@ setup_rust() {
# Scenario 1: Rustup not installed - fresh install
if ! command -v rustup &>/dev/null; then
msg_info "Setup Rust ($RUST_TOOLCHAIN, profile: $RUST_PROFILE)"
curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --profile "$RUST_PROFILE" --default-toolchain "$RUST_TOOLCHAIN" || {
msg_info "Setup Rust ($RUST_TOOLCHAIN)"
curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" || {
msg_error "Failed to install Rust"
msg_error "Hint: Check connectivity to sh.rustup.rs and static.rust-lang.org"
return 7

View File

@@ -50,19 +50,6 @@ start_routines() {
no) msg_error "Selected no to Backup" ;;
esac
# --- Update Current PBS 3 System ---
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS 3 UPDATE" --menu \
"\nUpdate current PBS 3 (Bookworm) packages before upgrade?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3)
case $CHOICE in
yes)
msg_info "Updating current PBS 3 packages"
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade
msg_ok "Updated current PBS 3 packages"
;;
no) msg_error "Skipped updating current packages" ;;
esac
# --- Debian 13 Sources ---
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "PBS 4 SOURCES" --menu \
"Switch to Debian 13 (Trixie) sources for PBS 4?" 14 58 2 "yes" " " "no" " " 3>&2 2>&1 1>&3)