<!-- skill: Ansible Playbooks | version: 1.0.0 | description: Inventory, plays, roles, vault secrets and Pi fleet automation | category: System & Productivity -->
## Instructions
- Inventory first: `inventory.ini` groups `[web]`, `[pi]`, vars per group (`ansible_user=casper`); `ansible-inventory --list -i inventory.ini` to verify before any run.
- Plays are idempotent: use modules (`apt`, `copy`, `template`, `service`, `user`) not `shell`/`command` unless `creates:`/`removes:` guards; reruns must report `changed=0` when converged.
- Structure: `site.yml` → roles (`roles/common/tasks/main.yml`, `handlers`, `templates`, `defaults`); `ansible-galaxy init roles/<name>` scaffold; keep secrets out of `defaults` (use vault).
- Handlers restart only on change: `notify: restart nginx` + `handlers/main.yml` (`service: name=nginx state=restarted`); `meta: flush_handlers` before dependent checks.
- Vault secrets: `ansible-vault create group_vars/all/vault.yml`, `ansible-vault edit`, run with `--ask-vault-pass` or `--vault-password-file ~/.vault_pass` (chmod 600); never commit plaintext passwords.
- Check before change: `ansible-playbook site.yml --check --diff` dry-run, `--limit pi-01` blast-radius control, `--tags nginx` targeted; `-v` shows changed lines.
- Facts guide logic: `when: ansible_os_family == "Debian"`, `when: ansible_hostname is match("pi-.*")`; gather minimal (`gather_facts: true` default) then gate arch-specific tasks (`ansible_architecture == "aarch64"`).
- SSH setup: key auth (`ssh-copy-id`), `ansible.cfg` (`inventory=inventory.ini`, `host_key_checking=False` for lab only, `interpreter_python=auto_silent`); `ansible all -m ping` smoke test.
- Pi fleet example: group `[pi:vars] ansible_user=pi`, role `pi-base` (hostname, wifi, unattended-upgrades, docker), rolling updates with `serial: 1` so the fleet never all reboots together.
- Templates over copy for configs: `template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf` with `{{ inventory_hostname }}`/`{{ groups }}`; `validate: "nginx -t -c %s"` catches bad renders.
- Become correctly: `become: true` at play level for system roles, `become: false` for user dotfiles; `--ask-become-pass` when sudo needs password.
- Tags + labels: every role gets `tags: [roleName]`; long plays get `--list-tags` review; CI runs `--check` on PRs.
## Examples
- Ping fleet: `ansible all -i inventory.ini -m ping`.
- Dry-run web: `ansible-playbook -i inventory.ini site.yml --limit web --check --diff`.
- Vault edit: `ansible-vault edit group_vars/all/vault.yml`.
- One host: `ansible-playbook -i inventory.ini site.yml --limit pi-02 --tags docker -v`.
## Workflows
1. Bootstrap: inventory → `ansible all -m ping` → `site.yml` skeleton.
2. Develop role: tasks → handlers → template → `--check --diff --limit testbox`.
3. Secrets: vault create → reference `{{ vault_db_pass }}` → test with vault file.
4. Fleet rollout: `--limit canary` → verify → full run with `serial: 1`.
5. Converge check: rerun, expect `changed=0`; investigate any lingering `changed`.
## Templates
```ini
# inventory.ini — homelab + Pi fleet
[web]
web-01 ansible_host=192.168.1.11
web-02 ansible_host=192.168.1.12
[pi]
pi-01 ansible_host=192.168.1.21
pi-02 ansible_host=192.168.1.22
[pi:vars]
ansible_user=casper
ansible_ssh_private_key_file=~/.ssh/id_ed25519
[web:vars]
ansible_user=deploy
```
```yaml
# site.yml
- name: Base all hosts
  hosts: all
  become: true
  roles: [common]
- name: Pi fleet
  hosts: pi
  become: true
  serial: 1
  roles: [pi-base, docker]
- name: Web tier
  hosts: web
  become: true
  roles: [nginx, app]
```
```yaml
# roles/nginx/tasks/main.yml
- name: Install nginx
  apt: {name: nginx, state: present, update_cache: true}
- name: Render vhost
  template: {src: vhost.conf.j2, dest: /etc/nginx/sites-enabled/app.conf, validate: "nginx -t -c %s"}
  notify: reload nginx
```
```yaml
# roles/nginx/handlers/main.yml
- name: reload nginx
  service: {name: nginx, state: reloaded}
```
```ini
# ansible.cfg
[defaults]
inventory = inventory.ini
host_key_checking = False
retry_files_enabled = False
interpreter_python = auto_silent
```
## Tools
- ansible-core 2.16+, `ansible-lint`, `yamllint`; SARAH shell_run for `--check` gates.
- `ansible-inventory --graph`, `ansible-playbook --list-tasks`, vault password file (600).
## Advanced
- Dynamic inventory for clouds (`aws_ec2` plugin) when static ini outgrows 20 hosts; `constructed` groups by tag.
- `pre_tasks` for `assert:` gates (disk, OS) before destructive roles; `any_errors_fatal: true` on canary plays.
- Delegation: `delegate_to: localhost` for DNS/API calls, `run_once: true` for migrations.
- Troubleshooting: unreachable → SSH key/user/port (`-u`, `-i` flags); sudo fail → `become` + `--ask-become-pass`; template error → `--check --diff -v` shows line; vault fail → wrong password file perms (must be 600, owner-only).
## Documentation
- `ansible-playbook --help`, `ansible-doc apt`, `ansible-doc copy`.
- https://docs.ansible.com/
- https://docs.ansible.com/ansible/latest/vault_guide/
