diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 05b19f62..c1bb2158 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -51,6 +51,17 @@ pub async fn serve(port: u16, coord: Arc) -> Result<()> { ); } tracing::info!(static_dir = %static_dir.display(), "dashboard static dir resolved"); + // Optional matrix-GUI static dist (#607). When `HIVE_MATRIX_GUI_DIR` + // is set and the dir exists, mount it at `/matrix/` as a sibling + // ServeDir before the SPA fallback. Pre-#15 (nginx front) this + // is the simplest way to put fluffychat-web on the same origin as + // the dashboard so the operator has matrix chat one tab away. + let matrix_gui_dir: Option = std::env::var_os("HIVE_MATRIX_GUI_DIR") + .map(PathBuf::from) + .filter(|p| p.is_dir()); + if let Some(dir) = &matrix_gui_dir { + tracing::info!(matrix_gui_dir = %dir.display(), "matrix GUI mount enabled at /matrix"); + } let app = Router::new() .route("/api/state", get(api_state)) .route("/approve/{id}", post(post_approve)) @@ -92,6 +103,14 @@ pub async fn serve(port: u16, coord: Arc) -> Result<()> { // /static/dashboard.css → dist/static/dashboard.css, etc.). .fallback_service(ServeDir::new(&static_dir)) .with_state(AppState { coord }); + // Sibling static mount for the optional matrix GUI dist. Layered + // before the SPA fallback so `/matrix/...` resolves there instead + // of falling through to the dashboard. + let app = if let Some(dir) = matrix_gui_dir { + app.nest_service("/matrix", ServeDir::new(dir)) + } else { + app + }; let addr = SocketAddr::from(([0, 0, 0, 0], port)); let listener = bind_with_retry(addr).await?; tracing::info!(%port, "dashboard listening"); @@ -223,13 +242,10 @@ struct StateSnapshot { /// links each container's config + each approval's commit into the /// forge's `agent-configs` repos. forge_present: bool, - /// Whether the matrix GUI is reachable at `/matrix/`. Sourced from - /// `HIVE_MATRIX_GUI_ENABLED` env var (set by the c0re NixOS module - /// when `services.hyperhive.matrix.gui.enable` is on). The gateway - /// (hive-gateway.nix) does the actual `/matrix/` static serving; - /// this flag is just an availability signal for iris's dashboard - /// chrome so the `M4TR1X →` tab doesn't flash when the GUI is off - /// (#607, #634). + /// Whether the matrix GUI static dist is mounted at `/matrix/` + /// (`HIVE_MATRIX_GUI_DIR` set + dir exists). Drives the dashboard's + /// `M4TR1X →` tab-strip entry so it doesn't flash a dead link when + /// the operator hasn't opted in (#607). matrix_gui_enabled: bool, } @@ -423,13 +439,9 @@ async fn api_state(headers: HeaderMap, State(state): State) -> axum::J port_conflicts, rebuild_queue: state.coord.rebuild_queue.snapshot(), 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") - }), + matrix_gui_enabled: std::env::var_os("HIVE_MATRIX_GUI_DIR") + .map(PathBuf::from) + .is_some_and(|p| p.is_dir()), }) } diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 9492f015..c5aeacce 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -241,15 +241,6 @@ in # Agents poll this URL for Forgejo notifications. Derived from # services.hyperhive.forge.{domain,httpPort} so it tracks forge config changes. HIVE_FORGE_URL = "http://${config.services.hyperhive.forge.domain}:${toString config.services.hyperhive.forge.httpPort}"; - } - // lib.optionalAttrs config.services.hyperhive.matrix.gui.enable { - # Availability flag for `/api/state.matrix_gui_enabled`. The - # gateway (hive-gateway.nix) does the actual static serving of - # fluffychat-web at `/matrix/`; c0re doesn't host the dist - # itself (#634, mara on PR #620). This env var just tells the - # dashboard chrome whether the GUI is reachable so iris's - # `M4TR1X →` tab doesn't show when the GUI is off. - HIVE_MATRIX_GUI_ENABLED = "1"; }; # Matrix GUI static serving lives entirely on the hive-gateway # nginx since #609 — when gateway is off the operator opts out diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index 3c17d271..a2ef7abe 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -159,56 +159,34 @@ in gui = { enable = lib.mkOption { type = lib.types.bool; - default = cfg.enable; - defaultText = lib.literalExpression "config.services.hyperhive.matrix.enable"; + default = false; description = '' - Serve a matrix web client (default `pkgs.fluffychat-web`) as - a static dist at `/matrix/` via the hive-gateway nginx - (#607 / #634). Defaults to whatever - `services.hyperhive.matrix.enable` is — turning on the - homeserver gives you the web client by default; set to - `false` explicitly to opt out of the GUI while keeping - the homeserver running for agents. Requires - `services.hyperhive.gateway.enable` (default on); when - gateway is off no one hosts the GUI and the - `M4TR1X →` dashboard tab is hidden. + Mount a matrix web client (default `pkgs.fluffychat-web`) as + a static sibling of the dashboard at `/matrix/`, served by + hive-c0re's own axum router via the `HIVE_MATRIX_GUI_DIR` + env var (#607 v0). Off by default; flip to `true` once the + operator wants matrix chat one tab away from the dashboard + without standing up a separate gateway. - fluffychat-web supports per-login server pick — point it at - the in-host tuwunel URL (`http://localhost:8008` by - default) the first time. The post-#15 nginx-front re-root - (`https://matrix.''${services.hyperhive.domain}`) is tracked - separately in #609. + Same-origin via hive-c0re is the simplest single-host + shape; the post-#15 nginx-front re-root (`https://matrix.''${services.hyperhive.domain}`) + is tracked separately in #609. fluffychat-web supports + per-login server pick — point it at the in-host tuwunel URL + (`http://localhost:8008` by default) the first time. ''; }; package = lib.mkOption { type = lib.types.package; - default = pkgs.fluffychat-web.overrideAttrs (old: { - # fluffychat-web ships with `` baked into its - # index.html — flutter's `--base-href` build flag replaces - # that placeholder. Default upstream build is `--base-href "/"` - # which is wrong for hyperhive's `/matrix/` sub-path mount: - # the browser resolves relative asset paths (`Imaging.js`, - # `flutter.js`, `splash/*`) against the document ROOT, - # producing 404s for every asset (#634). Inject the right - # base-href into the build via nixpkgs's `buildFlutterApplication` - # targetFlags. When subdomain routing lands (#609 — - # `matrix.${hyperhiveDomain}`), drop this override; upstream's - # `/` base-href is correct at the root of a dedicated subdomain. - targetFlags = (old.targetFlags or [ ]) ++ [ - "--base-href" - "/matrix/" - ]; - }); - defaultText = lib.literalMD "`pkgs.fluffychat-web` rebuilt with `--base-href /matrix/` via `targetFlags`."; + default = pkgs.fluffychat-web; + defaultText = lib.literalExpression "pkgs.fluffychat-web"; description = '' Static web client dist to serve at `/matrix/`. Defaults to - `pkgs.fluffychat-web` rebuilt with `--base-href "/matrix/"` - so relative asset paths resolve under the sub-path mount - (#634). Override to swap for `hydrogen-web` (lightest), + `pkgs.fluffychat-web` — first-class threads support, e2ee + + voice + spaces, full feature surface in the flutter→web + build. Override to swap for `hydrogen-web` (lightest), `cinny` (no threads), `element-web` (heaviest, full - features), or an out-of-tree client dist — any replacement - also needs its `` aligned with the mount path. + features), or an out-of-tree client dist. ''; }; };