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>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-08-23 13:03:29 +02:00
co-authored by Claude Fable 5
parent 6cd8cc6ff5
commit 0221253cba
9 changed files with 951 additions and 17 deletions
+54
View File
@@ -131,3 +131,57 @@ jobs:
}
EOF
docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
# ── redoal.com - second instance, same build ─────────────────
# One binary serves both faces: branding is content-driven
# (site.yaml in each CONTENT_REPO), so only the env differs.
# The compile-time SITE_NAME baked above is just this instance's
# fallback - redoal/questions' site.yaml overrides it.
- name: Ship redoal release
run: |
set -euo pipefail
rel="/srv/app/redoal-portal/releases/${{ github.sha }}"
mkdir -p "$rel"
cp "$CARGO_TARGET_DIR/release/portal" "$rel/redoal-portal"
cp -r target/site "$rel/site"
ln -sfn "$rel" /srv/app/redoal-portal/current
- name: Write redoal service env
run: |
cat > /etc/app/redoal-portal.env <<EOF
NATS_URL=${{ secrets.PORTAL_NATS_URL }}
KANIDM_URL=${{ vars.PORTAL_KANIDM_URL }}
OAUTH2_CLIENT_ID=${{ vars.REDOAL_OAUTH2_CLIENT_ID }}
OAUTH2_CLIENT_SECRET=${{ secrets.REDOAL_OAUTH2_CLIENT_SECRET }}
PUBLIC_URL=https://redoal.com
COOKIE_SECURE=true
CONTENT_REPO=https://project.uhhm.no/redoal/questions
CONTENT_BRANCH=main
LEPTOS_SITE_ADDR=0.0.0.0:3020
LEPTOS_SITE_ROOT=site
# Same read-only token as uhhm's instance - here it also
# backs the gitea_releases source for the private
# redoal/redoal repo, so the owning user needs read access
# there.
GITEA_API_TOKEN=${{ secrets.PORTAL_GITEA_API_TOKEN }}
EOF
- name: Restart redoal service
run: systemctl restart app@redoal-portal.service
- name: Update redoal Caddy routing
run: |
cat > /etc/caddy/services.d/redoal-portal.caddy <<'EOF'
www.redoal.com {
redir https://redoal.com{uri} permanent
}
redoal.com {
reverse_proxy host.docker.internal:3020
log {
output file /var/log/caddy/redoal.log
}
}
EOF
docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile