Watch
0
0
Fork
You've already forked hyperhive
0

matrix: serve the web client unconditionally; link it from the swarm domain

Removes services.hyperhive.deploy.matrix.gui.enable and its
swarm.matrix.gui.enable alias; both are mkRemovedOptionModule stubs. A
host running the homeserver serves fluffychat at gatewayHost's vhost,
and the hive's /matrix/ redirect follows the same condition.

The swarm-controller builds the Matrix quick link from
swarm.matrix.gatewayHost, replacing hive-matrix.nix's per-host entry.
HIVE_MATRIX_PUBLIC_URL is set on every hive with a gatewayHost, so
`hivectl open matrix` resolves off the homeserver's host too.

Drops HIVE_MATRIX_GUI_ENABLED and the dashboard's matrix_gui_enabled
field; nothing in the frontend reads it.

Refs #4885
This commit is contained in:
atlas 2026-10-02 18:41:21 +02:00
commit f60f8af33b
10 changed files with 48 additions and 96 deletions

View file

@ -8,7 +8,7 @@ homeserver, on one host. Two namespaces configure it:
- `services.hyperhive.swarm.matrix.*` — what the homeserver **is**, as - `services.hyperhive.swarm.matrix.*` — what the homeserver **is**, as
every hive sees it: `serverName`, `gatewayHost`, ports, `allowEncryption`. every hive sees it: `serverName`, `gatewayHost`, ports, `allowEncryption`.
- `services.hyperhive.deploy.matrix.*` — what the host running it decides: - `services.hyperhive.deploy.matrix.*` — what the host running it decides:
`enable`, `gui.enable`, `openFirewall`, `trustedServers`, `enable`, `gui.package`, `openFirewall`, `trustedServers`,
`maxRequestSize`, `sso.clientSecretFile`. `maxRequestSize`, `sso.clientSecretFile`.
Vhost routing lives in [`gateway.md`](../networking/gateway.md). Vhost routing lives in [`gateway.md`](../networking/gateway.md).

View file

@ -113,19 +113,19 @@ same whichever host runs each service:
| Grafana | `services.hyperhive.swarm.grafana.domain` | | Grafana | `services.hyperhive.swarm.grafana.domain` |
| Metrics | `services.hyperhive.swarm.victoriametrics.domain` | | Metrics | `services.hyperhive.swarm.victoriametrics.domain` |
| Logs | `services.hyperhive.swarm.victorialogs.domain` | | Logs | `services.hyperhive.swarm.victorialogs.domain` |
| Matrix | `services.hyperhive.swarm.matrix.gatewayHost` |
`nix/host-modules/swarm-controller.nix` builds these entries. The `nix/host-modules/swarm-controller.nix` builds these entries. The Forge
Matrix and Forge entries, and `swarm-ui.nix`'s entry for this UI's own API entry, and `swarm-ui.nix`'s entry for this UI's own API docs, come from
docs, come from those services' own modules, and only when the controller's those services' own modules, and only when the controller's host runs that
host runs that service. An operator can add entries directly. An service. An operator can add entries directly. An empty list hides the
empty list hides the button. button.
The **Matrix** entry opens the swarm's matrix web client (fluffychat, The **Matrix** entry opens the swarm's matrix web client (fluffychat,
`services.hyperhive.deploy.matrix.gui.package`) at the homeserver's `services.hyperhive.deploy.matrix.gui.package`) at the homeserver's
gateway host, `chat.<swarm domain>` by default. `hive-matrix.nix` adds it gateway host, `chat.<swarm domain>` by default. The homeserver's host
only when `services.hyperhive.deploy.matrix.gui.enable` is on and the always serves the client at `/` on that vhost, so the entry is present
homeserver has a gateway host, the same condition under which that vhost whenever the swarm names a gateway host.
serves the client at `/`.
<details><summary>Adding a swarm service name: the two wiring sites</summary> <details><summary>Adding a swarm service name: the two wiring sites</summary>

View file

