pixie-lab 0.1.0__tar.gz → 0.3.0__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.1.0 → pixie_lab-0.3.0}/.github/workflows/cicd.yaml +132 -0
  2. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/CHANGELOG.md +129 -4
  3. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/Containerfile +4 -0
  4. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/PKG-INFO +8 -6
  5. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/README.md +7 -5
  6. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-nbdboot.toml +59 -0
  7. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_run_chain_test.py +41 -5
  8. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/boot-modes.md +40 -1
  9. pixie_lab-0.3.0/docs/src/deployment.md +219 -0
  10. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/hardware-quirks.md +32 -0
  11. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/quickstart.md +3 -3
  12. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pyproject.toml +1 -1
  13. pixie_lab-0.3.0/src/pixie/_partition.py +152 -0
  14. pixie_lab-0.3.0/src/pixie/_util.py +17 -0
  15. pixie_lab-0.3.0/src/pixie/catalog/__init__.py +49 -0
  16. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_fetcher.py +231 -68
  17. pixie_lab-0.3.0/src/pixie/catalog/_live_env.py +128 -0
  18. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_routes.py +5 -6
  19. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_store.py +10 -9
  20. pixie_lab-0.3.0/src/pixie/catalog/catalog.toml +81 -0
  21. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/deploy/_main.py +122 -26
  22. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/_kinds.py +64 -1
  23. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/_log.py +53 -1
  24. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/exports/__init__.py +0 -5
  25. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/exports/_routes.py +3 -2
  26. pixie_lab-0.3.0/src/pixie/exports/_store.py +347 -0
  27. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/exports/_supervisor.py +143 -7
  28. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/flash.py +4 -6
  29. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/images.py +8 -9
  30. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/machines/_routes.py +10 -0
  31. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/machines/_store.py +52 -9
  32. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pivot/nbdboot +116 -55
  33. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pxe/_renderer.py +127 -4
  34. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pxe/_routes.py +2 -2
  35. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tui/__init__.py +7 -9
  36. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tui/_app.py +10 -7
  37. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tui_catalog.py +16 -22
  38. pixie_lab-0.3.0/src/pixie/web/__init__.py +9 -0
  39. pixie_lab-0.3.0/src/pixie/web/_bind_preview.py +84 -0
  40. pixie_lab-0.3.0/src/pixie/web/_inventory.py +275 -0
  41. pixie_lab-0.3.0/src/pixie/web/_overlays.py +225 -0
  42. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_settings_store.py +23 -0
  43. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/catalog.html +2 -0
  44. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/dashboard.html +110 -32
  45. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/events.html +17 -0
  46. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/nbdboot.j2 +10 -1
  47. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/layout.html +6 -0
  48. pixie_lab-0.3.0/src/pixie/web/_templates/live_env.html +169 -0
  49. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/machine_detail.html +218 -68
  50. pixie_lab-0.3.0/src/pixie/web/_templates/overlays.html +248 -0
  51. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/settings.html +0 -58
  52. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/main.py +581 -18
  53. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/conftest.py +17 -0
  54. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/conftest.py +7 -0
  55. pixie_lab-0.3.0/tests/test_catalog_seed.py +118 -0
  56. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_deploy_unit.py +71 -0
  57. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_events_unit.py +33 -0
  58. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_exports_unit.py +57 -0
  59. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_fetch_progress.py +69 -2
  60. pixie_lab-0.3.0/tests/test_live_env_fetch.py +203 -0
  61. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_machines_unit.py +73 -0
  62. pixie_lab-0.3.0/tests/test_overlays_ui.py +255 -0
  63. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_settings.py +4 -4
  64. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_ui_pages.py +107 -9
  65. pixie_lab-0.3.0/tests/test_web_bind_preview.py +89 -0
  66. pixie_lab-0.3.0/tests/test_web_inventory.py +232 -0
  67. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/uv.lock +1 -1
  68. pixie_lab-0.1.0/docs/src/deployment.md +0 -120
  69. pixie_lab-0.1.0/src/pixie/_util.py +0 -38
  70. pixie_lab-0.1.0/src/pixie/catalog/__init__.py +0 -25
  71. pixie_lab-0.1.0/src/pixie/exports/_store.py +0 -156
  72. pixie_lab-0.1.0/src/pixie/web/__init__.py +0 -6
  73. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.dockerignore +0 -0
  74. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.github/workflows/docs.yml +0 -0
  75. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.gitignore +0 -0
  76. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/LICENSE +0 -0
  77. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/Makefile +0 -0
  78. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/PLAN.md +0 -0
  79. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/netboot-pc.toml +0 -0
  80. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-flash-always.toml +0 -0
  81. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-flash.toml +0 -0
  82. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-inventory.toml +0 -0
  83. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-tui.toml +0 -0
  84. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe.toml +0 -0
  85. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/usbboot-pc.toml +0 -0
  86. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/live_build.py +0 -0
  87. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pixie_ipxe_build.py +0 -0
  88. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pixie_wheel_stage.py +0 -0
  89. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_flash_stage.py +0 -0
  90. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_inventory_stage.py +0 -0
  91. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_prepare.py +0 -0
  92. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/usb_iso_build.py +0 -0
  93. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/netboot-pc.yaml +0 -0
  94. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-flash-always.yaml +0 -0
  95. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-flash.yaml +0 -0
  96. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-inventory.yaml +0 -0
  97. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-nbdboot.yaml +0 -0
  98. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-tui.yaml +0 -0
  99. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe.yaml +0 -0
  100. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/usbboot-pc.yaml +0 -0
  101. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/audit.md +0 -0
  102. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/conf.py +0 -0
  103. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/index.md +0 -0
  104. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/README.md +0 -0
  105. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/pyproject.toml +0 -0
  106. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/src/pixie_docs/__init__.py +0 -0
  107. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/src/pixie_docs/cli.py +0 -0
  108. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/README.md +0 -0
  109. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/auxiliary/cloudinit-metadata.meta +0 -0
  110. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/auxiliary/ipxe-embed.ipxe +0 -0
  111. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/auxiliary/ipxe-local-general.h +0 -0
  112. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/auto/config +0 -0
  113. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/archives/backports.list.binary +0 -0
  114. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/archives/backports.list.chroot +0 -0
  115. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/bootloaders/grub-efi/splash.png +0 -0
  116. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/bootloaders/syslinux/splash.png +0 -0
  117. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0500-pixie-install.hook.chroot +0 -0
  118. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0500-pixie-skip-bootloader-menu.hook.binary +0 -0
  119. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0600-pixie-r8125-dkms.hook.chroot +0 -0
  120. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0700-pixie-clock-from-http.hook.chroot +0 -0
  121. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0800-pixie-ssh-live.hook.chroot +0 -0
  122. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0900-pixie-enable-services.hook.chroot +0 -0
  123. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0980-pixie-apt-validate.hook.chroot +0 -0
  124. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/issue +0 -0
  125. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/modprobe.d/zz-pixie-blacklist-nouveau.conf +0 -0
  126. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/motd +0 -0
  127. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/profile.d/pixie-version.sh +0 -0
  128. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/getty@.service.d/zzz-pixie-autologin-root.conf +0 -0
  129. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-early.service +0 -0
  130. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-late.service +0 -0
  131. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-banner-mid.service +0 -0
  132. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-clock-from-http.service +0 -0
  133. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-images-discover.service +0 -0
  134. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-on-tty1.service +0 -0
  135. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/pixie-usb-grow.service +0 -0
  136. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/systemd/system/var-lib-pixie-images.mount +0 -0
  137. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/udev/rules.d/70-pixie-realtek-2g5-offloads.rules +0 -0
  138. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/lib/systemd/system-generators/pixie-skip-usb-only-units-on-netboot +0 -0
  139. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-boot-banner +0 -0
  140. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-clock-from-http +0 -0
  141. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-images-discover +0 -0
  142. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-on-tty1 +0 -0
  143. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-trace +0 -0
  144. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-usb-grow +0 -0
  145. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/package-lists/pixie-base.list.chroot +0 -0
  146. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/package-lists/pixie-flash.list.chroot +0 -0
  147. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/preferences/backports.pref.binary +0 -0
  148. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/preferences/backports.pref.chroot +0 -0
  149. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/__init__.py +0 -0
  150. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_schema.py +0 -0
  151. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/deploy/__init__.py +0 -0
  152. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/deploy/_templates.py +0 -0
  153. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/disks.py +0 -0
  154. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/__init__.py +0 -0
  155. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/_routes.py +0 -0
  156. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/machines/__init__.py +0 -0
  157. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/oras.py +0 -0
  158. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pivot/__init__.py +0 -0
  159. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pxe/__init__.py +0 -0
  160. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tftp/__init__.py +0 -0
  161. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tftp/_supervisor.py +0 -0
  162. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_auth.py +0 -0
  163. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/.gitkeep +0 -0
  164. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/bootstrap-icons.min.css +0 -0
  165. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/bootstrap.min.css +0 -0
  166. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/fonts/bootstrap-icons.woff +0 -0
  167. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/fonts/bootstrap-icons.woff2 +0 -0
  168. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/htmx.min.js +0 -0
  169. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/pixie-favicon.png +0 -0
  170. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/sse.js +0 -0
  171. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_table_state.py +0 -0
  172. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/_partials/page_description.html +0 -0
  173. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/_partials/recent_events.html +0 -0
  174. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/_partials/table_helpers.html +0 -0
  175. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/catalog_detail.html +0 -0
  176. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/bootstrap.j2 +0 -0
  177. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/exit.j2 +0 -0
  178. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/pixie-live-env.j2 +0 -0
  179. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/unavailable.j2 +0 -0
  180. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/login.html +0 -0
  181. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/machines.html +0 -0
  182. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/__init__.py +0 -0
  183. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/__init__.py +0 -0
  184. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_catalog_fetch.py +0 -0
  185. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_deploy.py +0 -0
  186. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_exports.py +0 -0
  187. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_inventory.py +0 -0
  188. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_pxe.py +0 -0
  189. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_tftp.py +0 -0
  190. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_auth.py +0 -0
  191. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_catalog_api.py +0 -0
  192. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_event_kinds.py +0 -0
  193. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_healthz.py +0 -0
  194. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_pivot.py +0 -0
  195. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_pxe_plan_json.py +0 -0
  196. {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_schema.py +0 -0
@@ -17,6 +17,14 @@ name: cicd
17
17
  # version cannot be re-uploaded -- so the job gates on every
18
18
  # prior job (test + integration + build-container + build-wheel
19
19
  # + build-media + every PXE chain test) succeeding first.
20
+ # * ``publish-container``: build + push the appliance image to
21
+ # ghcr.io/<owner>/pixie (``:<version>`` + ``:latest``), v* tags
22
+ # only. Before this the image was smoke-built and thrown away, so
23
+ # the documented ghcr image was never populated by CI.
24
+ # * ``publish-release``: create the GitHub Release for the tag and
25
+ # attach the media ``build-media`` produces -- the netboot-pc
26
+ # live-env bake + the usbboot-pc bootable image + checksums. These
27
+ # used to live only as ephemeral, login-gated workflow artifacts.
20
28
  #
21
29
  # First publish (claiming the pixie-lab name) requires a one-time
22
30
  # PyPI setup: register a Pending Publisher on https://pypi.org
@@ -602,3 +610,127 @@ jobs:
602
610
  # later job is retrying), succeed silently rather than
603
611
  # fail.
604
612
  skip-existing: true
613
+
614
+ publish-container:
615
+ name: Publish container image to ghcr
616
+ # Build + push the appliance image to ghcr.io/<owner>/pixie on a
617
+ # v* tag: ``:<version>`` (the tag minus its leading ``v``) plus
618
+ # ``:latest``. This is the image the compose deploy runs; before
619
+ # this the ``build-container`` job only smoke-built it and threw it
620
+ # away, so ghcr.io/safl/pixie was never populated by CI. Gated on
621
+ # the whole pipeline like publish-pypi -- never ship an image the
622
+ # boot-chain tests did not pass against.
623
+ needs:
624
+ - test
625
+ - integration
626
+ - build-container
627
+ - build-wheel
628
+ - build-media
629
+ - test-pxe
630
+ - test-pxe-nbdboot
631
+ - test-pxe-inventory
632
+ - test-pxe-tui
633
+ - test-pxe-flash
634
+ - test-pxe-flash-always
635
+ runs-on: ubuntu-latest
636
+ if: startsWith(github.ref, 'refs/tags/v')
637
+ permissions:
638
+ contents: read
639
+ packages: write
640
+ steps:
641
+ - uses: actions/checkout@v6
642
+ - name: Log in to ghcr.io
643
+ run: |
644
+ echo "${{ secrets.GITHUB_TOKEN }}" \
645
+ | podman login ghcr.io -u "${{ github.actor }}" --password-stdin
646
+ - name: Build + push image
647
+ run: |
648
+ set -eu
649
+ IMAGE=ghcr.io/${{ github.repository_owner }}/pixie
650
+ VERSION=${GITHUB_REF_NAME#v}
651
+ podman build \
652
+ --build-arg PIXIE_VERSION="$VERSION" \
653
+ -t "$IMAGE:$VERSION" \
654
+ -t "$IMAGE:latest" \
655
+ -f Containerfile .
656
+ podman push "$IMAGE:$VERSION"
657
+ podman push "$IMAGE:latest"
658
+
659
+ publish-release:
660
+ name: Publish GitHub Release (media assets)
661
+ # Attach the media CI already builds -- the netboot-pc live-env
662
+ # bake (vmlinuz + initrd + squashfs) and the usbboot-pc bootable
663
+ # image -- to a GitHub Release on v* tags. Before this they only
664
+ # existed as ephemeral, login-gated workflow artifacts, so an
665
+ # operator had no released live-env or USB image to download. Same
666
+ # full-pipeline gate as the other publish jobs.
667
+ needs:
668
+ - test
669
+ - integration
670
+ - build-container
671
+ - build-wheel
672
+ - build-media
673
+ - test-pxe
674
+ - test-pxe-nbdboot
675
+ - test-pxe-inventory
676
+ - test-pxe-tui
677
+ - test-pxe-flash
678
+ - test-pxe-flash-always
679
+ runs-on: ubuntu-latest
680
+ if: startsWith(github.ref, 'refs/tags/v')
681
+ permissions:
682
+ contents: write
683
+ steps:
684
+ - uses: actions/checkout@v6
685
+ - name: Download media artifacts
686
+ uses: actions/download-artifact@v8
687
+ with:
688
+ # Both build-media matrix legs upload under this shape:
689
+ # ``pixie-netboot-pc-x86_64`` + ``pixie-usbboot-pc-x86_64``.
690
+ # merge-multiple flattens both into one directory.
691
+ pattern: pixie-*-pc-x86_64
692
+ merge-multiple: true
693
+ path: release-assets
694
+ - name: List release assets
695
+ run: ls -la release-assets
696
+ - name: Assemble live-env fetch tarball
697
+ # pixie's in-app "Fetch live-env" pulls ONE tarball that unpacks
698
+ # to vmlinuz + initrd + live.squashfs (see catalog/_live_env.py).
699
+ # Repackage the netboot-pc trio under those stable names so
700
+ # ``.../releases/latest/download/pixie-live-env-x86_64.tar.gz``
701
+ # -- the default PIXIE_LIVE_ENV_SRC -- resolves.
702
+ run: |
703
+ set -eu
704
+ d=$(mktemp -d)
705
+ cp release-assets/pixie-netboot-pc-x86_64-*.vmlinuz "$d/vmlinuz"
706
+ cp release-assets/pixie-netboot-pc-x86_64-*.initrd "$d/initrd"
707
+ cp release-assets/pixie-netboot-pc-x86_64-*.squashfs "$d/live.squashfs"
708
+ tar -C "$d" -czf release-assets/pixie-live-env-x86_64.tar.gz \
709
+ vmlinuz initrd live.squashfs
710
+ ( cd release-assets && sha256sum pixie-live-env-x86_64.tar.gz \
711
+ > pixie-live-env-x86_64.tar.gz.sha256 )
712
+ # Ship the curated catalog (the netboot-capable nosi subset)
713
+ # so DEFAULT_CATALOG_URL (.../releases/latest/download/catalog.toml)
714
+ # resolves and pixie's Import + live-env TUI default work.
715
+ cp src/pixie/catalog/catalog.toml release-assets/catalog.toml
716
+ ls -la release-assets
717
+ - name: Create / update GitHub Release
718
+ uses: softprops/action-gh-release@v2
719
+ with:
720
+ files: release-assets/*
721
+ # Fail loud if the glob matched nothing -- a release with no
722
+ # media is the bug this job exists to prevent.
723
+ fail_on_unmatched_files: true
724
+ generate_release_notes: true
725
+ body: |
726
+ ## Distribution
727
+
728
+ - Container image: `ghcr.io/${{ github.repository_owner }}/pixie:latest` and the matching version tag (this tag without the leading `v`).
729
+ - Python CLI: `uv tool install pixie-lab` (or `pip install pixie-lab`).
730
+
731
+ ## Media assets attached here
732
+
733
+ - `pixie-live-env-x86_64.tar.gz` — the live-env trio packaged for pixie's in-app **Fetch live-env** action (the default `PIXIE_LIVE_ENV_SRC`). Click Fetch live-env on the dashboard and pixie pulls + stages this itself; no manual bake or copy.
734
+ - `pixie-netboot-pc-x86_64-*.{vmlinuz,initrd,squashfs}` — the same live-env (netboot-pc bake) as loose files, if you prefer to stage `PIXIE_LIVE_ENV_DIR` (`data/live-env/`) by hand. Used by the `pixie-flash-once` / `pixie-flash-always` / `pixie-inventory` / `pixie-tui` boot modes.
735
+ - `pixie-usbboot-pc-x86_64-*.iso.gz` — bootable USB / ISO media for the pixie live env.
736
+ - `*.sha256` — checksums for the above.
@@ -7,15 +7,140 @@ perceives, defaults that survived a `pip install -U`, and gates that
7
7
  landed in CI.
8
8
 
9
9
  Per-release commit history lives in `git log`; this file is the
10
- operator-facing summary. Nothing between the 0.1.0 skeleton and the
11
- next real release has been tagged: the intermediate work all lands
12
- under `[Unreleased]` until an operator can drive the full flow end-
13
- to-end on real hardware.
10
+ operator-facing summary.
14
11
 
15
12
  ## [Unreleased]
16
13
 
14
+ ## [0.3.0] - 2026-07-23
15
+
16
+ Operator-facing surface + release plumbing. A fleet overlay-management
17
+ page, a tightened inventory view, and a dedicated live-env pane; the
18
+ release now ships the container image + boot media + a curated catalog,
19
+ and pixie can fetch its own live-env.
20
+
21
+ ### Added
22
+
23
+ **Fleet-wide overlay management page.** A new **Overlays** page
24
+ (`/ui/overlays`) lists every persistent nbdboot qcow2 across the fleet
25
+ with disk-used, last-modified, serving port, and a state
26
+ (active / idle / orphaned / missing); per-row Reset and a Prune that
27
+ reclaims only the orphaned + missing ones. The machine-detail Inventory
28
+ view was tightened from stacked stat-cards into one dense summary.
29
+
30
+ **Pixie ships a curated catalog and defaults to it, not nosi's.** A
31
+ fresh (empty) catalog is seeded on first start from a `catalog.toml`
32
+ bundled in the package: a strict subset of the upstream nosi catalog
33
+ restricted to the four netboot-capable images pixie's nbdboot + live-env
34
+ chains actually test and support (debian-13-headless, ubuntu-2404 /
35
+ 2604-headless, fedora-44-headless, each with its netboot bundle). The
36
+ desktop / proxmox / rpios / freebsd variants that have no netboot bundle
37
+ are omitted. The "Import catalog" field and the live-env TUI now default
38
+ to the pixie release copy of this curated catalog rather than the full
39
+ nosi catalog; both remain overridable by URL. Seeding is one-shot,
40
+ never clobbers an operator-populated catalog, and is disabled with
41
+ `PIXIE_SEED_CATALOG=0`.
42
+
43
+ **Pixie can fetch its own live-env, from a dedicated Live-env pane.** A
44
+ new **Live env** page (`/ui/live-env`) is the one place the live env is
45
+ managed: staged-media readiness, a **Fetch live env** action, the fetch
46
+ source, and the extra-kernel-cmdline override. Fetch downloads the
47
+ netboot-pc bake as a single tarball (`PIXIE_LIVE_ENV_SRC`, defaulting to
48
+ the latest GitHub release's `pixie-live-env-x86_64.tar.gz`) and stages
49
+ `vmlinuz` + `initrd` + `live.squashfs` under `PIXIE_LIVE_ENV_DIR`,
50
+ reusing the catalog fetch's curl transport. This replaces the only
51
+ artifact an operator previously had to bake locally
52
+ (`make build VARIANT=netboot-pc`) or hand-copy. The source is overridable
53
+ per deploy (point at a mirror for air-gapped installs). The dashboard
54
+ Live-env card is now status-only and links to the pane; the live-env
55
+ knobs moved off the Settings page. The `publish-release` job assembles
56
+ that tarball so the default source resolves.
57
+
58
+ **Releases now ship the container image and the boot media, not just
59
+ the PyPI package.** Tagging `v*` publishes the appliance image to
60
+ `ghcr.io/safl/pixie` (`:<version>` + `:latest`) and creates a GitHub
61
+ Release with the boot media attached: the netboot-pc live-env bake
62
+ (`vmlinuz` + `initrd` + `squashfs`) that the `pixie-flash-once` /
63
+ `pixie-flash-always` / `pixie-inventory` / `pixie-tui` modes chain
64
+ into, and the `usbboot-pc` bootable `.iso.gz`, each with a `.sha256`.
65
+ Previously a tag published only to PyPI, so the ghcr image that
66
+ `pixie-lab deploy` pins and the live-env media both existed only as
67
+ ephemeral CI artifacts or a local `make build` -- a fresh
68
+ `pixie-lab deploy` pulled an image that was never pushed.
69
+
70
+ ## [0.2.0] - 2026-07-22
71
+
72
+ First real release after the 0.1.0 skeleton: nbdboot (ephemeral +
73
+ persistent per-machine overlays) validated end-to-end on real
74
+ hardware, plus a bty-lab-shaped deploy CLI and an operator-managed
75
+ event log.
76
+
17
77
  ### Added
18
78
 
79
+ **Event-log Acknowledge + Clear actions.** The `/ui/events` page grows
80
+ two bulk actions: **Acknowledge** advances the ack cursor so the
81
+ dashboard's unacknowledged-error count zeros without touching the log,
82
+ and **Clear** wipes the whole log (behind a confirm) and drops one
83
+ `events.cleared` marker so the reset itself stays on the record.
84
+ Mirrors the ack/clear affordances bty's event log carried.
85
+
86
+ **`pixie-lab purge` reworked to match `bty-lab`.** `purge` now prints
87
+ a plan and gates the destructive parts behind a `y/N` confirmation
88
+ (`-y`/`--yes` to skip; a non-TTY refuses without `--yes`). Flags:
89
+ `--data` deletes the on-disk state (previously `--wipe-data`, which
90
+ never actually removed the bind-mounted `data/`), `--images` removes
91
+ the container image, and `--all` also removes the deploy directory
92
+ (implies `--data`).
93
+
94
+ **Per-machine persistent qcow2 overlays under `nbdboot`.** A new
95
+ `overlay_profile` field on the machine binding flips one target from
96
+ the default ephemeral-tmpfs behaviour to a per-machine writable
97
+ overlay without changing anything else about the bind. A non-blank
98
+ profile maps to a `data/overlays/<mac>/<image_sha>/<profile>.qcow2`
99
+ file with the image's base blob as `backing_file`, served over NBD by
100
+ `qemu-nbd`; the target mounts it read-write and system changes (apt
101
+ installs, kernel modules, hardware-specific config) survive reboots.
102
+ Overlays are keyed by `(mac, image_sha, profile)`, so different
103
+ machines have fully independent files under the same profile name and
104
+ rebinding to a different image leaves the old image's overlays on
105
+ disk for a later resume. A Reset button on the machine detail page
106
+ tears down `qemu-nbd`, unlinks the qcow2, and lets the next boot
107
+ lazy-create a fresh overlay from the base. New `overlays` table on
108
+ state.db (idempotent additive migration), new `overlay.created`,
109
+ `overlay.reset`, `overlay.booted` events. Concurrency is by
110
+ construction (a MAC boots one target at a time), so there is no
111
+ holder tracking or force-reclaim.
112
+
113
+ **Slick Inventory card viz.** The machine detail Inventory card was
114
+ rewritten to consume a normalised view of the stored `lshw -json`
115
+ blob. CPU renders one stat-block per socket with the model as
116
+ headline, an architecture badge, and Bootstrap `display-6` big-type
117
+ numbers for cores over threads plus max clock. Memory shows a total
118
+ headline plus a per-DIMM slot-fill row (filled blocks for populated
119
+ SMBIOS type-17 bank records, outlined blocks for empty slots,
120
+ hover-title tooltip per slot showing size / speed / type), with a
121
+ total-only fallback for firmwares that skimp on bank records. The
122
+ extractor lives in `pixie.web._inventory.normalise_inventory` and
123
+ runs at render time, so a wire-format change or a new lshw quirk
124
+ touches one function. Two new Jinja filters: `humanize_bytes` and
125
+ `humanize_hz`.
126
+
127
+ ### Fixed
128
+
129
+ **Truncated `.img.gz` fetches now fail at the download stage.**
130
+ Operators saw "decompress img.gz failed: Compressed file ended before
131
+ the end-of-stream marker was reached" on the catalog page when a
132
+ ghcr download was interrupted mid-transfer. Root cause: the fetch
133
+ pipeline's byte-copy loop treated `resp.read()` returning an empty
134
+ chunk as "done" but urllib does not raise when a peer closes the
135
+ connection early, so a short body was accepted and the gzip trailer
136
+ check surfaced the truncation several minutes downstream of the
137
+ actual failure. `_stream_to_tmpfile` now cross-checks bytes-written
138
+ against `Content-Length` and raises a clear `download truncated for
139
+ <url>: got X of Y bytes` `FetchError` at the point of cause. A
140
+ related cleanup leak (the `finally` block only unlinked `.inflight`
141
+ files for `tar.gz`, so failing `img.gz` fetches left multi-GB
142
+ orphans in `data/tmp/` forever) is fixed as part of the same change.
143
+
19
144
  **Settings pane with per-operator display picks.** New top-nav
20
145
  pill `/ui/settings` with two knobs: display timezone (IANA zone
21
146
  name) and datetime format (strftime pattern). Both resolve override
@@ -44,6 +44,9 @@ LABEL org.opencontainers.image.title="pixie" \
44
44
  # /usr/lib/ipxe/, which we copy into pixie's
45
45
  # TFTP root so the daemon can serve them.
46
46
  # qemu-utils: qemu-img info + qemu-nbd for qcow2 handling.
47
+ # fdisk: sfdisk for pixie._partition partition-offset lookup
48
+ # on persistent-overlay renders. On Ubuntu 26.04
49
+ # sfdisk moved out of util-linux into this package.
47
50
  # ca-certificates: HTTPS fetch of ORAS + release assets.
48
51
  #
49
52
  # Verified against ubuntu:26.04's package list in CI on every build;
@@ -64,6 +67,7 @@ RUN apt-get update \
64
67
  tftpd-hpa \
65
68
  ipxe \
66
69
  qemu-utils \
70
+ fdisk \
67
71
  && rm -rf /var/lib/apt/lists/*
68
72
 
69
73
  # App under /app with editable-style install from the source we copy in.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixie-lab
3
- Version: 0.1.0
3
+ Version: 0.3.0
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
@@ -81,11 +81,13 @@ top nav has Machines, Catalog, Events, and Settings.
81
81
 
82
82
  Everything writable lives under `PIXIE_DATA_DIR` (default
83
83
  `/var/lib/pixie`). `state.db` holds catalog rows, machine rows,
84
- event log, settings, and NBD-export records. `blobs/<sha>/blob`
85
- holds fetched disk images. `artifacts/<sha>/{vmlinuz,initrd,
86
- manifest.json}` holds unpacked netboot bundles. `live-env/` holds
87
- the pixie live env kernel + initrd + squashfs the netboot-pc bake
88
- produced.
84
+ event log, settings, NBD-export records, and persistent-overlay
85
+ records. `blobs/<sha>/blob` holds fetched disk images.
86
+ `artifacts/<sha>/{vmlinuz,initrd,manifest.json}` holds unpacked
87
+ netboot bundles. `overlays/<mac>/<image_sha>/<profile>.qcow2` holds
88
+ per-machine writable overlays for the `nbdboot` boot mode.
89
+ `live-env/` holds the pixie live env kernel + initrd + squashfs the
90
+ netboot-pc bake produced.
89
91
 
90
92
  Both admin password and display timezone / strftime pattern have
91
93
  env-var overrides plus DB overrides via `/ui/settings`; env wins
@@ -53,11 +53,13 @@ top nav has Machines, Catalog, Events, and Settings.
53
53
 
54
54
  Everything writable lives under `PIXIE_DATA_DIR` (default
55
55
  `/var/lib/pixie`). `state.db` holds catalog rows, machine rows,
56
- event log, settings, and NBD-export records. `blobs/<sha>/blob`
57
- holds fetched disk images. `artifacts/<sha>/{vmlinuz,initrd,
58
- manifest.json}` holds unpacked netboot bundles. `live-env/` holds
59
- the pixie live env kernel + initrd + squashfs the netboot-pc bake
60
- produced.
56
+ event log, settings, NBD-export records, and persistent-overlay
57
+ records. `blobs/<sha>/blob` holds fetched disk images.
58
+ `artifacts/<sha>/{vmlinuz,initrd,manifest.json}` holds unpacked
59
+ netboot bundles. `overlays/<mac>/<image_sha>/<profile>.qcow2` holds
60
+ per-machine writable overlays for the `nbdboot` boot mode.
61
+ `live-env/` holds the pixie live env kernel + initrd + squashfs the
62
+ netboot-pc bake produced.
61
63
 
62
64
  Both admin password and display timezone / strftime pattern have
63
65
  env-var overrides plus DB overrides via `/ui/settings`; env wins
@@ -88,3 +88,62 @@ needle = "systemd["
88
88
  [[test.pxe.chain_markers]]
89
89
  key = "debian-userspace"
90
90
  needle = "Debian"
91
+
92
+ # ---- Positive userland proof ---------------------------------------
93
+ #
94
+ # Getty printed the login prompt on the serial console: this is what
95
+ # "operator-usable userland" actually looks like from outside the VM.
96
+ # Getty runs as part of getty.target, which is pulled in by
97
+ # multi-user.target, so a login prompt on serial is unambiguous
98
+ # proof that multi-user.target was reached. Chosen over the
99
+ # "Reached target multi-user.target" systemd banner because Debian
100
+ # 13 boots in ``quiet`` mode by default and does not print that
101
+ # banner on the console -- getty's login prompt is always printed.
102
+ # Catches the "boots to systemd but a hanging unit blocks userspace"
103
+ # shape where the earlier markers (systemd-started, debian-userspace)
104
+ # fire but the target never reaches operator-usable userland.
105
+ [[test.pxe.chain_markers]]
106
+ key = "login-prompt"
107
+ needle = "login:"
108
+
109
+ # ---- Forbidden markers ---------------------------------------------
110
+ #
111
+ # Anything below here MUST NOT appear on the client serial log or
112
+ # in pixie's container logs. The check runs alongside the positive-
113
+ # marker poll and fails the whole test the moment any needle hits.
114
+ # Kept scoped to failure signals that would leave a booted-looking
115
+ # target with a broken userland (booted markers OK, but the console
116
+ # spells out the bad news).
117
+
118
+ # systemd-resolved / networkd starvation with no fallback resolv.conf.
119
+ # The mount hook's mask of systemd-networkd + NetworkManager killed
120
+ # both DHCP daemons on the pivoted rootfs; if the initrd's
121
+ # /etc/resolv.conf write did not stick, apt / curl on the target log
122
+ # this literal string on serial. Nosi 2026.W30's mount hook unlinks
123
+ # the /etc/resolv.conf symlink before writing so glibc's resolver
124
+ # reads the file directly -- if a regression puts the symlink back,
125
+ # this marker fires.
126
+ [[test.pxe.forbidden_markers]]
127
+ key = "dns-broken"
128
+ needle = "Temporary failure resolving"
129
+
130
+ # sshd itself failed to start (its unit went into failed state during
131
+ # multi-user.target activation). Distinct from "sshd-started not
132
+ # seen" so the operator gets a clear "start failure" vs "start never
133
+ # attempted" diagnosis in the CI log.
134
+ [[test.pxe.forbidden_markers]]
135
+ key = "sshd-failed"
136
+ needle = "Failed to start ssh.service"
137
+
138
+ # Dracut emergency shell: the mount hook died with _pixie_die and
139
+ # systemd-emergency published the emergency-shell prompt. Every
140
+ # persist-branch regression this session ended here.
141
+ [[test.pxe.forbidden_markers]]
142
+ key = "dracut-emergency"
143
+ needle = "dracut-emergency"
144
+
145
+ # Post-pivot emergency mode (something failed in userspace's own
146
+ # init and systemd fell back to emergency.target).
147
+ [[test.pxe.forbidden_markers]]
148
+ key = "emergency-mode"
149
+ needle = "Emergency mode"
@@ -243,6 +243,7 @@ def main(args, cijoe) -> int:
243
243
  client = _start_client_vm(workspace, cfg, client_log, firmware)
244
244
 
245
245
  markers = _build_markers(cfg)
246
+ forbidden = [(e["key"], e["needle"]) for e in cfg.get("forbidden_markers", [])]
246
247
  # ``mode=flash`` runs the full real-image pipeline (oras
247
248
  # pull + img.gz decompress + dd of ~10 GiB) inside the live
248
249
  # env; ``mode=nbdboot`` also pulls the real nosi image but
@@ -252,7 +253,20 @@ def main(args, cijoe) -> int:
252
253
  # blob store, similar order of magnitude. Everything else
253
254
  # finishes well within the 5 min short-mode budget.
254
255
  chain_deadline = CHAIN_TIMEOUT_FLASH if mode in ("flash", "nbdboot") else CHAIN_TIMEOUT
255
- seen = _wait_for_chain_markers(client_log, markers, chain_deadline)
256
+ try:
257
+ seen = _wait_for_chain_markers(client_log, markers, chain_deadline, forbidden)
258
+ except ForbiddenMarkerSeen as exc:
259
+ log.error(
260
+ f"PXE chain hit forbidden marker {exc.key!r} ({exc.needle!r}); "
261
+ "a known-bad userland signal appeared on the client serial log "
262
+ "or in pixie's container logs. Dumping context."
263
+ )
264
+ qemu_log = client_log.with_suffix(".qemu.log")
265
+ _dump_tail(qemu_log, 60)
266
+ _dump_tail(workspace / "dnsmasq.log", 60)
267
+ _dump_tail(client_log, 200)
268
+ _dump_container_logs()
269
+ return errno.EPROTO
256
270
  missing = [k for k, ok in seen.items() if not ok]
257
271
  if missing:
258
272
  log.error(f"PXE chain incomplete; missing markers: {', '.join(missing)}")
@@ -620,30 +634,52 @@ def _build_markers(cfg):
620
634
  return out
621
635
 
622
636
 
623
- def _wait_for_chain_markers(log_path: Path, markers, timeout: int):
637
+ def _wait_for_chain_markers(log_path: Path, markers, timeout: int, forbidden=None):
638
+ """Poll the client serial log + pixie's container logs for the
639
+ positive markers, checking the same buffers for any ``forbidden``
640
+ substrings each round. A forbidden hit shortcuts the wait with
641
+ ``ForbiddenMarkerSeen`` so the caller fails the test loud instead
642
+ of timing out looking for a positive marker that would never
643
+ arrive (the DNS / sshd / emergency-shell regressions come with
644
+ highly recognisable serial output and belong in this list rather
645
+ than in the reactive "check what's missing" branch)."""
624
646
  seen = {key: False for key, _ in markers}
647
+ forbidden = forbidden or []
625
648
  deadline = time.monotonic() + timeout
626
649
  while time.monotonic() < deadline and not all(seen.values()):
627
- # Check the serial log for iPXE-side markers.
650
+ body = ""
628
651
  if log_path.exists():
629
652
  body = log_path.read_text(encoding="utf-8", errors="replace")
630
653
  for key, needle in markers:
631
654
  if not seen[key] and needle in body:
632
655
  log.info(f" + {key}: matched {needle!r}")
633
656
  seen[key] = True
634
- # Also mirror-check container logs so a server-side hit counts
635
- # even when the client's console doesn't spell the fetch out.
636
657
  cont = _container_log_snapshot()
637
658
  for key, needle in markers:
638
659
  if not seen[key] and needle in cont:
639
660
  log.info(f" + {key}: matched {needle!r} in container logs")
640
661
  seen[key] = True
662
+ for key, needle in forbidden:
663
+ if needle in body or needle in cont:
664
+ raise ForbiddenMarkerSeen(key, needle)
641
665
  if all(seen.values()):
642
666
  break
643
667
  time.sleep(2)
644
668
  return seen
645
669
 
646
670
 
671
+ class ForbiddenMarkerSeen(RuntimeError):
672
+ """A ``[[test.pxe.forbidden_markers]]`` entry was observed on the
673
+ client serial log or in pixie's container logs. Raised by the
674
+ marker-polling loop so the caller can log the exact key + needle
675
+ that fired and fail with a distinct error code."""
676
+
677
+ def __init__(self, key: str, needle: str) -> None:
678
+ super().__init__(f"forbidden marker {key!r} matched {needle!r}")
679
+ self.key = key
680
+ self.needle = needle
681
+
682
+
647
683
  def _container_log_snapshot() -> str:
648
684
  res = subprocess.run(
649
685
  ["podman", "logs", "--tail", "500", CONTAINER_NAME],
@@ -49,11 +49,50 @@ known-clean image each cycle.
49
49
  ### `nbdboot`
50
50
 
51
51
  The target boots the image's OWN kernel (extracted from the sibling
52
- netboot bundle) and mounts the image over NBD. Root is an
52
+ netboot bundle) and mounts the image over NBD. By default, root is an
53
53
  overlay-on-tmpfs: writes go to RAM, nothing propagates back to the
54
54
  source blob. Multiple targets can nbdboot the same image
55
55
  simultaneously because they each get their own overlay.
56
56
 
57
+ **Persistent overlays** flip a single target from ephemeral to dev
58
+ mode without changing anything else about the bind. On the machine
59
+ detail page, the `Overlay profile` field is blank by default
60
+ (ephemeral, unchanged behaviour) or names a per-machine profile
61
+ (e.g. `simon`, `karl`, `ci-with-nvme-tools`). A non-blank profile
62
+ maps to a per-machine qcow2 file with the image's base blob as
63
+ `backing_file`, served by `qemu-nbd` at a dedicated port. The target
64
+ mounts the NBD device read-write; system-level changes (apt-installed
65
+ packages, hardware-specific config, kernel modules) land on the qcow2
66
+ and survive reboots.
67
+
68
+ Overlays are keyed by `(mac, image_content_sha256, profile)`:
69
+ different machines have fully independent files even under the same
70
+ profile name, and rebinding a machine to a different image leaves the
71
+ old image's overlays on disk (a rebind back resumes them). Storage is
72
+ `data/overlays/<mac>/<image_sha>/<profile>.qcow2`. The **Reset**
73
+ button on the machine detail page tears down `qemu-nbd`, unlinks the
74
+ qcow2, and lets the next boot lazy-create a fresh overlay from the
75
+ base.
76
+
77
+ Concurrency is by construction: a MAC boots one target at a time, so
78
+ two machines can never contend for the same qcow2. There is no
79
+ holder-tracking or force-reclaim.
80
+
81
+ **Kexec into a locally-installed kernel.** The netboot bundle owns
82
+ the kernel and initrd pixie serves. Installing `linux-image-*` on the
83
+ target's persistent overlay writes files to `/boot` but the next
84
+ power-cycle refetches pixie's kernel and those files sit unused.
85
+ `kexec` bridges that gap: the netboot kernel comes up, then the
86
+ operator runs
87
+ `kexec -l /boot/vmlinuz-<v> --initrd=/boot/initrd.img-<v>
88
+ --reuse-cmdline && systemctl kexec` to switch to the local kernel
89
+ without going through firmware. Every netboot-shipping nosi variant
90
+ now bakes `kexec-tools` in; see nosi's [Custom kernel under netboot
91
+ (kexec)](https://safl.github.io/nosi/kexec.html) for the full
92
+ workflow. Recovery from a bad kernel is a power-cycle back to
93
+ pixie's kernel; nothing kexec's automatically, so a broken install
94
+ never becomes a boot loop.
95
+
57
96
  ## Prerequisites at a glance
58
97
 
59
98
  | Mode | Needs image | Needs inventory | Needs target disk |