Merge pull request 'plymouth-working' (#2) from plymouth-working into main
Reviewed-on: https://git.bitasylum.org/audiobuzz/diskless-workstation/pulls/2
This commit is contained in:
@@ -49,8 +49,16 @@ env:
|
|||||||
# per-environment DNS name (e.g. nvme.fleet.lab) resolved at boot so one UKI
|
# per-environment DNS name (e.g. nvme.fleet.lab) resolved at boot so one UKI
|
||||||
# works across environments — the serving stack's dnsmasq resolves it.
|
# works across environments — the serving stack's dnsmasq resolves it.
|
||||||
NVMET_TRADDR: "192.168.50.10"
|
NVMET_TRADDR: "192.168.50.10"
|
||||||
# URL the fullscreen Chromium kiosk loads at boot. Empty disables the kiosk.
|
# URL the fullscreen Chromium kiosk loads at boot. Defaults to the test page
|
||||||
KIOSK_URL: "https://www.reddit.com"
|
# served by the boot HTTP server (serving/http/kiosk) at /kiosk/; boot.fleet.lab
|
||||||
|
# resolves to the serving host via the stack's dnsmasq. Empty disables the kiosk.
|
||||||
|
KIOSK_URL: "http://boot.fleet.lab/kiosk/"
|
||||||
|
# Plymouth boot splash. "false" => verbose console boot (rhgb quiet NOT baked;
|
||||||
|
# plymouth.enable=0 instead) — kept OFF in CI while the network-manager port is
|
||||||
|
# being bench-verified, so the boot chain stays debuggable. Flip to "true" to
|
||||||
|
# ship the graphical splash once boot is trusted. (Plymouth is in the image
|
||||||
|
# either way; this only flips the cmdline.)
|
||||||
|
BOOT_SPLASH: "true"
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# TEMPORARY: 802.1X + MACsec are DISABLED for this build (rd.dot1x=0) so the
|
# TEMPORARY: 802.1X + MACsec are DISABLED for this build (rd.dot1x=0) so the
|
||||||
# UKI / rootfs / NVMe-oF boot path can be validated on an OPEN switch port.
|
# UKI / rootfs / NVMe-oF boot path can be validated on an OPEN switch port.
|
||||||
@@ -178,4 +186,5 @@ jobs:
|
|||||||
-var nvmet_traddr="${{ env.NVMET_TRADDR }}" \
|
-var nvmet_traddr="${{ env.NVMET_TRADDR }}" \
|
||||||
-var ssh_authorized_key="${{ secrets.SSH_AUTHORIZED_KEY }}" \
|
-var ssh_authorized_key="${{ secrets.SSH_AUTHORIZED_KEY }}" \
|
||||||
-var kiosk_url="${{ env.KIOSK_URL }}" \
|
-var kiosk_url="${{ env.KIOSK_URL }}" \
|
||||||
|
-var boot_splash=${{ env.BOOT_SPLASH }} \
|
||||||
.
|
.
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ files/etc/dot1x/ Supplicant config + RADIUS CA (placeho
|
|||||||
files/etc/dracut.conf.d/90-diskless.conf no-hostonly + drivers + modules
|
files/etc/dracut.conf.d/90-diskless.conf no-hostonly + drivers + modules
|
||||||
(NM unmanaged split is now runtime:
|
(NM unmanaged split is now runtime:
|
||||||
95dot1x-macsec/nm-unmanaged.sh, secure-mode only)
|
95dot1x-macsec/nm-unmanaged.sh, secure-mode only)
|
||||||
|
files/plymouth/themes/fleet/ Custom boot splash (scripted security
|
||||||
|
checklist; driven by hook status tokens)
|
||||||
|
files/kiosk/ weston+Chromium kiosk + chromium lockdown policy
|
||||||
scripts/extract-artifacts.sh qcow2 -> kernel/initramfs/erofs+verity/cmdline
|
scripts/extract-artifacts.sh qcow2 -> kernel/initramfs/erofs+verity/cmdline
|
||||||
scripts/build-oci.sh ukify + sbsign + roothash gate + OCI image
|
scripts/build-oci.sh ukify + sbsign + roothash gate + OCI image
|
||||||
serving/ Lab serving stack: dnsmasq + nginx + SPDK
|
serving/ Lab serving stack: dnsmasq + nginx + SPDK
|
||||||
@@ -149,6 +152,13 @@ instead of working around it.
|
|||||||
build; the supplicant config carries both options.
|
build; the supplicant config carries both options.
|
||||||
- erofs module availability on the target RHEL kernel (fallback:
|
- erofs module availability on the target RHEL kernel (fallback:
|
||||||
journal-less ext4 in `extract-artifacts.sh`).
|
journal-less ext4 in `extract-artifacts.sh`).
|
||||||
|
- `fleet` Plymouth theme: confirm (a) the script plugin package actually
|
||||||
|
installed (`/usr/lib64/plymouth/script.so` — else the build fell back to
|
||||||
|
`spinner`, logged as a WARNING), (b) the scripted theme + a font got bundled
|
||||||
|
into the initramfs (`lsinitrd | grep -E 'fleet.script|script.so'`), and (c) it
|
||||||
|
renders (text via `Image.Text` needs the label plugin + a font in the early
|
||||||
|
initramfs). If blank, the splash silently no-ops but boot continues; ESC shows
|
||||||
|
the log. Untested in this workspace (needs a display).
|
||||||
- `systemd.verity_root_options=hash-offset` is **NOT supported** by Rocky 9.8
|
- `systemd.verity_root_options=hash-offset` is **NOT supported** by Rocky 9.8
|
||||||
systemd-veritysetup (verified: "unknown option hash-offset=" → "Failed to load
|
systemd-veritysetup (verified: "unknown option hash-offset=" → "Failed to load
|
||||||
verity superblock"). Resolved by the documented fallback — data and hash are
|
verity superblock"). Resolved by the documented fallback — data and hash are
|
||||||
@@ -161,9 +171,10 @@ instead of working around it.
|
|||||||
`network-manager` module hands its initramfs connections to the real-root NM
|
`network-manager` module hands its initramfs connections to the real-root NM
|
||||||
(state under `/run/NetworkManager`), which renews the lease. No more
|
(state under `/run/NetworkManager`), which renews the lease. No more
|
||||||
`dhcp-client`/`dhclient` (deprecated). The hook drives macsec0 DHCP via `nmcli`
|
`dhcp-client`/`dhclient` (deprecated). The hook drives macsec0 DHCP via `nmcli`
|
||||||
(not `ifup`). See invariant on the managed/unmanaged split. BENCH-VERIFY: lease
|
(not `ifup`). See invariant on the managed/unmanaged split. Test path
|
||||||
actually renews past expiry (test path); macsec0 managed + `en*` unmanaged with
|
VERIFIED on the bench 2026-06-17 (lease renews past expiry; host stays up).
|
||||||
MKA surviving switch-root (secure path).
|
STILL BENCH-VERIFY when `disable_dot1x=false`: macsec0 managed + `en*`
|
||||||
|
unmanaged with MKA surviving switch-root (secure path).
|
||||||
- Writable root overlay is `systemd.volatile=overlay`, **not** `rootovl`:
|
- Writable root overlay is `systemd.volatile=overlay`, **not** `rootovl`:
|
||||||
RHEL/Rocky dracut ships no `overlayfs`/`rootovl` module. Needs
|
RHEL/Rocky dracut ships no `overlayfs`/`rootovl` module. Needs
|
||||||
`systemd-volatile-root.service` in the initramfs (bundled by recent dracut;
|
`systemd-volatile-root.service` in the initramfs (bundled by recent dracut;
|
||||||
@@ -194,6 +205,23 @@ These bit us bringing the build up on Rocky 9.8; they are easy to regress.
|
|||||||
stack stays in the image, dormant. The matching lab DHCP/HTTP/NVMe-oF serving
|
stack stays in the image, dormant. The matching lab DHCP/HTTP/NVMe-oF serving
|
||||||
stack lives in `serving/` (docker compose). Re-enable security by dropping the
|
stack lives in `serving/` (docker compose). Re-enable security by dropping the
|
||||||
var / setting `DISABLE_DOT1X: "false"` in the CI workflow.
|
var / setting `DISABLE_DOT1X: "false"` in the CI workflow.
|
||||||
|
- **Boot splash toggle**: `boot_splash=true` (default) bakes `rhgb quiet` for the
|
||||||
|
Plymouth splash; `false` bakes `plymouth.enable=0` for a verbose console boot —
|
||||||
|
keep OFF while debugging the boot chain. Plymouth is always in the image +
|
||||||
|
initramfs (the `plymouth` dracut module, which pulls `drm`); only the cmdline
|
||||||
|
changes. Press ESC at boot to see details behind a splash. ks.cfg no longer
|
||||||
|
excludes `plymouth`.
|
||||||
|
- **Splash theme = custom `fleet`** (`files/plymouth/themes/fleet/`, script
|
||||||
|
plugin, asset-free — drawn with `Image.Text`). It renders a security-handshake
|
||||||
|
checklist (TPM → 802.1X → MACsec → Network → NVMe-oF) driven by best-effort
|
||||||
|
`plymouth update --status=<token>` calls from the dracut hooks
|
||||||
|
(`dot1x-macsec-up.sh`, `nvmf-connect.sh`). Tokens: `mode-test` (marks
|
||||||
|
auth/MACsec skipped), `tpm|dot1x|macsec|net|nvme`-`active|done`. **Rules:** the
|
||||||
|
emits are non-fatal (`plymouth … || true`) and must never reorder the
|
||||||
|
load-bearing boot sequence; show only STATES, never per-host data (invariant
|
||||||
|
#1). The provisioner installs the script plugin best-effort and **falls back to
|
||||||
|
the stock `spinner` theme** if `script.so` is absent, so a plugin-package-name
|
||||||
|
change can't break the build.
|
||||||
|
|
||||||
## Testing changes
|
## Testing changes
|
||||||
|
|
||||||
|
|||||||
+40
-4
@@ -120,8 +120,14 @@ variable "enable_kiosk" {
|
|||||||
|
|
||||||
variable "kiosk_url" {
|
variable "kiosk_url" {
|
||||||
type = string
|
type = string
|
||||||
description = "URL the fullscreen Chromium kiosk loads at boot (cage Wayland session on tty1, baked into the immutable root). Empty disables the kiosk install entirely. A runtime config-agent could override it later via /etc/kiosk/kiosk.env."
|
description = "URL the fullscreen Chromium kiosk loads at boot (weston session on tty1, baked into the immutable root). Defaults to the self-contained test page served by the lab boot HTTP server at /kiosk/ (boot.fleet.lab resolves to the serving host via the stack's dnsmasq) — validates the boot end-to-end without external connectivity. Empty disables the kiosk install entirely. A runtime config-agent could override it later via /etc/kiosk/kiosk.env."
|
||||||
default = "https://www.reddit.com"
|
default = "http://boot.fleet.lab/kiosk/"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "boot_splash" {
|
||||||
|
type = bool
|
||||||
|
description = "true (default): bake 'rhgb quiet' into the UKI cmdline so Plymouth shows a graphical boot splash (covers the network-auth + NVMe/TCP + verity phase). false: bake 'plymouth.enable=0' for a fully verbose console boot — use while debugging the boot chain. Plymouth itself is always in the image; this only flips the cmdline. (The cmdline is immutable once signed, so this is a build-time choice.)"
|
||||||
|
default = true
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "sb_key" {
|
variable "sb_key" {
|
||||||
@@ -206,6 +212,13 @@ build {
|
|||||||
destination = "/tmp/"
|
destination = "/tmp/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Custom 'fleet' Plymouth theme (scripted security-handshake splash). Staged to
|
||||||
|
# /tmp/plymouth, installed + set default in the package step before dracut runs.
|
||||||
|
provisioner "file" {
|
||||||
|
source = "files/plymouth"
|
||||||
|
destination = "/tmp/"
|
||||||
|
}
|
||||||
|
|
||||||
# --- 2. Packages, configs, diskless adaptations --------------------------
|
# --- 2. Packages, configs, diskless adaptations --------------------------
|
||||||
provisioner "shell" {
|
provisioner "shell" {
|
||||||
inline = [
|
inline = [
|
||||||
@@ -222,6 +235,22 @@ build {
|
|||||||
# dracut warns and silently falls back to gzip (a larger initramfs).
|
# dracut warns and silently falls back to gzip (a larger initramfs).
|
||||||
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network NetworkManager zstd openssl",
|
"dnf -y install wpa_supplicant tpm2-tools tpm2-tss nvme-cli cryptsetup dracut-network NetworkManager zstd openssl",
|
||||||
|
|
||||||
|
# Plymouth boot splash. The custom scripted 'fleet' theme renders the
|
||||||
|
# security-handshake checklist driven by the dracut hooks
|
||||||
|
# (`plymouth update --status=...`). plymouth-plugin-label gives Image.Text
|
||||||
|
# its font rendering; dejavu fonts back it. The script-plugin package name
|
||||||
|
# has varied across releases, so install it best-effort and FALL BACK to the
|
||||||
|
# stock 'spinner' theme if script.so is absent — never break the build over
|
||||||
|
# the theme. Set the default theme BEFORE the dracut build below so the
|
||||||
|
# (--no-hostonly) initramfs bundles the theme + its plugin + a font; no -R
|
||||||
|
# (we rebuild the initramfs explicitly). Whether the splash shows at boot is
|
||||||
|
# gated by the cmdline (boot_splash var), not by installing this.
|
||||||
|
"dnf -y install plymouth plymouth-scripts plymouth-plugin-label dejavu-sans-fonts",
|
||||||
|
"dnf -y install plymouth-plugin-script 2>/dev/null || dnf -y install plymouth-theme-script 2>/dev/null || true",
|
||||||
|
"cp -a /tmp/plymouth/themes/fleet /usr/share/plymouth/themes/fleet",
|
||||||
|
"if [ -e /usr/lib64/plymouth/script.so ]; then plymouth-set-default-theme fleet; echo '==> plymouth theme: fleet (scripted security splash)'; else dnf -y install plymouth-theme-spinner && plymouth-set-default-theme spinner; echo 'WARNING: plymouth script plugin (script.so) missing — using spinner; fleet security splash unavailable' >&2; fi",
|
||||||
|
"rm -rf /tmp/plymouth",
|
||||||
|
|
||||||
# Stage etc files into place
|
# Stage etc files into place
|
||||||
"install -D -m0644 /tmp/etc-stage/dot1x/radius-ca.pem /etc/dot1x/radius-ca.pem",
|
"install -D -m0644 /tmp/etc-stage/dot1x/radius-ca.pem /etc/dot1x/radius-ca.pem",
|
||||||
"install -D -m0644 /tmp/etc-stage/dot1x/wpa_supplicant-dot1x.conf /etc/dot1x/wpa_supplicant-dot1x.conf",
|
"install -D -m0644 /tmp/etc-stage/dot1x/wpa_supplicant-dot1x.conf /etc/dot1x/wpa_supplicant-dot1x.conf",
|
||||||
@@ -266,6 +295,8 @@ build {
|
|||||||
# a dracut hook).
|
# a dracut hook).
|
||||||
"grep -q 'nm-unmanaged.sh' /tmp/lsinitrd.txt || { echo 'ERROR: nm-unmanaged.sh hook missing from initramfs' >&2; exit 1; }",
|
"grep -q 'nm-unmanaged.sh' /tmp/lsinitrd.txt || { echo 'ERROR: nm-unmanaged.sh hook missing from initramfs' >&2; exit 1; }",
|
||||||
"grep -q 'nvmf-connect.service' /tmp/lsinitrd.txt || { echo 'ERROR: nvmf-connect.service missing from initramfs' >&2; exit 1; }",
|
"grep -q 'nvmf-connect.service' /tmp/lsinitrd.txt || { echo 'ERROR: nvmf-connect.service missing from initramfs' >&2; exit 1; }",
|
||||||
|
# Plymouth must be in the initramfs to splash during the early boot phase.
|
||||||
|
"grep -q 'plymouthd' /tmp/lsinitrd.txt || { echo 'ERROR: plymouth missing from initramfs (plymouth dracut module not installed)' >&2; exit 1; }",
|
||||||
|
|
||||||
# ---- Build hygiene ---------------------------------------------------
|
# ---- Build hygiene ---------------------------------------------------
|
||||||
"dnf clean all",
|
"dnf clean all",
|
||||||
@@ -308,12 +339,17 @@ build {
|
|||||||
"install -D -m0755 /tmp/kiosk/kiosk-session /usr/local/bin/kiosk-session",
|
"install -D -m0755 /tmp/kiosk/kiosk-session /usr/local/bin/kiosk-session",
|
||||||
"install -D -m0644 /tmp/kiosk/kiosk.service /etc/systemd/system/kiosk.service",
|
"install -D -m0644 /tmp/kiosk/kiosk.service /etc/systemd/system/kiosk.service",
|
||||||
"install -D -m0644 /tmp/kiosk/weston.ini /etc/xdg/weston/weston.ini",
|
"install -D -m0644 /tmp/kiosk/weston.ini /etc/xdg/weston/weston.ini",
|
||||||
|
# Chromium managed policy: stops the browser phoning home to Google
|
||||||
|
# (sign-in, variations, optimization-guide, component/auto update, metrics,
|
||||||
|
# safe browsing). EPEL chromium reads /etc/chromium/policies/managed/*.json.
|
||||||
|
# Pairs with the hardening flags in kiosk-session.
|
||||||
|
"install -D -m0644 /tmp/kiosk/chromium-kiosk-policy.json /etc/chromium/policies/managed/10-kiosk-lockdown.json",
|
||||||
# URL from the var (weston renderer is set via --use-pixman in the unit).
|
# URL from the var (weston renderer is set via --use-pixman in the unit).
|
||||||
"install -d -m0755 /etc/kiosk",
|
"install -d -m0755 /etc/kiosk",
|
||||||
"printf 'KIOSK_URL=%s\\n' \"$KIOSK_URL\" > /etc/kiosk/kiosk.env",
|
"printf 'KIOSK_URL=%s\\n' \"$KIOSK_URL\" > /etc/kiosk/kiosk.env",
|
||||||
"chmod 0644 /etc/kiosk/kiosk.env",
|
"chmod 0644 /etc/kiosk/kiosk.env",
|
||||||
# Label the new files so they survive into the enforcing erofs.
|
# Label the new files so they survive into the enforcing erofs.
|
||||||
"restorecon -R /usr/local/bin/kiosk-session /etc/systemd/system/kiosk.service /etc/xdg/weston /etc/kiosk",
|
"restorecon -R /usr/local/bin/kiosk-session /etc/systemd/system/kiosk.service /etc/xdg/weston /etc/kiosk /etc/chromium",
|
||||||
# Boot graphical and start the kiosk (replaces the tty1 getty).
|
# Boot graphical and start the kiosk (replaces the tty1 getty).
|
||||||
"systemctl enable kiosk.service",
|
"systemctl enable kiosk.service",
|
||||||
"systemctl set-default graphical.target",
|
"systemctl set-default graphical.target",
|
||||||
@@ -402,7 +438,7 @@ build {
|
|||||||
# home dirs) — we dropped mkfs.erofs --all-root, which used to mask this by
|
# home dirs) — we dropped mkfs.erofs --all-root, which used to mask this by
|
||||||
# flattening everything to root:root. sudo is a no-op if already root, and
|
# flattening everything to root:root. sudo is a no-op if already root, and
|
||||||
# the runner has passwordless sudo (cf. the `sudo apt-get` install step).
|
# the runner has passwordless sudo (cf. the `sudo apt-get` install step).
|
||||||
"sudo bash -c \"NVMET_TRADDR='${var.nvmet_traddr}' bash scripts/extract-artifacts.sh output-qemu/diskless-build.qcow2 ${var.artifact_dir}/${var.image_version} ${var.disable_dot1x}\"",
|
"sudo bash -c \"NVMET_TRADDR='${var.nvmet_traddr}' BOOT_SPLASH='${var.boot_splash}' bash scripts/extract-artifacts.sh output-qemu/diskless-build.qcow2 ${var.artifact_dir}/${var.image_version} ${var.disable_dot1x}\"",
|
||||||
# Hand the OUTPUT FILES back to the build user (their in-erofs ownership is
|
# Hand the OUTPUT FILES back to the build user (their in-erofs ownership is
|
||||||
# already baked correctly) so build-oci.sh can write the UKI and push with
|
# already baked correctly) so build-oci.sh can write the UKI and push with
|
||||||
# the build user's `buildah login`.
|
# the build user's `buildah login`.
|
||||||
|
|||||||
+27
-9
@@ -5,13 +5,18 @@ Persistent backlog, kept in sync with the working session task tracker. Status:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [~] 1. Port the dracut module off `network-legacy` → `network-manager`
|
## [x] 1. Port the dracut module off `network-legacy` → `network-manager`
|
||||||
|
|
||||||
**IMPLEMENTED 2026-06-17 — awaiting boot-bench verification.** Fixes the
|
**DONE — test path VERIFIED ON THE BENCH 2026-06-17** (host stays up past the
|
||||||
confirmed lease-expiry outage: network-legacy's one-shot DHCP lease was never
|
DHCP lease time; no more lease-expiry outage). Fixed the confirmed outage:
|
||||||
renewed post-switch-root, so the address dropped at lease expiry → NVMe/TCP root
|
network-legacy's one-shot DHCP lease was never renewed post-switch-root, so the
|
||||||
died ("keep alive timeout" / "no route to host"). NM hands its initramfs
|
address dropped at lease expiry → NVMe/TCP root died ("keep alive timeout" /
|
||||||
connections to the real-root NM (`/run/NetworkManager` state), which renews.
|
"no route to host"). NM hands its initramfs connections to the real-root NM
|
||||||
|
(`/run/NetworkManager` state), which renews.
|
||||||
|
|
||||||
|
REMAINING: the **secure path** (macsec0 DHCP via `nmcli`, physical NIC unmanaged
|
||||||
|
via `nm-unmanaged.sh`) is only exercised once `disable_dot1x=false` — verify on
|
||||||
|
the swtpm + hostapd + FreeRADIUS bench when re-enabling security (see #2/cleanup).
|
||||||
|
|
||||||
Done:
|
Done:
|
||||||
- `90-diskless.conf` `add_dracutmodules`: `network-legacy` → `network-manager`.
|
- `90-diskless.conf` `add_dracutmodules`: `network-legacy` → `network-manager`.
|
||||||
@@ -125,8 +130,11 @@ Path to getting here (don't re-discover):
|
|||||||
smaller image. **Re-enable with `-var enable_kiosk=true` when returning to
|
smaller image. **Re-enable with `-var enable_kiosk=true` when returning to
|
||||||
kiosk work** — CI does not pass the var, so CI currently builds lean (no
|
kiosk work** — CI does not pass the var, so CI currently builds lean (no
|
||||||
kiosk) too; add `-var enable_kiosk=true` to the Gitea workflow to ship it.
|
kiosk) too; add `-var enable_kiosk=true` to the Gitea workflow to ship it.
|
||||||
- `kiosk_url` Packer var (default `https://www.reddit.com`; empty disables the
|
- `kiosk_url` Packer var (default `http://boot.fleet.lab/kiosk/` — the
|
||||||
whole install). Wired in CI as `KIOSK_URL` env.
|
self-contained test page served by the boot HTTP server at
|
||||||
|
`serving/http/kiosk/`, via the `/kiosk/` nginx location; validates a boot
|
||||||
|
without external connectivity. Empty disables the whole install). Wired in CI
|
||||||
|
as `KIOSK_URL` env.
|
||||||
- Provisioner enables EPEL, installs `weston chromium mesa-dri-drivers
|
- Provisioner enables EPEL, installs `weston chromium mesa-dri-drivers
|
||||||
mesa-libEGL mesa-libgbm dejavu-sans-fonts`, creates a passwordless
|
mesa-libEGL mesa-libgbm dejavu-sans-fonts`, creates a passwordless
|
||||||
unprivileged `kiosk` user (video/input groups).
|
unprivileged `kiosk` user (video/input groups).
|
||||||
@@ -147,6 +155,15 @@ Path to getting here (don't re-discover):
|
|||||||
avc`; the kiosk user is unconfined so likely none — but do NOT set permissive
|
avc`; the kiosk user is unconfined so likely none — but do NOT set permissive
|
||||||
if they appear; add a policy module). Admin SSH (the key user) still works for
|
if they appear; add a policy module). Admin SSH (the key user) still works for
|
||||||
debug even if the kiosk fails (it lands at graphical.target either way).
|
debug even if the kiosk fails (it lands at graphical.target either way).
|
||||||
|
- **Phone-home lockdown (done 2026-06-17):** Chromium was dialing Google
|
||||||
|
(accounts/variations/optimization-guide/component+auto update/safe browsing —
|
||||||
|
seen in dnsmasq logs). Stopped via a managed policy
|
||||||
|
(`files/kiosk/chromium-kiosk-policy.json` → `/etc/chromium/policies/managed/`)
|
||||||
|
plus hardening flags in `kiosk-session` (`--disable-background-networking`,
|
||||||
|
`--disable-component-update`, `--disable-features=OptimizationHints,...`, etc.).
|
||||||
|
Verify with the dnsmasq query log after a rebuild — only `boot.fleet.lab`
|
||||||
|
(kiosk page) + chrony NTP should remain. NTP is the OS (chrony), not Chromium;
|
||||||
|
optionally point it at the serving host to fully contain egress.
|
||||||
- **Image size:** Chromium+Mesa add hundreds of MB to the *uncompressed* erofs —
|
- **Image size:** Chromium+Mesa add hundreds of MB to the *uncompressed* erofs —
|
||||||
feeds the "shrink the rootfs" concern (and #7); reconsider whether a lighter
|
feeds the "shrink the rootfs" concern (and #7); reconsider whether a lighter
|
||||||
browser/render path is viable.
|
browser/render path is viable.
|
||||||
@@ -156,7 +173,8 @@ Path to getting here (don't re-discover):
|
|||||||
|
|
||||||
|
|
||||||
Make the diskless workstation boot straight into a locked-down kiosk: a
|
Make the diskless workstation boot straight into a locked-down kiosk: a
|
||||||
fullscreen Chromium showing a configured site (initial target: Reddit). All
|
fullscreen Chromium showing a configured site (default: the boot HTTP server's
|
||||||
|
`/kiosk/` test page). All
|
||||||
baked at **build time** into the root tree (immutable erofs/verity root; runtime
|
baked at **build time** into the root tree (immutable erofs/verity root; runtime
|
||||||
is the tmpfs overlay), fleet-generic (no per-host state).
|
is the tmpfs overlay), fleet-generic (no per-host state).
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ STAMP=/run/dot1x/.done
|
|||||||
[ -e "$STAMP" ] && exit 0
|
[ -e "$STAMP" ] && exit 0
|
||||||
mkdir -p /run/dot1x /run/wpa_supplicant
|
mkdir -p /run/dot1x /run/wpa_supplicant
|
||||||
|
|
||||||
|
# Best-effort boot-splash status for the 'fleet' Plymouth theme. NEVER fatal and
|
||||||
|
# NEVER reorders anything — a splash call must not perturb the load-bearing
|
||||||
|
# auth -> macsec -> DHCP -> NVMe sequence below. No-op if plymouth isn't running.
|
||||||
|
ply() { plymouth update --status="$1" 2>/dev/null || true; }
|
||||||
|
|
||||||
# --- knobs (override on the kernel cmdline) ---------------------------------
|
# --- knobs (override on the kernel cmdline) ---------------------------------
|
||||||
NIC=$(getarg rd.dot1x.if=)
|
NIC=$(getarg rd.dot1x.if=)
|
||||||
if [ -z "$NIC" ]; then
|
if [ -z "$NIC" ]; then
|
||||||
@@ -42,6 +47,7 @@ info "dot1x-macsec: using interface $NIC, NV index $NVINDEX"
|
|||||||
# networks only. Remove rd.dot1x=0 from the cmdline to restore full security.
|
# networks only. Remove rd.dot1x=0 from the cmdline to restore full security.
|
||||||
if [ "$(getarg rd.dot1x=)" = "0" ]; then
|
if [ "$(getarg rd.dot1x=)" = "0" ]; then
|
||||||
warn "dot1x-macsec: 802.1X/MACsec DISABLED (rd.dot1x=0) — TEST MODE, no link security"
|
warn "dot1x-macsec: 802.1X/MACsec DISABLED (rd.dot1x=0) — TEST MODE, no link security"
|
||||||
|
ply mode-test # splash: mark TPM/802.1X/MACsec rows as skipped (not failed)
|
||||||
# Networking in test mode is owned ENTIRELY by `ip=dhcp` on the cmdline:
|
# Networking in test mode is owned ENTIRELY by `ip=dhcp` on the cmdline:
|
||||||
# dracut's network module brings the NIC up, gets a lease and writes
|
# dracut's network module brings the NIC up, gets a lease and writes
|
||||||
# resolv.conf. Do NOT `ifup`/`ip link set up` the NIC here — a second
|
# resolv.conf. Do NOT `ifup`/`ip link set up` the NIC here — a second
|
||||||
@@ -70,6 +76,7 @@ if [ "$(getarg rd.dot1x=)" = "0" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# --- 1. client certificate out of TPM NV ------------------------------------
|
# --- 1. client certificate out of TPM NV ------------------------------------
|
||||||
|
ply tpm-active
|
||||||
if [ ! -s /run/dot1x/client.pem ]; then
|
if [ ! -s /run/dot1x/client.pem ]; then
|
||||||
if ! tpm2_nvread -T device:/dev/tpmrm0 "$NVINDEX" -o /run/dot1x/client.der; then
|
if ! tpm2_nvread -T device:/dev/tpmrm0 "$NVINDEX" -o /run/dot1x/client.der; then
|
||||||
warn "dot1x-macsec: tpm2_nvread failed — cannot authenticate"
|
warn "dot1x-macsec: tpm2_nvread failed — cannot authenticate"
|
||||||
@@ -77,6 +84,7 @@ if [ ! -s /run/dot1x/client.pem ]; then
|
|||||||
fi
|
fi
|
||||||
openssl x509 -inform DER -in /run/dot1x/client.der -out /run/dot1x/client.pem
|
openssl x509 -inform DER -in /run/dot1x/client.der -out /run/dot1x/client.pem
|
||||||
fi
|
fi
|
||||||
|
ply tpm-done
|
||||||
|
|
||||||
# --- 2. supplicant as a root storage daemon ---------------------------------
|
# --- 2. supplicant as a root storage daemon ---------------------------------
|
||||||
ip link set "$NIC" up
|
ip link set "$NIC" up
|
||||||
@@ -88,6 +96,7 @@ if [ ! -e /run/dot1x/wpa.pid ]; then
|
|||||||
-c /etc/dot1x/wpa_supplicant-dot1x.conf \
|
-c /etc/dot1x/wpa_supplicant-dot1x.conf \
|
||||||
-P /run/dot1x/wpa.pid" &
|
-P /run/dot1x/wpa.pid" &
|
||||||
fi
|
fi
|
||||||
|
ply dot1x-active
|
||||||
|
|
||||||
# --- 3. wait for authentication + macsec0 -----------------------------------
|
# --- 3. wait for authentication + macsec0 -----------------------------------
|
||||||
i=0
|
i=0
|
||||||
@@ -104,9 +113,13 @@ if [ "$i" -ge "$TIMEOUT" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
info "dot1x-macsec: port authorized, macsec0 up after ${i}s"
|
info "dot1x-macsec: port authorized, macsec0 up after ${i}s"
|
||||||
|
ply dot1x-done
|
||||||
|
ply macsec-active
|
||||||
|
|
||||||
# --- 4. address the secure interface; netroot rides on macsec0 --------------
|
# --- 4. address the secure interface; netroot rides on macsec0 --------------
|
||||||
ip link set macsec0 up
|
ip link set macsec0 up
|
||||||
|
ply macsec-done
|
||||||
|
ply net-active
|
||||||
# DHCP on macsec0 via NetworkManager (NOT the old network-legacy `ifup`): NM owns
|
# DHCP on macsec0 via NetworkManager (NOT the old network-legacy `ifup`): NM owns
|
||||||
# macsec0, so it RENEWS the lease in the running system after switch-root — the
|
# macsec0, so it RENEWS the lease in the running system after switch-root — the
|
||||||
# whole point of the network-manager port. macsec0 was created externally by
|
# whole point of the network-manager port. macsec0 was created externally by
|
||||||
@@ -127,6 +140,7 @@ if [ "$i" -ge "$TIMEOUT" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
info "dot1x-macsec: macsec0 addressed via NetworkManager (DHCP)"
|
info "dot1x-macsec: macsec0 addressed via NetworkManager (DHCP)"
|
||||||
|
ply net-done
|
||||||
|
|
||||||
# --- 5. derive per-host NVMe identity from the TPM certificate --------------
|
# --- 5. derive per-host NVMe identity from the TPM certificate --------------
|
||||||
# Keeps the image fleet-generic: the host NQN is a function of the machine
|
# Keeps the image fleet-generic: the host NQN is a function of the machine
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ _trsvcid=${_rest##*,}
|
|||||||
# nvme_tcp is usually auto-loaded; ensure it so /dev/nvme-fabrics exists.
|
# nvme_tcp is usually auto-loaded; ensure it so /dev/nvme-fabrics exists.
|
||||||
modprobe nvme_tcp 2>/dev/null
|
modprobe nvme_tcp 2>/dev/null
|
||||||
|
|
||||||
|
# Best-effort boot-splash status for the 'fleet' Plymouth theme (never fatal). By
|
||||||
|
# the time this runs the ip=dhcp link is up, so mark Network done and NVMe active.
|
||||||
|
ply() { plymouth update --status="$1" 2>/dev/null || true; }
|
||||||
|
ply net-done
|
||||||
|
ply nvme-active
|
||||||
|
|
||||||
# Keep-alive / reconnect tuning for the ROOT device. These persist across
|
# Keep-alive / reconnect tuning for the ROOT device. These persist across
|
||||||
# switch-root (the in-kernel nvme-tcp controller and its opts survive).
|
# switch-root (the in-kernel nvme-tcp controller and its opts survive).
|
||||||
# -k 30 keep-alive timeout (s). The 5s default means a KA every 2.5s; under
|
# -k 30 keep-alive timeout (s). The 5s default means a KA every 2.5s; under
|
||||||
@@ -53,6 +59,7 @@ while [ "$_i" -lt 60 ]; do
|
|||||||
if [ -b "$_ns" ]; then
|
if [ -b "$_ns" ]; then
|
||||||
mkdir -p /run/dot1x
|
mkdir -p /run/dot1x
|
||||||
: > /run/dot1x/.nvmf-connected
|
: > /run/dot1x/.nvmf-connected
|
||||||
|
ply nvme-done
|
||||||
echo "nvmf-connect: connected ${_trtype}/${_traddr}/${_trsvcid} ($_ns)"
|
echo "nvmf-connect: connected ${_trtype}/${_traddr}/${_trsvcid} ($_ns)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ hostonly="no"
|
|||||||
# lease was never renewed post-switch-root -> the address was dropped at lease
|
# lease was never renewed post-switch-root -> the address was dropped at lease
|
||||||
# expiry -> NVMe/TCP root died ("keep alive timeout" / "no route to host").
|
# expiry -> NVMe/TCP root died ("keep alive timeout" / "no route to host").
|
||||||
add_dracutmodules+=" dot1x-macsec network-manager nvmf systemd-veritysetup "
|
add_dracutmodules+=" dot1x-macsec network-manager nvmf systemd-veritysetup "
|
||||||
|
# plymouth: graphical boot splash, shown from early initramfs through the long
|
||||||
|
# network-auth + NVMe/TCP + verity phase until the kiosk/login takes over. The
|
||||||
|
# plymouth dracut module depends on 'drm', which in --no-hostonly mode pulls a
|
||||||
|
# broad set of DRM/KMS drivers (incl. the VM's bochs/virtio-gpu); if a specific
|
||||||
|
# fleet GPU's splash doesn't render, add its drm kmod to add_drivers like the
|
||||||
|
# NICs. Whether the splash actually displays is gated by the cmdline (rhgb quiet
|
||||||
|
# vs plymouth.enable=0), set from the boot_splash var in extract-artifacts.sh.
|
||||||
|
add_dracutmodules+=" plymouth "
|
||||||
filesystems+=" erofs overlay "
|
filesystems+=" erofs overlay "
|
||||||
add_drivers+=" macsec nvme-tcp nvme-fabrics dm-verity @NIC_DRIVERS@ "
|
add_drivers+=" macsec nvme-tcp nvme-fabrics dm-verity @NIC_DRIVERS@ "
|
||||||
install_items+=" /etc/dot1x/radius-ca.pem /etc/dot1x/wpa_supplicant-dot1x.conf "
|
install_items+=" /etc/dot1x/radius-ca.pem /etc/dot1x/wpa_supplicant-dot1x.conf "
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Chromium managed policy for the diskless kiosk. Installed to /etc/chromium/policies/managed/. Stops Chromium phoning home to *.google.com / *.googleapis.com (sign-in, variations, optimization guide, component/auto updates, metrics, safe browsing) — none of which a fixed-URL kiosk needs. Pairs with the hardening flags in kiosk-session. Unknown keys are ignored by Chromium, so this is forward/backward safe.",
|
||||||
|
|
||||||
|
"MetricsReportingEnabled": false,
|
||||||
|
"UrlKeyedAnonymizedDataCollectionEnabled": false,
|
||||||
|
"DeviceMetricsReportingEnabled": false,
|
||||||
|
|
||||||
|
"BrowserSignin": 0,
|
||||||
|
"SyncDisabled": true,
|
||||||
|
|
||||||
|
"SafeBrowsingProtectionLevel": 0,
|
||||||
|
"SafeBrowsingExtendedReportingEnabled": false,
|
||||||
|
|
||||||
|
"ComponentUpdatesEnabled": false,
|
||||||
|
"ChromeVariations": 2,
|
||||||
|
"BrowserNetworkTimeQueriesEnabled": false,
|
||||||
|
|
||||||
|
"SearchSuggestEnabled": false,
|
||||||
|
"DefaultSearchProviderEnabled": false,
|
||||||
|
"SpellCheckServiceEnabled": false,
|
||||||
|
"TranslateEnabled": false,
|
||||||
|
"NetworkPredictionOptions": 2,
|
||||||
|
|
||||||
|
"BackgroundModeEnabled": false,
|
||||||
|
"PromotionalTabsEnabled": false,
|
||||||
|
"WelcomePageOnOSUpgradeEnabled": false,
|
||||||
|
"DefaultBrowserSettingEnabled": false,
|
||||||
|
|
||||||
|
"AutofillAddressEnabled": false,
|
||||||
|
"AutofillCreditCardEnabled": false,
|
||||||
|
"PasswordManagerEnabled": false,
|
||||||
|
"ShoppingListEnabled": false,
|
||||||
|
"EnableMediaRouter": false
|
||||||
|
}
|
||||||
@@ -34,10 +34,24 @@ BROWSER=$(command -v chromium-browser || command -v chromium || true)
|
|||||||
done
|
done
|
||||||
export WAYLAND_DISPLAY="$SOCK"
|
export WAYLAND_DISPLAY="$SOCK"
|
||||||
while :; do
|
while :; do
|
||||||
|
# --disable-* / --no-pings etc.: stop Chromium phoning home to Google
|
||||||
|
# (background networking = variations/field-trials + GCM, component &
|
||||||
|
# auto updates, domain reliability, crash upload, hyperlink pings). The
|
||||||
|
# --disable-features list kills the Optimization Guide hints fetch
|
||||||
|
# (optimizationguide-pa.googleapis.com), Translate, Media Router discovery
|
||||||
|
# and Autofill server calls. The comprehensive lockdown (sign-in, metrics,
|
||||||
|
# safe browsing, search suggest, ...) is the managed policy at
|
||||||
|
# /etc/chromium/policies/managed/10-kiosk-lockdown.json; these flags cover
|
||||||
|
# what policy can't. A fixed-URL kiosk needs none of it.
|
||||||
"$BROWSER" \
|
"$BROWSER" \
|
||||||
--kiosk --noerrdialogs --disable-infobars --incognito --no-first-run \
|
--kiosk --noerrdialogs --disable-infobars --incognito --no-first-run \
|
||||||
--disable-session-crashed-bubble --start-maximized \
|
--disable-session-crashed-bubble --start-maximized \
|
||||||
--ozone-platform=wayland --disable-gpu \
|
--ozone-platform=wayland --disable-gpu \
|
||||||
|
--disable-background-networking --disable-component-update \
|
||||||
|
--disable-sync --disable-domain-reliability --disable-breakpad \
|
||||||
|
--no-pings --no-default-browser-check --metrics-recording-only \
|
||||||
|
--disable-search-engine-collection \
|
||||||
|
--disable-features=Translate,OptimizationHints,OptimizationTargetPrediction,MediaRouter,AutofillServerCommunication,InterestFeedContentSuggestions \
|
||||||
--user-data-dir=/tmp/kiosk-profile --disk-cache-dir=/tmp/kiosk-cache \
|
--user-data-dir=/tmp/kiosk-profile --disk-cache-dir=/tmp/kiosk-cache \
|
||||||
"$KIOSK_URL" || true
|
"$KIOSK_URL" || true
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[Plymouth Theme]
|
||||||
|
Name=Fleet
|
||||||
|
Description=Cyber sci-fi secure diskless bring-up splash (TPM / 802.1X / MACsec / NVMe-oF). Driven by `plymouth update --status=` tokens from the dracut hooks.
|
||||||
|
ModuleName=script
|
||||||
|
|
||||||
|
[script]
|
||||||
|
ImageDir=/usr/share/plymouth/themes/fleet
|
||||||
|
ScriptFile=/usr/share/plymouth/themes/fleet/fleet.script
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Fleet — cyber sci-fi secure-bring-up splash (Plymouth "script" plugin).
|
||||||
|
#
|
||||||
|
# Asset-free on purpose: everything is drawn with Image.Text, so the repo carries
|
||||||
|
# no binary images. The dracut hooks drive it with `plymouth update --status=<tok>`
|
||||||
|
# (see dot1x-macsec-up.sh / nvmf-connect.sh); this script maps each token to a
|
||||||
|
# security-handshake checklist and pulses the in-progress step. Tokens are
|
||||||
|
# best-effort and order-independent — anything not reported just stays "pending",
|
||||||
|
# and in rd.dot1x=0 test mode the auth/MACsec rows are marked "skipped" (never
|
||||||
|
# "failed"). No per-host data is ever shown (only states), per the no-state rule.
|
||||||
|
|
||||||
|
# ---- background (dark teal -> black) ----
|
||||||
|
Window.SetBackgroundTopColor(0.00, 0.05, 0.07);
|
||||||
|
Window.SetBackgroundBottomColor(0.00, 0.00, 0.00);
|
||||||
|
|
||||||
|
sw = Window.GetWidth();
|
||||||
|
sh = Window.GetHeight();
|
||||||
|
|
||||||
|
# ---- title / subtitle ----
|
||||||
|
title_img = Image.Text("F L E E T", 0.0, 1.0, 0.85, 1, "Sans Bold 36");
|
||||||
|
title = Sprite(title_img);
|
||||||
|
title.SetX(sw / 2 - title_img.GetWidth() / 2);
|
||||||
|
title.SetY(sh / 2 - 150);
|
||||||
|
|
||||||
|
sub_img = Image.Text("secure diskless bring-up", 0.30, 0.65, 0.65, 1, "Sans 14");
|
||||||
|
sub = Sprite(sub_img);
|
||||||
|
sub.SetX(sw / 2 - sub_img.GetWidth() / 2);
|
||||||
|
sub.SetY(sh / 2 - 102);
|
||||||
|
|
||||||
|
# ---- handshake stages ----
|
||||||
|
# state per stage: 0 pending, 1 active, 2 done, 3 skipped
|
||||||
|
n = 5;
|
||||||
|
label[0] = "TPM identity";
|
||||||
|
label[1] = "802.1X EAP-TLS";
|
||||||
|
label[2] = "MACsec link";
|
||||||
|
label[3] = "Network (DHCP)";
|
||||||
|
label[4] = "NVMe-oF root";
|
||||||
|
|
||||||
|
col_x = sw / 2 - 150;
|
||||||
|
row_y = sh / 2 - 36;
|
||||||
|
row_h = 34;
|
||||||
|
|
||||||
|
fun draw(i) {
|
||||||
|
glyph = "[ ]"; r = 0.22; g = 0.42; b = 0.42; # pending (dim teal)
|
||||||
|
if (state[i] == 1) { glyph = "[>>]"; r = 1.00; g = 0.72; b = 0.10; } # active (amber)
|
||||||
|
if (state[i] == 2) { glyph = "[OK]"; r = 0.10; g = 1.00; b = 0.50; } # done (neon green)
|
||||||
|
if (state[i] == 3) { glyph = "[--]"; r = 0.30; g = 0.30; b = 0.36; } # skipped (grey)
|
||||||
|
img = Image.Text(glyph + " " + label[i], r, g, b, 1, "Sans 16");
|
||||||
|
row[i].SetImage(img);
|
||||||
|
row[i].SetX(col_x);
|
||||||
|
row[i].SetY(row_y + i * row_h);
|
||||||
|
row[i].SetOpacity(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (i < n) {
|
||||||
|
state[i] = 0;
|
||||||
|
row[i] = Sprite();
|
||||||
|
draw(i);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- footer ----
|
||||||
|
foot_img = Image.Text("erofs + dm-verity • NVMe/TCP • no per-host state",
|
||||||
|
0.25, 0.45, 0.45, 1, "Sans 11");
|
||||||
|
foot = Sprite(foot_img);
|
||||||
|
foot.SetX(sw / 2 - foot_img.GetWidth() / 2);
|
||||||
|
foot.SetY(sh / 2 + 150);
|
||||||
|
|
||||||
|
# ---- status tokens from the boot hooks ----
|
||||||
|
fun set(i, s) { state[i] = s; draw(i); }
|
||||||
|
|
||||||
|
fun on_status(t) {
|
||||||
|
if (t == "mode-test") { set(0, 3); set(1, 3); set(2, 3); }
|
||||||
|
if (t == "tpm-active") set(0, 1);
|
||||||
|
if (t == "tpm-done") set(0, 2);
|
||||||
|
if (t == "dot1x-active") set(1, 1);
|
||||||
|
if (t == "dot1x-done") set(1, 2);
|
||||||
|
if (t == "macsec-active") set(2, 1);
|
||||||
|
if (t == "macsec-done") set(2, 2);
|
||||||
|
if (t == "net-active") set(3, 1);
|
||||||
|
if (t == "net-done") set(3, 2);
|
||||||
|
if (t == "nvme-active") set(4, 1);
|
||||||
|
if (t == "nvme-done") set(4, 2);
|
||||||
|
}
|
||||||
|
Plymouth.SetUpdateStatusFunction(on_status);
|
||||||
|
|
||||||
|
# ---- optional free-text line (e.g. warnings via `plymouth message`) ----
|
||||||
|
msg = Sprite();
|
||||||
|
fun on_message(text) {
|
||||||
|
img = Image.Text(text, 0.55, 0.80, 0.80, 1, "Sans 12");
|
||||||
|
msg.SetImage(img);
|
||||||
|
msg.SetX(sw / 2 - img.GetWidth() / 2);
|
||||||
|
msg.SetY(sh / 2 + 182);
|
||||||
|
}
|
||||||
|
Plymouth.SetMessageFunction(on_message);
|
||||||
|
|
||||||
|
# ---- pulse the in-progress row ----
|
||||||
|
tick = 0;
|
||||||
|
fun on_refresh() {
|
||||||
|
tick = tick + 1;
|
||||||
|
o = 1.0;
|
||||||
|
if (tick % 24 < 12) o = 0.45;
|
||||||
|
i = 0;
|
||||||
|
while (i < n) {
|
||||||
|
if (state[i] == 1) row[i].SetOpacity(o);
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Plymouth.SetRefreshFunction(on_refresh);
|
||||||
@@ -32,7 +32,6 @@ reboot
|
|||||||
kernel
|
kernel
|
||||||
openssh-server
|
openssh-server
|
||||||
chrony
|
chrony
|
||||||
-plymouth
|
|
||||||
-firewalld
|
-firewalld
|
||||||
%end
|
%end
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ DISABLE_DOT1X=${3:-false}
|
|||||||
# environments. Defaults to the literal placeholder so build-oci.sh's warning
|
# environments. Defaults to the literal placeholder so build-oci.sh's warning
|
||||||
# fires if it was never set.
|
# fires if it was never set.
|
||||||
NVMET_TRADDR=${NVMET_TRADDR:-NVMET_TRADDR}
|
NVMET_TRADDR=${NVMET_TRADDR:-NVMET_TRADDR}
|
||||||
|
# Plymouth boot splash toggle (from the boot_splash Packer var). "true" bakes
|
||||||
|
# "rhgb quiet" so Plymouth shows the graphical splash; anything else bakes
|
||||||
|
# "plymouth.enable=0" for a fully verbose console boot (boot-chain debugging).
|
||||||
|
BOOT_SPLASH=${BOOT_SPLASH:-true}
|
||||||
# Stable namespace UUIDs. Devices are referenced by-id
|
# Stable namespace UUIDs. Devices are referenced by-id
|
||||||
# (/dev/disk/by-id/nvme-uuid.<uuid>) rather than /dev/nvmeNnM because NVMe
|
# (/dev/disk/by-id/nvme-uuid.<uuid>) rather than /dev/nvmeNnM because NVMe
|
||||||
# controller/namespace indices are NOT stable (a transient discovery controller
|
# controller/namespace indices are NOT stable (a transient discovery controller
|
||||||
@@ -142,6 +146,16 @@ jq -n --arg hash "$ROOT_HASH" --arg erofs_uuid "$FIXED_UUID" \
|
|||||||
# The device node assumes the root namespace is the host's first/only
|
# The device node assumes the root namespace is the host's first/only
|
||||||
# NVMe/TCP namespace; adjust if not. NVMET_TRADDR: set per environment
|
# NVMe/TCP namespace; adjust if not. NVMET_TRADDR: set per environment
|
||||||
# *before* the UKI build, since the cmdline is immutable once signed.
|
# *before* the UKI build, since the cmdline is immutable once signed.
|
||||||
|
# Plymouth: rhgb (Red Hat graphical boot) + quiet for a clean splash, or
|
||||||
|
# plymouth.enable=0 for a verbose debug boot. Press ESC at boot to see details
|
||||||
|
# behind the splash; or override at runtime via the SMBIOS Type 11
|
||||||
|
# io.systemd.stub.kernel-cmdline-extra OEM string (adds args without a rebuild).
|
||||||
|
if [ "$BOOT_SPLASH" = "true" ]; then
|
||||||
|
SPLASH_FRAGMENT="rhgb quiet "
|
||||||
|
else
|
||||||
|
SPLASH_FRAGMENT="plymouth.enable=0 "
|
||||||
|
fi
|
||||||
|
|
||||||
DOT1X_FRAGMENT=""
|
DOT1X_FRAGMENT=""
|
||||||
if [ "$DISABLE_DOT1X" = "true" ]; then
|
if [ "$DISABLE_DOT1X" = "true" ]; then
|
||||||
echo "==> WARNING: building TEST cmdline with rd.dot1x=0 (802.1X + MACsec DISABLED)" >&2
|
echo "==> WARNING: building TEST cmdline with rd.dot1x=0 (802.1X + MACsec DISABLED)" >&2
|
||||||
@@ -154,6 +168,7 @@ if [ "$DISABLE_DOT1X" = "true" ]; then
|
|||||||
DOT1X_FRAGMENT="rd.dot1x=0 ip=dhcp "
|
DOT1X_FRAGMENT="rd.dot1x=0 ip=dhcp "
|
||||||
fi
|
fi
|
||||||
printf '%s' \
|
printf '%s' \
|
||||||
|
"$SPLASH_FRAGMENT" \
|
||||||
"$DOT1X_FRAGMENT" \
|
"$DOT1X_FRAGMENT" \
|
||||||
"rd.neednet=1 systemd.volatile=overlay " \
|
"rd.neednet=1 systemd.volatile=overlay " \
|
||||||
"rd.dot1x.nvindex=0x01800001 rd.dot1x.timeout=60 " \
|
"rd.dot1x.nvindex=0x01800001 rd.dot1x.timeout=60 " \
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ docker compose ps
|
|||||||
# HTTP: UKI is served
|
# HTTP: UKI is served
|
||||||
curl -sI http://${SERVER_IP}/fleet-${IMAGE_VERSION}.efi | head -1 # 200 OK
|
curl -sI http://${SERVER_IP}/fleet-${IMAGE_VERSION}.efi | head -1 # 200 OK
|
||||||
|
|
||||||
|
# HTTP: kiosk test page is served (what the diskless kiosk loads by default)
|
||||||
|
curl -sI http://${SERVER_IP}/kiosk/ | head -1 # 200 OK
|
||||||
|
|
||||||
# NVMe/TCP: target is listening + discoverable
|
# NVMe/TCP: target is listening + discoverable
|
||||||
ss -lntp | grep 4420
|
ss -lntp | grep 4420
|
||||||
sudo nvme discover -t tcp -a ${SERVER_IP} -s 4420 # lists the subsystem
|
sudo nvme discover -t tcp -a ${SERVER_IP} -s 4420 # lists the subsystem
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ services:
|
|||||||
subpath: artifacts # mount only /artifacts from the image
|
subpath: artifacts # mount only /artifacts from the image
|
||||||
# Fallback (Docker < 28 / offline): bind the local Packer output dir:
|
# Fallback (Docker < 28 / offline): bind the local Packer output dir:
|
||||||
# - ${ARTIFACT_SRC}:/usr/share/nginx/html:ro
|
# - ${ARTIFACT_SRC}:/usr/share/nginx/html:ro
|
||||||
|
# Serve the kiosk test page at /kiosk/ alongside the artifacts. Config
|
||||||
|
# adds the location; the page is lab content (NOT in the artifact image).
|
||||||
|
- ./http/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- ./http/kiosk:/usr/share/nginx/kiosk:ro
|
||||||
# For HTTPS boot (production), terminate TLS here with the boot-infra CA's
|
# For HTTPS boot (production), terminate TLS here with the boot-infra CA's
|
||||||
# cert and switch the dhcp-boot URL above to https://. Left HTTP for the lab.
|
# cert and switch the dhcp-boot URL above to https://. Left HTTP for the lab.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Lab boot HTTP server (nginx). Replaces the stock default.conf so one server on
|
||||||
|
# :80 serves BOTH:
|
||||||
|
# / -> the OCI release artifacts (UKI fleet-<ver>.efi, etc.), mounted
|
||||||
|
# read-only from the image volume at /usr/share/nginx/html
|
||||||
|
# /kiosk/ -> the diskless kiosk test page (lab content bind-mounted at
|
||||||
|
# /usr/share/nginx/kiosk, deliberately NOT part of the artifact
|
||||||
|
# image, which stays FROM-scratch).
|
||||||
|
# A separate kiosk container was considered, but every service here uses host
|
||||||
|
# networking, so a second nginx would fight for :80; a single location is cleaner.
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# UKI + release artifacts (firmware fetches /fleet-<ver>.efi from here).
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
autoindex on;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Kiosk test page. The diskless kiosk (kiosk_url) points at
|
||||||
|
# http://boot.fleet.lab/kiosk/ — resolved to this host by the stack's dnsmasq.
|
||||||
|
location /kiosk/ {
|
||||||
|
alias /usr/share/nginx/kiosk/;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--
|
||||||
|
Kiosk test page — served by the lab boot HTTP server (nginx) at /kiosk/, NOT
|
||||||
|
baked into the OCI artifact image (that stays FROM-scratch: UKI + rootfs only).
|
||||||
|
The diskless kiosk (weston + Chromium) loads this instead of an internet site,
|
||||||
|
so a boot is validated end-to-end without external connectivity: DNS resolves
|
||||||
|
boot.fleet.lab via dnsmasq, HTTP fetches from the serving host, and Chromium
|
||||||
|
renders it. Fully self-contained (no external fonts/CDN) so it works offline.
|
||||||
|
-->
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Fleet Kiosk — Boot OK</title>
|
||||||
|
<style>
|
||||||
|
:root { --fg: #00ffd0; --dim: #4a8c84; --bg: #040a0d; --accent: #ff2e88; }
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
html, body { height: 100%; margin: 0; }
|
||||||
|
body {
|
||||||
|
background: radial-gradient(circle at 50% 30%, #0a1620 0%, var(--bg) 70%);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: "DejaVu Sans Mono", ui-monospace, monospace;
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
align-items: center; justify-content: center;
|
||||||
|
text-align: center; gap: 1.2rem; padding: 2rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
h1 { font-size: clamp(2rem, 7vw, 5rem); margin: 0; letter-spacing: .15em;
|
||||||
|
text-shadow: 0 0 18px var(--fg); }
|
||||||
|
.ok { color: var(--accent); text-shadow: 0 0 18px var(--accent); }
|
||||||
|
.sub { color: var(--dim); font-size: clamp(.9rem, 2.2vw, 1.3rem);
|
||||||
|
letter-spacing: .1em; }
|
||||||
|
.grid { display: grid; grid-template-columns: auto auto; gap: .35rem 1.2rem;
|
||||||
|
margin-top: 1rem; font-size: clamp(.8rem, 1.8vw, 1.1rem);
|
||||||
|
text-align: left; }
|
||||||
|
.grid .k { color: var(--dim); }
|
||||||
|
.clock { font-size: clamp(1.4rem, 4vw, 2.4rem); text-shadow: 0 0 12px var(--fg); }
|
||||||
|
.bar { width: min(60vw, 520px); height: 3px; background: var(--dim); opacity:.4;
|
||||||
|
border-radius: 2px; }
|
||||||
|
footer { position: fixed; bottom: 1rem; color: var(--dim); font-size: .8rem;
|
||||||
|
letter-spacing: .1em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><span class="ok">▣</span> FLEET KIOSK ONLINE</h1>
|
||||||
|
<div class="sub">diskless boot validated — weston + chromium rendering local HTTP</div>
|
||||||
|
<div class="bar"></div>
|
||||||
|
<div class="clock" id="clock">--:--:--</div>
|
||||||
|
<div class="grid">
|
||||||
|
<span class="k">served from</span><span id="origin">…</span>
|
||||||
|
<span class="k">page url</span><span id="href">…</span>
|
||||||
|
<span class="k">user agent</span><span id="ua">…</span>
|
||||||
|
<span class="k">screen</span><span id="screen">…</span>
|
||||||
|
</div>
|
||||||
|
<footer>boot HTTP server · replace this page or point kiosk_url elsewhere</footer>
|
||||||
|
<script>
|
||||||
|
// Live clock proves JS executes; the origin/href confirm where it was fetched.
|
||||||
|
function pad(n){ return String(n).padStart(2,'0'); }
|
||||||
|
function tick(){
|
||||||
|
var d = new Date();
|
||||||
|
document.getElementById('clock').textContent =
|
||||||
|
pad(d.getHours())+':'+pad(d.getMinutes())+':'+pad(d.getSeconds());
|
||||||
|
}
|
||||||
|
setInterval(tick, 1000); tick();
|
||||||
|
document.getElementById('origin').textContent = location.host || '(file)';
|
||||||
|
document.getElementById('href').textContent = location.href;
|
||||||
|
document.getElementById('ua').textContent = navigator.userAgent;
|
||||||
|
document.getElementById('screen').textContent =
|
||||||
|
window.innerWidth + '×' + window.innerHeight;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user