I would like to note. This was originally planned for completion and release on the 15th of March. That time has come and gone. And as a result, I would like to say that the April post may also be delayed. The normal schedule will hopefully be restored by May.

Since sometime in November I’ve been running a Gotify Server on my Proxmox Instance. With the initial intention being to use it to receive notifications from Proxmox. And for that, Gotify functions fine. The issue though is that it’s missing on feature I wanted. And that being the ability for the server to act as a repeater/proxy for other services that can send out notifications. But since Gotify has plugin support I figured I could write up that functionality myself. So I got started on figuring out how.

Project Setup

Now luckily enough finding the documentation for it was pretty easy. They have a GitHub Repository called plugin-template (what a creative name) that provides the needed files for a simple “hello world” plugin.

Unfortunately, it didn’t work quite write on the Ubuntu VM have been SSHing into to tinker with Go. The first of being that I have not configured Docker to be able to be run without using sudo. But that was a simple fix by just adding it to the Makefile.

The other issue. Which seemed strange. Was the inability to find a program called gomod-cap. With the commands originally being gomod-cap -from ${BUILDDIR}/gotify-server.mod -to go.mod. Looking through the Makefile I found that one of the Makefile’s targets appears to “install” it using GO111MODULE=off go get -u github.com/gotify/plugin-api/cmd/gomod-cap. Unfortunately, while that was functioning. It didn’t install it in such a way that it would be runnable as a stand-alone command.

So instead I replaced gomod-cap within that command to go run github.com/gotify/plugin-api/cmd/gomod-cap which works like npx in the npm world. Where it downloads the script as a temp file and executes it. Now that’s likely not the best solution but that’s what I did to get started. Eventually, I’ll get around to understanding why it didn’t work. But on with the project.

From there I modified it to Makefile to give me a target that would build the amd64 version of the plugin and then place it in the correct location where a docker-compose file would pick it up for a test gotify server. Loading the newest version of the plugin into a gotify server every time I ran the target. Which finally gave me an environment to at least test whether this idea would even work.

Initial Prototype

So first up I created a VERY simple demo/prototype just to make sure what I was going to do was even achievable. Initially, I had thought that there would be the ability to hook into an event listener. But that doesn’t exist directly within the Plugin interface. So what I ended up doing, and what someone else did in a different plugin that does this to send emails after the fact, was to use the REST API to get a WebSocket connection that does exactly what I need.

That requires two things then. An HTTP or HTTPS path to the gotify server, cause we can’t guarantee it will be within docker or on a different port than standard. And a client token that can be used for authentication. So this being a proof of concept I simply hard-coded those values.

From there I ended up fiddling around and failing quite frequently to get a Websocket to connect when the server started up and the plugin enabled itself. Until I realized that I was attempting to connect to nothing because Gotify initializes the plugins before it starts accepting traffic. 🤦

But after that was fixed. I then created a loop that would read the messages received from the socket and send out a Discord Webhook every time it received something. And what would you know? It worked. After some trial and error and learning more about how Go handles JSON. But hey can’t make an omelet without breaking a few legs. But it worked as a proof of concept.

Version 0

But then I wanted to make something with what I considered the bare minimum of functional, at least for a kind of Version 0. So I came up with a list of requirements.

Requirements

  • Must Properly Handle enabling and disabling of the plugin.
  • No Hard Coded Values. (URL for Gotify, Client Token, Discord Webhook)
  • Configurable from within Gotity.
  • Simple Formatted Discord Message
  • Information about the plugin displayed within Gotify “Displayer” per the recommendations.

All of which are relatively simple. Most of it more or less is refactoring the already existing demo and properly handling the errors that it might run into. But the result looked like this.

And overall it functions exactly as I was hoping it would though it’s not perfect.

Discovered Flaws/Next Version

Once I deployed version 0 of my plugin to my Gotify Server I decided to run some tests. Unfortunately, these tests presented some issues I didn’t have when running it for short periods when developing. Such as the fact that the plugin currently can not recover itself in the event the Websocket is closed for whatever reason. Requiring the user to disable and enable the plugin to fix it. Another issue and one I didn’t think I’d have to deal with is the fact that a Gotify event message can contain extra information. Which I discovered Proxmox makes use of. So that now needs to be handled and can’t be ignored. So onward with the improvements.

Version 1 - Working So Much Better

Now first off with version 1 came the corrections to quite a few bugs from testing. Which included the inability of the plugin to recover from connection issues. And while I’m not an expert at this point I think I’ve more or less solved it in this case. The problem with Proxmox was also solved relatively quickly by simply not parsing the entire message structure. Only parsing the parts I’m currently using which at this point seems to be the smarter move.

But fixing bugs wasn’t the only thing I wanted in version one. I also wanted to improve the interface for configuring the plugin. And while the Configurator is probably great for some projects. I wanted an interface that could guide the user. At least to some degree. So I began to implement it using the Webhooker Gotify provides and HTMX.

And I must say. I can see the usefulness of HTMX. It allowed me to not have to mess with front-end Javascript to be able to lazy load data or even submit data back to the server. You just have the HTTP server, or in this case, the Gotify server, to respond with properly formatted HTML, which provided me with a very simple way to easily build the kind of interface I needed. At least once I had the time and learned some of its quirks.

In creating the interface I also ended up adding some new features that can be seen in the image above. Such as the ability to more easily set the token that the plugin will use to listen for messages. As well as the ability to manage “transmitters” which is what I’m going to be calling the different destinations that the plugin can relay messages to. With there currently only being two. The “Log Transmitter” and one for Discord Webhooks. Eventually, I plan on expanding the selection but for now, this works for demonstration purposes and my current use.

All of this is being built using HTMX for the interaction with the server itself (all the APIs I created require a valid token) and Bootstrap 5 for some CSS. Which I’m pretty pleased about. And now with that done and working I can comfortably begin the next month’s project. Now to figure out what it is. Well, I have some ideas.

If anyone else may find this plugin useful it is currently available in a public Github Repo here.