Simple Mobile Push Notifications

Posted: September 17, 2024 at 12:00 PM

TL;DR: Check out WPN for code that implements push notifications to your browser (mobile or desktop).

Background

I run my own server/services, so it’s helpful to be able to receive notifications, such as when a hard drive starts throwing errors, or when I get an SMS to my home phone, or when snapaid fails to process a FreeBSD announcement. I had used a Java Signal client for a while, but that required both a phone number, but also a complex Java runtime to keep running, and eventually Signal upgraded the server protocol such that the client I had stopped working, and getting a new client working was too much work.

I was originally thinking about writing a custom Android App that just did this, but thinking about things a bit more, I realized that Web browsers have both a Push API and a Notifications API that I could use. Using an official notification API would keep the battery usage in check unlike using a custom protocol (or I’d have to use Google’s GCM directly, and yes, I do realize that web browsers are likely using it under the hood).

The blog post Push notifications overview has a good overview on how they work.

Overview

With a little bit of research, I found some example code that would register the push api, post the necessary info, and then display the notification received.

There are better guides on this, but the short is that it requests notifications, and once the user grants permission, registers a service worker, and once that service worker is registered, subscribe to the Push API, and POSTs the necessary info to the server. With the information POSTd, there’s a simple client that can be used to push notifications.

With a little bit of work, I got it working and modified to be even more simple by not requiring a Python webserver, but instead use a CGI so that it integrates with your existing web server.

The installation and setup instructions are located in the WPN repository.

Isolated IP Management

Updated: August 25, 2023 at 4:15 PM

Originally Posted: August 17, 2023 at 12:00 PM

Edited on 2023-08-25 to add how to start the jail on each boot.

The latest iteration of my home firewall has a spare interface. Many switches these days have a dedicated management interface, and I wanted something similar for my firewall. I want an interface that I can plug in, get an IP via DHCP, and I can ssh into and it’ll “just work” even if there’s a misconfiguration, or an IP conflict.

On FreeBSD, vnet jails are a way to isolate an interface such that it won’t interfere (or receive interference) from other interfaces. Some may have just used epair and assigned IPs, but this could cause conflict, while using a unix domain socket keeps things isolated, and FreeBSD ships w/ everything needed to make this work. A package like socat isn’t needed.

Configuring the host was straight forward, make a directory for the socket:

mkdir /var/mgmt

And then add a line to /etc/inetd.conf to listen to incoming connections on the socket and launch sshd:

/var/mgmt/mgmt.ssh.sock stream  unix    nowait  root    /usr/sbin/sshd  sshd -i

The -i option to sshd tells it to run in “inetd” mode, which means that the stdin and stdout of the process is the socket to use for communication.

The next harder part was getting a jail configured that would accept incoming connections and forward them to the unix domain socket.

First the jail configuration, which goes in /etc/jail.conf or similar location:

mgmt {
        host.hostname = mgmt;                           # Hostname

        vnet ="new";
        vnet.interface="mgmt0";

        path = "/usr/jails/mgmt/root";                   # Path to the jail
        mount.fstab="/usr/jails/mgmt/fstab";             # mount spec

        mount.devfs;                                    # Mount devfs inside the jail
        devfs_ruleset = "101";

        exec.start = "/bin/sh /etc/rc";                 # Start command
        exec.stop = "/bin/sh /etc/rc.shutdown";         # Stop command
}

There isn’t anything special in this. It’s pretty standard jail configuration, the differences are the vnet configuration, and the devfs_ruleset.

The devfs_ruleset is necessary in order to expose the bpf interface used by dhclient. This required the following lines in /etc/devfs.rules:

[mydevfsrules_jail=100]
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login

[devfsrules_jail_dhcp=101]
add include $mydevfsrules_jail
add path 'bpf*' unhide

Note that after adding the above lines, you need to run:

service devfs start

to load the rules (per devfs(8) man page).

Note: I learned my mistake not to number my blocks immediately after the standard defaults (from /etc/defaults/devfs.rules). I had done that befure but there’s now a conflict, so I skip ahead a bit to get a unique range.

Now I needed to make a number of directories for the jail:

mkdir -p /usr/jails/mgmt/root
mkdir -p /usr/jails/mgmt/etc
mkdir -p /usr/jails/mgmt/var/mgmt
mkdir -p /usr/jails/mgmt/tmp

I needed to setup the fstab for the jail:

# Device                Mountpoint      FStype  Options         Dump    Pass#
/                       /usr/jail/mgmt/root     nullfs  ro      0       0
/usr/jail/mgmt/etc      /usr/jail/mgmt/root/etc nullfs  rw      0       0
/usr/jail/mgmt/var      /usr/jail/mgmt/root/var nullfs  rw      0       0
/var/mgmt               /usr/jail/mgmt/root/var/mgmt    nullfs  ro      0       0
/usr/jail/mgmt/tmp      /usr/jail/mgmt/root/tmp nullfs  rw      0       0

