hivectl: extend --password to forge; clarify matrix M_USER_IN_USE asymmetry (#663 / argus)

This commit is contained in:
damocles 2026-05-30 21:55:25 +02:00 committed by Mara
commit 97797cf790
3 changed files with 100 additions and 13 deletions

View file

@ -70,11 +70,32 @@ enum ForgeCmd {
/// non-container account), creates the forgejo user and prints the
/// freshly-minted token to stdout — no `/var/lib/hyperhive/agents/`
/// directory is created for the user (#662).
///
/// Without `--password` / `--password-stdin` a random throwaway is
/// used (fine for agents — they auth by token via tea / hive-forge).
/// Set a password to log into the forge web UI afterwards (#663).
/// `--password` is idempotent: re-running with the same value sets
/// the same password (covers password resets on already-created
/// accounts since `forgejo admin user create` silently no-ops once
/// the user exists).
CreateUser {
/// Forgejo username. For agents: the container/agent name
/// (`<n>` in `h-<n>`; manager uses the literal `manager`).
/// For humans: any forgejo username — `mara`, `damocles`, etc.
name: String,
/// Set the account password to this string instead of a random
/// throwaway. Use this for operator accounts that need to log
/// into the forge web UI. Mutually exclusive with
/// `--password-stdin`. WARNING: the password is visible in
/// shell history + process listings; prefer `--password-stdin`
/// for anything sensitive.
#[arg(long)]
password: Option<String>,
/// Read the password from stdin (single line, trailing newline
/// stripped) instead of an inline flag. Mutually exclusive with
/// `--password`.
#[arg(long, conflicts_with = "password")]
password_stdin: bool,
},
}
@ -128,7 +149,11 @@ async fn main() -> Result<()> {
let cli = Cli::parse();
match cli.cmd {
Cmd::Forge { cmd } => match cmd {
ForgeCmd::CreateUser { name } => forge_create_user(&name).await,
ForgeCmd::CreateUser {
name,
password,
password_stdin,
} => forge_create_user(&name, password.as_deref(), password_stdin).await,
},
Cmd::Matrix { cmd } => match cmd {
MatrixCmd::CreateUser {
@ -149,13 +174,23 @@ fn is_agent(name: &str) -> bool {
Coordinator::agent_state_root(name).exists()
}
async fn forge_create_user(name: &str) -> Result<()> {
async fn forge_create_user(
name: &str,
password: Option<&str>,
password_stdin: bool,
) -> Result<()> {
if !hive_c0re::forge::is_present().await {
bail!(
"hive-forge container not running — start it (services.hyperhive.forge.enable = true) before provisioning forge users"
);
}
let user_password = resolve_password(password, password_stdin)?;
if is_agent(name) {
if user_password.is_some() {
bail!(
"forge create-user: --password / --password-stdin is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via API token"
);
}
hive_c0re::forge::ensure_user_for(name)
.await
.with_context(|| format!("forge create-user {name}"))?;
@ -163,11 +198,18 @@ async fn forge_create_user(name: &str) -> Result<()> {
println!("forge: provisioned agent user '{name}'");
println!("token persisted at: {}", path.display());
} else {
let token = hive_c0re::forge::provision_user_token(name)
let token = hive_c0re::forge::provision_user_token(name, user_password.as_deref())
.await
.with_context(|| format!("forge create-user {name}"))?;
println!("forge: provisioned user '{name}' (not an agent — token not persisted)");
println!("token: {token}");
if password.is_some() || password_stdin {
println!("password: set as supplied — use it to log into the forge web UI");
} else {
println!(
"password: random throwaway (not surfaced — pass --password or --password-stdin to set one you can use)"
);
}
}
Ok(())
}
@ -221,7 +263,7 @@ async fn matrix_create_user(
// doesn't accept a password — agents auth by access_token,
// never by password. Refuse rather than silently dropping it.
bail!(
"matrix create-user: --password is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via access_token"
"matrix create-user: --password / --password-stdin is for non-agent (operator) accounts only; '{name}' is an agent which authenticates via access_token"
);
}
hive_c0re::matrix::ensure_user_for(&client, name, &register_token)