fix(#1375): clean up pedantic warnings and re-enable -D warnings without pedantic bypass
This commit is contained in:
parent
da7f1d6c45
commit
fb726197ea
28 changed files with 109 additions and 90 deletions
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ pub async fn update_container(name: &str) -> Result<(String, String)> {
|
||||||
|
|
||||||
/// Streaming variant: forward stdout/stderr lines to `on_line` as they
|
/// Streaming variant: forward stdout/stderr lines to `on_line` as they
|
||||||
/// arrive. Returns `Ok(())` on success; the callback is responsible for
|
/// arrive. Returns `Ok(())` on success; the callback is responsible for
|
||||||
/// appending lines to build_logs or otherwise capturing the output.
|
/// appending lines to `build_logs` or otherwise capturing the output.
|
||||||
pub async fn update_container_streaming(
|
pub async fn update_container_streaming(
|
||||||
name: &str,
|
name: &str,
|
||||||
on_line: impl FnMut(PrivStream, &str),
|
on_line: impl FnMut(PrivStream, &str),
|
||||||
|
|
|
||||||
|
|
@ -1364,9 +1364,9 @@ mod tests {
|
||||||
assert!(!q.set_step(999, "anything"));
|
assert!(!q.set_step(999, "anything"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A MetaUpdate cascade Rebuild (with parent_id = Some(meta_id)) must
|
/// A `MetaUpdate` cascade `Rebuild` (with `parent_id` = `Some(meta_id)`) must
|
||||||
/// NOT dedup into a pre-existing Queued Rebuild with a different parent_id
|
/// NOT dedup into a pre-existing `Queued` `Rebuild` with a different `parent_id`
|
||||||
/// (e.g. from a startup sweep). Without the parent_id dedup guard the
|
/// (e.g. from a startup sweep). Without the `parent_id` dedup guard the
|
||||||
/// cascade rebuild would be swallowed and the agent would never rebuild
|
/// cascade rebuild would be swallowed and the agent would never rebuild
|
||||||
/// against the post-lock-bump meta.
|
/// against the post-lock-bump meta.
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ fn tick(coord: &Arc<Coordinator>) {
|
||||||
// Single-transaction batch: one DB lock acquisition for N reminders
|
// Single-transaction batch: one DB lock acquisition for N reminders
|
||||||
// instead of N sequential lock/unlock cycles.
|
// instead of N sequential lock/unlock cycles.
|
||||||
let results = coord.broker.deliver_reminders_batch(&items);
|
let results = coord.broker.deliver_reminders_batch(&items);
|
||||||
let any_delivered = results.iter().any(|r| r.is_ok());
|
let any_delivered = results.iter().any(Result::is_ok);
|
||||||
for ((id, agent, _body), result) in items.iter().zip(results.iter()) {
|
for ((id, agent, _body), result) in items.iter().zip(results.iter()) {
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
let reason = format!("{e:#}");
|
let reason = format!("{e:#}");
|
||||||
|
|
|
||||||
|
|
@ -666,7 +666,7 @@ mod tests {
|
||||||
let mut top = top_level_agents_in(&topo);
|
let mut top = top_level_agents_in(&topo);
|
||||||
top.sort();
|
top.sort();
|
||||||
let mut expected = vec![crate::lifecycle::MANAGER_NAME, "orphan"];
|
let mut expected = vec![crate::lifecycle::MANAGER_NAME, "orphan"];
|
||||||
expected.sort();
|
expected.sort_unstable();
|
||||||
assert_eq!(top, expected);
|
assert_eq!(top, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,12 @@ struct WhoamiResponse {
|
||||||
/// session to call whoami).
|
/// session to call whoami).
|
||||||
/// 3. Build the real Client with the sqlite store + `restore_session`
|
/// 3. Build the real Client with the sqlite store + `restore_session`
|
||||||
/// using a synthetic `MatrixSession`.
|
/// using a synthetic `MatrixSession`.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the token file is missing or empty, if the
|
||||||
|
/// `whoami` request fails, or if the matrix-sdk client fails to build
|
||||||
|
/// or restore the session.
|
||||||
pub async fn build_and_restore(
|
pub async fn build_and_restore(
|
||||||
homeserver: &str,
|
homeserver: &str,
|
||||||
token_file: &Path,
|
token_file: &Path,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ pub const WAKE_BODY_TRUNCATE: usize = 100;
|
||||||
/// `#[serde(tag = "cmd", rename_all = "snake_case")]`. Must be `"cmd"`,
|
/// `#[serde(tag = "cmd", rename_all = "snake_case")]`. Must be `"cmd"`,
|
||||||
/// not `"kind"` — the harness deserialises against the hive-sh4re type
|
/// not `"kind"` — the harness deserialises against the hive-sh4re type
|
||||||
/// and silently discards requests that don't match.
|
/// and silently discards requests that don't match.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error on socket connect failure, serialisation failure,
|
||||||
|
/// or I/O error writing to or reading from the socket.
|
||||||
pub async fn send_wake(socket: &Path, body: impl AsRef<str>) -> Result<()> {
|
pub async fn send_wake(socket: &Path, body: impl AsRef<str>) -> Result<()> {
|
||||||
use tokio::io::AsyncBufReadExt;
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
|
||||||
|
|
@ -117,6 +122,7 @@ pub fn format_unread_summary(rooms: &[crate::protocol::RoomUnread]) -> String {
|
||||||
/// Truncate `s` to `max` Unicode chars, appending `…` when cut.
|
/// Truncate `s` to `max` Unicode chars, appending `…` when cut.
|
||||||
/// Char-based not byte-based so multi-byte content (most chat) doesn't
|
/// Char-based not byte-based so multi-byte content (most chat) doesn't
|
||||||
/// get cut mid-codepoint.
|
/// get cut mid-codepoint.
|
||||||
|
#[must_use]
|
||||||
pub fn truncate_chars(s: &str, max: usize) -> String {
|
pub fn truncate_chars(s: &str, max: usize) -> String {
|
||||||
let mut end = s.len();
|
let mut end = s.len();
|
||||||
for (count, (i, _)) in s.char_indices().enumerate() {
|
for (count, (i, _)) in s.char_indices().enumerate() {
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn socket_listener() -> Result<UnixListener> {
|
fn socket_listener() -> Result<UnixListener> {
|
||||||
|
use std::os::unix::fs::PermissionsExt as _;
|
||||||
// Socket activation: systemd passes the socket as fd 3 when
|
// Socket activation: systemd passes the socket as fd 3 when
|
||||||
// LISTEN_FDS >= 1 and LISTEN_PID matches our pid.
|
// LISTEN_FDS >= 1 and LISTEN_PID matches our pid.
|
||||||
let listen_fds: Option<i32> = std::env::var("LISTEN_FDS")
|
let listen_fds: Option<i32> = std::env::var("LISTEN_FDS")
|
||||||
|
|
@ -92,7 +93,6 @@ fn socket_listener() -> Result<UnixListener> {
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
let listener = UnixListener::bind(path).with_context(|| format!("bind {PRIV_SOCK}"))?;
|
let listener = UnixListener::bind(path).with_context(|| format!("bind {PRIV_SOCK}"))?;
|
||||||
// Mode 0660: only the hive-core group can connect.
|
// Mode 0660: only the hive-core group can connect.
|
||||||
use std::os::unix::fs::PermissionsExt as _;
|
|
||||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o660))
|
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o660))
|
||||||
.context("chmod priv.sock")?;
|
.context("chmod priv.sock")?;
|
||||||
tracing::info!(path = PRIV_SOCK, "bound priv socket");
|
tracing::info!(path = PRIV_SOCK, "bound priv socket");
|
||||||
|
|
@ -163,6 +163,7 @@ async fn write_line_event(writer: &mut OwnedWriteHalf, stream: PrivStream, data:
|
||||||
/// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`)
|
/// For streaming ops (`CreateContainer`/`UpdateContainer` with `stream: true`)
|
||||||
/// output lines are forwarded to `writer` as `PrivEvent::Line` messages and
|
/// output lines are forwarded to `writer` as `PrivEvent::Line` messages and
|
||||||
/// the returned strings are empty.
|
/// the returned strings are empty.
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, String)> {
|
async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String, String)> {
|
||||||
match req {
|
match req {
|
||||||
PrivRequest::StartContainer { ref name } => {
|
PrivRequest::StartContainer { ref name } => {
|
||||||
|
|
@ -232,7 +233,15 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
||||||
} => {
|
} => {
|
||||||
validate_container_system_name(container)?;
|
validate_container_system_name(container)?;
|
||||||
read_container_journal(
|
read_container_journal(
|
||||||
container, lines, boot, output, unit, priority, grep, since, until,
|
container,
|
||||||
|
lines,
|
||||||
|
boot,
|
||||||
|
output,
|
||||||
|
unit.as_deref(),
|
||||||
|
priority.as_deref(),
|
||||||
|
grep.as_deref(),
|
||||||
|
since.as_deref(),
|
||||||
|
until.as_deref(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -308,9 +317,9 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
|
||||||
ref agent_name,
|
ref agent_name,
|
||||||
mode,
|
mode,
|
||||||
} => {
|
} => {
|
||||||
|
use std::os::unix::fs::PermissionsExt as _;
|
||||||
validate_agent_name(agent_name)?;
|
validate_agent_name(agent_name)?;
|
||||||
let path = socket_dir_path(agent_name);
|
let path = socket_dir_path(agent_name);
|
||||||
use std::os::unix::fs::PermissionsExt as _;
|
|
||||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode))
|
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode))
|
||||||
.with_context(|| format!("chmod {:o} {}", mode, path.display()))?;
|
.with_context(|| format!("chmod {:o} {}", mode, path.display()))?;
|
||||||
Ok((String::new(), String::new()))
|
Ok((String::new(), String::new()))
|
||||||
|
|
@ -597,17 +606,17 @@ async fn container_run_streaming(
|
||||||
/// hard error — journalctl's own diagnostic (folded into `stderr` with
|
/// hard error — journalctl's own diagnostic (folded into `stderr` with
|
||||||
/// the exit status) is what the caller surfaces to the operator, so the
|
/// the exit status) is what the caller surfaces to the operator, so the
|
||||||
/// helper never bails.
|
/// helper never bails.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
|
||||||
async fn read_container_journal(
|
async fn read_container_journal(
|
||||||
container: &str,
|
container: &str,
|
||||||
lines: u32,
|
lines: u32,
|
||||||
boot: bool,
|
boot: bool,
|
||||||
output: JournalOutput,
|
output: JournalOutput,
|
||||||
unit: &Option<String>,
|
unit: Option<&str>,
|
||||||
priority: &Option<String>,
|
priority: Option<&str>,
|
||||||
grep: &Option<String>,
|
grep: Option<&str>,
|
||||||
since: &Option<String>,
|
since: Option<&str>,
|
||||||
until: &Option<String>,
|
until: Option<&str>,
|
||||||
) -> Result<(String, String)> {
|
) -> Result<(String, String)> {
|
||||||
let mut args: Vec<String> = vec![
|
let mut args: Vec<String> = vec![
|
||||||
"-M".to_owned(),
|
"-M".to_owned(),
|
||||||
|
|
@ -622,11 +631,11 @@ async fn read_container_journal(
|
||||||
}
|
}
|
||||||
if let Some(u) = unit {
|
if let Some(u) = unit {
|
||||||
args.push("-u".to_owned());
|
args.push("-u".to_owned());
|
||||||
args.push(u.clone());
|
args.push(u.to_owned());
|
||||||
}
|
}
|
||||||
if let Some(p) = priority {
|
if let Some(p) = priority {
|
||||||
args.push("-p".to_owned());
|
args.push("-p".to_owned());
|
||||||
args.push(p.clone());
|
args.push(p.to_owned());
|
||||||
}
|
}
|
||||||
// `--grep=`/`--since=`/`--until=` use the `=`-joined form so a value
|
// `--grep=`/`--since=`/`--until=` use the `=`-joined form so a value
|
||||||
// can never be parsed as a separate journalctl flag.
|
// can never be parsed as a separate journalctl flag.
|
||||||
|
|
@ -833,6 +842,7 @@ fn write_nspawn_flags(
|
||||||
binds: &[BindMount],
|
binds: &[BindMount],
|
||||||
isolation: Option<&NetworkIsolation>,
|
isolation: Option<&NetworkIsolation>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
use std::fmt::Write as _;
|
||||||
let path = format!("/etc/nixos-containers/{container}.conf");
|
let path = format!("/etc/nixos-containers/{container}.conf");
|
||||||
let original = std::fs::read_to_string(&path).with_context(|| format!("read {path}"))?;
|
let original = std::fs::read_to_string(&path).with_context(|| format!("read {path}"))?;
|
||||||
let lines: Vec<&str> = original
|
let lines: Vec<&str> = original
|
||||||
|
|
@ -855,10 +865,10 @@ fn write_nspawn_flags(
|
||||||
if let Some(iso) = isolation {
|
if let Some(iso) = isolation {
|
||||||
out.push_str("PRIVATE_NETWORK=1\n");
|
out.push_str("PRIVATE_NETWORK=1\n");
|
||||||
out.push_str("HOST_ADDRESS=\n");
|
out.push_str("HOST_ADDRESS=\n");
|
||||||
out.push_str(&format!("LOCAL_ADDRESS={}\n", iso.agent_ip));
|
let _ = writeln!(out, "LOCAL_ADDRESS={}", iso.agent_ip);
|
||||||
out.push_str("HOST_ADDRESS6=\n");
|
out.push_str("HOST_ADDRESS6=\n");
|
||||||
out.push_str("LOCAL_ADDRESS6=\n");
|
out.push_str("LOCAL_ADDRESS6=\n");
|
||||||
out.push_str(&format!("HOST_BRIDGE={}\n", iso.bridge));
|
let _ = writeln!(out, "HOST_BRIDGE={}", iso.bridge);
|
||||||
} else {
|
} else {
|
||||||
out.push_str("PRIVATE_NETWORK=0\n");
|
out.push_str("PRIVATE_NETWORK=0\n");
|
||||||
out.push_str("HOST_ADDRESS=\n");
|
out.push_str("HOST_ADDRESS=\n");
|
||||||
|
|
@ -875,6 +885,6 @@ fn write_nspawn_flags(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let flags_joined = flags.join(" ");
|
let flags_joined = flags.join(" ");
|
||||||
out.push_str(&format!("EXTRA_NSPAWN_FLAGS=\"{flags_joined}\"\n"));
|
let _ = writeln!(out, "EXTRA_NSPAWN_FLAGS=\"{flags_joined}\"");
|
||||||
std::fs::write(&path, out).with_context(|| format!("write {path}"))
|
std::fs::write(&path, out).with_context(|| format!("write {path}"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ pub enum JournalOutput {
|
||||||
|
|
||||||
impl JournalOutput {
|
impl JournalOutput {
|
||||||
/// The string journalctl expects after `--output=`.
|
/// The string journalctl expects after `--output=`.
|
||||||
|
#[must_use]
|
||||||
pub fn as_journalctl(self) -> &'static str {
|
pub fn as_journalctl(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
JournalOutput::Short => "short",
|
JournalOutput::Short => "short",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue