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 /cls aliases.
  • 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 exec menu 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.sh had the fix before).
  • Added several new troubleshooting entries to docs/INSTALL.md/docs/INSTALL-PI.md/docs/06-echomail.md covering common newer-sysop pain points (SELinux blocking nginx, MRC config path overrides, Pi /tmp confusion, 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_at was 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 the last_poll_at stamp into
    _do_poll()'s finally block 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 in tests/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
    whatever REGISTRY_URL/QWK_HUB_ID this 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_CONFIG path (in a config/ 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 default mrc/bridge/config.json path the entire time, a file his own service never actually reads. Every fix shipped in v1.0b2.107-111 (the send_info_fields()/BBSMETA additions, 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-text MRCBBS.DAT example 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 /identify actually get recognized, a single /identify started replaying the join once per matching database session record for that handle, not once per live connection — and a hard systemctl 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 + BBSMETA addition, 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 both send_bbsmeta() and send_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's mrc_send_info_fields() byte-for-byte against its own create_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 own BBSMETA call a few lines above) that real hubs apparently expect literally. ANetBBS's send_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/|07 pipe-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_handle before searching.
  • FIX: applied the same stripping defensively to send_info_fields() (v1.0b2.107) — a sysop's bbs_telnet/bbs_ssh/bbs_website/bbs_sysop config 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 from MRCBBS.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's mrc_send_info_fields): every MRC client is expected to broadcast its own INFODSC/INFOTEL/INFOSSH/INFOWEB/INFOSYS fields once per connect (sourced there from MRCBBS.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. Added send_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's config.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//bbses handling 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=True the 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 /identify and separately /join again by hand. Fixed: a successful /identify now always re-sends the room join (previously this only happened in the opt-in strict identify_required_mode=True path), so registered handles self-heal automatically the moment they identify — no follow-up /join needed. 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 existing SUPPRESS_USERLIST_LINES/SUPPRESS_ROOMTOPIC_LINES pattern).
  • FIX: the structured userlist WS 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 fragile tryParseUserListFromServerMessage regex 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 /identify entirely — it shows a permanent, non-blocking "Use /identify password for MRC Trust" notice on every connect (confirmed in helper_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 /identify still 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).