Debian Appliance Model

Status: bootable dev image, not production. This document names how ICN distributes a node: as a single Debian-based appliance image that boots, runs icnd, and can later be claimed, role-profiled, and used as a substrate for governed service hosting. As of the QCOW2-build-and-smoke slice, a local dev image can be built and a one-VM smoke can verify icnd health on port 8080. The dev image is not production: unsigned, not immutable, no A-B updates, no claim flow, no partner federation activation. It does not authorize mutation of K3s, DNS, Forgejo, GitHub, or homelab state.

Purpose

ICN currently ships as a daemon that an operator installs into an existing Debian/Ubuntu host (deploy/install.sh, deploy/icnd.service) or runs as a multi-container devnet (deploy/devnet/). Neither of those is a node unit that a partner cooperative can claim and place into operation without becoming an operating-system administrator.

The appliance addresses that gap: one bootable image, one node, one operator action to bring it on-line. The same image is used for every role; the role is a declarative profile applied to the running appliance, not a different image.

This document records the model. The appliance itself does not exist yet.

Non-goals

This document and the scaffold it accompanies explicitly do not:

  • Introduce any Rust types or wire formats.
  • Re-implement GovernedServiceBinding, WorkloadManifest, or RuntimeProvider from docs/spec/governed-service-binding.md. Those remain forward-direction primitives.
  • Re-implement the service-hosting stages from docs/architecture/SERVICE_HOSTING_MODEL.md.
  • Replace the existing native install (deploy/install.sh, deploy/icnd.service) or the existing 3-node devnet (deploy/devnet/). Both continue to be supported until the appliance reaches a comparable acceptance bar.
  • Claim production readiness.
  • Claim a signed release.
  • Claim a live federation or activated partner cooperative.
  • Claim Phase 2 completion.
  • Treat NYCN as production-activated.
  • Embed secrets, keystore passphrases, JWT secrets, or any other private material in the image, in repo, or in build artifacts.

Relationship to existing architecture

This document extends, but does not replace:

  • `SERVICE_HOSTING_MODEL.md` — hosted → governed → ICN-native service progression. Appliance hosts the substrate; service hosting layers above it.
  • `../spec/governed-service-binding.md` — abstract GovernedServiceBinding, WorkloadManifest, RuntimeProvider. The appliance hosts one or more runtime providers; it does not redefine the primitives.
  • `KERNEL_APP_SEPARATION.md` — meaning firewall. The appliance image is a substrate concern; nothing in the image teaches the kernel to read domain types.
  • `../../deploy/icnd.service` — the systemd unit the appliance reuses unmodified for icnd itself.
  • `../../deploy/install.sh` — the existing native install path. The appliance's first-boot scaffold mirrors its directory layout (/var/lib/icn, /etc/icn, icn system user).
  • `../../deploy/devnet/` — the existing Docker devnet. Appliance behavior should eventually converge with devnet behavior (3-node bootstrap, gossip convergence, health) but the first acceptance target is a single boot.

Lifecycle stages

A given appliance moves through stages. The image artifact and the running node have separate stage progressions; both are listed below.

Image artifact stages

Stage What exists What does not
Unbuilt scaffold Docs, manifest templates, role examples, first-boot scaffold, dry-run build script. (Landed PR #1865.) No image. No partner consumer.
Bootable dev image (today) A local QCOW2 image that boots Debian + icnd, generates per-instance secrets at first boot, runs icnd --init, and exposes /v1/health on 8080. A smoke-local.sh --real one-VM smoke verifies the path. Not signed. Not reproducible. Not for partner hands.
Reproducible build A build path (Packer / debos / live-build) that produces the same image hash from the same inputs. Still not signed. Still not immutable.
Signed release The reproducible build emits a signature operators can verify. Still not immutable. No A-B updates.
Production-signed appliance Signed updates, immutable rootfs, A-B updates, measured boot, TPM-backed identity material.

Today is Bootable dev image. The next implementation slice after this is role-profile application so a booted appliance can join a local devnet under the sandbox or genesis profile.

Node-instance states

Once an appliance is running, the node moves through:

State What it means
unclaimed The appliance has booted but no operator has claimed it. icnd may not be running, or is running in an unconfigured state.
claimed An operator has performed the claim action: identity initialized, keystore created, role profile selected. The node knows what it is.
configured The role profile's expected configuration has been applied (peers, gateway binding, observability, role-specific units).
running icnd is running, /v1/health returns OK, role-specific services are alive.
degraded One or more role-specific services are unhealthy, or the node has diverged from federation state, or anti-entropy probes are failing. The node continues to serve what it can.
suspended Authority to act has been revoked (operator pause, governance decision, or emergency stop). The node holds state but does not dispatch effects.
decommissioned The node has been removed from operation. Custody handoff and data export have been performed per the appliance manifest's exit policy.

State transitions are operational facts. None of them is an authority transition by itself; authority changes flow through governance acts per docs/spec/governed-service-binding.md.

Role profiles

Initial role-profile vocabulary. A role is a declared shape the appliance is being used as, not a different image and not a different binary. The role is applied as a manifest reference; the appliance reads the role profile and starts (or refuses to start) the matching unit set.

Profile Purpose Initial expected services
genesis First node in a new local domain. Bootstraps identity and the local genesis state. icnd only. No peers yet.
witness-archive Observes a federation, retains state, serves no policy authority. icnd, observability, retention.
commons-executor Runs commons compute workloads under the existing compute admission policy (ADR-0031). icnd, compute provider.
domain-host Runs the substrate for one institutional domain (docs/spec/institutional-domain.md). icnd, domain-scoped storage, member-shell endpoint.
service-host Hosts governed services declared by GovernedServiceBindings once those land. Today: no services. icnd, future runtime providers.
ingress Public-facing entry point for institutional domains. Routes member-shell and steward-cockpit traffic. icnd, reverse-proxy adapter.
storage Holds durable storage for a domain (custody-class storage per docs/spec/storage-durability-policies.md). icnd, storage backend.
forge-build Build runner for the sovereign forge once it exists. Today: not implemented. icnd, build executor (future).
observability Aggregates metrics, traces, and audit evidence for a federation. icnd, Prometheus, log shipper.
sandbox Disposable node for rehearsals, fixture work, and local proof-loops. Never production. icnd, devnet-style overrides allowed.

Roles are not authority. A node profiled as service-host does not gain authority to host services; it gains the capacity to host them once a GovernedServiceBinding selects it as the substrate. The kernel does not consult role profiles. Role profiles are a substrate-layer config artifact.

Runtime provider roadmap

Each runtime class named in docs/spec/governed-service-binding.md §"Runtime classes" eventually has at least one provider implementation on the appliance. The appliance does not implement providers; it hosts them.

Provider Status Notes
systemd / native First-class baseline icnd.service plus role-specific unit files. The appliance ships with this.
oci-container Future Hosts containerized governed services (forge, auth, status, metrics, registry, docs). Likely backed by podman or containerd, not Docker, to keep the substrate auditable.
k3s Optional, future For roles that need clustered orchestration. Not baseline. An appliance does not require k3s to run.
wasm Future Deterministic legitimacy compute (per ADR-0021) and utility computation. Runs sandboxed in-process or via a dedicated runtime.
microvm Future Stronger-isolation provider for untrusted code paths.
accelerator Future For hardware-accelerated workloads where the appliance is provisioned with the relevant hardware.
external-bridge Future Integrations with non-ICN systems, granted only by explicit policy.

The appliance manifest declares which providers are present on a given image. Adding a provider is an image change; granting a binding to use a provider is a governance act.

Service-hosting future

The appliance is not just icnd. The longer-term posture is:

appliance image
        ↓
icnd (substrate daemon)        ← already runs here
        ↓
runtime providers              ← hosted by the appliance
        ↓
GovernedServiceBindings        ← chosen by governance
        ↓
governed services              ← forge, auth, status, metrics, registry, docs, institution-hosted

The discipline is the same one SERVICE_HOSTING_MODEL.md already states:

ICN owns authority. Services expose functions. Adapters translate service events. Receipts prove transitions. Compute produces evidence. Governance authorizes power.

The appliance is below all of that. It boots, runs the daemon, exposes the provider surface, and lets governance decide what to do with it. Operator scope (who runs the appliance) does not become institutional authority (who authorizes what runs on it).

Security notes

These are scaffold-stage statements. They define the posture the appliance must hold; they do not declare that posture implemented.

Concern Posture
Embedded secrets None. No JWT, keystore passphrase, TLS key, or partner-shared material is in the image, in this repo, or in any build output. The image as built contains zero secrets.
Private data Not in repo. Any institution-specific material lives in partner repos under partner-controlled access.
First-boot material Generated locally on the appliance, per VM, on first boot, by icn-appliance-firstboot.service. JWT secret (openssl rand -hex 32) and keystore passphrase (openssl rand -base64 32) are written to /etc/icn/icnd.env mode 600 owned icn:icn, then icnd --init runs once. Mirrors deploy/install.sh's JWT-generation pattern; extended to also bootstrap the keystore so the dev image's icnd.service can start without manual operator intervention. The operator opts out via ICN_FIRSTBOOT_INIT_IDENTITY=0. To rotate, remove the marker and the keystore and reboot.
SSH access Not granted by the image. The dev smoke uses cloud-init to inject an operator-supplied smoke-only SSH key per VM. The image has no embedded SSH keys, no default password, and disable_root: true / ssh_pwauth: false.
Signed updates Future work. Until signed updates land, an appliance is updated by replacing the image.
Immutable rootfs Future work. The first dev image is mutable.
A-B updates Future work.
TPM-backed identity Future work. Identity material is held in ~icn/.icn/keystore.age (per the existing pattern), Age-encrypted, passphrase-derived.
Measured boot Future work.
Devnet shared secrets Explicitly never used by the appliance. The devnet's devnet-insecure shared secrets exist only in deploy/devnet/. The appliance does not import them, copy them, or accept them as defaults.

Build path posture

The first build target is a single local QCOW2 image. The longer-term posture supports multiple backends; the choice is deferred until the first local build is working.

Candidate backend Strength Weakness
Debian cloud image + virt-customize Smallest jump from existing native install; reuses official Debian images. Single-shot; less reproducible.
Packer (HashiCorp / community) Mature, well-documented, multi-format output. Tooling overhead; license/governance review required for HashiCorp lineage.
debos Native Debian; designed for declarative image builds. Smaller community; more bespoke pipeline.
live-build Official Debian project. Older tooling style; documentation rougher.

deploy/appliance/build-image.sh now picks Debian cloud image + virt-customize: it is the smallest credible step, and it is what the --real path implements. The longer-term choice between Packer / debos / live-build is deferred until reproducibility becomes the focus.

Acceptance for the bootable dev image

The dev image is accepted when, on a disposable local VM:

  1. The image boots Debian to a usable login state.
  2. The first-boot unit runs once and is marked complete (does not run on subsequent boots).
  3. icnd.service starts.
  4. /v1/health on port 8080 returns HTTP 200 to a request from inside the VM via SSH (the dev image does not bind the gateway to non-loopback addresses, preserving systemd hardening).
  5. The appliance can be cleanly destroyed and rebuilt without manual intervention.

deploy/appliance/smoke/smoke-local.sh --real performs exactly this acceptance loop. Required tools and inputs (staged Debian base image, smoke SSH key, cloud-init seed) are listed in `../../deploy/appliance/README.md` §"Real local build + boot smoke".

Honest non-claims (repeated for clarity)

  • No production image exists.
  • No signed release exists.
  • No GovernedServiceBinding runtime implementation lives here.
  • No partner-federation activation exists.
  • NYCN is not production-activated under appliance primitives.
  • No K3s, DNS, Forgejo, GitHub, or homelab state is mutated by this document or the accompanying scaffold.
  • No claim that the existing systemd / Docker / K8s deploy paths are deprecated; they remain primary.

Related documents