diff --git a/flake-modules/default.nix b/flake-modules/default.nix new file mode 100644 index 0000000..2b66fd5 --- /dev/null +++ b/flake-modules/default.nix @@ -0,0 +1,32 @@ +{ inputs, ... }: +{ + imports = [ + inputs.treefmt-nix.flakeModule + ./package.nix + ./overlay.nix + ./module.nix + ]; + + perSystem = + { pkgs, ... }: + { + # Setup formatters + treefmt = { + projectRootFile = "flake.nix"; + programs = { + nixfmt.enable = true; + rustfmt.enable = true; + statix.enable = true; + typos.enable = true; + }; + }; + + devShells.default = pkgs.mkShellNoCC { + packages = [ + pkgs.cargo-insta + pkgs.cargo-udeps + pkgs.mold + ]; + }; + }; +} diff --git a/flake-modules/module.nix b/flake-modules/module.nix new file mode 100644 index 0000000..6ffbba6 --- /dev/null +++ b/flake-modules/module.nix @@ -0,0 +1,196 @@ +let + module = + { + lib, + pkgs, + config, + ... + }: + let + cfg = config.services.webnsupdate; + inherit (lib) + mkOption + mkEnableOption + mkPackageOption + types + ; + in + { + options.services.webnsupdate = mkOption { + description = "An HTTP server for nsupdate."; + default = { }; + type = types.submodule { + options = { + enable = mkEnableOption "webnsupdate"; + extraArgs = mkOption { + description = '' + Extra arguments to be passed to the webnsupdate server command. + ''; + type = types.listOf types.str; + default = [ ]; + example = [ "--ip-source" ]; + }; + package = mkPackageOption pkgs "webnsupdate" { }; + bindIp = mkOption { + description = '' + IP address to bind to. + + Setting it to anything other than localhost is very insecure as + `webnsupdate` only supports plain HTTP and should always be behind a + reverse proxy. + ''; + type = types.str; + default = "localhost"; + example = "0.0.0.0"; + }; + bindPort = mkOption { + description = "Port to bind to."; + type = types.port; + default = 5353; + }; + passwordFile = mkOption { + description = '' + The file where the password is stored. + + This file can be created by running `webnsupdate mkpasswd $USERNAME $PASSWORD`. + ''; + type = types.path; + example = "/secrets/webnsupdate.pass"; + }; + keyFile = mkOption { + description = '' + The TSIG key that `nsupdate` should use. + + This file will be passed to `nsupdate` through the `-k` option, so look + at `man 8 nsupdate` for information on the key's format. + ''; + type = types.path; + example = "/secrets/webnsupdate.key"; + }; + ttl = mkOption { + description = "The TTL that should be set on the zone records created by `nsupdate`."; + type = types.ints.positive; + default = 60; + example = 3600; + }; + records = mkOption { + description = '' + The fqdn of records that should be updated. + + Empty lines will be ignored, but whitespace will not be. + ''; + type = types.nullOr types.lines; + default = null; + example = '' + example.com. + + example.org. + ci.example.org. + ''; + }; + recordsFile = mkOption { + description = '' + The fqdn of records that should be updated. + + Empty lines will be ignored, but whitespace will not be. + ''; + type = types.nullOr types.path; + default = null; + example = "/secrets/webnsupdate.records"; + }; + user = mkOption { + description = "The user to run as."; + type = types.str; + default = "named"; + }; + group = mkOption { + description = "The group to run as."; + type = types.str; + default = "named"; + }; + }; + }; + }; + + config = + let + recordsFile = + if cfg.recordsFile != null then cfg.recordsFile else pkgs.writeText "webnsrecords" cfg.records; + args = lib.strings.escapeShellArgs ( + [ + "--records" + recordsFile + "--key-file" + cfg.keyFile + "--password-file" + cfg.passwordFile + "--address" + cfg.bindIp + "--port" + (builtins.toString cfg.bindPort) + "--ttl" + (builtins.toString cfg.ttl) + "--data-dir=%S/webnsupdate" + ] + ++ cfg.extraArgs + ); + cmd = "${lib.getExe cfg.package} ${args}"; + in + lib.mkIf cfg.enable { + # warnings = + # lib.optional (!config.services.bind.enable) "`webnsupdate` is expected to be used alongside `bind`. This is an unsopported configuration."; + assertions = [ + { + assertion = + (cfg.records != null || cfg.recordsFile != null) + && !(cfg.records != null && cfg.recordsFile != null); + message = "Exactly one of `services.webnsupdate.records` and `services.webnsupdate.recordsFile` must be set."; + } + ]; + + systemd.services.webnsupdate = { + description = "Web interface for nsupdate."; + wantedBy = [ "multi-user.target" ]; + after = [ + "network.target" + "bind.service" + ]; + preStart = "${cmd} verify"; + path = [ pkgs.dig ]; + startLimitIntervalSec = 60; + serviceConfig = { + ExecStart = [ cmd ]; + Type = "exec"; + Restart = "on-failure"; + RestartSec = "10s"; + # User and group + User = cfg.user; + Group = cfg.group; + # Runtime directory and mode + RuntimeDirectory = "webnsupdate"; + RuntimeDirectoryMode = "0750"; + # Cache directory and mode + CacheDirectory = "webnsupdate"; + CacheDirectoryMode = "0750"; + # Logs directory and mode + LogsDirectory = "webnsupdate"; + LogsDirectoryMode = "0750"; + # State directory and mode + StateDirectory = "webnsupdate"; + StateDirectoryMode = "0750"; + # New file permissions + UMask = "0027"; + # Security + NoNewPrivileges = true; + ProtectHome = true; + }; + }; + }; + }; +in +{ + flake.nixosModules = { + default = module; + webnsupdate = module; + }; +} diff --git a/flake-modules/overlay.nix b/flake-modules/overlay.nix new file mode 100644 index 0000000..fba3bc1 --- /dev/null +++ b/flake-modules/overlay.nix @@ -0,0 +1,5 @@ +{ + flake = { + overlays.default = _final: prev: { webnsupdate = prev.callPackage ../default.nix { }; }; + }; +} diff --git a/flake-modules/package.nix b/flake-modules/package.nix new file mode 100644 index 0000000..d4ff12a --- /dev/null +++ b/flake-modules/package.nix @@ -0,0 +1,24 @@ +{ + perSystem = + { pkgs, ... }: + { + packages = + let + webnsupdate = pkgs.callPackage ../default.nix { }; + in + { + inherit webnsupdate; + default = webnsupdate; + cargo-update = pkgs.writeShellApplication { + name = "cargo-update-lockfile"; + runtimeInputs = with pkgs; [ + cargo + gnused + ]; + text = '' + CARGO_TERM_COLOR=never cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log + ''; + }; + }; + }; +} diff --git a/flake.lock b/flake.lock index e6a68d4..e5a0f2a 100644 --- a/flake.lock +++ b/flake.lock @@ -39,7 +39,8 @@ "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", - "systems": "systems" + "systems": "systems", + "treefmt-nix": "treefmt-nix" } }, "systems": { @@ -56,6 +57,26 @@ "repo": "default", "type": "github" } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1729613947, + "narHash": "sha256-XGOvuIPW1XRfPgHtGYXd5MAmJzZtOuwlfKDgxX5KT3s=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "aac86347fb5063960eccb19493e0cadcdb4205ca", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index e01433f..3fb5460 100644 --- a/flake.nix +++ b/flake.nix @@ -7,73 +7,16 @@ url = "github:hercules-ci/flake-parts"; inputs.nixpkgs-lib.follows = "nixpkgs"; }; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ ./flake-modules ]; systems = import inputs.systems; - perSystem = - { - lib, - pkgs, - self', - ... - }: - { - packages = - let - webnsupdate = pkgs.callPackage ./default.nix { }; - in - { - inherit webnsupdate; - default = webnsupdate; - cargo-update = pkgs.writeShellApplication { - name = "cargo-update-lockfile"; - runtimeInputs = with pkgs; [ - cargo - gnused - ]; - text = '' - CARGO_TERM_COLOR=never cargo update 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log - ''; - }; - }; - - formatter = pkgs.nixfmt-rfc-style; - - checks = { - fmtRust = pkgs.callPackage ./run-cmd.nix { - src = inputs.self; - name = "fmt-rust"; - extraNativeBuildInputs = [ pkgs.rustfmt ]; - cmd = "${lib.getExe pkgs.cargo} fmt --all --check --verbose"; - }; - fmtNix = pkgs.callPackage ./run-cmd.nix { - src = inputs.self; - name = "fmt-nix"; - cmd = "${lib.getExe self'.formatter} --check ."; - }; - lintNix = pkgs.callPackage ./run-cmd.nix { - src = inputs.self; - name = "lint-nix"; - cmd = "${lib.getExe pkgs.statix} check ."; - }; - }; - - devShells.default = pkgs.mkShell { - packages = [ - pkgs.cargo-insta - pkgs.cargo-udeps - pkgs.mold - ]; - }; - }; - - flake = { - overlays.default = final: prev: { webnsupdate = final.callPackage ./default.nix { }; }; - - nixosModules.default = ./module.nix; - }; }; } diff --git a/module.nix b/module.nix deleted file mode 100644 index 203998f..0000000 --- a/module.nix +++ /dev/null @@ -1,177 +0,0 @@ -{ - lib, - pkgs, - config, - ... -}: -let - cfg = config.services.webnsupdate; - inherit (lib) mkOption mkEnableOption types; -in -{ - options.services.webnsupdate = mkOption { - description = "An HTTP server for nsupdate."; - default = { }; - type = types.submodule { - options = { - enable = mkEnableOption "webnsupdate"; - extraArgs = mkOption { - description = '' - Extra arguments to be passed to the webnsupdate server command. - ''; - type = types.listOf types.str; - default = [ ]; - example = [ "--ip-source" ]; - }; - bindIp = mkOption { - description = '' - IP address to bind to. - - Setting it to anything other than localhost is very insecure as - `webnsupdate` only supports plain HTTP and should always be behind a - reverse proxy. - ''; - type = types.str; - default = "localhost"; - example = "0.0.0.0"; - }; - bindPort = mkOption { - description = "Port to bind to."; - type = types.port; - default = 5353; - }; - passwordFile = mkOption { - description = '' - The file where the password is stored. - - This file can be created by running `webnsupdate mkpasswd $USERNAME $PASSWORD`. - ''; - type = types.path; - example = "/secrets/webnsupdate.pass"; - }; - keyFile = mkOption { - description = '' - The TSIG key that `nsupdate` should use. - - This file will be passed to `nsupdate` through the `-k` option, so look - at `man 8 nsupdate` for information on the key's format. - ''; - type = types.path; - example = "/secrets/webnsupdate.key"; - }; - ttl = mkOption { - description = "The TTL that should be set on the zone records created by `nsupdate`."; - type = types.ints.positive; - default = 60; - example = 3600; - }; - records = mkOption { - description = '' - The fqdn of records that should be updated. - - Empty lines will be ignored, but whitespace will not be. - ''; - type = types.nullOr types.lines; - default = null; - example = '' - example.com. - - example.org. - ci.example.org. - ''; - }; - recordsFile = mkOption { - description = '' - The fqdn of records that should be updated. - - Empty lines will be ignored, but whitespace will not be. - ''; - type = types.nullOr types.path; - default = null; - example = "/secrets/webnsupdate.records"; - }; - user = mkOption { - description = "The user to run as."; - type = types.str; - default = "named"; - }; - group = mkOption { - description = "The group to run as."; - type = types.str; - default = "named"; - }; - }; - }; - }; - - config = - let - recordsFile = - if cfg.recordsFile != null then cfg.recordsFile else pkgs.writeText "webnsrecords" cfg.records; - args = lib.strings.escapeShellArgs ( - [ - "--records" - recordsFile - "--key-file" - cfg.keyFile - "--password-file" - cfg.passwordFile - "--address" - cfg.bindIp - "--port" - (builtins.toString cfg.bindPort) - "--ttl" - (builtins.toString cfg.ttl) - ] - ++ cfg.extraArgs - ); - cmd = "${lib.getExe pkgs.webnsupdate} ${args}"; - in - lib.mkIf cfg.enable { - # warnings = - # lib.optional (!config.services.bind.enable) "`webnsupdate` is expected to be used alongside `bind`. This is an unsopported configuration."; - assertions = [ - { - assertion = - (cfg.records != null || cfg.recordsFile != null) - && !(cfg.records != null && cfg.recordsFile != null); - message = "Exactly one of `services.webnsupdate.records` and `services.webnsupdate.recordsFile` must be set."; - } - ]; - - systemd.services.webnsupdate = { - description = "Web interface for nsupdate."; - wantedBy = [ "multi-user.target" ]; - after = [ - "network.target" - "bind.service" - ]; - preStart = "${cmd} verify"; - path = [ pkgs.dig ]; - startLimitIntervalSec = 60; - serviceConfig = { - ExecStart = [ cmd ]; - Type = "exec"; - Restart = "on-failure"; - RestartSec = "10s"; - # User and group - User = cfg.user; - Group = cfg.group; - # Runtime directory and mode - RuntimeDirectory = "webnsupdate"; - RuntimeDirectoryMode = "0750"; - # Cache directory and mode - CacheDirectory = "webnsupdate"; - CacheDirectoryMode = "0750"; - # Logs directory and mode - LogsDirectory = "webnsupdate"; - LogsDirectoryMode = "0750"; - # New file permissions - UMask = "0027"; - # Security - NoNewPrivileges = true; - ProtectHome = true; - }; - }; - }; -} diff --git a/run-cmd.nix b/run-cmd.nix deleted file mode 100644 index 02bbe9f..0000000 --- a/run-cmd.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - stdenvNoCC, - src, - name, - cmd, - extraBuildInputs ? [ ], - extraNativeBuildInputs ? [ ], -}: -stdenvNoCC.mkDerivation { - name = "${name}-src"; - inherit src; - buildInputs = extraBuildInputs; - nativeBuildInputs = extraNativeBuildInputs; - buildPhase = cmd; - installPhase = "mkdir $out"; -}