---
# os_init role: user creation, OS package upgrade, and ufw firewall.
#
# IMPORTANT: the "allow OpenSSH" rule is applied BEFORE the default-deny
# incoming policy and BEFORE `ufw --force enable`. Reversing this order can
# drop the very SSH session used to run this playbook.

- name: "os_init : Update apt cache"
  ansible.builtin.apt:
    update_cache: true
    cache_valid_time: 3600

- name: "os_init : Upgrade all packages"
  ansible.builtin.apt:
    upgrade: dist

- name: "os_init : Create setup user"
  ansible.builtin.user:
    name: "{{ os_init_user | default('appuser') }}"
    shell: /bin/bash
    create_home: true
    groups: sudo
    append: true

- name: "os_init : Install ufw"
  ansible.builtin.apt:
    name: ufw
    state: present

# `ufw status verbose` is the single source of truth for the tasks below.
# When ufw is active it prints e.g.
#   Status: active
#   Default: deny (incoming), allow (outgoing), disabled (routed)
# so we can gate the default-policy tasks on the effective policy. When ufw is
# inactive it prints only `Status: inactive` (no Default line), so a fresh host
# still applies both defaults (and `'inactive' in ...stdout` keeps the Enable
# task below firing).
- name: "os_init : Check ufw status"
  ansible.builtin.command:
    cmd: ufw status verbose
  register: os_init_ufw_status
  changed_when: false

# --- Ordering guard: OpenSSH must be allowed before anything below enables
# --- the firewall or sets a default-deny incoming policy.
- name: "os_init : Allow OpenSSH through ufw"
  ansible.builtin.command:
    cmd: ufw allow OpenSSH
  register: os_init_ufw_allow_ssh
  changed_when: "'Skipping' not in os_init_ufw_allow_ssh.stdout"

# `ufw default deny incoming` prints "Default incoming policy changed to 'deny'"
# UNCONDITIONALLY (even when already deny), so a stdout grep can never be
# idempotent. Instead only run it when the effective default is not already
# deny; when it runs, it did change the policy, hence `changed_when: true`.
- name: "os_init : Set ufw default incoming policy to deny"
  ansible.builtin.command:
    cmd: ufw default deny incoming
  when: "'deny (incoming)' not in os_init_ufw_status.stdout"
  changed_when: true

# Same reasoning for the outgoing default (ufw prints its "changed" line
# unconditionally): only apply when the effective default is not already allow.
- name: "os_init : Set ufw default outgoing policy to allow"
  ansible.builtin.command:
    cmd: ufw default allow outgoing
  when: "'allow (outgoing)' not in os_init_ufw_status.stdout"
  changed_when: true

- name: "os_init : Enable ufw"
  ansible.builtin.command:
    cmd: ufw --force enable
  when: "'inactive' in os_init_ufw_status.stdout"
  register: os_init_ufw_enable
  changed_when: "'Firewall is active' in os_init_ufw_enable.stdout"
