10.4 Handlers και οι 3 διανομές 🐧

Το πρόβλημα

Το πιο συχνό πρόβλημα με handlers σε multi-distro περιβάλλον είναι ότι τα services έχουν διαφορετικά ονόματα:

┌─────────────────────────────────────────────────────────┐
│  Service    │ Debian/Ubuntu │ RedHat/Fedora │ openSUSE  │
├─────────────┼───────────────┼───────────────┼───────────┤
│  SSH        │ ssh           │ sshd          │ sshd      │
│  Cron       │ cron          │ crond         │ cron      │
│  Nginx      │ nginx         │ nginx         │ nginx     │
│  Apache     │ apache2       │ httpd         │ apache2   │
│  MySQL      │ mysql         │ mysqld        │ mysql     │
│  PostgreSQL │ postgresql    │ postgresql    │ postgresql│
│  Redis      │ redis         │ redis         │ redis     │
└─────────────┴───────────────┴───────────────┴───────────┘

Λύση 1: group_vars Variables (Βέλτιστη πρακτική ✅)

Ορίζουμε τα ονόματα των services στα group_vars:

# inventory/group_vars/debian_based.yml
---
service_ssh:    ssh
service_cron:   cron
service_apache: apache2
service_mysql:  mysql
# inventory/group_vars/redhat_based.yml
---
service_ssh:    sshd
service_cron:   crond
service_apache: httpd
service_mysql:  mysqld
# inventory/group_vars/opensuse_based.yml
---
service_ssh:    sshd
service_cron:   cron
service_apache: apache2
service_mysql:  mysql
# Χρήση στο playbook — ένας handler για όλες τις distros!
handlers:

  - name: Restart SSH
    ansible.builtin.service:
      name:  "{{ service_ssh }}"    # ← από group_vars
      state: restarted

  - name: Restart cron
    ansible.builtin.service:
      name:  "{{ service_cron }}"   # ← από group_vars
      state: restarted

  - name: Restart Apache
    ansible.builtin.service:
      name:  "{{ service_apache }}" # ← από group_vars
      state: restarted

Λύση 2: Inline Conditional (για απλές περιπτώσεις)

{% raw %}

handlers:

  # SSH — διαφορετικό όνομα σε Debian
  - name: Restart SSH
    ansible.builtin.service:
      name: >-
        {{ 'ssh'
           if ansible_facts['os_family'] == 'Debian'
           else 'sshd' }}
      state: restarted

  # Apache — διαφορετικό όνομα σε RedHat
  - name: Restart Apache
    ansible.builtin.service:
      name: >-
        {{ 'apache2'
           if ansible_facts['os_family'] != 'RedHat'
           else 'httpd' }}
      state: restarted

  # Cron — διαφορετικό όνομα σε RedHat
  - name: Restart cron
    ansible.builtin.service:
      name: >-
        {{ 'cron'
           if ansible_facts['os_family'] == 'Debian'
           else 'crond' }}
      state: restarted

Λύση 3: Ξεχωριστοί Handlers ανά Distro

handlers:

  # ── SSH handlers ──────────────────────────
  - name: Restart SSH Debian
    ansible.builtin.service:
      name:  ssh
      state: restarted
    when: ansible_facts['os_family'] == "Debian"

  - name: Restart SSH RedHat
    ansible.builtin.service:
      name:  sshd
      state: restarted
    when: ansible_facts['os_family'] == "RedHat"

  - name: Restart SSH openSUSE
    ansible.builtin.service:
      name:  sshd
      state: restarted
    when: ansible_facts['os_family'] == "Suse"
# Tasks που τους καλούν
tasks:

  - name: SSH config
    ansible.builtin.lineinfile:
      path:   /etc/ssh/sshd_config
      regexp: '^#?MaxAuthTries'
      line:   'MaxAuthTries 3'
    notify:
      - Restart SSH Debian
      - Restart SSH RedHat
      - Restart SSH openSUSE
      # ↑ Μόνο ο σωστός θα τρέξει λόγω when

📝 Ποια λύση να διαλέξεις:

Λύση 1 (group_vars) → βέλτιστη πρακτική ✅
                       καθαρό, επαναχρησιμοποιήσιμο
Λύση 2 (inline)     → για απλές δυαδικές επιλογές
Λύση 3 (ξεχωριστοί) → για πολύπλοκη λογική ανά distro

Πλήρες παράδειγμα — Multi-distro Handlers

---
# multi-distro-handlers.yml
# Handlers που δουλεύουν και στις 3 διανομές

