Command reference
oko install
Installs this build, natively signed through Stado's product signing, over the installed one, links it on PATH, and has the installed build serve new terminals at once while the replaced service keeps only the terminals it started.
Invocation#
oko install [--into PATH] [--identity NAME]Inputs and options#
- --into, the destination (the declared install path when omitted); --identity, the signing identity passed to Stado.
Output and state effect#
- Stages, signs and replaces the binary; prints `installed <path> (<size> bytes) with verified native signing` and the signer's words.
- A running service of a build that knows the handover gives its control socket to the installed build at once (`the installed build serves new terminals on <socket> now; …`) and keeps answering for its own terminals on `<socket>.retired.<pid>`, through which the serving broker lists and forwards them.
- A running service of an older build only stops admitting terminals; install then starts the installed build on the socket (`the running build predates the handover; started the installed build on <socket> as process <pid>, …; it writes to <socket>.serve.log`). That broker finds the ownership lock held, asks the holder's version, and — when it is this executable's own path and does not know `yield` — moves its socket to `<socket>.retired.<pid>` and its lock beside it, then binds the control socket (`serve: took the control socket <socket> from the replaced build (pid <pid>), which knows no handover; …`). A holder of another executable is never moved: `another process holds the broker ownership lock <path>, and the broker answering on <socket> is not a replaced build of this executable that could be taken over`. A holder that answers nothing on the socket — its socket file was removed or replaced while it runs, so it listens on a path no client reaches — is not moved either, because nothing tells it apart from a broker that has taken the lock and not yet bound; the broker refuses naming both: `another process holds the broker ownership lock <path>, but no broker answers on <socket> (<cause>), so the holder serves no control socket a client can reach; end the oko serve process that holds <path> (lsof <path> names it) and this broker can take the socket`.
- When nothing answers on the control socket — the build a retiring broker handed it to never came up or ended, or the socket file was removed while the service process still runs — install starts the installed build there (`no Oko broker answered on <socket> (<cause>); started the installed build there as process <pid>, which owns the control socket now; it writes to <socket>.serve.log`), so an install ends with a broker answering, or refuses naming why the started build did not listen; while a holder that answers nothing keeps the ownership lock, the started build ends with the refusal above in its log. A running service whose executable is replaced does the same when it handed the socket over earlier and nothing answers there now (`serve: nothing owns the control socket <socket> (<cause>); starting <path> on it`): a retired broker that still holds terminals never ends, so launchd never restarts the unit to take the socket back.
- A broker started by install or by a retiring broker runs in its own session with its input closed and its output appended to `<socket>.serve.log`, never to the output of the process that started it. It is handed one end of a socket pair (named by `OKO_BROKER_READY_FD`, which it clears at once) and writes `broker listening on <socket>` there as soon as it is bound; the starter waits for that line, announced on standard error as a wait on the process, and only then says the installed build listens. So `oko install` returns once the started broker listens, never later; read the log for the broker's own lines.
- Nothing collects the end of a broker started in its own session, so the broker says it itself: ended by SIGTERM, SIGHUP, SIGINT or SIGQUIT, it writes `serve: the broker on <socket> ends on signal <n> (<name>) sent by process <pid> (<its command line>)` to its log, then ends on that signal as before. The sender's command line is read from the kernel (`KERN_PROCARGS2`), not by running `ps`; when the kernel keeps its arguments from this user it is the kernel's short name in parentheses, and when the sender is gone the parentheses say `unread: the kernel no longer describes it to this user`. SIGKILL cannot be caught: a log that stops without that line was ended by SIGKILL or by the machine. A broker that leads its own process group (as launchd and install start it) first passes the signal to every other process of that group — the programs it runs to answer, such as a policy pass's `stado` or `tama` — and waits until the kernel says each ended, declared as a wait on the process (`czekam: the broker's process group to end after signal <n>: <pid> (<command line>), …`), saying `serve: process <pid> (<command line>) of the broker's group ended` for each; one already gone or one it may not signal is named and not waited for. A second signal ends a broker still waiting at once. A broker inside another program's group (a shell's job) signals nothing but itself. Its terminals lead groups of their own and end with their terminal once it is gone. The real journey `cargo test --manifest-path rust/Cargo.toml --test broker-ending-signal` sends a broker of its own SIGTERM and checks that line and the exit signal, and sends one leading its group, with one member that ends on SIGTERM and one that ignores it, SIGTERM twice: the wait names both, the first is said ended and killed by the signal, and the second SIGTERM ends the broker.
- A destination that exists but cannot be resolved refuses with `the install destination <path> cannot be resolved: <error>` before anything is staged; a destination not installed yet is taken as named. A stale staged build that cannot be removed fails staging (`could not stage <path>: <error>`). A staged build left after a refused signing or a failed replace is removed, and one that cannot be is named on stderr (`# the staged build <path> is left behind and cannot be removed: <error>`).
- Once a broker answers, install declares the standing routines in Stado (`oko routines standing`, printed `standing routine <name> <change>`) and the standing factory signals (`oko reflection signals standing`, printed `standing signal <name> declared|kept`), so the improvement loop of every model policy pass has signals to read; a signal already declared under a standing name is kept. Either refusing ends install with `installed <path>, but its standing routines … could not be declared: <detail>` or `installed <path>, but its standing signals could not be declared: <detail>`.
Refusals and safe exits#
- `<path> is already the installed build; run the build you want to install`; `could not stage <path>: <error>`; `stado product signing could not run: <error>`; `native signing refused <path>: <output>`; `could not replace <path>: <error>`; `installed <path>, but Oko retirement request failed on <socket>: <detail>; the running service was not replaced`; `installed <path>, but no broker answered on <socket> (<cause>) and the installed build could not be started there: <detail>`; `installed <path>, but the running build predates the handover and the installed one could not be started on <socket>: <detail>`, where the detail is `the log <socket>.serve.log of the installed build <path> could not be opened: <error>` when its log cannot be opened, `cannot make the channel the installed build <path> says it listens on: <error>`, `the installed build <path> (process <pid>) could not be heard on its readiness channel: <error>; read its log <socket>.serve.log`, or `the installed build <path> (process <pid>) closed its readiness channel without listening on <socket>; read its log <socket>.serve.log` when it ended before binding. A started broker that cannot write the line because its starter went away keeps serving and logs `serve: cannot tell the process that started this broker that it listens on <socket>: <error>`.