fix(fips,kiosk): auto-activate FIPS at onboarding end + 5-min kiosk wait

1. FIPS auto-activate at server startup only fires if fips_key already
   exists on disk, which on a fresh install is never true until AFTER
   onboarding. By the time the user completes seed-generate/restore,
   archipelago has been running for minutes and the startup task has
   long since exited. User still had to hit Activate.

   Fix: call spawn_post_onboarding_fips_activate() from the tail of
   handle_seed_generate and handle_seed_restore — the moment the
   fips_key materialises, a detached task runs `fips::config::install`
   + `archipelago-fips.service activate`. Logged only, never blocks
   the onboarding RPC.

2. Kiosk health-poll window was 30 × 2s (configs/ copy was 60 × 2s
   but unused — the heredoc in build-auto-installer-iso.sh is what
   actually lands on disk). On .198's slower hardware archipelago
   /health wasn't ready within 60s, so Chromium launched against a
   not-yet-running backend → blank window until manual reboot. Bumped
   to 150 × 2s (5 min) + TimeoutStartSec=360. .253 was already well
   within the window; this protects the slower box too. Standalone
   configs/archipelago-kiosk.service updated in lockstep so the two
   copies don't drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-19 18:09:46 -04:00
parent 78e7c59e78
commit 6b78bd692d
3 changed files with 59 additions and 11 deletions

View File

@@ -26,6 +26,36 @@ impl Drop for OnboardingMnemonicState {
const MNEMONIC_TTL: std::time::Duration = std::time::Duration::from_secs(600); // 10 minutes
/// Best-effort: install fips.yaml + start archipelago-fips.service after the
/// seed onboarding has written the fips_key to disk. Runs in a detached task
/// so the user-facing RPC returns immediately — the systemctl calls can take
/// a few seconds the first time on slow hardware. Any failure is logged but
/// does not break onboarding; the user can still hit fips.install manually
/// from the dashboard as an escape hatch.
fn spawn_post_onboarding_fips_activate(data_dir: std::path::PathBuf) {
tokio::spawn(async move {
let identity_dir = data_dir.join("identity");
if !crate::identity::fips_key_exists(&identity_dir) {
return;
}
// Touch load_fips_keys first so any legacy raw-byte file is migrated
// to bech32 before we copy it into /etc/fips/.
if let Err(e) = crate::identity::load_fips_keys(&identity_dir).await {
tracing::warn!("post-onboarding fips key load/migrate failed: {}", e);
return;
}
if let Err(e) = crate::fips::config::install(&identity_dir).await {
tracing::warn!("post-onboarding fips config install failed: {}", e);
return;
}
if let Err(e) = crate::fips::service::activate(crate::fips::SERVICE_UNIT).await {
tracing::warn!("post-onboarding archipelago-fips activate failed: {}", e);
return;
}
tracing::info!("archipelago-fips auto-activated post-onboarding");
});
}
impl RpcHandler {
/// Generate a new 24-word BIP-39 mnemonic, derive and persist node keys.
/// Returns the words for the user to write down.
@@ -54,6 +84,11 @@ impl RpcHandler {
// Initialize identity index at 0.
crate::seed::save_identity_index(&self.config.data_dir, 0).await?;
// fips_key is now on disk — auto-activate archipelago-fips so the
// user doesn't have to hit an "Activate" button. Detached task;
// the onboarding RPC returns immediately.
spawn_post_onboarding_fips_activate(self.config.data_dir.clone());
let words: Vec<&str> = mnemonic.words().collect();
// Hold mnemonic in memory for the verify step.
@@ -193,6 +228,10 @@ impl RpcHandler {
let did = crate::identity::did_key_from_pubkey_hex(&pubkey_hex)?;
let nostr_npub = nostr_keys.public_key().to_bech32().unwrap_or_default();
// Same as seed.generate: the key is materialised, kick the FIPS
// service up without user interaction.
spawn_post_onboarding_fips_activate(self.config.data_dir.clone());
Ok(serde_json::json!({
"did": did,
"nostr_npub": nostr_npub,

View File

@@ -2648,15 +2648,22 @@ chmod +x /mnt/target/usr/local/bin/archipelago-kiosk-launcher
cat > /mnt/target/etc/systemd/system/archipelago-kiosk.service <<'KIOSKSVC'
[Unit]
Description=Archipelago Kiosk (X11 + Chromium)
After=archipelago.service
Wants=archipelago.service
After=archipelago.service systemd-user-sessions.service network-online.target
Wants=archipelago.service network-online.target
ConditionPathExists=/usr/local/bin/archipelago-kiosk-launcher
Conflicts=getty@tty1.service
[Service]
Type=simple
ExecStartPre=/bin/bash -c 'for i in $(seq 1 30); do curl -sf http://localhost/health >/dev/null 2>&1 && exit 0; sleep 2; done; exit 0'
# First-boot health-poll window is 300s (150 × 2s). Slow hardware
# (e.g. the atom-class box at .198) was blowing past the old 60s /
# 120s window, so Chromium launched against a not-yet-ready backend
# and showed a blank window that only recovered on reboot. At 300s
# even the unbundled-FileBrowser-pull + archipelago state sync + frontend
# settle fits with headroom. TimeoutStartSec is bumped in lockstep.
ExecStartPre=/bin/bash -c 'for i in $(seq 1 150); do curl -sf http://localhost/health >/dev/null 2>&1 && exit 0; sleep 2; done; exit 0'
ExecStart=/usr/local/bin/archipelago-kiosk-launcher
TimeoutStartSec=90
TimeoutStartSec=360
Restart=always
RestartSec=5

View File

@@ -7,14 +7,16 @@ Conflicts=getty@tty1.service
[Service]
Type=simple
# Wait up to 120s for archipelago to serve /health. On first boot it
# can take longer than 30s — the backend initialises state, unbundled
# ISO pulls FileBrowser, and the frontend dist has to settle. The
# previous 30s cap was firing Chromium at a not-yet-ready backend and
# the resulting blank window only recovered on reboot.
ExecStartPre=/bin/bash -c 'for i in $(seq 1 60); do curl -sf http://localhost/health >/dev/null 2>&1 && break; sleep 2; done'
# Wait up to 5 min for archipelago to serve /health. On slow hardware
# first-boot is dominated by the FileBrowser pull (unbundled ISO),
# initial archipelago state sync, and frontend settle — .198 took
# longer than 120s and chromium launched against an empty backend,
# producing a white window that only recovered on reboot. 300s gives
# slow-but-functional hardware enough headroom; TimeoutStartSec is
# bumped in lockstep so systemd doesn't kill us mid-wait.
ExecStartPre=/bin/bash -c 'for i in $(seq 1 150); do curl -sf http://localhost/health >/dev/null 2>&1 && break; sleep 2; done'
ExecStart=/usr/local/bin/archipelago-kiosk-launcher
TimeoutStartSec=180
TimeoutStartSec=360
Restart=always
RestartSec=5