Files
potato-mesh/matrix/README.md
l5y 175a8f368f matrix: add docker file for bridge (#556)
* matrix: add docker file for bridge

* matrix: address review comments

* matrix: address review comments

* matrix: address review comments

* matrix: address review comments

* matrix: address review comments
2025-12-16 08:53:01 +01:00

280 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# potatomesh-matrix-bridge
A small Rust daemon that bridges **PotatoMesh** LoRa messages into a **Matrix** room.
For each PotatoMesh node, the bridge creates (or uses) a **Matrix puppet user**:
- Matrix localpart: `potato_` + the hex node id (without `!`), e.g. `!67fc83cb``@potato_67fc83cb:example.org`
- Matrix display name: the nodes `long_name` from the PotatoMesh API
Messages from PotatoMesh are periodically fetched and forwarded to a single Matrix room as those puppet users.
---
## Features
- Polls `https://potatomesh.net/api/messages` (deriving `/api` from the configured base domain)
- Looks up node metadata via `GET /api/nodes/{hex}` and caches it
- One Matrix user per node:
- username: `potato_{hex node id}`
- display name: `long_name`
- Forwards `TEXT_MESSAGE_APP` messages into a single Matrix room
- Persists last-seen message ID to avoid duplicates across restarts
---
## Architecture Overview
- **PotatoMesh side**
- `GET /api/messages` returns an array of messages
- `GET /api/nodes/{hex}` returns node metadata (including `long_name`)
- **Matrix side**
- Uses the Matrix Client-Server API with an **appservice access token**
- Impersonates puppet users via `user_id=@potato_{hex}:{server_name}&access_token={as_token}`
- Sends `m.room.message` events into a configured room
This is **not** a full appservice framework; it just speaks the minimal HTTP needed.
---
## Requirements
- Rust (stable) and `cargo`
- A Matrix homeserver you control (e.g. Synapse)
- An **application service registration** on your homeserver that:
- Whitelists the puppet user namespace (e.g. `@potato_[0-9a-f]{8}:example.org`)
- Provides an `as_token` the bridge can use
- Network access from the bridge host to:
- `https://potatomesh.net/` (bridge appends `/api`)
- Your Matrix homeserver (`https://matrix.example.org`)
---
## Configuration
All configuration is in `Config.toml` in the project root.
Example:
```toml
[potatomesh]
# Base domain (bridge will call {base_url}/api)
base_url = "https://potatomesh.net/"
# Poll interval in seconds
poll_interval_secs = 10
[matrix]
# Homeserver base URL (client API) without trailing slash
homeserver = "https://matrix.example.org"
# Appservice access token (from your registration.yaml)
as_token = "YOUR_APPSERVICE_AS_TOKEN"
# Server name (domain) part of Matrix user IDs
server_name = "example.org"
# Room ID to send into (must be joined by the appservice / puppets)
room_id = "!yourroomid:example.org"
[state]
# Where to persist last seen message id
state_file = "bridge_state.json"
````
### PotatoMesh API
The bridge assumes:
* Messages: `GET {base_url}/api/messages` JSON array, for example:
```json
[
{
"id": 2947676906,
"rx_time": 1764241436,
"rx_iso": "2025-11-27T11:03:56Z",
"from_id": "!da6556d4",
"to_id": "^all",
"channel": 1,
"portnum": "TEXT_MESSAGE_APP",
"text": "Ping",
"rssi": -111,
"hop_limit": 1,
"lora_freq": 868,
"modem_preset": "MediumFast",
"channel_name": "TEST",
"snr": -9.0,
"node_id": "!06871773"
}
]
```
* Nodes: `GET {base_url}/api/nodes/{hex}` JSON, for example:
```json
{
"node_id": "!67fc83cb",
"short_name": "83CB",
"long_name": "Meshtastic 83CB",
"role": "CLIENT_HIDDEN",
"last_heard": 1764250515,
"first_heard": 1758993817,
"last_seen_iso": "2025-11-27T13:35:15Z"
}
```
Node hex ID is derived from `node_id` by stripping the leading `!` and using the remainder inside the puppet localpart prefix (`potato_{hex}`).
---
## Matrix Appservice Setup (Synapse example)
You need an appservice registration file (e.g. `potatomesh-bridge.yaml`) configured in Synapse.
A minimal example sketch (you **must** adjust URLs, secrets, namespaces):
```yaml
id: potatomesh-bridge
url: "http://your-bridge-host:8080" # not used by this bridge if it only calls out
as_token: "YOUR_APPSERVICE_AS_TOKEN"
hs_token: "SECRET_HS_TOKEN"
sender_localpart: "potatomesh-bridge"
rate_limited: false
namespaces:
users:
- exclusive: true
regex: "@potato_[0-9a-f]{8}:example.org"
```
For this bridge, only the `as_token` and `namespaces.users` actually matter. The bridge does not accept inbound events; it only uses the `as_token` to call the homeserver.
In Synapses `homeserver.yaml`, add the registration file under `app_service_config_files`, restart, and invite a puppet user to your target room (or use room ID directly).
---
## Build
```bash
# clone
git clone https://github.com/YOUR_USER/potatomesh-matrix-bridge.git
cd potatomesh-matrix-bridge
# build
cargo build --release
```
The resulting binary will be at:
```bash
target/release/potatomesh-matrix-bridge
```
---
## Docker
Build the container from the repo root with the included `matrix/Dockerfile`:
```bash
docker build -f matrix/Dockerfile -t potatomesh-matrix-bridge .
```
Provide your config at `/app/Config.toml` and persist the bridge state file by mounting volumes. Minimal example:
```bash
docker run --rm \
-v bridge_state:/app \
-v "$(pwd)/matrix/Config.toml:/app/Config.toml:ro" \
potatomesh-matrix-bridge
```
If you prefer to isolate the state file from the config, mount it directly instead of the whole `/app` directory:
```bash
docker run --rm \
-v bridge_state:/app \
-v "$(pwd)/matrix/Config.toml:/app/Config.toml:ro" \
potatomesh-matrix-bridge
```
The image ships `Config.example.toml` for reference, but the bridge will exit if `/app/Config.toml` is not provided.
---
## Run
Ensure `Config.toml` is present and valid, then:
```bash
./target/release/potatomesh-matrix-bridge
```
Environment variables you may care about:
* `RUST_LOG` for logging, e.g.:
```bash
RUST_LOG=info,reqwest=warn ./target/release/potatomesh-matrix-bridge
```
The bridge will:
1. Load state from `bridge_state.json` (if present).
2. Poll PotatoMesh every `poll_interval_secs`.
3. For each new `TEXT_MESSAGE_APP`:
* Fetch node info.
* Ensure puppet is registered (`@potato_{hex}:{server_name}`).
* Set puppet display name to `long_name`.
* Send a formatted text message into `room_id` as that puppet.
* Update and persist `bridge_state.json`.
Delete `bridge_state.json` if you want it to replay all currently available messages.
---
## Development
Run tests (currently mostly compile checks, no real tests yet):
```bash
cargo test
```
Format code:
```bash
cargo fmt
```
Lint (optional but recommended):
```bash
cargo clippy -- -D warnings
```
---
## GitHub Actions CI
This repository includes a GitHub Actions workflow (`.github/workflows/ci.yml`) that:
* runs on pushes and pull requests
* caches Cargo dependencies
* runs:
* `cargo fmt --check`
* `cargo clippy`
* `cargo test`
See the workflow file for details.
---
## Caveats & Future Work
* No E2EE: this bridge posts into unencrypted (or server-side managed) rooms. For encrypted rooms, youd need real E2EE support and key management.
* No inbound Matrix → PotatoMesh direction yet. This is a one-way bridge (PotatoMesh → Matrix).
* No pagination or `since` support on the PotatoMesh API. The bridge simply deduplicates by message `id` and stores the highest seen.
If you change the PotatoMesh API, adjust the types in `src/potatomesh.rs` accordingly.