Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

Alma Relay

English | 简体中文

Your own relay for Alma Remote Access: control the Alma on your desktop from the Alma app on your phone, from anywhere, with no port forwarding, no VPN, and no third party in between.

desktop Alma ──outbound WSS──▶  your relay  ◀──HTTPS/WSS── Alma on your phone

The desktop dials out to the relay and keeps the connection open. The phone sends its requests to the relay, which passes them through that connection to the desktop: HTTP, streaming (SSE) and WebSockets. With end-to-end encryption on (the Alma default), the relay only ever sees ciphertext. It stores nothing on disk and never sees your connect code.

Alma ships with a hosted relay (relay.alma.now). Run your own if you want the traffic on infrastructure you control, a region closer to you, or no dependency on anyone else's server.

Deploy

Pick one. Each takes a few minutes.

Option A: Cloudflare Workers (free, no server)

Deploy to Cloudflare

Or from a clone:

git clone https://github.com/yetone/alma-relay && cd alma-relay
npm install
npx wrangler deploy        # prints https://alma-relay.<you>.workers.dev

This runs on the Workers Free plan, using SQLite-backed Durable Objects with WebSocket hibernation, so an idle pairing costs next to nothing. TLS and a *.workers.dev hostname come with it. To use your own domain, uncomment routes in wrangler.jsonc.

Option B: Docker with automatic HTTPS (any VPS)

You need a server with ports 80 and 443 open and a DNS record pointing at it.

git clone https://github.com/yetone/alma-relay && cd alma-relay
cp .env.example .env       # set RELAY_DOMAIN=relay.example.com
docker compose up -d

Caddy obtains and renews the Let's Encrypt certificate on its own. Your relay is at https://relay.example.com.

Option C: Behind your own reverse proxy

docker run -d --restart unless-stopped -p 127.0.0.1:8787:8787 ghcr.io/yetone/alma-relay
# or, without Docker (Node ≥ 20):
npx github:yetone/alma-relay

Then proxy your HTTPS hostname to 127.0.0.1:8787. WebSockets must be allowed and responses must not be buffered:

nginx
location / {
    proxy_pass http://127.0.0.1:8787;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_buffering off;
    proxy_read_timeout 1h;
    client_max_body_size 128m;
}
Caddy
relay.example.com {
    reverse_proxy 127.0.0.1:8787 {
        flush_interval -1
    }
}

Check it with curl https://relay.example.com/healthz.

Connect Alma

  1. On the desktop, open Settings → Remote Access.
  2. Click Use a self-hosted relay, enter your relay's https:// address, and click Use this relay.
  3. If your relay requires a key (see below), paste it under Alma account → Paste a token manually.
  4. Scan the pairing QR code with the Alma app on your phone.

The phone doesn't need any setup: the pairing link carries the relay address. Phones paired before you switched relays need to scan again.

Configuration

Set everything through environment variables: in .env for Docker, or with wrangler secret put NAME (secrets) or vars in wrangler.jsonc (plain settings) on Cloudflare. All of them are optional.

VariableMeaning
RELAY_REGISTRATION_KEYA shared key desktops must present before they can register. Paste it into Alma as the token.
RELAY_TOKEN_SECRETUse per-user signed tokens instead of a shared key. Useful when you run one relay for a team (see below). Takes precedence over RELAY_REGISTRATION_KEY.
RELAY_TOKEN_MESSAGEWhat a desktop without a valid relay token is told, e.g. how to get one (default: A valid relay token is required).
PORT / HOSTListen address (Node: 8787 / 0.0.0.0).
MAX_BODY_MBRequest-body cap in MB (Node: 128. Cloudflare: 100 for desktops on protocol 2, a fixed 24 for older ones).
MAX_BUFFERED_MBNode: cap on all request bodies held in memory at once, in MB (512); over it, 503. Bodies to protocol-2 desktops are streamed and don't count.
MAX_AGENTSNode: most desktops at once (10000).
MAX_REQUESTS_PER_AGENT / MAX_SOCKETS_PER_AGENTNode: concurrent HTTP requests / WebSockets per desktop (256 each).
MAX_CONNECTIONSNode: TCP connection cap for the process (unset: no cap).
FLOW_STREAM_WINDOW_KBProtocol-3 desktops: how many KB of one stream a desktop may send ahead of the relay (1024). A slow phone then holds back only its own stream. See protocol 3.
FLOW_HIGH_WATER_MB / FLOW_LOW_WATER_MBNode: stop reading a desktop once one of its phones has this much queued, and read again below the low mark (8 / 1). A safety net for protocol-3 desktops. See flow control.
STALL_TIMEOUT_SCut off a phone that reads nothing for this many seconds (Node), or a protocol-2 desktop that takes in none of an upload (both editions) (60). See flow control.
RELAY_PROTOCOLNewest protocol offered to desktops (3). 2 turns off per-stream flow control; 1 also turns off protocol 2 (streamed uploads, binary frames).
LOCATION_HINTCloudflare only: where tunnels live, for example apac, enam or weur. By default a tunnel is created near the desktop. Set this when the desktop sits behind a proxy far away from the phone. Changing it moves each desktop to a new tunnel the next time it redials; phones find desktops that haven't yet.
TURN_URLS / TURN_SECRET / TURN_TTLServe TURN credentials for the phone's live view of the desktop browser (WebRTC).
APNS_KEY_FILE or APNS_KEY, plus APNS_KEY_ID, APNS_TEAM_ID, APNS_TOPICPhone notifications (see below).
FCM_SERVICE_ACCOUNT_FILE or FCM_SERVICE_ACCOUNTAndroid phone notifications through Firebase (see below).

