The rebuild and its phases
The platform is rebuilt, not repaired. One module in this repository stands it up from nothing, one phase at a time, against one named target, and tears it down again in reverse.
The command
The rebuild is three functions of the rebuild Dagger module, which
dagger.toml makes the workspace’s entry point. Run them from this
repository’s checkout:
dagger call up --target <name> [--resume] [--through <phase>] [--no-pause]
dagger call down --target <name> [--confirm <name>]
dagger call preflight --target <name>
upruns the preflight check, then applies and verifies phases in order. It stops at a checkpoint after each phase.--no-pauseruns on without stopping, and--throughnames the last phase it runs. With--resume, it continues the target’s unfinished run instead of starting a new one.downtears down every phase in reverse order, phase 0 last. It’s safe to run again after it fails partway, and it succeeds when there’s nothing left.preflightruns only the preflight check, and changes nothing.
There’s no default target: each function refuses to run without --target.
None of them is ever served from Dagger’s cache, and neither is any command a
phase runs against a target, so every call really reads and changes the
target it names.
Targets
A target is the one place a run is pointed at, such as the live platform or a
scratch drill. Drill and live differ only in the target. Each has a file in
the values checkout, targets/<name>.yaml, which says which it is:
kind: drill
kind is live or drill. The file is read strictly: a key it shouldn’t
have, a second YAML document or any other kind is refused, and so is a target
with no file. The rest of the file, the values each phase reads, comes with
phase 0.
Only one target is live. Every call reads every target’s file, as the values checkout’s commit has it and as the checkout stands, and refuses to run when more than one target is live. A target is live when either version of its file says so, so an uncommitted edit can’t turn the live target into a drill.
The values checkout is a git repository outside this one. A Dagger function can’t read the machine it’s called from, so the module takes the checkout as an argument, which the operator sets once as a user-level Dagger setting; see What you need.
Three rules keep a drill off the live platform:
- There’s no default target.
- A run against the live target refuses uncommitted changes, in this repository or in the values checkout, so every change to the live platform was made from history. A drill may run from uncommitted changes, and its record says so.
downagainst the live target needs--confirm <name>. A drill doesn’t, but refuses a name that isn’t its own.
The phase order
Each phase is one part of the platform. The order is fixed in one place,
rebuild/phases.go:
| Phase | Part |
|---|---|
| 0 | the state backend |
| 1a | the cloud anchor and the edge |
| 1b | the operations tunnel |
| 1c | home-lab hosts |
| 2 | tenant compute, then the offerings tenants use |
| 3 | backup, and observability |
| 4 | the canary tenant |
A phase joins the order when its part lands, and a rebuild runs every phase
the order holds. No phase has landed yet, so the order is empty: up against
any target passes the preflight check as a rebuild from nothing, and finishes
with nothing to do.
The phase contract
Every phase meets one interface, in rebuild/run/:
Applymakes the target match the phase’s definitions.Verifyfails unless the target is what the phase last applied, and it works.Teardownremoves everythingApplymade. It succeeds when there’s nothing left to remove, including whenApplynever ran.
Each runs against one target, and each is safe to run again after it fails partway. A phase reads every value it needs from the target, and nothing from the machine the run started on.
Checkpoints
A Dagger function runs to completion, so a checkpoint is the end of a call, not a prompt inside one.
After each phase verifies, up writes the run’s record and returns a summary,
which dagger call prints: what the phase made, how long it took, and the
run’s time so far. Then it stops. The operator chooses:
- continue, with
up --resume, which runs the next phase; - tear down, with
down.
With --no-pause, as in CI and in drills, up doesn’t stop between phases.
Each phase’s verify is then the only gate.
A failed apply or verify stops the run at once. up writes the record and
fails with what failed, followed by the summary, and leaves the target as it
is to look at.
Resuming
A run that stopped, at a checkpoint, where --through said, or on a failure,
is unfinished. up --resume:
- stops unless the target’s last run is unfinished;
- runs
Verifyfor every phase the run has verified so far, and stops on any failure, because that’s drift; - continues from the first phase that didn’t verify, starting with its
Apply.
Whether a target is fit to resume is the operator’s call. A failure can leave
a phase in a state its Apply can’t fix. Resuming then fails again, and
down followed by a fresh up is the way back.
A resumed run is the same run, with the same ID. It may resume from a different commit than it started from, such as one that fixes the phase that failed, and its record keeps the commits each phase was applied from.
The preflight check
Before up applies anything, it checks the target. preflight runs the same
check on its own.
- If the target has no state backend, this is a rebuild from nothing, and the check passes.
- If the target’s last run is unfinished, the check stops.
upnever starts a new run on top of one: resume it withup --resume, or tear the target down withdown. - Otherwise it runs
Verifyfor every phase the last run applied. Any failure is drift, and the check stops before anything changes.
Drift is a difference between the target and what the rebuild last applied to it. A difference between what was last applied and today’s definitions isn’t drift: it’s the change this run applies.
Records
Each run keeps a record in the target’s state backend, which phase 0 makes. It holds:
- the commits of this repository and of the values checkout that each phase was applied from, and whether either had uncommitted changes;
- each phase it applied, what the phase made and how long it took;
- where it stopped, at a checkpoint, where
--throughsaid or on a failure, or that it finished.
The record of the last run that finished is the target’s last-known-good.
The record lives only in the target, and down removes it with phase 0. Until
phase 0 lands, no target has a state backend, so no run is recorded and every
up is a rebuild from nothing.
Time
A rebuild is meant to take at most 60 minutes. A run’s time is measured from
the start of its first up to the last phase’s verify. It includes the
preflight check, the time between calls at every checkpoint, and the time
between a failure and its resume. The summary shows each phase’s time and the
run’s, and marks a run over 60 minutes, and the record keeps both.
A run is never stopped for time. A live rebuild that’s running long is still the fastest way back.
Next: The platform