Skip to content

Applying changes

Applying is two steps: compile your Emet to a manifest, then ship it to the agent. golemctl apply does both — carrying on from the web.emet you wrote in The config.

Terminal window
golemctl apply web.emet http://127.0.0.1:7474

That address is a local agent. A deployed one binds loopback and is named ssh://[user@]host[:port] instead — golemctl opens the SSH forward and carries the shared bearer token from GOLEM_AUTH_TOKEN or GOLEM_AUTH_TOKEN_FILE. Every command on this page takes either shape. See Trust model.

Read the diff first

golemctl plan asks the agent what an apply would do and writes nothing:

Terminal window
golemctl plan web.emet http://127.0.0.1:7474

It answers with one collapsed line per action and a change count, diffed against golem’s own journal — golem’s record of what it has already applied. --detail expands every group to one glyph per line with content ids. A plan is safe to run while an apply is in flight.

Plan against the host, not just the journal

The journal is silent about anything golem did not do itself. Point golem at a host another tool already configured — Ansible, a hand-run script, a human with a shell — and the journal is empty, so every glyph in the manifest reads as an install, even one the host already has byte for byte. --against-host answers the question the journal can’t: not what golem’s records say, but what is actually on the box right now, read live from its filesystem, systemd, and dpkg.

Terminal window
golemctl plan web.emet http://127.0.0.1:7474 --against-host

The report grows a second block beneath the journal one, the same glyphs checked against reality:

Plan for web-01 · against no prior revision · against the host · manifest 3f9c1a…
journal
+ install 12 apt packages nginx curl jq git vim htop tmux rsync … and 4 more
(web/base, web/extra)
+ install 4 files /etc/nginx/nginx.conf /etc/ssh/sshd_config /etc/motd /etc/hosts.allow
(web/nginx, web/base)
+ install 1 systemd unit nginx.service (web/nginx)
host
= match 17 glyphs
17 changes · 17 install
host · every declared glyph already matches — applying this manifest changes nothing

That is the case --against-host exists for: proving, before the first apply of a fleet’s life, that a host already matches the manifest and applying it changes nothing — a statement no amount of reading the journal could produce, because the journal has nothing in it yet.

--against-host is opt-in because it costs more, not because it needs more authority. It runs the same per-glyph checks apply already runs before touching anything, against the same files, units, and packages the manifest already names — a change in how much work plan does, not in what it is allowed to touch. It also surfaces drift the journal-only plan is blind to: a glyph whose content id hasn’t moved is a silent Noop against the journal even if someone hand-edited the file since, or a unit was stopped by hand — the host block reports that row as disagreeing while the journal still says there is nothing to do. Anything golem can’t read on the host — a file it lacks permission for, a secret it has no key for — reports unknown rather than a guess, and an unknown never counts toward “already matches.”

See CLI reference for the flag on fleet plan and the /plan endpoint, and Trust model for what a secret-bearing file’s row can and can’t reveal.

What golemctl apply does

  1. Compiles (if given .emet). For a .emet source, golemctl shells out to emetc build and captures the binary manifest — a content-addressed artifact carrying every host’s scroll. Hand it a prebuilt .manifest instead and it ships those bytes directly.

  2. Posts it. POST /manifest to the agent. The manifest is the whole fleet; the agent picks out its own host.

  3. Follows the reconcile. The POST comes back at once with a reconcile_id; golemctl then polls that reconcile and draws the units as they settle, ending on the report.

You can also split compile from ship:

Terminal window
emetc build web.emet -o web.manifest # compile
emetc build web.emet --text # eyeball the plan
golemctl apply web.manifest http://127.0.0.1:7474

What the agent does on receipt

POST /manifest returns 202 Accepted with {"reconcile_id": N} as soon as the manifest is ingested; the reconcile runs detached from the request. A reconcile can take tens of minutes — an apt update, a cold image pull — and a held-open request loses its report the moment the connection drops. Progress is read back instead from GET /reconciles/:id (or /reconciles/latest), which takes ?after=<seq> so a follower resumes where it left off.

On the request itself:

  1. Decode + check format_version.
  2. Select the AddressedScroll whose scroll.name matches its --host. Every other host’s scroll is ignored.

Then, detached:

  1. Diff the desired scroll’s glyphs against its journal, by glyph key and content id, into ordered ops: Install, Remove, Replace, Noop.
  2. Enact each op through a reversible reconciler, capturing the prior host state so the edit can be undone.
  3. Journal the ordered outcomes as a Reconcile revision.

Because the diff is content-addressed, re-applying an unchanged fleet is all no-ops. See Reversible reconcile for the mechanism.

Inspecting a node

Terminal window
# The applied scroll and its content id.
golemctl state http://127.0.0.1:7474
# The revision journal.
golemctl history http://127.0.0.1:7474
# One revision — its ops and reversal receipts.
golemctl show http://127.0.0.1:7474 1

More than one host

Naming an address per host stops scaling at about two. golemctl fleet reads a TOML inventory instead and fans the same verbs out concurrently, one connection per host:

Terminal window
golemctl fleet status
golemctl fleet plan web.emet --hosts web-1,web-2
golemctl fleet apply web.emet

One host’s failure never stops the others, and a host the manifest names no scroll for is skipped rather than emptied. The inventory’s fields are in the CLI reference.

Rolling forward

Edit your Emet, re-apply. The changed glyphs get new content ids and are replaced (reverse the old version, apply the new); everything unchanged is a no-op. There is no version number to type — content ids are the version axis.

Terminal window
# Bump an image tag in your Emet, then:
golemctl apply web.emet http://127.0.0.1:7474

Applying is also how you go back: edit the source to the version that worked and apply again. And if a unit of the new state fails, it returns to its last committed state on its own while the rest of the host settles forward, so trying a change you are unsure of costs one apply. See Reversible reconcile.

Taking it back

Remove a glyph (or a whole abstraction) from your Emet and re-apply. The glyphs no longer in the scroll become Remove ops — each the exact recorded uninstaller. A package golem installed is removed; a package that was already on the box is left alone. “Remove everything” is applying a scroll with no glyphs.

Where to next