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