Full Install Guide
Tested on Debian 12 / Ubuntu 22.04+. Should work on any Linux with Python ≥3.10.
1. System dependencies
sudo apt update
sudo apt install -y python3 python3-venv python3-pip python3-dev \
build-essential libffi-dev git rsync curl \
openssh-client lrzsz
(rsync/curl/openssh-client are used by the update flow;
lrzsz provides X/Y/Zmodem for terminal file transfers.)
Optional, but unlock features:
# DOS doors via DOSBox (any of these — auto-detected)
sudo apt install -y dosbox-staging # preferred
# or: sudo apt install -y dosbox-x
# or: sudo apt install -y dosbox
# Synchronet .js doors (full compatibility)
# Install Synchronet itself: see https://wiki.synchro.net/install:nix
# Set SBBSEXEC to the dir containing jsexec, or symlink jsexec into PATH.
# Node.js fallback for Synchronet doors when jsexec is unavailable
sudo apt install -y nodejs
# Clamav for upload virus scanning
sudo apt install -y clamav clamav-daemon
sudo systemctl enable --now clamav-freshclam clamav-daemon
# LHA archive description extraction
sudo apt install -y lhasa
# Sixel images in terminal RSS reader (SyncTERM, IcyTERM, foot, mlterm, etc.)
# Provides img2sixel — without it the terminal RSS reader is text-only.
sudo apt install -y libsixel-bin
2. Get the code + Python deps
sudo mkdir -p /opt/anetbbs
sudo chown $USER /opt/anetbbs
cd /opt/anetbbs
git clone <your-repo-url> . # or extract the release tarball here
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -e .
Optional Python extras (for full archive support in the FILE_ID.DIZ extractor):
pip install py7zr rarfile
3. Configure
Generate a real secret and set it (the app refuses to boot in production
mode without one):
export SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"
Other useful env vars (defaults shown):
export BBS_NAME="My Cool BBS"
export WEB_PORT=5000
export TELNET_PORT=2233
export SSH_PORT=2234
export MSP_PORT=18 # privileged
export SYSTAT_PORT=11 # privileged (UDP)
export DATABASE_URL="sqlite:////opt/anetbbs/data/anetbbs.db"
4. First start
cd /opt/anetbbs
source venv/bin/activate
anetbbs-web
Watch the log for:
INITIAL ADMIN ACCOUNT CREATED
username: admin
password: <random>
also written to: /opt/anetbbs/data/admin_password.txt
CHANGE THIS PASSWORD on first login!
Browse to http://<host>:5000/, log in as admin, change the password
under your profile, then configure echomail / file areas / doors.
5. Run as a systemd service
The unit templates in deploy/ hardcode User=anetbbs//opt/anetbbs —
create that dedicated system account and hand ownership over to it
before installing them (you built everything as your own login in
steps 2-4, which is fine, but the service itself shouldn't run as
your personal account):
sudo useradd -r -d /opt/anetbbs -s /usr/sbin/nologin anetbbs
sudo chown -R anetbbs:anetbbs /opt/anetbbs
Then install the unit templates — anetbbs.service is the unified
process for telnet/SSH/rlogin/FTP (which of those actually start is
driven by the *_ENABLED flags in .env, not by which unit you
install):
sudo cp deploy/anetbbs-web.service /etc/systemd/system/
sudo cp deploy/anetbbs.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now anetbbs-web anetbbs
journalctl -u anetbbs-web -f
(Older versions of this project shipped split anetbbs-telnet.service/
anetbbs-ssh.service units — those templates still exist in deploy/
for reference, but are legacy: they fought each other for ports and
are replaced by the unified anetbbs.service above. Don't install
them on a fresh setup.)
If you're enabling Finger and/or BinkP (BinkP/FidoNet setup is covered
in §8 below; Finger is a simple RFC 1288 per-user info query service,
see docs/PORTS.md), they run as their own separate systemd units,
not part of anetbbs.service:
# Finger (RFC 1288) — only if you plan to enable it
sudo cp deploy/anetbbs-finger.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now anetbbs-finger
# BinkP (FidoNet inbound mail) — only if you're joining a FidoNet-style network
sudo cp deploy/anetbbs-binkp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now anetbbs-binkp
Both templates already carry AmbientCapabilities=CAP_NET_BIND_SERVICE
where needed (Finger binds the privileged port 79) so they don't need
to run as root.
6. Privileged ports (MSP/SYSTAT)
Ports < 1024 require root or a capability. Pick one:
A. setcap on the python binary (simplest)
sudo setcap 'cap_net_bind_service=+ep' \
/opt/anetbbs/venv/bin/python3
sudo systemctl restart anetbbs-web
B. systemd AmbientCapabilities (cleanest)
Edit /etc/systemd/system/anetbbs-web.service, add inside [Service]:
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
sudo systemctl daemon-reload
sudo systemctl restart anetbbs-web
C. High port + iptables redirect
export MSP_PORT=1118
export SYSTAT_PORT=1011
sudo iptables -t nat -A PREROUTING -p tcp --dport 18 -j REDIRECT --to-port 1118
sudo iptables -t nat -A PREROUTING -p udp --dport 11 -j REDIRECT --to-port 1011
# Persist with `iptables-save | sudo tee /etc/iptables/rules.v4` (debian) or netfilter-persistent
Verify it's working:
sudo ss -ltnp | grep ':18 ' # TCP listener
sudo ss -lunp | grep ':11 ' # UDP listener
7. nginx reverse proxy (recommended for production)
deploy/anetbbs-nginx.conf.template is a starting point — copy to
/etc/nginx/sites-available/anetbbs, edit server_name and the cert
paths, symlink to sites-enabled, nginx -t && systemctl reload nginx.
This puts TLS in front of the Flask app and isolates the web socket from
the privileged ports.
8. Inbound BinkP / FTN
If you participate in FidoNet, install + enable anetbbs-binkp.service
(see §5 above), forward TCP 24554 (BinkP), and put your linked node
configs in /admin/echomail/networks. See docs/06-echomail.md.
9. FTP file-area access (optional)
FTP isn't a separate systemd unit — it runs as a background thread
inside the same unified anetbbs.service process as telnet/SSH/rlogin
(see anetbbs/main.py / anetbbs/ftp/server.py), gated by
FTP_ENABLED. To turn it on:
export FTP_ENABLED=true
export FTP_PORT=21 # privileged — see §6 for the setcap/
# AmbientCapabilities/iptables options.
# NOTE: unlike MSP/SYSTAT, FTP runs in
# anetbbs.service, not anetbbs-web.service
# — if you use the AmbientCapabilities
# option (§6.B), edit anetbbs.service.
export FTP_PASV_PORTS=40000-40050 # passive-mode data channel range —
# open this range on your firewall too
export FTP_ANON_ENABLED=true # read-only anonymous access to public areas
export FTP_ROOT_DIR=data/ftp_root # virtual FTP tree, rebuilt on every start
export FTP_BANNER="ANetBBS FTP — file areas"
Optional FTPS (AUTH TLS) — set both to enable, reusing the same cert
nginx uses:
export FTP_TLS_CERTFILE=/etc/letsencrypt/live/yourdomain/fullchain.pem
export FTP_TLS_KEYFILE=/etc/letsencrypt/live/yourdomain/privkey.pem
Put these in .env (not just your shell) so they survive a service
restart, then sudo systemctl restart anetbbs. pyftpdlib>=2.0.0 is
already a normal dependency (installed by pip install -e . in step
2) — if it's missing, FTP logs a warning and stays disabled rather
than crashing the rest of the BBS. See docs/PORTS.md for the full
port table and firewall rules.
Troubleshooting
MSP: cannot bind ... Permission deniedin bbs.log → see step 6.SECRET_KEY is the dev defaultwarning → setSECRET_KEYenv var
(orRuntimeErrorwill hit you in production mode)./admin/echomail/...returns 500 after upgrading → restart the web
service so the auto-migration adds new columns.- Synchronet door fails silently → check the log for the door child's
stderr; if no jsexec is found, the Node.js shim runs but doesn't cover
every Synchronet API. Install Synchronet for full compat. - BotWars / RDQ3 fails with
EACCES: permission deniedreading
sbbs_stubs/sbbsdefs.jsor writing a save file → service user
needs read/write group access.install.shsets this on every run;
if you used a manual rsync deploy that reset the perms, run:
bash sudo chmod -R g+rX,o+rX /opt/anetbbs/anetbbs/games/sbbs_stubs sudo chmod -R g+rwX /opt/anetbbs/doors bbs.logPermissionError → service user needs write access to the
install dir.install.shchowns on every run; if you re-pointed the
unit file at a different install dir, mirror the perms there.- Web service stuck in restart loop with
EADDRINUSEon :5000 →
an old gunicorn worker is leaking past the master. Our systemd unit
ships withKillMode=mixedto prevent this, but if you adopted an
older unit file, addKillMode=mixedandRestartSec=10to
[Service]anddaemon-reload. - MRC
<no name>in Synchronet's IM display → Synchronet IDENTs
(RFC 1413) the sender to look up "real name". ANetBBS doesn't ship
an identd; this is a known cosmetic-only limitation.