My Profile Photo

AndrewCz


Using liberty-minded opensource tools, and using them well


Gitlist - A minimalistic Git Repo Viewer




I got tired of the headache that is gitlab. For one person, or one organization, it's helluva overkill. So I was looking around for a non-90's frontend, and found Gitlist.


Clone the code. Here's the repo.

There’s not really much to say about this. Gitlist, of which mine is accessible here is just a minimal web service. The thing that took me the longest was setting up the CSS and colors, etc. Getting the theming down.

There was one bit I needed to change, though, to get it to work with my setup. I needed to make sure that the right subdirectory would be receptive to incoming traffic at /gitlist/. This was done using Apache’s mod_rewrite. I needed to make sure that Allow Overwrite was yes and that RewriteBase was /gitlist/. Other than that, there was really nothing strenuous about the install. However, that’s just installing the thing. There’s a bit more functionality that I would like from the software before I am comfortable just using it.

Theme

To successfully compile the theme, the mixinx.less file has to be changed, and a couple definitions added at the bottom.

Restarting

Every time you make a change somewhere, you have to make sure the cache gets flushed.

# rm -rf /srv/httpd/gitlist/cache/* && systemctl restart httpd

Javascript

I don’t wanna sound like a fool here, but I found out you can’t iterate over div id’s. So I made the class .md-view render markdown like the id #md-content did.

Git on the Server - Smart HTTP

This is dealing with the Apache setup, in relation to the gitlist application. Gitlist is working very similarly to gitweb, the default, but much less pretty web front-end. They both rewrite the URLs in order to facilitate pretty URLs. This makes cloning a bit more difficult, but not impossible.

Env Vars

There are a couple environment variables that git will need in order to function correctly.

  • GIT_PROJECT_ROOT </path/to/repos>
    • Root directory (not URL) containing all (public) repos
  • GIT_HTTP_EXPORT_ALL
    • Allows the server to process any pull/fetch/clone request
  • REMOTE_USER=$REDIRECT_REMOTE_USER
    • Enables the service receive-pack, so that the server can receive git pushes.

Catching git commands

If I had a location on the webserver that was just going to serve git repos, then I wouldn’t have to mess around with regex’s. All I would have to do would be to put a ScriptAlias directive matching up with the subdirectory calling /usr/libexec/git-core. However, since we are using a web front-end, this is not the case.

Luckily enough, there is a known regex that catches all of the possible git commands, and lets everything else pass through to the web front-end. In order to redirect all git commands to the appropriate script, we have to set up the following directive.

ScriptAliasMatch "^/gitlist/(.*/(HEAD|\
    info/refs|\
    objects/(info/[^/]+|\
    [0-9a-f]{2}/[0-9a-f]{38}|\
    pack/pack-[0-9a-f]{40}\.(pack|\
    idx))|git-(upload|receive)-pack))$" \
    /usr/libexec/git-core/git-http-backend/$1

ExecCGI

This will catch the commands, but it won’t do anything with them, unless they are allowed to execute! That is done by setting the +ExecCGI directive. This allows scripts that are called to be executed. Keep in mind this directive is for the script directory, not the repository directory.

<Directory "/usr/libexec/git-core">
    Options +ExecCGI +FollowSymlinks
    Require all granted
</Directory>

Even More Permissions

Also, don’t forget that the repo directory has to be accessible by the webserver as well. The gitlist application does a correct redirect typically, so there shouldn’t be much need to do any corrections, unless the permissions on the directory are somehow outta wack. There are also some SELinux considerations, but I’ll get to those once I get around to dedicating myself to learning that hunk o’ junk.

# chown -R apache:apache /srv/repos

And the Apache config:

<Directory "/srv/repos">
    Require all granted
</Directory>

This is only for testing - there are plenty of ways to do authentication for pushing and pulling to git repos through Apache, but right now we just want it to work.

Permissions for Prod

Update your man page Linus!

