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 viasocketio_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=nullandautolock
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 inGame.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.syscorrectly resolves
to e.g./data/temp/node3/door32.sys. Game.drop_file_pathis 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 realjsexec.
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
@hostfrom 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 thatdoor_runner.pyexports. - 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 hardening —
KillMode=mixed+RestartSec=10on the
gunicorn unit so eventlet workers don't leak past the master and
cause EADDRINUSE crash loops;_create_default_dataosshadowing
bug fixed;bodykeyword forPrivateMessage(wascontent);
/helpendpoint 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,/whoonalias of/who /info <id>now passes the BBS index to the server/scrollremoved (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)
- anetmrc-style stationary status bar at the top (room, topic,
- 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
/whonow 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.shrewrite handlesINSTALL_DIRsubstitution 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.