How to publish your personal Git repositories on web

2020-04-03

(last time edited: 2021-09-01)

tags: git, services

Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

Notice: I highly recommend using a simple git server and stagit + NGINX. If you are a bad enough dude to go very minimal ignore the OPTIONAL items!

Create a Git server

Install Git in your server.

Create a Git user with git-shell as default shell to limit its operability.

# useradd -md /srv/git -s /usr/bin/git-shell git

or

# adduser -h /srv/git -s /usr/bin/git-shell git

And disabling password login with an impossible password (*). It is better than locking the account with (!) an exclamation point. This is explained here.

The account will not be locked, the password login will be disabled and the key SSH login will enabled.

You can see the changes of git user in the /etc/shadow file. A * will appear next to the username.

It is absolutely NOT necessary to enable PAM settings in /etc/ssh/sshd_config from server side.

# usermod -p "*" git

If you really want more security and disable interactive logins, you can create a directory in git's home dir.

# su - git -s /bin/sh -c "mkdir git-shell-commands"

Then create a file declaring interactive logins to be disabled.

# su - git -s /bin/sh -c "touch ~/git-shell-commands/no-interactive-login"

Now add shell commands to that file with your favorite editor.

# vim /srv/git/git-shell-commands/no-interactive-login

#!/bin/sh
echo "what the hell are you trying to do?"

Give execution permissions to the file.

# chmod +x /srv/git/git-shell-commands/no-interactive-login

Create a SSH directory. Keep in mind we are calling /bin/sh because git's default shell is git-shell.

# su - git -s /bin/sh -c "mkdir -m 700 .ssh"

Create the file which contains SSH authorized keys.

# su - git -s /bin/sh -c "touch ~/.ssh/authorized_keys"

Change permissions for authorized_keys file.

# chmod 600 /srv/git/.ssh/authorized_keys

In your personal workstation. If you don't have SSH keys I recommend you to create Ed25519 keys.

$ ssh-keygen -t ed25519 -f keyname -C ""

Copy and paste the contents of your personal SSH public key (usually saved at ~/id_ed25519.pub) to the /srv/git/.ssh/authorized_keys file. Example:

ssh-ed25519 AAAAC3NzaC1lZg42950gm45gg24oprm......knW6Q3VtFfaasf2gWgs12d4 your@email.com

In your server create an empty git repository.

# su - git -s /bin/sh -c "git init --bare your_repository.git"

Create a Gitolite server (OPTIONAL)

Gitolite allows you to setup git hosting on a central server, with fine-grained access control and many more powerful features.

If you need a Git server for multiple users and other specific reasons Gitolite is your best choice and completely skip the first step.

Install Gitolite in your server.

In your personal workstation. If you don't have SSH keys I recommend you to create Ed25519 keys.

$ ssh-keygen -t ed25519 -f keyname -C ""

In your server create a Git user account.

# useradd -md /srv/git git

Disable password login for the git account.

# usermod -p "*" git

From your workstation send the public key to the server.

$ scp /path/to/keyname.pub root@your_domain.com:/tmp

In your server make Gitolite install your SSH public key.

# su - git -s /bin/sh -c "gitolite setup -pk /tmp/*.pub"

Remove the pub key from the tmp directory.

# rm /tmp/*.pub

In your workstation clone the gitolite-admin Git repository in order to administrate Gitolite.

$ git clone git@your_domain.com:gitolite-admin

Use your favorite text editor and open the file gitolite.conf

$ vim /path/to/gitolite-admin/conf/gitolite.conf

You can create empty Git repositories, delete repositories and manage users from that file. It looks something like this:

repo gitolite-admin
RW+ = your_username

repo testing
RW+ = @all

repo your_repository
RW+ = your_username

repo your_repository2
RW+ = your_username

Make commit of what you just changed.

$ git -C /path/to/gitolite-admin commit -am "Setting up a new custom repository."

Push the changes to the server. Gitolite will read all changes.

$ git -C /path/to/gitolite-admin push

