Watch
0
0
Fork
You've already forked hyperhive
0

swarmctl: add bao init and bao grant-bootstrap

init prints the store's keys and root token once and keeps none; with
--and-grant it goes on to the granter bootstrap in the same process.
grant-bootstrap does that step alone, reading the root token from a
hidden prompt or stdin. The token reaches bao only as BAO_TOKEN in the
child's environment.
This commit is contained in:
atlas 2026-11-08 01:29:09 +01:00 • committed by mara
commit a20596fa59

View file

@ -19,7 +19,8 @@
//! work they ask for is only the controller's to do (see [`agent`]). They
//! reach it over its unix socket on its host, or, with `--swarm`, through
//! the swarm domain's gateway as the operator's own SSO account
//! ([`remote`]). The `user` verbs act on the local users file only.
//! ([`remote`]). The `user` verbs act on the local users file only; the `bao`
//! verbs run the `bao` CLI against the secret store.
//!
//! Distinct from `hivectl`, which drives one hive's `hive-c0re` over its
//! admin socket. This crate deliberately does not link `swarm-controller`,
@ -27,6 +28,7 @@
mod agent;
mod agents_md;
mod bao;
mod forge;
mod inference;
mod login;
@ -220,6 +222,15 @@ enum Verb {
#[command(subcommand)]
command: SetupVerb,
},
/// Initialise the swarm's secret store and bootstrap its granter.
///
/// Runs the `bao` CLI against the store over HTTPS, so the store need
/// not run on this host. The root token never reaches a file, an
/// argument list or an environment variable of this process.
Bao {
#[command(subcommand)]
command: BaoVerb,
},
/// Log in to the swarm named by `--swarm` as your own SSO account.
///
/// With no `--swarm` or `SWARMCTL_SWARM`, logs in to
@ -286,6 +297,25 @@ struct SetupStatusArgs {
socket: ControllerSocketArg,
}
#[derive(Subcommand)]
enum BaoVerb {
/// Initialise the store, printing its keys and the root token once.
///
/// Prints the unseal or recovery keys and the root token to stdout, once,
/// and stores none of them: record them off this host before doing
/// anything else. Refuses an initialised store.
Init(bao::InitArgs),
/// Bootstrap the granter: the one-time step that lets `swarm-bao-granter-role`
/// write every `swarm-*` policy and role.
///
/// Reads the root token from a hidden prompt, or from stdin when stdin isn't
/// a terminal; never from an argument or an environment variable. Writes
/// the `bao-bootstrap` policy, places a 24h grant token for the granter unit
/// and restarts it, so run it as root on the host running that unit. Says so
/// and does nothing when the granter exists already.
GrantBootstrap(bao::GrantBootstrapArgs),
}
#[derive(Subcommand)]
enum AgentVerb {
/// Queue creation of a new agent on a hive in this swarm.
@ -798,6 +828,13 @@ fn main() -> Result<()> {
let target = remote.target(args.controller_socket)?;
forge::make_admin(&target, &args.name)
}
// Acts through the `bao` CLI only: none of the `SWARMCTL_AUTHELIA_*` values apply.
Verb::Bao {
command: BaoVerb::Init(args),
} => bao::init(args),
Verb::Bao {
command: BaoVerb::GrantBootstrap(args),
} => bao::grant_bootstrap(args),
// Same socket-resolution reasoning as `Create` above.
Verb::Schedule { command } => schedule(&remote, command),
// Neither the controller socket nor the users file is required: a
@ -1718,7 +1755,7 @@ mod tests {
/// rather than from this one: a write fd on it held here could be inherited
/// by a child another test thread forks, and exec'ing a file some process
/// still holds open for writing fails with `ETXTBSY`.
fn write_executable(path: &std::path::Path, body: &str) {
pub(crate) fn write_executable(path: &std::path::Path, body: &str) {
let status = std::process::Command::new("/bin/sh")
.args([
"-c",