~/e4b4.eu

Adding some CI to this blog

Rationale

Having a whole CI pipeline for a blog that wasn't updated in almost three years is.... maybe a bit overkill (okay, a lot overkill).

However while I have used Gitlab CI extensively for work, I never had the opportunity to try out ForgeJo/Gitea/Github actions. So I guess this is as good an excuse as any.

Backend setup

ForgeJo setup

(ForgeJo is a fork of Gitea, and both implement their Actions system which is mostly compatible with Github Actions)

Before enabling actions, one needs a runner. Thankfully the setup is dead simple and well documented.

Secrets

I simply added the following to my "server bootstrap script":

# Create user
cut -d: -f 1 /etc/passwd | grep -qx e4b4-deploy || useradd -d /var/lib/e4b4-deploy e4b4-deploy
[[ -d /var/lib/e4b4-deploy ]] || cp -r /etc/skel /var/lib/e4b4-deploy/
mkdir -p /var/lib/e4b4-deploy/.ssh

# Change file permissions on $E4B4_DIR
chown -R :$(id -g e4b4-deploy) "$E4B4_DIR"
chmod -R g+w "$E4B4_DIR"

I then created a key and added it:

$ ssh-keygen -t ssh-ed25519 /tmp/e4b4-deploy
$ sudo cp /tmp/e4b4-deploy.pub /var/lib/e4b4-deploy/.ssh/authorized_keys
$ [# Save the private key for later]
$ rm /tmp/e4b4-deploy*

I then went to my repo's Settings → Actions → Secrets and added the private key to a new secret called DEPLOY_SSH_KEY.

And there we have it, the project now has all the credentials it needs to push files to the server!

Actions setup

Finally, the part I was actually interested in. Here is how it ended up looking:

name: Deploy to e4b4.eu
run-name: ${{ forgejo.actor }} deploying to e4b4.eu
on: [push]
jobs:
  Make-And-Deploy:
    runs-on: docker
    container:
      image: python:3-alpine
    steps:
      - run: apk add --no-cache git make npm openssh-client rsync && pip install pygments jinja2 markdown && npm install -g sass
      - uses: actions/checkout@v3
        with:
          submodules: true
      - run: make
      - run: |
          eval $(ssh-agent -s)
          echo "${{ secrets.DEPLOY_SSH_KEY }}" | ssh-add -
          make deploy WEB_SERVER=e4b4-deploy@[SSH SERVER] SSH="ssh -oStrictHostKeyChecking=no"

Overall this is not too dissimilar in principle to Gitlab CI, although I'm observing the following differences:

  • The web interface (at least in ForgeJo) is much snappier than GitLab which (at least in my experience) polls the server quite slowly. It doesn't matter too much but it makes the pipeline nicer to look at.
  • Having to explicitly checkout the repo is weird to me, but thinking more about it I guess it makes a lot more sense than GitLab's default of starting with an already checked-out repo. For instance I could have much more easily set-up some credentials if some submodule was not hosted publicly.
    Also just like with Gitlab CI, the submodules aren't checked out automatically, which I suppose is good for "performance by default" but bad for my sanity as it took me a little bit to figure out why I was missing some files (I had completely forgotten that there even was a submodule in the project to be honest).
  • Apparently eval $(ssh-agent -s) cannot be done in a separate run: block like with Gitlab CI because the exported SSH_AUTH_SOCK is not going to stay exported in the following steps of the job which seem to be run in separate shells (unlike with GitLab which basically concatenates the yaml array with &&).
    It doesn't matter in this case but if I had to use the ssh agent in multiple steps then I would have to set the socket path (ssh-agent -a <socket path>) and $SSH_AUTH_SOCK explicitly.
  • The ways actions can be composed with uses is at a glance a lot nicer than Gitlab CI's mess of includes and triggers which can be very tricky to orchestrate.

Potential improvements

This is a personal project I only use very occasionally so I'm happy where I ended up, but the following could be implemented:

  • Separate job to build the pipeline image. Right now we reinstall all the build dependencies at runtime which is ugly, but good enough for very occasional use.
  • Scheduled secrets rotation (presumably using the ForgeJo REST API). Given the very limited value and scope of this project, limited permissions available to the created user, and the fact that my SSH server isn't exposed on internet, I have elected not to bother.
  • Proper use of the VCS
  • Merge pipelines
  • Deploying to multiple environments/subdomains, e.g. <branch name>.test.e4b4.eu
  • Linting (I've had a positive experience with markdownlint-cli2 in the past)

Conclusion

Now I can commit this file that I'm writing right now and it should pop up automatically on the website. How convenient!