← All guides

Mac mini devbox

Turn a Mac mini into an always-on Agent Grid devbox — one short physical session, then everything remote forever over SSH and Tailscale.

In this guide

Set up a Mac mini once, in person, then never touch it again. After the physical session you drive it entirely over SSH and Tailscale: it boots unattended, logs itself in, launches Agent Grid, and comes back on its own after a power cut or a reboot. This guide covers the one hands-on session and the remote bootstrap that follows.

The two scripts referenced here live in the repo under scripts/devbox/: mac-mini-bootstrap.sh provisions the machine, and devbox-reboot.sh reboots it and waits until it is genuinely ready.

The physical session

Everything in this section happens once, with a keyboard and display attached. The goal is to leave the machine in a state where it boots straight into a live GUI login without a human.

  1. Setup Assistant. Power on and walk through first boot. Skip Migration Assistant — a devbox starts clean. Name the account dev (the scripts and examples assume this user).

  2. Decline FileVault. When Setup Assistant offers disk encryption, turn it off. This is the step people get wrong — see Why FileVault must be off below.

  3. Enable Remote Login (SSH). System Settings → General → Sharing → Remote Login on.

  4. Enable Screen Sharing. Same Sharing pane → Screen Sharing on. You need this once, to sign in to Agent Grid.

  5. Enable auto-login. System Settings → Users & Groups → Automatically log in as dev. This is what gives you a live GUI session after every boot with no one at the keyboard.

  6. Set up key-based SSH from your laptop. Copy your public key over, then confirm a non-interactive connection works with no password prompt (use the machine's local hostname if it differs):

    Code
    ssh-copy-id dev@dev.local
    ssh -o BatchMode=yes dev@dev.local true && echo ok

    devbox-reboot.sh polls with password prompts disabled, so it needs this key to work.

  7. Unplug. Disconnect the keyboard and display. On Apple Silicon you do not need a dummy HDMI dongle to keep a GUI session alive.

From here on the machine is headless. Everything below runs over SSH.

Why FileVault must be off

FileVault is the one setting that quietly breaks an unattended devbox, so it is worth understanding rather than just toggling.

  • It halts every boot at a pre-boot unlock screen. Before macOS is up — before your login session, before Tailscale — FileVault stops at a screen that wants the disk password. On macOS releases before 26 that screen has no networking at all, so an unattended reboot simply hangs there until someone walks over with a keyboard. macOS 26 does let a same-LAN peer send the unlock over SSH, but as the note below explains that still doesn't give you unattended recovery. Either way, a lone box that reboots by itself is stuck.
  • It is mutually exclusive with auto-login. macOS won't auto-log-in an account on an encrypted volume. But auto-login is exactly what gives Agent Grid a live GUI session to run in — the Electron app needs a real logged-in desktop, not just an SSH shell. You can't have both, so for a devbox you choose auto-login.
Unattended boot sequence: the Mac mini powers on with no FileVault unlock screen, the Tailscale daemon makes it reachable before anyone logs in, auto-login gives the dev user a live GUI session, and Agent Grid relaunches through its launch-at-login agent.

Every link in this chain runs without a person at the machine. Leaving FileVault off is what lets the boot reach auto-login on its own.

Diagram by AgentGrid

Run the bootstrap script

mac-mini-bootstrap.sh does the rest of the provisioning: Xcode Command Line Tools, Homebrew, the dev toolchain (git, gh, node, pnpm, tmux, ripgrep, jq), Tailscale, power settings so the box never sleeps and recovers from power loss, and Agent Grid itself with a launch-at-login agent.

Copy it over and run it:

Code
scp scripts/devbox/mac-mini-bootstrap.sh dev@dev.local:~/
ssh -t dev@dev.local 'bash ~/mac-mini-bootstrap.sh'

The -t matters — the script needs a TTY to prompt for your password once, up front. After that it keeps sudo alive for the duration so it never stops to ask again.

The script is idempotent and safely re-runs: it skips the Command Line Tools, Homebrew, the Tailscale service and Agent Grid when they are already in place, and Homebrew leaves already-installed dev tools alone. If a step fails (say, a network blip during the Agent Grid download), fix the cause and run it again. An Agent Grid upgrade unpacks and checks the new build before swapping it in, so a failed download or unpack leaves the existing install in place. If an upgrade is interrupted mid-swap, the next run first restores the previous build, kept as Agent Grid.previous.app, and only discards that backup once the new build is in place.

Authenticate Tailscale

The bootstrap installs Tailscale but does not log it in — that is a one-time interactive step. Finish it over SSH:

Code
sudo /opt/homebrew/bin/tailscale up --ssh

Use the full path: sudo drops Homebrew from PATH, and the daemon is owned by root. This prints a URL; open it on your laptop to authorize the machine on your tailnet.

The script deliberately installs the daemon variant of Tailscale, registered as a system LaunchDaemon via sudo brew services, not the menu-bar app. The bootstrap stops with a warning if that registration does not take. The daemon runs without a GUI session, so Tailscale is up the moment the machine boots — before and independent of the auto-login desktop. That is what keeps the box reachable even if the GUI session ever drops.

Sign in to Agent Grid

This is currently the only step that needs a human at a GUI. Connect over Screen Sharing:

Code
open vnc://dev@dev.local

Sign in to Agent Grid in the desktop session, then disconnect. If the bootstrap warned that it could not load the launch-at-login agent, make sure dev is signed in at the console through auto-login and re-run it. The launch-at-login agent the bootstrap registered means Agent Grid relaunches on every boot, and once signed in it stays signed in.

From your laptop's canvas, connect to the devbox and you're done.

Verify with devbox-reboot.sh

To prove the machine truly recovers unattended, reboot it remotely and watch it come back:

Code
./scripts/devbox/devbox-reboot.sh

Or, to check readiness without rebooting:

Code
./scripts/devbox/devbox-reboot.sh --wait-only

Point it at a different host with DEVBOX=dev@100.125.183.106 ./scripts/devbox/devbox-reboot.sh, and adjust the wait with TIMEOUT=600.

Ready means all of the following are true at once:

  • sshd is answering — the machine is up and accepting SSH.
  • the boot epoch changed (on a real reboot) — kern.boottime moved, which distinguishes an actual restart from reconnecting before the box went down.
  • Tailscale reports Running — tailscale status shows the backend connected to the tailnet, not just an address left over from an earlier session.
  • a live console GUI session exists — the dev user owns /dev/console with a running Dock, i.e. auto-login completed and there is a real desktop for Agent Grid.

When all four hold, the script prints READY with the elapsed time and the before/after boot epochs.

Troubleshooting

systemsetup steps warn about permissions. If the power-configuration step reports a failure, sshd needs Full Disk Access. Grant it in System Settings → Privacy & Security → Full Disk Access, then re-run the bootstrap. The pmset power settings still apply; only the systemsetup calls need this.

No dummy HDMI dongle needed. On Apple Silicon the GUI session stays alive with no display attached. You do not need a headless-display adapter.

Limitations

Today a machine holds one Agent Grid identity, so this is a single-user devbox for now. One person signs in over Screen Sharing and drives it. Multi-identity per machine is not supported yet.

  • Shared projects — pair the devbox with your laptop and open the same project on both, in live two-way sync.
  • Terminals — the PTY model behind remote terminal panes.