Nota · a quien corresponda O_SDK es un wrapper Docker no oficial de Oasis · SolarNET.HuB: unos ficheros para levantarlo en contenedores. Hoy lleva Oasis 1.3.8 y sostiene un pub de verdad; lo probado y lo que sigue en obras, en ¿Quién llega? Software libre, work in progress, sin prisa.
Skip to content

Protocolo de recuperación de Oasis (fork dockerizado) ​

Checklist operativo reutilizable para recuperar un despliegue de Oasis (cliente o pub) tras corrupción de disco, pérdida del log SSB o daño en el repositorio git. Complementa a UPGRADE-PROTOCOL.md. Deriva de una recuperación real completa (repo + imagen + identidad).

Modelo mental. Solo hay UNA cosa irremplazable: el secret (identidad SSB) y las claves (GPG, SSH). Todo lo demás es derivable: el log se re-replica desde el pub, los índices (db2/indexes y db2/jit; flume/ en ≤ 1.1.x) se reconstruyen del log, los blobs son content-addressed y se re-descargan, el código vive en GitHub y la imagen Docker se reconstruye del árbol. El pub es la fuente de verdad de los feeds: lo que el pub no tenga de tu feed, no existe.

0. Triaje — verificar integridad ANTES de usar nada ​

Nunca confiar en un fichero de un disco sospechoso sin verificarlo. El daño típico: fichero con tamaño intacto pero contenido a ceros (total o por tramos alineados a 4KB).

bash
# Texto: buscar bytes NUL (un fichero de texto sano tiene CERO)
rg -la '\x00' <dir>                    # lista los dañados
tr -dc '\000' < fichero | wc -c        # debe dar 0

# Binarios: NUL no discrimina (un PNG sano tiene NULs) → comprobar magic bytes
head -c 8 fichero | od -An -tx1
#   PNG=89504e47  JPG=ffd8ff  GGUF=47475546  gzip=1f8b  PACK(git)=5041434b

Trampas conocidas de las herramientas:

  • grep -qP '\x00' NO detecta NUL de forma fiable → falsos negativos silenciosos. Usar rg.
  • grep -c en ficheros binarios cuenta líneas, no ocurrencias → usar grep -ao ... | wc -l.
  • Los greps son case-sensitive: buscar nombres propios con -i.
  • Un fichero "legible" que muestra solo espacios en el editor puede ser 100% NUL (el render los pinta como blancos). Verificar con od.

1. Prioridades (en este orden, siempre) ​

  1. Blindar lo insustituible: copiar secret/claves a otro medio físico y VERIFICAR la copia (tamaño, sin NUL, estructura). Un backup no verificado no es un backup.
  2. Blindar el working tree: robocopy /E /XD node_modules volumes-dev a una carpeta de recovery ANTES de tocar git. Los uncommitted viven en disco, no en .git.
  3. Sacar el código de la máquina: commit + push a GitHub en cuanto haya algo verificado.
  4. Solo después: reconstruir imagen, contenedor y log.

2. Recuperar el repositorio git ​

Si el object store está corrupto, NO reparar in-place: dejar el .git dañado intacto como forense y reconstruir aparte.

bash
# 1) Clon limpio en carpeta de recovery
git clone <origin-url> recovery-clean

# 2) Apuntar el .git sano al working tree real (sin copiar nada)
git --git-dir=recovery-clean/.git --work-tree=<working-tree> status --short

# 3) Elegir la base correcta: la rama remota MÁS CERCANA al working tree
#    (comparar diff --name-status contra cada candidata; menor huella gana)

# 4) Commit por plumbing (no toca el working tree):
git ... add -A && WTREE=$(git ... write-tree)
git ... commit-tree $WTREE -p <base> -m "recover: ..." && git ... update-ref ...

Del store corrupto aún se puede salvar:

  • El reflog (logs/HEAD) es texto plano: da los SHAs de los commits locales perdidos.
  • Commits/árboles individuales pueden ser legibles aunque la punta esté corrupta: git cat-file -p <sha> uno a uno, del más nuevo al más viejo.
  • Truco blob-id: el hash de un blob está en el árbol (legible) aunque el blob esté corrupto. Si el blob-id local == blob-id en una rama remota, la versión de GitHub es byte-exacta a la local → recuperación exacta garantizada sin leer el blob.

Antes del push, SIEMPRE:

  • Dry-run de secretos: git add -An + grep de .env|secret|.key|.gpg|.ssh|id_ed25519 por nombre Y por contenido (BEGIN.*PRIVATE KEY).
  • Verificación NUL/magic de todo lo que entra (§0) — commitear un árbol dañado obliga a un commit correctivo después.
  • Si hay daño mezclado: separar por procedencia (¿este diff es trabajo real o es daño?); los binarios que el trabajo no tocó se restauran wholesale de la rama remota.

3. Reconstruir la imagen y el contenedor cliente ​

bash
# SIEMPRE desde un árbol verificado (el working tree dañado hornea el daño en la imagen)
docker compose build oasis-client
docker rm <contenedor-viejo>           # los datos viven en los bind mounts, no se pierden
docker compose up -d oasis-client
  • Un docker-compose.override.yml (no versionado) permite apuntar los bind-volumes al volumes-dev original desde otra carpeta de código.
  • El modelo AI se re-descarga solo si falta (download_ai_model del entrypoint); opt-out con OASIS_SKIP_AI_MODEL=true. Apartar el dañado: el entrypoint decide por presencia del fichero.
  • Síntoma "package.json parse error at position 4096" dentro del contenedor = imagen construida con árbol dañado → rebuild desde árbol limpio.

4. Restaurar un log SSB perdido — ⚠️ LA PARTE DELICADA ​

REGLA DE ORO: nunca arrancar la GUI de Oasis con un log vacío. backend.js auto-publica oasisVersion al arrancar; sobre log vacío eso crea un seq-1 nuevo → FORK del feed frente al historial que guarda el pub → la replicación del propio feed queda bloqueada para siempre (EBT no puede reconciliar) y la identidad puede quedar marcada como bifurcada en la red.

Actualización 1.1.2 (2026-09-17). El oasisVersion del arranque ya está guardado en upstream (if (!latestSeq) return), pero la regla sigue en pie porque el PM de bienvenida de la GUI (backend.js, a los 3 s) no lo está: se publica si no existe ~/.ssb/oasis-first-contact o si el flag es de otra identidad (lo borra y publica). Mitigación: pre-crear el flag con el feed y welcome=done. Todo el flujo está empaquetado en client/scripts/import-identity.sh + client/scripts/sync-only.sh (../CLIENT-PROTOCOL.md §2-§3); lo de abajo es el mecanismo.

Secuencia correcta:

bash
# 1) Preservar: secret, config, gossip.json, blobs/. Apartar: flume/, ebt/ (derivados),
#    y borrar socket y manifest.json residuales.
#    Oasis >= 1.2: el log es db2/log.bipf y los derivados db2/indexes y db2/jit. Si se aparta el
#    log, se aparta db2/ ENTERO y se DEJA flume/log.offset si es la guarda de migración (texto,
#    «OASIS: this log was migrated…»): impide que una versión vieja arranque con un log vacío.

# 2) Arrancar sbot PURO (sin GUI → nada puede publicar). Hoy: bash client/scripts/sync-only.sh start
#    (= modo `server` del entrypoint: chown, parches de runtime y `exec node SSB_server.js start`; nunca backend.js)
docker compose run -d --rm --no-deps --name oasis-sync-only -e OASIS_SKIP_AI_MODEL=true oasis-client server

# 3) Vigilar la re-replicación (termómetro = el log creciendo; sync-only.sh status lo hace solo):
watch stat -c%s volumes-dev/ssb-data/db2/log.bipf        # Oasis <= 1.1.x: …/flume/log.offset

# 4) Preguntar al pub qué tiene de tu feed (respuesta definitiva, no adivinar):
bash devops/scripts/pub-feed-seq.sh '<tu-feed>'            # seq_pub · pub_follows_feed (pub/tools/ssb-probe.js por stdin)
bash client/scripts/sync-only.sh status --pub              # compara fichero / sbot / pub → SYNC-OK | AHEAD | BEHIND | PUB-UNKNOWN
#    Cuando el log local alcance ese seq (y esté estable): sincronización COMPLETA.

# 5) Solo entonces: parar el sbot puro y arrancar la GUI normal.
#    Su auto-publish caerá en seq N+1 = continuación legítima, sin fork.
docker stop oasis-sync-only && docker start oasis-client

Tips de diagnóstico SSB:

  • "Another Oasis instance is already running" también salta con un OpenError de LevelDB (índices corruptos), no solo con un lock real (SSB_server.js, isLockError).
  • conn.json corrupto bloquea el marcado de conexiones y NO siempre se auto-sana → borrarlo; se regenera desde gossip.json.
  • Conexión al pub verificable sin herramientas: cat /proc/net/tcp dentro del contenedor y buscar la IP del pub en hex little-endian, estado 01 = ESTABLISHED.
  • Si EBT se estanca con la conexión caída y el scheduler no rediala: docker restart del contenedor fuerza el redial y EBT continúa desde el último seq.
  • El /dev/tcp/... de bash no existe en el sh (dash) del contenedor → falso "no conecta". Probar con curl telnet://host:puerto (exit 0/52/56 = conectó).
  • El perfil (nombre/avatar) son mensajes about DEL log: si la GUI muestra la clave truncada, no es pérdida de identidad — es que el log aún no tiene los abouts.

5. Healthcheck final ​

  • Contenedor healthy; la web muestra la versión esperada.
  • Oasis ID del boot = feed id de siempre.
  • Log local en el mismo seq que el pub (paso 4.4) y mensajes nuevos apendizando en seq siguientes (sin errores de fork en logs).
  • Perfil con nombre y avatar visibles.
  • Anotar en el journal (devops/logs/deploy-history.jsonl) qué se restauró, desde qué fuente y hasta qué seq.

6. Después: reducir la superficie de la próxima ​

  • Backup periódico VERIFICADO de secret + claves en medio externo (y probar la restauración).
  • El pub guarda tu feed: sincronizar el cliente con el pub a menudo ES el backup del log.
  • No acumular trabajo sin push: el working tree es un único punto de fallo.
  • Mantener .gitignore anidados en las carpetas de identidad (patrón * + whitelist de README/scripts) para que un git add -A de emergencia nunca pueda filtrar claves.