Excalibur's Sheath

Cyber Sword BBS — Building a Modern Door System on Linux

Jul 19, 2026 By: Jordan McGilvraybbs,synchronet,linux,dosemu2,doors,doorparty,interactive-fiction,zcode,tradewars-2002,lord,retrocomputing

In my last project update, Cyber Sword BBS — Building a Modern Linux BBS Inspired by the Dial-Up Era, I stepped back from the technical details to reflect on what I had learned while rebuilding a modern Linux bulletin board system inspired by the dial-up era. Looking at the project as a whole helped me appreciate how much it had grown beyond the original idea and highlighted several lessons that only became apparent after months of development. That article provides the broader context for the work I’ll be discussing here, as this project update builds directly on those experiences.

This time, I’d like to look beneath the surface at one of the most interesting parts of the project: the door system. While callers simply choose a game or application from a menu, there’s a surprising amount of infrastructure working behind the scenes to make that experience feel seamless. Supporting classic DOS doors, modern Linux console games, Interactive Fiction, remote DoorParty games, and native Synchronet JavaScript doors required solving a series of different problems, each of which influenced the design of the next solution.

Like much of Cyber Sword BBS, this architecture didn’t appear fully formed. It evolved one challenge at a time. I started with Interactive Fiction because I wanted Infocom and modern Z-Code games to behave like traditional BBS doors. That led to BBSZCodeRunner. Later, I realized many Linux console applications had almost identical requirements, resulting in BBConsoleRunner. When it came time to bring classic DOS doors back to life under Linux, I encountered an entirely new set of challenges involving DOSEMU2, dropfiles, COM ports, FOSSIL support, and decades-old software that still expected to be running on a real DOS machine.

In this article, I’ll walk through that evolution, explaining not only the final architecture but also the ideas, experiments, dead ends, and small discoveries that shaped it. Along the way, we’ll explore how doors communicate with a BBS through dropfiles, why I chose specific technologies, how the wrapper scripts evolved from one-off utilities into reusable infrastructure, and why the classic BBS door model remains flexible enough to support software spanning more than forty years of computing history.

Understanding Door Systems

Before diving into the architecture behind Cyber Sword BBS, it’s worth taking a step back to understand what a door actually is. The term is frequently associated with classic DOS games like Legend of the Red Dragon and TradeWars 2002, but those games represent only one chapter in the history of doors. To understand why the architecture in this project works, we first need to understand the concept itself.

What Is a Door?

A door is an external program that a bulletin board system temporarily launches on behalf of a caller. While the door is running, the user interacts directly with that program instead of the BBS. When the user exits the door, control returns to the BBS exactly where they left off, allowing them to continue browsing message boards, downloading files, or launching another application.

The important point is that a door is defined by its relationship with the BBS, not by the operating system it runs on. Throughout the history of bulletin board systems, doors have existed on DOS, Commodore 64, Apple II, Amiga, OS/2, Unix, Linux, Windows, and other platforms. The BBS simply needed a standardized way to launch an external program, provide it with information about the current caller, and regain control when the program exited.

A door is defined by its relationship with the BBS, not by the operating system it runs on.”

During the 1990s, DOS-based doors became the dominant implementation. The widespread popularity of MS-DOS, combined with an explosion of creative door developers, produced classics such as Legend of the Red Dragon, TradeWars 2002, Barren Realms Elite, Usurper, and hundreds of other games and utilities. Because these titles became synonymous with the golden age of dial-up BBSing, many people today equate the term door with DOS door. Historically, that’s understandable—but technically, DOS was simply the most successful platform for a much broader concept.

Why Doors Were So Important

Doors transformed bulletin board systems from collections of message boards and file libraries into interactive communities. Multiplayer games encouraged users to log in every day, role-playing games fostered long-running rivalries and alliances, and utilities expanded the capabilities of the BBS far beyond what the base software provided. Many sysops became known as much for their collection of doors as for the BBS software they were running.

This flexibility also encouraged experimentation. Sysops weren’t limited to software written by the authors of their BBS package. If someone could write a program that followed the conventions expected by the BBS, it could become another feature of the system. That openness helped create a rich ecosystem of games, educational software, productivity tools, and online experiences that continued to evolve for decades.

The more I worked on Cyber Sword BBS, the more I realized that this flexibility—not DOS itself—was the real legacy of the door model. My goal wasn’t simply to run classic DOS doors under Linux. It was to preserve the idea that a BBS can temporarily hand control to another application and then seamlessly resume the user’s session when that application exits. In many ways, that philosophy mirrors ideas I’ve written about before in articles such as Ports for Everyone, where understanding the interface between systems is often more important than the systems themselves.

How Doors Communicate with a BBS

If the BBS temporarily hands control to another program, the obvious question becomes: How does that program know who the caller is?

Long before modern APIs or inter-process communication became commonplace, BBS developers adopted a remarkably simple solution. Before launching the door, the BBS writes a small text file containing information about the current session. The door reads that file, learns who the user is, how much time remains, what node they’re connected through, and other details needed to operate correctly.

These files became known as dropfiles, because the BBS “dropped” them into a location where the door could find them before execution.

Typical information found in a dropfile includes:

  • User name or alias
  • Node number
  • Remaining session time
  • Security level
  • ANSI/terminal capabilities
  • Communication port information
  • Connection information

Once the door has finished reading the dropfile, it has everything it needs to behave as though it were part of the BBS itself.

DOOR.SYS and XTRN.DAT

Over the years, several dropfile formats emerged, but two are particularly relevant to Cyber Sword BBS.

DOOR.SYS became the de facto industry standard and is still recognized by many classic DOS doors. Its widespread adoption means that software written decades ago can often still run today without modification, provided the environment around it is recreated accurately.

Synchronet also provides its own native format, XTRN.DAT. Compared to DOOR.SYS, it is smaller, simpler, and easier to parse. Whenever I write new wrappers or native applications, I generally prefer XTRN.DAT. However, many classic DOS doors expect DOOR.SYS, so supporting both formats allows modern software and legacy applications to coexist without compromise.

Understanding dropfiles turned out to be one of the key insights behind the entire architecture described in this article. Whether the external program is:

  • a DOS game running under DOSEMU2,
  • a Linux console application,
  • an Interactive Fiction interpreter,
  • a DoorParty remote game, or
  • a native Synchronet JavaScript application,

the launch process always begins in essentially the same way. The BBS prepares the session information, launches an external program, and waits for control to return. Everything that follows in this article is simply a different way of adapting that remarkably durable interface.

Interactive Fiction as Doors

Although classic multiplayer door games were always part of my long-term plans for Cyber Sword BBS, the first type of door I actually implemented wasn’t a DOS game at all. It was Interactive Fiction. Looking back, that turned out to be a fortunate decision because it allowed me to solve many of the architectural problems of running external applications before adding the additional complexity of DOS emulation.

Why Start with Z-Code?

Before choosing specific games, I decided to start with the Z-Code format itself. Originally developed by Infocom, the Z-machine has matured into one of the longest-lived virtual machines in computing. Modern interpreters such as Frotz are well-tested, stable, and capable of running thousands of story files spanning more than forty years of Interactive Fiction.

From an engineering perspective, that made Z-Code an ideal place to begin. The interpreter was already mature, the file format was well documented, and I could concentrate on solving the problem I was actually interested in: making a modern Linux application behave like a traditional BBS door. Rather than debugging an immature runtime, I could focus on integrating it cleanly with Synchronet.

Why Interactive Fiction?

I’ve enjoyed Infocom’s games for many years. Titles such as Zork, Planetfall, Trinity, Moonmist, and The Hitchhiker’s Guide to the Galaxy represent an important part of computer gaming history. While these games were traditionally played locally, I wanted callers to experience them as first-class citizens of the BBS alongside traditional door games.

Interactive Fiction also presented an interesting technical challenge. A BBS door needs to know who the current user is, where to store save files, how to restore previous progress, and how to return cleanly to the BBS when the game exits. Solving those problems would establish patterns that could later be reused by other types of doors.

In many ways, Interactive Fiction became my proof of concept. If I could successfully make a modern Linux application behave like a traditional BBS door, then the same architecture could likely be adapted to other applications as well. Like several of the scripting projects I’ve written about before, including Why Update Scripts, the real lesson wasn’t solving one specific problem—it was building something reusable enough to solve the next one as well.

Building BBSZCodeRunner

The result was BBSZCodeRunner, a wrapper designed specifically for Synchronet. Rather than simply launching Frotz, the wrapper reads Synchronet’s XTRN.DAT dropfile, determines which user is currently logged in, creates or locates that caller’s save directory, maps the BBS user to a consistent filename, and then launches the requested Z-Code story.

Each caller receives their own persistent save directory, allowing them to continue games across multiple sessions without interfering with other users. From the caller’s perspective, the experience feels much like a traditional door game: choose a title from the menu, play, save when finished, and continue where you left off the next time you log in.

One design decision that proved particularly useful was separating game files, save files, and user mappings into their own directories. That organization made the project easier to maintain as additional games were added and also established a directory structure that I would later reuse in other wrappers.

Originally, BBSZCodeRunner maintained its own log directory alongside the application. As the project expanded, however, it made more sense for every wrapper to write into Synchronet’s central log directory. Today, all wrapper logs are written to /opt/sbbs/sbbs/logs/, making troubleshooting much simpler because everything related to the BBS is collected in one place.

Current directory layout:

/opt/sbbs/doors/zcode/
├── bbszcoderunner.sh
├── games/
├── saves/
└── usernames.map

The wrapper keeps detailed log files, maintains a simple username mapping so save files remain consistent even if a user’s alias changes, and automatically creates the directory structure needed for each caller. These are relatively small implementation details, but together they make the system considerably more reliable for long-term use.

If you’d like to use BBSZCodeRunner on your own Synchronet system, the complete source code, installation instructions, and documentation are available in the BBSZCodeRunner GitHub repository.

Building a Reusable Architecture

One of the biggest surprises was realizing that very little of BBSZCodeRunner was actually specific to Interactive Fiction. Reading a dropfile, identifying the caller, creating user-specific directories, maintaining save files, logging activity, and launching an external Linux application are tasks that many different applications need.

