stagit setup on OpenBSD

stagit is a static page generator for git repos. It’s very minimal - which I like.

In this post I’ll detail how I set up git.zakaria.org on my OpenBSD server.

The majority of my setup is based upon a post by poptart. Be sure to check out the original post here: https://hosakacorp.net/p/stagit-server.html. It’s pretty well done, and easy to follow (unlike my post here - which is really more for my sake when I inevitably bork my server again and have to re-do it all). poptart has some other great OpenBSD-related posts on there as well.

Setup

On the server install git, create the _git user, and make some directories:

server# pkg_add git libgit2
server# groupadd _git
server# mkdir -p -m 744 /var/git/repos /var/git/template
server# useradd -g _git -L daemon -c "git backend user" -d /var/git/repos -s /usr/local/bin/git-shell _git
server# chown _git:_git /var/git/repos

The directories we created:

The next steps are only if like me you’ve made source changes to stagit and you’d like to use your custom version. If you want to use plain stagit, install it on the server with pkg_add stagit.
On another machine, compile stagit and sftp it over to the server:

laptop$ make
laptop$ md5 stagit stagit-index
laptop$ # remember these checksums for verifying it's been copied over correctly
laptop$ sftp enderman <<EOF
> put stagit
> put stagit-index
> bye
EOF

Back on the server, verify the binaries were copied over correctly:

server# md5 stagit stagit-index
server# # verify the checksums

Copy these binaries to /usr/local/bin and set permissions appropriately:

server# cp stagit{,-index} /usr/local/bin
server# chown root:bin /usr/local/bin/stagit{,-index} 
server# chmod 755 /usr/local/bin/stagit{,-index}

The following is a message from the future:

I wrote this post a while ago. But I recall abandoning the modified stagit binary setup purely because the libraries on my laptop were newer than my server, and I could not get static compilation to work… So I gave up and installed stagit via pkg_add stagit on the server :P

Scripts

Next we’ll configure a few scripts:

Some internal variables will be shared between these scripts, for instance the git user, htdocs path, etc. Instead of writing these out into each script we can create a config file that each script will source before running anything.
In /var/git/config.rc:

# shared variables
GIT_HOME="/var/git"
GIT_REPOS="${GIT_HOME}/repos"
WWW_HOME="/var/www/htdocs/git.zakaria.org
CLONE_URI="_git@git.zakaria.org"
DEFAULT_OWNER="zakaria"
DEFAULT_DESC=""
GIT_USER="_git"

We restrict access to this file to root:wheel:

server# chown root:wheel /var/git/config.rc

After a git push we want the server to (re)generate the static html pages. To do this, we write a script in /var/git/template/post-receive:

#!/bin/sh
# Author: Cale "poptart" Black
# Modified by: zakaria @ zakaria.org
# License: MIT

set -euf

. /var/git/config.rc

export LC_TYPE="en_US.UTF-8"
src="$(pwd)"
name="$(basename "$src")"
dest="${WWW_HOME}/$(basename "$name" '.git')"
mkdir -p "$dest"
cd "$dest" || exit 1

echo "[stagit] building $dest"
/usr/local/bin/stagit "$src"

echo "[stagit] linking $dest"
# if a README.html exists use that as the index.html
# if not use log.html
if [ -f "README.html" ]; then
    ln -sf README.html index.html
else
    ln -sf log.html index.html
fi
ln -sf ../style.css style.css
ln -sf ../logo.png logo.png

Next, we need a script to generate the stagit /index.html. In /usr/local/bin/stagit-gen-index:

#!/bin/sh 
# Author: Cale "poptart" Black
# License: MIT

set -eu

. /var/git/config.rc
stagit-index "${GIT_REPOS}/*.git" > "${WWW_HOME}/index.html"

The next script will be used to setup a new repository. In /usr/local/bin/stagit-newrepo:

#!/bin/sh
# Author: Cale "poptart" Black
# Modified by: zakaria @ zakaria.org
# License: MIT
# Usage: stagit-newrepo <name> [desc] [author]

set -eu

. /var/git/config.rc

log() {
    printf '%s\n' "$*" >&2
}
die() {
    log "error: $*"
    printf 'exiting...\n'
    exit 1
}

REPO="$1"
DESC="${2:-$DEFAULT_DESC}"
OWNER="${3:-$DEFAULT_OWNER}"

if [ -z "$REPO" ]; then
    die "no repo name given"
fi

REPO_PATH="${GIT_REPOS}/${REPO}.git"

git init --bare "$REPO_PATH"
cp "${GIT_HOME}/template/post-receive" "${REPO_PATH}/hooks/post-receive"
echo "${CLONE_URI}/${REPO}.git" > "${REPO_PATH}/url"
echo "$OWNER" > "${REPO_PATH}/owner"
echo "$DESC" > "${REPO_PATH}/description"

chmod u+x "${REPO_PATH}/hooks/post-receive"
mkdir "${WWW_HOME}/${REPO}"
/usr/local/bin/stagit-gen-index

At the end of this script we call our stagit-gen-index helper script to regenerate the index.html.

The last script is a simple wrapper that will allow us to change a repo’s description:

#!/bin/sh
# Author: zakaria / zakaria.org
# License: MIT
# Usage: stagit-chdesc <repo> <new_description>

set -eu

. /var/git/config.rc

log() {
    printf '%s\n' "$*" >&2
}
die() {
    log "error: $*"
    printf 'exiting...\n'
    exit 1
}

REPO="$1"
DESC="$2"

if [ -z "$REPO" ]; then
    die "no repo name provided"
fi
if [ -z "$DESC" ]; then
    die "no new description provided"
fi

REPO_PATH="${GIT_REPOS}/${REPO}.git"

echo "$DESC" > ${REPO_PATH}/description

Then set the appropriate permissions and restrictions for these scripts:

$ chmod +x /usr/local/bin/stagit-{gen-index,newrepo,chdesc}
$ chown -R _git:_git /var/git/template
$ chmod +x $GIT_HOME/template
$ chown -R _git:_git $WWW_HOME

Replacing GIT_HOME and WWW_HOME with the variables we configured previously in config.rc.

Finally we configure our doas.conf(5) to allow our main user to run these commands:

permit nopass mite as _git cmd /usr/local/bin/stagit-newrepo
permit nopass mite as _git cmd /usr/local/bin/stagit-gen-index
permit nopass mite as _git cmd /usr/local/bin/stagit-chdesc

I’ve made a git repository for these scripts (and maybe a few more) available on GitHub which is mirrored on git.zakaria.org

httpd config

To actually serve the generated static files we need to configure httpd(8). In /etc/httpd.conf:

...snip...

server "git.zakaria.org" {
    listen on 127.0.0.1 on 8080

    # for letsencrypt
    location "/.well-known/acme-challenge/*" {
        root "/acme"
        request strip 2
    }

    root "/htdocs/git.zakaria.org"
}

...snip...

I use relayd(8) as a TLS proxy, so I don’t need to configure TLS certs in httpd.conf.