P4: Miniflux migration + PostgreSQL consolidation (#33)

## Summary
- Deploy miniflux in k8s via ArgoCD
- Expose via Tailscale Ingress at feed.tail8d86e.ts.net
- Retire brew PostgreSQL (no longer needed)
- Rename k8s-pg to pg (canonical hostname)
- Remove ansible miniflux and postgresql roles
- Update borgmatic to backup pg.tail8d86e.ts.net
- Update all zk documentation

## Deployment and Testing
- [x] Miniflux pod running in k8s
- [x] User login works at https://feed.tail8d86e.ts.net
- [x] Feeds and entries visible
- [x] brew miniflux and postgresql stopped
- [x] Tailscale services migrated (feed, pg)
- [x] zk documentation updated
- [x] Run ansible to apply role removals
- [ ] Verify borgmatic backup with new pg hostname

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.tail8d86e.ts.net/eblume/blumeops/pulls/33
This commit is contained in:
Erich Blume 2026-01-20 09:04:47 -08:00
commit 735b643429
25 changed files with 336 additions and 518 deletions

28
argocd/apps/miniflux.yaml Normal file
View file

@ -0,0 +1,28 @@
# Miniflux RSS Reader
# Requires: CloudNativePG PostgreSQL cluster and manual secret setup
#
# Before syncing, create the database secret:
# kubectl create namespace miniflux
# op inject -i argocd/manifests/miniflux/secret-db.yaml.tpl | kubectl apply -f -
#
# Note: The Tailscale Ingress may initially get hostname "feed-1" if "feed" is
# already claimed. After clearing the old service, delete the device from
# Tailscale admin to allow the Ingress to claim "feed".
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: miniflux
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/miniflux
destination:
server: https://kubernetes.default.svc
namespace: miniflux
syncPolicy:
syncOptions:
- CreateNamespace=true
# Manual sync only - no automated sync on git push

View file

@ -1,13 +1,12 @@
# Tailscale LoadBalancer for PostgreSQL access
# Temporary service for testing during migration (k8s-pg.tail8d86e.ts.net)
# Will be replaced by pg.tail8d86e.ts.net in Phase 4
# Canonical hostname: pg.tail8d86e.ts.net
apiVersion: v1
kind: Service
metadata:
name: blumeops-pg-tailscale
namespace: databases
annotations:
tailscale.com/hostname: "k8s-pg"
tailscale.com/hostname: "pg"
tailscale.com/proxy-class: "crio-compat"
spec:
type: LoadBalancer

View file

@ -0,0 +1,62 @@
# Miniflux Kubernetes Deployment
RSS/Atom feed reader deployed via ArgoCD.
## Prerequisites
- CloudNativePG PostgreSQL cluster running in `databases` namespace
- Miniflux database and user created in PostgreSQL (from Phase 3 migration)
- Tailscale operator installed
## Setup
1. Create the namespace and database secret:
```bash
kubectl create namespace miniflux
# The miniflux user password is auto-generated by CNPG in blumeops-pg-app secret
kubectl create secret generic miniflux-db -n miniflux \
--from-literal=url="$(kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d)"
```
2. Apply the ArgoCD application:
```bash
kubectl apply -f argocd/apps/miniflux.yaml
argocd app sync miniflux
```
## Access
- URL: https://feed.tail8d86e.ts.net
- Exposed via Tailscale Ingress
## Configuration
Environment variables in `deployment.yaml`:
- `POLLING_FREQUENCY`: How often to check feeds (minutes)
- `BATCH_SIZE`: Number of feeds to refresh per interval
- `CLEANUP_ARCHIVE_UNREAD_DAYS`: Days to keep unread entries
- `CLEANUP_ARCHIVE_READ_DAYS`: Days to keep read entries
## Management
```bash
# View logs
kubectl -n miniflux logs -f deployment/miniflux
# Restart deployment
kubectl -n miniflux rollout restart deployment/miniflux
# Check health
curl https://feed.tail8d86e.ts.net/healthcheck
```
## Database Connection
Connects to PostgreSQL via internal k8s DNS:
`blumeops-pg-rw.databases.svc.cluster.local:5432`
The database is also accessible externally via Tailscale at:
`pg.tail8d86e.ts.net:5432`

View file

@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: miniflux
namespace: miniflux
spec:
replicas: 1
selector:
matchLabels:
app: miniflux
template:
metadata:
labels:
app: miniflux
spec:
containers:
- name: miniflux
image: ghcr.io/miniflux/miniflux:latest
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: miniflux-db
key: url
- name: RUN_MIGRATIONS
value: "1"
- name: BASE_URL
value: "https://feed.tail8d86e.ts.net/"
- name: POLLING_FREQUENCY
value: "60"
- name: BATCH_SIZE
value: "100"
- name: POLLING_SCHEDULER
value: "entry_frequency"
- name: CLEANUP_ARCHIVE_UNREAD_DAYS
value: "180"
- name: CLEANUP_ARCHIVE_READ_DAYS
value: "60"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /healthcheck
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthcheck
port: 8080
initialDelaySeconds: 5
periodSeconds: 10

View file

@ -0,0 +1,17 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: miniflux-tailscale
namespace: miniflux
annotations:
tailscale.com/proxy-class: "crio-compat"
spec:
ingressClassName: tailscale
defaultBackend:
service:
name: miniflux
port:
number: 8080
tls:
- hosts:
- feed

View file

@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: miniflux
resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml

View file

@ -0,0 +1,13 @@
# Miniflux database connection secret
#
# The miniflux user password is auto-generated by CloudNativePG and stored in
# blumeops-pg-app secret in the databases namespace. To create this secret:
#
# 1. Get the URI from CNPG secret:
# kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d
#
# 2. Create the secret (one-liner):
# kubectl create secret generic miniflux-db -n miniflux \
# --from-literal=url="$(kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d)"
#
# Note: Uses internal k8s DNS hostname (blumeops-pg-rw.databases) not Tailscale

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: miniflux
namespace: miniflux
spec:
selector:
app: miniflux
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP