From c1d9f8278c3a9d0a954c9b3c096844bba409fc2e Mon Sep 17 00:00:00 2001 From: Chuck D'Antonio Date: Mon, 20 Oct 2025 09:44:46 -0400 Subject: [PATCH 1/5] Researches the problem --- ...25-10-18-darwin-desktop-app-environment.md | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 docs/research/2025-10-18-darwin-desktop-app-environment.md diff --git a/docs/research/2025-10-18-darwin-desktop-app-environment.md b/docs/research/2025-10-18-darwin-desktop-app-environment.md new file mode 100644 index 0000000..ed1e239 --- /dev/null +++ b/docs/research/2025-10-18-darwin-desktop-app-environment.md @@ -0,0 +1,327 @@ +--- +date: 2025-10-18T15:43:27+0000 +researcher: Claude Code +git_commit: 5c08c54ff6293d144cdf36128a18914dd8286cb1 +branch: feature/crdant/provides-env-for-apps +repository: dotfiles +topic: "Making Terminal Environment Variables Available to Desktop Apps on Darwin" +tags: [research, codebase, darwin, macos, environment-variables, gui-apps, nix-darwin, home-manager] +status: complete +last_updated: 2025-10-18 +last_updated_by: Claude Code +--- + +# Research: Making Terminal Environment Variables Available to Desktop Apps on Darwin + +**Date**: 2025-10-18T15:43:27+0000 +**Researcher**: Claude Code +**Git Commit**: 5c08c54ff6293d144cdf36128a18914dd8286cb1 +**Branch**: feature/crdant/provides-env-for-apps +**Repository**: dotfiles + +## Research Question + +How can environment variables that are currently available in terminal environments be made available to Desktop/GUI applications on Darwin (macOS)? + +## Summary + +This codebase uses **nix-darwin** and **Home Manager** for declarative system configuration. Currently, environment variables are primarily configured for shell sessions via: +- `home.sessionVariables` (user-level, cross-shell) +- `home.sessionPath` (PATH modifications) +- `programs.zsh.envExtra` (shell-specific environment setup) + +**Key Finding**: The codebase does **not** currently have a mechanism to propagate terminal environment variables to GUI/Desktop applications on macOS. The traditional macOS approach would use `launchd` or `~/.MacOSX/environment.plist`, but this codebase uses nix-darwin's declarative system management instead. + +**Solution Path**: To make environment variables available to GUI apps, you should use **nix-darwin's `launchd.user.agents`** or configure system-level environment variables that are inherited by GUI applications at login. + +## Detailed Findings + +### Current Environment Variable Configuration + +#### User-Level Session Variables +**File**: `home/modules/base/default.nix:14-19` + +The primary mechanism for setting user environment variables: + +```nix +home = { + sessionVariables = { + EDITOR = "nvim"; + VISUAL = "nvim"; + NIXPKGS_ALLOW_UNFREE = 1; + NIXPKGS_ALLOW_BROKEN = 1; + }; +}; +``` + +**Scope**: These variables are available in shell sessions (zsh, bash, fish) but **NOT** automatically available to GUI applications. + +#### Shell-Specific Environment Variables +**File**: `home/modules/base/default.nix:278-288` + +More complex environment setup using ZSH: + +```nix +programs = { + zsh = { + envExtra = '' + export XDG_CONFIG_HOME="${config.xdg.configHome}" + + if [[ -d $HOME/.rd ]] ; then + export PATH=$PATH:"$HOME/.rd/bin" + fi + + unset RPS1 + ''; + }; +}; +``` + +**Scope**: Only available in ZSH sessions, not to GUI applications. + +#### Module-Specific Variables +Multiple modules define their own environment variables: +- `home/modules/infrastructure/default.nix:37-43` - GOVC_URL, GOVC_USERNAME, etc. +- `home/modules/ai/default.nix:67-74` - CLAUDE_CONFIG_DIR +- `home/modules/certificates/default.nix:40-42` - CERTBOT_ROOT +- `home/modules/kubernetes/default.nix` - K8s-related variables + +**Scope**: Shell sessions only. + +### System-Level Configuration + +#### System Environment Variables +**File**: `systems/modules/base/terminfo.nix:67-69` + +```nix +environment.variables = { + TERMINFO_DIRS = "${pkgs.ncurses}/share/terminfo"; +}; +``` + +**Note**: `environment.variables` in nix-darwin sets system-wide environment variables, but the current usage is minimal and focused on terminal-specific settings. + +#### Desktop Module +**File**: `systems/modules/desktop/default.nix:55-70` + +The desktop module manages GUI applications via Homebrew: + +```nix +environment = { + systemPackages = with pkgs; [ + espanso + slack + zoom-us + # ... more GUI apps + ]; +}; +``` + +**Finding**: No environment variable configuration specific to GUI applications. + +### No Traditional macOS Environment Mechanisms Found + +**Traditional approaches NOT in use:** +1. **`~/.MacOSX/environment.plist`** - Not found or configured +2. **launchd agents for environment** - Not configured +3. **`/etc/launchd.conf`** - Deprecated on modern macOS, not used +4. **Login hooks** - Not configured + +**Why**: This codebase uses nix-darwin's declarative approach, which avoids imperative configuration files. + +## Architecture Insights + +### Current Architecture +``` +┌─────────────────────────────────────┐ +│ Shell Sessions (zsh/bash/fish) │ +│ │ +│ ✓ home.sessionVariables │ +│ ✓ home.sessionPath │ +│ ✓ programs.zsh.envExtra │ +│ ✓ Module-specific env vars │ +└─────────────────────────────────────┘ + ↑ + │ Available in shells + │ +┌─────────────────────────────────────┐ +│ Terminal Applications │ +│ (nvim, git, kubectl, etc.) │ +└─────────────────────────────────────┘ + + ✗ NOT available ✗ + +┌─────────────────────────────────────┐ +│ GUI/Desktop Applications │ +│ (Slack, VS Code, Browsers, etc.) │ +└─────────────────────────────────────┘ +``` + +### Nix-Darwin Configuration Flow +``` +flake.nix (Entry point) + ↓ +darwinConfigurations.{host} + ↓ +systems/hosts/{host}/default.nix + ↓ +systems/modules/{various}/default.nix + ↓ +nix-darwin system configuration + (system.defaults, environment, etc.) +``` + +### Home Manager Configuration Flow +``` +flake.nix (Entry point) + ↓ +homeConfigurations.{user}@{host} + ↓ +home/users/{user}/{profile}.nix + ↓ +home/modules/{various}/default.nix + ↓ +home-manager user configuration + (home.sessionVariables, programs.*, etc.) +``` + +## Recommended Solutions + +### Option 1: System-Wide Environment Variables (Simplest) + +For variables that should be available everywhere (shells + GUI apps), use nix-darwin's system-level environment configuration. + +**File to modify**: Create or modify a system module (e.g., `systems/modules/environment/default.nix`) + +```nix +{ config, pkgs, lib, ... }: + +{ + # System-wide environment variables for GUI and shell apps + environment.variables = { + # Example: Make these available to all applications + MY_VAR = "value"; + ANOTHER_VAR = "value2"; + }; +} +``` + +**Scope**: Available to both shell sessions and GUI applications after logout/login. + +### Option 2: launchd User Agents (More Control) + +For more complex scenarios, use nix-darwin's `launchd.user.agents` to set environment variables at user login. + +**File to create**: `systems/modules/environment/gui-environment.nix` + +```nix +{ config, pkgs, lib, ... }: + +{ + launchd.user.agents.environment = { + serviceConfig = { + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + launchctl setenv MY_VAR "value" + launchctl setenv PATH "$PATH:/custom/path" + '' + ]; + RunAtLoad = true; + }; + }; +} +``` + +**Scope**: Runs at user login, sets environment for all user processes including GUI apps. + +### Option 3: Per-Application Environment (Most Specific) + +For application-specific environment, use home-manager's `home.file` to create application-specific config files. + +**Example for VS Code** (`home/modules/development/vscode.nix`): + +```nix +{ config, pkgs, lib, ... }: + +{ + programs.vscode = { + enable = true; + userSettings = { + "terminal.integrated.env.osx" = { + MY_VAR = "value"; + }; + }; + }; +} +``` + +### Option 4: Hybrid Approach (Recommended) + +1. **Core variables** → `environment.variables` (system-level) +2. **Development tools** → Keep in shell configs for flexibility +3. **App-specific** → Use app-specific configuration + +## Code References + +### Key Configuration Files +- `home/modules/base/default.nix:14-19` - Current sessionVariables +- `home/modules/base/default.nix:21-30` - Current sessionPath +- `home/modules/base/default.nix:278-288` - ZSH envExtra +- `systems/modules/base/terminfo.nix:67-69` - System environment.variables example +- `systems/modules/desktop/default.nix:55-70` - Desktop applications +- `flake.nix:61-67` - darwinConfigurations + +### Example Modules with Environment Variables +- `home/modules/infrastructure/default.nix:37-43` - Infrastructure tools +- `home/modules/ai/default.nix:67-74` - AI tools +- `home/modules/certificates/default.nix:40-42` - Certificate management +- `home/modules/kubernetes/default.nix:30-32` - Kubernetes tools + +## Implementation Checklist + +To make terminal environment variables available to GUI apps: + +1. **Identify variables** - Determine which environment variables need to be available to GUI apps +2. **Choose approach** - Select Option 1, 2, 3, or 4 based on requirements +3. **Create module** - Create `systems/modules/environment/default.nix` or similar +4. **Configure variables** - Use `environment.variables` or `launchd.user.agents` +5. **Import module** - Add module import to system configuration +6. **Test** - Run `darwin-rebuild switch` and logout/login +7. **Verify** - Launch a GUI app and check if variables are available + +## Open Questions + +1. **Which specific environment variables need to be available to GUI apps?** + - PATH modifications? + - API keys or configuration paths? + - Tool-specific variables? + +2. **Should all terminal variables be available to GUI apps, or only a subset?** + - Security consideration: Some variables might be sensitive + - Performance consideration: Some variables might be shell-specific + +3. **What's the precedence when combining system and user environment variables?** + - nix-darwin's `environment.variables` vs home-manager's `home.sessionVariables` + +4. **Are there any GUI applications that currently lack necessary environment variables?** + - This would help prioritize which variables to expose + +5. **Should this be per-profile or global?** + - Consider if different profiles (minimal, development, ai, etc.) need different GUI environments + +## Related Resources + +- [nix-darwin documentation](https://github.com/LnL7/nix-darwin) +- [Home Manager manual](https://nix-community.github.io/home-manager/) +- [macOS launchd documentation](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html) +- [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) + +## Next Steps + +1. **Discuss requirements** - Determine which variables need GUI access +2. **Design module structure** - Create a clean, modular solution +3. **Implement incrementally** - Start with critical variables +4. **Test thoroughly** - Verify GUI apps receive correct environment +5. **Document patterns** - Add examples to module documentation From 9662bbb0d73c4c474ba8468981e9cb59040249cb Mon Sep 17 00:00:00 2001 From: Chuck D'Antonio Date: Mon, 20 Oct 2025 12:20:37 -0400 Subject: [PATCH 2/5] Add implementation plan for GUI environment variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates a detailed plan to make terminal environment variables available to GUI/Desktop applications on macOS using Home Manager launchd agents. The plan implements per-module launchd agents for: - base module: PATH, EDITOR, VISUAL, XDG_CONFIG_HOME - ai module: CLAUDE_CONFIG_DIR - kubernetes module: krew PATH addition Each agent uses launchctl setenv to propagate variables to the GUI session at user login. This enables GUI apps like Claude Desktop, VS Code, and Kubernetes tools to access environment that was previously shell-only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../2025-10-20-gui-environment-variables.md | 511 ++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100644 docs/plans/2025-10-20-gui-environment-variables.md diff --git a/docs/plans/2025-10-20-gui-environment-variables.md b/docs/plans/2025-10-20-gui-environment-variables.md new file mode 100644 index 0000000..561f906 --- /dev/null +++ b/docs/plans/2025-10-20-gui-environment-variables.md @@ -0,0 +1,511 @@ +# GUI Environment Variables via launchd Agents Implementation Plan + +## Overview + +Enable GUI/Desktop applications on macOS to access environment variables that are currently only available in terminal sessions. This will be implemented using Home Manager's `launchd.agents` configuration with per-module launchd agents that use `launchctl setenv` to propagate variables to the GUI session. + +## Current State Analysis + +### What Exists Now + +**Environment Variables (Shell Only):** +- `home.sessionVariables` in `home/modules/base/default.nix:14-19` defines EDITOR, VISUAL, NIXPKGS_* variables +- `home.sessionPath` in `home/modules/base/default.nix:21-30` adds custom paths (~/workspace/go/bin, Homebrew, etc.) +- `programs.zsh.envExtra` in `home/modules/base/default.nix:278-288` sets XDG_CONFIG_HOME +- Module-specific variables in ai, kubernetes, certificates, infrastructure modules + +**Current Architecture:** +``` +Terminal Sessions (zsh/bash/fish) + ↓ + ✓ Has all environment variables + ↓ +Terminal Applications (nvim, kubectl, etc.) + ↓ + ✓ Inherits from shell + +GUI Applications (Claude Desktop, VS Code, etc.) + ↓ + ✗ NO environment variables from shell config +``` + +### What's Missing + +GUI applications launched from Finder, Spotlight, or Dock do NOT have access to: +- Custom PATH entries (~/workspace/go/bin, ~/.krew/bin, ~/.local/bin) +- EDITOR/VISUAL variables (needed by Git GUI clients) +- XDG_CONFIG_HOME (needed by XDG-compliant apps) +- CLAUDE_CONFIG_DIR (needed by Claude Desktop app) + +### Key Discoveries + +1. **Home Manager supports launchd agents**: `home/modules/certificates/default.nix:46-78` shows existing pattern for creating launchd agents +2. **Darwin-specific conditionals**: Modules use `isDarwin` to conditionally enable macOS-specific features +3. **No existing GUI environment setup**: No modules currently use launchd for environment variable propagation + +## Desired End State + +### Target Architecture + +``` +User Login + ↓ +launchd user agents (RunAtLoad=true) + ↓ + launchctl setenv VAR "value" + ↓ +GUI Session Environment + ↓ +GUI Applications + ✓ PATH includes ~/.local/bin, ~/workspace/go/bin, ~/.krew/bin, Homebrew paths + ✓ EDITOR=nvim, VISUAL=nvim + ✓ XDG_CONFIG_HOME set correctly + ✓ CLAUDE_CONFIG_DIR set based on username +``` + +### Verification Methods + +After implementation, GUI apps should have access to environment variables. Verify by: + +#### Automated Verification: +- [ ] Home Manager builds successfully: `home-manager switch` +- [ ] Launchd agents are created: `launchctl list | grep io.crdant.env` +- [ ] No syntax errors in Nix configuration: `nix-instantiate --eval` + +#### Manual Verification: +1. **Logout and log back in** (or reboot) +2. **Verify launchd agents loaded:** + ```bash + launchctl list | grep io.crdant.env + # Should show: io.crdant.env.base, io.crdant.env.ai, io.crdant.env.kubernetes + ``` +3. **Check environment in GUI context:** + ```bash + launchctl getenv PATH + launchctl getenv EDITOR + launchctl getenv CLAUDE_CONFIG_DIR + launchctl getenv XDG_CONFIG_HOME + ``` +4. **Test with actual GUI app:** + - Open Claude Desktop → Check it finds config at CLAUDE_CONFIG_DIR + - Open VS Code → Terminal should have correct PATH + - Any app that spawns git → Should use nvim as EDITOR + +## What We're NOT Doing + +1. **Not exposing ALL environment variables** - Only PATH, EDITOR, VISUAL, XDG_CONFIG_HOME, CLAUDE_CONFIG_DIR +2. **Not using system-level configuration** - Using Home Manager (user-level) instead of nix-darwin system-level +3. **Not exposing sensitive variables** - GOVC_PASSWORD, API keys stay shell-only +4. **Not exposing Nix-specific variables** - NIXPKGS_ALLOW_UNFREE stays in shell sessions +5. **Not creating one monolithic agent** - Each module manages its own launchd agent +6. **Not modifying shell configuration** - Existing sessionVariables/sessionPath remain unchanged + +## Implementation Approach + +### Strategy + +Use **per-module launchd agents** where each module that needs GUI environment variables creates its own launchd agent. This approach: +- Keeps environment configuration co-located with the module that needs it +- Allows modules to be independently enabled/disabled +- Follows the existing pattern in the certificates module +- Makes it easy to add more modules in the future + +### Pattern + +Each module will add a `launchd.agents` block (Darwin-only) that: +1. Runs at user login (`RunAtLoad = true`) +2. Uses a bash script with `launchctl setenv` to set variables +3. Has a unique label per module (e.g., `io.crdant.env.base`) +4. Logs to XDG-compliant paths for debugging + +## Phase 1: Add launchd Agent to base Module + +### Overview +Add GUI environment support to the base module for core variables that all GUI apps need. + +### Changes Required + +#### File: `home/modules/base/default.nix` + +**Location:** After the `home.activation` block (around line 87), add a new `launchd` block. + +**Add this new configuration block:** + +```nix +launchd = lib.mkIf isDarwin { + enable = true; + agents = { + "io.crdant.env.base" = { + enable = true; + config = { + Label = "io.crdant.env.base"; + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + # Set PATH for GUI applications + launchctl setenv PATH "${config.home.homeDirectory}/.local/bin:${config.home.homeDirectory}/workspace/go/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin" + + # Set editor variables for Git GUI clients and other apps + launchctl setenv EDITOR "nvim" + launchctl setenv VISUAL "nvim" + + # Set XDG_CONFIG_HOME for XDG-compliant applications + launchctl setenv XDG_CONFIG_HOME "${config.xdg.configHome}" + '' + ]; + RunAtLoad = true; + StandardOutPath = "${config.xdg.stateHome}/launchd/env.base.out"; + StandardErrorPath = "${config.xdg.stateHome}/launchd/env.base.err"; + }; + }; + }; +}; +``` + +**Important Notes:** +1. The PATH construction must include system paths (/usr/bin, etc.) in addition to custom paths +2. Uses `config.xdg.configHome` to reference the XDG config directory +3. Uses `config.home.homeDirectory` to reference the home directory +4. Logs go to `${config.xdg.stateHome}/launchd/` for debugging + +### Success Criteria + +#### Automated Verification: +- [ ] Nix configuration builds: `nix-instantiate --eval --strict home/modules/base/default.nix` +- [ ] Home Manager builds: `home-manager build` +- [ ] No Home Manager activation errors: Check output of `home-manager switch` + +#### Manual Verification: +- [ ] After logout/login, launchd agent is loaded: `launchctl list | grep io.crdant.env.base` +- [ ] PATH is set in GUI context: `launchctl getenv PATH` includes ~/.local/bin, ~/workspace/go/bin, Homebrew paths +- [ ] EDITOR is set: `launchctl getenv EDITOR` returns "nvim" +- [ ] VISUAL is set: `launchctl getenv VISUAL` returns "nvim" +- [ ] XDG_CONFIG_HOME is set: `launchctl getenv XDG_CONFIG_HOME` returns correct path +- [ ] VS Code integrated terminal has correct PATH when opened via Finder/Spotlight +- [ ] Git operations from GUI apps use nvim as editor + +--- + +## Phase 2: Add launchd Agent to ai Module + +### Overview +Add GUI environment support to the ai module so Claude Desktop and other AI GUI tools can find their configuration. + +### Changes Required + +#### File: `home/modules/ai/default.nix` + +**Location:** After the `programs.zsh` block (around line 76), add a new `launchd` block. + +**Add this new configuration block:** + +```nix +launchd = lib.mkIf isDarwin { + enable = true; + agents = { + "io.crdant.env.ai" = { + enable = true; + config = { + Label = "io.crdant.env.ai"; + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + # Set CLAUDE_CONFIG_DIR based on username (matches shell logic) + if [[ "$(whoami)" == "chuck" ]] ; then + launchctl setenv CLAUDE_CONFIG_DIR "${config.xdg.configHome}/claude/replicated" + else + launchctl setenv CLAUDE_CONFIG_DIR "${config.xdg.configHome}/claude/personal" + fi + '' + ]; + RunAtLoad = true; + StandardOutPath = "${config.xdg.stateHome}/launchd/env.ai.out"; + StandardErrorPath = "${config.xdg.stateHome}/launchd/env.ai.err"; + }; + }; + }; +}; +``` + +**Important Notes:** +1. This replicates the same conditional logic from `programs.zsh.envExtra` (lines 67-74) +2. The username check ensures correct config directory for work (chuck) vs personal (crdant) contexts + +### Success Criteria + +#### Automated Verification: +- [ ] Nix configuration builds: `nix-instantiate --eval --strict home/modules/ai/default.nix` +- [ ] Home Manager builds: `home-manager build` +- [ ] No Home Manager activation errors: Check output of `home-manager switch` + +#### Manual Verification: +- [ ] After logout/login, launchd agent is loaded: `launchctl list | grep io.crdant.env.ai` +- [ ] CLAUDE_CONFIG_DIR is set: `launchctl getenv CLAUDE_CONFIG_DIR` returns correct path based on username +- [ ] Claude Desktop app finds configuration correctly +- [ ] Claude Desktop app can access custom commands and agents +- [ ] No errors in log files: `cat ${config.xdg.stateHome}/launchd/env.ai.err` + +--- + +## Phase 3: Add launchd Agent to kubernetes Module + +### Overview +Add GUI environment support to the kubernetes module so GUI Kubernetes tools can find krew plugins. + +### Changes Required + +#### File: `home/modules/kubernetes/default.nix` + +**Location:** After the `home.sessionPath` block (around line 33), add a new `launchd` block. + +**Add this new configuration block:** + +```nix +launchd = lib.mkIf isDarwin { + enable = true; + agents = { + "io.crdant.env.kubernetes" = { + enable = true; + config = { + Label = "io.crdant.env.kubernetes"; + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + # Add krew bin to PATH for GUI Kubernetes tools + CURRENT_PATH=$(launchctl getenv PATH) + if [[ -z "$CURRENT_PATH" ]]; then + # If PATH doesn't exist yet, set a basic one + launchctl setenv PATH "${config.home.homeDirectory}/.krew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + else + # Prepend krew to existing PATH if not already present + if [[ "$CURRENT_PATH" != *"${config.home.homeDirectory}/.krew/bin"* ]]; then + launchctl setenv PATH "${config.home.homeDirectory}/.krew/bin:$CURRENT_PATH" + fi + fi + '' + ]; + RunAtLoad = true; + StandardOutPath = "${config.xdg.stateHome}/launchd/env.kubernetes.out"; + StandardErrorPath = "${config.xdg.stateHome}/launchd/env.kubernetes.err"; + }; + }; + }; +}; +``` + +**Important Notes:** +1. This agent checks if PATH already exists before modifying it +2. If base agent runs first, this will prepend to existing PATH +3. If this runs first, it sets a minimal PATH with krew +4. Avoids duplicate PATH entries with conditional check + +### Success Criteria + +#### Automated Verification: +- [ ] Nix configuration builds: `nix-instantiate --eval --strict home/modules/kubernetes/default.nix` +- [ ] Home Manager builds: `home-manager build` +- [ ] No Home Manager activation errors: Check output of `home-manager switch` + +#### Manual Verification: +- [ ] After logout/login, launchd agent is loaded: `launchctl list | grep io.crdant.env.kubernetes` +- [ ] PATH includes krew: `launchctl getenv PATH` includes ~/.krew/bin +- [ ] No duplicate PATH entries: `launchctl getenv PATH | tr ':' '\n' | sort | uniq -d` returns nothing +- [ ] GUI Kubernetes tools (like Lens or Rancher Desktop UI) can find krew plugins +- [ ] No errors in log files: `cat ${config.xdg.stateHome}/launchd/env.kubernetes.err` + +--- + +## Phase 4: Create Log Directory + +### Overview +Ensure the launchd log directory exists before agents try to write to it. + +### Changes Required + +#### File: `home/modules/base/default.nix` + +**Location:** In the `home.activation` block (around line 71-86), add a new entry. + +**Add to the existing activation block:** + +```nix +launchdLogDirectory = lib.hm.dag.entryBefore [ "writeBoundary" ] '' + mkdir -p ${config.xdg.stateHome}/launchd +''; +``` + +**Full context (showing where to add it):** + +```nix +activation = { + workspaceDirectory = lib.hm.dag.entryBefore [ "writeBoundary" ] '' + mkdir -p ~/workspace + mkdir -p ~/sandbox + ''; + + launchdLogDirectory = lib.hm.dag.entryBefore [ "writeBoundary" ] '' + mkdir -p ${config.xdg.stateHome}/launchd + ''; + + customizeOmz = lib.hm.dag.entryAfter [ "writeBoundary" "installPackages" "git" ] '' + # ... existing customizeOmz code ... + ''; +}; +``` + +### Success Criteria + +#### Automated Verification: +- [ ] Directory is created during activation: `test -d ~/.local/state/launchd && echo "exists" || echo "missing"` +- [ ] Home Manager activation succeeds: `home-manager switch` + +#### Manual Verification: +- [ ] Directory exists after home-manager switch +- [ ] Log files are created when agents run: `ls ~/.local/state/launchd/` +- [ ] Agents can write logs: Check that .out and .err files are being written to + +--- + +## Testing Strategy + +### Unit Tests +Not applicable - this is declarative configuration, not code. + +### Integration Tests +Not applicable - Home Manager doesn't have a test framework for launchd agents. + +### Manual Testing Steps + +#### Step 1: Build and Activate Configuration +```bash +# Build the configuration first to catch syntax errors +home-manager build + +# If build succeeds, activate it +home-manager switch +``` + +#### Step 2: Logout and Login +Launchd agents with `RunAtLoad = true` only execute at login, so you must: +```bash +# Option A: Logout from GUI and log back in +# Option B: Reboot the system +``` + +#### Step 3: Verify Agents Are Loaded +```bash +# Check all three agents are loaded +launchctl list | grep io.crdant.env + +# Expected output: +# - 0 io.crdant.env.ai +# - 0 io.crdant.env.base +# - 0 io.crdant.env.kubernetes +``` + +#### Step 4: Verify Environment Variables +```bash +# Check each variable is set in GUI context +launchctl getenv PATH +launchctl getenv EDITOR +launchctl getenv VISUAL +launchctl getenv XDG_CONFIG_HOME +launchctl getenv CLAUDE_CONFIG_DIR + +# PATH should include: ~/.local/bin, ~/workspace/go/bin, ~/.krew/bin, Homebrew paths +# EDITOR should be: nvim +# VISUAL should be: nvim +# XDG_CONFIG_HOME should be: /Users/chuck/.config +# CLAUDE_CONFIG_DIR should be: /Users/chuck/.config/claude/replicated (for chuck) +``` + +#### Step 5: Test with Real GUI Applications + +**Test 1: VS Code** +1. Open VS Code from Finder or Spotlight (NOT from terminal) +2. Open integrated terminal +3. Run: `echo $PATH` +4. Verify it includes ~/.local/bin, ~/workspace/go/bin, ~/.krew/bin + +**Test 2: Claude Desktop** +1. Launch Claude Desktop app +2. Check that it finds custom commands and agents +3. Verify it's using the correct config directory + +**Test 3: Git GUI Operations** +1. Open any Git GUI client (GitHub Desktop, Fork, etc.) +2. Try to edit a commit message or create a new commit +3. Verify it opens nvim (not vim or nano) + +**Test 4: Any App Spawning Editor** +1. Open any GUI app that might spawn an editor +2. Trigger editor launch (e.g., git commit in a GUI) +3. Verify EDITOR=nvim is respected + +#### Step 6: Check for Errors +```bash +# Check log files for any errors +cat ~/.local/state/launchd/env.base.err +cat ~/.local/state/launchd/env.ai.err +cat ~/.local/state/launchd/env.kubernetes.err + +# All should be empty or contain only harmless warnings +``` + +#### Step 7: Verify Shell Sessions Still Work +```bash +# Open a new terminal +# Verify shell environment is unchanged +echo $EDITOR # Should be nvim +echo $PATH # Should include all the custom paths +echo $XDG_CONFIG_HOME # Should be set +``` + +## Performance Considerations + +### Agent Execution Time +- Each agent executes a simple bash script with 1-4 `launchctl setenv` commands +- Expected execution time: < 100ms per agent +- Total overhead at login: < 300ms for all three agents +- This is negligible compared to typical macOS login time + +### Memory Usage +- Launchd agents that have finished executing (`RunAtLoad=true` with no `KeepAlive`) do not consume memory +- Only the environment variables themselves consume memory (negligible: ~1KB total) + +### PATH Order Concerns +- Multiple agents modifying PATH could create ordering issues +- Mitigation: kubernetes agent checks for existing PATH and appends (not replaces) +- If ordering becomes an issue, consider consolidating all PATH manipulation into base agent + +## Migration Notes + +### Backwards Compatibility +- Shell sessions are **completely unaffected** - all existing sessionVariables, sessionPath, and envExtra remain +- This is purely additive functionality +- Users who don't logout/login won't see any changes (graceful degradation) + +### Rollback Plan +If issues occur: +1. Remove the `launchd` blocks from the three modules +2. Run `home-manager switch` +3. Logout/login to unload the agents +4. Environment returns to previous state (shell-only variables) + +### Host Considerations +- These changes will apply to all Darwin hosts (aguardiente, grappa, sochu) +- All three hosts include the base, ai, and kubernetes modules +- No per-host customization needed + +## References + +- Original research: `docs/research/2025-10-18-darwin-desktop-app-environment.md` +- Existing launchd pattern: `home/modules/certificates/default.nix:46-78` +- Base module: `home/modules/base/default.nix:14-19` (sessionVariables) +- AI module: `home/modules/ai/default.nix:67-74` (CLAUDE_CONFIG_DIR logic) +- Kubernetes module: `home/modules/kubernetes/default.nix:30-32` (sessionPath) +- Home Manager launchd docs: https://nix-community.github.io/home-manager/options.xhtml#opt-launchd.agents +- macOS launchd docs: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html From 67d4b870e4441411605f4ea5c120c97e7e1683d9 Mon Sep 17 00:00:00 2001 From: Chuck D'Antonio Date: Mon, 20 Oct 2025 12:24:47 -0400 Subject: [PATCH 3/5] Adds launchd agents to expose environment variables to GUI applications on macOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GUI applications on macOS don't inherit shell environment variables, causing issues with tools like Claude Desktop, VS Code, and Kubernetes clients that need access to PATH, config directories, and other environment settings. This implements per-module launchd agents that run at login to set environment variables in the macOS launch services environment: - Base module: Sets PATH, EDITOR, VISUAL, and XDG_CONFIG_HOME for all GUI apps - AI module: Sets CLAUDE_CONFIG_DIR based on username (personal vs replicated) - Kubernetes module: Adds krew bin directory to PATH for kubectl plugins Also adds activation script to create log directory for launchd agent output. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- home/modules/ai/default.nix | 27 +++++++++++++++++++++++ home/modules/base/default.nix | 34 +++++++++++++++++++++++++++++ home/modules/kubernetes/default.nix | 32 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/home/modules/ai/default.nix b/home/modules/ai/default.nix index 0ac8524..d014d09 100644 --- a/home/modules/ai/default.nix +++ b/home/modules/ai/default.nix @@ -76,6 +76,33 @@ in { }; + launchd = lib.mkIf isDarwin { + enable = true; + agents = { + "io.crdant.env.ai" = { + enable = true; + config = { + Label = "io.crdant.env.ai"; + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + # Set CLAUDE_CONFIG_DIR based on username (matches shell logic) + if [[ "$(whoami)" == "chuck" ]] ; then + launchctl setenv CLAUDE_CONFIG_DIR "${config.xdg.configHome}/claude/replicated" + else + launchctl setenv CLAUDE_CONFIG_DIR "${config.xdg.configHome}/claude/personal" + fi + '' + ]; + RunAtLoad = true; + StandardOutPath = "${config.xdg.stateHome}/launchd/env.ai.out"; + StandardErrorPath = "${config.xdg.stateHome}/launchd/env.ai.err"; + }; + }; + }; + }; + # uncomment when Claude code can handle symlinks # xdg = { # enable = true; diff --git a/home/modules/base/default.nix b/home/modules/base/default.nix index 4562467..827f056 100644 --- a/home/modules/base/default.nix +++ b/home/modules/base/default.nix @@ -74,6 +74,10 @@ in { mkdir -p ~/sandbox ''; + launchdLogDirectory = lib.hm.dag.entryBefore [ "writeBoundary" ] '' + mkdir -p ${config.xdg.stateHome}/launchd + ''; + customizeOmz = lib.hm.dag.entryAfter [ "writeBoundary" "installPackages" "git" ] '' if [ ! -d ~/workspace/oh-my-zsh-custom ]; then ${pkgs.git}/bin/git clone https://github.com/crdant/oh-my-zsh-custom ~/workspace/oh-my-zsh-custom || { @@ -355,6 +359,36 @@ in { }; }; + launchd = lib.mkIf isDarwin { + enable = true; + agents = { + "io.crdant.env.base" = { + enable = true; + config = { + Label = "io.crdant.env.base"; + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + # Set PATH for GUI applications + launchctl setenv PATH "${config.home.homeDirectory}/.local/bin:${config.home.homeDirectory}/workspace/go/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin" + + # Set editor variables for Git GUI clients and other apps + launchctl setenv EDITOR "nvim" + launchctl setenv VISUAL "nvim" + + # Set XDG_CONFIG_HOME for XDG-compliant applications + launchctl setenv XDG_CONFIG_HOME "${config.xdg.configHome}" + '' + ]; + RunAtLoad = true; + StandardOutPath = "${config.xdg.stateHome}/launchd/env.base.out"; + StandardErrorPath = "${config.xdg.stateHome}/launchd/env.base.err"; + }; + }; + }; + }; + sops = lib.mkIf (secretsFile != null) { defaultSopsFile = secretsFile; gnupg = { diff --git a/home/modules/kubernetes/default.nix b/home/modules/kubernetes/default.nix index a0ec6f9..7a1514c 100644 --- a/home/modules/kubernetes/default.nix +++ b/home/modules/kubernetes/default.nix @@ -43,4 +43,36 @@ in { }; }; }; + + launchd = lib.mkIf isDarwin { + enable = true; + agents = { + "io.crdant.env.kubernetes" = { + enable = true; + config = { + Label = "io.crdant.env.kubernetes"; + ProgramArguments = [ + "${pkgs.bash}/bin/bash" + "-c" + '' + # Add krew bin to PATH for GUI Kubernetes tools + CURRENT_PATH=$(launchctl getenv PATH) + if [[ -z "$CURRENT_PATH" ]]; then + # If PATH doesn't exist yet, set a basic one + launchctl setenv PATH "${config.home.homeDirectory}/.krew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + else + # Prepend krew to existing PATH if not already present + if [[ "$CURRENT_PATH" != *"${config.home.homeDirectory}/.krew/bin"* ]]; then + launchctl setenv PATH "${config.home.homeDirectory}/.krew/bin:$CURRENT_PATH" + fi + fi + '' + ]; + RunAtLoad = true; + StandardOutPath = "${config.xdg.stateHome}/launchd/env.kubernetes.out"; + StandardErrorPath = "${config.xdg.stateHome}/launchd/env.kubernetes.err"; + }; + }; + }; + }; } From 54d4ebf9fe7fef82e7348aa1f54ba88214b96c6f Mon Sep 17 00:00:00 2001 From: Chuck D'Antonio Date: Mon, 20 Oct 2025 12:47:59 -0400 Subject: [PATCH 4/5] Adds SSH_AUTH_SOCK to launchd agent for GUI application authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Configures the base module's launchd agent to expose SSH_AUTH_SOCK pointing to the GPG agent SSH socket (~/.gnupg/S.gpg-agent.ssh). This enables GUI applications like Git clients, VS Code, and other macOS applications to use the Yubikey-backed GPG agent for SSH authentication, matching the behavior already configured for shell sessions in zsh initExtra. Without this configuration, GUI applications would not have access to the GPG agent socket and would fall back to default SSH authentication methods, bypassing the Yubikey security. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- home/modules/base/default.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/home/modules/base/default.nix b/home/modules/base/default.nix index 827f056..5a2bf27 100644 --- a/home/modules/base/default.nix +++ b/home/modules/base/default.nix @@ -379,6 +379,9 @@ in { # Set XDG_CONFIG_HOME for XDG-compliant applications launchctl setenv XDG_CONFIG_HOME "${config.xdg.configHome}" + + # Set SSH_AUTH_SOCK to GPG agent socket for GUI applications + launchctl setenv SSH_AUTH_SOCK "${config.home.homeDirectory}/.gnupg/S.gpg-agent.ssh" '' ]; RunAtLoad = true; From d84b37fa95b016bc3905a77662d2961b9e8fe581 Mon Sep 17 00:00:00 2001 From: Chuck D'Antonio Date: Mon, 20 Oct 2025 13:25:37 -0400 Subject: [PATCH 5/5] Tightens up handling of PATH variable Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- home/modules/kubernetes/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/home/modules/kubernetes/default.nix b/home/modules/kubernetes/default.nix index 7a1514c..bef6310 100644 --- a/home/modules/kubernetes/default.nix +++ b/home/modules/kubernetes/default.nix @@ -62,7 +62,7 @@ in { launchctl setenv PATH "${config.home.homeDirectory}/.krew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" else # Prepend krew to existing PATH if not already present - if [[ "$CURRENT_PATH" != *"${config.home.homeDirectory}/.krew/bin"* ]]; then + if [[ ":$CURRENT_PATH:" != *":${config.home.homeDirectory}/.krew/bin:"* ]]; then launchctl setenv PATH "${config.home.homeDirectory}/.krew/bin:$CURRENT_PATH" fi fi