7,627 bytes · last changed 2026-10-27 in No seeds: pyrtan.com is GitHub Pages and a hub list, nothing more
Running the demo on OpenMediaVault
Two ways. Docker through OMV's Compose plugin is the one that fits the box; the systemd unit is for running it bare on the host.
Replace <nas-ip> below with your NAS's address on the LAN. Both end with the demo page at http://<nas-ip>:8700/ (Docker) or http://<nas-ip>:8701/ (systemd): a sentence box, the answer, the output, and the trace, plus the list of everything the swarm can answer.
Docker, through the Compose plugin
The stack in openmediavault/docker-compose.yml runs four containers: a hub on port 8700, two example services (news: Hacker News and GitHub; weather: Open-Meteo and site status) that register with the hub over the compose network, and mine, which serves a folder of your own trees from a shared folder and reloads them when you edit a file.
- Install the plugin, if it is not there: in OMV, *System › Plugins*, install
openmediavault-compose(needs omv-extras on OMV 6/7). It installs Docker. - Pick a shared folder for the files. Say the share is
appdataon the data disk; on the host that is a path like/srv/dev-disk-by-uuid-…/appdata. Find it under *Storage › Shared Folders*, or runls /srvover SSH. - Clone the repository there, over SSH on the NAS:
`` cd /srv/dev-disk-by-uuid-…/appdata git clone https://github.com/b-i-t-8/pyrtan.git mkdir -p pyrtan/trees ``
pyrtan/trees is the folder mine watches. Copy an example in to start: cp pyrtan/examples/site-status/site-status.toml pyrtan/trees/.
- Add the stack: *Services › Compose › Files › Add*. Name it
pyrtan, paste the compose file, and replace bothCHANGE-MEpaths with the path from step 2 (the checkout at…/appdata/pyrtan, the trees folder at…/appdata/pyrtan/trees). Save. - Up: select the file, press *Up*. The first time it builds the image on the NAS, which takes a minute or two.
- Open
http://<nas-ip>:8700/. The page should say4/4 peers upwithin ten seconds (the hub polls every ten). Try "top 3 stories on hacker news".
To update: git pull in the checkout, then *Down* and *Up* with *Build* ticked (or docker compose -p pyrtan up -d --build over SSH). To check a tree you dropped into trees/ before it is live: docker compose -p pyrtan exec mine pyrtan check /trees. To see gaps the demo has met: http://<nas-ip>:8700/gaps.
The services do not expose their own ports; everything goes through the hub on 8700. As shipped nothing authenticates: this is a demo for your LAN. Before forwarding 8700 anywhere, set a token: uncomment PYRTAN_TOKEN in the compose file's x-pyrtan block (every container reads it, so the swarm shares it), put TLS in front, and open https://pyrtan.com/console/ with the hub's URL and the token; the compose file already passes --cors https://pyrtan.com to the hub. The mine service keeps its gaps in trees/.gaps.jsonl so they survive a restart; the container runs as uid 10001, so chown 10001 trees (or chmod o+w trees) on the NAS, else the gaps stay in memory and /health reports gaps_file_error.
To let this swarm answer for another (a friend's hub, or any hub in the directory), give the hub a public address, with a tunnel that opens no port (cloudflared tunnel --url http://127.0.0.1:8700, or tailscale funnel 8700) or a port forward behind TLS, then set in the compose file's environment PYRTAN_ADVERTISE (that address), PYRTAN_JOIN: default and PYRTAN_LISTED: "true", with PYRTAN_TOKEN set and PYRTAN_EGRESS: public (hubs that follow yours may ask it to run trees, and any would let them point a tree at your LAN). The hub then joins the default network pyrtan.com publishes, follows the first hub in it that answers (from pyrtan.com's hub list, then its snapshot), routes to it, and is listed in the directory every hub gossips; pyrtan follow URL --at <your hub> --token … follows any other. For a network of your own, pyrtan network new home --at <your hub> --token … prints an invite; a friend's hub joins with pyrtan join <invite>, or by adding it to its PYRTAN_JOIN. Following is one-directional trust: your hub routes to the hubs you follow, and hubs that follow yours may ask it. See ../_meta/federation.md.
Your phone is part of it too: open the hub's console on the phone, enter the token, and in the Member tab join; the phone then keeps the hub's blobs and runs the hub's trees when asked. Trees meant for the phones rather than the NAS (they need the internet the NAS is kept from, or the network the person is on) go in a handout directory next to trees, with PYRTAN_HANDOUT: /handout in the compose file.
Trees that read the box (a file node over /var/log, a shell node running df, a serial node on a USB modem) see the container, not the NAS: mount the paths and pass the devices through in the compose file (there are commented lines for it), and the tree's own paths, commands and devices lists decide what it may touch. Trees that drive a browser need Playwright, which the image does not carry; run those from a checkout with uv sync --extra browser.
Without the plugin
Over SSH, from the checkout:
cd /srv/dev-disk-by-uuid-…/appdata/pyrtan cp deploy/openmediavault/docker-compose.yml . sed -i "s#/srv/dev-disk-by-uuid-CHANGE-ME/pyrtan#$PWD#g" docker-compose.yml docker compose up -d --build
One container only
If you want a single process and no swarm:
docker build -t pyrtan:local . docker run -d --name pyrtan -p 8701:8701 --restart unless-stopped pyrtan:local
It serves the examples at http://<nas-ip>:8701/.
Bare, with systemd
For a host with no Docker. Pyrtan needs Python 3.12+ and OMV 7 ships 3.11, so uv fetches an interpreter. Everything is installed under /opt/pyrtan and run by a system user with no home directory.
curl -LsSf https://astral.sh/uv/install.sh | sudo env UV_INSTALL_DIR=/usr/local/bin sh sudo env UV_TOOL_DIR=/opt/pyrtan/tools UV_TOOL_BIN_DIR=/opt/pyrtan/bin \ UV_PYTHON_INSTALL_DIR=/opt/pyrtan/python \ uv tool install --python 3.12 git+https://github.com/b-i-t-8/pyrtan sudo useradd --system --no-create-home --shell /usr/sbin/nologin pyrtan sudo mkdir -p /var/lib/pyrtan/trees sudo cp -r /path/to/pyrtan/examples/* /var/lib/pyrtan/trees/ sudo chown -R pyrtan:pyrtan /var/lib/pyrtan sudo cp deploy/systemd/pyrtan-demo.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now pyrtan-demo curl http://127.0.0.1:8701/health
The unit runs one process holding every tree in /var/lib/pyrtan/trees on port 8701, hardened to read its trees and reach the network and nothing else. Logs: journalctl -u pyrtan-demo -f. To update, run the same uv tool install line with --force, then sudo systemctl restart pyrtan-demo.
What the demo shows
- A sentence routed by the hub to the service whose tree answers it, and the trace of every observation and decision on the way back.
- A tree in one service delegating to a tree in another: ask "top 3 stories on hacker news" and watch
hacker-newson news callhacker-news-itemthree times. - Hot reload: edit a file in
trees/from the share and ask again. - Gaps: a decision that met a value no branch covers, at
/gaps. - Unanswered: ask something no tree answers and the page shows the tree that would, with the command that writes it; the list is at
/unanswered. - Health as one field:
/peerson the hub shows each service'slink.