# Shared shell steps for a host oneshot that fetches a credential into a # file a service inside a container loads at start (`LoadCredential`, or a # config file expanded once while parsing). Such a service never sees a # value that lands after it started — late or rotated — until it restarts. # # The file's mtime is the record of a change, so write the file only when # `secret_differs` says so. The restart decision is then re-derived from # the file on every run: a run that wrote the file but failed before the # restart leaves a retry that still sees the file newer than the service. # # Pure function — NOT a NixOS module. Call it from a module's `let`: # # refreshConsumer = import ./lib/refresh-consumer.nix { }; # script = '' # ${refreshConsumer} # if secret_differs "$path" "$secret"; then # atomic_write_secret 0400 root:root "$path" "$secret" # fi # refresh_consumer my-machine my.service "$path" # ''; # # Requires `systemd` and `coreutils` on the caller's `path`. { }: '' # True when $1 is missing or holds something other than $2. `$(< path)` # is a bash builtin, so the value never becomes an argument in /proc; # both sides lose their trailing newlines, which is how # `atomic_write_secret` writes and `$(bao …)` reads. secret_differs() { [ ! -f "$1" ] || [ "$(< "$1")" != "$2" ] } # . Nothing while `container@` is not # active (a container that starts later loads the file as it starts), or # when last entered `active` after was last written. # Otherwise a running consumer is restarted and a failed one — it may # have hit its start limit without the credential — is reset and started; # one stopped on purpose stays stopped. `--no-block` because a caller # ordered `Before=` its consumer must not wait on a job that waits on the # caller (see `swarm-services-cert`'s propagation in ../hive-tls.nix). refresh_consumer() { local machine="$1" unit="$2" path="$3" started started_us=0 written_us if ! systemctl is-active --quiet "container@$machine.service"; then return 0 fi # Empty for a unit that has never been active, which `date` would # otherwise read as today's midnight. started="$(systemctl --machine="$machine" show --timestamp=us+utc -p ActiveEnterTimestamp --value "$unit")" if [ -n "$started" ]; then started_us="$(date -u -d "$started" +%s%6N)" fi written_us="$(stat -c %.6Y "$path" | tr -d .)" if [ "$written_us" -le "$started_us" ]; then return 0 fi if systemctl --machine="$machine" is-failed --quiet "$unit"; then echo "$path changed after $unit in $machine last started, and $unit had failed — starting it" systemctl --machine="$machine" reset-failed "$unit" systemctl --machine="$machine" start --no-block "$unit" else echo "$path changed after $unit in $machine last started — restarting it if it runs" systemctl --machine="$machine" try-restart --no-block "$unit" fi } ''