Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

holger-estate-front/src/main.rs

//! `holger-estate-front` — **the PID 1 of holger's front medium** (vm1). No
//! arguments: the medium's cmdline says everything (`korp.env.HOLGER_APPLIANCE_DATA=/data`,
//! `korp.env.HOLGER_NET=eth0:dhcp,eth1:dhcp`), the seed is on the data disk, the
//! installer feeds the rest.
//!
//! In order (gunnar's front's order, on the shared crates): the boot log kept
//! for the installer from the first line; as PID 1 the reaper and both NICs
//! (DHCP, renewed); the medium's seed; a fresh installation clears the previous
//! estate's pairing; the estate's clock (signed time for the boxes); the
//! installer's listener on its WireGuard link (for life:
//! after the close it is the log door); the front's role (relay + the twin's
//! certification); the paid half (monetize, the plugin door); the web face on
//! :80/:443 with the console and the repository route. As PID 1 it never
//! returns: a refusal is said on every console and the machine stays up, so
//! the installer can still collect the boot log that says why.

use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

use holger_estate_front as front;

// DEV-NARRATE (feature `dev-narrate`) and the STAGE lines: the holger-dev-narrate crate,
// shared with holger-server (A28 L12).
use holger_dev_narrate as dev_narrate;
use nordisk_pairing::bootlog::BootLog;
use nordisk_pairing::seed::{Kind, Seed};

/// The front's boot log, kept for the installer from the first line — with a pinned head:
/// the install, the pairing and the first boot are kept for life (nordisk-pairing seals the
/// head at the installer's close), the runtime after it is the evictable tail (Gunnar
/// f21d8d09; test3's front lost its boot lines to a headless `BootLog::new` on 2026-10-20).
static BOOT_LOG: BootLog = BootLog::for_install();

/// Say `msg` on stderr (the serial line) AND the VGA text screen — the only
/// console a cloud VM shows.
fn shout(msg: &str) {
    use std::io::Write;
    eprint!("{msg}");
    if let Ok(mut tty0) = std::fs::OpenOptions::new().write(true).open("/dev/tty0") {
        let _ = tty0.write_all(msg.replace('\n', "\r\n").as_bytes());
    }
}

/// One line where a person sees it, never fd 2 (captured into the boot log): the serial
/// console and the VGA text screen. DEV-NARRATE's own lines.
fn console_line(line: &str) {
    use std::io::Write;
    for dev in ["/dev/console", "/dev/tty0"] {
        if let Ok(mut f) = std::fs::OpenOptions::new().write(true).open(dev) {
            let _ = f.write_all(format!("{line}\r\n").as_bytes());
        }
    }
}

/// Stay up, for ever: PID 1 must not exit (the kernel would panic and the boot
/// log with it).
fn idle(why: &str) -> ! {
    shout(&format!("holger-estate-front: {why}\n"));
    loop {
        std::thread::sleep(Duration::from_secs(3600));
    }
}

/// The kernel's story and a network line into the boot log (edda `narration::ship_boot_narration`).
fn narrate_boot(note: &str) {
    appliance_pid1::narration::ship_boot_narration(note, &|l| BOOT_LOG.push(l));
}

/// The front's late lease, narrated into its boot log.
fn front_late_lease() -> appliance_pid1::net::LateLease {
    front::pid1_fault::late_lease(narrate_boot)
}

fn main() {
    BOOT_LOG.capture_esp(
        Path::new(nordisk_pairing::bootlog::ESP_BOOTLOG),
        "holger-estate-front",
    );
    // vm1's log on its own disk too (bounded), and what the last boot said before it
    // stopped — a crash included — carried into this boot's log door (Gunnar 6dcbbe38).
    let carried = front::keep_on_disk(&BOOT_LOG, &front::data_root());
    // A panic is edda's fault with Holger's code (`HL-99`, lane G6, Gunnar's GN-99): said on
    // the console, the crash record with the boot log's tail, then on the appliance a
    // serving front restarts (unless it faulted within 30 min) and a booting one powers off.
    front::pid1_fault::install_panic_fault(
        front::data_root(),
        || BOOT_LOG.since(0, 4000).lines.join("\n").into_bytes(),
        shout,
    );
    // fd 2 into the boot log (owner 2026-09-28: monetize's log through the log door):
    // monetize-server and its guests log with eprintln!, which reached the serial line
    // only. The tracing Tap writes the console capture_stderr hands back, never fd 2.
    let console: Box<dyn std::io::Write + Send> = match nordisk_pairing::bootlog::capture_stderr(
        &BOOT_LOG,
    ) {
        Ok(console) => Box::new(console),
        Err(e) => {
            BOOT_LOG.push(&format!("holger-estate-front: fd 2 stays the console only ({e}); monetize's eprintln! lines will not reach the log door"));
            Box::new(std::io::stderr())
        }
    };
    let tap = std::sync::Mutex::new(nordisk_pairing::bootlog::Tap::new(&BOOT_LOG, console));
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()),
        )
        .with_ansi(false)
        .with_writer(tap)
        .init();
    tracing::info!(
        build = front::GIT_HASH,
        "holger-estate-front: holger's FRONT medium (vm1) starts"
    );
    if carried > 0 {
        tracing::info!(carried, file = %front::data_root().join(front::KEEP_REL).display(), "holger-estate-front: the previous boot's last lines are carried into this log (kept on the front's disk)");
    }
    tracing::info!(
        "{}",
        dev_narrate::stage_line(
            "exec",
            &format!(
                "holger-estate-front {} dev-narrate={}",
                front::GIT_HASH,
                if dev_narrate::ENABLED { "on" } else { "off" }
            )
        )
    );
    // DEV builds only: the same boot log, sealed to the installer's key, over UDP from the
    // moment the NIC has an address — before the installer's link (Gunnar's dev media).
    #[cfg(feature = "dev-narrate")]
    dev_narrate::start(
        &BOOT_LOG,
        front::data_root(),
        "holger-estate-front",
        console_line,
    );
    #[cfg(not(feature = "dev-narrate"))]
    let _ = console_line;
    let runtime = match tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
    {
        Ok(r) => r,
        Err(e) => idle(&format!("no async runtime: {e}")),
    };
    runtime.block_on(run())
}

/// The front's downstream interface, its sync door and the look at main's list (lane HOL-WGDS):
/// this front MAY serve WireGuard downstreams (its estate's tunnel is WireGuard). Nothing is
/// opened here (RA5-28): the interface and its public udp/51830 open only once main lists a
/// WireGuard downstream ([`serve_downstreams`]).
async fn start_downstreams(root: &Path) {
    let _ = DOWNSTREAMS_ROOT.set(root.to_path_buf());
    tracing::info!(udp = holger_downstream_rules::via::HUB_UDP_PORT, "front: WireGuard downstreams are served from here once main lists one; until then nothing listens");
}

/// The data root of a front that may serve WireGuard downstreams ([`start_downstreams`]).
static DOWNSTREAMS_ROOT: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();

/// **Open the downstreams' interface and its sync door** (main lists a WireGuard downstream).
fn open_downstreams(
    root: &Path,
    vm: &Arc<front::FrontVm>,
) -> Option<Arc<front::downstreams::DownstreamHub>> {
    use holger_downstream_rules::via;
    let hub = match front::downstreams::identity(root).and_then(|id| {
        let tun = front::downstreams::open_tun()?;
        front::downstreams::DownstreamHub::start(via::HUB_UDP_PORT, id.private, tun)
            .map_err(|e| format!("udp/{}: {e}", via::HUB_UDP_PORT))
    }) {
        Ok(h) => {
            front::front_report::DOWNSTREAMS_OPEN.store(true, std::sync::atomic::Ordering::SeqCst);
            Arc::new(h)
        }
        Err(e) => {
            tracing::error!(error = %e, "front: the downstreams' interface could not be opened — no WireGuard downstream can sync (the `via: Front` ones are untouched)");
            return None;
        }
    };
    tracing::info!(interface = via::HUB_IFACE, address = %via::HUB_ADDR, udp = via::HUB_UDP_PORT, key = %hub.relay.public_b64, "front: main lists a WireGuard downstream: the downstreams' interface is up (isolated: each downstream reaches the sync door only)");
    let v1 = Arc::clone(vm);
    let targets: front::downstreams::Targets = Arc::new(move || {
        let ip = v1.latest().and_then(|r| front::primary_addr(&r))?;
        Some((
            std::net::SocketAddrV4::new(ip, front::HTTP_PORT),
            std::net::SocketAddrV4::new(ip, front::CONTROL_PORT),
        ))
    });
    let names = Arc::clone(&hub);
    let router = front::downstreams::door(targets, Arc::new(move |ip| names.name_at(ip)));
    // A32-14, stays a task: the sync door on the downstreams' interface, for the front's life.
    tokio::spawn(async move {
        match front::downstreams::serve_door((via::HUB_ADDR, via::DOOR_PORT).into(), router).await {
            Ok(at) => {
                tracing::info!(address = %at, "front: the sync door is open on the downstreams' interface")
            }
            Err(e) => tracing::error!(error = %e, "front: the sync door could not open"),
        }
    });
    Some(hub)
}

