Quick Start
Don't have root/sudo on this box, or would rather not run a
2,000+ line script as root? See
01b — No-Root Quick Start instead — a
complete, verified path to a fully working BBS (web UI, telnet, SSH)
using nothing but your own account. The rest of this page assumes
you have (or are fine using)sudo.
Requirements
- Linux (Ubuntu 22.04+ or Debian 12+ recommended)
- Python 3.10+ (3.12 preferred — the installer looks for
python3.12
first, falling back to 3.11, 3.10, then plainpython3) - ~500 MB disk for app + venv, plus space for
data/ - Open TCP ports for any protocols you want exposed
(default: 5000 web, 2233 telnet, 2234 SSH, 513 rlogin,
21 + 40000-40050 FTP if enabled)
Fresh install
# extract the release tarball
tar xzf ANetBBS-v1.0.29.tar.gz
cd ANetBBS-v1.0.29
# run the installer
sudo bash install.sh
The wizard asks every question it needs, roughly in this order:
- Install mode (
production/behind/test) — this is the
first question and changes several other defaults (web port,
whether nginx+SSL get offered, etc.).production= full public BBS
with HTTPS via Let's Encrypt;behind= you already have your own
reverse proxy/TLS in front;test= local/LAN, no domain needed. - BBS name + description
- System service user + install directory
- Sysop username (your own login handle) + sysop password
(leave blank to auto-generate one — it's written to
data/admin_password.txteither way) - Domain / hostname + web / telnet / SSH ports
- Enable Telnet? Enable SSH? Enable nginx reverse proxy?
- Install the MRC bridge (inter-BBS chat)?
- Enable inter-BBS Instant Messaging (MSP/SYSTAT — privileged ports
18/11)? Enable Finger (privileged port 79)? Enable BinkP (FidoNet
inbound mail, port 24554)? - (if nginx enabled) Enable SSL via Let's Encrypt?
- Optional extras: DOSBox-staging (DOS door games), dosemu2
(door_dosemugame type — TW2002-style doors needing a real
FOSSIL/COM1), ClamAV (upload virus scanning), lhasa (.lzh archive
descriptions), chafa + libsixel-bin (terminal image viewing — RSS
reader and Image Gallery), Mystic BBS runtime (.mps door support) —
each its own yes/no, all default no. - Configure UFW firewall to open the ports you just enabled?
(default no) — this prompt only appears inproductionmode;
testandbehindinstalls skip it (test mode is local-only, and
behindmode assumes your own reverse proxy/firewall already
handles this).
Number of concurrent terminal nodes (telnet/SSH/rlogin) and echomail
enable/disable are not wizard prompts — they default on
(BBS_NODES=8, ECHOMAIL_ENABLED=true) and are changed later via
.env or Admin → Settings, not during install. (Door games have
their own, separate concurrency cap, GAMES_MAX_NODES=10, also not a
wizard prompt.)
It then, without any further prompts:
- Generates a strong random
SECRET_KEYand writes.env(skips
this if.envalready exists — re-run with--forceto
regenerate it). - Creates
data/,logs/, etc. - Builds a Python venv and runs
pip install -e .. - Initializes the SQLite database and seeds your admin user.
- Installs systemd unit files (paths and service user substituted for
this install — no/opt/anetbbshardcoding unless that's where
you actually installed it) and enables + starts every service you
said yes to — there's no separate manualsystemctl enable --now
step needed. - Runs a set of post-install health checks and prints a summary.
Visit http://<your_host>:<web_port>/ (port 5000 by default in
production mode, 8080 in test/behind mode) and log in with the
sysop credentials from the wizard.
Re-running the wizard
Safe to re-run. It won't touch an existing .env — it just skips
regenerating it and prints "already exists — skipping (use --force
to overwrite)". Pass --force if you actually want it to overwrite
your current config.
Alternative: the Python installer toolchain
Separate from install.sh/update.sh above, four console-script
entry points ship with the package itself and become reachable the
moment you pip install -e . (e.g. via the manual-install path in
docs/INSTALL.md):
anetbbs-install(anetbbs/installer/wizard.py) — a shorter,
self-contained interactive Python installer, and the basis of the
no-root path in 01b. Asks install
directory, BBS branding, ports,BBS_NODES, echomail on/off, and
sysop credentials; writes.env, creates the venv, runs
pip install -e ., initializes the database + seeds the sysop
account, offers a choice of how to keep it running (system-wide
systemd — needs root; a systemd user service — no root, survives
logout vialoginctl enable-linger; or none at all), and attempts
/usr/local/bincommand symlinks (best-effort — silently skipped,
not fatal, if that directory isn't writable without root).anetbbs-upgrade(anetbbs/installer/upgrade.py) — point it at a
release tarball or extracted directory. Backs updata/,.env, and
logs/to a timestamped directory, rsyncs the new code in, reinstalls
Python deps, runs schema migrations, restarts services, probes
/healthz, and offers automatic rollback to the backup if the health
check fails.anetbbs-symlinks/anetbbs-cleanup(symlinks.py,
cleanup.py) — smaller helpers the two wizards above call
internally; also runnable standalone.
install.sh and update.sh never call any of these — it's a
completely separate, lighter-weight toolchain. Reach for it for a
minimal or scripted install where the long interactive install.sh
prompt sequence (install-mode selection, nginx, optional-dependency
prompts, etc.) is more than you need.
Known gaps vs. install.sh — worth knowing before you pick this
path:
- No install-mode concept at all (no
production/behind/test
choice) — it always writesFLASK_ENV=production. - No nginx or Let's Encrypt/SSL prompts — TLS termination is entirely
on you afterward. - No Finger or BinkP prompts — neither gets set up; do that by hand
afterward (docs/INSTALL.md§8, §9). MSP/SYSTAT are explicitly
written as disabled (MSP_ENABLED=false/SYSTAT_ENABLED=false) —
both bind privileged ports (18/11) this wizard has no way to grant
capability for, so leaving them atconfig.py's own default of
truewould log a permission-denied bind failure on every boot for
every install this wizard creates, root or not. Turn them back on
in.envif you have root and want them (docs/INSTALL.md§6
covers the capability grant). - No UFW/firewall step.
wizard.pywritesIDLE_TIMEOUT_SECONDS=0into.env— that's
different from theConfigclass default of1800(30 minutes)
that aninstall.sh/manual install without an explicit override
gets.0means idle terminal sessions are never force-disconnected;
if you use this installer, that's worth changing on purpose rather
than by surprise.
If you want everything install.sh sets up — nginx, SSL, UFW,
MSP/SYSTAT/Finger/BinkP — use install.sh. The Python installer
toolchain is a lighter, scriptable alternative, not a superset of it.
Putting nginx in front
Copy deploy/anetbbs-nginx.conf.template to
/etc/nginx/sites-available/anetbbs.conf, edit the server_name,
symlink it to sites-enabled/, and reload nginx. The template handles:
- HTTPS via certbot
- WebSocket upgrade for SocketIO + the MRC bridge
auth_requestgating of the MRC WebSocket so only logged-in users
can chat
(If you picked production mode in the wizard, this is already done
for you.)
What the install lays down
| Path | Purpose |
|---|---|
/opt/anetbbs/ |
code (default) |
data/ |
uploads, avatars, SQLite DB |
data/personal_pages/ |
sysop + per-user web pages |
data/admin_password.txt |
auto-generated sysop password (if you left it blank) |
logs/ |
app logs |
venv/ |
Python virtual env |
.env |
runtime config (mode 0600) |
Migrating users from another BBS
Coming from Synchronet or Mystic and want to bring your existing user
accounts over instead of starting from scratch? anetbbs-import-users
(installed as a console script; also runnable as python
tools/import_users.py) reads a Synchronet user.dat or Mystic
users.dat file directly and creates matching ANetBBS accounts.
# Dry-run first — shows what would be imported, touches nothing
anetbbs-import-users --dry-run synchronet /sbbs/data/user/user.dat
# Live import, with placeholder emails generated from your domain
anetbbs-import-users synchronet /sbbs/data/user/user.dat \
--email-domain mybbs.net
Passwords are never migrated (the two systems use incompatible
hashing) — each imported account gets a random 16-character temporary
password, written to a CSV report (--report, default
import_report.csv) alongside each user's handle, so you can notify
them individually. --on-conflict skip|update|rename controls what
happens when an imported username already exists locally.