Who can use your relay

With no key set, anyone who knows your relay's address can open tunnels on it. They still can't reach your desktop: that takes its 256-bit connect code. For a personal relay this is usually fine. Set RELAY_REGISTRATION_KEY to keep strangers from using your server at all.

To share one relay with a team and be able to cut off individual people, set RELAY_TOKEN_SECRET and mint a token for each person:

RELAY_TOKEN_SECRET=… npm run mint-token -- alice 365     # user id, days valid
# Docker: docker compose exec relay node scripts/mint-token.mjs alice

Tokens are standard HS256 JWTs (aud: "alma-relay", sub, exp), so any JWT library can mint them too. Rotate the secret to revoke them all.

Phone notifications

The iOS app can't keep a connection open while the phone is locked. "Reply finished" and "Alma has a question" therefore arrive through Apple Push, which the relay sends on the desktop's behalf. Apple only accepts these from the key of the team that signs the app. A self-hosted relay therefore can't notify the official Alma iOS app. Everything else works. If you build the iOS app yourself, give the relay your own key:

APNS_KEY_FILE=/path/AuthKey_XXXX.p8 APNS_KEY_ID=XXXX APNS_TEAM_ID=YYYY APNS_TOPIC=your.bundle.id
# Cloudflare: wrangler secret put APNS_KEY < AuthKey_XXXX.p8  (and APNS_KEY_ID, APNS_TEAM_ID)

Android phones are notified through Firebase Cloud Messaging instead. Give the relay the Firebase project's service-account key (Firebase console → Project settings → Service accounts → Generate new private key):

FCM_SERVICE_ACCOUNT_FILE=/path/service-account.json
# Cloudflare: wrangler secret put FCM_SERVICE_ACCOUNT < service-account.json

Like APNs, Firebase only delivers to the app of the project that key belongs to. A relay with only one of the two keys answers the other kind of phone as ApnsUnsupported / FcmUnsupported.

Without either key, the relay reports unsupported and the desktop stops asking.

TURN for the live browser view

The phone can show the desktop's browser live over WebRTC. On most networks that needs a TURN server. The Docker setup includes coturn:

# in .env:
TURN_SECRET=<a long random string>
TURN_URLS=stun:relay.example.com:3478,turn:relay.example.com:3478?transport=udp,turn:relay.example.com:3478?transport=tcp

docker compose --profile turn up -d

Open UDP/TCP 3478 and UDP 49160-49200 in your firewall. On Cloudflare, run coturn somewhere else and set TURN_URLS / TURN_SECRET on the Worker. The relay only issues short-lived credentials. Media never passes through it.

Security

  • End-to-end encryption. With E2E on (the default for new pairings), the phone and desktop run an authenticated ECDH handshake over the tunnel and derive an AES-256-GCM session. The connect code is part of the key derivation, so whoever runs the relay can't intercept or read the traffic. Sessions have forward secrecy.
  • Use Alma 0.4.329 or later. From that version on, a desktop with E2E on refuses every plaintext request, so whoever runs the relay can't call the desktop's API directly. Earlier versions still answered plaintext requests sent by the relay itself.
  • Nothing to steal. The relay routes by sha256(routing token), and the routing token is a one-way derivation of the code. Codes, tokens and traffic are never written to disk or logs.
  • Always serve it over HTTPS. E2E protects the content either way, but TLS also hides the routing metadata.
  • The connect code is a password for your desktop. Regenerate it in Remote Access settings to revoke every paired phone.

Development

npm install
npm start                    # Node edition on :8787
npx wrangler dev             # Cloudflare edition, locally

npm test                     # end-to-end suite against the Node edition
npm run test:cloudflare      # the same suite against `wrangler dev`
node test/e2e.mjs https://relay.example.com   # smoke-test a deployment
File
index.mjsNode edition (the ws package is its only dependency)
cloudflare/worker.mjsCloudflare Workers + Durable Objects edition
shared.mjs, apns.mjsCode both editions use
docs/PROTOCOL.mdThe wire protocol, if you want to write your own relay or agent

License

MIT

关于 About

Self-host the relay for Alma Remote Access — control your desktop Alma from your phone, anywhere. Docker or Cloudflare Workers.

语言 Languages

JavaScript99.6%
Dockerfile0.4%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
2
Total Commits
峰值: 2次/周
Less
More

核心贡献者 Contributors