Protocolo del cliente (nodo personal)
Este repo es el sitio del cliente (
C:\S_LAB\o-sdk,https://github.com/alephscriptorium-eng/O_SDK.git). El cliente se levanta con el compose de la raíz (docker-compose.yml, proyectoo-sdk, serviciooasis-client); el pub, el HUB y el entorno scriptorium viven enpub/(proyectooasis-pub-scriptorium) y no comparten puertos, nombres, redes ni directorios devolumes-dev/con el cliente (ver §0).
Estado · ACTIVO 2026-09-17 13:37 UTC (WP-O98). Oasis 1.1.2, feed
@tMJzSfcZSNCsFRF3pl3rMoFDatz6VjDCjQ8/TpjYIRY=.ed25519(«Alephillo»), importado desde el cliente 0.8.8 deBlockchainComPort\volumes-dev\ssb-data(seq 44 al importar; 45 =oasisVersiondel sbot puro; 46 =karmaScorede la GUI;seq_pub= 46;Verification: forks 0). Manifiestovolumes-dev/ssb-data/.import-20260917-153020.txt; backupdevops/backups/client/20260917-153020/. Blobs del origen descartados (todos dañados por NUL): 4 propios pendientes de la red. Reporteplan/REPORTES/WP-O98-cliente-fresco-identidad.md. Instalación antigua retirada el mismo día (§7: contenedor, imagen, 6 volúmenes y 2 redes); el directorio físico queda como backup frío.
ECOin en el cliente · implementado en rama, drill pendiente (WP-O103, 2026-09-18): dirección o cartera propia, independiente del VPS. Procedimiento y avisos en §8.
Checklist operativo para dar de alta un cliente fresco, traer una identidad SSB existente sin bifurcar su feed, sincronizarlo con el pub, subirlo de versión, verificarlo y volver atrás. Deriva de UPGRADE-PROTOCOL.md (misma imagen, tres modos) y de RECOVERY-PROTOCOL.md §4 (regla de oro), y del primer alta+importación hecha en este repo (WP-O98, 2026-09-17).
Modelo mental. Solo hay una cosa irremplazable: el
secret. Y una cosa que conviene no perder: tu propio log (flume/log.offset), porque lo que el pub no tenga de tu feed no existe. Todo lo demás (índices flume,ebt/,conn.json,config, blobs) es derivable. Oasis no tiene guardia contra la bifurcación del feed: si la GUI arranca con tusecrety un log vacío, publica (en 1.1.2, el PM de bienvenida a los 3 s) un mensaje consequence: 1→ fork irreversible frente a lo que guarda el pub. Por eso importar = ficheros con el cliente parado + sbot puro hasta sincronizar + GUI al final.
0. Estado y convivencia con el pub local
docker compose -p o-sdk ps -a # cliente: oasis-client (+ ecoin-wallet con el perfil ecoin, §8)
docker compose -p oasis-pub-scriptorium ps -a # pub+HUB local: no debe ocupar 3000/8008 (usa 8009/8088/8443/8788 con pub/.env.local)
ls volumes-dev/ # cliente: ssb-data ai-models logs client-state · pub: oasis-pub oasis-hub teatro
docker volume inspect o-sdk_oasis-ssb-data-dev --format '{{index .Options "device"}}' # = <repo>\volumes-dev\ssb-data (o no existe aún)
docker volume ls --filter label=o-sdk.role=client-wallet # o-sdk-client-ecoin-data: la wallet.dat del cliente (§8), si se creó| Cliente | Pub + HUB (local) | |
|---|---|---|
| Compose / proyecto | docker-compose.yml · o-sdk | pub/docker-compose.pub.yml · oasis-pub-scriptorium |
| Contenedores | oasis-client, ecoin-wallet | oasis-pub-scriptorium, oasis-pub-hub, oasis-pub-hub-cache, oasis-pub-web, oasis-pub-panel-api |
| Puertos host | 3000, 8008 (ecoin-wallet no publica ninguno: RPC 7474 y P2P 7408 solo en la red del compose, §8) | con pub/.env.local: 8009, 8088, 8443, 8788, 3001 (maint-ui) |
volumes-dev/ | ssb-data, ai-models, logs, client-state (la wallet.dat no vive aquí: volumen externo o-sdk-client-ecoin-data, §8) | oasis-pub/*, oasis-hub/*, teatro |
| Red | o-sdk_oasis-network | oasis-pub-scriptorium_oasis_pub_net |
| Imagen | o-sdk-oasis-client (mismo Dockerfile) | oasis-pub-scriptorium:latest |
Regla: el pub local siempre con npm run pub:local:* (--env-file .env.local). Sin --env-file el compose del pub toma los defaults del VPS (8008/80/443) y choca con el cliente y con Windows.
1. Alta fresca (identidad nueva)
npm run build # imagen o-sdk-oasis-client (10-20 min la primera vez)
npm run setup # volumes-dev/{ssb-data,ai-models,logs,client-state/banking}; sin esto el bind falla
# Modelo IA (4,08 GB): tres opciones
# a) copiar uno existente a volumes-dev/ai-models/oasis-42-1-chat.Q4_K_M.gguf (head -c4 = GGUF; sha256 igual)
# b) dejar que el entrypoint lo descargue (3,8 GB, solarnethub.com) en el primer arranque
# c) sin IA: OASIS_SKIP_AI_MODEL=true en un docker-compose.override.yml no versionado
docker compose up -d oasis-client
docker compose logs -f oasis-client # parches 3/3 ✓ · [Version: X.Y.Z] · secret creado si no existíaQué hace el primer arranque: el entrypoint (root) crea y chowna .ssb, models, logs; escribe ~/.ssb/config si falta (cap aleatorio que src/configs/server-config.json pisa: el cap de red es el del repo); aplica 3 parches a node_modules; backend.js levanta el sbot embebido, que crea secret si no existe, la GUI en :3000 y (bajo demanda) la IA en :4001. La GUI publica el PM de bienvenida y crea oasis-first-contact.
Healthcheck del alta: §5. El primer día: npm run client:backup-keys y guarda la copia fuera de la máquina. npm run up = setup + up -d: no lo uses durante una importación (§2).
2. Importar una identidad existente
Con el cliente parado (docker compose stop oasis-client) y volumes-dev/ssb-data vacío (o --force, que lo aparta a ssb-data.pre-import-<ts> sin borrar nada):
npm run client:import-identity -- --from "C:/ruta/al/.ssb/viejo" --with-blobs --dry-run # verifica sin copiar
npm run client:import-identity -- --from "C:/ruta/al/.ssb/viejo" --with-blobs [--force]| Se copia (lista blanca) | Por qué |
|---|---|
secret | la identidad |
flume/log.offset | tu feed completo (y el de tus pares): elimina la ventana de fork por construcción |
gossip.json | direcciones/claves de pubs conocidos: el cliente vuelve a encontrar al pub solo |
keys/ | claves de tribus (no derivables) |
blobs/ (opcional) | avatares/adjuntos; content-addressed, re-descargables |
| No se copia | Por qué |
|---|---|
flume/* (índices), ebt/, blobs_push/ | vistas derivadas; las de otra versión hacen que el arranque muera con «Another Oasis instance is already running» (isLockError trata cualquier OpenError de leveldb como lock) o isCorruptStoreError |
conn.json | se regenera desde gossip.json; corrupto bloquea conexiones |
config | lo escribe el entrypoint; server-config.json manda (caps, connections, hops) |
socket, manifest.json, node_modules, *.nul-damaged-bak | residuos |
El script además: verifica el origen (secret con id = @public, sin bytes NUL; log.offset por frames con client/scripts/lib/inspect-log-offset.js → tailOk, badFrames: 0, mySeq), guarda un backup verificado en devops/backups/client/<ts>/ (sha256), copia, crea oasis-first-contact = <feed>\n<ISO>\nwelcome=done\n (formato onboarding_model.adopt; apaga el PM de bienvenida y el banner), verifica sha256 y frames tras la copia (y una muestra de blobs por hash) y deja el manifiesto volumes-dev/ssb-data/.import-<ts>.txt.
Después no arranques la GUI: §3.
Solo
secret(sin log): válido si no tienes el log (RECOVERY-PROTOCOL.mdmodelo mental: el pub guarda tu feed). El script exigelog.offset; para ese caso copia elsecreta mano, crea el flag igual y ve a §3: el sbot puro traerá tu feed del pub. Lo que el pub no tenga se pierde.
3. Sincronizar con el pub antes de la GUI (sbot puro)
npm run client:sync-only -- start # modo `server` del entrypoint: solo sbot, nada publica
docker logs -f oasis-sync-only # CONNECTED net:pub.escrivivir.co:8008 · sin "Another Oasis"/"corrupt"
npm run client:sync-only -- status --pub --watch # cada 30 s hasta SYNC-OK
npm run client:sync-only -- stop # exige log.offset estable 60 s; verifica el frame final
docker compose up -d oasis-client # ahora sí. (El propio sbot puro ya publicó `oasisVersion` en seq N+1 a los 7 s:
# SSB_server.js solo lo hace si el log NO está vacío; el pub lo acepta como continuación)start tiene un gate: si el sbot no se identifica como el feed del secret (ID-MISMATCH), se para solo (habría creado otra identidad sobre un montaje equivocado). status compara el seq propio según el fichero, según el sbot y según el pub (devops/scripts/pub-feed-seq.sh, solo lectura):
| Veredicto | Significa | Qué hacer |
|---|---|---|
| SYNC-OK | seq local = seq pub, log.offset estable ≥ 5 min, pub visto conectado | stop y arrancar la GUI |
| AHEAD | local > pub (publicaste offline) | normal: el sbot empuja al pub (EBT es bidireccional); repetir |
| BEHIND | local < pub | no arrancar la GUI; esperar; si no avanza en 10 min docker restart oasis-sync-only |
| PUB-UNKNOWN | el pub no tiene el feed ni lo sigue | npm run devops:invite -- 1 y SSB_INVITE='…' npm run client:sync-only -- invite (redime desde el sbot puro; publica un contact, legítimo porque local ≥ pub). Nunca join-prod-client.sh (necesita GUI) |
| ID-MISMATCH | el sbot no es tu feed | parar; revisar docker volume inspect o-sdk_oasis-ssb-data-dev |
La parada acaba en SIGKILL (PID 1 del contenedor es su): por eso stop exige estabilidad y comprueba el último frame con inspect-log-offset.js. log.offset es append-only: un kill sin escritura en curso es inocuo.
4. Upgrade del cliente
Misma imagen y mismo ciclo que el pub: UPGRADE-PROTOCOL.md §1-§3 (rama upgrade/oasis-X.Y.Z, overlay, guards). Para el cliente:
docker tag o-sdk-oasis-client o-sdk-oasis-client:<ver-vieja> # rollback preparado
npm run build && docker compose up -d oasis-client # .ssb, ai-models y client-state son binds: se preservan- Con el log intacto la GUI arranca directa: su
oasisVersioncae en seq N+1. - Si el upgrade obligó a apartar
flume/(índices corruptos):Settings › Rebuild database(POST /settings/rebuild, no tocalog.offset). Si se apartó el log entero, entonces §3 antes de la GUI. - La config de la GUI y el estado bancario viven en
volumes-dev/client-state(§8.4): sobreviven al rebuild, y las claves nuevas que traiga upstream enoasis-config.jsonentran por el default de la imagen. - Healthcheck: §5 (y
npm run client:ecoin:verifysi usas ECOin, §8). Journal: no aplica (el journal es del pub).
5. Healthcheck
docker ps --filter name=oasis-client --format '{{.Status}}' # healthy
curl -s http://localhost:3000/settings | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1 # versión
grep -o '"id": *"[^"]*"' volumes-dev/ssb-data/secret # = tu feed
npm run client:inspect-log -- '<tu-feed>' # tailOk, mySeq
npm run devops:pub-feed-seq -- '<tu-feed>' # seq_pub ≤ seq local (+ oasisVersion)
curl -s -X POST -H 'Referer: http://localhost:3000/settings' http://localhost:3000/settings/verify -o /dev/null && \
curl -s http://localhost:3000/settings | grep -i -A3 'verification' # sin gaps / broken links / forks (mine)
docker compose logs oasis-client | grep -c 'welcome-pm' # 0 tras una importación
npm run client:test-ai # POST /ai dentro del contenedorPerfil sin nombre/avatar tras importar no es pérdida de identidad: los about son mensajes del log; llegan con la replicación.
6. Rollback
- Upgrade fallido:
docker compose stop oasis-client→docker tag o-sdk-oasis-client:<ver-vieja> o-sdk-oasis-client→docker compose up -d --no-build oasis-client..ssbintacto (binds). - Importación fallida (log corrupto al arrancar,
ID-MISMATCH):sync-only stop --now; volver avolumes-dev/ssb-data.pre-import-<ts>(si lo había) o re-importar desde el backupdevops/backups/client/<ts>/; en último caso solosecret+ flag + §3. - Nunca truncar
log.offsetni tocarsecret. Ningún camino de este protocolo cambia elsecret.
7. Retirar instalaciones antiguas
Solo tras §5 en verde unos días y con backup verificado fuera de la máquina:
docker rm oasis-server-dev # contenedor del cliente antiguo (otro repo)
docker rmi alephscript-clean-oasis-dev # su imagen (4,36 GB)
docker volume rm alephscript-clean_oasis-ssb-data-dev alephscript-clean_oasis-ai-models-dev alephscript-clean_oasis-logs-dev \
blockchaincomport_oasis-ssb-data-dev blockchaincomport_oasis-ai-models-dev blockchaincomport_oasis-logs-dev
docker network rm alephscript-clean_oasis-network blockchaincomport_oasis-networkLos volúmenes con nombre son punteros a directorios bind: borrarlos no borra los datos. El directorio físico de origen se conserva como backup frío (solo lectura) hasta decisión del custodio; sus *.nul-damaged-bak (5,7 GB) son los primeros candidatos a borrar.
8. ECOin en el cliente
Estado · en
maindesde el 2026-09-18 (WP-O103). Ensayado de punta a punta con una identidad desechable (§8.9): montaje, publicación única, recreate, rebuild,down -v, backup y restore. Es un protocolo para cualquier habitante que quiera su cartera: no depende de ninguna identidad concreta. Publicar la dirección es siempre un acto manual, único y tuyo: nada de esta sección lo hace por ti. Doc hermana (el lado del banco):PUB/ECOIN-PROTOCOL.md. Asiento: D-O19. Se escribió sobre Oasis 1.1.2.Puesta al día para Oasis 1.1.4 (2026-09-19, WP-O108), ensayada con identidad desechable. Tres cosas cambiaron respecto a 1.1.2 y gobiernan esta sección: (1) con la cartera cableada, abrir la GUI publica tu dirección sola (§8.2); (2) el estado bancario vive en
~/.ssb/oasis/banking, dentro dessb-data(§8.4); (3) el banco se autodescubre (§8.7).
Modelo mental. Solo hay una cosa irremplazable: el
secret. Y una cosa que conviene no perder: tu propio log (flume/log.offset), porque lo que el pub no tenga de tu feed no existe. Todo lo demás (índices flume,ebt/,conn.json,config, blobs) es derivable. Oasis no tiene guardia contra la bifurcación del feed: si la GUI arranca con tusecrety un log vacío, publica (en 1.1.2, el PM de bienvenida a los 3 s) un mensaje consequence: 1→ fork irreversible frente a lo que guarda el pub. Por eso importar = ficheros con el cliente parado + sbot puro hasta sincronizar + GUI al final.
0. Estado y convivencia con el pub local
docker compose -p o-sdk ps -a # cliente: oasis-client (+ ecoin-wallet con el perfil ecoin, §8)
docker compose -p oasis-pub-scriptorium ps -a # pub+HUB local: no debe ocupar 3000/8008 (usa 8009/8088/8443/8788 con pub/.env.local)
ls volumes-dev/ # cliente: ssb-data ai-models logs client-state · pub: oasis-pub oasis-hub teatro
docker volume inspect o-sdk_oasis-ssb-data-dev --format '{{index .Options "device"}}' # = <repo>\volumes-dev\ssb-data (o no existe aún)
docker volume ls --filter label=o-sdk.role=client-wallet # o-sdk-client-ecoin-data: la wallet.dat del cliente (§8), si se creó| Cliente | Pub + HUB (local) | |
|---|---|---|
| Compose / proyecto | docker-compose.yml · o-sdk | pub/docker-compose.pub.yml · oasis-pub-scriptorium |
| Contenedores | oasis-client, ecoin-wallet | oasis-pub-scriptorium, oasis-pub-hub, oasis-pub-hub-cache, oasis-pub-web, oasis-pub-panel-api |
| Puertos host | 3000, 8008 (ecoin-wallet no publica ninguno: RPC 7474 y P2P 7408 solo en la red del compose, §8) | con pub/.env.local: 8009, 8088, 8443, 8788, 3001 (maint-ui) |
volumes-dev/ | ssb-data, ai-models, logs, client-state (la wallet.dat no vive aquí: volumen externo o-sdk-client-ecoin-data, §8) | oasis-pub/*, oasis-hub/*, teatro |
| Red | o-sdk_oasis-network | oasis-pub-scriptorium_oasis_pub_net |
| Imagen | o-sdk-oasis-client (mismo Dockerfile) | oasis-pub-scriptorium:latest |
Regla: el pub local siempre con npm run pub:local:* (--env-file .env.local). Sin --env-file el compose del pub toma los defaults del VPS (8008/80/443) y choca con el cliente y con Windows.
1. Alta fresca (identidad nueva)
npm run build # imagen o-sdk-oasis-client (10-20 min la primera vez)
npm run setup # volumes-dev/{ssb-data,ai-models,logs,client-state/banking}; sin esto el bind falla
# Modelo IA (4,08 GB): tres opciones
# a) copiar uno existente a volumes-dev/ai-models/oasis-42-1-chat.Q4_K_M.gguf (head -c4 = GGUF; sha256 igual)
# b) dejar que el entrypoint lo descargue (3,8 GB, solarnethub.com) en el primer arranque
# c) sin IA: OASIS_SKIP_AI_MODEL=true en un docker-compose.override.yml no versionado
docker compose up -d oasis-client
docker compose logs -f oasis-client # parches 3/3 ✓ · [Version: X.Y.Z] · secret creado si no existíaQué hace el primer arranque: el entrypoint (root) crea y chowna .ssb, models, logs; escribe ~/.ssb/config si falta (cap aleatorio que src/configs/server-config.json pisa: el cap de red es el del repo); aplica 3 parches a node_modules; backend.js levanta el sbot embebido, que crea secret si no existe, la GUI en :3000 y (bajo demanda) la IA en :4001. La GUI publica el PM de bienvenida y crea oasis-first-contact.
Healthcheck del alta: §5. El primer día: npm run client:backup-keys y guarda la copia fuera de la máquina. npm run up = setup + up -d: no lo uses durante una importación (§2).
2. Importar una identidad existente
Con el cliente parado (docker compose stop oasis-client) y volumes-dev/ssb-data vacío (o --force, que lo aparta a ssb-data.pre-import-<ts> sin borrar nada):
npm run client:import-identity -- --from "C:/ruta/al/.ssb/viejo" --with-blobs --dry-run # verifica sin copiar
npm run client:import-identity -- --from "C:/ruta/al/.ssb/viejo" --with-blobs [--force]| Se copia (lista blanca) | Por qué |
|---|---|
secret | la identidad |
flume/log.offset | tu feed completo (y el de tus pares): elimina la ventana de fork por construcción |
gossip.json | direcciones/claves de pubs conocidos: el cliente vuelve a encontrar al pub solo |
keys/ | claves de tribus (no derivables) |
blobs/ (opcional) | avatares/adjuntos; content-addressed, re-descargables |
| No se copia | Por qué |
|---|---|
flume/* (índices), ebt/, blobs_push/ | vistas derivadas; las de otra versión hacen que el arranque muera con «Another Oasis instance is already running» (isLockError trata cualquier OpenError de leveldb como lock) o isCorruptStoreError |
conn.json | se regenera desde gossip.json; corrupto bloquea conexiones |
config | lo escribe el entrypoint; server-config.json manda (caps, connections, hops) |
socket, manifest.json, node_modules, *.nul-damaged-bak | residuos |
El script además: verifica el origen (secret con id = @public, sin bytes NUL; log.offset por frames con client/scripts/lib/inspect-log-offset.js → tailOk, badFrames: 0, mySeq), guarda un backup verificado en devops/backups/client/<ts>/ (sha256), copia, crea oasis-first-contact = <feed>\n<ISO>\nwelcome=done\n (formato onboarding_model.adopt; apaga el PM de bienvenida y el banner), verifica sha256 y frames tras la copia (y una muestra de blobs por hash) y deja el manifiesto volumes-dev/ssb-data/.import-<ts>.txt.
Después no arranques la GUI: §3.
Solo
secret(sin log): válido si no tienes el log (RECOVERY-PROTOCOL.mdmodelo mental: el pub guarda tu feed). El script exigelog.offset; para ese caso copia elsecreta mano, crea el flag igual y ve a §3: el sbot puro traerá tu feed del pub. Lo que el pub no tenga se pierde.
3. Sincronizar con el pub antes de la GUI (sbot puro)
npm run client:sync-only -- start # modo `server` del entrypoint: solo sbot, nada publica
docker logs -f oasis-sync-only # CONNECTED net:pub.escrivivir.co:8008 · sin "Another Oasis"/"corrupt"
npm run client:sync-only -- status --pub --watch # cada 30 s hasta SYNC-OK
npm run client:sync-only -- stop # exige log.offset estable 60 s; verifica el frame final
docker compose up -d oasis-client # ahora sí. (El propio sbot puro ya publicó `oasisVersion` en seq N+1 a los 7 s:
# SSB_server.js solo lo hace si el log NO está vacío; el pub lo acepta como continuación)start tiene un gate: si el sbot no se identifica como el feed del secret (ID-MISMATCH), se para solo (habría creado otra identidad sobre un montaje equivocado). status compara el seq propio según el fichero, según el sbot y según el pub (devops/scripts/pub-feed-seq.sh, solo lectura):
| Veredicto | Significa | Qué hacer |
|---|---|---|
| SYNC-OK | seq local = seq pub, log.offset estable ≥ 5 min, pub visto conectado | stop y arrancar la GUI |
| AHEAD | local > pub (publicaste offline) | normal: el sbot empuja al pub (EBT es bidireccional); repetir |
| BEHIND | local < pub | no arrancar la GUI; esperar; si no avanza en 10 min docker restart oasis-sync-only |
| PUB-UNKNOWN | el pub no tiene el feed ni lo sigue | npm run devops:invite -- 1 y SSB_INVITE='…' npm run client:sync-only -- invite (redime desde el sbot puro; publica un contact, legítimo porque local ≥ pub). Nunca join-prod-client.sh (necesita GUI) |
| ID-MISMATCH | el sbot no es tu feed | parar; revisar docker volume inspect o-sdk_oasis-ssb-data-dev |
La parada acaba en SIGKILL (PID 1 del contenedor es su): por eso stop exige estabilidad y comprueba el último frame con inspect-log-offset.js. log.offset es append-only: un kill sin escritura en curso es inocuo.
4. Upgrade del cliente
Misma imagen y mismo ciclo que el pub: UPGRADE-PROTOCOL.md §1-§3 (rama upgrade/oasis-X.Y.Z, overlay, guards). Para el cliente:
docker tag o-sdk-oasis-client o-sdk-oasis-client:<ver-vieja> # rollback preparado
npm run build && docker compose up -d oasis-client # .ssb, ai-models y client-state son binds: se preservan- Con el log intacto la GUI arranca directa: su
oasisVersioncae en seq N+1. - Si el upgrade obligó a apartar
flume/(índices corruptos):Settings › Rebuild database(POST /settings/rebuild, no tocalog.offset). Si se apartó el log entero, entonces §3 antes de la GUI. - La config de la GUI y el estado bancario viven en
volumes-dev/client-state(§8.4): sobreviven al rebuild, y las claves nuevas que traiga upstream enoasis-config.jsonentran por el default de la imagen. - Healthcheck: §5 (y
npm run client:ecoin:verifysi usas ECOin, §8). Journal: no aplica (el journal es del pub).
5. Healthcheck
docker ps --filter name=oasis-client --format '{{.Status}}' # healthy
curl -s http://localhost:3000/settings | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1 # versión
grep -o '"id": *"[^"]*"' volumes-dev/ssb-data/secret # = tu feed
npm run client:inspect-log -- '<tu-feed>' # tailOk, mySeq
npm run devops:pub-feed-seq -- '<tu-feed>' # seq_pub ≤ seq local (+ oasisVersion)
curl -s -X POST -H 'Referer: http://localhost:3000/settings' http://localhost:3000/settings/verify -o /dev/null && \
curl -s http://localhost:3000/settings | grep -i -A3 'verification' # sin gaps / broken links / forks (mine)
docker compose logs oasis-client | grep -c 'welcome-pm' # 0 tras una importación
npm run client:test-ai # POST /ai dentro del contenedorPerfil sin nombre/avatar tras importar no es pérdida de identidad: los about son mensajes del log; llegan con la replicación.
6. Rollback
- Upgrade fallido:
docker compose stop oasis-client→docker tag o-sdk-oasis-client:<ver-vieja> o-sdk-oasis-client→docker compose up -d --no-build oasis-client..ssbintacto (binds). - Importación fallida (log corrupto al arrancar,
ID-MISMATCH):sync-only stop --now; volver avolumes-dev/ssb-data.pre-import-<ts>(si lo había) o re-importar desde el backupdevops/backups/client/<ts>/; en último caso solosecret+ flag + §3. - Nunca truncar
log.offsetni tocarsecret. Ningún camino de este protocolo cambia elsecret.
7. Retirar instalaciones antiguas
Solo tras §5 en verde unos días y con backup verificado fuera de la máquina:
docker rm oasis-server-dev # contenedor del cliente antiguo (otro repo)
docker rmi alephscript-clean-oasis-dev # su imagen (4,36 GB)
docker volume rm alephscript-clean_oasis-ssb-data-dev alephscript-clean_oasis-ai-models-dev alephscript-clean_oasis-logs-dev \
blockchaincomport_oasis-ssb-data-dev blockchaincomport_oasis-ai-models-dev blockchaincomport_oasis-logs-dev
docker network rm alephscript-clean_oasis-network blockchaincomport_oasis-networkLos volúmenes con nombre son punteros a directorios bind: borrarlos no borra los datos. El directorio físico de origen se conserva como backup frío (solo lectura) hasta decisión del custodio; sus *.nul-damaged-bak (5,7 GB) son los primeros candidatos a borrar.
8. ECOin en el cliente
Estado · en
maindesde el 2026-09-18 (WP-O103). Ensayado de punta a punta con una identidad desechable (§8.9): montaje, publicación única, recreate, rebuild,down -v, backup y restore. Es un protocolo para cualquier habitante que quiera su cartera: no depende de ninguna identidad concreta. Publicar la dirección es siempre un acto manual, único y tuyo: nada de esta sección lo hace por ti. Doc hermana (el lado del banco):PUB/ECOIN-PROTOCOL.md. Asiento: D-O19. Se escribió sobre Oasis 1.1.2.AVISO · Oasis 1.1.4 (2026-09-19). El árbol
src/demainya es 1.1.4 y esta sección todavía no: §8.3, §8.4 y §8.7 describen un cableado que upstream cambió (el estado bancario vive en~/.ssb/oasis/banking,src/configs/wallet-addresses.jsonya no existe,walletPub.pubIddesapareció y el banco se autodescubre por los anuncios de los pubs). No reconstruyas un cliente con cartera sobre 1.1.4 hasta WP-O108: conOASIS_BANKING_DIRdefinido habría dos mapas de direcciones y riesgo de publicar una segunda dirección. Un cliente sin cartera (wallet.urlvacía) no corre ese riesgo.
Modelo mental. Oasis lee la cartera solo de
src/configs/oasis-config.json(wallet.{url,user,pass,fee}); las variablesECOIN_RPC_*no las lee nadie salvo el entrypoint, que las escribe ahí. Un mensaje SSBwalletcon tu dirección es permanente y solo sirve si conservas lawallet.datque tiene su clave: una dirección publicada sin copia de su cartera queda huérfana para siempre. Por eso el orden es siempre cartera → backup → publicar. En 1.1.4 «publicar» ocurre al abrir la GUI: el backup va antes de ese momento.
El cliente tiene ECOin independiente del VPS, en dos niveles. El valor por defecto es el primero sin nada cableado: wallet.url = "", ningún RPC saliente, /banking sin latencia.
| (i) Solo dirección | (ii) Cartera propia | |
|---|---|---|
| Sirve para | aparecer, recibir y reclamar RBU | además saldo, envíos e historial (/wallet/* usa RPC) |
ecoind propio | solo el rato de generar la dirección | corriendo siempre (perfil ecoin) |
.env raíz | COMPOSE_PROFILES= · ECOIN_RPC_URL= | COMPOSE_PROFILES=ecoin · ECOIN_RPC_URL=http://ecoin-wallet:7474 |
| Quién paga la RBU | el ecoind del banco (bot-2), nunca el tuyo | ídem |
| Coste | ninguno en reposo | ~50 MB de cadena, ~13 min de sync inicial (medido en el VPS), hasta 512 MB de memoria |
En los dos niveles la dirección sale de una wallet.dat tuya. Nunca publiques una dirección cuya cartera no controlas ni tienes respaldada.
8.1 Nivel (i): solo dirección
Se arranca ecoind sin cablear Oasis (ECOIN_RPC_URL vacía), se saca una dirección, se respalda la cartera, se para, y se da de alta la dirección a mano una vez.
npm run ecoin:build # antepone ecoin:fetch-deb (sha256 del .deb verificado; imagen de WP-O102)
npm run client:ecoin:init -- --mode address # .env raíz con credenciales generadas + volumen externo o-sdk-client-ecoin-data
npm run ecoin:up # ecoin-init.sh --ensure + up -d --wait ecoin-wallet (healthy)
npm run ecoin:address # getnewaddress → APÚNTALA. Cada llamada genera OTRA: llámalo una vez
npm run client:wallet:backup # devops/backups/client-wallet/<TS>/ con sha256 y manifiesto (§8.6)
npm run ecoin:stop # la wallet.dat queda en el volumen; no hace falta que siga corriendoDespués, con la GUI arriba, una sola vez: http://localhost:3000/banking?filter=addresses → añadir tu feed y la dirección apuntada. Ese formulario (POST /banking/addresses) publica el mensaje wallet sin RPC y no es idempotente: cada envío publica otro mensaje permanente. Comprueba después con npm run client:ecoin:verify que hay exactamente 1 mensaje wallet en tu feed.
Copia el backup fuera de la máquina antes de publicar. Para pasar más adelante al nivel (ii) basta client:ecoin:init -- --mode own: la cartera es la misma y la dirección publicada sigue valiendo.
8.2 Nivel (ii): cartera propia
npm run ecoin:build
npm run client:ecoin:init -- --mode own # COMPOSE_PROFILES=ecoin · ECOIN_RPC_URL=http://ecoin-wallet:7474
npm run ecoin:up # healthy (hasta ~70 s); sincroniza la cadena en segundo plano
npm run client:wallet:backup # ANTES de abrir la GUI
docker compose up -d oasis-client # recrea el cliente: el entrypoint cablea wallet.url/user/pass (§8.3)
npm run client:ecoin:verify # RPC por DNS de servicio, config cableada, estado, nº de mensajes walletAbrir la GUI publica tu dirección (medido en 1.1.4). Con
wallet.urlrespondiendo RPC, la primera visita a/o/wallet(en el cliente real/bankingsola no bastó) pide una dirección a tuecoind(getnewaddress), la guarda en~/.ssb/oasis/banking/wallet-addresses.jsony publica el mensajewalleten tu feed: irreversible y sin preguntar. Ensayo: 0 → 1 mensaje tras los primeros GET; sigue en 1 tras más visitas, un--force-recreatey nuevas visitas (upstream compruebahasPublishedAddress). Consecuencias:
- El
npm run client:wallet:backupva antes de abrir la GUI. Vale: la dirección sale del keypool de la cartera (claves pregeneradas), así que ese backup ya contiene su clave. Haz otro después.- No des de alta la dirección a mano en
/banking?filter=addresses: ya está publicada y ese POST no es idempotente (publicaría un segundo mensaje).- Si no quieres publicar todavía, no cablees la cartera:
wallet.urlvacía = nada de esto ocurre.- Compruébalo:
npm run client:ecoin:verify -- --expect-wallet-msgs 1.
8.3 Qué hace el entrypoint con cada variable
docker-entrypoint.sh (raíz; zona wholesale, fuera de los guards de src/) solo actúa si OASIS_CLIENT_STATE_DIR está definido y el modo no es server. El pub, el HUB y bot-2 no lo definen: para ellos nada cambia. Orden: persist_client_state → wire_wallet_config → setup_oasis_config.
| Variable | Qué hace el entrypoint | Clave de oasis-config.json |
|---|---|---|
OASIS_CLIENT_STATE_DIR (/app/state) | activa todo lo de esta tabla; crea y chowna el directorio; fusiona el default de la imagen con el oasis-config.json persistido (gana lo persistido) y deja src/configs/oasis-config.json como symlink al resultado | el fichero entero |
OASIS_BANKING_DIR | retirada en 1.1.4: no la definas. banking_model.js la honra y backend.js no: tendrías dos mapas de direcciones. El entrypoint la desactiva (unset) y, si encuentra un banking/ de 1.1.2 en el state dir, lo copia una vez a ~/.ssb/oasis/banking/ sin pisar | — |
ECOIN_RPC_URL | si está definida, aunque vacía, se asigna tal cual; pasa antes por la guarda anti-remoto | wallet.url |
ECOIN_RPC_USER | se asigna junto a la URL; nunca se imprime | wallet.user |
ECOIN_RPC_PASS | se asigna junto a la URL; nunca se imprime | wallet.pass |
OASIS_WALLET_FEE | opcional; si viene, se asigna | wallet.fee |
OASIS_WALLET_PUB_ID | retirada en 1.1.4: el banco se autodescubre (§8.7). Si queda una clave walletPub en la config persistida, el entrypoint la borra | — |
ECOIN_RPC_ALLOW_REMOTE | solo el valor i-know desactiva la guarda anti-remoto | — |
OASIS_WALLET_WIRING | manual → el entrypoint no toca wallet.* | — |
ECOIN_MEM_LIMIT | no la lee el entrypoint: es el mem_limit de ecoin-wallet en el compose (512m por defecto) | — |
COMPOSE_PROFILES | no la lee el entrypoint: ecoin hace que docker compose up -d levante también ecoin-wallet | — |
- Guarda anti-remoto. El host de
ECOIN_RPC_URLdebe serecoin-wallet,localhost,127.0.0.1ohost.docker.internal. Cualquier otro → error en el log ywallet.url = "". - Env manda. Lo que teclees en
/settings/walletse pisa en el siguiente arranque con lo que diga el entorno. Es deliberado: la GUI no sabe vaciarwallet.url(solo el entrypoint puede poner"") y así las credenciales viven en un único sitio, el.envraíz (ignorado por git). Escape:OASIS_WALLET_WIRING=manualy gestionas la cartera desde la GUI bajo tu responsabilidad. - El resto de la config de la GUI (tema, idioma, módulos) no se pisa: se persiste (§8.4).
8.4 Estado persistente: client-state y ssb-data/oasis
Dos sitios, los dos persistentes:
./volumes-dev/client-state→/app/state: solooasis-config.json(config de la GUI + cableado de la cartera). La GUI lo reescribe entero desde 11 rutas: un bind de solo lectura la rompería, ysrc/configs/contiene código y guards, así que se persiste solo el fichero, por symlink;setup_oasis_configestá en node porquesed -irompe los symlinks. Lleva las credenciales RPC:volumes-dev/está ignorado por git.ssb-data/oasis/**(desde Oasis 1.1.3,src/configs/state-manager.js):banking/(mapa de direcciones, épocas, libro de pagos),flags/,content/,peers/,keys/… Upstream lo migra solo al arrancar desde~/.ssb/ysrc/configs/. Viaja con tu identidad: el backup dessb-dataya lo incluye, yclient:import-identity -- --forcelo aparta junto con ella. La cartera no se toca.
Si vienes de un cliente 1.1.2 con client-state/banking/, el entrypoint lo copia una vez al sitio nuevo y conserva el original.
8.5 La wallet.dat: volumen nombrado externo
wallet.dat y la cadena viven en el volumen Docker o-sdk-client-ecoin-data (etiqueta o-sdk.role=client-wallet), declarado external en el compose. Ya no existe volumes-dev/ecoin-data.
- Por qué no un bind: Berkeley DB sobre un bind de Windows (virtiofs) es frágil y lento; una
wallet.datcorrupta es dinero perdido. - Por qué
external:docker compose down -vno borra volúmenes externos. Lo creaclient:ecoin:init, no el compose; el cliente arranca igual sin el perfil y sin que el volumen exista. - Contrapartida: un volumen nombrado vive dentro de la VM de Docker Desktop (WSL2) y se pierde con un factory reset. El backup al host (§8.6) es obligatorio, no opcional.
ecoin-walletno publica puertos: el RPC (7474) y el P2P (7408, solo saliente) quedan eno-sdk_oasis-network. El 12000 desaparece. Quien quiera P2P entrante añade7408:7408en undocker-compose.override.ymlno versionado. El RPC no se publica nunca.- Credenciales: generadas por
client:ecoin:init(usuariooasis-+ 8 hex, contraseña de 48 hex) en el.envraíz; la imagen arranca fail-closed (ECOIN_REQUIRE_CREDS=1rechaza vacío yecoinrpc).ecoin:info,ecoin:balanceyecoin:addressno llevan credenciales en la línea de comandos (ecoindlas lee de su conf) yecoin:systemno vuelcarpcpassword.
8.6 Backup y restore de la cartera
npm run client:wallet:backup # en caliente: backupwallet → docker cp → sha256 contenedor = host
npm run client:wallet:backup -- --cold # con ecoind parado
npm run client:wallet:restore -- devops/backups/client-wallet/<TS> # aparta la actual a wallet.dat.pre-restore-<TS>; nunca sobrescribeCada backup deja en devops/backups/client-wallet/<TS>/ la cartera (wallet-<TS>.dat), SHA256SUMS.txt, MANIFEST.json (getinfo.blocks, direcciones, imagen, volumen) y RESTORE.txt; npm run client:wallet:backup -- --verify seguido del directorio recomprueba los sha256 sin tocar Docker. El restore exige que el contenedor exista (npm run ecoin:up al menos una vez). Tras un restore: validateaddress de la dirección publicada debe dar ismine: true.
- La copia no está cifrada y es material de claves: fuera de git (ya ignorado), fuera de cualquier imagen y con una copia fuera de la máquina.
- Keypool: una copia antigua solo conoce las claves pregeneradas hasta ese momento. Haz backup después de cada tanda de direcciones nuevas (y tras abrir la GUI por primera vez en el nivel ii).
- Cuándo: antes de publicar la dirección, antes de un upgrade, antes de cualquier
down. npm run client:backup-keysrespalda la identidad SSB, no la cartera (avisa si el volumen existe).
8.7 Tu banco: se descubre solo
Desde Oasis 1.1.3 no se configura ningún banco. Tu cliente mira los anuncios pubAvailability que ha replicado y elige: primero el pub del invite de SolarNetHub si está disponible y reciente; si no, el anuncio disponible más nuevo (caducan a los 3 días). /banking muestra la lista de pubs con su pool y un botón para donar. OASIS_WALLET_PUB_ID y --pub-id ya no hacen nada.
- Para ver un pub en la lista tienes que replicar su anuncio: síguelo o ten a alguien a tus saltos que lo siga. El de la casa es
@ecoin.escrivivir.co(PUB/INSTANCIA-SCRIPTORIUM.md), encendido desde el 2026-09-19. - Cada claim es un mensaje
ubiClaim: irreversible. Lo paga cualquier pub con el motor encendido y fondos que te vea y te considere elegible: feed con ≥ 30 días, dirección publicada y actividad. - Quien paga es el
ecoinddel pub hacia tu dirección publicada: para cobrar basta el nivel (i).
8.8 Avisos
- Nunca apuntes
ECOIN_RPC_URLalecoinddel VPS. Su RPC es HTTP plano y es un monedero único sin cuentas: tus credenciales viajarían en claro y quien las tenga maneja los fondos del banco. La guarda anti-remoto lo rechaza;ECOIN_RPC_ALLOW_REMOTE=i-knowexiste para laboratorio, no para esto. - Prohibido
docker system prune --volumes(ydocker volume prune): es lo único que borra el volumen externo con tuwallet.datsiecoin-walletestá parado.npm run downDELETEVOLSynpm run cleanDELETEVOLSpasan antes porclient/scripts/guard-destroy.sh: lista qué sobrevive, exige escribirBORRARy se niega si haywallet.datsin backup de menos de 24 h. - Los mensajes SSB son permanentes:
wallet(tu dirección) yubiClaimno se borran ni se editan. Una dirección equivocada o duplicada se queda en tu feed y en el pub. - Abrir la GUI con el nivel (ii) activo llama a
getnewaddress(§8.2): backup antes. .envraíz: lo comparte todo el compose de la raíz. El pub usa su propio--env-filey no lo lee.- Al pasar de cartera propia a solo dirección, para antes
ecoind:npm run ecoin:stop. Undocker compose downsin--profile ecoinno ve el servicioecoin-wallet: lo deja corriendo y la red queda «still in use». - En modo solo dirección
wallet.urlqueda vacía, perowallet.userywallet.passsiguen envolumes-dev/client-state/oasis-config.json: inocuo (no hay RPC), pero es un fichero con secretos.
Medido en el drill del 2026-09-18 (identidad desechable, proyecto o-sdk-drill): el healthcheck del contenedor pide / sin seguir el 302 y no genera direcciones; la dirección la fija la primera visita a /wallet, /banking o /activity (un navegador que abre / sí sigue la redirección). En el drill ecoind reutilizó su dirección por defecto (0 nuevas); en el VPS creó dos: no des por fijo ese número. La publicación automática no ocurre en 1.1.2 (fallo de upstream): se publica con el alta manual de §8.1, una sola vez. Tras recrear el contenedor, reconstruir la imagen y down -v siguieron la misma dirección, la config de la GUI y un único mensaje wallet; backup y restore devolvieron ismine: true.
8.9 Drill con identidad desechable
Todo lo anterior se ensaya antes con una identidad que no es la tuya, en un proyecto compose aparte que no comparte nada con el cliente real: proyecto o-sdk-drill, contenedores oasis-client-drill y ecoin-wallet-drill, puertos 3100:3000 y 8108:8008, binds en volumes-dev/drill/*, volumen externo o-sdk-drill-ecoin-data, env-file client/.env.drill (no versionado), sin GPU y con OASIS_SKIP_AI_MODEL=true. La identidad se crea vacía, con gossip.json vacío y sin invite: no replica con nadie y no ensucia la red.
# SIEMPRE desde la raíz del repo, con los DOS -f (el drill es un override del compose raíz) y el --env-file
export MSYS_NO_PATHCONV=1
D="docker compose -p o-sdk-drill -f docker-compose.yml -f client/docker-compose.drill.yml --env-file client/.env.drill"
mkdir -p volumes-dev/drill/{ssb-data,ai-models,logs,client-state/banking}
[ -f volumes-dev/drill/ssb-data/gossip.json ] || echo "[]" > volumes-dev/drill/ssb-data/gossip.json
ECOIN_ENV_FILE=client/.env.drill ECOIN_VOLUME=o-sdk-drill-ecoin-data ECOIN_STATE_DIR=volumes-dev/drill/client-state \
npm run client:ecoin:init -- --mode own # credenciales y volumen del drill, no los reales
$D up -d --build
CLIENT_CONTAINER=oasis-client-drill ECOIN_CONTAINER=ecoin-wallet-drill HOST_GUI_PORT=3100 npm run client:ecoin:verify
ECOIN_CONTAINER=ecoin-wallet-drill npm run client:wallet:backup
ECOIN_VOLUME=o-sdk-drill-ecoin-data ECOIN_CONTAINER=ecoin-wallet-drill GUARD_DATA_DIR=volumes-dev/drill \
bash client/scripts/guard-destroy.sh && $D down -v # el volumen externo sobrevive| # | Qué se comprueba en el drill |
|---|---|
| D1 | ecoin:fetch-deb y build |
| D2 | docker port ecoin-wallet-drill vacío; el host no alcanza 7474/7408/12000; RPC 200 por DNS de servicio desde oasis-client-drill; ecoinrpc → 401 |
| D3 | config cableada y symlinks (client:ecoin:verify) |
| D4 | /wallet con saldo 0 y dirección; publicar una vez; exactamente 1 mensaje wallet |
| D5 | --force-recreate y rebuild: sobreviven tema, config de cartera y mapa; sigue habiendo 1 mensaje |
| D6 | down -v con guard: wallet.dat intacta |
| D7 | backup → volumen nuevo (otra dirección) → restore → ismine: true |
| D8 | sin perfil: arranca, /banking sin latencia, cableado omitido |
| D9 | guarda anti-remoto: URL remota → wallet.url = "" |
Puerta: solo con D1-D9 en verde y confirmación expresa del custodio se pasa a la identidad real, en este orden: client:backup-keys → client:ecoin:init → ecoin:up → backup de cartera antes de publicar → publicar una vez → 1 mensaje wallet en el feed y replicado al pub → después, --pub-id de bot-2. El primer claim real es otra confirmación aparte.
8.10 La GUI desde el navegador del host: el puente de loopback
Oasis solo acepta las acciones sensibles (reclamar o rechazar la RBU, Wallet, Settings, update) si la petición llega desde 127.0.0.1 (isLoopbackRequest, src/backend/backend.js): upstream da por hecho que la GUI corre en la misma máquina que el backend. En el cliente dockerizado el navegador del host entra por el mapeo de puertos y el backend ve la IP del bridge: 403 con el cuerpo vacío («Se denegó el acceso a localhost»). Apareció el 2026-09-19 con el primer «Claim UBI» pulsado en la UI; hasta entonces esas peticiones se habían hecho siempre desde dentro del contenedor.
Sin tocar src/: con OASIS_LOOPBACK_PROXY_PORT=3001 el entrypoint arranca un reenviador TCP :3001 → 127.0.0.1:3000, y el compose publica la GUI como 127.0.0.1:3000:3001. El backend ve un cliente local y la garantía de «solo esta máquina» se conserva en el host. Nunca publiques ese puerto en 0.0.0.0: darías a toda la LAN las acciones que Oasis reserva al dueño del nodo. Pub, HUB y bots no definen la variable.
Comprobar: docker port oasis-client → 3001/tcp -> 127.0.0.1:3000; el log dice «Puente de loopback»; y en Banking → UBI, «Claim UBI» deja de dar 403. Un reclamo es un mensaje ubiClaim al mes, irreversible; el botón de al lado, «Refuse UBI», renuncia a la del mes y tampoco se deshace. Tras pulsar, la página puede no refrescarse: cuenta el mensaje en el feed antes de volver a pulsar.
9. Piezas
| Pieza | Fichero |
|---|---|
| Compose del cliente | docker-compose.yml (raíz) |
| Directorios de volúmenes | client/scripts/setup.sh (npm run setup) |
| Importar identidad | client/scripts/import-identity.sh (npm run client:import-identity) |
| Integridad y seq del log | client/scripts/lib/inspect-log-offset.js (npm run client:inspect-log) |
| Sbot puro / sincronización | client/scripts/sync-only.sh (npm run client:sync-only) |
| Sonda dentro del contenedor | pub/tools/ssb-probe.js (por stdin; pub, HUB o cliente; siempre docker exec -u oasis -e HOME=/home/oasis) |
| Seq de un feed en el pub | devops/scripts/pub-feed-seq.sh (npm run devops:pub-feed-seq) |
| Backup de identidad | client/scripts/backup-keys.sh (npm run client:backup-keys) |
| Prueba de IA | client/scripts/test-ai-service.sh (npm run client:test-ai) |
| ECOin: credenciales, volumen y modo | client/scripts/ecoin-init.sh (npm run client:ecoin:init; --mode address o --mode own, --pub-id, --ensure) |
| ECOin: backup y restore de la cartera | client/scripts/backup-wallet.sh (npm run client:wallet:backup, npm run client:wallet:restore) |
| ECOin: verificación de los CA | client/scripts/ecoin-verify.sh (npm run client:ecoin:verify) |
| ECOin: guarda de borrado | client/scripts/guard-destroy.sh (antes de downDELETEVOLS y cleanDELETEVOLS) |
| ECOin: cableado de la config | docker-entrypoint.sh (persist_client_state, wire_wallet_config, setup_oasis_config) |
| ECOin: plantilla de entorno | .env.example (raíz) → .env (ignorado) |
| ECOin: drill | client/docker-compose.drill.yml + client/.env.drill (no versionado) |
ECOin: imagen de ecoind | ecoin/ (endurecida en WP-O102; se reutiliza tal cual) |
| Identidad GPG del usuario | client/identity/init-gpg-key.sh |