1- # Shared, provider-agnostic helpers for the declarative-service pairings: the
2- # .tf.json label/file helpers and the run-once OpenTofu reconciler unit.
3- #
4- # Provider-specific pieces — the provider-wrapped executor, the .tf.json
5- # provider/resource generation, the token variable name — live in each pairing's
6- # services/<svc>/lib.nix and are injected into the helpers below.
1+ # shared helpers for the pairings: tf-label/file helpers and the run-once
2+ # reconciler unit. provider-specific bits live in services/<svc>/lib.nix.
73{ pkgs } :
84let
95 inherit ( pkgs ) lib ;
106in
117rec {
12- # Sanitize an arbitrary string into a valid Terraform block label
13- # ([A-Za-z_][A-Za-z0-9_-]*). Always prefixed, so the result starts with a
14- # letter regardless of the input.
8+ # turn an arbitrary string into a valid Terraform block label. always
9+ # prefixed so the result starts with a letter.
1510 tfLabel =
1611 prefix : name :
1712 "${ prefix } _"
1813 + lib . stringAsChars ( c : if builtins . match "[A-Za-z0-9_-]" c != null then c else "_" ) name ;
1914
20- # Render a config attrset to a .tf.json store file. Safe by construction: the
21- # config must carry no secrets (anything in the store is world-readable) .
15+ # write the config as a .tf.json file in the nix store. must contain no
16+ # secrets -- the store is world-readable.
2217 tfJsonFile = name : config : pkgs . writeText "${ name } .tf.json" ( builtins . toJSON config ) ;
2318
24- # Build the run-once reconciler systemd service definition .
19+ # build the run-once reconciler systemd service.
2520 #
26- # name unit + generated-config name (e.g. "declarative-forgejo")
27- # tfConfig the provider-specific config attrset to apply
28- # afterUnits units to order/require after (the service's primary unit)
29- # healthUrl URL polled until the service answers, before applying
30- # tokenFile runtime path to the admin token, exposed via LoadCredential
31- # executor OpenTofu wrapped with the pairing's provider (offline mirror)
32- # tokenVar Terraform input variable carrying the token; also the
33- # LoadCredential id, so `TF_VAR_<tokenVar>` is fed from it
34- # credentials extra TF_VAR_<id> -> host file path pairs (per-resource
35- # secrets); each is LoadCredential'd and exported like the token
36- # user/group the base service's user/group the reconciler runs as
37- # stateDir the base service's primary state dir; Terraform state lives in
38- # a `declarative-terraform` subdir of it, co-located with the service
39- # dynamicUser set when the base service runs as systemd DynamicUser (so
40- # the `User=` name only exists per-unit). The reconciler then
41- # runs with `DynamicUser=true` too, so systemd allocates the
42- # same hashed UID as the primary unit, and the work dir is
43- # created via `StateDirectory=` (derived from `stateDir`)
44- # rather than `mkdir`. Requires `stateDir` to live under
45- # `/var/lib`; enforced at eval time.
21+ # name unit + generated-config name (e.g. "declarative-forgejo")
22+ # tfConfig the .tf.json config to apply
23+ # afterUnits units to order/require after (the service's main unit)
24+ # healthUrl url polled until the service answers, before applying
25+ # tokenFile path to the admin token on the host, read via LoadCredential
26+ # executor OpenTofu wrapped with the pairing's provider (offline)
27+ # tokenVar name of the sensitive tf variable carrying the token; also
28+ # the LoadCredential id -- exported as TF_VAR_<tokenVar>
29+ # credentials extra TF_VAR_<id> -> host path pairs for per-resource
30+ # secrets, handled the same way as tokenFile
31+ # user/group the service's user/group; the reconciler runs as them
32+ # stateDir base dir for the reconciler; tfstate lives in a
33+ # `declarative-terraform` subdir of it
34+ # dynamicUser set when the base service uses systemd DynamicUser=true
35+ # (the `User=` name only exists per-unit). the reconciler
36+ # then runs with DynamicUser=true too, so it picks up the
37+ # same hashed UID as the main unit, and systemd creates
38+ # the work dir via StateDirectory= (derived from stateDir).
39+ # requires stateDir to live under /var/lib.
4640 mkReconcileService =
4741 {
4842 name ,
@@ -60,19 +54,17 @@ rec {
6054 } :
6155 let
6256 confFile = tfJsonFile name tfConfig ;
63- # Admin token + any per-resource secret files, each kept out of the store
64- # and exposed to tofu as TF_VAR_<id> via systemd LoadCredential= .
57+ # admin token + per-resource secrets, all read via LoadCredential
58+ # so they never land in the world-readable store .
6559 allCredentials = {
6660 ${ tokenVar } = tokenFile ;
6761 }
6862 // credentials ;
69- # Terraform state is co-located with the base service: a subdir of its
70- # primary state directory, created and owned by the service user.
63+ # tfstate lives in this subdir of stateDir.
7164 workDir = "${ stateDir } /declarative-terraform" ;
72- # Under DynamicUser= the absolute work dir is also expressed as a
73- # relative `StateDirectory=` so systemd creates and owns it. Deriving
74- # both from `stateDir` is the single source of truth: the path the
75- # script `cd`s into and the path the unit declares can never drift.
65+ # under DynamicUser= systemd creates the dir via StateDirectory=
66+ # (relative to /var/lib). derive both from stateDir so the script
67+ # path and the unit declaration cannot drift.
7668 stateDirectoryRelative = lib . removePrefix "/var/lib/" workDir ;
7769 in
7870 assert lib . assertMsg ( ! dynamicUser || lib . hasPrefix "/var/lib/" stateDir )
8274 after = afterUnits ;
8375 requires = afterUnits ;
8476 wantedBy = [ "multi-user.target" ] ;
85- # Re -apply whenever the generated configuration changes.
77+ # re -apply when the generated config changes.
8678 restartTriggers = [ confFile ] ;
8779 path = [
8880 executor
@@ -96,17 +88,16 @@ rec {
9688 serviceConfig = {
9789 Type = "oneshot" ;
9890 RemainAfterExit = true ;
99- # Run as the base service's user so Terraform state can live in (and be
100- # backed up alongside) that service's primary state directory.
91+ # run as the service's own user so tfstate sits next to its data.
10192 User = user ;
10293 Group = group ;
103- # Secrets stay out of the store: read from the credentials dir at runtime.
94+ # secrets stay out of the store; loaded into $CREDENTIALS_DIRECTORY at runtime.
10495 LoadCredential = lib . mapAttrsToList ( id : path : "${ id } :${ path } " ) allCredentials ;
10596 }
10697 // lib . optionalAttrs dynamicUser {
107- # Bases like Keycloak ship no persistent state dir and run as
108- # systemd DynamicUser=true; the ` User=` name is hashed to a stable UID
109- # that's reused across units, and systemd creates/ owns the state dir.
98+ # services like Keycloak use DynamicUser=true and have no
99+ # persistent state dir. systemd hashes the User= name to a
100+ # stable UID that's shared across units and owns the state dir.
110101 DynamicUser = true ;
111102 StateDirectory = stateDirectoryRelative ;
112103 StateDirectoryMode = "0700" ;
@@ -115,23 +106,23 @@ rec {
115106 set -euo pipefail
116107 umask 077
117108
118- # Work in a Terraform state dir under the base service's primary state
119- # directory, created 0700 on first run and owned by the service user.
109+ # work in a subdir of the service's state dir; created 0700 on
110+ # first run, owned by the service user.
120111 mkdir -p ${ lib . escapeShellArg workDir }
121112 cd ${ lib . escapeShellArg workDir }
122113
123- # Refresh the generated config (state persists across runs).
114+ # refresh the generated config (tfstate persists across runs).
124115 install -m 0600 ${ confFile } ./main.tf.json
125116
126- # Gate on the service actually answering before applying.
117+ # wait for the service to actually answer before applying.
127118 for _ in $(seq 1 60); do
128119 if curl -fsS -o /dev/null "${ healthUrl } "; then
129120 break
130121 fi
131122 sleep 2
132123 done
133124
134- # Feed every credential to tofu as TF_VAR_<id>, read from the creds dir .
125+ # pass each credential to tofu as TF_VAR_<id>.
135126 for id in ${ lib . escapeShellArgs ( lib . attrNames allCredentials ) } ; do
136127 export "TF_VAR_$id=$(cat "$CREDENTIALS_DIRECTORY/$id")"
137128 done
0 commit comments