Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
Author SHA1 Message Date
atlas
72bff26336 fix tracker refs flagged by check-issue-refs.sh
Reworded the two hits scripts/check-issue-refs.sh found — a bare hash-
number tag in .forgejo/workflows/ci.yml's push-trigger comment and a
hyperhive#4345 tag in matrix_account.rs's doc comment — into prose that
stands on its own, per the lint's own rule. Neither carried semantic
weight beyond what the prose already says once reworded.

Refs #4345
2026-09-16 14:53:44 +02:00
atlas
87af0f38d3 swarm-controller: make the homeserver default fn pure, fixing test race
homeserver_or_configured_default read DEFAULT_HOMESERVER_ENV internally,
so its three unit tests raced each other by set_var/remove_var-ing the
same process env var with no synchronization under cargo test's default
parallelism (argus, PR #4443 review).

Take the default as a plain parameter instead of reading the env var
inside the function. The one env read moves to a new
configured_default_homeserver() helper, called once at the edge
(main.rs's startup diagnostic); homeserver_or_configured_default itself
is now pure and its tests need no env mutation at all.

Refs #4345
2026-09-16 14:51:30 +02:00
atlas
24ca14fd4a ci.yml: note that CI deliberately does not run on main
Refs #4345
2026-09-16 14:45:07 +02:00
atlas
c894192e4f swarm-controller: add configured default matrix homeserver URL
Adds services.hyperhive.deploy.swarm-controller.matrixHomeserverUrl,
threaded to the daemon as SWARM_CONTROLLER_MATRIX_HOMESERVER_URL, and a
Rust helper (homeserver_or_configured_default) that lets a caller-supplied
homeserver keep overriding it. Config plumbing only: put_matrix_account
does not call the helper yet, so this is a no-op for every current caller.

Refs #4345
2026-09-16 14:43:13 +02:00
4 changed files with 108 additions and 2 deletions

View file

@ -1,6 +1,7 @@
name: CI name: CI
on: on:
# No `push` trigger, so nothing re-validates a merge commit on main — an accepted infra-load tradeoff mara signed off on, not an oversight.
pull_request: pull_request:
branches: ["**"] branches: ["**"]
# Lets `hive-forge ci-rerun` re-trigger CI via workflow-dispatch API # Lets `hive-forge ci-rerun` re-trigger CI via workflow-dispatch API

View file

@ -192,6 +192,15 @@ let
SWARM_CONTROLLER_AUTH_BRIDGE_URL = deployCfg.swarm-controller.authBridgeUrl; SWARM_CONTROLLER_AUTH_BRIDGE_URL = deployCfg.swarm-controller.authBridgeUrl;
}; };
# Swarm-wide default for `PUT .../matrix-accounts/{account}`'s own
# `homeserver` field, for a request that omits one. Same shape as
# `authBridgeEnv` above: genuinely optional, gated on the option
# resolving rather than assumed. Not read by anything yet — see the
# option's own description.
matrixHomeserverEnv = lib.optionalAttrs (deployCfg.swarm-controller.matrixHomeserverUrl != null) {
SWARM_CONTROLLER_MATRIX_HOMESERVER_URL = deployCfg.swarm-controller.matrixHomeserverUrl;
};
# The swarm's own display name, for `GET /api/swarm` (swarm-ui's chrome). # The swarm's own display name, for `GET /api/swarm` (swarm-ui's chrome).
# Gated on the option resolving, not defaulted to an empty string: an # Gated on the option resolving, not defaulted to an empty string: an
# operator who never named the swarm gets `name: null` from the # operator who never named the swarm gets `name: null` from the
@ -520,6 +529,24 @@ in
''; '';
}; };
matrixHomeserverUrl = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "https://matrix.example.org";
description = ''
Swarm-wide default homeserver for `PUT
.../matrix-accounts/{account}` requests that omit their own
`homeserver` — see that route's own doc comment
(`swarm-controller/src/matrix_account.rs`) for why the field is
optional in token mode and what omitting it currently resolves to.
`null` (the default) leaves that per-request resolution exactly as
it is today. **Not yet consulted by the route at all**: this option
only exists to carry the value in, ahead of the route being taught
to fall back to it.
'';
};
baoClientCertFile = lib.mkOption { baoClientCertFile = lib.mkOption {
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
default = null; default = null;
@ -859,6 +886,7 @@ in
// webhookEnv // webhookEnv
// authBridgeEnv // authBridgeEnv
// swarmNameEnv // swarmNameEnv
// matrixHomeserverEnv
// baoEnv // baoEnv
// otelEnv; // otelEnv;
}; };

View file

