PowerCloud

Docs

Install a node

How to run PowerCloud OS on a physical machine, a QEMU, libvirt or Proxmox VM, AWS, Google Cloud or Azure - the image files, writing and booting them, cloud upload scripts, firewall ports, supported hardware and Secure Boot.

View as Markdown·llms.txt

A PowerCloud node is any x86-64 or arm64 machine, physical or virtual, that boots the PowerCloud disk image with UEFI firmware. This page covers getting the image onto a machine and booting it; once it is up, it waits to be enrolled, which Enroll a node covers. AWS is tested on real instances; Google Cloud and Azure images and upload scripts exist but have not yet been booted on those clouds.

The images

Each PowerCloud release carries, for amd64 and arm64 (VERSION is the release name, vYYYY.MM.DD):

File What it is Used for
powercloud-VERSION-ARCH.img.xz The bootable disk image, compressed Physical machines, VMs, and AWS (decompressed)
powercloud-VERSION-ARCH.gce.tar.gz The same disk image packed for Google Cloud scripts/gcp-image.sh
powercloud-VERSION-ARCH.vhd.xz The same disk image as a fixed VHD, compressed scripts/azure-image.sh
powercloud-VERSION-ARCH.efi The OS alone, as one EFI file Upgrading running nodes (pcctl release publish; see Upgrades and releases)
SHA256SUMS Checksums of every file Checking downloads

Check what you download with sha256sum -c SHA256SUMS --ignore-missing. To build the images from the PowerCloud source instead, make image writes out/powercloud-amd64.img and out/powercloud-arm64.img (and the .efi files), and make cloud-images packs them for Google Cloud and Azure. The upload scripts named on this page are in the source's scripts/ directory.

The disk image is a 1 GiB GPT disk with two partitions:

  • An EFI system partition (256 MiB) holding the whole OS as one file at the firmware's default boot path: the kernel with its initramfs and a fixed command line built in. There is no boot loader and no boot menu, and nothing outside can add kernel options.
  • The state partition, empty until the node is enrolled. At enrollment the node creates its encrypted state volume there. On every boot it grows the partition to fill the disk, so a disk larger than the image (or a cloud disk resized later) is used in full.

What you see when it boots

The kernel logs to the serial console and to a monitor, if one is attached. pcd prints its message to both:

  PowerCloud node 262c2fdc861a9cf2 is waiting to be enrolled.
  On your admin machine run:
    pcctl node enroll <this-node-address> <code> <name>
  enrollment code: EYWC-7XEG-DKOP-F2IR-23UA-GTJQ-5G3M-ZXTO

The node gets its network address by DHCP. Its API listens on TCP port 7443, which your admin machine must be able to reach. After enrollment, every reboot shows an unlock code instead, unless TPM or peer unlock is on (see Unlocking nodes after a reboot). A machine given a join bundle skips the code and joins by itself (see Enroll a node).

Physical machines

  1. Decompress and write the image to the disk the machine will boot from. On Linux, with the target disk as /dev/sdX (double-check the name: everything on it is overwritten):

    xz -d powercloud-VERSION-amd64.img.xz
    sudo dd if=powercloud-VERSION-amd64.img of=/dev/sdX bs=4M conv=fsync
    
  2. Boot it with UEFI. Legacy BIOS boot is not supported; turn off CSM or legacy mode if the firmware has it. The firmware finds the OS at the default path, so no boot entry is needed.

  3. Secure Boot. Release images are not signed for Secure Boot. On a machine with Secure Boot on (the default on most PCs), either turn it off in the firmware setup, or build images signed with your own key and enroll it (below).

  4. Read the enrollment code from the monitor or serial console, and enroll the node from your admin machine.

Caveats when writing the disk:

  • Don't use conv=sparse. The state partition must start out zeroed, and pcd refuses to format a state partition whose first MiB holds anything else. A sparse write leaves whatever the disk held before.
  • dd overwrites only the image's size (1 GiB). Erase a disk that held sensitive data first, with blkdiscard or the drive's own secure erase.
  • One PowerCloud disk per machine. If two disks in the same machine carry a PowerCloud state partition, pcd stops with "several PowerCloud state partitions". Erase the one you don't want.
  • arm64 machines need UEFI firmware with ACPI, as Arm servers have. The arm64 image boots through UEFI and describes the machine with ACPI, not a device tree.

Virtual machines: QEMU, libvirt and Proxmox

Any KVM host works: give the VM UEFI firmware (OVMF on x86, AAVMF on arm64), the disk image as a virtio disk, and a virtio NIC. On a Debian or Ubuntu host with the ovmf package, a QEMU node with its API forwarded to the host's port 7443 looks like this:

