šŸ’¾ Archived View for nytpu.com ā€ŗ gemlog ā€ŗ 2021-03-07.gmi captured on 2024-06-20 at 13:05:03. Gemini links have been rewritten to link to archived content

View Raw

More Information

ā¬…ļø Previous capture (2022-03-01)

šŸš§ View Differences

-=-=-=-=-=-=-

ā†© go back to index

self-hosting git; or, how git servers actually work, and how to keep yours secure

march 7, 2021

notices and foreword

Disclaimer: Iā€™m no sysadmin extraordinaire, please please please correct me if thereā€™s something blatantly wrong here:

alex@nytpu.com

Also, I have to thank a whole bunch of resources that helped me put this together:

juneā€™s post on their cgit setup

the git book - chapter 4 and 10 for this in particular, but the whole thing is great

zx2c4 for writing cgit, but also for writing actually good documentation

Finally, to see my setup in action before we get started:

https://git.nytpu.com/

https://golang.nytpu.com/

why?

Well, Iā€™m sure most people here on Gemini recognize the value of having control over your own software and data, but for git specifically thereā€™s enough possible objections to where I feel I should address them.

The first major objection Iā€™ve heard (and had myself in the distant past) is ā€œwell how do people contribute to your code?ā€ The answer is actually how git was originally intended to be used prior to githubā€™s idea of locking people in and forcing them to make an account. Just use email! On your READMEs just make a note of what email you want people to send patches to, customarily a mailing list, but a private email works just fine.

git-send-email.io is a great tutorial!

Submitting an email is really scary, I know I was really worried I messed something up the first time I sent a patch. I actually didnā€™t that time, but in later times I did. But, maintainers are always nice and donā€™t mind if you mess something up, and itā€™s trivial to resend a fixed patch again, no more stressful than editing a pull request.

just how do git protocols work?

Before we can get to setting stuff up, we need to go over some prerequisite information about how cloning/pulling and pushing works in git.

ā€œlocal protocolā€

The local protocol is what is used when you do something like `git clone ../repo`, all it does is /essentially/ copy the repo from one place to another. Thereā€™s a bit of other stuff that goes on, but in principle it really is just copying the folder. The interesting thing is that you can actually push and pull from that the same you could as any other protocol. Not /particularly/ useful, but you could have aĀ full ā€œstableā€ repo instead of just a branch if you wanted to, or you could have the ā€œremoteā€ on a network disk.

ssh protocol

Now, the ssh protocol works exactly the same as the local protocol, just over an ssh connection. The same way you can `rsync local1 local2` or `rsync local1 user@remote:remote1`, or `borg create localrepo::name ~` and `borg create user@remote:remoterepo::name ~`, you can do with git. Just use a ssh name and path instead of just a path, and youā€™ve got it working.

For something like a code forge, they use a special shell that comes with git called the `git-shell`, and all it does is allow you to do is `git receive-pack`, `git upload-pack`, and `git upload-archive` (these are what is used behind the scenes for pushing and pulling), and it doesnā€™t allow interactive use which is why you canā€™t `ssh git@git.sr.ht` and start issuing commands. However, in principle itā€™s the exact same. And, on your self-hosted git, you actually can just use your regular ssh user the same way. For instance, my remotes are alex@nytpu.com:pub/<repo_name>.git, instead of a special user like `git@nytpu.com`.

Edit (2021-03-08, suggested by Drew DeVault): If you want to make it read-only, you have to give ssh access to the machine still, but since it respects unix file permissions you can simply make the repos read-only and that'll stop pushing.

git protocol

The git protocol works the same way as the ssh protocol, but doesnā€™t require authentication or encryption, and is restricted to only what actions a git-shell can do. And yes, it supports all the actions, you can technically make a push-able git: repo, but you just never would because itā€™s publicly modifiable. Itā€™s generally just used as an alternative to the http protocols when cloning.

ā€œdumbā€ http

The ā€œdumbā€ http protocol literally just expects a bare git repo to be served as raw files by a web server. The client just reads the files in the bare repo and constructs diffs, figures out what content to pull, etc. This is the only method cgit supports on itā€™s own without anything else. Itā€™s also /really/ slow, because the client has to go and parse and create the logs, graph, history, the whole thing every time so it can figure out what to get.

ā€œsmartā€ http

