From 56885d70b69b064bdb79970a599be0eb733a7c75 Mon Sep 17 00:00:00 2001
From: Tobias Reisinger <tobias@msrg.cc>
Date: Thu, 31 Oct 2024 02:25:02 +0100
Subject: [PATCH] Add postgresql role

---
 playbooks/filter_plugins/utils.py             |  7 ++
 playbooks/roles/backup/files/hooks/postgresql |  3 +-
 .../roles/backup/files/recovery/postgresql    | 29 ++++++
 .../roles/lego/files/hooks/db.serguzim.me     |  5 +-
 playbooks/roles/postgresql/tasks/main.yml     | 90 +++++++++++++++++++
 playbooks/roles/postgresql/vars/main.yml      | 20 +++++
 services.auto.tfvars                          |  2 +-
 7 files changed, 152 insertions(+), 4 deletions(-)
 create mode 100755 playbooks/roles/backup/files/recovery/postgresql
 create mode 100644 playbooks/roles/postgresql/tasks/main.yml
 create mode 100644 playbooks/roles/postgresql/vars/main.yml

diff --git a/playbooks/filter_plugins/utils.py b/playbooks/filter_plugins/utils.py
index 8e88f4b..6f49eb4 100644
--- a/playbooks/filter_plugins/utils.py
+++ b/playbooks/filter_plugins/utils.py
@@ -3,6 +3,7 @@ class FilterModule(object):
         return {
             'list_prefix_suffix': self.list_prefix_suffix,
             'list_prefix_path_suffix': self.list_prefix_path_suffix,
+            'postgresql_restart_required': self.postgresql_restart_required,
         }
 
     def list_prefix_suffix(self, values, prefix, suffix):
@@ -17,3 +18,9 @@ class FilterModule(object):
         for value in values:
             result.append(f"{prefix}{value}{suffix}")
         return result
+
+    def postgresql_restart_required(self, results):
+        for result in results:
+            if result.get('restart_required') and result.get('changed'):
+                return True
+        return False
diff --git a/playbooks/roles/backup/files/hooks/postgresql b/playbooks/roles/backup/files/hooks/postgresql
index f3db61d..e4e8b0d 100755
--- a/playbooks/roles/backup/files/hooks/postgresql
+++ b/playbooks/roles/backup/files/hooks/postgresql
@@ -11,5 +11,6 @@ do
 	echo " done"
 done
 
-echo "dumping all"
+echo -n "dumping all ..."
 sudo -u postgres pg_dumpall | gzip >"pg_dumpall.sql.gz"
+echo " done"
diff --git a/playbooks/roles/backup/files/recovery/postgresql b/playbooks/roles/backup/files/recovery/postgresql
new file mode 100755
index 0000000..4bc4936
--- /dev/null
+++ b/playbooks/roles/backup/files/recovery/postgresql
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+set -e
+
+target="$1"
+recovery_file="pg_dumpall.sql.gz"
+if [ -n "$target" ]; then
+	recovery_file="pg_dump_$target.sql.gz"
+fi
+
+tmp_dir=$(mktemp -d)
+recovery_file_path="$tmp_dir/opt/services/_backup/postgresql/$recovery_file"
+
+cd /opt/services/backup/
+
+echo "Restoring backup..."
+autorestic -c .autorestic.all.yml restore -l postgresql --from borgbase --to "$tmp_dir"
+echo "Backup restored."
+
+echo "Recovery file: $recovery_file"
+
+echo "Restoring database..."
+cat "$recovery_file_path" \
+  | gunzip \
+  | sudo -u postgres psql
+echo "Database restored."
+
+echo "Removing temporary files..."
+rm -rf "$tmp_dir"
diff --git a/playbooks/roles/lego/files/hooks/db.serguzim.me b/playbooks/roles/lego/files/hooks/db.serguzim.me
index b411f33..f22a617 100755
--- a/playbooks/roles/lego/files/hooks/db.serguzim.me
+++ b/playbooks/roles/lego/files/hooks/db.serguzim.me
@@ -5,12 +5,13 @@ domain="db.serguzim.me"
 _install() {
   install --owner=postgres --group=postgres --mode=600 \
 	  "$CERTIFICATES_PATH/$domain.$1" \
-	  "/var/lib/postgres/data/server.$1"
+	  "/etc/postgresql/cert.$1"
 }
 
 _install crt
 _install key
 
-sudo -u postgres pg_ctl -D /var/lib/postgres/data/ reload
+#sudo -u postgres pg_ctl -D /var/lib/postgres/data/ reload
+systemctl reload postgresql
 
 # vim: ft=sh
diff --git a/playbooks/roles/postgresql/tasks/main.yml b/playbooks/roles/postgresql/tasks/main.yml
new file mode 100644
index 0000000..762a879
--- /dev/null
+++ b/playbooks/roles/postgresql/tasks/main.yml
@@ -0,0 +1,90 @@
+- name: Ensure directory for postgresql repo key exists
+  ansible.builtin.file:
+    path: "{{ postgresql_repo_key_dir }}"
+    state: directory
+    mode: "0755"
+  become: true
+- name: Add postgresql repo key
+  ansible.builtin.get_url:
+    url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
+    dest: "{{ postgresql_repo_key_file }}"
+    mode: "0644"
+  become: true
+
+- name: Add postgresql repo into sources list
+  ansible.builtin.apt_repository:
+    repo: deb [signed-by={{ postgresql_repo_key_file }}] https://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main
+    state: present
+  become: true
+
+- name: Install postgresql
+  ansible.builtin.apt:
+    pkg:
+      - postgresql-16
+      - python3-psycopg
+    state: present
+    update_cache: true
+  become: true
+
+- name: Check out if postgresql is initialized
+  ansible.builtin.stat:
+    path: "{{ postgresql_data_dir }}"
+  register: postgresql_data_stat
+  become: true
+
+- name: Initialize postgresql
+  ansible.builtin.command: "{{ postgresql_bin_initdb }} -D {{ postgresql_data_dir }}"
+  when: not postgresql_data_stat.stat.exists
+  changed_when: true
+
+- name: Replace everything with a new set of rules
+  community.postgresql.postgresql_pg_hba:
+    dest: "{{ postgresql_conf_pg_hba }}"
+    overwrite: true # remove pre-existing rules
+
+    # defaults
+    rules_behavior: combine
+    contype: hostssl
+    users: all
+    databases: all
+    method: scram-sha-256
+
+    rules:
+      - contype: local
+        users: postgres
+        method: trust
+      - contype: hostssl
+        address: "0.0.0.0/0"
+      - contype: hostssl
+        address: "::/0"
+  become: true
+  register: pg_hba_reg
+
+- name: Restart postgresql on pg_hba change
+  ansible.builtin.systemd_service:
+    state: restarted
+    name: postgresql
+  when: pg_hba_reg.changed # noqa: no-handler TODO can we add a handler here?
+  become: true
+
+- name: Start postgresql
+  ansible.builtin.systemd_service:
+    state: started
+    name: postgresql
+  become: true
+
+- name: Set parameters
+  community.postgresql.postgresql_set:
+    login_unix_socket: /var/run/postgresql
+    login_user: postgres
+    name: "{{ item.name }}"
+    value: "{{ item.value }}"
+  loop: "{{ postgresql_set_vars | dict2items(key_name='name', value_name='value') }}"
+  register: set
+
+- name: Restart postgresql
+  ansible.builtin.systemd_service:
+    state: restarted
+    name: postgresql
+  when: set.results | postgresql_restart_required
+  become: true
diff --git a/playbooks/roles/postgresql/vars/main.yml b/playbooks/roles/postgresql/vars/main.yml
new file mode 100644
index 0000000..e052b91
--- /dev/null
+++ b/playbooks/roles/postgresql/vars/main.yml
@@ -0,0 +1,20 @@
+postgresql_version: 16
+
+postgresql_repo_key_dir: /usr/share/postgresql-common/pgdg
+postgresql_repo_key_file: "{{ (postgresql_repo_key_dir, 'apt.postgresql.org.asc') | path_join }}"
+
+postgresql_conf_dir: /etc/postgresql/{{ postgresql_version }}/main
+postgresql_conf_pg_hba: "{{ (postgresql_conf_dir, 'pg_hba.conf') | path_join }}"
+
+postgresql_data_dir: /var/lib/postgresql/{{ postgresql_version }}/main
+
+postgresql_bin_dir: /usr/lib/postgresql/{{ postgresql_version }}/bin
+postgresql_bin_initdb: "{{ (postgresql_bin_dir, 'initdb') | path_join }}"
+
+postgresql_set_vars:
+  listen_addresses: '*'
+  ssl: true
+  ssl_cert_file: /etc/postgresql/cert.crt
+  ssl_key_file: /etc/postgresql/cert.key
+  log_timezone: '{{ timezone }}'
+  TimeZone: '{{ timezone }}'
diff --git a/services.auto.tfvars b/services.auto.tfvars
index 35bcfc6..8862d15 100644
--- a/services.auto.tfvars
+++ b/services.auto.tfvars
@@ -339,7 +339,7 @@ services = {
   },
 
   "postgresql" = {
-    host = "node002"
+    host = "node001"
     dns = [{
       domain = "db.serguzim.me"
     }]