feat(roswell): despliegue GitOps del corpus — bot+scheduler, backup pg_dump diario, postgres adoptado

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 16:29:19 +00:00
co-authored by Claude Fable 5
parent ad3716bf80
commit 6416c6c3af
5 changed files with 334 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# Se aplica una vez imperativamente (kubectl apply -f); después ArgoCD
# se autogestiona porque este fichero también vive en k8s-manifests/roswell/.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: roswell
namespace: argocd
spec:
project: default
source:
repoURL: https://git.chemavx.xyz/chemavx/k8s-manifests
targetRevision: HEAD
path: roswell
destination:
server: https://kubernetes.default.svc
namespace: roswell
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+78
View File
@@ -0,0 +1,78 @@
# Backup diario de la BD del corpus con retención 7 (PLAN §Fase 6).
# pg_dump en formato custom (-Fc, comprimido) verificado con pg_restore --list.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
# ArgoCD no debe podar este PVC: contiene los backups de la BD.
argocd.argoproj.io/sync-options: Prune=false
name: roswell-backups
namespace: roswell
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: roswell-db-backup
namespace: roswell
labels:
app: roswell
spec:
schedule: "30 3 * * *"
timeZone: "Europe/Madrid"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 900
template:
metadata:
labels:
app: roswell-db-backup
spec:
restartPolicy: Never
containers:
- name: backup
# Misma imagen que el postgres del corpus: pg_dump 16 garantizado
# y ya está en la caché del nodo.
image: pgvector/pgvector:pg16
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: roswell-pg-secret
key: password
command:
- /bin/sh
- -c
- |
set -eu
STAMP=$(date +%Y%m%d)
DEST="/backups/roswell-${STAMP}.dump"
echo "pg_dump roswell -> ${DEST}"
pg_dump -h postgres.roswell.svc.cluster.local -U roswell \
-d roswell -Fc -f "${DEST}"
# Verificación: un dump corrupto no lista su TOC.
pg_restore --list "${DEST}" > /dev/null
echo "Dump verificado ($(du -h ${DEST} | cut -f1))"
# Retención: conservar los 7 más recientes
for f in $(ls -1t /backups/roswell-*.dump | tail -n +8); do
echo "Podando backup antiguo ${f}"
rm -f "${f}"
done
ls -lh /backups/
volumeMounts:
- name: backups
mountPath: /backups
volumes:
- name: backups
persistentVolumeClaim:
claimName: roswell-backups
+117
View File
@@ -0,0 +1,117 @@
# Bot + scheduler en un pod (PLAN §Fase 6). El Secret roswell-secrets se crea
# imperativamente (nunca en git):
# kubectl create secret generic roswell-secrets -n roswell \
# --from-literal=telegram-bot-token=... \
# --from-literal=telegram-allowed-users=... \
# --from-literal=anthropic-api-key=... \
# --from-literal=database-url=postgresql://roswell:<pass>@postgres.roswell.svc.cluster.local:5432/roswell \
# --from-literal=podcastindex-key=... \
# --from-literal=podcastindex-secret=...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
# ArgoCD no debe podar este PVC: guarda la caché del modelo Whisper (~3GB)
# y los PDFs subidos por Telegram.
argocd.argoproj.io/sync-options: Prune=false
name: roswell-data
namespace: roswell
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: roswell
namespace: roswell
labels:
app: roswell
spec:
replicas: 1
# Dos pods haciendo polling del mismo token de Telegram entran en conflicto:
# Recreate garantiza que el viejo muere antes de arrancar el nuevo.
strategy:
type: Recreate
selector:
matchLabels:
app: roswell
template:
metadata:
labels:
app: roswell
spec:
containers:
- name: roswell
# Tag placeholder: el workflow de Gitea Actions lo sustituye por el
# sha8 real en cada build (ver .gitea/workflows/build.yml del repo).
image: git.chemavx.xyz/chemavx/roswell-corpus:00000000
imagePullPolicy: Always
env:
- name: TELEGRAM_BOT_TOKEN
valueFrom:
secretKeyRef:
name: roswell-secrets
key: telegram-bot-token
- name: TELEGRAM_ALLOWED_USERS
valueFrom:
secretKeyRef:
name: roswell-secrets
key: telegram-allowed-users
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: roswell-secrets
key: anthropic-api-key
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: roswell-secrets
key: database-url
- name: PODCASTINDEX_KEY
valueFrom:
secretKeyRef:
name: roswell-secrets
key: podcastindex-key
- name: PODCASTINDEX_SECRET
valueFrom:
secretKeyRef:
name: roswell-secrets
key: podcastindex-secret
- name: OLLAMA_URL
value: "http://ollama.ollama.svc.cluster.local:11434"
- name: SEARXNG_URL
value: "http://searxng-svc.researchowl.svc.cluster.local:8080"
- name: WHISPER_MODEL
value: "large-v3"
- name: WHISPER_DEVICE
value: "cpu"
- name: DATA_DIR
value: "/data"
- name: FEED_CHECK_HOURS
value: "24"
# Caché de modelos Whisper en el PVC: la descarga (~3GB) sobrevive
# a los redeploys.
- name: HF_HOME
value: "/data/hf-cache"
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
# Whisper large-v3 en CPU ≈ 3-4 GB (PLAN §10). Sin límite de CPU:
# la transcripción aprovecha los cores libres del nodo.
memory: "4Gi"
volumes:
- name: data
persistentVolumeClaim:
claimName: roswell-data
imagePullSecrets:
- name: gitea-registry
+27
View File
@@ -0,0 +1,27 @@
apiVersion: secrets.infisical.com/v1beta1
kind: InfisicalStaticSecret
metadata:
name: gitea-registry
namespace: roswell
spec:
# Referencia la capa de auth COMPARTIDA en infisical-operator (cross-namespace).
infisicalAuthRef:
name: infisical-auth
namespace: infisical-operator
sources:
- projectId: 17e98e9d-70f5-43d1-8382-7da818dfcdd0 # project "homelab"
environmentSlug: prod
secretPath: /registry
syncOptions:
refreshInterval: 60s
targets:
- kind: Secret
name: gitea-registry
namespace: roswell
creationPolicy: Owner
secretType: kubernetes.io/dockerconfigjson
template:
engineVersion: v1
data:
# Reconstruye el pull-secret docker desde los dos campos de /registry.
.dockerconfigjson: '{"auths":{"git.chemavx.xyz":{"username":"{{ .GITEA_REGISTRY_USERNAME.Value }}","password":"{{ .GITEA_REGISTRY_TOKEN.Value }}","auth":"{{ printf "%s:%s" .GITEA_REGISTRY_USERNAME.Value .GITEA_REGISTRY_TOKEN.Value | encodeBase64 }}"}}}'
+90
View File
@@ -0,0 +1,90 @@
# Postgres 16 + pgvector para el corpus (namespace roswell).
# El Secret roswell-pg-secret se crea imperativamente (PLAN Fase 6):
# kubectl create secret generic roswell-pg-secret -n roswell \
# --from-literal=password=<pass>
# NodePort 30432 para acceso desde la LAN/nodos durante el desarrollo local.
apiVersion: v1
kind: Namespace
metadata:
name: roswell
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
# ArgoCD no debe podar nunca este PVC: contiene la BD del corpus.
argocd.argoproj.io/sync-options: Prune=false
name: roswell-pgdata
namespace: roswell
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: roswell
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: roswell-postgres
template:
metadata:
labels:
app: roswell-postgres
spec:
containers:
- name: postgres
image: pgvector/pgvector:pg16
env:
- name: POSTGRES_DB
value: roswell
- name: POSTGRES_USER
value: roswell
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: roswell-pg-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: 256Mi
cpu: 100m
limits:
memory: 1Gi
readinessProbe:
exec:
command: ["pg_isready", "-U", "roswell", "-d", "roswell"]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: roswell-pgdata
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: roswell
spec:
type: NodePort
selector:
app: roswell-postgres
ports:
- port: 5432
targetPort: 5432
nodePort: 30432