๐ (federation): Add user and deployment guides under docs/
Changes
3 files changed, +286 -1
MODIFY
README.md
+2 -1
@@ -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
@@ -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
@@ -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.