diff --git a/.github/workflows/markdown-link-config.json b/.github/workflows/markdown-link-config.json index 61755630..39aa9b20 100644 --- a/.github/workflows/markdown-link-config.json +++ b/.github/workflows/markdown-link-config.json @@ -2,6 +2,15 @@ "ignorePatterns": [ { "pattern": "^http://localhost" + }, + { + "pattern": "^https://www.freedesktop.org" + }, + { + "pattern": "^https://goreleaser.com" + }, + { + "pattern": "^https://wttr.in" } ], "replacementPatterns": [ diff --git a/.gitignore b/.gitignore index 1729ad0c..26926db9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,12 @@ roles/claude/files/skills/peon-ping-*/ # Crush directory .crush/ -# Claude session snapshots (personal work state) -.claude/snapshots/ +# Claude Code personal files (keep agents/skills/commands) +.claude/settings.json +.claude/settings.local.json +.claude/projects/ +.claude/notepads/ +.claude/*.md # Ansible cache/runtime directories .ansible/ @@ -50,3 +54,9 @@ node_modules/ # Task files tasks.json + +# User overrides (fork customizations) +overrides/roles/ +overrides/config/dotfiles.conf +!overrides/config/dotfiles.conf.example +!overrides/README.md diff --git a/README.md b/README.md index c23ac2b0..89c5b6f6 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,35 @@ bash -c "$(curl -fsSL https://raw.githubusercontent.com/TechDufus/dotfiles/main/ - Customize your setup by editing `~/.dotfiles/group_vars/all.yml` - Run `dotfiles` anytime to update your environment +## 🍴 Fork This Project + +**This repo is designed for forking.** Customize your dotfiles without merge conflicts. + +### How It Works + +1. **Fork** this repository +2. **Create overrides** in `overrides/` directory (gitignored) +3. **Pull upstream** anytime - your customizations stay separate + +### What You Can Override + +| Override Type | Location | Effect | +|--------------|----------|--------| +| Variables | `overrides/roles/{role}/vars/main.yml` | Change settings (paths, names, options) | +| Config Files | `overrides/roles/{role}/files/` | Replace config files with your own | +| Tasks | `overrides/roles/{role}/tasks/main.yml` | Replace entire role behavior | + +### Common Customizations + +| What | Override | Example | +|------|----------|---------| +| Git identity | `overrides/roles/git/vars/main.yml` | Your name and email | +| Shell config | `overrides/roles/zsh/files/.zshrc` | Personal aliases | +| Terminal | `overrides/roles/ghostty/files/config` | Theme, font, colors | +| Editor | `overrides/roles/neovim/files/` | Plugins, keybindings | + +See [`overrides/README.md`](overrides/README.md) for the complete guide. + --- ## 🎯 Goals diff --git a/bin/dotfiles b/bin/dotfiles index badc0142..d43e71e7 100755 --- a/bin/dotfiles +++ b/bin/dotfiles @@ -111,12 +111,22 @@ DOTFILES_LOG="$HOME/.dotfiles.log" DOTFILES_DIR="$HOME/.dotfiles" IS_FIRST_RUN="$HOME/.dotfiles_run" DOTFILES_VERSION="1.0.0" + +# Fork configuration - forkers change this or use overrides/dotfiles.conf +DOTFILES_GITHUB_USER="TechDufus" +DOTFILES_REPO_NAME="dotfiles" +DOTFILES_CONFIG="$HOME/.dotfiles/overrides/config/dotfiles.conf" +if [[ -f "$DOTFILES_CONFIG" ]]; then + # shellcheck source=/dev/null + source "$DOTFILES_CONFIG" +fi + # Spinner PID tracking SPINNER_PID="" # Show usage/help information show_help() { - echo -e "${CAT_SAPPHIRE}TechDufus Dotfiles${NC} - Ansible-based dotfiles management" + echo -e "${CAT_SAPPHIRE}${DOTFILES_GITHUB_USER} Dotfiles${NC} - Ansible-based dotfiles management" echo "" echo -e "${CAT_BLUE}USAGE:${NC}" echo " dotfiles [OPTIONS] [-- ANSIBLE_ARGS]" @@ -148,7 +158,7 @@ show_help() { echo " All arguments not recognized by dotfiles are passed to ansible-playbook." echo " Common options: -t , --check, -vvv, --list-tags" echo "" - echo -e "${CAT_SUBTEXT0}For more information: https://github.com/TechDufus/dotfiles${NC}" + echo -e "${CAT_SUBTEXT0}For more information: https://github.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}${NC}" } # Show version information @@ -157,7 +167,7 @@ show_version() { if [[ -d "$DOTFILES_DIR/.git" ]]; then git_hash=$(git -C "$DOTFILES_DIR" rev-parse --short HEAD 2>/dev/null || echo "unknown") fi - echo -e "${CAT_SAPPHIRE}TechDufus Dotfiles${NC} version ${CAT_GREEN}${DOTFILES_VERSION}${NC} (${CAT_SUBTEXT1}${git_hash}${NC})" + echo -e "${CAT_SAPPHIRE}${DOTFILES_GITHUB_USER} Dotfiles${NC} version ${CAT_GREEN}${DOTFILES_VERSION}${NC} (${CAT_SUBTEXT1}${git_hash}${NC})" } # Spinner function that runs in background @@ -452,7 +462,7 @@ esac if ! [[ -d "$DOTFILES_DIR" ]]; then __task "Downloading dotfiles repository (This may take a minute)" - _cmd "git clone --quiet https://github.com/TechDufus/dotfiles.git $DOTFILES_DIR" + _cmd "git clone --quiet https://github.com/${DOTFILES_GITHUB_USER}/${DOTFILES_REPO_NAME}.git $DOTFILES_DIR" _task_done else __task "Updating dotfiles repository" diff --git a/docs/README.md b/docs/README.md index 8a16e5f2..4ca9e2bc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,9 @@ # Dotfiles Documentation +## Forking & Customization + +See [`../overrides/README.md`](../overrides/README.md) for the fork-friendly override system. + ## Role Development Guide See [example-role/](example-role/) for a complete template demonstrating the standard patterns used across all dotfiles roles. @@ -144,4 +148,4 @@ The uninstall process will: 2. **Be idempotent** - Running twice should not change anything 3. **Handle errors gracefully** - Provide clear messages and alternatives 4. **Document non-obvious choices** - Add comments for complex logic -5. **Test on all platforms** - Ensure consistency across OS \ No newline at end of file +5. **Test on all platforms** - Ensure consistency across OS diff --git a/main.yml b/main.yml index 7f787857..2fcfbd3f 100644 --- a/main.yml +++ b/main.yml @@ -50,12 +50,11 @@ tags: - always - - name: Run roles - ansible.builtin.include_role: - apply: - tags: - - "{{ roles_item }}" - name: "{{ roles_item }}" + - name: Run roles with override support + ansible.builtin.include_tasks: + file: "{{ playbook_dir }}/tasks/run_role_with_overrides.yml" + vars: + _role_name: "{{ roles_item }}" loop_control: loop_var: roles_item with_items: "{{ run_roles }}" diff --git a/overrides/.gitkeep b/overrides/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/overrides/README.md b/overrides/README.md new file mode 100644 index 00000000..ed11ca8b --- /dev/null +++ b/overrides/README.md @@ -0,0 +1,225 @@ +# Overrides Directory + +Fork-friendly customization system. Override any role without modifying upstream files. + +## Quick Start + +1. **Fork** the repository on GitHub +2. **Clone** your fork locally +3. **Create** your override: `mkdir -p overrides/roles/{role}/files` +4. **Customize** by copying and editing files +5. **Run** `dotfiles` - your overrides apply automatically + +Your overrides stay local (gitignored), so you can pull upstream updates without conflicts. + +## Override Levels + +| Level | Path | Behavior | +|-------|------|----------| +| **Vars** | `overrides/roles/{role}/vars/main.yml` | Merged before role runs | +| **Files** | `overrides/roles/{role}/files/` | Role uses these files instead | +| **Tasks** | `overrides/roles/{role}/tasks/main.yml` | **Replaces role entirely** | + +## How It Works + +The wrapper checks for overrides before running each role: + +1. **Vars override** - Loaded first, variables merge/override role defaults +2. **Files override** - Sets `_files_path` so role symlinks your files +3. **Task override** - Original role is **skipped**, your tasks run instead + +## Examples + +### Override Variables Only + +```bash +mkdir -p overrides/roles/git/vars +cat > overrides/roles/git/vars/main.yml << 'EOF' +git_user_name: "My Fork Name" +git_user_email: "fork@example.com" +EOF +``` + +### Override Config Files Only + +```bash +mkdir -p overrides/roles/neovim/files +cp -r roles/neovim/files/* overrides/roles/neovim/files/ +# Edit files in overrides/roles/neovim/files/ as needed +``` + +### Replace Role Entirely + +```bash +mkdir -p overrides/roles/custom-tool/tasks +cat > overrides/roles/custom-tool/tasks/main.yml << 'EOF' +--- +- name: "custom-tool | My custom implementation" + ansible.builtin.debug: + msg: "Running custom tasks instead of original role" + +# Use _files_path for file operations (role_path unavailable in overrides) +- name: "custom-tool | Symlink config" + ansible.builtin.file: + src: "{{ _files_path }}/config" + dest: "{{ ansible_user_dir }}/.config/custom-tool" + state: link +EOF +``` + +### Add New Custom Roles + +Create roles that don't exist upstream - perfect for personal tools or workflows: + +```bash +mkdir -p overrides/roles/my-tool/tasks +cat > overrides/roles/my-tool/tasks/main.yml << 'EOF' +--- +- name: "my-tool | Install my custom tool" + ansible.builtin.package: + name: my-tool + state: present + +- name: "my-tool | Deploy config" + ansible.builtin.copy: + src: "{{ _files_path }}/config" + dest: "{{ ansible_user_dir }}/.config/my-tool/config" +EOF + +# Add config files +mkdir -p overrides/roles/my-tool/files +echo "my config" > overrides/roles/my-tool/files/config +``` + +Run with the `-t` flag: +```bash +dotfiles -t my-tool +``` + +Custom roles are only run when explicitly tagged - they won't run during a full `dotfiles` execution unless added to `default_roles`. + +## Commonly Overridden Files + +| What to Customize | Override Path | Key Settings | +|-------------------|---------------|--------------| +| Git identity | `overrides/roles/git/vars/main.yml` | `git_user_name`, `git_user_email` | +| Git config | `overrides/roles/git/files/.gitconfig` | Aliases, merge strategy | +| Shell aliases | `overrides/roles/zsh/files/.zshrc` | Personal shortcuts | +| Terminal colors | `overrides/roles/ghostty/files/config` | Theme, font, opacity | +| Editor config | `overrides/roles/neovim/files/` | Plugins, keybindings | +| Tmux config | `overrides/roles/tmux/files/tmux/` | Prefix key, status bar | + +## Partial File Overrides + +**Important:** When overriding files, you must provide ALL files the role expects. + +The `_files_path` variable points to ONE directory - either your override OR the role's files, not both. + +**Example:** If `roles/git/files/` contains: +- `.gitconfig` +- `global.commit.template` + +And you only put `.gitconfig` in `overrides/roles/git/files/`, the role will fail looking for `global.commit.template`. + +**Solution:** Copy everything, then modify what you need: +```bash +cp -r roles/git/files/* overrides/roles/git/files/ +# Now edit only the files you want to change +``` + +## Important Notes + +- **Task overrides skip the original role** - your tasks have full control +- **Use `_files_path`** in override tasks, not `role_path` (magic variable unavailable) +- **This directory is gitignored** - your customizations stay local +- **Pull upstream cleanly** - no merge conflicts with your overrides + +## Variable Scope Behavior + +**Important:** Variables loaded from `overrides/roles/{role}/vars/main.yml` persist for the entire playbook run. + +This is standard Ansible behavior - `include_vars` loads into play scope. After a role runs, its override variables remain set for all subsequent roles. + +### Best Practices + +1. **Use role-prefixed variable names** - `git_user_email` not `user_email` +2. **Check role defaults first** - `roles/{role}/defaults/main.yml` shows expected names +3. **Avoid generic names** - `config`, `version`, `name` could collide with other roles +4. **Override only what you need** - fewer variables = fewer collision chances + +This behavior matches how `group_vars/` works in Ansible - it's not a bug, just something to be aware of. + +## Debugging Overrides + +### Verify Override Detection + +Run with verbose output to see which overrides are detected: +```bash +dotfiles -t git -vvv 2>&1 | grep -E "(override|_files_path)" +``` + +### Check _files_path Value + +In your playbook output, look for: +``` +TASK [run_role_with_overrides : Set _files_path] +ok: [localhost] => {"ansible_facts": {"_files_path": "/path/to/overrides/roles/git/files"}} +``` + +If it shows `roles/git/files` instead, your override wasn't detected. + +### Common Issues + +| Problem | Cause | Fix | +|---------|-------|-----| +| Override not applied | Wrong directory structure | Check path: `overrides/roles/{role}/files/` not `override/` | +| File not found | Missing file in override | Copy ALL files from `roles/{role}/files/` | +| Variables not merged | Wrong vars path | Use `overrides/roles/{role}/vars/main.yml` | + +## Migrating from Modified Roles + +If you've already forked and modified role files directly: + +1. **Identify changes** - `git diff upstream/main -- roles/` +2. **Create override structure** - `mkdir -p overrides/roles/{role}/files` +3. **Copy your modified files** - Move from `roles/{role}/files/` to `overrides/roles/{role}/files/` +4. **Reset role to upstream** - `git checkout upstream/main -- roles/{role}/files/` +5. **Test** - Run `dotfiles -t {role}` to verify + +Your modifications now live in `overrides/`, cleanly separated from upstream. + +## Bootstrap Customization + +When you fork this repo, the bootstrap script needs to know to clone YOUR fork, not the original. + +### Quick Setup + +1. Edit `bin/dotfiles` in your fork and change: + ```bash + DOTFILES_GITHUB_USER="YourGitHubUsername" + ``` + +2. Commit and push to your fork + +3. Run your fork's bootstrap: + ```bash + bash -c "$(curl -fsSL https://raw.githubusercontent.com/YourGitHubUsername/dotfiles/main/bin/dotfiles)" + ``` + +### Config File Override + +After initial clone, you can also use a config file for subsequent runs: + +```bash +cp overrides/config/dotfiles.conf.example overrides/config/dotfiles.conf +# Edit dotfiles.conf with your settings +``` + +This is useful if you want to keep `bin/dotfiles` unmodified for easier upstream merges. + +### Available Settings + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DOTFILES_GITHUB_USER` | `TechDufus` | GitHub username for repo URL | +| `DOTFILES_REPO_NAME` | `dotfiles` | Repository name (if renamed) | diff --git a/overrides/config/dotfiles.conf.example b/overrides/config/dotfiles.conf.example new file mode 100644 index 00000000..540a80ab --- /dev/null +++ b/overrides/config/dotfiles.conf.example @@ -0,0 +1,11 @@ +# Dotfiles Bootstrap Configuration +# Copy this file to dotfiles.conf and customize for your fork. +# +# This file is sourced by bin/dotfiles before cloning/updating. +# Your customizations here override the defaults in the script. + +# GitHub username for your fork (required for bootstrap to clone your fork) +# DOTFILES_GITHUB_USER="YourGitHubUsername" + +# Repository name (usually "dotfiles" unless you renamed it) +# DOTFILES_REPO_NAME="dotfiles" diff --git a/roles/1password/tasks/main.yml b/roles/1password/tasks/main.yml index 05221c9d..b11916a9 100644 --- a/roles/1password/tasks/main.yml +++ b/roles/1password/tasks/main.yml @@ -28,6 +28,6 @@ - name: "{{ role_name }} | Deploy ssh config" ansible.builtin.copy: - src: agent.toml + src: "{{ _files_path }}/agent.toml" dest: "{{ ansible_facts['user_dir'] }}/.config/1Password/ssh/agent.toml" mode: "0644" diff --git a/roles/alacritty/tasks/wsl.yml b/roles/alacritty/tasks/wsl.yml index 9ccf9ff4..08a808bf 100644 --- a/roles/alacritty/tasks/wsl.yml +++ b/roles/alacritty/tasks/wsl.yml @@ -23,5 +23,5 @@ - name: Copy Alacritty config ansible.builtin.copy: - src: wsl_alacritty.yaml + src: "{{ _files_path }}/wsl_alacritty.yaml" dest: /mnt/c/Users/{{ wsl_host_user }}/AppData/Roaming/alacritty/alacritty.yml diff --git a/roles/awesomewm/tasks/Ubuntu.yml b/roles/awesomewm/tasks/Ubuntu.yml index 15958462..611ccab6 100644 --- a/roles/awesomewm/tasks/Ubuntu.yml +++ b/roles/awesomewm/tasks/Ubuntu.yml @@ -95,49 +95,49 @@ - name: "awesomewm | Ubuntu | Deploy Cell Management Config" ansible.builtin.copy: - src: "config/cell-management/" + src: "{{ _files_path }}/config/cell-management/" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/cell-management/" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" - name: "awesomewm | Ubuntu | Deploy Notifications Config" ansible.builtin.copy: - src: "config/notifications/" + src: "{{ _files_path }}/config/notifications/" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/notifications/" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" - name: "awesomewm | Ubuntu | Deploy wibar configuration" ansible.builtin.copy: - src: "config/wibar.lua" + src: "{{ _files_path }}/config/wibar.lua" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/wibar.lua" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" - name: "awesomewm | Ubuntu | Deploy window switcher" ansible.builtin.copy: - src: "config/window-switcher.lua" + src: "{{ _files_path }}/config/window-switcher.lua" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/window-switcher.lua" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" - name: "awesomewm | Ubuntu | Deploy icon resolver" ansible.builtin.copy: - src: "config/icon-resolver.lua" + src: "{{ _files_path }}/config/icon-resolver.lua" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/icon-resolver.lua" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" - name: "awesomewm | Ubuntu | Deploy Catppuccin Mocha Theme" ansible.builtin.copy: - src: "config/themes/" + src: "{{ _files_path }}/config/themes/" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/themes/" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" - name: "awesomewm | Ubuntu | Deploy rc.lua" ansible.builtin.copy: - src: "config/rc.lua" + src: "{{ _files_path }}/config/rc.lua" dest: "{{ ansible_facts['user_dir'] }}/.config/awesome/rc.lua" mode: "0644" notify: "awesomewm | Ubuntu | Restart AwesomeWM" @@ -150,13 +150,13 @@ - name: "awesomewm | Ubuntu | Deploy rofi configuration" ansible.builtin.copy: - src: "rofi/config.rasi" + src: "{{ _files_path }}/rofi/config.rasi" dest: "{{ ansible_facts['user_dir'] }}/.config/rofi/config.rasi" mode: "0644" - name: "awesomewm | Ubuntu | Deploy rofi Catppuccin theme" ansible.builtin.copy: - src: "rofi/catppuccin-mocha.rasi" + src: "{{ _files_path }}/rofi/catppuccin-mocha.rasi" dest: "{{ ansible_facts['user_dir'] }}/.config/rofi/catppuccin-mocha.rasi" mode: "0644" @@ -230,7 +230,7 @@ - name: "awesomewm | Ubuntu | Deploy Flameshot configuration" ansible.builtin.copy: - src: "flameshot/flameshot.ini" + src: "{{ _files_path }}/flameshot/flameshot.ini" dest: "{{ ansible_facts['user_dir'] }}/.config/flameshot/flameshot.ini" mode: "0644" @@ -242,13 +242,13 @@ - name: "awesomewm | Ubuntu | Deploy CopyQ configuration" ansible.builtin.copy: - src: "copyq/copyq.conf" + src: "{{ _files_path }}/copyq/copyq.conf" dest: "{{ ansible_facts['user_dir'] }}/.config/copyq/copyq.conf" mode: "0644" - name: "awesomewm | Ubuntu | Deploy CopyQ commands" ansible.builtin.copy: - src: "copyq/copyq-commands.ini" + src: "{{ _files_path }}/copyq/copyq-commands.ini" dest: "{{ ansible_facts['user_dir'] }}/.config/copyq/copyq-commands.ini" mode: "0644" diff --git a/roles/bash/tasks/main.yml b/roles/bash/tasks/main.yml index 7ac1d3e1..fab46d54 100644 --- a/roles/bash/tasks/main.yml +++ b/roles/bash/tasks/main.yml @@ -30,20 +30,20 @@ - name: "Bash | Copy .bashrc" ansible.builtin.copy: - src: ".bashrc" + src: "{{ _files_path }}/.bashrc" dest: "{{ ansible_facts['user_dir'] }}/.bashrc" mode: "0644" - name: "Bash | Copy .profile" ansible.builtin.copy: - src: ".profile" + src: "{{ _files_path }}/.profile" dest: "{{ ansible_facts['user_dir'] }}/.profile" mode: "0644" - name: "Bash | Copy custom bash config" ansible.builtin.copy: dest: "{{ ansible_facts['user_dir'] }}/.config/" - src: "bash" + src: "{{ _files_path }}/bash" mode: "0644" directory_mode: "0755" force: true @@ -51,19 +51,19 @@ - name: "Bash | Copy custom bash theme" ansible.builtin.copy: dest: "{{ ansible_facts['user_dir'] }}/.oh-my-bash/custom/" - src: "themes" + src: "{{ _files_path }}/themes" directory_mode: "0755" mode: "0644" force: true - name: "Bash | {{ ansible_facts['distribution'] }} | Identify distribution config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/{{ ansible_facts['distribution'] }}" + path: "{{ _files_path }}/os/{{ ansible_facts['distribution'] }}" register: bash_os_distribution_config - name: "Bash | {{ ansible_facts['os_family'] }} | Identify os family config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/{{ ansible_facts['os_family'] }}" + path: "{{ _files_path }}/os/{{ ansible_facts['os_family'] }}" register: bash_os_family_config when: not bash_os_distribution_config.stat.exists diff --git a/roles/bat/tasks/MacOSX.yml b/roles/bat/tasks/MacOSX.yml index 1752d5fd..c71496a9 100644 --- a/roles/bat/tasks/MacOSX.yml +++ b/roles/bat/tasks/MacOSX.yml @@ -14,7 +14,7 @@ - name: "Bat | MacOSX | Install bat config" ansible.builtin.copy: - src: config + src: "{{ _files_path }}/config" dest: "{{ lookup('env', 'HOME') }}/.config/bat/config" mode: "0644" diff --git a/roles/borders/tasks/MacOSX.yml b/roles/borders/tasks/MacOSX.yml index 5b342ac0..75783519 100644 --- a/roles/borders/tasks/MacOSX.yml +++ b/roles/borders/tasks/MacOSX.yml @@ -17,7 +17,7 @@ - name: "{{ role_name }} | macOS | Deploy bordersrc configuration" ansible.builtin.copy: - src: bordersrc + src: "{{ _files_path }}/bordersrc" dest: "{{ ansible_facts['env']['HOME'] }}/.config/borders/bordersrc" mode: '0755' backup: yes diff --git a/roles/btop/tasks/Fedora.yml b/roles/btop/tasks/Fedora.yml index f2013cda..705caded 100644 --- a/roles/btop/tasks/Fedora.yml +++ b/roles/btop/tasks/Fedora.yml @@ -28,6 +28,6 @@ - name: "BTOP | Copy btop config" ansible.builtin.copy: - src: btop.conf + src: "{{ _files_path }}/btop.conf" dest: ~/.config/btop/btop.conf mode: "0644" \ No newline at end of file diff --git a/roles/btop/tasks/main.yml b/roles/btop/tasks/main.yml index 909b7ace..f29206ad 100644 --- a/roles/btop/tasks/main.yml +++ b/roles/btop/tasks/main.yml @@ -20,12 +20,12 @@ - name: "BTOP | Copy btop config" ansible.builtin.copy: - src: btop.conf + src: "{{ _files_path }}/btop.conf" dest: ~/.config/btop/btop.conf mode: "0644" - name: "BTOP | Copy btop colors" ansible.builtin.copy: - src: catppuccin_mocha.theme + src: "{{ _files_path }}/catppuccin_mocha.theme" dest: ~/.config/btop/themes/catppuccin_mocha.theme mode: "0644" diff --git a/roles/claude/tasks/main.yml b/roles/claude/tasks/main.yml index 0725a8eb..6fe3a832 100644 --- a/roles/claude/tasks/main.yml +++ b/roles/claude/tasks/main.yml @@ -30,7 +30,7 @@ - name: "{{ role_name }} | Create symlink to Claude settings.json" ansible.builtin.file: - src: "{{ role_path }}/files/settings.json" + src: "{{ _files_path }}/settings.json" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/settings.json" state: link force: true @@ -48,7 +48,7 @@ - name: "{{ role_name }} | Create symlink to Claude scripts" ansible.builtin.file: - src: "{{ role_path }}/files/scripts" + src: "{{ _files_path }}/scripts" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/scripts" state: link force: true @@ -66,7 +66,7 @@ - name: "{{ role_name }} | Create symlink to Claude commands" ansible.builtin.file: - src: "{{ role_path }}/files/commands" + src: "{{ _files_path }}/commands" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/commands" state: link force: true @@ -84,7 +84,7 @@ - name: "{{ role_name }} | Create symlink to Claude hooks" ansible.builtin.file: - src: "{{ role_path }}/files/hooks" + src: "{{ _files_path }}/hooks" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/hooks" state: link force: true @@ -102,7 +102,7 @@ - name: "{{ role_name }} | Create symlink to Claude agents" ansible.builtin.file: - src: "{{ role_path }}/files/agents" + src: "{{ _files_path }}/agents" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/agents" state: link force: true @@ -120,7 +120,7 @@ - name: "{{ role_name }} | Create symlink to Claude skills" ansible.builtin.file: - src: "{{ role_path }}/files/skills" + src: "{{ _files_path }}/skills" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/skills" state: link force: true @@ -138,7 +138,7 @@ - name: "{{ role_name }} | Create symlink to Claude LSP configuration" ansible.builtin.file: - src: "{{ role_path }}/files/.lsp.json" + src: "{{ _files_path }}/.lsp.json" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/.lsp.json" state: link force: true diff --git a/roles/gh/tasks/main.yml b/roles/gh/tasks/main.yml index 561e0455..2f3c6ec7 100644 --- a/roles/gh/tasks/main.yml +++ b/roles/gh/tasks/main.yml @@ -17,7 +17,7 @@ - name: "{{ role_name }} | Copy gh-dash config" ansible.builtin.copy: - src: config.yaml + src: "{{ _files_path }}/config.yaml" dest: "{{ ansible_facts['env']['HOME'] }}/.config/gh-dash/config.yaml" mode: '0644' owner: "{{ ansible_facts['env']['USER'] }}" diff --git a/roles/ghostty/tasks/main.yml b/roles/ghostty/tasks/main.yml index fdc4af08..a8aee68e 100644 --- a/roles/ghostty/tasks/main.yml +++ b/roles/ghostty/tasks/main.yml @@ -16,20 +16,20 @@ - name: "{{ role_name }} | Deploy Configuration" ansible.builtin.copy: - src: config + src: "{{ _files_path }}/config" dest: "{{ ansible_facts['env']['HOME'] }}/.config/ghostty/config" mode: "0644" - name: "{{ role_name }} | Deploy Shaders directory" ansible.builtin.copy: - src: shaders + src: "{{ _files_path }}/shaders" dest: "{{ ansible_facts['env']['HOME'] }}/.config/ghostty/" mode: "0644" directory_mode: "0755" - name: "{{ role_name }} | Deploy Themes directory" ansible.builtin.copy: - src: themes + src: "{{ _files_path }}/themes" dest: "{{ ansible_facts['env']['HOME'] }}/.config/ghostty/" mode: "0644" directory_mode: "0755" diff --git a/roles/git/tasks/main.yml b/roles/git/tasks/main.yml index 356effb1..57973099 100644 --- a/roles/git/tasks/main.yml +++ b/roles/git/tasks/main.yml @@ -189,7 +189,7 @@ - name: "Git | Copy global.commit.template" ansible.builtin.copy: - src: global.commit.template + src: "{{ _files_path }}/global.commit.template" dest: "{{ ansible_facts['user_dir'] }}/.config/git/commit_template" mode: "0644" diff --git a/roles/glab/tasks/main.yml b/roles/glab/tasks/main.yml index 11621046..952d70ca 100644 --- a/roles/glab/tasks/main.yml +++ b/roles/glab/tasks/main.yml @@ -29,7 +29,7 @@ - name: "{{ role_name }} | Configure | Default aliases" ansible.builtin.copy: - src: "{{ role_path }}/files/config.yml" + src: "{{ _files_path }}/config.yml" dest: "{{ ansible_facts['user_dir'] }}/.config/glab-cli/config.yml" mode: "0600" when: not glab_config.stat.exists diff --git a/roles/hammerspoon/tasks/MacOSX.yml b/roles/hammerspoon/tasks/MacOSX.yml index 8a5a7a0c..4c471581 100644 --- a/roles/hammerspoon/tasks/MacOSX.yml +++ b/roles/hammerspoon/tasks/MacOSX.yml @@ -17,7 +17,7 @@ - name: "Hammerspoon | MacOSX | Deploy Karabiner Configuration (CapsLock to F13)" ansible.builtin.copy: - src: "karabiner/karabiner.json" + src: "{{ _files_path }}/karabiner/karabiner.json" dest: "{{ ansible_facts['user_dir'] }}/.config/karabiner/karabiner.json" mode: "0644" @@ -35,7 +35,7 @@ - name: "Hammerspoon | MacOSX | Deploy Hammerspoon Configuration" ansible.builtin.copy: - src: "config/" + src: "{{ _files_path }}/config/" dest: "{{ ansible_facts['user_dir'] }}/.hammerspoon" mode: "0644" diff --git a/roles/kitty/tasks/Fedora.yml b/roles/kitty/tasks/Fedora.yml index ce298c8b..2788c569 100644 --- a/roles/kitty/tasks/Fedora.yml +++ b/roles/kitty/tasks/Fedora.yml @@ -52,7 +52,7 @@ - name: "Kitty | {{ ansible_facts['distribution'] }} | Deploy kitty.conf" ansible.builtin.copy: - src: "kitty.conf" + src: "{{ _files_path }}/kitty.conf" dest: "{{ ansible_facts['env']['HOME'] }}/.config/kitty/kitty.conf" mode: '0644' backup: yes diff --git a/roles/kitty/tasks/MacOSX.yml b/roles/kitty/tasks/MacOSX.yml index 98af7794..365b7bc6 100644 --- a/roles/kitty/tasks/MacOSX.yml +++ b/roles/kitty/tasks/MacOSX.yml @@ -16,7 +16,7 @@ - name: "Kitty | MacOSX | Checking for Distribution Config: {{ ansible_facts['distribution'] }}" ansible.builtin.stat: - path: "{{ role_path }}/files/kitty_{{ ansible_facts['distribution'] }}.conf" + path: "{{ _files_path }}/kitty_{{ ansible_facts['distribution'] }}.conf" register: kitty_distribution_config - name: "Kitty | MacOSX | Found, Setting Kitty Config: kitty_{{ ansible_facts['distribution'] }}.conf" @@ -27,6 +27,6 @@ - name: "Kitty | MacOSX | Deploy Kitty Config: {{ kitty_config }}" ansible.builtin.copy: - src: "{{ kitty_config }}" + src: "{{ _files_path }}/{{ kitty_config }}" dest: "{{ ansible_facts['user_dir'] }}/.config/kitty/kitty.conf" mode: "0644" diff --git a/roles/lsd/tasks/main.yml b/roles/lsd/tasks/main.yml index 4c821a4a..7e4e4894 100644 --- a/roles/lsd/tasks/main.yml +++ b/roles/lsd/tasks/main.yml @@ -16,12 +16,12 @@ - name: "LSD | Copy lsd config" ansible.builtin.copy: - src: "config.yaml" + src: "{{ _files_path }}/config.yaml" dest: "{{ ansible_facts['user_dir'] }}/.config/lsd/config.yaml" mode: "0644" - name: "LSD | Copy colors.yaml" ansible.builtin.copy: - src: "colors.yaml" + src: "{{ _files_path }}/colors.yaml" dest: "{{ ansible_facts['user_dir'] }}/.config/lsd/colors.yaml" mode: "0644" diff --git a/roles/neofetch/tasks/main.yml b/roles/neofetch/tasks/main.yml index 88e807a2..a13c2730 100644 --- a/roles/neofetch/tasks/main.yml +++ b/roles/neofetch/tasks/main.yml @@ -16,6 +16,6 @@ - name: "Neofetch | {{ ansible_facts['distribution'] }} | Copy config file" ansible.builtin.copy: - src: config + src: "{{ _files_path }}/config" dest: "{{ ansible_facts['user_dir'] }}/.config/neofetch/config" mode: "0644" diff --git a/roles/neovim/tasks/main.yml b/roles/neovim/tasks/main.yml index 70ae01f3..5dd48771 100644 --- a/roles/neovim/tasks/main.yml +++ b/roles/neovim/tasks/main.yml @@ -31,7 +31,7 @@ - name: "Neovim | Create symlink to role files directory" ansible.builtin.file: - src: "{{ role_path }}/files" + src: "{{ _files_path }}" dest: "{{ ansible_facts['user_dir'] }}/.config/nvim" state: link force: true diff --git a/roles/opencode/tasks/main.yml b/roles/opencode/tasks/main.yml index a7a1059d..4ccaf1c9 100644 --- a/roles/opencode/tasks/main.yml +++ b/roles/opencode/tasks/main.yml @@ -52,7 +52,7 @@ - name: "{{ role_name }} | Create symlink to opencode.json" ansible.builtin.file: - src: "{{ role_path }}/files/opencode.json" + src: "{{ _files_path }}/opencode.json" dest: "{{ ansible_facts['env']['HOME'] }}/.config/opencode/opencode.json" state: link force: true @@ -70,7 +70,7 @@ - name: "{{ role_name }} | Create symlink to opencode scripts" ansible.builtin.file: - src: "{{ role_path }}/files/scripts" + src: "{{ _files_path }}/scripts" dest: "{{ ansible_facts['env']['HOME'] }}/.config/opencode/scripts" state: link force: true @@ -88,7 +88,7 @@ - name: "{{ role_name }} | Create symlink to opencode commands" ansible.builtin.file: - src: "{{ role_path }}/files/command" + src: "{{ _files_path }}/command" dest: "{{ ansible_facts['env']['HOME'] }}/.config/opencode/command" state: link force: true @@ -114,7 +114,7 @@ - name: "{{ role_name }} | Create symlink to AGENTS.md" ansible.builtin.file: - src: "{{ role_path }}/files/AGENTS.md" + src: "{{ _files_path }}/AGENTS.md" dest: "{{ ansible_facts['env']['HOME'] }}/.config/opencode/AGENTS.md" state: link force: true @@ -168,7 +168,7 @@ - name: "{{ role_name }} | Create symlink to oh-my-opencode.json" ansible.builtin.file: - src: "{{ role_path }}/files/oh-my-opencode.json" + src: "{{ _files_path }}/oh-my-opencode.json" dest: "{{ ansible_facts['env']['HOME'] }}/.config/opencode/oh-my-opencode.json" state: link force: true diff --git a/roles/sesh/tasks/config.yml b/roles/sesh/tasks/config.yml index 6b81a35d..655f51d1 100644 --- a/roles/sesh/tasks/config.yml +++ b/roles/sesh/tasks/config.yml @@ -7,6 +7,6 @@ - name: "{{ role_name }} | Deploy sesh configuration" ansible.builtin.copy: - src: sesh.toml + src: "{{ _files_path }}/sesh.toml" dest: ~/.config/sesh/sesh.toml mode: '0644' \ No newline at end of file diff --git a/roles/system/tasks/Ubuntu.yml b/roles/system/tasks/Ubuntu.yml index c3d6ab97..833a8f60 100644 --- a/roles/system/tasks/Ubuntu.yml +++ b/roles/system/tasks/Ubuntu.yml @@ -134,7 +134,7 @@ - name: "System | {{ ansible_facts['distribution'] }} | Configure power button to suspend" ansible.builtin.copy: - src: logind.conf.d/power-button.conf + src: "{{ _files_path }}/logind.conf.d/power-button.conf" dest: /etc/systemd/logind.conf.d/power-button.conf mode: "0644" become: true diff --git a/roles/taskfile/tasks/main.yml b/roles/taskfile/tasks/main.yml index cf3dc68c..208707fc 100644 --- a/roles/taskfile/tasks/main.yml +++ b/roles/taskfile/tasks/main.yml @@ -23,12 +23,12 @@ block: - name: "TASKFILE | {{ ansible_facts['distribution'] }} | Identify distribution config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/Taskfile_{{ ansible_facts['distribution'] }}.yml" + path: "{{ _files_path }}/os/Taskfile_{{ ansible_facts['distribution'] }}.yml" register: taskfile_os_distribution_config - name: "TASKFILE | {{ ansible_facts['os_family'] }} | Identify os family config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/Taskfile_{{ ansible_facts['os_family'] }}.yml" + path: "{{ _files_path }}/os/Taskfile_{{ ansible_facts['os_family'] }}.yml" register: taskfile_os_family_config when: not taskfile_os_distribution_config.stat.exists diff --git a/roles/tmux/tasks/main.yml b/roles/tmux/tasks/main.yml index bed2efbb..cbfff948 100644 --- a/roles/tmux/tasks/main.yml +++ b/roles/tmux/tasks/main.yml @@ -15,7 +15,7 @@ - name: "Tmux | Configure tmux" ansible.builtin.copy: - src: tmux + src: "{{ _files_path }}/tmux" dest: "{{ ansible_facts['user_dir'] }}/.config/" mode: "0644" directory_mode: "0755" diff --git a/roles/zsh/tasks/Fedora.yml b/roles/zsh/tasks/Fedora.yml index 9a56b8f5..fefeccdd 100644 --- a/roles/zsh/tasks/Fedora.yml +++ b/roles/zsh/tasks/Fedora.yml @@ -112,7 +112,7 @@ - name: "Zsh | Deploy .zshrc" ansible.builtin.copy: - src: ".zshrc" + src: "{{ _files_path }}/.zshrc" dest: "{{ ansible_facts['env']['HOME'] }}/.zshrc" mode: '0644' backup: yes diff --git a/roles/zsh/tasks/Ubuntu.yml b/roles/zsh/tasks/Ubuntu.yml index d6c70da8..9bb6000f 100644 --- a/roles/zsh/tasks/Ubuntu.yml +++ b/roles/zsh/tasks/Ubuntu.yml @@ -112,7 +112,7 @@ - name: "Zsh | Deploy .zshrc" ansible.builtin.copy: - src: ".zshrc" + src: "{{ _files_path }}/.zshrc" dest: "{{ ansible_facts['env']['HOME'] }}/.zshrc" mode: '0644' backup: yes diff --git a/roles/zsh/tasks/main.yml b/roles/zsh/tasks/main.yml index 05ed981b..8cd17309 100644 --- a/roles/zsh/tasks/main.yml +++ b/roles/zsh/tasks/main.yml @@ -10,32 +10,32 @@ - name: "ZSH | Copy .zshrc" ansible.builtin.copy: - src: ".zshrc" + src: "{{ _files_path }}/.zshrc" dest: "{{ ansible_facts['user_dir'] }}/.zshrc" mode: "0644" - name: "ZSH | Copy .p10k.zsh" ansible.builtin.copy: - src: ".p10k.zsh" + src: "{{ _files_path }}/.p10k.zsh" dest: "{{ ansible_facts['user_dir'] }}/.p10k.zsh" mode: "0644" - name: "ZSH | Copy custom zsh config" ansible.builtin.copy: dest: "{{ ansible_facts['user_dir'] }}/.config/" - src: "zsh" + src: "{{ _files_path }}/zsh" mode: "0644" directory_mode: "0755" force: true - name: "ZSH | {{ ansible_facts['distribution'] }} | Identify distribution config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/{{ ansible_facts['distribution'] }}" + path: "{{ _files_path }}/os/{{ ansible_facts['distribution'] }}" register: zsh_os_distribution_config - name: "ZSH | {{ ansible_facts['os_family'] }} | Identify os family config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/{{ ansible_facts['os_family'] }}" + path: "{{ _files_path }}/os/{{ ansible_facts['os_family'] }}" register: zsh_os_family_config when: not zsh_os_distribution_config.stat.exists diff --git a/tasks/run_role_with_overrides.yml b/tasks/run_role_with_overrides.yml new file mode 100644 index 00000000..a31c8784 --- /dev/null +++ b/tasks/run_role_with_overrides.yml @@ -0,0 +1,64 @@ +--- +# Wrapper task for running roles with optional overrides +# Supports three override levels: +# - vars: overrides/roles/{role}/vars/main.yml - merged before role runs +# - files: overrides/roles/{role}/files/ - sets _files_path for file operations +# - tasks: overrides/roles/{role}/tasks/main.yml - REPLACES role entirely + +# 1. Check if full task override exists +- name: "Override | Check for task override: {{ _role_name }}" + ansible.builtin.stat: + path: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/tasks/main.yml" + register: _task_override + tags: + - always + +# 2. Check and load vars override if exists +- name: "Override | Check for vars override: {{ _role_name }}" + ansible.builtin.stat: + path: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/vars/main.yml" + register: _vars_override + tags: + - always + +- name: "Override | Load vars override: {{ _role_name }}" + ansible.builtin.include_vars: + file: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/vars/main.yml" + when: _vars_override.stat.exists + tags: + - always + +# 3. Check and set _files_path (override if exists, else default) +- name: "Override | Check for files override: {{ _role_name }}" + ansible.builtin.stat: + path: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/files" + register: _files_override + tags: + - always + +- name: "Override | Set files path: {{ _role_name }}" + ansible.builtin.set_fact: + _files_path: "{{ (playbook_dir ~ '/overrides/roles/' ~ _role_name ~ '/files') if _files_override.stat.exists else (playbook_dir ~ '/roles/' ~ _role_name ~ '/files') }}" + tags: + - always + +# 4. BRANCH: Run override tasks OR original role +- name: "Override | Run override tasks: {{ _role_name }}" + ansible.builtin.include_tasks: + file: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/tasks/main.yml" + apply: + tags: + - "{{ _role_name }}" + when: _task_override.stat.exists + tags: + - always + +- name: "Override | Run role: {{ _role_name }}" + ansible.builtin.include_role: + name: "{{ _role_name }}" + apply: + tags: + - "{{ _role_name }}" + when: not _task_override.stat.exists + tags: + - always