• 0.2.0 1acc163163

    0.2.0 Stable

    github-actions[bot] released this 2026-07-19 23:04:21 +00:00 | 193 commits to main since this release

    Added

    • users apply grants the box tier, not just its socket (#49) — role
      box resolved to exactly one action, usermod -aG incus. That is the
      socket; it is step 1 of the five box grant performs, so every box-role
      user still needed an admin to run box grant <user> by hand before their
      first box new would do anything but refuse ("your project has no box-net
      profile"), and until that admin arrived they held an incus membership
      with no converged project — incus-user would lazily hand them a stock
      unhardened NAT bridge, which is worse than no grant at all. On host=yes
      apply now calls box grant per box-role user, after useradd (grant
      refuses an unknown account) and with the group ADD deferred to grant, so a
      grant that fails partway can take the socket back with it. Failures split
      the way the host= guard beside them already splits: a missing box CLI
      on host=yes dies (a broken VM host), a per-user grant failure warns and
      continues (one box-role user must not stop apply for the fleet). host=no
      and marker-less boxes keep their existing skip-with-warning. An
      incus-admin member is warned, not fatal — box grant refuses them today,
      which heavy-duty/box#99 fixes box-side with no rig change needed.

    Changed

    • BREAKING: rig bootstrap takes the users file, and requires it (#51) —
      bootstrap already knew everything else about what a box is (class, host,
      join, hostname) and wrote /etc/rig/role to say so; the users file was the
      last piece of that answer it did not take, so bring-up was two commands and
      the second was the forgettable one. --users <path> now runs the users apply convergence as bootstrap's final phase — after the traits, after
      the verified tailnet join, after the role marker (apply reads that
      marker), and after the host=yes box install (so box-role users find the
      incus group box's own setup-host built). One command, and the box has
      its people on it. The file is still passed per invocation and never
      persisted
      ; --users - is refused, because bootstrap's stdin belongs to
      the pre-auth key prompt.

      Migration: every existing rig bootstrap invocation must add --users <path> or --no-users. Omitting both is now a usage error (exit 2)
      naming both flags, and passing both is a usage error too. Scripted
      bring-up that already ran rig users apply as a separate step can either
      fold it in (--users ./users, and drop the separate call) or keep the old
      shape verbatim by adding --no-users. Required on class=server as well
      as class=human: a server nobody logs into routinely is exactly where
      shared-root access rots, and per-human accounts keep attribution intact
      for the times someone does go in — so the complete path is the default
      path, and skipping it is deliberate rather than an omission that looks
      identical to forgetting. The box TENANT roles (claude|codex|grok| staging) take neither flag: a guest is minted non-interactively by box,
      never joins the tailnet, and has no SSH door of its own — entry is box shell, gated by the host's incus grants.

      A bad users file is caught up front now (the same parser apply uses,
      before apt, the hostname change, and any spent pre-auth key), and on
      host=yes with RIG_SKIP_BOX_INSTALL=1 a box-role user with no incus
      group refuses immediately instead of a hundred lines later — the one case
      where the outcome is already certain. rig still never installs Incus and
      never calls box setup-host on its own account; every other way that step
      can fail lands in users apply's existing refusal, unchanged.

    Fixed

    • A release no longer disarms the changelog under the PRs still in
      flight
      (#67) — the ceremony stamps ## Unreleased to
      ## X.Y.Z — YYYY-MM-DD and stops. Every PR authored before that merge
      wrote its entry under ## Unreleased; with the heading gone, git files
      the entry under whatever now occupies the position — the release that
      already shipped. There is no conflict, because the stamped heading and
      the incoming entry never overlap textually, so the one signal an author
      relies on ("git told me to look") is absent exactly when the outcome is
      wrong. It happened here: #60's #58 entry landed inside ## 0.1.0 at
      67386b4 and was repaired two minutes later by 0ff520c; #54 would
      have filed a BREAKING entry the same way. The published release body
      is never affected — release.yml extracts it from the tree at the tag,
      before the late merges land — so the only file that drifts is the one
      only maintainers read, which is why it survived a whole release batch
      unnoticed. Fixed in both halves the failure has. The ceremony now
      re-arms: it adds a fresh empty ## Unreleased above the section it
      just stamped, so a late merge has somewhere correct to land with no
      author action. That belongs to the ceremony step in
      CONTRIBUTING.md, not to release.yml — no workflow
      has ever touched the heading; the stamping was always by hand, and the
      -dev re-arm the workflow does perform was only ever about VERSION.
      And test/release.sh now keys its guard to VERSION rather than
      demanding a literal heading: a stamped top section is legal exactly when
      VERSION is bare, and the moment it carries -dev — main, where
      feature PRs merge — the top section must be ## Unreleased. That
      distinguishes the two states the old check collapsed into one, so it
      catches a disarmed main without re-breaking the ceremony's own tree
      the way the pre-#44 guard did. The rule is proven against seven
      constructed VERSION + CHANGELOG.md pairs, including a re-armed
      ceremony whose top section is legitimately empty — the state the old
      non-empty assert would have rejected. box and cast carry the same flow
      and the same exposure (heavy-duty/box#96); cast is disarmed on main
      as of this writing and is getting the sibling fix.

    • A host=no box with an incus group no longer hands out the bare
      socket
      (#58) — users apply consulted the host= trait only when group
      incus was ABSENT (die on host=yes, skip on host=no). When the group
      was PRESENT the trait was never asked, so a host=no or marker-less box
      that nonetheless carried the group — box setup-host ran, then the box was
      re-bootstrapped with other traits — gave every box-role user a bare
      usermod -aG incus: the socket with no tier behind it, which incus-user
      answers by lazily building an UNHARDENED project under whoever opens it
      (incusbr-<uid>, NAT on v4 and v6, no ACL, no dns.mode=none, no port
      isolation). The marker now decides in BOTH directions, through one new pure
      gate (assert_marker_hosts_vms, testable against fixture markers non-root
      like assert_marker_human): the box role applies only where the box CLAIMS
      to host VMs, so the verdict is identical whether or not the group exists.
      The machine deliberately does not overrule the marker — but the skip is not
      silent either: when the group exists and the trait disagrees, the warning
      names the contradiction and rig bootstrap as the repair. On such a box
      exact-membership convergence now strips box-role users out of incus, on
      the same reasoning: a membership inherited from a previous life is the same
      half-grant as a freshly added one.

    • Dropping the box role revokes through box, not behind its back
      (#50) — users apply converged group incus with a bare gpasswd -d,
      the same move it makes for rig-admin and rig. Those two are rig's;
      incus is box's, and box revoke does strictly more with it: it says
      out loud that supplementary groups are read at LOGIN, so a session the
      dropped operator already holds keeps the Incus socket until it dies, and
      hands over loginctl terminate-user <user> as the remedy. rig logged
      removed <user> from incus and moved on, so an operator who dropped
      someone from the users file and watched apply succeed believed the VM
      access was gone — and was wrong for as long as that user held a session.
      Both removal paths (the per-user convergence and the dropped-user sweep)
      now call box revoke, which keeps one owner for the group. Never
      --purge: that deletes the user's boxes, images and project, and
      destroying someone's running machines is not a convergence step — it
      stays an explicit admin act. The exit code is not trusted (#12's lesson):
      a revoke that returns 0 with the membership still standing has not closed
      the socket, and rig falls back to removing the group itself, as it also
      does where box is not installed. Every fallback path carries the session
      warning, because the silence was the bug.

    • rig bootstrap refuses a users file that names no users (#57) — an
      empty, comments-only or whitespace-only file is not a parse error, so it
      passed pre-flight, converged nothing, and left the box root-only: the exact
      outcome --no-users exists to make explicit, reached by the flag added to
      guarantee the opposite. Bootstrap's pre-flight now catches the zero-user
      parse — before apt, the hostname change, or a spent pre-auth key — and
      refuses, naming --no-users as the way to ask for a root-only box out loud.
      Scoped to rig bootstrap's contract only: a standalone rig users apply
      against an emptied file is a real de-provisioning operation and is
      unchanged.

    Downloads