Prior to Apache 2.4, the way that anonymous web app views and git pull/fetch/clone commands were married with authenticated git pushes was through abusing mod_rewrite and Apache environment variables.

Even more flexibility is available through the mod_rewrite’s RewriteRule which uses the [E=…] option to set environment variables. –Apache Docs

The git http-backend man page references the following as a “working example”, which is false advertising, as you’ll see below.

RewriteCond %{QUERY_STRING} service=git-receive-pack [OR]
RewriteCond %{REQUEST_URI} /git-receive-pack$
RewriteRule ^/gitlist/ - [E=AUTHREQUIRED:yes]
<LocationMatch "^/git/">
    Order Deny,Allow
    Deny from env=AUTHREQUIRED

    AuthType Basic
    AuthName "Git Access"
    Require group committers
    Satisfy Any
    ...
</LocationMatch>

I wouldn’t care so much if it wasn’t the case that Apache 2.4 breaks backwards compatibility with these Order Deny,Allow statements. So we gotta find another way to do this.

Enter Sandman

The directive, added in 2.4, replaces many things that mod_rewrite has traditionally been used to do, and you should probably look there first before resorting to mod_rewrite. --[Apache Docs](https://httpd.apache.org/docs/2.4/howto/access.html)

However, recently, Apache has added support for if-statements. This is even more powerful when you consider that these same if-statements can use the built-in expression processing. So, above, we can see that previously we were testing to find out if service=git-receive-pack was in the QUERY_STRING environment variable, or if /git-receive-pack$ was the end of the REQUEST_URI. If one of those was true, then the environment variable AUTHREQUIRED would be set. Next, we needed to go into a LocationMatch that matched literally all of the URLs and we had to test for the environment variable. This is quite unwieldy and nigh unreadable.

Now, with the if-statements, all we have to do is test the build-in special variables in the main configuration file in one simple <If> block:

<If "%{QUERY_STRING} == 'service=git-receive-pack' || %{REQUEST_URI} == '/git-receive-pack$'" >
    AuthType Digest
    AuthName "Git Access"
    AuthUserFile /srv/repos/.htpasswd
    Require valid-user
</If>

This means that it someone is trying to push to the repo, then they have to authenticate.

I’ll encrypt my passphrases, thank you very much

Ansible only has a htpasswd module, but htpasswd doesn’t encrypt passphrases. In fact, there are two AuthType types for Apache.

  • Basic
  • Digest

Digest is very similar to Basic, but uses different ciphers to…ya know what? Just read the RFC.

The pertinent detail is that I can set both in a text file. Since I’m using digest though, the format is:

<user>:<realm>:$(md5sum '<user>:<realm>:<passphrase>')

I am able to make a template with ansible to populate this with as many users as I want, but I’d love to see it be automated.

If that’s not your cup of tea, you can always do it from the command line using htdigest.

Federated PR’s

I’ll probably use email or something. But for the time being, I’m just happy that I can use git over https.

Creating a bare repo

I leaned about bare repositories during my struggle to set this up. Basically, it’s a type of repo that’s not a “working repo”. If I set up all of the repos on my server with a plain git init, or even just copied my working repos there using scp (which I totally didn’t try to do at first), I would never be able to push to master, and I’m sure there would be a dozen other things that would be wrong about it. However, the right way to go about it is to first set up a bare repo:

# git init --bare <repo-name>.git

They are typically named in that style, but it’s not wrong necessarily to deviate from that, it’s just against convention. Anyways, once those are set up, they are still not quite ready for regular use. They first have to be initialized.

Avoiding the HEAD error

I initially got an error regarding bad default revision 'HEAD'. This is normal for bare repositories. It happens if there is no branch that HEAD is supposed to track. HEAD is initially set to master, however, master is not created. It has to be pushed to the bare repo. So, a client can create a new working repo, or use an existing repo, and add the server’s repository as as remote:

# git remote add <remote-name> <repo-url>

Then, it can push to that repository, and assuming that push pushes the branch that the bare repo is looking for (master) there should be no more errors.


References: