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