Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions docs/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ jobs through the Docker socket.
[Adding a host](ADDING-A-HOST.md) sections 1-3. The installer itself
additionally requires a systemd-based host with `python3`, `tar`,
`install`, `flock`, and standard coreutils — it fails closed with a
named missing command if one is absent.
named missing command if one is absent. The Docker host architecture
must be `amd64` or `arm64`; the runner image build rejects every other
architecture.
2. On the **fleet host**, clone the public engine repository and check
out the exact reviewed engine commit you intend to pin — later steps
copy templates from it and run its installer as root, so it must be
Expand Down Expand Up @@ -107,7 +109,7 @@ jobs through the Docker socket.
repository name separately with `--repository` so
`allowed_repositories` names a repository that exists. Push both
branches to a new **private** GitHub repository, open a pull request
from `initialize-fleet` to `main`, and **merge it**. Managed controller managed controller
from `initialize-fleet` to `main`, and **merge it**. Managed controller
lifecycle is permitted only from a reviewed, merged private
configuration commit. Resolve and record that merge commit SHA
(`RESOLVED_CONFIG_COMMIT` below); do not install from an unmerged
Expand Down Expand Up @@ -164,10 +166,21 @@ onboarding never touches a fleet host.
steps:
- run: echo "fleet proof on ${RUNNER_NAME:-unknown}"
```
This controlled-first-job path is valid only when the staged proof is
the only workflow that can target the shared label. If the repository is
active or has any other matching trigger, stop: use a dedicated empty
proof repository and runner group for Steps 1-3, then onboard the active
repository through [Adding a project](ADDING-A-PROJECT.md).
2. Confirm the repository you passed as `--repository` to `init.sh` is
in the pool's `allowed_repositories` (the initializer put it there;
add it only if you skipped initialization), and validate with
`./scripts/validate.sh --strict`.
`./scripts/validate.sh --strict`. Immediately before authorization,
audit every queued Actions job across every repository already authorized
for the runner group and this repository. Cancel every queued job whose
complete `runs-on` expression could match the pool's shared label; do not
authorize until that matching queue is empty. If you cannot prove that
Comment thread
Nickfost marked this conversation as resolved.
condition, use a dedicated empty proof repository and runner group rather
than exposing the pool to an unknown first job.
3. Authorize the repository in the GitHub runner group.

Full contract: [Adding a project](ADDING-A-PROJECT.md).
Expand All @@ -177,11 +190,11 @@ Full contract: [Adding a project](ADDING-A-PROJECT.md).
Runs on: dispatched from GitHub, but steps execute inside an ephemeral
runner on your fleet host with host-root-equivalent Docker access.
Changes state: yes, transiently — the job creates a runner container and
may create Docker containers, networks, and volumes; the proof below
verifies all of it is cleaned up.
the proof below verifies that it is cleaned up. The supplied proof job does
not invoke project Compose or create project containers, networks, or volumes.

1. Dispatch the staged proof workflow and record its run ID from the
run URL (`GITHUB_RUN_ID` scopes the residue checks below). Its explicit
1. Dispatch the staged proof workflow and record its run ID from the run
metadata. Its explicit
`permissions: contents: read` and `timeout-minutes: 5` keep the job
from inheriting a read-write `GITHUB_TOKEN` default or occupying the
single runner past the ordinary-CI ceiling. Do not copy a nested
Expand All @@ -206,15 +219,6 @@ verifies all of it is cleaned up.
sudo docker ps -aq \
--filter label=io.randomdevelopment.ci-fleet.managed=true \
--filter label=io.randomdevelopment.ci-fleet.kind=runner
# No resources owned by the dispatched proof run remain. Compliant
# jobs name Compose projects ci-<repo>-<run-id>-<attempt>-<task>-<shard>,
# and Docker prefixes container names with '/', so anchor accordingly
# and scope to this run ID (host-wide queries would also match a
# concurrent unrelated run):
RUN_ID=<dispatched-run-id>
sudo docker ps -a --filter "name=^/ci-.*-${RUN_ID}-" --format '{{.Names}}'
sudo docker network ls --filter "name=^ci-.*-${RUN_ID}-" --format '{{.Name}}'
sudo docker volume ls -q --filter "name=^ci-.*-${RUN_ID}-"
# Run a fresh health evaluation, then check installed state:
sudo systemctl start ci-fleet-health.service
sudo systemctl is-failed ci-fleet-health.service # expect: inactive/failed must NOT be failed
Expand All @@ -225,7 +229,7 @@ verifies all of it is cleaned up.

The health service is a timer-driven oneshot — `systemctl status`
alone only shows its previous run, so start it explicitly after the
proof job. Every resource listing above must be empty and the check
proof job. The runner-container listing above must be empty and the check
must report `CHECK_OK`. The [Live pilot runbook](LIVE-PILOT.md)
documents the full isolated first-job proof including read-only job
permissions.
Expand Down
19 changes: 19 additions & 0 deletions scripts/test_quickstart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env python3
from pathlib import Path

raw_quickstart = (Path(__file__).resolve().parents[1] / "docs" / "QUICKSTART.md").read_text()
quickstart = " ".join(raw_quickstart.split())

required = (
"host architecture must be `amd64` or `arm64`",
"Cancel every queued job",
"across every repository already authorized for the runner group",
"the only workflow that can target the shared label",
)
for text in required:
assert text in quickstart, f"quickstart safety contract missing: {text}"

assert quickstart.index("Cancel every queued job") < quickstart.index("3. Authorize the repository")
assert "PROJECT_PREFIX=" not in raw_quickstart
assert "managed controller managed controller" not in quickstart
print("quickstart_contract=PASS")
4 changes: 3 additions & 1 deletion scripts/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ python3 -m py_compile \
scripts/health.py \
scripts/scan_committed_secrets.py \
scripts/test_desired_state.py \
scripts/test_health.py
scripts/test_health.py \
scripts/test_quickstart.py
python3 .github/actions/plan/test_plan.py
python3 scripts/test_desired_state.py
python3 scripts/test_health.py
python3 scripts/test_quickstart.py
python3 .github/actions/plan/plan.py --plan examples/project/scripts/ci/plan.json --group fast >/dev/null
python3 .github/actions/plan/plan.py --plan examples/project/scripts/ci/plan.json --group full >/dev/null
scripts/test-capacity-preflight.sh
Expand Down