11 KiB
11 KiB
CLAUDE.md - Project context for AI assistants
What this repository is
A collection of peripheral tools, scripts, and snippets for working with Incus, IncusOS, and the broader ecosystem (Operations Center, Migration Manager). Primarily targeting home lab environments but aiming for production-quality scripts.
Repository structure
incu-contrib/
├── CLAUDE.md # This file -- project context
├── README.md # Main overview
├── .gitignore
├── incusos/ # IncusOS installation tooling
│ ├── README.md # Detailed usage docs
│ ├── incusos-iso # ISO/IMG builder (wraps flasher-tool)
│ ├── incusos-seed # Seed archive generator
│ ├── incusos-proxmox # Declarative Proxmox VM deployment
│ ├── lab-test # Guided lab validation (workloads, clustering, migration)
│ ├── TESTING.md # Testing guide for incusos-proxmox and lab-test
│ └── examples/ # Example seed + Proxmox YAML files
└── notes/ # Research notes and reference material
Key technical context
Incus version differences
- Debian stable ships Incus 6.0 LTS which is significantly behind upstream. The Zabbly repo (https://github.com/zabbly/incus) provides latest on Debian/Ubuntu.
- macOS (Homebrew) and Arch Linux both track latest upstream (currently 6.21). macOS is client-only by design; Arch has no client-only split package.
incus remote get-client-certificatewas added in Incus 6.3+ and does not exist in 6.0 LTS. Scripts must never depend on it as the only cert path.- Always prefer reading
~/.config/incus/client.crtdirectly from disk. Fall back to the CLI command only as a secondary option. - See
notes/incus-version-compatibility.mdfor full platform matrix and install instructions.
IncusOS flasher-tool
- Install:
go install github.com/lxc/incus-os/incus-osd/cmd/flasher-tool@latest - Actual CLI flags:
-f/--format,-s/--seed,-c/--channel,-i/--image,-v/--version - There is NO
--seed-tarflag -- it's just--seed(or-s). - There is NO
--archflag -- architecture is determined by the downloaded image. For cross-arch builds, download the image manually and pass via--image. - CDN index:
https://images.linuxcontainers.org/os/index.json - CDN images:
https://images.linuxcontainers.org/os/{version}/{arch}/IncusOS_{version}.{format}.gz
Seed archives
- Tar archives containing YAML files at the root level.
- Written to byte offset 2148532224 (the seed partition) in the image.
- Alternative: external boot media labeled
SEED_DATAattached as CD-ROM.- ISO 9660 (
genisoimage -V SEED_DATA -J -r): preferred for CD-ROM devices. Volume labels are correctly detected by the kernel on/dev/sr*. - FAT image (
mkfs.fat -n SEED_DATA): works for USB/block devices but NOT for CD-ROM -- the Linux kernel'ssr_moddriver does not expose FAT filesystem labels, so IncusOS cannot find the seed.
- ISO 9660 (
- Key files:
install.yaml,applications.yaml,incus.yaml,operations-center.yaml,network.yaml,update.yaml.
Client certificates
- Stored at
~/.config/incus/client.crtand~/.config/incus/client.key. - Running
incus remote listtriggers auto-generation if no keypair exists. - For Incus seed: injected under
preseed.certificates[](NOTpreseed.server.certificates[]). TheInitPreseed.Serverfield usesyaml:",inline"so its fields (includingcertificates) are promoted to the top level of thepreseedobject. - For Operations Center seed: injected under
trusted_client_certificates[]. - Operations Center requires at least one trusted certificate -- without it, you are locked out after installation.
Proxmox VE deployment
incusos-proxmoxreads a YAML config, generates per-VM SEED_DATA images viaincusos-seed --format iso, uploads the ISO + seeds to Proxmox, creates VMs with IncusOS-correct settings, and boots them through installation.- Connection methods: SSH (default,
ssh root@host qm ...) or API (curl -k https://host:8006/api2/json/...withPVEAPITokenheader). - Minimum API privileges for token-based access:
VM.Allocate VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.CDROM VM.Config.Options VM.Config.HWType VM.PowerMgmt VM.Audit Datastore.AllocateSpace Datastore.AllocateTemplate Datastore.Audit SDN.Use - Required VM settings (getting any wrong causes IncusOS install failure):
bios=ovmf,machine=q35-- UEFI boot requiredefidisk0:pre-enrolled-keys=0-- IncusOS enrolls its own Secure Boot keystpmstate0:version=v2.0-- required for disk encryptioncpu=host-- needed for x86_64_v3 instruction set requirementscsihw=virtio-scsi-pci+scsi0-- VirtIO-blk is broken with IncusOSballoon=0-- IncusOS manages memory internallyide3-- SEED_DATA ISO 9660 image attached as second CD-ROM- Minimum 50 GiB disk, minimum 4096 MiB RAM
- Disk target: do NOT specify
disk-targetin the seed for Proxmox VMs. IncusOS does literal string matching (not glob) on disk device IDs.scsi-*does NOT matchscsi-0QEMU_QEMU_HARDDISK_drive-scsi0. Omitdisk-targetentirely and let IncusOS auto-detect (works for single-disk VMs). - Install flow (automated by
incusos-proxmox):- Boot VM with ISO (ide2) + SEED_DATA (ide3) +
force_reboot: truein seed - IncusOS reads seed, installs to disk (scsi0), auto-reboots
- Detect install completion by polling
blockstat.scsi0.wr_bytesvia API -- when disk writes start then stop for 15s (3 stable polls), install is done - Stop the VM (Proxmox stop, not guest shutdown)
- Delete ide2 and ide3 -- IncusOS checks for install media at every boot and refuses to start if found, regardless of boot order
- Set boot order to
order=scsi0and start from disk
- Boot VM with ISO (ide2) + SEED_DATA (ide3) +
- IP detection: IncusOS is immutable and has no QEMU guest agent. Use ARP-based lookup: get MAC from Proxmox VM config → flush stale ARP → ping broadcast → look up MAC in ARP table. Verify with direct ping before trusting the result.
- force_reboot is required: without
force_reboot: truein the seed, IncusOS sits at "please remove installation media" and waits indefinitely. It does NOT halt automatically.force_reboottriggers a guest-level reboot (note: this does NOT reset QEMU VM uptime -- only a Proxmox stop/start does). - Resource pool isolation: the optional
proxmox.poolconfig field scopes all VM operations to a Proxmox resource pool. When set, the script only "sees" VMs in that pool (for collision detection and cleanup), and the API token ACL can be scoped to/pool/<name>instead of/. Setup:pveum pool add IncusLab --comment "IncusOS Lab VMs" pveum pool modify IncusLab --storage local-lvm,local pveum aclmod /pool/IncusLab -user automation@pve -role IncusOSDeploy --statuscommand:incusos-proxmox --status config.yamlshows per-VM deployment status (Proxmox state, install status, IP, port 8443, incus remote). Also runs post-deployment checks (Incus connectivity, Operations Center URL).- Reconcile on re-runs: when
--phase alldetects existing VMs from config, an interactive menu offers: (1) run status checks, (2) continue install for incomplete VMs, (3) destroy and redeploy, (4) abort. With--yes, defaults to option 1 (safe -- never auto-destroys). - Install idempotency:
phase_installchecks each VM's state before acting -- already-running VMs are skipped, stopped-but-installed VMs are started from disk, only VMs with install media proceed through installation.
Incus clustering via remotes
- Cluster formation is done entirely through the
incusCLI using remotes. No SSH to the IncusOS nodes is needed (IncusOS is immutable, no shell access). - Workflow:
# Enable clustering on first node incus cluster enable <remote1>: <member-name> # Generate join token (on cluster, for new member name) incus cluster add <remote1>: <new-member-name> # Join second node to cluster incus cluster join <remote1>: <remote2>: # Verify incus cluster list <remote1>: apply_defaults: truemust be set on all nodes that will be clustered -- this ensures matching storage pool (local) and network bridge names, which are required for migration.- After joining, the cluster is managed through the init node's remote. The individual node remotes still work for node-specific operations.
lab-testautomates cluster formation, workload testing, and migration.
Lab validation (lab-test)
lab-testreads the same YAML config asincusos-proxmoxand operates on the VM names defined there (expectsincusremotes to exist).- Phases: deploy, single (workloads), cluster, workload (on cluster), migrate (stop/move + evacuate/restore).
- Test instances use names starting with
test-for easy cleanup. - The script reports PASS/FAIL/SKIP for each test and prints a summary.
Operations Center
- CLI binary:
operations-center(installed from GitHub releases or built from source atgithub.com/FuturFusion/operations-center). - Config directory:
~/.config/operations-center/(uses same cert format as Incus: copyclient.crtandclient.keyfrom~/.config/incus/). - Provisioning workflow: create token → get provisioned ISO → deploy nodes with that ISO → nodes auto-register with OC → form cluster via OC.
- OC is under active development (v0.2.2+). Commands and APIs may change.
The
--doctorcommand onincusos-proxmoxreports whether the CLI is installed.
incusos-proxmox doctor and cleanup
--doctor: standalone environment check. No config file required. Checks tool versions, IncusOS CDN, and optionally Proxmox connectivity.--cleanup --deep: destroys VMs + deletes ISOs/seeds from Proxmox + removes incus remotes + clears local ISO cache.--cleanup-all: pool-wide cleanup. Destroys all VMs with the[incusos-lab:managed]marker in the configured resource pool.
Coding conventions for scripts
- Shell: bash with
set -euo pipefail - Arithmetic: use
var=$((var + 1))instead of((var++))to avoid false exits underset -ewhen the value is 0. - Colors: support
NO_COLOR=1andTERM=dumb; use setup_colors() pattern. - Flags: support both short (
-d) and long (--defaults) options. - Defaults: sane defaults so the script does something useful with zero flags.
- Dry run: all scripts should support
--dry-runto preview actions. - Cert detection order: files on disk first, CLI command second.
- Error messages: include actionable remediation steps, not just "failed".
- No hardcoded package managers: say "install the Incus client" with a link, not "sudo apt install incus".
Git workflow
- Main branch:
main - Development happens on feature branches
- Remote: private Gitea at
ssh://git@192.168.1.200:2222/maarten/incu-contrib.git