Skip to main content

max / makenotwork

Put mnw-cli and magicmirror on Bento, and give each a --version Both were binaries somebody had placed on astra by hand, with no release history, no rollback and no way to ask a running one what it was: the only handle was the file's mtime. That is how mnw-cli came to be an April build four months behind its source, on the machine that also holds every git mirror, with nothing able to notice. Each gets a bento.toml declaring kind = "service" and a [[deploy]] table, a linux.rhai recipe ending at deploy rather than collect, and a --version flag. The flag is not decoration: the recipes assert `<binary> --version | grep -qw <version>` to prove the checkout and the release agree, and without it the release could not tell a stale binary from a fresh one either. Hand-rolled rather than reaching for clap, since neither crate takes an argument parser at all. Both install paths move to /usr/local/bin. install-service.sh refuses any dst outside it, and that guard is the only thing bounding a NOPASSWD sudo grant, so the binary moves rather than the guard widening -- the call wam made on 2026-08-15. mnw-cli's WorkingDirectory stays /opt/mnw-cli, which is its state directory and not where the binary lives. mnw-cli is aarch64-only on purpose. Prod already gets this binary inside the MNW server release as a Sando companion, so astra's copy was the only unpipelined one; a second x86_64 build here would be two delivery stories for one binary on one machine. Health assertions live inside the deploy step, not a step of their own, for the reason wam's recipe records: a health step aborts AFTER the install, leaving the binary live and the release reading failed. Each asserts in its own vocabulary -- mnw-cli that the unit is active and the port answers an SSH banner, magicmirror that the unit is active, since a TUI on a console has no port.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-23 19:07 UTC
Signed with PGP, not checked
Commit: d1fec7bc24d9a234b30b0d0545fd775b41001ba4
Parent: 4f7d8bc
11 files changed, +315 insertions, -22 deletions
@@ -1056,7 +1056,7 @@
1056 1056
1057 1057 [[package]]
1058 1058 name = "magicmirror"
1059 - version = "0.1.0"
1059 + version = "0.1.1"
1060 1060 dependencies = [
1061 1061 "anyhow",
1062 1062 "chrono",
@@ -2902,10 +2902,6 @@
2902 2902 source = "registry+https://github.com/rust-lang/crates.io-index"
2903 2903 checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
2904 2904
2905 - [[patch.unused]]
2906 - name = "docengine"
2907 - version = "0.7.0"
2908 -
2909 2905 [[patch.unused]]
2910 2906 name = "synckit-client"
2911 2907 version = "0.8.1"
@@ -2914,10 +2910,6 @@
2914 2910 name = "synckit-config"
2915 2911 version = "0.2.0"
2916 2912
2917 - [[patch.unused]]
2918 - name = "quasi-type"
2919 - version = "0.1.0"
2920 -
2921 2913 [[patch.unused]]
2922 2914 name = "quasi-axum"
2923 2915 version = "0.56.0"
@@ -2965,3 +2957,11 @@
2965 2957 [[patch.unused]]
2966 2958 name = "tagtree"
2967 2959 version = "0.4.1"
2960 +
2961 + [[patch.unused]]
2962 + name = "docengine"
2963 + version = "0.7.0"
2964 +
2965 + [[patch.unused]]
2966 + name = "quasi-type"
2967 + version = "0.1.0"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "magicmirror"
3 - version = "0.1.0"
3 + version = "0.1.1"
4 4 edition = "2024"
5 5 license = "MIT"
6 6 description = "One terminal surface over every operator daemon that emits an ops-status payload. Tabs per source plus a worst-first rollup."
@@ -1931,7 +1931,7 @@
1931 1931
1932 1932 [[package]]
1933 1933 name = "mnw-cli"
1934 - version = "0.1.1"
1934 + version = "0.1.2"
1935 1935 dependencies = [
1936 1936 "anyhow",
1937 1937 "bytes",
@@ -4761,6 +4761,10 @@
4761 4761 source = "registry+https://github.com/rust-lang/crates.io-index"
4762 4762 checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
4763 4763
4764 + [[patch.unused]]
4765 + name = "docengine"
4766 + version = "0.7.0"
4767 +
4764 4768 [[patch.unused]]
4765 4769 name = "quasi-axum"
4766 4770 version = "0.56.0"
@@ -4797,14 +4801,6 @@
4797 4801 name = "quasi-webview"
4798 4802 version = "0.56.0"
4799 4803
4800 - [[patch.unused]]
4801 - name = "synckit-config"
4802 - version = "0.2.0"
4803 -
4804 - [[patch.unused]]
4805 - name = "docengine"
4806 - version = "0.7.0"
4807 -
4808 4804 [[patch.unused]]
4809 4805 name = "kberg"
4810 4806 version = "0.1.0"
@@ -4824,3 +4820,7 @@
4824 4820 [[patch.unused]]
4825 4821 name = "quasi-type"
4826 4822 version = "0.1.0"
4823 +
4824 + [[patch.unused]]
4825 + name = "synckit-config"
4826 + version = "0.2.0"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "mnw-cli"
3 - version = "0.1.1"
3 + version = "0.1.2"
4 4 edition = "2024"
5 5 license = "LicenseRef-PolyForm-Noncommercial-1.0.0"
6 6
@@ -46,6 +46,28 @@
46 46 const TICK: Duration = Duration::from_millis(500);
47 47
48 48 fn main() -> Result<()> {
49 + // `--version`, answered before the terminal is taken over and before any
50 + // config is read.
51 + //
52 + // Not decoration. This binary is placed on astra by hand and drawn onto
53 + // tty1, and until now there was no way to ask a running one what it was:
54 + // the only handle was the mtime of the file, which is how the sibling
55 + // services on that host came to be four months stale without anyone
56 + // noticing. Bento's release recipes also assert
57 + // `<binary> --version | grep -qw <version>` to prove the checkout and the
58 + // release agree. Infra `e6acf532`.
59 + //
60 + // Hand-rolled rather than reached for clap: this crate takes no argument
61 + // parser at all, and a dependency for one string would be the wrong trade.
62 + let argv: Vec<String> = std::env::args().collect();
63 + if matches!(
64 + argv.get(1).map(String::as_str),
65 + Some("--version" | "-V" | "version")
66 + ) {
67 + println!("magicmirror {}", env!("CARGO_PKG_VERSION"));
68 + return Ok(());
69 + }
70 +
49 71 // Before any TLS work: reqwest is built `rustls-no-provider`, so without a
50 72 // process default every client build would fail at the first poll.
51 73 tls::install_crypto_provider();
@@ -9,7 +9,14 @@
9 9 User=mnw-cli
10 10 Group=mnw-cli
11 11 WorkingDirectory=/opt/mnw-cli
12 - ExecStart=/opt/mnw-cli/mnw-cli
12 + # /usr/local/bin, not /opt/mnw-cli, since 2026-08-23. The binary is delivered by
13 + # Bento now, and install-service.sh refuses any dst outside /usr/local/bin --
14 + # that guard is the only thing bounding a NOPASSWD sudo grant, so the binary
15 + # moves rather than the guard widening. Same call wam made on 2026-08-15.
16 + #
17 + # WorkingDirectory stays /opt/mnw-cli deliberately: that is this service's state
18 + # directory (.env, HOME, ReadWritePaths), not where the binary lives.
19 + ExecStart=/usr/local/bin/mnw-cli
13 20 Restart=always
14 21 RestartSec=5
15 22
@@ -32,9 +32,31 @@
32 32
33 33 #[tokio::main]
34 34 async fn main() -> anyhow::Result<()> {
35 + let argv: Vec<String> = std::env::args().collect();
36 +
37 + // `--version`, answered before anything else and before any config is read.
38 + //
39 + // Not decoration. This binary was placed on astra by hand and sat four
40 + // months stale (an April build against a repo that had moved on), and the
41 + // reason nobody noticed is that there was no way to ask a running one what
42 + // it was: the only handle was the mtime of the file. A service that cannot
43 + // say its own version cannot be audited, and Bento's release recipes assert
44 + // `<binary> --version | grep -qw <version>` to prove the checkout and the
45 + // release agree. Infra `e6acf532`.
46 + //
47 + // Hand-rolled rather than reached for clap: this crate takes no argument
48 + // parser at all (`ota` routes on `argv[1]` a few lines down), and a
49 + // dependency for one string would be the wrong trade.
50 + if matches!(
51 + argv.get(1).map(String::as_str),
52 + Some("--version" | "-V" | "version")
53 + ) {
54 + println!("mnw-cli {}", env!("CARGO_PKG_VERSION"));
55 + return Ok(());
56 + }
57 +
35 58 // One-shot operator subcommand: `mnw-cli ota publish ...`. Routed before the
36 59 // SSH daemon boots so the same binary doubles as the OTA publisher.
37 - let argv: Vec<String> = std::env::args().collect();
38 60 if argv.get(1).map(String::as_str) == Some("ota") {
39 61 return ota::run(&argv[2..]).await;
40 62 }
@@ -1,0 +1,47 @@
1 + # How Bento releases magicmirror. Lives here rather than in the daemon's config
2 + # so it is versioned with the code it describes.
3 + #
4 + # A service, not an app or a library: magicmirror is run rather than distributed.
5 + # There is no bundle for a user to download and no crate to publish.
6 + #
7 + # WHY THIS FILE EXISTS. astra's copy was a binary somebody had placed at
8 + # ~/.local/bin/magicmirror by hand, with no release history, no rollback and no
9 + # way to ask it what it was -- the only handle was the file's mtime. It was the
10 + # least stale of the three services on that host in that state, which is not the
11 + # same as being watched. Infra `e6acf532`.
12 + kind = "service"
13 +
14 + # aarch64 only. This runs on exactly one machine: it is drawn onto astra's tty1
15 + # as an always-on wall panel, and there is no second instance anywhere.
16 + targets = ["linux/aarch64"]
17 +
18 + version_path = "Cargo.toml"
19 +
20 + # MNW is one .git over several separately-versioned products, so the default
21 + # `v{version}` names no product in particular here.
22 + tag_format = "magicmirror-v{version}"
23 +
24 + # One target, so inert either way. Stated rather than omitted so the answer does
25 + # not have to be reasoned out if a second is ever added.
26 + require_all_targets = true
27 +
28 + # CEREMONY, and this is the cheapest rung on purpose. A bad deploy here costs a
29 + # monitoring panel on a console going blank. Nobody's money, nobody's morning,
30 + # and no data. So it installs itself through Bento the way wam does rather than
31 + # paying for Sando's tiers, and the rollback is the installer's `<dst>.prev`.
32 + # See wiki `deploy-ceremony-tiers`.
33 + #
34 + # install_path is /usr/local/bin/magicmirror, which is a MOVE: the running
35 + # binary was at ~/.local/bin/magicmirror. install-service.sh refuses any dst
36 + # outside /usr/local/bin, and that guard is the only thing bounding a NOPASSWD
37 + # sudo grant, so the binary moves rather than the guard widening -- the same
38 + # call wam made on 2026-08-15. The unit's ExecStart moves with it.
39 + #
40 + # No `health_url`: this is a TUI drawn on a console, not a server, so there is
41 + # no port to probe and no status code to read. The recipe asserts the unit is
42 + # active, which is the whole of what "up" means for this one.
43 + [[deploy]]
44 + target = "linux/aarch64"
45 + host = "astra"
46 + install_path = "/usr/local/bin/magicmirror"
47 + service = "magicmirror-panel.service"
@@ -1,0 +1,56 @@
1 + // magicmirror: Linux release recipe for Bento (aarch64 on astra).
2 + //
3 + // Ends at `deploy`, not `collect`: magicmirror installs itself rather than
4 + // handing off to Sando. That is a judgement about blast radius, not a house
5 + // style -- a bad deploy here blanks a monitoring panel on a console. The
6 + // cheapest rung, honestly taken. See wiki `deploy-ceremony-tiers`.
7 + //
8 + // No host is named anywhere in here: build_host() resolves to whichever native
9 + // machine the topology assigns aarch64, and deploy() to whichever machine
10 + // bento.toml says runs it. They happen to be the same box; the recipe should
11 + // not know that.
12 +
13 + let h = build_host();
14 + let v = version();
15 + let r = repo();
16 +
17 + step("checkout");
18 + let sha = checkout_sha(h);
19 + log("pinned " + h + " to magicmirror-v" + v + " @ " + sha);
20 +
21 + step("prebuild");
22 + sh_ok(h, "cd " + r + " && cargo clippy --all-targets " + feature_flags() + " -- -D warnings");
23 + sh_ok(h, "cd " + r + " && cargo test " + feature_flags());
24 +
25 + step("build");
26 + sh_ok(h, "cd " + r + " && cargo build --release " + feature_flags());
27 + let binary = resolve_artifact(h, r + "/target/release/magicmirror");
28 +
29 + step("verify");
30 + log(glibc_check(binary));
31 +
32 + // The version about to ship is the version in the tag. This assertion is the
33 + // reason `--version` exists on this binary at all: it was added 2026-08-23 with
34 + // this recipe, and before it the only way to ask a running magicmirror what it
35 + // was was to stat the file.
36 + sh_ok(h, binary + " --version | grep -qw " + v);
37 +
38 + step("deploy");
39 + // Stages under /var/tmp/bento-deploy/magicmirror/ and calls the privileged
40 + // installer, which keeps the outgoing binary as <dst>.prev, installs
41 + // atomically, and restarts the unit.
42 + log(deploy(binary));
43 +
44 + // Health assertions live inside the deploy step rather than a step of their
45 + // own: a "health" step would abort the run AFTER the install has happened,
46 + // leaving the binary live and the release reading failed.
47 + //
48 + // This is a TUI on a console, so there is no port to probe. The unit being
49 + // active is the whole of what "up" means here -- and it is not a formality: the
50 + // binary reads only ~/.config/magicmirror/ with no fallback to the old
51 + // ops-viewer path, so a config that did not move with a rename is exactly the
52 + // failure that leaves the unit dead and the screen dark.
53 + sh_ok(deploy_host(), "test -x " + install_path());
54 + sh_ok(deploy_host(), "systemctl is-active --quiet magicmirror-panel.service");
55 +
56 + log("magicmirror " + v + " (" + target() + ") built on " + h + " and live on " + deploy_host());
@@ -1,0 +1,59 @@
1 + # How Bento releases mnw-cli. Lives here rather than in the daemon's config so
2 + # it is versioned with the code it describes.
3 + #
4 + # A service, not an app or a library: mnw-cli is run rather than distributed.
5 + # There is no bundle for a user to download and no crate to publish.
6 + #
7 + # WHY THIS FILE EXISTS AT ALL, which is the whole point of it. Until 2026-08-23
8 + # astra's mnw-cli was a binary somebody had placed there by hand in April, with
9 + # no release history, no rollback and no way to ask it what it was -- the only
10 + # handle was the file's mtime. It was four months old against a source tree that
11 + # had moved on, and nothing could have noticed, because a service outside the
12 + # pipeline has no release history to be stale against. Infra `e6acf532`.
13 + kind = "service"
14 +
15 + # aarch64 only, and the asymmetry is deliberate rather than an omission.
16 + #
17 + # The Hetzner box already gets this binary, but it gets it INSIDE the MNW server
18 + # release: Sando ships it as a companion to `/opt/mnw/current/companions/mnw-cli`,
19 + # so prod's copy is pipelined already and building a second x86_64 one here would
20 + # be two delivery stories for one binary on one machine. astra's copy is the one
21 + # with no pipeline, and astra is aarch64.
22 + #
23 + # If prod ever stops carrying it as a companion, add `linux/x86_64` here and a
24 + # second `[[deploy]]` -- do not hand-place it.
25 + targets = ["linux/aarch64"]
26 +
27 + version_path = "Cargo.toml"
28 +
29 + # MNW is one .git over the server, sando, multithreaded, pom, wam, magicmirror
30 + # and mnw-cli, each versioned separately, so the default `v{version}` names no
31 + # product in particular here.
32 + tag_format = "mnw-cli-v{version}"
33 +
34 + # One target, so this is inert either way. Stated rather than omitted so the
35 + # answer does not have to be worked out from first principles if a second
36 + # target is ever added.
37 + require_all_targets = true
38 +
39 + # The service host. A deploy destination is deliberately NOT a `[[host]]` in the
40 + # daemon config: a build host is granted build/package, a service host
41 + # deploy/restart, and neither is granted the other's.
42 + #
43 + # install_path is /usr/local/bin/mnw-cli, which is a MOVE: the running binary
44 + # was at /opt/mnw-cli/mnw-cli, placed there by hand in April. install-service.sh
45 + # refuses any dst outside /usr/local/bin, and that guard is the only thing
46 + # bounding a NOPASSWD sudo grant, so the binary moves rather than the guard
47 + # widening. That is the same call wam made on 2026-08-15 and for the same
48 + # reason. The unit's ExecStart moves with it; WorkingDirectory stays
49 + # /opt/mnw-cli, which is its state directory (.env, HOME, ReadWritePaths) and
50 + # not where the binary lives.
51 + #
52 + # No `health_url`: mnw-cli speaks SSH, not HTTP. The recipe asserts the unit is
53 + # active and that the port answers with an SSH banner instead, which is the same
54 + # question in this service's own vocabulary.
55 + [[deploy]]
56 + target = "linux/aarch64"
57 + host = "astra"
58 + install_path = "/usr/local/bin/mnw-cli"
59 + service = "mnw-cli.service"
@@ -1,0 +1,80 @@
1 + // mnw-cli: Linux release recipe for Bento (aarch64 on astra).
2 + //
3 + // Ends at `deploy`, not `collect`. mnw-cli installs itself rather than handing
4 + // off to Sando, and that is a judgement about blast radius rather than a house
5 + // style: a bad deploy here stops git-over-SSH to the mirrors on astra. Nothing
6 + // user-facing, and the mirrors are the redundancy story rather than the serving
7 + // one, so it earns a real install path with a rollback and not Sando's tiers.
8 + // Same rung as wam, one below pom. See wiki `deploy-ceremony-tiers`.
9 + //
10 + // One arch, one host, and no host is named anywhere in here: build_host()
11 + // resolves to whichever native machine the topology assigns aarch64, and
12 + // deploy() to whichever machine bento.toml says runs it.
13 +
14 + let h = build_host();
15 + let v = version();
16 + let r = repo();
17 +
18 + step("checkout");
19 + // Pin to the release tag, not whatever main is at pull time. The binary that
20 + // ships has to be the binary the tag names, or the version it reports below is
21 + // a claim about a commit nobody built.
22 + let sha = checkout_sha(h);
23 + log("pinned " + h + " to mnw-cli-v" + v + " @ " + sha);
24 +
25 + step("prebuild");
26 + sh_ok(h, "cd " + r + " && cargo clippy --all-targets " + feature_flags() + " -- -D warnings");
27 + sh_ok(h, "cd " + r + " && cargo test " + feature_flags());
28 +
29 + step("build");
30 + sh_ok(h, "cd " + r + " && cargo build --release " + feature_flags());
31 + let binary = resolve_artifact(h, r + "/target/release/mnw-cli");
32 +
33 + step("verify");
34 + // The build host must not produce a binary the service host's glibc is too old
35 + // to exec. Answerable here because this app keeps its `[[deploy]]` entry: the
36 + // check needs to know which machine runs the binary, and that entry is where
37 + // that is written.
38 + log(glibc_check(binary));
39 +
40 + // The version about to ship is the version in the tag. A binary reporting
41 + // something else means the checkout and the release disagree.
42 + //
43 + // This assertion is the reason `--version` exists on this binary at all. It was
44 + // added 2026-08-23 with this recipe: before it, the only way to ask a running
45 + // mnw-cli what it was was to stat the file, which is exactly how astra's copy
46 + // sat four months stale without anyone noticing.
47 + sh_ok(h, binary + " --version | grep -qw " + v);
48 +
49 + step("deploy");
50 + // Stages under /var/tmp/bento-deploy/mnw-cli/ and calls the privileged
51 + // installer, which keeps the outgoing binary as <dst>.prev, installs
52 + // atomically, and restarts the unit. That .prev file is the whole rollback
53 + // story, and it is proportionate: putting it back and restarting is one
54 + // command.
55 + log(deploy(binary));
56 +
57 + // Health assertions live inside the deploy step rather than a step of their
58 + // own. `step()` names one of a fixed set the daemon knows, and a "health" step
59 + // would abort the run AFTER the install has already happened -- the binary live
60 + // and the release reading failed, which is the worst of both. Assertions about
61 + // the running service belong to the step that started it.
62 + //
63 + // What "healthy" means is the service's business. mnw-cli speaks SSH rather
64 + // than HTTP, so there is no status code to read: the proof is that the unit is
65 + // active and that the port answers with an SSH identification string. A dead
66 + // binary satisfies neither, and a binary that starts and then panics satisfies
67 + // the first only, which is why both are here.
68 + sh_ok(deploy_host(), "test -x " + install_path());
69 + sh_ok(deploy_host(), "systemctl is-active --quiet mnw-cli.service");
70 + //
71 + // 2222 is hardcoded here and is config on the host (`SSH_PORT` in
72 + // /opt/mnw-cli/.env, read 2026-08-23). Not read from that file on purpose: it
73 + // is owned by the service user and the deploy user would need sudo to see it,
74 + // which is a wider grant than an assertion is worth. If the port ever moves,
75 + // this line moves with it -- and it failing loudly is the correct outcome of
76 + // forgetting, since a release that cannot find the service it just restarted
77 + // should not read green.
78 + sh_ok(deploy_host(), "banner=$(timeout 5 bash -c 'exec 3<>/dev/tcp/127.0.0.1/2222; head -c 4 <&3' 2>/dev/null); test \"$banner\" = 'SSH-' || { echo \"mnw-cli did not answer with an SSH banner on 2222: got '$banner'\"; exit 1; }");
79 +
80 + log("mnw-cli " + v + " (" + target() + ") built on " + h + " and live on " + deploy_host());