Ansible Collection — Typical Linux Stack

Production-ready Ansible collection structure for your_namespace.rz_standard_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_collections/ └── your_namespace/ └── rz_standard_linux_stack/ ├── galaxy.yml ├── README.md ├── .ansible-lint ├── meta/ │ └── runtime.yml ├── roles/ │ ├── chrony_client/ │ │ ├── defaults/main.yml │ │ ├── handlers/main.yml │ │ ├── meta/main.yml │ │ ├── meta/argument_specs.yml │ │ ├── tasks/main.yml │ │ ├── tasks/install.yml │ │ ├── tasks/config.yml │ │ ├── tasks/service.yml │ │ ├── tasks/verify.yml │ │ ├── templates/chrony.conf.j2 │ │ └── README.md │ ├── rsyslog_client/ │ │ ├── defaults/main.yml │ │ ├── handlers/main.yml │ │ ├── meta/main.yml │ │ ├── meta/argument_specs.yml │ │ ├── tasks/main.yml │ │ ├── tasks/install.yml │ │ ├── tasks/config.yml │ │ ├── tasks/service.yml │ │ ├── tasks/verify.yml │ │ ├── templates/forwarding.conf.j2 │ │ └── README.md │ ├── rsyslog_server/ │ │ └── [same structure] │ ├── postfix_server/ │ │ └── [same structure — template: main.cf.j2] │ ├── dovecot_server/ │ │ └── [same structure — template: dovecot.conf.j2] │ └── squid_proxy/ │ └── [same structure — template: squid.conf.j2] ├── playbooks/ │ ├── build-log.yml │ ├── build-eml.yml │ ├── build-pxy.yml │ └── build-linux-base.yml ├── docs/ │ ├── usage.md │ ├── role-matrix.md │ └── inventory-example.md └── tests/ ├── inventory ├── test-build-log.yml ├── test-build-eml.yml ├── test-build-pxy.yml └── test-build-linux-base.yml

.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
Why this matters: Without 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

rsyslog_client

rsyslog_server

postfix_server

dovecot_server

squid_proxy

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: