DownloadUpgrading to 1.9.6
Make sure you have a current backup of your database and files before upgrading.
Table of Contents
Summary of Changes
Chat Room Bridging (Matterbridge)
-
Local BinktermPHP chat rooms can now relay messages to and from external platforms (Discord, Slack, IRC, and others) via the third-party Matterbridge gateway.
-
A new Matterbridge Bridge Settings panel on Admin ? Chat Rooms lets the sysop configure the global API connection (URL, token, bridge user, and default username suffix).
-
Each chat room has new per-room bridge fields: enable/disable bridging and a Matterbridge gateway name that maps the room to a configured gateway in `matterbridge.toml`.
-
Outbound bridging is handled in-process by `ChatMessageService`. Inbound messages are injected by a new background daemon, `scripts/matterbridge_daemon.php`, which polls the Matterbridge API and inserts messages into local chat under a dedicated bridge user account.
AI Settings
-
A dedicated AI Settings admin page is now available at Admin ? AI Settings. It consolidates all AI-related configuration in one place.
-
The Enable AI Assistant toggle, previously located on the BBS Settings page, has moved to the AI Settings page.
-
A new Enable AI summaries for shared message links toggle controls whether the AI share summarizer feature is available to users.
-
The system prompt used when generating share summaries is configurable directly on the AI Settings page. Leave it blank to use the built-in default.
Ollama AI Provider
-
Ollama is now supported as a third AI provider alongside OpenAI and Anthropic. It can be used self-hosted (local inference, no API key, no per-token billing) or via the Ollama cloud service.
-
Set `OLLAMA_API_BASE` in `.env` to enable the provider. No other change is required for a self-hosted install. Cloud installs also set `OLLAMA_API_KEY`.
-
Token pricing follows the same `AI_PRICE_OLLAMA_*` env var pattern used by other providers and defaults to zero, reflecting that local inference has no per-token cost.
-
Optional power cost tracking: set `OLLAMA_POWER_COST_PER_KWH_USD` and `OLLAMA_GPU_POWER_WATTS` to have the system estimate electricity cost per request and record it in the AI usage ledger alongside token accounting.
-
Tool calling (required by the message reader AI assistant) is off by default. Set `OLLAMA_SUPPORTS_TOOLS=true` only with a model that supports function calling, such as `llama3.1`, `llama3.2`, or `qwen2.5`.
AI Share Summarizer
-
Users can now generate a one-sentence AI-written link preview description when sharing an echomail message. An AI button appears in the share dialog when this feature is enabled and an AI provider is configured.
-
The generated description is stored with the shared message and used as the `og:description` meta tag on the shared page, so link previews on social platforms and messaging apps show a meaningful summary of the post rather than generic site text.
-
The system prompt the AI receives can be customized in Admin ? AI Settings.
Messaging
-
The unread netmail count shown on the dashboard, the netmail sidebar, and the unread filter list now reflects only messages addressed to the logged-in user. Previously the count included outbound messages composed by the user, causing the badge to show a higher number than the unread inbox actually contained. Soft-deleted messages are also excluded from the unread sync so the count and the visible list always agree.
-
Added a new web-reader Re-Post action for echomail and netmail. Re-post opens the composer with the original message body, preserves the original message charset and markup format, prefixes the subject with `FWD:`, and leaves the recipient or target area unset so the user must choose where to send it.
-
Added a new echomail reader action to Forward by Netmail. This opens the netmail composer using the selected echomail message as the source, keeps the original charset and markup mode, prefixes the subject with `FWD:`, and leaves the netmail recipient unset so the user can choose where to forward it.
Echomail
-
The dashboard now includes a New Echoareas summary section listing echoareas that are available to subscribe to but haven't been subscribed to yet. The section can be collapsed and its state is remembered between page loads.
-
The subscriptions page now shows a New badge beside echoareas that were recently added to the system, making it easier to discover areas added since the last visit.
-
The subscriptions list now supports sorting by name, unread count, last-activity date, or subscription date.
-
The plaintext echomail composer now has an Insert Image toolbar button (above the text area, matching the position of the Markdown toolbar). Clicking it prompts for an image file upload; once uploaded, the image URL is inserted at the cursor position so the image appears inline in the message body.
Shared Pages
-
Fixed shared message pages so they no longer emit two `og:description` tags. Social previews now use the shared message's AI-generated summary (if one has been created) or subject/body excerpt, instead of also including the site-wide description from the global appearance settings.
-
Applied the same metadata override pattern to shared file pages so file shares also emit a single page-specific `og:description` value.
-
The echomail share dialog now displays any existing share links for the same message, including links created by other users. Each link shows the sharer's username and a one-click copy button, so users can copy an existing link without creating a duplicate.
-
The sharing icon in the echomail message list now appears whenever any user has an active share for a message, not just the currently logged-in user.
-
Users can now upload a custom preview image when creating a shared echomail link. The image is stored in the sharer's private file area and served at a dedicated URL that includes the file extension, so social platforms and messaging apps that infer image format from the URL can display it correctly. The image appears as the `og:image` and `twitter:image` meta tags on the shared page.
Sharing Analytics
-
Shared message and shared file page visits now record external HTTP referrers, allowing the system to show which outside sites are sending traffic to shared links.
-
Admin ? Analytics ? Sharing now includes a Top Referrers column for each active shared message and shared file, listing the most common external URLs that led visitors to that share.
-
Shared message page loads no longer count twice when the browser fetches the page shell and then loads the message JSON. View totals for shared messages now reflect one counted access per page visit.
Markdown Editor
-
Fixed the Markdown editor inserting unnecessary backslash escapes before underscores and hyphens when composing messages in WYSIWYG mode. Characters typed in plain text (for example underscores in usernames or filenames, and hyphen runs used as dividers) were being stored with backslash prefixes such as `\_` or `\-\-\-`, which could appear as literal backslash sequences in some renderers.
Terminal Server
-
Terminal login, main-menu, and goodbye art screens now support simple rotating file families. You can keep a single file such as `telnet/screens/login.ans`, or add numbered variants such as `telnet/screens/login1.ans`, `telnet/screens/login2.ans`, `telnet/screens/mainmenu1.sixel`, and `telnet/screens/bye1.ans`.
-
When multiple matching files exist for the same screen family and file type, the terminal server now uses a glob match and randomly selects one file each time that screen is shown.
Documentation
-
Moved installation instructions from `README.md` into a dedicated `docs/INSTALL.md` and overhauled it: restructured around a clear two-user model (admin account vs. dedicated `binktermphp` system account), added a BBS user creation section, moved PostgreSQL setup into its own section, added `SITE_URL` configuration guidance with common scenario examples, added a firewall rules preface to the network ports section, grouped unsupported web servers under a single heading, added a PHP built-in server safety warning, and added a Next Steps section pointing to the getting-started guide.
-
Expanded the user guide with a dedicated message reader section that explains the web reader interface and lists the supported keyboard shortcuts for both echomail and netmail readers. The translated user guide variants were updated to include the same section.
-
Added `docs/ARCHITECTURE.md` ? a new system architecture reference covering the full daemon map, component diagram, FTN packet lifecycle (inbound and outbound), daemon IPC model, door game subsystem, and AI pipeline.
-
Added `docs/DATA_MODEL.md` ? a conceptual overview of the key database tables and their relationships, written as a mental model for developers rather than a schema dump.
-
Added `docs/WebDoor-Tutorial.md` ? a step-by-step tutorial for building a WebDoor from scratch, covering the manifest, PHP entry point, SDK usage, credit integration, and enabling in admin.
-
Added an authentication quickstart to `docs/API.md` with a complete login request/response example and a follow-up authenticated request, generated as part of the API doc build so it is preserved on regeneration.
-
Restructured `docs/index.md`: AI & Integrations promoted above Doors & Games; Access Methods section now includes Gemini and PacketBBS; "Content & Display" renamed to "Content & Media"; new documents added throughout.
-
Expanded the `docs/DEVELOPER_GUIDE.md` architecture section with a full system diagram and a daemon reference table listing all processes, their purposes, and whether they are required or optional.
-
Added new reference pages to `docs/`: Gateway Token Authentication, Markdown and StyleCodes, Nodelist, Performance Tuning, Shoutbox, Bulletins, Voting Booth, Dashboard, and Analytics.
-
Updated the README opening paragraph to describe the platform's multi-protocol nature rather than framing it as a web-based BBS.
Developer Tools
-
Added `scripts/generate_api_docs.php`, a CLI utility that generates developer-facing API reference documentation directly from the route files. It produces Markdown or OpenAPI 3.0 YAML and can optionally call a configured AI provider (Anthropic or OpenAI) to enrich each endpoint with a description, parameter tables, request body schema, and response fields. No migration or configuration change is required; the script is a standalone developer tool.
Networks
-
Corrected the website URL for the DoveNet built-in network entry. The entry now links to the active DoveNet listing at `https://clrghouz.bbs.dege.au/domain/view/34`.
Local Chat
-
When opening a direct chat with an AI bot, the bot's description now appears in the chat header beside the bot's name, using the same styling as a chat room description. Previously the header showed only the bot's username.
MeshCore Contact Management
-
BinktermPHP now mirrors the companion contact list from MeshCore bridge devices. When a bridge connects, it fetches the full contact list from the radio and reports each contact to the BBS, where they are stored and displayed in a new per-node Contact Manager on the Admin ? Packet BBS Nodes page.
-
Contacts can be viewed, edited (display name, linked BBS account, notes), and deleted individually or in bulk from the admin Contact Manager. Deleting a contact that has a known full public key queues a `remove_contact` command that the bridge sends to the radio on its next poll, removing the contact from the device's companion list as well.
-
Users can register their own MeshCore radio node under Settings ? MeshCore Radio. A registration accepts either the 12-character node ID prefix (visible in the MeshCore app) or the full 64-character public key. When the bridge next reports a contact whose prefix matches, the registration row is claimed automatically and the user becomes the owner of that contact.
-
When a user registers a radio contact, they now select which companion radio (bridge device) should relay messages to and from that contact. Registering a contact with a known full public key immediately queues an `add_contact` device command so the selected bridge pushes the contact to its radio.
-
Contact sync from the bridge is now limited to companion-type (`chat`) nodes. Repeaters and sensors are no longer reported to the BBS contact store.
-
Sysops can now configure the MeshCore device's auto-add contact policy from the node edit modal in Admin ? Packet BBS Nodes. The policy controls which node types (companions, repeaters, room servers, sensors) the radio automatically adds to its contact list when heard over the air. A "Read from Device" button queues a read request; the bridge reports the current device value back to the BBS the next time it polls.
MeshCore Bridge Update Required
The changes above require an updated version of the MeshCore Bridge. See MeshCore bridge update in the detailed section below.
Navigation
-
When the About page is enabled in Admin ? Appearance, a link to that page now appears in the top navigation bar for visitors who are not logged in. Previously the link only appeared in the footer.
Realtime Chat Delivery
-
Fixed WebSocket connections dropping when BinktermPHP is deployed behind a reverse proxy (Caddy, Nginx, etc.). The WebSocket server now sends a keep-alive ping frame to each connected client every 20 seconds, preventing proxies from closing idle connections mid-session.
-
Fixed the BinkStream event cursor drifting out of sync when a client reconnects. Events such as `dashboard_stats` (generated by incoming FTN mail) accumulate in the event queue without being delivered to chat-page clients, causing the stored cursor to lag behind the server's position. On reconnect the client would replay the full backlog ? potentially hundreds of thousands of events ? before seeing new messages. The server now sends a lightweight cursor-sync message after processing any batch that contains unsubscribed events, keeping the client's stored position current.
-
On reconnect, catch-up replay is now capped to the most recent 5,000 events. Chat message history is loaded through the messages API on page load, so BinkStream only needs to cover a short real-time window. Previously there was no cap, and clients that had been disconnected during a large FTN mail import could spend minutes replaying stale events before receiving new chat messages.
Chat Room Bridging (Matterbridge)
BinktermPHP chat rooms can now relay messages bidirectionally to external platforms ? Discord, Slack, IRC, Telegram, and any other network supported by the third-party Matterbridge tool.
How it works
Two processes are required beyond the web server:
-
The Matterbridge binary ? a separate Go program you download and run. It maintains connections to external platforms and exposes a local HTTP API. BinktermPHP sends outbound messages to it and polls it for inbound ones.
-
scripts/matterbridge_daemon.php ? a BinktermPHP background daemon that polls the Matterbridge API every few seconds, matches incoming messages to local rooms by gateway name, and inserts them into `chat_messages` under a configured bridge user account.
Neither process is started automatically ? you must run the Matterbridge binary yourself and start matterbridge_daemon.php as described below.
Configuration
Configure it through the admin panel, or copy config/matterbridge.json.example to config/matterbridge.json and fill in your values.
Global settings (API URL, token, bridge user, default username suffix) are managed at Admin ? Chat Rooms ? Matterbridge Bridge Settings. Per-room settings (enable bridging, gateway name, username template) are on each room's edit form.
Running the inbound daemon
# Start
scripts/restart_daemons.sh --start matterbridge_daemon
# The daemon also participates in a full restart ? but only if it was already running
scripts/restart_daemons.sh
Or directly:
php scripts/matterbridge_daemon.php --daemon --pid-file=data/run/matterbridge_daemon.pid
The daemon exits immediately if Matterbridge is not enabled in config/matterbridge.json or if no bridge user is set.
AI Settings
A dedicated AI Settings page is now available in the admin panel. All AI-related configuration has been consolidated there, replacing the scattered controls that previously lived on the BBS Settings page.
The Enable AI Assistant toggle ? which gates the in-reader AI assistant available to users ? has moved from Admin ? BBS Settings to Admin ? AI Settings. Its behaviour is unchanged; the setting is simply managed from the new location.
The AI Settings page also hosts the Enable AI summaries for shared message links toggle and the configurable system prompt described in the next section.
Ollama AI Provider
Ollama can now be used as an AI provider in BinktermPHP, alongside the existing OpenAI and Anthropic integrations. Ollama runs open-weight large language models either locally on your own hardware or through the Ollama cloud service.
Enabling the provider
Set OLLAMA_API_BASE in .env to enable the provider. The provider is registered automatically when this variable is non-empty.
Self-hosted (local install):
OLLAMA_API_BASE=http://localhost:11434/v1
OLLAMA_DEFAULT_MODEL=llama3.1
OLLAMA_SUPPORTS_TOOLS=true
AI_DEFAULT_PROVIDER=ollama
Ollama cloud (ministral-3:3b-cloud is available on the free plan as of May 2026):
AI_DEFAULT_PROVIDER=ollama
OLLAMA_API_BASE=https://ollama.com/v1/
OLLAMA_DEFAULT_MODEL=ministral-3:3b-cloud
OLLAMA_SUPPORTS_TOOLS=true
OLLAMA_API_KEY=yourapikey
See docs/AIProviders.md for the full list of configuration examples.
Tool calling
The message reader AI assistant requires tool calling support. Set OLLAMA_SUPPORTS_TOOLS=true only with a model known to support function calling. Models that do not support tool calling will cause the assistant to fail. Examples of compatible models include llama3.1, llama3.2, and qwen2.5.
Token pricing
Token rates default to zero for Ollama, which is correct for self-hosted installs. For cloud-hosted usage, set rates using the same pattern as other providers:
AI_PRICE_OLLAMA_INPUT_PER_MILLION_USD=0.10
AI_PRICE_OLLAMA_OUTPUT_PER_MILLION_USD=0.30
Power cost tracking (self-hosted only)
For self-hosted installs, the real cost of running inference is electricity. Set these variables to have the system estimate power cost per request and include it in ai_requests.estimated_cost_usd:
OLLAMA_POWER_COST_PER_KWH_USD=0.12
OLLAMA_GPU_POWER_WATTS=200
The estimate uses the actual request duration recorded in the usage ledger:
power_cost = (duration_ms / 3_600_000) (gpu_watts / 1000) cost_per_kwh
Power cost variables are not needed for cloud-hosted installs.
AI Share Summarizer
When sharing an echomail message, users can now optionally attach a short AI-written description to the share link. When the feature is enabled and an AI provider (OpenAI or Anthropic) is configured in .env, an AI button appears in the share dialog. Clicking it sends the message subject and body to the configured AI provider and fills the description field with a one-to-two sentence plain-text summary.
The description is stored with the shared message record and is served as the og:description meta tag on the public shared-message page. Link previews generated by social platforms, chat applications, and messaging clients will show this description rather than falling back to generic site text.
The system prompt the AI receives when generating these summaries can be customized in Admin ? AI Settings ? Share summary system prompt. Leaving the field blank uses the built-in default prompt, which instructs the model to write a concise, plain-text Open Graph description in one to two sentences without Markdown, HTML, or preamble.
The summarizer is locale-aware. When a user requests a summary, the language instruction sent to the AI reflects that user's configured interface language. A French-language user will receive a summary written in French, a German-language user in German, and so on. Users with their interface set to English receive the default English output with no additional instruction.
To enable this feature:
-
Add an `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` to your `.env` file.
-
Go to Admin ? AI Settings and enable AI summaries for shared message links.
Messaging
Unread Netmail Count Correction
The unread netmail count displayed on the dashboard card, the netmail page sidebar, and the unread filter list has been corrected to count only messages addressed to the logged-in user ? that is, messages where the recipient name and FTN destination address match the user's account. Previously, the count included outbound messages the user had composed, causing the badge number to exceed what the unread inbox actually contained. The unread filter in the netmail message list has been updated to match, so the count and the list now agree. No action is required; the corrected counts take effect immediately on the next page load.
Forwarding and Re-Posting
The web message readers for echomail and netmail now include a Re-Post action alongside the existing reply tools. Re-post is intended for taking an existing message and sending it again as a new message rather than as a threaded reply.
When a user chooses Re-Post, the composer opens with the original message text already inserted, the original message charset preselected, and the original markup mode restored when the source message used Markdown or StyleCodes. The subject is copied with an added FWD: prefix. Netmail re-posts leave the recipient fields blank, and echomail re-posts leave the area selector blank, so the user must deliberately choose the new destination before sending.
The echomail reader also now includes a Forward by Netmail action in its message menu. This action opens the netmail composer while using the selected echomail message as the forwarding source. The forwarded draft preserves the original message body, charset, and markup mode, prefixes the subject with FWD:, and leaves the netmail destination blank so the user can choose the recipient explicitly.
For echomail, the send flow now keeps track of which area the user started from. After either an echomail repost or an echomail-to-netmail forward is sent, the browser returns to that original area view instead of navigating into the newly selected destination or leaving the user in the netmail section. This keeps the user in their previous reading context after forwarding or cross-posting content.
Echomail
New Echoarea Discovery
The dashboard now surfaces echoareas that exist on the system but haven't been subscribed to yet. A New Echoareas section lists these available areas so sysops and users can discover new forums without navigating to the full echoarea browser. The section can be collapsed using a toggle button at the top; the collapsed or expanded state is remembered per-user between page loads.
The subscriptions page (accessible from the navigation menu) now shows a New badge beside echoareas that were recently added to the system. This gives subscribers a clear visual signal when new areas appear without having to compare the full list against memory.
Subscription Sorting
The subscriptions list now supports sorting by multiple criteria ? echoarea name, unread message count, date of last activity, and subscription date. The sort preference is preserved for the session.
Insert Image in Plaintext Compose
The plaintext echomail composer now has a small toolbar above the text area containing an Insert Image button (camera icon). This matches the position of the Markdown editor toolbar so the controls are consistent regardless of compose mode. Clicking the button opens a file picker; after the user selects an image file it is uploaded via the existing markdown-images endpoint and the returned URL is inserted at the current cursor position in the message body. The button is hidden automatically when the StyleCodes editor is active, which has its own toolbar.
Shared Pages
Shared message pages now override the default description metadata provided by the site shell templates. Previously, a shared message page could output both the global site description from Admin -> Appearance and a second message-specific og:description tag based on the shared post content. Link preview crawlers that saw both tags could pick the wrong one, causing the preview text to describe the BBS in general rather than the shared message itself.
The page now emits only the message-specific description metadata when a shared message is being viewed. This keeps the Open Graph preview aligned with the shared message's AI-generated summary (if one has been created) or subject and excerpt.
The same override structure is also applied to shared file pages. Shared files continue to use their own file description or fallback text, but they no longer risk combining that description with a second site-wide Open Graph description tag.
Share Dialog Improvements
The echomail share dialog now queries for existing active share links on the same message before displaying the creation form. If other users have already shared the message, their links are listed above the form, each with the sharer's username and a copy button. This prevents accidental duplicate shares and lets anyone quickly reuse an existing public link.
The sharing icon shown beside messages in the echomail message list has also been updated. Previously the icon only appeared when the currently logged-in user had an active share for that message. It now appears whenever any user on the system has an active share, giving readers a clearer signal that the message is publicly accessible.
OG Preview Image Upload
The share dialog now includes an image upload section below the share link. When a sharer uploads an image, it is stored in a shared-messages/ subfolder of their private file area and associated with the share record. The shared message page then includes og:image and twitter:image meta tags pointing to a public serve URL that includes the original file extension (for example /shared-image/abc123?.jpg). Social platforms and messaging clients that infer image type from the URL extension will display the image correctly in their link previews.
Each share can hold one image at a time. Uploading a new image replaces the previous one. The upload form is hidden when an image is already present, replaced by a thumbnail preview and a Remove button. Images must be 5 MB or smaller.
Two new database columns are added to shared_messages:
| Column | Purpose |
|--------|---------|
| og_image_path | Absolute filesystem path to the stored image file |
| og_image_slug | Filename with extension (e.g. abc123?.jpg), used as the serve URL parameter |
These are added by the migrations applied automatically when you run php scripts/setup.php.
Sharing Analytics
Shared links now capture the external HTTP referrer that led a visitor to the page when the browser provides one. This applies to both public shared message pages and public shared file pages. Internal links from the same BBS are ignored so the analytics focus on outside traffic sources rather than normal in-site navigation.
The Admin ? Analytics ? Sharing page now shows a Top Referrers column beside each active share. For each shared message or file, the page lists up to ten of the most frequent external referring URLs along with the number of visits attributed to each one. This gives the sysop a direct view of which forums, social posts, directories, or websites are driving traffic to individual shared links.
Shared message access counting has also been tightened up. The public shared message page renders server-side and then loads its message content through the API. Previously, that could increment the share's view counter twice for a single human visit. The count is now recorded only on the page request itself, so the displayed totals better match real page visits.
Markdown Editor
The Markdown composer in WYSIWYG mode was inserting unnecessary backslash escapes before underscores. This happened because the underlying Toast UI editor serializes WYSIWYG content to Markdown and defensively escapes characters that carry special meaning in Markdown syntax, including underscores. The post-processing step that already stripped similar unnecessary escapes from ., ~, and | was extended to cover _ and - as well. Underscores and hyphens typed in plain prose are now stored without backslash prefixes.
Terminal Server
Rotating Screen Families
The terminal server now supports rotating custom screen families for login, main menu, and goodbye art. Instead of being limited to one fixed file per screen, you can now place multiple matching files in telnet/screens/ and let the server pick one at random each time the screen is displayed.
Examples include telnet/screens/login.ans, telnet/screens/login1.ans, telnet/screens/login2.ans, telnet/screens/mainmenu.sixel, telnet/screens/mainmenu1.sixel, and telnet/screens/bye1.ans. Matching is done per file type, so Sixel-capable clients randomize across matching .sixel files and ANSI-capable clients randomize across matching .ans files.
Documentation
Installation Guide Overhaul
Installation instructions have been moved out of README.md into a dedicated docs/INSTALL.md, which has also been significantly restructured to make the installation process clearer for sysops of all experience levels. The previous guide mixed steps that must be run as a privileged admin user with steps that must be run as the BBS service account, without distinguishing between them.
The revised guide introduces a two-user model throughout: an admin account (your normal login with sudo access) for system-level tasks, and a dedicated binktermphp service account for everything BinktermPHP-specific. Each section is labelled with which account should be used, and the guide walks through creating the binktermphp account explicitly before any install steps.
Other changes to docs/INSTALL.md:
-
PostgreSQL setup is now its own top-level section, clearly framed as an admin-user task separate from the BinkTermPHP install.
-
Web server configuration is positioned after the install methods, since sysops need the install path before they can fill in the config.
-
A `SITE_URL` configuration section has been added to Step 3 of the Git install path, with a table covering the common scenarios: public domain, IP-only, local machine, and PHP built-in server.
-
Nginx and Apache configurations are grouped under an "Other web servers (unsupported)" sub-heading to make Caddy's recommended status more visible.
-
The PHP built-in server entry now carries an explicit warning that it is not safe for production use.
-
The Network Ports section now opens with a plain-language explanation of the table and includes a `ufw` example showing the most common firewall rules for inbound and outbound services.
-
A Next Steps section at the end of the guide directs sysops to `docs/GettingStarted.md` once installation is complete.
-
The Database Management reference (scripts and migration system) has been moved from `docs/INSTALL.md` to `README.md` under the Operation section, where it is more naturally discoverable during day-to-day administration.
Other Documentation Changes
The user guide now includes a dedicated message reader section. It explains that the web message reader is shared by echomail and netmail and documents the supported keyboard shortcuts for navigation, viewer mode changes, downloads, full-screen mode, shortcut help, and closing the reader.
The localized user guide files were updated alongside the main English guide so the same message reader guidance is available across the translated variants.
Several new developer-focused documents have been added:
ARCHITECTURE.md is the primary new addition. It covers the full system in one place: a layered component diagram showing all clients, daemons, and the data layer; descriptions of each component's role; separate diagrams for the inbound FTN packet lifecycle (binkp connection ? packet processor ? database ? BinkStream notification) and the outbound lifecycle (user post ? packet bundler ? binkp poll ? uplink); the daemon IPC model (shared database, admin daemon as coordinator, PID files); the door game subsystem (browser WebSocket ? multiplexing bridge ? PTY/DOSBox-X); and the AI pipeline (user prompt ? MessageAiAssistant ? MCP tool loop ? AI provider ? browser).
DATA_MODEL.md documents the key database tables conceptually ? not as a schema dump, but as a mental model for contributors. It covers the core message tables (echomail, netmail, echoareas), the user tables (users, users_meta, user_transactions), FTN network tables, the sse_events real-time queue, and a reference table for all supporting tables. An entity-relationship diagram shows how the most important tables connect.
WebDoor-Tutorial.md walks through building a complete WebDoor from scratch. The example is a coin-flip game that demonstrates the full stack: manifest format, PHP entry point using the WebDoor SDK, server-side credit debit and award logic, JSON API response pattern, and enabling the door in admin. A "Going Further" section covers icons, leaderboards, config overrides, and persistent save state.
The API.md authentication section now includes a quickstart with a complete login request/response example showing the session cookie and CSRF token, followed by examples of a GET request and a state-changing POST with the CSRF header. This is generated by scripts/generate_api_docs.php and is preserved on regeneration.
The index.md structure has been updated: the AI & Integrations section is now placed above Doors & Games; Access Methods now consolidates all access types including Gemini and PacketBBS alongside Telnet and SSH; "Content & Display" is renamed "Content & Media"; and new documents are linked throughout.
The DEVELOPER_GUIDE.md architecture section now includes a full ASCII system diagram and a daemon reference table listing every daemon, its script path, its purpose, and whether it is required or optional for a given feature set.
Developer Tools
A new CLI script, scripts/generate_api_docs.php, generates developer-facing API reference documentation from the SimpleRouter route files. It uses PHP's built-in tokenizer to walk the route file structure, resolve nested group prefixes, extract PHPDoc comments, and detect authentication requirements ? producing output that covers all endpoints in a given route set without requiring a running server.
Two output formats are supported:
-
Markdown (default) ? a structured document with a table of contents, per-section endpoint tables, and per-endpoint detail blocks. Suitable for `docs/API.md`, a GitHub wiki, or any Markdown renderer.
-
OpenAPI 3.0 YAML ? a spec file compatible with Swagger UI, Postman, and API code-generation tools.
Four route sets can be selected individually or together: api (the public API, selected by default), admin, door, and webdoor. Pass --routes=all to document every route file in one run.
Without any AI flags the output is static: HTTP method, path, auth flag, and any comment immediately above the route definition in the source. Passing --ai activates enrichment: the script sends batches of route code snippets to a configured Anthropic or OpenAI provider and back-fills a one-sentence summary, a fuller description, path and query parameter tables, a request body schema, a response field list, and common error codes for each endpoint.
# Static Markdown for the public API
php scripts/generate_api_docs.php --output=docs/API.md
# AI-enriched OpenAPI spec for all routes
php scripts/generate_api_docs.php --routes=all --ai --format=openapi --output=docs/openapi.yaml
Full usage is documented in docs/DEVELOPER_GUIDE.md under API Documentation Generator. No migration or configuration change is required to use the script.
Realtime Chat Delivery
Three fixes address WebSocket reliability and event delivery performance for installations running BinktermPHP behind a reverse proxy.
WebSocket keep-alive pings. Reverse proxies such as Caddy and Nginx reap connections that carry no traffic for a period of time. The WebSocket server had no mechanism to keep idle connections alive, so connections would be silently dropped and clients would reconnect with exponential backoff ? causing messages to arrive in bursts seconds after they were sent rather than in real time. The server now sends a WebSocket ping frame (RFC 6455 opcode 0x9) to each connected client every 20 seconds. Browsers respond automatically with a pong, which keeps both sides of the proxy connection active.
BinkStream cursor sync. The BinkStream event stream delivers different event types to different pages. The sse_events table accumulates rows for all event types: chat_message, dashboard_stats (triggered by incoming FTN mail), binkp_session, and others. The WebSocket server advances its internal cursor past every row it fetches ? including rows for event types the current client is not subscribed to ? but only sends the subscribed events to the client. Because the client's stored cursor was only updated when a subscribed event was received, a gap of unsubscribed rows between two chat messages would leave the stored cursor pointing before that gap. On reconnect, the client would replay the entire gap before receiving anything new. The server now sends a __cursor_sync message after each polling batch that contains unsubscribed trailing rows, so the client's stored position always matches the server's.
Catch-up replay cap. Even with the cursor sync fix in place, clients that reconnect after a prolonged disconnection (such as a suspended laptop waking from sleep) could arrive with a very stale cursor. The replay window is now capped at 5,000 events from the current position. If the stored cursor is further back than that, the client starts from maxId ? 5000 instead of the stored value. Chat history is unaffected because it is loaded through the /api/chat/messages endpoint on page load; BinkStream is only responsible for delivering events that arrive after the page has finished loading.
Networks
The built-in DoveNet network record has been updated with a corrected website URL. The networks table entry for DoveNet now points to https://clrghouz.bbs.dege.au/domain/view/34, which is the active DoveNet listing.
This is applied automatically by the migration when you run php scripts/setup.php.
MeshCore Contact Management
MeshCore bridge devices maintain a local companion contact list ? the set of radio nodes the device has heard or been told about. BinktermPHP now mirrors this list into a new meshcore_contacts database table so sysops and users can manage radio contacts from the web interface.
How contact sync works
When a MeshCore bridge connects to BinktermPHP, the bridge firmware is asked for its full contact list. The radio responds with every contact it knows, and the bridge forwards each one to the BBS via POST /api/meshcore/contact. During normal operation, newly discovered contacts are also reported as the radio hears them. The BBS upserts each contact on its full 64-character public key.
A contact identified only by its 12-character node ID prefix (the short form shown in the MeshCore app) is stored without a full key until the bridge reports the matching full-key entry ? at which point the two records are merged into one.
Admin Contact Manager
On the Admin ? Packet BBS Nodes page, each MeshCore node row now has a contacts button (address book icon). Clicking it opens the Contact Manager for that node, which shows all contacts synced from that bridge device.
From the Contact Manager, a sysop can:
-
Edit a contact's display name, linked BBS user account, and admin notes. The display name overrides the name broadcast by the radio. The linked account associates that radio node with a specific BBS user.
-
Delete a single contact by clicking the trash icon on a row and confirming. If the contact has a known full public key and a known bridge node, a `remove_contact` device command is queued and sent to the radio on the bridge's next poll cycle.
-
Bulk delete by selecting any combination of rows with the checkboxes (or using Select All), then clicking Delete Selected. All selected contacts are deleted in a single database operation. Device commands are queued for each contact that has a full public key.
Contacts that have only a 12-character prefix and no full public key are removed from the BBS database only. The radio cannot be told to remove them because the full key needed to address the device command is not known.
Device command propagation
When a contact is deleted, BinktermPHP records a pending remove_contact entry in the new meshcore_device_commands table. The bridge polls for pending commands at the same interval it checks for outbound messages:
GET /api/meshcore/pending-commands?bridge_node_id=<hex>
For each remove_contact command, the bridge sends the radio a remove frame addressed to the contact's full 32-byte public key, then acknowledges the command:
POST /api/meshcore/commands/{id}/ack
If the bridge is offline when the delete happens, the command stays queued until the bridge reconnects.
User radio registration
Users can register their own MeshCore radio node under Settings ? MeshCore Radio. The tab accepts either the 12-character node ID prefix shown in the MeshCore app or the full 64-character public key. Registering by prefix creates a placeholder row; the full key is filled in automatically when the bridge next reports a contact whose prefix matches. Registering by full key matches immediately.
Once a registration row is claimed, the user 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 an admin deletion.
Companion radio bridge association
When a user registers a radio contact under Settings ? MeshCore Radio, the registration form now 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:
-
The contact record is stored with a reference to that bridge node, so the BBS knows which device is responsible for it.
-
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 without requiring the operator to manually add it through the MeshCore app.
If a companion radio is later changed (via the edit form), a new add_contact command is queued for the newly selected bridge.
Device auto-add policy
MeshCore devices can be configured to automatically add nodes they hear over the air to their local contact list. In the default firmware configuration this is often enabled for all node types, which can fill the contact list with repeaters and sensors the operator does not need.
The node edit modal in Admin ? Packet BBS Nodes now includes an Auto-Add Contact Policy section (visible when editing MeshCore nodes). It presents individual checkboxes for each auto-add type the device supports:
| Checkbox | Device flag | Recommended |
|---|---|---|
| Auto-add companions (chat) | AUTO_ADD_CHAT | Operator preference |
| Auto-add repeaters | AUTO_ADD_REPEATER | Off (recommended) |
| Auto-add room servers | AUTO_ADD_ROOM_SERVER | Off (recommended) |
| Auto-add sensors | AUTO_ADD_SENSOR | Off (recommended) |
| Overwrite oldest when full | AUTO_ADD_OVERWRITE_OLDEST | Operator preference |
Saving the node with these checkboxes queues a set_autoadd_config device command. The bridge picks up the command on its next poll and sends CMD_SET_AUTOADD_CONFIG to the radio, which takes effect immediately and persists across device restarts.
The Read from Device button queues a get_autoadd_config command. After the bridge polls, the device's actual current setting is stored in the BBS database. Refresh the page to see the updated values. This is useful when the device has been configured through another tool (such as the MeshCore app) and the BBS record is out of date or not yet known.
Database migrations
Three migrations are applied automatically when you run php scripts/setup.php:
| Migration | Adds |
|---|---|
| v20260514120000_meshcore_contacts | meshcore_contacts table with partial unique index on pub_key_full |
| v20260514130000_meshcore_device_commands | meshcore_device_commands table with index on pending commands per node |
| v20260515041402_add_autoadd_config_to_packet_bbs_nodes | autoadd_config column on packet_bbs_nodes to store the last known auto-add bitmask |
MeshCore bridge update {#meshcore-bridge-update-1}
If you are running the MeshCore Bridge, update it alongside BinktermPHP. The updated bridge adds and changes:
-
Startup contact poll ? sends `CMD_GET_CONTACTS` to the radio immediately after the handshake so the BBS receives the full contact list on each bridge connection.
-
Contact reporting ? forwards companion (`chat`) contact advertisements to the BBS as the radio reports them. Repeaters and sensors are no longer forwarded.
-
Device command polling ? checks for pending commands from the BBS and forwards them to the radio. Supported command types: `remove_contact`, `add_contact`, `set_autoadd_config`, `get_autoadd_config`.
-
Auto-add config reporting ? when `get_autoadd_config` is executed, the bridge reads the device's current policy and posts it back to the BBS so the admin panel stays in sync.
Local Chat
When a user opens a direct message thread with an AI bot, the bot's configured description is now displayed in the chat header beside the bot's username. The description uses the same formatting as a chat room description, so the header reads as "BOTNAME description text" in the same visual style as "ROOM NAME room description" for room threads. Previously the bot header showed only the bot's username with no additional context about what the bot does.
Navigation
When the About page is enabled (via Admin ? Appearance ? About Page), a link to /about now appears in the top navigation bar for visitors who are not logged in. Previously the link only appeared in the page footer. No configuration change is required; the nav link appears automatically if the About page is already enabled.
Upgrade Instructions
From Git
git pull
php scripts/setup.php
scripts/restart_daemons.sh
Using the Installer
Download the latest installer from the BinktermPHP website and run it. The installer handles file replacement, runs setup, and restarts all daemons automatically ? no manual steps required.
|