Add PostgreSQL and Miniflux services to tailnet #16

Merged
eblume merged 15 commits from feature/add-miniflux-postgresql into main 2026-01-16 12:30:21 -08:00
14 changed files with 318 additions and 0 deletions
Showing only changes of commit 248e118102 - Show all commits

Add PostgreSQL and Miniflux services to tailnet

- Add postgresql ansible role (postgresql@18 via homebrew)
  - Creates miniflux database and user
  - Configures pg_hba.conf for local scram-sha-256 auth
  - Exposed via Tailscale at pg.tail8d86e.ts.net:5432

- Add miniflux ansible role (RSS/Atom feed reader)
  - Depends on postgresql role
  - Configures via /opt/homebrew/etc/miniflux.conf
  - Reads DB password from ~/.miniflux-db-password
  - Supports first-run admin creation via miniflux_create_admin flag
  - Exposed via Tailscale at feed.tail8d86e.ts.net

- Update Pulumi ACL tags (tag:pg, tag:feed)
- Update tailscale_serve role with new service definitions
- Update Alloy log collection for both services
- Update indri.yml playbook with new roles

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Erich Blume 2026-01-16 07:26:59 -08:00

View file

@ -26,5 +26,9 @@
tags: devpi_metrics
- role: plex_metrics
tags: plex_metrics
- role: postgresql
tags: postgresql
- role: miniflux
tags: miniflux
- role: tailscale_serve
tags: tailscale-serve

View file

@ -40,6 +40,12 @@ alloy_brew_logs:
- path: /opt/homebrew/var/transmission/transmission-daemon.log
service: transmission
stream: stdout
- path: /opt/homebrew/var/log/postgresql@18.log
service: postgresql
stream: stdout
- path: /opt/homebrew/var/log/miniflux.log
service: miniflux
stream: stdout
alloy_mcquack_logs:
- path: /Users/erichblume/Library/Logs/mcquack.devpi.out.log

View file

@ -0,0 +1,34 @@
---
# Miniflux configuration
# Network settings
miniflux_listen_addr: "127.0.0.1:8080"
miniflux_base_url: "https://feed.tail8d86e.ts.net/"
# Database connection (password read from file on host)
miniflux_db_host: localhost
miniflux_db_port: 5432
miniflux_db_name: miniflux
miniflux_db_user: miniflux
miniflux_db_password_file: ~/.miniflux-db-password
# Config paths
miniflux_config_file: /opt/homebrew/etc/miniflux.conf
# First run settings
# Set miniflux_create_admin to 1 for initial setup, then 0 after
miniflux_create_admin: 0
miniflux_admin_username: admin
miniflux_admin_password_file: ~/.miniflux-admin-password
# Always run migrations to keep schema updated
miniflux_run_migrations: 1
# Polling settings
miniflux_polling_frequency: 60
miniflux_batch_size: 100
miniflux_polling_scheduler: "entry_frequency"
# Cleanup settings (archive old entries)
miniflux_cleanup_archive_unread_days: 180
miniflux_cleanup_archive_read_days: 60

View file

@ -0,0 +1,5 @@
---
- name: restart miniflux
ansible.builtin.command: brew services restart miniflux
async: 120
poll: 0

View file

@ -0,0 +1,3 @@
---
dependencies:
- role: postgresql

View file

@ -0,0 +1,88 @@
---
# Miniflux installation and configuration
#
# ONE-TIME SETUP (before running ansible):
#
# 1. Create miniflux database password in 1Password (same as PostgreSQL user password)
# 2. Create the password file on indri:
#
# ssh indri 'echo "your-password" > ~/.miniflux-db-password && chmod 600 ~/.miniflux-db-password'
#
# FIRST RUN (admin creation):
#
# 1. Create admin password in 1Password
# 2. Create admin password file on indri:
#
# ssh indri 'echo "your-admin-password" > ~/.miniflux-admin-password && chmod 600 ~/.miniflux-admin-password'
#
# 3. Set miniflux_create_admin: 1 in playbook vars or via --extra-vars
# 4. Run ansible: mise run provision-indri -- --tags miniflux -e miniflux_create_admin=1
# 5. After successful first run, remove admin password file:
#
# ssh indri 'rm ~/.miniflux-admin-password'
#
- name: Install miniflux via homebrew
community.general.homebrew:
name: miniflux
state: present
- name: Check if database password file exists
ansible.builtin.stat:
path: "{{ miniflux_db_password_file }}"
register: db_password_file
- name: Fail if database password not configured
ansible.builtin.fail:
msg: |
Miniflux database password not found at {{ miniflux_db_password_file }}
Create it with:
echo "YOUR_PASSWORD" > {{ miniflux_db_password_file }} && chmod 600 {{ miniflux_db_password_file }}
Password should match what was set for the miniflux PostgreSQL user.
when: not db_password_file.stat.exists
- name: Read database password
ansible.builtin.slurp:
src: "{{ miniflux_db_password_file }}"
register: db_password_content
when: db_password_file.stat.exists
- name: Set database password fact
ansible.builtin.set_fact:
miniflux_db_password: "{{ db_password_content.content | b64decode | trim }}"
when: db_password_file.stat.exists
# Handle first run admin setup
- name: Check if admin password file exists (for first run)
ansible.builtin.stat:
path: "{{ miniflux_admin_password_file }}"
register: admin_password_file
when: miniflux_create_admin | int == 1
- name: Read admin password for first run
ansible.builtin.slurp:
src: "{{ miniflux_admin_password_file }}"
register: admin_password_content
when:
- miniflux_create_admin | int == 1
- admin_password_file.stat.exists | default(false)
- name: Set admin password fact
ansible.builtin.set_fact:
miniflux_admin_password: "{{ admin_password_content.content | b64decode | trim }}"
when:
- miniflux_create_admin | int == 1
- admin_password_file.stat.exists | default(false)
- name: Deploy miniflux configuration
ansible.builtin.template:
src: miniflux.conf.j2
dest: "{{ miniflux_config_file }}"
mode: '0600'
notify: restart miniflux
- name: Ensure miniflux service is started
ansible.builtin.command: brew services start miniflux
register: brew_start
changed_when: "'Successfully started' in brew_start.stdout"
failed_when: false

View file

@ -0,0 +1,31 @@
# {{ ansible_managed }}
# Miniflux configuration - KEY=VALUE format
# Server settings
LISTEN_ADDR={{ miniflux_listen_addr }}
BASE_URL={{ miniflux_base_url }}
# Database connection
DATABASE_URL=postgres://{{ miniflux_db_user }}:{{ miniflux_db_password }}@{{ miniflux_db_host }}:{{ miniflux_db_port }}/{{ miniflux_db_name }}?sslmode=disable
# Migrations (always run to keep schema updated)
RUN_MIGRATIONS={{ miniflux_run_migrations }}
{% if miniflux_create_admin | int == 1 and miniflux_admin_password is defined %}
# First run admin creation
CREATE_ADMIN=1
ADMIN_USERNAME={{ miniflux_admin_username }}
ADMIN_PASSWORD={{ miniflux_admin_password }}
{% endif %}
# Polling settings
POLLING_FREQUENCY={{ miniflux_polling_frequency }}
BATCH_SIZE={{ miniflux_batch_size }}
POLLING_SCHEDULER={{ miniflux_polling_scheduler }}
# Cleanup settings
CLEANUP_ARCHIVE_UNREAD_DAYS={{ miniflux_cleanup_archive_unread_days }}
CLEANUP_ARCHIVE_READ_DAYS={{ miniflux_cleanup_archive_read_days }}
# Logging
LOG_LEVEL=info

View file

@ -0,0 +1,23 @@
---
# PostgreSQL configuration
# Formula and version
postgresql_formula: postgresql@18
# Paths (keg-only formula on macOS)
postgresql_bin_dir: /opt/homebrew/opt/postgresql@18/bin
postgresql_data_dir: /opt/homebrew/var/postgresql@18
postgresql_config_dir: /opt/homebrew/var/postgresql@18
# Network settings
postgresql_port: 5432
postgresql_listen_addresses: "localhost"
# Databases to create
postgresql_databases:
- name: miniflux
owner: miniflux
# Users to create (passwords set manually via 1Password)
postgresql_users:
- name: miniflux

View file

@ -0,0 +1,5 @@
---
- name: restart postgresql
ansible.builtin.command: brew services restart {{ postgresql_formula }}
async: 120
poll: 0

View file

@ -0,0 +1,89 @@
---
# PostgreSQL installation and configuration
#
# ONE-TIME SETUP (after running ansible):
#
# 1. Create the miniflux database password in 1Password
# 2. SSH to indri and set the password:
#
# /opt/homebrew/opt/postgresql@18/bin/psql -c "ALTER USER miniflux PASSWORD 'your-password-here';"
#
# 3. For borgmatic backups, create ~/.pgpass:
# echo "localhost:5432:miniflux:miniflux:YOUR_PASSWORD" > ~/.pgpass
# chmod 600 ~/.pgpass
#
- name: Install {{ postgresql_formula }} via homebrew
community.general.homebrew:
name: "{{ postgresql_formula }}"
state: present
- name: Check if postgresql data directory is initialized
ansible.builtin.stat:
path: "{{ postgresql_data_dir }}/PG_VERSION"
register: pg_data
- name: Initialize postgresql database cluster
ansible.builtin.command: >
{{ postgresql_bin_dir }}/initdb
--locale=en_US.UTF-8 -E UTF-8
{{ postgresql_data_dir }}
when: not pg_data.stat.exists
- 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 database users (without passwords - set manually via 1Password)
- name: Check if postgresql users exist
ansible.builtin.command: >
{{ postgresql_bin_dir }}/psql -tAc
"SELECT 1 FROM pg_roles WHERE rolname = '{{ item.name }}';"
loop: "{{ postgresql_users }}"
register: user_check
changed_when: false
failed_when: false
- name: Create postgresql users
ansible.builtin.command: >
{{ postgresql_bin_dir }}/psql -c "CREATE USER {{ item.item.name }};"
loop: "{{ user_check.results }}"
when: item.stdout != "1"
changed_when: true
# Create databases
- name: Check if postgresql databases exist
ansible.builtin.command: >
{{ postgresql_bin_dir }}/psql -tAc
"SELECT 1 FROM pg_database WHERE datname = '{{ item.name }}';"
loop: "{{ postgresql_databases }}"
register: db_check
changed_when: false
failed_when: false
- name: Create postgresql databases
ansible.builtin.command: >
{{ postgresql_bin_dir }}/createdb
--owner={{ item.item.owner }}
{{ item.item.name }}
loop: "{{ db_check.results }}"
when: item.stdout != "1"
changed_when: true

View file

@ -0,0 +1,13 @@
# {{ ansible_managed }}
# PostgreSQL Client Authentication Configuration File
# TYPE DATABASE USER ADDRESS METHOD
# Local connections (Unix socket) - trust for initial setup
local all all trust
# IPv4 local connections (password auth for services)
host all all 127.0.0.1/32 scram-sha-256
# IPv6 local connections
host all all ::1/128 scram-sha-256

View file

@ -25,3 +25,13 @@ tailscale_services:
https:
port: 443
upstream: http://127.0.0.1:3141
- name: svc:pg
tcp:
port: 5432
upstream: tcp://localhost:5432
- name: svc:feed
https:
port: 443
upstream: http://localhost:8080

View file

@ -4,3 +4,4 @@ dependencies:
- role: forgejo
- role: kiwix
- role: devpi
- role: miniflux

View file

@ -89,6 +89,12 @@
// Loki log collection
"tag:loki": ["autogroup:admin"],
// PostgreSQL database server
"tag:pg": ["autogroup:admin"],
// Miniflux RSS/Atom feed reader
"tag:feed": ["autogroup:admin"],
// This tag is applied to resources modified by blumeops-pulumi IaC
"tag:blumeops": ["autogroup:admin"],
},