Operational introduction — lab site, HA, and ops
Experimental and unsupported: YAIDAP provisioning, BM install, and GPU passthrough change bare metal and hypervisors. Use reviewed lab inventory only.
This is Phase B of the operational introduction. Goal: provision site homelab through YAIDAP, bring up lab-single-0 and HA lab-ha-0, prove site-routed apps and Authelia, exercise node lifecycle, then KubeVirt, GPU clusters, and hub LiteLLM inference.
Examples use jumphost linux@jump.otthol.hu:30200, lab LAN 172.16.10.0/24, and zone homelab.otthol.hu. Complete Phase A first, or start from a hub that is already verified. See YAIDAP and UMKIM integration.
B1 — Lab site and ingress
Section titled “B1 — Lab site and ingress”umkim site create -f takes a FleetSite manifest. The published umkim tree does not include examples/homelab/. The lab manifest sets jumphost to linux@jump.otthol.hu:30200 and ingress rows homelab-pf, homelab-gpu-pf (port_forward, wanAddressMode: dns), and edge-rp (reverse_proxy). Omitting spec.rockyVersion on a FleetSite stores the CRD default "9". Guest OS is inventory os: rocky-10. Cluster create goes through RockyMajor, which stores 10 when rockyVersion is omitted (FLEET_SITE_ROCKY_MAJOR defaults to 10).
umkim site create -f site-homelab.jsonumkim site ingress set homelab homelab-pf --address 172.16.10.100umkim site ingress add homelab --id homelab-ha-pf --method port_forward \ --address 172.16.10.100 --zone homelab.otthol.hu \ --wildcard '*.homelab.otthol.hu'umkim site get homelabumkim site ingress list homelabWAN DNAT for *.homelab.otthol.hu is HTTP 172.16.10.100:30080 and HTTPS 172.16.10.100:30443 (Traefik NodePorts on the lab-ha-0 VIP). OpenWRT already has that DNAT.
Verify: site Ready. homelab-pf and homelab-ha-pf show address 172.16.10.100 and zone homelab.otthol.hu. homelab-gpu-pf stays DNS mode (*.gpu.homelab.otthol.hu) until a later cluster bind.
B2 — YAIDAP provisioner + inventory
Section titled “B2 — YAIDAP provisioner + inventory”umkim site provision set takes --inventory (API field inventoryPath). umkim site provision yaidap-sync returns when the job is accepted. https:// and git@ remotes require --yaidap-git-secret: a Secret in fleet-console whose key is id_ed25519, deploy_key, or id_rsa. This demo uses the SSH remote git@github.com:rborso/yaidap.git at ref main.
# Secret fleet-site-homelab-yaidap-git must already exist (deploy key; do not commit it).umkim site provision set homelab --provider yaidap \ --jumphost linux@jump.otthol.hu:30200 \ --ssh-secret fleet-site-homelab-provision-ssh \ --yaidap-repo git@github.com:rborso/yaidap.git \ --yaidap-ref main \ --yaidap-root /home/linux/yaidap \ --inventory /home/linux/yaidap/inventory/homelab \ --yaidap-git-secret fleet-site-homelab-yaidap-gitumkim site provision yaidap-sync homelabumkim site provision get homelabumkim site inventory migrate homelab# The published checkout has no examples/homelab/inventory/. Put files from a local inventory directory.# Skip lab-gpu-0.yml — empty hosts: {} is rejected.INV_DIR=/path/to/site-inventoryfor p in \ hypervisors.yml bare_metals.yml guests.yml \ group_vars/all/all.yml \ lab-ha-0.yml group_vars/lab-ha-0.yml \ lab-ha-0-w1.yml group_vars/lab-ha-0-w1.yml \ group_vars/lab-gpu-0.yml \ lab-gpu-pt-0.yml group_vars/lab-gpu-pt-0.yml \ host_vars/virt01.yml host_vars/virt02.yml host_vars/virt03.yml host_vars/virt04.yml \ host_vars/lab-ha-0-cp1.yml host_vars/lab-ha-0-cp2.yml host_vars/lab-ha-0-cp3.yml \ host_vars/lab-ha-0-w1.yml host_vars/lab-gpu-pt-0-cp1.ymldo umkim site inventory files put homelab --path "$p" -f "$INV_DIR/$p"doneumkim site inventory sync homelab --waitInventory shape: hypervisors.yml (virt01 172.16.10.2, virt02 172.16.10.3, virt04 172.16.10.6), bare_metals.yml (virt03 172.16.10.4), guests.yml plus matching host_vars/*.yml (os: rocky-10). Root {cluster}.yml needs hosts or children; empty hosts: {} is rejected.
Verify: provisioner yaidap; umkim site provision get shows the synced git revision; inventory sync Succeeded; jumphost can reach virt01–virt04.
B3 — virt03: discover the existing host
Section titled “B3 — virt03: discover the existing host”install_bm reinstalls a machine and requires --confirm. virt03 already runs Rocky Linux 10.2 at 172.16.10.4 and is listed in bare_metals.yml, so this demo discovers the host and imports inventory.
umkim site host get homelab virt03ssh -J linux@jump.otthol.hu:30200 linux@172.16.10.4 'cat /etc/redhat-release; hostname'umkim site host provision homelab virt03 --action discover --waitumkim site inventory import preview homelab --waitumkim site inventory import apply homelab --confirm \ --paths host_vars/virt03.yml,bare_metals.ymlumkim site host get homelab virt03Verify: discover Succeeded; host Ready; ansible_host 172.16.10.4, osFamily rocky-10 (GTX 1050 Ti present for later GPU stages).
B4 — Hypervisors prepare
Section titled “B4 — Hypervisors prepare”umkim site inventory sync homelab --waitumkim site host prepare homelab virt01 --wait # 172.16.10.2 — action prepare_hypervisorumkim site host prepare homelab virt02 --wait # 172.16.10.3Verify: virt01 / virt02 prepared as hypervisors; SSH from jumphost; libvirt healthy.
B5 — Single-node cluster lab-single-0
Section titled “B5 — Single-node cluster lab-single-0”umkim cluster create -f cluster-lab-single-0.json# bootstrapHost 172.16.10.4, site homelab, ingressId homelab-pfumkim cluster bootstrap lab-single-0 --bootstrap-host 172.16.10.4 --ssh-user linux \ --jumphost linux@jump.otthol.hu:30200umkim cluster wait lab-single-0 --phase Validated --timeout 45mumkim cluster provision lab-single-0umkim cluster wait lab-single-0 --phase Ready --timeout 45mumkim cluster get lab-single-0Verify: phase Ready; spoke overlay 10.10.0.10.
B6 — HA cluster lab-ha-0 (VIP 172.16.10.100)
Section titled “B6 — HA cluster lab-ha-0 (VIP 172.16.10.100)”Create guests then the HA cluster:
for h in lab-ha-0-cp1 lab-ha-0-cp2 lab-ha-0-cp3 lab-ha-0-w1; do umkim site host provision homelab "$h" --action create_vm --waitdone# Addresses: cp1 172.16.10.101, cp2 .102, cp3 .103, w1 .140# vm_location in the example host_vars: least-utilized for the CPs, virt02 for w1.# Example placement: cp1 and cp3 on virt01 (172.16.10.2), cp2 and w1 on virt02 (172.16.10.3).
umkim cluster create -f cluster-lab-ha-0.json# controlPlaneVip 172.16.10.100; the file's edge.ingressId is edge-rp (B7 rebinds homelab-ha-pf)umkim cluster bootstrap lab-ha-0 --bootstrap-host 172.16.10.101 --ssh-user linux \ --jumphost linux@jump.otthol.hu:30200umkim cluster wait lab-ha-0 --phase Validated --timeout 45mumkim cluster provision lab-ha-0umkim cluster wait lab-ha-0 --phase Ready --timeout 90mVerify: cluster Ready; three control planes + worker; VIP 172.16.10.100 Traefik NodePorts respond when site-routed ingress is configured; spoke overlay 10.10.0.12.
B7 / B7b — Site-routed apps and spoke Authelia
Section titled “B7 / B7b — Site-routed apps and spoke Authelia”The lab HA create body binds edge.ingressId to edge-rp. Point the cluster at the port-forward row instead. Spoke ACME for port_forward uses Traefik TLS-ALPN (tlschallenge); HTTP-01 is off because the spoke redirects port 80 to HTTPS. Hub ACME stays HTTP-01. FleetIngressAuth on the cluster is separate from hub ingress auth.
umkim cluster patch lab-ha-0 --ingress-id homelab-ha-pfumkim app create --cluster lab-ha-0 --chart whoami --version 0.1.0 \ --name whoami-site --release whoami-site \ --route-mode site --route-host whoami.homelab.otthol.huumkim cluster ingress-auth enable lab-ha-0 \ --lldap-password-file /path/to/lldap-user-secret # REDACTED path; password stays in the fileumkim cluster acme enable lab-ha-0cat > /tmp/whoami-site-auth.json <<'EOF'{ "name": "whoami-site-auth", "clusterName": "lab-ha-0", "chart": "whoami", "version": "0.1.0", "releaseName": "whoami-site-auth", "route": { "mode": "site", "siteHost": "whoami-auth.homelab.otthol.hu", "requireAuth": true }}EOFumkim app create -f /tmp/whoami-site-auth.jsoncurl -4 -sS https://whoami.homelab.otthol.hu/ | headcurl -4 -sSI https://whoami-auth.homelab.otthol.hu/ | headVerify:
- FleetApp
whoami-siteis onlab-ha-0, hostwhoami.homelab.otthol.hu, route modesite, no auth.tlsPhaseReady. - The certificate is a production Let’s Encrypt certificate, issuer CN
YR1(Let’s Encrypt). HTTP returns 308 to HTTPS. HTTPS verifies. auth.homelab.otthol.huhas a production Let’s Encrypt certificate (issuer CNYR1).- Public HTTPS returns whoami for
whoami.homelab.otthol.hu. - Unauthenticated
whoami-auth.homelab.otthol.huchallenges Authelia athttps://auth.homelab.otthol.hu/?rd=…; authenticated session returns 200.
Spoke firewalld
Section titled “Spoke firewalld”configure_firewalld stays false, so YAIDAP prepare does not start firewalld or lock SSH.
The fleet_spoke_firewall role stays off unless fleet_spoke_firewall_enabled is true. That flag defaults to configure_firewalld (false). The role does not disable firewalld. When it is enabled it starts firewalld, trusts the WireGuard interface, and allows the overlay to TCP 6443, 10250, and hostNetwork LLM TCP 8080. Spoke Traefik NodePorts 30080, 30443, and 30081 are opened when spoke ingress is enabled.
When an operator turns YAIDAP firewalld on, firewalld_cluster_profile (default true) trusts 10.10.0.0/16 (UMKIM WireGuard), 192.168.0.0/16 (Calico; flannel is disabled), and 10.43.0.0/16, and trusts wg-umkim and wg0 when those interfaces exist.
Inventory zones also get TCP 80, 443, 30080, 30443, and 30081. Zone targets are DROP. TCP 6443 and 10250 are not opened on those DROP zones; trusted overlay and pod sources can reach them. TCP 179 (Calico BGP) is not in that zone list. host_prepare opens TCP 179, 6443, and 10250 only on its opt-in firewalld path (configure_firewalld true), because Calico peers are management-LAN addresses.
When firewalld is already running (firewall-cmd --state succeeds), K3s overlay tasks open spoke Traefik NodePorts if spoke ingress is enabled and trust discovered pod CIDRs on every cluster node, not only the bootstrap node. Those tasks do not start firewalld.
B8 — Cluster node operations
Section titled “B8 — Cluster node operations”On lab-ha-0:
| Step | Action | Verify |
|---|---|---|
| B8a | Worker lab-ha-0-w1 (172.16.10.140) is already in cluster-lab-ha-0.json nodes[] and was created with create_vm in B6 |
umkim node list --cluster lab-ha-0 shows cp1–cp3 + w1 Ready |
| B8b | Remove the worker. group_vars/lab-ha-0-w1.yml sets mode: none and autoApply: false so inventory reconcile does not addWorker it back |
Worker gone from the API; three CPs Ready |
| B8c | Provision lab-ha-0-cp5 @ 172.16.10.144, then umkim node replace |
Ready nodes cp1 / cp2 / cp5 |
# B8b — put the autoApply:false group_vars, sync, then remove.# A remove while another node operation is in progress returns HTTP 422.umkim site inventory files put homelab --path group_vars/lab-ha-0-w1.yml \ -f group_vars/lab-ha-0-w1.ymlumkim site inventory sync homelab --waitumkim node remove lab-ha-0-w1 --cluster lab-ha-0 --force
# B8c — node name is --old-node. The published examples stop at cp1–cp3 and w1.# Add replacement guest lab-ha-0-cp5 (172.16.10.144) to guests.yml# and host_vars/lab-ha-0-cp5.yml in the same shape as lab-ha-0-cp1.yml, then:umkim site inventory sync homelab --waitumkim site host provision homelab lab-ha-0-cp5 --action create_vm --waitumkim node replace --cluster lab-ha-0 \ --old-node lab-ha-0-cp4 \ --address 172.16.10.144 \ --hostname lab-ha-0-cp5While a replace is Pending or Running, the controller stores oldNodeAddress and injects that control-plane host into the leave inventory on every rebuild. This example leaves lab-ha-0-cp4 and joins lab-ha-0-cp5. Ready nodes afterward: lab-ha-0-cp1 172.16.10.101, lab-ha-0-cp2 172.16.10.102, lab-ha-0-cp5 172.16.10.144. Check umkim node-op history --cluster lab-ha-0 if the phase stays non-Ready.
B9 — KubeVirt and Multus
Section titled “B9 — KubeVirt and Multus”The command is umkim cluster platform enable-kubevirt. --lan-bridge configures host bridge n0p0_mgmt_nw (override with --bridge) and Multus NAD default/mgmt-l2. --wait is a duration; 0 returns after the API accepts the job. Helm values use --values-file. -f on app create is a JSON body.
umkim cluster platform enable-kubevirt lab-ha-0 --lan-bridge --wait 45mumkim cluster platform status lab-ha-0umkim app create --cluster lab-ha-0 --chart rocky-10-vm --version 0.1.3 \ --name rocky-kv-pod --release rocky-kv-pod --route-mode noneumkim app create --cluster lab-ha-0 --chart rocky-10-vm --version 0.1.3 \ --name rocky-kv-lan --release rocky-kv-lan --route-mode none \ --values-file rocky-lan-ha-values.yamlVerify: KubeVirt platform Ready; capabilities show kubevirt / multus; rocky-kv-pod and rocky-kv-lan Ready/Running. See Fleet KubeVirt.
B10 — virt04 GPU passthrough guest
Section titled “B10 — virt04 GPU passthrough guest”virt04 is 172.16.10.6 in hypervisors.yml. GPU prepare requires --confirm. The combined flag is umkim site host prepare … --gpu-passthrough, which posts action prepare_hypervisor_gpu (prepare plus VFIO). This demo uses the granular action prepare_gpu_passthrough. A prepare against a name that is not in the hub inventory returns HTTP 404 (guest not found / not found).
umkim site inventory sync homelab --waitumkim site host prepare homelab virt04 --waitumkim site host provision homelab virt04 --action prepare_gpu_passthrough --confirm --wait# equivalent combined action, once virt04 is in inventory:# umkim site host prepare homelab virt04 --gpu-passthrough --confirm --waitumkim site host provision homelab lab-gpu-pt-0-cp1 --action create_vm --wait# guest lab-gpu-pt-0-cp1 @ 172.16.10.141, vm_location virt04 (GTX 1650)ssh -J linux@jump.otthol.hu:30200 linux@172.16.10.141 'hostname; uptime'Verify: guest SSH reachable; GPU hostdev attached (driver install continues in B13 with umkim cluster gpu host-prep).
B11 — GPU clusters Ready
Section titled “B11 — GPU clusters Ready”lab-single-0 on virt03 was already Ready from B5. Provision the passthrough guest as its own cluster:
umkim cluster create -f cluster-lab-gpu-pt-0.json# bootstrapHost 172.16.10.141, ingressId homelab-gpu-pfumkim cluster bootstrap lab-gpu-pt-0 --bootstrap-host 172.16.10.141 --ssh-user linux \ --jumphost linux@jump.otthol.hu:30200umkim cluster provision lab-gpu-pt-0umkim cluster wait lab-gpu-pt-0 --phase Ready --timeout 120mumkim cluster list -o wideVerify: umkim cluster list -o wide shows lab-single-0 and lab-gpu-pt-0 Ready (spoke overlays 10.10.0.10 and 10.10.0.14). homelab-gpu-pf is DNS mode for *.gpu.homelab.otthol.hu (no fixed WAN address in the example). Bind with umkim cluster patch lab-gpu-pt-0 --ingress-id homelab-gpu-pf when the create body did not already set edge.ingressId. In this lab, ACME is enabled on lab-ha-0 and lab-single-0, and not on lab-gpu-pt-0.
B12 — Hub LiteLLM
Section titled “B12 — Hub LiteLLM”umkim llm set writes FleetLLM. umkim llm reconcile --wait polls until phase Ready; --timeout is seconds (default 600), not a Go duration.
umkim llm set --hub-enabled --public-host llm.api.demo.otthol.huumkim llm reconcile --wait --timeout 600umkim llm get# The token is printed once on stderr. Save it yourself, mode 600, outside git# This demo saves it at ~/.umkim-llm-intro.key:umkim auth llm-api-keys create --name intro-lab --display-name "Intro lab"curl -4 -sS -H "Authorization: Bearer $(cat ~/.umkim-llm-intro.key)" \ https://llm.api.demo.otthol.hu/v1/models | headVerify: phase Ready; endpoint https://llm.api.demo.otthol.hu/v1; litellm + redis pods Running in fleet-llm. See fleet-llm.
B13 — llama.cpp on GPU clusters + inference
Section titled “B13 — llama.cpp on GPU clusters + inference”--wait on gpu host-prep and gpu enable takes a duration string (45m, 30m). Host prep on a cluster that is already GPU-Ready needs --force. --llm-backend is true or false (it sets annotation fleet.umkim.com/llm-backend). --llama-profile and --values-file are mutually exclusive on create. umkim app update takes --values-file and replaces spec.valuesYaml as a whole.
umkim cluster gpu host-prep lab-single-0 --wait 45mumkim cluster gpu enable lab-single-0 --wait 30mumkim cluster gpu discover lab-single-0umkim cluster gpu status lab-single-0# GTX 1050 Ti, driver 535, clusterGpuCapability=true, gpuAvailable=1
umkim cluster gpu host-prep lab-gpu-pt-0 --wait 45mumkim cluster gpu enable lab-gpu-pt-0 --wait 30mumkim cluster gpu discover lab-gpu-pt-0umkim cluster gpu status lab-gpu-pt-0# GTX 1650 / TU117 (pci 10de:1f82), same capability flags
umkim app create --cluster lab-single-0 --chart llama-cpp-helm --version 0.1.4 \ --llama-profile qwen35-2b-ud-4g --namespace llm --release llama-cpp \ --name llama-virt03 --llm-backend true --route-mode none
umkim app create --cluster lab-gpu-pt-0 --chart llama-cpp-helm --version 0.1.4 \ --llama-profile qwen35-2b-ud-4g --namespace llm --release llama-cpp \ --name llama-gpu-pt --llm-backend true --route-mode none# Guest has ~3.6Gi RAM. Profile qwen35-2b-ud-4g requests memory 4Gi / limits 6Gi (CPU 2/4),# which does not fit this guest. --values-file replaces spec.valuesYaml entirely (not a merge).umkim llm profile values qwen35-2b-ud-4g > /tmp/llama-gpu-pt-mem.yaml# Edit that file: resources.requests memory 1500Mi / cpu "1", limits memory 2800Mi / cpu "3".umkim app update llama-gpu-pt --values-file /tmp/llama-gpu-pt-mem.yaml
umkim llm backends sync-from-appsumkim llm reconcile --wait --timeout 600# backends: 10.10.0.10:8080 + 10.10.0.14:8080 model Qwen3.5-2B-UDcurl -sS http://10.10.0.10:8080/health # {"status":"ok"}curl -sS http://10.10.0.14:8080/health # {"status":"ok"}
curl -4 -sS https://llm.api.demo.otthol.hu/v1/chat/completions \ -H "Authorization: Bearer $(cat ~/.umkim-llm-intro.key)" \ -H "Content-Type: application/json" \ -d '{"model":"Qwen3.5-2B-UD","messages":[{"role":"user","content":"Say Hi!"}]}'Verify: both backends /health return ok; hub LiteLLM chat returns HTTP 200 with assistant content “Hi!”.
What you should see
Section titled “What you should see”- Site
homelabReady lab-single-0Ready (virt03, GTX 1050 Ti)lab-ha-0Ready with control planescp1,cp2, andcp5- Site-routed
whoami-siteonlab-ha-0(whoami.homelab.otthol.hu, no auth,tlsPhaseReady) andwhoami-site-authReady - KubeVirt
rocky-kv-podandrocky-kv-lanReady lab-gpu-pt-0Ready (guest172.16.10.141)- Hub LiteLLM Ready at
llm.api.demo.otthol.huwith synced llama backends and a successful chat
Next / related
Section titled “Next / related”- Return to the operational introduction overview
- Component deep-dives: fleet-site, fleet-apps, fleet-llm, GPU host prep
- Optional UI chrome tour: console walkthrough