Ansible is cool. This guy's config is cool. (see Allaman) and so is this guy's pigmonkey I've copied a lot from them.
This is my attempt at fully automating the setup of my linux machines with it.
This is my Ansible playbook to automatically configure a new Linux installation on an Arch or Debian system. The following roles are being used:
- packages installs packages via package manager and an AUR helper
- system configure system related settings
- homebrew installs and manages packages via Homebrew (for both Arch and Debian systems)
- binaries "installs" applications by downloading it's binary and placing them in PATH (for tools not available in Homebrew)
- dotfiles fork of Allaman's Ansible role to clone and link dotfiles
- shell fork of Allaman's Ansible role that installs shell tools
- asdf manages programming language versions (replaces pyenv)
- uv_python_packages manages Python package installation using the modern uv package manager
- gpu_detect auto-detects GPU hardware (AMD, NVIDIA, Intel) using lspci (Arch Linux only)
- gpu_drivers installs appropriate GPU drivers based on detected or configured GPU type (Arch Linux only)
You should checkout each roles README to see configuration options and decide if you need to fork a role for your own uses.
Starting from this version, the playbook uses Homebrew as the primary package manager for development tools and CLI utilities on both Arch and Debian-based systems. This provides:
- Consistency: Same package versions across different Linux distributions
- Up-to-date packages: Homebrew often has newer versions than distribution repositories
- Easy management: Simple installation and updates with
brew installandbrew upgrade
For packages not available in Homebrew, the ansible-role-binaries role downloads and installs binaries to /usr/local/bin.
All development tools are installed system-wide, making them available to every user on the machine:
| Tool | Location | PATH mechanism |
|---|---|---|
| asdf (languages, kubectl, etc.) | /opt/asdf |
/etc/profile.d/asdf.sh |
| Linuxbrew (bat, fd, ripgrep, etc.) | /home/linuxbrew/.linuxbrew |
/etc/profile.d/linuxbrew.sh |
| Rust/Cargo | /opt/rust |
/etc/profile.d/rust.sh |
| Go | /usr/local/go |
Already on PATH |
| Direct binaries | /usr/local/bin |
Already on PATH |
| AI tools (pi, forge) | asdf shims / /usr/local/bin |
/etc/profile.d/asdf.sh |
New users get access automatically when added to the devtools and linuxbrew groups:
sudo usermod -aG devtools,linuxbrew <username>Per-user personalization (shell plugins, dotfiles, AI tool config) is separated into the user_environment overlay (see Overlays below). Set user_environment: false in group_vars/all/local.yml to skip user-specific setup entirely.
The playbook ships with profiles — pre-configured bundles that set up a complete desktop stack in one command. Each profile selects the right roles, display manager, and environment variables automatically.
| Profile | Desktop Environment | Display Manager |
|---|---|---|
headless |
CLI-only (no display) | none |
i3 |
i3 window manager (X11) | LightDM |
hyprland |
Hyprland compositor (Wayland) | SDDM |
gnome |
GNOME desktop | GDM |
awesomewm |
AwesomeWM tiling WM | LightDM |
kde |
KDE Plasma | SDDM |
Quick start with a profile:
# List all available profiles
make list-profiles
# Configure with a specific profile
make profile-i3
make profile-hyprland
make profile-gnome
make profile-awesomewm
make profile-kde
make profile-headlessYou can still apply specific tags within a profile run by appending TAGS=:
make profile-i3 TAGS="editors,shell"Profile definitions live in the profiles/ directory as YAML files:
profiles/
├── _base.yml # Core roles shared by all profiles
├── headless.yml # CLI-only (extends _base)
├── i3.yml # i3 + X11 (extends _base)
├── hyprland.yml # Hyprland + Wayland (extends _base)
├── gnome.yml # GNOME (extends _base)
├── awesomewm.yml # AwesomeWM (extends _base)
└── kde.yml # KDE Plasma (extends _base)
Overlays are optional role groups in profiles/overlays/ that apply on top of
any profile. Each overlay gates itself on a variable from
group_vars/all/local.yml:
| Overlay | Enabled when | Roles |
|---|---|---|
user_environment |
by default — set user_environment: false to skip |
shell, dotfiles, gnupg, ai (AI tooling additionally needs ai_enabled: true) |
laptop |
laptop: true |
laptop, network_shares, backlight (display required) |
bluetooth |
bluetooth: defined and not disabled (Arch only) |
bluetooth |
# group_vars/all/local.yml examples
laptop: true # enable the laptop overlay
bluetooth:
disable: false # defining bluetooth enables the overlay (default)
user_environment: false # skip shell/dotfiles/gnupg/ai personalizationTo opt out of bluetooth entirely, set disable: true (or remove the
bluetooth: variable).
Roles in profiles/*.yml are entries with gating annotations. At run time the
dispatcher translates them into when: conditions over pre-resolved facts
(_is_arch, _has_display, _is_i3, … and the _overlay_* flags):
| Annotation | Meaning | Example |
|---|---|---|
tags |
Run targets for make configure TAGS= |
[editors] |
os |
Restrict to archlinux or debian |
os: archlinux |
requires_display |
Only runs with a display server (_has_display) |
requires_display: true |
requires_config |
Match a specific config value | requires_config: { display_manager: lightdm } |
config_check |
Jinja2 expression evaluated against host vars | "ai_enabled | default(false) | bool" |
section |
play.yml grouping; section order and comments are defined in profiles/_sections.yml |
section: dev |
play.yml is auto-generated from profiles/ — never edit it by hand (its
header says so). Edit the profile instead, regenerate, and commit both files
together:
# 1. edit a profile or overlay
$EDITOR profiles/i3.yml
# 2. regenerate play.yml from the profile definitions
make generate-playbook
# 3. commit the profile change and the regenerated play.yml together
git add profiles/i3.yml play.yml && git commit -m "profiles: ..."CI runs make check-sync and fails when play.yml is stale relative to
profiles/. Useful companion commands:
make validate-profiles # validate all profiles + overlays
make list-profiles # human-readable profile table
python3 scripts/profile_dispatcher.py resolve-role-manifest --profile i3
# inspect the fully resolved manifestThe direct
python3 ...dispatcher calls needpyyaml+jinja2in that interpreter (themaketargets handle this for you by resolving the pipx-managed ansible venv) — prefer the make targets when in doubt.
To add a new profile, create profiles/<name>.yml (usually with
extends: _base.yml) using an existing profile as a reference, then run
make generate-playbook.
If you prefer to configure desktop environments manually instead of using profiles, you can set variables in group_vars/all/local.yml:
# Install only i3
desktop_environment: i3
display_manager: lightdm
# Install only Hyprland
desktop_environment: hyprland
display_manager: sddmOpt-out variables are also supported:
# Disable i3 (only install Hyprland)
disable_i3: true
# Disable Hyprland (only install i3)
disable_hyprland: trueWhen neither opt-out variables nor desktop_environment are set, both i3 and Hyprland are installed automatically.
The playbook automatically detects and installs GPU drivers on Arch Linux systems.
The gpu_detect role uses lspci to identify your GPU hardware and sets the appropriate driver variables for:
- AMD: Mesa drivers, Vulkan support, amdgpu_top
- NVIDIA: Open-source (nouveau) or proprietary drivers
- Intel: Mesa drivers, Intel Vulkan support
- Hybrid systems: Multiple GPUs handled with
gpu_drivers_hybrid_install_all
Configure GPU behavior in group_vars/all/local.yml:
# Detection mode: auto (default), amd, nvidia, intel
gpu_drivers_detection_mode: auto
# Force a specific GPU type (overrides detection)
# gpu_drivers_type: nvidia
# Use proprietary NVIDIA drivers
# gpu_drivers_nvidia_proprietary: true
# Install drivers for all GPUs in a hybrid system
# gpu_drivers_hybrid_install_all: true# Test GPU detection only
make configure TAGS="gpu_detect"
# Test GPU driver installation
make configure TAGS="gpu_drivers"
# Show detected GPU hardware
make gpu-infoi3 + betterlockscreen supports unlocking with the fingerprint sensor (fprintd).
The lock wrapper (~/.local/bin/lock-fingerprint, managed by chezmoi in the
dotfiles repo) runs fprintd-verify alongside i3lock and unlocks on a
successful scan; typing your password still works. It is bound to
$mod+Shift+x and is used by the idle auto-lock (xidlehook).
Why a wrapper instead of PAM: i3lock only starts its PAM conversation after you press Enter, so
pam_fprintdnever runs. The wrapper performs the equivalent authentication itself.
The goesimage role deploys a systemd drop-in
(~/.config/systemd/user/goesimage.service.d/override.conf) that re-renders
the betterlockscreen cache from the current satellite wallpaper every time
goesimage updates it.
Enrollment requires an active graphical session and a running polkit
authentication agent (the i3 config autostarts
/usr/lib/polkit-kde-authentication-agent-1); a password prompt appears
before enrollment begins.
# List enrolled fingers
fprintd-list "$USER"
# Enroll a finger (touch the sensor repeatedly until "enroll-completed")
fprintd-enroll # defaults to right-index-finger
fprintd-enroll -f left-thumb # or name any other finger
# Delete one and re-enroll
fprintd-delete "$USER" right-index-finger
# Verify a scan without locking
fprintd-verifyIf enrollment fails with PermissionDenied: Not Authorized, your session is
not active or no polkit agent is running — start the agent and retry.
- Python 3
- A non-superuser account with sudo privileges (the playbook will prompt for the sudo password when needed)
Fresh machine? You no longer need to install
pipxby hand. Runmake setupfirst — it installs pipx (and puts~/.local/binon PATH) and then installs Ansible via pipx, all in one shot. (See Quick start below.)
The playbook is designed to be run by a non-superuser account. It will automatically escalate privileges (via sudo) for tasks that require root access, such as package installations and system configuration.
> make
all Run all goals
bootstrap-pipx Install pipx and add ~/.local/bin to PATH (run FIRST on a fresh system)
bootstrap Install ansible (pipx required)
configure Run ansible (optionally with TAGS="tag1,tag2")
gpu-info Display detected GPU information
help print this help
install Install roles via ansible-galaxy
list-profiles List all available configuration profiles
list-tags List all available tags in the playbook
profile-awesomewm Run AwesomeWM tiling window manager profile
profile-gnome Run GNOME desktop environment profile
profile-headless Run headless profile (CLI-only, no display)
profile-hyprland Run Hyprland Wayland compositor profile
profile-i3 Run i3 window manager profile
profile-kde Run KDE Plasma desktop profile
setup One-shot fresh-system setup: install pipx + PATH, then ansible-
Bootstrap (first time on a fresh machine): install pipx + PATH + Ansible
make setup # installs pipx, ~/.local/bin on PATH, and ansible exec $SHELL -l # reload the shell so the new PATH takes effect
If
pipxis already installed and only Ansible is missing, usemake bootstrapinstead. -
Install roles: Install required Ansible roles and collections
make install
-
Create your local config: Copy the template for your machine type and edit at minimum
hostname# Desktop or laptop: cp group_vars/templates/desktop.yml group_vars/all/local.yml # Headless server: cp group_vars/templates/server.yml group_vars/all/local.yml
Then open
group_vars/all/local.ymland set:hostname— short hostname for this machinenetwork.trusted_uuid— runnmcli connection showto find your UUIDsilluminancedpaths (desktop/laptop only) — runls /sys/class/backlight/to find your device
-
Configure: Run the playbook (you will be prompted for your sudo password)
make configure
The playbook uses --ask-become-pass to prompt for your sudo password when privilege escalation is needed. This ensures that the playbook can be run by any user with sudo privileges, not just root.
You can run specific roles by using tags. This allows you to only run certain parts of the playbook instead of the entire configuration.
-
List all available tags:
make list-tags
-
Run a single tag:
make configure TAGS="docker" -
Run multiple tags (comma-separated):
make configure TAGS="docker,editors,shell"
This is useful when you only want to configure specific components without running the entire playbook.