Four host glue units fetched a secret from swarm-bao and rendered it with `> path; chmod`: a reader racing the write could see a truncated file, and briefly one at the wrong mode before the chmod landed. glue-matrix-bao-token.nix, glue-queue-agent-credential.nix (both files), swarm-grafana.nix and swarm-otel.nix now write to a same- directory temp file, set its final mode/owner, then `mv -f` it over the target — a shared `atomic_write_secret` helper (nix/host-modules/lib/atomic-write-secret.nix) so the five call sites share one implementation. The first-boot `/root/.claude` migration in nix/agent-modules/user.nix wrote its done-marker unconditionally, so a failed `cp` (disk full, permission error) left the marker behind and no boot ever retried the copy. The marker is now written only when there was nothing to migrate or the copy succeeded; `cp -an`'s no-clobber semantics already make a retry after a partial copy safe. Refs #4723
37 lines
1.5 KiB
Nix
37 lines
1.5 KiB
Nix
# Shared shell step for a systemd oneshot that lands a freshly-fetched
|
|
# secret on disk: never `> path` the live file directly, because a reader
|
|
# racing the write can open it between the truncate and the write, or
|
|
# between the write and a `chmod` that follows it, and see an empty file
|
|
# or one at the wrong mode. `mktemp` always creates its file at 0600
|
|
# regardless of umask, so the temp file is private for its whole life; only
|
|
# the `chmod`/`chown`/`mv -f` sequence below ever makes the target mode and
|
|
# owner visible, and only once the content is already final.
|
|
#
|
|
# Pure function — NOT a NixOS module. Call it from a module's `let`:
|
|
#
|
|
# atomicWriteSecret = import ./lib/atomic-write-secret.nix { };
|
|
# ...
|
|
# script = ''
|
|
# ${atomicWriteSecret}
|
|
# printf '%s\n' "$secret" | atomic_write_secret 0600 "" "$path"
|
|
# printf '%s\n' "$secret" | atomic_write_secret 0400 "grafana:0" "$path"
|
|
# '';
|
|
#
|
|
# Third argument to `atomic_write_secret` is the target path; the second is
|
|
# an owner for `chown` (`user:group` or a bare uid), or "" to leave the
|
|
# mktemp-created root:root ownership as it is. Reads its content from
|
|
# stdin. Requires `coreutils` on the caller's `path`.
|
|
{ }:
|
|
''
|
|
atomic_write_secret() {
|
|
local mode="$1" owner="$2" target="$3" tmp
|
|
tmp="$(mktemp "$(dirname -- "$target")/.$(basename -- "$target").XXXXXX")"
|
|
trap 'rm -f "$tmp"' RETURN
|
|
cat > "$tmp"
|
|
chmod "$mode" "$tmp"
|
|
if [ -n "$owner" ]; then
|
|
chown "$owner" "$tmp"
|
|
fi
|
|
mv -f "$tmp" "$target"
|
|
}
|
|
''
|