| 1 |
# Deploying pom |
| 2 |
|
| 3 |
pom ships through **Bento**, not Sando, and not by hand. |
| 4 |
|
| 5 |
Sando cannot do this job. `sando-daemon.toml` sets `build_host = "fw13"` and |
| 6 |
`build::run` refuses to compile anywhere else, which is the never-build-on-prod |
| 7 |
invariant and also makes Sando single-architecture. pom runs on astra (aarch64) |
| 8 |
and on the Hetzner box (x86_64), so Sando could never build half of this release |
| 9 |
without breaking its own rule. Bento already fans out native builds across both |
| 10 |
hosts, so pom is a Bento recipe. |
| 11 |
|
| 12 |
`deploy.sh` is gone. It cross-compiled with `cargo zigbuild` (against the |
| 13 |
native-per-architecture rule, with a cargo-zigbuild that is not installed on |
| 14 |
fw13), reached Hetzner on port 2200 which refuses this machine's key, and copied |
| 15 |
config and the unit file over the live ones with no diff and no backup. It did |
| 16 |
not work as written when 0.4.0 went out; that deploy was done by hand. |
| 17 |
|
| 18 |
## Running a deploy |
| 19 |
|
| 20 |
Bump the version in `Cargo.toml`, commit, tag `v<version>`, push, then trigger |
| 21 |
the build through Bento for both targets. The `/deploy` skill has the procedure. |
| 22 |
|
| 23 |
What the recipe does, per target (`dist/recipes/linux.rhai`): |
| 24 |
|
| 25 |
1. Pin the checkout to the release tag, and check both hosts report one commit. |
| 26 |
2. `cargo clippy -D warnings` and `cargo test`, on that target's own build host. |
| 27 |
3. `cargo build --release`. |
| 28 |
4. Compare the binary's highest `GLIBC_` symbol against the service host's |
| 29 |
`ldd --version`, and assert `pom --version` matches the tag. |
| 30 |
5. Stage the binary on the service host and call the privileged installer. |
| 31 |
6. Poll `/api/health` until the restarted instance answers, then assert the |
| 32 |
running binary reports the version that was just installed. |
| 33 |
|
| 34 |
The two instances go one at a time, and `require_all_targets` keeps the release |
| 35 |
from counting as done until both are green. pom watches its own deploy, so the |
| 36 |
restart takes one watcher down for a moment; the other stays up and keeps |
| 37 |
watching. Never both at once. |
| 38 |
|
| 39 |
## What a deploy does not touch |
| 40 |
|
| 41 |
**Config.** `pom-astra.toml` and `pom-hetzner.toml` differ per instance, and the |
| 42 |
live config on prod once carried a `[targets.mnw.tests]` block this repo did not |
| 43 |
have. `deploy.sh` would have silently deleted it. Config is a separate, |
| 44 |
deliberate act; the installer refuses to go near it. |
| 45 |
|
| 46 |
**The unit file.** Same reasoning. A hardened unit that has drifted from the |
| 47 |
repo is a question for a human, not something a binary deploy overwrites. |
| 48 |
|
| 49 |
## One-time host setup |
| 50 |
|
| 51 |
Each host that runs pom needs the installer and its scoped sudo grant: |
| 52 |
|
| 53 |
``` |
| 54 |
sudo install -d /usr/local/lib/bento |
| 55 |
sudo install -m 0755 install-service.sh /usr/local/lib/bento/install-service.sh |
| 56 |
sudo install -m 0440 bento-deploy.sudoers /etc/sudoers.d/bento-deploy |
| 57 |
sudo visudo -cf /etc/sudoers.d/bento-deploy |
| 58 |
``` |
| 59 |
|
| 60 |
Edit the sudoers file first so the user matches how Bento reaches that host: |
| 61 |
`max` on astra, `root` over Tailscale SSH on the Hetzner box. The grant covers |
| 62 |
one script and nothing else; the script bounds its own arguments (source under |
| 63 |
`/var/tmp/bento-deploy`, destination under `/usr/local/bin`, a bare `*.service` |
| 64 |
unit), so it is a script-guarded grant rather than a broad |
| 65 |
`install` + `systemctl` one. |
| 66 |
|
| 67 |
## Rollback |
| 68 |
|
| 69 |
The installer keeps the previous binary as `<install-path>.prev`. Rolling back |
| 70 |
is putting it back and restarting: |
| 71 |
|
| 72 |
``` |
| 73 |
sudo install -m 0755 /usr/local/bin/pom.prev /usr/local/bin/pom |
| 74 |
sudo systemctl restart pom.service |
| 75 |
``` |
| 76 |
|