With my preferred Git repository hosting provider preparing to introduce some changes that don't cohere with my philosophical ideals, I finally decided to make the move to Fossil. Git has never been favoured but its ubiquity made it hard to ignore. The OpenBSD development of got was more than a desireable improvement but now that I needed to self-host, there was no longer an obstacle to using Fossil that could be avoided by staying with Git—and so I switched. Fossil is super simple, clean, and consistent—making source code management and version control easy. In the same vein, installation and setup is just as painless. On that note, the following presumes an OpenBSD 6.7 installation.

Install Fossil

Use pkg_add to install Fossil; select the statically linked binary.

bsdbox# pkg_add fossil
quirks-3.325 signed on 2020-06-12T06:24:53Z
Ambiguous: choose package for fossil
        0: <None>
        1: fossil-2.10v0
        2: fossil-2.10v0-static
Your choice: 2
fossil-2.10v0-static: ok

Because it's installed into the chroot, create a symbolic link of the fossil executable into /usr/local/bin (for using locally on the server; this can be avoided otherwise).

bsdbox# ln -s /var/www/bin/fossil /usr/local/bin/fossil

Create the file /var/www/cgi-bin/scm with the following contents to make the CGI script that httpd will execute in response to cvs.domain.tld requests; all paths are relative to the /var/www chroot.

directory: /htdocs/cvs.domain.tld
notfound: https://domain.tld
errorlog: /logs/fossil.log

The directory directive instructs Fossil to serve all repositories found in /var/www/htdocs/cvs.domain.tld while errorlog sets logging to be saved to /var/www/logs/fossil.log; create the repository directory and log file—making the latter owned by the www user, and the script executable.

bsdbox# mkdir /var/www/htdocs/cvs.domain.tld
bsdbox# touch /var/www/logs/fossil.log
bsdbox# chown www /var/www/logs/fossil.log
bsdbox# chmod 755 /var/www/cgi-bin/scm
Fossil repository list

Setup chroot

Fossil needs both /dev/random and /dev/null, which aren't accessible from within the chroot, so need to be constructed; /var, however, is mounted with the nodev option. Rather than removing this default setting, we'll build a small memory filesystem and then mount it on to /var/www/dev with mount_mfs(8) so that the random and null device files can be created. In order to avoid neccessitating a startup script to recreate the device files at boot, we'll create a template of the needed /dev tree to automatically populate the memory filesystem.

First, to make the mountable memory filesystem permanent, add the following line to /etc/fstab as a privileged user.

swap /var/www/dev mfs rw,-s=1048576,-P=/template/dev 0 0

Then execute the following commands.

bsdbox# mkdir /var/www/dev
bsdbox# install -d -g daemon /template/dev
bsdbox# cd /template/dev
bsdbox# /dev/MAKEDEV urandom
bsdbox# mknod -m 666 null c 2 2
bsdbox# mount /var/www/dev
bsdbox# ls -l /var/www/dev
total 0
crw-rw-rw-  1 root  daemon    2,   2 Jun 20 08:56 null
lrwxr-xr-x  1 root  daemon         7 Jun 18 06:30 random@ -> urandom
crw-r--r--  1 root  wheel    45,   0 Jun 18 06:30 urandom

In this installation, we're going to grant repository directory ownership to the user who will push to, pull from, and create repositories. In addition, the same user that executes the fossil binary must have writable access to the repository directory specified above in the CGI script, which on OpenBSD is www.

bsdbox# chown -R user:www /var/www/htdocs/cvs.domain.tld
bsdbox# chmod 775 /var/www/htdocs/cvs.domain.tld

Configure httpd

Enable and start slowcgi—OpenBSD's FastCGI wrapper server that executes CGI scripts.

bsdbox# rcctl enable slowcgi
bsdbox# rcctl start slowcgi

Edit /etc/httpd.conf to setup the server, which will automatically redirect standard http requests to https; apart from Let's Encrypt challenges issued in response to acme-client certificate requests.

server "cvs.domain.tld" {
        listen on * port http
        root "/htdocs/cvs.domain.tld"
        location "/.well-known/acme-challenge/*" {
                 root "/acme"
                 request strip 2
        location * {
                 block return 301 "https://$HTTP_HOST$REQUEST_URI"
        location  "/*" {
                 fastcgi { param SCRIPT_FILENAME "/cgi-bin/scm" }

server "cvs.domain.tld" {
        listen on * tls port https
        root "/htdocs/cvs.domain.tld"
        tls {
                 certificate "/etc/ssl/domain.tld.fullchain.pem"
                 key "/etc/ssl/private/domain.tld.key"
        hsts {
                 max-age 15768000
        connection max request body 104857600
        directory index "index.cgi"
        location  "/*" {
                 fastcgi { param SCRIPT_FILENAME "/cgi-bin/scm" }
        location "/.well-known/acme-challenge/*" {
                 root "/acme"
                 request strip 2
Before starting httpd, ensure you have a zone record for the subdomain with your registrar or nameserver. Then follow this guide to get a free HTTPS certificate from Let's Encrypt using acme-client(1), ignoring the httpd.conf configuration, which has already been done here.

Once the certificates and key are in place, confirm the syntax of httpd.conf before restarting the server.

bsdbox# httpd -vnf /etc/httpd.conf
configuration OK
bsdbox# rcctl restart httpd

Client Setup

To facilitate creating new repositories and pushing them to the server, add the following function to your ~/.cshrc or ~/.zprofile or the config file for whichever shell you are using on your development box.

finit() {
        fossil init $1.fossil && \
        chmod 664 $1.fossil && \
        fossil open $1.fossil && \
        fossil user password $USER $PASSWD && \
        fossil remote-url https://$USER:$PASSWD@cvs.domain.tld/$1 && \
        rsync --perms $1.fossil $USER@cvs.domain.tld:/var/www/htdocs/cvs.domain.tld/ >/dev/null && \
        chmod 644 $1.fossil && \
        fossil ui

Now a new repository can be made with finit repo, which will create the fossil repository file repo.fossil in the current directory; by default, the repository user is set to the environment variable $USER. It then opens the repository and sets the user password to the $PASSWD environment variable (which you can either set with export PASSWD 'password' on the command line or add to a secured shell environment file), and the remote-url to https://cvs.domain.tld/repo with the credentials of $USER who is authenticated with $PASSWD. Finally it rsync's the file to the server before opening up the local repository in your browser where you can adjust settings such as anonymous user access and set other repository details. Thereafter, you can add files with fossil add, and commit with fossil ci -m 'commit message' where Fossil, by default, will push to the remote-url server. It's suggested you read the Fossil documentation; with a sane and consistent development model, the system is much more efficient and cohesive than git so the learning curve is not steep at all.

Fossil netcalc repository


comments powered by Disqus