@ -73,12 +73,6 @@ pub(super) struct StateSnapshot {
/// links each container's config + each approval's commit into the /// links each container's config + each approval's commit into the
/// forge's `agent-configs` repos. /// forge's `agent-configs` repos.
forge_present: bool, forge_present: bool,
/// Whether `services.hyperhive.deploy.matrix.gui.enable` is on. Sourced
/// from the `HIVE_MATRIX_GUI_ENABLED` env var the c0re NixOS module
/// sets. The matrix client itself is served on the homeserver's
/// gateway vhost; the hive's `/matrix/` path redirects there
/// (`nix/host-modules/hive-gateway/vhosts.nix`).
matrix_gui_enabled: bool,
/// Whether `hive-gateway` is in front of this dashboard. Sourced /// Whether `hive-gateway` is in front of this dashboard. Sourced
/// from the `HIVE_GATEWAY_ENABLED` env var, which the c0re NixOS /// from the `HIVE_GATEWAY_ENABLED` env var, which the c0re NixOS
/// module now always sets (the gateway runs unconditionally /// module now always sets (the gateway runs unconditionally
@ -313,17 +307,10 @@ pub(super) async fn api_state(
tombstones, tombstones,
port_conflicts, port_conflicts,
forge_present: crate::forge::is_present().await, forge_present: crate::forge::is_present().await,
matrix_gui_enabled: std::env::var_os("HIVE_MATRIX_GUI_ENABLED").is_some_and(|v| {
// Accept any truthy string ("1", "true", "yes") since the
// env var is set by NixOS module wiring with the literal
// "1"; defensive parse so manual overrides also work.
let s = v.to_string_lossy().to_ascii_lowercase();
matches!(s.as_str(), "1" | "true" | "yes")
}),
gateway_enabled: std::env::var_os("HIVE_GATEWAY_ENABLED").is_some_and(|v| { gateway_enabled: std::env::var_os("HIVE_GATEWAY_ENABLED").is_some_and(|v| {
// Same truthy-string parse as `matrix_gui_enabled`; the // Accept any truthy string ("1", "true", "yes"): the c0re
// env var is set by the c0re NixOS module to the literal // NixOS module sets the literal "1" (the gateway always runs
// "1" — the gateway always runs alongside hyperhive. // alongside hyperhive), and manual overrides also parse.
let s = v.to_string_lossy().to_ascii_lowercase(); let s = v.to_string_lossy().to_ascii_lowercase();
matches!(s.as_str(), "1" | "true" | "yes") matches!(s.as_str(), "1" | "true" | "yes")
}), }),

View file

@ -30,7 +30,7 @@ pub(crate) async fn open_url(socket: &Path, target: OpenTarget) -> Result<()> {
), ),
OpenTarget::Matrix => ( OpenTarget::Matrix => (
urls.matrix, urls.matrix,
"the matrix GUI URL needs `services.hyperhive.deploy.matrix.gui.enable = true`", "the matrix GUI URL needs `services.hyperhive.swarm.matrix.gatewayHost` set",
), ),
}; };
let url = url.with_context(|| format!("no URL available for this surface — {hint}"))?; let url = url.with_context(|| format!("no URL available for this surface — {hint}"))?;

View file

@ -277,10 +277,16 @@ in
The old token file at /var/lib/hyperhive/matrix-register-token is read by The old token file at /var/lib/hyperhive/matrix-register-token is read by
nothing now and can be deleted. See docs/integrations/matrix.md. nothing now and can be deleted. See docs/integrations/matrix.md.
'') '')
(lib.mkRenamedOptionModule (lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "matrix" "gui" "enable" ] ''
[ "services" "hyperhive" "swarm" "matrix" "gui" "enable" ] The matrix web client has no on/off switch: every host that runs the
[ "services" "hyperhive" "deploy" "matrix" "gui" "enable" ] homeserver serves it at services.hyperhive.swarm.matrix.gatewayHost,
) and the swarm UI links to it. Remove this definition.
'')
(lib.mkRemovedOptionModule [ "services" "hyperhive" "deploy" "matrix" "gui" "enable" ] ''
The matrix web client has no on/off switch: every host that runs the
homeserver serves it at services.hyperhive.swarm.matrix.gatewayHost,
and the swarm UI links to it. Remove this definition.
'')
(lib.mkRenamedOptionModule (lib.mkRenamedOptionModule
[ "services" "hyperhive" "swarm" "matrix" "sso" "clientSecretFile" ] [ "services" "hyperhive" "swarm" "matrix" "sso" "clientSecretFile" ]
[ "services" "hyperhive" "deploy" "matrix" "sso" "clientSecretFile" ] [ "services" "hyperhive" "deploy" "matrix" "sso" "clientSecretFile" ]

View file

@ -191,12 +191,6 @@ in
# one. # one.
HIVE_MATRIX_API_URL = config.services.hyperhive.swarm.matrix.apiUrl; HIVE_MATRIX_API_URL = config.services.hyperhive.swarm.matrix.apiUrl;
} }
// lib.optionalAttrs config.services.hyperhive.deploy.matrix.gui.enable {
# Surfaces as `matrix_gui_enabled` in the dashboard's `/api/state`.
# The matrix GUI is served entirely by the gateway nginx. Gateway
# routing detail: docs/networking/gateway.md::Vhost map.
HIVE_MATRIX_GUI_ENABLED = "1";
}
// { // {
# The gateway always runs, so the dashboard always builds # The gateway always runs, so the dashboard always builds
# same-origin `/agent/<name>/` links (never the direct # same-origin `/agent/<name>/` links (never the direct
@ -216,19 +210,13 @@ in
# forge links rather than emitting one it can't justify. # forge links rather than emitting one it can't justify.
HIVE_FORGE_PUBLIC_URL = config.services.hyperhive.swarm.forge.publicUrl; HIVE_FORGE_PUBLIC_URL = config.services.hyperhive.swarm.forge.publicUrl;
} }
// // lib.optionalAttrs (config.services.hyperhive.swarm.matrix.gatewayHost != null) {
lib.optionalAttrs # Browser-facing matrix GUI (fluffychat) URL — the gateway
( # vhost (`gatewayHost`, `chat.<swarm-domain>` by default). Surfaced via the daemon's `Urls`
config.services.hyperhive.deploy.matrix.gui.enable # request for `hivectl open matrix`. Absent when no gatewayHost is
&& config.services.hyperhive.swarm.matrix.gatewayHost != null # set (no browser-reachable matrix vhost).
) HIVE_MATRIX_PUBLIC_URL = "https://${config.services.hyperhive.swarm.matrix.gatewayHost}/";
{ }
# Browser-facing matrix GUI (fluffychat) URL — the gateway
# vhost (`gatewayHost`, `chat.<swarm-domain>` by default). Surfaced via the daemon's `Urls`
# request for `hivectl open matrix`. Absent when the GUI is off
# or no gatewayHost is set (no browser-reachable matrix vhost).
HIVE_MATRIX_PUBLIC_URL = "https://${config.services.hyperhive.swarm.matrix.gatewayHost}/";
}
// lib.optionalAttrs (config.services.hyperhive.swarm.snapshotStore.address != null) { // lib.optionalAttrs (config.services.hyperhive.swarm.snapshotStore.address != null) {
# `host:port` of the swarm's single snapshot store, for pushing agent # `host:port` of the swarm's single snapshot store, for pushing agent
# snapshots (hive-c0re::snapshot_push). One per swarm, not one per # snapshots (hive-c0re::snapshot_push). One per swarm, not one per

View file

@ -46,8 +46,7 @@ let
# by default; legacy deep-link shim during the fluffychat sub-domain move). # by default; legacy deep-link shim during the fluffychat sub-domain move).
# See `docs/networking/gateway.md`. # See `docs/networking/gateway.md`.
matrixRedirectLocations = matrixRedirectLocations =
lib.optionalAttrs lib.optionalAttrs (matrixDeployCfg.enable && matrixCfg.gatewayHost != null)
(matrixDeployCfg.enable && matrixDeployCfg.gui.enable && matrixCfg.gatewayHost != null)
( (
let let
target = "${publicScheme}://${matrixCfg.gatewayHost}${publicPortSuffix}"; target = "${publicScheme}://${matrixCfg.gatewayHost}${publicPortSuffix}";

View file

@ -537,22 +537,6 @@ in
''; '';
}; };
gui.enable = lib.mkOption {
type = lib.types.bool;
default = deployCfg.matrix.enable;
defaultText = lib.literalExpression "config.services.hyperhive.deploy.matrix.enable";
description = ''
Serve a matrix web client at `gatewayHost`'s vhost (`chat.<swarm-domain>`
by default) and add the swarm UI's **Matrix** quick link to it
(`services.hyperhive.swarm.controller.links`). Requires
`gatewayHost != null`; the gateway itself always runs. See
`docs/networking/gateway.md` for the discovery flow that lets clients
auto-find the sub-domain. The client build itself is
`deploy.matrix.gui.package` — which client, as opposed to whether
this host serves it.
'';
};
gui.package = lib.mkOption { gui.package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = fluffychat-web-fixed; default = fluffychat-web-fixed;
@ -667,19 +651,6 @@ in
# through the hive's dnsmasq whether or not the gateway fronts it. # through the hive's dnsmasq whether or not the gateway fronts it.
services.hyperhive.gateway.dns.enable = lib.mkDefault true; services.hyperhive.gateway.dns.enable = lib.mkDefault true;
# This swarm-ui quick-links entry. Gated on `gui.enable` too, not just
# `gatewayHost != null`: `/` on that vhost only serves fluffychat
# (below) when the GUI is on — otherwise the link would 404. See
# docs/swarm/ui.md::Quick links and
# `services.hyperhive.swarm.controller.links`'s description.
services.hyperhive.swarm.controller.links =
lib.optional (cfg.gatewayHost != null && deployCfg.matrix.gui.enable)
{
label = "Matrix";
icon = "💬";
url = "https://${cfg.gatewayHost}/";
};
# Accept-header SPA map, used only by the `/` location below (see # Accept-header SPA map, used only by the `/` location below (see
# docs/networking/gateway.md "SPA fallback"): text/html → index.html, else a # docs/networking/gateway.md "SPA fallback"): text/html → index.html, else a
# sentinel so `try_files` falls through to 404. `appendHttpConfig` # sentinel so `try_files` falls through to 404. `appendHttpConfig`
@ -687,7 +658,7 @@ in
# contributes instead of replacing it. # contributes instead of replacing it.
# #
# The dashboard needs no equivalent — it routes by path. # The dashboard needs no equivalent — it routes by path.
services.nginx.appendHttpConfig = lib.optionalString deployCfg.matrix.gui.enable '' services.nginx.appendHttpConfig = ''
map $http_accept $matrix_spa_target { map $http_accept $matrix_spa_target {
default "/__matrix_spa_no_html_fallback"; default "/__matrix_spa_no_html_fallback";
"~*text/html" "/index.html"; "~*text/html" "/index.html";
@ -695,8 +666,8 @@ in
''; '';
# `server_name = gatewayHost`. `/_matrix/*` → tuwunel (CORS `*`, 50M # `server_name = gatewayHost`. `/_matrix/*` → tuwunel (CORS `*`, 50M
# body cap, 1h long-poll timeout). `/` serves fluffychat, or 404 # body cap, 1h long-poll timeout). `/` serves fluffychat. nginx's
# with the GUI off. nginx's longest-prefix rule puts `/_matrix/` # longest-prefix rule puts `/_matrix/`
# ahead of `/` with no ordering needed. # ahead of `/` with no ordering needed.
# #
# ⚠️ The `.well-known/matrix/*` delegation is deliberately NOT here. # ⚠️ The `.well-known/matrix/*` delegation is deliberately NOT here.
@ -727,8 +698,6 @@ in
add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Origin *;
''; '';
}; };
}
// lib.optionalAttrs deployCfg.matrix.gui.enable {
# fluffychat at sub-domain root, SPA-fallback via the # fluffychat at sub-domain root, SPA-fallback via the
# Accept-header `$matrix_spa_target` map above. # Accept-header `$matrix_spa_target` map above.
"/" = { "/" = {
@ -746,11 +715,6 @@ in
return 200 '{"defaultHomeserver":"${hyperhiveDomain}"}'; return 200 '{"defaultHomeserver":"${hyperhiveDomain}"}';
''; '';
}; };
}
// lib.optionalAttrs (!deployCfg.matrix.gui.enable) {
"/" = {
return = "404";
};
}; };
}; };
}; };

View file

@ -51,7 +51,14 @@ let
icon = "📜"; icon = "📜";
inherit (swarmCfg.victorialogs) domain; inherit (swarmCfg.victorialogs) domain;
} }
]; ]
# A null `gatewayHost` means no vhost fronts the homeserver, so there is
# no web client to link to.
++ lib.optional (swarmCfg.matrix.gatewayHost != null) {
label = "Matrix";
icon = "💬";
url = "https://${swarmCfg.matrix.gatewayHost}/";
};
# Where the secret store is, and whether this host holds the controller's # Where the secret store is, and whether this host holds the controller's
# own leaf for it. ⚠️ The controller's pair, NOT `deploy.bao.clientCertFile` # own leaf for it. ⚠️ The controller's pair, NOT `deploy.bao.clientCertFile`
@ -451,8 +458,9 @@ in
or swarm-ui change. or swarm-ui change.
The Authelia, Grafana, Metrics and Logs entries come from The Authelia, Grafana, Metrics and Logs entries come from
`services.hyperhive.swarm.<service>.domain`, so they are present `services.hyperhive.swarm.<service>.domain`, and the Matrix entry
whichever host runs each service. `hive-matrix.nix`, from `services.hyperhive.swarm.matrix.gatewayHost` when it is set,
so they are present whichever host runs each service.
`hive-forge/default.nix` and `swarm-ui.nix` contribute their own `hive-forge/default.nix` and `swarm-ui.nix` contribute their own
entries, and only where they are enabled on this host. entries, and only where they are enabled on this host.

View file

@ -115,9 +115,9 @@ let
Grafana = s.grafana.domain; Grafana = s.grafana.domain;
Metrics = s.victoriametrics.domain; Metrics = s.victoriametrics.domain;
Logs = s.victorialogs.domain; Logs = s.victorialogs.domain;
Matrix = s.matrix.gatewayHost;
# forge: added once hive-forge/default.nix drops its per-host link # forge: added once hive-forge/default.nix drops its per-host link
# matrix: added once its GUI gate is settled # bao: its web UI at `swarm.bao.ui.domain` has no link yet
# bao: added once its UI gate is settled
}; };
swarmServiceLinksOf = swarmServiceLinksOf =
cfg: cfg: