recipe_bootstrap.ts

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: [...]}).

view source

Declarations
#

3 declarations

fuz_app_bootstrap_token
#

recipe_bootstrap.ts view source

(options: FuzAppBootstrapTokenOptions): FuzAppBootstrapTokenScript import {fuz_app_bootstrap_token} from '@fuzdev/zap/recipe_bootstrap.js';

Build the check/run for a fuz_app bootstrap-token script.

check passes when the token path is a non-empty regular file (not a symlink to one) or the bootstrap_lock row already marks the server bootstrapped; run mints a token of 32 random bytes as 64 lowercase hex characters at <config_dir>/secret_bootstrap_token (mode 600, owned by user), ending in one LF, which fuz_app's reader strips.

options

returns

FuzAppBootstrapTokenScript

the script check/run payload, ready to spread.

throws

  • Error - if `user` is empty, starts with `#`, or is a reference, or `config_dir` isn't absolute

FuzAppBootstrapTokenOptions
#

recipe_bootstrap.ts view source

FuzAppBootstrapTokenOptions import type {FuzAppBootstrapTokenOptions} from '@fuzdev/zap/recipe_bootstrap.js';

user

The service user that owns config_dir and writes the token file. run runs as them (sudo -u <user>), so the connecting user's sudo rule must allow running as them, even when it is the same user. A user name written out: not empty, not #<uid>, and not a reference.

type string

service

Database name probed by the bootstrap_lock fallback check (the psql_args default is -d <service>).

type string

config_dir

Absolute directory holding secret_bootstrap_token, e.g. /home/<user>/.myapp. The token path is <config_dir>/secret_bootstrap_token. Must already exist and be writable by user (declare the config-dir directory resource, owned by user, as a depends_on).

type string

psql_args?

psql connection args for the bootstrap_lock fallback probe. Defaults to -d <service>: the Unix socket, as the connecting user, whom the stock peer rule admits when the user is the role (the deploy user running the service, as fuz_app_env_base's DATABASE_URL assumes). These are shell words, spliced into the check as written: quote a value in them yourself. Every other option is passed as one single-quoted word.

type string

FuzAppBootstrapTokenScript
#

recipe_bootstrap.ts view source

FuzAppBootstrapTokenScript import type {FuzAppBootstrapTokenScript} from '@fuzdev/zap/recipe_bootstrap.js';

The check/run fuz_app_bootstrap_token returns (caller supplies id/name/depends_on).

check

Check command — exit 0 means the script is already applied (skip).

type string

run

The command the planner emits when the check fails.

type string

Depends on
#