Inicio / Guía / Sincronización

Sincronización

Desktop y móvil, siempre al día. QR pairing, E2EE, relay.

Sincronización — Desktop y móvil, siempre al día

Monolith Desktop es la fuente de verdad. El Companion recibe tus datos por WebSocket cifrado de extremo a extremo. El servidor de relay nunca ve tu información — solo reenvía bytes entre tus dispositivos.

¿Para qué? Trabajar en el móvil (capturar ideas, ver tu agenda, usar el Pulso) sin perder nada. Todo vuelve al Desktop.


Cómo funciona

Desktop (vault .md)

Desktop Electron App
    ↕ (DaySnapshot / deltas cifrados)
Relay Server (WebSocket)

Companion App (SQLite cache)
  1. Desktop envía un DaySnapshot con todo tu día: foco, tareas, ecos, inbox, rocas, pulso activo
  2. Companion recibe y aplica los datos a su caché SQLite
  3. Companion envía deltas cuando haces cambios: completar subtarea, agregar a inbox, iniciar pulso
  4. Desktop recibe y aplica los deltas a tu vault

Pairing por QR

  1. En Desktop: Settings > Sync > “Generar QR”
  2. En Companion: abre la app y apunta la cámara al QR
  3. El pairing se establece automáticamente
  4. Los datos comienzan a sincronizar

¿Qué se intercambia? Una clave de sesión cifrada. El relay solo ve el session_id (un UUID), nunca el contenido.


Seguridad

CapaQué protege
NaCl secretboxCifrado simétrico de todos los mensajes WebSocket
Session keyDerivada del QR, almacenada en SecureStore (Companion)
Relay statelessNo persiste mensajes, no guarda datos, no parsea contenido
E2EEEl servidor solo reenvía bytes cifrados. Nunca ve texto plano

El relay

Monolith Relay es un servidor WebSocket que conecta el Desktop con el Companion. Los mensajes viajan cifrados de extremo a extremo — el relay solo reenvía bytes entre pares vinculados. Cero dependencias (puro Bun runtime).

Repositorio de GitHub: monolith-relay

Arquitectura

Desktop ──WS──> Monolith Relay ──WS──> Companion
                    |
               /state HTTP API
                    |
          (Pulse state cache)
  • Sesiones: Dos dispositivos (desktop + companion) comparten una sesión identificada por un UUID v4.
  • Pairing: El primer dispositivo se une (esperando). El segundo se une (vinculado). Ambos reciben session_paired.
  • Reenvío: Los mensajes de un peer se reenvían al otro. Si el peer está offline, los mensajes se encolan (FIFO, máximo 1000).
  • State API: El Desktop publica el estado del Pulso vía HTTP POST /state. El Companion lo lee vía GET /state (para el widget sin necesidad de WebSocket).
  • Reconexión: Si un dispositivo se reconecta con el mismo device_id, el socket anterior se reemplaza (código 4014).
  • Revocación: Un dispositivo puede enviar revoke para desvincular la sesión permanentemente.
  • Unlink: Un dispositivo puede enviar unlink para desconectar sin revocar.

Quick Start

git clone https://github.com/OzkrRouj/monolith-relay
cd monolith-relay
bun install
bun run dev    # desarrollo con --watch
bun run start  # producción

Variables de entorno

VariableDefaultDescripción
PORT3005Puerto de escucha del relay
STATE_AUTH_SECRET''Si se define, HTTP /state requiere header X-Monolith-Secret

Despliegue (Dokploy)

  1. Push del repo a GitHub
  2. En Dokploy, crear nuevo servicio → apuntar al repo
  3. Variables de entorno: PORT=3005, STATE_AUTH_SECRET=<tu-secreto>
  4. Exponer puerto 3005 (Traefik maneja SSL)

Protocolo

Join

El cliente envía inmediatamente después de conectar WebSocket:

{ "type": "join", "session_id": "<uuid-v4>", "device_id": "<unique-per-device>", "version": 1 }

Mensajes del relay (servidor → cliente)

TipoCuándo
session_pairedSegundo dispositivo se unió, o primero en espera
peer_connectedPeer (re)conectado
peer_disconnectedPeer desconectado
peer_revokedSesión revocada por el otro peer
device_unlinkedPeer envió unlink
server_shutdownServidor cerrándose (graceful)

Mensajes del cliente

TipoAcción
joinIdentificarse y crear/unirse a sesión
unlinkDesconexión graceful sin revocar
revokeRevocar sesión permanentemente (el otro peer recibe peer_revoked)

Todos los demás mensajes se reenvían transparentemente al peer.

HTTP Endpoints

EndpointDescripción
GET /healthEstado del servidor, versión, sesiones activas
GET /state?sessionId=<uuid>Estado cacheado del Pulso para una sesión (requiere X-Monolith-Secret si STATE_AUTH_SECRET está definido)
POST /stateDesktop publica estado del Pulso para una sesión

Qué datos se sincronizan

DirecciónDatos
Desktop → CompanionDaySnapshot: foco, tareas del día, ecos, inbox, rocas, pulso activo
Companion → DesktopDeltas: subtask_complete, inbox_append, inbox_delete, inbox_update, pulso_start, pulso_end, foco_change, task_activate, roca_changed

Regla: Desktop siempre tiene prioridad (LWW — Last Writer Wins). Si hay conflicto, gana el Desktop.


Offline

El Companion mantiene una cola de deltas en AsyncStorage. Cuando te reconectas, los deltas pendientes se envían automáticamente. No pierdes nada si estuviste sin conexión.

El Background Fetch ejecuta un ciclo de sync cada 15 minutos incluso cuando la app está cerrada.