ci(demo): deploy main to a public demo on every push #1

Merged
genji1024 merged 9 commits from feat/demo-deploy into main 2026-10-04 12:33:30 +09:00
Owner

Summary

Adds continuous deployment of main to a public demo instance, which holds only dummy data. The demo shares a host with a real koto, so it runs as a separate user with rootless Docker. If a deploy is compromised, the damage stays inside that user.

host
├─ your user            rootless Docker: front Traefik (owns :443) + your own koto
└─ koto-demo (new user) rootless Docker: its own Traefik on <host address>:18443 + the demo
                        forgejo-runner, label koto-demo, jobs run as this user

The front Traefik passes the demo's hostnames through by SNI without decrypting them. The demo's own Traefik terminates TLS and obtains its own certificates, exactly as in docs/traefik, so every deploy also checks that the documented setup works.

Changes

  • .forgejo/workflows/deploy-demo.yml: runs on every push to main, or on demand, on a runner labelled koto-demo. It checks out the pushed commit in the demo's checkout and runs deploy.sh. The job is skipped unless the repository variable DEMO_DIR is set, so forks and other installs never queue a job that no runner will pick up.
  • scripts/deploy.sh: applies the current checkout to the running stack. It re-renders the config, pulls images, rebuilds and starts the stack, waits for Synapse to become healthy, then runs check.sh. When homeserver.yaml changed, it stops Synapse before starting the stack, rather than restarting it afterwards, so it never interrupts Synapse while Synapse creates or migrates its database. Images are pulled before anything is stopped, so a failed pull leaves the running stack untouched. It is not specific to the demo and works as a one-command upgrade after git pull.
  • docs/traefik: the host ports are configurable through HTTP_PUBLISH and HTTPS_PUBLISH. The default is still 80 and 443. The README documents running behind a front proxy and explains which address to publish on.
  • docs/demo.md: host runbook covering:
    • the user and its rootless Docker, including the Compose plugin a new user lacks
    • the demo stack and its Traefik
    • the front proxy's TCP passthrough
    • the runner, created in the Forgejo UI, with its token kept in a separate private file, run as a systemd user service

A companion change to the front proxy (koto-edge) is committed there, not in this PR. It adds a file provider for the passthrough route, and allowacmebypass on the websecure entrypoint so that the demo's TLS-ALPN challenges reach the demo's Traefik.

