PHP Classes

File: docs/PacketBBS.md

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   docs/PacketBBS.md   Download  
File: docs/PacketBBS.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 40,368 bytes
 

Contents

Class file image Download

PacketBBS Gateway

Table of Contents

PacketBBS is BinktermPHP's compact text gateway for PacketBBS, MeshCore, and similar packet radio or mesh text bridges. It exposes BBS mail functions through short command/response messages instead of a full-screen terminal UI.

The gateway is designed for low-bandwidth radio links:

  • short ASCII responses
  • compact message lists
  • one-line commands
  • paged output with `M` / `MORE` and `P` / `PREV`
  • compose mode that accepts one body line per packet

PacketBBS is not a web frontend and is not an ANSI terminal shell. A separate radio bridge sends HTTP requests to BinktermPHP and relays the plain-text response back to the radio network.

That makes PacketBBS an access method, not a separate mini-BBS. It reaches into the same platform data as the browser UI and terminal services, but does so through terse command/response exchanges that fit radio and mesh conditions.

Architecture

The bridge talks to BinktermPHP through:

POST /api/packetbbs/command
GET  /api/packetbbs/pending

Every bridge request must include:

Authorization: Bearer <node-api-key>

The API key belongs to a registered PacketBBS node in the admin UI. The key is stored server-side as a SHA-256 hash and is only shown once when generated.

Bridge Adapters

PacketBBS requires a bridge adapter to connect a radio network to the BinktermPHP HTTP API.

| Adapter | Radio network | Repository | Status | |---|---|---|---| | MeshCore Bridge | MeshCore | awehttam/binktermphp-meshcorebridge | Available | | Meshtastic Bridge | Meshtastic (via TCP or USB serial) | awehttam/binktermphp-meshtasticbridge | Experimental | | AX.25 KISS Bridge | AX.25 packet radio (hardware or software TNC) | awehttam/binktermphp-ax25kiss | Experimental |

Bridge Node vs Sender Node

PacketBBS supports a bridge device serving more than one radio sender.

  • `bridge_node_id` is the registered bridge device ID used for API-key authorization.
  • `node_id` is the sender/session ID for the radio operator using the bridge.

If bridge_node_id is omitted, PacketBBS uses node_id for both authorization and the user session.

Sessions are keyed by node_id, so multiple radio users behind one bridge can have separate login and compose state as long as the bridge sends their distinct sender IDs.

Workflow: how PacketBBS fits into low-bandwidth access

  1. A bridge node receives a radio or mesh message from a remote operator.
  2. The bridge translates that short command into an authenticated HTTP request to BinktermPHP.
  3. PacketBBS reads or updates the same platform mail and session state used by the browser and terminal access methods.
  4. BinktermPHP returns a compact plain-text reply sized for low-bandwidth transport.
  5. The bridge relays that reply back across the packet or mesh network.

Sysop Setup

1. Configure PacketBBS Defaults

PacketBBS defaults live under packet_bbs in config/bbs.json and are configurable from the Admin ? BBS Settings ? Packet BBS Settings card:

{
  "packet_bbs": {
    "session_timeout_minutes": 15,
    "allow_guest_who": true
  }
}

Options:

| Option | Default | Meaning | |---|---:|---| | session_timeout_minutes | 15 | Inactive authenticated sessions are cleared after this many minutes. The next command returns Session expired. LOGIN again. | | allow_guest_who | true | Allows unauthenticated users to run WHO. If false, WHO requires login. |

Login failures are rate-limited per sender node: 5 failed attempts in 10 minutes blocks further attempts briefly. Successful login clears prior failures.

2. Register a Bridge Node

Go to:

Admin -> Packet BBS Nodes

Add a node:

| Field | Purpose | |---|---| | Node ID | The bridge device ID. For MeshCore this is the bridge node hash/ID. For AX.25 KISS use the bridge callsign (e.g. N0BBS-1). | | Handle / Callsign | A friendly name for the node. For MeshCore nodes this should match the node name in the app; it is used as the contact display name when the bridge QR-codes itself into another operator's contact list. Setting it to the BBS hostname is recommended. | | Interface Type | Output profile that controls line width and page size. Choose MeshCore, Meshtastic, or AX.25 TNC (KISS) for packet radio bridges. | | Location Description | Optional free-text location label shown on the public node directory and dashboard widget (e.g. "Lower Mainland BC"). | | Coordinates | Optional GPS coordinates used to place the node on the public node map. Not displayed directly to users. |

