Setup, deployment, and configuration for KUI administrators. For product overview and architecture, see PRD and Architecture.
| Requirement | Notes |
|---|---|
| OS | Linux with libvirt (KVM) |
| libvirt | libvirt and libvirt-dev packages; libvirtd running |
| Remote hosts | nc (netcat) installed; SSH key auth to qemu+ssh:// |
| Storage | /var/lib/kui writable (DB, templates, audit); /etc/kui for config |
| Build | Go 1.22+; CGO for libvirt bindings (-tags libvirt) |
See spec-libvirt-connector and decision-log §1 for remote libvirt and test driver.
When config is missing, KUI runs in setup mode and serves a setup wizard. No auth is required until setup completes.
- Start KUI (see Deployment).
- Open the UI in a browser. You are redirected to the setup flow.
- Configure:
- Admin account — username and password (stored in SQLite).
- Hosts — at least one libvirt host. For each host:
id— short identifier (e.g.local,prod)uri—qemu:///system(local) orqemu+ssh://user@host/system?keyfile=/path/to/keykeyfile— path to SSH private key for remote hosts (null for local).
- Default host — select which host is used by default for VM operations.
- Complete setup. KUI writes config to disk. Restart KUI (e.g.
systemctl restart kui) to load the new config; then log in.
Setup is idempotent: once config exists, the wizard is unavailable. See api-auth spec and schema-storage.
Config is written by the setup wizard. Full YAML structure and env overrides are in schema-storage spec §2.6.
Required:
hosts— list of libvirt connection targets.jwt_secret— min 32 bytes; set by setup wizard.
Common optional:
vm_defaults— CPU, RAM, network for VM create (default: 2 vCPU, 2048 MB,defaultnetwork).default_host— default host ID.vm_lifecycle.graceful_stop_timeout— timeout before force stop (default: 30s).
KUI supports a runtime prefix: --prefix on the CLI or the KUI_PREFIX environment variable. Unknown keys in YAML are ignored, so a runtime: block (for example runtime.prefix) is not read for prefix—use the flag or env. When the effective prefix is non-empty, the process resolves local filesystem paths as if that directory were / (chroot-style re-rooting, not a real chroot(2)). A path like /var/lib/kui/kui.db opens as {prefix}/var/lib/kui/kui.db. This is useful for non-root installs, relocatable trees, and tests without writing under the real FHS.
Paths that are not re-rooted include libvirt URIs, pool names (non-path strings), JWT/session settings, and host discovery paths used by KVM checks (for example /dev/kvm, /etc/os-release).
Create directories that match the logical paths your config uses (defaults assume FHS-style locations). For a minimal layout that mirrors common defaults:
PREFIX="$HOME/kui-run" # or: PREFIX="$(mktemp -d)"
mkdir -p "$PREFIX/etc/kui" "$PREFIX/var/lib/kui"
# If you rely on default pool directory heuristics under prefix:
mkdir -p "$PREFIX/var/lib/libvirt/images"Place config.yaml under {prefix}/etc/kui/ when using logical path /etc/kui/config.yaml, or adjust mkdir to match your YAML.
Use normal absolute-looking strings in YAML; with a prefix set, they refer to locations under the prefix, not the host root:
# Ignored for prefix (use --prefix / KUI_PREFIX instead):
# runtime:
# prefix: /opt/kui-run
db:
path: /var/lib/kui/kui.db
git:
path: /var/lib/kui
hosts:
- id: local
uri: qemu:///system
keyfile: null
# Remote example: key path is also under prefix when prefix is set
# - id: remote
# uri: qemu+ssh://user@host/system
# keyfile: /home/kui/.ssh/id_ed25519
jwt_secret: "<set-via-setup-or-generate>"On disk with PREFIX=/opt/kui-run, the DB file above is opened at /opt/kui-run/var/lib/kui/kui.db.
--prefix— if you pass this flag, it wins.KUI_PREFIX— used when the--prefixflag was not set on the command line.
There is no YAML field for prefix: values under runtime: (or similar) in config are not applied. Tests may set an equivalent via LoadOptions.Prefix.
The bootstrap prefix (--prefix / KUI_PREFIX) is also used to resolve the config path before reading (for example --prefix /tmp/r --config /etc/kui/config.yaml reads /tmp/r/etc/kui/config.yaml).
When a non-empty effective prefix is in use:
--tls-certand--tls-keyare resolved under the prefix the same way as other filesystem paths (for example logical/etc/kui/tls/cert.pem→{prefix}/etc/kui/tls/cert.pem).KUI_WEB_DIR, when set, is resolved under the effective prefix from--prefix/KUI_PREFIX(same as other local paths).
Use empty prefix (omit --prefix and KUI_PREFIX) when:
- KUI should use real host paths for SQLite, git data, TLS material, and pool directories—typical system libvirt deployments on FHS (
/var/lib/libvirt/images,/var/lib/kui,/etc/kui, …). - You expect logical
/var/...paths in config to mean the actual host filesystem. With a prefix set, default pool path probes (for example under/var/lib/libvirt/images) are evaluated under the prefix; libvirt on the host still uses host paths unless you mirror layout under the prefix, use bind mounts, or align pool configuration explicitly.
Optional steps for humans validating a local binary; automated tests are canonical—run go test ./... or make all for project verification.
export PREFIX="$(mktemp -d)"(or choose a fixed directory under$HOME).mkdir -p "$PREFIX/etc/kui" "$PREFIX/var/lib/kui"and any other logical paths your config references (including default pool dirs if you exercise provisioning defaults).- Install or author a minimal
config.yamlunder$PREFIX/etc/kui/using absolute-style paths as in the sample above; confirm files you care about exist only under$PREFIX/.... - From the repo:
go build -o bin/kui ./cmd/kui, then run (example)./bin/kui --prefix "$PREFIX" --config /etc/kui/config.yaml --listen 127.0.0.1:0(adjust--listenas needed).
Note: Full VM lifecycle still depends on host libvirt and KVM; a contained smoke run may only confirm HTTP listener, config load, and DB open. Libvirt URIs and host device access are unchanged by prefix.
| Topic | Document |
|---|---|
| systemd | deploy/systemd/README.md — unit file, install, runtime dirs |
| TLS & reverse proxy | deployment.md — HTTP, direct TLS, nginx/Caddy, WebSocket/SSE |
KUI listens on :8080 by default. Behind a reverse proxy, configure WebSocket and SSE passthrough per deployment.md.
# With libvirt (default, production)
make all
# Without libvirt (CI, no KVM)
make build BUILD_TAGS=
make test BUILD_TAGS=Frontend: corepack enable (once), then cd web && corepack yarn install && corepack yarn run build. Uses Yarn 4 via Corepack (packageManager in package.json). Embedded in binary via //go:embed; or set KUI_WEB_DIR to serve from disk. See Makefile.
When a libvirt host has no storage pools or no networks, KUI can provision them. This is needed when:
- Setup wizard — validate-host returns "no storage pools" or "no networks".
- Create VM — the selected host has no pools or networks in the dropdown.
- Audit — KUI shows what would be created (pool path, network name/subnet).
- Review — User confirms the proposed configuration.
- Execute — KUI creates the pool and/or network.
| Condition | Pool path |
|---|---|
/var/lib/libvirt/images exists and is non-empty |
Use existing path |
/var/lib/libvirt/images missing or empty |
Propose /var/lib/kui/images; create dir before pool define |
The KUI process must be able to create /var/lib/kui and subdirs (e.g. /var/lib/kui/images). Typically run as root or a dedicated kui user with appropriate permissions in systemd.
Provisioning is supported only for local hosts (qemu:///system). Remote hosts (qemu+ssh://) return 400 with "remote host provisioning not supported in this version". Remote provisioning is planned for a future release.
| Issue | Check |
|---|---|
| Setup wizard not shown | Config exists at KUI_CONFIG (default /etc/kui/config.yaml). Remove or rename to re-run setup. |
| Provision fails (permission denied) | KUI must create /var/lib/kui/images when libvirt default path is empty. Ensure KUI runs as root or has write access. |
| Host unreachable | Verify libvirtd on remote host; SSH key in authorized_keys; nc installed. See spec-libvirt-connector. |
| Console (VNC/serial) fails | Local hosts only in MVP; remote requires KUI on same host as libvirt or tunnel. See deployment.md for WebSocket proxy setup. |
| WebSocket/SSE not working | Reverse proxy must forward Upgrade and Connection headers; disable buffering for SSE. See deployment.md. |