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

nordisk/gunnar

.nornir/build-variants.toml

# gunnar — .nornir/build-variants.toml — EMITTED, NOT SOURCE.
#
# Generated by `cargo run -p gunnar-build-thing -- emit` from
# crates/gunnar-build-thing (the Rust `impl BuildThing`: recipe.rs for the rows
# and the media, variants.rs for the cargo build points, atom.rs for the
# delivery matrix). The code is the one source; this file DESCRIBES the build
# for readers and for the tools that discover a repo by this path (the dwarves
# forge, the ⚒ pane, xtask, installtest, the bench harness). Editing it changes
# NOTHING: re-emit after changing the code. Do not edit by hand.
#
# Model: variant → features → settings/sub-settings → typ. gunnar is a git
# client and a git server in ONE binary whose two halves have different
# constraints — the server is worktree-free, spawns nothing and is
# meant to be PID 1 — which is why the server atom and the client atom are two
# build points and not one with a flag.
#
# The ~2 200 lines of reasoning that used to sit between these rows are kept
# VERBATIM in the project's reasoning archive, in file order. New reasoning
# goes beside the code that acts on it — a doc comment in
# crates/gunnar-build-thing/src — and never here: a comment written in this file
# is deleted by the next `emit` without anybody noticing.
#
# The `[nornir-build-thing] schema` header above the rows, when present, is
# COMPUTED from what this set declares (nornir_build_thing::schema::required_by,
# R1) — never chosen, never bumped by an emission that says nothing new.

[nornir-build-thing]
schema = 6

[build]
offers = [
    "acme",
    "admit-share",
    "client",
    "clock-fetch",
    "console",
    "control",
    "dev-narrate",
    "dhcp-client",
    "egress-door",
    "engine-znippy",
    "fleet-pull",
    "forges",
    "grow-runtime",
    "http",
    "live-app",
    "mail",
    "metered",
    "mimalloc",
    "mirror-http",
    "public-site",
    "replication",
    "resolver-probe",
    "server",
    "sso",
    "sso-egress",
    "systemd",
    "wg-client",
]

[build.allow_unbuilt]
cipher = "gunnar-crypt's envelope cipher (AES-SIV, HKDF, HMAC): enabled through `keygen`/`wrap` by gunnar-cli's `client` and gunnar-policy, and alone by the browser console in gunnar-ui; a cell cannot name another crate's feature."
conformance = "a test-only harness feature (each crate dev-depends on itself for it); shipping it would be the defect."
container-robot = "gunnar-server's container robot TARGET (`tests/appliance_container_robot.rs`, A28 L14): a test tool, never a delivery shape. It runs as `--features container-robot -- --ignored`; the feature turns on edda's `robot` (`wg-appliance-core/robot`, published in 0.1.7), and no row builds it."
diff = "gunnar-crypt's plaintext diff of an encrypted file: gunnar-crypt's own default and gunnar-cli's `client`; a cell cannot name another crate's feature."
engine-gix = "the second arm of the storage A/B, taken only by a dev/bench build that compiles both engines and selects at runtime; never a delivery shape, and never in the appliance."
federation = "a named extra of the products (gunnar, gunnar-cli): the other forges, opt-in at `cargo install <product> --features federation`; compiled by `cargo xtask ci`'s `extras` step (`full`)."
fleet = "gunnar-repair's estate side of monetize's pull channel: monetize-estate-side's SHARED pump with gunnar's executor as its PumpBox (A28 M12), and it SHIPS — named as `gunnar-repair/fleet` by `gunnar-server`'s `fleet-pull`, which every appliance row carries (the community build has `replication` without it and links no monetize crate). Transitive, like `porcelain`."
fs = "gunnar-lfs's on-disk object store, enabled transitively by the server half; a cell cannot name another crate's feature."
full = "a named extra of the products: every open extra at once, opt-in at `cargo install <product> --features full`; compiled by `cargo xtask ci`'s `extras` step, delivered by no estate row."
grpc = "gunnar-repair's tonic helpers (poison codec, the Bootstrap.Restore call), and it SHIPS — reached through `gunnar-repair/proto` from `gunnar-server`'s `replication` and `gunnar-cli`'s `control`. Transitive, like `porcelain`."
jobs = "gunnar-common's named jera jobs (a box's duty and the job queue on jera ChainQueue lanes), and it SHIPS — named as `gunnar-common/jobs` by `gunnar-server`'s and `gunnar-front`'s manifests, so every server and front row carries it. Transitive, like `stderr-capture`."
keygen = "gunnar-crypt's epoch-key minting: enabled through `wrap` by gunnar-cli's `client` and gunnar-policy's dev build; a cell cannot name another crate's feature."
mcp = "gunnar-front's MCP connector (TASKS-13, owner 2026-10-25): OFF by default and in every release row until the owner turns it on, so no delivery row may build it (`tests/mcp_off_by_default.rs` holds the default tree free of gunnar-mcp); its own test target `mcp_bridge` requires it."
online = "a named extra of the products (gunnar, gunnar-cli): opt-in at `cargo install <product> --features online`, the outbound surfaces no delivered row ships; `cargo xtask ci`'s `extras` step compiles every product with `full`, which includes it."
porcelain = "gunnar-client's own default (gix + a real worktree), and it SHIPS — reached transitively as `gunnar-client/porcelain` from `gunnar-cli`'s `client` feature, which is itself offered and built. A cell names features of the atom's own package, so the transitive spelling is invisible to the `built` column."
pq = "gunnar-crypt's quantum-safe key and ring (CRYPT-PQ-DEFAULT: the default level): enabled through `wrap` by gunnar-cli's `client`, and alone beside `user-key` by the browser console in gunnar-ui (no SSH); a cell cannot name another crate's feature."
pq-keys = "gunnar-crypt's age1pq1 key decoding (bech32): enabled by gunnar-core's dependency line for its key store, and through `wrap`; a cell cannot name another crate's feature."
proto = "gunnar-repair's adapters over gunnar's own generated clients, and it SHIPS — named as `gunnar-repair/proto` by `gunnar-server` (`replication`) and `gunnar-cli` (`control`). Transitive, like `porcelain`."
pump = "gunnar-repair's async pump and verb orchestration, and it SHIPS — reached through `gunnar-repair/grpc` from `gunnar-server`'s `replication` and `gunnar-cli`'s `control`. Transitive, like `porcelain`."
stages = "gunnar-wire's eighteen instrumentation stages (`dep:gunnar-stage`), enabled only by `bench/`'s own default — a DETACHED workspace, so no delivery variant can name it. Measurement scaffolding, never a shipped shape."
stderr-capture = "gunnar-common's fd-2 pipe whose lines are said through tracing (monetize-server's eprintln! reaches the boot log), and it SHIPS — named as `gunnar-common/stderr-capture` by `gunnar-front`'s manifest, so every front row carries it. Transitive, like `porcelain`."
systemd = "gunnar-cli's systemd service shape (`cargo install gunnar-cli --features systemd`, the retired `gunnar-service` bin): compiled by `cargo xtask ci`'s `systemd-check` and `systemd-tests` steps and by installtest's subject build; no estate row ships it, the appliance has no init system."
test-hooks = "gunnar-core's test-only hooks, enabled by gunnar-server's DEV dependency on gunnar-core; shipping it would be the defect."
testmatrix = "gunnar-server's emit channel for nornir-testmatrix rows (`functional_status`); on only under `cargo test`, and with it off the call is an `#[inline]` no-op with the crate absent from the graph entirely. Shipping a test reporter in the appliance would be the defect."
user-key = "gunnar-crypt's person's encryption key (CRYPT-YUBI): enabled through `wrap` by gunnar-cli's `client`, and by the browser console in gunnar-ui with `pq` (no SSH); a cell cannot name another crate's feature."
webhooks = "outbound event delivery, and it does NOT ship in 5.4 (UW-22): `gunnar-cli`'s `server` feature names `gunnar-server/webhooks`, but no variant builds `gunnar-cli` with `server`, and no appliance row (`gunnar-server*`) names it, so no shipped box delivers events. `Webhooks.*` is called only by `gunnar-cli control`, and the console has no screen for it. Shipping it is a product decision: add it to the appliance rows and give the console a screen."
wrap = "gunnar-crypt's age/HPKE key wrap: gunnar-crypt's own default, and enabled by gunnar-cli's `client` (`gunnar-crypt` with wrap + diff); a cell cannot name another crate's feature."

[build.allow_unbuilt_bins]
gunnar-build-thing = "repository tooling, never shipped: the cell runner that materializes CELLS' declared argv (one implementation — owns no build knowledge); tool axis like xtask, not the delivery axis."
gunnar-installtest = "the detached install-test harness (own [workspace], deps on draupnir + tunnr-vm); run by `cargo xtask install-test`, never shipped."
gunnar-lfs-serve = "an ssh-exec interop fixture for tests/interop.rs; the shipped path is gunnar-cli's GitService::LfsTransfer arm, which every atom builds."
xtask = "repository tooling, never shipped; built by every `cargo xtask` run, not by a delivery."

[[build.variant]]
name = "gunnar"
package = "gunnar"
bin = "gunnar"
features = [
    "engine-znippy",
    "http",
    "replication",
]
no_default_features = true
profile = "release"
member = "gunnar"
role = "server"
suites = ["MVP"]
default = true

[[build.variant]]
name = "gunnar-ui"
package = "gunnar-ui"
bin = "gunnar-ui"
profile = "release"
manifest_dir = "../gunnar-ui/crates/gunnar-ui"
siblings = ["gunnar-ui"]
linkage = "thin"
role = "client"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-cli"
package = "gunnar-cli"
bin = "gunnar-cli"
features = [
    "client",
    "control",
]
no_default_features = true
profile = "release"
member = "gunnar"
linkage = "thin"
role = "client"
suites = ["MVP"]
default = true

[[build.variant]]
name = "gunnar-server"
package = "gunnar-server"
bin = "gunnar-server"
features = [
    "server",
    "engine-znippy",
    "mimalloc",
    "clock-fetch",
    "resolver-probe",
    "fleet-pull",
    "mirror-http",
    "sso-egress",
    "dhcp-client",
    "wg-client",
    "replication",
    "grow-runtime",
    "live-app",
]
open = ["dev-narrate"]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-server-narrate"
package = "gunnar-server"
bin = "gunnar-server"
features = [
    "server",
    "engine-znippy",
    "mimalloc",
    "clock-fetch",
    "resolver-probe",
    "fleet-pull",
    "mirror-http",
    "sso-egress",
    "dhcp-client",
    "wg-client",
    "replication",
    "grow-runtime",
    "live-app",
    "dev-narrate",
]
no_default_features = true
profile = "dev-media"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-server-thin"
package = "gunnar-server"
bin = "gunnar-server"
features = [
    "server",
    "engine-znippy",
    "mimalloc",
    "clock-fetch",
    "resolver-probe",
    "fleet-pull",
    "mirror-http",
    "sso-egress",
    "dhcp-client",
    "replication",
    "grow-runtime",
]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-server-metered"
package = "gunnar-server"
bin = "gunnar-server"
features = [
    "server",
    "engine-znippy",
    "mimalloc",
    "clock-fetch",
    "resolver-probe",
    "fleet-pull",
    "mirror-http",
    "sso-egress",
    "dhcp-client",
    "wg-client",
    "replication",
    "metered",
    "grow-runtime",
    "live-app",
    "admit-share",
]
open = ["dev-narrate"]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-server-metered-narrate"
package = "gunnar-server"
bin = "gunnar-server"
features = [
    "server",
    "engine-znippy",
    "mimalloc",
    "clock-fetch",
    "resolver-probe",
    "fleet-pull",
    "mirror-http",
    "sso-egress",
    "dhcp-client",
    "wg-client",
    "replication",
    "metered",
    "grow-runtime",
    "live-app",
    "admit-share",
    "dev-narrate",
]
no_default_features = true
profile = "dev-media"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-server-metered-thin"
package = "gunnar-server"
bin = "gunnar-server"
features = [
    "server",
    "engine-znippy",
    "mimalloc",
    "clock-fetch",
    "resolver-probe",
    "fleet-pull",
    "mirror-http",
    "sso-egress",
    "dhcp-client",
    "replication",
    "metered",
    "grow-runtime",
    "admit-share",
]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-cli-musl"
package = "gunnar-cli"
bin = "gunnar-cli"
features = [
    "client",
    "control",
]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
linkage = "thin"
role = "client"
suites = ["MVP"]

[build.variant.sign]
purpose = "plugin"

[[build.variant]]
name = "gunnar-cli-musl-metered"
package = "gunnar-cli"
bin = "gunnar-cli-metered"
features = [
    "client",
    "control",
    "metered",
]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
member = "gunnar"
linkage = "thin"
role = "client"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-front"
package = "gunnar-front"
bin = "gunnar-front"
features = [
    "dhcp-client",
    "console",
    "forges",
    "clock-fetch",
    "resolver-probe",
    "acme",
    "egress-door",
    "mail",
    "sso",
]
open = ["dev-narrate"]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
manifest_dir = "crates/gunnar-front"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-front-narrate"
package = "gunnar-front"
bin = "gunnar-front"
features = [
    "dhcp-client",
    "console",
    "forges",
    "clock-fetch",
    "resolver-probe",
    "acme",
    "egress-door",
    "mail",
    "sso",
    "dev-narrate",
]
no_default_features = true
profile = "dev-media"
target = "x86_64-unknown-linux-musl"
manifest_dir = "crates/gunnar-front"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-front-metered"
package = "gunnar-front"
bin = "gunnar-front"
features = [
    "dhcp-client",
    "metered",
    "public-site",
    "forges",
    "clock-fetch",
    "resolver-probe",
    "acme",
    "egress-door",
    "mail",
    "sso",
]
open = ["dev-narrate"]
no_default_features = true
profile = "release"
target = "x86_64-unknown-linux-musl"
manifest_dir = "crates/gunnar-front"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-front-metered-narrate"
package = "gunnar-front"
bin = "gunnar-front"
features = [
    "dhcp-client",
    "metered",
    "public-site",
    "forges",
    "clock-fetch",
    "resolver-probe",
    "acme",
    "egress-door",
    "mail",
    "sso",
    "dev-narrate",
]
no_default_features = true
profile = "dev-media"
target = "x86_64-unknown-linux-musl"
manifest_dir = "crates/gunnar-front"
member = "gunnar"
role = "server"
suites = ["MVP"]

[[build.variant]]
name = "gunnar-image"
role = "server"
suites = ["MVP"]
kind = "container"
container_ref = "gunnar-scratch"

[[build.variant]]
name = "gunnar-appliance-iso"
role = "server"
suites = ["MVP"]
kind = "iso"

[[build.variant]]
name = "gunnar-installer"
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-open"
open = ["dev-narrate"]
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-front-installer-open"
open = ["dev-narrate"]
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-front-installer-metered"
open = ["dev-narrate"]
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-gce"
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-aws"
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-azure"
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-metered"
open = ["dev-narrate"]
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-metered-aws"
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.variant]]
name = "gunnar-installer-metered-azure"
role = "server"
suites = ["MVP"]
kind = "installer"

[[build.container]]
name = "gunnar-scratch"
base = "scratch"
entrypoint = ["/gunnar"]
cmd = ["serve"]
workdir = "/data"
expose = [
    2222,
    50051,
]
tags = ["gunnar-scratch:1.1.3"]
archive = ".container-out/gunnar-image.tar"

[[build.container.layers]]
dest = "/gunnar"
from_variant = "gunnar-server"
executable = true

[build.container.env]
GUNNAR_APPLIANCE = "1"
GUNNAR_DATA_ROOT = "/data"

[build.container.labels]
"se.nordisk.role" = "gunnar-appliance"

[[build.iso]]
name = "gunnar-appliance-iso"
staging = "assets/appliance-root"
volume_id = "GUNNAR_APPLIANCE"
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "GUNNAR_DATA_ROOT=/data",
    "GUNNAR_NET=eth0:dhcp",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
    "--",
    "serve",
    "--grpc-bind",
    "0.0.0.0:50051",
    "--disk-floor",
    "5%,64MiB",
]
format = "uefi"
from_variant = "gunnar-server"
boot-logo = "assets/gunnar.webp"

[[build.iso.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.iso.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.iso.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.iso.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.iso.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.iso.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.iso.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.iso.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[build.iso.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[[build.installer]]
name = "gunnar-installer"
variant = "gunnar-server"
volume_id = "GUNNAR_INSTALL"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--no-grpc,--admin-key,@/data/admin.pub",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "admin_key"
label = "Bootstrap-admin (kundens OpenSSH-publika nyckel)"
kind = "text"
required = true
staged_file = "admin.pub"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-open"
variant = "gunnar-server"
volume_id = "GUNNAR_INSTALL"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[[build.installer.params]]
name = "pairing_seed"
label = "Pairing seed (nordisk-pairing seed.toml: estate, front, installer CA, bootstrap cert+key)"
kind = "text"
required = false
staged_file = "pairing/seed.toml"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.options]]
name = "dev-narrate"
label = "DEV-NARRATE (sealed early-boot log to the installer, udp/51822) — dev media only"
default = false
feature = "dev-narrate"
forbid_symbols = ["nordisk_narrate"]

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-gce"
variant = "gunnar-server"
volume_id = "GUNNAR_INSTALL_GCE"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel-scsi"

[build.installer.target]
firmware = "both"
consoles = [
    "vga",
    "serial",
]
uarts = ["com1"]
buses = [
    "virtio-blk",
    "virtio-scsi",
]
nic = "virtio"
nics = 1
cpu = "generic"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-aws"
variant = "gunnar-server"
volume_id = "GUNNAR_INSTALL_AWS"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel-nvme"

[build.installer.target]
preset = "aws-nitro"
firmware = "both"
consoles = [
    "vga",
    "serial",
]
uarts = ["com1"]
buses = [
    "ide-cdrom",
    "virtio-blk",
]
nic = "virtio"
nics = 1
cpu = "generic"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-azure"
variant = "gunnar-server"
volume_id = "GUNNAR_INSTALL_AZURE"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "gpt"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel-hyperv"

[build.installer.target]
preset = "azure-gen2"
firmware = "both"
consoles = [
    "vga",
    "serial",
]
uarts = ["com1"]
buses = [
    "ide-cdrom",
    "virtio-blk",
]
nic = "virtio"
nics = 1
cpu = "generic"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-metered"
variant = "gunnar-server-metered"
volume_id = "GUNNAR_INSTALL_METERED"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.env]
GUNNAR_ADMIT_LIMIT = "none"
GUNNAR_BILLING_URL = "https://www.gunnar.rs/billing/{tenant}"
GUNNAR_MONETIZE_PUBKEY = "1c3663248d60900e2996389fba4efa1d7179d6dbd08627d0426f16f94e44c4e6"
GUNNAR_MONETIZE_VITAL = "true"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[[build.installer.params]]
name = "pairing_seed"
label = "Pairing seed (nordisk-pairing seed.toml: estate, front, installer CA, bootstrap cert+key)"
kind = "text"
required = false
staged_file = "pairing/seed.toml"

[[build.installer.params]]
name = "monetize_pubkey"
label = "monetize-server's Ed25519 verifying key, 64 hex characters (the front's MONETIZE_SIGNING_PUBKEY)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_MONETIZE_PUBKEY"

[[build.installer.params]]
name = "monetize_vital"
label = "is monetize vital: true raises entitlement.key_mismatch and maintenance when stored facts do not verify at boot, false only logs (default true)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_MONETIZE_VITAL"

[[build.installer.params]]
name = "admit_limit"
label = "the usage share's transfer budget per window for a namespace: a size (100GB, 5GiB, bytes) or none = PassThrough (default none)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_ADMIT_LIMIT"

[[build.installer.params]]
name = "admit_window_days"
label = "the usage share's rolling window in UTC days (default 30)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_ADMIT_WINDOW_DAYS"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.options]]
name = "dev-narrate"
label = "DEV-NARRATE (sealed early-boot log to the installer, udp/51822) — dev media only"
default = false
feature = "dev-narrate"
forbid_symbols = ["nordisk_narrate"]

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-metered-aws"
variant = "gunnar-server-metered"
volume_id = "GUNNAR_INSTALL_METERED_AWS"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.env]
GUNNAR_ADMIT_LIMIT = "none"
GUNNAR_BILLING_URL = "https://www.gunnar.rs/billing/{tenant}"
GUNNAR_MONETIZE_PUBKEY = "1c3663248d60900e2996389fba4efa1d7179d6dbd08627d0426f16f94e44c4e6"
GUNNAR_MONETIZE_VITAL = "true"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel-nvme"

[build.installer.target]
preset = "aws-nitro"
firmware = "both"
consoles = [
    "vga",
    "serial",
]
uarts = ["com1"]
buses = [
    "ide-cdrom",
    "virtio-blk",
]
nic = "virtio"
nics = 1
cpu = "generic"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[[build.installer.params]]
name = "monetize_pubkey"
label = "monetize-server's Ed25519 verifying key, 64 hex characters (the front's MONETIZE_SIGNING_PUBKEY)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_MONETIZE_PUBKEY"

[[build.installer.params]]
name = "monetize_vital"
label = "is monetize vital: true raises entitlement.key_mismatch and maintenance when stored facts do not verify at boot, false only logs (default true)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_MONETIZE_VITAL"

[[build.installer.params]]
name = "admit_limit"
label = "the usage share's transfer budget per window for a namespace: a size (100GB, 5GiB, bytes) or none = PassThrough (default none)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_ADMIT_LIMIT"

[[build.installer.params]]
name = "admit_window_days"
label = "the usage share's rolling window in UTC days (default 30)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_ADMIT_WINDOW_DAYS"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-installer-metered-azure"
variant = "gunnar-server-metered"
volume_id = "GUNNAR_INSTALL_METERED_AZURE"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_APPLIANCE=1",
    "korp.args=serve,--grpc-bind,utility:50051",
    "korp.entry=gunnar",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "gpt"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.env]
GUNNAR_ADMIT_LIMIT = "none"
GUNNAR_BILLING_URL = "https://www.gunnar.rs/billing/{tenant}"
GUNNAR_MONETIZE_PUBKEY = "1c3663248d60900e2996389fba4efa1d7179d6dbd08627d0426f16f94e44c4e6"
GUNNAR_MONETIZE_VITAL = "true"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel-hyperv"

[build.installer.target]
preset = "azure-gen2"
firmware = "both"
consoles = [
    "vga",
    "serial",
]
uarts = ["com1"]
buses = [
    "ide-cdrom",
    "virtio-blk",
]
nic = "virtio"
nics = 1
cpu = "generic"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (the ceremony hub.conf, public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[[build.installer.params]]
name = "static_ip"
label = "Statisk adress med prefix (t.ex. 10.0.2.15/24)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_IP"

[[build.installer.params]]
name = "gateway"
label = "Standard-gateway (t.ex. 10.0.2.2)"
kind = "text"
required = true
boot_param = "korp.env.GUNNAR_GATEWAY"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "Clock sources (peer://ip:port for the front's signed time; https:// fallback, comma-separated)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "clock_key"
label = "Clock peer's ed25519 public key (base64 blob of the OpenSSH line)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK_KEY"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[[build.installer.params]]
name = "monetize_pubkey"
label = "monetize-server's Ed25519 verifying key, 64 hex characters (the front's MONETIZE_SIGNING_PUBKEY)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_MONETIZE_PUBKEY"

[[build.installer.params]]
name = "monetize_vital"
label = "is monetize vital: true raises entitlement.key_mismatch and maintenance when stored facts do not verify at boot, false only logs (default true)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_MONETIZE_VITAL"

[[build.installer.params]]
name = "admit_limit"
label = "the usage share's transfer budget per window for a namespace: a size (100GB, 5GiB, bytes) or none = PassThrough (default none)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_ADMIT_LIMIT"

[[build.installer.params]]
name = "admit_window_days"
label = "the usage share's rolling window in UTC days (default 30)"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_ADMIT_WINDOW_DAYS"

[build.installer.fixtures]
hub_conf = "wg-hub"

[[build.installer.options]]
name = "dhcp"
label = "DHCP (eth0 + eth1)"
default = true
feature = "dhcp-client"
params_when_off = [
    "static_ip",
    "gateway",
]
forbid_symbols = ["appliance_pid1::dhcp"]

[build.installer.options.param_kinds]
gateway = "ipv4"
static_ip = "ipv4-cidr"

[build.installer.options.bakes]
boot_param = "korp.env.GUNNAR_NET"
when_on = "eth0:dhcp,eth1:dhcp"
when_off = "eth0:{static_ip}:{gateway}"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.options]]
name = "wireguard"
label = "WireGuard-klient"
default = true
feature = "wg-client"
params_when_on = ["hub_conf"]
forbid_symbols = [
    "wg_appliance_core",
    "boringtun",
]

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "TUN is a netdevice — the driver lives under the NETDEVICES menu (drivers/net/Kconfig)"

[[build.installer.options.kernel_requires]]
symbol = "CONFIG_INET"
value = "y"
reason = "CONFIG_TUN `depends on INET` (drivers/net/Kconfig), and the tunnel's outer packets are UDP over IPv4"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer]]
name = "gunnar-front-installer-open"
variant = "gunnar-front"
volume_id = "GUNNAR_FRONT_INSTALL"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_UI_FORGES=on",
    "korp.env.GUNNAR_FRONT_ACME=on",
    "korp.env.GUNNAR_FRONT_EGRESS=on",
    "korp.entry=gunnar-front",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "The front's own clock sources (https://… or ntp://…, comma-separated): the estate's root of time"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[[build.installer.params]]
name = "pairing_seed"
label = "Pairing seed (nordisk-pairing seed.toml: estate, front, installer CA, bootstrap cert+key)"
kind = "text"
required = false
staged_file = "pairing/seed.toml"

[[build.installer.options]]
name = "dev-narrate"
label = "DEV-NARRATE (sealed early-boot log to the installer, udp/51822) — dev media only"
default = false
feature = "dev-narrate"
forbid_symbols = ["nordisk_narrate"]

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[[build.installer]]
name = "gunnar-front-installer-metered"
variant = "gunnar-front-metered"
volume_id = "GUNNAR_FRONT_INSTALL_METERED"
target_disk_gib = 64
gpu = false
boot_params = [
    "console=tty0",
    "console=ttyS0,115200",
    "panic=10",
    "korp.env.GUNNAR_DATA_ROOT=/data",
    "korp.env.GUNNAR_NET=eth0:dhcp,eth1:dhcp",
    "korp.env.GUNNAR_UI_FORGES=on",
    "korp.env.GUNNAR_FRONT_ACME=on",
    "korp.env.GUNNAR_FRONT_EGRESS=on",
    "korp.entry=gunnar-front",
    "korp.znippy=/app.znippy",
]
boot-logo = "assets/gunnar.webp"
medium_boot = "hybrid"

[build.installer.payload]
kind = "embedded_iso"

[build.installer.kernel_donor]
repo = "tunnr"
kernel = "tunnr-kernel"

[build.installer.data]
fs = "xfs"
io = "uring"

[[build.installer.params]]
name = "log_udp"
label = "Boot-narration UDP sink for a console-less VM (debug; a.b.c.d:port)"
kind = "endpoint"
required = false
boot_param = "korp.log_udp"

[[build.installer.params]]
name = "clock"
label = "The front's own clock sources (https://… or ntp://…, comma-separated): the estate's root of time"
kind = "text"
required = false
boot_param = "korp.env.GUNNAR_CLOCK"

[[build.installer.params]]
name = "reinstall"
label = "Reinstall policy for a disk that already carries data: keep (upgrade, /data survives) | wipe (lay it fresh)"
kind = "text"
required = false
boot_param = "korp.reinstall"

[[build.installer.params]]
name = "install_then"
label = "After INSTALL-OK: reboot (a person at the box) | poweroff (a cloud re-image tool detaches the medium)"
kind = "text"
required = false
boot_param = "korp.install.then"

[[build.installer.params]]
name = "live_app"
label = "Live updates of the app: on (default) | off (every new app is a base update)"
kind = "text"
required = false
boot_param = "korp.live_update"

[[build.installer.params]]
name = "pairing_seed"
label = "Pairing seed (nordisk-pairing seed.toml: estate, front, installer CA, bootstrap cert+key)"
kind = "text"
required = false
staged_file = "pairing/seed.toml"

[[build.installer.options]]
name = "dev-narrate"
label = "DEV-NARRATE (sealed early-boot log to the installer, udp/51822) — dev media only"
default = false
feature = "dev-narrate"
forbid_symbols = ["nordisk_narrate"]

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI"
value = "y"
reason = "the hot-plug core: a member detached from and re-attached to the RUNNING appliance is otherwise invisible, and UpCloud resizes only detached volumes"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_ACPI"
value = "y"
reason = "acpiphp: the firmware-driven eject path (QEMU i440fx, q35 since 6.1) that acknowledges a detach instead of answering HOTPLUG_FAILED"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCIEPORTBUS"
value = "y"
reason = "binds the PCIe root ports and requests _OSC control, without which native PCIe hot-plug never takes a slot"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_PCIE"
value = "y"
reason = "pciehp: the native PCIe hot-plug path, for q35 with ACPI hot-plug off and for metal"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HOTPLUG_PCI_SHPC"
value = "y"
reason = "shpchp: a member behind a conventional pci-bridge slot (the cloud's bus topology is unmeasured)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PCI_MSI"
value = "y"
reason = "MEASURED necessary: without MSI `_OSC: not requesting OS control` and pciehp is compiled in but dead"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_HYPERVISOR_GUEST"
value = "y"
reason = "the gate symbol for every paravirt guest feature; with it off the x86 wall clock falls through to mach_get_cmos_time and the appliance boots on whatever the emulated CMOS was filled with"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT"
value = "y"
reason = "the paravirt op vectors kvmclock installs itself through: x86_platform.get_wallclock and the clocksource"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_KVM_GUEST"
value = "y"
reason = "kvm_get_wallclock: the boot wall clock read from the KVM wall-clock MSR, which the host fills from its own CLOCK_REALTIME - UTC, and out of reach of the hypervisor's -rtc base="

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_PARAVIRT_CLOCK"
value = "y"
reason = "the pvclock reader itself (pvclock_read_wallclock), and a monotonic clocksource that survives vCPU migration instead of raw TSC"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_NET"
value = "y"
reason = "the DHCPv4 handshake is four datagrams on a socket; a kernel with no network stack has no socket to give (appliance_pid1::dhcp)"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_INET"
value = "y"
reason = "DISCOVER/REQUEST are UDP: the client binds 0.0.0.0:68 and sends to 255.255.255.255:67 (dhcp::client_socket), and the OFFER/ACK come back broadcast because the interface has no address yet"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_NETDEVICES"
value = "y"
reason = "SO_BINDTODEVICE names the interface the limited broadcast leaves by — the one thing that makes 255.255.255.255 routable with no address and no route"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_NET_CORE"
value = "y"
reason = "the netdev core that carries that broadcast out of eth0 before there is a FIB entry to look up"

[[build.installer.requires]]
edge = "kernel"
surface = "kconfig"
symbol = "CONFIG_TUN"
value = "y"
reason = "boringtun is a USERSPACE data plane: wg_appliance_core opens /dev/net/tun and drives the tunnel from the app, which is why CONFIG_WIREGUARD is vetoed everywhere — with this option off nothing in the medium opens that device and the kernel needs no TUN at all"

[build.toolchain]
image = "nordisk-cargo-toolchain"
image_sha256 = "86d575e874677da3eb9aff3ba6e38ca600781ee9b7948a9df43ef89a6213c762"
rust_version = "1.97.1"
targets = [
    "x86_64-unknown-linux-gnu",
    "x86_64-unknown-linux-musl",
    "wasm32-unknown-unknown",
    "x86_64-unknown-uefi",
]
timeout_secs = 3600
mem_limit_mb = 8192
cpus = 8
siblings = [
    "edda",
    "znippy-zoomies",
    "gunnar-ui",
    "monetize",
    "Skidbladnir",
    "znippy",
    "draupnir",
    "tunnr",
]