From e7dbe470da264f7583dc2160786608a54ab50502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20D=C3=A9siles?= <1536672+cdesiles@users.noreply.github.com> Date: Tue, 11 Nov 2025 00:02:15 +0100 Subject: [PATCH] feat: pg with extensions and open to podmans containers --- roles/postgres/README.md | 142 +++++++++++------------- roles/postgres/defaults/main.yml | 16 ++- roles/postgres/tasks/archlinux.yml | 38 +++++++ roles/postgres/tasks/debian.yml | 16 +++ roles/postgres/tasks/main.yml | 36 +++--- roles/postgres/templates/custom.conf.j2 | 4 + roles/postgres/vars/archlinux.yml | 4 + 7 files changed, 158 insertions(+), 98 deletions(-) create mode 100644 roles/postgres/tasks/archlinux.yml create mode 100644 roles/postgres/tasks/debian.yml diff --git a/roles/postgres/README.md b/roles/postgres/README.md index 13afa9f..0f627bc 100644 --- a/roles/postgres/README.md +++ b/roles/postgres/README.md @@ -20,6 +20,24 @@ This Ansible role installs and configures PostgreSQL for local use only. It prov Available variables with defaults (see `defaults/main.yml`): ```yaml +# PostgreSQL admin user +postgres_admin_user: postgres + +# PostgreSQL admin password (REQUIRED - must be set explicitly) +# postgres_admin_password: "" # Intentionally undefined + +# PostgreSQL data directory +postgres_data_dir: /var/lib/postgres/data + +# Network configuration +postgres_listen_addresses: 127.0.0.1 # For container access: "127.0.0.1,{{ podman_subnet_gateway }}" +postgres_port: 5432 + +# Firewall configuration +postgres_firewall_allowed_sources: + - 127.0.0.0/8 # Localhost + - "{{ podman_subnet | default('10.88.0.0/16') }}" # Podman bridge network + # Performance tuning postgres_shared_buffers: 256MB postgres_effective_cache_size: 1GB @@ -75,90 +93,38 @@ Each service role (immich, nextcloud, etc.) manages its own: - Each user has a unique password - Passwords stored in service role variables (use Ansible Vault for production) -## How to Use from Service Roles - -### Pattern for Service Roles - -When creating a service role that needs PostgreSQL: - -**1. Add postgres as a dependency** (`meta/main.yml`): -```yaml -dependencies: - - role: postgres -``` - -**2. Define database variables** (`defaults/main.yml`): -```yaml -myservice_db_name: myservice -myservice_db_user: myservice_user -myservice_db_password: changeme # Use Ansible Vault in production! -myservice_db_host: localhost -myservice_db_port: 5432 -``` - -**3. Create database and user** (`tasks/main.yml`): -```yaml -- name: Create PostgreSQL database for myservice - community.postgresql.postgresql_db: - name: "{{ myservice_db_name }}" - state: present - become: true - become_user: "{{ postgres_admin_user }}" - -- name: Create PostgreSQL user for myservice - community.postgresql.postgresql_user: - name: "{{ myservice_db_user }}" - password: "{{ myservice_db_password }}" - db: "{{ myservice_db_name }}" - priv: ALL - state: present - become: true - become_user: "{{ postgres_admin_user }}" - -- name: Ensure user has no superuser privileges - community.postgresql.postgresql_user: - name: "{{ myservice_db_user }}" - role_attr_flags: NOSUPERUSER,NOCREATEDB,NOCREATEROLE - state: present - become: true - become_user: "{{ postgres_admin_user }}" -``` - -**Note:** `postgres_admin_user` is provided by the postgres role and defaults to `postgres`. - -**4. Configure your service** to connect to: -``` -Host: localhost -Port: 5432 -Database: myservice -User: myservice_user -Password: changeme -``` - -### Real Example: Immich - -See `roles/immich/` for a complete working example of using this pattern. - ## Connection Methods ### From Containers -If your service runs in a container (Docker/Podman), you need to: +If your service runs in a container (Docker/Podman), you need to configure PostgreSQL to listen on the Podman bridge gateway: -**Option 1: Use host network mode** +**Step 1: Configure PostgreSQL in inventory** ```yaml -network_mode: host -``` -Then connect to `localhost:5432` - -**Option 2: Use host.containers.internal (Podman/Docker)** -```yaml -DB_HOSTNAME: host.containers.internal -DB_PORT: 5432 +# inventory/host_vars/yourserver.yml +postgres_listen_addresses: "127.0.0.1,{{ podman_subnet_gateway }}" +postgres_firewall_allowed_sources: + - 127.0.0.0/8 + - "{{ podman_subnet }}" ``` -**Option 3: Bridge with firewall (less secure)** -Bind postgres to `0.0.0.0` and use container gateway IP. +**Step 2: Use host.containers.internal in containers** +```yaml +# docker-compose.yml +services: + myservice: + extra_hosts: + - "host.containers.internal:host-gateway" + environment: + DB_HOSTNAME: host.containers.internal + DB_PORT: 5432 +``` + +**What this does:** +- PostgreSQL listens on `127.0.0.1` (localhost) and `10.88.0.1` (Podman gateway) +- UFW firewall allows connections from localhost and Podman subnet +- `pg_hba.conf` automatically configured to allow Podman subnet +- `host.containers.internal` resolves to the gateway IP inside containers ### From System Services @@ -198,11 +164,14 @@ The pattern above ensures users have: - ❌ Cannot create roles - ❌ Cannot access other databases -### 4. Local-Only Access +### 4. Controlled Access -PostgreSQL is configured to listen on `localhost` only: -- No remote connections allowed -- Services must run on the same host +PostgreSQL default configuration: +- Listens on `localhost` only by default +- To allow container access, set `postgres_listen_addresses` to include Podman gateway +- UFW firewall rules automatically configured for allowed sources +- `pg_hba.conf` automatically configured for Podman subnet when enabled +- No remote network access by default ## Troubleshooting @@ -228,7 +197,20 @@ sudo -u postgres psql ### Test connection from service ```bash +# From localhost psql -h localhost -U immich -d immich + +# From Podman gateway (if configured) +psql -h 10.88.0.1 -U immich -d immich + +# Check listen addresses +sudo -u postgres psql -c "SHOW listen_addresses;" + +# Check firewall rules +sudo ufw status | grep 5432 + +# Check pg_hba.conf +sudo grep -v "^#" /var/lib/postgres/data/pg_hba.conf | grep -v "^$" ``` ### View logs diff --git a/roles/postgres/defaults/main.yml b/roles/postgres/defaults/main.yml index 7be1b01..5d8eddb 100644 --- a/roles/postgres/defaults/main.yml +++ b/roles/postgres/defaults/main.yml @@ -1,15 +1,25 @@ --- -# PostgreSQL admin user (used by service roles for database management) +# PostgreSQL port +postgres_port: 5432 + +# PostgreSQL admin user (only for database management) postgres_admin_user: postgres # PostgreSQL admin password (REQUIRED - must be set explicitly) # Set via inventory, host_vars, or ansible-vault -# See this file's comments for setup instructions -# postgres_admin_password: "" # Intentionally undefined - role will fail if not set +# postgres_admin_password: "" # PostgreSQL data directory postgres_data_dir: /var/lib/postgres/data +# Binding address(es) +postgres_listen_addresses: "127.0.0.1,{{ podman_subnet_gateway | default('10.88.0.1' }}" + +# Firewall configuration +postgres_firewall_allowed_sources: + - 127.0.0.0/8 # Localhost + - "{{ podman_subnet | default('10.88.0.0/16') }}" # Podman bridge network + # Performance tuning (adjust based on your hardware) postgres_shared_buffers: 256MB postgres_effective_cache_size: 1GB diff --git a/roles/postgres/tasks/archlinux.yml b/roles/postgres/tasks/archlinux.yml new file mode 100644 index 0000000..e54b61c --- /dev/null +++ b/roles/postgres/tasks/archlinux.yml @@ -0,0 +1,38 @@ +--- +- name: Check if pgvector is installed + ansible.builtin.command: pacman -Qi pgvector + register: pgvector_installed + changed_when: false + failed_when: false + +- name: Install pgvector from AUR + when: pgvector_installed.rc != 0 + block: + - name: Disable SUDOERS password prompt for AUR installation + no_log: true + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^#?%wheel" + line: "%wheel ALL=(ALL) NOPASSWD: ALL" + validate: /usr/sbin/visudo -cf %s + + - name: Install pgvector from AUR + become: false + ansible.builtin.command: + cmd: "paru -S --noconfirm pgvector" + + - name: Restore SUDOERS password prompt after AUR installation + no_log: true + ansible.builtin.lineinfile: + dest: /etc/sudoers + state: present + regexp: "^#?%wheel" + line: "%wheel ALL=(ALL:ALL) ALL" + validate: /usr/sbin/visudo -cf %s + +- name: Ensure PostgreSQL is initialized + ansible.builtin.command: + cmd: initdb -D {{ postgres_data_dir }} + creates: "{{ postgres_data_dir }}/PG_VERSION" + become_user: "{{ postgres_admin_user }}" diff --git a/roles/postgres/tasks/debian.yml b/roles/postgres/tasks/debian.yml new file mode 100644 index 0000000..3c412ae --- /dev/null +++ b/roles/postgres/tasks/debian.yml @@ -0,0 +1,16 @@ +--- +- name: Create current version symlink + ansible.builtin.shell: + cmd: set -o pipefail && ln -sf $(ls -1 /etc/postgresql/ | grep -E '^[0-9]+$' | sort -V | tail -n1) /etc/postgresql/current + creates: /etc/postgresql/current + executable: /bin/bash + +- name: Get installed PostgreSQL version + ansible.builtin.shell: psql --version | grep -oP '\d+' | head -1 + register: postgres_version + changed_when: false + +- name: Install pgvector extension + ansible.builtin.package: + name: "postgresql-{{ postgres_version.stdout }}-pgvector" + state: present diff --git a/roles/postgres/tasks/main.yml b/roles/postgres/tasks/main.yml index 0809429..c38573d 100644 --- a/roles/postgres/tasks/main.yml +++ b/roles/postgres/tasks/main.yml @@ -20,20 +20,8 @@ name: "{{ postgres_packages }}" state: present -- name: Create current version symlink (Debian) - ansible.builtin.shell: - cmd: set -o pipefail && ln -sf $(ls -1 /etc/postgresql/ | grep -E '^[0-9]+$' | sort -V | tail -n1) /etc/postgresql/current - creates: /etc/postgresql/current - executable: /bin/bash - when: ansible_facts['os_family'] == 'Debian' - -- name: Ensure PostgreSQL is initialized (Arch) - ansible.builtin.command: - cmd: initdb -D {{ postgres_data_dir }} - creates: "{{ postgres_data_dir }}/PG_VERSION" - become: true - become_user: "{{ postgres_admin_user }}" - when: ansible_facts['os_family'] == 'Archlinux' +- name: Include OS-specific tasks + ansible.builtin.include_tasks: "{{ ansible_facts['os_family'] | lower }}.yml" - name: Ensure PostgreSQL config directory exists ansible.builtin.file: @@ -60,6 +48,25 @@ mode: "0640" notify: Restart PostgreSQL +- name: Configure pg_hba.conf for Podman subnet access + ansible.builtin.lineinfile: + path: "{{ postgres_hba_path }}" + line: "host all all {{ podman_subnet }} scram-sha-256" + insertafter: "^# IPv4 local connections:" + state: present + when: podman_subnet is defined + notify: Restart PostgreSQL + +- name: Setup firewall rules for PostgreSQL + community.general.ufw: + rule: allow + src: "{{ item }}" + port: "{{ postgres_port }}" + proto: tcp + direction: in + comment: "PostgreSQL" + loop: "{{ postgres_firewall_allowed_sources }}" + - name: Enable and start PostgreSQL service ansible.builtin.systemd: name: "{{ postgres_service_name }}" @@ -71,5 +78,4 @@ name: "{{ postgres_admin_user }}" password: "{{ postgres_admin_password }}" state: present - become: true become_user: "{{ postgres_admin_user }}" diff --git a/roles/postgres/templates/custom.conf.j2 b/roles/postgres/templates/custom.conf.j2 index 3774d6b..847c922 100644 --- a/roles/postgres/templates/custom.conf.j2 +++ b/roles/postgres/templates/custom.conf.j2 @@ -1,6 +1,10 @@ # Custom PostgreSQL configuration managed by Ansible # Override settings from main postgresql.conf +# Network configuration +listen_addresses = '{{ postgres_listen_addresses }}' +port = {{ postgres_port }} + # Performance tuning shared_buffers = {{ postgres_shared_buffers }} effective_cache_size = {{ postgres_effective_cache_size }} diff --git a/roles/postgres/vars/archlinux.yml b/roles/postgres/vars/archlinux.yml index 1c263f6..fb811ae 100644 --- a/roles/postgres/vars/archlinux.yml +++ b/roles/postgres/vars/archlinux.yml @@ -3,6 +3,10 @@ postgres_packages: - postgresql - python-psycopg2 +# AUR packages (installed via paru) +postgres_aur_packages: + - pgvector + postgres_service_name: postgresql postgres_config_path: "{{ postgres_data_dir }}/postgresql.conf" postgres_config_dir: "{{ postgres_data_dir }}/conf.d"