# Home Lab

> A private home fleet that backs up personal data, runs coding agents, filters DNS, monitors itself, and gives old hardware a useful job

- HTML version: https://robbiepalmer.me/projects/homelab
- Status: live
- Started: 2026-08-02
- Updated: 2026-09-09
- Technologies: Ansible, Tailscale, AdGuard Home, Hotspot Shield, t3-code, Claude Code, Codex, Grok Build, opencode, Ente, DVC, Netdata, NixOS, K3s, Terraform, Hetzner Cloud, CUPS, Docker, Jellyfin, Prowlarr, Sonarr, Radarr, qBittorrent, Recyclarr, Trakt, Colima

Read the shorter [project pitch deck](https://robbiepalmer.me/projects/homelab/deck.md).

# Vision

A small, mostly always-on fleet that handles backups, remote development,
network privacy, and monitoring without relying on me to remember each job.

The home lab exists to serve four goals, in priority order:

1. **Data backup and coordination.** The lab syncs and backs up cloud photo
   libraries, datasets, and project files. Manual drive synchronisation has
   failed repeatedly. The machines now own that work.
2. **Agentic development environments.** Always-available machines host my
   coding harnesses (Claude Code, Codex, OpenCode, Grok Build) behind a
   mobile-friendly GUI, so I can drive real development work from my phone over
   the tailnet even when the home power or broadband is unavailable.
3. **Privacy.** Network-wide ad and tracker blocking keeps my devices from
   phoning home to trackers, applied to every device on the LAN regardless of
   what it runs.
4. **Avoiding e-waste.** A 2011 printer, a Pascal-era GPU, and three old phones
   get useful jobs instead of going in a drawer or to landfill.

The lab is also where I learn networking, DNS, and orchestration across macOS,
Linux, Android, and iOS. Every experiment still has to solve a real problem.

Core services are reachable from my personal devices over the
[Tailscale](/projects/homelab/adrs/000-tailscale) tailnet, so I can inspect and
drive the lab without exposing it to the public internet.

# Problem statement

The problems the lab solves are the recurring ones that manual effort handles
badly:

**Manual data sync is painful and lossy.** Copying photos and files between
drives by hand is easy to forget and easy to get wrong. It also leaves no
audit trail. The cloud photo library has grown into the single source of truth
for family photos, which makes it a single point of failure.

**Agentic development needs a persistent host.** Coding agents are most useful
when they run somewhere always-on with the right repos, credentials, and tools
already prepared. A laptop that sleeps when closed can't drive a session from
a phone. Mobile "cloud" agent environments exist, but they re-bootstrap
context each time, and they're slower, less reliable, and burn more tokens
than a prepared local environment.

**Home devices make a lot of tracking DNS traffic.** Without network-wide
filtering, apps and embedded trackers on phones, TVs, and streaming boxes
resolve ad and analytics domains on every device on the LAN. Fixing this at
the DNS layer covers devices nothing else can be installed on. This adds a
dependency (the resolver), so it has to be built with redundancy from the
start.

**Unsupported hardware can still do narrow jobs well.** The Canon MX3100
printer, GTX 1050, and old phones all work. Their vendors have moved on, but
open drivers and careful isolation can keep the hardware useful. Buying new
kit would solve a software problem with more hardware.

# User stories

* As the owner of thousands of family photos, I want them automatically
  synced from the cloud down to a local 10TB drive, so I have an offline
  backup that doesn't depend on a subscription or the internet.
* As a developer working away from my desk, I want to open the home hub's
  agent GUI on my phone and have working repos, credentials, and a choice of
  agents, so I can keep shipping from the sofa.
* As someone with two Codex subscriptions, I want both managed in one place,
  so I can use each plan's allowance and pick the best-fitting one per task.
* As a developer during a home power or broadband outage, I want an off-site
  t3-code environment already running, so coding capacity is available over a
  modest mobile connection without bootstrapping a laptop locally.
* As a household with a home router, I want ad and tracker blocking applied
  network-wide with automatic failover, so every device is protected even if
  one DNS box reboots.
* As someone who owns a 2011 printer, I want to print from my phone remotely
  over CUPS, so I keep using hardware that still works and avoid e-waste.
* As someone running a NixOS box, I want the whole config declared in git, so
  an upgrade can never silently break the GPU and every change is reviewable
  and rollbackable.
* As the lab owner, I want Netdata to tell Slack when something is
  overheating, down, or hung, so I find out about problems before they matter.
* As a web developer, I want agents to test changes on real Android 10, newer
  Android, and iOS 15 hardware, so mobile regressions are caught before
  a pull request merges.
* As the person who operates the DNS servers, I want a battery-backed device
  on mobile data to check the lab from outside its own network, so a total DNS,
  power, or internet outage can still raise an alert.
* As someone deciding what to watch from the sofa, I want my Trakt watchlist
  taps to become Jellyfin library entries automatically, so choosing media
  never involves an admin panel.

# Topology

## System context

Personal and household devices reach the lab over its private network. The lab
pulls photos from Ente, works with code and agent providers, follows a Trakt
watchlist to acquire media, and sends operational alerts to Slack.

```mermaid
flowchart TB
Owner["Lab owner"]
Household["Household"]
Lab["Home lab"]
Cloud["Ente"]
Code["GitHub and<br/>AI providers"]
Alerts["Slack"]
Trakt["Trakt"]

Owner -->|"Manages"| Lab
Household -->|"DNS and printing"| Lab
Cloud -->|"Photos"| Lab
Trakt -->|"Watchlist taps"| Lab
Lab -->|"Viewing history"| Trakt
Lab -->|"Code and agents"| Code
Lab -->|"Alerts"| Alerts

classDef person fill:#7c3aed,color:#fff,stroke:#6d28d9
classDef system fill:#2563eb,color:#fff,stroke:#1d4ed8
classDef external fill:#475569,color:#fff,stroke:#334155
class Owner,Household person
class Lab system
class Cloud,Code,Alerts,Trakt external
```

## Containers

### Interactive services

Tailscale is the private entry point for DNS, development, media, and printing.

```mermaid
flowchart TB
People["Owner and household devices"]

subgraph lab["Home lab"]
Access["Tailscale<br/>Private access network"]
DNS["AdGuard Home<br/>Filtered DNS"]
Dev["t3-code and coding agents<br/>Development workspace"]
Media["Jellyfin<br/>Media server"]
Print["CUPS<br/>Print server"]
end

People --> Access
Access --> DNS
Access --> Dev
Access --> Media
Access --> Print
DNS ~~~ Dev ~~~ Media ~~~ Print

classDef external fill:#475569,color:#fff,stroke:#334155
classDef service fill:#0f766e,color:#fff,stroke:#115e59
class People external
class Access,DNS,Dev,Media,Print service
```

### Data and monitoring services

Ente sync and Jellyfin share local storage. The media automation stack pulls a
Trakt watchlist into that storage, and Jellyfin scrobbles plays back so
recommendations improve. Netdata sends operational alerts to Slack, while the
development workspace talks to GitHub and the agent APIs.

```mermaid
flowchart TB
Ente["Ente cloud"] --> Backup["Ente sync<br/>Photo backup"]
Backup --> Storage[("Local storage")]
Storage --> Media["Jellyfin<br/>Media server"]
Tracker["Trakt"] -->|"Watchlist"| Auto["Media automation<br/>Prowlarr, Sonarr,<br/>Radarr, qBittorrent"]
Auto -->|"Downloads"| Storage
Media -->|"Scrobbles"| Tracker
Monitor["Netdata<br/>Monitoring"] --> Slack["Slack"]
Dev["t3-code<br/>Coding agents"] --> Code["GitHub and<br/>AI providers"]

Backup ~~~ Monitor ~~~ Dev

classDef external fill:#475569,color:#fff,stroke:#334155
classDef service fill:#0f766e,color:#fff,stroke:#115e59
classDef data fill:#4338ca,color:#fff,stroke:#3730a3
class Ente,Slack,Code,Tracker external
class Backup,Media,Monitor,Dev,Auto service
class Storage data
```

## Deployment

### Device context

The Mac mini is the home hub. The Raspberry Pi keeps DNS available during hub
maintenance and owns the printer connection.

```mermaid
flowchart TB
Router["Home router"]
Internet["Internet"]
Slack["Slack"]
Printer["Canon MX3100"]
FireTV["Fire TV Stick"]
Drive[("10TB HDD")]

Mini["Mac mini<br/>Home hub"]
Pi["Raspberry Pi<br/>Fallback host"]

Router -->|"Primary DNS"| Mini
Router -->|"Fallback DNS"| Pi
Pi -->|"Metrics"| Mini
Mini --> Drive
Mini --> FireTV
Mini --> Slack
Mini -->|"VPN egress"| Internet
Pi --> Printer

classDef live fill:#0f766e,color:#fff,stroke:#115e59
classDef data fill:#4338ca,color:#fff,stroke:#3730a3
class Mini,Pi live
class Drive data
```

### Mac mini containers

The Mac mini hosts the lab's main services. Hotspot Shield routes its outbound
traffic through an encrypted tunnel, and a launchd supervisor only starts the
media automation stack while that tunnel is up. Inside the stack, Prowlarr
feeds indexers to Sonarr and Radarr, which grab releases through qBittorrent
and import finished downloads into the library; Recyclarr keeps their quality
profiles aligned with the TRaSH guides. A Trakt watchlist feeds both arrs, and
Jellyfin scrobbles plays back.

```mermaid
flowchart TB
subgraph mini["Mac mini"]
DNS["AdGuard Home<br/>Primary DNS"]
VPN["Hotspot Shield<br/>All outbound traffic"]
Dev["t3-code<br/>Coding agents"]
Backup["Ente sync<br/>Photo backup"]
Monitor["Netdata<br/>Parent node"]
KeepAlive["keep-running agent<br/>Stack supervisor"]
Media["Jellyfin<br/>Media server"]
Storage[("10TB HDD")]

subgraph media["Media automation (Docker Compose)"]
Prowlarr["Prowlarr<br/>Indexer proxy"]
Sonarr["Sonarr<br/>TV"]
Radarr["Radarr<br/>Movies"]
QBit["qBittorrent<br/>Downloads"]
Recyclarr["Recyclarr<br/>TRaSH profiles"]
end
end

Trakt["Trakt<br/>Watchlist and tracking"]

DNS ~~~ VPN ~~~ Dev ~~~ Backup ~~~ Monitor
KeepAlive -.->|"Starts only while<br/>the tunnel is up"| media
Backup --> Storage
Media --> Storage
Media --> FireTV["Fire TV Stick"]

Trakt -->|"Watchlist"| Sonarr
Trakt -->|"Watchlist"| Radarr
Media -->|"Scrobbles"| Trakt
Prowlarr -->|"Indexers"| Sonarr
Prowlarr -->|"Indexers"| Radarr
Recyclarr -->|"Profiles"| Sonarr
Recyclarr -->|"Profiles"| Radarr
Sonarr -->|"Releases"| QBit
Radarr -->|"Releases"| QBit
QBit -->|"Completed downloads"| Storage

DNS -->|"Quad9 DNS"| VPN
Dev -->|"Code and agent APIs"| VPN
Backup -->|"Ente sync"| VPN
Monitor -->|"Slack alerts"| VPN
VPN --> Internet["Internet"]

classDef service fill:#0f766e,color:#fff,stroke:#115e59
classDef supervisor fill:#b45309,color:#fff,stroke:#92400e
classDef data fill:#4338ca,color:#fff,stroke:#3730a3
classDef external fill:#475569,color:#fff,stroke:#334155
class DNS,VPN,Dev,Backup,Media,Monitor,Prowlarr,Sonarr,Radarr,QBit,Recyclarr service
class KeepAlive supervisor
class Storage data
class Trakt external
```

### Raspberry Pi containers

The Pi runs the fallback DNS resolver, sends metrics to the Mac mini, and
connects the old Canon printer to the network.

```mermaid
flowchart LR
subgraph pi["Raspberry Pi"]
DNS["AdGuard Home<br/>Fallback DNS"]
Monitor["Netdata<br/>Child node"]
Print["CUPS<br/>Print server"]
end


DNS -->|"Syncs config"| Primary["Primary DNS<br/>Mac mini"]
Monitor -->|"Sends metrics"| Hub["Netdata parent<br/>Mac mini"]
Print --> Printer["Canon MX3100"]

classDef service fill:#0f766e,color:#fff,stroke:#115e59
class DNS,Monitor,Print service
```

### Proposed devices and remote development

The Mac mini already sends jobs to the Asus desktop over the tailnet. It will
wake the 4080 laptop for GPU jobs and run browser tests on the phone lab. The
View 20 will check the live hosts over cellular so it can still report a home
power or internet failure. An independent cloud development plane remains
reachable when every device at home is offline.

```mermaid
flowchart TB
Mini["Mac mini<br/>Home hub"]
Pi["Raspberry Pi<br/>Fallback host"]
Worker["Asus desktop<br/>NixOS and GTX 1050"]
Laptop["Laptop<br/>RTX 4080 compute"]
Cache[("1TB DVC cache")]
Phones["Phone lab<br/>Android and iOS QA"]
Watchdog["View 20<br/>Cellular watchdog"]
Remote["Hetzner VPS<br/>NixOS and independent K3s"]
Slack["Slack"]
GitHub["GitHub<br/>Branches and PRs"]

Mini -->|"GPU jobs"| Worker
Mini -->|"GPU jobs"| Laptop
Worker --> Cache
Mini -->|"Browser tests"| Phones
Watchdog -->|"Checks"| Mini
Watchdog -->|"Checks"| Pi
Watchdog -->|"Alerts"| Slack
Remote -->|"Cloud t3-code"| GitHub
Mini -->|"Home t3-code"| GitHub

classDef live fill:#0f766e,color:#fff,stroke:#115e59
classDef proposed fill:#fef3c7,color:#78350f,stroke:#d97706,stroke-dasharray:5 5
classDef data fill:#4338ca,color:#fff,stroke:#3730a3
class Mini,Pi,Worker live
class Laptop,Phones,Watchdog,Remote proposed
class Cache data
```

# Nodes

## Mac mini, the home hub

The Mac mini is the primary always-on machine. It hosts the coding agents and
owns the photo backup pipeline, the lab's two most important jobs.

* **Agentic development hub.** It runs [Claude Code](/projects/homelab/adrs/002-claude-code),
  [Codex](/projects/homelab/adrs/003-codex) (both subscriptions),
  [OpenCode](/projects/homelab/adrs/005-opencode) and
  [Grok Build](/projects/homelab/adrs/004-grok-build), all orchestrated from
  [t3-code](/projects/homelab/adrs/006-t3-code) so I can drive remote,
  mobile-friendly, agentic development.
* **Photo backup.** It connects to [Ente](/projects/homelab/adrs/007-ente-photo-backup),
  the end-to-end encrypted cloud photo provider, and regularly syncs my
  library down to a connected 10TB HDD as an offline backup.
* **DNS privacy.** It runs [AdGuard Home](/projects/homelab/adrs/001-adguard-home)
  as the primary DNS server that my router points at. It blocks \~33% of all
  requests over my home network. AdGuard is also reachable over the tailnet,
  so the phone keeps blocking trackers and ads on cellular or any other
  network. A router-level setup cannot provide that coverage.
* **Always-on VPN egress.** The mini runs
  [Hotspot Shield](https://www.hotspotshield.com) as an always-on client, so
  every outbound connection leaves through its tunnel and the ISP sees one
  encrypted stream instead of individual destinations. This forced one config
  change elsewhere: Hotspot Shield kills TLS connections to known
  encrypted-DNS endpoints, so AdGuard's DNS-over-HTTPS upstream stopped
  resolving under the tunnel. The upstreams are now plain UDP Quad9
  (9.9.9.9), which passes through untouched. LAN devices keep their filtering
  either way; only the mini's own traffic is tunnelled.
* **Monitoring.** It runs [Netdata](/projects/homelab/adrs/009-netdata),
  connected to my Slack workspace via a Slack app, to alarm on anything
  going wrong. The media stack feeds it per-service health gauges through
  its StatsD listener, and the supervisor that keeps that stack alive
  restarts containers Docker marks unhealthy, the hung-process case a
  restart policy can never catch.
* **Media.** It runs [Jellyfin](/projects/homelab/adrs/011-jellyfin) to serve
  TV shows and movies to my Fire TV Stick, in Docker Compose under colima
  with its config declared in `homelab/hosts/mac-mini/jellyfin/` in this
  repo. The library lives on the 10TB HDD and is served over the LAN and the
  tailnet only.
* **Media acquisition.** It runs an automated
  [arr stack](/projects/homelab/adrs/016-media-automation-arr-stack),
  [Prowlarr](/projects/homelab/adrs/017-prowlarr-indexer-management),
  Sonarr, Radarr, [qBittorrent](/projects/homelab/adrs/018-single-containerized-torrent-client),
  and [Recyclarr](/projects/homelab/adrs/019-recyclarr-trash-guides), in
  Docker Compose under colima, supervised by a launchd agent that only starts
  it while the VPN tunnel is up ([ADR 020](/projects/homelab/adrs/020-vpn-gated-stack)).
  Trakt watchlist taps become monitored titles, and finished downloads land
  straight in Jellyfin ([ADR 021](/projects/homelab/adrs/021-trakt-watchlist)).

## Remote development plane, proposed

[ADR 025](/projects/homelab/adrs/025-cloud-remote-development-plane) proposes
a long-lived Hetzner VPS so a home power or broadband outage does not remove
all coding capacity. Terraform provisions it, the repository's NixOS flake
declares the host, and an independent single-node K3s cluster runs t3-code.

It does not extend the home cluster across the internet. Home and cloud share
Kustomize bases where useful, but retain separate control planes, overlays,
volumes, authentication homes, and t3-code state. GitHub branches and pull
requests are the handoff between them. Tailscale is the only application access
path; t3-code and the K3s API remain closed to the public internet.

## Raspberry Pi, the independent fallback

A secondary node that removes single points of failure from the two things
the whole house depends on: DNS and monitoring.

* **Secondary AdGuard Home.** The home router also points at the Pi, so if
  either DNS box is down (e.g. during reboots) the other keeps the network
  functioning.
* **Netdata child.** Exports metrics to the Mac mini as the main hub, and
  alerts my Slack workspace if its temperature climbs too high.
* **CUPS print server.** It connects CUPS to my 2011 Canon MX3100 printer.
  Canon no longer supports the printer, but Gutenprint lets every device on
  the network use it, including my phone over Tailscale.

## Asus desktop, the NixOS GPU worker

A headless batch-CV box, configured declaratively with
[NixOS](/projects/homelab/adrs/010-nixos-gpu-worker). The host config lives in
`homelab/hosts/asus-desktop` in this repo, defined by a flake locked to the
pinned nixpkgs channel and deployed with `nixos-rebuild`.

* **Old, unsupported 1050 GPU.** The GTX 1050 is Pascal-era (`sm_61`) with
  2GB of VRAM. CUDA 13 and the newer driver branches dropped Pascal, so the
  flake pins nixos-25.11, the 6.12 LTS kernel, and the 580 driver branch
  (580.142, closed kernel modules, since the open modules need Turing or
  newer).
* **Batch GPU CV jobs.** It will run batch computer vision jobs. This workload
  needs many GPU-hours, not a modern GPU.
* **DVC data cache.** It inherits the
  [DVC ADR](/projects/homelab/adrs/008-dvc) and its 1TB HDD is mounted at
  `/data` (916G free), holding local copies of datasets for the ML pipelines
  (`ml-pipelines/recipe-parsing/`) and any CV work.
* **Reachable over the tailnet.** The desktop has no ethernet run to the
  router, so its uplink is an Honor Magic5 Pro over USB tethering. That link
  drops when the box sleeps and can't carry a WoL magic packet, so
  wake-on-LAN and sleep-on-idle wait on a pair of powerline ethernet
  adapters. The NixOS config already carries the WoL link policy for that
  day. Until then the box stays awake and is managed over Tailscale.

## Future 4080 laptop

I have an idle laptop with a 4080 GPU in it. Once the NixOS box proves out
the wake-on-demand pattern, the laptop is a natural candidate for a second,
much faster compute node, particularly for heavier inference or training that
the 1050 can't handle.

## Phone device lab, proposed

An Honor View 20, Honor Magic5 Pro, and iPhone 6s will become physical test
targets for the coding agents. Wired connections to the Mac mini provide the
most reliable control. Together they cover an old Android release, a newer
Android release, and iOS 15 WebKit.

The [phone device lab ADR](/projects/homelab/adrs/015-phone-device-lab) also
proposes a second job for the View 20. A cheap data SIM, Tailscale, and a small
Termux watchdog would let it check both DNS servers and the hub from cellular.
That closes the monitoring gap where Netdata cannot send a Slack alert because
the network or DNS it depends on has failed.

The phones will keep their stock operating systems. Neither Honor has a useful
supported custom-ROM path, and Linux on the iPhone 6s is still an experiment.
They will not become DNS servers, general compute nodes, or permanent cameras.

# Plan

The lab is being built incrementally, starting from the highest-value
workloads and layering on the rest:

| Phase | Workload                                                      | Status   |
| ----- | ------------------------------------------------------------- | -------- |
| 1     | Photo backup (Ente → 10TB HDD)                                | Live     |
| 2     | Agentic development hub (t3-code + agents)                    | Live     |
| 3     | DNS privacy (AdGuard Home, primary + failover)                | Live     |
| 4     | CUPS printing on the Pi (avoiding printer e-waste)            | Live     |
| 5     | NixOS GPU worker for batch CV jobs, with DVC-managed datasets | Live     |
| 6     | Jellyfin media server for the Fire TV Stick                   | Live     |
| 7     | Physical Android and iOS device lab                           | Proposed |
| 8     | Cellular watchdog on the View 20                              | Proposed |
| 9     | Second compute node (idle 4080 laptop)                        | Idea     |
| 10    | Automated media acquisition (arr stack behind the VPN gate)   | Live     |
| 11    | Trakt watchlist recommendation loop into Jellyfin             | Live     |
| 12    | Off-site t3-code environment on an independent NixOS/K3s VPS  | Proposed |

# What this is not

* **Not a public service.** Everything is behind the tailnet. No ports
  forwarded to the internet, no exposed dashboards.
* **Not a data hoard.** Bulk data has a purpose and an owner: the photo
  backup, local dataset copies, and planned media library. Backup scope may
  grow to documents, games, movies, TV, and music, but each addition has to
  earn its place.
* **Not a reason to turn every device into a server.** The phones are useful
  because they are phones. The Mac mini and Pi already run persistent
  services better.
* **Not a fixed shape.** The lab started as a handful of machines doing
  boring jobs. A proper NAS or a compute scheduler may earn a place later.

# Future direction

These ideas remain outside the current build plan:

* **Offsite backup.** The 10TB drive is a second copy, but it's in the same
  house. A cheap offsite target (a friend's tailnet node, or cold storage)
  for the photo library would close the last gap in the backup story.
* **Expanded backup scope.** Documents, game saves, movies, TV, and music
  are candidates for the same sync-and-verify pipeline as the photos.
* **Dashboards on this site.** A public page could show selected Netdata
  graphs and lab status without exposing the private services behind them.
* **Heterogeneous orchestration.** If the lab grows past two compute nodes,
  scheduling work across the 1050 box and the 4080 laptop becomes a real
  orchestration problem. Kubernetes is one possible answer, but two machines
  do not justify it yet.

# Risks

## Home power removes the development environment

The Mac mini and broadband router share one power and network failure domain.
When both disappear, the agent subscriptions still exist but their prepared
host, repositories, authentication, and t3-code interface are unreachable.

**Mitigation.** ADR 025 proposes an independent cloud t3-code installation
behind Tailscale. The two installations keep separate mutable state and hand
work off through pushed Git branches. An outage drill must prove the cloud
environment works from mobile data before it is treated as continuity.

## DNS is a single point of failure for the whole house

Running a local resolver adds a dependency that didn't exist before. If both
AdGuard Home instances go down, every device in the house loses DNS and the
internet "stops working."

**Mitigation.** The router uses two independent DNS nodes on separate
hardware. The Pi keeps working during Mac mini reboots and vice versa. The
proposed View 20 watchdog will use mobile data to report a failure of both
resolvers.

## The VPN tunnel dies quietly after a reboot

Hotspot Shield ships as an App Store app, and its tunnel only comes back
after a user session starts. An unattended restart (power cut, forced update)
puts the mini back online with traffic egressing from the home IP until
someone logs in.

**Mitigation.** Netdata watches the mini's public IP and alerts Slack when it
falls outside the VPN exit range. Until that alert exists, treat the tunnel as
down after any unattended reboot.

## Photo backup silently failing

A sync pipeline that fails quietly means the library isn't backed up. Catching
that late is only slightly better than not having a backup at all.

**Mitigation.** Netdata monitors the sync process and alerts Slack on
failure or when the HDD fills up. The backup is treated as a system to be
monitored, not a script to be forgotten.

## Agent blast radius

The Mac mini holds credentials, plugins, and browser state for remote agent
sessions, and it also holds personal photos and the backup drive. A broad
permission profile on any one agent could expose unrelated personal data, and
an agent acting wrongly could delete things as easily as read them.

**Mitigation.** The [Codex ADR](/projects/homelab/adrs/003-codex) already
documents the need for least-privilege filesystem rules and sandboxing on
the connected host. This stays a first-class concern as more agents land on
the hub, with backups covering the "wrong deletion" case.

## Pascal never comes back

The 1050's driver support is a moving target that NixOS upgrades could
silently break.

**Mitigation.** The declarative NixOS config pins the driver, kernel, and CUDA
container versions in git. No surprise upgrade can break the GPU without a
reviewable, rollbackable diff.

## Old phones widen the trust boundary

The View 20 runs an old Android release, and the Mac mini will communicate with
all three phones through persistent USB debugging or inspection channels.
Treating them like fully trusted tailnet peers would give them more access than
their jobs require.

**Mitigation.** Give lab phones tagged Tailscale identities with access only
to preview sites and explicit health-check ports. Keep personal accounts,
photos, and general lab credentials off the View 20. USB debugging stays
authorized only for the Mac mini, and the cellular watchdog holds a separate,
narrowly scoped alert credential.

## Media server scope creep

Jellyfin is the lab's most consumer-facing service and the easiest one to
over-invest in.

**Mitigation.** Keep it as one container behind the tailnet, serving the Fire
TV Stick. Add complexity only to fix a playback or operational problem.

## Architecture Decision Records

- [ADR 000: Tailscale](https://robbiepalmer.me/projects/homelab/adrs/000-tailscale.md) — Accepted, 2026-08-02
- [ADR 001: AdGuard Home for DNS](https://robbiepalmer.me/projects/homelab/adrs/001-adguard-home.md) — Accepted, 2026-08-02
- [ADR 004: Grok Build](https://robbiepalmer.me/projects/homelab/adrs/004-grok-build.md) — Accepted, 2026-08-02
- [ADR 005: OpenCode](https://robbiepalmer.me/projects/homelab/adrs/005-opencode.md) — Accepted, 2026-08-02
- [ADR 006: t3-code as the agent orchestrator](https://robbiepalmer.me/projects/homelab/adrs/006-t3-code.md) — Accepted, 2026-08-02
- [ADR 007: Ente for photo backup](https://robbiepalmer.me/projects/homelab/adrs/007-ente-photo-backup.md) — Accepted, 2026-08-02
- [ADR 009: Netdata for monitoring](https://robbiepalmer.me/projects/homelab/adrs/009-netdata.md) — Accepted, 2026-08-02
- [ADR 010: NixOS GPU worker](https://robbiepalmer.me/projects/homelab/adrs/010-nixos-gpu-worker.md) — Accepted, 2026-08-02
- [ADR 011: Jellyfin media server](https://robbiepalmer.me/projects/homelab/adrs/011-jellyfin.md) — Accepted, 2026-08-15
- [ADR 012: CUPS for the unsupported printer](https://robbiepalmer.me/projects/homelab/adrs/012-cups.md) — Accepted, 2026-08-02
- [ADR 013: Resurrecting the first-gen Amazon Echo](https://robbiepalmer.me/projects/homelab/adrs/013-amazon-echo.md) — Rejected, 2026-08-02
- [ADR 014: Basic Memory and SilverBullet for an extensible agentic knowledge base](https://robbiepalmer.me/projects/homelab/adrs/014-basic-memory-silverbullet-agentic-knowledge-base.md) — Proposed, 2026-08-15
- [ADR 015: Old phones as a physical device lab and cellular watchdog](https://robbiepalmer.me/projects/homelab/adrs/015-phone-device-lab.md) — Proposed, 2026-08-22
- [ADR 016: Automated media acquisition](https://robbiepalmer.me/projects/homelab/adrs/016-media-automation-arr-stack.md) — Accepted, 2026-08-23
- [ADR 017: Prowlarr manages the indexers](https://robbiepalmer.me/projects/homelab/adrs/017-prowlarr-indexer-management.md) — Accepted, 2026-08-23
- [ADR 018: One torrent client, containerized](https://robbiepalmer.me/projects/homelab/adrs/018-single-containerized-torrent-client.md) — Accepted, 2026-08-23
- [ADR 019: Recyclarr keeps quality profiles on the TRaSH Guides](https://robbiepalmer.me/projects/homelab/adrs/019-recyclarr-trash-guides.md) — Accepted, 2026-08-23
- [ADR 020: the media stack waits for the VPN tunnel](https://robbiepalmer.me/projects/homelab/adrs/020-vpn-gated-stack.md) — Accepted, 2026-08-23
- [ADR 021: Trakt drives the watchlist](https://robbiepalmer.me/projects/homelab/adrs/021-trakt-watchlist.md) — Accepted, 2026-08-23
- [ADR 022: Ansible as a bridge to K3s](https://robbiepalmer.me/projects/homelab/adrs/022-ansible-k3s-migration-bridge.md) — Accepted, 2026-09-01
- [ADR 023: K3s for declarative homelab workloads](https://robbiepalmer.me/projects/homelab/adrs/023-k3s-declarative-workloads.md) — Proposed, 2026-09-01
- [ADR 025: Cloud remote-development plane](https://robbiepalmer.me/projects/homelab/adrs/025-cloud-remote-development-plane.md) — Proposed, 2026-09-04

---

Markdown index of this site: https://robbiepalmer.me/llms.txt
