From 2ea7f853e474ac27c242654a3bb7d9c14a542b7f Mon Sep 17 00:00:00 2001 From: 0xrsydn Date: Tue, 30 Sep 2025 17:57:13 +0700 Subject: [PATCH] add sops nix for secret management --- flake.nix | 5 +- modules/home/rsydn/secrets.nix | 70 +++++++++++++++++++ .../rsydn/shell/oh-my-posh/capr4n.omp.json | 70 +++++++++++++++++++ secrets/.gitignore | 5 ++ secrets/README.md | 58 +++++++++++++++ 5 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 modules/home/rsydn/secrets.nix create mode 100644 modules/home/rsydn/shell/oh-my-posh/capr4n.omp.json create mode 100644 secrets/.gitignore create mode 100644 secrets/README.md diff --git a/flake.nix b/flake.nix index 71c7136..d64705e 100644 --- a/flake.nix +++ b/flake.nix @@ -18,12 +18,15 @@ nix-ai-tools.url = "github:numtide/nix-ai-tools"; nix-ai-tools.inputs.nixpkgs.follows = "nixpkgs"; + sops-nix.url = "github:Mic92/sops-nix"; + sops-nix.inputs.nixpkgs.follows = "nixpkgs"; + zig-overlay.url = "github:mitchellh/zig-overlay"; zig-overlay.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = - inputs@{ self, nixpkgs, darwin, home-manager, ghostty, nix-ai-tools, ... }: + inputs@{ self, nixpkgs, darwin, home-manager, ghostty, nix-ai-tools, sops-nix, ... }: let inherit (nixpkgs.lib) genAttrs; lib = nixpkgs.lib; diff --git a/modules/home/rsydn/secrets.nix b/modules/home/rsydn/secrets.nix new file mode 100644 index 0000000..002bf27 --- /dev/null +++ b/modules/home/rsydn/secrets.nix @@ -0,0 +1,70 @@ +{ config, lib, inputs, ... }: +let + inherit (lib) mkEnableOption mkOption mkIf types; + cfg = config.rsydn.secrets; + + sanitizeSecret = name: secret: + let + sopsFile = secret.sopsFile or cfg.defaultSopsFile; + extra = lib.filterAttrs (k: _: !(builtins.elem k [ "path" "mode" "sopsFile" ])) secret; + in + { + path = secret.path or "${config.xdg.configHome}/secrets/${name}"; + mode = secret.mode or "0400"; + } + // (lib.optionalAttrs (sopsFile != null) { inherit sopsFile; }) + // extra; +in { + imports = [ inputs.sops-nix.homeManagerModules.sops ]; + + options.rsydn.secrets = { + enable = mkEnableOption "sops-nix integration for managing decrypted secrets"; + + ageKeyFile = mkOption { + type = types.str; + default = "${config.home.homeDirectory}/.config/sops/age/keys.txt"; + description = "Path to the Age key used for decrypting secrets."; + }; + + defaultSopsFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Optional default SOPS file used when a secret definition omits `sopsFile`."; + }; + + secrets = mkOption { + type = types.attrsOf types.attrs; + default = { }; + description = "Secret entries forwarded to `sops.secrets` with sensible defaults."; + example = lib.literalExpression '' + { + "api-key" = { + sopsFile = ./secrets.yaml; + path = "${config.xdg.configHome}/secrets/api-key"; + mode = "0400"; + }; + } + ''; + }; + }; + + config = mkIf cfg.enable { + sops = + { + age = { + keyFile = cfg.ageKeyFile; + generateKey = true; + }; + + secrets = lib.mapAttrs sanitizeSecret cfg.secrets; + } + // (lib.optionalAttrs (cfg.defaultSopsFile != null) { + defaultSopsFile = cfg.defaultSopsFile; + }); + + home.activation.ensureSecretDir = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + mkdir -p "${config.xdg.configHome}/secrets" + chmod 700 "${config.xdg.configHome}/secrets" + ''; + }; +} diff --git a/modules/home/rsydn/shell/oh-my-posh/capr4n.omp.json b/modules/home/rsydn/shell/oh-my-posh/capr4n.omp.json new file mode 100644 index 0000000..7926528 --- /dev/null +++ b/modules/home/rsydn/shell/oh-my-posh/capr4n.omp.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json", + "blocks": [ + { + "alignment": "right", + "segments": [ + { + "background": "#003543", + "foreground": "#ffffff", + "style": "plain", + "template": " \ue641 {{ .CurrentDate | date .Format }} ", + "type": "time" + }, + { + "background": "#83769c", + "foreground": "#ffffff", + "properties": { + "always_enabled": true + }, + "style": "plain", + "template": " \ueba2 {{ .FormattedMs }} ", + "type": "executiontime" + } + ], + "type": "rprompt" + }, + { + "alignment": "left", + "segments": [ + { + "background": "#61AFEF", + "foreground": "#ffffff", + "properties": { + "display_host": false + }, + "style": "diamond", + "template": "{{if .Root}} \uf0e7 {{.UserName}} {{else}} {{.UserName}} {{end}}", + "trailing_diamond": "\ue0b0", + "type": "session" + }, + { + "background": "#C678DD", + "foreground": "#ffffff", + "powerline_symbol": "\ue0b0", + "properties": { + "folder_icon": "\uf115", + "folder_separator_icon": " \ue0b1 ", + "max_depth": 2, + "style": "agnoster_short" + }, + "style": "powerline", + "template": " {{ .Path }} ", + "type": "path" + }, + { + "background": "#95ffa4", + "foreground": "#193549", + "powerline_symbol": "\ue0b0", + "style": "powerline", + "template": " {{ .HEAD }} ", + "type": "git" + } + ], + "type": "prompt" + } + ], + "console_title_template": "{{if .Root}} \u26a1 {{end}}{{.UserName}} \u2794 📁{{.Folder}}", + "final_space": true, + "version": 3 +} diff --git a/secrets/.gitignore b/secrets/.gitignore new file mode 100644 index 0000000..4aa2dbe --- /dev/null +++ b/secrets/.gitignore @@ -0,0 +1,5 @@ +# Ignore decrypted or temporary secrets material; commit only encrypted SOPS files +* +!.gitignore +!README.md +!*.sops.yaml diff --git a/secrets/README.md b/secrets/README.md new file mode 100644 index 0000000..aa865df --- /dev/null +++ b/secrets/README.md @@ -0,0 +1,58 @@ +# Secrets with sops-nix + +This repo wires [`sops-nix`](https://github.com/Mic92/sops-nix) into the Home Manager profile so secrets are decrypted on-demand into `~/.config/secrets`. The module is enabled by default and auto-generates an Age key if one does not already exist. Only the *encrypted* payloads under `secrets/*.sops.yaml` are meant to live in git; decrypted files never leave your machine. + +## One-time setup +1. Ensure `age` and `sops` are available (e.g. `nix develop` or `nix profile install nixpkgs#age nixpkgs#sops`). +2. Generate or import an Age key. Either let Home Manager create one automatically on first activation or run `age-keygen -o ~/.config/sops/age/keys.txt` yourself. +3. Capture the public half with `age-keygen -y -f ~/.config/sops/age/keys.txt` and add it to the `recipients` list in each encrypted file (`age1…`). Commit the public key under version control or share it through your password manager so other hosts can decrypt. +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. + +Example snippet for `modules/home/rsydn/secrets.nix` consumers: + +```nix +rsydn.secrets.secrets = { + "openai-api-key" = { + sopsFile = ./secrets.sops.yaml; + path = "${config.xdg.configHome}/secrets/openai-api-key"; + }; +}; +``` + +Create or update secrets by running: + +```bash +SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt \ + sops secrets/secrets.sops.yaml +``` + +When Home Manager activates, the secret is decrypted to the target path. Source it in Nushell with something like: + +```nu +let-openai-key = (open $env.XDG_CONFIG_HOME ++ "/secrets/openai-api-key" | str trim) +``` + +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`. + +## 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. + +## Frequently asked questions +- **Where do encrypted files live?** In this repo under `secrets/*.sops.yaml`; they are safe to commit. +- **Where does plaintext live?** At runtime under `~/.config/secrets/*` (managed by Home Manager). Keep permissions tight and never add these paths to git. +- **How do I share secrets with another machine?** Copy the Age public key from that machine into the `recipients` list, re-run `sops updatekeys`, commit, then pull and rebuild on the other host. +- **Can I keep using `.env`?** Yes—encrypt it (e.g. `secrets/project.env.sops`) and load it with `sops exec-env` inside `direnv` so per-project shells receive the decrypted variables without storing them on disk.