Commit Graph
2 Commits
Author SHA1 Message Date
jcoffey-dev ad7c2d5135 Cut over a container, or refuse to for a reason
A container cannot be edited in place the way a unit file can, so cutting
one over means rebuilding it. That makes silent loss the default failure:
a container recreated without its capabilities, its custom network or its
device mappings starts cleanly and is quietly not the server it was.

Section 4.5 already answers this for a unit file -- it rewrites in place
rather than regenerating, because a generated unit would drop hardening
options this tool has no business having an opinion about, and it refuses
to edit a line it only partly understands. The same rule applies here,
where the whole definition has to be rebuilt: the parts this understands
are carried across, and a container using anything else is refused by name
rather than rebuilt without it. The list of what it looks for is
conservative and not exhaustive, which is the safe direction: docker's
HostConfig has far more fields, and one this does not know about is a
reason not to be recreating that container at all.

The old container is renamed, not removed, and nothing here prunes the old
image. Together they are the container's manual restore path -- one command
starts the previous container again -- which is as close to section 4.2's
preserved binary as a container gets. The inspect output is preserved as an
artifact before anything is replaced, for the reason the unit file is: an
operator putting a machine back by hand should not also be reconstructing
the definition from memory.

Recovery-mode variables are stripped from the recreated container's
environment. Leaving STALWART_RECOVERY_MODE set would recovery-boot on
every restart, which is the same footgun the unit rewrite exists to
prevent.

Run now branches, with the health check and quota recalculation shared:
those ask the same question whatever started the server. The binary path
moved inside an else and is otherwise untouched -- no existing test needed
editing, which is the evidence for that.

The container path is opt-in through Options.Container, so a Docker
deployment without it is still refused exactly as before. Nothing calls it
yet; wiring `run` up and lifting preflight's refusal is what remains of #3,
and ARCHITECTURE.md says so in both places it previously said Docker was
refused outright.
2026-08-28 17:30:33 -07:00
jcoffey-dev 6e7a05e478 Tell a container operator what stands in the way
Preflight refuses a container, and until now that was all it said. The two
things that actually decide whether one could be migrated at all were never
looked at, so an operator was refused without being told what to fix or what
a manual migration would involve.

It now inspects the container and reports three things. What image it is
running, tag and digest kept apart because a moved tag makes them disagree
and only one of them says what is really there. Whether compose manages it,
which matters beyond this tool's current refusal: recreating a
compose-managed container out from under compose leaves the container and
the compose file disagreeing about what is deployed, and the next
`compose up` reverts the migration. And whether the data is on a volume at
all -- a container keeping it in its own writable layer loses it when the
container is replaced, and replacing the container is what migrating it
means, so that one is fatal in a way no later phase could recover from.

All three are advisory under rehearse, on the same reasoning that already
made the deployment check advisory there: rehearse never stops or recreates
anything, and an operator doing this by hand needs these facts more than an
automated run does.

Preflight stays read-only. Preserving the container definition as an
artifact belongs with the phase that replaces it, so it goes with cutover
rather than here.

One limitation this surfaced and does not fix: --data-dir naming a path
inside the container breaks the host-side disk-space check, which stats it
locally. Path translation is the next piece of work; the data-volume check
says so when the path matches no mount.
2026-08-28 16:50:09 -07:00