Immutable Alpine Linux-based virtualization host for Wise Foundry, supporting KVM/QEMU, libvirt, LXC, containers, datastores, clustering, and live migration.
  • Go 50.1%
  • TypeScript 29.3%
  • Shell 18.3%
  • C 1.2%
  • CSS 0.5%
  • Other 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Michael J. Manley 823c0f1b0b Version from release/YY.MM branches; fixes from the pre-deployment sweep
- build/version.sh: YY.MM comes from the branch when it is exactly
  release/YY.MM, otherwise from the build month; PATCH stays the patch id and
  VERSION= overrides. The Makefile, build.sh, plugin builds and QEMU suites ask
  it; a unit suite covers 22 cases.
- mkartifacts.sh: the .ironkey check no longer fails when objdump is killed by
  SIGPIPE under pipefail.
- QEMU suites: boot-screen reads /dev/vcs1 with LC_ALL=C; plugin suites take
  the newest build; test-backup polls for the dropped checkpoint; test-site-shots
  watches the display through QMP when the image has no serial console.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XvgVC2H8Wa7QukpNzTJia9
2026-10-01 16:43:54 -07:00
build Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00
cmd Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
docs Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00
initramfs Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
internal Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
locales Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
pluginsdk Make pluginsdk a module of its own, for plugins built outside the tree 2026-09-28 23:43:00 -07:00
rootfs Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
sdk Every string a person reads comes from a translation catalog 2026-09-29 11:46:26 -07:00
test Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00
tools Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
web Signed image manifest, free-form kernel options, and ESXi-style boot screens 2026-10-01 00:56:46 -07:00
.gitignore Alloy enrollment, support bundles, port-group-only Docker networking 2026-09-15 12:08:44 -07:00
CHANGELOG.md Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00
CLAUDE.md Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00
eslint.config.js feat: console extensions, plugin docs, and the installer work 2026-09-19 11:04:06 -07:00
go.mod Every string a person reads comes from a translation catalog 2026-09-29 11:46:26 -07:00
go.sum Every string a person reads comes from a translation catalog 2026-09-29 11:46:26 -07:00
iron-icon.png Slim the base image, version by build date, add VLAN trunks and Autostart 2026-09-24 08:23:46 -07:00
iron-product-logo-white.png Slim the base image, version by build date, add VLAN trunks and Autostart 2026-09-24 08:23:46 -07:00
iron-product-logo.png Slim the base image, version by build date, add VLAN trunks and Autostart 2026-09-24 08:23:46 -07:00
LICENSE.md Alloy enrollment, support bundles, port-group-only Docker networking 2026-09-15 12:08:44 -07:00
Makefile Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00
NOTICE.md Mount hugetlbfs, keep plugins apart by key set, report running plugin versions 2026-09-25 22:50:51 -07:00
README.md Version from release/YY.MM branches; fixes from the pre-deployment sweep 2026-10-01 16:43:54 -07:00

Wise Foundry Iron

Wise Foundry Iron is the bare-metal host OS of Wise Foundry: an immutable Alpine Linux appliance that runs KVM virtual machines (libvirt QEMU), system containers (libvirt LXC), and Docker workloads, managed by a local agent and the Iron Console status screen.

  • Read-only, dm-verity verified EROFS root in two A/B slots
  • Signed unified kernel images booted by systemd-boot, with boot counting and a post-boot health check for automatic rollback
  • Host configuration on /persist, workload data on /var; an OS rollback never touches either
  • Signed update bundles (.ironupd), a hybrid installer ISO, and a pre-installed raw disk image

Design, disk layout, persistence map, and all tool contracts: docs/architecture.md.

Evaluating Iron

If you are evaluating the CTP Preview, start with the evaluator guide: hardware and guest support, installation with Secure Boot, networking, workloads, operations and known issues.

Building

Requirements: podman, make. Builds run rootless in an Alpine 3.24 builder container; no loop devices or root privileges are needed.

A bare make (or make help) lists every target and the variables that matter; it builds nothing. There are two variants: VARIANT=dev, the debug build (the default; development keys in ./keys, depots for PREREL), and VARIANT=release (keys outside the repository, depots for PRODUCTION).

make help                  # every target, grouped, with the variables
make keys                  # development signing keys in ./keys (never commit)
make image                 # dev (debug) variant -> out/iron-<v>-dev.*
make release               # installable images -> out/iron-<v>.{iso,raw,ironupd}, v = YY.MM.PATCH, plugins -> out/plugins/release/ (IRON_SIGNING_KEYS; docs/release-keys.md)
make test-images           # images for the QEMU tests -> out/test/ (never install these)
make plugins               # plugins -> out/plugins/dev/ (needs this version's image for its SBOM)

<v> is YY.MM.PATCH (build/version.sh): YY.MM is this repository's branch when it is release/YY.MM, else (dev, any other branch, a detached HEAD, no git) the year and month of the build (UTC); PATCH is 0 unless given (make release PATCH=1); VERSION=YY.MM.PATCH overrides both. On dev, a later make plugins, depot or usb in another month needs VERSION=YY.MM.PATCH to find the build.

Outputs in out/:

File Use
iron-<v>.iso UEFI installer (CD or USB); also upgrades existing hosts
iron-<v>.raw Pre-installed 16 GiB disk, slot A populated
iron-<v>.ironupd Signed update bundle for iron-update apply
iron-<v>.txt Build ID, root hash, checksums

Write the hybrid installer ISO to a USB stick on Linux by identifying its whole-disk device with lsblk, then running:

sudo make usb USB_DEVICE=/dev/sdX USB_IMAGE=out/iron-<v>.iso

This erases the selected disk. The writer refuses partitions, mounted disks and the disk containing the running system, and asks you to type the full device name before it writes. Use the .iso, not the pre-installed .raw image, for an installer USB.

