Skip to content

Commit ffffc50

Browse files
authored
Merge pull request #484 from dahlia/feature/split-domain
Split-domain WebFinger handle
2 parents 9a562c2 + 1dc2068 commit ffffc50

24 files changed

Lines changed: 1063 additions & 36 deletions

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ STORAGE_URL_BASE=https://your-bucket.s3.amazonaws.com
428428
| `REMOTE_REPLIES_SCRAPE_COOLDOWN_SECONDS` | 300 | Completed scrape deduplication window |
429429
| `MEDIA_PROXY` | off | Remote media proxy: `off`, `proxy`, `cache` (booleans accepted: `true``proxy`, `false``off`) |
430430
| `REMOTE_MEDIA_THUMBNAILS` | on | Generate local sharp thumbnails for remote attachments (boolean) |
431+
| `HANDLE_HOST` | - | Split-domain WebFinger handle host (e.g. `example.com`); must be set together with `WEB_ORIGIN` |
432+
| `WEB_ORIGIN` | - | Split-domain ActivityPub server origin (e.g. `https://ap.example.com`); must be set together with `HANDLE_HOST` |
431433

432434

433435
Adding new environment variables

CHANGES.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@ Version 0.9.0
66

77
To be released.
88

9+
- Added optional split-domain WebFinger support. When the new
10+
`HANDLE_HOST` and `WEB_ORIGIN` environment variables are set,
11+
Hollo uses Fedify's `origin` configuration so that fediverse
12+
handles (e.g. `@alice@example.com`) and ActivityPub actor URIs
13+
(e.g. `https://ap.example.com/@alice`) can live on different
14+
domains. The Mastodon-compatible `/api/v1/instance` and
15+
`/api/v2/instance` endpoints expose `HANDLE_HOST` as the instance
16+
domain when this is configured, so clients display the correct
17+
`@user@HANDLE_HOST` handle. Both variables must be set together;
18+
setting only one is a startup error. They must be configured
19+
*before* the first account is created — changing the handle
20+
domain after federation has begun breaks remote follow
21+
relationships; on startup Hollo logs a warning when the
22+
configured `HANDLE_HOST` does not match the existing account's
23+
stored handle. Operators must also configure their reverse
24+
proxy on the handle domain to redirect `/.well-known/webfinger`
25+
to `WEB_ORIGIN`. See the [Split-domain WebFinger guide]. [[#161], [#484]]
26+
927
- Added a media proxy that re-serves remote avatars, headers, post
1028
attachments, custom emojis, and preview-card images from Hollo's own
1129
origin. This sidesteps CORS configurations on remote object stores
@@ -187,6 +205,8 @@ To be released.
187205
- Upgraded Fedify to 2.2.1.
188206

189207
[FEP-044f]: https://w3id.org/fep/044f
208+
[Split-domain WebFinger guide]: https://docs.hollo.social/install/split-domain/
209+
[#161]: https://github.com/fedify-dev/hollo/issues/161
190210
[@hollo@hollo.social]: https://hollo.social/@hollo
191211
[#67]: https://github.com/fedify-dev/hollo/issues/67
192212
[#127]: https://github.com/fedify-dev/hollo/issues/127
@@ -200,6 +220,7 @@ To be released.
200220
[#481]: https://github.com/fedify-dev/hollo/issues/481
201221
[#482]: https://github.com/fedify-dev/hollo/pull/482
202222
[#483]: https://github.com/fedify-dev/hollo/pull/483
223+
[#484]: https://github.com/fedify-dev/hollo/pull/484
203224

204225

205226
Version 0.8.4

bin/server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { serve } from "@hono/node-server";
44
import { behindProxy } from "x-forwarded-fetch";
55

66
import "../src/logging";
7+
import { checkHandleHostConsistency } from "../src/handle-host-check";
78
import { configureSentry } from "../src/sentry";
89

910
// oxlint-disable-next-line typescript/dot-notation
@@ -40,6 +41,9 @@ if (!["all", "web", "worker"].includes(NODE_TYPE)) {
4041
process.exit(1);
4142
}
4243

44+
// Warn if the configured HANDLE_HOST disagrees with an existing account.
45+
await checkHandleHostConsistency();
46+
4347
// Start web server if running as web or all node
4448
if (NODE_TYPE === "web" || NODE_TYPE === "all") {
4549
const { default: app } = await import("../src/index");

docs/astro.config.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ export default defineConfig({
108108
},
109109
slug: "install/workers",
110110
},
111+
{
112+
label: "Split-domain WebFinger",
113+
translations: {
114+
ko: "도메인 분리 WebFinger",
115+
ja: "ドメイン分離 WebFinger",
116+
"zh-CN": "分域 WebFinger",
117+
},
118+
slug: "install/split-domain",
119+
},
111120
{
112121
label: "Setting up",
113122
translations: {

docs/src/content/docs/install/env.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@ Turned off by default.
8080
security reasons.
8181
</Aside>
8282

83+
### `HANDLE_HOST` <Badge text="Optional" />
84+
85+
The fediverse handle domain when running a [split-domain WebFinger
86+
setup][split-domain]. When set, fediverse handles take the form
87+
`@user@HANDLE_HOST` even though Hollo itself is served at
88+
[`WEB_ORIGIN`](#web_origin).
89+
90+
Must be set together with `WEB_ORIGIN`; setting only one is a startup
91+
error. Configure both *before* creating the first account — changing
92+
the handle domain after federation has begun breaks remote follow
93+
relationships.
94+
95+
Not set by default.
96+
97+
[split-domain]: /install/split-domain/
98+
99+
### `WEB_ORIGIN` <Badge text="Optional" />
100+
101+
The origin (scheme + host) where Hollo's ActivityPub server actually
102+
runs in a [split-domain WebFinger setup][split-domain], e.g.
103+
`https://ap.example.com`. Actor URIs, inbox URLs, and other federation
104+
endpoints are built under this origin.
105+
106+
Must be set together with [`HANDLE_HOST`](#handle_host); setting only
107+
one is a startup error.
108+
109+
Not set by default.
110+
83111
### `ALLOW_PRIVATE_ADDRESS` <Badge text="Optional" />
84112

85113
Setting this to `true` disables SSRF (Server-Side Request Forgery) protection.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: Split-domain WebFinger
3+
description: Run Hollo at one domain while addressing accounts under another.
4+
---
5+
6+
import { Aside, Code, Tabs, TabItem } from '@astrojs/starlight/components';
7+
8+
Hollo supports a *split-domain WebFinger* setup: your fediverse handles
9+
live under one domain (e.g. `@alice@example.com`) while the actual
10+
ActivityPub server runs under another (e.g. `https://ap.example.com`).
11+
This is the same pattern Mastodon documents under "[hosting WebFinger
12+
at the root domain][mastodon-webfinger]" and that GoToSocial calls
13+
*host-meta*-based host swapping.
14+
15+
This is implemented on top of [Fedify's `origin` configuration option].
16+
17+
[mastodon-webfinger]: https://docs.joinmastodon.org/admin/config/#web_domain
18+
[Fedify's `origin` configuration option]: https://fedify.dev/manual/federation#separating-webfinger-host-from-the-server-origin
19+
20+
21+
Why split the domain?
22+
---------------------
23+
24+
The typical reason is that you already own a nice short domain
25+
(`example.com`) and want your handles to look like
26+
`@alice@example.com`, but you don't want to host Hollo at the apex of
27+
that domain — the apex is reserved for a homepage, a different web app,
28+
or an existing service.
29+
30+
In a split-domain setup:
31+
32+
- Users address you as `@alice@example.com` everywhere in the
33+
fediverse.
34+
- Hollo itself is served from `https://ap.example.com`. The web
35+
UI, the Mastodon-compatible API, OAuth, and the actor URIs all
36+
live there.
37+
- The apex domain `example.com` only needs to handle one thing:
38+
redirecting `/.well-known/webfinger` requests over to
39+
`ap.example.com`.
40+
41+
<Aside type="caution">
42+
Pick the handle domain carefully — you can't change it after
43+
federation has begun without breaking every remote follow. See the
44+
warning at the bottom of this page.
45+
</Aside>
46+
47+
48+
Configuration
49+
-------------
50+
51+
Set both of these environment variables on your Hollo instance:
52+
53+
- [`HANDLE_HOST`](/install/env/#handle_host) — the bare hostname
54+
used in handles (no scheme, no path), e.g. `example.com`.
55+
- [`WEB_ORIGIN`](/install/env/#web_origin) — the scheme + host
56+
where Hollo runs, e.g. `https://ap.example.com`.
57+
58+
Both must be set together; setting only one causes Hollo to refuse to
59+
start.
60+
61+
~~~~ env
62+
HANDLE_HOST=example.com
63+
WEB_ORIGIN=https://ap.example.com
64+
~~~~
65+
66+
With this in place, Fedify takes care of the rest:
67+
68+
- WebFinger responses use `acct:alice@example.com` as the subject.
69+
- Actor URIs are built under `https://ap.example.com/@alice`.
70+
- The Mastodon-compatible `/api/v1/instance` and `/api/v2/instance`
71+
endpoints report `example.com` as the instance domain, so clients
72+
display the correct handle.
73+
74+
75+
Reverse proxy redirect
76+
----------------------
77+
78+
Hollo itself only listens on the `WEB_ORIGIN` host. Remote servers
79+
that look up `@alice@example.com` will send their WebFinger query to
80+
`https://example.com/.well-known/webfinger`, so you must configure
81+
that domain's reverse proxy to redirect those requests to Hollo.
82+
83+
A 301 redirect that preserves the query string is enough. Send
84+
`/.well-known/nodeinfo` and `/.well-known/host-meta` along as well,
85+
since some implementations probe those during discovery.
86+
87+
<Tabs>
88+
<TabItem label="nginx">
89+
~~~~ nginx
90+
server {
91+
listen 443 ssl;
92+
server_name example.com;
93+
# … your normal site config …
94+
95+
location /.well-known/webfinger {
96+
return 301 https://ap.example.com$request_uri;
97+
}
98+
location /.well-known/nodeinfo {
99+
return 301 https://ap.example.com$request_uri;
100+
}
101+
location /.well-known/host-meta {
102+
return 301 https://ap.example.com$request_uri;
103+
}
104+
}
105+
~~~~
106+
</TabItem>
107+
<TabItem label="Caddy">
108+
~~~~ caddy
109+
example.com {
110+
# … your normal site config …
111+
112+
redir /.well-known/webfinger* https://ap.example.com{uri} permanent
113+
redir /.well-known/nodeinfo* https://ap.example.com{uri} permanent
114+
redir /.well-known/host-meta* https://ap.example.com{uri} permanent
115+
}
116+
~~~~
117+
</TabItem>
118+
</Tabs>
119+
120+
You do **not** need to redirect the `/@username` path or any actor
121+
URLs — those URLs live on `ap.example.com` and remote servers will go
122+
straight there after resolving the WebFinger response.
123+
124+
125+
Verifying the setup
126+
-------------------
127+
128+
After deploying, three quick checks confirm everything is wired up:
129+
130+
1. WebFinger from the handle domain redirects:
131+
132+
~~~~ sh frame="none"
133+
curl -i "https://example.com/.well-known/webfinger?resource=acct:alice@example.com"
134+
~~~~
135+
136+
should return a 301 to `https://ap.example.com/.well-known/webfinger?...`.
137+
138+
2. WebFinger from the server domain responds:
139+
140+
~~~~ sh frame="none"
141+
curl "https://ap.example.com/.well-known/webfinger?resource=acct:alice@example.com"
142+
~~~~
143+
144+
should return a JRD whose `subject` is `acct:alice@example.com`
145+
and whose `self` link points at `https://ap.example.com/@alice`.
146+
147+
3. The instance endpoint reports the handle domain:
148+
149+
~~~~ sh frame="none"
150+
curl https://ap.example.com/api/v2/instance | jq .domain
151+
~~~~
152+
153+
should print `"example.com"`.
154+
155+
For a more thorough audit, point Julian Fietkau's [WebFinger Canary]
156+
at your handle domain. Mastodon and Fedify-based servers handle
157+
split-domain setups correctly today; Misskey and Pixelfed currently
158+
do not.
159+
160+
[WebFinger Canary]: https://correct.webfinger-canary.fietkau.software/
161+
162+
163+
<Aside type="danger">
164+
**Configure `HANDLE_HOST` and `WEB_ORIGIN` before creating your
165+
first account.**
166+
167+
Hollo stores each local account's full `@user@host` handle in the
168+
database when the account is created. Other ActivityPub
169+
implementations also cache your handle on first contact. Changing
170+
the handle domain later means your existing followers can no longer
171+
resolve you, and there is no migration path that keeps existing
172+
follow relationships intact.
173+
174+
On startup, Hollo logs a warning if `HANDLE_HOST` disagrees with the
175+
stored handle of the existing account. Treat that warning as a
176+
problem to investigate before continuing to run the instance.
177+
</Aside>

docs/src/content/docs/ja/install/env.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,34 @@ HolloがL7ロードバランサーの後ろにある場合(通常はそうす
8080
この動作はセキュリティ上注意が必要です。
8181
</Aside>
8282

83+
### `HANDLE_HOST` <Badge text="オプション" />
84+
85+
[ドメイン分離 WebFinger 設定][split-domain]で使用するフェディバースの
86+
ハンドルドメインです。このオプションを設定すると、フェディバースの
87+
ハンドルは`@user@HANDLE_HOST`の形式になりますが、Hollo自体は
88+
[`WEB_ORIGIN`](#web_origin)で動作します。
89+
90+
`WEB_ORIGIN`と一緒に設定する必要があり、片方だけを設定すると
91+
Holloは起動に失敗します。**最初のアカウントを作成する前に**
92+
両方の変数を設定してください。連合が始まった後にハンドルドメインを
93+
変更すると、リモートフォロー関係が壊れます。
94+
95+
デフォルトでは設定されていません。
96+
97+
[split-domain]: /ja/install/split-domain/
98+
99+
### `WEB_ORIGIN` <Badge text="オプション" />
100+
101+
[ドメイン分離 WebFinger 設定][split-domain]でHolloの ActivityPub
102+
サーバーが実際に動作するオリジン(スキーム + ホスト)です。例:
103+
`https://ap.example.com`。アクターURI、インボックスURLなどの
104+
連合エンドポイントは、すべてこのオリジンを基に構築されます。
105+
106+
[`HANDLE_HOST`](#handle_host)と一緒に設定する必要があり、
107+
片方だけを設定するとHolloは起動に失敗します。
108+
109+
デフォルトでは設定されていません。
110+
83111
### `ALLOW_PRIVATE_ADDRESS` <Badge text="オプション" />
84112

85113
このオプションを`true`に設定すると、サーバーサイドリクエストフォージェリ(SSRF)攻撃の防止を解除します。

0 commit comments

Comments
 (0)