2020-06-23-setting-up-a-personal-git-server.html (12866B) - raw


      1 <!-- title: Setting up a personal Git server -->
      2 <!-- slug: setting-up-a-personal-git-server -->
      3 <!-- categories: Decentralization, Personal domain, Self-hosting -->
      4 <!-- date: 2020-06-23T16:10:00Z -->
      5 <!-- lastmod: 2020-07-24T15:17:00Z -->
      6 
      7 <p>
      8   Running a personal Git server is something that has been on my mind for quite a long time. One of
      9   the most popular solutions I have seen around is <a href="https://gitea.io">Gitea</a>. A couple of
     10   months ago, when trying out different things, I decided to run Gitea locally to see how easy it
     11   would be to implement on my server. It turns out pretty easy, especially if you are using Docker.
     12   However, my server doesn't run Docker as of now and it also felt like customizing it would be hard
     13   (for example, getting rid of the username in the URLs). Gitea looks like a very good solution for
     14   self-hosting Git (and the sites look very nice!), but in my case, it felt like using a
     15   sledgehammer to crack a nut. I figured most self-hosted Git solutions would turn out to be a bit
     16   too much for my use case, so I decided to look into hosting Git without any other program.</p>
     17 <!-- /p -->
     18 
     19 <p>
     20   I had experience setting up a bare-bones Git server only usable through SSH, so I looked up how to
     21   create a website with the <code>bare</code> repositories. It turns out there's even a built-in
     22   option<sup id="fnref1"><a href="#fn1">1</a></sup>! Other programs have more or less similar looks,
     23   but I decided to check if there was any way to have a static generator for the webpage—the fewer
     24   things running on my server the better! And there is one (that I found):
     25   <a href="https://codemadness.org/stagit.html">stagit</a>. It is very simple, and it does the job.
     26   On top of that, the program is super small, which makes it very easy to modify it if needed<sup
     27   id="fnref2"><a href="#fn2">2</a></sup>. I gave it a try and it worked nicely. I decided that it
     28   was a good solution for my use case, therefore having a vanilla Git server would work, so I
     29   started building it<sup id="fnref3"><a href="#fn3">3</a></sup>.</p>
     30 <!-- /p -->
     31 
     32 <p>Let's see how to set it up!</p>
     33 
     34 <h2>Setting up a Git server</h2>
     35 
     36 <p>
     37   What will happen is we'll have <code>bare</code> repositories on our server which we'll then
     38   clone/fetch/push to using SSH<sup id="fnref4"><a href="#fn4">4</a></sup>. We'll put these
     39   repositories on the <code>/srv/git</code> directory—because I keep everything served from my
     40   server on <code>/srv</code>—but any location will work. To keep Git separated from the root user,
     41   we'll create a new <code>git</code> user, with <code>/srv/git</code> as its home folder, this way,
     42   the remote address will be <code>git@domain:repo.git</code> (no need for an absolute address).
     43   Let's do that:</p>
     44 <!-- /p -->
     45 
     46 <pre><code><!--
     47      -->useradd -d /srv/git git
     48 <!-- -->mkdir /srv/git
     49 <!-- -->chown git /srv/git</code></pre>
     50 
     51 <p>Now, let's create the folder for the SSH configuration where we'll add our public key.</p>
     52 
     53 <pre><code><!--
     54      -->su git
     55 <!-- -->cd
     56 <!-- -->mkdir .ssh &amp;&amp; chmod 700 .ssh
     57 <!-- -->touch .ssh/authorized_keys &amp;&amp; chmod 600 .ssh/authorized_keys</code></pre>
     58 
     59 <p>
     60   We could stop here, adding our key to the file (you can also use <code>ssh-copy-id</code>), but we
     61   can take a couple more steps to isolate the user from the server, useful especially if you want to
     62   share access to the git user with someone else.</p>
     63 <!-- /p -->
     64 
     65 <p>
     66   To do that, we'll use the <code>git-shell</code> shell, which will only run scripts found on
     67   <code>~/git-shell-commands</code>.<sup id="fnref5"><a href="#fn5">5</a></sup></p>
     68 <!-- /p -->
     69 
     70 <pre><code>chsh git -s $(which git-shell)</code></pre>
     71 
     72 <p>
     73   Now if you add someone else's public SSH key to the server, they won't be able to run any command.
     74   If you want to allow some commands, create scripts on <code>~/git-shell-commands</code>. For
     75   example, I have scripts to initiate repositories, add SSH keys and other useful commands, you can
     76   see them all <a href="https://git.oscarbenedito.com/git-shell-commands/">here</a>.</p>
     77 <!-- /p -->
     78 
     79 <h2>Making repositories public</h2>
     80 
     81 <p>
     82   Now we can pull and push to our repository, and we can share access to them by adding SSH keys (or
     83   sharing the password if you use one). However, we might want to have public repositories that
     84   people should be able to clone without the need to have access to all of them. To do so, we'll use
     85   the Git Daemon (that uses the Git Protocol). All we have to do is run the following command (and
     86   keep it running, I recommend running a systemd service if you use systemd, there is an example
     87   <a href="https://git-scm.com/book/en/v2/Git-on-the-Server-Git-Daemon">here</a>).</p>
     88 <!-- /p -->
     89 
     90 <pre><code>git daemon --reuseaddr --base-path=/srv/git/ /srv/git/</code></pre>
     91 
     92 <p>
     93   This daemon will only serve repositories that have a file named <code>git-daemon-export-ok</code>
     94   in them, so if you want to make a certain repository public, all you have to do is run:</p>
     95 <!-- /p -->
     96 
     97 <pre><code>touch /srv/git/repo.git/git-daemon-export-ok</code></pre>
     98 
     99 <p>
    100   Remove that file to make it private again. The cloning address will be
    101   <code>git://domain/repo.git</code> and you can't push to that address. You can also serve
    102   repositories through HTTP which will allow you to have fine-grained control over who can access
    103   which repositories, look it up if you are interested.</p>
    104 <!-- /p -->
    105 
    106 <h2>Making a website</h2>
    107 
    108 <p>
    109   Now we can host private and public repositories, but we may want to share the public ones easily.
    110   A website is a good way to allow people to quickly see your repositories without the need to clone
    111   every single one of them. We'll use stagit to create the HTML files which we'll host as a static
    112   site. First of all, let's install stagit:</p>
    113 <!-- /p -->
    114 
    115 <pre><code><!--
    116      -->git clone git://git.codemadness.org/stagit
    117 <!-- -->cd stagit
    118 <!-- -->sudo make install</code></pre>
    119 
    120 <p>To create a website for a repository run</p>
    121 
    122 <pre><code>stagit /path/to/repo.git</code></pre>
    123 
    124 <p>from the directory where you want the site built. To create an index file with all your repositories simply run</p>
    125 
    126 <pre><code>stagit-index /path/to/repo-1.git /path/to/repo-2.git &gt; index.html</code></pre>
    127 
    128 <p>
    129   with the path to all your repositories. Make sure you have the correct information on the
    130   <code>owner</code>, <code>description</code> and <code>url</code> files so that stagit uses them
    131   when creating the HTML.</p>
    132 <!-- /p -->
    133 
    134 <p>
    135   Having to do this is every time you update your repositories is not a reasonable solution, but we
    136   can set up a <code>post-receive</code> hook to do it for us. There are examples of scripts to use
    137   as an initial creation script for the whole site and a post-receive hook on stagit's source code
    138   repository. I have changed those scripts a bit to only process public repositories (the ones with
    139   the <code>git-daemon-export-ok</code> file) as well a modified the source a bit. You can find the
    140   changes on my <a href="https://git.oscarbenedito.com/stagit/">stagit fork</a>.</p>
    141 <!-- /p -->
    142 
    143 <h2>Pros of this setup</h2>
    144 
    145 <p>
    146   I have only been using this server for a couple of days, but I have also been setting up a bunch
    147   of <a href="https://suckless.org">suckless</a> tools<sup id="fnref6"><a href="#fn6">6</a></sup>,
    148   so I have been using Git a lot. One of the best things is that setting up repositories is very
    149   easy. No need to open a browser, log in to GitHub and go through the process of creating a new
    150   repository<sup id="fnref7"><a href="#fn7">7</a></sup>. I just run</p>
    151 <!-- /p -->
    152 
    153 <pre><code><!--
    154      -->ssh git@oscarbenedito.com
    155 <!-- -->init reponame</code></pre>
    156 
    157 <p>
    158   Done! I can also set it up as public by running a script I have on my <code>git-shell</code> and
    159   change the description/owner/url just as easily.</p>
    160 <!-- /p -->
    161 
    162 <p>
    163   Another thing I have noticed is that Git's clone/fetch/push commands are significantly faster on
    164   this server than GitLab or GitHub. However, I don't know compared to a self-hosted instance of
    165   Gitea or <a href="https://gogs.io">Gogs</a>.</p>
    166 <!-- /p -->
    167 
    168 <h2>Cons of this setup</h2>
    169 
    170 <p>
    171   One thing that might be missed by this setup is the ability to download a tarball with the code
    172   from a certain commit, browse the code as it was in a given commit, see git blame, etc. This could
    173   be solved by using another tool for the website part—such as <a
    174   href="https://git.zx2c4.com/cgit/about/">cgit</a>—so if I ever want to do that, it shouldn't be hard.</p>
    175 <!-- /p -->
    176 
    177 <p>
    178   Another thing is how the website looks. Other self-hosting solutions like Gogs, Gitea, GitLab...
    179   look a lot nicer than stagit. However this isn't a priority right now and I appreciate the ability
    180   to have full control over how the server works—and it has been very interesting to learn about it
    181   all. Once again this is something to do with the website, and not the repository hosting
    182   itself.</p>
    183 <!-- /p -->
    184 
    185 <h2>Final comments</h2>
    186 
    187 <p>
    188   It has been fun to set everything up and realize how easy it is to self-host Git without the need
    189   for other software. On top of that, I can now share my repositories from my domain, which means it
    190   is easier to stop using other hosting services if I stop wanting to accept their policies, similar
    191   to having email on my domain.</p>
    192 <!-- /p -->
    193 
    194 <p>
    195   For now, I will still have my public repositories hosted on GitLab and GitHub, as I don't mind it
    196   and some people might be more used to those UIs. They also act as a backup in case something
    197   happens to my server or if I want to edit my repositories from a computer without SSH access to my
    198   server.</p>
    199 <!-- /p -->
    200 
    201 <p>
    202   Finally, I have talked about some software that will allow you to self-host Git, but I don't want
    203   to end this post without mentioning <a href="https://sourcehut.org/">sourcehut</a>. I find it a
    204   very good solution because everyone can contribute to your projects without the need to create yet
    205   another account. Everything is done through email, which is nicely supported by Git (learn more
    206   <a href="https://git-send-email.io/">here</a>).</p>
    207 <!-- /p -->
    208 
    209 <p>
    210   I almost forgot! If you want to check out my Git website or clone some repositories from my
    211   domain, you can find all of that here: <a href="https://git.oscarbenedito.com">https://git.oscarbenedito.com</a>.</p>
    212 <!-- /p -->
    213 
    214 <p>
    215   <em>Edit</em>: The post originally said that creating a new repository on GitLab was a long
    216   process. However, you can just push to a new remote address and GitLab will automatically create
    217   the new repository.</p>
    218 <!-- /p -->
    219 
    220 <!-- footnotes -->
    221 <hr />
    222 
    223 <ol>
    224   <li id="fn1">
    225     Run <code>git instaweb</code> from a Git repository to try it. <a href="#fnref1" title="Jump
    226     back to footnote 1 in the text">&#8617;</a></li>
    227   <!-- /li -->
    228   <li id="fn2">
    229     I haven't modified the source code much yet, but I have some ideas in mind. <a href="#fnref2"
    230     title="Jump back to footnote 2 in the text">&#8617;</a></li>
    231   <!-- /li -->
    232   <li id="fn3">
    233     Funnily enough, I had already set up a Git server there for a couple of repositories containing
    234     a lot of personal information to avoid hosting them on GitLab or GitHub. I completely forgot
    235     about it. I deleted it and started from scratch, as I wanted to document the process on my
    236     personal wiki. <a href="#fnref3" title="Jump back to footnote 3 in the text">&#8617;</a></li>
    237   <!-- /li -->
    238   <li id="fn4">
    239     Bare repositories are simply a folder with the files of the <code>.git</code> directory of a
    240     repository. Indeed, you can clone a repository from your computer running <code>git clone
    241     /path/to/repo/.git cloned-repo</code>. This directory contains all the information of the
    242     repository, that is why Git is distributed. <a href="#fnref4" title="Jump back to footnote 4 in
    243     the text">&#8617;</a></li>
    244   <!-- /li -->
    245   <li id="fn5">
    246     If the <code>chsh</code> command doesn't work, make sure <code>git-shell</code> is in
    247     <code>/etc/shells</code>, if not, add it manually. <a href="#fnref5" title="Jump back to
    248     footnote 5 in the text">&#8617;</a></li>
    249   <!-- /li -->
    250   <li id="fn6">
    251     Fun fact: after setting everything up I realized that suckless uses stagit to show their
    252     repositories, indeed the author of stagit is the current maintainer of some suckless projects
    253     like <a href="https://st.suckless.org/">st</a> and <a
    254     href="https://tools.suckless.org/dmenu/">dmenu</a>. <a href="#fnref6" title="Jump
    255     back to footnote 6 in the text">&#8617;</a></li>
    256   <!-- /li -->
    257   <li id="fn7">
    258     I originally thought this was also the case for GitLab, however, you can push a new repository
    259     to a new remote address and GitLab will automatically create it. <a href="#fnref7" title="Jump
    260     back to footnote 7 in the text">&#8617;</a></li>
    261   <!-- /li -->
    262 </ol>