The SysOp Experience: Building Admin Tools for a Modern BBS

June 19, 2026

Every BBS needs a sysop. The system operator — the person who manages users, moderates boards, configures channels, and keeps the system running. In the 90s, the sysop sat at the physical machine, running tools from a dedicated admin menu. Today, the sysop connects remotely, just like any other user — but with more power.

Phosphor BBS gives the sysop a full terminal-based admin panel. You connect over Telnet or SSH, log in as a sysop-level user, and get access to tools that regular users never see. Everything runs in the terminal — no web dashboard required.

The SysOp Menu

When a sysop logs in, the main menu shows extra options. Pressing S opens the SysOp menu — a hub screen that branches into sub-screens for each admin function:

  • Who’s Online — see every active session, with IP, idle time, and current screen
  • User Admin — create, edit, ban, and manage users and their group memberships
  • Board Manager — create and configure message boards with group-based read/write access
  • Chat Channel Manager — create channels and set minimum group requirements
  • File Area Manager — create file areas with folder hierarchies and read group permissions
  • System Stats — dashboard with user counts, message counts, and health indicators
  • Database Viewer — run SQL queries directly from the terminal (sysop only)
  • Screen Spy — watch another user’s screen in real time

Each sub-screen is a full jterm screen with borders, tables, input dialogs, and keyboard navigation. No different from the user-facing screens — just with different data and permissions.

Who’s Online

The Who’s Online screen shows a live table of every active session:

┌─ Who's Online ─────────────────────────────────────────┐
│ Username    IP Address     Protocol  Screen      Idle  │
│ alice       192.168.1.10   ssh       MainMenu    2s    │
│ bob         10.0.0.5       telnet    Chat:lobby  15s   │
│ charlie     172.16.0.3     web       Boards      1m    │
└────────────────────────────────────────────────────────┘

It updates in real time — when a new user connects, they appear in the list. When a user disconnects, they disappear. The sysop can press Enter on any session to see more details or initiate a screen spy.

User Administration

The User Admin screen is a CRUD interface for user accounts. The sysop can:

  • Create new users with a username, password (BCrypt-hashed), and display name
  • Edit user profiles and status (active, suspended, banned)
  • Assign users to groups (vip, sysop, banned, etc.)
  • Reset passwords
  • View user statistics (post count, login count, last seen)

Group management is integrated directly. The sysop selects a user, presses G for groups, and gets a checklist of all available groups. Toggling a group updates the database and immediately refreshes the user’s session — no relogin needed:

// When groups change, refresh the user's live session
registry.refreshGroupsForUser(username, authService);

This is the BBS design rule: live updates via listeners, never require logout/login. When a sysop adds a user to the vip group, that user’s next screen render shows the new menu items available to vip members. The SessionRegistry.refreshGroupsForUser method iterates all active sessions for that user, reloads their groups from the database, and fires a groupsChanged event to all session listeners.

Board and Channel Management

The Board Manager lets the sysop create message boards and assign read/write group permissions. A board visible to the vip group is invisible to regular users — they don’t even see it in the board list.

public class BoardManagerScreen extends CrudScreen<Board> {
    @Override
    protected void createNew() {
        var dialog = new FormDialog(session, "New Board",
                List.of("Name", "Description"));
        // ... collect input, then:
        boardService.createBoard(name, description, readGroups, writeGroups);
    }
}

The Chat Channel Manager works the same way — create channels, set a minimum group requirement, and only users in that group can join. The file area manager handles folder hierarchies and read permissions.

All three managers extend CrudScreen<T> — a base class that provides the standard CRUD lifecycle (list, create, edit, delete) with consistent keyboard shortcuts and error handling. This eliminates hundreds of lines of duplicated code:

public abstract class CrudScreen<T> extends ContentScreen {
    protected abstract void createNew();
    protected abstract void editItem(T item);
    protected abstract void deleteItem(T item);
    protected abstract void refresh();
    // Framework handles: [N] new, [E] edit, [D] delete, [Q] quit
}

Screen Spy: Watching Users in Real Time

The most unique sysop tool is Screen Spy. It lets the sysop watch another user’s terminal screen in real time — seeing exactly what they see, as they navigate menus, type messages, and play door games.

The implementation uses jterm’s double-buffered screen architecture. Every DefaultScreen has a front buffer (what the user sees) and a back buffer (what’s being drawn). The spy polls the target session’s front buffer at 10fps, copies it into the spy’s back buffer, and refreshes:

public class ScreenSpy {
    public enum PrivacyMode { SILENT, NOTIFY, CONSENT }

    public void start(BbsSession target, DefaultScreen spyScreen) {
        this.target = target;
        this.spyScreen = spyScreen;
        target.setSpied(true);
        poller = Thread.ofVirtual()
                .name("screen-spy-" + target.sessionId())
                .start(this::poll);
    }

    private void poll() {
        while (!stopped.get() && active.get()) {
            var frontBuffer = frontBufferOf(target.screen());
            if (frontBuffer != null) {
                // Copy target's front buffer into spy's back buffer
                spyScreen.getBackBuffer().copyFrom(frontBuffer);
                spyScreen.refresh();
            }
            Thread.sleep(100); // 10fps
        }
    }
}

Three privacy modes control how the target user is notified:

  • SILENT — the user is not notified at all. The sysop watches silently.
  • NOTIFY — a brief message appears on the user’s screen: “A sysop is viewing your screen.”
  • CONSENT — the user must approve the spy request before it begins.

The spy runs on a virtual thread (Thread.ofVirtual()), so it doesn’t consume a platform thread while idle. Multiple sysops can spy on multiple users simultaneously without thread exhaustion.

Database Viewer

For deep debugging, the sysop can press V to open the Database Viewer — a screen that lets you run arbitrary SQL queries and see results in a DataGrid:

┌─ Database Query ───────────────────────────────────────┐
│ SELECT username, status, last_login FROM bbs.users     │
│                                                        │
│ username    status    last_login                       │
│ alice       active    2026-06-19 14:23:01              │
│ bob         active    2026-06-19 14:15:30              │
│ charlie     suspended 2026-06-18 09:01:22             │
└────────────────────────────────────────────────────────┘

Tab toggles between the query input and the results table. Enter on an empty query exits. It’s a full SQL client in the terminal — no phpMyAdmin required.

The Philosophy

Every sysop tool runs in the same terminal interface the users see. No separate admin dashboard, no web panel, no second application. The sysop connects to the same BBS, on the same port, with the same client. The only difference is what they see after login.

This is deliberate. A BBS is a self-contained world. The admin tools should feel like part of that world — same keyboard shortcuts, same visual style, same response speed. When a sysop manages users, they’re using the same jterm widgets, the same screen navigation, the same everything. It’s one system, not two.

The sysop experience is also where the BBS design philosophy shines brightest. Live updates — no relogin. Group changes propagate instantly. Screen spy runs on virtual threads. CRUD screens share a base class. Everything is consistent, everything is terminal-native, and everything works the way a BBS should.