NomadCast is a small, stubborn bridge between Reticulum-hosted podcasts and the app you already use (Apple Podcasts, Overcast, Pocket Casts, etc.). It runs a tiny local service that looks like a normal HTTP feed, while quietly fetching the real RSS and audio over Reticulum in the background.
In other words: you subscribe to a normal localhost URL, and your podcast app never needs to know Reticulum exists. NomadCast handles the underground tunnels for you.
One-line context: Reticulum is the resilient, off-grid networking layer underneath all of this—learn more at https://reticulum.network/.
Default feature (entirely optional): mirroring is baked into streaming. When you listen, NomadCast already downloads the RSS and media files into its local cache. With mirroring enabled, NomadCast simply exposes those cached files to Nomad Network via symlinks and keeps a tiny index page up to date. It is the bright, cooperative future of local-first media: anyone can stream a show, light up a mirror, and keep the signal alive even when the wider network flickers.
Mirroring warning (read before enabling): “Mirroring is how we build a resilient, decentralized future. NomadCast will download and store episodes on disk and share them to other Reticulum peers via your Nomad Network pages, so only turn this on if you are good with the disk use and serving that content onward.”
Thanks to this repo—and the frictionless mirroring built into streaming—podcasts can now be fully decentralized, with no single point of failure and no gatekeepers between you and the signal.
Status + overview
NomadCast is a work in progress. It may be incomplete, unstable, or not behave the way you expect yet.
You’re invited to poke at the edges and report back. Tell me what feels confusing, broken, or missing. If you’re a normal listener or creator, now is a good time to sketch your Reticulum podcast, design your show page, and experiment with the flow. Just don’t expect it to be fully reliable quite yet.
Why it’s built this way (privacy + control)
NomadCast is built to be aggressively private and radically user-controlled for listeners and publishers alike:
- No data collection. No accounts, no analytics, no telemetry, no usage data, no unique device IDs, no fingerprinting. Your listening habits never leave your machine. NomadCast only requests the exact Reticulum objects you ask it to fetch.
- Completely open source, MIT licensed. Every line of code is inspectable and hackable, with a permissive license that keeps you in control.
- Your preferred player, your feeds, your network. Pick the podcast app you already trust, then subscribe to any show you want from anywhere on the Reticulum network. NomadCast just bridges the gap and gets out of your way.
- Publisher-side freedom. Publish at any bitrate, in any format. Audio-only, video podcasts, experimental formats — if your RSS references it, NomadCast will fetch it as-is over Reticulum.
What a normal listener does (quick path)
Think of this like subscribing to any other podcast, just with one extra helper app.
- Install NomadCast.
- Start the NomadCast daemon (it runs quietly in the background).
- Click a NomadCast podcast link on a NomadNet page; NomadCast pops up to confirm the add.
- NomadCast adds the show to its config and launches your regular podcast player with a local URL like:
- http://127.0.0.1:5050/feeds/<identity_hash%3AShowName>
After that, your podcast app behaves normally: it sees an RSS feed, downloads episodes, and plays them. NomadCast keeps the feed and episode files available even when Reticulum is slow or offline by serving a local cache.
Publisher sample creator app (recommended for creators)
Welcome to the Relay Room. This is the fastest way for podcast creators to get on the air with NomadCast. The app spins up a ready-to-publish starter show for you and points you straight at the files so you can remix them.
How to run it:
- From the repo, launch the standalone app:
python -m nomadcast_sample
What it will do for you:
- Ask for your NomadNet node ID and weave it into your show pages + RSS.
- Install your show pages either at
~/.nomadnetwork/storage/pagesor under~/.nomadnetwork/storage/pages/podcast. - Refresh your show files at
~/.nomadnetwork/storage/files/ExampleNomadCastPodcast. - Give you quick buttons to open the pages and media folders, so you can start editing right away.
When you’re done, hand it off like a real broadcast: swap in your show name, update the RSS metadata, and drop in your audio files. Your NomadNet node will host the updated files as soon as you save them.
For developers: manual publishing steps
NomadCast does not generate RSS for you. You publish a normal podcast RSS file and normal episode files, and you host them on your existing Reticulum setup using Nomad Network, which already supports hosting pages and files.
If you want a concrete, copy-pastable reference, jump to the examples tour for full sample files you can adapt.
In your NomadNet page (or wherever you share the show), publish a normal-looking subscribe link that points to the NomadCast locator. This keeps the page clean and gives users one obvious action.
Example:
[Subscribe to this podcast](nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestPodcastInTheWorld)
If you want a fallback for users who cannot click the link, include the raw locator on the next line:
nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestPodcastInTheWorld
Notes:
\<identity_hash\>is the publisher identity hash (32 hex chars) that listeners route to.- The show name is cosmetic. The identity hash is authoritative.
- Since NomadCast is a new project, consider linking your podcast page back to this repo so listeners can install NomadCast and start using your show right away.
If this project helps you out, a star, watch, share, or a gentle mention goes a long way. Thanks for helping NomadCast find its people. 💛
Micron example (NomadNet-friendly):
## Subscribe
[Subscribe to this podcast](nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20%3ABestPodcastInTheWorld)
If that link does not open your podcast app, copy and paste that link into [NomadCast](https://github.com/jamesdwilson/nomadcast)
On first run, NomadCast registers itself as a system-wide protocol handler for nomadcast:// links, so clicking the link above will open NomadCast directly on supported systems.
-
Install Nomad Network:
pip install nomadnet
-
Run Nomad Network once to initialize your config:
nomadnet
-
Put your podcast RSS and episode files into your node storage (Nomad Network hosts them under
/file/):RSS: ~/.nomadnetwork/storage/files/<YourShow>/feed.rss Episode files: ~/.nomadnetwork/storage/files/<YourShow>/media/<episode files>Notes:
- Nomad Network nodes can host files. In NomadNet content, files are typically linked under a
/file/path. Episode media is still just a file, so keep it under the same/file/tree. - Keep your RSS a standard RSS 2.0 feed with
<enclosure>URLs. NomadCast will rewrite those URLs for listeners. - If you want a starting point, the
examples/storage/files/ExampleNomadCastPodcast/feed.rssfile is ready to copy and rename.
- Nomad Network nodes can host files. In NomadNet content, files are typically linked under a
-
In your NomadNet page (or wherever you share the show), publish a locator that includes your Reticulum identity hash plus a human-readable show name:
<identity_hash:YourShowName>
Listeners paste that string into NomadCast. If you have a working MeshChat file link, the hash before :/file/... is the identity hash to use.
Publisher requirement: the identity hash must be stable. Use the same identity hash over time so the locator stays valid.
Examples tour
If you learn best by example, there’s a small, cheerful sample podcast site in the examples/ directory. It is laid out like a fresh Nomad Network storage tree, so you can copy it directly into ~/.nomadnetwork/storage/ if you want a ready-to-run starter webroot. Think of it as a friendly mock webroot for someone brand new to NomadNet.
examples/storage/pages/index.mu— a rich NomadNet page with a subscribe button, episode summaries, and credits.examples/storage/files/ExampleNomadCastPodcast/feed.rss— a standard RSS 2.0 feed wired up to the sample episodes (with credit notes in the metadata).examples/storage/files/ExampleNomadCastPodcast/media/CCC - Reticulum - Unstoppable Networks for The People-smaller.mp3— sample audio from a Chaos Communication Congress (CCC) community recording.examples/storage/files/ExampleNomadCastPodcast/media/Option Plus - How to fix the Internet – Nostr, Reticulum and other ideas.mp3— sample audio referencing the Option Plus podcast.
Each file references the others so you can see the entire flow: NomadNet page → RSS feed → episode files. The example uses a placeholder identity hash (0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f), so replace it with your node’s real hash when you publish. You can use these as a template, rename things to your show, and publish with confidence.
Community conventions
NomadCast aims to follow Reticulum community norms for discoverability and publishing:
- Use Nomad Network file hosting paths (
/file/) for RSS and episode file links when publishing on NomadNet pages. - Treat the Reticulum identity hash as the canonical show identifier; the human-readable name is optional and cosmetic.
- Keep RSS feeds standard RSS 2.0 (and iTunes-compatible) so clients and tooling remain interoperable.
Nomad Network mirroring (automatic)
Mirroring is an implicit feature of streaming. When you listen, NomadCast already caches RSS and media under its normal storage path. If mirroring is enabled, it simply exposes those cached files to Nomad Network via symlinks and publishes a tiny index page so other Reticulum peers can discover mirrored feeds.
How it works:
- RSS and media are not copied. NomadCast creates symlinks from your Nomad Network storage tree to the existing NomadCast cache.
- The index page lives at
/nomadcast/index.muand lists subscriptions in config order. - The page is rendered from
nomadcastd/templates/nomadnet_index.mu, which is the board-style template used for mirroring. The renderer fills placeholder tokens like{{PodcastName_1}},{{OriginSiteHref_1}}, and{{MirrorRssHref_1}}for up to 10 entries. - For the first three entries, NomadCast also fills up to three episode previews from the cached publisher RSS when the mirrored media files exist locally.
[mirror rss]points to the Nomad Network hosted RSS (the symlinked RSS path).[origin rss]points to the publisher RSS when known (falls back to/file/<ShowName>/feed.rss).
First run prompt:
- On the first interactive run, NomadCast asks whether you want mirroring enabled by default for all podcasts. The answer is stored in your config so you only see it once.
- If the process is non-interactive, NomadCast defaults to mirroring on and logs how to disable it.
Per-feed override:
- Use
--no-mirrorwhen subscribing to prevent NomadCast from exposing that feed via Nomad Network (it still caches locally for playback).
Mirroring warning: “Mirroring is how we build a resilient, decentralized future. NomadCast will download and store episodes on disk and share them to other Reticulum peers via your Nomad Network pages, so only turn this on if you are good with the disk use and serving that content onward.”
How it works (more technical)
-
nomadcastd (daemon)
- Runs an HTTP server on 127.0.0.1:5050
- Maintains a small local cache per show:
- The most recent fetched RSS bytes
- The last N episode media objects (default N=5, configurable)
- Talks to Reticulum (Python RNS) to fetch:
- The publisher RSS file
- Episode media objects referenced by the RSS feed
-
nomadcast (UI, v0)
- A minimal Tkinter prompt that collects a show locator and writes it to the daemon config.
- After adding a show, it opens the local subscription URL in the OS (so your default podcast handler can take over).
- The UI starts hidden and lives in the system tray/menu bar. Click the tray icon (or choose the default Show/Hide menu item) to toggle the window, and use the menu option to quit the UI without stopping the daemon.
flowchart LR
Listener[Listener + Podcast App] -->|GET /feeds + /media| Daemon[nomadcastd HTTP server]
UI[nomadcast UI] -->|Writes subscription URI| Config[config file]
Daemon -->|Loads subscriptions| Config
Daemon -->|Fetch RSS + media| Reticulum[Reticulum Network]
Reticulum --> Publisher[Publisher RSS + Media]
Daemon --> Cache[(Local cache)]
Cache -->|Serve cached feed/media| Listener
-
You add a show locator:
- <identity_hash:ShowName>
-
The daemon creates a local, stable feed URL:
- http://127.0.0.1:5050/feeds/<identity_hash%3AShowName>
-
Podcast app requests the feed:
- GET /feeds/<identity_hash%3AShowName>
-
The daemon responds immediately with cached RSS (if present) and triggers refresh in the background:
- It fetches the authoritative RSS bytes from the publisher over Reticulum.
- It stores the raw bytes.
- It rewrites only the media URLs inside the RSS so enclosures point back to localhost.
-
Podcast app requests episode audio:
- GET /media/<identity_hash%3AShowName>/<episode_id_or_filename>
-
The daemon serves from local cache if available.
- If not cached, it queues a Reticulum fetch.
- Reticulum fetches are application-level requests over an established Link, delivered using Resource (or Bundle for larger payloads).
- v0 behavior: it returns an HTTP error quickly (to keep the podcast app from hanging) while the episode is queued for retrieval.
- When the fetch completes, the next attempt succeeds.
Reticulum transfer behavior (v0 expectations):
- Resource transfers are reliable while the Link stays up (packetization, sequencing, integrity checks, retransmits).
- Reticulum does not provide transparent "resume from byte offset" across a broken Link.
- NomadCast v0 retries from scratch if an episode transfer is interrupted.
- Future resume behavior, if desired, should be implemented at the application layer by chunking and deduplicating (for example via Bundle and chunk hashes).
NomadCast is a pass-through for publisher-defined RSS. It does not redesign feeds or strip metadata.
It only rewrites:
<enclosure url="...">and any other media URLs that point at the publisher’s Reticulum-hosted objects
Into:
http://127.0.0.1:5050/media/<identity_hash%3AShowName>/<token>
Everything else is preserved, byte-for-byte where feasible:
- title, description, GUID, pubDate, iTunes tags, chapters, artwork references, etc.
- NomadCast keeps the most recent N episodes per show in the local cache.
- Default N = 5.
- N is configurable per show in the daemon config.
The daemon will:
- Prefer the newest episodes by pubDate (or RSS ordering when pubDate is missing)
- Evict older cached episodes beyond N
- Default bind: 127.0.0.1:5050
- Rationale: common developer-local port, typically unprivileged, low collision with Reticulum tools.
Source code guide
NomadCast is split into a UI package and a daemon package:
nomadcast/: v0 UI and protocol handler entrypoint.__main__.pyhandles CLI invocations (including protocol handler launches).ui.pynormalizes locators and writes subscriptions to the config.ui_tk.pylaunches the Tkinter UI.
nomadcastd/: the daemon implementation.daemon.pyorchestrates refreshes, queueing, cache management, and RSS rewrites.server.pyexposes the HTTP endpoints (/feeds,/media,/reload) and Range support.rss.pyparses RSS and rewrites enclosure URLs to localhost.parsing.pyvalidatesnomadcast://locators and encodes/decodes show paths.storage.pyowns on-disk layout helpers and atomic writes.config.pyreads/writes the INI config format used by the daemon.fetchers.pydefines the Reticulum fetcher interface (with a mock for tests).
Tests live in tests/ and focus on parsing, RSS rewriting, and HTTP range behavior.
For readability-first conventions (package boundaries, dependency rules, docstrings, and comments), see
docs/architecture.md.
Protocol handler (nomadcast://)
NomadCast v0 registers a system URL protocol handler for the nomadcast:// scheme the first time you run the UI.
Expectation:
- NomadNet users can click a
nomadcast://link and NomadCast will open. - NomadCast will add the subscription to the daemon config.
- NomadCast will then auto-launch the system
podcast://handler to subscribe the user’s podcast app to the local feed URL.
Publisher-facing link format (what you put on a NomadNet page):
- Subscribe to this podcast
Use the double-slash form when you want a fully-qualified URL scheme in browsers.
Listener side behavior (v0):
- Link click launches
nomadcastwith the fullnomadcast://...URI as an argument. nomadcastwrites the subscription to config and triggers daemon reload.nomadcastopens:
- podcast://127.0.0.1:5050/feeds/a7c3e9b14f2d6a80715c9e3b1a4d8f20%3ABestPodcastInTheWorld
Then nomadcast exits.
On first run, NomadCast registers the protocol handler in a platform-native way:
- Windows: writes the per-user
HKEY_CURRENT_USER\Software\Classes\nomadcastregistry keys. - macOS: creates a lightweight app bundle in
~/Applicationsand registers it with Launch Services. - Linux: writes a
nomadcast.desktopfile under~/.local/share/applicationsand callsxdg-mimeto set the handler.
This means any publisher-facing page can rely on the scheme opening NomadCast after the UI has been launched once.
The UI also offers to install NomadCast into your system’s Applications location on first launch. This is optional and creates lightweight launchers only (no system-wide installer required):
- macOS: installs
NomadCast.appinto/Applications(or~/Applicationsif the system folder is not writable). - Windows: writes a per-user launcher into
%LOCALAPPDATA%\Programs\NomadCast. - Linux: creates a
nomadcastlauncher under~/.local/binplus anomadcast.desktopentry in~/.local/share/applications.
Installation notes (developer-oriented)
NomadCast is expected to track the Reticulum ecosystem’s Python-first gravity.
- Python daemon (
nomadcastd) uses the ReticulumRNSmodule. - Minimal UI is Tkinter.
- NomadNet is only required for publishers hosting files; listeners running the daemon do not need it.
nomadcastd requires the Reticulum Python module (RNS) in the same environment you run the daemon, even if you already have other Reticulum-based apps (NomadNet, MeshChat, etc.) running elsewhere. Install it with pip:
pip install reticulumOr follow the canonical Reticulum install instructions: https://markqvist.github.io/Reticulum/manual/
If you want a requirements file, use requirements-daemon.txt, which pins the daemon-only dependency.
NomadCast has no third-party runtime dependencies for the UI, but the daemon needs Reticulum (RNS). The easiest path is: clone the repo, create a venv, and install the daemon requirement.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-daemon.txt
cp .env.example .envIf you like isolated environments, you can still use a venv; requirements.txt is intentionally empty because the UI runs on the Python standard library.
We can add packaging metadata later to support a one-liner pipx install experience, but for now the simplest option is just running from the repo.
If you want to override the config path, export NOMADCAST_CONFIG (the .env file is provided as a convenience for tools like direnv).
export NOMADCAST_CONFIG=~/.nomadcast/configConfiguration (daemon)
NomadCast reads an INI config file from (first found):
NOMADCAST_CONFIG(if set)/etc/nomadcast/config~/.config/nomadcast/config~/.nomadcast/config
Default config (created on first run):
[nomadcast]
listen_host = 127.0.0.1
listen_port = 5050
storage_path = ~/.nomadcast/storage
episodes_per_show = 5
strict_cached_enclosures = yes
rss_poll_seconds = 900
retry_backoff_seconds = 300
max_bytes_per_show = 0
public_host =
[subscriptions]
uri =
[mirroring]
nomadnet_root = ~/.nomadnetwork/storage
[reticulum]
config_dir =
destination_app = nomadnetwork
destination_aspects = nodeReticulum/NomadNet considerations:
listen_host/listen_portcontrol the local HTTP feed server. Leave the default unless you need to bind a different port or non-localhost interface.mirroring.enabled(written after the first-run prompt) controls whether NomadCast exposes cached RSS/media to Nomad Network by default.mirroring.nomadnet_rootdefines the Nomad Network storage root used for symlinks and/nomadcast/index.mu.mirroring.no_mirror_urican be repeated to opt specific subscriptions out of mirroring.- NomadCast relies on Reticulum itself to load and apply interface settings. By default Reticulum reads
~/.reticulum/config, or you can point NomadCast at a different directory viareticulum.config_dir. destination_app/destination_aspectscontrol which Reticulum destination is used for NomadNet resources. MeshChat-style URLs (identity hash +/file/...) usenomadnetwork+node, which is now the default.rss_poll_secondsandretry_backoff_secondsare the main knobs for latency/refresh behavior; higher values reduce background traffic, lower values refresh faster.max_bytes_per_showandepisodes_per_showhelp cap cache size if storage or slow links are a concern.
How to run tests & coverage
Install dev tooling (coverage) once:
pip install -r requirements-dev.txtRun unit tests:
python -m unittestRun tests with coverage and view reports:
python -m coverage run -m unittest
python -m coverage report
python -m coverage htmlRun the apps
python -m nomadcastdpython -m nomadcastTo add a subscription from the command line (simulating a protocol handler click):
python -m nomadcast "nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestShow"To opt a single feed out of Nomad Network exposure during subscribe:
python -m nomadcast --no-mirror "nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestShow"To manage subscriptions directly with the daemon (handy for scripts or headless nodes):
Use the feeds subcommands to list (ls), add (add), or remove (rm) subscriptions from the daemon's config:
python -m nomadcastd feeds ls
python -m nomadcastd feeds add "nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestShow"
python -m nomadcastd feeds add --no-mirror "nomadcast://a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestShow"
python -m nomadcastd feeds rm "a7c3e9b14f2d6a80715c9e3b1a4d8f20:BestShow"You can pair these with --config if you want to target a non-default config file.
See:
- Reticulum manual: https://markqvist.github.io/Reticulum/manual/
- Reticulum site mirror: https://reticulum.network/manual/
- Nomad Network: https://github.com/markqvist/NomadNet
Roadmap (future capabilities)
Detailed tracking now lives in ROADMAP.md. The items below link to their fuller descriptions:
Related projects and references
- Reticulum (RNS): https://github.com/markqvist/Reticulum
- Reticulum manual: https://markqvist.github.io/Reticulum/manual/
- Nomad Network: https://github.com/markqvist/NomadNet
- Sideband (LXMF client with GUI): https://github.com/markqvist/Sideband
- MeshChat (web UI LXMF client): https://github.com/liamcottle/reticulum-meshchat
- rBrowser (NomadNet browser UI): https://github.com/fr33n0w/rBrowser
- Reticulum OpenAPI (community experiment): https://github.com/FreeTAKTeam/Reticulum_OpenAPI
