Artificial Intelligence

How I Turned a Mac Mini Into an Always-On AI Server To Code, Write, and Podcast

Every marketer and developer who has spent real time with Claude Code, Codex, or any other agentic coding assistant eventually hits the same wall: the good work happens in long, uninterrupted loops (refactors, test runs, multi-file migrations, research passes) and none of that survives you closing the laptop lid to go make dinner. Worse, every token those loops burn is a token you’re paying for on your API bill or subtracting from your subscription’s usage window, even when the task is mechanical enough that a $0 local model could have handled it.

The fix isn’t a bigger cloud bill. It’s a small, silent, always-on machine sitting on a shelf in a closet, wired directly into your network, doing the boring and the long-running work so your primary machine, and your token budget, don’t have to. This is the exact setup I run at home. Below is every piece, in the order you’d install it.

Keep in mind that I’m a master of all trades… I develop, write, record, edit, and design. Outside of digital transformation and integration, I don’t see myself as an expert in any one area. I’m an AI enthusiast and wanted opportunities to bring open-source models into the work I was doing to see whether they could reduce my token spend and lower the overhead of siloed enterprise AI platforms.

In this guide:

Why Bother: The Case For A Home AI Server

There are two distinct payoffs here, and it’s worth separating them because they justify the setup in different ways.

1. Loops and Processes that Outlive Your Laptop Lid

An agentic coding session that runs for twenty minutes, an hour, or overnight (a large migration, a test-and-fix loop, a scheduled research job) needs a machine that stays on. Your laptop is not that machine; it’s the thing you close and carry to a meeting, to the kitchen, onto a plane. A dedicated server changes the question from Can I keep this running to Where do I check in on it? You start a job over SSH, detach, close the lid, and the work keeps going on a box that was never going to sleep in the first place.

2. A real Reduction in Paid Token Usage

Not every prompt needs a frontier model. Boilerplate generation, first-pass drafts, mechanical refactors, formatting passes, and well-scoped do this exact thing subtasks are all things a local model running on Apple Silicon can handle for free, on hardware you already own. Routing that class of work to a local model, instead of spending metered API tokens or eating into a subscription’s usage window, directly reduces spend.

It doesn’t replace Claude for judgment-heavy work; it takes the mechanical tail off the top of your usage, which on a heavy day is often a third or more of total token volume. That holds regardless of which model in the Claude 5 lineup is driving the session: Opus 5 for the hardest reasoning, Sonnet 5 as the default workhorse, Fable 5, or Haiku 4.5 for fast built-in calls; the local machine is quietly absorbing the same mechanical tail underneath all of them.

The same logic extends past text: voice generation for narration or podcast audio is metered per character on every hosted TTS service, and image generation is metered per image on every hosted model, and a local model erases both line items too.

The pattern in practice: Claude Code (or any MCP-aware agent) does the reasoning, planning, and anything that needs real judgment, then hands off a well-scoped, mechanical subtask- generating code, drafting text, synthesizing a line of narration, or producing an image or diagram- to a local model over MCP, gets the result back, and keeps going. You get one continuous session with several engines under the hood: one you pay for by the token, the rest you already own outright.

3. Four Jobs, One always-On Machine

Once the bridge exists, what you route through it stops being just “code.” The same MCP pattern, Claude deciding mid-session that a subtask is well-scoped enough to hand off, covers four distinct jobs on this one machine:

  • Coding: boilerplate, mechanical refactors, test generation, and quick, well-scoped subtasks handed to a local Ollama model instead of the paid session, freeing the frontier model for the parts of the diff that actually need judgment.
  • Writing: first-pass drafts, summarization, rewrites, and bulk copy work, the same local models used for code, prompted for prose instead, run through the identical local_llm_generate tool.
  • Text-to-speech (TTS) and voice cloning: narrating a paragraph to sanity-check tone, or generating a full multi-speaker, cloned-voice podcast episode, entirely on-device, with no per-character billing.
  • Image and diagram creation: on-brand marketing graphics, flat icon sets, and architecture-style diagrams generated locally, plus converting a raster logo or icon into a clean vector SVG, with no per-image billing either.

Steps 4 through 6 below set up the MCP bridge for each of those four jobs in turn.

The ROI Math on a $2,800 Mac Mini

The configuration below, an Apple M4 Pro Mac Mini with 48GB of unified memory and a 2TB SSD, runs about $2,800. That number only makes sense against what it replaces.

