Files
quixos/tofu
2026-06-27 13:30:29 -07:00
..
2026-06-27 13:30:29 -07:00
2026-06-17 21:41:15 -07:00
2026-06-21 11:02:00 -07:00

Quixos OpenTofu

This directory is split into two independent projects:

  • ami-builder/: submodule that publishes a project-agnostic NixOS EC2 AMI with a matched root snapshot and preseeded ZFS /nix snapshot.
  • shared-state/: long-lived encrypted EBS volume for state that should survive runtime replacement, such as Caddy ACME account/certificate material and SSH host keys. Runtime deploys attach this volume but do not own or destroy it.
  • runtime/: Quixos deployment stack. It consumes a published AMI for the system image and creates app/runtime resources separately. The deploy wrapper can resolve that AMI from the AMI-builder submodule revision and target architecture.

Destroying the runtime stack destroys the EC2/app/runtime resources only. It must not deregister the AMI or delete the AMI root or /nix snapshots. Those belong to the image-builder state. It also must not delete the shared state volume; that belongs to shared-state.

Shared State

Create the long-lived shared state volume before runtime deploys:

cd tofu/shared-state
tofu init
tofu apply

Use the same aws_region, vpc_id, and subnet_id values as the runtime stack if you override them. The shared state volume must be in the same availability zone as the runtime EC2 instance.

Runtime Deploy

Create tofu/runtime/terraform.tfvars:

aws_region      = "us-west-2"
route53_zone_id = "Z..."
subdomain       = "quixos.example.com"

Then:

cd tofu/runtime
tofu init
./deploy.sh --packages-ref master

deploy.sh resolves the AMI by looking for an owned EC2 image tagged with the AMI-builder submodule HEAD and target architecture. It writes the resolved value to resolved-ami.auto.tfvars.json, which is intentionally local state and is ignored by Git. You can still override it explicitly with --ami-id ami-.... The ami_id Terraform variable has a null default so tofu destroy does not prompt for an AMI just to destroy resources already recorded in state.

deploy.sh resolves the package-tree ref. If .quixos-package-tree.json contains quixos.rev, that rev is deployed. Use --quixos-ref master to override it.

If the package tree or its submodules are private, provide HTTPS credentials through the wrapper. The token is written to /persist-local/secrets/quixos-git.env and consumed by the package-tree systemd unit through GIT_ASKPASS.

QUIXOS_PACKAGE_TREE_GIT_USERNAME=oauth2 \
QUIXOS_PACKAGE_TREE_GIT_TOKEN=... \
./deploy.sh --packages-ref master

Runtime Shape

The runtime AMI is expected to provide /nix from the AMI-attached nixos-zfs-nix ZFS volume from first boot. Runtime user data creates/imports no filesystems; it is a NixOS flake consumed by the AMI's user-data-flake service.

The runtime /persist-local volume contains deployment-local Quixos workspace, secret, and package state. It uses a ZFS pool named quixos-persist with a quixos-persist/persist dataset. Runtime NixOS initializes that pool on the attached local persist EBS volume if the volume is blank, imports it if it already exists, and fails on unknown existing filesystem signatures.

The /persist mount is reserved for long-lived shared state. It uses the external shared-state EBS volume and a ZFS pool named quixos-shared with a quixos-shared/persist dataset. Caddy's data directory is /persist/var/lib/caddy, and SSH host keys live under /persist/etc/ssh, so both survive runtime tofu destroy.

The visualizer static derivation uses project-visualizer/yarn-project.nix, generated by yarn-plugin-nixify.