pixie-lab 0.4.0__tar.gz → 0.4.1__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 (203) hide show
  1. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/CHANGELOG.md +42 -0
  2. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/PKG-INFO +1 -1
  3. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/src/boot-modes.md +22 -8
  4. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/src/deployment.md +7 -0
  5. pixie_lab-0.4.1/pixie-media/live-build/config/includes.chroot/etc/initramfs-tools/conf.d/pixie-modules-most +22 -0
  6. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pyproject.toml +1 -1
  7. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/machines/_routes.py +8 -6
  8. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/machines/_store.py +29 -4
  9. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/pxe/_routes.py +31 -2
  10. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_settings_store.py +23 -6
  11. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/main.py +31 -1
  12. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_machines_unit.py +58 -0
  13. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_pxe_plan_json.py +3 -1
  14. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_settings.py +20 -0
  15. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/uv.lock +1 -1
  16. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/.dockerignore +0 -0
  17. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/.github/workflows/cicd.yaml +0 -0
  18. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/.github/workflows/docs.yml +0 -0
  19. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/.gitignore +0 -0
  20. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/Containerfile +0 -0
  21. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/LICENSE +0 -0
  22. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/Makefile +0 -0
  23. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/PLAN.md +0 -0
  24. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/README.md +0 -0
  25. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/netboot-pc.toml +0 -0
  26. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-pxe-flash-always.toml +0 -0
  27. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-pxe-flash.toml +0 -0
  28. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-pxe-inventory.toml +0 -0
  29. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-pxe-nbdboot.toml +0 -0
  30. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-pxe-tui.toml +0 -0
  31. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-pxe.toml +0 -0
  32. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/test-usb-ventoy.toml +0 -0
  33. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/configs/usbboot-pc.toml +0 -0
  34. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/live_build.py +0 -0
  35. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/pixie_ipxe_build.py +0 -0
  36. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/pixie_wheel_stage.py +0 -0
  37. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/pxe_flash_stage.py +0 -0
  38. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/pxe_inventory_stage.py +0 -0
  39. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/pxe_prepare.py +0 -0
  40. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/pxe_run_chain_test.py +0 -0
  41. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/usb_iso_build.py +0 -0
  42. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/usb_iso_verify.py +0 -0
  43. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/usb_ventoy_guest_start.py +0 -0
  44. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/scripts/usb_ventoy_stage.py +0 -0
  45. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/netboot-pc.yaml +0 -0
  46. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-pxe-flash-always.yaml +0 -0
  47. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-pxe-flash.yaml +0 -0
  48. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-pxe-inventory.yaml +0 -0
  49. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-pxe-nbdboot.yaml +0 -0
  50. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-pxe-tui.yaml +0 -0
  51. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-pxe.yaml +0 -0
  52. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/test-usb-ventoy.yaml +0 -0
  53. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/cijoe/tasks/usbboot-pc.yaml +0 -0
  54. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/audit.md +0 -0
  55. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/src/conf.py +0 -0
  56. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/src/hardware-quirks.md +0 -0
  57. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/src/index.md +0 -0
  58. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/src/quickstart.md +0 -0
  59. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/tooling/README.md +0 -0
  60. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/tooling/pyproject.toml +0 -0
  61. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/tooling/src/pixie_docs/__init__.py +0 -0
  62. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/docs/tooling/src/pixie_docs/cli.py +0 -0
  63. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/README.md +0 -0
  64. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/auxiliary/cloudinit-metadata.meta +0 -0
  65. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/auxiliary/ipxe-embed.ipxe +0 -0
  66. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/auxiliary/ipxe-local-general.h +0 -0
  67. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/auto/config +0 -0
  68. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/archives/backports.list.binary +0 -0
  69. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/archives/backports.list.chroot +0 -0
  70. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/bootloaders/grub-efi/splash.png +0 -0
  71. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/bootloaders/syslinux/splash.png +0 -0
  72. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0500-pixie-install.hook.chroot +0 -0
  73. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0500-pixie-skip-bootloader-menu.hook.binary +0 -0
  74. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0600-pixie-r8125-dkms.hook.chroot +0 -0
  75. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0700-pixie-clock-from-http.hook.chroot +0 -0
  76. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0800-pixie-ssh-live.hook.chroot +0 -0
  77. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0900-pixie-enable-services.hook.chroot +0 -0
  78. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/hooks/normal/0980-pixie-apt-validate.hook.chroot +0 -0
  79. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/issue +0 -0
  80. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/modprobe.d/zz-pixie-blacklist-nouveau.conf +0 -0
  81. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/motd +0 -0
  82. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/profile.d/pixie-version.sh +0 -0
  83. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/getty@.service.d/zzz-pixie-autologin-root.conf +0 -0
  84. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-early.service +0 -0
  85. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-late.service +0 -0
  86. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-mid.service +0 -0
  87. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-clock-from-http.service +0 -0
  88. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-images-discover.service +0 -0
  89. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-on-tty1.service +0 -0
  90. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-usb-grow.service +0 -0
  91. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/var-lib-pixie-images.mount +0 -0
  92. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/etc/udev/rules.d/70-pixie-realtek-2g5-offloads.rules +0 -0
  93. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/lib/systemd/system-generators/pixie-skip-usb-only-units-on-netboot +0 -0
  94. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-boot-banner +0 -0
  95. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-clock-from-http +0 -0
  96. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-images-discover +0 -0
  97. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-on-tty1 +0 -0
  98. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-trace +0 -0
  99. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-usb-grow +0 -0
  100. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/package-lists/pixie-base.list.chroot +0 -0
  101. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/package-lists/pixie-flash.list.chroot +0 -0
  102. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/preferences/backports.pref.binary +0 -0
  103. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/pixie-media/live-build/config/preferences/backports.pref.chroot +0 -0
  104. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/__init__.py +0 -0
  105. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/_partition.py +0 -0
  106. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/_util.py +0 -0
  107. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/__init__.py +0 -0
  108. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/_fetcher.py +0 -0
  109. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/_live_env.py +0 -0
  110. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/_routes.py +0 -0
  111. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/_schema.py +0 -0
  112. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/_store.py +0 -0
  113. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/catalog/catalog.toml +0 -0
  114. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/deploy/__init__.py +0 -0
  115. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/deploy/_main.py +0 -0
  116. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/deploy/_templates.py +0 -0
  117. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/disks.py +0 -0
  118. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/events/__init__.py +0 -0
  119. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/events/_kinds.py +0 -0
  120. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/events/_log.py +0 -0
  121. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/events/_routes.py +0 -0
  122. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/exports/__init__.py +0 -0
  123. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/exports/_routes.py +0 -0
  124. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/exports/_store.py +0 -0
  125. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/exports/_supervisor.py +0 -0
  126. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/flash.py +0 -0
  127. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/images.py +0 -0
  128. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/machines/__init__.py +0 -0
  129. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/oras.py +0 -0
  130. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/pivot/__init__.py +0 -0
  131. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/pivot/nbdboot +0 -0
  132. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/pxe/__init__.py +0 -0
  133. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/pxe/_renderer.py +0 -0
  134. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/tftp/__init__.py +0 -0
  135. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/tftp/_supervisor.py +0 -0
  136. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/tui/__init__.py +0 -0
  137. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/tui/_app.py +0 -0
  138. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/tui_catalog.py +0 -0
  139. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/__init__.py +0 -0
  140. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_auth.py +0 -0
  141. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_bind_preview.py +0 -0
  142. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_fsutil.py +0 -0
  143. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_images.py +0 -0
  144. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_inventory.py +0 -0
  145. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_overlay_bind.py +0 -0
  146. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_overlays.py +0 -0
  147. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/.gitkeep +0 -0
  148. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/bootstrap-icons.min.css +0 -0
  149. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/bootstrap.min.css +0 -0
  150. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/fonts/bootstrap-icons.woff +0 -0
  151. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/fonts/bootstrap-icons.woff2 +0 -0
  152. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/htmx.min.js +0 -0
  153. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/pixie-favicon.png +0 -0
  154. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_static/sse.js +0 -0
  155. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_table_state.py +0 -0
  156. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/_partials/page_description.html +0 -0
  157. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/_partials/recent_events.html +0 -0
  158. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/_partials/table_helpers.html +0 -0
  159. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/catalog.html +0 -0
  160. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/catalog_detail.html +0 -0
  161. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/dashboard.html +0 -0
  162. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/events.html +0 -0
  163. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/image_detail.html +0 -0
  164. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/images.html +0 -0
  165. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/ipxe/bootstrap.j2 +0 -0
  166. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/ipxe/exit.j2 +0 -0
  167. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/ipxe/nbdboot.j2 +0 -0
  168. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/ipxe/pixie-live-env.j2 +0 -0
  169. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/ipxe/unavailable.j2 +0 -0
  170. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/layout.html +0 -0
  171. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/live_env.html +0 -0
  172. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/login.html +0 -0
  173. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/machine_detail.html +0 -0
  174. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/machines.html +0 -0
  175. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/overlays.html +0 -0
  176. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/src/pixie/web/_templates/settings.html +0 -0
  177. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/__init__.py +0 -0
  178. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/conftest.py +0 -0
  179. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/__init__.py +0 -0
  180. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/conftest.py +0 -0
  181. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/test_catalog_fetch.py +0 -0
  182. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/test_deploy.py +0 -0
  183. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/test_exports.py +0 -0
  184. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/test_inventory.py +0 -0
  185. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/test_pxe.py +0 -0
  186. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/integration/test_tftp.py +0 -0
  187. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_auth.py +0 -0
  188. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_catalog_api.py +0 -0
  189. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_catalog_seed.py +0 -0
  190. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_deploy_unit.py +0 -0
  191. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_event_kinds.py +0 -0
  192. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_events_unit.py +0 -0
  193. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_exports_unit.py +0 -0
  194. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_fetch_progress.py +0 -0
  195. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_healthz.py +0 -0
  196. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_images_ui.py +0 -0
  197. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_live_env_fetch.py +0 -0
  198. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_overlays_ui.py +0 -0
  199. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_pivot.py +0 -0
  200. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_schema.py +0 -0
  201. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_ui_pages.py +0 -0
  202. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_web_bind_preview.py +0 -0
  203. {pixie_lab-0.4.0 → pixie_lab-0.4.1}/tests/test_web_inventory.py +0 -0
@@ -11,6 +11,48 @@ operator-facing summary.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.4.1] - 2026-07-25
15
+
16
+ ### Changed
17
+
18
+ **New machines default to `pixie-inventory`, one-shot.** A
19
+ freshly-discovered MAC now auto-registers with `pixie-inventory`
20
+ instead of `ipxe-exit`: non-destructive (boots the live env, collects
21
+ lshw + disks, posts them, exits to firmware) and immediately useful --
22
+ every new machine's hardware shows up and the flash modes (which
23
+ require inventory) become available without a manual pass. It is
24
+ one-shot: the first inventory POST flips the binding to `ipxe-exit`, so
25
+ a PXE-first box inventories itself exactly once and never boot-loops.
26
+ With no live env staged the plan degrades to exit, so a bare deploy
27
+ behaves like before until the operator fetches the live env. Override
28
+ per deploy with `PIXIE_DEFAULT_BOOT_MODE` (e.g. `ipxe-exit` for the old
29
+ behaviour).
30
+
31
+ ### Fixed
32
+
33
+ **Live env boots on arbitrary NICs (netboot-pc initramfs is now
34
+ `MODULES=most`).** The live env fetches its squashfs over HTTP from the
35
+ initramfs before pivot-root, so the target's NIC must be driven from
36
+ inside the initrd. It was built the mkinitramfs default `MODULES=dep` --
37
+ only the build host's drivers (plus the DKMS'd r8125) -- so a target
38
+ with a different NIC came up to `initrd` and then stalled forever at the
39
+ squashfs fetch, silently breaking `pixie-inventory` / `-tui` / `-flash`
40
+ on that box (observed on an ASRock Rack board). The netboot-pc bake now
41
+ sets `MODULES=most`, baking the broad Debian-installer driver set so an
42
+ arbitrary lab machine's NIC (and storage) is alive before the fetch.
43
+ Costs some initramfs size; worth it for an appliance whose job is
44
+ booting unknown hardware.
45
+
46
+ **No more `${PIXIE_LIVE_ENV_EXTRA_CMDLINE:-}` literal on the kernel
47
+ cmdline.** On a deploy whose compose runner doesn't expand `${VAR:-}`
48
+ (podman-compose passes the unset default through verbatim), the
49
+ container env held the literal string `${PIXIE_LIVE_ENV_EXTRA_CMDLINE:-}`,
50
+ which pixie then appended to every live-env / nbdboot kernel cmdline.
51
+ Harmless (the kernel ignores the unknown token) but wrong. The
52
+ extra-cmdline resolver now treats a value that is nothing but an
53
+ unexpanded `${...}` placeholder as unset, so it can't leak -- fixed for
54
+ any deploy without regenerating compose.
55
+
14
56
  ## [0.4.0] - 2026-07-24
15
57
 
16
58
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixie-lab
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -8,14 +8,14 @@ tokens.
8
8
 
9
9
  ## The six modes
10
10
 
11
- ### `ipxe-exit` (default)
11
+ ### `ipxe-exit`
12
12
 
13
13
  Pixie exits the iPXE chain. The target's BIOS boot order picks the
14
- next bootable device. This is the safe default for a fresh MAC:
15
- pixie won't reflash a machine or attempt to netboot it until an
16
- operator has explicitly bound it.
14
+ next bootable device. A pure no-op: pixie won't reflash a machine or
15
+ netboot it. This is where `pixie-inventory` self-terminates to (see
16
+ below), and where an operator parks a machine pixie should leave alone.
17
17
 
18
- ### `pixie-inventory`
18
+ ### `pixie-inventory` (default)
19
19
 
20
20
  The target boots pixie's own live env. The `pixie` CLI collects
21
21
  disk + NIC + lshw output and POSTs it to `/pxe/<mac>/inventory`,
@@ -23,6 +23,18 @@ then the target reboots to firmware. Prerequisite for
23
23
  `pixie-flash-*`: without inventory, pixie doesn't know which disk
24
24
  serial to hand the flash pipeline.
25
25
 
26
+ This is the **default a freshly-discovered MAC auto-registers with**:
27
+ it's non-destructive (touches no disk) and immediately useful (every
28
+ new machine's hardware shows up on `/ui/machines`, and the flash modes
29
+ become available without a manual pass). It is **one-shot** -- on the
30
+ first successful inventory POST pixie flips the binding to `ipxe-exit`,
31
+ so a PXE-first machine inventories itself exactly once and then stops
32
+ touching the boot (no re-inventory loop). Rebinding to
33
+ `pixie-inventory` buys one more pass. If no live env is staged the plan
34
+ degrades to `unavailable`/exit, so a bare deploy behaves like the old
35
+ exit-by-default until the operator fetches the live env. Set
36
+ `PIXIE_DEFAULT_BOOT_MODE=ipxe-exit` to restore the old exit-by-default.
37
+
26
38
  ### `pixie-tui`
27
39
 
28
40
  The target boots pixie's live env into an interactive TUI. An
@@ -123,9 +135,11 @@ volumes alone.
123
135
 
124
136
  A binding is what pixie serves a MAC next time it PXEs: a boot mode,
125
137
  plus (for the modes that need one) an image, plus (for `nbdboot`) an
126
- optional overlay alias. A fresh MAC auto-registers as `ipxe-exit` with
127
- no image on first contact; the machine detail page is where an
128
- operator promotes it. Saving the bind form persists all three fields
138
+ optional overlay alias. A fresh MAC auto-registers with
139
+ `PIXIE_DEFAULT_BOOT_MODE` (default `pixie-inventory`, which
140
+ self-terminates to `ipxe-exit` after one inventory pass) and no image
141
+ on first contact; the machine detail page is where an operator promotes
142
+ it. Saving the bind form persists all three fields
129
143
  together, and the plan renderer reads them per PXE request: it resolves
130
144
  the image's netboot bundle, resolves the alias to its overlay (and
131
145
  base image), ensures the NBD export is up, and renders the plan. A
@@ -135,6 +135,13 @@ if HTTP is proxied but NBD is direct.
135
135
  to every pixie-live-env chain. Common values are hardware-quirk
136
136
  workarounds; see [](hardware-quirks.md).
137
137
 
138
+ **`PIXIE_DEFAULT_BOOT_MODE`** (default: `pixie-inventory`) - boot mode a
139
+ freshly-discovered MAC auto-registers with. The default is
140
+ non-destructive (collect + post inventory, then exit) and one-shot (the
141
+ first inventory POST flips it to `ipxe-exit`, so a PXE-first box does
142
+ not re-inventory). Set to `ipxe-exit` for the old exit-by-default
143
+ behaviour. See [](boot-modes.md).
144
+
138
145
  **`PIXIE_DATA_DIR`** (default: `/var/lib/pixie`) - state root. Holds
139
146
  `state.db`, `blobs/` (fetched image bytes), `artifacts/` (extracted
140
147
  netboot bundles), `overlays/<alias>.qcow2`
@@ -0,0 +1,22 @@
1
+ # pixie netboot-pc live env: bake a BROAD driver set into the initramfs.
2
+ #
3
+ # The live env's very first action after the kernel hands off is to
4
+ # fetch its squashfs over HTTP from the kernel cmdline `fetch=` param
5
+ # (Debian live-boot). That means the target's NIC must be driven from
6
+ # INSIDE the initramfs, before pivot-root -- iPXE loaded vmlinuz+initrd
7
+ # over the firmware's network stack, but from Linux on it's the kernel
8
+ # module that matters.
9
+ #
10
+ # mkinitramfs' default is MODULES=dep: only the drivers the BUILD HOST
11
+ # needs. So the netboot-pc initrd shipped drivers for the builder's NIC
12
+ # (plus the r8125 DKMS hook) and nothing else -- a target with a
13
+ # different NIC (e.g. matx / ASRock Rack) came up to `initrd` and then
14
+ # stalled forever at the squashfs fetch, because its NIC had no driver.
15
+ #
16
+ # MODULES=most bakes the broad Debian-installer driver set, so an
17
+ # arbitrary lab box's NIC (and storage) is alive before the fetch.
18
+ # Costs initramfs size (~2x); acceptable for a netboot appliance whose
19
+ # whole job is booting unknown hardware. Narrow to an explicit NIC list
20
+ # in /etc/initramfs-tools/modules later if size becomes a problem and
21
+ # the target NICs are known.
22
+ MODULES=most
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pixie-lab"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  description = "Bare-metal netboot appliance: catalog + fetch + NBD + PXE + TFTP + operator TUI in one container"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -20,7 +20,7 @@ from pydantic import BaseModel, Field
20
20
  from pixie.events._kinds import MACHINE_BINDING_CHANGED, MACHINE_BOUND, MACHINE_DELETED
