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@latestgit 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 applyInitialize 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 configurationFlags:
| 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 locationFlags:
| 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 ~/dotfilesFlags:
| Flag | Short | Description |
|---|---|---|
--config |
-c |
Path to config file or directory |
Display detected system information and available package managers.
devstrap osExample 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: "you@example.com"
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/shareDirectory 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: copyProfile 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: "jane@example.com"
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: trueprofiles.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: symlinkconfigs.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.