Architecture
Astromesh OS is an immutable appliance. Its architecture exists to make one guarantee cheap to verify: the root filesystem you are running is exactly the image that was shipped, and updates either succeed cleanly or roll back. This page covers how that guarantee is built.
Immutability: dm-verity over a read-only root
Section titled “Immutability: dm-verity over a read-only root”The OS ships as a disk image, not a container. The root filesystem is mounted read-only and protected by dm-verity: a Merkle (hash) tree over the root partition lets the kernel verify every block it reads against a single trusted roothash. A tampered or corrupted root block fails verification rather than being served silently.
This is why there is no package manager and no shell at runtime — there is nothing writable to install into. Mutable state lives elsewhere (see the shared /var below), and the runtime’s generated runtime.yaml is produced at boot into a writable path (tmpfs/overlay on /var or /etc) rather than living on the verity-protected root. Read-only profiles are baked into /etc/astromesh/profiles/.
Distribution: OCI artifact via ORAS
Section titled “Distribution: OCI artifact via ORAS”The image is distributed as an OCI artifact and pulled with oras pull — it is never docker run. The artifact is a disk image that happens to live in an OCI registry; treating it as a container would be a category error. Pull it, write it to a disk or convert it to a cloud image format, and boot it.
A/B updates with UKI + systemd-sysupdate
Section titled “A/B updates with UKI + systemd-sysupdate”Updates are atomic image swaps across two root slots, A and B.
ESP (/EFI/Linux) GPT partitions ┌───────────────────────┐ ┌──────────────────────────┐ │ UKI v1 (Type-2) │ │ root-A + root-A-verity │ ← active slot │ UKI v2 (Type-2) │ ◄──── │ root-B + root-B-verity │ ← inactive (being updated) └───────────┬───────────┘ │ /var (SHARED, writable) │ │ └──────────────────────────┘ systemd-boot picks the NEWEST UKI- Each image build produces a UKI (Unified Kernel Image) — kernel, initrd, and command line in one signed file — installed as a Type-2 entry in the ESP under
/EFI/Linux. systemd-boot picks the newest UKI automatically. - systemd-sysupdate downloads the new image into the inactive slot.
- The A/B updater then relabels the inactive slot’s GPT partition UUIDs to the new image’s verity roothash halves (the roothash is split across the root and root-verity partition UUIDs), using
sfdisk. This is what binds a slot’s partitions to a specific verified root.
Automatic rollback
Section titled “Automatic rollback”A new image must prove itself before it is trusted. systemd-boot boots the newest entry with a bounded number of tries; the booted slot is only blessed as good by Astromesh OS’s own health-gated check once /v1/health confirms the agent is up. The stock systemd-bless-boot.service, which would mark any booted slot good unconditionally, is masked so blessing happens only on health.
If a freshly updated slot boots but fails its health check, it is never blessed; systemd-boot exhausts its tries and automatically rolls back to the previous, known-good slot. An update that breaks the agent therefore self-heals.
Shared /var and the fixed Seed
Section titled “Shared /var and the fixed Seed”/var is a single partition shared across both A and B slots — it holds writable runtime state that must survive an update. This creates a subtle constraint: both v1 and v2 must agree on /var’s filesystem UUID, because each build bakes that UUID into its /etc/fstab.
To guarantee agreement, mkosi uses a fixed Seed so repart-generated UUIDs are reproducible across builds. Without a fixed seed, v1 and v2 would derive different UUIDs for /var; the new slot’s var.mount would then wait forever for a by-uuid device that never appears, dropping the system into emergency mode.
Fixed mkosi Seed ──► deterministic /var fs UUID ──► same /etc/fstab entry in v1 and v2 ──► shared /var mounts on either slotBoot path
Section titled “Boot path”Boot is systemd-boot + UKI. The kernel command line forwards both the kernel log and the journal to the serial console (console=ttyS0, systemd.journald.forward_to_console=1) so the QEMU/CI boot harness can observe boot progress and service startup. The default target is the agent itself, so a successful boot ends with astromeshd serving its API.
Security layer (Phase 3)
Section titled “Security layer (Phase 3)”On top of the verified, read-only root, the appliance is hardened so that secrets and the agent itself are bound to an intact boot:
- TPM-sealed secrets — provider keys and other secrets are sealed to the platform’s TPM (PCR-bound), so they are recoverable only when the machine boots the expected, measured image. A tampered boot cannot unseal them.
- No interactive shell + break-glass — the appliance ships with no login and no runtime package manager (
RootPassword=hashed:!). A separate, audited break-glass path exists for recovery rather than a standing shell. - AppArmor + tool sandbox —
astromeshdand the agent’s tool execution run confined under AppArmor, so a tool cannot reach beyond its sandbox. - Egress governance — outbound network access is governed (allowlisted), not open by default.
- Secure Boot — the signed UKI chain is verified by the firmware.
A fail-closed rule ties these together: the agent does not start unless its tool sandbox and egress governance are in place. Security is a boot precondition, not a runtime toggle.
Fleet layer (Phase 4)
Section titled “Fleet layer (Phase 4)”A Phase 4 appliance is a self-configuring fleet member — it joins the mesh from its declarative machine-config alone, with no SSH:
- Machine-config join — a node reads its peers and identity from machine-config (static peers first, migrating to Maia gossip as it matures) and joins on boot.
- Mesh mTLS / IPsec — node-to-node traffic is mutually authenticated and encrypted.
- OpenTelemetry export — boot, health, and runtime metrics are exported over OTel.
- eBPF causal egress — egress is observed and attributed at the kernel layer with eBPF, so cost and network use trace back to the agent (and action) that caused them.
This is the appliance’s defensible core — trust, attribution, and density — rather than raw speed. See the roadmap for how these capabilities are sequenced.
The 500 MB ceiling
Section titled “The 500 MB ceiling”The core image must be ≤ 500 MB, and currently lands at ~285 MB. This is a hard build gate, not a guideline: a finalize step measures the assembled rootfs and fails the build if it exceeds the budget (default 500 MB), printing a breakdown of the heaviest paths to help diagnose the overage.
The dominant term is not the base distribution — it is the Python closure of the runtime. Accordingly, the base is trimmed (locale, man, doc, info, lintian removed) and the runtime’s virtualenv is stripped of bytecode caches (__pycache__, *.pyc) and in-package test directories during the image build.
Reproducibility: runtime.pin
Section titled “Reproducibility: runtime.pin”The exact astromesh runtime built into the image is pinned by a commit SHA in runtime.pin:
# runtime.pinASTROMESH_REF=<commit SHA of monaccode/astromesh>The image is reproducible from that exact ref. Bumping the runtime is a deliberate edit to ASTROMESH_REF; CI fails if it cannot resolve the ref. See Building for the bump procedure.
mkosi.conf highlights
Section titled “mkosi.conf highlights”The build is driven by mkosi.conf. Key settings:
| Key | Value | Why it matters |
|---|---|---|
Distribution / Release | debian / trixie | Debian trixie base, kernel 6.12+ LTS |
Format | disk | Produces a bootable disk image, not a container |
Bootloader | systemd-boot | Selects the newest UKI for A/B |
UnifiedKernelImages | yes | Type-2 UKI in the ESP, replaceable per update |
SplitArtifacts | uki,partitions | Emits the UKI and partitions separately for A/B + verity |
Seed | fixed UUID | Reproducible UUIDs so v1/v2 agree on the shared /var |
KernelCommandLine | console=ttyS0 ...forward_to_console=1 | Boot + journal visible on serial for CI/QEMU |
Packages | systemd, systemd-boot(-efi), python3, python3-venv, fdisk, linux-image-cloud-amd64, … | Minimal set; fdisk provides sfdisk for the A/B updater |
RemoveFiles | locale, i18n, info, lintian, doc, man | Trim toward the 500 MB ceiling |
RootPassword | hashed:! | Root account locked — no interactive login |
The root and root-verity partition sizes are pinned (min == max) so the verity hash partition is identical across repart passes, keeping the verity roothash reproducible — which is what lets the UKI command line match the on-disk verity layout.