pixie-lab 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/.github/workflows/cicd.yaml +68 -0
  2. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/CHANGELOG.md +16 -0
  3. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/Makefile +10 -1
  4. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/PKG-INFO +1 -1
  5. pixie_lab-0.3.2/cijoe/configs/test-usb-ventoy.toml +48 -0
  6. pixie_lab-0.3.2/cijoe/scripts/usb_iso_verify.py +92 -0
  7. pixie_lab-0.3.2/cijoe/scripts/usb_ventoy_guest_start.py +48 -0
  8. pixie_lab-0.3.2/cijoe/scripts/usb_ventoy_stage.py +231 -0
  9. pixie_lab-0.3.2/cijoe/tasks/test-usb-ventoy.yaml +119 -0
  10. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pyproject.toml +1 -1
  11. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/.dockerignore +0 -0
  12. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/.github/workflows/docs.yml +0 -0
  13. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/.gitignore +0 -0
  14. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/Containerfile +0 -0
  15. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/LICENSE +0 -0
  16. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/PLAN.md +0 -0
  17. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/README.md +0 -0
  18. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/netboot-pc.toml +0 -0
  19. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/test-pxe-flash-always.toml +0 -0
  20. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/test-pxe-flash.toml +0 -0
  21. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/test-pxe-inventory.toml +0 -0
  22. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/test-pxe-nbdboot.toml +0 -0
  23. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/test-pxe-tui.toml +0 -0
  24. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/test-pxe.toml +0 -0
  25. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/configs/usbboot-pc.toml +0 -0
  26. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/live_build.py +0 -0
  27. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/pixie_ipxe_build.py +0 -0
  28. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/pixie_wheel_stage.py +0 -0
  29. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/pxe_flash_stage.py +0 -0
  30. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/pxe_inventory_stage.py +0 -0
  31. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/pxe_prepare.py +0 -0
  32. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/pxe_run_chain_test.py +0 -0
  33. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/scripts/usb_iso_build.py +0 -0
  34. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/netboot-pc.yaml +0 -0
  35. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/test-pxe-flash-always.yaml +0 -0
  36. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/test-pxe-flash.yaml +0 -0
  37. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/test-pxe-inventory.yaml +0 -0
  38. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/test-pxe-nbdboot.yaml +0 -0
  39. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/test-pxe-tui.yaml +0 -0
  40. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/test-pxe.yaml +0 -0
  41. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/cijoe/tasks/usbboot-pc.yaml +0 -0
  42. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/audit.md +0 -0
  43. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/src/boot-modes.md +0 -0
  44. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/src/conf.py +0 -0
  45. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/src/deployment.md +0 -0
  46. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/src/hardware-quirks.md +0 -0
  47. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/src/index.md +0 -0
  48. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/src/quickstart.md +0 -0
  49. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/tooling/README.md +0 -0
  50. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/tooling/pyproject.toml +0 -0
  51. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/tooling/src/pixie_docs/__init__.py +0 -0
  52. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/docs/tooling/src/pixie_docs/cli.py +0 -0
  53. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/README.md +0 -0
  54. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/auxiliary/cloudinit-metadata.meta +0 -0
  55. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/auxiliary/ipxe-embed.ipxe +0 -0
  56. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/auxiliary/ipxe-local-general.h +0 -0
  57. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/auto/config +0 -0
  58. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/archives/backports.list.binary +0 -0
  59. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/archives/backports.list.chroot +0 -0
  60. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/bootloaders/grub-efi/splash.png +0 -0
  61. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/bootloaders/syslinux/splash.png +0 -0
  62. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0500-pixie-install.hook.chroot +0 -0
  63. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0500-pixie-skip-bootloader-menu.hook.binary +0 -0
  64. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0600-pixie-r8125-dkms.hook.chroot +0 -0
  65. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0700-pixie-clock-from-http.hook.chroot +0 -0
  66. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0800-pixie-ssh-live.hook.chroot +0 -0
  67. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0900-pixie-enable-services.hook.chroot +0 -0
  68. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/hooks/normal/0980-pixie-apt-validate.hook.chroot +0 -0
  69. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/issue +0 -0
  70. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/modprobe.d/zz-pixie-blacklist-nouveau.conf +0 -0
  71. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/motd +0 -0
  72. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/profile.d/pixie-version.sh +0 -0
  73. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/getty@.service.d/zzz-pixie-autologin-root.conf +0 -0
  74. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-early.service +0 -0
  75. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-late.service +0 -0
  76. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-mid.service +0 -0
  77. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-clock-from-http.service +0 -0
  78. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-images-discover.service +0 -0
  79. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-on-tty1.service +0 -0
  80. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-usb-grow.service +0 -0
  81. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/var-lib-pixie-images.mount +0 -0
  82. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/etc/udev/rules.d/70-pixie-realtek-2g5-offloads.rules +0 -0
  83. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/lib/systemd/system-generators/pixie-skip-usb-only-units-on-netboot +0 -0
  84. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-boot-banner +0 -0
  85. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-clock-from-http +0 -0
  86. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-images-discover +0 -0
  87. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-on-tty1 +0 -0
  88. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-trace +0 -0
  89. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-usb-grow +0 -0
  90. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/package-lists/pixie-base.list.chroot +0 -0
  91. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/package-lists/pixie-flash.list.chroot +0 -0
  92. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/preferences/backports.pref.binary +0 -0
  93. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/pixie-media/live-build/config/preferences/backports.pref.chroot +0 -0
  94. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/__init__.py +0 -0
  95. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/_partition.py +0 -0
  96. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/_util.py +0 -0
  97. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/__init__.py +0 -0
  98. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/_fetcher.py +0 -0
  99. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/_live_env.py +0 -0
  100. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/_routes.py +0 -0
  101. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/_schema.py +0 -0
  102. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/_store.py +0 -0
  103. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/catalog/catalog.toml +0 -0
  104. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/deploy/__init__.py +0 -0
  105. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/deploy/_main.py +0 -0
  106. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/deploy/_templates.py +0 -0
  107. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/disks.py +0 -0
  108. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/events/__init__.py +0 -0
  109. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/events/_kinds.py +0 -0
  110. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/events/_log.py +0 -0
  111. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/events/_routes.py +0 -0
  112. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/exports/__init__.py +0 -0
  113. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/exports/_routes.py +0 -0
  114. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/exports/_store.py +0 -0
  115. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/exports/_supervisor.py +0 -0
  116. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/flash.py +0 -0
  117. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/images.py +0 -0
  118. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/machines/__init__.py +0 -0
  119. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/machines/_routes.py +0 -0
  120. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/machines/_store.py +0 -0
  121. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/oras.py +0 -0
  122. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/pivot/__init__.py +0 -0
  123. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/pivot/nbdboot +0 -0
  124. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/pxe/__init__.py +0 -0
  125. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/pxe/_renderer.py +0 -0
  126. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/pxe/_routes.py +0 -0
  127. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/tftp/__init__.py +0 -0
  128. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/tftp/_supervisor.py +0 -0
  129. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/tui/__init__.py +0 -0
  130. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/tui/_app.py +0 -0
  131. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/tui_catalog.py +0 -0
  132. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/__init__.py +0 -0
  133. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_auth.py +0 -0
  134. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_bind_preview.py +0 -0
  135. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_inventory.py +0 -0
  136. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_overlays.py +0 -0
  137. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_settings_store.py +0 -0
  138. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/.gitkeep +0 -0
  139. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/bootstrap-icons.min.css +0 -0
  140. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/bootstrap.min.css +0 -0
  141. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/fonts/bootstrap-icons.woff +0 -0
  142. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/fonts/bootstrap-icons.woff2 +0 -0
  143. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/htmx.min.js +0 -0
  144. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/pixie-favicon.png +0 -0
  145. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_static/sse.js +0 -0
  146. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_table_state.py +0 -0
  147. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/_partials/page_description.html +0 -0
  148. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/_partials/recent_events.html +0 -0
  149. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/_partials/table_helpers.html +0 -0
  150. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/catalog.html +0 -0
  151. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/catalog_detail.html +0 -0
  152. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/dashboard.html +0 -0
  153. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/events.html +0 -0
  154. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/ipxe/bootstrap.j2 +0 -0
  155. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/ipxe/exit.j2 +0 -0
  156. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/ipxe/nbdboot.j2 +0 -0
  157. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/ipxe/pixie-live-env.j2 +0 -0
  158. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/ipxe/unavailable.j2 +0 -0
  159. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/layout.html +0 -0
  160. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/live_env.html +0 -0
  161. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/login.html +0 -0
  162. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/machine_detail.html +0 -0
  163. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/machines.html +0 -0
  164. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/overlays.html +0 -0
  165. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/_templates/settings.html +0 -0
  166. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/src/pixie/web/main.py +0 -0
  167. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/__init__.py +0 -0
  168. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/conftest.py +0 -0
  169. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/__init__.py +0 -0
  170. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/conftest.py +0 -0
  171. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/test_catalog_fetch.py +0 -0
  172. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/test_deploy.py +0 -0
  173. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/test_exports.py +0 -0
  174. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/test_inventory.py +0 -0
  175. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/test_pxe.py +0 -0
  176. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/integration/test_tftp.py +0 -0
  177. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_auth.py +0 -0
  178. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_catalog_api.py +0 -0
  179. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_catalog_seed.py +0 -0
  180. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_deploy_unit.py +0 -0
  181. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_event_kinds.py +0 -0
  182. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_events_unit.py +0 -0
  183. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_exports_unit.py +0 -0
  184. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_fetch_progress.py +0 -0
  185. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_healthz.py +0 -0
  186. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_live_env_fetch.py +0 -0
  187. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_machines_unit.py +0 -0
  188. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_overlays_ui.py +0 -0
  189. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_pivot.py +0 -0
  190. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_pxe_plan_json.py +0 -0
  191. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_schema.py +0 -0
  192. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_settings.py +0 -0
  193. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_ui_pages.py +0 -0
  194. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_web_bind_preview.py +0 -0
  195. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/tests/test_web_inventory.py +0 -0
  196. {pixie_lab-0.3.1 → pixie_lab-0.3.2}/uv.lock +0 -0
