Files
home/docs/nix-architecture.md
Morten Olsen f42a092b60 init
2025-12-15 13:15:09 +01:00

1288 lines
37 KiB
Markdown

# Nix Home Manager Flake Architecture
## Overview
This document defines the architecture for migrating from chezmoi to Nix Home Manager with flakes. The design supports two target environments (personal and work) while maintaining a clean, modular, and extensible structure.
---
## 1. Directory Structure
```
.
├── flake.nix # Main flake entry point
├── flake.lock # Locked dependencies
├── README.md # Setup and usage documentation
├── hosts/ # Machine-specific configurations
│ ├── personal/
│ │ └── default.nix # Personal machine darwin config
│ └── work/
│ └── default.nix # Work machine darwin config
├── home/ # Home Manager configurations
│ ├── default.nix # Shared home configuration
│ ├── personal.nix # Personal profile overrides
│ └── work.nix # Work profile overrides
├── modules/ # Reusable Nix modules
│ ├── darwin/ # macOS-specific modules
│ │ ├── default.nix # Common darwin settings
│ │ ├── homebrew.nix # Homebrew cask management
│ │ └── system.nix # System preferences
│ │
│ └── home/ # Home Manager modules
│ ├── shell/
│ │ ├── default.nix # Shell module entry point
│ │ ├── zsh.nix # Zsh configuration
│ │ ├── starship.nix # Starship prompt
│ │ ├── atuin.nix # Shell history
│ │ ├── direnv.nix # Directory environments
│ │ └── aliases.nix # Shell aliases
│ │
│ ├── git/
│ │ ├── default.nix # Git module entry point
│ │ ├── config.nix # Core git configuration
│ │ └── identities.nix # Git identity management
│ │
│ ├── ssh/
│ │ ├── default.nix # SSH module entry point
│ │ └── hosts.nix # SSH host configurations
│ │
│ ├── terminal/
│ │ ├── default.nix # Terminal module entry point
│ │ ├── tmux.nix # Tmux configuration
│ │ └── neovim.nix # Neovim configuration
│ │
│ ├── packages/
│ │ ├── default.nix # Package module entry point
│ │ ├── cli.nix # CLI tools
│ │ ├── development.nix # Development tools
│ │ └── kubernetes.nix # K8s tools
│ │
│ └── apps/
│ ├── default.nix # Apps module entry point
│ └── aerospace.nix # Window manager config
├── lib/ # Helper functions
│ └── default.nix # Utility functions
├── overlays/ # Nixpkgs overlays
│ └── default.nix # Custom package overlays
├── secrets/ # Encrypted secrets (sops-nix)
│ ├── secrets.yaml # Encrypted secrets file
│ └── .sops.yaml # SOPS configuration
└── docs/ # Documentation
├── migration-analysis.md # Migration analysis
└── nix-architecture.md # This document
```
### Design Rationale
1. **`hosts/`** - Machine-level configurations using nix-darwin. Each host can have unique system settings while sharing common darwin modules.
2. **`home/`** - Profile-specific home-manager configurations. The [`default.nix`](home/default.nix) contains shared settings, while [`personal.nix`](home/personal.nix) and [`work.nix`](home/work.nix) contain profile-specific overrides.
3. **`modules/`** - Reusable, composable modules organized by functionality. Split into `darwin/` (system-level) and `home/` (user-level) to maintain clear separation.
4. **`lib/`** - Helper functions for reducing boilerplate and improving maintainability.
5. **`overlays/`** - Custom package modifications or additions to nixpkgs.
6. **`secrets/`** - Encrypted secrets managed by sops-nix for sensitive data like API tokens.
---
## 2. Flake Structure
### 2.1 Inputs
```nix
{
inputs = {
# Core dependencies
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# Home Manager for user environment
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
# nix-darwin for macOS system configuration
nix-darwin = {
url = "github:LnL7/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs";
};
# Secrets management
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
# Optional: Homebrew management through nix-darwin
nix-homebrew = {
url = "github:zhaofengli-wip/nix-homebrew";
};
};
}
```
### 2.2 Outputs Structure
```nix
{
outputs = { self, nixpkgs, home-manager, nix-darwin, sops-nix, nix-homebrew, ... }@inputs:
let
# Supported systems (macOS for now, Linux can be added later)
systems = [ "aarch64-darwin" "x86_64-darwin" ];
# Helper to generate per-system attributes
forAllSystems = nixpkgs.lib.genAttrs systems;
# Common special args passed to all modules
specialArgs = { inherit inputs; };
in
{
# Darwin (macOS) system configurations
darwinConfigurations = {
# Personal machine configuration
"personal" = nix-darwin.lib.darwinSystem {
system = "aarch64-darwin";
inherit specialArgs;
modules = [
./hosts/personal
./modules/darwin
home-manager.darwinModules.home-manager
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
extraSpecialArgs = specialArgs;
users.alice = { ... }: {
imports = [
./home
./home/personal.nix
];
};
};
}
];
};
# Work machine configuration
"work" = nix-darwin.lib.darwinSystem {
system = "aarch64-darwin";
inherit specialArgs;
modules = [
./hosts/work
./modules/darwin
home-manager.darwinModules.home-manager
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
extraSpecialArgs = specialArgs;
users.alice = { ... }: {
imports = [
./home
./home/work.nix
];
};
};
}
];
};
};
# Standalone Home Manager configurations (for non-darwin systems)
homeConfigurations = {
"personal@linux" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
extraSpecialArgs = specialArgs;
modules = [
./home
./home/personal.nix
];
};
"work@linux" = home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.x86_64-linux;
extraSpecialArgs = specialArgs;
modules = [
./home
./home/work.nix
];
};
};
# Development shells for this repository
devShells = forAllSystems (system:
let pkgs = nixpkgs.legacyPackages.${system};
in {
default = pkgs.mkShell {
packages = with pkgs; [ nixfmt sops age ];
};
}
);
};
}
```
### 2.3 Key Design Decisions
1. **nix-darwin as primary entry point** - On macOS, use `darwinConfigurations` which integrates home-manager as a module. This allows managing both system-level settings (Homebrew, system preferences) and user-level dotfiles in one command.
2. **Standalone homeConfigurations** - Provided for future Linux support or cases where nix-darwin isn't desired.
3. **`useGlobalPkgs` and `useUserPackages`** - Ensures home-manager uses the same nixpkgs instance as darwin, avoiding duplicate package downloads.
4. **`specialArgs`** - Passes inputs to all modules, enabling access to flake inputs anywhere in the configuration.
---
## 3. Module Design
### 3.1 Module Architecture Diagram
```mermaid
graph TB
subgraph Flake
F[flake.nix]
end
subgraph Darwin Configs
DP[hosts/personal]
DW[hosts/work]
end
subgraph Darwin Modules
DM[modules/darwin/default.nix]
HB[modules/darwin/homebrew.nix]
SYS[modules/darwin/system.nix]
end
subgraph Home Configs
HC[home/default.nix]
HP[home/personal.nix]
HW[home/work.nix]
end
subgraph Home Modules
SH[modules/home/shell]
GIT[modules/home/git]
SSH[modules/home/ssh]
TM[modules/home/terminal]
PKG[modules/home/packages]
APP[modules/home/apps]
end
F --> DP
F --> DW
DP --> DM
DW --> DM
DM --> HB
DM --> SYS
DP --> HC
DW --> HC
DP --> HP
DW --> HW
HC --> SH
HC --> GIT
HC --> SSH
HC --> TM
HC --> PKG
HC --> APP
```
### 3.2 Shared vs Profile-Specific Content
| Component | Shared | Personal-Specific | Work-Specific |
|-----------|--------|-------------------|---------------|
| **Shell** | zsh, starship, atuin, aliases | - | - |
| **Git** | Core config, aliases, delta | Personal signing key, personal email | Work signing key, work email, zeronorth config |
| **SSH** | 1Password agent, control master | github-private host | github-zeronorth host |
| **Packages** | CLI tools, dev tools, k8s | Personal apps (darktable, proton, steam) | - |
| **Homebrew Casks** | 1password, ghostty, raycast | signal, proton-*, steam | - |
| **Project Configs** | - | ~/Projects/.gitconfig | ~/Projects/private/.gitconfig, ~/Projects/zeronorth/.gitconfig |
### 3.3 Module Option Pattern
Each module should follow this pattern for configurability:
```nix
# modules/home/git/default.nix
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.modules.git;
in
{
options.modules.git = {
enable = mkEnableOption "Git configuration";
userName = mkOption {
type = types.str;
default = "Morten Olsen";
description = "Git user name";
};
userEmail = mkOption {
type = types.str;
description = "Git user email";
};
signing = {
enable = mkEnableOption "Git commit signing";
key = mkOption {
type = types.str;
description = "SSH signing key";
};
};
includes = mkOption {
type = types.listOf (types.submodule {
options = {
condition = mkOption { type = types.str; };
path = mkOption { type = types.str; };
};
});
default = [];
description = "Conditional git config includes";
};
};
config = mkIf cfg.enable {
programs.git = {
enable = true;
userName = cfg.userName;
userEmail = cfg.userEmail;
signing = mkIf cfg.signing.enable {
key = cfg.signing.key;
signByDefault = true;
};
extraConfig = {
gpg.format = "ssh";
gpg.ssh.program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign";
# ... other shared config
};
includes = cfg.includes;
};
};
}
```
### 3.4 Module Implementations
#### Shell Module ([`modules/home/shell/default.nix`](modules/home/shell/default.nix))
```nix
{ config, lib, pkgs, ... }:
{
imports = [
./zsh.nix
./starship.nix
./atuin.nix
./direnv.nix
./aliases.nix
];
options.modules.shell = {
enable = lib.mkEnableOption "Shell configuration";
};
config = lib.mkIf config.modules.shell.enable {
# Enable all shell sub-modules
modules.shell.zsh.enable = true;
modules.shell.starship.enable = true;
modules.shell.atuin.enable = true;
modules.shell.direnv.enable = true;
};
}
```
#### Git Module ([`modules/home/git/default.nix`](modules/home/git/default.nix))
```nix
{ config, lib, pkgs, ... }:
{
imports = [
./config.nix
./identities.nix
];
options.modules.git = {
enable = lib.mkEnableOption "Git configuration";
profile = lib.mkOption {
type = lib.types.enum [ "personal" "work" ];
description = "Git profile to use";
};
};
}
```
#### SSH Module ([`modules/home/ssh/default.nix`](modules/home/ssh/default.nix))
```nix
{ config, lib, pkgs, ... }:
{
imports = [ ./hosts.nix ];
options.modules.ssh = {
enable = lib.mkEnableOption "SSH configuration";
onePasswordAgent = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Use 1Password SSH agent";
};
};
config = lib.mkIf config.modules.ssh.enable {
programs.ssh = {
enable = true;
controlMaster = "auto";
controlPath = "/tmp/ssh-%r@%h:%p";
forwardAgent = true;
extraConfig = lib.mkIf config.modules.ssh.onePasswordAgent ''
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
'';
includes = [ "~/.colima/ssh_config" ];
};
};
}
```
#### Homebrew Module ([`modules/darwin/homebrew.nix`](modules/darwin/homebrew.nix))
```nix
{ config, lib, pkgs, ... }:
let
cfg = config.modules.homebrew;
in
{
options.modules.homebrew = {
enable = lib.mkEnableOption "Homebrew management";
personalCasks = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Include personal-only casks";
};
};
config = lib.mkIf cfg.enable {
homebrew = {
enable = true;
onActivation = {
autoUpdate = true;
cleanup = "zap";
upgrade = true;
};
taps = [
"nikitabobko/tap"
"coder/coder"
"fluxcd/tap"
"sst/tap"
];
casks = [
"1password"
"1password-cli"
"aerospace"
"dbeaver-community"
"ghostty"
"home-assistant"
"jellyfin-media-player"
"lens"
"localsend"
"mqtt-explorer"
"obsidian"
"ollama-app"
"raycast"
] ++ lib.optionals cfg.personalCasks [
"darktable"
"signal"
"proton-mail-bridge"
"proton-pass"
"protonvpn"
"steam"
];
};
};
}
```
### 3.5 Secrets Management
For sensitive data, use sops-nix with age encryption:
```nix
# secrets/.sops.yaml
keys:
- &personal age1... # Personal machine key
- &work age1... # Work machine key
creation_rules:
- path_regex: secrets/personal\.yaml$
key_groups:
- age:
- *personal
- path_regex: secrets/work\.yaml$
key_groups:
- age:
- *work
- path_regex: secrets/secrets\.yaml$
key_groups:
- age:
- *personal
- *work
```
For 1Password integration (already used in current setup), reference secrets directly:
```nix
# In modules that need secrets
home.sessionVariables = {
NODE_AUTH_TOKEN = "op://Employee/Github NPM Token/password";
};
```
---
## 4. Configuration Profiles
### 4.1 Shared Configuration ([`home/default.nix`](home/default.nix))
```nix
{ config, lib, pkgs, ... }:
{
imports = [
../modules/home/shell
../modules/home/git
../modules/home/ssh
../modules/home/terminal
../modules/home/packages
../modules/home/apps
];
home = {
stateVersion = "24.05";
sessionVariables = {
EDITOR = "nvim";
LANG = "en_US.UTF-8";
};
sessionPath = [
"$HOME/.local/bin"
"$HOME/.cargo/bin"
"/opt/homebrew/bin"
];
};
# Enable shared modules
modules = {
shell.enable = true;
git.enable = true;
ssh.enable = true;
terminal.enable = true;
packages.enable = true;
apps.enable = true;
};
}
```
### 4.2 Personal Profile ([`home/personal.nix`](home/personal.nix))
```nix
{ config, lib, pkgs, ... }:
{
# Git configuration for personal
modules.git = {
profile = "personal";
userEmail = "fbtijfdq@void.black";
signing.key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFaIAP/ZJ7+7jeR44e1yIJjfQAB6MN351LDKJAXVF62P";
includes = [
{
condition = "gitdir:~/Projects/";
path = "~/Projects/.gitconfig";
}
];
};
# SSH hosts for personal
modules.ssh.hosts = {
github-private = {
hostname = "ssh.github.com";
user = "git";
port = 443;
identityFile = "~/.ssh/keys/github-private.pub";
};
gitea-ssh-olsen-cloud = {
hostname = "gitea-ssh.olsen.cloud";
user = "git";
port = 2202;
identityFile = "~/.ssh/keys/github-private.pub";
};
};
# Personal-only packages
modules.packages.personal = true;
# Project-specific git config
home.file.".Projects/.gitconfig".text = ''
[user]
email = fbtijfdq@void.black
name = Morten Olsen
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFaIAP/ZJ7+7jeR44e1yIJjfQAB6MN351LDKJAXVF62P
[commit]
gpgsign = true
[gpg]
format = ssh
[gpg "ssh"]
program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
[url "ssh://git@ssh-gitea.olsen.cloud:2205/"]
insteadOf = "https://gitea.olsen.cloud/"
[url "git@github-private:"]
insteadOf = https://github.com/
'';
}
```
### 4.3 Work Profile ([`home/work.nix`](home/work.nix))
```nix
{ config, lib, pkgs, ... }:
{
# Git configuration for work
modules.git = {
profile = "work";
userEmail = "fbtijfdq@void.black"; # Default email
signing.key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAzuPy7D/54GxMq9Zhz0CUjaDnEQ6RkQ/yqVYl7U55k";
includes = [
{
condition = "gitdir:~/Projects/private/";
path = "~/Projects/private/.gitconfig";
}
{
condition = "gitdir:~/Projects/zeronorth/";
path = "~/Projects/zeronorth/.gitconfig";
}
];
};
# SSH hosts for work
modules.ssh.hosts = {
github-private = {
hostname = "ssh.github.com";
user = "git";
port = 443;
identityFile = "~/.ssh/keys/github-private.pub";
};
github-zeronorth = {
hostname = "ssh.github.com";
user = "git";
port = 443;
identityFile = "~/.ssh/keys/github-zeronorth.pub";
};
gitea-ssh-olsen-cloud = {
hostname = "gitea-ssh.olsen.cloud";
user = "git";
port = 2202;
identityFile = "~/.ssh/keys/github-private.pub";
};
};
# Work-only packages (none currently)
modules.packages.work = true;
# Project-specific git configs
home.file = {
"Projects/private/.gitconfig".text = ''
[user]
email = fbtijfdq@void.black
name = Morten Olsen
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILAzuPy7D/54GxMq9Zhz0CUjaDnEQ6RkQ/yqVYl7U55k
[commit]
gpgsign = true
[gpg]
format = ssh
[gpg "ssh"]
program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
[url "ssh://git@ssh-gitea.olsen.cloud:2205/"]
insteadOf = "https://gitea.olsen.cloud/"
[url "git@github-private:"]
insteadOf = https://github.com/
'';
"Projects/zeronorth/.gitconfig".text = ''
[user]
email = morten.olsen@zeronorth.com
name = Morten Olsen
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKDbZITpz5QrVIxPn9gKVWMPK+3W3YZZGszFOQvO/h7M
[commit]
gpgsign = true
[gpg]
format = ssh
[gpg "ssh"]
program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
[url "git@github-zeronorth:"]
insteadOf = https://github.com/
'';
"Projects/zeronorth/.envrc".text = ''
export NODE_AUTH_TOKEN="op://Employee/Github NPM Token/password"
export NPM_TOKEN="op://Employee/Github NPM Token/password"
export NPM_GITHUB_TOKEN="op://Employee/Github NPM Token/password"
export AWS_PROFILE=zntest
'';
};
}
```
### 4.4 Profile Selection Flow
```mermaid
flowchart TD
A[New Machine Setup] --> B{Machine Type?}
B -->|Personal| C[darwin-rebuild switch --flake .#personal]
B -->|Work| D[darwin-rebuild switch --flake .#work]
C --> E[Loads hosts/personal + home/personal.nix]
D --> F[Loads hosts/work + home/work.nix]
E --> G[Personal SSH keys, git config, packages]
F --> H[Work SSH keys, git config, zeronorth setup]
G --> I[System Ready]
H --> I
```
---
## 5. Usage Patterns
### 5.1 Initial Setup on New Machine
#### Prerequisites
1. Install Nix with flakes enabled:
```bash
curl -L https://nixos.org/nix/install | sh
```
2. Enable flakes in Nix configuration:
```bash
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
```
#### Personal Machine Setup
```bash
# Clone the dotfiles repository
git clone https://github.com/username/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
# Build and activate the personal configuration
nix build .#darwinConfigurations.personal.system
./result/sw/bin/darwin-rebuild switch --flake .#personal
# Subsequent rebuilds
darwin-rebuild switch --flake .#personal
```
#### Work Machine Setup
```bash
# Clone the dotfiles repository
git clone https://github.com/username/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
# Build and activate the work configuration
nix build .#darwinConfigurations.work.system
./result/sw/bin/darwin-rebuild switch --flake .#work
# Subsequent rebuilds
darwin-rebuild switch --flake .#work
```
### 5.2 Switching Configurations
If you need to switch a machine from personal to work (or vice versa):
```bash
# Switch to work configuration
darwin-rebuild switch --flake .#work
# Switch back to personal configuration
darwin-rebuild switch --flake .#personal
```
**Note:** Switching profiles will change git configs, SSH hosts, and installed packages. Some manual cleanup may be needed for files that were created by the previous profile.
### 5.3 Updating Configuration
#### After Editing Nix Files
```bash
# Rebuild and switch to new configuration
darwin-rebuild switch --flake .#personal # or .#work
# Build without switching (for testing)
darwin-rebuild build --flake .#personal
```
#### Updating Flake Inputs
```bash
# Update all inputs
nix flake update
# Update specific input
nix flake lock --update-input nixpkgs
nix flake lock --update-input home-manager
# Rebuild after update
darwin-rebuild switch --flake .#personal
```
### 5.4 Common Commands Reference
| Command | Description |
|---------|-------------|
| `darwin-rebuild switch --flake .#personal` | Apply personal configuration |
| `darwin-rebuild switch --flake .#work` | Apply work configuration |
| `darwin-rebuild build --flake .#personal` | Build without applying |
| `nix flake update` | Update all flake inputs |
| `nix flake check` | Validate flake configuration |
| `nix repl --expr 'builtins.getFlake "."'` | Interactive debugging |
| `darwin-rebuild --rollback` | Rollback to previous generation |
| `nix-collect-garbage -d` | Clean up old generations |
### 5.5 Development Workflow
```bash
# Enter development shell with tools for editing this repo
nix develop
# Format Nix files
nixfmt **/*.nix
# Check configuration validity
nix flake check
```
---
## 6. Future Extensibility
### 6.1 Adding Linux Support
The architecture already includes placeholder `homeConfigurations` for Linux. To fully support Linux:
1. Add Linux-specific modules in `modules/home/` with platform conditionals:
```nix
{ config, lib, pkgs, ... }:
{
config = lib.mkMerge [
# Common configuration
{ ... }
# macOS-specific
(lib.mkIf pkgs.stdenv.isDarwin { ... })
# Linux-specific
(lib.mkIf pkgs.stdenv.isLinux { ... })
];
}
```
2. Create Linux host configurations in `hosts/linux/`.
3. Use `homeConfigurations` for standalone home-manager on Linux.
### 6.2 Adding New Machines
To add a new machine configuration:
1. Create a new directory under `hosts/`:
```
hosts/
├── personal/
├── work/
└── new-machine/
└── default.nix
```
2. Add the configuration to `flake.nix`:
```nix
darwinConfigurations."new-machine" = nix-darwin.lib.darwinSystem {
# ...
};
```
### 6.3 Adding New Modules
To add a new module (e.g., for a new application):
1. Create the module file:
```
modules/home/apps/new-app.nix
```
2. Import it in the parent module:
```nix
# modules/home/apps/default.nix
imports = [
./aerospace.nix
./new-app.nix
];
```
3. Add options and configuration following the established pattern.
---
## 7. Migration Checklist
When implementing this architecture, follow this order:
- [ ] Create basic flake.nix with inputs
- [ ] Set up directory structure
- [ ] Implement darwin modules (homebrew, system)
- [ ] Implement shell modules (zsh, starship, atuin)
- [ ] Implement git modules with profile support
- [ ] Implement SSH modules
- [ ] Implement terminal modules (tmux)
- [ ] Implement package modules
- [ ] Implement apps modules (aerospace)
- [ ] Create personal profile configuration
- [ ] Create work profile configuration
- [ ] Set up secrets management (sops-nix)
- [ ] Test on personal machine
- [ ] Test on work machine
- [ ] Document any manual steps
- [ ] Remove chezmoi after successful migration
---
## 8. Summary
This architecture provides:
1. **Clean separation of concerns** - Darwin system config, home-manager user config, and reusable modules are clearly separated.
2. **Profile-based configuration** - Personal and work environments are distinct profiles that share common modules but override specific settings.
3. **Modular design** - Each functional area (shell, git, ssh, packages) is a self-contained module with configurable options.
4. **macOS-first with extensibility** - Designed for macOS with nix-darwin, but structured to easily add Linux support later.
5. **Declarative package management** - CLI tools via nixpkgs, GUI apps via Homebrew casks managed through nix-darwin.
6. **Secrets handling** - Support for both sops-nix encrypted secrets and 1Password CLI references.
7. **Simple usage patterns** - Single command to apply configuration, easy switching between profiles.
The migration from chezmoi to this Nix-based setup will provide reproducible, declarative configuration management with the full power of the Nix ecosystem.
---
## 9. Final Implementation
This section documents the actual implemented structure, which may differ slightly from the original design above.
### 9.1 Implemented Directory Structure
```
.
├── flake.nix # Main flake entry point
├── flake.lock # Locked dependencies
├── README.md # Setup and usage documentation
├── home/ # Home Manager configurations
│ ├── default.nix # Shared home configuration
│ ├── personal.nix # Personal profile overrides
│ └── work.nix # Work profile overrides
├── hosts/ # Machine-specific nix-darwin configs
│ ├── personal/
│ │ └── default.nix # Personal machine darwin settings
│ └── work/
│ └── default.nix # Work machine darwin settings
├── modules/ # Reusable Nix modules
│ ├── darwin/
│ │ └── homebrew.nix # Homebrew cask management
│ │
│ └── home/ # Home Manager modules
│ ├── apps.nix # Application configs (aerospace, jellyfin-tui)
│ ├── git.nix # Git configuration with signing
│ ├── git-files.nix # Project-specific git configs
│ ├── packages.nix # CLI packages via Nix
│ ├── shell.nix # Shell environment (zsh, starship, etc.)
│ ├── ssh.nix # SSH configuration with 1Password
│ └── tmux.nix # Tmux configuration
└── docs/ # Documentation
├── migration-analysis.md # Original chezmoi analysis
├── nix-architecture.md # This document
└── usage.md # Detailed usage guide
```
### 9.2 Module Reference
#### Darwin Modules
| Module | File | Purpose |
|--------|------|---------|
| **Homebrew** | [`modules/darwin/homebrew.nix`](../modules/darwin/homebrew.nix) | Manages Homebrew taps, formulae, and casks declaratively |
#### Home Manager Modules
| Module | File | Purpose |
|--------|------|---------|
| **Shell** | [`modules/home/shell.nix`](../modules/home/shell.nix) | Zsh, Starship, Atuin, Direnv, Zoxide, FZF, Pyenv |
| **Packages** | [`modules/home/packages.nix`](../modules/home/packages.nix) | CLI tools and development packages |
| **Git** | [`modules/home/git.nix`](../modules/home/git.nix) | Git configuration with delta, signing, aliases |
| **Git Files** | [`modules/home/git-files.nix`](../modules/home/git-files.nix) | Project-specific gitconfig files |
| **SSH** | [`modules/home/ssh.nix`](../modules/home/ssh.nix) | SSH with 1Password agent, host configurations |
| **Tmux** | [`modules/home/tmux.nix`](../modules/home/tmux.nix) | Tmux with vim navigation, plugins |
| **Apps** | [`modules/home/apps.nix`](../modules/home/apps.nix) | Aerospace window manager, Jellyfin TUI |
### 9.3 Module Configuration Options
#### `modules.homebrew` (Darwin)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | bool | `false` | Enable Homebrew management |
| `casks.shared` | list of str | (see module) | Casks installed on all machines |
| `casks.personal` | list of str | (see module) | Casks installed only on personal machines |
| `casks.enablePersonal` | bool | `false` | Whether to install personal casks |
| `brews` | list of str | (see module) | Homebrew formulae to install |
| `taps` | list of str | (see module) | Homebrew taps to add |
| `cleanup` | enum | `"zap"` | Cleanup behavior: `"none"`, `"uninstall"`, `"zap"` |
#### `modules.ssh` (Home)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | bool | `false` | Enable SSH configuration |
| `enableGitHubPrivate` | bool | `false` | Enable github-private host |
| `enableGitHubZeronorth` | bool | `false` | Enable github-zeronorth host |
| `enableCoder` | bool | `false` | Enable Coder SSH hosts |
| `enableGiteaPrivate` | bool | `false` | Enable gitea-ssh.olsen.cloud host |
| `githubPrivateKeyPath` | str | `"~/.ssh/keys/github-private.pub"` | Path to GitHub private key |
| `githubZeronorthKeyPath` | str | `"~/.ssh/keys/github-zeronorth.pub"` | Path to GitHub Zeronorth key |
#### `modules.git` (Home)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | bool | `false` | Enable Git configuration |
| `userName` | str | `"Morten Olsen"` | Git user name |
| `userEmail` | str | (required) | Git user email |
| `signingKey` | str | (required) | SSH signing key (public key) |
| `includes` | list of {condition, path} | `[]` | Conditional includes for project configs |
#### `modules.gitFiles` (Home)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | bool | `false` | Enable project-specific git files |
| `personal.enable` | bool | `false` | Create ~/Projects/.gitconfig |
| `personal.email` | str | (example) | Email for personal projects |
| `personal.signingKey` | str | (required) | Signing key for personal projects |
| `private.enable` | bool | `false` | Create ~/Projects/private/.gitconfig |
| `private.email` | str | (example) | Email for private projects |
| `private.signingKey` | str | (required) | Signing key for private projects |
| `zeronorth.enable` | bool | `false` | Create ~/Projects/zeronorth/.gitconfig |
| `zeronorth.email` | str | (example) | Email for zeronorth projects |
| `zeronorth.signingKey` | str | (required) | Signing key for zeronorth projects |
#### `modules.apps` (Home)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `enable` | bool | `false` | Enable application configurations |
| `aerospace.enable` | bool | `true` | Enable Aerospace window manager config |
| `jellyfin-tui.enable` | bool | `false` | Enable Jellyfin TUI config |
| `jellyfin-tui.serverUrl` | str | `""` | Jellyfin server URL |
| `jellyfin-tui.serverName` | str | `"Home Server"` | Display name for server |
| `jellyfin-tui.username` | str | `""` | Jellyfin username |
| `jellyfin-tui.passwordFile` | str | `""` | Path to password file |
### 9.4 Profile Configuration Summary
#### Personal Profile ([`home/personal.nix`](../home/personal.nix))
```nix
modules.git = {
enable = true;
userEmail = "alice@personal.example.com";
signingKey = "ssh-ed25519 AAAAC3...";
includes = [{ condition = "gitdir:~/Projects/"; path = "~/Projects/.gitconfig"; }];
};
modules.gitFiles = {
enable = true;
personal = { enable = true; email = "..."; signingKey = "..."; };
};
modules.ssh = {
enableGitHubPrivate = true;
enableGiteaPrivate = true;
enableGitHubZeronorth = false;
enableCoder = false;
};
modules.apps = {
jellyfin-tui = { enable = true; serverUrl = "..."; username = "..."; };
};
```
#### Work Profile ([`home/work.nix`](../home/work.nix))
```nix
modules.git = {
enable = true;
userEmail = "alice@work.example.com";
signingKey = "ssh-ed25519 AAAAC3...";
includes = [
{ condition = "gitdir:~/Projects/private/"; path = "~/Projects/private/.gitconfig"; }
{ condition = "gitdir:~/Projects/zeronorth/"; path = "~/Projects/zeronorth/.gitconfig"; }
];
};
modules.gitFiles = {
enable = true;
private = { enable = true; email = "..."; signingKey = "..."; };
zeronorth = { enable = true; email = "..."; signingKey = "..."; };
};
modules.ssh = {
enableGitHubPrivate = true;
enableGitHubZeronorth = true;
enableCoder = true;
enableGiteaPrivate = false;
};
```
### 9.5 Packages Installed
#### Via Nix (from [`modules/home/packages.nix`](../modules/home/packages.nix))
| Category | Packages |
|----------|----------|
| **Modern CLI** | bat, eza, fd, ripgrep, delta |
| **File Utils** | jq, yq, tree, rsync, unzip, curl, wget, watch |
| **Git** | git, gh, jujutsu, lazygit |
| **Development** | neovim, tmux, nodejs_22, deno, rustup, python313, uv, gnumake, cmake |
| **Containers** | docker, docker-buildx, docker-compose, colima |
| **Kubernetes** | kubectl, kubernetes-helm, helmfile, k9s, istioctl, fluxcd |
| **Infrastructure** | terraform, ansible, sshpass, awscli2 |
| **Media** | ffmpeg |
| **Security** | gnupg, age, sops |
| **Misc** | graphviz, tree-sitter, htop, ncdu, tldr, nixfmt-rfc-style, nil |
#### Via Homebrew Casks (from [`modules/darwin/homebrew.nix`](../modules/darwin/homebrew.nix))
**Shared (all machines):**
- 1password, 1password-cli
- ghostty, dbeaver-community, lens
- aerospace
- raycast, obsidian
- jellyfin-media-player, ollama-app
- localsend, mqtt-explorer, home-assistant
**Personal only:**
- darktable
- proton-mail-bridge, proton-pass, protonvpn
- signal
- steam
#### Via Homebrew Formulae
- coder/coder/coder
- fluxcd/tap/flux
- sst/tap/opencode
- tree-sitter-cli
### 9.6 Key Differences from Original Design
1. **Simplified module structure** - Instead of deeply nested modules (e.g., `modules/home/shell/zsh.nix`), the implementation uses flatter files (e.g., `modules/home/shell.nix`) for simplicity.
2. **No sops-nix** - Secrets are managed via 1Password CLI references rather than sops-nix encrypted files.
3. **No lib/ directory** - Helper functions are defined inline in modules rather than in a separate lib directory.
4. **No overlays/** - No custom package overlays are currently needed.
5. **Combined shell module** - All shell-related configuration (zsh, starship, atuin, direnv, zoxide, fzf, pyenv) is in a single `shell.nix` file.
6. **Separate git-files module** - Project-specific gitconfig files are managed in a dedicated `git-files.nix` module rather than inline in profile files.
### 9.7 Migration Status
The following chezmoi files have been migrated:
| Chezmoi File | Nix Module | Status |
|--------------|------------|--------|
| `dot_zshrc` | `modules/home/shell.nix` | ✅ Migrated |
| `dot_tmux.conf` | `modules/home/tmux.nix` | ✅ Migrated |
| `dot_gitconfig.tmpl` | `modules/home/git.nix` | ✅ Migrated |
| `dot_gitignore_global` | `modules/home/git.nix` | ✅ Migrated |
| `dot_Brewfile.tmpl` | `modules/darwin/homebrew.nix` + `modules/home/packages.nix` | ✅ Migrated |
| `dot_aerospace.toml` | `modules/home/apps.nix` | ✅ Migrated |
| `dot_config/atuin/config.toml` | `modules/home/shell.nix` | ✅ Migrated |
| `dot_shellrc/rc.d/*` | `modules/home/shell.nix` | ✅ Migrated |
| `dot_ssh/config` | `modules/home/ssh.nix` | ✅ Migrated |
| `dot_ssh/config.d/*` | `modules/home/ssh.nix` | ✅ Migrated |
| `private_Projects/*` | `modules/home/git-files.nix` | ✅ Migrated |
| `private_Library/.../jellyfin-tui/*` | `modules/home/apps.nix` | ✅ Migrated |
The original chezmoi files are kept in the repository for reference during the transition period.