# Contributing ## Code style - YAML files use 4-space indentation, formatted with `yamlfix`. - Jinja2 templates use the file extension `.j2`. - Role names use snake_case (e.g., `docker_registry`). - Task names are human-readable sentences (e.g., "Ensure SSL private keys are readable by containers"). ## Role structure Each role follows the standard Ansible layout: ``` roles// ├── defaults/main.yml # Default variables ├── tasks/ │ ├── main.yml # Entry point │ └── *.yml # Additional task files (deploy, restore, etc.) ├── templates/ # Jinja2 templates (.j2) ├── files/ # Static files └── meta/main.yml # Role dependencies ``` ## Adding a new service 1. Create a new role in `ansible/roles//`. 2. Add it to the proxy or IDP play in `ansible/playbooks/setup.yml` with a `tags: []` annotation. 3. If the role depends on docker or apache, those roles handle themselves internally — just declare the dependency in `meta/main.yml`. ## Testing changes Always test locally before deploying to production: ```bash # Run against local VMs pipenv run ansible-playbook -i ansible/inventories/debug/hosts.ini ansible/playbooks/setup.yml \ --tags ``` ## Committing - Keep commits focused: one logical change per commit. - Use conventional-ish commit messages: - `feat: add offline email notifications to prosody` - `fix: correct registry vhost name collision` - `chore: migrate collection roles to local roles/` ## Vault changes When adding new secrets, add them to the vault file and document the variable name (but not the value) somewhere discoverable — either in the role's `defaults/main.yml` as a commented placeholder or in the playbook next to the role invocation.