After creating the node, click the key button and generate an API key. Copy it immediately; it will not be shown again.

3. Configure the Bridge

> Bridge developers: this section is aimed at you. Sysops only need to supply the BBS URL and the API key generated in step 2.

Start with the bridge's configuration file. At minimum it needs the BBS URL and the API key for the registered node:

{
  "bbs_url": "https://your-bbs.example",
  "api_key": "paste-the-key-generated-in-step-2-here"
}

The bridge uses these to authenticate its requests to BinktermPHP. Refer to the bridge's own documentation for the full list of configuration options.

User Enrollment

Users must enable the PacketBBS authenticator before they can log in by radio.

Steps for the user:

  1. Log in to the web UI.
  2. Open `Settings -> Account`.
  3. Find `PacketBBS Authenticator`.
  4. Click `Set up authenticator`.
  5. Scan the QR code with a TOTP authenticator app, or enter the secret manually.
  6. Enter the 6-digit code to verify enrollment.

The authenticator issuer is:

<BBS Name> - PacketBBS

Radio login uses TOTP codes, not the web password.

Over-the-Air Authentication Security

> Warning: PacketBBS over radio should not be treated as a hardened secure channel. The login step uses TOTP, but the security of the session after login depends on the underlying transport and bridge behavior.

PacketBBS authenticates the user at login time with a TOTP code, then keeps a session associated with the sender node_id. Whether that session is resistant to hijacking depends on how trustworthy the transport's sender identity is, and on who can read or inject traffic on that network.

Sender spoofing after login

On transports such as AX.25/KISS, the sender identity is typically just the source callsign or node identifier carried in the frame. AX.25 does not provide cryptographic proof that the sender identity matches the station that actually transmitted it. Any operator with suitable radio hardware and software can transmit frames using another station's callsign.

This creates a session-hijacking exposure:

  1. A monitoring station observes a frame containing `LOGIN alice 123456`.
  2. The monitoring station transmits a frame using the same source callsign or sender ID as the logged-in station.
  3. The bridge receives that spoofed frame, looks up the active session for that sender ID, and executes the command as the logged-in user.

The TOTP code itself expires after 30 seconds, so replaying the login is only a narrow-window risk. However, commands sent after login do not require a fresh code. If the transport allows sender-ID spoofing, an attacker may be able to use the session for as long as it remains active.

Practical implications by transport

| Transport | Sender identity assurance | Traffic confidentiality | Status in BinktermPHP | Risk | |---|---|---|---|---| | AX.25 / KISS (hardware or Direwolf) | None | None | Experimental | High: sender spoofing is practical, so active sessions can be hijacked | | AX.25 / KISS over RF via igate | None | None | Experimental | High | | Meshtastic | Better than AX.25 for mesh membership control, but not validated here as a secure PacketBBS transport | Shared-channel encryption; not documented here as per-recipient PacketBBS protection | Experimental and currently untested | Unknown to moderate: do not assume PacketBBS session privacy or anti-spoofing properties have been verified | | MeshCore | Cryptographic node identity at the radio layer | Per-packet cryptographic protection at the radio layer | Available | Lower for on-air sender spoofing; still depends on trusted bridge and endpoints |

Meshtastic note

Meshtastic is not documented here as a fully secure PacketBBS transport, and BinktermPHP's Meshtastic bridge support is currently experimental and untested.

Meshtastic's own documentation describes channels as groups that share a channel name and encryption key, and states that packet payloads are encrypted using the channel's shared key. It also documents that nodes can send messages directly to a specific radio. Taken together, that means direct-addressed Meshtastic messages should not be assumed to be private in the same sense as end-to-end per-recipient encryption: any node that has the same channel key may be able to decrypt the payload.

In other words, Meshtastic may provide meaningful protection against casual over-the-air observation by stations that are not on the channel, but it should not currently be documented as providing strong private user-to-user confidentiality for PacketBBS sessions. Until this bridge path has been tested and reviewed, sysops should treat Meshtastic PacketBBS use as experimental, untested, and not suitable for sensitive actions.

MeshCore note

MeshCore provides stronger identity guarantees on the radio link than AX.25-style transports, so it materially reduces the specific risk of over-the-air sender spoofing. That said, this does not make the overall PacketBBS path universally secure. A compromised bridge, stolen device, or compromised endpoint can still defeat those protections. MeshCore should be described as reducing spoofing risk, not eliminating all security risk.

Mitigations and recommendations

  • Keep session_timeout_minutes short. The default of 15 minutes is reasonable; consider 5 to 10 minutes on higher-risk links.
  • Avoid sensitive actions over untrusted or shared RF transports.
  • Treat PacketBBS login as authentication only, not as end-to-end command protection.
  • Assume AX.25/KISS traffic can be spoofed and hijacked after login.
  • Treat Meshtastic cautiously. Its bridge support in this project is experimental and currently untested, and its shared-channel model should not be described as equivalent to per-user private messaging.
  • Prefer MeshCore where sender identity assurance matters.
  • Do not perform sysop or other high-trust operations over radio unless you fully trust the transport, bridge, and endpoints.

These risks are driven mostly by the underlying transport and bridge trust model, not by TOTP itself. Sysops should assess both who can inject traffic and who can decrypt traffic on their chosen radio network before enabling authenticated PacketBBS use on the air.

The practical attack surface is often smaller than an Internet-facing service because an attacker usually needs to know the radio settings, use compatible equipment, and be within RF or mesh reach. This reduces casual exposure, but it should not be treated as a substitute for cryptographic identity or confidentiality.

End-User Command Guide

PacketBBS is intentionally terse. Send HELP or H first:

H

Typical response:

GEN L user code | W | BU #
GEN U/Q | M/B
NET N | R/Y id | S to subj
ECHO A | T tag | P subj

Session Context

PacketBBS keeps lightweight session context per sender node. That means:

  • `AREA <tag>` or `T <tag>` makes that echoarea the current working area
  • `POST` can reuse the current area instead of requiring the tag every time
  • `REPLY` can reuse the current message when the target is unambiguous
  • `M`, `P`, and `B` continue the current list or message
  • guided flows like `POST` -> `Subj?` keep state until sent or cancelled

Example:

AREA LVLY_TEST
POST
Subj?
Testing from radio
Msg:
hello from field
+
/SEND
Posted to LVLY_TEST.

Command Tables

Global Context

| Full command | Short code | Description | |---|---|---| | HELP | H | Show general help or contextual help such as H MAIL or H U. | | FULLHELP | HF | Show verbose help with full command names. Aliases: HELPFUL, HELPFULL. | | LOGIN <user> <code> | L <user> <code> | Log in using PacketBBS TOTP. | | WHO | W | Show who is online. | | STATUS | U | Show current area, list, message, or draft state. | | BULLETINS | BU | List active bulletins. BU <id> reads bulletin number <id>. | | CHAT | C | Enter the default chat room. CHAT <room> enters a named room. CHAT <user> opens a DM with that user. CHAT LIST or CL lists available rooms. | | SEARCHAREAS <term> | SA <term> | Search echomail. Searches the current area if one is active, otherwise all areas. SA <area> <term> restricts to a specific subscribed area. | | SEARCHMAIL <term> | SM <term> | Search netmail for the logged-in user. | | WEATHER | WX | Show current weather for the bridge node's location (if coordinates are set). WX <city> looks up any city by name. | | QUIT | Q | Context-aware: exits the current area if one is active, or ends the PacketBBS session from the top level. Use the full word QUIT to end the session unconditionally from anywhere. | | WEBSITE | WEB | Show the BBS website URL. |

Area Navigation Context

| Full command | Short code | Description | |---|---|---| | AREAS | A or E | List subscribed areas. | | AREAS <search> | A <search> | Search subscribed areas by tag, description, or domain. If the text resolves to a subscribed area, it opens that area instead. | | AREA <tag> | AREA <tag> or T <tag> or ER <tag> | Open an area and make it the current working area. | | MORE | M | Show the next page of the current list or message. | | PREV | B, P, or PREV | Show the previous page of the current list or message. |

Netmail Context

| Full command | Short code | Description | |---|---|---| | MAIL | N, NM, or NETMAIL | List netmail. | | READ <id> | R <id> or NR <id> | Read a netmail or current-list message by ID. R without an ID reopens the current message when one is active. | | REPLY <id> | Y <id>, RP <id>, or NRP <id> | Reply to a specific message. REPLY without an ID replies to the current message when context is clear. | | SEND <user-or-ftn-address> <subject> | S <user-or-ftn-address> <subject> or NS <user-or-ftn-address> <subject> | Start a new netmail draft in one step. The destination may be a local user or a literal FTN address. |

Echomail / Current Area Context

| Full command | Short code | Description | |---|---|---| | AREA <tag> | T <tag> or ER <tag> | Open an area and set area context. | | READ <id> | R <id> | Read an echomail message by ID. R without an ID reopens the current message when one is active. | | REPLY <id> | Y <id> or RP <id> | Reply to a message by ID, or to the current message when no ID is given and context is clear. | | POST <tag> <subject> | EP <tag> <subject> | Start a post in an explicitly named area. | | POST <subject> | EP <subject> | Start a post in the current area using the whole remainder as the subject. | | POST | EP | Start a guided post flow in the current area and prompt for subject. |

Guided Flow Context

| Full command | Short code | Description | |---|---|---| | /SEND | . or /S | Send the current draft. | | /CANCEL | CANCEL or /C | Cancel the current draft or guided flow. | | STATUS | U | Show current draft target, subject, and body progress. |

Login

LOGIN <username> <6-digit-code>

Short form:

L <username> <6-digit-code>

Example:

LOGIN alice 123456

Success:

Hi alice. HELP for commands.

If the session is idle too long, log in again.

Online Users

WHO

Short form:

W

Lists users currently online. Depending on sysop configuration, this may be available before login.

Status

U

Shows the current working context. Example responses:

area LVLY_TEST
list echomail p1/3

or:

draft post LVLY_TEST
subj Testing from radio
2 lines

Netmail

List netmail:

MAIL

Aliases:

N
NM
NETMAIL

Example response:

MAIL 1/3
*12 Bob Re: Meeting
 15 Alice Files
R <id>, M

* means unread.

Read a message:

R 12

If a current message is already open, you can also send:

R

to reopen that message from the top.

The same replay behavior applies to NR and EM when they are used without an ID and a current message is active.

Compatibility aliases:

READ 12
NR 12

Reply:

REPLY 12

Compatibility aliases:

RP 12
NRP 12
Y 12

If a message is already open, REPLY without an ID uses the current message.

Start new netmail:

SEND <user-or-ftn-address> <subject>

Compatibility aliases:

S <user-or-ftn-address> <subject>
NS <user-or-ftn-address> <subject>

For a direct FTN destination, use the address in the first slot:

SEND 1:234/56 Test message

Echomail

List subscribed areas:

AREAS

Aliases:

A
E

Example response:

AREAS 1/3
FIDONET General Fidonet discussion
LVLY_CHAT@lovlynet Chat
LVLY_TEST@lovlynet Test echo area
AREA <tag>, M

The header shows the current page and total pages. If there are more pages, the footer shows AREA <tag>, M. Use M and P or B to navigate pages.

Networked areas appear as TAG@domain. Use M to page through all subscribed areas.

Area Search

To search for areas matching a keyword across name, description, and domain:

AREAS linux

Example response:

AREAS "linux" 1/1
LINUX General Linux discussion
LVLY_LINUX@lovlynet Linux users
AREA <tag>

The search term is preserved across M / P pages. To return to the full list, send AREAS without arguments.

List messages in an area:

AREA LVLY_TEST@lovlynet

Aliases:

T LVLY_TEST@lovlynet
ER LVLY_TEST@lovlynet

If the tag is unique for the user, the domain may be omitted:

AREA LVLY_TEST

Read an echomail message:

R 44

EM behaves the same way as R for the current message: if you already have a message open, EM without an ID reopens it from the top.