- name: Multi-distro Service Configuration
  hosts: all
  become: true
  gather_facts: true

  vars:
    ssh_port:            2022
    configure_firewall:  true
    configure_fail2ban:  true

  tasks:

    # ══════════════════════════════════════════
    # SSH CONFIGURATION
    # ══════════════════════════════════════════

    - name: SSH - αλλαγή port
      ansible.builtin.lineinfile:
        path:    /etc/ssh/sshd_config
        regexp:  '^#?Port '
        line:    "Port {{ ssh_port }}"
        backup:  true
      notify: Restart SSH

    - name: SSH - απενεργοποίηση root login
      ansible.builtin.lineinfile:
        path:   /etc/ssh/sshd_config
        regexp: '^#?PermitRootLogin'
        line:   'PermitRootLogin no'
      notify: Restart SSH

    - name: SSH - μόνο key auth
      ansible.builtin.lineinfile:
        path:   /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line:   'PasswordAuthentication no'
      notify: Restart SSH

    # ══════════════════════════════════════════
    # FIREWALL CONFIGURATION
    # ══════════════════════════════════════════

    # ── ferm (Debian) ─────────────────────────
    - name: Εγκατάσταση ferm (Debian)
      ansible.builtin.apt:
        name:  ferm
        state: present
      when:
        - configure_firewall
        - ansible_facts['os_family'] == "Debian"

    - name: Ρύθμιση ferm (Debian)
      ansible.builtin.copy:
        dest:   /etc/ferm/ferm.conf
        mode:   '0640'
        backup: true
        content: |
          table filter {
            chain INPUT {
              policy DROP;
              mod state state (ESTABLISHED RELATED) ACCEPT;
              interface lo ACCEPT;
              proto icmp ACCEPT;
              proto tcp dport {{ ssh_port }} ACCEPT;
              proto tcp dport (80 443) ACCEPT;
            }
            chain OUTPUT { policy ACCEPT; }
            chain FORWARD { policy DROP; }
          }
      when:
        - configure_firewall
        - ansible_facts['os_family'] == "Debian"
      notify: Restart ferm

    # ── firewalld (RedHat/openSUSE) ───────────
    - name: Εγκατάσταση firewalld (RedHat/openSUSE)
      ansible.builtin.package:
        name:  firewalld
        state: present
      when:
        - configure_firewall
        - ansible_facts['os_family'] in ["RedHat", "Suse"]

    - name: Εκκίνηση firewalld
      ansible.builtin.service:
        name:    firewalld
        state:   started
        enabled: true
      when:
        - configure_firewall
        - ansible_facts['os_family'] in ["RedHat", "Suse"]

    - name: Άνοιγμα SSH port (firewalld)
      ansible.posix.firewalld:
        port:      "{{ ssh_port }}/tcp"
        permanent: true
        state:     enabled
        immediate: true
      when:
        - configure_firewall
        - ansible_facts['os_family'] in ["RedHat", "Suse"]
      notify: Restart firewalld

    - name: Άνοιγμα HTTP (firewalld)
      ansible.posix.firewalld:
        service:   http
        permanent: true
        state:     enabled
        immediate: true
      when:
        - configure_firewall
        - ansible_facts['os_family'] in ["RedHat", "Suse"]
      notify: Restart firewalld

    # ══════════════════════════════════════════
    # FAIL2BAN CONFIGURATION
    # ══════════════════════════════════════════

    - name: Εγκατάσταση fail2ban
      ansible.builtin.package:
        name:  fail2ban
        state: present
      when: configure_fail2ban

    - name: Ρύθμιση fail2ban
      ansible.builtin.copy:
        dest:   /etc/fail2ban/jail.local
        mode:   '0644'
        backup: true
        content: |
          [DEFAULT]
          bantime  = 3600
          findtime = 600
          maxretry = 5
          backend  = systemd

          [sshd]
          enabled  = true
          port     = {{ ssh_port }}
          logpath  = %(sshd_log)s
          maxretry = 3
      when: configure_fail2ban
      notify: Restart fail2ban

    # ══════════════════════════════════════════
    # FLUSH & VERIFY
    # ══════════════════════════════════════════

    - name: Εφαρμογή pending handlers
      ansible.builtin.meta: flush_handlers

    - name: Έλεγχος SSH service
      ansible.builtin.service_facts:

    - name: SSH status
      ansible.builtin.debug:
        msg: >
          SSH ({{ service_ssh }}):
          {{ ansible_facts.services[service_ssh + '.service'].state
             | default('unknown') }}

    - name: Fail2ban status
      ansible.builtin.debug:
        msg: >
          fail2ban:
          {{ ansible_facts.services['fail2ban.service'].state
             | default('unknown') }}
      when: configure_fail2ban

  # ══════════════════════════════════════════
  # HANDLERS
  # ══════════════════════════════════════════
  handlers:

    # ── SSH ───────────────────────────────────
    - name: Restart SSH
      ansible.builtin.service:
        name:  "{{ service_ssh }}"     # ← από group_vars!
        state: restarted
      ignore_errors: "{{ ansible_check_mode }}"

    # ── ferm ──────────────────────────────────
    - name: Restart ferm
      ansible.builtin.service:
        name:  ferm
        state: restarted
      when: ansible_facts['os_family'] == "Debian"
      ignore_errors: "{{ ansible_check_mode }}"

    # ── firewalld ─────────────────────────────
    - name: Restart firewalld
      ansible.builtin.service:
        name:  firewalld
        state: restarted
      when: ansible_facts['os_family'] in ["RedHat", "Suse"]
      ignore_errors: "{{ ansible_check_mode }}"

    # ── fail2ban ──────────────────────────────
    - name: Restart fail2ban
      ansible.builtin.service:
        name:  fail2ban
        state: restarted
      ignore_errors: "{{ ansible_check_mode }}"
# Syntax check
ansible-playbook playbooks/multi-distro-handlers.yml \
    --syntax-check

# Dry run
ansible-playbook playbooks/multi-distro-handlers.yml \
    --check --diff \
    --limit nextcloud

# Εκτέλεση
ansible-playbook playbooks/multi-distro-handlers.yml \
    --limit nextcloud -v

Σύνοψη 10.4

Handlers και 3 Διανομές
├── Πρόβλημα: διαφορετικά service names
│   ├── ssh   vs sshd
│   ├── cron  vs crond
│   └── httpd vs apache2
│
├── Λύσεις:
│   ├── group_vars variables ✅ (βέλτιστο)
│   │   └── service_ssh, service_cron κλπ
│   ├── Inline conditional
│   │   └── {{ 'ssh' if Debian else 'sshd' }}
│   └── Ξεχωριστοί handlers με when
│
└── Βέλτιστη πρακτική:
    ├── group_vars/debian_based.yml  → service_ssh: ssh
    ├── group_vars/redhat_based.yml  → service_ssh: sshd
    ├── group_vars/opensuse_based.yml→ service_ssh: sshd
    └── handler: name: "{{ service_ssh }}"