AOS Hub / Docs

Tutorial: Build and boot AOS from source

This tutorial builds a custom server image, supplies first-boot storage policy, and boots the result under the AOS-built QEMU and UEFI firmware. The guest uses DHCP and exposes SSH through a host port forward.

Run the commands from the repository root on an x86_64-linux machine with Nix, KVM, and at least 20 GiB of free disk space.

#1. Define the system image

Create systems/demo-server.nix:

{...}: {
  imports = [./server.nix];

  aos.networking.hostName = "aos-demo";
  aos.services.ssh.enable = true;

  environment.etc."ssh/authorized_keys/root" = {
    text = ''
      ssh-ed25519 AAAA_REPLACE_WITH_YOUR_PUBLIC_KEY operator@example.com
    '';
    mode = "0600";
  };
}

Replace the example public key before building. This file is part of the image definition: do not put private keys or other secrets in it.

Build the raw disk image and the host-side tools used below:

nix-build -A systems.demo-server.build.image.raw -o result-aos-image
nix-build -A pkgs.qemu -o result-aos-qemu
nix-build -A pkgs.edk2 -o result-aos-edk2
nix-build -A pkgs.gptfdisk -o result-aos-gptfdisk
nix-build -A pkgs.libisoburn -o result-aos-libisoburn
nix-build -A pkgs.zstd -o result-aos-zstd

The image is result-aos-image/aos-aos.img.zst. It contains a zstd-compressed UEFI system partition and the immutable root-a filesystem. Swap and /var are created in free space on first boot.

#2. Define first-boot storage

Create host.nix in the repository root:

{
  aos.provisioning.storage.partitions = {
    swap = {
      sizeMin = "1G";
      sizeMax = "1G";
    };

    var.sizeMin = "8G";
  };
}

host.nix is literal Nix, not JSON or cloud-config. This example focuses on first-boot storage; the same policy can manage runtime hostname, networking, firewall, service, and package configuration as described in the host.nix guide. The storage layout is committed once; changing this file after the first successful boot does not resize or repartition the machine.

Put the file on an AOS metadata ISO:

mkdir -p aos-demo-metadata
cp host.nix aos-demo-metadata/host.nix
./result-aos-libisoburn/bin/xorriso -as mkisofs \
  -V aos-metadata \
  -o aos-demo-metadata.iso \
  aos-demo-metadata

The volume label and file name are part of the interface. A detached host.nix.sig and facts.json may be placed on the same ISO in deployments that use them.

#3. Prepare a writable VM disk

Copy the immutable build result, grow the copy to 16 GiB, and move the backup GPT header to the new end of the disk:

./result-aos-zstd/bin/zstd -d result-aos-image/aos-aos.img.zst -o aos-demo.img
chmod u+w aos-demo.img
truncate -s 16G aos-demo.img
./result-aos-gptfdisk/sbin/sgdisk -e aos-demo.img

cp result-aos-edk2/FV/OVMF_VARS.fd aos-demo-OVMF_VARS.fd
chmod u+w aos-demo-OVMF_VARS.fd

The stock storage policy needs more than 6 GiB of free space after root-a: a fixed 2 GiB swap partition, a /var partition with a 4 GiB minimum, the provisioning marker, and alignment. This tutorial asks for 1 GiB of swap and at least 8 GiB for /var, so 16 GiB leaves comfortable room for the image and its state partitions.

#4. Boot the guest

./result-aos-qemu/bin/qemu-system-x86_64 \
  -machine q35,smm=on,accel=kvm \
  -cpu host \
  -m 4096 \
  -smp 2 \
  -nographic \
  -global driver=cfi.pflash01,property=secure,value=on \
  -global ICH9-LPC.disable_s3=1 \
  -drive if=pflash,unit=0,format=raw,readonly=on,file=result-aos-edk2/FV/OVMF_CODE.fd \
  -drive if=pflash,unit=1,format=raw,file=aos-demo-OVMF_VARS.fd \
  -drive file=aos-demo.img,format=raw,if=virtio \
  -drive id=metadata,file=aos-demo-metadata.iso,if=none,format=raw,readonly=on \
  -device virtio-scsi-pci,id=scsi0 \
  -device scsi-cd,drive=metadata,bus=scsi0.0 \
  -nic user,model=virtio-net-pci,hostfwd=tcp::2222-:22

Leave QEMU running. From another terminal, connect with the public key baked into the image:

ssh -p 2222 root@127.0.0.1

The first boot may take longer while AOS commits the storage layout and creates /var. If SSH is not ready, inspect the serial console in the QEMU terminal.

#5. Inspect the running host

Inside the guest:

cat /etc/os-release
systemctl is-system-running
systemctl --failed
findmnt /
findmnt /var
lsblk -o NAME,SIZE,FSTYPE,PARTLABEL,MOUNTPOINTS
cat /var/lib/aos-provisioning/audit.json

The root filesystem should be EROFS and read-only. /var should be a separate writable filesystem, and the provisioning audit should record the operator input.

Use systemctl poweroff to stop the guest. QEMU exits after the virtual machine powers off.

You now have a system variant you can extend with services, users, firewall policy, or static networking. Continue with Build and customize release images, then read Install an image before deploying the disk outside this disposable VM.

#Clean up

The system variant is source code; keep it if it represents a real deployment. The VM artifacts are disposable:

rm -r aos-demo-metadata
rm aos-demo-metadata.iso aos-demo.img aos-demo-OVMF_VARS.fd
rm host.nix