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..0cad4d23 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 70c7f750..7b1fae3c 100644 --- a/hosts/sobotka/server.nix +++ b/hosts/sobotka/server.nix @@ -3,108 +3,264 @@ enable = true; email = "adam@cnst.dev"; domain = "cnix.dev"; + ip = "192.168.88.14"; user = "share"; group = "share"; uid = 994; gid = 993; - traefik = { - enable = true; - }; - 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; + infra = { + authentik = { + enable = true; + url = "auth.cnst.dev"; + port = 9000; + cloudflared = { + tunnelId = "b66f9368-db9e-4302-8b48-527cda34a635"; + credentialsFile = config.age.secrets.authentikCloudflared.path; + }; + }; + traefik = { + enable = true; + }; + tailscale = { + enable = true; + }; + 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; + }; }; }; - vaultwarden = { - enable = true; - url = "vault.cnst.dev"; - cloudflared = { - tunnelId = "fdd98086-6a4c-44f2-bba0-eb86b833cce5"; - credentialsFile = config.age.secrets.vaultwardenCloudflared.path; + + services = { + homepage-dashboard = { + enable = true; + subdomain = "dash"; + exposure = "local"; + port = 8082; }; - }; - www = { - enable = true; - url = "cnst.dev"; - cloudflared = { - tunnelId = "e5076186-efb7-405a-998c-6155af7fb221"; - credentialsFile = config.age.secrets.wwwCloudflared.path; + n8n = { + enable = true; + subdomain = "n8n"; + exposure = "local"; + port = 5678; + homepage = { + name = "n8n"; + description = "A workflow automation platform"; + icon = "n8n.svg"; + category = "Services"; + }; }; - }; - authentik = { - enable = true; - url = "auth.cnst.dev"; - cloudflared = { - tunnelId = "b66f9368-db9e-4302-8b48-527cda34a635"; - credentialsFile = config.age.secrets.authentikCloudflared.path; + bazarr = { + enable = true; + subdomain = "bazarr"; + exposure = "local"; + port = 6767; + homepage = { + name = "Bazarr"; + description = "Subtitle manager"; + icon = "bazarr.svg"; + category = "Arr"; + }; + }; + prowlarr = { + enable = true; + subdomain = "prowlarr"; + exposure = "local"; + port = 9696; + homepage = { + name = "prowlarr"; + description = "PVR indexer"; + icon = "prowlarr.svg"; + category = "Arr"; + }; + }; + flaresolverr = { + enable = true; + subdomain = "flaresolverr"; + exposure = "local"; + port = 8191; + homepage = { + name = "FlareSolverr"; + description = "Proxy to bypass Cloudflare/DDoS-GUARD protection"; + icon = "flaresolverr.svg"; + category = "Arr"; + }; + }; + lidarr = { + enable = true; + subdomain = "lidarr"; + exposure = "local"; + port = 8686; + homepage = { + name = "Lidarr"; + description = "Music collection manager"; + icon = "lidarr.svg"; + category = "Arr"; + }; + }; + sonarr = { + enable = true; + subdomain = "sonarr"; + exposure = "local"; + port = 8989; + homepage = { + name = "Sonarr"; + description = "Internet PVR for Usenet and Torrents"; + icon = "sonarr.svg"; + category = "Arr"; + }; + }; + radarr = { + enable = true; + subdomain = "radarr"; + exposure = "local"; + port = 7878; + homepage = { + name = "Radarr"; + description = "Movie collection manager"; + icon = "radarr.svg"; + category = "Arr"; + }; + }; + jellyseerr = { + enable = true; + subdomain = "jellyseerr"; + exposure = "local"; + 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"; + exposure = "local"; + 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; + homepage = { + name = "Nextcloud"; + description = "A safe home for all your data"; + icon = "nextcloud.svg"; + category = "Services"; + }; }; - }; - 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; + subdomain = "qbt"; + exposure = "local"; port = 8080; + homepage = { + name = "qBittorrent"; + description = "Torrent client"; + icon = "qbittorrent.svg"; + category = "Downloads"; + }; }; slskd = { enable = true; + subdomain = "slskd"; + exposure = "local"; + port = 5030; + homepage = { + name = "Soulseek"; + description = "Web-based Soulseek client"; + icon = "slskd.svg"; + category = "Downloads"; + }; }; pihole = { enable = true; + subdomain = "pihole"; + exposure = "local"; port = 8053; + homepage = { + name = "PiHole"; + description = "Adblocking and DNS service"; + icon = "pi-hole.svg"; + category = "Services"; + path = "/admin"; + }; }; }; }; diff --git a/hosts/sobotka/settings.nix b/hosts/sobotka/settings.nix index 3c42b75d..b200a2d1 100644 --- a/hosts/sobotka/settings.nix +++ b/hosts/sobotka/settings.nix @@ -4,6 +4,10 @@ username = "cnst"; mail = "adam@cnst.dev"; sshUser = "sobotka"; + domains = { + local = "cnix.dev"; + public = "cnst.dev"; + }; }; }; } diff --git a/lib/server/default.nix b/lib/server/default.nix new file mode 100644 index 00000000..2f3cc182 --- /dev/null +++ b/lib/server/default.nix @@ -0,0 +1,26 @@ +{lib}: let + server = { + mkDomain = config: service: let + localDomain = config.settings.accounts.domains.local; + publicDomain = config.settings.accounts.domains.public; + tailscaleDomain = "ts.${publicDomain}"; + in + if service.exposure == "tunnel" + then publicDomain + else if service.exposure == "tailscale" + then tailscaleDomain + else localDomain; + + mkFullDomain = config: service: let + domain = server.mkDomain config service; + in "${service.subdomain}.${domain}"; + + mkHostDomain = config: service: let + domain = server.mkDomain config service; + in "${domain}"; + + mkSubDomain = config: service: "${service.subdomain}"; + }; +in { + server = server; +} diff --git a/modules/default.nix b/modules/default.nix index f0c6f11d..dc448107 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -123,28 +123,6 @@ server = { imports = [ ./server - ./server/fail2ban - ./server/homepage-dashboard - ./server/nextcloud - ./server/vaultwarden - ./server/bazarr - ./server/prowlarr - ./server/lidarr - ./server/radarr - ./server/sonarr - ./server/jellyseerr - ./server/jellyfin - ./server/n8n - ./server/podman - ./server/unbound - ./server/uptime-kuma - ./server/keepalived - ./server/gitea - ./server/postgres - ./server/traefik - ./server/www - ./server/authentik - ./server/tailscale ]; }; settings = { diff --git a/modules/home/programs/hyprlock/default.nix b/modules/home/programs/hyprlock/default.nix index 10499243..5cc69813 100644 --- a/modules/home/programs/hyprlock/default.nix +++ b/modules/home/programs/hyprlock/default.nix @@ -3,10 +3,9 @@ pkgs, lib, osConfig, - cLib, + clib, ... -}: -let +}: let inherit (lib) mkIf mkEnableOption; cfg = osConfig.nixos.programs.hyprland; @@ -14,9 +13,8 @@ let # hyprlockPkg = pkgs.hyprlock; # bg = osConfig.settings.theme.background; - inherit (cLib.theme.bgs) resolve; -in -{ + inherit (clib.theme.bgs) resolve; +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..dac6688c 100644 --- a/modules/home/services/hyprpaper/default.nix +++ b/modules/home/services/hyprpaper/default.nix @@ -3,16 +3,15 @@ pkgs, inputs, osConfig, - cLib, + clib, ... -}: -let +}: let inherit (lib) mkIf; cfg = osConfig.nixos.programs.hyprland; hyprpaperFlake = inputs.hyprpaper.packages.${pkgs.system}.default; bg = osConfig.settings.theme.background; - bgs = cLib.theme.bgs; + bgs = clib.theme.bgs; monitorMappings = [ { @@ -32,8 +31,7 @@ let bg = bg.primary; } ]; -in -{ +in { config = mkIf cfg.enable { services.hyprpaper = { enable = true; diff --git a/modules/server/bazarr/default.nix b/modules/server/bazarr/default.nix deleted file mode 100644 index dba3f1d5..00000000 --- a/modules/server/bazarr/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/default.nix b/modules/server/default.nix index 3f7362e4..9b3ebf24 100644 --- a/modules/server/default.nix +++ b/modules/server/default.nix @@ -1,101 +1,16 @@ { + self, lib, - config, - pkgs, ... }: let - hardDrives = [ - "/dev/disk/by-label/data" - ]; - inherit (lib) mkOption types; - cfg = config.server; - ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups; + clib = import "${self}/lib/server" {inherit lib;}; 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 - ''; - }; - 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 - ''; - }; - }; - - 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" - ]; - }; - }; - }; + imports = [ + { + _module.args.clib = clib; + } + ./options.nix + ./infra + ./services + ]; } diff --git a/modules/server/gitea/default.nix b/modules/server/gitea/default.nix deleted file mode 100644 index 0331ef2c..00000000 --- a/modules/server/gitea/default.nix +++ /dev/null @@ -1,170 +0,0 @@ -# "inspired" by @jtojnar <3 -{ - config, - lib, - self, - ... -}: let - unit = "gitea"; - srv = config.server; - cfg = config.server.${unit}; -in { - options.server.${unit} = { - enable = lib.mkEnableOption { - description = "Enable ${unit}"; - }; - url = lib.mkOption { - type = lib.types.str; - default = "git.${srv.domain}"; - }; - port = lib.mkOption { - type = lib.types.int; - default = 5003; - description = "The port to host Gitea on."; - }; - 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 { - age.secrets = { - giteaCloudflared.file = "${self}/secrets/giteaCloudflared.age"; - }; - - server = { - fail2ban = lib.mkIf config.server.fail2ban.enable { - jails = { - gitea = { - serviceName = "gitea"; - failRegex = ''.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from ''; - }; - }; - }; - }; - - 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}"; - }; - }; - - ${unit} = { - enable = true; - appName = "cnix code forge"; - - database = { - type = "postgres"; - socket = "/run/postgresql"; - name = "gitea"; - user = "gitea"; - createDatabase = false; - }; - - lfs = { - enable = true; - }; - - settings = { - cors = { - ENABLED = true; - SCHEME = "https"; - ALLOW_DOMAIN = cfg.url; - }; - log = { - MODE = "console"; - }; - mailer = { - ENABLED = false; - MAILER_TYPE = "sendmail"; - FROM = "noreply+adam@cnst.dev"; - SENDMAIL_PATH = "/run/wrappers/bin/sendmail"; - }; - picture = { - DISABLE_GRAVATAR = true; - }; - repository = { - DEFAULT_BRANCH = "main"; - DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls"; - DISABLE_DOWNLOAD_SOURCE_ARCHIVES = true; - }; - indexer = { - REPO_INDEXER_ENABLED = true; - }; - oauth2_client = { - ENABLE_AUTO_REGISTRATION = true; - ACCOUNT_LINKING = "auto"; - }; - server = { - DOMAIN = cfg.url; - LANDING_PAGE = "explore"; - HTTP_PORT = cfg.port; - ROOT_URL = "https://${cfg.url}/"; - }; - security = { - DISABLE_GIT_HOOKS = false; - }; - service = { - DISABLE_REGISTRATION = true; - }; - session = { - COOKIE_SECURE = true; - }; - }; - }; - }; - - services.traefik = { - dynamicConfigOptions = { - http = { - 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"; - } - ]; - }; -} diff --git a/modules/server/authentik/default.nix b/modules/server/infra/authentik/default.nix similarity index 84% rename from modules/server/authentik/default.nix rename to modules/server/infra/authentik/default.nix index 133e12af..f5a463fc 100644 --- a/modules/server/authentik/default.nix +++ b/modules/server/infra/authentik/default.nix @@ -1,15 +1,14 @@ { config, lib, - pkgs, self, ... }: let unit = "authentik"; - cfg = config.server.${unit}; - srv = config.server; + cfg = config.server.infra.${unit}; + srv = config.server.infra.www.domain; in { - options.server.${unit} = { + options.server.infra.${unit} = { enable = lib.mkEnableOption { description = "Enable ${unit}"; }; @@ -17,6 +16,10 @@ in { type = lib.types.str; default = "auth.${srv.www.domain}"; }; + port = lib.mkOption { + type = lib.types.port; + description = "The local port the service runs on"; + }; cloudflared = { credentialsFile = lib.mkOption { type = lib.types.str; @@ -31,21 +34,11 @@ in { example = "00000000-0000-0000-0000-000000000000"; }; }; - homepage.name = lib.mkOption { - type = lib.types.str; - default = "Authentik"; - }; - homepage.description = lib.mkOption { - 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"; + homepage = { + name = "Authentik"; + description = "An open-source IdP for modern SSO"; + icon = "authentik.svg"; + category = "Services"; }; }; @@ -59,8 +52,8 @@ in { }; }; - server = { - fail2ban = lib.mkIf cfg.enable { + server.infra = { + fail2ban = { jails = { authentik = { serviceName = "authentik"; 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/fail2ban/default.nix b/modules/server/infra/fail2ban/default.nix similarity index 98% rename from modules/server/fail2ban/default.nix rename to modules/server/infra/fail2ban/default.nix index 6de50898..880c704c 100644 --- a/modules/server/fail2ban/default.nix +++ b/modules/server/infra/fail2ban/default.nix @@ -5,9 +5,9 @@ pkgs, ... }: let - cfg = config.server.fail2ban; + cfg = config.server.infra.fail2ban; in { - options.server.fail2ban = { + options.server.infra.fail2ban = { enable = lib.mkEnableOption { description = "Enable cloudflare fail2ban"; }; @@ -61,6 +61,7 @@ in { ); }; }; + config = lib.mkIf cfg.enable { services.fail2ban = { enable = true; diff --git a/modules/server/keepalived/default.nix b/modules/server/infra/keepalived/default.nix similarity index 72% rename from modules/server/keepalived/default.nix rename to modules/server/infra/keepalived/default.nix index e782527a..858db4f6 100644 --- a/modules/server/keepalived/default.nix +++ b/modules/server/infra/keepalived/default.nix @@ -3,27 +3,24 @@ config, self, ... -}: -let +}: let unit = "keepalived"; - cfg = config.server.${unit}; + cfg = config.server.infra.${unit}; - hostCfg = - hostname: - if hostname == "sobotka" then - { - ip = "192.168.88.14"; - priority = 20; - state = "MASTER"; - } - else if hostname == "ziggy" then - { - ip = "192.168.88.12"; - priority = 10; - state = "BACKUP"; - } - else - throw "No keepalived config defined for host ${hostname}"; + hostCfg = hostname: + if hostname == "sobotka" + then { + ip = "192.168.88.14"; + priority = 20; + state = "MASTER"; + } + else if hostname == "ziggy" + then { + ip = "192.168.88.12"; + priority = 10; + state = "BACKUP"; + } + else throw "No keepalived config defined for host ${hostname}"; _self = hostCfg config.networking.hostName; @@ -34,9 +31,8 @@ let # Remove self from peers peers = builtins.filter (ip: ip != _self.ip) allPeers; -in -{ - options.server.${unit} = { +in { + options.server.infra.${unit} = { enable = lib.mkEnableOption { description = "Enable ${unit}"; }; diff --git a/modules/server/infra/podman/acquis.yaml b/modules/server/infra/podman/acquis.yaml new file mode 100644 index 00000000..0ae1151e --- /dev/null +++ b/modules/server/infra/podman/acquis.yaml @@ -0,0 +1,6 @@ +--- +filenames: + - /var/log/traefik/access.log +poll_without_inotify: true +labels: + type: traefik diff --git a/modules/server/infra/podman/default.nix b/modules/server/infra/podman/default.nix new file mode 100644 index 00000000..7b5904ec --- /dev/null +++ b/modules/server/infra/podman/default.nix @@ -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" + ]; + }; + }) + ]; + }; +} diff --git a/modules/server/postgres/default.nix b/modules/server/infra/postgres/default.nix similarity index 100% rename from modules/server/postgres/default.nix rename to modules/server/infra/postgres/default.nix diff --git a/modules/server/postgres/postgres-upgrade.nix b/modules/server/infra/postgres/postgres-upgrade.nix similarity index 94% rename from modules/server/postgres/postgres-upgrade.nix rename to modules/server/infra/postgres/postgres-upgrade.nix index addb6bd8..5171ae26 100644 --- a/modules/server/postgres/postgres-upgrade.nix +++ b/modules/server/infra/postgres/postgres-upgrade.nix @@ -7,10 +7,10 @@ }: let inherit (lib) types mkOption; - cfg = config.server.postgresql; + cfg = config.server.infra.postgresql; in { options = { - server.postgresql = { + server.infra.postgresql = { upgradeTargetPackage = mkOption { type = types.nullOr types.package; default = null; diff --git a/modules/server/postgres/postgres.nix b/modules/server/infra/postgres/postgres.nix similarity index 98% rename from modules/server/postgres/postgres.nix rename to modules/server/infra/postgres/postgres.nix index 595aa297..94abdfe8 100644 --- a/modules/server/postgres/postgres.nix +++ b/modules/server/infra/postgres/postgres.nix @@ -7,7 +7,7 @@ }: let inherit (lib) types mkOption; - cfg = config.server.postgresql; + cfg = config.server.infra.postgresql; database = {name, ...}: { options = { @@ -31,7 +31,7 @@ }; in { options = { - server.postgresql = { + server.infra.postgresql = { databases = mkOption { type = types.listOf (types.submodule database); default = []; diff --git a/modules/server/tailscale/default.nix b/modules/server/infra/tailscale/default.nix similarity index 75% rename from modules/server/tailscale/default.nix rename to modules/server/infra/tailscale/default.nix index 8ed32a8b..6750be56 100644 --- a/modules/server/tailscale/default.nix +++ b/modules/server/infra/tailscale/default.nix @@ -5,16 +5,11 @@ ... }: with lib; let - cfg = config.server.tailscale; + cfg = config.server.infra.tailscale; in { - options.server.tailscale = { + options.server.infra.tailscale = { enable = mkEnableOption "Enable tailscale server configuration"; - url = lib.mkOption { - type = lib.types.str; - default = "ts.cnst.dev"; - }; }; - config = mkIf cfg.enable { age.secrets.sobotkaTsAuth.file = "${self}/secrets/sobotkaTsAuth.age"; diff --git a/modules/server/infra/traefik/default.nix b/modules/server/infra/traefik/default.nix new file mode 100644 index 00000000..5ced27bd --- /dev/null +++ b/modules/server/infra/traefik/default.nix @@ -0,0 +1,202 @@ +{ + lib, + clib, + 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); + + generateRouters = services: config: + lib.mapAttrs' ( + name: service: + lib.nameValuePair name { + entryPoints = ["websecure"]; + # FIX 3: Use backticks for the Host rule and interpolation + rule = "Host(`${clib.server.mkFullDomain config service}`)"; + service = name; + tls.certResolver = "letsencrypt"; + } + ) (lib.filterAttrs (_: s: s.enable) services); + + generateServices = services: + lib.mapAttrs' (name: service: + lib.nameValuePair name { + 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 = { + services = generateServices srv.services; + + routers = + (generateRouters srv.services config) + // { + 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"; + # }; + # }; + # }; + # }; + }; + }; + }; + }; + }; +} diff --git a/modules/server/unbound/default.nix b/modules/server/infra/unbound/default.nix similarity index 79% rename from modules/server/unbound/default.nix rename to modules/server/infra/unbound/default.nix index fda9dd05..280e211c 100644 --- a/modules/server/unbound/default.nix +++ b/modules/server/infra/unbound/default.nix @@ -5,7 +5,25 @@ ... }: let unit = "unbound"; - cfg = config.server.${unit}; + cfg = config.server.infra.${unit}; + srv = config.server; + + svcNames = lib.attrNames srv.services; + + localARecords = builtins.concatLists (map ( + name: let + s = srv.services.${name}; + in + if s != null && s.enable && s.subdomain != null + then [''"${s.subdomain}.${srv.domain}. A ${srv.ip}"''] + else [] + ) + svcNames); + + revParts = lib.lists.reverseList (lib.splitString "." srv.ip); + revName = lib.concatStringsSep "." revParts; + + localPTRs = ["${revName}.in-addr.arpa. PTR traefik.${srv.domain}"]; hostIp = hostname: if hostname == "ziggy" @@ -14,11 +32,12 @@ then "192.168.88.14" else throw "No IP defined for host ${hostname}"; in { - options.server.${unit} = { + options.server.infra.${unit} = { enable = lib.mkEnableOption { description = "Enable ${unit}"; }; }; + config = lib.mkIf cfg.enable { services = { # resolved.enable = lib.mkForce false; @@ -97,6 +116,10 @@ in { "255.255.255.255/32" "2001:db8::/32" ]; + local-data = localARecords; + + # Example PTR entry: "14.88.168.192.in-addr.arpa. PTR traefik.cnix.dev." + # local-data-ptr = localPTRs; }; }; }; diff --git a/modules/server/www/default.nix b/modules/server/infra/www/default.nix similarity index 91% rename from modules/server/www/default.nix rename to modules/server/infra/www/default.nix index 394f1c68..b0b9ef71 100644 --- a/modules/server/www/default.nix +++ b/modules/server/infra/www/default.nix @@ -1,15 +1,13 @@ { lib, config, - pkgs, self, ... }: let - inherit (lib) mkOption mkEnableOption mkIf types; - cfg = config.server.www; - srv = config.server; + inherit (lib) mkIf mkEnableOption mkOption types; + cfg = config.server.infra.www; in { - options.server.www = { + options.server.infra.www = { enable = mkEnableOption { description = "Enable personal website"; }; @@ -20,6 +18,12 @@ in { 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 = { credentialsFile = lib.mkOption { type = lib.types.str; @@ -41,8 +45,8 @@ in { wwwCloudflared.file = "${self}/secrets/wwwCloudflared.age"; }; - server = { - fail2ban = lib.mkIf config.server.www.enable { + server.infra = { + fail2ban = { jails = { nginx-404 = { serviceName = "nginx"; diff --git a/modules/server/jellyfin/default.nix b/modules/server/jellyfin/default.nix deleted file mode 100644 index 3b7edb0d..00000000 --- a/modules/server/jellyfin/default.nix +++ /dev/null @@ -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"; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/jellyseerr/default.nix b/modules/server/jellyseerr/default.nix deleted file mode 100644 index 859c5f33..00000000 --- a/modules/server/jellyseerr/default.nix +++ /dev/null @@ -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"; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/lidarr/default.nix b/modules/server/lidarr/default.nix deleted file mode 100644 index b80502d2..00000000 --- a/modules/server/lidarr/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/n8n/default.nix b/modules/server/n8n/default.nix deleted file mode 100644 index 98209498..00000000 --- a/modules/server/n8n/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/nextcloud/default.nix b/modules/server/nextcloud/default.nix deleted file mode 100644 index 45f0b76a..00000000 --- a/modules/server/nextcloud/default.nix +++ /dev/null @@ -1,153 +0,0 @@ -{ - config, - pkgs, - lib, - self, - ... -}: let - unit = "nextcloud"; - cfg = config.server.${unit}; - srv = config.server; -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 { - age.secrets = { - nextcloudAdminPass.file = "${self}/secrets/nextcloudAdminPass.age"; - nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age"; - }; - - server.fail2ban = lib.mkIf config.server.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)?"''; - }; - }; - }; - - services = { - ${unit} = { - enable = true; - package = pkgs.nextcloud32; - hostName = "nextcloud"; - configureRedis = true; - caching = { - redis = true; - }; - phpOptions = { - "opcache.interned_strings_buffer" = "32"; - }; - maxUploadSize = "50G"; - settings = { - maintenance_window_start = "1"; - trusted_proxies = [ - "127.0.0.1" - "::1" - ]; - trusted_domains = ["cloud.${srv.domain}"]; - overwriteprotocol = "https"; - enabledPreviewProviders = [ - "OC\\Preview\\BMP" - "OC\\Preview\\GIF" - "OC\\Preview\\JPEG" - "OC\\Preview\\Krita" - "OC\\Preview\\MarkDown" - "OC\\Preview\\MP3" - "OC\\Preview\\OpenDocument" - "OC\\Preview\\PNG" - "OC\\Preview\\TXT" - "OC\\Preview\\XBitmap" - "OC\\Preview\\HEIC" - ]; - }; - config = { - dbtype = "pgsql"; - dbuser = "nextcloud"; - dbhost = "/run/postgresql"; - dbname = "nextcloud"; - adminuser = "cnst"; - adminpassFile = cfg.adminpassFile; - }; - }; - - nginx = { - defaultListen = [ - { - addr = "127.0.0.1"; - port = 8182; - } - { - addr = "127.0.0.1"; - port = 8482; - } - ]; - virtualHosts.nextcloud = { - 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 = [ - { - database = "nextcloud"; - } - ]; - systemd.services."nextcloud-setup" = { - requires = ["postgresql.service"]; - after = ["postgresql.service"]; - }; - }; -} 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/podman/default.nix b/modules/server/podman/default.nix deleted file mode 100644 index 395f3aab..00000000 --- a/modules/server/podman/default.nix +++ /dev/null @@ -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" - ]; - }; - }) - ]; - }; -} diff --git a/modules/server/prowlarr/default.nix b/modules/server/prowlarr/default.nix deleted file mode 100644 index 681152f0..00000000 --- a/modules/server/prowlarr/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/radarr/default.nix b/modules/server/radarr/default.nix deleted file mode 100644 index d89b8b89..00000000 --- a/modules/server/radarr/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/services/bazarr/default.nix b/modules/server/services/bazarr/default.nix new file mode 100644 index 00000000..7c568b67 --- /dev/null +++ b/modules/server/services/bazarr/default.nix @@ -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; + }; + }; +} 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/flaresolverr/default.nix b/modules/server/services/flaresolverr/default.nix new file mode 100644 index 00000000..dbf9bdfc --- /dev/null +++ b/modules/server/services/flaresolverr/default.nix @@ -0,0 +1,16 @@ +{ + config, + lib, + ... +}: let + unit = "flaresolverr"; + cfg = config.server.services.${unit}; +in { + config = lib.mkIf cfg.enable { + services = { + ${unit} = { + enable = true; + }; + }; + }; +} diff --git a/modules/server/services/gitea/default.nix b/modules/server/services/gitea/default.nix new file mode 100644 index 00000000..63825bbf --- /dev/null +++ b/modules/server/services/gitea/default.nix @@ -0,0 +1,94 @@ +{ + config, + lib, + self, + ... +}: 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"; + + server.infra = { + fail2ban.jails.${unit} = { + serviceName = "${unit}"; + failRegex = ''.*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from ''; + }; + + postgresql.databases = [ + {database = "gitea";} + ]; + }; + + services = { + cloudflared = { + enable = true; + tunnels.${cfg.cloudflared.tunnelId} = { + credentialsFile = cfg.cloudflared.credentialsFile; + default = "http_status:404"; + ingress."${domain}".service = "http://localhost:${toString cfg.port}"; + }; + }; + + gitea = { + enable = true; + appName = "cnix code forge"; + + database = { + type = "postgres"; + socket = "/run/postgresql"; + name = "gitea"; + user = "gitea"; + createDatabase = false; + }; + + lfs.enable = true; + + settings = { + cors = { + ENABLED = true; + SCHEME = "https"; + ALLOW_DOMAIN = 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; + + repository = { + DEFAULT_BRANCH = "main"; + DEFAULT_REPO_UNITS = "repo.code,repo.issues,repo.pulls"; + DISABLE_DOWNLOAD_SOURCE_ARCHIVES = true; + }; + + indexer.REPO_INDEXER_ENABLED = true; + + oauth2_client = { + ENABLE_AUTO_REGISTRATION = true; + ACCOUNT_LINKING = "auto"; + }; + + server = { + DOMAIN = domain; + LANDING_PAGE = "explore"; + HTTP_PORT = cfg.port; + ROOT_URL = "https://${domain}/"; + }; + + security.DISABLE_GIT_HOOKS = false; + service.DISABLE_REGISTRATION = true; + session.COOKIE_SECURE = true; + }; + }; + }; + }; +} diff --git a/modules/server/homepage-dashboard/default.nix b/modules/server/services/homepage-dashboard/default.nix similarity index 67% rename from modules/server/homepage-dashboard/default.nix rename to modules/server/services/homepage-dashboard/default.nix index ea6cbf10..0f78cad8 100644 --- a/modules/server/homepage-dashboard/default.nix +++ b/modules/server/services/homepage-dashboard/default.nix @@ -2,51 +2,27 @@ config, lib, self, + clib, ... }: let unit = "homepage-dashboard"; - cfg = config.server.homepage-dashboard; + cfg = config.server.services.${unit}; srv = config.server; 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 { age.secrets = { homepageEnvironment = { file = "${self}/secrets/homepageEnvironment.age"; }; }; + services = { glances.enable = true; + ${unit} = { enable = true; environmentFile = config.age.secrets.homepageEnvironment.path; + settings = { layout = [ { @@ -81,10 +57,12 @@ in { }; } ]; + headerStyle = "clean"; statusStyle = "dot"; hideVersion = "true"; }; + widgets = [ { openmeteo = { @@ -105,6 +83,7 @@ in { }; } ]; + services = let homepageCategories = [ "Arr" @@ -112,31 +91,37 @@ in { "Downloads" "Services" ]; - hl = config.server; - mergedServices = hl // hl.podman; - homepageServices = x: (lib.attrsets.filterAttrs ( - name: value: value ? homepage && value.homepage.category == x + allServices = srv.services; + + getDomain = s: clib.server.mkHostDomain config s; + + homepageServicesFor = category: + lib.filterAttrs + ( + name: value: + name + != unit + && value ? homepage + && value.homepage.category == category ) - mergedServices); + allServices; in lib.lists.forEach homepageCategories (cat: { "${cat}" = lib.lists.forEach - (lib.attrsets.mapAttrsToList (name: value: { - inherit name; - url = value.url; - homepage = value.homepage; - }) (homepageServices "${cat}")) - (x: { - "${x.homepage.name}" = { - icon = x.homepage.icon; - description = x.homepage.description; - href = "https://${x.url}${x.homepage.path or ""}"; - siteMonitor = "https://${x.url}${x.homepage.path or ""}"; + (lib.attrsets.mapAttrsToList (name: _value: name) (homepageServicesFor cat)) + (x: let + service = allServices.${x}; + domain = getDomain service; + in { + "${service.homepage.name}" = { + icon = service.homepage.icon; + description = service.homepage.description; + href = "https://${domain}"; + siteMonitor = "https://${domain}"; }; }); }) - ++ [{Misc = cfg.misc;}] ++ [ { Glances = let @@ -212,25 +197,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"]; - }; - }; - }; - }; - }; }; }; } diff --git a/modules/server/services/jellyfin/default.nix b/modules/server/services/jellyfin/default.nix new file mode 100644 index 00000000..76fe84d1 --- /dev/null +++ b/modules/server/services/jellyfin/default.nix @@ -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 + ]; + }; +} diff --git a/modules/server/services/jellyseerr/default.nix b/modules/server/services/jellyseerr/default.nix new file mode 100644 index 00000000..c175aef6 --- /dev/null +++ b/modules/server/services/jellyseerr/default.nix @@ -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; + }; + }; +} diff --git a/modules/server/services/lidarr/default.nix b/modules/server/services/lidarr/default.nix new file mode 100644 index 00000000..f80a2ce3 --- /dev/null +++ b/modules/server/services/lidarr/default.nix @@ -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; + }; + }; +} diff --git a/modules/server/services/n8n/default.nix b/modules/server/services/n8n/default.nix new file mode 100644 index 00000000..2e673ff6 --- /dev/null +++ b/modules/server/services/n8n/default.nix @@ -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; + }; + }; + }; +} diff --git a/modules/server/services/nextcloud/default.nix b/modules/server/services/nextcloud/default.nix new file mode 100644 index 00000000..03280b58 --- /dev/null +++ b/modules/server/services/nextcloud/default.nix @@ -0,0 +1,101 @@ +{ + config, + pkgs, + lib, + self, + ... +}: let + unit = "nextcloud"; + cfg = config.server.services.${unit}; + srv = config.server; +in { + config = lib.mkIf cfg.enable { + age.secrets = { + nextcloudAdminPass.file = "${self}/secrets/nextcloudAdminPass.age"; + nextcloudCloudflared.file = "${self}/secrets/nextcloudCloudflared.age"; + }; + + 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 = { + ${unit} = { + enable = true; + package = pkgs.nextcloud32; + hostName = "nextcloud"; + configureRedis = true; + caching = { + redis = true; + }; + phpOptions = { + "opcache.interned_strings_buffer" = "32"; + }; + maxUploadSize = "50G"; + settings = { + maintenance_window_start = "1"; + trusted_proxies = [ + "127.0.0.1" + "::1" + ]; + trusted_domains = ["cloud.${srv.domain}"]; + overwriteprotocol = "https"; + enabledPreviewProviders = [ + "OC\\Preview\\BMP" + "OC\\Preview\\GIF" + "OC\\Preview\\JPEG" + "OC\\Preview\\Krita" + "OC\\Preview\\MarkDown" + "OC\\Preview\\MP3" + "OC\\Preview\\OpenDocument" + "OC\\Preview\\PNG" + "OC\\Preview\\TXT" + "OC\\Preview\\XBitmap" + "OC\\Preview\\HEIC" + ]; + }; + config = { + dbtype = "pgsql"; + dbuser = "nextcloud"; + dbhost = "/run/postgresql"; + dbname = "nextcloud"; + adminuser = "cnst"; + adminpassFile = config.age.secrets.nextcloudAdminPass.path; + }; + }; + + nginx = { + defaultListen = [ + { + addr = "127.0.0.1"; + port = 8182; + } + { + addr = "127.0.0.1"; + port = 8482; + } + ]; + virtualHosts.nextcloud = { + forceSSL = false; + }; + }; + }; + + server.infra.postgresql.databases = [ + { + database = "nextcloud"; + } + ]; + systemd.services."nextcloud-setup" = { + requires = ["postgresql.service"]; + after = ["postgresql.service"]; + }; + }; +} diff --git a/modules/server/services/prowlarr/default.nix b/modules/server/services/prowlarr/default.nix new file mode 100644 index 00000000..e308c06e --- /dev/null +++ b/modules/server/services/prowlarr/default.nix @@ -0,0 +1,16 @@ +{ + config, + lib, + ... +}: let + unit = "prowlarr"; + cfg = config.server.services.${unit}; +in { + config = lib.mkIf cfg.enable { + services = { + ${unit} = { + enable = true; + }; + }; + }; +} diff --git a/modules/server/services/radarr/default.nix b/modules/server/services/radarr/default.nix new file mode 100644 index 00000000..1e7fc28e --- /dev/null +++ b/modules/server/services/radarr/default.nix @@ -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; + }; + }; +} diff --git a/modules/server/services/sonarr/default.nix b/modules/server/services/sonarr/default.nix new file mode 100644 index 00000000..013793b8 --- /dev/null +++ b/modules/server/services/sonarr/default.nix @@ -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; + }; + }; +} diff --git a/modules/server/services/uptime-kuma/default.nix b/modules/server/services/uptime-kuma/default.nix new file mode 100644 index 00000000..2edfd40f --- /dev/null +++ b/modules/server/services/uptime-kuma/default.nix @@ -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; + }; + }; + }; +} diff --git a/modules/server/services/vaultwarden/default.nix b/modules/server/services/vaultwarden/default.nix new file mode 100644 index 00000000..b668d485 --- /dev/null +++ b/modules/server/services/vaultwarden/default.nix @@ -0,0 +1,59 @@ +# from @fufexan & @notthebee +{ + config, + lib, + self, + ... +}: let + unit = "vaultwarden"; + cfg = config.server.services.${unit}; + domain = "${cfg.subdomain}.${config.server.infra.www.url}"; +in { + config = lib.mkIf cfg.enable { + age.secrets = { + vaultwardenCloudflared.file = "${self}/secrets/vaultwardenCloudflared.age"; + vaultwardenEnvironment.file = "${self}/secrets/vaultwardenEnvironment.age"; + }; + + server.infra.fail2ban.jails.${unit} = { + serviceName = "${unit}"; + failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: \. Username:.*$''; + }; + + services = { + cloudflared = { + enable = true; + tunnels.${cfg.cloudflared.tunnelId} = { + credentialsFile = cfg.cloudflared.credentialsFile; + default = "http_status:404"; + ingress."${domain}".service = "http://localhost:${toString cfg.port}"; + }; + }; + + vaultwarden = { + enable = true; + environmentFile = config.age.secrets.vaultwardenEnvironment.path; + + backupDir = "/var/backup/vaultwarden"; + + config = { + DOMAIN = "https://${domain}"; + SIGNUPS_ALLOWED = false; + ROCKET_ADDRESS = "127.0.0.1"; + ROCKET_PORT = cfg.port; + IP_HEADER = "CF-Connecting-IP"; + + logLevel = "warn"; + extendedLogging = true; + useSyslog = true; + invitationsAllowed = true; + showPasswordHint = false; + }; + }; + }; + systemd.services.backup-vaultwarden.serviceConfig = { + User = "root"; + Group = "root"; + }; + }; +} diff --git a/modules/server/sonarr/default.nix b/modules/server/sonarr/default.nix deleted file mode 100644 index aa77ae3f..00000000 --- a/modules/server/sonarr/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/traefik/default.nix b/modules/server/traefik/default.nix deleted file mode 100644 index 1f0add66..00000000 --- a/modules/server/traefik/default.nix +++ /dev/null @@ -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"]; - } - ]; - }; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/uptime-kuma/default.nix b/modules/server/uptime-kuma/default.nix deleted file mode 100644 index 63f9aa85..00000000 --- a/modules/server/uptime-kuma/default.nix +++ /dev/null @@ -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"]; - }; - }; - }; - }; - }; - }; - }; -} diff --git a/modules/server/vaultwarden/default.nix b/modules/server/vaultwarden/default.nix deleted file mode 100644 index 67581415..00000000 --- a/modules/server/vaultwarden/default.nix +++ /dev/null @@ -1,89 +0,0 @@ -# from @fufexan & @notthebee -{ - config, - lib, - self, - ... -}: let - inherit (lib) mkIf mkEnableOption; - vcfg = config.services.vaultwarden.config; - cfg = config.server.vaultwarden; -in { - options = { - 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 = { - vaultwardenCloudflared.file = "${self}/secrets/vaultwardenCloudflared.age"; - vaultwardenEnvironment.file = "${self}/secrets/vaultwardenEnvironment.age"; - }; - - server = { - fail2ban = lib.mkIf config.server.fail2ban.enable { - jails = { - vaultwarden = { - serviceName = "vaultwarden"; - failRegex = ''^.*?Username or password is incorrect\. Try again\. IP: \. Username:.*$''; - }; - }; - }; - }; - - systemd.services.backup-vaultwarden.serviceConfig = { - User = "root"; - Group = "root"; - }; - - services = { - vaultwarden = { - enable = true; - environmentFile = config.age.secrets.vaultwardenEnvironment.path; - - backupDir = "/var/backup/vaultwarden"; - - config = { - DOMAIN = "https://${cfg.url}"; - SIGNUPS_ALLOWED = false; - ROCKET_ADDRESS = "127.0.0.1"; - ROCKET_PORT = 8222; - IP_HEADER = "CF-Connecting-IP"; - - logLevel = "warn"; - extendedLogging = true; - useSyslog = true; - invitationsAllowed = true; - showPasswordHint = false; - }; - }; - cloudflared = { - enable = true; - tunnels.${cfg.cloudflared.tunnelId} = { - credentialsFile = cfg.cloudflared.credentialsFile; - default = "http_status:404"; - ingress."${cfg.url}".service = "http://${vcfg.ROCKET_ADDRESS}:${toString vcfg.ROCKET_PORT}"; - }; - }; - }; - }; -} diff --git a/modules/settings/accounts/default.nix b/modules/settings/accounts/default.nix index 3602fe11..101351e9 100644 --- a/modules/settings/accounts/default.nix +++ b/modules/settings/accounts/default.nix @@ -2,8 +2,7 @@ lib, config, ... -}: -let +}: let inherit (lib) mkOption types; sshKeys = { @@ -16,14 +15,14 @@ let keyName = config.settings.accounts.sshUser or null; selectedKey = - if keyName != null then + if keyName != null + then lib.attrByPath [ keyName - ] (builtins.abort "No SSH key defined for hostname/key '${toString keyName}'") sshKeys - else - builtins.abort "No accounts.sshUser provided, cannot select SSH key."; -in -{ + ] (builtins.abort "No SSH key defined for hostname/key '${toString keyName}'") + sshKeys + else builtins.abort "No accounts.sshUser provided, cannot select SSH key."; +in { options.settings.accounts = { username = mkOption { type = types.str; @@ -46,5 +45,21 @@ in default = null; description = "Optional override for selecting an SSH key by name"; }; + domains = lib.mkOption { + type = lib.types.submodule { + options = { + local = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + description = "The local domain of the host"; + }; + public = lib.mkOption { + type = lib.types.str; + default = "example.com"; + description = "The public domain of the host"; + }; + }; + }; + }; }; } diff --git a/secrets/crowdsecApi.age b/secrets/crowdsecApi.age new file mode 100644 index 00000000..93aea645 --- /dev/null +++ b/secrets/crowdsecApi.age @@ -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 +wxqz:.{?fAjT{J >Fē$,Q" .{_! \ No newline at end of file diff --git a/secrets/homepageEnvironment.age b/secrets/homepageEnvironment.age index f5a686db..7d021e55 100644 Binary files a/secrets/homepageEnvironment.age and b/secrets/homepageEnvironment.age differ diff --git a/secrets/mikrotikSecret.age b/secrets/mikrotikSecret.age new file mode 100644 index 00000000..be96bb5d --- /dev/null +++ b/secrets/mikrotikSecret.age @@ -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 + XPCh; |3O Z()t~qv0] 2Ѣ \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix index f8f53525..38c8a01b 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -63,6 +63,8 @@ in { "wwwCloudflared.age".publicKeys = kima ++ sobotka; "authentikCloudflared.age".publicKeys = kima ++ sobotka; "sobotkaTsAuth.age".publicKeys = kima ++ sobotka; + "mikrotikSecret.age".publicKeys = kima ++ sobotka; + "crowdsecApi.age".publicKeys = kima ++ sobotka; # Ziggy-specific "cloudflareDnsCredentialsZiggy.age".publicKeys = kima ++ ziggy;