@@ -10,6 +10,11 @@ name: cicd
10
10
  # * ``build-media``: build the live-env media (netboot-pc + usbboot-pc
11
11
  # variants) via the cijoe pipelines under cijoe/tasks/. Requires
12
12
  # live-build + passwordless sudo; the runner has both.
13
+ # * ``verify-usbboot``: structurally verify the usbboot-pc .iso
14
+ # (hybrid ISO + El Torito BIOS & UEFI entries + sha256), then boot
15
+ # it via a real Ventoy stick in QEMU and assert the pixie live env
16
+ # comes up. Gates the publish jobs so a broken / non-Ventoy .iso
17
+ # never ships.
13
18
  # * ``build-wheel``: sdist + wheel for PyPI. Runs on every push +
14
19
  # PR so the packaging story is exercised regardless of tag state.
15
20
  # * ``publish-pypi``: trusted publishing of the wheel + sdist,
@@ -556,6 +561,66 @@ jobs:
556
561
  path: cijoe/_build/test-pxe
557
562
  if-no-files-found: warn
558
563
 
564
+ verify-usbboot:
565
+ name: Verify usbboot .iso (structural + Ventoy boot in QEMU)
566
+ # Downloads the usbboot-pc bake, structurally verifies the hybrid
567
+ # ISO (ISO 9660 + isohybrid MBR + El Torito BIOS & UEFI entries +
568
+ # sha256), then boots it via a real Ventoy stick in QEMU and asserts
569
+ # the pixie live env comes up: pixie-usb-grow skips (no PIXIE_IMGS
570
+ # under a loop-mounted ISO), pixie-images-discover bind-mounts the
571
+ # operator drop, pixie-on-tty1 renders the CLI wizard. Gates the
572
+ # release -- a broken / non-bootable / non-Ventoy-compatible ISO
573
+ # must not ship.
574
+ needs: build-media
575
+ runs-on: ubuntu-latest
576
+ timeout-minutes: 40
577
+ steps:
578
+ - uses: actions/checkout@v6
579
+
580
+ - name: Install host tools (qemu, ovmf, exfat, xorriso)
581
+ run: |
582
+ set -eu
583
+ sudo apt-get update
584
+ sudo apt-get install -y --no-install-recommends \
585
+ qemu-system-x86 \
586
+ qemu-utils \
587
+ ovmf \
588
+ exfat-fuse \
589
+ exfatprogs \
590
+ xorriso \
591
+ file
592
+ sudo chmod a+rw /dev/kvm
593
+ ls -la /dev/kvm
594
+
595
+ - name: Install cijoe
596
+ run: pipx install --force cijoe
597
+
598
+ - name: Install uv
599
+ uses: astral-sh/setup-uv@v8.1.0
600
+
601
+ - name: Download usbboot-pc artifact
602
+ uses: actions/download-artifact@v7
603
+ with:
604
+ name: pixie-usbboot-pc-x86_64
605
+ path: ~/system_imaging/disk/
606
+
607
+ - name: Show downloaded artifact contents
608
+ run: |
609
+ set -eu
610
+ echo "--- ~/system_imaging/disk (recursive) ---"
611
+ find ~/system_imaging/disk -maxdepth 5 -print
612
+
613
+ - name: Verify + Ventoy-boot the usbboot .iso
614
+ run: make test-usb-ventoy
615
+
616
+ - name: Upload cijoe output
617
+ if: always()
618
+ uses: actions/upload-artifact@v7
619
+ with:
620
+ name: cijoe-output-test-usb-ventoy
621
+ path: cijoe/cijoe-output
622
+ if-no-files-found: warn
623
+
559
624
  build-wheel:
