Expanding a Reticulum News Node: Feeds, Guestbooks, and the Quiet Tyranny of Permissions

If you've been following along, you already have a NomadNet node serving RSS feeds over Reticulum. One node, a handful of Canadian sources, a Pi humming away at home. This post is about what happens when you try to grow it — add more feeds, add a guestbook, make it feel like something someone would want to visit.
What I expected to be a quick evening of pasting in some new URLs turned into a two-hour troubleshooting detour through NomadNet's caching, file permissions, and one delightfully subtle systemd gotcha. Here's all of it, not just the happy path.
The Goal
I wanted to do three things:
Add five more Canadian news sources to the RSS fetcher
Add a guestbook page so visitors could leave a note
Link everything up on the index so people could actually find the new pages
The starting state was nine feeds — CBC News, National Post, Globe and Mail, Rebel News, True North, Macdonald-Laurier Institute, Fraser Institute, Straight Arrow News, and OpenCanada. A decent lineup, but heavy on commentary and light on diversity of perspective.
Choosing the New Feeds
For a news node that's going to sit at a Maker Faire booth and be browsed by strangers, editorial range matters. I added:
The Tyee —
https://thetyee.ca/rss2.xml— independent BC outlet, solid counterweight to the market-oriented think tanksPolicy Options (IRPP) —
https://policyoptions.irpp.org/feed/— non-partisan policy magazineWestern Standard —
https://www.westernstandard.news/feed— Alberta-based, fits the local flavourThe Hub —
https://thehub.ca/feed/— newer centre-right outletFinancial Post —
https://feeds.feedburner.com/FP_TopStories— business and markets
Five more feeds, broader coverage. The mix feels less like "opinion journalism only" and more like what a thoughtful person's news diet might actually look like.
Step 1: Adding Feeds to the Fetcher
The fetcher is a Python script that pulls each RSS URL every 30 minutes via cron, parses the XML, and writes a trimmed plaintext cache file for each feed. Adding a new source is just adding an entry to the FEEDS dict:
Five new entries, save the file, run it manually to populate the cache:
Expected output for each feed is a line like [OK] The Tyee - 15 items. Anything that 404s or times out will print [ERR] but won't crash the rest — each feed is wrapped in its own try/except.
So far, so good. All five new feeds fetched successfully on the first run.
Step 2: Making Feed Pages
Each feed has a corresponding .mu page script that reads the cache file and renders it as Micron markup (NomadNet's lightweight markup language). The existing scripts are all variants of the same template — same ASCII header, same parser, same layout. Only two things change per feed: the cache filename and the display name.
Rather than copy-pasting five times and risking a typo, I generated them programmatically. Here's the template structure:
The #!c=0 on line 2 is a NomadNet directive that tells the server not to cache this page — important for anything dynamic, because NomadNet will otherwise happily serve stale output forever.
Five generated scripts later, scp them over:
Mark them executable:
Step 3: Linking Everything on the Index
The index page got a few new sections:
Simple enough. scp it over. Refresh in MeshChat.
And that's where things broke.
The Troubleshooting Arc
Problem 1: The Index Hangs
The news node page sat on "loading" forever. Not an error, not a timeout — just a spinner.
First thing I checked was whether NomadNet was even running:
Active. Fine. Then I tried running the index page script directly to see if it was crashing:
And got:
There it was. Python was trying to parse index.mu as a script, choking on the block characters in the ASCII header.
The cause: earlier in the session, I'd run chmod +x *.mu — which marked everything executable, including the static Micron files that were never meant to be scripts. NomadNet saw the executable bit, tried to run index.mu, and just hung when Python threw a syntax error on the first line.
Fix: strip the executable bit from static pages only.
Check which files are scripts versus static with a quick head -1:
Anything starting with #!/usr/bin/env python3 is a real script and needs +x. Anything starting with Micron markup (`c, `B, backticks generally) is static and needs no executable bit at all.
Index loads, feeds load. Onward.
Problem 2: The New Feeds Still Hang
The index rendered fine. But clicking any of the five new feed links still just spun forever. Meanwhile the guestbook link did nothing too.
When the scripts work in a terminal but not when served, the issue is almost always one of three things: permissions, line endings, or the server doesn't know the file exists.
First check — ls -la:
Look closely at the permission strings. The working file is 755. The new file is 711 — owner read has been stripped. Executable, yes, but the group and other bits only have execute without read. When NomadNet tries to open the file to serve it, the read fails silently.
That's a weird mode to get from scp. My best guess is it came from whatever umask or file mode was set on the source machine when the files were downloaded from the browser — Mac Safari sometimes applies unusual permission bits to downloaded files. Fix:
First command normalizes everything to readable and executable. Second strips the bit off the static index again (because I caught that one with the broad chmod and didn't want to repeat Problem 1).
Pages still hung.
Problem 3: The One That Got Me
At this point the scripts were executable, readable, and ran cleanly from the terminal. Output looked correct. The index linked to them. NomadNet was running. And yet the pages still hung when clicked in MeshChat.
I thought it was caching. I cleared the NomadNet page cache, the Reticulum resource cache, restarted the service. Nothing changed.
What finally cracked it was looking at the actual process list:
Two processes came back:
Two NomadNet instances. One for my main node, one for the news node — two separate config directories, two separate services, two separate PIDs.
And every single sudo systemctl restart nomadnet I'd run that evening had been restarting the wrong one.
The news node service is named nomadnet_news.service. It hadn't been restarted since the previous day. It was serving pages from whatever state it had cached in memory from before any of my changes.
Pages loaded immediately.
The Lessons
I came out of this session with three pieces of working memory that would have saved me hours:
Executable bits are load-bearing. In NomadNet, the executable bit is how the server distinguishes a script (run this, capture stdout) from a static page (just serve it).
chmod +x *.mubreaks anything that's static markup. Checkhead -1before bulk-chmod'ing anything.scp can do weird things to permissions. If a file works when you run it directly but not when something else tries to read it,
ls -lafirst.-rwx--x--xis not the same as-rwxr-xr-x, and the difference matters the moment a different process tries to open the file.If you run multiple instances of the same service, name them clearly and remember both exist. When I set this up, I called the news node
nomadnet_news.servicethinking it was obvious. Six weeks later, muscle memory had me typingsystemctl restart nomadnet— the Poutine Node service — and wondering why nothing changed. Consider adding both service names to a cheat sheet in your home directory:
What's Next
The news node now has 14 feeds, a working guestbook, and a layout that doesn't feel thin. Next up: figuring out why Rebel News keeps 404'ing on its RSS endpoint (the URL may have moved), and eventually turning the whole setup into something that can run offline-first when the internet gets flaky.
If you're building your own node, the meta-lesson is that the ecosystem is small enough that most problems aren't documented anywhere — you learn by doing and by breaking things. Which, honestly, is most of the fun.

