Setup: utente admin via web, ruoli e run-context con security-group, onboarding install/uninstall script
Nightly Build / build (push) Failing after 6m12s

This commit is contained in:
2026-07-20 12:54:56 +01:00
parent b6128e4053
commit 44dc67cda0
29 changed files with 1631 additions and 110 deletions
+47 -15
View File
@@ -25,13 +25,9 @@ use std::io::{self, IsTerminal, Write};
use anyhow::{Context, Result};
use skald_core::db::{self, SYSTEM_DB_PATH};
use skald_core::setup::{self, FirstAdmin};
use skald_core::users::UserManager;
/// The role id given to the first user. There is no `roles` table yet, and
/// `users.role_id` has no foreign key, so this is a plain string for now — the
/// seeded `admin` preset (blueprint §12) will adopt it later without a migration.
const ADMIN_ROLE: &str = "admin";
/// SQLCipher's per-user privacy is only as strong as the password's entropy
/// times the KDF cost (§5.1). The KDF is fixed; this is the floor we put under
/// the entropy. Not a substitute for a real strength meter — a deliberate,
@@ -139,6 +135,7 @@ async fn step_first_user(users: &UserManager, pool: &sqlx::SqlitePool, has_admin
println!("\nWelcome to Skald. Let's create the first user (the admin).\n");
let profile = prompt_profile()?;
let username = prompt_username()?;
let display_name = prompt_line("Display name (optional): ")?;
let display_name = display_name.trim();
@@ -148,16 +145,23 @@ async fn step_first_user(users: &UserManager, pool: &sqlx::SqlitePool, has_admin
let encrypt = prompt_encrypt()?;
let password = prompt_new_password()?;
let id = users
.register_user(&username, display_name, ADMIN_ROLE, Some(&password), encrypt)
.await
.context("creating the admin user")?;
// The first-run language choice is instance-wide: the registry config
// default every user follows until they override it on their profile.
skald_core::i18n::set_default_locale(pool, &locale)
.await
.context("saving the default language")?;
// The shared seam both setup shells call: seed the chosen profile's roles,
// create the admin, set the instance default language — one implementation, so
// the terminal and web wizards can never drift apart.
let id = setup::initialize_instance(
users,
pool,
&profile,
FirstAdmin {
username: &username,
display_name,
password: Some(&password),
encrypted: encrypt,
locale: Some(&locale),
},
)
.await
.context("initializing the instance")?;
println!("\n✓ Admin user '{username}' created (id {id}).");
if encrypt {
@@ -169,6 +173,34 @@ async fn step_first_user(users: &UserManager, pool: &sqlx::SqlitePool, has_admin
// ── Prompts ────────────────────────────────────────────────────────────────
/// Which seed profile to provision. The profile decides which domain roles the
/// instance starts with (`family` → member + children). With a single profile
/// there is nothing to choose, so it is selected silently.
fn prompt_profile() -> Result<String> {
let profiles = setup::seed_profiles();
if profiles.len() <= 1 {
return Ok(profiles
.into_iter()
.next()
.map(|p| p.id.to_string())
.unwrap_or_else(|| "family".to_string()));
}
println!("What kind of instance is this?");
for (i, p) in profiles.iter().enumerate() {
println!(" {}) {}", i + 1, p.label);
}
loop {
let line = prompt_line("Choose [1]: ")?;
let line = line.trim();
let choice = if line.is_empty() { 1 } else { line.parse::<usize>().unwrap_or(0) };
if (1..=profiles.len()).contains(&choice) {
println!();
return Ok(profiles[choice - 1].id.to_string());
}
println!(" Please enter a number between 1 and {}.", profiles.len());
}
}
fn prompt_username() -> Result<String> {
loop {
let name = prompt_line("Username: ")?;