Compare commits

..

20 Commits

Author SHA1 Message Date
900e46c667 add VPS Traefik dynamic config — hubris.network fallback routes
All services use certResolver: letsencrypt (TLS-ALPN-01).
Patterns:
  HTTP backends: blog, artifacto, trmnl, matrix, house, sso, media, paperless
  HTTPS+SNI: mcp, oikos (via caddy-hubris serversTransport)
  HTTPS+insecure: proxmox (via proxmox serversTransport)
Static config: --serverstransport.insecureskipverify=true

Config lives at /opt/traefik-dynamic.yaml on VPS.
Auto-deploy: webhook receiver on :8082 -> git pull + docker kill -s HUP netbird-traefik
2026-08-12 18:26:43 +02:00
root
6e6bfe3aa0 Revert "test: end-to-end deploy pipeline (both hosts)"
This reverts commit 28543e1662.
2026-08-12 14:52:48 +02:00
root
28543e1662 test: end-to-end deploy pipeline (both hosts) 2026-08-12 14:52:29 +02:00
root
3d52e9d1c1 Revert "test: debug sig v3"
This reverts commit fa1dff62c6.
2026-08-12 14:51:25 +02:00
root
fa1dff62c6 test: debug sig v3 2026-08-12 14:50:27 +02:00
root
2faea41d3e Revert "test: debug webhook signature v2"
This reverts commit 3cebb8fd11.
2026-08-12 14:49:40 +02:00
root
3cebb8fd11 test: debug webhook signature v2 2026-08-12 14:48:29 +02:00
root
4635001feb Revert "test: verify auto-deploy pipeline"
This reverts commit 1ea60a9aca.
2026-08-12 14:47:53 +02:00
root
1ea60a9aca test: verify auto-deploy pipeline 2026-08-12 14:45:11 +02:00
root
e7bf4f9d93 fix: clean shared/authentik (fix tab escaping) + add :8080 webhook endpoint 2026-08-12 14:42:40 +02:00
root
2c418d395d refactor: split Caddyfile into per-host configs with shared snippets
Structure:
  shared/authentik  - forward-auth proxy + authentik snippet (both hosts)
  hosts/hubris/      - git, mcp, oikos, paperless, cloud, photos, etc.
  hosts/strong/      - media, books, roms, jellyseerr, qbit, sab, matrix, etc.

