Compare commits

...

5 Commits

Author SHA1 Message Date
50c930eeaf Add flaresolverr, disable bazarr, tweak resources. 2025-11-22 19:27:37 +00:00
8dde15b8ef Add prowlarr, recyclarr, and jellyseerr. 2025-11-22 17:32:14 +00:00
6100d8dc69 Fix override. 2025-11-21 16:43:39 +00:00
a92f0fcb28 Tighten up security. 2025-11-21 16:39:45 +00:00
bd4604cdcc Auth docs. 2025-11-21 14:12:19 +00:00
4 changed files with 207 additions and 15 deletions

View File

@@ -20,7 +20,8 @@ NixOS cluster configuration using flakes. Homelab infrastructure with Nomad/Cons
├── docs/ ├── docs/
│ ├── CLUSTER_REVAMP.md # Master plan for architecture changes │ ├── CLUSTER_REVAMP.md # Master plan for architecture changes
│ ├── MIGRATION_TODO.md # Tracking checklist for migration │ ├── MIGRATION_TODO.md # Tracking checklist for migration
── NFS_FAILOVER.md # NFS failover procedures ── NFS_FAILOVER.md # NFS failover procedures
│ └── AUTH_SETUP.md # Authentication (Pocket ID + Traefik OIDC)
└── services/ # Nomad job specs (.hcl files) └── services/ # Nomad job specs (.hcl files)
``` ```
@@ -76,6 +77,12 @@ NixOS cluster configuration using flakes. Homelab infrastructure with Nomad/Cons
- SOPS for secrets, files in `secrets/` - SOPS for secrets, files in `secrets/`
- Keys managed per-host - Keys managed per-host
**Authentication**:
- Pocket ID (OIDC provider) at `pocket-id.v.paler.net`
- Traefik uses `traefik-oidc-auth` plugin for SSO
- Services add `middlewares=oidc-auth@file` tag to protect
- See `docs/AUTH_SETUP.md` for details
## Migration Status ## Migration Status
**Phase 3 & 4**: COMPLETE! GlusterFS removed, all services on NFS **Phase 3 & 4**: COMPLETE! GlusterFS removed, all services on NFS

55
docs/AUTH_SETUP.md Normal file
View File

@@ -0,0 +1,55 @@
# Authentication Setup
SSO for homelab services using OIDC.
## Architecture
**Pocket ID** (`pocket-id.v.paler.net`) - Lightweight OIDC provider, data in `/data/services/pocket-id`
**Traefik** - Uses `traefik-oidc-auth` plugin (v0.16.0) to protect services
- Plugin downloaded from GitHub at startup, cached in `/data/services/traefik/plugins-storage`
- Middleware config in `/data/services/traefik/rules/middlewares.yml`
- Protected services add tag: `traefik.http.routers.<name>.middlewares=oidc-auth@file`
## Flow
1. User hits protected service → Traefik intercepts
2. Redirects to Pocket ID for login
3. Pocket ID returns OIDC token
4. Traefik validates and forwards with `X-Oidc-Username` header
## Protected Services
Use `oidc-auth@file` middleware (grep codebase for full list):
- Wikis (TiddlyWiki instances)
- Media stack (Radarr, Sonarr, Plex, etc.)
- Infrastructure (Traefik dashboard, Loki, Jupyter, Unifi)
## Key Files
- `services/pocket-id.hcl` - OIDC provider
- `services/traefik.hcl` - Plugin declaration
- `/data/services/traefik/rules/middlewares.yml` - Middleware definitions (oidc-auth, simple-auth fallback)
## Cold Start Notes
- Traefik needs internet to download plugin on first start
- Pocket ID needs `/data/services` NFS mounted
- Pocket ID down = all protected services inaccessible
## Troubleshooting
**Infinite redirects**: Check `TRUST_PROXY=true` on Pocket ID
**Plugin not loading**: Clear cache in `/data/services/traefik/plugins-storage/`, restart Traefik
**401 after login**: Verify client ID/secret in middlewares.yml matches Pocket ID client config
## Migration History
- Previous: Authentik with forwardAuth (removed Nov 2024)
- Current: Pocket ID + traefik-oidc-auth (simpler, lighter)
---
*Manage users/clients via Pocket ID UI. Basic auth fallback available via `simple-auth` middleware.*

View File

@@ -24,4 +24,20 @@
externalInterface = "enp1s0"; externalInterface = "enp1s0";
internalInterfaces = [ "tailscale0" ]; internalInterfaces = [ "tailscale0" ];
}; };
# Security hardening: Enable firewall (override global setting)
networking.firewall = {
enable = lib.mkForce true;
allowedTCPPorts = [ 80 443 ]; # Public web traffic only
allowedUDPPorts = [ 41641 ]; # Tailscale
trustedInterfaces = [ "tailscale0" ]; # Full access via VPN
};
# Security hardening: Restrict SSH to Tailscale only + key-based auth
services.openssh = {
listenAddresses = [
{ addr = "100.75.147.49"; port = 22; } # Tailscale IP only
];
settings.PasswordAuthentication = false; # Keys only
};
} }

