Recipe for a fuz_app server's first-admin bootstrap token.
fuz_app_bootstrap_token builds the check/run of the
bootstrap-token script resource a fuz_app server deploy declares. On a
fresh deploy the fuz_app server exposes /api/account/bootstrap while a token
file exists at FUZ_BOOTSTRAP_TOKEN_PATH; this resource mints that token once.
Idempotency comes entirely from the two-part check:
test -f P -a ! -h P -a -s P covers the freshly-minted case (the token file
is still on disk). An empty file fails it, so a mint that wrote nothing is
retried rather than read as converged; so does a directory at the token path,
which test -s alone would pass, and a symlink, which test -f and test -s
follow: the check runs as root, so a link at the path to any non-empty file
would otherwise read as a minted token. It is no boundary against user, who
owns config_dir and can write a regular file there that the server then reads
as its token.- the
bootstrap_lock psql probe covers the case where the fuz_app handler
already consumed and deleted the token file on success — the row marks the
server bootstrapped, so a later apply skips run rather than re-minting an
inert token with no consumer.
On a brand-new server (no file, no lock row) the check fails, run mints the
token, and a re-apply converges. The check carries the whole idempotency
contract here, so keep it precise.
To force a re-mint (e.g. rotating a stuck token), clear the target-side state
the check reads: remove <config_dir>/secret_bootstrap_token and reset the
bootstrap_lock row, so the next apply's check fails and run mints again.
Recipes are imported at config eval time inside zap's sandboxed config eval
(fgro eval), which has no network, no writes, and no environment, and where
the clock and randomness throw. This module is therefore a pure function: it only assembles strings
from its arguments — no I/O, no clock, no randomness, no imports beyond types and
the pure single_quote, which quotes each value it splices into a command, and
sudo_as_user.
The openssl rand call runs on the target at apply time, not at config
eval, so determinism of the plan is preserved. The target needs openssl;
without it run fails and writes nothing.
user and config_dir are refused at config eval when they can't mean what
the recipe assumes: an empty user, one starting with #, which sudo -u
reads as a uid (#0 is root), or a reference, whose value isn't known until it
runs (sudo_as_user); and a config_dir that isn't absolute.
The caller spreads the result into a script resource, e.g.
zap.script({id: 'bootstrap-token', name: '...', ...fuz_app_bootstrap_token({user, service, config_dir}), depends_on: [...]}).