ANetBBS Changelog

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

v1.0a2.84 — Fix: custom ANSI menu files not loading (DATA_DIR path bug) (June 2026)

load_menu_ansi() always returned None. It used os.environ.get('DATA_DIR')
which is never written to .envDATA_DIR is derived from __file__ at
runtime in config.py, not stored in the environment. Fixed by computing the
data directory path from __file__ directly (same logic as config.py).

v1.0a2.83 — Feature: custom ANSI headers for all hard-coded menus/submenus (June 2026)

All hard-coded telnet/SSH/rlogin menus now support optional .ans file
overrides via data/text/menus/<slot>.ans. Supported slots: game_center,
door_games, chat, irc_chat, dialout (plus main and any BbsMenu name
already supported by the menu engine). If the file is absent the built-in
menu renders unchanged.

v1.0a2.82 — Fix: custom ANSI menu screens showing garbled CP437 block characters (June 2026)

Custom ANSI menu .ans files displayed wrong characters (Ü, ß, etc. instead
of block graphics).
The menu engine decoded the file as latin-1 (correct and
lossless), but then passed the string to session.write() which re-encodes as
CP437 — a non-symmetric transformation that mapped e.g. (0xDC) → Ü (0x9A)
and (0xDF) → ß/β (0xE1). Fixed by writing the ANSI content as raw
latin-1-encoded bytes via session.writer.write() directly, same as
_show_ansi_screen() already did.

v1.0a2.81 — Fix: DOSBox-X detection (revised) + web auto-update tar failure on VPS hosts (June 2026)

DOSBox-X detection fix revised. The v1.0a2.80 fix (SDL_VIDEODRIVER=dummy)
was insufficient — dosbox-x on headless Debian/Ubuntu still hangs during the
subprocess version probe (audio init, not just display). Detection now simply
checks file existence and executable bit. If apt installed the binary, it's
the right arch; exec-format errors surface clearly at door launch time.

Web auto-update "tar extract failed" on VPS hosts fixed. On LXC containers
and some cloud VPS hosts, root cannot chown files to the uid baked into the
tarball (uid 1000 = developer's build machine). run_upgrade.sh now passes
--no-same-owner to tar, letting extracted files take the ownership of the
extracting process instead.

v1.0a2.80 — Fix: duplicate Admin Caller Log entry + DOSBox-X detection on headless servers (June 2026)

Duplicate "Caller Log" entry in Admin menu removed. The Admin dropdown
showed two Caller Log links — a plain-text duplicate and the correct icon
version. The duplicate has been removed.

DOSBox-X detection fixed on headless servers. Running dosbox-x --version
without a display could hang on SDL display init, timing out the 5-second
runability check and making dosbox-x appear absent even when installed via
apt install dosbox-x. The detection subprocess now sets
SDL_VIDEODRIVER=dummy so SDL skips display init. The "No usable DOSBox"
error message now lists the paths that were actually tried, and dosbox-x
is now the first install recommendation.

v1.0a2.79 — Fix: nginx immutable caching + MRC-optional update + web update completion (May 2026)

nginx Cache-Control: immutable caused permanently-cached 404s. The
/static/ nginx block shipped with public, immutable which tells browsers
the URL will never change. When anetbbs/static/mrc/client.js didn't exist
(pre-v1.0a2.76), nginx 404'd the file with the immutable header — browsers
cached that 404 forever and never re-fetched the real file after upgrading.
Fixed: immutable removed, expires 7d removed, replaced with
max-age=86400. update.sh now auto-patches the running nginx config and
reloads nginx.

install.sh nginx block fixed — MRC bridge proxy_pass corrected from
/mrcws to /ws; MRC auth_request block added (prevents unauthenticated
WebSocket connections); client_max_body_size 110m added (nginx's 1m default
was rejecting avatar uploads and large file posts).

update.sh no longer force-starts MRC bridge on servers that don't use
it.
Optional services (anetbbs-mrc-bridge, anetbbs-finger) are now only
restarted if they were running before the update. Servers where MRC is
stopped or failed skip MRC bridge restart entirely. systemctl reset-failed
added before each restart to clear start-limit-hit state.

Web update UI completion detection fixed. The "Check for Updates" web
upgrade was polling forever after a successful update: the exit N marker
was written by a Python thread that dies when gunicorn stops mid-update
(Step 3). After the new gunicorn restarted, the UI saw running=false but
no exit N, then polled for 30 minutes and gave up. The UI now also accepts
[upgrade] upgrade to X complete (written by run_upgrade.sh) as a
terminal condition.

v1.0a2.78 — Fix: web MRC full UI (real index.html + client.js wired to Flask) (May 2026)

The web MRC chat UI was rendering from a 406-line stub template rather than
the real 2300-line mrc/web/index.html that runs on the live server. The stub
was missing themes, the user sidebar, macros, mentions panel, reconnection, pipe
color rendering, server selection, mobile layout, and the full slash-command UI.

  • Replaced anetbbs/templates/mrc/index.html with the real mrc/web/index.html
  • Three minimal Jinja2 injections added: url_for for client.js static path,
    window.RETURN_TO_BBS_URL for the back-to-BBS link, {{ suggested_handle }}
    pre-fills the handle input with the logged-in username
  • anetbbs/static/mrc/client.js updated to the production version from
    mrc/web/client.js (reconnection, pipe colors, auto-rejoin)

v1.0a2.77 — Fix: web MRC WebSocket connection error on HTTPS installs (May 2026)

Web MRC chat connected for the sysop (direct HTTP on port 5000) but failed for
all users on HTTPS installs with a WebSocket connection error. Two bugs:

Bug 1 — wrong protocol (ws:// vs wss://): Flask sits behind nginx, so
request.is_secure is always False — Flask only sees plain HTTP from the
proxy. The template was generating ws:// WebSocket URLs on HTTPS sites, which
browsers block as mixed content. Fixed by checking X-Forwarded-Proto: https
header in mrc_web.py.

Bug 2 — nginx proxied to wrong bridge path: The nginx template forwarded
/mrcws to http://127.0.0.1:8080/mrcws, but the bridge only registered its
WebSocket handler at /ws. Added /mrcws as an alias route in
mrc/bridge/main.py and corrected the nginx template to proxy to /ws.

v1.0a2.76 — Fix: web MRC chat "MRCClient is not defined" (May 2026)

anetbbs/static/mrc/client.js was never created, so every web user opening
/mrc/ got a JS ReferenceError: MRCClient is not defined and the chat page
was non-functional.

Added anetbbs/static/mrc/client.js with the full MRCClient WebSocket
class:
- connect() — opens the WebSocket to the bridge, resolves on open
- joinRoom(handle, room) — sends join_room to the bridge
- sendMessage(text) — sends send_message to the current room
- sendServerCommand(cmd) — sends server_cmd (slash commands)
- leaveRoom() — sends leave_room
- disconnect() — tears down the WebSocket cleanly
- 30-second keepalive ping so the bridge doesn't drop idle connections
- Tracks current room/handle from joined / room_changed / handle_changed
events so callers don't need to pass the room on every message

v1.0a2.75 — Raspberry Pi full install guide (May 2026)

Added docs/INSTALL-PI.md — a comprehensive guide for running ANetBBS on
Raspberry Pi 4/5, covering hardware selection, OS recommendations, DDNS setup,
port forwarding, Let's Encrypt SSL, moving data/ to a USB SSD, service
management, door game compatibility, and a troubleshooting section covering
all known Pi-specific issues (SQLite path bug, CSRF cookie, disk space).
Linked from README.md documentation section.

v1.0a2.74 — Fix: telnet/SSH "unable to open database file" on Pi / wizard installs (May 2026)

main.py (the anetbbs telnet/SSH entry point) was not pinning DATABASE_URL
before importing anetbbs.config. At import time, if DATABASE_URL was absent
from the environment — wizard installs wrote SQLALCHEMY_DATABASE_URI instead,
a key the config never reads — the fallback resolved DATA_DIR from
config.py's location in the venv site-packages, which doesn't exist, causing
"sqlite3.OperationalError: unable to open database file" on every DB operation
in the terminal session. The web server was unaffected because deploy/serve.py
always pins DATABASE_URL from its own __file__ location before any imports.

Fix: main.py now applies the same self-pinning logic at startup, before any
anetbbs imports. Also corrected wizard.py to write DATABASE_URL (not
SQLALCHEMY_DATABASE_URI) to .env so wizard-installed systems have the right
key going forward.

v1.0a2.73 — ANSI file drop-in, bulletin word-wrap, screen-slot editor link, sysop name fix (May 2026)

ANSI file drop-in system (data/text/): sysops can now drop .ans files
into the install to override screens without touching the admin UI. Resolution
order: file first, then DB, then built-in fallback.
- data/text/welcome.ans — pre-login / logon screen
- data/text/goodbye.ans — logoff screen
- data/text/newuser.ans — new-user registration screen
- data/text/menus/<name>.ans — menu header ANSI (e.g. main.ans, games.ans)
Display codes (@USER@, |BN, etc.) are applied to file-based screens too.

ANSI editor → screen slot link: the ANSI editor now has an "Apply to Screen
Slot" card alongside "Apply to Menu". One click pushes the art directly into
the welcome/goodbye/newuser DB slot — no copy-paste required.

Bulletin word-wrap: bulletin text in the terminal pager now word-wraps to
the user's negotiated terminal width (NAWS) instead of silently truncating every
line at 78 characters.

Sysop name as admin username default: the installer now defaults the admin
login username to the sysop display name entered earlier, so sysops who type
"Firehawke" as their sysop name get "Firehawke" as the default admin account
name instead of "admin". install.sh also now writes SYSOP_NAME to .env
and asks for a sysop display name separately.

Logged-out intro screen uses BBS_NAME: the fallback login menu shown when no
custom welcome ANSI is configured now reads BBS_NAME from config instead of
always printing "Welcome to AnetBBS".

v1.0a2.72 — Fix: login fails with CSRF error on HTTP-only installs (May 2026)

ProductionConfig hardcoded SESSION_COOKIE_SECURE = True. Browsers silently
discard Secure-flagged cookies on plain HTTP connections, so the CSRF token stored
in the session on the GET of /auth/login was never returned on the POST — Flask-WTF
saw no token and returned 400 Bad Request. Affected any install without nginx+TLS
(direct port 5000 access).

Fix: SESSION_COOKIE_SECURE is now an env-var flag (default false). install.sh
sets it to true only when SSL was enabled during setup. Upgrading installs that
lack the key in .env automatically get false via the env-var default.

v1.0a2.71 — Fix: Trade Wars 2 player database silently wiped on player deletion (May 2026)

json-client.js treated db.write(scope, key) with no value as setting the key to
undefined. JSON.stringify omits undefined-valued keys, so the players array was
deleted from tw2.json the first time any player was removed from the game. Two call sites
triggered this: DeletePlayer (which flushes the modified players array) and the kill
handler (which flushes a modified team record). Universe data (sectors, ports,
planets, etc.) was unaffected because those writes always supply an explicit value.

Fix: json-client.js now treats a no-value write(scope, key) as "mark dirty and save",
preserving the in-place mutation already made to the cached reference. Added null guards in
LoadPlayer, RankPlayers, and MatchPlayer for resilience, and a warning log when a db
file exists but fails to parse.

v1.0a2.70 — Fix: DATABASE_URL from EnvironmentFile not overridden correctly (May 2026)

serve.py guarded the DATABASE_URL override with if not os.environ.get('DATABASE_URL').
Systemd's EnvironmentFile loads .env before the process starts, so DATABASE_URL is
always set (even to a stale/relative value from a preserved .env), and the conditional
never triggered. Fix: always set DATABASE_URL from serve.py's own install dir path.
Operators who need Postgres or a non-default path can set ANETBBS_DB_URL instead.
Added diagnostic log lines (INSTALL_DIR, DATABASE_URL, DB file accessibility).