184 lines
5.8 KiB
YAML
184 lines
5.8 KiB
YAML
|
|
---
|
||
|
|
# PostgreSQL installation and configuration
|
||
|
|
#
|
||
|
|
# Passwords are fetched from 1Password at runtime using the `op` CLI.
|
||
|
|
# Requires: `op` authenticated on the control machine (run `op signin` first).
|
||
|
|
|
||
|
|
- name: Install {{ postgresql_formula }} via homebrew
|
||
|
|
community.general.homebrew:
|
||
|
|
name: "{{ postgresql_formula }}"
|
||
|
|
state: present
|
||
|
|
|
||
|
|
# === Fetch passwords from 1Password (on control machine) ===
|
||
|
|
# These are skipped when running full playbook (pre_tasks sets them)
|
||
|
|
# but run when using --tags postgresql
|
||
|
|
|
||
|
|
- name: Fetch superuser password from 1Password
|
||
|
|
ansible.builtin.command:
|
||
|
|
cmd: op --vault {{ postgresql_op_vault }} item get {{ postgresql_op_superuser_item }} --fields password --reveal
|
||
|
|
delegate_to: localhost
|
||
|
|
register: pg_superuser_password_result
|
||
|
|
changed_when: false
|
||
|
|
no_log: true
|
||
|
|
when: pg_superuser_password is not defined
|
||
|
|
|
||
|
|
- name: Set superuser password fact
|
||
|
|
ansible.builtin.set_fact:
|
||
|
|
pg_superuser_password: "{{ pg_superuser_password_result.stdout }}"
|
||
|
|
no_log: true
|
||
|
|
when: pg_superuser_password is not defined
|
||
|
|
|
||
|
|
- name: Fetch user passwords from 1Password
|
||
|
|
ansible.builtin.command:
|
||
|
|
cmd: op --vault {{ postgresql_op_vault }} item get {{ item.op_item }} --fields {{ item.op_field }} --reveal
|
||
|
|
delegate_to: localhost
|
||
|
|
loop: "{{ postgresql_users }}"
|
||
|
|
register: pg_user_passwords_result
|
||
|
|
changed_when: false
|
||
|
|
no_log: true
|
||
|
|
when: pg_user_passwords is not defined
|
||
|
|
|
||
|
|
- name: Build user password lookup
|
||
|
|
ansible.builtin.set_fact:
|
||
|
|
pg_user_passwords: "{{ pg_user_passwords | default({}) | combine({item.item.name: item.stdout}) }}"
|
||
|
|
loop: "{{ pg_user_passwords_result.results }}"
|
||
|
|
no_log: true
|
||
|
|
when: pg_user_passwords is not defined
|
||
|
|
|
||
|
|
# === Initialize PostgreSQL cluster ===
|
||
|
|
|
||
|
|
- name: Check if postgresql data directory is initialized
|
||
|
|
ansible.builtin.stat:
|
||
|
|
path: "{{ postgresql_data_dir }}/PG_VERSION"
|
||
|
|
register: pg_data
|
||
|
|
|
||
|
|
- name: Create temporary password file for initdb
|
||
|
|
ansible.builtin.copy:
|
||
|
|
content: "{{ pg_superuser_password }}"
|
||
|
|
dest: /tmp/.pg_init_pwfile
|
||
|
|
mode: '0600'
|
||
|
|
when: not pg_data.stat.exists
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- name: Initialize postgresql database cluster with superuser password
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/initdb
|
||
|
|
--locale=en_US.UTF-8 -E UTF-8
|
||
|
|
--pwfile=/tmp/.pg_init_pwfile
|
||
|
|
{{ postgresql_data_dir }}
|
||
|
|
when: not pg_data.stat.exists
|
||
|
|
|
||
|
|
- name: Remove temporary password file
|
||
|
|
ansible.builtin.file:
|
||
|
|
path: /tmp/.pg_init_pwfile
|
||
|
|
state: absent
|
||
|
|
when: not pg_data.stat.exists
|
||
|
|
|
||
|
|
# === Configure and start PostgreSQL ===
|
||
|
|
|
||
|
|
- name: Deploy pg_hba.conf
|
||
|
|
ansible.builtin.template:
|
||
|
|
src: pg_hba.conf.j2
|
||
|
|
dest: "{{ postgresql_config_dir }}/pg_hba.conf"
|
||
|
|
mode: '0600'
|
||
|
|
notify: restart postgresql
|
||
|
|
|
||
|
|
- name: Ensure postgresql service is started
|
||
|
|
ansible.builtin.command: brew services start {{ postgresql_formula }}
|
||
|
|
register: brew_start
|
||
|
|
changed_when: "'Successfully started' in brew_start.stdout"
|
||
|
|
failed_when: false
|
||
|
|
|
||
|
|
- name: Wait for postgresql to accept connections
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/pg_isready -h localhost -p {{ postgresql_port }}
|
||
|
|
register: pg_ready
|
||
|
|
until: pg_ready.rc == 0
|
||
|
|
retries: 10
|
||
|
|
delay: 2
|
||
|
|
changed_when: false
|
||
|
|
|
||
|
|
# === Create users with passwords ===
|
||
|
|
|
||
|
|
- name: Check if postgresql users exist
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/psql -h localhost -d postgres -tAc
|
||
|
|
"SELECT 1 FROM pg_roles WHERE rolname = '{{ item.name }}';"
|
||
|
|
environment:
|
||
|
|
PGPASSWORD: "{{ pg_superuser_password }}"
|
||
|
|
loop: "{{ postgresql_users }}"
|
||
|
|
register: user_check
|
||
|
|
changed_when: false
|
||
|
|
failed_when: false
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- name: Create postgresql users with passwords
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/psql -h localhost -d postgres -c
|
||
|
|
"CREATE USER {{ item.item.name }} WITH PASSWORD '{{ pg_user_passwords[item.item.name] }}';"
|
||
|
|
environment:
|
||
|
|
PGPASSWORD: "{{ pg_superuser_password }}"
|
||
|
|
loop: "{{ user_check.results }}"
|
||
|
|
when: item.stdout != "1"
|
||
|
|
changed_when: true
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- name: Update postgresql user passwords (idempotent)
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/psql -h localhost -d postgres -c
|
||
|
|
"ALTER USER {{ item.name }} WITH PASSWORD '{{ pg_user_passwords[item.name] }}';"
|
||
|
|
environment:
|
||
|
|
PGPASSWORD: "{{ pg_superuser_password }}"
|
||
|
|
loop: "{{ postgresql_users }}"
|
||
|
|
changed_when: false
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
# === Grant roles to users ===
|
||
|
|
|
||
|
|
- name: Grant roles to users
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/psql -h localhost -d postgres -c "GRANT {{ item.1 }} TO {{ item.0.name }};"
|
||
|
|
environment:
|
||
|
|
PGPASSWORD: "{{ pg_superuser_password }}"
|
||
|
|
loop: "{{ postgresql_users | subelements('roles', skip_missing=True) }}"
|
||
|
|
changed_when: false
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
# === Create databases ===
|
||
|
|
|
||
|
|
- name: Check if postgresql databases exist
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/psql -h localhost -d postgres -tAc
|
||
|
|
"SELECT 1 FROM pg_database WHERE datname = '{{ item.name }}';"
|
||
|
|
environment:
|
||
|
|
PGPASSWORD: "{{ pg_superuser_password }}"
|
||
|
|
loop: "{{ postgresql_databases }}"
|
||
|
|
register: db_check
|
||
|
|
changed_when: false
|
||
|
|
failed_when: false
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
- name: Create postgresql databases
|
||
|
|
ansible.builtin.command: >
|
||
|
|
{{ postgresql_bin_dir }}/createdb -h localhost
|
||
|
|
--owner={{ item.item.owner }}
|
||
|
|
{{ item.item.name }}
|
||
|
|
environment:
|
||
|
|
PGPASSWORD: "{{ pg_superuser_password }}"
|
||
|
|
loop: "{{ db_check.results }}"
|
||
|
|
when: item.stdout != "1"
|
||
|
|
changed_when: true
|
||
|
|
no_log: true
|
||
|
|
|
||
|
|
# === Write credential files for local access ===
|
||
|
|
|
||
|
|
# .pgpass is used by borgmatic for pg_dump backups
|
||
|
|
# Only includes read-only roles (borgmatic has pg_read_all_data)
|
||
|
|
- name: Write .pgpass file for borgmatic backups
|
||
|
|
ansible.builtin.copy:
|
||
|
|
content: |
|
||
|
|
# Managed by ansible - only read-only roles
|
||
|
|
localhost:{{ postgresql_port }}:*:borgmatic:{{ pg_user_passwords['borgmatic'] }}
|
||
|
|
dest: ~/.pgpass
|
||
|
|
mode: '0600'
|
||
|
|
no_log: true
|