A modern, OS-agnostic CLI tool for automating developer machine setup using YAML-based configuration.
- Cross-platform: Works on macOS and Linux (Ubuntu, Arch, Fedora)
- Declarative configuration: Define your environment in YAML files
- Idempotent: Safe to run multiple times - skips what's already done
- Dotfile templating: Use Go templates with OS-specific conditionals
- System settings: Configure macOS defaults and GNOME gsettings
- Multiple package managers: Homebrew, APT, Pacman, DNF
- Dry-run mode: Preview changes before applying
curl -fsSL https://raw.githubusercontent.com/codoworks/devstrap/main/install.sh | bashgo install github.com/codoworks/devstrap@latest
git clone https://github.com/codoworks/devstrap.git cd devstrap go build -o devstrap .
# 1. Initialize configuration devstrap init # 2. Edit your config files vim ~/.config/devstrap/tools.yaml # 3. Preview what will happen devstrap apply --dry-run # 4. Apply configuration devstrap apply
Initialize a new configuration directory with sample files.
devstrap init # Create config in ~/.config/devstrap devstrap init --path ~/dotfiles # Create config in custom location devstrap init --force # Overwrite existing configuration
Flags:
| Flag | Short | Description |
|---|---|---|
--path |
-p |
Path to create config (default: ~/.config/devstrap) |
--force |
-f |
Overwrite existing configuration |
Apply configuration to set up your development environment.
devstrap apply # Apply from default location devstrap apply --dry-run # Preview changes without executing devstrap apply --verbose # Show detailed output devstrap apply --config ~/dotfiles # Use custom config location
Flags:
| Flag | Short | Description |
|---|---|---|
--config |
-c |
Path to config file or directory (default: ~/.config/devstrap) |
--dry-run |
-n |
Show what would be done without making changes |
--verbose |
-v |
Show detailed output |
Show what's installed vs what's configured.
devstrap status # Check status from default config devstrap status --config ~/dotfiles
Flags:
| Flag | Short | Description |
|---|---|---|
--config |
-c |
Path to config file or directory |
Display detected system information and available package managers.
devstrap os
Example output:
System Information
──────────────────
OS: macOS
Architecture: arm64
Hostname: macbook
Home: /Users/dexter
Shell: /bin/zsh
Config: /Users/dexter/.config/devstrap
Package Managers
──────────────────
brew (primary)
Devstrap uses YAML configuration files. By default, these live in ~/.config/devstrap/.
~/.config/devstrap/
├── devstrap.yaml # Main config (includes other files)
├── tools.yaml # Tools to install
├── directories.yaml # Directories to create
├── profiles.yaml # Dotfiles to manage
├── configs.yaml # System settings (macOS defaults, gsettings)
└── dotfiles/ # Dotfile templates
├── .gitconfig.tmpl
└── .aliases
version: "1" include: - tools.yaml - directories.yaml - profiles.yaml - configs.yaml vars: git_name: "Your Name" git_email: "[email protected]" editor: "vim"
tools: # Simple - uses native package manager - name: git - name: curl - name: jq # Specify which package manager to use - name: go platform: brew package: brew: golang apt: golang-go pacman: go # With OS-specific package names - name: fd package: macos: fd ubuntu: fd-find arch: fd # Platform with sub-tools (e.g., Node.js with npm packages) - name: node method: script script: | export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" nvm install 20 check: "node --version" tools: - name: typescript install: "npm install -g typescript" check: "tsc --version" - name: yarn install: "npm install -g yarn" check: "yarn --version" # macOS cask application - name: visual-studio-code method: cask when: os: [macos] # Homebrew tap - name: heroku platform: brew method: tap options: tap: heroku/brew # Custom script installation - name: nvm method: script script: | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash check: "[ -d ~/.nvm ]"
Tool fields:
| Field | Description |
|---|---|
name |
Package name (required) |
platform |
Package manager to use: brew, apt, pacman, dnf |
package |
OS/platform-specific package name overrides |
method |
Install method: native, cask, tap, script |
options |
Platform-specific options (e.g., tap: heroku/brew for brew taps) |
script |
Custom install script (for method: script) |
check |
Command to verify installation |
post_install |
Commands to run after installation |
when.os |
Only install on specified OS: macos, ubuntu, arch, fedora, linux |
tools |
Sub-tools that depend on this tool (see below) |
Sub-tool fields:
| Field | Description |
|---|---|
name |
Sub-tool name (required) |
install |
Installation command (required) |
check |
Command to verify installation (optional) |
Sub-tools are displayed indented under their parent tool in devstrap status:
Tools:
[+] node
[+] typescript
[+] yarn
[-] eslint
directories: - path: ~/Developer env: DEV_HOME - path: ~/bin add_to_path: true - path: ~/Developer/projects - path: ~/.local/share
Directory fields:
| Field | Description |
|---|---|
path |
Directory path (required, supports ~) |
env |
Environment variable to set to this path |
add_to_path |
Add directory to PATH |
profiles: # Template with variable substitution - name: gitconfig source: ~/.config/devstrap/dotfiles/.gitconfig.tmpl target: ~/.gitconfig action: template # Symlink (changes to source reflect immediately) - name: aliases source: ~/.config/devstrap/dotfiles/.aliases target: ~/.aliases action: symlink # Copy (one-time copy) - name: ssh-config source: ~/.config/devstrap/dotfiles/ssh-config target: ~/.ssh/config action: copy
Profile actions:
| Action | Description |
|---|---|
symlink |
Create symbolic link to source |
copy |
Copy source to target |
template |
Process as Go template, then copy |
Manage OS-level settings like macOS defaults and GNOME gsettings.
configs: # macOS Dock settings - name: dock-autohide domain: com.apple.dock key: autohide value: true type: bool restart: ["killall Dock"] when: os: [macos] description: "Enable Dock auto-hide" # macOS keyboard settings - name: key-repeat-rate domain: NSGlobalDomain key: KeyRepeat value: 2 type: int when: os: [macos] description: "Fast key repeat" # GNOME desktop settings (Linux) - name: gnome-dark-mode backend: gsettings domain: org.gnome.desktop.interface key: color-scheme value: "'prefer-dark'" when: os: [linux] description: "Enable GNOME dark mode"
Config fields:
| Field | Description |
|---|---|
name |
Display name for the setting (required) |
backend |
Config backend: macosDefaults, gsettings (auto-detected if empty) |
domain |
Domain/schema (e.g., com.apple.dock, org.gnome.desktop.interface) |
key |
Configuration key (e.g., autohide, color-scheme) |
value |
Value to set (bool, int, float, string) |
type |
Value type: bool, int, float, string (inferred if empty) |
restart |
Commands to run after setting (e.g., ["killall Dock"]) |
when.os |
Only apply on specified OS: macos, linux |
description |
Human-readable description |
Dotfiles with action: template support Go's text/template syntax.
| Variable | Description |
|---|---|
.OS |
Operating system: macos, linux |
.Arch |
Architecture: amd64, arm64 |
.Home |
Home directory path |
.Username |
Current username |
.Hostname |
Machine hostname |
Plus all variables defined in vars: section of your config.
String functions: lower, upper, title, trimSpace, replace, contains, hasPrefix, hasSuffix, split, join
Path functions: joinPath, dirName, baseName, extName, cleanPath
Environment: env, envOr, expandEnv
Conditionals: default, coalesce, isSet
# ~/.config/devstrap/dotfiles/.gitconfig.tmpl
[user]
name = {{.git_name}}
email = {{.git_email}}
[core]
editor = {{.editor}}
{{- if eq .OS "macos"}}
ignorecase = false
[credential]
helper = osxkeychain
{{- end}}
[alias]
co = checkout
br = branch
st = status
lg = log --oneline --graph
Here's a full configuration for a typical development setup:
devstrap.yaml:
version: "1" include: - tools.yaml - directories.yaml - profiles.yaml - configs.yaml vars: git_name: "Jane Developer" git_email: "[email protected]" editor: "code --wait" github_user: "janedev"
tools.yaml:
tools: # Essentials - name: git - name: curl - name: wget - name: jq - name: ripgrep - name: fd package: ubuntu: fd-find - name: fzf - name: tmux - name: neovim package: macos: neovim ubuntu: neovim arch: neovim fedora: neovim # Development - name: go post_install: - go install golang.org/x/tools/gopls@latest - name: node check: "node --version" # macOS apps - name: iterm2 method: cask when: os: [macos] - name: docker method: cask when: os: [macos]
directories.yaml:
directories: - path: ~/Developer - path: ~/Developer/personal - path: ~/Developer/work - path: ~/bin add_to_path: true
profiles.yaml:
profiles: - name: gitconfig source: ~/.config/devstrap/dotfiles/.gitconfig.tmpl target: ~/.gitconfig action: template - name: zshrc source: ~/.config/devstrap/dotfiles/.zshrc target: ~/.zshrc action: symlink - name: tmux source: ~/.config/devstrap/dotfiles/.tmux.conf target: ~/.tmux.conf action: symlink
configs.yaml:
configs: - name: dock-autohide domain: com.apple.dock key: autohide value: true restart: ["killall Dock"] when: os: [macos] - name: finder-show-extensions domain: NSGlobalDomain key: AppleShowAllExtensions value: true restart: ["killall Finder"] when: os: [macos]
| Package Manager | Platforms | Notes |
|---|---|---|
| Homebrew | macOS, Linux | Also supports casks on macOS |
| APT | Debian, Ubuntu | |
| Pacman | Arch Linux | |
| DNF | Fedora, RHEL |
MIT License - See LICENSE for details.