Development images (-dev) add a root automation shell on ttyS1 (used by the QEMU tests) and are labelled "development" everywhere. No image has a login getty: the only local shell is the credential-protected emergency shell (the Iron Console, or tty2 on the machine's own screen), which is a service and off by default. Everything under out/test/ exists only for automated tests: some of those images are deliberately broken (-fault-*) and must never be installed.

Testing

make test-unit             # host tool unit tests (Alpine container)
make test-go               # Go vet + unit tests
make test-web              # web console typecheck, lint, unit tests
make test-images           # out/test: this version as base, PATCH+1 good, PATCH+2 faulty
make test-guest-media      # out/test/cache: LXC rootfs, Alpine cloud image and virt ISO for guest tests
make test-lxc              # LXC containers on a real image (datastore, runtime mounts, snapshots, clones)
make test-vm               # virtual machines under nested KVM (cloud-init, consoles, snapshots, clones, OVMF + TPM)
make test-guest-controls   # NUMA, USB, throttling, blockers, monitor, guest agent, console expiry, snapshot chains, quotas, interrupted moves
make test-network          # kSwitches, teams, oSwitches, port groups, routers, revert protection
make test-datastores       # directory, disk and NFS datastores, health and the content browser
make test-docker           # Docker containers and Compose stacks
make test-maintenance      # maintenance mode
make test-ova              # OVA/OVF import and export
make test-plugin-caps      # the plugin capability framework (builds the fixture plugin first)
make test-bind-mounts      # datastore bind-mount security regressions (symlinks, Compose, restarts)
make test-dd-usb           # the ISO written raw to a USB disk boots and installs
make test-iscsi            # iSCSI initiator and target plugins (builds the shipping plugins first)
make test-zfs              # ZFS plugin
make test-cluster-fs       # shared OCFS2 datastore on an iSCSI LUN
make test-secureboot       # Secure Boot enrollment and refusals
make test-alloy            # host side of Alloy enrollment against the reference alloy-dev
make test-qemu             # the QEMU suite (test/qemu/run-all.sh): boot, install, update/rollback, upgrades,
                           # web API, network, storage, plugins, LXC, VMs, OVA, recovery ISO; not secureboot or alloy
bash test/qemu/test-web-screenshots.sh   # Foundry Console screenshots from a real VM (out/web-screenshots)

QEMU tests need /dev/kvm, qemu-system-x86_64, OVMF (edk2-ovmf), socat, and python3 on the host; the VM test also needs nested KVM (it falls back to TCG) and ssh-keygen, and the guest tests download media once (curl, mkfs.ext4).

Host console

The physical console, BMC viewer and serial port show the Iron Console, laid out like the ESXi DCUI with orange in place of yellow: host and hardware summary, management URLs, and <F2> Customize System/View Logs / <F12> Shut Down/Restart. F2 opens System Customization (password, management network with automatic revert, network tests and restore, maintenance mode, rollback, plugins, troubleshooting options with the emergency shell, logs, support information) after signing in with a local account. Upgrades, Alloy enrollment and turning SSH on are done in the web console. Screens and keys: docs/console-ui.md.

Web console and users

Each host serves the Foundry Console at https://<host>/, laid out like the ESXi Host Client: host summary and monitoring, system settings (host name, DNS, time, services, certificate), users and roles, updates and rollback, datastores with a datastore browser, containers (Docker: create and edit, logs, browser shell, stats, images, volumes, networks), and networking (kSwitches with NIC teaming, kernel adapters with automatic revert, oSwitches, port groups, virtual routers, physical NICs). Long operations run as tasks shown in Recent tasks. The certificate is self-signed on first boot; compare its SHA-256 fingerprint with System information on the web console's Host page, opened from a trusted network, before trusting it.

Local accounts are shared by the web console and the Iron Console:

Role Can
Administrator everything, including users, network and storage configuration, upgrades and the emergency shell
Operator power, maintenance mode, service restarts, network tests, audit log, datastore files, and creating and operating VMs, LXC and Docker containers (including logs and shell), without host devices or privileged guests
Read-only view status, configuration, logs, datastores, VMs, LXC and Docker containers (without environment values); change own password

The built-in admin account is the break-glass administrator and cannot be removed.

Local accounts can add two-factor sign-in (TOTP, with recovery codes) for the web console, the API and the recovery console, and an Administrator can require it for administrators (docs/mfa.md); the Iron Console and SSH do not ask for it.

SSH is off by default. An Administrator can enable it in Manage › Services (the Iron Console's Troubleshooting Options only shows its state); it then accepts only root with public keys added under Manage › Services › SSH, and both consoles show a warning while it is enabled. Manage accounts in Host › Manage › Security & users, or from the emergency shell with iron-agent user list|add|passwd|set-role|disable|enable|delete|reset-mfa. API and screens: docs/web-api.md, docs/web-console.md.

Build the web console with the image (make image); make test-web runs its type checks, lint and unit tests.

Virtual machines

Virtual machines run on libvirt QEMU/KVM (Host › Virtual machines). Disks, EFI variable stores and cloud-init seeds are stored on datastores; ISO images come from each datastore's iso/ folder or are downloaded from a URL. VMs support the Proxmox VE hardware and options — SeaBIOS or OVMF, TPM 2.0, q35 or i440fx, CPU topology, types and limits, ballooning and hugepages, VirtIO SCSI, VirtIO, SATA and IDE disks with cache, discard, I/O threads and limits, network models, PCI and USB passthrough, cloud-init, virtiofs shares, guest agent — plus snapshots, full clones, disk import, resize and move, OVA import and export, and noVNC and serial consoles in the browser. Details: docs/vm.md.

Datastores and stacks

Datastores can be local directories, ext4 or xfs disks, or NFS shares (for example a shared ISO library); see docs/datastores.md. Plugins add ZFS pools, iSCSI LUNs and shared OCFS2 volumes. Docker containers can also be deployed as Compose stacks with the same safety rules as single containers; see docs/compose.md.

Networking

Networking has two layers. kSwitches are kernel bridges on the physical NICs: they carry NIC teaming (active-backup, LACP and the other bonding modes) and the kernel adapters (krn0 for management) that hold the host's addresses. oSwitches are Open vSwitch bridges, each tied to a kSwitch; their port groups (external VLANs or host-internal networks) are what VMs, LXC and Docker containers attach to. Virtual routers connect port groups with DHCP, DNS, source NAT, port forwards, floating addresses and static routes. Docker uses Iron's iron network driver, so docker run --network and Compose work with port groups. Hosts on the earlier iSwitch model migrate automatically. Details: docs/networking.md.

LXC containers

System containers run on libvirt LXC (Host › LXC containers). Create them from the images.linuxcontainers.org catalog or from a custom root filesystem on a datastore; each container's root disk and volumes are qcow2 images on datastores, attached only while it runs. Containers are unprivileged by default and support the Proxmox VE container options plus snapshots, full clones, CPU pinning and more. Details: docs/lxc.md.

Plugins

Plugins are signed layers (packages, drivers, kernel modules, development tools) stacked over the read-only system image at boot; none are installed by default. Install and enable them in Host › Manage › Plugins (or with iron-plugin install FILE --enable) and restart. make plugins builds the plugins under build/plugins/ into out/plugins/dev/; make release into out/plugins/release/, the only directory for an Alloy holding the release key (docs/release-keys.md "Where plugins go").

A plugin can also extend the Foundry Console — a tab, a toolbar button, a group of fields — and that code ships in its layer rather than in the base image, so a host without the plugin never downloads it (docs/console-extensions.md).

APK is not part of the base image, and neither is its package database. A plugin built with --writable-root gives the host a persistent writable root layer for development (none is built today). While one is enabled both consoles show a development-mode warning; Discard development changes returns the root to the stock image at the next restart.

Writing one: docs/plugins.md. The format and the boot-time checks: "Plugins" in docs/architecture.md. The plugins in this tree have their own documents — ZFS, NFS exports, iSCSI initiator and target, cluster filesystem, Proxmox import and Intel Quick Sync.

Upgrading

Every Iron release can upgrade any earlier installation: the bundle format and disk layout are stable, the new version goes to the unused A/B slot, and the previous version stays installed for rollback. A newer build of the same version also counts as an upgrade; an equal or older build needs Allow downgrade. Host configuration (/persist) and workload data (/var, datastores) are never rewritten.

From installer media (like an ESXi upgrade): boot the newer ISO, select the disk marked * (contains Wise Foundry Iron), choose Upgrade Wise Foundry Iron, preserve configuration and data, and confirm with F11. For unattended upgrades put an iron-answers.conf with MODE=upgrade and DISK=/dev/… on media labelled IRONANSWERS. If the host's last upgrade never confirmed (for example it shows "Boot not yet confirmed"), the installer keeps the last confirmed version and replaces the unconfirmed one.

From the running host: in the web console, Host › Manage › Updates › Upgrade, then upload a bundle, give a URL for the host to download, or pick attached installer media. The bundle's signature, compatibility and space checks are listed before the upgrade starts; the new version starts when you restart the host. The Iron Console does not upgrade; it only rolls back.

From a shell (emergency shell or automation):

iron-update find-media                                  # attached installer media
iron-update fetch https://updates.example/iron-26.09.0.ironupd
iron-update verify --json /var/lib/iron/staging/iron-26.09.0.ironupd
iron-update apply --reboot /var/lib/iron/staging/iron-26.09.0.ironupd
iron-update status

The new deployment boots with three tries. iron-health blesses it once its checks pass (the management network, local datastores, /dev/kvm, libvirt answering and every agent component; docs/architecture.md "Health check and boot blessing"); otherwise the host reboots until systemd-boot falls back to the previous deployment, and the Iron Console shows the rollback. Rollback to Previous Version (or iron-update rollback --reboot) returns to the previous deployment on request.

Support bundles

Host › Monitor › Support in the web console (or POST /api/v1/support/bundles, or iron-support bundle --output FILE) collects logs, configuration and diagnostics for Wise Global Solutions support into one gzip tar. Password hashes, private keys, enrollment codes, container environments, .env files and cloud-init user data are never included; the host keeps the five newest bundles. A host whose boot stopped before the agent started can still hand one over through the recovery console on port 9443 (docs/recovery-console.md). See docs/support-bundles.md.

Wise Foundry Alloy

A host joins Wise Foundry Alloy with an enrollment code created in Alloy (Host › Manage › Alloy › Enroll, the Iron Console's F2 › Wise Foundry Alloy, or iron-agent alloy enroll CODE). The host pins Alloy's certificate and stores Alloy's Ed25519 public key; Alloy then signs every call to the host API and the host checks each one against that key. make alloy-dev builds a reference Alloy for development and tests. How the association works is described in docs/alloy.md.

Secure Boot

UKIs and systemd-boot are signed with the db key of the build's key set: keys/ for development builds, a directory outside the repository (IRON_SIGNING_KEYS) for release builds. Images carry PK.auth, KEK.auth and db.auth in loader/keys/iron/ on the ESP; with the firmware in setup mode, choose Enroll Secure Boot keys: iron in the boot menu. Key sets, enrollment files, the no-rotation rule and the enforcement test are described in docs/release-keys.md; evaluators follow docs/guide/install.md.

License

Wise Foundry Iron is licensed under the Apache License, Version 2.0; see LICENSE.md. Third-party components and their licenses are listed in NOTICE.md.