Skip to content

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 29 Oct 10:16
· 73 commits to main since this release
8b3c37e

Changelog

Three things done by hand now have commands. A release can declare tasks an
operator runs by name, installation remove takes an installation off the
machine, and update generates a secret the new release adds. A bundle author
working with an AI coding agent can give it a skill for writing bundles. Two
writes to the secret state at once no longer lose one of them, init keeps the
recovery key when it has no secret to write, and exec says when it cut a
command's output.

Upgrading changes no schema: the installation stays at 12, and a bundle built
for 0.7.0 installs unchanged. A manifest that declares tasks: needs
compatibility.min_manager_version: "0.8.0". Six things behave differently:

  • update generates each secret the new release declares with a generator
    and has no value for, before it converges, and an unattended update does the
    same. Such an update used to stop at load-secrets until somebody ran
    morzer secret generate; update --generate-secrets=false keeps that, for
    one run.
  • Writes to the secret state take turns under a new lock,
    /var/lib/<product>/manager/locks/secrets.lock. A secret command run while
    another write is under way waits for it, a second or two, instead of racing
    it.
  • init with --recovery-recipient on a release that declares no secrets
    creates an empty secret state for the machine's key and the recovery key,
    where it used to create none and drop the recovery key. A machine initialised
    that way by an earlier version, that has written a secret since, has no
    recovery recipient: morzer doctor warns, and
    morzer secret recipients add <age-public-key> --kind recovery adds one.
  • exec and task run print a warning on stderr after output the runtime
    cut at 256 KiB a stream. Stdout and exit codes are unchanged.
  • A command that prints a line longer than 4 MiB runs to completion. It used to
    block on a full pipe and be killed at its timeout.
  • The journal, notifications and attestations can carry two new operation
    types, task and remove, and a task's container sees PHASE=task.

exec --json carries truncated when the output was cut, and task list,
task run and installation remove have payloads of their own. No existing
JSON field changed.

Added

  • A release can declare tasks an operator runs by name. Filling a sandbox with demo data, unlocking a user or rebuilding an index meant docker exec by hand: knowing the Compose project's labels, naming the entrypoint wrapper that loads *_FILE secrets, and having the container running, with nothing journalled and nothing stopping a sandbox's seed on production. A manifest's new tasks: names a service, a command, whether an operator may append arguments, and the machine modes it may run on. morzer task list shows them and whether this machine may run each; morzer task run <task> [-- args...] runs one as a one-shot container of the service, through its entrypoint, starting its dependencies, as the operation task: under the deployment lock, journalled with its arguments scrubbed of known secret values, attested and notified. A task whose modes leave out this machine's is refused by name before anything runs, and --force does not override it. The task's output is printed when it finishes, its stdout on stdout, and the invocation exits with the task's own exit code. A manifest with tasks needs compatibility.min_manager_version: "0.8.0", so an older manager says it is too old.
  • installation remove takes an installation off the machine. Nothing removed one, so retrying a failed first install, changing a mode fixed at creation, or retiring one of several products on a host meant deleting units, containers and directories by hand, with a glob that could reach another product's units and no record that it happened. The command runs as an operation, under the deployment lock, journaled step by step and plannable with --dry-run. It removes the units, the containers, the releases, the images no release left on the machine names, the rendered secrets, the configuration, the signing key and the installation record. By default it keeps the named volumes, the data and backup directories, the attestations, the journal, the secret state and the machine key, which unlock the kept data, and says where each is. --purge removes those too, leaving only the removal's own journal record and attestation. Nothing on a backup target is removed, and the removal's own attestation is pushed there. It needs --force, and on a production installation --confirm <installation-id> as well. A machine left by a failed init is cleaned with --product and --force. The removal is attested and pushed like other operations, and the journal is renamed to operations-removed-<operation-id>.jsonl, so a later init starts with an empty one.
  • update generates a secret the new release adds. Only init generated secrets, so a release declaring a new secret with a generator stopped every existing machine at load-secrets with required secret(s) not set, and an unattended machine retried the same failure every tick until somebody ran morzer secret generate by hand. An update now generates, before it converges, each secret the new release declares with a generator that has no value and that the release being replaced did not declare; the step names them, a plan names what it would generate, and no value is printed or journaled. A secret both releases declare and nobody set stays unset, a new one with no generator still stops the update, and update --generate-secrets=false leaves the new ones to you. A value generated by an update that then fails is kept, so the retry uses the same one. apply and rollback generate nothing.
  • A skill for AI coding agents that write a release bundle. An agent asked to author a bundle guessed at fields and conventions the documentation covers. The repository now ships morzer-bundle-authoring in the Agent Skills format, installed with npx skills add; it gives the agent the verify loop and the decisions a passing verify cannot catch, and sends it to the right page for each part of the bundle. The documentation build checks every command, long flag and link it names.

Fixed

  • exec says when it cut a command's output. The runtime keeps the first 256 KiB of each of a command's streams, and exec printed that much as if it were all of it, so morzer exec db -- pg_dump > dump.sql could write a dump cut short with nothing on the terminal to say so. A warning on stderr now follows output that was cut, and --json carries truncated: true; task run does the same. The exit code is still the command's own.
  • A command that printed a line longer than 4 MiB no longer stalls until its timeout. The manager stopped reading a command's output at such a line, so a hook, migration service, exec or task that went on printing blocked on a full pipe and was killed at its timeout. The rest of the output is now read and dropped, and the output is reported as cut.
  • Two writes to the secret state at once no longer lose one of them. Every write decrypts the whole state, changes it and encrypts it back, and nothing stopped two from overlapping, so the later one saved over the earlier one's change: a secret set while an update generated a secret its release adds, two secret commands in parallel, or a script setting several values at once. Writes now take turns under a secrets lock, and reading takes none.
  • init keeps the recovery key when it has no secret to write. On a release that declares no secrets, init put off adding --recovery-recipient until the first secret was written and kept the key nowhere, so the state that first write created could be opened by the machine's key alone. init now creates the state for both keys at once. A machine initialised that way by an earlier version, with secrets written since, has no recovery recipient, and morzer doctor warns about it; morzer secret recipients add <age-public-key> --kind recovery adds one.

Installing this release

curl -fsSL https://morzecrew.github.io/morzer/install.sh | sh -s -- --version 0.8.0

The script verifies the checksum always and the signature whenever
minisign is present; --require-signature makes a missing minisign fatal,
which is what a production runbook sets. --print-only shows everything it
detected and would do, and changes nothing. install.sh is an asset of this
release and is covered by SHA256SUMS below.

Verifying this release by hand

ARCHIVE=morzer_0.8.0_linux_amd64.tar.zst   # the one you downloaded

curl -fsSLO https://raw.githubusercontent.com/morzecrew/morzer/main/morzer.pub
minisign -Vm SHA256SUMS -p morzer.pub
grep " ${ARCHIVE}$" SHA256SUMS | sha256sum -c -

SHA256SUMS covers every architecture, so checking it whole fails on the
archives you did not download — and --ignore-missing, the usual way
around that, reports OK when no archive is present at all. Pulling out
the line for the file you have fails when there is nothing to check.

The signing key is the same for every release. If you have verified a
morzer release before, use the copy you already have rather than fetching
it again — a key that changes between releases is the thing worth noticing.

Release bundles are a separate artifact with their own signature and their
own key; see
https://morzecrew.github.io/morzer/reference/release-commands/#verification