21
21
  from pixie.machines._store import (
22
22
  BOOT_MODES,
23
- DEFAULT_BOOT_MODE,
23
+ UNCOMMITTED_BOOT_MODES,
24
24
  BadMac,
25
25
  MachinesStore,
26
26
  normalise_mac,
@@ -165,12 +165,14 @@ def upsert_machine(
165
165
  details: dict[str, Any] = {"boot_mode": row.boot_mode}
166
166
  if row.image_content_sha256:
167
167
  details["image_content_sha256"] = row.image_content_sha256
168
- # ``machine.bound`` on a fresh MAC or on a row that was
169
- # discovered-only (previous.boot_mode default with no bound
170
- # image); ``machine.binding.changed`` when the mode or image
171
- # actually shifted between the previous state and the new one.
168
+ # ``machine.bound`` on a fresh MAC or on a row that was only
169
+ # auto-registered (an uncommitted mode -- ipxe-exit or
170
+ # pixie-inventory -- with no bound image, including a machine
171
+ # that auto-inventoried then flipped back to ipxe-exit);
172
+ # ``machine.binding.changed`` when the mode or image actually
173
+ # shifted from a real prior operator bind.
172
174
  was_bound = previous is not None and (
173
- bool(previous.image_content_sha256) or previous.boot_mode != DEFAULT_BOOT_MODE
175
+ bool(previous.image_content_sha256) or previous.boot_mode not in UNCOMMITTED_BOOT_MODES
174
176
  )
175
177
  changed = previous is not None and (
176
178
  previous.boot_mode != row.boot_mode
@@ -46,7 +46,24 @@ target boots into ``unavailable.j2``. Kept close to :data:`BOOT_MODES`
46
46
  so a reader adding a mode sees both spots at once."""
47
47
 
48
48
  BOOT_MODES: frozenset[str] = frozenset({"ipxe-exit", "nbdboot"}) | LIVE_ENV_MODES
49
- DEFAULT_BOOT_MODE = "ipxe-exit"
49
+
50
+ # A freshly-discovered MAC auto-registers with this mode. Default is
51
+ # ``pixie-inventory``: non-destructive (boots the live env, collects
52
+ # lshw + disks, POSTs them back, reboots to firmware) and immediately
53
+ # useful -- it populates the machine's inventory and unblocks the flash
54
+ # modes, which require it. Inventory is one-shot: on the first inventory
55
+ # POST the binding auto-flips to ``ipxe-exit`` (see pxe/_routes.py) so a
56
+ # PXE-first machine doesn't re-inventory + boot-loop. Overridable per
57
+ # deploy via ``PIXIE_DEFAULT_BOOT_MODE`` (resolved in web/main.py).
58
+ # ``ipxe-exit`` degrades safely when no live env is staged: the renderer
59
+ # falls back to an exit plan, so a bare deploy behaves like the old
60
+ # default until the operator fetches the live env.
61
+ DEFAULT_BOOT_MODE = "pixie-inventory"
62
+
63
+ # The non-committal auto states: a machine sitting in one of these with
64
+ # no bound image has not been meaningfully operator-bound yet. Used to
65
+ # tell a first ``machine.bound`` from a later ``machine.binding.changed``.
66
+ UNCOMMITTED_BOOT_MODES: frozenset[str] = frozenset({"ipxe-exit", "pixie-inventory"})
50
67
 
51
68
  # Presentation metadata for the six boot modes. Ordered
52
69
  # passthrough -> diagnostic -> interactive -> destructive so the
@@ -480,10 +497,18 @@ class MachinesStore:
480
497
  row = conn.execute("SELECT * FROM machines WHERE mac = ?", (canon,)).fetchone()
481
498
  return _row(row)
482
499
 
483
- def touch_seen(self, mac: str, *, ip: str = "") -> Machine:
500
+ def touch_seen(
501
+ self, mac: str, *, ip: str = "", default_boot_mode: str = DEFAULT_BOOT_MODE
502
+ ) -> Machine:
484
503
  """Discovery-side write: create-or-update ``last_seen_at`` +
485
- optionally ``last_seen_ip``. Does NOT touch operator fields."""
504
+ optionally ``last_seen_ip``. Does NOT touch operator fields.
505
+
506
+ A brand-new MAC is auto-registered with ``default_boot_mode``
507
+ (the deploy's ``PIXIE_DEFAULT_BOOT_MODE``, resolved by the
508
+ caller; falls back to :data:`DEFAULT_BOOT_MODE`). Unknown values
509
+ are refused here so a bad env can't seed an unrenderable mode."""
486
510
  canon = normalise_mac(mac)
511
+ mode = default_boot_mode if default_boot_mode in BOOT_MODES else DEFAULT_BOOT_MODE
487
512
  now = now_iso()
488
513
  with _DB_WRITE_LOCK, self._conn() as conn:
489
514
  existing = conn.execute("SELECT * FROM machines WHERE mac = ?", (canon,)).fetchone()
@@ -495,7 +520,7 @@ class MachinesStore:
495
520
  discovered_at, last_seen_at, last_seen_ip, updated_at
496
521
  ) VALUES (?, ?, '', ?, ?, ?, ?)
497
522
  """,
498
- (canon, DEFAULT_BOOT_MODE, now, now, ip, now),
523
+ (canon, mode, now, now, ip, now),
499
524
  )
500
525
  else:
501
526
  conn.execute(
@@ -21,13 +21,14 @@ from fastapi import APIRouter, HTTPException, Request
21
21
  from fastapi.responses import PlainTextResponse
22
22
 
23
23
  from pixie.events._kinds import (
24
+ MACHINE_BINDING_CHANGED,
24
25
  MACHINE_DISCOVERED,
25
26
  MACHINE_INVENTORY_UPDATED,
26
27
  PXE_PLAN_RENDERED,
27
28
  PXE_PLAN_UNAVAILABLE,
28
29
  PXE_STATUS_RECEIVED,
29
30
  )
30
- from pixie.machines._store import BadMac, MachinesStore, normalise_mac
31
+ from pixie.machines._store import DEFAULT_BOOT_MODE, BadMac, MachinesStore, normalise_mac
31
32
  from pixie.pxe._renderer import PlanRenderer, RenderContext
32
33
 
33
34
  _log = logging.getLogger(__name__)
@@ -132,6 +133,33 @@ async def pxe_inventory(request: Request, mac: str) -> PlainTextResponse:
132
133
  summary=f"{canon} posted inventory",
133
134
  details=details,
134
135
  )
136
+
137
+ # Inventory is one-shot: once a machine has reported, flip a
138
+ # pixie-inventory binding to ipxe-exit so a PXE-first machine does
139
+ # not re-inventory and boot-loop. Mirrors pixie-flash-once's
140
+ # self-terminating flip (see pxe_status). Preserve the row's other
141
+ # fields; the collected inventory stays. Rebinding to
142
+ # pixie-inventory buys exactly one more pass.
143
+ row = machines.get(canon)
144
+ if row is not None and row.boot_mode == "pixie-inventory":
145
+ with contextlib.suppress(ValueError):
146
+ machines.upsert_binding(
147
+ canon,
148
+ boot_mode="ipxe-exit",
149
+ image_content_sha256=row.image_content_sha256,
150
+ labels=list(row.labels),
151
+ target_disk_serial=row.target_disk_serial,
152
+ extra_cmdline=row.extra_cmdline,
153
+ overlay_alias=row.overlay_alias,
154
+ )
155
+ if log is not None:
156
+ log.emit(
157
+ MACHINE_BINDING_CHANGED,
158
+ subject_kind="machine",
159
+ subject_id=canon,
160
+ summary=f"{canon}: inventory collected, auto-exit (pixie-inventory is one-shot)",
161
+ details={"boot_mode": "ipxe-exit", "reason": "inventory-once"},
162
+ )
135
163
  return PlainTextResponse("", status_code=204)
136
164
 
137
165
 
@@ -168,7 +196,8 @@ def pxe_plan(request: Request, mac: str) -> PlainTextResponse:
168
196
  # event fires exactly once (touch_seen itself can't emit -- stores
169
197
  # hold no EventsLog by design).
170
198
  is_new = machines.get(canon) is None
171
- row = machines.touch_seen(canon, ip=_client_ip(request))
199
+ default_mode = getattr(request.app.state, "default_boot_mode", DEFAULT_BOOT_MODE)
200
+ row = machines.touch_seen(canon, ip=_client_ip(request), default_boot_mode=default_mode)
172
201
 
173
202
  ctx = _render_context(request)
174
203
  body = _get_renderer(request).render(row, ctx)
@@ -18,6 +18,7 @@ from __future__ import annotations
18
18
 
19
19
  import contextlib
20
20
  import os
21
+ import re
21
22
  import sqlite3
22
23
  from collections.abc import Generator
23
24
  from datetime import UTC, datetime
@@ -66,6 +67,13 @@ DEFAULT_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
66
67
  KEY_LIVE_ENV_EXTRA_CMDLINE = "live_env.extra_cmdline"
67
68
  ENV_LIVE_ENV_EXTRA_CMDLINE = "PIXIE_LIVE_ENV_EXTRA_CMDLINE"
68
69
 
70
+ # A value that is nothing but an unexpanded shell placeholder --
71
+ # ``${VAR}`` / ``${VAR:-default}`` / ``${VAR:+x}`` -- which is what a
72
+ # compose runner that doesn't expand ``${VAR:-}`` (podman-compose)
73
+ # leaks into the container env. Such a value is treated as "unset" so
74
+ # it never reaches a kernel cmdline.
75
+ _UNEXPANDED_ENV_PLACEHOLDER = re.compile(r"^\$\{[A-Za-z_][A-Za-z0-9_]*(:[-+?=]?[^}]*)?\}$")
76
+
69
77
  # Where the operator "Fetch live-env" action pulls the netboot-pc bake
70
78
  # from: a single tarball ``src`` (``https://`` or ``oras://``, the same
71
79
  # schemes the catalog fetch speaks) that unpacks to vmlinuz + initrd +
@@ -175,12 +183,21 @@ class SettingsStore:
175
183
  silently.
176
184
 
177
185
  Empty when both DB override + env are unset -- the template
178
- expects a string and skips the append when it's empty."""
179
- return (
180
- self.get(KEY_LIVE_ENV_EXTRA_CMDLINE)
181
- or (os.environ.get(ENV_LIVE_ENV_EXTRA_CMDLINE) or "").strip()
182
- or ""
183
- )
186
+ expects a string and skips the append when it's empty.
187
+
188
+ Defends against a compose runner that fails to expand
189
+ ``${VAR:-}``: podman-compose passes the unset default through
190
+ LITERALLY, so the container env holds the string
191
+ ``${PIXIE_LIVE_ENV_EXTRA_CMDLINE:-}``. A value that is nothing
192
+ but an unexpanded ``${...}`` placeholder is treated as empty so
193
+ it can't leak onto every live-env / nbdboot kernel cmdline."""
194
+ override = self.get(KEY_LIVE_ENV_EXTRA_CMDLINE)
195
+ if override:
196
+ return override
197
+ env = (os.environ.get(ENV_LIVE_ENV_EXTRA_CMDLINE) or "").strip()
198
+ if env and not _UNEXPANDED_ENV_PLACEHOLDER.match(env):
199
+ return env
200
+ return ""
184
201
 
185
202
  def resolve_live_env_src(self) -> str:
186
203
  """Effective live-env fetch src: DB override -> env -> default
@@ -66,7 +66,12 @@ from pixie.exports._routes import router as exports_router
66
66
  from pixie.exports._store import ExportsStore, OverlaysStore
67
67
  from pixie.exports._supervisor import DEFAULT_PORT_BASE, NbdServer
68
68
  from pixie.machines._routes import router as machines_router
69
- from pixie.machines._store import IMAGE_BOOT_MODES, MachinesStore
69
+ from pixie.machines._store import (
70
+ BOOT_MODES,
71
+ DEFAULT_BOOT_MODE,
72
+ IMAGE_BOOT_MODES,
73
+ MachinesStore,
74
+ )
70
75
  from pixie.pxe._renderer import PlanRenderer
71
76
  from pixie.pxe._routes import router as pxe_router
72
77
  from pixie.tftp import DEFAULT_TFTP_ROOT, TftpServer
@@ -183,6 +188,18 @@ def _resolve_live_env_dir() -> Path | None:
183
188
  return default
184
189
 
185
190
 
191
+ def _resolve_default_boot_mode() -> str:
192
+ """Boot mode a freshly-discovered MAC auto-registers with.
193
+
194
+ ``PIXIE_DEFAULT_BOOT_MODE`` override -> :data:`DEFAULT_BOOT_MODE`
195
+ (``pixie-inventory``). An unknown value falls back to the default
196
+ rather than seeding an unrenderable mode on every new machine."""
197
+ override = (os.environ.get("PIXIE_DEFAULT_BOOT_MODE") or "").strip()
198
+ if override in BOOT_MODES:
199
+ return override
200
+ return DEFAULT_BOOT_MODE
201
+
202
+
186
203
  def _resolve_state_dir() -> Path:
187
204
  """Where pixie writes state.db + blobs/ + artifacts/.
188
205
 
@@ -405,6 +422,18 @@ def _deployment_envvar_docs() -> list[dict[str, str]]:
405
422
  " the Live env page for the live-editable override."
406
423
  ),
407
424
  },
425
+ {
426
+ "name": "PIXIE_DEFAULT_BOOT_MODE",
427
+ "default": "pixie-inventory",
428
+ "purpose": (
429
+ "Boot mode a freshly-discovered MAC auto-registers with."
430
+ " Default 'pixie-inventory' is non-destructive (collects"
431
+ " lshw + disks, posts them, exits) and one-shot: the first"
432
+ " inventory POST flips it to 'ipxe-exit' so a PXE-first"
433
+ " box does not re-inventory. Set 'ipxe-exit' for the old"
434
+ " exit-by-default behaviour."
435
+ ),
436
+ },
408
437
  {
409
438
  "name": "PIXIE_LIVE_ENV_SRC",
410
439
  "default": (
@@ -726,6 +755,7 @@ def create_app() -> FastAPI:
726
755
  nbdkit_bin=_resolve_nbdkit_bin(),
727
756
  )
728
757
  app.state.live_env_dir = _resolve_live_env_dir()
758
+ app.state.default_boot_mode = _resolve_default_boot_mode()
729
759
  # Root for per-machine qcow2 overlay files. Sub-directory under
730
760
  # the state dir so a single ``PIXIE_DATA_DIR`` override still
731
761
  # relocates everything (catalog blobs + overlays + state.db)
@@ -550,3 +550,61 @@ def test_ui_machines_live_requires_auth(client: TestClient) -> None:
550
550
  r = client.get("/ui/machines-live.json", follow_redirects=False)
551
551
  assert r.status_code == 303
552
552
  assert r.headers["location"] == "/ui/login"
553
+
554
+
555
+ def test_touch_seen_defaults_to_pixie_inventory(tmp_path: Path) -> None:
556
+ """A freshly-discovered MAC auto-registers with the default boot
557
+ mode -- now pixie-inventory (non-destructive + useful), not the old
558
+ ipxe-exit no-op."""
559
+ store = MachinesStore(tmp_path / "state.db")
560
+ m = store.touch_seen("aa:bb:cc:dd:ee:01")
561
+ assert m.boot_mode == "pixie-inventory"
562
+
563
+
564
+ def test_touch_seen_honours_default_and_rejects_unknown(tmp_path: Path) -> None:
565
+ store = MachinesStore(tmp_path / "state.db")
566
+ assert store.touch_seen("aa:bb:cc:dd:ee:02", default_boot_mode="ipxe-exit").boot_mode == (
567
+ "ipxe-exit"
568
+ )
569
+ # An unknown value can't seed an unrenderable mode on every new MAC.
570
+ assert store.touch_seen("aa:bb:cc:dd:ee:03", default_boot_mode="bogus").boot_mode == (
571
+ "pixie-inventory"
572
+ )
573
+
574
+
575
+ def test_resolve_default_boot_mode_env_override(monkeypatch: pytest.MonkeyPatch) -> None:
576
+ from pixie.web.main import _resolve_default_boot_mode
577
+
578
+ monkeypatch.delenv("PIXIE_DEFAULT_BOOT_MODE", raising=False)
579
+ assert _resolve_default_boot_mode() == "pixie-inventory"
580
+ monkeypatch.setenv("PIXIE_DEFAULT_BOOT_MODE", "ipxe-exit")
581
+ assert _resolve_default_boot_mode() == "ipxe-exit"
582
+ monkeypatch.setenv("PIXIE_DEFAULT_BOOT_MODE", "bogus")
583
+ assert _resolve_default_boot_mode() == "pixie-inventory"
584
+
585
+
586
+ def test_inventory_post_flips_pixie_inventory_to_ipxe_exit(client: TestClient) -> None:
587
+ """Inventory is one-shot: a machine sitting in pixie-inventory flips
588
+ to ipxe-exit on the first inventory POST (so a PXE-first box doesn't
589
+ re-inventory + boot-loop), and the collected inventory is kept."""
590
+ mac = "aa:bb:cc:dd:ee:f0"
591
+ client.get(f"/pxe/{mac}") # discovery -> pixie-inventory default
592
+ assert client.get(f"/machines/{mac}").json()["boot_mode"] == "pixie-inventory"
593
+
594
+ r = client.post(
595
+ f"/pxe/{mac}/inventory", json={"lshw": {"x": 1}, "disks": [{"path": "/dev/sda"}]}
596
+ )
597
+ assert r.status_code == 204
598
+ assert client.get(f"/machines/{mac}").json()["boot_mode"] == "ipxe-exit"
599
+ # the inventory itself survives the flip
600
+ assert client.get(f"/machines/{mac}/inventory").json()["inventory"]["disks"]
601
+
602
+
603
+ def test_inventory_post_does_not_flip_a_non_inventory_mode(client: TestClient) -> None:
604
+ """A machine an operator put on, say, nbdboot is left alone -- only
605
+ pixie-inventory is one-shot."""
606
+ c = _authed(client)
607
+ mac = "aa:bb:cc:dd:ee:f1"
608
+ c.put(f"/machines/{mac}", json={"boot_mode": "nbdboot"})
609
+ client.post(f"/pxe/{mac}/inventory", json={"lshw": {}, "disks": []})
610
+ assert client.get(f"/machines/{mac}").json()["boot_mode"] == "nbdboot"
@@ -360,7 +360,9 @@ def test_pxe_first_contact_emits_machine_discovered_once(client: TestClient) ->
360
360
  discovered = [e for e in log.list(limit=500) if e.kind == "machine.discovered"]
361
361
  assert len(discovered) == 1
362
362
  assert discovered[0].subject_id == mac
363
- assert discovered[0].details.get("boot_mode") == "ipxe-exit"
363
+ # A fresh MAC auto-registers with the default boot mode, now
364
+ # pixie-inventory (non-destructive + useful).
365
+ assert discovered[0].details.get("boot_mode") == "pixie-inventory"
364
366
 
365
367
  client.get(f"/pxe/{mac}") # second contact for the now-known MAC
366
368
  still = [e for e in log.list(limit=500) if e.kind == "machine.discovered"]
@@ -258,3 +258,23 @@ def test_ui_settings_live_env_blank_clears(client: TestClient) -> None:
258
258
  store.set_value(KEY_LIVE_ENV_EXTRA_CMDLINE, "pci=realloc=on,nocrs")
259
259
  c.post("/ui/live-env/cmdline/edit", data={"extra_cmdline": ""})
260
260
  assert store.get(KEY_LIVE_ENV_EXTRA_CMDLINE) is None
261
+
262
+
263
+ def test_resolve_extra_cmdline_ignores_unexpanded_placeholder(
264
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
265
+ ) -> None:
266
+ """A compose runner that doesn't expand ``${VAR:-}`` (podman-compose)
267
+ leaks the literal placeholder into the container env. It must not
268
+ reach the kernel cmdline; a real value still passes through."""
269
+ from pixie.web._settings_store import ENV_LIVE_ENV_EXTRA_CMDLINE, SettingsStore
270
+
271
+ store = SettingsStore(tmp_path / "state.db")
272
+ for leaked in ("${PIXIE_LIVE_ENV_EXTRA_CMDLINE:-}", "${FOO}", "${BAR:-x}"):
273
+ monkeypatch.setenv(ENV_LIVE_ENV_EXTRA_CMDLINE, leaked)
274
+ assert store.resolve_live_env_extra_cmdline() == ""
275
+ # A real value passes through untouched, including one that merely
276
+ # contains (but is not wholly) a ${...} token.
277
+ monkeypatch.setenv(ENV_LIVE_ENV_EXTRA_CMDLINE, "pci=nommconf amd_iommu=off")
278
+ assert store.resolve_live_env_extra_cmdline() == "pci=nommconf amd_iommu=off"
279
+ monkeypatch.setenv(ENV_LIVE_ENV_EXTRA_CMDLINE, "foo=${bar} baz")
280
+ assert store.resolve_live_env_extra_cmdline() == "foo=${bar} baz"
@@ -494,7 +494,7 @@ wheels = [
494
494
 
495
495
  [[package]]
496
496
  name = "pixie-lab"
497
- version = "0.4.0"
497
+ version = "0.4.1"
498
498
  source = { editable = "." }
499
499
  dependencies = [
500
500
  { name = "fastapi" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes