For operators whose AI already does real work, on a laptop they keep closing.
Your AI Operating System, On A Machine That Never Sleeps
A Mac Mini in the corner of a room is the cheapest always-on server you can buy, and it runs the same tools you already use. This guide is the full build: the settings that decide whether it survives a power cut, the private network, the scheduled routines, the autonomous agents on top, and the five failures that will take it down before you notice.
Outcome
Never sleeps
Runs at 05:40 whether you are awake or not
Outcome
Reachable anywhere
Private mesh, no open ports
Outcome
Two layers
Scheduled routines, plus agents on top
Foundation 01
One Machine Runs, One Machine Works
The split that removes most sync problems before they exist, and the reason a Mac beats a rented server for this job.
One sentence decides almost every question that follows. The Mini owns everything that must run when you are not at a desk. The laptop owns everything you do with your hands. No workload lives in both places. That rule removes most sync problems before they exist, because a file that only ever has one owner cannot have two versions.
Owns Everything scheduled
The Mini
/Scheduled routines, all of them
/Autonomous agents and their state
/The canonical copy of your working tree
/Dashboards, databases, long-running jobs
/Anything with a clock attached to it
Owns Everything hands-on
The laptop
/Client work and active development
/Content capture, recording, editing
/Interactive AI sessions you are watching
/Local dev databases and containers
/Anything you would miss if it was elsewhere
Why a second machine and not a cloud server
Because the desktop apps only run on a desktop.
Scheduled AI routines, the browser sessions some tools still need, the password manager, the signed-in accounts: all of it assumes a logged-in graphical session. A Linux box in a data centre gives you none of that, and you end up rebuilding half your stack in a shape you did not choose. A Mini is a normal Mac that happens to stay on.
It also collapses the bill. One machine at home with 24GB of memory replaced a rented server for us, plus whatever we were paying to keep the gaps covered, and there is one less box to patch.
The split is a decision, not a drift. Write it down before you build anything. Every time you are unsure where something belongs, ask whether it has to work at 04:00 with the lid shut. If the answer is yes, it is not allowed to live on the laptop, no matter how convenient that is today.
Foundation 02
What To Buy, And What Not To
The base machine is enough. Everything worth spending on keeps it awake, keeps it connected, or gets it back.
The base Mini is enough machine. Everything worth spending on is about keeping it awake, keeping it connected, and being able to recover it.
24GB
Memory, not cores
Agent sessions are memory-hungry and they stack. A dozen of them idling will eat 8GB before anything real happens. Cores decide how fast one job finishes; memory decides how many jobs can exist at once, and this box exists to run many.
Not optional
Ethernet cable
Recent macOS does not reliably join Wi-Fi before login, and idle Wi-Fi drops are a known Mini complaint. A headless machine that loses the network at 03:00 is a machine that missed the morning.
1TB is plenty
External SSD
The moment this box becomes the only home of your working tree, it needs its own backup disk. Time Machine on a cheap SSD is the layer that git cannot give you, because it also covers live databases.
Optional, about $15
HDMI dummy plug
Apple Silicon creates its own virtual display when headless, but it defaults to fuzzy 1080p over screen sharing. The plug gets you a crisp 4K desktop for the times you have to click something by hand.
Two setup choices you cannot undo cheaply
Use the same account name as your laptop.
Every absolute path baked into a config file, a scheduled job, a service definition or a project instruction starts with your home folder. Match the short username and all of it keeps working when you copy things across. Mismatch it and you spend a weekend rewriting paths for no benefit at all.
Skip Migration Assistant. Build it clean.
The point of this exercise is a lean server and a lean laptop. If you clone the laptop onto it you inherit every browser, updater and background helper you have collected, and you will never be sure what is running. Install a short list on purpose.
Foundation 03
Boot It Like A Server, Not A Mac
Four settings decide whether it recovers from a power cut on its own. One of them is greyed out until you fix something nobody tells you about.
A Mac out of the box is built to sleep, lock and update itself. A server must do none of those things. Four settings separate a machine that recovers from a power cut on its own from one that quietly stops being useful while you are away.
01. Disk encryption off
An encrypted disk stops every reboot at a pre-boot unlock screen. Nobody is standing there to type the password, so the machine sits dark and nothing runs until you notice. Single user, locked house: the trade is fine. If the box lives somewhere public, keep encryption and accept that it needs a human after every power cut.
02. Automatic login on
Services that live in the user session only start once someone is logged in. Without auto-login a reboot lands at the login window with no routines, no agents and no dashboards. This is the single setting the whole build rests on.
03. Never sleep, restart after power loss
Sleep is the default state of a consumer Mac and it will silently swallow your 05:40 job. Set it at the power-management layer rather than trusting an app switch or a keep-awake command.
04. No screensaver, no lock, no hot corners
A locked screen blocks the graphical automation some tools still need, and a hot corner set to display sleep will do it to you by accident.
power and login
# never sleep, wake on network, come back after a power cut
sudo pmset -a sleep 0 displaysleep 0 disksleep 0 \
powernap 0 womp 1 tcpkeepalive 1 autorestart 1
# verify it stuck
pmset -g
# automatic login (prompts for the account password)
sudo sysadminctl -autologin set -userName <your-username> -password -
sysadminctl -autologin status # must name your account
# screensaver and lock off
defaults -currentHost write com.apple.screensaver idleTime 0
sysadminctl -screenLock off -password -
The gotcha that stops most people. The automatic login option is greyed out when the account signs in with an Apple ID password.
Auto-login needs a separate local password on the account. Set one and the option frees up. This is expected behaviour, not a fault, and it costs an hour of confusion the first time because nothing tells you why the dropdown is dead.
Then take software updates off automatic.
An unattended OS update reboots your server at a time of Apple's choosing and can reset the settings above on the way through. Patch monthly, by hand, and re-check the power settings afterwards.
updates
# stop the machine rebooting itself on Apple's schedule
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate \
AutomaticallyInstallMacOSUpdates -bool false
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate \
AutomaticDownload -bool false
# then patch monthly, in your window, on purpose
softwareupdate -l
sudo softwareupdate -i -R <label>
The test that proves it, before you build anything on top
Pull the plug. Wait ten seconds. Plug it back in.
With no keyboard attached, the machine must come back to a logged-in desktop on its own. If it powers on and stops at the login window, auto-login did not register. Fix that now, because every later phase assumes a session exists. Expect the search indexer to peg the processor for twenty minutes after early boots. That part is normal.
Enjoying the guide?
Enter your details to unlock the remaining insights. We'll also send you the complete guide as a reference.
The Build
The Private Mesh
Reach the box from anywhere without opening a single port to the internet. Build this before you copy anything, so the copy runs over it.
Build the network before you copy a single file, because the copy itself should run over it. Turn on Remote Login and Screen Sharing on the Mini first. Screen sharing is not a luxury: the first time a permission dialog appears on a headless box, it blocks a job invisibly until somebody clicks it.
Owns Reach
A private mesh
Install Tailscale on the Mini, the laptop and your phone, sign them all into the same account, and every device can reach every other one by name from anywhere. It is the same encryption as a hand-rolled VPN with the key management and the network traversal already done.
Owns Access
Keys, not passwords
One key on disk for scripted work, so bulk transfers run without a prompt on every file. Optionally a second key held in your password manager for hands-on sessions, unlocked by fingerprint and never written to disk.
Owns Visibility
Dashboards on your phone
Anything the Mini serves on its own network interface becomes reachable from the couch or a client site, with nothing exposed to the public internet. This is what makes an always-on box feel owned rather than hidden.
ssh setup
# authorise your key on the Mini (asks for its password once)
ssh-copy-id -i ~/.ssh/id_ed25519.pub mini
# should now work with no password
ssh mini 'hostname; sw_vers -productVersion'
# once keys work, turn passwords off entirely
sudo tee /etc/ssh/sshd_config.d/100-hardening.conf <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
AllowUsers <your-username>
EOF
sudo launchctl kickstart -k system/com.openssh.sshd
Four rules for this layer
Never forward port 22 on your router. The mesh already reaches the machine from anywhere. An open SSH port on a home connection buys you nothing except a login attempt every few seconds for the rest of its life.
Use the standalone app, not the store build. The store version is sandboxed differently and the command line tool is harder to reach. Add it to login items so it comes back after a reboot.
If the machine name will not resolve, use its mesh address. Name resolution through the system resolver is the flakiest part of this layer and it is not worth a morning. The address is stable and works from every device on the mesh.
Bind services to all interfaces, not just loopback. A dashboard listening only on localhost is invisible to your phone. Bound to the machine, it is reachable across the mesh and still not exposed to the internet.
One public door, at most. If something genuinely has to be reachable by the outside world, a webhook receiver or an unsubscribe link, put a single tunnel in front of that one service and leave everything else on the private mesh. The moment you have two public doors you have started running infrastructure, and this box was meant to save you from that.
The Build
The Toolchain, And The Version Traps
A short list installed on purpose. Two pinned versions that will otherwise cost you a weekend.
Install a short list on purpose. Every extra package on a server is another thing that updates itself at a bad moment. Pull more from your laptop only when something actually fails without it.
on the Mini, in Terminal
# 1. run this ON the Mini, in Terminal. It needs a password,
# a keypress, and it installs Apple's command line tools (that is
# where git comes from). Everything after it can run over SSH.
/bin/bash -c "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. put brew on PATH (Apple Silicon installs outside the default PATH)
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# 3. proof the chain works
brew --version && git --version
over SSH, from the laptop
# the short list, not your laptop's whole Brewfile
brew install node@24 gh uv rsync tmux ffmpeg
# pin the runtime, do not take "latest"
brew unlink node 2>/dev/null
brew link --overwrite --force node@24
# match the package manager version your projects build with
npm install -g pnpm@10.28.2
# the AI layer
curl -fsSL https://claude.ai/install.sh | bash
claude # completes the browser login, then /status to confirm
Pin your versions. This is the biggest time sink in the whole build.
The runtime. A plain install grabs the current release, which breaks native modules that compile against the long-term support line. Native database bindings are the usual casualty, and the error you get points at anything except the version.
The package manager. A newer major version can hard-fail on build scripts your projects have always used. Match what your laptop builds with, exactly. A mismatched package manager cost us more hours than every other step of the migration put together.
The rest of the install list
The desktop AI app, signed in, in login items. Scheduled routines only fire while the app is open, so it has to relaunch after every reboot. Turn on its keep-computer-awake option as a second belt to the power settings.
A browser, signed in, in login items. Most automation should run through APIs, but a few things still need a real logged-in browser session. Set those cookies up once, here.
Your password manager. You will want it during the re-authentication pass, when every connected service asks for a fresh login on this new machine.
The other agent CLIs, if you use them. A second and third model on the same box costs nothing while idle and gives you a reviewer that does not share the first one's blind spots. Sign each in once, when you are ready.
Credentials do not travel. Sign in fresh on each machine.
Logins live in the system keychain and copying them across is neither supported nor a good idea. Signing in on both machines is the correct flow. Note that these logins expire, and an expired login on a headless server stalls every scheduled job at once with no obvious symptom, so a weekly status glance earns its place on the care list.
The Layers
Primary: Routines That Run Without You
A saved prompt with a clock on it. Ten of them turn an assistant you have to open into a team that has already done your first two hours.
A scheduled routine is a saved prompt with a clock on it. It opens a session, runs a tested process against your files and connected tools, writes its output somewhere you will see it, and exits. Ten of them turn an assistant you have to open into a team that has already done the first two hours of your day.
The one rule: a routine is never enabled on two machines at once.
Two live copies both message your team, both write to your database, both post the same thing. Worse, quitting the app is not an off switch: the scheduler fires one catch-up run per missed slot when it reopens, so a laptop you shut on Friday will happily replay a week of jobs on Monday. Turn each task off individually on the old machine, then turn it on at the new one. Off first, on second, never both.
Then decide what deserves a model at all.
Scheduled AI routine
Needs a model in the loop
Triage an inbox and decide what matters. Draft a follow-up in your voice. Read a transcript and pull out the decisions. Classify a pile of content. Anything where the right answer changes with the input and a script would have to guess.
Plain scheduler
Same steps every time
Pull an API, write rows, rebuild an index, back up a database, post a queued item. If you can write it as a script that either works or fails loudly, it does not need a language model and it should not be paying for one.
Why the split matters more than it sounds
Every AI routine spawns a session process. Sixty routines firing through the day is sixty processes, each holding memory, and mechanical jobs are usually the most frequent ones. This quietly took our ten-core machine to a load average of 73 before we connected the dots.
Move the deterministic jobs to a plain scheduler and they run as short-lived scripts with no session at all. Same output, a fraction of the memory, and the failures are ordinary script failures you can read in a log.
Shape the day so the chain runs in order.
Early morningData pulls and index rebuilds, so everything downstream reads fresh state.
Before you startBriefs and prep. Inbox triage, meeting context, the day's priorities.
Through the dayWatchers on a short cycle. Mentions, replies, queue drains, approvals.
End of dayRoll-ups. What moved, what is stuck, what needs you tomorrow.
Late nightBackups and heavy jobs, when nobody is waiting on the machine.
Order is the whole trick. A brief that runs before the sync it depends on is a brief built on yesterday, and it will look completely normal.
Supervise the first morning in person.
First runs raise permission prompts: per-job allowances, local network access, automation between apps. Screen share in with coffee and watch the chain go. Every dialog you approve is approved forever. Every one you miss blocks a job silently, with no error anywhere.
The Layers
Secondary: A Standing Agent And A Local Brain
Routines cover the clock. They do not cover the thought you have while walking the dog. That needs something always up, with a locked toolset.
Routines cover the clock. They do not cover the moment you think of something while walking the dog. That is the second layer: a standing agent that lives on the same box, holds its own memory, and answers a message on your phone.
Owns Continuity
An autonomous agent
A routine runs, answers and forgets. An agent stays up, keeps a memory of you across sessions, and is reachable from a chat app on your phone. Hermes Agent from Nous Research is the open-source one worth starting with: self-hosted, model-agnostic, with skills, cron and tool connections built in.
Owns Independence
A local model
Install Ollama and pull an 8B open-weight model. It is a few gigabytes and it runs on the unified memory you already bought. It is not as good as a frontier model and it does not need to be. It is what answers when the API is down, the limit is spent, or the data should not leave the room.
Owns Safety
A locked toolset
This thing runs unattended on the machine that holds everything. Decide what it can touch before it is live, not after. Most useful agents need far less permission than they are given by default.
How the two layers fit together
Same box, separate everything else. Separate state directory, separate tokens, separate background service. If the agent falls over it must not take a single routine with it, and the way you guarantee that is by never letting them share anything but the hardware.
Pin the version. Agent frameworks move fast. Install a tagged release rather than the main branch, and upgrade on purpose, after reading what changed. An unattended agent that updates itself overnight is a support ticket waiting for a morning.
Point it at the subscription you already pay for. These frameworks route to whichever provider you configure, so the agent can use the same account as everything else on the box, with the local model underneath as the fallback.
local fallback
# local fallback brain, about 5GB, runs on Apple Silicon
brew install ollama
brew services start ollama
ollama pull qwen3:8b
ollama run qwen3:8b # prove it answers, then wire it in as the fallback
01. Give it the smallest toolset that does the job
A coach that reads your knowledge base and gives advice needs files, skills and web search. It does not need shell access, a browser, or the ability to send messages to anyone but you. Switch those off explicitly. An agent with a shell on your always-on box is a very different risk from an agent that can only read.
02. Allowlist the chat channel to yourself
A messaging bot with an open token will happily talk to whoever finds it. Restrict it to your own account on day one.
03. Mount your knowledge read-only, write through an outbox
Give it a mirror of your notes and wikis that it cannot edit. When it learns something worth keeping, have it write a candidate file to an outbox folder that you promote by hand. The canonical source is never writable by the agent.
04. Scrub what you feed it
If you generate a context bundle from your business files, strip secrets, emails, phone numbers and addresses on the way in, and make the generator abort on a hit rather than warn. It is a single script and it is the difference between a useful agent and a leak with a personality.
The fallback lies with total confidence.
An overload error on our main provider dropped a single turn to the local 8B model. It invented a source link and made up terminology, in the same voice as every good answer before it. We nearly diagnosed it as a problem with the feature being discussed, because nothing on screen said the brain had changed.
Two fixes, both cheap. Raise the retry count so a brief outage does not fall through at all. Then put a fallback clause in the agent's own instructions telling it to say plainly that it is running local, keep answers short, and cite nothing. Watch for whatever status marker your framework shows on a provider switch, and treat that turn as unverified.
Operations
Making It Stay Up
Nothing important lives in a terminal window you left open. Plus the file-access wall that will cost you an afternoon.
Nothing important on this machine should live in a terminal window you left open. Every long-running piece becomes a launch agent: it starts when the session starts, restarts when it crashes, and writes its output to a log you can read later.
# load it into the running session
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.you.dashboard.plist
# a live PID and exit status 0 is what you want
launchctl list | grep com.you
# after editing the script or the plist
launchctl kickstart -k gui/$(id -u)/com.you.dashboard
Why the system scheduler and not a process manager
Node process managers are the habit most people bring with them, and they cause two specific problems here. Their daemon hangs when it is first spawned over SSH, which is exactly how you will be setting this box up. And they inherit the same file-access limits as anything else launched outside your login session, so they hit the wall below.
Worse, running both is actively harmful. Two process managers respawning the same server means a port fight and a crash loop that reads like an application bug. Pick one. On a Mac, that is the system scheduler.
The file-access wall that will cost you an afternoon.
macOS protects Documents, Desktop and Downloads. A background service gets permission denied reading files there, even though your SSH session reads them fine, and the error looks like a broken path rather than a policy. The fix needs no permission grant and no clicking: move the real storage somewhere unprotected and leave a symlink behind.
the fix
# move the real storage out of the protected folder, symlink it back
mv ~/Documents/Projects ~/Projects
ln -sfn ~/Projects ~/Documents/Projects
Every path baked into a config still resolves through the link, and the service now reads the real files from an unprotected location. Verified the hard way: a background process can read through a symlink to an unprotected path, but not the protected folder directly.
Something has to own the desktop app between restarts
Scheduled AI routines only fire while the app is open, and the app does not always come back on its own. An in-app updater will wait for sessions to go idle, quit to apply the update, and simply not relaunch. The machine then sits there with no routines at all until somebody opens it by hand, and nothing anywhere raises an alert.
Run a watchdog on a sixty-second poll that relaunches the app if it is missing. Do not use the scheduler's own keep-alive for this: it fights your nightly restart job and relaunches the app mid-quit. Have the restart job hold a lock file while it runs and have the watchdog respect it, with the lock ignored once it is more than thirty minutes old so a crashed restart cannot silently disable the whole thing.
Then restart the whole machine once a week.
Long-lived apps leak counters and handles that only a real restart clears. Pick a quiet slot with clearance before the first job of the day, and do not stack it on the same night as your app-restart job.
weekly reboot
# a weekly whole-machine restart, Sunday 03:00
sudo pmset repeat restart U 03:00:00
pmset -g sched # verify it stuck
This one needs administrator rights, which means it does not live in a file you can copy. If you ever rebuild the machine it is a fresh manual step, and nothing will warn you that it is missing. Write it on the rebuild list now.
Operations
Five Failures That Will Bite You
None of them announce themselves. Every one ran for weeks looking perfectly healthy.
None of these announce themselves. Each one runs for weeks looking healthy, and every one of them was found only after something visible broke. Build the checks in now and you skip the fortnight of confusion.
01. Sessions that never exit
How it shows up
The machine gets slower over days, then a dashboard stops answering. Memory is gone and the load average is absurd, but no single job looks guilty.
Every scheduled AI run spawns a session process. It finishes its work and the process stays alive forever. Seven to ten leak an hour, so about a hundred and fifty a day, at roughly 140MB each.
Make every routine close its own session on exit, as the last step of the run. Then add a reaper on an hourly cycle as a backstop, targeting only sessions that carry the flag headless runs always have and interactive ones never do. That is a precise test. An idle-CPU guess is not, because leaked sessions still tick over at one or two percent.
02. The process finder that cannot see the processes
How it shows up
A cleanup script reports success and nothing is actually cleaned. Or worse, it kills the wrong thing.
The usual process-matching tool returns nothing for the desktop app under any pattern, and undercounts session processes by exactly one: the session running the check. A reaper built on it will happily kill the session doing the reaping.
List processes properly and match on an exact command path. Prefix-match the argument list rather than searching anywhere inside it, because helper wrappers repeat the same path later in their own arguments and a plain substring search double-counts.
03. The log that said success for months
How it shows up
A nightly restart job whose log reads clean every single night, while the thing it restarts has not restarted once.
Its process lookup matched nothing, so the quit and the force-kill were both silent no-ops. The one line that did work was the cleanup, and that is what made the log look healthy.
Never let a script report success from the fact that it reached the end. Make it check the outcome: capture the process id before and after and fail loudly if it did not change. A job that cannot report failure is not monitoring, it is decoration.
04. Live databases inside a file sync
How it shows up
A database that was fine yesterday is corrupt, and there is no single moment you can point at.
File sync tools copy a database file mid-write, along with its journal, and the two halves arrive out of step. The same applies to internal repository files. This is documented behaviour, not bad luck.
Exclude every database file and its journals from any sync, and exclude repository internals too. One machine owns live data. If the other machine needs to read it, expose it through an interface rather than copying the file.
05. Two one-way syncs pointed at each other
How it shows up
Work you definitely did is missing, with nothing in any log to explain it.
Neither direction knows the other side changed. For any file both machines touched between runs, one version silently overwrites the other. There is no merge step and no record.
Use a real two-way sync that detects the double edit and keeps both copies as a conflict file, plus versioning so a propagated delete is recoverable. Then carve out the things above. The reconcile is safe because it is a union: everything from both sides survives, and only genuine double edits become conflicts.
matching processes properly
# match the app on an exact command path
ps -axo pid=,args= | awk -v b="/Applications/YourApp.app/Contents/MacOS/YourApp" \
'$2==b {print $1}'
# match session processes on a command PREFIX, not a substring
ps -axo pid=,args= | awk -v b="$HOME/Library/Application Support/YourApp/agent" \
'{pid=$1; line=$0; sub(/^[ ]*[0-9]+[ ]+/,"",line); if (index(line,b)==1) print pid}'
The big visible number is usually not the problem.
We blamed a sidebar full of hundreds of old session records for the memory, then for the cost, then for jobs being skipped. It was none of them. A finished session record is a file on disk, not a live context: no memory, no tokens, and the scheduler only counts sessions that are actually running. Before you act on a number that looks alarming, work out what it would have to be doing to cause the symptom.
Operations
The Box Is Now A Single Point Of Failure
The simplicity that removed your sync problems also removed your redundancy. Four layers, none of them expensive.
The moment this build works, you have concentrated your entire operation onto one machine sitting on a shelf. That is the trade: the simplicity that removes your sync problems is the same simplicity that removes your redundancy. Four layers, none of them expensive.
Everything, versioned
Time Machine
Plug the SSD in, point Time Machine at it, hourly. This is the only layer that covers live databases and the piles of generated files no repository tracks. It is also the one that turns a bad day into a restore.
Code and history, offsite
Private remotes
Any repository without a remote is one disk failure from gone. Give each one a private remote and push. This is your offsite copy and your history in one move.
The databases specifically
A nightly data dump
A scheduled job that exports your live databases to plain files, which then ride along in both layers above. Databases are the thing you notice last and miss most.
The cheap layer
Anything rebuildable
Search indexes and vector stores usually rebuild from source in one command. Know which of your pieces are in this category, because they need no backup at all, and confusing them with the rest wastes effort.
repositories with no remote
# find repositories with no remote, before you need them
cd ~/Projects
for d in */ */*/; do
[ -d "$d/.git" ] || continue
git -C "$d" remote -v | grep -q . || echo "NO REMOTE: $d"
done
# give one a private home and push it
cd ~/Projects/<repo> && gh repo create <name> --private --source . --push
Two honest warnings, both ours.
Our machine ran for weeks as the single home of everything with no backup at all, because the backup step kept losing to more interesting work. Buy the disk at the same time as the Mini and set it up on day one, before the box holds anything you would miss.
Then a database backup job ran cleanly for two months while backing up the wrong database, a retired one, and nobody caught it until an unrelated migration. A backup you have never restored from is a hypothesis. Restore one file from each layer, once, and then you know.
The care list, and it is genuinely this short
Weekly, two minutes. Check your AI logins are not near expiry, because an expired one stalls every routine at once. Check your services are all showing a live process. Skim for sync conflict files and clear them.
Monthly, fifteen minutes. Install OS updates in your own window, then re-check the power settings and the automatic-update switches, because major upgrades reset them. Confirm the backup history looks continuous.
Once, after the first week. Only when seven days have passed with no missed jobs and no duplicate output should you clean up the old machine. Until then, the disabled copies on the laptop are your rollback and it takes thirty minutes to use them.
Walkthrough
Hand The Build To Claude
Three copy-paste prompts: build the server, move your scheduled jobs across, and audit what is still running on the old machine.
None of this needs to be typed by hand. Open a session on the Mini and hand it the build. The prompt below is written to make it verify each step and stop between phases, which is the difference between a machine you understand and a machine that merely appears to work.
The always-on server build prompt
You are helping me turn a Mac Mini into the always-on server for my AI operating system. I am running you on the Mini itself, over SSH from my laptop.
Work through this in phases. After each phase, stop and show me a short table of what you changed and how you verified it. Do not move to the next phase until I say go.
## Rules
- Verify, never assume. After every change, run the command that proves it took effect and show me the output. If a check fails, say FAILED plainly. Never report success because you reached the end of a step.
- Ask before anything destructive: deleting files, changing shell config, or touching anything under /etc.
- Anything needing administrator rights, hand to me as a command to paste, with one sentence on what it does.
- Pin versions. Never install "latest" for a runtime or a package manager.
- Tell me plainly when a step must be done in the graphical interface rather than the terminal.
## Context you should gather first
1. macOS version, chip, total memory, free disk.
2. Whether disk encryption is on.
3. Whether automatic login is configured, and for which account.
4. Current power management settings.
5. What is already installed: package manager, runtime versions, existing background services.
Report that back as a table before changing anything.
## Phase 1: make it behave like a server
- Confirm disk encryption is off, or tell me what it will cost me if I keep it on.
- Configure automatic login for my account. If the option is unavailable, diagnose why and tell me the fix.
- Set power management so it never sleeps, wakes for network access, and restarts automatically after a power failure.
- Turn off the screensaver, the screen lock, and any hot corner that sleeps the display.
- Turn off automatic OS updates and downloads, and write me a monthly manual update procedure.
- Then tell me to pull the power cable and confirm it returns to a logged-in desktop by itself.
## Phase 2: the toolchain
- Install the package manager if missing, and put it on PATH in my shell profile.
- Install: the long-term-support runtime line, a version control CLI, a terminal multiplexer, a file sync utility, and media tools.
- Pin the runtime to the long-term-support major version, not current. Explain to me why in one line.
- Pin the package manager to the same version my projects already build with. Ask me what that is.
- Verify every binary resolves to the expected path and print the versions as a table.
## Phase 3: services that survive a reboot
For each long-running thing I name:
- Write a start script under ~/AI/scripts/ with the full environment it needs, since a background service inherits almost nothing.
- Write a launch agent plist with run-at-load and keep-alive on, logging to ~/Library/Logs/.
- Load it, then confirm it shows a live process id and a clean exit status.
- Bind anything that serves a page to all interfaces, not only loopback, so I can reach it from my other devices. Confirm nothing is exposed to the public internet.
Before any of this, check whether my working files live in a protected folder (Documents, Desktop, Downloads). If they do, move the real storage somewhere unprotected and leave a symlink behind, then explain what would have broken if we had not.
## Phase 4: keep the AI layer alive
- Write a watchdog that polls every sixty seconds and relaunches the desktop AI app if it is not running. Use an exact command-path match, not a fuzzy process search, and explain why that matters.
- Write a nightly restart job that quits the app politely, waits, escalates if needed, relaunches it, and verifies the process id actually changed. It must report FAILED when the id is unchanged.
- Coordinate the two with a lock file, and ignore a lock older than thirty minutes.
- Give me the administrator command for a weekly whole-machine restart, in a quiet slot with clearance before my first scheduled job.
## Phase 5: prove it
Write me a single health-check script that reports, in one screen: uptime, memory free, every one of my launch agents with its status, the count of running AI session processes, whether the backup disk is attached and current, and anything listening on a network port. Then run it and walk me through the output.
Start with the context gathering. Do not change anything yet.
Two smaller ones you will want on the day.
The first moves your scheduled jobs across without a silent mismatch, which matters because the registry is the one thing you cannot easily reconstruct by hand. Export it from the old machine first, and keep the old copies switched off but present as your rollback.
migrate-routines.md
Recreate my scheduled tasks on this machine.
Read the registry I exported from my old machine at ~/routines-registry.json,
and the task specifications in ~/.claude/scheduled-tasks/.
For each task: same name, same schedule, same model, same working folder.
Enable ONLY the tasks marked enabled in the registry. Leave the rest
registered but switched off.
Work in batches of ten. After each batch, show me a table of name, schedule,
model and enabled state so I can check it against the registry before you
continue.
When you are finished, give me the total registered and the total enabled,
and list anything in the registry you could not recreate and why.
The second is the one people skip. A week after the server takes over, run it on the old machine. The point of this build is that one box runs things. Leftover jobs quietly firing on a laptop is exactly the duplicate-work problem you moved everything to avoid.
audit-background-work.md
Audit what is still running in the background on this machine.
Check all of these and give me one table: what it is, what starts it, and
whether it still needs to exist now that the server has taken over.
- Loaded launch agents and daemons, excluding Apple's own
- Everything in the user and system LaunchAgents folders
- Any cron entries
- Any process manager still holding saved processes
- Services started by the package manager
- Login items and background-permitted apps
For each one, recommend keep, remove, or ask me, with a one-line reason.
Do not remove anything yet. Give me the table first, then wait.
Read the plan before you approve it. These prompts are deliberately written to stop and report between phases. That pause is where you catch the wrong assumption, while it still costs one line to fix. Approving a server build without reading it is how you end up with a machine only the model understands.