repositories / bugabinga.net
bugabinga.net
personal infrastructure for bugabinga!
owned by admin
services/luci/internal/web/assets/llms.txt
Raw# Luci CI
Luci is public, repository-agnostic CI. Repository configuration is read only from direct .ci/*.kdl files at the selected commit. Files and jobs retain lexical order; job names are unique.
## Configuration
A job needs a nonblank image and at least one run command. Jobs without triggers are manual-only.
~~~kdl
job "test" {
image "docker.io/library/node:${node}-alpine"
trigger {
push {
branch "trunk"
tag "v*"
include "src/**"
exclude "src/generated/**"
}
schedule "0 3 * * *"
}
matrix {
node "22" "24"
}
cache "npm" {
path ".npm-${node}"
key {
file "package-lock.json"
}
}
secret "TOKEN" env="SERVICE_TOKEN"
secret "KEY" file=".secrets/key"
run "npm ci"
run "npm test"
publish "site" {
from "dist"
to "project/${rev}"
}
}
~~~
Supported job children: image, run, trigger, matrix, cache, secret, mount, artifact, publish. Unknown nodes fail. Generic env, services, arbitrary host mounts, capabilities, and per-job resources are unsupported.
Triggers are additive. push accepts repeated branch, tag, include, and exclude globs. ** matches across path separators. A push runs when its ref matches and at least one changed path is included and not excluded. With no path filters, only the ref match applies. New refs compare the complete new tree. Manual runs ignore trigger filters.
One schedule may be declared per job. It is standard five-field cron in UTC, with minute precision. Luci reads schedules from the symbolic default branch and pins its commit when firing. There is no downtime backfill. Duplicate minutes are persisted, an already queued or running scheduled job coalesces later ticks, and effective frequency is limited to one run per 15 minutes. At most 64 scheduled parents may wait; excess due ticks are dropped. Push and manual work is claimed before schedules.
Matrices expand deterministically and permit at most 64 children. ${axis} interpolates in image, run, cache paths/key files, and publish fields; children receive CI_MATRIX_<UPPERCASE_AXIS>. Publish fields also support ${rev}, the full pinned commit.
Caches restore before commands and save after successful commands. An exact key hit restores that version; on a miss Luci restores the newest stored version of the same cache name, so toolchains that verify content (cargo fingerprints, npm tarball integrity) rebuild only the delta and repeatedly failing jobs stay warm. Keep cache contents self-validating for that reason. Cache paths must be workspace-relative; keep toolchain and dependency state inside the workspace so it is cacheable (for example CARGO_HOME=/work/.cargo-home or MISE_DATA_DIR=/work/.mise) and key caches by lockfiles. Each cache name keeps a bounded number of newest versions (operator default 3) and may have a byte budget; oldest entries are evicted first. Publishing follows successful cache saves. registry uses workspace-relative from OCI archive and registry to target; image is rejected. The archive must not exist before commands, cannot overlap cache or artifact paths, is staged as a bounded regular file, then copied daemonlessly with skopeo. On successful registry publication only, the child log emits `[luci] registry-published destination=<destination> digest=<sha256:...> reference=<repository>@<sha256:...>` from Skopeo's digest file. destination retains requested target; reference removes any requested tag. A pinned requested digest must equal Skopeo's actual manifest digest. This is machine-extractable publication evidence, not a deployment action. pkg and site read workspace-relative from and replace to beneath the operator-configured publication root. Missing adapter configuration fails the child.
Secrets use exactly one env or workspace-relative file target. Missing secrets fail before execution. Known values are masked best-effort. Never print secrets: repositories, logs, and artifacts are public, and job containers have network access.
artifact "set" contains one or more workspace-relative path children, plus optional true-only required and after-failure children. Successful children collect all sets; failed children only collect after-failure sets. Collection rejects traversal, symlinks, devices, sockets, secret-file overlap, and auth mounts; it stages immutable public files before visibility. A child allows at most 100 regular files, 10 MiB per file, and 50 MiB total across its sets. Artifact downloads are attachment responses with nosniff and CSP sandbox.
Durable mount sources live below <data>/mounts/<type>/<repo>/<job>/<name>. cache mounts are created owner-only and read-write; auth mount sources must already be owner-only and are read-write; secret binds are read-only. Luci serializes each durable mount with owner-only files below <data>/mount-locks/; temporary secret files never enter workspaces, artifacts, caches, or logs.
Each child receives a detached shallow Git checkout pinned to its revision. Git metadata is available, but no remote is configured.
Commands run in declaration order through a single /bin/sh -c. Each command is wrapped in a step marker: logs show "[luci] step N/M: <command>" and "[luci] step N/M done in Xs" (or "failed after Xs"), so slow commands are identifiable directly in the log. Children run sequentially with bounded timeout, memory, and CPU. The first failed child fails the parent and skips remaining children. A successful zero-child push means no job matched, not that checks ran.
## CLI
Read-only: luci status [--all]; luci runs (alias for status --all); luci repo <repo>; luci show [run-id|repo]; luci watch [run-id|repo] [--json] [--logs] [--timeout 30m]; luci log [run-id|repo] [<child-id or job>]; luci artifact [run-id|repo] [path]; luci docs; luci help.
Mutation: luci run <repo> <job> --ref <ref>; luci run <repo> <job> --rev <commit>.
Manual submission resolves the requested ref or revision, validates the requested job at that exact commit, then persists the canonical commit before enqueueing. The requested ref remains reporting provenance. Execution never resolves it again. Legacy queued manual events without a persisted resolved_rev fail safely and must be resubmitted. If repository object retention removes the pinned commit before execution, checkout fails rather than running another revision.
Children get positional ids (001, 002, …) shown by luci show; luci log accepts such an id or a unique job name, and lists children with their ids when the argument is omitted or ambiguous.
Nothing needs to be typed in full: show, watch, and log default to the latest run, a repository name selects its latest run, and a unique run-id prefix is accepted. watch resolves once then remains pinned to that run. It has no default timeout; --timeout requires a positive duration. --json emits one NDJSON object per snapshot, parent, child, log, or summary event. watch exits 0 for a successful run, 1 for an unsuccessful run, 2 for command or operational errors, 124 for timeout, and 130 when cancelled. In luci log a bare word selects the most recent run containing a job of that name.
## HTTP
/ is the dashboard. /repos/<repo> lists repository runs. /runs/<run-id> shows parent, children, details, and log tails. /runs/<run-id>/logs/<child-id> returns the complete plain-text log. /docs is the human reference. /llms.txt is this reference.
Run identifiers render as proquints: four dashed five-letter words (arXiv:0901.4016) such as kudab-lusab-babad-fugab. Displayed ids and revisions use the shortest prefix that stays unique among current runs, jj-style; the web emphasizes that prefix. luci show and luci log accept the proquint, its leading blocks, the decimal id, or a unique decimal id prefix. Web run URLs accept the proquint form too.
Diagnose failures in order: parent detail, child detail, log tail, complete log. Read the [luci] step markers to attribute time or locate the failing command, and the [luci] cache lines (exact, fallback, or miss) to explain slow cold runs. Run pages also show the commit subject, per-child queue wait, the step timing table, and the .ci/*.kdl files at the run revision. Match the displayed revision before drawing conclusions.
Public SSH commands are restricted to: ci status [--all], ci runs, ci repo <repo>, ci show [run-id|repo], ci watch [run-id|repo] [--json] [--logs] [--timeout duration], ci log [run-id|repo] [child-id or job], ci artifact [run-id|repo] [path], ci run <repo> <job> --ref <ref>|--rev <commit>, ci docs, and ci env-check. An empty SSH command or "help" prints the reference. Local-only operator commands include luci serve [--once]; SSH never permits serving.