๐ (docs): Add Docker Compose deployment guide
Changes
2 files changed, +378 -0
ADD
docs/README.md
+17 -0
@@ -0,0 +1,17 @@
1
+# git-shark documentation ๐ฆ
2
+
3
+Self-hosted Git platform as a single, natively-compiled Quarkus service โ bare Git
4
+repositories on disk served over smart HTTP and SSH, a server-rendered Qute web UI,
5
+OIDC login, and PostgreSQL metadata.
6
+
7
+## Contents
8
+
9
+- **[Getting Started](getting-started.md)** โ deploy git-shark with Docker Compose,
10
+ from zero to a running instance behind TLS with OIDC login and SSH git access.
11
+
12
+## Where else to look
13
+
14
+- [`../README.md`](../README.md) โ feature overview and architecture notes.
15
+- Application configuration lives in `src/main/resources/application.properties`.
16
+ Every production knob is overridable via an environment variable (listed in
17
+ [Getting Started โ Configuration reference](getting-started.md#configuration-reference)).
ADD
docs/getting-started.md
+361 -0
@@ -0,0 +1,361 @@
1
+# Getting Started: deploying git-shark with Docker Compose
2
+
3
+This guide takes you from an empty host to a running git-shark instance:
4
+
5
+1. A PostgreSQL database (metadata store).
6
+2. The git-shark application container (web UI + smart-HTTP git + embedded SSH).
7
+3. A TLS-terminating reverse proxy in front (git-shark requires HTTPS).
8
+4. An external OIDC provider for login (kanidm, Keycloak, or any OpenID Connect IdP).
9
+
10
+By the end you can browse the web UI over HTTPS, log in with OIDC, and clone/push
11
+over both `https://` and `ssh://`.
12
+
13
+> **git-shark has no built-in password login.** Authentication to the web UI is
14
+> delegated entirely to an OIDC provider. You must have one reachable (or stand one
15
+> up) before the app is usable. Git transport authenticates separately โ personal
16
+> access tokens over HTTP Basic, SSH public keys over SSH.
17
+
18
+---
19
+
20
+## Prerequisites
21
+
22
+- **Docker Engine 24+** and the **Docker Compose v2** plugin (`docker compose`, not the
23
+ legacy `docker-compose`).
24
+- A **DNS name** pointing at the host (e.g. `gitshark.example.com`). OIDC redirect URIs
25
+ and โ if you enable federation โ permanent actor IDs are derived from it.
26
+- An **OIDC provider** with an authorization-code client for git-shark. PKCE is required.
27
+- Ports **80/443** (reverse proxy) and **2222** (SSH git) reachable from your clients.
28
+
29
+---
30
+
31
+## Step 1 โ Get the application image
32
+
33
+Use the prebuilt image published to GitHub Container Registry โ nothing to build:
34
+
35
+```bash
36
+docker pull ghcr.io/workaround-org/git-shark:latest
37
+```
38
+
39
+Pin a specific release instead of `latest` for reproducible deploys (e.g.
40
+`ghcr.io/workaround-org/git-shark:1.0.0`).
41
+
42
+The image listens on **8080** (HTTP) and, once configured, **2222** (SSH). It runs as
43
+UID `185` and reads all production settings from environment variables.
44
+
45
+> **Building it yourself instead.** git-shark ships a JVM Dockerfile, so from the repo
46
+> root you can build a local image and point the Compose file's `image:` at it:
47
+>
48
+> ```bash
49
+> ./mvnw package # produces target/quarkus-app/
50
+> docker build -f src/main/docker/Dockerfile.jvm -t git-shark:local .
51
+> ```
52
+>
53
+> For a smaller, faster-starting image build the native variant (`-Dnative` with
54
+> `Dockerfile.native-micro`).
55
+
56
+---
57
+
58
+## Step 2 โ Register the OIDC client
59
+
60
+git-shark uses the OIDC **authorization code flow** with **PKCE**. Create a confidential
61
+client at your IdP and note three things: the **issuer/discovery URL**, the **client ID**,
62
+and the **client secret**. Set the redirect URI to your public origin with a trailing
63
+slash: `https://gitshark.example.com/`.
64
+
65
+### kanidm example
66
+
67
+```bash
68
+kanidm system oauth2 create git-shark "Git Shark" https://gitshark.example.com
69
+kanidm system oauth2 add-redirect-url git-shark https://gitshark.example.com/
70
+kanidm group create gitshark_users
71
+kanidm group add-members gitshark_users <your-user>
72
+kanidm system oauth2 update-scope-map git-shark gitshark_users openid profile email
73
+kanidm system oauth2 show-basic-secret git-shark # -> client secret
74
+```
75
+
76
+The auth-server URL for kanidm is `https://<kanidm-host>/oauth2/openid/git-shark`.
77
+
78
+### Keycloak / other IdPs
79
+
80
+Create a confidential client with:
81
+- Standard flow (authorization code) enabled, PKCE `S256` required.
82
+- Redirect URI `https://gitshark.example.com/`.
83
+- Scopes `openid profile email`.
84
+
85
+The auth-server URL is the realm issuer, e.g.
86
+`https://keycloak.example.com/realms/<realm>`.
87
+
88
+---
89
+
90
+## Step 3 โ Generate the encryption secrets
91
+
92
+Two secrets encrypt the PKCE state cookie and the post-login session cookie. Each must
93
+be **at least 32 characters** (Quarkus minimum). Generate them once and keep them stable
94
+โ rotating them invalidates in-flight logins and existing sessions.
95
+
96
+```bash
97
+openssl rand -hex 16 # 32 hex chars โ run twice, for the two secrets below
98
+```
99
+
100
+---
101
+
102
+## Step 4 โ Write the `.env` file
103
+
104
+Compose reads these values. Put the file next to `docker-compose.yml`, keep it out of
105
+version control (it holds secrets).
106
+
107
+```dotenv
108
+# --- Public origin ---
109
+APP_DOMAIN=gitshark.example.com
110
+
111
+# --- PostgreSQL ---
112
+POSTGRES_DB=gitshark
113
+POSTGRES_USER=gitshark
114
+POSTGRES_PASSWORD=change-me-strong-db-password
115
+
116
+# --- OIDC (from Step 2) ---
117
+OIDC_AUTH_SERVER_URL=https://idm.example.com/oauth2/openid/git-shark
118
+OIDC_CLIENT_ID=git-shark
119
+OIDC_CLIENT_SECRET=the-basic-secret-from-your-idp
120
+
121
+# --- OIDC cookie encryption (from Step 3, >= 32 chars each) ---
122
+OIDC_STATE_SECRET=paste-first-openssl-rand-output
123
+OIDC_TOKEN_STATE_SECRET=paste-second-openssl-rand-output
124
+```
125
+
126
+---
127
+
128
+## Step 5 โ The Compose file
129
+
130
+```yaml
131
+name: git-shark
132
+
133
+services:
134
+ db:
135
+ image: postgres:17
136
+ restart: unless-stopped
137
+ environment:
138
+ POSTGRES_DB: ${POSTGRES_DB}
139
+ POSTGRES_USER: ${POSTGRES_USER}
140
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
141
+ volumes:
142
+ - db-data:/var/lib/postgresql/data
143
+ healthcheck:
144
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
145
+ interval: 10s
146
+ timeout: 5s
147
+ retries: 5
148
+
149
+ app:
150
+ image: ghcr.io/workaround-org/git-shark:latest # or a pinned tag; see Step 1
151
+ restart: unless-stopped
152
+ depends_on:
153
+ db:
154
+ condition: service_healthy
155
+ environment:
156
+ # --- Datasource ---
157
+ QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://db:5432/${POSTGRES_DB}
158
+ QUARKUS_DATASOURCE_USERNAME: ${POSTGRES_USER}
159
+ QUARKUS_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD}
160
+ # --- OIDC ---
161
+ QUARKUS_OIDC_AUTH_SERVER_URL: ${OIDC_AUTH_SERVER_URL}
162
+ QUARKUS_OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
163
+ QUARKUS_OIDC_CREDENTIALS_SECRET: ${OIDC_CLIENT_SECRET}
164
+ QUARKUS_OIDC_AUTHENTICATION_STATE_SECRET: ${OIDC_STATE_SECRET}
165
+ QUARKUS_OIDC_TOKEN_STATE_ENCRYPTION_SECRET: ${OIDC_TOKEN_STATE_SECRET}
166
+ # --- Storage & SSH ---
167
+ GITSHARK_STORAGE_ROOT: /data/repositories
168
+ GITSHARK_SSH_HOST_KEY: /data/ssh/host-key
169
+ GITSHARK_SSH_PORT: "2222"
170
+ ports:
171
+ - "2222:2222" # SSH git access, published directly
172
+ volumes:
173
+ - repos:/data/repositories # bare git repositories
174
+ - ssh:/data/ssh # persistent SSH host key
175
+ healthcheck:
176
+ test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8080 && echo ok >&3"]
177
+ interval: 15s
178
+ timeout: 5s
179
+ retries: 10
180
+ start_period: 40s
181
+
182
+volumes:
183
+ db-data:
184
+ repos:
185
+ ssh:
186
+```
187
+
188
+Notes:
189
+
190
+- **Separate volumes for `/data/repositories` and `/data/ssh`** so Docker creates both
191
+ mount points with the right ownership โ no init container or `mkdir` needed. The SSH
192
+ host key is generated on first boot and persists across restarts (so client
193
+ `known_hosts` entries stay valid).
194
+- **HTTP port 8080 is not published** โ it's reached through the reverse proxy on the
195
+ Compose network (Step 6). Only SSH (2222) is exposed directly.
196
+- **Single app replica.** git-shark keeps git state on a `ReadWriteOnce`-style filesystem
197
+ volume; do not scale `app` beyond one instance.
198
+- Flyway migrates the schema automatically at startup (`migrate-at-start=true`), so the
199
+ database needs no manual initialization beyond an empty database + owner.
200
+
201
+---
202
+
203
+## Step 6 โ TLS reverse proxy (required)
204
+
205
+git-shark always builds **HTTPS** OIDC redirect URIs and trusts `X-Forwarded-*` headers
206
+(`force-redirect-https-scheme=true`, `proxy-address-forwarding=true`). It is designed to
207
+run behind a TLS-terminating proxy โ plain HTTP will break the login redirect.
208
+
209
+Add a Caddy service to the Compose file โ it fetches and renews a Let's Encrypt
210
+certificate automatically:
211
+
212
+```yaml
213
+ proxy:
214
+ image: caddy:2
215
+ restart: unless-stopped
216
+ depends_on:
217
+ - app
218
+ ports:
219
+ - "80:80"
220
+ - "443:443"
221
+ volumes:
222
+ - ./Caddyfile:/etc/caddy/Caddyfile:ro
223
+ - caddy-data:/data
224
+ - caddy-config:/config
225
+```
226
+
227
+Add `caddy-data:` and `caddy-config:` to the top-level `volumes:` block, then create a
228
+`Caddyfile` next to the Compose file:
229
+
230
+```caddyfile
231
+gitshark.example.com {
232
+ reverse_proxy app:8080
233
+}
234
+```
235
+
236
+Caddy forwards `X-Forwarded-Proto`/`-For`/`-Host` by default, which is exactly what
237
+git-shark's OIDC redirect construction needs. Using Traefik or nginx instead is fine โ
238
+just terminate TLS and forward those headers.
239
+
240
+---
241
+
242
+## Step 7 โ Bring it up
243
+
244
+```bash
245
+docker compose up -d
246
+docker compose logs -f app # watch for "Listening on: http://0.0.0.0:8080"
247
+```
248
+
249
+Then open `https://gitshark.example.com/`, click **Log in**, and complete the OIDC flow.
250
+On first login you're redirected to `/onboarding` to pick a URL-safe handle
251
+(`^[a-z0-9][a-z0-9-]{0,38}$`) โ this handle, not the IdP username, appears in all repo,
252
+SSH, and federation URLs.
253
+
254
+---
255
+
256
+## Step 8 โ Verify git access
257
+
258
+**HTTP** (anonymous read on public repos; push/private read use a personal access token
259
+as the HTTP Basic password โ create one under *Access tokens* in the UI):
260
+
261
+```bash
262
+git clone https://gitshark.example.com/git/<owner>/<repo>.git
263
+```
264
+
265
+**SSH** (public-key only; add your key under *SSH keys* in the UI):
266
+
267
+```bash
268
+git clone ssh://git@gitshark.example.com:2222/<owner>/<repo>.git
269
+```
270
+
271
+> Want bare `git@gitshark.example.com` without the `:2222`? Publish the container's 2222
272
+> on host port 22 (`"22:2222"`) โ but only if the host's own sshd isn't already using 22.
273
+
274
+---
275
+
276
+## Configuration reference
277
+
278
+Every value below is an environment variable on the `app` service. Defaults come from
279
+`src/main/resources/application.properties`.
280
+
281
+| Variable | Required | Default | Purpose |
282
+|---|---|---|---|
283
+| `QUARKUS_DATASOURCE_JDBC_URL` | โ
| โ | PostgreSQL JDBC URL |
284
+| `QUARKUS_DATASOURCE_USERNAME` | โ
| โ | DB user |
285
+| `QUARKUS_DATASOURCE_PASSWORD` | โ
| โ | DB password |
286
+| `QUARKUS_OIDC_AUTH_SERVER_URL` | โ
| โ | OIDC issuer / discovery URL |
287
+| `QUARKUS_OIDC_CLIENT_ID` | โ
| โ | OIDC client ID |
288
+| `QUARKUS_OIDC_CREDENTIALS_SECRET` | โ
| โ | OIDC client secret |
289
+| `QUARKUS_OIDC_AUTHENTICATION_STATE_SECRET` | โ
| โ | Encrypts PKCE state cookie (โฅ 32 chars) |
290
+| `QUARKUS_OIDC_TOKEN_STATE_ENCRYPTION_SECRET` | โ
| โ | Encrypts session/token cookie (โฅ 32 chars) |
291
+| `GITSHARK_STORAGE_ROOT` | โ | `data/repositories` | On-disk bare-repo root |
292
+| `GITSHARK_SSH_HOST_KEY` | โ | `data/ssh/host-key` | Persistent SSH host key path |
293
+| `GITSHARK_SSH_PORT` | โ | `2222` | Embedded SSH server port |
294
+| `GITSHARK_FEDERATION_ENABLED` | โ | `false` | Turn on ForgeFed/ActivityPub |
295
+| `GITSHARK_FEDERATION_BASE_URL` | โ | โ | Public HTTPS origin; permanent actor-ID base |
296
+| `GITSHARK_FEDERATION_PEER_ALLOWLIST` | โ | โ | Comma-separated peer hosts (empty denies all) |
297
+| `GITSHARK_FEDERATION_MAX_ATTEMPTS` | โ | `8` | Outbound delivery retry cap |
298
+| `GITSHARK_FEDERATION_DEV_ALLOW_INSECURE` | โ | `false` | Dev only: allow http/loopback peers |
299
+
300
+### Optional: federation (ForgeFed)
301
+
302
+Off by default. Enabling it publishes **permanent** actor IDs derived from
303
+`GITSHARK_FEDERATION_BASE_URL`, so set a real, stable, non-loopback HTTPS origin before
304
+turning it on โ git-shark refuses to emit actor documents otherwise.
305
+
306
+```yaml
307
+ GITSHARK_FEDERATION_ENABLED: "true"
308
+ GITSHARK_FEDERATION_BASE_URL: https://gitshark.example.com
309
+ GITSHARK_FEDERATION_PEER_ALLOWLIST: peer-a.example,peer-b.example
310
+```
311
+
312
+Inbound activities need a valid HTTP Signature from an allowlisted peer; outbound
313
+fetches are HTTPS-only, allowlist-bound, and SSRF-guarded. Never set
314
+`GITSHARK_FEDERATION_DEV_ALLOW_INSECURE=true` in production.
315
+
316
+---
317
+
318
+## Operations
319
+
320
+**Backups** โ two things hold state:
321
+- The `db-data` volume (metadata: users, repo records, issues, MRs, comments).
322
+- The `repos` volume (the actual git objects).
323
+
324
+Back both up together and consistently. A logical DB dump:
325
+
326
+```bash
327
+docker compose exec db pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" > gitshark-db.sql
328
+```
329
+
330
+Snapshot the `repos` volume with your host's volume/snapshot tooling while the app is
331
+quiesced (or accept crash-consistent snapshots โ bare repos tolerate them well).
332
+
333
+**Upgrades** โ pull the new image and recreate the app:
334
+
335
+```bash
336
+docker compose pull app
337
+docker compose up -d app
338
+```
339
+
340
+Flyway applies any new migrations on startup. Because the app uses the `Recreate`
341
+pattern (one writer, filesystem state), a brief downtime during redeploy is expected.
342
+
343
+**Logs & health**:
344
+
345
+```bash
346
+docker compose ps
347
+docker compose logs -f app
348
+```
349
+
350
+---
351
+
352
+## Troubleshooting
353
+
354
+| Symptom | Likely cause |
355
+|---|---|
356
+| Login redirects to `http://โฆ` or loops | Proxy not forwarding `X-Forwarded-Proto`, or you hit the app over plain HTTP. Front it with TLS (Step 6). |
357
+| Boot fails on OIDC discovery | `QUARKUS_OIDC_AUTH_SERVER_URL` wrong/unreachable, or IdP demands HTTPS the app can't reach. |
358
+| App exits complaining about secret length | `*_STATE_SECRET` shorter than 32 chars. Regenerate with `openssl rand -hex 16`. |
359
+| SSH host key changed after redeploy | The `ssh` volume wasn't persisted โ confirm it's a named volume, not a throwaway mount. |
360
+| `git push` over HTTP rejected | Use a personal access token (from *Access tokens*) as the Basic-auth password, not your OIDC password. |
361
+| Schema validation error at start | DB not empty / migrated by a different tool. git-shark's Flyway owns the schema; start from an empty database. |