17 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Overview
This is an Ansible playbook repository for automating Arch Linux development environment setup. It configures a local workstation with development tools, window managers, shells, and productivity applications. The playbooks are designed to run locally (not against remote hosts) using localhost as the target.
The project has evolved from monolithic playbooks to a modular, role-based architecture with feature flags for flexible deployments.
Project Structure
arch_custom/
├── playbooks/
│ ├── local_setup.yml # MAIN PLAYBOOK (production)
│ └── debug/ # Archived legacy playbooks
│ ├── playbook.yml # Original monolithic version
│ ├── playbook-2.yml # Iteration attempt
│ └── local_setups.yml # Incomplete test version
├── roles/
│ ├── custom/ # Core system setup (modular)
│ │ ├── tasks/
│ │ │ ├── main.yml # Base packages, yay, UV, Hyprland, productivity tools
│ │ │ ├── capps.yml # Development apps (PyCharm, R/RStudio, databases)
│ │ │ └── terms.yml # Terminal setup (Alacritty, TMUX with TPM)
│ │ └── files/
│ │ └── alacritty.yml # Alacritty configuration file
│ ├── manage_zsh/ # Zsh with optional Powerlevel10k (modular)
│ │ ├── tasks/
│ │ │ ├── main.yml # Install packages via pacman, copy .zshrc, fastfetch
│ │ │ ├── zsh-plugin.yml # Git clone method for plugins (conditional)
│ │ │ └── zsh-p10k.yml # Oh My Zsh + Powerlevel10k (conditional)
│ │ ├── files/
│ │ │ ├── .zshrc # Zsh configuration for Powerlevel10k
│ │ │ └── .p10k.zsh # Powerlevel10k theme configuration
│ │ ├── defaults/
│ │ ├── handlers/
│ │ └── vars/
│ ├── starship/ # Starship prompt (production-ready)
│ │ ├── tasks/
│ │ │ └── main.yml # Install Starship + modern CLI tools
│ │ └── files/
│ │ ├── starship.toml # Starship prompt configuration
│ │ └── zshrc # Zsh config optimized for Starship
│ └── geerlingguy.docker/ # External Galaxy role for Docker
├── inventories/
│ └── hosts # Localhost inventory with groups
├── requirement.yml # Ansible Galaxy role dependencies
├── ansible.cfg # Ansible configuration (local connection, logging, facts cache)
├── pyproject.toml # Python dependencies (ansible, ansible-lint, yamllint)
├── README.md # User-facing documentation
└── CLAUDE.md # This file (developer documentation)
Key Architecture Decisions
Execution Model
- All playbooks target
localhostwithconnection: local - Uses privilege escalation (
become: yes) for system package installation - Switches to non-root (
become: falseorbecome_user: "{{ user }}") for user-specific configurations - Variables
usernameanduser_homeare derived from the executing user's environment
Modular Role Architecture
The project has evolved significantly:
- Legacy: Monolithic playbooks (now in
playbooks/debug/) - Current: Modular roles with sub-task files and feature flags
Custom Role Modularity:
main.yml: Core system setup (packages, yay, UV, system tools)capps.yml: Development applications (PyCharm, R, RStudio, databases)terms.yml: Terminal emulators and multiplexers (Alacritty, TMUX with TPM)
Manage_zsh Role Modularity:
main.yml: Package installation via pacman, .zshrc deployment, fastfetch setupzsh-plugin.yml: Git clone method for plugins (used when not using pacman)zsh-p10k.yml: Oh My Zsh + Powerlevel10k installation (conditional onUSE_P10K)
Shell Configuration Strategy:
- Two mutually exclusive shell prompt options controlled by feature flags
USE_STARSHIP: true→ Modern, fast, Rust-based prompt (current default)USE_P10K: true→ Feature-rich, customizable prompt with Oh My Zsh- Only one should be enabled at a time to avoid conflicts
Feature Flags
The playbook uses boolean variables for conditional component installation:
USE_STARSHIP: true # Enable Starship prompt (recommended)
USE_P10K: false # Enable Powerlevel10k with Oh My Zsh
USE_ALACRITTY: false # Set Alacritty as default terminal
INSTALL_CUSTOM_APPS: false # Install development apps (PyCharm, R, databases)
install_tiling_wm: true # Install tiling window manager
These allow users to customize their installation without modifying role code.
Package Management Strategy
- Official Arch packages:
community.general.pacmanmodule - AUR packages:
- Method 1:
ansible.builtin.commandwithyay -S --noconfirm <package> - Method 2:
community.general.pacmanwithexecutable: yay
- Method 1:
- Yay installation: Handled conditionally (checks if already installed before building)
- Ansible Galaxy roles: Installed via
ansible-galaxy install -r requirement.yml
Zsh Plugin Management:
- Default approach (manage_zsh): Install via pacman system packages
zsh-autosuggestions,zsh-syntax-highlighting,zsh-completions,zsh-transient-prompt- Source from
/usr/share/zsh/plugins/paths - Benefits: Automatic updates with
pacman -Syu, clean uninstall
- Alternative approach (starship): Git clone to
~/.zsh/directory- Used for cross-distro compatibility in dotfiles
- Manual updates required
User Context Switching Pattern
Tasks frequently switch between root and user contexts:
# System package installation (as root)
- name: Install packages
community.general.pacman:
name: package-name
state: present
# Implicit: become: yes (from playbook level)
# User-specific configuration (as non-root)
- name: Copy config file
become: false
ansible.builtin.copy:
src: config.yml
dest: "{{ user_home }}/.config/app/config.yml"
# Explicit user context
- name: Install AUR package
become: false
ansible.builtin.command: yay -S --noconfirm package-name
External Role Dependencies
The project uses geerlingguy.docker role from Ansible Galaxy:
- Professional, well-maintained role for Docker installation
- Handles Docker service management and user group setup
- Version pinned in
requirement.yml:7.3.0 - Install before running playbook:
ansible-galaxy install -r requirement.yml
Configuration Variables
Required Variables (set in playbooks/local_setup.yml)
username: "{{ lookup('env', 'USER') }}" # Auto-detected from environment
user_home: "/home/{{ username }}" # User's home directory
user: "{{ username }}" # Alias for role compatibility
home: "{{ user_home }}" # Alias for role compatibility
Feature Flags
USE_STARSHIP: true # Enable Starship shell prompt
USE_P10K: false # Enable Powerlevel10k (don't enable with Starship)
USE_ALACRITTY: false # Set Alacritty as default terminal emulator
INSTALL_CUSTOM_APPS: false # Install PyCharm, R/RStudio, databases
install_tiling_wm: true # Install tiling window manager
tiling_wm: "i3-gaps" # Which tiling WM (i3-gaps, sway, awesome, qtile, bspwm)
git_server_port: 3000 # Port for local Git server (if enabled)
Common Commands
First-Time Setup
# Install Ansible on Arch Linux
sudo pacman -S ansible
# Set up Python environment for linting (optional)
uv sync
source .venv/bin/activate
# Install Galaxy role dependencies
ansible-galaxy install -r requirement.yml
# Run the main playbook
ansible-playbook playbooks/local_setup.yml
Run with Specific Tags
# Only setup core packages and tools
ansible-playbook playbooks/local_setup.yml --tags custom
# Only setup Zsh environment
ansible-playbook playbooks/local_setup.yml --tags manage_zsh
# Only setup Starship prompt
ansible-playbook playbooks/local_setup.yml --tags starship
# Only setup Docker
ansible-playbook playbooks/local_setup.yml --tags docker
# Run all base setup tasks (custom + manage_zsh + starship + docker)
ansible-playbook playbooks/local_setup.yml --tags base_setup
# Skip Docker installation
ansible-playbook playbooks/local_setup.yml --skip-tags docker
Validation and Testing
# Syntax check
ansible-playbook playbooks/local_setup.yml --syntax-check
# Dry run (check mode)
ansible-playbook playbooks/local_setup.yml --check
# Run with verbose output
ansible-playbook playbooks/local_setup.yml -vv
# Linting (requires uv environment)
uv sync && source .venv/bin/activate
ansible-lint playbooks/local_setup.yml
yamllint .
Inventory Management
# View inventory structure
ansible-inventory --list
# View inventory graph
ansible-inventory --graph
Development Workflow
Adding New Roles
- Create role structure:
ansible-galaxy init roles/<role-name> - Add tasks to
roles/<role-name>/tasks/main.yml - Define defaults in
roles/<role-name>/defaults/main.ymlif needed - Store config files in
roles/<role-name>/files/for thecopymodule - Include role in
playbooks/local_setup.yml:roles: - role: <role-name> tags: - <role-name> - base_setup # Optional: include in base setup
Modularizing Existing Roles
When a role's main.yml becomes too large, split into focused sub-task files:
Example: Custom role structure
# roles/custom/tasks/main.yml
---
- name: Base system setup
# Core tasks...
- name: Include development apps
include_tasks: capps.yml
when: INSTALL_CUSTOM_APPS
- name: Include terminal setup
include_tasks: terms.yml
Sub-task files:
capps.yml: Development applications (PyCharm, R, databases)terms.yml: Terminal emulators and configurations
This pattern keeps files focused (< 200 lines) and improves readability.
Adding Configuration Files
Store configuration files in roles/<role-name>/files/:
# Reference file in copy task
- name: Configure application
ansible.builtin.copy:
src: config.yml # Looks in roles/<role-name>/files/config.yml
dest: "{{ user_home }}/.config/app/config.yml"
mode: '0644'
For inline content, use content parameter:
- name: Create config file
ansible.builtin.copy:
dest: "{{ user_home }}/.config/app/config.yml"
content: |
key: value
setting: true
mode: '0644'
Handling AUR Packages
Standard pattern for AUR packages:
- name: Install AUR package
become: false
ansible.builtin.command: yay -S --noconfirm <package-name>
register: result
changed_when: result.rc == 0
failed_when: result.rc != 0 and 'error' in result.stderr
Alternative using pacman module:
- name: Install AUR package
become: false
community.general.pacman:
name: <package-name>
state: present
executable: yay
extra_args: "--builddir /tmp/yay"
Managing Shell Plugins
For Powerlevel10k (manage_zsh role):
- Plugins installed via pacman: Edit
roles/manage_zsh/tasks/main.yml - Add package to the pacman task list
- Source in
.zshrcfrom/usr/share/zsh/plugins/<plugin-name>/
For Starship (starship role):
- Plugins cloned via git: Edit
roles/starship/tasks/main.yml - Add git clone task for the plugin
- Source in
zshrcfrom~/.zsh/<plugin-name>/
Conditional Role Execution
Use when clauses with feature flags:
roles:
- role: starship
when: USE_STARSHIP
tags:
- starship
- base_setup
- role: manage_zsh
when: USE_P10K
tags:
- manage_zsh
- base_setup
Internal conditionals in roles:
# In manage_zsh/tasks/main.yml
roles:
- role: zsh-plugin
when: USE_P10K
- role: zsh-p10k
when: USE_P10K
Facts Caching
Facts are cached to ./facts_cache/ with a 2-hour timeout:
# Clear cache if needed
rm -rf facts_cache/*
# Disable cache for testing
ansible-playbook playbooks/local_setup.yml --flush-cache
Important Patterns and Conventions
Variable Naming
- Feature flags:
USE_<FEATURE>orINSTALL_<FEATURE>(boolean) - User variables:
username,user_home,user,home - Role-specific: Prefix with role name (
manage_zsh_user)
Task Naming
- Prefix with role name:
Manage_zsh | Install required packages - Use descriptive, action-oriented names
- Follow pattern:
<Role> | <Action> <Target>
File Organization
- Keep
main.ymlunder 200 lines - Split into logical sub-task files when needed
- Use
include_tasksfor conditional sub-tasks - Store static files in
files/directory - Use
templates/for Jinja2 templates (if needed)
Idempotency
Ensure tasks can be run multiple times safely:
# Check before creating
- name: Check if exists
ansible.builtin.stat:
path: /path/to/resource
register: resource_check
- name: Create resource
when: not resource_check.stat.exists
# ... creation task
# Use creates argument
- name: Build and install
ansible.builtin.command: makepkg -si --noconfirm
args:
creates: /usr/bin/executable
Error Handling
# Allow task to fail without stopping playbook
- name: Optional task
ansible.builtin.command: some-command
failed_when: false
# Define specific failure conditions
- name: Install package
ansible.builtin.command: yay -S --noconfirm package
register: result
failed_when: result.rc != 0 and 'error' in result.stderr
# Ignore specific return codes
- name: Check for resource
ansible.builtin.command: which program
register: check
changed_when: false
failed_when: check.rc not in [0, 1]
Logs and Debugging
Log Files
- Ansible logs:
./ansible.log(configured inansible.cfg) - Location: Project root directory
- Persistent across runs (appends)
Verbosity Levels
# Standard output
ansible-playbook playbooks/local_setup.yml
# Show task results (-v)
ansible-playbook playbooks/local_setup.yml -v
# Show task inputs and outputs (-vv)
ansible-playbook playbooks/local_setup.yml -vv
# Debug level (-vvv)
ansible-playbook playbooks/local_setup.yml -vvv
# Connection debug level (-vvvv)
ansible-playbook playbooks/local_setup.yml -vvvv
Debugging Specific Tasks
# Start at specific task
ansible-playbook playbooks/local_setup.yml --start-at-task="Task Name"
# Run single task using tags
ansible-playbook playbooks/local_setup.yml --tags custom -vv
# Step through tasks interactively
ansible-playbook playbooks/local_setup.yml --step
Debug Tasks
Add debug tasks to troubleshoot:
- name: Debug user variables
ansible.builtin.debug:
msg:
- "Username: {{ username }}"
- "Home: {{ user_home }}"
- "Variable: {{ my_var | default('NOT SET') }}"
Post-Installation Manual Steps
Document these for users in README.md:
- Docker group: Log out and back in for group changes
- TMUX plugins: Run
tmuxthen pressCtrl+Space + I - Starship config: Edit
~/.config/starship.tomlfor customization - Powerlevel10k: Run
p10k configurefor initial setup - Python venvs: Set up per-project environments with
uv venv
Testing Recommendations
- Test in VM: Use Arch Linux VM for testing before deploying to main system
- Use check mode: Always run with
--checkfirst - Tag-based testing: Test individual roles before full run
- Incremental approach: Add new features behind feature flags
- Version control: Commit working states before major changes
Common Issues and Solutions
Yay build failures
- Ensure
base-develis installed - Check disk space for build directory
- Clear yay cache:
yay -Sc
Plugin sourcing errors
- Verify plugin path exists:
ls -la ~/.zsh/or/usr/share/zsh/plugins/ - Check .zshrc syntax:
zsh -n ~/.zshrc - Ensure plugin installed before sourcing
Permission errors
- Verify
become: falsefor user-specific tasks - Check file ownership:
ls -la ~/.config/ - Use
become_userfor explicit user context
Role not found
- Install Galaxy dependencies:
ansible-galaxy install -r requirement.yml - Check role path in
ansible.cfg:roles_path = ./roles - Verify role directory structure exists
Migration Notes
If working with older versions of this playbook:
- Legacy playbooks are in
playbooks/debug/(archived, not deleted) - Old monolithic custom role has been split into modular sub-tasks
- Plugin installation migrated from git clone to pacman packages (where possible)
- Feature flags introduced for conditional installations
- geerlingguy.docker replaced inline Docker tasks
When updating from old versions:
- Review feature flags in
playbooks/local_setup.yml - Run
ansible-galaxy install -r requirement.ymlfor new dependencies - Check README.md for updated configuration variables
- to memorize