diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 17662b48..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,6 +242,11 @@ 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 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, } /// `OpQuestion` + computed `question_refs` / `answer_refs`. Built @@ -415,6 +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_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 20e2e1bd..ae288a34 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -220,6 +220,13 @@ in # Agents poll this URL for Forgejo notifications. Derived from # hyperhive.forge.{domain,httpPort} so it tracks forge config changes. HIVE_FORGE_URL = "http://${config.hyperhive.forge.domain}:${toString config.hyperhive.forge.httpPort}"; + } + // lib.optionalAttrs config.hyperhive.matrix.gui.enable { + # Optional matrix-GUI static dist mounted at /matrix/ by the + # dashboard router (#607 v0). Pre-#15 / pre-nginx-front: this is + # the simplest same-origin shape — fluffychat-web ships as a + # static dist, no runtime daemon needed. + HIVE_MATRIX_GUI_DIR = "${config.hyperhive.matrix.gui.package}"; }; serviceConfig = { ExecStart = "${cfg.package}/bin/hive-c0re --socket /run/hyperhive/host.sock serve --hyperhive-flake ${cfg.hyperhiveFlake} --dashboard-port ${toString cfg.dashboardPort} --operator-pronouns ${lib.escapeShellArg cfg.operatorPronouns} --context-window-tokens ${lib.escapeShellArg (builtins.toJSON cfg.contextWindowTokens)}"; diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index d029728d..fb5244e2 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -155,6 +155,41 @@ in registration tokens. ''; }; + + gui = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + 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. + + Same-origin via hive-c0re is the simplest single-host + shape; the post-#15 nginx-front re-root (`https://matrix.''${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; + defaultText = lib.literalExpression "pkgs.fluffychat-web"; + description = '' + Static web client dist to serve at `/matrix/`. Defaults to + `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. + ''; + }; + }; }; config = lib.mkIf cfg.enable {