Skip to content

Configuration reference

Every configuration knob Supply Drop BBS exposes, with type, default, and behaviour. The example file at config.example.toml is a runnable starting point; this document is the dictionary.

Status: stub. Each section will be fleshed out as the corresponding code lands. Sections marked TBD are placeholders.

Format and overlay

Format: TOML. See ADR-0008.

Configuration sources, in increasing priority:

  1. Compiled-in defaults
  2. The TOML file (resolved per the search order in [file resolution](#file-resolution))
  3. Environment variables
  4. Command-line flags

Each source can override settings from a lower-priority source. Operators see what's actually in effect via:

sh
supply-drop-bbs config show

File resolution

The first file found in this order is used:

  1. The path given to --config <PATH> on the command line
  2. The path given by the SUPPLY_DROP_CONFIG environment variable
  3. ./config.toml (the current working directory)
  4. /etc/supply-drop-bbs/config.toml (system install)
  5. ~/.config/supply-drop-bbs/config.toml (user install)

If none of these exist and no init flag is set, the BBS exits with an error pointing at the recommended location.

Environment variable overrides

Pattern: SUPPLY_DROP__SECTION__KEY=value. Double underscores separate hierarchy levels.

Examples:

Env varEquivalent TOML
SUPPLY_DROP__BBS__NAME="Foo BBS"[bbs] name = "Foo BBS"
SUPPLY_DROP__DATABASE__PATH=/srv/bbs.db[database] path = "/srv/bbs.db"
SUPPLY_DROP__LOGGING__LEVEL=DEBUG[logging] level = "DEBUG"
SUPPLY_DROP__PLUGINS__WEB__BIND=:8080[plugins.web] bind = ":8080"

Values are parsed using TOML's coercion rules. A bare integer 8080 becomes the integer 8080; a quoted string "8080" becomes the string. Booleans use TOML conventions (true/false).

Command-line flags

The CLI accepts a small set of overrides:

FlagEffect
--config <PATH>Use this config file
--data-dir <PATH>Override [bbs] data_dir
--log-level <LEVEL>Override [logging] level (announced loudly)
--bind-cli <PATH>Override [plugins.cli] socket
--bind-web <ADDR>Override [plugins.web] bind (only if web feature)
--no-webDisable the web plugin even if the feature is built

Subcommands:

  • supply-drop-bbs setup - interactive setup wizard (device type, radio config, systemd install)
  • supply-drop-bbs config check - validate config without starting
  • supply-drop-bbs config show - print the effective config
  • supply-drop-bbs migrate - apply pending DB migrations
  • supply-drop-bbs backup - trigger a manual backup
  • supply-drop-bbs version - print version + features compiled in

Top-level sections

The config is split into the following top-level sections:

SectionPurpose
[bbs]System identity, data paths, room defaults
[location]GPS coordinates broadcast to the radio on connect
[database]DB path, pool sizes, PRAGMA overrides
[logging]Level, file path, rotation, per-target overrides
[security]Argon2 parameters, session lifetimes, rate limits
[backup]Schedule, retention, target directory
[plugins.cli]CLI transport: socket path, permissions
[plugins.mesh]Mesh transport: bridge address, retry policy
[plugins.web]Web admin: bind, CSRF, CSP, config editor path
[plugins.<other>]Per-plugin sections for any other loaded plugins

Sections referencing plugins not loaded at compile time are an error (typo protection). The compiled-in feature set determines which plugin sections are valid.

[bbs] - system identity

KeyTypeDefaultRequiredDescription
namestring"Supply Drop BBS"noDisplay name shown to users on connect
data_dirpath/var/lib/supply-drop-bbs (root) or ~/.local/share/supply-drop-bbs (user)noWhere the BBS stores its data
starting_roomstring"Lobby"noRoom a newly logged-in user lands in
welcome_msgstring"Welcome to {name}."noBanner shown on connect; supports {name} substitution
timezonestring"UTC"noDisplay timezone for sysop UI; storage is always UTC

[location] - GPS coordinates

When set, the mesh transport sends your node's coordinates to the radio immediately after each connection is established, so it appears on the MeshCore map in LoRa adverts. Takes effect on the next mesh transport reconnect — no server restart needed.

KeyTypeDefaultRequiredDescription
latitudefloat(unset)noWGS-84 latitude, degrees (-90…90)
longitudefloat(unset)noWGS-84 longitude, degrees (-180…180)

Both keys must be set together; setting only one has no effect. Remove both (or omit the section) to stop broadcasting GPS coordinates.

The setup wizard prompts for these during initial configuration and writes them here. They can also be edited live from the web admin's Settings page without restarting the server.

toml
[location]
latitude  = 37.7749
longitude = -122.4194

[database] - persistence

KeyTypeDefaultRequiredDescription
pathpath<data_dir>/bbs.sqlitenoSQLite file location
read_pool_sizeintegercpu_count + 2noRead-only connection pool size
busy_timeout_msinteger5000noSQLite busy_timeout in milliseconds
synchronousenum"NORMAL"no"NORMAL" / "FULL" / "OFF". See ADR-0005.
wal_autocheckpointinteger10000noWAL pages between checkpoints
journal_size_limit_bytesinteger67108864noMax WAL file size

[logging] - observability

KeyTypeDefaultRequiredDescription
levelenum"INFO"noRoot level: TRACE/DEBUG/INFO/WARN/ERROR
filepath<data_dir>/log/bbs.lognoLog file path
max_bytesinteger10485760noRotation size per file (10 MB)
backup_countinteger5noNumber of rotated files to keep
formatenum"compact"no"compact", "pretty", "json"
targetstable{}noPer-target level overrides; see below

Per-target overrides example:

toml
[logging.targets]
"supply_drop_bbs::transport::mesh" = "DEBUG"
"sqlx::query" = "INFO"
"meshcore_companion::frame" = "WARN"

See ADR-0009 for the no-silent-overrides rule.

[security] - authentication and rate limiting

KeyTypeDefaultRequiredDescription
argon2_memory_kibinteger19456noArgon2 memory cost (~19 MB; tuned for ~250ms on Pi 4)
argon2_iterationsinteger2noArgon2 time cost
argon2_parallelisminteger1noArgon2 parallelism
session_lifetime_web_secsinteger43200noWeb session lifetime (12 hours)
session_lifetime_mesh_secsinteger259200noMesh session lifetime (3 days)
login_rate_per_mininteger5noFailed login attempts per minute per source
command_rate_per_mininteger60noCommands per minute per session

[backup] - disaster recovery

KeyTypeDefaultRequiredDescription
enabledbooltruenoWhether the periodic backup runs
interval_hoursinteger6noHours between automatic backups
directorypath<data_dir>/backupsnoWhere backup files go
keep_dailyinteger7noDaily backups to retain
keep_weeklyinteger4noWeekly backups to retain

[plugins.cli] - CLI transport

KeyTypeDefaultRequiredDescription
enabledbooltruenoWhether to start the CLI listener
socketpath<data_dir>/cli.socknoUnix socket path
socket_modestring"0600"noOctal mode of the socket file
socket_ownerstring(process owner)noUsername/UID to chown socket to

[plugins.mesh] - mesh transport

Connection type

KeyTypeDefaultRequiredDescription
enabledbooltruenoWhether to start the mesh transport
connection_typeenum"serial"noHow to reach the radio: "serial", "tcp", or "hat". See ADR-0013.
command_prefixstring""noOptional single-character prefix for BBS commands (e.g., "!"). Empty string means no prefix - every message is treated as a command.

Serial mode (connection_type = "serial")

Used for USB-native MeshCore devices (Heltec V3, T-Beam, etc.) that run companion-frame firmware. No pymc_core required.

KeyTypeDefaultRequiredDescription
serial_portstring"/dev/ttyACM0"noSerial device path. The setup wizard auto-detects this.
baud_rateinteger115200noSerial baud rate

TCP / HAT mode (connection_type = "tcp" or "hat")

Used when pymc_core is running separately (Pi HAT setups or any external CompanionFrameServer). "hat" and "tcp" are identical at the transport level; the distinction tells the setup wizard to install pymc_core as a systemd dependency.

KeyTypeDefaultRequiredDescription
addrstring"127.0.0.1:5000"noAddress of the CompanionFrameServer
reconnect_delay_initial_msinteger1000noInitial reconnect delay after disconnect (ms)
reconnect_delay_max_msinteger60000noMaximum reconnect delay after repeated failures (ms). Backoff is exponential between initial and max.
app_target_versioninteger3noCompanion protocol version to negotiate. Leave at default unless you know the bridge speaks an older version.

HAT pin configuration (connection_type = "hat")

Pin config lives under [plugins.mesh.hat]. The setup wizard populates this from the chosen preset; manual overrides are supported.

KeyTypeDefaultRequiredDescription
presetstring-yes (hat)HAT model: "zebrahat", "meshadv-mini", "meshadv", "waveshare", "uconsole", "custom"
bus_idinteger0noSPI bus
cs_pininteger-customSPI chip-select GPIO (BCM numbering)
reset_pininteger-customRadio reset GPIO
busy_pininteger-customRadio busy GPIO
irq_pininteger-customRadio IRQ GPIO
txen_pininteger-1noTX-enable GPIO (-1 = not connected)
rxen_pininteger-1noRX-enable GPIO (-1 = not connected)

Preset defaults (set automatically by the wizard; override only if your wiring differs from the standard layout):

PresetCSResetBusyIRQNotes
zebrahat24172722
meshadv-mini8242016
meshadv21182016TXEN=13, RXEN=12
waveshare21182016TXEN=13, RXEN=12
uconsole-1252426bus_id=1, hardware CS

[plugins.meshtastic] - Meshtastic transport (only if transport-meshtastic feature enabled)

Meshtastic is a separate radio protocol from MeshCore. Both can run simultaneously on the same BBS — each talks to its own radio device. Meshtastic radios connect either via USB serial or TCP to a running meshtasticd instance.

Connection type

KeyTypeDefaultRequiredDescription
enabledboolfalsenoWhether to start the Meshtastic transport
connection_typeenum"serial"noHow to reach the radio: "serial" or "tcp"
command_prefixstring(unset)noOptional single-character prefix for BBS commands
welcome_messagestringsee defaultnoGreeting sent to a node on first contact
max_payload_bytesinteger220noMaximum UTF-8 bytes per outbound text packet
node_credential_ttl_daysinteger14noDays a node auto-login credential remains valid (0 disables)
hop_limitinteger3noHop limit for outbound replies and notifications
want_ackbooltruenoRequest Meshtastic radio-layer acknowledgements
reconnect_delay_initial_msinteger1000noInitial reconnect delay after disconnect
reconnect_delay_max_msinteger60000noMaximum reconnect delay after repeated failures

Serial mode (connection_type = "serial")

Direct USB connection to a Meshtastic radio (Heltec V3, T-Beam, RAK4631, etc.). No daemon required.

KeyTypeDefaultRequiredDescription
serial_portstring"/dev/ttyACM0"noSerial device path
baud_rateinteger115200noSerial baud rate

TCP mode (connection_type = "tcp")

Connects to a running meshtasticd instance or any Meshtastic node that exposes a TCP stream. Default port for meshtasticd is 4403.

KeyTypeDefaultRequiredDescription
addrstring"127.0.0.1:4403"noAddress of the meshtasticd listener

Example

toml
[plugins.meshtastic]
enabled         = true
connection_type = "serial"
serial_port     = "/dev/ttyUSB0"

[plugins.web] - admin web (only if admin-web feature enabled)

KeyTypeDefaultRequiredDescription
enabledbooltruenoWhether to start the web listener (set false to disable without recompiling)
bindstring"127.0.0.1:8080"noAddress to bind. Default 127.0.0.1.
external_originstring(none)when behind reverse proxyPublic origin URL for CSRF / cookie policy
cookie_securebooltruenoSecure flag on cookies. Set false only for local dev.
prometheusboolfalsenoExpose /metrics
cspstring(built-in strict CSP)noContent-Security-Policy override
config_pathpath(none)noAbsolute path to the config file the web admin's Settings page reads and writes. Must be writable by the server process. When unset, the Settings page loads in read-only mode and explains the gap.

Enabling the Settings page

The Settings page (/settings in the web admin) lets sysops view and edit the config file through a form UI without touching the TOML directly. Point config_path at the same file the BBS is reading:

toml
[plugins.web]
config_path = "/etc/supply-drop-bbs/config.toml"

The server process must have write permission to that file. If it does not, the Settings page opens in read-only mode and shows the exact chown/chmod commands needed to fix the permission. Changes written through the UI require a restart to take effect (the GPS [location] section is the one exception — it applies on the next mesh reconnect).

If bind is 0.0.0.0 and external_origin is unset, startup fails with an error: binding to all interfaces without specifying the public origin makes CSRF protection unsafe.

[[plugins.process]] - externally-spawned transport plugins (only if transport-process feature enabled)

Process transport plugins are external executables that speak the Supply Drop IPC protocol over stdin/stdout. Each plugin is a separate entry in an array table. Manage them with supply-drop-bbs plugin add/remove/enable/disable or directly in the web admin UI.

toml
[[plugins.process]]
name              = "my-telnet"           # unique stable identifier
command           = "/usr/local/bin/my-telnet-plugin"
args              = ["--port", "23"]      # optional
enabled           = true
restart_on_crash  = true                  # respawn automatically on non-zero exit
restart_delay_secs = 5                    # seconds to wait before respawn

[[plugins.process]]
name    = "my-aprs"
command = "/opt/aprs-bridge/aprs-bridge"
enabled = false                           # start disabled; enable via web UI
KeyTypeDefaultRequiredDescription
namestringyesUnique stable identifier. Used in logs, web UI, and CLI output. Lowercase ASCII with hyphens.
commandstringyesPath to the plugin executable.
argsstring[][]noArguments passed verbatim to the executable.
enabledbooltruenoWhether to start this plugin on BBS startup.
restart_on_crashbooltruenoRespawn the process when it exits with a non-zero code.
restart_delay_secsinteger5noSeconds to wait before respawning after a crash.

See PROCESS_TRANSPORTS_OPS.md for plugin installation, the IPC protocol, and troubleshooting.

Validation rules

Validation runs at startup, before any service starts. Failures exit the process with a clear error message including:

  • TOML well-formedness. Errors include file:line:col.
  • Required keys present. Every field without a default fails with the missing path (e.g., plugins.web.bind).
  • Type correctness. 8080 is not a string; "foo" is not a port.
  • Range checks. Ports 1..=65535; positive sizes; valid enum variants.
  • Cross-references. Plugin sections must correspond to loaded plugins; referenced rooms must exist; backup directory must be writable.
  • Permission/ownership. Files containing secrets must be mode 0600 or stricter on Unix.
  • Conflicting settings. bind = "0.0.0.0" without external_origin is an error, not a warning.

Sysop bootstrap

The first sysop account is created during supply-drop-bbs init. It is not in the config file (passwords don't go in TOML). The init flow prompts for username + password and creates the account in the DB.

To bootstrap a sysop after init (e.g., when migrating between machines), use:

sh
supply-drop-bbs admin create-sysop --username <name>

This prompts for a password and creates the account, gated on having direct DB write access (i.e., it requires running on the machine, with read access to the DB file). It is not an HTTP endpoint.

Reload behaviour

Most config changes require a restart. The keys that can change without restart are:

  • [logging] level (via SIGHUP, future enhancement)
  • [security] login_rate_per_min (via SIGHUP, future enhancement)

All other keys require a process restart. We document this on each key as the implementation lands. TBD.

See also

Released under the Apache 2.0 + Commons Clause License.