chore(pocketbase): track live quotes hook and add git-based hook sync

quotes.pb.js is copied byte-for-byte from the live WFDASH PocketBase.
DEPLOY lists the hooks the VPS cron syncs into the hooks volume; the
audit hooks stay undeployed for now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-10-17 15:34:41 +02:00
parent 810f0d7350
commit 6b99f52dac
5 changed files with 172 additions and 2 deletions

View file

@ -135,8 +135,11 @@ Read side: `src/lib/server/audit-log-service.ts` →
Noisy fields (search/AI/timers) are skipped via `IGNORED_FIELDS` in the
hook utils — add new auto-updated fields there.
The hook files are deployed by hand into the PB server's `pb_hooks/`
(like select values, it's a manual step on the live instance).
Hooks deploy from git: a VPS cron syncs the files listed in
`pocketbase/pb_hooks/DEPLOY` from `main` into the live PB hooks volume
(see `scripts/pb-hooks/README.md`). **As of 2026-10-17 only `quotes.pb.js`
is deployed — the audit hooks are NOT live yet**, so `audit_log` is empty
until both `audit_log.pb.js` and `audit_log_utils.js` are added to `DEPLOY`.
# Giellie agent (`/giellie`) — tools come from the MCP, policy lives in one file

View file

@ -0,0 +1,8 @@
# Hook files the VPS sync copies into the live WFDASH PocketBase.
# One filename per line (relative to this folder). Anything not listed here
# is NOT deployed, and a *.js file on the server that is not listed here is
# removed on the next sync. See scripts/pb-hooks/README.md.
#
# Not deployed yet: audit_log.pb.js + audit_log_utils.js — add BOTH lines
# together when the audit trail is switched on (utils is required by the hook).
quotes.pb.js

View file

@ -0,0 +1,63 @@
onRecordCreate((e) => {
if (e.record.getString("quote_number") !== "") {
e.next();
return;
}
try {
var now = new Date();
var year = now.getFullYear();
var month = String(now.getMonth() + 1).padStart(2, '0');
var prefix = "Q" + year + month;
let attempts = 0;
let candidate;
do {
// findRecordsByFilter accepts sort as the 3rd arg.
// findFirstRecordByFilter's 3rd arg is params (dbx.Params),
// which is why the old "-quote_number" sort string threw
// "could not convert -quote_number to dbx.Params".
const records = $app.findRecordsByFilter(
"quotes",
"quote_number != ''",
"-quote_number",
1,
0
);
let newNum = 1;
if (records && records.length > 0) {
var raw = records[0].getString("quote_number");
// Extract trailing digits (handles Q202607011,
// legacy Q-2026-07-011, Q1782862717620, etc.).
var m = raw.match(/(\d+)$/);
if (m) {
newNum = parseInt(m[1], 10) + 1 + attempts;
}
}
candidate = prefix + String(newNum).padStart(3, '0');
attempts++;
// check if this candidate already exists
var exists;
try {
exists = $app.findFirstRecordByFilter(
"quotes",
"quote_number = {:num}",
{ num: candidate }
);
} catch (e) {
exists = null; // not found throws in some versions
}
} while (exists && attempts < 5);
e.record.set("quote_number", candidate);
} catch (err) {
console.error("Auto-numbering error:", err);
e.record.set("quote_number", "Q" + Date.now()); // fallback that's at least unique
}
e.next();
}, "quotes");

View file

@ -0,0 +1,24 @@
# PocketBase hooks sync (WFDASH)
The live WFDASH PocketBase (Coolify service `pocketbase-wfdash`, container
`pocketbase-gnqc88suy8fqk6wyryjjdpvl`, PocketBase 0.39.8) loads its hooks
from the Docker volume `gnqc88suy8fqk6wyryjjdpvl_pocketbase-hooks`, mounted
at `/app/pb_hooks`.
Hooks are deployed from git, not edited on the server:
1. Edit or add files in `pocketbase/pb_hooks/` in this repo.
2. List every file to deploy in `pocketbase/pb_hooks/DEPLOY`.
3. Push to `main` on Forgejo.
Within ~2 minutes the VPS cron runs `sync-pb-hooks.sh`, which pulls `main`
into `/opt/wfdash-pb-hooks/repo` (sparse clone, read-only deploy key) and
copies the listed files into the volume. PocketBase reloads itself when a hook
file changes. A `.js` file on the server that isn't in `DEPLOY` is removed.
- Log: `/var/log/wfdash-pb-hooks-sync.log` on the VPS.
- Force a sync: `/opt/wfdash-pb-hooks/repo/scripts/pb-hooks/sync-pb-hooks.sh --force`
- Roll back: revert the commit (or remove the line from `DEPLOY`) and push.
- Pause syncing: comment out the cron line (`crontab -e` as root).
The sync never touches `pb_data` (the database).

View file

@ -0,0 +1,72 @@
#!/bin/sh
# Sync the WFDASH PocketBase hooks from git into the live hooks volume.
#
# Runs on the VPS from cron. Pulls origin/main into a sparse clone and copies
# every file listed in pocketbase/pb_hooks/DEPLOY into the PocketBase hooks
# volume. PocketBase reloads itself when a file in pb_hooks changes, so no
# container restart is needed. Files are only written when their content
# changed, so an unchanged repo never triggers a reload.
#
# Usage: sync-pb-hooks.sh [--force] (--force re-syncs even if HEAD is current)
set -eu
REPO="${PB_HOOKS_REPO:-/opt/wfdash-pb-hooks/repo}"
TARGET="${PB_HOOKS_TARGET:-/var/lib/docker/volumes/gnqc88suy8fqk6wyryjjdpvl_pocketbase-hooks/_data}"
LOCK="${PB_HOOKS_LOCK:-/tmp/wfdash-pb-hooks-sync.lock}"
log() { echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $*"; }
exec 9>"$LOCK"
if ! flock -n 9; then
log "another sync is running; skipping"
exit 0
fi
cd "$REPO"
git fetch --quiet origin main
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ] && [ "${1:-}" != "--force" ]; then
exit 0
fi
git reset --quiet --hard origin/main
SRC="$REPO/pocketbase/pb_hooks"
LIST="$SRC/DEPLOY"
if [ ! -f "$LIST" ]; then
log "no $LIST; nothing deployed"
exit 1
fi
# Validate the whole list before touching the live folder.
WANTED=$(grep -v '^[[:space:]]*#' "$LIST" | sed 's/[[:space:]]*$//' | grep -v '^$' || true)
for f in $WANTED; do
case "$f" in
*/*|.*) log "refusing unsafe name in DEPLOY: $f"; exit 1 ;;
esac
if [ ! -f "$SRC/$f" ]; then
log "listed file missing in repo: $f — nothing changed"
exit 1
fi
done
changed=0
for f in $WANTED; do
if ! cmp -s "$SRC/$f" "$TARGET/$f"; then
cp "$SRC/$f" "$TARGET/$f.tmp"
mv "$TARGET/$f.tmp" "$TARGET/$f"
log "updated $f"
changed=1
fi
done
# Remove deployed .js files that are no longer listed.
for path in "$TARGET"/*.js; do
[ -e "$path" ] || continue
name=$(basename "$path")
if ! printf '%s\n' $WANTED | grep -qx "$name"; then
rm -f "$path"
log "removed $name (not in DEPLOY)"
changed=1
fi
done
log "synced $(git rev-parse --short HEAD) (changed=$changed)"