Deploying A Self-Hosted Portable Notification Service

Gotify

In a previous article I wrote about how I've changed my relationship with my phone. One of the benefits of degoogling is that your device is a little less spied upon. The downside of running lineage for microg is that certain functionality is a bit harder to come by.

I like minimal notifications, but there are things happening I want to know about. On iOS I used prowl to tell me about reboots. As there's no f-droid client I found myself without a emergency notification system. I saw gotify in the f-droid app and thought I'd give it a go. So far, I'm pretty happy with it.

I recently rebuilt an old unused box to self-host low-priority services. I'm a big fan of self-hosting having been burnt several times by online services. I'm not against online services making a living, but I'd rather own my stuff than rent.

The box was rebuilt to use docker and docker-compose. I find docker a double-edged sword. You either have to maintain your own docker repository or trust someone else's. This box only runs low-priority services. I'm ok running images from other people's repositories.

Installing Gotify Server With Docker-Compose

I set up caddy as a front-end service to manage letsencrypt. I prefer nginx but for docker, Caddy's fine. I also use ouroboros to auto-update images when new ones come out. If I'm going to use other people's repos I may as well get some value out of it.

Creating a gotify docker-compose entry was easy. I've included ouroboros and the caddy frontend in mine below:

version: '3'
services:
  ouroboros:
    container_name: ouroboros
    hostname: ouroboros
    image: pyouroboros/ouroboros
    environment:
      - CLEANUP=true
      - INTERVAL=300
      - LOG_LEVEL=info
      - SELF_UPDATE=true
      - IGNORE=mongo influxdb postgres mariadb
      - TZ=Europe/London
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
	
  caddy:
    container_name: caddy
    image: abiosoft/caddy:no-stats
    restart: unless-stopped
    volumes:
      - ./caddy/Caddyfile:/etc/Caddyfile
      - ./caddy/caddycerts:/etc/caddycerts
      - ./caddy/data:/data:ro
    ports:
      - "80:80"
      - "443:443"
    env_file:
      - ./caddy/caddy.env
	
  gotify:
    container_name: gotify
    image: gotify/server
    restart: unless-stopped
    volumes:
      - ./apps/gotify/data:/app/data

My caddy config needed an additional section for the new host:

gotify.steves.box {
  root /data
  log stdout
  errors stdout
  tls s@steves.box
  gzip
  proxy / gotify:80 {
    transparent
    websocket
  }
}

Hostnames have been changed to protect the innocent. When using caddy, specify websocket in the proxy section. The Android app uses websockets to handle notifications.

A quick docker-compose up -d and I was up and running. The default username and password is admin/admin. Change that first, then create a user account to receive notifications.

After creating the user account, log out of admin, and log back in as the new user. Notifications are per-application and per-user. You'll have to send notifications for each user. I hope group notifications will be possible at some point.

Gotify notifications

I added a cute puppy picture to my app, making unexpected reboots all the more cute. The installed the gotify app from f-droid and added my server. I checked the app and server logs for HTTP 400 errors. This would stop notifications from working.

A Portable Commandline Notification Tool

I wrote a quick python-based tool to send notifications from the command line. You can use the official gotify client tool, or even curl. I wanted something portable that would work without 3rd-party libraries.

#!/usr/bin/env python
# gotipy.py - A python gotify client using only built-in modules
	
import json, urllib, urllib2, argparse
	
parser = argparse.ArgumentParser(description='gotify python client')
	
parser.add_argument('-p','--priority', help="priority number (higher's more intrusive)", type=int, required=True)
parser.add_argument('-t','--title', help="title notification", required=True)
parser.add_argument('-m','--message', help="message to display", required=True)
parser.add_argument('-v','--verbose', help="print response", action='store_true')
args = parser.parse_args()
	
url = 'https://gotify.steves.box/message?token=changeme'
data = urllib.urlencode({"message": args.message, 
			"priority": args.priority,
			"title": args.title})
	
req = urllib2.Request(url, data)
resp = urllib2.urlopen(req)
if args.verbose:
	print resp.read()

If you use the script, don't forget to change the token value in the url variable to one for your app.

The final thing to do is to set up a reboot notification for the chargen.one box. We can do this on OpenBSD using a cron job. I've copied gotipy.py into /usr/local/bin and set up a cron job as a normal user to run on reboot:

@reboot python2 /usr/local/bin/gotipy.py -p 8 -t "Chargen.one" -m "Rebooted at `date`"

Now if we reboot the system, we can check that it's working by looking in /var/cron/log:

Apr 6 16:11:10 chargen cron[41858]: (asdf) CMD (python2 /usr/local/bin/gotipy.py -p 8 -t "Chargen.one" -m "Rebooted at `date`")

Please note that some OSes only run @reboot jobs for root. If you're having trouble, check your cron daemon supports non-root @reboot jobs.

If you're wondering what else I plan to use this for, it's not really much. I like only having serious event notifications and want to keep it minimal. Some of the things I'll use this for include:

For pretty much everything else, there's email and I can pick that up in slow time.