Skip to content

nickromney/n-dotfiles

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

146 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

n-dotfiles

An opinionated dotfiles setup designed to:

  • work on Mac OS X with brew
  • work on Ubuntu (particularly in dev containers and for GitHub Actions)
  • be extensible to add other package managers

Design considerations

  • I work on Mac OS X
  • I'm trying to move to use dev containers
  • I'm a DevOps Engineer by recent training, so like idempotent code

I looked at nix flakes but although I'm often tweaking my configuration, I don't need to set up whole new machines enough to warrant it. This blog from Julia Evans convinced me away from it.

Quick Start

Fresh macOS Installation

For a brand new Mac, use the bootstrap script:

# Create directory structure and clone
mkdir -p ~/Developer/personal
cd ~/Developer/personal
git clone https://github.com/nickromney/n-dotfiles.git
cd n-dotfiles

# Run bootstrap to install essential tools
./bootstrap.sh

# Preferred installation flow
make install           # Config-driven: brew, apt fallback, then mise runtimes
make personal stow     # Stow configurations
make personal configure # Apply macOS settings

Existing System

If you already have Homebrew and basic tools:

# Clone and enter directory
git clone https://github.com/nickromney/n-dotfiles.git ~/n-dotfiles
cd ~/n-dotfiles

# Install the default safe/base toolchain (common profile)
make install

# Install personal additions
make install personal

# Provision a work Mac
make work install

# Apply macOS tweaks (dock, defaults) for the active profile
make personal configure

# Or just install VSCode and extensions
make focus-vscode

Using the Makefile

The Makefile provides convenient targets for different configurations:

Combine a profile (common, personal, work, or all) with an action (install, update, stow, configure). Order does not matter, so make work install equals make install work.

# Profile + action examples
make install              # Safe base install (common profile default)
make install personal     # Install personal apps and CLIs
make work update          # Update tooling for work machines
make stow work            # Symlink configs for the work profile
make personal configure   # Apply macOS defaults (dock, keyboard, etc.)
make install all          # Install all profile bundles
make install PROFILE=work # Alternate syntax using PROFILE env var
make app-store install    # Optional Mac App Store apps (after "purchasing" once)

# Focus targets (specific tool categories)
make vscode               # VSCode and extensions
make neovim               # Neovim and plugins
make app-store            # Mac App Store apps (requires App Store login)

# VSCode for different editors
VSCODE_CLI=cursor make vscode  # Install extensions for Cursor

# Package manager updates (updates both the manager and all installed packages)
make brew update           # Update Homebrew and all brew/cask packages
make cargo update          # Update Rust toolchain and cargo binaries
make uv update             # Update uv package manager (use ./install.sh -u for uv tools)
make mas update            # Update Mac App Store applications

> **Note:** Mac App Store installs require you to sign in via the App Store app and click "Get" once per app before `make app-store install` (or any `mas install`) can succeed.

> **Note:** Some tools (`arkade`, `slicer`) install their binaries to `/usr/local/bin` which is
> root-owned on Apple Silicon Macs. Self-update commands for these tools (`arkade update`,
> `slicer update`) require `sudo` as a result. This is expected — the install scripts for both
> tools write to `/usr/local/bin` by default, and the root ownership is not a Homebrew concern
> on Apple Silicon (Homebrew uses `/opt/homebrew` instead).

Features

  • Automatically detects available package managers
  • Skips unavailable package managers without failing
  • Installs only tools that match available package managers
  • Uses GNU Stow for configuration management
  • Force mode (-f) to handle existing configurations

Usage

Package Installation and Configuration

# Preferred workflow
make install                 # safe base/common profile
make install personal        # personal additions (or: make install work)
make app-store install       # optional, run after clicking "Get" in App Store
make stow personal           # symlink dotfiles
make personal configure      # apply macOS settings

# Direct install.sh usage (legacy/CI)
./install.sh              # Install packages only
./install.sh -s           # Install packages and stow configurations
./install.sh -d -s        # Preview changes
./install.sh -s -f        # Force stow
./install.sh -u           # Update installed packages

Preferred Installer Stack

