From cd82f4a96839ad4b7907e0355a87ded23b5fe584 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Tue, 9 Sep 2025 16:31:35 -0700 Subject: add the mechanism by which Rust will ask the nix config for details this also adds the beginnings of a test harness; the test harness will become useful in a future CL, but for now `nix flake check` should continue to do what we want it to, and maybe slightly more Change-Id: I7f05bcb5588f2b52d79cf05cf22263f084e8be49 --- flake.nix | 52 ++++++++++++++++++++++--- options.nix | 125 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 143 insertions(+), 34 deletions(-) diff --git a/flake.nix b/flake.nix index 9e73b80..8df1039 100644 --- a/flake.nix +++ b/flake.nix @@ -26,18 +26,18 @@ forAllSystems = nixpkgs.lib.genAttrs supportedSystems; nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; }); in { - packages = forAllSystems (system: let pkgs = nixpkgsFor.${system}; in { - default = (crane.mkLib pkgs).buildPackage { - src = ./.; - }; - }); - nixosModules.default = { ... }: { imports = [ ./options.nix ]; }; + packages = forAllSystems (system: let pkgs = nixpkgsFor.${system}; in { + default = (crane.mkLib pkgs).buildPackage { + src = ./.; + }; + }); + devShells = forAllSystems (system: let pkgs = nixpkgsFor.${system}; in { default = pkgs.mkShell { nativeBuildInputs = with pkgs; [ @@ -46,5 +46,45 @@ ]; }; }); + + checks = forAllSystems (system: + let pkgs = nixpkgsFor.${system}; + mkNixEvalCheck = name: input: expected: pkgs.stdenv.mkDerivation { + name = "smalltech-nix-test-${name}"; + + src = pkgs.symlinkJoin { + name = "smalltech-nix-test-${name}-src"; + paths = [ + (pkgs.writeTextDir "input" input) + (pkgs.writeTextDir "expected" "${expected}\n") + ]; + }; + + dontUnpack = true; + + nativeBuildInputs = with pkgs; [ diffutils nix ]; + + buildPhase = '' + mkdir nix-store + ${pkgs.nix}/bin/nix \ + --extra-experimental-features nix-command \ + --store dummy:// \ + eval --json --file $src/input > $out + + if ! ${pkgs.diffutils}/bin/diff $src/expected $out; then + echo + echo "This is a nix evaluation test case. The expected eval" + echo "output differed from the actual output. In an ideal" + echo "world, the above diff would help you understand why." + echo + false + fi + ''; + }; + in { + nix-trivial = mkNixEvalCheck "trivial" "1 + 2" "3"; + + rust = self.packages.${system}.default; + }); }; } diff --git a/options.nix b/options.nix index 044833b..5fa70dc 100644 --- a/options.nix +++ b/options.nix @@ -1,38 +1,107 @@ - { pkgs, lib }: -let - scriptRegistry = { - bigsecret = ''head -c 64 /dev/urandom | base32''; - medsecret = ''head -c 32 /dev/urandom | base32''; - smallsecret = ''head -c 16 /dev/urandom | base32''; - }; -in { - secrets.secrets = lib.mkOption { - description = '' +{ + options.secrets = { + secrets = lib.mkOption { + description = '' A set of secrets, each identified by a name (e.g. mattermost, gitlabce). Each secret has a filename and a script to generate it. - ''; - default = { }; - type = lib.types.attrsOf (lib.types.submodule { - options = { - filename = lib.mkOption { - type = lib.types.str; - description = '' - The filename where this secret is stored, all - files at /etc/nixos/secrets - ''; - example = "mysecret.key"; - }; - script = lib.mkOption { - type = lib.types.str; - description = '' + ''; + default = { }; + type = lib.types.attrsOf (lib.types.submodule { + options = { + filename = lib.mkOption { + type = lib.types.pathWith { + absolute = false; + inStore = false; + }; + description = '' + The filename this secret should have. This is a relative path, + relative to the base of the secret store, which is normally + `/etc/nixos/secrets`. It is fine for it to be a bare filename. + ''; + example = "mysecret.key"; + }; + + script = lib.mkOption { + type = lib.types.lines; + description = '' Shell command that generates the secret. - "; - example = "head -c 32 /dev/urandom | base32"; + ''; + example = "head -c 32 /dev/urandom | base32"; + }; + }; + }); + }; + + # For modularity's sake, we export a cleanly-defined structure for the CLI + # tool to wrap around. That way we avoid inadvertently relying on the + # overall structure of the secrets options, and in particular we avoid + # relying on implementation details that will change. + export = lib.mkOption { + description = '' + An internal value used for the command-line secret-management tool to + query an assembled NixOS configuration about what secrets it expects + to exist, and their properties. + ''; + example = { + mattermost = { + path = "/etc/nixos/secrets/mattermost.key"; + script = "touch /etc/nixos/secrets/mattermost.key" + }; + + neooffice = { + path = "/etc/nixos/secrets/neooffice.key"; + script = "head -c 32 /dev/urandom > /etc/nixos/secrets/neooffice.key" + }; + }; + + type = lib.types.attrsOf lib.types.submodule = { + options = { + path = { + type = lib.types.pathWith { + absolute = true; + inStore = false; + }; + description = '' + An internal value which is part of `secrets.export`, used by + the command-line secret-management tool. This is an absolute + path to the file which, if the secret exists, should contain it. + When the CLI tool is run, this directory is expected to exist; + whether the file exists is the thing the CLI tool is responsible + for managing. + ''; + example = "/etc/nixos/secrets/neooffice.key"; + }; + + script = { + type = lib.types.lines; + description = '' + An internal value which is part of `secrets.export`, used by + the command-line secret-management tool. This is a shell script + which will create the secret, destructively overwriting it if it + already exists. It runs in an empty shell environment with no + environment variables. It should start with an appropriate + `#!` line pointing to the correct shell to run it under. + ''; + example = '' + #!/nix/store/s68ja06r60xbs51k8lrdciva4di46y61-bash-5.2/bin/bash + head -c 32 /dev/urandom > /etc/nixos/secrets/neooffice.key + ''; + }; }; }; - }); + }; }; + + config.secrets.export = { config, pkgs, ... }: + let exportSecret = name: secret: { + path = "/etc/nixos/secrets/${secret.file}"; + script = '' + #!${pkgs.bash}/bin/bash + ${secret.script} + ''; + }; + in mapAttrs exportSecret config.secrets.secrets; } -- cgit 1.4.1