diff --git a/CLAUDE.md b/CLAUDE.md index 3461a40..900f2fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,170 +1,98 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to Claude Code when working with code in this repository. ## Project Overview -This is a home server Docker infrastructure repository that orchestrates self-hosted services using Docker Compose behind a Traefik reverse proxy with Authelia SSO/2FA authentication. +Home server Docker infrastructure orchestrating self-hosted services via Docker Compose behind a Traefik reverse proxy with Authelia SSO/2FA. -## Common Commands - -```bash -# Start all services -docker compose up -d - -# Start specific service -docker compose up -d - -# View logs -docker compose logs -f +## Architecture -# Restart a service -docker compose restart +### Networks +- **t2_proxy** (192.168.90.0/24): Main network for Traefik and all web-accessible services +- **socket_proxy** (192.168.91.0/24): Isolated network for Docker Socket Proxy only -# Stop all services -docker compose down +Both networks are external — create before first run: +```bash +docker network create --gateway 192.168.90.1 --subnet 192.168.90.0/24 t2_proxy +docker network create --gateway 192.168.91.1 --subnet 192.168.91.0/24 socket_proxy ``` -## Architecture - -### Network Topology -- **t2_proxy** (192.168.90.0/24): Main network for Traefik and web-accessible services -- **socket_proxy** (192.168.91.0/24): Isolated network for Docker Socket Proxy access only - ### Security Stack -1. **Traefik** - Reverse proxy with automatic Let's Encrypt certificates via Cloudflare DNS challenge -2. **Docker Socket Proxy** - Restricts Docker API access (never expose Docker socket directly) -3. **Authelia** - SSO/2FA protecting external access via Traefik middleware chains - -### Storage Strategy -- **NFS volumes** (Synology NAS at 192.168.0.6): Service configurations for backup/persistence -- **Local NVME** (/usr/local/plex, /tmp/plex_transcode): High-performance storage for Plex transcoding - -### Service Categories -- **Infrastructure**: Traefik, Docker Socket Proxy, Authelia, Portainer, Task Scheduler -- **Media**: Plex (LinuxServer.io, GPU-enabled), Sonarr, Radarr, Bazarr, SABnzbd, NZBHydra2 -- **Books**: Calibre-Web (Frontend), Lazy Librarian (Manager) -- **DNS/Ad-blocking**: Pi-hole (port 53) -- **Home Automation**: Home Assistant (privileged, host network) -- **Dashboard**: Organizr - -## Key Configuration Files - -- `docker-compose.yml` - Main service definitions -- `env.example` - Required environment variables template (copy to `.env`) -- `acme/acme.json` - Let's Encrypt certificates (Traefik managed) -- `pihole/` - Pi-hole DNS configuration -- `task-scheduler/` - Scheduled maintenance tasks (Docker cleanup, Plex restart) +1. **Traefik** — Reverse proxy, Let's Encrypt certs via Cloudflare DNS challenge +2. **Docker Socket Proxy** — Restricts Docker API access (never expose the socket directly) +3. **Authelia** — SSO/2FA on all external-facing services via `chain-authelia@file` middleware + +### Storage +- **NFS volumes** (Synology at 192.168.0.6): Service configs and media libraries +- **Local NVME** (`/usr/local/plex`, `/tmp/plex_transcode`): Plex config and transcode scratch + +### Services + +| Category | Services | +|----------|----------| +| Infrastructure | Traefik, Socket Proxy, Authelia, Portainer, Task Scheduler | +| Media | Plex (GPU), Sonarr, Radarr, Bazarr, SABnzbd, NZBHydra2 | +| Collection Mgmt | Kometa (runs daily at 1 AM) | +| Books | Calibre-Web, Lazy Librarian | +| DNS | Pi-hole (host port 53) | +| Home Automation | Home Assistant (privileged, host network) | +| Dashboard | Organizr | +| Utilities | iSponsorBlockTV, LibreSpeed, Smokeping, Slideshow Updater | + +## Key Files + +- `docker-compose.yml` — All service definitions +- `env.example` — Required environment variables (copy to `.env`) +- `kometa/config.yml.example` — Kometa collection manager config template +- `scripts/validate-traefik.sh` — Post-change validation script +- `acme/acme.json` — Let's Encrypt certs (Traefik-managed, chmod 600) ## Environment Variables -Critical variables in `.env`: -- `DOMAINNAME` - Primary domain for all service subdomains -- `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY`, `CLOUDFLARE_ZONEID` - DNS challenge auth -- `DOCKERDIR` - NFS-mounted config directory -- `LOCALDOCKERDIR` - Local (non-NFS) docker directory -- `SYNOLOGYDIR` - Synology mount path for media volumes -- `HOST_IP` - Used for DSM admin access through Traefik +| Variable | Purpose | +|----------|---------| +| `DOMAINNAME` | Primary domain for all subdomains | +| `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY`, `CLOUDFLARE_ZONEID` | DNS challenge | +| `DOCKERDIR` | NFS mount path (e.g. `/mnt/docker`) | +| `LOCALDOCKERDIR` | Local storage path (e.g. `/home/user/Projects/docker-services`) | +| `SYNOLOGYDIR` | Synology NAS mount | +| `HOST_IP` | Host IP for DSM access through Traefik | +| `PLEX_TOKEN` | Plex auth token (used by Kometa) | +| `TMDB_API_READ_ACCESS_TOKEN` | TMDb v4 read token (used by Kometa) | ## Adding New Services -1. Add service definition to `docker-compose.yml` -2. Connect to `t2_proxy` network for web access -3. Add Traefik labels for routing: `traefik.http.routers..rule=Host(\`.$DOMAINNAME\`)` -4. Use `chain-authelia@file` middleware for authentication -5. Create NFS volume in volumes section if config persistence needed -6. Reference existing services (e.g., sonarr, radarr) as templates -7. Add the new service subdomain to `scripts/validate-traefik.sh` SERVICES array -8. Run the validation script (see Validation section below) +1. Add service to `docker-compose.yml` using an existing service (e.g. radarr) as a template +2. Connect to `t2_proxy` network +3. Add Traefik labels: `traefik.http.routers..rule=Host(\`.$DOMAINNAME\`)` +4. Use `chain-authelia@file` middleware +5. Add NFS volume if config persistence needed (follow existing pattern, Synology at 192.168.0.6) +6. Add subdomain to `SERVICES` array in `scripts/validate-traefik.sh` +7. Run `./scripts/validate-traefik.sh` ## Validation -**IMPORTANT: Always run the validation script after modifying `docker-compose.yml`.** +**Always run after modifying `docker-compose.yml`:** ```bash -# Run validation (reads domain from .env) ./scripts/validate-traefik.sh - -# Or specify domain explicitly -./scripts/validate-traefik.sh yourdomain.com -``` - -The script validates: -- All services are accessible via HTTPS -- HTTP to HTTPS redirect is working -- Security headers are present -- TLS certificate is valid -- Traefik dashboard and API are responding - -When adding a new service, add its subdomain to the `SERVICES` array in `scripts/validate-traefik.sh`. - -## Log Management - -### Docker Container Logs (stdout/stderr) - -All containers use standardized Docker logging with automatic rotation: -- **Driver**: json-file -- **Max size**: 10MB per log file -- **Max files**: 3 files retained -- **Total capacity**: ~30MB per container - -Configuration is defined via YAML anchor in `docker-compose.yml`: -```yaml -x-logging: &default-logging - driver: "json-file" - options: - max-size: "10m" - max-file: "3" ``` -View container logs: `docker compose logs -f ` - -### Application Logs (on NFS mount) +Checks HTTPS access, HTTP→HTTPS redirect, security headers, TLS cert, and Traefik API for all services. -Many applications maintain their own logs on the NFS mount at `/mnt/docker//logs/`. These have **built-in rotation**: +## Logging -| Application | Location | Rotation Method | Max Size | -|-------------|----------|-----------------|----------| -| **Radarr** | `/mnt/docker/radarr/logs/` | Built-in (1MB/file, ~25 files) | ~25-30MB | -| **Sonarr** | `/mnt/docker/sonarr/logs/` | Built-in (1MB/file, ~25 files) | ~25-30MB | -| **SABnzbd** | `/mnt/docker/sabnzb/logs/` | Configured (5MB max, 5 backups) | ~30MB | -| **NZBHydra2** | `/mnt/docker/hydra/logs/` | Date-based rotation | ~1MB | -| **Home Assistant** | `/mnt/docker/homeassistant/` | Python logging rotation | ~10MB | - -**Log levels** are configured per application: -- Radarr/Sonarr: Set to `info` level in `/mnt/docker//config.xml` -- SABnzbd: Configured via Web UI (Settings → Logging) -- NZBHydra2: Configured via Web UI (Settings → Logging) - -**Do not** add external logrotate configuration - applications manage their own logs and Docker handles container logs. +All containers use `*default-logging` (json-file, 10MB max, 3 files). Application-level logs on NFS have built-in rotation — do not add external logrotate. ## Git Workflow -**IMPORTANT: Never commit directly to master.** - -All changes must be made in a feature branch: -1. If the user hasn't specified a branch name, suggest a descriptive one (e.g., `fix-portainer-permissions`, `add-new-service`) -2. Create the branch before making any changes -3. Commit changes to the branch -4. Push the branch to remote -5. Offer to create a pull request when changes are complete - -Example workflow: -```bash -git checkout -b feature-branch-name -# Make changes... -git add . -git commit -m "descriptive message" -git push -u origin feature-branch-name -# Create PR via gh cli or web interface -``` +**Never commit directly to master.** Always use a feature branch, push, and open a PR. ## Notes -- Pi-hole runs on host port 53 - may conflict with systemd-resolved -- Plex uses LinuxServer.io image (`lscr.io/linuxserver/plex`) with `/dev/dri` for GPU transcoding -- Plex uses `VERSION=public` for auto-updates on container restart -- Home Assistant runs privileged with host network for device access -- Task Scheduler runs scheduled maintenance: - - Daily at midnight: Docker cleanup (prune images/containers/volumes older than 7 days) - - Sunday at 3 AM: Plex restart (picks up VERSION=public updates) +- **Plex**: LinuxServer.io image with `/dev/dri` GPU transcoding; `VERSION=public` enables auto-updates on container restart +- **Task Scheduler**: Docker cleanup nightly at midnight; Plex restart Sundays at 3 AM +- **Kometa**: Runs daily at 1 AM; reads `PLEX_TOKEN` and `TMDB_API_READ_ACCESS_TOKEN` from env; config at `/mnt/docker/kometa/config.yml` +- **Pi-hole**: Host port 53 — may conflict with systemd-resolved +- **Home Assistant**: Privileged mode with host network (required for device access) diff --git a/README.md b/README.md index 8abc5bb..cb0e9f7 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,97 @@ -# Home Server setup - -I've done a whole bunch of stuff with my server setup before writing this. I'm probably missing steps. Bummer. - -Here are a few guides and docs that proved invaluable: -* [https://www.smarthomebeginner.com/traefik-2-docker-tutorial/](https://www.smarthomebeginner.com/traefik-2-docker-tutorial/) -* [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/) -* [https://docs.docker.com/compose/compose-file](https://docs.docker.com/compose/compose-file) - -## What's working - -So far this is the list of things that I'm serving: -* Traefik + LetsEncrypt + Cloudflare for reverse proxy with SSL -* Authelia for 2FA auth (only for external network access) -* Portainer for giggles. I haven't actually used it directly yet. -* Organizr -* Medusa -* Calibre-Web -* Lazy Librarian -* PiHole -- Still in place, but I'm using NextDNS now. -* Plex -* SabNZB -* Sonarr -* Radarr -* Hydra -* Bazarr -* iSponsorBlockTV -* Home Assistant -* Docker Socket Proxy -* Garbage Collection - -## TODO - -* ~Plex (currently hosted on Synology)~ -* ~SabNZB (also on Synology)~ -* [https://ombi.io/](https://ombi.io/) -* OwnCloud / NextCloud / etc -* HomeBridge for HomeKit (currently hosted on Synology) -* ~Radarr~ -* Lidarr -* Watchtower -* GitLab - -## Maybe - -* Firefox -* Guacamole -* VSCode +# Home Server Docker Stack + +Self-hosted services running on Docker Compose behind Traefik with Authelia SSO/2FA and a Synology NAS for storage. + +## References + +- [Traefik Docker tutorial](https://www.smarthomebeginner.com/traefik-2-docker-tutorial/) +- [Traefik docs](https://doc.traefik.io/traefik/) +- [Docker Compose reference](https://docs.docker.com/compose/compose-file) + +## Services + +| Category | Service | Subdomain | +|----------|---------|-----------| +| Infrastructure | Traefik | `traefik.$DOMAINNAME` | +| Infrastructure | Authelia | `authelia.$DOMAINNAME` | +| Infrastructure | Portainer | `portainer.$DOMAINNAME` | +| Infrastructure | Docker Socket Proxy | internal only | +| Infrastructure | Task Scheduler | internal only | +| Dashboard | Organizr | `start.$DOMAINNAME` | +| Media | Plex | `plex.$DOMAINNAME` | +| Media | Sonarr | `sonarr.$DOMAINNAME` | +| Media | Radarr | `radarr.$DOMAINNAME` | +| Media | Bazarr | `bazarr.$DOMAINNAME` | +| Media | SABnzbd | `sabnzb.$DOMAINNAME` | +| Media | NZBHydra2 | `hydra.$DOMAINNAME` | +| Collection Mgmt | Kometa | `kometa.$DOMAINNAME` | +| Books | Calibre-Web | `books.$DOMAINNAME` | +| Books | Lazy Librarian | `lazylib.$DOMAINNAME` | +| DNS | Pi-hole | `pihole.$DOMAINNAME/admin/` | +| Home Automation | Home Assistant | `homeassistant.$DOMAINNAME` | +| Utilities | iSponsorBlockTV | internal only | +| Utilities | LibreSpeed | `speedtest.$DOMAINNAME` | +| Utilities | Smokeping | `smokeping.$DOMAINNAME` | +| Utilities | Slideshow Updater | internal only | ## Setup -* `cp env.example .env` -* Update the values in `.env`. I'm using cloudflare for DNS. See [https://www.smarthomebeginner.com/traefik-2-docker-tutorial/#4_Proper_DNS_Records](https://www.smarthomebeginner.com/traefik-2-docker-tutorial/#4_Proper_DNS_Records) -* I have two config directories. Things that aren't as finicky go on a NFS mount (to my synology). Something just don't play nice with being on a network mount. So I have another directory for those configs. Ergo $DOCKERDIR and $LOCALDOCKERDIR. -* Also I have my synology SMB mounted with the following attributes: `//$IP/audiobooks /mnt/audiobooks cifs user,vers=3.0,uid=$USER,gid=$GROUP,rw,suid,nobrl,file_mode=0600,dir_mode=0700,credentials=/etc/cifspwd 0 0`. Replace `$IP,$USER,$GROUP` with the correct values. -* Create networks: +### 1. Environment + +```bash +cp env.example .env +# Fill in all values in .env ``` -# docker network create t2_proxy -# docker network create socket_proxy -# Alternatively, you can specify the gateway and subnet to use + +Key variables: +- `DOMAINNAME` — your domain (all services run as subdomains) +- `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY`, `CLOUDFLARE_ZONEID` — for Let's Encrypt DNS challenge +- `DOCKERDIR` — NFS mount path (e.g. `/mnt/docker`) +- `LOCALDOCKERDIR` — local project directory path +- `PLEX_TOKEN` — required for Kometa (get from Plex account settings) +- `TMDB_API_READ_ACCESS_TOKEN` — required for Kometa (TMDb v4 read token) + +### 2. Docker Networks + +```bash docker network create --gateway 192.168.90.1 --subnet 192.168.90.0/24 t2_proxy -# docker network create --gateway 192.168.91.1 --subnet 192.168.91.0/24 socket_proxy -# Subnet range 192.168.0.0/16 covers 192.168.0.0 to 192.168.255.255 +docker network create --gateway 192.168.91.1 --subnet 192.168.91.0/24 socket_proxy +``` + +### 3. Traefik + +```bash +touch acme/acme.json +chmod 600 acme/acme.json +``` + +### 4. Kometa + +Copy and configure the Kometa config on your NFS mount: +```bash +mkdir -p /mnt/docker/kometa/assets +cp kometa/config.yml.example /mnt/docker/kometa/config.yml ``` -### Traefik specific +Token values are read from environment — no edits needed if `.env` is populated. + +### 5. Portainer -* touch the `traefik.log` file -* `chmod 600 traefik.log` +After first start: Settings → Environments → Add Environment → Docker → set URL to `socket-proxy:2375`. -### Authelia +### 6. Start Everything -* [https://www.smarthomebeginner.com/docker-authelia-tutorial/](https://www.smarthomebeginner.com/docker-authelia-tutorial/) +```bash +docker compose up -d +``` -### pihole +## Maintenance -* `touch pihole/pihole.log` +Task Scheduler handles routine maintenance automatically: +- **Nightly at midnight**: Docker cleanup (prunes images/containers/volumes older than 7 days) +- **Sundays at 3 AM**: Plex restart (picks up `VERSION=public` updates) +- **Daily at 1 AM**: Kometa runs and updates Plex collections -### portainer +## Development -* After running for the first time, add a new docker endpoint and set the url to socket-proxy:2375 -* Settings -> Endpoints -> Add Endpoint -> Docker. -* Fill in "Endpoint URL" with `socket-proxy:2375` and give it a name -* Not sure if this is required but I removed the "primary" endpoint since the docker.sock was not available any more +See [DEVELOPMENT.md](DEVELOPMENT.md) for the automated task workflow using `ralph.sh`. diff --git a/prompt.md b/prompt.md index 3092b64..fa31525 100644 --- a/prompt.md +++ b/prompt.md @@ -49,7 +49,7 @@ 7. **Create Git Commit** - Stage relevant changes - Create a descriptive commit message following the project's conventions - - Include `Co-Authored-By: Claude Sonnet 4.5 ` + - Include `Co-Authored-By: Claude Sonnet 4.6 ` - **Do NOT push** unless explicitly instructed 8. **Stop Processing** @@ -85,7 +85,7 @@ When selecting the most important task, consider: - Change 2 - Change 3 -Co-Authored-By: Claude Sonnet 4.5 +Co-Authored-By: Claude Sonnet 4.6 ``` ## Important Notes