Decommission wave-1 minikube services (paperless, teslamate, mealie) (#365)

Final step of the wave-1 indri-k8s migration. paperless, teslamate, mealie run on ringtail with data migrated, verified, and backed up (local + BorgBase offsite via PR #364).

- Remove minikube paperless/teslamate/mealie manifest dirs + ArgoCD app defs (prunes the parked Deployments/Services + redundant minikube mealie/paperless PVCs)
- Drop paperless/teslamate roles + ExternalSecrets from the minikube blumeops-pg cluster
- miniflux + authentik stay on minikube (later waves)

Finalization after merge: sync apps + databases to prune, then DROP DATABASE paperless/teslamate on indri's blumeops-pg (fresh safety dump taken first).

Reviewed-on: #365
This commit is contained in:
Erich Blume 2026-06-03 12:36:06 -07:00
commit 46f0002178
25 changed files with 11 additions and 755 deletions

View file

@ -1,17 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mealie
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/mealie
destination:
server: https://kubernetes.default.svc
namespace: mealie
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -1,17 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: paperless
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/paperless
destination:
server: https://kubernetes.default.svc
namespace: paperless
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -1,32 +0,0 @@
# TeslaMate Tesla Data Logger
# Requires: CloudNativePG PostgreSQL cluster and manual secret setup
#
# Before syncing, create the namespace and secrets:
# kubectl create namespace teslamate
# op inject -i argocd/manifests/databases/secret-teslamate.yaml.tpl | kubectl apply -f -
# op inject -i argocd/manifests/teslamate/secret-encryption-key.yaml.tpl | kubectl apply -f -
# op inject -i argocd/manifests/teslamate/secret-db.yaml.tpl | kubectl apply -f -
#
# Then create the database:
# PGPASSWORD=$(op read "op://blumeops/postgres/password") \
# psql -h pg.ops.eblu.me -U eblume -c "CREATE DATABASE teslamate OWNER teslamate;"
#
# After syncing, access the TeslaMate UI at https://tesla.tail8d86e.ts.net to complete
# Tesla API authentication via OAuth flow.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: teslamate
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/teslamate
destination:
server: https://kubernetes.default.svc
namespace: teslamate
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -44,18 +44,9 @@ spec:
- pg_read_all_data
passwordSecret:
name: blumeops-pg-borgmatic
# teslamate user for TeslaMate Tesla data logger
# Superuser removed. Extension ownership (cube, earthdistance)
# transferred manually so teslamate can ALTER EXTENSION UPDATE.
# earthdistance is untrusted — DROP+CREATE needs temporary
# superuser escalation during upgrades.
- name: teslamate
login: true
connectionLimit: -1
ensure: present
inherit: true
passwordSecret:
name: blumeops-pg-teslamate
# teslamate + paperless roles removed: migrated to ringtail blumeops-pg
# (wave-1 decommission). Their databases were dropped from this cluster
# after the cutover was verified and backed up.
# authentik user for Authentik identity provider (runs on ringtail)
- name: authentik
login: true
@ -65,14 +56,6 @@ spec:
createdb: true
passwordSecret:
name: blumeops-pg-authentik
# paperless user for Paperless-ngx document management
- name: paperless
login: true
connectionLimit: -1
ensure: present
inherit: true
passwordSecret:
name: blumeops-pg-paperless
# Resource limits for minikube environment
resources:

View file

@ -1,28 +0,0 @@
# ExternalSecret for Paperless database user password
#
# 1Password item: "Paperless (blumeops)" in blumeops vault
# Field: "postgresql-password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: blumeops-pg-paperless
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: blumeops-pg-paperless
creationPolicy: Owner
template:
type: kubernetes.io/basic-auth
data:
username: paperless
password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: Paperless (blumeops)
property: postgresql-password

View file

@ -1,30 +0,0 @@
# ExternalSecret for TeslaMate database user password
#
# Replaces the manual op inject workflow from secret-teslamate.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "db_password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: blumeops-pg-teslamate
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: blumeops-pg-teslamate
creationPolicy: Owner
template:
type: kubernetes.io/basic-auth
data:
username: teslamate
password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: TeslaMate
property: db_password

View file

@ -9,6 +9,4 @@ resources:
- service-metrics-tailscale.yaml
- external-secret-eblume.yaml
- external-secret-borgmatic.yaml
- external-secret-teslamate.yaml
- external-secret-authentik.yaml
- external-secret-paperless.yaml

View file

@ -1,96 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mealie
namespace: mealie
spec:
# Migrated to ringtail (mealie-ringtail). Scaled to 0; SQLite PVC retained
# for rollback until the decommission PR. See [[migrate-wave1-ringtail]].
replicas: 0
selector:
matchLabels:
app: mealie
template:
metadata:
labels:
app: mealie
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: mealie
image: registry.ops.eblu.me/blumeops/mealie:kustomized
ports:
- containerPort: 9000
env:
- name: BASE_URL
value: "https://meals.ops.eblu.me"
- name: ALLOW_SIGNUP
value: "false"
- name: TZ
value: "America/Los_Angeles"
- name: MAX_WORKERS
value: "1"
- name: WEB_CONCURRENCY
value: "1"
# OIDC — Authentik (public client, PKCE)
- name: OIDC_AUTH_ENABLED
value: "true"
- name: OIDC_CONFIGURATION_URL
value: "https://authentik.ops.eblu.me/application/o/mealie/.well-known/openid-configuration"
- name: OIDC_CLIENT_ID
value: "mealie"
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: mealie-secrets
key: oidc-client-secret
- name: OIDC_AUTO_REDIRECT
value: "false"
- name: OIDC_PROVIDER_NAME
value: "Authentik"
- name: OIDC_ADMIN_GROUP
value: "admins"
- name: OIDC_SIGNUP_ENABLED
value: "true"
- name: OIDC_USER_CLAIM
value: "email"
# OpenAI — recipe parsing, image OCR, ingredient extraction
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: mealie-secrets
key: openai-api-key
- name: OPENAI_MODEL
value: "gpt-4o"
- name: OPENAI_REQUEST_TIMEOUT
value: "120"
- name: OPENAI_WORKERS
value: "1"
volumeMounts:
- name: data
mountPath: /app/data
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "1000Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/app/about
port: 9000
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/app/about
port: 9000
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: mealie-data

View file

@ -1,23 +0,0 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mealie-secrets
namespace: mealie
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: mealie-secrets
creationPolicy: Owner
data:
- secretKey: oidc-client-secret
remoteRef:
key: "Authentik (blumeops)"
property: mealie-client-secret
- secretKey: openai-api-key
remoteRef:
key: "openai (blumeops)"
property: credential

View file

@ -1,15 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mealie
resources:
- deployment.yaml
- service.yaml
- pvc.yaml
# ingress removed: name 'meals' handed off to mealie-ringtail at cutover
- external-secret.yaml
images:
- name: registry.ops.eblu.me/blumeops/mealie
newTag: v3.12.0-613f05d

View file

@ -1,13 +0,0 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mealie-data
namespace: mealie
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 2Gi

View file

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

View file

@ -1,133 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: paperless
namespace: paperless
spec:
# Migrated to ringtail (paperless-ringtail). Scaled to 0 to prevent
# double-writing the now-ringtail-owned database; manifest retained for
# rollback until the decommission PR. See [[migrate-wave1-ringtail]].
replicas: 0
selector:
matchLabels:
app: paperless
template:
metadata:
labels:
app: paperless
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: paperless
image: registry.ops.eblu.me/blumeops/paperless:kustomized
ports:
- containerPort: 8000
name: http
env:
- name: PAPERLESS_URL
value: "https://paperless.ops.eblu.me"
- name: PAPERLESS_REDIS
value: "redis://localhost:6379"
- name: PAPERLESS_DBHOST
value: "pg.ops.eblu.me"
- name: PAPERLESS_DBPORT
value: "5432"
- name: PAPERLESS_DBNAME
value: "paperless"
# Explicit port to override k8s-injected PAPERLESS_PORT env var
# (k8s sets PAPERLESS_PORT=tcp://... for a service named 'paperless')
- name: PAPERLESS_PORT
value: "8000"
- name: PAPERLESS_DBUSER
value: "paperless"
- name: PAPERLESS_DBPASS
valueFrom:
secretKeyRef:
name: paperless-secrets
key: db-password
- name: PAPERLESS_SECRET_KEY
valueFrom:
secretKeyRef:
name: paperless-secrets
key: secret-key
- name: PAPERLESS_TIME_ZONE
value: "America/Los_Angeles"
- name: PAPERLESS_OCR_LANGUAGE
value: "eng"
- name: PAPERLESS_TASK_WORKERS
value: "1"
# Admin account (created on first startup)
- name: PAPERLESS_ADMIN_USER
value: "eblume"
- name: PAPERLESS_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: paperless-secrets
key: admin-password
- name: PAPERLESS_ADMIN_MAIL
value: "blume.erich@gmail.com"
# OIDC via Authentik
# Full JSON blob pulled from 1Password (includes client secret)
- name: PAPERLESS_APPS
value: "allauth.socialaccount.providers.openid_connect"
- name: PAPERLESS_SOCIALACCOUNT_PROVIDERS
valueFrom:
secretKeyRef:
name: paperless-secrets
key: socialaccount-providers
- name: PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS
value: "true"
- name: PAPERLESS_SOCIAL_AUTO_SIGNUP
value: "true"
- name: PAPERLESS_ACCOUNT_ALLOW_SIGNUPS
value: "false"
- name: PAPERLESS_REDIRECT_LOGIN_TO_SSO
value: "false"
volumeMounts:
- name: data
mountPath: /usr/src/paperless/data
- name: media
mountPath: /usr/src/paperless/media
- name: consume
mountPath: /usr/src/paperless/consume
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
- name: redis
image: docker.io/library/redis:kustomized
ports:
- containerPort: 6379
resources:
requests:
memory: "32Mi"
cpu: "10m"
limits:
memory: "128Mi"
volumes:
- name: data
emptyDir: {}
- name: media
persistentVolumeClaim:
claimName: paperless-media
- name: consume
emptyDir: {}

View file

@ -1,31 +0,0 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: paperless-secrets
namespace: paperless
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: paperless-secrets
creationPolicy: Owner
data:
- secretKey: db-password
remoteRef:
key: "Paperless (blumeops)"
property: postgresql-password
- secretKey: secret-key
remoteRef:
key: "Paperless (blumeops)"
property: secret-key
- secretKey: admin-password
remoteRef:
key: "Paperless (blumeops)"
property: admin-password
- secretKey: socialaccount-providers
remoteRef:
key: "Paperless (blumeops)"
property: socialaccount-providers

View file

@ -1,19 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: paperless
resources:
- deployment.yaml
- service.yaml
- pv-nfs.yaml
- pvc.yaml
# ingress removed: name 'paperless' handed off to paperless-ringtail at cutover
- external-secret.yaml
images:
- name: registry.ops.eblu.me/blumeops/paperless
newTag: v2.20.13-07f52e9
- name: docker.io/library/redis
newName: registry.ops.eblu.me/blumeops/valkey
newTag: v8.1.7-ecded30

View file

@ -1,22 +0,0 @@
# NFS PersistentVolume for Paperless document library
# Requires: NFS share on sifaka at /volume1/paperless with NFS permissions for indri
#
# To create on Synology:
# 1. Control Panel > Shared Folder > Create
# 2. Name: paperless, Location: Volume 1
# 3. Control Panel > File Services > NFS > NFS Rules
# 4. Add rule for "paperless" share: Hostname=indri, Privilege=Read/Write, Squash=No mapping
apiVersion: v1
kind: PersistentVolume
metadata:
name: paperless-media-nfs-pv
spec:
capacity:
storage: 500Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
nfs:
server: sifaka
path: /volume1/paperless

View file

@ -1,15 +0,0 @@
# PersistentVolumeClaim for Paperless document library
# Binds to the NFS PV for sifaka:/volume1/paperless
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: paperless-media
namespace: paperless
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
volumeName: paperless-media-nfs-pv
resources:
requests:
storage: 500Gi

View file

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

View file

@ -1,69 +0,0 @@
# TeslaMate
TeslaMate is a self-hosted Tesla data logger that collects and visualizes vehicle data.
## Prerequisites
### 1. Create 1Password Secrets
Create two items in the blumeops 1Password vault:
1. **TeslaMate DB Password**
- Generate a secure password for the teslamate PostgreSQL user
- Add a field named `password` with the generated value
2. **TeslaMate Encryption Key**
- Generate with: `openssl rand -base64 32`
- Add a field named `key` with the generated value
- This encrypts Tesla API tokens at rest in the database
### 2. Apply Kubernetes Secrets
```bash
# Create namespace
kubectl create namespace teslamate
# Apply database user secret (for CNPG)
op inject -i argocd/manifests/databases/secret-teslamate.yaml.tpl | kubectl apply -f -
# Apply teslamate secrets
op inject -i argocd/manifests/teslamate/secret-encryption-key.yaml.tpl | kubectl apply -f -
op inject -i argocd/manifests/teslamate/secret-db.yaml.tpl | kubectl apply -f -
```
### 3. Create Database
After the teslamate user exists in PostgreSQL (sync blumeops-pg first):
```bash
PGPASSWORD=$(op read "op://blumeops/postgres/password") \
psql -h pg.ops.eblu.me -U eblume -c "CREATE DATABASE teslamate OWNER teslamate;"
```
## Deployment
```bash
# Sync ArgoCD apps
argocd app sync apps
argocd app sync blumeops-pg teslamate grafana grafana-config
```
## Tesla API Setup
1. Access TeslaMate UI at https://tesla.tail8d86e.ts.net
2. Click "Sign in with Tesla"
3. Complete OAuth flow in browser
4. Tokens are encrypted and stored in database
5. Verify vehicle appears and data collection starts
## Grafana Dashboards
TeslaMate dashboards are available in Grafana at https://grafana.tail8d86e.ts.net
They use the "TeslaMate" PostgreSQL datasource (not Prometheus).
## Notes
- MQTT is disabled (can be enabled later for Home Assistant integration)
- Timezone is set to America/Los_Angeles
- Encryption key protects Tesla API tokens at rest

View file

@ -1,68 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: teslamate
namespace: teslamate
spec:
# Migrated to ringtail (teslamate-ringtail). Scaled to 0 to prevent
# double-writing the now-ringtail-owned database; manifest retained for
# rollback until the decommission PR. See [[migrate-wave1-ringtail]].
replicas: 0
selector:
matchLabels:
app: teslamate
template:
metadata:
labels:
app: teslamate
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: teslamate
image: registry.ops.eblu.me/blumeops/teslamate:kustomized
ports:
- containerPort: 4000
env:
- name: DATABASE_USER
value: "teslamate"
- name: DATABASE_PASS
valueFrom:
secretKeyRef:
name: teslamate-db
key: password
- name: DATABASE_NAME
value: "teslamate"
- name: DATABASE_HOST
value: "blumeops-pg-rw.databases.svc.cluster.local"
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: teslamate-encryption
key: key
- name: DISABLE_MQTT
value: "true"
- name: CHECK_ORIGIN
value: "false"
- name: TZ
value: "America/Los_Angeles"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 4000
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /
port: 4000
initialDelaySeconds: 10
periodSeconds: 10

View file

@ -1,25 +0,0 @@
# ExternalSecret for TeslaMate database password
#
# Replaces the manual op inject workflow from secret-db.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "db_password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: teslamate-db
namespace: teslamate
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: teslamate-db
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: TeslaMate
property: db_password

View file

@ -1,27 +0,0 @@
# ExternalSecret for TeslaMate encryption key
#
# Replaces the manual op inject workflow from secret-encryption-key.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "api_enc_key"
#
# This key encrypts Tesla API tokens at rest in the database.
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: teslamate-encryption
namespace: teslamate
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: teslamate-encryption
creationPolicy: Owner
data:
- secretKey: key
remoteRef:
key: TeslaMate
property: api_enc_key

View file

@ -1,15 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: teslamate
resources:
- deployment.yaml
- service.yaml
# ingress removed: name 'tesla' handed off to teslamate-ringtail at cutover
- external-secret-db.yaml
- external-secret-encryption-key.yaml
images:
- name: registry.ops.eblu.me/blumeops/teslamate
newTag: v3.0.0-08c698e

View file

@ -1,12 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: teslamate
namespace: teslamate
spec:
selector:
app: teslamate
ports:
- port: 4000
targetPort: 4000
type: ClusterIP

View file

@ -0,0 +1,8 @@
Decommission the wave-1 services on minikube-indri now that paperless,
teslamate, and mealie run on ringtail with their data backed up. Removes the
minikube `paperless`/`teslamate`/`mealie` manifest dirs + ArgoCD app
definitions (pruning the parked Deployments, Services, and the redundant
minikube mealie/paperless PVCs), and drops the `paperless`/`teslamate` roles
from the minikube `blumeops-pg` cluster. The `paperless` and `teslamate`
databases are dropped from indri's blumeops-pg as the finalization step.
miniflux + authentik remain on the minikube cluster (later waves).