feat(backup-system): segundo offsite Backblaze B2 (crown jewels)
- b2-crown-jewels.sh: sync diario 04:00 de las 2 copias mas recientes de cada servicio irremplazable (todo menos uptime-kuma) a b2:chemavx-k3s-backups/crown-jewels/ (~2GB, tier gratuito). - reddit-intel.sh sube tambien a B2 (retencion 3d). - verify.sh comprueba Mega (completo) + B2 (crown jewels). - Config via Secret out-of-band rclone-conf-b2 (mega+b2, kubectl, NO git); consolidar en Infisical RCLONE_CONF cuando se anada [b2] por UI (la identity del operator es read-only por API). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,9 @@
|
|||||||
- **Mega** (`mega:k3s-backups/`): espejo diario de `/data/backups/backups` del master
|
- **Mega** (`mega:k3s-backups/`): espejo diario de `/data/backups/backups` del master
|
||||||
(retención 7 por servicio; 3 para `uptime-kuma` y `home-master`), más el subdir
|
(retención 7 por servicio; 3 para `uptime-kuma` y `home-master`), más el subdir
|
||||||
`reddit-intel/` que sube el CronJob de n97.
|
`reddit-intel/` que sube el CronJob de n97.
|
||||||
- **Backblaze B2** (`b2:.../`): copia reducida "crown jewels" (últimas 2 de cada
|
- **Backblaze B2** (`b2:chemavx-k3s-backups/crown-jewels/` + `reddit-intel/`):
|
||||||
servicio irremplazable). Segundo proveedor por si Mega falla.
|
copia reducida "crown jewels" (últimas 2 de cada servicio irremplazable, todo
|
||||||
|
menos uptime-kuma). Segundo proveedor por si Mega falla.
|
||||||
- Acceso: credenciales rclone en Infisical (`/backup-system`, key `RCLONE_CONF`).
|
- Acceso: credenciales rclone en Infisical (`/backup-system`, key `RCLONE_CONF`).
|
||||||
**Si el cluster está muerto, Infisical también** → usa el login de Mega/B2 de la
|
**Si el cluster está muerto, Infisical también** → usa el login de Mega/B2 de la
|
||||||
cuenta personal (Vaultwarden... que también está en el backup: guarda estas dos
|
cuenta personal (Vaultwarden... que también está en el backup: guarda estas dos
|
||||||
|
|||||||
@@ -247,6 +247,33 @@ data:
|
|||||||
log "Sync completado."
|
log "Sync completado."
|
||||||
log "Archivos en Mega:"
|
log "Archivos en Mega:"
|
||||||
rclone ls mega:k3s-backups/ --config /rclone/rclone.conf
|
rclone ls mega:k3s-backups/ --config /rclone/rclone.conf
|
||||||
|
b2-crown-jewels.sh: |
|
||||||
|
#!/bin/sh
|
||||||
|
# Segunda copia offsite (Backblaze B2): "crown jewels" — las 2 copias más
|
||||||
|
# recientes de cada servicio irremplazable (~2GB, cabe en los 10GB gratis).
|
||||||
|
# Todo menos uptime-kuma (622M de historial de heartbeats, prescindible).
|
||||||
|
# Imagen BusyBox — sin GNU-ismos. Config: Secret out-of-band rclone-conf-b2
|
||||||
|
# (mega+b2; consolidar en Infisical RCLONE_CONF cuando se añada [b2] en la UI).
|
||||||
|
set -e
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||||||
|
BACKUP_DIR=/data/backups/backups
|
||||||
|
CROWN="n8n openclaw homarr authentik-pg grafana k3s-db gitea ghost-en-db ghost-en-content zona-exclusion-db zona-exclusion-content researchowl roswell-pg umami-pg infisical-pg trilium vaultwarden filebrowser-db home-master"
|
||||||
|
|
||||||
|
KEEP=/tmp/crown-jewels
|
||||||
|
rm -rf "$KEEP"; mkdir -p "$KEEP"
|
||||||
|
for p in $CROWN; do
|
||||||
|
for f in $(ls -1 "$BACKUP_DIR/${p}"_* 2>/dev/null | sort | tail -2); do
|
||||||
|
ln -s "$f" "$KEEP/$(basename "$f")"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
N=$(ls -1 "$KEEP" | wc -l)
|
||||||
|
log "Sync de $N artefactos crown-jewels a B2..."
|
||||||
|
# -L: resuelve los symlinks; sync = espejo → la retención remota (2/servicio) es automática
|
||||||
|
rclone sync "$KEEP" b2:chemavx-k3s-backups/crown-jewels/ -L \
|
||||||
|
--config /rclone/rclone.conf --log-level INFO --stats 60s
|
||||||
|
rm -rf "$KEEP"
|
||||||
|
log "B2 crown-jewels completado ($N artefactos)."
|
||||||
reddit-intel.sh: |
|
reddit-intel.sh: |
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Corre en chemavx-n97 (imagen rclone, BusyBox — sin GNU-ismos).
|
# Corre en chemavx-n97 (imagen rclone, BusyBox — sin GNU-ismos).
|
||||||
@@ -265,6 +292,9 @@ data:
|
|||||||
rclone copy "$F" mega:k3s-backups/reddit-intel/ --config /rclone/rclone.conf --log-level INFO
|
rclone copy "$F" mega:k3s-backups/reddit-intel/ --config /rclone/rclone.conf --log-level INFO
|
||||||
log "Podando copias remotas de más de 8 días..."
|
log "Podando copias remotas de más de 8 días..."
|
||||||
rclone delete mega:k3s-backups/reddit-intel/ --min-age 8d --config /rclone/rclone.conf
|
rclone delete mega:k3s-backups/reddit-intel/ --min-age 8d --config /rclone/rclone.conf
|
||||||
|
log "Subiendo a B2 (crown jewels, retención 3 días)..."
|
||||||
|
rclone copy "$F" b2:chemavx-k3s-backups/reddit-intel/ --config /rclone/rclone.conf --log-level INFO
|
||||||
|
rclone delete b2:chemavx-k3s-backups/reddit-intel/ --min-age 3d --config /rclone/rclone.conf
|
||||||
rm -f "$F"
|
rm -f "$F"
|
||||||
log "reddit-intel OK"
|
log "reddit-intel OK"
|
||||||
verify.sh: |
|
verify.sh: |
|
||||||
@@ -325,12 +355,32 @@ data:
|
|||||||
TOTAL=$(echo "$FILES" | grep -c "tar.gz\|sql.gz\|db.gz" || true)
|
TOTAL=$(echo "$FILES" | grep -c "tar.gz\|sql.gz\|db.gz" || true)
|
||||||
log "Total archivos en Mega: $TOTAL"
|
log "Total archivos en Mega: $TOTAL"
|
||||||
|
|
||||||
|
# === Segundo offsite: B2 crown-jewels (todo menos uptime-kuma) ===
|
||||||
|
log "=== Verificación de crown-jewels en B2 ==="
|
||||||
|
CROWN="n8n openclaw homarr authentik-pg grafana k3s-db gitea ghost-en-db ghost-en-content zona-exclusion-db zona-exclusion-content researchowl roswell-pg umami-pg infisical-pg trilium vaultwarden filebrowser-db home-master"
|
||||||
|
B2FILES=$(rclone ls b2:chemavx-k3s-backups/ --config /rclone/rclone.conf 2>&1)
|
||||||
|
for svc in $CROWN; do
|
||||||
|
FOUND=0
|
||||||
|
for d in $ACCEPTED_DATES; do
|
||||||
|
if echo "$B2FILES" | grep -q "${svc}_${d}"; then
|
||||||
|
FOUND=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$FOUND" -eq 1 ]; then
|
||||||
|
log "[OK] b2/$svc"
|
||||||
|
else
|
||||||
|
log "[WARN] b2/$svc — sin backup en los últimos 3 días"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
if [ "$ERRORS" -gt 0 ]; then
|
if [ "$ERRORS" -gt 0 ]; then
|
||||||
log "RESULTADO: $ERRORS servicio(s) sin backup reciente. Revisar."
|
log "RESULTADO: $ERRORS servicio(s) sin backup reciente. Revisar."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "RESULTADO: Todos los backups verificados correctamente."
|
log "RESULTADO: Todos los backups verificados correctamente (Mega + B2)."
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: backup-scripts
|
name: backup-scripts
|
||||||
|
|||||||
@@ -36,9 +36,10 @@ spec:
|
|||||||
- mountPath: /scripts
|
- mountPath: /scripts
|
||||||
name: scripts
|
name: scripts
|
||||||
volumes:
|
volumes:
|
||||||
|
# rclone-conf-b2: secret out-of-band con mega+b2 (verify comprueba ambos)
|
||||||
- name: rclone-conf
|
- name: rclone-conf
|
||||||
secret:
|
secret:
|
||||||
secretName: rclone-conf-infisical
|
secretName: rclone-conf-b2
|
||||||
items:
|
items:
|
||||||
- key: RCLONE_CONF
|
- key: RCLONE_CONF
|
||||||
path: rclone.conf
|
path: rclone.conf
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: rclone-b2-backup
|
||||||
|
namespace: backup-system
|
||||||
|
spec:
|
||||||
|
# Segunda copia offsite (crown jewels → Backblaze B2), tras el sync a Mega
|
||||||
|
schedule: "0 4 * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
failedJobsHistoryLimit: 2
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
activeDeadlineSeconds: 3600
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: chemavx-k8
|
||||||
|
restartPolicy: Never
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
containers:
|
||||||
|
- name: rclone
|
||||||
|
# OJO: imagen BusyBox — el script debe evitar GNU-ismos
|
||||||
|
image: docker.io/rclone/rclone:1.74.3
|
||||||
|
command: ["/bin/sh", "/scripts/b2-crown-jewels.sh"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /data/backups/backups
|
||||||
|
name: backup-dir
|
||||||
|
readOnly: true
|
||||||
|
- mountPath: /rclone
|
||||||
|
name: rclone-conf
|
||||||
|
- mountPath: /scripts
|
||||||
|
name: scripts
|
||||||
|
volumes:
|
||||||
|
- name: backup-dir
|
||||||
|
hostPath:
|
||||||
|
path: /data/backups/backups
|
||||||
|
type: Directory
|
||||||
|
# Secret OUT-OF-BAND (kubectl, no en git): remotes mega + b2.
|
||||||
|
# Fuente de verdad deseada: Infisical /backup-system RCLONE_CONF —
|
||||||
|
# cuando se añada el bloque [b2] en la UI, volver a rclone-conf-infisical
|
||||||
|
# y borrar este secret (la identity del operator es read-only, no pudo
|
||||||
|
# escribirse por API el 2026-07-15).
|
||||||
|
- name: rclone-conf
|
||||||
|
secret:
|
||||||
|
secretName: rclone-conf-b2
|
||||||
|
items:
|
||||||
|
- key: RCLONE_CONF
|
||||||
|
path: rclone.conf
|
||||||
|
- name: scripts
|
||||||
|
configMap:
|
||||||
|
name: backup-scripts
|
||||||
|
defaultMode: 0755
|
||||||
@@ -46,9 +46,10 @@ spec:
|
|||||||
hostPath:
|
hostPath:
|
||||||
path: /opt/reddit-intel
|
path: /opt/reddit-intel
|
||||||
type: Directory
|
type: Directory
|
||||||
|
# rclone-conf-b2: secret out-of-band con mega+b2 (sube a los dos)
|
||||||
- name: rclone-conf
|
- name: rclone-conf
|
||||||
secret:
|
secret:
|
||||||
secretName: rclone-conf-infisical
|
secretName: rclone-conf-b2
|
||||||
items:
|
items:
|
||||||
- key: RCLONE_CONF
|
- key: RCLONE_CONF
|
||||||
path: rclone.conf
|
path: rclone.conf
|
||||||
|
|||||||
Reference in New Issue
Block a user