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
|
# Installation
gitpocket installs either as a container built from this repository or as a single binary under systemd. Both keep every piece of state in one data directory, `/srv/gitpocket` by default, and both expect nginx or another TLS proxy in front and the host's own sshd for git over SSH.
## Requirements
A Linux host with git 2.38 or newer, which `merge-tree --write-tree` needs, and OpenSSH. Building from source needs Go 1.27 or newer, and the container route needs Docker with the compose plugin. 512 MB of memory is enough for a small instance.
## Prepare the host
Create the account that owns the data and receives SSH logins. Its name appears in clone URLs, so `git` is the usual choice.
```
useradd -r -m -d /srv/gitpocket -s /bin/sh git
```
## Docker
The compose file runs the server as the `git` account with a read only root filesystem, no capabilities, `no-new-privileges`, a 512 MB memory ceiling and the port bound to loopback only. It mounts `/srv/gitpocket` from the host, so the data outlives the container.
```
git clone https://gitpocket.io/~gitpocket/gitpocket /opt/gitpocket
cd /opt/gitpocket
printf 'GITPOCKET_HOST=git.example.org\nGITPOCKET_UID=%s\nGITPOCKET_GID=%s\n' $(id -u git) $(id -g git) > deploy/.env
docker compose -f deploy/compose.yaml up -d --build
```
The server trusts `X-Forwarded-For` from loopback and from the compose network gateway, `172.31.255.1`, which is where connections from the host proxy appear. Change the subnet in `deploy/compose.yaml` if it collides with one you already use.
SSH logins are answered on the host, so copy the binary out of the image to the same path it has inside it. Repository hooks name that path, and the container and the host must agree on it.
```
docker cp gitpocket:/usr/local/bin/gitpocket /usr/local/bin/gitpocket
```
## Binary
```
git clone https://gitpocket.io/~gitpocket/gitpocket && cd gitpocket
CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o gitpocket .
install -m 755 gitpocket /usr/local/bin/gitpocket
install -m 644 deploy/gitpocket.service /etc/systemd/system/
```
Replace `git.example.org` in `/etc/systemd/system/gitpocket.service` with your hostname, then start it.
```
systemctl daemon-reload
systemctl enable --now gitpocket
```
The unit sets `GOMEMLIMIT` to 192 MiB, caps the server and every git it starts at 512 MB, and allows writes only beneath `/srv/gitpocket`.
## Reverse proxy
`deploy/nginx.conf` is a complete site. Set `server_name` and the certificate paths, then install it.
```
cp deploy/nginx.conf /etc/nginx/sites-available/gitpocket
ln -s ../sites-available/gitpocket /etc/nginx/sites-enabled/
certbot certonly --nginx -d git.example.org
nginx -t && systemctl reload nginx
```
Whatever proxy you use has to forward `Host` and `X-Forwarded-Proto`, set `X-Forwarded-For` to the client address, turn request buffering off and allow large bodies, otherwise bigger HTTPS pushes are cut short. The server takes the last address in `X-Forwarded-For`, so a client cannot pick its own rate limit bucket.
## SSH
Add this to the end of `/etc/ssh/sshd_config` and reload sshd.
```
Match User git
AuthenticationMethods publickey
PasswordAuthentication no
AuthorizedKeysFile none
AuthorizedKeysCommand /usr/local/bin/gitpocket -root /srv/gitpocket keys %u %h %t %k
AuthorizedKeysCommandUser root
PermitTTY no
DisableForwarding yes
```
`keys` opens the database read only and answers with a forced command of `gitpocket shell <user>`, which checks the requested repository and permission before it execs `git upload-pack` or `git receive-pack`. Nothing is left running after the push. If the account is not called `git`, pass `-ssh-user` to `serve` so the clone URLs match.
## First run
Open the site, sign up, then grant yourself administration and choose how others join.
```
sudo -u git gitpocket admin you
sudo -u git gitpocket registration invite
```
Under Docker the same commands run as `docker exec gitpocket gitpocket admin you`. Registration starts open, `invite` hands out single use links from `/admin`, and `closed` leaves `gitpocket user <name>` as the only way in, with the password read from stdin.
## Configuration
`serve` takes its settings as flags.
| Flag | Default | Meaning |
|---|---|---|
| `-host` | `localhost` | public hostname used in clone URLs, mail and feeds |
| `-name` | `gitpocket` | site name shown in the header |
| `-listen` | `127.0.0.1:3200` | address the HTTP server binds |
| `-ssh-user` | `git` | account shown in SSH clone URLs |
| `-proxy` | none | comma separated networks, besides loopback, whose `X-Forwarded-For` is trusted |
The data directory holds a few optional files, each read once at startup. `logo.png` replaces the header logo and `icon.png` the favicon. `smtp.json` enables mail, and `gitpocket quota 2G` sets a per owner disk quota that pushes are checked against.
## Mail
Mail is optional. Without it nobody receives notifications and password resets go through `gitpocket passwd <name>`. Plain SMTP takes `host`, `port`, `user`, `password`, `from` and `starttls`. Many VPS providers block outbound submission ports, so `api` and `key` post to a Resend compatible endpoint instead, and `account` and `token` use the Cloudflare email API.
```
{"api": "https://api.resend.com/emails", "key": "re_...", "from": "gitpocket <noreply@git.example.org>"}
```
Save it as `/srv/gitpocket/smtp.json`, readable only by `git`, restart the server and send a test with `gitpocket mail you@example.org`.
## Backups
`gitpocket backup <dir>` writes a dated copy of the database with `vacuum into`, keeps the last fourteen, and bundles every repository whose refs changed since its previous bundle and copies new image attachments. The supplied timer runs it nightly into `/srv/gitpocket/backups`, which should then be copied off the host.
```
install -m 644 deploy/gitpocket-backup.service deploy/gitpocket-backup.timer /etc/systemd/system/
systemctl enable --now gitpocket-backup.timer
```
To restore, stop the server, put the newest database back as `gitpocket.db`, and `git clone --mirror` each bundle to `repos/<owner>/<name>.git` and copy `attachments` back. The server reinstalls repository hooks when it starts.
## Upgrading
Back up first. Schema changes are applied automatically when the server starts.
```
cd /opt/gitpocket && git pull
docker compose -f deploy/compose.yaml up -d --build
docker cp gitpocket:/usr/local/bin/gitpocket /usr/local/bin/gitpocket
```
For a binary install, rebuild, `install` the new binary over the old one and `systemctl restart gitpocket`.
|