From 5f42b1cb814449805da157d583fa5777ec727e71 Mon Sep 17 00:00:00 2001 From: 0xrsydn Date: Tue, 14 Apr 2026 14:49:48 +0700 Subject: [PATCH] refactor(secrets): refactor sops secrets --- flake.lock | 24 ++++---- modules/darwin/home/default.nix | 55 ++----------------- modules/home/shell/nushell.nix | 28 ++++------ secrets/README.md | 51 +++++++++-------- ...-tokens.sops.yaml => global-env.sops.yaml} | 0 5 files changed, 58 insertions(+), 100 deletions(-) rename secrets/{local-ai-tokens.sops.yaml => global-env.sops.yaml} (100%) diff --git a/flake.lock b/flake.lock index 5be8b02..4562ed5 100644 --- a/flake.lock +++ b/flake.lock @@ -132,11 +132,11 @@ ] }, "locked": { - "lastModified": 1772408722, - "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", "type": "github" }, "original": { @@ -265,11 +265,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1774622711, - "narHash": "sha256-ClwgphRM+reX96UMkNv6LxcOljFPIZEziyV9ikxJ1Ek=", + "lastModified": 1776137484, + "narHash": "sha256-3gUwNO4y8ZmTyQBb3fCrLNRnqzAuuwNNSlPjmhrGnMo=", "owner": "numtide", "repo": "llm-agents.nix", - "rev": "275aaf50be7c4249d77981c2c72ee3db9daa254a", + "rev": "e70ca11dc1a327e99c58dcd580ed209056a61130", "type": "github" }, "original": { @@ -371,11 +371,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1774273680, - "narHash": "sha256-a++tZ1RQsDb1I0NHrFwdGuRlR5TORvCEUksM459wKUA=", + "lastModified": 1775888245, + "narHash": "sha256-nwASzrRDD1JBEu/o8ekKYEXm/oJW6EMCzCRdrwcLe90=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fdc7b8f7b30fdbedec91b71ed82f36e1637483ed", + "rev": "13043924aaa7375ce482ebe2494338e058282925", "type": "github" }, "original": { @@ -509,11 +509,11 @@ ] }, "locked": { - "lastModified": 1773297127, - "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=", + "lastModified": 1775636079, + "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016", + "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", "type": "github" }, "original": { diff --git a/modules/darwin/home/default.nix b/modules/darwin/home/default.nix index e402c87..d3ebc80 100644 --- a/modules/darwin/home/default.nix +++ b/modules/darwin/home/default.nix @@ -13,59 +13,16 @@ ./programs/kitty.nix ]; - # Darwin-specific secrets configuration + # Darwin-specific secrets configuration. + # Materialize the whole encrypted env map once, then let Nushell load it. rsydn.secrets = { enable = lib.mkDefault true; - defaultSopsFile = ../../../secrets/local-ai-tokens.sops.yaml; + defaultSopsFile = ../../../secrets/global-env.sops.yaml; secrets = { - "openai-api-key" = { + "global-env" = { format = "yaml"; - key = "OPENAI_API_KEY"; - }; - - "openrouter-api-key" = { - format = "yaml"; - key = "OPENROUTER_API_KEY"; - }; - - "zai-api-key" = { - format = "yaml"; - key = "ZAI_API_KEY"; - }; - - "moonshot-api-key" = { - format = "yaml"; - key = "MOONSHOT_API_KEY"; - }; - - "anthropic-api-key" = { - format = "yaml"; - key = "ANTHROPIC_API_KEY"; - }; - - "exa-api-key" = { - format = "yaml"; - key = "EXA_API_KEY"; - }; - - "fal-api-key" = { - format = "yaml"; - key = "FAL_API_KEY"; - }; - - "groq-api-key" = { - format = "yaml"; - key = "GROQ_API_KEY"; - }; - - "firecrawl-api-key" = { - format = "yaml"; - key = "FIRECRAWL_API_KEY"; - }; - - "kimi-api-key" = { - format = "yaml"; - key = "KIMI_API_KEY"; + key = ""; + path = "${config.xdg.configHome}/secrets/global-env.yaml"; }; }; }; diff --git a/modules/home/shell/nushell.nix b/modules/home/shell/nushell.nix index f526aab..cbc74af 100644 --- a/modules/home/shell/nushell.nix +++ b/modules/home/shell/nushell.nix @@ -181,26 +181,22 @@ in $env.NIX_PROFILES = "/run/current-system/sw ${config.home.profileDirectory}" - # Load sops-managed API keys as environment variables - let secrets_dir = "${config.xdg.configHome}/secrets" + # Load globally scoped sops-managed environment variables. + let global_secrets_file = "${config.xdg.configHome}/secrets/global-env.yaml" - def --env load-secret [secret_name: string, env_var: string] { - let secret_file = ($secrets_dir | path join $secret_name) + def --env load-global-secrets [secret_file: string] { if ($secret_file | path exists) { - let secret_value = (open --raw $secret_file | str trim) - {} | insert $env_var $secret_value | load-env + let secret_values = (open $secret_file) + + $secret_values + | transpose name value + | reduce -f {} {|entry, acc| + $acc | upsert $entry.name ($entry.value | into string) + } + | load-env } } - load-secret "zai-api-key" "ZAI_API_KEY" - load-secret "openai-api-key" "OPENAI_API_KEY" - load-secret "openrouter-api-key" "OPENROUTER_API_KEY" - load-secret "moonshot-api-key" "MOONSHOT_API_KEY" - load-secret "anthropic-api-key" "ANTHROPIC_API_KEY" - load-secret "exa-api-key" "EXA_API_KEY" - load-secret "fal-api-key" "FAL_API_KEY" - load-secret "groq-api-key" "GROQ_API_KEY" - load-secret "firecrawl-api-key" "FIRECRAWL_API_KEY" - load-secret "kimi-api-key" "KIMI_API_KEY" + load-global-secrets $global_secrets_file ''; } diff --git a/secrets/README.md b/secrets/README.md index aa865df..a091f8e 100644 --- a/secrets/README.md +++ b/secrets/README.md @@ -9,47 +9,52 @@ This repo wires [`sops-nix`](https://github.com/Mic92/sops-nix) into the Home Ma 4. (Optional) Store the private key securely in macOS Keychain or 1Password (`security add-generic-password …`) so rebuilds work without manual prompts. ## Managing secrets -- Declare secrets in Home Manager under `rsydn.secrets.secrets`. Each entry is forwarded to `sops.secrets.` and defaults to writing `~/.config/secrets/` with mode `0400`. -- Structure encrypted data however you like (`YAML`, `JSON`, `.env`). Only the encrypted file is tracked; plaintext never leaves `sops`. -- Keep sensitive values scoped. Per-project secrets usually live in `.env.sops` + `direnv`; long-lived device secrets belong here. +- Global shell env vars live in one encrypted YAML file: `secrets/global-env.sops.yaml`. +- Home Manager decrypts that file to `~/.config/secrets/global-env.yaml` during activation. +- Nushell reads `global-env.yaml` on startup and exports each top-level key as an environment variable. +- Only the encrypted file is tracked in git; plaintext stays local. -Example snippet for `modules/home/rsydn/secrets.nix` consumers: +Example Darwin configuration: ```nix -rsydn.secrets.secrets = { - "openai-api-key" = { - sopsFile = ./secrets.sops.yaml; - path = "${config.xdg.configHome}/secrets/openai-api-key"; +rsydn.secrets = { + enable = true; + defaultSopsFile = ../../../secrets/global-env.sops.yaml; + secrets."global-env" = { + format = "yaml"; + key = ""; + path = "${config.xdg.configHome}/secrets/global-env.yaml"; }; }; ``` -Create or update secrets by running: +Create or update global env vars by running: ```bash SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt \ - sops secrets/secrets.sops.yaml + sops secrets/global-env.sops.yaml ``` -When Home Manager activates, the secret is decrypted to the target path. Source it in Nushell with something like: +Add entries like: + +```yaml +OPENAI_API_KEY: sk-... +ANTHROPIC_API_KEY: sk-ant-... +GITHUB_TOKEN: ghp_... +``` + +After `darwin-rebuild switch --flake .#macbook-pro`, the decrypted file is refreshed and every new Nushell session automatically gets: ```nu -let-openai-key = (open $env.XDG_CONFIG_HOME ++ "/secrets/openai-api-key" | str trim) +$env.OPENAI_API_KEY +$env.ANTHROPIC_API_KEY +$env.GITHUB_TOKEN ``` -Because the secret stays on disk, prefer reading it only when needed (inside commands/functions) instead of exporting it globally. For project workflows, you can also load it into env vars temporarily: - -```nu -def with-openai-key [cmd] { - let key = open $env.XDG_CONFIG_HOME ++ "/secrets/openai-api-key" | str trim - with-env { OPENAI_API_KEY: $key } { nu -c $cmd } -} -``` - -To attach multiple secrets at once, use structured data (e.g. a YAML mapping) and parse it with Nushell's `from yaml`. +For secrets that should stay file-based instead of being auto-exported, you can still declare extra entries under `rsydn.secrets.secrets` with their own `path`, `format`, and `key`. ## Rotating keys -If you regenerate your Age key, re-encrypt the file with the new recipient (`sops updatekeys secrets/secrets.sops.yaml`) and re-run `darwin-rebuild --dry-run --flake .#macbook-pro` to verify the deployment. Remember to remove old recipients so machines without access can no longer decrypt. +If you regenerate your Age key, re-encrypt the file with the new recipient (`sops updatekeys secrets/global-env.sops.yaml`) and re-run `darwin-rebuild --dry-run --flake .#macbook-pro` to verify the deployment. Remember to remove old recipients so machines without access can no longer decrypt. ## Frequently asked questions - **Where do encrypted files live?** In this repo under `secrets/*.sops.yaml`; they are safe to commit. diff --git a/secrets/local-ai-tokens.sops.yaml b/secrets/global-env.sops.yaml similarity index 100% rename from secrets/local-ai-tokens.sops.yaml rename to secrets/global-env.sops.yaml