diff --git a/webapp/services/ipxe_update.py b/webapp/services/ipxe_update.py index bf9c5c1..19ff459 100644 --- a/webapp/services/ipxe_update.py +++ b/webapp/services/ipxe_update.py @@ -1,13 +1,25 @@ """Keep the Secure Boot iPXE chain on the TFTP root current. What dnsmasq serves (see /etc/dnsmasq.conf): - * ipxe-shim.efi - Microsoft-signed shim (upstream x86_64-sb/shimx64.efi), - for the MACs tagged ipxe2; it always loads ipxe.efi from - the TFTP ROOT, whatever folder it came from; - * ipxe.efi - iPXE signed by the iPXE Secure Boot CA, loaded by the shim; - * ipxe-bcm.efi - the old 2021 Broadcom-signed build, everyone else. - Never touched here; - * ipxe.pxe - legacy BIOS iPXE (unsigned; BIOS has no Secure Boot). + * ipxe-shim.efi - Microsoft-signed shim, for the MACs tagged ipxe2. Over + TFTP it always loads ipxe.efi from the TFTP root (it + does not derive the name from its own, whatever the + upstream docs say); + * snponly-shim.efi - the same shim under the name dnsmasq hands the + ipxesnp-tagged MACs; + * ipxe.efi - iPXE signed by the iPXE Secure Boot CA, loaded by the + shim. The full build drives USB itself, which kills a + USB keyboard at the iPXE menu; + * snponly.efi - the same release built to use the firmware's network + and USB drivers (keyboard keeps working). Served to a + client as ipxe.efi from its per-MAC folder; + * ipxe-bcm.efi - the old 2021 Broadcom-signed build, everyone else. + Never touched here; + * ipxe.pxe - legacy BIOS iPXE (unsigned; BIOS has no Secure Boot). + +Per-MAC folders: with tftp-unique-root=mac, /srv/tftp// +wins over the root for that client, with NO fallback for a file missing +from it. They are shown here read-only and never written. Upstream publishes the signed set as one tarball on every release: https://github.com/ipxe/ipxe/releases/latest/download/ipxeboot.tar.gz, with @@ -18,10 +30,13 @@ The chain check is by certificate name (sbverify --list); the cryptographic trust comes from the GitHub digest. Do not swap in `sbverify --cert`: it prints "Signature verification OK" even for a missing or unrelated cert. -Why it matters: the shim and the Broadcom build are signed by Microsoft's -UEFI CA 2011, whose signing period ended in 2026. Machines that only trust -the 2023 CA, or a revocation (DBX/SBAT) update, will refuse them; the fix is -a newer upstream release, installed here. +Why it matters: the Broadcom build, and the shim in the iPXE tarball, are +signed only by Microsoft's UEFI CA 2011, whose signing period ended in 2026. +Machines that only trust the 2023 CA, or a revocation (DBX/SBAT) update, +will refuse them. iPXE publishes a shim dual-signed with the 2011 and 2023 +CAs as a separate release (github.com/ipxe/shim); staging fetches it, checks +its digest and signatures, and uses it instead of the tarball's shim when it +trusts the same iPXE CA. Staging runs as a background job (python3 -m services.ipxe_update stage); install and rollback are a few MB of copying and run inline. @@ -45,6 +60,8 @@ import config from services import updates_common as uc GITHUB_API = "https://api.github.com/repos/ipxe/ipxe/releases/latest" +SHIM_API = "https://api.github.com/repos/ipxe/shim/releases/latest" +SHIM_ASSET = "ipxe-shimx64.efi" RELEASES_PAGE = "https://github.com/ipxe/ipxe/releases" LATEST_TARBALL_URL = "https://github.com/ipxe/ipxe/releases/latest/download/ipxeboot.tar.gz" TARBALL = "ipxeboot.tar.gz" @@ -54,11 +71,14 @@ JOB_UNIT = "pxe-webapp-ipxe-stage" # Secure Boot signature (and from whom). MANAGED = { "ipxe-shim.efi": ("ipxeboot/x86_64-sb/shimx64.efi", "microsoft"), + "snponly-shim.efi": ("ipxeboot/x86_64-sb/shimx64.efi", "microsoft"), "ipxe.efi": ("ipxeboot/x86_64-sb/ipxe.efi", "ipxe"), + "snponly.efi": ("ipxeboot/x86_64-sb/snponly.efi", "ipxe"), "ipxe.pxe": ("ipxeboot/x86_64/ipxe.pxe", None), } +SHIMS = ("ipxe-shim.efi", "snponly-shim.efi") # Installed by default; ipxe.pxe (legacy BIOS) only when asked. -DEFAULT_INSTALL = ("ipxe-shim.efi", "ipxe.efi") +DEFAULT_INSTALL = ("ipxe-shim.efi", "snponly-shim.efi", "ipxe.efi", "snponly.efi") # Shown on the page for reference, never written. READ_ONLY = ("ipxe-bcm.efi",) @@ -193,6 +213,20 @@ def vendor_cert_cn(path): return None +def drivers_of(path): + """'native' when an iPXE build carries its own USB host drivers (it takes + the USB controller from the firmware, so a USB keyboard stops working), + 'firmware' when it has none (snponly), '' for anything else.""" + try: + with open(path, "rb") as f: + data = f.read() + except OSError: + return "" + if b"iPXE" not in data or b"$Version: " in data: + return "" # not iPXE, or a shim + return "native" if re.search(rb"\0xhci\0|\0ehci\0", data) else "firmware" + + def describe(path): if not os.path.isfile(path): return None @@ -210,6 +244,7 @@ def describe(path): "sbat": sbat_generations(path) if efi else "", "signers": pairs, "kind": (signature_kind(pairs) if not error else "error") if efi else "bios", "trusts": vendor_cert_cn(path) if efi else None, + "drivers": drivers_of(path) if efi else "", "sig_error": error, } @@ -229,15 +264,46 @@ def installed(): if info and origin.get("sha256") == info["sha256"]: info["release"] = origin.get("tag") rows.append({"name": name, "managed": name in MANAGED, "info": info}) - # The shim only starts an ipxe.efi signed under the CA it embeds. + # The shim only starts an iPXE signed under the CA it embeds. by = {r["name"]: r["info"] for r in rows} - shim, ipxe = by.get("ipxe-shim.efi"), by.get("ipxe.efi") - if shim and ipxe and shim.get("trusts"): - ipxe["chain_ok"] = shim["trusts"] in {i for _, i in ipxe["signers"]} - ipxe["trusts_needed"] = shim["trusts"] + _check_chain(by.get("ipxe-shim.efi") or by.get("snponly-shim.efi"), + [by.get("ipxe.efi"), by.get("snponly.efi")]) return rows +def _check_chain(shim, ipxes): + for ipxe in ipxes: + if shim and ipxe and shim.get("trusts"): + ipxe["chain_ok"] = shim["trusts"] in {i for _, i in ipxe["signers"]} + ipxe["trusts_needed"] = shim["trusts"] + + +MAC_DIR_RE = re.compile(r"^[0-9a-f]{2}(-[0-9a-f]{2}){5}$") + + +def mac_folders(): + """Per-client TFTP folders (tftp-unique-root=mac): [{mac, files}], each + file described. Read-only; install never writes into them.""" + out = [] + try: + names = sorted(os.listdir(config.TFTP_ROOT)) + except OSError: + return out + for d in names: + folder = os.path.join(config.TFTP_ROOT, d) + if not (MAC_DIR_RE.match(d) and os.path.isdir(folder)): + continue + files = [] + for f in sorted(os.listdir(folder)): + info = describe(os.path.join(folder, f)) + if info: + files.append({"name": f, "info": info}) + by = {f["name"]: f["info"] for f in files} + _check_chain(next((by[n] for n in SHIMS if n in by), None), [by.get("ipxe.efi")]) + out.append({"mac": d.replace("-", ":"), "dir": d, "files": files}) + return out + + # --- dnsmasq ---------------------------------------------------------------- MAC_RE = re.compile(r"^(?:[0-9a-fA-F*]{1,2}[:-]){5}[0-9a-fA-F*]{1,2}$") @@ -322,7 +388,7 @@ def dnsmasq_routing(): boots.append({"file": rest[0] if rest else "", "need": need, "exclude": exclude, "line": s, "source": src}) if key in ("dhcp-boot", "dhcp-host", "dhcp-match", "dhcp-userclass", "dhcp-vendorclass", - "tag-if", "enable-tftp", "tftp-root"): + "tag-if", "enable-tftp", "tftp-root", "tftp-unique-root"): if key != "dhcp-host" or "set:" in val: raw.append(s) for b in boots: @@ -334,7 +400,8 @@ def dnsmasq_routing(): b["read_only"] = rel in READ_ONLY b["other_copy"] = rel not in MANAGED and os.path.basename(rel) in MANAGED return {"tags": tag_macs, "boots": boots, "lines": raw, - "shim_macs": sorted({m for b in boots if os.path.basename(b["file"]) == "ipxe-shim.efi" + "unique_root": any(s.startswith("tftp-unique-root") for s in raw), + "shim_macs": sorted({m for b in boots if os.path.basename(b["file"]) in SHIMS for m in b["macs"]})} @@ -459,17 +526,59 @@ def stage(info=None): else: ok, error = True, "" files[name] = dict(d, ok=ok, error=error, source=inner) - # The shim from this release must trust the CA its ipxe.efi is signed by. - shim, ipxe = files.get("ipxe-shim.efi") or {}, files.get("ipxe.efi") or {} - if shim.get("ok") and ipxe.get("ok") and shim.get("trusts"): - if shim["trusts"] not in {i for _, i in ipxe["signers"]}: - ipxe.update(ok=False, error=f"not signed under {shim['trusts']}, which this shim trusts") + _progress(step="shim") + _use_dual_signed_shim(folder, files) + # The shim must trust the CA this release's iPXE builds are signed by. + shim = files.get("ipxe-shim.efi") or {} + for name in ("ipxe.efi", "snponly.efi"): + ipxe = files.get(name) or {} + if shim.get("ok") and ipxe.get("ok") and shim.get("trusts"): + if shim["trusts"] not in {i for _, i in ipxe["signers"]}: + ipxe.update(ok=False, error=f"not signed under {shim['trusts']}, which this shim trusts") record = {"tag": info["tag"], "published": info.get("published"), "folder": folder, "tarball_sha256": info["sha256"], "files": files, "staged": uc.now()} uc.write_json(os.path.join(folder, "staged.json"), record) return record +def _use_dual_signed_shim(folder, files): + """Swap the tarball's shim for the ipxe/shim release build when that one + is signed under the Microsoft UEFI CA 2023 and trusts the same iPXE CA. + Any problem leaves the tarball shim in place, with the reason noted.""" + tar_shim = files.get("ipxe-shim.efi") or {} + if not tar_shim.get("ok") or tar_shim.get("kind") == "microsoft-2023": + return + try: + rel = json.loads(uc.fetch_text(SHIM_API, timeout=20)) + asset = next((a for a in rel.get("assets", []) if a.get("name") == SHIM_ASSET), None) + digest = str((asset or {}).get("digest") or "") + if not asset or not digest.startswith("sha256:"): + raise RuntimeError(f"{rel.get('tag_name')} lists no sha256 for {SHIM_ASSET}") + sha = digest.split(":", 1)[1].lower() + dest = os.path.join(folder, SHIM_ASSET) + if not (os.path.isfile(dest) and uc.file_hash(dest).lower() == sha): + if os.path.exists(dest): + os.remove(dest) + uc.download(asset["browser_download_url"], dest, expect=sha, timeout=300) + d = describe(dest) + if d["kind"] != "microsoft-2023": + raise RuntimeError(f"{SHIM_ASSET} is {d['kind']}, not signed under {MS_CA_2023}") + if d.get("trusts") != tar_shim.get("trusts"): + raise RuntimeError(f"{SHIM_ASSET} trusts {d.get('trusts')}, the tarball's iPXE needs " + f"{tar_shim.get('trusts')}") + except Exception as ex: # optional upgrade: keep the tarball shim + note = f"dual-signed shim not used: {str(ex)[:200]}" + for n in SHIMS: + if n in files: + files[n]["note"] = note + return + for n in SHIMS: + if n in files: + shutil.copy2(dest, os.path.join(folder, n)) + files[n] = dict(d, ok=True, error="", source=f"github.com/ipxe/shim {rel.get('tag_name')} " + f"{SHIM_ASSET} (dual-signed, 2011 + 2023)") + + def staged(tag=None): root = os.path.join(_dir(), "staged") if not os.path.isdir(root): @@ -647,8 +756,8 @@ def install(tag, names=DEFAULT_INSTALL): names = [n for n in MANAGED if n in names] if not names: raise ValueError("nothing selected") - if "ipxe-shim.efi" in names and "ipxe.efi" not in names: - raise ValueError("install ipxe-shim.efi together with ipxe.efi") + if any(n in names for n in SHIMS) and "ipxe.efi" not in names: + raise ValueError("install a shim together with ipxe.efi") if uc.job_running(JOB_UNIT): raise RuntimeError("a download is running; wait for it to finish") with _Lock(): @@ -663,13 +772,16 @@ def _install(tag, rec, names): path = os.path.join(rec["folder"], n) if not os.path.isfile(path) or uc.file_hash(path) != f["sha256"]: raise ValueError(f"{n}: staged copy changed since it was verified; download again") - if "ipxe.efi" in names and "ipxe-shim.efi" not in names: - # ipxe.efi alone must still be one the installed shim will start. + if not any(n in names for n in SHIMS): + # iPXE alone must still be one the installed shim will start. trusts = vendor_cert_cn(os.path.join(config.TFTP_ROOT, "ipxe-shim.efi")) - issuers = {i for _, i in rec["files"]["ipxe.efi"].get("signers", [])} - if trusts and trusts not in issuers: - raise ValueError(f"the installed shim trusts {trusts}; this ipxe.efi is not signed " - "under it, install the shim from the same release too") + for n in ("ipxe.efi", "snponly.efi"): + if n not in names: + continue + issuers = {i for _, i in rec["files"][n].get("signers", [])} + if trusts and trusts not in issuers: + raise ValueError(f"the installed shim trusts {trusts}; this {n} is not signed " + "under it, install the shim from the same release too") backup = _backup(names, f"before installing {tag}") _place_all([(os.path.join(rec["folder"], n), n, rec["files"][n]["sha256"]) for n in names]) log = _installed_log() diff --git a/webapp/templates/updates_ipxe.html b/webapp/templates/updates_ipxe.html index c5bff3e..b887e8b 100644 --- a/webapp/templates/updates_ipxe.html +++ b/webapp/templates/updates_ipxe.html @@ -37,6 +37,8 @@ {% endif %} {% if info.trusts %}
Starts second stages signed by {{ info.trusts }}
{% endif %} {% if info.sbat %}
SBAT: {{ info.sbat }}
{% endif %} + {% if info.drivers == 'native' %}
This iPXE build drives USB itself: it takes the USB controller from the firmware, so a USB keyboard stops working at the iPXE menu. snponly.efi uses the firmware's drivers instead.
+ {% elif info.drivers == 'firmware' %}
Uses the firmware's network and USB drivers, so USB keyboards keep working.
{% endif %} {%- endmacro %} {% block extra_head %} @@ -162,11 +164,14 @@
What this page manages

- The Secure Boot chain that dnsmasq hands out over TFTP: the Microsoft-signed shim (ipxe-shim.efi) - starts ipxe.efi, 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 sbverify before it can be installed. ipxe-bcm.efi, - dnsmasq.conf and the Blancco files are never changed here. + The Secure Boot chain that dnsmasq hands out over TFTP: the Microsoft-signed shim (ipxe-shim.efi, + and the same file as snponly-shim.efi) starts ipxe.efi, which is signed by the + iPXE Secure Boot CA. snponly.efi is the same release built on the firmware's drivers (USB keyboards + keep working). Updates come from the upstream iPXE release tarball on GitHub, plus the dual-signed shim (Microsoft UEFI CA + 2011 and 2023) from the separate ipxe/shim release; each download's sha256 is checked against GitHub's published digest and + each file's signer chain is read with sbverify before it can be installed. + ipxe-bcm.efi, the per-client folders, dnsmasq.conf and the Blancco files are + never changed here.

@@ -203,6 +208,42 @@ {% endfor %} +{# ---- Per-client folders ---------------------------------------------------- #} +{% if mac_folders %} +
+

Per-client folders

+
With tftp-unique-root=mac, a client whose MAC has a folder gets every file from it, with no fallback to the root. Read-only here: installs do not update them.
+ {% for m in mac_folders %} +
{{ m.mac }}/srv/tftp/{{ m.dir }}/ + {{ m.files | length }} file{{ 's' if m.files | length != 1 }}
+ {% for f in m.files %} + {% set i = f.info %} + {% set chain_ok = i.chain_ok if i.chain_ok is defined else none %} +
+
+ + {{ f.name }}{% if i.drivers == 'firmware' and f.name == 'ipxe.efi' %}snponly build{% endif %} + {{ i.version or '-' }} + {% if chain_ok == false %}{{ ui.chip('Shim will refuse', 'err') }}{% else %}{{ sig_chip(i) }}{% endif %} + + +
+ {{ sig_text(i, chain_ok) }} +
+
Path
/srv/tftp/{{ m.dir }}/{{ f.name }}
+
Size
{{ "{:,}".format(i.size) }} B
+
Modified
{{ i.modified | timestamp_fmt }}
+
sha256
{{ i.sha256 }}
+
+ {{ chain(i) }} +
+
+
+ {% endfor %} + {% endfor %} +
+{% endif %} + {# ---- Upstream ------------------------------------------------------------ #}

Upstream release

@@ -240,7 +281,7 @@ {% if staged %}

Downloaded and verified

-
Tick the files to install. ipxe-shim.efi is only installed together with ipxe.efi from the same release; ipxe.pxe is for legacy BIOS clients.
+
Tick the files to install. A shim is only installed together with ipxe.efi from the same release; ipxe.pxe is for legacy BIOS clients.
{% for s in staged %}
@@ -266,8 +307,9 @@
{% if not f.ok %}
{{ f.error }}
{% elif f.kind %}{{ sig_text(f) }}{% endif %} + {% if f.note %}
{{ f.note }}
{% endif %}
-
From tarball
{{ f.source or '-' }}
+
Source
{{ f.source or '-' }}
{% if f.sha256 %}
sha256
{{ f.sha256 }}
{% endif %}
{% if f.signers is defined %}{{ chain(f) }}{% endif %} @@ -288,6 +330,7 @@

Who gets which file

Read from the dnsmasq configuration; never changed here.
+ {% if routing.unique_root %}
dnsmasq has tftp-unique-root=mac: a client with its own folder (see Per-client folders) is served from it instead of the root, whatever file name this list shows.
{% endif %} {% if routing.boots %} {% for b in routing.boots %}
@@ -375,11 +418,14 @@
  • Find the release. The latest release is at {{ releases_page }}. The signed boot files are the asset ipxeboot.tar.gz; GitHub shows its sha256 next to the asset (or ask the API: - curl -s https://api.github.com/repos/ipxe/ipxe/releases/latest | grep -B2 -A2 '"digest"').
  • + curl -s https://api.github.com/repos/ipxe/ipxe/releases/latest | grep -B2 -A2 '"digest"'). + The tarball's shim is signed by Microsoft's 2011 CA only; the dual-signed shim (2011 and 2023) is the asset + ipxe-shimx64.efi of the separate ipxe/shim release.
  • Download and check the sha256. It must equal the digest on the release page.
    cd /tmp
     curl -fLO {{ tarball_url }}
    -sha256sum ipxeboot.tar.gz
    +curl -fLO https://github.com/ipxe/shim/releases/latest/download/ipxe-shimx64.efi +sha256sum ipxeboot.tar.gz ipxe-shimx64.efi GitHub goes through Zscaler. A certificate error from curl means the Zscaler root CA is missing from /usr/local/share/ca-certificates/ (the playbook installs it).
  • Unpack. The Secure Boot files are in ipxeboot/x86_64-sb/: @@ -389,27 +435,34 @@ sha256sum ipxeboot.tar.gz
    tar xzf ipxeboot.tar.gz
     ls -l ipxeboot/x86_64-sb/
  • Check the signatures. -
    sbverify --list ipxeboot/x86_64-sb/shimx64.efi
    -sbverify --list ipxeboot/x86_64-sb/ipxe.efi
    - The shim must be issued by CN=Microsoft Corporation UEFI CA 2011 or, on a newer build, - CN=Microsoft UEFI CA 2023 (prefer a 2023-signed shim when upstream ships one). +
    sbverify --list ipxe-shimx64.efi
    +sbverify --list ipxeboot/x86_64-sb/ipxe.efi
    +sbverify --list ipxeboot/x86_64-sb/snponly.efi
    + The dual-signed shim shows CN=Microsoft UEFI CA 2023 (sbverify lists the first signature only; + the 2011 one is nested inside it). Fall back to the tarball's shimx64.efi + (CN=Microsoft Corporation UEFI CA 2011) only if the dual-signed one is unavailable. ipxe.efi must show CN=iPXE Secure Boot Intermediate ... issued by CN=iPXE Secure Boot CA. The shim's warning: data remaining ... gaps between PE/COFF sections? is normal. Do not rely on sbverify --cert: it reports OK even for the wrong certificate.
  • Back up the current files. A backup made in this folder also shows in the Backups list above, with Restore.
    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"/
  • +mkdir -p "$B" && cp -p /srv/tftp/ipxe-shim.efi /srv/tftp/snponly-shim.efi /srv/tftp/ipxe.efi /srv/tftp/snponly.efi "$B"/
  • Install both files, from the same release. The shim only starts an ipxe.efi signed under the CA it carries, and it always loads ipxe.efi from the TFTP root, so the name must stay ipxe.efi. Copy to a temporary name and rename, so a client that is booting never reads half a file: -
    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
    - No dnsmasq restart is needed. Never replace ipxe-bcm.efi; it serves every other UEFI client.
  • +
    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/snponly.efi  /srv/tftp/.snponly.efi.new      && mv -f /srv/tftp/.snponly.efi.new      /srv/tftp/snponly.efi
    +install -m 0644 ipxe-shimx64.efi                /srv/tftp/.ipxe-shim.efi.new    && mv -f /srv/tftp/.ipxe-shim.efi.new    /srv/tftp/ipxe-shim.efi
    +install -m 0644 ipxe-shimx64.efi                /srv/tftp/.snponly-shim.efi.new && mv -f /srv/tftp/.snponly-shim.efi.new /srv/tftp/snponly-shim.efi
    + No dnsmasq restart is needed. Never replace ipxe-bcm.efi; it serves every other UEFI client. + A per-client folder keeps its own copies: update its ipxe.efi (the snponly build) and shim the same way.
  • Who gets it. dnsmasq decides, in /etc/dnsmasq.conf: clients listed as dhcp-host=<mac>,set:ipxe2 get the shim through dhcp-boot=tag:ipxe2,tag:!ipxe,tag:!bios,ipxe-shim.efi; every other UEFI client gets - ipxe-bcm.efi through dhcp-boot=tag:!bios,tag:!ipxe2,ipxe-bcm.efi. Who gets which file - above shows the live lines. Test on one tagged machine before tagging more.
  • + ipxe-bcm.efi through the untagged dhcp-boot=tag:!bios,...,ipxe-bcm.efi line. Clients tagged + ipxesnp get snponly-shim.efi; since the shim asks for ipxe.efi + whatever it is called, each of them also needs a per-client folder whose ipxe.efi is the snponly build. + Who gets which file above shows the live lines. Test on one tagged machine before tagging more.
  • Roll back. Use Restore in the Backups list, or copy the backed-up files back the same way (temporary name, then mv -f).
  • Normal log noise. While booting, the shim asks TFTP for optional files such as @@ -433,7 +486,7 @@ install -m 0644 ipxeboot/x86_64-sb/shimx64.efi /srv/tftp/.ipxe-shim.efi.new & .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'; + var step = j.step === 'download' ? 'Downloading' : j.step === 'verify' ? 'Verifying' : j.step === 'shim' ? 'Fetching the dual-signed shim for' : '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); diff --git a/webapp/views/ipxe.py b/webapp/views/ipxe.py index f4ced5f..bbde839 100644 --- a/webapp/views/ipxe.py +++ b/webapp/views/ipxe.py @@ -32,6 +32,7 @@ def page(): rows=rows, installed_sha={n: (i or {}).get("sha256") for n, i in by_name.items()}, routing=ipxe_update.dnsmasq_routing(), + mac_folders=ipxe_update.mac_folders(), latest=latest, status=status, job=ipxe_update.job_state(),