Jamdroid Server Manager
In the summer of 2021, I found myself using a friend’s game server management panel. This runs on the same server that he uses to run a number of game servers (mostly Minecraft, but with the ability to host other games too) and allows the operator to start, stop, and run commands on the servers with a nice user interface. This gave me the idea to work on my own server panel system, though with the ability to extend it in order to fulfill a number of different roles (such as the webhook integrations described later).
The server is hosted using the free tier of Google Cloud Platform’s (GCP) e2 family of machines, providing more than enough performance for the purposes of a web server. It does mean that, should I want to host any game servers, I would need to either use a separate server or increase the tier of machine being used by the web server.
The server is written in Python 3, specifically Python 3.9. I would have liked to use Python 3.10 since one of its new features was structural pattern matching (which would have made chat bot commands somewhat neater to implement), however that version was not yet readily available on Debian when I was setting up the server itself. The project utilises the Flask microframework, as well as the Gunicorn WSGI HTTP server.
User accounts on the site are handled by utilising GitHub’s OAuth functionality, meaning that features such as 2FA can be utilised without any additional effort. This also means that users’ passwords do not need to be saved on the server, helping to improve its security. As an additional touch, the user’s GitHub avatar is shown on the index page when they are logged in.
Overall, the site works on a concept of “apps”, which originally were intended to represent a game server. For a user that has logged in, and that has access to a given app, they will see it appear on the index page along with a number of controls related to that app (e.g. opening a settings page, starting/stopping the app, etc.). The current most fully fledged apps are:
- Discord Bot
Discord allows developers to create bots that can respond to chat messages. Recently, they also implemented a feature allowing users to explicitly run a bot’s application commands, including a UI within the client prompting for command parameters and alowing for some type restrictions. Jamdroid includes an endpoint to allow Discord to send interaction data via a webhook, with responses being returned as JSON objects. This method better fits a web server environment than the alternative, which requires a more active connection to Discord’s gateway in order to receive events.
- Webhook Handlers
GitHub repositories and organisations can be set up to send webhooks when certain events take place, such as new commits being pushed or releases being created. Previously, I had a bot written using Hubot that would receive these webhooks and send messages to a specific Slack channel if something of importance had happened. This functionality has been implemented in Jamdroid, which means that the logic can now be written in Python rather than Coffeescript, which I am more comfortable with overall.
The Twitch webhook integration sends a message to a specific Discord channel when it receives a payload indication a channel has started broadcasting. It also listens for events showing the channel has stopped broadcasting, and provides a small API in return for our Gameshow and charity stream websites, such that they can show a banner when the channel is currently live.
Twitter’s webhook system is more complicated than the others, in that it requires the owner of the account that will generate events to log in to your website. As a result, Jamdroid implements a “Log In With Twitter” button, and can then register with Twitter to receive webhooks when new events take place. The only one that is important is when a new tweet has been sent, at which point a link to the tweet is forwarded to a specific Slack channel and a specific Discord channel. This set up allows messages to be sent nearly instantly, as opposed to somewhere between a few minutes and an hour or two when using a service such as IFTTT.
YouTube uses WebSub (PubSubHubbub) to send webhooks when channels publish or update videos. One of the main difficulties is that the subscription must be kept active in order to continue, otherwise once the expiration time is met no webhooks will be sent. The other main difficulty for my purposes is that updates trigger webhooks, whereas only new videos should trigger a message to be sent to a specific Discord channel. As a result, Jamdroid maintains a list of all videos it has seen before (which was populated with previously uploaded videos manually) such that it will not send a duplicate announcement message.
- URL Shortener
Somewhat simpler than the webhook handlers, Jamdroid powers a URL shortener hosted on a different domain name (jmy.fyi). This required setting up the main Flask app to care about the host header being sent by the browser, and allows the two different domain names to be served from the same server on the same IP address. As a result, urls such as jmy.fyi/about to be resolved without needing to set up more than one server in GCP.
- Miscellaneous Services for RASA Studios
Jamdroid, being run on a server 24/7, is able to run some services for RASA Studios. These include, but are not limited to, regularly checking some websites/APIs in order to find updates and then relaying these to a specific Slack or Discord channel.
In addition, there are some quality of life features that I have included, such as:
- Automatic Updates
As the source code for the server is hosted on GitHub, I can use the same system for handling GitHub webhooks and use it to pull any changes and restart the server to apply them automatically.
Users with the correct permissions are able to see the server logs from the current day, and from any other day for which logs are still available.
- Dark Mode
The site has an automatic dark mode applied utilising the CSS
@media (prefers-color-scheme: dark)rule, which (amongst other things) sets the page backgrounds to a dark gray and the text to white.