Compare the alternatives:

  • Renting equivalent always-on GPU compute in the cloud commonly runs in the neighborhood of $1-3/hour for on-demand instances capable of serving a 30B-class model, before you’ve paid for a single token of your actual coding assistant. Left running continuously, that’s easily $700-$2,000+ a month. The Mac Mini breaks even against that within the first one to two months of genuinely heavy use, and every month after is free.
  • Power draw is trivial. Apple Silicon idles at single-digit watts and rarely exceeds 60-65W under sustained local-inference load on the Mini. Run it 24/7 for a year, and electricity costs are measured in tens of dollars, not hundreds.
  • Token offload compounds. If even a quarter of a heavy day’s prompt volume is the kind of mechanical work a local model can absorb (boilerplate, formatting, draft passes, simple refactors, narration audio, marketing graphics), that’s a standing quarter-off discount on your API or subscription spend, every day, indefinitely, on hardware that has already paid for itself.
  • Per-image and per-character billing disappears too. Hosted image generation and hosted TTS are both metered the same way API tokens are, per image, per character, and both get replaced outright by local models running on the Mini’s own GPU, with no separate line item to track.

The output side of the ledger matters just as much as the cost side. Once loops don’t have to babysit a laptop lid, you can queue up work that used to be too long to sit and watch: a full-repo migration, speed optimization of a WordPress instance, a batch of research tasks, an overnight test-fix cycle. That’s throughput you weren’t getting before, not just a cost avoided: the honest way to think about the $2,800 is less money saved and more a second, tireless set of hands added to the team for a one-time cost.

The Hardware and Network

Here’s exactly what’s running this setup:

ComponentSpec
MachineApple Mac Mini (M4 Pro)
CPU14-core (10 performance + 4 efficiency)
GPU20-core Apple GPU (Metal 4)
Unified Memory48 GB
Storage2 TB SSD
Approx. price$2,800
Network uplink2 Gbps fiber internet connection
LANEero mesh Wi-Fi system for the rest of the house
Server connectionHardwired via Ethernet directly into an Eero unit (no Wi-Fi)

The 48GB of unified memory is the load-bearing spec here, not the CPU core count. Local models in the 27B-32B range take up 18-20GB on disk and need comfortable headroom above that in memory to run without swapping. 48GB gives room to run a model that size alongside macOS and the MCP bridge processes without ever feeling the ceiling. 16GB or 24GB configurations will run smaller 7B-class models fine but will choke on anything in the 27B+ class.

The hardwired Ethernet connection matters more than it should. This machine is meant to be left alone in a closet or on a shelf, headless, for weeks at a time, and you don’t want to troubleshoot a flaky Wi-Fi association on a box you can’t see. A wired connection into the Eero mesh gives it a stable, low-latency path to the router with zero variability, which matters both for SSH responsiveness and for anything pulling large model files over the 2Gb fiber line.

The Mac Mini is the only device on this network with a permanent wired connection; everything else roams on Wi-Fi or reaches it remotely through Tailscale.

NOTE: Before you start thinking about copying and pasting code here… leverage your AI to do the hard work for you! I set up Claude Code CLI on my Mac Mini and, at ever step, provided it permissions along the way to help me configure and troubleshoot everything I did. It wrote a custom MCP that incorporates all my models into workflows, configured, and tested everything below.

Step 1 of 10: Turn the Mac Mini into a headless, always-on server

Before installing anything, get macOS out of the way so it behaves like a server, not a laptop that naps.

  1. Give the machine a clear hostname so it’s identifiable on the network and in Tailscale later:
    sudo scutil --set ComputerName "myserv"
    sudo scutil --set HostName "myserv"
    sudo scutil --set LocalHostName "myserv"
  2. Turn off sleep entirely (system, disk, and display) since this machine has no reason to ever nap:
    sudo pmset -a sleep 0
    sudo pmset -a disksleep 0
    sudo pmset -a displaysleep 0
    sudo pmset -a womp 1   # wake on network access, useful if you ever want remote wake
  3. Enable Remote Login (SSH) so you can reach it headless:

    System Settings → General → Sharing → Remote Login: toggle it on, and allow access for your user account.

  4. Optional but recommended: enable auto-login and disable the login-screen password prompt on boot if the Mini lives somewhere physically secure, so a power blip doesn’t leave it stuck at a lock screen with no monitor attached to unlock it.

Why this matters: everything that follows assumes this machine is reachable and awake 24/7 without anyone touching it. Skipping this step is the most common reason a home AI server quietly goes to sleep mid-job.

