feat(IP): migrate to traefik and authentik, remove dead code

This commit is contained in:
2025-09-23 18:13:28 +02:00
parent b752781064
commit 86624f362d
37 changed files with 1202 additions and 803 deletions

View File

@@ -127,14 +127,12 @@
./server/fail2ban
./server/homepage-dashboard
./server/nextcloud
./server/keycloak
./server/vaultwarden
./server/bazarr
./server/prowlarr
./server/lidarr
./server/radarr
./server/sonarr
./server/syncthing
./server/jellyseerr
./server/jellyfin
./server/podman
@@ -143,6 +141,9 @@
./server/keepalived
./server/gitea
./server/postgres
./server/traefik
./server/www
./server/authentik
];
};
settings = {

View File

@@ -74,8 +74,6 @@ in {
wgCredentials.file = "${self}/secrets/wgCredentials.age";
wgSobotkaPrivateKey.file = "${self}/secrets/wgSobotkaPrivateKey.age";
gluetunEnvironment.file = "${self}/secrets/gluetunEnvironment.age";
keycloakCloudflared.file = "${self}/secrets/keycloakCloudflared.age";
keycloakDbPasswordFile.file = "${self}/secrets/keycloakDbPasswordFile.age";
nextcloudAdminPass.file = "${self}/secrets/nextcloudAdminPass.age";
nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age";
vaultwardenCloudflared.file = "${self}/secrets/vaultwardenCloudflared.age";

View File

@@ -39,16 +39,16 @@ in {
group = config.services.caddy.group;
environmentFile = getCloudflareCredentials config.networking.hostName;
};
# certs.${config.server.domainPublic} = {
# reloadServices = ["nginx.service"];
# domain = "${config.server.domainPublic}";
# extraDomainNames = ["*.${config.server.domainPublic}"];
# dnsProvider = "cloudflare";
# dnsResolver = "1.1.1.1:53";
# dnsPropagationCheck = true;
# group = config.services.nginx.group;
# environmentFile = getCloudflareCredentials config.networking.hostName;
# };
certs.${config.server.www.url} = {
reloadServices = ["caddy.service"];
domain = "${config.server.www.url}";
extraDomainNames = ["*.${config.server.www.url}"];
dnsProvider = "cloudflare";
dnsResolver = "1.1.1.1:53";
dnsPropagationCheck = true;
group = config.services.caddy.group;
environmentFile = getCloudflareCredentials config.networking.hostName;
};
};
services.caddy = {
@@ -67,6 +67,17 @@ in {
redir https://{host}{uri}
'';
};
"http://${config.server.www.url}" = {
extraConfig = ''
redir https://{host}{uri}
'';
};
"http://*.${config.server.www.url}" = {
extraConfig = ''
redir https://{host}{uri}
'';
};
};
};
};

View File

@@ -0,0 +1,102 @@
{
config,
lib,
pkgs,
self,
...
}: let
unit = "authentik";
cfg = config.server.${unit};
srv = config.server;
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
};
url = lib.mkOption {
type = lib.types.str;
default = "auth.${srv.domain}";
};
homepage.name = lib.mkOption {
type = lib.types.str;
default = "Authentik";
};
homepage.description = lib.mkOption {
type = lib.types.str;
default = "Open Source Identity and Access Management";
};
homepage.icon = lib.mkOption {
type = lib.types.str;
default = "authentik.svg";
};
homepage.category = lib.mkOption {
type = lib.types.str;
default = "Services";
};
};
config = lib.mkIf cfg.enable {
age.secrets.authentikEnv = {
file = "${self}/secrets/authentikEnv.age";
owner = "authentik";
};
services = {
authentik = {
enable = true;
environmentFile = config.age.secrets.authentikEnv.path;
settings = {
email = {
};
disable_startup_analytics = true;
avatars = "initials";
};
};
traefik = {
dynamicConfigOptions = {
http = {
middlewares = {
authentik = {
forwardAuth = {
tls.insecureSkipVerify = true;
address = "https://localhost:9443/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"
];
};
};
};
services = {
auth.loadBalancer.servers = [
{
url = "http://localhost:9000";
}
];
};
routers = {
auth = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`) || HostRegexp(`{subdomain:[a-z0-9]+}.${srv.domain}`) && PathPrefix(`/outpost.goauthentik.io/`)";
service = "auth";
tls.certResolver = "letsencrypt";
};
};
};
};
};
};
};
}

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
unit = "bazarr";
srv = config.server;
cfg = config.server.${unit};
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -44,11 +42,21 @@ in
user = srv.user;
group = srv.group;
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:${toString config.services.${unit}.listenPort}
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.bazarr.loadBalancer.servers = [{url = "http://127.0.0.1:${toString config.services.${unit}.listenPort}";}];
routers = {
bazarr = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "bazarr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
}

View File

@@ -27,13 +27,6 @@ in {
Domain name to be used to access the server services via Caddy reverse proxy
'';
};
domainPublic = mkOption {
default = "";
type = types.str;
description = ''
Public domain name to be used to access the server services via Caddy reverse proxy
'';
};
user = lib.mkOption {
default = "share";
type = lib.types.str;
@@ -99,6 +92,8 @@ in {
"share"
"render"
"input"
"authentik"
"traefik"
];
};
};

View File

@@ -3,13 +3,11 @@
config,
lib,
...
}:
let
}: let
unit = "gitea";
srv = config.server;
cfg = config.server.${unit};
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -101,11 +99,21 @@ in
};
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:5003
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.gitea.loadBalancer.servers = [{url = "http://127.0.0.1:5003";}];
routers = {
gitea = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "gitea";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
server.postgresql.databases = [

View File

@@ -0,0 +1,119 @@
# taken from @jtojnar
{
config,
lib,
...
}: let
unit = "gitea";
srv = config.server;
cfg = config.server.${unit};
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
};
url = lib.mkOption {
type = lib.types.str;
default = "git.${srv.domain}";
};
port = lib.mkOption {
type = lib.types.int;
default = 5003;
description = "The port to host Gitea on.";
};
homepage.name = lib.mkOption {
type = lib.types.str;
default = "Gitea";
};
homepage.description = lib.mkOption {
type = lib.types.str;
default = "Git with a cup of tea";
};
homepage.icon = lib.mkOption {
type = lib.types.str;
default = "gitea.svg";
};
homepage.category = lib.mkOption {
type = lib.types.str;
default = "Services";
};
};
config = lib.mkIf cfg.enable {
services.${unit} = {
enable = true;
appName = "cnix code forge";
database = {
type = "postgres";
socket = "/run/postgresql";
name = "gitea";
user = "gitea";
createDatabase = false;
};
lfs = {
enable = true;
};
settings = {
cors = {
ENABLED = true;
SCHEME = "https";
ALLOW_DOMAIN = cfg.url;
};
log = {
MODE = "console";
};
mailer = {
ENABLED = false;
MAILER_TYPE = "sendmail";
FROM = "noreply+adam@cnst.dev";
SENDMAIL_PATH = "/run/wrappers/bin/sendmail";
};
picture = {
DISABLE_GRAVATAR = true;
};
repository = {
DEFAULT_BRANCH = "main";
DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls";
DISABLE_DOWNLOAD_SOURCE_ARCHIVES = true;
};
indexer = {
REPO_INDEXER_ENABLED = true;
};
server = {
DOMAIN = cfg.url;
LANDING_PAGE = "explore";
HTTP_PORT = cfg.port;
ROOT_URL = "https://${cfg.url}/";
};
security = {
DISABLE_GIT_HOOKS = false;
};
service = {
DISABLE_REGISTRATION = true;
};
session = {
COOKIE_SECURE = true;
};
};
};
services.traefik.dynamicConfigOptions.http.routers."${unit}" = {
rule = "Host(`" + cfg.url + "`)";
service = "${unit}-service";
entryPoints = ["websecure"];
tls = {};
};
services.traefik.dynamicConfigOptions.http.services."${unit}-service".loadBalancer.servers = [
{url = "http://127.0.0.1:${toString cfg.port}";}
];
server.postgresql.databases = [
{
database = "gitea";
}
];
};
}

View File

@@ -2,19 +2,17 @@
config,
lib,
...
}:
let
}: let
unit = "homepage-dashboard";
cfg = config.server.homepage-dashboard;
srv = config.server;
in
{
in {
options.server.homepage-dashboard = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
};
misc = lib.mkOption {
default = [ ];
default = [];
type = lib.types.listOf (
lib.types.attrsOf (
lib.types.submodule {
@@ -38,81 +36,81 @@ in
};
};
config = lib.mkIf cfg.enable {
services.glances.enable = true;
services.${unit} = {
enable = true;
allowedHosts = srv.domain;
settings = {
layout = [
services = {
glances.enable = true;
${unit} = {
enable = true;
allowedHosts = srv.domain;
settings = {
layout = [
{
Glances = {
header = false;
style = "row";
columns = 4;
};
}
{
Arr = {
header = true;
style = "column";
};
}
{
Downloads = {
header = true;
style = "column";
};
}
{
Media = {
header = true;
style = "column";
};
}
{
Services = {
header = true;
style = "column";
};
}
];
headerStyle = "clean";
statusStyle = "dot";
hideVersion = "true";
};
widgets = [
{
Glances = {
header = false;
style = "row";
columns = 4;
openmeteo = {
label = "Kalmar";
timezone = "Europe/Stockholm";
units = "metric";
cache = 5;
latitude = 56.707262;
longitude = 16.324541;
};
}
{
Arr = {
header = true;
style = "column";
datetime = {
text_size = "x1";
format = {
hour12 = false;
timeStyle = "short";
dateStyle = "long";
};
};
}
{
Downloads = {
header = true;
style = "column";
};
}
{
Media = {
header = true;
style = "column";
};
}
{
Services = {
header = true;
style = "column";
resources = {
label = "";
memory = true;
disk = ["/"];
};
}
];
headerStyle = "clean";
statusStyle = "dot";
hideVersion = "true";
};
widgets = [
{
openmeteo = {
label = "Kalmar";
timezone = "Europe/Stockholm";
units = "metric";
cache = 5;
latitude = 56.707262;
longitude = 16.324541;
};
}
{
datetime = {
text_size = "x1";
format = {
hour12 = false;
timeStyle = "short";
dateStyle = "long";
};
};
}
{
resources = {
label = "";
memory = true;
disk = [ "/" ];
};
}
];
services =
let
services = let
homepageCategories = [
"Arr"
"Media"
@@ -122,15 +120,14 @@ in
];
hl = config.server;
mergedServices = hl // hl.podman;
homepageServices =
x:
(lib.attrsets.filterAttrs (
homepageServices = x: (lib.attrsets.filterAttrs (
name: value: value ? homepage && value.homepage.category == x
) mergedServices);
)
mergedServices);
in
lib.lists.forEach homepageCategories (cat: {
"${cat}" =
lib.lists.forEach
lib.lists.forEach homepageCategories (cat: {
"${cat}" =
lib.lists.forEach
(lib.attrsets.mapAttrsToList (name: value: {
inherit name;
url = value.url;
@@ -144,15 +141,13 @@ in
siteMonitor = "https://${x.url}${x.homepage.path or ""}";
};
});
})
++ [ { Misc = cfg.misc; } ]
++ [
{
Glances =
let
})
++ [{Misc = cfg.misc;}]
++ [
{
Glances = let
port = toString config.services.glances.port;
in
[
in [
{
Info = {
widget = {
@@ -220,14 +215,26 @@ in
};
}
];
}
];
};
services.caddy.virtualHosts."${srv.domain}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:${toString config.services.${unit}.listenPort}
'';
}
];
};
traefik = {
dynamicConfigOptions = {
http = {
services.homepage.loadBalancer.servers = [{url = "http://127.0.0.1:${toString config.services.${unit}.listenPort}";}];
routers = {
homepage = {
entryPoints = ["websecure"];
rule = "Host(`cnix.dev`)";
service = "homepage";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
};
}

View File

@@ -3,13 +3,11 @@
lib,
pkgs,
...
}:
let
}: let
service = "jellyfin";
cfg = config.server.${service};
srv = config.server;
in
{
in {
options.server.${service} = {
enable = lib.mkEnableOption {
description = "Enable ${service}";
@@ -48,11 +46,21 @@ in
environment.systemPackages = with pkgs; [
jellyfin-ffmpeg
];
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:8096
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.jellyfin.loadBalancer.servers = [{url = "http://127.0.0.1:8096";}];
routers = {
jellyfin = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "jellyfin";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
}

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
service = "jellyseerr";
srv = config.server;
cfg = config.server.${service};
in
{
in {
options.server.${service} = {
enable = lib.mkEnableOption {
description = "Enable ${service}";
@@ -43,11 +41,21 @@ in
enable = true;
port = cfg.port;
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:${toString cfg.port}
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.jellyseerr.loadBalancer.servers = [{url = "http://127.0.0.1:${toString cfg.port}";}];
routers = {
jellyseerr = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "jellyseerr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
}

View File

@@ -1,100 +0,0 @@
{
config,
lib,
pkgs,
...
}: let
unit = "keycloak";
cfg = config.server.${unit};
srv = config.server;
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
};
url = lib.mkOption {
type = lib.types.str;
default = "login.${srv.domain}";
};
homepage.name = lib.mkOption {
type = lib.types.str;
default = "Keycloak";
};
homepage.description = lib.mkOption {
type = lib.types.str;
default = "Open Source Identity and Access Management";
};
homepage.icon = lib.mkOption {
type = lib.types.str;
default = "keycloak.svg";
};
homepage.category = lib.mkOption {
type = lib.types.str;
default = "Services";
};
dbPasswordFile = lib.mkOption {
type = lib.types.path;
};
cloudflared = {
credentialsFile = lib.mkOption {
type = lib.types.str;
example = lib.literalExpression ''
pkgs.writeText "cloudflare-credentials.json" '''
{"AccountTag":"secret"."TunnelSecret":"secret","TunnelID":"secret"}
'''
'';
};
tunnelId = lib.mkOption {
type = lib.types.str;
example = "00000000-0000-0000-0000-000000000000";
};
};
};
config = lib.mkIf cfg.enable {
server.postgresql.databases = [
{
database = "keycloak";
}
];
services.cloudflared = {
enable = true;
tunnels.${cfg.cloudflared.tunnelId} = {
credentialsFile = cfg.cloudflared.credentialsFile;
default = "http_status:404";
ingress."${cfg.url}".service = "http://127.0.0.1:${
toString config.services.${unit}.settings.http-port
}";
};
};
environment.systemPackages = [
pkgs.keycloak
];
services.${unit} = {
enable = true;
initialAdminPassword = "pwpwpw";
database = {
type = "postgresql";
host = "127.0.0.1";
port = 5432;
name = "keycloak";
username = "keycloak";
passwordFile = cfg.dbPasswordFile;
useSSL = false;
};
settings = {
spi-theme-static-max-age = "-1";
spi-theme-cache-themes = false;
spi-theme-cache-templates = false;
http-port = 8821;
hostname = cfg.url;
hostname-strict = false;
hostname-strict-https = false;
proxy-headers = "xforwarded";
http-enabled = true;
};
};
};
}

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
unit = "lidarr";
srv = config.server;
cfg = config.server.${unit};
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -44,11 +42,21 @@ in
user = srv.user;
group = srv.group;
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:8686
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.lidarr.loadBalancer.servers = [{url = "http://127.0.0.1:8686";}];
routers = {
lidarr = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "lidarr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
}

View File

@@ -54,118 +54,80 @@ in {
};
};
services.${unit} = {
enable = true;
package = pkgs.nextcloud31;
hostName = "nextcloud";
configureRedis = true;
caching = {
redis = true;
};
phpOptions = {
"opcache.interned_strings_buffer" = "32";
};
maxUploadSize = "50G";
settings = {
maintenance_window_start = "1";
trusted_proxies = [
"127.0.0.1"
"::1"
];
trusted_domains = ["cloud.${srv.domain}"];
overwriteprotocol = "https";
enabledPreviewProviders = [
"OC\\Preview\\BMP"
"OC\\Preview\\GIF"
"OC\\Preview\\JPEG"
"OC\\Preview\\Krita"
"OC\\Preview\\MarkDown"
"OC\\Preview\\MP3"
"OC\\Preview\\OpenDocument"
"OC\\Preview\\PNG"
"OC\\Preview\\TXT"
"OC\\Preview\\XBitmap"
"OC\\Preview\\HEIC"
];
};
config = {
dbtype = "pgsql";
dbuser = "nextcloud";
dbhost = "/run/postgresql";
dbname = "nextcloud";
adminuser = "cnst";
adminpassFile = cfg.adminpassFile;
};
};
users.groups.nextcloud.members = [
config.services.caddy.user
];
services = {
nginx.enable = false;
phpfpm.pools.nextcloud.settings = {
"listen.owner" = config.services.caddy.user;
"listen.group" = config.services.caddy.group;
${unit} = {
enable = true;
package = pkgs.nextcloud31;
hostName = "nextcloud";
configureRedis = true;
caching = {
redis = true;
};
phpOptions = {
"opcache.interned_strings_buffer" = "32";
};
maxUploadSize = "50G";
settings = {
maintenance_window_start = "1";
trusted_proxies = [
"127.0.0.1"
"::1"
];
trusted_domains = ["cloud.${srv.domain}"];
overwriteprotocol = "https";
enabledPreviewProviders = [
"OC\\Preview\\BMP"
"OC\\Preview\\GIF"
"OC\\Preview\\JPEG"
"OC\\Preview\\Krita"
"OC\\Preview\\MarkDown"
"OC\\Preview\\MP3"
"OC\\Preview\\OpenDocument"
"OC\\Preview\\PNG"
"OC\\Preview\\TXT"
"OC\\Preview\\XBitmap"
"OC\\Preview\\HEIC"
];
};
config = {
dbtype = "pgsql";
dbuser = "nextcloud";
dbhost = "/run/postgresql";
dbname = "nextcloud";
adminuser = "cnst";
adminpassFile = cfg.adminpassFile;
};
};
caddy.virtualHosts.${cfg.url} = let
webroot = config.services.nginx.virtualHosts.nextcloud.root;
in {
useACMEHost = srv.domain;
extraConfig = ''
encode zstd gzip
root * ${webroot}
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
redir /.well-known/* /index.php{uri} 301
redir /remote/* /remote.php{uri} 301
header {
Strict-Transport-Security max-age=31536000
Permissions-Policy interest-cohort=()
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy no-referrer
X-XSS-Protection "1; mode=block"
X-Permitted-Cross-Domain-Policies none
X-Robots-Tag "noindex, nofollow"
-X-Powered-By
}
php_fastcgi unix/${config.services.phpfpm.pools.nextcloud.socket} {
root ${webroot}
env front_controller_active true
env modHeadersAvailable true
}
@forbidden {
path /build/* /tests/* /config/* /lib/* /3rdparty/* /templates/* /data/*
path /.* /autotest* /occ* /issue* /indie* /db_* /console*
not path /.well-known/*
}
error @forbidden 404
@immutable {
path *.css *.js *.mjs *.svg *.gif *.png *.jpg *.ico *.wasm *.tflite
query v=*
}
header @immutable Cache-Control "max-age=15778463, immutable"
@static {
path *.css *.js *.mjs *.svg *.gif *.png *.jpg *.ico *.wasm *.tflite
not query v=*
nginx = {
defaultListen = [
{
addr = "127.0.0.1";
port = 8182;
}
header @static Cache-Control "max-age=15778463"
{
addr = "127.0.0.1";
port = 8482;
}
];
virtualHosts.nextcloud = {
forceSSL = false;
};
};
@woff2 path *.woff2
header @woff2 Cache-Control "max-age=604800"
file_server
'';
traefik.dynamicConfigOptions.http = {
routers.nextcloud = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "nextcloud";
tls.certResolver = "letsencrypt";
};
services.nextcloud.loadBalancer.servers = [
{url = "http://127.0.0.1:8182";}
];
};
};
server.postgresql.databases = [
{
database = "nextcloud";

View File

@@ -137,31 +137,58 @@ in {
];
};
services.caddy.virtualHosts = lib.mkMerge [
services.traefik = lib.mkMerge [
(lib.mkIf cfg.pihole.enable {
dynamicConfigOptions = {
http = {
services = {
pihole.loadBalancer.servers = [{url = "http://localhost:${toString cfg.pihole.port}";}];
};
routers = {
pihole = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.pihole.url}`)";
service = "pihole";
tls.certResolver = "letsencrypt";
};
};
};
};
})
(lib.mkIf cfg.qbittorrent.enable {
"${cfg.qbittorrent.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:${toString cfg.qbittorrent.port}
'';
dynamicConfigOptions = {
http = {
services = {
qbittorrent.loadBalancer.servers = [{url = "http://localhost:${toString cfg.qbittorrent.port}";}];
};
routers = {
qbittorrent = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.qbittorrent.url}`)";
service = "qbittorrent";
tls.certResolver = "letsencrypt";
};
};
};
};
})
(lib.mkIf cfg.slskd.enable {
"${cfg.slskd.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:${toString cfg.slskd.port}
'';
};
})
(lib.mkIf cfg.pihole.enable {
"${cfg.pihole.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:${toString cfg.pihole.port}
'';
dynamicConfigOptions = {
http = {
services = {
slskd.loadBalancer.servers = [{url = "http://localhost:${toString cfg.slskd.port}";}];
};
routers = {
slskd = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.slskd.url}`)";
service = "slskd";
tls.certResolver = "letsencrypt";
};
};
};
};
})
];
@@ -268,9 +295,6 @@ in {
environment = {
TZ = "Europe/Stockholm";
CUSTOM_CACHE_SIZE = "0";
# PIHOLE_DNS_ = "10.88.0.1#5335";
# DNSSEC = "false";
# REV_SERVER = "true";
WEBTHEME = "default-darker";
};
environmentFiles = getPiholeSecret config.networking.hostName;

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
unit = "prowlarr";
srv = config.server;
cfg = config.server.${unit};
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -47,18 +45,34 @@ in
enable = true;
};
caddy = {
virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:9696
'';
};
virtualHosts."flaresolverr.${srv.domain}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:8191
'';
traefik = {
dynamicConfigOptions = {
http = {
services = {
prowlarr = {
loadBalancer.servers = [{url = "http://127.0.0.1:9696";}];
};
flaresolverr = {
loadBalancer.servers = [{url = "http://127.0.0.1:8191";}];
};
};
routers = {
prowlarr = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "prowlarr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
flaresolverr = {
entryPoints = ["websecure"];
rule = "Host(`flaresolverr.${srv.domain}`)";
service = "flaresolverr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
unit = "radarr";
srv = config.server;
cfg = config.server.${unit};
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -44,11 +42,21 @@ in
user = srv.user;
group = srv.group;
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:7878
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.radarr.loadBalancer.servers = [{url = "http://127.0.0.1:7878";}];
routers = {
radarr = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "radarr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
}

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
unit = "sonarr";
srv = config.server;
cfg = config.server.${unit};
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -44,11 +42,21 @@ in
user = srv.user;
group = srv.group;
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:8989
'';
services.traefik = {
dynamicConfigOptions = {
http = {
services.sonarr.loadBalancer.servers = [{url = "http://127.0.0.1:8989";}];
routers = {
sonarr = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "sonarr";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
}

View File

@@ -1,62 +0,0 @@
{
config,
lib,
...
}: let
unit = "syncthing";
srv = config.server;
cfg = config.server.${unit};
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
};
url = lib.mkOption {
type = lib.types.str;
default = "${unit}.${srv.domain}";
};
homepage.name = lib.mkOption {
type = lib.types.str;
default = "Syncthing";
};
homepage.description = lib.mkOption {
type = lib.types.str;
default = "Continuous file synchronization program";
};
homepage.icon = lib.mkOption {
type = lib.types.str;
default = "syncthing.svg";
};
homepage.category = lib.mkOption {
type = lib.types.str;
default = "Services";
};
};
config = lib.mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [
8384
22000
];
allowedUDPPorts = [
22000
21027
];
};
services.${unit} = {
enable = true;
user = srv.user;
guiAddress = "0.0.0.0:8384";
overrideFolders = false;
overrideDevices = false;
dataDir = "/home/${srv.user}/syncthing";
configDir = "/home/${srv.user}/syncthing/.config/syncthing";
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:8384
'';
};
};
}

View File

@@ -0,0 +1,100 @@
{
lib,
config,
pkgs,
self,
...
}: let
inherit (lib) mkEnableOption mkIf types;
cfg = config.server.traefik;
getCloudflareCredentials = hostname:
if hostname == "ziggy"
then config.age.secrets.cloudflareDnsCredentialsZiggy.path
else if hostname == "sobotka"
then config.age.secrets.cloudflareDnsCredentials.path
else throw "Unknown hostname: ${hostname}";
in {
options.server.traefik = {
enable = mkEnableOption "Enable global Traefik reverse proxy with ACME";
};
config = mkIf cfg.enable {
age.secrets.traefikEnv = {
file = "${self}/secrets/traefikEnv.age";
mode = "640";
owner = "root";
group = "traefik";
};
systemd.services.traefik = {
serviceConfig = {
EnvironmentFile = [config.age.secrets.traefikEnv.path];
};
};
networking.firewall.allowedTCPPorts = [80 443];
services = {
tailscale.permitCertUid = "traefik";
traefik = {
enable = true;
staticConfigOptions = {
log = {
level = "DEBUG";
};
tracing = {};
api = {
dashboard = true;
};
certificatesResolvers = {
tailscale.tailscale = {};
letsencrypt = {
acme = {
email = "adam@cnst.dev";
storage = "/var/lib/traefik/cert.json";
dnsChallenge = {
provider = "cloudflare";
resolvers = [
"1.1.1.1:53"
"1.0.0.1:53"
];
};
};
};
};
entryPoints = {
redis = {
address = "0.0.0.0:6381";
};
postgres = {
address = "0.0.0.0:5433";
};
web = {
address = "0.0.0.0:80";
http.redirections.entryPoint = {
to = "websecure";
scheme = "https";
permanent = true;
};
};
websecure = {
address = "0.0.0.0:443";
http.tls = {
certResolver = "letsencrypt";
domains = [
{
main = "cnix.dev";
sans = ["*.cnix.dev"];
}
];
};
};
};
};
};
};
};
}

View File

@@ -2,13 +2,11 @@
config,
lib,
...
}:
let
}: let
unit = "uptime-kuma";
cfg = config.server.${unit};
srv = config.server;
in
{
in {
options.server.${unit} = {
enable = lib.mkEnableOption {
description = "Enable ${unit}";
@@ -39,14 +37,26 @@ in
};
};
config = lib.mkIf cfg.enable {
services.${unit} = {
enable = true;
};
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = srv.domain;
extraConfig = ''
reverse_proxy http://127.0.0.1:3001
'';
services = {
${unit} = {
enable = true;
};
traefik = {
dynamicConfigOptions = {
http = {
services.uptime-kuma.loadBalancer.servers = [{url = "http://127.0.0.1:3001";}];
routers = {
uptime-kuma = {
entryPoints = ["websecure"];
rule = "Host(`${cfg.url}`)";
service = "uptime-kuma";
tls.certResolver = "letsencrypt";
# middlewares = ["authentik"];
};
};
};
};
};
};
};
}

View File

@@ -0,0 +1,44 @@
{
lib,
config,
pkgs,
...
}: let
inherit (lib) mkOption mkEnableOption mkIf types;
cfg = config.server.www;
srv = config.server;
in {
options.server.www = {
enable = mkEnableOption {
description = "Enable personal website";
};
url = mkOption {
default = "";
type = types.str;
description = ''
Public domain name to be used to access the server services via Caddy reverse proxy
'';
};
};
config = mkIf cfg.enable {
services.caddy.virtualHosts."${cfg.url}" = {
useACMEHost = cfg.url;
extraConfig = ''
handle_path /.well-known/webfinger {
header Content-Type application/jrd+json
respond `{
"subject": "acct:adam@${cfg.url}",
"links": [
{
"rel": "http://openid.net/specs/connect/1.0/issuer",
"href": "https://login.${cfg.url}/realms/cnix"
}
]
}`
}
reverse_proxy http://127.0.0.1:8283
'';
};
};
}