Nightly Distribution
What is published
A push to dev builds and tests four distribution artifacts on the amd64 Linux
runner. A successful run publishes one immutable Gitea prerelease tagged
nightly-YYYYMMDD-<commit>.
| Target | Release file | Distribution boundary | Execution gate |
|---|---|---|---|
| Linux x86-64 | pit-nightly-linux-x86_64.tar.gz | pit with a linker-injected root plus adjacent boot.qop content | Native full suites and CLI tests |
| Windows x86-64 | pit-nightly-windows-x86_64.tar.gz | pit.exe with a linker-injected Windows root plus adjacent boot.qop content | Full suites under Wine |
| Web wasm32 | pit-nightly-web-wasm32.tar.gz | Static HTML, JavaScript, Wasm, and web boot assets | Wasm suites under Node |
| Playdate simulator on Linux x86-64 | pit-nightly-playdate-simulator.tar.gz | A pit.pdx simulator application | Panic’s Linux simulator under Xvfb |
The runtime binary and content bundle have different jobs. Forge links the
target root into the binary at construction, so no runtime cart choice or tail
repack remains. Linux and Windows archives are self-contained distributions:
the tested binary is packaged unchanged beside its boot.qop content bundle.
The Playdate file is explicitly a Linux simulator build, not a device build. There is no macOS artifact until a macOS runner can build and execute it.
Compatibility manifest
Every release has a fifth attachment, compatibility.json. This is the
machine-readable answer to “which artifact can run where?” It records, for
each archive:
- target system and architecture;
- the target recipe and all profile axes (numeric representation, endian, text, record, GC, and Wota width);
- Mach ABI;
- how the boot media is delivered;
- the execution gate that passed;
- archive size and SHA-256.
The expected matrix lives in .gitea/nightly-targets.json. Each target archive
also carries BUILD.txt and an internal SHA256SUMS. The release fan-in
rejects a missing archive, an unexpected archive, metadata drift, a payload
checksum failure, or a commit mismatch before it contacts Gitea.
Publication transaction
The platform jobs are producers. They test, package, and upload temporary
Gitea Actions artifacts. publish-nightly depends on Linux, the hosted profile
matrix, Web, Windows, and Playdate simulator jobs, then:
- downloads the exact archives made by those jobs;
- validates and assembles
compatibility.json; - checks that
devstill names the candidate commit; - creates an immutable Gitea prerelease as a draft;
- uploads all five attachments;
- makes the release visible.
If upload fails, the release stays a resumable draft. If a newer push has
already advanced dev, the older candidate is treated as superseded and is
not published. The 14-day Actions artifacts are staging and diagnostics; the
Gitea prerelease is the durable distribution.
No workflow deletes an older release. Establish a retention rule after real artifact sizes and release frequency are visible; cleanup is an operational policy, not something a build should infer while publishing a new artifact.
Gitea 1.25 defaults to five release attachments, which is why checksums are inside each archive and the four archive hashes are carried by the fifth attachment rather than uploaded as a sixth file.
Local reproduction
The local Docker commands use the same amd64 Ubuntu image and committed Linux seed as the dedicated runner. The target seed includes its content stamp, so a clean job can prove it matches the checkout without regenerating it:
scripts/ci_local.sh image
scripts/ci_local.sh vm # Linux, Web, and Windows VM suites in parallel
scripts/ci_local.sh linux # full native Linux gate
scripts/ci_local.sh windows # full Wine gate
scripts/ci_local.sh web # full Wasm gate
On an Apple Silicon Mac, the default linux/amd64 lanes run through Docker
Desktop’s architecture emulation. The focused vm lanes are useful parity
checks, but a full lane can run one Pit turn slowly enough to trip the target’s
normal slow-turn watchdog. That is a host-speed failure, not evidence that the
same target failed natively. Do not relax the distributed runtime’s scheduler
policy to make emulation green: the dedicated native-amd64 runner is the
authoritative full-suite and release gate.
scripts/package_nightly.sh creates one target archive.
scripts/prepare_nightly.py validates all target archives and constructs the
release directory. scripts/publish_nightly.sh --dry-run verifies that
directory without needing a token or contacting Gitea.
Dedicated Alpine runner setup
The runner host must be amd64 and have enough free Docker storage for the roughly 1.7 GB toolchain image footprint plus build layers and concurrent job containers. Keep the runner private: Docker socket access is effectively root access to the host.
On Alpine, install and enable Docker if it is not already present:
apk add docker git
rc-update add docker default
service docker start
Create a Gitea personal access token with package-write permission, then log the host Docker client into the registry. Do not put the token in this repository or in shell history:
docker login gitea.pockle.world
The repository workflow .gitea/workflows/build-ci-image.yml builds and pushes
the image on the image-builder host whenever .gitea/ci.Dockerfile changes.
It publishes both an immutable revision tag, derived by
scripts/ci_image_tag.sh, and latest. Configure the runner labels to use
the host for image builds and latest for ordinary jobs:
runner:
labels:
- "image-builder"
- "pit-ci:docker://gitea.pockle.world/john/pit-ci:latest"
container:
force_pull: true
force_pull: true ensures a job sees the newest published image. The revision
tag remains available for rollback and for reproducing an older run. The
runner’s Docker credentials must be able to pull the private registry image;
the image-builder job’s registry login is sufficient when both labels use the
same host Docker daemon and user.
If act_runner itself runs in Docker, it must mount the host socket at
/var/run/docker.sock; if it runs as an Alpine service, its user needs access
to the socket. Restart the runner after changing its labels, then confirm in
Gitea Administration that it reports pit-ci and the desired capacity.
The release job uses Gitea’s short-lived built-in GITEA_TOKEN; no release PAT
is stored. It requests code: read, actions: read, and releases: write in
the workflow. Under Repository Settings → Actions → General, confirm that
the configured maximum job-token permission allows release writes. Gitea
clamps the workflow request to that maximum.
Before the first push, check Gitea’s [repository.release] settings. The
defaults in 1.25 allow five files up to 2048 MB each, which matches this release
shape. A custom ALLOWED_TYPES, MAX_FILES, or FILE_MAX_SIZE must still
allow the four .tar.gz files and compatibility.json.
Server handoff checklist
Before changing the server, collect these facts:
uname -m
cat /etc/alpine-release
getconf _NPROCESSORS_ONLN
grep MemTotal /proc/meminfo
docker version
docker info --format '{{.Architecture}} {{.DockerRootDir}}'
df -h
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
Then locate the active act_runner config.yaml and determine whether the
runner is an OpenRC service or a Docker container. Those two facts decide the
exact restart command; they should be inspected rather than guessed.