Samir Parikh / Blog


Originally published on 08 November 2019

Contents

Background

I've been thinking that I should start putting my blog posts on some type of version control system both to allow me to track changes and different versions as well as to provide another location to store them. (Right now, everything lives on either my local laptop or the virtual private server hosting this site.) I've used GitHub before and really like its interface and the fact that you find a fairly large community there. But I've also wanted to try to self host something if I could. I looked at local hosting options from both GitLab and Gitea but could never manage to make all the pieces work, especially on FreeBSD. I finally settled on an application called cgit which is the creation of Jason Donenfeld who is also the developer of the command-line password tool pass and the relatively new VPN tool Wireguard. cgit is a really fast and lightweight front-end for git repositories that is written in C. It also comes as a package for FreeBSD. It doesn't have many of the features of GitHub or GitLab such as an issue tracker, pull requests or wikis, but it serves as a nice way for me to browse any repositories and clone them back to another local machine.1

Let's get started on installing and configuring cgit on FreeBSD.

Prerequisites

I installed cgit on a separate VPS than the one serving this site. This way, if anything ever happened to the VPS hosting my site, my git repositories would not be impacted (at least in theory). Once I spun up the VPS, I went through the following posts from my site to prepare the VPS to host my repositories:

Configure DNS Zone

I also followed my post on how to Create Azure DNS Zone for my VPS but when creating a Record Set, I used the name "git" to reflect a new subdomain. This way, my git repositories would be accessible via git.example.com. I also used the IP address of the VPS hosting my repositories.

Create git User Account

All of the repos on the new VPS will have to owned by a user. I decided to create a separate user account for this. While it's probably best practice not to provide a shell for this user, I went ahead and provided one as it makes it easier to configure the git repo directories and to troubleshoot any issues.

$ sudo adduser
Username: git
Full name: git user account
Uid (Leave empty for default): 
Login group [git]: 
Login group is git. Invite git into other groups? []: 
Login class [default]: 
Shell (sh csh tcsh git-shell nologin) [sh]: 
Home directory [/home/git]: 
Home directory permissions (Leave empty for default): 
Use password-based authentication? [yes]: no
Lock out the account after creation? [no]: 
Username   : git
Password   : 
Full Name  : git user account
Uid        : 1003
Class      : 
Groups     : git 
Home       : /home/git
Home Mode  : 
Shell      : /bin/sh
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (git) to the user database.
Add another user? (yes/no): no
Goodbye!

Add SSH Keys to git User Account

Since we provided the git user with a shell, it only makes sense that we can log into that user via SSH.

$ sudo su git # change to git user
$ mkdir /home/git/.ssh # create the SSH directory
$ vi /home/git/.ssh/authorized_keys  create the authorized_keys file

You can also use something like ssh-copy-id(1). Once you've pasted in or copied over the public SSH keys into the authorized_keys file, you can test it out by trying to log in:

$ ssh -p 22 git@ip.address

Create Directory for git Repositories

Now we can create the directories where our git repositories will reside. Make sure you are logged in as the user git.

$ mkdir -p /home/git/repos/myFirstRepo
$ cd /home/git/repos/myFirstRepo/
$ git init --bare
Initialized empty Git repository in /usr/home/git/repos/myFirstRepo/

If you created the directories housing the git repos using another user, make sure that your permission and ownership attributes are set correctly using chmod(1) and chown(8).

Edit the description file as this is what will be displayed by cgit on the initial "Index" page.

$ vi description 

Log out and then see if you can push changes from local repository up to the hosted git repo on the FreeBSD virtual machine.

$ exit

Push Changes from Local Machine to Remote git Repository

This is an example of how you can create a git repository on your local machine and then push changes up to your remote git host that you just created. From the local client:

$ cd /path/to/local/directory
$ git init
Initialized empty Git repository in /path/to/local/directory

Add a README file which can be used in the "About" page of your repo on cgit letting people know what the repository is about.

If you need to, edit the .gitignore file. For example:

# ignore all files
*.*

# except the ones with the following extensions
!*.c
!*.cpp

Continue on with the traditional git commands to add the files to the repository and then push the commits to the remote host:

$ git add -A
$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached ..." to unstage)

        new file:   .gitignore
        new file:   README
        new file:   color.png
        new file:   dub.json
        new file:   dub.selections.json
        new file:   matplotlib
        new file:   simple.png
        new file:   source/app.d
        new file:   source/app.d.1

$ git commit -am "add new repo"
[master (root-commit) 0a5871c] add new repo
 9 files changed, 69 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 README
 create mode 100644 color.png
 create mode 100644 dub.json
 create mode 100644 dub.selections.json
 create mode 100755 matplotlib
 create mode 100644 simple.png
 create mode 100644 source/app.d
 create mode 100644 source/app.d.1
$ git status
On branch master
nothing to commit, working directory clean
$ git remote add origin ssh://git@ip.address:/usr/home/git/repos/myFirstRepo
$ git remote -v
origin  ssh://git@ip.address:/usr/home/git/repos/myFirstRepo (fetch)
origin  ssh://git@ip.address:/usr/home/git/repos/myFirstRepo (push)
$ git push -u origin master
Counting objects: 12, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (12/12), 633.93 KiB | 0 bytes/s, done.
Total 12 (delta 0), reused 0 (delta 0)
To ssh://git@ip.address:/usr/home/git/repos/myFirstRepo
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