Step 2 of 10: Install Homebrew

Homebrew is the package manager that installs everything else in this guide. From a terminal (over screen-share or a temporarily attached keyboard/monitor, or already over SSH if you got that far):

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Follow the prompts it gives you to add Homebrew to your shell’s PATH (on Apple Silicon it installs to /opt/homebrew). Confirm it’s working:

brew --version

Step 3 of 10: Install Ollama and pull local models

Ollama runs the local models and exposes them via a local API. Install it and set it up to run as a background service so it starts on boot, not just when a terminal is open:

brew install ollama
brew services start ollama

That runs Ollama as a persistent LaunchAgent listening on localhost:11434. Now pull the models. This is where the 48GB of memory and the 2 Gbps fiber both earn their keep; these are multi-gigabyte downloads:

# A strong general coding model, fits comfortably in 48GB
ollama pull qwen2.5-coder:32b

# A multimodal reasoning model for writing, vision, and long-context offload
ollama pull qwen3.8:27b-mlx

# A fast, small coding model for quick, cheap subtasks
ollama pull qwen2.5-coder:7b

Verify they’re pulled and available:

ollama list
ModelSizeUse it for
qwen2.5-coder:32b~19.9 GBSubstantial local coding subtasks: refactors, boilerplate, test generation
qwen3.8:27b-mlx~18 GBGeneral writing, reasoning, and vision offload: it ships with tool-calling and a “thinking” mode, plus a 262K-token context window, so it can take a long log dump, a full repo’s worth of context, or a screenshot and still keep up
qwen2.5-coder:7b~4.7 GBFast default for small, quick, well-scoped code tasks

That middle slot is worth calling out: it used to run a plain 32B instruct model for general writing and reasoning offload. Swapping it for a newer release with vision and a huge context window took one ollama pull of the new tag and one ollama rm of the old one, nothing else on the machine, not the MCP bridge, not Claude Code’s configuration, had to change. That’s the actual benefit of routing this through Ollama instead of hard-coding a model name somewhere: the local model stack stays current without becoming a project of its own.

Step 4 of 10: Bridge Ollama into Claude Code with an MCP server

Ollama running by itself is just a local API nobody’s calling. The piece that makes this actually useful is a small Model Context Protocol (MCP) server that exposes those local models as tools Claude Code can call mid-session, so Claude can decide, on its own, this next bit is mechanical, I’ll hand it to the local model instead of spending its own tokens on it.

You’ll need Node.js installed first (brew install node if you don’t have it). Then create the server:

mkdir -p ~/mcp-servers/local-llm
cd ~/mcp-servers/local-llm
npm init -y
npm install @modelcontextprotocol/sdk zod

Write server.js as a small stdio MCP server with two tools: one to list what’s pulled, one to actually generate a response. Give requests a hard timeout and a readable error if Ollama isn’t running, local models can be slow to load the first time they’re called, and a hung tool call is worse than a slow one:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const OLLAMA_BASE = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434";
const DEFAULT_MODEL = process.env.LOCAL_LLM_DEFAULT_MODEL ?? "qwen2.5-coder:7b";
const REQUEST_TIMEOUT_MS = 15 * 60 * 1000; // local models can be slow on first load

const server = new McpServer({ name: "local-llm", version: "1.0.0" });

server.registerTool("local_llm_generate", {
  title: "Local LLM Generate",
  description: "Send a prompt to a local model running in Ollama (on this Mac) and return its response. " +
    "Use this to offload a well-scoped subtask (code generation, writing, refactor) to a local model " +
    "instead of doing it yourself. Call local_llm_list_models first if unsure which models are available.",
  inputSchema: {
    prompt: z.string().describe("The task/prompt to send to the local model"),
    model: z.string().optional().describe(`Ollama model tag to use, e.g. "qwen2.5-coder:32b". Defaults to "${DEFAULT_MODEL}".`),
    system: z.string().optional().describe("Optional system prompt to steer the local model"),
    temperature: z.number().min(0).max(2).optional(),
  },
}, async ({ prompt, model, system, temperature }) => {
  const messages = [];
  if (system) messages.push({ role: "system", content: system });
  messages.push({ role: "user", content: prompt });

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

  try {
    const res = await fetch(`${OLLAMA_BASE}/api/chat`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ model: model ?? DEFAULT_MODEL, messages, stream: false,
        ...(temperature !== undefined ? { options: { temperature } } : {}) }),
      signal: controller.signal,
    });
    if (!res.ok) {
      const body = await res.text();
      return { isError: true, content: [{ type: "text", text: `Ollama returned ${res.status}: ${body}` }] };
    }
    const data = await res.json();
    return { content: [{ type: "text", text: data.message?.content ?? "" }] };
  } catch (err) {
    const reason = err.name === "AbortError" ? `Timed out after ${REQUEST_TIMEOUT_MS / 1000}s`
      : err.cause?.code === "ECONNREFUSED" ? "Could not connect to Ollama on localhost:11434, is it running? (`brew services start ollama`)"
      : String(err);
    return { isError: true, content: [{ type: "text", text: reason }] };
  } finally {
    clearTimeout(timer);
  }
});

