6.5 KiB
RemoteTerm for MeshCore
Backend server + browser interface for MeshCore mesh radio networks. Attach your radio over serial, and then you can:
- Send and receive DMs and GroupTexts
- Cache all received packets, decrypting as you gain keys
- Monitor unlimited contacts and channels (radio limits don't apply -- packets are decrypted server-side)
- Access your radio remotely over your network or VPN
- Brute force hashtag room names for GroupTexts you don't have keys for yet
Warning: This app has no auth, and is for trusted environments only. Do not put this on an untrusted network, or open it to the public. The bots can execute arbitrary Python code which means anyone on your network can, too. If you need access control, consider using a reverse proxy like Nginx, or extending FastAPI.
Disclaimer
This is entirely vibecoded slop -- no warranty of fitness for any purpose. It's been lovingly guided by an engineer with a passion for clean code and good tests, but it's still mostly LLM output, so you may find some bugs.
If extending, have your LLM read the three AGENTS.md files: ./AGENTS.md, ./frontend/AGENTS.md, and ./app/AGENTS.md.
Requirements
- Python 3.10+
- Node.js 18+ (for frontend development only)
- UV package manager:
curl -LsSf https://astral.sh/uv/install.sh | sh - MeshCore radio connected via USB serial
Find your serial port:
#######
# Linux
#######
ls /dev/ttyUSB* /dev/ttyACM*
#######
# macOS
#######
ls /dev/cu.usbserial-* /dev/cu.usbmodem*
######
# WSL2
######
# Run this in an elevated PowerShell (not WSL) window
winget install usbipd
# restart console
# find device ID (e.g. 3-8)
usbipd list
# attach device to WSL
usbipd bind --busid 3-8
Quick Start
This approach is recommended over Docker due to intermittent serial communications issues I've seen on *nix systems.
git clone https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
cd Remote-Terminal-for-MeshCore
# Install backend dependencies
uv sync
# Build frontend
cd frontend && npm install && npm run build && cd ..
# Run server
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
The server auto-detects the serial port. To specify manually:
MESHCORE_SERIAL_PORT=/dev/ttyUSB0 uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
Access at http://localhost:8000
Note: WebGPU cracking requires HTTPS when not on localhost. See the HTTPS section under Additional Setup.
Docker
Warning: Docker has intermittent issues with serial event subscriptions. The native method above is more reliable.
docker run -d \
--device=/dev/ttyUSB0 \
-v remoteterm-data:/app/data \
-p 8000:8000 \
jkingsman/remote-terminal-for-meshcore:latest
Development
Backend
uv sync
uv run uvicorn app.main:app --reload
# Or with explicit serial port
MESHCORE_SERIAL_PORT=/dev/ttyUSB0 uv run uvicorn app.main:app --reload
Frontend
cd frontend
npm install
npm run dev # Dev server at http://localhost:5173 (proxies API to :8000)
npm run build # Production build to dist/
Run both the backend and npm run dev for hot-reloading frontend development.
Code Quality & Tests
Please test, lint, format, and quality check your code before PRing or committing. At the least, run a lint + autoformat + pyright check on the bakend, and a lint + autoformat on the frontend.
But how?
# python
uv run ruff check app/ tests/ --fix # lint + auto-fix
uv run ruff format app/ tests/ # format (always writes)
uv run pyright app/ # type checking
PYTHONPATH=. uv run pytest tests/ -v # backend tests
# frontend
cd frontend
npm run lint:fix # esLint + auto-fix
npm run test:run # run tests
npm run format # prettier (always writes)
npm run build # build the frontend
Configuration
| Variable | Default | Description |
|---|---|---|
MESHCORE_SERIAL_PORT |
(auto-detect) | Serial port path |
MESHCORE_SERIAL_BAUDRATE |
115200 | Baud rate |
MESHCORE_LOG_LEVEL |
INFO | DEBUG, INFO, WARNING, ERROR |
MESHCORE_DATABASE_PATH |
data/meshcore.db | SQLite database path |
MESHCORE_MAX_RADIO_CONTACTS |
200 | Max recent contacts to keep on radio for DM ACKs |
Additional Setup
HTTPS (Required for WebGPU Cracking outside localhost)
WebGPU requires a secure context. When not on localhost, serve over HTTPS:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --ssl-keyfile=key.pem --ssl-certfile=cert.pem
For Docker:
# generate TLS cert
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'
# run with cert
docker run -d \
--device=/dev/ttyUSB0 \
-v remoteterm-data:/app/data \
-v $(pwd)/cert.pem:/app/cert.pem:ro \
-v $(pwd)/key.pem:/app/key.pem:ro \
-p 8000:8000 \
jkingsman/remote-terminal-for-meshcore:latest \
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --ssl-keyfile=/app/key.pem --ssl-certfile=/app/cert.pem
Accept the browser warning, or use mkcert for locally-trusted certs.
Systemd Service (Linux)
# Create service user
sudo useradd -r -m -s /bin/false remoteterm
# Install to /opt/remoteterm
sudo mkdir -p /opt/remoteterm
sudo cp -r . /opt/remoteterm/
sudo chown -R remoteterm:remoteterm /opt/remoteterm
# Install dependencies
cd /opt/remoteterm
sudo -u remoteterm uv venv
sudo -u remoteterm uv sync
# Build frontend (required for the backend to serve the web UI)
cd /opt/remoteterm/frontend
sudo -u remoteterm npm install
sudo -u remoteterm npm run build
# Install and start service
sudo cp /opt/remoteterm/remoteterm.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now remoteterm
# Check status
sudo systemctl status remoteterm
sudo journalctl -u remoteterm -f
Edit /etc/systemd/system/remoteterm.service to set MESHCORE_SERIAL_PORT if needed.
Testing
Backend:
PYTHONPATH=. uv run pytest tests/ -v
Frontend:
cd frontend
npm run test:run
API Documentation
With the backend running: http://localhost:8000/docs
