pixie-lab 0.1.0__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/__init__.py +20 -0
- pixie/_util.py +38 -0
- pixie/catalog/__init__.py +25 -0
- pixie/catalog/_fetcher.py +513 -0
- pixie/catalog/_routes.py +392 -0
- pixie/catalog/_schema.py +263 -0
- pixie/catalog/_store.py +234 -0
- pixie/deploy/__init__.py +26 -0
- pixie/deploy/_main.py +321 -0
- pixie/deploy/_templates.py +153 -0
- pixie/disks.py +85 -0
- pixie/events/__init__.py +28 -0
- pixie/events/_kinds.py +203 -0
- pixie/events/_log.py +210 -0
- pixie/events/_routes.py +42 -0
- pixie/exports/__init__.py +17 -0
- pixie/exports/_routes.py +211 -0
- pixie/exports/_store.py +156 -0
- pixie/exports/_supervisor.py +240 -0
- pixie/flash.py +1971 -0
- pixie/images.py +473 -0
- pixie/machines/__init__.py +16 -0
- pixie/machines/_routes.py +190 -0
- pixie/machines/_store.py +623 -0
- pixie/oras.py +547 -0
- pixie/pivot/__init__.py +180 -0
- pixie/pivot/nbdboot +348 -0
- pixie/pxe/__init__.py +19 -0
- pixie/pxe/_renderer.py +244 -0
- pixie/pxe/_routes.py +386 -0
- pixie/tftp/__init__.py +21 -0
- pixie/tftp/_supervisor.py +129 -0
- pixie/tui/__init__.py +185 -0
- pixie/tui/_app.py +2219 -0
- pixie/tui_catalog.py +657 -0
- pixie/web/__init__.py +6 -0
- pixie/web/_auth.py +70 -0
- pixie/web/_settings_store.py +211 -0
- pixie/web/_static/.gitkeep +0 -0
- pixie/web/_static/bootstrap-icons.min.css +5 -0
- pixie/web/_static/bootstrap.min.css +12 -0
- pixie/web/_static/fonts/bootstrap-icons.woff +0 -0
- pixie/web/_static/fonts/bootstrap-icons.woff2 +0 -0
- pixie/web/_static/htmx.min.js +1 -0
- pixie/web/_static/pixie-favicon.png +0 -0
- pixie/web/_static/sse.js +290 -0
- pixie/web/_table_state.py +261 -0
- pixie/web/_templates/_partials/page_description.html +22 -0
- pixie/web/_templates/_partials/recent_events.html +47 -0
- pixie/web/_templates/_partials/table_helpers.html +189 -0
- pixie/web/_templates/catalog.html +364 -0
- pixie/web/_templates/catalog_detail.html +386 -0
- pixie/web/_templates/dashboard.html +256 -0
- pixie/web/_templates/events.html +125 -0
- pixie/web/_templates/ipxe/bootstrap.j2 +12 -0
- pixie/web/_templates/ipxe/exit.j2 +10 -0
- pixie/web/_templates/ipxe/nbdboot.j2 +57 -0
- pixie/web/_templates/ipxe/pixie-live-env.j2 +55 -0
- pixie/web/_templates/ipxe/unavailable.j2 +14 -0
- pixie/web/_templates/layout.html +345 -0
- pixie/web/_templates/login.html +40 -0
- pixie/web/_templates/machine_detail.html +786 -0
- pixie/web/_templates/machines.html +186 -0
- pixie/web/_templates/settings.html +265 -0
- pixie/web/main.py +1910 -0
- pixie_lab-0.1.0.dist-info/METADATA +107 -0
- pixie_lab-0.1.0.dist-info/RECORD +70 -0
- pixie_lab-0.1.0.dist-info/WHEEL +4 -0
- pixie_lab-0.1.0.dist-info/entry_points.txt +3 -0
- pixie_lab-0.1.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{% extends "layout.html" %}
|
|
2
|
+
{% from "_partials/table_helpers.html" import search_input, sort_header, pagination_inline %}
|
|
3
|
+
{% from "_partials/recent_events.html" import recent_events_card %}
|
|
4
|
+
{% from "_partials/page_description.html" import page_description %}
|
|
5
|
+
{% block title %}Machines - pixie{% endblock %}
|
|
6
|
+
{% block subnav %}
|
|
7
|
+
<nav class="navbar subnav-strip" aria-label="Section sub-navigation">
|
|
8
|
+
<div class="container">
|
|
9
|
+
{# LEFT: anchor links to in-page sections. Don't repeat the
|
|
10
|
+
top-nav label ("Machines"); use section-specific verbs. #}
|
|
11
|
+
<span class="subnav-jumps d-flex align-items-center flex-wrap">
|
|
12
|
+
<a href="#list">List</a>
|
|
13
|
+
</span>
|
|
14
|
+
{# RIGHT: no inline actions on this page. Bindings are edited
|
|
15
|
+
per-machine on the detail page. #}
|
|
16
|
+
</div>
|
|
17
|
+
</nav>
|
|
18
|
+
{% endblock %}
|
|
19
|
+
{% block content %}
|
|
20
|
+
{{ page_description('pc-display', 'Every MAC pixie has seen. A row appears here on the target\'s first PXE contact; the last-seen columns refresh every 5 s while the page is open. Click a MAC to bind its boot mode, edit labels, or inspect the inventory and events for that target.') }}
|
|
21
|
+
<div class="card" id="list">
|
|
22
|
+
<div class="card-header bg-light fw-semibold d-flex flex-wrap align-items-center gap-3 py-2">
|
|
23
|
+
<span class="text-nowrap"><i class="bi bi-pc-display me-1"></i>Machines</span>
|
|
24
|
+
<span class="vr"></span>
|
|
25
|
+
{{ search_input('/ui/machines', q, preserved, 'MAC / boot mode / sha / last IP') }}
|
|
26
|
+
{{ pagination_inline(page_state, preserved, label='machines') }}
|
|
27
|
+
</div>
|
|
28
|
+
{% if machines %}
|
|
29
|
+
<div class="table-responsive">
|
|
30
|
+
<table class="table table-hover align-middle mb-0">
|
|
31
|
+
<thead class="table-light">
|
|
32
|
+
<tr>
|
|
33
|
+
{{ sort_header('MAC', 'mac', sort, preserved) }}
|
|
34
|
+
{{ sort_header('Boot mode', 'boot_mode', sort, preserved) }}
|
|
35
|
+
<th scope="col">Image</th>
|
|
36
|
+
<th scope="col">Inventory</th>
|
|
37
|
+
{{ sort_header('Last seen', 'last_seen_at', sort, preserved) }}
|
|
38
|
+
<th scope="col" class="text-end">Actions</th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
<tbody>
|
|
42
|
+
{% for m in machines %}
|
|
43
|
+
<tr data-machine-row="{{ m.mac }}">
|
|
44
|
+
<td data-cell="mac-labels">
|
|
45
|
+
<a href="/ui/machines/{{ m.mac }}" class="text-decoration-none">
|
|
46
|
+
<code>{{ m.mac }}</code>
|
|
47
|
+
</a>
|
|
48
|
+
{% if m.labels %}
|
|
49
|
+
<div class="mt-1">
|
|
50
|
+
{% for lbl in m.labels %}
|
|
51
|
+
<span class="badge text-bg-light border me-1">{{ lbl }}</span>
|
|
52
|
+
{% endfor %}
|
|
53
|
+
</div>
|
|
54
|
+
{% endif %}
|
|
55
|
+
</td>
|
|
56
|
+
<td data-cell="boot-mode">
|
|
57
|
+
{% if m.boot_mode == 'nbdboot' %}
|
|
58
|
+
<span class="badge text-bg-primary">
|
|
59
|
+
<i class="bi bi-hdd-network me-1"></i>nbdboot
|
|
60
|
+
</span>
|
|
61
|
+
{% else %}
|
|
62
|
+
<span class="badge text-bg-secondary">
|
|
63
|
+
<i class="bi bi-box-arrow-right me-1"></i>{{ m.boot_mode }}
|
|
64
|
+
</span>
|
|
65
|
+
{% endif %}
|
|
66
|
+
</td>
|
|
67
|
+
<td data-cell="image">
|
|
68
|
+
{% if m.image_content_sha256 %}
|
|
69
|
+
<code title="{{ m.image_content_sha256 }}">{{ m.image_content_sha256[:12] }}</code>
|
|
70
|
+
{% else %}
|
|
71
|
+
<span class="text-muted small">-</span>
|
|
72
|
+
{% endif %}
|
|
73
|
+
</td>
|
|
74
|
+
<td data-cell="inventory">
|
|
75
|
+
{% if m.inventory %}
|
|
76
|
+
{% set d = m.inventory.get("disks") or [] %}
|
|
77
|
+
<span class="small">
|
|
78
|
+
<i class="bi bi-hdd me-1"></i>{{ d|length }} disk{{ '' if d|length == 1 else 's' }}
|
|
79
|
+
</span>
|
|
80
|
+
{% if m.inventory.get("lshw") %}
|
|
81
|
+
<span class="text-muted small">+ lshw</span>
|
|
82
|
+
{% endif %}
|
|
83
|
+
{% else %}
|
|
84
|
+
<span class="text-muted small">-</span>
|
|
85
|
+
{% endif %}
|
|
86
|
+
</td>
|
|
87
|
+
<td class="small text-muted" data-cell="last-seen">
|
|
88
|
+
{{ m.last_seen_at | fmt_ts }}
|
|
89
|
+
{% if m.last_seen_ip %}<br><code>{{ m.last_seen_ip }}</code>{% endif %}
|
|
90
|
+
</td>
|
|
91
|
+
<td class="text-end text-nowrap">
|
|
92
|
+
{# The MAC cell already links to the detail page;
|
|
93
|
+
an "open" icon here is redundant. Keep only
|
|
94
|
+
the destructive action, which needs its own
|
|
95
|
+
control (a confirm-prompt on a link would be
|
|
96
|
+
surprising). #}
|
|
97
|
+
<form method="post" action="/ui/machines/delete" class="d-inline"
|
|
98
|
+
onsubmit="return confirm('Delete machine record for {{ m.mac }}? Inventory + binding will be lost.');">
|
|
99
|
+
<input type="hidden" name="mac" value="{{ m.mac }}">
|
|
100
|
+
<button type="submit"
|
|
101
|
+
class="btn btn-sm btn-outline-danger"
|
|
102
|
+
title="Delete {{ m.mac }}"
|
|
103
|
+
aria-label="Delete {{ m.mac }}">
|
|
104
|
+
<i class="bi bi-trash" aria-hidden="true"></i>
|
|
105
|
+
</button>
|
|
106
|
+
</form>
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
{% endfor %}
|
|
110
|
+
</tbody>
|
|
111
|
+
</table>
|
|
112
|
+
</div>
|
|
113
|
+
{% else %}
|
|
114
|
+
<div class="card-body">
|
|
115
|
+
<p class="text-muted mb-0">
|
|
116
|
+
<i class="bi bi-inbox me-1"></i>
|
|
117
|
+
{% if q %}
|
|
118
|
+
No machines match <code>{{ q }}</code>.
|
|
119
|
+
{% else %}
|
|
120
|
+
No machines yet. Machines appear when a PXE-booting target hits
|
|
121
|
+
<code>/pxe/<mac></code>; open a machine to edit its binding.
|
|
122
|
+
{% endif %}
|
|
123
|
+
</p>
|
|
124
|
+
</div>
|
|
125
|
+
{% endif %}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<script>
|
|
129
|
+
// Live refresh: every 5 s hit /ui/machines-live.json and update the
|
|
130
|
+
// per-machine cells (boot_mode badge, image sha, inventory count,
|
|
131
|
+
// last_seen_at + IP). Only rows currently on the page get updated
|
|
132
|
+
// (rows on other pagination pages aren't visible; a full poll of
|
|
133
|
+
// them would just churn the state dict). A new MAC that shows up
|
|
134
|
+
// server-side but wasn't rendered here does NOT get injected --
|
|
135
|
+
// pagination + sort would be wrong without a re-render; the
|
|
136
|
+
// operator sees it on their next page load.
|
|
137
|
+
(function () {
|
|
138
|
+
const POLL_MS = 5000;
|
|
139
|
+
const rows = document.querySelectorAll("[data-machine-row]");
|
|
140
|
+
if (!rows.length) return;
|
|
141
|
+
|
|
142
|
+
function bootBadge(mode) {
|
|
143
|
+
if (mode === "nbdboot") {
|
|
144
|
+
return `<span class="badge text-bg-primary"><i class="bi bi-hdd-network me-1"></i>nbdboot</span>`;
|
|
145
|
+
}
|
|
146
|
+
return `<span class="badge text-bg-secondary"><i class="bi bi-box-arrow-right me-1"></i>${mode}</span>`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function poll() {
|
|
150
|
+
try {
|
|
151
|
+
const r = await fetch("/ui/machines-live.json", {credentials: "same-origin"});
|
|
152
|
+
if (!r.ok) return;
|
|
153
|
+
const rowsData = await r.json();
|
|
154
|
+
rows.forEach((tr) => {
|
|
155
|
+
const mac = tr.getAttribute("data-machine-row");
|
|
156
|
+
const d = rowsData[mac];
|
|
157
|
+
if (!d) return;
|
|
158
|
+
const setCell = (key, html) => {
|
|
159
|
+
const cell = tr.querySelector(`[data-cell="${key}"]`);
|
|
160
|
+
if (cell && cell.innerHTML !== html) cell.innerHTML = html;
|
|
161
|
+
};
|
|
162
|
+
setCell("boot-mode", bootBadge(d.boot_mode));
|
|
163
|
+
setCell("image", d.image_content_sha256
|
|
164
|
+
? `<code title="${d.image_content_sha256}">${d.image_content_sha256.slice(0,12)}</code>`
|
|
165
|
+
: `<span class="text-muted small">-</span>`);
|
|
166
|
+
let inv;
|
|
167
|
+
if (d.inventory_at) {
|
|
168
|
+
const disks = d.disks_count || 0;
|
|
169
|
+
inv = `<span class="small"><i class="bi bi-hdd me-1"></i>${disks} disk${disks === 1 ? "" : "s"}</span>`;
|
|
170
|
+
if (d.has_lshw) inv += ` <span class="text-muted small">+ lshw</span>`;
|
|
171
|
+
} else {
|
|
172
|
+
inv = `<span class="text-muted small">-</span>`;
|
|
173
|
+
}
|
|
174
|
+
setCell("inventory", inv);
|
|
175
|
+
let seen = d.last_seen_at_display || d.last_seen_at || "-";
|
|
176
|
+
if (d.last_seen_ip) seen += `<br><code>${d.last_seen_ip}</code>`;
|
|
177
|
+
setCell("last-seen", seen);
|
|
178
|
+
});
|
|
179
|
+
} catch (e) { /* swallow; try again next tick */ }
|
|
180
|
+
}
|
|
181
|
+
poll();
|
|
182
|
+
setInterval(poll, POLL_MS);
|
|
183
|
+
})();
|
|
184
|
+
</script>
|
|
185
|
+
{{ recent_events_card(machine_events, "machine") }}
|
|
186
|
+
{% endblock %}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{% extends "layout.html" %}
|
|
2
|
+
{% from "_partials/page_description.html" import page_description %}
|
|
3
|
+
{% block title %}Settings - pixie{% endblock %}
|
|
4
|
+
{% block subnav %}
|
|
5
|
+
<nav class="navbar subnav-strip" aria-label="Section sub-navigation">
|
|
6
|
+
<div class="container">
|
|
7
|
+
<span class="subnav-jumps d-flex align-items-center flex-wrap">
|
|
8
|
+
<a href="#display">Display</a>
|
|
9
|
+
<a href="#live-env">Live-env</a>
|
|
10
|
+
<a href="#deployment">Deployment</a>
|
|
11
|
+
</span>
|
|
12
|
+
</div>
|
|
13
|
+
</nav>
|
|
14
|
+
{% endblock %}
|
|
15
|
+
{% block content %}
|
|
16
|
+
{{ page_description('sliders', 'Operator-editable knobs that pixie persists across restarts, alongside the environment variables the deploy consumes at startup. UI edits below win over env-var defaults, so tuning a knob live in the browser does not need a redeploy. Env-var defaults are documented in the Deployment card at the bottom.') }}
|
|
17
|
+
{% if flash_error %}
|
|
18
|
+
<div class="alert alert-danger">
|
|
19
|
+
<i class="bi bi-exclamation-triangle me-1"></i>{{ flash_error }}
|
|
20
|
+
</div>
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
<div class="card shadow-sm mb-4" id="display">
|
|
24
|
+
<div class="card-header bg-light fw-semibold d-flex flex-wrap align-items-center gap-3 py-2">
|
|
25
|
+
<span class="text-nowrap"><i class="bi bi-clock me-1"></i>Display</span>
|
|
26
|
+
<span class="text-muted small">
|
|
27
|
+
timezone + strftime pattern for every operator-facing timestamp
|
|
28
|
+
</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="card-body">
|
|
31
|
+
<form method="post" action="/ui/settings/display/edit" class="row g-3">
|
|
32
|
+
<div class="col-md-6">
|
|
33
|
+
<label for="timezone" class="form-label">
|
|
34
|
+
Display timezone
|
|
35
|
+
<span class="badge text-bg-{% if display_tz.override %}primary{% else %}secondary{% endif %} ms-1">
|
|
36
|
+
{% if display_tz.override %}override{% elif display_tz.effective != display_tz.default %}env{% else %}default{% endif %}
|
|
37
|
+
</span>
|
|
38
|
+
</label>
|
|
39
|
+
<input type="text" id="timezone" name="timezone"
|
|
40
|
+
class="form-control font-monospace"
|
|
41
|
+
value="{{ display_tz.override }}"
|
|
42
|
+
placeholder="{{ display_tz.default }}"
|
|
43
|
+
list="tz-suggestions">
|
|
44
|
+
<datalist id="tz-suggestions">
|
|
45
|
+
<option value="UTC">
|
|
46
|
+
<option value="Europe/Copenhagen">
|
|
47
|
+
<option value="Europe/London">
|
|
48
|
+
<option value="Europe/Berlin">
|
|
49
|
+
<option value="America/New_York">
|
|
50
|
+
<option value="America/Los_Angeles">
|
|
51
|
+
<option value="Asia/Tokyo">
|
|
52
|
+
</datalist>
|
|
53
|
+
<div class="form-text">
|
|
54
|
+
IANA timezone name. Blank falls back to
|
|
55
|
+
<code>{{ display_tz.env }}</code> then <code>{{ display_tz.default }}</code>.
|
|
56
|
+
Currently effective: <code>{{ display_tz.effective }}</code>.
|
|
57
|
+
{% if display_tz.updated_at %}
|
|
58
|
+
<br>Last updated: <code>{{ display_tz.updated_at | fmt_ts }}</code>.
|
|
59
|
+
{% endif %}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="col-md-6">
|
|
63
|
+
<label for="datetime_format" class="form-label">
|
|
64
|
+
Datetime format
|
|
65
|
+
<span class="badge text-bg-{% if datetime_format.override %}primary{% else %}secondary{% endif %} ms-1">
|
|
66
|
+
{% if datetime_format.override %}override{% elif datetime_format.effective != datetime_format.default %}env{% else %}default{% endif %}
|
|
67
|
+
</span>
|
|
68
|
+
</label>
|
|
69
|
+
<input type="text" id="datetime_format" name="datetime_format"
|
|
70
|
+
class="form-control font-monospace"
|
|
71
|
+
value="{{ datetime_format.override }}"
|
|
72
|
+
placeholder="{{ datetime_format.default }}">
|
|
73
|
+
<div class="form-text">
|
|
74
|
+
Python <code>strftime</code> pattern (e.g.
|
|
75
|
+
<code>%Y-%m-%d %H:%M:%S %Z</code>,
|
|
76
|
+
<code>%d %b %Y %H:%M</code>). Blank falls back to
|
|
77
|
+
<code>{{ datetime_format.env }}</code> then the default.
|
|
78
|
+
Currently effective: <code>{{ datetime_format.effective }}</code>.
|
|
79
|
+
{% if datetime_format.updated_at %}
|
|
80
|
+
<br>Last updated: <code>{{ datetime_format.updated_at | fmt_ts }}</code>.
|
|
81
|
+
{% endif %}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="col-12">
|
|
85
|
+
<button type="submit" class="btn btn-primary">
|
|
86
|
+
<i class="bi bi-check2 me-1"></i>Save
|
|
87
|
+
</button>
|
|
88
|
+
<span class="text-muted small ms-2">
|
|
89
|
+
Blank fields CLEAR the override. Env / default takes over.
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
</form>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="card shadow-sm mb-4" id="live-env">
|
|
97
|
+
<div class="card-header bg-light fw-semibold d-flex flex-wrap align-items-center gap-3 py-2">
|
|
98
|
+
<span class="text-nowrap"><i class="bi bi-cpu me-1"></i>Live-env</span>
|
|
99
|
+
<span class="text-muted small">
|
|
100
|
+
per-deploy kernel-cmdline workarounds for hardware quirks
|
|
101
|
+
</span>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="card-body">
|
|
104
|
+
<form method="post" action="/ui/settings/live-env/edit" class="row g-3">
|
|
105
|
+
<div class="col-12">
|
|
106
|
+
<label for="extra_cmdline" class="form-label">
|
|
107
|
+
Extra kernel cmdline
|
|
108
|
+
<span class="badge text-bg-{% if live_env_extra_cmdline.override %}primary{% else %}secondary{% endif %} ms-1">
|
|
109
|
+
{% if live_env_extra_cmdline.override %}override{% elif live_env_extra_cmdline.effective %}env{% else %}default{% endif %}
|
|
110
|
+
</span>
|
|
111
|
+
</label>
|
|
112
|
+
<input type="text" id="extra_cmdline" name="extra_cmdline"
|
|
113
|
+
class="form-control font-monospace"
|
|
114
|
+
value="{{ live_env_extra_cmdline.override }}"
|
|
115
|
+
placeholder="e.g. pci=realloc=on,nocrs"
|
|
116
|
+
list="live-env-cmdline-suggestions">
|
|
117
|
+
<datalist id="live-env-cmdline-suggestions">
|
|
118
|
+
<option value="pci=realloc=on,nocrs">
|
|
119
|
+
</datalist>
|
|
120
|
+
<div class="form-text">
|
|
121
|
+
Tokens appended verbatim to the <code>pixie-live-env</code>
|
|
122
|
+
kernel cmdline (after <code>pixie.mac=</code> /
|
|
123
|
+
<code>bty.mac=</code>, so last-token-wins on any
|
|
124
|
+
conflict). Whitespace-separated; single line.
|
|
125
|
+
Blank falls back to <code>{{ live_env_extra_cmdline.env }}</code>
|
|
126
|
+
then no append.
|
|
127
|
+
Currently effective:
|
|
128
|
+
{% if live_env_extra_cmdline.effective %}
|
|
129
|
+
<code>{{ live_env_extra_cmdline.effective }}</code>
|
|
130
|
+
{% else %}
|
|
131
|
+
<em class="text-muted">(none)</em>
|
|
132
|
+
{% endif %}.
|
|
133
|
+
Known-good values per hardware in
|
|
134
|
+
<a href="https://github.com/safl/pixie/blob/main/docs/src/hardware-quirks.md" target="_blank" rel="noreferrer">docs/hardware-quirks.md</a>.
|
|
135
|
+
{% if live_env_extra_cmdline.updated_at %}
|
|
136
|
+
<br>Last updated: <code>{{ live_env_extra_cmdline.updated_at | fmt_ts }}</code>.
|
|
137
|
+
{% endif %}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="col-12">
|
|
141
|
+
<button type="submit" class="btn btn-primary">
|
|
142
|
+
<i class="bi bi-check2 me-1"></i>Save
|
|
143
|
+
</button>
|
|
144
|
+
<span class="text-muted small ms-2">
|
|
145
|
+
Takes effect on the NEXT <code>GET /pxe/<mac></code>;
|
|
146
|
+
a target already mid-boot must be power-cycled.
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
</form>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="card shadow-sm mb-4" id="deployment">
|
|
154
|
+
<div class="card-header bg-light fw-semibold d-flex flex-wrap align-items-center gap-3 py-2">
|
|
155
|
+
<span class="text-nowrap"><i class="bi bi-gear-wide-connected me-1"></i>Deployment</span>
|
|
156
|
+
<span class="text-muted small">
|
|
157
|
+
environment variables consumed at startup + DHCP handoff
|
|
158
|
+
</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="card-body">
|
|
161
|
+
<p class="text-muted small">
|
|
162
|
+
Environment variables the pixie process reads at startup.
|
|
163
|
+
The compose file bundled by <code>pixie deploy</code> sets
|
|
164
|
+
the important ones from <code>envvars</code>; anything not
|
|
165
|
+
set falls back to the default in the table. Changes here
|
|
166
|
+
require a container restart (they are read once at
|
|
167
|
+
startup), unlike the UI knobs above which are live.
|
|
168
|
+
</p>
|
|
169
|
+
|
|
170
|
+
<div class="table-responsive">
|
|
171
|
+
<table class="table table-sm mb-0 align-middle">
|
|
172
|
+
<thead>
|
|
173
|
+
<tr>
|
|
174
|
+
<th scope="col">Variable</th>
|
|
175
|
+
<th scope="col">Default</th>
|
|
176
|
+
<th scope="col">Purpose</th>
|
|
177
|
+
</tr>
|
|
178
|
+
</thead>
|
|
179
|
+
<tbody>
|
|
180
|
+
{% for row in deployment_envvars %}
|
|
181
|
+
<tr>
|
|
182
|
+
<td><code>{{ row.name }}</code></td>
|
|
183
|
+
<td>
|
|
184
|
+
{% if row.default %}<code>{{ row.default }}</code>
|
|
185
|
+
{% else %}<em class="text-muted small">(none)</em>{% endif %}
|
|
186
|
+
</td>
|
|
187
|
+
<td class="small">{{ row.purpose }}</td>
|
|
188
|
+
</tr>
|
|
189
|
+
{% endfor %}
|
|
190
|
+
</tbody>
|
|
191
|
+
</table>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<h3 class="h6 fw-semibold mt-4 mb-2">
|
|
195
|
+
<i class="bi bi-router me-1"></i>Pointing DHCP at pixie
|
|
196
|
+
</h3>
|
|
197
|
+
<p class="small mb-2">
|
|
198
|
+
Pixie chainloads iPXE via TFTP: the DHCP server tells the
|
|
199
|
+
target's PXE firmware where to fetch the iPXE bootloader,
|
|
200
|
+
and iPXE then chains to pixie's HTTP endpoint. The two
|
|
201
|
+
options below cover the common firmware split:
|
|
202
|
+
</p>
|
|
203
|
+
<div class="row g-3">
|
|
204
|
+
<div class="col-md-6">
|
|
205
|
+
<div class="border rounded p-3 h-100 bg-body-tertiary">
|
|
206
|
+
<div class="fw-semibold mb-1">
|
|
207
|
+
<i class="bi bi-hdd-network me-1"></i>Legacy BIOS / PXE
|
|
208
|
+
</div>
|
|
209
|
+
<div class="small text-muted mb-2">
|
|
210
|
+
Target boots via PXE ROM. DHCP hands out an
|
|
211
|
+
iPXE binary that pixie serves over TFTP;
|
|
212
|
+
iPXE re-DHCPs and pixie sees the second
|
|
213
|
+
request come in from an iPXE user-class
|
|
214
|
+
+ chains to <code>/pxe-bootstrap.ipxe</code>.
|
|
215
|
+
</div>
|
|
216
|
+
<pre class="mb-0 small"><code># dnsmasq
|
|
217
|
+
dhcp-boot=tag:!ipxe,undionly.kpxe,{{ deployment.host }},{{ deployment.host }}
|
|
218
|
+
dhcp-userclass=set:ipxe,iPXE
|
|
219
|
+
dhcp-boot=tag:ipxe,http://{{ deployment.host }}:{{ deployment.port }}/pxe-bootstrap.ipxe</code></pre>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="col-md-6">
|
|
223
|
+
<div class="border rounded p-3 h-100 bg-body-tertiary">
|
|
224
|
+
<div class="fw-semibold mb-1">
|
|
225
|
+
<i class="bi bi-hdd-network-fill me-1"></i>UEFI
|
|
226
|
+
</div>
|
|
227
|
+
<div class="small text-muted mb-2">
|
|
228
|
+
Target boots via UEFI PXE. Same shape but
|
|
229
|
+
the DHCP hand-off must serve an EFI iPXE
|
|
230
|
+
binary. Same iPXE user-class re-DHCP dance
|
|
231
|
+
lands on <code>/pxe-bootstrap.ipxe</code>.
|
|
232
|
+
</div>
|
|
233
|
+
<pre class="mb-0 small"><code># dnsmasq
|
|
234
|
+
dhcp-boot=tag:!ipxe,ipxe.efi,{{ deployment.host }},{{ deployment.host }}
|
|
235
|
+
dhcp-userclass=set:ipxe,iPXE
|
|
236
|
+
dhcp-boot=tag:ipxe,http://{{ deployment.host }}:{{ deployment.port }}/pxe-bootstrap.ipxe</code></pre>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="mt-3 small text-muted">
|
|
242
|
+
<div>
|
|
243
|
+
<span class="fw-semibold">TFTP listen:</span>
|
|
244
|
+
{% if deployment.tftp_enabled %}
|
|
245
|
+
<code>{{ deployment.tftp_bind }}:{{ deployment.tftp_port }}/udp</code>
|
|
246
|
+
(root <code>{{ deployment.tftp_root }}</code>)
|
|
247
|
+
{% else %}
|
|
248
|
+
<em>disabled</em> (set <code>PIXIE_TFTP_ENABLED=1</code>
|
|
249
|
+
to bind udp/69)
|
|
250
|
+
{% endif %}
|
|
251
|
+
</div>
|
|
252
|
+
<div>
|
|
253
|
+
<span class="fw-semibold">Bootstrap URL:</span>
|
|
254
|
+
<code>http://{{ deployment.host }}:{{ deployment.port }}/pxe-bootstrap.ipxe</code>
|
|
255
|
+
</div>
|
|
256
|
+
<div>
|
|
257
|
+
<span class="fw-semibold">NBD listen:</span>
|
|
258
|
+
<code>{{ deployment.nbd_bind }}</code> from port
|
|
259
|
+
<code>{{ deployment.nbd_port_base }}</code> upward
|
|
260
|
+
(one export per bound nbdboot image)
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
{% endblock %}
|