diff --git a/.gitignore b/.gitignore index 17b872a..c2d78e8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ hosts/*/local.nix *.tfstate* .terraform/ *.bak + +# Appliance image build outputs (Makefile --out-link target dir + artifacts) +out/ +*.iso +*.qcow2 +*.raw diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d93e105 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Coder box — appliance image build targets. +# +# An "appliance" is the box prebuilt as a bootable image (no nixos/install.sh): +# it boots straight into the fully-configured Coder box. Three formats: +# +# make appliance/iso # live ISO (tmpfs overlay; state wiped on reboot) +# make appliance/qcow2 # disk image (persistent; boots in QEMU/libvirt) +# make appliance/raw # disk image (persistent; dd-able to a drive) +# +# Each format also takes an architecture suffix; short names are normalized to +# a *-linux triple (e.g. aarch64 -> aarch64-linux): +# +# make appliance/iso/x86_64-linux +# make appliance/qcow2/aarch64-linux +# make appliance/raw/aarch64 +# +# Requires Nix with flakes enabled (nix-command + flakes). All builds run on +# Linux only; cross-arch builds need a matching builder (native remote builder +# or binfmt/QEMU emulation). qcow2/raw additionally boot a QEMU VM during the +# build (disko image builder), so they want KVM to be fast. +# +# Outputs land in ./result (printed out-path). Flash a raw image or the ISO to +# a drive with e.g. +# sudo dd if=result/...img of=/dev/sdX bs=4M status=progress oflag=sync + +NIX ?= nix +FLAKE ?= . + +# Normalize an arch token to a *-linux triple: $(call norm_arch,aarch64) -> aarch64-linux +norm_arch = $(if $(filter %-linux,$(1)),$(1),$(1)-linux) + +# Single build helper used by every target. extendModules lets us override +# nixpkgs.hostPlatform (per-arch) and the disko image format from one recipe, +# so adding a format/arch is just a thin target below — no duplicated nix +# plumbing. We ALWAYS pin nixpkgs.hostPlatform: when no arch is given we use +# `builtins.currentSystem` (the builder's native arch), otherwise the bare +# `appliance/` targets would inherit configuration.nix's +# `nixpkgs.hostPlatform = lib.mkOptionDefault "x86_64-linux"` and always build +# x86_64 even on an aarch64 host. `--impure` is what makes currentSystem +# available. +# $(1) = host (nixosConfigurations.) +# $(2) = system.build. (isoImage | diskoImages) +# $(3) = extra module fields (nix attrset body, may be empty) +# $(4) = arch token (empty = builder's native arch) +# The built image lives in /nix/store (always — that's how Nix works), but +# `--out-link` plants a GC-root symlink to it under ./out (named after the +# target, e.g. out/appliance-iso, out/appliance-raw-aarch64-linux). That's the +# native, non-copy way to surface the result in the repo: ./out/ points +# straight at the store path, and being a GC root it won't be garbage-collected. +# ./out is gitignored. +define box_build + @mkdir -p out + $(NIX) build --impure --no-write-lock-file --print-out-paths \ + --out-link 'out/$(subst /,-,$@)' --expr \ + 'let f = builtins.getFlake (toString ./.); in (f.nixosConfigurations.$(1).extendModules { modules = [ { nixpkgs.hostPlatform = "$(if $(4),$(call norm_arch,$(4)),$${builtins.currentSystem})"; $(3) } ]; }).config.system.build.$(2)' +endef + +.PHONY: appliance/iso appliance/qcow2 appliance/raw + +# ── appliance/iso — live ephemeral ISO (hosts/_appliance_iso) ──────────────── +appliance/iso: + $(call box_build,_appliance_iso,isoImage,,) +appliance/iso/%: + $(call box_build,_appliance_iso,isoImage,,$*) + +# ── appliance/qcow2 — persistent disk image (hosts/_appliance-disk) ────────── +appliance/qcow2: + $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "qcow2";,) +appliance/qcow2/%: + $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "qcow2";,$*) + +# ── appliance/raw — persistent disk image, dd-able (hosts/_appliance-disk) ──── +appliance/raw: + $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "raw";,) +appliance/raw/%: + $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "raw";,$*) diff --git a/README.md b/README.md index f0390cc..73a1ee9 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ NixOS configuration for Coder demo and workshop boxes. flake.nix # entry point: nixosConfigurations. per machine flake.lock # pinned nixpkgs / disko / nixos-facter-modules configuration.nix # shared NixOS config (all machines) +Makefile # appliance build targets: appliance/{iso,qcow2,raw}[/] local.nix.example # template copied to hosts//local.nix by install.sh .gitignore # ignores hosts/*/local.nix nixos/ @@ -38,6 +39,8 @@ nixos/ k3s-sysbox.nix # k3s + sysbox-runc runtime class k3s-podman.nix # k3s + rootless Podman socket screenconnect.nix # optional ScreenConnect remote access client + box-turnkey.nix # shared turn-key bits for prebuilt images (login + Coder bootstrap) + live-iso.nix # ephemeral live ISO module (hosts/_appliance_iso) pkgs/ coder.nix # custom Coder server package coderd-provider.nix # terraform-provider-coderd package @@ -49,6 +52,10 @@ hosts/ local.nix # gitignored: admin creds, secrets, SSH users templates/ nook-android/ # Workspace: build trmnl-nook-simple-touch APK + _appliance_iso/ # `_appliance_iso` host: ephemeral live "Box" ISO (no disk install) + default.nix # imports nixos/live-iso.nix only (no disko/facter/hardware-config) + _appliance-disk/ # `_appliance-disk` host: persistent qcow2/raw disk image + default.nix # imports disko-standard.nix + box-turnkey.nix coderd/ main.tf # manages all Coder templates via coderd Terraform provider templates/ @@ -60,10 +67,15 @@ coderd/ This repo is a Nix flake. `flake.nix` auto-discovers every subdirectory of `./hosts/` that contains a `default.nix` and exposes it as -`nixosConfigurations.`. The folder name is the hostname, so -`nixos-rebuild switch --flake .` auto-selects the right config on the -running box. Adding a new host means creating a host folder, no flake.nix -edit. The installer does this for you. +`nixosConfigurations.`. For normal install hosts the folder name +is also the hostname, so `nixos-rebuild switch --flake .` auto-selects the +right config on the running box. Adding a new host means creating a host +folder, no flake.nix edit. The installer does this for you. + +Hosts whose folder name starts with an underscore (`_appliance_iso`, +`_appliance-disk`) are image/appliance builds, not per-machine installs: they +do **not** get the folder-name hostname and instead inherit the central +default `networking.hostName = "coder-box"` (set in `configuration.nix`). Two community tools do the heavy lifting: @@ -108,6 +120,83 @@ The installer generates `hosts//{default.nix,local.nix,facter.json}`, > ``` > And use a BIOS-compatible disko layout instead of `disko-standard.nix`. +## Prebuilt images (The Box™ without `install.sh`) + +Sometimes you don't want to run the installer; you just want The Box™. Two +image flavours build the *exact same* configured system — KDE Plasma, the Coder +server, k3s, Podman, the bundled templates — with admin bootstrap and template +deploy happening on boot just like a real install. Neither is an installer. + +These prebuilt images are called **appliances** (the box, prebuilt — no +`install.sh`). Build them with `make appliance/`: + +| Format | Host | State | Build | +|---|---|---|---| +| **iso** (live, ephemeral) | `live` | tmpfs overlay — wiped on reboot | `make appliance/iso` | +| **qcow2** (persistent disk) | `persistent-disk` | persists across reboots | `make appliance/qcow2` | +| **raw** (persistent disk) | `persistent-disk` | persists across reboots | `make appliance/raw` | + +All builds need a Linux machine with Nix + flakes. Every target also takes an +architecture suffix (short names are normalized to `*-linux`); cross-arch +builds need a matching builder (native remote builder or binfmt/QEMU): + +```sh +make appliance/iso/aarch64-linux +make appliance/qcow2/aarch64-linux +make appliance/raw/x86_64 +``` + +Each target drops a `--out-link` (GC-root symlink) in `./out/` named after the +target — e.g. `out/appliance-iso`, `out/appliance-raw-aarch64-linux` — pointing +straight at the built image in the Nix store (no copy; `./out` is gitignored). +The ISO is then at `out/appliance-iso/iso/coder-box-appliance-*.iso`, and a disk +image at `out/appliance-raw/coder-box-appliance-*.raw` (or +`out/appliance-qcow2/coder-box-appliance-*.qcow2`). All names carry the arch, +e.g. `coder-box-appliance-aarch64-linux.iso`. + +The turn-key login + Coder admin bootstrap shared by both flavours live in +[`nixos/box-turnkey.nix`](nixos/box-turnkey.nix): autologin to the `coderbox` +desktop, and admin `admin@coder.com` / `PleaseChangeMe1234`. Coder comes up at +`http://.local:3000` (or the `*.try.coder.app` tunnel URL in +`/etc/motd`). Change these before sharing an image by dropping a gitignored +`hosts//local.nix` (same shape as `local.nix.example`). + +### Live ISO (`live`) + +The live root filesystem is the squashfs + tmpfs overlay from nixpkgs' +`iso-image.nix`, so there's no partition to format or mount and **all state is +discarded on reboot**. `hosts/_appliance_iso/default.nix` imports +[`nixos/live-iso.nix`](nixos/live-iso.nix) (which pulls in `box-turnkey.nix`) — +**no** `disko-standard.nix`, `hardware-configuration.nix`, or `facter.json`. +The installed-machine `systemd-boot` / EFI-variable settings are forced off; the +ISO carries its own GRUB-EFI + isolinux loader (BIOS boot is x86-only, so the +aarch64 ISO is EFI-only). Flash it (it's isohybrid) and boot: + +```sh +sudo dd if=out/appliance-iso/iso/coder-box-appliance-*.iso of=/dev/sdX bs=4M status=progress oflag=sync +``` + +### Persistent disk image (`persistent-disk`) + +Built with [disko](https://github.com/nix-community/disko)'s image builder, so +it carries the real on-disk GPT layout from `nixos/disko-standard.nix` (1 GB +ESP + ext4 root) and **state survives reboots**, exactly like a machine you ran +`install.sh` on. `hosts/_appliance-disk/default.nix` imports +`disko-standard.nix` + `box-turnkey.nix`. + +- **`qcow2`** — boot it directly in QEMU/libvirt/UTM. A qcow2 is a container + format, so it can **not** be `dd`'d to a drive as-is — convert first + (`qemu-img convert -O raw box.qcow2 box.img`) or build the raw image instead. +- **`raw`** — a plain disk image you can `dd` straight onto a physical drive: + ```sh + sudo dd if=result/*.img of=/dev/sdX bs=4M status=progress oflag=sync + ``` + +Both image hosts are completely separate from the disk-install flow above +(`nixos/install.sh`, `nixos-facter`); adding them changes nothing for normal +installs. The `persistent-disk` host shares only the disk *layout* +(`disko-standard.nix`) with real installs, never the install process itself. + ## After install The installer auto-creates the admin user, mints a long-lived API token to diff --git a/agents.md b/agents.md index 99a8a5d..c4823d7 100644 --- a/agents.md +++ b/agents.md @@ -184,10 +184,16 @@ sudo k3s kubectl describe pod -n coder-workspaces k3s-sysbox.nix # k3s + sysbox runtime k3s-podman.nix # k3s + rootless Podman socket screenconnect.nix # ScreenConnect remote access client + box-turnkey.nix # shared turn-key bits for prebuilt images (login + Coder bootstrap) + live-iso.nix # ephemeral live "Box" ISO module (imported by hosts/_appliance_iso) pkgs/ coder.nix # Coder server package derivation coderd-provider.nix # terraform-provider-coderd derivation hosts/ + _appliance_iso/ # `_appliance_iso` host: ephemeral live "Box" ISO; no disko/facter/hardware-config + # build: make appliance/iso (or appliance/iso/) + _appliance-disk/ # `_appliance-disk` host: persistent qcow2/raw disk image (disko image builder) + # build: make appliance/qcow2 | make appliance/raw (or .../) coder-thinkcentre/ # folder name = hostname; default.nix has a hardware-model header comment default.nix # host module: imports facter/legacy + local.nix + thinkcentre-only services hardware-configuration.nix # legacy fallback (used until facter.json exists) diff --git a/configuration.nix b/configuration.nix index 1c88c8f..983e839 100644 --- a/configuration.nix +++ b/configuration.nix @@ -106,9 +106,19 @@ in zramSwap.enable = lib.mkDefault true; # ── Networking ──────────────────────────────────────────────────────────── - # networking.hostName is set by flake.nix's mkHost to the host folder - # name; per-host modules can override with lib.mkForce in - # hosts//local.nix or default.nix. + # Central default hostname. Install hosts override this: flake.nix's mkHost + # injects `networking.hostName = lib.mkDefault ` for every + # non-underscore host (so coder-thinkcentre stays coder-thinkcentre, etc.). + # Underscore-prefixed image/appliance hosts (_appliance_iso, _appliance-disk) + # get no injection and so inherit "coder-box". + # + # Priority 1250 (mkOverride) is deliberately BETWEEN mkDefault (1000) and + # mkOptionDefault (1500): it beats the option's own built-in default + # ("nixos", which nixpkgs sets at mkOptionDefault and would otherwise tie + # and error), while still losing to flake.nix's mkDefault folder-name + # injection on install hosts. A host's local.nix/default.nix can override at + # normal (100) priority or mkForce. + networking.hostName = lib.mkOverride 1250 "coder-box"; networking.networkmanager.enable = true; # mDNS: every box reachable as .local on the LAN diff --git a/flake.nix b/flake.nix index 9685672..32416ea 100644 --- a/flake.nix +++ b/flake.nix @@ -37,10 +37,12 @@ forAllSystems = lib.genAttrs systems; # Each subdirectory of ./hosts that contains a default.nix becomes a - # nixosConfigurations entry. The folder name IS the hostname, so - # `nixos-rebuild switch --flake .` auto-selects the right config on - # the running box without needing `.#`. Adding a new host means - # just creating ./hosts//default.nix; no flake.nix edit. + # nixosConfigurations entry. For install hosts the folder name IS the + # hostname, so `nixos-rebuild switch --flake .` auto-selects the right + # config on the running box without needing `.#`. Adding a new host + # means just creating ./hosts//default.nix; no flake.nix edit. + # (Underscore-prefixed folders like _appliance_iso are image builds that + # skip the folder-name hostname; see mkHost below.) hostNames = lib.attrNames (lib.filterAttrs (name: type: type == "directory" @@ -58,8 +60,17 @@ disko.nixosModules.disko nixos-facter-modules.nixosModules.facter (./hosts + "/${hostname}") - { networking.hostName = lib.mkDefault hostname; } - ]; + ] + # Install hosts use their folder name as the hostname so + # `nixos-rebuild switch --flake .` auto-selects the right config on the + # running box. Underscore-prefixed folders (e.g. _appliance_iso, + # _appliance-disk) are image/appliance builds whose names aren't valid + # hostnames and aren't installed per-machine; they fall through to the + # central default (networking.hostName = "coder-box" in + # configuration.nix). mkDefault here (1000) overrides that central + # mkOptionDefault (1500) for install hosts. + ++ lib.optional (!lib.hasPrefix "_" hostname) + { networking.hostName = lib.mkDefault hostname; }; }; in { nixosConfigurations = diff --git a/hosts/_appliance-disk/default.nix b/hosts/_appliance-disk/default.nix new file mode 100644 index 0000000..b61656c --- /dev/null +++ b/hosts/_appliance-disk/default.nix @@ -0,0 +1,57 @@ +# Persistent "Box" disk image host — "it's just The Box™" on a real disk. +# +# Folder name = nixosConfigurations attribute (see flake.nix host +# auto-discovery), so this host is exposed as `nixosConfigurations._appliance-disk`. +# Unlike the live ISO (hosts/_appliance_iso), this builds a *persistent* disk +# image (qcow2 or raw) using disko's image builder: it carries the real on-disk +# GPT layout (1 GB ESP + ext4 root from nixos/disko-standard.nix) and state +# survives reboots, exactly like a machine you ran nixos/install.sh on. +# +# Build (the format is chosen at build time, see Makefile / README): +# +# make appliance/qcow2 # qcow2 for this machine's arch +# make appliance/raw # raw (dd-able straight to a drive) +# make appliance/qcow2/aarch64-linux # cross-arch (needs a matching builder) +# +# # without make, e.g. a raw image: +# nix build .#nixosConfigurations._appliance-disk.config.system.build.diskoImages +# # (override disko.imageBuilder.imageFormat = "qcow2" for qcow2) +# +# This host is independent of nixos/install.sh; it shares the disk LAYOUT with +# real installs (disko-standard.nix) but is never itself part of the install +# flow. The turn-key login + Coder admin bootstrap (shared with the live ISO) +# live in nixos/box-turnkey.nix. + +{ lib, pkgs, ... }: + +{ + imports = [ + ../../nixos/disko-standard.nix # 1 GB ESP + ext4 root single-disk layout + ../../nixos/box-turnkey.nix # shared turn-key config (login + Coder bootstrap) + ] ++ lib.optional (builtins.pathExists ./local.nix) ./local.nix; + + # No networking.hostName here on purpose: underscore-prefixed image hosts get + # no folder-name injection from flake.nix and inherit the central default + # "coder-box" (configuration.nix). Override in local.nix if you need another. + + # disko writes the image for this device node; /dev/vda is the virtio disk a + # built image is partitioned against. The on-disk filesystems mount by LABEL + # (see disko-standard.nix), so the image still boots if the runtime device + # node differs (sda/nvme0n1/etc.). + disko.devices.disk.main.device = lib.mkForce "/dev/vda"; + + # Output file name: disko defaults imageName to the disk attr name ("main"), + # which would produce main.raw / main.qcow2. Name it after the appliance and + # include the arch (like the ISO's image.baseName) so the built image is + # coder-box-appliance-.raw / .qcow2 — arch visible, and x86_64/aarch64 + # images don't collide in ./out. + disko.devices.disk.main.imageName = + lib.mkForce "coder-box-appliance-${pkgs.stdenv.hostPlatform.system}"; + + # The image is built offline in a VM with no EFI variable store, so install + # the bootloader without touching EFI variables. systemd-boot (enabled by + # default in configuration.nix) also writes the removable EFI fallback path + # (EFI/BOOT/BOOTX64.EFI), so the image still boots on firmware that has no + # pre-existing boot entry. + boot.loader.efi.canTouchEfiVariables = lib.mkForce false; +} diff --git a/hosts/_appliance_iso/default.nix b/hosts/_appliance_iso/default.nix new file mode 100644 index 0000000..5abe6bc --- /dev/null +++ b/hosts/_appliance_iso/default.nix @@ -0,0 +1,28 @@ +# Live "Box" ISO appliance host — "it's just The Box™", not an installer. +# +# Folder name = nixosConfigurations attribute (see flake.nix host +# auto-discovery), so this host is exposed as `nixosConfigurations._appliance_iso`. +# It's normally built via the Makefile rather than by attribute: +# +# make appliance/iso # → out/appliance-iso/iso/coder-box-appliance-*.iso +# # equivalently: +# nix build .#nixosConfigurations._appliance_iso.config.system.build.isoImage +# +# Unlike the install hosts (coder-thinkcentre, qemu-arm64), this host does NOT +# import nixos/disko-standard.nix, hardware-configuration.nix, or facter.json: +# the live root filesystem is the squashfs + tmpfs overlay provided by +# nixos/live-iso.nix. All of the live-specific wiring lives in that module. +# +# This host is independent of nixos/install.sh and never participates in the +# disk-install flow; adding it changes nothing for disko/nixos-install installs. + +{ lib, ... }: + +{ + imports = [ ../../nixos/live-iso.nix ] + ++ lib.optional (builtins.pathExists ./local.nix) ./local.nix; + + # No networking.hostName here on purpose: underscore-prefixed image hosts get + # no folder-name injection from flake.nix and inherit the central default + # "coder-box" (configuration.nix). Override in local.nix if you need another. +} diff --git a/nixos/box-turnkey.nix b/nixos/box-turnkey.nix new file mode 100644 index 0000000..87cb17e --- /dev/null +++ b/nixos/box-turnkey.nix @@ -0,0 +1,85 @@ +# Shared "turn-key" Box™ config — the bits that make an image boot straight +# into a fully-configured, ready-to-use Coder box with no install step. +# +# Imported by both image flavours: +# - nixos/live-iso.nix (live, ephemeral ISO: hosts/live) +# - hosts/persistent-disk/ (persistent disk image: qcow2 / raw) +# +# On real installs these settings come from nixos/install.sh + the gitignored +# hosts//local.nix it generates. The image flavours have no install step, +# so this module supplies the same turn-key defaults (same values the installer +# defaults to). Change them before handing an image to anyone untrusted, or +# override per-image via hosts//local.nix. + +{ config, lib, pkgs, modulesPath, self, inputs, ... }: + +{ + imports = [ + # Broad driver/firmware set so the image boots on arbitrary hardware / + # virtual machines. This replaces the per-host facter.json / + # hardware-configuration.nix that installed hosts rely on (image hosts + # ship neither). + (modulesPath + "/profiles/all-hardware.nix") + ]; + + # ── Bake the repo into the image at /etc/nixos-repo ────────────────────────── + # The on-disk installer copies the working tree to /etc/nixos-repo; the Coder + # bootstrap units (coder-init-admin.service, the coder-template-sync + # activation script) read templates from /etc/nixos-repo/coderd and the + # locally-packaged provider mirror. Point /etc/nixos-repo at the flake source + # baked into the image so it deploys templates exactly like an installed box. + # (The git-commit lookups in those scripts fall back to "unknown" when no + # .git is present, which is fine.) + # + # IMPORTANT: filter out build artifacts before baking. `self.outPath` is the + # flake source, but on a DIRTY working tree `getFlake`/`nix build .#…` copies + # untracked files into it *even if they're gitignored* — including the + # Makefile's ./out (where built images land) and any stray *.iso/*.qcow2/*.raw + # in the repo. Baking that unfiltered means each build's image gets embedded + # into /etc/nixos-repo → into the squashfs → into the *next* image, so the ISO + # grows on every rebuild (a feedback loop). cleanSourceWith strips those paths + # so the baked repo is stable regardless of build artifacts, while still + # shipping the full tree (coderd/ etc.) for nixos-rebuild / coder-reset. + environment.etc."nixos-repo".source = lib.cleanSourceWith { + name = "nixos-repo-src"; + src = self.outPath; + filter = path: type: + let base = baseNameOf (toString path); in + base != "out" + && base != "result" + && !(lib.hasPrefix "result-" base) + && !(lib.hasSuffix ".iso" base) + && !(lib.hasSuffix ".qcow2" base) + && !(lib.hasSuffix ".raw" base); + }; + + # Make the pinned nixpkgs resolvable on the box so `nix` / flake commands + # behave like an installed system, without shipping a channel. + nix.registry.nixpkgs.flake = inputs.nixpkgs; + + # ── Login + Coder admin bootstrap ──────────────────────────────────────────── + # Autologin drops straight into the Plasma desktop, mirroring a + # freshly-installed, configured box. + services.displayManager.autoLogin = { + enable = true; + user = "coderbox"; + }; + + users.users.coderbox = { + isNormalUser = true; + description = "coderbox"; + extraGroups = [ "networkmanager" "wheel" ]; + packages = [ pkgs.kdePackages.kate ]; + initialPassword = "coderbox"; + }; + + # coder-init-admin.service reads CODER_ADMIN_* from coder.service's + # environment and creates a local admin on first boot, then mints a session + # token and deploys the templates from /etc/nixos-repo/coderd. With these set + # the Coder instance is ready to use immediately. + systemd.services.coder.environment = { + CODER_ADMIN_EMAIL = "admin@coder.com"; + CODER_ADMIN_USERNAME = "admin"; + CODER_ADMIN_PASSWORD = "PleaseChangeMe1234"; + }; +} diff --git a/nixos/live-iso.nix b/nixos/live-iso.nix new file mode 100644 index 0000000..ac63fba --- /dev/null +++ b/nixos/live-iso.nix @@ -0,0 +1,65 @@ +# Live "Box" ISO module — "it's just The Box™", not an installer. +# +# Turns the shared Coder box configuration into a bootable *ephemeral* live ISO +# that runs entirely from the USB/CD + RAM, with no disk install. Booting it +# gives the same system the on-disk install produces (KDE, Coder server, k3s, +# Podman, the bundled templates, all started automatically) — but the root +# filesystem is a squashfs + tmpfs overlay, so all state is discarded on +# reboot. For a *persistent* image (state survives reboots) build the +# persistent-disk host instead (qcow2 / raw); see the Makefile / README. +# +# Build (folder name `live` => nixosConfigurations.live, see flake.nix): +# +# make live-ephemeral-iso +# # or: nix build .#nixosConfigurations.live.config.system.build.isoImage +# # → result/iso/coder-box-appliance-*.iso (flash with `dd`, Ventoy, etc.) +# +# This module is imported only by hosts/live/default.nix and is independent of +# the regular disk-install flow (nixos/install.sh, disko, nixos-facter). It +# imports NO disko / hardware-configuration.nix / facter.json: the live root is +# the squashfs + tmpfs overlay that nixpkgs' iso-image.nix sets up. +# +# The turn-key login + Coder admin bootstrap (shared with the persistent-disk +# image) live in nixos/box-turnkey.nix. + +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = [ + # Core ISO builder: squashfs nix store, tmpfs overlay root, kernel/initrd, + # and the EFI + BIOS ISO bootloader. Provides `system.build.isoImage` and + # the `isoImage.*` options used below. + (modulesPath + "/installer/cd-dvd/iso-image.nix") + # Shared turn-key config (all-hardware, baked /etc/nixos-repo, autologin, + # Coder admin bootstrap). + ./box-turnkey.nix + ]; + + # ── ISO image settings ────────────────────────────────────────────────────── + isoImage.makeEfiBootable = true; # boot on UEFI machines + # Legacy BIOS boot uses syslinux, which is x86-only. Enable it just for x86 + # so the same module also evaluates/builds for an aarch64 live ISO (which + # boots via EFI only). isx86 covers both i686 and x86_64. + isoImage.makeBiosBootable = pkgs.stdenv.hostPlatform.isx86; + isoImage.makeUsbBootable = true; # `dd` straight to a USB stick and boot + isoImage.volumeID = "BOX_LIVE"; + # Boot-menu label (both the BIOS/isolinux and EFI/grub entries). The label is + # " "; the default append is + # " Installer", which is misleading here since this is the live appliance, not + # the installer. Append " - Coder Box Appliance" -> "NixOS - Coder + # Box Appliance". Leading space is required (it's concatenated directly). + isoImage.appendToMenuLabel = " - Coder Box Appliance"; + # ISO file name. iso-image.nix derives isoName from image.baseName as + # ".iso", and defaults baseName to "nixos--". We + # override baseName (mkForce, to win over that default) but keep the arch + # suffix so the file is e.g. coder-box-appliance-aarch64-linux.iso — the arch + # is visible in the name and x86_64/aarch64 ISOs don't collide in ./out. + image.baseName = lib.mkForce "coder-box-appliance-${pkgs.stdenv.hostPlatform.system}"; + + # ── Boot loader: let iso-image.nix own it ──────────────────────────────────── + # configuration.nix sets these for installed UEFI machines; force them off so + # they don't conflict with the image's own bootloader or try to touch the + # host's EFI variables when the live system activates. + boot.loader.systemd-boot.enable = lib.mkForce false; + boot.loader.efi.canTouchEfiVariables = lib.mkForce false; +}