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:
parent
4fae6a7766
commit
44caa72c19
1 changed files with 2172 additions and 82 deletions
|
|
@ -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| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue