Skip to content

ECADInfra/beacon-synapse

Repository files navigation

beacon-synapse

Production-ready Matrix homeserver image for operating Beacon relay nodes. Beacon is the open communication standard connecting Tezos wallets and dApps, ratified as TZIP-10. Matrix provides the federated transport layer.

Provenance

Beacon relay infrastructure was originally created by Papers (AirGap) as part of the Beacon SDK ecosystem. Papers designed the protocol, built the SDK, and operated the first relay nodes, establishing the standard that the entire Tezos wallet and dApp ecosystem relies on today.

This image builds on AirGap's beacon-node, with upgrades to the underlying Synapse version, added observability, and worker support for higher throughput.

What changed from upstream

  • Synapse v1.98.0 to v1.147.1: Upgraded to current release
  • crypto_auth_provider.py v0.3: PyNaCl-based Ed25519 auth (no gcc/libsodium-dev build deps), structured logfmt logging, race condition handling
  • beacon_monitor_module.py: Observability module for diagnosing connection and federation issues. Logs operational metadata (room lifecycle, membership changes, payload sizes, login events) in logfmt format. All Beacon message payloads are encrypted end-to-end between wallet and dApp using NaCl cryptobox before reaching the relay server; message content is not and cannot be logged. User and room identifiers are opaque hashes with no link to real-world identity.
  • beacon_info_module.py: HTTP endpoint exposing server region and known relay servers
  • Worker mode: Official Element HQ worker orchestration (supervisord + nginx + redis) for horizontal scaling
  • MAX_PDU_SIZE patch: 64KB to 1MB (Beacon messages can exceed the default Matrix limit)
  • logfmt logging: Structured log output for ingestion into Loki/Grafana/etc.
  • SSRF protection: federation_ip_range_blacklist covering RFC 1918, link-local, and loopback
  • Robust entrypoint: Template variable substitution, database readiness check, single-process or multi-worker modes

Quick start

# Single-process mode (default)
docker compose -f docker-compose.example.yml up --build

# Worker mode
SYNAPSE_WORKERS=true docker compose -f docker-compose.example.yml up --build

This starts Synapse with PostgreSQL. The server will be available at http://localhost:8008.

Architecture

Single-process mode (default)

Client/Federation --> :8008 [Synapse] --> PostgreSQL

One Synapse process handles everything. Good for development and low-traffic deployments.

Worker mode

Client/Federation --> :8008 [nginx] --> :8080 [Synapse main]
                                   \-> :18009+ [workers]
                                        - synchrotron (sync)
                                        - event_persister (writes)
                                        - federation_inbound (federation)
                     :9469 [nginx] --> Prometheus service discovery
                     [redis] <------> inter-process replication
                     [supervisord] --> manages all processes

Supervisord orchestrates the main Synapse process, worker processes, nginx, and redis, all inside a single container. nginx routes requests to the appropriate worker based on URL patterns. Redis handles inter-process replication.

Configuration

The image uses template variables in homeserver.yaml that are substituted at startup. Pass them as environment variables:

Variable Required Description
SERVER_NAME Yes Matrix server name (e.g., beacon-1.example.com)
DB_HOST Yes PostgreSQL hostname
DB_USER Yes PostgreSQL username
DB_PASS Yes PostgreSQL password
DB_NAME Yes PostgreSQL database name
SIGNING_KEY Yes Synapse signing key (e.g., ed25519 a_key0 <base64>)
REGISTRATION_SHARED_SECRET Yes Synapse admin registration secret
SERVER_REGION No Region label for the /beacon/info endpoint
SYNAPSE_ENABLE_METRICS No Set to 1 to expose Prometheus metrics on port 19090
SYNAPSE_WORKERS No Set to true to enable multi-worker mode
SYNAPSE_WORKER_TYPES No Comma-separated worker types (default: synchrotron:2,event_persister:1,federation_inbound:1)
PUBLIC_BASEURL No Public URL for federation (default: https://SERVER_NAME)
SERVE_WELLKNOWN No Set to true to serve .well-known/matrix/server for Cloudflare
DB_CP_MIN No Minimum database connections (default: 20)
DB_CP_MAX No Maximum database connections (default: 80)

Worker types

The SYNAPSE_WORKER_TYPES variable accepts the official Element HQ worker type syntax:

# Simple list
SYNAPSE_WORKER_TYPES="synchrotron,event_persister,federation_inbound"

# With multipliers
SYNAPSE_WORKER_TYPES="synchrotron:2,event_persister:2,federation_inbound:1"

# Combined workers (merge types into one process)
SYNAPSE_WORKER_TYPES="stream_writers=account_data+presence+typing"

# Custom names
SYNAPSE_WORKER_TYPES="sync=synchrotron:2,persist=event_persister:1"

Available types: synchrotron, event_persister, federation_inbound, federation_sender, federation_reader, client_reader, event_creator, media_repository, user_dir, pusher, appservice, background_worker, account_data, presence, receipts, to_device, typing, push_rules, device_lists, thread_subscriptions.

Entrypoint options

# Default: uses /config/homeserver.yaml with variable substitution
docker run ghcr.io/ecadinfra/beacon-synapse

# Custom config path
docker run ghcr.io/ecadinfra/beacon-synapse -c /custom/homeserver.yaml

# Skip template substitution (if you mount a pre-configured file)
docker run ghcr.io/ecadinfra/beacon-synapse --skip-templating

Ports

Port Mode Service
8008 Both HTTP (client + federation). Direct in single mode, nginx in worker mode
8080 Worker Main Synapse process (internal, behind nginx)
9469 Worker Prometheus service discovery + metrics proxy
19090 Both Prometheus metrics (main process, when enabled)

Prometheus service discovery (worker mode)

When SYNAPSE_ENABLE_METRICS=1 and worker mode is active, port 9469 serves:

  • GET /metrics/service_discovery - JSON for Prometheus http_sd_config
  • GET /metrics/worker/<name> - Proxied metrics for each worker
  • GET /metrics/worker/main - Proxied metrics for the main process

Prometheus config:

scrape_configs:
  - job_name: beacon-synapse
    http_sd_configs:
      - url: http://synapse:9469/metrics/service_discovery
    honor_labels: true

Federation behind Cloudflare

If your server is behind Cloudflare (or any proxy that doesn't support port 8448), enable .well-known delegation:

environment:
  PUBLIC_BASEURL: "https://beacon-1.example.com"
  SERVE_WELLKNOWN: "true"

This configures Synapse to:

  1. Serve /.well-known/matrix/server with delegation to port 443
  2. Set public_baseurl for proper federation discovery

Other Matrix servers will then connect on port 443 instead of 8448. Ensure your reverse proxy routes:

  • /.well-known/matrix/server -> Synapse (port 8008)
  • /_matrix/federation/* -> Synapse (port 8008)
  • /_matrix/client/* -> Synapse (port 8008)

Important: Configure Cloudflare to not challenge /_matrix/federation/* paths (these are server-to-server requests, not browsers).

Authentication protocol

This image replaces Matrix password authentication with Ed25519 signature verification via crypto_auth_provider.py.

  • Username: BLAKE2b hash of the Ed25519 public key (hex encoded)
  • Password: ed:<signature>:<public_key> (both hex encoded)
  • Signature covers: BLAKE2b("login:<time_window>") where time_window = floor(unix_time / 300)
  • Clock tolerance: Accepts signatures for the current, previous, and next 5-minute windows
  • Auto-registration: New users are automatically registered on first successful authentication

This is the standard Beacon authentication mechanism defined in TZIP-10. Any Beacon SDK client or wallet that implements the specification will work with any compliant relay node, regardless of operator.

Modules

beacon_monitor_module.py

Hooks into Synapse's module API to log operational metadata in logfmt format. Designed for diagnosing connection failures, federation lag, and capacity planning.

  • event=MEMBERSHIP: Room joins, leaves, invites with local/remote/federated classification
  • event=ROOM_CREATED: New room creation
  • event=MESSAGE: Payload size in bytes (content is not logged)
  • event=LOGIN: Login events with auth provider info
  • event=ENCRYPTION_ENABLED: Room encryption events

Privacy: Beacon messages are encrypted end-to-end (NaCl cryptobox) by the SDK before reaching the relay server. This module logs only the size of encrypted payloads, never their content. Relay operators cannot decrypt message payloads. User IDs are BLAKE2b hashes of ephemeral public keys, and room IDs are opaque Matrix identifiers. Neither is linked to real-world identity.

beacon_info_module.py

Exposes /_synapse/client/beacon/info returning:

{
  "region": "na-west",
  "known_servers": ["beacon-node-1.diamond.papers.tech", "..."],
  "timestamp": 1708300000.0
}

Running your own relay node

If you want to operate a Beacon relay node for the Tezos ecosystem:

  1. Deploy this image with the configuration above
  2. Ensure port 8448 (or 443 if using .well-known delegation) is reachable from other relay nodes
  3. Configure federation with existing operators so wallets and dApps on your node can communicate with the broader network
  4. Open an issue or reach out to coordinate federation peering

The more independent operators running relay infrastructure, the more resilient the network is for everyone.

Links

License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only).

See NOTICE for attribution of upstream works.

About

Production Matrix homeserver image for Tezos Beacon relay nodes. Ed25519 auth, observability, worker support.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors