Copia de seguridad
Qué hay que guardar:
- Base de datos: usuarios, progreso, listas, títulos y numeración cambiados a mano. Se copia
con
pg_dump, no copiando el volumen. HOST_DATA_DIR: los vídeos convertidos y las miniaturas. Va con la base de datos (cada episodio eshls/<id>). ConDELETE_SOURCES=truees la única copia de los vídeos..env: la configuración y las claves (BETTER_AUTH_SECRET, la de Cloudflare, las de R2…). Guárdalo en tu gestor de contraseñas, como nota segura. Nunca en git, ni en una nube sin cifrado de extremo a extremo. Perderlo no es grave (las claves se pueden volver a crear); que se filtre, sí: si pasa, cambia las de Cloudflare, R2 y OpenRouter,WUD_PASSWORDyBETTER_AUTH_SECRET.HOST_MEDIA_DIR, opcional: los originales. Con ellos se puede regenerarHOST_DATA_DIR, pero son horas de conversión.
scripts/backup.sh lo hace todo, con la app en marcha. Ejecútalo desde la carpeta de
compose.prod.yaml y .env (necesita jq y rsync). Un HDD externo va bien: tras la primera
copia, rsync solo copia lo nuevo. La carpeta de destino tiene que existir ya (créala una vez en
el disco de la copia), así que con el disco desconectado no copia nada al disco interno; tampoco
copia al mismo disco que HOST_DATA_DIR:
scripts/backup.sh /ruta/del/backup # base de datos, convertidos y .envscripts/backup.sh --media /ruta/del/backup # además, los originalesDeja jellycartoon.dump, data/, .env y, con --media, media/. Las carpetas son un espejo:
cada copia sustituye a la anterior en el mismo destino. Primero hace el volcado y luego copia
los ficheros; un episodio que termine de convertirse entre medias se volvería a convertir al
restaurar, pero nunca queda uno listo sin sus ficheros.
Para restaurar, con el .env de la copia en su sitio y las carpetas de HOST_DATA_DIR (y
HOST_MEDIA_DIR) vueltas a copiar:
docker compose -f compose.prod.yaml up -d dbdocker compose -f compose.prod.yaml exec -T db \ pg_restore -U jellycartoon -d jellycartoon --clean --if-exists --no-owner < /ruta/del/backup/jellycartoon.dumpdocker compose -f compose.prod.yaml up -dLa app aplica al arrancar las migraciones posteriores a la copia.
Copia en Cloudflare R2
Sección titulada «Copia en Cloudflare R2»Con el perfil backup, cada noche se sube la base de datos a un bucket de
R2. Antes de salir del equipo se cifra con
age: el servidor solo tiene la clave pública, que cifra pero no
descifra, y la privada la guardas tú aparte. Solo va la base de datos (unos MB), no los vídeos; la
copia completa sigue siendo scripts/backup.sh.
Una vez, en Cloudflare (R2 en el panel):
- Crea un bucket, por ejemplo
jellycartoon, con location Automatic (no una jurisdicción como la UE, que usa otra dirección) y storage class Standard: es la que entra en la capa gratuita, y Infrequent Access cobra cada descarga y un mínimo de 30 días por fichero. - R2 → Manage API tokens → Create API token, con el permiso Object Read & Write y solo ese bucket. Apunta el Access Key ID, el Secret Access Key y el Account ID.
- En el bucket, Settings → Bucket lock rules (bloqueo):
una regla de 5 días para el prefijo
backups/. Como el token también puede borrar, así nadie que lo tenga borra las copias recientes.
Cada bucket, de una sola casa: una clave de R2 vale para el bucket entero, y con uno compartido una
casa podría borrar las copias de otra. Tras cada copia se borran las de antes de las 7 últimas noches
(BACKUP_KEEP): por número y no por fecha, para que si las copias dejan de subir no se vayan también
las últimas buenas.
En tu ordenador, la clave para cifrar (age-keygen viene con age):
age-keygen -o jellycartoon-copias.keyGuarda el fichero entero en tu gestor de contraseñas: sin él no se pueden leer las copias. La línea
# public key: age1… es la que va en el .env:
COMPOSE_PROFILES="updates,backup" # los que uses, separados por comasR2_ACCOUNT_ID="..."R2_ACCESS_KEY_ID="..."R2_SECRET_ACCESS_KEY="..."R2_BUCKET="jellycartoon/backups" # bucket y carpeta (prefijo): aparece sola con la primera copiaBACKUP_AGE_RECIPIENT="age1..."# BACKUP_TIME="03:30" # a qué hora (la de TZ)# BACKUP_KEEP="7" # cuántas noches se guardanLas claves de R2 van, como el resto del .env, en el gestor de contraseñas; la de age también, en
la misma nota o en otra.
Para probarla sin esperar a la noche:
docker compose -f compose.prod.yaml up -ddocker compose -f compose.prod.yaml run --rm backup nowLa copia aparece en el bucket como backups/jellycartoon-<fecha UTC>.dump.age. Si una noche no se sube,
docker compose -f compose.prod.yaml ps backup la muestra unhealthy al día siguiente, y
logs backup dice por qué. Para restaurar, descarga la copia desde el panel de R2, descífrala y
sigue los pasos de arriba con el fichero que sale:
age -d -i jellycartoon-copias.key jellycartoon-<fecha>.dump.age > jellycartoon.dumpSi una copia falla y el log no basta, esto enseña cada petición a R2 y su respuesta, sin claves ni firmas:
docker compose -f compose.prod.yaml run --rm -e RCLONE_DUMP=headers -e RCLONE_VERBOSE=2 backup now 2>&1 \ | grep -E ': (PUT|POST|GET|HEAD|DELETE) |: HTTP/1|Subida|Attempt'