ANetBBS Changelog
Versions are internal build numbers. Public releases are tagged
separately. Current release: v1.0b2.58 (July 2026). Full release: August 1 2026.
v283.6 — Echomail message reader: color + paging (May 2026)
After picking an area under Echomail (E), the resulting message
list and message body were plain monochrome and dumped without
paging. Now matches the rest of the terminal UI:
- Banner + footer wrap on both list and body screens
- Colored columns on the message index (yellow #, white subject,
green from, cyan date) -- more (Enter) --paging every 18 lines on both the index
and the message body, so long posts don't scroll past on a
24-row terminal- Same paging treatment in Compose Echomail (C) for the area-empty
screen, prompts, and the queued-for-BinkP confirmation
v283.5 — Echomail (E) area list paging (May 2026)
The terminal main menu's Echomail (E) area listing scrolled past on
24-row terminals — compose_echomail (C) already paged every 18
lines with -- more (Enter) --, but list_echo_areas dumped
everything at once. Now uses the same paging.
v283.4 — Dark-theme: list-group + table row tints (May 2026)
Bootstrap's default .list-group-item and .table-warning/-info/...
row tints render light-on-light, which clashes with the dark theme.
The most visible offenders were:
- The RSS Reader feed list and River feed (white cards, hard to read).
- The Inter-BBS Instant Messages inbox unread-row highlight (cream
yellow on white). - @mention autocomplete popup, leaderboards, stats, profile,
oneliners, calendar, docs nav — all used.list-group-item.
base.html now overrides these classes to use the same dark palette
as the .card and .alert-* styles. No template changes needed —
all consumers benefit automatically.
v283.3 — NodeSpy kick audit-log fix (May 2026)
The kick endpoint in v283.2 raised
TypeError: 'action' is an invalid keyword argument for UserActivity
on every kick attempt — the audit-log column is activity_type, not
action. (The legacy v283 code had the same bug but never ran far
enough to trip it.) Now uses the correct field and also records
ip_address and service='web'.
v283.2 — NodeSpy kick: cross-process fix (May 2026)
The kick button in v283 / v283.1 appeared to work but didn't actually
disconnect anyone. Root cause: anetbbs-web (gunicorn) and
anetbbs-telnet are separate systemd services with separate Python
processes. The kick endpoint flipped a flag in the web process's
in-memory _NODES dict, but the active terminal session lived in the
telnet process — different memory, no effect.
Now the kick crosses the process boundary via the database:
NodeActivitygains two columns:kick_requested(bool) and
kick_reason(string). Auto-sweep adds them on next gunicorn start./admin/control/nodespy/<slot>/kicksimply updates those columns
and commits.- A new
_kick_watchdogtask inBBSSessionpolls its own
NodeActivityrow every 5 seconds. Whenkick_requestedis set, it
writes the goodbye banner, closes the writer, and the session
unwinds via the normal teardown path. - The watchdog is cancelled on session close, before
_close_node_activitydeletes the row.
Worst-case latency: ~5 seconds between kick click and disconnect.
Stuck/idle users are unaffected (the watchdog runs on its own timer,
not on user input).
v283 — NodeSpy kick (May 2026)
Sysop can now disconnect a stuck or misbehaving terminal user
straight from the NodeSpy panel:
- Kick button in the per-row NodeSpy table (red door-arrow icon)
- Kick form in the per-node detail page with an optional reason
text field - New endpoint:
POST /admin/control/nodespy/<slot>/kick(CSRF-
protected, admin-only) - New function:
multinode.kick_node(slot, reason)— pushes a 'kick'
payload to the session's chat queue, writes a goodbye line to the
user's terminal, then closes the underlying transport. The session's
reader EOFs and the session cleans up naturally. NodeEntrygained asessionreference (passed byacquire_slot
incore/session.py) so the kick handler has a handle to close.- Audit log entry in
user_activityfor every kick (action=kick_node,
details = "slot N (username): reason"). So sysop kicks are
traceable.
Different from a ban — this just drops the current connection. The
user can reconnect immediately. To prevent reconnect, sysop must
also add an IP ban under /admin/ip-bans/.
v282 — consolidation cut (May 2026)
Clean release after the v280-v281.3 hotfix series. No new behavior
beyond what the v281.3 patches already shipped — this is purely
a docs + memory + audit-pass roll-up so the next deploy carries
matching docs alongside the running code.
- Docs updated: CHANGELOG, FEATURES, 14-door-games (door_rlogin
worked example, dosemu removal note), 16-rss-reader. - Memory entries updated: project_status reflects current cursor
- RSS + door_rlogin work; rlogin handshake quirk recorded at
project_rlogin_format.md; LORD setup guidance at
project_lord_setup.md; door-files-hands-off feedback at
feedback_door_files.md. - Final audit: 50 blueprints, 319 routes, 189 templates parse,
zero brokenurl_for()references, app boots clean. Pyflakes
has no undefined-name / redefinition warnings — only intentional
drains and load-bearing imports remain.
v1.0a — RSS reader (May 2026, internal v281)
Built-in RSS / Atom feed reader for both web and terminal. Sysop
manages feeds at /admin/rss/. Background poller refreshes every
30 minutes (configurable via RSS_POLL_INTERVAL env var). X-News
seeded by default so a fresh install has at least one feed populated.
New tables (auto-created by the lightweight migration sweep):
- rss_feeds — sysop-configured feeds
- rss_items — articles (deduped per-feed by GUID)
- rss_read_status — per-user read markers
New blueprints: rss at /rss/, rss_admin at /admin/rss/.
New file: anetbbs/rss/poller.py — background daemon thread.
New dependency: feedparser>=6.0 (handles RSS 2.0 / Atom / RSS 1.0).
Web UI: Tools → RSS Reader. Feed list with unread badges, "all
feeds" river view, paginated per-feed item lists, single-item full
content view, mark-as-read tracking per user.
Terminal UI: Main BBS Menu → R. Feed picker, river, paginated
item lists, single-item viewer with word-wrapped body. Same mark-read
state shared with the web UI.
Pre-seeded feed: https://x-bit.org/rss/rss.xml (X-News).
v281.1 — fixed Jinja name collision. Renamed template var from
unread to feed_unread because base.html does {% set unread = ... %}
for the PM badge counter, which shadowed our context variable inside
child templates and turned the dict into an int.
v281.2 — added RSS to the data-driven menu engine. v281 only
added it to the hardcoded BBSMenuUI.show_main() fallback; the
running terminal sessions use the BbsMenu table-driven menu, which
needed a new rss action_type registration + a R hotkey entry in
DEFAULT_MENUS (auto-backfilled to existing installs by
seed_default_menus).
v281.3 — typo: FG['ylw'] → FG['yel']. Crashed show_rss()
on first launch.
See docs/16-rss-reader.md for full feature docs.
v1.0a — A-Net Game Server / rlogin doors (May 2026, internal v280)
New game type: A-Net Game Server (rlogin). Lets BBS users
transparently rlogin into a remote Synchronet door game server
(or DoorParty, or any other rlogin-accepting BBS host). The user
sees one continuous session — no second login prompt.
Architecture:
- New
anetbbs/games/rlogin_bridge.pywithRloginConnection—
same write/stop/bind_emit shape asDosBridgeso it slots into
the existing DoorSession machinery (send_input, terminate_session,
cleanup all work without changes). - New
launch_rlogin_session()indoor_runner.pyfor the web flow. - New
play_rlogin_telnet()indoor_runner.pyfor the terminal
flow. Both use the sameRloginConnection. - Game type
door_rloginselectable in/admin/games/with its own
helper section in the form (server host:port + user template +
password + optional terminal hint for direct-to-door launches).
Wire format: Synchronet's BBS-mode rlogin server uses INVERTED
field order vs RFC 1282 — the password goes in the client-user-name
slot (1st field) and the BBS username in the server-user-name slot
(2nd). Verified against game.a-net-online.lol on 2026-05-09. Memory
note saved at memory/project_rlogin_format.md.
dosemu removed. The half-finished door_dosemu game type was
yanked from the dropdown — drive-letter detection was wrong and the
admin form fields were missing. _build_dosemu_command stays in the
source tree as dead code in case someone wants to validate it
properly later.
v280.1–v280.4 — admin form polish + rename: dropdown renamed
"Synchronet xtrn / DoorParty" → "A-Net Game Server (rlogin)". Added
a dedicated form section so the executable_path / command_line_args
fields are visible when door_rlogin is selected (same fix pattern as
the gallery_admin disappearing-fields bug). Updated helper text with
working examples for game.a-net-online.lol direct-to-door
(xtrn=LORD408, xtrn=ASSASSIN, etc.).
v1.0a — door I/O safety + auto-recovery (May 2026, internal v279.1–v279.9)
A series of hotfixes after v279 wired up the DosBridge — fixing
real-world door issues that surfaced during testing.
- v279.1:
_build_dos_commandnow acceptstoken_ctx=Nonefor
validation-only callers (the terminal launcher previewed the
command before allocating a bridge port; without this it crashed
with "unexpected keyword argument"). - v279.2: DOOR.SYS / DORINFO / DOOR32.SYS now identify as
COM1: - 38400 baud + comm type 1 (FOSSIL) instead of
COM0:+ 0 + comm
type 2. Most classic doors (LORD especially) interpret COM0/0 as
"local console — bypass FOSSIL, write to BIOS only", which dropped
all output before it could reach the bridge. - v279.3: bridge diagnostic logging — first 5 chunks logged
verbatim, "0 bytes after 10s" warning if door isn't writing to
FOSSIL, total chunk/byte count on close. - v279.4: terminal
door_dosnow uses the unified launch path
(launch_door_gameviaplay_door_game_telnet). The legacy
play_dos_game_telnethad its own DOSBox-launching code that
predated the bridge wiring and never got updated; terminal users
were getting silent hangs. - v279.5: waitpid watcher thread. xvfb-run can leave Xvfb running
past the door exit (Xvfb inherits the PTY slave_fd, keeping
master_fd from EOFing), which means our PTY watcher never wakes
to fire cleanup. The new watcher doesos.waitpid(pid, 0)and
triggers_cleanup_sessionwhen the door subprocess actually
exits — independent of PTY EOF. - v279.6: bridge close → cleanup signal. When DOSBox closes its
end of the TCP nullmodem (which it does on exit), the bridge fires
a newon_closecallback that runs_cleanup_sessionimmediately.
Faster than waitpid for the common case. - v279.7: real Ctrl+]q abort. The launch banner has been telling
users "press Ctrl+] then q to abort" since forever, but the input
pump just forwarded those bytes to the door. Now the pump
actually watches for the sequence and triggers cleanup. - v279.8: idle-timeout watchdog (default 300s). If zero bytes
flow in either direction for the timeout, the bridge force-closes
and triggers cleanup. Catches stuck-door cases (LORD's exit-loop,
infinite "press any key" prompts on hidden screens). - v279.9: idle timeout reduced to 60s default + made configurable
viaDOOR_IDLE_TIMEOUT. Also force-kills the door process group
via SIGTERM (then SIGKILL 2s later) when timeout fires, instead of
just trusting_cleanup_sessionto do it.
End result: stuck DOS doors always return to the BBS within 60
seconds, the BBS user's session never permanently hangs, orphan
DOSBox processes get reaped.
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