Skip to content
afit21Public

About

RNFS - A distributed file system for Reticulum based on InterPlanetary File System (IPFS)

Topics

Resources

Contributing

Stars

23 stars

Watchers

1 watching

Forks

Repository files navigation

🌍 RNFS - Reticulum Network File System

A decentralized file system built for Reticulum & based on IPFS (InterPlanetary File System).

Share files over LoRa radio, Wi-Fi, local networks or the internet without relying on a an authoritative server for file hosting or peer lookups.

Access any file on RNFS by it's 'CID'.

Example: Moby Dick book .txt file: bagaibqabciqfweclrlsfdkqgkhtmbavijftqfkiogrprcntnew5jalakjsykq4q

License: MIT Python 3.11+ Reticulum


Publish files that anyone can fetch over Reticulum by it's hash

No centralized servers

rnfs demo

Contents

Install · Sample files (with CIDs) · Getting started · How rnfs adapts IPFS · Settings · Commands · Building apps on rnfs · Keeping content off your node · Example use cases · Running several nodes · Known limitations · specification.md (the protocol) · design.md (the code)

Why Use RNFS?

RNFS provides a number of benefits compared to other 'torrenting for RNS' or file hosting projects.

Key Features

  • 📃 Share File With A Single Line (CID) - To fetch a file, all someone needs it's a 'CID'. For example: Moby Dick book .txt file - bagaibqabciqfweclrlsfdkqgkhtmbavijftqfkiogrprcntnew5jalakjsykq4q
  • 🔖 Names (links that can change) - A CID is permanent, but a name can point to a new version. Others can follow a name to keep the latest version pinned, and keepers keep it resolvable while you're offline.
  • ⚡ Multi-source, resumable downloads - Pulls pieces from several seeders at once, preferring nearby, fast and long-standing nodes, and picks up where it left off after an interruption.
  • 🌐 Global Namespace - CIDs are deterministic hashes of the actual files. No tracker is required like in traditional torrents
  • 🗺️ Distributed Hash Table (Kademlia) - The Kademlia algorithm is used to build a distributed dictionary of who has which files. This means you only need to know the CID of a file to download it. Because the global 'phone book' is decentralized, there is zero reliance on trackers, or for the torrent file to be up to date as long as at least one other node has pinned (is seeding) the file.
  • 📃 Nodes automatically split up lookup data between themselves - Lookup 'Kademlia' for more in depth information, however all nodes on the RNFS network will take responsibility for remembering a part of the overall 'address book' of nodes and a list of which files they host. Assignment of the information is automatic.
  • 🌱 Automatically find seeders - No file of known seeders is needed, RNFS will automatically ask it's peers "who has this file". Kademlia is very efficient at doing this. In a 1 million node network, this would only take 6 hops to find a seeder.

Ways To Use RNFS

  • GUI Application (Planned) - User friendly interface for rnfs coming soon
  • Standard Install (Command line + HTML gateway) - See the Quick Start or Getting started sections to install this project and get started with RNFS.
  • Host a NomadNet Gateway - See RNFS NomadNet Gateway Project
  • Project Gutenberg NomadNet Mirror - Visit 78d812594409fc12489f3d392974bec9:/page/index.mu on NomadNet. This is essentially a site that indexes a mirror of Project Gutenberg (libary of over 70k public domain books). It serves files through a gateway rather than RNFS but also provides CIDs.
  • Public RNFS NomadNet Gateway - Access file on RNFS via the nomadnet gateway at 14c5fc21e231343073ff9e64a26d6f5d:/page/index.mu

Quick start

This section covers the basics to get you using rnfs.

1 - Install

Warning

If you don't already have Reticulum running on your machine, you'll need to configure some interfaces to connect to the backbone: see Connecting to the network.

Different package options available at latest release

Windows:

rnfs-0.2.0-windows-x64.exe, saved as rnfs.exe somewhere on your PATH (how).

Linux:

One liner for install script:

curl -fsSL https://raw.githubusercontent.com/afit21/rnfs/main/install.sh | sh

To install as a systemctl service, add the service flag:

curl -fsSL https://raw.githubusercontent.com/afit21/rnfs/main/install.sh | sh -s -- --service

Or installers for (Debian & Ubuntu). Flatpak

macOS:

pipx install https://github.com/afit21/rnfs/releases/download/v0.2.0/rnfs-0.2.0-py3-none-any.whl (details).

2 - Start rnfs and leave it running in its own terminal:

rnfs daemon

3 - Download a file

for example one of the sample files:

rnfs get bagaibqabciqkq6rth6jhr2vkh7c2ip7p6bsmdxmslbia47rjbmwpcz6wwu6veya -o manifesto.pdf

Or open it in your browser: http://127.0.0.1:8789/rnfs/bagaibqabciqkq6rth6jhr2vkh7c2ip7p6bsmdxmslbia47rjbmwpcz6wwu6veya?filename=manifesto.pdf

4 - Share your own file:

rnfs add holiday.jpg        # prints its CID: give that to anyone who should have the file

That's all you need for the basics. Getting started goes a step further (folders, links that can change, sharing in the background), and Commands lists everything.

Install

rnfs runs on Linux, macOS and Windows. Every package includes Reticulum and gives you one rnfs command. Your data in ~/.rnfs is never touched by an update or uninstall.

System Download
Windows (64-bit) rnfs-0.2.0-windows-x64.exe (how)
Debian 12+, Ubuntu 24.04+, Raspberry Pi OS (64-bit) rnfs_0.2.0_amd64.deb or rnfs_0.2.0_arm64.deb (how)
Other Linux (x86_64) rnfs-0.2.0-x86_64.flatpak (how), or the install script
macOS, or any system with Python 3.11+ rnfs-0.2.0-py3-none-any.whl (how)

These links are for 0.2.0; for a newer version, take the same files from the latest release, which also has the source archive (rnfs-0.2.0.tar.gz) and SHA256SUMS (checking a download).

Windows

The exe is a single file with Python and Reticulum inside. In PowerShell, from your download folder:

$bin = "$env:USERPROFILE\.local\bin"
New-Item -ItemType Directory -Force $bin | Out-Null
Move-Item .\rnfs-0.2.0-windows-x64.exe "$bin\rnfs.exe" -Force
Unblock-File "$bin\rnfs.exe"
[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";$bin", "User")
# open a new terminal, then:
rnfs daemon
  • The exe isn't code-signed: if SmartScreen warns you, choose "More info", then "Run anyway".
  • Update: stop the daemon and replace rnfs.exe. Uninstall: delete it.
  • Start at logon: download contrib/install-windows-task.ps1 and run powershell -ExecutionPolicy Bypass -File install-windows-task.ps1 (-Uninstall removes it). It logs to %USERPROFILE%\.rnfs\daemon.log.
  • Windows has no execute bit, so a directory with executable scripts gets a different CID than on Linux or macOS. Windows support is the newest, so please report problems.

Debian and Ubuntu

Pick amd64 or arm64 to match dpkg --print-architecture. Ubuntu 22.04's Python is 3.10, which is too old: there, run sudo apt install python3.11 python3.11-venv and then the install script, which finds python3.11.

curl -LO https://github.com/afit21/rnfs/releases/download/v0.2.0/rnfs_0.2.0_amd64.deb
sudo apt install ./rnfs_0.2.0_amd64.deb
systemctl --user enable --now rnfs      # run it as a service; logs: journalctl --user -u rnfs -f
loginctl enable-linger $USER            # keep it running while you're logged out
  • Update: install the newer .deb the same way, then systemctl --user restart rnfs.
  • Uninstall: systemctl --user disable --now rnfs && sudo apt remove rnfs.

Other Linux (Flatpak)

curl -LO https://github.com/afit21/rnfs/releases/download/v0.2.0/rnfs-0.2.0-x86_64.flatpak
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --user rnfs-0.2.0-x86_64.flatpak
alias rnfs='flatpak run io.github.afit21.rnfs'      # add to ~/.bashrc
rnfs daemon

The app can use the network, ~/.rnfs and ~/.reticulum, and your Downloads folder: not the rest of your home folder, nor any device. Grant more with flatpak override --user --device=all io.github.afit21.rnfs (RNode and other serial interfaces) or flatpak override --user --filesystem=<folder> io.github.afit21.rnfs (another folder, or ~/.config/reticulum if your Reticulum config is there); --reset takes them back. Until 0.1.6 the app had your whole home folder and every device: after updating, grant the ones you used again. Update by installing the newer .flatpak; uninstall with flatpak uninstall --user io.github.afit21.rnfs.

macOS

brew install pipx && pipx ensurepath          # then open a new terminal
pipx install https://github.com/afit21/rnfs/releases/download/v0.2.0/rnfs-0.2.0-py3-none-any.whl
rnfs daemon
  • Update: pipx install --force <the newer .whl URL>. Uninstall: pipx uninstall rnfs.
  • Start at login:
    curl -fsSL -o ~/Library/LaunchAgents/io.github.afit21.rnfs.plist \
      https://raw.githubusercontent.com/afit21/rnfs/main/contrib/io.github.afit21.rnfs.plist
    launchctl load -w ~/Library/LaunchAgents/io.github.afit21.rnfs.plist
  • pipx (and plain pip install) resolves rnfs's dependencies unpinned, at the newest Reticulum and cryptography rnfs allows, not at the tested versions and hashes in packaging/requirements.lock that the other packages and the install script use.

Install script

For any Linux with Python 3.11+ and venv (sudo apt install python3 python3-venv on Debian/Ubuntu). It installs the latest release into ~/.local/share/rnfs/venv, with the dependency versions and hashes pinned in the release, and checks the download against SHA256SUMS and, if the GitHub CLI is installed, its signed provenance for that release's tag. If gh is installed but not logged in, it stops: run gh auth login, or set RNFS_REQUIRE_ATTESTATION=0 to check the checksum only. Without gh it warns (RNFS_REQUIRE_ATTESTATION=1 makes it stop): curl | sh without gh trusts whoever can write the release's files, since SHA256SUMS comes from the same place.

curl -fsSL https://raw.githubusercontent.com/afit21/rnfs/main/install.sh | sh                   # install or update
curl -fsSL https://raw.githubusercontent.com/afit21/rnfs/main/install.sh | sh -s -- --service    # also run it as a systemd user service
curl -fsSL https://raw.githubusercontent.com/afit21/rnfs/main/install.sh | sh -s -- --uninstall  # remove it (keeps ~/.rnfs)
  • Running it again updates rnfs and restarts the service if you installed it.
  • --main installs the main branch instead (no checksum to check; give it on every update).
  • --system, as root, installs for all users in /opt/rnfs and /usr/local/bin/rnfs.
  • --python PATH picks the Python; --force replaces another rnfs command (e.g. pipx's); RNFS_SOURCE=<dir, archive, .whl or URL> installs something other than the latest release (nothing checks it; a plain http:// URL gets a warning).
  • With the service, loginctl enable-linger $USER keeps it running while you're logged out. More sandboxing (ProtectSystem, PrivateTmp) needs unprivileged user namespaces, so it is an optional drop-in, for this unit or the .deb's: see contrib/rnfs.service.d/hardening.conf.

Checking a download

Each release's SHA256SUMS lists every file's checksum. With it next to your download:

sha256sum -c SHA256SUMS --ignore-missing                  # Linux; macOS: shasum -a 256 <file>
gh attestation verify rnfs_0.2.0_amd64.deb --repo afit21/rnfs

A matching checksum shows the file is whole, not who made it. The signed build provenance, checked with the GitHub CLI (2.49 or newer, logged in), shows it was built by this repository's release workflow. On Windows, Get-FileHash .\rnfs-0.2.0-windows-x64.exe gives the checksum.

From source

git clone https://github.com/afit21/rnfs && cd rnfs
pip install --require-hashes -r packaging/requirements.lock   # the tested Reticulum (optional)
pip install -e .
rnfs daemon
python3 -m unittest discover tests                             # the tests

pipx install git+https://github.com/afit21/rnfs installs the main branch without a checkout. The release packages are built by .github/workflows/release.yml; the scripts it uses are in packaging/. To work on rnfs itself (the tests and their options, lint, the locks, adding a schema step or an API route, releasing), see CONTRIBUTING.md.

Reticulum and where things live

rnfs uses Reticulum's own config directory (/etc/reticulum, ~/.config/reticulum or ~/.reticulum; %USERPROFILE%\.reticulum on Windows), and attaches to rnsd if it is running. Your network interfaces (LAN, TCP, RNode/LoRa…) are set up in its config file: see the Reticulum manual. daemon --rnsconfig DIR uses another.

Everything else is in the rnfs directory (--dir, default ~/.rnfs/), which only you can read:

Path Contents
rnfs.toml your settings
storage/identity the node's identity (your node ID comes from it); storage/lxmf_identity and lxmf/, if there, are what rnfs's own LXMF left before 0.2.0 (LXMF moved out)
blocks/ stored blocks, one file per block in 1,024 subdirectories (blocks/<xy>/<cid>)
state/rnfs.db pins, records, jobs, names' sequences, contacts, deny lists, the stored blocks' sizes (SQLite; an older version's JSON files are imported once and kept as *.migrated)
names/ name keys: back them up with rnfs name export
files/ the shared folder: anything put here is added, pinned and shared
api_token, daemon.lock the running daemon's API token and lock
debug.log with debug_log on, this run's debug log (debug.log.1: the previous run's)

Keep files/ writable only by you. rnfs skips links and files with several hard links in it, so copy files in rather than linking them. rnfs refuses an identity or rnfs.toml owned by another account.

Sample files

Some files & folders that I host as examples for everyone to download. Fetch them with a running daemon that is connected to a network (Getting started):

Moby Dick book .txt file - bagaibqabciqfweclrlsfdkqgkhtmbavijftqfkiogrprcntnew5jalakjsykq4q

The Communist Manifesto - bagaibqabciqkq6rth6jhr2vkh7c2ip7p6bsmdxmslbia47rjbmwpcz6wwu6veya (PDF, 715 KB)

Original Doge Photo - bafkreigqte4wbglswc74766w445b67scla7kd2wvrqtp6t7a2ofw32znny

MeshChatv2.4.0 (folder) - bagaibqabciqaiphoe7qtscjuqprvwpr2ifr3hgnvuk5rgeh53mpk4ntapwutr3a

Nasa Apolo 11 'One Small Step' audio mp3 - bagaibqabciqbe75h7g7s4zqozhwrbxbica6krk5iggn7itaoh6t2fh7lnhqpjeq

Reticulum github zip taken on 11/10/2026 - bagaibqabciqd6s6yrwpp6eemv27sa7ud4l3r7rjcyu5oh4fgziopqa432htdxma

Apline Linux 3.24.2 ISOs (folder) - bagaibqabciqh5e4vsvwazopdfypoiflpfjvbfqionjko7cvxsmzmumsdoppzixy

SCP-106 Original Image - bagaibqabciqospcwo756sqjp7yl3h2vukd3ijkvffy4nyh2umljg4yluwp2ce6a

rockyou.txt.zip - bagaibqabciqp26agunewwmavivqgwv3c53xvnxdn7vpz4rdknjlfai72zszhdza

Fetch one with rnfs get <cid> -o <output file name>, or open it in your browser through the local gateway (?filename= sets the type and the download name):

rnfs get bagaibqabciqospcwo756sqjp7yl3h2vukd3ijkvffy4nyh2umljg4yluwp2ce6a -o scp-106.jpg
http://127.0.0.1:8789/rnfs/bagaibqabciqkq6rth6jhr2vkh7c2ip7p6bsmdxmslbia47rjbmwpcz6wwu6veya?filename=ManifestoOftheCommunistParty.pdf

Getting started

1. Run the daemon and connect it

rnfs daemon runs your node; leave it running (or install it as a service), since every other command talks to it. Your node needs Reticulum interfaces that reach other nodes:

  • Local network: Reticulum's default AutoInterface finds nodes on the same LAN or Wi-Fi with no setup.
  • Internet: add an interface to a public transport node to your Reticulum config; see reticulum.network/connect.html for the public backbone.
  • Radio: add an RNodeInterface for LoRa (Reticulum manual).

Running a LoRa island's gateway. A node on the radio finds other nodes with path requests, and Reticulum never repeats a path request on the interface it came in on: a request reaches only the nodes in radio range, and those with transport enabled answer from the paths they already know. So the gateway (the node with the radio and an internet interface) should forward requests for paths it doesn't know to the internet side: run it with enable_transport = Yes, and give its radio interface mode = gateway (or recursive_prs = yes). A relay bridging two radios needs the same on both. That serves the nodes in the gateway's (or the relay's) radio range; nodes farther out find only the servers whose announces their neighbours heard, however many path requests they send, as a relay with one radio can't pass a request on. (In Reticulum 1.5.6 write mode = gateway: interface_mode = gateway isn't read correctly.)

A node one radio hop from its gateway. By default every server within 3 hops over a slow interface counts as your island's (local_hops), which suits a mesh of relays; but a node whose only way out is one gateway reaches the servers on the gateway's internet hub in 2 or 3 hops too, and counts them as local. It then asks them first and, while the island is cut off, keeps asking them in vain. On such a node set local_hops = 1, or name the radio interface (local_interfaces) on the gateway itself, where rnfs sees which servers are behind the radio.

Your node then joins the DHT through the project's built-in bootstrap nodes and the rnfs nodes it hears announcing. rnfs status shows your node ID and known_nodes, and once a few lookups have completed, about how many DHT servers the whole network has (network_size); rnfs peers lists the nodes it knows. To also join through a node you choose, run rnfs daemon --bootstrap <NODE_ID> (or set bootstrap_nodes); default_bootstrap = false stops it contacting the built-in ones.

2. Get files

rnfs get <cid> -o out.pdf               # a progress bar; Ctrl+C cancels, and a later get resumes
rnfs get <cid>/docs/ --background       # leave it running in the daemon: rnfs jobs
rnfs ls <cid>                           # list a directory; rnfs cat <cid> writes to stdout
rnfs get <cid> --from <node ID>         # ask a node you know has it, without the DHT

In a browser, http://127.0.0.1:8789/rnfs/<cid>/ shows a directory's listing or its index.html, so a folder with a website in it works as a site. rnfs url <cid> prints its address on a subdomain of its own, http://k….rnfs.localhost:8789/, where its pages can also read its files with fetch() (names: http://<name>.name.localhost:8789/).

3. Share files

rnfs add report.pdf             # prints its CID; give that to others
rnfs add photos/                # a whole directory
rnfs add report.pdf --wrap      # in a directory, so the link keeps its name: <cid>/report.pdf

add stores and pins the file and tells the DHT you have it. Files dropped into ~/.rnfs/files/ are shared automatically. rnfs pin rm <cid> stops sharing, and rnfs gc frees space used by downloads you didn't pin. By default your node also shares what you download while it's cached, so others can see what you fetched, unless it is a slow radio node, which shares only what you pin (publishing each download costs airtime); provide_strategy = "pinned" shares only what you pin on any node, "roots" downloads too. Every file and folder inside what you pin is also findable by its own CID (provide_entries), so a file you shared in a folder can be fetched without the folder's CID; on a slow radio node, only while there are few of them. Content other nodes asked yours to keep (remote pins) and names you follow are shared only as a whole. That also lets anyone who has one of those files look up its CID and see that your node holds it, so pin only what may be known to be on your node, or set provide_entries = false. add refuses the rnfs dir (~/.rnfs) and any folder holding it, such as ~: it holds your node's keys and API token.

4. Names: a link that can change

A CID always means the same content. A name is a fixed ID you can point at new CIDs:

rnfs name create site                   # prints the name (32 hex characters)
rnfs name publish site <cid>            # again later for a new version
rnfs name sync site ~/site              # or keep it pointing at a folder, published as it changes
rnfs contacts add site <name>           # a nickname, so others can type "site"
rnfs get site/index.html                # whatever the name points at now
rnfs name follow <name>                 # keep the latest version pinned

In a browser: http://127.0.0.1:8789/name/<name>/. Back up the key with rnfs name export site -o site.key: only it can update the name.

How rnfs adapts IPFS

rnfs follows IPFS where its design helps, uses Reticulum in place of libp2p, and drops what only makes sense on IP networks. It is not compatible with IPFS. The full comparison is in specification.md, Appendix A.

IPFS rnfs
CIDs Same idea: a raw block or one rnfs DAG codec, SHA-256, base32. A file of up to 64 KiB has the same CID as in IPFS
UnixFS 64 KiB chunks (optionally FastCDC) in balanced trees, sorted directory indexes. The execute bit is kept; mtimes and symlinks aren't
PeerID, transports, NAT traversal A node ID is a Reticulum destination hash; Reticulum routes and encrypts
Kademlia DHT For provider records only, K = 10, server and client modes, Sybil resistance from seniority
Bitswap Multi-source, resumable, verified /blocks requests over Reticulum, sized to each link's measured rate
IPNS Signed records in Reticulum announces, re-served by keepers so a name outlives its owner's announces
Pinning, remote pinning, GC The same, with pin sources (user, folder, name, remote, follow, apps)
CAR files, HTTP gateway export / import archives; a local gateway with sandboxed pages and trustless ?format=raw / archive
MFS dir set / dir rm make edited copies; a name is the mutable root
Pubsub, DNSLink Dropped (floods are unaffordable on radio; Reticulum has no DNS). Use name follow and rnfs: links
Private networks Reticulum interface access codes, plus rnfs private mode (members only)

Settings

Settings go in ~/.rnfs/rnfs.toml; command-line options override them. Times are in seconds, sizes in bytes, rates in bits/s, and an unknown setting is an error (one a newer version retired, such as max_records, is ignored with a warning). These are the ones you're most likely to change; every setting and its default is in specification.md, section 12.

