Execute break-fix exercises, document results and fix VMIDs

Exercises executed on 2026-02-24:
- Exercise 1 (Update Observation): observed download, A/B switch, DB-leader
  transfer during reboot. All 3 nodes updated to 202602240349.
- Exercise 3 (Network Isolation): OVN gateway is SPOF (no failover),
  east-west traffic survives, MAC change causes boot hang.
- Exercise 4 (Full Node Failure): 2/3 quorum held, ~20s detection,
  ~50s recovery, instances auto-restart.
- Exercise 2 (Simulated Failed Update): blocked (no update available).

Fixes: VMIDs corrected from 900-902 to 400-402, added MAC safety rule,
documented Proxmox API --data-urlencode requirement for MAC addresses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maarten 2026-02-24 16:51:26 +01:00
parent 95316a5bee
commit 360fba5680
2 changed files with 292 additions and 176 deletions

View File

@ -13,15 +13,16 @@ paths:
- TPM-based full disk encryption: root + swap volumes unlocked via TPM
measured boot. Secure Boot enforced with PK, KEK, and 2x db certs.
- ZFS storage pool "local" on partition 11, contains volume "incus".
- Single SCSI disk per node (64 GiB in lab, QEMU HARDDISK).
- Single SCSI disk per node (64-100 GiB in lab, QEMU HARDDISK).
- MAC-dependent boot: udev renames NIC by MAC. Changing MAC = boot hang.
## Lab cluster nodes
| Node | VMID | IP | Version |
|------|------|----|---------|
| node-01 | 900 | 192.168.102.140 | 202602240349 |
| node-02 | 901 | 192.168.102.141 | 202602230420 |
| node-03 | 902 | 192.168.102.142 | 202602230420 |
| node-01 | 400 | 192.168.102.140 | 202602240349 |
| node-02 | 401 | 192.168.102.141 | 202602240349 |
| node-03 | 402 | 192.168.102.142 | 202602240349 |
- Management + cluster traffic on interface "mgmt" (static IPs, /22 subnet).
- DNS + gateway: 192.168.100.1.
@ -36,7 +37,7 @@ All on port 8443, authenticated with Incus client certificate:
- `/os/1.0/system/storage` -- disks, partitions, ZFS pools
- `/os/1.0/system/resources` -- CPU, memory, hardware
- `/os/1.0/system/network` -- interfaces, DNS, routes
- `/os/1.0/system/update` -- channel, frequency, pending updates
- `/os/1.0/system/update` -- GET: state; PUT: change config (frequency, auto_reboot)
- `/os/1.0/services/{ovn,iscsi,lvm,multipath,nvme,tailscale,usbip}`
## incusos-health helper
@ -61,28 +62,35 @@ cluster nodes. Actions:
provisioned nodes (TPM state already sealed).
- **One node at a time** for break-fix exercises. 3-node cluster needs
2/3 quorum. Taking down 2 nodes = cluster failure.
- **Proxmox snapshots** before every destructive test (VMID range 900-939).
- **Target node-03** (VMID 902) for destructive tests -- not the leader,
- **Proxmox snapshots** before every destructive test (VMID 400-402).
Note: API token may lack `VM.Snapshot` permission.
- **Target node-03** (VMID 402) for destructive tests -- not the leader,
fewest workloads.
- **Verify health** with `incusos-health --all` before and after exercises.
- **Monitor via Grafana** during all exercises.
- **Never change MAC addresses** on IncusOS VMs. Use `--data-urlencode`
when modifying NIC config via Proxmox API.
## Update mechanics
## Update mechanics (tested)
- `os_version` = current running version.
- `os_version_next` = version on standby partition (same as current if
no update pending, different if update downloaded).
- `needs_reboot` = true when update is downloaded and waiting for reboot.
- `auto_reboot: false` in lab -- updates download but don't reboot.
- Check frequency: 6 hours, stable channel.
- Check frequency configurable via PUT. Default may be "never" on some nodes.
- POST to trigger immediate check returns 501 (not implemented).
- Boot-time update check: IncusOS checks for updates during boot sequence.
- Nodes update independently (rolling, not coordinated).
## Break-fix exercises (defined, not yet executed)
## Break-fix exercise results (2026-02-24)
1. **Normal Update Observation** -- watch download, reboot, A/B switch
2. **Simulated Failed Update** -- hard-stop node-03 mid-update, expect rollback
3. **Network Isolation** -- disconnect node-03 NIC, observe OVN tunnel loss
4. **Full Node Failure** -- hard-stop node-03, verify 2/3 quorum holds
1. **Normal Update Observation** -- Completed. Observed download, reboot,
A/B switch on node-02. Database-leader transfer seamless during reboot.
2. **Simulated Failed Update** -- Blocked (no update available to interrupt).
3. **Network Isolation** -- Completed. OVN gateway is SPOF (no failover).
East-west traffic works, north-south broken. Recovery: 26s cluster, 75s gateway.
4. **Full Node Failure** -- Completed. 2/3 quorum held. Detection: ~20s.
Recovery: ~50s to cluster rejoin. All instances auto-restarted.
See `notes/incusos-break-fix.md` for full exercise details, prerequisites,
expected behavior, and recovery steps.
See `notes/incusos-break-fix.md` for full exercise details and results.

View File

@ -2,10 +2,10 @@
IncusOS is an immutable, purpose-built operating system for running Incus
clusters. This guide documents what we discovered about its internals through
the `/os/1.0` API, and defines a catalog of break-fix exercises for testing
the `/os/1.0` API, and records the results of break-fix exercises testing
cluster resilience in a safe lab environment.
All observations come from a 3-node Proxmox-hosted cluster (VMID 900-902)
All observations come from a 3-node Proxmox-hosted cluster (VMID 400-402)
running IncusOS with virtual TPM, Secure Boot, and OVN networking.
## IncusOS Architecture
@ -28,27 +28,31 @@ Key architectural properties discovered via the `/os/1.0` API:
Example: `202602240349` = 2026-02-24 at 03:49 UTC.
- **ZFS storage**: pool "local" on a dedicated partition (raid0 on single
disk, ~30 GiB usable), encrypted with its own pool recovery key.
- **Update system**: stable channel, 6-hour check frequency,
- **Update system**: stable channel, configurable check frequency,
`auto_reboot: false` (updates download but do not reboot automatically).
- **MAC-dependent boot**: IncusOS uses udev rules to rename the network
interface by MAC address at boot. Changing the VM's MAC address will cause
`ERROR timed out waiting for udev to rename interface(s)` and a boot hang.
### Lab cluster state
### Lab cluster state (post-exercises)
| Node | VMID | IP | IncusOS Version |
|------|------|----|-----------------|
| node-01 | 900 | 192.168.102.140 | 202602240349 |
| node-02 | 901 | 192.168.102.141 | 202602230420 |
| node-03 | 902 | 192.168.102.142 | 202602230420 |
| node-01 | 400 | 192.168.102.140 | 202602240349 |
| node-02 | 401 | 192.168.102.141 | 202602240349 |
| node-03 | 402 | 192.168.102.142 | 202602240349 |
node-01 received a newer build than nodes 02/03, proving that nodes update
independently (rolling updates, not cluster-wide atomic upgrades).
All 3 nodes updated to `202602240349` during the break-fix exercises.
node-01 was already on this version; node-02 and node-03 were on
`202602230420` and updated independently (proving rolling updates work).
## Partition & Disk Layout
Each node has a single 64 GiB SCSI disk (QEMU HARDDISK) with this layout:
Each node has a single SCSI disk (QEMU HARDDISK) with this layout:
```
+------------------------------------------------------+
| SCSI Disk (64 GiB, QEMU HARDDISK) |
| SCSI Disk (64-100 GiB, QEMU HARDDISK) |
|------------------------------------------------------|
| Partition 1 EFI System Partition |
| Partition A Root filesystem (active or standby) |
@ -61,8 +65,8 @@ Each node has a single 64 GiB SCSI disk (QEMU HARDDISK) with this layout:
Key observations from the storage API:
- **Partition 11** is the ZFS data partition, hosting pool "local".
- **Pool "local"**: raid0, single vdev, ~30 GiB total, contains volume
"incus" (~10 GiB used for Incus database, images, instances).
- **Pool "local"**: raid0, single vdev, 30-66 GiB depending on disk size,
contains volume "incus" (Incus database, images, instances).
- **Root + swap**: both encrypted, both unlocked by TPM at boot.
No manual key entry needed unless TPM state is corrupted.
@ -134,40 +138,48 @@ The management interface carries both management traffic (API, cluster
heartbeats) and OVN Geneve tunnel traffic. In production, these should
be separated.
## Update Mechanics (Observed)
## Update Mechanics (Observed and Tested)
The IncusOS update system uses A/B partitions for safe, rollback-capable
updates. Observations from querying the update API:
updates. Observations from querying the update API and live testing:
1. **No pending update**: `os_version` and `os_version_next` are identical.
This means no update has been downloaded or is waiting for reboot.
2. **Pending update**: `os_version_next` would differ from `os_version`,
2. **Pending update**: `os_version_next` differs from `os_version`,
indicating a new build has been downloaded to the standby partition.
The update status shows: `"IncusOS has been updated to version YYYYMMDDHHMI"`.
3. **Auto-reboot disabled**: `auto_reboot: false` means updates download
to the standby partition but the node continues running the current
version until explicitly rebooted (or until an admin triggers reboot
via API / UI).
version until explicitly rebooted.
4. **No reboot needed**: `needs_reboot: false` confirms no downloaded
update is waiting for a reboot to activate.
4. **`needs_reboot: true`** confirms a downloaded update is waiting for
reboot to activate.
5. **Independent node updates**: node-01 is on `202602240349` while
nodes 02/03 are on `202602230420`. This proves each node checks for
and applies updates independently. There is no cluster-wide coordinated
update mechanism at the OS level.
5. **Independent node updates**: node-01 was on `202602240349` while
nodes 02/03 were on `202602230420`. Each node checks for and applies
updates independently. There is no cluster-wide coordinated update.
6. **Check frequency**: 6 hours. The stable channel is checked
automatically on this interval.
6. **Check frequency is configurable via API**: Use PUT to
`/os/1.0/system/update` with `{"config":{"check_frequency":"6h"}}`.
Default may be "never" on some nodes -- verify and set explicitly.
POST to trigger an immediate check returns 501 (not implemented).
### Update lifecycle (theoretical)
7. **Boot-time update check**: When a node reboots, IncusOS checks for
updates during the boot sequence. node-03 updated from `202602230420`
to `202602240349` during a hard-stop/restart cycle, even though the
update timer had not yet fired.
### Update lifecycle (observed)
```
Check timer fires (every 6h)
Config: check_frequency set via PUT /os/1.0/system/update
--> Timer fires (or boot-time check runs)
--> Query stable channel for new version
--> Download new rootfs to standby partition (A or B)
--> Download new rootfs to standby partition
--> os_version_next updated, needs_reboot = true
--> status = "IncusOS has been updated to version YYYYMMDDHHMI"
--> (if auto_reboot) Reboot automatically
--> (if !auto_reboot) Wait for manual reboot
--> On reboot: boot from new partition
@ -176,13 +188,12 @@ Check timer fires (every 6h)
--> If invalid: rollback to previous partition
```
## Break-Fix Exercise Catalog
---
These exercises are designed to test IncusOS cluster resilience in the
Proxmox lab. All exercises follow strict safety rules (see Safety Rules
section below).
## Break-Fix Exercise Results
**Current status**: All exercises are defined but not yet executed.
These exercises were executed on 2026-02-24 against the 3-node lab cluster.
All exercises followed the safety rules (see Safety Rules section below).
---
@ -191,27 +202,51 @@ section below).
**Goal**: Observe the full update lifecycle -- download, reboot, A/B
partition switch, version verification.
**Prerequisites**:
- Proxmox snapshot of all cluster nodes (VMID 900-902)
- Grafana monitoring active (observe cluster metrics during update)
- Verify cluster health with `incusos-health --all`
**Executed**: 2026-02-24
**Steps**:
1. Record current versions: `incusos-health --update`
2. Trigger update via Operations Center UI, or wait for 6h check interval
3. Monitor via Grafana for download activity and node state changes
4. When `needs_reboot: true`, reboot one node at a time
5. After reboot, verify new version: `incusos-health --status`
6. Confirm A/B partition switch: `incusos-health --partitions`
#### Pre-exercise state
**What to observe**:
- Download phase duration
- Reboot duration (typically 30-60s for IncusOS)
- Cluster behavior while one node reboots (2/3 quorum maintained)
- New version number in `os_version`
- Previous version still available on standby partition
| Node | Version | Check Frequency | Last Check |
|------|---------|----------------|------------|
| node-01 | 202602240349 | 6h | 2026-02-24T15:05 |
| node-02 | 202602230420 | never | never |
| node-03 | 202602230420 | never | never |
**Status**: Defined, not yet executed.
Key finding: nodes 02/03 had `check_frequency: never` by default, which
is why they never downloaded the available update. The check frequency
must be explicitly configured via the API.
#### Steps and observations
1. Changed check frequency on nodes 02/03 via API PUT to `6h`.
2. The update timer is not immediately responsive -- setting `10s` or `6h`
does not trigger an immediate check. POST returns 501 (not implemented).
3. **node-03 updated during a hard-stop/restart** (Exercise 4 triggered
the reboot). The boot-time check found and applied the update.
4. **node-02 download observed**: After setting frequency to `6h`, the
node eventually checked and downloaded the update:
- Status: `"IncusOS has been updated to version 202602240349"`
- `os_version: 202602230420`, `os_version_next: 202602240349`
- `needs_reboot: true`
5. Rebooted node-02 (graceful, via Proxmox API). The database-leader
role seamlessly transferred during the reboot.
6. Post-reboot: node-02 running `202602240349`, `needs_reboot: false`.
#### Timing
| Event | Duration |
|-------|----------|
| node-02 reboot (offline → online) | ~50s |
| Database-leader role transfer | seamless (no cluster disruption) |
| All instances on node-02 auto-restart | within boot time |
#### Result
All 3 nodes successfully updated to `202602240349`. The A/B partition
scheme works as documented -- updates download to the standby partition
and activate on reboot.
**Status**: Completed successfully.
---
@ -220,35 +255,18 @@ partition switch, version verification.
**Goal**: Verify that IncusOS rolls back to the previous partition after
a failed update (simulated by hard-stopping the VM during update).
**Prerequisites**:
- Proxmox snapshot of node-03 (VMID 902) -- non-leader, fewest workloads
- Verify node-03 has no critical workloads
- Confirm 3/3 nodes healthy before starting
**Status**: Not executable. All 3 nodes were already on the latest
version (`202602240349`) after Exercises 1 and 4. No pending update
was available to interrupt.
**Target node**: node-03 (VMID 902) ONLY. Never the cluster leader.
**Steps**:
1. Take Proxmox snapshot of node-03: `incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/snapshot -d '{"snapname":"pre-break-fix-2"}'`
2. Trigger an update on node-03 (if one is available)
3. During the update download or early reboot phase, hard-stop node-03
via Proxmox API: `incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/stop`
4. Wait 10 seconds, then start node-03:
`incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/start`
5. Monitor boot via console screenshots: `incusos/helpers/proxmox-screenshot 902`
6. After boot, check version: `incusos-health --status` on node-03
**Expected behavior**:
- Node boots from the previous (known-good) partition
- Failed partition is marked as bad / not bootable
- `os_version` returns to the pre-update version
- Node rejoins the cluster automatically
**Safety**:
- ONLY node-03 -- maintains 2/3 quorum (node-01 + node-02 continue)
- Do NOT hard-stop during *first boot* (corrupts TPM permanently)
- If recovery fails, restore from Proxmox snapshot
**Status**: Defined, not yet executed.
**To execute in the future**: Wait for a new IncusOS build to be
published to the stable channel, then:
1. Take Proxmox snapshot of node-03 (VMID 402)
2. Trigger update check (change frequency, wait for download)
3. During early reboot phase (after download, during boot), hard-stop
the VM via `proxmox-api POST /nodes/pve/qemu/402/status/stop`
4. Start the VM and observe which partition boots (A or B)
5. Check `os_version` -- should be the pre-update version if rollback worked
---
@ -258,34 +276,74 @@ a failed update (simulated by hard-stopping the VM during update).
connectivity -- OVN tunnel loss, cluster membership changes, and
automatic recovery on reconnection.
**Prerequisites**:
- Evacuate all workloads from node-03 before disconnecting
- Grafana monitoring active (watch OVN tunnel metrics, cluster events)
- Verify cluster health: `incusos-health --all`
**Executed**: 2026-02-24
**Steps**:
1. Evacuate workloads from node-03:
`incus cluster evacuate oc-node-03 --target oc-node-01`
2. Disconnect node-03 NIC via Proxmox API:
`incusos/helpers/proxmox-api PUT /nodes/pve/qemu/902/config -d '{"net0":"virtio=...,link_down=1"}'`
3. Observe in Grafana: OVN Geneve tunnel loss, cluster detecting missing node
4. Wait 2-5 minutes for cluster to mark node-03 as offline
5. Reconnect NIC: set `link_down=0` via Proxmox API
6. Monitor recovery: node-03 should rejoin cluster, OVN tunnels re-establish
7. Measure total recovery time from reconnection to healthy state
#### Pre-exercise state
**Expected behavior**:
- Cluster detects node-03 offline within heartbeat timeout
- OVN tunnels from/to node-03 fail (Geneve encap packets lost)
- Cluster continues operating with 2/3 quorum
- On reconnection: node-03 rejoins, tunnels re-establish, workloads
can be restored
All 3 nodes healthy, 20 instances running. node-03 hosted 6 instances
including `ovn-central` and `node-exp-03`.
**Recovery**:
- If node-03 does not rejoin: check OVN service status, restart if needed
- If cluster state is inconsistent: restore from Proxmox snapshot
#### Steps and observations
**Status**: Defined, not yet executed.
1. **Evacuated workloads** from node-03 via `incus cluster evacuate`.
3 instances migrated to nodes 01/02, 3 stopped in place.
2. **Disconnected NIC** via Proxmox API:
```
proxmox-api PUT /nodes/pve/qemu/402/config \
--data-urlencode 'net0=virtio=BC:24:11:11:6E:F9,bridge=vmbr0,tag=69,link_down=1'
```
3. **Cluster detection**: node-03 detected as OFFLINE within ~20s of
NIC disconnect. Heartbeat message shows exact last heartbeat timestamp.
4. **Critical finding -- OVN gateway is a SPOF**: The OVN router's
external gateway was scheduled on node-03's chassis. When node-03
lost network:
- **East-west traffic** (container ↔ container on OVN): **still worked**
- **North-south traffic** (OVN ↔ physical LAN): **completely broken**
- Grafana from LAN: unreachable (OVN forward goes through gateway)
- Monitoring → management network (SNAT): broken
- Monitoring → other OVN containers (10.10.10.x): still working
- **No gateway failover occurred** even after 3+ minutes of waiting.
This is a single point of failure in the current OVN configuration.
5. **NIC reconnection gotcha**: Using Proxmox `PUT /config` with `-d`
flag regenerates the MAC address because curl interprets `:` in the
MAC as URL parameters. **Must use `--data-urlencode`** to preserve
the MAC address. Changing the MAC causes IncusOS boot failure:
`ERROR timed out waiting for udev to rename interface(s)`
6. **Recovery after correct MAC restored**:
- Cluster rejoin: ~26s after VM start
- OVN gateway recovery: ~75s (north-south traffic restored)
#### Key findings
| Metric | Value |
|--------|-------|
| Detection time | ~20s (heartbeat timeout) |
| OVN east-west during isolation | Working |
| OVN north-south during isolation | Broken (no failover) |
| Cluster quorum | 2/3 maintained |
| Recovery (cluster rejoin) | ~26s |
| Recovery (OVN gateway) | ~75s |
#### Lessons learned
- **OVN gateway HA**: The OVN logical router gateway chassis does not
automatically failover when a node goes offline. In production, this
would need to be addressed with gateway chassis groups or redundant
uplinks. This is likely an Incus OVN configuration issue, not an
IncusOS limitation.
- **Proxmox NIC manipulation**: Use `--data-urlencode` for any Proxmox
API PUT that includes MAC addresses. The `-d` flag with raw data
corrupts the MAC, regenerating a new one.
- **IncusOS MAC dependency**: The boot process uses udev rules tied to
the NIC's MAC address. A MAC change = boot failure. This is important
for VM migration, NIC replacement, or Proxmox config changes.
**Status**: Completed with significant findings.
---
@ -295,40 +353,71 @@ automatic recovery on reconnection.
maintains operations with 2/3 quorum. Measure cluster rejoin time
after node recovery.
**Prerequisites**:
- Proxmox snapshot of node-03 (VMID 902)
- Evacuate ALL workloads from node-03
- Verify cluster health: `incusos-health --all`
**Executed**: 2026-02-24
**Steps**:
1. Evacuate workloads: `incus cluster evacuate oc-node-03`
2. Take Proxmox snapshot of node-03
3. Hard-stop node-03 via Proxmox:
`incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/stop`
4. Verify cluster continues operating:
- `incus cluster list` should show 2 online, 1 offline
- Existing workloads on node-01/02 remain accessible
- OVN gateway should failover if node-03 was a gateway
5. Wait 5 minutes, observing Grafana metrics
6. Restart node-03:
`incusos/helpers/proxmox-api POST /nodes/pve/qemu/902/status/start`
7. Monitor boot: `incusos/helpers/proxmox-screenshot 902`
8. Measure time from start to cluster rejoin (node shows online)
9. Restore workloads: `incus cluster restore oc-node-03`
#### Pre-exercise state
**Expected behavior**:
- 2/3 quorum maintained -- all cluster operations continue
- OVN gateway failover occurs if node-03 was elected gateway
- After restart: node-03 boots, TPM unlocks, Incus starts, rejoins cluster
- Typical rejoin time: 1-3 minutes after boot
All 3 nodes healthy. node-03 hosted 6 instances. No evacuation was
performed before the hard-stop (to test realistic failure behavior).
**Safety**:
- This is NOT a first-boot scenario -- hard-stop is safe for already-
provisioned nodes (TPM state is already sealed)
- One node at a time ONLY
- If node-03 fails to rejoin after restart, restore snapshot
#### Steps and observations
**Status**: Defined, not yet executed.
1. **Hard-stopped node-03** via Proxmox API at 16:12:19 CET:
```
proxmox-api POST /nodes/pve/qemu/402/status/stop
```
2. **Detection**: Cluster marked node-03 OFFLINE within ~20s. The status
message includes the exact last heartbeat timestamp:
`No heartbeat for 26.79s (2026-02-24 15:12:10 UTC)`
3. **Cluster behavior with node-03 down**:
- 2/3 quorum maintained -- node-01 and node-02 "Fully operational"
- All 14 instances on nodes 01/02 remained RUNNING
- 6 instances on node-03 went to ERROR state
- `incus cluster list`, `incus list`, `incus exec` all continued working
- Storage pool listing worked normally
- **OVN operations continued** despite `network.ovn.northbound_connection`
pointing to node-03 (192.168.102.142:6641). The Incus database layer
handles OVN NB database failover transparently.
4. **Prometheus impact**: 7/9 targets UP (node-03 Incus metrics + node-exp-03 down)
5. **Grafana**: accessible from LAN (monitoring container on node-02)
6. **Restarted node-03** at 16:17:17 CET. Recovery timeline:
- +50s: Cluster shows node-03 "Fully operational"
- All 6 instances on node-03 auto-restarted (RUNNING within 1-2 min)
- 9/9 Prometheus targets back to UP
7. **Bonus finding**: node-03 updated from `202602230420` to `202602240349`
during the reboot! IncusOS performs an update check at boot time,
found the newer version, and activated it.
#### Timing
| Event | Time |
|-------|------|
| Hard-stop → OFFLINE detection | ~20s |
| Cluster operations during failure | Fully functional (2/3 quorum) |
| VM start → cluster rejoin | ~50s |
| VM start → all instances RUNNING | ~2 min |
| VM start → 9/9 Prometheus targets | ~3 min |
#### Key findings
- Hard-stop of a non-leader node is safe (TPM state preserved for
already-provisioned nodes, as expected).
- Cluster quorum with 2/3 nodes provides full operational capability.
- Instance auto-restart on node recovery is automatic -- no manual
intervention needed.
- OVN NB database failover is transparent (even when the configured
connection target is the downed node).
- Boot-time update checks can apply pending updates during recovery.
**Status**: Completed successfully.
---
## Safety Rules
@ -343,8 +432,9 @@ These rules are non-negotiable for all break-fix exercises:
quorum and all operations halt.
3. **Proxmox snapshots before every destructive test.** Snapshot the
target node's VM (VMID 900-939) before any exercise that involves
stopping, disconnecting, or modifying the node.
target node's VM (VMID 400-402) before any exercise that involves
stopping, disconnecting, or modifying the node. Note: the API token
may lack `VM.Snapshot` permissions -- verify before relying on this.
4. **Verify cluster health before and after.** Run `incusos-health --all`
(or equivalent API checks) before starting an exercise and after
@ -354,7 +444,7 @@ These rules are non-negotiable for all break-fix exercises:
issues that API polling might miss (e.g., OVN tunnel flapping,
storage I/O spikes).
6. **Target node-03 for destructive tests.** node-03 (VMID 902) is the
6. **Target node-03 for destructive tests.** node-03 (VMID 402) is the
preferred target because it is not the cluster leader and typically
has the fewest workloads. Evacuate before testing.
@ -362,6 +452,11 @@ These rules are non-negotiable for all break-fix exercises:
ZFS pool recovery key should be retrieved and stored securely before
any exercise that might corrupt TPM state.
8. **Never change MAC addresses on IncusOS VMs.** IncusOS uses udev
rules tied to the NIC MAC. Changing the MAC causes a boot hang.
When using Proxmox API for NIC operations, always use
`--data-urlencode` to preserve the MAC address.
## Helper Script: incusos-health
The `incusos/helpers/incusos-health` script queries the IncusOS API on
@ -401,21 +496,34 @@ incusos/helpers/incusos-health --all # Full health report
All IncusOS system information is available via the REST API on each node.
| Endpoint | Returns |
|----------|---------|
| `/os/1.0` | Version, hostname, basic system info |
| `/os/1.0/system/security` | TPM status, Secure Boot, encryption keys |
| `/os/1.0/system/storage` | Disks, partitions, ZFS pools |
| `/os/1.0/system/resources` | CPU, memory, hardware info |
| `/os/1.0/system/network` | Interfaces, DNS, routes |
| `/os/1.0/system/update` | Update channel, version, pending updates |
| `/os/1.0/services/ovn` | OVN configuration and status |
| `/os/1.0/services/iscsi` | iSCSI configuration |
| `/os/1.0/services/lvm` | LVM configuration |
| `/os/1.0/services/multipath` | Multipath configuration |
| `/os/1.0/services/nvme` | NVMe-oF configuration |
| `/os/1.0/services/tailscale` | Tailscale VPN configuration |
| `/os/1.0/services/usbip` | USB/IP configuration |
| Endpoint | Method | Returns |
|----------|--------|---------|
| `/os/1.0` | GET | Version, hostname, basic system info |
| `/os/1.0/system/security` | GET | TPM status, Secure Boot, encryption keys |
| `/os/1.0/system/storage` | GET | Disks, partitions, ZFS pools |
| `/os/1.0/system/resources` | GET | CPU, memory, hardware info |
| `/os/1.0/system/network` | GET | Interfaces, DNS, routes |
| `/os/1.0/system/update` | GET | Update channel, version, pending updates |
| `/os/1.0/system/update` | PUT | Change update config (check_frequency, auto_reboot, channel) |
| `/os/1.0/services/ovn` | GET | OVN configuration and status |
| `/os/1.0/services/iscsi` | GET | iSCSI configuration |
| `/os/1.0/services/lvm` | GET | LVM configuration |
| `/os/1.0/services/multipath` | GET | Multipath configuration |
| `/os/1.0/services/nvme` | GET | NVMe-oF configuration |
| `/os/1.0/services/tailscale` | GET | Tailscale VPN configuration |
| `/os/1.0/services/usbip` | GET | USB/IP configuration |
The API listens on the management interface, HTTPS, port 8443 (same as
Incus). Authentication uses the Incus client certificate.
### Update config via API
```bash
# Change check frequency (accepted values: "never", "1h", "6h", "12h", "24h")
curl -sk --cert "$CERT" --key "$KEY" -X PUT \
https://NODE_IP:8443/os/1.0/system/update \
-H "Content-Type: application/json" \
-d '{"config":{"auto_reboot":false,"channel":"stable","check_frequency":"6h"}}'
# Note: POST to trigger immediate check returns 501 (not implemented)
```