Each host imports /etc/caddy/shared/* at the top of its Caddyfile.
Systemd overrides point to hosts/<hostname>/Caddyfile.

Deploy workflow: git push → webhook → caddy reload
2026-08-12 14:37:30 +02:00
root
86e727255b chore: add .gitignore for backup and env files 2026-08-12 14:33:33 +02:00
root
2b8bddc488 split: move 10 strong-hosted services to dedicated Caddy on CT 140 (.250)
Moved media, books, roms, jellyseerr, qbit, sab, matrix, element,
house, seanime to strong Caddy (192.168.8.250). These services
backends live on strong LXCs — co-locating Caddy removes cross-host
proxy dependency.

Remaining on hubris (.175): git, mcp, oikos, paperless, cloud, photos,
proxmox, secrets, home, trmnl, auth.
2026-08-12 14:33:03 +02:00
4238adb81c fix: zimaos.hubris.network reverse_proxy target IP .102→.195 (VM serving at original .195) 2026-08-06 22:05:17 +02:00
f266f0605f add matrix.hubris.network reverse proxy to Synapse (192.168.8.242:8008) 2026-08-05 17:11:34 +02:00
48549d0072 fix(zimaos): update reverse_proxy to 192.168.8.102:80 (correct ZimaOS VM IP) 2026-08-04 21:28:00 +02:00
root
d17ee47ce5 add pascal.hubris.network → 192.168.8.100:3001 2026-08-04 14:39:23 +02:00
25b81b2dce Add /oidc-callback to enroll bypass for desktop OIDC flow 2026-07-13 23:21:41 +02:00
root
a3d9368a54 Merge branch 'master' of http://192.168.8.121:3000/dtoro/caddy-conf 2026-07-12 23:15:18 +02:00
root
89ff86e36e add tube.hubris.network -> 192.168.178.44:8082 2026-07-12 23:11:13 +02:00
7 changed files with 691 additions and 69 deletions

9
.gitignore vendored
View File

@@ -1,5 +1,4 @@
# Environment variables
.env
.env.local
.env.*.local
!.env.example
# Backup files
Caddyfile.bak*
caddy.env
*.env

View File

@@ -35,14 +35,6 @@
# this machine's public IP, then replace ":80" below with your
# domain name.
media.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
# SSO plugin handles auth via OIDC redirect to Authentik.
# No forward-auth gate — Jellyfin login page shows SSO button directly.
reverse_proxy 192.168.8.246:8096
}
git.hubris.network {
tls {
@@ -71,18 +63,12 @@ paperless.hubris.network {
}
}
books.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.247:6060
}
roms.hubris.network {
pascal.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.249:80
reverse_proxy 192.168.8.100:3001
}
home.hubris.network {
@@ -122,36 +108,8 @@ docker.hubris.network {
}
}
jellyseerr.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.245:5056
}
qbit.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
@api path /api/*
handle @api {
reverse_proxy 192.168.8.245:8080
}
handle {
import authentik
reverse_proxy 192.168.8.245:8080
}
}
sab.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
handle {
import authentik
reverse_proxy 192.168.8.245:8082
}
}
blog.hubris.network {
tls {
@@ -231,7 +189,7 @@ zimaos.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.195
reverse_proxy 192.168.8.195:80
}
# Homelab MCP server (read-only context + management tools).
@@ -249,12 +207,9 @@ sso.hubris.network {
}
element.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.242:8080
}
# Matrix Synapse federation + client API.
# No authentik gate — Synapse handles its own auth.
trmnl.hubris.network {
tls {
@@ -263,12 +218,6 @@ trmnl.hubris.network {
reverse_proxy 192.168.8.211:9851
}
house.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.244:3000
}
teddy.hubris.network {
tls {
@@ -281,12 +230,6 @@ teddy.hubris.network {
}
}
seanime.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.248:43211
}
# ─── Oikos (Go rewrite, Docker on mac-mini) ───
# Phase 6 cutover — replaces apps/105 Python MCP + console.
@@ -297,7 +240,7 @@ oikos.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
@enroll path /api/v1/clients/enroll
@enroll path /api/v1/clients/enroll /oidc-callback
handle @enroll {
reverse_proxy 192.168.178.182:8090
}
@@ -318,3 +261,10 @@ mcp.hubris.network {
hermes.hubris.network {
reverse_proxy 192.168.178.182:8092
}
tube.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.178.44:8082
}

1
caddy.env.example Normal file
View File

@@ -0,0 +1 @@
IONOS_AUTH_API_TOKEN=your_token_here

234
hosts/hubris/Caddyfile Normal file
View File

@@ -0,0 +1,234 @@
import /etc/caddy/shared/*
git.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
handle_path /_plantuml/* {
rewrite * /plantuml{uri}
reverse_proxy 192.168.8.205:8079
}
handle {
reverse_proxy 192.168.8.121:3000
}
}
paperless.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
@api path /api/*
handle @api {
reverse_proxy 192.168.8.130:8000
}
handle {
import authentik
reverse_proxy 192.168.8.130:8000
}
}
pascal.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.100:3001
}
home.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.101:8123
}
cloud.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.224:80
}
proxmox.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy https://192.168.8.77:8006 {
transport http {
tls_insecure_skip_verify
}
}
}
docker.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy https://192.168.8.205:9443 {
transport http {
tls_insecure_skip_verify
}
}
}
blog.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.205:8080
}
photos.hubris.network {
# Mulimage 2.0 — PhotoPrism + Go sidecar + SvelteKit (LXC 120)
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
encode zstd gzip
# PhotoPrism API + static assets + share links
@pp path /api/v1/* /api/v1 /static/* /share/*
reverse_proxy @pp 192.168.8.136:2342
# OIDC hand-off: PhotoPrism redirects to /library/login after SSO;
# bounce back to / so the SvelteKit SPA picks up the session.
redir /library / 302
redir /library/* / 302
# Go sidecar — marks, folder mutations, duplicates, heap convert
@sc path /api/sidecar/*
reverse_proxy @sc 192.168.8.136:8000
# SvelteKit frontend (catch-all)
reverse_proxy 192.168.8.136:3000
}
prism.hubris.network {
# PhotoPrism — direct access for Prismatic mobile client (LXC 120)
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
encode zstd gzip
reverse_proxy 192.168.8.136:2342
}
auth.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy https://auth.hubris.network:443 {
transport http {
tls_server_name auth.hubris.network
}
header_up Host auth.hubris.network
}
}
artifacto.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
# Public paths: share URLs, static assets, health — no SSO
@public path /p/* /static/* /healthz
handle @public {
reverse_proxy 192.168.8.205:3100
}
# Admin + login surface: Authentik forward-auth + shared-secret header
# so the app can auto-login an Authentik-authenticated user.
handle {
import authentik
reverse_proxy 192.168.8.205:3100 {
header_up X-Artifacto-Gateway {env.ARTIFACTO_GATEWAY_SECRET}
}
}
}
plato.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.190:8080
}
zimaos.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.195:80
}
# Homelab MCP server (read-only context + management tools).
# Mesh+LAN-gated by the service itself + nftables; no SSO so agent clients work.
sso.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy https://auth.hubris.network:443 {
transport http {
tls_server_name auth.hubris.network
}
header_up Host auth.hubris.network
}
}
# Matrix Synapse federation + client API.
# No authentik gate — Synapse handles its own auth.
trmnl.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.211:9851
}
teddy.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy https://192.168.8.150:8443 {
transport http {
tls_insecure_skip_verify
}
}
}
# ─── Oikos (Go rewrite, Docker on mac-mini) ───
# Phase 6 cutover — replaces apps/105 Python MCP + console.
# /api/v1/clients/enroll bypasses Authentik (IP-gated in the API handler).
# The SPA is its own container (:8091, compose/web/Dockerfile in the oikos
# repo) — not served by api itself, which no longer embeds it.
oikos.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
@enroll path /api/v1/clients/enroll /oidc-callback
handle @enroll {
reverse_proxy 192.168.178.182:8090
}
@api path /api/v1/* /mcp /agent/*
handle @api {
reverse_proxy 192.168.178.182:8090
}
handle {
import authentik
reverse_proxy 192.168.178.182:8091
}
}
mcp.hubris.network {
reverse_proxy 192.168.178.182:8090
}
hermes.hubris.network {
reverse_proxy 192.168.178.182:8092
}
tube.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.178.44:8082
}

83
hosts/strong/Caddyfile Normal file
View File

@@ -0,0 +1,83 @@
import /etc/caddy/shared/*
media.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
# SSO plugin handles auth via OIDC redirect to Authentik.
# No forward-auth gate — Jellyfin login page shows SSO button directly.
reverse_proxy 192.168.8.246:8096
}
books.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.247:6060
}
roms.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.249:80
}
jellyseerr.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.245:5056
}
qbit.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
@api path /api/*
handle @api {
reverse_proxy 192.168.8.245:8080
}
handle {
import authentik
reverse_proxy 192.168.8.245:8080
}
}
sab.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
handle {
import authentik
reverse_proxy 192.168.8.245:8082
}
}
matrix.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.242:8008
}
element.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.242:8080
}
house.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.244:3000
}
seanime.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
reverse_proxy 192.168.8.248:43211
}

View File

@@ -0,0 +1,324 @@
tcp:
serversTransports:
pp-v2:
proxyProtocol:
version: 2
tls:
certificates:
- certFile: /letsencrypt/fullchain.crt
keyFile: /letsencrypt/privkey.key
- certFile: /letsencrypt/blog.fullchain.crt
keyFile: /letsencrypt/blog.privkey.key
- certFile: /letsencrypt/trmnl.fullchain.crt
keyFile: /letsencrypt/trmnl.privkey.key
- certFile: /letsencrypt/sso.fullchain.crt
keyFile: /letsencrypt/sso.privkey.key
- certFile: /letsencrypt/media.fullchain.crt
keyFile: /letsencrypt/media.privkey.key
- certFile: /letsencrypt/paperless.fullchain.crt
keyFile: /letsencrypt/paperless.privkey.key
http:
serversTransports:
caddy-hubris:
serverName: mcp.hubris.network
proxmox:
serverName: proxmox.hubris.network
insecureSkipVerify: true
routers:
artifacto-public:
rule: 'Host(`artifacto.hubris.network`) && (PathPrefix(`/p/`) || PathPrefix(`/static/`) || Path(`/healthz`))'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- artifacto-strip-sso
- artifacto-ratelimit
service: artifacto-public
blog-public:
rule: 'Host(`blog.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- blog-ratelimit
service: blog-public
trmnl-public:
rule: 'Host(`trmnl.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- trmnl-ratelimit
service: trmnl-public
matrix-public:
rule: 'Host(`matrix.hubris.network`) && !PathPrefix(`/.well-known/matrix/`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- matrix-ratelimit
service: matrix-public
matrix-wellknown:
rule: 'Host(`matrix.hubris.network`) && (PathPrefix(`/.well-known/matrix/`) || PathPrefix(`/.well-known/acme-challenge/`))'
entryPoints:
- websecure
priority: 20
tls:
certResolver: letsencrypt
service: matrix-wellknown-svc
house-public:
rule: 'Host(`house.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- house-ratelimit
service: house-public
sso-public:
rule: 'Host(`sso.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- sso-ratelimit
service: sso-public
media-public:
rule: 'Host(`media.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- media-ratelimit
service: media-public
paperless-api-public:
rule: 'Host(`paperless.hubris.network`) && PathPrefix(`/api/`)'
entryPoints:
- websecure
priority: 20
tls:
certResolver: letsencrypt
middlewares:
- paperless-ratelimit
service: paperless-public
paperless-public:
rule: 'Host(`paperless.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- authentik-forwardauth
- paperless-ratelimit
service: paperless-public
git-public:
rule: 'Host(`git.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- git-ratelimit
service: git-public
mcp-public:
rule: 'Host(`mcp.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- mcp-ratelimit
service: mcp-public
oikos-public:
rule: 'Host(`oikos.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- oikos-ratelimit
service: oikos-public
proxmox-public:
rule: 'Host(`proxmox.hubris.network`)'
entryPoints:
- websecure
priority: 10
tls:
certResolver: letsencrypt
middlewares:
- proxmox-ratelimit
service: proxmox-public
middlewares:
admin-allowlist:
ipAllowList:
sourceRange:
- "5.61.168.0/24"
artifacto-strip-sso:
headers:
customRequestHeaders:
X-Artifacto-Gateway: ""
X-Authentik-Username: ""
X-Authentik-Groups: ""
X-Authentik-Email: ""
X-Authentik-Name: ""
X-Authentik-Uid: ""
X-Authentik-Jwt: ""
X-Authentik-Meta-Jwks: ""
X-Authentik-Meta-Outpost: ""
X-Authentik-Meta-Provider: ""
X-Authentik-Meta-App: ""
X-Authentik-Meta-Version: ""
artifacto-ratelimit:
rateLimit:
average: 50
period: 1s
burst: 100
blog-ratelimit:
rateLimit:
average: 100
period: 1s
burst: 200
trmnl-ratelimit:
rateLimit:
average: 20
period: 1s
burst: 40
matrix-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
house-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
sso-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
media-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
paperless-ratelimit:
rateLimit:
average: 20
period: 1s
burst: 40
authentik-forwardauth:
forwardAuth:
address: "http://192.168.8.6:9000/outpost.goauthentik.io/auth/traefik"
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- X-authentik-jwt
- X-authentik-meta-jwks
- X-authentik-meta-outpost
- X-authentik-meta-provider
- X-authentik-meta-app
- X-authentik-meta-version
git-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
mcp-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
oikos-ratelimit:
rateLimit:
average: 30
period: 1s
burst: 60
proxmox-ratelimit:
rateLimit:
average: 20
period: 1s
burst: 40
services:
artifacto-public:
loadBalancer:
servers:
- url: 'http://192.168.8.205:3100'
blog-public:
loadBalancer:
servers:
- url: 'http://192.168.8.205:8080'
trmnl-public:
loadBalancer:
servers:
- url: 'http://192.168.8.211:9851'
matrix-public:
loadBalancer:
servers:
- url: 'http://192.168.8.242:8008'
matrix-wellknown-svc:
loadBalancer:
servers:
- url: 'http://matrix-wellknown:80'
house-public:
loadBalancer:
servers:
- url: 'http://192.168.8.244:3000'
sso-public:
loadBalancer:
servers:
- url: 'http://192.168.8.6:9000'
media-public:
loadBalancer:
servers:
- url: 'http://192.168.8.246:8096'
paperless-public:
loadBalancer:
servers:
- url: 'http://192.168.8.130:8000'
git-public:
loadBalancer:
servers:
- url: 'http://192.168.8.121:3000'
mcp-public:
loadBalancer:
serversTransport: caddy-hubris
servers:
- url: 'https://192.168.8.175:443'
oikos-public:
loadBalancer:
serversTransport: caddy-hubris
servers:
- url: 'https://192.168.8.175:443'
proxmox-public:
loadBalancer:
serversTransport: proxmox
servers:
- url: 'https://192.168.8.77:8006'

31
shared/authentik Normal file
View File

@@ -0,0 +1,31 @@
# Internal forward-auth upstream proxy (no TLS between Caddy handlers)
:8099 {
@outpost path /outpost.goauthentik.io/*
reverse_proxy @outpost https://auth.hubris.network:443 {
transport http {
tls_server_name auth.hubris.network
}
header_up Host auth.hubris.network
}
}
# Authentik forward-auth snippet (domain-level mode).
# Usage: add "import authentik" inside any site block to gate it.
(authentik) {
route {
reverse_proxy /outpost.goauthentik.io/* https://auth.hubris.network:443
forward_auth http://127.0.0.1:8099 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
trusted_proxies private_ranges
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Uri {uri}
}
}
}
# Internal webhook receiver for auto-deploy from Gitea
:8080 {
reverse_proxy 127.0.0.1:8081
}