hyperhive/hivectl/src/forge.rs

89 lines
2.8 KiB
Rust

//! `hivectl forge create-user <name>` — provision a Forgejo account via the
//! daemon (which owns the forge admin token + account-token persistence);
//! hivectl just resolves the password client-side and relays the request.
use std::io::{self, Write};
use std::path::Path;
use anyhow::Result;
use hive_host_sock::{HostRequest, ReconcileDirection};
use crate::cli::ReconcileFrom;
use crate::util::{daemon_request, resolve_password};
pub(crate) async fn forge_create_user(
socket: &Path,
name: &str,
password: Option<&str>,
password_stdin: bool,
) -> Result<()> {
// Resolve the password client-side (inline flag or stdin read); the
// daemon never touches this process's stdin. The is-present check, the
// agent-vs-operator branch, and token persistence now live in the
// daemon handler.
let password = resolve_password(password, password_stdin)?;
daemon_request(
socket,
hive_host_sock::HostRequest::ForgeCreateUser {
name: crate::util::parse_ident(name)?,
password,
},
"forge",
)
.await
}
/// `hivectl forge reconcile-config <agent> [--from <forge|local>] [--verbose]`.
/// Always shows the divergence first (daemon computes it read-only), then
/// applies the chosen direction — from `--from` or an interactive prompt.
pub(crate) async fn forge_reconcile_config(
socket: &Path,
agent: &str,
from: Option<ReconcileFrom>,
verbose: bool,
) -> Result<()> {
daemon_request(
socket,
HostRequest::ReconcileConfigStatus {
agent: crate::util::parse_ident(agent)?,
verbose,
},
"forge",
)
.await?;
let direction = match from {
Some(f) => Some(f.into()),
None => prompt_direction()?,
};
let Some(direction) = direction else {
println!("forge: aborted — no changes made");
return Ok(());
};
daemon_request(
socket,
HostRequest::ReconcileConfigApply {
agent: crate::util::parse_ident(agent)?,
direction,
},
"forge",
)
.await
}
/// Prompt the operator for the reconcile direction after showing the diff.
/// Returns `None` to abort (empty / `q` / unrecognized input).
fn prompt_direction() -> Result<Option<ReconcileDirection>> {
print!("reconcile from [forge/local] (or q to abort): ");
io::stdout().flush()?;
let mut line = String::new();
io::stdin().read_line(&mut line)?;
match line.trim().to_ascii_lowercase().as_str() {
"forge" | "f" => Ok(Some(ReconcileDirection::Forge)),
"local" | "l" => Ok(Some(ReconcileDirection::Local)),
"" | "q" | "quit" => Ok(None),
other => {
println!("forge: unrecognized direction {other:?} — aborting");
Ok(None)
}
}
}