Creating and pushing a repository to the server

In your workstation initialize a Git repository.

$ git init your_repository

We will add the remote for this new repository pointing to the server.

$ git -C /path/to/your_repository remote add origin git@your_domain.com:your_repository.git

Add some files to your new repository.

$ echo hello world > /path/to/your_repository/testfile

Add those files to the git.

$ git -C /path/to/dotfiles add /path/to/your_repository/testfile

Create a commit.

$ git -C /path/to/dotfiles commit -am "This is my first commit."

We will use SSH to upload the commits. We need the remote server (hostname), the user we will connect via SSH at, and the identity file which is the private key we created before.

Check that your ~/.ssh/config file is configured correctly.

Usually looks something like this.

Host ssh.gitgud.io
    User git
    Hostname ssh.gitgud.io
    IdentityFile /path/to/id_ed25519

Push the files to the server.

$ git -C /path/to/dotfiles push --set-upstream origin master

Newer versions of Git will use main as default branch.

In your workstation if you wanna recheck your remote origin url.

$ git remote get-url origin

Install and automate stagit

stagit is a static page generator for git.

In comparison with Cgit, stagit will not refresh your repositories online automatically when you push changes to a git repository in your server. You will have to create a simple script in order to automate this.

Install stagit in your server.

Create an specific dir for stagit. It will be created with 755 permissions by default, which is fine.

# mkdir /srv/git/stagit

Add permissions so NGINX can read all files we are going to put inside. If you take a look, git will be the owner of the dir, and nginx will be the group. Meaning that all users inside the nginx group can read the files, including the owner.

# chown git:nginx /srv/git/stagit

Do NOT run the following POSIX shell script as root. Run it as the git user you've created before.

#!/bin/sh

for DIR in $(ls | grep \.git | cut -f 1 -d .); do

    mkdir -p /srv/git/stagit/$DIR # Creates a directory for every repository found in git's $HOME

    cd /srv/git/stagit/$DIR # Goes to a created repo html dir
    stagit ~/$DIR.git # Runs stagit on every git repo and adds it to stagit as html

    echo "https://YOUR_HOST/$DIR.git" > ~/$DIR.git/url # Adds a git clone url
    echo your_name > ~/$DIR.git/owner # Adds owner
    echo $DIR > ~/$DIR.git/description # Adds a description

done

