Skip to content

Latest commit

 

History

History
127 lines (104 loc) · 6.27 KB

File metadata and controls

127 lines (104 loc) · 6.27 KB

Contributing

Smoke tests. scripts/smoke.sh builds every committed example and, with --boot, boots it. Three stages, cheapest first: check validates the declaration, image builds it and inspects what a successful build doesn't already prove, and boot makes a disk, boots it headless, and asks the machine whether the boot was healthy. That last verdict is greenboot's own, so the check that would roll an update back is the one that decides whether the test passed.

$ cargo test                    # the tier that needs no machine
$ cargo fmt                     # rustfmt.toml settles layout; CI checks it
$ scripts/smoke.sh              # check + image, every example
$ scripts/smoke.sh --boot       # all three stages (needs KVM and sudo)
$ scripts/smoke.sh --boot cosmic

Building without a compiler. The machines most likely to run kuma are image-based and ship no compiler, and layering one onto a bootc host to work on the tool that builds bootc hosts is the wrong shape. scripts/Containerfile.dev builds a container that has one. Cargo is not in it: that comes from your home directory, so a build inside the container shares target/ and the registry cache with one outside it.

$ podman build -t kuma-dev-gcc -f scripts/Containerfile.dev .
$ podman run --rm --userns=keep-id --security-opt label=disable \
    -v "$HOME:$HOME" -w "$PWD" -e "HOME=$HOME" kuma-dev-gcc \
    sh -c 'export PATH=$HOME/.cargo/bin:$PATH; cargo test'

CI runs formatting, tests, clippy at -D warnings, shellcheck, actionlint, and the image stage on every committed example, desktops included. That covers the compose and both desktop arms, so the build-time guards in them run on a push. The boot stage stays local because its verdict comes from booting the disk, which needs KVM and sudo. Run --boot locally before pushing anything that changes what a machine does at runtime.

Two limits in that list worth knowing before you trust it. shellcheck reads scripts/smoke.sh and nothing else, so the shell that images actually run (the sync units and helpers, which live in Rust string literals in containerfile.rs) is unchecked; run it by hand against the generated file if you touch one. And the image stage proves an image builds, never that it works, so anything whose behaviour appears at runtime has no gate on a push at all.

actionlint is there because a workflow can be valid YAML and still be rejected by Actions, which says so by running no job at all and leaving no log to read.

A separate job runs cargo audit against the committed Cargo.lock, on every push and again weekly, because a dependency becomes vulnerable when the advisory lands rather than when someone next touches the tree.

Cutting a release. Bump version in Cargo.toml, refresh the lock, rename the Unreleased section in CHANGELOG.md to the new version, commit all three, then tag:

$ cargo update -p kuma --offline   # Cargo.lock records kuma's own version
$ git tag -a v0.4.0 -m "kuma v0.4.0"
$ git push origin v0.4.0

Cargo.lock is not optional here. It carries the workspace member's version too, so bumping only Cargo.toml leaves the lock disagreeing and every --locked call fails, which is most of CI.

The tag and Cargo.toml have to agree as well. The release workflow checks and fails rather than publishing a binary whose own --version contradicts the release it sits in.

The changelog is checked twice, once where it can still be fixed cheaply. A test fails locally when Cargo.toml's version has no section, and the release workflow fails on a tag whose section is missing, because it builds the release notes out of that section. Leave a fresh empty Unreleased behind for the next one: entries are meant to land in the same push as the change they describe, and a section written at tag time is a section written from memory.

What goes in it is what changes a machine, not what changed in the tree. Docs, tests, and CI stay out. A release with nothing user-facing in it is a release whose section says so in one line.

Push to main first and let it go green. A push that touches anything other than documentation runs the release workflow itself rather than a copy of it, so main is a complete rehearsal of every step a tag will run: the same test on the release target, the same packaging, the same signature. It stops one step short, publishing nothing, and keeps the binary as a workflow artifact instead. A problem surfaces there while it costs nothing, instead of once a tag already exists.

The releases page only ever lists tagged versions. A rolling entry on top of them reads as a version that shipped, and none did.

A release is one static x86_64 binary, its checksum, and a Sigstore bundle. The asset name carries no version on purpose: that is what keeps the README's releases/latest/download/ URL correct from one release to the next, and a test pins the two together so a rename can't quietly break the front door.

Knowing which binary you have. kuma --version reports the commit it was built from and appends -dirty when that tree had uncommitted changes. Worth checking whenever a change doesn't appear in the image: kuma build runs whatever kuma is on $PATH, which is not necessarily the tree you just edited.

Booting a VM. kuma vm builds a qcow2 via bootc-image-builder and boots it in QEMU (it needs sudo; bootc-image-builder runs as root). Log in as your declared [user], or the always-present test user kuma/kuma (ssh -p 2222 kuma@localhost; your ssh key is injected). Pass --rebuild after rebuilding the image; kuma warns when the reused disk is older.

Iterating without losing state. kuma vm --apply streams the freshly built image into the running VM and switches inside it. /var survives, so flatpaks, brew, and homes don't re-download. It's also the real update path, so bootc rollback inside the VM undoes it.

Installer media. kuma iso builds an Anaconda installer ISO (iso/bootiso/install.iso), bootable in GNOME Boxes or dd'd to a USB stick. Kuma-owned choices are preseeded; the rest is interactive. A declared [user] rides into the installer, and kuma iso says so when it happens, so build shareable media from a declaration without one.

Inspecting an image. It's a normal OCI image: podman run --rm -it localhost/kuma:latest bash.