Backups, migraciones y actualizaciones
Backups, migraciones y actualizaciones
Los capítulos anteriores dejaron el relay corriendo, configurado y observado. Este cubre lo que hace que ese relay sobreviva a un cambio de versión, a un disco perdido y a un error humano: el estado del esquema, el respaldo de cada pieza de estado y el procedimiento de actualización. La idea rectora es simple: Buzz guarda estado en cuatro lugares distintos y ninguno respalda a los otros.
El inventario de estado
Antes de hablar de backups conviene saber exactamente qué hay que respaldar. El
propio bundle de producción lo enumera: deploy/compose/run.sh incluye una función
backup_hint() que se imprime con ./run.sh backup-hint y también al final de cada
./run.sh upgrade.
flowchart TD
Relay["Relay buzz"] --> PG["Postgres: eventos, canales, membresia, moderacion"]
Relay --> S3["MinIO o S3: media y objetos git"]
Relay --> GIT["Volumen buzz-git-data en BUZZ_GIT_REPO_PATH"]
Relay --> SEC["Secretos: .env con BUZZ_RELAY_PRIVATE_KEY y HMAC"]
Relay --> REDIS["Redis: pubsub, presencia, rate limits"]
REDIS -.->|"efimero, se regenera"| Relay
| Pieza | Dónde vive | Qué se pierde si falta |
|---|---|---|
| Postgres | Volumen buzz-postgres-data (Compose) o base gestionada | El almacén canónico de eventos: mensajes, canales, hilos, membresía, moderación, audit log, invitaciones. Sin esto no hay comunidad. |
| Bucket S3/MinIO | Volumen buzz-minio-data o proveedor externo | Blobs de media y objetos git. Los eventos que los referencian siguen en Postgres, pero apuntan a nada. |
| Volumen git | buzz-git-data, montado en BUZZ_GIT_REPO_PATH=/data/git (compose.yml:20); en Helm es el PVC git con mountPath: /var/lib/buzz/git (values.yaml:280-287) | Estado en disco de repos servidos por el endpoint git. |
BUZZ_RELAY_PRIVATE_KEY | deploy/compose/.env o un Secret de Kubernetes | La identidad del relay. Rotarla equivale a un relay nuevo: el README del chart lo dice sin rodeos, “rotating it = new identity”. |
BUZZ_GIT_HOOK_HMAC_SECRET | Idem | El secreto que autentica los hooks de git. |
| Clave privada del owner | En manos del operador, no en el chart | Sin ella no hay quien administre la comunidad. El chart la restaura reinstalando con el mismo ownerPubkey. |
| Redis | Volumen buzz-redis-data | Nada crítico: scripts/dev-reset.sh lo describe como “ephemeral and always wiped on restart”. |
| Volúmenes de Caddy | buzz-caddy-data / buzz-caddy-config | Certificados TLS emitidos. Se vuelven a emitir, pero cuentan contra los límites de rate de la CA. |
La regla que cierra la lista, textual en run.sh: “Keep Postgres + object/git state
snapshots from the same maintenance window”. Un dump de Postgres de las 03:00 con un
espejo de S3 de las 09:00 produce eventos que referencian blobs inexistentes y blobs
huérfanos que ningún evento reclama.
Migraciones
Cómo están construidas
Buzz no usa una herramienta externa de migraciones. Usa sqlx con las migraciones
embebidas en el binario. La declaración vive en una sola línea
(crates/buzz-db/src/migration.rs:11):
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("../../migrations");
La macro sqlx::migrate! lee la carpeta migrations/ en tiempo de compilación y
la incrusta en el ejecutable. Consecuencia práctica para el operador: la imagen
ghcr.io/block/buzz:<tag> lleva dentro exactamente las migraciones que existían en
ese commit. No hay que copiar archivos .sql al servidor, y tampoco se puede
“parchear” una migración sin reconstruir la imagen.
La carpeta tiene hoy 28 archivos, de 0001_initial_schema.sql a
0028_long_reaction_payloads.sql. El 0001 es el esquema multi-tenant consolidado;
su cabecera aclara que no es aditivo sobre el esquema single-community anterior y
que el cutover legado es un script de operador aparte, no estado de migración de
arranque. El resto son incrementos pequeños y específicos: 0004_events_tags_gin.sql
añade un índice GIN, 0021_created_at_fence_floor.sql instala el trigger de piso de
created_at, y 0028_long_reaction_payloads.sql es una sola sentencia
ALTER TABLE reactions ALTER COLUMN emoji TYPE VARCHAR(66), para que un shortcode de
emoji personalizado de 64 caracteres quepa con sus dos : envolventes de NIP-25. El
control de qué se aplicó vive en la tabla estándar de sqlx, _sqlx_migrations.
Los tres caminos para aplicarlas
flowchart LR
A["BUZZ_AUTO_MIGRATE habilitado"] --> M["MIGRATOR.run sobre el pool"]
B["buzz-admin migrate"] --> M
C["just migrate en desarrollo"] --> B
M --> D["Tabla _sqlx_migrations actualizada"]
- Al arrancar el relay.
crates/buzz-relay/src/main.rs:188-198leeBUZZ_AUTO_MIGRATEy, si está habilitada, llamadb.migrate(). Si falla, el arranque aborta conDatabase migration failed. Si no está habilitada, deja el logSkipping database migrations because BUZZ_AUTO_MIGRATE is not enabled. La función que interpreta la variable aceptatrue,1,yesuon(case-insensitive); cualquier otra cosa cuenta como desactivado. - Por CLI.
buzz-admin migrateconecta a la base y ejecuta lo mismo (crates/buzz-admin/src/main.rs:139-142), imprimiendoDatabase migrations complete. - En desarrollo.
just migrate(Justfile:699) no tiene cuerpo propio: depende de la receta privada_ensure-migrations(Justfile:193-195), que a su vez espera a que Postgres y Redis esténhealthy, correcargo run -p buzz-admin -- migratey después./scripts/seed-local-community.sh. Esa dependencia es la razón de quejust relay,just relay-releaseyjust devmigren solos antes de arrancar.
El default de BUZZ_AUTO_MIGRATE depende de dónde estés, y este es un punto donde
mucha gente se equivoca:
| Contexto | Valor |
|---|---|
| El binario, sin variable definida | Desactivado (main.rs:188-198) |
deploy/compose/compose.yml:21 | false si el .env no lo define |
deploy/compose/.env.example:19 | true |
Chart Helm, values.yaml:377 | migrate.autoMigrate: true |
El README del bundle Compose lo llama explícitamente “opt-in”: hay que ponerlo en
true o correr buzz-admin migrate antes de arrancar contra una base nueva.
Guardas: la migración falla en cerrado
run_migrations() no es solo MIGRATOR.run(). Tiene una guarda antes y otra después:
Antes, reject_legacy_nip_rs_cardinality_ambiguity() consulta
SELECT max(version) FROM _sqlx_migrations WHERE success. Si la base está poblada y
todavía en 0001-0006, busca filas de kind 30078 con d_tag de forma
read-state:<32 hex> cuya cardinalidad de tags d/t sea ambigua. Si encuentra
alguna, aborta antes de que sqlx abra su transacción, con el mensaje:
NIP-RS migration blocked: pre-0007 database contains kind-30078 rows with
ambiguous d/t tag cardinality; repair or remove those nonconforming rows before retrying
El motivo está en el comentario del código: la migración 0007 está
checksum-frozen y purgaría ese historial de forma irreversible. La guarda existe
para que el operador inspeccione y repare esas filas primero.
Después de MIGRATOR.run(), verify_floor_guard_catalog() comprueba que el
trigger de piso de created_at introducido por 0021 exista con la forma correcta
en la tabla padre events y en cada una de sus particiones. CREATE TABLE ... PARTITION OF
clona los triggers del padre, pero una partición adjuntada con ATTACH PARTITION o
creada por un camino de código antiguo escaparía silenciosamente a la guarda. Si falta
en alguna, la migración falla.
Ese mismo chequeo se vuelve a ejecutar al arrancar en relays que no migran: si la
verificación falla, el fence de réplica queda cerrado y todas las lecturas de cursor
se sirven desde el writer (Replica fence disabled — floor guard verification failed).
Es una degradación de rendimiento ruidosa, no una caída.
Justo después de migrar, el relay llama db.ensure_future_partitions(3)
(main.rs:200-203): crea por adelantado las particiones futuras de events. Un fallo
ahí se registra pero no aborta el arranque.
Inspeccionar el estado del esquema
No hay un subcomando buzz-admin migrate --status; el inventario se consulta con SQL
directo sobre la tabla de sqlx. Con el bundle Compose:
cd deploy/compose
docker compose --env-file .env exec postgres \
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
-c "SELECT version, description, success, installed_on FROM _sqlx_migrations ORDER BY version;"
Para la comprobación rápida de “¿estoy al día?”, basta SELECT max(version) FROM _sqlx_migrations WHERE success; — exactamente el valor que consulta la guarda del
propio código. Compáralo con el número del último archivo de migrations/ en el commit
de la imagen que corres: si es menor, estás sirviendo contra un esquema viejo.
Ese caso importa especialmente en Kubernetes. El README del chart es explícito: los readiness probes solo verifican conectividad a la base, no frescura del esquema, así que un pod puede aparecer sano contra un esquema sin migrar y fallar bajo carga.
Para saber qué versión del relay está corriendo sin entrar al contenedor, usa el
documento NIP-11: el campo version de RelayInfo se rellena con
env!("CARGO_PKG_VERSION") del crate buzz-relay (crates/buzz-relay/src/nip11.rs).
curl -fsS -H 'Accept: application/nostr+json' https://buzz.midominio.cl/ | jq '.version, .software'
Migraciones en Kubernetes
Con el chart, migrate.autoMigrate: true es el default y helm upgrade es todo el
procedimiento de upgrade. Varias réplicas arrancando a la vez compiten de forma
race-safe detrás de un advisory lock de Postgres.
Si prefieres desacoplar migración de servicio, migrate.autoMigrate=false es válido,
pero entonces el chart no migra por ti: el operador debe correr buzz-admin migrate
(un Pod aparte o un Job one-shot) antes de cada helm install / helm upgrade. El
NOTES.txt del chart imprime esa advertencia al instalar.
El values expone migrate.preUpgradeJob.enabled con backoffLimit y
activeDeadlineSeconds, pero es una llave reservada: el README lo declara como
roadmap del chart y no existe plantilla que la implemente. No la configures esperando
que haga algo.
Como red de seguridad de todo esto, just test-unit corre cargo nextest run -p buzz-db --lib: tests puros de parseo que concatenan el SQL de todas las migraciones
embebidas en orden de versión y verifican los lints de scoping de tenant. Sin ese gate,
un archivo perdido en migrations/ se publicaría en verde.
Backup
La checklist canónica
./run.sh backup-hint imprime esto (deploy/compose/run.sh:38-51):
Back up these before upgrades and on a regular schedule:
- deploy/compose/.env, especially BUZZ_RELAY_PRIVATE_KEY, DB/Redis/S3 secrets, and BUZZ_GIT_HOOK_HMAC_SECRET
- The owner private key if bootstrap generated one for RELAY_OWNER_PUBKEY
- Postgres data (prefer pg_dump or a quiesced volume snapshot)
- MinIO/S3 bucket contents for media and git objects
- buzz-git-data volume (BUZZ_GIT_REPO_PATH=/data/git)
- Caddy data/config volumes if using compose.caddy.yml
Keep Postgres + object/git state snapshots from the same maintenance window.
Nota que backup-hint es uno de los pocos subcomandos de run.sh que no pasa por
require_env(): puedes imprimir la checklist aunque el .env todavía tenga
CHANGE_ME.
Postgres
El bundle corre postgres:17-alpine (deploy/compose/compose.yml:52), así que
pg_dump está dentro del contenedor. La recomendación del propio script es preferir
pg_dump o un snapshot de volumen en reposo (quiesced) — no un cp del
directorio de datos con el servidor escribiendo.
cd deploy/compose
set -a; . ./.env; set +a
docker compose --env-file .env exec -T postgres \
pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom \
> "buzz-$(date -u +%Y%m%dT%H%M%SZ).dump"
Restauración sobre una base vacía:
docker compose --env-file .env exec -T postgres \
pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists \
< buzz-20260807T030000Z.dump
Después de restaurar, verifica el esquema antes de abrir tráfico: consulta
max(version) en _sqlx_migrations y, si el dump venía de una versión anterior a la
imagen que vas a levantar, deja que el arranque migre (o corre buzz-admin migrate)
antes de exponer el puerto.
MinIO / S3
El bundle ya trae un cliente mc: el servicio minio-init usa la imagen
minio/mc y hace mc alias set local http://minio:9000 ..., mc mb --ignore-existing
y mc anonymous set none sobre BUZZ_S3_BUCKET (default buzz-media). El mismo
cliente sirve para espejar el bucket a un destino externo dentro de la ventana de
mantenimiento.
Recuerda que BUZZ_S3_ENDPOINT no es configurable por .env en el bundle:
compose.yml:14 lo fija a http://minio:9000 en la sección environment, que gana
sobre env_file. Si usas S3 gestionado, el respaldo lo hacen las herramientas del
proveedor (versionado de bucket, replicación entre regiones), no este stack.
El bucket contiene dos clases de objetos que hay que respaldar juntas: blobs de media y
objetos git. El backend de git sobre object storage hidrata un repo efímero desde S3 en
cada request, así que el bucket es la fuente de verdad; el volumen buzz-git-data
guarda el estado en disco bajo BUZZ_GIT_REPO_PATH.
Secretos
Los secretos no están en la base ni en el bucket: están en deploy/compose/.env o en
un Secret de Kubernetes. Son la pieza más fácil de olvidar y la más cara de perder,
porque no se regeneran: se reemplazan por otros distintos y eso cambia la identidad.
BUZZ_RELAY_PRIVATE_KEY(deploy/compose/.env.example:27): identidad del relay. El README del chart lo dice sin ambigüedad: rotarla es un relay nuevo, y los peers de federación dejan de reconocerlo. Además es la clave con la que el relay firma sus propios eventos, incluido el roster que vimos en Membresía, roles y moderación.BUZZ_GIT_HOOK_HMAC_SECRET(deploy/compose/.env.example:28).- Credenciales de Postgres, Redis y S3.
- La clave privada del owner, que no vive en el stack. El chart la restaura
reinstalando con el mismo
ownerPubkey.
El README.md del bundle exige que estos valores sean estables entre reinicios;
require_env() en run.sh:19-36 aborta start, restart, pull, upgrade y
config si el .env falta o si queda cualquier línea con CHANGE_ME.
En Kubernetes, el NOTES.txt advierte que poner secrets.relayPrivateKey o
secrets.gitHookHmacSecret inline en values.yaml filtra secretos al historial de git
y a los logs de CI: lo correcto es un Secret externo con secrets.existingSecret. Ese
camino es además el único seguro bajo ArgoCD o Flux, porque esas herramientas renderizan
con helm template y ahí lookup devuelve vacío, rotando los secretos en cada sync.
Orden de restauración
flowchart TD
S1["Parar el relay: run.sh stop"] --> S2["Restaurar secretos: .env o Secret"]
S2 --> S3["Restaurar Postgres: pg_restore"]
S3 --> S4["Restaurar bucket S3 y volumen git"]
S4 --> S5["Aplicar migraciones pendientes"]
S5 --> S6["Arrancar relay y verificar _readiness"]
S6 --> S7["Verificar version NIP-11 y max version de _sqlx_migrations"]
El relay va último a propósito: arrancarlo antes de que la base y el bucket estén
coherentes lo hace servir eventos que apuntan a blobs inexistentes, y el sondeo de
conformidad de git contra S3 puede abortar el arranque (BUZZ_GIT_CONFORMANCE_PROBE
está en true por defecto y su fallo es fatal, como vimos en
Medios y almacenamiento).
just reset: qué destruye exactamente
just reset (Justfile:68-70) es una receta de desarrollo, no de producción, y
tiene confirmación interactiva obligatoria vía el atributo [confirm(...)] de just:
This will DELETE all development data and preserve installed Buzz. Continue? (y/N)
Al confirmar ejecuta ./scripts/dev-reset.sh --yes, que hace en orden:
scripts/reset-desktop-dev-state.sh— borra el estado de desarrollo de la app desktop.docker compose down -v --remove-orphans— para los contenedores y elimina todos los volúmenes del compose de desarrollo. Aquí es donde se pierden Postgres y MinIO locales.exec scripts/dev-setup.sh— recrea el entorno desde cero y vuelve a migrar.
La advertencia literal del script:
WARNING: This will DELETE all development data (desktop state, postgres, minio volumes).
Installed Buzz app state and its production keyring are preserved.
Redis data is ephemeral and always wiped on restart.
Dos precisiones: just reset opera sobre el docker-compose.yml de la raíz, el stack
de desarrollo, y no toca deploy/compose/ — pero el -v es indiscriminado dentro de
ese proyecto de compose. Y el equivalente en producción no existe: ./run.sh stop
ejecuta compose down sin -v y conserva los volúmenes deliberadamente.
Actualizar de versión
Qué leer antes
Dos archivos, y no son intercambiables:
| Archivo | De qué habla |
|---|---|
CHANGELOG.md (raíz) | Cambios de la app desktop y compartidos. Encabezados ## v0.5.5, ## v0.5.4, … |
crates/buzz-relay/CHANGELOG.md | Cambios del relay. Encabezados ## relay-v0.2.0, ## relay-v0.1.1. |
RELEASING.md | El procedimiento de publicación completo, por carril. |
Si estás operando un relay, el changelog que te importa es el del crate.
Versionado y canales de release
RELEASING.md describe tres carriles independientes, que versionan por separado:
| Carril | Entrada | Artefacto | Autoridad de versión |
|---|---|---|---|
| Desktop | just release-desktop <version> | App empaquetada firmada | desktop/package.json y manifiestos sincronizados |
| Relay | just release-relay | Imagen ghcr.io/block/buzz | crates/buzz-relay/Cargo.toml |
| Mobile | scripts/mobile-release.sh candidate X.Y.Z | Tag inmutable mobile-vX.Y.Z-rc.N | El propio tag remoto |
Para el relay el flujo es: just release-relay corre sobre main, abre o actualiza un
PR relay-release/<version>, bumpea crates/buzz-relay/Cargo.toml, regenera
Cargo.lock y actualiza el changelog del relay. Al mergear, el workflow
auto-tag-on-release-pr-merge empuja el tag relay-v<version>, y ese tag dispara
docker.yml.
Los tags de imagen que produce docker.yml están documentados en su cabecera:
| Disparador | Tags publicados |
|---|---|
Push a main | :main, :sha-<7>, más :debug-main y :debug-sha-<7> |
Tag relay-v*.*.* | :{version}, :{major}.{minor}, :{major} y sus :debug-* |
| Release estable | Además mueve :latest y :debug-latest |
Detalle que decide tu política de pinning: :latest sigue solo releases estables.
metadata-action usa flavor.latest=auto, así que un relay-v0.3.0-rc.1 publica
:0.3.0-rc.1 sin mover :latest, y los pushes a main nunca generan :latest. Los
tags debug-* traen los mismos binarios optimizados pero con información de línea para
profilers nativos; los normales van stripped y son el default de despliegue.
El README.md del bundle Compose es explícito sobre el default: BUZZ_IMAGE apunta a
ghcr.io/block/buzz:main, pensado para pruebas tempranas, y para producción hay que
pinnear ghcr.io/block/buzz:sha-<7> o un tag semver.
El chart Helm es un cuarto artefacto versionado aparte (Chart.yaml version: 0.1.7,
appVersion: "0.1.0"), publicado como artefacto OCI en
oci://ghcr.io/block/buzz/charts/buzz mediante tags chart-v*. Su
image.tag vacío significa “usa .Chart.AppVersion”, que casi nunca es lo que quieres
en producción: fíjalo explícitamente.
El orden seguro
sequenceDiagram
participant Op as Operador
participant BK as Backup
participant DB as Postgres
participant R as Relay
participant C as Clientes
Op->>BK: 1. Dump de Postgres, espejo de S3 y git, copia de secretos
Op->>DB: 2. Migraciones aplicadas o auto-migrate habilitado
Op->>R: 3. Pull de la imagen nueva y recreacion
R-->>Op: 4. _readiness responde ready y NIP-11 muestra la version nueva
Op->>C: 5. Recien ahora se actualizan las apps de escritorio
Con el bundle Compose el paso 3 es un solo comando, que además vuelve a imprimir la
checklist de respaldo al terminar (run.sh:69-74):
cd deploy/compose
# 1. backup — ver seccion anterior
./run.sh backup-hint
# 2 y 3. actualizar BUZZ_IMAGE en .env y aplicar
$EDITOR .env # BUZZ_IMAGE=ghcr.io/block/buzz:0.2.0
./run.sh config # valida la config combinada antes de tocar nada
./run.sh upgrade # compose pull + compose up -d --wait + backup_hint
# 4. verificar
./run.sh status
curl -fsS "http://127.0.0.1:$(grep -E '^BUZZ_HTTP_PORT=' .env | cut -d= -f2-)/_liveness"
curl -fsS -H 'Accept: application/nostr+json' http://127.0.0.1:3000/ | jq .version
./run.sh upgrade es literalmente require_env + compose pull + compose up -d --wait
backup_hint. El--waitrespeta el healthcheck del relay, que abre/dev/tcp/127.0.0.1/8080, haceGET /_readinessy busca200 OK, conretries: 12ystart_period: 30s. Si la migración falla al arrancar, el contenedor no llega a sano y--waitdevuelve error: eso es exactamente lo que quieres.
Si solo cambiaste variables de entorno o la imagen y no quieres tocar el resto del
stack, ./run.sh restart hace compose up -d --wait --force-recreate relay.
En Kubernetes, con autoMigrate en true, helm upgrade es el procedimiento
completo. Con autoMigrate en false, el buzz-admin migrate va antes, siempre.
helm upgrade buzz oci://ghcr.io/block/buzz/charts/buzz \
--version 0.1.7 \
-n buzz \
--set image.tag=0.2.0 \
--reuse-values \
--wait
kubectl -n buzz rollout status deployment/buzz
Rollback
El rollback de binario es barato: vuelve a apuntar BUZZ_IMAGE al tag anterior y
repite ./run.sh upgrade, o usa helm rollback en Kubernetes.
El rollback de esquema no existe. Las migraciones sqlx de Buzz son solo hacia
adelante: no hay archivos down, y varias migraciones son destructivas por diseño
(0007 es checksum-frozen y purga historial NIP-RS). Un relay viejo contra un esquema
nuevo es territorio no soportado. Por eso:
- El backup de Postgres anterior a la migración es tu único rollback real de datos.
- Antes de un salto de varias versiones, prueba la migración sobre una copia restaurada del dump, no sobre producción.
- Si la guarda pre-0007 aborta tu migración, no la esquives: repara las filas señaladas. Está ahí precisamente para evitar una pérdida irreversible.
Una nota tranquilizadora: la superficie que cambia en un upgrade es la que tú cambias en
el .env. Por ejemplo BUZZ_MESH es false por defecto en el binario y solo on,
true o 1 lo activan, así que un upgrade de imagen no abre puertos UDP nuevos solo.
Compatibilidad relay ↔ app de escritorio
Aquí conviene ser honesto sobre lo que el repositorio dice y lo que no dice.
Lo que sí dice:
- Los carriles versionan de forma independiente.
docker.ymllo comenta explícitamente: los tagsv*de desktop ysprig-v*de agente no publican la imagen del relay; solorelay-v*lo hace, “so the relay image version trackscrates/buzz-relay/Cargo.toml, never desktop”. Un relay0.2.0y un desktop0.5.5son perfectamente normales: no son la misma escala de versión. - El contrato real entre cliente y relay es el protocolo, no el número de versión. El
documento NIP-11 que sirve el relay en
/(conAccept: application/nostr+json) y en/infopublicasupported_nips,supported_extensions(hoy incluye"nip-er"),limitationconauth_requiredypayment_required, y el camposelfcon la clave del relay cuando aplica. Los NIPs draft no se anuncian ensupported_nipshasta tener número upstream: se anuncian como cadenas ensupported_extensions. - La app de escritorio se actualiza por su propio canal: el release
buzz-desktop-latestcon sulatest.json, que cubre las cuatro claves de plataforma (darwin-aarch64,darwin-x86_64,linux-x86_64,windows-x86_64). Si el updater reporta “no update available”,RELEASING.mdmanda verificar que ese release exista y contenga unlatest.jsonválido; una entrada de plataforma ausente suele significar que ese job de release falló.
Lo que no dice: el repositorio no publica una matriz de compatibilidad
relay-versión ↔ desktop-versión, ni un campo de versión mínima de cliente que el relay
rechace. No inventes una. La consecuencia operativa práctica es la del orden seguro de
la sección anterior: actualiza el relay primero y los clientes después. Un cliente
viejo contra un relay nuevo pierde, como mucho, funciones que dependen de NIPs recién
añadidos y que el cliente aún no sabe pedir; un cliente nuevo contra un relay viejo
pide capacidades que ese relay todavía no anuncia en supported_nips.
Recuerda también que varias capacidades del listado son condicionales, no fijas: NIP-43
solo se añade a supported_nips cuando el relay tiene clave estable y aplica
membresía, y el bloque push aparece únicamente cuando la entrega push está
configurada. Un supported_nips más corto de lo esperado puede ser configuración, no
versión: revísalo contra el capítulo
Configuración antes de culpar al
upgrade.
Por último, la tabla “Works today / Being wired up / Strong opinions” del README pone las notificaciones push y los clientes móviles fuera de la columna de lo que funciona hoy. Al planificar upgrades, trátalas como trabajo en curso, no como contrato estable.
Resumen
- Buzz guarda estado en cuatro lugares independientes: Postgres, el bucket S3/MinIO, el
volumen git y los secretos del
.env. Redis es efímero y se descarta al reiniciar. - Las migraciones son sqlx embebidas en el binario vía
sqlx::migrate!("../../migrations"); hoy son 28 archivos y su estado vive en la tabla_sqlx_migrations. - Se aplican por tres caminos:
BUZZ_AUTO_MIGRATEal arrancar el relay,buzz-admin migratepor CLI, ojust migrateen desarrollo. El default deBUZZ_AUTO_MIGRATEdepende del contexto: opt-in en el binario y encompose.yml,trueendeploy/compose/.env.exampley en el chart. - La migración falla en cerrado: una guarda rechaza bases pre-0007 con filas de kind
30078 ambiguas, y otra verifica el trigger de piso de
created_atde la migración 0021 eneventsy en cada partición. - Los readiness probes solo comprueban conectividad a la base, no frescura del esquema: un pod puede parecer sano contra un esquema sin migrar.
./run.sh backup-hintimprime la checklist canónica de respaldo, y los snapshots de Postgres y de objeto/git deben venir de la misma ventana de mantenimiento.- Perder
BUZZ_RELAY_PRIVATE_KEYsignifica un relay con identidad nueva; la clave del owner no vive en el stack y se restaura reinstalando con el mismoownerPubkey. just resetpide confirmación y ejecutadev-reset.sh --yes, que hacedocker compose down -vsobre el stack de desarrollo y borra todos sus volúmenes. No existe un equivalente para producción.- El relay se versiona con tags
relay-v*desdecrates/buzz-relay/Cargo.toml, ydocker.ymlpublica:{version},:{major}.{minor},:{major}y:latestsolo para estables; los pushes amainproducen:mainy:sha-<7>. - Orden seguro de actualización: backup → migraciones → relay → clientes. El rollback de imagen es trivial; el rollback de esquema no existe, así que el dump previo es tu única salida.
- No hay matriz de compatibilidad publicada entre relay y desktop: el contrato es el
documento NIP-11 con
supported_nipsysupported_extensions, y los carriles de release son independientes.