blumeops/docs/how-to/deployment/add-ansible-role.md
Erich Blume 27d8f3cf1f Review gandi-operations doc and reorganize how-to guides (#200)
## Summary
- **Doc review:** Reviewed `gandi-operations.md` — added `last-reviewed` frontmatter, verified all wiki-links, confirmed Pulumi state has no drift
- **Gandi reference fix:** Added missing `cv.eblu.me` CNAME row to `gandi.md` DNS records table (was present in Pulumi but undocumented)
- **Pulumi comment fix:** Updated stale `README.md` reference in `__main__.py` to point to `docs/how-to/gandi-operations.md`
- **How-to reorg:** Moved 14 how-to guides into 3 subdirectories (`deployment/`, `configuration/`, `operations/`), collapsed the Documentation and Database index sections into Configuration and Operations respectively

## Verification
- `docs-check-links` — all 180 wiki-links valid
- `docs-check-filenames` — all 90 filenames unique
- `dns-preview` — 5 resources unchanged, no drift
- All pre-commit hooks pass

## Test plan
- [ ] Verify docs site builds correctly with new paths
- [ ] Spot-check a few wiki-links from other pages to moved how-to guides

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/200
2026-02-17 07:29:33 -08:00

3.6 KiB

title modified last-reviewed tags
Add Ansible Role 2026-02-13 2026-02-13
how-to
ansible

Add an Ansible Role

Quick reference for adding a new Ansible role to provision services on indri.

Create Role Structure

ansible/roles/<role>/
├── defaults/main.yml    # Default variables
├── tasks/main.yml       # Task definitions
├── handlers/main.yml    # Handlers (restarts, etc.)
├── templates/           # Jinja2 templates
└── files/               # Static files (optional)

Minimal Role Example

# ansible/roles/<role>/defaults/main.yml
---
role_data_dir: ~/Library/Application Support/<service>
role_port: 8080
# ansible/roles/<role>/tasks/main.yml
---
- name: Ensure data directory exists
  ansible.builtin.file:
    path: "{{ role_data_dir }}"
    state: directory
    mode: '0755'

- name: Deploy configuration
  ansible.builtin.template:
    src: config.j2
    dest: "{{ role_data_dir }}/config"
    mode: '0644'
  notify: Restart <service>

- name: Deploy LaunchAgent plist
  ansible.builtin.template:
    src: launchagent.plist.j2
    dest: ~/Library/LaunchAgents/mcquack.<service>.plist
    mode: '0644'
  notify: Restart <service>
# ansible/roles/<role>/handlers/main.yml
---
- name: Restart <service>
  ansible.builtin.shell: |
    launchctl unload ~/Library/LaunchAgents/mcquack.<service>.plist 2>/dev/null || true
    launchctl load ~/Library/LaunchAgents/mcquack.<service>.plist
  changed_when: true

Add Role to Playbook

Edit ansible/playbooks/indri.yml:

  roles:
    # ... existing roles ...
    - role: <role>
      tags: <role>

Add Secrets (if needed)

If the role needs secrets from 1Password, add pre_tasks:

  pre_tasks:
    # ... existing pre_tasks ...
    - name: Fetch <role> secret
      ansible.builtin.command:
        cmd: op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/<item-id>/<field>"
      delegate_to: localhost
      register: _role_secret
      changed_when: false
      no_log: true
      check_mode: false
      tags: <role>

    - name: Set <role> secret fact
      ansible.builtin.set_fact:
        role_secret_var: "{{ _role_secret.stdout }}"
      no_log: true
      tags: <role>

Then use role_secret_var in your role with a guard:

# In role's tasks, fetch if not already set (allows running with --tags)
- name: Fetch secret if not set
  ansible.builtin.command:
    cmd: op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/<item-id>/<field>"
  delegate_to: localhost
  register: _role_secret
  changed_when: false
  no_log: true
  check_mode: false
  when: role_secret_var is not defined

Test and Deploy

# Dry run
mise run provision-indri -- --tags <role> --check --diff

# Apply
mise run provision-indri -- --tags <role>

# Verify
ssh indri 'launchctl list | grep <service>'

Add Observability (optional)

For metrics collection, create a companion <role>_metrics role that:

  1. Writes metrics to /opt/homebrew/var/node_exporter/textfile/
  2. Runs via a LaunchAgent (cronjob-style)

See alloy for how metrics are collected from textfiles.

Checklist

  • Role created in ansible/roles/<role>/
  • Role added to ansible/playbooks/indri.yml with tag
  • Secrets wired via pre_tasks (if needed)
  • Dry run passes: mise run provision-indri -- --tags <role> --check --diff
  • Service added to service-versions.yaml for version tracking