stagit-index ~/*.git > /srv/git/stagit/index.html # Creates an index based on every repo found in git's $HOME

You can run it from root with the following command:

# su - git -s /bin/sh -c "sh script.sh"

It is recommendable to run the script manually everytime you wanna see the changes online. This is better for security and uses less workload in comparison to Cgit. You can also use cron jobs to run the script at a certain time. There are multiple cron forks and implementations to choose. Or you can also use a git post-receive hook.

You can find more scripts for automatic generation of html files and .tar.gz files here.

Install NGINX.

Now create a custom configuration in /etc/nginx/conf.d

# vi /etc/nginx/conf.d/yourconf.conf

The file should look something like this if you wanna serve it in a subdomain:

# ...

server {
    listen 443 ssl;
    server_name git.your_domain;
    root /srv/git/stagit;
    index index.html;
}

# ...

Or like this if you wanna serve it in a sub directory:

# ...

server {
    listen 443 ssl;
    server_name your_domain;

    location /git {
        alias /srv/git/stagit;
        index index.html;
    }

}

# ...

Notice: Don't forget to include the custom .conf in /etc/nginx/nginx.conf

http {

    # ...

    include conf.d/yourconf.conf;

    # ...

}

Configure git-http-backend for stagit + NGINX

Stagit won't let you clone your repositories like Cgit does. So we will use a CGI program that comes with git called git-http-backend and NGINX will serve the repositories via HTTP. git-http-backend does not use SSH. git-http-backend is very insecure by default, anyone can push, pull, clone, etc... unless you restrict access using HTTP Basic Authorization with NGINX and other git-http-backend configuration inside each repo.

Create a custom conf for NGINX.

server {
    listen 443 ssl;
    server_name git.your_domain.com;

    location ~ (^.*\.git.*$) {
        root /srv/git;
        client_max_body_size 0;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend;
        fastcgi_param DOCUMENT_ROOT /srv/git;
        fastcgi_param GIT_HTTP_EXPORT_ALL "";
        fastcgi_param GIT_PROJECT_ROOT /srv/git;
        fastcgi_param PATH_INFO $1;
        fastcgi_param REMOTE_USER $remote_user;
        fastcgi_pass unix:/var/run/git-http-backend-cgi.sock;
    }

}

Install fcgi, fcgiwrap and spawn-fcgi.

Restart NGINX and run spawn-fcgi as ROOT.

# spawn-fcgi -u git -U nginx -s /var/run/git-htpp-backend-cgi.sock /bin/fcgiwrap

Fcgiwrap will mantaing our repos accessible on the Internet when NGINX is running.

If you want more security in your private Git repositories you can add login via NGINX using the HTTP Basic Authorization. Now you'll be prompted for a second set of credentials we are going to set using htpasswd.

Add this to a server block.

server {

    # ...

    auth_basic "stay away";
    auth_basic_user_file /etc/nginx/gitpass;

    # ...

}

And create a password with htpasswd. This beautiful tool comes in the apache-htpasswd package.

# htpasswd -c /etc/nginx/zncpass any_name

git-http-backend has specific repository functionalities that you can enable and disable.

Let's read the git-http-backend manual.

# man git-http-backend

These functionalities are listed and explained there.

http.getanyfile
http.uploadpack
http.receivepack

Inside each git bare repository you have created in /srv/git there will be a config file. Edit that config file so nobody can push commits, and only be able to clone it.

Add this snippet if you only want people to be able to clone your repos, and nothing else. Repeat same configuration on every repository.

[http]
    getanyfile = false
    uploadpack = true
    receivepack = false

Install and configure Cgit (OPTIONAL)

Cgit is a fast web-interface (CGI) for git written in the C programming language. It makes it possible for potential contributors to track and view project source code from the web instead of through a git client.

Install Cgit in your server.

Edit /etc/cgitrc

# vim /etc/cgitrc

If you want to show an specific repository your configuration should look something like this:

virtual-root=/
repo.path=/srv/git/your_repository.git
repo.url=your_repository
repo.name=your_repository
repo.owner=Your Name
repo.desc=something something

If you want to scan and show all the repositories you have, your configuration should look something like this:

virtual-root=/
scan-path=/srv/git

Install, configure and start NGINX to work with Cgit (OPTIONAL)

Install NGINX.

Create a custom.conf file configuration in the NGINX conf.d directory

The file should look something like this:

server {

    listen 443 ssl;
    server_name cgit.your_domain.com;

    location / {
        root /usr/share/webapps/cgit;
        try_files $uri @cgit;
    }

    location @cgit {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/fastcgi.sock;
        fastcgi_param PATH_INFO $uri;
        fastcgi_param SCRIPT_FILENAME /usr/share/webapps/cgit/cgit.cgi;
    }

}

Notice: Don't forget to include the custom.conf in nginx.conf

http {

    # ...

    include conf.d/custom.conf;

    # ...

}

Just like git-http-backend, Cgit also depends on fcgiwrap and Spawn-FCGI to run. Spawn-FCGI should be executed as root with -u parameter calling for git user and -U parameter for nginx user.

# spawn-fcgi -u git -U nginx -s /var/run/cgit-cgi.sock /bin/fcgiwrap

Create SSL certificates with Certbot

Certbot is a free, open source software tool for automatically using Let's Encrypt certificates on manually-administrated websites to enable HTTPS.

Install Certbot.

Stop the NGINX service.

Run Certbot for your domains.

# certbot certonly --manual -d your_domain.com -d git.your_domain.com

Add the following lines to your custom NGINX conf in the 443 ssl server block.

ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;

Start the NGINX service.