- macOS (Apple Silicon or Intel)
- Xcode Command Line Tools:
xcode-select --installFollow these steps in order on a new machine:
# 1. Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 2. Install Ansible
brew install ansible
# 3. Clone this repo
git clone https://github.com/your-user/devops-daily-driver.git
cd devops-daily-driver
# 4. Install Ansible Galaxy collections
ansible-galaxy collection install -r requirements.yml
# 5. Configure your machine variables
cp host_vars/localhost.yml host_vars/localhost.yml.bak
# Edit host_vars/localhost.yml with your settings
# 6. Run the playbook
ansible-playbook daily-driver.ymlThe playbook prompts once for a sudo password. It is only used by casks that ship
their own installers (currently the Logitech ones) — press enter to skip it and
those tasks will fail while everything else proceeds. No task uses become, so
-K is not needed.
Preview a run without changing anything:
ansible-playbook daily-driver.yml --checkdevops-daily-driver/
├── .github/workflows/ # CI: yamllint, ansible-lint, syntax-check
├── .ansible-lint # Rule config, with deferred rules documented
├── .yamllint # Line length, ignores .ansible/
├── ansible.cfg # Inventory path, deprecation_warnings
├── inventory.yml # Explicit localhost (avoids Ansible warnings)
├── daily-driver.yml # Main playbook
├── requirements.yml # Galaxy collections
├── host_vars/
│ └── localhost.yml # Machine-specific variables (cloud providers, etc.)
└── roles/
├── system/ # preflight, directories, dotfiles
├── macos/ # dock, finder, keyboard shortcuts
├── apps/ # ui-apps, cli-apps, docs, networking
├── dev-tools/ # git, gnupg, golang, cicd, kubernetes, iac
├── virtualization/ # docker, colima, buildx
├── editors/ # vscode, cursor, jetbrains, neovim
├── terminal/ # ghostty, zsh, tmux, fonts, nvim
├── aws/ # awscli, eksctl, granted
├── azure/ # azure-cli
├── gcp/ # google-cloud-sdk
└── custom-tools/ # custom taps and tools
# Only macOS settings
ansible-playbook daily-driver.yml --tags macos
# Only CLI apps
ansible-playbook daily-driver.yml --tags cli-apps
# Only dotfiles
ansible-playbook daily-driver.yml --tags dotfiles
# Only the AeroSpace setup (WM, borders, SketchyBar, fonts, macOS tweaks, dotfiles,
# then the interactive permissions guide)
ansible-playbook daily-driver.yml --tags aerospace
# Only the permissions guide (e.g. after `brew upgrade sketchybar`)
ansible-playbook daily-driver.yml --tags permissionsTags come in two layers. Role and area tags are the ones you normally reach for:
system, preflight, directories, dotfiles, macos, dock, finder,
keyboard, apps, ui-apps, cli-apps, logitech, networking,
documentation-tools, dev-tools, git-tools, gh-cli, glab, golang,
cicd, kubernetes, cluster-visualization, iac, terraform, opentofu,
secrets, linters, virtualization, colima, docker-buildx, editors,
terminal, ghostty, zsh, pyenv, nvm, fonts, nvim, aws, azure,
gcp, custom-tools, demo, menubar.
Feature tags cut across roles: aerospace runs everything for the window
manager setup (AeroSpace, borders, SketchyBar, its fonts, macOS Spaces/menu bar
tweaks, dotfiles sync) and ends with the interactive permissions guide.
Individual packages also carry their own tag (bat, jq, htop, k9s, …), so
you can install a single tool without running its whole role:
ansible-playbook daily-driver.yml --tags k9sMachine-specific variables live in host_vars/localhost.yml. Role-specific defaults are in each role's defaults/main.yml and can be overridden via host_vars.
Key variables to configure in host_vars/localhost.yml:
# Cloud providers (opt-in, default false)
install_aws: true
install_azure: false
install_gcp: false
# Window manager — aerospace or yabai (yabai also installs skhd-zig)
window_manager: aerospace
# Dotfiles
dotfiles_repo: "https://github.com/your-user/dotfiles.git"
# Directories to create in $HOME
directories_to_create:
- personal
- work
# Custom tools (tap + formulae)
custom_user_taps:
- your-tap/tap
custom_user_tools:
- your-tap/tap/tool-nameCloud provider roles are opt-in and default to false. Enable them in host_vars/localhost.yml or override on the fly:
ansible-playbook daily-driver.yml -e install_aws=trueCombine with tags to run a single provider:
ansible-playbook daily-driver.yml --tags aws -e install_aws=trueClones the repository defined in host_vars/localhost.yml, syncs .config and .config_macos to ~/.config, copies .zshrc_macos to ~/.zshrc, and copies any of .gitconfig, .gitconfig-personal, .gitconfig-work if present. Syncing .config_macos reloads the selected window manager (AeroSpace, or Yabai and skhd-zig).
The macos role configures:
-
Dock — size, position (right), magnification, animation, indicators
-
Finder — extensions, hidden files, path bar, status bar, column view
-
Menu bar — Sound always shown
-
Spaces — no automatic rearranging of Spaces
-
Keyboard — full keyboard access, Spotlight shortcuts disabled, Siri disabled; with AeroSpace, Mission Control ctrl ←/→ disabled
Both Spotlight and Siri bind ⌘Space; Siri's default is hold ⌘Space, which swallows the keystroke before any app sees it. The playbook frees the shortcut but cannot set Raycast's hotkey — Raycast keeps it in an encrypted internal database with no supported
defaultskey. See Manual steps.
Set window_manager in host_vars/localhost.yml:
aerospace(default) — installs AeroSpace (nikitabobko/tap/aerospace). Config and cheat sheet live in dotfiles.config_macos/aerospace/.yabai— installs Yabai and skhd-zig.
The cli-apps tasks check with pgrep whether the selected window manager is
running and start it if not, so "running" is the desired state rather than a side
effect of the install task firing a handler. Syncing dotfiles .config_macos
reloads it through handlers.
JankyBorders (FelixKratz/formulae/borders)
highlights the focused window; its config lives in dotfiles .config_macos/borders/bordersrc.
SketchyBar (FelixKratz/formulae/sketchybar) is a
bottom status bar: AeroSpace workspaces with app icons, focused app, Spotify, Slack, kube
context (red for prod), Colima profiles, GitHub/GitLab reviews, AWS/GCP sessions,
CPU, RAM, weather and outdated Homebrew packages. Config lives in dotfiles .config_macos/sketchybar/;
fonts (font-sketchybar-app-font, font-sf-pro) are installed by the fonts tag.
The GitLab, AWS and GCP items stay hidden until their CLI is logged in.
Installs Ghostty (tip/development build) and syncs config from dotfiles if dotfiles/.config/ghostty/ exists.
Installs neovim via Homebrew. Config is managed via dotfiles.
The dev-tools role installs gnupg and pinentry-mac and writes
~/.gnupg/gpg-agent.conf, so GPG signing works if you already carry a key.
If you keep your SSH keys in an agent (1Password, Bitwarden, ssh-agent), SSH
signing is less work than maintaining a GPG keyring — git has supported it since
2.34 and there is no key material on disk:
# ~/.gitconfig-personal
[user]
email = <id>+<user>@users.noreply.github.com
signingkey = key::ssh-ed25519 AAAA...
[gpg]
format = ssh
[commit]
gpgsign = trueThe key:: prefix means "this is the key itself", not a path — git hands the
public key to the agent, which signs with the private half. Register the same
public key on GitHub a second time under Settings → SSH and GPG keys, with key
type Signing Key; a key registered only for authentication will not verify
signatures.
git log --show-signature reports No signature until you also point
gpg.ssh.allowedSignersFile at a file mapping emails to keys. That is a local
verification concern only — GitHub verifies against the keys on your account.
Raycast keeps its settings in a SQLCipher-encrypted database and exposes no
deeplink, CLI or defaults key to configure it, so nothing about it can be set
declaratively. Two things follow.
The hotkey stays manual — set ⌘Space in Settings → General → Raycast Hotkey. The playbook frees the shortcut by disabling the Spotlight and Siri bindings, but cannot assign it.
Everything else can travel in a .rayconfig export (Settings → Advanced → Export
Settings & Data), which covers extensions and their preferences, quicklinks,
snippets, window layouts and general settings. Point raycast_config_path at it
and run:
ansible-playbook daily-driver.yml --tags raycast-importThe task carries the never tag, so ordinary runs skip it — otherwise every run
would pop Raycast's import dialog. Import still asks for confirmation; this makes
it one click instead of reconfiguring extension by extension.
Keep the export out of a public repo. It is an opaque blob that may carry extension credentials, so secret scanners like gitleaks will not catch them. Store it in a password manager or private location. Raycast Pro's Cloud Sync (
requiresPro) removes the need for the file entirely.
macOS only lets the user grant these. --tags permissions (also part of
--tags aerospace, never part of a full run) opens the right pane, reveals the
sketchybar binary in Finder and waits for Enter.
| Where (System Settings → Privacy & Security) | App | Why |
|---|---|---|
| Accessibility | AeroSpace (/Applications/AeroSpace.app) |
move and tile windows |
| Accessibility | sketchybar (/opt/homebrew/Cellar/sketchybar/<version>/bin/sketchybar) |
read Slack/Discord Dock badges |
| Automation → System Events | sketchybar (prompted on first use) | Dock badges |
| Automation → Spotify | sketchybar (prompted on first use) | play/pause |
After brew upgrade sketchybar the Cellar path changes and the Accessibility
grant is lost (badges turn grey): remove the old entry and run --tags permissions.
The playbook cannot automate these:
| Step | Why |
|---|---|
| Raycast hotkey (Settings → General) | stored in an encrypted internal database |
| Register the signing key on GitHub | account-side, needs your session |
Fill in ~/.gitconfig-work |
the repo ships a redacted placeholder; force: false keeps your local value |
| Approve system extensions | Logitech and similar installers need Privacy & Security approval |
| AeroSpace Accessibility permission | TCC prompt, needs user approval |
| Keep a single macOS Desktop | AeroSpace only manages windows on the current Space |
Firefox full-screen-api.macos-native-full-screen = false |
about:config, per profile; avoids fullscreen video creating its own Space |
Lint and validate before pushing — CI runs the same three checks:
yamllint .
ansible-lint
ansible-playbook --syntax-check daily-driver.yml.ansible-lint carries a skip_list of rules that are deliberately deferred
rather than disabled; each entry is commented with what it covers.