Skip to content

Create VM: default install source to a storage pool marked for ISOs#2683

Open
schaerfl wants to merge 1 commit into
cockpit-project:mainfrom
schaerfl:iso-install-media-pool
Open

Create VM: default install source to a storage pool marked for ISOs#2683
schaerfl wants to merge 1 commit into
cockpit-project:mainfrom
schaerfl:iso-install-media-pool

Conversation

@schaerfl

@schaerfl schaerfl commented Jun 23, 2026

Copy link
Copy Markdown

Problem

When creating a VM with Local install media (ISO image or distro install tree), the Installation source field is a free-path typeahead that always opens at the filesystem root (/). Users who keep their ISOs in a dedicated location — very commonly a dir storage pool at e.g. /var/lib/libvirt/iso — have to navigate down from / (/, /var, /var/lib, …) every single time they create a VM, even though they already told libvirt where their install media lives when they defined that pool.

There is currently no way to say "this pool holds my installation media, start there."

What this does

Let the user mark a storage pool as installation media with a toggle on the pool's Overview tab (shown for dir and netfs pools).

When at least one pool is marked, the Create VM → Local install media source field is pre-filled with that pool's target directory, so the path picker opens directly inside the ISO directory and the user only has to pick the image.

With no pool marked, behaviour is completely unchanged — the field still defaults to /. The feature is purely opt-in.

Screenshots: pool Overview tab with the "Use for installation media" switch, and the Create VM dialog with the source pre-filled to /var/lib/libvirt/iso/ — to be added.

Why it's built this way

Where the marker lives

The natural place to store "this pool is for ISOs" would be on the pool itself. That isn't possible:

  • libvirt storage pools have no <metadata> element. Domains have <metadata> + virDomainSetMetadata, but pools do not. The only escape hatch — storage-pool namespaces — is documented by libvirt as carrying "no support guarantees… should never be used in production."
  • The pool type is a fixed enum (dir, netfs, iscsi, …), validated on define, so a synthetic iso-dir type is rejected.

So the marker is stored host-side in a small Cockpit-owned JSON file keyed by pool UUID:

Connection Path
system /etc/cockpit/machines/install-media-pools.json (written with superuser: "try")
session ~/.config/cockpit/machines/install-media-pools.json
{ "pools": ["7fb3a128-ac8a-4f6e-8e10-581c2321b6a4"] }

This mirrors existing cockpit-machines behaviour — the code already reads /etc/libvirt/qemu.conf via cockpit.file(...) (src/helpers.ts) and writes host files via .replace(). Keying on UUID (not name) means renaming a pool is safe; a UUID whose pool no longer exists is simply ignored and pruned on the next write.

Implementation (3 files)

  • src/libvirtApi/installMediaPools.ts (new)getInstallMediaPools() / setInstallMediaPool() read and atomically update the config via cockpit.file(path, { syntax: JSON }).modify(...), per connection.
  • src/components/storagePools/storagePoolOverviewTab.tsx — a Switch ("Use for installation media") for dir/netfs pools that have a UUID, with an optimistic update that reverts if the write fails.
  • src/components/create-vm-dialog/createVmDialog.tsxprefillInstallMediaSource() runs both on dialog mount and when the user switches the installation type to local media; it sets the source to the marked pool's target/path only when the user hasn't supplied/typed a source.

One subtlety worth flagging for review: FileAutoComplete reads its value prop only in its constructor (no componentDidUpdate), so an async pre-fill arriving after mount is otherwise ignored. The dialog gives that field a one-shot key (installMediaSourceKey) that bumps exactly once when the pre-fill lands, forcing a single remount so the field picks up the value — without remounting on every keystroke (which would steal focus).

Trade-offs / things to discuss

I want to be upfront about the design decisions rather than oversell — the core UX win is clear, but there are real points for maintainers to weigh:

  • Cockpit-owned state file. cockpit-machines is otherwise a thin, mostly stateless view over libvirt. This introduces a small piece of app-owned configuration on the host. I think that's justified given libvirt offers no place to put it, but it's a genuine departure and the main thing I'd like a decision on. (Alternative: file this as a libvirt RFE for pool metadata and wait — much slower, and arguably overkill for a UI convenience.)
  • Multiple marked pools. The current behaviour pre-fills from the first matching pool. If marking several pools is a real use case, this could become a small selector instead. Easy to change once the direction is settled.
  • Stale entries. If a marked pool is deleted, its UUID lingers in the file until the next toggle write prunes it. Harmless (unmatched UUIDs are ignored), but noted for completeness.
  • localStorage was considered and rejected — it's per-browser/per-user and wouldn't reflect server state or be visible to another admin, which is wrong for a server-management tool.

Testing

Verified manually end-to-end on against a live system (Arch Linux) connection: marking the pool writes the config file; the Create VM dialog pre-fills /var/lib/libvirt/iso/ and the picker opens there; un-marking reverts to /. npm run eslint is clean and there are no new type errors.

Integration tests (test/check-machines-storage-pools / check-machines-create) are not yet included — I'd like to confirm the design direction (especially the host-side config file) before writing them, hence opening as a draft. Happy to add them, or to split the rationale into a separate issue first if that's preferred.

Screenshots

Storage Pool config:

image

Create VM autofill in dialog:

image

🤖 Generated with Claude Code

The "Local install media" installation source is a free-path typeahead
that always starts at the filesystem root, so users with a dedicated ISO
directory (e.g. a dir pool at /var/lib/libvirt/iso) must navigate down
from "/" on every VM creation.

Let the user mark a storage pool as installation media on the pool's
Overview tab. When such a pool exists, the Create VM dialog pre-fills the
local install-media source with the pool's target directory, so the path
picker opens inside the ISO directory. With no pool marked, behaviour is
unchanged and the field still defaults to "/".

libvirt storage pools have no <metadata> element to persist such a flag
(unlike domains), and the pool type is a fixed enum, so the marks are
stored host-side in a small Cockpit-owned file keyed by pool UUID
(/etc/cockpit/machines/install-media-pools.json for the system
connection, ~/.config/cockpit/machines/ for the session connection),
mirroring how the code already reads /etc/libvirt/qemu.conf via
cockpit.file().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@schaerfl schaerfl marked this pull request as ready for review June 23, 2026 09:45
@mvollmer

Copy link
Copy Markdown
Member

When creating a VM with Local install media (ISO image or distro install tree), the Installation source field is a free-path typeahead that always opens at the filesystem root (/). Users who keep their ISOs in a dedicated location — very commonly a dir storage pool at e.g. /var/lib/libvirt/iso — have to navigate down from / (/, /var, /var/lib, …) every single time they create a VM, even though they already told libvirt where their install media lives when they defined that pool.

We are currently working on improving this with a new File Chooser widget, see cockpit-project/cockpit#23380.

Here is a demo of how this would look in Cockpit Machines.

I'll check your PR here to see how this interacts with your proposal.

@mvollmer

Copy link
Copy Markdown
Member

I'll check your PR here to see how this interacts with your proposal.

With the File Chooser, checking the "Use for installation media" flag would add the directory of the pool to the shortcuts in the sidebar. All of them would be shown.

But instead of making this a property of a storage pool, I would add general bookmarks to the File Chooser dialog. I think the checkbox on the storage pool page is too far away from where it's effect can be observed. Managing bookmarks right in the dialog should give the same benefits and be easier to discover and understand.

Where to store the bookmarks (browser storage, host file system) would still need to be answered. For bookmarks, my initial reaction would be to store them in browser localStorage. They are sufficiently "personal" and much less "system config".

Thanks for the contribution! What do you think?

@schaerfl

Copy link
Copy Markdown
Author

I've setup/administrated some Proxmox Environments and inspired myself from the ISO directory selection. Its kinda similar to my implementation. When setting up a disk (in Cockpit a storage pool) you choose the contents of the disk and when you select ISO, it appears as a selectable source for ISOs in the VM setup and automatically gives you the ISO options which are on the disk / in the selected path. Maybe this way of implementation is a little to complicated, as you mentioned correctly.

I really like the File Chooser with automatic search and listing for .iso files, aswell as the bookmark idea and i totally agree, that the bookmark idea is more intuitive and easier to find.

Regarding the browsers localStorage... I had this thought aswell but scratched it.
I get Bookmarks are personal, maybe bookmarks should be saved in the server state, but per user? Therefore if the same admin account is used from two separate administrators, they would have the same bookmarks. But if these two separate administrators each have an account for their own, they could both have separate bookmarks.
Also I didnt do much with localStorage yet, but does it get cleared by deleting the browser cache or does it invalidate over time?
Also one more thing, when switching browser/device you would loose all your bookmarks right? An option to export and import bookmarks as .json (maybe) would be a great idea in this case.

@mvollmer mvollmer self-assigned this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants