Split-domain WebFinger handle#484
Conversation
When the new HANDLE_HOST and WEB_ORIGIN environment variables are set, Hollo wires Fedify's origin configuration so that fediverse handles (e.g. @alice@example.com) and ActivityPub actor URIs (e.g. https://ap.example.com/@alice) can live on different domains. The Mastodon-compatible /api/v1/instance and /api/v2/instance endpoints now report HANDLE_HOST as the canonical instance domain, the dashboard and home page surface it in their UIs, and account creation stores the local owner's handle and instanceHost under HANDLE_HOST rather than the request host. Local-account lookups (acct=username on /api/v1/accounts) and the local-emoji stripping logic in /api/v1/statuses are anchored to the same canonical host. Both variables must be set together; setting only one is a startup error. They must be configured before the first account is created, since changing the handle domain after federation has begun breaks existing follow relationships. At startup, Hollo logs a warning when HANDLE_HOST disagrees with the stored handle of an existing account. Operators are responsible for redirecting /.well-known/webfinger on the handle domain to WEB_ORIGIN at the reverse proxy layer. A dedicated documentation page covers the setup with nginx and Caddy snippets in all four supported languages. Resolves fedify-dev#161 Assisted-by: Claude Code:claude-opus-4-7 Assisted-by: Codex:gpt-5.5
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 427ea00890
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Code Review
This pull request implements split-domain WebFinger support, enabling fediverse handles to use a different domain than the ActivityPub server origin via new HANDLE_HOST and WEB_ORIGIN environment variables. The changes include updating API endpoints, account serialization, and Fedify configuration to respect these settings, alongside a new startup consistency check and extensive documentation. Feedback was provided regarding the handle parsing logic in the consistency check, suggesting a refactor into a shared helper to better handle edge cases and maintain a single source of truth for handle parsing.
In split-domain mode, local accounts and custom emojis are stored under HANDLE_HOST, but Fedify also recognizes the WEB_ORIGIN host as an equivalent alias for the same account. The Mastodon API endpoints, however, only stripped or normalized the HANDLE_HOST suffix, so a client supplying alice@ap.example.com would 404 against @alice@example.com on /api/v1/accounts/lookup, and a local emoji reaction sent as myemoji@ap.example.com would fall through to the remote-emoji branch and fail. Add an isLocalHost(host, requestUrl) helper in src/instance-host.ts that recognizes the request host, HANDLE_HOST, and the host part of WEB_ORIGIN as local. Use it in /api/v1/accounts/lookup to canonicalize the lookup handle to the stored HANDLE_HOST form, and in the two emoji reaction handlers in /api/v1/statuses.ts to strip a local suffix before deciding whether the emoji is remote. fedify-dev#484 (comment) fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
The startup consistency check in src/handle-host-check.ts extracted
the host portion of each stored handle with lastIndexOf("@") and
slice(at + 1), which produced a wrong existingHost on a malformed
handle that doesn't follow the canonical "@user@host" form: a bare
"@alice" gives at = 0 and existingHost = "alice", which would then
never match HANDLE_HOST and trigger a spurious warning.
In practice Hollo always writes canonical handles, so no real
warning fires today. Tighten the parser defensively: require the
leading "@", require lastIndexOf("@") > 0 so the second "@" actually
exists and sits past the leading one, and skip empty host segments.
fedify-dev#484 (comment)
Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements optional split-domain WebFinger support, allowing fediverse handles to reside on a different domain than the ActivityPub server. It introduces the HANDLE_HOST and WEB_ORIGIN environment variables, updates the Mastodon-compatible instance and account APIs to utilize the canonical handle host, and adds a startup check to verify handle consistency with existing database records. Comprehensive documentation was also provided in English, Japanese, Korean, and Simplified Chinese. The reviewer recommended refactoring repeated handle and emoji normalization logic into a shared helper function to ensure consistent parameter processing across the API endpoints.
|
Codex Review: Didn't find any major issues. Chef's kiss. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Gemini Code Assist flagged that /api/v1/accounts/lookup (the acct param) and the two /api/v1/statuses/:id/emoji_reactions/:emoji handlers don't strip a leading @ from user-supplied input. The lookup case is a real bug. If a client passes the user-typed form, e.g. acct=@alice@example.com, every existing branch prepends its own @ when building the DB lookup, so the handle becomes @@alice@example.com and matches no stored row. Many fediverse clients pass through whatever the user typed, so this is reachable in practice. The two emoji cases are theoretical (Mastodon clients don't @-prefix shortcodes), but the same one-line fix applies. Add normalizeHandle(handle) to src/patterns.ts next to HANDLE_PATTERN. It strips one leading @. The lookup endpoint and both emoji handlers call it on the user input before any further parsing. While in src/api/v1/accounts.ts, the same helper also replaces the three existing inline query.q.replace(/^@/, "") calls in /search, in line with the reviewer's single-source-of-truth point. Other .replace(/^@/, "") occurrences across the codebase operate on already-canonical stored handles with a guaranteed leading @, not on user input, so they're a different concern and stay as they are. fedify-dev#484 (comment) fedify-dev#484 (comment) fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Code Review
This pull request introduces optional split-domain WebFinger support, allowing ActivityPub handles (e.g., @user@example.com) to reside on a different domain than the server origin (e.g., https://ap.example.com). Key changes include the addition of HANDLE_HOST and WEB_ORIGIN environment variables, integration with Fedify's origin configuration, and updates to API endpoints and account serialization to ensure consistent handle display. A startup check was also implemented to warn if the configured HANDLE_HOST conflicts with existing account data. Feedback suggests adding explicit URL validation for WEB_ORIGIN in src/env.ts to provide clearer error messages and documenting the limitations of this check in accordance with repository guidelines.
If WEB_ORIGIN is set to a string that isn't a parseable URL, for example the operator forgets the scheme and writes WEB_ORIGIN=example.com, Hollo currently fails with a generic TypeError thrown from new URL(WEB_ORIGIN) at the top of src/instance-host.ts when that module loads. That's a poor signal for a misconfiguration. Add a URL.canParse(rawWebOrigin) check after the "both set together" guard in src/env.ts, and throw a friendly "WEB_ORIGIN must be a valid URL (e.g. https://ap.example.com)." when the value doesn't parse. A short comment notes that this is a syntax check only; Fedify still enforces the stricter shape (http/https scheme, no path/query/fragment) when the origin is wired into createFederation. fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c6be3f7cc4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Code Review
This pull request implements optional split-domain WebFinger support, allowing fediverse handles and ActivityPub actor URIs to reside on different domains via new HANDLE_HOST and WEB_ORIGIN environment variables. Key changes include updating instance metadata endpoints, account lookup/search logic, and emoji reaction handling to correctly resolve handles across both domains, alongside a startup check for configuration consistency. Reviewer feedback suggests centralizing handle normalization logic into a shared helper function to ensure consistency across the lookup and search endpoints and to avoid unnecessary remote lookups when local aliases are used.
PR fedify-dev#484 already canonicalized WEB_ORIGIN-host aliases on /api/v1/accounts/lookup, but the same logic wasn't applied to /api/v1/accounts/search. Searching for @alice@ap.example.com misses the local account stored as @alice@example.com and skips the resolve-side refresh path entirely. Extract normalizeHandleForLookup(handle, requestUrl) into src/instance-host.ts. It composes the existing helpers (normalizeHandle to strip a leading @, isLocalHost to recognize the request host / HANDLE_HOST / WEB_ORIGIN host as local, and getInstanceHost for the canonical handle host) into one canonicalizer that yields the @user@host form local accounts are stored under, or returns a remote handle with a leading @ otherwise. /lookup in src/api/v1/accounts.ts reduces to a single call to that helper. /search builds handleLookup once and threads it through both the resolve-side exact-match query and the orderBy clauses, so searching by WEB_ORIGIN-host alias prioritizes the matching local account. The remote-WebFinger fallback in /lookup keeps working: the dropped acct local becomes an inline normalizeHandle(query.acct) at the lookupObject call site. fedify-dev#484 (comment) fedify-dev#484 (comment) fedify-dev#484 (comment) fedify-dev#484 (comment) fedify-dev#484 (comment) fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces optional split-domain WebFinger support, allowing fediverse handles to be hosted on a different domain than the ActivityPub server. Key changes include the addition of HANDLE_HOST and WEB_ORIGIN environment variables, startup consistency checks, and updates to API endpoints and account serialization to respect the configured handle host. Comprehensive documentation was added in several languages. Review feedback suggests implementing stricter validation for WEB_ORIGIN to ensure it only contains a scheme and host, as required by Fedify, and explicitly documenting that this is a syntax-level check.
There was a problem hiding this comment.
💡 Codex Review
Line 437 in 16b2d6d
When split-domain mode is enabled, handleLookup correctly rewrites local aliases like @alice@ap.example.com to the canonical stored handle (@alice@example.com), but the main findMany filter still searches with raw query.q. In that case, where: ilike(accounts.handle, %${query.q}%) excludes the local account before ordering runs, so /api/v1/accounts/search?resolve=true&q=@alice@ap.example.com can return no results even though the account exists locally. The canonicalized value needs to participate in the where clause (or be OR’d in) to avoid dropping valid local matches.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The earlier WEB_ORIGIN check in src/env.ts only confirmed that URL.canParse accepted the string, so values like https://ap.example.com/path, ftp://ap.example.com, or https://ap.example.com?x=1 sailed past env.ts and only failed later from a generic TypeError inside src/instance-host.ts or Fedify's createFederation. Keep the URL.canParse check and add two more. The protocol must be http: or https:, otherwise throw "WEB_ORIGIN must use the http or https scheme". The parsed URL's pathname must be empty or /, its search must be empty, and its hash must be empty, otherwise throw "WEB_ORIGIN must be a bare origin (scheme and host only) with no path, query string, or fragment". Rewrite the leading comment to say this is syntax-only (no DNS resolution) and explain why we duplicate Fedify's own validation: catching it at the env-variable layer points the operator at the configuration mistake instead of a downstream TypeError. Smoke-tested with WEB_ORIGIN values "not a url", "ftp://...", "https://example.com/path", and "https://example.com?x=1"; each produces the matching message. The canonical https://ap.example.com still loads. fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
Codex Review: Didn't find any major issues. Chef's kiss. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Code Review
This pull request introduces optional split-domain WebFinger support, allowing ActivityPub actor URIs and fediverse handles to reside on different domains. Key changes include the addition of HANDLE_HOST and WEB_ORIGIN environment variables, updated account lookup logic, and comprehensive documentation across multiple languages. A startup check was also implemented to ensure consistency between the configured handle host and existing database records. The reviewer recommended moving Markdown reference links to the end of their respective sections to comply with the style guide and suggested normalizing the WEB_ORIGIN variable to lowercase to prevent potential case-sensitivity issues.
There was a problem hiding this comment.
Code Review
This pull request introduces optional split-domain WebFinger support, allowing fediverse handles to be hosted on a different domain than the ActivityPub server. It implements two new environment variables, HANDLE_HOST and WEB_ORIGIN, and updates the Mastodon-compatible API, account lookup logic, and UI components to respect these settings. Additionally, a startup check was added to warn of inconsistencies between the configured host and existing account data, accompanied by extensive documentation updates across multiple languages. I have no feedback to provide.
Hostnames are case-insensitive, but if an operator writes WEB_ORIGIN=https://AP.Example.COM the env.ts module previously exported that string verbatim, forcing every downstream consumer to do its own .toLowerCase() if it wanted to compare against another host. After the canParse, scheme, and origin-shape checks pass, store webOriginUrl.origin in a local normalizedWebOrigin and export that as WEB_ORIGIN. URL.origin yields the canonical form (lowercased host, no trailing slash), so consumers can rely on a normalized value. A short comment names what URL.origin guarantees. Smoke-tested with WEB_ORIGIN=https://AP.Example.COM: the exported value comes out as https://ap.example.com. fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements optional split-domain WebFinger support, allowing fediverse handles to exist on a different domain than the ActivityPub server. Key changes include the introduction of HANDLE_HOST and WEB_ORIGIN environment variables, updates to account lookup and serialization logic to handle domain aliases, and the addition of a startup consistency check to warn about domain mismatches. Comprehensive documentation and multi-language guides for this setup were also added. Feedback suggests adding stricter validation for the HANDLE_HOST variable to ensure it is a bare hostname without a scheme or port.
|
Codex Review: Didn't find any major issues. You're on a roll. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The earlier env.ts validation only trimmed and lowercased HANDLE_HOST without checking its shape, so values like HANDLE_HOST=https://example.com or HANDLE_HOST=example.com:3000 slipped past env.ts and only failed downstream with a generic Fedify TypeError. HANDLE_HOST is the bare hostname used in fediverse handles (the part after the second @), so it must not carry a scheme, port, or path. After the "both set together" guard, reject the value if the trimmed and lowercased rawHandleHost contains either / or :. The error message is "HANDLE_HOST must be a bare hostname (e.g. example.com) with no scheme, port, or path." A short comment notes that this is a syntax-level check only (no DNS resolution) and explains why a scheme, port, or path makes no sense for a WebFinger handle host. The check is slightly stricter than Fedify's own (Fedify rejects slashes but tolerates ports), which is intentional: WebFinger handles never carry ports in practice, and rejecting them in env.ts catches a common footgun where operators conflate WEB_ORIGIN's port with HANDLE_HOST. Smoke-tested HANDLE_HOST=example.com:3000, https://example.com, and example.com/path: each produces the new friendly error; the canonical example.com still loads. fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request adds optional split-domain WebFinger support, allowing fediverse handles to reside on a different domain than the ActivityPub server. It introduces the HANDLE_HOST and WEB_ORIGIN environment variables, updates documentation in multiple languages, and implements a consistency check for existing accounts. The internal logic across API endpoints and entity serialization is updated to handle these split domains. Review feedback correctly identifies that host comparisons should use hostname to exclude port numbers and suggests more robust regex validation for the HANDLE_HOST variable to prevent misconfigurations.
|
Codex Review: Didn't find any major issues. Bravo. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Both new URL(WEB_ORIGIN).host.toLowerCase() (the cached WEB_ORIGIN_HOST) and requestUrl.host.toLowerCase() (the inline comparison in isLocalHost) used the URL .host property, which includes a non-default port. The value those compare against comes from the host segment of a fediverse handle (the part after the second @), which never carries a port. So when WEB_ORIGIN is http://localhost:3000, WEB_ORIGIN_HOST was set to localhost:3000 and requestUrl.host was localhost:3000, and a handle like @alice@localhost wasn't recognized as local because localhost !== localhost:3000. Switch both .host accesses to .hostname, which strips the port, and add a short comment at each site naming why fediverse handles can't carry ports. fedify-dev#484 (comment) fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
The earlier HANDLE_HOST shape check rejected only / and :,
which catches full URLs and host-with-port but lets other
malformed values through: HANDLE_HOST="exa mple.com" (with
whitespace) for example sneaks past env.ts and only fails
downstream with a generic Fedify TypeError.
After the slash/colon check, also run URL.canParse on the
synthesized URL https://${rawHandleHost}/. Anything that
URL.canParse can't accept as a hostname (whitespace, control
characters, empty labels) throws "HANDLE_HOST must be a valid
hostname (e.g. example.com)." This mirrors how Fedify itself
validates handleHost downstream, but produces the friendlier
error at the env-variable layer.
The Unicode-aware URL parser leaves IDN domains alone.
Smoke-tested: "exa mple.com" produces the new error, while
"한국.kr", "café.fr", and "example.com" all load cleanly.
fedify-dev#484 (comment)
Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements optional split-domain WebFinger support, allowing fediverse handles to reside on a different domain than the ActivityPub server origin. Key changes include the introduction of HANDLE_HOST and WEB_ORIGIN environment variables, a startup consistency check for existing accounts, and updated account lookup and serialization logic to support domain aliases. Documentation has been added in multiple languages to guide operators through the setup. Review feedback identifies a critical potential TypeError during environment variable initialization and an inconsistency in the search logic where local handle normalization is bypassed for certain queries.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a9cea99fa8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The previous commit (77ed649, "Use hostname not host for local-handle comparison") fixed Gemini's port-in-WEB_ORIGIN concern but introduced a regression for non-split-domain deployments. Without HANDLE_HOST/WEB_ORIGIN set, getInstanceHost falls back to requestUrl.host (including any non-default port), so stored handles for a local dev instance at localhost:3000 are written as @alice@localhost:3000. Stripping the port out of the comparison in isLocalHost then made those handles fail to match: the comparison input is localhost:3000 (the stored host segment) but the check value was localhost (the hostname). Emoji reactions sent as myemoji@localhost:3000 got misclassified as remote and returned 404 when no prior remote reaction existed. Keep the .hostname check (which still addresses the port-in- WEB_ORIGIN concern) and add the .host comparison back as a second clause. Both forms are accepted, so split-domain deployments (where stored handles use HANDLE_HOST without a port) and non-split-domain dev (where stored handles include the port from request.host) both work. The comment is rewritten to spell out why both forms are checked. fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Code Review
This pull request introduces optional split-domain WebFinger support, allowing fediverse handles to exist on a different domain than the ActivityPub server. It adds two new environment variables, HANDLE_HOST and WEB_ORIGIN, along with comprehensive documentation in multiple languages and a startup consistency check to warn if the configured handle host conflicts with existing accounts. Feedback includes a suggestion to use the URL constructor for more robust origin normalization in src/env.ts, a performance concern regarding the enumeration of all account handles at startup in src/handle-host-check.ts, and a recommendation to document the limitations of the isLocalHost security check. Additionally, the search logic in src/api/v1/accounts.ts could be refined to avoid redundant ilike conditions.
Add a short block comment above isLocalHost spelling out what the function does and does not do: it's a string-equality check between hostnames, with no DNS resolution and no authority over the request itself. This mirrors the AGENTS.md convention of documenting the limits of security-adjacent checks so a future reader doesn't mistake it for a gatekeeper. No behavior change. fedify-dev#484 (comment) Assisted-by: Claude Code:claude-opus-4-7
|
@codex review |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces optional split-domain WebFinger support, allowing fediverse handles and ActivityPub actor URIs to reside on different domains. It adds HANDLE_HOST and WEB_ORIGIN environment variables, updates the server to validate these settings on startup, and includes comprehensive documentation in multiple languages. The implementation also includes logic to normalize handles for lookups and ensures Mastodon-compatible instance endpoints report the correct domain. Feedback was provided regarding the placement of reference-style links in the new documentation files, which currently violate the repository's Markdown style guide requiring them to be at the end of each section.
|
Codex Review: Didn't find any major issues. Nice work! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Closes #161.
Issue #161 asks for a way to surface Fedify's split-domain WebFinger capability through Hollo's configuration, so that operators can address fediverse accounts under one domain (
@alice@example.com) while running the ActivityPub server itself under another (https://ap.example.com). The usual motivation is an apex domain that's already taken by a homepage or another service, so Hollo has to run on a subdomain even though handles should look short.Configuration
Two new optional environment variables, both required when either is set.
HANDLE_HOSTis the bare hostname used in handles (e.g.example.com).WEB_ORIGINis the scheme and host where Hollo actually runs (e.g.https://ap.example.com). When both are present, src/federation/federation.ts passes them through asorigin: { handleHost, webOrigin }tocreateFederation. If only one is set, Hollo refuses to start.Operators are responsible for redirecting
/.well-known/webfinger(and, as a courtesy,/.well-known/nodeinfoand/.well-known/host-meta) from the handle domain to the web origin at the reverse proxy layer. Hollo itself only listens on the web origin.What Fedify handles
In Fedify 2.x,
ctx.hostreturns the request host, whilectx.canonicalOrigin(and thereforectx.getActorUri()and friends) returns the configuredwebOrigin. Fedify builds actor IRIs and inbox URLs underWEB_ORIGIN; it builds WebFinger acct subjects underhandleHost. Hollo only needs to choose the right host when it constructs@user@hostfor storage or display.Where the canonical host is used
A small helper in src/instance-host.ts returns the configured
HANDLE_HOSTwhen set, falling back to the request host otherwise. It replacesnew URL(c.req.url).hostandfedCtx.hostwhere Hollo composes or displays the canonical instance domain.The Mastodon-compatible src/api/v1/instance.ts and src/api/v2/instance.ts endpoints report it as
uri,domain, andtitle. Account creation in src/pages/accounts.tsx stores@user@HANDLE_HOSTinaccounts.handleand writesHANDLE_HOSTintoaccounts.instanceHostandinstances.host. Local-username lookups in src/api/v1/accounts.ts expand bareacct=usernameagainst it. The local-accountacctshortening in src/entities/account.ts and the local-emoji suffix stripping in src/api/v1/statuses.ts both anchor on it. The dashboard'shost=props in src/pages/accounts.tsx and the home page title in src/pages/home/index.tsx display it to humans.Immutability and the startup warning
HANDLE_HOSTshould be configured before the first account is created. Hollo stores the full@user@hosthandle inaccounts.handlewhen a local account is set up, and other ActivityPub implementations cache that string at first contact. Changing the handle domain later breaks every existing remote follow, and there's no migration that keeps existing relationships intact.The check in src/handle-host-check.ts runs from bin/server.ts on every boot, for both web and worker nodes. If
HANDLE_HOSTdisagrees with the host portion of any existing local account's stored handle, Hollo emits a warning log under thehollo·configlogger and continues. It deliberately does not refuse to start, because there are legitimate migration paths (with manual stored-handle rewriting) and refusing to boot would be worse than warning.Documentation
The install docs add docs/src/content/docs/install/split-domain.mdx, with parallel translations under docs/src/content/docs/ja/install/, docs/src/content/docs/ko/install/, and docs/src/content/docs/zh-cn/install/. It includes nginx and Caddy reverse-proxy snippets, curl-based verification steps, a WebFinger Canary interop note, and an immutability warning. The two variables are also documented inline in each install/env.mdx. CHANGES.md gets a detailed entry under 0.9.0, AGENTS.md picks up matching rows in its Optional variables table, and docs/astro.config.mjs gains a translated sidebar entry.
Out of scope
Migrating an existing instance's handle domain by rewriting stored
accounts.handlerows is not supported and not attempted here. A setup-wizard UI knob for the handle domain is also out of scope, because exposing it in the UI invites exactly the footgun the issue warns about. GoToSocial's restriction that the handle domain must share a root with the server host is not enforced, following the issue author's preference and Fedify's own behavior.OAuth metadata, pagination
Linkheaders, and dashboard URL construction continue to derive from the request origin: those endpoints are served fromWEB_ORIGINin a correct deployment, so the request URL is already the right value.Tests
pnpm check(typecheck, oxlint, oxfmt) is clean.pnpm test:ciruns 435 passing tests with one skip, no fixture changes.Still to verify before merge: boot on a fresh database with
HANDLE_HOST=example.comandWEB_ORIGIN=http://localhost:3000, confirm WebFinger foracct:alice@example.comreturns a JRD whoseselfpoints athttp://localhost:3000/@alice,/api/v2/instancereports"domain": "example.com", andaccounts.handlereads@alice@example.com. Boot with onlyHANDLE_HOSTset and confirm Hollo refuses to start. Boot with a mismatchedHANDLE_HOSTand confirm the warning log fires.