docs: document disk images, installer, vendor packaging, and secure boot

- docs/secureboot.md: MOK workflow (generate, build, enroll).
- docs/building.md: image + install quickstart, new outputs, ARCLINE_SIGN.
- docs/observability.md: vendor .debs are now the primary packaging path.
- docs/editions.md: cloud ships a qcow2; qemu test command.
- docs/architecture.md: full pipeline table incl. deploy/install; the
  four follow-up items are now implemented; new "on the horizon" list.
- README: updated feature list, quickstart, and status.
This commit is contained in:
Blake Ridgway
2026-08-21 13:33:17 -05:00
parent 3a17504dd0
commit a27d3fb313
6 changed files with 155 additions and 31 deletions

View File

@@ -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-<edition>` 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

View File

@@ -35,16 +35,20 @@ Pipeline stages live in `scripts/`:
| overlay | `apply-overlays.sh` | copies `overlays/base` + `overlays/<edition>` 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-<edition>` |
## 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-<edition>` produces bootable qcow2/raw images;
the cloud edition ships as a qcow2 by default.
2. **Installer**`scripts/install.sh <device>` 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).

View File

@@ -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/<edition>/` | extracted rootfs (from `make rootfs-*`) |
| `build/artifacts/arcline-<edition>-<version>-<arch>.tar.xz` | archived rootfs |
| `build/artifacts/arcline-<edition>-<version>-<arch>.iso` | bootable live ISO |
| `build/debs/*.deb` | Arcline toolchain packages (from `make toolchain`) |
| `build/artifacts/arcline-<edition>-<version>-<arch>.qcow2` | bootable disk image |
| `build/debs/*.deb` | Arcline toolchain + vendor packages |
Each artifact ships with a `.sha256` checksum file.
## Disk images & installing
- `make image-<edition>` 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

View File

@@ -3,11 +3,11 @@
Three flavours, one hardened base. Each is defined entirely by its manifest
under `editions/<name>/`.
| 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
```

View File

@@ -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_<v>_amd64.deb, arcline-loki_<v>.deb, arcline-promtail_<v>.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

61
docs/secureboot.md Normal file
View File

@@ -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.