Skip to content

iso-detection — Integration with the ISO library

The detector pipeline runs automatically every time an operator uploads an ISO. The dashboard never has to call the detection API directly — it just polls the cache.

crates/daemon/src/api/isos.rs is the existing upload handler. After the streamed body lands at its final path the handler spawns a background blocking task:

let detect_path = final_path.clone();
tokio::task::spawn_blocking(move || {
match crate::iso_detection::detect_path(&detect_path) {
Ok(result) => {
tracing::info!(
iso = %detect_path.display(),
detector = %result.detector,
profile_id = %result.hint.profile_id,
confidence = ?result.confidence,
"ISO detected"
);
if let Err(e) = crate::iso_detection::store::cache_for_path(&detect_path, &result) {
tracing::warn!(?e, "ISO detection cache write failed");
}
}
Err(e) => {
tracing::warn!(iso = %detect_path.display(), error = %e, "ISO detection failed");
}
}
});

Three properties matter here:

  1. The HTTP response returns immediately. Detection happens out of band; the dashboard’s “uploaded” toast fires before the pipeline starts.
  2. Detection is best-effort. A detector failure (mount refused, wimlib missing, no detector matched) is logged but never breaks the upload — the file is still in the library and the operator can pick a profile manually.
  3. The cache is the source of truth for the dashboard. Anything that wants to know “what is this ISO” reads GET /api/v1/iso-detection/results, not the live pipeline.
upload handler --> spawn_blocking
|
v
detect_path() -> DetectionResult
|
v
store::cache_for_path(path, result)
|
sha256 the file (streaming)
|
v
prisma row absent? -> sled.put(sha256, Entry)
prisma row exists? -> sled.update(...) (refresh sha + size)

The sled tree is iso-detection. Keys are hex SHA-256 strings; values are JSON-serialised Entry blobs. Database location:

  • prod: /var/lib/synvirt/iso-detection.db/
  • dev (SYNVIRT_DEV set): $HOME/.synvirt/iso-detection.db/

The dashboard’s ISOs view fans out to two endpoints in parallel:

GET /api/v1/isos → flat list of files
GET /api/v1/iso-detection/results → cache contents (sha256-keyed)

It joins them client-side on filename / sha256 to render rows like:

Filename Size Detected Profile suggestion
Win11_23H2_x64.iso 5.3 GiB windows_modern (high) windows-11
ubuntu-24.04-live-server-amd64.iso 2.7 GiB ubuntu (high) generic-other (until v0.11)
nas-recovery-2024.iso 312 MiB generic (low) generic-other

ISOs that landed before this module shipped show “—” in the “Detected” column until the operator hits “Re-detect” (which calls POST /api/v1/iso-detection/re-detect and refreshes the cache).

Terminal window
curl -fsS -u admin:hunter2 -H 'content-type: application/json' \
-d '{"iso_path":"/var/lib/synvirt/isos/Win11_23H2_x64.iso"}' \
https://node.local/api/v1/iso-detection/re-detect | jq

Today this endpoint is a synchronous probe. v0.10.1 will also write through to the cache so the dashboard’s results endpoint reflects the new run automatically — see ROADMAP.md.

The original v0.10.0 spec mentioned /api/v1/storage-pools/{id}/isos. The current daemon doesn’t have per-pool ISO buckets — /api/v1/isos is a flat library at /var/lib/synvirt/isos/. When per-pool ISO folders ship (planned for the storage refactor), the upload handler already passes the final path into the detector unchanged, so detection will keep working without any change to this module.

Symptom Cause Fix
Cache empty after upload Pipeline crashed before reaching the store, or sled DB couldn’t open Check daemon logs for “ISO detection failed” / “ISO detection cache write failed”
wimlib-imagex warnings wimtools package not installed on the host apt install wimtools (already in the live-build package list as of 0.10.0)
mount: failed Loop devices exhausted (/dev/loop* busy) systemd’s periodic loop-detach should clear stale ones; manually losetup -d /dev/loop* if needed
Detection succeeded but profile_id = "generic-other" for a Linux ISO Linux family vm-profiles haven’t shipped yet (v0.11) Pick the base profile manually in the dashboard for now