ANetBBS federation directory (anetbbs.lst)
A small federation registry that lets ANetBBS instances find each other.
Modelled on Synchronet's sbbsimsg.lst but JSON-shaped and built into
the BBS itself (no separate service to run).
This page covers two related but distinct things:
- The
anetbbs.lstpeer-directory/registry described below — a
lightweight "who else is running ANetBBS" list. - ANotherNetwork, ANetBBS's bundled echomail/QWK network — see
ANotherNetwork further down. Both roles are
gated by the sameREGISTRY_MODE_ENABLEDflag; see
REGISTRY_MODE_ENABLED gates two roles
before you flip it on.
How it works
One BBS is designated the central hub. Other BBSes (the peers)
register against it on first start, heartbeat daily, and pull the
peer list down into their local inter-BBS directory.
┌──────────────────────────┐
│ central hub │
│ REGISTRY_MODE_ENABLED │
│ │
│ /registry/api/v1/* │
│ /anetbbs.lst (JSON) │
└────────────▲─────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌────┴─────┐ ┌───┴──────┐ ┌────┴─────┐
│ ANetBBS │ │ ANetBBS │ │ ANetBBS │
│ peer 1 │ │ peer 2 │ │ peer 3 │
└──────────┘ └──────────┘ └──────────┘
The current canonical hub is https://bbs.a-net.fyi — set as the
default REGISTRY_URL in config.py. Every fresh ANetBBS install
will pull anetbbs.lst from there on startup.
For peer sysops — getting your BBS listed
Edit /path/to/anetbbs/.env:
# Required
BBS_DOMAIN=bbs.example.org
SYSOP_EMAIL=sysop@example.org
REGISTRY_SELF_REGISTER=true
# Friendly metadata (shown in the public list)
BBS_NAME=Example BBS
SYSOP_NAME=Alice
BBS_LOCATION=Seattle, WA
# Default already points at bbs.a-net.fyi; override only if running
# against a private federation hub
# REGISTRY_URL=https://bbs.a-net.fyi
Restart the web service:
sudo systemctl restart anetbbs-web.service
On the next service start, ANetBBS POSTs your metadata to the hub.
Two acceptance gates before you appear on the public list:
- Email verification. The hub returns a
verify_url. Open
https://yourbbs/admin/registry/selfto see it — click through.
Future plan: the hub will email this URL toSYSOP_EMAILdirectly
once outbound mail support is added. - Sysop approval. After you verify, the hub sysop reviews the
entry athttps://bbs.a-net.fyi/admin/registry/and approves it.
Both gates passed → you appear in https://bbs.a-net.fyi/anetbbs.lst,
every other peer pulls you on their next daily refresh, and your BBS
shows up in their /imsg/directory/ listing with a blue ANetBBS
badge.
For hub sysops — running a federation registry
If you want to run your own federation hub (private network, parallel
registry, etc):
# In .env
REGISTRY_MODE_ENABLED=true
Restart the web service. New endpoints become live:
POST /registry/api/v1/register— peers announce themselvesPOST /registry/api/v1/heartbeat— daily keep-aliveGET /registry/verify/<token>— peer sysop clicks to confirmGET /anetbbs.lst— public JSON list of approved peersGET /registry/api/v1/list— same content, machine-readable URL
A SYSTAT prober runs hourly against every listed peer (UDP/11). After
3 consecutive failures (REGISTRY_PROBE_FAILURE_THRESHOLD), the peer
is dropped from the public list — it re-lists automatically on
recovery. Threshold + interval are configurable in .env.
Approve / reject / edit / delete via the admin UI at
/admin/registry/. Entries flow through these states:
awaiting verify— peer registered, hasn't clicked the email
token yetawaiting approval— verified by peer, waiting for you to OK themlisted— public onanetbbs.lstdelisted— was listed, you rejected (row kept, can re-list)
JSON format
{
"version": 1,
"updated": "2026-05-15T14:00:00Z",
"maintainer": "StingRay",
"bbses": [
{
"host": "bbs.a-net.fyi",
"msp_port": 18,
"systat_port": 11,
"name": "A-Net BBS",
"sysop": "StingRay",
"location": "Woodstock, GA",
"software": "ANetBBS",
"software_version": "v1.0a2.216",
"notes": "",
"since": "2026-05-15",
"last_seen": "2026-05-15T14:00:00Z"
}
]
}
The bbses array is empty until the hub has at least one verified +
approved + active peer. Stale entries (no heartbeat for
REGISTRY_HEARTBEAT_STALE_HOURS, default 48) are filtered out
automatically.
Rate limits
The hub applies coarse rate limits to keep spam attempts cheap:
- register: 5s per host floor, 60 calls/hour per source IP
- heartbeat: 10s per host floor, 100 calls/hour per source IP
Hitting the floor returns HTTP 429. Daily heartbeat from a sane peer
never approaches the limit.
Configuration reference
All env vars are read at service start. Defaults are sensible for the
"peer pulling from bbs.a-net.fyi" case — most sysops only need to set
REGISTRY_SELF_REGISTER=true + the BBS/sysop metadata.
| Variable | Default | Notes |
|---|---|---|
REGISTRY_URL |
https://bbs.a-net.fyi |
where to register + pull |
REGISTRY_SELF_REGISTER |
false |
opt in to publishing your BBS |
REGISTRY_MODE_ENABLED |
false |
THIS install is a hub |
BBS_DOMAIN |
(empty) |
your public hostname (required for self-reg) |
SYSOP_EMAIL |
(empty) |
required for self-reg |
SYSOP_NAME |
(empty) |
friendly display |
BBS_LOCATION |
(empty) |
friendly display |
REGISTRY_HEARTBEAT_INTERVAL_SEC |
86400 |
client heartbeat cadence (1 day) |
REGISTRY_HEARTBEAT_STALE_HOURS |
48 |
hub: drop entries quiet this long |
REGISTRY_PROBE_INTERVAL_SEC |
3600 |
hub: SYSTAT probe cadence (1 hour) |
REGISTRY_PROBE_FAILURE_THRESHOLD |
3 |
hub: probe-fails before delist |
ANETBBS_DIRECTORY_REFRESH_SEC |
86400 |
peer pull cadence (1 day) |
ANotherNetwork
Separate from the anetbbs.lst peer directory above, ANotherNetwork
is a real, pre-configured echomail/QWK network that ships with every
ANetBBS install — the same way Dove-Net ships with a fresh Synchronet
install. It's seeded automatically (anetbbs/web_app.py, the
"Seed ANotherNetwork" block) and needs no setup to exist, only to be
activated:
- 26 message echo areas (tags
ANN.GENERAL,ANN.BBS,
ANN.ANETBBS,ANN.DOORS,ANN.SYSOP, etc.) spanning General,
Technology, BBS Scene, Retro, Hobby, Trading, Data, SysOp, and Test
categories. - 9 file-echo areas (tags
ANN.FILES.NODELIST,ANN.FILES.BBSSOFT,
ANN.FILES.DOORS,ANN.FILES.EBOOKS,ANN.FILES.LINUX,
ANN.FILES.RETRO,ANN.FILES.ANSIART,ANN.FILES.INFOPACK,
ANN.FILES.TEST) distributed via TIC, same as any other file echo. - Two
EchomailNetworkrows are seeded — one BinkP, one QWK/FTP —
both inactive and unsubscribed by default. All 26 message areas
and all 9 file areas are shared between the two transports via the
hub tosser, so a sysop can join over whichever transport suits them.
To actually join, apply for a node at bbs.a-net.fyi and fill in the
node address/packet-id + password on the seeded network row(s) at
/admin/echomail/, then activate + subscribe the areas you want.
ftn_domain — fixing the BinkP address suffix
EchomailNetwork.ftn_domain (anetbbs/models.py) is an optional,
≤8-character override for the BinkP qualified-address domain suffix
(the part after @ in 1200:1/1@domain). Per FSP-1028 that suffix
must be ≤8 chars and [a-z0-9_~-]+; without an override the poller
derives it from the network's name field instead, which is why
"ANotherNetwork" on its own would otherwise truncate to the awkward
anothern. The seeded ANotherNetwork BinkP row sets ftn_domain=anet
to sidestep this. It's editable per-network at /admin/echomail/ —
the "Domain suffix override (addr@domain, max 8 chars, optional)"
field on the network edit form (anetbbs/web/echomail_admin.py).
Leave it blank on other networks to keep the old name-derived behavior.
Hub Management panel
The install designated as the ANotherNetwork hub gets an extra admin
section at /admin/echomail/hub/ (anetbbs/web/hub_admin.py) —
404s entirely on any install that isn't REGISTRY_MODE_ENABLED, so
peer installs never see it. It covers:
- BinkP node management — CRUD for downstream
BinkPNoderows
(FTN address, session password, sysop/system/location metadata for
the nodelist, per-node subscribe/catch-up/flush-queue actions). - QWK node management — CRUD for downstream
QWKNoderows (packet
ID, download password, subscribe/reset-high-water-mark, and a
Preview action that builds a QWK packet on demand without
marking anything as sent — useful for testing a node's setup without
affecting their real next download). - Hold queue — messages held back from distribution pending review.
- Node-request approval — the queue of sysops applying for a QWK
node via the terminal wizard (anetbbs/features/bbs_ui.py's
_apply_qwk_node(), submittingQWKNodeRequestrows); approve/deny
at/admin/echomail/hub/qwk/requests. There has never been a BinkP
equivalent of this wizard — BinkP nodes are only ever created by hand
at/admin/echomail/or via the public join form below. (This queue
used to be visible on every install, which meant a sysop applying
for a node from any ANetBBS install had their request land in that
install's own local queue instead of the real hub's — fixed by
gating this whole blueprint to the hub install only.) - Public join form — a separate, newer way in: a public page at
/join/(anetbbs/web/network_join.py), reachable with no login at
all, where anyone can apply to join this hub's network. One generic
form covers both transports — a BinkP address/crash-or-hold section
and a QWK packet-ID section, either of which can be left blank as
long as at least one is filled in. There are no password fields
anywhere on the form; like the QWK terminal wizard above, real
credentials are always hub-generated at approval time, never
applicant-supplied. Above the form, the applicant reads the rules
text pulled from the sysop's uploaded infopack (see the Join
Form tab below) and must check a box confirming they read it
before submitting. The route 404s until bothREGISTRY_MODE_ENABLED
and the Join Form tab's own "enabled" checkbox are on — two
independent switches, not one. Applications land in their own review
queue at/admin/echomail/hub/join/requests(separate from the
QWK-only queue above) and every admin gets notified. Approving a
request creates aBinkPNodeand/or aQWKNodedepending on which
transport section(s) the applicant filled in — zero, one, or both —
each with its own independently hub-generated password; if either
address is already taken the whole approval is rejected rather than
partially creating one node. If SMTP relay is configured on this
install, the new credentials are emailed to the applicant
automatically. This isn't ANotherNetwork-specific — any sysop
running their own ANetBBS hub can turn it on for their own network. - Generation & Distribution panel, with four tabs:
- Nodelist — shows the
hub_generate_nodelistscheduled event's
schedule and last-run status, plus a Generate Now button that
fires the same handler synchronously for testing. See
21-scheduled-events.md. - QWK Packets — per-node Preview links (see above).
- TIC / File Distribution — pending/failed counts from the
HatchQueuetable; files uploaded to anyANN.FILES.*area are
queued automatically, no manual step needed, delivery happens on
each peer's next BinkP poll. - Join Form — enable/configure the public
/join/page above.
A checkbox turns it on, with optional "Network Name" and short
"Intro Text" fields displayed above the rules on the public page.
Upload one infopack zip (rules text, area lists, ANSI art, whatever
applicants should see or download) and the system automatically
picks the largest.txtmember inside it as the rules text shown
inline on the public page — a dropdown lists every.txtmember
found in the zip so the sysop can override the pick if it grabbed
the wrong file, without re-uploading. The full zip is also served
as a public, no-login download link.
REGISTRY_MODE_ENABLED gates two roles
One flag, two separate hub roles activate together. Setting
REGISTRY_MODE_ENABLED=true in .env doesn't just turn this install
into the anetbbs.lst federation/peer-directory hub described earlier
on this page — it also turns it into the ANotherNetwork echomail hub:
- Federation/directory role (covered above):
/registry/api/v1/*
endpoints,/anetbbs.lst, the SYSTAT prober,anetbbs/web/registry.py,
anetbbs/msp/probe.py,anetbbs/msp/hub_self_register.py,
anetbbs/web/peer_health.py. - ANotherNetwork echomail hub role: the
/admin/echomail/hub/
blueprint (hub_admin.pyabove), QWK node serving
(anetbbs/web/qwk_hub.py), the hub's own self-registration
(hub_self_register.py), and the weeklyhub_generate_nodelist
scheduled event that only gets seeded on these installs (see
21-scheduled-events.md).
Both checks read the exact same REGISTRY_MODE_ENABLED config value —
there's no way to enable one role without the other today. A sysop
standing up a private federation registry only (no interest in running
an ANotherNetwork-style echomail hub) should know that flipping this
flag on also exposes the Hub Management panel and starts seeding the
nodelist-generation event. Neither does anything harmful on its own —
the echomail hub role is inert until you actually add downstream
BinkP/QWK nodes — but it's worth understanding both switch on together,
not just the directory role this page is mostly about.
Dialing yourself is handled safely
If your install is the ANotherNetwork hub, the same seeded "point at
the hub" network row also exists on your own install (since every
fresh install seeds it) — activating that row on the hub's own BBS
would otherwise mean the poller tries to dial itself. It doesn't fail
or spam errors: anetbbs/echomail/poller.py's _self_referential_reason()
detects this automatically (BinkP: our_address matches hub_address;
QWK: qwk_host matches this BBS's own public host) and the poller
skips the dial-out cleanly.
As of v1.0b2.41 this check runs before _do_poll() creates an
EchomailPollLog row, so a detected self-reference leaves no poll-log
entry at all — not even a "skipped" status row — only a
logger.debug(...) application-log line, invisible at the default
INFO log level. It used to create a real EchomailPollLog row every
time, but since the poller loop re-checks every network once a
minute, that flooded the admin UI's poll log with dozens of identical
entries within about 20 minutes of uptime on a freshly-started hub
(live-caught on Jerry's own install), drowning out real poll activity.
The areas themselves stay visible either way — this only stops the
pointless outbound connection attempt and its logging noise, nothing
else is affected.
Limitations + future work
- No federation between multiple registries yet. v1.0a3 ships one
hub model. Mirror-sync between registries is post-alpha (v1.0b). - No public-key signing of heartbeats. Today an entry is owned by
whoever holds the verify token + has been approved. Cryptographic
ownership is future work. anetbbs.lstexposed at root URL doesn't includecontact_email
— that's intentional, only the hub sysop sees it via the admin UI.
For the day-of-development arc that led to this design, see the
v287.13 → v287.20 entries in CHANGELOG.md.