Installing morzer¶
morzer is a single static binary with no runtime dependency on the machine's libc. Getting it is a download and a checksum.
The script¶
curl -fsSL https://morzecrew.github.io/morzer/install.sh | sh -s -- --version 0.9.0
It resolves the archive for this machine's architecture, checks the checksum
against the release's own SHA256SUMS, checks the signature when minisign is
installed, installs one file, and puts the prefix on PATH if it is not there
already. Then it tells you what it did.
Unpacking needs zstd on the machine
Releases are tar.zst, and GNU tar's --zstd runs the zstd binary as a
filter rather than decompressing on its own — so a machine without it cannot
unpack a release however new its tar is. apt install zstd, apk add zstd
or dnf install zstd.
An installed server usually has it — on Debian and Ubuntu the initramfs
tooling pulls it in. Container images mostly do not: of
debian:bookworm-slim, ubuntu:24.04, rockylinux:9, alpine:3.20 and
fedora:41, only Fedora ships it.
--print-only reports whether this machine has it, and reports it before
anything is downloaded. Without it the script still downloads, checksums and
verifies the archive, then refuses at the last step and names this.
# See everything it detected and would do, and change nothing.
curl -fsSL https://morzecrew.github.io/morzer/install.sh | sh -s -- --print-only
# A production runbook: pin the version, and refuse to install unsigned.
curl -fsSL https://morzecrew.github.io/morzer/install.sh \
| sh -s -- --version 0.9.0 --require-signature
# Somewhere else, without touching a startup file.
curl -fsSL https://morzecrew.github.io/morzer/install.sh \
| sh -s -- --version 0.9.0 --dir /opt/morzer/bin --no-modify-path
sh -s -- is how arguments reach a script that arrived on stdin. MORZER_VERSION
and MORZER_INSTALL_DIR do the same job for a runbook that would rather set
environment variables than get that incantation right.
| Option | |
|---|---|
--version X.Y.Z |
The release to install. Without it, the newest published release — resolved once, then printed. A prerelease is installed when you name it and never when it is inferred. |
--dir PATH |
Where to install. Default: /usr/local/bin when writable, otherwise $HOME/.local/bin. |
--digest sha256:… |
Bytes you are asserting. Checked before SHA256SUMS, and a mismatch is fatal. |
--require-signature |
Refuse to install when minisign is missing or the signature does not verify. |
--no-verify-signature |
Skip the signature check, and say so. Refused together with --require-signature. |
--no-modify-path |
Never edit a startup file; print the block instead. |
--completions / --no-completions |
Force shell completions on or off. On by default at a terminal, off in a build. |
--shell NAME |
Override the shell detected from $SHELL. |
--print-only |
Print what it detected and would do. Changes nothing. |
What it never does: run sudo on its own initiative — if the prefix needs root
it prints the command to re-run and exits non-zero — install anything but
<dir>/morzer, or leave a partial download behind.
Reading it first¶
Piping a script into a shell means running what a server chose to send. The criticism is correct in general, and the answer here is that everything the script does is also documented below, and that you can read it before running it:
curl -fsSLO https://morzecrew.github.io/morzer/install.sh
less install.sh
sh install.sh --print-only
sh install.sh --version 0.9.0
The same file is at
raw.githubusercontent.com
— swap main for a tag to pin the script itself — and it ships as an asset of
every release, covered by that release's SHA256SUMS. So the script that
verifies the archive is itself verifiable against the file it teaches you to
check.
By hand¶
The script is doing this. It stays documented because an operator who wants to check its work needs to be able to, and because a machine that cannot reach the site can still install from a release it already has.
VERSION=0.9.0 # the release you mean, not "whatever is newest"
ARCHIVE=morzer_${VERSION}_linux_amd64.tar.zst
BASE=https://github.com/morzecrew/morzer/releases/download/v${VERSION}
curl -fsSLO ${BASE}/${ARCHIVE}
curl -fsSLO ${BASE}/SHA256SUMS
curl -fsSLO ${BASE}/SHA256SUMS.minisig
curl -fsSLO https://raw.githubusercontent.com/morzecrew/morzer/main/morzer.pub
minisign -Vm SHA256SUMS -p morzer.pub # who published it
grep " ${ARCHIVE}\$" SHA256SUMS | sha256sum -c - # that this is what they published
tar --zstd -xf ${ARCHIVE} # runs zstd as a filter; install it first
sudo install -m 0755 morzer /usr/local/bin/morzer
Both checks matter and they answer different questions. The signature says a key you trust produced the checksum file; the checksum says the archive you have is the one that file describes.
Not sha256sum -c --ignore-missing
SHA256SUMS covers every architecture's archive, so a plain
sha256sum -c SHA256SUMS fails on the ones you did not download and
--ignore-missing is the usual way around that. It is also the way to make
the check meaningless: with no archive present — a download that failed,
a typo in the name — --ignore-missing skips every line and reports OK.
Pulling this archive's own line out of the file first is one more pipe and fails when there is nothing to check.
The version is written out rather than using /releases/latest/download/….
latest is a moving target: a runbook written today installs something else
tomorrow, which is the same reason update.channel exists and is a decision an
operator makes rather than a default. If you do want the newest release, take
the version from the releases page
and put it in the runbook.
Keep your copy of the public key
The key is published in the same repository as the releases it signs, so on its own it proves only that the repository was self-consistent. Its value is continuity: a release signed by a different key than the one you verified last time is worth stopping over. Save it the first time.
linux/arm64 is published alongside amd64.
From source¶
git clone https://github.com/morzecrew/morzer
cd morzer
just build # ./morzer
Needs Go 1.26 or newer. just build-all cross-compiles both architectures with
a SHA256SUMS; the flags match what the release pipeline uses, so a binary you
build from a tag is byte-identical to the published one.
Shell completion¶
morzer completion install
Puts the completion script where your shell reads completions from — bash, zsh
and fish — creating the directory if it is missing, and printing anything else
that shell needs. It defaults to $SHELL; name one to override.
The install script above runs exactly this command when the install is
interactive and the shell is one of the three, which is why there is no second
copy of these paths written in sh: a completion in the wrong directory
produces no error at all, just a Tab key that does nothing.
A shell it cannot place a file for gets the script on stdout instead, and exits
0. The paths are in completion install.
What the machine needs¶
| Tool | Why | Checked by |
|---|---|---|
docker and docker compose |
The runtime the manager coordinates. | preflight, doctor |
sops |
Encrypting and decrypting the secret state. | preflight, doctor |
A tmpfs at /run |
Decrypted secrets are written there. Standard on systemd hosts. | doctor |
| systemd | Optional. Boot-time convergence and scheduled backups. | doctor |
The versions a release requires are declared in its manifest, not by the
manager, so a bundle that needs docker >= 24 says so and preflight enforces
it:
morzer doctor
On a machine with no installation yet, doctor still reports whether the tools
are present — which is the question you have before running init, not after.
One machine may hold more than one installation: every path, unit and lock is
keyed by the product name. morzer ls says what a host is already running,
which is worth asking on a machine somebody else set up — see
Several installations.
Running as root¶
The manager writes to /etc, /var/lib, /run and /opt, installs systemd
units, and runs release hooks. It expects to be root.
To try it without being root, or without touching those paths at all, see Your first deployment — every managed path derives from a single prefix, and there is a flag that moves all of them.