pixie-lab 0.4.3__py3-none-any.whl → 0.4.4__py3-none-any.whl

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/events/_kinds.py CHANGED
@@ -196,7 +196,14 @@ EVENTS_CLEARED = "events.cleared"
196
196
  right after the wipe so the freshly-empty log still records who reset
197
197
  it and when; carries the deleted-row count in ``details``."""
198
198
 
199
- # ---------- live-env fetch ------------------------------------------
199
+ # ---------- live env ------------------------------------------------
200
+
201
+ LIVE_ENV_IMAGE_SELECTED = "live_env.image.selected"
202
+ """The one-click "Set up live env" action fetched the pixie-live-env
203
+ image + its bundle and selected the image (set ``live_env.image_sha``).
204
+ Carries the selected ``image_sha`` in ``details``."""
205
+
206
+
200
207
  # The canonical closed set. Every kind above is registered here; the
201
208
  # ``EventsLog.emit`` call rejects anything not in this frozenset.
202
209
  KNOWN_EVENT_KINDS: frozenset[str] = frozenset(
@@ -230,6 +237,7 @@ KNOWN_EVENT_KINDS: frozenset[str] = frozenset(
230
237
  AUTH_LOGIN_SUCCEEDED,
231
238
  AUTH_LOGIN_FAILED,
232
239
  EVENTS_CLEARED,
240
+ LIVE_ENV_IMAGE_SELECTED,
233
241
  }
234
242
  )
235
243
 
@@ -6,7 +6,8 @@
6
6
  <nav class="navbar subnav-strip" aria-label="Section sub-navigation">
7
7
  <div class="container">
8
8
  <span class="subnav-jumps d-flex align-items-center flex-wrap">
9
- <a href="#image">Image</a>
9
+ <a href="#setup">Setup</a>
10
+ <a href="#advanced">Advanced</a>
10
11
  <a href="#options">Options</a>
11
12
  </span>
12
13
  </div>
@@ -14,60 +15,104 @@
14
15
  {% endblock %}
15
16
 
16
17
  {% block content %}
17
- {{ page_description('boombox', 'The pixie live env that the pixie-flash-once / pixie-flash-always / pixie-inventory / pixie-tui boot modes boot into. It is a fetched disk image (the pixie-live-env catalog entry) booted over ephemeral nbdboot: select it here by content sha. Until an image is selected and fetched, those boot modes fall back to an "unavailable" iPXE plan.') }}
18
+ {% set ss = live_env_setup_state or {} %}
19
+ {{ page_description('boombox', 'The pixie live env that the pixie-inventory / pixie-tui / pixie-flash-once / pixie-flash-always boot modes boot into: a disk image (the pixie-live-env catalog entry) booted over ephemeral nbdboot. Click Set up live env to fetch it and select it in one step; until then those boot modes render an "unavailable" plan.') }}
18
20
 
19
21
  {% if flash_error %}
20
22
  <div class="alert alert-danger py-2">{{ flash_error }}</div>
21
23
  {% endif %}
22
24
 
23
- {# ---- Live-env image (the nbdboot selector) ---------------------- #}
24
- <div class="card shadow-sm mb-4" id="image">
25
+ {# ---- One-click setup -------------------------------------------- #}
26
+ <div class="card shadow-sm mb-4" id="setup">
25
27
  <div class="card-header bg-light d-flex align-items-center gap-2">
26
- <h2 class="h5 mb-0"><i class="bi bi-boombox me-2"></i>Live-env image</h2>
27
- {% if live_env_image_sha.effective %}
28
- <span class="badge text-bg-success ms-auto"><i class="bi bi-check-circle me-1"></i>selected</span>
28
+ <h2 class="h5 mb-0"><i class="bi bi-boombox me-2"></i>Live env</h2>
29
+ {% if live_env_ready %}
30
+ <span class="badge text-bg-success ms-auto"><i class="bi bi-check-circle me-1"></i>ready</span>
31
+ {% elif ss.state == 'running' %}
32
+ <span class="badge text-bg-info ms-auto"><span class="spinner-border spinner-border-sm me-1" role="status"></span>setting up</span>
29
33
  {% else %}
30
- <span class="badge text-bg-warning ms-auto"><i class="bi bi-exclamation-triangle me-1"></i>none selected</span>
34
+ <span class="badge text-bg-warning ms-auto"><i class="bi bi-exclamation-triangle me-1"></i>not set up</span>
31
35
  {% endif %}
32
36
  </div>
37
+ <div class="card-body">
38
+ {% if live_env_ready %}
39
+ <p class="small mb-3">
40
+ Image <code>{{ live_env_image_sha.effective[:12] }}</code> is fetched and
41
+ selected; the <code>pixie-inventory</code> / <code>pixie-tui</code> /
42
+ <code>pixie-flash-*</code> modes nbdboot it. Nothing else to do.
43
+ </p>
44
+ {% else %}
45
+ <p class="small mb-3">
46
+ One click fetches the <code>pixie-live-env</code> image (~900&nbsp;MiB) +
47
+ its arch-headless netboot bundle from the catalog, then selects the image.
48
+ No catalog hopping or sha copying. It runs on the server; you can leave
49
+ this page. Re-running when everything is already fetched just re-selects.
50
+ </p>
51
+ {% endif %}
52
+
53
+ {# Progress (polled live while a setup runs). #}
54
+ <div id="setup-progress" class="{% if ss.state != 'running' %}d-none{% endif %} mb-3">
55
+ <div class="d-flex align-items-center gap-2 small mb-1">
56
+ <span class="spinner-border spinner-border-sm" role="status"></span>
57
+ <span id="setup-step">Fetching {{ ss.step or 'live env' }}{% if ss.phase %} ({{ ss.phase }}){% endif %}...</span>
58
+ </div>
59
+ <div class="progress" role="progressbar" style="height: 1.1rem;">
60
+ <div id="setup-bar" class="progress-bar progress-bar-striped progress-bar-animated"
61
+ style="width: {% if ss.total_bytes %}{{ (100 * ss.bytes_downloaded / ss.total_bytes) | round(0, 'floor') }}{% else %}100{% endif %}%"></div>
62
+ </div>
63
+ </div>
64
+ <div id="setup-error" class="alert alert-danger py-2 small {% if ss.state != 'error' %}d-none{% endif %}">
65
+ <i class="bi bi-exclamation-triangle me-1"></i><span id="setup-error-msg">{{ ss.error }}</span>
66
+ </div>
67
+
68
+ <form method="post" action="/ui/live-env/setup" class="m-0">
69
+ <button type="submit" class="btn {% if live_env_ready %}btn-outline-secondary{% else %}btn-primary{% endif %}"
70
+ id="setup-btn" {% if ss.state == 'running' %}disabled{% endif %}>
71
+ {% if ss.state == 'running' %}
72
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>Setting up...
73
+ {% else %}
74
+ <i class="bi bi-boombox me-1"></i>{% if live_env_ready %}Re-run setup{% else %}Set up live env{% endif %}
75
+ {% endif %}
76
+ </button>
77
+ <span class="text-muted small ms-2">Fetch + select in one step.</span>
78
+ </form>
79
+ </div>
80
+ </div>
81
+
82
+ {# ---- Advanced: select by content sha ---------------------------- #}
83
+ <div class="card shadow-sm mb-4" id="advanced">
84
+ <div class="card-header bg-light fw-semibold">
85
+ <i class="bi bi-sliders me-1"></i>Advanced: select by content sha
86
+ </div>
33
87
  <div class="card-body">
34
88
  <p class="small text-muted mb-3">
35
- Fetch the <code>pixie-live-env</code> image on the
36
- <a href="/ui/catalog">Catalog</a> page, then paste its
37
- <code>content_sha256</code> here. The
38
- <code>pixie-inventory</code> / <code>pixie-tui</code> /
39
- <code>pixie-flash-*</code> modes boot it over
40
- <strong>ephemeral nbdboot</strong> (a RO NBD lower + tmpfs
41
- overlay); dracut brings the NIC up where the retired
42
- live-boot squashfs hung in the initramfs on some boards. The
43
- on-target pixie CLI phones home so inventory / flash work as
44
- before.
89
+ Manual override of what Set up above does automatically: point the live env
90
+ at a specific fetched image by its <code>content_sha256</code> (from its
91
+ <a href="/ui/catalog">Catalog</a> row). The image plus its netboot bundle
92
+ (via the entry's <code>netboot_src</code>) must both be fetched.
45
93
  </p>
46
94
  <form method="post" action="/ui/live-env/image/edit" class="row g-3">
47
95
  <div class="col-12">
48
96
  <label for="live_env_image_sha" class="form-label">
49
97
  Image content sha
50
98
  <span class="badge text-bg-{% if live_env_image_sha.override %}primary{% else %}secondary{% endif %} ms-1">
51
- {% if live_env_image_sha.override %}override{% elif live_env_image_sha.effective %}env{% else %}default{% endif %}
99
+ {% if live_env_image_sha.override %}override{% elif live_env_image_sha.effective %}env{% else %}unset{% endif %}
52
100
  </span>
53
101
  </label>
54
102
  <input type="text" id="live_env_image_sha" name="live_env_image_sha"
55
103
  class="form-control font-monospace"
56
104
  value="{{ live_env_image_sha.override }}"
57
- placeholder="(unset - live-env modes degrade to the unavailable plan)"
105
+ placeholder="(unset - live-env modes render the unavailable plan)"
58
106
  pattern="[0-9a-fA-F]{64}">
59
107
  <div class="form-text">
60
- A fetched disk-image's <code>content_sha256</code> (64 hex), plus its
61
- netboot bundle (via the entry's <code>netboot_src</code>), must both be
62
- fetched. Blank falls back to <code>{{ live_env_image_sha.env }}</code>
63
- then unset, in which case the live-env boot modes render the
64
- <code>unavailable</code> plan. Effective:
108
+ Blank falls back to <code>{{ live_env_image_sha.env }}</code> then unset.
109
+ Effective:
65
110
  {% if live_env_image_sha.effective %}<code>{{ live_env_image_sha.effective }}</code>{% else %}<em class="text-muted">(none)</em>{% endif %}.
66
111
  {% if live_env_image_sha.updated_at %}<br>Last updated: <code>{{ live_env_image_sha.updated_at | fmt_ts }}</code>.{% endif %}
67
112
  </div>
68
113
  </div>
69
114
  <div class="col-12">
70
- <button type="submit" class="btn btn-primary"><i class="bi bi-check2 me-1"></i>Save</button>
115
+ <button type="submit" class="btn btn-outline-secondary"><i class="bi bi-check2 me-1"></i>Save</button>
71
116
  <span class="text-muted small ms-2">
72
117
  Takes effect on the NEXT <code>GET /pxe/&lt;mac&gt;</code>; a target already mid-boot must be power-cycled.
73
118
  </span>
@@ -79,7 +124,7 @@
79
124
  {# ---- Options (config) ------------------------------------------- #}
80
125
  <div class="card shadow-sm mb-4" id="options">
81
126
  <div class="card-header bg-light fw-semibold">
82
- <i class="bi bi-sliders me-1"></i>Options
127
+ <i class="bi bi-gear me-1"></i>Options
83
128
  </div>
84
129
  <div class="card-body">
85
130
  <form method="post" action="/ui/live-env/cmdline/edit" class="row g-3">
@@ -99,7 +144,7 @@
99
144
  <option value="pci=realloc=on,nocrs">
100
145
  </datalist>
101
146
  <div class="form-text">
102
- Tokens appended verbatim to the <code>pixie-live-env</code> kernel cmdline
147
+ Tokens appended verbatim to the live-env kernel cmdline
103
148
  (after <code>pixie.mac=</code> / <code>bty.mac=</code>, so last-token-wins on any
104
149
  conflict). Whitespace-separated; single line. Blank falls back to
105
150
  <code>{{ live_env_extra_cmdline.env }}</code> then no append. A machine with its own
@@ -119,4 +164,34 @@
119
164
  </form>
120
165
  </div>
121
166
  </div>
167
+
168
+ {# Poll setup progress while a run is in flight; reload when it lands
169
+ so the ready badge + selected sha update. #}
170
+ <script>
171
+ (function () {
172
+ if ("{{ ss.state }}" !== "running") return;
173
+ var box = document.getElementById("setup-progress");
174
+ var step = document.getElementById("setup-step");
175
+ var bar = document.getElementById("setup-bar");
176
+ function poll() {
177
+ fetch("/ui/live-env/setup-state.json", { headers: { "Accept": "application/json" } })
178
+ .then(function (r) { return r.json(); })
179
+ .then(function (s) {
180
+ if (!s || s.state === "running") {
181
+ if (step) step.textContent = "Fetching " + (s.step || "live env") +
182
+ (s.phase ? " (" + s.phase + ")" : "") + "...";
183
+ if (bar && s.total_bytes) {
184
+ bar.style.width = Math.floor(100 * s.bytes_downloaded / s.total_bytes) + "%";
185
+ }
186
+ setTimeout(poll, 1500);
187
+ } else {
188
+ // done or error -> reload to render the final state.
189
+ location.reload();
190
+ }
191
+ })
192
+ .catch(function () { setTimeout(poll, 3000); });
193
+ }
194
+ setTimeout(poll, 1500);
195
+ })();
196
+ </script>
122
197
  {% endblock %}
pixie/web/main.py CHANGED
@@ -20,7 +20,7 @@ from __future__ import annotations
20
20
  import os
21
21
  import secrets
22
22
  import tempfile
23
- from collections.abc import AsyncIterator
23
+ from collections.abc import AsyncIterator, Callable
24
24
  from concurrent.futures import ThreadPoolExecutor
25
25
  from contextlib import asynccontextmanager
26
26
  from contextlib import suppress as contextlib_suppress
@@ -55,6 +55,7 @@ from pixie.events._kinds import (
55
55
  CATALOG_IMPORT_OK,
56
56
  EVENTS_CLEARED,
57
57
  EXPORT_NBDKIT_SPAWNED,
58
+ LIVE_ENV_IMAGE_SELECTED,
58
59
  TFTP_STARTED,
59
60
  TFTP_STOPPED,
60
61
  )
@@ -100,6 +101,13 @@ STATE_DIR_ENV = "PIXIE_DATA_DIR"
100
101
  FETCH_POOL_SIZE_ENV = "PIXIE_FETCH_POOL_SIZE"
101
102
  DEFAULT_FETCH_POOL_SIZE = 4
102
103
 
104
+ # Catalog entry name of the pixie-live-env disk image the one-click
105
+ # "Set up live env" action fetches + selects. Matches the bundled seed
106
+ # catalog (and the release catalog fragment); its netboot bundle is
107
+ # resolved from the entry's ``netboot_src``, so only the image name is
108
+ # pinned here.
109
+ LIVE_ENV_IMAGE_ENTRY = "pixie-live-env"
110
+
103
111
  # NBD supervisor knobs. In production ``--network=host`` covers the
104
112
  # bind + port range; in dev / tests operators tweak.
105
113
  NBD_PORT_BASE_ENV = "PIXIE_NBD_PORT_BASE"
@@ -752,6 +760,11 @@ def create_app() -> FastAPI:
752
760
  thread_name_prefix="pixie-fetch",
753
761
  )
754
762
  app.state.fetch_states = {}
763
+ # Progress for the one-click "Set up live env" action (fetch the
764
+ # pixie-live-env image + its bundle, then select the image). One per
765
+ # pixie, mirrors a fetch_states entry: state running/done/error +
766
+ # step + the underlying fetch's phase/byte counters.
767
+ app.state.live_env_setup_state = {}
755
768
 
756
769
  # SessionMiddleware signs the ``pixie-token`` cookie. Sliding TTL:
757
770
  # 7 days from last touch. ``https_only=False`` because pixie is
@@ -2440,14 +2453,25 @@ def create_app() -> FastAPI:
2440
2453
 
2441
2454
  def _live_env_context(request: Request, flash_error: str | None = None) -> dict[str, Any]:
2442
2455
  """Render context for the dedicated /ui/live-env pane: the
2443
- selected live-env disk image (nbdboot), plus the extra-cmdline
2444
- override, each with provenance."""
2456
+ selected live-env disk image (nbdboot), the one-click setup
2457
+ progress, plus the extra-cmdline override."""
2445
2458
  state = request.app.state
2446
2459
  store: SettingsStore = state.settings_store
2460
+ # Ready == an image sha is selected AND its catalog image is
2461
+ # fetched on disk (the at-a-glance signal; the renderer does the
2462
+ # full bundle+blob check at boot).
2463
+ selected_sha = store.resolve_live_env_image_sha()
2464
+ live_env_ready = bool(selected_sha) and any(
2465
+ e.content_sha256 == selected_sha
2466
+ for e in state.catalog_store.list_entries()
2467
+ if getattr(e, "bindable", False) and e.content_sha256
2468
+ )
2447
2469
  return {
2448
2470
  "version": pixie.__version__,
2449
2471
  "authed": True,
2450
2472
  "page": "live-env",
2473
+ "live_env_ready": live_env_ready,
2474
+ "live_env_setup_state": dict(state.live_env_setup_state),
2451
2475
  "live_env_extra_cmdline": {
2452
2476
  "override": store.get(KEY_LIVE_ENV_EXTRA_CMDLINE) or "",
2453
2477
  "effective": store.resolve_live_env_extra_cmdline(),
@@ -2542,6 +2566,114 @@ def create_app() -> FastAPI:
2542
2566
  store.clear(KEY_LIVE_ENV_IMAGE_SHA)
2543
2567
  return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
2544
2568
 
2569
+ @app.post("/ui/live-env/setup")
2570
+ def ui_live_env_setup(
2571
+ request: Request,
2572
+ _auth: None = Depends(_require_ui_auth),
2573
+ ) -> RedirectResponse:
2574
+ """One-click live-env setup: fetch the ``pixie-live-env`` image +
2575
+ its netboot bundle (from the seeded catalog), then select the
2576
+ image (set ``live_env.image_sha``) -- the whole
2577
+ catalog-fetch-then-copy-sha dance in a single click. Runs on the
2578
+ fetch pool; progress lands on ``app.state.live_env_setup_state``
2579
+ for the Live env page to poll. Idempotent: re-running when
2580
+ everything is already fetched just re-selects the image."""
2581
+ state = request.app.state
2582
+ store = state.catalog_store
2583
+ settings: SettingsStore = state.settings_store
2584
+ setup_state = state.live_env_setup_state
2585
+ events = getattr(state, "events_log", None)
2586
+
2587
+ if setup_state.get("state") == "running":
2588
+ return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
2589
+
2590
+ image_entry = store.get_entry(LIVE_ENV_IMAGE_ENTRY)
2591
+ if image_entry is None:
2592
+ setup_state.clear()
2593
+ setup_state.update(
2594
+ {
2595
+ "state": "error",
2596
+ "error": (
2597
+ f"no {LIVE_ENV_IMAGE_ENTRY!r} entry in the catalog; import the "
2598
+ "release catalog first (it seeds on a fresh deploy)."
2599
+ ),
2600
+ "at_iso": _now_iso(),
2601
+ }
2602
+ )
2603
+ return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
2604
+ bundle_entry = (
2605
+ store.get_entry_by_src(image_entry.netboot_src) if image_entry.netboot_src else None
2606
+ )
2607
+ if bundle_entry is None:
2608
+ setup_state.clear()
2609
+ setup_state.update(
2610
+ {
2611
+ "state": "error",
2612
+ "error": (
2613
+ f"{LIVE_ENV_IMAGE_ENTRY!r} has no netboot bundle in the catalog "
2614
+ "(its netboot_src does not resolve)."
2615
+ ),
2616
+ "at_iso": _now_iso(),
2617
+ }
2618
+ )
2619
+ return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
2620
+
2621
+ setup_state.clear()
2622
+ setup_state.update({"state": "running", "step": "bundle", "at_iso": _now_iso()})
2623
+
2624
+ def _step_report(step: str) -> Callable[[dict[str, Any]], None]:
2625
+ def cb(payload: dict[str, Any]) -> None:
2626
+ setup_state.update(payload)
2627
+ setup_state["step"] = step
2628
+ setup_state["state"] = "running"
2629
+
2630
+ return cb
2631
+
2632
+ def _run() -> None:
2633
+ try:
2634
+ # Bundle first (small; the render needs its unpacked
2635
+ # kernel/initrd), then the image (the ~900 MiB leg).
2636
+ setup_state["step"] = "bundle"
2637
+ _fetch(bundle_entry, store, progress=_step_report("bundle"))
2638
+ setup_state["step"] = "image"
2639
+ img = _fetch(image_entry, store, progress=_step_report("image"))
2640
+ settings.set_value(KEY_LIVE_ENV_IMAGE_SHA, img.content_sha256)
2641
+ setup_state.clear()
2642
+ setup_state.update(
2643
+ {
2644
+ "state": "done",
2645
+ "image_sha": img.content_sha256,
2646
+ "at_iso": _now_iso(),
2647
+ }
2648
+ )
2649
+ if events is not None:
2650
+ events.emit(
2651
+ LIVE_ENV_IMAGE_SELECTED,
2652
+ summary=(
2653
+ f"live env set up + selected (image sha {img.content_sha256[:12]})"
2654
+ ),
2655
+ details={"image_sha": img.content_sha256},
2656
+ )
2657
+ except FetchError as exc:
2658
+ setup_state.clear()
2659
+ setup_state.update({"state": "error", "error": str(exc), "at_iso": _now_iso()})
2660
+ except Exception as exc: # pragma: no cover -- defensive
2661
+ setup_state.clear()
2662
+ setup_state.update(
2663
+ {"state": "error", "error": f"internal: {exc}", "at_iso": _now_iso()}
2664
+ )
2665
+
2666
+ state.fetch_pool.submit(_run)
2667
+ return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
2668
+
2669
+ @app.get("/ui/live-env/setup-state.json")
2670
+ def ui_live_env_setup_state(
2671
+ request: Request,
2672
+ _auth: None = Depends(_require_ui_auth),
2673
+ ) -> JSONResponse:
2674
+ """Poll target for the Live env page's setup progress bar."""
2675
+ return JSONResponse(dict(request.app.state.live_env_setup_state))
2676
+
2545
2677
  # ---------- feature routers --------------------------------------
2546
2678
  #
2547
2679
  # Catalog + blob + artifacts routes live at the same URL shape they
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixie-lab
3
- Version: 0.4.3
3
+ Version: 0.4.4
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
@@ -67,13 +67,18 @@ top nav has Machines, Catalog, Events, and Settings.
67
67
  2. Click Fetch on a row to download it. The status pill ticks
68
68
  through downloading -> decompressing -> unpacking with a
69
69
  `bytes / total` counter so you see progress live.
70
- 3. Power-cycle a target with pixie in its DHCP next-server chain.
70
+ 3. For the live-env boot modes (`pixie-inventory` / `pixie-tui` /
71
+ `pixie-flash-*`), Fetch the `pixie-live-env` image + its netboot
72
+ bundle, then set the image's `content_sha256` on the Live env page
73
+ (`/ui/live-env`) or via `PIXIE_LIVE_ENV_IMAGE_SHA`. Those modes
74
+ nbdboot that image; until it's selected they render `unavailable`.
75
+ 4. Power-cycle a target with pixie in its DHCP next-server chain.
71
76
  A first contact shows up on `/ui/machines` with the default
72
77
  `ipxe-exit` boot mode. Open the row to bind a mode + image.
73
- 4. `pixie-inventory` prompts the live env to POST an inventory
78
+ 5. `pixie-inventory` prompts the live env to POST an inventory
74
79
  blob; the target's disks + NICs + memory show up on the
75
80
  machine detail page.
76
- 5. Flash modes (`pixie-flash-once`, `pixie-flash-always`) require
81
+ 6. Flash modes (`pixie-flash-once`, `pixie-flash-always`) require
77
82
  a target disk serial that came in on the inventory. The
78
83
  binding form disables Save until that prerequisite is met.
79
84
 
@@ -85,9 +90,11 @@ event log, settings, NBD-export records, and persistent-overlay
85
90
  records. `blobs/<sha>/blob` holds fetched disk images.
86
91
  `artifacts/<sha>/{vmlinuz,initrd,manifest.json}` holds unpacked
87
92
  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.
93
+ per-machine writable overlays for the `nbdboot` boot mode. The live env
94
+ (`pixie-inventory` / `pixie-tui` / `pixie-flash-*`) is the fetched
95
+ `pixie-live-env` disk image, selected by `PIXIE_LIVE_ENV_IMAGE_SHA` /
96
+ the Live env page and nbdbooted like any other image, so it lives in
97
+ `blobs/` + `artifacts/` too - there is no separate live-env directory.
91
98
 
92
99
  Both admin password and display timezone / strftime pattern have
93
100
  env-var overrides plus DB overrides via `/ui/settings`; env wins
@@ -17,7 +17,7 @@ pixie/deploy/__init__.py,sha256=3PvSvrIRdqxLCz0nTdVSBiPzEtDl359XSUBgyMBkMT8,1019
17
17
  pixie/deploy/_main.py,sha256=Or2espz9oWNmnGfyEzIsEyvRwWEHL4uxyE6U9OdMZJs,15403
18
18
  pixie/deploy/_templates.py,sha256=WkkKRDU00FDWVZM0XeKR-BCtSBPvaZ2QHM1TBeq0RYg,5418
19
19
  pixie/events/__init__.py,sha256=3Quxl56ePkKD9-PjbomCOOZOpmqJ3wHcYa7VcT9KXv8,923
20
- pixie/events/_kinds.py,sha256=LyyIrg4pGaat062sBIFrfGYGbZflsenfq7P-PkWhP04,10160
20
+ pixie/events/_kinds.py,sha256=BWHDCQC4oBS7qSgI_W6Wwz7839AZkFD1Cb1mGf0bDH8,10443
21
21
  pixie/events/_log.py,sha256=0XSdIYrPIEtmxjTq9L7gy1Q4qGotTxgHhMkM9fv8tyY,9041
22
22
  pixie/events/_routes.py,sha256=MbY69aZKDEzy4O8AizHtv_POPbdNMqz94imccFbtelM,1363
23
23
  pixie/exports/__init__.py,sha256=RiI69GG_bjkb_d1CNRyk9KI7wyMpLVvWc_NgNnWxOYc,546
@@ -46,7 +46,7 @@ pixie/web/_overlay_bind.py,sha256=7aKYqMaujxt-XRa1jB6mVEkbt-w_koXjLvUzBTzy2o8,41
46
46
  pixie/web/_overlays.py,sha256=5yZp6pDiFj_WKdLx6vCoEKD8mXG3KjkMOQt4OGYYdyc,8571
47
47
  pixie/web/_settings_store.py,sha256=X9dO5dr31PvRUM-Y7cLilEiNG_7MCV8CpDW2Nc6V0Tc,11242
48
48
  pixie/web/_table_state.py,sha256=zGH5oZ-XlMqUFQqmYglYqT_fu1Yrtaw-g5yhPtAKAEc,9524
49
- pixie/web/main.py,sha256=r4ZmVBBjvj5Cs8nWOOHZkBVv2BttNeNOSnKc_fNb-1Q,108149
49
+ pixie/web/main.py,sha256=PkBcCmSs04GiEsCudlwqG9SrVmR_yMvN4lBFCrb8E9U,114145
50
50
  pixie/web/_static/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  pixie/web/_static/bootstrap-icons.min.css,sha256=pdY4ejLKO67E0CM2tbPtq1DJ3VGDVVdqAR6j3ZwdiE4,87008
52
52
  pixie/web/_static/bootstrap.min.css,sha256=NXgoHLHuUWz0lbqh8PRwCIl7_QhZE74v-FCwrYdCW1E,234717
@@ -62,7 +62,7 @@ pixie/web/_templates/events.html,sha256=ifawe--m5FMcicKZ2daCCycV7rGgpLJz7A309UrT
62
62
  pixie/web/_templates/image_detail.html,sha256=--DjHCDgvripwr-9_lmnmaLCe4Iw2W_gXIGuKLMvcJw,6926
63
63
  pixie/web/_templates/images.html,sha256=PO8r0hZ6tXMqHZo_m1t_wgh4NEZP6u_72owH_Jbz2Zo,7266
64
64
  pixie/web/_templates/layout.html,sha256=9kQ1sWyAqMl9YgwJO1jIjwRbiG8bt2sUpJLNrbBZmzo,13988
65
- pixie/web/_templates/live_env.html,sha256=EtZB728ZG-v2kG-YAwox3R9moz0O24rBD_HGJXkpRQM,7132
65
+ pixie/web/_templates/live_env.html,sha256=1oNnjBq9r-MSr-tmnThi0xMI8ysQVOl8I1TpwFNNtJQ,10878
66
66
  pixie/web/_templates/login.html,sha256=d60N8kmr7YaAoTKRBaOiGN5Srdy8-BgB8_Zz9TQvGwc,1810
67
67
  pixie/web/_templates/machine_detail.html,sha256=ijI5FKuyqOdDwCdLGM_H7yHgqn6fFMm6pOPVBLT9G00,49797
68
68
  pixie/web/_templates/machines.html,sha256=modlu9MNqpxOkDd90o-yV1g5yuEiC61I6JtXQk7_PUo,9803
@@ -75,8 +75,8 @@ pixie/web/_templates/ipxe/bootstrap.j2,sha256=xt13H2dHf-SeLhWRQvptqGY51sJ_L24C2E
75
75
  pixie/web/_templates/ipxe/exit.j2,sha256=pbTXTwjgiEsPZlRuv7_J0Q3BepXEqXnb82mv1sxDW24,371
76
76
  pixie/web/_templates/ipxe/nbdboot.j2,sha256=s1CwIKou5W6LRoC2MdZYwA3RcRN8AU3B1gbJmGLS9MM,3950
77
77
  pixie/web/_templates/ipxe/unavailable.j2,sha256=zt4DYfYRXmOIjL13diYPxEApTP3CRnn5Beszeh3BPBw,440
78
- pixie_lab-0.4.3.dist-info/METADATA,sha256=5vQcLth91856S2hyQf638LF20qcMfqksG7kk_vHkRvU,4452
79
- pixie_lab-0.4.3.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
80
- pixie_lab-0.4.3.dist-info/entry_points.txt,sha256=G3vtKpg_gQtisiyS31EjdvC4QC52Kfw2K7iMsfsnj5A,71
81
- pixie_lab-0.4.3.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
82
- pixie_lab-0.4.3.dist-info/RECORD,,
78
+ pixie_lab-0.4.4.dist-info/METADATA,sha256=KQr4pQm8RhsU-8vEBSZ06JYfBT2S_8_0-3L9HxldDEE,4989
79
+ pixie_lab-0.4.4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
80
+ pixie_lab-0.4.4.dist-info/entry_points.txt,sha256=G3vtKpg_gQtisiyS31EjdvC4QC52Kfw2K7iMsfsnj5A,71
81
+ pixie_lab-0.4.4.dist-info/licenses/LICENSE,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
82
+ pixie_lab-0.4.4.dist-info/RECORD,,