Contents

stagit on OpenBSD

⊕ 2020-05-10

I have a lot of personal projects that I work on in a semi-distributed fashion and I like to keep development of all my projects on a locally hosted git server over SSH. Unfortunately this is not the best for browsing through projects or quickly visually showing updates. So I started hosting an internal service on my git server using stagit(1) which will turn a git repo into a set of static HTML resources.

In the end it looks something like this:

And actually viewing a repository:

I'll be using nginx for serving the content and git hooks for generating the static HTML. Theoretically the only part of this that will need a user interaction apart from git interaction is the initial creation of a new repository. This is done using some doas(1) trickery and helps keep everything done by the git user done in a safer non-shell spawning fashion.

Setting up

The first steps are to install git and nginx from the OpenBSD packages and then create a new _git user that will be our local user for managing the git and stagit functionality. (This name can be changed to something more like the traditional git user, but I wanted to keep with the underscore for service user tradition on OpenBSD):

1pkg_add stagit nginx git
2groupadd -g 998 _git
3mkdir -p -m 744 /var/git/repos /var/git/template
4useradd -u 998 -g 998 -L daemon -c "git backend user" -d /var/git/repos -s /usr/local/bin/git-shell _git
5chown _git:_git /var/git/repos

Next set up a pseudo-config file that will be imported by all the automation scripts on this deployment, in this example these are just shell script variables. Change these to match your needs. This file will hold the default git home directory, web server directory, clone URI, default owner, and default description. Place the following in /var/git/config.rc:

1GIT_HOME="/var/git/repos"
2WWW_HOME="/var/www/htdocs/git"
3CLONE_URI="_git@git.hosakacorp.net"
4DEFAULT_OWNER="poptart"
5DEFAULT_DESCRIPTION="default description"
6GIT_USER="_git"

Restrict access to this to prevent the git user from being the owner of the configuration:

1chown root:wheel /var/git/config.rc

Now we will set up the post-receive hook for git, this server will run the stagit generators after the server receives a push. Simply this pulls our configuration file, changes directories to the web server directory for the current repository, and then generates and links the relevant created files. This should be placed in /var/git/repos/template/post-receive:

 1#!/bin/sh
 2# Author: Cale "poptart" Black
 3# License: MIT
 4
 5set -euf
 6
 7. /var/git/config.rc
 8
 9export LC_CTYPE='en_US.UTF-8'
10src="$(pwd)"
11name=$(basename "$src")
12dst="$WWW_HOME/$(basename "$name" '.git')"
13mkdir -p "$dst"
14cd "$dst" || exit 1
15
16echo "[stagit] building $dst"
17/usr/local/bin/stagit "$src"
18
19echo "[stagit] linking $dst"
20ln -sf log.html index.html
21ln -sf ../style.css style.css
22ln -sf ../logo.png logo.png

The hook is just a shell script so make sure it is marked executable:

1chmod +x /var/git/template/post-recieve

Next I decided to split up the logic for the main index generating file into a separate invocable executable that can also be used in the other scripts /usr/local/bin/stagit-gen-index:

1#!/bin/sh 
2# Author: Cale "poptart" Black
3# License: MIT
4
5set -eu
6
7. /var/git/config.rc
8stagit-index "$GIT_HOME"/*.git > "$WWW_HOME/index.html"

The last shell script we need to write is for automating the creating on a new git repository on the system. This reads in a title and description for the new repo and then initializes the new repository in an empty and bares state, sets up the git hooks, and applies our defined defaults. The following was created in /usr/local/bin/stagit-newrepo:

 1#!/bin/sh
 2# Author: Cale "poptart" Black
 3# License: MIT
 4
 5set -eu
 6
 7. /var/git/config.rc
 8
 9e_log() {
10    printf '%s\n' "$*"
11}
12
13e_err() {
14    printf '%s\n' "$*" >&2
15}
16
17e_exit() {
18    e_err "$*"
19    exit 1
20}
21
22DESC=""
23REPO=""
24
25if [ $# -gt 1 ]; then
26        DESC="$2"
27else
28        DESC="$DEFAULT_DESCRIPTION"
29fi
30
31if [ $# -eq 0 ]; then
32        e_exit "not enough args"
33else
34        REPO="$(basename "$1")"
35fi
36
37git init --bare "$GIT_HOME/$REPO.git"
38cp "$GIT_HOME/template/post-receive" "$GIT_HOME/$REPO.git/hooks/post-receive"
39echo "$CLONE_URI/$REPO.git" > "$GIT_HOME/$REPO.git/url"
40echo "$DEFAULT_OWNER" > "$GIT_HOME/$REPO.git/owner"
41if [ -n "$DESC" ]; then
42        echo "$DESC" > "$GIT_HOME/$REPO.git/description"
43else
44        echo "this is a placeholder" > "$GIT_HOME/$REPO.git/description"
45 fi
46chmod u+x "$GIT_HOME/$REPO.git/hooks/post-receive"
47mkdir "$WWW_HOME/$REPO"
48/usr/local/bin/stagit-gen-index

Then clean up and mark all of our files with the appropriate permissions and restrictions:

1chmod +x /usr/local/bin/stagit-newrepo
2chmod +x $GIT_HOME/template
3chown _git:_git /var/www/htdocs/git

Now in order to keep everything tidy I allow anyone in the gitadmin group or my personal account to invoke the scripts that we wrote as the _git user:

/etc/doas.conf

1permit nopass poptart as _git cmd /usr/local/bin/stagit-newrepo
2permit nopass poptart as _git cmd /usr/local/bin/stagit-gen-index
3permit nopass :gitadmins as _git cmd /usr/local/bin/stagit-newrepo
4permit nopass :gitadmins as _git cmd /usr/local/bin/stagit-gen-index

The nginx(1) configuration for this is incredibly barebones and essentially is just changing the root to where I had it configured in the config.rc file or wherever you have it configured. I added additional TLS/SSL config blocks for good practice reasons:

/etc/nginx/nginx.conf

 1server {
 2    listen       443;
 3    server_name  git.hosakacorp.net;
 4    root         /var/www/htdocs/git;
 5
 6    ssl                  on;
 7    ssl_certificate      /etc/ssl/git.hosakacorp.net-fullchain.pem;
 8    ssl_certificate_key  /etc/ssl/private/git.hosakacorp.net-key.pem;
 9    ssl_password_file    /etc/ssl/private/git-key-password
10
11    ssl_session_timeout  5m;
12    ssl_session_cache    shared:SSL:1m;
13
14    ssl_ciphers  HIGH:!aNULL:!MD5:!RC4;
15    ssl_prefer_server_ciphers   on;
16}
17
18...snip...

Give the nginx server a simple rcctl enable nginx and start nginx to get things started and we can get back to the stagit configuration.

Now in order to see an example we can generate an empty index file and then create a new test repo with the description test description:

1doas -u _git /usr/local/bin/stagit-gen-index
2doas -u _git /usr/local/bin/stagit-newrepo test "test description"

NOTE: The repository will not have the test git repo until the bare repository has at least one commit to generate off of.

Now we can clone the repo and start using it like normal:

1git clone _git@git.hosakacorp.net:test.git

When you push commits your commits will look like similar to the following and be accessible from the web server:

 1$ git push -f
 2Enumerating objects: 12, done.
 3Counting objects: 100% (12/12), done.
 4Delta compression using up to 4 threads
 5Compressing objects: 100% (4/4), done.
 6Writing objects: 100% (12/12), 1.57 KiB | 61.00 KiB/s, done.
 7Total 12 (delta 0), reused 0 (delta 0)
 8remote: [stagit] building /var/www/htdocs/git/test
 9remote: [stagit] linking /var/www/htdocs/git/test
10To git.hosakacorp.net:test.git
11 * [new branch]      master -> master