Instead of viewing BBSZCodeRunner as simply an Interactive Fiction launcher, I began seeing it as the first piece of a much larger architecture.

The wrapper had solved the general problem of integrating Linux applications into Synchronet, and only a relatively small amount of code was truly specific to the Z-machine itself. Once that realization clicked, I stopped thinking in terms of games and started thinking in terms of reusable infrastructure.

I stopped thinking in terms of games and started thinking in terms of reusable infrastructure.

Linux Console Games as Doors

By the time BBSZCodeRunner was working reliably, I realized I hadn’t really solved an Interactive Fiction problem—I had solved a Linux application integration problem. Reading a dropfile, identifying the caller, preparing a user-specific environment, launching an application, and returning cleanly to Synchronet weren’t unique to Z-Code. Those same requirements applied to many Linux console applications as well.

That realization fundamentally changed the direction of the project. Instead of writing another wrapper specifically for one game, I began thinking about how to integrate an entire category of software into the BBS.

Why Linux Console Games?

One advantage of running Synchronet on Linux is that an enormous ecosystem of software is immediately available. Thousands of applications already run inside a terminal, and many of them feel perfectly at home on a text-mode bulletin board system.

Classic roguelikes such as Rogue, NetHack, Angband, and Moria naturally fit the BBS environment. So do lighter games like Moon Buggy, Vitetris, Nudoku, Pacman4Console, nInvaders, and many others. Unlike DOS doors, these applications are actively maintained, portable, and already run natively under Linux.

The challenge wasn’t getting the games to run—it was making them feel like doors instead of shell commands.

It’s also worth noting that not every Linux console application needs a wrapper. Many programs can simply be launched directly by Synchronet and work perfectly well without any additional infrastructure. BBConsoleRunner exists for applications that maintain per-user state, such as save files, configuration files, or other persistent data. Those applications need to know who the current caller is and where their personal files belong, making a wrapper the simplest way to bridge that gap.

Building BBConsoleRunner

Rather than starting from scratch, I used BBSZCodeRunner as the template for the new wrapper. Much of the underlying infrastructure was already there: reading Synchronet’s XTRN.DAT dropfile, identifying the current caller, preparing user-specific resources, logging activity, and returning cleanly to the BBS when the application exited.

As I adapted the script, I removed everything that was specific to the Z-machine and replaced it with code capable of launching arbitrary Linux console applications. That naturally led to a more configuration-driven design. Instead of embedding game-specific behavior directly into the wrapper, individual applications define their executable, command-line arguments, save locations, and other options through a shared configuration file.

That decision dramatically reduced the amount of code required to add new games. As the infrastructure improved, supporting another application often became little more than adding another configuration entry rather than writing another wrapper.

Current directory layout:

/opt/sbbs/doors/consolegames/
├── bbsconsolerunner.sh
├── bbsconsolegames.conf
├── games/
│   └── dwarf-fortress-ascii (and other locally installed games)
├── saves/
└── usernames.map

Like BBSZCodeRunner, game files remain separate from user data, while logging has been centralized under /opt/sbbs/sbbs/logs/ alongside the rest of the Synchronet infrastructure. Keeping every wrapper’s logs in one location has made troubleshooting considerably easier as the project has grown.

If you’d like to use BBConsoleRunner on your own Synchronet system, the complete source code, installation instructions, and documentation are available in the BBConsoleRunner GitHub repository.

Seeing the Pattern

Looking back, BBConsoleRunner confirmed something I had only suspected while building BBSZCodeRunner. The wrapper itself contains very little game-specific logic. Most of its responsibility is simply acting as an adapter between Synchronet and another Linux application.

By this point, I had stopped thinking about adding games individually. Instead, I was building reusable infrastructure capable of integrating entire categories of software into the BBS. Every successful wrapper reinforced the same architectural pattern, making the next integration easier than the last.

Perhaps more importantly, each wrapper required less code than the one before it. Once the underlying architecture existed, I was no longer solving the same problems repeatedly. I was simply extending an infrastructure that had already proven itself, which is exactly what I had hoped to achieve when I first began experimenting with Interactive Fiction as BBS doors.

Remote Doors with DoorParty

By this point, Cyber Sword BBS could launch native Linux applications, but there was still one major category of software I wanted to support: remote multiplayer door games. While many classic doors can still be run locally, some are actively maintained on dedicated DoorParty servers, allowing multiple bulletin board systems to share the same persistent game worlds. That means callers from different BBSes can interact with one another while each sysop avoids the work of maintaining a separate installation.

Rather than replacing local doors, DoorParty complements them. It expands the available library of games while allowing Cyber Sword BBS to focus on the titles I wanted to host locally.

Why DoorParty?

One of the strengths of the classic BBS community has always been sharing resources. Not every sysop wants to install, configure, patch, and maintain dozens of multiplayer games. DoorParty solves that problem by hosting the games centrally while allowing individual BBSes to present them as though they were local doors.

From the caller’s perspective, the experience is almost seamless. They select a game from the Synchronet menu, play as they normally would, and when they exit they are returned directly to the BBS. The fact that the game is actually running on another server is almost completely transparent.

How the Architecture Works

One detail I particularly like about DoorParty is how it separates responsibilities. Synchronet never connects directly to the remote DoorParty servers. Instead, Synchronet establishes a local RLogin connection to the DoorParty Connector service running on the same machine. The connector then authenticates with DoorParty and establishes the secure connection to the remote game server.

The overall architecture is surprisingly straightforward:

Caller
   │
Synchronet
   │
Local RLogin
   │
DoorParty Connector
   │
Secure Connection
   │
DoorParty Game Server

This separation keeps the responsibilities of each component well defined. Synchronet manages the caller’s session, while the DoorParty Connector handles authentication and communication with the remote infrastructure.

The JavaScript module inside Synchronet is intentionally small because most of the work has already been delegated to the connector service.

// Sensitive values replaced with placeholders.
load("sbbsdefs.js");
var HOST = "localhost";
var PORT = 9999;
var USER_PREFIX = "BBS";
var PASSWORD = "<password>";
var username = USER_PREFIX + user.alias;
var tag = argv.length ? argv[0] : "lord";
bbs.rlogin_gate(
    HOST + ":" + PORT,
    PASSWORD,
    username,
    tag
);

Reusing an Existing Service

One part of this project that reflects its organic development is that I didn’t install a new DoorParty Connector specifically for Synchronet. Earlier in the project, while experimenting with Mystic BBS, I had already configured the connector service. It was reliable, well understood, and already did exactly what I needed.

Rather than replacing a perfectly good component simply because I had changed BBS software, I kept using the existing installation. Synchronet simply connects to the local connector, and the connector continues handling communication with DoorParty exactly as it always had.

The service itself remains remarkably simple:

# Sensitive values replaced with placeholders.

[Unit]
Description=DoorParty Connector Service
After=network.target
[Service]
ExecStart=/path/to/doorparty-connector
WorkingDirectory=/path/to/doorparty
Restart=always
User=<user>
Group=<group>
Environment=PATH=/usr/bin:/bin:/usr/sbin:/sbin
Environment=HOME=/path/to/doorparty
[Install]
WantedBy=multi-user.target

Looking back, this turned out to be one of the better engineering decisions in the project. The connector already worked well, so there was no value in replacing it simply because the surrounding infrastructure had changed.

Building Systems from Proven Components

Working with DoorParty reinforced another lesson that had been emerging throughout this project. Good systems don’t require every component to be written from scratch. Sometimes the best solution is to understand how existing software works, connect reliable components together, and let each one do the job it was designed to do.

That philosophy has quietly shaped much of Cyber Sword BBS. Frotz interprets Z-Code stories. BBConsoleRunner adapts Linux applications into doors. DoorParty hosts multiplayer games. My contribution wasn’t replacing those tools—it was designing an architecture that allows them to work together as though they were all part of a single, cohesive bulletin board system.

Native Synchronet JavaScript Doors

After building wrappers for Linux applications and integrating remote DoorParty games, it would have been easy to assume that every door required some kind of adapter or supporting infrastructure. One pleasant surprise was discovering that Synchronet’s native JavaScript doors require neither. They already live inside the BBS itself, giving them direct access to Synchronet’s APIs without the need for dropfiles, wrapper scripts, connector services, or emulators.

That made them a natural addition to Cyber Sword BBS. While much of this project focused on adapting external software to behave like traditional BBS doors, Synchronet’s JavaScript doors reminded me that not every problem needs another layer of infrastructure. Sometimes the simplest solution is already built into the platform.

Native Doors Instead of Adapted Doors

Unlike every other door system discussed so far, Synchronet JavaScript doors don’t require a wrapper, connector, or emulator. Synchronet simply loads the JavaScript application directly, allowing it to interact with the current caller through the BBS’s own APIs.

That means a JavaScript door already has access to information about the current user, the terminal, messaging, file areas, and other Synchronet services. Instead of communicating through a dropfile like DOOR.SYS or XTRN.DAT, it communicates directly with the BBS itself.

From an architectural perspective, these are the simplest doors on the system. The application starts, interacts with the caller, exits, and returns control directly to Synchronet without any additional layers between the two.

Taking Advantage of Synchronet’s Ecosystem

Synchronet ships with a surprisingly large collection of native JavaScript doors and utilities. Rather than recreating functionality that already existed, I simply enabled many of the applications that best fit the style and goals of Cyber Sword BBS.

Today the BBS offers a mixture of classic and modern experiences. Alongside DOS doors, Linux console games, Interactive Fiction, and DoorParty titles are Synchronet’s own JavaScript games and utilities, including Wordle, 2048, Sea Battle, Gooble Gooble, and many others.

Including these applications was an intentional decision. One goal of Cyber Sword BBS is preserving classic bulletin board software, but another is showcasing that Synchronet remains an actively developed platform. Its JavaScript ecosystem demonstrates that new BBS software is still being written and that modern development can coexist alongside software that is several decades old.

Choosing the Simplest Solution

As the project grew, one design philosophy kept resurfacing: choose the simplest integration that solves the problem.

If an application already runs natively inside Synchronet, there is no reason to wrap it. If it is a Linux application that needs per-user save data, BBConsoleRunner provides that infrastructure. If it is Interactive Fiction, BBSZCodeRunner fills that role. If it is hosted remotely, DoorParty bridges the connection. Each solution exists because it solves a different problem rather than forcing every application into the same architecture.

Looking back, that may be one of the biggest lessons of the project. Good architecture isn’t about making every component work the same way. It’s about understanding each technology well enough to integrate it using the simplest solution that accomplishes the goal.

After spending so much time building wrappers, adapters, and connector services, it was genuinely refreshing to simply enable software that was already designed to work within Synchronet. Sometimes the best engineering decision isn’t writing another script—it’s recognizing when the platform already provides exactly what you need.

Bringing Classic DOS Doors Back to Life

By the time I reached this stage of the project, Cyber Sword BBS could already launch Interactive Fiction, native Linux console games, DoorParty titles, and Synchronet JavaScript doors. Each of those technologies presented its own challenges, but they all shared one important advantage: they were designed for modern operating systems. DOS doors were different. They weren’t simply older software—they expected to run in an environment that had effectively disappeared decades ago.

My goal with DOS Doors is not to run every possible door. I am building a currated collection of the best remembered doors. I am relying on services like DoorParty to offer the full range of DOS Doors.

Bringing those applications back to life meant recreating enough of that environment that they still believed they were running on a multi-node DOS BBS. That required understanding not only the games themselves, but also dropfiles, virtual COM ports, FOSSIL communication, DOSEMU2, and the assumptions that door developers made in the early 1990s. Looking back, this was easily the most technically demanding part of the entire project.

Why DOS Was Different

One of the interesting discoveries during this project was realizing that a DOS door is much more than a DOS executable. Games such as Legend of the Red Dragon and TradeWars 2002 don’t simply start and run. They expect to be launched by a bulletin board system that has already prepared their environment.

That environment includes a dropfile, usually DOOR.SYS, containing information about the current caller, the communication port, remaining session time, ANSI capabilities, and many other details. The game also expects to communicate through a COM port, often using a FOSSIL driver, while assuming it has exclusive control of the terminal until it exits back to the BBS.

None of those assumptions are true on a modern Linux server.

Unlike the Linux applications discussed earlier in this article, DOS doors couldn’t simply be launched from a wrapper script. They needed an entire DOS environment capable of convincing software written more than thirty years ago that it was still running on the hardware it expected.

That challenge made DOS doors fundamentally different from every other type of door on Cyber Sword BBS.

Starting with LORD

Although my long-term goal was supporting both Legend of the Red Dragon and TradeWars 2002, I deliberately started with LORD.

There were two reasons for that decision. First, LORD has long been regarded as one of the classic BBS door games, making it an obvious title to include. More importantly, however, it presented a simpler technical target than TradeWars 2002. If I couldn’t reliably launch LORD under Linux, there was little reason to believe the more complex TradeWars installation would succeed.

That made LORD my proof of concept for DOS doors.

The goal wasn’t to build the perfect wrapper or support every feature immediately. The goal was simply to reach the point where a caller could launch LORD from Synchronet, play normally, save their game, and return cleanly to the BBS.

Like many engineering projects, that first success would provide the foundation for everything that followed.

Once I had decided to begin with LORD, the next question became how to run DOS software under modern Linux. Several DOS emulators are available today, but DOSEMU2 stood out because it was specifically designed for running DOS applications inside a Linux environment while maintaining good compatibility with classic software.

Like most of this project, I didn’t discover the solution entirely on my own. One of the most useful resources I found was BBSWordle’s guide to running DOS doors under DOSEMU2. It provided a straightforward explanation of how to install the current packages and pointed me toward the official DOSEMU2 package repository.

Getting LORD Running

With DOSEMU2 installed and the directory structure in place, I finally reached the point where I could begin launching an actual DOS door. As planned, Legend of the Red Dragon became my first target. My goal wasn’t to solve every problem at once—it was simply to get one classic door running reliably before attempting anything more complicated.

That decision proved to be the right one.

The first few attempts were exactly what I expected from software written more than thirty years ago. Some things worked immediately, others failed in unexpected ways, and many small problems only revealed themselves one at a time. Rather than trying to solve everything simultaneously, I treated each successful step as progress. If one change got me a little farther than the previous attempt, I kept it and moved on to the next problem.

Discovering What Really Mattered

One lesson became obvious fairly quickly: most of the work wasn’t running LORD itself. DOSEMU2 had no trouble executing the program. The difficult part was convincing LORD that it was running inside a real multi-node bulletin board system.

That meant making sure the dropfile was correct, the communication settings matched what the game expected, the DOS environment behaved normally, and Synchronet handed control to the game in exactly the right way. Every piece was relatively small by itself, but they all had to work together before the game became stable.

Some solutions came from experimentation. Others came from reading documentation or studying how other sysops had approached the same problems. More often than not, the project moved forward because of a series of small discoveries rather than one dramatic breakthrough.

Before changing emulator settings, verify that your dropfile is correct. Many classic DOS door problems originate in incorrect session information rather than DOSEMU2 itself.

Normalizing DOOR.SYS

One of the most useful discoveries came from tsali’s dosemu2-bbs-doors project. While comparing my own setup against that project, I noticed a discussion about normalizing lines 18 and 19 of DOOR.SYS before launching certain DOS doors.

That wasn’t something I would have expected to matter, but at this point in the project I was willing to try anything that had worked for someone else.

My wrapper now normalizes the dropfile before every launch, limiting the reported baud rate and remaining session time to values that older DOS software expects while also ensuring the file uses proper DOS-style line endings.

It was a surprisingly small change, but one that solved a compatibility problem I had spent considerably longer trying to understand.

COM Ports and FOSSIL

Even after normalizing DOOR.SYS, there were still problems to solve. DOS doors expect to communicate through a COM port, frequently using a FOSSIL driver, and getting those expectations to line up with a modern Linux environment required a considerable amount of experimentation.

This was probably the least scientific part of the project.

I adjusted configuration files, experimented with virtual COM ports, compared behavior between different launches, and gradually removed variables until the remaining configuration became increasingly stable. There wasn’t a single moment where everything suddenly made sense. Instead, the system slowly reached a point where each successful change built on the last.

Looking back, I don’t think I ever consciously decided that I had found the “correct” configuration. Eventually I reached a point where LORD simply worked reliably, and I stopped changing things. That may not be the most satisfying engineering answer, but it’s an honest description of how many real projects evolve. Sometimes stability is discovered incrementally rather than designed all at once.

One Working Door Changes Everything

Getting LORD running successfully was much more important than adding a single game to the BBS. It demonstrated that classic DOS doors could still run naturally under Linux without requiring users to think about emulation or compatibility layers.

Just as importantly, it established the foundation for everything that followed. Once one DOS door was working reliably, I no longer had to wonder whether the overall approach was possible. The remaining challenge became making that infrastructure reusable for additional doors instead of solving the same problems from the beginning each time.

From LORD to TradeWars 2002

With LORD running reliably, it was finally time to tackle the game that had inspired much of this project from the beginning: TradeWars 2002.

At first, I expected the process to be largely the same. After all, both games were classic DOS doors that relied on DOOR.SYS, virtual COM ports, and the same DOSEMU2 environment. While that assumption proved mostly correct, TradeWars introduced one important difference that reflected its own history.

Shareware Assumptions

Like many sysops in the 1990s, I originally began experimenting with the shareware version of TradeWars 2002. That version supports only a single node, so its startup script naturally assumed every caller would always be running on Node 1.

The original START.BAT looked something like this:

SET TWNODE=1
TW2002.EXE

That worked perfectly well while I was experimenting with a single node, but it obviously wasn’t suitable for the registered ten-node version I eventually licensed for Cyber Sword BBS.

Once I upgraded, the startup script needed to become dynamic:

SET TWNODE=%1
TW2002.EXE

Instead of assuming a fixed node number, the wrapper now passes the appropriate Synchronet node as a command-line argument, allowing every caller to launch the correct TradeWars node automatically.

It was a small modification, but it transformed a single-node DOS game into one that fit naturally within a modern multi-node BBS.

Preparing Each Node

TradeWars also expects each node directory to contain the appropriate DOOR.SYS file before the game starts.

Fortunately, by this point the wrapper infrastructure already understood which Synchronet node had launched the door. That made it straightforward to copy the freshly generated DOOR.SYS file into the correct TradeWars node before starting the game.

Once the game exited, Synchronet simply resumed control of the caller’s session exactly as it had with LORD.

From the user’s perspective, all of that preparation is invisible. They choose TradeWars 2002 from the menu, the game starts, and when they leave they return directly to the BBS. Behind the scenes, however, the wrapper has already prepared the DOS environment, selected the correct node, copied the appropriate dropfile, and launched the game inside DOSEMU2.

Two Wrappers Become One

Originally, LORD and TradeWars 2002 each had their own wrapper script.

That seemed perfectly reasonable at first. They were different games, installed in different directories, maintained different log files, and required slightly different startup commands. Writing two separate scripts felt like the simplest solution.

After both wrappers were working, however, I happened to compare them side by side.

The differences were surprisingly small.

Nearly all of the code was identical. Both wrappers validated the Synchronet node, located the correct DOOR.SYS file, normalized the dropfile, configured virtual COM ports, launched DOSEMU2, logged the session, and returned control to the BBS. The only meaningful differences were things like the game’s installation directory, startup batch file, and log filename.

That realization immediately suggested a better approach.

Instead of maintaining two nearly identical scripts, I merged them into a single wrapper that selects the appropriate configuration using a simple case statement.

If two wrappers differ only in configuration values, they’re probably one reusable wrapper waiting to be written.

case "$DOOR" in
    lord)
        ...
        ;;
    tw2002|tw)
        ...
        ;;
esac

The wrapper simply populates a handful of variables—such as the game directory, startup command, and log file—before executing the common launch sequence shared by every supported DOS door.

Looking back, this refactoring feels very similar to what happened earlier with BBSZCodeRunner and BBConsoleRunner. Once I had solved the general problem, there was very little value in maintaining multiple copies of nearly identical code. The better solution was to identify the small pieces that actually varied and make everything else reusable.

Building Reusable Infrastructure Again

