Leviculum is an independent reimplementation based on the Reticulum protocol specification. It is wire-compatible with Reticulum and runs on Linux, macOS, and embedded devices.
Reticulum is a networking stack for building resilient, encrypted mesh networks over any transport medium. It works over LoRa radios, TCP, UDP, serial links, or anything that can carry bytes. Every node gets a cryptographic identity. Every connection is end-to-end encrypted. No servers, no accounts, no infrastructure required.
Leviculum provides the same functionality as Python Reticulum but compiled to native code. The lnsd daemon is a drop-in replacement for rnsd, lncp replaces rncp, lnstatus replaces rnstatus, lnprobe replaces rnprobe, and lnpath covers rnpath's path query, wait and drop. Python CLI tools like rnstatus, rnpath, and rnprobe also work against a running lnsd without modification.
The protocol core (leviculum-core) compiles as no_std with only alloc, so it runs on microcontrollers. The same code powers the Linux daemon, a future Android app, and embedded firmware.
Leviculum is in active development. The protocol implementation is functionally complete: routing, path discovery, link establishment, encrypted channels, file transfer, forward secrecy ratchets, and LoRa radio support all work and are tested against Python Reticulum on real hardware. (One caveat: sending files larger than one megabyte as multiple segments is not implemented yet — Codeberg #27; receiving multi-segment transfers works.) It is not yet production-ready.
# amd64
wget https://codeberg.org/Lew_Palm/leviculum/releases/download/nightly/leviculum-nightly-amd64.deb
sudo apt install ./leviculum-nightly-amd64.deb
# arm64
wget https://codeberg.org/Lew_Palm/leviculum/releases/download/nightly/leviculum-nightly-arm64.deb
sudo apt install ./leviculum-nightly-arm64.debSets up lnsd as a systemd service under a dedicated leviculum user, with its config directory at /etc/reticulum. The directory is mode 2775 (group-writable, setgid), so any user in the leviculum group shares it as a single source of truth — no per-user config or extra flags. To opt in:
sudo usermod -aG leviculum "$USER"
# log out and back in for the group to applyThat covers everything: the native Rust clients (lnstatus, lnstest, lncp, lnprobe, lnpath) talk to lnsd via the shared-instance socket, and Python Reticulum tools (rnstatus, rncp, rnpath, rnprobe, Sideband, Nomadnet, …) auto-detect /etc/reticulum (per RNS.Reticulum.__init__'s standard lookup) and connect through the same socket. If you ever swap lnsd out for the Python rnsd daemon, the same configdir keeps working — lnsd and rnsd are wire- and config-compatible.
The binaries are statically linked against musl, so the package installs on Debian ≥ 9 and Ubuntu ≥ 16.04 (amd64 + arm64) regardless of host glibc. SHA-256 checksums are published alongside every .deb with the suffix .sha256. The exact build is embedded in the binaries — lnsd --version prints e.g. 0.7.1-nightly.20260419-5a5df20.
Two programs ship as their own nightly packages, so neither drags in the auto-started lnsd service that leviculum sets up. Both are versioned independently of the stack — their version numbers track the programs, not the protocol core.
# lnomad — Nomadnet terminal browser
sudo apt install ./lnomad-nightly-amd64.deb
# lblogd — dev-blog server (NomadNet page node + clearnet web)
sudo apt install ./lblogd-nightly-amd64.debDownload them from the same nightly release, as .deb or as a userspace .tar.gz, for amd64 and arm64. lnomad is a plain terminal program. lblogd installs and starts a systemd service that serves on http://127.0.0.1:8180/ until you point it at a domain in /etc/lblogd/config.toml. Both need a running Reticulum instance for their mesh side — lnsd from the package above, or the Python rnsd.
Tested on current Debian (trixie). Install the toolchain once. Rust comes from rustup because the Debian cargo is usually too old; everything else is apt packages. No file outside these packages and the cloned repo is needed.
# Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
. "$HOME/.cargo/env"
# Build, test, and flash tooling:
sudo apt install just docker.io docker-compose build-essential pkg-config \
python3 python3-venv python3-pip python3-serial python-is-python3 esptool
# Let the interop and integration tests run containers without sudo:
sudo usermod -aG docker "$USER" # then log out and back inThen clone and build:
git clone https://codeberg.org/Lew_Palm/leviculum.git
cd leviculum
git submodule update --init reference/Reticulum
cargo build --release --bin lnsd --bin lnstatus --bin lncp --bin lnstest --bin lnprobe --bin lnpath
./target/x86_64-unknown-linux-musl/release/lnsd --versionThe workspace pins x86_64-unknown-linux-musl as its build target (see
.cargo/config.toml for why), so the binaries land under
target/x86_64-unknown-linux-musl/release/, not target/release/. No
system C libraries are linked into the daemon.
On a non-x86_64 host — a Raspberry Pi or any other arm64 board — that
pin is the wrong architecture, and cargo has no way to make it follow the
host. The build succeeds and the binaries then refuse to run. Name your
own target instead, once per shell or in ~/.cargo/env:
rustup target add aarch64-unknown-linux-musl
export CARGO_BUILD_TARGET=aarch64-unknown-linux-musl
cargo build --release --bin lnsd --bin lnstatus --bin lncp --bin lnstest --bin lnprobe --bin lnpathThe binaries then land under target/aarch64-unknown-linux-musl/release/.
Building without it prints a warning naming this override. The .deb
packages above are already built for arm64 and need none of this.
Run the test tiers:
cargo test-core # unit tests
cargo test-interop # against Python Reticulum (needs Docker and the submodule)See the installation guide for all cargo aliases and test levels.
The embedded LNode firmware ships prebuilt — you do not need this checkout
to put it on a board. The nightly release carries lnflash, a self-contained
bundle holding the flasher, one firmware image per supported board, and
Nordic's S140 SoftDevice with its licence for the board that can need it:
wget https://codeberg.org/Lew_Palm/leviculum/releases/download/nightly/lnflash-nightly-amd64.tar.gz
tar xzf lnflash-nightly-amd64.tar.gz
cd lnflash-*
sudo ./lnflash # add --dry-run first to see what it would doNothing is downloaded and nothing is installed; everything it writes to the board is in that directory. The flasher binary is amd64, the images inside it are not architecture-specific. The boards it carries an image for:
| board | hardware | built from source by |
|---|---|---|
t114 |
Heltec Mesh Node T114 | just flash |
rak4631 |
RAK4631 — the WisMesh Pocket V2 and the other carriers built around that module | just flash-rak4631 |
lnflash and not a bare UF2 you drag onto the bootloader drive, because the
images have a precondition that cannot be checked by dragging: our firmware
places its application above an S140 7.x SoftDevice, and a factory T114 ships
S140 6.1.1, which puts the boundary one page lower. Writing the image onto
such a board produces a device that never reaches USB — no serial ports, no
drive, nothing on the bus. It is recoverable with a double-tap of RESET, and
it looks exactly like dead hardware while it lasts. lnflash reads the
installed version off the board before it writes anything: on a T114 it
installs the SoftDevice it ships beside the image, and on a RAK — for which it
deliberately carries no SoftDevice, because every RAK we have met already runs
a usable one — it stops and tells you what it found rather than flashing into
a brick. The mechanism, and the board that spent weeks written off
as bricked, are in
docs/src/concepts/lnode-flashing.md.
To build the firmware from this checkout instead, install the embedded
toolchain once — the target itself, flip-link (the firmware's linker), and
llvm-tools (provides the llvm-objcopy the UF2 flasher uses) — then flash
attached devices over USB with the recipes in the table above:
rustup target add thumbv7em-none-eabihf
rustup component add llvm-tools
cargo install --locked flip-link
just flash # every attached T114
just flash-rak4631 # every attached RAK4631For the RNode radios (LilyGO T-Beam, Heltec, ESP32 family), extract Mark Qvist's signed firmware off a known-good RNode once, then flash. The ESP32 cannot be bricked, a failed flash is always recoverable by re-running flash-rnode:
just flash-rnode-setup # once per machine: a pinned esptool
just flash-rnode-extract /dev/ttyACM6 # once, from a trusted RNode
just flash-rnode /dev/ttyACM6The chip and the flash offsets follow the board. The optional last argument
names it (tbeam, heltec-v4, or a chip name); left out, the chip is read
off the device. To back up or restore a whole board, image and all:
just flash-rnode-read-image /dev/ttyACM6 board.bin
just flash-rnode-write-image /dev/ttyACM6 board.binOnly for producing the static musl .deb artifacts via just build-deb:
rustup target add x86_64-unknown-linux-musl aarch64-unknown-linux-musl
cargo install cargo-zigbuild cargo-deb
pip install --user ziglangReport bugs and ask questions at
https://codeberg.org/Lew_Palm/leviculum/issues. When the daemon is
involved, attach the output of lnstest diag (see the
quickstart for the exact command). For
patches, see CONTRIBUTING.md.
A suspected security vulnerability is the one thing that does not belong in the tracker: an issue is public from the moment it is filed, which hands the flaw to everyone who might use it. SECURITY.md names the private contact, the acknowledgement window, and what is in scope.
AGPL-3.0-or-later. See LICENSE for the full text.