# Provisioning a Mac for the fleet Three Macs are provisioned this way as of 2026-09-02: `vuongs-mac-mini` (10.100.79.2), `esh-macbook-air` (10.0.10.83), `esh-mac-studio` (10.0.10.10). None is in `servers/` or `dns/internal.yaml` — they are the operator's personal machines, not PFI-managed fleet hosts, and registering them there would imply otherwise. ## Two stages **Stage 1 — an account I can reach.** Operator-run, because it needs a password I do not have. See "Operator steps" below. **Stage 2 — the harness.** `scripts/provision-mac-dsh.sh [name]`, idempotent, run once per account. scripts/provision-mac-dsh.sh 10.0.10.10 vhpfi esh-mac-studio scripts/provision-mac-dsh.sh --check 10.0.10.10 vhpfi ## Operator steps (stage 1) ```zsh sudo sysadminctl -addUser infra-ops -fullName "PFI infra-ops" \ -shell /bin/zsh -home /Users/infra-ops -password - sudo dseditgroup -o edit -a infra-ops -t user admin sudo mkdir -p /Users/infra-ops/.ssh sudo tee /Users/infra-ops/.ssh/authorized_keys >/dev/null <<'KEY' ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN+1HBwfXrkfTYWdcnWCjLJ6VLAGC87gxH5h5vKaaA3c infra-ops@pfi-fleet KEY sudo chown -R infra-ops:staff /Users/infra-ops/.ssh sudo chmod 700 /Users/infra-ops/.ssh; sudo chmod 600 /Users/infra-ops/.ssh/authorized_keys ``` Then hand over a throwaway password; infra-ops rotates it, vaults it as `/infra-ops-password`, and installs a `visudo`-validated NOPASSWD drop-in. ⚠ **Install the key and prove key-auth on a FRESH connection BEFORE touching the password.** These machines have no out-of-band access — a failed rotation means the operator walks to the machine. ## ⚠ Traps, all of them paid for **An account may not own its own home.** If `/Users/` was created by a `sudo mkdir` before `sysadminctl` ran, sysadminctl adopts the existing directory and leaves it **root-owned**. The account then authenticates, gets a shell, has a correct `$HOME`, and cannot write to it — surfacing as a bare `Permission denied` from `mkdir` long after the account looked healthy. The script detects and fixes this; a hand-run will not. **`sudo -u ` keeps the CALLER's `$HOME`.** Without `-H` and an explicit `HOME=`, `"$HOME/.local"` resolves to the caller's home. On 2026-09-02 that pointed an `rm -rf` at a working install in another account; only filesystem permissions stopped it. The remote half of the script refuses to run unless `$HOME` matches the target account. **A wrong username looks exactly like a wrong password.** sshd answers `Permission denied (publickey,password,keyboard-interactive)` for a bad user, a bad password, AND a user outside `com.apple.access_ssh`. Two of the three Macs produced a false diagnosis this way. **Check `dscl . -list /Users` first** — the Studio's operator account is `vhpfi`, not `lkraven`. **`com.apple.access_ssh` gates SSH when it exists, but admins usually pass anyway** through a nested group. Do not *create* the group if absent: doing so flips SSH from open-to-all to members-only and can lock out the operator. **Password rotation: use `dscl . -passwd`, not `sysadminctl`.** With FileVault on and no Secure Token on the account, `sysadminctl -resetPasswordFor` refuses with *"Operation is not permitted without secure token unlock"*. `dscl` works precisely because there is no token to desync. True on all three Macs. ⚠ **FileVault kills remote access across reboots.** The machine sits at the pre-boot unlock screen with no network until someone unlocks it physically. Nothing unattended should depend on a Mac being reachable after a restart. **macOS has no `adduser`, `useradd`, or `timeout`.** Use `sysadminctl`, and wrap the ssh call locally rather than reaching for a remote `timeout`. ## Harness specifics Node is installed **private to the account** under `~/.local/node`, checksum-verified — deliberately not Homebrew, which owns `/opt/homebrew` and edits PATH. One **device-scoped** gateway key per machine (`-dsh`, scoped to `gen-reasoning`), shared by that machine's accounts and vaulted at `/litellm-dsh-key`; the script verifies the scope (200 on gen-reasoning, 403 on gen) rather than trusting the mint. See `stacks/litellm/README.md` for why `reasoningEffort: high` works at all, and `scripts/provision-mac-dsh.sh` for the three measured limits (`reasoningEffort`, `maxTokens`, `defaultContextWindow`) and the hard-coded model catalog the web GUI reads independently of the default model.