Watch
0
0
Fork
You've already forked hyperhive
0

swarmctl: interactive setup walks the first-run ceremony

swarmctl setup with no subcommand checks the setup status first, then takes the open steps one at a time: prompts for input, shows a running action as one live line, prints the store keys, root token and new password under a warning until the operator confirms, and names the deploy option for a step this host cannot do. It calls the code of bao init, bao grant-bootstrap, user add, forge make-admin, agent create, agent permissions set and agent set-state. Refuses without root or a terminal.
This commit is contained in:
atlas 2026-11-08 13:28:09 +01:00 • committed by mara
commit 44caa72c19

View file

@ -356,23 +356,30 @@ pub(crate) fn render(steps: &[Step]) -> String {
///
/// `target` and `users_file` arrive as the results of resolving them, so a
/// host that lacks either still reports the steps that need neither.
pub(crate) fn status(target: Result<Target>, users_file: Result<std::path::PathBuf>) -> bool {
pub(crate) fn status(target: &Result<Target>, users_file: &Result<std::path::PathBuf>) -> bool {
let observed = observe(target, users_file);
let steps = classify(&observed);
print!("{}", render(&steps));
complete(&steps)
}
fn observe(target: Result<Target>, users_file: Result<std::path::PathBuf>) -> Observed {
/// Run every check once. The inputs are borrowed so that `swarmctl setup` can
/// look again after each step.
pub(crate) fn observe(
target: &Result<Target>,
users_file: &Result<std::path::PathBuf>,
) -> Observed {
let store = bao_status();
let granter = granter();
let policy_units = policy_units();
let admin_user = users_file
.map_err(|e| unset_reason(&e))
.and_then(|path| has_admin_user(&path).map_err(|e| format!("{e:#}")));
.as_ref()
.map_err(unset_reason)
.and_then(|path| has_admin_user(path).map_err(|e| format!("{e:#}")));
let agents = target
.map_err(|e| unset_reason(&e))
.and_then(|t| agents(&t).map_err(|e| format!("{e:#}")));
.as_ref()
.map_err(unset_reason)
.and_then(|t| agents(t).map_err(|e| format!("{e:#}")));
Observed {
store,
granter,
@ -467,6 +474,20 @@ fn granter() -> Result<Granter, String> {
Ok(Granter { state, uploaded })
}
/// Whether this host has the granter unit, which only the host running
/// swarm-bao does.
pub(crate) fn granter_unit_exists() -> bool {
run(Command::new("systemctl").args([
"show",
GRANTER_UNIT,
"-p",
"LoadState",
"-p",
"ActiveState",
]))
.is_ok_and(|shown| parse_unit_state(&shown.stdout, GRANTER_UNIT).is_ok())
}
/// `ActiveState` out of `systemctl show -p LoadState -p ActiveState`.
fn parse_unit_state(shown: &str, unit: &str) -> Result<String, String> {
let field = |name: &str| {