feat(refactor): WIP refactor server modules
This commit is contained in:
@@ -3,109 +3,253 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
email = "adam@cnst.dev";
|
email = "adam@cnst.dev";
|
||||||
domain = "cnix.dev";
|
domain = "cnix.dev";
|
||||||
|
ip = "192.168.88.14";
|
||||||
user = "share";
|
user = "share";
|
||||||
group = "share";
|
group = "share";
|
||||||
uid = 994;
|
uid = 994;
|
||||||
gid = 993;
|
gid = 993;
|
||||||
|
|
||||||
traefik = {
|
infra = {
|
||||||
enable = true;
|
authentik = {
|
||||||
};
|
|
||||||
tailscale = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
unbound = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
homepage-dashboard = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
n8n = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
bazarr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
prowlarr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
lidarr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
sonarr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
radarr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
jellyseerr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
jellyfin = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
uptime-kuma = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
gitea = {
|
|
||||||
enable = true;
|
|
||||||
url = "git.cnst.dev";
|
|
||||||
cloudflared = {
|
|
||||||
tunnelId = "33e2fb8e-ecef-4d42-b845-6d15e216e448";
|
|
||||||
credentialsFile = config.age.secrets.giteaCloudflared.path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
vaultwarden = {
|
|
||||||
enable = true;
|
|
||||||
url = "vault.cnst.dev";
|
|
||||||
cloudflared = {
|
|
||||||
tunnelId = "fdd98086-6a4c-44f2-bba0-eb86b833cce5";
|
|
||||||
credentialsFile = config.age.secrets.vaultwardenCloudflared.path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
www = {
|
|
||||||
enable = true;
|
|
||||||
url = "cnst.dev";
|
|
||||||
cloudflared = {
|
|
||||||
tunnelId = "e5076186-efb7-405a-998c-6155af7fb221";
|
|
||||||
credentialsFile = config.age.secrets.wwwCloudflared.path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
authentik = {
|
|
||||||
enable = true;
|
|
||||||
url = "auth.cnst.dev";
|
|
||||||
cloudflared = {
|
|
||||||
tunnelId = "b66f9368-db9e-4302-8b48-527cda34a635";
|
|
||||||
credentialsFile = config.age.secrets.authentikCloudflared.path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
nextcloud = {
|
|
||||||
enable = true;
|
|
||||||
adminpassFile = config.age.secrets.nextcloudAdminPass.path;
|
|
||||||
};
|
|
||||||
fail2ban = {
|
|
||||||
enable = true;
|
|
||||||
apiKeyFile = config.age.secrets.cloudflareFirewallApiKey.path;
|
|
||||||
zoneId = "0027acdfb8bbe010f55b676ad8698dfb";
|
|
||||||
};
|
|
||||||
keepalived = {
|
|
||||||
enable = true;
|
|
||||||
interface = "enp6s0";
|
|
||||||
};
|
|
||||||
podman = {
|
|
||||||
enable = true;
|
|
||||||
gluetun.enable = true;
|
|
||||||
qbittorrent = {
|
|
||||||
enable = true;
|
enable = true;
|
||||||
port = 8080;
|
url = "auth.cnst.dev";
|
||||||
|
port = 9000;
|
||||||
|
cloudflared = {
|
||||||
|
tunnelId = "b66f9368-db9e-4302-8b48-527cda34a635";
|
||||||
|
credentialsFile = config.age.secrets.authentikCloudflared.path;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
slskd = {
|
traefik = {
|
||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
pihole = {
|
tailscale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
port = 8053;
|
|
||||||
};
|
};
|
||||||
|
unbound = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
fail2ban = {
|
||||||
|
enable = true;
|
||||||
|
apiKeyFile = config.age.secrets.cloudflareFirewallApiKey.path;
|
||||||
|
zoneId = "0027acdfb8bbe010f55b676ad8698dfb";
|
||||||
|
};
|
||||||
|
keepalived = {
|
||||||
|
enable = true;
|
||||||
|
interface = "enp6s0";
|
||||||
|
};
|
||||||
|
gluetun = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
podman = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
www = {
|
||||||
|
enable = true;
|
||||||
|
url = "cnst.dev";
|
||||||
|
port = 8283;
|
||||||
|
cloudflared = {
|
||||||
|
tunnelId = "e5076186-efb7-405a-998c-6155af7fb221";
|
||||||
|
credentialsFile = config.age.secrets.wwwCloudflared.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
# homepage-dashboard = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "";
|
||||||
|
# port = "8082";
|
||||||
|
# };
|
||||||
|
# n8n = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "n8n";
|
||||||
|
# port = "5678";
|
||||||
|
# homepage = {
|
||||||
|
# name = "n8n";
|
||||||
|
# description = "A workflow automation platform";
|
||||||
|
# icon = "n8n.svg";
|
||||||
|
# category = "Services";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# bazarr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "bazarr";
|
||||||
|
# port = 6767;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Bazarr";
|
||||||
|
# description = "Subtitle manager";
|
||||||
|
# icon = "bazarr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# prowlarr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "prowlarr";
|
||||||
|
# port = 9696;
|
||||||
|
# homepage = {
|
||||||
|
# name = "prowlarr";
|
||||||
|
# description = "PVR indexer";
|
||||||
|
# icon = "prowlarr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# flaresolverr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "flaresolverr";
|
||||||
|
# port = 8191;
|
||||||
|
# homepage = {
|
||||||
|
# name = "FlareSolverr";
|
||||||
|
# description = "Proxy to bypass Cloudflare/DDoS-GUARD protection";
|
||||||
|
# icon = "flaresolverr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# lidarr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "lidarr";
|
||||||
|
# port = 8686;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Lidarr";
|
||||||
|
# description = "Music collection manager";
|
||||||
|
# icon = "lidarr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# sonarr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "sonarr";
|
||||||
|
# port = 8989;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Sonarr";
|
||||||
|
# description = "Internet PVR for Usenet and Torrents";
|
||||||
|
# icon = "sonarr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# radarr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "radarr";
|
||||||
|
# port = 7878;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Radarr";
|
||||||
|
# description = "Movie collection manager";
|
||||||
|
# icon = "radarr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# jellyseerr = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "jellyseerr";
|
||||||
|
# port = 5055;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Jellyseerr";
|
||||||
|
# description = "Media request and discovery manager";
|
||||||
|
# icon = "jellyserr.svg";
|
||||||
|
# category = "Arr";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# jellyfin = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "fin";
|
||||||
|
# exposure = "tailscale";
|
||||||
|
# port = 8096;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Jellyfin";
|
||||||
|
# description = "The Free Software Media System";
|
||||||
|
# icon = "jellyfin.svg";
|
||||||
|
# category = "Media";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# uptime-kuma = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "uptime";
|
||||||
|
# port = 3001;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Uptime Kuma";
|
||||||
|
# description = "Service monitoring tool";
|
||||||
|
# icon = "uptime-kuma.svg";
|
||||||
|
# category = "Services";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# gitea = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "git";
|
||||||
|
# exposure = "tunnel";
|
||||||
|
# port = 5003;
|
||||||
|
# cloudflared = {
|
||||||
|
# tunnelId = "33e2fb8e-ecef-4d42-b845-6d15e216e448";
|
||||||
|
# credentialsFile = config.age.secrets.giteaCloudflared.path;
|
||||||
|
# };
|
||||||
|
# homepage = {
|
||||||
|
# name = "Gitea";
|
||||||
|
# description = "Git with a cup of tea";
|
||||||
|
# icon = "gitea.svg";
|
||||||
|
# category = "Services";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
vaultwarden = {
|
||||||
|
enable = true;
|
||||||
|
# subdomain = "vault";
|
||||||
|
# exposure = "tunnel";
|
||||||
|
# port = 8222;
|
||||||
|
# cloudflared = {
|
||||||
|
# tunnelId = "fdd98086-6a4c-44f2-bba0-eb86b833cce5";
|
||||||
|
# credentialsFile = config.age.secrets.vaultwardenCloudflared.path;
|
||||||
|
# };
|
||||||
|
# homepage = {
|
||||||
|
# name = "Vaultwarden";
|
||||||
|
# description = "Password manager";
|
||||||
|
# icon = "vaultwarden.svg";
|
||||||
|
# category = "Services";
|
||||||
|
# };
|
||||||
|
};
|
||||||
|
nextcloud = {
|
||||||
|
enable = true;
|
||||||
|
subdomain = "cloud";
|
||||||
|
exposure = "local";
|
||||||
|
port = 8182;
|
||||||
|
adminpassFile = config.age.secrets.nextcloudAdminPass.path;
|
||||||
|
homepage = {
|
||||||
|
name = "Nextcloud";
|
||||||
|
description = "A safe home for all your data";
|
||||||
|
icon = "nextcloud.svg";
|
||||||
|
category = "Services";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# qbittorrent = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "qbt";
|
||||||
|
# port = 8080;
|
||||||
|
# homepage = {
|
||||||
|
# name = "qBittorrent";
|
||||||
|
# description = "Torrent client";
|
||||||
|
# icon = "qbittorrent.svg";
|
||||||
|
# category = "Downloads";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# slskd = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "slskd";
|
||||||
|
# port = 5030;
|
||||||
|
# homepage = {
|
||||||
|
# name = "Soulseek";
|
||||||
|
# description = "Web-based Soulseek client";
|
||||||
|
# icon = "slskd.svg";
|
||||||
|
# category = "Downloads";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# pihole = {
|
||||||
|
# enable = true;
|
||||||
|
# subdomain = "pihole";
|
||||||
|
# port = 8053;
|
||||||
|
# homepage = {
|
||||||
|
# name = "PiHole";
|
||||||
|
# description = "Adblocking and DNS service";
|
||||||
|
# icon = "pi-hole.svg";
|
||||||
|
# category = "Services";
|
||||||
|
# path = "/admin";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,28 +123,31 @@
|
|||||||
server = {
|
server = {
|
||||||
imports = [
|
imports = [
|
||||||
./server
|
./server
|
||||||
./server/fail2ban
|
|
||||||
./server/homepage-dashboard
|
./server/infra/authentik
|
||||||
./server/nextcloud
|
./server/infra/fail2ban
|
||||||
./server/vaultwarden
|
./server/infra/keepalived
|
||||||
./server/bazarr
|
./server/infra/podman
|
||||||
./server/prowlarr
|
./server/infra/postgres
|
||||||
./server/lidarr
|
./server/infra/tailscale
|
||||||
./server/radarr
|
./server/infra/traefik
|
||||||
./server/sonarr
|
./server/infra/unbound
|
||||||
./server/jellyseerr
|
./server/infra/www
|
||||||
./server/jellyfin
|
|
||||||
./server/n8n
|
./server/services/bazarr
|
||||||
./server/podman
|
./server/services/flaresolverr
|
||||||
./server/unbound
|
./server/services/gitea
|
||||||
./server/uptime-kuma
|
./server/services/homepage-dashboard
|
||||||
./server/keepalived
|
./server/services/jellyfin
|
||||||
./server/gitea
|
./server/services/jellyseerr
|
||||||
./server/postgres
|
./server/services/lidarr
|
||||||
./server/traefik
|
./server/services/n8n
|
||||||
./server/www
|
./server/services/nextcloud
|
||||||
./server/authentik
|
./server/services/prowlarr
|
||||||
./server/tailscale
|
./server/services/radarr
|
||||||
|
./server/services/sonarr
|
||||||
|
./server/services/uptime-kuma
|
||||||
|
./server/services/vaultwarden
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
settings = {
|
settings = {
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "bazarr";
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${unit}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Bazarr";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Subtitle manager";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "bazarr.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Arr";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.${unit} = {
|
|
||||||
enable = true;
|
|
||||||
user = srv.user;
|
|
||||||
group = srv.group;
|
|
||||||
};
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,11 @@ in {
|
|||||||
Domain name to be used to access the server services via Caddy reverse proxy
|
Domain name to be used to access the server services via Caddy reverse proxy
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
ip = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "The local IP of the service.";
|
||||||
|
};
|
||||||
user = lib.mkOption {
|
user = lib.mkOption {
|
||||||
default = "share";
|
default = "share";
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -62,6 +67,86 @@ in {
|
|||||||
Time zone to be used for the server services
|
Time zone to be used for the server services
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
services = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkEnableOption "the service";
|
||||||
|
subdomain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "The subdomain for the service (e.g., 'jellyfin')";
|
||||||
|
};
|
||||||
|
exposure = lib.mkOption {
|
||||||
|
type = lib.types.enum ["local" "tunnel" "tailscale"];
|
||||||
|
default = "local";
|
||||||
|
description = "Controls where the service is exposed";
|
||||||
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 80;
|
||||||
|
description = "The port to host service on.";
|
||||||
|
};
|
||||||
|
configDir = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "/var/lib/${name}";
|
||||||
|
description = "Configuration directory for ${name}.";
|
||||||
|
};
|
||||||
|
cloudflared = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
credentialsFile = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "/path/to/cloudflare-credentials.json";
|
||||||
|
# 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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
description = "Cloudflare tunnel configuration for this service.";
|
||||||
|
};
|
||||||
|
homepage = lib.mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Display name on the homepage.";
|
||||||
|
};
|
||||||
|
description = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "A short description for the homepage tile.";
|
||||||
|
};
|
||||||
|
icon = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "Zervices c00l stuff";
|
||||||
|
description = "Icon file name for the homepage tile.";
|
||||||
|
};
|
||||||
|
category = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Homepage category grouping.";
|
||||||
|
};
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
example = "/admin";
|
||||||
|
description = "Optional path suffix for homepage links (e.g. /admin).";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
description = "Homepage metadata for this service.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
|
||||||
self,
|
self,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
unit = "authentik";
|
unit = "authentik";
|
||||||
cfg = config.server.${unit};
|
cfg = config.server.infra.${unit};
|
||||||
srv = config.server;
|
srv = config.server.infra.www.domain;
|
||||||
in {
|
in {
|
||||||
options.server.${unit} = {
|
options.server.infra.${unit} = {
|
||||||
enable = lib.mkEnableOption {
|
enable = lib.mkEnableOption {
|
||||||
description = "Enable ${unit}";
|
description = "Enable ${unit}";
|
||||||
};
|
};
|
||||||
@@ -17,6 +16,10 @@ in {
|
|||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "auth.${srv.www.domain}";
|
default = "auth.${srv.www.domain}";
|
||||||
};
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
description = "The local port the service runs on";
|
||||||
|
};
|
||||||
cloudflared = {
|
cloudflared = {
|
||||||
credentialsFile = lib.mkOption {
|
credentialsFile = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -31,21 +34,11 @@ in {
|
|||||||
example = "00000000-0000-0000-0000-000000000000";
|
example = "00000000-0000-0000-0000-000000000000";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
homepage.name = lib.mkOption {
|
homepage = {
|
||||||
type = lib.types.str;
|
name = "Authentik";
|
||||||
default = "Authentik";
|
description = "An open-source IdP for modern SSO";
|
||||||
};
|
icon = "authentik.svg";
|
||||||
homepage.description = lib.mkOption {
|
category = "Services";
|
||||||
type = lib.types.str;
|
|
||||||
default = "An open-source IdP for modern SSO";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "authentik.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Services";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,8 +52,8 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
server = {
|
server.infra = {
|
||||||
fail2ban = lib.mkIf cfg.enable {
|
fail2ban = {
|
||||||
jails = {
|
jails = {
|
||||||
authentik = {
|
authentik = {
|
||||||
serviceName = "authentik";
|
serviceName = "authentik";
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
cfg = config.server.fail2ban;
|
cfg = config.server.infra.fail2ban;
|
||||||
in {
|
in {
|
||||||
options.server.fail2ban = {
|
options.server.infra.fail2ban = {
|
||||||
enable = lib.mkEnableOption {
|
enable = lib.mkEnableOption {
|
||||||
description = "Enable cloudflare fail2ban";
|
description = "Enable cloudflare fail2ban";
|
||||||
};
|
};
|
||||||
@@ -61,6 +61,7 @@ in {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
services.fail2ban = {
|
services.fail2ban = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -3,27 +3,24 @@
|
|||||||
config,
|
config,
|
||||||
self,
|
self,
|
||||||
...
|
...
|
||||||
}:
|
}: let
|
||||||
let
|
|
||||||
unit = "keepalived";
|
unit = "keepalived";
|
||||||
cfg = config.server.${unit};
|
cfg = config.server.infra.${unit};
|
||||||
|
|
||||||
hostCfg =
|
hostCfg = hostname:
|
||||||
hostname:
|
if hostname == "sobotka"
|
||||||
if hostname == "sobotka" then
|
then {
|
||||||
{
|
ip = "192.168.88.14";
|
||||||
ip = "192.168.88.14";
|
priority = 20;
|
||||||
priority = 20;
|
state = "MASTER";
|
||||||
state = "MASTER";
|
}
|
||||||
}
|
else if hostname == "ziggy"
|
||||||
else if hostname == "ziggy" then
|
then {
|
||||||
{
|
ip = "192.168.88.12";
|
||||||
ip = "192.168.88.12";
|
priority = 10;
|
||||||
priority = 10;
|
state = "BACKUP";
|
||||||
state = "BACKUP";
|
}
|
||||||
}
|
else throw "No keepalived config defined for host ${hostname}";
|
||||||
else
|
|
||||||
throw "No keepalived config defined for host ${hostname}";
|
|
||||||
|
|
||||||
_self = hostCfg config.networking.hostName;
|
_self = hostCfg config.networking.hostName;
|
||||||
|
|
||||||
@@ -34,9 +31,8 @@ let
|
|||||||
|
|
||||||
# Remove self from peers
|
# Remove self from peers
|
||||||
peers = builtins.filter (ip: ip != _self.ip) allPeers;
|
peers = builtins.filter (ip: ip != _self.ip) allPeers;
|
||||||
in
|
in {
|
||||||
{
|
options.server.infra.${unit} = {
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
enable = lib.mkEnableOption {
|
||||||
description = "Enable ${unit}";
|
description = "Enable ${unit}";
|
||||||
};
|
};
|
||||||
6
modules/server/infra/podman/acquis.yaml
Normal file
6
modules/server/infra/podman/acquis.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
filenames:
|
||||||
|
- /var/log/traefik/access.log
|
||||||
|
poll_without_inotify: true
|
||||||
|
labels:
|
||||||
|
type: traefik
|
||||||
162
modules/server/infra/podman/default.nix
Normal file
162
modules/server/infra/podman/default.nix
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
self,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
infra = config.server.infra;
|
||||||
|
cfg = config.server.services;
|
||||||
|
|
||||||
|
getPiholeSecret = hostname:
|
||||||
|
if hostname == "ziggy"
|
||||||
|
then [config.age.secrets.piholeZiggy.path]
|
||||||
|
else if hostname == "sobotka"
|
||||||
|
then [config.age.secrets.pihole.path]
|
||||||
|
else throw "Unknown hostname: ${hostname}";
|
||||||
|
in {
|
||||||
|
options.server.infra = {
|
||||||
|
podman.enable = lib.mkEnableOption "Enables Podman";
|
||||||
|
gluetun.enable = lib.mkEnableOption "Enables gluetun";
|
||||||
|
};
|
||||||
|
config = lib.mkIf infra.podman.enable {
|
||||||
|
age.secrets = {
|
||||||
|
pihole.file = "${self}/secrets/${config.networking.hostName}Pihole.age";
|
||||||
|
slskd.file = "${self}/secrets/slskd.age";
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualisation = {
|
||||||
|
containers.enable = true;
|
||||||
|
podman.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = lib.mkIf cfg.pihole.enable {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
53
|
||||||
|
5335
|
||||||
|
];
|
||||||
|
allowedUDPPorts = [
|
||||||
|
53
|
||||||
|
5335
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualisation.oci-containers.containers = lib.mkMerge [
|
||||||
|
(lib.mkIf infra.gluetun.enable {
|
||||||
|
gluetun = {
|
||||||
|
image = "qmcgaw/gluetun";
|
||||||
|
ports = [
|
||||||
|
"8388:8388"
|
||||||
|
"58846:58846"
|
||||||
|
"8080:8080"
|
||||||
|
"5030:5030"
|
||||||
|
"5031:5031"
|
||||||
|
"50300:50300"
|
||||||
|
];
|
||||||
|
devices = ["/dev/net/tun:/dev/net/tun"];
|
||||||
|
autoStart = true;
|
||||||
|
extraOptions = [
|
||||||
|
"--cap-add=NET_ADMIN"
|
||||||
|
];
|
||||||
|
volumes = ["/var:/gluetun"];
|
||||||
|
environmentFiles = [
|
||||||
|
config.age.secrets.gluetunEnvironment.path
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
DEV_MODE = "false";
|
||||||
|
VPN_SERVICE_PROVIDER = "mullvad";
|
||||||
|
VPN_TYPE = "wireguard";
|
||||||
|
SERVER_CITIES = "Stockholm";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.qbittorrent.enable {
|
||||||
|
qbittorrent = {
|
||||||
|
image = "ghcr.io/hotio/qbittorrent:latest";
|
||||||
|
autoStart = true;
|
||||||
|
dependsOn = ["gluetun"];
|
||||||
|
ports = [
|
||||||
|
"8080:8080"
|
||||||
|
"58846:58846"
|
||||||
|
];
|
||||||
|
extraOptions = [
|
||||||
|
"--network=container:gluetun"
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"/var/lib/qbittorrent:/config:rw"
|
||||||
|
"/mnt/data/media/downloads:/downloads:rw"
|
||||||
|
];
|
||||||
|
environmentFiles = [
|
||||||
|
config.age.secrets.gluetunEnvironment.path
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
PUID = "994";
|
||||||
|
PGID = "993";
|
||||||
|
TZ = "Europe/Stockholm";
|
||||||
|
WEBUI_PORT = "${builtins.toString cfg.qbittorrent.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.slskd.enable {
|
||||||
|
slskd = {
|
||||||
|
image = "slskd/slskd:latest";
|
||||||
|
autoStart = true;
|
||||||
|
dependsOn = ["gluetun"];
|
||||||
|
ports = [
|
||||||
|
"5030:5030"
|
||||||
|
"5031:5031"
|
||||||
|
"50300:50300"
|
||||||
|
];
|
||||||
|
extraOptions = [
|
||||||
|
"--network=container:gluetun"
|
||||||
|
];
|
||||||
|
volumes = [
|
||||||
|
"/var/lib/slskd:/app:rw"
|
||||||
|
"/mnt/data/media/downloads:/downloads:rw"
|
||||||
|
];
|
||||||
|
environmentFiles = [
|
||||||
|
config.age.secrets.gluetunEnvironment.path
|
||||||
|
config.age.secrets.slskd.path
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
TZ = "Europe/Stockholm";
|
||||||
|
PUID = "981";
|
||||||
|
PGID = "982";
|
||||||
|
SLSKD_REMOTE_CONFIGURATION = "true";
|
||||||
|
SLSKD_REMOTE_FILE_MANAGEMENT = "true";
|
||||||
|
SLSKD_DOWNLOADS_DIR = "/downloads";
|
||||||
|
SLSKD_UMASK = "022";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.pihole.enable {
|
||||||
|
pihole = {
|
||||||
|
autoStart = true;
|
||||||
|
image = "pihole/pihole:2025.08.0";
|
||||||
|
volumes = [
|
||||||
|
"/var/lib/pihole:/etc/pihole/"
|
||||||
|
"/var/lib/dnsmasq.d:/etc/dnsmasq.d/"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
TZ = "Europe/Stockholm";
|
||||||
|
CUSTOM_CACHE_SIZE = "0";
|
||||||
|
WEBTHEME = "default-darker";
|
||||||
|
};
|
||||||
|
environmentFiles = getPiholeSecret config.networking.hostName;
|
||||||
|
ports = [
|
||||||
|
"53:53/tcp"
|
||||||
|
"53:53/udp"
|
||||||
|
"8053:80/tcp"
|
||||||
|
];
|
||||||
|
extraOptions = [
|
||||||
|
"--cap-add=NET_ADMIN"
|
||||||
|
"--cap-add=SYS_NICE"
|
||||||
|
"--cap-add=SYS_TIME"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
}: let
|
}: let
|
||||||
inherit (lib) types mkOption;
|
inherit (lib) types mkOption;
|
||||||
|
|
||||||
cfg = config.server.postgresql;
|
cfg = config.server.infra.postgresql;
|
||||||
in {
|
in {
|
||||||
options = {
|
options = {
|
||||||
server.postgresql = {
|
server.infra.postgresql = {
|
||||||
upgradeTargetPackage = mkOption {
|
upgradeTargetPackage = mkOption {
|
||||||
type = types.nullOr types.package;
|
type = types.nullOr types.package;
|
||||||
default = null;
|
default = null;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
}: let
|
}: let
|
||||||
inherit (lib) types mkOption;
|
inherit (lib) types mkOption;
|
||||||
|
|
||||||
cfg = config.server.postgresql;
|
cfg = config.server.infra.postgresql;
|
||||||
|
|
||||||
database = {name, ...}: {
|
database = {name, ...}: {
|
||||||
options = {
|
options = {
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
options = {
|
options = {
|
||||||
server.postgresql = {
|
server.infra.postgresql = {
|
||||||
databases = mkOption {
|
databases = mkOption {
|
||||||
type = types.listOf (types.submodule database);
|
type = types.listOf (types.submodule database);
|
||||||
default = [];
|
default = [];
|
||||||
@@ -5,16 +5,11 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
with lib; let
|
with lib; let
|
||||||
cfg = config.server.tailscale;
|
cfg = config.server.infra.tailscale;
|
||||||
in {
|
in {
|
||||||
options.server.tailscale = {
|
options.server.infra.tailscale = {
|
||||||
enable = mkEnableOption "Enable tailscale server configuration";
|
enable = mkEnableOption "Enable tailscale server configuration";
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "ts.cnst.dev";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
age.secrets.sobotkaTsAuth.file = "${self}/secrets/sobotkaTsAuth.age";
|
age.secrets.sobotkaTsAuth.file = "${self}/secrets/sobotkaTsAuth.age";
|
||||||
|
|
||||||
192
modules/server/infra/traefik/default.nix
Normal file
192
modules/server/infra/traefik/default.nix
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
self,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
inherit (lib) mkEnableOption mkIf types;
|
||||||
|
|
||||||
|
cfg = config.server.infra.traefik;
|
||||||
|
srv = config.server;
|
||||||
|
|
||||||
|
# Generates all Traefik routers from the central service list
|
||||||
|
generateRouters = services:
|
||||||
|
lib.mapAttrs' (
|
||||||
|
name: service: let
|
||||||
|
domain =
|
||||||
|
if service.exposure == "tunnel"
|
||||||
|
then "cnst.dev"
|
||||||
|
else if service.exposure == "tailscale"
|
||||||
|
then "ts.cnst.dev"
|
||||||
|
else srv.domain;
|
||||||
|
in
|
||||||
|
lib.nameValuePair "${service.subdomain}" {
|
||||||
|
entryPoints = ["websecure"];
|
||||||
|
rule = "Host(`${service.subdomain}.${domain}`)";
|
||||||
|
service = service.subdomain;
|
||||||
|
tls.certResolver = "letsencrypt";
|
||||||
|
}
|
||||||
|
) (lib.filterAttrs (name: service: service.enable) services);
|
||||||
|
|
||||||
|
# Generates all Traefik backend services
|
||||||
|
generateServices = services:
|
||||||
|
lib.mapAttrs' (name: service:
|
||||||
|
lib.nameValuePair "${service.subdomain}" {
|
||||||
|
loadBalancer.servers = [{url = "http://localhost:${toString service.port}";}];
|
||||||
|
}) (lib.filterAttrs (name: service: service.enable) services);
|
||||||
|
|
||||||
|
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.infra.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 = "traefik";
|
||||||
|
group = "traefik";
|
||||||
|
};
|
||||||
|
crowdsecApi.file = "${self}/secrets/crowdsecApi.age";
|
||||||
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
accesslog = {filepath = "/var/lib/traefik/logs/access.log";};
|
||||||
|
|
||||||
|
tracing = {};
|
||||||
|
api = {
|
||||||
|
dashboard = true;
|
||||||
|
insecure = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
certificatesResolvers = {
|
||||||
|
vpn.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 = ":80";
|
||||||
|
forwardedHeaders.insecure = true;
|
||||||
|
http.redirections.entryPoint = {
|
||||||
|
to = "websecure";
|
||||||
|
scheme = "https";
|
||||||
|
permanent = true;
|
||||||
|
};
|
||||||
|
# http.middlewares = "crowdsec@file";
|
||||||
|
};
|
||||||
|
|
||||||
|
websecure = {
|
||||||
|
address = ":443";
|
||||||
|
forwardedHeaders.insecure = true;
|
||||||
|
http.tls = {
|
||||||
|
certResolver = "letsencrypt";
|
||||||
|
domains = [
|
||||||
|
{
|
||||||
|
main = "cnix.dev";
|
||||||
|
sans = ["*.cnix.dev"];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
main = "ts.cnst.dev";
|
||||||
|
sans = ["*ts.cnst.dev"];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
# http.middlewares = "crowdsec@file";
|
||||||
|
};
|
||||||
|
|
||||||
|
experimental = {
|
||||||
|
address = ":1111";
|
||||||
|
forwardedHeaders.insecure = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
experimental = {
|
||||||
|
# Install the Crowdsec Bouncer plugin
|
||||||
|
plugins = {
|
||||||
|
#enabled = "true";
|
||||||
|
bouncer = {
|
||||||
|
moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin";
|
||||||
|
version = "v1.4.5";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
dynamicConfigOptions = {
|
||||||
|
http = {
|
||||||
|
# Generate the services from your central list
|
||||||
|
services = generateServices srv.services;
|
||||||
|
|
||||||
|
# Generate the routers and manually add the special 'api' router
|
||||||
|
routers =
|
||||||
|
(generateRouters srv.services)
|
||||||
|
// {
|
||||||
|
api = {
|
||||||
|
entryPoints = ["websecure"];
|
||||||
|
rule = "Host(`traefik.${srv.domain}`)";
|
||||||
|
service = "api@internal";
|
||||||
|
tls.certResolver = "letsencrypt";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# middlewares = {
|
||||||
|
# crowdsec = {
|
||||||
|
# plugin = {
|
||||||
|
# bouncer = {
|
||||||
|
# enabled = "true";
|
||||||
|
# logLevel = "DEBUG";
|
||||||
|
# crowdsecLapiKeyFile = config.age.secrets.crowdsecApi.path;
|
||||||
|
# crowdsecMode = "live";
|
||||||
|
# crowdsecLapiHost = ":4223";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,7 +5,13 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
unit = "unbound";
|
unit = "unbound";
|
||||||
cfg = config.server.${unit};
|
cfg = config.server.infra.${unit};
|
||||||
|
srv = config.server;
|
||||||
|
|
||||||
|
generateLocalRecords = services:
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: service: "local-data: \"${service.subdomain}.${srv.domain}. A ${srv.ip}\""
|
||||||
|
) (lib.filterAttrs (name: service: service.enable) services);
|
||||||
|
|
||||||
hostIp = hostname:
|
hostIp = hostname:
|
||||||
if hostname == "ziggy"
|
if hostname == "ziggy"
|
||||||
@@ -14,11 +20,12 @@
|
|||||||
then "192.168.88.14"
|
then "192.168.88.14"
|
||||||
else throw "No IP defined for host ${hostname}";
|
else throw "No IP defined for host ${hostname}";
|
||||||
in {
|
in {
|
||||||
options.server.${unit} = {
|
options.server.infra.${unit} = {
|
||||||
enable = lib.mkEnableOption {
|
enable = lib.mkEnableOption {
|
||||||
description = "Enable ${unit}";
|
description = "Enable ${unit}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
services = {
|
services = {
|
||||||
# resolved.enable = lib.mkForce false;
|
# resolved.enable = lib.mkForce false;
|
||||||
@@ -97,6 +104,10 @@ in {
|
|||||||
"255.255.255.255/32"
|
"255.255.255.255/32"
|
||||||
"2001:db8::/32"
|
"2001:db8::/32"
|
||||||
];
|
];
|
||||||
|
local-data = generateLocalRecords srv.services;
|
||||||
|
local-data-ptr = [
|
||||||
|
"local-data: \"traefik.${srv.domain}. A ${srv.ip}\""
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs,
|
|
||||||
self,
|
self,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit (lib) mkOption mkEnableOption mkIf types;
|
inherit (lib) mkIf mkEnableOption mkOption types;
|
||||||
cfg = config.server.www;
|
cfg = config.server.infra.www;
|
||||||
srv = config.server;
|
|
||||||
in {
|
in {
|
||||||
options.server.www = {
|
options.server.infra.www = {
|
||||||
enable = mkEnableOption {
|
enable = mkEnableOption {
|
||||||
description = "Enable personal website";
|
description = "Enable personal website";
|
||||||
};
|
};
|
||||||
@@ -20,6 +18,12 @@ in {
|
|||||||
Public domain name to be used to access the server services via Traefik reverse proxy
|
Public domain name to be used to access the server services via Traefik reverse proxy
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 8283;
|
||||||
|
description = "The port to host webservice on.";
|
||||||
|
};
|
||||||
|
|
||||||
cloudflared = {
|
cloudflared = {
|
||||||
credentialsFile = lib.mkOption {
|
credentialsFile = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -41,8 +45,8 @@ in {
|
|||||||
wwwCloudflared.file = "${self}/secrets/wwwCloudflared.age";
|
wwwCloudflared.file = "${self}/secrets/wwwCloudflared.age";
|
||||||
};
|
};
|
||||||
|
|
||||||
server = {
|
server.infra = {
|
||||||
fail2ban = lib.mkIf config.server.www.enable {
|
fail2ban = {
|
||||||
jails = {
|
jails = {
|
||||||
nginx-404 = {
|
nginx-404 = {
|
||||||
serviceName = "nginx";
|
serviceName = "nginx";
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "jellyfin";
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
srv = config.server;
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "fin.${srv.tailscale.url}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Jellyfin";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "The Free Software Media System";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "jellyfin.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Media";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.${unit} = {
|
|
||||||
enable = true;
|
|
||||||
user = srv.user;
|
|
||||||
group = srv.group;
|
|
||||||
};
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
jellyfin-ffmpeg
|
|
||||||
];
|
|
||||||
services.traefik = {
|
|
||||||
dynamicConfigOptions = {
|
|
||||||
http = {
|
|
||||||
services.${unit}.loadBalancer.servers = [{url = "http://localhost:8096";}];
|
|
||||||
routers = {
|
|
||||||
jellyfinRouter = {
|
|
||||||
entryPoints = ["websecure"];
|
|
||||||
rule = "Host(`${cfg.url}`)";
|
|
||||||
service = "${unit}";
|
|
||||||
tls.certResolver = "letsencrypt";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "jellyseerr";
|
|
||||||
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 = "seer.${srv.tailscale.url}";
|
|
||||||
default = "jellyseerr.${srv.domain}";
|
|
||||||
};
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 5055;
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Jellyseerr";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Media request and discovery manager";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "jellyseerr.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Arr";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.${unit} = {
|
|
||||||
enable = true;
|
|
||||||
port = cfg.port;
|
|
||||||
};
|
|
||||||
services.traefik = {
|
|
||||||
dynamicConfigOptions = {
|
|
||||||
http = {
|
|
||||||
services.jellyseerr.loadBalancer.servers = [{url = "http://localhost:${toString cfg.port}";}];
|
|
||||||
routers = {
|
|
||||||
jellyseerr = {
|
|
||||||
entryPoints = ["websecure"];
|
|
||||||
rule = "Host(`${cfg.url}`)";
|
|
||||||
service = "${unit}";
|
|
||||||
tls.certResolver = "letsencrypt";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "lidarr";
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${unit}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Lidarr";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Music collection manager";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "lidarr.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Arr";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.${unit} = {
|
|
||||||
enable = true;
|
|
||||||
user = srv.user;
|
|
||||||
group = srv.group;
|
|
||||||
};
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "n8n";
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${unit}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "n8n";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "A workflow automation platform";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "n8n.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Services";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services = {
|
|
||||||
n8n = {
|
|
||||||
enable = true;
|
|
||||||
openFirewall = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
traefik = {
|
|
||||||
dynamicConfigOptions = {
|
|
||||||
http = {
|
|
||||||
services.n8n.loadBalancer.servers = [{url = "http://127.0.0.1:5678";}];
|
|
||||||
routers = {
|
|
||||||
n8n = {
|
|
||||||
entryPoints = ["websecure"];
|
|
||||||
rule = "Host(`${cfg.url}`)";
|
|
||||||
service = "n8n";
|
|
||||||
tls.certResolver = "letsencrypt";
|
|
||||||
# middlewares = ["authentik"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
self,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.podman;
|
|
||||||
|
|
||||||
piholeUrl =
|
|
||||||
if config.networking.hostName == "sobotka"
|
|
||||||
then "pihole0"
|
|
||||||
else if config.networking.hostName == "ziggy"
|
|
||||||
then "pihole1"
|
|
||||||
else throw "Unknown hostname";
|
|
||||||
|
|
||||||
getPiholeSecret = hostname:
|
|
||||||
if hostname == "ziggy"
|
|
||||||
then [config.age.secrets.piholeZiggy.path]
|
|
||||||
else if hostname == "sobotka"
|
|
||||||
then [config.age.secrets.pihole.path]
|
|
||||||
else throw "Unknown hostname: ${hostname}";
|
|
||||||
in {
|
|
||||||
options.server.podman = {
|
|
||||||
enable = lib.mkEnableOption "Enables Podman";
|
|
||||||
gluetun.enable = lib.mkEnableOption "Enables gluetun";
|
|
||||||
|
|
||||||
qbittorrent = {
|
|
||||||
enable = lib.mkEnableOption "Enable qBittorrent";
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "qbt.${srv.domain}";
|
|
||||||
};
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 8080;
|
|
||||||
description = "The port to host qBittorrent on.";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "qBittorrent";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Torrent client";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "qbittorrent.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Downloads";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
slskd = {
|
|
||||||
enable = lib.mkEnableOption "Enable Soulseek";
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "slskd.${srv.domain}";
|
|
||||||
};
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 5030;
|
|
||||||
description = "The port to host Soulseek webui on.";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "slskd";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Web-based Soulseek client";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "slskd.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Downloads";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pihole = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable";
|
|
||||||
};
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 8053;
|
|
||||||
description = "The port to host PiHole on.";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${piholeUrl}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "PiHole";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Adblocking and DNS service";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "pi-hole.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Services";
|
|
||||||
};
|
|
||||||
homepage.path = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/admin";
|
|
||||||
description = "Optional path suffix for homepage links (e.g. /admin).";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
age.secrets = {
|
|
||||||
pihole.file = "${self}/secrets/${config.networking.hostName}Pihole.age";
|
|
||||||
slskd.file = "${self}/secrets/slskd.age";
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualisation = {
|
|
||||||
containers.enable = true;
|
|
||||||
podman.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall = lib.mkIf cfg.pihole.enable {
|
|
||||||
allowedTCPPorts = [
|
|
||||||
53
|
|
||||||
5335
|
|
||||||
];
|
|
||||||
allowedUDPPorts = [
|
|
||||||
53
|
|
||||||
5335
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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 {
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
virtualisation.oci-containers.containers = lib.mkMerge [
|
|
||||||
(lib.mkIf cfg.gluetun.enable {
|
|
||||||
gluetun = {
|
|
||||||
image = "qmcgaw/gluetun";
|
|
||||||
ports = [
|
|
||||||
"8388:8388"
|
|
||||||
"58846:58846"
|
|
||||||
"8080:8080"
|
|
||||||
"5030:5030"
|
|
||||||
"5031:5031"
|
|
||||||
"50300:50300"
|
|
||||||
];
|
|
||||||
devices = ["/dev/net/tun:/dev/net/tun"];
|
|
||||||
autoStart = true;
|
|
||||||
extraOptions = [
|
|
||||||
"--cap-add=NET_ADMIN"
|
|
||||||
];
|
|
||||||
volumes = ["/var:/gluetun"];
|
|
||||||
environmentFiles = [
|
|
||||||
config.age.secrets.gluetunEnvironment.path
|
|
||||||
];
|
|
||||||
environment = {
|
|
||||||
DEV_MODE = "false";
|
|
||||||
VPN_SERVICE_PROVIDER = "mullvad";
|
|
||||||
VPN_TYPE = "wireguard";
|
|
||||||
SERVER_CITIES = "Stockholm";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
(lib.mkIf cfg.qbittorrent.enable {
|
|
||||||
qbittorrent = {
|
|
||||||
image = "ghcr.io/hotio/qbittorrent:latest";
|
|
||||||
autoStart = true;
|
|
||||||
dependsOn = ["gluetun"];
|
|
||||||
ports = [
|
|
||||||
"8080:8080"
|
|
||||||
"58846:58846"
|
|
||||||
];
|
|
||||||
extraOptions = [
|
|
||||||
"--network=container:gluetun"
|
|
||||||
];
|
|
||||||
volumes = [
|
|
||||||
"/var/lib/qbittorrent:/config:rw"
|
|
||||||
"/mnt/data/media/downloads:/downloads:rw"
|
|
||||||
];
|
|
||||||
environmentFiles = [
|
|
||||||
config.age.secrets.gluetunEnvironment.path
|
|
||||||
];
|
|
||||||
environment = {
|
|
||||||
PUID = "994";
|
|
||||||
PGID = "993";
|
|
||||||
TZ = "Europe/Stockholm";
|
|
||||||
WEBUI_PORT = "${builtins.toString cfg.qbittorrent.port}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
(lib.mkIf cfg.slskd.enable {
|
|
||||||
slskd = {
|
|
||||||
image = "slskd/slskd:latest";
|
|
||||||
autoStart = true;
|
|
||||||
dependsOn = ["gluetun"];
|
|
||||||
ports = [
|
|
||||||
"5030:5030"
|
|
||||||
"5031:5031"
|
|
||||||
"50300:50300"
|
|
||||||
];
|
|
||||||
extraOptions = [
|
|
||||||
"--network=container:gluetun"
|
|
||||||
];
|
|
||||||
volumes = [
|
|
||||||
"/var/lib/slskd:/app:rw"
|
|
||||||
"/mnt/data/media/downloads:/downloads:rw"
|
|
||||||
];
|
|
||||||
environmentFiles = [
|
|
||||||
config.age.secrets.gluetunEnvironment.path
|
|
||||||
config.age.secrets.slskd.path
|
|
||||||
];
|
|
||||||
environment = {
|
|
||||||
TZ = "Europe/Stockholm";
|
|
||||||
PUID = "981";
|
|
||||||
PGID = "982";
|
|
||||||
SLSKD_REMOTE_CONFIGURATION = "true";
|
|
||||||
SLSKD_REMOTE_FILE_MANAGEMENT = "true";
|
|
||||||
SLSKD_DOWNLOADS_DIR = "/downloads";
|
|
||||||
SLSKD_UMASK = "022";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
|
|
||||||
(lib.mkIf cfg.pihole.enable {
|
|
||||||
pihole = {
|
|
||||||
autoStart = true;
|
|
||||||
image = "pihole/pihole:2025.08.0";
|
|
||||||
volumes = [
|
|
||||||
"/var/lib/pihole:/etc/pihole/"
|
|
||||||
"/var/lib/dnsmasq.d:/etc/dnsmasq.d/"
|
|
||||||
];
|
|
||||||
environment = {
|
|
||||||
TZ = "Europe/Stockholm";
|
|
||||||
CUSTOM_CACHE_SIZE = "0";
|
|
||||||
WEBTHEME = "default-darker";
|
|
||||||
};
|
|
||||||
environmentFiles = getPiholeSecret config.networking.hostName;
|
|
||||||
ports = [
|
|
||||||
"53:53/tcp"
|
|
||||||
"53:53/udp"
|
|
||||||
"8053:80/tcp"
|
|
||||||
];
|
|
||||||
extraOptions = [
|
|
||||||
"--cap-add=NET_ADMIN"
|
|
||||||
"--cap-add=SYS_NICE"
|
|
||||||
"--cap-add=SYS_TIME"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "prowlarr";
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${unit}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Prowlarr";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "PVR indexer";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "prowlarr.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Arr";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services = {
|
|
||||||
${unit} = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
flaresolverr = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "radarr";
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${unit}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Radarr";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Film collection manager";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "radarr.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Arr";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.${unit} = {
|
|
||||||
enable = true;
|
|
||||||
user = srv.user;
|
|
||||||
group = srv.group;
|
|
||||||
};
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
17
modules/server/services/bazarr/default.nix
Normal file
17
modules/server/services/bazarr/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "bazarr";
|
||||||
|
srv = config.server;
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.${unit} = {
|
||||||
|
enable = true;
|
||||||
|
user = srv.user;
|
||||||
|
group = srv.group;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
16
modules/server/services/flaresolverr/default.nix
Normal file
16
modules/server/services/flaresolverr/default.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "flaresolverr";
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services = {
|
||||||
|
${unit} = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,60 +6,15 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
unit = "gitea";
|
unit = "gitea";
|
||||||
srv = config.server;
|
cfg = config.server.services.${unit};
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
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.";
|
|
||||||
};
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
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 {
|
config = lib.mkIf cfg.enable {
|
||||||
age.secrets = {
|
age.secrets = {
|
||||||
giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age";
|
giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age";
|
||||||
};
|
};
|
||||||
|
|
||||||
server = {
|
server.infra = {
|
||||||
fail2ban = lib.mkIf config.server.fail2ban.enable {
|
fail2ban = {
|
||||||
jails = {
|
jails = {
|
||||||
gitea = {
|
gitea = {
|
||||||
serviceName = "gitea";
|
serviceName = "gitea";
|
||||||
@@ -144,24 +99,7 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.traefik = {
|
server.infra.postgresql.databases = [
|
||||||
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 = [
|
|
||||||
{
|
{
|
||||||
database = "gitea";
|
database = "gitea";
|
||||||
}
|
}
|
||||||
@@ -5,37 +5,9 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
unit = "homepage-dashboard";
|
unit = "homepage-dashboard";
|
||||||
cfg = config.server.homepage-dashboard;
|
cfg = config.server.services.homepage-dashboard;
|
||||||
srv = config.server;
|
srv = config.server;
|
||||||
in {
|
in {
|
||||||
options.server.homepage-dashboard = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
misc = lib.mkOption {
|
|
||||||
default = [];
|
|
||||||
type = lib.types.listOf (
|
|
||||||
lib.types.attrsOf (
|
|
||||||
lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
href = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
siteMonitor = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
age.secrets = {
|
age.secrets = {
|
||||||
homepageEnvironment = {
|
homepageEnvironment = {
|
||||||
@@ -112,31 +84,24 @@ in {
|
|||||||
"Downloads"
|
"Downloads"
|
||||||
"Services"
|
"Services"
|
||||||
];
|
];
|
||||||
hl = config.server;
|
hl = config.server.services;
|
||||||
mergedServices = hl // hl.podman;
|
|
||||||
homepageServices = x: (lib.attrsets.filterAttrs (
|
homepageServices = x: (lib.attrsets.filterAttrs (
|
||||||
name: value: value ? homepage && value.homepage.category == x
|
_name: value: value ? homepage && value.homepage.category == x
|
||||||
)
|
)
|
||||||
mergedServices);
|
srv.services);
|
||||||
in
|
in
|
||||||
lib.lists.forEach homepageCategories (cat: {
|
lib.lists.forEach homepageCategories (cat: {
|
||||||
"${cat}" =
|
"${cat}" =
|
||||||
lib.lists.forEach
|
lib.lists.forEach (lib.attrsets.mapAttrsToList (name: _value: name) (homepageServices "${cat}"))
|
||||||
(lib.attrsets.mapAttrsToList (name: value: {
|
|
||||||
inherit name;
|
|
||||||
url = value.url;
|
|
||||||
homepage = value.homepage;
|
|
||||||
}) (homepageServices "${cat}"))
|
|
||||||
(x: {
|
(x: {
|
||||||
"${x.homepage.name}" = {
|
"${hl.${x}.homepage.name}" = {
|
||||||
icon = x.homepage.icon;
|
icon = hl.${x}.homepage.icon;
|
||||||
description = x.homepage.description;
|
description = hl.${x}.homepage.description;
|
||||||
href = "https://${x.url}${x.homepage.path or ""}";
|
href = "https://${hl.${x}.url}";
|
||||||
siteMonitor = "https://${x.url}${x.homepage.path or ""}";
|
siteMonitor = "https://${hl.${x}.url}";
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
++ [{Misc = cfg.misc;}]
|
|
||||||
++ [
|
++ [
|
||||||
{
|
{
|
||||||
Glances = let
|
Glances = let
|
||||||
@@ -212,25 +177,6 @@ in {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
21
modules/server/services/jellyfin/default.nix
Normal file
21
modules/server/services/jellyfin/default.nix
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "jellyfin";
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
srv = config.server;
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.${unit} = {
|
||||||
|
enable = true;
|
||||||
|
user = srv.user;
|
||||||
|
group = srv.group;
|
||||||
|
};
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
jellyfin-ffmpeg
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
15
modules/server/services/jellyseerr/default.nix
Normal file
15
modules/server/services/jellyseerr/default.nix
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "jellyseerr";
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.${unit} = {
|
||||||
|
enable = true;
|
||||||
|
port = cfg.port;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
17
modules/server/services/lidarr/default.nix
Normal file
17
modules/server/services/lidarr/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "lidarr";
|
||||||
|
srv = config.server;
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.${unit} = {
|
||||||
|
enable = true;
|
||||||
|
user = srv.user;
|
||||||
|
group = srv.group;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
17
modules/server/services/n8n/default.nix
Normal file
17
modules/server/services/n8n/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "n8n";
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services = {
|
||||||
|
n8n = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,52 +6,16 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
unit = "nextcloud";
|
unit = "nextcloud";
|
||||||
cfg = config.server.${unit};
|
cfg = config.server.services.${unit};
|
||||||
srv = config.server;
|
srv = config.server;
|
||||||
in {
|
in {
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
adminpassFile = lib.mkOption {
|
|
||||||
type = lib.types.path;
|
|
||||||
};
|
|
||||||
adminuser = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "cnst";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "cloud.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Nextcloud";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "A safe home for all your data";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "nextcloud.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Services";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
age.secrets = {
|
age.secrets = {
|
||||||
nextcloudAdminPass.file = "${self}/secrets/nextcloudAdminPass.age";
|
nextcloudAdminPass.file = "${self}/secrets/nextcloudAdminPass.age";
|
||||||
nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age";
|
nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age";
|
||||||
};
|
};
|
||||||
|
|
||||||
server.fail2ban = lib.mkIf config.server.fail2ban.enable {
|
server.infra.fail2ban = lib.mkIf srv.infra.fail2ban.enable {
|
||||||
jails = {
|
jails = {
|
||||||
nextcloud = {
|
nextcloud = {
|
||||||
serviceName = "${unit}";
|
serviceName = "${unit}";
|
||||||
@@ -107,7 +71,7 @@ in {
|
|||||||
dbhost = "/run/postgresql";
|
dbhost = "/run/postgresql";
|
||||||
dbname = "nextcloud";
|
dbname = "nextcloud";
|
||||||
adminuser = "cnst";
|
adminuser = "cnst";
|
||||||
adminpassFile = cfg.adminpassFile;
|
adminpassFile = config.age.secrets.nextcloudAdminPass.path;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,21 +90,9 @@ in {
|
|||||||
forceSSL = false;
|
forceSSL = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
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 = [
|
server.infra.postgresql.databases = [
|
||||||
{
|
{
|
||||||
database = "nextcloud";
|
database = "nextcloud";
|
||||||
}
|
}
|
||||||
16
modules/server/services/prowlarr/default.nix
Normal file
16
modules/server/services/prowlarr/default.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "prowlarr";
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services = {
|
||||||
|
${unit} = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
17
modules/server/services/radarr/default.nix
Normal file
17
modules/server/services/radarr/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "radarr";
|
||||||
|
srv = config.server;
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.${unit} = {
|
||||||
|
enable = true;
|
||||||
|
user = srv.user;
|
||||||
|
group = srv.group;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
17
modules/server/services/sonarr/default.nix
Normal file
17
modules/server/services/sonarr/default.nix
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "sonarr";
|
||||||
|
srv = config.server;
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.${unit} = {
|
||||||
|
enable = true;
|
||||||
|
user = srv.user;
|
||||||
|
group = srv.group;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
16
modules/server/services/uptime-kuma/default.nix
Normal file
16
modules/server/services/uptime-kuma/default.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
unit = "uptime-kuma";
|
||||||
|
cfg = config.server.services.${unit};
|
||||||
|
in {
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services = {
|
||||||
|
${unit} = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,57 +5,37 @@
|
|||||||
self,
|
self,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit (lib) mkIf mkEnableOption;
|
unit = "vaultwarden";
|
||||||
vcfg = config.services.vaultwarden.config;
|
cfg = config.server.services.${unit};
|
||||||
cfg = config.server.vaultwarden;
|
www = config.server.infra.www;
|
||||||
in {
|
in {
|
||||||
options = {
|
config = lib.mkIf cfg.enable {
|
||||||
server.vaultwarden = {
|
|
||||||
enable = mkEnableOption "Enables vaultwarden";
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${cfg.domain}";
|
|
||||||
};
|
|
||||||
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 = mkIf cfg.enable {
|
|
||||||
age.secrets = {
|
age.secrets = {
|
||||||
vaultwardenCloudflared.file = "${self}/secrets/vaultwardenCloudflared.age";
|
vaultwardenCloudflared.file = "${self}/secrets/vaultwardenCloudflared.age";
|
||||||
vaultwardenEnvironment.file = "${self}/secrets/vaultwardenEnvironment.age";
|
vaultwardenEnvironment.file = "${self}/secrets/vaultwardenEnvironment.age";
|
||||||
};
|
};
|
||||||
|
|
||||||
server = {
|
server.infra = {
|
||||||
fail2ban = lib.mkIf config.server.fail2ban.enable {
|
fail2ban = {
|
||||||
jails = {
|
jails = {
|
||||||
vaultwarden = {
|
vaultwarden = {
|
||||||
serviceName = "vaultwarden";
|
serviceName = "${unit}";
|
||||||
failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$'';
|
failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.backup-vaultwarden.serviceConfig = {
|
|
||||||
User = "root";
|
|
||||||
Group = "root";
|
|
||||||
};
|
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
cloudflared = {
|
||||||
|
enable = true;
|
||||||
|
tunnels.${cfg.cloudflared.tunnelId} = {
|
||||||
|
credentialsFile = cfg.cloudflared.credentialsFile;
|
||||||
|
default = "http_status:404";
|
||||||
|
ingress."${cfg.url}".service = "http://localhost:${toString cfg.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
vaultwarden = {
|
vaultwarden = {
|
||||||
enable = true;
|
enable = true;
|
||||||
environmentFile = config.age.secrets.vaultwardenEnvironment.path;
|
environmentFile = config.age.secrets.vaultwardenEnvironment.path;
|
||||||
@@ -63,10 +43,10 @@ in {
|
|||||||
backupDir = "/var/backup/vaultwarden";
|
backupDir = "/var/backup/vaultwarden";
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
DOMAIN = "https://${cfg.url}";
|
DOMAIN = "https://vault.${www.url}";
|
||||||
SIGNUPS_ALLOWED = false;
|
SIGNUPS_ALLOWED = false;
|
||||||
ROCKET_ADDRESS = "127.0.0.1";
|
ROCKET_ADDRESS = "127.0.0.1";
|
||||||
ROCKET_PORT = 8222;
|
ROCKET_PORT = cfg.port;
|
||||||
IP_HEADER = "CF-Connecting-IP";
|
IP_HEADER = "CF-Connecting-IP";
|
||||||
|
|
||||||
logLevel = "warn";
|
logLevel = "warn";
|
||||||
@@ -76,14 +56,10 @@ in {
|
|||||||
showPasswordHint = false;
|
showPasswordHint = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
cloudflared = {
|
};
|
||||||
enable = true;
|
systemd.services.backup-vaultwarden.serviceConfig = {
|
||||||
tunnels.${cfg.cloudflared.tunnelId} = {
|
User = "root";
|
||||||
credentialsFile = cfg.cloudflared.credentialsFile;
|
Group = "root";
|
||||||
default = "http_status:404";
|
|
||||||
ingress."${cfg.url}".service = "http://${vcfg.ROCKET_ADDRESS}:${toString vcfg.ROCKET_PORT}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "sonarr";
|
|
||||||
srv = config.server;
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/${unit}";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "${unit}.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Sonarr";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Series collection manager";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "sonarr.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Arr";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.${unit} = {
|
|
||||||
enable = true;
|
|
||||||
user = srv.user;
|
|
||||||
group = srv.group;
|
|
||||||
};
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
{
|
|
||||||
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 = "traefik";
|
|
||||||
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 = {
|
|
||||||
vpn.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"];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
main = "ts.cnst.dev";
|
|
||||||
sans = ["*ts.cnst.dev"];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
unit = "uptime-kuma";
|
|
||||||
cfg = config.server.${unit};
|
|
||||||
srv = config.server;
|
|
||||||
in {
|
|
||||||
options.server.${unit} = {
|
|
||||||
enable = lib.mkEnableOption {
|
|
||||||
description = "Enable ${unit}";
|
|
||||||
};
|
|
||||||
configDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/uptime-kuma";
|
|
||||||
};
|
|
||||||
url = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "uptime.${srv.domain}";
|
|
||||||
};
|
|
||||||
homepage.name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Uptime Kuma";
|
|
||||||
};
|
|
||||||
homepage.description = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Service monitoring tool";
|
|
||||||
};
|
|
||||||
homepage.icon = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "uptime-kuma.svg";
|
|
||||||
};
|
|
||||||
homepage.category = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "Services";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
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"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
11
secrets/crowdsecApi.age
Normal file
11
secrets/crowdsecApi.age
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 t9iOEg lf7aPbZX2v3WGzE/KI/069DBObphqrDtjq7rhNriGl8
|
||||||
|
Vv+Pqk6DbcE5R1A9135gVKroCex1xKsCLPETZdT3yTg
|
||||||
|
-> ssh-ed25519 KUYMFA XxtBSmCwrQCZ9G3VcCrbzTdMshTK1pjlHPYj7fke818
|
||||||
|
9tO2EcnHPD6v3TNeuZdL+zP39SM5R5q7om5sCFDB8lg
|
||||||
|
-> ssh-ed25519 76RhUQ I6O/fYFRqYxExC9uLijZr6/kFze7uze0cIudCsl2jTo
|
||||||
|
WAwb822vVj5UtUAdE1oVJ0/q6nQbWqdx0OHuGEogO7M
|
||||||
|
-> ssh-ed25519 Jf8sqw gWBoe4HhXNw7Ih58lQ/L2vBoQfbU1ht8+ZSLUx/4TWk
|
||||||
|
xor0ieJ2UI5bK4rSlCM0dX61PVbxYE37FNry0YSmHG4
|
||||||
|
--- Cp8b3eTb3NfjPFvBE12a2c+Yni2jW6DZUK10IaXmmvo
|
||||||
|
w<EFBFBD>xq<EFBFBD><EFBFBD>z:<3A>.{<7B>?<3F><><EFBFBD>f<EFBFBD><66><1D><><16><>A<EFBFBD>jT<6A><54>{<7B>J <20><>
|
||||||
11
secrets/mikrotikSecret.age
Normal file
11
secrets/mikrotikSecret.age
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
age-encryption.org/v1
|
||||||
|
-> ssh-ed25519 t9iOEg MLi7IOM8QlpvlCMSmo4SwZbTwZ9pyysSbiMMuWD/dyU
|
||||||
|
cotV5TJf7oyyXIaAmu8n9Ie1rl27i8w7hsduwtQFnis
|
||||||
|
-> ssh-ed25519 KUYMFA BhFQ/RXOH8L7gl/FSabAUv28fbaod+muvTGSV3rYQSs
|
||||||
|
fWqwAkhSAmg6YB+yEtj0e83Q4XO/r+TBnMTN7vXBNqU
|
||||||
|
-> ssh-ed25519 76RhUQ b1fDfGPNdJ9c3wtr8ww0mW5K4fKJxpxxTZy/ZCECWzs
|
||||||
|
qhbvucUrv7dzOPKUmUaRs/AtXtwQfy/qp5HnaYzZ5eQ
|
||||||
|
-> ssh-ed25519 Jf8sqw 19D2ztjyxJfGQAiUOTdgWyC0ZFso/wrC9VPEkmI34U8
|
||||||
|
PavT5O8M6Zc2Num9Hb2sY+F3UmMPqRgjUZxuvP6AhyM
|
||||||
|
--- uYOcbsL7JWoDF2mRUDLhXrbp6ssLFbQ9+a6RhAXNNPA
|
||||||
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> X<><58>PC<50><43>h<EFBFBD>;
|
||||||
@@ -63,6 +63,8 @@ in {
|
|||||||
"wwwCloudflared.age".publicKeys = kima ++ sobotka;
|
"wwwCloudflared.age".publicKeys = kima ++ sobotka;
|
||||||
"authentikCloudflared.age".publicKeys = kima ++ sobotka;
|
"authentikCloudflared.age".publicKeys = kima ++ sobotka;
|
||||||
"sobotkaTsAuth.age".publicKeys = kima ++ sobotka;
|
"sobotkaTsAuth.age".publicKeys = kima ++ sobotka;
|
||||||
|
"mikrotikSecret.age".publicKeys = kima ++ sobotka;
|
||||||
|
"crowdsecApi.age".publicKeys = kima ++ sobotka;
|
||||||
|
|
||||||
# Ziggy-specific
|
# Ziggy-specific
|
||||||
"cloudflareDnsCredentialsZiggy.age".publicKeys = kima ++ ziggy;
|
"cloudflareDnsCredentialsZiggy.age".publicKeys = kima ++ ziggy;
|
||||||
|
|||||||
Reference in New Issue
Block a user