# ~/.rnfs/rnfs.toml
dht_mode = "client"               # on phones, laptops and battery nodes
max_storage = 21474836480         # 20 GiB
provide_strategy = "pinned"       # don't share your downloads
upload_budget = 5_000_000         # upload at most ~5 MB per hour
no_serve_interfaces = ["RNode"]   # don't upload over the LoRa radio
quiet_hours = "22:00-06:00"
Setting Default What it does
dht_mode "server" "client" only asks the DHT, for nodes often offline or short of bandwidth
bootstrap_nodes, default_bootstrap [], true Node IDs to join through first; whether to also use the built-in ones
slow "auto" A slow node uses one provider at a time and fewer parallel queries; auto when every interface is under slow_bitrate (100 kbit/s)
announce_interval 21600 (6 h) How often the node and its names announce; keep it ≥ 1 h
provide_strategy "auto" "roots" shares pins and every complete download while it's cached (even a cat or stat), so anyone can learn what you downloaded; "pinned" shares only pins; "auto" is "pinned" on a slow node, "roots" on others
provide_entries unset Also share every file and folder inside your pins under its own CID (not inside downloads, remote pins or followed names). Unset: on, for up to provide_entries_max of them, oldest pins first; on a slow node only while republishing them would take under 2% of the radio channel (about 40 keys in all on a large network: rnfs status says when they're off and why). true or false overrides it. Each one tells anyone who looks that your node has that file
provide_entries_max 100000 The most files and folders provide_entries shares; a pin that would pass it shares none of its own (rnfs status: provide_entries_capped). Raise it for a large mirror
max_storage 10 GiB Cached downloads are evicted above this; pins never are. It counts disk use: each block rounded up to the filesystem's cluster, which on a FAT32 drive (16–32 KiB) makes small files take several times their size (rnfs status: allocated_bytes). The disk keeps max(256 MiB, 2%) free
max_download_size none Refuse fetches larger than this
record_budget 1 GiB Disk the provider records your node stores for others (as a DHT server) may take; when full, your node contacts and publishers seen for days keep their place, and strangers share a small part
max_jobs 2; 1 on slow nodes Transfers running at once
upload_budget none Bytes per hour your node uploads in total
serve_interfaces, no_serve_interfaces all, none Reticulum interfaces to upload on, or never upload on (names or prefixes)
quiet_hours off Local times with no uploads, e.g. "22:00-06:00"
local_interfaces [] On a LoRa island: the Reticulum interfaces (names or prefixes; with rnsd, as its config names them) whose servers are your island's. Empty: servers within local_hops (3) over a slow interface. Set it on a gateway, or behind a big public hub
local_hops, local_replicas 3, 3 How far a slow interface's servers count as local when local_interfaces is empty; and how many of them also store each thing you share (0 to 5), so your island finds it while cut off
remote_pin_identities, remote_pin_quota [], 1 GiB Identities allowed to ask this node to pin (pin remote add), and the storage each may use
deny_lists [] Deny lists to apply
keep_max_names 100 Names you keep or follow for others
follow_quota 1 GiB Storage each followed name's target may use; a larger one isn't fetched and the previous one stays pinned (up to twice this while it changes). 0 stops fetching followed names
api_port, gateway_port 8788, 8789 Local API and gateway ports (127.0.0.1 only)
gateway_scripts true Let gateway pages run scripts
debug_log false Write ~/.rnfs/debug.log (readable by you only) for diagnosing problems (daemon --debug-log)
verbosity 0 Extra logging: 1 per-query detail, 2 RNS debug output (daemon -v, -vv)

Changing settings while the daemon runs. rnfs config lists every setting with the value the daemon runs with (* set in rnfs.toml, ! changed there but waiting for a restart, ^ set on the daemon's command line, which wins); rnfs config get <name> describes one. rnfs config set <name> <value> (a TOML value: true, 10, "text", ["a", "b"]; a bare word is text) and rnfs config unset <name> edit rnfs.toml in place, keeping your comments, after checking the whole file as a start would. Some apply at once: max_storage (enforced then), max_download_size, max_jobs, quiet_hours, the serve interfaces, the quotas and a few more (config get says which); the rest at the next start. Apps do the same through GET /api/config and POST /api/config/set, which carry each setting's type, range, unit, default and description.

Private mode. private = true with a members list (identity hashes, from each node's rnfs status) makes the node link to, serve and keep records for members only. A name's announce still reaches everyone, so outsiders can see which CID it points at and when it changes (not fetch it), and rnfs name publish warns of this in private mode.

LXMF moved out of rnfs. Until 0.2.0 rnfs could send and receive links over LXMF itself (rnfs send, inbox, outbox, lxmf address, lxmf sync, the lxmf_* settings and the /api/lxmf/ routes). That is now a separate app built on rnfs's local API and library, and rnfs 0.2.0 no longer has any of it; rnfs: links stay rnfs's (rnfs.Node and rnfs.Client take one as a target). An upgrade keeps what your node's LXMF had: its identity (storage/lxmf_identity) and state (lxmf/) are left untouched, so an LXMF app can adopt them and keep your LXMF address; your inbox and outbox stay in state/rnfs.db for it to import; your LXMF contacts stay in rnfs contacts; and lxmf_* settings left in rnfs.toml are ignored with a warning (remove them with rnfs config unset). Files fetched automatically from LXMF senders stay pinned, as the pins of an app called lxmf (app:lxmf:<address> in rnfs pin ls): an LXMF app registered under that name takes them over, and if you don't want them, rnfs app add lxmf then rnfs app rm lxmf releases them.

Commands

Every command except daemon and url talks to the running daemon, so run them as the daemon's user. --dir DIR and --api-port N pick another daemon; rnfs <command> --help shows every option. A target is <cid>[/path] or <contact>/path; where a node ID is expected, a node contact's nick works too.

Command Description
rnfs daemon Run a node with its API and gateway (--bootstrap, --rnsconfig, --private, --debug-log, -v…)
rnfs status / peers / stats Node ID and identity hash, storage, jobs, and what it sees of the network (estimated size, density-check alarms, partition mode, provider records against record_budget by tier, refusals and evictions) / known nodes, their round-trip time and whether each is online / traffic
rnfs ping NODE_ID / providers CID Round-trip time to a node / nodes providing a CID
rnfs add PATH|- Add a file, directory or stdin, pin it and print its CID (options below)
rnfs add DEST:/page/PATH / add DEST:/file/PATH Add a snapshot of a NomadNet page or file (DEST is the NomadNet node's 32-hex address), pin it and print its CID
rnfs get TARGET [-o OUT] [--from NODE_ID] [--background] Fetch and write out a file or directory
rnfs cat TARGET [--offset N] [--length N] Write a file, or part of one, to stdout
rnfs ls TARGET / stat TARGET List a directory / size and how much is stored locally
rnfs url TARGET The target's gateway site URL (http://k….rnfs.localhost:8789/; a name's too); needs no daemon
rnfs dir set TARGET NAME CID / dir rm TARGET/NAME Make a copy of a directory with an entry added, replaced or removed
rnfs export TARGET -o FILE / import FILE Write or import an archive (USB sticks, sneakernet)
rnfs pin add TARGET / pin rm CID / pin ls Fetch and pin / unpin / list pins, their sources and where they came from
rnfs pin remote add|rm NODE_ID CID Ask another node to pin or unpin (it must list you in remote_pin_identities)
rnfs pin remote ls [NODE_ID] [CID] [--check] / pin remote ls --served The remote pins you asked for and whether each is done (the node found among the CID's providers; --check looks now) / those others asked of your node, what became of each and their use of remote_pin_quota
rnfs gc / verify Evict cached downloads / re-check every stored block
rnfs deny add|rm ENTRY… / deny ls Edit your deny list / show it and the lists in use
rnfs jobs [list|show|wait|cancel|clear] [ID] Background transfers: progress, rate, time left
rnfs name create|list|publish|resolve|rm Your names and what they point at
rnfs name export|import ALIAS Back up or restore a name's private key
rnfs name keep|unkeep|kept NAME Store and serve someone's name record, so it resolves while they're offline
rnfs name follow|unfollow|following NAME Keep the latest version a name points at pinned
rnfs name sync ALIAS FOLDER / name unsync ALIAS / name syncs Keep your name pointing at a folder's current content: published when it changes, once unchanged a minute, at most once an hour / stop (the name keeps its target) / list the syncs and how they stand
rnfs contacts add NICK HEX / rm / ls Nicknames for names, node IDs and LXMF addresses (an LXMF app reads them)

add options: --wrap keeps the file's name in the link; --only-hash prints the CID without storing; --no-pin keeps it as evictable cache; --chunker fastcdc lets an edited version share most blocks; --to RECIPIENT… (after the path) seals a file so only those node IDs, LXMF addresses or contacts can read it, and --to-self adds you; a folder is sealed as one file, a tar named <folder>.tar, hiding its names too.

A NomadNet link (rnfs add e166ecdbc405bcc9762b6767617fc949:/page/index.mu) is fetched from that node as it serves it now and added like a file, with the link shown in pin ls; run it again for a newer snapshot. The same options apply, and --name N wraps it under that name. Requests are anonymous; --identify identifies with rnfs's own NomadNet identity (storage/nomadnet_identity, not your node's) for pages the node serves only to identities it lists: rnfs status shows its hash (nomadnet_identity_hash), for the node's operator to list. A local file whose name looks like such a link is added as ./NAME.

get without -o uses the target's name only if it is a safe file name (printable, not starting with .), otherwise its CID, and writes under a temporary name until the transfer is complete. It decrypts sealed files you can open, and unpacks a sealed folder to its directory (--no-extract keeps the .tar); --raw gives the stored bytes, which is what scripts that show content to others should use.

Gateway (http://127.0.0.1:8789): /rnfs/<cid>[/path] and /name/<name>[/path] serve files and directories; ?filename= sets the type and download name (on a site, below, only the download name); ?format=raw and ?format=archive return data you can check against the CID. Pages are sandboxed and can't read the gateway's responses, and sealed files are never decrypted there. Each CID and name also has a site of its own, http://<k-cid>.rnfs.localhost:8789/[path] (rnfs url <cid> prints it) and http://<name>.name.localhost:8789/[path], whose pages can read their own files; browsers resolve *.localhost themselves, other tools should use the paths above.

Building apps on rnfs

Python programs use rnfs the way they use RNS, through one of two classes with the same methods, objects and errors:

  • rnfs.Node runs a node inside your program, beside its own Reticulum, with its own storage and identity.
  • rnfs.Client uses the user's running daemon (their content, pins and names). It never loads Reticulum, so it starts fast.

The API is provisional until rnfs 1.0.0 (rnfs.API_STATUS): the library and the local API may change in 0.2.x releases too; capabilities() (GET /api/capabilities) says what a daemon offers.

import RNS, rnfs

RNS.Reticulum()                                   # your program's Reticulum, as with any RNS app
with rnfs.Node("~/.myapp/rnfs") as node:          # or: rnfs.Client(), for the daemon (no RNS.Reticulum())
    added = node.add("photos/")                   # rnfs.Added: .cid, .size, .warnings
    for entry in node.ls(added.cid):              # rnfs.Entry(name, kind, size, cid)
        print(entry.name, entry.size)
    with node.open(f"{added.cid}/cat.jpg") as f:  # seekable: a read after a seek fetches only what it covers
        data = f.read(4096)
    site = node.names.create("site")
    site.publish(added.cid)
    node.names.watch(site, lambda record: print("now", record.cid))
    node.jobs.get(f"rnfs:name/{site.hex}", "/tmp/copy").wait()
  • An embedded node is a DHT client unless you pass settings={"dht_mode": "server", ...}. It never stops your Reticulum. Never point it at the daemon's ~/.rnfs/: a directory is used by one node at a time.
  • Callbacks run on one event thread; keep them short.
  • Beyond content, pins, names and jobs: peers() and stats(), the address book (contacts.add/get/list/remove), and keeping and following names (names.keep/unkeep/kept, names.follow/unfollow/following).
  • To follow what changes instead of polling lists: events.watch(callback, kinds=...) (jobs, pins, the cache, names, peers, remote pins; an event with event.lost means "read everything again"), or events.since(...). Also capabilities(), pin labels (label_pin), the cache (cache(), uncache(cid)), settings (config(), config_set({...})), tars and archives as streams (add_tar, get_tar, export_stream, import_stream), and for a Client shutdown() and daemon_info().
  • A Client can have a token of its own, so the user sees and removes what your app pinned: the user runs rnfs app add myapp -o ~/.myapp/token, and you use rnfs.Client(app="myapp", token_file="~/.myapp/token"). Its pins get the source app:myapp (with client.pin_key("inbox"): ... makes them app:myapp:inbox's, a group your app can hold and release on its own); client.unpin() releases only your app's, and rnfs app rm myapp revokes the token and releases them all. The token isn't scoped: your app can do anything the user's rnfs command can, and so in effect manage apps too. rnfs app itself refuses an app's token, but an app can read and write files as the daemon's user (add, get, export), which is enough to reach the command line's own token. Give a token only to software you would trust with rnfs itself.
  • A Client waits as long as each request takes unless given timeout=; with client.timeout(5): ... bounds just the calls your thread makes inside it (a Node accepts it too, and does nothing).
  • examples/rnfs_share.py is a runnable example using either class; design.md's "Public API" section is the reference. Other languages can use the local API's JSON endpoints: docs/openapi.json is an OpenAPI 3.1 reference for every route (parameters, answers, error codes, the token headers and the events), which OpenAPI tools can read or turn into a client.

Keeping content off your node

A deny list keeps chosen content off a public gateway or provider node. Entries are CIDs or file hashes (sha256:<hex>, as sha256sum prints it):

rnfs deny add bafkreidhtnhwyqkmbhxvp2mrery66f4kpdsfbxew3qrfqrbtwbs6zb5zce
rnfs deny add sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

To use a list someone maintains (a text file, one entry per line, # for comments), add its CID, or name/<name>/path to follow its updates, to deny_lists in rnfs.toml.

A denied CID is never fetched, stored or served, whether asked for directly or found inside other content (the gateway answers 451). Cached copies are deleted and remote pins released; your own pins are kept but not served, and the log names them so you can remove them. A file hash is only known once a file is downloaded, so it stops the file being handed out or added, not its blocks being fetched: deny the CID for that (the first time rnfs refuses a file by its hash, the log names the command). While there are file hashes, a file that repeats its content more than 16 times beyond 64 MiB can't be read or imported (451): it can't be checked cheaply.

Example use cases

Community library over LoRa. A librarian publishes a directory of documents under a name and updates it with dir set and name publish. Library nodes rnfs name follow it, so each always holds the latest edition, and readers rnfs get library/maps/region.pdf.

Sharing with a friend. Send them rnfs:<cid>/report.pdf?from=<your node ID> any way you like (Sideband, NomadNet and MeshChat show it as text, and an LXMF app built on rnfs sends and fetches such links for you); they fetch it with rnfs get <cid>/report.pdf --from <your node ID>, asking your node first.

Private group. rnfs add minutes.pdf --to <alice's node ID> bob --to-self seals a file so only those people can read it, while any node can carry it. For a fully closed group, run every node with private = true and the same members.

Offline sneakernet. rnfs export <cid> -o bundle.rnfs on a connected node, rnfs import bundle.rnfs on the offline one: it's verified and pinned.

Mirroring a publisher. rnfs name follow <their name> keeps their latest content pinned and their name resolvable through your node while they're offline.

Field kit with an airtime budget. A battery node on a shared LoRa channel: dht_mode = "client", max_jobs = 1, upload_budget = 2_000_000, quiet_hours = "22:00-06:00" and no_serve_interfaces = ["RNode"]. Interface rules need rnfs to run Reticulum itself: attached to a shared rnsd, every link looks like it arrives on the local interface.

A website on the mesh. rnfs add mysite/, then rnfs name create site && rnfs name publish site <cid>. Visitors open http://127.0.0.1:8789/name/<name>/ on their own node, and each name publish updates it; or rnfs name sync site mysite/ publishes the folder whenever it changes (at most once an hour, and only once it has been unchanged for a minute, so a half-copied site isn't published). NomadNet pages can't open rnfs: links, so show the link as text: rnfs:name/<name>/index.html.

Running several nodes

Give each daemon its own --dir, --api-port, --gateway-port and Reticulum config (--rnsconfig). The first node's Reticulum config has a TCPServerInterface with enable_transport = Yes; the others have a TCPClientInterface to it and share_instance = No, and start with --bootstrap <first node ID>:

rnfs --dir /tmp/a --api-port 9001 daemon --rnsconfig /tmp/a --gateway-port 9101 --bootstrap <hub id>
rnfs --dir /tmp/a --api-port 9001 add somefile
rnfs --dir /tmp/b --api-port 9002 get <cid> -o out

Known limitations

  • A name resolves only while its owner, a transport node that remembers its announce, or a keeper is reachable, and can be updated about once an hour.
  • Sybil attacks can make a CID hard to find (get --from bypasses the DHT), but data can never be forged.
  • Sealed files hide content and file name, not size or recipients, and don't prove who sealed them. get of a directory leaves sealed files inside it encrypted.
  • A sealed file is readable by whoever holds the recipient's node identity, which is online whenever the node runs and stored unencrypted in the rnfs dir (storage/identity): sealing protects against the network, not against someone with access to the recipient's disk.
  • Under provide_strategy = "roots", the default on nodes that aren't slow, anyone can learn what your node downloaded. With provide_entries (on by default), anyone can learn which files your pinned folders hold by looking up each file's CID, without knowing the folder.

About

RNFS - A distributed file system for Reticulum based on InterPlanetary File System (IPFS)

Topics

Resources

Contributing

Stars

23 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages