# MeshChatX > MeshChatX is an all-in-one Reticulum client: LXMF messaging, LXST voice calls, NomadNet browsing, relay chat, maps, and Reticulum utilities. No central servers. Identity is a destination hash. Site: https://meshchatx.com Source: https://github.com/Quad4-Software/MeshChatX # Documentation ## Android (Termux) MeshChatX runs on Android through [Termux](https://termux.dev/). Release wheels ship the Python backend and built web UI, so you do not need Node on the phone for a normal install. ## Install from wheel The wheel bundles server code and frontend assets. ### System packages ``` pkg upgrade pkg install python pkg install rust pkg install binutils pkg install build-essential ``` > Note: Python 3.11 or higher is required. Check with python --version. ### Wheel install Download the latest wheel from the [releases page](https://github.com/Quad4-Software/MeshChatX/releases), then: ``` pip install reticulum_meshchatx-*-py3-none-any.whl ``` The wheel pulls Python dependencies automatically. Building cryptography can take several minutes on Android. ### Run ``` meshchatx ``` (meshchat is a compatibility alias for the same entry point.) Open http://localhost:8000 in the Android browser. ## Install from source Use this path for development or when no wheel fits your setup. ### System packages ``` pkg upgrade pkg install git pkg install nodejs-lts pkg install python pkg install rust pkg install binutils pkg install build-essential ``` ### pnpm ``` corepack enable corepack prepare pnpm@latest --activate ``` ### Clone and build ``` git clone https://github.com/Quad4-Software/MeshChatX.git cd MeshChatX pip install uv uv sync --group dev pnpm install pnpm run build-frontend uv build --wheel pip install dist/*.whl ``` ### Run ``` meshchatx ``` (meshchat is a compatibility alias for the same entry point.) ## Configuration notes > Note: The default AutoInterface may not work on your Android device. Configure another interface such as TCPClientInterface in the settings. ## Architecture and design MeshChatX is a fork of Reticulum MeshChat with LXST telephony, RRC relay chat, Nomad tooling, plugins, and a SQL backend without Peewee. 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. Mesh features should follow Reticulum post-IP design patterns: portable identity hashes, announces, store-and-forward, transport-agnostic APIs, and scarce payloads. See the [Zen of Reticulum](https://reticulum.network/manual/zen.html). ## 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 package (src/backend/http/) | +-- middleware + register_all_routes | +-- routes/* (/api/v1/*, shell, static helpers) | +-- ws/* (dispatch and handlers) +-- 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 wires HTTP via meshchatx/src/backend/http/, 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. ## Destination aspects Mesh peers are addressed by destination hash plus aspect. MeshChatX currently uses: | Aspect | Role | | ------------------- | ---------------------------- | | lxmf.delivery | LXMF mail | | lxmf.propagation | Propagation node | | lxst.telephony | LXST calls | | nomadnetwork.node | NomadNet / Mesh Server pages | | rrc.hub | Relay Chat hub | | map-data-v1 | Published map overlay packs | map-data-v1 announce app_data is a short JSON label and file count. Catalog and file bytes travel over an RNS Link, not announce payloads or LXMF. The destination is created and announced only after at least one pack is published. Mesh announce stays a separate opt-in. ## 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 through backend/http/register.py into aiohttp route tables (still discoverable as @routes. for contract scanners). 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) - Map overlays and map-data-v1 publish/discover - 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 (Alpine Dockerfile with standard/extra VARIANT, plus hardened Chainguard) - 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 WASM plugins** run in wasmtime with fuel metering and capability-gated host functions. - **Backend Python plugins** (backend.type: "python") run in-process with a permission-checked host (log, managers, storage, network flag). - **WASM bundles** embed manifest/files/signature in custom sections and unpack on install. - **Sideband-compatible loader** optionally execs flat *.py plugins with PLUGIN_COMMAND LXMF dispatch. - **Security core** verifies RSG signatures, trusted publishers, integrity hashes, and heuristic findings. - **HTTP API** under /api/v1/plugins/* and /api/v1/sideband-plugins/* for install, enable, invoke, trust, and Sideband config. 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 - Generic RNS Link transport over WebSocket (rns.link.*) for external consoles and plugins (see [RNS Link API](rns-link-api)) Granted plugin manager capabilities include destinationPath.read, debugLog.read, bugReport.*, and rnsLink.open / identify / request / send / close. Hooks include announce.received and rns.link.event. Storage (storage:isolated) and outbound HTTP (network:fetch) are also grantable. The installation preview scans plugin files for external URLs and stores the user-selected grant subset. 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. - [Plugins](plugins) for extension architecture and security. - The **Reticulum** tab in Documentation for protocol reference. ## 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** microphone (transmit) or speaker (receive) - Switch **full duplex** or **half duplex** during a live call - Use **push-to-talk** while in half duplex (hold the PTT control or Space) Half duplex uses LXST packetizer squelch so idle airtime stays low on constrained links. Full duplex keeps both directions open. Call state changes arrive over the WebSocket (telephone_ringing, telephone_call_established, telephone_call_ended, and related events). While connected, the Call screen shows link stats (packets, bytes, approximate bitrates, path hops, and interface). ## 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. Unread missed calls show as a red count on the Calls sidebar icon and the header phone button. Opening the Call page clears that count. Desktop and Android still show a one-shot missed-call notification when the event happens. ## 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 ``` ## Windows microphone (Electron, Windows 10 / 11) Calls and voice attachments use the mic through Chromium. If the UI has no access or getUserMedia fails, check Windows privacy first. That is a common miss for Win32 apps, Electron included. 1. Win+R, paste ms-settings:privacy-microphone, Enter. 2. Turn Microphone access on. 3. Enable Let desktop apps access your microphone (wording varies by Windows version). 4. If a per-app list appears, make sure MeshChatX is not denied. Also check Settings, System, Sound so the app is not muted and a working input device is selected. ## 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. - **Docker / headless web**: containers have no PulseAudio host devices. MeshChatX forces the web audio bridge (MESHCHAT_FORCE_WEB_AUDIO=1) and installs hostless LXST backends so calls can use the browser mic/speaker. Enable telephone in settings, then place a call from the web UI over HTTPS. - **Android Codec2**: native libcodec2.so must be preloaded before pycodec2. If Codec2 profiles are hidden, check /api/v1/telephone/codec2/status and rebuild with vendor wheels that bundle pycodec2/libcodec2.so. ## 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 ## Building from source and packaging How to build MeshChatX offline, package desktop artifacts, and produce Android APKs. For day-to-day install and CLI flags, see **Installation and setup**. Tagged releases build Linux wheel/AppImage/deb/rpm, Windows, macOS, Flatpak, and Android APKs (when the tag is on dev or master) in [build-release.yml](https://github.com/Quad4-Software/MeshChatX/blob/master/.github/workflows/build-release.yml). The container image is [docker.yml](https://github.com/Quad4-Software/MeshChatX/blob/master/.github/workflows/docker.yml). Branch and PR Android CI is [android-build.yml](https://github.com/Quad4-Software/MeshChatX/blob/master/.github/workflows/android-build.yml). Linux x64 and arm64 AppImage and DEB are built on GitHub. RPM is uploaded when the job produces one. ## Offline builds Two levels: 1. Cached: you already ran make install once, so node_modules, .venv, and local caches exist. 2. Air-gapped: the build machine has never had internet. Build a bundle on a networked machine and copy it over. ### Cached offline builds Set MESHCHATX_OFFLINE_BUILD=1 before any build command. That skips micron-parser-go WASM, the Reticulum manual, and repository wheel fetches, and runs package managers offline. Missing cache files fail the build instead of hanging. ```bash MESHCHATX_OFFLINE_BUILD=1 make install MESHCHATX_OFFLINE_BUILD=1 pnpm run build:offline MESHCHATX_OFFLINE_BUILD=1 pnpm run dist:linux:offline MESHCHATX_OFFLINE_BUILD=1 ./gradlew :app:assembleRelease ``` Cached mode only skips build-time network. The first make install still needs the network, or pre-populated pnpm and uv caches. ### Air-gapped builds On the online machine: ```bash pnpm run bundle:offline bash scripts/create-offline-bundle.sh --warm-packaging tar czf meshchatx-offline-linux-x64.tar.gz -C vendor/offline meshchatx-offline-bundle-*/ ``` --warm-packaging is optional. It pre-downloads tools such as appimagetool. On the air-gapped machine: ```bash tar xzf meshchatx-offline-linux-x64.tar.gz bash scripts/install-offline.sh MESHCHATX_OFFLINE_BUILD=1 make build MESHCHATX_OFFLINE_BUILD=1 pnpm run dist:linux ``` The bundle is platform-specific (Electron, esbuild, and other native binaries). Create it on the same OS and architecture as the air-gapped host. That host still needs node, pnpm, uv, and python3. The bundle is dependencies and caches, not the toolchain. Android is separate. The offline bundle does not include Chaquopy wheels. Build those on an online machine with bash scripts/build-android-wheels-local.sh, copy android/vendor/ next to the project, then run Gradle with MESHCHATX_OFFLINE_BUILD=1. ## Desktop packages from source ```bash pnpm run dist:linux-x64 pnpm run dist:linux-arm64 pnpm run dist:rpm task dist:fe:rpm ``` Windows (x64 and arm64) and macOS (arm64 and universal) scripts are in package.json for local builds. ## Container build (wheel, AppImage, deb, rpm) [Dockerfile.build](../../Dockerfile.build) runs the same shell steps CI uses (Poetry, pnpm, task, packaging APT deps). It is aimed at linux/amd64 (NodeSource amd64 tarball, Task amd64 binary). MESHCHATX_BUILD_TARGETS defaults to all. Other values: wheel, or electron (AppImage + deb for x64 and arm64, best-effort RPM, no wheel). ```bash docker build -f Dockerfile.build -t meshchatx-build:local . docker build -f Dockerfile.build --build-arg MESHCHATX_BUILD_TARGETS=wheel -t meshchatx-build:wheel . ``` Copy artifacts off the image: ```bash cid=$(docker create meshchatx-build:local) docker cp "${cid}:/artifacts" ./meshchatx-artifacts docker rm "${cid}" ``` ## Android APK Native APK builds, not only Termux. From the repo root: ```bash bash scripts/build-android-wheels-local.sh cd android ./gradlew --no-daemon :app:assembleDebug :app:assembleRelease ``` Offline: ```bash MESHCHATX_OFFLINE_BUILD=1 ./gradlew --no-daemon :app:assembleRelease ``` That skips the repository wheels fetch. android/vendor/ wheels and meshchatx/public/repository-server-bundled/bundled/ must already be present. There is one Android variant. Gradle syncs the full meshchatx/ tree into app/src/main/python/meshchatx/, including the offline repository wheel bundle. Published builds are universal: one debug APK and one release APK per run, with the native ABIs from android/app/build.gradle. - Debug: android/app/build/outputs/apk/debug/app-debug.apk - Release: android/app/build/outputs/apk/release/ReticulumMeshChatX-v*-android-universal-unsigned.apk - GitHub release: ReticulumMeshChatX-v-android-universal.apk Release APKs are unsigned unless you configure signing (scripts/sign-android-apks.sh). Native ABIs follow android/app/build.gradle, including armeabi-v7a when that ABI is enabled. Building those wheels needs an Android SDK on ANDROID_HOME. If dist/reticulum_meshchatx-*.whl exists (for example from python -m build --wheel -o dist .), bundled repository refresh prefers that wheel over PyPI. CI builds that wheel before the Android Gradle step. More: [Android (Termux)](android-termux), [android/README.md](https://github.com/Quad4-Software/MeshChatX/blob/master/android/README.md), [Meta Quest (SideQuest)](quest-sidequest). ## See also - **Installation and setup** for Docker, wheels, and CLI flags - **Development** for task targets and version sync - **Linux sandboxing** for Firejail and Bubblewrap around a built binary ## Development Contributor workflow: install, format, lint, test, version bumps, and adding locales. Runtime install paths are in **Installation and setup**. Packaging is in **Building from source and packaging**. ## Branches | Branch | Purpose | | ------ | -------------------------------------------------- | | master | Stable releases | | dev | Active development. May be incomplete or breaking. | ## Daily commands ```bash task install task hooks:install # pre-commit format/lint + commitlint (once per clone) task format task lint task test task build ``` Makefile targets call the same Taskfile commands: | Command | Delegates to | Description | | -------------------- | ------------------ | ----------------------------------------------------------- | | make install | task install | Install pnpm and UV dependencies | | make run | task run | Run MeshChatX via UV | | make build | task build | Build frontend and backend artifacts | | make format | task format | Format frontend and backend | | make lint | task lint | ESLint, vue-tsc, knip, Ruff, basedpyright | | make test | task test | Frontend and backend tests | | make clean | task clean | Remove build artifacts and node_modules | | make tree-rsm-verify | (shell) | Verify meshchatx.rsm signature and hashes | | make tree-rsm-sign | (shell) | Sign tree inventory (needs RNS_ID_PATH) | | make hooks-install | task hooks:install | Git hooks: format/lint staged files, commitlint, RSM resign | For a Vite HMR loop, use task dev as described in **Installation and setup**. ## Lockfiles and install scripts From a clean clone: ```bash git clone https://github.com/Quad4-Software/MeshChatX.git cd MeshChatX corepack enable pnpm config set verify-store-integrity true pnpm install --frozen-lockfile pip install "uv==0.11.15" uv lock --check uv sync --group dev pnpm run build-frontend uv run python -m meshchatx.meshchat --headless --host 127.0.0.1 ``` pnpm install --frozen-lockfile fails if pnpm-lock.yaml does not match package.json, so an unexpected upstream version cannot land silently. Store integrity is also on in pnpm-workspace.yaml. The extra pnpm config set line hardens the user-level config too. pnpm v11+ blocks lifecycle scripts by default. Only packages listed under allowBuilds in pnpm-workspace.yaml may run install scripts (electron, electron-winstaller, esbuild). uv lock --check fails if uv.lock is out of date with pyproject.toml. uv sync then installs from the lockfile only. Pin UV with pip install "uv==0.11.15" to match CI. To update dependencies on purpose, run pnpm update or uv lock in its own commit and read the lockfile diff before you push. ## Versioning Edit the version field in package.json, then run pnpm run version:sync (also the first step of pnpm run build). That copies the number into pyproject.toml, the Python version modules, Android Gradle, electron/app-version.json, the README and translated READMEs, the Raspberry Pi pipx example, Arch PKGBUILD helpers, third-party notices, and GitHub issue-template placeholders. Changelog entries are still written by hand when you cut a release. meshchatx.__version__ is read from meshchatx/src/version.py without importing meshchatx.src, so import meshchatx stays lightweight. ## Adding a language Locale discovery is automatic. Add a file under meshchatx/src/frontend/locales/ (for example xx.json) with the same keys as en.json and a top-level _languageName string for the selector label. Copy en.json and translate the values. Machine-assisted generation is optional. For a machine-generated first draft from en.json, use scripts/argos_translate.py. It keeps interpolation variables such as {count} intact. ```bash pipx install argostranslate python scripts/argos_translate.py --from en --to xx --input meshchatx/src/frontend/locales/en.json --output meshchatx/src/frontend/locales/xx.json --name "Your Language Name" ``` After a machine pass, have an LLM or a human check grammar, context, and tone. ```bash pnpm test -- tests/frontend/i18n.test.js --run ``` That checks key parity with en.json. No other code changes. The app, language selector, and tests pick up locales from meshchatx/src/frontend/locales/ at build time. Translation fixes are welcome via LXMF (f489752fbef161c64d65e385a4e9fc74) or a pull request. In-app MeshChatX guides under docs/en/ are English today. Localized landing pages exist for the Reticulum manual tab. ## See also - **Architecture and design** for process layout and managers - **Building from source and packaging** for offline and APK builds ## Getting started 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. In a regular browser (including headless or LAN installs), MeshChatX may register a service worker that caches hashed UI assets and the app shell so repeat loads are faster and a hard refresh can still show the boot splash while the local backend restarts. Mesh messaging, identity, and API data still require the Python backend. Electron does not use this service worker path. ## 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 | | Network visualiser | /network-visualiser | Graph view of mesh topology (sidebar Explore) | | Tools | /tools | Ping, path tools, RNCP, bots, documentation, and more | | Settings | /settings | Theme, language, LXMF, telephone, security | | Archives | /archives | Versioned snapshots of Nomad pages (sidebar More) | | Interfaces | /interfaces | Add and manage Reticulum interfaces (sidebar More) | | Blocked | /blocked | Blocked destinations (sidebar More) | | Identities | /identities | Create, import, or switch identities (sidebar More) | | About | /about | Version, health, backups (sidebar More or footer) | | Documentation | /documentation | MeshChatX guides and the Reticulum manual | Relay chat appears only when rrc_enabled is turned on in settings. When enabled, the Relay chat icon can show a red mention count. Messages shows unread conversation count. Calls shows unread missed-call count. ## 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 (CLI default) | | --------------------- | ---------------------------------------------------------------------------------------------- | | MeshChatX app data | ./storage (or --storage-dir / MESHCHAT_STORAGE_DIR) | | Reticulum config | ~/.reticulum (or --reticulum-config-dir / MESHCHAT_RETICULUM_CONFIG_DIR) | | Portable bundle | /storage and /.reticulum when using --data-dir / MESHCHAT_DATA_DIR | | Desktop Electron data | ~/.reticulum-meshchatx and ~/.reticulum unless overridden at launch | | 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 CLI flags. - **Building from source and packaging** covers offline builds, Dockerfile.build, and Android APKs. - **Development** covers task/make, version sync, and adding locales. - **Architecture and design** explains backend managers, identity scoping, and the API model. - **LXMF messaging** and **Audio calls** describe day-to-day communication features. - **Identities, privacy, and security** covers backups and corruption recovery. - **Reticulum interfaces** explains how your node joins the mesh. - Platform guides under **Platform guides** cover Raspberry Pi, Android Termux, Meta Quest, and Linux sandboxing (Firejail and Bubblewrap). For protocol-level detail, open the **Reticulum** tab in Documentation or visit the [Reticulum manual](https://reticulum.network/manual/) online. ## 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) | | map-data-v1 | Published GeoJSON/KML/KMZ packs | 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. ### Demo mode and ALTCHA MESHCHAT_DEMO_MODE=1 (or --demo) enables a public showcase profile: privacy mode on, plugins off, no outbound announces, and a default-deny HTTP mutation policy with mesh send blocked. Status reports demo_mode: true. When MESHCHAT_ALTCHA_ENABLED=1, login and setup require a valid [ALTCHA](https://altcha.org/docs/v2/widget-v3/) proof-of-work payload (widget v3, server challenges use PBKDF2/SHA-256 by default). Set MESHCHAT_ALTCHA_HMAC_KEY to a long random secret on the server. Optional MESHCHAT_ALTCHA_COST tunes PoW difficulty. The widget loads from the bundled altcha npm package and fetches challenges from /api/v1/auth/altcha/challenge. MESHCHAT_AUTH_PAGE_HINT sets optional plain text on the login page (independent of demo mode). Demo Docker compose defaults to username and password hints for the showcase account. MESHCHAT_AUTH_BYPASS=1 skips session auth for local testing only. Do not use it on internet-facing deployments. ## 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. - WebSocket upgrades require a same-authority Origin when the browser sends one. Missing Origin is allowed on loopback binds (and when password auth is enabled) so local non-browser tools keep working. On a non-loopback bind without password auth, a missing Origin is rejected. - /ws inbound messages are rate-limited per connection. Nomad file downloads over WS are capped (default 10 MiB) and large transfers use chunked frames. Debug counters are at GET /api/v1/debug/websocket. 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 On Linux, MeshChatX can enable two complementary in-process sandboxes when supported: - **Landlock** restricts filesystem paths the backend may use. User-local pipx tools (for example Argos Translate under ~/.local) need explicit read and sometimes write roots. See **Linux sandboxing** in Platform guides. - **Seccomp-BPF** installs a syscall denylist (via libseccomp) that blocks kernel-admin and related calls a mesh client does not need. Both auto-enable when available and fall back to a no-op when the platform, kernel, or libraries cannot support them. Override with: - MESHCHAT_LANDLOCK=0 or 1 - MESHCHAT_SECCOMP=0 or 1 Android never enables these in-process sandboxes (the Android app seccomp policy already constrains the process, and Landlock syscalls are blocked there). See **Linux sandboxing** in Platform guides for optional Firejail and Bubblewrap wrappers around the host install. ## Windows Electron AppContainer Windows desktop builds can spawn the Python backend inside an LPAC AppContainer when MESHCHAT_APPCONTAINER=1. Default installs start the backend directly without that wrapper. Check /api/v1/server/security for appcontainer_active when debugging sandbox-related SQLite or filesystem errors on Windows. ## 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/. Before a schema upgrade, MeshChatX writes a backup-pre-migrate-v*-to-v*.zip in that folder unless MESHCHAT_SKIP_PRE_MIGRATE_BACKUP=1. After a successful migration it runs PRAGMA quick_check and keeps the five newest pre-migrate zips (override with MESHCHAT_PRE_MIGRATE_BACKUP_KEEP, 0 disables pruning). If the stored schema version is newer than this build supports, startup refuses to migrate. Only one process should use a given identity storage directory at a time (storage lock). Roll back by restoring a backup zip and running an older MeshChatX build. Export snapshots from **About** or the API. Electron crash recovery can offer restore when integrity checks fail. CLI examples: ```bash meshchatx --list-backups meshchatx --export-backup /path/to/export.zip meshchatx --export-backup backup-20260101-120000.zip /path/to/copy.zip meshchatx --restore-db /path/to/backup.zip ``` When the backend can start briefly, or you run from source: ```bash meshchatx --storage-dir /path/to/storage --restore-db /path/to/backup.zip ``` ## Database corruption and data reset If MeshChatX fails to start with errors such as database disk image is malformed, DatabaseError, or corrupted ratchet data, the desktop crash screen offers: - Restore latest backup from database-backups/ or snapshots/ inside the MeshChatX storage folder - Choose backup file for a zip you saved elsewhere - Try auto-repair (--auto-recover: SQLite checkpoint / integrity pass) - Emergency mode, which opens the app without the database so you can export from About when possible - Copy reset instructions with the folders to delete for a clean reinstall ### Storage locations | Platform | MeshChatX storage | Reticulum network stack | | ---------------- | ---------------------------------------------- | ------------------------------------ | | Linux / macOS | ~/.reticulum-meshchatx/ | ~/.reticulum/ | | Windows | %USERPROFILE%\.reticulum-meshchatx\ | %USERPROFILE%\.reticulum\ | | Windows portable | \.reticulum-meshchatx\ | \.reticulum\ | Legacy Reticulum MeshChat data may still exist at ~/.reticulum-meshchat/ (or the Windows equivalent). Automatic database backups go to database-backups/ inside the MeshChatX storage folder after a successful run. ### Complete removal Quit MeshChatX. On Windows, also end ReticulumMeshChatX.exe in Task Manager if it is still running. Then delete the MeshChatX storage folder and the Reticulum config folder for your install type. That removes the local identity, messages, contacts, path cache, and ratchet state. The next launch creates a new identity unless you restore a backup first. Linux / macOS: ```bash rm -rf ~/.reticulum-meshchatx ~/.reticulum ~/.reticulum-meshchat ``` Windows PowerShell: ```powershell Remove-Item -Recurse -Force "$env:USERPROFILE\.reticulum-meshchatx", "$env:USERPROFILE\.reticulum", "$env:USERPROFILE\.reticulum-meshchat" -ErrorAction SilentlyContinue ``` If you pass --storage-dir or --reticulum-config-dir, delete those directories instead. ## 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. ## Plugin signing and trust Packaged plugins may include a Reticulum Signature (.rsg) over a canonical ZIP payload (sorted paths, fixed 1980-01-01 mtimes, signature file excluded). MeshChatX plugin signing writes meshchatx.plugin.rsg and WASM sections meshchatx.plugin / meshchatx.files / meshchatx.signature. Policy: - Unsigned packages are allowed - Present but invalid signatures hard-block install - Valid signers can be added to a user trusted-publishers list (ignored if the list file is tampered outside MeshChatX) - Installed plugin trees get an integrity hash, on-disk changes disable the plugin as tampered Sideband Python plugins are opt-in via a master danger switch. They are not ZIP-permission gated. Optional per-file .py.rsg signatures are verified over script bytes. ## 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 ## 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 | | PyPI (reticulum-meshchatx) | Yes | Headless install without building the UI | | Release wheel | Yes | Same as PyPI from a GitHub artifact | | 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). Tag suffixes: none for the standard Alpine image, -hardened for Chainguard/Wolfi, -extra for Alpine plus i2pd and yggdrasil (VARIANT=extra on the same Dockerfile). ## Docker Quick start with Compose: ```bash docker compose up -d ``` Basic run: ```bash docker run -d --name reticulum-meshchatx \ -p 127.0.0.1:8000:8000 \ -v meshchatx-config:/config \ ghcr.io/quad4-software/meshchatx:latest ``` Hardened example with a named volume for persistence: ```bash docker run -d --name reticulum-meshchatx \ --restart unless-stopped \ --init \ --user 1000:1000 \ --security-opt no-new-privileges:true \ --cap-drop ALL \ --read-only \ --tmpfs /tmp:noexec,nosuid,size=256m \ --tmpfs /home/meshchat:nosuid,size=64m \ --cpus=2.0 \ --memory=1g \ --memory-reservation=256m \ --pids-limit=512 \ -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. Compose caps Docker's own json-file logs at **10 MB × 5 files** per container (logging.options). App file logs under /config already rotate separately (meshchatx.log, about 20 MB). For a bare docker run without Compose, add the same limits or the host can fill under /var/lib/docker/containers/: ```bash docker run -d --name reticulum-meshchatx \ --log-opt max-size=10m --log-opt max-file=5 \ -p 127.0.0.1:8000:8000 \ -v meshchatx-config:/config \ ghcr.io/quad4-software/meshchatx:latest ``` Coolify and other hosts that ignore Compose logging: should set equivalent log rotation in the platform UI. 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. Run only **one** MeshChatX instance per /config volume. Startup takes an exclusive storage lock so schema migration and runtime do not overlap. For Docker or Coolify, use a single replica on that volume and replace containers in a rolling stop-then-start order instead of two replicas sharing one config path. ### Public demo instance (Coolify) For a read-only mesh showcase on [Coolify](https://coolify.io/docs/knowledge-base/docker/compose), deploy [docker-compose.demo.yml](https://github.com/Quad4-Software/MeshChatX/blob/master/docker-compose.demo.yml). For a normal (non-demo) Coolify deployment, use [docker-compose.coolify.yml](https://github.com/Quad4-Software/MeshChatX/blob/master/docker-compose.coolify.yml). - MESHCHAT_DEMO_MODE=1 blocks outbound mesh actions and almost all API mutations. - MESHCHAT_AUTH=1 with default showcase password demo (MESHCHAT_DEMO_AUTH_PASSWORD). - Optional MESHCHAT_AUTH_PAGE_HINT shows custom text on the login page (for example Username: demo and Password: demo). Demo compose sets a default hint. - MESHCHAT_ALTCHA_ENABLED=1 and a strong MESHCHAT_ALTCHA_HMAC_KEY (required in demo compose via :?). The UI uses ALTCHA widget v3 with PBKDF2/SHA-256 challenges from /api/v1/auth/altcha/challenge. - Assign a domain with container port **8000**, for example https://meshchatx.example.com:8000. - Do not set MESHCHAT_AUTH_BYPASS=1 on a public host. ## Python package (PyPI or release wheel) Published on PyPI as [reticulum-meshchatx](https://pypi.org/project/reticulum-meshchatx/). Wheels include the built web assets. No Node.js on the runtime host. ```bash pip install reticulum-meshchatx # or pipx install reticulum-meshchatx # or uv tool install reticulum-meshchatx ``` ```bash meshchatx --headless --host 127.0.0.1 ``` The meshchat command is a compatibility alias for the same entry point. From a GitHub release artifact instead of PyPI: ```bash pip install ./reticulum_meshchatx-*-py3-none-any.whl ``` On hosts where libopus is installed but libogg is not, LXST's vendored pyogg can raise NameError: c_int_p on import. MeshChatX applies a ctypes compatibility fix at startup (the same patch Docker runs after install). Optional telephony audio still needs the usual Opus/Ogg system libraries when you use those codecs. ## From source (git clone) HTTPS: ```bash git clone https://github.com/Quad4-Software/MeshChatX.git cd MeshChatX ``` Over Reticulum with rngit (git-remote-rns): ```bash git clone rns://06a54b505bb67b25ef3f8097e8001edc/public/MeshChatX cd MeshChatX ``` Then use Make or Task (equivalent targets): ```bash make install make build make run ``` ```bash task install task build task run ``` ## Linux AppImage and packages **AppImage** ```bash chmod +x ./ReticulumMeshChatX-v*-linux-*.AppImage ./ReticulumMeshChatX-v*-linux-*.AppImage ``` **Debian package** ```bash sudo apt install ./ReticulumMeshChatX-v*-linux-*.deb ``` Adjust the filename for your architecture. **RPM** ```bash sudo rpm -Uvh ./ReticulumMeshChatX-v*-linux-*.rpm ``` Download the .rpm only when the release includes one. CI uploads RPM when the packaging job produces it. ## Linux desktop emoji fonts The emoji picker uses system fonts through Electron/Chromium. Empty squares mean a color emoji package is missing. Install one and restart the app. | Distro | Package | | -------------------- | ----------------------------------------- | | Arch, Artix, Manjaro | sudo pacman -S noto-fonts-emoji | | Debian, Ubuntu | sudo apt install fonts-noto-color-emoji | | Fedora | google-noto-emoji-color-fonts | If glyphs still fail, run fc-cache -fv or wait until the next login. noto-fonts helps on minimal installs that lack other symbol coverage. ## From source (development) ```bash task install task dev ``` task dev starts the HTTPS backend on 127.0.0.1:8000 and Vite on [http://127.0.0.1:5173](http://127.0.0.1:5173). Open that Vite URL. The [Vue DevTools](https://devtools.vuejs.org/) overlay is injected for this serve only. vite build / task run never ship it (__VUE_PROD_DEVTOOLS__ is false). Set MESHCHAT_VUE_DEVTOOLS=0 to hide the overlay. Click a component in the inspector to open it in the editor (LAUNCH_EDITOR, default code). Python breakpoints: task debug is the same stack with [debugpy](https://github.com/microsoft/debugpy) listening on 127.0.0.1:5678 (never 0.0.0.0). Run **MeshChatX: Vite + Python** from the debugger, or start task debug and attach **Backend: Attach debugpy**. task debug:wait pauses the backend until that attach happens. A production-like run without HMR: ```bash 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, task test:fe:ui, 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 | | --reset-password | MESHCHAT_RESET_PASSWORD | false | Clear the stored password hash so a new one can be set in the UI | | --storage-dir | MESHCHAT_STORAGE_DIR | ./storage | Application data directory | | --public-dir | MESHCHAT_PUBLIC_DIR | auto/bundled | Frontend files. Needed for source installs without bundled assets. | | --reticulum-config-dir | MESHCHAT_RETICULUM_CONFIG_DIR | ~/.reticulum | Reticulum configuration | | --data-dir | MESHCHAT_DATA_DIR | none | Portable root (storage + .reticulum subdirs when the two paths above are unset) | | --identity-file | MESHCHAT_IDENTITY_FILE | none | Load identity from file | | --rns-log-level | MESHCHAT_RNS_LOG_LEVEL | none | Reticulum log level | | (env only) | MESHCHAT_RNS_LOG_DEST | logging | stdout keeps RNS on the console. With a log dir, default is the rotating Python logger | | --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. ### Portable installs (removable media, Tails, USB sticks) MeshChatX already supports relocating all persistent state off the home directory. Use either explicit paths or a single data root: ```bash export PERSIST="/media/amnesia/Persistent/meshchatx" mkdir -p "$PERSIST" meshchatx --headless \ --data-dir="$PERSIST" ``` That creates and uses $PERSIST/storage for MeshChatX (identities, SQLite, plugins) and $PERSIST/.reticulum for Reticulum interfaces and transport config. You can set the same layout with environment variables: ```bash export MESHCHAT_DATA_DIR="$PERSIST" meshchatx --headless ``` Equivalent explicit form (overrides any --data-dir subpaths when you set these yourself): ```bash meshchatx --headless \ --storage-dir="$PERSIST/storage" \ --reticulum-config-dir="$PERSIST/.reticulum" ``` The Electron desktop app (AppImage, portable exe, macOS bundle) honors the same --data-dir / --storage-dir / --reticulum-config-dir flags (or the matching MESHCHAT_DATA_DIR / MESHCHAT_STORAGE_DIR / MESHCHAT_RETICULUM_CONFIG_DIR environment variables) on every platform, not just Windows: ```bash export PERSIST="/media/amnesia/Persistent/meshchatx" ./MeshChatX-x86_64.AppImage --data-dir="$PERSIST" ``` On Windows portable exe builds, storage and Reticulum config also default next to the .exe when PORTABLE_EXECUTABLE_DIR is set (used by the portable target automatically), without needing any flags. ## Reticulum manual bundle The Reticulum HTML manual is fetched from the upstream website **master** branch at build time by default (clearnet ZIP). There is no in-app clearnet refresh. After cloning the repository, or before packaging a release, run: ```bash pnpm run build-docs ``` CI release builds use the clearnet path. Without a bundled copy the Reticulum tab may show an upload prompt until you build docs or upload a manual ZIP offline. ## Advanced: Optional RNS-only installation (pip-rns) MeshChatX includes optional tooling to pull rns, lxmf, lxst, and the Reticulum manual from markqvist's rngit remotes over the mesh instead of clearnet. **Note:** Installing Python packages over RNS is slower than PyPI and fits mesh-only hosts with restricted clearnet. PyPI remains the default path for CI and standard development. | Remote | Purpose | | ------------------------------------------------------------ | --------------------- | | rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum | RNS package | | rns://7649a50d84610232d1416b41d2896aff/reticulum/lxmf | LXMF package | | rns://7649a50d84610232d1416b41d2896aff/reticulum/lxst | LXST package | | rns://7649a50d84610232d1416b41d2896aff/reticulum/website | Manual / website HTML | This uses [pip-rns](https://github.com/Quad4-Software/pip-rns) for the Python packages and git + git-remote-rns for the docs tree. Default aliases live in scripts/pip-rns/aliases. **Bootstrap note:** pip-rns needs a working Reticulum stack to reach the remotes. Install rns once from PyPI, a wheel, or an existing environment, then use the mesh path for updates. ```bash # Optional: Install/update rns, lxmf, lxst into the uv environment over RNS task deps:backend:rns # Optional: Bundle the Reticulum manual from the rngit website remote task docs:rns ``` Equivalent direct commands: ```bash bash scripts/pip-rns-deps.sh python scripts/build/fetch_reticulum_manual.py --force --via-rns ``` Set PIP_RNS_CONFIG to point at another aliases directory if needed. MESHCHATX_RETICULUM_DOCS_URL=rns://... also works for a custom website remote. ## 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, including **Linux sandboxing** (Firejail and Bubblewrap). Offline packaging, Android APK builds, and Dockerfile.build are in **Building from source and packaging**. Contributor task targets and locales are in **Development**. ## 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 | | HTTPInterface | HTTP/S tunnel (bundled RNS-over-HTTP) | | Custom external types | Advanced setups | Community-curated suggestions come from bundled community_interfaces.json, built from [meshchatx.com/api/mcx-interfaces](https://meshchatx.com/api/mcx-interfaces) at release time. Browse listings at [meshchatx.com/interfaces](https://meshchatx.com/interfaces). An optional public/community_interfaces.json override can replace that list locally. The app does not fetch the directory over the network at runtime. ## 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. ## HTTP tunnel interface MeshChatX vendors [RNS-over-HTTP](https://github.com/Quad4-Software/RNS-over-HTTP) and installs HTTPInterface.py into your Reticulum interfacepath on startup. Use **Add interface -> HTTP Tunnel** for client or server mode when only HTTP/S egress is available. Default transport is HTTP/1.1. HTTP/2 and HTTP/3 need TLS and optional extra packages on the server side. ## 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. ## I2P I2P uses the local router's SAM API (usually 127.0.0.1:7656). Enable SAM in the router. Do not run Java I2P and i2pd at the same time. MeshChatX allows one I2P interface, last in the list, with Transport Mode on. New interfaces default connectable off. Turn it on only if this node should accept inbound I2P peers. That makes the node an I2P transport. At least one b32.i2p peer is required. ## 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 ## Linux sandboxing This page shows how to run **meshchatx** under **Firejail** or **Bubblewrap** (bwrap) on Linux. The legacy CLI name **meshchat** installs the same entry point and can be substituted in these examples. Use this when you install MeshChatX natively (wheel, package, or Poetry) and want an extra layer of filesystem and process isolation compared to running the binary directly. These tools do **not** replace a full virtual machine or hardware-enforced boundary. They reduce exposure of your home directory and other paths the process can write to, when you configure them with tight whitelists or bind mounts. MeshChatX also applies optional **in-process** Linux sandboxes when available: - **Landlock** for filesystem path rules (MESHCHAT_LANDLOCK=0 to disable) - **Seccomp-BPF** syscall denylist via libseccomp (MESHCHAT_SECCOMP=0 to disable) Those layers fall back cleanly when unsupported. Firejail and Bubblewrap remain useful as an outer wrapper. **Landlock and user-local tools:** When Landlock is active, MeshChatX whitelists common pipx paths (~/.local/bin, ~/.local/share/pipx) and Argos Translate data under ~/.local/share/argos-translate so local translation and similar CLIs keep working. Tools installed elsewhere (for example only under ~/.nvm) or symlink shims that point outside those trees may still fail with permission errors. Disable Landlock temporarily with MESHCHAT_LANDLOCK=0 while debugging PATH-only failures. **Landlock and USB serial:** Landlock read roots include /sys so pyserial can read USB product strings for RNode listing. /dev is already a write root (including IOCTL_DEV on ABI 5+) so opening /dev/ttyACM* still works. Seccomp does not block serial ioctl. The user still needs dialout (or equivalent) group membership, and Ubuntu/Kubuntu brltty can steal CDC ACM devices before MeshChatX sees them. **Landlock and custom code:** Interface modules under the Reticulum interfaces/ directory, Mesh Server executable pages under identity storage, and installed MeshChatX plugins under storage/plugins stay readable and writable. location_cmd, PipeInterface commands, and Sideband plugin folders must already exist on an allowed root at process start (/usr, ~/.local/bin, storage, or the configured Sideband path). Restart after pointing Sideband at a new directory. **Containers:** If you already run MeshChatX with Docker or Podman, that is a different isolation model, this document is aimed at **host-installed** meshchatx (or meshchat). ## Prerequisites Install one or both from your distribution: - **Firejail:** package name is usually firejail. - **Bubblewrap:** package name is usually bubblewrap, the binary is bwrap. You need a working **meshchatx** on your PATH (for example after pipx install, pip install --user, or a distro package). The **meshchat** command is the same binary if both entry points are installed. Pick a **dedicated data directory** for sandboxed runs so you do not mix permissions or policies with a non-sandboxed install. The examples below use: ```bash DATA="${XDG_DATA_HOME:-$HOME/.local/share}/meshchatx-sandbox" mkdir -p "$DATA/storage" "$DATA/.reticulum" ``` Adjust paths if you prefer another location. ## Firejail Firejail applies a profile (or defaults) on top of your command. For MeshChatX you typically want: - **Network** left available so Reticulum and the web UI can work (do not use --net=none unless you know you need it). - **Writable** only your chosen data directory (and any other paths the app needs). ### Installed meshchatx (pip, pipx, or system package) ```bash DATA="${XDG_DATA_HOME:-$HOME/.local/share}/meshchatx-sandbox" mkdir -p "$DATA/storage" "$DATA/.reticulum" firejail --quiet \ --whitelist="$DATA" \ meshchatx --headless --host 127.0.0.1 \ --storage-dir="$DATA/storage" \ --reticulum-config-dir="$DATA/.reticulum" ``` If the default profile blocks something MeshChatX needs, you can start from a looser base and tighten later, for example: ```bash firejail --noprofile --whitelist="$DATA" \ meshchatx --headless --host 127.0.0.1 \ --storage-dir="$DATA/storage" \ --reticulum-config-dir="$DATA/.reticulum" ``` --noprofile disables many Firejail restrictions. Treat it as a stepping stone, not the final hardening. ### From source with UV Poetry needs the project tree and the virtualenv. Example: ```bash cd /path/to/reticulum-meshchatX DATA="${XDG_DATA_HOME:-$HOME/.local/share}/meshchatx-sandbox" VENV="$(pwd)/.venv" mkdir -p "$DATA/storage" "$DATA/.reticulum" firejail --quiet \ --whitelist="$(pwd)" \ --whitelist="$VENV" \ --whitelist="$DATA" \ uv run python -m meshchatx.meshchat --headless --host 127.0.0.1 \ --storage-dir="$DATA/storage" \ --reticulum-config-dir="$DATA/.reticulum" ``` You may need extra --whitelist= entries if UV or dependencies read config elsewhere (for example under $HOME/.config). ### USB serial (RNode or similar) If Reticulum talks to a radio over a serial device, Firejail may need explicit access to TTY devices, for example: ```bash firejail --noblacklist=/dev/ttyACM0 --noblacklist=/dev/ttyUSB0 \ ... ``` Use the device nodes your system actually exposes (dmesg, ls /dev/tty*). ## Bubblewrap (bwrap) Bubblewrap does not ship profiles, so you must list every mount and option. The pattern below keeps the **whole root filesystem read-only**, mounts a **writable tmpfs** on /tmp, and makes **only** your data directory writable at its normal path. **Network namespaces are not changed**, so Reticulum and TCP/UDP behave like an unsandboxed process unless you add --unshare-net (which usually breaks mesh networking). ### Installed meshchatx ```bash DATA="${XDG_DATA_HOME:-$HOME/.local/share}/meshchatx-sandbox" mkdir -p "$DATA/storage" "$DATA/.reticulum" exec bwrap \ --die-with-parent \ --new-session \ --proc /proc \ --dev /dev \ --ro-bind / / \ --tmpfs /tmp \ --bind "$DATA" "$DATA" \ --uid "$(id -u)" --gid "$(id -g)" \ meshchatx --headless --host 127.0.0.1 \ --storage-dir="$DATA/storage" \ --reticulum-config-dir="$DATA/.reticulum" ``` Notes: - If meshchatx lives only inside a venv that is **not** under $DATA, the read-only root still allows **reading** that path, you do not have to bind-mount the venv separately unless you also need writes there. - Distributions that merge / and /usr (merged-usr) still work with --ro-bind / / on typical glibc setups. If bwrap fails with missing library paths, add the extra --ro-bind lines your distro documents (for example /lib64). ### From source with UV Bind the repository and the UV venv read-only, and keep DATA writable: ```bash cd /path/to/reticulum-meshchatX DATA="${XDG_DATA_HOME:-$HOME/.local/share}/meshchatx-sandbox" VENV="$(pwd)/.venv" mkdir -p "$DATA/storage" "$DATA/.reticulum" PROJ="$(pwd)" exec bwrap \ --die-with-parent \ --new-session \ --proc /proc \ --dev /dev \ --ro-bind / / \ --tmpfs /tmp \ --bind "$DATA" "$DATA" \ --ro-bind "$PROJ" "$PROJ" \ --ro-bind "$VENV" "$VENV" \ --uid "$(id -u)" --gid "$(id -g)" \ --setenv PATH "$VENV/bin:$PATH" \ --chdir "$PROJ" \ uv run python -m meshchatx.meshchat --headless --host 127.0.0.1 \ --storage-dir="$DATA/storage" \ --reticulum-config-dir="$DATA/.reticulum" ``` uv itself must be reachable on PATH inside the sandbox (often under /usr or $HOME/.local/bin, both visible with --ro-bind / /). If uv run fails because it cannot read ~/.cache/uv, add a read-only bind for that directory or invoke the venv interpreter directly instead of uv run: ```bash exec bwrap \ ... same mounts as above ... \ --setenv PATH "$VENV/bin:$PATH" \ --chdir "$PROJ" \ meshchatx --headless --host 127.0.0.1 \ --storage-dir="$DATA/storage" \ --reticulum-config-dir="$DATA/.reticulum" ``` (Use the meshchatx script, the legacy meshchat alias, or python -m entry point from $VENV/bin if your install exposes it there.) ### USB serial under Bubblewrap You may need a clearer view of devices than the minimal --dev /dev provides. Options include --dev-bind /dev /dev (broader device exposure) or binding only the specific character device. Balance convenience against attack surface. ## 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. The Messages sidebar icon shows a red count when you have unread LXMF conversations. Opening a thread marks it read. If a new message arrives while that thread is already open, it is marked read without needing to switch away and back. 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 propagation peer via AutoPropagationManager from local lxmf.propagation announces and RNS paths (small sync probe, remembered destination hashes, no central directory) - **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. - While a large message is downloading, the header shows **Cancel incoming**. That stops active LXMF delivery resource transfers (cancel_all_inbound / per-resource cancel). Outbound send cancel stays on each message menu. ## See also - **Reticulum interfaces** for connectivity - **Identities, privacy, and security** for auth and HTTPS - Reticulum manual section on LXMF for protocol detail ## 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. **Private tabs** (incognito icon, purple strip) browse without writing archives, without favourites or Identify, and without reusing the shared Nomad link cache. Private tabs are not restored from localStorage. The destination hash stays out of the URL bar and browser history (same idea as SearXNG keeping queries off GET URLs). They use the same Reticulum process as the active identity. They do not create a separate IdentityContext. ## 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. ### Executable (dynamic) pages You can opt in per node to **executable pages**. When enabled: - Non-executable pages are served as static files. - Pages marked executable in the Mesh Server editor run as scripts. On Linux and macOS, chmod +x on the page file also marks it. - The first line must be a shebang such as #!/usr/bin/env python3. Windows does not exec scripts by shebang, so Mesh Server resolves that interpreter on PATH (python, py, node, and similar). - Request field_* and var_* values are passed as environment variables. - link_id and remote_identity are supplied when available. - Script stdout is returned as the page body. Failures return a controlled error page. Editing a page in Mesh Server always shows the file source, never the script output. 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) ## 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