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.
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.github/workflows/cicd.yaml +132 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/CHANGELOG.md +129 -4
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/Containerfile +4 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/PKG-INFO +8 -6
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/README.md +7 -5
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-nbdboot.toml +59 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_run_chain_test.py +41 -5
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/boot-modes.md +40 -1
- pixie_lab-0.3.0/docs/src/deployment.md +219 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/hardware-quirks.md +32 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/quickstart.md +3 -3
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pyproject.toml +1 -1
- pixie_lab-0.3.0/src/pixie/_partition.py +152 -0
- pixie_lab-0.3.0/src/pixie/_util.py +17 -0
- pixie_lab-0.3.0/src/pixie/catalog/__init__.py +49 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_fetcher.py +231 -68
- pixie_lab-0.3.0/src/pixie/catalog/_live_env.py +128 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_routes.py +5 -6
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_store.py +10 -9
- pixie_lab-0.3.0/src/pixie/catalog/catalog.toml +81 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/deploy/_main.py +122 -26
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/_kinds.py +64 -1
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/_log.py +53 -1
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/exports/__init__.py +0 -5
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/exports/_routes.py +3 -2
- pixie_lab-0.3.0/src/pixie/exports/_store.py +347 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/exports/_supervisor.py +143 -7
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/flash.py +4 -6
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/images.py +8 -9
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/machines/_routes.py +10 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/machines/_store.py +52 -9
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pivot/nbdboot +116 -55
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pxe/_renderer.py +127 -4
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pxe/_routes.py +2 -2
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tui/__init__.py +7 -9
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tui/_app.py +10 -7
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tui_catalog.py +16 -22
- pixie_lab-0.3.0/src/pixie/web/__init__.py +9 -0
- pixie_lab-0.3.0/src/pixie/web/_bind_preview.py +84 -0
- pixie_lab-0.3.0/src/pixie/web/_inventory.py +275 -0
- pixie_lab-0.3.0/src/pixie/web/_overlays.py +225 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_settings_store.py +23 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/catalog.html +2 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/dashboard.html +110 -32
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/events.html +17 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/nbdboot.j2 +10 -1
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/layout.html +6 -0
- pixie_lab-0.3.0/src/pixie/web/_templates/live_env.html +169 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/machine_detail.html +218 -68
- pixie_lab-0.3.0/src/pixie/web/_templates/overlays.html +248 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/settings.html +0 -58
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/main.py +581 -18
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/conftest.py +17 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/conftest.py +7 -0
- pixie_lab-0.3.0/tests/test_catalog_seed.py +118 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_deploy_unit.py +71 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_events_unit.py +33 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_exports_unit.py +57 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_fetch_progress.py +69 -2
- pixie_lab-0.3.0/tests/test_live_env_fetch.py +203 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_machines_unit.py +73 -0
- pixie_lab-0.3.0/tests/test_overlays_ui.py +255 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_settings.py +4 -4
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_ui_pages.py +107 -9
- pixie_lab-0.3.0/tests/test_web_bind_preview.py +89 -0
- pixie_lab-0.3.0/tests/test_web_inventory.py +232 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/uv.lock +1 -1
- pixie_lab-0.1.0/docs/src/deployment.md +0 -120
- pixie_lab-0.1.0/src/pixie/_util.py +0 -38
- pixie_lab-0.1.0/src/pixie/catalog/__init__.py +0 -25
- pixie_lab-0.1.0/src/pixie/exports/_store.py +0 -156
- pixie_lab-0.1.0/src/pixie/web/__init__.py +0 -6
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.dockerignore +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.github/workflows/docs.yml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/.gitignore +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/LICENSE +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/Makefile +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/PLAN.md +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/netboot-pc.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-flash-always.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-flash.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-inventory.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe-tui.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/test-pxe.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/configs/usbboot-pc.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/live_build.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pixie_ipxe_build.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pixie_wheel_stage.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_flash_stage.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_inventory_stage.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/pxe_prepare.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/scripts/usb_iso_build.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/netboot-pc.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-flash-always.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-flash.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-inventory.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-nbdboot.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe-tui.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/test-pxe.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/cijoe/tasks/usbboot-pc.yaml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/audit.md +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/conf.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/src/index.md +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/README.md +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/pyproject.toml +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/src/pixie_docs/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/docs/tooling/src/pixie_docs/cli.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/README.md +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/auxiliary/cloudinit-metadata.meta +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/auxiliary/ipxe-embed.ipxe +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/auxiliary/ipxe-local-general.h +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/auto/config +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/archives/backports.list.binary +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/archives/backports.list.chroot +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/bootloaders/grub-efi/splash.png +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/bootloaders/syslinux/splash.png +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/hooks/normal/0500-pixie-install.hook.chroot +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/issue +0 -0
- {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
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/etc/motd +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/includes.chroot/usr/local/sbin/pixie-trace +0 -0
- {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
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/package-lists/pixie-base.list.chroot +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/package-lists/pixie-flash.list.chroot +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/preferences/backports.pref.binary +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/pixie-media/live-build/config/preferences/backports.pref.chroot +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/catalog/_schema.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/deploy/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/deploy/_templates.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/disks.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/events/_routes.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/machines/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/oras.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pivot/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/pxe/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tftp/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/tftp/_supervisor.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_auth.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/.gitkeep +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/bootstrap-icons.min.css +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/bootstrap.min.css +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/fonts/bootstrap-icons.woff +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/fonts/bootstrap-icons.woff2 +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/htmx.min.js +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/pixie-favicon.png +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_static/sse.js +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_table_state.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/_partials/page_description.html +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/_partials/recent_events.html +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/_partials/table_helpers.html +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/catalog_detail.html +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/bootstrap.j2 +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/exit.j2 +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/pixie-live-env.j2 +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/ipxe/unavailable.j2 +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/login.html +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/src/pixie/web/_templates/machines.html +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/__init__.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_catalog_fetch.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_deploy.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_exports.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_inventory.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_pxe.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/integration/test_tftp.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_auth.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_catalog_api.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_event_kinds.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_healthz.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_pivot.py +0 -0
- {pixie_lab-0.1.0 → pixie_lab-0.3.0}/tests/test_pxe_plan_json.py +0 -0
- {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.
|
|
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.
|
|
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,
|
|
85
|
-
holds fetched disk images.
|
|
86
|
-
manifest.json}` holds unpacked
|
|
87
|
-
|
|
88
|
-
|
|
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,
|
|
57
|
-
holds fetched disk images.
|
|
58
|
-
manifest.json}` holds unpacked
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 |
|