From c9e2ff930cfe37c94031f6492d74066d0c10aba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20D=C3=A9siles?= <1536672+cdesiles@users.noreply.github.com> Date: Fri, 29 May 2026 22:24:16 +0200 Subject: [PATCH] feat(net_config): safer ufw restart on NAT/forwarding changes - Replace 'ufw disable && ufw --force enable' single-shot handler with a block that dry-runs the ruleset, disables, re-enables, then verifies ufw is active. No '&&' short-circuit, so failures are loud instead of leaving the host firewall-less. - Rename handler to 'Restart ufw (ip-forwarding settings changed)' to reflect that this is a full restart (required to pick up /etc/default/ufw and /etc/ufw/before.rules changes per ufw(8)). - Add NAT/masquerade tasks: enable ipv4 forwarding, set DEFAULT_FORWARD_POLICY=ACCEPT, and write a per-interface *nat block in /etc/ufw/before.rules. - Declare requires_ansible >=2.15 in meta/runtime.yml (handler uses block:, supported since 2.12; 2.15 is a safe modern floor). - README: document Ansible version requirement, port reservation rules, and Immich pgvector Q&A. --- README.md | 30 +++++++++++++++++++++++ meta/runtime.yml | 2 ++ roles/net_config/handlers/main.yml | 23 ++++++++++++++++++ roles/net_config/tasks/main.yml | 38 ++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 meta/runtime.yml create mode 100644 roles/net_config/handlers/main.yml diff --git a/README.md b/README.md index 6d621b9..32cfdf3 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,24 @@ This is a good playground to learn and I encourage you to adapt these roles to y | static-web | Static website hosting | | vpn | WireGuard server | +## Port Reservation Rules + +Reserved ports that **must not** be used as role defaults: + +| Port(s) | Protocol | Reserved for | +| --- | --- | --- | +| 80 | tcp | Nginx | +| 443 | tcp | Nginx | +| 3000-3009 | tcp | Testing | +| 4430 | tcp | Testing | +| 8080 | tcp | Testing | + +When adding a new role, pick a default port outside these ranges. + ## Requirements +Ansible `>=2.15` + Base tools: ```sh @@ -110,3 +126,17 @@ Linting: ansible-lint npx prettier --write . ``` + +## Q&A + +### Immich crash loop: `PostgresError: must be owner of extension vector` + +Immich tries to self-update the `pgvector` extension at startup, but its database user is intentionally `NOSUPERUSER`, so the `ALTER EXTENSION vector UPDATE` call fails and the microservices worker exits with code 1. + +Fix it on the running host by updating the extension as the `postgres` superuser: + +```sh +sudo -u postgres psql -d immich -c 'ALTER EXTENSION vector UPDATE;' +``` + +The Immich role also runs this automatically on subsequent playbook runs, so re-deployments after a pgvector package upgrade do not require manual intervention. diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..accfbd8 --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,2 @@ +--- +requires_ansible: ">=2.15" diff --git a/roles/net_config/handlers/main.yml b/roles/net_config/handlers/main.yml new file mode 100644 index 0000000..c11e8ea --- /dev/null +++ b/roles/net_config/handlers/main.yml @@ -0,0 +1,23 @@ +--- +# UFW must be fully restarted (disable + enable) — not just reloaded — to pick +# up changes in /etc/default/ufw (DEFAULT_FORWARD_POLICY) and the *nat block +# in /etc/ufw/before.rules. See ufw(8) "RULE SYNTAX" → IP forwarding. +- name: Restart ufw (ip-forwarding settings changed) + block: + - name: Validate ufw ruleset before restart (dry-run) + ansible.builtin.command: ufw --dry-run reload + changed_when: false + + - name: Disable ufw + ansible.builtin.command: ufw disable + changed_when: true + + - name: Enable ufw + ansible.builtin.command: ufw --force enable + changed_when: true + + - name: Verify ufw is active after restart + ansible.builtin.command: ufw status + register: ufw_status_after + changed_when: false + failed_when: "'Status: active' not in ufw_status_after.stdout" diff --git a/roles/net_config/tasks/main.yml b/roles/net_config/tasks/main.yml index 3e52d99..f34b3c5 100644 --- a/roles/net_config/tasks/main.yml +++ b/roles/net_config/tasks/main.yml @@ -32,3 +32,41 @@ ansible.builtin.set_fact: network_reload_required: true when: netdev_result is changed or network_result is changed + + ## Routing & NAT (when interface has forward + masquerade enabled) + - name: Enable IPv4 forwarding + ansible.posix.sysctl: + name: net.ipv4.ip_forward + value: "1" + state: present + sysctl_set: true + reload: true + when: + - interface.ipv4.forward | default(false) + - interface.ipv4.masquerade | default(false) + + - name: Set UFW default forward policy to ACCEPT + ansible.builtin.lineinfile: + path: /etc/default/ufw + regexp: "^DEFAULT_FORWARD_POLICY=" + line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' + when: + - interface.ipv4.forward | default(false) + - interface.ipv4.masquerade | default(false) + notify: Restart ufw (ip-forwarding settings changed) + + - name: Configure NAT masquerade in UFW before.rules for {{ interface.name }} + ansible.builtin.blockinfile: + path: /etc/ufw/before.rules + insertbefore: "^\\*filter" + marker: "# {mark} ANSIBLE MANAGED - NAT {{ interface.name }}" + block: | + *nat + :POSTROUTING ACCEPT [0:0] + -A POSTROUTING -s {{ interface.ipv4.address | ansible.utils.ipaddr('network/prefix') }} -o {{ interface.ipv4.nat_out_interface }} -j MASQUERADE + COMMIT + when: + - interface.ipv4.forward | default(false) + - interface.ipv4.masquerade | default(false) + - interface.ipv4.nat_out_interface is defined + notify: Restart ufw (ip-forwarding settings changed)