pixie-lab 0.4.2__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,23 +196,12 @@ 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 ------------------------------------------
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``."""
199
+ # ---------- live env ------------------------------------------------
211
200
 
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``."""
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``."""
216
205
 
217
206
 
218
207
  # The canonical closed set. Every kind above is registered here; the
@@ -248,8 +237,7 @@ KNOWN_EVENT_KINDS: frozenset[str] = frozenset(
248
237
  AUTH_LOGIN_SUCCEEDED,
249
238
  AUTH_LOGIN_FAILED,
250
239
  EVENTS_CLEARED,
251
- LIVE_ENV_FETCH_DONE,
252
- LIVE_ENV_FETCH_FAILED,
240
+ LIVE_ENV_IMAGE_SELECTED,
253
241
  }
254
242
  )
255
243
 
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 instead of the Debian live-boot squashfs.
95
- # Operator-set via PIXIE_LIVE_ENV_IMAGE_SHA / the Live env page.
96
- # Empty (default) keeps the squashfs path so existing deploys are
97
- # untouched; when set, ``render`` streams this image over NBD for
98
- # every LIVE_ENV_MODES machine (the machine's own bind still drives
99
- # what GET /pxe/<mac>/plan returns, so the on-target CLI dispatches
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
- Two deliveries, picked by ``ctx.live_env_image_sha``
345
- (PIXIE_LIVE_ENV_IMAGE_SHA / the Live env page):
346
-
347
- * Set -> boot that disk image over EPHEMERAL nbdboot, the same
348
- code path the ``nbdboot`` mode uses. Proven on hardware to
349
- bring the NIC up via dracut where the squashfs live-boot
350
- hangs in the initramfs on some boards. If the configured
351
- image or its netboot bundle isn't fetched, degrade to
352
- ``unavailable`` (matching how ``nbdboot`` degrades) rather
353
- than silently falling back -- a configured-but-broken live
354
- env is an operator error worth surfacing.
355
- * Unset -> the historical Debian live-boot squashfs
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 exactly as before."""
339
+ dispatches inventory / interactive / flash accordingly."""
362
340
  live_env_sha = ctx.live_env_image_sha
363
- if live_env_sha:
364
- resolved = self._resolve_bundle_and_blob(live_env_sha)
365
- if isinstance(resolved, str):
366
- return self._unavailable(
367
- machine,
368
- f"live-env image {live_env_sha[:12]} not bootable: {resolved}",
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
- if not self._live_env_ready():
375
- # netboot-pc bake artifacts have not been staged on this
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"boot_mode={mode!r} needs pixie live-env media; "
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
- return self._env.get_template("pixie-live-env.j2").render(
385
- mac=machine.mac,
386
- boot_mode=mode,
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:
@@ -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 instead of the Debian live-boot squashfs. When set
92
- # (and the named image + its netboot bundle are fetched) the renderer
93
- # streams this image over NBD -- proven on hardware to bring the NIC up
94
- # via dracut where the squashfs live-boot hangs in the initramfs on some
95
- # boards. Empty (the default) keeps the historical squashfs path so
96
- # existing deploys are untouched. Resolution: DB override -> env ->
97
- # empty. A value that is not 64 lowercase hex chars is treated as unset
98
- # so a fat-fingered setting degrades to the squashfs path rather than
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 media readiness (OUTPUT ONLY). The dashboard reports
130
- whether the pixie-* boot modes have their media; the Fetch action +
131
- source / cmdline config live on the dedicated /ui/live-env pane. #}
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-env media</h2>
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 lfs.state == 'fetching' %}
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 staged</span>
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="Fetch + configure the live env">
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-2">
153
- Staged at <code>{{ stats.live_env_dir }}</code>;
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> can serve the live env.
152
+ <code>pixie-flash-*</code> boot it over ephemeral nbdboot.
156
153
  </p>
157
- {% else %}
158
- <p class="small text-warning mb-2">
159
- Live-env media is missing under <code>{{ stats.live_env_dir or '(unset)' }}</code>.
160
- Every <code>pixie-*</code> boot_mode falls back to the "unavailable" iPXE
161
- plan until it is staged. <a href="/ui/live-env">Fetch it &rarr;</a>
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 &rarr;</a>
162
159
  </p>
163
- {% endif %}
164
- {% if lfs.state == 'error' %}
165
- <p class="small text-danger mb-2">
166
- <i class="bi bi-exclamation-triangle me-1"></i>Last fetch failed &mdash; see
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 &rarr;</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,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="#media">Media</a>
10
- <a href="#fetch">Fetch</a>
9
+ <a href="#setup">Setup</a>
10
+ <a href="#advanced">Advanced</a>
11
11
  <a href="#options">Options</a>
12
12
  </span>
13
13
  </div>
@@ -15,104 +15,108 @@
15
15
  {% endblock %}
16
16
 
17
17
  {% block content %}
18
- {% set lfs = live_env_fetch_state or {} %}
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.') }}
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.') }}
20
20
 
21
21
  {% if flash_error %}
22
22
  <div class="alert alert-danger py-2">{{ flash_error }}</div>
23
23
  {% endif %}
24
24
 
25
- {# ---- Media state (output) --------------------------------------- #}
26
- <div class="card shadow-sm mb-4" id="media">
25
+ {# ---- One-click setup -------------------------------------------- #}
26
+ <div class="card shadow-sm mb-4" id="setup">
27
27
  <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>Staged media</h2>
28
+ <h2 class="h5 mb-0"><i class="bi bi-boombox me-2"></i>Live env</h2>
29
29
  {% if live_env_ready %}
30
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>
31
33
  {% else %}
32
- <span class="badge text-bg-warning ms-auto"><i class="bi bi-exclamation-triangle me-1"></i>not staged</span>
34
+ <span class="badge text-bg-warning ms-auto"><i class="bi bi-exclamation-triangle me-1"></i>not set up</span>
33
35
  {% endif %}
34
36
  </div>
35
37
  <div class="card-body">
36
- <p class="small text-muted mb-2">
37
- Staged under <code>{{ live_env_dir or '(unset)' }}</code>, served to targets at
38
- <code>/boot/pixie-live-env/</code>. The renderer re-checks these files on every
39
- <code>GET /pxe/&lt;mac&gt;</code>, so a fresh fetch takes effect without a restart.
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.
40
50
  </p>
41
- <dl class="row small mb-0">
42
- {% for name, size in live_env_files.items() %}
43
- <dt class="col-sm-3 font-monospace">{{ name }}</dt>
44
- <dd class="col-sm-9">
45
- {% if size is not none %}
46
- <span class="text-success"><i class="bi bi-check-circle me-1"></i>{{ (size / 1024 / 1024) | round(1) }} MiB</span>
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...
47
73
  {% else %}
48
- <span class="text-warning"><i class="bi bi-x-circle me-1"></i>missing</span>
74
+ <i class="bi bi-boombox me-1"></i>{% if live_env_ready %}Re-run setup{% else %}Set up live env{% endif %}
49
75
  {% endif %}
50
- </dd>
51
- {% endfor %}
52
- </dl>
76
+ </button>
77
+ <span class="text-muted small ms-2">Fetch + select in one step.</span>
78
+ </form>
53
79
  </div>
54
80
  </div>
55
81
 
56
- {# ---- Fetch (action) --------------------------------------------- #}
57
- <div class="card shadow-sm mb-4" id="fetch">
82
+ {# ---- Advanced: select by content sha ---------------------------- #}
83
+ <div class="card shadow-sm mb-4" id="advanced">
58
84
  <div class="card-header bg-light fw-semibold">
59
- <i class="bi bi-cloud-download me-1"></i>Fetch live env
85
+ <i class="bi bi-sliders me-1"></i>Advanced: select by content sha
60
86
  </div>
61
87
  <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&nbsp;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 %}
88
+ <p class="small text-muted mb-3">
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.
93
+ </p>
94
+ <form method="post" action="/ui/live-env/image/edit" class="row g-3">
95
+ <div class="col-12">
96
+ <label for="live_env_image_sha" class="form-label">
97
+ Image content sha
98
+ <span class="badge text-bg-{% if live_env_image_sha.override %}primary{% else %}secondary{% endif %} ms-1">
99
+ {% if live_env_image_sha.override %}override{% elif live_env_image_sha.effective %}env{% else %}unset{% endif %}
84
100
  </span>
85
101
  </label>
86
- <input type="text" id="live_env_src" name="live_env_src"
102
+ <input type="text" id="live_env_image_sha" name="live_env_image_sha"
87
103
  class="form-control font-monospace"
88
- value="{{ live_env_src.override }}"
89
- placeholder="{{ live_env_src.default }}">
90
- </div>
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">
104
+ value="{{ live_env_image_sha.override }}"
105
+ placeholder="(unset - live-env modes render the unavailable plan)"
106
+ pattern="[0-9a-fA-F]{64}">
97
107
  <div class="form-text">
98
- Tarball to download (<code>https://</code> or <code>oras://</code>); must unpack to
99
- <code>vmlinuz</code> + <code>initrd</code> + <code>live.squashfs</code>. Blank falls
100
- back to <code>{{ live_env_src.env }}</code> then the latest pixie GitHub release.
101
- Effective: <code>{{ live_env_src.effective }}</code>.
102
- {% if live_env_src.updated_at %}<br>Last updated: <code>{{ live_env_src.updated_at | fmt_ts }}</code>.{% endif %}
108
+ Blank falls back to <code>{{ live_env_image_sha.env }}</code> then unset.
109
+ Effective:
110
+ {% if live_env_image_sha.effective %}<code>{{ live_env_image_sha.effective }}</code>{% else %}<em class="text-muted">(none)</em>{% endif %}.
111
+ {% if live_env_image_sha.updated_at %}<br>Last updated: <code>{{ live_env_image_sha.updated_at | fmt_ts }}</code>.{% endif %}
103
112
  </div>
104
113
  </div>
105
- </form>
106
-
107
- <form method="post" action="/ui/live-env/fetch" class="m-0">
108
- <button type="submit" class="btn btn-primary" {% if lfs.state == 'fetching' %}disabled{% endif %}>
109
- {% if lfs.state == 'fetching' %}
110
- <span class="spinner-border spinner-border-sm me-1" role="status"></span>Fetching...
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>
114
+ <div class="col-12">
115
+ <button type="submit" class="btn btn-outline-secondary"><i class="bi bi-check2 me-1"></i>Save</button>
116
+ <span class="text-muted small ms-2">
117
+ Takes effect on the NEXT <code>GET /pxe/&lt;mac&gt;</code>; a target already mid-boot must be power-cycled.
118
+ </span>
119
+ </div>
116
120
  </form>
117
121
  </div>
118
122
  </div>
@@ -120,7 +124,7 @@
120
124
  {# ---- Options (config) ------------------------------------------- #}
121
125
  <div class="card shadow-sm mb-4" id="options">
122
126
  <div class="card-header bg-light fw-semibold">
123
- <i class="bi bi-sliders me-1"></i>Options
127
+ <i class="bi bi-gear me-1"></i>Options
124
128
  </div>
125
129
  <div class="card-body">
126
130
  <form method="post" action="/ui/live-env/cmdline/edit" class="row g-3">
@@ -140,7 +144,7 @@
140
144
  <option value="pci=realloc=on,nocrs">
141
145
  </datalist>
142
146
  <div class="form-text">
143
- Tokens appended verbatim to the <code>pixie-live-env</code> kernel cmdline
147
+ Tokens appended verbatim to the live-env kernel cmdline
144
148
  (after <code>pixie.mac=</code> / <code>bty.mac=</code>, so last-token-wins on any
145
149
  conflict). Whitespace-separated; single line. Blank falls back to
146
150
  <code>{{ live_env_extra_cmdline.env }}</code> then no append. A machine with its own
@@ -158,48 +162,36 @@
158
162
  </span>
159
163
  </div>
160
164
  </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/&lt;mac&gt;</code>; a target already mid-boot must be power-cycled.
194
- </span>
195
- </div>
196
- </form>
197
165
  </div>
198
166
  </div>
199
167
 
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 %}
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>
205
197
  {% endblock %}