Just to be sure, you can add additional files and then push them up to the remote repository:

$ touch post7.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add ..." to include in what will be committed)

        post7.md

nothing added to commit but untracked files present (use "git add" to track)
$ git add -A
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD ..." to unstage)

        new file:   post7.md

$ git commit -am "add post7.md"
[master 8a5e32d] add post7.md
 1 file changed, 1 insertion(+)
 create mode 100644 blog/post7.md
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working directory clean
$ git push
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 383 bytes | 0 bytes/s, done.
Total 4 (delta 1), reused 0 (delta 0)
To ssh://git@ip.address:/usr/home/git/repos/myFirstRepo
   8331a60..8a5e32d  master -> master
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

Install nginx, fcgiwrap and cgit

Now it's time to install nginx, fcgiwrap and cgit. Log back into the FreeBSD VM as the user with sudo privileges. The remaining steps reference Pascal Schmid's blog post.

$ sudo pkg install nginx fcgiwrap cgit

Enable nginx and fcgiwrap by adding the following lines in /etc/rc.conf:

nginx_enable="YES"
fcgiwrap_enable="YES"
fcgiwrap_user="www"
fcgiwrap_socket_owner="www"
fcgiwrap_socket_group="www"

Create an "About" page for the overall site via the file /usr/home/git/repos/about:

<br>This is a compilation of the git repositories that I own.
<br>
<br>This file is supposed to tell you more about them.
<br>
<br>But I have nothing more to write.
<br>
<br>Sorry.

Add your favicon.png file to /usr/local/www/cgit.

There are two main configuration files that need to be defined: /usr/local/etc/cgitrc and /usr/local/etc/nginx/nginx.conf.

Here is my /usr/local/etc/cgitrc. See the cgit man page for more details on how to configure the various options in this file.

css=/cgit.css
logo=/cgit.png

# Add a cgit favicon
favicon=/favicon.png

robots=noindex, nofollow

virtual-root=/

root-title=My cgit Repository
root-desc=wealth of useless information

clone-url=git://git.example.com

enable-index-links=1
enable-log-filecount=1
enable-log-linecount=1
enable-commit-graph=1
enable-remote-branches=1

snapshots=tar.gz tar.bz
max-stats=quarter
#root-readme=/usr/local/www/cgit/about.htm
root-readme=/usr/home/git/repos/about

# Show owner on index page
enable-index-owner=0

##
## Search for these files in the root of the default branch of repositories
## for coming up with the about page:
##
readme=:README.md
readme=:readme.md
readme=:README.mkd
readme=:readme.mkd
readme=:README.rst
readme=:readme.rst
readme=:README.html
readme=:readme.html
readme=:README.htm
readme=:readme.htm
readme=:README.txt
readme=:readme.txt
readme=:README
readme=:readme
readme=:INSTALL.md
readme=:install.md
readme=:INSTALL.mkd
readme=:install.mkd
readme=:INSTALL.rst
readme=:install.rst
readme=:INSTALL.html
readme=:install.html
readme=:INSTALL.htm
readme=:install.htm
readme=:INSTALL.txt
readme=:install.txt
readme=:INSTALL
readme=:install

scan-path=/usr/home/git/repos

Here is my /usr/local/etc/nginx/nginx.conf. Don't forget to update the server_name.

user  www;
worker_processes  4;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    error_log /var/log/nginx.error.log error;
    sendfile        on;
    keepalive_timeout  70;

    server {
        listen       80;
        #server_name  localhost;
        server_name  git.example.com;

        index cgit.cgi;
        access_log  /var/log/git.access.log;
        root /usr/local/www/cgit;
        try_files       $uri @cgit;

        # Require auth for requests sent to cgit that originated in location /
        location @cgit {
            # $document_root is now set properly, and you don't need to override it
            index cgit.cgi;
            fastcgi_param   SCRIPT_FILENAME $document_root/cgit.cgi;
            fastcgi_param   GIT_HTTP_EXPORT_ALL "";
            fastcgi_param   PATH_INFO       $uri;
            fastcgi_param   QUERY_STRING    $args;
            fastcgi_param   HTTP_HOST       $server_name;
            fastcgi_pass    unix:/var/run/fcgiwrap/fcgiwrap.sock;
            include fastcgi_params;

            gzip off;
            #rewrite ^ https://$server_name$request_uri permanent;
            rewrite ^/([^/]+/.*)?$ /cgit.cgi?url=$1 break;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;
        }
    }
}

Once you've updated the cgit and nginx configuration files, it's time to restart the services:

$ sudo service fcgiwrap start
$ sudo service nginx start

Final Steps

The last step is to provision an SSL certificate to enable HTTPS to the remote git host.


  1. I would be remiss if I did not also mention Drew DeVault's sourcehut which, at the time of this writing, is a relatively new set of open source software development tools which also includes the ability to host git repositories. It's currently in the alpha stage but is worth keeping an eye on.