Commit Graph
13 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5 2b593ba53f Deploy becomes publish: portal ships as a versioned release artifact
Publish release / publish (push) Successful in 1m7s
The portal repo no longer deploys instances. CI builds once, packages
portal + question_lint + site/ into a tarball, and publishes it as a
Gitea release tagged build-<shortsha> using the run's ephemeral token.
Content repos (uhhm/questions, redoal/questions) now own their instance
deploys - env, unit restart, Caddy route - pinned to a release tag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 21:21:06 +02:00
Bendik Aagaard LynghaugandClaude Fable 5 0221253cba Gesture input type, redoal-relay client, gitea_releases, content-driven branding
Four coupled additions that let one portal build serve a second face
(redoal.com) next to uhhm.no:

- type: gesture requirement - gesture.js draws a single stroke on a
  DPR-aware canvas (pointer events, touch-action: none), mirrors
  {points, key} into the paired hidden input prosekit-style, and -
  when content declares relay: wss://... - speaks the redoal-relay
  protocol: announce on stroke end, ghost the ack's decoded key path,
  show echoes of similar strokes as thumbnails. Offline/broken relay
  degrades to a plain drawing input; the widget handle's stop()
  closes the socket on SPA navigation (yes.js lifecycle, not
  prosekit's fire-and-forget). Submit re-parses gesture values so the
  bucket stores a real object, not double-encoded JSON.
- gitea_releases resource source - token-authenticated
  /repos/{owner}/{repo}/releases, for advertising a private repo's
  releases (content pins url: null - private html_urls 404 publicly).
- site.yaml branding - optional, at the content repo root: title,
  wordmark, hero {kind: yes|gesture|plain, relay}. Absent file means
  the historical uhhm look, so uhhm changes nothing without a content
  edit. Hot-swapped with questions/aggregates on content reload;
  question_lint validates it in both --path and --repo modes.
- deploy.yml ships the same build twice: uhhm-portal (3010) as
  before, redoal-portal (3020, CONTENT_REPO=redoal/questions,
  redoal.com vhost). Needs host prep + REDOAL_OAUTH2_* repo
  secrets/vars before the new steps succeed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 13:03:29 +02:00
Bendik Aagaard LynghaugandClaude Sonnet 5 1c1cbfc13b deploy.yml: build and publish question_lint alongside the app binary
Deploy / deploy (push) Successful in 1m5s
questions' CI never actually ran this - the binary existed in-repo but
nothing built or exposed it. Publish it to the same stable release path
as the app binary so questions' lint job can exec it directly (same
bare-metal runner/host, no artifact download needed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 21:16:34 +02:00
Bendik Aagaard LynghaugandClaude Sonnet 5 f6c5d8e05c deploy.yml: write GITEA_API_TOKEN for the new GiteaStarred/OrgRepos resource sources
Deploy / deploy (push) Failing after 3s
Needs the PORTAL_GITEA_API_TOKEN repo secret set (Settings -> Actions ->
Secrets) - a fresh read-only token, not yet wired in as a value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 08:55:27 +02:00
Bendik Aagaard Lynghaug 44c8c9d410 Add prosekit rich-text field, Gitea repo embeds, automation KV read endpoint; fix apex/www session-cookie mismatch on /auth/callback
Deploy / deploy (push) Successful in 29s
- type: prosekit requirement kind, backed by public/prosekit-editor.js
  (ProseMirror via prosekit, loaded from esm.sh, no bundler) - mirrors
  its HTML into a paired hidden input so it reuses the existing
  RwSignal/on:input wiring.
- Pasting a project.uhhm.no/<owner>/<repo> URL in the editor embeds a
  repo card, resolved server-side via a new /gitea-repo handler
  (content::gitea_repo_handler) so the browser never needs Gitea API
  CORS.
- New /automation/kv/{bucket} handler, bearer-token gated
  (AUTOMATION_READ_TOKEN), for backing automations (n8n) to read a
  NATS KV bucket without a browser session.
- Fix: a login started on one of apex/www set its session cookie
  there, but Kanidm's redirect_uri is fixed to PUBLIC_URL - landing
  the callback on a different, empty session ("no login in
  progress"). Caddy now redirects www -> apex so every visit stays on
  one canonical host.
2026-08-05 13:45:56 +02:00
Bendik Aagaard Lynghaug b0086b2ed9 Fix static asset 404s and build-time SITE_NAME
Deploy / deploy (push) Successful in 27s
Two runtime gaps found via hwatu after the first successful deploy:
- /pkg/*.{js,wasm,css} 404'd because the running binary looked for
  target/site (Cargo.toml's site-root, a build-time path) but the
  deploy step copies the bundle to releases/<sha>/site with no
  target/ prefix - LEPTOS_SITE_ROOT=site in the runtime env corrects
  it.
- SITE_NAME is read via option_env! (compile-time), so writing it
  only into the runtime env file never reached the binary - the
  wordmark stayed "portal". Now exported for the Build step too.

Verified live: applied both fixes directly to the running deployment
first (no CI round-trip), confirmed via hwatu (no console errors,
hero renders, all three alternatives' forms present) before folding
them back into the workflow.
2026-08-04 20:46:00 +02:00
Bendik Aagaard Lynghaug 6b1af34d57 Caddy snippet: use multi-line log block
Deploy / deploy (push) Successful in 27s
Single-line "log { output file X }" parses fine directly in the main
Caddyfile but not through import services.d/*.caddy - Caddy rejects it
with "Unexpected next token after '{' on same line". Verified the
multi-line form validates cleanly (caddy validate) against the real
Caddyfile.
2026-08-04 20:39:12 +02:00
Bendik Aagaard Lynghaug 70a7586471 Drop sudo from the deploy workflow - NoNewPrivileges blocks it outright
Deploy / deploy (push) Failing after 27s
sudo needs setuid escalation, which the runner's own unit disables
entirely via NoNewPrivileges=yes (unconditionally, independent of any
sudoers config - the earlier sudoers.d/deploy-runner grant was never
going to work). Caddy reload already works via the runner's existing
docker group membership; the service restart now goes through plain
systemctl (talks to PID1 over D-Bus, no escalation needed), authorized
by a new polkit rule scoped to deploy-runner + app@*.service.
2026-08-04 20:36:54 +02:00
Bendik Aagaard Lynghaug 634e8dcdfa Fix Ship release: site-root is project-relative, not CARGO_TARGET_DIR
Deploy / deploy (push) Failing after 26s
cp was looking for the site bundle under $CARGO_TARGET_DIR/site, but
site-root = "target/site" in Cargo.toml is resolved relative to the
project directory regardless of CARGO_TARGET_DIR - only the plain
cargo build outputs (release/, front/) actually move when that's
overridden.
2026-08-04 20:31:58 +02:00
Bendik Aagaard Lynghaug 6ac64eeb8f Redirect cargo-leptos's tool cache out of act-runner-bare's StateDirectory
Deploy / deploy (push) Failing after 27s
wasm-bindgen/wasm-opt binaries cargo-leptos downloads under
XDG_CACHE_HOME ended up owned by the kernel's overflow "nobody" uid
inside systemd's StateDirectory for this unit - a quirk of that
specific path, not anything about our shared-cache setup (CARGO_TARGET_DIR
elsewhere is owned correctly). Pointing XDG_CACHE_HOME at our own
shared /var/local/leptos-cache sidesteps it; verified with a clean
checkout + a from-scratch env matching the workflow's exactly.
2026-08-04 20:30:09 +02:00
Bendik Aagaard Lynghaug abe13c139c Give CI its own SCCACHE_SERVER_PORT
Deploy / deploy (push) Failing after 1m21s
Sharing the default port meant whichever context's server happened to
be running (usually the interactive shell's, via rust-analyzer's
background cargo checks) silently served the other's build requests
too - and since sccache's server writes outputs under its own user,
that meant permission-denied for whichever context didn't own it.
Separate ports keep the server processes apart; SCCACHE_DIR is what's
actually shared, giving real cache hits across both.
2026-08-04 13:30:39 +02:00
Bendik Aagaard Lynghaug 76d101b7a8 Share sccache between manual dev builds and CI
Deploy / deploy (push) Failing after 4s
SCCACHE_DIR/CARGO_TARGET_DIR now point at /var/local/sccache and
/var/local/cargo-target, shared with the interactive dev shell via a
new `builders` group, instead of the CI job hitting a leftover
personal sccache server it had no permission to write build output for.
2026-08-04 13:27:22 +02:00
Bendik Aagaard LynghaugandClaude Sonnet 5 286fdbf67f Load content from Gitea directly, drop the local clone; add deploy workflow
Deploy / deploy (push) Failing after 16s
load_questions_from_gitea (content.rs) fetches question YAML straight
from the questions repo's public Gitea contents API at startup instead
of scanning a local directory - one fewer moving part in production
(no git clone to keep in sync, no separate questions-repo deploy
workflow). Still just an in-memory startup load, same as before -
served from RAM for every request, no per-request network call.
Verified against the real repo (all 5 questions fetch correctly).

CONTENT_DIR is replaced by CONTENT_REPO/CONTENT_BRANCH, defaulting to
the real questions repo so local dev needs no env override.

Also adds .gitea/workflows/deploy.yml: builds with cargo-leptos,
ships the release under /srv/app/uhhm-portal (the generic app@.service
deploy layout), writes /etc/app/uhhm-portal.env from this repo's own
Actions Variables/Secrets, restarts the service, and drops this app's
Caddy routing snippet into services.d/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 06:10:06 +02:00