Self-Hosting a Personal Git Server
2616 words · 14 minutes
My Approach to Self-Hosting Git
I have often tried to self-host my Git repositories, but have always fallen short when I tried to find a suitable web interface to show on the front-end.
After a few years, I have finally found a combination of methods that allow me to easily self-host my projects, view them on the web, and access them from anywhere.
Before I dive into the details, I want to state a high-level summary of my self-hosted Git approach:
- This method uses the
ssh://
(read & write) andgit://
(read-only) protocols for push and pull access.- For the
git://
protocol, I create agit-daemon-export-ok
file in any repository that I want to be cloneable by anyone. - The web interface I am using (
cgit
) allows simple HTTP cloning by default. I do not disable this setting as I want beginners to be able to clone one of my repositories even if they don't know the proper method.
- For the
- I am not enabling Smart HTTPS for any repositories. Updates to repositories must be pushed via SSH.
- Beyond the actual repository management, I am using
cgit
for the front-end web interface.- If you use the
scan-path=<path>
configuration in thecgitrc
configuration file to automatically find repositories, you can't exclude a repository fromcgit
if it's stored within the path thatcgit
reads. To host private repositories, you'd need to set up another directory thatcgit
can't read.
- If you use the
Assumptions
For the purposes of this walkthrough, I am assuming you have a URL (git.example.com
) or IP address (207.84.26.991
) addressed to the server that you will be using to host your git repositories.
Adding a Git User
In order to use the SSH method associated with git, we will need to add a user named git
. If you have used the SSH method for other git hosting sites, you are probably used to the following syntax:
The syntax above is an scp
-like syntax for using SSH on the git
user on the server to access your repository.
Let's delete any remnants of an old git
user, if any, and create the new user account:
Import Your SSH Keys to the Git User
Once the git
user is created, you will need to copy your public SSH key on your local development machine to the git
user on the server.
If you don't have an SSH key yet, create one with this command:
Once you create the key pair, the public should be saved to ~/.ssh/id_rsa.pub
.
If your server still has password-based authentication available, you can copy it over to your user's home directory like this:
Otherwise, copy it over to any user that you can access.
Once on the server, you will need to copy the contents into the git
user's authorized_keys
file:
(Optional) Disable Password-Based SSH
If you want to lock down your server and ensure that no one can authenticate in via SSH with a password, you will need to edit your SSH configuration.
Within this file, find the following settings and set them to the values I am showing below:
no
no
publickey
You may have other Authentication Methods required in your personal set-up, so the key here is just to ensure that AuthenticationMethods
does not allow passwords.
Setting up the Base Directory
Now that we have set up a git
user to handle all transport methods, we need to set up the directory that we will be using as our base of all repositories.
In my case, I am using /git
as my source folder. To create this folder and assign it to the user we created, execute the following commands:
Creating a Test Repository
On your server, switch over to the git
user in order to start managing git files.
Once logged-in as the git
user, go to your base directory and create a test repository.
&&
If you want to make this repo viewable/cloneable to the public via the git://
protocol, you need to create a git-daemon-export-ok
file inside the repository.
Change the Login Shell for git
To make sure that the git
user is only used for git operations and nothing else, you need to change the user's login shell. To do this, simply use the chsh
command:
The interactive prompt will ask which shell you want the git
user to use. You must use the following value:
Once done, no one will be able to SSH to the git
user or execute commands other than the standard git commands.
Opening the Firewall
Don't forget to open up ports on the device firewall and network firewall if you want to access these repositories publicly. If you're using default ports, forward ports 22
(ssh) and 9418
(git) from your router to your server's IP address.
If your server also has a firewall, ensure that the firewall allows the same ports that are forwarded from the router. For example, if you use ufw
:
Non-Standard SSH Ports
If you use a non-standard port for SSH, such as 9876
, you will need to create an SSH configuration file on your local development machine in order to connect to your server's git repositories.
To do this, you'll need to define your custom port on your client machine in your ~/.ssh/config
file:
.example.com
git.example.com
9876
git
git
Testing SSH
There are two main syntaxes you can use to manage git over SSH:
git clone [user@]server:project.git
git clone ssh://[user@]server/project.git
I prefer the first, which is an scp
-like syntax. To test it, try to clone the test repository you set up on the server:
Enabling Read-Only Access
If you want people to be able to clone any repository where you've placed a git-daemon-export-ok
file, you will need to start the git daemon.
To do this on a system with systemd
, create a service file:
Inside the git-daemon.service
file, paste the following:
[Unit]
Start Git Daemon
[Service]
/usr/bin/git daemon --reuseaddr --base-path=/git/ /git/
always
500ms
syslog
syslog
git-daemon
git
git
[Install]
multi-user.target
Once created, enable and start the service:
To clone read-only via the git://
protocol, you can use the following syntax:
Migrating Repositories
At this point, we have a working git server that works with both SSH and read-only access.
For each of the repositories I had hosted a different provider, I executed the following commands in order to place a copy on my server as my new source of truth:
Server:
&&
# If you want to make this repo viewable/cloneable to the public
Client:
Optional Web View: cgit
If you want a web viewer for your repositories, you can use various tools, such as gitweb
, cgit
, or klaus
. I chose cgit
due to its simple interface and fairly easy set-up (compared to others). Not to mention that the Linux kernel uses cgit
.
Docker Compose
Instead of using my previous method of using a docker run
command, I've updated this section to use docker-compose
instead for an easier installation and simpler management and configuration.
In order to use Docker Compose, you will set up a docker-compose.yml
file to automatically connect resources like the repositories, cgitrc
, and various files or folders to the cgit
container you're creating:
&&
'3'
invokr/cgit
- /git:/git
- ./cgitrc:/etc/cgitrc
- ./logo.png:/var/www/htdocs/cgit/logo.png
- ./favicon.png:/var/www/htdocs/cgit/favicon.png
- ./filters:/var/www/htdocs/cgit/filters
- "8763:80"
restart: always
Then, just start the container:
Once it's finished installing, you can access the site at <SERVER_IP>:8763
or use a reverse-proxy service to forward cgit
to a URL, such as git.example.com
. See the next section for more details on reverse proxying a URL to a local port.
Nginx Reverse Proxy
I am using Nginx as my reverse proxy so that the cgit
Docker container can use git.example.com
as its URL. To do so, I simply created the following configuration file:
server {
80;
git.example.com;
if ($host = git.example.com) {
301 ;
}
404;
}
server {
git.example.com;
443 ssl http2;
location / {
/;
X-Frame-Options SAMEORIGIN;
X-XSS-Protection "1; mode=block";
off;
off;
Host $host;
X-Real-IP $remote_addr;
X-Forwarded-For $proxy_add_x_forwarded_for;
X-Forwarded-Proto $scheme;
X-Forwarded-Port $server_port;
}
include /etc/letsencrypt/options-ssl-nginx.conf;
/etc/letsencrypt/ssl-dhparams.pem;
}
Once created, symlink it and restart the web server.
As we can see below, my site at git.example.com
is available and running:
Settings Up Git Details
Once you have cgit
running, you can add some small details, such as repository owners and descriptions by editing the following files within each repository.
Alternatively, you can use the cgitrc
file to edit these details if you only care to edit them for the purpose of seeing them on your website.
The description
file within the repository on your server will display the description online.
You can add a [gitweb]
block to the config
file in order to display the owner of the repository.
[gitweb]
"YourName"
Note that you can ignore the configuration within each repository and simply set up this information in the cgitrc
file, if you want to do it that way.
Editing cgit
In order to edit certain items within cgit
, you need to edit the cgitrc
file.
Below is an example configuration for cgitrc
. You can find all the configuration options within the [configuration manual] (https://git.zx2c4.com/cgit/plain/cgitrc.5.txt).
/cgit.css
/logo.png
/favicon.png
noindex, nofollow
1
1
1
1
1
1
My Git Website
My personal git repositories.
tar.gz tar.bz2 zip
.gif=image/gif
.html=text/html
.jpg=image/jpeg
.jpeg=image/jpeg
.pdf=application/pdf
.png=image/png
.svg=image/svg+xml
/var/www/htdocs/cgit/filters/syntax-highlighting.py
/var/www/htdocs/cgit/filters/about-formatting.sh
:README.md
:readme.md
:README.mkd
:readme.mkd
:README.rst
:readme.rst
:README.html
:readme.html
:README.htm
:readme.htm
:README.txt
:readme.txt
:README
:readme
git/test-section
.url=test.git
.path=/git/test.git
.readme=:README.md
.owner=John Doe
.desc=An example repository!
Final Fixes: Syntax Highlighting & README Rendering
After completing my initial install and playing around with it for a few days, I noticed two issues:
- Syntax highlighting did not work when viewing the source code within a file.
- The
about
tab within a repository was not rendered to HTML.
The following process fixes these issues. To start, let's go to the cgit
directory where we were editing our configuration file earlier.
In here, create two folders that will hold our syntax files:
&& &&
Next, download the default filters:
Finally, download the HTML conversion files you need. The example below downloads the Markdown converter:
If you need other filters or html-converters found within the cgit project files, repeat the curl
and chmod
process above for whichever files you need.
However, formatting will not work quite yet since the Docker cgit container we're using doesn't have the formatting package installed. You can install this easily by install Python 3+ and the pygments
package:
# Enter the container's command line
# Install the necessary packages and then exit
&& \
&& \
&& \
&& \
You will need to enter the cgit docker container and re-run these yum
commands every time you kill and restart the container!
If not done already, we need to add the following variables to our cgitrc
file in order for cgit
to know where our filtering files are:
/var/www/htdocs/cgit/filters/syntax-highlighting.py
/var/www/htdocs/cgit/filters/about-formatting.sh
Now you should see that syntax highlighting and README rendering to the about
tab is fixed.
Theming
I won't go into much detail in this section, but you can fully theme your installation of cgit
since you have access to the cgit.css
file in your web root. This is another file you can add as a volume to the docker-compose.yml
file if you want to edit this without entering the container's command line.
⚠️ Remember to Back Up Your Data!
The last thing to note is that running services on your own equipment means that you're assuming a level of risk that exists regarding data loss, catastrophes, etc. In order to reduce the impact of any such occurrence, I suggest backing up your data regularly.
Backups can be automated via cron
, by hooking your base directory up to a cloud provider, or even setting up hooks to push all repository info to git mirrors on other git hosts. Whatever the method, make sure that your data doesn't vanish in the event that your drives or servers fail.