tl;dr - my work was superceeded by Zoom releasing their own official repo.

I successfully rolled my own unofficial Zoom repo, but it:

  1. didn’t automatically update, and
  2. wasn’t publicly accessible

Which was problematic - #1 meant it didn’t work for me, and #2 meant it didn’t work for the friend that nerd sniped me into this.

This post is me figuring things out, as well as a curveball that meant I could actually delete all of this.

Generating it on a timer

I turned to systemd to handle this, running my fetch-and-generate script every 4 hours.

I tried using DynamicUser in conjunction with StateDirectory, but just made it more difficult to keep in sync so I initially skipped it and ran it under my normal user account.

$ cat ~/.config/systemd/user/generaterepo.timer
[Unit]
Description=Trigger new RPM version check

[Install]
WantedBy=timers.target

[Timer]
OnCalendar=*-*-* 00/4:00:00
Unit=generaterepo.service
$ cat ~/.config/systemd/user/generaterepo.service
[Unit]
Description=Check for new RPM versions & update the repo if need be

[Service]
Type=exec
ExecStart=%h/customrepo/generate.sh
StandardOutput=append:%h/customrepo/execlog
StandardError=inherit
$ cat ~/customrepo/generate.sh
#!/bin/bash -x
BASE_DIR="${HOME}/customrepo"
LATEST=$(curl -s -I HEAD https://zoom.us/client/latest/zoom_x86_64.rpm | grep '^location:' | sed -e 's@^.*/prod/\([.0-9]*\)/zoom_x86_64.rpm.*$@\1@')
if [ ! -f ${BASE_DIR}/"${LATEST}"/zoom_x86_64.rpm ]; then
    mkdir -p ${BASE_DIR}/"${LATEST}"
    curl --no-progress-meter -L https://zoom.us/client/"${LATEST}"/zoom_x86_64.rpm -o $BASE_DIR/"${LATEST}"/zoom_x86_64.rpm
fi
createrepo --update --baseurl "https://zoom.us/client/" ${BASE_DIR}

Trying Caddy

I want to give this to the friend that nerd-sniped me, so it needs to be internet-accessible. Aka LetsEncrypt certs, some logging, etc.

I had an Ansible playbook that sets up nginx but it’s not integrated with Let’s Encrypt (yet), so I decided to try Caddy for the static file serving before going further. Caddy does seems simpler than nginx for this one use case

dnf install caddy
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --zone=public --add-service=https --permanent
firewall-cmd --zone=public --add-service=https
firewall-cmd --zone=public --add-service=http
$ cat localserving.Caddyfile
localdomain.net {
        root * /home/admin/customrepo
        file_server browse {
                hide *.rpm
        }
        log
}

Static Hosting

With the fact that there’s no special redirect rewriting required, I wondered if this would work in static hosts like Github Pages or Cloudflare Pages.

Turns out yes, with a few caveats. Biggest is there’s no file browsing so I had to build the paths in Firefox to check if files were where they were meant to be.

The Cloudflare Pages CLI (Wrangler) hated the RPM files which were present in the directory I was trying to sync (size complaints, wanted me to use R2), but Netlify worked fine.

Thanks Zoom

I built a pipeline that worked and I had a short list of improvements to consider my unofficial Zoom repo “good enough to let friends use it”:

  • Try integrating healthchecks.io as light weight alarming if the shell script failed
  • Actually use systemd DynamicUser
  • Extension: Have a periodic cleanup that cleans up RPMs if there’s >5 as part of the systemd timer? Thinking about triggering a script with the After= parameter

Then I found out Zoom quietly put up their own RPM repo at some point. They even have a zoom_release.repo file you can put into /etc/yum.repos.d:

sudo curl --location https://repo.zoom.us/repo/rpm/zoom_release.repo --output /etc/yum.repos.d/zoom_release.repo
sudo rpmkeys --import https://zoom.us/linux/download/pubkey?version=5-12-6
sudo dnf install zoom

Oddly their own repo file doesn’t use the latest gpgkey parameter, but you can import the current key manually and dnf subseqently appears to ignore the gpgkey option in the repo spec, so… /shrug.

RIP my service

Hilariously (in retrospect), at the start of this I wrote:

I’d love for Zoom to release a proper repo so I can consign this to being an interesting investigation and delete everything.

Time to delete everything, the functionality I need is run by someone else so I don’t have to roll my own service.

To wrap this up somewhat nicely, I don’t think there’s anything special that I did here other than use the baseurl option to createrepo. Everything else is pretty standard, the systemd timer and the script can live as examples for future me.