cp /usr/share/OVMF/OVMF_VARS_4M.fd node-vars.fd
qemu-img resize -f raw powercloud-VERSION-amd64.img 8G    # optional: more room for the state volume
qemu-system-x86_64 -machine q35,accel=kvm -cpu max -smp 2 -m 1G -nographic \
  -drive if=pflash,format=raw,unit=0,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
  -drive if=pflash,format=raw,unit=1,file=node-vars.fd \
  -drive file=powercloud-VERSION-amd64.img,format=raw,if=virtio \
  -netdev user,id=n0,hostfwd=tcp::7443-:7443,hostfwd=udp::51820-:51820 \
  -device virtio-net-pci,netdev=n0 -device virtio-rng-pci

With -nographic, the serial console (and the enrollment code) is in the terminal. Enroll it at 127.0.0.1. The automated tests run nodes like this with 1 GiB of memory.

For a VM to join with nobody at its console, give it a join bundle as a QEMU fw_cfg file:

pcctl --repo LOC,... node provision vm-1 vm-1.join
# add to the qemu command line:
  -fw_cfg name=opt/powercloud/join,file=vm-1.join

libvirt (a fwcfg sysinfo entry) and Proxmox (the VM's args option) can pass the same fw_cfg file; the plain QEMU path is the one covered by automated tests. See Enroll a node for what the node does with the bundle.

The same image runs on VMware (vmxnet3 network, PVSCSI or SATA disk) with UEFI firmware.

AWS

scripts/aws-image.sh turns a disk image into an AMI in about a minute. It writes the image straight into an EBS snapshot (skipping all-zero blocks, so no S3 bucket or import role is needed) and registers a UEFI AMI with ENA networking and NitroTPM. It uses the AWS CLI's usual credentials and region, and prints the AMI ID.

xz -d powercloud-VERSION-arm64.img.xz
scripts/aws-image.sh arm64 powercloud-VERSION-arm64.img        # amd64 for x86 instances

Launch it on any Nitro instance type, x86 (t3, m6i, ...) or Graviton (t4g, m7g, ...), with a join bundle as user data so it joins unattended:

pcctl --repo LOC,... node provision node-1 node-1.join
aws ec2 run-instances --image-id AMI_ID --instance-type t4g.micro \
  --metadata-options HttpTokens=required --user-data file://node-1.join ...
pcctl --repo LOC,... node addr node-1 PUBLIC_IP
  • Security group. Open TCP 7443 (the node API) to your admin machines only, and UDP 51820 (WireGuard) to the internet. A public node that relays for nodes behind strict NATs also needs UDP 51821-51920.
  • Without user data, the node waits for console enrollment, and aws ec2 get-console-output --instance-id ID --latest shows the code.
  • New public IP after a stop and start. Run pcctl --repo LOC,... node addr NAME NEW_IP, which also updates the node's mesh endpoint for its peers. An Elastic IP avoids this.
  • Secure Boot. With POWERCLOUD_SB_CERT set to the certificate your image was signed with, the AMI turns Secure Boot on with your key as its only trusted key, so instances boot only images you signed. Then pcctl --repo LOC,... node tpm NAME on lets the node unlock itself with its NitroTPM after a stop and start. This needs the PowerCloud source and a Go toolchain on the machine running the script.
  • Confidential VMs. On AMD SEV-SNP instances (c6a, m6a, r6a, offered by AWS on shared tenancy in US East (Ohio) and Europe (Ireland)), pcctl verifies the CPU's attestation before it unlocks the node; see Unlocking nodes after a reboot.

Limits: older Xen instance types (t2, m4, ...) are not supported. NitroTPM is AWS's virtual TPM, so TPM unlock protects against a stolen EBS snapshot, not against AWS itself.

Google Cloud (not yet tried)

The Google Cloud image and script are built to Google's documented import format and CLI, but have not yet been booted on Google Cloud. Expect to be among the first to try them.

scripts/gcp-image.sh copies the image into a Cloud Storage bucket you own, creates a Compute Engine image from it (UEFI, gVNIC, and SEV-SNP capable on x86), deletes the uploaded file and prints the image name. It uses gcloud's usual account and project, and takes either the release's .gce.tar.gz or a disk image (which needs the PowerCloud source and Go to pack).

GCP_BUCKET=my-bucket scripts/gcp-image.sh arm64 powercloud-VERSION-arm64.gce.tar.gz
gcloud compute instances create node-1 --image IMAGE --machine-type t2a-standard-1 \
  --network-interface nic-type=GVNIC --shielded-vtpm \
  --metadata-from-file user-data=node-1.join ...

Launch it as a Shielded VM (which has a vTPM), x86 (n2d, c3, ...) or Arm (t2a, c4a). Allow TCP 7443 from your admin machines and UDP 51820 from anywhere in the VPC firewall. With POWERCLOUD_SB_CERT, the image carries your certificate as its Secure Boot keys, and --shielded-secure-boot boots only images you signed; without it, leave Secure Boot off, since Google's default keys don't trust PowerCloud images.

Azure (not yet tried)

The Azure image and script are built to Azure's documented import format and CLI, but have not yet been booted on Azure.

AZURE_RESOURCE_GROUP=GROUP scripts/azure-image.sh uploads the VHD into a managed disk with azcopy and makes it a version of the image powercloud-ARCH in an Azure Compute Gallery named powercloud in that group, creating both if missing. It uses the Azure CLI's usual account and subscription, takes the release's .vhd.xz or a disk image, and prints the image version's ID.

AZURE_RESOURCE_GROUP=my-group scripts/azure-image.sh amd64 powercloud-VERSION-amd64.vhd.xz
az vm create -g my-group -n node-1 --image IMAGE_VERSION_ID --specialized --size Standard_D2as_v6 \
  --security-type TrustedLaunch --enable-secure-boot false --enable-vtpm true \
  --user-data node-1.join --nsg-rule NONE ...

The images are Generation 2 (UEFI) and specialized, so Azure provisions nothing in them; the node joins from its user data. On x86 they are Trusted Launch, which gives instances a vTPM. Arm sizes (Standard_D2ps_v6, ...) take the same command without the three security options. Secure Boot stays off, because Azure's default keys don't trust PowerCloud images. --nsg-rule NONE opens nothing, so add network security group rules for TCP 7443 from your admin machines and UDP 51820.

Supported hardware

Every driver is built into the kernel, which loads no modules. Network cards whose drivers need a separate firmware file are not supported.

Where Network Disk
KVM: Proxmox, libvirt, Hetzner, DigitalOcean, Vultr, ... virtio-net virtio-blk, virtio-scsi
AWS (Nitro instances, x86 and Graviton) ENA NVMe
Google Cloud (x86 and Arm) gVNIC, virtio-net NVMe, virtio-scsi
Azure (x86 and Cobalt) Hyper-V netvsc, MANA Hyper-V storvsc, NVMe
VMware vmxnet3 PVSCSI (amd64), SATA
Physical servers Intel e1000e, igb, igc, ixgbe, i40e, ice; Mellanox ConnectX-4 and later; Broadcom NetXtreme-C/E; Realtek r8169 NVMe, SATA (AHCI), Broadcom MegaRAID, SAS3 HBAs

What has actually run: the drivers QEMU can emulate (virtio, e1000e, vmxnet3, SATA, MegaRAID, NVMe) are checked in automated tests, with one node's disk moved between controllers; ENA and NVMe have run on real AWS instances, x86 and Graviton. gVNIC, Hyper-V and MANA are built in but have not run yet. Physical machines are expected to work with the drivers above but are not part of the automated tests.

Secure Boot with your own key

Because the whole OS is one EFI file, one signature covers everything up to the services. With Secure Boot on and your key enrolled, the firmware boots only images you signed: not a modified image, and not one a cloud provider swaps in.

  1. Create a key once, and keep db.key offline:

    scripts/sb-keygen.sh ~/powercloud-sb
    

    It writes db.key (private), db.crt (for signing) and db.cer (for enrolling in firmware).

  2. Build images signed with it, from the PowerCloud source:

    POWERCLOUD_SB_KEY=~/powercloud-sb/db.key POWERCLOUD_SB_CERT=~/powercloud-sb/db.crt make image
    
  3. Enroll the certificate: on a physical machine, in the firmware setup (Secure Boot custom mode, add db.cer to db); on AWS and Google Cloud, by passing POWERCLOUD_SB_CERT to the upload script. Enrolling on real hardware has not been tested yet.

pcctl node info NAME shows boot UEFI, Secure Boot on once it works. Sign every later image with the same key, or the firmware refuses it (an upgrade then rolls back by itself). Secure Boot is also what TPM unlock builds on; see Unlocking nodes after a reboot.

Next steps

  • Enroll a node: enroll the node from its console, or join it unattended.
  • Troubleshooting: a node that doesn't boot, can't be reached or won't format its disk.
All documentation