From 580dbfd24e3cd757499dcfdbd143deac36988aca Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 7 Jul 2026 07:30:52 -0500 Subject: [PATCH] feat(docs): add comprehensive documentation for MeshChatX, including installation, architecture, messaging, audio calls, and identity management; remove outdated meshchatx.md and nomadmesh_pages.md files --- docs/en/architecture.md | 146 ++++++ docs/en/audio-calls.md | 87 ++++ docs/en/getting-started.md | 97 ++++ docs/en/identity-and-security.md | 105 ++++ docs/en/installation.md | 153 ++++++ docs/en/interfaces.md | 87 ++++ docs/en/messaging.md | 111 ++++ docs/en/nomad-network.md | 77 +++ docs/en/nomadmesh-pages.md | 52 ++ .../en/platform-guides/android-termux.md | 2 +- .../en/platform-guides/linux-sandbox.md | 2 +- .../en/platform-guides/quest-sidequest.md | 4 +- .../platform-guides/raspberry-pi.md} | 2 +- docs/en/tools.md | 105 ++++ docs/manifest.json | 105 ++++ docs/meshchatx.md | 174 ------- docs/nomadmesh_pages.md | 46 -- meshchatx/meshchat.py | 85 ++-- meshchatx/src/backend/docs_manager.py | 449 +++++++++++----- meshchatx/src/backend/markdown_renderer.py | 127 ++++- .../src/frontend/components/docs/DocsPage.vue | 480 +++++++++++++----- meshchatx/src/frontend/locales/de.json | 32 +- meshchatx/src/frontend/locales/en.json | 32 +- .../public/meshchatx-docs/en/architecture.md | 146 ++++++ .../public/meshchatx-docs/en/audio-calls.md | 87 ++++ .../meshchatx-docs/en/getting-started.md | 97 ++++ .../en/identity-and-security.md | 105 ++++ .../public/meshchatx-docs/en/installation.md | 153 ++++++ .../public/meshchatx-docs/en/interfaces.md | 87 ++++ .../public/meshchatx-docs/en/messaging.md | 111 ++++ .../public/meshchatx-docs/en/nomad-network.md | 77 +++ .../meshchatx-docs/en/nomadmesh-pages.md | 52 ++ .../en/platform-guides/android-termux.md | 2 +- .../en/platform-guides/linux-sandbox.md | 2 +- .../en/platform-guides/quest-sidequest.md | 4 +- .../platform-guides/raspberry-pi.md} | 2 +- .../public/meshchatx-docs/en/tools.md | 105 ++++ .../public/meshchatx-docs/manifest.json | 107 ++++ .../public/meshchatx-docs/meshchatx.md | 174 ------- .../public/meshchatx-docs/nomadmesh_pages.md | 46 -- scripts/sync-meshchatx-docs.js | 71 ++- tests/backend/http_api_response_registry.py | 2 +- tests/backend/test_archives_api_robustness.py | 27 +- tests/backend/test_docs_manager.py | 161 +++++- tests/backend/test_markdown_renderer.py | 19 + tests/frontend/DocsPage.test.js | 309 ++++++++++- vite.config.js | 2 + 47 files changed, 3750 insertions(+), 758 deletions(-) create mode 100644 docs/en/architecture.md create mode 100644 docs/en/audio-calls.md create mode 100644 docs/en/getting-started.md create mode 100644 docs/en/identity-and-security.md create mode 100644 docs/en/installation.md create mode 100644 docs/en/interfaces.md create mode 100644 docs/en/messaging.md create mode 100644 docs/en/nomad-network.md create mode 100644 docs/en/nomadmesh-pages.md rename meshchatx/src/frontend/public/meshchatx-docs/meshchatx_on_android_with_termux.md => docs/en/platform-guides/android-termux.md (98%) rename meshchatx/src/frontend/public/meshchatx-docs/meshchatx_linux_sandbox.md => docs/en/platform-guides/linux-sandbox.md (99%) rename meshchatx/src/frontend/public/meshchatx-docs/meshchatx_on_quest_with_sidequest.md => docs/en/platform-guides/quest-sidequest.md (91%) rename docs/{meshchatx_on_raspberry_pi.md => en/platform-guides/raspberry-pi.md} (99%) create mode 100644 docs/en/tools.md create mode 100644 docs/manifest.json delete mode 100644 docs/meshchatx.md delete mode 100644 docs/nomadmesh_pages.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/architecture.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/audio-calls.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/getting-started.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/identity-and-security.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/installation.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/interfaces.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/messaging.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/nomad-network.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/nomadmesh-pages.md rename docs/meshchatx_on_android_with_termux.md => meshchatx/src/frontend/public/meshchatx-docs/en/platform-guides/android-termux.md (98%) rename docs/meshchatx_linux_sandbox.md => meshchatx/src/frontend/public/meshchatx-docs/en/platform-guides/linux-sandbox.md (99%) rename docs/meshchatx_on_quest_with_sidequest.md => meshchatx/src/frontend/public/meshchatx-docs/en/platform-guides/quest-sidequest.md (91%) rename meshchatx/src/frontend/public/meshchatx-docs/{meshchatx_on_raspberry_pi.md => en/platform-guides/raspberry-pi.md} (99%) create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/en/tools.md create mode 100644 meshchatx/src/frontend/public/meshchatx-docs/manifest.json delete mode 100644 meshchatx/src/frontend/public/meshchatx-docs/meshchatx.md delete mode 100644 meshchatx/src/frontend/public/meshchatx-docs/nomadmesh_pages.md diff --git a/docs/en/architecture.md b/docs/en/architecture.md new file mode 100644 index 00000000..6267173d --- /dev/null +++ b/docs/en/architecture.md @@ -0,0 +1,146 @@ +# Architecture and design + +MeshChatX is a heavily extended fork of Reticulum MeshChat. The goals below shaped how the codebase is organized. + +## Design goals + +- Keep a local-first runtime that works on desktop, mobile, containers, and single-board computers. +- Preserve Reticulum and LXMF semantics while improving usability and operational tooling. +- Support multiple identities in one process without cross-identity data leakage. +- Keep the Python backend and Vue frontend independently testable. +- Run in constrained environments with predictable SQLite behaviour. + +## Process overview + +One Python process owns the web server, Reticulum stack, and all per-identity managers. The Vue frontend is static assets served from `meshchatx/public/` after a Vite build. + +``` +ReticulumMeshChat (meshchat.py) + | + +-- HTTP routes (/api/v1/*, static files) + +-- WebSocket (/ws, /ws/telephone/audio) + +-- IdentityContext (per active identity) + | +-- SQLite via database layer + | +-- LXMRouter + | +-- TelephoneManager (LXST) + | +-- Domain managers (messages, map, docs, RRC, ...) + +-- Shared Reticulum instance (~/.reticulum by default) +``` + +Optional **Electron** wraps the same backend binary and loads the UI from the local HTTPS server. + +## Application shell + +`ReticulumMeshChat` in `meshchatx/meshchat.py` is the orchestration layer. It registers routes, starts and stops identity contexts, wires crash recovery, and coordinates shared process concerns. + +Path helpers live in `meshchatx/src/path_utils.py`, `ssl_self_signed.py`, and `env_utils.py`. `meshchat.py` re-exports them for compatibility. + +## Identity-scoped context + +`IdentityContext` in `meshchatx/src/backend/identity_context.py` encapsulates everything tied to one cryptographic identity: + +- Storage under `storage/identities//` +- Identity-local SQLite database (schema version tracked in migrations) +- LXMF router state and propagation directories +- Manager instances for messages, announces, docs, maps, forwarding, bots, RRC, Nomad page nodes, and more + +Switching identities tears down the old context and loads another. Global mutable state that could leak between identities is avoided by design. + +## Manager-centric domain logic + +Feature behaviour lives in modules under `meshchatx/src/backend/`. Examples include message handling, announce trimming, documentation, maps, page nodes, telemetry, interfaces, forwarding aliases, and RN-specific tool handlers. + +`meshchat.py` should stay focused on transport and lifecycle. Business rules belong in managers where they can be unit tested. + +## Persistence + +- **Engine:** SQLite with explicit SQL and migrations (no ORM). +- **Schema:** Versioned migrations run during startup and identity setup. +- **Backups:** Automatic and manual database backups under `database-backups/`. +- **Recovery:** `--auto-recover`, emergency mode, and Electron crash UI can restore from backups. + +## HTTP API + +Routes are registered explicitly on the aiohttp application. Categories include: + +- Application status and configuration +- Authentication and session management +- LXMF messaging and conversations +- Telephone and voicemail +- Interfaces and Reticulum configuration +- Nomad Network and page nodes +- RRC client and server +- Tools (ping, RNPath, RNCP, RNSH, translator, bots) +- Documentation and maintenance + +The frontend uses `fetch` via `apiClient.js` with CSRF tokens on mutating requests. + +## WebSockets + +The UI connects to `/ws` for low-latency updates. Event types include new LXMF messages, identity switches, telephone state, RRC activity, Nomad download progress, RNCP transfers, and plugin events. Handlers are registered in `wsEventRegistry.js` and dispatched through `wsEventBridge.js`. + +Audio calls can use `/ws/telephone/audio` for browser-side codec bridging. + +## Security model + +MeshChatX defaults toward secure local operation: + +- HTTPS and WSS enabled by default. +- Self-signed certificates generated per identity when custom PEM files are absent. +- Optional HTTP basic authentication (`--auth`). +- Encrypted session cookies via `aiohttp_session`. +- CORS, CSP, and defensive middleware on HTTP responses. +- Access attempt logging with lockout when auth is enabled. + +The project includes extensive automated tests around auth and sessions. Even so, exposing MeshChatX directly to the public internet is not recommended without additional hardening. + +Password reset is available with `--reset-password` or `MESHCHAT_RESET_PASSWORD=true`, which clears the stored bcrypt hash so you can set a new password in the UI. + +## Build and packaging + +One source tree produces: + +- Development runs via `uv run python -m meshchatx.meshchat` +- Python wheels with bundled `public/` assets +- Container images (Dockerfile and hardened variants) +- Electron builds for Windows, macOS, and Linux +- Android APK via Chaquopy + +Frontend build output always lands in `meshchatx/public/` so runtime behaviour matches across targets. + +## Reliability features + +- Crash recovery integration in Electron and backend startup checks +- Database integrity verification +- Backup, restore, and snapshot APIs +- Explicit teardown when switching identities or shutting down forwarding resources +- Health and status endpoints suitable for container probes + +## Extensibility + +MeshChatX supports plugins with separate frontend and backend runtimes: + +- **Contribution registries** under `meshchatx/src/frontend/js/registries/` for navigation, tools, commands, settings, and WebSocket events. +- **Frontend plugins** run in dedicated Workers (`PluginHost.js`) with declarative UI slots. +- **Backend plugins** run in wasmtime with fuel metering and capability-gated host functions. +- **HTTP API** under `/api/v1/plugins/*` for install, enable, invoke, and assets. + +Practical extension paths today: + +- Plugin manifests with `contributes` and `permissions` blocks +- New API routes and manager modules +- Frontend pages wired through registries +- New settings via `ConfigManager` and CLI or environment variables +- Database schema changes through migrations + +When adding features, prefer identity-scoped state, explicit migrations, endpoint tests, and narrowly declared plugin permissions. + +## NomadNet and Mesh Server + +The Nomad browser and Mesh Server (page nodes) share a rendering pipeline for Micron, Markdown, plain text, and sanitised HTML. Authoring rules are documented in **NomadNet page formats**. + +## Related reading + +- **Getting started** for UI navigation and first steps. +- **LXMF messaging**, **Audio calls**, and **Reticulum interfaces** for feature behaviour. +- The **Reticulum** tab in Documentation for protocol reference. diff --git a/docs/en/audio-calls.md b/docs/en/audio-calls.md new file mode 100644 index 00000000..c2ce38ce --- /dev/null +++ b/docs/en/audio-calls.md @@ -0,0 +1,87 @@ +# Audio calls (LXST) + +MeshChatX uses LXST for voice telephony over Reticulum. Telephone functionality is optional and controlled per identity in settings. + +## Enable telephony + +Turn on **telephone** in settings before using the **Call** page. MeshChatX announces your callable destination under aspect `lxst.telephony` when announcing is enabled. + +Peers who announce the same aspect appear as callable contacts. + +## Placing and receiving calls + +From **Call** or a contact entry you can: + +- **Dial** another identity by hash +- **Answer** or **decline** inbound rings +- **Hang up** an active session +- **Mute** transmit or receive paths + +Call state changes arrive over the WebSocket (`telephone_ringing`, `telephone_call_established`, `telephone_call_ended`, and related events). + +## Audio path + +The frontend loads Codec2 assets for voice encoding (`Codec2Loader.js`). Browser and Electron builds use a Web Audio bridge at `/ws/telephone/audio`. Packaged desktop builds bundle the backend that negotiates LXST sessions. + +## Voicemail + +When you miss a call, voicemail may be offered depending on settings: + +- Record a custom greeting +- Upload or generate greeting audio +- Play back messages left for you + +Voicemail events surface as `new_voicemail` on the WebSocket. + +## Call history and recordings + +The **Call** area keeps history of placed, received, and missed calls. You can record calls when the feature is enabled and policy allows storage on your device. + +## Ringtones + +Upload custom ringtones and assign them per contact. Default sounds are used when no override exists. + +## Do not disturb and contacts-only + +Settings support: + +- **Do not disturb** to silence inbound rings +- **Contacts-only** mode to reject calls from unknown hashes + +Combine these with the **Blocked** list for finer control. + +## Telephone contacts + +Import and export telephone contacts separately from LXMF conversation peers. Contacts drive caller display names and ringtone overrides. + +## Call setup flow + +``` +Caller UI: initiate call + | + v +GET /api/v1/telephone/call/{identity_hash} + | + v +LXST Telephone session over Reticulum + | + +--> Signalling and media via LXST + | + +--> /ws/telephone/audio (browser audio bridge) + | + v +Callee UI: ring, answer, or decline +``` + +## Tips + +- Verify **Interfaces** and paths before troubleshooting audio quality. Packet loss on the mesh affects voice. +- Use headphones on mobile and Quest builds to prevent echo. +- Review microphone permissions in Electron or the Android system settings if the UI shows no input level. +- Keep LXST and Reticulum versions aligned with MeshChatX release notes when upgrading. + +## See also + +- **LXMF messaging** for text conversations with the same peers +- **Identities, privacy, and security** for HTTPS and local access controls +- LXST project documentation for codec and session details diff --git a/docs/en/getting-started.md b/docs/en/getting-started.md new file mode 100644 index 00000000..441c8f07 --- /dev/null +++ b/docs/en/getting-started.md @@ -0,0 +1,97 @@ +# Getting started with MeshChatX + +MeshChatX is a local-first mesh communications client built on the Reticulum Network Stack. It combines direct messaging over LXMF, voice calls over LXST, NomadNet page browsing, relay chat, maps, and a large set of Reticulum utilities in one application you can run on a desktop, a headless server, or a mobile device. + +MeshChatX is an independent fork of [Reticulum MeshChat](https://github.com/liamcottle/reticulum-meshchat). It is not affiliated with the upstream project. The website is [meshchatx.com](https://meshchatx.com). Source and releases live on [GitHub](https://github.com/Quad4-Software/MeshChatX). + +## What you need to know first + +Reticulum is the mesh networking layer. It handles identities, paths, interfaces, and encrypted transport between nodes. LXMF is the messaging protocol MeshChatX uses for conversations, attachments, and propagation. LXST is the telephony layer used for audio calls. + +MeshChatX does not replace Reticulum. It runs Reticulum inside a Python process, exposes a web UI, and stores your per-identity data locally in SQLite. + +## How the application is laid out + +When you open MeshChatX you work inside a single-page web interface. The sidebar lists the main areas of the app. The **Tools** page groups diagnostics and utilities. **Settings** holds per-identity configuration. **Identities** lets you create or switch between separate cryptographic identities. + +Typical first-day workflow: + +1. Install MeshChatX using a method that fits your device. See **Installation and setup**. +2. Open the web UI. The default address is `https://127.0.0.1:8000` when HTTPS is enabled. +3. Go to **Interfaces** and add a way to reach the mesh. A TCP client, community interface suggestion, or LoRa RNode are common starting points. +4. Wait for paths and announces to populate. Peers appear in the announces list and in feature-specific views. +5. Open **Messages** to start an LXMF conversation, or **Nomad Network** to browse a page node. + +## Runtime shape + +MeshChatX ships as one Python service that serves both the API and the built frontend assets. + +``` +Browser or Electron window + | + v +Vue 3 frontend (hash routes such as #/messages) + | + | REST under /api/v1/* and WebSocket at /ws + v +meshchatx/meshchat.py (aiohttp server) + | + +--> SQLite database (per identity) + +--> LXMF router and message store + +--> LXST telephone (when enabled) + +--> Reticulum stack (interfaces, paths, announces) +``` + +The same backend code powers Docker images, Python wheels, Linux packages, Electron desktop builds, and the Android APK. Packaging differs. Behaviour is intended to stay consistent. + +## Main areas of the UI + +| Area | Route | Purpose | +| ------------------ | --------------------- | ----------------------------------------------------- | +| Messages | `/messages` | LXMF direct messaging, folders, attachments | +| Audio calls | `/call` | LXST voice calls and voicemail | +| Contacts | `/contacts` | Telephone contacts and call-related entries | +| Relay chat | `/relay-chat` | RRC hubs and rooms (when enabled in settings) | +| Nomad Network | `/nomadnetwork` | Browse remote NomadNet pages and files | +| Map | `/map` | OpenLayers map, offline tiles, telemetry | +| Archives | `/archives` | Versioned snapshots of Nomad pages | +| Tools | `/tools` | Ping, path tools, RNCP, bots, documentation, and more | +| Interfaces | `/interfaces` | Add and manage Reticulum interfaces | +| Network visualiser | `/network-visualiser` | Graph view of mesh topology | +| Blocked | `/blocked` | Blocked destinations | +| Settings | `/settings` | Theme, language, LXMF, telephone, security | +| Identities | `/identities` | Create, import, or switch identities | +| Documentation | `/documentation` | MeshChatX guides and the Reticulum manual | + +Relay chat appears only when `rrc_enabled` is turned on in settings. + +## Documentation in the app + +The **Documentation** page has two tabs. + +**MeshChatX** shows the guides in this bundle. They are markdown files synced from the `docs/` directory in the repository and rendered offline inside the app. + +**Reticulum** shows the upstream Reticulum manual as pre-built HTML. It is bundled at build time. You can upload a newer manual ZIP if you need a different version. + +Use the search bar to query both sets at once. MeshChatX guide text is currently available in English. The Reticulum manual body is English. Localized landing pages exist for several languages on the Reticulum tab. + +## Storage locations + +| Data | Typical path | +| --------------------- | -------------------------------------------------- | +| MeshChatX app data | `~/.reticulum-meshchatx/` on Linux and macOS | +| Reticulum config | `~/.reticulum/` | +| Per-identity database | `/identities//database.db` | +| Docker volume | `meshchatx-config` mounted at `/config` | + +Legacy upstream data may still exist under `~/.reticulum-meshchat/`. Migration tooling can move you to the MeshChatX layout. + +## Where to go next + +- **Installation and setup** covers Docker, wheels, desktop packages, and development builds. +- **Architecture and design** explains backend managers, identity scoping, and the API model. +- **LXMF messaging** and **Audio calls** describe day-to-day communication features. +- **Reticulum interfaces** explains how your node joins the mesh. +- Platform guides under **Platform guides** cover Raspberry Pi, Android Termux, Meta Quest, and Linux sandboxing. + +For protocol-level detail, open the **Reticulum** tab in Documentation or visit the [Reticulum manual](https://reticulum.network/manual/) online. diff --git a/docs/en/identity-and-security.md b/docs/en/identity-and-security.md new file mode 100644 index 00000000..b95e3fb1 --- /dev/null +++ b/docs/en/identity-and-security.md @@ -0,0 +1,105 @@ +# Identities, privacy, and security + +MeshChatX separates cryptographic identities, network security, and optional privacy controls. This page summarises how they interact. + +## Identities + +Each identity is a Reticulum key pair with its own: + +- SQLite database and LXMF router directory +- Settings in the `config` table via `ConfigManager` +- Storage path under `storage/identities//` + +Create, import, or switch identities from **Identities**. Only one identity is active in the UI at a time. Switching runs a teardown path so routers and managers do not leak state. + +Shared resources include the Reticulum process and interface configuration in `~/.reticulum` unless you override paths. + +## Announces + +MeshChatX tracks announces for aspects such as: + +| Aspect | Meaning | +| ------------------- | --------------------------------- | +| `lxmf.delivery` | Peer accepts LXMF messages | +| `lxst.telephony` | Peer accepts LXST calls | +| `lxmf.propagation` | Propagation node | +| `nomadnetwork.node` | NomadNet page server | +| `rrc.hub` | Relay chat hub (when RRC enabled) | + +Announce records store signal metadata and parsed app data for display names and icons. + +## Web UI authentication + +Optional HTTP basic authentication is enabled with `--auth` or `MESHCHAT_AUTH=true`. Sessions use encrypted cookies. Mutating API requests require CSRF tokens. + +Access attempts are logged. Repeated failures can trigger lockout when auth is enabled. + +Reset a forgotten password with `--reset-password` or `MESHCHAT_RESET_PASSWORD=true`, then set a new password in the UI. + +## Transport security + +- HTTPS and WSS are on by default. +- Self-signed certificates are generated per identity when custom PEM files are missing. +- Pass `--ssl-cert` and `--ssl-key` for managed certificates. +- Use `--no-https` only on trusted loopback setups. + +Electron loads the UI from the local HTTPS origin served by the embedded backend. + +## IP allowlisting + +`app_security_settings` can restrict which client IPs may use the web UI. Combine with auth when exposing the service beyond localhost. + +## Privacy mode + +**Privacy mode** blocks outbound HTTP from MeshChatX features that would otherwise call the public internet. Translation and similar tools respect this flag. + +Privacy mode does not disable Reticulum mesh traffic. It limits clearnet fetches from the app itself. + +## Linux sandboxing + +Optional Landlock sandboxing on Linux restricts filesystem access for the backend. See **Linux sandboxing** in Platform guides for Firejail and Bubblewrap examples. + +## Blocking and filtering + +Use **Blocked** for specific destination hashes. Combine with sieve filters, message blocklists, and LXMF stamp policies described in **LXMF messaging**. + +## Data backup + +Database backups land in `database-backups/`. Export snapshots from **About** or the API. Electron crash recovery can offer restore when integrity checks fail. + +CLI restore example: + +```bash +meshchatx --restore-db /path/to/backup.zip +``` + +## Integrity checks + +Startup integrity verification runs in packaged Electron builds and can be triggered from the backend. Failed checks surface recovery options instead of silently corrupting data. + +## Safe deployment patterns + +``` +Recommended for most users + | + v +Bind 127.0.0.1, use HTTPS, enable auth if others use the same host + | + v +Add interfaces only for meshes you trust + | + v +Keep backups and test restore on upgrades +``` + +Avoid exposing port 8000 directly to the internet without a reverse proxy, strong auth, and network-level filtering. MeshChatX is designed as a personal or small-team operator console, not a multi-tenant public website. + +## Multi-user hosts + +On shared computers, use separate OS user accounts or separate `--storage-dir` values so SQLite databases and identity files do not overlap. + +## See also + +- **Architecture and design** for session and API details +- **Installation and setup** for CLI security flags +- Reticulum manual cryptography chapters for identity math diff --git a/docs/en/installation.md b/docs/en/installation.md new file mode 100644 index 00000000..2d55425a --- /dev/null +++ b/docs/en/installation.md @@ -0,0 +1,153 @@ +# Installation and setup + +MeshChatX can be installed in several ways. All release artifacts that ship the web UI include pre-built frontend assets. You do not need Node.js on the machine that only runs the Python wheel or Docker image. + +## Requirements + +| Component | Version | +| --------- | -------------------------------------------------- | +| Python | 3.11 or newer (`pyproject.toml`) | +| Node.js | 24 or newer (development and frontend builds only) | +| pnpm | 11.1.2 (development) | +| UV | Used by Taskfile and CI | + +**Browsers for the web UI:** Safari 16.4+, Chrome 111+, Firefox 128+. + +## Choose an install method + +| Method | Frontend included | Best for | +| ---------------- | ----------------- | ---------------------------------------- | +| Docker image | Yes | Fast server setup on Linux | +| Python wheel | Yes | Headless install without building the UI | +| Linux AppImage | Yes | Portable desktop on x64 or arm64 | +| Debian `.deb` | Yes | Debian and Ubuntu systems | +| RPM package | Yes | Fedora, RHEL, openSUSE style systems | +| Electron desktop | Yes | Integrated desktop with bundled backend | +| Android APK | Yes | Phones, tablets, Meta Quest sideload | +| From source | Built locally | Development and custom builds | + +Release images are published to Docker Hub (`quad4io/meshchatx`) and GHCR (`ghcr.io/quad4-software/meshchatx`). + +## Docker + +Quick start with Compose: + +```bash +docker compose up -d +``` + +Manual run with a named volume for persistence: + +```bash +docker run -d --name reticulum-meshchatx \ + --restart unless-stopped \ + --security-opt no-new-privileges:true \ + -p 127.0.0.1:8000:8000 \ + -v meshchatx-config:/config \ + ghcr.io/quad4-software/meshchatx:latest +``` + +Default Compose maps `127.0.0.1:8000` on the host to port `8000` in the container. Data persists in the `meshchatx-config` volume at `/config`. + +To bind a host directory instead, mount it at `/config`. The container runs as UID 1000. The host directory must be writable by that user. + +## Python wheel + +1. Download `reticulum_meshchatx-*-py3-none-any.whl` from [releases](https://github.com/Quad4-Software/MeshChatX/releases). +2. Install with pip, pipx, or uv: + +```bash +pip install reticulum_meshchatx-*.whl +``` + +3. Start the server: + +```bash +meshchatx --headless --host 127.0.0.1 +``` + +The `meshchat` command is a compatibility alias for the same entry point. + +## Linux AppImage and packages + +**AppImage** + +```bash +chmod +x ./ReticulumMeshChatX-v*-linux-*.AppImage +./ReticulumMeshChatX-v*-linux-*.AppImage +``` + +**Debian package** + +```bash +sudo dpkg -i reticulum-meshchatx_*_amd64.deb +``` + +Adjust the filename for your architecture. + +## From source (development) + +```bash +task install +pnpm run build-frontend +uv run python -m meshchatx.meshchat --headless --host 127.0.0.1 +``` + +Useful task targets include `task format`, `task lint`, `task test`, and `task build`. + +## First launch + +On first run MeshChatX creates a random Reticulum identity if you do not pass one on the command line. The identity file is stored under your configured storage directory. + +Open the UI at the host and port you chose. HTTPS is enabled by default with a self-signed certificate unless you pass `--no-https` or provide your own PEM files. + +## Command-line options + +Common flags and environment variables: + +| Flag | Environment variable | Default | Description | +| ------------------------ | ------------------------ | -------------- | ---------------------------------- | +| `--host` | `MESHCHAT_HOST` | `127.0.0.1` | Bind address | +| `--port` | `MESHCHAT_PORT` | `8000` | HTTP or HTTPS port | +| `--no-https` | `MESHCHAT_NO_HTTPS` | false | Serve plain HTTP | +| `--ssl-cert` | `MESHCHAT_SSL_CERT` | auto | TLS certificate path | +| `--ssl-key` | `MESHCHAT_SSL_KEY` | auto | TLS private key path | +| `--headless` | `MESHCHAT_HEADLESS` | false | Do not open a browser | +| `--auth` | `MESHCHAT_AUTH` | false | Require HTTP basic auth for the UI | +| `--storage-dir` | `MESHCHAT_STORAGE_DIR` | `./storage` | Application data directory | +| `--reticulum-config-dir` | (see `--help`) | `~/.reticulum` | Reticulum configuration | +| `--identity-file` | `MESHCHAT_IDENTITY_FILE` | none | Load identity from file | +| `--rns-log-level` | `MESHCHAT_RNS_LOG_LEVEL` | none | Reticulum log level | +| `--auto-recover` | `MESHCHAT_AUTO_RECOVER` | false | Attempt SQLite recovery on start | +| `--emergency` | | false | Start without database | +| `--disable-plugins` | | false | Disable the plugin system | + +CLI flags override environment variables when both are set. + +## Reticulum manual bundle + +The Reticulum HTML manual is fetched at build time. After cloning the repository, run: + +```bash +pnpm run build-docs +``` + +This populates `meshchatx/public/reticulum-docs-bundled/current/`. Without that step the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP. + +## Identity bootstrap + +You can supply an identity at startup: + +- `--identity-file /path/to/identity` +- `--identity-base64` or `--identity-base32` with the corresponding environment variables + +Otherwise MeshChatX generates one and saves it under `/identity`. Additional identities are created from the **Identities** page. Each identity has its own database, LXMF router, and settings while sharing one Reticulum process. + +## After install + +1. Add at least one **interface** so Reticulum can reach peers. +2. Review **Settings** for display name, theme, language, and LXMF stamp costs. +3. Enable **telephone** in settings if you plan to use audio calls. +4. Open **Documentation** for MeshChatX guides and the Reticulum manual offline. + +Platform-specific notes live under **Platform guides** in this documentation bundle. diff --git a/docs/en/interfaces.md b/docs/en/interfaces.md new file mode 100644 index 00000000..edbc435f --- /dev/null +++ b/docs/en/interfaces.md @@ -0,0 +1,87 @@ +# Reticulum interfaces + +Interfaces connect your MeshChatX node to the Reticulum mesh. Manage them from the **Interfaces** page. + +## What an interface does + +Each interface is a Reticulum transport definition. Examples include TCP over the internet, UDP discovery, LoRa through an RNode, serial KISS devices, I2P tunnels, and automatic LAN discovery. + +MeshChatX reads and writes interface configuration in your Reticulum config directory (default `~/.reticulum`). + +## Supported interface types + +The **Add interface** flow includes: + +| Type | Typical use | +| --------------------- | --------------------------------------------- | +| TCPClientInterface | Connect outbound to a known TCP peer | +| TCPServerInterface | Accept inbound TCP connections | +| BackboneInterface | High-throughput backbone link | +| UDPInterface | UDP transport with discovery helpers | +| RNodeInterface | LoRa via RNode (serial, BLE, or IP transport) | +| RNodeIPInterface | RNode reached over IP | +| SerialInterface | Direct serial devices | +| KISSInterface | KISS TNC devices | +| I2PInterface | I2P-based Reticulum transport | +| AutoInterface | Automatic discovery on local networks | +| Custom external types | Advanced setups | + +Community-curated suggestions come from `community_interfaces.json`, sourced from [directory.rns.recipes](https://directory.rns.recipes). + +## Interface discovery + +Discovery can automatically connect to peers on your LAN or configured networks. You can maintain allowlists and blocklists, set autoconnect behaviour, and assign a network identity for discovered peers. + +## Import and export + +Export your interface set for backup or clone it to another machine. Import validates entries before applying them. + +## RNode tools + +LoRa setups often need firmware management. **Tools → RNode Flasher** opens the bundled flasher at `/rnode-flasher/`. Configure frequency, bandwidth, spreading factor, and TX power when adding an RNode interface. + +## Websocket server interface + +MeshChatX includes a custom `WebsocketServerInterface` for WebSocket-based Reticulum transport. Use it when bridging to web-friendly gateways. + +## Getting onto the mesh + +A minimal path for a new node: + +``` +Install MeshChatX + | + v +Add interface (TCP client, community suggestion, or RNode) + | + v +Reticulum establishes transport + | + v +Paths and announces populate in the UI + | + v +LXMF, LXST, and Nomad features become reachable +``` + +1. Pick a community interface or ask your mesh operator for TCP endpoint details. +2. Add the interface and enable it. +3. Watch the path table (**Tools → RNPath**) if connectivity fails. +4. Enable **auto-announce** so your services are visible. + +## Bundled documentation hints + +The Interfaces UI links into the Reticulum manual sections on interface options. Open **Documentation → Reticulum** and search for `interfaces` if you need field-by-field reference. + +## Tips + +- Run only the interfaces you need. Each open port or radio adds attack surface and power draw. +- On Raspberry Pi and Android, prefer a single well-known TCP uplink if LoRa hardware is not attached. +- After editing Reticulum config externally, use the reload controls or restart MeshChatX so changes apply cleanly. +- Keep firmware on RNodes current using the flasher tool before debugging RF issues. + +## See also + +- **Installation and setup** for Reticulum config directory flags +- **Tools and utilities** for RNPath, RNProbe, and Ping +- Reticulum manual **Interfaces** chapter for protocol-level detail diff --git a/docs/en/messaging.md b/docs/en/messaging.md new file mode 100644 index 00000000..d54688de --- /dev/null +++ b/docs/en/messaging.md @@ -0,0 +1,111 @@ +# LXMF messaging + +MeshChatX uses LXMF (LXMF Message Format) for direct and store-and-forward messaging over Reticulum. Each identity has an `LXMRouter` registered under aspect `lxmf.delivery`. + +## Conversations + +Open **Messages** to see your conversation list. Each row is a peer destination you have exchanged traffic with or selected from announces. + +From a conversation you can: + +- Send and receive text messages +- Attach images, audio clips, and files +- Reply with quotes and add reactions +- Organise threads into folders and pin important chats +- Run bulk operations on multiple conversations + +Incoming messages arrive over the WebSocket as `lxmf_message` events. The UI updates without a full page reload. + +## Attachments and rich content + +The composer supports: + +- **Images** via LXMF image fields +- **Audio** via LXMF audio fields +- **Files** as LXMF file attachments +- **Stickers and GIFs** when enabled in settings +- **User icons** stored as LXMF app data + +Large payloads follow LXMF sizing and stamp rules configured in settings. + +## Propagation nodes + +When a peer is not reachable directly, LXMF can store messages on propagation nodes. + +MeshChatX can: + +- Run a **local propagation node** on your identity +- **Sync** with remote propagation nodes you trust +- **Auto-select** a preferred node via `AutoPropagationManager` +- **Retry** failed direct deliveries through propagation when configured + +Manage nodes from **Tools → Propagation nodes** or related settings entries. + +## Stamp costs and stranger protection + +LXMF uses work proofs (stamps) to limit abuse. Settings let you tune: + +- Outbound stamp costs for your messages +- Inbound stamp requirements for unknown senders +- **Stranger protection** options such as blocking strangers, attachments, or links from unknown peers +- **Flood protection** with dynamic inbound stamp costs based on rate + +Raise inbound costs when you operate a public-facing node. Lower them on trusted private meshes. + +## Filtering and blocking + +- **Blocked** destinations stop traffic from specific hashes. +- **Sieve filters** (beta) drop inbound messages by pattern. +- **Message blocklist** (beta) complements sieve rules for known bad content. +- **Spam reporting** helps you mark unwanted conversations. + +## Paper messages + +**Tools → Paper message** generates LXMF URIs you can share as QR codes. Another MeshChatX user can ingest the URI to receive the payload. Useful for offline handoff when no live path exists yet. + +## Forwarding + +`ForwardingManager` supports alias identities that forward messages between peers according to rules you define. Configure forwarding from **Tools → Forwarder**. + +## Import and export + +You can import and export messages and folder structures for backup or migration. Operations go through the API and respect identity boundaries. + +## Local retention + +**Local message auto-delete** removes old messages after a configured retention period. Tune this in settings if you operate on storage-constrained hardware. + +## Messaging flow + +``` +Composer in UI + | + v +POST /api/v1/lxmf-messages/send + | + v +LXMRouter (identity-local) + | + +--> Direct path to peer destination + | + +--> Propagation node (when direct delivery fails or policy requires it) + | + v +Peer LXMF router + | + v +WebSocket lxmf_message event on recipient UI +``` + +## Tips + +- Set a **display name** in settings so announces show a friendly label. +- Enable **auto-announce** so your `lxmf.delivery` aspect stays visible on the mesh. +- Check **Interfaces** if messages stall. No path to the peer means LXMF cannot deliver. +- Review stamp settings before joining busy public meshes. + +## See also + +- **Reticulum interfaces** for connectivity +- **Identities, privacy, and security** for auth and HTTPS +- Reticulum manual section on LXMF for protocol detail diff --git a/docs/en/nomad-network.md b/docs/en/nomad-network.md new file mode 100644 index 00000000..0c0cd821 --- /dev/null +++ b/docs/en/nomad-network.md @@ -0,0 +1,77 @@ +# Nomad Network and Mesh Server + +Nomad Network is a distributed page and file system on top of Reticulum. MeshChatX includes a browser for remote nodes and a **Mesh Server** tool for hosting your own pages. + +## Nomad browser + +Open **Nomad Network** and enter a node destination hash. MeshChatX fetches the default entry page (usually `/page/index.mu`) over Reticulum link requests. + +Supported page types: + +| Extension | Format | +| --------- | ------------------------------------ | +| `.mu` | Micron markup (NomadNet default) | +| `.md` | Markdown with GFM-oriented rendering | +| `.txt` | Plain text with preserved whitespace | +| `.html` | Static HTML with sanitised CSS | + +Follow links inside pages to browse further paths on the same node. Download files offered at `/file/*` paths. + +Rendering uses `NomadPageRenderer.js` with DOMPurify sanitization. Micron can use a JavaScript parser or optional Go WASM when `nomad_micron_wasm_enabled` is set. + +## Favourites and caching + +Save frequent nodes as favourites. Link caching (`nomadnet_cached_links`) speeds up repeat visits on slow links. + +## Archives + +When **page archiver** is enabled, MeshChatX stores versioned snapshots of pages you visit. Open **Archives** to browse historical copies. An optional crawler can archive automatically. + +Archived pages use the same renderer as the live browser based on the stored `page_path` extension. + +## Mesh Server (page nodes) + +**Tools → Mesh Server** lets you run a `nomadnetwork.node` destination locally. + +Typical workflow: + +1. Create a page node in the UI. +2. Upload `.mu`, `.md`, `.txt`, or `.html` pages and optional files. +3. Start the node and announce it on the mesh. +4. Share your destination hash so others can open `/page/index.mu` on your node. + +API endpoints under `/api/v1/page-nodes/` manage CRUD operations, start and stop, and file listings. + +Pages are served at `/page/` and files at `/file/` on the node destination. + +## Browsing flow + +``` +User enters destination hash + | + v +RNS link request to /page/index.mu (or chosen path) + | + v +Remote page node responds with content + | + v +NomadPageRenderer picks Micron, Markdown, text, or HTML pipeline + | + v +Sanitised HTML shown in Nomad Network view +``` + +## Authoring pages + +Read **NomadNet page formats** for security rules, Markdown quirks, and API behaviour. The Mesh Server rejects disallowed extensions on upload. + +## Micron editor + +**Tools → Micron editor** helps author `.mu` pages before you upload them to your node. + +## See also + +- **NomadNet page formats** for detailed authoring reference +- **Tools and utilities** for the full tools list +- **Reticulum interfaces** if remote pages time out (likely a path issue) diff --git a/docs/en/nomadmesh-pages.md b/docs/en/nomadmesh-pages.md new file mode 100644 index 00000000..325726d9 --- /dev/null +++ b/docs/en/nomadmesh-pages.md @@ -0,0 +1,52 @@ +# NomadNet page formats + +MeshChatX serves pages from a **Mesh Server** page node and displays them in the **Nomad Network** browser. Pages are fetched with the Nomad path convention `/page/`. + +## Supported filenames + +| Extension | Role | +| --------- | ------------------------------------------------------- | +| `.mu` | Micron markup (NomadNet default) | +| `.md` | Markdown with GitHub-flavored features via the renderer | +| `.txt` | Plain text with escaped HTML and preserved whitespace | +| `.html` | Static HTML with CSS only (see security below) | + +If you add a page without a recognised extension, the server stores it as `.mu`. Filenames with other extensions (for example `.exe`) are rejected when saving through the API. + +## Plain text (`.txt`) + +Content is HTML-escaped and shown with pre-wrapped whitespace. There is no Markdown parsing on `.txt` pages. + +## Markdown (`.md`) + +**Not the same engine as chat.** Conversations use the lightweight `MarkdownRenderer` in the messaging UI. Nomad `.md` pages use `marked` with GFM-oriented rules plus sanitisation. Features and edge cases can differ between the two paths. Automated tests cover both. + +Authoring tips: + +- Use ATX headings with a hash and a space before the title, for example `# Title`, `## Section`, `#### Subsection`. +- Fenced code blocks keep indentation. +- Off-mesh `http` and `https` links in rendered content are removed or restricted so the preview cannot drive external navigation without mesh-style URLs. + +## HTML (`.html`) + +- **JavaScript** is not executed. `script` tags and event-handler attributes are stripped. +- **External resources** are blocked where possible. `@import` and `url(...)` pointing at `http://`, `https://`, or protocol-relative URLs are removed from CSS. +- Embedded ` - - -
- {html_content} -
- -""" - html_file = os.path.splitext(file)[0] + ".html" - with open( - os.path.join(self.meshchatx_docs_dir, html_file), - "w", - encoding="utf-8", - ) as f: - f.write(full_html) - index_links.append( - f'
  • {html_file}
  • ' - ) - except Exception as e: - logging.exception(f"Failed to render {file} to HTML: {e}") - - # Generate an index.html so /meshchatx-docs/index.html resolves - if index_links and os.access(self.meshchatx_docs_dir, os.W_OK): - index_html = f""" - - - - - MeshChatX Documentation - - - - -

    MeshChatX Documentation

    -
      - {"".join(index_links)} -
    - -""" - with open( - os.path.join(self.meshchatx_docs_dir, "index.html"), - "w", - encoding="utf-8", - ) as f: - f.write(index_html) + self._sync_docs_tree(src_docs, self.meshchatx_docs_dir) + self._render_meshchatx_html_exports() except Exception as e: logging.exception(f"Failed to populate MeshChatX docs: {e}") + def _sync_docs_tree(self, src_docs, dest_dir): + """Copy manifest, markdown, and text files from src_docs into dest_dir.""" + for root, _, files in os.walk(src_docs): + rel_root = os.path.relpath(root, src_docs) + target_root = ( + dest_dir if rel_root == "." else os.path.join(dest_dir, rel_root) + ) + os.makedirs(target_root, exist_ok=True) + for file in files: + if file == MANIFEST_FILENAME or file.endswith(DOC_FILE_SUFFIXES): + src_path = os.path.join(root, file) + dest_path = os.path.join(target_root, file) + if os.path.abspath(src_path) != os.path.abspath(dest_path): + shutil.copy2(src_path, dest_path) + + def _render_meshchatx_html_exports(self): + index_links: list[str] = [] + manifest, _manifest_error = self._read_manifest() + if manifest and manifest.get("sections"): + for section in sorted( + manifest["sections"], key=lambda s: s.get("order", 0) + ): + section_title = self._localized_text( + section.get("title"), + manifest.get("default_language", "en"), + ) + if section_title: + index_links.append( + f'
  • {html.escape(section_title)}
  • ', + ) + for item in section.get("items", []): + rel_path = item.get("path") + if not rel_path or not rel_path.endswith(DOC_FILE_SUFFIXES): + continue + title = self._localized_text( + item.get("title"), + item.get("lang") or manifest.get("default_language", "en"), + ) + html_file = self._doc_html_name(rel_path) + if title: + index_links.append( + f'
  • {html.escape(title)}
  • ', + ) + self._write_doc_html_export(rel_path) + else: + for doc in self._collect_flat_docs(): + rel_path = doc["path"] + html_file = self._write_doc_html_export(rel_path) + if html_file: + label = os.path.basename(rel_path) + index_links.append( + f'
  • {html.escape(label)}
  • ', + ) + + if index_links: + index_html = self._standalone_html_shell( + "MeshChatX Documentation", + f'

    MeshChatX Documentation

      {"".join(index_links)}
    ', + ) + with open( + os.path.join(self.meshchatx_docs_dir, "index.html"), + "w", + encoding="utf-8", + ) as f: + f.write(index_html) + + def _write_doc_html_export(self, rel_path): + full_path = os.path.join(self.meshchatx_docs_dir, rel_path) + if not os.path.isfile(full_path): + return None + try: + with open(full_path, encoding="utf-8") as f: + content = f.read() + if rel_path.endswith(".md"): + body = MarkdownRenderer.render(content) + else: + body = f"
    {html.escape(content)}
    " + title = os.path.basename(rel_path) + html_file = self._doc_html_name(rel_path) + html_path = os.path.join(self.meshchatx_docs_dir, html_file) + os.makedirs(os.path.dirname(html_path), exist_ok=True) + doc_html = self._standalone_html_shell( + title, f'
    {body}
    ' + ) + with open(html_path, "w", encoding="utf-8") as f: + f.write(doc_html) + return html_file + except Exception as e: + logging.exception(f"Failed to render {rel_path} to HTML: {e}") + return None + + @staticmethod + def _doc_html_name(rel_path): + base, _ext = os.path.splitext(rel_path) + return f"{base}.html" + + @staticmethod + def _standalone_html_shell(title, body_html): + safe_title = html.escape(title) + return f""" + + + + + + {safe_title} + + + + + {body_html} + +""" + def get_status(self): return { "status": self.upload_status, @@ -309,31 +379,160 @@ class DocsManager: } def has_meshchatx_docs(self): - return ( - any( - f.endswith((".md", ".txt")) for f in os.listdir(self.meshchatx_docs_dir) - ) - if os.path.exists(self.meshchatx_docs_dir) - else False - ) + if not os.path.exists(self.meshchatx_docs_dir): + return False + return len(self._collect_flat_docs()) > 0 - def get_meshchatx_docs_list(self): + def get_meshchatx_docs_list(self, lang="en"): + manifest, manifest_error = self._read_manifest() + flat_docs = self._collect_flat_docs() + languages = manifest.get("languages") if manifest else None + if not languages: + languages = [{"code": "en", "name": "English"}] + default_language = manifest.get("default_language", "en") if manifest else "en" + sections = self._build_sections(manifest, lang, default_language, flat_docs) + result = { + "docs": flat_docs, + "sections": sections, + "languages": languages, + "default_language": default_language, + } + if manifest_error: + result["manifest_error"] = manifest_error + return result + + @staticmethod + def _is_safe_doc_path(path): + if not path or not isinstance(path, str): + return False + if "\0" in path: + return False + normalized = path.replace("\\", "/").strip() + if not normalized or normalized.startswith("/"): + return False + parts = [part for part in normalized.split("/") if part not in ("", ".")] + return ".." not in parts + + def _read_manifest(self): + manifest_path = os.path.join(self.meshchatx_docs_dir, MANIFEST_FILENAME) + if not os.path.isfile(manifest_path): + return None, None + try: + with open(manifest_path, encoding="utf-8") as f: + data = json.load(f) + if not isinstance(data, dict): + return None, "Manifest must be a JSON object" + return data, None + except json.JSONDecodeError as e: + logging.exception(f"Failed to parse docs manifest: {e}") + return None, "Invalid manifest JSON" + except OSError as e: + logging.exception(f"Failed to read docs manifest: {e}") + return None, "Could not read manifest file" + + def _collect_flat_docs(self): docs = [] if not os.path.exists(self.meshchatx_docs_dir): return docs - docs.extend( - { - "name": file, - "path": file, - "type": "markdown" if file.endswith(".md") else "text", - } - for file in os.listdir(self.meshchatx_docs_dir) - if file.endswith((".md", ".txt")) - ) - return sorted(docs, key=lambda x: x["name"]) + for root, _, files in os.walk(self.meshchatx_docs_dir): + for file in files: + if not file.endswith(DOC_FILE_SUFFIXES): + continue + file_path = os.path.join(root, file) + try: + rel_path = os.path.relpath(file_path, self.meshchatx_docs_dir) + except ValueError: + continue + rel_path = rel_path.replace("\\", "/") + docs.append( + { + "name": file, + "path": rel_path, + "type": "markdown" if file.endswith(".md") else "text", + }, + ) + return sorted(docs, key=lambda x: x["path"]) + + def _build_sections(self, manifest, lang, default_language, flat_docs): + if not manifest or not manifest.get("sections"): + return [ + { + "id": "all", + "title": self._localized_text( + {"en": "Guides"}, lang, default_language + ), + "items": [ + { + "path": doc["path"], + "title": self._title_from_path(doc["path"]), + "lang": default_language, + "type": doc["type"], + } + for doc in flat_docs + ], + }, + ] + + available = {doc["path"] for doc in flat_docs} + sections = [] + for section in sorted( + manifest.get("sections", []), key=lambda s: s.get("order", 0) + ): + items = [] + for item in section.get("items", []): + rel_path = item.get("path") + if not rel_path or rel_path not in available: + continue + item_lang = item.get("lang") or default_language + doc_type = "markdown" if rel_path.endswith(".md") else "text" + items.append( + { + "path": rel_path, + "title": self._localized_text( + item.get("title"), + lang, + item_lang or default_language, + ) + or self._title_from_path(rel_path), + "lang": item_lang, + "type": doc_type, + }, + ) + if items: + sections.append( + { + "id": section.get("id") or section.get("title", "section"), + "title": self._localized_text( + section.get("title"), + lang, + default_language, + ), + "items": items, + }, + ) + return sections + + @staticmethod + def _localized_text(value, lang, fallback="en"): + if value is None: + return "" + if isinstance(value, str): + return value + if isinstance(value, dict): + return ( + value.get(lang) or value.get(fallback) or next(iter(value.values()), "") + ) + return str(value) + + @staticmethod + def _title_from_path(rel_path): + base = os.path.basename(rel_path) + return os.path.splitext(base)[0].replace("-", " ").replace("_", " ") def get_doc_content(self, path): + if not self._is_safe_doc_path(path): + return None try: full_path = os.path.realpath(os.path.join(self.meshchatx_docs_dir, path)) base = os.path.realpath(self.meshchatx_docs_dir) @@ -344,20 +543,28 @@ class DocsManager: if not os.path.isfile(full_path): return None - with open(full_path, encoding="utf-8", errors="ignore") as f: - content = f.read() + try: + with open(full_path, encoding="utf-8", errors="ignore") as f: + content = f.read() + except OSError as e: + logging.exception(f"Failed to read MeshChatX doc {path}: {e}") + return None - if path.endswith(".md"): + try: + if path.endswith(".md"): + return { + "content": content, + "html": MarkdownRenderer.render(content), + "type": "markdown", + } return { "content": content, - "html": MarkdownRenderer.render(content), - "type": "markdown", + "html": f"
    {html.escape(content)}
    ", + "type": "text", } - return { - "content": content, - "html": f"
    {html.escape(content)}
    ", - "type": "text", - } + except Exception as e: + logging.exception(f"Failed to render MeshChatX doc {path}: {e}") + return None def export_docs(self): """Build a ZIP archive containing the active Reticulum docs and MeshChatX docs.""" @@ -424,9 +631,11 @@ class DocsManager: query = query.lower() if os.path.exists(self.meshchatx_docs_dir): - for file in os.listdir(self.meshchatx_docs_dir): - if file.endswith((".md", ".txt")): - file_path = os.path.join(self.meshchatx_docs_dir, file) + for root, _, files in os.walk(self.meshchatx_docs_dir): + for file in files: + if not file.endswith(DOC_FILE_SUFFIXES): + continue + file_path = os.path.join(root, file) try: with open( file_path, @@ -447,7 +656,7 @@ class DocsManager: results.append( { "title": file, - "path": f"/meshchatx-docs/{file}", + "path": f"/meshchatx-docs/{os.path.relpath(file_path, self.meshchatx_docs_dir).replace(os.sep, '/')}", "snippet": snippet, "source": "MeshChatX", }, diff --git a/meshchatx/src/backend/markdown_renderer.py b/meshchatx/src/backend/markdown_renderer.py index 5e792e8a..80eb8b89 100644 --- a/meshchatx/src/backend/markdown_renderer.py +++ b/meshchatx/src/backend/markdown_renderer.py @@ -28,11 +28,30 @@ def _safe_href(url): class MarkdownRenderer: """A simple Markdown to HTML renderer.""" + _heading_ids: dict[str, int] = {} + + @classmethod + def _reset_heading_ids(cls): + cls._heading_ids = {} + + @classmethod + def _heading_id(cls, text, level): + slug_base = re.sub(r"[^\w\s-]", "", html.unescape(text)).strip().lower() + slug_base = re.sub(r"[-\s]+", "-", slug_base) or "section" + key = f"{level}:{slug_base}" + count = cls._heading_ids.get(key, 0) + cls._heading_ids[key] = count + 1 + if count: + return f"{slug_base}-{count + 1}" + return slug_base + @staticmethod def render(text): if not text: return "" + MarkdownRenderer._reset_heading_ids() + # Escape HTML entities first to prevent XSS # Use a more limited escape if we want to allow some things, # but for docs, full escape is safest. @@ -58,6 +77,8 @@ class MarkdownRenderer: flags=re.DOTALL, ) + text = MarkdownRenderer._render_tables(text) + # Horizontal Rules text = re.sub( r"^---+$", @@ -67,27 +88,49 @@ class MarkdownRenderer: ) # Headers + def heading_repl(level, classes): + def repl(match): + title = match.group(1) + heading_id = MarkdownRenderer._heading_id(title, level) + return ( + f'{title}' + ) + + return repl + text = re.sub( r"^# (.*)$", - r'

    \1

    ', + heading_repl( + 1, + "text-3xl font-bold mt-8 mb-4 text-gray-900 dark:text-zinc-100 scroll-mt-24", + ), text, flags=re.MULTILINE, ) text = re.sub( r"^## (.*)$", - r'

    \1

    ', + heading_repl( + 2, + "text-2xl font-bold mt-8 mb-3 text-gray-900 dark:text-zinc-100 scroll-mt-24 border-b border-gray-200 dark:border-zinc-800 pb-2", + ), text, flags=re.MULTILINE, ) text = re.sub( r"^### (.*)$", - r'

    \1

    ', + heading_repl( + 3, + "text-xl font-semibold mt-6 mb-2 text-gray-900 dark:text-zinc-100 scroll-mt-24", + ), text, flags=re.MULTILINE, ) text = re.sub( r"^#### (.*)$", - r'

    \1

    ', + heading_repl( + 4, + "text-lg font-semibold mt-4 mb-2 text-gray-900 dark:text-zinc-100 scroll-mt-24", + ), text, flags=re.MULTILINE, ) @@ -207,7 +250,7 @@ class MarkdownRenderer: continue # If it already starts with a block tag, don't wrap in

    - if re.match(r"^<(h\d|ul|ol|li|blockquote|hr|div)", part): + if re.match(r"^<(h\d|ul|ol|li|blockquote|hr|div|table)", part): processed_parts.append(part) else: # Replace single newlines with
    for line breaks within paragraphs @@ -223,3 +266,77 @@ class MarkdownRenderer: text = text.replace(f"[[CB{i}]]", code_html) return text + + @staticmethod + def _is_table_row(line): + stripped = line.strip() + return ( + stripped.startswith("|") + and stripped.endswith("|") + and stripped.count("|") >= 2 + ) + + @staticmethod + def _split_table_cells(line): + return [cell.strip() for cell in line.strip().strip("|").split("|")] + + @staticmethod + def _is_table_separator(line): + if not MarkdownRenderer._is_table_row(line): + return False + cells = MarkdownRenderer._split_table_cells(line) + if not cells: + return False + return all(re.fullmatch(r":?-{3,}:?", cell) for cell in cells) + + @staticmethod + def _table_block_to_html(lines): + header_cells = MarkdownRenderer._split_table_cells(lines[0]) + body_rows = [ + MarkdownRenderer._split_table_cells(row) + for row in lines[2:] + if MarkdownRenderer._is_table_row(row) + ] + thead = "".join( + f'{cell}' + for cell in header_cells + ) + tbody_rows = [] + for row in body_rows: + padded = row + [""] * (len(header_cells) - len(row)) + cells = "".join( + f'{cell}' + for cell in padded[: len(header_cells)] + ) + tbody_rows.append(f"{cells}") + tbody = "".join(tbody_rows) + return ( + '

    ' + '' + f"{thead}" + f"{tbody}" + "
    " + ) + + @staticmethod + def _render_tables(text): + lines = text.split("\n") + out = [] + i = 0 + while i < len(lines): + line = lines[i] + if ( + i + 1 < len(lines) + and MarkdownRenderer._is_table_row(line) + and MarkdownRenderer._is_table_separator(lines[i + 1]) + ): + block = [line, lines[i + 1]] + i += 2 + while i < len(lines) and MarkdownRenderer._is_table_row(lines[i]): + block.append(lines[i]) + i += 1 + out.append(MarkdownRenderer._table_block_to_html(block)) + else: + out.append(line) + i += 1 + return "\n".join(out) diff --git a/meshchatx/src/frontend/components/docs/DocsPage.vue b/meshchatx/src/frontend/components/docs/DocsPage.vue index 85e6fb69..65933b9c 100644 --- a/meshchatx/src/frontend/components/docs/DocsPage.vue +++ b/meshchatx/src/frontend/components/docs/DocsPage.vue @@ -26,7 +26,7 @@ " @click="activeTab = 'meshchatx'" > - MeshChatX + {{ $t("docs.tab_meshchatx") }} @@ -50,7 +50,7 @@ v-model="searchQuery" type="text" class="block w-full pl-8 pr-8 py-1.5 border border-gray-200 dark:border-zinc-700 rounded-lg bg-gray-50 dark:bg-zinc-800 text-gray-900 dark:text-zinc-100 text-[11px] focus:outline-hidden focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all" - placeholder="Search documentation..." + :placeholder="$t('docs.search_placeholder')" @input="debounceSearch" />
    @@ -84,7 +84,7 @@ >
    - Versions + {{ + $t("docs.versions") + }}
    - Upload ZIP + {{ $t("docs.upload_zip") }}
    @@ -216,7 +218,7 @@ class="hidden sm:flex items-center px-2.5 py-1.5 bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 rounded-lg hover:opacity-90 transition-opacity font-bold text-[10px] shadow-xs" > - Open + {{ $t("docs.open_external") }} @@ -238,7 +240,7 @@ " @click="activeTab = 'meshchatx'" > - MeshChatX + {{ $t("docs.tab_meshchatx") }} @@ -262,7 +264,7 @@ v-model="searchQuery" type="text" class="block w-full pl-9 pr-9 py-2 border border-gray-200 dark:border-zinc-700 rounded-lg bg-gray-50 dark:bg-zinc-800 text-gray-900 dark:text-zinc-100 text-xs focus:outline-hidden focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all" - placeholder="Search all documentation..." + :placeholder="$t('docs.search_placeholder_mobile')" @input="debounceSearch" />
    @@ -300,10 +302,12 @@ >
    -

    Search Results

    +

    + {{ $t("docs.search_results") }} +

    {{ searchResults.length }} matches{{ $t("docs.matches_count", { count: searchResults.length }) }}
    @@ -341,7 +345,7 @@
    -

    No results found

    -

    Try different keywords or check spelling.

    +

    {{ $t("docs.no_results") }}

    +

    {{ $t("docs.no_results_hint") }}

    +
    + +
    +
    + +
    +

    {{ $t("docs.search_failed") }}

    +

    {{ searchError }}

    +
    @@ -381,7 +402,7 @@ class="text-[10px] font-bold text-red-500/60 hover:text-red-500 uppercase tracking-widest transition-colors" @click="dismissError" > - Dismiss + {{ $t("docs.dismiss") }}
    @@ -408,71 +429,166 @@

    {{ $t("docs.status_extracting") }}

    -

    {{ status.progress }}% Complete

    +

    + {{ $t("docs.complete_percent", { percent: status.progress }) }} +

    - - - - -
    - -
    - -
    - -
    -
    -
    + {{ meshchatxListError }} +

    +
    +
    -
    +
    +

    + {{ section.title }} +

    +
    + +
    +
    + + + + +
    +
    +
    + + +
    + +
    +
    +
    +
    +
    +
    + +

    {{ $t("docs.load_doc_failed") }}

    +

    {{ docLoadError }}

    +
    +
    + +

    {{ $t("docs.select_doc") }}

    +
    +
    + +

    {{ $t("docs.no_docs_found") }}

    +

    {{ $t("docs.no_docs_hint") }}

    +
    +
    + + +
    -
    - -

    No MeshChatX docs found

    -

    Place .md or .txt files in your docs folder.

    -
    +
    +

    + {{ $t("docs.on_this_page") }} +

    +
    + +
    @@ -483,18 +599,22 @@ ref="docsFrame" :src="localDocsUrl" class="w-full h-full border-none opacity-0 transition-opacity duration-1000" - @load="$el.querySelector('iframe').style.opacity = '1'" + @load="onReticulumFrameLoad" >
    -

    Reticulum Manual

    +

    + {{ $t("docs.reticulum_manual") }} +

    {{ $t("docs.empty_state_hint") }}

    @@ -543,6 +663,15 @@ export default { searchTimeout: null, activeTab: "meshchatx", meshchatxDocs: [], + docSections: [], + docLanguages: [], + defaultDocsLanguage: "en", + meshchatxDocsLang: "en", + docToc: [], + meshchatxListError: null, + docLoadError: null, + manifestWarning: null, + searchError: null, selectedDocPath: null, selectedDocContent: null, selectedReticulumPath: null, @@ -583,6 +712,26 @@ export default { reticulumDocsQueryParam() { return this.$route?.query?.reticulum; }, + visibleDocSections() { + const lang = this.meshchatxDocsLang; + const fallback = this.defaultDocsLanguage || "en"; + return this.docSections + .map((section) => ({ + ...section, + items: (section.items || []).filter( + (item) => item.lang === lang || item.lang === fallback || lang === fallback + ), + })) + .filter((section) => section.items.length > 0); + }, + firstDocPath() { + for (const section of this.visibleDocSections) { + if (section.items?.length) { + return section.items[0].path; + } + } + return this.meshchatxDocs[0]?.path || null; + }, }, watch: { reticulumDocsQueryParam() { @@ -622,28 +771,109 @@ export default { this.status = { ...this.status, last_error: null }; }, async fetchMeshChatXDocs() { + this.meshchatxListError = null; + this.manifestWarning = null; try { - const response = await window.api.get("/api/v1/meshchatx-docs/list"); - this.meshchatxDocs = response.data; + const response = await window.api.get("/api/v1/meshchatx-docs/list", { + params: { lang: this.meshchatxDocsLang }, + }); + const data = response.data; + if (Array.isArray(data)) { + this.meshchatxDocs = data; + this.docSections = []; + this.docLanguages = [{ code: "en", name: "English" }]; + } else { + this.meshchatxDocs = data.docs || []; + this.docSections = data.sections || []; + this.docLanguages = data.languages || [{ code: "en", name: "English" }]; + this.defaultDocsLanguage = data.default_language || "en"; + if (data.manifest_error) { + this.manifestWarning = this.$t("docs.manifest_warning"); + } + } + if (!this.docLanguages.some((l) => l.code === this.meshchatxDocsLang)) { + this.meshchatxDocsLang = this.defaultDocsLanguage || "en"; + } if (this.meshchatxDocs.length > 0 && !this.selectedDocPath) { - this.selectDoc(this.meshchatxDocs[0].path); + const start = this.firstDocPath; + if (start) { + this.selectDoc(start); + } } } catch (error) { console.error("Failed to fetch MeshChatX docs list:", error); + this.meshchatxDocs = []; + this.docSections = []; + this.meshchatxListError = error.response?.data?.error || this.$t("docs.load_list_failed"); } }, + async setMeshchatxDocsLang(langCode) { + if (this.meshchatxDocsLang === langCode) { + return; + } + this.meshchatxDocsLang = langCode; + this.selectedDocPath = null; + this.selectedDocContent = null; + this.docToc = []; + await this.fetchMeshChatXDocs(); + }, async selectDoc(path) { + if (!path) { + return; + } this.selectedDocPath = path; + this.docLoadError = null; try { const response = await window.api.get("/api/v1/meshchatx-docs/content", { params: { path }, }); + if (!response.data?.html && !response.data?.content) { + throw new Error("Empty document response"); + } this.selectedDocContent = response.data; + this.docToc = this.extractDocToc(this.selectedDocContent?.html || ""); } catch (error) { console.error("Failed to fetch doc content:", error); - this.selectedDocContent = { - html: '
    Failed to load document.
    ', - }; + this.docLoadError = error.response?.data?.error || this.$t("docs.load_doc_failed"); + this.selectedDocContent = null; + this.docToc = []; + } + }, + extractDocToc(htmlContent) { + if (!htmlContent) { + return []; + } + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlContent, "text/html"); + return Array.from(doc.querySelectorAll("h2, h3")) + .map((heading) => ({ + id: heading.id, + text: heading.textContent?.trim() || "", + level: heading.tagName === "H2" ? 2 : 3, + })) + .filter((entry) => entry.id && entry.text); + } catch { + return []; + } + }, + scrollToHeading(id) { + const prose = this.$refs.docsProse; + if (!prose || typeof prose.querySelector !== "function") { + return; + } + if (!id || !/^[a-z0-9-]+$/.test(id)) { + return; + } + const target = prose.querySelector(`#${id}`); + if (target) { + target.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }, + onReticulumFrameLoad() { + const frame = this.$refs.docsFrame; + if (frame && frame.style) { + frame.style.opacity = "1"; } }, async switchVersion(version) { @@ -664,7 +894,7 @@ export default { } }, async deleteVersion(version) { - if (!confirm(`Are you sure you want to delete version "${version}"?`)) { + if (!confirm(this.$t("docs.confirm_delete_version", { version }))) { return; } @@ -681,7 +911,7 @@ export default { const file = event.target.files[0]; if (!file) return; - const version = prompt("Enter version name for this upload:", `upload-${Date.now()}`); + const version = prompt(this.$t("docs.prompt_version_name"), `upload-${Date.now()}`); if (!version) return; const formData = new FormData(); @@ -696,7 +926,8 @@ export default { this.fetchStatus(); } catch (error) { console.error("Failed to upload docs zip:", error); - alert("Failed to upload docs zip: " + (error.response?.data?.error || error.message)); + const message = error.response?.data?.error || error.message || ""; + alert(this.$t("docs.failed_upload_alert", { message })); } }, async exportDocs() { @@ -745,6 +976,7 @@ export default { async performSearch() { if (!this.searchQuery) return; this.isSearching = true; + this.searchError = null; try { const response = await window.api.get("/api/v1/docs/search", { params: { @@ -752,9 +984,11 @@ export default { lang: this.currentLang, }, }); - this.searchResults = response.data.results; + this.searchResults = response.data?.results || []; } catch (error) { console.error("Search failed:", error); + this.searchResults = []; + this.searchError = error.response?.data?.error || this.$t("docs.search_failed"); } finally { this.isSearching = false; } @@ -762,6 +996,7 @@ export default { clearSearch() { this.searchQuery = ""; this.searchResults = []; + this.searchError = null; }, applyDocumentationRouteQuery() { const q = this.reticulumDocsQueryParam; @@ -821,69 +1056,78 @@ export default { diff --git a/meshchatx/src/frontend/locales/de.json b/meshchatx/src/frontend/locales/de.json index e42b0809..3c8a9cee 100644 --- a/meshchatx/src/frontend/locales/de.json +++ b/meshchatx/src/frontend/locales/de.json @@ -1697,6 +1697,31 @@ "docs": { "title": "Dokumentation", "subtitle": "Reticulum-Handbuch und MeshChatX-Anleitungen, nach Einrichtung offline lesbar.", + "tab_meshchatx": "MeshChatX", + "tab_reticulum": "Reticulum", + "search_placeholder": "Dokumentation durchsuchen...", + "search_placeholder_mobile": "Gesamte Dokumentation durchsuchen...", + "search_results": "Suchergebnisse", + "matches_count": "{count} Treffer", + "no_results": "Keine Ergebnisse", + "no_results_hint": "Andere Stichwörter versuchen oder Schreibweise prüfen.", + "clear_search": "Suche löschen", + "versions": "Versionen", + "no_versions": "Keine Versionen verfügbar", + "default_version": "Standard", + "upload_zip": "ZIP hochladen", + "open_external": "Öffnen", + "dismiss": "Schließen", + "sections_title": "Anleitungen", + "on_this_page": "Auf dieser Seite", + "language_label": "Sprache der Anleitungen", + "select_doc": "Wähle eine Anleitung in der Seitenleiste", + "no_docs_found": "Keine MeshChatX-Anleitungen gefunden", + "no_docs_hint": "Anleitungen werden beim Start aus dem docs-Ordner kopiert.", + "reticulum_manual": "Reticulum-Handbuch", + "complete_percent": "{percent} % abgeschlossen", + "confirm_delete_version": "Dokumentationsversion \"{version}\" löschen?", + "prompt_version_name": "Versionsname für diesen Upload eingeben:", "status_title": "Dokumentationsstatus", "status_extracting": "Wird entpackt...", "status_available": "Offline-Handbuch verfügbar", @@ -1707,7 +1732,12 @@ "error": "Fehler", "failed_upload_docs": "Hochladen der Dokumentation fehlgeschlagen", "docs_link_copied": "Dokumentationslink in Zwischenablage kopiert", - "failed_copy_link": "Link kopieren fehlgeschlagen" + "failed_copy_link": "Link kopieren fehlgeschlagen", + "load_list_failed": "Anleitungsliste konnte nicht geladen werden. Bitte später erneut versuchen.", + "load_doc_failed": "Diese Anleitung konnte nicht geladen werden.", + "search_failed": "Suche fehlgeschlagen. Verbindung prüfen und erneut versuchen.", + "manifest_warning": "Die Dokumentationsindex-Datei konnte nicht gelesen werden. Verfügbare Dateien werden ohne Abschnittsgruppierung angezeigt.", + "failed_upload_alert": "Hochladen der Dokumentation fehlgeschlagen: {message}" }, "licenses": { "section_label": "Rechtliches", diff --git a/meshchatx/src/frontend/locales/en.json b/meshchatx/src/frontend/locales/en.json index 9f512820..24b729f6 100644 --- a/meshchatx/src/frontend/locales/en.json +++ b/meshchatx/src/frontend/locales/en.json @@ -1809,6 +1809,31 @@ "docs": { "title": "Documentation", "subtitle": "Reticulum manual and MeshChatX guides, available offline after setup.", + "tab_meshchatx": "MeshChatX", + "tab_reticulum": "Reticulum", + "search_placeholder": "Search documentation...", + "search_placeholder_mobile": "Search all documentation...", + "search_results": "Search results", + "matches_count": "{count} matches", + "no_results": "No results found", + "no_results_hint": "Try different keywords or check spelling.", + "clear_search": "Clear search", + "versions": "Versions", + "no_versions": "No versions available", + "default_version": "Default", + "upload_zip": "Upload ZIP", + "open_external": "Open", + "dismiss": "Dismiss", + "sections_title": "Guides", + "on_this_page": "On this page", + "language_label": "Guide language", + "select_doc": "Select a guide from the sidebar", + "no_docs_found": "No MeshChatX guides found", + "no_docs_hint": "Guides are copied from the docs folder when the app starts.", + "reticulum_manual": "Reticulum manual", + "complete_percent": "{percent}% complete", + "confirm_delete_version": "Delete documentation version \"{version}\"?", + "prompt_version_name": "Enter a version name for this upload:", "status_title": "Documentation Status", "status_extracting": "Extracting Documentation...", "status_available": "Offline Manual Available", @@ -1819,7 +1844,12 @@ "error": "Error", "failed_upload_docs": "Failed to upload documentation", "docs_link_copied": "Documentation link copied to clipboard", - "failed_copy_link": "Failed to copy link" + "failed_copy_link": "Failed to copy link", + "load_list_failed": "Could not load the guide list. Try again in a moment.", + "load_doc_failed": "Could not load this guide.", + "search_failed": "Search failed. Check your connection and try again.", + "manifest_warning": "The documentation index file could not be read. Showing available files without section grouping.", + "failed_upload_alert": "Failed to upload documentation: {message}" }, "licenses": { "section_label": "Legal", diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/architecture.md b/meshchatx/src/frontend/public/meshchatx-docs/en/architecture.md new file mode 100644 index 00000000..6267173d --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/architecture.md @@ -0,0 +1,146 @@ +# Architecture and design + +MeshChatX is a heavily extended fork of Reticulum MeshChat. The goals below shaped how the codebase is organized. + +## Design goals + +- Keep a local-first runtime that works on desktop, mobile, containers, and single-board computers. +- Preserve Reticulum and LXMF semantics while improving usability and operational tooling. +- Support multiple identities in one process without cross-identity data leakage. +- Keep the Python backend and Vue frontend independently testable. +- Run in constrained environments with predictable SQLite behaviour. + +## Process overview + +One Python process owns the web server, Reticulum stack, and all per-identity managers. The Vue frontend is static assets served from `meshchatx/public/` after a Vite build. + +``` +ReticulumMeshChat (meshchat.py) + | + +-- HTTP routes (/api/v1/*, static files) + +-- WebSocket (/ws, /ws/telephone/audio) + +-- IdentityContext (per active identity) + | +-- SQLite via database layer + | +-- LXMRouter + | +-- TelephoneManager (LXST) + | +-- Domain managers (messages, map, docs, RRC, ...) + +-- Shared Reticulum instance (~/.reticulum by default) +``` + +Optional **Electron** wraps the same backend binary and loads the UI from the local HTTPS server. + +## Application shell + +`ReticulumMeshChat` in `meshchatx/meshchat.py` is the orchestration layer. It registers routes, starts and stops identity contexts, wires crash recovery, and coordinates shared process concerns. + +Path helpers live in `meshchatx/src/path_utils.py`, `ssl_self_signed.py`, and `env_utils.py`. `meshchat.py` re-exports them for compatibility. + +## Identity-scoped context + +`IdentityContext` in `meshchatx/src/backend/identity_context.py` encapsulates everything tied to one cryptographic identity: + +- Storage under `storage/identities//` +- Identity-local SQLite database (schema version tracked in migrations) +- LXMF router state and propagation directories +- Manager instances for messages, announces, docs, maps, forwarding, bots, RRC, Nomad page nodes, and more + +Switching identities tears down the old context and loads another. Global mutable state that could leak between identities is avoided by design. + +## Manager-centric domain logic + +Feature behaviour lives in modules under `meshchatx/src/backend/`. Examples include message handling, announce trimming, documentation, maps, page nodes, telemetry, interfaces, forwarding aliases, and RN-specific tool handlers. + +`meshchat.py` should stay focused on transport and lifecycle. Business rules belong in managers where they can be unit tested. + +## Persistence + +- **Engine:** SQLite with explicit SQL and migrations (no ORM). +- **Schema:** Versioned migrations run during startup and identity setup. +- **Backups:** Automatic and manual database backups under `database-backups/`. +- **Recovery:** `--auto-recover`, emergency mode, and Electron crash UI can restore from backups. + +## HTTP API + +Routes are registered explicitly on the aiohttp application. Categories include: + +- Application status and configuration +- Authentication and session management +- LXMF messaging and conversations +- Telephone and voicemail +- Interfaces and Reticulum configuration +- Nomad Network and page nodes +- RRC client and server +- Tools (ping, RNPath, RNCP, RNSH, translator, bots) +- Documentation and maintenance + +The frontend uses `fetch` via `apiClient.js` with CSRF tokens on mutating requests. + +## WebSockets + +The UI connects to `/ws` for low-latency updates. Event types include new LXMF messages, identity switches, telephone state, RRC activity, Nomad download progress, RNCP transfers, and plugin events. Handlers are registered in `wsEventRegistry.js` and dispatched through `wsEventBridge.js`. + +Audio calls can use `/ws/telephone/audio` for browser-side codec bridging. + +## Security model + +MeshChatX defaults toward secure local operation: + +- HTTPS and WSS enabled by default. +- Self-signed certificates generated per identity when custom PEM files are absent. +- Optional HTTP basic authentication (`--auth`). +- Encrypted session cookies via `aiohttp_session`. +- CORS, CSP, and defensive middleware on HTTP responses. +- Access attempt logging with lockout when auth is enabled. + +The project includes extensive automated tests around auth and sessions. Even so, exposing MeshChatX directly to the public internet is not recommended without additional hardening. + +Password reset is available with `--reset-password` or `MESHCHAT_RESET_PASSWORD=true`, which clears the stored bcrypt hash so you can set a new password in the UI. + +## Build and packaging + +One source tree produces: + +- Development runs via `uv run python -m meshchatx.meshchat` +- Python wheels with bundled `public/` assets +- Container images (Dockerfile and hardened variants) +- Electron builds for Windows, macOS, and Linux +- Android APK via Chaquopy + +Frontend build output always lands in `meshchatx/public/` so runtime behaviour matches across targets. + +## Reliability features + +- Crash recovery integration in Electron and backend startup checks +- Database integrity verification +- Backup, restore, and snapshot APIs +- Explicit teardown when switching identities or shutting down forwarding resources +- Health and status endpoints suitable for container probes + +## Extensibility + +MeshChatX supports plugins with separate frontend and backend runtimes: + +- **Contribution registries** under `meshchatx/src/frontend/js/registries/` for navigation, tools, commands, settings, and WebSocket events. +- **Frontend plugins** run in dedicated Workers (`PluginHost.js`) with declarative UI slots. +- **Backend plugins** run in wasmtime with fuel metering and capability-gated host functions. +- **HTTP API** under `/api/v1/plugins/*` for install, enable, invoke, and assets. + +Practical extension paths today: + +- Plugin manifests with `contributes` and `permissions` blocks +- New API routes and manager modules +- Frontend pages wired through registries +- New settings via `ConfigManager` and CLI or environment variables +- Database schema changes through migrations + +When adding features, prefer identity-scoped state, explicit migrations, endpoint tests, and narrowly declared plugin permissions. + +## NomadNet and Mesh Server + +The Nomad browser and Mesh Server (page nodes) share a rendering pipeline for Micron, Markdown, plain text, and sanitised HTML. Authoring rules are documented in **NomadNet page formats**. + +## Related reading + +- **Getting started** for UI navigation and first steps. +- **LXMF messaging**, **Audio calls**, and **Reticulum interfaces** for feature behaviour. +- The **Reticulum** tab in Documentation for protocol reference. diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/audio-calls.md b/meshchatx/src/frontend/public/meshchatx-docs/en/audio-calls.md new file mode 100644 index 00000000..c2ce38ce --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/audio-calls.md @@ -0,0 +1,87 @@ +# Audio calls (LXST) + +MeshChatX uses LXST for voice telephony over Reticulum. Telephone functionality is optional and controlled per identity in settings. + +## Enable telephony + +Turn on **telephone** in settings before using the **Call** page. MeshChatX announces your callable destination under aspect `lxst.telephony` when announcing is enabled. + +Peers who announce the same aspect appear as callable contacts. + +## Placing and receiving calls + +From **Call** or a contact entry you can: + +- **Dial** another identity by hash +- **Answer** or **decline** inbound rings +- **Hang up** an active session +- **Mute** transmit or receive paths + +Call state changes arrive over the WebSocket (`telephone_ringing`, `telephone_call_established`, `telephone_call_ended`, and related events). + +## Audio path + +The frontend loads Codec2 assets for voice encoding (`Codec2Loader.js`). Browser and Electron builds use a Web Audio bridge at `/ws/telephone/audio`. Packaged desktop builds bundle the backend that negotiates LXST sessions. + +## Voicemail + +When you miss a call, voicemail may be offered depending on settings: + +- Record a custom greeting +- Upload or generate greeting audio +- Play back messages left for you + +Voicemail events surface as `new_voicemail` on the WebSocket. + +## Call history and recordings + +The **Call** area keeps history of placed, received, and missed calls. You can record calls when the feature is enabled and policy allows storage on your device. + +## Ringtones + +Upload custom ringtones and assign them per contact. Default sounds are used when no override exists. + +## Do not disturb and contacts-only + +Settings support: + +- **Do not disturb** to silence inbound rings +- **Contacts-only** mode to reject calls from unknown hashes + +Combine these with the **Blocked** list for finer control. + +## Telephone contacts + +Import and export telephone contacts separately from LXMF conversation peers. Contacts drive caller display names and ringtone overrides. + +## Call setup flow + +``` +Caller UI: initiate call + | + v +GET /api/v1/telephone/call/{identity_hash} + | + v +LXST Telephone session over Reticulum + | + +--> Signalling and media via LXST + | + +--> /ws/telephone/audio (browser audio bridge) + | + v +Callee UI: ring, answer, or decline +``` + +## Tips + +- Verify **Interfaces** and paths before troubleshooting audio quality. Packet loss on the mesh affects voice. +- Use headphones on mobile and Quest builds to prevent echo. +- Review microphone permissions in Electron or the Android system settings if the UI shows no input level. +- Keep LXST and Reticulum versions aligned with MeshChatX release notes when upgrading. + +## See also + +- **LXMF messaging** for text conversations with the same peers +- **Identities, privacy, and security** for HTTPS and local access controls +- LXST project documentation for codec and session details diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/getting-started.md b/meshchatx/src/frontend/public/meshchatx-docs/en/getting-started.md new file mode 100644 index 00000000..5c012b56 --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/getting-started.md @@ -0,0 +1,97 @@ +# Getting started with MeshChatX + +MeshChatX is a local-first mesh communications client built on the Reticulum Network Stack. It combines direct messaging over LXMF, voice calls over LXST, NomadNet page browsing, relay chat, maps, and a large set of Reticulum utilities in one application you can run on a desktop, a headless server, or a mobile device. + +MeshChatX is an independent fork of [Reticulum MeshChat](https://github.com/liamcottle/reticulum-meshchat). It is not affiliated with the upstream project. The website is [meshchatx.com](https://meshchatx.com). Source and releases live on [GitHub](https://github.com/Quad4-Software/MeshChatX). + +## What you need to know first + +Reticulum is the mesh networking layer. It handles identities, paths, interfaces, and encrypted transport between nodes. LXMF is the messaging protocol MeshChatX uses for conversations, attachments, and propagation. LXST is the telephony layer used for audio calls. + +MeshChatX does not replace Reticulum. It runs Reticulum inside a Python process, exposes a web UI, and stores your per-identity data locally in SQLite. + +## How the application is laid out + +When you open MeshChatX you work inside a single-page web interface. The sidebar lists the main areas of the app. The **Tools** page groups diagnostics and utilities. **Settings** holds per-identity configuration. **Identities** lets you create or switch between separate cryptographic identities. + +Typical first-day workflow: + +1. Install MeshChatX using a method that fits your device. See **Installation and setup**. +2. Open the web UI. The default address is `https://127.0.0.1:8000` when HTTPS is enabled. +3. Go to **Interfaces** and add a way to reach the mesh. A TCP client, community interface suggestion, or LoRa RNode are common starting points. +4. Wait for paths and announces to populate. Peers appear in the announces list and in feature-specific views. +5. Open **Messages** to start an LXMF conversation, or **Nomad Network** to browse a page node. + +## Runtime shape + +MeshChatX ships as one Python service that serves both the API and the built frontend assets. + +``` +Browser or Electron window + | + v +Vue 3 frontend (hash routes such as #/messages) + | + | REST under /api/v1/* and WebSocket at /ws + v +meshchatx/meshchat.py (aiohttp server) + | + +--> SQLite database (per identity) + +--> LXMF router and message store + +--> LXST telephone (when enabled) + +--> Reticulum stack (interfaces, paths, announces) +``` + +The same backend code powers Docker images, Python wheels, Linux packages, Electron desktop builds, and the Android APK. Packaging differs. Behaviour is intended to stay consistent. + +## Main areas of the UI + +| Area | Route | Purpose | +| ---- | ----- | ------- | +| Messages | `/messages` | LXMF direct messaging, folders, attachments | +| Audio calls | `/call` | LXST voice calls and voicemail | +| Contacts | `/contacts` | Telephone contacts and call-related entries | +| Relay chat | `/relay-chat` | RRC hubs and rooms (when enabled in settings) | +| Nomad Network | `/nomadnetwork` | Browse remote NomadNet pages and files | +| Map | `/map` | OpenLayers map, offline tiles, telemetry | +| Archives | `/archives` | Versioned snapshots of Nomad pages | +| Tools | `/tools` | Ping, path tools, RNCP, bots, documentation, and more | +| Interfaces | `/interfaces` | Add and manage Reticulum interfaces | +| Network visualiser | `/network-visualiser` | Graph view of mesh topology | +| Blocked | `/blocked` | Blocked destinations | +| Settings | `/settings` | Theme, language, LXMF, telephone, security | +| Identities | `/identities` | Create, import, or switch identities | +| Documentation | `/documentation` | MeshChatX guides and the Reticulum manual | + +Relay chat appears only when `rrc_enabled` is turned on in settings. + +## Documentation in the app + +The **Documentation** page has two tabs. + +**MeshChatX** shows the guides in this bundle. They are markdown files synced from the `docs/` directory in the repository and rendered offline inside the app. + +**Reticulum** shows the upstream Reticulum manual as pre-built HTML. It is bundled at build time. You can upload a newer manual ZIP if you need a different version. + +Use the search bar to query both sets at once. MeshChatX guide text is currently available in English. The Reticulum manual body is English. Localized landing pages exist for several languages on the Reticulum tab. + +## Storage locations + +| Data | Typical path | +| ---- | ------------ | +| MeshChatX app data | `~/.reticulum-meshchatx/` on Linux and macOS | +| Reticulum config | `~/.reticulum/` | +| Per-identity database | `/identities//database.db` | +| Docker volume | `meshchatx-config` mounted at `/config` | + +Legacy upstream data may still exist under `~/.reticulum-meshchat/`. Migration tooling can move you to the MeshChatX layout. + +## Where to go next + +- **Installation and setup** covers Docker, wheels, desktop packages, and development builds. +- **Architecture and design** explains backend managers, identity scoping, and the API model. +- **LXMF messaging** and **Audio calls** describe day-to-day communication features. +- **Reticulum interfaces** explains how your node joins the mesh. +- Platform guides under **Platform guides** cover Raspberry Pi, Android Termux, Meta Quest, and Linux sandboxing. + +For protocol-level detail, open the **Reticulum** tab in Documentation or visit the [Reticulum manual](https://reticulum.network/manual/) online. diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/identity-and-security.md b/meshchatx/src/frontend/public/meshchatx-docs/en/identity-and-security.md new file mode 100644 index 00000000..07068a1b --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/identity-and-security.md @@ -0,0 +1,105 @@ +# Identities, privacy, and security + +MeshChatX separates cryptographic identities, network security, and optional privacy controls. This page summarises how they interact. + +## Identities + +Each identity is a Reticulum key pair with its own: + +- SQLite database and LXMF router directory +- Settings in the `config` table via `ConfigManager` +- Storage path under `storage/identities//` + +Create, import, or switch identities from **Identities**. Only one identity is active in the UI at a time. Switching runs a teardown path so routers and managers do not leak state. + +Shared resources include the Reticulum process and interface configuration in `~/.reticulum` unless you override paths. + +## Announces + +MeshChatX tracks announces for aspects such as: + +| Aspect | Meaning | +| ------ | ------- | +| `lxmf.delivery` | Peer accepts LXMF messages | +| `lxst.telephony` | Peer accepts LXST calls | +| `lxmf.propagation` | Propagation node | +| `nomadnetwork.node` | NomadNet page server | +| `rrc.hub` | Relay chat hub (when RRC enabled) | + +Announce records store signal metadata and parsed app data for display names and icons. + +## Web UI authentication + +Optional HTTP basic authentication is enabled with `--auth` or `MESHCHAT_AUTH=true`. Sessions use encrypted cookies. Mutating API requests require CSRF tokens. + +Access attempts are logged. Repeated failures can trigger lockout when auth is enabled. + +Reset a forgotten password with `--reset-password` or `MESHCHAT_RESET_PASSWORD=true`, then set a new password in the UI. + +## Transport security + +- HTTPS and WSS are on by default. +- Self-signed certificates are generated per identity when custom PEM files are missing. +- Pass `--ssl-cert` and `--ssl-key` for managed certificates. +- Use `--no-https` only on trusted loopback setups. + +Electron loads the UI from the local HTTPS origin served by the embedded backend. + +## IP allowlisting + +`app_security_settings` can restrict which client IPs may use the web UI. Combine with auth when exposing the service beyond localhost. + +## Privacy mode + +**Privacy mode** blocks outbound HTTP from MeshChatX features that would otherwise call the public internet. Translation and similar tools respect this flag. + +Privacy mode does not disable Reticulum mesh traffic. It limits clearnet fetches from the app itself. + +## Linux sandboxing + +Optional Landlock sandboxing on Linux restricts filesystem access for the backend. See **Linux sandboxing** in Platform guides for Firejail and Bubblewrap examples. + +## Blocking and filtering + +Use **Blocked** for specific destination hashes. Combine with sieve filters, message blocklists, and LXMF stamp policies described in **LXMF messaging**. + +## Data backup + +Database backups land in `database-backups/`. Export snapshots from **About** or the API. Electron crash recovery can offer restore when integrity checks fail. + +CLI restore example: + +```bash +meshchatx --restore-db /path/to/backup.zip +``` + +## Integrity checks + +Startup integrity verification runs in packaged Electron builds and can be triggered from the backend. Failed checks surface recovery options instead of silently corrupting data. + +## Safe deployment patterns + +``` +Recommended for most users + | + v +Bind 127.0.0.1, use HTTPS, enable auth if others use the same host + | + v +Add interfaces only for meshes you trust + | + v +Keep backups and test restore on upgrades +``` + +Avoid exposing port 8000 directly to the internet without a reverse proxy, strong auth, and network-level filtering. MeshChatX is designed as a personal or small-team operator console, not a multi-tenant public website. + +## Multi-user hosts + +On shared computers, use separate OS user accounts or separate `--storage-dir` values so SQLite databases and identity files do not overlap. + +## See also + +- **Architecture and design** for session and API details +- **Installation and setup** for CLI security flags +- Reticulum manual cryptography chapters for identity math diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/installation.md b/meshchatx/src/frontend/public/meshchatx-docs/en/installation.md new file mode 100644 index 00000000..6b7938d9 --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/installation.md @@ -0,0 +1,153 @@ +# Installation and setup + +MeshChatX can be installed in several ways. All release artifacts that ship the web UI include pre-built frontend assets. You do not need Node.js on the machine that only runs the Python wheel or Docker image. + +## Requirements + +| Component | Version | +| --------- | ------- | +| Python | 3.11 or newer (`pyproject.toml`) | +| Node.js | 24 or newer (development and frontend builds only) | +| pnpm | 11.1.2 (development) | +| UV | Used by Taskfile and CI | + +**Browsers for the web UI:** Safari 16.4+, Chrome 111+, Firefox 128+. + +## Choose an install method + +| Method | Frontend included | Best for | +| ------ | ----------------- | -------- | +| Docker image | Yes | Fast server setup on Linux | +| Python wheel | Yes | Headless install without building the UI | +| Linux AppImage | Yes | Portable desktop on x64 or arm64 | +| Debian `.deb` | Yes | Debian and Ubuntu systems | +| RPM package | Yes | Fedora, RHEL, openSUSE style systems | +| Electron desktop | Yes | Integrated desktop with bundled backend | +| Android APK | Yes | Phones, tablets, Meta Quest sideload | +| From source | Built locally | Development and custom builds | + +Release images are published to Docker Hub (`quad4io/meshchatx`) and GHCR (`ghcr.io/quad4-software/meshchatx`). + +## Docker + +Quick start with Compose: + +```bash +docker compose up -d +``` + +Manual run with a named volume for persistence: + +```bash +docker run -d --name reticulum-meshchatx \ + --restart unless-stopped \ + --security-opt no-new-privileges:true \ + -p 127.0.0.1:8000:8000 \ + -v meshchatx-config:/config \ + ghcr.io/quad4-software/meshchatx:latest +``` + +Default Compose maps `127.0.0.1:8000` on the host to port `8000` in the container. Data persists in the `meshchatx-config` volume at `/config`. + +To bind a host directory instead, mount it at `/config`. The container runs as UID 1000. The host directory must be writable by that user. + +## Python wheel + +1. Download `reticulum_meshchatx-*-py3-none-any.whl` from [releases](https://github.com/Quad4-Software/MeshChatX/releases). +2. Install with pip, pipx, or uv: + +```bash +pip install reticulum_meshchatx-*.whl +``` + +3. Start the server: + +```bash +meshchatx --headless --host 127.0.0.1 +``` + +The `meshchat` command is a compatibility alias for the same entry point. + +## Linux AppImage and packages + +**AppImage** + +```bash +chmod +x ./ReticulumMeshChatX-v*-linux-*.AppImage +./ReticulumMeshChatX-v*-linux-*.AppImage +``` + +**Debian package** + +```bash +sudo dpkg -i reticulum-meshchatx_*_amd64.deb +``` + +Adjust the filename for your architecture. + +## From source (development) + +```bash +task install +pnpm run build-frontend +uv run python -m meshchatx.meshchat --headless --host 127.0.0.1 +``` + +Useful task targets include `task format`, `task lint`, `task test`, and `task build`. + +## First launch + +On first run MeshChatX creates a random Reticulum identity if you do not pass one on the command line. The identity file is stored under your configured storage directory. + +Open the UI at the host and port you chose. HTTPS is enabled by default with a self-signed certificate unless you pass `--no-https` or provide your own PEM files. + +## Command-line options + +Common flags and environment variables: + +| Flag | Environment variable | Default | Description | +| ---- | -------------------- | ------- | ----------- | +| `--host` | `MESHCHAT_HOST` | `127.0.0.1` | Bind address | +| `--port` | `MESHCHAT_PORT` | `8000` | HTTP or HTTPS port | +| `--no-https` | `MESHCHAT_NO_HTTPS` | false | Serve plain HTTP | +| `--ssl-cert` | `MESHCHAT_SSL_CERT` | auto | TLS certificate path | +| `--ssl-key` | `MESHCHAT_SSL_KEY` | auto | TLS private key path | +| `--headless` | `MESHCHAT_HEADLESS` | false | Do not open a browser | +| `--auth` | `MESHCHAT_AUTH` | false | Require HTTP basic auth for the UI | +| `--storage-dir` | `MESHCHAT_STORAGE_DIR` | `./storage` | Application data directory | +| `--reticulum-config-dir` | (see `--help`) | `~/.reticulum` | Reticulum configuration | +| `--identity-file` | `MESHCHAT_IDENTITY_FILE` | none | Load identity from file | +| `--rns-log-level` | `MESHCHAT_RNS_LOG_LEVEL` | none | Reticulum log level | +| `--auto-recover` | `MESHCHAT_AUTO_RECOVER` | false | Attempt SQLite recovery on start | +| `--emergency` | | false | Start without database | +| `--disable-plugins` | | false | Disable the plugin system | + +CLI flags override environment variables when both are set. + +## Reticulum manual bundle + +The Reticulum HTML manual is fetched at build time. After cloning the repository, run: + +```bash +pnpm run build-docs +``` + +This populates `meshchatx/public/reticulum-docs-bundled/current/`. Without that step the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP. + +## Identity bootstrap + +You can supply an identity at startup: + +- `--identity-file /path/to/identity` +- `--identity-base64` or `--identity-base32` with the corresponding environment variables + +Otherwise MeshChatX generates one and saves it under `/identity`. Additional identities are created from the **Identities** page. Each identity has its own database, LXMF router, and settings while sharing one Reticulum process. + +## After install + +1. Add at least one **interface** so Reticulum can reach peers. +2. Review **Settings** for display name, theme, language, and LXMF stamp costs. +3. Enable **telephone** in settings if you plan to use audio calls. +4. Open **Documentation** for MeshChatX guides and the Reticulum manual offline. + +Platform-specific notes live under **Platform guides** in this documentation bundle. diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/interfaces.md b/meshchatx/src/frontend/public/meshchatx-docs/en/interfaces.md new file mode 100644 index 00000000..81a47461 --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/interfaces.md @@ -0,0 +1,87 @@ +# Reticulum interfaces + +Interfaces connect your MeshChatX node to the Reticulum mesh. Manage them from the **Interfaces** page. + +## What an interface does + +Each interface is a Reticulum transport definition. Examples include TCP over the internet, UDP discovery, LoRa through an RNode, serial KISS devices, I2P tunnels, and automatic LAN discovery. + +MeshChatX reads and writes interface configuration in your Reticulum config directory (default `~/.reticulum`). + +## Supported interface types + +The **Add interface** flow includes: + +| Type | Typical use | +| ---- | ----------- | +| TCPClientInterface | Connect outbound to a known TCP peer | +| TCPServerInterface | Accept inbound TCP connections | +| BackboneInterface | High-throughput backbone link | +| UDPInterface | UDP transport with discovery helpers | +| RNodeInterface | LoRa via RNode (serial, BLE, or IP transport) | +| RNodeIPInterface | RNode reached over IP | +| SerialInterface | Direct serial devices | +| KISSInterface | KISS TNC devices | +| I2PInterface | I2P-based Reticulum transport | +| AutoInterface | Automatic discovery on local networks | +| Custom external types | Advanced setups | + +Community-curated suggestions come from `community_interfaces.json`, sourced from [directory.rns.recipes](https://directory.rns.recipes). + +## Interface discovery + +Discovery can automatically connect to peers on your LAN or configured networks. You can maintain allowlists and blocklists, set autoconnect behaviour, and assign a network identity for discovered peers. + +## Import and export + +Export your interface set for backup or clone it to another machine. Import validates entries before applying them. + +## RNode tools + +LoRa setups often need firmware management. **Tools → RNode Flasher** opens the bundled flasher at `/rnode-flasher/`. Configure frequency, bandwidth, spreading factor, and TX power when adding an RNode interface. + +## Websocket server interface + +MeshChatX includes a custom `WebsocketServerInterface` for WebSocket-based Reticulum transport. Use it when bridging to web-friendly gateways. + +## Getting onto the mesh + +A minimal path for a new node: + +``` +Install MeshChatX + | + v +Add interface (TCP client, community suggestion, or RNode) + | + v +Reticulum establishes transport + | + v +Paths and announces populate in the UI + | + v +LXMF, LXST, and Nomad features become reachable +``` + +1. Pick a community interface or ask your mesh operator for TCP endpoint details. +2. Add the interface and enable it. +3. Watch the path table (**Tools → RNPath**) if connectivity fails. +4. Enable **auto-announce** so your services are visible. + +## Bundled documentation hints + +The Interfaces UI links into the Reticulum manual sections on interface options. Open **Documentation → Reticulum** and search for `interfaces` if you need field-by-field reference. + +## Tips + +- Run only the interfaces you need. Each open port or radio adds attack surface and power draw. +- On Raspberry Pi and Android, prefer a single well-known TCP uplink if LoRa hardware is not attached. +- After editing Reticulum config externally, use the reload controls or restart MeshChatX so changes apply cleanly. +- Keep firmware on RNodes current using the flasher tool before debugging RF issues. + +## See also + +- **Installation and setup** for Reticulum config directory flags +- **Tools and utilities** for RNPath, RNProbe, and Ping +- Reticulum manual **Interfaces** chapter for protocol-level detail diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/messaging.md b/meshchatx/src/frontend/public/meshchatx-docs/en/messaging.md new file mode 100644 index 00000000..d54688de --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/messaging.md @@ -0,0 +1,111 @@ +# LXMF messaging + +MeshChatX uses LXMF (LXMF Message Format) for direct and store-and-forward messaging over Reticulum. Each identity has an `LXMRouter` registered under aspect `lxmf.delivery`. + +## Conversations + +Open **Messages** to see your conversation list. Each row is a peer destination you have exchanged traffic with or selected from announces. + +From a conversation you can: + +- Send and receive text messages +- Attach images, audio clips, and files +- Reply with quotes and add reactions +- Organise threads into folders and pin important chats +- Run bulk operations on multiple conversations + +Incoming messages arrive over the WebSocket as `lxmf_message` events. The UI updates without a full page reload. + +## Attachments and rich content + +The composer supports: + +- **Images** via LXMF image fields +- **Audio** via LXMF audio fields +- **Files** as LXMF file attachments +- **Stickers and GIFs** when enabled in settings +- **User icons** stored as LXMF app data + +Large payloads follow LXMF sizing and stamp rules configured in settings. + +## Propagation nodes + +When a peer is not reachable directly, LXMF can store messages on propagation nodes. + +MeshChatX can: + +- Run a **local propagation node** on your identity +- **Sync** with remote propagation nodes you trust +- **Auto-select** a preferred node via `AutoPropagationManager` +- **Retry** failed direct deliveries through propagation when configured + +Manage nodes from **Tools → Propagation nodes** or related settings entries. + +## Stamp costs and stranger protection + +LXMF uses work proofs (stamps) to limit abuse. Settings let you tune: + +- Outbound stamp costs for your messages +- Inbound stamp requirements for unknown senders +- **Stranger protection** options such as blocking strangers, attachments, or links from unknown peers +- **Flood protection** with dynamic inbound stamp costs based on rate + +Raise inbound costs when you operate a public-facing node. Lower them on trusted private meshes. + +## Filtering and blocking + +- **Blocked** destinations stop traffic from specific hashes. +- **Sieve filters** (beta) drop inbound messages by pattern. +- **Message blocklist** (beta) complements sieve rules for known bad content. +- **Spam reporting** helps you mark unwanted conversations. + +## Paper messages + +**Tools → Paper message** generates LXMF URIs you can share as QR codes. Another MeshChatX user can ingest the URI to receive the payload. Useful for offline handoff when no live path exists yet. + +## Forwarding + +`ForwardingManager` supports alias identities that forward messages between peers according to rules you define. Configure forwarding from **Tools → Forwarder**. + +## Import and export + +You can import and export messages and folder structures for backup or migration. Operations go through the API and respect identity boundaries. + +## Local retention + +**Local message auto-delete** removes old messages after a configured retention period. Tune this in settings if you operate on storage-constrained hardware. + +## Messaging flow + +``` +Composer in UI + | + v +POST /api/v1/lxmf-messages/send + | + v +LXMRouter (identity-local) + | + +--> Direct path to peer destination + | + +--> Propagation node (when direct delivery fails or policy requires it) + | + v +Peer LXMF router + | + v +WebSocket lxmf_message event on recipient UI +``` + +## Tips + +- Set a **display name** in settings so announces show a friendly label. +- Enable **auto-announce** so your `lxmf.delivery` aspect stays visible on the mesh. +- Check **Interfaces** if messages stall. No path to the peer means LXMF cannot deliver. +- Review stamp settings before joining busy public meshes. + +## See also + +- **Reticulum interfaces** for connectivity +- **Identities, privacy, and security** for auth and HTTPS +- Reticulum manual section on LXMF for protocol detail diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/nomad-network.md b/meshchatx/src/frontend/public/meshchatx-docs/en/nomad-network.md new file mode 100644 index 00000000..68cb88d8 --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/nomad-network.md @@ -0,0 +1,77 @@ +# Nomad Network and Mesh Server + +Nomad Network is a distributed page and file system on top of Reticulum. MeshChatX includes a browser for remote nodes and a **Mesh Server** tool for hosting your own pages. + +## Nomad browser + +Open **Nomad Network** and enter a node destination hash. MeshChatX fetches the default entry page (usually `/page/index.mu`) over Reticulum link requests. + +Supported page types: + +| Extension | Format | +| --------- | ------ | +| `.mu` | Micron markup (NomadNet default) | +| `.md` | Markdown with GFM-oriented rendering | +| `.txt` | Plain text with preserved whitespace | +| `.html` | Static HTML with sanitised CSS | + +Follow links inside pages to browse further paths on the same node. Download files offered at `/file/*` paths. + +Rendering uses `NomadPageRenderer.js` with DOMPurify sanitization. Micron can use a JavaScript parser or optional Go WASM when `nomad_micron_wasm_enabled` is set. + +## Favourites and caching + +Save frequent nodes as favourites. Link caching (`nomadnet_cached_links`) speeds up repeat visits on slow links. + +## Archives + +When **page archiver** is enabled, MeshChatX stores versioned snapshots of pages you visit. Open **Archives** to browse historical copies. An optional crawler can archive automatically. + +Archived pages use the same renderer as the live browser based on the stored `page_path` extension. + +## Mesh Server (page nodes) + +**Tools → Mesh Server** lets you run a `nomadnetwork.node` destination locally. + +Typical workflow: + +1. Create a page node in the UI. +2. Upload `.mu`, `.md`, `.txt`, or `.html` pages and optional files. +3. Start the node and announce it on the mesh. +4. Share your destination hash so others can open `/page/index.mu` on your node. + +API endpoints under `/api/v1/page-nodes/` manage CRUD operations, start and stop, and file listings. + +Pages are served at `/page/` and files at `/file/` on the node destination. + +## Browsing flow + +``` +User enters destination hash + | + v +RNS link request to /page/index.mu (or chosen path) + | + v +Remote page node responds with content + | + v +NomadPageRenderer picks Micron, Markdown, text, or HTML pipeline + | + v +Sanitised HTML shown in Nomad Network view +``` + +## Authoring pages + +Read **NomadNet page formats** for security rules, Markdown quirks, and API behaviour. The Mesh Server rejects disallowed extensions on upload. + +## Micron editor + +**Tools → Micron editor** helps author `.mu` pages before you upload them to your node. + +## See also + +- **NomadNet page formats** for detailed authoring reference +- **Tools and utilities** for the full tools list +- **Reticulum interfaces** if remote pages time out (likely a path issue) diff --git a/meshchatx/src/frontend/public/meshchatx-docs/en/nomadmesh-pages.md b/meshchatx/src/frontend/public/meshchatx-docs/en/nomadmesh-pages.md new file mode 100644 index 00000000..c50f76cc --- /dev/null +++ b/meshchatx/src/frontend/public/meshchatx-docs/en/nomadmesh-pages.md @@ -0,0 +1,52 @@ +# NomadNet page formats + +MeshChatX serves pages from a **Mesh Server** page node and displays them in the **Nomad Network** browser. Pages are fetched with the Nomad path convention `/page/`. + +## Supported filenames + +| Extension | Role | +| --------- | ---- | +| `.mu` | Micron markup (NomadNet default) | +| `.md` | Markdown with GitHub-flavored features via the renderer | +| `.txt` | Plain text with escaped HTML and preserved whitespace | +| `.html` | Static HTML with CSS only (see security below) | + +If you add a page without a recognised extension, the server stores it as `.mu`. Filenames with other extensions (for example `.exe`) are rejected when saving through the API. + +## Plain text (`.txt`) + +Content is HTML-escaped and shown with pre-wrapped whitespace. There is no Markdown parsing on `.txt` pages. + +## Markdown (`.md`) + +**Not the same engine as chat.** Conversations use the lightweight `MarkdownRenderer` in the messaging UI. Nomad `.md` pages use `marked` with GFM-oriented rules plus sanitisation. Features and edge cases can differ between the two paths. Automated tests cover both. + +Authoring tips: + +- Use ATX headings with a hash and a space before the title, for example `# Title`, `## Section`, `#### Subsection`. +- Fenced code blocks keep indentation. +- Off-mesh `http` and `https` links in rendered content are removed or restricted so the preview cannot drive external navigation without mesh-style URLs. + +## HTML (`.html`) + +- **JavaScript** is not executed. `script` tags and event-handler attributes are stripped. +- **External resources** are blocked where possible. `@import` and `url(...)` pointing at `http://`, `https://`, or protocol-relative URLs are removed from CSS. +- Embedded `