server.registerTool("local_llm_list_models", {
  title: "List Local LLM Models",
  description: "List the Ollama models currently pulled and available on this Mac.",
  inputSchema: {},
}, async () => {
  try {
    const res = await fetch(`${OLLAMA_BASE}/api/tags`);
    const data = await res.json();
    const lines = (data.models ?? []).map(m => `${m.name} (${(m.size / 1e9).toFixed(1)} GB)`);
    return { content: [{ type: "text", text: lines.length ? lines.join("n") : "No models pulled yet, run `ollama pull `." }] };
  } catch (err) {
    return { isError: true, content: [{ type: "text", text: `Could not reach Ollama: ${String(err)}` }] };
  }
});

const transport = new StdioServerTransport();
await server.connect(transport);

Register it with Claude Code at the user scope, so it’s available in every project on the machine rather than just one repo:

claude mcp add --scope user local-llm -- node ~/mcp-servers/local-llm/server.js

Confirm it’s live:

claude mcp list
# local-llm: node ~/mcp-servers/local-llm/server.js - ✔ Connected

From here on, Claude Code can call local_llm_list_models and local_llm_generate as tools in any session on this machine, offloading well-scoped work to Ollama for free instead of spending paid tokens on it.

Step 5 of 10: Add a second MCP server for local voice generation

Text generation was the first thing worth bridging into Claude Code as an MCP tool. Voice was the second. Chatterbox is Resemble AI’s open-weight voice model, and like Ollama, it runs entirely on the Mac Mini’s own GPU, no API key, no per-character billing, using Apple’s Metal Performance Shaders backend through PyTorch. Wrapped in its own MCP server, it gives Claude Code the ability to narrate a passage, clone a voice from a short reference clip, or generate a full multi-speaker podcast episode without leaving the terminal session.

Install the Python side first. A virtual environment keeps its dependencies (PyTorch, torchaudio) isolated from anything else on the machine ffmpeg is needed later to mux a finished episode into an mp4:

brew install ffmpeg
mkdir -p ~/mcp-servers/chatterbox-tts
cd ~/mcp-servers/chatterbox-tts
python3 -m venv .venv
source .venv/bin/activate
pip install chatterbox-tts torch torchaudio

Cloned voices are configured once in voices/voices.json: a key, a short reference WAV clip of that voice, and a couple of generation parameters (exaggeration for emotional intensity, cfg_weight for pacing/guidance):

{
  "DOUGLAS_KARR": {
    "reference_audio": "reference_audio/douglas_karr.wav",
    "exaggeration": 0.45,
    "cfg_weight": 0.5
  },
  "HOST_B": {
    "reference_audio": "reference_audio/host_b.wav",
    "exaggeration": 0.5,
    "cfg_weight": 0.5
  }
}

The server itself follows the same Node/MCP SDK pattern as Step 4, with one difference: loading a voice model fresh on every call would add many seconds of latency to every single tool invocation, so server.js spawns a persistent Python worker process on startup and keeps it warm, communicating over newline-delimited JSON on stdin/stdout. That worker exposes three tools to Claude Code:

  • tts_list_voices: lists the configured voice keys and their persona notes, so Claude can check what’s available before generating anything
  • tts_generate: synthesizes a single line or passage to a WAV file, using a configured voice key, a one-off reference clip, or the default built-in voice
  • tts_make_podcast: takes a full multi-speaker dialogue script (SPEAKER: text lines) and returns a stitched, loudness-normalized episode, a WAV plus an mp4 with a waveform video track, cloning each speaker’s voice in one call

Same registration pattern as before, a separate MCP server at the user scope:

claude mcp add --scope user chatterbox-tts -- node ~/mcp-servers/chatterbox-tts/server.js
claude mcp list
# local-llm: node ~/mcp-servers/local-llm/server.js - ✔ Connected
# chatterbox-tts: node ~/mcp-servers/chatterbox-tts/server.js - ✔ Connected

In practice this gets used two ways: narrating a paragraph out loud to sanity-check tone before it ships, and handing Claude Code a full interview-style script for this site’s own audio content, cloned voices, mixed and loudness-normalized, no separate recording session. One detail is worth calling out as a reusable technique on its own: the tool description for tts_generate includes a standing instruction that one of the configured voices is an explicitly AI co-host persona and must always be introduced as such, never written as a human.

Because that rule lives in the tool’s own description rather than in a prompt you’d have to remember to repeat, Claude follows it automatically every time the tool is called. Baking house rules into the MCP tool description itself, instead of into your own memory, is a pattern worth reusing anywhere a tool has a constraint that must never lapse.

Step 6 of 10: Add a Third MCP Server for Local Image Generation and Vectorization

Text and voice were the first two jobs bridged into Claude Code as MCP tools. Images and diagrams are the third. This one leans on two separate local models behind a single MCP server: FLUX.1 Schnell, run through ComfyUI on the same Metal Performance Shaders backend Ollama and Chatterbox already use, for generating images from a prompt, and StarVector for converting a finished raster image into a clean vector SVG. Same GPU, same no-per-image-billing story as the other two.

Install ComfyUI and the StarVector daemon into their own isolated environment, the same pattern as Step 5:

mkdir -p ~/mcp-servers/image-gen
cd ~/mcp-servers/image-gen
python3 -m venv .venv
source .venv/bin/activate
pip install comfy-cli starvector torch torchvision

# Pull the ComfyUI workflow and the FLUX.1 Schnell checkpoint the first time
comfy install
comfy model download --url https://huggingface.co/black-forest-labs/FLUX.1-schnell

The MCP server itself is the same Node/MCP SDK shape as Steps 4 and 5: it talks to ComfyUI’s local API for generation and to the StarVector daemon for conversion, spawning and keeping both backing processes warm rather than reloading models on every call. Register it at the user scope like the other two:

claude mcp add --scope user image-gen -- node ~/mcp-servers/image-gen/server.js
claude mcp list
# local-llm: node ~/mcp-servers/local-llm/server.js - ✔ Connected
# chatterbox-tts: node ~/mcp-servers/chatterbox-tts/server.js - ✔ Connected
# image-gen: node ~/mcp-servers/image-gen/server.js - ✔ Connected

That gives Claude Code two more tools:

  • image_generate: runs FLUX.1 Schnell via ComfyUI on the Mini’s GPU, with a style parameter (marketing, diagram, or icon) for on-brand flat and vector-style graphics; it’s not built for photorealism, and isn’t used for it.
  • svg_convert: runs StarVector to turn a raster image into vector SVG output, useful for cleaning up icons, logos, and simple flat graphics into an editable, infinitely scalable format.

Both backing processes, ComfyUI and the StarVector daemon, auto-start on the first call in a session rather than running all the time, so the very first request after a restart is slower while the models load into memory; every call after that is fast. In practice this covers a narrower but still real slice of work: a quick on-brand hero graphic or icon set for a post, an architecture-style diagram to accompany a technical walkthrough (like the one earlier in this guide), or vectorizing a logo asset someone handed over as a flat PNG, all without opening a design tool or paying per image.

Step 7 of 10: Install Tmux for Sessions That Never Die

tmux is the piece that actually delivers on close the laptop lid, work keeps going. It’s a terminal multiplexer: you start a session over SSH, and that session keeps running on the server even after you disconnect. You reattach to it later, from the same laptop, a different laptop, or a phone, and it’s exactly where you left it.

brew install tmux

The workflow is three commands you’ll use constantly:

# Start a new named session
tmux new -s claude-loop

# ...run Claude Code, kick off a long job, then detach without killing it:
# press Ctrl-b, then d

# Reattach later, from anywhere you can SSH in
tmux attach -t claude-loop

# List all running sessions
tmux ls

This is the mechanism, not Tailscale or Termius: those two just get you to the point where you can type tmux attach. tmux is what makes the session survive the trip.

Step 8 of 10: Install Tailscale for secure remote access

Tailscale is a mesh VPN built on WireGuard. It puts every device you own (the Mac Mini, your laptop, your phone) on the same private virtual network, with a stable address for each, no matter what Wi-Fi or cellular network they’re actually on. No port-forwarding on your Eero, no exposing SSH to the open internet.

