Ansible Collection — Typical Linux Stack
This page describes a Galaxy-grade Ansible collection covering Chrony, Rsyslog (client + server), Postfix, Dovecot, and Squid. The design follows Ansible's documented collection packaging model, uses FQCN throughout, and targets ansible-lint's shared profile for published content.
Collection tree
.ansible-lint
Target the shared profile — intended for Galaxy/Automation Hub published content:
profile: shared
use_default_rules: true
exclude_paths:
- .git/
- .github/
skip_list: []
warn_list:
- experimental
meta/runtime.yml
Use a bounded range for an explicit compatibility contract:
requires_ansible: ">=2.16.0,<2.20.0"
Playbooks with FQCN role references
Use fully qualified collection names in every playbook. ansible-lint's FQCN rule and Ansible docs both recommend this:
---
- name: Configure proxy servers
hosts: pxy
become: true
roles:
- role: your_namespace.rz_standard_linux_stack.chrony_client
- role: your_namespace.rz_standard_linux_stack.squid_proxy
argument_specs.yml — all roles
meta/argument_specs.yml is the biggest improvement for Galaxy-quality content. It gives you role input validation, cleaner generated docs, and safer reuse by others.
roles/chrony_client/meta/argument_specs.yml
argument_specs:
main:
short_description: Install and configure chrony client
description:
- Installs chrony, renders chrony.conf, ensures chronyd is running, and verifies service health.
options:
chrony_package_name:
type: str
default: chrony
chrony_service_name:
type: str
default: chronyd
chrony_config_path:
type: str
default: /etc/chrony.conf
chrony_manage_config:
type: bool
default: true
chrony_manage_service:
type: bool
default: true
chrony_run_verify:
type: bool
default: true
chrony_servers:
type: list
elements: str
default: []
chrony_pools:
type: list
elements: str
default: []
chrony_driftfile:
type: str
default: /var/lib/chrony/drift
chrony_makestep:
type: str
default: "1.0 3"
chrony_extra_config:
type: list
elements: str
default: []
roles/rsyslog_client/meta/argument_specs.yml
argument_specs:
main:
short_description: Configure rsyslog forwarding
options:
rsyslog_package_name:
type: str
default: rsyslog
rsyslog_service_name:
type: str
default: rsyslog
rsyslog_forwarding_conf_path:
type: str
default: /etc/rsyslog.d/60-forwarding.conf
rsyslog_manage_config:
type: bool
default: true
rsyslog_manage_service:
type: bool
default: true
rsyslog_run_verify:
type: bool
default: true
rsyslog_forward_server:
type: str
required: true
rsyslog_forward_port:
type: int
default: 514
rsyslog_forward_protocol:
type: str
choices:
- tcp
- udp
default: tcp
rsyslog_forward_selector:
type: str
default: "*.*"
roles/postfix_server/meta/argument_specs.yml
argument_specs:
main:
short_description: Install and configure Postfix
options:
postfix_package_name:
type: str
default: postfix
postfix_service_name:
type: str
default: postfix
postfix_main_cf_path:
type: str
default: /etc/postfix/main.cf
postfix_manage_config:
type: bool
default: true
postfix_manage_service:
type: bool
default: true
postfix_run_verify:
type: bool
default: true
postfix_myhostname:
type: str
required: true
postfix_mydomain:
type: str
required: true
postfix_myorigin:
type: str
default: "$mydomain"
postfix_inet_interfaces:
type: str
default: all
postfix_inet_protocols:
type: str
default: ipv4
postfix_mydestination:
type: list
elements: str
default:
- "$myhostname"
- "localhost.$mydomain"
- "localhost"
postfix_mynetworks:
type: list
elements: str
default:
- "127.0.0.0/8"
postfix_relayhost:
type: str
default: ""
postfix_home_mailbox:
type: str
default: Maildir/
postfix_extra_config:
type: list
elements: str
default: []
roles/dovecot_server/meta/argument_specs.yml
argument_specs:
main:
short_description: Install and configure Dovecot
options:
dovecot_package_name:
type: str
default: dovecot
dovecot_service_name:
type: str
default: dovecot
dovecot_config_path:
type: str
default: /etc/dovecot/dovecot.conf
dovecot_manage_config:
type: bool
default: true
dovecot_manage_service:
type: bool
default: true
dovecot_run_verify:
type: bool
default: true
dovecot_protocols:
type: list
elements: str
default:
- imap
- pop3
- lmtp
dovecot_mail_location:
type: str
default: maildir:~/Maildir
dovecot_disable_plaintext_auth:
type: str
default: "yes"
dovecot_ssl:
type: str
default: "yes"
dovecot_extra_config:
type: list
elements: str
default: []
roles/squid_proxy/meta/argument_specs.yml
argument_specs:
main:
short_description: Install and configure Squid proxy
options:
squid_package_name:
type: str
default: squid
squid_service_name:
type: str
default: squid
squid_config_path:
type: str
default: /etc/squid/squid.conf
squid_manage_config:
type: bool
default: true
squid_manage_service:
type: bool
default: true
squid_run_verify:
type: bool
default: true
squid_http_port:
type: int
default: 3128
squid_allowed_networks:
type: list
elements: str
default:
- 127.0.0.1/32
squid_dns_v4_first:
type: str
default: "on"
squid_access_log:
type: str
default: /var/log/squid/access.log
squid_cache_log:
type: str
default: /var/log/squid/cache.log
squid_coredump_dir:
type: str
default: /var/spool/squid
squid_extra_config:
type: list
elements: str
default: []
Tags pattern
Add consistent tags to every task file so runs can be targeted. Use a role-level tag plus a stage-level tag:
squid_proxy/tasks/install.yml
---
- name: Ensure squid is installed
ansible.builtin.package:
name: "{{ squid_package_name }}"
state: present
become: true
tags:
- squid
- squid_install
squid_proxy/tasks/config.yml
---
- name: Deploy squid configuration
ansible.builtin.template:
src: squid.conf.j2
dest: "{{ squid_config_path }}"
owner: root
group: root
mode: "0644"
backup: true
notify: Restart squid
become: true
tags:
- squid
- squid_config
squid_proxy/tasks/service.yml
---
- name: Ensure squid is enabled and started
ansible.builtin.service:
name: "{{ squid_service_name }}"
state: started
enabled: true
become: true
tags:
- squid
- squid_service
flush_handlers before verify
If config changes and your verify tasks depend on the restarted service, flush handlers before running verification. Apply this pattern in every role's tasks/main.yml:
postfix_server/tasks/main.yml
---
- name: Include postfix install tasks
ansible.builtin.import_tasks: install.yml
tags:
- postfix
- postfix_install
- name: Include postfix config tasks
ansible.builtin.import_tasks: config.yml
when: postfix_manage_config | bool
tags:
- postfix
- postfix_config
- name: Flush postfix handlers before verification
ansible.builtin.meta: flush_handlers
when: postfix_manage_config | bool
tags:
- postfix
- postfix_config
- postfix_verify
- name: Include postfix service tasks
ansible.builtin.import_tasks: service.yml
when: postfix_manage_service | bool
tags:
- postfix
- postfix_service
- name: Include postfix verify tasks
ansible.builtin.import_tasks: verify.yml
when: postfix_run_verify | bool
tags:
- postfix
- postfix_verify
flush_handlers, your verify task might check the service before the handler has restarted it after a config change, causing a false pass or failure.
Assertions for required inputs
Validate assumptions before doing risky work:
postfix_server/tasks/config.yml — assert identity values
---
- name: Assert postfix identity values are set
ansible.builtin.assert:
that:
- postfix_myhostname | length > 0
- postfix_mydomain | length > 0
- postfix_mydestination | length > 0
- postfix_mynetworks | length > 0
fail_msg: "Postfix requires hostname, domain, mydestination, and mynetworks to be defined."
quiet: true
tags:
- postfix
- postfix_config
squid_proxy/tasks/config.yml — assert allowed networks
---
- name: Assert squid allowed networks are defined
ansible.builtin.assert:
that:
- squid_allowed_networks | length > 0
fail_msg: "squid_allowed_networks must include at least one source network."
quiet: true
tags:
- squid
- squid_config
chrony_client/tasks/config.yml — assert at least one source
- name: Assert chrony sources are defined
ansible.builtin.assert:
that:
- chrony_servers | length > 0 or chrony_pools | length > 0
fail_msg: "Define at least one chrony server or pool."
quiet: true
tags:
- chrony
- chrony_config
rsyslog_server — assert a listener is configured
- name: Assert rsyslog listener configuration is valid
ansible.builtin.assert:
that:
- rsyslog_enable_udp or rsyslog_enable_tcp
fail_msg: "At least one of rsyslog_enable_udp or rsyslog_enable_tcp must be true."
quiet: true
Stricter verify tasks
Parse config AND check the service is active:
dovecot_server/tasks/verify.yml
---
- name: Validate dovecot configuration
ansible.builtin.command: doveconf -n
register: dovecot_validate
changed_when: false
become: true
tags:
- dovecot
- dovecot_verify
- name: Check dovecot is active
ansible.builtin.command: systemctl is-active {{ dovecot_service_name }}
register: dovecot_status
changed_when: false
failed_when: dovecot_status.stdout.strip() != "active"
become: true
tags:
- dovecot
- dovecot_verify
squid_proxy/tasks/verify.yml
---
- name: Validate squid configuration
ansible.builtin.command: squid -k parse -f {{ squid_config_path }}
register: squid_validate
changed_when: false
become: true
tags:
- squid
- squid_verify
- name: Check squid is active
ansible.builtin.command: systemctl is-active {{ squid_service_name }}
register: squid_status
changed_when: false
failed_when: squid_status.stdout.strip() != "active"
become: true
tags:
- squid
- squid_verify
Role meta/main.yml
Make them uniform and explicit across all roles:
galaxy_info:
author: Your Name
description: Install and configure a central rsyslog server
license: MIT
min_ansible_version: "2.16"
platforms:
- name: EL
versions:
- "8"
- "9"
galaxy_tags:
- rsyslog
- logging
- system
dependencies: []
Role READMEs
Because argument_specs.yml handles documentation, READMEs can be lightweight and point to real examples:
# rsyslog_client
Configures rsyslog forwarding to a central log server.
## Requirements
- Enterprise Linux style target
- `rsyslog_forward_server` must be defined
## Example
```yaml
- hosts: all
become: true
roles:
- role: your_namespace.rz_standard_linux_stack.rsyslog_client
vars:
rsyslog_forward_server: log01.internal.example
rsyslog_forward_protocol: tcp
rsyslog_forward_port: 514
```
Tests
tests/test-build-pxy.yml
---
- name: Test proxy build playbook
ansible.builtin.import_playbook: ../playbooks/build-pxy.yml
docs/usage.md — test commands
# Syntax check
ansible-playbook -i tests/inventory tests/test-build-linux-base.yml --syntax-check
ansible-playbook -i tests/inventory tests/test-build-log.yml --syntax-check
ansible-playbook -i tests/inventory tests/test-build-eml.yml --syntax-check
ansible-playbook -i tests/inventory tests/test-build-pxy.yml --syntax-check
# Check mode (dry run)
ansible-playbook -i tests/inventory tests/test-build-linux-base.yml --check
# Lint
ansible-lint .
Role-by-role delta summary
chrony_client
- Add
meta/argument_specs.yml - Add tags to all task files
- Add
flush_handlersintasks/main.yml - Assert at least one of
chrony_serversorchrony_poolsis set
rsyslog_client
- Add
argument_specs.ymlwithrsyslog_forward_servermarkedrequired: true - Add tags and
flush_handlers
rsyslog_server
- Add
argument_specs.yml - Add tags and
flush_handlers - Assert at least one of TCP or UDP is enabled
postfix_server
- Add
argument_specs.ymlwithpostfix_myhostnameandpostfix_mydomainrequired - Add tags and
flush_handlers - Assert identity/network settings are defined
- Add active service check to
verify.yml
dovecot_server
- Add
argument_specs.yml - Add tags and
flush_handlers - Add active service check to
verify.yml
squid_proxy
- Add
argument_specs.yml - Add tags and
flush_handlers - Assert
squid_allowed_networksis not empty - Add active service check to
verify.yml
What makes it publishable
When you have added all of the following, this collection moves from a good study scaffold to a reasonable Galaxy-grade starter collection:
meta/argument_specs.ymlin every role.ansible-lintwithprofile: shared- FQCN everywhere (modules and role references)
- Tags on every task file
flush_handlersbefore verify in every role- Assertions on required inputs
- Service-state verify checks in all roles
- Consistent
meta/main.ymlacross all roles