ANetBBS Changelog

Versions are internal build numbers. Public releases are tagged
separately. Current release: v1.0b2.33 (July 2026). Full release: August 1 2026.

v1.0a — DOS door bridge wired up (May 2026, internal v279)

v279 — actual DOS door I/O. The previous DOSBox configs wrote
serial1=stdio, which DOSBox-staging 0.80+ silently downgraded to
dummy — meaning DOS door output and keystrokes never reached BBS
users. The whole DOS door path was end-to-end broken on modern DOSBox.

This release wires dos_bridge.py (which has been complete-but-unused
in the tree for a while) into the launch flow:

  • DosBridge picks a free TCP port (5000–5100) and listens.
  • DOSBox config is serial1=nullmodem server:127.0.0.1 port:NNNN transparent:1. DOSBox dials in at boot.
  • New DosBridge.bind_emit(emit_fn) reads bytes off the TCP socket and
    forwards them to the BBS user's terminal via socketio_emit_fn.
  • New DosBridge.write(data) accepts BBS-side keystrokes and pushes
    them onto the TCP socket so DOSBox's COM1 receives them.
  • DoorSession.write() routes through the bridge when present, so the
    rest of the BBS doesn't have to care which transport is in use.
  • DoorSession.close() also stops the bridge so the listener doesn't
    leak.
  • The PTY remains open on door_dos sessions but only as a process-exit
    watcher; door I/O no longer flows through it.
  • DOSBox config also stripped invalid output=null and autolock
    options that staging 0.82.x rejects.

End result: vanilla DOSBox 0.74-3, DOSBox-staging 0.82.x, and DOSBox-X
all work for door_dos games via TCP nullmodem. No special build
required.

v1.0a — door-runtime polish (May 2026, internal v277–v278)

Hot-fixes plus the dosemu2 path, deployed on top of the v276 audit pass.

v278:
- New door_dosemu game type — uses dosemu2 (-dumb stdio) instead of
DOSBox. Better latency, no FOSSIL gymnastics over TCP nullmodem,
apt-packaged via dosemu2 PPA on Ubuntu / native on Debian. Mounts
game dir + per-node temp dir + bundled FOSSIL driver dir, autoexec
loads BNU.COM on COM1 for parity with the DOSBox path so the same
LORDCFG / TWCFG settings work either way.
- Snap-confine detector in _build_dos_command: when dosbox /
dosbox-staging resolves to a /snap/ path, we reject it up front
with a clear "remove snap, install apt or AppImage instead" message.
Avoids the cryptic cap_dac_override not found error on first
launch from systemd.

v277:
- DOSBox per-node mount: the per-node temp dir is now mounted as drive
E: in addition to the game dir on C:. Sysops set LORDCFG's "Path to
dropfile" to E:\ and multi-node LORD / TradeWars / TQW just works
without overwriting each other's drop files.
- docs/14-door-games.md and docs/15-synchronet-compat.md published.

v1.0a — pre-release polish (May 2026, internal v195–v276)

Final shake-out before tagging the alpha.

New: Image galleries
- /gallery/ — paginated thumbnail grid + full-screen modal viewer.
Browser-native, lazy-loaded.
- /admin/galleries/ — full CRUD on collections (label/slug/path/sort/
active) plus per-gallery file management (drag-drop multi-upload,
click-to-delete, pagination).
- Storage: gallery-config.json at install root, auto-seeded on first
run from any DSR gifs / swim directories that exist. Excluded
from deploy so sysop edits survive rsync --delete.
- Standalone terminal viewer: /home/<user>/anet-gallery.sh (chafa /
img2sixel) — kept outside the install dir so deploys don't remove it.
- Galleries link lives under Tools in the top nav (kept off the
primary bar to avoid wrapping on smaller widths).
- New doc: 13-image-galleries.md.

Removed: DSR (Digital Showroom)
- The Synchronet sixel image-viewer door is no longer wired into the
games menu. The convert → .sixel pipeline returned rc=0 with no
output file when the door was launched from a gunicorn-spawned PTY
child (worked manually but not under the web service); root cause
not identified after extensive investigation. The web /gallery/ +
terminal anet-gallery.sh cover the same use-case at higher quality.
- DSR files remain on disk under doors/sbbs/dsr/ so a future
reinstatement is possible without re-extracting from the .zip.

