CI: build with the Crafter.Build Flatpak on a repo-scoped privileged runner
Some checks failed
Deploy / build-deploy (push) Failing after 6m19s

Crafter.Build's CI publishes only the Flatpak since 2026-10-02, so the
rolling `latest` tarball the Install crafter-build step downloaded is gone
and the 2026-10-12 deploy failed with a 404 before building anything.

Take the route imsd's package job takes. The job runs on `flatpak`, a
privileged runner registered to this repository alone
(forgejo-runner-catcrafts on the box, same arrangement as imsd's), with the
installed Flatpak and its Sdk kept on a docker volume between runs. The
container installs nothing that compiles any more: clang, lld, libc++, the
WASI sysroot, cmake, perl, gpg and OpenSSL all come with the Flatpak and its
Sdk. `crafter-build` is a wrapper around `flatpak run`, `crafter-run` runs a
built binary inside the sandbox, cast is installed under /opt where the
sandbox can see it, and the cache path is the Flatpak's own.

The server binary links the Flatpak's libc++, libc++abi and libunwind, which
are newer than the host's, so the deploy ships them next to the binary the
way it already ships libmsquic; the rpath finds them there. They need at
most GLIBC_2.38, the host has 2.41.

ShouldMatchBankTransfers passed braced lists straight into a std::span
parameter. That compiled only because libc++ 22 had the C++26
span(initializer_list) constructor; the libc++ 23 in the Flatpak does not,
so the lists now go through a vector like the rest of the file.

deploy/README.md documents the runner, the sandbox's private /tmp and how
to run a built binary where the host lacks a matching libc++.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-12 16:32:57 +02:00
commit 0bfa715e6a
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
3 changed files with 181 additions and 44 deletions

View file

@ -82,11 +82,48 @@ systemctl reload caddy
## Runner configuration
Both mounts are required. In the Forgejo runner's `config.yaml`:
The deploy job runs on a privileged runner registered to this repository alone
(`runs-on: flatpak`), not on the shared `arch-latest` runner. crafter-build is
the Crafter.Build Flatpak, and bubblewrap inside the job container needs the
privilege; imsd's package job is set up the same way. On the box since
2026-10-12:
* `/etc/forgejo-runner/config-catcrafts.yaml`: label
`flatpak:docker://archlinux:latest`, `privileged: true`, cache proxy on port
34570.
* `forgejo-runner-catcrafts.service`, a copy of the imsd unit, working
directory `/var/lib/forgejo-runner-catcrafts`. Its `.runner` is the
registration: scope `Catcrafts/catcrafts.net`, name
`cx43-catcrafts-net-privileged`.
* docker volume `catcrafts-flatpak-system`, mounted at `/var/lib/flatpak` in
the job container: the installed Flatpak and its Sdk, about 4 GB, so a
deploy downloads neither. Deleting the volume only costs the next run that
download.
To register it afresh (a lost `.runner`): as the forgejo user in
`/var/lib/forgejo`, `forgejo -c /etc/forgejo/app.ini actions
generate-runner-token --scope Catcrafts/catcrafts.net`, then as forgejo-runner
in the working directory, `forgejo-runner register --no-interactive -c
/etc/forgejo-runner/config-catcrafts.yaml --instance
https://forgejo.catcrafts.net --token <token> --name
cx43-catcrafts-net-privileged`.
Inside the crafter-build sandbox the job container's top-level directories are
visible at the same paths (`/deploy-app`, `/workspace`, `/opt`, `/root`), but
`/tmp` is the sandbox's own: a file a step writes under `/tmp` does not exist
for a suite, and the other way round. The media mirror lives on `/deploy-app`
and cast is installed under `/opt` for that reason.
Both deploy mounts are required, in that runner's config:
```yaml
container:
options: "-v /srv/catcrafts.net:/deploy -v /srv/catcrafts-app:/deploy-app"
privileged: true
options: "--volume /var/www/catcrafts.net:/deploy --volume /srv/catcrafts-app:/deploy-app --volume catcrafts-flatpak-system:/var/lib/flatpak"
valid_volumes:
- /var/www/catcrafts.net
- /srv/catcrafts-app
- catcrafts-flatpak-*
```
The deploy steps fail with an explanatory message if either is missing, rather
@ -103,9 +140,12 @@ reacts.
`catcrafts-deploy.service` runs `catcrafts-server --render /` as `ExecStartPre`
before restarting. The unit tests already gated the deploy in CI
(`crafter-build test --product=server`); this on-host check covers what CI
cannot — the dynamic loader finding libmsquic on this machine and a page
rendering from the deployed content — so **a broken binary leaves the working
server running** instead of replacing it.
cannot — the dynamic loader finding libmsquic, libc++, libc++abi and libunwind
next to the binary on this machine and a page rendering from the deployed
content — so **a broken binary leaves the working server running** instead of
replacing it. The binary links the Flatpak's libc++, not the host's, which is
why the deploy ships those three libraries into `/srv/catcrafts-app` alongside
libmsquic; the rpath from `project.cpp` finds them there.
The binary is installed as `catcrafts-server.new` and `mv`d into place, so a
request arriving mid-copy never hits a truncated executable.
@ -992,6 +1032,12 @@ crafter-build test --product=server # every suite: the unit assertions
<server>/catcrafts-server --orders FILE # the orders ledger + manual transitions
```
`crafter-build` is the Crafter.Build Flatpak (`net.catcrafts.CrafterBuild`),
and a server binary it builds links the Flatpak's libc++ rather than the
host's. Where the host has no matching libc++, run the binary inside the
sandbox, which is what CI's `crafter-run` wrapper does:
`flatpak run --command=$PWD/<server>/catcrafts-server net.catcrafts.CrafterBuild --routes`.
### If a `bin/` glob matches two directories
The variant directory name embeds a config hash, and **`` and