ā€œSmartā€ http is what you see used on something like github for pulling and pushing, but even if itā€™s a read-only http link you usually still use the smart http because itā€™s so much faster than the ā€œdumbā€ is. Itā€™s essentially like the ssh protocol, but instead of shoving {recieve,upload}-pack and upload-archive into an ssh connection, theyā€™re instead shoved into an http connection. Otherwise itā€™s the same principle, authenticate in a protocol-specific way then shove the data from these git commands into the stream. The main difference is that smart http lets you have a mix of authenticated and unauthenticated users, unlike the completely unauthenticated git and dumb http protocols. This mix of authenticated and unauthenticated is usually used to make it read-only for everyone, and read-write for authorized users, but you could technically make it work like the git protocol and let everyone write, or make it like dumb http and make it completely read-only.

summary

So thatā€™s all the background, basically all you need to know is that thereā€™s four major protocols for accessing remote git repositories:

Weā€™re going to get set up with smart http (but still read-only), and ssh for our read-write access. We can also optionally make them read-only accessible over the git protocol.

setting up cgit

cgit itself

Just install it. Iā€™m going to be using arch package names for this whole thing, but it should be trivial to find what you need for your distro. `sudo pacman -S cgit` and youā€™re ready. We need to configure it, but thereā€™s no default or example configuration file! Luckily, Iā€™m providing you all with my cgitrc, and if you need to make any changes the cgitrc(5) man page is very good:

https://git.zx2c4.com/cgit/tree/cgitrc.5.txt

my annotated cgitrc

Also, the cgitrc references a custom.css, given here:

files/cgit/custom.css

Finally, cgit has these thingā€™s called ā€œfilters,ā€ that are used as an html preprocessor before displaying the file. cgit comes with some pretty good ones, and I use its provided one for the source-filter, but I wrote my own about-filter to add support for scdoc.

files/cgit/cgit-about-filter

Thatā€™s about it for cgit itself, the rest is for nginx.

nginx

I assume you have a working nginx installation, and all we need to do is add a new subdomain `git.example.com`. If you donā€™t already have it set up, then thereā€™s numerous tutorials online, go get it working and come back.

Something thatā€™s good for nginx security but bad for trying to run stuff is that nginx has no built-in cgi support, and it only supports fastcgi, while cgit is a regular cgi script. Luckily, thereā€™s a simple little thing called fcgiwrap to the rescue! Just install fcgiwrap (`sudo pacman -S fcgiwrap`), and start up its service (if youā€™re unfortunate enough to be using systemd on your server, thereā€™s a `fcgiwrap.socket`). Thatā€™s it, no more configuration required for fcgiwrap!

Before we even start getting nginx setup, weā€™re going to address a very common issue with cgit: cloning through http is /really fucking slow/. This is because cgit only supports the ā€œdumbā€ http protocol (see above)! Luckily, git comes with a script called `git-http-backend` that supports smart http. In the nginx config weā€™ll just match paths that are queried for by a smart git request and pass those to git-http-backend. If we didnā€™t do this, those requests would 404 and the client would fall back to cgitā€™s dumb http.

Now, this is where people can get worried about security. Well, as long as you havenā€™t given out your ssh key, then your ssh is safe. cgitā€™s dumb http is safe, and when we set up the git protocol daemon later on, it disables pushing unless you explicitly enable it on a per-repo basis (which you shouldnā€™t, thatā€™s public!). The smart http is all we have to worry about, but itā€™s fine just like this. It wonā€™t enable pushing unless you set up authentication in your nginx config, so unless you just authenticate people willy-nilly itā€™s all read-only!

Now all you just need to make a new config in sites-available, for instance, mineā€™s git.nytpu.com.conf, and then enable it once you get it all pasted in.

finally, hereā€™s my annotated nginx config. itā€™s stripped down a bit to just the relevant parts, make sure to top it off with your own specific configuration

A final note, is that if you mess around with css, or even just change repo descriptions, youā€™re going to need to delete the cache files in order for your changes to show up immediately. For instance, I run `sudo rm /var/cache/cgit/*` (which is pretty dangerous, what if my history got corrupted and put a space after ā€œcgit/ā€?).

There! Look you lucky duck, you have your cgit all set up once you restart nginx! Before you start doing anything though, letā€™s get a regular git protocol server set up and show you how to actually set up repos.

git protocol

Just for fun, letā€™s set up a git protocol server. However, my setup is literally word-for-word from the git book, all I changed is the path itā€™s serving from. Just follow this link:

git book: 4.5 Git on the Server - Git Daemon

