Serving Mercurial on OpenBSD with Gunicorn

One and other thing lead me to hosting my mercurial repositories on an OpenBSD VPS.

Here’s a bit of memo on how I did it.

A dedicated user needs to be created. I call it hg which is as generic as it can be. Then I create home directory of /home/hg, and set its $HOME to /home/hg/repos. Wait.

It’s so I can just push to ssh://hg@hg.myconan.net/reponame and not having to specify additional namespace. The /home/hg itself needs to contain some other files so that’s just how it ended up. I can probably put the extra files somewhere else but it seems simpler to have them all in single directory tree. Now I write it maybe I should’ve made it at /var/hg/root or something like that.

Well, it’s done deal.

I also made ~hg/.ssh/authorized_keys file and fill it with my key. Again, so I can push to it.

With that done, next is installing the required packages:

  • py3-gunicorn
  • supervisor
  • mercurial
  • nginx
  • certbot

Refer to this post on configuring the certbot. It worked so well and requires barely any maintenance so far.

As for gunicorn, I made /home/hg/hgweb directory which contains following files:

  • gunicorn.conf.py
  • hgweb.config
  • hgweb.py

Gunicorn config is pretty simple:

bind = 'unix:/home/hg/gunicorn.sock'
workers = 4
accesslog = '-'
errorlog = '-'
timeout = 30

Nothing fancy, and there’s no worker_class because none of the supported workers (apart of sync) seem to be supported under OpenBSD. Should be fine as it’s just for my personal use.

As for hgweb.py, it’s copied from /usr/local/share/mercurial/hgweb.cgi with config path adjusted to local hgweb.config and removed references to wsgicgi (import and .launch) as I’m using Gunicorn, not CGI.

hgweb.config itself on the other hand, it’s also pretty basic:

[paths]
/ = /home/hg/repos/*

[web]
baseurl = https://hg.myconan.net/
contact = nanaya
staticurl = /static

All those done, last part to start serving with Gunicorn is updating /etc/supervisord.conf. There’s an example in their official docs and I made some adjustments:

[program:hg]
command=/usr/local/bin/gunicorn --config=/home/hg/hgweb/gunicorn.conf.py hgweb:application
user=hg
directory=/home/hg/hgweb
stopsignal=INT
environment=PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
stdout_logfile=/var/log/supervisord/%(program_name)s-%(process_num)s.stdout.log
stderr_logfile=/var/log/supervisord/%(program_name)s-%(process_num)s.stderr.log

Mainly for having non-random log file path.

Create log directory with mkdir -p /var/log/supervisord, enable the service with rcctl enable supervisord, and hope it works.

Oh and chown hg:www /home/hg && chmod 710 /home/hg for basic file permissions. Oh and hg:hg owner and 700 permission for repos directory itself.

And lastly nginx:

server {
    listen 443;
    listen [::]:443;
    server_name hg.myconan.net;

    access_log /var/log/nginx/hg.myconan.net-access.log;
    error_log /var/log/nginx/hg.myconan.net-error.log;

    ssl_certificate certs/hg.myconan.net/fullchain.pem;
    ssl_certificate_key certs/hg.myconan.net/privkey.pem;
    ssl_trusted_certificate certs/hg.myconan.net/chain.pem;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    root /nonexistent;

    location = /favicon.ico {
        return 204;
    }

    location = /robots.txt {
        return 204;
    }

    location / {
        proxy_pass http://unix:/home/hg/tmp/gunicorn.sock;
        proxy_set_header Client-Ip $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Port $remote_port;
        proxy_set_header X-Forwarded-Proto $scheme;

        limit_except GET HEAD {
            deny all;
        }
    }

    location /static/ {
        root /usr/local/lib/python3.8/site-packages/mercurial/templates;
    }
}

Nothing fancy either, just a basic https proxying setup with no write permission as I don’t want to setup http auth and only push using ssh.

/static/ directory is served directly to the installation’s templates directory. Subdirectory name already matches so no alias or symlink is needed.

rcctl enable nginx and don’t forget to rotate the log files by adding the two specified files to /etc/newsyslog.conf.

…that’s kinda long.

Moebooru rebased

Since I forgot to branch the original source, the branching looked awesomely crappy. Therefore I decided to rebase entire thing to ease up keeping track with Moebooru “mainline”. All my commits are now in branch “default”. If you didn’t do any change, backout up to revision 9174b6b5b02d and then pull again. And then, don’t ever touch moe branch again anymore.