Compare commits
4 Commits
a8545fc501
...
c9e2ff930c
| Author | SHA1 | Date | |
|---|---|---|---|
| c9e2ff930c | |||
| 36d6baaecb | |||
| 5f2c82d296 | |||
| dbc7ca203a |
@@ -239,7 +239,7 @@ boilerplate.
|
|||||||
|
|
||||||
- name: Set user home directory fact
|
- name: Set user home directory fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
user_home_dir: "{{ getent_passwd[ansible_user][4] }}"
|
user_home_dir: "{{ ansible_facts['getent_passwd'][ansible_user][4] }}"
|
||||||
|
|
||||||
- name: Create systemd user directory
|
- name: Create systemd user directory
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -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 |
|
| static-web | Static website hosting |
|
||||||
| vpn | WireGuard server |
|
| 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
|
## Requirements
|
||||||
|
|
||||||
|
Ansible `>=2.15`
|
||||||
|
|
||||||
Base tools:
|
Base tools:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -110,3 +126,17 @@ Linting:
|
|||||||
ansible-lint
|
ansible-lint
|
||||||
npx prettier --write .
|
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.
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
---
|
||||||
|
requires_ansible: ">=2.15"
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
- name: Set user home directory fact
|
- name: Set user home directory fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
user_home_dir: "{{ getent_passwd[ansible_user][4] }}"
|
user_home_dir: "{{ ansible_facts['getent_passwd'][ansible_user][4] }}"
|
||||||
|
|
||||||
- name: Create systemd user directory for Gitea
|
- name: Create systemd user directory for Gitea
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
|
|
||||||
- name: Set user home directory fact
|
- name: Set user home directory fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
user_home_dir: "{{ getent_passwd[ansible_user][4] }}"
|
user_home_dir: "{{ ansible_facts['getent_passwd'][ansible_user][4] }}"
|
||||||
|
|
||||||
- name: Create systemd user directory for Immich
|
- name: Create systemd user directory for Immich
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -32,3 +32,41 @@
|
|||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
network_reload_required: true
|
network_reload_required: true
|
||||||
when: netdev_result is changed or network_result is changed
|
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)
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
- name: Set user home directory fact
|
- name: Set user home directory fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
user_home_dir: "{{ getent_passwd[ansible_user][4] }}"
|
user_home_dir: "{{ ansible_facts['getent_passwd'][ansible_user][4] }}"
|
||||||
|
|
||||||
- name: Create systemd user directory for ntfy
|
- name: Create systemd user directory for ntfy
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
|
|
||||||
- name: Set user home directory fact
|
- name: Set user home directory fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
user_home_dir: "{{ getent_passwd[ansible_user][4] }}"
|
user_home_dir: "{{ ansible_facts['getent_passwd'][ansible_user][4] }}"
|
||||||
|
|
||||||
- name: Create systemd user directory for uptime-kuma
|
- name: Create systemd user directory for uptime-kuma
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
- name: "Validate required fields for tunnel {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that:
|
||||||
|
- _tunnel.interface is defined
|
||||||
|
- _tunnel.interface | length > 0
|
||||||
|
- _tunnel.address is defined
|
||||||
|
- _tunnel.address | length > 0
|
||||||
|
fail_msg: |
|
||||||
|
Tunnel is missing required fields: 'interface' and 'address' are mandatory.
|
||||||
|
See roles/wireguard/defaults/main.yml for configuration instructions.
|
||||||
|
success_msg: "Tunnel {{ _tunnel.interface }} validation passed"
|
||||||
|
|
||||||
|
- name: "Check if private key exists for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ wireguard_config_base_path }}/{{ _tunnel.interface }}.privatekey"
|
||||||
|
register: _tunnel_pkey_file
|
||||||
|
|
||||||
|
- name: "Generate wireguard keys for {{ _tunnel.interface }} if not present"
|
||||||
|
ansible.builtin.shell: >
|
||||||
|
wg genkey |
|
||||||
|
tee {{ wireguard_config_base_path }}/{{ _tunnel.interface }}.privatekey |
|
||||||
|
wg pubkey > {{ wireguard_config_base_path }}/{{ _tunnel.interface }}.publickey
|
||||||
|
when: not _tunnel_pkey_file.stat.exists
|
||||||
|
|
||||||
|
- name: "Retrieve wireguard private key for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: "{{ wireguard_config_base_path }}/{{ _tunnel.interface }}.privatekey"
|
||||||
|
register: _tunnel_private_key_b64
|
||||||
|
|
||||||
|
- name: "Set wireguard private key fact for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_tunnel_private_key: "{{ _tunnel_private_key_b64['content'] | b64decode }}"
|
||||||
|
|
||||||
|
- name: "Resolve effective DNS for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
_tunnel_effective_dns: "{{ (_tunnel.dns | default('')) if (unbound_custom_lan_records is not defined) else '' }}"
|
||||||
|
|
||||||
|
- name: "Install wireguard config for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: wireguard.conf.j2
|
||||||
|
dest: "{{ wireguard_config_base_path }}/{{ _tunnel.interface }}.conf"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0600"
|
||||||
|
|
||||||
|
- name: "Create systemd override directory for wg-quick@{{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "/etc/systemd/system/wg-quick@{{ _tunnel.interface }}.service.d"
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: "Deploy systemd override for network dependency for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: systemd-override.conf.j2
|
||||||
|
dest: "/etc/systemd/system/wg-quick@{{ _tunnel.interface }}.service.d/network-dependency.conf"
|
||||||
|
mode: "0644"
|
||||||
|
notify: Reload systemd
|
||||||
|
|
||||||
|
- name: "Enable IP forwarding for {{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/sysctl.d/99-wireguard.conf
|
||||||
|
content: |
|
||||||
|
net.ipv4.ip_forward = 1
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
notify: Apply sysctl
|
||||||
|
when: _tunnel.server_mode | default(false)
|
||||||
|
|
||||||
|
- name: "Configure the firewall for {{ _tunnel.interface }}"
|
||||||
|
community.general.ufw:
|
||||||
|
rule: allow
|
||||||
|
port: "{{ _tunnel.port }}"
|
||||||
|
proto: udp
|
||||||
|
direction: in
|
||||||
|
comment: "Wireguard VPN ({{ _tunnel.interface }})"
|
||||||
|
retries: 5
|
||||||
|
delay: 2
|
||||||
|
register: _ufw_result
|
||||||
|
until: _ufw_result is succeeded
|
||||||
|
when:
|
||||||
|
- _tunnel.server_mode | default(false)
|
||||||
|
- _tunnel.port is defined
|
||||||
|
|
||||||
|
- name: "Start and enable wg-quick@{{ _tunnel.interface }}"
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: "wg-quick@{{ _tunnel.interface }}"
|
||||||
|
state: started
|
||||||
|
enabled: true
|
||||||
|
daemon_reload: true
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: "paru -S --noconfirm zfs-dkms zfs-utils"
|
cmd: "paru -S --noconfirm zfs-dkms zfs-utils"
|
||||||
|
|
||||||
- name: Restore SUDOERS password prompt after yay
|
- name: Restore SUDOERS password prompt after paru
|
||||||
no_log: true
|
no_log: true
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /etc/sudoers
|
dest: /etc/sudoers
|
||||||
|
|||||||
Reference in New Issue
Block a user