Pop out Player/Article to new tab/window
Instead of "How To"s I am calling articles like these "How Did I"s. I am doing so for two reasons, the first is just to be difficult, and the second is that I don't consider myself an expert on any of these subjects, so I want to encourage discussion on what I could have done differently or better. Hopefully these can help someone overall, or possibly help me to improve my own techniques, so feel free to leave feedback (Hoping to have a comment system written for the website, but until then feel free to use the BBS message boards, Discord server, or video comments).
Recently, the Digital Lethargia BBS went live. Since I have separated out the database, web server, and app server portions of Digital Lethargia into Docker containers, I wanted to do the same for the BBS. This helps keeps things modular, easily updated with change control, and somewhat sand boxed. Plus with telnet being a somewhat insecure protocol, I liked the idea of it going to a container rather than directly to the host itself. I chose Synchronet as the BBS system to use because it is still actively maintained, has a good community, and has good management tools. This article assumes a small level of experience with building packages and using Docker.
Prerequisites
The first thing I needed to do was get my system ready to build and test the Synchronet system. I am currently using an Ubuntu 20.04 system, so these steps may differ depending on your distribution. The Synchronet Wiki lists the needed perquisites (with Debian package name) for linux systems here. Personally I was missing the ncurses dev library (libncursessw5-dev) and the Netscape Portable Runtime Library (libnspr4-dev) so I had to install them using apt:
sudo apt install libncursesw5-dev sudo apt install libnspr4-dev
Since I planned to be running some old DOS doors with the BBS, I was going to need dosemu to run them. There didn't seem to be a package for dosemu in the official Ubuntu repositories, so I ended up finding a package built for 19.10 on Launchpad and installing that using apt (you can use dpkg as well).
wget http://launchpadlibrarian.net/363141379/dosemu_1.4.0.7+20130105+b028d3f-2build1_amd64.deb sudo apt install ./dosemu_1.4.0.7+20130105+b028d3f-2build1_amd64.deb
Building
Now that I had the prerequisites installed, I was able to proceed with building the package from source. Luckily, the Synchronet Wiki has very good instructions for this and a wonderful Makefile that even handles the code checkout for you, so generally all you need to do is obtain the Makefile and build:
wget https://gitlab.synchro.net/main/sbbs/-/raw/master/install/GNUmakefile make install USE_DOSEMU=1
Note that if you are just building Synchronet to run outside of a container, I would suggest following the instructions and use the symlink flag (e.g. "make install SYMLINK=1 USE_DOSEMU=1") as that will make subsequent updates/upgrades to the system easier in the future. Since I will be rebuilding the container for updates, I chose to not use the symlink flag.
I got a number of warnings during the build, but luckily no errors. At this point I ran a quick test to see if the scfg utility would load to ensure the build worked. A reminder that by default, Synchronet looks for its' config files under the /sbbs/ctrl folder, so you will need to add the SBBSCTRL environment variable if you are not using that path:
export SBBSCTRL=/home/lemac/src/sbbs/ctrl
Containerization
I already had a Docker environment setup on my machine, so I won't go into that setup here. I am still somewhat of a beginner on the Docker side of things so I am sure there are some ways I can improve this. I started by creating a docker folder in my sbbs folder (The git repository is one level down still so this doesn't conflict). I also created folders and moved in needed items like the dosemu package and termcap file to make things easier.
Within the docker folder I created a Dockerfile, which you can think of as a Makefile for docker containers. It tells docker all the information and pieces it needs to build a container.
Here is the final Dockerfile I ended up with:
#Use Ubuntu 20.04 FROM ubuntu:20.04 LABEL Description="Synchronet BBS server" EXPOSE 23 22 RUN mkdir /sbbs COPY ./ /sbbs/ COPY ./dosemu/dosemu.deb /tmp/dosemu.deb COPY ./termcap/ansi.bbs /tmp/ansi.bbs RUN apt update RUN apt install -y \ libnspr4 \ libncursesw5 \ zip RUN apt install /tmp/dosemu.deb -y RUN rm -rf /var/lib/apt/lists/* RUN chmod -R 0755 /sbbs/exec RUN tic /tmp/ansi.bbs RUN mkdir /media/cdrom COPY ./dosemu/dosemu.conf /etc/dosemu/dosemu.conf ENV LD_LIBRARY_PATH="/sbbs/exec:${LD_LIBRARY_PATH}" ENV LANG="en_US" WORKDIR /sbbs/exec ENTRYPOINT ["/sbbs/exec/sbbs"]
FROM ubuntu:20.04 - tells Docker which base image to start from and generally needs to be the first line. Since I used Ubuntu 20.04 to build the system, I chose that as my base image.
LABEL - Defines a description for the container
EXPOSE - These are the ports the container will be listening on. These can be mapped to host ports at runtime.
RUN/COPY - The commands themselves are pretty self explanatory, they run a command within the container, or copy items into it. I am doing the following set of steps here:
- Create the /sbbs folder
- Copy the sbbs folder structure in from my dev directory
- Copy the dosemu deb package into the tmp folder
- Copy the ansi-bbs termcap file into the tmp folder
- Run the "apt-update" command to populate the list of available Ubuntu packages
- Install the needed packages (Note that these are the binary versions of the dev prerequisites I installed before building)
- Install the dosemu deb package
- Remove the package list files to save space now that everything is installed
- Make sure everything in the /sbbs/exec folder is executable
- Compile the ansi-bbs termcap info file
- Copy my modified dosemu config file over the default
- Add the /sbbs/exec folder to the LD_LIBRARY_PATH so that the executables can locate their needed libraries
- Ensure the LANG variable is set to "en_US"
WORKDIR - This line defines the working folder the container will be in at runtime. In this case the exec folder of sbbs
ENTRYPOINT - This lines defines the command that will be executed when the container starts. In this case the sbbs server.
Once I had everything defined, I built it with the "docker build" command. I use the -t argument to tag the container (My naming convention is repository:ServiceDateBuild), the -f argument to specify which Dockerfile to user, and finally the required context argument as "." to use the current directory:
docker build -t="diglet:bbs12152001" -f docker/Dockerfile .
The command will download the base image you specified in the FROM line, apply all the changes/commands specified in the rest of the Dockerfile, and package it up as a new container image.
Running/Testing
To test the container I first just ran it in the foreground to see all the output with the "docker run" command, using the --name argument to name the container, followed by the tag of the container image to run:
docker run --name diglet-bbs-test diglet:bbs12122001
At which point I saw the familiar Synchronet output and things were looking good, so time to test it out. Because of the way I started the container, it will only be accessible from the local machine, which is fine for initial testing. To do so I needed to find out what IP address docker gave the container within its' external range to do so I used the "docker inspect" command and looked for the "IPAddress" line in the output:
docker inspect diglet-bbs-test ... "IPAddress": "172.17.0.2", ...
With that information I was able to load up SyncTerm, and connect to the BBS using telnet. Success!
The nice thing about Syncrhronet is that you can run the scfg utility right from within a session to the bbs. There may situations where you need to be able to do run a command from within the container. To do so I execute the bash shell within the container in interactive mode (-i) using a psuedo terminal (-t)
docker exec -it diglet-bbs-test /bin/bash
This gives you a prompt to run anything you need to.
Persistent config and external access
At this point I have a container image that I can deploy a default Synchronet setup from at anytime. Once deployed I can connect to it and configure it. The next issue I needed to solve was the fact that all the data and config are stored within the container itself, so that anytime I need to deploy an updated container, all that info will be wiped out. Luckily, docker allows you to map folders within your container to folders on the host (-v), even if they already exist within the container itself. Note that you will have to copy the contents of these folders from your initial build folder to the host mapped folders the first time. To allow external access, you can tell docker to map an exposed port on the container to a port on the host (-p). And finally, you can tell docker to run the container "detached" (-d) so that it keeps running in the background. An example of the run command similar to my prod host:
docker run --name diglet-bbs \ -v /root/dockerdata/sbbs/ctrl:/sbbs/ctrl \ -v /root/dockerdata/sbbs/text:/sbbs/text \ -v /root/dockerdata/sbbs/xtrn:/sbbs/xtrn \ -v /root/dockerdata/sbbs/data:/sbbs/data \ -v /root/dockerdata/sbbs/mods:/sbbs/mods \ -p 23:23 -p 22:22 \ -d reponame/diglet:bbs12122001
Note that I also mapped the entire xtrn folder to my host, which means I would have to copy any updates done to the xtrn folder when upgrading the system as well. A better practice might be to have a separate folder for doors that you add to your system.
And that is basically it. I can now delete and redeploy the container, and as long as I keep the same mapped folders, my config/data will persist.
References
- Synchronet: https://www.synchro.net/
- Synchronet Wiki: http://wiki.synchro.net/index
- Docker: https://www.docker.com/
- DOSEMU: http://www.dosemu.org/
- Ubuntu LaunchPad: https://launchpad.net/ubuntu