My GitHub account has become cluttered with many private Git repositories over the years. I have repositories for schoolwork, tutorials, notes for my Dungeons and Dragons campaign; pretty much any text-based project gets put in a Git repository. I love being able to roll back changes and easily share work between my machines. Unfortunately, now my GitHub account is covered with private repositories, and logging in to GitHub every time I want to create a repository is a pain.
I recently converted an old gaming PC into a home server, and I’ve always heard Git is pretty easy to self-host.
So, I thought why not give it a try.
After an afternoon of tinkering I now have a setup I enjoy using.
I can create a new repository with gits init newrepo
.
Then newrepo
is available as a remote for all my machines.
I can also gits ls
to list all the repositories on the server and delete repositories with gits rm newrepo
.
Here’s how I did it.
Self-hosting a Git remote is fairly easy as far as these things go.
You will need to know how to log into a server with SSH and the basics of Git, and need a server running Linux to host on.
For this post I’m going to use example.com
as the server name.
My server is running Fedora Server, but the following should work on pretty much any Linux distribution.
The core functionality of a git server is quick to set up.
A Git remote repository is essentially just a folder containing a bunch of bare git repositories, which can be accessed over SSH.
A bare git repository is everything in the .git
folder of a normal repository.
There is no working tree in a bare git repository, so you can’t make comments or edit files, it just stores all the git metadata.
First, we need to create a user on the server to handle git transactions.
$ sudo useradd -m git
$ sudo su git
Then add the necessary folders for other users to connect via SSH.
$ mkdir -m 700 ~/.ssh/
$ touch ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys
Now would also be a good time to pick a directory to house your git repositories.
The git documentation uses /srv/git
, but I prefer to use the git user’s home directory.
This keeps everything in one place on the file system and makes for nicer syntax when interacting with the remote.
To grant a user access to your Git server, copy their public ssh key to /home/git/.ssh/authorized_keys
.
Once you’ve added a user’s key you’re done. Yep, it’s just that easy.
To create a new repository, log in to your server as git
and create a new bare repository like so:
$ ssh [email protected]
# As git
$ mkdir ~/newrepo.git
$ git init --bare ~/newrepo.git
This will create a new directory and initialize it as a bare git repository.
Any user with a key in /home/git/.ssh/authorized_keys
can clone the repository with
$ git clone [email protected]:newrepo.git
git-shell
While all the basic Git functionality is available, we still need to log in to the server and create git repositories manually. It would be great to make the UX a bit nicer. Luckily, Git comes with git-shell, which is designed for this purpose.
To utilize git-shell, create a folder git-shell-commands in the git user’s home directory on the server. When a git-shell session is started, this folder will be used as a PATH, where any scripts in the directory will be available as commands. I’ve created a repository with the commands for my git interface. Feel free to copy the commands, or even easier, clone the repository into your git home directory.
$ cd
$ git clone https://git.sr.ht/~jakesco/git-shell-commands
The repository contains 4 files.
Note that these are simply shell scripts. You can define arbitrary commands and save them in this folder for use by git-shell. For example, here is the init command:
#!/bin/sh
if [ $# -ne 1 ]; then
echo "usage: init <name>"
exit 1
fi
base_dir="$HOME"
new_repo="$1"
case $new_repo in
*\.git)
;;
*)
new_repo="${new_repo}.git"
;;
esac
if [ -d "$new_repo" ]; then
echo "$new_repo already exists"
exit 1
fi
mkdir "$base_dir/$new_repo"
git init --bare "$base_dir/$new_repo" >/dev/null 2>&1\
&& echo "Initialized empty Git repository: git@$(uname -n):$new_repo"
After copying the git shell commands, our home directory should look like this:
$ tree -a /home/git
.
├── git-shell-commands
│ ├── help
│ ├── init
│ ├── ls
│ └── rm
│
└── .ssh
└── authorized_keys
All that’s left to do is change the git user’s default shell to git-shell.
$ sudo chsh git -s $(which git-shell)
Now when any user logs into the server as git they will be presented with git-shell like so:
Welcome to git-shell on example.com!
Available Commands:
ls: list all repositories.
init <name>: Initialize new Git repository with <name>
rm <name>: Delete Git repository with <name>
git>
You can also access these commands via SSH with
$ ssh [email protected] <command>
You can make an alias for an even easier interface
$ alias gits='ssh [email protected]'
Now you can manage the git server with gits init, gits rm, and gits ls.
This setup is fine for a single user home server setup, but if you want to open this up to the internet or have multiple users, we should lock down a few things.
First, depending on your server’s SSH configuration, anyone with access can use their connection to create an ssh tunnel.
Simply prepend any SSH keys in /home/git/.ssh/authorized_keys
with no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
.
So keys in the authorized_keys
file should look like this:
$ cat /home/git/.ssh/authroized_keys \
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty \
ssh-rsa AAA...<key content> Description
Second, I like to disable password log in for utility users like git.
$ sudo passwd -l git
Now git can only be accessed via SSH.
And there you have it, a fully functional private git server.