Changelog¶
The same release notes shown in the console's What's New prompt (bell icon, top nav), in full and in order.
MFCloud Console — Changelog¶
All notable changes to the MFCloud console, curated by release. Dates are the
completion date of each version. This is a distilled highlight list — the full
per-item engineering history lives in patch_note/RAOD_MAP_SHIPPED.txt.
Format loosely follows Keep a Changelog. Versions
are MFCloud product versions (the APP_VERSION baked into each build).
Versioning scheme (from 0.40.0-beta on): semver, pre-1.0 — 0.<minor>.<patch>-<stage>.
The 0.x major signals the product hasn't hit a stable 1.0 yet; <minor> increments
per release the way the old V<n> counter did. Older headers below (V39, V38, …)
predate this and are left as originally written.
0.42.5.13-beta — 2026-07-29¶
Added¶
- Proxmox VE import (V2V) — a third "From Proxmox (Live Copy)" tab
alongside the existing "From Datastore" and "From vCenter" V2V Import
options. Browse a Proxmox host's stopped guests over its REST API (API
token auth) and pull one straight in; disk conversion runs over SSH +
qemu-imgon the source Proxmox host (dir/NFS, LVM/LVM-thin, ZFS, and Ceph RBD-backed disks all supported) and then goes through the same virt-v2v conversion, driver injection, and placement pipeline the vCenter path already uses. v1 scope: single-VM only — batch import (like the vCenter tab's multi-VM placement table) is a planned fast-follow. ⚠️ Not yet validated against a real Proxmox host — please test carefully before relying on it, and report anything that doesn't match a real Proxmox deployment's storage layout.
0.42.5.12-beta — 2026-07-29¶
Fixed¶
- noVNC console broken after VM migration: a live migration transfers
the currently-running domain's XML as-is — if that VM's
<graphics>listen address was ever anything other than0.0.0.0(e.g. a VM that reached inventory outside the normal deploy flow), it stayed that way on the destination host too, and the console proxy (which always connects to the compute node's real IP, never localhost) could never reach it. Both the live and cold migration paths now force the destination VM's VNC listen address to0.0.0.0before/during the move — self-healing the misconfiguration on every migration instead of only at first deploy.
0.42.5.11-beta — 2026-07-29¶
Fixed¶
- Edit Hardware context-menu item was still masking a real
0vCPU/RAM as "unset" for stopped containers — the 0.42.5.10-beta fix only covered the running-container branch of the context menu. Caught by testing the 0.42.5.10-beta fix batch locally before this went further.
0.42.5.10-beta — 2026-07-29¶
Fixed¶
- OVN/macvlan container-NIC bypass: creating an LXD container with both an OVN IP pool and a macvlan-backed network selected silently routed the container's traffic through macvlan (bypassing the OVN logical switch's ACLs entirely) while the API still reported it as OVN-bound with a real IPAM-allocated IP/MAC. The combination is now rejected with a clear error.
- Placement:
score_hosts()was probing every label-matching host for live capacity before filtering by OS class, wasting a liveqemu+sshround trip on hosts that could never be selected for the VM being placed. - LXD NIC management:
remove_niccould silently clear an instance'sstatefulflag and could wrongly reattach thedefaultprofile; bothadd_nicandremove_nicnow use LXD's ETag/If-Matchsupport so a concurrent NIC change on the same instance is rejected and retried instead of silently clobbered. - V2V debug mode: a cancelled import with
V2V_DEBUG=1set now cleans up its staging directory like every other exit path, instead of leaving a large (up to tens of GB) source copy behind. - Hardened silent-failure handling in the SSH-penalty-exempt/VNC-firewall repair helpers and the LXD-evacuation alarm query — both now log instead of quietly degrading.
- Assorted V2V batch-import and dashboard-widget correctness/cleanup fixes
from a full code-review pass (Chart.js instances leaking on repeated tab
opens, a modal-form Enter key submitting unedited fields, a container
hardware-edit dialog treating a real
0as unset, redundant/api/alarmspolling).
0.42.5.9-beta — 2026-07-29¶
Changed¶
- Host Health honeycomb (Compute Overview widget) now shows a small IP
label under each hex cell, so a bad host can be identified at a glance
instead of hovering over every hex to read its tooltip. Shows the last
two octets (e.g.
14.11) rather than the full IP — the default text-truncation would otherwise clip from the end and hide exactly the octets that distinguish one host from another.
0.42.5.8-beta — 2026-07-28¶
Fixed¶
- Root cause of the intermittent "0 VMs" / "awaiting host data" dashboard
blips: compute nodes' OpenSSH
PerSourcePenaltyExemptListonly contained the floating control-plane VIP, which is never the actual source IP of an outbound connection — each of the 3 console replicas connects from its own real interface IP. So none of the 3 replicas were actually exempt from OpenSSH 9.8+'s brute-force penalty box, and their redundant polling of the same compute nodes periodically tripped it, causing real, intermittentqemu+sshfailures (not a client-side rendering bug — see 0.42.5.7-beta above for a related but separate fix).routers/hosts/repair.py::_ensure_penalty_exempt()(run on every Repair Worker action) only ever wrote the single VIP; its sibling function,_ensure_vnc_firewall_restricted(), already correctly pulled every real HA host IP fromha_cluster_hosts— the two had drifted. Now both agree. Retroactively fixed on all enrolled compute nodes the same day.
0.42.5.7-beta — 2026-07-28¶
Fixed¶
- The Summary tab's VM/host stats and Overview widgets could occasionally
drop to "0 VMs" / empty for one poll cycle (e.g. Active Virtual Machines,
Top VMs by CPU/Memory %, CPU Usage Share). Cause:
refreshAll()treated ANY non-array response from/api/vms(or non-{hosts:[...]}response from/api/hosts) as an empty list — including a transient error body that still parsed as valid JSON, which a request racing a backend restart could plausibly return. Now only a recognizably well-formed response overwrites the cached VM/host list; anything else keeps the last known-good data and logs a console warning instead of silently blanking the dashboard.
0.42.5.6-beta — 2026-07-28¶
Fixed¶
- Renaming the console root label (sidebar tree root) reverted to the
container hostname on the next page load.
PUT /api/system/console-namepersistedCONSOLE_NAMEto.envbut never updated the already-running process's environment, soos.getenv("CONSOLE_NAME")kept returning nothing until the container was recreated — falling back tosocket.gethostname()(a random container ID like3ed2785f3a3a). Now also setsos.environ["CONSOLE_NAME"]in the same request so the rename takes effect immediately.
0.42.5.5-beta — 2026-07-28¶
Fixed¶
- The Host Availability rings, Host Health honeycomb, and CPU Usage Share pie visibly reset/regrew every ~3 seconds on the Summary tab. Cause: the underlying Chart.js instances were destroyed and recreated on every background poll tick even when the numbers hadn't changed, replaying the entry animation each time. Now only rebuilds when the data actually changed, and the CPU Usage Share pie updates its existing chart in place instead of tearing it down.
0.42.5.4-beta — 2026-07-28¶
Fixed¶
- The new Host Health honeycomb and Host Availability "Up" ring were an
overly bright neon green — switched both to the theme's existing darker
--success-solidtoken (already used for solid-fill buttons) instead of the badge/text-tuned--successgreen. Same swap for the "Top VMs by Memory %" bars. - Switching the sidebar scope (selecting a different host/cluster/datacenter) now immediately repaints the Summary tab's overview widgets instead of waiting up to 3 seconds for the next background poll.
0.42.5.3-beta — 2026-07-28¶
Added¶
- The Summary tab now opens on a real monitoring-style overview instead of just the old stat cards and host tables. New widgets: Host Availability (Up/Down/Unreachable/Maintenance donuts), a per-host Health honeycomb (colored by status and CPU/RAM thresholds), an Alert Summary (Critical/Warning/Info, from the same counts the Alerts panel uses), Top VMs by CPU % and by Memory % ranked lists, and a CPU Usage Share pie for the top 6 VMs. Everything is real data already available client-side or one existing endpoint away — no new telemetry collection was added.
- Datastores now have an Overview tab alongside the existing per-VM Summary view (Storage tab, or the dashboard Datastores view — click a datastore name). Shows capacity, IOPS, throughput, availability history, and disk utilization. VM/host counts are real (drawn from the same domain-XML disk match as the existing Summary tab); capacity, IOPS, throughput, and history are clearly labeled as sample data until per-datastore telemetry sampling exists.
0.42.5.2-beta — 2026-07-28¶
Added¶
- V2V Import now supports migrating multiple VMs from vCenter in one go. The "From vCenter (Live Copy)" tab's guest picker now lets you check 2 or more VMs at once. With multiple guests selected, pick an MFCloud placement cluster and the console automatically decides which KVM host each VM lands on — scoring free RAM/CPU headroom the same way "Deploy to cluster" does for new VMs, and spreading the batch across hosts rather than stacking them on one. A per-VM preview (target name, OS type, and assigned host) is shown before anything imports, and any row's assigned host can still be overridden by hand. Single-VM imports, and the file-based (OVA/VMX-on-datastore) import path, are unchanged.
0.42.5.0-beta — 2026-07-27¶
This release rolls up everything since 0.42.3.7-beta into one build — the patch versions in between (0.42.3.7 through 0.42.3.14) were incremental local milestones that never individually shipped. Their detail stays below.
Added¶
- LXD containers now have an Edit Hardware action. Previously CPU/memory limits could only be set at create time — now right-click a container (in the Containers tab or the sidebar tree) and change them live, no restart needed.
- LXD containers now have a NIC manager. Add or remove network adapters on an existing container — previously only the single NIC set at create time was possible. The network dropdown offers the same choices (LXD- managed networks and macvlan port groups) as Create Container.
- LXD containers can now attach to a macvlan-backed port group directly
through the Create Container UI, not just LXD-managed networks like
lxdbr0. Newnetworks.macvlan_parentfield lets a bridge-backed port group point at a host-side interface (a VLAN sub-interface, or the plain physical NIC for untagged/bridged attachment) that containers attach to via a macvlan NIC — closing a gap where the only way to get this networking shape was to call LXD's API directly. - NFS Datastores now show which VMs are actually on them. Clicking a datastore name in the Storage tab or the dashboard Datastores view opens a summary panel listing every VM with a disk on that share.
Changed¶
- Replaced several plain browser pop-up boxes with proper on-screen forms —
tenant/cluster resource quotas, cluster storage backend, renaming a
datacenter, a cluster's HA failover timing, tagging a host's capabilities,
and evicting a Ceph node all got a real form instead of chained
prompt()boxes. Destroying a Ceph cluster is now one styled confirmation requiring you to type DESTROY, instead of two stacked pop-ups. - Combined the Datastores toolbar's three storage buttons into one — "Manage NFS," "Manage ZFS," and "LINSTOR / DRBD" opened the same window on different tabs, so now it's a single "Manage Storage" button.
- Fixed a button getting cut off in the Datastore Manager's Enterprise Pools list — "Fix Permissions" and "Unmount" now live behind a single "⋯" menu / right-click, matching hosts and ZFS pools elsewhere.
- Fixed hard-to-read status badges and action buttons across Storage — LINSTOR/DRBD replication status, and the bright-green primary buttons ("Create Policy," "Format & Create Array," "Deploy Replicated Volume," etc.) all used low-contrast colors in dark mode; both now use the same soft-tinted / darker-green style as the rest of the console.
- Light theme cleanup — telemetry chart colors, capacity-bar tracks, RAM/disk mini-cards, floating-panel shadows, and sidebar/tab/task-drawer hover highlighting all had leftover dark-theme styling that did nothing (or looked broken) in light mode; all now follow the current theme.
Fixed¶
- Right-clicking a container in the sidebar tree did nothing (the browser's own context menu appeared instead) — the tree's context-menu dispatcher had no branch for container leaves. Right-click now shows the same menu as the Containers tab table (Console, Stats, Snapshots, Edit Hardware, NICs, Migrate, Power, Delete).
- K8s Scale Workers could fail with a raw
qemu-imgerror when a requested workerdisk_gbwas smaller than the chosen template's actual virtual size — now checks the template's real size first and raises a clear error naming the minimumdisk_gbrequired, for everycreate_vmcall, not just K8s workers. - K8s cluster node table had no visibility into DB/live drift — nodes the live cluster reports that MFCloud never provisioned, or nodes MFCloud believes exist that the live cluster no longer reports, are now flagged ("⚠ untracked" / a synthetic "Missing" row) with a way to clear stale bookkeeping.
- LINSTOR CSI cluster delete left every node behind as an OFFLINE satellite — cluster delete now best-effort deregisters each node's LINSTOR satellite before dropping the DB row, instead of requiring manual cleanup afterward.
0.42.3.14-beta — 2026-07-27¶
Fixed¶
- Light theme cleanup — several places still used colors leftover from the dark theme design that were never updated. Telemetry charts (Performance tab, VM/Container/Ceph monitoring) had washed-out axis labels and a nearly invisible "current value" readout on light backgrounds — chart colors now follow the current theme. CPU/RAM/disk capacity bars and modal progress bars had an invisible empty track. The RAM/disk telemetry mini-cards used the old bright green/yellow instead of the readable darker versions. A few floating panels (search results, task log, AI chat) had a much heavier drop shadow than everything else. Hover highlighting on the sidebar, inventory tabs, and task drawer silently did nothing in light mode since it was a "lighten a dark background" effect with nothing to lighten.
0.42.3.13-beta — 2026-07-27¶
Changed¶
- Fixed the same hard-to-read green buttons everywhere in Storage. "Create Policy," "Format & Create Array" (ZFS), "Format & Add to Ceph Cluster," "Deploy Replicated Volume," "Extend Existing Pool," and "Finish Setup" (DRBD) all used the same overly bright, low-contrast green with white text as the LINSTOR/DRBD status badges fixed earlier — now they use a darker green that's easy to read in dark mode while still standing out as the primary action button.
0.42.3.12-beta — 2026-07-27¶
Changed¶
- Combined the Datastores toolbar's three storage buttons into one. "Manage NFS," "Manage ZFS," and "LINSTOR / DRBD" all opened the exact same Storage window, just landing on a different tab — now it's a single "Manage Storage" button, and you pick NFS/ZFS/DRBD from the tabs inside as before. The separate Ceph HCI Cluster button stays on its own, since it opens a different, more specialized screen for managing the whole Ceph cluster.
0.42.3.11-beta — 2026-07-27¶
Added¶
- NFS Datastores now show which VMs are actually on them. Clicking a datastore name in the Storage tab or the dashboard Datastores view opens a summary panel listing every VM with a disk on that share (VM, host, disk path) — previously the only way to check was to hunt through each VM's settings individually.
0.42.3.10-beta — 2026-07-27¶
Changed¶
- Fixed a button getting cut off in the Datastore Manager's Enterprise Pools list. The row for each storage pool had two buttons ("Fix Permissions" and "Unmount") that no longer fit in the window and were getting clipped off the edge. Both actions now live behind a single "⋯" menu button (right-click the row also works) — same style used for hosts and ZFS pools elsewhere in the console.
0.42.3.9-beta — 2026-07-26¶
Changed¶
- Fixed hard-to-read status labels on the LINSTOR / DRBD replication tables (node connection status, storage pool health, and the "Replication State" column on the Datastores dashboard). These used solid bright green/yellow badges with white text, which is very low contrast in dark mode — now they use the same soft-tinted badge style used everywhere else in the console.
0.42.3.8-beta — 2026-07-26¶
Changed¶
- Replaced several plain browser pop-up boxes with proper on-screen forms. Setting a tenant's or cluster's resource quota no longer chains 2-3 separate gray text-entry pop-ups — it's one clean form now. Choosing a cluster's storage backend is now a dropdown of valid choices instead of a box where a typo would just get rejected. Renaming a datacenter, setting a cluster's HA failover timing, tagging a host's capabilities, and evicting a node from a Ceph cluster all got the same treatment.
- Destroying a Ceph cluster is safer to confirm. This used to be two stacked pop-ups (a yes/no, then "type DESTROY"). It's now a single styled confirmation box, and the Destroy button stays disabled until you actually type DESTROY into it.
0.42.3.7-beta — 2026-07-25¶
Fixed¶
- K8s Scale Workers could fail with a raw
qemu-imgerror when a requested workerdisk_gbwas smaller than the chosen template's actual virtual size —qemu-img resizerefuses to shrink a disk without--shrink(which we never want to pass; truncating a live filesystem corrupts the guest), so the operator saw an opaque subprocess exit-code failure instead of an actionable message.tasks/vm/storage.pynow checks the template's real size withqemu-img infobefore attempting the resize (zfs/nfs/local backends — the ceph backend already handled this via its own grow-only comparison) and raises a clear error naming the minimumdisk_gbrequired. Applies to everycreate_vmcall, not just K8s workers. - K8s cluster node table had no visibility into DB/live drift — MFCloud
had no way to flag a node the live cluster reports that MFCloud never
provisioned (manual
kubeadm join, or a scale-up whose DB commit never landed), or the opposite: a worker MFCloud believes it added that the live cluster no longer reports (crashed VM, failed join never rolled back).GET /{cluster_id}/nodesnow cross-referencesworker_ips/control_plane_ipagainst the livekubectl get nodessnapshot and annotates each row — the Nodes modal (static/js/kubernetes.js) shows an "⚠ untracked" badge for the former and a synthetic "Missing" row for the latter, with a "Clear" action to drop the stale bookkeeping entry. Additive only — never touchesk8s_clustersstate itself. - LINSTOR CSI cluster delete left every node behind as an OFFLINE
satellite — a full cluster delete tore down the k8s engine on each node
but never told the LINSTOR controller, so LINSTOR-backed clusters had to
be cleaned up by hand (Storage → LINSTOR → node delete) after every
teardown.
DELETE /{cluster_id}now best-effort deregisters each node's satellite (LinstorClient.delete_node) before dropping the DB row — tolerant of an already-dead cluster or missing LINSTOR config, same as the existing per-node uninstall loop.
0.42.3.6-beta — 2026-07-25¶
Fixed¶
- VM consoles fleet-wide could fail to connect ("Connection error") —
every VM's XML correctly asks libvirt to bind VNC to all interfaces, but
libvirt only honors that at define-time if the node's own
/etc/libvirt/qemu.confagrees; on affected nodes it silently fell back to loopback-only, so the console proxy could never reach it no matter what the VNC firewall rule allowed through. Node enrollment (services/node_provisioner.py) and the fast Repair Worker path (routers/hosts/repair.py) now both assertqemu.conf'svnc_listen = "0.0.0.0"and restartlibvirtd, mirroring the existing VNC-firewall-rule re-assertion pattern — every node, fresh or repaired, gets this from now on. Doesn't retroactively fix already-running VMs on already-affected nodes (a VM's QEMU process keeps whatever address it bound to at its own start time) — those need a restart or a Repair Worker run against the node followed by a VM restart.
0.42.3.5-beta — 2026-07-24¶
Fixed¶
- Sidebar inventory tree (VMs/Hosts/Storage/Networks folders) reset to fully
expanded on every browser reload — the tree's collapse/expand state was
only ever carried across the 3s polling rebuild in memory, never persisted,
so a manually-collapsed folder snapped back open the moment the page was
refreshed. Collapse/expand choices are now saved to
localStorage(static/js/main.js) keyed by tree node id and re-applied both on the initial page load and on every subsequent tree rebuild — a folder now stays collapsed across reloads until explicitly re-expanded.
0.42.3.4-beta — 2026-07-23¶
Fixed¶
- "Enter Maintenance" could silently leave VMs stuck, and permanently lock
a compute node's row for every future admin action against it — the
endpoint held one open DB transaction for the entire request, including a
live SSH capacity probe of every other host in the cluster
(
core/placement.score_hosts) that can run for several seconds against a slow or unreachable node. If the client/proxy gave up waiting mid-probe, the cancelled request left that transaction open, and the connection leaked back to the pool "idle in transaction" — blocking any later Maintenance/ Activate/Reactivate call for that same host indefinitely, with no error surfaced anywhere until someone found and killed the stuck DB session. The endpoint now uses short, separately-committed connections instead of one request-long transaction, so nothing is held open across the slow probe — a cancelled request just cancels, it doesn't wedge the row. - Postgres now also enforces a 2-minute
idle_in_transaction_session_timeoutfleet-wide (previously 0/disabled) as a backstop for this class of leak in general, not just this one endpoint.
0.42.3.3-beta — 2026-07-23¶
Added¶
- "Migrate VMs Back" failback action — a node returning from an HA fence
no longer leaves its VMs stranded on their emergency host forever. Each
failed-over VM now remembers the host it fenced away from
(
vms.ha_failed_over_from); once that host is healthy again, its right-click menu shows "↩ Migrate VMs Back (N)" so an operator can live-migrate them home in one click — offered, never automatic, since the operator is best placed to judge whether a just-returned node is actually ready for load. The flag clears on any explicit migration, so manually placing a VM elsewhere doesn't keep nagging to send it back to a home you moved it away from on purpose.
Fixed¶
- HA fence could fail with a spurious "No module named 'services'" on a
node's first-ever fence event —
core/fencing.py's Ceph blocklist fence re-importedservices.ceph_taskson every call instead of once; the import is now resolved eagerly at module load and cached, removing that class of failure. A node with no BMC/iDRAC configured (nested/lab hosts) also no longer attempts and logs a power fence that was never going to work — it now checks BMC availability up front and goes straight to the Ceph storage fence.
0.42.3.2-beta — 2026-07-22¶
Fixed¶
- Storage migration to/from Ceph could hang indefinitely — the disk-convert
helper behind Move Disk read
qemu-img convert's progress from stdout only after its stderr had been fully drained, a deadlock risk any time the child writes enough to stderr to fill the pipe buffer before finishing. Local/ZFS/NFS moves rarely write to stderr so this stayed hidden, but the Ceph (RBD) path routes through librbd/librados, which is chatty enough to trigger it — a migration to or from Ceph HA storage could sit stuck at "Running" forever instead of completing or failing. stderr is now drained concurrently on a background thread so it can never block the progress read. - ISO/large file upload showed no progress — the file browser's Upload
button (Datastores → Browse) sent the file via
fetch(), which gives no upload-progress signal; a multi-GB ISO would sit behind a static "Uploading…" toast for however long the transfer took, indistinguishable from a hang. Now usesXMLHttpRequestwithupload.onprogressto drive a live percentage bar and transfer rate, and turns red with the actual error detail (e.g. a Caddy 413) instead of a bare "Upload failed" on failure. - Large ISO uploads rejected outright — Caddy's shared
request_bodycap was 2GB, applied to every route behind it including the upload endpoint, so anything past 2GB never reached the app. Raised to 20GB.
0.42.3-beta — 2026-07-21¶
Added¶
- Resizable inventory sidebar — drag the right edge of the datacenter tree
(the Hosts / VMs / Storage / Networks navigator on the left) to widen it.
Previously fixed at 280px, so VM and host names with an IP suffix (e.g.
MF-VDI-04 (192.168.x.x)) were clipped by the tree's ellipsis with no way to see the full name. Width persists across sessions (200–640px range).
Fixed¶
- Cluster/folder row action icons stranded far from the name — the ⓘ /
✎ / 📁+ / 🗑 buttons on cluster and folder rows used
margin-left:auto(and the row label usedflex:1 1 auto), both of which pin trailing content to the row's far right edge. That was invisible at the old fixed 280px width but left a large empty gap after short names once the sidebar could be widened. Icons now sit immediately after the name.
0.42.2-beta — 2026-07-21¶
Image hardening pass — no application code changes, console behavior is identical to 0.42.0-beta. Ships from a new public registry (see below).
Security¶
- Shipped image CVEs cleared (21 → 0) — the published
mfconsole/mfconsoleDocker image carried 8 HIGH / 10 MEDIUM / 3 LOW vulnerabilities in OS-level Python packages (cryptography,urllib3,setuptools,jwcrypto,requests,idna) pulled in transitively by the base image's package manager — never imported by the running app, but visible to any image scanner (Docker Scout, Trivy, etc.) now that the repo is public.Dockerfile.ship's runtime stage now patches these in place and clears the stale package metadata so scans come back clean.
Changed¶
- Distribution moved to a public Docker Hub org —
mfconsole/mfconsolereplaces the prior self-hosted, token-gatedregistry.mfcloud.io. Nodocker loginis required to pull. Seemfconsole-ship/README.mdfor the access-control tradeoff this implies. - One-command install — the deploy-files bundle now includes
install.sh, which wraps.envsetup,setup.sh, image pull, anddocker compose upinto a single step.
Fixed¶
setup.shnever generatedDB_PASS— it shipped as the literal placeholder string instead of a random value; now generated like the other secrets.DEFAULT_ADMIN_PASSplaceholder mismatch — following the documented install steps left the first-login password as the raw placeholder string instead of the documentedmfprodefault.
0.42.1-beta — 2026-07-21¶
Added¶
- Editions / licensing system — MFCloud now ships six editions: Free
(default, 2 nodes / 10 VMs, no paid features), Homelab ($14.99/mo, 3
nodes / 100 VMs, personal/non-commercial use), Starter ($29/mo, 5 nodes,
unlimited VMs, adds S3 backup), Growth ($79/mo, 10 nodes, unlimited VMs,
same feature set as Starter — bridges the old Starter→Business price
cliff), and Beta/Pro (unlimited nodes and VMs, every feature flag
on: HA, S3 backup, multi-tenancy, LDAP, AIOps, Commander, Kubernetes).
License keys are Ed25519-signed (
MFC2.<payload>.<sig>— the signing private key never ships; only the public key is embedded, so a customer with full source access still cannot forge a key) with the original HMAC-signedMFC1format still accepted for compatibility with any beta test keys already handed out. A lapsedvalid_untildowngrades a deployment to Free-tier caps at read time without touching the stored license row or any existing VM/node — enforcement only blocks new node/VM creation and gated-feature access (core/license.py,routers/system/license.py, Settings → License). Node/VM limits and feature gates (assert_can_add_node,assert_can_create_vm,assert_feature) are enforced at every relevant create path across hosts, VMs, HA, S3 backup, multi-tenancy, LDAP, AIOps, Commander, and Kubernetes. An optional, fire-and-forget activation ping (LICENSE_ACTIVATION_URL, off by default) lets the vendor flag a paid key later reused on a second deployment for manual follow-up — it never blocks or remotely disables a console; a valid key is honored fully offline regardless.
0.42.0-beta — 2026-07-17¶
V40 (Managed Kubernetes Engine) completion pass. The HA and LINSTOR features are code-complete but not yet live-tested — see
KNOWN_ISSUES.md → Kubernetesfor the pending checklist.
Added¶
- YAML manifest deployments — each cluster row has a new YAML button:
upload (or paste) a raw Kubernetes YAML file — multi-document supported —
and the console runs
kubectl applyon the cluster's control-plane. Manifests are listed in their own table with kubectl's own created/configured output as the status detail; re-uploading under the same manifest name updates it in place (declarative apply), and Delete replays the stored YAML throughkubectl delete -f. Uploads are pre-parsed server-side so a YAML typo fails immediately, not minutes into the apply task. - Per-deployment logs — App Catalog deployments and YAML manifests each have a Log button showing the full deployment trail (Queued → Deploying → Waiting for pods → Success/Failed, including error details), on top of the live Recent Tasks stream that already existed.
- MCP: deploy apps from any MCP client — the mfcloud-mcp server gains
six tools:
list_kubernetes_clusters,list_app_catalog,list_app_deployments,get_app_deployment_log, and the confirmation-gatedpropose_app_deployment/confirm_app_deploymentpair (same server-enforced propose→review→confirm flow as VM deployment; single-use 5-minute tokens bound to the engineer's own API key). - HA Kubernetes control plane — the deploy wizard now offers a 3-node control plane (embedded etcd quorum) behind a kube-vip virtual IP, chosen at create time. Workers and the downloaded kubeconfig target the VIP, so losing any single control-plane node no longer takes down the API. The VIP is leased from a normal IPAM pool (visible in the leases table, released on cluster delete) and must come from a different pool than MetalLB's. Existing single-CP clusters and the 1-CP path are unchanged. Day-2 operations (node list, worker scaling, storage/networking retries, App Catalog) are now control-plane-failover aware on all clusters.
- LINSTOR CSI storage — clusters can attach to the platform's existing
external LINSTOR controller: every K8s node becomes a diskless satellite,
data replicas stay on the compute-node satellites via a dedicated resource
group, and a
mfcloud-linstorStorageClass (expansion enabled,WaitForFirstConsumer) is created. Joins the existing Ceph CSI option. - Per-tenant Kubernetes cluster quota —
max_k8s_clustersceiling (0 = unlimited) enforced at cluster create, settable from the IAM Resource Quotas widget alongside vCPU/RAM. - Real TLS certificates (internal PKI) — new
TLS_MODEin.env, applied bysetup.sh(regenerates the Caddy TLS config intls.d/):internal(self-signed, unchanged default),byo(operator cert from AD CS or any internal CA, dropped into./tls/; optional IP-SAN coverage for the by-IP site viaTLS_BYO_COVERS_IP), andacme-internal(auto-issue and renew from an internal ACME CA such as step-ca). Trusting the built-in Caddy root CA (export + GPO) is documented as the zero-config path.setup.shauto-addshttps://<TLS_DOMAIN>toALLOWED_ORIGINS.
Changed¶
- OVN-Kubernetes CNI formally dropped from the roadmap — upstream owns its own in-cluster OVN stack and cannot attach to MFCloud's external OVN fabric; Flannel/Canal + MetalLB remain the supported path.
Fixed¶
- K8s worker auto-naming no longer collides after a scale-down — the next
-wNsuffix is now derived from the highest existing suffix instead of the worker-list length, so removingw2and then adding a worker no longer re-mintsw3and fails with "VM name already taken". - Device-mode ZFS clusters get the right scale-up pre-flight — adding a
worker to a
zfs_pool_source='device'cluster no longer applies the file-mode "disk_gb ≥ pool file + 10 GB" guard (the request-model default of 50 GB was being persisted even for device clusters), and the API now enforces what device mode actually needs: acsi_zfs_pool_name/size for every auto-provisioned worker (previously only the UI checked this). - MetalLB pool collision checks now also run at cluster create — the create wizard's MetalLB pool is rejected if it is any cluster's API-VIP pool or already another cluster's MetalLB pool (two MetalLB instances on one range assign duplicate Service IPs); previously only the day-2 Configure Networking path checked, and pool sharing wasn't blocked at all.
0.41.6-beta¶
Added¶
- AI Assistant panel: resize + per-answer stats — the chat panel can now be resized from its top-left corner (size persists, like its position), and each answer shows a stats line: a live "Thinking · Ns" timer while the model works, then total response time and token usage (in/out) reported by the backend (Claude API usage, or Ollama's eval counts).
Fixed¶
- AI Assistant input no longer locks up after a backend error — an error mid-turn (e.g. an Ollama timeout) ended the stream without the completion envelope, leaving the message box disabled until the panel was reopened.
0.41.0-beta — current¶
Upgrading an existing deployment: the compose network is now pinned to a fixed subnet (
172.30.238.0/24). Docker Compose cannot change a network in-place — rundocker compose downonce beforeup -dwith this version.
Added¶
- Kubernetes worker scaling — each cluster row now has a Nodes modal showing a live node snapshot (name, IP, role, Ready state), with Scale Workers to join additional workers onto a healthy cluster and drain-and-remove a single worker. Progress is reported through Recent Tasks.
- App Catalog — deploy curated application stacks (Ollama + Open WebUI, Jupyter + PyTorch, ComfyUI, vLLM) onto an MFCloud-managed Kubernetes cluster from the new App Catalog in the Kubernetes tab. Apps are exposed via a MetalLB LoadBalancer when available, NodePort otherwise. All stacks are CPU-only in this release (GPU support arrives with GPUaaS).
- Ask AI from search — the Ctrl+K search box now offers an "Ask AI" row on any query, handing it to the console's AI chat panel — useful for questions ("why is this VM slow?") that no inventory name matches.
Changed¶
- License gating extended — LDAP, AIOps, and the AI assistant are now edition-gated alongside HA, S3 backup, and multi-tenancy. Existing keys for editions that include a newly gated feature keep it automatically — no key re-issue needed.
Security¶
- Hardening pass (2026-07-13) — 17 fixes from a full-codebase review, including: a tenant-isolation bypass on VM power actions and a cross-tenant VM telemetry route; a cloud-init SSH-key injection; Ceph dashboard and LDAP/OIDC bind secrets now encrypted at rest (AES-256-GCM); the worker→master internal callback now pins the master's TLS certificate fingerprint instead of skipping verification; a race in SSH host-key first-pinning; a narrowed trusted-proxy range on the login rate limiter (with the compose-network subnet pin above); RBAC initialization failures now abort startup instead of serving with half-seeded permissions; and database-failover handling that discards connections to a demoted primary.
Fixed¶
- Dashboard could hang when a compute node went offline — libvirt connections to unreachable nodes are now bounded, so the VM list stays responsive while a node is down.
- App Catalog: Jupyter + PyTorch deploys crash-looped — two environment collisions (a template-variable clash and Kubernetes' auto-injected service port variable overriding the image's own port setting) are both fixed.
- Terraform
destroyreturning a 500, a route-shadowing bug that hid the pending-node-approval UI, and avirshregression onlocalhost-managed hosts. - Shipped console images now include the Emergency Host UI overlay
(
agent_overlay/), which was silently missing from registry builds.
0.40.5-beta¶
Covers 0.40.1–0.40.5; combined here since they shipped in quick succession.
Added¶
- Usage Reports — new admin Reports tab: Compute and Network usage over a chosen time range, plus a point-in-time Storage snapshot, each exportable to CSV.
- Host anomaly detection — the telemetry pipeline now flags hosts whose CPU/RAM/disk/network behavior breaks from their own recent baseline.
Changed¶
- Datacenter-console visual reskin — new masthead color tokens (navy header in both light and dark themes), flatter corner radii, uppercase button labels, underlined tabs, and reduced glow/shadow throughout; fixed a light-mode readability issue in the inventory tree.
- Telemetry pipeline — the background sampler now runs its per-host collection in parallel (fixes an intermittent stall), and the rollup used by Reports/telemetry charts was widened to cover disk and network in addition to CPU/RAM, making the Reports tab noticeably faster to load.
Fixed¶
- Network security groups were not being enforced at all — ACL pushes were silently targeting a logical switch that didn't exist, and the failure was swallowed. Security groups now resolve the VM's real switch and correctly union rules across multiple assigned groups.
- IPAM: a lease-allocation race, a reserved-IP bypass, a leftover logical switch port on delete, and a pool-naming conflict were all fixed.
- Storage: an orphaned LINSTOR resource-definition case, a datastore
name-conflict split-brain, several leaked SSH connections, and missing
guards around
iostatoutput and node-count edge cases.
0.40.0-beta¶
Added¶
- ZFS pool replication + VM restore (DR) — schedule async, snapshot-based
replication of a ZFS pool to another host in the same cluster, with a live
progress bar (when
pvis installed) and a one-click Restore to rebuild and boot the replicated VMs on the target if the source goes down. See the new Knowledge Base guide: ZFS Pool Replication & VM Restore (DR). - Windows Secure Boot + vTPM 2.0 across all three guest paths — fresh deploy, V2V import, and the VM Settings firmware toggle. Fresh deploys enroll Microsoft's keys automatically for a Windows guest on UEFI; V2V import and the Settings toggle preserve the guest's existing UEFI variable store (so boot entries and any already-enrolled keys survive).
- New Knowledge Base guide: Install a Windows VM (Server & 11) — VirtIO driver loading, firmware/Secure Boot settings, and converting to a reusable template.
- What's new in this release — the console now shows a short release note the first time you log in after an update, with a bell icon in the header to reopen it any time. Sourced from this same changelog.
Changed¶
- The Deploy VM dialog and VM Settings → Boot Options now expose a Secure Boot control (Auto / On / Off) alongside Machine Type and Firmware.
V39.17-alpha — public-alpha hardening¶
The V39.13 → V39.17 band is the public-alpha hardening track. See
SECURITY_AUDIT.md and KNOWN_ISSUES.md for the security posture and the
short list of accepted limitations.
Added¶
- Compact density mode — console-wide density toggle in the top nav (Comfortable ↔ Compact), persisted per-browser and applied flash-free, driven by a single density-token scale so every table/card/widget re-paces at once.
- Sticky table headers — column headers stay pinned while long VM / host / task lists scroll; sharper row-hover with an accent edge.
- Console identity — the inventory-tree root now shows the server hostname
(or
CONSOLE_NAMEfrom.env) so engineers managing several consoles can tell them apart at a glance.
Security (hardening pass — see SECURITY_AUDIT.md "Audit Pass 2")¶
- Secrets hygiene:
.gitignore+.env.example;setup.shbootstraps.envand generatesDB_PASS/DEFAULT_ADMIN_PASS/REDIS_PASSWORD. - Redis broker
requirepass+ provisioner credential-preserving URL rewrite. - Forced first-login password change (
must_change_password+ blocking overlay). - Closed cloud-init YAML injection, config-export secret leak, OIDC token-in-URL,
cross-tenant VM access, unauthenticated backup/snapshot routes, webhook SSRF,
and hardcoded admin elevation. Weak
INTERNAL_SECRETnow fails closed.
V39 — Distributed clustering, DR, and backup UX¶
- V39 / V39.0.2 — Distributed container clustering: native LXD/Incus dqlite quorum, replicated container storage (LINSTOR/DRBD), free-RAM scheduler, live migration, and HA auto-evacuation of workloads off failed members.
- V39.6 — Console navigation & UX pass: neutral product naming pass; inventory and recent-tasks polish.
- V39.7 — PCIe passthrough in VM Settings; UEFI-VM lifecycle fixes.
- V39.8 — Agentless S3 Disaster Recovery: dirty-bitmap incrementals, ZSTD streaming, AES-256-GCM direct-to-cloud upload, one-click cloud restore.
- V39.9–V39.12 — Backup UX: cluster-scoped NFS datastores, destination picker, scheduled backups v2, capacity bars, and live task progress.
V38 — Containers, marketplace, and identity federation¶
- V38 — LXD / Incus container support: lifecycle, interactive console (xterm.js), live metrics, snapshots/restore, remote image import, and OVN logical-switch binding.
- V38.2 — VM table right-click context menu.
- V38.3 — Agent callback restoration & VM inventory discovery.
- V38.4 — Turnkey marketplace & dedicated Catalog tab.
- V38.5 — Trust pinning, recent-tasks polish, NFS hardening, light/dark theme.
- V38.7 — Identity federation (OIDC/SSO): "Sign in with Microsoft" (Entra ID, Google, Okta), auto-provisioning, group-claim → role mapping, RP-initiated logout, shorter OIDC token TTL.
V37 — Database high availability¶
- V37 — Patroni + etcd + HAProxy: TimescaleDB in HA; writes routed to the current primary; sync (psycopg2) and async (asyncpg) pools.
V31–V36 — Platform foundation¶
- V31.6 — Enterprise identity: LDAP / Active Directory authentication.
- V31.7 / V31.8 — Performance tab redesign and inventory tree.
- V31.10 — SDN: OVN logical networks + security groups.
- V31.11 — LINSTOR REST API rewrite + DRBD cluster wizard.
- V31.12 / V36 — Hypervisor ISO + imaged-node fast path: zero-touch node provisioning and enrollment.
- V31.13 — Security audit pass; Caddy TLS termination; password-only SSH path.
- V32 — Agent Trust API: TOFU host-key pinning, agent credential delivery.
- V33 — Releases incl. V33.2 iDRAC / BMC hardware health monitoring.
- V34 — Cloud-Init guest customization.
- V36.1 — Knowledge base bootstrap (served at
/kb).
V23–V30 — Initial platform¶
- Core VM lifecycle (create/start/stop/delete/edit), snapshots & backups with retention, clone + template conversion, live/cold migration.
- VNC console proxy (noVNC) and web SSH terminal over WebSocket.
- Host auto-bootstrap, maintenance + VM evacuation, multi-host telemetry.
- OVS VLAN portgroups, KVM NAT networks, NFS datastores, image management.
- Ceph cluster bootstrap + OSD management; ZFS pool management.
- JWT + API-key auth, RBAC, per-user vCPU/RAM quotas, webhook alerts, task history, real-time task streaming.