feat(secrets)!: Replace sops with agenix-rekey

sops-nix is tending to be fairly complex for our use-cases, which adds
difficulty to deploying, maintaining our wrapper module, keeping
".env.bin" files, etc.

agenix-rekey is a lot simpler.

notable in this commit is the `// { outputPath = ...; }` hack in
flake.nix. This is needed due to snowfall-lib otherwise butchering paths
such that agenix-rekey is unable to show us what secrets exist with
`agenix edit`, etc... companion to that is the lib.snowfall.fs stuff in
the secrets/default.nix file

Change-Id: Id3e79cfc7d37a7b7de7b8cc42f7392c4d8bd07c5
Reviewed-on: https://git.clicks.codes/c/Infra/NixFiles/+/801
Reviewed-by: Skyler Grey <minion@clicks.codes>
Tested-by: Skyler Grey <minion@clicks.codes>
diff --git a/modules/nixos/clicks/secrets/README.md b/modules/nixos/clicks/secrets/README.md
deleted file mode 100644
index 83f43b9..0000000
--- a/modules/nixos/clicks/secrets/README.md
+++ /dev/null
@@ -1,62 +0,0 @@
-<!--
-SPDX-FileCopyrightText: 2024 Clicks Codes
-
-SPDX-License-Identifier: GPL-3.0-only
--->
-
-# Clicks SOPS
-
-To create a secret you can do the following:
-
-```nix
-clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.json}" = {
-  file = ./headscale.sops.json;
-  group = "headscale";
-  keys = [
-    "oidc_client_secret"
-    "database_password"
-    "noise_private_key"
-    "private_key"
-  ];
-  neededForUsers = false;
-};
-```
-The secret name is based on the secret file's hash.
-`file` is a path to the secrets file. It is required.
-`group` is the group the key should be owned by. We chose to use groups instead of users so that you can allow multiple
-different users to read the file. If you don't set it, we'll use `"root`.
-`keys` is a list of the keys of the secret file, assuming it's not a binary file. If it isn't a binary file, you are
-required to set this. If it is a binary file, you shouldn't specify this.
-`neededForUsers` requires the secret to be present before users are created on boot, it's identical to the sops option
-of the same name. Use it for user passwords. If you don't specify it, we'll use `false`.
-
----
-
-You can then refer to the different keys directly from the secret, no need to manually create individual files:
-
-```nix
-client_secret_path = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.json}".paths.oidc_client_secret;
-```
-
-If the secret file is a binary file, the path can be accessed via
-
-```nix
-private_key = config.clicks.secrets."${lib.clicks.secrets.name ./privatekey.bin}".path;
-```
-
----
-
-We recommend using `lib.clicks.secrets.name` with your path to name your secrets. This avoids you creating naming
-conflicts or having messy names. This is not a hard requirement for using the module outside of Clicks, but if you're
-contributing to Clicks infrastructure we will enforce this at review.
-
-This takes a path, and is guarenteed to be stable when passed the same file at the same path.
-
-```nix
-lib.clicks.secrets.name ./file.sops.json
-```
-
----
-
-In Clicks, secrets are only ever encrypted to a single host. You'll need to make the secrets within the
-`systems/<arch>/<hostname>` directory to let sops know what host to encrypt to.
diff --git a/modules/nixos/clicks/secrets/default.nix b/modules/nixos/clicks/secrets/default.nix
deleted file mode 100644
index 19f01b4..0000000
--- a/modules/nixos/clicks/secrets/default.nix
+++ /dev/null
@@ -1,138 +0,0 @@
-# SPDX-FileCopyrightText: 2024 Clicks Codes
-#
-# SPDX-License-Identifier: GPL-3.0-only
-
-{
-  lib,
-  pkgs,
-  config,
-  ...
-}:
-let
-  cfg = config.clicks.security.sops;
-
-  guessFormat =
-    extension:
-    if extension == "json" then
-      "json"
-    else if extension == "yaml" || extension == "yml" then
-      "yaml"
-    else if extension == "env" then
-      "dotenv"
-    else if extension == "ini" then
-      "ini"
-    else
-      "binary";
-
-  getExtension =
-    filePath:
-    let
-      pathParts = builtins.split ''\.'' (builtins.toString filePath);
-      numPathParts = builtins.length pathParts;
-    in
-    builtins.elemAt pathParts (numPathParts - 1);
-in
-{
-  options.clicks.secrets =
-    let
-      generateNonBinarySopsPaths =
-        file: keys:
-        lib.lists.forEach keys (key: {
-          name = key;
-          value = config.sops.secrets."${lib.clicks.secrets.name file}:${key}".path;
-        });
-    in
-    lib.mkOption {
-      type = lib.types.attrsOf (
-        lib.types.submodule (
-          { ... }@submodule:
-          {
-            options = {
-              file = lib.mkOption {
-                type = lib.types.pathInStore;
-                description = "The store path to your secrets file";
-              };
-              group = lib.mkOption {
-                type = lib.types.str;
-                description = "The user the secret should be owned by.";
-                default = "root";
-              };
-              keys = lib.mkOption {
-                type = lib.types.nullOr (lib.types.listOf lib.types.str);
-                description = "List of keys to pull from the structured data.";
-                default = null;
-              };
-              neededForUsers = lib.mkEnableOption "This secret is needed for users";
-              paths = lib.mkOption {
-                type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
-                description = "Automatically populated with the SOPS paths to your keys, null if you are using binary secrets";
-                default =
-                  if guessFormat (getExtension submodule.config.file) != "binary" then
-                    builtins.listToAttrs (generateNonBinarySopsPaths submodule.config.file submodule.config.keys)
-                  else
-                    null;
-              };
-              path = lib.mkOption {
-                type = lib.types.nullOr lib.types.str;
-                description = "Populated automatically with the SOPS path of the secret, null if you are using non binary secrets";
-                default =
-                  if guessFormat (getExtension submodule.config.file) == "binary" then
-                    config.sops.secrets.${lib.clicks.secrets.name submodule.config.file}.path
-                  else
-                    null;
-              };
-            };
-          }
-        )
-      );
-      description = "";
-      default = { };
-    };
-
-  config =
-    let
-      generateBinarySopsSecret = secret: {
-        name = lib.clicks.secrets.name secret.value.file;
-        value = {
-          mode = "0400";
-          owner = config.users.users.root.name;
-          group = config.users.groups.${secret.value.group}.name;
-          sopsFile = secret.value.file;
-          format = guessFormat (getExtension secret.value.file);
-          inherit (secret.value) neededForUsers;
-        };
-      };
-
-      generateNonBinarySopsSecrets =
-        secret:
-        lib.lists.forEach secret.value.keys (key: {
-          name = "${lib.clicks.secrets.name secret.value.file}:${key}";
-          value = {
-            mode = "0040";
-            owner = config.users.users.root.name;
-            group = config.users.groups.${secret.value.group}.name;
-            sopsFile = secret.value.file;
-            format = guessFormat (getExtension secret.value.file);
-            inherit (secret.value) neededForUsers;
-            inherit key;
-          };
-        });
-
-      secretsAsList = lib.attrsets.attrsToList config.clicks.secrets;
-
-      secretsAsSops = lib.pipe secretsAsList [
-        (map (
-          secret:
-          if guessFormat (getExtension secret.value.file) == "binary" then
-            generateBinarySopsSecret secret
-          else
-            generateNonBinarySopsSecrets secret
-        ))
-        lib.flatten
-        builtins.listToAttrs
-      ];
-    in
-    {
-      sops.secrets = secretsAsSops;
-    };
-}
diff --git a/modules/nixos/clicks/security/secrets/default.nix b/modules/nixos/clicks/security/secrets/default.nix
new file mode 100644
index 0000000..9a97f9d
--- /dev/null
+++ b/modules/nixos/clicks/security/secrets/default.nix
@@ -0,0 +1,32 @@
+# SPDX-FileCopyrightText: 2024 Auxolotl Infrastructure Contributors
+# SPDX-FileCopyrightText: 2024 Clicks Codes
+#
+# SPDX-License-Identifier: GPL-3.0-only
+
+{ config, lib, pkgs, inputs, ... }: let
+  cfg = config.clicks.security.secrets;
+in {
+  options.clicks.security.secrets.enable = lib.mkOption {
+    description = "Enable using agenix-rekey for secrets";
+    type = lib.types.bool;
+    default = true;
+  };
+
+  config = lib.mkIf cfg.enable {
+    age.rekey = {
+      masterIdentities = [
+        "${inputs.self}/secrets/keys/minion/collabora-yubikey.pub"
+        "${inputs.self}/secrets/keys/minion/tiny-yubikey.pub"
+        "${inputs.self}/secrets/keys/minion/iyubikey.pub"
+      ];
+      storageMode = "local";
+      generatedSecretsDir = lib.snowfall.fs.get-snowfall-file "secrets/generated/${config.networking.hostName}";
+      localStorageDir = lib.snowfall.fs.get-snowfall-file "secrets/rekeyed/${config.networking.hostName}";
+    };
+
+    age.identityPaths = lib.mkIf config.clicks.storage.impermanence.enable [
+      "/persist/data/etc/ssh/ssh_host_ed25519_key"
+      "/persist/data/etc/ssh/ssh_host_rsa_key"
+    ];
+  };
+}
diff --git a/modules/nixos/clicks/services/headscale/README.md b/modules/nixos/clicks/services/headscale/README.md
index 9e87c05..6c22a0f 100644
--- a/modules/nixos/clicks/services/headscale/README.md
+++ b/modules/nixos/clicks/services/headscale/README.md
@@ -45,7 +45,7 @@
     issuer = "https://login.clicks.codes/realms/master";
     allowed_groups = [ "/clicks" ];
     client_id = "headscale";
-    client_secret_path = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.a1d1.json}".paths.oidc_client_secret;
+    client_secret_path = config.age.secrets."clicks.services.headscale.oidc.client_secret_path".path;
   };
 };
 ```
@@ -64,9 +64,9 @@
 
 ```nix
 clicks.services.headscale = {
-  database_password_path = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.a1d1.json}".paths.database_password;
-  noise_private_key_path = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.a1d1.json}".paths.noise_private_key;
-  private_key_path = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.a1d1.json}".paths.private_key;
+  database_password_path = config.age.secrets."clicks.services.headscale.database_password_path".path;
+  noise_private_key_path = config.age.secrets."clicks.services.headscale.noise_private_key_path".path;
+  private_key_path = config.age.secrets."clicks.services.headscale.private_key_path".path;
 }
 ```
 
diff --git a/modules/nixos/clicks/services/postgres/README.md b/modules/nixos/clicks/services/postgres/README.md
index 3efd637..be29a14 100644
--- a/modules/nixos/clicks/services/postgres/README.md
+++ b/modules/nixos/clicks/services/postgres/README.md
@@ -8,12 +8,12 @@
 
 You can create a database, user and credentials by using `clicks.services.postgres.databases.<name>`. You should set this to a file containing the password for your database user.
 
-We recommend using our secrets module to create this password file.
+We recommend using [agenix-rekey](https://github.com/oddlama/agenix-rekey) to create this password file
 
 ```nix
 clicks.services.postgres = {
   enable = true;
-  databases.headscale = config.clicks.secrets."${lib.clicks.secrets.name ./headscale.sops.json}".paths.database_password;
+  databases.headscale = config.age.secrets."clicks.services.postgres.databases.headscale".path;
 };
 ```