560
625
  name: Build wheel + sdist
561
626
  needs: test
@@ -591,6 +656,7 @@ jobs:
591
656
  - test-pxe-tui
592
657
  - test-pxe-flash
593
658
  - test-pxe-flash-always
659
+ - verify-usbboot
594
660
  runs-on: ubuntu-latest
595
661
  if: startsWith(github.ref, 'refs/tags/v')
596
662
  environment:
@@ -632,6 +698,7 @@ jobs:
632
698
  - test-pxe-tui
633
699
  - test-pxe-flash
634
700
  - test-pxe-flash-always
701
+ - verify-usbboot
635
702
  runs-on: ubuntu-latest
636
703
  if: startsWith(github.ref, 'refs/tags/v')
637
704
  permissions:
@@ -676,6 +743,7 @@ jobs:
676
743
  - test-pxe-tui
677
744
  - test-pxe-flash
678
745
  - test-pxe-flash-always
746
+ - verify-usbboot
679
747
  runs-on: ubuntu-latest
680
748
  if: startsWith(github.ref, 'refs/tags/v')
681
749
  permissions:
@@ -11,6 +11,22 @@ operator-facing summary.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.3.2] - 2026-07-24
15
+
16
+ ### Added
17
+
18
+ **CI verifies the usbboot `.iso` boots via Ventoy.** A new
19
+ `verify-usbboot` job structurally checks the bake (valid ISO 9660 +
20
+ isohybrid MBR + El Torito BIOS *and* UEFI boot entries + sha256), then
21
+ installs Ventoy onto a QEMU disk, drops the `.iso` + a sentinel image
22
+ catalog onto it, and boots it -- asserting the pixie live env comes up
23
+ correctly on the Ventoy loop-boot path (`pixie-usb-grow` skips with no
24
+ `PIXIE_IMGS` partition, `pixie-images-discover` bind-mounts the
25
+ operator drop at `/var/lib/pixie/images`, and `pixie-on-tty1` renders
26
+ the CLI wizard). The job gates the publish jobs, so a broken,
27
+ non-bootable, or non-Ventoy-compatible ISO can never ship. Ported from
28
+ bty's `test-usb-ventoy`.
29
+
14
30
  ## [0.3.1] - 2026-07-24
15
31
 
16
32
  ### Fixed
@@ -23,7 +23,7 @@ endif
23
23
 
24
24
  .PHONY: help \
25
25
  deps test lint format format-check typecheck ci wheel \
26
- media-deps build ipxe test-pxe \
26
+ media-deps build ipxe test-pxe test-usb-ventoy \
27
27
  clean
28
28
 
29
29
  help:
@@ -56,6 +56,8 @@ help:
56
56
  @echo " (same deps as test-pxe-flash; asserts no /done flip)"
57
57
  @echo " test-pxe-tui end-to-end PXE pixie-tui chain test"
58
58
  @echo " (same deps as test-pxe-flash; asserts wizard entry)"
59
+ @echo " test-usb-ventoy structural + Ventoy-boot verify of the usbboot .iso"
60
+ @echo " (needs a prior VARIANT=usbboot-pc bake + qemu/KVM/OVMF + ventoy deps)"
59
61
  @echo ""
60
62
  @echo "Variant: $(VARIANT) (override with VARIANT=netboot-pc, ...)"
61
63
  @echo " usbboot-pc - bootable USB live ISO via live-build (.iso, x86_64)"
@@ -166,6 +168,13 @@ test-pxe-flash-always:
166
168
  test-pxe-tui:
167
169
  cd cijoe && cijoe tasks/test-pxe-tui.yaml --monitor -c configs/test-pxe-tui.toml
168
170
 
171
+ # Structural + Ventoy-boot verification of the usbboot .iso. Needs a
172
+ # prior VARIANT=usbboot-pc bake staged under ~/system_imaging/disk/ (or
173
+ # a downloaded pixie-usbboot-pc-x86_64 CI artifact) + qemu/KVM/OVMF +
174
+ # ventoy tooling deps (losetup, exfat-fuse, passwordless sudo).
175
+ test-usb-ventoy:
176
+ cd cijoe && cijoe tasks/test-usb-ventoy.yaml --monitor -c configs/test-usb-ventoy.toml
177
+
169
178
  # ---------- Cleanup ------------------------------------------------------
170
179
 
171
180
  clean:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixie-lab
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Bare-metal netboot appliance: catalog + fetch + NBD + PXE + TFTP + operator TUI in one container
5
5
  Project-URL: Homepage, https://github.com/safl/pixie
6
6
  Project-URL: Repository, https://github.com/safl/pixie