/// The look at main's list, once there is a front `vm`; the interface opens at its first
/// WireGuard downstream ([`open_downstreams`]).
async fn serve_downstreams(vm: &Arc<front::FrontVm>) {
    let Some(root) = DOWNSTREAMS_ROOT.get().cloned() else {
        return;
    };
    let (v1, v2) = (Arc::clone(vm), Arc::clone(vm));
    let start: front::downstreams::HubStart = Arc::new(move || open_downstreams(&root, &v1));
    let main: front::downstreams::MainNow = Arc::new(move || {
        let reply = v2.latest()?;
        let ip = front::primary_addr(&reply)?;
        let bearer = front::console_key::bearers(&reply)
            .ok()
            .flatten()
            .and_then(|b| {
                front::primary_control(&reply).and_then(|c| b.at(&c).map(str::to_string))
            });
        Some((std::net::SocketAddrV4::new(ip, front::HTTP_PORT), bearer))
    });
    front::downstreams::follow_lazily(start, main, front::downstreams::POLL);
}

async fn run() -> ! {
    let root = front::data_root();
    // ── PID 1's duties: the reaper, the network (nothing else brings one up) ──
    if appliance_pid1::net::is_init() {
        // The previous stop's record (kept on the ESP), said once and removed.
        appliance_pid1::stop_record::say_previous("holger", &|l| tracing::info!("{l}"));
        // Whether this /data survived the last install (korp-installer's record on the ESP), once.
        appliance_pid1::install::announce_install_mode(None, &|l| tracing::info!("{l}"));
        // A32-14, stays a task: PID 1's zombie reaper, for the box's life.
        tokio::spawn(appliance_pid1::runtime::reaper());
        let spec = std::env::var("HOLGER_NET")
            .ok()
            .filter(|s| !s.trim().is_empty())
            .unwrap_or_else(|| "eth0:dhcp,eth1:dhcp".to_string());
        match appliance_pid1::net::NetConfig::parse_list(&spec) {
            Ok(plans) => {
                // A7 row 29: the resolver proven on a name, asked again for about 30 s on a miss.
                let how = appliance_pid1::net::Bringup {
                    hostname_prefix: "holger-front",
                    resolver_probe: Some("crates.io"),
                    narrate: &narrate_boot,
                };
                // A late lease (the utility NIC's slow first DHCP) is narrated as the boot's own.
                match appliance_pid1::net::bring_up_network_with(plans, &how, front_late_lease()) {
                    Ok(sessions) => {
                        let pending: Vec<&str> = ["eth0", front::UTILITY_INTERFACE]
                            .into_iter()
                            .filter(|i| appliance_pid1::net::dhcp_pending(i))
                            .collect();
                        if pending.is_empty() {
                            tracing::info!(
                                leases = sessions.len(),
                                "front: the declared interfaces are up"
                            );
                        } else {
                            tracing::warn!(leases = sessions.len(), pending = ?pending, "front: NOT every declared interface is up — PID 1 asks DHCP again in the background for the pending ones");
                        }
                        tracing::info!(
                            "{}",
                            dev_narrate::stage_line(
                                "network",
                                &format!("{} DHCP lease(s) held", sessions.len())
                            )
                        );
                        appliance_pid1::fault::reached("network");
                        for s in sessions {
                            // A32-14, stays a task: PID 1's lease renewal, one per lease, for the box's life.
                            tokio::spawn(appliance_pid1::runtime::dhcp_renewal(s, None));
                        }
                    }
                    Err(e) => {
                        tracing::error!(error = %format!("{e:#}"), "front: the network could not be brought up — the installer cannot reach this VM")
                    }
                }
            }
            Err(e) => {
                tracing::error!(error = %format!("{e:#}"), "REFUSED [holger-net] HOLGER_NET = {spec:?}")
            }
        }
    }
    // A utility NIC whose boot DHCP failed (UpCloud's DHCP is slow on a first boot: test3-holger-rs
    // r6's front never had eth1, lane D12) is waited for while PID 1 asks again in the
    // background — Gunnar's front: the boxes dial the relay on this address.
    if appliance_pid1::net::interface_ipv4(front::UTILITY_INTERFACE)
        .ok()
        .flatten()
        .is_none()
        && appliance_pid1::net::dhcp_pending(front::UTILITY_INTERFACE)
    {
        // Said by name with its start, once a minute (holger-live `waits`, test3-holger-rs 2026-10-22).
        let mut wait = holger_live::waits::Wait::new(
            "the front",
            &format!(
                "its utility address on {} (PID 1 asks DHCP again in the background)",
                front::UTILITY_INTERFACE
            ),
        );
        while appliance_pid1::net::interface_ipv4(front::UTILITY_INTERFACE)
            .ok()
            .flatten()
            .is_none()
            && appliance_pid1::net::dhcp_pending(front::UTILITY_INTERFACE)
        {
            if let Some(l) = wait.due() {
                tracing::warn!("front: {l}");
            }
            tokio::time::sleep(Duration::from_secs(1)).await;
        }
        tracing::info!("front: {}", wait.done());
    }
    let utility = appliance_pid1::net::interface_ipv4(front::UTILITY_INTERFACE)
        .ok()
        .flatten();
    tracing::info!(interface = front::UTILITY_INTERFACE, utility = ?utility, "front: the utility address");

    // ── the medium's seed: without it there is no installer to answer ────────
    // A release medium stages only the metadata marker: its seed is the shop's, in user_data.
    // The ONE seed reader (Skidbladnir nordisk-pairing `seed::metadata`, P4a; A28 L12).
    let seed: Seed = match nordisk_pairing::seed::metadata::read(&root.join(front::SEED_REL)) {
        Err(e) => idle(&e),
        Ok(None) => idle(&format!("no pairing seed at {} — this medium was composed without `--param pairing_seed=@<seed.toml>`, so no installer can reach it", root.join(front::SEED_REL).display())),
        Ok(Some(s)) if s.kind == Kind::Front => s,
        Ok(Some(s)) => idle(&format!("REFUSED [pairing-seed-kind] the staged seed is for a `{:?}` box; this is holger's FRONT medium", s.kind)),
    };
    // A keep boot of the SAME estate keeps the feed, the key and the certificate (lane G5).
    match front::first_boot(
        Path::new("/boot"),
        &root,
        &seed,
        &nordisk_pairing::boxside::mark_esp,
    ) {
        Some(c) if c.is_empty() => {
            tracing::info!(estate = %seed.estate, "front: a keep boot of the same estate onto its kept data — the installer's feed, this box's key and certificate are KEPT")
        }
        Some(cleared) => {
            tracing::warn!(cleared = ?cleared, "front: the first boot of this installation — it starts UNPAIRED")
        }
        None => {}
    }
    // ── an estate front is WireGuard-only (lane E1): its own overlay interface ──
    // A WireGuard seed is an estate: the front opens its overlay interface (the
    // relay carries it) and reaches main and the twin at their overlay addresses only.
    if seed.tunnel == nordisk_pairing::seed::TunnelKind::Wireguard {
        front::wg_only::set_wg_only(true);
        match front::wg_only::open_hub() {
            Ok(at) => {
                tracing::info!(interface = front::wg_only::HUB_IFACE, address = %at, "front: the overlay interface is up — the boxes are reached over the tunnel only");
                // The lean estate (owner 2026-10-23): main and the twin hold no public IPv4 and
                // leave through this front — its egress door, on its overlay address only.
                let door = std::net::SocketAddr::from((
                    nordisk_pairing::OVERLAY_FRONT,
                    nordisk_pairing::egress::EGRESS_PORT,
                ));
                match nordisk_pairing::egress::serve(door, nordisk_pairing::egress::Policy::OUT) {
                    Ok(at) => {
                        tracing::info!(address = %at, "front: the egress door is open — the boxes behind this front leave through it (TLS ports, outside addresses only)")
                    }
                    Err(e) => {
                        tracing::error!(error = %e, "front: the egress door could not open — a box with no public IPv4 reaches nothing outside the estate")
                    }
                }
            }
            Err(e) => {
                tracing::error!(error = %e, "front: the overlay interface could not be opened — the front cannot reach a box over the tunnel, and dials no other way")
            }
        }
    }
    // ── the downstreams' own interface (lane HOL-WGDS): `holger-ds`, its own subnet and UDP
    // port, never the estate's inner tunnel; a downstream peer reaches main's sync door only ──
    if seed.tunnel == nordisk_pairing::seed::TunnelKind::Wireguard {
        start_downstreams(&root).await;
    }
    // The console's key (A7 row 14): minted once and kept; every box derives its console login
    // from its public half, offered below.
    match front::console_key::adopt(&root) {
        Ok(_) => tracing::info!(
            "front: the console's key is ready; its public half is offered to every box"
        ),
        Err(e) => {
            tracing::error!(error = %e, "front: the console's key cannot be minted — the console reaches no box")
        }
    }
    #[allow(unused_mut)]
    let mut product = front::front::HolgerFront::new(&root, seed.tunnel);
    // The front's own live slot (L20): on a korp appliance, its A/B app slots.
    #[cfg(target_os = "linux")]
    if holger_live::appliance::present() {
        product = product.with_live(Arc::new(holger_live::appliance::slot()));
    }
    let vm: Arc<front::FrontVm> =
        match nordisk_pairing::frontside::Front::open(&root, seed.clone(), product) {
            Ok(v) => Arc::new(v),
            Err(e) => idle(&format!("this box cannot prepare its own key: {e}")),
        };

    // ── the clock: its key and peer are offered, so start it first ─────────
    // The estate's root of time (edda `estate-clock`, gunnar's pattern): this
    // front keeps its own clock from the medium's `clock` sources
    // (`korp.env.ESTATE_CLOCK`) and serves signed time on `<overlay>:50123` on an
    // estate (`<utility>:50123` off one);
    // every holger box takes `clock_peer` + `clock_key` from the offer.
    let say_clock: Arc<dyn Fn(&str) + Send + Sync> =
        Arc::new(|l: &str| tracing::info!("clock: {l}"));
    // On an estate the clock is served on the front's overlay address (lane E1): the
    // boxes take their time over wg0 too, never over the utility network.
    let clock_at = if front::wg_only::wg_only() {
        front::wg_only::hub().map(|_| nordisk_pairing::OVERLAY_FRONT)
    } else {
        utility
    };
    match estate_clock::offer::start(&root, clock_at, &seed.installer_ca_pem, say_clock) {
        Ok(offer) => {
            tracing::info!(key = %offer.key_line, peer = ?offer.peer, "front: the clock is started and offered");
            vm.product.clock_started(offer);
        }
        Err(e) => {
            tracing::error!(error = %e, "front: the clock could not start — the boxes will be fed no clock")
        }
    }

    // ── the installer's listener (for life), and the front's role ────────────
    // Each listener line at its level (nordisk-pairing's Tone, Gunnar 3eb19cd3): a refusal
    // is a WARN, the listener doing its job (the close, the log door, a reader going away)
    // an INFO — the untoned serve said every line at WARN.
    let say: nordisk_pairing::boxside::Say =
        Arc::new(|t: nordisk_pairing::bootstrap::Tone, l: &str| match t {
            nordisk_pairing::bootstrap::Tone::Info => tracing::info!("installer: {l}"),
            nordisk_pairing::bootstrap::Tone::Warn => tracing::warn!("installer: {l}"),
        });
    // A front the installer closed on an earlier start serves the log door only
    // (the install is not served again after a restart).
    let serve = if vm.closed() {
        nordisk_pairing::boxside::serve_installer_after_close_toned
    } else {
        nordisk_pairing::boxside::serve_installer_toned
    };
    // The installer sees the front with its renewal steps (lane E2): the certificate it
    // renews at two thirds of its life is kept, and so is the fresh bootstrap handed with it.
    // Its pair status names the twin's certify attempts while they fail (lane D10).
    let certify_root = root.clone();
    let served: Arc<dyn nordisk_pairing::bootstrap::Vm> = Arc::new(
        front::renewing::Renewing::new(Arc::clone(&vm), &root, front::SEED_REL, &seed).with_status(
            Arc::new(move |s| {
                front::certify::with_certify(
                    s,
                    front::certify::last_failure(&certify_root).as_ref(),
                )
            }),
        ),
    );
    if let Err(e) = serve(served, seed.clone(), &BOOT_LOG, say) {
        tracing::error!(error = %e, "installer: the listener's thread could not start");
    }
    tracing::info!(
        "{}",
        dev_narrate::stage_line(
            "pairing",
            "the seed is read and the installer's listener serves"
        )
    );
    appliance_pid1::fault::reached("pairing");
    front::role::start(
        Arc::clone(&vm),
        tokio::runtime::Handle::current(),
        front::role::RoleConfig::default(),
    );
    // Lane HOL-WGDS: the sync door and main's list of WireGuard downstreams.
    serve_downstreams(&vm).await;
    // The front tells main its doors and routes (lane HOL-FLOW-FRONT): main's flow unfolds it.
    {
        let r = Arc::clone(&vm);
        let now: front::front_report::Now = Arc::new(move || {
            let reply = r.latest()?;
            let at = front::primary_door(&reply)?.parse().ok()?;
            let key = front::front_report::main_key(&reply)?;
            Some((reply.feed.clone(), reply.me.name.clone(), at, key))
        });
        front::front_report::start(
            seed.estate.clone(),
            now,
            Arc::new(|| {
                front::front_report::DOWNSTREAMS_OPEN.load(std::sync::atomic::Ordering::SeqCst)
            }),
            cfg!(feature = "console"),
        );
    }
    // Every paired link renewed before it ends (lane E2): jera jobs on the front's own ledger.
    front::renew::start(Arc::clone(&vm), root.clone());
    // WireGuard-only: the front tries a plain-IP connection to each box, and the tunnel, and
    // says what it saw (jera `front.mesh_probe` looks, lane W9; Gunnar's mesh probe).
    if front::wg_only::wg_only() {
        front::mesh_probe::start(Arc::clone(&vm), &root);
    }
    // A re-laid main that awaits its twin is filled from it by this front when the estate fed
    // `repair_auto = true` (jera `front.autofill` looks, lane G11; Gunnar's autofill).
    front::autofill::start(Arc::clone(&vm), &root);
    // A live update on trial: confirmed once the web face served for a minute, else rolled back
    // (jera `live.trial` jobs on the front's own live ledger, L21).
    if let Some(slot) = vm.product.live_slot().cloned() {
        front::live::watch_trial(
            slot,
            || {
                std::net::TcpStream::connect_timeout(
                    &(std::net::Ipv4Addr::LOCALHOST, estate_front::web::HTTPS_PORT).into(),
                    Duration::from_secs(2),
                )
                .is_ok()
            },
            &root,
        );
    }

    // ── the paid half: the ONE monetize's SoldEstate door (no monetize runs here) ──
    #[cfg(feature = "metered")]
    front::sold::start(Arc::clone(&vm), root.clone());

    // ── the web face on the public 80/443: the front holds its certificate ──
    let fed = Arc::clone(&vm);
    let feed: estate_front::web::Feed =
        Arc::new(move || fed.latest().map(|r| r.feed).unwrap_or_default());
    let any = std::net::Ipv4Addr::UNSPECIFIED;
    // The shop's bearer swapped for the primary's console bearer on EVERY route of :443 (the
    // console's pass-through and the repository route), each swap audited (lane OX-WGDS3).
    #[cfg(feature = "console")]
    let swap = front::console::Swap::default();
    #[cfg(feature = "console")]
    let hooks = estate_front::web::Hooks {
        outer: Some(front::console::swap_layer(swap.clone())),
        tls: Some(front::tls()),
        ..estate_front::web::Hooks::default()
    };
    #[cfg(not(feature = "console"))]
    let hooks = estate_front::web::Hooks {
        tls: Some(front::tls()),
        ..estate_front::web::Hooks::default()
    };
    match estate_front::web::start_with(
        &root,
        seed.estate.clone(),
        feed,
        (any, estate_front::web::HTTP_PORT).into(),
        (any, estate_front::web::HTTPS_PORT).into(),
        hooks,
    )
    .await
    {
        Ok(web) => {
            tracing::info!("front: the web face is served on :80 and :443");
            tracing::info!(
                "{}",
                dev_narrate::stage_line("serving", "the web face on :80 and :443")
            );
            // From here a panic restarts the front (unless it loops): edda appliance_pid1::fault.
            appliance_pid1::fault::serving_now();
            // The repository: `repo_hosts` carried to the primary's HTTP and gRPC.
            let r = Arc::clone(&vm);
            // On an estate the repository route reaches the primary over wg0 (lane E1).
            let now: estate_front::route::Now = Arc::new(move || {
                r.latest()
                    .map(|reply| (reply.feed.clone(), front::primary_addr(&reply)))
            });
            estate_front::route::start(
                web.app.clone(),
                now,
                seed.estate.clone(),
                front::PRODUCT,
                Some((front::HTTP_PORT, front::CONTROL_PORT)),
                front::CARRIED_GRPC,
            );
            #[cfg(feature = "console")]
            {
                let l = Arc::clone(&vm);
                front::console::start_swapping(
                    web,
                    Arc::new(move || l.latest()),
                    root.clone(),
                    swap,
                );
            }
            #[cfg(not(feature = "console"))]
            let _ = web;
        }
        Err(e) => tracing::error!(error = %e, "front: the web face cannot be served"),
    }
    // As PID 1: a signal OR the ACPI power button (a cloud's soft stop, which
    // is never a signal) stops the front — synced and powered off, never an
    // exit (PID 1 exiting is `Attempted to kill init!`, and `panic=10` brings
    // back the box it was asked to stop).
    if appliance_pid1::net::is_init() {
        let why = appliance_pid1::runtime::shutdown_request().await;
        // The front's own duties (A28 M18) queue nothing more from here.
        tracing::info!("front: {} duty(ies) stopped", holger_live::duty::stop_all());
        tracing::warn!(request = why, "front: shutting down; the data disk quiesced, the stop kept on the ESP, then the power (or a re-exec into a staged live update)");
        // A7 row 18 (Gunnar f8594dd6 / 812f4ffc / dcf1d82f): quiesce /data bounded at 10 s,
        // keep the stop on the ESP, then the power with no sync after the remount.
        let said = format!("holger-estate-front: stopped cleanly ({why})");
        // The power only on the appliance (lane G6): PID 1 in a container cannot `reboot(2)`,
        // and a refused power-off parks the container instead of ending it.
        if appliance_pid1::stop::after_clean_stop(appliance_pid1::stop::on_appliance())
            == appliance_pid1::stop::AfterStop::PowerOff
        {
            let data = front::data_root();
            #[cfg(target_os = "linux")]
            holger_live::appliance::end_of_stop(|| -> () {
                appliance_pid1::power::power_off_cleanly("holger", &data, &said, &|l| {
                    tracing::info!("{l}")
                })
            });
            #[cfg(not(target_os = "linux"))]
            appliance_pid1::power::power_off_cleanly("holger", &data, &said, &|l| {
                tracing::info!("{l}")
            });
        }
        tracing::warn!("{said}; not the appliance — the process exits");
        std::process::exit(0);
    }
    loop {
        tokio::time::sleep(Duration::from_secs(3600)).await;
    }
}