Monorepo for the intar platform:
crates/contains the Rust workspace for the agent, kino, stargate, and shared contracts.apps/web/contains the Cloudflare-hosted web and control plane.content/contains authored Course and Scenario source locks.
Tasks are defined in env.cue and run with cuenv
0.57.3. cuenv task lists them. Common commands:
cuenv task install-js
cuenv task check
cuenv task test
cuenv task build
cuenv task check-generatedcuenv task ci runs every CI lane, and cuenv task lanes.<name> runs one
(rust, security, images, web, or docs). The workflows are rendered
from CUE; see ci/README.md.
cuenv task clean-generated removes only allowlisted repository output. It preserves
the root node_modules/, Bun cache, Cargo cache, and root target/.
The website database is schema-first. Files under apps/web/src/db/schema/
are the only source of truth, and every file under apps/web/migrations/ must
be generated by Drizzle Kit and committed with its generated meta/ files.
Never hand-edit migration SQL or metadata, create custom migrations, or add SQL
triggers. Run bun run --cwd apps/web db:generate after a typed schema change
and bun run check:database-migrations before committing it.
content/courses/ contains Markdown-first Course source. Each Course contains
course.md and ordered Lecture directories. Each Lecture contains lecture.md
and can contain a technical scenario.hcl. The base image catalog remains at
content/scenarios/base-images.hcl.
The agent, builder, image CLI, kino, and Stargate are released as GitHub
releases tagged <prefix>/vX.Y.Z. Nothing is published to crates.io or any
other registry.
- Release pull request: on every push to main, the Release workflow
rebuilds
release/next. git-cliff reads the Conventional Commits since each product's latest tag, scoped to the paths intools/workflows/release/products.json. Main takes only squash merges named after the pull request title ("Merge settings" inci/README.md), so each commit is a pull request title, which CI'spr-titlejob checks. Afeatbumps the minor version, and afix,perf,refactor, orbuildbumps the patch version. A breaking change bumps the minor version while a product is on 0.x. The rootCargo.tomlandCargo.lockbelong to no product, so afeat(deps),fix(deps), orbuild(deps)commit that changes either of them, with or without other files, also counts for each product that ships what it changed (tools/workflows/release/deps-commits.py): aCargo.lockpackage in the dependency closure of the product's crates, including the jailer and jailerd in the agent's archive, or a[workspace.dependencies]entry those crates use directly. Any other rootCargo.tomlchange, such as a profile or a patch, counts for every product. Such a commit bumps the product like any other and gets a line in its changelog. The pull request bumps each changed product'sCargo.tomlversion andCargo.lockentry, and prepends itsCHANGELOG.md. The workflow tags before it rebuilds the pull request; should tagging fail, a product whose manifest version is not tagged yet is left out until the first run after its tag, so no version is skipped. The app cannot move the branch across a change to.github/workflows, so such a push to main replaces the pull request with a new one. - Dry run: the merge queue builds and smoke-tests every product whose version a pull request changes, the release pull request included, with the release build's own steps. A change to the release build dry-runs every product on the pull request, and again in the merge queue only when main changed the Rust code under it.
- Publishing: when the merge queue lands that pull request, the Release workflow tags each new version on main and opens a draft release with that version's changelog section as its notes. It builds and smoke-tests each draft from its tag, attests the payload, and publishes it. CI's rust lane already ran the workspace checks in the merge queue, so the build runs them again only for a tag whose commit never passed CI there. A failed build leaves its draft, and the next run of the workflow rebuilds and publishes it: after the next merge, a re-run, or a dispatch from main.
- After an image CLI release, the workflow opens a pull request that points the website at the new scenario compiler.
Never bump a product version, edit a product CHANGELOG.md, or create a
product tag as part of other work, and never push a tag by hand. To release a
version other than the one the release pull request proposes, change the
version in a pull request of its own; the next run tags and publishes it. A
Rust dependency update, such as a security fix in the root Cargo.lock, ships
through the release pull request when it is a fix(deps) pull request, as
described above, even when it also changes the code the update needs. The release pull request is rebuilt on every push to main,
so edits made on it are lost.
The workflow needs a GitHub App to push release/next and open pull requests,
because pushes made with the workflow's own token do not run CI. To set it up:
- Create the
intar-releaseGitHub App with contents and pull requests read and write, and install it only on this repository. - Create the
release-prenvironment and limit its deployment branches tomain. - In that environment, add the app's client ID as the
RELEASE_APP_CLIENT_IDvariable and a private key as theRELEASE_APP_PRIVATE_KEYsecret.
The release workflow is the only place a tool is built: every product, Kino included, is built by the same steps and ships only as its GitHub release. Kino's release is the guest build that runs inside scenario VMs. No workflow installs a tool on a host. Rollouts run from an operator's machine, by hand or through an agent, and take the release straight from GitHub:
gh release download <prefix>/vX.Y.Z --repo intar-dev/intar-dev --dir releaseIn release, sha256sum --check --ignore-missing <binary>_X.Y.Z_checksums.txt
checks the downloads. gh attestation verify <archive> --repo intar-dev/intar-dev
checks that this repository's release workflow built them; releases published
before the workflow attested them have no attestation. Then:
-
Agent on a personal host:
sudo intar-host update, or--version X.Y.Zfor a specific release. It downloads the release from GitHub itself; seedeploy/personal-metal/README.md. -
Agent on a platform host: the release's
deploy/install.shinstalls it, and refuses while the agent or any VM still runs.- Unpack the checked archive as root into
/var/lib/intar/releases/agent-vX.Y.Z(mode 0700), and check it withsha256sum --check --strict deploy/SHA256SUMSthere. Unpack the running release the same way, as the rollback target. - Dispatch image-ops
gate-drained, and check that nointar-vm-*.serviceunit is left. systemctl stop intar-agent, thensh deploy/install.shin the new release directory.systemctl restart intar-platform-check, which re-runs the jailer self-test and the agent doctor on the new binaries (it stays active after boot, so starting the agent alone skips it), thensystemctl start intar-agent.- Check that the installed binaries match the release and that the journal
shows
bridge v8 handshake completewith no warnings. Then dispatch image-opsgate-open, after any Kino rollout that shares the drain.
If a check fails, stop the agent and run
install.shfrom the rollback release's directory instead.swamp workflow run agent-rollout --input version=X.Y.Z --input rollback_version=<running X.Y.Z>, run from the repository root afterswamp extension install, runs these steps. It stops for approval before the drain and again before a rollback; after eachswamp workflow approve agent-rollout <step>,swamp workflow resume agent-rolloutcontinues the run. - Unpack the checked archive as root into
-
Builder: on the builder host, run the new binary's
sudo ./intar-builder doctor --config /etc/intar-builder/config.toml. Wait until the host is idle: the latestapplied builder desired statejournal line shows"desired_builds":0, and nothing matchespgrep -f '^/usr/bin/qemu'. Thensystemctl stop intar-builder, keep the old binary as/usr/local/bin/intar-builder.pre-vX.Y.Z,install -m 0755the new one, start the service, and check the journal for the handshake and the desired state with no warnings. The binary has no--version; tell releases apart by their SHA-256.swamp workflow run builder-rollout --input version=X.Y.Zruns these steps and stops for approval before the install and again before a rollback; after eachswamp workflow approve builder-rollout <step>,swamp workflow resume builder-rolloutcontinues the run. -
Stargate: see "Rolling out" in
deploy/stargate/README.md. -
Kino: image-ops
tools-buildwith thekino_tag, then the guest-tools steps inapps/web/README.md. -
Image CLI: merge the website pin pull request that the release opens. The next website deploy uses it, and
tools-buildbuilds tools disks with it.