From 63f495fa0da5fb07667aec2a0ceae20c25687fe7 Mon Sep 17 00:00:00 2001 From: cnst Date: Mon, 13 Oct 2025 21:13:53 +0200 Subject: [PATCH] feat(refactor): WIP 2.0 some progress --- flake.nix | 39 +- hosts/default.nix | 207 +++++----- hosts/sobotka/server.nix | 359 +++++++++--------- lib/server/default.nix | 5 + lib/server/serviceurl/default.nix | 23 ++ lib/server/serviceurl/serviceurl.nix | 11 + modules/default.nix | 25 -- modules/home/programs/hyprlock/default.nix | 6 +- modules/home/services/hyprpaper/default.nix | 6 +- modules/server/default.nix | 190 +-------- modules/server/infra/default.nix | 13 + modules/server/infra/traefik/default.nix | 31 +- modules/server/options.nix | 180 +++++++++ modules/server/services/default.nix | 18 + modules/server/services/gitea/default.nix | 75 ++-- .../services/homepage-dashboard/default.nix | 37 +- modules/server/services/nextcloud/default.nix | 22 +- .../server/services/vaultwarden/default.nix | 18 +- 18 files changed, 653 insertions(+), 612 deletions(-) create mode 100644 lib/server/default.nix create mode 100644 lib/server/serviceurl/default.nix create mode 100644 lib/server/serviceurl/serviceurl.nix create mode 100644 modules/server/infra/default.nix create mode 100644 modules/server/options.nix create mode 100644 modules/server/services/default.nix diff --git a/flake.nix b/flake.nix index 112cf7b6..724aad41 100644 --- a/flake.nix +++ b/flake.nix @@ -1,9 +1,8 @@ { description = "cnix nix"; - outputs = - inputs: - inputs.flake-parts.lib.mkFlake { inherit inputs; } { + outputs = inputs: + inputs.flake-parts.lib.mkFlake {inherit inputs;} { systems = [ "x86_64-linux" "aarch64-linux" @@ -17,25 +16,23 @@ ./fmt-hooks.nix ]; - perSystem = - { - config, - pkgs, - ... - }: - { - devShells.default = pkgs.mkShell { - packages = [ - pkgs.git - config.packages.repl - ]; - name = "dots"; - env.DIRENV_LOG_FORMAT = ""; - shellHook = '' - ${config.pre-commit.installationScript} - ''; - }; + perSystem = { + config, + pkgs, + ... + }: { + devShells.default = pkgs.mkShell { + packages = [ + pkgs.git + config.packages.repl + ]; + name = "dots"; + env.DIRENV_LOG_FORMAT = ""; + shellHook = '' + ${config.pre-commit.installationScript} + ''; }; + }; }; inputs = { diff --git a/hosts/default.nix b/hosts/default.nix index 13f0224a..f9126462 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -4,114 +4,111 @@ homeImports, self, ... -}: -{ - flake.nixosConfigurations = - let - cLib = import ../lib inputs.nixpkgs.lib; - userConfig = "${self}/home"; - systemConfig = "${self}/system"; - hostConfig = "${self}/hosts"; +}: { + flake.nixosConfigurations = let + cLib = import ../lib inputs.nixpkgs.lib; + userConfig = "${self}/home"; + systemConfig = "${self}/system"; + hostConfig = "${self}/hosts"; - cnstConfig = "${self}/users/cnst"; - toothpickConfig = "${self}/users/toothpick"; + cnstConfig = "${self}/users/cnst"; + toothpickConfig = "${self}/users/toothpick"; - umodPath = "${self}/modules/home"; - smodPath = "${self}/modules/system"; + umodPath = "${self}/modules/home"; + smodPath = "${self}/modules/system"; - inherit (inputs.nixpkgs.lib) nixosSystem; - inherit (self) outputs; + inherit (inputs.nixpkgs.lib) nixosSystem; + inherit (self) outputs; - specialArgs = { - inherit - cLib - inputs - outputs - self - userConfig - systemConfig - hostConfig - cnstConfig - toothpickConfig - umodPath - smodPath - ; - }; - in - { - kima = nixosSystem { - inherit specialArgs; - modules = [ - ./kima - "${self}/nix" - { - home-manager = { - users.cnst.imports = homeImports."cnst@kima"; - extraSpecialArgs = specialArgs; - }; - } - self.nixosModules.nixos - self.nixosModules.settings - inputs.chaotic.nixosModules.default - inputs.agenix.nixosModules.default - ]; - }; - bunk = nixosSystem { - inherit specialArgs; - modules = [ - ./bunk - "${self}/nix" - { - home-manager = { - users.cnst.imports = homeImports."cnst@bunk"; - extraSpecialArgs = specialArgs; - }; - } - self.nixosModules.nixos - self.nixosModules.settings - inputs.chaotic.nixosModules.default - inputs.agenix.nixosModules.default - ]; - }; - sobotka = nixosSystem { - inherit specialArgs; - modules = [ - ./sobotka - "${self}/nix" - self.nixosModules.nixos - self.nixosModules.settings - self.nixosModules.server - inputs.agenix.nixosModules.default - inputs.authentik.nixosModules.default - ]; - }; - ziggy = nixosSystem { - inherit specialArgs; - modules = [ - ./ziggy - "${self}/nix" - self.nixosModules.nixos - self.nixosModules.settings - self.nixosModules.server - inputs.agenix.nixosModules.default - ]; - }; - toothpc = nixosSystem { - inherit specialArgs; - modules = [ - ./toothpc - "${self}/nix" - { - home-manager = { - users.toothpick.imports = homeImports."toothpick@toothpc"; - extraSpecialArgs = specialArgs; - }; - } - self.nixosModules.nixos - self.nixosModules.settings - inputs.chaotic.nixosModules.default - inputs.agenix.nixosModules.default - ]; - }; + specialArgs = { + inherit + cLib + inputs + outputs + self + userConfig + systemConfig + hostConfig + cnstConfig + toothpickConfig + umodPath + smodPath + ; }; + in { + kima = nixosSystem { + inherit specialArgs; + modules = [ + ./kima + "${self}/nix" + { + home-manager = { + users.cnst.imports = homeImports."cnst@kima"; + extraSpecialArgs = specialArgs; + }; + } + self.nixosModules.nixos + self.nixosModules.settings + inputs.chaotic.nixosModules.default + inputs.agenix.nixosModules.default + ]; + }; + bunk = nixosSystem { + inherit specialArgs; + modules = [ + ./bunk + "${self}/nix" + { + home-manager = { + users.cnst.imports = homeImports."cnst@bunk"; + extraSpecialArgs = specialArgs; + }; + } + self.nixosModules.nixos + self.nixosModules.settings + inputs.chaotic.nixosModules.default + inputs.agenix.nixosModules.default + ]; + }; + sobotka = nixosSystem { + inherit specialArgs; + modules = [ + ./sobotka + "${self}/nix" + self.nixosModules.nixos + self.nixosModules.settings + self.nixosModules.server + inputs.agenix.nixosModules.default + inputs.authentik.nixosModules.default + ]; + }; + ziggy = nixosSystem { + inherit specialArgs; + modules = [ + ./ziggy + "${self}/nix" + self.nixosModules.nixos + self.nixosModules.settings + self.nixosModules.server + inputs.agenix.nixosModules.default + ]; + }; + toothpc = nixosSystem { + inherit specialArgs; + modules = [ + ./toothpc + "${self}/nix" + { + home-manager = { + users.toothpick.imports = homeImports."toothpick@toothpc"; + extraSpecialArgs = specialArgs; + }; + } + self.nixosModules.nixos + self.nixosModules.settings + inputs.chaotic.nixosModules.default + inputs.agenix.nixosModules.default + ]; + }; + }; } diff --git a/hosts/sobotka/server.nix b/hosts/sobotka/server.nix index 6145850e..3cca1160 100644 --- a/hosts/sobotka/server.nix +++ b/hosts/sobotka/server.nix @@ -55,160 +55,159 @@ }; 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"; - # }; - # }; + 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"; - # }; + 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"; @@ -216,40 +215,40 @@ 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"; - # }; - # }; + 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"; + }; + }; }; }; } diff --git a/lib/server/default.nix b/lib/server/default.nix new file mode 100644 index 00000000..9d87bca6 --- /dev/null +++ b/lib/server/default.nix @@ -0,0 +1,5 @@ +{ + imports = [ + ./serviceurl + ]; +} diff --git a/lib/server/serviceurl/default.nix b/lib/server/serviceurl/default.nix new file mode 100644 index 00000000..2d11508f --- /dev/null +++ b/lib/server/serviceurl/default.nix @@ -0,0 +1,23 @@ +{ + lib, + config, + ... +}: let + mkServiceUrl' = import ./serviceurl.nix {inherit config;}; +in { + options.clib = { + server = { + mkServiceUrl = lib.mkOption { + type = lib.types.function; + readOnly = true; + description = "Helper function to generate a service URL."; + }; + }; + }; + + config.clib = { + server = { + mkServiceUrl = mkServiceUrl'; + }; + }; +} diff --git a/lib/server/serviceurl/serviceurl.nix b/lib/server/serviceurl/serviceurl.nix new file mode 100644 index 00000000..f3cdca7a --- /dev/null +++ b/lib/server/serviceurl/serviceurl.nix @@ -0,0 +1,11 @@ +{config}: service: let + mainDomain = config.server.networking.domain; + tailscaleDomain = "ts.${mainDomain}"; + + domain = + if service.exposure == "tunnel" + then mainDomain + else if service.exposure == "tailscale" + then tailscaleDomain + else (service.domain or mainDomain); +in "${service.subdomain}.${domain}" diff --git a/modules/default.nix b/modules/default.nix index d94992bb..dc448107 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -123,31 +123,6 @@ server = { imports = [ ./server - - ./server/infra/authentik - ./server/infra/fail2ban - ./server/infra/keepalived - ./server/infra/podman - ./server/infra/postgres - ./server/infra/tailscale - ./server/infra/traefik - ./server/infra/unbound - ./server/infra/www - - ./server/services/bazarr - ./server/services/flaresolverr - ./server/services/gitea - ./server/services/homepage-dashboard - ./server/services/jellyfin - ./server/services/jellyseerr - ./server/services/lidarr - ./server/services/n8n - ./server/services/nextcloud - ./server/services/prowlarr - ./server/services/radarr - ./server/services/sonarr - ./server/services/uptime-kuma - ./server/services/vaultwarden ]; }; settings = { diff --git a/modules/home/programs/hyprlock/default.nix b/modules/home/programs/hyprlock/default.nix index 10499243..82ab96a5 100644 --- a/modules/home/programs/hyprlock/default.nix +++ b/modules/home/programs/hyprlock/default.nix @@ -5,8 +5,7 @@ osConfig, cLib, ... -}: -let +}: let inherit (lib) mkIf mkEnableOption; cfg = osConfig.nixos.programs.hyprland; @@ -15,8 +14,7 @@ let # bg = osConfig.settings.theme.background; inherit (cLib.theme.bgs) resolve; -in -{ +in { config = mkIf cfg.enable { programs.hyprlock = { enable = true; diff --git a/modules/home/services/hyprpaper/default.nix b/modules/home/services/hyprpaper/default.nix index dc280850..2511e56d 100644 --- a/modules/home/services/hyprpaper/default.nix +++ b/modules/home/services/hyprpaper/default.nix @@ -5,8 +5,7 @@ osConfig, cLib, ... -}: -let +}: let inherit (lib) mkIf; cfg = osConfig.nixos.programs.hyprland; @@ -32,8 +31,7 @@ let bg = bg.primary; } ]; -in -{ +in { config = mkIf cfg.enable { services.hyprpaper = { enable = true; diff --git a/modules/server/default.nix b/modules/server/default.nix index f513f156..b3514378 100644 --- a/modules/server/default.nix +++ b/modules/server/default.nix @@ -1,186 +1,8 @@ -{ - lib, - config, - pkgs, - ... -}: let - hardDrives = [ - "/dev/disk/by-label/data" +{self, ...}: { + imports = [ + "${self}/lib/server" + ./options.nix + ./infra + ./services ]; - inherit (lib) mkOption types; - cfg = config.server; - ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups; -in { - options.server = { - enable = lib.mkEnableOption "The server services and configuration variables"; - email = mkOption { - default = ""; - type = types.str; - description = '' - Email name to be used to access the server services via Caddy reverse proxy - ''; - }; - domain = mkOption { - default = ""; - type = types.str; - description = '' - 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 { - default = "share"; - type = lib.types.str; - description = '' - User to run the server services as - ''; - }; - group = lib.mkOption { - default = "share"; - type = lib.types.str; - description = '' - Group to run the server services as - ''; - }; - uid = lib.mkOption { - default = 1000; - type = lib.types.int; - description = '' - UID to run the server services as - ''; - }; - gid = lib.mkOption { - default = 1000; - type = lib.types.int; - description = '' - GID to run the server services as - ''; - }; - timeZone = lib.mkOption { - default = "Europe/Stockholm"; - type = lib.types.str; - description = '' - 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 { - users = { - groups.${cfg.group} = { - gid = cfg.gid; - }; - users.${cfg.user} = { - uid = cfg.uid; - isSystemUser = true; - group = cfg.group; - extraGroups = ifTheyExist [ - "audio" - "video" - "docker" - "libvirtd" - "qemu-libvirtd" - "rtkit" - "fail2ban" - "vaultwarden" - "qbittorrent" - "lidarr" - "prowlarr" - "bazarr" - "sonarr" - "radarr" - "media" - "share" - "render" - "input" - "authentik" - "traefik" - ]; - }; - }; - }; } diff --git a/modules/server/infra/default.nix b/modules/server/infra/default.nix new file mode 100644 index 00000000..797096f4 --- /dev/null +++ b/modules/server/infra/default.nix @@ -0,0 +1,13 @@ +{ + imports = [ + ./authentik + ./fail2ban + ./keepalived + ./podman + ./postgres + ./tailscale + ./traefik + ./unbound + ./www + ]; +} diff --git a/modules/server/infra/traefik/default.nix b/modules/server/infra/traefik/default.nix index 5c6e2f2c..e487f4a8 100644 --- a/modules/server/infra/traefik/default.nix +++ b/modules/server/infra/traefik/default.nix @@ -11,23 +11,34 @@ 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); + 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 + name: service: lib.nameValuePair "${service.subdomain}" { entryPoints = ["websecure"]; - rule = "Host(`${service.subdomain}.${domain}`)"; + rule = "Host(`${config.clib.server.mkServiceUrl service}`)"; service = service.subdomain; tls.certResolver = "letsencrypt"; } - ) (lib.filterAttrs (name: service: service.enable) services); + ) (lib.filterAttrs (_: s: s.enable) services); # Generates all Traefik backend services generateServices = services: diff --git a/modules/server/options.nix b/modules/server/options.nix new file mode 100644 index 00000000..8a0e8672 --- /dev/null +++ b/modules/server/options.nix @@ -0,0 +1,180 @@ +{ + lib, + config, + ... +}: let + inherit (lib) mkOption types; + ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups; + cfg = config.server; +in { + options.server = { + enable = lib.mkEnableOption "The server services and configuration variables"; + email = mkOption { + default = ""; + type = types.str; + description = '' + Email name to be used to access the server services via Caddy reverse proxy + ''; + }; + domain = mkOption { + default = ""; + type = types.str; + description = '' + 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 { + default = "share"; + type = lib.types.str; + description = '' + User to run the server services as + ''; + }; + group = lib.mkOption { + default = "share"; + type = lib.types.str; + description = '' + Group to run the server services as + ''; + }; + uid = lib.mkOption { + default = 1000; + type = lib.types.int; + description = '' + UID to run the server services as + ''; + }; + gid = lib.mkOption { + default = 1000; + type = lib.types.int; + description = '' + GID to run the server services as + ''; + }; + timeZone = lib.mkOption { + default = "Europe/Stockholm"; + type = lib.types.str; + description = '' + 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 = 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 { + users = { + groups.${cfg.group} = { + gid = cfg.gid; + }; + users.${cfg.user} = { + uid = cfg.uid; + isSystemUser = true; + group = cfg.group; + extraGroups = ifTheyExist [ + "audio" + "video" + "docker" + "libvirtd" + "qemu-libvirtd" + "rtkit" + "fail2ban" + "vaultwarden" + "qbittorrent" + "lidarr" + "prowlarr" + "bazarr" + "sonarr" + "radarr" + "media" + "share" + "render" + "input" + "authentik" + "traefik" + ]; + }; + }; + }; +} diff --git a/modules/server/services/default.nix b/modules/server/services/default.nix new file mode 100644 index 00000000..710e86f9 --- /dev/null +++ b/modules/server/services/default.nix @@ -0,0 +1,18 @@ +{ + imports = [ + ./bazarr + ./flaresolverr + ./gitea + ./homepage-dashboard + ./jellyfin + ./jellyseerr + ./lidarr + ./n8n + ./nextcloud + ./prowlarr + ./radarr + ./sonarr + ./uptime-kuma + ./vaultwarden + ]; +} diff --git a/modules/server/services/gitea/default.nix b/modules/server/services/gitea/default.nix index e9689e9a..91ed0eb1 100644 --- a/modules/server/services/gitea/default.nix +++ b/modules/server/services/gitea/default.nix @@ -1,4 +1,3 @@ -# "inspired" by @jtojnar <3 { config, lib, @@ -7,21 +6,23 @@ }: let unit = "gitea"; cfg = config.server.services.${unit}; + domain = "${cfg.subdomain}.${config.server.infra.www.url}"; in { config = lib.mkIf cfg.enable { - age.secrets = { - giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age"; - }; + age.secrets.giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age"; server.infra = { - fail2ban = { - jails = { - gitea = { - serviceName = "gitea"; - failRegex = ''.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from ''; - }; - }; + fail2ban.jails.unit = { + serviceName = "${unit}"; + failRegex = '' + .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* + from + ''; }; + + postgresql.databases = [ + {database = unit;} + ]; }; services = { @@ -30,11 +31,11 @@ in { tunnels.${cfg.cloudflared.tunnelId} = { credentialsFile = cfg.cloudflared.credentialsFile; default = "http_status:404"; - ingress."${cfg.url}".service = "http://localhost:${toString cfg.port}"; + ingress."${domain}".service = "http://localhost:${toString cfg.port}"; }; }; - ${unit} = { + gitea = { enable = true; appName = "cnix code forge"; @@ -46,63 +47,51 @@ in { createDatabase = false; }; - lfs = { - enable = true; - }; + lfs.enable = true; settings = { cors = { ENABLED = true; SCHEME = "https"; - ALLOW_DOMAIN = cfg.url; - }; - log = { - MODE = "console"; + ALLOW_DOMAIN = domain; }; + + log.MODE = "console"; + mailer = { ENABLED = false; MAILER_TYPE = "sendmail"; FROM = "noreply+adam@cnst.dev"; SENDMAIL_PATH = "/run/wrappers/bin/sendmail"; }; - picture = { - DISABLE_GRAVATAR = true; - }; + + 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; - }; + + indexer.REPO_INDEXER_ENABLED = true; + oauth2_client = { ENABLE_AUTO_REGISTRATION = true; ACCOUNT_LINKING = "auto"; }; + server = { - DOMAIN = cfg.url; + DOMAIN = domain; 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; + ROOT_URL = "https://${domain}/"; }; + + security.DISABLE_GIT_HOOKS = false; + service.DISABLE_REGISTRATION = true; + session.COOKIE_SECURE = true; }; }; }; - - server.infra.postgresql.databases = [ - { - database = "gitea"; - } - ]; }; } diff --git a/modules/server/services/homepage-dashboard/default.nix b/modules/server/services/homepage-dashboard/default.nix index a90c327d..30b230b9 100644 --- a/modules/server/services/homepage-dashboard/default.nix +++ b/modules/server/services/homepage-dashboard/default.nix @@ -5,7 +5,7 @@ ... }: let unit = "homepage-dashboard"; - cfg = config.server.services.homepage-dashboard; + cfg = config.server.services.${unit}; srv = config.server; in { config = lib.mkIf cfg.enable { @@ -14,11 +14,14 @@ in { file = "${self}/secrets/homepageEnvironment.age"; }; }; + services = { glances.enable = true; + ${unit} = { enable = true; environmentFile = config.age.secrets.homepageEnvironment.path; + settings = { layout = [ { @@ -53,10 +56,12 @@ in { }; } ]; + headerStyle = "clean"; statusStyle = "dot"; hideVersion = "true"; }; + widgets = [ { openmeteo = { @@ -77,6 +82,7 @@ in { }; } ]; + services = let homepageCategories = [ "Arr" @@ -84,21 +90,30 @@ in { "Downloads" "Services" ]; - hl = config.server.services; - homepageServices = x: (lib.attrsets.filterAttrs ( - _name: value: value ? homepage && value.homepage.category == x + + allServices = srv.services; + + homepageServicesFor = category: + lib.filterAttrs + ( + name: value: + name + != unit + && value ? homepage + && value.homepage.category == category ) - srv.services); + allServices; in lib.lists.forEach homepageCategories (cat: { "${cat}" = - lib.lists.forEach (lib.attrsets.mapAttrsToList (name: _value: name) (homepageServices "${cat}")) + lib.lists.forEach + (lib.attrsets.mapAttrsToList (name: _value: name) (homepageServicesFor cat)) (x: { - "${hl.${x}.homepage.name}" = { - icon = hl.${x}.homepage.icon; - description = hl.${x}.homepage.description; - href = "https://${hl.${x}.url}"; - siteMonitor = "https://${hl.${x}.url}"; + "${allServices.${x}.homepage.name}" = { + icon = allServices.${x}.homepage.icon; + description = allServices.${x}.homepage.description; + href = "https://${allServices.${x}.url}"; + siteMonitor = "https://${allServices.${x}.url}"; }; }); }) diff --git a/modules/server/services/nextcloud/default.nix b/modules/server/services/nextcloud/default.nix index 03d0870f..03280b58 100644 --- a/modules/server/services/nextcloud/default.nix +++ b/modules/server/services/nextcloud/default.nix @@ -15,19 +15,15 @@ in { nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age"; }; - server.infra.fail2ban = lib.mkIf srv.infra.fail2ban.enable { - jails = { - nextcloud = { - serviceName = "${unit}"; - _groupsre = ''(?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)''; - failRegex = '' - ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Login failed: - ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Two-factor challenge failed: - ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Trusted domain error. - ''; - datePattern = '',?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"''; - }; - }; + server.infra.fail2ban.jails.nextcloud = { + serviceName = "${unit}"; + _groupsre = ''(?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)''; + failRegex = '' + ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Login failed: + ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Two-factor challenge failed: + ^\{%(_groupsre)s,?\s*"remoteAddr":""%(_groupsre)s,?\s*"message":"Trusted domain error. + ''; + datePattern = '',?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"''; }; services = { diff --git a/modules/server/services/vaultwarden/default.nix b/modules/server/services/vaultwarden/default.nix index 6ce5e491..b668d485 100644 --- a/modules/server/services/vaultwarden/default.nix +++ b/modules/server/services/vaultwarden/default.nix @@ -7,7 +7,7 @@ }: let unit = "vaultwarden"; cfg = config.server.services.${unit}; - www = config.server.infra.www; + domain = "${cfg.subdomain}.${config.server.infra.www.url}"; in { config = lib.mkIf cfg.enable { age.secrets = { @@ -15,15 +15,9 @@ in { vaultwardenEnvironment.file = "${self}/secrets/vaultwardenEnvironment.age"; }; - server.infra = { - fail2ban = { - jails = { - vaultwarden = { - serviceName = "${unit}"; - failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: \. Username:.*$''; - }; - }; - }; + server.infra.fail2ban.jails.${unit} = { + serviceName = "${unit}"; + failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: \. Username:.*$''; }; services = { @@ -32,7 +26,7 @@ in { tunnels.${cfg.cloudflared.tunnelId} = { credentialsFile = cfg.cloudflared.credentialsFile; default = "http_status:404"; - ingress."${cfg.url}".service = "http://localhost:${toString cfg.port}"; + ingress."${domain}".service = "http://localhost:${toString cfg.port}"; }; }; @@ -43,7 +37,7 @@ in { backupDir = "/var/backup/vaultwarden"; config = { - DOMAIN = "https://vault.${www.url}"; + DOMAIN = "https://${domain}"; SIGNUPS_ALLOWED = false; ROCKET_ADDRESS = "127.0.0.1"; ROCKET_PORT = cfg.port;