Zaroz
Units your code controls

Signals

A unit talks to the platform through a small agent that runs beside it and shares the directory /zaroz. There is no network port: you write files, or call a Unix socket.

What a unit can signal (contract v1, a closed set)

SignalMeaning
readyI have loaded; send me traffic. Needed only with readiness: signal
accepting true or falsein or out of the load-balanced set while staying healthy. false removes the unit from traffic and from allocation within about a second
idle, busywhat the unit is doing. Drives idle_action and the rollout and scale-down choices. Only transitions are recorded
shutdownI am done. Same as exiting with code 0

Unknown names are ignored and logged by the agent, never fatal.

By file

TerminalCode
echo > /zaroz/signals/ready echo false > /zaroz/signals/accepting # full: stop sending players echo true > /zaroz/signals/accepting echo > /zaroz/signals/idle echo > /zaroz/signals/busy echo > /zaroz/signals/shutdown

The agent consumes each file as it appears. An empty accepting file means true.

By socket

HTTP/1.1 over the Unix socket /zaroz/signal.sock:

TerminalCode
curl --unix-socket /zaroz/signal.sock -X POST http://unit/ready curl --unix-socket /zaroz/signal.sock -X POST http://unit/accepting -d false curl --unix-socket /zaroz/signal.sock http://unit/unit curl --unix-socket /zaroz/signal.sock http://unit/user-data curl --unix-socket /zaroz/signal.sock "http://unit/allocation?wait=60" # long-poll up to 60 s curl --unix-socket /zaroz/signal.sock "http://unit/state?wait=60" # returns when draining starts

What a unit receives

FileContent
/zaroz/unit.jsonkey, labels (updated live), origin, workload, private name, public endpoints
/zaroz/user-data.jsonyour user_data, or null
/zaroz/allocation.jsonnull until allocated, then {id, allocated_at, generation}
/zaroz/state.json{draining: false} or {draining: true, reason: "stop" | "destroy" | "replace" | "scale_down"}

Every file is written atomically (a temporary name, then a rename), so a reader never sees half a file. Read them again when allocation.json or state.json changes; the socket's ?wait= long-polls save the polling.

A minimal unit

TypeScriptCode
import { watch, readFileSync, writeFileSync } from "node:fs"; const dir = process.env.ZAROZ_DIR ?? "/zaroz"; const read = (f: string) => JSON.parse(readFileSync(`${dir}/${f}`, "utf8")); const signal = (name: string, body = "") => writeFileSync(`${dir}/signals/${name}`, body); await startMyServer(); // load the map, open the port signal("ready"); watch(`${dir}/allocation.json`, () => { const allocation = read("allocation.json"); if (allocation) startMatch(read("user-data.json")); // the roster arrives here }); watch(`${dir}/state.json`, () => { if (read("state.json").draining) { signal("accepting", "false"); // stop taking new players finishMatchThenExit(); // exit 0 when done } });

Where signals go

The agent writes them to a per-unit object in the cluster; the platform watches it. Nothing billed depends on a signal, so a program that lies about its signals can only stop, idle-destroy or mark full its own unit.

If the region is unreachable, units keep serving and load balancing (readiness is local); allocation answers 503 region-unreachable.

Last modified on