summaryrefslogtreecommitdiff
path: root/forgejno.html
blob: 71246e76a365a51a5629653ec113926d9232023c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>You don't need Forgejo</title>

		<link rel="stylesheet" href="style.css">
		<link rel="stylesheet" href="prose.css">
		<style>
			summary {
				font-family: 'Gelasio';
				color: #888;
			}
			summary::marker {
				color: #888;
			}
			
			code {
				font-size: 1rem;
				color: #00ff78;
			}
		</style>
		<script>
			function htmlTableOfContents() {
				var toc = document.getElementById('toc-list');
				var headings = document.body.querySelectorAll('h1, h2, h3, h4, h5, h6');
				headings.forEach(function (heading, index) {
					var link = document.createElement('a');
					link.setAttribute('href', '#'+heading.id.toLowerCase());
					link.textContent = heading.textContent;
					
					var li = document.createElement('li');
					li.appendChild(link);
					
					toc.appendChild(li);
					});
				}
		</script>
	</head>
	<body onload="htmlTableOfContents();">
		<div class="toc">
			<ul id="toc-list"></ul>
		</div>

		<div class="content">
			<h1 id="title">You Don't Need Forgejo</h1>
			<p><a href="https://mitchellh.com/writing/ghostty-leaving-github">People</a>
			<a href="https://vinyl-cache.org/organization/moving.html">ditching</a>
			GitHub after <a href="https://github.blog/news-insights/company-news/an-update-on-github-availability">recent issues</a>
			are looking to self-hosting Forgejo as an alternative.</p>
			<p>Despite some (poorly disclosed) <a href="https://dustri.org/b/carrot-disclosure-forgejo.html">security issues</a>, I think there's a more fundemental reason why you shouldn't use Forgejo.</p>
			<p>If you're just a single person trying to publish your independent coding projects, why do you need: CI, Issue Tracking, Pull Requests, Multi-user account management, etc?</p>
			<p>just use <a href="https://git.zx2c4.com/cgit">cgit</a>.</p>

			<h2 id="forge">How do git forges (like GitHub) even work?</h2>
			<p>A git forge is basically just a directory of git repositories, served over http or ssh, with a fancy web frontend.</p>
			<p>On the server, since the repos don't need to store mutable state (local commits, staged files, etc) they're stored in a special format that's basically just the <code>.git</code>
				directory of a normal git repository.</p>
			<p>This is called a bare repository.</p>

			<h2 id="cgit">What is cgit?</h2>
			<p>cgit is essentially just a web-based frontend for these bare repos.</p>
			<p>While full-on software forges (like GitHub or Forgejo) give you many extra features, cgit is just the read-only frontend, with nothing else included.</p>
			<p>Interested?</p>

			<h2 id="guide">cgit Setup Guide</h2>
			<p>The cgit setup process is fairly involved, but if you were planning on self-hosting Forgejo, then you can handle it.</p>
			<p class="comment">I'll be using Caddy as the webserver for this setup, but I'll link tutorials for NGINX and Apache if prefer xml configuration files and manual https certificates ;&rpar;</p>

			<p>To keep everything organized, I'll run cgit under a user called "git". Our git repos will be stored in this directory.</p>
			<p><code>$ useradd -r -d /var/lib/cgit -s /usr/bin/git-shell git</code></p>

			<h3 id="cfg-fcgiwrap">fcgiwrap</h3>
			<p>cgit is served as a <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">cgi application</a>.</p>
			<p>A long time ago in a galaxy far far away, web servers had builtin support for cgi, but now its often delegated to <a href="https://github.com/gnosek/fcgiwrap">fcgiwrap</a>.</p>
			<p>So our first step will be setting up fcgiwrap.</p>

			<ol>
				<li><p>Install fcgiwrap with a package manager. This will create two systemd files: fcgiwrap.service and fcgiwrap.socket.</p></li>
				<li><p>Open fcgiwrap.service in a text editor (<code>$ systemctl edit fcgiwrap.service</code>) and set the User and Group to be our "git" user.</p></li>
				<li><p>Open fcgiwrap.socket (<code>$ systemctl edit fcgiwrap.socket</code>) and add "SocketUser=caddy" and "SocketGroup=caddy" under the [Socket] section. (replace with the correct Caddy user for your system)</p></li>
			</ol>

			<h3 id="cfg-cgit">cgit</h3>
			<p>Since cgit is a cgi application, it does not run as its own service. It is simply a script that gets executed by Caddy and piped into fcgiwrap. As such, the setup is pretty simple:</p>
			<ol>
				<li><p>Install cgit with a package manager. This will place the cgit files at /var/www/htdocs/cgit</p></li>
				<li>
					<p>Make sure that location is owned by "git", but is readable by all users:</p>
					<p><code>$ chown -R git /var/www/htdocs/cgit && chmod -R 764 /var/www/htdocs/cgit</code></p>
				</li>
			</ol>

			<h3 id="cfg-caddy">Caddy</h3>
			<p>To complete the set up, add this block to the Caddyfile to serve cgit.</p>
			<pre><code>git.my.tld {
    @assets path /cgit.css /cgit.js /favicon.svg /robots.txt
    handle @assets {
        root * /var/www/htdocs/cgit
        file_server
    }

    reverse_proxy unix//run/fcgiwrap-cgit.sock {
        transport fastcgi {
            env SCRIPT_FILENAME /var/www/htdocs/cgit/cgit.cgi
        }
    }
}</code></pre>

			<h3 id="cfg-shell">Lock down SSH access</h3>
			<p>Using <code>git-shell</code>
				blocks normal shell access and allows only git-style SSH commands.</p>
			<ol>
				<li><p>Create an SSH directory for the <code>git</code>
					user: <code>$ install -d -o git -g git -m 700 /var/lib/cgit/.ssh</code></p></li>
				<li><p>Add your public key to <code>/var/lib/cgit/.ssh/authorized_keys</code>
					with mode <code>600</code>.</p></li>
			</ol>

			<h3 id="cfg-repos">Create some repos</h3>
			<p>Create a bare repository:</p>
			<p><code>$ sudo -u git git init --bare /var/lib/cgit/example</code></p>
			<p>Push to it from your local machine:</p>
			<pre><code>$ git remote add origin git@git.my.tld:example
$ git push -u origin main</code></pre>

			<h3 id="cfg-cgitrc">cgitrc</h3>
			<p>A clean default /etc/cgitrc file might look like this:</p>
			<pre><code>css=/cgit.css
logo=
virtual-root=/

enable-index-owner=0
enable-http-clone=1
scan-path=/var/lib/cgit

root-title=git.my.tld
root-desc=Personal git repositories
snapshots=tar.gz zip</code></pre>

			<h2 id="tradeoffs">Tradeoffs</h2>
			<p>You lose integrated issues, pull requests, built-in CI, orgs, and a social graph.</p>
			<p>In return, you get a tiny stack, fewer moving parts, lower attack surface, lower resource use, and a setup you can understand.</p>
			<p>For my personal projects, that trade is acceptable.</p>
		</div>
	</body>
</html>