View File

@@ -7,9 +7,12 @@ job "media" {
group "servers" { group "servers" {
network { network {
port "radarr" { to = 7878 } port "radarr" { static = 7878 }
port "sonarr" { to = 8989 } port "sonarr" { static = 8989 }
port "bazarr" { to = 6767 } port "bazarr" { to = 6767 }
port "prowlarr" { static = 9696 }
port "jellyseerr" { static = 5055 }
port "flaresolverr" { static = 8191 }
port "pms" { static = 32400 } port "pms" { static = 32400 }
port "qbt_ui" { static = 8080 } port "qbt_ui" { static = 8080 }
port "qbt_torrent" { static = 51413 } port "qbt_torrent" { static = 51413 }
@@ -34,7 +37,7 @@ job "media" {
} }
resources { resources {
cpu = 200 cpu = 1000
} }
service { service {
@@ -68,7 +71,8 @@ job "media" {
} }
resources { resources {
cpu = 200 cpu = 1000
memory = 500
} }
service { service {
@@ -83,15 +87,49 @@ job "media" {
} }
} }
task "bazarr" { # task "bazarr" {
# driver = "docker"
#
# config {
# image = "ghcr.io/hotio/bazarr:latest"
# ports = [ "bazarr" ]
# volumes = [
# "/data/services/media/bazarr:/config",
# "/data/media/media:/data/media",
# ]
# }
#
# env {
# PUID = 1000
# PGID = 1000
# TZ = "Europe/Lisbon"
# }
#
# resources {
# cpu = 200
# memory = 500
# }
#
# service {
# name = "bazarr"
# port = "bazarr"
#
# tags = [
# "traefik.enable=true",
# "traefik.http.routers.bazarr.entryPoints=websecure",
# "traefik.http.routers.bazarr.middlewares=oidc-auth@file",
# ]
# }
# }
task "prowlarr" {
driver = "docker" driver = "docker"
config { config {
image = "ghcr.io/hotio/bazarr:latest" image = "ghcr.io/hotio/prowlarr:latest"
ports = [ "bazarr" ] ports = [ "prowlarr" ]
volumes = [ volumes = [
"/data/services/media/bazarr:/config", "/data/services/media/prowlarr:/config",
"/data/media/media:/data/media",
] ]
} }
@@ -106,17 +144,93 @@ job "media" {
} }
service { service {
name = "bazarr" name = "prowlarr"
port = "bazarr" port = "prowlarr"
tags = [ tags = [
"traefik.enable=true", "traefik.enable=true",
"traefik.http.routers.bazarr.entryPoints=websecure", "traefik.http.routers.prowlarr.entryPoints=websecure",
"traefik.http.routers.bazarr.middlewares=oidc-auth@file", "traefik.http.routers.prowlarr.middlewares=oidc-auth@file",
] ]
} }
} }
task "jellyseerr" {
driver = "docker"
config {
image = "fallenbagel/jellyseerr:latest"
ports = [ "jellyseerr" ]
volumes = [
"/data/services/media/jellyseerr:/app/config",
]
}
env {
TZ = "Europe/Lisbon"
}
resources {
cpu = 200
}
service {
name = "jellyseerr"
port = "jellyseerr"
tags = [
"traefik.enable=true",
"traefik.http.routers.jellyseerr.entryPoints=websecure",
"traefik.http.routers.jellyseerr.middlewares=oidc-auth@file",
]
}
}
task "flaresolverr" {
driver = "docker"
config {
image = "ghcr.io/flaresolverr/flaresolverr:latest"
ports = [ "flaresolverr" ]
}
env {
LOG_LEVEL = "info"
TZ = "Europe/Lisbon"
}
resources {
cpu = 500
memory = 1024
}
service {
name = "flaresolverr"
port = "flaresolverr"
}
}
task "recyclarr" {
driver = "docker"
config {
image = "ghcr.io/recyclarr/recyclarr:latest"
volumes = [
"/data/services/media/recyclarr:/config",
]
}
env {
TZ = "Europe/Lisbon"
CRON_SCHEDULE = "0 0 * * *" # Daily at midnight
}
resources {
cpu = 100
memory = 256
}
}
task "plex" { task "plex" {
driver = "docker" driver = "docker"
@@ -177,7 +291,7 @@ job "media" {
resources { resources {
cpu = 2000 cpu = 2000
memory = 1000 memory = 1500
} }
service { service {