On the Mac Mini:

brew install tailscale
sudo tailscale up

That opens a browser link to authenticate the device against your Tailscale account (their free tier comfortably covers a personal setup like this). Once it’s authenticated, the Mini gets a stable address on your private tailnet and a name; in this setup, it shows up as myserv. Check its status any time with:

tailscale status

Now install Tailscale on your laptop and your phone too, and sign into the same account. All three devices land on the same private network, and you can reach the Mini by its Tailscale name from anywhere (home Wi-Fi, a coffee shop, cellular data) with the exact same security posture as if you were on your own LAN, because you effectively are.

Why not just port-forward SSH? Because that exposes port 22 to the entire internet and turns your home network into a target for credential-stuffing bots. Tailscale’s mesh approach means the Mini is never listening on a public IP at all; every connection is authenticated device-to-device over WireGuard.

Step 9 of 10: Install Termius so You Can Drive it From Anywhere

Termius is an SSH client available on macOS, iOS, Android, and Windows, with your host list synced across all of them. Combined with Tailscale, this is what turns I can technically SSH into my home server into I actually do, from my phone, standing in line.

  1. Install Termius on your laptop and on your phone (App Store / Play Store).
  2. Sign into the same Termius account on both so hosts sync automatically.
  3. Add a new host pointing at the Mini’s Tailscale name or address (e.g. myserv), port 22, with your Mac account username and either your SSH key or password.
  4. Save it, and from now on it’s a single tap to open a full terminal session on the Mini, from either device, from anywhere Tailscale can reach.

This is the piece that actually delivers the “close the laptop” promise in daily life: you don’t need the laptop at all to check on a job. Open Termius on your phone, tap the host, tmux attach, see exactly where the loop is, detach again, done.

Step 10 of 10: Put it All Together: The Daily Workflow

With all eight pieces installed, here’s what actually running this setup looks like day to day:

  1. Connect. Open Termius on your laptop or phone, tap the myserv host, reachable instantly over Tailscale, regardless of what network you’re on.
  2. Attach or start a session. tmux attach -t claude-loop to rejoin work in progress, or tmux new -s claude-loop to start fresh.
  3. Run Claude Code. Kick off your session as normal, on whichever model in the Claude 5 lineup fits the work. When the work includes mechanical, well-scoped subtasks, Claude reaches for local_llm_generate and hands them to the Mini’s own Ollama models; when it needs narration or a podcast episode, it reaches for tts_generate or tts_make_podcast; when it needs a graphic, icon, diagram, or a vectorized logo, it reaches for image_generate or svg_convert instead, all without spending your paid tokens on any of it.
  4. Detach and walk away. Ctrl-b, then d. Close the laptop, put the phone in your pocket. The session, and the Mini, keep running. No sleep, no dropped connection, no lost state.
  5. Check in from anywhere. Later, from any device, Termius, Tailscale, tmux attach, and you’re back exactly where you left off.

What You Have Running Now

Following the ten steps above, you now have:

  • A dedicated, headless Mac Mini that never sleeps, hardwired into your network over Ethernet rather than Wi-Fi
  • Three local language models pulled and served locally through Ollama, with zero per-token cost
  • An MCP bridge that lets Claude Code offload well-scoped coding and writing work to those local models mid-session, automatically
  • A second MCP server for local, cloned-voice generation, from a single narrated line to a fully mixed multi-speaker podcast episode
  • A third MCP server for local image generation and raster-to-SVG vectorization, for on-brand graphics, icons, and diagrams with no per-image billing
  • tmux sessions that survive disconnects, laptop lids closing, and network changes entirely
  • A private Tailscale mesh network connecting your server, laptop, and phone with no exposed ports and no port-forwarding
  • Termius set up on every device so reaching the server is a single tap, from anywhere

The net result is a second machine that does the waiting for you: the long loops, the overnight jobs, the mechanical coding and writing subtasks, the audio generation, the image and diagram generation, while your primary device, and your token budget, stay free for the work that actually needs judgment. For a one-time hardware cost, that’s not a convenience. It’s added capacity.

Of course, as open-source models continue to excel… you’ll be able to add whatever models you need without overloading your Mac Mini. And if you do, you can move up to a Mac Studio! The models I presented here are the latest ones that seem widely accepted… that list could change tomorrow, though.

Related Articles