diff --git a/hosts/freun-dev/configuration.nix b/hosts/freun-dev/configuration.nix index 0d530b3..a57329a 100644 --- a/hosts/freun-dev/configuration.nix +++ b/hosts/freun-dev/configuration.nix @@ -3,7 +3,10 @@ # and in the NixOS manual (accessible by running `nixos-help`). { config, pkgs, ssh, ... }: - +let + ipv4Address = "65.21.145.150"; + ipv6Address = "2a01:4f9:c011:9ac1::1"; +in { nix = { settings = { @@ -25,13 +28,24 @@ networking.useDHCP = false; networking.nftables.enable = true; + services.octodns.records."" = { + A = { + ttl = 86400; + values = [ ipv4Address ]; + }; + AAAA = { + ttl = 86400; + values = [ ipv6Address ]; + }; + }; + systemd.network = { enable = true; networks.static = { name = "enp1s0"; address = [ - "65.21.145.150/32" - "2a01:4f9:c011:9ac1::1/64" + "${ipv4Address}/32" + "${ipv6Address}/64" ]; routes = [ { Gateway = "fe80::1"; } diff --git a/hosts/freun-dev/secrets.nix b/hosts/freun-dev/secrets.nix index 428b4db..1397952 100644 --- a/hosts/freun-dev/secrets.nix +++ b/hosts/freun-dev/secrets.nix @@ -11,6 +11,7 @@ "vaultwarden" "donetick" "dnote" + "octodns" ] ) // { smtp-password = { diff --git a/hosts/freun-dev/services.nix b/hosts/freun-dev/services.nix index b2ee18c..b692bd9 100644 --- a/hosts/freun-dev/services.nix +++ b/hosts/freun-dev/services.nix @@ -12,7 +12,6 @@ let secrets = config.age.secrets; in { - services.postgresql.package = pkgs.postgresql_16; virtualisation.podman.enable = true; virtualisation.oci-containers.backend = "podman"; @@ -33,6 +32,16 @@ in }; services = { + postgresql.package = pkgs.postgresql_16; + octodns = { + enable = true; + records."".MX = { + ttl = 86400; + values = [{ exchange = "${smtp.host}."; }]; + }; + defaults.CNAME.ttl = 60; + }; + hastebin = { enable = true; subdomain = "bin"; diff --git a/modules/services/default.nix b/modules/services/default.nix index a5d5d3d..272b03c 100644 --- a/modules/services/default.nix +++ b/modules/services/default.nix @@ -17,5 +17,6 @@ ./readeck.nix ./donetick.nix ./dnote.nix + ./octodns.nix ]; } diff --git a/modules/services/grafana.nix b/modules/services/grafana.nix index c04a149..00003de 100644 --- a/modules/services/grafana.nix +++ b/modules/services/grafana.nix @@ -31,28 +31,30 @@ in }; }; - services.prometheus = { - enable = true; - exporters.node.enable = true; - scrapeConfigs = [ - { - job_name = "node"; - static_configs = [ - { targets = [ "localhost:9100" ]; } - ]; - } - ]; - }; + services = { + prometheus = { + enable = true; + exporters.node.enable = true; + scrapeConfigs = [ + { + job_name = "node"; + static_configs = [ + { targets = [ "localhost:9100" ]; } + ]; + } + ]; + }; - services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.settings.server.http_port; + webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.settings.server.http_port; - services.postgresql = { - enable = lib.mkDefault true; - ensureDatabases = [ "grafana" ]; - ensureUsers = [{ - name = "grafana"; - ensureDBOwnership = true; - }]; + postgresql = { + enable = lib.mkDefault true; + ensureDatabases = [ "grafana" ]; + ensureUsers = [{ + name = "grafana"; + ensureDBOwnership = true; + }]; + }; }; }; } diff --git a/modules/services/invidious.nix b/modules/services/invidious.nix index de3e3f0..434df3c 100644 --- a/modules/services/invidious.nix +++ b/modules/services/invidious.nix @@ -11,24 +11,26 @@ in }; config = lib.mkIf cfg.enable { - services.invidious = { - domain = fqdn; - address = "127.0.0.1"; + services = { + invidious = { + domain = fqdn; + address = "127.0.0.1"; - settings = { - external_port = 443; - db = { - dbname = "invidious"; - user = "invidious"; + settings = { + external_port = 443; + db = { + dbname = "invidious"; + user = "invidious"; + }; }; }; - }; - services.postgresql.enable = lib.mkDefault true; + postgresql.enable = lib.mkDefault true; - services.webserver = { - enable = lib.mkDefault true; - vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; }; }; } diff --git a/modules/services/octodns.nix b/modules/services/octodns.nix new file mode 100644 index 0000000..c3e213f --- /dev/null +++ b/modules/services/octodns.nix @@ -0,0 +1,210 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.services.octodns; + secrets = config.age.secrets; + + types = { + ttlOptions = default: { + ttl = lib.mkOption { + type = lib.types.int; + default = default; + }; + }; + defaults = lib.types.submodule { + options = { + A = lib.mkOption { + type = lib.types.submodule { + options = types.ttlOptions cfg.defaults.ttl; + }; + default = { }; + }; + AAAA = lib.mkOption { + type = lib.types.submodule { + options = types.ttlOptions cfg.defaults.ttl; + }; + default = { }; + }; + CNAME = lib.mkOption { + type = lib.types.submodule { + options = types.ttlOptions cfg.defaults.ttl; + }; + default = { }; + }; + MX = lib.mkOption { + type = lib.types.submodule { + options = types.ttlOptions cfg.defaults.ttl; + }; + default = { }; + }; + } // (types.ttlOptions 3600); + }; + aRecord = lib.types.submodule { + options = { + values = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + ttl = lib.mkOption { + type = lib.types.int; + default = cfg.defaults.A.ttl; + }; + }; + }; + aaaaRecord = lib.types.submodule { + options = { + values = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + }; + ttl = lib.mkOption { + type = lib.types.int; + default = cfg.defaults.AAAA.ttl; + }; + }; + }; + cnameRecord = lib.types.submodule { + options = { + target = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + toRoot = lib.mkOption { + type = lib.types.bool; + default = false; + }; + ttl = lib.mkOption { + type = lib.types.int; + default = cfg.defaults.CNAME.ttl; + }; + }; + }; + mxValue = lib.types.submodule { + options = { + exchange = lib.mkOption { + type = lib.types.str; + }; + preference = lib.mkOption { + type = lib.types.int; + default = 10; + }; + }; + }; + mxRecord = lib.types.submodule { + options = { + values = lib.mkOption { + type = lib.types.listOf types.mxValue; + default = [ ]; + }; + ttl = lib.mkOption { + type = lib.types.int; + default = cfg.defaults.MX.ttl; + }; + }; + }; + records = lib.types.submodule { + options = { + A = lib.mkOption { + type = types.aRecord; + default = { }; + }; + AAAA = lib.mkOption { + type = types.aaaaRecord; + default = { }; + }; + CNAME = lib.mkOption { + type = types.cnameRecord; + default = { }; + }; + MX = lib.mkOption { + type = types.mxRecord; + default = { }; + }; + }; + }; + }; + + yamlFormat = pkgs.formats.yaml { }; + + zoneFile = yamlFormat.generate "octodns-zone" + ( + lib.filterAttrs (_: records: (lib.length records) > 0) + (lib.mapAttrs + (_: types: + lib.filter + ({ values ? [ ], value ? null, ... }: (lib.length values) > 0 || !(builtins.isNull value)) + (lib.mapAttrsToList + (type: { ttl, ... }@options: + if (type == "CNAME") + then + let + inherit (options) target toRoot; + value = if toRoot then "${config.networking.domain}." else target; + in + { inherit type ttl value; } + else + { inherit type ttl; inherit (options) values; } + ) + types + ) + ) + cfg.records + ) + ); + + zonesDir = pkgs.linkFarm "octodns-zones" { + "${config.networking.domain}.yaml" = zoneFile; + }; + + + configFile = yamlFormat.generate "octodns-config.yaml" { + providers = { + config = { + class = "octodns.provider.yaml.YamlProvider"; + directory = zonesDir; + default_ttl = cfg.defaults.ttl; + }; + hetzner = { + class = "octodns_hetzner.HetznerProvider"; + token = "env/HETZNER_TOKEN"; + }; + }; + + zones."*" = { + sources = [ "config" ]; + targets = [ "hetzner" ]; + }; + }; + + octodns = pkgs.octodns.withProviders (_: [ + pkgs.octodns-providers.hetzner + ]); +in +{ + options.services.octodns = { + enable = lib.mkEnableOption "Enable octodns"; + records = lib.mkOption { + type = lib.types.attrsOf types.records; + default = { }; + }; + defaults = lib.mkOption { + type = types.defaults; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.octodns = { + enable = true; + description = "OctoDNS"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${octodns}/bin/octodns-sync --config-file ${configFile} --doit"; + DynamicUser = true; + EnvironmentFile = secrets.octodns.path; + RemainAfterExit = true; + }; + wantedBy = lib.optionals config.services.nginx.enable [ "nginx.service" ]; + before = lib.optionals config.services.nginx.enable [ "nginx.service" ]; + }; + }; +} diff --git a/modules/services/owncast.nix b/modules/services/owncast.nix index 51489c6..09d8a55 100644 --- a/modules/services/owncast.nix +++ b/modules/services/owncast.nix @@ -11,10 +11,12 @@ in }; config = lib.mkIf cfg.enable { - services.owncast = { - openFirewall = true; - }; + services = { + owncast = { + openFirewall = true; + }; - services.webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + webserver.vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; }; } diff --git a/modules/services/syncthing.nix b/modules/services/syncthing.nix index 1a757e7..d87f06e 100644 --- a/modules/services/syncthing.nix +++ b/modules/services/syncthing.nix @@ -15,15 +15,17 @@ in }; config = lib.mkIf cfg.enable { - services.syncthing = { - openDefaultPorts = true; - guiAddress = "[::1]:${toString cfg.port}"; - settings.gui.insecureSkipHostCheck = true; - }; + services = { + syncthing = { + openDefaultPorts = true; + guiAddress = "[::1]:${toString cfg.port}"; + settings.gui.insecureSkipHostCheck = true; + }; - services.webserver = { - enable = lib.mkDefault true; - vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = cfg.port; + }; }; }; } diff --git a/modules/services/webserver.nix b/modules/services/webserver.nix index 4cd93ec..4d5bca5 100644 --- a/modules/services/webserver.nix +++ b/modules/services/webserver.nix @@ -46,36 +46,47 @@ in }; config = lib.mkIf cfg.enable { - services.nginx = { - enable = lib.mkDefault true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - recommendedBrotliSettings = true; - recommendedGzipSettings = true; - recommendedZstdSettings = true; - recommendedOptimisation = true; + services = { + nginx = { + enable = lib.mkDefault true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + recommendedBrotliSettings = true; + recommendedGzipSettings = true; + recommendedZstdSettings = true; + recommendedOptimisation = true; - virtualHosts = lib.mapAttrs - (_: { proxyBuffering, locations }: { - forceSSL = true; - enableACME = true; - http2 = true; - acmeRoot = lib.mkIf cfg.acme.dnsChallenge null; - extraConfig = lib.concatLines [ - (lib.optionalString (!proxyBuffering) "proxy_buffering off;") - "charset utf-8;" - ]; - locations = lib.mapAttrs - (_: { proxyPort, extraConfig }: lib.mergeAttrsList [ - { inherit extraConfig; } - (if (lib.isInt proxyPort) then { - proxyWebsockets = true; - proxyPass = "http://localhost:${toString proxyPort}"; - } else { }) - ]) - locations; - }) - cfg.vHosts; + virtualHosts = lib.mapAttrs + (_: { proxyBuffering, locations }: { + forceSSL = true; + enableACME = true; + http2 = true; + acmeRoot = lib.mkIf cfg.acme.dnsChallenge null; + extraConfig = lib.concatLines [ + (lib.optionalString (!proxyBuffering) "proxy_buffering off;") + "charset utf-8;" + ]; + locations = lib.mapAttrs + (_: { proxyPort, extraConfig }: lib.mergeAttrsList [ + { inherit extraConfig; } + (if (lib.isInt proxyPort) then { + proxyWebsockets = true; + proxyPass = "http://localhost:${toString proxyPort}"; + } else { }) + ]) + locations; + }) + cfg.vHosts; + }; + + octodns.records = lib.filterAttrs (name: _: name != config.networking.domain) ( + lib.mapAttrs' + (fqdn: _: { + name = lib.removeSuffix ".${config.networking.domain}" fqdn; + value = { CNAME.toRoot = true; }; + }) + cfg.vHosts + ); }; security.acme = { diff --git a/modules/services/workout-sync.nix b/modules/services/workout-sync.nix index 01c89c1..8d9e171 100644 --- a/modules/services/workout-sync.nix +++ b/modules/services/workout-sync.nix @@ -46,16 +46,18 @@ in confinement.enable = true; }; - services.webserver = { - enable = lib.mkDefault true; - vHosts.${fqdn}.locations."/" = { - proxyPort = port; - extraConfig = '' - client_max_body_size 50m; - proxy_send_timeout 300; - proxy_read_timeout 300; - send_timeout 300; - ''; + services = { + webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/" = { + proxyPort = port; + extraConfig = '' + client_max_body_size 50m; + proxy_send_timeout 300; + proxy_read_timeout 300; + send_timeout 300; + ''; + }; }; }; }; diff --git a/modules/services/workout-tracker.nix b/modules/services/workout-tracker.nix index b93facf..140f8c0 100644 --- a/modules/services/workout-tracker.nix +++ b/modules/services/workout-tracker.nix @@ -13,11 +13,13 @@ in }; config = lib.mkIf cfg.enable { - services.workout-tracker = { inherit package; }; + services = { + workout-tracker = { inherit package; }; - services.webserver = { - enable = lib.mkDefault true; - vHosts.${fqdn}.locations."/".proxyPort = port; + webserver = { + enable = lib.mkDefault true; + vHosts.${fqdn}.locations."/".proxyPort = port; + }; }; }; } diff --git a/secrets/octodns.age b/secrets/octodns.age new file mode 100644 index 0000000..2167c93 Binary files /dev/null and b/secrets/octodns.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 94ce235..1371c8c 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -19,4 +19,5 @@ in "readeck.age".publicKeys = users ++ [ freun-dev ]; "donetick.age".publicKeys = users ++ [ freun-dev ]; "dnote.age".publicKeys = users ++ [ freun-dev ]; + "octodns.age".publicKeys = users ++ [ freun-dev ]; }