mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-06-02 21:59:35 +02:00
Compare commits
67 Commits
fix/webtre
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32597584e2 | ||
|
|
131d521052 | ||
|
|
f679553c0f | ||
|
|
6131060b19 | ||
|
|
b978bd3499 | ||
|
|
23f6b8a158 | ||
|
|
e77461942d | ||
|
|
ef65fb396a | ||
|
|
6410586e2e | ||
|
|
0c4e5b5a63 | ||
|
|
354ceef128 | ||
|
|
1a6dbb0bf8 | ||
|
|
f14eca3bc9 | ||
|
|
d4b4880e0d | ||
|
|
9dc08aa8c1 | ||
|
|
06af8cca46 | ||
|
|
5668ad9a8d | ||
|
|
bd9bae075d | ||
|
|
7cdb6c8133 | ||
|
|
6315547e65 | ||
|
|
e9a9bf17ee | ||
|
|
40b86bef63 | ||
|
|
a43ca27d2f | ||
|
|
ee06ac1819 | ||
|
|
1a63343a17 | ||
|
|
cd8a6f1de0 | ||
|
|
da64475612 | ||
|
|
a2aea34aa7 | ||
|
|
839fba1e0c | ||
|
|
83398645ca | ||
|
|
06cf2ec50d | ||
|
|
bdc968e86d | ||
|
|
0b7d64b240 | ||
|
|
e013403db1 | ||
|
|
465996653a | ||
|
|
6a7391f430 | ||
|
|
aed34659c9 | ||
|
|
cd4a3e854e | ||
|
|
be46da731a | ||
|
|
58179050a8 | ||
|
|
955cb97a3b | ||
|
|
04e618798e | ||
|
|
4c358450d8 | ||
|
|
fa238ddbdd | ||
|
|
0fe653f8de | ||
|
|
278e8315f3 | ||
|
|
ded4bba04d | ||
|
|
5b6f8e2674 | ||
|
|
162cb9b887 | ||
|
|
5776f3fef5 | ||
|
|
26377b7a7f | ||
|
|
151cd6581f | ||
|
|
69641d322d | ||
|
|
6608deeb92 | ||
|
|
de7c00e285 | ||
|
|
eb74ba2edb | ||
|
|
203131d042 | ||
|
|
44db73c58d | ||
|
|
d074d3f292 | ||
|
|
65f3a23cff | ||
|
|
4da4a47eb7 | ||
|
|
cf3d8f902c | ||
|
|
8c0333151b | ||
|
|
dc355d07bf | ||
|
|
4e51373669 | ||
|
|
256020b847 | ||
|
|
19cd2b3dad |
129
.github/changelogs/2026/05.md
generated
vendored
129
.github/changelogs/2026/05.md
generated
vendored
@@ -1,3 +1,132 @@
|
||||
## 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
|
||||
|
||||
18
.github/workflows/check-node-versions.yml
generated
vendored
18
.github/workflows/check-node-versions.yml
generated
vendored
@@ -336,14 +336,18 @@ 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
|
||||
# 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)"
|
||||
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
|
||||
else
|
||||
status="🔸 Drift → upstream=$upstream_major ($upstream_hint)"
|
||||
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
|
||||
drift_count=$((drift_count + 1))
|
||||
status="✅ Ahead of upstream ($upstream_major via $upstream_hint)"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
71
.github/workflows/close-new-script-prs.yml
generated
vendored
71
.github/workflows/close-new-script-prs.yml
generated
vendored
@@ -3,7 +3,7 @@ name: Close Unauthorized New Script PRs
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: ["main"]
|
||||
types: [opened, labeled]
|
||||
types: [opened, labeled, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
check-new-script:
|
||||
@@ -24,13 +24,6 @@ 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]",
|
||||
@@ -42,38 +35,40 @@ jobs:
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Check if author is a member of the contributor team ---
|
||||
const teamSlug = "contributor";
|
||||
let isMember = false;
|
||||
|
||||
try {
|
||||
const { status } = await github.rest.teams.getMembershipForUserInOrg({
|
||||
org: owner,
|
||||
team_slug: teamSlug,
|
||||
username: author,
|
||||
});
|
||||
// status 200 means the user is a member (active or pending)
|
||||
isMember = true;
|
||||
} catch (error) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
// --- 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;
|
||||
}
|
||||
|
||||
if (isMember) {
|
||||
core.info(`PR #${prNumber} by contributor "${author}" — skipping.`);
|
||||
// --- 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,
|
||||
});
|
||||
hasAddedScriptFile = files.some(
|
||||
f => f.status === "added" && scriptPrefixes.some(p => f.filename.startsWith(p))
|
||||
);
|
||||
} catch (error) {
|
||||
core.warning(`Could not list files for PR #${prNumber}: ${error.message}`);
|
||||
}
|
||||
|
||||
if (!hasNewScriptLabel && !hasAddedScriptFile) {
|
||||
core.info(`PR #${prNumber} is not a new-script submission (no label, no added script file) — skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
71
.github/workflows/close_issue_in_dev.yaml
generated
vendored
71
.github/workflows/close_issue_in_dev.yaml
generated
vendored
@@ -56,46 +56,57 @@ jobs:
|
||||
echo "$slugs" > pocketbase_slugs.txt
|
||||
echo "count=$(echo $slugs | wc -w)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Search for Issues with Similar Titles
|
||||
- name: Find matching issues in ProxmoxVED by slug
|
||||
id: find_issue
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
issues=$(gh issue list --repo community-scripts/ProxmoxVED --json number,title --jq '.[] | {number, title}')
|
||||
|
||||
best_match_score=0
|
||||
best_match_number=0
|
||||
|
||||
for issue in $(echo "$issues" | jq -r '. | @base64'); do
|
||||
_jq() {
|
||||
echo ${issue} | base64 --decode | jq -r ${1}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if [ "$best_match_number" != "0" ]; then
|
||||
echo "issue_number=$best_match_number" >> $GITHUB_ENV
|
||||
else
|
||||
echo "No matching issue found."
|
||||
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)
|
||||
|
||||
- name: Comment on the Best-Matching Issue and Close It
|
||||
if: env.issue_number != ''
|
||||
# Normalize: lowercase, strip spaces and hyphens (same shape as the slug derivation)
|
||||
norm() { echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[[:space:]]//g; s/-//g'; }
|
||||
|
||||
issues=$(gh issue list --repo community-scripts/ProxmoxVED --state open --limit 1000 --json number,title,body)
|
||||
|
||||
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 '.[]')
|
||||
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"
|
||||
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 != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PAT_MICHEL }}
|
||||
run: |
|
||||
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
|
||||
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
|
||||
|
||||
- name: Set is_dev to false in PocketBase
|
||||
if: steps.get_slugs.outputs.count != '0'
|
||||
|
||||
4
.github/workflows/lock-issue.yaml
generated
vendored
4
.github/workflows/lock-issue.yaml
generated
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const daysBeforeLock = 3;
|
||||
const daysBeforeLock = 7;
|
||||
const lockDate = new Date();
|
||||
lockDate.setDate(lockDate.getDate() - daysBeforeLock);
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
/dependabot/i
|
||||
];
|
||||
|
||||
// Search for closed, unlocked issues older than 3 days (paginated, oldest first)
|
||||
// Search for closed, unlocked issues older than 7 days (paginated, oldest first)
|
||||
let page = 1;
|
||||
let totalLocked = 0;
|
||||
|
||||
|
||||
610
.github/workflows/pocketbase-ai-bot.yml
generated
vendored
Normal file
610
.github/workflows/pocketbase-ai-bot.yml
generated
vendored
Normal file
@@ -0,0 +1,610 @@
|
||||
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
24
.github/workflows/pocketbase-bot.yml
generated
vendored
@@ -13,8 +13,8 @@ jobs:
|
||||
pocketbase-bot:
|
||||
runs-on: self-hosted
|
||||
|
||||
# Only act on /pocketbase commands
|
||||
if: startsWith(github.event.comment.body, '/pocketbase')
|
||||
# Act on comments that contain a /pocketbase command line (precise line check happens in-script)
|
||||
if: contains(github.event.comment.body, '/pocketbase')
|
||||
|
||||
steps:
|
||||
- name: Execute PocketBase bot command
|
||||
@@ -257,6 +257,22 @@ 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') {
|
||||
@@ -272,10 +288,6 @@ 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]*?)```/);
|
||||
|
||||
226
CHANGELOG.md
226
CHANGELOG.md
@@ -56,6 +56,9 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +72,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
|
||||
<details>
|
||||
<summary><h4>May (23 entries)</h4></summary>
|
||||
<summary><h4>May (30 entries)</h4></summary>
|
||||
|
||||
[View May 2026 Changelog](.github/changelogs/2026/05.md)
|
||||
|
||||
@@ -467,6 +470,87 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-06-02
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- DDNS-Updater ([#14883](https://github.com/community-scripts/ProxmoxVE/pull/14883))
|
||||
- InvoiceShelf ([#14882](https://github.com/community-scripts/ProxmoxVE/pull/14882))
|
||||
- Certimate ([#14881](https://github.com/community-scripts/ProxmoxVE/pull/14881))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- OpenThread-BR: preserve config during update [@tomfrenzel](https://github.com/tomfrenzel) ([#14893](https://github.com/community-scripts/ProxmoxVE/pull/14893))
|
||||
- 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
|
||||
@@ -1020,142 +1104,4 @@ 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))
|
||||
|
||||
## 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))
|
||||
- Mail-Archiver: update dependencies [@tremor021](https://github.com/tremor021) ([#14152](https://github.com/community-scripts/ProxmoxVE/pull/14152))
|
||||
64
ct/certimate.sh
Normal file
64
ct/certimate.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://certimate.me/
|
||||
|
||||
APP="Certimate"
|
||||
var_tags="${var_tags:-ssl;certificates;acme;automation}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-256}"
|
||||
var_disk="${var_disk:-2}"
|
||||
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/certimate/certimate ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "certimate" "certimate-go/certimate"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop certimate
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/certimate/pb_data /opt/certimate_pb_data_backup
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
fetch_and_deploy_gh_release "certimate" "certimate-go/certimate" "prebuild" "latest" "/opt/certimate" "certimate_*_linux_amd64.zip"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/certimate_pb_data_backup/. /opt/certimate/pb_data
|
||||
rm -rf /opt/certimate_pb_data_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start certimate
|
||||
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}:8090${CL}"
|
||||
@@ -25,7 +25,7 @@ function update_script() {
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
NODE_VERSION="26" setup_nodejs
|
||||
ensure_dependencies build-essential
|
||||
|
||||
if command -v cross-seed &>/dev/null; then
|
||||
|
||||
62
ct/ddns-updater.sh
Normal file
62
ct/ddns-updater.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/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: reptil1990
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/qdm12/ddns-updater
|
||||
|
||||
APP="DDNS-Updater"
|
||||
var_tags="${var_tags:-network}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-512}"
|
||||
var_disk="${var_disk:-2}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-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/ddns-updater ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
if check_for_gh_release "ddns-updater" "qdm12/ddns-updater"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop ddns-updater
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/ddns-updater/data /opt/ddns-updater_data_backup
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "ddns-updater" "qdm12/ddns-updater" "singlefile" "latest" "/opt/ddns-updater" "ddns-updater_*_linux_amd64"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/ddns-updater_data_backup/. /opt/ddns-updater/data/
|
||||
rm -rf /opt/ddns-updater_data_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start ddns-updater
|
||||
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}:8000${CL}"
|
||||
10
ct/degoog.sh
10
ct/degoog.sh
@@ -49,11 +49,21 @@ 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"
|
||||
|
||||
@@ -34,7 +34,7 @@ function update_script() {
|
||||
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*
|
||||
systemctl --all stop 'fileflows*'
|
||||
msg_info "Stopped Service"
|
||||
|
||||
msg_info "Creating Backup"
|
||||
@@ -46,7 +46,7 @@ function update_script() {
|
||||
fetch_and_deploy_from_url "https://fileflows.com/downloads/zip" "/opt/fileflows"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start fileflows*
|
||||
systemctl --all start 'fileflows*'
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
|
||||
@@ -53,6 +53,12 @@ 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"
|
||||
|
||||
@@ -47,6 +47,7 @@ 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"
|
||||
|
||||
12
ct/glance.sh
12
ct/glance.sh
@@ -34,8 +34,20 @@ 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"
|
||||
|
||||
6
ct/headers/certimate
Normal file
6
ct/headers/certimate
Normal file
@@ -0,0 +1,6 @@
|
||||
______ __ _ __
|
||||
/ ____/__ _____/ /_(_)___ ___ ____ _/ /____
|
||||
/ / / _ \/ ___/ __/ / __ `__ \/ __ `/ __/ _ \
|
||||
/ /___/ __/ / / /_/ / / / / / / /_/ / /_/ __/
|
||||
\____/\___/_/ \__/_/_/ /_/ /_/\__,_/\__/\___/
|
||||
|
||||
6
ct/headers/ddns-updater
Normal file
6
ct/headers/ddns-updater
Normal file
@@ -0,0 +1,6 @@
|
||||
____ ____ _ _______ __ __ __ __
|
||||
/ __ \/ __ \/ | / / ___/ / / / /___ ____/ /___ _/ /____ _____
|
||||
/ / / / / / / |/ /\__ \______/ / / / __ \/ __ / __ `/ __/ _ \/ ___/
|
||||
/ /_/ / /_/ / /| /___/ /_____/ /_/ / /_/ / /_/ / /_/ / /_/ __/ /
|
||||
/_____/_____/_/ |_//____/ \____/ .___/\__,_/\__,_/\__/\___/_/
|
||||
/_/
|
||||
6
ct/headers/invoiceshelf
Normal file
6
ct/headers/invoiceshelf
Normal file
@@ -0,0 +1,6 @@
|
||||
____ _ _____ __ ______
|
||||
/ _/___ _ ______ (_)_______ / ___// /_ ___ / / __/
|
||||
/ // __ \ | / / __ \/ / ___/ _ \\__ \/ __ \/ _ \/ / /_
|
||||
_/ // / / / |/ / /_/ / / /__/ __/__/ / / / / __/ / __/
|
||||
/___/_/ /_/|___/\____/_/\___/\___/____/_/ /_/\___/_/_/
|
||||
|
||||
@@ -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 '(?<=^Database Password:\s).*' ~/infisical.creds | head -n1)
|
||||
DB_PASS=$(grep -Po '(?<=^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"
|
||||
|
||||
|
||||
77
ct/invoiceshelf.sh
Normal file
77
ct/invoiceshelf.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://invoiceshelf.com/
|
||||
|
||||
APP="InvoiceShelf"
|
||||
var_tags="${var_tags:-invoicing;finance;business}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-1024}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_arm64="${var_arm64:-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/invoiceshelf ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "invoiceshelf" "InvoiceShelf/InvoiceShelf"; then
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop caddy
|
||||
msg_ok "Stopped Services"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp /opt/invoiceshelf/.env /opt/invoiceshelf.env.bak
|
||||
cp -r /opt/invoiceshelf/storage /opt/invoiceshelf_storage_backup
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "invoiceshelf" "InvoiceShelf/InvoiceShelf" "tarball"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp /opt/invoiceshelf.env.bak /opt/invoiceshelf/.env
|
||||
rm -f /opt/invoiceshelf.env.bak
|
||||
cp -r /opt/invoiceshelf_storage_backup/. /opt/invoiceshelf/storage
|
||||
rm -rf /opt/invoiceshelf_storage_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Updating Application"
|
||||
cd /opt/invoiceshelf
|
||||
$STD composer install --no-dev --optimize-autoloader
|
||||
$STD yarn install
|
||||
$STD yarn build
|
||||
$STD php artisan migrate --force
|
||||
$STD php artisan optimize:clear
|
||||
chown -R www-data:www-data /opt/invoiceshelf
|
||||
msg_ok "Updated Application"
|
||||
|
||||
msg_info "Starting Services"
|
||||
systemctl start caddy
|
||||
msg_ok "Started Services"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
|
||||
@@ -39,7 +39,7 @@ function update_script() {
|
||||
cp /opt/kan/.env /opt/kan.env.bak
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_tag "kan" "kanbn/kan" "tarball"
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_tag "kan" "kanbn/kan" "latest"
|
||||
|
||||
msg_info "Restoring Configuration"
|
||||
cp /opt/kan.env.bak /opt/kan/.env
|
||||
@@ -49,8 +49,10 @@ function update_script() {
|
||||
msg_info "Building Application"
|
||||
cd /opt/kan
|
||||
set -a && source /opt/kan/.env && set +a
|
||||
export NEXT_PUBLIC_USE_STANDALONE_OUTPUT=true CI=true
|
||||
$STD pnpm install
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
|
||||
22
ct/kometa.sh
22
ct/kometa.sh
@@ -32,6 +32,7 @@ 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"
|
||||
@@ -42,7 +43,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
|
||||
@@ -50,9 +51,28 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@ 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"
|
||||
|
||||
@@ -57,14 +55,12 @@ 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,config/credentials.yml.enc,config/master.key}
|
||||
rm -rf /opt/manyfold/app/{storage,tmp}
|
||||
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 /opt/manyfold_credentials.yml.enc /opt/manyfold_master.key
|
||||
rm -rf /opt/manyfold_storage_backup /opt/manyfold_tmp_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Installing Manyfold"
|
||||
@@ -80,6 +76,8 @@ 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
|
||||
'
|
||||
|
||||
@@ -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="22" NODE_MODULE="pnpm@10.33.0" setup_nodejs
|
||||
NODE_VERSION="25" NODE_MODULE="pnpm@10.33.0" setup_nodejs
|
||||
|
||||
msg_info "Building Frontend"
|
||||
cd /opt/musicseerr/frontend
|
||||
|
||||
@@ -216,6 +216,12 @@ 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
|
||||
|
||||
@@ -47,6 +47,10 @@ function update_script() {
|
||||
systemctl stop otbr-agent
|
||||
msg_ok "Stopped Services"
|
||||
|
||||
msg_info "Backing up Configuration"
|
||||
cp /etc/default/otbr-agent /etc/default/otbr-agent.bak
|
||||
msg_ok "Backed up Configuration"
|
||||
|
||||
msg_info "Updating Source"
|
||||
$STD git reset --hard origin/main
|
||||
$STD git submodule update --depth 1 --init --recursive
|
||||
@@ -70,6 +74,31 @@ 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 "Restoring Configuration"
|
||||
mv /etc/default/otbr-agent.bak /etc/default/otbr-agent
|
||||
msg_ok "Restored Configuration"
|
||||
|
||||
msg_info "Starting Services"
|
||||
systemctl start otbr-agent
|
||||
systemctl start otbr-web
|
||||
|
||||
@@ -30,7 +30,7 @@ function update_script() {
|
||||
exit
|
||||
fi
|
||||
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
if check_for_gh_release "soulsync" "Nezreka/SoulSync"; then
|
||||
msg_info "Stopping Service"
|
||||
|
||||
@@ -52,6 +52,9 @@ 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
|
||||
|
||||
|
||||
40
install/certimate-install.sh
Normal file
40
install/certimate-install.sh
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/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://certimate.me/
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
fetch_and_deploy_gh_release "certimate" "certimate-go/certimate" "prebuild" "latest" "/opt/certimate" "certimate_*_linux_amd64.zip"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<'EOF' >/etc/systemd/system/certimate.service
|
||||
[Unit]
|
||||
Description=Certimate SSL Certificate Manager
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/certimate
|
||||
ExecStart=/opt/certimate/certimate serve --http "0.0.0.0:8090"
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now certimate
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -17,7 +17,7 @@ msg_info "Installing Dependencies"
|
||||
$STD apt install -y build-essential
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
NODE_VERSION="26" setup_nodejs
|
||||
|
||||
msg_info "Setup Cross-Seed"
|
||||
$STD npm install cross-seed@latest -g
|
||||
|
||||
59
install/ddns-updater-install.sh
Normal file
59
install/ddns-updater-install.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: reptil1990
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/qdm12/ddns-updater
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
fetch_and_deploy_gh_release "ddns-updater" "qdm12/ddns-updater" "singlefile" "latest" "/opt/ddns-updater" "ddns-updater_*_linux_amd64"
|
||||
|
||||
msg_info "Configuring DDNS-Updater"
|
||||
mkdir -p /opt/ddns-updater/data
|
||||
cat <<EOF >/opt/ddns-updater/data/config.json
|
||||
{
|
||||
"settings": [
|
||||
{
|
||||
"provider": "namecheap",
|
||||
"domain": "example.com",
|
||||
"password": "e5322165c1d74692bfa6d807100c0310"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
msg_ok "Configured DDNS-Updater"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/ddns-updater.service
|
||||
[Unit]
|
||||
Description=DDNS-Updater
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/bash -c 'for i in \$(seq 1 30); do curl -sf --max-time 5 https://1.1.1.1 >/dev/null 2>&1 && break || sleep 2; done'
|
||||
ExecStart=/opt/ddns-updater/ddns-updater
|
||||
Environment=DATADIR=/opt/ddns-updater/data
|
||||
Environment=LISTENING_ADDRESS=:8000
|
||||
Environment=LOG_LEVEL=info
|
||||
Environment=PERIOD=5m
|
||||
WorkingDirectory=/opt/ddns-updater
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now ddns-updater
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -16,7 +16,8 @@ update_os
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
git \
|
||||
unzip
|
||||
unzip \
|
||||
valkey
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
msg_info "Installing Bun"
|
||||
@@ -38,6 +39,9 @@ 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
|
||||
@@ -62,11 +66,16 @@ 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
|
||||
After=network.target valkey-server.service
|
||||
Wants=valkey-server.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
@@ -121,6 +121,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,13 @@ msg_ok "Configured Firefly III"
|
||||
|
||||
msg_info "Configuring Data Importer"
|
||||
cp /opt/firefly/dataimporter/.env.example /opt/firefly/dataimporter/.env
|
||||
sed -i "s#FIREFLY_III_URL=#FIREFLY_III_URL=http://${LOCAL_IP}#g" /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
|
||||
chown -R www-data:www-data /opt/firefly
|
||||
msg_ok "Configured Data Importer"
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ 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
|
||||
|
||||
69
install/invoiceshelf-install.sh
Normal file
69
install/invoiceshelf-install.sh
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/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://invoiceshelf.com/
|
||||
|
||||
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 caddy
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PHP_VERSION="8.4" PHP_FPM="YES" PHP_MODULES="bcmath,gd,intl,xml,zip,pdo_pgsql,mbstring,curl,exif" setup_php
|
||||
setup_composer
|
||||
NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs
|
||||
PG_VERSION="16" setup_postgresql
|
||||
PG_DB_NAME="invoiceshelf" PG_DB_USER="invoiceshelf" setup_postgresql_db
|
||||
|
||||
fetch_and_deploy_gh_release "invoiceshelf" "InvoiceShelf/InvoiceShelf" "tarball"
|
||||
|
||||
msg_info "Setting up InvoiceShelf"
|
||||
cd /opt/invoiceshelf
|
||||
cp .env.example .env
|
||||
sed -i "s|^APP_ENV=.*|APP_ENV=production|" .env
|
||||
sed -i "s|^APP_DEBUG=.*|APP_DEBUG=false|" .env
|
||||
sed -i "s|^APP_URL=.*|APP_URL=http://${LOCAL_IP}|" .env
|
||||
sed -i "s|^DB_CONNECTION=.*|DB_CONNECTION=pgsql|" .env
|
||||
sed -i "s|^DB_HOST=.*|DB_HOST=127.0.0.1|" .env
|
||||
sed -i "s|^DB_PORT=.*|DB_PORT=5432|" .env
|
||||
sed -i "s|^DB_DATABASE=.*|DB_DATABASE=${PG_DB_NAME}|" .env
|
||||
sed -i "s|^DB_USERNAME=.*|DB_USERNAME=${PG_DB_USER}|" .env
|
||||
sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=${PG_DB_PASS}|" .env
|
||||
COMPOSER_ALLOW_SUPERUSER=1 $STD composer install --no-dev --optimize-autoloader --no-interaction
|
||||
$STD php artisan key:generate
|
||||
$STD yarn install
|
||||
$STD yarn build
|
||||
mkdir -p storage/framework/{cache,sessions,views} storage/logs bootstrap/cache
|
||||
chown -R www-data:www-data /opt/invoiceshelf
|
||||
chmod -R 775 storage bootstrap/cache
|
||||
$STD php artisan migrate --force
|
||||
$STD php artisan storage:link
|
||||
msg_ok "Set up InvoiceShelf"
|
||||
|
||||
msg_info "Configuring Caddy"
|
||||
PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')
|
||||
cat <<EOF >/etc/caddy/Caddyfile
|
||||
:80 {
|
||||
root * /opt/invoiceshelf/public
|
||||
php_fastcgi unix//run/php/php${PHP_VER}-fpm.sock
|
||||
file_server
|
||||
encode gzip
|
||||
}
|
||||
EOF
|
||||
usermod -aG www-data caddy
|
||||
msg_ok "Configured Caddy"
|
||||
|
||||
systemctl enable -q --now php${PHP_VER}-fpm
|
||||
systemctl restart caddy
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -23,7 +23,7 @@ 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" "tarball"
|
||||
fetch_and_deploy_gh_tag "kan" "kanbn/kan" "latest"
|
||||
|
||||
msg_info "Configuring Application"
|
||||
AUTH_SECRET=$(openssl rand -base64 32)
|
||||
@@ -45,8 +45,9 @@ 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
|
||||
$STD pnpm install
|
||||
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"
|
||||
|
||||
@@ -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 -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"
|
||||
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"
|
||||
msg_ok "Downloaded Essentia ML Models"
|
||||
|
||||
msg_info "Downloading CLAP Model"
|
||||
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"
|
||||
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"
|
||||
msg_ok "Downloaded CLAP Model"
|
||||
|
||||
msg_info "Building Backend"
|
||||
|
||||
@@ -13,7 +13,7 @@ setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs
|
||||
NODE_VERSION="26" NODE_MODULE="yarn" setup_nodejs
|
||||
PG_VERSION="16" setup_postgresql
|
||||
PHP_VERSION="8.5" PHP_APACHE="YES" setup_php
|
||||
setup_composer
|
||||
|
||||
@@ -15,7 +15,7 @@ update_os
|
||||
|
||||
PYTHON_VERSION="3.13" setup_uv
|
||||
fetch_and_deploy_gh_release "musicseerr" "HabiRabbu/Musicseerr" "tarball"
|
||||
NODE_VERSION="22" NODE_MODULE="pnpm@10.33.0" setup_nodejs
|
||||
NODE_VERSION="25" NODE_MODULE="pnpm@10.33.0" setup_nodejs
|
||||
|
||||
msg_info "Building Frontend"
|
||||
cd /opt/musicseerr/frontend
|
||||
|
||||
@@ -160,7 +160,8 @@ $STD yarn install --network-timeout 600000
|
||||
msg_ok "Initialized Backend"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<'EOF' >/lib/systemd/system/npm.service
|
||||
CERTBOT_VER=$(/opt/certbot/bin/certbot --version 2>&1 | awk '{print $NF}')
|
||||
cat <<EOF >/lib/systemd/system/npm.service
|
||||
[Unit]
|
||||
Description=Nginx Proxy Manager
|
||||
After=network.target
|
||||
@@ -169,6 +170,7 @@ 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
|
||||
|
||||
@@ -67,6 +67,17 @@ 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
|
||||
|
||||
@@ -23,7 +23,7 @@ $STD apt install -y \
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
UV_PYTHON="3.11" setup_uv
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
fetch_and_deploy_gh_release "soulsync" "Nezreka/SoulSync" "tarball"
|
||||
|
||||
|
||||
@@ -41,29 +41,27 @@ cat <<EOF >/etc/caddy/Caddyfile
|
||||
}
|
||||
EOF
|
||||
usermod -aG www-data caddy
|
||||
msg_ok "Configured Caddy"
|
||||
|
||||
systemctl enable -q --now php${PHP_VER}-fpm
|
||||
systemctl restart caddy
|
||||
msg_ok "Configured Caddy"
|
||||
|
||||
msg_info "Automating Webtrees Setup"
|
||||
sleep 5
|
||||
cd /opt/webtrees
|
||||
WT_ADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c15)
|
||||
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
|
||||
$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
|
||||
|
||||
cat <<EOF >>~/webtrees.creds
|
||||
|
||||
|
||||
@@ -8227,11 +8227,13 @@ 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"
|
||||
|
||||
@@ -8243,8 +8245,8 @@ setup_rust() {
|
||||
|
||||
# Scenario 1: Rustup not installed - fresh install
|
||||
if ! command -v rustup &>/dev/null; then
|
||||
msg_info "Setup Rust ($RUST_TOOLCHAIN)"
|
||||
curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" || {
|
||||
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_error "Failed to install Rust"
|
||||
msg_error "Hint: Check connectivity to sh.rustup.rs and static.rust-lang.org"
|
||||
return 7
|
||||
|
||||
@@ -50,6 +50,19 @@ 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)
|
||||
|
||||
Reference in New Issue
Block a user