Skip to content

fix: broken ESM entry, colliding net ids, daemonize pidfile races - #1

Merged
AlexanderSlaa merged 1 commit into
mainfrom
fix/daemonize-multi-net-virtio-drives
Sep 3, 2026
Merged

fix: broken ESM entry, colliding net ids, daemonize pidfile races#1
AlexanderSlaa merged 1 commit into
mainfrom
fix/daemonize-multi-net-virtio-drives

Conversation

@AlexanderSlaa

Copy link
Copy Markdown
Contributor

Summary

Four bugs found together while using this library to daemonize 3-NIC,
2-virtio-drive VMs from a real provisioning script (nested-VM cluster PoC):

  • exports["."].import pointed at dist/index.es, an extension Node's
    ESM loader doesn't recognize -- plain import "node-qemu" fails with
    ERR_UNKNOWN_FILE_EXTENSION. The package was unusable via import as
    published on npm (1.2.1), only via require. Renamed the ESM build
    output to dist/index.mjs.
  • Every net entry without an explicit id defaulted to the literal
    "net0" regardless of position (unlike drives, which already
    indexes its default id as drive${i}), so 2+ net entries collided
    with Duplicate ID 'net0' for netdev.
  • A virtio: true drive never got if=none on its -drive clause, so
    QEMU's legacy "no if= means if=ide" auto-attach fought the explicit
    -device virtio-blk-pci attach: Drive 'driveN' is already in use ... did you need 'if=none'?.
  • With daemonize: true, the spawned process forks and exits almost
    immediately as part of normal, successful daemonization -- but the
    exit handler treated that exit like the real process dying and
    deleted config.pidfile, racing against (and usually winning against)
    QEMU's own -pidfile write for the actual detached daemon. Both the
    manager's own pre-write and the delete-on-exit are now skipped when
    daemonize is set; QEMU owns that file exclusively in that mode.

Test plan

  • npx vitest run -- 101/102 pass; the 1 remaining failure
    (tests/image/qemu-img.test.ts, a require() of a .ts path)
    pre-exists on main unrelated to this change, confirmed via
    git stash.
  • Added regression tests for all 3 args.ts/manager.ts fixes.
  • npx vite build then node -e "import('./dist/index.mjs')..." --
    resolves and exposes all expected exports.
  • End-to-end: launched a real 3-NIC, 2-virtio-drive, daemonized
    qemu-system-x86_64 VM with the patched build; boots, all 3 taps
    come up with correct MACs, SSH reachable, pidfile stable after the
    launcher process exits.

Note: package-lock.json is untouched -- installing locally with npm
9.2.0 (this sandbox's version) rewrote unrelated libc metadata across
the lockfile; reverted that noise before committing.

… races

- package.json/vite.config.ts: the ESM build output was named
  dist/index.es, an extension Node's loader doesn't recognize as
  JavaScript (ERR_UNKNOWN_FILE_EXTENSION on plain `import "node-qemu"`).
  Renamed to dist/index.mjs, unambiguously ESM regardless of any
  consumer's own package.json "type".

- args.ts: every QemuNet entry without an explicit `id` defaulted to the
  literal "net0" regardless of position, so 2+ net entries collided with
  "Duplicate ID 'net0' for netdev". Now defaults to `net${index}`,
  mirroring how drives already default to `drive${index}`.

- args.ts: a `virtio: true` drive never got `if=none` on its -drive
  clause, so QEMU's legacy "no if= means if=ide" behavior auto-attached
  it to an IDE device, and the explicit -device virtio-blk-pci attach
  then failed with "Drive 'driveN' is already in use ... did you need
  'if=none'?". virtio drives now emit if=none.

- manager.ts: with daemonize:true, the spawned process forks and exits
  almost immediately as part of normal, successful daemonization -- but
  the exit handler treated that exit like the real process dying and
  deleted config.pidfile, racing against (and usually winning against)
  QEMU's own -pidfile write for the actual detached daemon. Skip both
  the manager's own pre-write and the delete-on-exit when daemonize is
  set; QEMU owns that file exclusively in that mode.

All four surfaced together while daemonizing a 3-NIC, 2-virtio-drive VM.
Added regression coverage for each in tests/process/args.test.ts and
tests/process/manager.test.ts.
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

@AlexanderSlaa
AlexanderSlaa merged commit 2af9401 into main Sep 3, 2026
8 checks passed
@AlexanderSlaa
AlexanderSlaa deleted the fix/daemonize-multi-net-virtio-drives branch September 3, 2026 20:32
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.

1 participant