build(ci,deploy)!: the runner at home in a nightly window, deploys by hand (ADR 0039, D6, D13, D16)
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
The CI runner moves to halkyon.li's host at home and runs from 02:00 to 05:00 alone: a timer starts it, its unit's RuntimeMaxSec ends it, and night.sh rests the instance's heavy sidecars around it as deploy, while Caddy, PostgreSQL and the apps' servers keep serving. Jobs pushed during the day wait in Forgejo's queue. Its sandbox fits the new host: its folder on the HDD, the rest of /data hidden, containerd's store out of reach, no private IPv4 network and no IPv6 at all (the home network's IPv6 addresses are public ones under a changing prefix, on the internet's own interface), 5 GB of memory with swap beyond. The Sandbox step checks the tunnel, the home router and IPv6. BREAKING CHANGE: CI deploys nothing any more: the deploy job goes with learn.halkyon.eu's server, and halkyon.li deploys by hand with redeploy.sh once a commit's night's run passed. Jobs run on the runner labelled balthasar; sahaquiel's runner picks up none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
2218695dcf
commit
c8d67b1eb7
1 changed files with 401 additions and 76 deletions
|
|
@ -28,8 +28,8 @@ Halkyon's apps and the packages they share, in one repository and one pub worksp
|
|||
- **Before keeping a framework default, name the alternatives** and recommend one, especially where a choice becomes expensive once users or data exist.
|
||||
- Every decision carries its reason (in `NEXT.md` or an ADR under `docs/adr/`).
|
||||
- **Commits** are GPG-signed and carry a DCO `Signed-off-by` trailer (`git commit -s`; a local `prepare-commit-msg` hook adds it automatically), and **keep their type prefix** (`feat`, `fix`, `perf`, `refactor`, `docs`, `build`, `chore`, a `!` or a `BREAKING CHANGE:` line for what breaks): releases work out their versions from them (Learn's `AGENTS.md`, *Releases*). A commit whose change people using an app would notice says so for them in its trailers, `Whats-New: <one sentence>` and `Whats-New-De: <its German>`, once a change, short (a release's lines share F-Droid's 500 characters) and in the app's own names for its screens and buttons; the release drafts its *What's new* from them alone (Train's ADR 0030, D61, 4 Nov 2026). A commit scoped to apps (`feat(todo,meditate):`) counts for those alone, a shared package's for every app it touches; a refactor, a build change or a test carries none.
|
||||
- **Servers are named in prose by the domain they serve** (learn.halkyon.eu's server, the halkyon.app server), which says what a reader needs; the SSH aliases and the CI runner's label keep the servers' own names (`sahaquiel-deploy`, `ramiel-fdroid`, `sahaquiel:host`).
|
||||
- **Pushes** pass the checks first, through a local `pre-push` hook (in a fresh clone: `ln -s ../../tool/pre-push .git/hooks/pre-push`): `tool/pre-push` first refuses a push to `main` that brings a commit not signed by a key in `deploy/allowed-signers` (`tool/check_signatures.sh`, the deploy's own rule), so it fails on the laptop rather than after CI, then runs `tool/check.dart`, but for a push of release commits alone (the release tool's `chore(release):` commits changing an app's version line and its texts of what's new, nothing else: `tool/release_push.dart`, decided 3 Nov 2026, so that releases in a row don't run the checks once each; CI still runs them). CI runs the checks again, and a green `main` deploys learn.halkyon.eu (see *CI and deploys*), so an unsigned commit on `main` blocks every deploy after it.
|
||||
- **Servers are named in prose by the domain they serve** (halkyon.li's host at home, the halkyon.app server), which says what a reader needs; the SSH aliases and the CI runner's label keep the machines' own names (`balthasar-deploy`, `ramiel`, `ramiel-fdroid`, `balthasar:host`). learn.halkyon.eu's server, `sahaquiel`, retires in November 2026 (ADR 0039).
|
||||
- **Pushes** pass the checks first, through a local `pre-push` hook (in a fresh clone: `ln -s ../../tool/pre-push .git/hooks/pre-push`): `tool/pre-push` first refuses a push to `main` that brings a commit not signed by a key in `deploy/allowed-signers` (`tool/check_signatures.sh`, the deploy's own rule), so it fails on the laptop rather than after CI, then runs `tool/check.dart`, but for a push of release commits alone (the release tool's `chore(release):` commits changing an app's version line and its texts of what's new, nothing else: `tool/release_push.dart`, decided 3 Nov 2026, so that releases in a row don't run the checks once each; CI still runs them). CI runs the checks again, and `main` is deployed to halkyon.li by hand once its night's run passed (see *CI and deploys*), so an unsigned commit on `main` blocks every deploy after it.
|
||||
- **Lints**: LeanCode's strict set (`leancode_lint`), configured once in the root `analysis_options.yaml`, which also enables its analyzer plugin. Plugins can only be enabled at the root of a pub workspace and only reach packages whose options include that file: Learn's, Todo's and Train's servers and apps, Learn's curator and every shared package include it; the generated client packages keep their own, but for the module's, which includes it for its SRP half and leaves `lib/src/protocol/` out. Every `// ignore:` gets a comment saying why (`document_ignores`). Each server package switches off the four rules for Dart v3.13's constructor syntax (`use_primary_constructors`, `use_declaring_parameters`, `empty_container_bodies`, `unnecessary_type_name_in_constructor`): Serverpod's code generator (checked with v4.0.3) can't parse that syntax in endpoint files yet.
|
||||
|
||||
## Flutter and Serverpod
|
||||
|
|
@ -88,5 +88,5 @@ Checklist after doing changes, in this order:
|
|||
|
||||
## CI and deploys
|
||||
|
||||
- **CI** (`.forgejo/workflows/ci.yaml`) runs `dart run tool/check.dart` on every push of a branch (not of a tag: a release pushes its tag right after `main`, on the same commit), on the Forgejo runner on learn.halkyon.eu's server (`deploy/runner/`), except for a commit that passed before: the job marks passed commits in the runner's home (`checked/`, kept 90 days), so a branch's commit fast-forwarded into `main` goes straight to its deploy. It checks every package, whichever a push changed. Jobs run directly on the host as the unprivileged user `runner`, never in containers: Docker access would be root on the production server. The runner's systemd unit keeps jobs away from production (no `/home`, no Docker socket, Docker's container networks unreachable, memory and CPU capped); keep it that way when changing it. `runner` has user ID 61000 and must never share one with a container's processes (PostgreSQL and Learn's server run as 999, not remapped): the same user ID can read their environment and files. The job's first step (*Sandbox*) fails if any of this leaks. Its second (*Leftovers*) stops whatever an earlier job left running in a job's working folder: the runner (forgejo-runner v13.1.0) kills a cancelled or timed-out step's process group and runs no later step, not even an `if: always()` one, and on 22 Oct 2026 a cancelled job still left its 4 GB analysis server behind; with one job at a time, any such process is a leftover. Flutter is pinned in the workflow (`FLUTTER_VERSION`): move it together with the local one. Every server package's tests get throwaway passwords from the environment (`SERVERPOD_…`, new for every job), since no `passwords.yaml` is ever committed; without them they exit with code 1 and no message (Serverpod's own template does the same). CI installs the Serverpod CLI at `SERVERPOD_VERSION` (which the root's tests hold to the lock's Serverpod), so the checks' generated-code step fails there too if the committed generated code of any server differs from what the generator makes, as it does on the laptop since 23 Oct 2026 (before, only CI compared, and a push went red for an endpoint's changed comment). So never run `dart format` over a server's `lib/` or `test/` as a whole: it reformats the generated code (`lib/src/generated/`, `serverpod_test_tools.dart`); format the files you wrote. CI runs on pushes only: a pull request from a fork would run its code on the production server.
|
||||
- **A green `main` deploys learn.halkyon.eu**: this repository's `main`, whichever app or package a push changed, through `deploy/redeploy.sh` (Learn's `AGENTS.md`, *CI and deploys*, with the rules a deploy checks, the sidecars, the backups and the releases).
|
||||
- **CI** (`.forgejo/workflows/ci.yaml`) runs `dart run tool/check.dart` on every push of a branch (not of a tag: a release pushes its tag right after `main`, on the same commit), on the Forgejo runner on halkyon.li's host at home (`deploy/runner/`; ADR 0039, D6, D16), in a nightly window alone, 02:00 to 05:00 (`forgejo-runner.timer` starts it, its unit's `RuntimeMaxSec` ends it), while `deploy/night.sh` rests the instance's heavy sidecars: what is pushed during the day waits in Forgejo's queue. A commit that passed before isn't checked again: the job marks passed commits in the runner's home (`checked/`, kept 90 days). It checks every package, whichever a push changed. Jobs run directly on the host as the unprivileged user `runner`, never in containers: Docker access would be root on the instance's host. The runner's systemd unit keeps jobs away from the instance and the home network (no `/home`, nothing of `/data` but its own folder, no Docker or containerd socket, no private IPv4 network and no IPv6 at all, as the home network's IPv6 addresses are public ones under a changing prefix; memory, swap and CPU capped so the instance keeps serving through the night); keep it that way when changing it. `runner` has user ID 61000 and must never share one with a container's processes (PostgreSQL and Learn's server run as 999, not remapped): the same user ID can read their environment and files. The job's first step (*Sandbox*) fails if any of this leaks. Its second (*Leftovers*) stops whatever an earlier job left running in a job's working folder: the runner (forgejo-runner v13.1.0) kills a cancelled or timed-out step's process group and runs no later step, not even an `if: always()` one, and on 22 Oct 2026 a cancelled job still left its 4 GB analysis server behind; with one job at a time, any such process is a leftover. Flutter is pinned in the workflow (`FLUTTER_VERSION`): move it together with the local one. Every server package's tests get throwaway passwords from the environment (`SERVERPOD_…`, new for every job), since no `passwords.yaml` is ever committed; without them they exit with code 1 and no message (Serverpod's own template does the same). CI installs the Serverpod CLI at `SERVERPOD_VERSION` (which the root's tests hold to the lock's Serverpod), so the checks' generated-code step fails there too if the committed generated code of any server differs from what the generator makes, as it does on the laptop since 23 Oct 2026 (before, only CI compared, and a push went red for an endpoint's changed comment). So never run `dart format` over a server's `lib/` or `test/` as a whole: it reformats the generated code (`lib/src/generated/`, `serverpod_test_tools.dart`); format the files you wrote. CI runs on pushes only: a pull request from a fork would run its code on the instance's host.
|
||||
- **halkyon.li deploys by hand** (ADR 0039, D13): this repository's `main`, whichever app or package a push changed, through `deploy/redeploy.sh`, run with a commit once its night's run passed (Learn's `AGENTS.md`, *CI and deploys*, with the rules a deploy checks, the sidecars, the backups and the releases). No job of CI deploys.
|
||||
|
|
|
|||
Loading…
Reference in a new issue