diff --git a/README.md b/README.md index aad69d0..6e2b556 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ manifests into hardened, bootable operating system images for three editions: - A transparent, script-based build pipeline (no magic, everything auditable) - `debootstrap` → package install → overlay → in-chroot configure → live ISO +- **Disk images + installer**: `make image-` builds bootable qcow2/raw + images; `scripts/install.sh` installs to a real disk (both share the + `deploy-disk.sh` core: partition → btrfs layout → GRUB → real fstab) - **Secure by default**: hardened kernel cmdline + sysctl, default-deny nftables, key-only ssh, AppArmor, no core dumps - **btrfs-native**: subvolume layout, scheduled read-only snapshots, and @@ -19,9 +22,12 @@ manifests into hardened, bootable operating system images for three editions: - **Zero telemetry**: enforced by package selection, masked apt timers, and smoke tests that fail the build if telemetry is found - **Pre-configured observability** (server): Prometheus + node_exporter + - Grafana (auto-provisioned) + Loki + promtail, local-only + Grafana (auto-provisioned) + Loki + promtail, local-only — Grafana/Loki/ + promtail packaged as `.debs` by `make vendor` - **The Arcline toolchain**: a harness that packages all 11 Go tools into `.deb`s and installs them into the image +- **Optional secure boot**: MOK-based signing of the boot chain + (`ARCLINE_SIGN=1`) - Smoke tests, GitLab CI, and docs that explain every decision ## Quickstart @@ -32,7 +38,9 @@ make deps # install host build dependencies make check # validate the tree (fast, offline) make iso-server # build a bootable server ISO make iso # build all three editions +make image-cloud # cloud edition as a qcow2 disk image make toolchain # build the 11 Go tools into .deb +make vendor # build grafana/loki/promtail .debs make iso-server-minimal # server ISO WITHOUT the Arcline toolchain make test # run smoke tests against built rootfs(es) ``` @@ -69,15 +77,16 @@ os-build/ | [editions](docs/editions.md) | server / workstation / cloud manifests | | [observability](docs/observability.md) | the pre-configured monitoring stack | | [toolchain](docs/toolchain.md) | the 11 Go tools and their packaging | +| [secureboot](docs/secureboot.md) | MOK key generation + signing workflow | | [btrfs](btrfs/README.md) | subvolume layout + snapshots + rollback | ## Status -This is the **foundation**: the build pipeline, edition manifests, hardening -baseline, btrfs tooling, observability configs, tests, and CI are in place and -runnable. The next milestones (installer that writes the btrfs layout to disk, -cloud disk images, signed releases) are listed in -[docs/architecture.md](docs/architecture.md#follow-up-work). +The build pipeline, edition manifests, hardening baseline, btrfs tooling, +observability packaging, disk images + installer, tests, CI, and optional +secure boot are all in place and runnable. What's next: a Microsoft-KEK signed +boot chain (only relevant for commercial distribution), `arm64` support, and +booted-VM verification tests. ## License diff --git a/docs/architecture.md b/docs/architecture.md index a2317b6..f4a48ba 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -35,16 +35,20 @@ Pipeline stages live in `scripts/`: | overlay | `apply-overlays.sh` | copies `overlays/base` + `overlays/` into the rootfs | | configure | `configure-system.sh` | runs *in the chroot*: hostname, locale, kernel cmdline, services, live-boot, toolchain | | package | `build-iso.sh` | kernel + initramfs + squashfs → hybrid BIOS/UEFI ISO | +| image | `build-image.sh` | rootfs → bootable qcow2/raw disk image (via `deploy-disk.sh`) | +| deploy | `deploy-disk.sh` | partition → btrfs layout → copy rootfs → GRUB + fstab (shared by image + installer) | +| install | `install.sh` | scripted installer for a real disk (confirmation-gated) | | orchestrate | `build-edition.sh` / `Makefile` | wire the above to `make iso-` | -## The four source trees +## The source trees | Path | Role | |------|------| | `editions/` | per-edition **manifests**: package lists, kernel cmdline, fstab, metadata | | `overlays/` | **files that land in the image**, organised as layered rootfs trees | | `scripts/` | the **build pipeline** (all plain bash, readable top to bottom) | -| `btrfs/`, `toolchain/`, `observability/`, `tests/`, `ci/` | supporting subsystems | +| `btrfs/`, `toolchain/`, `tests/`, `ci/` | supporting subsystems | +| `scripts/secureboot/` | MOK key generation + boot-chain signing (optional) | There is no hidden magic: the Makefile is a thin wrapper, `versions.mk` / `scripts/common.sh` hold the single source of truth for versions and paths. @@ -59,10 +63,23 @@ There is no hidden magic: the Makefile is a thin wrapper, `versions.mk` / - The smoke tests (`tests/smoke/verify-rootfs.sh`) fail the build if telemetry artifacts are found. -## Follow-up work (explicitly out of scope for "the wires") +## What "the wires" now covers -- Partitioning/installer that runs `btrfs/init.sh` on a target disk. -- Cloud images (qcow2/raw) for the `cloud` edition — the ISO path is wired, - a disk-image path is the next step. -- Grafana/Loki packaged as `.deb`s rather than fetched from upstream. -- A signed (secure-boot) kernel for official releases. +The four original follow-up items are implemented: + +1. **Disk images** — `make image-` produces bootable qcow2/raw images; + the cloud edition ships as a qcow2 by default. +2. **Installer** — `scripts/install.sh ` installs to a real disk + (explicit confirmation, reuses the deploy module). +3. **Grafana/Loki/Promtail `.debs`** — `make vendor` packages them so the full + observability stack installs without upstream repos. +4. **Secure boot** — MOK-based signing (`scripts/secureboot/`), off by + default, enabled with `ARCLINE_SIGN=1`. + +## Still on the horizon + +- A signed Microsoft-KEK boot chain (only relevant for commercial + distribution; the MOK path covers self-hosted use). +- ARM64 (`arm64`) as a first-class arch (one-line change in `versions.mk`). +- Boot-time verification tests for installed systems (the smoke tests cover + the image contents, not a booted VM yet). diff --git a/docs/building.md b/docs/building.md index 7508159..5b8db63 100644 --- a/docs/building.md +++ b/docs/building.md @@ -27,7 +27,9 @@ make check # validate the tree (fast, offline, safe) make rootfs-server # build just the server rootfs make iso-server # build a bootable server ISO make iso # build all three editions +make image-cloud # build the cloud edition as a qcow2 disk image make toolchain # build the 11 Go tools into .deb +make vendor # build Grafana/Loki/Promtail .debs make test # run smoke tests against built rootfs(es) ``` @@ -40,10 +42,30 @@ Everything lands under `build/`: | `build/rootfs//` | extracted rootfs (from `make rootfs-*`) | | `build/artifacts/arcline---.tar.xz` | archived rootfs | | `build/artifacts/arcline---.iso` | bootable live ISO | -| `build/debs/*.deb` | Arcline toolchain packages (from `make toolchain`) | +| `build/artifacts/arcline---.qcow2` | bootable disk image | +| `build/debs/*.deb` | Arcline toolchain + vendor packages | Each artifact ships with a `.sha256` checksum file. +## Disk images & installing + +- `make image-` builds a bootable **qcow2** disk image (the cloud + edition's primary output; also handy for VM-testing server/workstation). + Options: `--format raw`, `--size 4G`, `--boot efi`. +- `scripts/install.sh /dev/sdX --edition server` installs a built rootfs onto + a real disk (asks for explicit confirmation, then partitions, lays out + btrfs subvolumes, installs GRUB, and writes a real fstab). Both reuse the + shared `scripts/deploy-disk.sh`. + +## Secure boot + +Optional, MOK-based. See [secureboot](secureboot.md): + +```bash +scripts/secureboot/gen-keys.sh +ARCLINE_SIGN=1 make iso-server +``` + ## Reproducibility knobs Set these as environment variables or edit `versions.mk`: @@ -58,6 +80,7 @@ Set these as environment variables or edit `versions.mk`: | `ARCLINE_LOCK_ROOT` | lock root account (`passwd -l root`) | `0` | | `ARCLINE_EXTRA_REPOS` | fetch grafana/loki upstream repos | `0` | | `ARCLINE_TOOLCHAIN` | toolchain in image builds | `auto` | +| `ARCLINE_SIGN` | sign boot chain with the MOK (secure boot) | `0` | ## Building without the Arcline toolchain diff --git a/docs/editions.md b/docs/editions.md index 2477d92..ee94474 100644 --- a/docs/editions.md +++ b/docs/editions.md @@ -3,11 +3,11 @@ Three flavours, one hardened base. Each is defined entirely by its manifest under `editions//`. -| Edition | Codename | Purpose | Kernel | Notes | -|---------|----------|---------|--------|-------| -| `server` | bastion | production server | generic (`linux-image-amd64`) | full observability stack, containers | -| `workstation` | forge | hardened daily driver | generic | KDE Plasma, dev toolchains | -| `cloud` | nimbus | cloud images | cloud (`linux-image-cloud-amd64`) | cloud-init, guest agents | +| Edition | Codename | Purpose | Kernel | Output | +|---------|----------|---------|--------|--------| +| `server` | bastion | production server | generic (`linux-image-amd64`) | ISO (and qcow2 for VM testing) | +| `workstation` | forge | hardened daily driver | generic | ISO | +| `cloud` | nimbus | cloud images | cloud (`linux-image-cloud-amd64`) | **qcow2/raw disk image** (`make image-cloud`) ## What an edition manifest contains @@ -39,6 +39,10 @@ and privacy-oriented defaults. Minimal footprint: cloud kernel, cloud-init (NoCloud/ConfigDrive/EC2/GCE/ Azure), guest agents (qemu/vmware), NVMe + iSCSI + multipath tooling. -`net.ifnames=0` for predictable, provider-friendly interface naming. The -disk-image (qcow2) output path is the next milestone — the manifest is ready, -the ISO path is wired today. +`net.ifnames=0` for predictable, provider-friendly interface naming. Ships as +a bootable **qcow2** disk image (`make image-cloud`) — BIOS boot by default, +UEFI via `--boot efi`; upload to your provider or test with qemu: + +``` +qemu-system-x86_64 -m 2G -drive file=build/artifacts/arcline-cloud-0.1.0-amd64.qcow2,format=qcow2 -nographic +``` diff --git a/docs/observability.md b/docs/observability.md index fed0a6d..6f1f587 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -32,16 +32,26 @@ service. ## Packaging note Prometheus + node_exporter + alertmanager are in Debian main and install with -the edition packages. **Grafana** and **Loki** are not: +the edition packages. **Grafana**, **Loki**, and **Promtail** are not — but +they're now packaged as `.debs` by `toolchain/build-vendor.sh`: -- Grafana is available via the official `apt.grafana.com` repo — add it by - setting `ARCLINE_EXTRA_REPOS=1` during the build - (`configure-system.sh` adds the repo + key). -- Loki ships as a static binary tarball; the packaging (a `.deb` in - `toolchain/`) is follow-up work. +```bash +make vendor # → build/debs/grafana__amd64.deb, arcline-loki_.deb, arcline-promtail_.deb +make iso-server +``` -Until then, the Grafana/Loki configs ship dormant in the image, ready for when -the binaries are installed — the `overlays/server` configs are the contract. +The debs land in `build/debs/` alongside the toolchain, so the next rootfs +build picks them up automatically and the configure hook enables +`grafana-server` / `loki` / `promtail` when their binaries are present. + +- `grafana` — the official OSS `.deb` from `dl.grafana.com`, vendored as-is. +- `arcline-loki` / `arcline-promtail` — static binaries from the Loki GitHub + release, wrapped in minimal debs that install the configs from + `overlays/server/etc/` and systemd units from `toolchain/vendor/`. + +If the downloads fail (no network / version bumped), the build skips them with +a warning — the old `ARCLINE_EXTRA_REPOS=1` path (apt.grafana.com) remains as +a fallback. ## Access diff --git a/docs/secureboot.md b/docs/secureboot.md new file mode 100644 index 0000000..5f47ce0 --- /dev/null +++ b/docs/secureboot.md @@ -0,0 +1,61 @@ +# Secure boot (MOK-based) + +Arcline can ship **self-signed** boot chains verified by your machine's own +secure-boot firmware. We use a **Machine Owner Key (MOK)** — the same approach +used to load custom kernels on Windows-certified laptops — rather than paying +for a Microsoft KEK signing cert. You own the key, you own the trust anchor. + +``` + MOK.priv ──(sbsign)──► vmlinuz, grubx64.efi, shimx64.efi + MOK.der ──(mokutil)──► enrolled into firmware MOK list (one-time prompt) +``` + +## Workflow + +1. **Generate the key** (once, keep it secret): + + ```bash + scripts/secureboot/gen-keys.sh # → build/keys/{MOK.priv,MOK.pem,MOK.der} + ``` + +2. **Build a signed image** (ISO or disk image): + + ```bash + ARCLINE_SIGN=1 make iso-server + ARCLINE_SIGN=1 make image-cloud + ``` + + The build signs every kernel + EFI binary in the boot chain with the MOK + and ships the *public* `MOK.der` into the image at `/etc/arcline/MOK.der`. + +3. **Enroll on first boot** — the `arcline-mok-enroll.service` unit imports the + key automatically the first time the system boots with secure boot enabled. + The firmware shows a one-time "Enroll MOK" prompt; confirm it, reboot, done. + The unit disables itself afterwards (and no-ops entirely when no key was + shipped — secure boot is off by default). + + Manual alternative: + + ```bash + sudo mokutil --import /etc/arcline/MOK.der + sudo reboot # then confirm at the blue MOK manager screen + ``` + +## What gets signed + +- kernels (`vmlinuz*`) — in `/boot` for installed systems, `/live` for ISOs +- EFI binaries (`*.efi`) — grubx64, shimx64, mmx64, fbx64 + +GRUB `.mod` modules are not PE binaries and are not individually signed (GRUB +has its own module-signature mechanism, out of scope here). If you use the +shim-provided fallback loader, the Microsoft-signed shim validates grubx64.efi +against your MOK. + +## Notes + +- Requires `sbsigntool` on the build host and `mokutil` in the image + (`mokutil` is pulled in by the `shim-signed` package already in the package + lists). +- Losing `MOK.priv` means you cannot sign future updates — back it up. +- Full vendor CA / Microsoft KEK signing is intentionally not used; revisit + only if a commercial distribution is ever pursued.