# CI runners Until 2026-08-02 this repository had **zero registered runners and zero workflow runs**. Seven workflow files existed and none of them had ever executed — for any commit, on any branch, ever: ``` GET /api/v1/repos/andodeki/nigig-org/actions/runners -> [] GET /api/v1/repos/andodeki/nigig-org/actions/tasks -> {"total_count": 0} ``` A gate that never runs is not a gate. Registering the first runner immediately turned up four defects that had been sitting on `main`, described at the bottom of this file. ## Registering a runner `gitdab.com` is Gitea 1.22 (Actions API compatible with `forgejo-runner`). Any Linux host with Docker, or without it in host mode, will do. ### 1. Get a registration token Repo → Settings → Actions → Runners → "Create new runner", or: ```bash curl -H "Authorization: token $GITEA_TOKEN" \ https://gitdab.com/api/v1/repos/andodeki/nigig-org/actions/runners/registration-token ``` Note this instance answers `GET` on that endpoint; `POST` returns 405. ### 2. Install and register ```bash VERSION=6.3.1 curl -sSLo forgejo-runner \ "https://code.forgejo.org/forgejo/runner/releases/download/v${VERSION}/forgejo-runner-${VERSION}-linux-amd64" chmod +x forgejo-runner ./forgejo-runner register --no-interactive \ --instance https://gitdab.com \ --token "" \ --name "" \ --labels 'ubuntu-latest:docker://node:20-bookworm' ``` Every job in `.forgejo/workflows/` uses `runs-on: ubuntu-latest`, so that label must exist or nothing is ever scheduled. **Host mode** (no Docker) also works and is what the first runs used: ```bash --labels 'ubuntu-latest:host://-self-hosted' ``` Host mode runs jobs directly on the machine as the runner user. Jobs `sudo apt-get install` native dependencies and write to `$HOME/.cargo`, so use a disposable machine or a dedicated unprivileged user. Docker mode is preferable for anything long-lived. ### 3. Run it ```bash ./forgejo-runner generate-config > config.yml ./forgejo-runner daemon --config config.yml ``` Confirm with: ```bash curl -H "Authorization: token $GITEA_TOKEN" \ https://gitdab.com/api/v1/repos/andodeki/nigig-org/actions/runners ``` `"status": "active"` means it is polling. ## Action resolution — read this before adding `uses:` Forgejo resolves `uses: owner/name@ref` against **`data.forgejo.org`**, not `github.com`, and there is **no fallback**. An action that does not exist there fails the job in `Set up job`, which cancels every subsequent step — so the job reports failure having compiled nothing, and the log looks like an infrastructure blip rather than a config bug. Verify before committing: ```bash git ls-remote --heads https://data.forgejo.org// ``` Known state: | Action | Resolves? | |---|---| | `actions/checkout@v4` | yes | | `actions/setup-java@v4` | yes | | `actions/cache@v3` | yes | | `actions/setup-rust@v1` | **no** | | `android-actions/setup-android@v3` | **no** | Prefer an inline `run:` step over a third-party action. `sms.yml` installs the Android SDK with `cmdline-tools` directly for this reason. ## Reading job logs The REST API on this instance returns 404 for `/api/v1/repos/{owner}/{repo}/actions/tasks/{id}/logs`. The web UI's JSON endpoint works: ``` POST /andodeki/nigig-org/actions/runs//jobs//attempt/1 Content-Type: application/json {"logCursors":[{"step":0,"cursor":null,"expanded":true}, ...]} ``` `` is the 0-based position of the job in the workflow file, and `` is `run_number` from the tasks API — not the task id. Repeat with the returned `cursor` per step to page through output. ## What the first real runs found All were invisible defects on `main`, each fixed in its own commit: 1. **`android-actions/setup-android@v3` does not exist on data.forgejo.org.** The `android` job in `sms.yml` — the only job that compiles the ~600 lines of JNI behind `#[cfg(target_os = "android")]` — died in `Set up job` and cancelled all seven of its steps. 2. **Five of six `tools/*.sh` were committed mode 100644.** Every one is invoked as `./tools/.sh`; both `pay-domain.yml` jobs died with `Permission denied` at their first substantive step. Now gated in `repo-hygiene.yml`. 3. **`cargo-deny --config` was passed to the binary instead of to the `check` subcommand** in `pay-domain.yml`, so the dependency audit exited 2 without auditing anything, skipping the three boundary gates behind it. 4. **`repo-hygiene.yml` was never scheduled.** It was written as ```yaml on: push: pull_request: ``` Valid YAML, and the spelling GitHub documents for "all branches" — but this instance does not schedule it. The one workflow with no path filter, whose entire purpose is to run on every commit, had never run once. `on: [push, pull_request]` fixes it. Use the list form here. 5. **`actions/setup-rust@v1` does not exist** either, so `nigig-map.yml` fails in `Set up job`. Not fixed — the `map` crate does not compile on `main` regardless, so that workflow has more than a CI problem. ## Current state Latest result per job, all on a real runner: | Workflow | Job | Status | |---|---|---| | doc-engine.yml | engine, consumer | pass | | nigig-build.yml | supply-chain, full-crate-check | pass | | nigig-build.yml | cad-module, full-crate-check, supply-chain | pass | | nigig-map.yml | test | pass | | pay-domain.yml | isolated-payment-tests, payment-ui-tests | pass | | pdf.yml | engine, makepad-integration | pass | | pdf.yml | fuzz | skipped (schedule / workflow_dispatch only) | | repo-hygiene.yml | hygiene | pass | | sms.yml | gates, robius-sms, android, nigig-sms, supply-chain | pass | **Every job passes.** `pdf.yml/fuzz` is gated on `schedule || workflow_dispatch`, so `skipped` is its correct result on a push. That is a starting line, not a finish line — see below for what is green because it is genuinely healthy versus green because the gate is deliberately loose. ### Known-not-gated Things that are broken but deliberately not failing a build, so nobody mistakes silence for health: - **`nigig-map` unit tests**: ratchet at 9 failures, not a hard gate. 530 pass; the 9 are real logic bugs (4 `mvt_parser`, 1 `overpass_parser`, 4 `sprite` classification) that were invisible until the crate compiled. - **`nigig-map` test targets**: `tests/ui.rs` imports `makepad_widgets::makepad_test`; `tests/makepad_visual_tests.rs` and `benches/tile_decode_bench.rs` import `pub(crate)` modules, and `criterion` is not a declared dev-dependency. The workflow runs `--lib` only for that reason. - **`nigig-map` fmt/clippy**: report-only. 392 pre-existing fmt diffs in `src/` (rustfmt could not parse `view.rs` before, so it silently skipped the whole directory) and 132 clippy warnings. ### Runner capacity One runner with `capacity: 1` serialises every job; a push touching several filtered paths takes a while to drain. Raise `capacity`, or add runners, if that becomes annoying. ### Writing a ratchet step The runner executes `run:` blocks under `bash -e`. A bare ```bash out="$(cargo test ...)" ``` aborts the step the moment the command exits non-zero — which is always true while a ratchet baseline is above zero — so the comparison never runs and the step fails at exactly the count it was meant to allow. This bit nigig-map.yml on its first run. Use: ```bash status=0 out="$(cargo test ... 2>&1)" || status=$? ``` `|| status=$?` makes it a tested compound command, which `-e` exempts.