Watch
0
0
Fork
You've already forked hyperhive
0

fix(#1375): clean up pedantic warnings and re-enable -D warnings without pedantic bypass

This commit is contained in:
damocles 2026-06-05 15:59:45 +02:00 • committed by mara
commit fb726197ea
28 changed files with 109 additions and 90 deletions

View file

@ -342,7 +342,7 @@
inherit cargoArtifacts nativeBuildInputs; inherit cargoArtifacts nativeBuildInputs;
pname = "hyperhive-workspace"; pname = "hyperhive-workspace";
version = "0.1.0"; version = "0.1.0";
cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings -A clippy::pedantic"; cargoClippyExtraArgs = "--workspace --all-targets -- -D warnings";
}; };
# `cargo test --workspace` lifted out of `buildPackage` so the # `cargo test --workspace` lifted out of `buildPackage` so the
# `hyperhive-assets` dep (which `hive-ag3nt::prompt::tests` # `hyperhive-assets` dep (which `hive-ag3nt::prompt::tests`

View file

@ -65,7 +65,8 @@ fn format_task(id: &str, task: &serde_json::Value) -> String {
let now = std::time::SystemTime::now() let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
.as_secs() as i64; .as_secs()
.cast_signed();
let _ = write!(out, ", running for {}s", now - started); let _ = write!(out, ", running for {}s", now - started);
} }
if let (Some(completed), Some(started)) = if let (Some(completed), Some(started)) =
@ -76,8 +77,8 @@ fn format_task(id: &str, task: &serde_json::Value) -> String {
let out_file = paths::task_out(id); let out_file = paths::task_out(id);
let err_file = paths::task_err(id); let err_file = paths::task_err(id);
let out_len = std::fs::metadata(&out_file).map(|m| m.len()).unwrap_or(0); let out_len = std::fs::metadata(&out_file).map_or(0, |m| m.len());
let err_len = std::fs::metadata(&err_file).map(|m| m.len()).unwrap_or(0); let err_len = std::fs::metadata(&err_file).map_or(0, |m| m.len());
if let Some(stdout) = task["stdout_tail"].as_str() { if let Some(stdout) = task["stdout_tail"].as_str() {
let s = stdout.trim(); let s = stdout.trim();
@ -151,6 +152,7 @@ struct BashRunArgs {
wait_seconds: Option<u64>, wait_seconds: Option<u64>,
} }
#[allow(clippy::unnecessary_wraps)]
fn default_wait() -> Option<u64> { fn default_wait() -> Option<u64> {
Some(3) Some(3)
} }

View file

@ -33,8 +33,7 @@ pub fn tasks_dir() -> PathBuf {
let state_path = PathBuf::from(&state); let state_path = PathBuf::from(&state);
state_path state_path
.parent() .parent()
.map(|p| p.join("harness")) .map_or_else(|| PathBuf::from(state), |p| p.join("harness"))
.unwrap_or_else(|| PathBuf::from(state))
}; };
base.join("bash-tasks") base.join("bash-tasks")
} }
@ -64,8 +63,7 @@ pub fn mcp_loose_ends_dir() -> PathBuf {
let state_path = PathBuf::from(&state); let state_path = PathBuf::from(&state);
state_path state_path
.parent() .parent()
.map(|p| p.join("harness")) .map_or_else(|| PathBuf::from(state), |p| p.join("harness"))
.unwrap_or_else(|| PathBuf::from(state))
}; };
base.join("mcp-loose-ends") base.join("mcp-loose-ends")
} }

View file

@ -49,7 +49,8 @@ fn now_unix() -> i64 {
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap_or_default() .unwrap_or_default()
.as_secs() as i64 .as_secs()
.cast_signed()
} }
/// Generate a task ID: `<timestamp_hex><seq_hex>`. /// Generate a task ID: `<timestamp_hex><seq_hex>`.
@ -112,7 +113,7 @@ fn refresh_loose_ends() {
let dest = dir.join("bash.json"); let dest = dir.join("bash.json");
let tmp = dest.with_extension("json.tmp"); let tmp = dest.with_extension("json.tmp");
let json = serde_json::to_string(&items).unwrap_or_else(|_| "[]".to_owned()); let json = serde_json::to_string(&items).unwrap_or_else(|_| "[]".to_owned());
if let Err(e) = std::fs::write(&tmp, &json).and_then(|_| std::fs::rename(&tmp, &dest)) { if let Err(e) = std::fs::write(&tmp, &json).and_then(|()| std::fs::rename(&tmp, &dest)) {
tracing::warn!(error = ?e, "bash_runner: write mcp-loose-ends/bash.json failed"); tracing::warn!(error = ?e, "bash_runner: write mcp-loose-ends/bash.json failed");
} }
} }
@ -220,7 +221,7 @@ async fn run_loop(socket: PathBuf) {
let claimed: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new())); let claimed: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
loop { loop {
poll_once(&socket, &claimed).await; poll_once(&socket, &claimed);
tokio::time::sleep(POLL_INTERVAL).await; tokio::time::sleep(POLL_INTERVAL).await;
} }
} }
@ -255,7 +256,7 @@ async fn mark_interrupted(socket: &Path) {
} }
} }
async fn poll_once(socket: &Path, claimed: &Arc<Mutex<HashSet<String>>>) { fn poll_once(socket: &Path, claimed: &Arc<Mutex<HashSet<String>>>) {
let Ok(rd) = std::fs::read_dir(paths::tasks_dir()) else { let Ok(rd) = std::fs::read_dir(paths::tasks_dir()) else {
return; return;
}; };
@ -452,6 +453,8 @@ pub(crate) async fn send_wake(
summary: &str, summary: &str,
output: Option<(&str, &str)>, output: Option<(&str, &str)>,
) { ) {
use tokio::io::{AsyncBufReadExt as _, BufReader};
use tokio::net::UnixStream;
let mut body = format!("bash task `{id}` finished: {summary}"); let mut body = format!("bash task `{id}` finished: {summary}");
if let Some((stdout, stderr)) = output { if let Some((stdout, stderr)) = output {
if !stdout.is_empty() { if !stdout.is_empty() {
@ -471,9 +474,6 @@ pub(crate) async fn send_wake(
transient: true, transient: true,
}; };
use tokio::io::{AsyncBufReadExt as _, BufReader};
use tokio::net::UnixStream;
match UnixStream::connect(socket).await { match UnixStream::connect(socket).await {
Ok(stream) => { Ok(stream) => {
let (read, mut write) = stream.into_split(); let (read, mut write) = stream.into_split();

View file

@ -301,6 +301,7 @@ pub(crate) async fn dispatch_shared(
}) })
} }
#[allow(clippy::too_many_lines)]
async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) -> AgentResponse { async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) -> AgentResponse {
if let Some(resp) = dispatch_shared(req, agent, coord).await { if let Some(resp) = dispatch_shared(req, agent, coord).await {
return resp; return resp;
@ -497,8 +498,7 @@ async fn dispatch(req: &AgentRequest, agent: &str, coord: &Arc<Coordinator>) ->
}; };
} }
tracing::info!(%agent, %name, "agent: request_init_config for child"); tracing::info!(%agent, %name, "agent: request_init_config for child");
match crate::manager_server::submit_init_config(coord, name, description.clone()).await match crate::manager_server::submit_init_config(coord, name, description.clone()) {
{
Ok(_id) => AgentResponse::Ok, Ok(_id) => AgentResponse::Ok,
Err(e) => AgentResponse::Err { Err(e) => AgentResponse::Err {
message: format!("{e:#}"), message: format!("{e:#}"),
@ -591,7 +591,7 @@ pub async fn dispatch_host_journal(
.await .await
{ {
Ok((stdout, stderr)) => { Ok((stdout, stderr)) => {
let content = if !stdout.is_empty() { stdout } else { stderr }; let content = if stdout.is_empty() { stderr } else { stdout };
AgentResponse::HostJournal { content } AgentResponse::HostJournal { content }
} }
Err(e) => AgentResponse::Err { Err(e) => AgentResponse::Err {

View file

@ -210,7 +210,7 @@ pub fn topology_sort(
let mut queue: VecDeque<String> = VecDeque::new(); let mut queue: VecDeque<String> = VecDeque::new();
// Seed roots: entries with no parent, or names not present in topo at all. // Seed roots: entries with no parent, or names not present in topo at all.
for name in &name_set { for name in &name_set {
if topo.get(name).is_none_or(|p| p.is_none()) { if topo.get(name).is_none_or(Option::is_none) {
depth.insert(name.clone(), 0); depth.insert(name.clone(), 0);
queue.push_back(name.clone()); queue.push_back(name.clone());
} }

View file

@ -23,7 +23,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::coordinator::Coordinator; use crate::coordinator::Coordinator;
const VACUUM_INTERVAL: Duration = Duration::from_secs(3600); const VACUUM_INTERVAL: Duration = Duration::from_hours(1);
/// Keep completed task files for 48 hours before sweeping them. /// Keep completed task files for 48 hours before sweeping them.
const KEEP_SECS: i64 = 48 * 3600; const KEEP_SECS: i64 = 48 * 3600;
@ -101,7 +101,7 @@ fn should_delete(json_path: &Path, cutoff: i64) -> bool {
} }
let completed_at = v let completed_at = v
.get("completed_at") .get("completed_at")
.and_then(|t| t.as_i64()) .and_then(serde_json::Value::as_i64)
.unwrap_or(i64::MAX); .unwrap_or(i64::MAX);
completed_at < cutoff completed_at < cutoff
} }

View file

@ -60,7 +60,7 @@ enum Cmd {
/// Gateway htpasswd user management. Add, remove, or list users in /// Gateway htpasswd user management. Add, remove, or list users in
/// an htpasswd file used by the gateway's HTTP Basic auth /// an htpasswd file used by the gateway's HTTP Basic auth
/// (`services.hyperhive.gateway.auth`). Credentials are stored as /// (`services.hyperhive.gateway.auth`). Credentials are stored as
/// BCrypt hashes — no extra service or PAM required. /// `BCrypt` hashes — no extra service or PAM required.
Gateway { Gateway {
#[command(subcommand)] #[command(subcommand)]
cmd: GatewayCmd, cmd: GatewayCmd,
@ -179,7 +179,7 @@ enum MatrixCmd {
SyncAdmin, SyncAdmin,
/// Promote a matrix user to homeserver admin via the admin API. /// Promote a matrix user to homeserver admin via the admin API.
/// Uses the hive system admin token at /// Uses the hive system admin token at
/// `/var/lib/hyperhive/matrix-admin-token`. The server_name is /// `/var/lib/hyperhive/matrix-admin-token`. The `server_name` is
/// discovered automatically from the running homeserver. /// discovered automatically from the running homeserver.
PromoteUser { PromoteUser {
/// Matrix localpart of the user to promote (e.g. `argus`). /// Matrix localpart of the user to promote (e.g. `argus`).
@ -205,7 +205,7 @@ const DEFAULT_HTPASSWD_FILE: &str = "/var/lib/hyperhive/gateway/gateway.htpasswd
#[derive(Subcommand)] #[derive(Subcommand)]
enum GatewayCmd { enum GatewayCmd {
/// Add a new user or update the password of an existing user in the /// Add a new user or update the password of an existing user in the
/// gateway htpasswd file. The password is hashed with BCrypt (cost 12). /// gateway htpasswd file. The password is hashed with `BCrypt` (cost 12).
/// ///
/// Pass `--password-stdin` when scripting or when you don't want the /// Pass `--password-stdin` when scripting or when you don't want the
/// password visible in shell history. The file is created if it does /// password visible in shell history. The file is created if it does
@ -581,7 +581,7 @@ fn htpasswd_write(path: &Path, lines: &[String]) -> Result<()> {
} }
/// Add or update `username` in the htpasswd file at `file`, hashing /// Add or update `username` in the htpasswd file at `file`, hashing
/// `password` with BCrypt (cost 12). Creates the file when absent. /// `password` with `BCrypt` (cost 12). Creates the file when absent.
fn gateway_create_user( fn gateway_create_user(
file: &Path, file: &Path,
username: &str, username: &str,
@ -698,7 +698,7 @@ fn validate_htpasswd_username(username: &str) -> Result<()> {
if username.contains(':') { if username.contains(':') {
bail!("username must not contain ':' (htpasswd field separator)"); bail!("username must not contain ':' (htpasswd field separator)");
} }
if username.chars().any(|c| c.is_control()) { if username.chars().any(char::is_control) {
bail!("username must not contain control characters"); bail!("username must not contain control characters");
} }
Ok(()) Ok(())

View file

@ -118,11 +118,11 @@ fn read_harness_flags(name: &str) -> (bool, bool) {
{ {
let rl = v let rl = v
.get("rate_limited") .get("rate_limited")
.and_then(|x| x.as_bool()) .and_then(serde_json::Value::as_bool)
.unwrap_or(false); .unwrap_or(false);
let nl = v let nl = v
.get("needs_login") .get("needs_login")
.and_then(|x| x.as_bool()) .and_then(serde_json::Value::as_bool)
.unwrap_or(false); .unwrap_or(false);
return (rl, nl); return (rl, nl);
} }

View file

@ -433,7 +433,7 @@ impl Coordinator {
} }
/// Emit a `ToolGroupsChanged` snapshot event. Called from the /// Emit a `ToolGroupsChanged` snapshot event. Called from the
/// rebuild-queue worker after a `PermChange` / ToolGroups entry /// rebuild-queue worker after a `PermChange` / `ToolGroups` entry
/// commits the JSON file, so the P3RM1SS10NS tab updates live. /// commits the JSON file, so the P3RM1SS10NS tab updates live.
pub fn emit_tool_groups_snapshot(self: &Arc<Self>) { pub fn emit_tool_groups_snapshot(self: &Arc<Self>) {
use hive_sh4re::ToolGroup; use hive_sh4re::ToolGroup;

View file

@ -1432,8 +1432,7 @@ mod tests {
fn tmproot(tag: &str) -> std::path::PathBuf { fn tmproot(tag: &str) -> std::path::PathBuf {
let ts = std::time::SystemTime::now() let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos()) .map_or(0, |d| d.as_nanos());
.unwrap_or(0);
let p = std::env::temp_dir().join(format!("hyperhive-test-{tag}-{ts}")); let p = std::env::temp_dir().join(format!("hyperhive-test-{tag}-{ts}"));
std::fs::create_dir_all(&p).unwrap(); std::fs::create_dir_all(&p).unwrap();
p p

View file

@ -415,7 +415,7 @@ mod tests {
.get("kind") .get("kind")
.and_then(|k| k.as_str()) .and_then(|k| k.as_str())
.expect("kind field present"); .expect("kind field present");
assert_eq!(ev.kind_tag(), serde_kind, "kind_tag() drift on {ev:?}",); assert_eq!(ev.kind_tag(), serde_kind, "kind_tag() drift on {ev:?}");
} }
} }
} }

View file

@ -228,9 +228,9 @@ pub async fn write(names: &[String]) -> Result<()> {
/// (gateway container temporarily down, systemd-run error) is /// (gateway container temporarily down, systemd-run error) is
/// recovered automatically without requiring a new file write. /// recovered automatically without requiring a new file write.
/// ///
/// Backs off to one retry per [`RELOAD_RETRY_SECS`] after a failure so a /// Backs off to one retry per `RELOAD_RETRY_SECS` after a failure so a
/// permanently broken gateway doesn't hammer `systemctl` on every tick. /// permanently broken gateway doesn't hammer `systemctl` on every tick.
/// A fresh `write()` call always resets the backoff (new RELOAD_PENDING /// A fresh `write()` call always resets the backoff (new `RELOAD_PENDING`
/// set to `true` + immediate attempt) so topology changes are still /// set to `true` + immediate attempt) so topology changes are still
/// applied promptly. /// applied promptly.
pub async fn reload_if_pending() { pub async fn reload_if_pending() {

View file

@ -25,7 +25,7 @@ pub const LOCAL_DIR: &str = "/var/lib/hyperhive/knowledge";
pub const CONTAINER_MOUNT: &str = "/knowledge"; pub const CONTAINER_MOUNT: &str = "/knowledge";
/// Default README pushed to a freshly created `internal/knowledge` repo. /// Default README pushed to a freshly created `internal/knowledge` repo.
/// Short explanation + empty ToC with an HTML comment instructing contributors /// Short explanation + empty table-of-contents with an HTML comment instructing contributors
/// to add entries when they create new files. /// to add entries when they create new files.
const README_CONTENT: &str = "\ const README_CONTENT: &str = "\
# knowledge # knowledge
@ -114,10 +114,10 @@ async fn seed_readme(core_token: &str) -> Result<()> {
.args(["-C", LOCAL_DIR].iter().chain(args.iter())) .args(["-C", LOCAL_DIR].iter().chain(args.iter()))
.output() .output()
.await .await
.with_context(|| format!("git {:?}", args))?; .with_context(|| format!("git {args:?}"))?;
if !out.status.success() { if !out.status.success() {
let stderr = String::from_utf8_lossy(&out.stderr).trim().to_owned(); let stderr = String::from_utf8_lossy(&out.stderr).trim().to_owned();
anyhow::bail!("git {:?} failed: {stderr}", args); anyhow::bail!("git {args:?} failed: {stderr}");
} }
} }
let url = format!("http://core:{core_token}@localhost:3000/{ORG}/{REPO}.git"); let url = format!("http://core:{core_token}@localhost:3000/{ORG}/{REPO}.git");

View file

@ -1127,6 +1127,7 @@ fn bind_child_agent_dirs(child: &str, binds: &mut Vec<BindMount>) {
}); });
} }
#[allow(clippy::too_many_lines)]
async fn set_nspawn_flags( async fn set_nspawn_flags(
container: &str, container: &str,
runtime_dir: &Path, runtime_dir: &Path,
@ -1299,7 +1300,7 @@ async fn set_nspawn_flags(
/// Build the per-line callback for `create_container_streaming` / /// Build the per-line callback for `create_container_streaming` /
/// `update_container_streaming`. Both ops share identical dispatch logic /// `update_container_streaming`. Both ops share identical dispatch logic
/// (stdout → info + append_stdout, stderr → warn + append_stderr); this /// (stdout → info + `append_stdout`, stderr → warn + `append_stderr`); this
/// helper avoids duplicating that match body across the two call sites. /// helper avoids duplicating that match body across the two call sites.
fn make_log_callback( fn make_log_callback(
logs: Option<std::sync::Arc<crate::build_logs::BuildLogs>>, logs: Option<std::sync::Arc<crate::build_logs::BuildLogs>>,
@ -1324,7 +1325,7 @@ fn make_log_callback(
} }
/// Execute a container operation via hive-priv and integrate with /// Execute a container operation via hive-priv and integrate with
/// build_logs.sqlite. hive-priv runs as root and forwards output lines /// `build_logs.sqlite`. hive-priv runs as root and forwards output lines
/// to hive-c0re in real time via the streaming priv protocol. Each line /// to hive-c0re in real time via the streaming priv protocol. Each line
/// is appended to the build-log row as it arrives, so the dashboard /// is appended to the build-log row as it arrives, so the dashboard
/// shows live progress during long `nixos-container create` / `update` runs. /// shows live progress during long `nixos-container create` / `update` runs.

View file

@ -206,6 +206,7 @@ async fn main() -> Result<()> {
/// dashboard), then serve the admin socket until a signal arrives. /// dashboard), then serve the admin socket until a signal arrives.
#[allow( #[allow(
clippy::too_many_arguments, clippy::too_many_arguments,
clippy::too_many_lines,
reason = "the `serve` subcommand's args are the host-level config the daemon \ reason = "the `serve` subcommand's args are the host-level config the daemon \
boots from (flakes, ports, pronouns, context-window + resource \ boots from (flakes, ports, pronouns, context-window + resource \
limits); they flow straight through to Coordinator::open" limits); they flow straight through to Coordinator::open"

View file

@ -83,7 +83,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
match req { match req {
ManagerRequest::RequestInitConfig { name, description } => { ManagerRequest::RequestInitConfig { name, description } => {
tracing::info!(%name, "manager: request_init_config"); tracing::info!(%name, "manager: request_init_config");
match submit_init_config(coord, name, description.clone()).await { match submit_init_config(coord, name, description.clone()) {
Ok(_id) => ManagerResponse::Ok, Ok(_id) => ManagerResponse::Ok,
Err(e) => ManagerResponse::Err { Err(e) => ManagerResponse::Err {
message: format!("{e:#}"), message: format!("{e:#}"),
@ -245,7 +245,7 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
.await .await
{ {
Ok((stdout, stderr)) => { Ok((stdout, stderr)) => {
let content = if !stdout.is_empty() { stdout } else { stderr }; let content = if stdout.is_empty() { stderr } else { stdout };
ManagerResponse::Logs { content } ManagerResponse::Logs { content }
} }
Err(e) => ManagerResponse::Err { Err(e) => ManagerResponse::Err {
@ -340,7 +340,7 @@ pub(crate) fn validate_commit_ref(commit_ref: &str) -> Result<()> {
/// Queue an `InitConfig` approval for a brand-new agent whose config repo /// Queue an `InitConfig` approval for a brand-new agent whose config repo
/// does not yet exist. Shared between the manager and agent sockets. /// does not yet exist. Shared between the manager and agent sockets.
pub(crate) async fn submit_init_config( pub(crate) fn submit_init_config(
coord: &Arc<Coordinator>, coord: &Arc<Coordinator>,
name: &str, name: &str,
description: Option<String>, description: Option<String>,

View file

@ -347,7 +347,7 @@ async fn discover_admin_room_id(
} }
json["room_id"] json["room_id"]
.as_str() .as_str()
.map(|s| s.to_owned()) .map(ToString::to_string)
.ok_or_else(|| anyhow::anyhow!("matrix: admin room alias response missing room_id: {json}")) .ok_or_else(|| anyhow::anyhow!("matrix: admin room alias response missing room_id: {json}"))
} }
@ -449,7 +449,7 @@ mod extract_new_password_tests {
/// ///
/// Strategy: send the command, capture its `event_id`, then poll backwards /// Strategy: send the command, capture its `event_id`, then poll backwards
/// (`dir=b&limit=20`) on each tick. Events in a backward response are /// (`dir=b&limit=20`) on each tick. Events in a backward response are
/// newest-first; we walk the list until we find our own command event_id, /// newest-first; we walk the list until we find our own command `event_id`,
/// then stop — everything before that marker in the list is a response that /// then stop — everything before that marker in the list is a response that
/// arrived *after* our command. We check `body` and `formatted_body` of /// arrived *after* our command. We check `body` and `formatted_body` of
/// every non-self message in that window. /// every non-self message in that window.
@ -651,31 +651,30 @@ pub async fn ensure_user_for(
// Account already exists — try to re-login with the stored password. // Account already exists — try to re-login with the stored password.
tracing::info!(%name, "matrix: user already exists, attempting login with stored password"); tracing::info!(%name, "matrix: user already exists, attempting login with stored password");
let pw_path = password_path(name); let pw_path = password_path(name);
let stored = match std::fs::read_to_string(&pw_path) let stored = if let Some(pw) = std::fs::read_to_string(&pw_path)
.ok() .ok()
.map(|s| s.trim().to_owned()) .map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
{ {
Some(pw) => pw, pw
None => { } else {
// Password file missing — attempt auto-recovery via admin API. // Password file missing — attempt auto-recovery via admin API.
// This covers the case where agent state dirs were wiped but the // This covers the case where agent state dirs were wiped but the
// homeserver still has the accounts. Requires the hive admin // homeserver still has the accounts. Requires the hive admin
// token at /var/lib/hyperhive/matrix-admin-token. // token at /var/lib/hyperhive/matrix-admin-token.
tracing::info!( tracing::info!(
%name, %name,
"matrix: stored password missing, attempting admin-API auto-recovery" "matrix: stored password missing, attempting admin-API auto-recovery"
); );
match auto_reset_password(client, name).await { match auto_reset_password(client, name).await {
Ok(new_pw) => new_pw, Ok(new_pw) => new_pw,
Err(e) => { Err(e) => {
anyhow::bail!( anyhow::bail!(
"matrix: user {name} already exists but password is missing \ "matrix: user {name} already exists but password is missing \
and admin auto-recovery failed ({e:#}) — run:\n\ and admin auto-recovery failed ({e:#}) — run:\n\
hivectl matrix reset-password {name}\n\ hivectl matrix reset-password {name}\n\
hivectl matrix create-user {name}" hivectl matrix create-user {name}"
) )
}
} }
} }
}; };
@ -783,7 +782,7 @@ pub async fn sync_agent_standalone(name: &str) {
/// non-empty. Does NOT promote the account via API (that requires /// non-empty. Does NOT promote the account via API (that requires
/// admin rights which this fn bootstraps); on a fresh homeserver the /// admin rights which this fn bootstraps); on a fresh homeserver the
/// first-registered rule fires automatically; on an existing homeserver /// first-registered rule fires automatically; on an existing homeserver
/// the operator must promote the account once via `hivectl matrix /// the operator must promote the account once via
/// `hivectl matrix promote-user hive` or the conduit admin room. /// `hivectl matrix promote-user hive` or the conduit admin room.
pub async fn ensure_admin_user(client: &reqwest::Client, register_token: &str) -> Result<()> { pub async fn ensure_admin_user(client: &reqwest::Client, register_token: &str) -> Result<()> {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;

View file

@ -334,7 +334,7 @@ pub async fn lock_update_hyperhive() -> Result<()> {
/// Write the tool-groups file for `agent` and commit it atomically /// Write the tool-groups file for `agent` and commit it atomically
/// under `META_LOCK`. Ensures the JSON change is staged + committed /// under `META_LOCK`. Ensures the JSON change is staged + committed
/// before the next `prepare_deploy` or `sync_agents` runs, so the /// before the next `prepare_deploy` or `sync_agents` runs, so the
/// working tree is never left dirty by an untimely PermChange write. /// working tree is never left dirty by an untimely `PermChange` write.
pub async fn commit_tool_groups(agent: &str, groups: &[String]) -> Result<()> { pub async fn commit_tool_groups(agent: &str, groups: &[String]) -> Result<()> {
let _guard = META_LOCK.lock().await; let _guard = META_LOCK.lock().await;
crate::tool_groups::set_groups(agent, groups)?; crate::tool_groups::set_groups(agent, groups)?;
@ -467,11 +467,7 @@ pub async fn bulk_commit_topology(
.iter() .iter()
.filter_map(|(child, new_parent)| { .filter_map(|(child, new_parent)| {
let old = topo_before.get(*child).cloned().flatten(); let old = topo_before.get(*child).cloned().flatten();
if old.as_deref() != *new_parent { (old.as_deref() != *new_parent).then_some((child.to_string(), old))
Some((child.to_string(), old))
} else {
None
}
}) })
.collect(); .collect();
Ok(changed) Ok(changed)

View file

@ -147,7 +147,7 @@ fn migrate_harness_files(name: &str) {
match std::fs::rename(&src, &dst) { match std::fs::rename(&src, &dst) {
Ok(()) => tracing::info!(%name, %file, "migration: moved to harness dir"), Ok(()) => tracing::info!(%name, %file, "migration: moved to harness dir"),
Err(e) => { Err(e) => {
tracing::warn!(%name, %file, error = ?e, "migration: move to harness dir failed") tracing::warn!(%name, %file, error = ?e, "migration: move to harness dir failed");
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show more