Reply:

REPLY 44

Post a new echomail message with an explicit area:

POST LVLY_TEST@lovlynet Testing radio post

Compatibility alias:

EP LVLY_TEST@lovlynet Testing radio post

If you already opened an area, PacketBBS remembers it. These are valid too:

AREA LVLY_TEST
POST Testing radio post

or guided:

AREA LVLY_TEST
POST
Subj?
Testing radio post
Msg:
hello from field

Compose Mode

Replying, sending netmail, and posting echomail enter compose mode.

Example:

RP 12
Replying to Bob.
Subj: Re: Meeting
Send lines. /SEND=send /CANCEL=abort

Guided POST can also prompt for the subject first:

POST
Subj?
Testing from field
Msg:

Then send one body line per radio message:

I can join tonight.

PacketBBS responds:

OK

Guided post flows reply with a shorter acknowledgement while the body is in progress:

+

Finish:

/SEND

Short form:

/S

Old-style . also sends:

.

Cancel:

/CANCEL

Short form:

/C

Old-style CANCEL also cancels.

Bulletins

When you log in, any unread bulletins are listed automatically:

Hi alice. HELP for commands.
2 unread bulletins:
#1 Welcome to the BBS
#3 Maintenance window tonight
BU to read

List all active bulletins at any time:

BU

Alias:

BULLETINS

Example response:

BULLETINS 3
*#1 Welcome to the BBS
 #2 Previous announcement
*#3 Maintenance window tonight
BU # to read

* marks bulletins you have not yet read.

Read a specific bulletin:

BU 1

Example response:

#1 Welcome to the BBS
Welcome! This BBS is...
BU for list

Reading a bulletin marks it as read for your account. The * will disappear from the list on your next BU.

Chat

PacketBBS supports real-time chat rooms and direct messages (DMs). Chat uses the same rooms as the web and terminal interfaces.

Enter the default room:

CHAT

Short form:

C

Enter a named room:

CHAT General

Open a DM with another user (by username):

CHAT alice

List available rooms:

CHAT LIST

Alias:

CL

Example room list response:

Rooms: General, Tech, Off-Topic
C <room> to enter

Once inside a room or DM, any text that is not a recognised command is posted as a message. Incoming messages from other users are delivered to your node via the outbound queue and arrive as separate pushes from the bridge.

In-Chat Commands

| Command | Short code | Description | |---|---|---| | CHAT | C | Refresh the current room or DM at the latest page. CHAT <room> switches to a different room. | | CHAT LIST | CL | List available rooms without leaving the current context. | | WHO | W | Show who is online. | | STATUS | U | Show the current chat context (room name or DM:<user>). | | MORE | M | Show older messages (page back in history). | | PREV | B or P | Show newer messages (page forward toward current). | | HELP | H or ? | Show in-chat help. | | Q or /C | | Exit the room or DM and return to the main context. Session stays active. | | QUIT | | End the PacketBBS session unconditionally. |

Any other text is posted as a chat message.

Switching Contexts

From inside any chat context you can jump directly to another room or open a DM without going back to the main context first:

CHAT Tech
CHAT bob

Weather

Show current weather for the bridge node's configured location:

WX

Look up any city by name:

WX Seattle
WX London, UK

Example response:

WX Seattle, US
Partly Cloudy 14°C/57°F
Wind SW 18kph Hum 72%

WX without a city uses the coordinates set on the bridge node in Admin ? Packet BBS Nodes. If no coordinates are configured, the command returns an error and prompts for a city name.

Weather uses the OpenWeatherMap API key configured under Admin ? Weather Report. If no key is configured, WX returns an error.

Search

Search Echomail

Search across all subscribed areas, or within a specific area:

SA <term>

SA LVLY_CHAT <term>

If a current area is active (set with AREA), SA <term> without an explicit area restricts the search to that area automatically.

Example:

SA linux

Response:

SA "linux" all 1/2
44 LINUX Bob kernel panic
12 LVLY_CHAT alice Re: linux
15 LVLY_CHAT alice linux tools
R <id>, M:more B:back

With a current area active:

AREA LVLY_CHAT
SA linux

Response:

