From a58e84281766276450866f0cd21e366b8ce91734 Mon Sep 17 00:00:00 2001 From: 0xrsydn Date: Wed, 2 Sep 2026 15:08:13 +0700 Subject: [PATCH] switch default shell to zsh and adopt catppuccin mocha --- README.MD | 6 +- modules/darwin/home/default.nix | 41 ++++-- modules/darwin/home/programs/kitty.nix | 61 +++++--- modules/darwin/homebrew.nix | 2 +- modules/darwin/system.nix | 4 +- modules/home/base.nix | 2 +- modules/home/devtools/direnv.nix | 1 + .../home/programs/nvim/lua/config/lazy.lua | 2 +- .../programs/nvim/lua/plugins/colorscheme.lua | 15 +- modules/home/shell/nushell.nix | 26 ++-- modules/home/shell/starship/default.nix | 1 + modules/home/shell/starship/starship.toml | 139 ++++++++++-------- modules/home/shell/tmux.nix | 17 ++- modules/home/shell/zsh.nix | 136 +++++++++++++++++ secrets/README.md | 92 +++++++----- 15 files changed, 388 insertions(+), 157 deletions(-) create mode 100644 modules/home/shell/zsh.nix diff --git a/README.MD b/README.MD index 2f7a023..cdc1aa7 100644 --- a/README.MD +++ b/README.MD @@ -7,8 +7,8 @@ So far, the dotfiles only used for my macOS configuration, maybe in the future I ## Core System - OS: macOS (nix-darwin) -- Shell: nushell with starship prompt -- Terminal: kitty with gruvbox theme +- Shell: Zsh with Oh My Zsh and Starship; Nushell is available through `nu` +- Terminal: Kitty with Catppuccin Mocha theme - Package Management: mix of nix and homebrew ## Developer Tools @@ -22,7 +22,7 @@ So far, the dotfiles only used for my macOS configuration, maybe in the future I ## AI Tools -- CLI-based tools: codex, crush, opencode, claude code +- CLI-based tools: codex, crush, claude code - Custom z.ai gateway wrapper for claude code - Secret management with SOPS-nix diff --git a/modules/darwin/home/default.nix b/modules/darwin/home/default.nix index d3ebc80..e68737f 100644 --- a/modules/darwin/home/default.nix +++ b/modules/darwin/home/default.nix @@ -4,26 +4,49 @@ lib, ... }: +let + globalEnvironmentSecretNames = [ + "ANTHROPIC_API_KEY" + "BRAVE_SEARCH_API_KEY" + "CROF_API_KEY" + "EXA_API_KEY" + "FAL_API_KEY" + "FIRECRAWL_API_KEY" + "GROQ_API_KEY" + "KIMI_API_KEY" + "MOONSHOT_API_KEY" + "OPENAI_API_KEY" + "OPENROUTER_API_KEY" + "ZAI_API_KEY" + ]; +in { # Import shared cross-platform home configuration imports = [ ../../home/base.nix ../../home/shell/nushell.nix + ../../home/shell/zsh.nix ./programs/aerospace ./programs/kitty.nix ]; - # Darwin-specific secrets configuration. - # Materialize the whole encrypted env map once, then let Nushell load it. + # Decrypt each global environment variable to a separate runtime file. + # Zsh and Nushell load the files without evaluating shell source text. rsydn.secrets = { enable = lib.mkDefault true; defaultSopsFile = ../../../secrets/global-env.sops.yaml; - secrets = { - "global-env" = { - format = "yaml"; - key = ""; - path = "${config.xdg.configHome}/secrets/global-env.yaml"; - }; - }; + secrets = lib.genAttrs globalEnvironmentSecretNames (name: { + format = "yaml"; + key = name; + path = "${config.xdg.configHome}/secrets/global-env/${name}"; + }); }; + + home.activation.ensureGlobalEnvironmentSecretDir = + lib.hm.dag.entryBetween [ "sops-nix" ] [ "writeBoundary" ] + '' + rm -f "${config.xdg.configHome}/secrets/global-env.yaml" + mkdir -p "${config.xdg.configHome}/secrets/global-env" + chmod 700 "${config.xdg.configHome}/secrets/global-env" + ''; } diff --git a/modules/darwin/home/programs/kitty.nix b/modules/darwin/home/programs/kitty.nix index c7476c0..e266539 100644 --- a/modules/darwin/home/programs/kitty.nix +++ b/modules/darwin/home/programs/kitty.nix @@ -4,34 +4,49 @@ enable = true; settings = { + # JetBrainsMono Nerd Font renders the Starship powerline icons. + font_family = "JetBrainsMono Nerd Font"; + font_size = 13.0; + background_opacity = "0.9"; confirm_os_window_close = 0; enabled_layouts = "splits,stack"; - # Gruvbox colors - background = "#282828"; - foreground = "#ebdbb2"; - cursor = "#ebdbb2"; - cursor_text_color = "#282828"; - selection_background = "#ebdbb2"; - selection_foreground = "#282828"; + # Catppuccin Mocha colors (catppuccin/kitty themes/mocha.conf) + background = "#1e1e2e"; + foreground = "#cdd6f4"; + cursor = "#f5e0dc"; + cursor_text_color = "#1e1e2e"; + selection_background = "#f5e0dc"; + selection_foreground = "#1e1e2e"; - color0 = "#282828"; - color1 = "#cc241d"; - color2 = "#98971a"; - color3 = "#d79921"; - color4 = "#458588"; - color5 = "#b16286"; - color6 = "#689d6a"; - color7 = "#a89984"; - color8 = "#928374"; - color9 = "#fb4934"; - color10 = "#b8bb26"; - color11 = "#fabd2f"; - color12 = "#83a598"; - color13 = "#d3869b"; - color14 = "#8ec07c"; - color15 = "#ebdbb2"; + # Kitty window border colors + active_border_color = "#b4befe"; + inactive_border_color = "#6c7086"; + + # Tab bar colors + active_tab_foreground = "#11111b"; + active_tab_background = "#cba6f7"; + inactive_tab_foreground = "#cdd6f4"; + inactive_tab_background = "#181825"; + tab_bar_background = "#11111b"; + + color0 = "#45475a"; + color1 = "#f38ba8"; + color2 = "#a6e3a1"; + color3 = "#f9e2af"; + color4 = "#89b4fa"; + color5 = "#f5c2e7"; + color6 = "#94e2d5"; + color7 = "#bac2de"; + color8 = "#585b70"; + color9 = "#f38ba8"; + color10 = "#a6e3a1"; + color11 = "#f9e2af"; + color12 = "#89b4fa"; + color13 = "#f5c2e7"; + color14 = "#94e2d5"; + color15 = "#a6adc8"; }; keybindings = { diff --git a/modules/darwin/homebrew.nix b/modules/darwin/homebrew.nix index a035c27..2855697 100644 --- a/modules/darwin/homebrew.nix +++ b/modules/darwin/homebrew.nix @@ -45,7 +45,7 @@ in default = [ "bitwarden" "brave-browser" - "font-jetbrains-mono" + "font-jetbrains-mono-nerd-font" "pgadmin4" "spotify" "vesktop" diff --git a/modules/darwin/system.nix b/modules/darwin/system.nix index 4b9a5a6..5e0f474 100644 --- a/modules/darwin/system.nix +++ b/modules/darwin/system.nix @@ -54,8 +54,8 @@ users.users.${user} = { home = lib.mkDefault "/Users/${user}"; - # Use the Home Manager nushell which has proper config setup - shell = "/etc/profiles/per-user/${user}/bin/nu"; + # Use the macOS system Zsh as the login shell. Nushell remains available as `nu`. + shell = "/bin/zsh"; }; system = { diff --git a/modules/home/base.nix b/modules/home/base.nix index f741012..704c8e2 100644 --- a/modules/home/base.nix +++ b/modules/home/base.nix @@ -49,7 +49,7 @@ rsydn.devTools = { enable = lib.mkDefault true; - # Note: jetbrains-mono removed - installed via Homebrew cask (font-jetbrains-mono) + # Note: jetbrains-mono removed - installed via Homebrew cask (font-jetbrains-mono-nerd-font) # due to nixpkgs-unstable syrupy test failures breaking the font's Python build deps packages = with pkgs; [ age diff --git a/modules/home/devtools/direnv.nix b/modules/home/devtools/direnv.nix index 48af946..b7cc3e2 100644 --- a/modules/home/devtools/direnv.nix +++ b/modules/home/devtools/direnv.nix @@ -21,6 +21,7 @@ in programs.direnv = { enable = true; enableNushellIntegration = true; + enableZshIntegration = true; nix-direnv.enable = true; silent = cfg.silent; }; diff --git a/modules/home/programs/nvim/lua/config/lazy.lua b/modules/home/programs/nvim/lua/config/lazy.lua index 2c4b53e..002bbdf 100644 --- a/modules/home/programs/nvim/lua/config/lazy.lua +++ b/modules/home/programs/nvim/lua/config/lazy.lua @@ -31,7 +31,7 @@ require("lazy").setup({ version = false, }, - install = { colorscheme = { "gruvbox", "habamax" } }, + install = { colorscheme = { "catppuccin-mocha", "habamax" } }, checker = { enabled = false }, change_detection = { notify = false }, diff --git a/modules/home/programs/nvim/lua/plugins/colorscheme.lua b/modules/home/programs/nvim/lua/plugins/colorscheme.lua index 56fb218..99b398a 100644 --- a/modules/home/programs/nvim/lua/plugins/colorscheme.lua +++ b/modules/home/programs/nvim/lua/plugins/colorscheme.lua @@ -2,13 +2,8 @@ return { -- gruvbox { "ellisonleao/gruvbox.nvim", - lazy = false, - priority = 1000, + lazy = true, opts = {}, - config = function() - require("gruvbox").setup({}) - vim.cmd.colorscheme("gruvbox") - end, }, -- tokyonight @@ -21,10 +16,12 @@ return { -- catppuccin { "catppuccin/nvim", - lazy = true, + lazy = false, priority = 1000, name = "catppuccin", opts = { + flavour = "mocha", + term_colors = true, lsp_styles = { underlines = { errors = { "undercurl" }, @@ -60,6 +57,10 @@ return { -- which_key = true, -- }, }, + config = function(_, opts) + require("catppuccin").setup(opts) + vim.cmd.colorscheme("catppuccin-mocha") + end, -- specs = { -- { -- "akinsho/bufferline.nvim", diff --git a/modules/home/shell/nushell.nix b/modules/home/shell/nushell.nix index cbc74af..6026f21 100644 --- a/modules/home/shell/nushell.nix +++ b/modules/home/shell/nushell.nix @@ -181,22 +181,24 @@ in $env.NIX_PROFILES = "/run/current-system/sw ${config.home.profileDirectory}" - # Load globally scoped sops-managed environment variables. - let global_secrets_file = "${config.xdg.configHome}/secrets/global-env.yaml" + # Load each globally scoped sops-managed environment variable. + let global_secrets_dir = "${config.xdg.configHome}/secrets/global-env" - def --env load-global-secrets [secret_file: string] { - if ($secret_file | path exists) { - let secret_values = (open $secret_file) + def --env load-global-secrets [secret_dir: string] { + if ($secret_dir | path exists) { + let secret_values = ( + ls $secret_dir + | reduce -f {} {|entry, acc| + let name = ($entry.name | path basename) + let value = (open --raw $entry.name | str trim) + $acc | upsert $name $value + } + ) - $secret_values - | transpose name value - | reduce -f {} {|entry, acc| - $acc | upsert $entry.name ($entry.value | into string) - } - | load-env + $secret_values | load-env } } - load-global-secrets $global_secrets_file + load-global-secrets $global_secrets_dir ''; } diff --git a/modules/home/shell/starship/default.nix b/modules/home/shell/starship/default.nix index 3998b2e..def512a 100644 --- a/modules/home/shell/starship/default.nix +++ b/modules/home/shell/starship/default.nix @@ -4,6 +4,7 @@ enable = lib.mkDefault true; enableFishIntegration = true; enableNushellIntegration = true; + enableZshIntegration = true; settings = lib.importTOML ./starship.toml; }; } diff --git a/modules/home/shell/starship/starship.toml b/modules/home/shell/starship/starship.toml index 1bfc934..ea8cb56 100644 --- a/modules/home/shell/starship/starship.toml +++ b/modules/home/shell/starship/starship.toml @@ -1,15 +1,15 @@ "$schema" = "https://starship.rs/config-schema.json" format = """ -[](color_orange)\ +[](peach)\ $os\ $username\ -[](bg:color_yellow fg:color_orange)\ +[](bg:yellow fg:peach)\ $directory\ -[](fg:color_yellow bg:color_aqua)\ +[](fg:yellow bg:teal)\ $git_branch\ $git_status\ -[](fg:color_aqua bg:color_blue)\ +[](fg:teal bg:blue)\ $c\ $cpp\ $rust\ @@ -20,32 +20,49 @@ $java\ $kotlin\ $haskell\ $python\ -[](fg:color_blue bg:color_bg3)\ +[](fg:blue bg:surface0)\ $docker_context\ $conda\ $pixi\ -[](fg:color_bg3 bg:color_bg1)\ +[](fg:surface0 bg:base)\ $time\ -[ ](fg:color_bg1)\ +[ ](fg:base)\ $line_break$character""" -palette = "gruvbox_dark" +palette = "catppuccin_mocha" -[palettes.gruvbox_dark] -color_fg0 = "#fbf1c7" -color_bg1 = "#3c3836" -color_bg3 = "#665c54" -color_blue = "#458588" -color_aqua = "#689d6a" -color_green = "#98971a" -color_orange = "#d65d0e" -color_purple = "#b16286" -color_red = "#cc241d" -color_yellow = "#d79921" +# Official Catppuccin Mocha palette (catppuccin/starship themes/mocha.toml) +[palettes.catppuccin_mocha] +rosewater = "#f5e0dc" +flamingo = "#f2cdcd" +pink = "#f5c2e7" +mauve = "#cba6f7" +red = "#f38ba8" +maroon = "#eba0ac" +peach = "#fab387" +yellow = "#f9e2af" +green = "#a6e3a1" +teal = "#94e2d5" +sky = "#89dceb" +sapphire = "#74c7ec" +blue = "#89b4fa" +lavender = "#b4befe" +text = "#cdd6f4" +subtext1 = "#bac2de" +subtext0 = "#a6adc8" +overlay2 = "#9399b2" +overlay1 = "#7f849c" +overlay0 = "#6c7086" +surface2 = "#585b70" +surface1 = "#45475a" +surface0 = "#313244" +base = "#1e1e2e" +mantle = "#181825" +crust = "#11111b" [os] disabled = false -style = "bg:color_orange fg:color_fg0" +style = "bg:peach fg:crust" [os.symbols] Windows = "󰍲" @@ -72,12 +89,12 @@ Pop = "" [username] show_always = true -style_user = "bg:color_orange fg:color_fg0" -style_root = "bg:color_orange fg:color_fg0" +style_user = "bg:peach fg:crust" +style_root = "bg:peach fg:crust" format = "[ $user ]($style)" [directory] -style = "fg:color_fg0 bg:color_yellow" +style = "fg:crust bg:yellow" format = "[ $path ]($style)" truncation_length = 3 truncation_symbol = "…/" @@ -91,90 +108,90 @@ Developer = "󰲋 " [git_branch] symbol = "" -style = "bg:color_aqua" -format = "[[ $symbol $branch ](fg:color_fg0 bg:color_aqua)]($style)" +style = "bg:teal" +format = "[[ $symbol $branch ](fg:crust bg:teal)]($style)" [git_status] -style = "bg:color_aqua" -format = "[[($all_status$ahead_behind )](fg:color_fg0 bg:color_aqua)]($style)" +style = "bg:teal" +format = "[[($all_status$ahead_behind )](fg:crust bg:teal)]($style)" [nodejs] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [c] symbol = " " -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [cpp] symbol = " " -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [rust] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [golang] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [php] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [java] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [kotlin] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [haskell] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [python] symbol = "" -style = "bg:color_blue" -format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" +style = "bg:blue" +format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)" [docker_context] symbol = "" -style = "bg:color_bg3" -format = "[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)" +style = "bg:surface0" +format = "[[ $symbol( $context) ](fg:teal bg:surface0)]($style)" [conda] -style = "bg:color_bg3" -format = "[[ $symbol( $environment) ](fg:#83a598 bg:color_bg3)]($style)" +style = "bg:surface0" +format = "[[ $symbol( $environment) ](fg:teal bg:surface0)]($style)" [pixi] -style = "bg:color_bg3" -format = "[[ $symbol( $version)( $environment) ](fg:color_fg0 bg:color_bg3)]($style)" +style = "bg:surface0" +format = "[[ $symbol( $version)( $environment) ](fg:text bg:surface0)]($style)" [time] disabled = false time_format = "%R" -style = "bg:color_bg1" -format = "[[  $time ](fg:color_fg0 bg:color_bg1)]($style)" +style = "bg:base" +format = "[[  $time ](fg:text bg:base)]($style)" [line_break] disabled = false [character] disabled = false -success_symbol = "[](bold fg:color_green)" -error_symbol = "[](bold fg:color_red)" -vimcmd_symbol = "[](bold fg:color_green)" -vimcmd_replace_one_symbol = "[](bold fg:color_purple)" -vimcmd_replace_symbol = "[](bold fg:color_purple)" -vimcmd_visual_symbol = "[](bold fg:color_yellow)" +success_symbol = "[](bold fg:green)" +error_symbol = "[](bold fg:red)" +vimcmd_symbol = "[](bold fg:green)" +vimcmd_replace_one_symbol = "[](bold fg:mauve)" +vimcmd_replace_symbol = "[](bold fg:mauve)" +vimcmd_visual_symbol = "[](bold fg:yellow)" \ No newline at end of file diff --git a/modules/home/shell/tmux.nix b/modules/home/shell/tmux.nix index 8d575b1..dccae97 100644 --- a/modules/home/shell/tmux.nix +++ b/modules/home/shell/tmux.nix @@ -14,7 +14,15 @@ prefix = "C-a"; escapeTime = 0; aggressiveResize = true; - plugins = [ { plugin = pkgs.tmuxPlugins.gruvbox; } ]; + plugins = [ + { + plugin = pkgs.tmuxPlugins.catppuccin; + extraConfig = '' + set -g @catppuccin_flavor "mocha" + set -g @catppuccin_window_status_style "rounded" + ''; + } + ]; extraConfig = '' # Terminal configuration for proper colors and features set-option -g default-terminal "screen-256color" @@ -50,9 +58,10 @@ bind J resize-pane -D 5 bind K resize-pane -U 5 bind L resize-pane -R 5 - # Ensure tmux panes spawn login nu so Starship integration runs - set -g default-shell "${config.home.profileDirectory}/bin/nu" - set -g default-command "${config.home.profileDirectory}/bin/nu --login" + # Start Zsh by default and keep Nushell available on prefix + C-n. + set -g default-shell "/bin/zsh" + set -g default-command "/bin/zsh --login" + bind-key C-n new-window -n nushell "${config.home.profileDirectory}/bin/nu --login" ''; }; } diff --git a/modules/home/shell/zsh.nix b/modules/home/shell/zsh.nix new file mode 100644 index 0000000..833831e --- /dev/null +++ b/modules/home/shell/zsh.nix @@ -0,0 +1,136 @@ +{ config, ... }: +{ + programs.zsh = { + enable = true; + enableCompletion = true; + dotDir = config.home.homeDirectory; + + oh-my-zsh = { + enable = true; + plugins = [ + "git" + "sudo" + ]; + }; + + shellAliases.ssh = "kitten ssh"; + + profileExtra = '' + if [[ -x /opt/homebrew/bin/brew ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif [[ -x /usr/local/bin/brew ]]; then + eval "$(/usr/local/bin/brew shellenv)" + fi + + [[ -r "$HOME/.orbstack/shell/init.zsh" ]] && source "$HOME/.orbstack/shell/init.zsh" + ''; + + initContent = '' + typeset -U path PATH + path=( + "$HOME/.cache/.bun/bin" + "$HOME/.local/bin" + $path + "$HOME/.lmstudio/bin" + ) + export PATH + + _load_global_secrets() { + local secret_dir="''${XDG_CONFIG_HOME:-$HOME/.config}/secrets/global-env" + local secret_file name value + + [[ -d "$secret_dir" ]] || return 0 + + for secret_file in "$secret_dir"/*(N.); do + name="''${secret_file:t}" + [[ "$name" =~ '^[A-Za-z_][A-Za-z0-9_]*$' ]] || continue + value="$(<"$secret_file")" + export "$name=$value" + done + } + + _load_global_secrets + unfunction _load_global_secrets + + dotfiles-qa() ( + local dotfiles_dir="${config.home.homeDirectory}/Development/dotfiles" + + print -P "%F{green}Running dotfiles QA validation...%f" + cd "$dotfiles_dir" || return 1 + + print -P "%F{blue}1. Formatting Nix files...%f" + nix fmt . || { + print -P "%F{red}Formatting failed.%f" + return 1 + } + + print -P "%F{blue}2. Running flake checks...%f" + XDG_CACHE_HOME="$dotfiles_dir/.cache" nix flake check || { + print -P "%F{red}Flake checks failed.%f" + return 1 + } + + print -P "%F{blue}3. Building the Darwin configuration...%f" + darwin-rebuild build --flake "$dotfiles_dir#macbook-pro" || { + print -P "%F{red}Darwin build failed.%f" + return 1 + } + + print -P "%F{green}Core QA checks passed.%f" + ) + + bd-init() { + if [[ ! -d .git && ! -f .git ]]; then + print -P "%F{red}Error: not in a Git repository.%f" + print "Initialize one with: git init" + return 1 + fi + + if [[ -e .beads ]]; then + print -P "%F{yellow}Beads is already initialized in this project.%f" + return 0 + fi + + bd init || return + print -P "%F{green}Beads initialized.%f" + print "Next steps:" + print ' bd create --title "Your task" --type feature' + print " bd ready" + } + + bd-ready() { + if [[ ! -e .beads ]]; then + print -P "%F{red}Not a Beads project. Run bd-init first.%f" + return 1 + fi + + local ready_tasks + ready_tasks="$(bd ready --json)" || return + + if [[ "$(print -r -- "$ready_tasks" | jq 'length')" == 0 ]]; then + print -P "%F{yellow}No ready tasks.%f" + return 0 + fi + + print -P "%F{green}Ready tasks:%f" + print -r -- "$ready_tasks" | jq -r ' + .[] | + "[\(.id)] \(.title)\n Type: \(.type) | Priority: \(.priority) | Status: \(.status)" + + (if ((.description // "") | length) > 0 then "\n \(.description)" else "" end) + + "\n" + ' + } + + bd-sync() { + if [[ ! -e .beads ]]; then + print -P "%F{red}Not a Beads project.%f" + return 1 + fi + + print -P "%F{blue}Syncing Beads database...%f" + bd sync || return + print -P "%F{green}Sync complete.%f" + } + ''; + }; +} diff --git a/secrets/README.md b/secrets/README.md index a091f8e..24b369b 100644 --- a/secrets/README.md +++ b/secrets/README.md @@ -1,41 +1,46 @@ # 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. +This repository uses [`sops-nix`](https://github.com/Mic92/sops-nix) with Home Manager. SOPS keeps encrypted values in Git. Home Manager decrypts each global environment variable to a separate runtime file under `~/.config/secrets/global-env/`. ## 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 -- 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. +1. Enter `nix develop` so `age` and `sops` are available. +2. Generate or import an Age key. Home Manager can generate one during activation. You can also run: -Example Darwin configuration: + ```bash + age-keygen -o ~/.config/sops/age/keys.txt + ``` -```nix -rsydn.secrets = { - enable = true; - defaultSopsFile = ../../../secrets/global-env.sops.yaml; - secrets."global-env" = { - format = "yaml"; - key = ""; - path = "${config.xdg.configHome}/secrets/global-env.yaml"; - }; -}; +3. Get the public key: + + ```bash + age-keygen -y -f ~/.config/sops/age/keys.txt + ``` + +4. Add the public key as a recipient in each encrypted SOPS file. +5. Store the private key in a secure backup such as 1Password or macOS Keychain. + +## Global environment secrets + +The encrypted source is `secrets/global-env.sops.yaml`. It contains a flat map of environment variable names and encrypted values. + +Home Manager declares each key in `modules/darwin/home/default.nix`. During activation, sops-nix writes one mode `0400` file per key: + +```text +~/.config/secrets/global-env/OPENAI_API_KEY +~/.config/secrets/global-env/ANTHROPIC_API_KEY ``` -Create or update global env vars by running: +Zsh and Nushell read these files and export each filename as an environment variable. The loaders do not evaluate secret values as shell code. + +Edit the encrypted source with: ```bash SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt \ sops secrets/global-env.sops.yaml ``` -Add entries like: +Use a flat YAML structure: ```yaml OPENAI_API_KEY: sk-... @@ -43,21 +48,42 @@ 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: +When you add or remove a key, also update `globalEnvironmentSecretNames` in `modules/darwin/home/default.nix`. Then apply the configuration: + +```bash +darwin-rebuild switch --flake .#macbook-pro +``` + +Open a new Zsh or Nushell session after activation. Access values as follows: + +```zsh +print -r -- "$OPENAI_API_KEY" +``` ```nu $env.OPENAI_API_KEY -$env.ANTHROPIC_API_KEY -$env.GITHUB_TOKEN ``` -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`. +Do not print real secret values during routine validation. Test only whether a variable exists. + +## File-based secrets + +Secrets that applications consume as files should stay file-based. Declare each entry under `rsydn.secrets.secrets` with its own `path`, `format`, and `key`. + +For project-specific environment variables, use an encrypted project file with `sops exec-env` through direnv. Do not add project secrets to the global shell environment. ## Rotating keys -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. -- **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. +If you replace an Age key, update the recipients: + +```bash +sops updatekeys secrets/global-env.sops.yaml +``` + +Remove old recipients, then validate the configuration: + +```bash +darwin-rebuild --dry-run --flake .#macbook-pro +``` + +Only encrypted SOPS files belong in Git. Never add decrypted files from `~/.config/secrets/`.