Skip to main content

max / makenotwork

5.2 KB · 98 lines History Blame Raw
1 // pom: Linux release recipe for Bento (x86_64 on fw13, aarch64 on astra).
2 //
3 // One recipe serves both arches. build_host() resolves to whichever native host
4 // the topology assigns the target and deploy() resolves to whichever machine
5 // bento.toml says that target runs on, so there is no cross-compilation and no
6 // hard-coded host name anywhere in here. That matters more than usual: the two
7 // legs of this release are different architectures on different machines, and a
8 // recipe that named hosts could install the aarch64 binary on the x86_64 box.
9 //
10 // Replaces deploy/deploy.sh, which cross-compiled with cargo zigbuild (against
11 // the native-per-architecture rule, and with a cargo-zigbuild that is not
12 // installed on fw13 anyway), reached Hetzner on a port that refuses this
13 // machine's key, and copied config and the unit file over the live ones with no
14 // diff and no backup. It did not work as written when 0.4.0 went out; that
15 // deploy was done by hand.
16 //
17 // This recipe no longer installs anything. Under the Sando/Bento boundary Bento
18 // builds and packages; Sando decides whether a thing advances a stage. So the
19 // recipe ends at `collect`, and the daemon's `[handoff.pom]` carries the
20 // artifact to sandod, which verifies it against its record and runs the gates
21 // that are about the artifact IN AN ENVIRONMENT -- node health, burn-in, the
22 // restart, and rollback if it goes wrong.
23 //
24 // The split is not arbitrary. What stays here is evidence about the ARTIFACT:
25 // clippy, the tests, the glibc check, the version assertion. Those are answerable
26 // on a build host. What left is evidence about the artifact in a place, which a
27 // build host cannot honestly produce.
28
29 let h = build_host();
30 let v = version();
31 let r = repo();
32
33 step("checkout");
34 // Pin to the release tag v<version>, not whatever main is at pull time. The
35 // daemon runs the same pin as a cross-host barrier before either target builds,
36 // so both arches come from one commit rather than from two machines' idea of
37 // main.
38 let sha = checkout_sha(h);
39 log("pinned " + h + " to v" + v + " @ " + sha);
40
41 // Gate: nothing is installed on a host that watches production from code that
42 // fails clippy or its tests. It runs on this target's own build host, so a
43 // break confined to one architecture is caught where it would have shipped.
44 step("prebuild");
45 sh_ok(h, "cd " + r + " && cargo clippy --workspace --all-targets " + feature_flags() + " -- -D warnings");
46 sh_ok(h, "cd " + r + " && cargo test --workspace " + feature_flags());
47
48 step("build");
49 sh_ok(h, "cd " + r + " && cargo build --release " + feature_flags());
50 let binary = resolve_artifact(h, r + "/target/release/pom");
51
52 step("verify");
53 // No `glibc_check` here any more, and its absence is the boundary rather than
54 // an omission. The check compares the built binary's highest GLIBC symbol
55 // against `ldd --version` on the machine that runs it, so it needs to know that
56 // machine -- it reads the `[[deploy]]` entry, which a handed-off service does
57 // not have. It is evidence about the artifact IN A PLACE, which is Sando's half
58 // of the split, and it was the one call in this recipe still on the wrong side.
59 //
60 // This is a real check to be without in the meantime, not a formality. pom's
61 // x86_64 half builds on fw13 and runs on Ubuntu 24.04 in Hetzner; measured at
62 // 0.4.2 the binary needs GLIBC 2.39 and that box has exactly 2.39. One point
63 // release of drift on the build host and it fails at exec, after the unit has
64 // already restarted onto it. Sando gaining the gate is GoingsOn work; until it
65 // does, node_health catches it after the restart rather than before.
66 //
67 // The version that is about to ship is the version in the tag. A binary
68 // reporting something else means the checkout and the release disagree.
69 sh_ok(h, binary + " --version | grep -qw " + v);
70
71 step("collect");
72 // Hand the binary to the daemon, which hashes it, writes the artifact record
73 // beside it, and (per `[handoff.pom]`) stages it into sando and asks for an
74 // intake. Nothing is installed or restarted from here.
75 //
76 // One collect per target, and the two never collide: the daemon files each
77 // under <dist_root>/pom/<version>/<target>/, so the aarch64 and x86_64 binaries
78 // are siblings rather than one overwriting the other. Sando then resolves which
79 // of the two each node takes from the platform on its own build record, so the
80 // aarch64 binary cannot land on the Hetzner box even by mistake.
81 collect(h, binary, "pom", v);
82
83 log("pom " + v + " (" + target() + ") built on " + h + " and handed off");
84
85 // What used to follow, and where it went:
86 //
87 // deploy(binary) -> sando promotes, through the ladder
88 // health poll after restart -> sando's node_health gate on each tier
89 // install_path --version -> sando verifies the bundle digest ON THE NODE
90 // after rsync and before the symlink swap, which is
91 // a stronger check than re-running --version: it
92 // proves the bytes, not just the number they print.
93 //
94 // The bootstrap caveat that lived here still holds, and is now sando's to
95 // honour: pom watches its own deploy, so restarting it takes the watcher down
96 // with it. The instances are promoted one tier at a time and health is read
97 // from a peer over the mesh -- never both at once.
98