While services like Github and Bitbucket are invaluable to our work as web and software developers, they are run by other companies, and as such aren’t in our direct control. They may go down at inconvieient times, or be buggy in other ways. You may also find yourself doing projects that you don’t want to actually put in the clutches of external entities at all. As a supplement to these (or similar services) you may want to consider running your own self-hosted (and potentially completely private) Git Server. I recently set up just this. I thought that it would be worth describing what we have going in case it would help anyone else.
The first thing that you’ll need is some computer to run as the server. In some cases a Raspberry Pi may do, but we were offered an old Mac Mini by family, and also have more complex uses for an internal server (like running Plex), so went with a full-fledged Mac instead. It is that configuraton that I’ll be discussing here. My first bit of advice is to clean install whatever the latest version of macOS that runs on the computer is, in our case that was macOS High Sierra. The reason to do this is to ensure that when you build your server it is as clean as it can be. We don’t want leftover cruft from when the Mac was being used as an everyday computer.
When you are setting up the fresh install I recommend first creating a dedicated apple
account as the only admin user, and setting a strong password on it. You will rarely actually be in this account directly. Create a separate standard user, I named ours server
, that is set to log in automatically. This is the account that will actually run the Git server. You’ll also want to ensure that the server starts up automatically after a power faiilure, and that it is automatically updating at least security updates. I also turned on screen sharing, file sharing, and remote login so we could manage the server from our own Macs, with the server itself running completely headless.
There are a number of projects that are Github clones, I chose Gitea to run as our internal Git server. Installing Gitea could not be easier, since it can be done via Homebrew:
brew tap go-gitea/gitea
brew install gitea
You will also need MySQL installed. Then you can go and create a user and database for Gitea to use. After that just run
gitea web
to start the server. It should tell you what the URL is to access the server, going there will take you to the Gitea setup screen. Basically, enter the database information and a few other details and you’ll be all set. Gitea should provide you with helpful hints as a brand new admin user.
By default Gitea is using /usr/local/bin
as its home directory. So I recommend changing that to something within the server
user. We chose to make this a gitea
folder in the server
user’s home directory. To do this you need to simpy run Gitea specifying a custom path to its config file:
gitea web -config /Users/server/gitea/conf/app.ini
The file should be under custom/conf
in /usr/local/bin
right now, so just move the file to a conf
folder in whatever folder you want Gitea to use for data. Open that file and you’ll see a number of other file paths specified. I sugest changing all of these to be subfolders of the folder that you’ve set for Gitea to use. You should also look at the config cheat sheet and set specific folders for all of the path settings the config file can have. For the most part these should be subfolders, except I would reccommend that logs go into a gitea
subfolder of the user’s logs folder at ~/Library/Logs
so that Console can be easily used to navigate logs. You should also set the URL and Port as appropriate for your environment.
The final step in setting up Gitea is to ensure that it runs at login (which is system startup if the user logs in automatically). Do this be creating a Launchd plist file for this service at ~/Library/LaunchAgents
, maybe name it io.gitea.gitea.plist
. The contents of this file should essentially be:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>/Users/server</string>
<key>EnvironmentVariables</key>
<dict>
<key>GITEA_WORK_DIR</key>
<string>/Users/server/gitea</string>
<key>GITEA_CUSTOM</key>
<string>/Users/server/gitea/conf</string>
<key>HOME</key>
<string>/Users/server</string>
</dict>
<key>StandardOutPath</key>
<string>/Users/server/Library/Logs/gitea/launchd.log</string>
<key>StandardErrorPath</key>
<string>/Users/server/Library/Logs/gitea/launchd_error.log</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>-c</string>
<string>/usr/local/bin/gitea web -config /Users/server/gitea/conf/app.ini</string>
</array>
<key>UserName</key>
<string>server</string>
<key>RunAtLoad</key>
<true/>
<key>Label</key>
<string>io.gitea.gitea</string>
</dict>
</plist>
Pay attention to the paths in this file to make sure that you change them from this example as needed. To start the service run:
launchctl load /Users/server/Library/LaunchAgents/io.gitea.gitea.plist
From here on out is should be kept running by macOS, and started on user login. At this point you have your own Git server running. You should explore it as much as you’d like, and start playing with it.
To use the second server, just add it like normal to your repo with git remote add
, but remember that its name needs to be something other than origin
. To fully integrate this new server as a backup of your primary remote you should push to it anytime that you push to the primary remote. Git makes this quite easy. A single remote can have more than one URL to use when pushing, so you set up the remote like so:
git remote set-url --add --push origin server@mac.local:test/my-project.git
You actually need to run this same command for the original remote URL to keep pushing to it as well. From here on out whenever you push you will be pushing to both your prinary and internal remotes. No need to worry about keeping your internal remote current. You can only pull from one remote at a time, and should only regularly do this from your primary remote, but you should still set up your internal remote the traditional way so you can pull from it if needed. The next time Github or Bitbucket go down, or even just your local internet connection, you will be able to continue pushing as normal so your partners can get your changes and you have the peace of mind to know that your changes aren’t on just one machine.