freeze-dried nix
note
consider reading that first
your$reader nix is soppy, right? but what if we added a bit more snowflake to your$reader nix?
doll
cold
why
#.sops.yaml · yaml · 17 lines
keys: &all- &op_stella ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMr6odlUppWKyn79H31iPFdXCW8s0QgY92cvklsmeKVm stella@eulr- &machine_xylem ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFJGMeJUFcSu49onjpfMaS3iEknma/30ZhgJtQrRX1qV root@xylemcreation_rules:- path_regex: shared-secrets\.yaml$key_groups:- age: *all- path_regex: host/xylem/secrets\.yaml$key_groups:- age:- *op_stella- *machine_xylem- path_regex: user/stella/secrets\.yaml$key_groups:- age:- *op_stella
the highlighting lib it$doll uses rn breaks on yaml. sowwy
which. not the worst, it$code's pretty clear about its intents
but it$doll already has a way to declare [machines, machine keys, admins, admin keys] with nix!
#darpa.nix · nix · 31 lines
{email = "stella@lifeless.space";admins = {stella = {# home-manager setup when administrating a serverhm = pkgs: {home.packages = with pkgs; [microcurlbtop];home.stateVersion = "25.11";};shell = pkgs: pkgs.nushell;# public keys used for ssh loginssh_publickeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMr6odlUppWKyn79H31iPFdXCW8s0QgY92cvklsmeKVm stella@eulr"];};};machines = {xylem = {age_publickey = "age1wp5pysaa0gasu0fu8z0vy84fl7wkxepj7l23wwqvapn8dc38svxqdl3vj3";admins = ["stella"];};};}
what if they kissed
nix is a functional language and a build system, so it$doll took advantage of that and made it build
.sops.yaml from that nix filelet's start with adding a packages output to the flake:
#flake.nix · nix · 23 lines
{inputs = {nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";systems.url = "github:nix-systems/default";# *snip* (other inputs)};outputs = inputs: letlib = inputs.nixpkgs.lib;forAllSystems = fn: lib.genAttrs (import inputs.systems) (system: fn {inherit system;pkgs = inputs.nixpkgs.legacyPackages.${system};});in {# *snip* (nixos system)packages = forAllSystems ({ system, pkgs }: {# we'll fill this in a moment});};}
and two packages to go in there
#flake.nix · nix · 8 lines
# exports a single-file package that is the .sops.yaml config filesops-config = pkgs.writeText "sops.yaml" ''# something goes here...'';# wraps sops to use the above config package as the configsops = pkgs.writeShellScriptBin "sops" ''${pkgs.sops}/bin/sops --config ${inputs.self.packages.${system}.sops-config} $@'';
function reference
writeText name text -> derivationwrites a text file with namenameand contentstextinto the store and returns the store pathwriteShellScriptBin name text -> derivationwrites an executable bash script with namenameand contentstextin a directory$storepath/bin/$nameinto the store and returns the store path
now all we need to do is plumb the keys into their corresponding file paths
#flake.nix · nix · 31 lines
# exports a single-file package that is the .sops.yaml (except it's actually json) config filesops-config = pkgs.writeText "sops.yaml" (builtins.toJSON {creation_rules = letdarpa = import ./darpa.nix;regexes =# user secrets{"shared-secrets\\.yaml" = lib.flatten [(lib.mapAttrsToList (name: machine: machine.age_publickey) darpa.machines)(lib.mapAttrsToList (name: admin: admin.ssh_publickeys) darpa.admins)];}# host secrets// lib.mapAttrs' (name: machine: lib.nameValuePair"host/${name}/secrets\\.yaml"(lib.flatten [machine.age_publickey(map (name: darpa.admins.${name}.ssh_publickeys) machine.admins)])) darpa.machines;in lib.mapAttrsToList (regex: keys: {path_regex = regex;key_groups = [{ age = keys; }];}) regexes;});# wraps sops to use the above config package as the configsops = pkgs.writeShellScriptBin "sops" ''${pkgs.sops}/bin/sops --config ${inputs.self.packages.${system}.sops-config} $@'';
function reference
writeText name text -> derivationwrites a text file with namenameand contentstextinto the store and returns the store pathtoJSON value -> stringconverts avalueto a json stringflatten value -> listflatten avalueinto a single list (nested lists are flattened too)mapAttrsToList (name: value: return) attrset -> listmap anattrsetinto a list, using the value the functionreturns, called for each attr with thenameandvaluemapAttrs' (name: value: { name = new_name; value = new_value; }) attrsef -> attrsetmap anattrsetinto anotherattrset, using thenew_nameandnew_valuethe function returns, called for each attr with thenameandvaluenameValuePair name value -> { name = name; value = value; }simple utility function to create aname+valuepairmap (value: return) list -> listmap each value of alist, using the value the functionreturns, called for eachvaluein thelist
Her
Hey there, dolly$doll, I$Her noticed it$doll's using
builtins.toJSON. Tell me$Her, why is this?yes Goddess! nix doesn't have a way to generate normal yaml, but it does have
lib.generators.toYAML, however:#nixpkgs/lib/generators.nix · nix · 17 lines · source
/**YAML has been a strict superset of JSON since 1.2, so weuse toJSON. Before it only had a few differences referringto implicit typing rules, so it should work with olderparsers as well.# InputsOptions: Empty set, there may be configuration options in the futureValue: The value to be converted to YAML*/toYAML = {}: lib.strings.toJSON;
Her
I$Her see, thank it$doll for explaining that. Good doll~!
awwwwawwawaaawawaa!!!
using it
now we can get the actual config with
nix build .#sops-config, which builds and symlinks the config to ./result!or, directly invoke sops with
nix run .#sops -- host/xylem/secrets.yaml!!wawawa... thank for reading
final code
#flake.nix · nix · 53 lines
{inputs = {nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";systems.url = "github:nix-systems/default";# *snip*};outputs = inputs: letlib = inputs.nixpkgs.lib;forAllSystems = fn: lib.genAttrs (import inputs.systems) (system: fn {inherit system;pkgs = inputs.nixpkgs.legacyPackages.${system};});in {# *snip*packages = forAllSystems ({ system, pkgs }: {# exports a single-file package that is the .sops.yaml (except it's actually json) config filesops-config = pkgs.writeText "sops.yaml" (builtins.toJSON {creation_rules = letdarpa = import ./darpa.nix;regexes =# user secrets{"shared-secrets\\.yaml" = lib.flatten [(lib.mapAttrsToList (name: machine: machine.age_publickey) darpa.machines)(lib.mapAttrsToList (name: admin: admin.ssh_publickeys) darpa.admins)];}# host secrets// lib.mapAttrs' (name: machine: lib.nameValuePair"host/${name}/secrets\\.yaml"(lib.flatten [machine.age_publickey(map (name: darpa.admins.${name}.ssh_publickeys) machine.admins)])) darpa.machines;in lib.mapAttrsToList (regex: keys: {path_regex = regex;key_groups = [{ age = keys; }];}) regexes;});# wraps sops to use the above config package as the configsops = pkgs.writeShellScriptBin "sops" ''${pkgs.sops}/bin/sops --config ${inputs.self.packages.${system}.sops-config} $@'';});};}
| created: | |
|---|---|
| updated: | |
| built: |