Skip to main content

LXMFy

Python framework for LXMF bots on the Reticulum Network.

Docs

Features

Category Capabilities
Core Interactive CLI, command prefixes, cron-style task scheduler, middleware and event systems
Connectivity Direct delivery with propagation fallback, auto-peering, RNS links, opportunistic sending, RRC hub client
Security Spam protection, role-based permissions, identity pinning, message signature policy, Landlock LSM filesystem sandbox on Linux
NLP Local offline intent classification, type-hinted argument parsing
Extensions Python cogs, external script cogs (Bash, Go, C, and others), subprocess sandboxing via Landlock, bwrap, or firejail
Storage JSON, SQLite, and in-memory backends, crash-safe outgoing message persistence
Reliability Stability and stress tests, chaos engineering hooks, resource leak checks
UX Help on first message, auto-generated help menus, customizable bot icons, attachments

Installation

Requirements: Python 3.11+, RNS 1.4.2+, LXMF 1.1.1+, cbor2 5.4.0+ (pulled in with LXMFy).

From PyPI

# pip
pip install lxmfy

# pipx
pipx install lxmfy

From source

git clone https://git.quad4.io/LXMFy/LXMFy.git
cd LXMFy
poetry install

Usage

lxmfy

Create a bot project:

lxmfy create

Debug send and receive:

lxmfy debug
lxmfy debug --config ./config --output ./lxmfy-debug-report.txt
lxmfy debug probe <destination_hash> --request-path --wait 30
lxmfy debug send <destination_hash>
lxmfy debug receive
lxmfy debug compare <hash_a> <hash_b>

Doctor mode prints a verdict and next steps, then categorized checks (OS, shared vs owned instance, disk permissions, interfaces, announce, send pipeline / storage history, receive readiness). It saves a privacy-redacted lxmfy-debug-*.txt you can share. Colors turn off when stdout is not a TTY, when NO_COLOR is set, or when Windows VT is unavailable. Use --no-color or NO_COLOR=1 for plain output.

Docker

Build and run

From the project root:

docker build -t lxmfy-test .
docker run -d \
    --name lxmfy-test-bot \
    -v $(pwd)/config:/bot/config \
    -v $(pwd)/.reticulum:/root/.reticulum \
    --restart unless-stopped \
    lxmfy-test

Host networking (AutoInterface):

docker run -d \
    --name lxmfy-test-bot \
    --network host \
    -v $(pwd)/config:/bot/config \
    -v $(pwd)/.reticulum:/root/.reticulum \
    --restart unless-stopped \
    lxmfy-test

Build a wheel

docker build -f docker/Dockerfile.Build -t lxmfy-wheel-builder .
docker run --rm -v "$(pwd)/dist_output:/output" lxmfy-wheel-builder

That copies the built wheel into ./dist_output.

Example

from lxmfy import LXMFBot, load_cogs_from_directory

bot = LXMFBot(
    name="LXMFy Test Bot", # Name of the bot that appears on the network.
    announce=5400, # Announce every hour, set to 0 to disable.
    announce_enabled=True, # Set to False to disable all announces (both initial and periodic)
    announce_immediately=True, # Set to False to disable initial announce
    admins=["your_lxmf_hash_here"], # List of admin hashes.
    hot_reloading=True, # Enable hot reloading.
    command_prefix="/", # Set to None to process all messages as commands.
    cogs_dir="cogs", # Specify cogs directory name.
    rate_limit=5, # 5 messages per minute
    cooldown=5, # 5 seconds cooldown
    max_warnings=3, # 3 warnings before ban
    warning_timeout=300, # Warnings reset after 5 minutes
    signature_verification_enabled=True, # Enable cryptographic signature verification
    require_message_signatures=False, # Allow unsigned messages but log them
    propagation_fallback_enabled=True, # Enable propagation fallback after direct delivery fails
    propagation_node="your_propagation_node_hash_here", # Manual propagation node (optional)
    autopeer_propagation=True, # Auto-discover propagation nodes (optional)
    autopeer_maxdepth=4, # Max hops for auto-peering (default: 4)
    enable_propagation_node=False, # Run as propagation node (default: False)
    message_storage_limit_mb=500, # Storage limit in MB for propagation node (default: 500)
    direct_delivery_retries=3, # Number of direct delivery attempts before falling back to propagation
    landlock_enabled=True, # Linux Landlock LSM sandbox for the bot process (default)
    external_cogs_sandbox_enabled=True, # Sandbox external script cogs on Linux
    external_cogs_sandbox_type="auto", # auto, landlock, bwrap, firejail, or none
)

load_cogs_from_directory(bot)

@bot.command(name="ping", description="Test if bot is responsive")
def ping(ctx):
    ctx.reply("Pong!")

@bot.command(name="echo", description="Echo a message", admin_only=True)
def echo(ctx, message: str):
    ctx.reply(message)

bot.run()

RRC (Reticulum Relay Chat)

Bots can join RRC hubs as ordinary clients over RNS Links with CBOR envelopes:

from lxmfy import LXMFBot, RRCMessage

bot = LXMFBot(
    name="RoomBot",
    rrc_enabled=True,
    rrc_hubs=["your_rrc_hub_destination_hash"],
    rrc_rooms=["lobby"],
    rrc_nick="RoomBot",
)

@bot.on_rrc
def on_rrc(event, client, payload):
    if event == "msg" and isinstance(payload, RRCMessage) and payload.mention:
        client.send_message(payload.room, f"Heard you, {payload.nick}")

bot.run()

Or connect at runtime with bot.connect_rrc(hub_hash, rooms=["lobby"]).

Hub sessions persist across restarts by default (rrc_persist_sessions=True). Outgoing LXMF messages are also persisted by default (message_persistence_enabled=True) so a crash mid-queue does not drop them. The outbound queue is bounded (message_queue_size, default 50) and drops the oldest message when full.

Propagation Node Configuration

LXMFy supports three modes for propagation node usage.

Manual configuration

Set a specific propagation node by hash:

bot = LXMFBot(
    name="MyBot",
    propagation_fallback_enabled=True,
    propagation_node="your_propagation_node_hash_here",
    direct_delivery_retries=3,
)

Automatic discovery (auto-peering)

Discover propagation nodes from network announces:

bot = LXMFBot(
    name="MyBot",
    propagation_fallback_enabled=True,
    autopeer_propagation=True,
    autopeer_maxdepth=4,
)

The bot peers with suitable nodes within autopeer_maxdepth hops.

Run as a propagation node

Store and forward messages for offline recipients:

bot = LXMFBot(
    name="MyPropagationBot",
    enable_propagation_node=True,
    message_storage_limit_mb=500,
)

message_storage_limit_mb caps disk use. Set to 0 for unlimited storage (not recommended).

Querying propagation status

status = bot.get_propagation_node_status()
print(f"Current outbound node: {status['current_outbound_node']}")
print(f"Discovered peers: {status['discovered_peers']}")

Setting the propagation node at runtime

bot.set_propagation_node("new_propagation_node_hash")

Storage limits

stats = bot.get_propagation_storage_stats()
print(f"Storage used: {stats['storage_size_mb']:.2f} MB")
print(f"Storage limit: {stats['storage_limit_mb']} MB")
print(f"Utilization: {stats['utilization_percent']:.1f}%")
print(f"Messages stored: {stats['message_count']}")

bot.set_message_storage_limit(megabytes=1000)

Propagation notes

  • Without manual config, auto-peering, or running as a node, messages that need propagation fail
  • You can combine modes (manual node plus auto-peering as backup)
  • A propagation-node bot still sends and receives normally
  • Auto-peering respects autopeer_maxdepth so distant nodes are skipped

Security and Sandboxing

On Linux kernels with Landlock support (5.13+), LXMFy can restrict filesystem access for the bot process and for external script cogs.

Bot process sandbox

When landlock_enabled=True (default), the bot applies a Landlock LSM sandbox after startup. System paths are read-only. Bot storage, config, cogs, Reticulum config, and temp directories stay writable.

bot = LXMFBot(
    name="SecureBot",
    landlock_enabled=True,
)

status = bot.get_landlock_status()
print(status)

Environment overrides:

  • LXMFY_LANDLOCK=0: disable Landlock
  • LXMFY_LANDLOCK=1: force an attempt on Linux
  • unset: follow landlock_enabled and kernel auto-detection

External script cog sandbox

Executable cogs in cogs/ can run in a restricted environment when external_cogs_sandbox_enabled=True (default). Set external_cogs_sandbox_type to:

  • auto (default): prefer Landlock, then bwrap, then firejail
  • landlock: Landlock-only via preexec_fn
  • bwrap: bubblewrap read-only bind sandbox
  • firejail: firejail private profile with no network
  • none: no subprocess sandbox

See the docs for full configuration details.

Development

poetry install
poetry run lxmfy run echo

Common Makefile targets:

make lint       # ruff check
make typecheck  # pyright lxmfy
make test       # pytest
make ci         # lint, typecheck, security check, test, build

Contributing

Send ideas and issues to LXMF: 7cc8d66b4f6a0e0e49d34af7f6077b5a

License

0BSD

Metadata

Release files for lxmfy 2.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lxmfy 2.0.3
File Size Uploaded
lxmfy-2.0.3.tar.gz 95.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lxmfy 2.0.3
File Interpreter ABI Platform
lxmfy-2.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 201.6 kB

Release files / lxmfy-2.0.3.tar.gz

Download URL lxmfy-2.0.3.tar.gz
Size 95.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f8029d60e518c5d3609990e6d3270ad9d0ac73400e09097a9bc588f66718ce5b
BLAKE2b-256 checksum
How to use checksums
d96c5ce1f9c70d2c0f9446513fe9fceafd15d22ea41aa10acf5b48f8b7478c44
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / lxmfy-2.0.3-py3-none-any.whl

Download URL lxmfy-2.0.3-py3-none-any.whl
Size 106.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
95ffcab1d4b277222bb90c4695c0d905ed945d3a05f98748aa5abdd14dd9be36
BLAKE2b-256 checksum
How to use checksums
618e35dcbf5da449a3f5e513418495b5b721a9468ccec79751aa03c24d4e5900
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

2.1.1

2 release files

This release

2.0.3 This release

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.6.5

2 release files

1.6.4

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.8.0

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.9

2 release files

0.6.8

1 release file

0.6.7

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.5.1

2 release files

0.4.9

2 release files

0.4.8

2 release files

0.4.7

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page