diff --git a/PLAN.md b/PLAN.md index 67f5c19..ad619ce 100644 --- a/PLAN.md +++ b/PLAN.md @@ -20,6 +20,7 @@ stub tab or a roadmap item. | Repo | Gitea `mitchell/hamlab`, private, `main` | Created 2026-09-17. | | Guest logins | A **guest** is an account like any other, with `guest: true` and an expiry: `login`, sessions and prefs need no special case, and **it may do everything an account may do**. The expiry is the whole feature — checked on every request and swept once a minute — with revoking as the manual version of it. | Built 2026-09-27. A read-only version was built first and **cut the same day**: Mitchell hands these to people he trusts and revokes them otherwise, so a console with half its tabs missing was solving a problem he doesn't have. Consequences, stated once and accepted: a guest has the Terminal tab, which is a root-capable shell on every host, and can make an account or token of its own that outlives the guest login. The one thing a guest is not is *an account*: it is left out of the Users list, out of "the last account can't be removed", and out of the single account an empty user name resolves to. | | Machines | **A view shows one machine: the one in the switcher.** No hub-side page lists or aggregates what its nodes are doing. To see or change anything on another machine, switch to it. | Mitchell, 2026-09-25, cutting a fleet-wide updates list out of the Docker tab. The switcher is the only place a machine is chosen, so there is exactly one answer to "which machine am I looking at" — a page that quietly reaches across breaks that, and it is never obvious which machine an action would land on. The hub still *proxies* for a node, and hub-only admin (Machines, Users, Settings, Alerts) is unaffected. | +| Capability, not policy | **A tab offers what the selected machine can do, detected live — never what a rule says should run there.** Where the software is missing but installable, Ham Lab installs it; where it cannot be installed or the hardware lacks it, the tab says so in a sentence and offers what does work. Every action lands on the machine in the switcher. | Mitchell, 2026-10-09, after the Virtualization plan's first draft excluded the Mac mini and `hamtv` on the strength of `hamlet/CLAUDE.md`'s deployment rules: *"I want the option to do it anywhere I want, unless the hardware / software literally doesn't support it. Ham Lab needs to be built in a general-purpose way that can run on any Linux system."* Those rules bind the agent's unasked deployments, not the product. | ## Layout @@ -786,11 +787,13 @@ what it has moved. free. - **A Virtualization tab** — asked for 2026-09-23; **scoped 2026-10-09 in `VIRTUALIZATION.md`**, - not started. Incus 6.0 LTS on `grid` (the only sensible VM host), containers and VMs in one - list with console/shell through the terminal machinery, snapshots, exports, and the old - Proxmox estate on `tank` as a later import. That file has the host probe, the decisions - (NVMe pool, `192.168.200.0/24` bridge because the LAN is a /8, the Docker `FORWARD` fix, - ARC cap) and the phased build. + not started. Incus 6.0 LTS as the engine; the tab detects per machine whether Incus is + running, installable, or unavailable, and whether KVM is there, and acts on the machine in + the switcher — never a default host. Containers and VMs in one list, console/shell through + the terminal machinery, snapshots, exports, and the old Proxmox estate on `tank` as a later + import. That file has the probe of every host, the per-machine layout Ham Lab computes + (pool driver, a bridge subnet that avoids the LAN's /8 and Docker's pools, the Docker + `FORWARD` fix) and the phased build. ## Open questions diff --git a/VIRTUALIZATION.md b/VIRTUALIZATION.md index 0bbea0b..b704486 100644 --- a/VIRTUALIZATION.md +++ b/VIRTUALIZATION.md @@ -1,63 +1,109 @@ -# Virtualization tab — plan (2026-10-09, not started) +# Virtualization tab — plan (2026-10-09, revised the same day; not started) A tab for creating, watching, consoling into and deleting **LXC containers and KVM virtual -machines**, the way Proxmox does with `pct` and `qm` — per machine, like every other page. -This is the scoping PLAN.md asked for. Nothing here is built. Live state of the hosts -stays in `hamlet/docs/hosts.md`. +machines**, the way Proxmox does with `pct` and `qm` — **per machine, like every other +page.** This is the scoping PLAN.md asked for. Nothing here is built. Live state of the +hosts stays in `hamlet/docs/hosts.md`. -## Decision: Incus 6.0 LTS, on `grid` first +## The governing rule: capability, not policy + +Stated by Mitchell 2026-10-09, after the first draft of this plan excluded hosts on the +strength of the homelab's deployment rules: + +> Anything that's done when a different machine is selected SHOULD GO ON THAT machine. I +> want the option to do it anywhere I want, unless the hardware / software literally +> doesn't support it. Ham Lab needs to be built in a general-purpose way that can run on +> any Linux system. If certain parts / libraries aren't available on that machine, those +> tabs should just report that. + +So: + +- **Every action lands on the machine in the switcher.** There is no "VM host", no default + machine, no cross-machine placement. `grid` is where he will most likely click first; + the tab does not know or care. +- **The tab is gated by what the machine can do, detected live** — never by a rule about + what *should* run there. The `hamlet/CLAUDE.md` do-not-break rules (Mac mini for + single-core workloads, `hamtv` not spare hardware) bind the *agent's* unasked + deployments. They are not feature gates for the product, and the first draft was wrong + to treat them as such. +- **Ham Lab owns the install and the data layout.** It detects what is missing, offers to + install it where it knows how, chooses and records where instance storage lives and which + subnet the bridge takes, and runs the backups. Where it cannot install or the hardware + cannot do the job, it says so in one sentence and offers what *does* work (containers + without KVM, for instance). + +## Decision: Incus 6.0 LTS as the engine | Option | Verdict | |---|---| -| **Incus** (LXD fork, linuxcontainers.org) | **Chosen.** One daemon, one CLI and one JSON API for both containers and VMs. In Debian 13 as `incus` 6.0.4 LTS — `apt install incus` pulls `qemu-system-x86`, `swtpm`, `virtiofsd`, `incus-agent`, so VMs work out of the box. Fedora 42 has 6.23 for the Mac mini if that is ever wanted. Native ZFS storage driver (instant snapshots and clones), built-in image server (`images:debian/13`, `images:ubuntu/24.04`, Alpine, Fedora, Arch… as container *or* VM), snapshot schedules with expiry, `incus export` backups, per-instance CPU/memory limits, cloud-init, text and VGA console over websocket. Everything is reachable as `incus … --format json` or `incus query`, which is exactly how Ham Lab already drives Docker: `exec.Command`, no new Go dependency. | +| **Incus** (LXD fork, linuxcontainers.org) | **Chosen.** One daemon, one CLI and one JSON API for both containers and VMs. Packaged by Debian 13 (`incus` 6.0.4 LTS — `apt install incus` pulls `qemu-system-x86`, `swtpm`, `virtiofsd`, `incus-agent`, so VMs work out of the box), Fedora (6.23), Arch, Alpine, Ubuntu (Zabbly repo); not by OpenWrt. Storage drivers for ZFS, btrfs, LVM and plain directories, so it fits a machine with or without ZFS. Built-in image server (`images:debian/13`, `images:ubuntu/24.04`, Alpine, Fedora, Arch… as container *or* VM, per architecture), snapshot schedules with expiry, `incus export` backups, per-instance CPU/memory limits, cloud-init, text and VGA console over websocket. Everything is reachable as `incus … --format json` or `incus query`, which is exactly how Ham Lab already drives Docker: `exec.Command`, no new Go dependency, no cgo. | | libvirt/KVM + `virsh`, plus LXC separately | Two daemons, two models, XML. libvirt's official Go binding needs cgo, which the static binary rules out (the pure-Go `go-libvirt` exists but then containers are a second project). Rejected. | -| Proxmox itself | Means reinstalling `grid`'s OS. No. | +| Proxmox | An OS, not a library. Rejected. | -## Where it can run +## What each machine can do today -Probed 2026-10-09 (`/dev/kvm`, cpu flags, `kvm_intel`/`kvm` modules, RAM, disk). +Probed 2026-10-09 (`/dev/kvm`, cpu flags, kvm modules, RAM, disk, package availability). +These are **facts the tab will detect at runtime**, not a list of where instances are +allowed. The last column is what the tab would show the day it ships. -| Host | KVM | RAM | Verdict | -|---|---|---|---| -| `grid` | yes — i7-11700T, VT-x, `kvm_intel` loaded, nested on | 62 GiB; ZFS ARC is at 23 GiB and allowed 40 | **The VM host.** 790 GiB free on the NVMe root, 4.5 TiB free on `tank`. Cap the ARC at 16 GiB and ~30 GiB is free for guests with headroom for Immich and the encode jobs. | -| `core` | yes — N100, `kvm_intel` loaded | 7.5 GiB, ~4 GiB available | Small LXC containers only, never a VM. Second host, later. | -| Mac mini | yes — Asahi KVM, `/dev/kvm` present, 16K pages | 15 GiB, 8 GiB available | Technically workable (Fedora ships Incus 6.23 and `qemu-system-aarch64`; arm64 guests only, no x86 VMs; 4K-page guests on the 16K host are what `muvm` already does). **Excluded by the rule: single-core-performance workloads only.** | -| `hamtv` | yes | 3.7 GiB | In service for HamTV. Excluded. | -| `gate` | **no** `/dev/kvm` (OpenWrt) | 8 GiB | Excluded. The tab says "no virtualisation on this machine". | +| Machine | Arch | KVM | RAM (free) | Incus from the package manager | Pool driver available | Tab state on first open | +|---|---|---|---|---|---|---| +| `grid` | x86-64 | yes — VT-x, `kvm_intel` loaded, nested on | 62 GiB (~25; ARC holds 23 of the rest, max 40) | apt: `incus` 6.0.4 | **zfs** (2.3.9, `tank`) | **Install** — containers and VMs | +| `core` | x86-64 | yes — N100, `kvm_intel` loaded | 7.5 GiB (~4) | apt: `incus` 6.0.4 | btrfs or dir (ZFS was purged 2026-09-23) | **Install** — containers and VMs; the create form shows free memory so a VM's size is an informed choice | +| Mac mini | aarch64 | yes — Asahi KVM, 16K pages | 15 GiB (~8) | dnf: `incus` 6.23, `qemu-system-aarch64-core`, `edk2-aarch64` | btrfs or dir | **Install** — containers and VMs, **arm64 images only** (the picker filters by host arch). Whether a stock 4K-page guest kernel boots under the 16K host is untested; `muvm` does exactly that, so it is expected to. | +| `hamtv` | x86-64 | yes | 3.7 GiB (~2.9) | apt: `incus` 6.0.4 | btrfs or dir | **Install** — containers and small VMs | +| `gate` | x86-64 | **no** `/dev/kvm` | 8 GiB (~6) | **none** — OpenWrt's opkg has no Incus | — | **Unavailable**: "Incus isn't packaged for OpenWrt, and this kernel has no KVM." Nothing to install. | Nothing is installed anywhere today: no qemu, libvirt, lxc or incus on any host. +### Detection the tab runs, in order + +1. `incus` on `PATH` and its socket answering → **ready**; read pools, networks, instances. +2. Else: a package manager Ham Lab knows (apt, dnf, pacman, apk) that has an `incus` + candidate → **installable**, with the Install dialog pre-filled (below). +3. Else → **unavailable**, with the reason in one sentence (no package; no root; OpenWrt). +4. Independently: `/dev/kvm` present and openable → VMs offered. Absent → **containers + only**, said once on the page, and the create form has no VM option. +5. Architecture from `uname -m`, so the image picker only shows images that can run here. +6. Pool driver: `zfs` if the module and tools are present, else `btrfs` (installing + `btrfs-progs` as part of Install), else `dir`. The dialog says which it picked and why. + ## What the tab does (v1) Mirrors the Docker tab's shape: a list, a detail page, actions that are followed tasks, destructive ones confirmed by typing the name. -- **Instances** — containers and VMs in one list: state, IPv4, CPU and memory live, disk, - snapshots count, uptime. Badge for type. Click for detail. -- **Create** — pick container or VM; image from the `images:` remote (searchable, with the - handful most people want pinned: Debian 13, Ubuntu 24.04, Alpine, Fedora); name, CPUs, - memory, root disk size; network (NAT bridge, or LAN once `br0` exists — below); optional - cloud-init user-data (SSH key, packages, a first command); profile. Create, or create and - start. Collisions (name taken) named before Create. +- **Install** — one task that runs *on the selected machine*: install the package(s), + `incus admin init --preseed` with the layout from the dialog, create the pool and bridge, + add Ham Lab's user to `incus-admin` (socket access, the `docker`-group shape) and add it + to the unit's `SupplementaryGroups`, apply the Docker fix where Docker is present (below), + then restart Ham Lab. Idempotent, so it doubles as "repair". The dialog shows and lets him + edit: pool driver (detected), pool size (proposed from free space), pool path, bridge + subnet (computed), whether to apply the Docker rules. +- **Instances** — containers and VMs in one list: type, state, IPv4, CPU and memory live, + disk, snapshot count, uptime. Click for detail. +- **Create** — container or VM (VM only where KVM is); image from the `images:` remote, + filtered to this architecture, searchable, with the common ones pinned (Debian 13, Ubuntu + 24.04, Alpine, Fedora); name, CPUs, memory (with this machine's free memory beside it), + root disk size; network (the NAT bridge, or a LAN bridge if this machine has one); + optional cloud-init user-data (SSH key, packages, a first command); profile. Create, or + create and start. Collisions (name taken) named before Create. - **Lifecycle** — start, stop, restart, force stop, rename, delete (type the name; the instance's snapshots go with it, and the dialog says so). -- **Console** — two buttons, both open as tabs in the Terminal page through the existing - PTY manager (`term.CreateCmd`, the `docker exec` path): **Console** (`incus console`, - the serial console — works before the network is up, shows boot output) and **Shell** - (`incus exec -- login shell`). Works for containers and, via `incus-agent`, VMs. +- **Console** — two buttons, both open as tabs in the Terminal page through the existing PTY + manager (`term.CreateCmd`, the `docker exec` path): **Console** (`incus console`, the + serial console — works before the network is up, shows boot output) and **Shell** + (`incus exec -- login shell`). Containers and, via `incus-agent`, VMs. - **Snapshots** — take, restore (confirmed), delete; a **schedule** per instance with an expiry, which is Incus's own `snapshots.schedule`/`snapshots.expiry`, so no cron file of ours this time. - **Resources** — edit CPU and memory limits (live for containers; VMs take CPU hotplug and - memory balloon live with the agent, else on restart), grow the root disk, add a bind - mount (a `tank` path into a container; virtiofs into a VM). + memory balloon live with the agent, else on restart), grow the root disk, add a bind mount + (a host path into a container; virtiofs into a VM). - **Images** — what is cached locally, delete, aliases; **publish** a stopped instance as an image (Proxmox's "convert to template"). - **Storage and networks** — the pools with their fill, the bridges with their subnets. - Read-only in v1 beyond what Create needs. -- **Not installed here** — a host with KVM but no Incus gets an **Install** button that - runs the whole phase-0 recipe below as a task (the smartmontools-install precedent). A - host with no KVM gets one sentence. + Read-only in v1 beyond what Create and Install need. - **Sensors** — `instance..cpu`, `.memory`, `.running` from Incus's state, like the `container.*` sensors, so Alerts gets an "instance down" default and widgets can show one. @@ -67,41 +113,48 @@ destructive ones confirmed by typing the name. |---|---| | **VGA console in the browser** | Incus hands the VM's SPICE socket over a websocket; drawing it needs `spice-html5` in the UI. Doable (the Incus UI does it), but text console + shell cover every Linux guest. Phase 3. | | **Windows VMs** | Incus runs them (ISO repacked with virtio drivers by `distrobuilder`), but without a VGA console in the browser they are unusable from the tab. After the above. | -| **GPU passthrough to a VM** | Takes the Arc A380 from the host, and from Jellyfin. No. Sharing the GPU into a *container* (`gpu` device) is fine and is how `r720-jellyfin` used to run. Phase 3. | -| **Clustering / live migration** | One real host; the others are different sizes and one is arm64; no shared storage. Not planned. | +| **GPU passthrough to a VM** | Removes the card from the host and from whatever uses it there (Jellyfin on `grid`). Offered only with that warning, later. Sharing a GPU into a *container* (`gpu` device) leaves the host its card and is how `r720-jellyfin` used to run. Phase 3. | +| **Clustering / live migration** | Incus clusters, but these machines differ in size and one is arm64, with no shared storage. Not planned. | | **A fleet-wide instance list** | One machine per view. The switcher is the machine. | -| **Mac mini, `hamtv`, `gate`** | Above. | +| **A second engine (bare LXC on OpenWrt, libvirt-only hosts)** | Would double the backend for one router that has no KVM. The tab reports Incus unavailable there and stops. | -## Decisions baked in (say if any is wrong) +## Layout decisions Ham Lab makes per machine (overridable in the Install dialog) -1. **Storage: instances on `grid`'s NVMe, data on `tank`.** A ZFS pool for Incus made as a - 300 GiB loop file on the NVMe root (`incus storage create default zfs size=300GiB`) — fast - enough for VM root disks where raidz2 spinners are not, and still ZFS so snapshots and - clones are instant. Big data is bind-mounted in from `tank`, the same split Docker already - has (apps on `core`, data on `grid`). A second pool on `tank/incus` can be added later for - a guest that genuinely wants terabytes. **Consequence:** `grid`'s root NVMe is not backed - up anywhere (backups land *on* `grid`), so instances need the nightly export below. -2. **Network: NAT bridge first, pinned to `192.168.200.1/24`.** Incus's default picks a - random `10.x.y.0/24`, and **the LAN here is `10.0.0.0/8`** — every host has a /8 on its - NIC, so any 10.x bridge collides. `192.168.200.0/24` is clear of Docker's pools on both - hosts (`172.28.0.0/14` on `grid`; `172.19`–`172.29` and `192.168.80`–`143` on `core`). - Instances get `name.incus` DNS from Incus's dnsmasq. **LAN addresses in phase 2:** a - `br0` on `grid`'s `enp10s0` (the 10 GbE uplink) so an instance can take a lease from - `gate` and a Pi-hole name. That rewrites `/etc/network/interfaces` and drops `grid` off - the network for seconds — its own step, with the NanoKVM standing by. macvlan was - considered and rejected: no host network change, but host and guests cannot talk, so - Immich/Jellyfin/backups on `grid` could never reach an instance. -3. **Docker and Incus on one host:** Docker sets the `FORWARD` policy to `DROP`, which - silently kills the Incus bridge's traffic. Fix is two `DOCKER-USER` accept rules for - `incusbr0`, persisted — part of the install recipe, on `grid` and later `core`. -4. **ARC cap on `grid`:** `zfs_arc_max` from 40 GiB to 16 GiB so guest RAM is deterministic. -5. **Privilege:** `core` joins `incus-admin` (socket access, the `docker`-group shape) and - `hamlab.service` gets `SupplementaryGroups=incus-admin`; no sudo in the hot path. -6. **Backups:** nightly `incus export --instance-only` of every instance to - `/tank/backups/incus/` with a retention, as a cron line like the ZFS schedules - (`hamlab incus-export`), from phase 2. Plus the instance's own snapshot schedule. +1. **Storage.** A pool on the machine's own disk, driver by the detection above, as a + loop-backed image where the driver needs one (`incus storage create default zfs + size=…`) — sized from free space, shown and editable. Bulk data is **bind-mounted in**, + not stored in the pool: on `grid` that means `/tank/…` paths into instances, the same + split Docker already has. A second pool on an existing dataset (`tank/incus`) is one + extra command if a guest wants terabytes. The pool's path and driver are recorded in + Ham Lab's state so the page can say where the data is. +2. **Bridge subnet, computed.** Incus's `auto` picks a random `10.x.y.0/24`, and **this + LAN is `10.0.0.0/8`** — every host has a /8 on its NIC, so any 10.x bridge collides. + Ham Lab reads the machine's routes and picks the first free /24 from `192.168.0.0/16`, + then `172.16.0.0/12`, that is neither on-link nor routed nor inside Docker's configured + pool (`172.28.0.0/14` on `grid`; `172.19`–`172.29` and `192.168.80`–`143` live on + `core`). Instances get `name.incus` DNS from Incus's dnsmasq. +3. **LAN addresses are a later, explicit step.** A `br0` on the machine's uplink lets an + instance take a lease from `gate` and a Pi-hole name, but it rewrites the host's network + config and drops it off the network for seconds — never part of Install, always its own + confirmed action (with the NanoKVM standing by on `grid`). macvlan was considered and + rejected: no host change, but host and guests cannot talk, so nothing on the host could + reach an instance. +4. **Docker and Incus on one host.** Docker sets the `FORWARD` policy to `DROP`, which + silently kills the Incus bridge's traffic. Fix is two `DOCKER-USER` accept rules for the + bridge, persisted — applied by Install wherever Docker is detected (`grid`, `core`, the + Mac, `hamtv` all run it). +5. **Memory.** A VM's memory is a hard reservation; the create form shows the machine's + free memory and warns past it, and never refuses. On a ZFS host Install offers to cap + the ARC (on `grid`, 40 GiB → 16 GiB) so guest RAM is deterministic; his choice. +6. **Privilege.** Ham Lab's user joins `incus-admin`; no sudo in the hot path. Install + itself goes through `priv` like the other installers. +7. **Backups.** Nightly `incus export --instance-only` of every instance to a per-machine + export directory with a retention, as a cron line like the ZFS schedules + (`hamlab incus-export`), from phase 2. Default is under Ham Lab's data directory (so on + `core` the nightly rsync already carries it); on `grid` he will point it at + `/tank/backups/incus/`, since `grid`'s own root disk is backed up nowhere. -## The old Proxmox estate (restorable, phase 3) +## The old Proxmox estate (importable, phase 3) `/tank/backups/proxmox/` holds the retired `r720` node: `/etc/pve` (12 LXC confs, 2 VM confs), 118 GiB of `vzdump` archives, and three raw VM disks. Latest of each: @@ -125,8 +178,9 @@ confs), 118 GiB of `vzdump` archives, and three raw VM disks. Latest of each: - **LXC:** feasible and scriptable. Unpack the rootfs (`tar --zstd -xpf … --numeric-owner`), read `etc/vzdump/pct.conf` for arch/ostype, drop it, write `metadata.yaml`, pack, `incus - image import`, launch. Unprivileged uid shifting is Incus's job after that. A wizard in the - tab is plausible once the script works by hand. + image import`, launch. Unprivileged uid shifting is Incus's job after that. An import + action in the tab (pick an archive on this machine) is plausible once the script works by + hand. x86-64 archives only run on x86-64 machines; the picker says so. - **VMs:** the raw disks import directly (`incus-migrate`, or an empty VM with the disk imported into its root volume). The `.vma.zst` archives need Proxmox's `vma` extractor (or the Python port) to become raw disks first. Most of these are the same `powerpanel` box @@ -137,17 +191,17 @@ confs), 118 GiB of `vzdump` archives, and three raw VM disks. Latest of each: | Phase | What | Effort | |---|---|---| -| **0 — host prep on `grid`** (by hand, documented) | `apt install incus`; `incus admin init --preseed` with the pool and bridge above; `DOCKER-USER` rules; ARC cap; `incus-admin` group + unit edit; launch one Debian 13 container and one Debian 13 VM; verify console, shell, network, snapshot, export. Write it up in `hamlet/docs/hosts.md` and the preseed into `hamlet/scripts/`. | 1 session | -| **1 — the tab** | `internal/incus` (CLI wrapper, JSON types, `incus monitor` for live state), `internal/api/virt.go` (routes below), `Virtualization.tsx` (list, detail, create dialog, snapshot panel), rail entry `virt`, terminal integration, Install button, the no-KVM state. | 2 sessions | -| **2 — operations** | Snapshot schedules UI, nightly exports with retention, limits editor, bind mounts, `br0` on `grid` and the LAN network option, instance sensors + alert default, cloud-init presets. | 1–2 sessions | -| **3 — the rest** | Proxmox import wizard, publish-as-image, GPU device for containers, VGA console (`spice-html5`), `core` as a second (containers-only) host. | as wanted | +| **1 — detection, Install, the tab** | `internal/incus` (detection, CLI wrapper, JSON types, `incus monitor` for live state, the preseed builder and subnet picker), `internal/api/virt.go` (routes below), `Virtualization.tsx` (the three page states; list, detail, create dialog, snapshot panel), rail entry `virt`, terminal integration, sensors. **Tested by clicking Install on `grid` first, then `core`** — two different pool drivers, both with Docker present. Then the Mac for the arm64 path. `gate` for the unavailable state. | 2–3 sessions | +| **2 — operations** | Snapshot schedules UI, nightly exports with retention and the per-machine export directory, limits editor, bind mounts, the LAN-bridge step, instance alert default, cloud-init presets. | 1–2 sessions | +| **3 — the rest** | Proxmox import, publish-as-image, GPU device for containers, VGA console (`spice-html5`), GPU passthrough to a VM with its warning. | as wanted | ### API sketch ``` -GET /api/virt {installed, kvm, pools[], networks[], instances[]} -POST /api/virt/install task: the phase-0 recipe -GET /api/virt/images?remote=images&q= local cache + remote search +GET /api/virt {state: ready|installable|unavailable, reason, kvm, arch, + pool_drivers[], pools[], networks[], instances[], free_memory} +POST /api/virt/install {driver, size, path, subnet, docker_rules, arc_max?} task +GET /api/virt/images?remote=images&q= local cache + remote search, filtered to this arch DELETE /api/virt/images/{fingerprint} POST /api/virt/instances {name, type, image, cpu, memory, disk, network, cloudinit, profile, start} task GET /api/virt/instances/{name} config, state, devices, snapshots