fx-activate is the build-time tool that turns a config.dhall into a bootable generation. It evaluates the config (dhall-c), loads the package set, computes the dependency closure, verifies every closure package is built, and requires dhake, fx-init, fxctl and fx-activate in the closure.
1. fx_packageset_load + fx_config_load(config.dhall) 2. fx_store_open 3. compute_paths (closure over config.packages) 4. verify every closure package is BUILT (store fact + stat dir) 5. resolve each service argv[0] against its pkg's store path + target 6. render the generation in memory (Dhakefile + etc/* + bin symlinks) 7. gen hash = sha256_hex over a canonical serialization 8. write + rename to <root>/<genhash>-system-generation (adopt if exists) 9. declare + txn_add_fact the generation facts; dl_publish_snapshot 10. print "activated <genhash> as version <v>; buildfile <path>"
The emitted Dhakefile is a small, validated template. /etc files are copied from the generation dir with Copy{from=GEN/etc/<f>} and always followed by Chmod (umask independence). Every /bin symlink is preceded by Rm<Plain> — dhake's bare symlink(2) fails on EEXIST. All targets are phony (always re-asserted).
The generation hash is sha256 over a canonical serialization: a fxgen-v1 magic, the hostname, the sorted /etc files, the sorted services (with argv, pkg, on, restart, backoff, probe), and the sorted closure store paths. The same inputs always produce the same hash, so re-activation of an identical config adopts the existing generation dir instead of rebuilding it.
Store paths are recorded store-relative (<hash>-<name>, not <store_root>/<hash>-<name>) so the genhash is independent of the store root. A generation activated against a host temp dir boots identically after the store is relocated to /fx/store in the chroot.
fx-activate declares the M4 init relations and writes one self-consistent set of facts per activation — clearing the previous activation's facts first, so a re-activation boots exactly this generation's service set, not the union of all past ones. boot_grace(ms) is persisted as a raw u32 so fx-init (which cannot read Dhall) honors the per-activation grace timeout from config.