dump
All checks were successful
Hello World / test (push) Successful in 12s

This commit is contained in:
plasmagoat 2025-07-05 11:12:20 +02:00
parent 4ed9ba0d24
commit a90630ecb6
98 changed files with 2063 additions and 729 deletions

View file

@ -0,0 +1,24 @@
{
keycloak = {
rule = "Host(`keycloak.procopius.dk`)";
service = "keycloak";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
oauth2proxy = {
rule = "Host(`radarr.procopius.dk`) && PathPrefix(`/oauth2/`)";
service = "oauth2proxy";
entryPoints = ["websecure"];
middlewares = ["auth-headers"];
tls.certResolver = "letsencrypt";
};
oauth2route = {
rule = "Host(`oauth.procopius.dk`)";
service = "oauth2proxy";
entryPoints = ["websecure"];
middlewares = ["auth-headers"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,5 @@
{
authentik.loadBalancer.servers = [{url = "http://authentik.lab:9000";}];
keycloak.loadBalancer.servers = [{url = "http://keycloak.lab:8080";}];
oauth2proxy.loadBalancer.servers = [{url = "http://localhost:4180";}];
}

View file

@ -0,0 +1,43 @@
{
traefik = {
rule = "Host(`traefik.procopius.dk`)";
service = "traefik";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
mail-acme = {
rule = "Host(`mail.procopius.dk`) && PathPrefix(`/.well-known/acme-challenge/`)";
service = "mail-acme";
entryPoints = ["web"];
priority = 1000;
middlewares = [];
};
forgejo = {
rule = "Host(`git.procopius.dk`)";
service = "forgejo";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
proxmox = {
rule = "Host(`proxmox.procopius.dk`)";
service = "proxmox";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
nas = {
rule = "Host(`nas.procopius.dk`)";
service = "nas";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
catchAll = {
rule = "HostRegexp(`.+`)";
service = "nginx";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,13 @@
{
traefik.loadBalancer.servers = [{url = "http://localhost:8080";}];
mail-acme.loadBalancer.servers = [{url = "http://mail.lab:80";}];
forgejo.loadBalancer.servers = [{url = "http://forgejo.lab:3000";}];
proxmox.loadBalancer.servers = [{url = "https://192.168.1.205:8006";}];
proxmox.loadBalancer.serversTransport = "insecureTransport";
nas.loadBalancer.servers = [{url = "https://192.168.1.226:5001";}];
nas.loadBalancer.serversTransport = "insecureTransport";
nginx.loadBalancer.servers = [{url = "https://192.168.1.226:4433";}];
nginx.loadBalancer.serversTransport = "insecureTransport";
}

View file

@ -0,0 +1,35 @@
{
jellyfin = {
rule = "Host(`jellyfin.procopius.dk`)";
service = "jellyfin";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
radarr = {
rule = "Host(`radarr.procopius.dk`)";
service = "radarr";
entryPoints = ["websecure"];
middlewares = [
"oauth-auth"
"restrict-admin"
];
tls.certResolver = "letsencrypt";
};
sonarr = {
rule = "Host(`sonarr.procopius.dk`)";
service = "sonarr";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
jellyseerr = {
rule = "Host(`jellyseerr.procopius.dk`)";
service = "jellyseerr";
entryPoints = ["websecure"];
# middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,6 @@
{
jellyfin.loadBalancer.servers = [{url = "http://media.lab:8096";}];
radarr.loadBalancer.servers = [{url = "http://media.lab:7878";}];
sonarr.loadBalancer.servers = [{url = "http://media.lab:8989";}];
jellyseerr.loadBalancer.servers = [{url = "http://media.lab:5055";}];
}

View file

@ -1,10 +1,43 @@
{ lib, config, ... }:
let
internalNetwork = "192.168.1.0/24";
in
{
in {
internal-whitelist = {
ipWhiteList.sourceRange = [ internalNetwork ];
ipWhiteList.sourceRange = [internalNetwork];
};
auth-headers = {
headers = {
sslRedirect = true;
stsSeconds = 315360000;
browserXssFilter = true;
contentTypeNosniff = true;
forceSTSHeader = true;
sslHost = "procopius.dk";
stsIncludeSubdomains = true;
stsPreload = true;
frameDeny = true;
};
};
oauth-auth = {
forwardAuth = {
address = "http://localhost:4180/";
trustForwardHeader = true;
authResponseHeaders = [
"Authorization"
"X-Auth-Request-Access-Token"
"X-Auth-Request-User"
"X-Auth-Request-Email"
"X-Auth-Request-Preferred-Username" # Recommended
"X-Auth-Request-Access-Token" # If you want to pass the token
"X-Auth-Request-Groups" # If you configured a mapper in Keycloak to emit groups
];
};
};
restrict-admin = {
forwardAuth = {
address = "http://localhost:4180/oauth2/auth?allowed_groups=role:admin";
};
};
}

View file

@ -0,0 +1,8 @@
{
mesterjakob = {
rule = "Host(`mester.jakobblum.dk`)";
service = "mesterjakob";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,3 @@
{
mesterjakob.loadBalancer.servers = [{url = "http://192.168.1.226:4200";}];
}

View file

@ -0,0 +1,28 @@
{
prometheus = {
rule = "Host(`prometheus.procopius.dk`)";
service = "prometheus";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
grafana = {
rule = "Host(`grafana.procopius.dk`)";
service = "grafana";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
alertmanager = {
rule = "Host(`alertmanager.procopius.dk`)";
service = "alertmanager";
entryPoints = ["websecure"];
middlewares = ["oauth-auth"];
tls.certResolver = "letsencrypt";
};
umami = {
rule = "Host(`umami.procopius.dk`)";
service = "umami";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,6 @@
{
prometheus.loadBalancer.servers = [{url = "http://monitor.lab:9090";}];
grafana.loadBalancer.servers = [{url = "http://monitor.lab:3000";}];
alertmanager.loadBalancer.servers = [{url = "http://monitor.lab:9093";}];
umami.loadBalancer.servers = [{url = "http://192.168.1.226:3333";}];
}

View file

@ -0,0 +1,35 @@
{
ente = {
rule = "Host(`ente.procopius.dk`)";
service = "ente";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
photos = {
rule = "Host(`photos.procopius.dk`)";
service = "photos";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
account = {
rule = "Host(`account.procopius.dk`)";
service = "account";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
minio = {
rule = "Host(`minio.procopius.dk`)";
service = "minio";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
minio-api = {
rule = "Host(`minio-api.procopius.dk`)";
service = "minio-api";
entryPoints = ["websecure"];
tls.certResolver = "letsencrypt";
};
}

View file

@ -0,0 +1,7 @@
{
ente.loadBalancer.servers = [{url = "http://192.168.1.226:8087";}];
photos.loadBalancer.servers = [{url = "http://192.168.1.226:3000";}];
account.loadBalancer.servers = [{url = "http://192.168.1.226:3001";}];
minio.loadBalancer.servers = [{url = "http://192.168.1.226:3201";}];
minio-api.loadBalancer.servers = [{url = "http://192.168.1.226:3200";}];
}

View file

@ -1,140 +0,0 @@
{ lib, config, ... }:
{
traefik = {
rule = "Host(`traefik.procopius.dk`)";
service = "traefik";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
proxmox = {
rule = "Host(`proxmox.procopius.dk`)";
service = "proxmox";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
forgejo = {
rule = "Host(`git.procopius.dk`)";
service = "forgejo";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
prometheus = {
rule = "Host(`prometheus.procopius.dk`)";
service = "prometheus";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
grafana = {
rule = "Host(`grafana.procopius.dk`)";
service = "grafana";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
alertmanager = {
rule = "Host(`alertmanager.procopius.dk`)";
service = "alertmanager";
entryPoints = [ "websecure" ];
middlewares = [ "internal-whitelist" ];
tls = { certResolver = "letsencrypt"; };
};
jellyfin = {
rule = "Host(`jellyfin.procopius.dk`)";
service = "jellyfin";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
sonarr = {
rule = "Host(`sonarr.procopius.dk`)";
service = "sonarr";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
radarr = {
rule = "Host(`radarr.procopius.dk`)";
service = "radarr";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
ente = {
rule = "Host(`ente.procopius.dk`)";
service = "ente";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
photos = {
rule = "Host(`photos.procopius.dk`)";
service = "photos";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
minio = {
rule = "Host(`minio.procopius.dk`)";
service = "minio";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
minio-api = {
rule = "Host(`minio-api.procopius.dk`)";
service = "minio-api";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
account = {
rule = "Host(`account.procopius.dk`)";
service = "account";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
auth = {
rule = "Host(`auth.procopius.dk`)";
service = "auth";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
nas = {
rule = "Host(`nas.procopius.dk`)";
service = "nas";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
umami = {
rule = "Host(`umami.procopius.dk`)";
service = "umami";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
mesterjakob = {
rule = "Host(`mester.jakobblum.dk`)";
service = "mesterjakob";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
catchAll = {
rule = "HostRegexp(`.+`)";
service = "nginx";
entryPoints = [ "websecure" ];
tls = { certResolver = "letsencrypt"; };
};
}

View file

@ -1,38 +0,0 @@
{ lib, config, ... }:
{
proxmox.loadBalancer.servers = [ { url = "https://192.168.1.205:8006"; } ];
proxmox.loadBalancer.serversTransport = "insecureTransport";
traefik.loadBalancer.servers = [ { url = "http://localhost:8080"; } ];
forgejo.loadBalancer.servers = [ { url = "http://forgejo.lab:3000"; } ];
nginx.loadBalancer.servers = [ { url = "https://192.168.1.226:4433"; } ];
nginx.loadBalancer.serversTransport = "insecureTransport";
prometheus.loadBalancer.servers = [ { url = "http://monitor.lab:9090"; } ];
grafana.loadBalancer.servers = [ { url = "http://monitor.lab:3000"; } ];
alertmanager.loadBalancer.servers = [ { url = "http://monitor.lab:9093"; } ];
# from nginx
account.loadBalancer.servers = [ { url = "http://192.168.1.226:3001"; } ];
auth.loadBalancer.servers = [ { url = "http://192.168.1.226:3005"; } ];
ente.loadBalancer.servers = [ { url = "http://192.168.1.226:8087"; } ];
photos.loadBalancer.servers = [ { url = "http://192.168.1.226:3000"; } ];
minio.loadBalancer.servers = [ { url = "http://192.168.1.226:3201"; } ];
minio-api.loadBalancer.servers = [ { url = "http://192.168.1.226:3200"; } ];
nas.loadBalancer.servers = [ { url = "https://192.168.1.226:5001"; } ];
nas.loadBalancer.serversTransport = "insecureTransport";
jellyfin.loadBalancer.servers = [ { url = "http://192.168.1.226:8096"; } ];
radarr.loadBalancer.servers = [ { url = "http://192.168.1.226:7878"; } ];
sonarr.loadBalancer.servers = [ { url = "http://192.168.1.226:8989"; } ];
umami.loadBalancer.servers = [ { url = "http://192.168.1.226:3333"; } ];
mesterjakob.loadBalancer.servers = [ { url = "http://192.168.1.226:4200"; } ];
}

View file

@ -1,11 +1,11 @@
{ lib, config, ... }:
{
entryPoints = {
web = {
address = ":80";
asDefault = true;
allowACMEByPass = true;
http.redirections.entrypoint = {
priority = 10;
to = "websecure";
scheme = "https";
};
@ -21,6 +21,8 @@
};
};
providers.file.watch = true;
api = {
dashboard = true;
insecure = true;
@ -37,7 +39,7 @@
dnsChallenge = {
provider = "cloudflare";
delayBeforeCheck = 10;
resolvers = [ "1.1.1.1:53" "8.8.8.8:53" ];
resolvers = ["1.1.1.1:53" "8.8.8.8:53"];
};
};
};

View file

@ -1,10 +1,16 @@
{ config, pkgs, modulesPath, lib, ... }:
{
config,
pkgs,
modulesPath,
lib,
...
}: {
imports = [
../../templates/base.nix
./networking.nix
./traefik.nix
./promtail.nix
./sops.nix
./oauth2proxy.nix
];
}

View file

@ -0,0 +1,76 @@
# /etc/nixos/configuration.nix
{
config,
lib,
pkgs,
...
}: let
oauth2ProxyKeyFile = config.sops.secrets."oauth2-proxy-env".path;
in {
services.oauth2-proxy = {
enable = true;
package = pkgs.oauth2-proxy;
keyFile = oauth2ProxyKeyFile;
provider = "keycloak-oidc"; # Use "oidc" for standard OIDC providers like Keycloak
oidcIssuerUrl = "https://keycloak.procopius.dk/realms/homelab";
clientID = "oauth2-proxy"; # Matches the client ID in Keycloak
# Public URL for oauth2-proxy itself, where Keycloak redirects back to
redirectURL = "https://oauth.procopius.dk/oauth2/callback";
upstream = ["static://202"];
extraConfig = {
code-challenge-method = "S256";
# email-domain = "*";
auth-logging = true;
request-logging = true;
whitelist-domain = ".procopius.dk";
pass-host-header = true;
skip-provider-button = true;
};
# Cookie configuration
cookie = {
name = "_oauth2_proxy_homelab";
domain = ".procopius.dk";
secure = true;
httpOnly = true;
expire = "24h";
refresh = "1h";
};
# Listen address for oauth2-proxy internally. Traefik will forward to this.
httpAddress = "http://127.0.0.1:4180"; # Ensure this port is not blocked by your firewall internally
# Reverse proxy settings for headers
reverseProxy = true; # Set to true because it's behind Traefik
# Headers to set for the upstream applications after successful authentication
setXauthrequest = true; # Set X-Auth-Request-User, X-Auth-Request-Email etc.
passBasicAuth = true; # Pass HTTP Basic Auth headers
passHostHeader = true; # Pass the original Host header to the upstream
# Authorization rules for who can access
# You can restrict by email domain (allows everyone from that domain)
email.domains = ["*"]; # Allows any authenticated user from Keycloak
# Or restrict by specific email addresses (if you want tighter control):
# email.addresses = allowedOauth2ProxyEmails;
# Logging
requestLogging = true;
# Optional: If you use specific scopes for Keycloak (e.g., if you want groups claim)
# scope = "openid profile email";
# If you specifically added a 'groups' claim in Keycloak:
scope = "openid profile email";
# You can add extra command-line flags here if needed, e.g., for debug logging
# extraConfig = {
#
# };
};
# Expose the internal port for oauth2-proxy if needed for debugging or direct access (less common)
networking.firewall.allowedTCPPorts = [4180];
}

View file

@ -1,7 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}: {
# This ensures the directory exists at boot, owned by traefik (writer) and readable by promtail.
systemd.tmpfiles.rules = [
"d /var/log/traefik 0755 traefik promtail -"
@ -12,9 +14,9 @@
job_name = "traefik";
static_configs = [
{
targets = [ "localhost" ];
targets = ["localhost"];
labels = {
job = "/var/log/traefik/*.log";
job = "traefik";
host = config.networking.hostName;
env = "proxmox";
instance = "${config.networking.hostName}.lab"; # prometheus scrape target

View file

@ -0,0 +1,10 @@
{
sops.secrets."traefik-env" = {
sopsFile = ../../secrets/traefik/secrets.yml;
mode = "0440";
};
sops.secrets."oauth2-proxy-env" = {
sopsFile = ../../secrets/traefik/secrets.yml;
mode = "0440";
};
}

View file

@ -1,23 +1,59 @@
{ config, lib, pkgs, ... }:
let
staticConfig = import ./configuration/static.nix { inherit lib config; };
middlewaresConfig = import ./configuration/middlewares.nix { inherit lib config; };
routersConfig = import ./configuration/routers.nix { inherit lib config; };
servicesConfig = import ./configuration/services.nix { inherit lib config; };
in
{
config,
lib,
...
}: let
# Import router and service declarations grouped in files
infraRouters = import ./configuration/infra/routers.nix;
infraServices = import ./configuration/infra/services.nix;
monitoringRouters = import ./configuration/monitoring/routers.nix;
monitoringServices = import ./configuration/monitoring/services.nix;
mediaRouters = import ./configuration/media-center/routers.nix;
mediaServices = import ./configuration/media-center/services.nix;
photosRouters = import ./configuration/photos/routers.nix;
photosServices = import ./configuration/photos/services.nix;
authRouters = import ./configuration/auth/routers.nix;
authServices = import ./configuration/auth/services.nix;
miscRouters = import ./configuration/misc/routers.nix;
miscServices = import ./configuration/misc/services.nix;
middlewares = import ./configuration/middlewares.nix;
staticConfig = import ./configuration/static.nix;
# Combine all routers and services from groups
allRouters = lib.foldl' (acc: routers: acc // routers) {} [
infraRouters
monitoringRouters
mediaRouters
photosRouters
authRouters
miscRouters
];
allServices = lib.foldl' (acc: services: acc // services) {} [
infraServices
monitoringServices
mediaServices
photosServices
authServices
miscServices
];
in {
services.traefik = {
enable = true;
environmentFiles = [config.sops.secrets."traefik-env".path];
# ==== Static Configuration ====
staticConfigOptions = staticConfig;
# ==== Dynamic Configuration ====
dynamicConfigOptions.http = {
routers = routersConfig;
services = servicesConfig;
middlewares = middlewaresConfig;
routers = allRouters;
services = allServices;
middlewares = middlewares;
serversTransports = {
insecureTransport = {
@ -26,11 +62,4 @@ in
};
};
};
systemd.services.traefik.serviceConfig.Environment = [
"CLOUDFLARE_DNS_API_TOKEN=gQYyG6cRw-emp_qpsUj9TrkYgoVC1v9UUtv94ozA"
"CLOUDFLARE_ZONE_API_TOKEN=gQYyG6cRw-emp_qpsUj9TrkYgoVC1v9UUtv94ozA"
];
virtualisation.docker.enable = true;
}