max / makenotwork
5 files changed,
+194 insertions,
-8 deletions
| @@ -1721,7 +1721,7 @@ | |||
| 1721 | 1721 | ||
| 1722 | 1722 | [[package]] | |
| 1723 | 1723 | name = "wam" | |
| 1724 | - | version = "0.2.0" | |
| 1724 | + | version = "0.3.0" | |
| 1725 | 1725 | dependencies = [ | |
| 1726 | 1726 | "axum", | |
| 1727 | 1727 | "chrono", | |
| @@ -2219,22 +2219,58 @@ | |||
| 2219 | 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2220 | 2220 | checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" | |
| 2221 | 2221 | ||
| 2222 | + | [[patch.unused]] | |
| 2223 | + | name = "synckit-client" | |
| 2224 | + | version = "0.8.0" | |
| 2225 | + | ||
| 2226 | + | [[patch.unused]] | |
| 2227 | + | name = "synckit-config" | |
| 2228 | + | version = "0.2.0" | |
| 2229 | + | ||
| 2222 | 2230 | [[patch.unused]] | |
| 2223 | 2231 | name = "docengine" | |
| 2224 | - | version = "0.4.0" | |
| 2232 | + | version = "0.7.0" | |
| 2225 | 2233 | ||
| 2226 | 2234 | [[patch.unused]] | |
| 2227 | 2235 | name = "kberg" | |
| 2228 | 2236 | version = "0.1.0" | |
| 2229 | 2237 | ||
| 2238 | + | [[patch.unused]] | |
| 2239 | + | name = "ops-status" | |
| 2240 | + | version = "0.1.0" | |
| 2241 | + | ||
| 2230 | 2242 | [[patch.unused]] | |
| 2231 | 2243 | name = "tagtree" | |
| 2232 | 2244 | version = "0.4.0" | |
| 2233 | 2245 | ||
| 2234 | 2246 | [[patch.unused]] | |
| 2235 | - | name = "synckit-client" | |
| 2236 | - | version = "0.8.0" | |
| 2247 | + | name = "quasi-axum" | |
| 2248 | + | version = "0.11.0" | |
| 2237 | 2249 | ||
| 2238 | 2250 | [[patch.unused]] | |
| 2239 | - | name = "synckit-config" | |
| 2240 | - | version = "0.2.0" | |
| 2251 | + | name = "quasi-basics" | |
| 2252 | + | version = "0.11.0" | |
| 2253 | + | ||
| 2254 | + | [[patch.unused]] | |
| 2255 | + | name = "quasi-http" | |
| 2256 | + | version = "0.11.0" | |
| 2257 | + | ||
| 2258 | + | [[patch.unused]] | |
| 2259 | + | name = "quasi-immediate" | |
| 2260 | + | version = "0.11.0" | |
| 2261 | + | ||
| 2262 | + | [[patch.unused]] | |
| 2263 | + | name = "quasi-router" | |
| 2264 | + | version = "0.11.0" | |
| 2265 | + | ||
| 2266 | + | [[patch.unused]] | |
| 2267 | + | name = "quasi-store" | |
| 2268 | + | version = "0.1.0" | |
| 2269 | + | ||
| 2270 | + | [[patch.unused]] | |
| 2271 | + | name = "quasi-tauri" | |
| 2272 | + | version = "0.11.0" | |
| 2273 | + | ||
| 2274 | + | [[patch.unused]] | |
| 2275 | + | name = "quasi-webview" | |
| 2276 | + | version = "0.11.0" |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "wam" | |
| 3 | - | version = "0.2.0" | |
| 3 | + | version = "0.3.0" | |
| 4 | 4 | edition = "2024" | |
| 5 | 5 | license = "MIT" | |
| 6 | 6 |
| @@ -4,8 +4,17 @@ | |||
| 4 | 4 | ||
| 5 | 5 | use crate::types::{Channel, Priority, Status}; | |
| 6 | 6 | ||
| 7 | + | /// `version` is not decoration: Bento's privileged installer runs | |
| 8 | + | /// `<staged-binary> --version` on the service host before it installs anything, | |
| 9 | + | /// so a binary without the flag cannot be deployed through the pipeline at all. | |
| 10 | + | /// It is also the check that catches an aarch64 build staged onto the x86_64 | |
| 11 | + | /// box, which is the failure the two-arch fan-out exists to risk. | |
| 7 | 12 | #[derive(Parser)] | |
| 8 | - | #[command(name = "wam", about = "Whack-a-Mole -- distributed ticket manager")] | |
| 13 | + | #[command( | |
| 14 | + | name = "wam", | |
| 15 | + | version, | |
| 16 | + | about = "Whack-a-Mole -- distributed ticket manager" | |
| 17 | + | )] | |
| 9 | 18 | pub(crate) struct Cli { | |
| 10 | 19 | #[command(subcommand)] | |
| 11 | 20 | pub command: Option<Command>, |
| @@ -1,0 +1,71 @@ | |||
| 1 | + | # How Bento releases wam. Lives here rather than in the daemon's config so it is | |
| 2 | + | # versioned with the code it describes. | |
| 3 | + | # | |
| 4 | + | # A service, not an app or a library: wam is run rather than distributed. There | |
| 5 | + | # is no bundle for a user to download and no crate to publish. | |
| 6 | + | # | |
| 7 | + | # wam deploys ITSELF, and that is the deliberate difference from pom. pom hands | |
| 8 | + | # its artifact to Sando, which gates it and promotes it tier by tier; wam keeps | |
| 9 | + | # the `[[deploy]]` tables and lets Bento install and restart directly. The two | |
| 10 | + | # are not disagreeing about what good practice is. They are answering a question | |
| 11 | + | # about blast radius, and getting different answers: | |
| 12 | + | # | |
| 13 | + | # What does a bad deploy cost? For the MNW server it costs paying creators | |
| 14 | + | # their platform, so it earns burn-in, tiers, migration dry-runs and a | |
| 15 | + | # rollback horizon. For pom it costs the monitoring, which is how anyone finds | |
| 16 | + | # out the server broke, so it earns the gates too. For wam it costs an | |
| 17 | + | # internal ticket list nobody's money or morning depends on -- and the | |
| 18 | + | # recovery is `install-service.sh`'s own `<dst>.prev` plus a restart. | |
| 19 | + | # | |
| 20 | + | # Charging wam the full Sando ceremony would buy a rollback horizon for tickets | |
| 21 | + | # we could re-sync from the peer, and would cost an afternoon of topology per | |
| 22 | + | # release path. The ladder is a real cost and it should be spent where a bad | |
| 23 | + | # deploy is expensive. See wiki `deploy-ceremony-tiers`. | |
| 24 | + | kind = "service" | |
| 25 | + | ||
| 26 | + | # Two arches, two machines, built natively on each: x86_64 on fw13 for the | |
| 27 | + | # Hetzner box, aarch64 on astra for astra. Same reason pom needs both -- the | |
| 28 | + | # mesh has a node on each -- and the same reason neither can be built by Sando, | |
| 29 | + | # whose build::run compiles only on its single configured build host. | |
| 30 | + | targets = ["linux/x86_64", "linux/aarch64"] | |
| 31 | + | ||
| 32 | + | version_path = "Cargo.toml" | |
| 33 | + | ||
| 34 | + | # MNW is one .git over the server, sando, multithreaded, pom, wam and more, each | |
| 35 | + | # versioned separately, so the default `v{version}` names no product in | |
| 36 | + | # particular here. | |
| 37 | + | tag_format = "wam-v{version}" | |
| 38 | + | ||
| 39 | + | # Both nodes or neither. The mesh syncs tickets between them and the sync | |
| 40 | + | # protocol is the thing most likely to break across a version boundary, so a | |
| 41 | + | # release that lands on one node and fails on the other leaves the mesh talking | |
| 42 | + | # to itself across a version gap. That is exactly the split-mesh failure pom's | |
| 43 | + | # 0.4.1 release hit. | |
| 44 | + | require_all_targets = true | |
| 45 | + | ||
| 46 | + | # The service hosts. A deploy destination is deliberately NOT a `[[host]]` in | |
| 47 | + | # the daemon config: a build host is granted build/package, a service host | |
| 48 | + | # deploy/restart, and neither is granted the other's. | |
| 49 | + | # | |
| 50 | + | # install_path is /usr/local/bin/wam on both, which is a move: the running | |
| 51 | + | # binary is at /opt/wam/wam on Hetzner today, placed there by hand in April. | |
| 52 | + | # install-service.sh refuses any dst outside /usr/local/bin, and that guard is | |
| 53 | + | # the only thing bounding a NOPASSWD sudo grant, so the binary moves rather than | |
| 54 | + | # the guard widening. Both units' ExecStart move with it. | |
| 55 | + | ||
| 56 | + | # Hetzner, reached over Tailscale SSH on 22 as root. NOT port 2200: that is what | |
| 57 | + | # the retired deploy.sh used, and it refuses this machine's key. | |
| 58 | + | [[deploy]] | |
| 59 | + | target = "linux/x86_64" | |
| 60 | + | host = "root@100.120.174.96" | |
| 61 | + | install_path = "/usr/local/bin/wam" | |
| 62 | + | service = "wam.service" | |
| 63 | + | health_url = "http://127.0.0.1:7890/tickets" | |
| 64 | + | ||
| 65 | + | # astra, over the tailnet as max. | |
| 66 | + | [[deploy]] | |
| 67 | + | target = "linux/aarch64" | |
| 68 | + | host = "astra" | |
| 69 | + | install_path = "/usr/local/bin/wam" | |
| 70 | + | service = "wam.service" | |
| 71 | + | health_url = "http://127.0.0.1:7890/tickets" |
| @@ -1,0 +1,70 @@ | |||
| 1 | + | // wam: Linux release recipe for Bento (x86_64 on fw13, aarch64 on astra). | |
| 2 | + | // | |
| 3 | + | // One recipe serves both arches and both service hosts. build_host() resolves | |
| 4 | + | // to whichever native host the topology assigns the target, and deploy() | |
| 5 | + | // resolves to whichever machine bento.toml says that target runs on, so there | |
| 6 | + | // is no cross-compilation and no hard-coded host name anywhere in here. That | |
| 7 | + | // matters more than usual here: the two legs are different architectures on | |
| 8 | + | // different machines, and a recipe that named hosts could install the aarch64 | |
| 9 | + | // binary on the Hetzner box. | |
| 10 | + | // | |
| 11 | + | // Unlike pom's recipe this one ends at `deploy`, not `collect`. wam installs | |
| 12 | + | // itself rather than handing off to Sando, because a bad wam deploy costs an | |
| 13 | + | // internal ticket list rather than anyone's platform or the monitoring that | |
| 14 | + | // watches it. The reasoning is written out in bento.toml next to the | |
| 15 | + | // `[[deploy]]` tables; the short form is that the ladder is a real cost and | |
| 16 | + | // belongs where a bad deploy is expensive. | |
| 17 | + | ||
| 18 | + | let h = build_host(); | |
| 19 | + | let v = version(); | |
| 20 | + | let r = repo(); | |
| 21 | + | ||
| 22 | + | step("checkout"); | |
| 23 | + | // Pin to the release tag, not whatever main is at pull time. The daemon runs | |
| 24 | + | // the same pin as a cross-host barrier before either target builds, so both | |
| 25 | + | // arches come from one commit rather than from two machines' idea of main -- | |
| 26 | + | // which for a mesh that syncs between the two nodes is the difference between | |
| 27 | + | // one protocol and two. | |
| 28 | + | let sha = checkout_sha(h); | |
| 29 | + | log("pinned " + h + " to wam-v" + v + " @ " + sha); | |
| 30 | + | ||
| 31 | + | step("prebuild"); | |
| 32 | + | sh_ok(h, "cd " + r + " && cargo clippy --all-targets " + feature_flags() + " -- -D warnings"); | |
| 33 | + | sh_ok(h, "cd " + r + " && cargo test " + feature_flags()); | |
| 34 | + | ||
| 35 | + | step("build"); | |
| 36 | + | sh_ok(h, "cd " + r + " && cargo build --release " + feature_flags()); | |
| 37 | + | let binary = resolve_artifact(h, r + "/target/release/wam"); | |
| 38 | + | ||
| 39 | + | step("verify"); | |
| 40 | + | // The build host must not produce a binary the service host's glibc is too old | |
| 41 | + | // to exec. Bento can make this check here, unlike in pom's handed-off recipe, | |
| 42 | + | // precisely because wam keeps its `[[deploy]]` entry: the check needs to know | |
| 43 | + | // which machine runs the binary, and that entry is where that is written. | |
| 44 | + | log(glibc_check(binary)); | |
| 45 | + | ||
| 46 | + | // The version that is about to ship is the version in the tag. A binary | |
| 47 | + | // reporting something else means the checkout and the release disagree. | |
| 48 | + | sh_ok(h, binary + " --version | grep -qw " + v); | |
| 49 | + | ||
| 50 | + | step("deploy"); | |
| 51 | + | // Stages under /var/tmp/bento-deploy/wam/ and calls the privileged installer, | |
| 52 | + | // which keeps the outgoing binary as <dst>.prev, installs atomically, and | |
| 53 | + | // restarts the unit. That .prev file is wam's whole rollback story, and it is | |
| 54 | + | // proportionate: putting it back and restarting is one command. | |
| 55 | + | log(deploy(binary)); | |
| 56 | + | ||
| 57 | + | step("health"); | |
| 58 | + | // What "healthy" means is the service's business, so the recipe asserts it | |
| 59 | + | // itself against the host it just restarted. | |
| 60 | + | // | |
| 61 | + | // The assertion is 401, not 200, and that is the point of this release. The | |
| 62 | + | // binary being replaced is an April build that predates the require_auth | |
| 63 | + | // middleware: it answered /tickets with 200 and the full ticket body to an | |
| 64 | + | // unauthenticated caller. A 200 here would mean the new binary did not take. | |
| 65 | + | // 401 proves both that wam came back up and that it is refusing anonymous | |
| 66 | + | // reads, which is the one thing this deploy exists to change. | |
| 67 | + | sh_ok(deploy_host(), "test -x " + install_path()); | |
| 68 | + | sh_ok(deploy_host(), "code=$(curl -s -o /dev/null -w '%{http_code}' " + health_url() + "); test \"$code\" = 401 || { echo \"wam answered $code, expected 401 -- auth is not being enforced\"; exit 1; }"); | |
| 69 | + | ||
| 70 | + | log("wam " + v + " (" + target() + ") built on " + h + " and live on " + deploy_host()); |