@ -1621,6 +1621,22 @@ async fn main() -> Result<()> {
} }
}; };
// Diagnostic only — `put_matrix_account` does not call this yet (see its
// own doc comment), so this cannot change any request's outcome. Logged
// so an operator can tell the config landed before the slice that
// consults it does.
if matrix_account::homeserver_or_configured_default(
None,
matrix_account::configured_default_homeserver(),
)
.is_none()
{
tracing::debug!(
"{}; matrix-account requests still need their own homeserver",
matrix_account::DEFAULT_HOMESERVER_ENV
);
}
let config_prs = forge_client.clone().map(config_pr::spawn); let config_prs = forge_client.clone().map(config_pr::spawn);
let state_forge = keep_forge_for_state(forge_client, webhook_secret.clone()); let state_forge = keep_forge_for_state(forge_client, webhook_secret.clone());

View file

@ -35,6 +35,41 @@ fn default_mode() -> String {
"token".to_owned() "token".to_owned()
} }
/// Env var the controller's NixOS module sets from
/// `services.hyperhive.deploy.swarm-controller.matrixHomeserverUrl` — the
/// swarm-wide default `PutMatrixAccountRequest::homeserver` falls back to
/// when a caller omits one. Read via [`configured_default_homeserver`], not
/// directly — see that fn's doc.
///
/// Not yet consulted by [`put_matrix_account`]: `homeserver_or_configured_default`
/// below exists for a later slice of this homeserver-default rollout to
/// call; this one only wires the config through.
pub(crate) const DEFAULT_HOMESERVER_ENV: &str = "SWARM_CONTROLLER_MATRIX_HOMESERVER_URL";
/// `caller`'s own homeserver, or `default` when the caller left it unset.
/// `caller` always wins — this only fills a gap it left, never replaces a
/// value it gave.
///
/// Takes `default` as a plain parameter rather than reading
/// [`DEFAULT_HOMESERVER_ENV`] itself: a caller wants that env read done once,
/// at the edge (see [`configured_default_homeserver`]), and keeping this fn
/// pure makes it testable without mutating shared process env — three tests
/// doing exactly that raced each other under `cargo test`'s default
/// parallelism in review.
pub(crate) fn homeserver_or_configured_default(
caller: Option<String>,
default: Option<String>,
) -> Option<String> {
caller.or(default)
}
/// Reads [`DEFAULT_HOMESERVER_ENV`] fresh each call — the one place this
/// crate touches that env var, so [`homeserver_or_configured_default`] above
/// can stay a pure function.
pub(crate) fn configured_default_homeserver() -> Option<String> {
std::env::var(DEFAULT_HOMESERVER_ENV).ok()
}
/// The credential to store for one agent's external matrix account. /// The credential to store for one agent's external matrix account.
/// ///
/// No `Debug` derive — matching `hive-c0re::dashboard::matrix_accounts`'s /// No `Debug` derive — matching `hive-c0re::dashboard::matrix_accounts`'s
@ -339,8 +374,8 @@ async fn matrix_password_login(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ use super::{
PutMatrixAccountRequest, is_reserved_account, password_fields, resolve_credential, PutMatrixAccountRequest, homeserver_or_configured_default, is_reserved_account,
token_credential, password_fields, resolve_credential, token_credential,
}; };
fn request(mode: &str) -> PutMatrixAccountRequest { fn request(mode: &str) -> PutMatrixAccountRequest {
@ -353,6 +388,32 @@ mod tests {
} }
} }
// `homeserver_or_configured_default` takes its default as a plain
// parameter rather than reading the env var itself, so these are pure
// — no process env mutation, and so no risk of racing each other (or
// any other test in the crate) under `cargo test`'s default parallelism.
#[test]
fn caller_supplied_homeserver_wins_even_with_a_default_configured() {
let result = homeserver_or_configured_default(
Some("https://caller.example.org".to_owned()),
Some("https://default.example.org".to_owned()),
);
assert_eq!(result.as_deref(), Some("https://caller.example.org"));
}
#[test]
fn falls_back_to_the_configured_default_when_the_caller_omits_one() {
let result =
homeserver_or_configured_default(None, Some("https://default.example.org".to_owned()));
assert_eq!(result.as_deref(), Some("https://default.example.org"));
}
#[test]
fn none_when_neither_caller_nor_default_is_set() {
assert_eq!(homeserver_or_configured_default(None, None), None);
}
#[test] #[test]
fn main_is_reserved_but_nothing_else_is() { fn main_is_reserved_but_nothing_else_is() {
assert!(is_reserved_account("main")); assert!(is_reserved_account("main"));