switch default shell to zsh and adopt catppuccin mocha

This commit is contained in:
Rasyidan Akbar F. 2026-09-02 15:08:13 +07:00
commit a58e842817
15 changed files with 391 additions and 160 deletions

View file

@ -7,8 +7,8 @@ So far, the dotfiles only used for my macOS configuration, maybe in the future I
## Core System ## Core System
- OS: macOS (nix-darwin) - OS: macOS (nix-darwin)
- Shell: nushell with starship prompt - Shell: Zsh with Oh My Zsh and Starship; Nushell is available through `nu`
- Terminal: kitty with gruvbox theme - Terminal: Kitty with Catppuccin Mocha theme
- Package Management: mix of nix and homebrew - Package Management: mix of nix and homebrew
## Developer Tools ## Developer Tools
@ -22,7 +22,7 @@ So far, the dotfiles only used for my macOS configuration, maybe in the future I
## AI Tools ## 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 - Custom z.ai gateway wrapper for claude code
- Secret management with SOPS-nix - Secret management with SOPS-nix

View file

@ -4,26 +4,49 @@
lib, 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 # Import shared cross-platform home configuration
imports = [ imports = [
../../home/base.nix ../../home/base.nix
../../home/shell/nushell.nix ../../home/shell/nushell.nix
../../home/shell/zsh.nix
./programs/aerospace ./programs/aerospace
./programs/kitty.nix ./programs/kitty.nix
]; ];
# Darwin-specific secrets configuration. # Decrypt each global environment variable to a separate runtime file.
# Materialize the whole encrypted env map once, then let Nushell load it. # Zsh and Nushell load the files without evaluating shell source text.
rsydn.secrets = { rsydn.secrets = {
enable = lib.mkDefault true; enable = lib.mkDefault true;
defaultSopsFile = ../../../secrets/global-env.sops.yaml; defaultSopsFile = ../../../secrets/global-env.sops.yaml;
secrets = { secrets = lib.genAttrs globalEnvironmentSecretNames (name: {
"global-env" = {
format = "yaml"; format = "yaml";
key = ""; key = name;
path = "${config.xdg.configHome}/secrets/global-env.yaml"; 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"
'';
} }

View file

@ -4,34 +4,49 @@
enable = true; enable = true;
settings = { settings = {
# JetBrainsMono Nerd Font renders the Starship powerline icons.
font_family = "JetBrainsMono Nerd Font";
font_size = 13.0;
background_opacity = "0.9"; background_opacity = "0.9";
confirm_os_window_close = 0; confirm_os_window_close = 0;
enabled_layouts = "splits,stack"; enabled_layouts = "splits,stack";
# Gruvbox colors # Catppuccin Mocha colors (catppuccin/kitty themes/mocha.conf)
background = "#282828"; background = "#1e1e2e";
foreground = "#ebdbb2"; foreground = "#cdd6f4";
cursor = "#ebdbb2"; cursor = "#f5e0dc";
cursor_text_color = "#282828"; cursor_text_color = "#1e1e2e";
selection_background = "#ebdbb2"; selection_background = "#f5e0dc";
selection_foreground = "#282828"; selection_foreground = "#1e1e2e";
color0 = "#282828"; # Kitty window border colors
color1 = "#cc241d"; active_border_color = "#b4befe";
color2 = "#98971a"; inactive_border_color = "#6c7086";
color3 = "#d79921";
color4 = "#458588"; # Tab bar colors
color5 = "#b16286"; active_tab_foreground = "#11111b";
color6 = "#689d6a"; active_tab_background = "#cba6f7";
color7 = "#a89984"; inactive_tab_foreground = "#cdd6f4";
color8 = "#928374"; inactive_tab_background = "#181825";
color9 = "#fb4934"; tab_bar_background = "#11111b";
color10 = "#b8bb26";
color11 = "#fabd2f"; color0 = "#45475a";
color12 = "#83a598"; color1 = "#f38ba8";
color13 = "#d3869b"; color2 = "#a6e3a1";
color14 = "#8ec07c"; color3 = "#f9e2af";
color15 = "#ebdbb2"; 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 = { keybindings = {

View file

@ -45,7 +45,7 @@ in
default = [ default = [
"bitwarden" "bitwarden"
"brave-browser" "brave-browser"
"font-jetbrains-mono" "font-jetbrains-mono-nerd-font"
"pgadmin4" "pgadmin4"
"spotify" "spotify"
"vesktop" "vesktop"

View file

@ -54,8 +54,8 @@
users.users.${user} = { users.users.${user} = {
home = lib.mkDefault "/Users/${user}"; home = lib.mkDefault "/Users/${user}";
# Use the Home Manager nushell which has proper config setup # Use the macOS system Zsh as the login shell. Nushell remains available as `nu`.
shell = "/etc/profiles/per-user/${user}/bin/nu"; shell = "/bin/zsh";
}; };
system = { system = {

View file

@ -49,7 +49,7 @@
rsydn.devTools = { rsydn.devTools = {
enable = lib.mkDefault true; 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 # due to nixpkgs-unstable syrupy test failures breaking the font's Python build deps
packages = with pkgs; [ packages = with pkgs; [
age age

View file

@ -21,6 +21,7 @@ in
programs.direnv = { programs.direnv = {
enable = true; enable = true;
enableNushellIntegration = true; enableNushellIntegration = true;
enableZshIntegration = true;
nix-direnv.enable = true; nix-direnv.enable = true;
silent = cfg.silent; silent = cfg.silent;
}; };

View file

@ -31,7 +31,7 @@ require("lazy").setup({
version = false, version = false,
}, },
install = { colorscheme = { "gruvbox", "habamax" } }, install = { colorscheme = { "catppuccin-mocha", "habamax" } },
checker = { enabled = false }, checker = { enabled = false },
change_detection = { notify = false }, change_detection = { notify = false },

View file

@ -2,13 +2,8 @@ return {
-- gruvbox -- gruvbox
{ {
"ellisonleao/gruvbox.nvim", "ellisonleao/gruvbox.nvim",
lazy = false, lazy = true,
priority = 1000,
opts = {}, opts = {},
config = function()
require("gruvbox").setup({})
vim.cmd.colorscheme("gruvbox")
end,
}, },
-- tokyonight -- tokyonight
@ -21,10 +16,12 @@ return {
-- catppuccin -- catppuccin
{ {
"catppuccin/nvim", "catppuccin/nvim",
lazy = true, lazy = false,
priority = 1000, priority = 1000,
name = "catppuccin", name = "catppuccin",
opts = { opts = {
flavour = "mocha",
term_colors = true,
lsp_styles = { lsp_styles = {
underlines = { underlines = {
errors = { "undercurl" }, errors = { "undercurl" },
@ -60,6 +57,10 @@ return {
-- which_key = true, -- which_key = true,
-- }, -- },
}, },
config = function(_, opts)
require("catppuccin").setup(opts)
vim.cmd.colorscheme("catppuccin-mocha")
end,
-- specs = { -- specs = {
-- { -- {
-- "akinsho/bufferline.nvim", -- "akinsho/bufferline.nvim",

View file

@ -181,22 +181,24 @@ in
$env.NIX_PROFILES = "/run/current-system/sw ${config.home.profileDirectory}" $env.NIX_PROFILES = "/run/current-system/sw ${config.home.profileDirectory}"
# Load globally scoped sops-managed environment variables. # Load each globally scoped sops-managed environment variable.
let global_secrets_file = "${config.xdg.configHome}/secrets/global-env.yaml" let global_secrets_dir = "${config.xdg.configHome}/secrets/global-env"
def --env load-global-secrets [secret_file: string] { def --env load-global-secrets [secret_dir: string] {
if ($secret_file | path exists) { if ($secret_dir | path exists) {
let secret_values = (open $secret_file) let secret_values = (
ls $secret_dir
$secret_values
| transpose name value
| reduce -f {} {|entry, acc| | reduce -f {} {|entry, acc|
$acc | upsert $entry.name ($entry.value | into string) let name = ($entry.name | path basename)
let value = (open --raw $entry.name | str trim)
$acc | upsert $name $value
} }
| load-env )
$secret_values | load-env
} }
} }
load-global-secrets $global_secrets_file load-global-secrets $global_secrets_dir
''; '';
} }

View file

@ -4,6 +4,7 @@
enable = lib.mkDefault true; enable = lib.mkDefault true;
enableFishIntegration = true; enableFishIntegration = true;
enableNushellIntegration = true; enableNushellIntegration = true;
enableZshIntegration = true;
settings = lib.importTOML ./starship.toml; settings = lib.importTOML ./starship.toml;
}; };
} }

View file

@ -1,15 +1,15 @@
"$schema" = "https://starship.rs/config-schema.json" "$schema" = "https://starship.rs/config-schema.json"
format = """ format = """
[](color_orange)\ [](peach)\
$os\ $os\
$username\ $username\
[](bg:color_yellow fg:color_orange)\ [](bg:yellow fg:peach)\
$directory\ $directory\
[](fg:color_yellow bg:color_aqua)\ [](fg:yellow bg:teal)\
$git_branch\ $git_branch\
$git_status\ $git_status\
[](fg:color_aqua bg:color_blue)\ [](fg:teal bg:blue)\
$c\ $c\
$cpp\ $cpp\
$rust\ $rust\
@ -20,32 +20,49 @@ $java\
$kotlin\ $kotlin\
$haskell\ $haskell\
$python\ $python\
[](fg:color_blue bg:color_bg3)\ [](fg:blue bg:surface0)\
$docker_context\ $docker_context\
$conda\ $conda\
$pixi\ $pixi\
[](fg:color_bg3 bg:color_bg1)\ [](fg:surface0 bg:base)\
$time\ $time\
[ ](fg:color_bg1)\ [ ](fg:base)\
$line_break$character""" $line_break$character"""
palette = "gruvbox_dark" palette = "catppuccin_mocha"
[palettes.gruvbox_dark] # Official Catppuccin Mocha palette (catppuccin/starship themes/mocha.toml)
color_fg0 = "#fbf1c7" [palettes.catppuccin_mocha]
color_bg1 = "#3c3836" rosewater = "#f5e0dc"
color_bg3 = "#665c54" flamingo = "#f2cdcd"
color_blue = "#458588" pink = "#f5c2e7"
color_aqua = "#689d6a" mauve = "#cba6f7"
color_green = "#98971a" red = "#f38ba8"
color_orange = "#d65d0e" maroon = "#eba0ac"
color_purple = "#b16286" peach = "#fab387"
color_red = "#cc241d" yellow = "#f9e2af"
color_yellow = "#d79921" 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] [os]
disabled = false disabled = false
style = "bg:color_orange fg:color_fg0" style = "bg:peach fg:crust"
[os.symbols] [os.symbols]
Windows = "󰍲" Windows = "󰍲"
@ -72,12 +89,12 @@ Pop = ""
[username] [username]
show_always = true show_always = true
style_user = "bg:color_orange fg:color_fg0" style_user = "bg:peach fg:crust"
style_root = "bg:color_orange fg:color_fg0" style_root = "bg:peach fg:crust"
format = "[ $user ]($style)" format = "[ $user ]($style)"
[directory] [directory]
style = "fg:color_fg0 bg:color_yellow" style = "fg:crust bg:yellow"
format = "[ $path ]($style)" format = "[ $path ]($style)"
truncation_length = 3 truncation_length = 3
truncation_symbol = "…/" truncation_symbol = "…/"
@ -91,90 +108,90 @@ Developer = "󰲋 "
[git_branch] [git_branch]
symbol = "" symbol = ""
style = "bg:color_aqua" style = "bg:teal"
format = "[[ $symbol $branch ](fg:color_fg0 bg:color_aqua)]($style)" format = "[[ $symbol $branch ](fg:crust bg:teal)]($style)"
[git_status] [git_status]
style = "bg:color_aqua" style = "bg:teal"
format = "[[($all_status$ahead_behind )](fg:color_fg0 bg:color_aqua)]($style)" format = "[[($all_status$ahead_behind )](fg:crust bg:teal)]($style)"
[nodejs] [nodejs]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[c] [c]
symbol = " " symbol = " "
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[cpp] [cpp]
symbol = " " symbol = " "
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[rust] [rust]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[golang] [golang]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[php] [php]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[java] [java]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[kotlin] [kotlin]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[haskell] [haskell]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[python] [python]
symbol = "" symbol = ""
style = "bg:color_blue" style = "bg:blue"
format = "[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)" format = "[[ $symbol( $version) ](fg:crust bg:blue)]($style)"
[docker_context] [docker_context]
symbol = "" symbol = ""
style = "bg:color_bg3" style = "bg:surface0"
format = "[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)" format = "[[ $symbol( $context) ](fg:teal bg:surface0)]($style)"
[conda] [conda]
style = "bg:color_bg3" style = "bg:surface0"
format = "[[ $symbol( $environment) ](fg:#83a598 bg:color_bg3)]($style)" format = "[[ $symbol( $environment) ](fg:teal bg:surface0)]($style)"
[pixi] [pixi]
style = "bg:color_bg3" style = "bg:surface0"
format = "[[ $symbol( $version)( $environment) ](fg:color_fg0 bg:color_bg3)]($style)" format = "[[ $symbol( $version)( $environment) ](fg:text bg:surface0)]($style)"
[time] [time]
disabled = false disabled = false
time_format = "%R" time_format = "%R"
style = "bg:color_bg1" style = "bg:base"
format = "[[  $time ](fg:color_fg0 bg:color_bg1)]($style)" format = "[[  $time ](fg:text bg:base)]($style)"
[line_break] [line_break]
disabled = false disabled = false
[character] [character]
disabled = false disabled = false
success_symbol = "[](bold fg:color_green)" success_symbol = "[](bold fg:green)"
error_symbol = "[](bold fg:color_red)" error_symbol = "[](bold fg:red)"
vimcmd_symbol = "[](bold fg:color_green)" vimcmd_symbol = "[](bold fg:green)"
vimcmd_replace_one_symbol = "[](bold fg:color_purple)" vimcmd_replace_one_symbol = "[](bold fg:mauve)"
vimcmd_replace_symbol = "[](bold fg:color_purple)" vimcmd_replace_symbol = "[](bold fg:mauve)"
vimcmd_visual_symbol = "[](bold fg:color_yellow)" vimcmd_visual_symbol = "[](bold fg:yellow)"

View file

@ -14,7 +14,15 @@
prefix = "C-a"; prefix = "C-a";
escapeTime = 0; escapeTime = 0;
aggressiveResize = true; 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 = '' extraConfig = ''
# Terminal configuration for proper colors and features # Terminal configuration for proper colors and features
set-option -g default-terminal "screen-256color" set-option -g default-terminal "screen-256color"
@ -50,9 +58,10 @@
bind J resize-pane -D 5 bind J resize-pane -D 5
bind K resize-pane -U 5 bind K resize-pane -U 5
bind L resize-pane -R 5 bind L resize-pane -R 5
# Ensure tmux panes spawn login nu so Starship integration runs # Start Zsh by default and keep Nushell available on prefix + C-n.
set -g default-shell "${config.home.profileDirectory}/bin/nu" set -g default-shell "/bin/zsh"
set -g default-command "${config.home.profileDirectory}/bin/nu --login" set -g default-command "/bin/zsh --login"
bind-key C-n new-window -n nushell "${config.home.profileDirectory}/bin/nu --login"
''; '';
}; };
} }

136
modules/home/shell/zsh.nix Normal file
View file

@ -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"
}
'';
};
}

View file

@ -1,41 +1,46 @@
# Secrets with sops-nix # 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 ## 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 1. Enter `nix develop` so `age` and `sops` are available.
- Global shell env vars live in one encrypted YAML file: `secrets/global-env.sops.yaml`. 2. Generate or import an Age key. Home Manager can generate one during activation. You can also run:
- 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 Darwin configuration: ```bash
age-keygen -o ~/.config/sops/age/keys.txt
```
```nix 3. Get the public key:
rsydn.secrets = {
enable = true; ```bash
defaultSopsFile = ../../../secrets/global-env.sops.yaml; age-keygen -y -f ~/.config/sops/age/keys.txt
secrets."global-env" = { ```
format = "yaml";
key = ""; 4. Add the public key as a recipient in each encrypted SOPS file.
path = "${config.xdg.configHome}/secrets/global-env.yaml"; 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 ```bash
SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt \ SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt \
sops secrets/global-env.sops.yaml sops secrets/global-env.sops.yaml
``` ```
Add entries like: Use a flat YAML structure:
```yaml ```yaml
OPENAI_API_KEY: sk-... OPENAI_API_KEY: sk-...
@ -43,21 +48,42 @@ ANTHROPIC_API_KEY: sk-ant-...
GITHUB_TOKEN: ghp_... 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 ```nu
$env.OPENAI_API_KEY $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 ## 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 If you replace an Age key, update the recipients:
- **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. ```bash
- **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. sops updatekeys secrets/global-env.sops.yaml
- **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. ```
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/`.