That single decision simplified the rest of the project considerably.

Adding another DOS door no longer meant copying an entire wrapper and modifying it by hand. In many cases, it became little more than adding another branch to the case statement and providing a few door-specific values.

The result was dosdoor.sh, a single wrapper capable of launching multiple DOS doors while sharing one common implementation for logging, node handling, DOOR.SYS normalization, COM port configuration, and DOSEMU2 execution.

If you’d like to use dosdoor.sh on your own Synchronet system, the complete source code and documentation are available as part of this project’s supporting files on GitHub.

More importantly, the wrapper represented another step in the same architectural evolution that had been unfolding throughout the project. Rather than writing increasingly specialized scripts, I found myself writing increasingly general solutions that could support entire categories of software with only small amounts of configuration.

Inside dosdoor.sh

By the time I merged the LORD and TradeWars wrappers, the resulting script had grown beyond a simple launcher. dosdoor.sh became the piece that tied Synchronet, DOSEMU2, and the individual DOS doors together. Every caller, regardless of which DOS game they selected, passed through the same sequence of preparation before the game was launched.

Rather than trying to make the games aware of Linux, the wrapper adapts Linux to look enough like a traditional DOS BBS that the games simply continue behaving as they always have.

The launch process follows a consistent sequence:

  1. Validate the Synchronet node.
  2. Select the requested DOS door.
  3. Verify the required files and directories.
  4. Normalize DOOR.SYS.
  5. Configure any required virtual COM ports.
  6. Launch DOSEMU2.
  7. Return control to Synchronet when the door exits.

Each of those steps is relatively simple by itself, but together they recreate the environment expected by software originally written for bulletin board systems decades ago.

Selecting the Door

The first task performed by the wrapper is determining which door the caller requested.

Rather than maintaining separate launch scripts for each game, a simple case statement selects the appropriate configuration before the common launch process begins.

case "$DOOR" in
    lord)
        DOOR_NAME="LORD"
        DOOR_BASE="$DOSEMU_DRIVE_C/LORD"
        START_COMMAND="START.BAT"
        ;;
    tw2002|tw)
        DOOR_NAME="TradeWars 2002"
        DOOR_BASE="$DOSEMU_DRIVE_C/TW2002"
        START_COMMAND="START.BAT"
        ;;
esac

Everything that follows uses those variables. The remainder of the wrapper doesn’t really care whether it’s launching LORD, TradeWars 2002, or another DOS door added in the future.

Preparing the Environment

Before DOSEMU2 is ever started, the wrapper performs a considerable amount of validation.

It verifies that the requested Synchronet node exists, confirms the game directory is present, checks that DOOR.SYS has been generated, ensures the DOSEMU2 configuration file is available, validates the game’s startup batch file, and records the launch in Synchronet’s central log directory.

These checks don’t make the games run any faster, but they do make failures much easier to diagnose. Instead of wondering why a door silently failed to start, the logs generally point directly to the missing file or configuration problem.

Over time, those validation steps became just as valuable as the launch process itself.

Normalizing the Dropfile

One of the wrapper’s most important responsibilities is preparing DOOR.SYS before the game ever sees it.

Every launch normalizes the file by ensuring DOS-style line endings, replacing blank fields with placeholder values where appropriate, and limiting certain values to ranges expected by older DOS software.

By handling that work automatically, every supported DOS door receives a consistent environment without requiring individual games to implement their own compatibility fixes.

It’s a good example of the wrapper quietly solving a problem once instead of forcing every game to solve it independently.

Launching DOSEMU2

Once the environment has been prepared, the wrapper finally launches DOSEMU2.

If the selected door requires a virtual COM port, the wrapper adds the appropriate configuration automatically before executing the game’s startup batch file.

Conceptually, the launch process looks like this:

Synchronet
      │
      ▼
 dosdoor.sh
      │
      ▼
Normalize DOOR.SYS
      │
      ▼
Configure Virtual COM Port
      │
      ▼
    DOSEMU2
      │
      ▼
 START.BAT
      │
      ▼
 DOS Door

Looking at the project now, it’s remarkable how much work occurs before the game itself ever starts. From the caller’s perspective, selecting a DOS door appears almost instantaneous. Behind the scenes, however, the wrapper has already prepared the node, validated the environment, normalized the dropfile, configured communication, and launched an entire DOS environment.

One Wrapper, Many Doors

Perhaps my favorite part of dosdoor.sh is that it no longer feels like a LORD wrapper or a TradeWars wrapper.

Instead, it has become a DOS door wrapper.

Adding another supported game rarely requires changing the launch logic itself. Most of the infrastructure already exists. New doors typically need little more than another entry in the configuration section and whatever installation files are unique to that particular game.

That’s exactly where I hoped the project would eventually arrive. Rather than accumulating a collection of unrelated scripts, Cyber Sword BBS now has a reusable piece of infrastructure capable of launching an entire class of software.

The complete dosdoor.sh wrapper, along with installation instructions and supporting files, is available in the project’s GitHub repository for anyone interested in adapting it for their own Synchronet system.

Lessons Learned

