ANetBBS Changelog — Beta History (pre-v1.0.0)
Archived history of every internal beta build number (v1.0a1.1 through
v1.0b2.239) from before ANetBBS's v1.0.0 full release in August 2026.
Preserved as-is — a real record of what changed at each step during
development — but split out of the main CHANGELOG.md
once it passed 6,000 lines, to keep that file scannable for the
v1.0.0-onward history that actually matters day to day. Newest-first,
same as the main changelog.
v1.0b2.118 — MRC bridge crash fix + protocol correctness; new terminal/web MRC features (July 2026)
- FIX: MRC bridge crash on a WebSocket-disconnect race (16 unguarded response sends) — confirmed via a live production crash-loop; very likely the actual cause of MRC trust unexpectedly lapsing.
- FIX: MRC bridge now handles the hub's version-enforcement messages instead of silently reconnect-looping or leaking them into chat, plus a few other protocol-correctness fixes (graceful shutdown notice, accurate capability list).
- NEW: MRC terminal client —
/set defaultroom,/set twitfilter,/set clockformat,/welcome,/changes, plus/q/b/clsaliases. - NEW: MRC web client — scrolling ticker/banner, latency display, clock, and timezone-offset setting (all previously missing entirely from the web client).
v1.0b2.117 — Full docs/wiki accuracy pass ahead of Aug 1; exec-door drop-file fix; new terminal gallery viewer (July 2026)
- DOCS: Full docs/wiki accuracy pass ahead of the Aug 1 release — every claim checked against the actual code; fixed dozens of stale, wrong, or fabricated entries across
docs/and the wiki (including invented MRC hub details, dead admin links, and wrong dosemu2/DOSBox drive-letter guidance). - FIX: Exec-door drop-file generation (the
execmenu action) was silently broken since it shipped — a wrong function name meant every configured drop file failed quietly. Now actually works. - NEW: Terminal image gallery viewer (
anet-gallery.sh) — documented for a while but never built. Now generated automatically by install.sh/update.sh. - FIX: RSS feed access-level restrictions weren't enforced on two of four web routes — a restricted feed was directly reachable even though hidden from the river/index.
v1.0b2.116 — install.sh: FTPS cert-renewal fix; expanded troubleshooting docs (July 2026)
- Fixed a gap where FTPS certificate permissions could silently break after the first Let's Encrypt renewal on a fresh install (only
update.shhad the fix before). - Added several new troubleshooting entries to
docs/INSTALL.md/docs/INSTALL-PI.md/docs/06-echomail.mdcovering common newer-sysop pain points (SELinux blocking nginx, MRC config path overrides, Pi/tmpconfusion, FTPS renewal, BinkP poll intervals).
v1.0b2.115 — install.sh/update.sh: better distro detection, dependency update checks, extra verification (July 2026)
- Improved Linux distro detection and package handling in
install.sh/update.sh(openSUSE, dosemu2 support, clearer firewall guidance where automation isn't available). - Added automated dependency update/security checks (
pip-audit+ Dependabot) so outdated or vulnerable packages get flagged instead of going unnoticed.
v1.0b2.114 — Echomail poller: back off after a failed poll instead of retrying every tick (July 2026)
- FIX:
EchomailNetwork.last_poll_atwas only ever stamped after a
successful poll — a failed poll left it untouched, so the poller's
own "is this network due yet?" check saw it as still due on the very
next scheduler tick (60 seconds later) and retried immediately,
regardless of the network's configured poll interval. Reported via
an external GitHub issue: some upstream hubs started blocking the
repeated rapid-fire attempts, which only made the original failure
harder to recover from. Fixed by moving thelast_poll_atstamp into
_do_poll()'sfinallyblock so it's set exactly once per attempt,
success or failure — a failed poll now waits out the normal interval
before retrying, same as a successful one. (Longer-term, a real
configurable back-off curve — short retry, growing toward the full
interval — would degrade more gracefully than an all-or-nothing wait;
noted as a possible follow-up, not needed to fix the reported
problem.) 3 new tests intests/test_poller_backoff_on_failure.py.
v1.0b2.113 — Multi-hub-identity: join form, nodelist, QWK hub, and BinkP auth all made identity-aware (July 2026)
Completes the multi-hub-identity feature (Phases 2-7; Phases 0-1 —
the HubIdentity model, its hub_identity_id foreign keys, and the
admin CRUD — shipped in v1.0b2.92-93). Every install still has exactly
one hub identity by default and sees zero behavior change — this
phase is about making the FK columns that already existed actually do
something, for a sysop who wants one install to operate as the
designated hub for more than one real echomail/QWK network at once.
- FEATURE: the public "apply to join this network" form now has a
second URL,/join/<slug>/, for any non-default hub identity
(/join/alone still means the default identity, unchanged) — each
identity gets its own config, infopack storage, and application
queue. Approving an application now stamps the created BinkP/QWK
node with the identity the application was actually submitted to,
and BinkP node auto-numbering is scoped per identity so two
identities reusing the same zone:net (unusual, but not disallowed)
don't collide on each other's node numbers. - FEATURE: nodelist generation (
generate_nodelist()and its two
callers — the public HTTP route and the scheduled file-echo
publisher) is now identity-aware: a non-default identity's nodelist
lists only its own downstream nodes and uses its own zone/net/hub-node/
name instead of always the hardcoded defaults. The scheduled
file-echo auto-publish stays default-identity-only (no per-identity
file-area assignment exists yet); every identity's nodelist is still
directly downloadable via its own URL. - FEATURE: QWK hub packet generation collapses three previously
independent, undocumented "what's our hub system ID" resolvers (the
web download/upload routes, the FTP-side packet writer, and the FTP
login handler) into one shared, identity-aware resolver — a node on
a non-default identity gets its own<HUBID>.QWK/<HUBID>.MSG
filenames instead of always the install's single global
QWK_HUB_ID. QWK/BinkP node admin forms grow a Hub Identity picker,
node/request lists grow an Identity column (only shown once a second
identity actually exists), and cross-identity QWK area subscriptions
are refused. - FIX (real bug, not just a missing feature): the inbound BinkP
listener's auth/matching queries were always safe (address-uniqueness-
backed, unscoped by identity on purpose — adding a stricter filter
here was the one thing this whole phase deliberately avoided, to
never risk locking out a real peer mid-migration), but outbound mail
to a downstream node was always stamped with the single process-wide
BINKP_OUR_ADDRESS, regardless of which hub identity that node
actually belonged to. A peer on a second hub identity got outbound
mail stamped with the default identity's AKA. Now resolves the
matched node's own hub identity (via its BinkP network's own
configured address, or a zone:net/hub-node reconstruction) and fails
open — logs a warning and falls back to the previous default-address
behavior — if that identity has nothing configured, rather than ever
rejecting an otherwise-valid, already-password-verified connection. - DOCS: terminal QWK-application wizard screens no longer hardcode a
literal hub hostname/QWK-hub-ID in their help text — now reflect
whateverREGISTRY_URL/QWK_HUB_IDthis particular install actually
has configured. Hub Management dashboard grows a per-identity
node-count breakdown once a second identity exists. Terminal QWK
Node Requests screen labels itself "(default hub identity only)"
once a second identity exists, since the terminal stays
default-identity-only by design (same precedent as BinkP/QWK peer
node CRUD already being web-only) — extra identities are managed
through the web admin. New wiki section under Sysop Control Panel
explaining the feature for other sysops. - 786 passed, 2 skipped total — full suite, including 47 new tests
across five new test files covering each phase (join/nodelist/QWK/
BinkP/admin-UI), plus a from-scratch characterization harness for
the BinkP listener's auth path, which had zero real test coverage
before this (the two pre-existing BinkP server test files short-
circuit before reaching it, and their query fakes silently ignored
filter kwargs entirely).
v1.0b2.112 — MRC bridge: root cause was a config-path mismatch, not code — plus pipe colors restored (July 2026)
- RESOLVED: the BBS-info-fields saga (v1.0b2.107-111) is now fully confirmed working live, and the real root cause turns out to have never been a code bug at all. One sysop's install had its bridge service pointed at a non-default
MRC_BRIDGE_CONFIGpath (in aconfig/subdirectory) via an environment variable — a leftover from running his own MRC bridge for two years before ANetBBS was built around it. He'd been editing the defaultmrc/bridge/config.jsonpath the entire time, a file his own service never actually reads. Every fix shipped in v1.0b2.107-111 (thesend_info_fields()/BBSMETAadditions, the field-order correction, the stale-session dedup) was correct and necessary — none of them had anything to verify against until this was found via the raw packet logging added in v1.0b2.111 finally showing default/empty values despite a correctly-edited file. No install/deploy tooling changes needed — this is a one-off historical artifact of that specific install, not something fresh installs or the Pi3 hit. - FIX: reverted the pipe-color-code stripping added for
send_info_fields()/send_bbsmeta()in v1.0b2.109/.110. That stripping was reasoned only from the reference client's own plain-textMRCBBS.DATexample having no color codes in it — the sysop pushed back with two years of hands-on experience with this exact protocol confirming pipe colors in the BBS name/telnet/ssh/website/sysop/description fields are expected and supported, same as the BBS name field itself (which was never stripped). These values now go out exactly as configured, colors included. The unrelated pipe-code fix in_extract_identified_handle(comparing the hub's identify-success reply against a plain-text stored handle, not anything ever displayed) is untouched and still correct. - 739 passed total (2 existing tests updated to assert colors are preserved, not stripped).
v1.0b2.111 — MRC bridge: fixed duplicate self-heal on stale sessions + raw packet logging for BBS-info (July 2026)
- FIX: once v1.0b2.109's pipe-code fix made
/identifyactually get recognized, a single/identifystarted replaying the join once per matching database session record for that handle, not once per live connection — and a hardsystemctl restart(used repeatedly during this same live-troubleshooting session) doesn't run the graceful WebSocket-close cleanup path, so stale records for the same handle piled up across restarts. Reported live as MOTD/CHATTERS showing up 4 times after a single/identify. Fixed: the self-heal now only acts on sessions with a genuinely live WebSocket connection right now, skipping any DB record with no matching live connection. - DIAGNOSTIC: v1.0b2.110's field-order fix +
BBSMETAaddition, while verified byte-for-byte against the reference client, still didn't make the sysop's BBS-directory entry populate live — the real root cause remains unconfirmed after two careful, reference-checked attempts. Rather than guess a third time, added raw outgoing-packet logging (logger.info) for bothsend_bbsmeta()andsend_info_fields()so the exact bytes leaving the bridge can be inspected directly. - 1 new test (739 passed total).
v1.0b2.110 — MRC bridge: fixed the BBS-info packet field order + added missing BBSMETA (July 2026)
- FIX: v1.0b2.107/.109's pipe-code stripping alone still wasn't enough — the sysop's directory entry stayed blank even with a fully clean, correctly-configured
config.json. Re-checking the reference client'smrc_send_info_fields()byte-for-byte against its owncreate_packet-equivalent call turned up a genuine field-order bug: the reference client sends these specific "broadcast to everyone" packets with the literal"ALL"and the epoch timestamp in the opposite wire positions from every other message in this protocol (room-scoped sends put the timestamp before the room; this one puts"ALL"before the timestamp) — a quirk (possibly an accidental one even in the reference client itself, inconsistent with its ownBBSMETAcall a few lines above) that real hubs apparently expect literally. ANetBBS'ssend_info_fields()used the "logically correct" order instead, which put a garbage timestamp string where the hub expects"ALL"— with nothing valid to route the broadcast by, the hub had no reason to store it, blank directory entry despite zero errors anywhere in the pipeline. Field order now matches the reference client's actual sent bytes exactly. - FEATURE: also added
send_bbsmeta()(BBSMETA: SecLevel(100) Sysop(...)), sent once right before the info fields — matching the reference client's own connect sequence exactly. ANetBBS never sent this at all; added alongside the field-order fix since both were live-suspected contributors and couldn't be isolated without another full live round-trip. - 4 new tests (738 passed total), plus an existing field-order test rewritten to check the raw wire bytes positionally instead of through the higher-level parser, since this packet type's field order doesn't match the parser's normal semantic labels.
v1.0b2.109 — MRC bridge: real root cause found — pipe color codes broke identify detection (July 2026)
- FIX (significant): the v1.0b2.108 diagnostic logging paid off immediately — live capture on the production server showed the hub's actual identify-success reply is
"...Successfully identified, welcome back |10StingRay|07"._extract_identified_handle(pre-dating this session, never previously verified against a real hub reply) never stripped the|10/|07pipe-color codes wrapped around the handle, so it returned the literal string"|10StingRay|07"instead of"StingRay"— which then never matched any real session's plain-text handle. This silently broke both the pre-existing strict-mode auto-join-after-identify and the newer v1.0b2.106 default-mode self-heal, on every install, the entire time — it just never surfaced before because every prior test (including the ones written for the v1.0b2.106 self-heal fix) used a fabricated clean message with no pipe codes at all. Added_strip_pipe_codes()and applied it in_extract_identified_handlebefore searching. - FIX: applied the same stripping defensively to
send_info_fields()(v1.0b2.107) — a sysop'sbbs_telnet/bbs_ssh/bbs_website/bbs_sysopconfig values copy-pasted from a colorized source (e.g."|15bbs.example.com:2233", a pipe-colored sysop name) now get sent as clean plain text, matching what the reference client actually sends (no color codes at all, sourced straight fromMRCBBS.DAT) and avoiding the same class of hub-side parsing risk just confirmed for the identify reply. (Note: this specific behavior was reverted in v1.0b2.112 — see above — once it turned out pipe colors in these fields are actually expected/supported, contrary to the assumption made here.) - 7 new tests (734 passed total), including two that reproduce the exact real captured wire text end-to-end and confirm the self-heal now actually fires against it.
v1.0b2.108 — MRC bridge: diagnostic logging for identify/join-rejection wire text (July 2026)
- DIAGNOSTIC: live testing on the production server (
bbs.a-net.fyi) after v1.0b2.106/.107 found the identify self-heal still not taking effect there, despite the deployed code, config, and service all being confirmed correct with zero errors in the logs — pointing at the detection logic itself rather than the deployment. The"successfully identified"/"welcome back "substring match this relies on (_on_upstream_packet,_extract_identified_handle) predates this session with no confirmed verification against a real hub reply. Added a temporary log line (logger.info) that captures the raw text of any incoming SERVER message shaped like an identify or join-rejection notice, so the actual wording can be confirmed against a live capture instead of assumed. No behavior change; safe to remove once the real wording is confirmed and, if needed, the detection updated to match.
v1.0b2.107 — MRC bridge: broadcast our own BBS info (description/telnet/ssh/website/sysop) (July 2026)
- FIX: reported by the sysop — "the BBS info that is not shown with ANetBBS but is with the mystic mrc client... this is where you look at the bbs info and see their web, telnet, ssh, description." Verified against the real reference C client (
anetmrc_v1.3.9/src/helper_protocol.c'smrc_send_info_fields): every MRC client is expected to broadcast its ownINFODSC/INFOTEL/INFOSSH/INFOWEB/INFOSYSfields once per connect (sourced there fromMRCBBS.DAT), which the hub aggregates into a per-BBS directory other callers browse via/bbses(to find a BBS's list number) then/info <n>(to view that entry). ANetBBS's bridge never sent any of these fields at all, so ANetBBS's own entry was always blank to anyone looking it up from another client — including ones (like the Mystic Python client) that actually surface this. Addedsend_info_fields(), called once right after the existing capabilities announcement on every connect; five new optional config keys (bbs_description,bbs_telnet,bbs_ssh,bbs_website,bbs_sysop) in the bridge'sconfig.json, each field only sent if actually configured (matching the reference client's own per-field guards — an unset field is omitted, not sent blank). The reverse direction — ANetBBS's own callers looking up other BBSes via/bbses+/info <n>— already worked with no changes needed: both are, and always were, generic passthrough commands on both clients (confirmed by inspecting the existing terminal/info//bbseshandling and web's catch-all slash-command forwarding). - 5 new tests, including one that parses the actual generated packet and checks its wire fields against the reference client's own field order.
v1.0b2.106 — MRC bridge: registered handles now self-heal after /identify (July 2026)
- FIX (significant): found live on the Pi immediately after deploying v1.0b2.105 (a registered handle, "StingRay") — the v1.0b2.103 default-join fix optimistically marks a session
in_room=Truethe moment it connects, but for a handle that's actually registered and not yet identified, the real hub silently rejects that join ("Cannot join ROOM, please IDENTIFY to use this handle"). The bridge kept believing it was joined and went on forwarding chat sends anyway, which the hub then bounced back with "No route to a room from your user, /join a room first." — the caller looked joined locally but couldn't actually chat, with no way out except realizing they had to/identifyand separately/joinagain by hand. Fixed: a successful/identifynow always re-sends the room join (previously this only happened in the opt-in strictidentify_required_mode=Truepath), so registered handles self-heal automatically the moment they identify — no follow-up/joinneeded. Unregistered/casual handles are unaffected (their optimistic join already succeeds at the hub and was never broken). - 2 new tests, including one that captures the exact reported sequence (join → hub-side rejection scenario → identify → confirms the join packets actually get re-sent, not just a local flag flip).
v1.0b2.105 — MRC Phase G: full regression + cross-client parity audit (July 2026)
Closes out the MRC feature-parity rework (Phases A-G, v1.0b2.94-105). Full automated regression suite re-run clean (720 passed, 2 skipped, 0 failures) plus a systematic code-level audit comparing every feature added in Phases A-F against both clients, specifically looking for the kind of one-sided fix that's already happened twice this rework (a bug found and fixed on one client without checking whether the same bug existed on the other). Found and fixed two real gaps, both in anetbbs/templates/mrc/index.html, both additive/non-breaking:
- FIX: the web client never got the STATS: noise fix from v1.0b2.102 (terminal-only at the time) — raw
STATS:175 15 39 2 170 34.0-shaped lines were still showing up inline on web with nothing to silence them. Added the same suppression terminal already has (SUPPRESS_STATS_LINES, defaulting on, matching the existingSUPPRESS_USERLIST_LINES/SUPPRESS_ROOMTOPIC_LINESpattern). - FIX: the structured
userlistWS event added in Phase A (mrc/bridge/main.py's_send_userlist_control) was specifically built so both clients could stop depending on regex-parsing free-text chat lines for the nick-list sidebar — the terminal client migrated to it in Phase B, but web was never updated to listen for it at all, silently left on the older, more fragiletryParseUserListFromServerMessageregex path the whole time. Web now also consumes the structured event directly (case 'userlist':) as a more robust source layered on top of the existing regex fallback, which stays in place unchanged. - No JS test framework exists in this repo (consistent with every prior web-side change in this rework) — both script blocks syntax-checked clean with
node --check.
v1.0b2.104 — MRC: restored the /identify "MRC Trust" notice (non-blocking) on both clients (July 2026)
- FIX: follow-up to v1.0b2.103. Correction from the sysop: the reference client doesn't hide
/identifyentirely — it shows a permanent, non-blocking "Use /identify password for MRC Trust" notice on every connect (confirmed inhelper_protocol.c), and a registered handle's trust status genuinely does lapse after a stretch of weeks on the real network, so periodically re-running/identifystill matters even though chat is never gated on it. v1.0b2.103's fix correctly removed the blocking, but its plain "Joined #room as handle." message dropped the FYI along with it. Restored: the bridge's immediate-join message now reads "Joined #room as handle. Use /identify \<pass> for MRC Trust." (surfaced automatically on web via its existing system-message display), and the terminal client's own local connect greeting — which never read the bridge's message field at all — gained a matching tip line, so both clients now show the same non-blocking reminder. - 1 test updated to match the restored notice (720 passed total).