git-subtree-dir: cms/core git-subtree-split: 2f52ec7536ee87b4f097d169e1cb0f6a85aebe84
12 KiB
openbureau-core — handover
For a fresh instance — START HERE
- Repo (local): cloned at
/home/karim/openbureau-core. Remotegit.openbureau.ch/karim/openbureau-coreis private; auth via an HTTPS token in~/.git-credentials(the SSH keyid_ed25519_openbureauis currently rejected by gitea/proxmox — server seems rebuilt; fix the key or keep using the token). - Where the work is: branch
stufe-2-5-scaffold(pushed, NOT merged).mainis still the pre-stage-2 foundation. Open a PR or merge when happy. - Done: stages 1–4 complete, stage 5 scaffolded. See "Migration stages" below
(markers
[done]/[scaffold]). New code:api/src/collections.js,api/src/plugin-manager.js,api/src/plugins/dialog/index.js+ their tests. - Run tests:
cd api && npm install && node --test→ 44 green.node_modulesis git-ignored; the fresh clone needsnpm installonce. - Immediate next action: stage 5 + 5.5 DONE on the branch (commits
c4230a4cutover,5e06337DDL,a93d11amigration runner). Plugin-manager singleton (api/src/plugins.js) wired intoindex.js/stats.js/publish.js, hard-coded dialog removed, source moved underapi/src/plugins/dialog/; DDL captured intoplugins/dialog/migrations/001_dialog.sql(idempotent);api/src/migrate.jsapplies migrations once viaschema_migrationsat boot. 53 tests green + a real boot. Live-verified on the dev stack (CT 134): a 2nd cms instance fromcms-cms:latest(mysrcmounted over/app/src, real Supabase viakong:8000, empty SITE_DIR → no writes) returned byte-identical/api/forums&/api/recentvs the running container,/api/content401 on both; the runner's tracking SQL was proven on a throwaway DB. NOT deployed (the running stack still runs old code). NEXT (stage 8): deploy core to openbureau — needsDATABASE_URLset for the cms service (e.g.postgres://postgres:$POSTGRES_PASSWORD@db:5432/postgres) so the runner can apply migrations, then full login/edit/publish verify before cutting the live container over. Then stages 6 (admin/api/schema), 7 (local auth), 9 (kgva). Dev stack: Proxmox CT 134openbureau-dev, repo/opt/openbureau, composecms/docker-compose.yml, containers openbureau-{cms,auth,kong,rest,db}, dev URL dev.openbureau.ch. Do NOT push a blind refactor to prod. - Env gotcha: editing a file that's open in Karim's VSCode makes the Edit/Write
tool hang/"interrupt" — write such files via Bash (
cat > f <<'EOF' … EOF) instead.
Goal
Extract a generic, schema-driven Hugo CMS engine ("openbureau-core") from the openbureau site's bundled CMS, so that both openbureau and karimgabrielevarano.xyz (kgva) consume it as a dependency. Decision: own repo (this one), and migrate openbureau to depend on it.
State (done this session)
- Repo
karim/openbureau-corecreated on Gitea, foundation pushed.api/= engine copied verbatim fromkarim/OPENBUREAU→cms/api(Hono/Node).admin/= React/Vite SPA copied verbatim fromOPENBUREAU/cms/admin.api/src/config.js= NEW loader: readsCMS_CONFIG→ a site config module.examples/openbureau.config.js,examples/kgva.config.js= target schemas.README.md= config API + plugin model + the migration checklist.
- Nothing in the live openbureau CMS was changed yet.
Engine is ~80% generic. openbureau-specific seams to make config-driven:
- [DONE]
api/src/files.jsclassify(rel)+ theordermap — now derived fromconfig.collectionsviaapi/src/collections.js(classify/buildPath/compareEntries);buildPathexposed there too. - [DONE]
api/src/routes/stats.js— now per-collection (statKey/draftStatKey), dialog counts gated byhasPlugin('dialog'). api/src/index.js— always mounts dialog routes + runssyncLibraryon boot. → only whenconfig.pluginsincludesdialog.api/src/routes/publish.js— callssyncLibrary. → gate by plugin.admin/src/App.jsx(714 lines) — hard-codesSECTIONS,KIND_LABEL, the field set, type dropdown,buildPath. → render sidebar groups + editor fields from the schema (addGET /api/schemareturningconfig.collections).- dialog subsystem (
dialog-store.js,routes/dialog.js,routes/comments.js- Supabase tables forums/threads/comments + library↔thread sync) = the
dialogplugin (openbureau on, kgva off). First gate by config, later move toapi/src/plugins/dialog/.
- Supabase tables forums/threads/comments + library↔thread sync) = the
CRITICAL
api and admin share a data contract (stats keys, field shapes). Generalising the backend alone breaks the live openbureau admin — they must change together and be tested on the live Supabase/Hugo stack. Do NOT push a blind half-refactor to production openbureau.
Plugin system (decided architecture)
Everything beyond the generic engine is a plugin; the engine gets a small
plugin manager. A plugin = api/src/plugins/<name>/index.js exporting a
manifest { name, routes:[{path,app,public?,admin?}], onBoot, onPublish, onPreview, migrations:[…], stats, admin:{…} }. The manager reads config.plugins, imports
each, mounts routes in the right auth tier (public before requireAuth, rest
after, admin:true behind requireAdmin), registers boot/publish/preview/stats
hooks, runs migrations; the admin SPA loads UI from admin/src/plugins/<name>.
openbureau's extras become plugins (first: dialog); kgva enables none.
(Full spec in README → "Plugins".)
Migration stages (also README checklist)
- [done] repo + engine + config loader + example configs.
- [done] files.js classify/order/buildPath ← config.collections
(new
api/src/collections.js= pure classify/buildPath/compareEntries/contentStats; files.js uses it, loads config lazily in listEntries;api/test/collections.test.jsproves 1:1 vs openbureau.config.js). All api tests green. - [done] stats.js ← collections:
contentviastatKey+draftStatKey(openbureau beitrag gotdraftStatKey: 'entwuerfe'), dialog counts gated byhasPlugin('dialog')(pluginless site does zero DB reads here). - [built, not wired] plugin manager —
api/src/plugin-manager.js(loadPlugins(names)importsplugins/<name>/index.js;createManager(manifests)= pure wiring:mountPublic/mountPrivate(+requireAdmin),runBoot/runPublish/ runPreview,collectStats(merged under plugin name),migrations()(resolves file URLs; execution deferred to the live DB)). Unit-tested inapi/test/plugin-manager.test.js(tiers/hooks/stats/migrations). TODO: wire into index.js (stage 5), and admin loads plugin UI fromadmin/src/plugins/<name>. - [DONE — commits c4230a4 (cutover) + 5e06337 (DDL), live e2e verified] extract openbureau extras
into the dialog plugin. DONE:
api/src/plugins/dialog/index.js= manifest wrapping the existing modules unchanged (public reads + widget login(rate-limited)- authed writes + self-guarding mod/adminForums sub-apps; onBoot/onPublish =
syncLibrary; stats = forum/thread/comment counts). CUTOVER DONE: shared manager
singleton
api/src/plugins.jswired into index.js (mountPublic before requireAuth, mountPrivate(+requireAdmin) after, manager.runBoot at serve()), publish.js (manager.runPublish) and stats.js (manager.collectStats,{content,users,dialog}contract preserved); hard-coded dialog REMOVED from all three; dialog source physically moved toplugins/dialog/{dialog-store,dialog,comments}.js(imports fixed). Tested:api/test/{dialog-plugin,cutover}.test.js, 48 green + real boot. DDL CAPTURED (commit 5e06337):migrations/001_dialog.sqlfrom the live dev DB, idempotent, validated on a throwaway DB; manifestmigrations: ['001_dialog.sql']. LIVE E2E VERIFIED (dev CT 134): 2nd cms instance from cms-cms:latest with my src mounted, real Supabase via kong:8000 — /api/forums & /api/recent byte-identical to the running container, /api/content 401 both, no writes. Migration runner = stage 5.5 (commit a93d11a, see new item below). Deploy is stage 8. NB: dialog-store.js still filterse.kind === 'beitrag'— openbureau-specific, already moved with the plugin. 5.5 [done, commit a93d11a] migration runner —api/src/migrate.jsrunMigrationstracks applied migrations inpublic.schema_migrationsand applies each declared file once, in its own transaction, at boot (wired into index.js before runBoot). Lean: no declared migrations → no DB connection, no pg import (kgva stays DB-less). Executor is injectable (unit-tested without a DB); default uses a lazily-importedpgagainstDATABASE_URL, warning-and-skipping if unset. NeedsDATABASE_URLwired into the cms service env for openbureau (stage 8). Tested: migrate.test.js + tracking SQL proven on the live dev Postgres (throwaway DB).
- authed writes + self-guarding mod/adminForums sub-apps; onBoot/onPublish =
syncLibrary; stats = forum/thread/comment counts). CUTOVER DONE: shared manager
singleton
- admin:
/api/schema+ App.jsx renders from schema (must reproduce openbureau's editor 1:1 when fedopenbureau.config.js). - auth provider (
config.auth=supabase|local). Verify is already local (HS256/JWT_SECRET in auth.js). Add alocalprovider: file-based users (bcrypt) + self-signed JWTs of the SAME claim shape (sub/email/app_metadata.role/exp), and a/loginroute to replace GoTrue; admin login (admin/src/supabase.js) calls it instead ofsignInWithPassword; users.js CRUD edits the user file. Goal: a dialog-less core runs Node+Hugo+nginx, no Supabase/Postgres (~80 MB). openbureau stayssupabase. Independent of the cutover —supabaseis the default no-op. - cut openbureau over to consume core (= core +
dialogplugin +supabaseauth + its config) — test login / list / edit / preview / publish / dialog — identical. - onboard kgva (
CMS_CONFIG=…/kgva.config.js,auth: 'local', no plugins) as the 2nd consumer — DB-less.
How to run / test
openbureau CMS stack: OPENBUREAU/cms/docker-compose.yml (Node api + Hugo +
Supabase: postgres/kong/gotrue). Env in cms/.env (see .env.example):
SUPABASE_URL/SERVICE_KEY, JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY (derive via
scripts/generate-keys.mjs), ADMIN_EMAILS, SITE_URL, GIT_*. For core add
CMS_CONFIG (path to the site config) and SITE_DIR (site repo mount, default
/site). Admin at /admin, preview /_preview, publish builds public/.
Infra access (NO secrets in this file)
- Proxmox node
192.168.1.2, userroot. Password: ask the user (provided ad hoc, not stored). No SSH key installed — use SSH_ASKPASS, or install a key first. - Gitea = container 120 (
pct exec 120 …), ROOT_URLgit.openbureau.ch, sqlite at/var/lib/gitea/data/gitea.db. Make a token:pct exec 120 -- su gitea -s /bin/bash -c "/usr/local/bin/gitea admin user generate-access-token --username karim --scopes all --token-name X --raw --config /etc/gitea/app.ini". Keep tokenkgva-deploy. Delete tokens via sqlite (DELETE FROM access_token WHERE name LIKE '…') since the API needs the account password. - Push without local git auth: tar → scp to node →
pct push 120into the gitea container →git push http://karim:<token>@127.0.0.1:3000/karim/<repo>.git. - kgva site deploy: container 130 (kgva-website). nginx serves
/var/www/karimgabrielevarano.xyz(= ZFStank/kgva-website, owned uid 100000). systemd timer runs/opt/kgva-deploy.shevery 60s: pullkarim/kgva(tokenkgva-deploy) →hugobuild → copy into webroot. So a push tokarim/kgvais live in ~1 min. Self-hosted video at/var/www/media(nginx/media/alias, outside the build). Public TLS for the domain terminates upstream → 192.168.1.130:80.
Watch out
- Gitea does NOT send CORS on its OAuth token endpoint → a browser git-CMS (Decap-style) can't auth without a same-origin proxy. openbureau-core uses its own Supabase auth, so this doesn't affect it; it's why kgva edits go via git push.
- Hugo versions: openbureau CMS bundles 0.161.1; the kgva site needs 0.163.3 features. Mind the version a core instance builds with.