Proposal: Hybrid FTN Filebone & BitTorrent Swarm Distribution
Draft notice: This proposal is a draft, was generated by AI, and may not have been reviewed for accuracy. Implementation details should be validated against current codebase state before any work begins.
Original concept: The hybrid FTN filebone + BitTorrent swarm idea described in this document originated from a post in the BBSing 2.0 Facebook group.
Overview
This proposal describes a hybrid file distribution model that layers BitTorrent swarm delivery on top of the existing FTN filebone and TIC processing infrastructure. The two systems operate as complementary layers rather than replacements for one another: FTN handles network orchestration (what files exist, where they belong, which nodes carry them, access control), while BitTorrent handles the actual bytes-in-flight problem through peer-assisted swarm delivery.
The result is that a file distributed across the BBS network can be downloaded by an end user from multiple nodes simultaneously, with bandwidth shared across the swarm, while the sysop retains full control over which files are hosted, who can access them, and how much bandwidth the torrent system is allowed to consume.
The user-facing communication channel for swarm download sessions is BinkStream — the existing WebSocket/SSE real-time layer already present in every BinktermPHP installation. BinkStream carries transfer commands from the browser to the BBS and streams progress events back. The actual file bytes are assembled server-side by the swarm daemon and delivered to the browser over a standard authenticated HTTP download endpoint once ready. This means no new port, no separate WebSocket daemon, and no additional authentication infrastructure for the user-facing side.
Magnet links are a first-class citizen in this design, not an afterthought. They provide a compact, tracker-independent reference to any file in the network and can be displayed alongside traditional download options without requiring the user to understand the underlying mechanism.
Table of Contents
- Goals
- Non-Goals
- Architecture Overview
- Component Design
- Database Changes
- File Area Integration
- Admin Configuration
- User Experience
- Security Considerations
- Implementation Phases
- B-Modem: The Original Concept (Direct-to-User Swarming)
- Open Questions
Goals
- Allow file downloads to be served from multiple BBS nodes simultaneously, reducing per-node bandwidth load.
- Preserve all existing TIC processing, file area policy, and FREQ workflows unchanged.
- Let sysops cap how much bandwidth the torrent subsystem may consume, both in aggregate and per-user.
- Generate magnet links for any file in a file area so users can obtain them via any compatible client if they prefer.
- Present swarm downloading as a natural part of the existing file queue workflow, not as a separate system users must learn.
- Keep participation optional: a node that does not run the torrent daemon continues to operate normally.
- Reuse BinkStream as the user-facing real-time channel with no new ports or authentication infrastructure.
- Deliver completed files through whichever mechanism suits the access context: HTTP/HTTPS link in the web UI, or ZModem over a telnet session.
Non-Goals
- Replacing the existing TIC/filebone distribution path.
- Supporting public internet BitTorrent traffic or standard torrent client connections by default (opt-in only).
- Distributing echomail or netmail content through this system.
- Implementing a DHT crawler or general-purpose torrent indexer.
- Pushing raw piece data through the BinkStream channel (BinkStream is the control and progress plane only).
- Defining a new file transfer protocol — delivery to the end user uses existing mechanisms (HTTP in the web UI, ZModem over telnet, which is the only transfer protocol currently supported for telnet sessions).
Architecture Overview
┌─────────────────────────────────────────────────────────────────┐
│ FTN Filebone Layer │
│ TIC processing → file area policy → FREQ routing │
│ Also carries: .torrent sidecar files via dedicated TIC area │
└───────────────────────────┬─────────────────────────────────────┘
│ torrent metadata propagates
▼
┌─────────────────────────────────────────────────────────────────┐
│ Network Tracker / Announce │
│ Shared tracker(s) all participating nodes announce to. │
│ Handles live peer lists — no need to redistribute .torrent │
│ files when nodes join or leave the swarm. │
└───────────────────────────┬─────────────────────────────────────┘
│ peer lists
▼
┌─────────────────────────────────────────────────────────────────┐
│ BBS Node — Transmission (external torrent client) │
│ Runs on the server; never directly exposed to end users. │
│ Seeds/leeches via BitTorrent among BBS nodes. │
│ Assembles completed files into a per-user staging area. │
└──────────────┬────────────────────────────┬─────────────────────┘
│ progress events via │ completed file
│ Unix socket → realtime │ → HTTP download endpoint
│ server in-memory dispatch │
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ BinkStream (existing infrastructure) │
│ WebSocket / SSE transport already present in every node. │
│ Carries commands (UI→BBS) and events (BBS→UI) for B-Modem. │
│ Progress events pushed in-memory — no database writes. │
│ Authentication is the existing BBS session — no new tokens. │
└───────────────────────────┬─────────────────────────────────────┘
│ window.BinkStream.on/send
▼
┌─────────────────────────────────────────────────────────────────┐
│ End User Browser │
│ Selects "B-Modem" from the file transfer protocol list. │
│ Receives progress events; fetches completed files via HTTP. │
│ Sees peer count, speed, ETA — one file at a time in the queue. │
└─────────────────────────────────────────────────────────────────┘
Two-plane design
| Plane |
Transport |
Content |
| Control + Progress |
BinkStream WebSocket (in-memory, no DB) |
Queue commands, progress %, speed, peer count, ETA, ready notification |
| Delivery (web) |
HTTPS download link |
File bytes served by the web server once assembly is complete |
| Delivery (telnet) |
ZModem |
Standard telnet transfer initiated after the node confirms the file is ready |
The swarm daemon never talks directly to the browser. Progress events are sent from the daemon to the realtime server (src/Realtime/WebSocketServer.php) via a Unix socket. The realtime server dispatches them directly to the user's live WebSocket connection from its in-memory connection table — no database writes, no sse_events rows. This keeps high-frequency streaming data entirely off PostgreSQL.
B-Modem requires an active WebSocket connection. If the user's BinkStream transport has fallen back to SSE, the transfer UI informs them and offers to wait or retry once WebSocket is available. This is a deliberate constraint: SSE's polling model is not suitable for the update frequency that a useful transfer progress display requires.
The key design decision for node-to-node transfers is that .torrent files are not redistributed through the filebone when the peer list changes. Peer discovery is the tracker's job. The .torrent file — or its magnet link equivalent — is distributed once through TIC, establishing the content hash. After that, the tracker maintains the live peer list dynamically.
Component Design
TIC Distribution of Torrent Metadata
When a file is added to a file area that participates in the hybrid system, two artifacts are generated:
- The file itself, distributed as normal through TIC.
- A
.torrent sidecar, distributed through a dedicated TIC area (e.g. BSWARM_META or a per-network equivalent). The sidecar carries the BitTorrent info hash and tracker announce URL but not the content itself.
The sidecar file follows a naming convention tied to the original file:
<original-basename>.torrent
Receiving nodes that run the swarm daemon pick up the .torrent sidecar, register it with their local daemon, and begin seeding the file if they have it (or leeching it if they do not yet have the content but want to). Nodes that do not run the daemon ignore the sidecar area entirely.
A new field on the files table (torrent_info_hash, torrent_announce_url) stores this metadata so it is available without parsing the sidecar file on every request.
Tracker Infrastructure
Rather than relying on the public DHT (which would expose node IP addresses to the open internet by default), the network operates one or more private trackers that only participating BBS nodes announce to. The tracker URL is embedded in the .torrent sidecar.
Options in increasing order of decentralisation:
| Option |
Description |
Notes |
| Central tracker |
One tracker run by the network hub |
Simple; single point of failure |
| Federated trackers |
Multiple trackers, all listed in the .torrent |
Resilient; standard multi-tracker spec |
| Private DHT |
DHT bootstrapped through known BBS nodes |
No central server; harder to set up |
| PEX only |
Peers exchange peer lists directly |
No tracker at all; requires initial contact |
The recommended starting point is a federated tracker arrangement (two or three trackers run by established network hubs), with PEX enabled so peers can find each other even if a tracker is temporarily down.
DHT should be disabled by default to prevent BBS node IP addresses from appearing in the public DHT, but can be opt-in for sysops who do not mind public visibility.
Magnet Link Support
A magnet link encodes enough information to locate a torrent without needing the .torrent file:
magnet:?xt=urn:btih:<info-hash>&dn=<display-name>&tr=<tracker-url>&tr=<tracker-url-2>
For every file that has a registered torrent_info_hash, the system can generate a magnet link on demand. Magnet links always include the private tracker announce URL(s) — they are never tracker-less. DHT is off by default, so a magnet link without a tracker URL would be non-functional in the standard configuration. This also means magnet links are only useful to clients that can reach the private tracker, which in practice means participating BBS nodes.
Magnet links are:
- Displayed in the file detail view alongside the standard download button.
- Available via a new API endpoint:
GET /api/files/{id}/magnet
- Included in FREQ responses when the requesting node supports the hybrid protocol (indicated by a capability flag in the session handshake).
- Embedded in TIC comment fields as a
MAGNET: kludge line so downstream nodes receive the link alongside the .torrent sidecar.
The BBS web UI can accept a magnet link as input — if a user pastes one into the download queue, the system resolves it to a local file ID where possible, or initiates a swarm fetch otherwise.
Swarm Download: Server-Side Assembly with Context-Dependent Delivery
Swarm downloading is a server-side background operation, not a file transfer protocol. The user initiates a swarm fetch for one or more queued files; the node assembles the file from the peer swarm; and once complete the user receives the file through whichever delivery mechanism is appropriate for their current session — an HTTP/HTTPS download link in the web UI, or a ZModem transfer in a telnet session. The two steps (swarm assembly and file delivery) are independent.
The swarm daemon runs entirely server-side, exchanging pieces with other BBS nodes over standard BitTorrent. As it assembles pieces it sends progress updates to the realtime server via a Unix socket. The realtime server pushes them directly to the user's WebSocket connection from its in-memory connection table — no database writes involved. When all pieces of a file are assembled, the node notifies the user that their file is ready, and delivery proceeds through the normal channel for their session type.
Web UI flow:
- User adds files to their download queue as normal.
- They select Swarm Download for the queue (a queue-level option, not a per-file transfer protocol).
- The browser sends a
swarm_start_transfer command over BinkStream.
- A progress panel opens showing swarm statistics: peer count, speed, ETA, queue position.
- As each file completes, the browser receives a
swarm_transfer_complete event. An HTTPS download link appears (or triggers automatically), served by the existing web server — no new delivery infrastructure required.
- The queue advances to the next file.
Telnet session flow:
- User adds files to their download queue through the telnet BBS menu as normal.
- They select Swarm Download for the queue.
- The node begins assembling the files from the swarm in the background.
- The terminal displays a progress screen with swarm statistics.
- When each file is ready, the user is prompted to choose a transfer protocol (ZModem) exactly as they would for a file already hosted locally. From the telnet client's perspective, the file is just a local file being sent — it has no knowledge of how the node obtained it.
BinkStream Commands and Events
These commands and events are used by the web UI. The telnet session has its own terminal-side progress display driven by the same underlying swarm session; it does not use BinkStream.
Commands (browser → BBS, via window.BinkStream.send)
| Command |
Payload |
Description |
swarm_start_transfer |
{file_ids: [int, ...], speed_limit_kbps: int|null} |
Begin a swarm download session for the given file queue |
swarm_pause_transfer |
{session_id: string} |
Pause all active fetches in the session |
swarm_resume_transfer |
{session_id: string} |
Resume a paused session |
swarm_cancel_transfer |
{session_id: string} |
Cancel the session and discard staged pieces |
swarm_set_speed_limit |
{session_id: string, kbps: int} |
Adjust the per-session download speed cap mid-transfer |
Events (BBS → browser, via window.BinkStream.on)
| Event type |
Payload |
Description |
swarm_session_started |
{session_id, file_ids, queue_length} |
Confirms the session was accepted |
swarm_transfer_progress |
{session_id, file_id, percent, speed_kbps, peers, eta_seconds} |
Periodic progress update (emitted every ~2 seconds) |
swarm_transfer_complete |
{session_id, file_id, download_url, expires_at} |
File is ready; browser fetches from download_url |
swarm_transfer_error |
{session_id, file_id, error_code} |
Transfer failed for this file |
swarm_session_complete |
{session_id} |
All files in the queue have completed or errored |
swarm_peer_update |
{session_id, file_id, peers} |
Peer count changed (emitted on significant changes only) |
All events are dispatched in-memory by the realtime server directly to the target user's WebSocket connection — they are never written to sse_events or any other database table. The realtime server already maintains a per-user connection map; swarm events are routed by user_id through that map. The session_id is a server-generated UUID returned in swarm_session_started and echoed in all subsequent events for that session.
The download_url in swarm_transfer_complete points to an existing API route such as GET /api/files/{id}/download with a short-lived token appended as a query parameter. The token is generated server-side and tied to the user's session — it is never sent by the browser, only by the server in the event payload.
Node Presence and Peer Availability
The tracker already handles the live question of which nodes are currently online and seeding. However, the filebone benefits from a higher-level view: which nodes have announced that they carry a given file, regardless of whether they are online right now.
A swarm_nodes table (see Database Changes) records the last-known announce status for each (info_hash, node_address) pair. This is populated by listening to tracker announce/scrape events (if the local node is running the tracker) or by periodic scrape requests to the tracker.
This table powers:
- Admin visibility: sysops can see which network nodes are seeding a given file.
- Network health reporting: a dashboard showing which files have healthy swarms vs. which are at risk of becoming unavailable.
Bandwidth Governance
Two levels of bandwidth control are provided:
Sysop level (configured in Admin → BBS Settings or via .env):
| Setting |
Description |
SWARM_MAX_UPLOAD_KBPS |
Maximum aggregate upload bandwidth for all torrent connections |
SWARM_MAX_DOWNLOAD_KBPS |
Maximum aggregate download bandwidth (leeching from swarm) |
SWARM_UPLOAD_RATIO |
What fraction of SWARM_MAX_UPLOAD_KBPS may be used by node-to-node seeding |
User level (set per-session via the swarm_start_transfer command or adjusted mid-session via swarm_set_speed_limit):
- Download limit for the current session (
speed_limit_kbps)
The swarm daemon enforces sysop limits as hard caps. User limits are applied on top — a user cannot set a limit higher than the sysop cap.
Database Changes
files table additions
ALTER TABLE files ADD COLUMN torrent_info_hash VARCHAR(64);
ALTER TABLE files ADD COLUMN torrent_announce_url TEXT;
ALTER TABLE files ADD COLUMN torrent_sidecar_path TEXT;
ALTER TABLE files ADD COLUMN distribution_method VARCHAR(16) NOT NULL DEFAULT 'tic_original';
ALTER TABLE files ADD COLUMN content_available BOOLEAN NOT NULL DEFAULT TRUE;
distribution_method records how this specific file was or will be hatched. Valid values:
| Value |
Meaning |
tic_original |
File content hatched through TIC as normal; no torrent involvement |
tic_torrent |
Only the .torrent sidecar is hatched through TIC; content distributed exclusively via swarm |
tic_both |
Both the original file and the .torrent sidecar are hatched through TIC (useful for testing/transition; does not reduce filebone bandwidth) |
local |
File is not hatched at all; remains on this node only |
The value is inherited from the file area's default_distribution_method at the time the file is approved, but can be overridden per-file by a sysop before hatching occurs.
content_available is TRUE for files whose content is present on disk (uploaded locally or received via tic_original/tic_both). For files received via tic_torrent, it is set to FALSE when the file record is created from the TIC and flipped to TRUE by the swarm daemon once the content has been fully assembled and piece-verified. The file listing and download endpoints check this flag to determine whether to show the pending indicator and disable the download button.
file_areas table addition
ALTER TABLE file_areas ADD COLUMN default_distribution_method VARCHAR(16) NOT NULL DEFAULT 'tic_original';
Sets the distribution method applied to all new files approved into this area. Sysops can override it on individual files before hatching. The same four values apply as on files.distribution_method.
New table: swarm_nodes
Tracks which network nodes have announced possession of a file.
CREATE TABLE swarm_nodes (
id SERIAL PRIMARY KEY,
info_hash VARCHAR(64) NOT NULL,
node_ftn_address VARCHAR(32) NOT NULL,
node_ws_endpoint TEXT,
last_announce_at TIMESTAMPTZ,
is_online BOOLEAN NOT NULL DEFAULT TRUE,
UNIQUE (info_hash, node_ftn_address)
);
CREATE INDEX ON swarm_nodes (info_hash);
New table: swarm_sessions
Tracks active and recent B-Modem transfer sessions per user.
CREATE TABLE swarm_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
file_ids INTEGER[] NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'active',
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ
);
CREATE INDEX ON swarm_sessions (user_id, status);
The swarm_transfer_tokens table from the previous draft is not needed. Authentication is handled by the existing BBS session validated at the BinkStream connection. Download URLs use short-lived query-parameter tokens generated at the moment the swarm_transfer_complete event is emitted and stored in the existing session infrastructure.
File Area Integration
No changes to the existing file area schema or TIC processing pipeline are required for nodes that do not participate in the swarm. For participating nodes:
Per-area distribution method
Each file area has a Default Distribution Method selector with the four options described in Database Changes. This controls how new files approved into that area are hatched by default:
tic_original — normal filebone behaviour; swarm not involved. Appropriate for areas that carry small files or where swarm coverage cannot be assumed.
tic_torrent — only the .torrent sidecar is hatched; content reaches other nodes via the swarm. This is the setting that actually reduces filebone bandwidth for large files. Should only be used on areas where sufficient swarm adoption exists among subscribing nodes to guarantee availability.
tic_both — hatch both the original file and the sidecar. Useful during initial rollout or testing; does not reduce filebone bandwidth.
local — file is not hatched; stays on this node only.
Per-file distribution method at upload
The TIC file is generated and queued at the moment the upload is processed, so the distribution method must be chosen at upload time — there is no useful "after the fact" override window. The upload form gains a Distribution Method selector visible only to sysops, pre-populated with the area's default_distribution_method. The sysop can change it before submitting to override the area default for that specific file.
This allows, for example, an area that defaults to tic_torrent to fall back to tic_original for a file that is too small to benefit from swarm distribution, or to mark a file local that should not leave the node at all.
Once the upload is submitted and hatching has been queued, the distribution_method on the files row is informational — it records how the file was distributed, not an instruction that can be re-executed.
TIC sidecar handling
The .torrent sidecar travels through the filebone in the same TIC area as the file it belongs to — no separate metadata area is needed. On the receiving end, the TIC processor recognises the .torrent sidecar by extension, hands it to the swarm daemon for registration, and discards it from the file area. It never appears in the file listing, just as .tic files themselves are processed and consumed rather than stored as browsable files.
File availability on receiving nodes
When a TIC is processed for a file with distribution_method of tic_torrent, the file record is created in the file area immediately — correct filename, description, and size are all present from the TIC metadata. The content has not arrived yet. The file is shown in the file area listing in a pending state while the swarm daemon fetches it in the background.
- The file listing shows a pending indicator (e.g. a progress badge) until the content is locally available.
- Sysops can see swarm fetch progress in the file detail panel.
- Users can queue a pending file for Swarm Download, which signals the daemon to prioritise fetching it.
- The standard download button is disabled while the file is pending, with a message indicating it is being retrieved from the swarm.
- Once the daemon has assembled the complete file and verified the piece hashes, the file transitions to available and all download options are enabled normally.
File detail panel
The file detail panel in the web UI gains a Swarm section (visible when torrent_info_hash is set) showing: info hash, magnet link (copy button), current seeder count from tracker scrape, and a list of known network nodes carrying the file.
The file download queue gains a Swarm Download option when the user is logged in and one or more queued files have a registered info hash.
Existing FREQ handling is unchanged. A future extension could include the magnet link in FREQ responses, but that is out of scope for this proposal.
Admin Configuration
Transmission Setup
Transmission must be installed and configured independently of the BBS. The sysop is responsible for this step; the BBS integration layer assumes Transmission is already running and reachable.
Installation (Debian/Ubuntu):
apt install transmission-daemon
systemctl enable --now transmission-daemon
Key Transmission settings (configured in /etc/transmission-daemon/settings.json — stop the daemon before editing):
| Transmission setting |
Recommended value |
Notes |
rpc-enabled |
true |
Required for BBS integration |
rpc-bind-address |
127.0.0.1 |
Keep RPC local-only; do not expose to the internet |
rpc-port |
9091 |
Default; change if there is a conflict |
rpc-authentication-required |
true |
Recommended; set rpc-username and rpc-password |
rpc-whitelist-enabled |
true |
Restrict API access to localhost |
rpc-whitelist |
127.0.0.1 |
|
download-dir |
path to BBS swarm staging directory |
Must be writable by the debian-transmission user and readable by the web server user |
incomplete-dir-enabled |
true |
Store partial downloads separately from completed files |
incomplete-dir |
path to a temp directory |
Keeps partial files out of the BBS file serving path |
dht-enabled |
false |
Disable public DHT to prevent node IP exposure (default for private networks) |
lpd-enabled |
false |
Disable local peer discovery |
peer-port |
51413 |
Inbound peer port; must be open in the firewall for other nodes to connect |
speed-limit-up-enabled |
optional |
Can also be managed via BBS admin settings through the RPC API |
speed-limit-down-enabled |
optional |
|
After editing settings.json, restart the daemon:
systemctl restart transmission-daemon
File permissions: The download-dir must be accessible to both the debian-transmission user (which Transmission runs as) and the web server user (e.g. www-data). A common approach is to add www-data to the debian-transmission group and set group read permissions on the staging directory.
BBS Settings
New settings in Admin → BBS Settings (Swarm section):
| Setting |
Description |
| Swarm enabled |
Master switch; disables integration and hides all swarm UI when off |
| Tracker announce URL(s) |
One or more tracker URLs the local node announces to |
| Max upload bandwidth |
Aggregate upload cap for node-to-node seeding (kbps); applied via Transmission RPC |
| Max download bandwidth |
Aggregate download cap for leeching from peers (kbps); applied via Transmission RPC |
| Allow public DHT |
Whether to enable DHT in Transmission (default off) |
| Staged file TTL |
How long assembled files are kept in the staging directory before cleanup (minutes) |
| Transmission RPC host |
Hostname or IP of the Transmission instance (default 127.0.0.1) |
| Transmission RPC port |
Port Transmission's JSON-RPC endpoint listens on (default 9091) |
| Transmission RPC user / password |
Credentials matching rpc-username / rpc-password in Transmission's settings |
These settings are stored in data/bbs.json and written via the admin daemon in the same pattern as other BBS settings. Transmission's RPC port is a local-only connection between the BBS and Transmission and should not be exposed publicly. No separate port configuration is needed for the user-facing side — B-Modem uses BinkStream, which already has its own port and proxy configuration.
User Experience
Web UI: Swarm Download
- User browses file areas and adds files to their queue as normal.
- At the download queue screen, they choose Swarm Download — a queue-level option separate from the per-file HTTP download button.
- The browser sends
swarm_start_transfer over BinkStream.
- A progress panel opens showing:
- File name and size (current file)
- Progress bar
- Transfer speed (current + average)
- Peers connected
- Estimated time remaining
- Queue position (e.g. "File 2 of 5")
- As each file finishes assembling, a
swarm_transfer_complete event delivers an HTTPS download link. The browser triggers the save-file dialogue automatically.
- The queue advances. The daemon may speculatively begin fetching later files in the background.
Telnet: Swarm Download
- User browses file areas and adds files to their queue through the normal telnet BBS menus.
- At the download queue screen, they choose Swarm Download alongside the standard transfer protocol options.
- The node begins assembling files from the swarm. A progress screen displays swarm statistics on the terminal.
- When each file is ready, the user is prompted to select a transfer protocol (ZModem) exactly as they would for any locally-hosted file. The swarm assembly step is invisible to the transfer protocol — it simply sees a file ready to send.
Magnet link access
- On any file detail panel: a Copy Magnet Link button.
- Users can paste a magnet link into a search/input field. If the info hash matches a known file in the system, the file detail page is shown. If not, the system can optionally initiate a swarm fetch (sysop-configurable).
Seeding back
End-user browsers do not participate as BitTorrent peers — seeding is a server-side operation between BBS nodes. The "seeding back" benefit in this model accrues at the network level: every BBS node that downloads a file also seeds it to other nodes, increasing swarm density automatically without requiring any action from end users.
Security Considerations
- IP exposure: Swarm participants' IP addresses are visible to each other and to the tracker. This applies to BBS node IPs, not end-user IPs (end users communicate with their local BBS node only via BinkStream/HTTP). Sysops should be informed of this before enabling swarm participation, especially if they host from a residential connection.
- Magnet link leakage: A sysop who participates in the swarm has the file, the tracker URL, and could share either through any channel. This is no different from the existing filebone trust model, where a sysop receiving a file through TIC has always been able to redistribute it outside the network. The swarm does not weaken this boundary — the trust model is sysops, the same as it has always been.
- Authentication: No new authentication mechanism is required for the user-facing side. The BinkStream connection is already authenticated by the BBS session cookie.
swarm_start_transfer commands are validated server-side against the same file access rules as any other download.
- Download URL lifetime: The
download_url emitted in swarm_transfer_complete events should expire quickly (e.g. 5 minutes) and be single-use to prevent sharing. If the browser does not fetch it within the window, the user can re-request the file.
- Piece verification: Standard BitTorrent SHA-1/SHA-256 piece hashes provide content integrity between nodes. A file that has been tampered with will fail piece verification and the daemon will discard it.
- Bandwidth amplification: A user who rapidly starts and cancels sessions could cause the daemon to initiate many partial swarm downloads. Rate limiting on
swarm_start_transfer commands (max N active sessions per user) mitigates this.
- Staged file storage: Assembled files waiting for browser download occupy disk space. The staged file TTL setting (default: 30 minutes) ensures they are cleaned up. The admin daemon should include swarm staging cleanup in its maintenance loop.
Implementation Phases
Phase 1 — Foundation
- Database migrations for
torrent_info_hash, torrent_announce_url, swarm_enabled on files.
swarm_nodes and swarm_sessions tables.
- Admin settings: swarm enable/disable, tracker URL(s), bandwidth caps, staged file TTL.
.torrent file generation when a file is added to a swarm-enabled area.
- Magnet link generation and
GET /api/files/{id}/magnet endpoint.
- Display of magnet link and info hash in the file detail panel.
Phase 2 — Sidecar TIC Distribution
- Metadata TIC area configuration.
- Automatic queuing of
.torrent sidecars for outbound TIC distribution.
- Inbound sidecar processing: parse and register info hash against the corresponding file.
Phase 3 — Torrent Client Integration and BinkStream Integration
Rather than building a bespoke BitTorrent daemon, this phase integrates with an existing torrent client over its management API. Transmission is the recommended client: it exposes a stable JSON-RPC API on a configurable local port, is available in every major Linux distribution's package manager, and has a straightforward PHP integration path.
src/Swarm/TransmissionClient.php — a thin PHP wrapper around the Transmission RPC API (POST /transmission/rpc). Responsible for: adding torrents, removing torrents, querying transfer status (speed, percent done, peer count, ETA), setting per-torrent speed limits, and seeding existing files.
src/Swarm/SwarmManager.php — the integration layer between the application data model (swarm_sessions, files) and the Transmission API. Translates application-level operations (start/pause/resume/cancel a user's swarm session) into the corresponding Transmission RPC calls.
- A PHP polling process (
scripts/swarm_poll.php) that runs on a short interval (e.g. every 2 seconds while active sessions exist) via the admin daemon, queries Transmission for in-progress torrent status, and forwards progress events to the realtime server via Unix socket. This avoids requiring Transmission to understand or call back into the BBS.
- Admin daemon command to check Transmission connectivity (useful for diagnosing misconfiguration). Transmission itself is started and stopped independently of the BBS — the admin daemon does not manage the Transmission process.
- BinkStream command handlers in
src/Realtime/CommandDispatcher.php:
swarm_start_transfer → validates access, creates swarm_sessions row, calls SwarmManager to add the torrent to Transmission
swarm_pause_transfer, swarm_resume_transfer, swarm_cancel_transfer, swarm_set_speed_limit
- Progress path:
swarm_poll.php → reads Transmission status → posts progress frames to realtime server Unix socket → realtime server dispatches in-memory to target user's WebSocket connection. No sse_events writes; no PostgreSQL involvement in the progress path.
- New message type in
src/Realtime/WebSocketServer.php to accept and route inbound swarm progress frames from the Unix socket.
- New
.env settings: SWARM_TRANSMISSION_HOST, SWARM_TRANSMISSION_PORT, SWARM_TRANSMISSION_USER, SWARM_TRANSMISSION_PASS, SWARM_STAGING_DIR.
Seeding existing files: When a file area has default_distribution_method of tic_torrent or when an individual file is flagged accordingly, SwarmManager calls Transmission's torrent-add with "paused": false and the local file path set as the download directory — Transmission recognises the file as complete and immediately begins seeding without re-downloading.
Phase 4 — User Interface
- Web UI: Swarm Download option on the download queue screen. Progress panel subscribing to
swarm_transfer_progress, swarm_transfer_complete, swarm_transfer_error, swarm_session_complete via window.BinkStream.on(...). Automatic HTTPS download trigger on swarm_transfer_complete.
- Telnet: Swarm Download option in the queue menu. Terminal progress screen driven by the swarm session. On completion, hand off to the standard transfer protocol prompt (ZModem).
- Magnet link paste/resolve in the file search input.
Phase 5 — Network Tracker
- Optional: self-hosted tracker (
scripts/swarm_tracker.php or a bundled lightweight tracker).
swarm_nodes population from tracker announce events.
- Admin dashboard: per-file seeder count, node availability, swarm health.
B-Modem: The Original Concept (Direct-to-User Swarming)
Note on framing: The server-side assembly design described earlier in this document (Transmission integration, SwarmManager.php, staged file delivery) is a compatibility fallback for clients that have no native BitTorrent capability. The concept described in this section — referred to as B-Modem (short for BitTorrent Modem; not to be confused with CompuServe's unrelated B-Modem file transfer protocol from the 1980s) — is the original intent of the hybrid filebone idea: the end user's client software participates directly in the BitTorrent swarm, receiving pieces from all seeding nodes simultaneously. The BBS node is simply one seed among many; it is not an intermediary assembler.
The core idea is straightforward: a lite BitTorrent client is built into or bundled with BBS terminal client software. The BBS feeds the client a queue of torrent references. The client joins the swarm, downloads pieces from every available seed in parallel, and notifies the BBS when each file is complete. The BBS ANSI/terminal display shows a traditional-looking file transfer progress screen — peer count, speed, progress bar — but behind it BitTorrent is doing what BitTorrent already does. No special swarm coordination code is needed beyond what BitTorrent provides natively.
The BBS does not need to re-invent swarming. Its role in this model is:
- Distribute
.torrent files (or magnet links) to clients via the existing TIC/filebone pipeline.
- Seed files it possesses, alongside every other node that has them.
- Present the transfer queue UI and display progress.
- Optionally run a tracker (or participate in a networked tracker arrangement with other BBS nodes).
Transport: WebSocket as the client connection layer
B-Modem as described assumes the BBS client connects over WebSocket rather than raw telnet. WebSocket wraps the connection in TLS and allows the BBS software and client to exchange structured data alongside the traditional terminal byte stream — something a classical telnet connection cannot do cleanly.
There is currently no universal BBS-scene standard for how WebSocket is used across different software — port assignments, framing, and API conventions vary by implementation. B-Modem would benefit from becoming one such standard: when a client connects and advertises B-Modem capability, the BBS knows it can send torrent metadata over the WebSocket channel and expect the client to handle it natively.
WebSocket in this context behaves somewhat like a lightweight VPN or SSH tunnel — it wraps whatever data needs to pass through it and applies encryption and authentication — but it is more natural for BBS use because it can run as a sidecar alongside the existing terminal stream on the same connection, rather than requiring a completely separate session.
B-Modem delivery paths
Path 1: Native B-Modem client (preferred)
The BBS terminal client has a built-in lite BitTorrent engine. The BBS sends torrent metadata (magnet link or .torrent content) over the WebSocket channel. The client:
- Receives the torrent queue from the BBS.
- Joins the swarm directly — connecting to all seeds (BBS nodes) that have the file.
- Downloads pieces from multiple sources simultaneously.
- Displays a progress screen that looks like a traditional file transfer UI to the user.
- When each file is complete, signals the BBS: "ready for next."
- The BBS advances the queue and sends the next torrent reference.
The user can optionally configure their client to keep or delete the .torrent file after the download completes.
The ANSI terminal display during transfer shows only what is going on — speed, peers, progress — exactly as an old-school ZModem screen would. The complexity lives in the client's BitTorrent engine, not in what is rendered on screen.
Path 2: ZModem .torrent hand-off (compatibility path for existing clients)
For clients that do not yet have a native B-Modem engine but do have an external BitTorrent client installed on the same machine:
- The BBS sends the
.torrent file to the client via ZModem — a standard file transfer the existing terminal software already understands.
- The client's local BitTorrent application picks up the
.torrent file (either automatically via a watch folder, or manually by the user) and begins downloading.
- When the download is complete, the user signals the BBS (e.g. presses a key or the client sends an acknowledgement).
- The BBS sends the next
.torrent in the queue via ZModem, and the process repeats.
This path requires no changes to existing terminal software and no new client development. It is less seamless — the BitTorrent download happens outside the terminal session — but it is functional today with any client that supports ZModem.
Path 3: Server-side assembly fallback
For clients with no BitTorrent capability at all (web browsers via HTTP, or telnet-only clients without a companion BitTorrent application), the server-side Transmission integration described earlier in this document applies. The BBS node assembles the file from the swarm and delivers it via HTTP download link (web) or ZModem (telnet). From the user's perspective it is a standard download; they are not a swarm peer.
WebSocket as a mandatory telnet security layer
A broader proposal connected to B-Modem: BBS software should be able to require that telnet connections arrive encapsulated in WebSocket, and reject raw telnet connections with an informational message listing supported clients, followed by NO CARRIER. This would make WebSocket the standard secure transport for BBS terminal access, analogous to what SSH provides for shell access — but better suited to BBS software because it can carry structured side-channel data (like B-Modem torrent queues) alongside the terminal stream.
For users on hardware that cannot speak WebSocket natively — vintage machines, hardware running old terminal software — a local proxy handles the translation. The proxy listens on a local telnet port and forwards connections to the BBS over WebSocket. It can run as a Docker container, a VM, a Raspberry Pi, or any small always-on device on the user's local network. It can be configured much like a telnet BBS door entry, with options to manually specify a BBS address. Dialup access via an attached modem could also be supported, letting the proxy be dialled into directly if desired.
This proposal is not specific to B-Modem — it is a general BBS connectivity and security position. No universal standard for WebSocket usage across BBS software currently exists; different implementations handle it differently. This would be a step toward establishing one.
Tracker architecture for B-Modem
Two viable arrangements:
| Arrangement |
Description |
| Central network tracker |
The network hub (e.g. LovlyNet HQ) runs a tracker all participating nodes announce to. Simple to deploy; single point of failure. |
| Federated trackers |
Multiple nodes each run a tracker; all tracker URLs are embedded in the .torrent file. Resilient to individual tracker outages. |
In both cases, .torrent files are distributed via TIC processing like any other filebone content. A local script on each BBS watches for incoming torrent files, registers them with the local client or seeding daemon, and handles cases where a new .torrent supersedes an older one for the same file. This can be a simple Python or PHP script — the heavy lifting (peer discovery, piece exchange) is handled by the BitTorrent client itself.
Summary: which path applies where
| Client type |
B-Modem path |
| Future terminal client with native B-Modem engine |
Path 1 — joins swarm directly over WebSocket |
| Existing terminal client + local BitTorrent app |
Path 2 — receives .torrent via ZModem, downloads externally |
| Web browser |
Path 3 — server-side assembly via Transmission, HTTP download |
| Telnet-only client, no companion BitTorrent app |
Path 3 — server-side assembly via Transmission, ZModem delivery |
Open Questions
-
Original intent: server-side assembly or direct-to-user swarming? Resolved: The original concept intends direct-to-user swarming — a lite BitTorrent client built into the terminal client software, with the BBS acting as one seed among many rather than an assembler. Server-side assembly (Transmission integration) is the compatibility fallback for clients without native B-Modem support, not the primary design. See B-Modem: The Original Concept for the full design including WebSocket transport, ZModem .torrent hand-off for existing clients, and the WebSocket-as-mandatory-telnet-security-layer proposal.
-
Torrent client choice. Resolved (leaning Transmission): Rather than building or embedding a BitTorrent daemon, the system integrates with an existing torrent client over its management API. Transmission is recommended as the starting point: its JSON-RPC API is simple, its PHP integration requires no additional library (plain curl/file_get_contents), and it is available in every major Linux package repository. Alternative clients and their tradeoffs:
| Client |
API |
Notes |
| Transmission |
JSON-RPC (HTTP) |
Recommended. Simple API, easy PHP integration, wide availability. |
| Deluge |
JSON-RPC (HTTP) |
Similar simplicity; fewer users than Transmission on headless Linux servers. |
| rTorrent |
XML-RPC |
More powerful, popular on seedboxes, but XML-RPC is more awkward to call from PHP. |
| libtorrent (Python binding) |
Custom IPC |
Maximum control; requires a custom Python service to bridge to PHP. |
| WebTorrent (Node.js) |
Custom IPC |
Relevant only for the B-Modem alternative where browser peers must connect via WebRTC. Not recommended for the primary server-side design. |
The sysop installs and configures Transmission independently of the BBS. The BBS integration layer (src/Swarm/TransmissionClient.php) communicates with it over the configured local RPC endpoint.
-
IPC between swarm daemon and realtime server. Progress events must not go through PostgreSQL — the write volume is too high and the data is entirely transient. The swarm daemon should communicate directly with the realtime server (scripts/realtime_server.php) via a dedicated Unix socket, bypassing the admin daemon entirely for the progress path. The realtime server already owns the WebSocket connection map and can push to a specific user's connection without any database involvement. This requires adding an inbound IPC listener to src/Realtime/WebSocketServer.php alongside the existing browser-facing WebSocket listener.
-
Seeding responsibility. Resolved: Seeding follows the distribution method flags. If a file area's default_distribution_method is tic_torrent, all files in that area are seeded automatically — the area-level flag is an implicit commitment to participate in the swarm for all content it carries. For areas with any other default, seeding is determined per-file: files with distribution_method of tic_torrent or tic_both are seeded; files with tic_original or local are not.
-
TIC metadata area governance. This is as much a network policy question as a software one. LovlyNet can host and administer the metadata area, making it the natural starting point for rollout. For broader reach across established networks (FidoNet, fsxNet, etc.), either an existing network would need to allow a new metadata area, or a dedicated lightweight FTN network solely for swarm metadata distribution could be established that any BBS could join regardless of other network memberships. The software itself is indifferent to which network carries the metadata area — the sysop configures the area tag and the system uses whatever TIC pipeline delivers files to it.
-
Torrent format: v1 vs. v2. BitTorrent v2 uses SHA-256 per-file hashes and supports hybrid torrents (v1+v2). New implementations should target v2 with v1 compatibility for broader client support.
-
Magnet link scope. Should magnet links generated by this system include the BBS tracker announce URLs, making them specific to the private network? Or should they be tracker-less (relying on DHT) for compatibility with standard clients? Resolved: Magnet links always include the private tracker announce URL(s). DHT is off by default, making tracker-less magnet links non-functional in the standard configuration. Public access is not a goal of this system. The trust boundary is sysops, the same as the existing filebone model — a sysop who wants to leak a file has always been able to do so regardless of the torrent system.
-
FREQ integration. A node receiving a FREQ for a file it does not host could respond with a magnet link instead of a binary transfer. This would be a significant improvement to FREQ for large files and is worth a follow-on proposal once the foundation is in place.
Proposal: Hybrid FTN Filebone & BitTorrent Swarm Distribution
Overview
This proposal describes a hybrid file distribution model that layers BitTorrent swarm delivery on top of the existing FTN filebone and TIC processing infrastructure. The two systems operate as complementary layers rather than replacements for one another: FTN handles network orchestration (what files exist, where they belong, which nodes carry them, access control), while BitTorrent handles the actual bytes-in-flight problem through peer-assisted swarm delivery.
The result is that a file distributed across the BBS network can be downloaded by an end user from multiple nodes simultaneously, with bandwidth shared across the swarm, while the sysop retains full control over which files are hosted, who can access them, and how much bandwidth the torrent system is allowed to consume.
The user-facing communication channel for swarm download sessions is BinkStream — the existing WebSocket/SSE real-time layer already present in every BinktermPHP installation. BinkStream carries transfer commands from the browser to the BBS and streams progress events back. The actual file bytes are assembled server-side by the swarm daemon and delivered to the browser over a standard authenticated HTTP download endpoint once ready. This means no new port, no separate WebSocket daemon, and no additional authentication infrastructure for the user-facing side.
Magnet links are a first-class citizen in this design, not an afterthought. They provide a compact, tracker-independent reference to any file in the network and can be displayed alongside traditional download options without requiring the user to understand the underlying mechanism.
Table of Contents
Goals
Non-Goals
Architecture Overview
Two-plane design
The swarm daemon never talks directly to the browser. Progress events are sent from the daemon to the realtime server (
src/Realtime/WebSocketServer.php) via a Unix socket. The realtime server dispatches them directly to the user's live WebSocket connection from its in-memory connection table — no database writes, nosse_eventsrows. This keeps high-frequency streaming data entirely off PostgreSQL.B-Modem requires an active WebSocket connection. If the user's BinkStream transport has fallen back to SSE, the transfer UI informs them and offers to wait or retry once WebSocket is available. This is a deliberate constraint: SSE's polling model is not suitable for the update frequency that a useful transfer progress display requires.
The key design decision for node-to-node transfers is that
.torrentfiles are not redistributed through the filebone when the peer list changes. Peer discovery is the tracker's job. The.torrentfile — or its magnet link equivalent — is distributed once through TIC, establishing the content hash. After that, the tracker maintains the live peer list dynamically.Component Design
TIC Distribution of Torrent Metadata
When a file is added to a file area that participates in the hybrid system, two artifacts are generated:
.torrentsidecar, distributed through a dedicated TIC area (e.g.BSWARM_METAor a per-network equivalent). The sidecar carries the BitTorrent info hash and tracker announce URL but not the content itself.The sidecar file follows a naming convention tied to the original file:
Receiving nodes that run the swarm daemon pick up the
.torrentsidecar, register it with their local daemon, and begin seeding the file if they have it (or leeching it if they do not yet have the content but want to). Nodes that do not run the daemon ignore the sidecar area entirely.A new field on the
filestable (torrent_info_hash,torrent_announce_url) stores this metadata so it is available without parsing the sidecar file on every request.Tracker Infrastructure
Rather than relying on the public DHT (which would expose node IP addresses to the open internet by default), the network operates one or more private trackers that only participating BBS nodes announce to. The tracker URL is embedded in the
.torrentsidecar.Options in increasing order of decentralisation:
.torrentThe recommended starting point is a federated tracker arrangement (two or three trackers run by established network hubs), with PEX enabled so peers can find each other even if a tracker is temporarily down.
DHT should be disabled by default to prevent BBS node IP addresses from appearing in the public DHT, but can be opt-in for sysops who do not mind public visibility.
Magnet Link Support
A magnet link encodes enough information to locate a torrent without needing the
.torrentfile:For every file that has a registered
torrent_info_hash, the system can generate a magnet link on demand. Magnet links always include the private tracker announce URL(s) — they are never tracker-less. DHT is off by default, so a magnet link without a tracker URL would be non-functional in the standard configuration. This also means magnet links are only useful to clients that can reach the private tracker, which in practice means participating BBS nodes.Magnet links are:
GET /api/files/{id}/magnetMAGNET:kludge line so downstream nodes receive the link alongside the.torrentsidecar.The BBS web UI can accept a magnet link as input — if a user pastes one into the download queue, the system resolves it to a local file ID where possible, or initiates a swarm fetch otherwise.
Swarm Download: Server-Side Assembly with Context-Dependent Delivery
Swarm downloading is a server-side background operation, not a file transfer protocol. The user initiates a swarm fetch for one or more queued files; the node assembles the file from the peer swarm; and once complete the user receives the file through whichever delivery mechanism is appropriate for their current session — an HTTP/HTTPS download link in the web UI, or a ZModem transfer in a telnet session. The two steps (swarm assembly and file delivery) are independent.
The swarm daemon runs entirely server-side, exchanging pieces with other BBS nodes over standard BitTorrent. As it assembles pieces it sends progress updates to the realtime server via a Unix socket. The realtime server pushes them directly to the user's WebSocket connection from its in-memory connection table — no database writes involved. When all pieces of a file are assembled, the node notifies the user that their file is ready, and delivery proceeds through the normal channel for their session type.
Web UI flow:
swarm_start_transfercommand over BinkStream.swarm_transfer_completeevent. An HTTPS download link appears (or triggers automatically), served by the existing web server — no new delivery infrastructure required.Telnet session flow:
BinkStream Commands and Events
These commands and events are used by the web UI. The telnet session has its own terminal-side progress display driven by the same underlying swarm session; it does not use BinkStream.
Commands (browser → BBS, via
window.BinkStream.send)swarm_start_transfer{file_ids: [int, ...], speed_limit_kbps: int|null}swarm_pause_transfer{session_id: string}swarm_resume_transfer{session_id: string}swarm_cancel_transfer{session_id: string}swarm_set_speed_limit{session_id: string, kbps: int}Events (BBS → browser, via
window.BinkStream.on)swarm_session_started{session_id, file_ids, queue_length}swarm_transfer_progress{session_id, file_id, percent, speed_kbps, peers, eta_seconds}swarm_transfer_complete{session_id, file_id, download_url, expires_at}download_urlswarm_transfer_error{session_id, file_id, error_code}swarm_session_complete{session_id}swarm_peer_update{session_id, file_id, peers}All events are dispatched in-memory by the realtime server directly to the target user's WebSocket connection — they are never written to
sse_eventsor any other database table. The realtime server already maintains a per-user connection map; swarm events are routed byuser_idthrough that map. Thesession_idis a server-generated UUID returned inswarm_session_startedand echoed in all subsequent events for that session.The
download_urlinswarm_transfer_completepoints to an existing API route such asGET /api/files/{id}/downloadwith a short-lived token appended as a query parameter. The token is generated server-side and tied to the user's session — it is never sent by the browser, only by the server in the event payload.Node Presence and Peer Availability
The tracker already handles the live question of which nodes are currently online and seeding. However, the filebone benefits from a higher-level view: which nodes have announced that they carry a given file, regardless of whether they are online right now.
A
swarm_nodestable (see Database Changes) records the last-known announce status for each(info_hash, node_address)pair. This is populated by listening to tracker announce/scrape events (if the local node is running the tracker) or by periodic scrape requests to the tracker.This table powers:
Bandwidth Governance
Two levels of bandwidth control are provided:
Sysop level (configured in Admin → BBS Settings or via
.env):SWARM_MAX_UPLOAD_KBPSSWARM_MAX_DOWNLOAD_KBPSSWARM_UPLOAD_RATIOSWARM_MAX_UPLOAD_KBPSmay be used by node-to-node seedingUser level (set per-session via the
swarm_start_transfercommand or adjusted mid-session viaswarm_set_speed_limit):speed_limit_kbps)The swarm daemon enforces sysop limits as hard caps. User limits are applied on top — a user cannot set a limit higher than the sysop cap.
Database Changes
filestable additionsdistribution_methodrecords how this specific file was or will be hatched. Valid values:tic_originaltic_torrent.torrentsidecar is hatched through TIC; content distributed exclusively via swarmtic_both.torrentsidecar are hatched through TIC (useful for testing/transition; does not reduce filebone bandwidth)localThe value is inherited from the file area's
default_distribution_methodat the time the file is approved, but can be overridden per-file by a sysop before hatching occurs.content_availableisTRUEfor files whose content is present on disk (uploaded locally or received viatic_original/tic_both). For files received viatic_torrent, it is set toFALSEwhen the file record is created from the TIC and flipped toTRUEby the swarm daemon once the content has been fully assembled and piece-verified. The file listing and download endpoints check this flag to determine whether to show the pending indicator and disable the download button.file_areastable additionSets the distribution method applied to all new files approved into this area. Sysops can override it on individual files before hatching. The same four values apply as on
files.distribution_method.New table:
swarm_nodesTracks which network nodes have announced possession of a file.
New table:
swarm_sessionsTracks active and recent B-Modem transfer sessions per user.
The
swarm_transfer_tokenstable from the previous draft is not needed. Authentication is handled by the existing BBS session validated at the BinkStream connection. Download URLs use short-lived query-parameter tokens generated at the moment theswarm_transfer_completeevent is emitted and stored in the existing session infrastructure.File Area Integration
No changes to the existing file area schema or TIC processing pipeline are required for nodes that do not participate in the swarm. For participating nodes:
Per-area distribution method
Each file area has a Default Distribution Method selector with the four options described in Database Changes. This controls how new files approved into that area are hatched by default:
tic_original— normal filebone behaviour; swarm not involved. Appropriate for areas that carry small files or where swarm coverage cannot be assumed.tic_torrent— only the.torrentsidecar is hatched; content reaches other nodes via the swarm. This is the setting that actually reduces filebone bandwidth for large files. Should only be used on areas where sufficient swarm adoption exists among subscribing nodes to guarantee availability.tic_both— hatch both the original file and the sidecar. Useful during initial rollout or testing; does not reduce filebone bandwidth.local— file is not hatched; stays on this node only.Per-file distribution method at upload
The TIC file is generated and queued at the moment the upload is processed, so the distribution method must be chosen at upload time — there is no useful "after the fact" override window. The upload form gains a Distribution Method selector visible only to sysops, pre-populated with the area's
default_distribution_method. The sysop can change it before submitting to override the area default for that specific file.This allows, for example, an area that defaults to
tic_torrentto fall back totic_originalfor a file that is too small to benefit from swarm distribution, or to mark a filelocalthat should not leave the node at all.Once the upload is submitted and hatching has been queued, the
distribution_methodon thefilesrow is informational — it records how the file was distributed, not an instruction that can be re-executed.TIC sidecar handling
The
.torrentsidecar travels through the filebone in the same TIC area as the file it belongs to — no separate metadata area is needed. On the receiving end, the TIC processor recognises the.torrentsidecar by extension, hands it to the swarm daemon for registration, and discards it from the file area. It never appears in the file listing, just as.ticfiles themselves are processed and consumed rather than stored as browsable files.File availability on receiving nodes
When a TIC is processed for a file with
distribution_methodoftic_torrent, the file record is created in the file area immediately — correct filename, description, and size are all present from the TIC metadata. The content has not arrived yet. The file is shown in the file area listing in a pending state while the swarm daemon fetches it in the background.File detail panel
The file detail panel in the web UI gains a Swarm section (visible when
torrent_info_hashis set) showing: info hash, magnet link (copy button), current seeder count from tracker scrape, and a list of known network nodes carrying the file.The file download queue gains a Swarm Download option when the user is logged in and one or more queued files have a registered info hash.
Existing FREQ handling is unchanged. A future extension could include the magnet link in FREQ responses, but that is out of scope for this proposal.
Admin Configuration
Transmission Setup
Transmission must be installed and configured independently of the BBS. The sysop is responsible for this step; the BBS integration layer assumes Transmission is already running and reachable.
Installation (Debian/Ubuntu):
apt install transmission-daemon systemctl enable --now transmission-daemonKey Transmission settings (configured in
/etc/transmission-daemon/settings.json— stop the daemon before editing):rpc-enabledtruerpc-bind-address127.0.0.1rpc-port9091rpc-authentication-requiredtruerpc-usernameandrpc-passwordrpc-whitelist-enabledtruerpc-whitelist127.0.0.1download-dirdebian-transmissionuser and readable by the web server userincomplete-dir-enabledtrueincomplete-dirdht-enabledfalselpd-enabledfalsepeer-port51413speed-limit-up-enabledspeed-limit-down-enabledAfter editing
settings.json, restart the daemon:File permissions: The
download-dirmust be accessible to both thedebian-transmissionuser (which Transmission runs as) and the web server user (e.g.www-data). A common approach is to addwww-datato thedebian-transmissiongroup and set group read permissions on the staging directory.BBS Settings
New settings in Admin → BBS Settings (Swarm section):
127.0.0.1)9091)rpc-username/rpc-passwordin Transmission's settingsThese settings are stored in
data/bbs.jsonand written via the admin daemon in the same pattern as other BBS settings. Transmission's RPC port is a local-only connection between the BBS and Transmission and should not be exposed publicly. No separate port configuration is needed for the user-facing side — B-Modem uses BinkStream, which already has its own port and proxy configuration.User Experience
Web UI: Swarm Download
swarm_start_transferover BinkStream.swarm_transfer_completeevent delivers an HTTPS download link. The browser triggers the save-file dialogue automatically.Telnet: Swarm Download
Magnet link access
Seeding back
End-user browsers do not participate as BitTorrent peers — seeding is a server-side operation between BBS nodes. The "seeding back" benefit in this model accrues at the network level: every BBS node that downloads a file also seeds it to other nodes, increasing swarm density automatically without requiring any action from end users.
Security Considerations
swarm_start_transfercommands are validated server-side against the same file access rules as any other download.download_urlemitted inswarm_transfer_completeevents should expire quickly (e.g. 5 minutes) and be single-use to prevent sharing. If the browser does not fetch it within the window, the user can re-request the file.swarm_start_transfercommands (max N active sessions per user) mitigates this.Implementation Phases
Phase 1 — Foundation
torrent_info_hash,torrent_announce_url,swarm_enabledonfiles.swarm_nodesandswarm_sessionstables..torrentfile generation when a file is added to a swarm-enabled area.GET /api/files/{id}/magnetendpoint.Phase 2 — Sidecar TIC Distribution
.torrentsidecars for outbound TIC distribution.Phase 3 — Torrent Client Integration and BinkStream Integration
Rather than building a bespoke BitTorrent daemon, this phase integrates with an existing torrent client over its management API. Transmission is the recommended client: it exposes a stable JSON-RPC API on a configurable local port, is available in every major Linux distribution's package manager, and has a straightforward PHP integration path.
src/Swarm/TransmissionClient.php— a thin PHP wrapper around the Transmission RPC API (POST /transmission/rpc). Responsible for: adding torrents, removing torrents, querying transfer status (speed, percent done, peer count, ETA), setting per-torrent speed limits, and seeding existing files.src/Swarm/SwarmManager.php— the integration layer between the application data model (swarm_sessions,files) and the Transmission API. Translates application-level operations (start/pause/resume/cancel a user's swarm session) into the corresponding Transmission RPC calls.scripts/swarm_poll.php) that runs on a short interval (e.g. every 2 seconds while active sessions exist) via the admin daemon, queries Transmission for in-progress torrent status, and forwards progress events to the realtime server via Unix socket. This avoids requiring Transmission to understand or call back into the BBS.src/Realtime/CommandDispatcher.php:swarm_start_transfer→ validates access, createsswarm_sessionsrow, callsSwarmManagerto add the torrent to Transmissionswarm_pause_transfer,swarm_resume_transfer,swarm_cancel_transfer,swarm_set_speed_limitswarm_poll.php→ reads Transmission status → posts progress frames to realtime server Unix socket → realtime server dispatches in-memory to target user's WebSocket connection. Nosse_eventswrites; no PostgreSQL involvement in the progress path.src/Realtime/WebSocketServer.phpto accept and route inbound swarm progress frames from the Unix socket..envsettings:SWARM_TRANSMISSION_HOST,SWARM_TRANSMISSION_PORT,SWARM_TRANSMISSION_USER,SWARM_TRANSMISSION_PASS,SWARM_STAGING_DIR.Seeding existing files: When a file area has
default_distribution_methodoftic_torrentor when an individual file is flagged accordingly,SwarmManagercalls Transmission'storrent-addwith"paused": falseand the local file path set as the download directory — Transmission recognises the file as complete and immediately begins seeding without re-downloading.Phase 4 — User Interface
swarm_transfer_progress,swarm_transfer_complete,swarm_transfer_error,swarm_session_completeviawindow.BinkStream.on(...). Automatic HTTPS download trigger onswarm_transfer_complete.Phase 5 — Network Tracker
scripts/swarm_tracker.phpor a bundled lightweight tracker).swarm_nodespopulation from tracker announce events.B-Modem: The Original Concept (Direct-to-User Swarming)
The core idea is straightforward: a lite BitTorrent client is built into or bundled with BBS terminal client software. The BBS feeds the client a queue of torrent references. The client joins the swarm, downloads pieces from every available seed in parallel, and notifies the BBS when each file is complete. The BBS ANSI/terminal display shows a traditional-looking file transfer progress screen — peer count, speed, progress bar — but behind it BitTorrent is doing what BitTorrent already does. No special swarm coordination code is needed beyond what BitTorrent provides natively.
The BBS does not need to re-invent swarming. Its role in this model is:
.torrentfiles (or magnet links) to clients via the existing TIC/filebone pipeline.Transport: WebSocket as the client connection layer
B-Modem as described assumes the BBS client connects over WebSocket rather than raw telnet. WebSocket wraps the connection in TLS and allows the BBS software and client to exchange structured data alongside the traditional terminal byte stream — something a classical telnet connection cannot do cleanly.
There is currently no universal BBS-scene standard for how WebSocket is used across different software — port assignments, framing, and API conventions vary by implementation. B-Modem would benefit from becoming one such standard: when a client connects and advertises B-Modem capability, the BBS knows it can send torrent metadata over the WebSocket channel and expect the client to handle it natively.
WebSocket in this context behaves somewhat like a lightweight VPN or SSH tunnel — it wraps whatever data needs to pass through it and applies encryption and authentication — but it is more natural for BBS use because it can run as a sidecar alongside the existing terminal stream on the same connection, rather than requiring a completely separate session.
B-Modem delivery paths
Path 1: Native B-Modem client (preferred)
The BBS terminal client has a built-in lite BitTorrent engine. The BBS sends torrent metadata (magnet link or
.torrentcontent) over the WebSocket channel. The client:The user can optionally configure their client to keep or delete the
.torrentfile after the download completes.The ANSI terminal display during transfer shows only what is going on — speed, peers, progress — exactly as an old-school ZModem screen would. The complexity lives in the client's BitTorrent engine, not in what is rendered on screen.
Path 2: ZModem
.torrenthand-off (compatibility path for existing clients)For clients that do not yet have a native B-Modem engine but do have an external BitTorrent client installed on the same machine:
.torrentfile to the client via ZModem — a standard file transfer the existing terminal software already understands..torrentfile (either automatically via a watch folder, or manually by the user) and begins downloading..torrentin the queue via ZModem, and the process repeats.This path requires no changes to existing terminal software and no new client development. It is less seamless — the BitTorrent download happens outside the terminal session — but it is functional today with any client that supports ZModem.
Path 3: Server-side assembly fallback
For clients with no BitTorrent capability at all (web browsers via HTTP, or telnet-only clients without a companion BitTorrent application), the server-side Transmission integration described earlier in this document applies. The BBS node assembles the file from the swarm and delivers it via HTTP download link (web) or ZModem (telnet). From the user's perspective it is a standard download; they are not a swarm peer.
WebSocket as a mandatory telnet security layer
A broader proposal connected to B-Modem: BBS software should be able to require that telnet connections arrive encapsulated in WebSocket, and reject raw telnet connections with an informational message listing supported clients, followed by NO CARRIER. This would make WebSocket the standard secure transport for BBS terminal access, analogous to what SSH provides for shell access — but better suited to BBS software because it can carry structured side-channel data (like B-Modem torrent queues) alongside the terminal stream.
For users on hardware that cannot speak WebSocket natively — vintage machines, hardware running old terminal software — a local proxy handles the translation. The proxy listens on a local telnet port and forwards connections to the BBS over WebSocket. It can run as a Docker container, a VM, a Raspberry Pi, or any small always-on device on the user's local network. It can be configured much like a telnet BBS door entry, with options to manually specify a BBS address. Dialup access via an attached modem could also be supported, letting the proxy be dialled into directly if desired.
This proposal is not specific to B-Modem — it is a general BBS connectivity and security position. No universal standard for WebSocket usage across BBS software currently exists; different implementations handle it differently. This would be a step toward establishing one.
Tracker architecture for B-Modem
Two viable arrangements:
.torrentfile. Resilient to individual tracker outages.In both cases,
.torrentfiles are distributed via TIC processing like any other filebone content. A local script on each BBS watches for incoming torrent files, registers them with the local client or seeding daemon, and handles cases where a new.torrentsupersedes an older one for the same file. This can be a simple Python or PHP script — the heavy lifting (peer discovery, piece exchange) is handled by the BitTorrent client itself.Summary: which path applies where
.torrentvia ZModem, downloads externallyOpen Questions
Original intent: server-side assembly or direct-to-user swarming? Resolved: The original concept intends direct-to-user swarming — a lite BitTorrent client built into the terminal client software, with the BBS acting as one seed among many rather than an assembler. Server-side assembly (Transmission integration) is the compatibility fallback for clients without native B-Modem support, not the primary design. See B-Modem: The Original Concept for the full design including WebSocket transport, ZModem
.torrenthand-off for existing clients, and the WebSocket-as-mandatory-telnet-security-layer proposal.Torrent client choice. Resolved (leaning Transmission): Rather than building or embedding a BitTorrent daemon, the system integrates with an existing torrent client over its management API. Transmission is recommended as the starting point: its JSON-RPC API is simple, its PHP integration requires no additional library (plain
curl/file_get_contents), and it is available in every major Linux package repository. Alternative clients and their tradeoffs:The sysop installs and configures Transmission independently of the BBS. The BBS integration layer (
src/Swarm/TransmissionClient.php) communicates with it over the configured local RPC endpoint.IPC between swarm daemon and realtime server. Progress events must not go through PostgreSQL — the write volume is too high and the data is entirely transient. The swarm daemon should communicate directly with the realtime server (
scripts/realtime_server.php) via a dedicated Unix socket, bypassing the admin daemon entirely for the progress path. The realtime server already owns the WebSocket connection map and can push to a specific user's connection without any database involvement. This requires adding an inbound IPC listener tosrc/Realtime/WebSocketServer.phpalongside the existing browser-facing WebSocket listener.Seeding responsibility. Resolved: Seeding follows the distribution method flags. If a file area's
default_distribution_methodistic_torrent, all files in that area are seeded automatically — the area-level flag is an implicit commitment to participate in the swarm for all content it carries. For areas with any other default, seeding is determined per-file: files withdistribution_methodoftic_torrentortic_bothare seeded; files withtic_originalorlocalare not.TIC metadata area governance. This is as much a network policy question as a software one. LovlyNet can host and administer the metadata area, making it the natural starting point for rollout. For broader reach across established networks (FidoNet, fsxNet, etc.), either an existing network would need to allow a new metadata area, or a dedicated lightweight FTN network solely for swarm metadata distribution could be established that any BBS could join regardless of other network memberships. The software itself is indifferent to which network carries the metadata area — the sysop configures the area tag and the system uses whatever TIC pipeline delivers files to it.
Torrent format: v1 vs. v2. BitTorrent v2 uses SHA-256 per-file hashes and supports hybrid torrents (v1+v2). New implementations should target v2 with v1 compatibility for broader client support.
Magnet link scope.
Should magnet links generated by this system include the BBS tracker announce URLs, making them specific to the private network? Or should they be tracker-less (relying on DHT) for compatibility with standard clients?Resolved: Magnet links always include the private tracker announce URL(s). DHT is off by default, making tracker-less magnet links non-functional in the standard configuration. Public access is not a goal of this system. The trust boundary is sysops, the same as the existing filebone model — a sysop who wants to leak a file has always been able to do so regardless of the torrent system.FREQ integration. A node receiving a FREQ for a file it does not host could respond with a magnet link instead of a binary transfer. This would be a significant improvement to FREQ for large files and is worth a follow-on proposal once the foundation is in place.