mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-07-06 18:01:19 +02:00
175a8f368f
* 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
280 lines
7.2 KiB
Markdown
280 lines
7.2 KiB
Markdown
# 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 node’s `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 Synapse’s `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, you’d 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.
|