feat: add postgres support

This commit is contained in:
Clément Désiles 2025-11-10 18:24:43 +01:00
parent 2c8f49191c
commit 83b6a38999
No known key found for this signature in database
8 changed files with 409 additions and 0 deletions

View File

@ -2,3 +2,4 @@
collections: collections:
- name: ansible.netcommon - name: ansible.netcommon
- name: community.general - name: community.general
- name: community.postgresql

277
roles/postgres/README.md Normal file
View File

@ -0,0 +1,277 @@
# PostgreSQL Role
This Ansible role installs and configures PostgreSQL for local use only. It provides a shared PostgreSQL instance that multiple services can use with isolated databases and users.
## Features
- Installs PostgreSQL
- Local-only access (localhost)
- Configurable performance settings
- Each service manages its own database/user (see below)
## Requirements
- Systemd-based Linux distribution
- Root/sudo access
- Python `psycopg2` package (for database operations from service roles)
## Role Variables
Available variables with defaults (see `defaults/main.yml`):
```yaml
# Performance tuning
postgres_shared_buffers: 256MB
postgres_effective_cache_size: 1GB
postgres_maintenance_work_mem: 64MB
postgres_work_mem: 4MB
postgres_max_connections: 100
```
## Dependencies
None.
## Example Playbook
```yaml
---
- hosts: servers
become: true
roles:
- role: postgres
- role: immich # Will create its own database
- role: nextcloud # Will create its own database
```
## Database Isolation Strategy
This role follows a **decentralized database management** pattern:
### 1. PostgreSQL Role Responsibility
- Install and configure PostgreSQL
- Manage global performance settings
- Ensure the service is running
### 2. Service Role Responsibility
Each service role (immich, nextcloud, etc.) manages its own:
- Database creation
- User creation
- Password management
- Schema migrations
### 3. Security & Isolation
**Database Isolation:**
- Each service gets its own database
- Example: `immich`, `nextcloud`, `gitea`
**User Isolation:**
- Each service gets its own PostgreSQL user
- Users can only access their own database
- Example: `immich``immich` database only
**Authentication:**
- 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:
**Option 1: Use host network mode**
```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
```
**Option 3: Bridge with firewall (less secure)**
Bind postgres to `0.0.0.0` and use container gateway IP.
### From System Services
Services running directly on the host can connect to `localhost:5432` without any special configuration.
## Security Best Practices
### 1. Use Ansible Vault for Passwords
```bash
# Create encrypted variables
ansible-vault encrypt_string 'my_secure_password' --name 'immich_db_password'
```
Add to your inventory or vars:
```yaml
immich_db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
...encrypted...
```
### 2. Unique Passwords per Service
Never reuse passwords between services:
```yaml
immich_db_password: unique_password_1
nextcloud_db_password: unique_password_2
gitea_db_password: unique_password_3
```
### 3. Minimal Privileges
The pattern above ensures users have:
- ✅ Access to their database only
- ❌ No superuser privileges
- ❌ Cannot create databases
- ❌ Cannot create roles
- ❌ Cannot access other databases
### 4. Local-Only Access
PostgreSQL is configured to listen on `localhost` only:
- No remote connections allowed
- Services must run on the same host
## Troubleshooting
### Check PostgreSQL status
```bash
systemctl status postgresql
```
### Connect to PostgreSQL
```bash
sudo -u postgres psql
```
### List databases
```sql
\l
```
### List users and permissions
```sql
\du
```
### Test connection from service
```bash
psql -h localhost -U immich -d immich
```
### View logs
```bash
journalctl -u postgresql -f
```
## Performance Tuning
Adjust variables based on your hardware:
**For systems with 4GB RAM:**
```yaml
postgres_shared_buffers: 1GB
postgres_effective_cache_size: 3GB
```
**For systems with 16GB RAM:**
```yaml
postgres_shared_buffers: 4GB
postgres_effective_cache_size: 12GB
```
**Rule of thumb:**
- `shared_buffers`: 25% of total RAM
- `effective_cache_size`: 50-75% of total RAM
## Backup Recommendations
Consider implementing:
1. **pg_dump** for logical backups
2. **WAL archiving** for point-in-time recovery
3. **Automated backup scripts** via cron
Example backup script for a service:
```bash
pg_dump -h localhost -U immich immich > /backup/immich_$(date +%Y%m%d).sql
```
## License
MIT
## Author Information
Created for managing shared PostgreSQL instances in NAS/homelab environments.

View File

@ -0,0 +1,18 @@
---
# PostgreSQL admin user (used by service roles 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
# PostgreSQL data directory
postgres_data_dir: /var/lib/postgres/data
# Performance tuning (adjust based on your hardware)
postgres_shared_buffers: 256MB
postgres_effective_cache_size: 1GB
postgres_maintenance_work_mem: 64MB
postgres_work_mem: 4MB
postgres_max_connections: 100

View File

@ -0,0 +1,10 @@
---
- name: Restart PostgreSQL
ansible.builtin.systemd:
name: "{{ postgres_service_name }}"
state: restarted
- name: Reload PostgreSQL
ansible.builtin.systemd:
name: "{{ postgres_service_name }}"
state: reloaded

View File

@ -0,0 +1,75 @@
---
- name: Validate required password is set
ansible.builtin.assert:
that:
- postgres_admin_password is defined
- postgres_admin_password | length >= 12
fail_msg: |
postgres_admin_password is required (min 12 chars).
See roles/postgres/defaults/main.yml for configuration instructions.
success_msg: "Password validation passed"
- name: Load OS-specific variables
ansible.builtin.include_vars: "{{ item }}"
with_first_found:
- "{{ ansible_facts['os_family'] }}.yml"
- debian.yml
- name: Install PostgreSQL packages
ansible.builtin.package:
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: Ensure PostgreSQL config directory exists
ansible.builtin.file:
path: "{{ postgres_config_dir }}"
state: directory
owner: postgres
group: postgres
mode: "0750"
- name: Enable include_dir in main postgresql.conf
ansible.builtin.lineinfile:
path: "{{ postgres_config_path }}"
regexp: "^#?include_dir ="
line: "include_dir = 'conf.d'"
state: present
notify: Restart PostgreSQL
- name: Deploy custom PostgreSQL configuration
ansible.builtin.template:
src: custom.conf.j2
dest: "{{ postgres_config_dir }}/custom.conf"
owner: postgres
group: postgres
mode: "0640"
notify: Restart PostgreSQL
- name: Enable and start PostgreSQL service
ansible.builtin.systemd:
name: "{{ postgres_service_name }}"
enabled: true
state: started
- name: Set PostgreSQL admin user password
community.postgresql.postgresql_user:
name: "{{ postgres_admin_user }}"
password: "{{ postgres_admin_password }}"
state: present
become: true
become_user: "{{ postgres_admin_user }}"

View File

@ -0,0 +1,9 @@
# Custom PostgreSQL configuration managed by Ansible
# Override settings from main postgresql.conf
# Performance tuning
shared_buffers = {{ postgres_shared_buffers }}
effective_cache_size = {{ postgres_effective_cache_size }}
maintenance_work_mem = {{ postgres_maintenance_work_mem }}
work_mem = {{ postgres_work_mem }}
max_connections = {{ postgres_max_connections }}

View File

@ -0,0 +1,9 @@
---
postgres_packages:
- postgresql
- python-psycopg2
postgres_service_name: postgresql
postgres_config_path: "{{ postgres_data_dir }}/postgresql.conf"
postgres_config_dir: "{{ postgres_data_dir }}/conf.d"
postgres_hba_path: "{{ postgres_data_dir }}/pg_hba.conf"

View File

@ -0,0 +1,10 @@
---
postgres_packages:
- postgresql
- postgresql-contrib
- python3-psycopg2
postgres_service_name: postgresql
postgres_config_path: /etc/postgresql/current/main/postgresql.conf
postgres_config_dir: /etc/postgresql/current/main/conf.d
postgres_hba_path: /etc/postgresql/current/main/pg_hba.conf