Forgejo Runners without containers
Forgejo has a cool feature called runners, that do stuff when events happen to your source code in the repository. Many people use this to build versions of the software based on new source code.
Because runners are very powerful, the documentation and base configuration is based on fairly complex setups, involving firing up Docker or LXC containers to run test and build processes in. This is all good and well, but my requirement for them was a lot simpler, which made setting up a runner pretty frustrating.
My simple setup
As I’ve said before, this blog is now generated static HTML. Originally, I had a copy of Zola on my laptop that I used to build the HTML, then I would rsync the HTML over to my web server. A bit later I fugured I can just pull the stuff from my Forgejo server with git pull and have a hook in git on the web server that runs Zola to build the site and copies the files to the right place.
This worked well, but still needed me to SSH to the web server to run the git pull command.
The basic idea
So why not just have a Forgejo runner sitting on the web server to run that git pull command when I push changes to Forgejo? Well, this is where the frustration with existing documentation started. By design, my web server is a very simple beast, it doesn’t do fancy stuff like Docker or LXC, and all the documentation I could find was geared for a setup where the runner fires off one or more containers, their consession to not using containers was a simple warning that running your jobs on the host was unsafe because there isn’t any separation from the host operating system.
So my requirement to just be able to run a simple git command on the web server was theoretically possible, but basically ignored by all the documentation I could find.
My setup
So… This blog post exists, so I must have figured it out, right? Yes I did, and this blog post exists in the hope that it will be of some use to someone else who has a very basic and simple Forgejo runner requirement.
On the web server
As discussed in the official documentation, the first task is to install the forgejo-runner daemon on the host. One thing I did, which I’m not convinced is needed, is that I only put a host tag in when I registered the runner with my Forgejo server. Oh, and because security is important, I also registered it at the user level on the Forgejo, not the server level.
Next I did forgejo-runner generate-config > ~/forgejo-config.yml to create a configuration file. I then edited the config file and, in the runner: part, put this:
labels:
- "self-hosted:host"
This effectively tells forgejo-runner that it will only be accepting jobs with the label self-hosted and it will run those jobs on the host. If you don’t do this, forgejo-runner will refuse to start because it will try to talk to Docker and fail.
The next step was to create a shell script in my home directory (here I’m going to change my username to user just because) called /home/user/fj-runner.sh with this in it:
#!/bin/bash
&
I then added to my crontab as follows:
@reboot /home/user/fj-runner.sh
The effect of that is to start the runner when the machine boots up without involving systemd in the process and adding that complexity.
The last step on the web server was just to run /home/user/fj-runner.sh manually to fire up the daemon.
In my repository
So to get a runner to actually do something, it has to be registered with the Forgejo server (done as part of installing forgejo-runner) and there has to be a workflow in the repository for it to run.
This workflow is a yaml file sitting in the .forgejo/workflows directory of your repository, so I created .forgejo/workflows/update-site.yml in my blog’s repository. There’s a lot of stuff that can be done in these workflows, but mine is pretty simple because I don’t need to do all manner of clever stuff with it, so it looks like this:
on:
push:
paths:
- 'content/**'
- 'static/**'
jobs:
deploy:
runs-on: self-hosted
steps:
- id: step1
run: |
cd /home/user/blog
git pull
By way to basic description of what happens here:
- The
on:section tells the runner when it has to run this workflow. In my case, I decided to just have it run whenever I push changes and those changes involve anything in thecontentorstaticdirectories. I’m sure there are much better ways, like maybe when there are pushes to a specific branch meant for deploymnent. - The
jobs:section defines the actions the runner must take, in my case just the one job calleddeploy.- The first line in
deploy:, thatruns-on: self-hostedis the bit of magic that tells the runner on my web server that it must run this job. The labels in my forgejo-runner config then tells it to run it on the host instead of trying to fire up a container. - The
run:bit is basically a list of commands the runner will exdcute in a shell. This just goes to the directory where the blog’s git repository sits on the web server and then runs agit-pull, which does the rest of the stuff to build and publish the site.
- The first line in
Wait, whut? Git magic?
OK, so this bit is slightly out of context for this post, but pretty cool. Like I said, I have stuff happening magically when I (or forgejo-runner running as my user) do git pull in the /home/user/blog directory.
To keep stuff simple, I actually configured my web server to serve this site from the /home/user/blog/public directory, because that’s where Zola puts the site when it builds it.
So how does this magic work? Well, git will look for “hooks” in the .git/hooks directory whenever it does something, the file name being the event the script will run for. Since a git pull involves a merge event if anything was pulled, I created /home/user/blog/.git/hooks/post-merge to run after the merge event, this file literally just contains:
#!/bin/bash