A file nobody in this repo writes
The service install script in scripts/ opens with a header saying it is generated from a cwkSiblingKit template: edit the kit, then run the kit's sync. So do scripts/family-topology.sh, scripts/configure-tailscale-serve.sh and scripts/kit_family_endpoints.py. Every family engine built on this template installs the same way. Sizzle's own facts ride in as a few template variables that the kit's manifest keeps per repo, such as the app label Sizzle and the health path /api/health for the Serve script, and everything else is read at run time. sibling-kit-quest's vendor-dont-package track explains why these are vendored copies and not a package, and its parameterize-never-fork track explains why per-app facts go into variables instead of edits.
The script is idempotent. Run it after any engine change and it brings the Mac to the state of its checkout: environment, dependencies, web build, launchd job, health, tailnet twin. Mind the word checkout. The script reads the working tree as it finds it and never asks git anything, so an uncommitted edit is deployed exactly like a committed one. Commit and push first if the running engine should match origin.
Refuse the wrong Mac first
Its first move is to ask the registry whether it should run at all. kit_family_endpoints.py runs-here cwkSizzle (the member id is the repository directory's name) exits 0 only where the port ledger, read through this Mac's host profile, places Sizzle. Anywhere else the script says it runs only where the family registry places cwkSizzle, and exits 1 before it touches anything. The gate's logic never names a Mac; the placement it reads lives in the registry. When the registry placed Sizzle on a different Mac on 2026-10-22, the gate's answer followed the ledger.
Then it asks for the network facts. family-topology.sh fetches Sizzle's member card from Firelink's loopback API and prints zsh assignments: KIT_LOOPBACK_URL, KIT_LOOPBACK_PORT, KIT_TAILNET_HTTPS, KIT_HTTPS_PORT, an optional tailnet HTTP URL, the host affinity, and whether that host is this Mac. It refuses outright when the card has no loopback URL with a port, or no tailnet HTTPS URL with a port. The reason sits in its comment, in Dad's words:
하드코딩은 죄악
"Hardcoding is a sin." A port typed into a script is a second copy of a fact, and second copies rot.
The steps, in order
- Environment.
conda env update --prunefromconda-envs/cwk-sizzle.yml: Python 3.12, pip, and the pinnedbackend/requirements.txt(FastAPI, uvicorn, httpx, Pydantic, Pillow, pillow-heif, pytest). Sizzle has nopyproject.toml, so the script also runspip install -r backend/requirements.txt. - Web build.
npm installandnpm run buildinfrontend/, which writes thefrontend/distthe engine mounts. - Render the plist.
kit_family_endpoints.py render-plistwrites a concrete copy to a temp file, with@@HOME@@,@@DATA_ROOT@@and any@@PORT:<member>@@token replaced for this Mac. Every later step reads the rendered copy, never the template. The log directories are created from that copy, because launchd will not create a log's directory, and a job whose log cannot open never starts. - Reload launchd. Read the running job's
ExitTimeOut(20 seconds when unset), copy the rendered plist into~/Library/LaunchAgents, runbootout, then wait up to that timeout plus ten seconds for the old job to disappear.bootoutreturns before the job is gone, and uvicorn's graceful shutdown waits for requests in flight. Thenbootstrap, tried up to three times. The job hasRunAtLoad, so the bootstrap has already started it, and nokickstart -kfollows. One used to, and it killed the first boot. - Health. Up to fifteen tries, two seconds apart, of
curlon/api/healthat the loopback URL. A failure names the log paths from the rendered plist. - Tailnet twin. Run
configure-tailscale-serve.sh.
The tailnet twin
configure-tailscale-serve.sh repeats the runs-here gate and reads the same topology. It checks the engine's health on loopback first, then runs tailscale serve --bg --yes --https=$KIT_HTTPS_PORT in front of the loopback URL (7601 for Sizzle), and finally fetches /api/health through the tailnet HTTPS address to prove the twin answers. An HTTP twin is added only when the registry declares a tailnet HTTP URL and the engine listens on loopback alone. An engine bound to every interface answers tailnet HTTP itself, and a Serve listener on its port would collide. For Sizzle, teach the HTTPS twin on 7601: the engine binds 127.0.0.1, and whether the registry declares tailnet HTTP for it is not recorded in this repository.