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
Owner

Summary

  • Add PostgreSQL 18 as a new service at pg.tail8d86e.ts.net:5432
  • Add Miniflux RSS/Atom feed reader at feed.tail8d86e.ts.net
  • Both services managed via homebrew/brew services
  • Pulumi ACL tags added (tag:pg, tag:feed)
  • Alloy log collection configured for both services
  • Zettelkasten documentation updated

Manual Setup Required

Before running ansible, the following steps are needed on indri:

1. Apply Pulumi tags

mise run tailnet-up

Then apply tags to indri in Tailscale admin console.

2. Create 1Password entries

  • miniflux PostgreSQL user password
  • miniflux admin password (for first run)

3. Set PostgreSQL user password (after ansible installs postgres)

ssh indri '/opt/homebrew/opt/postgresql@18/bin/psql -c "ALTER USER miniflux PASSWORD '\''your-password'\'';"'

4. Create password files on indri

ssh indri 'echo "your-db-password" > ~/.miniflux-db-password && chmod 600 ~/.miniflux-db-password'
ssh indri 'echo "your-admin-password" > ~/.miniflux-admin-password && chmod 600 ~/.miniflux-admin-password'

5. Create ~/.pgpass for borgmatic

ssh indri 'echo "localhost:5432:miniflux:miniflux:YOUR_PASSWORD" > ~/.pgpass && chmod 600 ~/.pgpass'

6. Run ansible with first-run admin creation

mise run provision-indri -- -e miniflux_create_admin=1

7. Update borgmatic config

Add to ~/.config/borgmatic/config.yaml on indri:

postgresql_databases:
    - name: miniflux
      hostname: localhost
      port: 5432
      username: miniflux

8. Cleanup after first run

ssh indri 'rm ~/.miniflux-admin-password'

Test plan

  • Run mise run tailnet-up and verify Pulumi changes
  • Apply tags to indri in Tailscale admin
  • Run mise run provision-indri -- --check --diff for dry run
  • Run mise run provision-indri -- -e miniflux_create_admin=1
  • Approve services in Tailscale admin
  • Verify PostgreSQL: ssh indri '/opt/homebrew/opt/postgresql@18/bin/pg_isready'
  • Verify Miniflux: curl https://feed.tail8d86e.ts.net/healthcheck
  • Run mise run indri-services-check

🤖 Generated with Claude Code

## Summary - Add PostgreSQL 18 as a new service at `pg.tail8d86e.ts.net:5432` - Add Miniflux RSS/Atom feed reader at `feed.tail8d86e.ts.net` - Both services managed via homebrew/brew services - Pulumi ACL tags added (tag:pg, tag:feed) - Alloy log collection configured for both services - Zettelkasten documentation updated ## Manual Setup Required Before running ansible, the following steps are needed on indri: ### 1. Apply Pulumi tags ```bash mise run tailnet-up ``` Then apply tags to indri in Tailscale admin console. ### 2. Create 1Password entries - miniflux PostgreSQL user password - miniflux admin password (for first run) ### 3. Set PostgreSQL user password (after ansible installs postgres) ```bash ssh indri '/opt/homebrew/opt/postgresql@18/bin/psql -c "ALTER USER miniflux PASSWORD '\''your-password'\'';"' ``` ### 4. Create password files on indri ```bash ssh indri 'echo "your-db-password" > ~/.miniflux-db-password && chmod 600 ~/.miniflux-db-password' ssh indri 'echo "your-admin-password" > ~/.miniflux-admin-password && chmod 600 ~/.miniflux-admin-password' ``` ### 5. Create ~/.pgpass for borgmatic ```bash ssh indri 'echo "localhost:5432:miniflux:miniflux:YOUR_PASSWORD" > ~/.pgpass && chmod 600 ~/.pgpass' ``` ### 6. Run ansible with first-run admin creation ```bash mise run provision-indri -- -e miniflux_create_admin=1 ``` ### 7. Update borgmatic config Add to `~/.config/borgmatic/config.yaml` on indri: ```yaml postgresql_databases: - name: miniflux hostname: localhost port: 5432 username: miniflux ``` ### 8. Cleanup after first run ```bash ssh indri 'rm ~/.miniflux-admin-password' ``` ## Test plan - [x] Run `mise run tailnet-up` and verify Pulumi changes - [x] Apply tags to indri in Tailscale admin - [x] Run `mise run provision-indri -- --check --diff` for dry run - [x] Run `mise run provision-indri -- -e miniflux_create_admin=1` - [x] Approve services in Tailscale admin - [x] Verify PostgreSQL: `ssh indri '/opt/homebrew/opt/postgresql@18/bin/pg_isready'` - [x] Verify Miniflux: `curl https://feed.tail8d86e.ts.net/healthcheck` - [x] Run `mise run indri-services-check` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- 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>
- All passwords fetched from 1Password at runtime using `op` CLI
- pg_hba.conf uses scram-sha-256 everywhere (no trust mode)
- initdb uses --pwfile for secure superuser password bootstrap
- All password-handling tasks use no_log: true
- Add borgmatic user with pg_read_all_data for backup dumps
- Remove pg-setup mise task (no longer needed)
- Miniflux fetches password directly from 1Password

Requires: `op signin` before running ansible

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 'alloy' PostgreSQL user with pg_monitor role (read-only)
- Configure Alloy prometheus.exporter.postgres for metrics collection
- Password fetched from 1Password with no_log protection
- Add PostgreSQL Grafana dashboard with:
  - Connection stats (active, total, by state)
  - Database sizes
  - Tuple operations rate
  - Transaction rate (commits/rollbacks)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of manually applying tags to indri in Tailscale admin,
use tailscale.DeviceTags resource to manage them declaratively.
This includes all service tags (grafana, forge, kiwix, devpi, loki,
pg, feed) plus homelab and blumeops tags.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove superuser from .pgpass since it's not needed for automated
operations. Only borgmatic (with pg_read_all_data role) needs
passwordless access for pg_dump backups.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The OAuth client acts as tag:blumeops, so it needs to own all tags
it manages on devices. This enables Pulumi to set device tags
automatically instead of requiring manual Tailscale admin console
changes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Without specifying a database, psql defaults to connecting to a
database named after the current user, which doesn't exist on a
fresh install.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move borgmatic config.yaml from manual to ansible-managed template
- Add postgresql_databases backup for miniflux database
- Consolidate 1Password credential fetching to playbook pre_tasks
  to reduce auth prompts during full playbook runs
- Roles now check if credentials are already defined before fetching,
  so they still work when running with --tags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add tags to pre_tasks so they only run when relevant roles are included
- Make tailscale_serve idempotent by checking serve status JSON before
  configuring services (skips if already configured)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The password contained special characters (@, !, *) that broke the
connection string URL parsing. Added urlencode filter to the template.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add custom query for pg_database XID age monitoring
- Add gauge showing XID age with threshold warnings (yellow at 150M, red at 180M)
- Add time series chart for XID age trends
- URL-encode postgres password in alloy connection string

XID (transaction ID) exhaustion can cause PostgreSQL to shut down to prevent
wraparound. Default autovacuum_freeze_max_age is 200M, so warnings start well
before that threshold.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
eblume merged commit adf6f4fbe9 into main 2026-01-16 12:30:21 -08:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
eblume/blumeops!16
No description provided.