Cross-platform dotfiles automation using A-frame architecture with DRY principles and nullable safety patterns.
git clone https://github.com/amamel/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
./installThe installer will detect your OS and guide you through an interactive setup.
- macOS 10.15+ (Intel and Apple Silicon) - Full support
- Linux (Ubuntu, Debian, elementary OS, and other Ubuntu/Debian derivatives) - Full support with APT and Snap package management
- Main configuration (
install.conf.yaml) handles shared logic - OS-specific modules (
scripts/macos/,scripts/linux/) provide platform customizations - Shared functions (
lib/) eliminate code duplication
All operations handle missing files, commands, and environment variables gracefully. Scripts continue execution even when optional components fail.
- Shared functions in
lib/utils.shandlib/logger.sh - Universal package manager interface (
scripts/common/package_manager.sh) - Centralized logging and validation
Beyond dotbot's built-in link/create/shell/clean directives, install.conf.yaml uses three vendored plugins (plugins/, each a git submodule):
- dotbot-conditional - real
if:/then:/else:gating for any directive (dotbot's ownshell:/create:have no nativeif:support; onlylink:does) - dotbot-flatpak - installs Flatpak packages on Linux
- dotbot-firefox - symlinks a managed
user.jsinto every detected Firefox profile
dotfiles/
├── install # Main installer script
├── install.conf.yaml # Dotbot configuration
├── lib/ # Shared shell libraries
│ ├── utils.sh # DRY helper functions
│ └── logger.sh # Centralized logging
├── plugins/ # Dotbot plugins (git submodules)
│ ├── dotbot-conditional/
│ ├── dotbot-flatpak/
│ └── dotbot-firefox/
├── configs/ # Configuration files
│ ├── shell/
│ │ ├── zsh/ # zsh config (+ macOS/Linux variants)
│ │ └── bash/ # bash config
│ ├── editors/
│ │ ├── nvim/
│ │ ├── vim/
│ │ └── vscode/ # settings, keybindings, extensions.json, tasks.json
│ ├── git/ # gitconfig + empty gitconfig.local/gitignore templates
│ ├── terminal/ # tmux, alacritty, iTerm2
│ ├── development/ # rbenv default-gems
│ └── tools/ # firefox, mackup, nano, neofetch, yt-dlp
├── scripts/ # Installation scripts
│ ├── common/ # Shared/cross-platform setup scripts
│ ├── macos/ # macOS-specific scripts
│ └── linux/ # Linux-specific scripts
├── packages/ # Package definitions
│ ├── macos/
│ │ ├── Brewfile # All brews/casks — select interactively at install time
│ │ └── mas.yaml
│ ├── linux/
│ │ ├── apt.yaml
│ │ └── snap.yaml
│ ├── common.yaml
│ └── node.yaml
└── .github/workflows/ # CI: shellcheck + YAML validation, secrets scan
The zsh configuration uses A-frame architecture:
~/.zshrc- Main frame with conditional OS loadingzshrc.macos- macOS-specific (Antidote plugin manager)zshrc.linux- Linux-specific (Oh-My-Zsh)
Control behavior through environment variables:
# Package installation
ENABLE_HOMEBREW_INSTALL=true
ENABLE_ESSENTIAL_TOOLS=true
ENABLE_BREWFILE=true
# SSH setup
SSH_KEY_TYPE=ed25519
AUTO_GENERATE_KEY=falseCreate local customization files that won't be tracked:
~/.zshenv.local,~/.zshenv.local.macos,~/.zshenv.local.linux- Environment overrides~/.zshrc.local,~/.zshrc.local.macos,~/.zshrc.local.linux- Shell customizations~/.bashrc.local,~/.bash_profile.local- Bash equivalents~/.gitconfig.local- Real git identity (configs/git/gitconfig.localships empty on purpose — never commit real name/email here)~/.gitignore- Personal global ignore patterns (configs/git/gitignoreships empty on purpose).env- Environment variable overrides (copy from.env.template)
Copy .env.template to .env in the root directory to customize behavior:
cp .env.template .env
# Edit .env with your preferencesAvailable configuration options:
- Package Installation: Control Homebrew, APT, Snap, and essential tools
- Interactive UI:
BREW_INTERACTIVEandMACOS_DEFAULTS_INTERACTIVE— set tofalsefor non-interactive installs - SSH Setup: Key type (ed25519/rsa/ecdsa), auto-generation
- Node.js: Version manager (nvm/n/system), version,
NVM_VERSION - Ruby: Version specification
- Logging: Enable/disable debug, colors, file logging
- Homebrew for command-line tools (auto-installed if missing)
- Homebrew Cask for GUI applications
- Mac App Store via
mas(YAML-based configuration) - Brewfile: Single combined file — an interactive
gumselector lets you choose exactly which packages to install - Formulas from third-party taps (e.g.
bjarneo/cliamp/cliamp) must use their fully-qualified name in the Brewfile — Homebrew Bundle auto-taps from that, a bare formula name won't resolve
- APT for system packages (Ubuntu/Debian and derivatives)
- Snap for GUI applications (optional, disabled by default)
- Flatpak for GUI applications, via the
dotbot-flatpakplugin (only runs when Flatpak is already present) - YAML-based package definitions:
packages/linux/apt.yaml- APT packages by profilepackages/linux/snap.yaml- Snap packages by profile
packages/common.yaml- Universal packages (git, curl, etc.)packages/node.yaml- NPM global packages by profile
Package lists are fully defined in YAML files under the packages/ directory, organized by OS and profile.
- Shell: zsh with plugins (Antidote on macOS, Oh-My-Zsh on Linux)
- Editors:
- Neovim with modern Lua configuration and Lazy.nvim plugin manager
- Vim with sensible defaults
- VS Code Insiders — settings/keybindings/tasks symlinked per-OS, plus
extensions.json(installed automatically via the CLI on setup)
- Browsers:
- Firefox —
user.js(telemetry/studies/Pocket/sponsored content/AI features off, plus other settings pulled from the real profile) symlinked into every profile viadotbot-firefox;extensions.jsonis a reference manifest of installed extensions (name + AMO link) — Firefox has no unprivileged auto-install mechanism, so this is documentation for manual reinstall viaabout:addons, not automation
- Firefox —
- Terminal:
- tmux with TPM plugins (resurrect, continuum, yank, etc.)
- iTerm2 color schemes (macOS)
- Alacritty configuration
- Version Control: Git with global ignore patterns and local overrides
- Languages:
- Go (via Homebrew/APT)
- Rust (via
rustup, not the OS package manager — seescripts/common/setup_rust.sh) - Node.js (via nvm, n, or system)
- Python 3 (system or pyenv)
- Ruby (via rbenv with auto-install latest stable)
- Media: cliamp, a terminal music player (Linux install is a checksum-verified upstream script, not an apt package — see
scripts/linux/setup_cliamp.sh)
- Alfred - Application launcher
- Rectangle - Window management
- Hammerspoon - Automation
- Hazel - File organization
- Mackup - Application settings sync
- Cyberduck - FTP/SFTP client
.github/workflows/ runs on every push/PR to main:
- code-quality.yml - shellcheck across all scripts, YAML syntax validation
- code-security.yml - secrets scan via gitleaks
utils.sh- DRY utilities library- Environment loading with multiple fallbacks
- Package management helpers (
install_package,install_packages) - Service management (systemctl/brew services)
- APT repository management (prefers
UBUNTU_CODENAMEfrom/etc/os-releaseso third-party repos resolve correctly on Ubuntu derivatives like elementary OS) - Internet and command availability checks
logger.sh- Centralized logging library- Color-coded output (info, success, warning, error, debug)
- Optional file logging support
- Configurable via environment variables
detect_os.sh- Comprehensive OS, architecture, and environment detection- Detects: macOS version, Linux distro, architecture (x86_64/arm64)
- Environment: Headless, CI, container, SSH session detection
- Exports variables to
/tmp/dotfiles_envfor use across all scripts
validate_installer.sh- Pre-installation validation- Checks: Git repo, required files, YAML syntax, permissions
- OS-specific validation
- Dotbot dry-run test
package_manager.sh- Universal repository setup (NodeSource, Docker, etc.) on top of the detected package manager
setup_shell.sh- Shell environment configuration (bash/zsh detection, Antidote/Oh-My-Zsh, bin directories)setup_ssh.sh- SSH key generation, config, and OS-specific agent setupsetup_node.sh- Node.js via nvm/n/system, NPM packages fromnode.yamlsetup_ruby.sh- rbenv install, latest stable Ruby, default gemssetup_rust.sh- rustup install (--no-modify-path, since shell rc files already handle~/.cargo/env),rustfmt/clippycomponentssetup_vscode_extensions.sh- installs any missing extensions fromconfigs/editors/vscode/extensions.jsonvia thecode/code-insidersCLI
macOS (scripts/macos/)
install_packages.sh- Full Homebrew + Cask + MAS installationbrew_select.sh- Interactivegumpackage selector for the Brewfilesetup_macos_defaults.sh- Extensive macOS system preferences, interactivegumselector
Linux (scripts/linux/)
install_packages.sh- APT package management, YAML-based, profile-specificapt_select.sh- Interactivegumpackage selector forapt.yaml/snap.yamlsetup_snap.sh- Snap bootstrap (headless-skipped)setup_cliamp.sh- ALSA bridge package (PipeWire/PulseAudio) + upstream checksum-verified installer for cliamp
./install
# Prompts for profile selection (workstation/server/home)
# Auto-detects OS./install --os macos # Force macOS detection
./install --os linux # Force Linux detectionmake install # Install dotfiles
make install-workstation # Install with the workstation profile
make install-linux # Install forcing Linux detection
make update # Update dotbot and reinstall
make validate # Run comprehensive validation (validate_installer.sh)
make test # Run basic configuration tests
make doctor # Full health check
make info # Show system information# During install, choose "V" at the confirmation prompt to validate without installing
make validate # Comprehensive validation
make test # YAML syntax and script checks
make lint # ShellCheck and YAML lint
make dry-run # Show what would be linkedmake archive # Create backup archive of the whole dotfiles directory
make mackup-backup # Backup app settings with Mackup
make mackup-restore # Restore app settings
make clean # Clean old logs
make logs # View recent logsmacOS packages - Edit packages/macos/Brewfile:
brew "new-package"
cask "new-app"Linux packages - Edit packages/linux/apt.yaml:
development:
- new-package- Place config files in
configs/directory - Add symlink in
install.conf.yaml:
~/.config/newapp/config:
path: configs/newapp/configlink: supports a native if: for simple per-OS symlinks:
~/.config/app/config:
if: '[ "$OS" = "macos" ]'
path: configs/app/config.macosFor anything beyond link: (e.g. a shell:/create: step that also needs to be conditional), wrap it in the conditional: plugin instead:
- conditional:
if: '[ "$OS" = "linux" ] && [ "$IS_HEADLESS" = "false" ]'
then:
- create:
- ~/some/dir
- shell:
- command: scripts/linux/some_setup.sh- Choose "V" at the install prompt to validate, or run
make validate - Check OS support and requirements
- Ensure internet connectivity
- Verify repository access
# Fix SSH permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/id_*.pub
# Fix Homebrew permissions (macOS)
sudo chown -R $(whoami) /opt/homebrew# Source environment manually
source /tmp/dotfiles_env
# Check detection script output
./scripts/common/detect_os.sh# Update package managers
brew update # macOS
sudo apt update # Linux
# Clear package caches
brew cleanup # macOS
sudo apt autoclean # LinuxAfter installation, you'll have access to powerful aliases and functions defined in:
~/.aliases- Common command shortcuts~/.functions- Utility functions
# Navigation
.. # cd ..
... # cd ../..
mkcd # Create directory and cd into it
# File operations (with modern tool fallbacks)
ls # Uses eza if available
cat # Uses bat if available
find # Uses fd if available
grep # Uses ripgrep (rg) if available
# Git shortcuts
g # git
gst # git status
gac # git add . && commit (function)
gacp # git add . && commit && push (function)
# Docker
d # docker
dc # docker-compose
dps # docker ps with formatting
# System
update # OS-aware system update (macOS: brew + softwareupdate, Linux: apt + snap)
sysinfo # System information (uses neofetch if available)dotfiles <command> # Quick dotfiles management
- update # Update dotfiles
- install # Reinstall
- status # Git status
- info # System info
- logs # View logs
rb <command> # Ruby version management
- list # List available Ruby versions
- install [version] # Install Ruby version
- use [version] # Set global Ruby version
mackup_backup # Backup app settings
mackup_restore # Restore app settings
mas_list # List installed Mac App Store apps
mas_search <app> # Search Mac App Store
bookmark <command> # Directory bookmarks
- save <name> # Bookmark current directory
- go <name> # Navigate to bookmark
- list # List all bookmarks
weather [city] # Get weather information
genpass [length] # Generate secure password
serve [port] # Quick HTTP server (default: 8000)
extract <file> # Smart archive extraction- Idempotent - Safe to run multiple times (verified end-to-end: a second run produces zero changes)
- Non-destructive - Won't overwrite existing configurations without confirmation
- Validation - Pre-installation environment checks
- Graceful degradation - Continues even when components fail
- Nullable safety - All operations handle missing files/commands gracefully
- Environment isolation - Clears contaminated OS variables before installation
- CI-checked - Every push/PR is shellchecked, YAML-validated, and secrets-scanned
See CHANGELOG.md for recent changes.
MIT License - see LICENSE file for details.