SA "linux" LVLY_CHAT 1/1
12 LVLY_CHAT alice Re: linux
15 LVLY_CHAT alice linux tools
R <id>, RP <id>

Results from multiple areas show the area tag on each row. Use R <id> to read a result. M and B page through results.

Search Netmail

Search your own netmail:

SM <term>

Example:

SM meeting

Response:

SM "meeting" 1/1
*12 Bob Re: Meeting
 15 Alice Meeting notes
R <id>, RP <id>

* marks unread messages. Use R <id> to read a result.

Paging

Paging applies to three things: the area list, message lists, and long message bodies.

Lists

If a list has more pages, the footer shows:

R <id>, M:more B:back

Long messages

If a message body wraps beyond the current interface profile's per-page limit, it is split into pages. The first page shows a progress footer:

1/3 M:more B:back

Subsequent pages show the same until the last page, which shows the normal reply prompt.

Navigation

Move forward one page:

M

Alias:

MORE

Move back one page:

B

Compatibility aliases:

P
PREV

B or P on the first page returns:

Already at first page.

M past the last page of a list returns:

End.

Quit

Q is context-aware:

  • Inside an area: exits the area and returns to the main context. Your session stays active.
  • At the top level: ends the PacketBBS session and clears state.
Q

To end the session unconditionally from anywhere, regardless of what area you are in, use the full word:

QUIT

Output Profiles

The interface request field controls line width and page size:

| Interface | List page size | Msg page size | Width | Intended use | |---|---:|---:|---:|---| | meshcore | 3 | 1 | 34 | Compact mode tuned to stay within MeshCore's 150-character transport limit. | | meshtastic | 4 | 3 | 34 | Smaller packets and narrower displays. | | tnc | 8 | 8 | 64 | Larger text frames. |

Unknown interface values fall back to the MeshCore profile.

Admin Operations

The admin Packet BBS page shows:

  • registered bridge nodes
  • whether each node has an API key
  • last-seen time
  • active PacketBBS sessions
  • outbound queue entries

Common operations:

  • Generate/regenerate API key for a node.
  • Edit node handle, location description, coordinates, or interface type.
  • Set the auto-add contact policy for MeshCore nodes.
  • Delete a node.
  • Kill a stuck session.
  • Flush unsent outbound queue entries.

Regenerating a node API key invalidates the old bridge key immediately.

The node edit modal uses a two-column layout. Left column: identity and location fields. Right column: Auto-Add Contact Policy (MeshCore nodes only). The auto-add policy is pushed to the device only when the bitmask changes ? saving other fields without touching the policy does not queue a device command.

Public Node Directory

Registered PacketBBS nodes are publicly listed at /packetbbs-nodes. No login is required to view the page.

The page shows:

  • an interactive map with a marker for every node that has GPS coordinates set
  • a sortable table listing all registered nodes by handle, interface type, and location description
  • a node info modal (handle, location description, coordinates with a copy button, public key with a copy button, and QR code)

Linking to a specific node info modal uses the URL hash #node-{id}:

https://your-bbs.example/packetbbs-nodes#node-42

The dashboard includes a PacketBBS Nodes card in the sidebar. It lists registered nodes with their handle and location description. Clicking a node name follows the #node-{id} link and opens the info modal automatically.

Adding Location Data

To make nodes useful in the public directory, fill in the Location Description and optionally the Coordinates fields when registering or editing a node in Admin ? Packet BBS Nodes:

  • Location Description ? human-readable label shown in the node table and dashboard (e.g. "Lower Mainland BC", "Mt. Baker repeater site").
  • Coordinates ? used to place the node on the map. Not displayed directly on the page; users see the location description instead.

MeshCore Companion Contacts

MeshCore radio devices maintain a local contact list (the "companion" list) of nodes they have heard or been manually told about. BinktermPHP mirrors this list into the meshcore_contacts database table so sysops and users can associate radio contacts with BBS accounts and manage them from the web interface.

How Contact Sync Works

At startup, the bridge sends CMD_GET_CONTACTS to the radio immediately after the handshake completes. The radio responds with its full contact list (CONTACT_START ? N × CONTACT ? CONTACT_END). The bridge reports each contact to the BBS via:

POST /api/meshcore/contact
Authorization: Bearer <node-api-key>

During normal operation, the bridge also reports contacts pushed by the radio in real time (for example, when a new node is heard and automatically added to the companion list).

The BBS upserts on the contact's full 64-character public key. If a user has already pre-registered a contact by its 12-character prefix (see User Radio Registration below), the incoming full-key report claims that row and fills in the complete key.

Contact Identifiers

Each MeshCore contact has two key identifiers:

| Field | Length | Description | |---|---|---| | Node ID prefix | 12 hex chars | The first 6 bytes of the public key, shown in the MeshCore app | | Full public key | 64 hex chars | The complete 32-byte public key; globally unique |

Two contacts can share the same 12-character prefix (the prefix space is 2^48, collisions are possible). Uniqueness is enforced only on the full key. The prefix is used for display and initial lookup; the full key is used for identity and for sending remove commands to the device.

Admin Contact Manager

On the Admin ? Packet BBS Nodes page, each MeshCore node row has a contacts button (address book icon). Clicking it opens the Contact Manager for that node.

The Contact Manager shows all contacts synced from that bridge node:

| Column | Description | |---|---| | Node ID (prefix) | 12-char hex prefix; hover for full key tooltip when known | | Name | Display name, either from the radio or set by the sysop | | Type | Advertisement type reported by the radio (chat, repeater, etc.) | | Owner | BBS user account linked to this radio contact | | Location | GPS coordinates, if broadcast by the contact | | Last Seen | Timestamp of the most recent bridge sync |

Editing a Contact

Click the edit button on any row to open the edit modal. Fields:

  • Display Name ? overrides the name broadcast by the radio; leave blank to use the radio name.
  • Owner ? BBS user account to associate with this radio node. Uses the same username autocomplete as the Auto Feed Manager. Defaults to the currently logged-in sysop.
  • Notes ? free-form admin notes.

Deleting Contacts

Single delete: click the trash icon on any row and confirm.

Bulk delete: check one or more rows (or use the Select All checkbox in the header), then click Delete Selected in the modal footer. A single request deletes all selected contacts at once.

When a contact has both a known bridge node and a full public key, deletion queues a remove_contact device command. The bridge picks up this command on its next poll interval and sends the remove command to the radio, removing the contact from the device's companion list as well.

Contacts that only have a 12-character prefix (no full key) are deleted from the BBS database only ? the device cannot be told to remove them because the full key needed to address the command is not known.

User Radio Registration

Users can register their own MeshCore radio node under Settings ? MeshCore Radio. This creates a pre-registration row in meshcore_contacts owned by that user.

Registration accepts either:

  • 12-character node ID ? the prefix shown in the MeshCore app. The full key will be filled in automatically when the bridge next reports this contact.
  • 64-character public key ? the full key. Preferred if known, as it matches immediately without waiting for a bridge sync.

Once the bridge reports a contact whose prefix matches a user's pre-registered row (and the full key is not yet known), the row is claimed and updated. The user's BBS account becomes the owner of that radio contact.

Users can rename or delete their registered radios from the same settings tab. Deleting a user-registered contact follows the same device command queue logic as admin deletion.

Companion Radio Association

When a user registers a radio contact under Settings ? MeshCore Radio, the registration form includes a Companion Radio selector. The user picks which bridge device should relay messages between the BBS and that contact.

Selecting a companion radio does two things:

  1. The contact record is linked to that bridge node so the BBS knows which device is responsible for it.
  2. If the full 64-character public key is already known at registration time, an `add_contact` device command is queued immediately. The bridge picks up the command on its next poll and sends `CMD_ADD_UPDATE_CONTACT` to the radio, adding the contact to the device's companion list. This happens without requiring the operator to add the contact manually through the MeshCore app.

If a companion radio is later changed through the edit form, a fresh add_contact command is queued for the newly selected bridge.

Users who have no full public key yet (registered by 12-character prefix only) must wait for the bridge to report the full key before the device push can happen.

Device Auto-Add Policy

MeshCore devices can be configured to automatically add nodes they hear over the air to their local contact list. By default this is often enabled for all node types, which can fill the contact list with repeaters and sensors the operator does not care about.

The Admin ? Packet BBS Nodes node edit modal includes an Auto-Add Contact Policy section for MeshCore nodes. Individual checkboxes control each auto-add type:

| Checkbox | Bit | Notes | |---|---|---| | Auto-add companions (chat) | 0x02 | Covers companion radios running the MeshCore companion firmware | | Auto-add repeaters | 0x04 | Recommended: off | | Auto-add room servers | 0x08 | Recommended: off | | Auto-add sensors | 0x10 | Recommended: off | | Overwrite oldest when full | 0x01 | When the contact list is at capacity, replaces the oldest non-favourite entry |

Saving the node queues a set_autoadd_config device command only when the bitmask changed since the modal was opened. The bridge sends CMD_SET_AUTOADD_CONFIG to the radio on its next poll cycle; the setting takes effect immediately and persists across device restarts. Saving other node fields (handle, location, coordinates) without changing the policy does not trigger a device command.

Read from Device: clicking this button queues a get_autoadd_config command. The bridge reads the device's actual current value and reports it back to the BBS. Refresh the admin page after the bridge next polls to see the result. This is useful when the device was configured through another tool and the BBS record does not yet match the device state.

The autoadd_config bitmask is stored in the autoadd_config column of packet_bbs_nodes. A NULL value means the config has not yet been read from or written to the device.

Device Command Queue

BinktermPHP records commands for the radio device in meshcore_device_commands. The bridge polls for pending commands on each poll cycle:

GET /api/meshcore/pending-commands?bridge_node_id=<hex>
Authorization: Bearer <node-api-key>

After executing each command the bridge acknowledges it:

POST /api/meshcore/commands/{id}/ack
Authorization: Bearer <node-api-key>

If the bridge is offline when a command is queued, it stays pending until the bridge reconnects and polls again.

Supported command types:

| Command type | Triggered by | Radio frame sent | |---|---|---| | remove_contact | Contact deleted by sysop or user | CMD_REMOVE_CONTACT | | add_contact | User registers a contact with full public key | CMD_ADD_UPDATE_CONTACT | | set_autoadd_config | Sysop saves auto-add policy in node edit modal | CMD_SET_AUTOADD_CONFIG | | get_autoadd_config | Sysop clicks "Read from Device" in node edit modal | CMD_GET_AUTOADD_CONFIG |

When the radio responds to CMD_GET_AUTOADD_CONFIG, the bridge posts the result back to the BBS:

POST /api/meshcore/autoadd-config
Authorization: Bearer <node-api-key>

The BBS stores the value in packet_bbs_nodes.autoadd_config so the admin panel can display the current device state without querying the radio on every page load.

Troubleshooting

Unknown Bridge Node

Radio response:

This node is not registered with this BBS. Contact the sysop to be added.

Fix:

  • Add the bridge node in `Admin -> Packet BBS Nodes`.
  • Verify the bridge sends the correct `bridge_node_id` or, if omitted, the correct `node_id`.
  • Verify the bearer token belongs to that node.

Unauthorized HTTP Response

An HTTP 401 Unauthorized means bridge authentication failed. The radio user usually should not see this as a BBS command response.

Fix:

  • Confirm the bridge sends `Authorization: Bearer <key>`.
  • Regenerate the node key and update the bridge configuration.
  • Confirm the bridge is using the matching registered node ID.

User Cannot Log In

Possible causes:

  • User has not enrolled PacketBBS Authenticator in `Settings -> Account`.
  • User entered an expired or incorrect TOTP code.
  • Too many failed attempts were made from the sender node.
  • The BBS account is inactive.

PacketBBS deliberately keeps login errors short and does not reveal which users have an authenticator enrolled.

Echomail Post Goes to the Wrong Area or Fails

Use the exact area identifier shown by AREAS, especially for networked areas:

POST LVLY_TEST@lovlynet Subject

If the area has no route or uplink, PacketBBS returns:

No route for area. Ask sysop.

Check the echoarea domain, subscription, and uplink configuration.

Logs

PacketBBS writes operational logs to:

data/logs/packetbbs.log

This log includes command routing and high-level errors. TOTP codes are never logged.

Related Systems