Looking back, bringing classic DOS doors back to life turned out to be the most technically demanding part of the entire Cyber Sword BBS project. Not because DOS software is unusually complicated, but because it expects an environment that modern operating systems no longer provide. Every small compatibility issue represented another assumption that had been perfectly reasonable in the early 1990s but now had to be recreated under Linux.

One of the biggest lessons was that incremental progress matters. I never sat down and designed the final solution from the beginning. Instead, the project advanced through a long series of small improvements. One day it was getting DOSEMU2 installed. Another day it was understanding symbolic links. Later it was discovering how DOOR.SYS needed to be normalized. Eventually it became COM ports, FOSSIL communication, and finally consolidating multiple wrappers into a single reusable script.

Perhaps the most honest thing I can say is that I never reached a moment where everything suddenly made sense. There wasn’t a breakthrough where I knew I had solved DOS doors once and for all. Instead, the system gradually became more reliable as each experiment removed another obstacle. Eventually I reached a point where everything was working consistently—and I simply stopped changing things.

That experience also reminded me of the importance of learning from the work of others. The articles from BBSWordle and the tsali/dosemu2-bbs-doors project saved me countless hours of experimentation. Neither provided my final solution, but both contained pieces of the puzzle that helped me move the project forward. Open source software and technical communities have always worked this way: each person contributes another piece that makes someone else’s project possible.

At the same time, the project reinforced something that had already become a recurring theme throughout this article. Good architecture evolves. I didn’t begin with a reusable DOS wrapper. I began with a wrapper for LORD. Then I built another for TradeWars 2002. Only after both were working did it become obvious that they were solving almost exactly the same problem. The reusable design emerged naturally from experience rather than from careful planning.

Good architecture evolves.

That same pattern appeared throughout the entire project. BBSZCodeRunner became the foundation for BBConsoleRunner. Two DOS wrappers became dosdoor.sh. Logging gradually moved into a centralized location. Existing components like Frotz, DOSEMU2, and the DoorParty Connector were reused instead of replaced. Looking back, Cyber Sword BBS wasn’t built by writing everything from scratch—it was built by understanding good tools, connecting them together, and gradually improving the infrastructure between them.

Perhaps that’s the biggest lesson I took away from rebuilding a modern BBS. The goal was never simply to preserve old software for the sake of nostalgia. It was to demonstrate that good ideas have remarkably long lives. The door model is more than forty years old, yet it still provides a clean and flexible way to integrate software written across multiple decades, operating systems, and programming languages.

Today, callers to Cyber Sword BBS can move seamlessly between native Synchronet JavaScript applications, Linux console games, Interactive Fiction, remote DoorParty games, and classic DOS doors. To them, they’re all simply doors. Behind the scenes, however, each one reaches that point through a different combination of technologies, wrappers, adapters, and services.

That invisible complexity is exactly how it should be. The best engineering often goes unnoticed by the people using it. If this project succeeds, callers won’t spend time thinking about DOSEMU2, wrapper scripts, symbolic links, or dropfiles. They’ll simply enjoy the games—and that’s exactly what the bulletin board systems of the 1980s and 1990s were always trying to achieve.

Summary

Over the course of this project, I set out to solve what initially seemed like a fairly straightforward goal: bringing classic BBS doors to a modern Linux-based Synchronet system. Along the way, however, the project became something much larger. What began with Interactive Fiction evolved into reusable wrappers for Linux console applications, integration with remote DoorParty games, native Synchronet JavaScript doors, and finally a complete environment capable of running classic DOS doors under DOSEMU2. Each stage built on the lessons learned from the one before it, gradually transforming a collection of individual solutions into a cohesive architecture.

One of the biggest insights from the project was realizing that not every door should be integrated the same way. Native Synchronet applications don’t require wrappers at all. Linux console games benefit from lightweight adapters that manage per-user state. Remote multiplayer games are better served by specialized connector software, while classic DOS applications require an environment that faithfully recreates the expectations of the original hardware and operating systems. Rather than forcing every application into a single model, Cyber Sword BBS uses the simplest integration that best fits each technology, allowing software spanning more than four decades of computing history to coexist naturally on the same system.

Perhaps the most rewarding part of the project wasn’t getting individual games to run—it was watching the architecture mature. Wrapper scripts evolved from one-off utilities into reusable infrastructure. Existing components such as Frotz, DOSEMU2, and the DoorParty Connector were reused instead of reinvented. Helpful articles, open-source projects, and years of accumulated knowledge from the BBS community all contributed small pieces that eventually came together into a stable and maintainable system. Looking back, the finished architecture wasn’t something I designed all at once. It emerged gradually through experimentation, refactoring, and a willingness to improve each piece as new ideas presented themselves.

Today, callers to Cyber Sword BBS can move seamlessly between Interactive Fiction, native Linux console games, remote DoorParty titles, Synchronet JavaScript applications, and classic DOS doors without ever needing to think about the layers of technology making that possible. That’s exactly how it should be. The best engineering often goes unnoticed because it allows people to focus on what they came to do rather than the infrastructure supporting it. If this project accomplishes anything, I hope it demonstrates that the classic BBS door model remains every bit as flexible today as it was decades ago—and that with a little creativity, these remarkable programs can continue entertaining and connecting people for many years to come.