Codebase audit
- Real bug found and fixed: current_app referenced in web/admin.py
but missing from the flask import line (would have NameError'd at the
affected admin routes).
- Real bug found and fixed: missing requests from setup.py
install_requires — webhooks.py imports it unconditionally so a fresh
pip install -e . would crash on first webhook dispatch.
- Real bug found and fixed: missing admin/time_budgets.html template
/admin/time-budgets would 500 on GET. Template added matching the
rest of the admin UI.
- Real bug found and fixed: 4 broken url_for() references that would
raise BuildError at render time (echomail.view_message,
boards.view_thread, pm.read_message, echomail.list_messages
endpoints renamed in earlier work but the saved-messages and
permalinks blueprints still pointed at the old names).
- Real bug found and fixed: 3 duplicate method definitions in
core/session.py (write / read_line / clear_screen) — Python
silently kept only the later definitions, the first ones were dead
code. Removed.
- Real bug found and fixed: 3 stray f''-prefixed strings without
placeholders in web/echomail_admin.py and web/irc_web.py.
- Real bug found and fixed: update.sh rsync was missing --exclude
for /doors/ and /gallery-config.json — closes the same hole that
caused the v195 deploy incident.
- Duplicate import json in echomail/tic.py removed.
- Duplicate from typing import Dict in core/session.py consolidated.
- Explicit __all__ added to anetbbs/core/__init__.py (silences
pyflakes false-positive on re-exports).
- Orphan templates removed: admin/callers.html (replaced by
admin/caller_log.html), chat/index.html (legacy local-chat,
replaced by MRC).
- ~80 unused imports trimmed across the package — pyflakes warning
count down from ~110 to 22, with the remaining warnings all
intentional (load-bearing SessionProtocol re-export, protocol-level
drain variables, dead global declarations).
- All 180 Jinja templates parse cleanly.
- All 308 url_for() references resolve to a registered endpoint.
- 79/81 SQLAlchemy models are queried in code; the two stragglers
(ChatMessage, MenuTranslation) are leftover scaffold tables that
stay in the schema for now to avoid alpha migration churn.
- Zero outstanding TODO/FIXME/XXX/HACK markers in our code
(matches in vendored Synchronet .js stubs are upstream's).
- New top-level FEATURES.md — single-page inventory
of every user-visible feature for the alpha.
- docs/00-overview.md updated with the gallery feature.
- docs/PORTS.md + deploy/README.md now document the Finger
service (port 79).

Error pages + nav coverage
- New branded 404.html / 403.html / 500.html templates wired up
via errorhandler() (previous behaviour fell through to Flask's
default monochrome error pages).
- Sysop nav: added Time Budgets link to the Admin → Users dropdown
(the /admin/time-budgets page was registered but unreachable from
the UI — only the URL bar got you there).

Config completeness
- .env.example expanded with previously-undocumented but code-used
settings: SYSOP_NAME, BBS_DOMAIN, BBS_PUBLIC_HOST, BBS_EMAIL,
BBS_LOCATION, BBS_NODES, IDLE_TIMEOUT_SECONDS,
FINGER_LISTEN_HOST/PORT, BINKP_LISTEN_HOST/PORT,
BINKP_OUR_ADDRESS, BINKP_SYSTEM_NAME, CLAMSCAN_PATH,
IRC_LOG_CHANNELS, NUV_ENABLED, RATIO_MIN.

Door-game runtime parity with Synchronet / Mystic

A real BBS gives each active node its own scratch directory and
substitutes shortcodes (Synchronet %f, Mystic %P etc.) in door
command lines. Both were missing; both shipped this session.

  • New anetbbs/games/node_paths.py — per-node temp dirs at
    <install>/data/temp/nodeN/ (1..BBS_NODES), auto-created on app
    boot. Owns the substitution table.
  • Synchronet %-token vocabulary supported in Game.executable_path,
    Game.working_directory, Game.command_line_args, and
    Game.drop_file_path: %a, %c, %d, %e, %f, %g, %h,
    %i, %j, %k, %l, %m, %n, %o, %p, %r, %s, %t,
    %u, %w, %y, %z, %!, %%.
  • Mystic %-token vocabulary supported in the same fields:
    %P (per-node temp dir w/ trailing /), %N, %M, %U, %T,
    %H, %R, %E, %S, %L. So %Pdoor32.sys correctly resolves
    to e.g. /data/temp/node3/door32.sys.
  • Game.drop_file_path is now %-token expanded (was only {node}).
  • New doc: docs/14-door-games.md — full token
    table, LORD walk-through, and the rest of the door-config story.
  • New doc: docs/15-synchronet-compat.md
    xtrn coverage survey, what stock Synchronet doors are likely to work
    through our Node shim vs. need a real jsexec.

Mystic .mps auto-compile
- New _ensure_mps_compiled() in door_runner.py — if mplc is on
the host, automatically compile .mps source → .mpx bytecode
before launch (only if source is newer than bytecode). Falls back to
an existing .mpx next to the source if mplc isn't installed.
- New MYSTIC_MPLC_PATH env var for explicit override.

Installer modes (test vs production)
- install.sh now starts with an "Install mode" prompt:
- production = real BBS facing the public internet — wants
nginx, SSL, a domain pointed at the box, privileged-port services
(Finger/MSP/SYSTAT) reachable from peers.
- test = local-only / behind NAT / no static IP / just trying it
out — auto-skips nginx, certbot, Finger/MSP/SYSTAT/BinkP. Gunicorn
binds 0.0.0.0:5000 directly. Sysop can flip individual flags later
without re-installing.
- New optional install step: download Mystic Linux freeware tarball,
extract mystic + mplc to /opt/mystic/, symlink into
/usr/local/bin/, write MYSTIC_MPLC_PATH to .env. Soft-fails
if the download URL is unreachable (no network, mysticbbs.com down).
- anetbbs-finger.service unit now installed by install.sh when
ENABLE_FINGER=y (was never deployed by the installer despite the
unit file existing in deploy/).
- Test-mode summary message at end of install spells out exactly what
was skipped and how to re-enable later.

Deploy / ops
- update.sh and the documented rsync recipe explicitly
--exclude=doors --exclude=gallery-config.json --exclude=data/ to
prevent the v195-era incident where a --delete deploy wiped the
doors tree (DSR, BotWars, RDQ3, the 6500-GIF library) from a
production install.

Misc
- permission denied regressions on the MRC bridge fixed by ensuring
service unit User=stingray matches actual file ownership on the
reference deployment (was getting accidentally chown'd to anetbbs).

v1.0a — alpha refresh (May 2026, internal v194)

Subsequent rolling internal builds (v165-v194) refine the alpha. Public
tarball at ANetBBS-v1.0a.tar.gz always tracks the latest internal cut.

Highlights since the v164 alpha:

  • MSP / Inter-BBS IM correctness — the previous wire format was a
    homegrown 5-field variant that Synchronet rejected silently and we
    mis-parsed in the inbound direction. v168 ships a real RFC 1312 MSP-2
    encoder/decoder (7 fields with a leading 'B'/'A' type byte), strips
    @host from inbound recipients, optionally answers acked-mode requests.
  • Synchronet @CODE@ substitution in our Synchronet stub layer's
    printfile@BBS@, @USER@, @SYSOP@, @TIME@, @DATE@,
    @SECURITY@, @CALLS@, @NODE@, etc. resolve from the BBS_*
    environment variables that door_runner.py exports.
  • Display-code preprocessor for ANSI screens and BbsMenu screens —
    Synchronet @-codes (@USER@, @BBS@, @TIME@, ...) and Mystic
    named codes (|UN, |BN, |DT, ...). Color-pipe codes like |07
    remain handled by the existing _pipe_to_ansi.
  • QWK polling improvements — Synchronet's 550 No QWK packet created (no new messages) is now treated as benign instead of a
    fatal poll error (was generating thousands of false ERROR lines per
    day). Poll interval clamped to ≥5 minutes to prevent a misconfigured
    DB from hammering remote hubs.
  • Web-stack hardeningKillMode=mixed + RestartSec=10 on the
    gunicorn unit so eventlet workers don't leak past the master and
    cause EADDRINUSE crash loops; _create_default_data os shadowing
    bug fixed; body keyword for PrivateMessage (was content);
    /help endpoint repaired.
  • Permissions — install.sh asserts ssh_host_key + sbbs_stubs/
  • doors/ permissions on every run (not only at first generation),
    with sbbs_stubs source files now shipped world-readable so future
    rsync deploys don't reset BotWars/RDQ3 to broken.
  • Terminal MRC client — extensive UX work:
    • anetmrc-style stationary status bar at the top (room, topic,
      mention badge, server latency)
    • DECSTBM scroll region for chat below it
    • HH:MM timestamp prefix on every chat line, ANSI-aware word-wrap
      with continuation indent
    • Tab-complete usernames (active list seeded from chat events,
      /who, /chatters)
    • New slash commands: /afk [msg], /back, /status,
      /roomconfig, /termsize, /whoon alias of /who
    • /info <id> now passes the BBS index to the server
    • /scroll removed (terminal-native scrollback works fine)
    • Removed the doubling-on-wrap bug (input is slide-windowed; chat
      uses ANSI-aware word wrap instead of native terminal wrap)
  • Terminal BBS UI — single-key hotkeys (no Enter required), screen
    clear before each menu, Q logoff confirms with Y/N, banner+colored
    rendering on Boards / Echomail / Bulletins / Who's Online / Sysop
    Tools / Profile / PM compose / IM compose / inboxes, paged area
    picker for compose-echomail, terminal IM reader ([I] and [J]).
  • Web nav reorg — Inter-BBS IM badge in the top bar (next to PM
    envelope and notification bell), Web Terminal moved from Chat
    dropdown to top of Tools dropdown.
  • Privacy — IP column on /who now hidden from non-admins.
  • CP437 mojibake recovery for ANSI screens stored as Latin-1 in
    the DB (encode→bytes→write-as-CP437 path so SyncTERM and modern
    terminals both render correctly).
  • Dialout default directory trimmed to a single seed entry
    (sysop adds the rest via /admin/dialout).
  • Door runner — door cleanup callback now runs inside a captured
    Flask app context (was leaking "Working outside of application
    context" errors when sessions ended).
  • Web service unit — config templated for /opt/anetbbs/; the
    install.sh rewrite handles INSTALL_DIR substitution properly.

v1.0a — first alpha (May 2026)

Bundles all internal versions through v164.

Major features:
- Web BBS (Flask + SocketIO) + telnet + SSH + rlogin front-ends
- FidoNet binkp echomail + netmail with full kludge support (MSGID, REPLY,
INTL, FMPT/TOPT, CHRS, PID, TZUTC), CRAM-MD5 BinkP auth, optional TLS
- DOVE-Net QWK echomail with REP packet outbound, CONTROL.DAT-driven
area auto-create, 1-on-1 QWK netmail compose
- Inter-BBS Instant Messaging via MSP (RFC 1312) on TCP/18 + SYSTAT/
ActiveUser on UDP/11, with the Synchronet sbbsimsg.lst directory
mirrored daily and a built-in BBS picker UI
- Terminal MRC chat (telnet/SSH/rlogin) — talks to the same local
websocket bridge that web users hit, so terminal and browser users
share the same hub identity and chat rooms. Full command set
(/identify /msg /me /join /list /who /motd /banners /topic /scroll /mentions /trust /broadcast …); ←/→ arrow keys cycle outgoing text
color; /identify mid-line password masking; @mention highlighting
with bell; outgoing 140-char cap with auto-split on word boundaries;
ROOMTOPIC banner + topic snippet baked into the prompt
- Local boards with voting, search, sticky/lock, threading, ANSI banners,
per-board moderators
- File bases with TIC ingest, outbound TIC hatching, FILE_ID.DIZ
auto-extract, per-area upload permissions
- Doors: DOSBox (auto-detect staging/x/vanilla), Mystic .mps and .mpy,
Synchronet .js (real jsexec when available, Node.js shim fallback),
native binaries — all with DOOR.SYS / DOOR32.SYS / DORINFO drop files
- Private messages, FTN netmail, CP437 + ANSI rendering everywhere
- AreaFix in/out with per-network admin queue, BadArea sysop-review queue
- Admin: rate limits on login/MSP/votes, random initial admin password
written to data/admin_password.txt, SECRET_KEY guard refusing dev
default in production, open-redirect-hardened login

Internal version history (build numbers)

ver summary
v164 MRC: ROOMTOPIC captured + shown in prompt + topic banner; mention bell + brighter highlight; /scroll empty-state; /mentions to clear counter
v163 MRC terminal: dropped DECSTBM scroll regions (unreliable on BBS terminals); switched to inline-redraw model
v162 MRC terminal: truncate-to-width on emit, dropped SAVE/RESTORE_CURSOR
v161 Taglines append on echomail + QWK netmail (was netmail-only)
v160 MRC terminal: separator/input row collision fix; ←/→ arrow-key color cycling
v159 MRC terminal: split-screen UI, 140-char outgoing cap, scrollback, mention highlighting, full command roster
v158 MRC terminal: trigger-space stays visible on /identify; broader server-noise suppression (BANNER, TYPING, CAPABILITIES, NEWROOM)
v157 MRC terminal: inline /identify password masking; USERLIST: protocol-leak suppression
v156 MRC terminal: /join uses NEWROOM (was resetting trust state via re-handshake)
v155 MRC terminal: dropped MRC submenu — choice 3 takes you straight in; masked-input tip
v154 MRC terminal: IDENTIFY/REGISTER/UPDATE corrected to top-level verbs (not TRUST subcommands)
v153 MRC terminal: full slash-command set (motd/banners/topic/roompass/lastseen/last/etc.); cleaner event renderer
v152 Restored SessionProtocol import (telnet/SSH service crash fix)
v151 Terminal MRC client (talks to local websocket bridge — terminal+web users share rooms)
v150 Hands-off installer: CAP_NET_BIND_SERVICE in systemd unit; optional-deps prompts (DOSBox/ClamAV/lhasa); MRC bridge gated; UFW prompt
v149 Cleanup: stale tarballs, pycache, egg-info, dead imports; file-delete bug (uploader_id vs user_id)
v148 Upgrade-wizard fix: auto-heals SECRET_KEY in .env; ANETBBS_SCHEMA_MIGRATE_ONLY bypass for migration subprocess
v147 SECRET_KEY guard relaxed for migration mode (incomplete; superseded by v148)
v146 Security pass: random admin pw, SECRET_KEY guard, decorator fix, open-redirect, rate limits
v145 TZUTC netmail kludge + board search route
v144 File-area scoping on uploads + upload_permission enforcement
v143 Synchronet doors prefer real jsexec; SBBSEXEC/SBBSCTRL/SBBSDATA/SBBSNODE env in fork
v142 MSP send form accepts user@host paste; wire-tested to a-net-online.lol
v141 SYSTAT/ActiveUser UDP service + sbbsimsg.lst directory + BBS browser UI
v140 MSP / RFC 1312 Inter-BBS Instant Messaging — server, client, inbox, send form
v139 FidoNet netmail compose actually sends (was dead-letter); FTS-0001 12-byte routing header fixed; @INTL/@FMPT/@TOPT
v138 Up/down voting on board posts, echomail, PMs
v137 File upload: bug fixes + FILE_ID.DIZ / README auto-extract; ALLOWED_EXTENSIONS broadened
v136 AreaFix log viewer
v135 QWK auto-create areas from CONTROL.DAT; poll-log no-longer-shows-negative-counts fix
v134 AreaFix-on-subscribe; QWK quick-add UI
v133 Auto-sweep schema migration; BadAreaLog viewer
v132 SBBSecho parity: CRAM-MD5 BinkP, soft-CR strip, UTF-8 detect, PATH dupe, BadAreaLog model
v131 CP437/ANSI rendering filter + image-link helper for boards
v130 CP437/ANSI Jinja filter; QWK netmail (private 1-on-1) compose + inbox + REP-with-status='*'
v129 QWK parser fix — num_chunks is ASCII not binary (caused garbage areas + crammed bodies)
v128 Echomail nav link, misc UI
... (earlier history archived)

The full per-version diff lives in git.