From a38c35ce2d3377ca3d847e491f521d5cdb4f88cd Mon Sep 17 00:00:00 2001 From: Michael Chus Date: Thu, 5 Mar 2026 18:18:22 +0300 Subject: [PATCH] docs: add three LiveCD/embedded patterns from bee project - alpine-livecd: mkimage profile rules, apkovl mechanics, workdir caching, squashfs compression, NIC firmware, long build survival via screen - vendor-installer-verification: checksum-before-download, cache validation, version URL verification before writing build scripts - unattended-boot-services: OpenRC invariants for headless environments, network-independent SSH, persistent DHCP, graceful degradation Co-Authored-By: Claude Sonnet 4.6 --- rules/patterns/alpine-livecd/contract.md | 142 ++++++++++++++++++ .../unattended-boot-services/contract.md | 133 ++++++++++++++++ .../vendor-installer-verification/contract.md | 82 ++++++++++ 3 files changed, 357 insertions(+) create mode 100644 rules/patterns/alpine-livecd/contract.md create mode 100644 rules/patterns/unattended-boot-services/contract.md create mode 100644 rules/patterns/vendor-installer-verification/contract.md diff --git a/rules/patterns/alpine-livecd/contract.md b/rules/patterns/alpine-livecd/contract.md new file mode 100644 index 0000000..2700ba7 --- /dev/null +++ b/rules/patterns/alpine-livecd/contract.md @@ -0,0 +1,142 @@ +# Contract: Alpine LiveCD Build + +Version: 1.0 + +## Purpose + +Rules for building bootable Alpine Linux ISO images with custom overlays using `mkimage.sh`. +Applies to any project that needs a LiveCD: hardware audit, rescue environments, kiosks. + +--- + +## mkimage Profile + +Every project must have a profile file `mkimg..sh` defining: + +```sh +profile_() { + arch="x86_64" # REQUIRED — without this mkimage silently skips the profile + hostname="" + apkovl="genapkovl-.sh" + image_ext="iso" + output_format="iso" + kernel_flavors="lts" + initfs_cmdline="modules=loop,squashfs,sd-mod,usb-storage quiet" + initfs_features="ata base cdrom ext4 mmc nvme raid scsi squashfs usb virtio" + grub_mod="all_video disk part_gpt part_msdos linux normal configfile search search_label efi_gop fat iso9660 cat echo ls test true help gzio" + apks="alpine-base linux-lts linux-firmware-none ..." +} +``` + +**`arch` is mandatory.** If missing, mkimage silently builds nothing and exits 0. + +--- + +## apkovl Mechanism + +The apkovl is a `.tar.gz` overlay extracted by initramfs at boot, overlaying `/etc`, `/usr`, `/root`. + +`genapkovl-.sh` generates the tarball: +- Must be in the **CWD** when mkimage runs — not only in `~/.mkimage/` +- `~/.mkimage/` is searched for mkimg profiles only, not genapkovl scripts + +```sh +# Copy both scripts to ~/.mkimage AND to CWD (typically /var/tmp) +cp "genapkovl-.sh" ~/.mkimage/ +cp "genapkovl-.sh" /var/tmp/ +cd /var/tmp +sh mkimage.sh --workdir /var/tmp/work ... +``` + +--- + +## Build Environment + +**Always use `/var/tmp`, not `/tmp`:** + +```sh +export TMPDIR=/var/tmp +cd /var/tmp +sh mkimage.sh ... +``` + +`/tmp` on Alpine builder VMs is typically a 1GB tmpfs. Kernel firmware squashfs alone exceeds this. +`/var/tmp` uses actual disk space. + +--- + +## Workdir Caching + +mkimage stores each ISO section in a hash-named subdirectory. Preserve expensive sections across builds: + +```sh +# Delete everything EXCEPT cached sections +if [ -d /var/tmp/bee-iso-work ]; then + find /var/tmp/bee-iso-work -maxdepth 1 -mindepth 1 \ + -not -name 'apks_*' \ # downloaded packages + -not -name 'kernel_*' \ # modloop squashfs + -not -name 'syslinux_*' \ # syslinux bootloader + -not -name 'grub_*' \ # grub EFI + -exec rm -rf {} + +fi +``` + +The apkovl section is always regenerated (contains project-specific config that changes per build). + +--- + +## Squashfs Compression + +Default compression is `xz` — slow but small. For RAM-loaded modloops, size rarely matters. +Use `lz4` for faster builds: + +```sh +mkdir -p /etc/mkinitfs +grep -q 'MKSQUASHFS_OPTS' /etc/mkinitfs/mkinitfs.conf 2>/dev/null || \ + echo 'MKSQUASHFS_OPTS="-comp lz4 -Xhc"' >> /etc/mkinitfs/mkinitfs.conf +``` + +Apply before running mkimage. Rebuilds modloop only when kernel version changes. + +--- + +## Long Builds + +NVIDIA driver downloads, kernel compiles, and package fetches can take 10–30 minutes. +Run in a `screen` session so builds survive SSH disconnects: + +```sh +apk add screen +screen -dmS build sh -c "sh build.sh > /var/log/build.log 2>&1" +tail -f /var/log/build.log +``` + +--- + +## NIC Firmware + +`linux-firmware-none` (default) contains zero firmware files. Real hardware NICs often require firmware. +Include firmware packages matching expected hardware: + +``` +linux-firmware-intel # Intel NICs (X710, E810, etc.) +linux-firmware-mellanox # Mellanox/NVIDIA ConnectX +linux-firmware-bnx2x # Broadcom NetXtreme +linux-firmware-rtl_nic # Realtek +linux-firmware-other # catch-all +``` + +--- + +## Versioning + +Pin all versions in a single `VERSIONS` file sourced by all build scripts: + +```sh +ALPINE_VERSION=3.21 +KERNEL_VERSION=6.12 +GO_VERSION=1.23.6 +NVIDIA_DRIVER_VERSION=590.48.01 +``` + +Never hardcode versions inside build scripts. diff --git a/rules/patterns/unattended-boot-services/contract.md b/rules/patterns/unattended-boot-services/contract.md new file mode 100644 index 0000000..d2eb3ff --- /dev/null +++ b/rules/patterns/unattended-boot-services/contract.md @@ -0,0 +1,133 @@ +# Contract: Unattended Boot Services (OpenRC) + +Version: 1.0 + +## Purpose + +Rules for OpenRC services that run in unattended environments: LiveCDs, kiosks, embedded systems. +No user is present. No TTY prompts. Every failure path must have a silent fallback. + +--- + +## Core Invariants + +- **Never block boot.** A service failure must not prevent other services from starting. +- **Never prompt.** No `read`, no `pause`, no interactive input of any kind. +- **Always exit 0.** Use `eend 0` at the end of `start()` regardless of the operation result. +- **Log everything.** Write results to `/var/log/` so SSH inspection is possible after boot. +- **Fail silently, degrade gracefully.** Missing tool → skip that collector. No network → skip network-dependent steps. + +--- + +## Service Dependencies + +Use the minimum necessary dependencies: + +```sh +depend() { + need localmount # almost always needed + after some-service # ordering without hard dependency + use logger # optional soft dependency + # DO NOT add: need net, need networking, need network-online +} +``` + +**Never use `need net` or `need networking`** unless the service is genuinely useless without +network and you want it to fail loudly when no cable is connected. +For services that work with or without network, use `after` instead. + +--- + +## Network-Independent SSH + +Dropbear (and any SSH server) must start without network being available. +Common mistake: installing dropbear-openrc which adds `need net` in its default init. + +Override with a custom init: + +```sh +#!/sbin/openrc-run +description="SSH server" + +depend() { + need localmount + after bee-sshsetup # key/user setup, not networking + use logger + # NO need net +} + +start() { + check_config || return 1 + ebegin "Starting dropbear" + /usr/sbin/dropbear ${DROPBEAR_OPTS} + eend $? +} +``` + +Place this file in the overlay at `etc/init.d/dropbear` — it overrides the package-installed version. + +--- + +## Persistent DHCP + +Do not use blocking DHCP (`-n` flag exits if no offer). Use background mode so the client +retries automatically when a cable is connected after boot: + +```sh +# Wrong — exits immediately if no DHCP offer +udhcpc -i "$iface" -t 3 -T 5 -n -q + +# Correct — background daemon, retries indefinitely +udhcpc -i "$iface" -b -t 0 -T 3 -q +``` + +The network service itself should complete immediately (exit 0) — udhcpc daemons run in background. + +--- + +## Service Start Order (typical LiveCD) + +``` +localmount + └── sshsetup (user creation, key injection — before dropbear) + └── dropbear (SSH — independent of network) + └── network (DHCP on all interfaces — does not block anything) + └── nvidia (or other hardware init — after network in case firmware needs it) + └── audit (main workload — after all hardware ready) +``` + +Services at the same level can start in parallel. Use `after` not `need` for ordering without hard dependency. + +--- + +## Error Handling in start() + +```sh +start() { + ebegin "Running audit" + /usr/local/bin/audit --output /var/log/audit.json >> /var/log/audit.log 2>&1 + local rc=$? + if [ $rc -eq 0 ]; then + einfo "Audit complete" + else + ewarn "Audit finished with errors — check /var/log/audit.log" + fi + eend 0 # always 0 — never fail the runlevel +} +``` + +- Capture exit code into a local variable. +- Log the result with `einfo` or `ewarn`. +- Always `eend 0` — a failed audit is not a reason to block the boot runlevel. +- The exception: services whose failure makes SSH impossible (e.g. key setup) may `return 1`. + +--- + +## Rules + +- Every `start()` ends with `eend 0` unless failure makes the entire environment unusable. +- Network is always best-effort. Test for it, don't depend on it. +- Proprietary drivers (NVIDIA, etc.): load failure → log warning → continue without enrichment. +- External tools (ipmitool, smartctl, etc.): not-found → skip that data source → do not abort. +- Timeout all external commands: `timeout 30 smartctl ...` prevents infinite hangs. +- Write all output to `/var/log/` — TTY output is secondary. diff --git a/rules/patterns/vendor-installer-verification/contract.md b/rules/patterns/vendor-installer-verification/contract.md new file mode 100644 index 0000000..3ca3def --- /dev/null +++ b/rules/patterns/vendor-installer-verification/contract.md @@ -0,0 +1,82 @@ +# Contract: Vendor Installer Verification + +Version: 1.0 + +## Purpose + +Rules for downloading and verifying proprietary vendor installers (`.run`, `.exe`, `.tar.gz`) +where the vendor publishes a checksum alongside the binary. +Applies to: NVIDIA drivers, vendor CLI tools, firmware packages. + +--- + +## Download Order + +Always download the checksum file **before** the installer: + +```sh +BASE_URL="https://vendor.example.com/downloads/${VERSION}" +BIN_FILE="/var/cache/vendor-${VERSION}.run" +SHA_FILE="/var/cache/vendor-${VERSION}.run.sha256sum" + +# 1. Download checksum first +wget -q -O "$SHA_FILE" "${BASE_URL}/vendor-${VERSION}.run.sha256sum" + +# 2. Download installer +wget --show-progress -O "$BIN_FILE" "${BASE_URL}/vendor-${VERSION}.run" + +# 3. Verify +cd /var/cache +sha256sum -c "$SHA_FILE" || { echo "ERROR: sha256 mismatch"; rm -f "$BIN_FILE"; exit 1; } +``` + +Reason: if the download is interrupted, you have the expected checksum to verify against on retry. + +--- + +## Cache with Verification + +Never assume a cached file is valid — a previous download may have been interrupted (0-byte file): + +```sh +verify_cached() { + [ -s "$SHA_FILE" ] || return 1 # sha256 file missing or empty + [ -s "$BIN_FILE" ] || return 1 # binary missing or empty + cd "$(dirname "$BIN_FILE")" + sha256sum -c "$SHA_FILE" --status 2>/dev/null +} + +if ! verify_cached; then + rm -f "$BIN_FILE" "$SHA_FILE" + # ... download and verify +else + echo "verified from cache" +fi +``` + +**Never check only for file existence.** Check that the file is non-empty (`-s`) AND passes checksum. + +--- + +## Version Validation + +Before writing build scripts, verify the version URL actually exists: + +```sh +curl -sIL "https://vendor.example.com/downloads/${VERSION}/installer.run" \ + | grep -i 'http/\|content-length' +``` + +A `404` or `content-length: 0` means the version does not exist on that CDN. +Vendor version numbering may have gaps (e.g. NVIDIA skips minor versions on some CDNs). + +--- + +## Rules + +- Download checksum before installer — never after. +- Verify checksum before extracting or executing. +- On mismatch: delete the file, exit with error. Never proceed with a bad installer. +- Cache by `version` + any secondary key (e.g. kernel version for compiled modules). +- Never commit installer files to git — always download at build time. +- Log the expected hash when downloading so failures are diagnosable.