gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Commits

๐Ÿ“ (docs): Add Docker Compose deployment guide

d90be31836d6b0eeb517ce6351950f544c4cafd2 ยท Miggi ยท 2026-07-08T15:13:48Z

Changes

2 files changed, +378 -0

ADD docs/README.md +17 -0
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..8582bbb
--- /dev/null
+++ b/docs/README.md
@@ -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
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..7ba95c7
--- /dev/null
+++ b/docs/getting-started.md
@@ -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. |

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog