From 283d7ce9a158453d4e8930de6ce422132b686ba5 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 10:06:32 -0600 Subject: [PATCH 01/13] feat(overrides): add wrapper task for selective override support Introduces a wrapper task that checks for three override levels before running each role: - vars: loads overrides/{role}/vars/main.yml to merge/override variables - files: sets _files_path for file operations - tasks: replaces role entirely with override tasks Based on approach from PR #144 but centralized in a single wrapper. --- tasks/run_role_with_overrides.yml | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tasks/run_role_with_overrides.yml diff --git a/tasks/run_role_with_overrides.yml b/tasks/run_role_with_overrides.yml new file mode 100644 index 00000000..36d9bd88 --- /dev/null +++ b/tasks/run_role_with_overrides.yml @@ -0,0 +1,61 @@ +--- +# Wrapper task for running roles with optional overrides +# Supports three override levels: +# - vars: overrides/{role}/vars/main.yml - merged before role runs +# - files: overrides/{role}/files/ - sets _files_path for file operations +# - tasks: overrides/{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/{{ _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/{{ _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/{{ _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/{{ _role_name }}/files" + register: _files_override + tags: + - always + +- name: "Override | Set files path: {{ _role_name }}" + ansible.builtin.set_fact: + _files_path: "{{ (playbook_dir ~ '/overrides/' ~ _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/{{ _role_name }}/tasks/main.yml" + 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 From aef860598d783df2e9a7c722f6d67833f0727d00 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 10:07:41 -0600 Subject: [PATCH 02/13] feat(overrides): use wrapper for role execution in main.yml Replace direct include_role with include_tasks calling the new wrapper. Tag filtering continues to work via tags: always on wrapper tasks. --- main.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 }}" From c359dcacb5ce056c8f94eb3df155fe74209b3b73 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 10:08:24 -0600 Subject: [PATCH 03/13] feat(overrides): update roles to support file overrides Update 7 roles to use _files_path variable with fallback to role_path: - neovim, claude, opencode, glab: symlink source paths - zsh, bash, taskfile: stat path checks for OS-specific configs Pattern: {{ _files_path | default(role_path ~ '/files') }} Maintains backward compatibility when _files_path is not set. --- roles/bash/tasks/main.yml | 4 ++-- roles/claude/tasks/main.yml | 14 +++++++------- roles/glab/tasks/main.yml | 2 +- roles/neovim/tasks/main.yml | 2 +- roles/opencode/tasks/main.yml | 10 +++++----- roles/taskfile/tasks/main.yml | 4 ++-- roles/zsh/tasks/main.yml | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/roles/bash/tasks/main.yml b/roles/bash/tasks/main.yml index 7ac1d3e1..9a8fc02f 100644 --- a/roles/bash/tasks/main.yml +++ b/roles/bash/tasks/main.yml @@ -58,12 +58,12 @@ - name: "Bash | {{ ansible_facts['distribution'] }} | Identify distribution config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/{{ ansible_facts['distribution'] }}" + path: "{{ _files_path | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/os/{{ ansible_facts['os_family'] }}" register: bash_os_family_config when: not bash_os_distribution_config.stat.exists diff --git a/roles/claude/tasks/main.yml b/roles/claude/tasks/main.yml index 0725a8eb..0cac80fa 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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/.lsp.json" dest: "{{ ansible_facts['env']['HOME'] }}/.claude/.lsp.json" state: link force: true diff --git a/roles/glab/tasks/main.yml b/roles/glab/tasks/main.yml index 11621046..dc0a8645 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 | default(role_path ~ '/files') }}/config.yml" dest: "{{ ansible_facts['user_dir'] }}/.config/glab-cli/config.yml" mode: "0600" when: not glab_config.stat.exists diff --git a/roles/neovim/tasks/main.yml b/roles/neovim/tasks/main.yml index 70ae01f3..19b90150 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 | default(role_path ~ '/files') }}" 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..4c560752 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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/oh-my-opencode.json" dest: "{{ ansible_facts['env']['HOME'] }}/.config/opencode/oh-my-opencode.json" state: link force: true diff --git a/roles/taskfile/tasks/main.yml b/roles/taskfile/tasks/main.yml index cf3dc68c..ac8b21e7 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 | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/os/Taskfile_{{ ansible_facts['os_family'] }}.yml" register: taskfile_os_family_config when: not taskfile_os_distribution_config.stat.exists diff --git a/roles/zsh/tasks/main.yml b/roles/zsh/tasks/main.yml index 05ed981b..5a8a3ac9 100644 --- a/roles/zsh/tasks/main.yml +++ b/roles/zsh/tasks/main.yml @@ -30,12 +30,12 @@ - name: "ZSH | {{ ansible_facts['distribution'] }} | Identify distribution config" ansible.builtin.stat: - path: "{{ role_path }}/files/os/{{ ansible_facts['distribution'] }}" + path: "{{ _files_path | default(role_path ~ '/files') }}/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 | default(role_path ~ '/files') }}/os/{{ ansible_facts['os_family'] }}" register: zsh_os_family_config when: not zsh_os_distribution_config.stat.exists From 36a6f0863d265760db9cec7c0a97e3580ee79cf4 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 10:09:08 -0600 Subject: [PATCH 04/13] docs(overrides): add override directory with usage guide Create gitignored overrides directory for fork customizations: - README.md documents three override levels (vars, files, tasks) - .gitkeep ensures directory is tracked - Fork users can customize without modifying upstream files --- overrides/.gitkeep | 0 overrides/README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 overrides/.gitkeep create mode 100644 overrides/README.md 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..3d257fb3 --- /dev/null +++ b/overrides/README.md @@ -0,0 +1,65 @@ +# Overrides Directory + +Fork-friendly customization system. Override any role without modifying upstream files. + +## Override Levels + +| Level | Path | Behavior | +|-------|------|----------| +| **Vars** | `overrides/{role}/vars/main.yml` | Merged before role runs | +| **Files** | `overrides/{role}/files/` | Role uses these files instead | +| **Tasks** | `overrides/{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/git/vars +cat > overrides/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/neovim/files +cp -r roles/neovim/files/* overrides/neovim/files/ +# Edit files in overrides/neovim/files/ as needed +``` + +### Replace Role Entirely + +```bash +mkdir -p overrides/custom-tool/tasks +cat > overrides/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 +``` + +## 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 From 7db4556eebb118a185addc9402cafc0524bd6f16 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 10:09:26 -0600 Subject: [PATCH 05/13] chore: gitignore user overrides directory Ignore override content while preserving structure files: - overrides/*/ ignored (user customizations) - .gitkeep and README.md preserved for structure --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 1729ad0c..c7b1fafc 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,8 @@ node_modules/ # Task files tasks.json + +# User overrides (fork customizations) +overrides/*/ +!overrides/.gitkeep +!overrides/README.md From 7c20be3c53383943269dda7a75f8ec06b5ed1a11 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 10:40:39 -0600 Subject: [PATCH 06/13] feat(overrides): complete _files_path migration Convert all roles with implicit file references to use explicit _files_path variable, enabling fork users to override any role's config files via overrides/{role}/files/ directory. Changes across 25 roles: - Simplify 7 roles that had verbose fallback pattern - Add _files_path to 18 new roles with copy/synchronize tasks - Update wrapper to always set _files_path before role execution Roles updated: 1password, alacritty, awesomewm, bash, bat, borders, btop, claude, gh, ghostty, git, glab, hammerspoon, kitty, lsd, neofetch, neovim, opencode, sesh, system, taskfile, tmux, zsh --- roles/1password/tasks/main.yml | 2 +- roles/alacritty/tasks/wsl.yml | 2 +- roles/awesomewm/tasks/Ubuntu.yml | 24 ++++++++++++------------ roles/bash/tasks/main.yml | 12 ++++++------ roles/bat/tasks/MacOSX.yml | 2 +- roles/borders/tasks/MacOSX.yml | 2 +- roles/btop/tasks/Fedora.yml | 2 +- roles/btop/tasks/main.yml | 4 ++-- roles/claude/tasks/main.yml | 14 +++++++------- roles/gh/tasks/main.yml | 2 +- roles/ghostty/tasks/main.yml | 6 +++--- roles/git/tasks/main.yml | 2 +- roles/glab/tasks/main.yml | 2 +- roles/hammerspoon/tasks/MacOSX.yml | 4 ++-- roles/kitty/tasks/Fedora.yml | 2 +- roles/kitty/tasks/MacOSX.yml | 4 ++-- roles/lsd/tasks/main.yml | 4 ++-- roles/neofetch/tasks/main.yml | 2 +- roles/neovim/tasks/main.yml | 2 +- roles/opencode/tasks/main.yml | 10 +++++----- roles/sesh/tasks/config.yml | 2 +- roles/system/tasks/Ubuntu.yml | 2 +- roles/taskfile/tasks/main.yml | 4 ++-- roles/tmux/tasks/main.yml | 2 +- roles/zsh/tasks/Fedora.yml | 2 +- roles/zsh/tasks/Ubuntu.yml | 2 +- roles/zsh/tasks/main.yml | 10 +++++----- tasks/run_role_with_overrides.yml | 3 +++ 28 files changed, 67 insertions(+), 64 deletions(-) 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 9a8fc02f..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: "{{ _files_path | default(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: "{{ _files_path | default(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 0cac80fa..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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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 dc0a8645..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: "{{ _files_path | default(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 19b90150..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: "{{ _files_path | default(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 4c560752..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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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: "{{ _files_path | default(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 ac8b21e7..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: "{{ _files_path | default(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: "{{ _files_path | default(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 5a8a3ac9..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: "{{ _files_path | default(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: "{{ _files_path | default(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 index 36d9bd88..c7f2d500 100644 --- a/tasks/run_role_with_overrides.yml +++ b/tasks/run_role_with_overrides.yml @@ -46,6 +46,9 @@ - name: "Override | Run override tasks: {{ _role_name }}" ansible.builtin.include_tasks: file: "{{ playbook_dir }}/overrides/{{ _role_name }}/tasks/main.yml" + apply: + tags: + - "{{ _role_name }}" when: _task_override.stat.exists tags: - always From c634765061beb8e17582d5dd251f6484f7be3baf Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 11:02:15 -0600 Subject: [PATCH 07/13] docs(overrides): add fork-friendly documentation Make the override system discoverable with prominent documentation: - README.md: Add "Fork This Project" section after Quick Start with override types table and common customizations - overrides/README.md: Expand from 66 to 143 lines with Quick Start workflow, debugging guide, partial override warnings, migration guide for existing forkers - CLAUDE.md: Add override row to "Where to Look" table - docs/README.md: Add Forking & Customization section with link --- README.md | 29 +++++++++++++++++ docs/README.md | 6 +++- overrides/README.md | 78 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c23ac2b0..d935118e 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/{role}/vars/main.yml` | Change settings (paths, names, options) | +| Config Files | `overrides/{role}/files/` | Replace config files with your own | +| Tasks | `overrides/{role}/tasks/main.yml` | Replace entire role behavior | + +### Common Customizations + +| What | Override | Example | +|------|----------|---------| +| Git identity | `overrides/git/vars/main.yml` | Your name and email | +| Shell config | `overrides/zsh/files/.zshrc` | Personal aliases | +| Terminal | `overrides/ghostty/files/config` | Theme, font, colors | +| Editor | `overrides/neovim/files/` | Plugins, keybindings | + +See [`overrides/README.md`](overrides/README.md) for the complete guide. + --- ## 🎯 Goals 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/overrides/README.md b/overrides/README.md index 3d257fb3..591c5423 100644 --- a/overrides/README.md +++ b/overrides/README.md @@ -2,6 +2,16 @@ 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/{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 | @@ -57,9 +67,77 @@ cat > overrides/custom-tool/tasks/main.yml << 'EOF' EOF ``` +## Commonly Overridden Files + +| What to Customize | Override Path | Key Settings | +|-------------------|---------------|--------------| +| Git identity | `overrides/git/vars/main.yml` | `git_user_name`, `git_user_email` | +| Git config | `overrides/git/files/.gitconfig` | Aliases, merge strategy | +| Shell aliases | `overrides/zsh/files/.zshrc` | Personal shortcuts | +| Terminal colors | `overrides/ghostty/files/config` | Theme, font, opacity | +| Editor config | `overrides/neovim/files/` | Plugins, keybindings | +| Tmux config | `overrides/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/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/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 + +## 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/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/{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/{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/{role}/files` +3. **Copy your modified files** - Move from `roles/{role}/files/` to `overrides/{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. From 3a87175512e0f92df0500969a4d8fdb70d64c1c4 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 11:12:28 -0600 Subject: [PATCH 08/13] chore: update .claude gitignore for custom content Ignore personal Claude Code files while preserving custom content: - Ignore: settings, projects/, notepads/, temp .md files - Keep: agents/, skills/, commands/ for repo-specific customizations --- .gitignore | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c7b1fafc..96679636 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/ From 533e1f23e49834487926aa814a8ba8a6aacb9116 Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 11:24:30 -0600 Subject: [PATCH 09/13] feat(bootstrap): add fork customization support Allow forkers to customize the bootstrap script without merge conflicts: - Add DOTFILES_GITHUB_USER and DOTFILES_REPO_NAME variables - Source overrides/dotfiles.conf if present for runtime overrides - Update clone URL to use variables instead of hardcoded values - Update help/version output to reflect configured user - Add dotfiles.conf.example with documented settings - Document bootstrap customization in overrides/README.md Forkers can either edit bin/dotfiles directly or use the config file approach for cleaner upstream merges. --- bin/dotfiles | 18 +++++++++++++---- overrides/README.md | 36 +++++++++++++++++++++++++++++++++ overrides/dotfiles.conf.example | 11 ++++++++++ 3 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 overrides/dotfiles.conf.example diff --git a/bin/dotfiles b/bin/dotfiles index badc0142..ac5c9d9a 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/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/overrides/README.md b/overrides/README.md index 591c5423..128d216f 100644 --- a/overrides/README.md +++ b/overrides/README.md @@ -141,3 +141,39 @@ If you've already forked and modified role files directly: 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/dotfiles.conf.example overrides/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/dotfiles.conf.example b/overrides/dotfiles.conf.example new file mode 100644 index 00000000..540a80ab --- /dev/null +++ b/overrides/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" From 34e79de20c0df6a602d21970bd60f4605cf2f43e Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 11:35:58 -0600 Subject: [PATCH 10/13] refactor(overrides): restructure directory with schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize overrides directory for clarity and extensibility: Structure: overrides/ ├── roles/{role}/ # Per-role overrides (vars, files, tasks) ├── config/ # Bootstrap configuration │ └── dotfiles.conf # User settings (gitignored) └── README.md Changes: - Move role overrides under overrides/roles/ subdirectory - Move dotfiles.conf to overrides/config/ - Update wrapper task, bin/dotfiles, and all documentation - Update gitignore patterns for new structure This schema allows clean separation between role overrides, config, and future override types (group_vars, pre_tasks, etc). --- .gitignore | 5 +- README.md | 14 +++--- bin/dotfiles | 2 +- overrides/README.md | 50 ++++++++++---------- overrides/{ => config}/dotfiles.conf.example | 0 tasks/run_role_with_overrides.yml | 18 +++---- 6 files changed, 45 insertions(+), 44 deletions(-) rename overrides/{ => config}/dotfiles.conf.example (100%) diff --git a/.gitignore b/.gitignore index 96679636..26926db9 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ node_modules/ tasks.json # User overrides (fork customizations) -overrides/*/ -!overrides/.gitkeep +overrides/roles/ +overrides/config/dotfiles.conf +!overrides/config/dotfiles.conf.example !overrides/README.md diff --git a/README.md b/README.md index d935118e..89c5b6f6 100644 --- a/README.md +++ b/README.md @@ -77,18 +77,18 @@ bash -c "$(curl -fsSL https://raw.githubusercontent.com/TechDufus/dotfiles/main/ | Override Type | Location | Effect | |--------------|----------|--------| -| Variables | `overrides/{role}/vars/main.yml` | Change settings (paths, names, options) | -| Config Files | `overrides/{role}/files/` | Replace config files with your own | -| Tasks | `overrides/{role}/tasks/main.yml` | Replace entire role behavior | +| 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/git/vars/main.yml` | Your name and email | -| Shell config | `overrides/zsh/files/.zshrc` | Personal aliases | -| Terminal | `overrides/ghostty/files/config` | Theme, font, colors | -| Editor | `overrides/neovim/files/` | Plugins, keybindings | +| 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. diff --git a/bin/dotfiles b/bin/dotfiles index ac5c9d9a..d43e71e7 100755 --- a/bin/dotfiles +++ b/bin/dotfiles @@ -115,7 +115,7 @@ 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/dotfiles.conf" +DOTFILES_CONFIG="$HOME/.dotfiles/overrides/config/dotfiles.conf" if [[ -f "$DOTFILES_CONFIG" ]]; then # shellcheck source=/dev/null source "$DOTFILES_CONFIG" diff --git a/overrides/README.md b/overrides/README.md index 128d216f..95ef001b 100644 --- a/overrides/README.md +++ b/overrides/README.md @@ -6,7 +6,7 @@ Fork-friendly customization system. Override any role without modifying upstream 1. **Fork** the repository on GitHub 2. **Clone** your fork locally -3. **Create** your override: `mkdir -p overrides/{role}/files` +3. **Create** your override: `mkdir -p overrides/roles/{role}/files` 4. **Customize** by copying and editing files 5. **Run** `dotfiles` - your overrides apply automatically @@ -16,9 +16,9 @@ Your overrides stay local (gitignored), so you can pull upstream updates without | Level | Path | Behavior | |-------|------|----------| -| **Vars** | `overrides/{role}/vars/main.yml` | Merged before role runs | -| **Files** | `overrides/{role}/files/` | Role uses these files instead | -| **Tasks** | `overrides/{role}/tasks/main.yml` | **Replaces role entirely** | +| **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 @@ -33,8 +33,8 @@ The wrapper checks for overrides before running each role: ### Override Variables Only ```bash -mkdir -p overrides/git/vars -cat > overrides/git/vars/main.yml << 'EOF' +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 @@ -43,16 +43,16 @@ EOF ### Override Config Files Only ```bash -mkdir -p overrides/neovim/files -cp -r roles/neovim/files/* overrides/neovim/files/ -# Edit files in overrides/neovim/files/ as needed +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/custom-tool/tasks -cat > overrides/custom-tool/tasks/main.yml << 'EOF' +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: @@ -71,12 +71,12 @@ EOF | What to Customize | Override Path | Key Settings | |-------------------|---------------|--------------| -| Git identity | `overrides/git/vars/main.yml` | `git_user_name`, `git_user_email` | -| Git config | `overrides/git/files/.gitconfig` | Aliases, merge strategy | -| Shell aliases | `overrides/zsh/files/.zshrc` | Personal shortcuts | -| Terminal colors | `overrides/ghostty/files/config` | Theme, font, opacity | -| Editor config | `overrides/neovim/files/` | Plugins, keybindings | -| Tmux config | `overrides/tmux/files/tmux/` | Prefix key, status bar | +| 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 @@ -88,11 +88,11 @@ The `_files_path` variable points to ONE directory - either your override OR the - `.gitconfig` - `global.commit.template` -And you only put `.gitconfig` in `overrides/git/files/`, the role will fail looking for `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/git/files/ +cp -r roles/git/files/* overrides/roles/git/files/ # Now edit only the files you want to change ``` @@ -117,7 +117,7 @@ dotfiles -t git -vvv 2>&1 | grep -E "(override|_files_path)" In your playbook output, look for: ``` TASK [run_role_with_overrides : Set _files_path] -ok: [localhost] => {"ansible_facts": {"_files_path": "/path/to/overrides/git/files"}} +ok: [localhost] => {"ansible_facts": {"_files_path": "/path/to/overrides/roles/git/files"}} ``` If it shows `roles/git/files` instead, your override wasn't detected. @@ -126,17 +126,17 @@ If it shows `roles/git/files` instead, your override wasn't detected. | Problem | Cause | Fix | |---------|-------|-----| -| Override not applied | Wrong directory structure | Check path: `overrides/{role}/files/` not `override/` | +| 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/{role}/vars/main.yml` | +| 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/{role}/files` -3. **Copy your modified files** - Move from `roles/{role}/files/` to `overrides/{role}/files/` +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 @@ -165,7 +165,7 @@ When you fork this repo, the bootstrap script needs to know to clone YOUR fork, After initial clone, you can also use a config file for subsequent runs: ```bash -cp overrides/dotfiles.conf.example overrides/dotfiles.conf +cp overrides/config/dotfiles.conf.example overrides/config/dotfiles.conf # Edit dotfiles.conf with your settings ``` diff --git a/overrides/dotfiles.conf.example b/overrides/config/dotfiles.conf.example similarity index 100% rename from overrides/dotfiles.conf.example rename to overrides/config/dotfiles.conf.example diff --git a/tasks/run_role_with_overrides.yml b/tasks/run_role_with_overrides.yml index c7f2d500..a31c8784 100644 --- a/tasks/run_role_with_overrides.yml +++ b/tasks/run_role_with_overrides.yml @@ -1,14 +1,14 @@ --- # Wrapper task for running roles with optional overrides # Supports three override levels: -# - vars: overrides/{role}/vars/main.yml - merged before role runs -# - files: overrides/{role}/files/ - sets _files_path for file operations -# - tasks: overrides/{role}/tasks/main.yml - REPLACES role entirely +# - 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/{{ _role_name }}/tasks/main.yml" + path: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/tasks/main.yml" register: _task_override tags: - always @@ -16,14 +16,14 @@ # 2. Check and load vars override if exists - name: "Override | Check for vars override: {{ _role_name }}" ansible.builtin.stat: - path: "{{ playbook_dir }}/overrides/{{ _role_name }}/vars/main.yml" + 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/{{ _role_name }}/vars/main.yml" + file: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/vars/main.yml" when: _vars_override.stat.exists tags: - always @@ -31,21 +31,21 @@ # 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/{{ _role_name }}/files" + 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/' ~ _role_name ~ '/files') if _files_override.stat.exists else (playbook_dir ~ '/roles/' ~ _role_name ~ '/files') }}" + _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/{{ _role_name }}/tasks/main.yml" + file: "{{ playbook_dir }}/overrides/roles/{{ _role_name }}/tasks/main.yml" apply: tags: - "{{ _role_name }}" From 90df16c10fb2b768a4bdf40b525a38a5fc11d2ce Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 11:54:42 -0600 Subject: [PATCH 11/13] fix(ci): ignore freedesktop.org in link checker The freedesktop.org documentation site returns 418 status to automated link checkers (bot detection). Add to ignore patterns since the link is valid but blocked for CI bots. --- .github/workflows/markdown-link-config.json | 9 +++++++++ 1 file changed, 9 insertions(+) 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": [ From be49ac8f61f7d8b614cea7b683eb621a004ac7dd Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 12:18:27 -0600 Subject: [PATCH 12/13] docs(overrides): add custom role creation guide Document how to create new roles that only exist in overrides/, run via the -t flag. Useful for personal tools or workflows that don't need to be upstreamed. --- overrides/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/overrides/README.md b/overrides/README.md index 95ef001b..74d8121e 100644 --- a/overrides/README.md +++ b/overrides/README.md @@ -67,6 +67,37 @@ cat > overrides/roles/custom-tool/tasks/main.yml << 'EOF' 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 | From 7f2002682bb130ae3e1a63e4a75befa9af03e20e Mon Sep 17 00:00:00 2001 From: TechDufus Date: Wed, 28 Jan 2026 21:40:08 -0600 Subject: [PATCH 13/13] docs(overrides): add variable scope behavior warning Document that vars overrides persist in play scope for all subsequent roles. Includes best practices for avoiding variable name collisions. --- overrides/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/overrides/README.md b/overrides/README.md index 74d8121e..ed11ca8b 100644 --- a/overrides/README.md +++ b/overrides/README.md @@ -134,6 +134,21 @@ cp -r roles/git/files/* overrides/roles/git/files/ - **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