# mvirt 0.18.4

migrations: no
wire: unchanged
rev: f1084a28c389d3bd3e861164c961bbdb8900f64b

mvirt 0.18.4 — the recurring roll landmines, defused

A patch release whose theme is the manual repair work every production roll used
to require. Two of those repairs are now the platform's own job, and three
long-standing traps around fresh installs, node builds and stored credentials
are closed. Nothing in here migrates the store or moves the wire, so a node
rolls back by booting its previous generation.

What changed

- Nodes recover their control-plane endpoint set on their own (#258). Any
  control-plane member — leader or follower — now answers
  `GET /v1/bootstrap/endpoints` from its local replicated store, and a node
  refreshes from it on every failed dial and at onboarding. A node holding a
  stale single-voter set after the raft grew, or after leadership moved, used to
  be unrecoverable without hand-editing control-plane.json on every host; it now
  converges by itself.
- The NAT anchor keeps away from the platform's own endpoint hosts (#260). The
  placement pass avoids them for new anchors and evicts a live owner stranded on
  one as soon as another node can take it — the automated form of the manual
  fence dance. What the eBPF datapath still drops on the owner's own public IP
  is now counted per reason (NAT_GW_DROPS) and reported, instead of vanishing
  into a silent TC_ACT_SHOT. The datapath hairpin itself remains open.
- A fresh install with objectGateway = true no longer bricks its own first boot
  (#262). The s3 secrets file is an optional EnvironmentFile on both units, so
  declaring object storage before hand-placing the secrets holds those units
  legibly instead of refusing to start the control plane.
- Node closures build from a cold store again (#256). crates.io rejects the
  User-Agent nixpkgs' fetchurl sends; an overlay gives the crate fetchers a
  plain one. Content-addressed outputs are unchanged, so nothing rebuilds.
- A stored assistant API key stays bound to the destination it was entered for
  (#254). Moving base_url, provider or search_provider without re-supplying the
  matching key is refused, instead of silently sending the standing credential
  to the newly named host on the next turn.

Also in this release, from work that landed on main since 0.18.3: a failed Task's
container output is read before its pod-VM is destroyed (#269) and the reason a
template import failed is surfaced (#216); the import Task's egress is plain
unrestricted outbound rather than DNS-resolved destination pins (#268); the
infra security groups name who may come in and re-converge on every level pass
(#276); the DNS forwarder speaks EDNS upstream on its own behalf, so a guest
stub without EDNS can resolve large TXT records; the route reflector counts pod
vNICs as hosted (#283); and the fleet Envoy image is pinned at m8 for the TLS
inspector (#287).


## Upgrading

Point your flake's `mvirt` input at this release and redeploy the control-plane
host first, then each node in turn:

```
nix run .#colmena -- apply boot --reboot --on <control-plane host>
nix run .#colmena -- apply boot --reboot --on <node>   # one at a time
```

This release adds no migrations, so the control-plane host rolls back the same way
a node does: boot the previous NixOS generation.
