gitshark

Clone repository

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

← Commits

๐Ÿ“ (federation): Add user and deployment guides under docs/

77d5412f5ef1f08a7497d3a64d1fc89d55333658 ยท Miggi ยท 2026-07-08T19:03:05Z

Changes

3 files changed, +286 -1

MODIFY README.md +2 -1
diff --git a/README.md b/README.md
index 448113e..155b5ab 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,8 @@
40 40 can interoperate. The first goal is **git-shark โ†” git-shark**: a remote instance follows a public
41 41 repository and receives a signed `Push` activity whenever commits land. Standard ActivityStreams +
42 42 ForgeFed vocabulary and HTTP Signatures (RSA) are used throughout, so other ForgeFed software *could*
43 -interoperate later (untested).
43 +interoperate later (untested). Detailed guides: [for users](docs/federation-users.md) and
44 +[for deployment](docs/federation-deployment.md).
44 45
45 46 When enabled, each public repository (and each user) is an actor under `/ap`:
46 47
ADD docs/federation-deployment.md +188 -0
diff --git a/docs/federation-deployment.md b/docs/federation-deployment.md
new file mode 100644
index 0000000..e514fd6
--- /dev/null
+++ b/docs/federation-deployment.md
@@ -0,0 +1,188 @@
1 +# Federation: deployment guide
2 +
3 +How to enable and operate ForgeFed/ActivityPub federation on a git-shark
4 +instance. For what federation offers end users, see the
5 +[user guide](federation-users.md); for a general deployment walkthrough, see
6 +[Getting Started](getting-started.md).
7 +
8 +Federation is **off by default** and fails closed: nothing is emitted or
9 +accepted until it is explicitly enabled *and* correctly configured.
10 +
11 +---
12 +
13 +## Configuration
14 +
15 +All settings come from environment variables:
16 +
17 +| Variable | Default | Meaning |
18 +|---|---|---|
19 +| `GITSHARK_FEDERATION_ENABLED` | `false` | Master switch |
20 +| `GITSHARK_FEDERATION_BASE_URL` | โ€” | Public HTTPS origin of this instance, e.g. `https://shark.example.com`. Actor IDs derive from it |
21 +| `GITSHARK_FEDERATION_PEER_ALLOWLIST` | โ€” (empty) | Comma-separated peer **hosts**, e.g. `shark.other.org,forge.example`. Empty denies every remote peer |
22 +| `GITSHARK_FEDERATION_MAX_ATTEMPTS` | `8` | Delivery attempts before an outbound activity is dead-lettered |
23 +| `GITSHARK_FEDERATION_DEV_ALLOW_INSECURE` | `false` | **Dev only.** Permits `http://` and loopback/private targets. Never in production |
24 +
25 +Minimal production setup:
26 +
27 +```bash
28 +GITSHARK_FEDERATION_ENABLED=true
29 +GITSHARK_FEDERATION_BASE_URL=https://shark.example.com
30 +GITSHARK_FEDERATION_PEER_ALLOWLIST=shark.other.org
31 +```
32 +
33 +> **`base-url` is permanent.** Actor IDs (`https://<base-url>/ap/...`) are
34 +> published to other servers and cannot change without breaking every existing
35 +> follow relationship. Set a real, stable, non-loopback HTTPS origin before
36 +> enabling. git-shark refuses to operate federation (`operational = false`) while
37 +> the base URL is unset, loopback, or otherwise unusable โ€” the switch alone is
38 +> not enough.
39 +
40 +### The peer allowlist
41 +
42 +Federation is mutual and allowlist-bound in **both** directions:
43 +
44 +- **Inbound**: an activity is only accepted if its HTTP Signature's key belongs
45 + to an actor on an allowlisted host. Everything else is `401`.
46 +- **Outbound**: actor fetches, WebFinger lookups, and deliveries only go to
47 + allowlisted hosts.
48 +
49 +Matching is by exact, case-insensitive host name. Both instances must allowlist
50 +each other, or follows stay Pending forever.
51 +
52 +---
53 +
54 +## What gets exposed
55 +
56 +With federation operational, these endpoints are public (no login):
57 +
58 +| Endpoint | Purpose |
59 +|---|---|
60 +| `GET /.well-known/webfinger?resource=acct:โ€ฆ` | Actor discovery (`user@host`, `owner/name@host`; the host part may be bare or `host:port`) |
61 +| `GET /ap/repos/{owner}/{name}` | ForgeFed `Repository` actor (public repos only) |
62 +| `GET /ap/users/{username}` | ActivityPub `Person` actor |
63 +| `GET /ap/instance` | Instance `Application` actor |
64 +| `GET โ€ฆ/outbox`, `โ€ฆ/followers` | Activity and follower collections |
65 +| `POST โ€ฆ/inbox` | Signed inbound activities (repo, user, and instance inboxes) |
66 +
67 +Private repositories are never exposed. Inbound posts are verified (HTTP
68 +Signature, body digest, date skew), deduplicated by activity id, then dispatched.
69 +
70 +---
71 +
72 +## Reverse proxy requirements
73 +
74 +Federation signs and verifies the `Host` header and the raw request path.
75 +The proxy in front of git-shark must therefore:
76 +
77 +- **Preserve the original `Host` header** (`proxy_set_header Host $host;` in
78 + nginx; Caddy does this by default). A rewritten host breaks signature
79 + verification on every inbound activity.
80 +- Pass `/ap/*` and `/.well-known/webfinger` through **without** auth,
81 + path rewriting, or body buffering surprises.
82 +- Terminate TLS โ€” peers will only talk `https` to you.
83 +
84 +The existing `quarkus.http.proxy.*` settings (already on in the default config)
85 +make git-shark trust `X-Forwarded-*` so actor URLs and signature reconstruction
86 +use the external origin.
87 +
88 +---
89 +
90 +## Keys and signing
91 +
92 +- Every local actor (each public repository, each user, plus the instance) gets
93 + an **RSA-2048 keypair**, generated on first use and stored in the
94 + `federation_keys` table. There is no key configuration and currently no
95 + rotation mechanism; protect your database accordingly.
96 +- Outbound activities are signed with draft-cavage HTTP Signatures
97 + (`SHA256withRSA`), key id `<actor-id>#main-key`.
98 +- Outbound follows are signed as the **user's `Person` actor**; `Accept` and
99 + `Push` fan-out are signed as the repository actor.
100 +
101 +---
102 +
103 +## Delivery queue and retries
104 +
105 +Outbound activities go through a persisted queue (`federation_delivery` table),
106 +drained every 10 seconds:
107 +
108 +- Failures retry with exponential backoff: 1m, 2m, 4m, โ€ฆ capped at 1h.
109 +- After `GITSHARK_FEDERATION_MAX_ATTEMPTS` (default 8) the row is dead-lettered
110 + as `FAILED` with the last error preserved โ€” it will not retry again.
111 +- States: `PENDING` โ†’ `DELIVERED` or `FAILED`.
112 +
113 +Monitoring queries worth having:
114 +
115 +```sql
116 +-- stuck or failing deliveries
117 +select target_inbox, attempts, state, last_error, next_attempt_at
118 +from federation_delivery
119 +where state <> 'DELIVERED'
120 +order by created_at desc;
121 +
122 +-- follow relationships still waiting for the remote's Accept
123 +select remote_actor_id, state, created_at from remote_follows where state = 'PENDING';
124 +```
125 +
126 +---
127 +
128 +## Data stored for federation
129 +
130 +| Table | Contents |
131 +|---|---|
132 +| `federation_keys` | Local actor keypairs (public + private PEM) |
133 +| `remote_actors` | Cache of fetched remote actors (inbox, public key; 6h TTL) |
134 +| `repository_followers` | Remote actors following local repositories |
135 +| `remote_follows` | Local users' follows of remote repositories (`PENDING`/`ACCEPTED`) |
136 +| `received_pushes` | `Push` activities received from followed repositories (feed) |
137 +| `federation_outbox` / `federation_inbox` | Published activities / inbound dedup log |
138 +| `federation_delivery` | Outbound delivery queue |
139 +
140 +---
141 +
142 +## SSRF protection
143 +
144 +All outbound federation traffic (actor fetches, WebFinger, deliveries) passes a
145 +URL guard: HTTPS only, host must be allowlisted, and the resolved address must
146 +not be loopback, link-local, private, or multicast. `dev-allow-insecure` relaxes
147 +the scheme and address checks **but never the allowlist**.
148 +
149 +---
150 +
151 +## Local two-host trial (dev)
152 +
153 +To try federation on one machine, run two dev instances that allowlist each
154 +other:
155 +
156 +```bash
157 +# terminal A
158 +GITSHARK_FEDERATION_ENABLED=true \
159 +GITSHARK_FEDERATION_BASE_URL=http://localhost:8080 \
160 +GITSHARK_FEDERATION_PEER_ALLOWLIST=localhost \
161 +GITSHARK_FEDERATION_DEV_ALLOW_INSECURE=true \
162 +./mvnw quarkus:dev
163 +
164 +# terminal B (second checkout or worktree)
165 +GITSHARK_FEDERATION_ENABLED=true \
166 +GITSHARK_FEDERATION_BASE_URL=http://localhost:8081 \
167 +GITSHARK_FEDERATION_PEER_ALLOWLIST=localhost \
168 +GITSHARK_FEDERATION_DEV_ALLOW_INSECURE=true \
169 +GITSHARK_SSH_PORT=2223 \
170 +./mvnw quarkus:dev -Dquarkus.http.port=8081 -Ddebug=false \
171 + -Dquarkus.datasource.devservices.shared=false
172 +```
173 +
174 +On B, log in and follow `alice/demo@localhost:8080` (dev seed data) from the
175 +Following page. The follow flips to Accepted within ~20s; pushing to the demo
176 +repo on A makes the push appear under "Recent pushes" on B.
177 +
178 +---
179 +
180 +## Troubleshooting
181 +
182 +| Symptom | Likely cause |
183 +|---|---|
184 +| Follow stays `PENDING` | Peer not allowlisted (either side), remote down, or delivery dead-lettered โ€” check `federation_delivery.last_error` |
185 +| Inbound activities all `401` | Signer host not on your allowlist, or the reverse proxy rewrites the `Host` header |
186 +| `Could not resolve handle` | Remote host not allowlisted locally, WebFinger unreachable, or repo not public |
187 +| Actor documents `404` | Federation not operational: switch off, or `base-url` unset/loopback |
188 +| Deliveries fail with `Only https is allowed` | Peer published an `http://` inbox/actor URL and you are not in dev-insecure mode |
ADD docs/federation-users.md +96 -0
diff --git a/docs/federation-users.md b/docs/federation-users.md
new file mode 100644
index 0000000..2581fc0
--- /dev/null
+++ b/docs/federation-users.md
@@ -0,0 +1,96 @@
1 +# Federation: user guide
2 +
3 +git-shark instances can talk to each other over [ForgeFed](https://forgefed.org)
4 +(ActivityPub). For you as a user that means: you can **follow a public repository
5 +that lives on another instance** and see its pushes on your own instance โ€” no
6 +account on the remote server needed.
7 +
8 +> Federation is optional and off by default. If the features below are missing on
9 +> your instance, the operator has not enabled it (see the
10 +> [deployment guide](federation-deployment.md)).
11 +
12 +---
13 +
14 +## What you can do
15 +
16 +- **Follow a remote public repository** and get its `Push` activity feed.
17 +- **Unfollow** it again.
18 +- **Be discovered**: your user and every public repository on your instance are
19 + visible to other ForgeFed servers.
20 +
21 +What is *not* federated (yet): issues, merge requests, comments, forks, and
22 +anything on private repositories. Private repositories are never exposed to
23 +federation at all.
24 +
25 +---
26 +
27 +## Following a remote repository
28 +
29 +Open **Following** in the header navigation (you must be logged in), then enter
30 +either form of address:
31 +
32 +| Form | Example | When to use |
33 +|---|---|---|
34 +| Handle | `alice/demo@shark.example.com` | You know owner, name, and host |
35 +| Actor URL | `https://shark.example.com/ap/repos/alice/demo` | You have a direct link |
36 +
37 +The handle is resolved via WebFinger on the remote host; both resolve to the same
38 +repository actor.
39 +
40 +After submitting, the follow appears in your list as **Pending**: your instance
41 +has sent a signed `Follow` and is waiting for the remote's `Accept`. Both
42 +directions go through delivery queues that run every few seconds, so expect
43 +**Pending โ†’ Accepted within roughly half a minute**. Refresh the page to see the
44 +state change.
45 +
46 +If it stays Pending for long, the remote is unreachable, not allowlisted by your
47 +instance (or vice versa โ€” federation requires **both** operators to allowlist each
48 +other), or the repository doesn't exist / isn't public. Your operator can check
49 +the delivery queue for the exact error.
50 +
51 +### Common errors when following
52 +
53 +| Message | Meaning |
54 +|---|---|
55 +| `Could not resolve handle` | WebFinger lookup failed: typo, host down, or host not on your instance's peer allowlist |
56 +| `Could not resolve remote repository` | Actor URL didn't fetch: not a ForgeFed actor, private, or blocked by the allowlist |
57 +| `Choose a username before following` | Your account hasn't finished onboarding โ€” pick a username first |
58 +
59 +---
60 +
61 +## The "Recent pushes" feed
62 +
63 +Once a follow is in place, pushes to the followed repository arrive as signed
64 +`Push` activities and show up in the **Recent pushes** section of the Following
65 +page: repository, ref, a summary like `Pushed 2 commit(s) to refs/heads/main`,
66 +and when it was received. The feed shows the newest 50 entries across everything
67 +you follow.
68 +
69 +Only pushes from repositories somebody on your instance follows are stored;
70 +everything else is dropped on arrival.
71 +
72 +---
73 +
74 +## Unfollowing
75 +
76 +Hit **Unfollow** next to the entry. Your instance sends an `Undo(Follow)` to the
77 +remote so it stops delivering, and the entry disappears from your list.
78 +
79 +---
80 +
81 +## Your federated identity
82 +
83 +When federation is enabled, you exist to other servers as an ActivityPub `Person`:
84 +
85 +- Actor: `https://<your-host>/ap/users/<username>`
86 +- WebFinger: `acct:<username>@<your-host>`
87 +
88 +Each public repository is a ForgeFed `Repository` actor
89 +(`https://<your-host>/ap/repos/<owner>/<name>`, WebFinger
90 +`acct:<owner>/<name>@<your-host>`). Remote users can follow your public
91 +repositories the same way you follow theirs; their servers receive your pushes
92 +automatically. Followers of a repository are public at
93 +`โ€ฆ/ap/repos/<owner>/<name>/followers`.
94 +
95 +Signing keys for your actors are generated and managed by the server โ€” there is
96 +nothing for you to configure.

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog