From 6e20baf8839e6b1f5eadfdae80687e811196158d Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Sat, 2 May 2026 22:44:03 -0400 Subject: [PATCH] feat(pi): merge sops-managed auth keys --- modules/home/programs/terminal/pi/default.nix | 82 ++++++++++++++++++- secrets/common/evanreichard.yaml | 5 +- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/modules/home/programs/terminal/pi/default.nix b/modules/home/programs/terminal/pi/default.nix index bdf0fb4..45704cf 100755 --- a/modules/home/programs/terminal/pi/default.nix +++ b/modules/home/programs/terminal/pi/default.nix @@ -21,6 +21,50 @@ let ]; piPackagesJson = pkgs.writeText "pi-packages.json" (builtins.toJSON piPackages); + + # Pi Auth Api Keys - These are merged into mutable auth.json on activation. + # Add another entry here to manage another top-level provider auth key. + piAuthApiKeys = [ + { + provider = "zai"; + secretName = "zai_apikey"; + jqVar = "zai"; + sopsFile = lib.snowfall.fs.get-file "secrets/common/evanreichard.yaml"; + } + ]; + + piAuthJqRawfiles = lib.concatStringsSep " \\\n " (map + (auth: ''--rawfile ${auth.jqVar} "${config.sops.secrets.${auth.secretName}.path}"'') + piAuthApiKeys); + + piAuthJqFilter = lib.concatStringsSep " | " (map + (auth: ''.["${auth.provider}"] = { type: "api_key", key: ($'' + auth.jqVar + '' | rtrimstr("\n")) }'') + piAuthApiKeys); + + piAuthMergeScript = pkgs.writeShellScript "pi-auth-merge" '' + set -euo pipefail + + PI_AUTH="$HOME/.pi/agent/auth.json" + mkdir -p "$(dirname "$PI_AUTH")" + + if [ -L "$PI_AUTH" ]; then + rm "$PI_AUTH" + fi + + for secret in ${lib.concatStringsSep " " (map (auth: ''"${config.sops.secrets.${auth.secretName}.path}"'') piAuthApiKeys)}; do + if [ ! -e "$secret" ]; then + echo "Skipping pi auth merge; missing sops secret: $secret" >&2 + exit 0 + fi + done + + [ -s "$PI_AUTH" ] || echo '{}' > "$PI_AUTH" + tmp=$(mktemp) + ${pkgs.jq}/bin/jq ${piAuthJqRawfiles} \ + '${piAuthJqFilter}' "$PI_AUTH" > "$tmp" + mv "$tmp" "$PI_AUTH" + chmod 600 "$PI_AUTH" + ''; in { options.${namespace}.programs.terminal.pi = { @@ -57,9 +101,16 @@ in # Pi Models Config - Inject llama-swap API key from sops into models.json # so pi can authenticate against the llm-api endpoint. sops = lib.mkIf osConfig.${namespace}.security.sops.enable { - secrets."llama_swap_api_keys/pi" = { - sopsFile = lib.snowfall.fs.get-file "secrets/common/llama-swap.yaml"; - }; + secrets = { + "llama_swap_api_keys/pi" = { + sopsFile = lib.snowfall.fs.get-file "secrets/common/llama-swap.yaml"; + }; + } // lib.listToAttrs (map + (auth: { + name = auth.secretName; + value.sopsFile = auth.sopsFile; + }) + piAuthApiKeys); templates."pi-models.json" = { path = "${config.home.homeDirectory}/.pi/agent/models.json"; content = builtins.toJSON { @@ -88,5 +139,30 @@ in '.packages = $pkgs[0]' "$PI_SETTINGS" > "$tmp" mv "$tmp" "$PI_SETTINGS" ''; + + # Merge Api Key Auth Into Mutable auth.json - Pi needs auth.json to stay + # writable, so merge sops-managed API keys instead of symlinking the whole + # file. Existing provider auth entries are preserved. + home.activation.piAuthMerge = lib.mkIf osConfig.${namespace}.security.sops.enable ( + config.lib.dag.entryAfter [ "sops-nix" "writeBoundary" ] '' + ${piAuthMergeScript} + '' + ); + + # Run Pi Auth Merge After Sops - During NixOS system activation, sops-nix + # can be restarted asynchronously and secrets may not exist yet. This user + # service retries the merge in the normal user systemd graph after sops-nix. + systemd.user.services.pi-auth-merge = lib.mkIf osConfig.${namespace}.security.sops.enable { + Unit = { + Description = "Merge sops-managed Pi auth entries"; + After = [ "sops-nix.service" ]; + Requires = [ "sops-nix.service" ]; + }; + Service = { + Type = "oneshot"; + ExecStart = piAuthMergeScript; + }; + Install.WantedBy = [ "default.target" ]; + }; }; } diff --git a/secrets/common/evanreichard.yaml b/secrets/common/evanreichard.yaml index a35edae..9846d5f 100644 --- a/secrets/common/evanreichard.yaml +++ b/secrets/common/evanreichard.yaml @@ -1,4 +1,5 @@ context7_apikey: ENC[AES256_GCM,data:K8/OoJMWBhN3ufmTa/tAiD3iMergDZQ1OBucUtLsrg+L26DXDPAko9D41w==,iv:/IVpaaPivUTn2rbIAPIwyN5nb7TmtDh05YlMdOlBkhE=,tag:0XJfoNlDelBwMXMAAqKjtQ==,type:str] +zai_apikey: ENC[AES256_GCM,data:eNgIfEqs8JGM7Qo6D5KMMqRF8fd1qLakYQ9F5oEDUvLqPJ+TAktz8GMVuSndwW5BxA==,iv:eR8IR/MDmhk2JUoT2chCwRYOJGfxEBFGARf1CI7EG8Q=,tag:3fmRWA5eof304WSWKntDFg==,type:str] kagi_token: ENC[AES256_GCM,data:6pxxMMQ3RCy6sdUFiuAy8rUzsIMMiBgPzphpgTVMfiHC98ejgVolvFszR8SHwEgTxMzzc8wMs3Eun8PzkZQt+7lqIKeNWeauiXJAHIsMZiaBJVDrxXVW,iv:zz48rUwbxAGV5+eff0Sg5Q5Pm1lGvOScUwo+g0t02z8=,tag:XgvUbgmOnXxYBZbXe3/Wkg==,type:str] rke2_kubeconfig: ENC[AES256_GCM,data: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,iv:CMrKYb+2QZVKEJMjW51rbiYW/cN6ATDzgwfBdSi9B10=,tag:qVqO5byXdj7DZdaHNx7S9A==,type:str] sops: @@ -66,7 +67,7 @@ sops: TC91cmtZWm03dzYwS2E3dkorNkdFY2sKj5OZHOtKx1NGPSGKsWjC/8+seUAhvmxb wQ0iuPAq6yDLhYV69n7Jx4G9fKoidLIQxq+Ia+tLcYt58UDX7aixJQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-04-27T14:23:33Z" - mac: ENC[AES256_GCM,data:uGNqEv/jV7iH6weoeePcaVROBGaXiF9bSgvBUMsvJy9Npt2VYrDrK2ercH7umBpbIiMDQC91oTvfF8UfmeQw9REWuoxmAB9gAUObaqAx9W3mRDMckGzrm1QyzF4c51VDaB+Uxp/SINxlz7QFbKdwDrQKGjaF47oR7mNFJQG09qE=,iv:0H5YDvvA4asqn7CG4PzamI6ez49t/hCT71aeKmFBgGE=,tag:YOnBaZq2McVUarcJ+UEF4A==,type:str] + lastmodified: "2026-05-03T02:31:38Z" + mac: ENC[AES256_GCM,data:SoiXTkxs4iT/pi0H3IVMP18NubVeKGx4fRHVHUtcbIUeb2ssXV77EZFchknZQRz0yq8KeQpdT0F/TW0qIRAbna3RAkKEHv9tM9/Nqe9QRwEjmCkuRLTozJT9eQnNvQFbmXIgfvb+eT+WsES6k3IG3T86gEr8CVwci9GMYSjvM70=,iv:PzhIUY/xboooroXjeALJJPGtdQdpC82XhrC2lvbl02U=,tag:/P3j5m41uOs1xrYrKnCe+g==,type:str] unencrypted_suffix: _unencrypted version: 3.12.1