| 1 |
# Local dev defaults. Override path with SANDO_CONFIG. |
| 2 |
listen = "127.0.0.1:7766" |
| 3 |
db_path = "./sando.db" |
| 4 |
topology_path = "../sando.toml" |
| 5 |
# Hostname (/proc/sys/kernel/hostname) this daemon may build on. build::run |
| 6 |
# refuses to compile on any other host, so a misdeployed daemon can't build on |
| 7 |
# prod. Required. |
| 8 |
build_host = "fw13" |
| 9 |
workdir = "./work" |
| 10 |
release_root = "./releases" |
| 11 |
# Shared cargo target dir across per-sha worktrees — incremental rebuilds reuse |
| 12 |
# the previous sha's compiled deps instead of clean-compiling each fresh |
| 13 |
# worktree. Safe because builds are serialized. Omit for per-worktree target/. |
| 14 |
cargo_target_dir = "./cargo-target" |
| 15 |
|
| 16 |
# Dropped and recreated on every migration_dry_run. Leave unset to skip. |
| 17 |
# The role must be SUPERUSER on the scratch cluster: the gates reset it, seed the |
| 18 |
# dump's owner role into it, and drop stale mnw_test_* clones left by a killed |
| 19 |
# run (including foreign-owned ones, which DROP DATABASE refuses without |
| 20 |
# superuser). The daemon asserts this at startup and refuses to boot otherwise. |
| 21 |
# ALTER ROLE sando SUPERUSER; |
| 22 |
scratch_db_url = "postgres://sando@127.0.0.1/sando_scratch" |
| 23 |
# Role that owns the objects in a prod pg_dump. The dump carries `ALTER ... OWNER |
| 24 |
# TO <role>` for every object, so it must exist in the scratch cluster before a |
| 25 |
# restore; reset_scratch creates it NOLOGIN and grants it CREATE on public, so no |
| 26 |
# manual SQL is needed on a fresh box. Must match the prod DB owner. |
| 27 |
scratch_owner_role = "makenotwork" |
| 28 |
|
| 29 |
# Databases the migration_dry_run gate dry-runs, in order: restore that |
| 30 |
# database's prod dump into a scratch DB, then run the worktree's migrations on |
| 31 |
# top. `backup` names a [[backup]] entry in the topology (sando.toml). Omit the |
| 32 |
# whole key to get just the first entry, which is the historical behavior. |
| 33 |
# |
| 34 |
# The server check leaves `scratch_db` unset, so it runs against scratch_db_url |
| 35 |
# itself and leaves it in migrated state for the cargo_test gate to reuse. Every |
| 36 |
# other check must name its own database; the daemon creates it (DROP + CREATE) |
| 37 |
# at the start of the check, so a new entry owes no host bootstrap step. |
| 38 |
[[migration_check]] |
| 39 |
dir = "server/migrations" |
| 40 |
backup = "server" |
| 41 |
|
| 42 |
# multithreaded applies its own 36+ migrations at boot (multithreaded/src/main.rs, |
| 43 |
# `sqlx::migrate!()`) against its own database, so it carried the server's |
| 44 |
# exposure with none of the server's gate: an edited already-applied migration |
| 45 |
# would not fail a dry run, it would fail to boot in prod. `owner_role` is that |
| 46 |
# dump's owner — pg_dump emits `ALTER ... OWNER TO multithreaded` for every |
| 47 |
# object, and the role must exist in the scratch cluster before the restore. |
| 48 |
[[migration_check]] |
| 49 |
dir = "multithreaded/migrations" |
| 50 |
backup = "multithreaded" |
| 51 |
scratch_db = "sando_scratch_mt" |
| 52 |
owner_role = "multithreaded" |
| 53 |
|
| 54 |
# TypeScript frontends the code_smoke gate compiles. Both build scripts downgrade |
| 55 |
# a tsc error to a cargo::warning so the Rust build still succeeds against a |
| 56 |
# stale static/dist/; this is where that failure is fatal instead. See the |
| 57 |
# frontend_build docs in daemon/src/config.rs. |
| 58 |
[[frontend_build]] |
| 59 |
dir = "server/frontend" |
| 60 |
|
| 61 |
[[frontend_build]] |
| 62 |
dir = "multithreaded/frontend" |
| 63 |
|
| 64 |
# Companion crates built from the same worktree/sha as the server and staged |
| 65 |
# into the release bundle, so a contract-coupled service can't drift out of |
| 66 |
# lockstep. mnw-cli is the public git-SSH server that proxies to /api/internal/* |
| 67 |
# — a two-month drift is what broke git hosting during the 0.10.14 deploy. Which |
| 68 |
# nodes install it is set per-node in the topology (see prod-1 in sando.toml). |
| 69 |
[[companion]] |
| 70 |
name = "mnw-cli" |
| 71 |
manifest_dir = "mnw-cli" |
| 72 |
bin = "mnw-cli" |
| 73 |
|
| 74 |
# multithreaded (forums.makenot.work) runs on alpha-west-1, the same host as the |
| 75 |
# server, x86_64, so the build host can compile it and the never-cross-compile |
| 76 |
# rule is satisfied. Its own deploy path was multithreaded/deploy/deploy-hetzner.sh, |
| 77 |
# which cross-compiled on macOS via cargo zigbuild and scp'd to root@ — against |
| 78 |
# that rule, and 41 commits behind by the time it was found (2026-07-30). |
| 79 |
# |
| 80 |
# mt is a single file to install because it embeds its own `static/` tree |
| 81 |
# (multithreaded/src/static_assets.rs). It used to serve those assets off disk |
| 82 |
# beside the binary, which the one-file companion mechanism cannot ship. |
| 83 |
# |
| 84 |
# Note this is a companion and NOT an entry in `bin_names`: bin_names lists the |
| 85 |
# bin targets of the SERVER crate, resolved under its own target/release, so |
| 86 |
# naming multithreaded there would fail the build looking for a binary the |
| 87 |
# server never produces. Wiki sando-mt-pom-pipelines had it wrong. |
| 88 |
[[companion]] |
| 89 |
name = "multithreaded" |
| 90 |
manifest_dir = "multithreaded" |
| 91 |
bin = "multithreaded" |
| 92 |
|
| 93 |
# Crates the cargo_test gate runs, in order. This list used to be hardcoded to |
| 94 |
# `server`, so every other crate in the repo shipped ungated — including |
| 95 |
# mnw-cli, which is BUILT as a companion and installed onto prod-1 in the same |
| 96 |
# promote it ships. All of these were verified green on 2026-07-21 before being |
| 97 |
# added; the gate stops at the first red one. |
| 98 |
# |
| 99 |
# scratch_db exports DATABASE_URL/TEST_DATABASE_URL. Only the server needs it |
| 100 |
# (its sqlx macros type-check against a live DB). Leaving it off elsewhere keeps |
| 101 |
# crates that ship offline `.sqlx` data in offline mode. |
| 102 |
[[test_target]] |
| 103 |
dir = "server" |
| 104 |
features = ["fast-tests"] |
| 105 |
scratch_db = true |
| 106 |
|
| 107 |
# Companion: ships to prod-1, so it is gated first among the rest. |
| 108 |
[[test_target]] |
| 109 |
dir = "mnw-cli" |
| 110 |
|
| 111 |
[[test_target]] |
| 112 |
dir = "multithreaded" |
| 113 |
scratch_db = true |
| 114 |
|
| 115 |
[[test_target]] |
| 116 |
dir = "pom" |
| 117 |
|
| 118 |
[[test_target]] |
| 119 |
dir = "wam" |
| 120 |
|
| 121 |
# docengine left the repo for Libraries/docengine on 2026-07-30 and is consumed |
| 122 |
# through the [[aux_repo]] checkout, which sits BESIDE the worktree rather than |
| 123 |
# under it. `dir = "shared/docengine"` therefore stopped resolving that day and |
| 124 |
# quietly became a warn-and-skip, indistinguishable from a bisect skip: the gate |
| 125 |
# stayed green having run one crate fewer than it claims. `aux_repo` names the |
| 126 |
# checkout, and `dir` is empty because the crate is at that repo's root. |
| 127 |
# |
| 128 |
# Still worth gating despite living in another repo: the checkout is at branch |
| 129 |
# HEAD and is compiled into these binaries, so a break there breaks this build. |
| 130 |
[[test_target]] |
| 131 |
aux_repo = "docengine" |
| 132 |
all_features = true |
| 133 |
|
| 134 |
# No tests of its own yet; listed so a compile break still fails the gate. |
| 135 |
[[test_target]] |
| 136 |
dir = "shared/egui-updater" |
| 137 |
|
| 138 |
[[test_target]] |
| 139 |
dir = "shared/kberg" |
| 140 |
all_features = true |
| 141 |
|
| 142 |
[[test_target]] |
| 143 |
dir = "shared/livechat" |
| 144 |
all_features = true |
| 145 |
|
| 146 |
[[test_target]] |
| 147 |
dir = "shared/ops-core" |
| 148 |
|
| 149 |
[[test_target]] |
| 150 |
dir = "shared/ops-exec" |
| 151 |
all_features = true |
| 152 |
|
| 153 |
[[test_target]] |
| 154 |
dir = "shared/pom-contract" |
| 155 |
|
| 156 |
[[test_target]] |
| 157 |
dir = "shared/s3-storage" |
| 158 |
|
| 159 |
[[test_target]] |
| 160 |
dir = "shared/synckit-client" |
| 161 |
all_features = true |
| 162 |
|
| 163 |
[[test_target]] |
| 164 |
dir = "shared/tagtree" |
| 165 |
|
| 166 |
# ---- products ---- |
| 167 |
# Products this daemon ships. Declaring any of these means this file is no longer |
| 168 |
# both halves: the daemon keys stay here and each product points at its own |
| 169 |
# pipeline config. `mnw` points back at this same file, which is what keeps the |
| 170 |
# rest of it meaningful and what makes the change a pure addition — the routes |
| 171 |
# an operator types (`/promote/b`) still address MNW, and pom lives under |
| 172 |
# `/apps/pom/`. |
| 173 |
[app.mnw] |
| 174 |
config = "sando-daemon.toml" |
| 175 |
|
| 176 |
# pom is intake-only: Bento builds it on astra (aarch64) and Hetzner (x86_64), |
| 177 |
# Sando gates and promotes what arrives. See sando-pom.toml. |
| 178 |
[app.pom] |
| 179 |
config = "sando-pom.toml" |
| 180 |
|
| 181 |
# Extra env for the code_smoke gate's binary. The fixed set (DATABASE_URL, HOST, |
| 182 |
# PORT, ...) overrides anything here, so this cannot redirect the gate. |
| 183 |
# |
| 184 |
# SEED_MEDIA_CACHE: the example seed fetches 34 third-party media assets and |
| 185 |
# caches them by id, trusting a cached file only when it matches the digest the |
| 186 |
# manifest pins. That cache defaults to `$TMPDIR/mnw-seed-media`, and sandod runs |
| 187 |
# with `PrivateTmp=true`, so every build got an empty `/tmp` and re-downloaded |
| 188 |
# all 34 from metmuseum.org and Wikimedia. Build 60 (2026-08-19) is what that |
| 189 |
# costs: seventeen of them came back as something other than the pinned bytes in |
| 190 |
# one burst, code_smoke went red, and 0.11.20 was parked on a release that had |
| 191 |
# nothing wrong with it. A persistent directory makes the cache work as intended |
| 192 |
# — the network is touched only for an asset not already held at its pinned |
| 193 |
# digest, so a bad minute at someone else's CDN cannot fail a build. |
| 194 |
[code_smoke_env] |
| 195 |
SEED_MEDIA_CACHE = "/srv/sando/seed-media-cache" |
| 196 |
|