A few notes: make sure to punch open port 9418 outgoing on your server. Also, if youā€™re a clever boi and already have repos set up, it wonā€™t work quite yet, Iā€™ll go over it in the next section.

actually using the darn thing

Now we gotta work on getting repos set up on your server. First off is creating them, even if you have a local repo you want to push up, you need to create an empty repo on the server to push to.

i just threw together a script for creating a new repo since itā€™s such a common thing to do. make sure to change the first cd to your public directory.

It initializes a bare repo (i.e. no working copies of files), and cds into it. Bare repos traditionally have ā€œ.gitā€ on the end to indicate itā€™s a bare repo. Donā€™t worry, our cgit configuration set it up so the .git is stripped off when creating the repo name. It then touches ā€œgit-daemon-export-okā€ which is what tells the git daemon we set up last section that itā€™s okay to make this repo public. If you didnā€™t touch this, then the git daemon would be smart enough to think that maybe you accidentally put it in the public folder and wonā€™t serve it. Finally, it opens up the ā€œdescriptionā€ file in the standard editor so you can set it. Now all you do is `git remote add origin user@host:pub/newrepo.git` and itā€™s all set up!

Edit (2021-03-08): I forgot to say this when I first wrote this but you can actually have ā€œprivateā€ and ā€œpublicā€ repos. For instance, I serve my repos from ~/pub/, but I also have a ~/priv/ that I can still push/pull to over ssh. When Iā€™m ready to make a repo public, I just move the repo from priv/ to pub/ and change my remote urlā€™s path. As simple as that!

bonus: fixing goā€™s mess for them: setting up a shim for goā€™s meta tags

I host a few go repos, none of them are libraries but I am partway through writing an actual library that people wonā€™t^W^W^W/will/ want to use. The thing is, google has the /outright hubris/ to expect a language-agnostic, no, file agnostic (git works for anything, not just source code) version control system to include programming-languageā€“specific meta tags! At least they didnā€™t continue going for hardcoding specific code forges. I guess since google is the centralization monopoly of the world they probably donā€™t quite understand that the entire point of git is that itā€™s decentralized, but I mean, come on, you canā€™t be so sheltered as to not know you can self host git. They didnā€™t even include sourcehut, they basically said ā€œif youā€™re not on github or gitlab, youā€™re SOL.ā€

drew devault says a thing about how shitty pkg.go.dev is.

But thatā€™s neither here nor there, they discarded the latter proposal, but you still need the go-specific tags. Luckily, the author of cgit also wrote a little script to automatically serve these for us on a new subdomain (mineā€™s golang.nytpu.com). Unluckily, the dependencies are really egregious unless you already have them installed.

golang-package-server: this very dumb script will serve up the proper go-import and godoc meta tags [and redirect to the source code when necessary].

He gives /no/ documentation, and Iā€™ve never once in my life used uwsgi before this, so Iā€™ll share what I got working for your benefit.

First, clone golang-package server wherever you want (Iā€™m using ā€œ/usr/share/webapps/ā€ for the rest of this). Edit repos.txt and load up the name of your modules and a link to the repo in cgit.

my repos.txt. note that it must be tab separated.

Now we have to install uwsgi and the uwsgi python plugin. Weā€™re going to be using uwsgiā€™s ā€œemperorā€ mode because itā€™s the easiest. Start out by checking that /etc/uwsgi/emperor.ini exists, and if not create it and use mine:

files/cgit/golang-package-server/emperor.ini

Then, create /etc/uwsgi/vassals/ and edit /etc/uwsgi/vassals/golang-package-server.ini with this:

my golang-package-server.ini. make sure to change `chdir` to the directory to *above* where golang-package-server is, not in the golang-package-server directory itself. also make sure to change the socket path if necessary.

Now you can start up the uwsgi emperor daemon using your preferred metod.

Finally, we need to set up our new nginx config:

all you should need to change is the socket path, if necessary.

And thatā€™s it, you can go to golang.nytpu.com and get a list of hosted repos, import golang.nytpu.com/LIBRARY as normal, and if you go to golang.nytpu.com/LIBRARY youā€™ll be redirected to git.nytpu.com/LIBRARY!

appendix

You are in a gemlog post. There is an appendix with strange gothic lettering here.

> examine appendix

The engravings translate to "This space intentionally left blank."

ā‚

ā†© go back to index

also available on the web

contact via email: alex [at] nytpu.com

or through anywhere else I'm at

backlinks

-- Copyright Ā© 2021 nytpu - CC BY-SA 4.0