Updates group: - Dell drivers and BIOS: check Dell's catalogs against all three driver maps and models.txt, download with a shared speed cap, repack, preview, apply with backups and rollback. - iPXE boot files: check GitHub for the signed Secure Boot set, verify the sha256 and each file's signer, install atomically, roll back. Includes the by-hand procedure. - Blancco: upload a release, stage it, test-boot it from the menu, activate, roll back. - BPRT sync: validate a batch uploaded from the MediaCreator Lite cache (scripts/windows/Sync-BPRT.ps1 or a zip), show the diff, swap with the site overlay files kept. Images: one sidebar entry per image with Overview, Drivers, Unattend and OS and Packages tabs. Every save shows a diff first, keeps the file's format, makes a backup and can be undone. Driver lint runs inline. Enrollment: upload standard/engineer MCL packages, one per tenant/purpose, with archive and restore. Hardware coverage counts engineer as Win11.
346 lines
22 KiB
HTML
346 lines
22 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}iPXE Boot Files - PXE Server Manager{% endblock %}
|
|
|
|
{% macro sig_badge(info, chain_ok=none) -%}
|
|
{% if info.kind == 'microsoft-2011' %}<span class="badge badge-warning" title="Signed only under Microsoft Corporation UEFI CA 2011. That CA's signing period ended in 2026: machines that trust only the 2023 CA, or a DBX/SBAT revocation update, will refuse this file.">Microsoft UEFI CA 2011 only</span>
|
|
{% elif info.kind == 'microsoft-2023' %}<span class="badge badge-success" title="Signed under Microsoft UEFI CA 2023">Microsoft UEFI CA 2023</span>
|
|
{% elif info.kind == 'microsoft' %}<span class="badge badge-info">Microsoft (other CA)</span>
|
|
{% elif info.kind == 'ipxe' %}<span class="badge badge-success" title="Chains to the iPXE Secure Boot CA, which the iPXE shim trusts">iPXE Secure Boot CA</span>
|
|
{% elif info.kind == 'bios' %}<span class="badge badge-secondary" title="Legacy BIOS has no Secure Boot">Legacy BIOS, unsigned</span>
|
|
{% elif info.kind == 'unsigned' %}<span class="badge badge-danger" title="Secure Boot firmware will refuse it">Unsigned</span>
|
|
{% elif info.kind == 'error' %}<span class="badge badge-danger" title="{{ info.sig_error }}">Not checked</span>
|
|
{% else %}<span class="badge badge-warning">Other signer</span>{% endif %}
|
|
{% if chain_ok == false %}<span class="badge badge-danger" title="The installed shim trusts {{ info.trusts_needed }}; this file is not signed under it, so the shim will refuse to start it">Shim will refuse</span>{% endif %}
|
|
{%- endmacro %}
|
|
|
|
{% macro chain(info) -%}
|
|
{% if info.signers %}
|
|
<details class="mt-1">
|
|
<summary class="small text-muted" style="cursor:pointer;">Signer chain ({{ info.signers | length }})</summary>
|
|
{% for subject, issuer in info.signers %}
|
|
<div class="small"><span class="mono">{{ subject }}</span> <span class="text-muted">issued by</span> <span class="mono">{{ issuer }}</span></div>
|
|
{% endfor %}
|
|
</details>
|
|
{% endif %}
|
|
{% if info.trusts %}<div class="small text-muted">Starts second stages signed by <span class="mono">{{ info.trusts }}</span></div>{% endif %}
|
|
{% if info.sbat %}<div class="small text-muted">SBAT: <span class="mono">{{ info.sbat }}</span></div>{% endif %}
|
|
{%- endmacro %}
|
|
|
|
{% block content %}
|
|
<div class="page-header">
|
|
<h1><i class="bi bi-shield-lock"></i> iPXE Boot Files</h1>
|
|
<div class="header-actions">
|
|
{% if status == 'update' %}<span class="badge badge-warning badge-lg">Update available: {{ latest.tag }}</span>
|
|
{% elif status == 'current' %}<span class="badge badge-success badge-lg">Up to date ({{ latest.tag }})</span>
|
|
{% elif status == 'newer' %}<span class="badge badge-info badge-lg">Ahead of {{ latest.tag }}</span>
|
|
{% elif latest %}<span class="badge badge-secondary badge-lg">Latest: {{ latest.tag }}</span>{% endif %}
|
|
<form method="post" action="{{ url_for('ipxe.check') }}" style="display:inline;">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="pxe-btn pxe-btn-secondary"><i class="bi bi-arrow-repeat"></i> Check for updates</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-muted">
|
|
The Secure Boot chain that dnsmasq hands out over TFTP: the Microsoft-signed shim (<span class="mono">ipxe-shim.efi</span>)
|
|
starts <span class="mono">ipxe.efi</span>, which is signed by the iPXE Secure Boot CA. Updates come from the upstream
|
|
iPXE release tarball on GitHub; its sha256 is checked against GitHub's published digest and each file's signer chain is
|
|
read with <span class="mono">sbverify</span> before it can be installed. <span class="mono">ipxe-bcm.efi</span>,
|
|
<span class="mono">dnsmasq.conf</span> and the Blancco files are never changed here.
|
|
</p>
|
|
|
|
{# ---- Upstream ------------------------------------------------------------ #}
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-cloud-arrow-down"></i> Upstream release</div>
|
|
<div class="row g-3 align-items-end">
|
|
<div class="col-lg-7">
|
|
{% if latest %}
|
|
<div>
|
|
<strong class="mono">{{ latest.tag }}</strong>
|
|
{% if latest.name and latest.name != latest.tag %}<span class="text-muted">{{ latest.name }}</span>{% endif %}
|
|
{% if status == 'update' %}<span class="badge badge-warning">newer than installed</span>
|
|
{% elif status == 'current' %}<span class="badge badge-success">installed</span>
|
|
{% elif status == 'newer' %}<span class="badge badge-info">installed build is newer</span>
|
|
{% else %}<span class="badge badge-secondary">installed version unknown</span>{% endif %}
|
|
</div>
|
|
<div class="small text-muted">
|
|
Published {{ latest.published | local_dt }} | checked {{ latest.checked | local_dt }}
|
|
{% if latest.size %}| {{ "%.1f"|format(latest.size / 1048576) }} MB{% endif %}
|
|
| <a href="{{ latest.page }}" target="_blank" rel="noopener">release page</a>
|
|
</div>
|
|
<div class="small">sha256 <span class="mono" style="white-space: normal; word-break: break-all;">{{ latest.sha256 or 'not published' }}</span></div>
|
|
{% else %}
|
|
<p class="text-muted mb-0">Not checked yet. <strong>Check for updates</strong> asks the GitHub API for the latest release (a few seconds).</p>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-lg-5 d-flex flex-wrap gap-2 justify-content-lg-end align-items-end">
|
|
<form method="post" action="{{ url_for('ipxe.save_settings') }}" class="d-flex gap-2 align-items-end">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<div class="form-field mb-0">
|
|
<label for="maxMbps" class="form-label">Speed cap (MB/s)</label>
|
|
<input type="number" class="form-control form-control-sm" id="maxMbps" name="max_mbps"
|
|
min="0" max="1000" step="0.5" value="{{ '%g'|format(settings.max_mbps) }}" style="width: 7rem;">
|
|
</div>
|
|
<button type="submit" class="pxe-btn pxe-btn-ghost pxe-btn-sm" title="Shared by every download on the Updates pages; 0 = no cap. The site link is about 400 Mbps (50 MB/s).">Save</button>
|
|
</form>
|
|
<form method="post" action="{{ url_for('ipxe.stage') }}">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="pxe-btn pxe-btn-primary"
|
|
{% if not latest or not latest.sha256 or job.state in ('queued', 'running') %}disabled{% endif %}>
|
|
<i class="bi bi-download"></i> Download and verify
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="form-text">Speed cap applies to every download on the Updates pages (Dell and iPXE); 0 means no cap.</div>
|
|
|
|
{% if job.state %}
|
|
<div id="ipxeJob" class="mt-3" data-state="{{ job.state }}">
|
|
{% if job.state in ('queued', 'running') %}
|
|
<div class="d-flex align-items-center gap-2 mb-1">
|
|
<span class="spinner-border spinner-border-sm" role="status"></span>
|
|
<span id="ipxeJobText">{{ 'Starting' if job.state == 'queued' else (job.step | capitalize) }} {{ job.tag }}...</span>
|
|
</div>
|
|
<div class="progress" style="height: 0.5rem;">
|
|
<div id="ipxeJobBar" class="progress-bar" style="width: {{ job.percent or 0 }}%;"></div>
|
|
</div>
|
|
{% elif job.state == 'error' %}
|
|
<div class="alert alert-danger mb-0"><strong>Download of {{ job.tag }} failed:</strong> <span class="mono">{{ job.error }}</span></div>
|
|
{% elif job.state == 'interrupted' %}
|
|
<div class="alert alert-warning mb-0">The download of {{ job.tag }} stopped without finishing (server restart?). Start it again; it resumes the partial file.</div>
|
|
{% elif job.state == 'done' and job.failed %}
|
|
<div class="alert alert-warning mb-0">{{ job.tag }} downloaded, but {{ job.failed | join(', ') }} failed verification; see below.</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# ---- Installed ----------------------------------------------------------- #}
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-hdd"></i> Installed on the TFTP root</div>
|
|
<div class="table-container">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr><th>File</th><th>Version</th><th>Size</th><th>Modified</th><th>sha256</th><th>Signature</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for r in rows %}
|
|
{% set i = r.info %}
|
|
<tr>
|
|
<td class="text-nowrap">
|
|
<span class="mono">{{ r.name }}</span>
|
|
{% if not r.managed %}<div class="small text-muted">read-only here</div>{% endif %}
|
|
</td>
|
|
{% if i %}
|
|
<td class="mono">{{ i.version or '-' }}{% if i.release %}<div class="small text-muted">from {{ i.release }}</div>{% endif %}</td>
|
|
<td class="text-nowrap">{{ "{:,}".format(i.size) }} B</td>
|
|
<td class="mono text-nowrap">{{ i.modified | timestamp_fmt }}</td>
|
|
<td class="mono" title="{{ i.sha256 }}">{{ i.sha256[:12] }}</td>
|
|
<td class="wrap">{{ sig_badge(i, i.chain_ok if i.chain_ok is defined else none) }}{{ chain(i) }}</td>
|
|
{% else %}
|
|
<td colspan="5" class="text-muted">Not present</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{# ---- Staged -------------------------------------------------------------- #}
|
|
{% if staged %}
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-box-seam"></i> Downloaded and verified</div>
|
|
{% for s in staged %}
|
|
<form method="post" action="{{ url_for('ipxe.install') }}" class="{{ 'mt-4' if not loop.first }}" data-tag="{{ s.tag }}"
|
|
onsubmit="var n=[].slice.call(this.querySelectorAll('input[name=names]:checked')).map(function(c){return c.value;}); if(!n.length){alert('Select at least one file.');return false;} return confirm('Install ' + n.join(', ') + ' from ' + this.dataset.tag + ' into the TFTP root? The current files are backed up first and every client that boots from now on gets the new ones.');">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="tag" value="{{ s.tag }}">
|
|
<div class="d-flex flex-wrap align-items-center gap-2 mb-2">
|
|
<strong class="mono">{{ s.tag }}</strong>
|
|
<span class="small text-muted">verified {{ s.staged | local_dt }} | tarball sha256 <span class="mono" title="{{ s.tarball_sha256 }}">{{ (s.tarball_sha256 or '')[:12] }}</span> matches GitHub</span>
|
|
</div>
|
|
<div class="table-container">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr><th style="width:2rem;"></th><th>File</th><th>From tarball</th><th>Version</th><th>sha256</th><th>Check</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for name, f in s.files.items() %}
|
|
<tr>
|
|
<td><input type="checkbox" class="form-check-input" name="names" value="{{ name }}" id="inst-{{ loop.index }}-{{ s.tag }}"
|
|
{% if not f.ok %}disabled{% elif name in default_install %}checked{% endif %}></td>
|
|
<td class="text-nowrap"><label for="inst-{{ loop.index }}-{{ s.tag }}" class="mono">{{ name }}</label>
|
|
{% if name not in default_install %}<div class="small text-muted">optional</div>{% endif %}</td>
|
|
<td class="mono small">{{ f.source or '-' }}</td>
|
|
<td class="mono">{{ f.version or '-' }}</td>
|
|
<td class="mono" title="{{ f.sha256 }}">{{ (f.sha256 or '')[:12] }}
|
|
{% if f.sha256 and f.sha256 == installed_sha[name] %}<div class="small text-muted">same as installed</div>{% endif %}</td>
|
|
<td class="wrap">
|
|
{% if f.ok %}<span class="badge badge-success">OK</span> {% if f.kind %}{{ sig_badge(f) }}{% endif %}
|
|
{% else %}<span class="badge badge-danger">Failed</span> <div class="small">{{ f.error }}</div>{% endif %}
|
|
{% if f.signers is defined %}{{ chain(f) }}{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="d-flex flex-wrap align-items-center gap-2 mt-2">
|
|
<button type="submit" class="pxe-btn pxe-btn-primary"><i class="bi bi-box-arrow-in-down"></i> Install selected</button>
|
|
<span class="form-text">ipxe-shim.efi is only installed together with ipxe.efi from the same release. ipxe.pxe is for legacy BIOS clients.</span>
|
|
</div>
|
|
</form>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# ---- dnsmasq routing ----------------------------------------------------- #}
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-diagram-3"></i> Who gets which file (dnsmasq, read-only)</div>
|
|
{% if routing.boots %}
|
|
<div class="table-container">
|
|
<table class="data-table">
|
|
<thead><tr><th>Boot file</th><th>Requires tag</th><th>Unless tagged</th><th>Clients</th></tr></thead>
|
|
<tbody>
|
|
{% for b in routing.boots %}
|
|
<tr>
|
|
<td class="mono">{{ b.file }}
|
|
{% if b.managed %}<span class="badge badge-info">updated here</span>{% elif b.read_only %}<span class="badge badge-secondary">never touched</span>
|
|
{% elif b.other_copy %}<span class="badge badge-warning" title="This page installs into the TFTP root only; this copy in a subfolder is not updated">other copy, not updated here</span>{% endif %}</td>
|
|
<td class="mono">{{ b.need | join(', ') or '-' }}</td>
|
|
<td class="mono">{{ b.exclude | join(', ') or '-' }}</td>
|
|
<td class="wrap">
|
|
{% if b.macs %}<span class="mono">{{ b.macs | join(', ') }}</span>
|
|
{% elif b.need %}<span class="text-muted">clients matching {{ b.need | join(' and ') }}</span>
|
|
{% else %}<span class="text-muted">every client not excluded</span>{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<details class="mt-2">
|
|
<summary class="eyebrow" style="cursor:pointer;">Relevant dnsmasq lines ({{ routing.lines | length }})</summary>
|
|
<pre class="mono mb-0 mt-2" style="white-space: pre-wrap; background: var(--bg); padding: 0.75rem; border-radius: 0.25rem;">{% for l in routing.lines %}{{ l }}
|
|
{% endfor %}</pre>
|
|
</details>
|
|
{% else %}
|
|
<p class="text-muted mb-0">No dhcp-boot lines found in the dnsmasq configuration.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# ---- Backups ------------------------------------------------------------- #}
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-clock-history"></i> Backups</div>
|
|
{% if backups %}
|
|
<div class="table-container">
|
|
<table class="data-table">
|
|
<thead><tr><th>Backup</th><th>Reason</th><th>Files</th><th class="text-end"></th></tr></thead>
|
|
<tbody>
|
|
{% for b in backups %}
|
|
<tr>
|
|
<td class="mono text-nowrap">{{ b.id }}</td>
|
|
<td>{{ b.reason or '-' }}</td>
|
|
<td>
|
|
{% for f in b.files %}<div class="small"><span class="mono">{{ f }}</span> <span class="text-muted">{{ b.versions[f] or '' }}</span></div>{% endfor %}
|
|
{% if not b.files %}<span class="text-muted">empty</span>{% endif %}
|
|
</td>
|
|
<td class="text-end text-nowrap">
|
|
{% if b.files %}
|
|
{# The id is a folder name (hand-made backups too): keep it out of the JS source. #}
|
|
<form method="post" action="{{ url_for('ipxe.rollback') }}" style="display:inline;"
|
|
data-confirm="Put back {{ b.files | join(', ') }} from backup {{ b.id }}? The current files are backed up first."
|
|
onsubmit="return confirm(this.dataset.confirm);">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="backup" value="{{ b.id }}">
|
|
<button type="submit" class="pxe-btn pxe-btn-ghost btn-row-action"><i class="bi bi-arrow-counterclockwise"></i> Rollback</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted mb-0">No backups yet. One is made automatically before every install and rollback.</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# ---- How-to -------------------------------------------------------------- #}
|
|
<div class="section-card">
|
|
<div class="section-title"><i class="bi bi-journal-text"></i> How to find and install the latest signed build by hand</div>
|
|
<p>Use this when the buttons above cannot reach GitHub, or to check what they do. All commands run on the PXE server as root.</p>
|
|
<ol>
|
|
<li class="mb-3"><strong>Find the release.</strong> The latest release is at
|
|
<a href="{{ releases_page }}" target="_blank" rel="noopener" class="mono">{{ releases_page }}</a>. The signed boot files are the
|
|
asset <span class="mono">ipxeboot.tar.gz</span>; GitHub shows its sha256 next to the asset (or ask the API:
|
|
<span class="mono">curl -s https://api.github.com/repos/ipxe/ipxe/releases/latest | grep -B2 -A2 '"digest"'</span>).</li>
|
|
<li class="mb-3"><strong>Download and check the sha256.</strong> It must equal the digest on the release page.
|
|
<pre class="mono mt-2 mb-1" style="white-space: pre-wrap; background: var(--bg); padding: 0.75rem; border-radius: 0.25rem;">cd /tmp
|
|
curl -fLO {{ tarball_url }}
|
|
sha256sum ipxeboot.tar.gz</pre>
|
|
<span class="small text-muted">GitHub goes through Zscaler. A certificate error from curl means the Zscaler root CA is missing from
|
|
<span class="mono">/usr/local/share/ca-certificates/</span> (the playbook installs it).</span></li>
|
|
<li class="mb-3"><strong>Unpack.</strong> The Secure Boot files are in <span class="mono">ipxeboot/x86_64-sb/</span>:
|
|
<span class="mono">shimx64.efi</span> (the shim), <span class="mono">ipxe.efi</span> and <span class="mono">snponly.efi</span>.
|
|
<span class="mono">ipxe-shim.efi</span> and <span class="mono">snponly-shim.efi</span> there are only symlinks to
|
|
<span class="mono">shimx64.efi</span>. The legacy BIOS build is <span class="mono">ipxeboot/x86_64/ipxe.pxe</span>.
|
|
<pre class="mono mt-2 mb-0" style="white-space: pre-wrap; background: var(--bg); padding: 0.75rem; border-radius: 0.25rem;">tar xzf ipxeboot.tar.gz
|
|
ls -l ipxeboot/x86_64-sb/</pre></li>
|
|
<li class="mb-3"><strong>Check the signatures.</strong>
|
|
<pre class="mono mt-2 mb-1" style="white-space: pre-wrap; background: var(--bg); padding: 0.75rem; border-radius: 0.25rem;">sbverify --list ipxeboot/x86_64-sb/shimx64.efi
|
|
sbverify --list ipxeboot/x86_64-sb/ipxe.efi</pre>
|
|
The shim must be issued by <span class="mono">CN=Microsoft Corporation UEFI CA 2011</span> or, on a newer build,
|
|
<span class="mono">CN=Microsoft UEFI CA 2023</span> (prefer a 2023-signed shim when upstream ships one).
|
|
<span class="mono">ipxe.efi</span> must show <span class="mono">CN=iPXE Secure Boot Intermediate ...</span> issued by
|
|
<span class="mono">CN=iPXE Secure Boot CA</span>. The shim's <span class="mono">warning: data remaining ... gaps between PE/COFF sections?</span>
|
|
is normal. Do not rely on <span class="mono">sbverify --cert</span>: it reports OK even for the wrong certificate.</li>
|
|
<li class="mb-3"><strong>Back up the current files.</strong> A backup made in this folder also shows in the Backups list above, with Rollback.
|
|
<pre class="mono mt-2 mb-0" style="white-space: pre-wrap; background: var(--bg); padding: 0.75rem; border-radius: 0.25rem;">B=/var/lib/pxe-webapp/updates/ipxe/backups/$(date +%Y%m%d-%H%M%S)
|
|
mkdir -p "$B" && cp -p /srv/tftp/ipxe-shim.efi /srv/tftp/ipxe.efi "$B"/</pre></li>
|
|
<li class="mb-3"><strong>Install both files, from the same release.</strong> The shim only starts an <span class="mono">ipxe.efi</span> signed under the
|
|
CA it carries, and it always loads <span class="mono">ipxe.efi</span> from the TFTP root, so the name must stay
|
|
<span class="mono">ipxe.efi</span>. Copy to a temporary name and rename, so a client that is booting never reads half a file:
|
|
<pre class="mono mt-2 mb-1" style="white-space: pre-wrap; background: var(--bg); padding: 0.75rem; border-radius: 0.25rem;">install -m 0644 ipxeboot/x86_64-sb/ipxe.efi /srv/tftp/.ipxe.efi.new && mv -f /srv/tftp/.ipxe.efi.new /srv/tftp/ipxe.efi
|
|
install -m 0644 ipxeboot/x86_64-sb/shimx64.efi /srv/tftp/.ipxe-shim.efi.new && mv -f /srv/tftp/.ipxe-shim.efi.new /srv/tftp/ipxe-shim.efi</pre>
|
|
<span class="small text-muted">No dnsmasq restart is needed. Never replace <span class="mono">ipxe-bcm.efi</span>; it serves every other UEFI client.</span></li>
|
|
<li class="mb-3"><strong>Who gets it.</strong> dnsmasq decides, in <span class="mono">/etc/dnsmasq.conf</span>: clients listed as
|
|
<span class="mono">dhcp-host=<mac>,set:ipxe2</span> get the shim through
|
|
<span class="mono">dhcp-boot=tag:ipxe2,tag:!ipxe,tag:!bios,ipxe-shim.efi</span>; every other UEFI client gets
|
|
<span class="mono">ipxe-bcm.efi</span> through <span class="mono">dhcp-boot=tag:!bios,tag:!ipxe2,ipxe-bcm.efi</span>. The table above shows the
|
|
live lines. Test on one tagged machine before tagging more.</li>
|
|
<li class="mb-3"><strong>Roll back.</strong> Use Rollback above, or copy the backed-up files back the same way (temporary name, then
|
|
<span class="mono">mv -f</span>).</li>
|
|
<li><strong>Normal log noise.</strong> While booting, the shim asks TFTP for optional files such as
|
|
<span class="mono">revocations_*.efi</span> and <span class="mono">shim_certificate_0.efi</span>. The "not found" lines for them in the
|
|
dnsmasq log are expected and harmless.</li>
|
|
</ol>
|
|
</div>
|
|
|
|
{% if job.state in ('queued', 'running') %}
|
|
<script>
|
|
// Poll the background download; reload once it finishes to show the result.
|
|
(function () {
|
|
var URL = "{{ url_for('ipxe.status') }}";
|
|
var text = document.getElementById('ipxeJobText');
|
|
var bar = document.getElementById('ipxeJobBar');
|
|
function tick() {
|
|
fetch(URL, { credentials: 'same-origin' })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (j) {
|
|
if (j.state !== 'queued' && j.state !== 'running') { location.reload(); return; }
|
|
var step = j.step === 'download' ? 'Downloading' : j.step === 'verify' ? 'Verifying' : 'Starting';
|
|
if (text) text.textContent = step + ' ' + (j.tag || '') + (j.step === 'download' && j.percent != null ? ' (' + j.percent + '%)' : '') + '...';
|
|
if (bar) bar.style.width = (j.step === 'verify' ? 100 : (j.percent || 0)) + '%';
|
|
setTimeout(tick, 2000);
|
|
})
|
|
.catch(function () { setTimeout(tick, 5000); });
|
|
}
|
|
setTimeout(tick, 1500);
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
{% endblock %}
|