pixie-lab 0.4.2__py3-none-any.whl → 0.4.3__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 +0 -20
- pixie/pxe/_renderer.py +31 -64
- pixie/web/_settings_store.py +8 -32
- pixie/web/_templates/dashboard.html +19 -41
- pixie/web/_templates/live_env.html +42 -125
- pixie/web/main.py +28 -218
- {pixie_lab-0.4.2.dist-info → pixie_lab-0.4.3.dist-info}/METADATA +1 -1
- {pixie_lab-0.4.2.dist-info → pixie_lab-0.4.3.dist-info}/RECORD +11 -13
- pixie/catalog/_live_env.py +0 -128
- pixie/web/_templates/ipxe/pixie-live-env.j2 +0 -55
- {pixie_lab-0.4.2.dist-info → pixie_lab-0.4.3.dist-info}/WHEEL +0 -0
- {pixie_lab-0.4.2.dist-info → pixie_lab-0.4.3.dist-info}/entry_points.txt +0 -0
- {pixie_lab-0.4.2.dist-info → pixie_lab-0.4.3.dist-info}/licenses/LICENSE +0 -0
pixie/events/_kinds.py
CHANGED
|
@@ -197,24 +197,6 @@ 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
199
|
# ---------- live-env fetch ------------------------------------------
|
|
200
|
-
#
|
|
201
|
-
# The operator "Fetch live-env" action pulls the netboot-pc bake into
|
|
202
|
-
# PIXIE_LIVE_ENV_DIR. No subject (``subject_kind`` = ""): the live env
|
|
203
|
-
# is per-pixie, not per-resource. Dotted ``.done`` / ``.failed`` names
|
|
204
|
-
# mirror the catalog-fetch kinds so ``.failed`` auto-classifies as an
|
|
205
|
-
# error via the ERROR_KINDS ``LIKE '%.failed'`` probe.
|
|
206
|
-
|
|
207
|
-
LIVE_ENV_FETCH_DONE = "live_env.fetch.done"
|
|
208
|
-
"""``POST /ui/live-env/fetch`` downloaded + staged vmlinuz + initrd +
|
|
209
|
-
live.squashfs into PIXIE_LIVE_ENV_DIR. Carries the src + content sha in
|
|
210
|
-
``details``."""
|
|
211
|
-
|
|
212
|
-
LIVE_ENV_FETCH_FAILED = "live_env.fetch.failed"
|
|
213
|
-
"""The live-env fetch failed (bad src scheme, download error, or a
|
|
214
|
-
tarball missing a required file). Carries the src + error in
|
|
215
|
-
``details``."""
|
|
216
|
-
|
|
217
|
-
|
|
218
200
|
# The canonical closed set. Every kind above is registered here; the
|
|
219
201
|
# ``EventsLog.emit`` call rejects anything not in this frozenset.
|
|
220
202
|
KNOWN_EVENT_KINDS: frozenset[str] = frozenset(
|
|
@@ -248,8 +230,6 @@ KNOWN_EVENT_KINDS: frozenset[str] = frozenset(
|
|
|
248
230
|
AUTH_LOGIN_SUCCEEDED,
|
|
249
231
|
AUTH_LOGIN_FAILED,
|
|
250
232
|
EVENTS_CLEARED,
|
|
251
|
-
LIVE_ENV_FETCH_DONE,
|
|
252
|
-
LIVE_ENV_FETCH_FAILED,
|
|
253
233
|
}
|
|
254
234
|
)
|
|
255
235
|
|
pixie/pxe/_renderer.py
CHANGED
|
@@ -91,12 +91,12 @@ class RenderContext:
|
|
|
91
91
|
# no-op is a legal value.
|
|
92
92
|
extra_cmdline: str = ""
|
|
93
93
|
# Disk-image content sha the live-env boot modes boot over
|
|
94
|
-
# ephemeral nbdboot
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
94
|
+
# ephemeral nbdboot. Operator-set via PIXIE_LIVE_ENV_IMAGE_SHA /
|
|
95
|
+
# the Live env page. Empty means no live-env image is configured,
|
|
96
|
+
# so LIVE_ENV_MODES machines degrade to the ``unavailable`` plan;
|
|
97
|
+
# when set, ``render`` streams this image over NBD for every
|
|
98
|
+
# LIVE_ENV_MODES machine (the machine's own bind still drives what
|
|
99
|
+
# GET /pxe/<mac>/plan returns, so the on-target CLI dispatches
|
|
100
100
|
# inventory / interactive / flash correctly).
|
|
101
101
|
live_env_image_sha: str = ""
|
|
102
102
|
|
|
@@ -113,7 +113,6 @@ class PlanRenderer:
|
|
|
113
113
|
overlays: OverlaysStore,
|
|
114
114
|
nbd: NbdServer,
|
|
115
115
|
overlays_dir: Path,
|
|
116
|
-
live_env_dir: Path | None = None,
|
|
117
116
|
events: Any = None,
|
|
118
117
|
) -> None:
|
|
119
118
|
self._catalog = catalog
|
|
@@ -128,29 +127,12 @@ class PlanRenderer:
|
|
|
128
127
|
# booted) land alongside the rest of pixie's audit trail. None
|
|
129
128
|
# for unit tests that don't wire an events sink.
|
|
130
129
|
self._events = events
|
|
131
|
-
# Where the netboot-pc bake's vmlinuz + initrd + squashfs are
|
|
132
|
-
# staged on disk. When set + the three files exist, the
|
|
133
|
-
# ``pixie-*`` boot modes chain into the live env; otherwise
|
|
134
|
-
# they fall back to the ``unavailable`` plan so a bound
|
|
135
|
-
# target does not silently boot nothing.
|
|
136
|
-
self._live_env_dir = live_env_dir
|
|
137
130
|
self._env = Environment(
|
|
138
131
|
loader=FileSystemLoader(str(_TEMPLATES_DIR)),
|
|
139
132
|
autoescape=select_autoescape(disabled_extensions=("j2",)),
|
|
140
133
|
keep_trailing_newline=True,
|
|
141
134
|
)
|
|
142
135
|
|
|
143
|
-
def _live_env_ready(self) -> bool:
|
|
144
|
-
"""True iff the netboot-pc artifacts are staged on disk under
|
|
145
|
-
``self._live_env_dir``. Called per-render so an operator
|
|
146
|
-
dropping the files in without a pixie restart takes effect
|
|
147
|
-
on the next PXE hit."""
|
|
148
|
-
if self._live_env_dir is None:
|
|
149
|
-
return False
|
|
150
|
-
return all(
|
|
151
|
-
(self._live_env_dir / name).is_file() for name in ("vmlinuz", "initrd", "live.squashfs")
|
|
152
|
-
)
|
|
153
|
-
|
|
154
136
|
def render(self, machine: Machine, ctx: RenderContext) -> str:
|
|
155
137
|
mode = machine.boot_mode
|
|
156
138
|
# Per-machine ``extra_cmdline`` fully overrides the deploy-wide
|
|
@@ -341,52 +323,37 @@ class PlanRenderer:
|
|
|
341
323
|
) -> str:
|
|
342
324
|
"""Render a plan for one of the ``LIVE_ENV_MODES``.
|
|
343
325
|
|
|
344
|
-
|
|
345
|
-
(PIXIE_LIVE_ENV_IMAGE_SHA / the Live
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
(``pixie-live-env.j2``), or ``unavailable`` when the
|
|
357
|
-
netboot-pc bake artifacts have not been staged.
|
|
358
|
-
|
|
359
|
-
Either way the machine's OWN ``boot_mode`` still drives what
|
|
326
|
+
The live env boots the disk image named by
|
|
327
|
+
``ctx.live_env_image_sha`` (PIXIE_LIVE_ENV_IMAGE_SHA / the Live
|
|
328
|
+
env page) over EPHEMERAL nbdboot, the same code path the
|
|
329
|
+
``nbdboot`` mode uses -- proven on hardware to bring the NIC up
|
|
330
|
+
via dracut where the retired Debian live-boot squashfs hung in
|
|
331
|
+
the initramfs on some boards. If no image is configured, or the
|
|
332
|
+
configured image / its netboot bundle isn't fetched, degrade to
|
|
333
|
+
``unavailable`` (matching how ``nbdboot`` degrades) rather than
|
|
334
|
+
booting nothing -- a live-env mode with no bootable image is an
|
|
335
|
+
operator setup gap worth surfacing.
|
|
336
|
+
|
|
337
|
+
The machine's OWN ``boot_mode`` still drives what
|
|
360
338
|
``GET /pxe/<mac>/plan`` returns, so the on-target pixie CLI
|
|
361
|
-
dispatches inventory / interactive / flash
|
|
339
|
+
dispatches inventory / interactive / flash accordingly."""
|
|
362
340
|
live_env_sha = ctx.live_env_image_sha
|
|
363
|
-
if live_env_sha:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
)
|
|
370
|
-
bundle_entry, blob = resolved
|
|
371
|
-
return self._render_ephemeral_nbdboot(
|
|
372
|
-
machine, ctx, live_env_sha, bundle_entry, blob, extra_cmdline
|
|
341
|
+
if not live_env_sha:
|
|
342
|
+
return self._unavailable(
|
|
343
|
+
machine,
|
|
344
|
+
f"boot_mode={mode!r} needs a live-env image; set "
|
|
345
|
+
"PIXIE_LIVE_ENV_IMAGE_SHA (or the Live env page) to a "
|
|
346
|
+
"fetched image's content sha",
|
|
373
347
|
)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
# deploy yet; degrade to the readable unavailable plan so a
|
|
377
|
-
# bound target lands on a legible screen instead of a
|
|
378
|
-
# bty-media initrd.
|
|
348
|
+
resolved = self._resolve_bundle_and_blob(live_env_sha)
|
|
349
|
+
if isinstance(resolved, str):
|
|
379
350
|
return self._unavailable(
|
|
380
351
|
machine,
|
|
381
|
-
f"
|
|
382
|
-
f"stage vmlinuz+initrd+squashfs under $PIXIE_LIVE_ENV_DIR",
|
|
352
|
+
f"live-env image {live_env_sha[:12]} not bootable: {resolved}",
|
|
383
353
|
)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
host=ctx.host,
|
|
388
|
-
port=ctx.port,
|
|
389
|
-
extra_cmdline=extra_cmdline,
|
|
354
|
+
bundle_entry, blob = resolved
|
|
355
|
+
return self._render_ephemeral_nbdboot(
|
|
356
|
+
machine, ctx, live_env_sha, bundle_entry, blob, extra_cmdline
|
|
390
357
|
)
|
|
391
358
|
|
|
392
359
|
def _unavailable(self, machine: Machine, reason: str) -> str:
|
pixie/web/_settings_store.py
CHANGED
|
@@ -74,29 +74,16 @@ ENV_LIVE_ENV_EXTRA_CMDLINE = "PIXIE_LIVE_ENV_EXTRA_CMDLINE"
|
|
|
74
74
|
# it never reaches a kernel cmdline.
|
|
75
75
|
_UNEXPANDED_ENV_PLACEHOLDER = re.compile(r"^\$\{[A-Za-z_][A-Za-z0-9_]*(:[-+?=]?[^}]*)?\}$")
|
|
76
76
|
|
|
77
|
-
# Where the operator "Fetch live-env" action pulls the netboot-pc bake
|
|
78
|
-
# from: a single tarball ``src`` (``https://`` or ``oras://``, the same
|
|
79
|
-
# schemes the catalog fetch speaks) that unpacks to vmlinuz + initrd +
|
|
80
|
-
# live.squashfs. Defaults to the pixie GitHub release's stable-named
|
|
81
|
-
# asset via ``/releases/latest/download/`` so a plain deploy can pull
|
|
82
|
-
# the live env with no config; override for an air-gapped mirror.
|
|
83
|
-
KEY_LIVE_ENV_SRC = "live_env.src"
|
|
84
|
-
ENV_LIVE_ENV_SRC = "PIXIE_LIVE_ENV_SRC"
|
|
85
|
-
DEFAULT_LIVE_ENV_SRC = (
|
|
86
|
-
"https://github.com/safl/pixie/releases/latest/download/pixie-live-env-x86_64.tar.gz"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
77
|
# Disk-image content sha the live-env boot modes
|
|
90
78
|
# (pixie-inventory / -tui / -flash-once / -flash-always) boot over
|
|
91
|
-
# EPHEMERAL nbdboot
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
# sending every live-env boot into the unavailable plan.
|
|
79
|
+
# EPHEMERAL nbdboot. When set (and the named image + its netboot bundle
|
|
80
|
+
# are fetched) the renderer streams this image over NBD -- proven on
|
|
81
|
+
# hardware to bring the NIC up via dracut where the retired squashfs
|
|
82
|
+
# live-boot hung in the initramfs on some boards. Empty (the default)
|
|
83
|
+
# means those modes degrade to the unavailable plan. Resolution: DB
|
|
84
|
+
# override -> env -> empty. A value that is not 64 lowercase hex chars
|
|
85
|
+
# is treated as unset so a fat-fingered setting degrades to unavailable
|
|
86
|
+
# cleanly.
|
|
100
87
|
KEY_LIVE_ENV_IMAGE_SHA = "live_env.image_sha"
|
|
101
88
|
ENV_LIVE_ENV_IMAGE_SHA = "PIXIE_LIVE_ENV_IMAGE_SHA"
|
|
102
89
|
|
|
@@ -216,17 +203,6 @@ class SettingsStore:
|
|
|
216
203
|
return env
|
|
217
204
|
return ""
|
|
218
205
|
|
|
219
|
-
def resolve_live_env_src(self) -> str:
|
|
220
|
-
"""Effective live-env fetch src: DB override -> env -> default
|
|
221
|
-
(the pixie GitHub release asset). Never empty; the caller feeds
|
|
222
|
-
it straight to the live-env fetch, which raises on a bad
|
|
223
|
-
scheme."""
|
|
224
|
-
return (
|
|
225
|
-
self.get(KEY_LIVE_ENV_SRC)
|
|
226
|
-
or (os.environ.get(ENV_LIVE_ENV_SRC) or "").strip()
|
|
227
|
-
or DEFAULT_LIVE_ENV_SRC
|
|
228
|
-
)
|
|
229
|
-
|
|
230
206
|
def resolve_live_env_image_sha(self) -> str:
|
|
231
207
|
"""Effective live-env disk-image content sha: DB override -> env
|
|
232
208
|
-> empty.
|
|
@@ -126,67 +126,45 @@
|
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
128
128
|
|
|
129
|
-
{# Live-env
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{% set lfs = stats.live_env_fetch_state or {} %}
|
|
129
|
+
{# Live-env readiness (OUTPUT ONLY). The dashboard reports whether the
|
|
130
|
+
pixie-* boot modes have a bootable image selected; the image + cmdline
|
|
131
|
+
config live on the dedicated /ui/live-env pane. #}
|
|
133
132
|
<div class="card shadow-sm mb-4">
|
|
134
133
|
<div class="card-header bg-light d-flex align-items-center gap-2">
|
|
135
|
-
<h2 class="h5 mb-0"><i class="bi bi-boombox me-2"></i>Live
|
|
134
|
+
<h2 class="h5 mb-0"><i class="bi bi-boombox me-2"></i>Live env</h2>
|
|
136
135
|
<div class="ms-auto d-flex align-items-center gap-2">
|
|
137
|
-
{% if
|
|
138
|
-
<span class="badge text-bg-info"><span class="spinner-border spinner-border-sm me-1" role="status"></span>fetching</span>
|
|
139
|
-
{% elif stats.live_env_ready %}
|
|
136
|
+
{% if stats.live_env_ready %}
|
|
140
137
|
<span class="badge text-bg-success"><i class="bi bi-check-circle me-1"></i>ready</span>
|
|
141
138
|
{% else %}
|
|
142
|
-
<span class="badge text-bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>not
|
|
139
|
+
<span class="badge text-bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>not ready</span>
|
|
143
140
|
{% endif %}
|
|
144
141
|
<a href="/ui/live-env" class="btn btn-sm btn-outline-primary text-nowrap"
|
|
145
|
-
title="
|
|
142
|
+
title="Select + configure the live env">
|
|
146
143
|
<i class="bi bi-boombox me-1"></i>Manage
|
|
147
144
|
</a>
|
|
148
145
|
</div>
|
|
149
146
|
</div>
|
|
150
147
|
<div class="card-body">
|
|
151
148
|
{% if stats.live_env_ready %}
|
|
152
|
-
<p class="small mb-
|
|
153
|
-
|
|
149
|
+
<p class="small mb-0">
|
|
150
|
+
Image <code>{{ stats.live_env_image_sha[:12] }}</code> selected and fetched;
|
|
154
151
|
<code>pixie-tui</code> / <code>pixie-inventory</code> /
|
|
155
|
-
<code>pixie-flash-*</code>
|
|
152
|
+
<code>pixie-flash-*</code> boot it over ephemeral nbdboot.
|
|
156
153
|
</p>
|
|
157
|
-
{%
|
|
158
|
-
<p class="small text-warning mb-
|
|
159
|
-
Live-env
|
|
160
|
-
Every <code>pixie-*</code> boot_mode falls back to the "unavailable"
|
|
161
|
-
plan until
|
|
154
|
+
{% elif stats.live_env_image_sha %}
|
|
155
|
+
<p class="small text-warning mb-0">
|
|
156
|
+
Live-env image <code>{{ stats.live_env_image_sha[:12] }}</code> is selected but
|
|
157
|
+
not fetched. Every <code>pixie-*</code> boot_mode falls back to the "unavailable"
|
|
158
|
+
iPXE plan until the image is fetched. <a href="/ui/catalog">Fetch it →</a>
|
|
162
159
|
</p>
|
|
163
|
-
{%
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<
|
|
167
|
-
<a href="/ui/live-env">Live env</a>.
|
|
160
|
+
{% else %}
|
|
161
|
+
<p class="small text-warning mb-0">
|
|
162
|
+
No live-env image selected. Every <code>pixie-*</code> boot_mode falls back to the
|
|
163
|
+
"unavailable" iPXE plan until one is set. <a href="/ui/live-env">Select it →</a>
|
|
168
164
|
</p>
|
|
169
165
|
{% endif %}
|
|
170
|
-
<dl class="row small mb-0">
|
|
171
|
-
{% for name, size in stats.live_env_files.items() %}
|
|
172
|
-
<dt class="col-sm-3 font-monospace">{{ name }}</dt>
|
|
173
|
-
<dd class="col-sm-9">
|
|
174
|
-
{% if size is not none %}
|
|
175
|
-
<span class="text-success"><i class="bi bi-check-circle me-1"></i>{{ (size / 1024 / 1024) | round(1) }} MiB</span>
|
|
176
|
-
{% else %}
|
|
177
|
-
<span class="text-warning"><i class="bi bi-x-circle me-1"></i>missing</span>
|
|
178
|
-
{% endif %}
|
|
179
|
-
</dd>
|
|
180
|
-
{% endfor %}
|
|
181
|
-
</dl>
|
|
182
166
|
</div>
|
|
183
167
|
</div>
|
|
184
|
-
{% if lfs.state == 'fetching' %}
|
|
185
|
-
{# The fetch runs on the server's thread pool; poll the page so the
|
|
186
|
-
spinner flips to "ready" (or an error) on its own. A big squashfs
|
|
187
|
-
download takes a while, so 5 s is frequent enough without hammering. #}
|
|
188
|
-
<script>setTimeout(function () { location.reload(); }, 5000);</script>
|
|
189
|
-
{% endif %}
|
|
190
168
|
|
|
191
169
|
<div class="card shadow-sm">
|
|
192
170
|
<div class="card-header bg-light d-flex align-items-center">
|
|
@@ -6,8 +6,7 @@
|
|
|
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="#
|
|
10
|
-
<a href="#fetch">Fetch</a>
|
|
9
|
+
<a href="#image">Image</a>
|
|
11
10
|
<a href="#options">Options</a>
|
|
12
11
|
</span>
|
|
13
12
|
</div>
|
|
@@ -15,104 +14,64 @@
|
|
|
15
14
|
{% endblock %}
|
|
16
15
|
|
|
17
16
|
{% block content %}
|
|
18
|
-
{
|
|
19
|
-
{{ page_description('boombox', 'The pixie live env (netboot-pc bake: vmlinuz + initrd + live.squashfs) that the pixie-flash-once / pixie-flash-always / pixie-inventory / pixie-tui boot modes chain into. Fetch it from the configured source, or point that source at a mirror. Until all three files are staged, those boot modes fall back to an "unavailable" iPXE plan.') }}
|
|
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.') }}
|
|
20
18
|
|
|
21
19
|
{% if flash_error %}
|
|
22
20
|
<div class="alert alert-danger py-2">{{ flash_error }}</div>
|
|
23
21
|
{% endif %}
|
|
24
22
|
|
|
25
|
-
{# ----
|
|
26
|
-
<div class="card shadow-sm mb-4" id="
|
|
23
|
+
{# ---- Live-env image (the nbdboot selector) ---------------------- #}
|
|
24
|
+
<div class="card shadow-sm mb-4" id="image">
|
|
27
25
|
<div class="card-header bg-light d-flex align-items-center gap-2">
|
|
28
|
-
<h2 class="h5 mb-0"><i class="bi bi-boombox me-2"></i>
|
|
29
|
-
{% if
|
|
30
|
-
<span class="badge text-bg-success ms-auto"><i class="bi bi-check-circle me-1"></i>
|
|
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>
|
|
31
29
|
{% else %}
|
|
32
|
-
<span class="badge text-bg-warning ms-auto"><i class="bi bi-exclamation-triangle me-1"></i>
|
|
30
|
+
<span class="badge text-bg-warning ms-auto"><i class="bi bi-exclamation-triangle me-1"></i>none selected</span>
|
|
33
31
|
{% endif %}
|
|
34
32
|
</div>
|
|
35
33
|
<div class="card-body">
|
|
36
|
-
<p class="small text-muted mb-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
<code>
|
|
34
|
+
<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.
|
|
40
45
|
</p>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{% else %}
|
|
48
|
-
<span class="text-warning"><i class="bi bi-x-circle me-1"></i>missing</span>
|
|
49
|
-
{% endif %}
|
|
50
|
-
</dd>
|
|
51
|
-
{% endfor %}
|
|
52
|
-
</dl>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
{# ---- Fetch (action) --------------------------------------------- #}
|
|
57
|
-
<div class="card shadow-sm mb-4" id="fetch">
|
|
58
|
-
<div class="card-header bg-light fw-semibold">
|
|
59
|
-
<i class="bi bi-cloud-download me-1"></i>Fetch live env
|
|
60
|
-
</div>
|
|
61
|
-
<div class="card-body">
|
|
62
|
-
{% if lfs.state == 'fetching' %}
|
|
63
|
-
<div class="alert alert-info py-2 small">
|
|
64
|
-
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
|
|
65
|
-
Fetching live-env{% if lfs.phase %} ({{ lfs.phase }}){% endif %}... this can take a while for the ~1 GiB squashfs.
|
|
66
|
-
</div>
|
|
67
|
-
{% elif lfs.state == 'error' %}
|
|
68
|
-
<div class="alert alert-danger py-2 small">
|
|
69
|
-
<i class="bi bi-exclamation-triangle me-1"></i>Last fetch failed: {{ lfs.error }}
|
|
70
|
-
</div>
|
|
71
|
-
{% elif lfs.state == 'done' %}
|
|
72
|
-
<div class="alert alert-success py-2 small">
|
|
73
|
-
<i class="bi bi-check-circle me-1"></i>Last fetch staged the live env{% if lfs.sha256 %} (sha {{ lfs.sha256[:12] }}){% endif %}.
|
|
74
|
-
</div>
|
|
75
|
-
{% endif %}
|
|
76
|
-
|
|
77
|
-
{# Editable source, then the fetch button that consumes it. #}
|
|
78
|
-
<form method="post" action="/ui/live-env/src/edit" class="row g-2 align-items-end mb-3">
|
|
79
|
-
<div class="col-12 col-lg-9">
|
|
80
|
-
<label for="live_env_src" class="form-label mb-1">
|
|
81
|
-
Source
|
|
82
|
-
<span class="badge text-bg-{% if live_env_src.override %}primary{% else %}secondary{% endif %} ms-1">
|
|
83
|
-
{% if live_env_src.override %}override{% elif live_env_src.effective != live_env_src.default %}env{% else %}default{% endif %}
|
|
46
|
+
<form method="post" action="/ui/live-env/image/edit" class="row g-3">
|
|
47
|
+
<div class="col-12">
|
|
48
|
+
<label for="live_env_image_sha" class="form-label">
|
|
49
|
+
Image content sha
|
|
50
|
+
<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 %}
|
|
84
52
|
</span>
|
|
85
53
|
</label>
|
|
86
|
-
<input type="text" id="
|
|
54
|
+
<input type="text" id="live_env_image_sha" name="live_env_image_sha"
|
|
87
55
|
class="form-control font-monospace"
|
|
88
|
-
value="{{
|
|
89
|
-
placeholder="
|
|
90
|
-
|
|
91
|
-
<div class="col-12 col-lg-3">
|
|
92
|
-
<button type="submit" class="btn btn-outline-secondary w-100">
|
|
93
|
-
<i class="bi bi-check2 me-1"></i>Save source
|
|
94
|
-
</button>
|
|
95
|
-
</div>
|
|
96
|
-
<div class="col-12">
|
|
56
|
+
value="{{ live_env_image_sha.override }}"
|
|
57
|
+
placeholder="(unset - live-env modes degrade to the unavailable plan)"
|
|
58
|
+
pattern="[0-9a-fA-F]{64}">
|
|
97
59
|
<div class="form-text">
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
back to <code>{{
|
|
101
|
-
|
|
102
|
-
|
|
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:
|
|
65
|
+
{% if live_env_image_sha.effective %}<code>{{ live_env_image_sha.effective }}</code>{% else %}<em class="text-muted">(none)</em>{% endif %}.
|
|
66
|
+
{% if live_env_image_sha.updated_at %}<br>Last updated: <code>{{ live_env_image_sha.updated_at | fmt_ts }}</code>.{% endif %}
|
|
103
67
|
</div>
|
|
104
68
|
</div>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{% else %}
|
|
112
|
-
<i class="bi bi-cloud-download me-1"></i>{% if live_env_ready %}Re-fetch live env{% else %}Fetch live env{% endif %}
|
|
113
|
-
{% endif %}
|
|
114
|
-
</button>
|
|
115
|
-
<span class="text-muted small ms-2">Downloads + stages the three files; overwrites what's there.</span>
|
|
69
|
+
<div class="col-12">
|
|
70
|
+
<button type="submit" class="btn btn-primary"><i class="bi bi-check2 me-1"></i>Save</button>
|
|
71
|
+
<span class="text-muted small ms-2">
|
|
72
|
+
Takes effect on the NEXT <code>GET /pxe/<mac></code>; a target already mid-boot must be power-cycled.
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
116
75
|
</form>
|
|
117
76
|
</div>
|
|
118
77
|
</div>
|
|
@@ -158,48 +117,6 @@
|
|
|
158
117
|
</span>
|
|
159
118
|
</div>
|
|
160
119
|
</form>
|
|
161
|
-
|
|
162
|
-
<hr class="my-4">
|
|
163
|
-
|
|
164
|
-
<form method="post" action="/ui/live-env/image/edit" class="row g-3">
|
|
165
|
-
<div class="col-12">
|
|
166
|
-
<label for="live_env_image_sha" class="form-label">
|
|
167
|
-
Boot live env as disk image (nbdboot)
|
|
168
|
-
<span class="badge text-bg-{% if live_env_image_sha.override %}primary{% else %}secondary{% endif %} ms-1">
|
|
169
|
-
{% if live_env_image_sha.override %}override{% elif live_env_image_sha.effective %}env{% else %}default{% endif %}
|
|
170
|
-
</span>
|
|
171
|
-
</label>
|
|
172
|
-
<input type="text" id="live_env_image_sha" name="live_env_image_sha"
|
|
173
|
-
class="form-control font-monospace"
|
|
174
|
-
value="{{ live_env_image_sha.override }}"
|
|
175
|
-
placeholder="(unset - use the live-boot squashfs above)"
|
|
176
|
-
pattern="[0-9a-fA-F]{64}">
|
|
177
|
-
<div class="form-text">
|
|
178
|
-
A fetched disk-image's <code>content_sha256</code> (64 hex). When set - and that
|
|
179
|
-
image plus its netboot bundle are fetched - the <code>pixie-inventory</code> /
|
|
180
|
-
<code>pixie-tui</code> / <code>pixie-flash-*</code> modes boot it over
|
|
181
|
-
<strong>ephemeral nbdboot</strong> instead of fetching the squashfs. dracut brings
|
|
182
|
-
the NIC up where the live-boot squashfs hangs in the initramfs on some boards; the
|
|
183
|
-
on-target pixie CLI still phones home so inventory / flash work unchanged. Blank
|
|
184
|
-
falls back to <code>{{ live_env_image_sha.env }}</code> then the squashfs path.
|
|
185
|
-
Effective:
|
|
186
|
-
{% if live_env_image_sha.effective %}<code>{{ live_env_image_sha.effective }}</code>{% else %}<em class="text-muted">(none - squashfs)</em>{% endif %}.
|
|
187
|
-
{% if live_env_image_sha.updated_at %}<br>Last updated: <code>{{ live_env_image_sha.updated_at | fmt_ts }}</code>.{% endif %}
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
<div class="col-12">
|
|
191
|
-
<button type="submit" class="btn btn-primary"><i class="bi bi-check2 me-1"></i>Save</button>
|
|
192
|
-
<span class="text-muted small ms-2">
|
|
193
|
-
Takes effect on the NEXT <code>GET /pxe/<mac></code>; a target already mid-boot must be power-cycled.
|
|
194
|
-
</span>
|
|
195
|
-
</div>
|
|
196
|
-
</form>
|
|
197
120
|
</div>
|
|
198
121
|
</div>
|
|
199
|
-
|
|
200
|
-
{% if lfs.state == 'fetching' %}
|
|
201
|
-
{# The fetch runs on the server's thread pool; reload so the spinner
|
|
202
|
-
flips to ready / error on its own. #}
|
|
203
|
-
<script>setTimeout(function () { location.reload(); }, 5000);</script>
|
|
204
|
-
{% endif %}
|
|
205
122
|
{% endblock %}
|
pixie/web/main.py
CHANGED
|
@@ -55,8 +55,6 @@ from pixie.events._kinds import (
|
|
|
55
55
|
CATALOG_IMPORT_OK,
|
|
56
56
|
EVENTS_CLEARED,
|
|
57
57
|
EXPORT_NBDKIT_SPAWNED,
|
|
58
|
-
LIVE_ENV_FETCH_DONE,
|
|
59
|
-
LIVE_ENV_FETCH_FAILED,
|
|
60
58
|
TFTP_STARTED,
|
|
61
59
|
TFTP_STOPPED,
|
|
62
60
|
)
|
|
@@ -83,12 +81,10 @@ from pixie.web._auth import (
|
|
|
83
81
|
using_default_password,
|
|
84
82
|
)
|
|
85
83
|
from pixie.web._settings_store import (
|
|
86
|
-
DEFAULT_LIVE_ENV_SRC,
|
|
87
84
|
KEY_DATETIME_FORMAT,
|
|
88
85
|
KEY_DISPLAY_TZ,
|
|
89
86
|
KEY_LIVE_ENV_EXTRA_CMDLINE,
|
|
90
87
|
KEY_LIVE_ENV_IMAGE_SHA,
|
|
91
|
-
KEY_LIVE_ENV_SRC,
|
|
92
88
|
SettingsStore,
|
|
93
89
|
SettingValueError,
|
|
94
90
|
format_ts,
|
|
@@ -101,7 +97,6 @@ _STATIC_DIR = _HERE / "_static"
|
|
|
101
97
|
|
|
102
98
|
DEFAULT_STATE_DIR = Path("/var/lib/pixie")
|
|
103
99
|
STATE_DIR_ENV = "PIXIE_DATA_DIR"
|
|
104
|
-
LIVE_ENV_DIR_ENV = "PIXIE_LIVE_ENV_DIR"
|
|
105
100
|
FETCH_POOL_SIZE_ENV = "PIXIE_FETCH_POOL_SIZE"
|
|
106
101
|
DEFAULT_FETCH_POOL_SIZE = 4
|
|
107
102
|
|
|
@@ -171,24 +166,6 @@ def _resolve_tftp_bin() -> str:
|
|
|
171
166
|
return (os.environ.get(TFTP_BIN_ENV) or "").strip() or DEFAULT_TFTP_BIN
|
|
172
167
|
|
|
173
168
|
|
|
174
|
-
def _resolve_live_env_dir() -> Path | None:
|
|
175
|
-
"""Directory holding the pixie netboot-pc bake artifacts
|
|
176
|
-
(vmlinuz + initrd + squashfs). Default: ``<state_dir>/live-env``.
|
|
177
|
-
Explicit override via ``PIXIE_LIVE_ENV_DIR``. Returns the resolved
|
|
178
|
-
path whether or not it exists yet -- readiness is decided per-render
|
|
179
|
-
by stat'ing the individual files, and the serving mount is created
|
|
180
|
-
with ``check_dir=False``, so a fresh deploy can stage the artifacts
|
|
181
|
-
into a running container and have them picked up without a restart."""
|
|
182
|
-
override = (os.environ.get(LIVE_ENV_DIR_ENV) or "").strip()
|
|
183
|
-
if override:
|
|
184
|
-
return Path(override)
|
|
185
|
-
# Default sits under the state dir so an operator dropping
|
|
186
|
-
# artifacts into ``/opt/pixie/data/live-env/`` on a compose
|
|
187
|
-
# deploy is the whole install step.
|
|
188
|
-
default = _resolve_state_dir() / "live-env"
|
|
189
|
-
return default
|
|
190
|
-
|
|
191
|
-
|
|
192
169
|
def _resolve_default_boot_mode() -> str:
|
|
193
170
|
"""Boot mode a freshly-discovered MAC auto-registers with.
|
|
194
171
|
|
|
@@ -403,16 +380,6 @@ def _deployment_envvar_docs() -> list[dict[str, str]]:
|
|
|
403
380
|
" live-env by default."
|
|
404
381
|
),
|
|
405
382
|
},
|
|
406
|
-
{
|
|
407
|
-
"name": LIVE_ENV_DIR_ENV,
|
|
408
|
-
"default": "$PIXIE_DATA_DIR/live-env",
|
|
409
|
-
"purpose": (
|
|
410
|
-
"Where the pixie live-env vmlinuz + initrd +"
|
|
411
|
-
" live.squashfs are staged. The pixie-* boot modes"
|
|
412
|
-
" degrade to an unavailable plan when this dir is"
|
|
413
|
-
" missing or incomplete."
|
|
414
|
-
),
|
|
415
|
-
},
|
|
416
383
|
{
|
|
417
384
|
"name": "PIXIE_LIVE_ENV_EXTRA_CMDLINE",
|
|
418
385
|
"default": "",
|
|
@@ -435,32 +402,18 @@ def _deployment_envvar_docs() -> list[dict[str, str]]:
|
|
|
435
402
|
" exit-by-default behaviour."
|
|
436
403
|
),
|
|
437
404
|
},
|
|
438
|
-
{
|
|
439
|
-
"name": "PIXIE_LIVE_ENV_SRC",
|
|
440
|
-
"default": (
|
|
441
|
-
"https://github.com/safl/pixie/releases/latest/download/"
|
|
442
|
-
"pixie-live-env-x86_64.tar.gz"
|
|
443
|
-
),
|
|
444
|
-
"purpose": (
|
|
445
|
-
"Tarball the dashboard 'Fetch live-env' action pulls the"
|
|
446
|
-
" netboot-pc bake from (https:// or oras://). Defaults to"
|
|
447
|
-
" the latest pixie GitHub release; point at a mirror for"
|
|
448
|
-
" air-gapped deploys. Live-editable override on the"
|
|
449
|
-
" Live env page."
|
|
450
|
-
),
|
|
451
|
-
},
|
|
452
405
|
{
|
|
453
406
|
"name": "PIXIE_LIVE_ENV_IMAGE_SHA",
|
|
454
407
|
"default": "",
|
|
455
408
|
"purpose": (
|
|
456
409
|
"Disk-image content sha (64 hex) the live-env boot modes"
|
|
457
|
-
" boot over EPHEMERAL nbdboot
|
|
458
|
-
"
|
|
459
|
-
"
|
|
460
|
-
"
|
|
461
|
-
"
|
|
462
|
-
"
|
|
463
|
-
"
|
|
410
|
+
" boot over EPHEMERAL nbdboot. When set (and the image +"
|
|
411
|
+
" its netboot bundle are fetched) the pixie-inventory /"
|
|
412
|
+
" -tui / -flash-* modes stream this image over NBD --"
|
|
413
|
+
" dracut brings the NIC up where the retired squashfs"
|
|
414
|
+
" live-boot hung on some boards. Empty means those modes"
|
|
415
|
+
" degrade to the unavailable plan. Live-editable override"
|
|
416
|
+
" on the Live env page."
|
|
464
417
|
),
|
|
465
418
|
},
|
|
466
419
|
{
|
|
@@ -769,7 +722,6 @@ def create_app() -> FastAPI:
|
|
|
769
722
|
bind=_resolve_nbd_bind(),
|
|
770
723
|
nbdkit_bin=_resolve_nbdkit_bin(),
|
|
771
724
|
)
|
|
772
|
-
app.state.live_env_dir = _resolve_live_env_dir()
|
|
773
725
|
app.state.default_boot_mode = _resolve_default_boot_mode()
|
|
774
726
|
# Root for per-machine qcow2 overlay files. Sub-directory under
|
|
775
727
|
# the state dir so a single ``PIXIE_DATA_DIR`` override still
|
|
@@ -783,7 +735,6 @@ def create_app() -> FastAPI:
|
|
|
783
735
|
overlays=app.state.overlays_store,
|
|
784
736
|
nbd=app.state.nbd_server,
|
|
785
737
|
overlays_dir=app.state.overlays_dir,
|
|
786
|
-
live_env_dir=app.state.live_env_dir,
|
|
787
738
|
events=app.state.events_log,
|
|
788
739
|
)
|
|
789
740
|
app.state.tftp_server = (
|
|
@@ -801,11 +752,6 @@ def create_app() -> FastAPI:
|
|
|
801
752
|
thread_name_prefix="pixie-fetch",
|
|
802
753
|
)
|
|
803
754
|
app.state.fetch_states = {}
|
|
804
|
-
# Single-slot progress dict for the operator "Fetch live-env"
|
|
805
|
-
# action (there is one live env per pixie, not one per resource).
|
|
806
|
-
# Shape mirrors a fetch_states entry: state -> fetching / done /
|
|
807
|
-
# error, plus phase + byte counters while the download runs.
|
|
808
|
-
app.state.live_env_fetch_state = {}
|
|
809
755
|
|
|
810
756
|
# SessionMiddleware signs the ``pixie-token`` cookie. Sliding TTL:
|
|
811
757
|
# 7 days from last touch. ``https_only=False`` because pixie is
|
|
@@ -845,28 +791,6 @@ def create_app() -> FastAPI:
|
|
|
845
791
|
templates.env.filters["humanize_hz"] = humanize_hz
|
|
846
792
|
if _STATIC_DIR.is_dir():
|
|
847
793
|
app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static")
|
|
848
|
-
# Serve the netboot-pc bake artifacts (vmlinuz + initrd + squashfs)
|
|
849
|
-
# under ``/boot/pixie-live-env/`` so the live-env iPXE chain can
|
|
850
|
-
# fetch them. Mounted UNCONDITIONALLY with ``check_dir=False``: on a
|
|
851
|
-
# fresh deploy the dir doesn't exist at startup (the operator stages
|
|
852
|
-
# it later via "Fetch live env"), and StaticFiles resolves each
|
|
853
|
-
# request against the directory live -- so a post-startup fetch is
|
|
854
|
-
# served without a pixie restart. Guarding the mount on ``is_dir()``
|
|
855
|
-
# at startup was a bug: the renderer would emit a plan pointing at
|
|
856
|
-
# ``/boot/pixie-live-env/vmlinuz`` that then 404'd until a restart.
|
|
857
|
-
if app.state.live_env_dir is not None:
|
|
858
|
-
# Create the dir so StaticFiles serves a clean 404 for a
|
|
859
|
-
# not-yet-staged file (an empty/absent dir would otherwise raise
|
|
860
|
-
# at request time) and picks up a later "Fetch live env" with no
|
|
861
|
-
# restart. ``check_dir=False`` keeps app startup resilient if the
|
|
862
|
-
# mkdir is somehow refused.
|
|
863
|
-
with contextlib_suppress(OSError):
|
|
864
|
-
app.state.live_env_dir.mkdir(parents=True, exist_ok=True)
|
|
865
|
-
app.mount(
|
|
866
|
-
"/boot/pixie-live-env",
|
|
867
|
-
StaticFiles(directory=str(app.state.live_env_dir), check_dir=False),
|
|
868
|
-
name="live-env",
|
|
869
|
-
)
|
|
870
794
|
|
|
871
795
|
# ---------- exception handlers -----------------------------------
|
|
872
796
|
|
|
@@ -1813,28 +1737,17 @@ def create_app() -> FastAPI:
|
|
|
1813
1737
|
overlays=overlays_store, machines=machines_store, catalog=catalog, nbd=nbd
|
|
1814
1738
|
)
|
|
1815
1739
|
)
|
|
1816
|
-
# Live-env
|
|
1817
|
-
# ``pixie-flash-*``
|
|
1818
|
-
#
|
|
1819
|
-
#
|
|
1820
|
-
#
|
|
1821
|
-
#
|
|
1822
|
-
# the
|
|
1823
|
-
|
|
1824
|
-
live_env_ready =
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
"initrd": None,
|
|
1828
|
-
"live.squashfs": None,
|
|
1829
|
-
}
|
|
1830
|
-
if live_env_dir is not None:
|
|
1831
|
-
for name in live_env_files:
|
|
1832
|
-
p = live_env_dir / name
|
|
1833
|
-
try:
|
|
1834
|
-
live_env_files[name] = p.stat().st_size if p.is_file() else None
|
|
1835
|
-
except OSError:
|
|
1836
|
-
live_env_files[name] = None
|
|
1837
|
-
live_env_ready = all(v is not None for v in live_env_files.values())
|
|
1740
|
+
# Live-env readiness. ``pixie-tui`` / ``pixie-inventory`` /
|
|
1741
|
+
# ``pixie-flash-*`` boot the disk image named by
|
|
1742
|
+
# ``live_env.image_sha`` (PIXIE_LIVE_ENV_IMAGE_SHA / the Live env
|
|
1743
|
+
# page) over ephemeral nbdboot. Ready == a sha is selected AND
|
|
1744
|
+
# its catalog image is fetched on disk; otherwise those modes
|
|
1745
|
+
# degrade to the "unavailable" plan. The renderer does the full
|
|
1746
|
+
# bundle+blob check at boot; this is the at-a-glance signal.
|
|
1747
|
+
live_env_image_sha = request.app.state.settings_store.resolve_live_env_image_sha()
|
|
1748
|
+
live_env_ready = bool(live_env_image_sha) and any(
|
|
1749
|
+
e.content_sha256 == live_env_image_sha for e in images if e.content_sha256
|
|
1750
|
+
)
|
|
1838
1751
|
# Events summary: total row count + unacknowledged error
|
|
1839
1752
|
# count + newest ts. ``events_ack_ts`` is a soft cursor an
|
|
1840
1753
|
# operator advances by hitting Acknowledge on the dashboard;
|
|
@@ -1870,10 +1783,7 @@ def create_app() -> FastAPI:
|
|
|
1870
1783
|
"events_last_ts": events_stats["last_ts"],
|
|
1871
1784
|
"events_ack_ts": events_ack_ts,
|
|
1872
1785
|
"live_env_ready": live_env_ready,
|
|
1873
|
-
"
|
|
1874
|
-
"live_env_files": live_env_files,
|
|
1875
|
-
"live_env_src": request.app.state.settings_store.resolve_live_env_src(),
|
|
1876
|
-
"live_env_fetch_state": dict(request.app.state.live_env_fetch_state),
|
|
1786
|
+
"live_env_image_sha": live_env_image_sha,
|
|
1877
1787
|
}
|
|
1878
1788
|
|
|
1879
1789
|
@app.get("/ui/dashboard-live.json")
|
|
@@ -2529,35 +2439,15 @@ def create_app() -> FastAPI:
|
|
|
2529
2439
|
return RedirectResponse(url="/ui/settings", status_code=status.HTTP_303_SEE_OTHER)
|
|
2530
2440
|
|
|
2531
2441
|
def _live_env_context(request: Request, flash_error: str | None = None) -> dict[str, Any]:
|
|
2532
|
-
"""Render context for the dedicated /ui/live-env pane:
|
|
2533
|
-
|
|
2534
|
-
|
|
2442
|
+
"""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."""
|
|
2535
2445
|
state = request.app.state
|
|
2536
2446
|
store: SettingsStore = state.settings_store
|
|
2537
|
-
live_env_dir = state.live_env_dir
|
|
2538
|
-
files: dict[str, int | None] = {"vmlinuz": None, "initrd": None, "live.squashfs": None}
|
|
2539
|
-
if live_env_dir is not None:
|
|
2540
|
-
for name in files:
|
|
2541
|
-
p = live_env_dir / name
|
|
2542
|
-
try:
|
|
2543
|
-
files[name] = p.stat().st_size if p.is_file() else None
|
|
2544
|
-
except OSError:
|
|
2545
|
-
files[name] = None
|
|
2546
2447
|
return {
|
|
2547
2448
|
"version": pixie.__version__,
|
|
2548
2449
|
"authed": True,
|
|
2549
2450
|
"page": "live-env",
|
|
2550
|
-
"live_env_ready": all(v is not None for v in files.values()),
|
|
2551
|
-
"live_env_dir": str(live_env_dir) if live_env_dir is not None else "",
|
|
2552
|
-
"live_env_files": files,
|
|
2553
|
-
"live_env_fetch_state": dict(state.live_env_fetch_state),
|
|
2554
|
-
"live_env_src": {
|
|
2555
|
-
"override": store.get(KEY_LIVE_ENV_SRC) or "",
|
|
2556
|
-
"effective": store.resolve_live_env_src(),
|
|
2557
|
-
"default": DEFAULT_LIVE_ENV_SRC,
|
|
2558
|
-
"env": "PIXIE_LIVE_ENV_SRC",
|
|
2559
|
-
"updated_at": store.updated_at(KEY_LIVE_ENV_SRC) or "",
|
|
2560
|
-
},
|
|
2561
2451
|
"live_env_extra_cmdline": {
|
|
2562
2452
|
"override": store.get(KEY_LIVE_ENV_EXTRA_CMDLINE) or "",
|
|
2563
2453
|
"effective": store.resolve_live_env_extra_cmdline(),
|
|
@@ -2615,23 +2505,6 @@ def create_app() -> FastAPI:
|
|
|
2615
2505
|
store.clear(KEY_LIVE_ENV_EXTRA_CMDLINE)
|
|
2616
2506
|
return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
|
|
2617
2507
|
|
|
2618
|
-
@app.post("/ui/live-env/src/edit", response_model=None)
|
|
2619
|
-
def ui_live_env_src_edit(
|
|
2620
|
-
request: Request,
|
|
2621
|
-
live_env_src: str = Form(""),
|
|
2622
|
-
_auth: None = Depends(_require_ui_auth),
|
|
2623
|
-
) -> RedirectResponse:
|
|
2624
|
-
"""Persist the live-env fetch-src override. Blank clears it so
|
|
2625
|
-
the value falls back to $PIXIE_LIVE_ENV_SRC then the default
|
|
2626
|
-
GitHub-release URL."""
|
|
2627
|
-
store: SettingsStore = request.app.state.settings_store
|
|
2628
|
-
raw = (live_env_src or "").strip()
|
|
2629
|
-
if raw:
|
|
2630
|
-
store.set_value(KEY_LIVE_ENV_SRC, raw)
|
|
2631
|
-
else:
|
|
2632
|
-
store.clear(KEY_LIVE_ENV_SRC)
|
|
2633
|
-
return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
|
|
2634
|
-
|
|
2635
2508
|
@app.post("/ui/live-env/image/edit", response_model=None)
|
|
2636
2509
|
def ui_live_env_image_edit(
|
|
2637
2510
|
request: Request,
|
|
@@ -2640,12 +2513,12 @@ def create_app() -> FastAPI:
|
|
|
2640
2513
|
) -> HTMLResponse | RedirectResponse:
|
|
2641
2514
|
"""Persist the live-env disk-image content sha. When set (and the
|
|
2642
2515
|
image + its netboot bundle are fetched) the live-env boot modes
|
|
2643
|
-
nbdboot this image ephemerally
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
anything that is not 64 lowercase hex chars so a
|
|
2647
|
-
doesn't quietly send every live-env boot into
|
|
2648
|
-
plan."""
|
|
2516
|
+
nbdboot this image ephemerally. Blank clears the override so the
|
|
2517
|
+
value falls back to $PIXIE_LIVE_ENV_IMAGE_SHA then empty, in
|
|
2518
|
+
which case the live-env boot modes degrade to the unavailable
|
|
2519
|
+
plan. Rejects anything that is not 64 lowercase hex chars so a
|
|
2520
|
+
fat-fingered sha doesn't quietly send every live-env boot into
|
|
2521
|
+
the unavailable plan."""
|
|
2649
2522
|
import re as _re
|
|
2650
2523
|
|
|
2651
2524
|
store: SettingsStore = request.app.state.settings_store
|
|
@@ -2669,69 +2542,6 @@ def create_app() -> FastAPI:
|
|
|
2669
2542
|
store.clear(KEY_LIVE_ENV_IMAGE_SHA)
|
|
2670
2543
|
return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
|
|
2671
2544
|
|
|
2672
|
-
@app.post("/ui/live-env/fetch")
|
|
2673
|
-
def ui_live_env_fetch(
|
|
2674
|
-
request: Request,
|
|
2675
|
-
_auth: None = Depends(_require_ui_auth),
|
|
2676
|
-
) -> RedirectResponse:
|
|
2677
|
-
"""Download the live-env tarball (from PIXIE_LIVE_ENV_SRC or its
|
|
2678
|
-
override) and stage vmlinuz + initrd + live.squashfs under
|
|
2679
|
-
PIXIE_LIVE_ENV_DIR, on the fetch pool. The in-app replacement
|
|
2680
|
-
for a manual ``make build VARIANT=netboot-pc`` + copy. Progress
|
|
2681
|
-
lands on ``app.state.live_env_fetch_state`` for the dashboard to
|
|
2682
|
-
poll; idempotent while a fetch is already running."""
|
|
2683
|
-
state = request.app.state
|
|
2684
|
-
live_env_dir = state.live_env_dir
|
|
2685
|
-
fs = state.live_env_fetch_state
|
|
2686
|
-
if live_env_dir is None:
|
|
2687
|
-
fs.clear()
|
|
2688
|
-
fs.update(
|
|
2689
|
-
{
|
|
2690
|
-
"state": "error",
|
|
2691
|
-
"error": "no live-env dir (PIXIE_LIVE_ENV_DIR unset or unwritable)",
|
|
2692
|
-
"at_iso": _now_iso(),
|
|
2693
|
-
}
|
|
2694
|
-
)
|
|
2695
|
-
return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
|
|
2696
|
-
if fs.get("state") == "fetching":
|
|
2697
|
-
return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
|
|
2698
|
-
|
|
2699
|
-
src = state.settings_store.resolve_live_env_src()
|
|
2700
|
-
fs.clear()
|
|
2701
|
-
fs.update({"state": "fetching", "src": src, "phase": "starting", "at_iso": _now_iso()})
|
|
2702
|
-
events = getattr(state, "events_log", None)
|
|
2703
|
-
|
|
2704
|
-
def _run() -> None:
|
|
2705
|
-
from pixie.catalog._live_env import stage_live_env
|
|
2706
|
-
|
|
2707
|
-
def _report(payload: dict[str, Any]) -> None:
|
|
2708
|
-
# Layer phase / byte counters onto the "fetching" pill.
|
|
2709
|
-
fs.update(payload)
|
|
2710
|
-
|
|
2711
|
-
try:
|
|
2712
|
-
result = stage_live_env(src, live_env_dir, progress=_report)
|
|
2713
|
-
except Exception as exc:
|
|
2714
|
-
fs.clear()
|
|
2715
|
-
fs.update({"state": "error", "src": src, "error": str(exc), "at_iso": _now_iso()})
|
|
2716
|
-
if events is not None:
|
|
2717
|
-
events.emit(
|
|
2718
|
-
LIVE_ENV_FETCH_FAILED,
|
|
2719
|
-
summary=f"live-env fetch failed: {exc}",
|
|
2720
|
-
details={"src": src, "error": str(exc)},
|
|
2721
|
-
)
|
|
2722
|
-
return
|
|
2723
|
-
fs.clear()
|
|
2724
|
-
fs.update({"state": "done", "src": src, "sha256": result.sha256, "at_iso": _now_iso()})
|
|
2725
|
-
if events is not None:
|
|
2726
|
-
events.emit(
|
|
2727
|
-
LIVE_ENV_FETCH_DONE,
|
|
2728
|
-
summary=f"live-env staged from {src} (sha {result.sha256[:12]})",
|
|
2729
|
-
details={"src": src, "sha256": result.sha256, "bytes": result.size},
|
|
2730
|
-
)
|
|
2731
|
-
|
|
2732
|
-
state.fetch_pool.submit(_run)
|
|
2733
|
-
return RedirectResponse(url="/ui/live-env", status_code=status.HTTP_303_SEE_OTHER)
|
|
2734
|
-
|
|
2735
2545
|
# ---------- feature routers --------------------------------------
|
|
2736
2546
|
#
|
|
2737
2547
|
# 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
|
+
Version: 0.4.3
|
|
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
|
|
@@ -9,7 +9,6 @@ pixie/oras_pull.py,sha256=Xyqga4o8vtCZrUSv5YgLggqmXs36y6FXd-_E9a_iRVo,3326
|
|
|
9
9
|
pixie/tui_catalog.py,sha256=nKiVd-SC_qfS3m6phQXRnqQdKN_nTqZbQFl2cIscIoQ,26305
|
|
10
10
|
pixie/catalog/__init__.py,sha256=DWBJOL0goBieMLWFO2RXTc_w6e0OQ3f6qQVn-oiTq3c,2309
|
|
11
11
|
pixie/catalog/_fetcher.py,sha256=xiMLccBoJsjNumy1DT38aJiqM4-wYSzHGtklRKnKWlE,26698
|
|
12
|
-
pixie/catalog/_live_env.py,sha256=DWbPecbipSZ4KQAx60yi4q_gFncOZt4f1yj-QZ7ZBV0,5068
|
|
13
12
|
pixie/catalog/_routes.py,sha256=8s_8bEDYL5zwg6gNAtxR7NOGPLY7ho9spqjPU83RIPE,14729
|
|
14
13
|
pixie/catalog/_schema.py,sha256=KC6RcKlToX1DgcRjn25djKMx78-eS-4WzNRY267h0I0,9769
|
|
15
14
|
pixie/catalog/_store.py,sha256=S7OM7A05Y5ES4RuhmwqQupDGNWXF--p_UMfUNOgQHsQ,9044
|
|
@@ -18,7 +17,7 @@ pixie/deploy/__init__.py,sha256=3PvSvrIRdqxLCz0nTdVSBiPzEtDl359XSUBgyMBkMT8,1019
|
|
|
18
17
|
pixie/deploy/_main.py,sha256=Or2espz9oWNmnGfyEzIsEyvRwWEHL4uxyE6U9OdMZJs,15403
|
|
19
18
|
pixie/deploy/_templates.py,sha256=WkkKRDU00FDWVZM0XeKR-BCtSBPvaZ2QHM1TBeq0RYg,5418
|
|
20
19
|
pixie/events/__init__.py,sha256=3Quxl56ePkKD9-PjbomCOOZOpmqJ3wHcYa7VcT9KXv8,923
|
|
21
|
-
pixie/events/_kinds.py,sha256=
|
|
20
|
+
pixie/events/_kinds.py,sha256=LyyIrg4pGaat062sBIFrfGYGbZflsenfq7P-PkWhP04,10160
|
|
22
21
|
pixie/events/_log.py,sha256=0XSdIYrPIEtmxjTq9L7gy1Q4qGotTxgHhMkM9fv8tyY,9041
|
|
23
22
|
pixie/events/_routes.py,sha256=MbY69aZKDEzy4O8AizHtv_POPbdNMqz94imccFbtelM,1363
|
|
24
23
|
pixie/exports/__init__.py,sha256=RiI69GG_bjkb_d1CNRyk9KI7wyMpLVvWc_NgNnWxOYc,546
|
|
@@ -31,7 +30,7 @@ pixie/machines/_store.py,sha256=zh89zj2s5CurMFO258i10ANW20VCTQk0kT8MgRhnWi0,3037
|
|
|
31
30
|
pixie/pivot/__init__.py,sha256=k_yIUOrIu-DKCJ_8XmRtnoVV7Rr9URagHU2Y76HB2ew,6195
|
|
32
31
|
pixie/pivot/nbdboot,sha256=stBWGXnSXMFJMMzmtip77dPwWxQLfOewXo9YOgw5u0I,19558
|
|
33
32
|
pixie/pxe/__init__.py,sha256=AKBRKQYdlH7DdNBvES9EwYTzPOkeNbWBN1S7IH1UEyw,784
|
|
34
|
-
pixie/pxe/_renderer.py,sha256=
|
|
33
|
+
pixie/pxe/_renderer.py,sha256=KU9M0eZ5aRxI9Tsyej9MMXkBt5TooRoKY-a7pSQHvR8,17277
|
|
35
34
|
pixie/pxe/_routes.py,sha256=VhWH5VXaaAC39Bt3asZPpp9ckTCOJdJx3DOLFVTn-2M,19169
|
|
36
35
|
pixie/tftp/__init__.py,sha256=FuHn1KF5_NoGOM_dHXSbubX5lJr0tuMugJGFxxwuUws,774
|
|
37
36
|
pixie/tftp/_supervisor.py,sha256=khpjgWqdMzdnhRaPYM4j25PoKijbBnvr-GjJLybP_rY,4659
|
|
@@ -45,9 +44,9 @@ pixie/web/_images.py,sha256=RhtTmzRhapr6zRbQN4SKaW_o3dhUhf6hyZGIlbxMh_k,6993
|
|
|
45
44
|
pixie/web/_inventory.py,sha256=rJDNAfejmtlNPCPUr2mhLey19C5Tu_dmpia9KVeyT7E,10189
|
|
46
45
|
pixie/web/_overlay_bind.py,sha256=7aKYqMaujxt-XRa1jB6mVEkbt-w_koXjLvUzBTzy2o8,4125
|
|
47
46
|
pixie/web/_overlays.py,sha256=5yZp6pDiFj_WKdLx6vCoEKD8mXG3KjkMOQt4OGYYdyc,8571
|
|
48
|
-
pixie/web/_settings_store.py,sha256=
|
|
47
|
+
pixie/web/_settings_store.py,sha256=X9dO5dr31PvRUM-Y7cLilEiNG_7MCV8CpDW2Nc6V0Tc,11242
|
|
49
48
|
pixie/web/_table_state.py,sha256=zGH5oZ-XlMqUFQqmYglYqT_fu1Yrtaw-g5yhPtAKAEc,9524
|
|
50
|
-
pixie/web/main.py,sha256=
|
|
49
|
+
pixie/web/main.py,sha256=r4ZmVBBjvj5Cs8nWOOHZkBVv2BttNeNOSnKc_fNb-1Q,108149
|
|
51
50
|
pixie/web/_static/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
51
|
pixie/web/_static/bootstrap-icons.min.css,sha256=pdY4ejLKO67E0CM2tbPtq1DJ3VGDVVdqAR6j3ZwdiE4,87008
|
|
53
52
|
pixie/web/_static/bootstrap.min.css,sha256=NXgoHLHuUWz0lbqh8PRwCIl7_QhZE74v-FCwrYdCW1E,234717
|
|
@@ -58,12 +57,12 @@ pixie/web/_static/fonts/bootstrap-icons.woff,sha256=9VUTt7WRy4SjuH_w406iTUgx1v7c
|
|
|
58
57
|
pixie/web/_static/fonts/bootstrap-icons.woff2,sha256=bHVxA2ShylYEJncW9tKJl7JjGf2weM8R4LQqtm_y6mE,134044
|
|
59
58
|
pixie/web/_templates/catalog.html,sha256=CfjO7qcoy5226qy-24Qkx-ZvpoeRtW5gWsTkQzhi8sg,17465
|
|
60
59
|
pixie/web/_templates/catalog_detail.html,sha256=G12HHq2iYKv5bn5wjGfppUPJ12iOm9G5TjDlr-DTsHc,18182
|
|
61
|
-
pixie/web/_templates/dashboard.html,sha256
|
|
60
|
+
pixie/web/_templates/dashboard.html,sha256=Ys3TBRpQF9PShMa2uFtTIKSWeAc6THF6JDRHaIXgOGA,14996
|
|
62
61
|
pixie/web/_templates/events.html,sha256=ifawe--m5FMcicKZ2daCCycV7rGgpLJz7A309UrTolM,7666
|
|
63
62
|
pixie/web/_templates/image_detail.html,sha256=--DjHCDgvripwr-9_lmnmaLCe4Iw2W_gXIGuKLMvcJw,6926
|
|
64
63
|
pixie/web/_templates/images.html,sha256=PO8r0hZ6tXMqHZo_m1t_wgh4NEZP6u_72owH_Jbz2Zo,7266
|
|
65
64
|
pixie/web/_templates/layout.html,sha256=9kQ1sWyAqMl9YgwJO1jIjwRbiG8bt2sUpJLNrbBZmzo,13988
|
|
66
|
-
pixie/web/_templates/live_env.html,sha256=
|
|
65
|
+
pixie/web/_templates/live_env.html,sha256=EtZB728ZG-v2kG-YAwox3R9moz0O24rBD_HGJXkpRQM,7132
|
|
67
66
|
pixie/web/_templates/login.html,sha256=d60N8kmr7YaAoTKRBaOiGN5Srdy8-BgB8_Zz9TQvGwc,1810
|
|
68
67
|
pixie/web/_templates/machine_detail.html,sha256=ijI5FKuyqOdDwCdLGM_H7yHgqn6fFMm6pOPVBLT9G00,49797
|
|
69
68
|
pixie/web/_templates/machines.html,sha256=modlu9MNqpxOkDd90o-yV1g5yuEiC61I6JtXQk7_PUo,9803
|
|
@@ -75,10 +74,9 @@ pixie/web/_templates/_partials/table_helpers.html,sha256=xObkXvfOSoM9rB5Ni_W3nnU
|
|
|
75
74
|
pixie/web/_templates/ipxe/bootstrap.j2,sha256=xt13H2dHf-SeLhWRQvptqGY51sJ_L24C2ExnLj9uXPc,490
|
|
76
75
|
pixie/web/_templates/ipxe/exit.j2,sha256=pbTXTwjgiEsPZlRuv7_J0Q3BepXEqXnb82mv1sxDW24,371
|
|
77
76
|
pixie/web/_templates/ipxe/nbdboot.j2,sha256=s1CwIKou5W6LRoC2MdZYwA3RcRN8AU3B1gbJmGLS9MM,3950
|
|
78
|
-
pixie/web/_templates/ipxe/pixie-live-env.j2,sha256=dPWuYCFrkXwwatPSnTRSfWuMUMgvs3VLDHqbJTG1pP0,3048
|
|
79
77
|
pixie/web/_templates/ipxe/unavailable.j2,sha256=zt4DYfYRXmOIjL13diYPxEApTP3CRnn5Beszeh3BPBw,440
|
|
80
|
-
pixie_lab-0.4.
|
|
81
|
-
pixie_lab-0.4.
|
|
82
|
-
pixie_lab-0.4.
|
|
83
|
-
pixie_lab-0.4.
|
|
84
|
-
pixie_lab-0.4.
|
|
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,,
|
pixie/catalog/_live_env.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
"""Fetch + stage the pixie live-env (the netboot-pc bake).
|
|
2
|
-
|
|
3
|
-
The live env -- ``vmlinuz`` + ``initrd`` + ``live.squashfs`` that the
|
|
4
|
-
``pixie-flash-*`` / ``pixie-inventory`` / ``pixie-tui`` boot modes chain
|
|
5
|
-
into -- was the one artifact pixie could not retrieve itself: it had to
|
|
6
|
-
be baked locally (``make build VARIANT=netboot-pc``) or hand-copied into
|
|
7
|
-
``PIXIE_LIVE_ENV_DIR``. This module pulls it from a single tarball
|
|
8
|
-
``src`` (the same ``https://`` / ``oras://`` schemes the catalog fetch
|
|
9
|
-
speaks) and stages the three files atomically, reusing the catalog
|
|
10
|
-
fetch pipeline's curl download + resume plumbing.
|
|
11
|
-
|
|
12
|
-
Contract with CI's ``publish-release`` job: the tarball carries exactly
|
|
13
|
-
``vmlinuz`` + ``initrd`` + ``live.squashfs`` at the archive root. Any
|
|
14
|
-
other member is ignored; a missing one is a hard error (better a loud
|
|
15
|
-
"tarball is wrong" than a live env that half-stages and boots into a
|
|
16
|
-
mysterious squashfs-fetch hang).
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
from __future__ import annotations
|
|
20
|
-
|
|
21
|
-
import os
|
|
22
|
-
import shutil
|
|
23
|
-
import tarfile
|
|
24
|
-
import tempfile
|
|
25
|
-
from dataclasses import dataclass, field
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
|
|
28
|
-
from pixie._util import CHUNK
|
|
29
|
-
from pixie.catalog._fetcher import (
|
|
30
|
-
FetchError,
|
|
31
|
-
ProgressReporter,
|
|
32
|
-
_resolve_fetch_url,
|
|
33
|
-
_stream_to_tmpfile,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
# The three files a target's iPXE chain pulls from
|
|
37
|
-
# ``/boot/pixie-live-env/``. Names match what ``pixie-live-env.j2``
|
|
38
|
-
# references (note ``live.squashfs``, not the bake's raw ``*.squashfs``
|
|
39
|
-
# -- the CI tar step renames it).
|
|
40
|
-
LIVE_ENV_FILES: frozenset[str] = frozenset({"vmlinuz", "initrd", "live.squashfs"})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass
|
|
44
|
-
class LiveEnvResult:
|
|
45
|
-
"""Outcome of a successful stage: the src it came from, the sha256
|
|
46
|
-
of the downloaded tarball, its byte size, and the staged file
|
|
47
|
-
sizes."""
|
|
48
|
-
|
|
49
|
-
src: str
|
|
50
|
-
sha256: str
|
|
51
|
-
size: int
|
|
52
|
-
files: dict[str, int] = field(default_factory=dict)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _unpack_live_env_tar(tar_path: Path, live_env_dir: Path) -> dict[str, int]:
|
|
56
|
-
"""Extract the live-env trio from ``tar_path`` into ``live_env_dir``
|
|
57
|
-
atomically. Returns ``{name: size_bytes}``.
|
|
58
|
-
|
|
59
|
-
Hardening mirrors :func:`pixie.catalog._fetcher._unpack_netboot_bundle`:
|
|
60
|
-
only basenames in :data:`LIVE_ENV_FILES` at the archive root are
|
|
61
|
-
accepted; nested paths, parent traversal, and dotfiles are dropped.
|
|
62
|
-
Files land in a staging subdir first, then each is ``os.replace``'d
|
|
63
|
-
onto its final name so a target fetching mid-stage sees the old
|
|
64
|
-
file or the new one, never a half-written squashfs.
|
|
65
|
-
"""
|
|
66
|
-
live_env_dir.mkdir(parents=True, exist_ok=True)
|
|
67
|
-
staging = Path(tempfile.mkdtemp(prefix=".staging-", dir=str(live_env_dir)))
|
|
68
|
-
try:
|
|
69
|
-
# ``r:*`` transparently handles .tar / .tar.gz / .tar.xz so the
|
|
70
|
-
# CI side can pick whatever compression without a pixie change.
|
|
71
|
-
with tarfile.open(str(tar_path), mode="r:*") as tar:
|
|
72
|
-
for member in tar.getmembers():
|
|
73
|
-
if not member.isfile():
|
|
74
|
-
continue
|
|
75
|
-
name = os.path.basename(member.name)
|
|
76
|
-
if not name or name.startswith(".") or name != member.name:
|
|
77
|
-
continue
|
|
78
|
-
if name not in LIVE_ENV_FILES:
|
|
79
|
-
continue
|
|
80
|
-
extracted = tar.extractfile(member)
|
|
81
|
-
if extracted is None:
|
|
82
|
-
continue
|
|
83
|
-
with (staging / name).open("wb") as out:
|
|
84
|
-
shutil.copyfileobj(extracted, out, length=CHUNK)
|
|
85
|
-
|
|
86
|
-
missing = LIVE_ENV_FILES - {p.name for p in staging.iterdir()}
|
|
87
|
-
if missing:
|
|
88
|
-
raise FetchError(
|
|
89
|
-
f"live-env tarball {tar_path.name} missing required file(s): {sorted(missing)}"
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
sizes: dict[str, int] = {}
|
|
93
|
-
for name in sorted(LIVE_ENV_FILES):
|
|
94
|
-
final = live_env_dir / name
|
|
95
|
-
os.replace(staging / name, final)
|
|
96
|
-
sizes[name] = final.stat().st_size
|
|
97
|
-
return sizes
|
|
98
|
-
finally:
|
|
99
|
-
shutil.rmtree(staging, ignore_errors=True)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def stage_live_env(
|
|
103
|
-
src: str,
|
|
104
|
-
live_env_dir: Path,
|
|
105
|
-
*,
|
|
106
|
-
progress: ProgressReporter = None,
|
|
107
|
-
) -> LiveEnvResult:
|
|
108
|
-
"""Download the live-env tarball at ``src`` and stage its three
|
|
109
|
-
files into ``live_env_dir``.
|
|
110
|
-
|
|
111
|
-
Reuses the catalog fetch's curl transport (resume + retry + stall
|
|
112
|
-
detection). The tarball lands in ``live_env_dir`` itself so the
|
|
113
|
-
unpack + ``os.replace`` stay on one filesystem. Raises
|
|
114
|
-
:class:`FetchError` on an empty/bad src, an unsupported scheme, a
|
|
115
|
-
download failure, or a tarball missing a required file.
|
|
116
|
-
"""
|
|
117
|
-
if not (src or "").strip():
|
|
118
|
-
raise FetchError("no live-env src configured (set PIXIE_LIVE_ENV_SRC)")
|
|
119
|
-
url, headers = _resolve_fetch_url(src.strip())
|
|
120
|
-
live_env_dir.mkdir(parents=True, exist_ok=True)
|
|
121
|
-
tmp_tar, sha256, size = _stream_to_tmpfile(url, headers, live_env_dir, progress)
|
|
122
|
-
try:
|
|
123
|
-
if progress is not None:
|
|
124
|
-
progress({"phase": "unpacking"})
|
|
125
|
-
sizes = _unpack_live_env_tar(tmp_tar, live_env_dir)
|
|
126
|
-
finally:
|
|
127
|
-
tmp_tar.unlink(missing_ok=True)
|
|
128
|
-
return LiveEnvResult(src=src.strip(), sha256=sha256, size=size, files=sizes)
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!ipxe
|
|
2
|
-
# pixie plan for {{ mac }} - boot_mode={{ boot_mode }}
|
|
3
|
-
#
|
|
4
|
-
# Boot pixie's own live env. The netboot-pc bake produced vmlinuz +
|
|
5
|
-
# initrd + squashfs artifacts; the pixie deploy staged them under
|
|
6
|
-
# ``$PIXIE_LIVE_ENV_DIR`` and this HTTP process exposes them at
|
|
7
|
-
# ``/boot/pixie-live-env/``. Debian live-boot cmdline shape:
|
|
8
|
-
#
|
|
9
|
-
# boot=live components fetch=<squashfs URL>
|
|
10
|
-
#
|
|
11
|
-
# handles the initrd -> squashfs pivot. ``pixie.server`` +
|
|
12
|
-
# ``pixie.mac`` are the two knobs pixie-on-tty1.service reads on
|
|
13
|
-
# every live-env boot; the pixie CLI then GETs
|
|
14
|
-
# ``<server>/pxe/<mac>/plan`` and dispatches (auto-flash,
|
|
15
|
-
# inventory + reboot, interactive wizard, or exit).
|
|
16
|
-
#
|
|
17
|
-
# ``ip=dhcp`` mirrors the nbdboot template's cmdline: the initrd's
|
|
18
|
-
# network module (Debian live-boot, in this case) needs an explicit
|
|
19
|
-
# DHCP directive to bring the primary NIC up before pixie CLI tries
|
|
20
|
-
# to reach ``$pixie-server``. ``console=`` chain matches nbdboot too
|
|
21
|
-
# so a target with a serial console reads output on both tty0 and
|
|
22
|
-
# ttyS0/1 -- most bench-side debugging happens on serial.
|
|
23
|
-
|
|
24
|
-
set pixie-base http://{{ host }}:{{ port }}
|
|
25
|
-
|
|
26
|
-
# Cmdline matches bty's proven-working live-env kernel line
|
|
27
|
-
# (bty/src/bty/web/_templates/ipxe_flash.j2 + ipxe_tui.j2). Deltas
|
|
28
|
-
# from an earlier pixie shape that had drifted:
|
|
29
|
-
#
|
|
30
|
-
# - ``ip=dhcp`` REMOVED. Debian live-boot's ``fetch=`` handler
|
|
31
|
-
# configures the network itself via its own DHCP client; keeping
|
|
32
|
-
# both racing produced silent hangs on GIGABYTE MC12-LE0 hardware
|
|
33
|
-
# where live-boot's ipconfig would return with no NIC configured
|
|
34
|
-
# despite iPXE having successfully DHCP'd from the same interface.
|
|
35
|
-
# bty never had it; verified live on 10.20.30.61 (matx-bmc) with
|
|
36
|
-
# nothing else changing.
|
|
37
|
-
# - ``plymouth.enable=0 modprobe.blacklist=nouveau,rndis_host
|
|
38
|
-
# nouveau.modeset=0 systemd.gpt_auto=0`` ADDED. Same set bty
|
|
39
|
-
# ships. plymouth stays off so the operator sees the full boot
|
|
40
|
-
# stream (a wedge between two ``[ OK ] Started X`` lines is
|
|
41
|
-
# immediately diagnostic); nouveau blacklist keeps NVIDIA
|
|
42
|
-
# GPU quirks from stalling non-graphics targets; rndis_host
|
|
43
|
-
# blacklist stops the kernel from claiming a USB-console-emulated
|
|
44
|
-
# NIC before the real one appears; systemd.gpt_auto=0 keeps
|
|
45
|
-
# systemd from auto-mounting random GPT partitions the flasher
|
|
46
|
-
# is about to overwrite.
|
|
47
|
-
#
|
|
48
|
-
# ``console=`` chain matches bty too; LAST-wins for /dev/console.
|
|
49
|
-
# The ttyS0 tail lands on most bench-side USB-serial adapters;
|
|
50
|
-
# operators driving from a BMC's SoL on ttyS1 append their own
|
|
51
|
-
# ``console=ttyS1,115200`` via PIXIE_LIVE_ENV_EXTRA_CMDLINE.
|
|
52
|
-
|
|
53
|
-
kernel ${pixie-base}/boot/pixie-live-env/vmlinuz boot=live fetch=${pixie-base}/boot/pixie-live-env/live.squashfs components console=tty0 console=ttyS1,115200 console=ttyS0,115200 earlyprintk=ttyS0,115200 plymouth.enable=0 modprobe.blacklist=nouveau,rndis_host nouveau.modeset=0 systemd.gpt_auto=0 pixie.server=${pixie-base} pixie.mac={{ mac }} bty.server=${pixie-base} bty.mac={{ mac }}{% if extra_cmdline %} {{ extra_cmdline }}{% endif %}
|
|
54
|
-
initrd ${pixie-base}/boot/pixie-live-env/initrd
|
|
55
|
-
boot
|
|
File without changes
|
|
File without changes
|
|
File without changes
|