This is a little bit more tricky, It first nullfs mounts the root system. I’m using ZFS boot environments, so this is a pretty clean FreeBSD install without much host specific data. It then mounts some jail specific directories for etc, tmp and var and finally mounts the shared directory w/ the unix domain socket to the host system. Also note that a couple of the mounts are read-only to prevent the jail from modifying the system.

The etc directory was populated from the system etc via:

tar -cf - -C /etc . | tar -xf - -C /usr/jails/mgmt/etc

Then the jail was configured, first /usr/jails/mgmt/etc/rc.conf:

hostname="mgmt.funkthat.com"

sendmail_enable="NONE"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"

# Management port
ifconfig_mgmt0="DHCP"

# necessary as devd can't be run in a jail
synchronous_dhclient="YES"

inetd_enable="YES"              # Run the network daemon dispatcher (YES/NO).

The key part of this configuration that took me a while to figure out was the synchronous_dhclient line. It used to be that netif would start dhclient, but in order to better handle USB ethernet devices and other removable interfaces, it was moved to devd. The only problem is that devd hasn’t been jail’ified, and you can’t run it to get things like link notifications that would normally launch dhclient. Setting this to yes, makes sure it gets launched when the jail starts.

And then the following line was added to /usr/jails/mgmt/etc/inetd.conf:

ssh     stream  tcp     nowait  root    /usr/bin/nc     nc -N -U /var/mgmt/mgmt.ssh.sock

This is the part that will forward incoming connections to the ssh port on to the unix domain socket.

Now that everything is configured, a simple jail -c mgmt will get the jail running and accepting connections.

To get the jail to start every boot, add the following to the host’s /etc/rc.conf:

jail_enable="YES"       # Set to NO to disable starting of any jails
jail_list="mgmt"

This was testing and deployed on a FreeBSD 14-CURRENT build as of August 8th, 2023, or more specifically, from main-n264621-09c20a29328, but it should work on all currently supported FreeBSD releases.

Making FreeBSD magnet links

Updated: December 7, 2022 at 12:20 PM

Originally Posted: July 3, 2018 at 4:49 PM

Note: This was originally posted here, but things have been improved since then and so it has been updated.

For the last few years, I’ve been producing torrents and publishing magnet links, but there is some special work that I do to make these. The first few releases, I inserted a bogus tracker into the torrent, because despite there being plenty of tools out there for producing trackerless (DHT) torrents, they were all GUI and I never found any that were command line based. The other was there was/is no tool for extracting the info hash and building the magnet link. There may be tools now, but I couldn’t find any when I started 3 years ago.

The following steps are based upon the recent release of FreeBSD 11.2‑R, adjust as necessary.

  1. Fetch the FreeBSD release and release announcement into a directory (I create a per release directory). remove the CHECKSUM.SHA256, CHECKSUM.SHA512 and index.html* files.
    
       $ REL=12.4-RELEASE
       $ RELABV=12.4R
       $ curl https://www.funkthat.com/~jmg/FreeBSD-snap/snapshot.idx.xz | xzcat | awk ' $2 == "'"$REL"'" { print $9 }' | xargs -L 1 -P 3  wget --no-verbose -c --limit-rate=7000k
       $ wget https://www.freebsd.org/releases/$RELABV/announce.asc
    
       
    This step depends upon snapaid running. If it is no longer running for some reason, addinfo.sh could be run manually against the release email (not the downloaded announce.asc file as the message id is needed) to generate snapshot.idx.xz locally, and use that instead.
  2. Verify the GPG key that signed the above files. This is usually Glen Barber’s key, but not always. I have met and verified his fingerprint in person, If you have verified someone’s key who has signed Glen’s key, that is another good way.
  3. Verify the release announcement:
    
       $ gpg --verify announce.asc
       Warning: using insecure memory!
       gpg: Signature made Mon Dec  5 17:34:59 2022 PST using RSA key ID 478FE293
       gpg: Good signature from "Glen Barber <gjb @ FreeBSD.org>" [unknown]
       gpg:                 aka "Glen Barber <gjb @ keybase.io>" [unknown]
       gpg:                 aka "Glen Barber <gjb @ glenbarber.us>" [unknown]
       gpg:                 aka "Glen Barber <glen.j.barber @ gmail.com>" [unknown]
       gpg: WARNING: This key is not certified with a trusted signature!
       gpg:          There is no indication that the signature belongs to the owner.
       Primary key fingerprint: 78B3 42BA 26C7 B2AC 681E  A7BE 524F 0C37 A0B9 46A3
            Subkey fingerprint: 8D12 403C 2E6C AB08 6CF6  4DA3 0314 58A5 478F E293
       
  4. In the past I have used BitTornado for other things, so I ended up using it as the basis to make the tool for creating trackerless torrent files. The modifications were simple. It appears that the original BitTornado CVS tree is off-line (anyways, it was served insecurely), but it looks like effigies/BitTornado is similar enough that it could be modified and used. I copied btmakemetafile.py to btmaketrackerless.py and applied the following patch:
    
       $ diff -u btmakemetafile.py btmaketrackerless.py 
       --- btmakemetafile.py   2004-05-24 12:54:52.000000000 -0700
       +++ btmaketrackerless.py        2016-10-10 17:13:32.742081000 -0700
       @@ -23,9 +23,9 @@
        def prog(amount):
            print '%.1f%% complete\r' % (amount * 100),
    
       -if len(argv) < 3:
       +if len(argv) < 2:
            a,b = split(argv[0])
       -    print 'Usage: ' + b + ' <trackerurl> <file> [file...] [params...]'
       +    print 'Usage: ' + b + ' <file> [file...] [params...]'
            print
            print formatDefinitions(defaults, 80)
            print_announcelist_details()
       @@ -33,9 +33,9 @@
            exit(2)
    
        try:
       -    config, args = parseargs(argv[1:], defaults, 2, None)
       -    for file in args[1:]:
       -        make_meta_file(file, args[0], config, progress = prog)
       +    config, args = parseargs(argv[1:], defaults, 1, None)
       +    for file in args[0:]:
       +        make_meta_file(file, None, config, progress = prog)
        except ValueError, e:
            print 'error: ' + str(e)
            print 'run with no args for parameter explanations'
    
       
    If you notice, the only thing that is done is to drop the first argument, and instead of passing it into make_meta_file, a None is passed instead. This will simply not add trackers to the torrent file.
  5. I then run the following script to verify the downloaded files, and generate the torrent files:
    
       $ cat cmp.sh
       #!/bin/sh -
       # REL=12.4-RELEASE
       # RELABV=12.4R
       # xzcat ~jmg/public_html/FreeBSD-snap/snapshot.idx.xz | awk ' $2 == "'"$REL"'" { print $9 }' | xargs -L 1 -P 3  wget --no-verbose -c --limit-rate=7000k
       # XXX - following command 404, manually saved from relase announcement
       # wget https://www.freebsd.org/releases/$RELABV/announce.asc
       # wget -c https://download.freebsd.org/ftp/releases/CI-IMAGES/$REL/amd64/Latest/FreeBSD-$REL-amd64-BASIC-CI.raw.xz
       # wget -c https://download.freebsd.org/ftp/releases/VM-IMAGES/$REL/riscv64/Latest/FreeBSD-$REL-riscv-riscv64.{qcow2,raw,vhd,vmdk}.xz
    
       grep -h '^   SHA512' announce.asc | sed -e 's/SHA512 (\(.*\)) = \(.*\)/\2 \1/' | sort -k 2 > sha512.from.asc
    
       while read hash fname; do
               if [ -e "$fname" ]; then
                       if [ -e "$fname".torrent ]; then
                               echo skipping "$fname"...
                               continue
                       fi
    
                       sigfile=$(grep -l -- "$fname" *.asc | head -n 1)
                       echo checking "$fname", sig in: "$sigfile"
                       #res=`sha512 -q "$fname"`
                       #res=`shasum -a 512 "$fname" | awk '{ print $1 }'`
                       res=$(openssl sha512  < "$fname" | awk '{ print $2 }')
                       echo "File is: $res"
                       if [ x"$res" != x"$hash" ]; then
                               echo missmatch!  "$fname"
                               exit 1
                       fi
                       btmaketrackerless.py "$fname" &
               else
                       echo missing "$fname"
                       exit 1
               fi
       done < sha512.from.asc
    
       
  6. Once all the torrents have been generated, I then make the magnet links:
    
       $ cat btmakemagnet.sh
       #!/bin/sh -
    
       # metainfo file.: FreeBSD-10.3-RELEASE-sparc64-bootonly.iso.torrent
       # info hash.....: 06091dabce1296d11d1758ffd071e7109a92934f
       # file name.....: FreeBSD-10.3-RELEASE-sparc64-bootonly.iso
       # file size.....: 203161600 (775 * 262144 + 0)
       # announce url..: udp://tracker.openbittorrent.com:80
       # btshowmetainfo 20030621 - decode BitTorrent metainfo files
    
       for i in *.torrent; do
               btshowmetainfo.py "$i" | awk '
       $0 ~ "^info hash" { info = $3 }
       $0 ~ "^file name" { name = $3 }
       END {
               print "magnet:?xt=urn:btih:" info "&dn=" name
       }'
       done
       
  7. I then create the magnet links file, and update the Torrents wiki page.