Take her from "95% of my crons are broken and my secrets live in code" to a properly-instrumented always-on home server with from-anywhere access, real backups, and a graduation path to cloud production.
Her plan (read this for context if you haven't): julianna-plan.austinrenfroe.com.
pmset/systemsetup/defaults write instead of "go to System Settings and clickβ¦"crontab -l > ~/.crontab.backup-$(date +%s). Before editing any config, snapshot it.sudo -v + a keepalive loop, then drop it.Critical: this work spans hours, possibly days. If Julianna comes back to a fresh Claude Code session and points you at this URL, you may be partway through. Do not restart from Phase 1 β you'll bother her with redundant signup asks, duplicate Healthchecks entries, and confusion.
Run this state probe first. It tells you what's already done:
# What's installed
echo "=== Installed tools ==="
brew --version 2>/dev/null | head -1 || echo "Homebrew: NOT INSTALLED"
gh --version 2>/dev/null | head -1 || echo "gh: NOT INSTALLED"
docker --version 2>/dev/null || echo "Docker: NOT INSTALLED"
op --version 2>/dev/null || echo "1Password CLI: NOT INSTALLED"
ls /Applications/Tailscale.app >/dev/null 2>&1 && echo "Tailscale: installed" || echo "Tailscale: NOT INSTALLED"
ls /Applications/Backblaze.app >/dev/null 2>&1 && echo "Backblaze: installed" || echo "Backblaze: NOT INSTALLED"
which cloudflared >/dev/null 2>&1 && echo "cloudflared: installed" || echo "cloudflared: NOT INSTALLED"
echo ""
echo "=== System settings ==="
pmset -g | grep -E "^ +(sleep|disablesleep|womp)"
sudo systemsetup -getremotelogin 2>/dev/null
echo ""
echo "=== Auth status ==="
gh auth status 2>&1 | head -3
op vault list 2>&1 | head -3
echo ""
echo "=== Tailscale ==="
/Applications/Tailscale.app/Contents/MacOS/Tailscale status 2>&1 | head -3
echo ""
echo "=== Crontab healthchecks instrumentation ==="
HC_LINES=$(crontab -l 2>/dev/null | grep -c "hc-run" || echo 0)
TOTAL_LINES=$(crontab -l 2>/dev/null | grep -cE "^[^#]*[a-z]" || echo 0)
echo "Crons instrumented: $HC_LINES / $TOTAL_LINES"
echo ""
echo "=== Projects in ~/projects ==="
ls -d ~/projects/*/ 2>/dev/null | head -10
echo ""
echo "=== State backup files ==="
ls ~/.crontab.backup-* 2>/dev/null | head -5
From the output, build a TodoWrite list with completed phases marked done. Resume from the first incomplete phase. Tell Julianna in one short message: "Looks like you're already through Phases X, Y, Z. Picking up at Phase N."
If gh auth status, op vault list, and Tailscale are all green, she does NOT need to re-paste tokens β they persist on disk. Only ask for what's actually missing. Keep tokens you collect in a small state file:
# Stash credentials between sessions (root-readable only)
mkdir -p ~/.julianna-setup
chmod 700 ~/.julianna-setup
# Write tokens as you collect them
echo "$HC_API_KEY" > ~/.julianna-setup/healthchecks-api-key
chmod 600 ~/.julianna-setup/healthchecks-api-key
Future sessions can read these instead of asking again.
Try to debug yourself before escalating to Julianna. She is not a sysadmin β she can't help you read tracebacks. She doesn't know what a PATH is, what cgroups are, why a certificate might be invalid, or how to interpret a Homebrew warning. Spend at least 2-3 attempts at fixes on your own before involving her.
Frame in plain language. Never paste raw stack traces or shell errors at her.
Bad: "Failed: error: failed to push some refs to 'https://github.com/...'\n ! [rejected] main -> main (fetch first)\nhint: Updates were rejected..."
Good: "GitHub says there's already content at the repo I tried to push to. Did you maybe push to it from another machine? I can either pull what's there and merge, or rename my local push target β your call."
Write what you tried into ~/.julianna-setup/handoff.md with the failed phase, the commands you ran, and the actual errors. Tell Julianna: "I'm stuck on X. I've written what I tried to ~/.julianna-setup/handoff.md. Start a new Claude session, paste this URL again, and tell it to read that file before resuming β it'll pick up with full context."
First message to Julianna (verbatim, friendly):
"Hi Julianna! I'm going to set up your Mac mini as your always-on dev hub. Before I start: am I currently running on your Mac mini, or are you talking to me from your MacBook (or another computer)? If you're not sure, you can check by running hostname in my next response."
Then run hostname yourself to confirm.
Continue to Phase 1.
Tell her: "For the first 20 minutes I need you to physically sit at your Mac mini, because I need to enable some things via its keyboard. Once Tailscale is installed there, you can come back to whichever device you prefer and I can do the rest remotely."
Then ask her to install Claude Code on the Mac mini and start a new session there with the same URL. (If Claude Code isn't yet installed on the Mac mini, walk her through one command: brew install anthropic-ai/tap/claude-code after installing Homebrew per Phase 3.1 β but the cleanest path is for her to switch to the Mac mini physically.)
Send Julianna a single batched message asking her to spend 15-20 minutes signing up for accounts and pasting back tokens. This is the only block of "her doing things" before you take over.
"Quick batch task before I take over: I need you to create accounts at 4 services and paste back a few tokens. About 15-20 minutes. After this you can mostly walk away. Do them in this order:"
1. Tailscale (remote access from anywhere)
tskey-auth-β¦).2. GitHub (where your code lives + automatic backup)
github_pat_β¦).3. Healthchecks (alerts when crons break)
4. (Optional, defer) Cloudflare β only needed when you launch your first public-facing thing. Skip for now; we'll do this later when you actually need it.
5. (Optional, defer) Backblaze β same, we'll handle when we get there. It's a manual app install with no API.
"Once you've pasted those 3 tokens back, take a 10-min break. I'll work for a while and check in when I need you again β probably ~45 min from now for one quick 1Password thing."
Wait until you have all 3 tokens. Store them in a TodoWrite item or your scratch context so you don't lose them.
Collect her macOS user password once. Tell her: "I need your Mac password for sudo β I'll use it for a batch of system-level changes and then drop it. Paste it now and I won't ask again for this batch."
Use sudo -v to cache it, then run a keepalive in the background while you batch:
# Validate sudo and start keepalive
sudo -v
( while true; do sudo -n true; sleep 50; kill -0 "$$" || exit; done 2>/dev/null ) &
KEEPALIVE_PID=$!
# Disable system sleep entirely (display sleep is fine, just don't suspend)
sudo pmset -a sleep 0
sudo pmset -a disablesleep 1
# Wake on network access (so the Mac mini wakes for SSH if it ever does sleep)
sudo pmset -a womp 1
# Don't sleep on power adapter (paranoid double-tap)
sudo pmset -c sleep 0
# Enable Remote Login (SSH)
sudo systemsetup -setremotelogin on
# Stop the sudo keepalive
kill $KEEPALIVE_PID 2>/dev/null
sudo -k
# Confirm sleep is off
pmset -g | grep -E "sleep|disablesleep|womp"
# Should show: sleep 0, disablesleep 1, womp 1
# Confirm SSH is on
sudo systemsetup -getremotelogin
# Should print: Remote Login: On
# Confirm SSH actually works locally
ssh -o BatchMode=no -o StrictHostKeyChecking=accept-new localhost echo OK
# (will prompt for her password once; expected)
If any of these don't pass, stop and explain. Don't continue to Phase 3.
Check first:
brew --version 2>/dev/null || /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
If installing fresh, the installer will ask for sudo. Use her cached password if still valid, otherwise ask once and use the keepalive trick again.
After install, ensure Homebrew is on her PATH (Apple Silicon location):
if ! grep -q 'opt/homebrew/bin' ~/.zprofile 2>/dev/null; then
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
fi
eval "$(/opt/homebrew/bin/brew shellenv)"
brew install gh jq coreutils
brew install --cask tailscale docker 1password 1password-cli
Notes:
jq for JSON parsing in your scriptscoreutils for GNU date and others (BSD versions on macOS are quirky)1password cask is safe to include even if she already has it (Homebrew will skip)open -a Docker
# Wait for Docker daemon to be ready
echo "Waiting for Docker daemon..."
while ! docker info >/dev/null 2>&1; do sleep 2; done
echo "Docker is ready."
If Docker prompts a GUI dialog (terms acceptance, privileged helper install), tell Julianna: "Docker is asking you to click through one dialog β accept the terms and let it install its helper. I'll wait."
brew --version
gh --version
docker --version
docker run --rm hello-world | head -5
ls /Applications | grep -E "Tailscale|1Password"
Use the auth key from Phase 1 to bring up Tailscale unattended.
# Open the Tailscale GUI app (registers the system extension on first run)
open -a Tailscale
# Give the app a moment to register
sleep 8
# CLI may live at /Applications/Tailscale.app/Contents/MacOS/Tailscale
TAILSCALE="/Applications/Tailscale.app/Contents/MacOS/Tailscale"
if [ ! -x "$TAILSCALE" ]; then
TAILSCALE="$(which tailscale || echo '')"
fi
[ -z "$TAILSCALE" ] && { echo "Tailscale CLI not found"; exit 1; }
# Bring up Tailscale with the auth key (replace TSKEY with her actual key)
"$TAILSCALE" up --authkey="$TSKEY" --hostname="mac-mini" --accept-routes
# Should return a 100.x.x.x address
"$TAILSCALE" ip -4
# Should show "Logged in" and the device's MagicDNS name
"$TAILSCALE" status | head -3
After Mac mini is on, tell Julianna (one batched message):
"Mac mini is now reachable from anywhere via Tailscale. Two more quick installs and we're done with the Tailscale piece:"
brew install --cask tailscale && open -a Tailscale β sign in with the same Tailscale account when prompted."Once those are done you can come back to your MacBook Pro and pick up where you were β I'll be on the Mac mini regardless."
Once she confirms her MacBook Pro is on Tailscale, run a probe yourself:
# Get the Mac mini's Tailscale name
MAC_MINI_TS_NAME="$("$TAILSCALE" status --json | jq -r '.Self.DNSName' | sed 's/\.$//')"
# From the Mac mini, attempt to ping the MacBook Pro by its tailnet name
# (You'll need to ask her: "What's the name of your MacBook Pro in Tailscale?
# Open Tailscale on it and look at the device list β looks like
# julianna-mbp.tail-scale.ts.net or similar.")
"$TAILSCALE" ping --c 3 "$MBP_TS_NAME"
If ping succeeds, the mesh is working. Note her MacBook Pro tailnet name in your todo list β you'll use it for the final SSH-in test in Phase 12.
# Authenticate gh non-interactively using her PAT
echo "$GITHUB_PAT" | gh auth login --with-token --hostname github.com
# Configure git to use gh's credentials
gh auth setup-git
# Set her git identity if not already
git config --global user.email "$(gh api user --jq .email)"
# If above is null, ask her: "What email should I use for git commits?"
git config --global user.name "$(gh api user --jq .name)"
gh auth status
gh api user --jq '.login'
# Should print her GitHub username
This is unfortunately the one step that requires her interaction. The 1Password CLI must authorize through the desktop app.
"One quick interactive step: I need you to open the 1Password app, then go to Settings β Developer (or Preferences β Developer on older versions), and check the box that says 'Integrate with 1Password CLI.' That lets me read secrets from your vaults without needing your master password every time. Tell me when it's done."
After she confirms, verify and create the working vault:
# Verify CLI β app integration
op vault list 2>&1 | head -5
# If this fails, ask her to open the 1Password app to the front so it can authorize.
# Create a dedicated vault for code-accessed secrets
if ! op vault list --format=json | jq -e '.[] | select(.name == "Code Secrets")' >/dev/null; then
op vault create "Code Secrets" --description "Secrets fetched at runtime by code on the Mac mini"
fi
# Verify
op vault list --format=json | jq -r '.[].name'
Use the Healthchecks API key from Phase 1 to create checks for every cron, then rewrite her crontab to ping. Do not modify the actual cron commands themselves β only wrap them.
crontab -l > ~/.crontab.backup-$(date +%Y%m%d-%H%M%S)
crontab -l > /tmp/crontab.current
sudo tee /usr/local/bin/hc-run > /dev/null <<'EOF'
#!/bin/bash
# Usage: hc-run <healthchecks-uuid> <cmd> [args...]
HC_UUID="$1"; shift
HC_BASE="https://hc-ping.com"
curl -fsS -m 10 --retry 3 "$HC_BASE/$HC_UUID/start" > /dev/null 2>&1 || true
"$@"
EXIT=$?
curl -fsS -m 10 --retry 3 "$HC_BASE/$HC_UUID/$EXIT" > /dev/null 2>&1 || true
exit $EXIT
EOF
sudo chmod +x /usr/local/bin/hc-run
For each line in her crontab that runs a real command (skip blanks/comments):
/Users/julianna/scripts/lead-intake.sh β "lead-intake") or ask her to confirm names if the command is opaque.curl -fsS https://healthchecks.io/api/v3/checks/ \
-H "X-Api-Key: $HC_API_KEY" \
-d "{
\"name\": \"$NAME\",
\"schedule\": \"$CRON_SCHEDULE\",
\"tz\": \"$(date +%Z)\",
\"grace\": 600,
\"unique\": [\"name\"]
}" | jq -r '.ping_url' | awk -F/ '{print $NF}'
Capture the returned UUID for each check.
Build a new crontab where each command line becomes:
# BEFORE:
*/15 8-18 * * * /Users/julianna/scripts/lead-intake.sh
# AFTER:
*/15 8-18 * * * /usr/local/bin/hc-run <UUID> /Users/julianna/scripts/lead-intake.sh
Preserve all comments, blank lines, and ordering. Show Julianna the proposed diff and get a "yes" before installing it. Then:
crontab /tmp/crontab.new
Via API, create an email integration (her account email is already there by default β just verify):
curl -fsS https://healthchecks.io/api/v3/channels/ \
-H "X-Api-Key: $HC_API_KEY" | jq '.channels[] | {kind, name, id}'
If no email channel exists, ask her: "What phone number should I send SMS alerts to?" and add SMS via the API (requires her to upgrade to a paid plan if she wants SMS β email-only is fine on free).
Verify hc-run end-to-end with a throwaway check:
# Create a throwaway test check
TEST_UUID=$(curl -fsS https://healthchecks.io/api/v3/checks/ \
-H "X-Api-Key: $HC_API_KEY" \
-d '{"name": "self-test (delete me)", "timeout": 60}' | jq -r '.ping_url' | awk -F/ '{print $NF}')
# Run hc-run with a successful command
/usr/local/bin/hc-run "$TEST_UUID" /bin/true
# Wait for the API to reflect the ping
sleep 5
# Verify the check is now "up"
STATUS=$(curl -fsS "https://healthchecks.io/api/v3/checks/$TEST_UUID" -H "X-Api-Key: $HC_API_KEY" | jq -r '.status')
[ "$STATUS" = "up" ] && echo "PASS" || echo "FAIL: status=$STATUS"
# Clean up
curl -fsS -X DELETE "https://healthchecks.io/api/v3/checks/$TEST_UUID" -H "X-Api-Key: $HC_API_KEY" > /dev/null
If FAIL, debug before continuing β the wrapper is foundational.
The point of Phase 7 is observability. Once her real crons run for ~24 hours with healthchecks attached, she'll have data on what's actually broken vs. what's actually fine. Triage in Phase 12. Premature fixes during instrumentation muddy the data.
# Common locations for her projects
ls -la ~ ~/Documents ~/Desktop 2>/dev/null | grep "^d"
# Look for git repos already
find ~ ~/Documents ~/Desktop -name ".git" -type d -maxdepth 4 2>/dev/null | head -30
Ask her to confirm which directories are projects vs. personal files. Show her a list, get a yes/no on each.
mkdir -p ~/projects
# For each confirmed project:
mv "$SOURCE_PATH" ~/projects/$PROJECT_NAME
for proj in ~/projects/*/; do
cd "$proj"
PROJECT_NAME="$(basename "$proj")"
# Skip if already has a remote
if git remote get-url origin >/dev/null 2>&1; then
echo "$PROJECT_NAME: already has remote, skipping"
continue
fi
# Init if needed
[ -d .git ] || git init -b main
# Standard .gitignore for safety β don't push secrets
cat <<'EOF' >> .gitignore
.env
.env.*
*.key
*.pem
credentials.json
secrets.json
.DS_Store
__pycache__/
node_modules/
*.log
EOF
# Dedupe
sort -u .gitignore -o .gitignore
git add .gitignore
git diff --staged --quiet || git commit -m "Add baseline .gitignore"
# Stage everything ELSE
git add .
git diff --staged --quiet || git commit -m "Initial commit"
# Create private GitHub repo + push
gh repo create "$PROJECT_NAME" --private --source=. --push
done
# Confirm every local project has a GitHub remote
for proj in ~/projects/*/; do
cd "$proj"
REMOTE=$(git remote get-url origin 2>/dev/null || echo "MISSING")
echo "$(basename "$proj"): $REMOTE"
done
cd ~/projects
grep -rEn '(api[_-]?key|secret|password|token|sk-[a-zA-Z0-9]+|ghp_[a-zA-Z0-9]+|xoxb-|AKIA[0-9A-Z]{16})' \
--include="*.py" --include="*.js" --include="*.ts" \
--include="*.sh" --include="*.rb" --include="*.go" \
--include=".env*" --include="config.*" \
. 2>/dev/null > /tmp/secret-candidates.txt
wc -l /tmp/secret-candidates.txt
Filter aggressively β many matches will be false positives (variable names, comments, doc strings). Build a shortlist of actual values.
Show her a deduplicated list of distinct secrets you found, grouped by service. Ask her to add each one to 1Password under the "Code Secrets" vault, in a single batch:
"I found N distinct secrets across your code. I'll list them by service. For each, please:
credential"List of distinct secrets to add:
[OpenAI] found in: openai_key.py:3, lead_intake.sh:12 β api key starting with sk-...
[GitHub PAT] found in: scripts/sync.sh:4 β token starting with ghp_...
[Asana] found in: asana_sync.py:18 β long opaque token
...
"Tell me when all N are added."
After she confirms, for each secret-bearing file:
$(op read "op://Code Secrets/<name>/credential")..env.template next to the script:
OPENAI_API_KEY=op://Code Secrets/OpenAI/credential
ASANA_TOKEN=op://Code Secrets/Asana/credential
Then change the script's invocation to op run --env-file=.env.template -- python script.py.op run --env-file=... if needed. Cron's PATH won't include /opt/homebrew/bin by default β use absolute paths.For each refactor, leave the old hardcoded value as a backup in a commented-out line ONE TIME for the commit, then delete it in the next commit. This way the migration commit is reviewable.
# For each script you modified, run a dry-equivalent that exercises the secret
# (don't run the real cron command β pick something that just authenticates and exits)
for script in $MODIFIED_SCRIPTS; do
echo "Testing: $script"
bash -n "$script" # syntax check
# Then a real test if you can identify a non-destructive auth check
done
If any test fails, restore from the backup commit and explain.
for proj in ~/projects/*/; do
cd "$proj"
git diff --quiet && continue
git add -u
git commit -m "Move secrets to 1Password CLI"
git push
done
Send Julianna a single message:
"Last manual step: Backblaze for full Mac mini backups. Two clicks:"
"It'll start backing up everything in the background. Tell me when you've done it and I'll verify it's running."
# Backblaze app is at /Applications/Backblaze.app and runs a daemon.
ls /Applications/ | grep -i backblaze
ps aux | grep -i bzbmenu | grep -v grep
If running: confirm to her, move on.
Skip this phase entirely until Julianna says she's ready to put a project on the public internet. There's no value in standing up Cloudflare for nothing. When she does ask, return here.
"Two things I need from you to wire up your first public site:"
brew install cloudflared
# Authenticate cloudflared (this opens a browser β one click for her)
cloudflared tunnel login
# After login, certificate lands at ~/.cloudflared/cert.pem
# Create the tunnel
cloudflared tunnel create mac-mini
TUNNEL_ID=$(cloudflared tunnel list -o json | jq -r '.[] | select(.name == "mac-mini") | .id')
cat <<EOF > ~/.cloudflared/config.yml
tunnel: $TUNNEL_ID
credentials-file: $HOME/.cloudflared/$TUNNEL_ID.json
ingress:
# Add hostname β service entries per project as you launch them.
# Example:
# - hostname: my-app.her-domain.com
# service: http://localhost:3000
- service: http_status:404
EOF
For reference, here's a working multi-hostname config you can use as a template when she's running multiple public services:
# Multi-hostname example (for when she has 2+ public projects)
tunnel: <tunnel-id>
credentials-file: /Users/julianna/.cloudflared/<tunnel-id>.json
ingress:
# Project 1 (apex + www)
- hostname: my-first-project.com
service: http://localhost:3000
- hostname: www.my-first-project.com
service: http://localhost:3000
# Project 2 (subdomain on her main domain)
- hostname: scheduler.julianna-domain.com
service: http://localhost:3001
# Project 3 (with TLS termination at the origin β rare)
- hostname: secure-app.julianna-domain.com
service: https://localhost:8443
originRequest:
noTLSVerify: true
# Catch-all 404 β must be the last rule
- service: http_status:404
Each public hostname needs (a) an ingress entry here, (b) a DNS route created via cloudflared tunnel route dns (Phase 11.3 below), and (c) the actual service running on the specified localhost:port. Reload after edits with sudo launchctl kickstart -k system/com.cloudflare.cloudflared (the launchd label may differ β check with sudo launchctl list | grep cloudflare).
For each public hostname:
cloudflared tunnel route dns mac-mini <subdomain>.<her-domain>
sudo cloudflared service install
# Or for user-only:
# cloudflared service install (without sudo, depending on version)
# Verify
sudo launchctl list | grep cloudflared
# Tunnel should show as connected
cloudflared tunnel info mac-mini
# Public hostname should resolve via Cloudflare
dig +short <subdomain>.<her-domain> @1.1.1.1
# End-to-end: from outside the LAN
curl -sI https://<subdomain>.<her-domain>/
Write a single verification script that tests every layer. Run it. Report a clean pass/fail summary to Julianna.
#!/bin/bash
# verify-stack.sh β runs all the smoke tests
set -u
PASS=0
FAIL=0
report() { if [ "$2" = "0" ]; then echo "PASS $1"; PASS=$((PASS+1)); else echo "FAIL $1"; FAIL=$((FAIL+1)); fi }
# 1. SSH from outside (run a remote command via Tailscale name, from the Mac mini, to itself by tailnet name β proves DNS + SSH listener)
TS_NAME=$(/Applications/Tailscale.app/Contents/MacOS/Tailscale status --json | jq -r '.Self.DNSName' | sed 's/\.$//')
ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new "$TS_NAME" "echo ok" >/dev/null 2>&1
report "Tailscale SSH self-loop" $?
# 2. Healthchecks roundtrip
TEST_UUID=$(curl -fsS https://healthchecks.io/api/v3/checks/ -H "X-Api-Key: $HC_API_KEY" -d '{"name":"verify-stack ephemeral","timeout":60}' | jq -r '.ping_url' | awk -F/ '{print $NF}')
/usr/local/bin/hc-run "$TEST_UUID" /bin/true
sleep 4
STATUS=$(curl -fsS "https://healthchecks.io/api/v3/checks/$TEST_UUID" -H "X-Api-Key: $HC_API_KEY" | jq -r '.status')
[ "$STATUS" = "up" ]
report "Healthchecks ping/status roundtrip" $?
curl -fsS -X DELETE "https://healthchecks.io/api/v3/checks/$TEST_UUID" -H "X-Api-Key: $HC_API_KEY" >/dev/null
# 3. GitHub: list one of her repos
gh repo list --limit 1 -q '.[].name' >/dev/null 2>&1
report "GitHub auth + repo list" $?
# 4. 1Password: list vaults
op vault list --format=json | jq -e '.[] | select(.name == "Code Secrets")' >/dev/null
report "1Password CLI + Code Secrets vault" $?
# 5. Docker: run hello-world
docker run --rm hello-world >/dev/null 2>&1
report "Docker daemon" $?
# 6. Sleep settings
SLEEP_VAL=$(pmset -g | awk '/^ +sleep/ {print $2; exit}')
[ "$SLEEP_VAL" = "0" ]
report "Mac mini sleep disabled" $?
# 7. SSH service
sudo systemsetup -getremotelogin 2>/dev/null | grep -q "On"
report "Remote Login enabled" $?
# 8. Cron healthchecks instrumented
HC_LINES=$(crontab -l 2>/dev/null | grep -c "hc-run")
TOTAL_LINES=$(crontab -l 2>/dev/null | grep -cE "^[^#]*[a-z]")
echo "INFO Crons instrumented: $HC_LINES / $TOTAL_LINES"
# 9. Backblaze running
pgrep -f bzbmenu >/dev/null
report "Backblaze daemon" $?
echo ""
echo "Results: $PASS passed, $FAIL failed"
exit $FAIL
Save as ~/verify-stack.sh, chmod +x, run it, and show Julianna only the final summary line plus any failures.
Write a final state summary to ~/.julianna-setup/done.md for any future Claude session to pick up:
cat <<EOF > ~/.julianna-setup/done.md
# Julianna's Mac mini setup β completed $(date)
- Phase 1 credentials: stored in ~/.julianna-setup/
- Phase 2 system prep: pmset, SSH on
- Phase 3 tools: $(brew list --formula 2>/dev/null | wc -l) brew formulas, $(brew list --cask 2>/dev/null | wc -l) casks
- Phase 4 Tailscale: $(/Applications/Tailscale.app/Contents/MacOS/Tailscale ip -4 2>/dev/null)
- Phase 5 GitHub: $(gh api user --jq .login 2>/dev/null)
- Phase 6 1Password: $(op vault list --format=json 2>/dev/null | jq length) vaults
- Phase 7 Healthchecks: $(crontab -l 2>/dev/null | grep -c hc-run) crons instrumented
- Phase 8 Projects: $(ls -d ~/projects/*/ 2>/dev/null | wc -l) repos
- Phase 9 Secrets migrated: see ~/.julianna-setup/secrets-migrated.txt
- Phase 10 Backblaze: $(pgrep -f bzbmenu >/dev/null && echo "running" || echo "NOT INSTALLED")
- Phase 11 Cloudflare: $(which cloudflared >/dev/null && echo "installed" || echo "deferred")
EOF
Then send Julianna this final message, in her language (no jargon):
What's running for you (you don't have to manage):
What's still on your plate:
How to do future work β important, save this list:
cd ~/projects && mkdir my-new-project && cd my-new-project && claude, then tell Claude: "Set up a new project here following my home-base setup conventions. Read julianna-plan.austinrenfroe.com if you need the architecture context."claude and tell it: "Set up a Cloudflare Tunnel route for this project at <subdomain>.<your-domain>, following Phase 11 of julianna-plan.austinrenfroe.com/claude-setup.html." It'll handle the rest.claude and tell it: "Graduate this project to its own Hetzner VM following the architecture in julianna-plan.austinrenfroe.com. The Mac mini stays as my dev environment; the cloud VM is the new prod." It'll provision the VM, copy your Docker config, set up DNS, and switch traffic over.That's it. You're now running on the same shape of setup that production engineering teams use, scaled down for one person. Build things.
End the session here. Your work is complete.