@@ -0,0 +1,48 @@
1
+ # pixie USB Ventoy-boot test - cijoe config
2
+ #
3
+ # Verifies an operator's realistic Ventoy workflow:
4
+ # 1. Install Ventoy onto a USB stick.
5
+ # 2. Drop pixie-usbboot-pc-x86_64-v*.iso + image files + a catalog.toml
6
+ # onto the Ventoy data partition (under pixie-images/).
7
+ # 3. Boot the stick; Ventoy menu auto-selects the only ISO + chains
8
+ # into the pixie live env.
9
+ # 4. ``pixie-usb-grow.service`` skips correctly (no PIXIE_IMGS
10
+ # partition on the booted device -- the .iso is loop-mounted, not
11
+ # dd'd).
12
+ # 5. ``pixie-images-discover.service`` finds the operator's drop on the
13
+ # Ventoy data partition and bind-mounts ``pixie-images/`` at
14
+ # ``/var/lib/pixie/images``.
15
+ # 6. ``pixie-on-tty1.service`` renders the pixie CLI wizard on tty1.
16
+
17
+ [cijoe.task]
18
+ fail_fast = true
19
+
20
+ [qemu]
21
+ img_bin = "qemu-img"
22
+
23
+ [qemu.systems.x86_64]
24
+ bin = "qemu-system-x86_64"
25
+
26
+ [qemu.guests.usb-ventoy]
27
+ path = "{{ local.env.HOME }}/system_imaging/usb-ventoy"
28
+ system_label = "x86_64"
29
+ system_args.kwa = { cpu = "host", smp = 2, m = "1G", accel = "kvm" }
30
+ # IDE-hd: Ventoy's bootloader (a chainloaded GRUB) reads via BIOS
31
+ # INT13 -- virtio-blk is invisible to it. Present the Ventoy stick as
32
+ # an IDE hard disk so the BIOS sees a bootable disk. ``-no-reboot`` so
33
+ # a failed boot exits instead of looping.
34
+ system_args.raw = "-drive file={{ local.env.HOME }}/system_imaging/usb-ventoy/disk.img,format=raw,if=ide -no-reboot"
35
+ system_args.tcp_forward = { host = 12223, guest = 22 }
36
+
37
+ [cijoe.transport.usb_ventoy_guest]
38
+ username = "root"
39
+ password = "pixie"
40
+ hostname = "localhost"
41
+ port = 12223
42
+
43
+ [test.usb_ventoy]
44
+ iso_dir = "{{ local.env.HOME }}/system_imaging/disk"
45
+ # Pinned Ventoy version so the test is reproducible. Bump deliberately
46
+ # when validating a newer release; this is the Ventoy2Disk.sh version
47
+ # the test installs with, not what an operator must use in production.
48
+ ventoy_version = "1.1.05"
@@ -0,0 +1,92 @@
1
+ """
2
+ Structurally verify the pixie usbboot .iso
3
+ ===========================================
4
+
5
+ Fast, no-boot sanity checks on ``pixie-usbboot-pc-x86_64-v*.iso`` before
6
+ the Ventoy boot test spends ~10 min proving it actually boots. Catches a
7
+ broken / non-hybrid / single-firmware bake immediately:
8
+
9
+ 1. ``file`` reports an ISO 9660 filesystem with a DOS/MBR boot sector
10
+ (the isohybrid MBR that makes ``dd`` to a USB stick BIOS-bootable).
11
+ 2. The El Torito catalog carries BOTH a BIOS and a UEFI/EFI boot
12
+ image -- i.e. the ISO boots on legacy BIOS AND UEFI firmware.
13
+ 3. The published ``.iso.sha256`` sidecar matches the image.
14
+
15
+ Retargetable: False (host-side; needs ``file`` + ``xorriso`` +
16
+ ``sha256sum`` on the initiator, which the workflow installs).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import errno
22
+ import logging as log
23
+ from argparse import ArgumentParser
24
+ from pathlib import Path
25
+
26
+ ISO_BASENAME_GLOB = "pixie-usbboot-pc-x86_64-v*.iso"
27
+
28
+
29
+ def add_args(parser: ArgumentParser):
30
+ del parser
31
+
32
+
33
+ def _out(res) -> str:
34
+ return res.output() if hasattr(res, "output") else str(res)
35
+
36
+
37
+ def main(args, cijoe):
38
+ del args
39
+ cfg = cijoe.getconf("test.usb_ventoy", {})
40
+ iso_dir = Path(cfg.get("iso_dir") or (Path.home() / "system_imaging" / "disk"))
41
+ candidates = sorted(iso_dir.glob(ISO_BASENAME_GLOB))
42
+ if not candidates:
43
+ log.error(f"no {ISO_BASENAME_GLOB} found in {iso_dir} (did usbboot-pc build run?)")
44
+ return errno.ENOENT
45
+ iso = candidates[-1]
46
+ log.info(f"verifying {iso.name} ({iso.stat().st_size} bytes)")
47
+
48
+ # 1. Valid ISO 9660 + isohybrid MBR (BIOS-bootable when dd'd).
49
+ err, res = cijoe.run_local(f"file -- {iso}")
50
+ if err:
51
+ log.error("`file` failed")
52
+ return errno.EIO
53
+ file_out = _out(res)
54
+ if "ISO 9660" not in file_out:
55
+ log.error(f"not an ISO 9660 image: {file_out}")
56
+ return errno.EINVAL
57
+ if "DOS/MBR boot sector" not in file_out and "bootable" not in file_out.lower():
58
+ log.error(f"no isohybrid MBR boot sector (not USB-dd bootable?): {file_out}")
59
+ return errno.EINVAL
60
+ log.info("OK: ISO 9660 + DOS/MBR boot sector")
61
+
62
+ # 2. El Torito catalog has both a BIOS and a UEFI/EFI boot image.
63
+ err, res = cijoe.run_local(f"xorriso -indev {iso} -report_el_torito plain 2>&1")
64
+ if err:
65
+ log.error("`xorriso -report_el_torito` failed")
66
+ return errno.EIO
67
+ et = _out(res)
68
+ log.info(f"El Torito report:\n{et}")
69
+ boot_lines = [ln for ln in et.splitlines() if "boot img" in ln.lower()]
70
+ has_bios = any("BIOS" in ln for ln in boot_lines)
71
+ has_uefi = any(("UEFI" in ln) or ("EFI" in ln) for ln in boot_lines)
72
+ if not has_bios:
73
+ log.error("El Torito catalog has no BIOS boot image")
74
+ return errno.EINVAL
75
+ if not has_uefi:
76
+ log.error("El Torito catalog has no UEFI/EFI boot image")
77
+ return errno.EINVAL
78
+ log.info("OK: El Torito has both BIOS and UEFI boot images")
79
+
80
+ # 3. sha256 sidecar matches.
81
+ sha = iso.with_name(iso.name + ".sha256")
82
+ if not sha.is_file():
83
+ log.error(f"missing sha256 sidecar: {sha}")
84
+ return errno.ENOENT
85
+ err, _ = cijoe.run_local(f"sh -c 'cd {iso_dir} && sha256sum -c {sha.name}'")
86
+ if err:
87
+ log.error("sha256 mismatch")
88
+ return errno.EIO
89
+ log.info("OK: sha256 sidecar matches")
90
+
91
+ log.info(f"{iso.name}: structural verification passed")
92
+ return 0
@@ -0,0 +1,48 @@
1
+ """
2
+ Start the Ventoy QEMU guest WITHOUT the is_up wait
3
+ ==================================================
4
+
5
+ Thin wrapper around ``cijoe.qemu.wrapper.Guest.start()`` that returns
6
+ immediately after the guest is daemonised, skipping the upstream
7
+ ``qemu.guest_start`` step's hardcoded 180-second ``is_up`` check
8
+ (which waits for the literal string ``login:`` on the serial console).
9
+
10
+ Why a local copy: Ventoy adds a chainload phase (menu -> grub ->
11
+ .iso -> initrd -> squashfs) on top of the pixie live env's normal
12
+ boot, which routinely pushes the time-to-login past the 180-second
13
+ ceiling baked into the upstream script. The YAML's
14
+ ``core.wait_for_transport`` step already polls SSH readiness with
15
+ its own (configurable) timeout, so this script intentionally splits
16
+ "start the guest" from "wait for it to be reachable" -- the latter
17
+ is the only concern that needs a tunable timeout.
18
+
19
+ Retargetable: False (host-side; same constraints as the upstream
20
+ script).
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import logging as log
26
+ from argparse import ArgumentParser
27
+
28
+ from cijoe.qemu.wrapper import Guest
29
+
30
+
31
+ def add_args(parser: ArgumentParser):
32
+ parser.add_argument("--guest_name", type=str, help="Name of the qemu guest.")
33
+
34
+
35
+ def main(args, cijoe):
36
+ if "guest_name" not in args:
37
+ log.error("missing argument: guest_name")
38
+ return 1
39
+
40
+ guest = Guest(cijoe, cijoe.config, args.guest_name)
41
+
42
+ err = guest.start()
43
+ if err:
44
+ log.error(f"guest.start() : err({err})")
45
+ return err
46
+
47
+ log.info(f"guest '{args.guest_name}' started; readiness handled by wait_for_transport")
48
+ return 0
@@ -0,0 +1,231 @@
1
+ """
2
+ Stage a Ventoy disk for the pixie Ventoy-boot test
3
+ ===================================================
4
+
5
+ Mirrors what an operator does in practice:
6
+
7
+ 1. Download Ventoy.
8
+ 2. Install Ventoy onto a USB stick (here: a 4 GiB sparse file
9
+ loop-attached so ``Ventoy2Disk.sh`` sees a block device).
10
+ 3. Drop ``pixie-usbboot-pc-x86_64-v*.iso`` onto the Ventoy data
11
+ partition so the Ventoy menu offers it.
12
+ 4. Drop a ``pixie-images/`` subdir with one sentinel image + a
13
+ ``catalog.toml`` -- the operator's image catalog the live env
14
+ should discover via ``pixie-images-discover.service``.
15
+ 5. Write a ``ventoy/ventoy.json`` with BOTH ``VTOY_MENU_TIMEOUT`` and
16
+ ``VTOY_SECONDARY_TIMEOUT`` = 1 so the two Ventoy menus auto-boot
17
+ the only ISO without keyboard input (bty proved both are needed).
18
+
19
+ The catalog carries a single ``oras://`` entry (rolling nosi tag);
20
+ it exercises pixie's catalog parser as the realistic source operators
21
+ drop next to their images.
22
+
23
+ Retargetable: False (host-side staging on the cijoe initiator;
24
+ sudo needed for losetup + Ventoy2Disk.sh).
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import errno
30
+ import logging as log
31
+ import urllib.request
32
+ from argparse import ArgumentParser
33
+ from pathlib import Path
34
+
35
+ ISO_BASENAME_GLOB = "pixie-usbboot-pc-x86_64-v*.iso"
36
+ DISK_BYTES = 4 * 1024 * 1024 * 1024 # 4 GiB
37
+
38
+ # ``pixie-images-discover`` bind-mounts the first vfat/exfat partition
39
+ # whose ``pixie-images/`` subdir holds a usable image (``*.img*`` /
40
+ # ``*.qcow2``). A 1 MiB sparse ``.img.gz`` is enough to trip that; it is
41
+ # never flashed.
42
+ IMAGES_SUBDIR = "pixie-images"
43
+ SENTINEL_IMAGE_NAME = "demo-pixie-ventoy-test.img.gz"
44
+ SENTINEL_IMAGE_BYTES = 1 * 1024 * 1024
45
+
46
+ # Single-entry catalog an operator would realistically drop alongside
47
+ # their images: a remote ``oras://`` ref pixie's catalog parser reads.
48
+ CATALOG_TOML = """\
49
+ version = 1
50
+
51
+ [[images]]
52
+ name = "ventoy-test-fedora-44-headless"
53
+ src = "oras://ghcr.io/safl/nosi/fedora-44-headless:latest"
54
+ format = "img.gz"
55
+ arch = "x86_64"
56
+ description = "Sentinel remote entry for the Ventoy-boot test"
57
+ """
58
+
59
+
60
+ def add_args(parser: ArgumentParser):
61
+ del parser
62
+
63
+
64
+ def main(args, cijoe):
65
+ del args
66
+ cfg = cijoe.getconf("test.usb_ventoy", {})
67
+ iso_dir = Path(cfg.get("iso_dir") or (Path.home() / "system_imaging" / "disk"))
68
+ candidates = sorted(iso_dir.glob(ISO_BASENAME_GLOB))
69
+ if not candidates:
70
+ log.error(f"no {ISO_BASENAME_GLOB} found in {iso_dir} (did usbboot-pc build run?)")
71
+ return errno.ENOENT
72
+ src_iso = candidates[-1]
73
+ log.info(f"using {src_iso.name} ({src_iso.stat().st_size} bytes)")
74
+
75
+ ventoy_version = cfg.get("ventoy_version", "1.1.05")
76
+ ventoy_url = (
77
+ f"https://github.com/ventoy/Ventoy/releases/download/"
78
+ f"v{ventoy_version}/ventoy-{ventoy_version}-linux.tar.gz"
79
+ )
80
+
81
+ guest_path_raw = cijoe.getconf("qemu.guests.usb-ventoy.path")
82
+ if not guest_path_raw:
83
+ log.error("missing qemu.guests.usb-ventoy.path in cijoe config")
84
+ return errno.EINVAL
85
+ guest_path = Path(guest_path_raw)
86
+ guest_path.mkdir(parents=True, exist_ok=True)
87
+
88
+ disk = guest_path / "disk.img"
89
+ ventoy_tarball = guest_path / f"ventoy-{ventoy_version}-linux.tar.gz"
90
+ ventoy_dir = guest_path / f"ventoy-{ventoy_version}"
91
+
92
+ # 1. Download Ventoy (cached across runs).
93
+ if not ventoy_tarball.is_file():
94
+ log.info(f"downloading {ventoy_url}")
95
+ urllib.request.urlretrieve(ventoy_url, ventoy_tarball)
96
+ if not ventoy_dir.is_dir():
97
+ log.info(f"extracting {ventoy_tarball.name}")
98
+ err, _ = cijoe.run_local(f"tar -xzf {ventoy_tarball} -C {guest_path}")
99
+ if err:
100
+ log.error("tar -xzf failed")
101
+ return errno.EIO
102
+
103
+ # 2. Create the sparse 4 GiB disk (Ventoy will format it).
104
+ log.info(f"creating sparse disk: {disk} ({DISK_BYTES} bytes)")
105
+ with disk.open("wb") as fh:
106
+ fh.truncate(DISK_BYTES)
107
+
108
+ # 3. losetup + Ventoy2Disk.sh -I (force install).
109
+ log.info("losetup -fP --show <disk>")
110
+ err, out = cijoe.run_local(f"sudo losetup -fP --show {disk}")
111
+ if err:
112
+ log.error("losetup failed")
113
+ return errno.EIO
114
+ loop_dev = out.output().strip().splitlines()[-1].strip() if hasattr(out, "output") else ""
115
+ if not loop_dev.startswith("/dev/loop"):
116
+ log.error(f"unexpected losetup output: {out!r}")
117
+ return errno.EIO
118
+ log.info(f"loop device: {loop_dev}")
119
+
120
+ mount_dir = guest_path / "ventoy-mount"
121
+ mount_dir.mkdir(exist_ok=True)
122
+ sentinel_tmp = guest_path / "demo.tmp"
123
+ catalog_tmp = guest_path / "catalog.toml.tmp"
124
+ ventoy_json_tmp = guest_path / "ventoy.json.tmp"
125
+
126
+ try:
127
+ # ``yes | Ventoy2Disk.sh -I <dev>``: force install over any
128
+ # existing partition table; ``yes`` answers the confirmation
129
+ # prompts (the disk is about to be erased).
130
+ log.info(f"Ventoy2Disk.sh -I {loop_dev}")
131
+ err, _ = cijoe.run_local(f"sudo sh -c 'yes | {ventoy_dir}/Ventoy2Disk.sh -I {loop_dev}'")
132
+ if err:
133
+ log.error("Ventoy2Disk.sh failed")
134
+ return errno.EIO
135
+
136
+ # Ventoy creates two partitions: p1 is the exFAT data partition
137
+ # (ISOs + operator files), p2 is the small VTOYEFI partition.
138
+ cijoe.run_local("sudo udevadm settle --timeout=10")
139
+ cijoe.run_local(f"sudo partprobe {loop_dev} || true")
140
+ cijoe.run_local("sudo udevadm settle --timeout=10")
141
+ cijoe.run_local(f"sudo lsblk -bno NAME,SIZE,TYPE,LABEL,FSTYPE {loop_dev}")
142
+
143
+ ventoy_data = f"{loop_dev}p1"
144
+ # Kernel exfat first (fast), fall back to mount.exfat-fuse.
145
+ # Ubuntu's ``mount -t exfat`` does NOT auto-fall-back to FUSE
146
+ # when the kernel module is missing; GHA's ubuntu-latest ships
147
+ # a stripped kernel without exfat, so the workflow installs
148
+ # ``exfat-fuse`` for the ``/sbin/mount.exfat-fuse`` userspace
149
+ # path.
150
+ log.info(f"mounting {ventoy_data}")
151
+ err, _ = cijoe.run_local(f"sudo mount -t exfat {ventoy_data} {mount_dir}")
152
+ if err:
153
+ log.info("kernel exfat unavailable; falling back to mount.exfat-fuse")
154
+ err, _ = cijoe.run_local(f"sudo mount.exfat-fuse {ventoy_data} {mount_dir}")
155
+ if err:
156
+ log.error(
157
+ f"both kernel and FUSE exfat mounts failed on {ventoy_data} "
158
+ f"(install ``exfat-fuse`` or ``linux-modules-extra-$(uname -r)``)"
159
+ )
160
+ return errno.EIO
161
+
162
+ try:
163
+ # 4. Drop the .iso at the root of the Ventoy data partition.
164
+ log.info(f"copying {src_iso.name} -> Ventoy data partition")
165
+ err, _ = cijoe.run_local(f"sudo cp {src_iso} {mount_dir}/")
166
+ if err:
167
+ log.error("cp .iso failed")
168
+ return errno.EIO
169
+
170
+ # 5. pixie-images/ subdir with sentinel image + catalog.toml.
171
+ cijoe.run_local(f"sudo mkdir -p {mount_dir}/{IMAGES_SUBDIR}")
172
+
173
+ log.info(f"staging sentinel image ({SENTINEL_IMAGE_BYTES} bytes)")
174
+ with sentinel_tmp.open("wb") as fh:
175
+ fh.truncate(SENTINEL_IMAGE_BYTES)
176
+ err, _ = cijoe.run_local(
177
+ f"sudo cp {sentinel_tmp} {mount_dir}/{IMAGES_SUBDIR}/{SENTINEL_IMAGE_NAME}"
178
+ )
179
+ if err:
180
+ log.error("cp sentinel image failed")
181
+ return errno.EIO
182
+
183
+ log.info("staging catalog.toml")
184
+ catalog_tmp.write_text(CATALOG_TOML, encoding="utf-8")
185
+ err, _ = cijoe.run_local(
186
+ f"sudo cp {catalog_tmp} {mount_dir}/{IMAGES_SUBDIR}/catalog.toml"
187
+ )
188
+ if err:
189
+ log.error("cp catalog.toml failed")
190
+ return errno.EIO
191
+
192
+ # 6. ventoy/ventoy.json: fully-headless auto-boot. Ventoy
193
+ # shows TWO menus that BOTH need a timeout (bty verified via
194
+ # a stuck-guest screendump): the primary ISO list
195
+ # (VTOY_MENU_TIMEOUT) and the secondary boot-mode menu
196
+ # (VTOY_SECONDARY_TIMEOUT). The primary timeout does not
197
+ # cascade; without the secondary knob the guest sits at the
198
+ # boot-mode menu forever and no kernel ever boots.
199
+ cijoe.run_local(f"sudo mkdir -p {mount_dir}/ventoy")
200
+ ventoy_json_tmp.write_text(
201
+ "{\n"
202
+ ' "control": [\n'
203
+ ' {"VTOY_MENU_TIMEOUT": "1"},\n'
204
+ ' {"VTOY_SECONDARY_TIMEOUT": "1"}\n'
205
+ " ]\n"
206
+ "}\n",
207
+ encoding="utf-8",
208
+ )
209
+ err, _ = cijoe.run_local(f"sudo cp {ventoy_json_tmp} {mount_dir}/ventoy/ventoy.json")
210
+ if err:
211
+ log.error("cp ventoy.json failed")
212
+ return errno.EIO
213
+
214
+ # Record what landed for the cijoe report.
215
+ cijoe.run_local(f"sudo ls -la {mount_dir}/")
216
+ cijoe.run_local(f"sudo ls -la {mount_dir}/{IMAGES_SUBDIR}/")
217
+ cijoe.run_local(f"sudo cat {mount_dir}/{IMAGES_SUBDIR}/catalog.toml")
218
+ cijoe.run_local(f"sudo cat {mount_dir}/ventoy/ventoy.json")
219
+ cijoe.run_local(f"sudo sync {mount_dir}")
220
+ finally:
221
+ cijoe.run_local(f"sudo umount {mount_dir}")
222
+ finally:
223
+ cijoe.run_local(f"sudo losetup -d {loop_dev}")
224
+ sentinel_tmp.unlink(missing_ok=True)
225
+ catalog_tmp.unlink(missing_ok=True)
226
+ ventoy_json_tmp.unlink(missing_ok=True)
227
+ if mount_dir.is_dir() and not any(mount_dir.iterdir()):
228
+ mount_dir.rmdir()
229
+
230
+ log.info(f"Ventoy disk ready at {disk}")
231
+ return 0