This commit is contained in:
2025-12-26 11:02:58 +01:00
commit 83841223bb
15 changed files with 922 additions and 0 deletions

94
README.md Normal file
View File

@@ -0,0 +1,94 @@
# Nix macOS Starter
A beginner-friendly Nix configuration for macOS using flakes, nix-darwin, Home Manager, and Mise.
## About
A clean, well-documented starting point for managing your macOS system declaratively with Nix. Includes sensible defaults for development tools, shell configuration, and system settings.
**Author:** Ben Gubler
## Prerequisites
1. **Install Nix** using the [Determinate Systems installer](https://docs.determinate.systems/#products) (download the graphical installer for macOS). After installation, restart your terminal.
**Note:** Homebrew is managed declaratively via nix-homebrew - if you already have it installed, it will auto-migrate. Otherwise, it's installed automatically.
## Quick Start
### 1. Clone and Configure
```bash
# Clone the repository
git clone https://github.com/nebrelbug/nix-macos-starter ~/.config/nix
cd ~/.config/nix
```
### 2. Customize Your Configuration
**For Intel Mac Users:** Change the system architecture in `flake.nix` from `"aarch64-darwin"` to `"x86_64-darwin"` on line 28.
**Replace all placeholders:**
- `flake.nix`: `YOUR_USERNAME` (this sets the username for the entire system)
- `home/git.nix`: `YOUR_NAME`, `YOUR_EMAIL`
### 3. Apply the Configuration
```bash
# Build and switch to the configuration
darwin-rebuild switch --flake .#my-macbook
# Or use the alias after initial setup
nix-switch
```
## What's Included
**Development Tools**: [mise](https://mise.jdx.dev/) for Node.js/Python/Rust/etc., Zsh with Starship prompt, essential CLI tools (curl, vim, tmux, htop, tree, ripgrep, gh, zoxide), code quality tools (nil, biome, nixfmt-rfc-style)
**GUI Applications**: Cursor, Ghostty, VS Code, Zed, Raycast, CleanShot, HiddenBar, BetterDisplay, Discord, Slack, 1Password, Brave Browser, Obsidian, Spotify
**System Configuration**: Git setup, macOS optimizations (Finder, Touch ID sudo), Nix settings (flakes, garbage collection), declarative Homebrew management
## Project Structure
```
nix-macos-starter/
├── flake.nix # Main flake configuration and inputs
├── darwin/
│ ├── default.nix # Core macOS system configuration
│ ├── settings.nix # macOS UI/UX preferences and defaults
│ └── homebrew.nix # GUI applications via Homebrew
├── home/
│ ├── default.nix # Home Manager configuration entry point
│ ├── packages.nix # Package declarations and mise setup
│ ├── git.nix # Git configuration
│ ├── shell.nix # Shell configuration
│ └── mise.nix # Development runtime management
└── hosts/
└── my-macbook/
├── configuration.nix # Host-specific packages and settings
└── shell-functions.sh # Custom shell scripts
```
## Customization
**Add CLI Tools**: Edit `home/packages.nix` packages array
**Add GUI Apps**: Edit `darwin/homebrew.nix` casks array
**Add Development Tools**: Add `${pkgs.mise}/bin/mise use --global tool@version` to `home/mise.nix` activation script
**Host-Specific Config**: Use `hosts/my-macbook/configuration.nix` for machine-specific packages/apps and `custom-scripts.sh` for shell scripts
## Troubleshooting
**"Command not found"**: Restart terminal
**Permission denied**: Use `sudo darwin-rebuild switch --flake .#my-macbook`
**Homebrew apps not installing**: nix-homebrew handles this automatically; ensure `/opt/homebrew/bin` in PATH
**Git config not applying**: Replace all `YOUR_*` placeholders, re-run darwin-rebuild
**Need help?** Check [Nix manual](https://nixos.org/manual/nix/stable/), [nix-darwin docs](https://github.com/LnL7/nix-darwin), [Home Manager options](https://nix-community.github.io/home-manager/options.html)
## Credits
- [Ethan Niser](https://github.com/ethanniser) for his [config repo](https://github.com/ethanniser/config) which I used as a reference for this project.
- David Haupt's excellent [tutorial series](https://davi.sh/blog/2024/01/nix-darwin/) which (although slightly outdated) helped me understand the basics of Nix.

63
darwin/default.nix Normal file
View File

@@ -0,0 +1,63 @@
{
pkgs,
inputs,
self,
primaryUser,
...
}:
{
imports = [
# ./homebrew.nix
./settings.nix
inputs.home-manager.darwinModules.home-manager
inputs.nix-homebrew.darwinModules.nix-homebrew
];
# nix config
nix = {
settings = {
experimental-features = [
"nix-command"
"flakes"
];
# disabled due to https://github.com/NixOS/nix/issues/7273
# auto-optimise-store = true;
};
enable = false; # using determinate installer
};
nixpkgs.config.allowUnfree = true;
# homebrew installation manager
nix-homebrew = {
user = primaryUser;
enable = true;
autoMigrate = true;
};
# home-manager config
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
users.${primaryUser} = {
imports = [
../home
];
};
extraSpecialArgs = {
inherit inputs self primaryUser;
};
};
# macOS-specific settings
system.primaryUser = primaryUser;
users.users.${primaryUser} = {
home = "/Users/${primaryUser}";
shell = pkgs.zsh;
};
environment = {
systemPath = [
"/opt/homebrew/bin"
];
pathsToLink = [ "/Applications" ];
};
}

54
darwin/homebrew.nix Normal file
View File

@@ -0,0 +1,54 @@
{ ... }:
{
homebrew = {
enable = true;
onActivation = {
autoUpdate = false;
upgrade = true;
cleanup = "zap";
};
caskArgs.no_quarantine = true;
global.brewfile = true;
# homebrew is best for GUI apps
# nixpkgs is best for CLI tools
casks = [
# OS enhancements
"aerospace"
"cleanshot"
"hiddenbar"
"raycast"
"betterdisplay"
# dev
"cursor"
"ghostty"
"visual-studio-code"
"zed"
# messaging
"discord"
"slack"
"signal"
# other
"1password"
"anki"
"brave-browser"
"obsidian"
"protonvpn"
"spotify"
"thebrowsercompany-dia"
"zen"
];
brews = [
"docker"
"colima"
];
taps = [
"nikitabobko/tap"
];
};
}

35
darwin/settings.nix Normal file
View File

@@ -0,0 +1,35 @@
{ self, ... }:
{
# touch ID for sudo
security.pam.services.sudo_local.touchIdAuth = true;
# system defaults and preferences
system = {
stateVersion = 6;
configurationRevision = self.rev or self.dirtyRev or null;
startup.chime = false;
defaults = {
loginwindow = {
GuestEnabled = false;
DisableConsoleAccess = true;
};
finder = {
AppleShowAllFiles = true; # hidden files
AppleShowAllExtensions = true; # file extensions
_FXShowPosixPathInTitle = true; # title bar full path
ShowPathbar = true; # breadcrumb nav at bottom
ShowStatusBar = true; # file count & disk space
};
NSGlobalDomain = {
NSAutomaticSpellingCorrectionEnabled = false;
NSAutomaticCapitalizationEnabled = false;
NSAutomaticPeriodSubstitutionEnabled = false;
NSAutomaticWindowAnimationsEnabled = false;
};
};
};
}

179
flake.lock generated Normal file
View File

@@ -0,0 +1,179 @@
{
"nodes": {
"brew-src": {
"flake": false,
"locked": {
"lastModified": 1749511373,
"narHash": "sha256-7u1TdHQaUCzzgf/n8T3bQosuYXyNBEPU/3WQQqozE5o=",
"owner": "Homebrew",
"repo": "brew",
"rev": "7b4ef99fed96966269ee35994407fa4c06097a4d",
"type": "github"
},
"original": {
"owner": "Homebrew",
"ref": "4.5.6",
"repo": "brew",
"type": "github"
}
},
"darwin": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1751313918,
"narHash": "sha256-HsJM3XLa43WpG+665aGEh8iS8AfEwOIQWk3Mke3e7nk=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "e04a388232d9a6ba56967ce5b53a8a6f713cdfcf",
"type": "github"
},
"original": {
"owner": "lnl7",
"repo": "nix-darwin",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1765835352,
"narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "a34fae9c08a15ad73f295041fec82323541400a9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1752093218,
"narHash": "sha256-+3rXu8ewcNDi65/2mKkdSGrivQs5zEZVp5aYszXC0d0=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "206ed3c71418b52e176f16f58805c96e84555320",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "master",
"repo": "home-manager",
"type": "github"
}
},
"nix-homebrew": {
"inputs": {
"brew-src": "brew-src"
},
"locked": {
"lastModified": 1749952250,
"narHash": "sha256-V2ix0knpdJXirQ+4pjbnggjdSALTsFWGIP/NDpaQkdU=",
"owner": "zhaofengli",
"repo": "nix-homebrew",
"rev": "37126f06f4890f019af3d7606ce5d30a457afcd0",
"type": "github"
},
"original": {
"owner": "zhaofengli",
"repo": "nix-homebrew",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1751949589,
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9b008d60392981ad674e04016d25619281550a9d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1766653575,
"narHash": "sha256-TPgxCS7+hWc4kPhzkU5dD2M5UuPhLuuaMNZ/IpwKQvI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3c1016e6acd16ad96053116d0d3043029c9e2649",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixvim": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs_2",
"systems": "systems"
},
"locked": {
"lastModified": 1766604046,
"narHash": "sha256-9Wvp2G/z0YYMn7oeN/E90pRtXJxQCo7EZrKKkNpwru4=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "48b23bdae0770d86e1d0cb8ed830a0cb58810333",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixvim",
"type": "github"
}
},
"root": {
"inputs": {
"darwin": "darwin",
"home-manager": "home-manager",
"nix-homebrew": "nix-homebrew",
"nixpkgs": "nixpkgs",
"nixvim": "nixvim"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

49
flake.nix Normal file
View File

@@ -0,0 +1,49 @@
{
description = "Nix Darwin Configuration";
inputs = {
# monorepo w/ recipes ("derivations")
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# manages configs
home-manager.url = "github:nix-community/home-manager/master";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
# system-level software and settings (macOS)
darwin.url = "github:lnl7/nix-darwin";
darwin.inputs.nixpkgs.follows = "nixpkgs";
# declarative homebrew management
nix-homebrew.url = "github:zhaofengli/nix-homebrew";
# declarative Neovim
nixvim.url = "github:nix-community/nixvim";
};
outputs =
{
self,
darwin,
nixpkgs,
home-manager,
nix-homebrew,
nixvim,
...
}@inputs:
let
# TODO: replace with your username
primaryUser = "phil";
in
{
# build darwin flake using:
# $ darwin-rebuild build --flake .#<name>
darwinConfigurations."cyper-mac" = darwin.lib.darwinSystem {
system = "x86_64-darwin"; # aarch64-darwin
modules = [
./darwin
./hosts/cyper-mac/configuration.nix
];
specialArgs = { inherit inputs self primaryUser; };
};
};
}

21
home/default.nix Normal file
View File

@@ -0,0 +1,21 @@
{ primaryUser, inputs, ... }:
{
imports = [
inputs.nixvim.homeManagerModules.nixvim
./neovim
./packages.nix
./git.nix
./shell.nix
];
home = {
username = primaryUser;
stateVersion = "25.11";
sessionVariables = {
# shared environment variables
};
# create .hushlogin file to suppress login messages
file.".hushlogin".text = "";
};
}

176
home/fastfetch.jsonc Normal file
View File

@@ -0,0 +1,176 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"type": "kitty-icat",
"source": "~/Pictures/Avatar/avatar_no_bg.png",
//"height": 15,
"width": 40,
"padding": {
"top": 0,
"left": 0
}
},
"modules": [
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Hardware──────────────────────┐"
},
{
"type": "host",
"key": " PC",
"keyColor": "green"
},
{
"type": "cpu",
"key": "│ ├",
"keyColor": "green"
},
{
"type": "gpu",
"key": "│ ├󰍛",
"keyColor": "green"
},
{
"type": "memory",
"key": "│ ├󰍛",
"keyColor": "green"
},
{
"type": "disk",
"key": "│ ├",
"keyColor": "green"
},
{
"type": "memory",
"key": "└ └󰍛",
"keyColor": "green"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Software──────────────────────┐"
},
{
"type": "os",
"key": " OS",
"keyColor": "yellow"
},
{
"type": "kernel",
"key": "│ ├",
"keyColor": "yellow"
},
{
"type": "bios",
"key": "│ ├",
"keyColor": "yellow"
},
{
"type": "packages",
"key": "│ ├󰏖",
"keyColor": "yellow"
},
{
"type": "shell",
"key": "└ └",
"keyColor": "yellow"
},
"break",
{
"type": "de",
"key": " DE",
"keyColor": "blue"
},
{
"type": "lm",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "wm",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "wmtheme",
"key": "│ ├󰉼",
"keyColor": "blue"
},
{
"type": "terminal",
"key": "└ └",
"keyColor": "blue"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌─────────────────Uptime / Age / DT──────────────────┐"
},
{
"type": "command",
"key": " OS Age ",
"keyColor": "magenta",
"text": "birth_install=$(stat -c %W /); current=$(date +%s); time_progression=$((current - birth_install)); days_difference=$((time_progression / 86400)); echo $days_difference days"
},
{
"type": "uptime",
"key": " Uptime ",
"keyColor": "magenta"
},
{
"type": "datetime",
"key": " DateTime ",
"keyColor": "magenta"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌─────────────────────Networking─────────────────────┐"
},
{
"type": "publicip",
"key": " Public ",
"keyColor": "magenta"
},
{
"type": "localip",
"key": " Local ",
"keyColor": "magenta"
},
{
"type": "dns",
"key": " DNS ",
"keyColor": "magenta"
},
{
"type": "netio",
"key": " Net I/O ",
"keyColor": "magenta"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
{
"type": "colors",
"paddingLeft": 2,
"symbol": "circle"
},
"break",
]
}

21
home/git.nix Normal file
View File

@@ -0,0 +1,21 @@
{ primaryUser, ... }:
{
programs.git = {
enable = true;
userName = "DerGrumpf"; # TODO replace
userEmail = "p.keier@beyerstedt-it.de"; # TODO replace
lfs.enable = true;
ignores = [ "**/.DS_STORE" ];
extraConfig = {
github = {
user = primaryUser;
};
init = {
defaultBranch = "main";
};
};
};
}

13
home/neovim/default.nix Normal file
View File

@@ -0,0 +1,13 @@
{...}:
{
imports = [
./treesitter.nix
];
programs.nixvim = {
enable = true;
defaultEditor = true;
};
}

View File

@@ -0,0 +1,5 @@
{
programs.nixvim.plugins.treesitter = {
enable = true;
};
}

31
home/packages.nix Normal file
View File

@@ -0,0 +1,31 @@
{ pkgs, ... }:
{
home = {
packages = with pkgs; [
# dev tools
curl
wget
vim
htop
tree
ripgrep
gh # Move to git
zoxide # Move to fish
# programming languages
#mise # node, deno, bun, rust, python, etc.
# misc
nil # move to nixvim
biome # move to nixvim
nixfmt-rfc-style # move to nixvim
yt-dlp
ffmpeg
ollama # move to nixvim
# fonts
nerd-fonts.fira-code
nerd-fonts.fira-mono
];
};
}

139
home/shell.nix Normal file
View File

@@ -0,0 +1,139 @@
_: {
programs.kitty = {
enable = true;
themeFile = "Catppuccin-Mocha";
settings = {
confirm_os_window_close = 0;
dynamic_background_opacity = true; # ctrl+shift+a>m/l
enable_audio_bell = false;
mouse_hide_wait = 3.0;
window_padding_width = 10;
background_opacity = 0.8;
background_blur = 5;
tab_bar_min_tabs = 1;
tab_bar_edge = "bottom";
tab_bar_style = "powerline"; # Should be changed to custom
tab_powerline_style = "slanted";
tab_title_template = "{title}{' :{}:'.format(num_windows) if num_windows > 1 else ''}";
symbol_map = let
mappings = [
"U+23FB-U+23FE"
"U+2B58"
"U+E200-U+E2A9"
"U+E0A0-U+E0A3"
"U+E0B0-U+E0BF"
"U+E0C0-U+E0C8"
"U+E0CC-U+E0CF"
"U+E0D0-U+E0D2"
"U+E0D4"
"U+E700-U+E7C5"
"U+F000-U+F2E0"
"U+2665"
"U+26A1"
"U+F400-U+F4A8"
"U+F67C"
"U+E000-U+E00A"
"U+F300-U+F313"
"U+E5FA-U+E62B"
];
in
(builtins.concatStringsSep "," mappings) + " Symbols Nerd Font";
};
};
programs.zsh = {
enable = true;
enableCompletion = true;
autosuggestion.enable = true;
syntaxHighlighting.enable = true;
shellAliases = {
la = "ls -la";
".." = "cd ..";
"nix-switch" = "sudo darwin-rebuild switch --flake ~/.config/nix";
};
};
programs.fish = {
enable = true;
shellAliases = {
la = "ls -la";
".." = "cd ..";
"nix-switch" = "sudo darwin-rebuild switch --flake ~/.config/nix";
};
};
programs.starship = {
enable = true;
settings = {
add_newline = true;
command_timeout = 500;
format = "$username$hostname $directory $git_branch$git_status\n$character ";
right_format = "$cmd_duration";
username = {
style_user = "bold #cba6f7";
style_root = "bold #f38ba8";
format = "[](bold #a6e3a1)[$user]($style)";
show_always = true;
};
hostname = {
style = "bold #74c7ec";
format = "[@](bold #fab387)[$hostname]($style)";
ssh_only = false;
};
directory = {
style = "bold #a6e3a1";
truncation_length = 0;
truncation_symbol = "";
format = "[ ](bold #f38ba8)[$path ]($style)";
};
git_branch = {
format = "[$branch]($style)";
style = "bold #f9e2af";
};
# Git status module settings
git_status = {
format = "[[(*$conflicted$untracked$modified$staged$renamed$deleted)](red) ($ahead_behind$stashed)]($style)";
style = "bold #a6e3a1";
conflicted = "";
untracked = "";
modified = "";
staged = "";
renamed = "";
deleted = "";
};
# Command duration module
cmd_duration = {
format = "[$duration]($style)";
style = "bold #cdd6f4";
min_time = 5000; # Only show if command takes longer than 5 seconds
};
# Character module (prompt symbol)
character = {
success_symbol = "[ ](bold #a6e3a1)";
error_symbol = "[ ](bold #f38ba8)";
};
nix_shell = {
format = "[$symbol$state( \($name\))]($style)";
symbol = "U+02744";
style = "bold #89dceb";
};
};
};
home.file.".config/fastfetch/config.jsonc".source = ./fastfetch.jsonc;
}

View File

@@ -0,0 +1,29 @@
{
pkgs,
primaryUser,
...
}:
{
networking.hostName = "cyper-mac";
# host-specific homebrew casks
#homebrew.casks = [
# "slack"
#];
# host-specific home-manager configuration
home-manager.users.${primaryUser} = {
home.packages = with pkgs; [
graphite-cli
];
programs = {
zsh = {
initContent = ''
# Source shell functions
source ${./shell-functions.sh}
'';
};
};
};
}

View File

@@ -0,0 +1,13 @@
# convenient shell functions
# quick navigation to git repo root
function cdroot() {
local root
root=$(git rev-parse --show-toplevel 2>/dev/null)
if [ $? -eq 0 ]; then
cd "$root"
else
echo "Not in a git repository"
return 1
fi
}