refactor(secrets): refactor sops secrets
This commit is contained in:
parent
70620411af
commit
5f42b1cb81
5 changed files with 55 additions and 97 deletions
24
flake.lock
generated
24
flake.lock
generated
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.<name>` and defaults to writing `~/.config/secrets/<name>` 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue