Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 45 additions & 20 deletions full-ai-cluster/nixos/modules/initial-password.nix
Original file line number Diff line number Diff line change
@@ -1,35 +1,60 @@
# full-ai-cluster/nixos/modules/initial-password.nix
#
# Initial password substrate for the `zeta` user on fresh installs.
# Per `.claude/rules/human-audit-and-legal-risk-acceptance-pattern-in-settings.md`
# Shape A: hashedPassword baked into per-host Nix module + operator
# rotates on first login. Composes with the Touch ID + biometric
# substrate (full-ai-cluster/tools/zflash-setup.ts) for the operator's
# Mac side; this is the cluster-node side.
#
# THE INITIAL PASSWORD IS: zeta-change-me
# iter-5.3 (B-0792 follow-on; the maintainer 2026-05-26 "also on
# startup can it ask for me to type a password instead of having a
# default"): the operator-chosen password set at install-time via
# zeta-install.sh's prompt-password step (read -s + mkpasswd → hash
# → /mnt/etc/zeta/initial-hashedpassword). This module reads that
# file via builtins.readFile at NixOS evaluation time + uses it
# for users.users.zeta.hashedPassword.
#
# zeta-install.sh prints this in big letters to the console + writes
# zeta-initial-credentials.txt back to the boot USB before the 10s
# auto-reboot so the operator can read it after pulling the USB.
# Operator UX (one TYPED prompt at install time; can't avoid for
# password since secrets shouldn't transit non-operator surfaces):
#
# Operator MUST rotate on first login:
# zeta-install.sh:
# [iter-5.3] Set initial password for the `zeta` user:
# (will be required for console login; not for SSH
# if iter-4.2 pubkey was injected)
# Password: ********
# Confirm: ********
# [iter-5.3] wrote hash to /mnt/etc/zeta/initial-hashedpassword
#
# passwd zeta
# Operator can still rotate later via `passwd zeta` if they want
# to change it again.
#
# Hash format: sha512crypt ($6$...). Generated via:
# openssl passwd -6 'zeta-change-me'
# BACKWARD-COMPAT FALLBACK: if /etc/zeta/initial-hashedpassword
# does NOT exist (e.g., during nixos-rebuild on an already-installed
# system where the file was never written, OR during `nix flake
# check` in CI where the file path is meaningless), fall back to
# the documented iter-4.x default hash of `zeta-change-me` so the
# module still evaluates + the system still has a known-default
# credential. Operator should rotate immediately in that case.
#
# Per simplest-first (per B-0786 memory): sha512crypt is the
# universally-portable shape; promote to yescrypt or agenix/sops-nix
# when the simple shape demonstrably can't meet a real requirement.
# Iter-4 v1 ships sha512crypt; iter-5+ may promote to a stronger
# secret-management substrate when (a) repo goes public OR
# (b) multi-operator key isolation becomes load-bearing.
# Hash format: sha512crypt ($6$...). zeta-install.sh generates via
# mkpasswd from the nixpkgs `mkpasswd` package.

{ config, pkgs, lib, ... }:

let
hashFile = "/etc/zeta/initial-hashedpassword";
injectedHash =
if builtins.pathExists hashFile
then
let
raw = builtins.readFile hashFile;
trimmed = lib.removeSuffix "\n" raw;
in
if lib.hasPrefix "$6$" trimmed then trimmed else null
else null;
# iter-4 v1 backward-compat fallback hash (= sha512crypt of
# "zeta-change-me"). Used when the operator-chosen hash isn't
# present (e.g., CI eval, nixos-rebuild without prior install).
fallbackHash =
"$6$wMTsqITU4II043Y8$DBR58Hhh.d975YkA40kwYNxQAunevJ9Cu9rYYigi9YjBYVEjlNrs.rk4hu.332sh6GkQuCb7yyLYr7lPTxySD1";
in
{
users.users.zeta.hashedPassword =
"$6$wMTsqITU4II043Y8$DBR58Hhh.d975YkA40kwYNxQAunevJ9Cu9rYYigi9YjBYVEjlNrs.rk4hu.332sh6GkQuCb7yyLYr7lPTxySD1";
if injectedHash != null then injectedHash else fallbackHash;
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@
nixos-install-tools
nix-output-monitor
nvd

# iter-5.3 prompt-password substrate (zeta-install.sh Step 6.55):
# mkpasswd is provided by the `mkpasswd` nixpkgs package; needed
# at install-time to hash the operator-typed password before
# writing to /mnt/etc/zeta/initial-hashedpassword.
mkpasswd
nh
# Declarative disk partitioning — used by the cookie-cutter
# disko-shapes/ modules. Pre-staged on the ISO so installs
Expand Down
68 changes: 68 additions & 0 deletions full-ai-cluster/usb-nixos-installer/zeta-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,74 @@ else
echo "=============================="
fi

# ── Step 6.55: iter-5.3 prompt-for-initial-password (B-0792) ────
#
# Per the maintainer 2026-05-26: "also on startup can it ask for
# me to type a password instead of having a default" — replaces
# the iter-4.x hardcoded `zeta-change-me` default with an
# operator-chosen password set at install time.
#
# Operator types password ONCE on cluster console (read -s; hidden);
# script hashes via mkpasswd ($6$ = sha512crypt); writes hash to
# /mnt/etc/zeta/initial-hashedpassword. The
# nixos/modules/initial-password.nix module reads that file via
# builtins.readFile at NixOS evaluation time + sets
# users.users.zeta.hashedPassword.
#
# Fallback: if operator presses Enter to skip (no password typed),
# the module's BACKWARD-COMPAT fallback hash (= sha512crypt of
# "zeta-change-me") stays in effect so the system still boots
# with a known credential.
#
# Why type-on-console (one exception to typing-avoidance discipline):
# secrets shouldn't transit non-operator surfaces (USB ESP, Aaron's
# Mac keychain, etc.); operator-typed at install time is the
# safest path. This composes with the wifi nmtui exception in
# zeta-first-boot.sh — both are operator-typed-once-on-cluster.
echo
echo "[iter-5.3] ── prompt for initial password (instead of default) ──"
echo "[iter-5.3] Set initial password for the 'zeta' user (used for"
echo "[iter-5.3] console login; SSH uses the iter-4.2-injected pubkey)."
echo "[iter-5.3] Operator can rotate later via 'passwd zeta' on the"
echo "[iter-5.3] installed system. Press Enter to skip + keep the"
echo "[iter-5.3] iter-4.x default ('zeta-change-me')."
echo
INJECTED_PW=""
INJECTED_PW_CONFIRM=""
# -s = silent (hidden); -p = inline prompt
read -r -s -p "[iter-5.3] Password (or Enter to skip): " INJECTED_PW
echo
if [ -n "$INJECTED_PW" ]; then
read -r -s -p "[iter-5.3] Confirm: " INJECTED_PW_CONFIRM
echo
if [ "$INJECTED_PW" != "$INJECTED_PW_CONFIRM" ]; then
echo "[iter-5.3] WARN: passwords don't match; skipping (keeps default)"
INJECTED_PW=""
fi
fi
if [ -n "$INJECTED_PW" ]; then
# mkpasswd from nixpkgs `mkpasswd` package. -m sha-512 selects
# sha512crypt; -s reads password from stdin (avoids exposing it
# in argv via ps).
INJECTED_HASH=$(echo "$INJECTED_PW" | mkpasswd -m sha-512 -s 2>/dev/null || echo "")
unset INJECTED_PW
unset INJECTED_PW_CONFIRM
if [ -n "$INJECTED_HASH" ] && echo "$INJECTED_HASH" | grep -Eq '^\$6\$'; then
sudo mkdir -p /mnt/etc/zeta
echo "$INJECTED_HASH" | sudo tee /mnt/etc/zeta/initial-hashedpassword >/dev/null
sudo chmod 0600 /mnt/etc/zeta/initial-hashedpassword
sudo chown root:root /mnt/etc/zeta/initial-hashedpassword
echo "[iter-5.3] operator-chosen password hash written + chmod 0600"
unset INJECTED_HASH
else
echo "[iter-5.3] WARN: mkpasswd produced invalid hash; falling back to default"
fi
else
echo "[iter-5.3] no password entered; iter-4.x default 'zeta-change-me' stays"
echo "[iter-5.3] in effect (rotate via 'passwd zeta' after first SSH login)"
fi
echo

# ── Step 6.6: iter-5.2 hostname injection (B-0792) ──────────────
#
# Per the maintainer 2026-05-26: "since our different roles are
Expand Down
Loading