make install follows this order:

  1. Read selected _configs/*.yaml bundle(s)
  2. Generate plain-text manifests (Brewfile, arkade.tsv, metadata.json)
  3. Apply system-wide dependencies with native tools (brew bundle, arkade, manager-specific runners)
  4. Fall back to apt for brew package tools when brew is unavailable
  5. Install project runtimes via mise install from local mise.toml
# One-shot preferred install path
make install

# Optional helpers
make install-system    # system deps only (config-driven install.sh)
make runtime-install   # project runtimes only (mise.toml)
make install-dry-run   # preview system + runtime changes
make manifests-generate # inspect generated install manifests for the selected profile

macOS System Configuration

Light-touch macOS configuration management:

# Show current system settings
./_macos/macos.sh

# Apply personal configuration
./_macos/macos.sh personal.yaml

# Dry run to preview changes
./_macos/macos.sh -d personal.yaml

# Equivalent Makefile helper
make personal configure

See _macos/README.md for detailed macOS configuration options.

1Password Integration

This repository includes comprehensive 1Password integration for secure credential and configuration management.

SSH Configuration Management

The setup-ssh-from-1password.sh script manages SSH configuration with security by default:

Default (Safe) Mode

# Download base SSH config + per-profile fragment + public keys only
./setup-ssh-from-1password.sh

# Check what's available without downloading
./setup-ssh-from-1password.sh --dry-run

In safe mode:

  • Downloads base SSH config from 1Password (stored as Secure Note)
  • Downloads a per-profile SSH config fragment from 1Password
  • Downloads public keys only for reference
  • Private keys remain in 1Password
  • Uses 1Password SSH Agent for authentication

Unsafe Mode (When 1Password SSH Agent Isn't Available)

# Download private keys (requires explicit confirmation)
./setup-ssh-from-1password.sh --unsafe

Use unsafe mode when:

  • 1Password SSH Agent cannot be installed in your environment
  • You're using a restricted system without agent support
  • You need keys for backup/migration purposes

Git Configuration Management

The setup-gitconfig-from-1password.sh script manages work-specific Git configurations:

# Download work Git config from 1Password
./setup-gitconfig-from-1password.sh

# Check availability without downloading
./setup-gitconfig-from-1password.sh --dry-run

This allows you to:

  • Store work-specific Git config in 1Password
  • Automatically apply it to ~/Developer/work/.gitconfig_include
  • Keep work email and GitHub Enterprise settings secure
  • Use includeIf in main .gitconfig for automatic switching

AWS Credentials Helper

The aws/.aws/aws-1password script provides on-demand AWS credential fetching:

# Configure AWS CLI to use 1Password
aws configure set credential_process "$HOME/.aws/aws-1password --username default"

# For different profiles
aws configure set credential_process "$HOME/.aws/aws-1password --username tfcli" --profile terraform

This approach:

  • Never stores AWS credentials on disk
  • Fetches credentials from 1Password when needed
  • Works seamlessly with AWS CLI and SDKs
  • Supports multiple AWS accounts/profiles

Setting Up 1Password Items

SSH Keys

  1. Open 1Password and create new item → SSH Key
  2. Name it exactly as expected by the script:
    • personal_github_authentication
    • personal_github_signing
    • work_2024_client_1_aws
    • work_2025_client_1_github
    • work_2025_client_2_github
    • work_2025_client_2_gitea
    • work_2025_client_2_ado
  3. Paste your private key
  4. Save to the vault expected by the script for that key

SSH Config

  1. Create new item → Secure Note

  2. Name it: ~/.ssh/config

  3. Add your base SSH configuration, for example:

    Host *
      IdentityAgent "~/.1password/agent.sock"
    
    Include ~/.ssh/config.d/*.conf
    Include ~/.ssh/config.d/*/*.conf
  4. Save it in the vault selected by SSH_CONFIG_VAULT or VAULT

SSH Config Fragments

  1. Create new item → Secure Note
  2. Name it as one of:
    • ~/.ssh/config.d/personal.conf
    • ~/.ssh/config.d/work-2024-client-1.conf
    • ~/.ssh/config.d/work-2025-client-1.conf
    • ~/.ssh/config.d/work-2025-client-2.conf
  3. Add only the host stanzas for that profile
  4. Save it in the same vault as that profile's SSH keys

Git Config

  1. Create new item → Secure Note

  2. Name it: work .gitconfig_include

  3. Add your work-specific Git configuration:

    [url "github-work:OrgName/"]
      insteadOf = [email protected]:OrgName/
      insteadOf = https://github.com/OrgName/
    
    [url "git@ado-work-2025-client-2:v3/ORG/PROJECT/"]
      insteadOf = [email protected]:v3/ORG/PROJECT/
    
    [user]
      email = [email protected]
  4. Save to "Private" vault

AWS Credentials

  1. Create new item → API Credential (or custom item)
  2. Name it based on your mapping (e.g., AWSCredsUsernameDefault)
  3. Add fields:
    • ACCESS_KEY: Your AWS Access Key ID
    • SECRET_KEY: Your AWS Secret Access Key
  4. Save to "CLI" vault (or adjust in script)

Security Benefits

  • No secrets in version control: All sensitive data stays in 1Password
  • Encrypted at rest: 1Password handles all encryption
  • Audit trail: 1Password logs all access to credentials
  • Easy rotation: Update credentials in one place
  • Team sharing: Safely share vaults with team members
  • MFA protection: Additional security with 1Password's MFA

Package Manager Setup

Installing Rust/Cargo

The dotfiles manage PATH configuration for Rust/Cargo in zsh/.zshrc (lines 197-208). To prevent rustup from modifying your shell files during installation:

# Install Rust without modifying shell configuration
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --no-modify-path

The --no-modify-path flag prevents rustup from adding its own PATH configuration to your shell files, since the dotfiles already handle $HOME/.cargo/bin in the managed PATH configuration.

Alternatively, you can:

  • Set RUSTUP_MODIFY_PATH=false before running the installer
  • Choose "Customize installation" (option 2) and decline PATH modification

PATH Management

The ZSH configuration automatically adds tool directories to PATH if they exist:

  • $HOME/.local/bin - Local user binaries
  • $HOME/.cargo/bin - Rust/Cargo binaries
  • $HOME/.lmstudio/bin - LM Studio CLI
  • $HOME/.tfenv/bin - Terraform version manager

Each directory is only added if it exists, preventing errors on partial installations.

Shell Configuration

The ZSH configuration (in zsh/.zshrc) uses defensive programming - every tool is checked before use, ensuring it works across all environments (personal Mac, work Mac, fresh installs, dev containers).

FZF + Bat File Preview Helpers

Quick file finding with syntax-highlighted previews:

f          # Launch fzf fuzzy finder
bf         # Select file, open in bat pager
nf         # Select file, open in neovim
pf         # Select file, copy path to clipboard

All commands show bat-powered syntax highlighting with line numbers in the preview pane.

Conditional Features

The shell adapts based on installed tools:

  • Completions: kubectl, gh, and other CLI tools
  • Integrations: direnv, zoxide, starship, mise
  • Plugins: zsh-autosuggestions, zsh-syntax-highlighting (via Homebrew)
  • Aliases: Conditional git, navigation, and file listing shortcuts

See zsh/README.md for complete shell configuration documentation.

Configuration Structure

The _configs/ directory uses a layered approach:

_configs/
├── shared/           # Cross-platform tools
│   ├── shell.yaml        # Shell utilities (zsh, starship, etc.)
│   ├── git.yaml          # Git tools
│   ├── search.yaml       # Search tools (ripgrep, fzf, etc.)
│   ├── neovim.yaml       # Neovim configuration
│   ├── file-tools.yaml   # File management utilities
│   ├── data-tools.yaml   # Data processing tools
│   └── network.yaml      # Network utilities
├── host/             # Host-specific configurations
│   ├── common.yaml       # Tools for any Mac (Ghostty, VSCode, Obsidian, etc.)
│   ├── personal.yaml     # Personal additions
│   └── work.yaml         # Work-specific tools
└── focus/            # Development focus areas
    ├── vscode.yaml       # VSCode + 38 extensions
    ├── python.yaml       # Python development
    ├── typescript.yaml   # TypeScript/Node development
    ├── rust.yaml         # Rust development
    ├── kubernetes.yaml   # Kubernetes tools
    └── container-base.yaml  # Base container tools

## All Available Configurations

### Tool Configurations (_configs/)

| Configuration | Type | Description | Use Case |
|--------------|------|-------------|----------|
| **focus/ai** | Focus | AI/ML tools (ollama, etc.) | AI development |
| **focus/container-base** | Focus | Podman and container tools | Container development |
| **focus/kubernetes** | Focus | K8s tools (kubectl, k9s, helm) | Kubernetes management |
| **focus/neovim** | Focus | Extended Neovim plugins | Advanced vim setup |
| **focus/python** | Focus | Python dev tools (uv, ruff) | Python development with Rust-based tooling |
| **focus/rust** | Focus | Rust toolchain and utilities | Rust development |
| **focus/typescript** | Focus | Node.js, TypeScript, Biome | JavaScript/TypeScript dev with Rust-based linting |
| **focus/vscode** | Focus | VSCode + 38 extensions | Full VSCode development |
| **host/common** | Host | Common Mac apps (Ghostty, VSCode, Obsidian) | Standard Mac productivity |
| **host/personal** | Host | Personal additions (games, media apps) | Personal Mac extras |
| **host/work** | Host | Work-specific tools | Work Mac requirements |
| **shared/data-tools** | Shared | Data processing (jq, yq, csvlens) | JSON/YAML/CSV manipulation |
| **shared/file-tools** | Shared | File management (eza, tree, etc.) | Directory navigation |
| **shared/git** | Shared | Git tools (delta, lazygit, gh CLI) | Version control essentials |
| **shared/neovim** | Shared | Neovim and plugins | Text editor setup |
| **shared/network** | Shared | Network utilities (httpie, curlie, etc.) | API testing and network debugging |
| **shared/search** | Shared | Search tools (ripgrep, fzf, fd, etc.) | File and text searching |
| **shared/shell** | Shared | Shell utilities (zsh, starship, atuin, etc.) | Essential for all setups |

### macOS System Settings (_macos/)

| Configuration | Description | Key Settings |
|--------------|-------------|--------------|
| **personal.yaml** | Personal Mac settings | Natural scroll, dock apps, keyboard shortcuts |
| **work.yaml** | Work Mac settings | Corporate defaults, security settings |

### Makefile Targets (Convenient Combinations)

| Target | Includes | Purpose |
|--------|----------|---------|
| **make install** | All shared/ + host/common | Safe base install (default profile) |
| **make common install** | All shared/ + host/common | Essential Mac setup |
| **make ai** | focus/ai | AI/ML development tools |
| **make app-store** | focus/app-store | Optional Mac App Store apps (requires prior purchase) |
| **make container-base** | focus/container-base | Podman and container tools |
| **make containers** | focus/containers | Podman container tools |
| **make hardware-home** | focus/hardware-home | Optional home hardware + chargeable apps |
| **make kubernetes** | focus/kubernetes | Kubernetes toolchain |
| **make neovim** | focus/neovim | Enhanced Neovim |
| **make python** | focus/python | Python development |
| **make rust** | focus/rust | Rust development |
| **make typescript** | focus/typescript | TypeScript/Node.js |
| **make vscode** | focus/vscode | VSCode + extensions |
| **make personal install** | Shared + host/common + host/personal + focus/containers + focus/kubernetes + focus/vscode + focus/cloud + focus/ai + focus/typescript | Base personal Mac (no hardware/chargeable extras) |
| **make work install** | Shared + host/common + host/work + focus/containers + focus/kubernetes + focus/vscode | Work laptop tooling |
| **make all install** | Shared + host/common + host/personal + host/work + focus/containers + focus/kubernetes + focus/vscode + focus/hardware-home + host/manual-check | Full superset |
| **make install hardware-home** | focus/hardware-home | Optional home hardware and chargeable apps |
| **make work-setup** | Runs setup-work-mac.sh | Legacy scripted work setup |

### Quick Setup Guide

For a new personal Mac (like yours):

```bash
# 1. Install base tools and personal apps
make personal install

# 2. Optional: install Mac App Store apps once you're signed in + clicked "Get"
make app-store install

# 3. Create configuration symlinks
make personal stow

# 4. Apply macOS system settings (mouse scroll, dock, etc.)
make personal configure

# Or stow + configure together:
make personal stow && make personal configure

Package Manager Examples

Tap a Homebrew repository

noahgorstein/tap:
  manager: brew
  type: tap
  check_command: "brew tap | grep -q '^noahgorstein/tap$'"
  install_args: []

Install a Homebrew cask application

kitty:
  manager: brew
  type: cask
  check_command: 'brew list --cask kitty >/dev/null 2>&1 || [ -d "/Applications/kitty.app" ] || which kitty >/dev/null 2>&1'
  install_args: []

Install a Homebrew package

bat:
  manager: brew
  type: package
  check_command: "bat --version"
  install_args: []

Install a Python tool via uv

posting:
  manager: uv
  type: tool
  check_command: "which posting >/dev/null 2>&1"
  install_args: ["--python", "3.12"]

Install VSCode extensions

prettier-vscode:
  manager: code
  type: extension
  extension_id: esbenp.prettier-vscode
  check_command: "code --list-extensions | grep -q esbenp.prettier-vscode"
  description: "Code formatter"
  documentation_url: "https://prettier.io/"
  category: vscode-extension

Each tool entry requires:

manager: Package manager to use (brew/uv/cargo/apt/code/manual/mas)
type: Installation method specific to the manager
check_command: Command to verify installation
install_args: Additional installation arguments (optional)
skip_update: Set to true to opt the tool out of `install.sh -u` updates (optional)
extension_id: Required for VSCode extensions (manager: code)

VSCode Extension Management

The code package manager supports VSCode and compatible editors:

# Default: uses 'code' CLI
make focus-vscode

# For Cursor editor
VSCODE_CLI=cursor make focus-vscode

# For VSCodium
VSCODE_CLI=vscodium make focus-vscode

Directory Structure

.
├── Brewfile        # Preferred personal bundle (generated from _configs)
├── Brewfile.*      # Profile/OS variants (work, common, posix)
├── install.sh      # Thin entrypoint for the manifest-driven installer
├── Makefile        # Convenient targets for common operations
├── _configs/       # Modular configuration files
│   ├── shared/     # Cross-platform tools
│   ├── host/       # Host-specific configurations
│   └── focus/      # Development focus areas
├── _macos/         # macOS system configuration
│   ├── macos.sh    # macOS settings script
│   └── *.yaml      # Settings profiles
├── _test/          # Comprehensive test suite
│   ├── install.bats     # Installation tests
│   ├── macos.bats       # macOS tests
│   ├── makefile.bats    # Makefile tests
│   └── run_tests.sh     # Test runner
└── */              # Stow directories for dotfiles
    ├── aerospace/  # Tiling window manager
    ├── git/        # Git configuration
    ├── nvim/       # Neovim config
    ├── tmux/       # Tmux config
    ├── vscode/     # VSCode settings
    ├── zsh/        # Zsh configuration
    └── ...         # Other tool configs

Testing

The repository includes a comprehensive test suite using BATS (Bash Automated Testing System):

# Install BATS (required for testing)
brew install bats-core  # macOS
sudo apt-get install bats  # Ubuntu/Debian

# Run all tests
./_test/run_tests.sh

# Run tests with specific filter
cd _test && bats install.bats --filter "install_tool"

Ubuntu 24.04 Lima smoke test (non-mac/POSIX path)

This repository now includes a Lima-based Ubuntu 24.04 smoke test for non-mac flows. It validates Linux Homebrew setup and runs the POSIX install/test path without applying macOS settings.

By default it uses the composed non-mac personal superset bundle at: _configs/host/personal-posix.list

# Start/create test VM
make test-lima-up

# Run POSIX/non-mac smoke tests in the VM
make test-lima-run

# One-shot: start VM (if needed) + run smoke tests
make test-lima

# Optional lifecycle helpers
make test-lima-status
make test-lima-down
make test-lima-destroy

# Optional: make shellcheck failures blocking for this VM run
STRICT_SHELLCHECK=true make test-lima-run

# Optional: override the config set for a run
POSIX_CONFIG_FILES="shared/shell shared/git focus/kubernetes" make test-lima-run

The test suite includes:

  • Unit tests for all utility functions
  • Integration tests for package manager detection
  • Installation tests for each package manager type
  • macOS configuration tests with defaults mocking
  • Mocking framework to simulate external commands
  • 65+ comprehensive tests covering all major functionality

Handling errexit in Shell Scripts

The install.sh script uses set -euo pipefail for strict error handling, which is a best practice for production scripts. However, this can cause issues in certain scenarios:

  1. Testing: When functions are sourced in test environments, commands that normally fail (like checking for non-existent commands) will cause the entire function to exit.

  2. Information Gathering: Functions that check system state need to handle failures gracefully without exiting.

The script handles this by temporarily disabling errexit in functions that need to tolerate failures:

get_available_managers() {
  # Save current errexit setting and disable it
  local old_errexit
  old_errexit=$(set +o | grep errexit)
  set +e

  # ... function body that may have failing commands ...

  # Restore errexit setting before returning
  eval "$old_errexit"
  return 0
}

This pattern ensures:

  • The function can complete even if some commands fail
  • The original shell options are preserved
  • The script maintains strict error handling elsewhere

Inspiration

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors