Add PostgreSQL and Miniflux services to tailnet #16
14 changed files with 318 additions and 0 deletions
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>
commit
248e118102
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
34
ansible/roles/miniflux/defaults/main.yml
Normal file
34
ansible/roles/miniflux/defaults/main.yml
Normal 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
|
||||
5
ansible/roles/miniflux/handlers/main.yml
Normal file
5
ansible/roles/miniflux/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
- name: restart miniflux
|
||||
ansible.builtin.command: brew services restart miniflux
|
||||
async: 120
|
||||
poll: 0
|
||||
3
ansible/roles/miniflux/meta/main.yml
Normal file
3
ansible/roles/miniflux/meta/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
dependencies:
|
||||
- role: postgresql
|
||||
88
ansible/roles/miniflux/tasks/main.yml
Normal file
88
ansible/roles/miniflux/tasks/main.yml
Normal 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
|
||||
31
ansible/roles/miniflux/templates/miniflux.conf.j2
Normal file
31
ansible/roles/miniflux/templates/miniflux.conf.j2
Normal 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
|
||||
23
ansible/roles/postgresql/defaults/main.yml
Normal file
23
ansible/roles/postgresql/defaults/main.yml
Normal 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
|
||||
5
ansible/roles/postgresql/handlers/main.yml
Normal file
5
ansible/roles/postgresql/handlers/main.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
- name: restart postgresql
|
||||
ansible.builtin.command: brew services restart {{ postgresql_formula }}
|
||||
async: 120
|
||||
poll: 0
|
||||
89
ansible/roles/postgresql/tasks/main.yml
Normal file
89
ansible/roles/postgresql/tasks/main.yml
Normal 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
|
||||
13
ansible/roles/postgresql/templates/pg_hba.conf.j2
Normal file
13
ansible/roles/postgresql/templates/pg_hba.conf.j2
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@ dependencies:
|
|||
- role: forgejo
|
||||
- role: kiwix
|
||||
- role: devpi
|
||||
- role: miniflux
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue