From d3ee4cc1690d07e71e3332119817341db0708517 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Mon, 7 Jul 2025 15:18:33 +0800 Subject: [PATCH 1/5] feat(lib): add admin user using custom lib function --- nix/flake-parts/meta.nix | 18 +++++++++++++- nix/lib/attrsets.nix | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 nix/lib/attrsets.nix diff --git a/nix/flake-parts/meta.nix b/nix/flake-parts/meta.nix index e7659e6..4398cfb 100644 --- a/nix/flake-parts/meta.nix +++ b/nix/flake-parts/meta.nix @@ -1,8 +1,16 @@ -{ lib, inputs, ... }: +{ + lib, + config, + inputs, + ... +}: let inherit (lib.options) mkOption; inherit (lib.types) path lazyAttrsOf raw; inherit (inputs.flake-parts.lib) mkSubmoduleOptions; + inherit (cfg.lib.attrsets) firstAttrNameMatching; + cfg = config.flake; + username = firstAttrNameMatching (_: v: v.primary or false) cfg.manifest.users; in { options.flake = mkSubmoduleOptions { @@ -14,5 +22,13 @@ in type = path; default = ""; }; + admin = mkOption { + type = lazyAttrsOf raw; + default = { }; + }; + }; + + config.flake.admin = cfg.manifest.users.${username} // { + inherit username; }; } diff --git a/nix/lib/attrsets.nix b/nix/lib/attrsets.nix new file mode 100644 index 0000000..1361c2a --- /dev/null +++ b/nix/lib/attrsets.nix @@ -0,0 +1,54 @@ +{ lib, ... }: +let + inherit (builtins) attrNames head; + inherit (lib.trivial) pipe; + inherit (lib.attrsets) filterAttrs; +in +{ + flake.lib.attrsets = { + /** + `firstAttrNameMatching pred set` filters an attribute set `set` based on a predicate `pred` + and returns the *first* attribute name that satisfies the predicate. + + # Example + + ```nix + let + mySet = { + a = { value = 1; }; + b = { value = 2; }; + c = { value = 3; }; + }; + + isGreaterThanOne = name: value: value.value > 1; + + result = firstAttrNameMatching isGreaterThanOne mySet; + + in + result + # Output: "b" + ``` + + # Type + + ``` + firstAttrNameMatching :: (String -> Any -> Bool) -> AttrSet -> String + ``` + + # Arguments + + pred + : A function that takes an attribute name and its value and returns a boolean. + + set + : The attribute set to filter. + */ + firstAttrNameMatching = + pred: set: + pipe set [ + (filterAttrs pred) + attrNames + head + ]; + }; +} From 986f99715b796e51474c94433ab990ac5931425c Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Mon, 7 Jul 2025 16:15:20 +0800 Subject: [PATCH 2/5] feat(nix): add sops-nix to flake inputs --- flake.lock | 21 +++++++++++++++++++++ flake.nix | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/flake.lock b/flake.lock index e3a940a..d5e0ec6 100644 --- a/flake.lock +++ b/flake.lock @@ -215,10 +215,31 @@ "import-tree": "import-tree", "make-shell": "make-shell", "nixpkgs": "nixpkgs", + "sops-nix": "sops-nix", "systems": "systems", "text": "text" } }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1751606940, + "narHash": "sha256-KrDPXobG7DFKTOteqdSVeL1bMVitDcy7otpVZWDE6MA=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "3633fc4acf03f43b260244d94c71e9e14a2f6e0d", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index 53bc0fa..eafeae6 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,11 @@ }; # impermanence provides a nice abstraction over linking files from /persist impermanence.url = "github:nix-community/impermanence"; + # sops-nix lets us version control secrets like passwords and api keys + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; # import-tree imports all nix files in a given directory. import-tree.url = "github:vic/import-tree"; # files lets us write text files and automatically add checks for them From d33aabf9d8ac6ece726d9f0b926b6a59a56ab281 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Mon, 7 Jul 2025 16:55:37 +0800 Subject: [PATCH 3/5] feat(nixos): add sops module for secrets Adds .sops.yaml file and sops module to nixos to manage secrets. --- .sops.yaml | 7 ++++++ nix/modules/secrets.nix | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .sops.yaml create mode 100644 nix/modules/secrets.nix diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..835dd06 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,7 @@ +keys: + - &rafiq age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 +creation_rules: + - path_regex: \.(yaml)$ + key_groups: + - age: + - *rafiq diff --git a/nix/modules/secrets.nix b/nix/modules/secrets.nix new file mode 100644 index 0000000..5d76562 --- /dev/null +++ b/nix/modules/secrets.nix @@ -0,0 +1,52 @@ +{ + config, + inputs, + lib, + ... +}: +let + cfg = config.flake; + inherit (builtins) readFile; + inherit (lib.meta) getExe; + inherit (lib.strings) trim; + inherit (cfg.admin) username pubkey; +in +{ + flake.modules.nixos.default = + { config, ... }: + { + imports = [ inputs.sops-nix.nixosModules.sops ]; + config.sops = { + defaultSopsFile = "${cfg.root}/secrets/secrets.yaml"; + age.sshKeyPaths = [ + "/persist${config.users.defaultUserHome}/${username}/.ssh/id_ed25519" + ]; + }; + }; + perSystem = + { pkgs, ... }: + { + files.files = [ + { + path_ = ".sops.yaml"; + drv = + pkgs.writeText ".sops.yaml" # yaml + '' + keys: + - &${username} ${trim ( + readFile "${ + pkgs.runCommand "" { } '' + mkdir $out; echo ${pubkey} | ${getExe pkgs.ssh-to-age} > $out/agepubkey + '' + }/agepubkey" + )} + creation_rules: + - path_regex: \.(yaml)$ + key_groups: + - age: + - *${username} + ''; + } + ]; + }; +} From f53a6c6c9968298de2373a778307ceba5d735c84 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Mon, 7 Jul 2025 17:23:36 +0800 Subject: [PATCH 4/5] feat(lib): add userListToAttrs function --- nix/lib/modules.nix | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/nix/lib/modules.nix b/nix/lib/modules.nix index 28b6a0b..0d5b50b 100644 --- a/nix/lib/modules.nix +++ b/nix/lib/modules.nix @@ -1,10 +1,41 @@ { lib, config, ... }: let cfg = config.flake; + inherit (builtins) foldl' attrNames; inherit (lib.attrsets) mapAttrs; in { flake.lib.modules = { + /** + Fold over the users list and create an attribute set. + + # Inputs + + `f` + + : A function that takes the name of a user and returns an attribute set. + + # Type + + ``` + userListToAttrs :: (String -> AttrSet) -> AttrSet + ``` + + # Examples + :::{.example} + ## `userListToAttrs` usage example + + ```nix + flake.manifest.users.rafiq = { ... }; + flake.modules.homeManager.users = userListToAttrs (name: { + ${name}.home.username = name; + }); + => flake.modules.homeManager.default.users.rafiq.home.username = "rafiq"; + ``` + + ::: + */ + userListToAttrs = f: foldl' (acc: elem: acc // (f elem)) { } (attrNames cfg.manifest.users); /** Return an attribute set for use with a option that needs to be used for all users. From 033755b4bb1b4188645f62f9114a965bc71fb4e2 Mon Sep 17 00:00:00 2001 From: Mohammad Rafiq Date: Mon, 7 Jul 2025 17:26:57 +0800 Subject: [PATCH 5/5] feat(nixos): add user password secrets to sops This commit adds the user password secrets to sops. It leverages the `userListToAttrs` lib function. --- nix/modules/users.nix | 8 ++++++-- secrets/secrets.yaml | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 secrets/secrets.yaml diff --git a/nix/modules/users.nix b/nix/modules/users.nix index c6e74e6..1b43eb6 100644 --- a/nix/modules/users.nix +++ b/nix/modules/users.nix @@ -1,7 +1,7 @@ { config, lib, ... }: let cfg = config.flake; - inherit (cfg.lib.modules) forAllUsers'; + inherit (cfg.lib.modules) userListToAttrs forAllUsers'; inherit (lib.lists) optional; in { @@ -19,13 +19,17 @@ in mutableUsers = false; groups.users.gid = 100; users = forAllUsers' ( - _: value: { + name: value: { isNormalUser = true; + hashedPasswordFile = config.sops.secrets."${name}/hashedPassword".path; extraGroups = optional (value.primary or false) "wheel"; openssh.authorizedKeys.keys = [ value.pubkey ]; } ); }; + sops.secrets = userListToAttrs (name: { + "${name}/hashedPassword".neededForUsers = true; + }); home-manager.users = forAllUsers' ( name: _: { home.username = name; diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml new file mode 100644 index 0000000..76fe7e0 --- /dev/null +++ b/secrets/secrets.yaml @@ -0,0 +1,18 @@ +rafiq: + password: ENC[AES256_GCM,data:8KAfatz+YSaNozd5VGo=,iv:LNRxt47iBKSWzMZuBHSxv/qDZ2h6JiTIPps7OK/o7uU=,tag:oiSfLyRVswb/wxSTE69QMA==,type:str] + hashedPassword: ENC[AES256_GCM,data:NogYQ3kR1TseC79HIXARrXhIncCnvxzf9zMF2QrUyTmojTffPXRGtMdjNpfMEFj5dkKfZujBL/QTIpPFFTm1py7Dreg5/9VSKQ==,iv:IwfZsrsJbLYG1ELte6aBHUtff6hIQu9rHT5tSvILIGQ=,tag:oav3paDcUY+cl4FJlZa90A==,type:str] +sops: + age: + - recipient: age12l33pas8eptwjc7ewux3d8snyzfzwz0tn9qg5kw8le79fswmjgjqdjgyy6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVd09tYkhKUkVjNTBRdld6 + a1RkUnZqdnRqMlFTSGgwUFVCZlRhL0tLTnpVCjNXVjZldzNUOE9DQ0ZGejhWakY2 + TmRIZnpobE0ydDhNSDdJQUp2U3pSTzgKLS0tIDkxU3Fxa2lMUkhZY0g1Wm02T2ZE + UkQwOWZtVXVPSGJiRk1qRHVHYkN2cDgKLiYiA0q5se/oHfGRqvHLn3gRRDfmefEZ + z2U2N1Tjt0QgCfYOOXVfPV9F36a7PpabFva5ElSazawHgvI+Bot6og== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-07-07T08:56:26Z" + mac: ENC[AES256_GCM,data:2uGjIMxRgk7uWToQC4MrHpHFAt4bI7sEhaHvPU6Ae3bvRVH/TdJxZtikSPe95LEwReOuBmPajbcM580/d3Jt6VbA7nZzj1JduVscrRkSAFCzZp9Ti/mbOGITPJa6xWSGwVF1wSN3BnHXYIHDcKeSGtUdP7L7nBZr1KXPkok4NCo=,iv:+ELIes7lzb8M6CvOemAcyoq7Rx7L6NkNmHwntJN/RSc=,tag:ubyxO6VllH9cQK3VbvxiGg==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2