Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

ci/forge/README.md

dwarf's CI: the dwarves forge is the build server

Since builder-64 was deleted (2026-10-27), the constellation's CI runs in dwarves itself: dwarves serve with the [ci] project in ci.dwarves.toml (crates/dwarves/src/forge_ci.rs, the Explorer lane in crates/dwarves/src/explorer.rs). There is no cron script and no shell runner. git.gunnar.rs has no Actions, so the .github/ and .forgejo/ workflow files of the repositories run nowhere.

What it does

The way to oden: a step container has HOME=/tmp and no keys, so the rows set DWARVES_CI_BENCH_SSH = "ssh -F /home/rickard/.config/dwarves-ci/bench-ssh/config", and that directory is in DWARVES_RUNA_BINDS. setup.sh makes it: a key of the forge's own (id_ed25519, never copied anywhere), a config naming Host oden (100.115.167.95, user rickard, that key, its own known_hosts, BatchMode), and it prints the public key, which goes into oden's ~/.ssh/authorized_keys once. The seeds live on oden in ~/bench-seeds/gunnar (a bare git.git, later linux) and ~/bench-seeds/holger (the real-client package sets); the repositories' bench-readme run says what it needs there.

The CI turn at the KVM gate

Gates hand ~/locks/kvm-gate.lock to one another in the same second, so a CI run that only waited for a free lock waited for hours (2026-10-30). Now (lane CI-RED, SC-GATE-STARVE):

CI provenance is not release provenance

A CI run writes no release provenance, and nothing it builds is a release. Releases are dwarves release-build (crate dwarves-slsa): builder https://git.gunnar.rs/nordisk/dwarves/slsa/builders/dwarves-server/v1, signed with the owner's YubiKey, SLSA Build L3. If a CI build writes provenance, its builder id is https://git.gunnar.rs/nordisk/dwarves/slsa/builders/dwarves-ci/v1. That provenance is signed with a software key and is SLSA L2 at most.

Set up, stop, start

# build (ci4, in a lane tree), then install once:
cargo build -p dwarves --no-default-features --features cli,server,runs-on-container --bin dwarves
sh ci/forge/setup.sh <target>/debug/dwarves

systemctl --user stop dwarves-ci          # stop the forge (its own processes only)
systemctl --user start dwarves-ci         # start it again
systemctl --user disable --now dwarves-ci dwarves-ci-podman   # turn CI off
journalctl --user -u dwarves-ci -f        # what it does

# run a pipeline now, without waiting for its push or its night:
touch ~/.dwarves-ci/warehouse/ci/ci/run-now/scorecard-gunnar

To see what a poll would do without running anything, set DWARVES_CI_DRY=1 in ~/.config/dwarves-ci/forge.env and restart the forge. The poll then fetches and logs DRY — would queue …, and it queues nothing.

A run's containers belong to the forge's own podman service (unix:///run/user/1001/dwarves-ci/podman.sock). They are named draupnir-runa-<forge pid>-<n>; a run the forge was stopped in keeps its container until it is stopped. To stop a run that is in progress, stop only those: podman --url unix:///run/user/1001/dwarves-ci/podman.sock ps --filter name=draupnir-runa-<pid>-.

The box's settings (forge.env)

env meaning
NORDISK_ROOT the checkouts (~/tgt/ci/src/<repo>)
DWARVES_CI_TARGETS, DWARVES_CI_TARGET_CAP_GB per-repo CARGO_TARGET_DIR and their cap (before and during a run)
DWARVES_CI_MIN_FREE_GB a run is stopped when the disk has less free
DWARVES_CI_OUT per-run NORNIR_CI_OUT (receipt, step logs, summary)
DWARVES_CI_STATUS the status checkout the forge commits and pushes
DWARVES_CI_RED_LOG one line per red run
DWARVES_CI_YIELD locks the forge yields to (a gate's)
DWARVES_CI_SLOTS the build slots a run holds one of
DWARVES_CI_DRY 1: poll and say, run nothing
DWARVES_RUNA_IMAGE, DWARVES_RUNA_USER=keep-id, DWARVES_RUNA_BINDS, DOCKER_HOST where and as whom a step's container runs

The step image is ci-forge.Containerfile: ci4 (by digest) plus Debian's Chromium, so the quick sets that run browser tests in the step container (holger-ui, monetize) find it at $CHROME (lane CI-RED, 2026-10-30; ci4 alone left holger-ui-ci red with "chromium starts: No such file or directory"). forge.env pins it by its digest; setup.sh builds DWARVES_CI_IMAGE_TAG when the box lacks that digest. Release builds keep ci4.

A pipeline may name its own step image, by digest, with image = "…@sha256:…" in its [[pipelines]] row; the project check refuses a tag. heimdall-ci does (lane heimci, 2026-10-30): heimdall's cargo xtask ci --quick runs its scripts/ci.sh (fmt, clippy, tests, the libseccomp shim, cargo deny, fuzz smoke runs) in nordisk-cargo-toolchain:heim-ci, built from heimdall's own ci/ci.Containerfile. setup.sh builds that tag from the checkout when the box lacks the pinned digest. The shared step image is not changed for it.

A pipeline may name tools the box keeps for it, tools = ["<name>"]: the directory <warehouse>/ci/tools/<name> (or DWARVES_CI_TOOLS/<name>) is bound read-only at /opt/ci-tools/<name> in its steps; a tool the box lacks refuses the run by name. tunnr-drift (lane HEIM-TOOLING, Heimdall tooling plan step 6) is the syscall-profile DRIFT GATE: on every head of tunnr's main it builds each measured row ([[build]] in .nornir/profiles/<row>/measure.toml) and runs cargo bt measure gate --build against the row-pinned heimdall (tools = ["heimdall"], HEIMDALL_TOOL=/opt/ci-tools/heimdall/heimdall). A call a checked-in profile does not allow fails the run, named by row, program, call and test. It only checks; a profile changes by measure syscalls <row> and a reviewed commit. setup.sh builds the tool in heim-ci when the box lacks it and warns when its sha256 is not the rows' pin (builds of the same version differ between hosts). The probe needs no privileges: it runs in the plain step container (default seccomp, no-new-privileges).

gunnar-ci and holger-ci set TMPDIR=/backup/ci-tmp/<repo> (a bind in DWARVES_RUNA_BINDS): Gunnar's server keeps a 5%,2GiB disk floor, which on the root array (3.7 TB at 96 %) is 186 GiB, more than is free, so with TMPDIR there 240 of its tests were refused "storage is full" (lane CI-FIX, 2026-10-29).