Servidor propio
El servidor de Miblo Remote, para levantar en tu propio host. Con él, el móvil y la computadora hablan solo a través de tu servidor, nunca de miblo.ai. Es opcional y gratis, para quien sabe alojarlo.
Descargar
Versión 1.3.0
- Código fuente (.tar.gz)https://miblo.ai/dl/relay/miblo-relay-1.3.0.tar.gz
- Firma (.sig)https://miblo.ai/dl/relay/miblo-relay-1.3.0.tar.gz.sig
- SHA256SUMS de todas las versioneshttps://miblo.ai/dl/relay/SHA256SUMS
- Firma del SHA256SUMShttps://miblo.ai/dl/relay/SHA256SUMS.sig
Se distribuye solo desde miblo.ai y está firmado con la misma clave de las actualizaciones de Miblo (Ed25519). Verifica la firma antes de usarlo: el comando está justo debajo, en Descargar y verificar.
Licencia AGPL-3.0, dentro del paquete.
¿Prefieres Git? El código fuente también está en GitHub: github.com/marcus-campos/miblo-relay
Guía del servidor
El README del paquete de esta versión.
The phone server of Miblo, to run on your own host: the session relay, the account your phones join, the phone registry and the phone app (PWA). It speaks the very protocol the Miblo plugin and miblo.ai speak, so everything of Miblo+ that goes through the phone (history, replies, approvals, remote tasks) works here, free and without a license, for anyone who runs their own server.
Privacy
Miblo is already end-to-end encrypted: the keys are made on your computer, reach each phone sealed to that phone's own key only after you type, on the computer, the 6-digit code the phone shows, and the server only relays ciphertext. A malicious server (miblo.ai or this one) reads nothing, cannot slip a phone of its own in, and cannot act on your computer.
What remains is the phone app's code: it is a web page, and whoever serves the page runs code with the phone's keys. On miblo.ai you trust what miblo.ai serves. On your own server, the app is what you built from this source (or a release whose hashes you checked), served by you. That removes the trust in the JavaScript miblo.ai serves. Details in the threat model (docs/threat-model.md).
While your server is set, the plugin sends nothing of the phone companion or the account to miblo.ai (it still reads its signed update manifest from miblo.ai).
Download and verify
The download comes from miblo.ai, at https://miblo.ai/dl/relay/, next to the firmware and the installers (the source is also on GitHub: https://github.com/marcus-campos/miblo-relay), next to the firmware and the installers, and signed with the key of Miblo's releases (Ed25519). The newest version is in https://miblo.ai/dl/extras/latest.json; the page miblo.ai/en/docs/self-hosting shows this guide and links it.
V=1.0.0 # the newest version: https://miblo.ai/dl/extras/latest.json
curl -fLO https://miblo.ai/dl/relay/miblo-relay-$V.tar.gz
curl -fLO https://miblo.ai/dl/relay/miblo-relay-$V.tar.gz.sig
node -e 'const c=require("crypto"),fs=require("fs"),f=process.argv[1],k=c.createPublicKey({key:{kty:"OKP",crv:"Ed25519",x:Buffer.from("LGtuUvDA506N8iD1tolbQzWAJBb9hUerW0OH4C2pBek=","base64").toString("base64url")},format:"jwk"});const ok=c.verify(null,fs.readFileSync(f),k,Buffer.from(fs.readFileSync(f+".sig","utf8").trim(),"base64"));console.log(ok?"signature OK":"BAD SIGNATURE: do not use this file");process.exit(ok?0:1)' miblo-relay-$V.tar.gz
tar -xzf miblo-relay-$V.tar.gz && cd miblo-relay-$VThe public key (LGtuUvDA506N8iD1tolbQzWAJBb9hUerW0OH4C2pBek=) is the one the Miblo plugin checks its updates with. https://miblo.ai/dl/relay/SHA256SUMS (signed in SHA256SUMS.sig) holds the SHA-256 of every published version.
Quick start (Docker, 3 steps)
You need a machine with Docker, a DNS name pointing at it and ports 80 and 443 open (Caddy gets the TLS certificate by itself).
1. Configure it, inside the folder you downloaded and checked above:
cd deploy/docker
cp .env.example .env # set RELAY_DOMAIN to your domain2. Start it
docker compose up -d --build
docker compose logs relay # shows the setup token and the server's identity fingerprint3. Create your account: open https://your-domain/conta (or /en/account), use the setup token, set a password and add a second factor (a passkey, recommended, or an authenticator app). Generate the recovery codes.
Without Docker: npm ci && npm run build && PUBLIC_ORIGIN=https://your-domain node dist/server.mjs (Node 22.13+ or 23.4+, which have node:sqlite) behind any TLS proxy.
Deploy to your own Cloudflare
A Worker on your Cloudflare account (the free plan is enough for one person), with D1 and Durable Objects:
npm ci
cp deploy/cloudflare/wrangler.jsonc.example deploy/cloudflare/wrangler.jsonc
cd deploy/cloudflare
npx wrangler d1 create miblo-relay # put the database_id in wrangler.jsonc
npx wrangler d1 migrations apply miblo-relay --remote
cd ../.. && npm run build:app && npm run cf:secrets # prints the setup token and the fingerprint
cd deploy/cloudflare && npx wrangler deployThen step 3 above.
Point your computer at it
In your own terminal (never through an AI agent), with a Miblo plugin that has miblo server:
miblo server set https://your-domainMiblo reads the server's identity key, shows its fingerprint (compare it with the one your server printed: docker compose logs relay or node dist/server.mjs fingerprint) and asks for the code on your Miblo's screen. In the Miblo desktop app: Settings › Phone server. Switching unlinks this computer and turns the phone companion off at the old server; then:
miblo account link # confirm the code at https://your-domain/plus/link
miblo phone onOn the phone, open https://your-domain/app, sign in and allow the phone on the computer with the code it shows. miblo server shows where it points; miblo server reset goes back to miblo.ai (also with the Miblo's code).
Configuration
| Variable | Required | What it is |
|---|---|---|
PUBLIC_ORIGIN | yes | The server's https address, the origin alone (https://relay.example.com): the app's origin, the passkeys' relying party, the device-link address. Never taken from a request. |
TRUSTED_PROXY | behind a proxy | Your proxy's addresses or ranges (CIDR), comma-separated (172.30.247.0/24). Only for a connection from them is the client address the last X-Forwarded-For entry (the one the proxy added). Only a rate-limit key. Never set it without a proxy in front. |
RELAY_VAPID_SUBJECT | no | A contact sent to the push services (mailto:you@example.com); defaults to the server's address. |
SESSION_SECRET, MFA_KEY, SERVER_IDENTITY_KEY, RELAY_VAPID_PUBLIC_KEY, RELAY_VAPID_PRIVATE_KEY, SETUP_TOKEN | no (Node) / yes (Cloudflare) | On Node, the ones you do not set are made once and kept in secrets.json (0600), in $MIBLO_RELAY_SECRETS_DIR. On Cloudflare, npm run cf:secrets makes and stores them all. |
MIBLO_RELAY_DATA | no | The database folder (Docker: /data). |
MIBLO_RELAY_SECRETS_DIR | no | Where secrets.json lives (default: MIBLO_RELAY_DATA; Docker: /secrets, a volume of its own, kept out of the database's backups). |
PORT, HOST | no | Where Node listens (default 0.0.0.0:8787). |
Node commands: node dist/server.mjs check, fingerprint, setup-token (a setup token works for one setup; once used, this command makes a new one, to redo an abandoned setup), reset-account (lost every factor: removes the factors and sessions and makes a new setup token; computers and phones stay). On Cloudflare, delete the mfa_* and sessions rows with wrangler d1 execute and set a new token with npx wrangler secret put SETUP_TOKEN (a long random value, like openssl rand -base64 24) for the same.
Upgrading
Download and verify the new version as in Download and verify, copy your deploy/docker/.env into it (on Cloudflare, your deploy/cloudflare/wrangler.jsonc) and, in the new folder:
cd deploy/docker && docker compose up -d --buildThe Docker volumes (database and secrets) stay the same: the Compose project is named after its folder (docker), the same in both versions. Database migrations run at start (Node). On Cloudflare: npm ci && npx wrangler d1 migrations apply miblo-relay --remote && npm run build:app && npx wrangler deploy. Watch the versions in https://miblo.ai/dl/extras/latest.json (security fixes are announced on miblo.ai/en/docs/self-hosting) and back up the relay-data volume first.
Security
Read the threat model (
docs/threat-model.md): a malicious operator is as powerful as a malicious miblo.ai, which the protocol already tolerates.One account, second factor mandatory; the password alone never signs in. Passwords use PBKDF2-SHA-256 (600,000 iterations on Node; 100,000 on Cloudflare, a Worker's maximum). Wrong passwords lock only the network they came from, for 15 minutes, with the same answer as a wrong name.
The plugin pins the server's identity key the first time (TOFU) and asks for a fresh signature with it before using the server; if the key changes, nothing is sent until you accept the new one with
miblo server set(and your Miblo's code).Keep
secrets.json(or the Worker's secrets) out of backups others can read.Found a vulnerability? Write to contato@miblo.ai (subject "Security"), privately, and do not publish it before the fix.
Reproducible builds
The build is deterministic (base image pinned by digest, dependencies from package-lock.json). The Docker image holds dist/HASHES.txt: the SHA-256 of every file served and of the server, and one hash of the set (build …):
docker compose exec relay tail -1 dist/HASHES.txt # what is running
npm ci && npm run build && npm run hashes | tail -1 # what this source buildsBoth must match: what runs is what this source, checked by its signature, builds.
Development
npm test runs the relay suite on both runtimes (workerd through Miniflare, and Node), the API tests, the Worker test and, with MIBLO_PLUGIN_DIR pointing at a Miblo plugin checkout's plugin/ folder, an end-to-end test of the real plugin against this server. npm run typecheck checks the types. The phone app under app/src is the same source as miblo.ai's phone app; app/shims and app/src/selfhost are what this build adds (see docs/sources.md (docs/sources.md)).
License
AGPL-3.0 (LICENSE): whoever offers a modified version of this server as a service must publish the source of their changes. The project owner can change the license while they are its only author.