- Welcome to BinktermPHP
- Project Architecture
- Directory Structure
- Development Workflow
- Localization (i18n)
- Credits System
- Optional Components & Daemons
- Getting Help
BinktermPHP is a modern web-based interface for FidoNet messaging networks. It combines a traditional bulletin board system (BBS) experience with contemporary web technologies, allowing users to send and receive netmail (private messages) and echomail (forums) through the FidoNet Technology Network (FTN).
FidoNet is a worldwide network of BBSs that exchange mail and files using store-and-forward technology. Messages are packaged into "packets" and transmitted between systems using the binkp protocol. Unlike modern internet messaging, FidoNet operates asynchronously - messages are bundled, transmitted to upstream nodes (hubs), and then distributed across the network.
BinktermPHP is built around several cooperating components:
- Web Interface: A PHP-based web application that users interact with via browser
- Binkp Mailer: Background daemon processes that handle FidoNet packet transmission
- Telnet Daemon: Background daemon that provides a Telnet BBS interface
- SSH Daemon: Background daemon that provides an SSH-2 BBS interface (
ssh/ssh_daemon.php) - Admin Daemon: Background daemon that manages configuration, logging, and triggered tasks (
scripts/admin_daemon.php)
- Frontend: jQuery + Bootstrap 5 for responsive, modern UI
- Backend: PHP with SimpleRouter for routing, Twig for templating
- Database: PostgreSQL for persistent storage
- Mailer: Custom PHP binkp protocol implementation for FidoNet connectivity
- Authentication: Simple username/password with long-lived cookies
- Usernames: System login identifiers (must be unique, case-insensitive)
- Real Names: Display names used in FidoNet messages (must be unique, case-insensitive)
- Both usernames and real names are unique across the system to prevent impersonation
- Netmail: Private point-to-point messages between FidoNet users
- Echomail: Public messages posted to echoareas (forums/conferences)
- Messages use FTN-standard formats with kludge lines (@msgid, @reply, etc.)
- Uplinks: Remote FidoNet nodes that relay messages to/from the wider network
- Domains: FTN addressing uses zone:net/node.point format (e.g., 21:1/999)
- Local Echoareas: Areas marked
is_local=trueare not transmitted to uplinks - Multi-Network Support: System can connect to multiple independent FTN networks
- Inbound: FTN packets arrive via binkp, are unpacked, and stored in database
- Outbound: Messages are bundled into packets and queued for transmission
- Polling: Background process (
scripts/binkp_poll.php) connects to uplinks on schedule - Server: Daemon (
scripts/binkp_server.php) accepts incoming connections from other nodes
- Telnet (
scripts/telnet_daemon.php) and SSH (ssh/ssh_daemon.php) share the same session logic (BbsSession) and deliver identical BBS features. - Both use the REST APIs for login, message retrieval, etc. — they focus on the terminal UI rather than duplicating business logic.
- The web interface is considered "first class"; terminal feature parity is desirable but not always required.
- When testing, use SyncTerm alongside PuTTY and other clients to catch client-specific behaviour.
The admin daemon (scripts/admin_daemon.php) is a long-running process that handles:
- Server-side logging from web-context code (use
AdminDaemonClient::log($level, $message)) - Configuration reads/writes for settings that require daemon coordination
- Post-session packet processing triggers
Prefer AdminDaemonClient::log() over error_log() for application-level log messages so they appear in the structured daemon log rather than the PHP error log.
binkterm-php/
├── config/ Configuration files (bbs.json, database, uplinks, i18n overrides)
│ └── i18n/ Translation catalogs (en/, es/, fr/)
├── data/ Runtime data (logs, packets, nodelists)
│ ├── inbound/ Received FTN packets
│ ├── outbound/ Packets queued for transmission
│ └── logs/ Application and packet logs
├── database/
│ └── migrations/ Database migrations (vX.Y.Z_description.sql or .php)
├── docs/ Documentation
├── native-doors/ Native door installations and drop files
├── public_html/ Web root (index.php, CSS, JS, webdoors)
│ └── webdoors/ WebDoor game installations
├── routes/ HTTP route definitions (web, API, admin)
├── scripts/ CLI tools (binkp server, poller, maintenance)
├── src/ Core PHP classes
│ ├── Admin/ Admin interface controllers and AdminDaemonClient
│ ├── Antivirus/ Pluggable antivirus scanner backends
│ ├── Binkp/ BinkP protocol implementation
│ ├── FileArea/ File area rule processing
│ ├── Database.php PDO connection singleton
│ ├── MessageHandler.php Message processing and storage
│ ├── Template.php Twig template wrapper
│ ├── UserCredit.php Credits/currency system
│ └── Version.php Application version management
├── ssh/ SSH-2 daemon
├── telnet/ Telnet BBS server
├── templates/ Twig templates
│ └── shells/ Shell-specific base templates (web/, bbs-menu/)
├── tests/ Debug/test scripts
└── vendor/ Composer dependencies (DO NOT EDIT)
- Variables/Functions: camelCase (
$userName,sendMessage()) - Classes: PascalCase (
MessageHandler,UserCredit) - Indentation: 4 spaces (no tabs)
- Database: Use
Database::getInstance()->getPdo()for connections - Environment variables: Always use
Config::env('VAR_NAME', 'default')— nevergetenv()or$_ENVdirectly
- Migration files:
database/migrations/vX.Y.Z_description.sql(or.php) - Apply migrations: Run
php scripts/setup.php— this runs both migrations and other upgrade tasks - DO NOT edit
postgres_schema.sqldirectly — use migrations - Version numbering: Before creating a migration, check the highest existing version with
ls database/migrations/ | sort -V | tail -5. The new file must be one increment higher — do not guess or reuse a version from a different branch of the version tree - Migrations can be SQL files or PHP files; see
CLAUDE.mdfor the PHP migration patterns
- Read before editing: Always read the file before modifying it
- Avoid over-engineering: Only implement what's requested
- DRY principle: Centralize repeated logic into classes
- Security: Watch for SQL injection, XSS, command injection
- Feature parity: Netmail and echomail features should generally be consistent — clarify when unsure
Always use the centralized Config::getSiteUrl() method when building full URLs:
$url = \BinktermPHP\Config::getSiteUrl() . '/path';This method:
- Checks
SITE_URLenvironment variable first (handles reverse proxies correctly) - Falls back to protocol detection using
$_SERVERvariables - Returns base URL without trailing slash
- Prevents code duplication and ensures consistent behavior
- Application version: Edit
src/Version.phponly - Auto-updates: Tearlines, footer, API responses update automatically
- Format: Semantic versioning (MAJOR.MINOR.PATCH)
- Database versions are independent of the application version and use the migration file naming scheme
When modifying public_html/css/style.css, also update all theme files:
public_html/css/amber.csspublic_html/css/dark.csspublic_html/css/greenterm.csspublic_html/css/cyberpunk.css
When changing CSS, JavaScript files, or i18n catalog strings, increment the CACHE_NAME version in public_html/sw.js (e.g. binkcache-v42 → binkcache-v43) to force clients to download fresh copies.
Template resolution order is templates/custom/ → templates/shells/<activeShell>/ → templates/. When adding nav links or modifying shared layout, update both templates/base.twig and templates/shells/web/base.twig (and bbs-menu if applicable).
Use AdminDaemonClient::log($level, $message, $context) for application-level log messages from web-context PHP code. This routes messages through the admin daemon's structured log rather than the PHP error log. The static method handles connection, logging, and cleanup in one call:
\BinktermPHP\Admin\AdminDaemonClient::log('INFO', 'Something happened', ['key' => 'value']);All user-facing UI text must go through the translation system — do not hardcode strings in templates or JavaScript.
Catalogs live in config/i18n/<locale>/common.php and config/i18n/<locale>/errors.php. Current supported locales: en, es, fr.
{{ t('ui.some.key', {}, 'common') }}
{{ t('ui.polls.cost', {'cost': poll_cost}, 'common') }}window.t('ui.some.key', {}, 'Fallback text')API responses must use structured errors:
apiError('errors.feature.something_failed', 'Human fallback', 500);Frontend resolves display text with window.getApiErrorMessage(payload, fallback).
php scripts/check_i18n_error_keys.php
php scripts/check_i18n_hardcoded_strings.phpSee docs/Localization.md for the full workflow.
- Multi-Network Support: Connect to multiple FTN networks simultaneously
- File Areas: FTN TIC file distribution with pluggable antivirus scanning (ClamAV, VirusTotal)
- WebDoors: Drop-in game/application system (see
docs/WebDoors.md) - Native Doors & DOS Doors: PTY and DOSBox-backed door games (see
docs/Doors.md) - Credits System: Configurable in-world currency (see below)
- Webshare: Share echomail messages via secure links with expiration
- Gateway Tokens: SSO-like authentication for external services
- ANSI Support: JavaScript-based ANSI art renderer for messages
- BBS Directory: Public directory of BBS systems auto-populated via Echomail Robots
The credits system is a configurable in-world currency tied to the users.credit_balance
column and the user_transactions ledger. Configuration lives in config/bbs.json
under the credits section. Admins can edit these values from the BBS Settings page
and changes apply immediately (no daemon restart required).
-
src/UserCredit.phpgetBalance($userId)reads the user balance.transact(...)performs an atomic ledger transaction.credit(...)/debit(...)are safe wrappers that returntrue/false.processDaily($userId)awards daily login credits (guarded by user meta).
-
Admin configuration:
- Stored in
config/bbs.json - Validated and saved via
routes/admin-routes.php(BBS Settings API) - UI in
templates/admin/bbs_settings.twig
- Stored in
Defaults are loaded when config values are missing:
enabled:truesymbol:"$"(or blank if explicitly configured blank)daily_amount:25daily_login_delay_minutes:5approval_bonus:300netmail_cost:1echomail_reward:3
Reading balances:
UserCredit::getBalance($userId);Charging or awarding credits (preferred — returns true/false, no exceptions):
UserCredit::debit($userId, $amount, $description);
UserCredit::credit($userId, $amount, $description);For strict error handling, call transact() directly and handle exceptions.
Security: Credit balance modifications must only occur server-side. JavaScript requests business actions (play game, buy item); the server decides whether credits are involved and handles all transactions internally. Never expose credit-specific endpoints to client code.
When credits.enabled is false, transact() throws, and credit()/debit() return false. Handle gracefully:
- Optional rewards (e.g. echomail rewards): attempt
credit(); iffalse, log and continue. - Hard requirements (e.g. netmail cost): if
debit()returnsfalse, abort and return an error to the user. - Games: if credits are disabled, either disallow play with a clear message, or run in a for-fun mode that skips all credit calls.
- If balances aren't updating, verify
credits.enabledinbbs.jsonistrue, and that the migration creatinguser_transactionsandusers.credit_balanceran. - If symbols don't match, check
credits.symbolinbbs.json. An empty string is valid and results in no symbol display.
Several features require additional background daemons or services beyond the core web interface and binkp mailer. These are optional — only run the ones that apply to the features you want to offer.
BinktermPHP supports three door game types. See Doors.md for shared setup (multiplexing bridge, WebSocket configuration, reverse proxy) and type-specific documentation below.
| Type | Description | Doc |
|---|---|---|
| WebDoors | HTML5/JavaScript games in a browser iframe — no extra server-side process required | WebDoors.md |
| Native Doors | Linux binaries or Windows executables launched via PTY | NativeDoors.md |
| DOS Doors | Classic DOS games running under DOSBox-X | DOSDoors.md |
Native Doors and DOS Doors both require the multiplexing bridge: scripts/dosbox-bridge/multiplexing-server.js. See Doors.md for full setup, environment variables, reverse proxy configuration, and service/daemon installation.
cd scripts/dosbox-bridge
npm install # installs ws, pg, node-pty, iconv-lite, dotenv
# Development
node scripts/dosbox-bridge/multiplexing-server.js
# Production (daemon mode)
node scripts/dosbox-bridge/multiplexing-server.js --daemonPID file: data/run/multiplexing-server.pid — Log: data/logs/multiplexing-server.log
Provides Multi Relay Chat — a real-time chat network for FTN BBSs. Maintains a persistent connection to an MRC server, relays messages, and keeps room state in the database. See MRC_Chat.md for configuration and setup.
php scripts/mrc_daemon.php --daemonPID file: data/run/mrc_daemon.pid
Serves BBS content (user capsules, echomail) over the Gemini protocol via TLS on port 1965. See GeminiCapsule.md for TLS certificate setup and configuration.
php scripts/gemini_daemon.php --daemonPID file: data/run/gemini_daemon.pid — Log: data/logs/gemini_daemon.log
The Telnet and SSH daemons provide a classic BBS terminal interface alongside the web UI. See TerminalServer.md for Telnet and SSHServer.md for SSH setup.
Processes robot command messages posted to special echo areas (e.g. AREAS.BBS subscription management, BBS directory updates). See Robots.md for available robots and configuration.
Maintenance and scheduling scripts typically run via cron or the admin daemon's task scheduler. See MAINTENANCE.md and CLI.md for full details.
| Script | Purpose |
|---|---|
scripts/binkp_scheduler.php |
Schedules automatic uplink polling |
scripts/binkp_poll.php |
Polls a single uplink on demand |
scripts/echomail_maintenance.php |
Prunes old messages per area retention settings |
scripts/chat_cleanup.php |
Removes expired MRC chat history |
scripts/logrotate.php |
Rotates application log files |
scripts/database_maintenance.php |
Periodic database VACUUM and upkeep |
scripts/update_nodelists.php |
Downloads and imports FTN nodelist files |
scripts/activity_digest.php |
Sends periodic activity digest emails |
scripts/rss_poster.php |
Posts RSS feed items as echomail |
scripts/weather_report.php |
Posts weather reports as echomail |
The restart_daemons.sh (Linux) and start_daemons_windows.cmd / start_daemons_windows.ps1 (Windows) scripts start or restart all long-running daemons in one step.
- FAQ: See
FAQ.mdfor common questions and troubleshooting - Antivirus: See
docs/AntiVirus.mdfor virus scanning setup and configuration - WebDoor API: See
docs/WebDoors.mdfor game integration - Door Games: See
docs/Doors.mdfor the multiplexing bridge and door types - Localization: See
docs/Localization.mdfor the full i18n workflow - Upgrade Guides: Check
docs/UPGRADING_x.x.x.mdfiles for version-specific changes