Verified on the host

  • the front Traefik reaches the demo's Traefik on the host's public address (rootless Docker with gvisor-tap-vsock cannot reach the host's loopback, even with host loopback allowed)
  • the demo's Traefik obtains its Let's Encrypt certificates through the passthrough, once allowacmebypass is set on the front Traefik
  • git fetch with github.token and vars in if: work on this Forgejo
  • a push deploys, and check.sh passes (tested with a temporary branch trigger, since removed)

Lessons recorded in docs/demo.md

  • Behind Cloudflare, the DNS records must be DNS only; the proxy would end TLS before the passthrough.
  • Synapse image tags carry a leading v (v1.162.0); pin SYNAPSE_VERSION, since database upgrades cannot be undone.
  • The runner's systemd unit needs a PATH that reaches docker, its CLI plugins and git, because host-label jobs run in that environment.

Security

Any workflow in this repository can request the koto-demo label, including one added in a pull request. Treat everything the koto-demo user owns as exposed:

  • register the runner for this repository only
  • give the user no secrets beyond its runner token
  • keep other home directories unreadable to it

Follow-ups

  • init.sh tells you to restart Synapse even when deploy.sh runs it and already handles that.
  • check.sh reports "federation must be closed" as ok even when it cannot connect at all.
  • Reset the demo on a schedule, with seeded dummy data.
## Summary Adds continuous deployment of `main` to a public demo instance, which holds only dummy data. The demo shares a host with a real koto, so it runs as a separate user with rootless Docker. If a deploy is compromised, the damage stays inside that user. ``` host ├─ your user rootless Docker: front Traefik (owns :443) + your own koto └─ koto-demo (new user) rootless Docker: its own Traefik on <host address>:18443 + the demo forgejo-runner, label koto-demo, jobs run as this user ``` The front Traefik passes the demo's hostnames through by SNI without decrypting them. The demo's own Traefik terminates TLS and obtains its own certificates, exactly as in `docs/traefik`, so every deploy also checks that the documented setup works. ## Changes - **`.forgejo/workflows/deploy-demo.yml`**: runs on every push to `main`, or on demand, on a runner labelled `koto-demo`. It checks out the pushed commit in the demo's checkout and runs `deploy.sh`. The job is skipped unless the repository variable `DEMO_DIR` is set, so forks and other installs never queue a job that no runner will pick up. - **`scripts/deploy.sh`**: applies the current checkout to the running stack. It re-renders the config, pulls images, rebuilds and starts the stack, waits for Synapse to become healthy, then runs `check.sh`. When `homeserver.yaml` changed, it stops Synapse *before* starting the stack, rather than restarting it afterwards, so it never interrupts Synapse while Synapse creates or migrates its database. Images are pulled before anything is stopped, so a failed pull leaves the running stack untouched. It is not specific to the demo and works as a one-command upgrade after `git pull`. - **`docs/traefik`**: the host ports are configurable through `HTTP_PUBLISH` and `HTTPS_PUBLISH`. The default is still 80 and 443. The README documents running behind a front proxy and explains which address to publish on. - **`docs/demo.md`**: host runbook covering: - the user and its rootless Docker, including the Compose plugin a new user lacks - the demo stack and its Traefik - the front proxy's TCP passthrough - the runner, created in the Forgejo UI, with its token kept in a separate private file, run as a systemd user service A companion change to the front proxy (koto-edge) is committed there, not in this PR. It adds a file provider for the passthrough route, and `allowacmebypass` on the `websecure` entrypoint so that the demo's TLS-ALPN challenges reach the demo's Traefik. ## Verified on the host - [x] the front Traefik reaches the demo's Traefik on the host's public address (rootless Docker with `gvisor-tap-vsock` cannot reach the host's loopback, even with host loopback allowed) - [x] the demo's Traefik obtains its Let's Encrypt certificates through the passthrough, once `allowacmebypass` is set on the front Traefik - [x] `git fetch` with `github.token` and `vars` in `if:` work on this Forgejo - [x] a push deploys, and `check.sh` passes (tested with a temporary branch trigger, since removed) ## Lessons recorded in `docs/demo.md` - Behind Cloudflare, the DNS records must be *DNS only*; the proxy would end TLS before the passthrough. - Synapse image tags carry a leading `v` (`v1.162.0`); pin `SYNAPSE_VERSION`, since database upgrades cannot be undone. - The runner's systemd unit needs a `PATH` that reaches `docker`, its CLI plugins and `git`, because host-label jobs run in that environment. ## Security Any workflow in this repository can request the `koto-demo` label, including one added in a pull request. Treat everything the `koto-demo` user owns as exposed: - register the runner for this repository only - give the user no secrets beyond its runner token - keep other home directories unreadable to it ## Follow-ups - `init.sh` tells you to restart Synapse even when `deploy.sh` runs it and already handles that. - `check.sh` reports "federation must be closed" as `ok` even when it cannot connect at all. - Reset the demo on a schedule, with seeded dummy data.
- Let the example Traefik sit behind a proxy that passes TLS through
  - make the host ports it publishes configurable
  - keep 80 and 443 as the default
  - document the setup behind a front proxy
- Apply a checkout to the running stack in one step
  - re-render the config and rebuild the stack
  - restart Synapse only when its config changed
  - wait for Synapse, then probe the public endpoints
- Keep a public demo running the latest main
  - deploy on each push to main, or on demand
  - check out the pushed commit, then run the deploy script
  - skip unless the demo's directory is configured
- Document how the demo is set up and contained
  - run it as its own user with rootless Docker
  - pass its hostnames through the front Traefik by SNI
  - register a runner for this repository only
- Never interrupt Synapse while it sets up or migrates its database
  - stop it before starting the stack when its config changed
  - let the start bring it back with the new config
- Reach the demo the way rootless Docker allows
  - publish the demo's Traefik on the host's public address
  - drop the host loopback route, unreachable with gvisor-tap-vsock
  - explain where to publish in the example Traefik guide
- Record what the first deployment ran into
  - let the front Traefik pass ACME challenges through
  - keep DNS records unproxied behind Cloudflare
  - install the Compose plugin for the new user
- Follow how Forgejo Runner connects today
  - create the runner in Forgejo and declare it in the config
  - keep its token in a separate, private file
- Make the steps runnable as written
  - create the systemd unit and reload before enabling it
  - give jobs a PATH that reaches docker and its plugins
ci(demo): TEMPORARY deploy from the PR branch
All checks were successful
deploy-demo / deploy (push) Successful in 1m21s
4f2b5f1175
- Try the workflow before merging it
  - also deploy on pushes to the PR branch
  - revert this commit before merging
- Keep the pinned Synapse version pullable
  - give the version example its leading v, as image tags have
- Deploy the demo from main only, now that the workflow is proven
  - drop the temporary trigger on the PR branch
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
genji1024/koto!1
No description provided.