From f0650180b9a0d6db45ebc176d29caba074c3483c Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 13:22:17 +0000 Subject: [PATCH 01/18] refactor: reorg image modules into nixos/_images/{_base,_appliance,_installer}; add installer ISO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize the prebuilt-image modules and add a new installer ISO host. nixos/ layout: - nixos/_images/_base/hardware.nix — all-hardware (single source) - nixos/_images/_base/iso.nix — shared ISO mechanics (iso-image.nix import, EFI/BIOS/USB bootable, bootloader overrides); NO image identity - nixos/_images/box-turnkey.nix — moved from nixos/_appliance/; shared turn-key Coder box (bake /etc/nixos-repo, registry, coderbox autologin, coder admin env); now imports _base/hardware.nix and is shared by all image hosts - nixos/_images/_appliance/iso.nix — was nixos/_appliance/live-iso.nix; now only sets appliance image identity (volumeID CODER_BOX_APPLIANCE, menu label, baseName) - nixos/_images/_installer/iso.nix — NEW; mirrors the appliance ISO (full GUI box + turn-key) but with installer identity (CODER_BOX_INSTALLER / coder-box-installer-) hosts/: - _appliance_iso, _appliance-disk: import paths updated to nixos/_images/... - _installer-iso: NEW host (ISO only) Makefile: add installer/iso[/] targets (reuses box_build). flake.nix: comment-only (installer keeps configuration.nix → GUI on). Docs: README + agents.md updated for the _images/ layout and installer ISO. The installer is intentionally GUI-on for now; the minimal/bash-only installer environment is deferred. Verified (nix): all 5 hosts eval; installer ISO builds end-to-end (4.1G, out/installer-iso/iso/coder-box-installer-x86_64-linux.iso) with volumeID CODER_BOX_INSTALLER, xserver enabled, coder admin env, autologin coderbox, hostname coder-box; appliance ISO/qcow2/raw still eval (volumeID CODER_BOX_APPLIANCE); coder-thinkcentre & qemu-arm64 toplevel drv hashes byte-identical to origin/main (no install-flow regression). --- Makefile | 19 +++++- README.md | 49 ++++++++++---- agents.md | 13 ++-- flake.nix | 5 +- hosts/_appliance-disk/default.nix | 6 +- hosts/_appliance_iso/default.nix | 4 +- hosts/_installer-iso/default.nix | 29 ++++++++ nixos/_appliance/live-iso.nix | 66 ------------------- nixos/_images/_appliance/iso.nix | 44 +++++++++++++ nixos/_images/_base/hardware.nix | 9 +++ nixos/_images/_base/iso.nix | 31 +++++++++ nixos/_images/_installer/iso.nix | 35 ++++++++++ nixos/{_appliance => _images}/box-turnkey.nix | 20 +++--- 13 files changed, 231 insertions(+), 99 deletions(-) create mode 100644 hosts/_installer-iso/default.nix delete mode 100644 nixos/_appliance/live-iso.nix create mode 100644 nixos/_images/_appliance/iso.nix create mode 100644 nixos/_images/_base/hardware.nix create mode 100644 nixos/_images/_base/iso.nix create mode 100644 nixos/_images/_installer/iso.nix rename nixos/{_appliance => _images}/box-turnkey.nix (81%) diff --git a/Makefile b/Makefile index 7a76e67..28571a8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -# Coder box — appliance image build targets. +# Coder box — image build targets. # # An "appliance" is the box prebuilt as a bootable image (no install.sh): # it boots straight into the fully-configured Coder box. Three formats: @@ -7,12 +7,19 @@ # 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 +# The "installer" is the box as an ISO that will install coder/box onto real +# hardware. For now it boots the same full GUI box as the appliance ISO; ISO only +# (no disk images): +# +# make installer/iso +# +# Each target 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 +# make installer/iso/aarch64-linux # # Requires Nix with flakes enabled (nix-command + flakes). All builds run on # Linux only; cross-arch builds need a matching builder (native remote builder @@ -55,7 +62,7 @@ define box_build '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 +.PHONY: appliance/iso appliance/qcow2 appliance/raw installer/iso # ── appliance/iso — ephemeral appliance ISO (hosts/_appliance_iso) ─────────── appliance/iso: @@ -74,3 +81,9 @@ appliance/raw: $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "raw";,) appliance/raw/%: $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "raw";,$*) + +# ── installer/iso — installer ISO (hosts/_installer-iso); ISO only ──────────── +installer/iso: + $(call box_build,_installer-iso,isoImage,,) +installer/iso/%: + $(call box_build,_installer-iso,isoImage,,$*) diff --git a/README.md b/README.md index 441ec11..a4d857a 100644 --- a/README.md +++ b/README.md @@ -29,7 +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}[/] +Makefile # image build targets: appliance/{iso,qcow2,raw}, installer/iso [/] local.nix.example # template copied to hosts//local.nix by install.sh .gitignore # ignores hosts/*/local.nix install.sh # one-shot installer: disko + nixos-install + bake /etc/nixos-repo @@ -39,9 +39,15 @@ nixos/ k3s-sysbox.nix # k3s + sysbox-runc runtime class k3s-podman.nix # k3s + rootless Podman socket screenconnect.nix # optional ScreenConnect remote access client - _appliance/ # prebuilt-appliance modules (ISO + persistent disk) - box-turnkey.nix # shared turn-key bits for appliances (login + Coder bootstrap) - live-iso.nix # ephemeral appliance ISO module (hosts/_appliance_iso) + _images/ # prebuilt-image modules (appliance + installer) + box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap); all image hosts + _base/ # primitives shared by every image + hardware.nix # all-hardware (boot on arbitrary hardware) + iso.nix # ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, bootloader) + _appliance/ + iso.nix # appliance ISO module (hosts/_appliance_iso) + _installer/ + iso.nix # installer ISO module (hosts/_installer-iso) pkgs/ coder.nix # custom Coder server package coderd-provider.nix # terraform-provider-coderd package @@ -54,9 +60,11 @@ hosts/ templates/ nook-android/ # Workspace: build trmnl-nook-simple-touch APK _appliance_iso/ # `_appliance_iso` host: ephemeral appliance ISO (no disk install) - default.nix # imports nixos/_appliance/live-iso.nix (no disko/facter/hardware-config) + default.nix # imports nixos/_images/_appliance/iso.nix (no disko/facter/hardware-config) _appliance-disk/ # `_appliance-disk` host: persistent qcow2/raw disk image - default.nix # imports disko-standard.nix + nixos/_appliance/box-turnkey.nix + default.nix # imports disko-standard.nix + nixos/_images/box-turnkey.nix + _installer-iso/ # `_installer-iso` host: installer ISO (ISO only; installs box onto hardware) + default.nix # imports nixos/_images/_installer/iso.nix coderd/ main.tf # manages all Coder templates via coderd Terraform provider templates/ @@ -74,7 +82,7 @@ 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 +`_appliance-disk`, `_installer-iso`) are image 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`). @@ -155,8 +163,8 @@ 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/_appliance/box-turnkey.nix`](nixos/_appliance/box-turnkey.nix): autologin to the `coderbox` +The turn-key login + Coder admin bootstrap shared by all image flavours live in +[`nixos/_images/box-turnkey.nix`](nixos/_images/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 @@ -167,7 +175,7 @@ desktop, and admin `admin@coder.com` / `PleaseChangeMe1234`. Coder comes up at The appliance 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/_appliance/live-iso.nix`](nixos/_appliance/live-iso.nix) (which pulls in `box-turnkey.nix`) — +[`nixos/_images/_appliance/iso.nix`](nixos/_images/_appliance/iso.nix) (which pulls in `_base/iso.nix` + `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 @@ -200,11 +208,30 @@ ESP + ext4 root) and **state survives reboots**, exactly like a machine you ran 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 +All image hosts are completely separate from the disk-install flow above (`install.sh`, `nixos-facter`); adding them changes nothing for normal installs. The `_appliance-disk` host shares only the disk *layout* (`disko-standard.nix`) with real installs, never the install process itself. +### Installer ISO (`_installer-iso`) + +The installer is the box as an ISO whose eventual job is to install `coder/box` +onto real hardware. **For now it is identical to the appliance ISO** (full GUI +box + turn-key Coder bootstrap), differing only in image identity (volume ID +`CODER_BOX_INSTALLER`, boot-menu label, and file name +`coder-box-installer-.iso`); the minimal/installer-only environment is a +future change. It builds **only as an ISO** (no qcow2/raw): + +```sh +make installer/iso # native arch +make installer/iso/aarch64-linux # explicit arch +# → out/installer-iso/iso/coder-box-installer-*.iso +``` + +`hosts/_installer-iso/default.nix` imports +[`nixos/_images/_installer/iso.nix`](nixos/_images/_installer/iso.nix), which — +like the appliance ISO — pulls in `_base/iso.nix` + `box-turnkey.nix`. + ## 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 deadcc0..a9d303d 100644 --- a/agents.md +++ b/agents.md @@ -184,17 +184,22 @@ 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 - _appliance/ # prebuilt-appliance modules (ISO + persistent disk) - box-turnkey.nix # shared turn-key bits for appliances (login + Coder bootstrap) - live-iso.nix # ephemeral appliance ISO module (imported by hosts/_appliance_iso) + _images/ # prebuilt-image modules (appliance + installer) + box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap); all image hosts + _base/hardware.nix # all-hardware (boot on arbitrary hardware) + _base/iso.nix # shared ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, bootloader) + _appliance/iso.nix # appliance ISO module (imported by hosts/_appliance_iso) + _installer/iso.nix # installer ISO module (imported by hosts/_installer-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 + _appliance_iso/ # `_appliance_iso` host: ephemeral appliance 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 .../) + _installer-iso/ # `_installer-iso` host: installer ISO (ISO only; full GUI box for now) + # build: make installer/iso (or installer/iso/) 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/flake.nix b/flake.nix index 4c086ba..b5cc7aa 100644 --- a/flake.nix +++ b/flake.nix @@ -41,8 +41,9 @@ # 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.) + # (Underscore-prefixed folders like _appliance_iso, _appliance-disk, and + # _installer-iso are image builds that skip the folder-name hostname; see + # mkHost below.) hostNames = lib.attrNames (lib.filterAttrs (name: type: type == "directory" diff --git a/hosts/_appliance-disk/default.nix b/hosts/_appliance-disk/default.nix index b4d4046..40348b2 100644 --- a/hosts/_appliance-disk/default.nix +++ b/hosts/_appliance-disk/default.nix @@ -20,14 +20,14 @@ # This host is independent of 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 appliance ISO) -# live in nixos/_appliance/box-turnkey.nix. +# live in nixos/_images/box-turnkey.nix. { lib, pkgs, ... }: { imports = [ - ../../nixos/disko-standard.nix # 1 GB ESP + ext4 root single-disk layout - ../../nixos/_appliance/box-turnkey.nix # shared turn-key config (login + Coder bootstrap) + ../../nixos/disko-standard.nix # 1 GB ESP + ext4 root single-disk layout + ../../nixos/_images/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 diff --git a/hosts/_appliance_iso/default.nix b/hosts/_appliance_iso/default.nix index a9e6a16..c43f446 100644 --- a/hosts/_appliance_iso/default.nix +++ b/hosts/_appliance_iso/default.nix @@ -11,7 +11,7 @@ # Unlike the install hosts (coder-thinkcentre, qemu-arm64), this host does NOT # import nixos/disko-standard.nix, hardware-configuration.nix, or facter.json: # the appliance root filesystem is the squashfs + tmpfs overlay provided by -# nixos/_appliance/live-iso.nix. All of the appliance-ISO wiring lives there. +# nixos/_images/_appliance/iso.nix. All of the appliance-ISO wiring lives there. # # This host is independent of install.sh and never participates in the # disk-install flow; adding it changes nothing for disko/nixos-install installs. @@ -19,7 +19,7 @@ { lib, ... }: { - imports = [ ../../nixos/_appliance/live-iso.nix ] + imports = [ ../../nixos/_images/_appliance/iso.nix ] ++ lib.optional (builtins.pathExists ./local.nix) ./local.nix; # No networking.hostName here on purpose: underscore-prefixed image hosts get diff --git a/hosts/_installer-iso/default.nix b/hosts/_installer-iso/default.nix new file mode 100644 index 0000000..944187e --- /dev/null +++ b/hosts/_installer-iso/default.nix @@ -0,0 +1,29 @@ +# Installer ISO host — boots the Coder box to install it onto real hardware. +# +# Folder name = nixosConfigurations attribute (see flake.nix host +# auto-discovery), so this host is exposed as `nixosConfigurations._installer-iso`. +# It's normally built via the Makefile rather than by attribute: +# +# make installer/iso # → out/installer-iso/iso/coder-box-installer-*.iso +# # equivalently: +# nix build .#nixosConfigurations._installer-iso.config.system.build.isoImage +# +# For now the installer is identical to the appliance ISO (full GUI box + +# turn-key Coder bootstrap) and differs only in image identity; the eventual +# minimal, GUI-less installer environment is deferred. Unlike the appliance, the +# installer ships ONLY as an ISO (no qcow2/raw disk images). All of the +# installer-ISO wiring lives in nixos/_images/_installer/iso.nix. +# +# 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/_images/_installer/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/_appliance/live-iso.nix b/nixos/_appliance/live-iso.nix deleted file mode 100644 index df63ac9..0000000 --- a/nixos/_appliance/live-iso.nix +++ /dev/null @@ -1,66 +0,0 @@ -# Appliance ISO module — "it's just The Box™", not an installer. -# -# Turns the shared Coder box configuration into a bootable *ephemeral* appliance -# 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* appliance (state survives reboots) build the -# _appliance-disk host instead (qcow2 / raw); see the Makefile / README. -# -# Build (hosts/_appliance_iso => nixosConfigurations._appliance_iso, see flake.nix): -# -# make appliance/iso -# # or: nix build .#nixosConfigurations._appliance_iso.config.system.build.isoImage -# # → out/appliance-iso/iso/coder-box-appliance-*.iso (flash with `dd`, Ventoy, etc.) -# -# This module is imported only by hosts/_appliance_iso/default.nix and is -# independent of the regular disk-install flow (install.sh, disko, -# nixos-facter). It imports NO disko / hardware-configuration.nix / facter.json: -# the appliance root is the squashfs + tmpfs overlay that nixpkgs' iso-image.nix -# sets up. -# -# The turn-key login + Coder admin bootstrap (shared with the _appliance-disk -# image) live in nixos/_appliance/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 appliance 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 = "CODER_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; -} diff --git a/nixos/_images/_appliance/iso.nix b/nixos/_images/_appliance/iso.nix new file mode 100644 index 0000000..da2d3c4 --- /dev/null +++ b/nixos/_images/_appliance/iso.nix @@ -0,0 +1,44 @@ +# Appliance ISO module — "it's just The Box™", not an installer. +# +# Turns the shared Coder box configuration into a bootable *ephemeral* appliance +# 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* appliance (state survives reboots) build the +# _appliance-disk host instead (qcow2 / raw); see the Makefile / README. +# +# Build (hosts/_appliance_iso => nixosConfigurations._appliance_iso, see flake.nix): +# +# make appliance/iso +# # or: nix build .#nixosConfigurations._appliance_iso.config.system.build.isoImage +# # → out/appliance-iso/iso/coder-box-appliance-*.iso (flash with `dd`, Ventoy, etc.) +# +# Composition: the ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, +# bootloader overrides, all-hardware) live in ../_base/iso.nix; the turn-key +# Coder box (baked /etc/nixos-repo, nixpkgs registry, coderbox autologin, Coder +# admin bootstrap) lives in ../box-turnkey.nix. This module only sets the +# appliance's image identity. + +{ config, lib, pkgs, ... }: + +{ + imports = [ + ../_base/iso.nix # shared ISO mechanics + ../box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap) + ]; + + # ── Image identity ─────────────────────────────────────────────────────────── + isoImage.volumeID = "CODER_BOX_APPLIANCE"; + # 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 appliance, not the + # installer. 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}"; +} diff --git a/nixos/_images/_base/hardware.nix b/nixos/_images/_base/hardware.nix new file mode 100644 index 0000000..f4832bf --- /dev/null +++ b/nixos/_images/_base/hardware.nix @@ -0,0 +1,9 @@ +# Broad driver/firmware set so an image boots on arbitrary real hardware or +# virtual machines. Single source of `all-hardware.nix` for the _images tree: +# imported by both _base/iso.nix and ../box-turnkey.nix. This replaces the +# per-host facter.json / hardware-configuration.nix that installed hosts rely +# on (image hosts ship neither). +{ modulesPath, ... }: +{ + imports = [ (modulesPath + "/profiles/all-hardware.nix") ]; +} diff --git a/nixos/_images/_base/iso.nix b/nixos/_images/_base/iso.nix new file mode 100644 index 0000000..a4fe635 --- /dev/null +++ b/nixos/_images/_base/iso.nix @@ -0,0 +1,31 @@ +# Shared ISO mechanics for every Coder box image that ships as an ISO +# (appliance ISO, installer ISO). This is a _base primitive: it wires up the +# nixpkgs ISO builder and the boot-loader overrides, but carries NO image +# identity (volumeID / menu label / file name) — each image module under +# _images/_appliance or _images/_installer sets those. +# +# Provides `config.system.build.isoImage` and the `isoImage.*` options. +{ config, lib, pkgs, modulesPath, ... }: +{ + imports = [ + # Core ISO builder: squashfs nix store, tmpfs overlay root, kernel/initrd, + # and the EFI + BIOS ISO bootloader. + (modulesPath + "/installer/cd-dvd/iso-image.nix") + # Broad driver/firmware set (see hardware.nix). + ./hardware.nix + ]; + + 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 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 + + # 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; +} diff --git a/nixos/_images/_installer/iso.nix b/nixos/_images/_installer/iso.nix new file mode 100644 index 0000000..202a512 --- /dev/null +++ b/nixos/_images/_installer/iso.nix @@ -0,0 +1,35 @@ +# Installer ISO module — boots the Coder box to install it onto real hardware. +# +# For now this is intentionally identical to the appliance ISO (full GUI box + +# turn-key Coder bootstrap), differing ONLY in image identity (volume ID, boot +# menu label, file name). The eventual plan is a minimal, GUI-less environment +# whose job is to install the coder/box repo onto a target disk — that strip- +# down is deferred; the GUI is kept on for now. +# +# Build (hosts/_installer-iso => nixosConfigurations._installer-iso, see flake.nix): +# +# make installer/iso +# # or: nix build .#nixosConfigurations._installer-iso.config.system.build.isoImage +# # → out/installer-iso/iso/coder-box-installer-*.iso (flash with `dd`, Ventoy, etc.) +# +# Composition mirrors the appliance ISO: ../_base/iso.nix (ISO mechanics) + +# ../box-turnkey.nix (turn-key Coder box). Unlike the appliance, the installer +# is built ONLY as an ISO (no qcow2/raw disk images). + +{ config, lib, pkgs, ... }: + +{ + imports = [ + ../_base/iso.nix # shared ISO mechanics + ../box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap) + ]; + + # ── Image identity ─────────────────────────────────────────────────────────── + isoImage.volumeID = "CODER_BOX_INSTALLER"; + # Boot-menu label (BIOS/isolinux + EFI/grub). See _appliance/iso.nix for the + # format; leading space is required. + isoImage.appendToMenuLabel = " - Coder Box Installer"; + # ISO file name, with arch suffix (e.g. coder-box-installer-x86_64-linux.iso). + # See _appliance/iso.nix for why this is mkForce + arch-suffixed. + image.baseName = lib.mkForce "coder-box-installer-${pkgs.stdenv.hostPlatform.system}"; +} diff --git a/nixos/_appliance/box-turnkey.nix b/nixos/_images/box-turnkey.nix similarity index 81% rename from nixos/_appliance/box-turnkey.nix rename to nixos/_images/box-turnkey.nix index 586174b..575d99c 100644 --- a/nixos/_appliance/box-turnkey.nix +++ b/nixos/_images/box-turnkey.nix @@ -1,9 +1,10 @@ # 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 appliance flavours: -# - nixos/_appliance/live-iso.nix (ephemeral appliance ISO: hosts/_appliance_iso) -# - hosts/_appliance-disk/ (persistent disk image: qcow2 / raw) +# Shared by every Coder box image flavour: +# - nixos/_images/_appliance/iso.nix (ephemeral appliance ISO: hosts/_appliance_iso) +# - nixos/_images/_installer/iso.nix (installer ISO: hosts/_installer-iso) +# - hosts/_appliance-disk/ (persistent disk image: qcow2 / raw) # # On real installs these settings come from install.sh + the gitignored # hosts//local.nix it generates. The image flavours have no install step, @@ -11,15 +12,18 @@ # 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, ... }: +{ config, lib, pkgs, 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") + # virtual machines (single source for the _images tree). Replaces the + # per-host facter.json / hardware-configuration.nix that installed hosts + # rely on (image hosts ship neither). The ISO flavours also pull this in + # via _base/iso.nix; importing the same module twice is a harmless no-op + # (NixOS dedups identical module paths), and the _appliance-disk host — + # which imports box-turnkey but NOT iso.nix — needs it from here. + ./_base/hardware.nix ]; # ── Bake the repo into the image at /etc/nixos-repo ────────────────────────── From 7cb633a22173bfec61d831b6baef617359b6668c Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 13:36:23 +0000 Subject: [PATCH 02/18] refactor: drop underscore from nixos/_images subfolders (_base/_appliance/_installer -> base/appliance/installer) The _images parent already signals 'not a host'; the subfolders don't need the underscore. Rename the three subdirs and update all import paths and doc/comment references. No behavior change. Verified: all 5 hosts eval; installer + appliance ISO derivations still build. --- README.md | 16 ++++++++-------- agents.md | 8 ++++---- hosts/_appliance_iso/default.nix | 4 ++-- hosts/_installer-iso/default.nix | 4 ++-- nixos/_images/{_appliance => appliance}/iso.nix | 4 ++-- nixos/_images/{_base => base}/hardware.nix | 2 +- nixos/_images/{_base => base}/iso.nix | 2 +- nixos/_images/box-turnkey.nix | 8 ++++---- nixos/_images/{_installer => installer}/iso.nix | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) rename nixos/_images/{_appliance => appliance}/iso.nix (95%) rename nixos/_images/{_base => base}/hardware.nix (82%) rename nixos/_images/{_base => base}/iso.nix (96%) rename nixos/_images/{_installer => installer}/iso.nix (93%) diff --git a/README.md b/README.md index a4d857a..e80788d 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,12 @@ nixos/ screenconnect.nix # optional ScreenConnect remote access client _images/ # prebuilt-image modules (appliance + installer) box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap); all image hosts - _base/ # primitives shared by every image + base/ # primitives shared by every image hardware.nix # all-hardware (boot on arbitrary hardware) iso.nix # ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, bootloader) - _appliance/ + appliance/ iso.nix # appliance ISO module (hosts/_appliance_iso) - _installer/ + installer/ iso.nix # installer ISO module (hosts/_installer-iso) pkgs/ coder.nix # custom Coder server package @@ -60,11 +60,11 @@ hosts/ templates/ nook-android/ # Workspace: build trmnl-nook-simple-touch APK _appliance_iso/ # `_appliance_iso` host: ephemeral appliance ISO (no disk install) - default.nix # imports nixos/_images/_appliance/iso.nix (no disko/facter/hardware-config) + default.nix # imports nixos/_images/appliance/iso.nix (no disko/facter/hardware-config) _appliance-disk/ # `_appliance-disk` host: persistent qcow2/raw disk image default.nix # imports disko-standard.nix + nixos/_images/box-turnkey.nix _installer-iso/ # `_installer-iso` host: installer ISO (ISO only; installs box onto hardware) - default.nix # imports nixos/_images/_installer/iso.nix + default.nix # imports nixos/_images/installer/iso.nix coderd/ main.tf # manages all Coder templates via coderd Terraform provider templates/ @@ -175,7 +175,7 @@ desktop, and admin `admin@coder.com` / `PleaseChangeMe1234`. Coder comes up at The appliance 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/_images/_appliance/iso.nix`](nixos/_images/_appliance/iso.nix) (which pulls in `_base/iso.nix` + `box-turnkey.nix`) — +[`nixos/_images/appliance/iso.nix`](nixos/_images/appliance/iso.nix) (which pulls in `base/iso.nix` + `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 @@ -229,8 +229,8 @@ make installer/iso/aarch64-linux # explicit arch ``` `hosts/_installer-iso/default.nix` imports -[`nixos/_images/_installer/iso.nix`](nixos/_images/_installer/iso.nix), which — -like the appliance ISO — pulls in `_base/iso.nix` + `box-turnkey.nix`. +[`nixos/_images/installer/iso.nix`](nixos/_images/installer/iso.nix), which — +like the appliance ISO — pulls in `base/iso.nix` + `box-turnkey.nix`. ## After install diff --git a/agents.md b/agents.md index a9d303d..cb463f6 100644 --- a/agents.md +++ b/agents.md @@ -186,10 +186,10 @@ sudo k3s kubectl describe pod -n coder-workspaces screenconnect.nix # ScreenConnect remote access client _images/ # prebuilt-image modules (appliance + installer) box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap); all image hosts - _base/hardware.nix # all-hardware (boot on arbitrary hardware) - _base/iso.nix # shared ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, bootloader) - _appliance/iso.nix # appliance ISO module (imported by hosts/_appliance_iso) - _installer/iso.nix # installer ISO module (imported by hosts/_installer-iso) + base/hardware.nix # all-hardware (boot on arbitrary hardware) + base/iso.nix # shared ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, bootloader) + appliance/iso.nix # appliance ISO module (imported by hosts/_appliance_iso) + installer/iso.nix # installer ISO module (imported by hosts/_installer-iso) pkgs/ coder.nix # Coder server package derivation coderd-provider.nix # terraform-provider-coderd derivation diff --git a/hosts/_appliance_iso/default.nix b/hosts/_appliance_iso/default.nix index c43f446..28f0f7a 100644 --- a/hosts/_appliance_iso/default.nix +++ b/hosts/_appliance_iso/default.nix @@ -11,7 +11,7 @@ # Unlike the install hosts (coder-thinkcentre, qemu-arm64), this host does NOT # import nixos/disko-standard.nix, hardware-configuration.nix, or facter.json: # the appliance root filesystem is the squashfs + tmpfs overlay provided by -# nixos/_images/_appliance/iso.nix. All of the appliance-ISO wiring lives there. +# nixos/_images/appliance/iso.nix. All of the appliance-ISO wiring lives there. # # This host is independent of install.sh and never participates in the # disk-install flow; adding it changes nothing for disko/nixos-install installs. @@ -19,7 +19,7 @@ { lib, ... }: { - imports = [ ../../nixos/_images/_appliance/iso.nix ] + imports = [ ../../nixos/_images/appliance/iso.nix ] ++ lib.optional (builtins.pathExists ./local.nix) ./local.nix; # No networking.hostName here on purpose: underscore-prefixed image hosts get diff --git a/hosts/_installer-iso/default.nix b/hosts/_installer-iso/default.nix index 944187e..98ea65d 100644 --- a/hosts/_installer-iso/default.nix +++ b/hosts/_installer-iso/default.nix @@ -12,7 +12,7 @@ # turn-key Coder bootstrap) and differs only in image identity; the eventual # minimal, GUI-less installer environment is deferred. Unlike the appliance, the # installer ships ONLY as an ISO (no qcow2/raw disk images). All of the -# installer-ISO wiring lives in nixos/_images/_installer/iso.nix. +# installer-ISO wiring lives in nixos/_images/installer/iso.nix. # # 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. @@ -20,7 +20,7 @@ { lib, ... }: { - imports = [ ../../nixos/_images/_installer/iso.nix ] + imports = [ ../../nixos/_images/installer/iso.nix ] ++ lib.optional (builtins.pathExists ./local.nix) ./local.nix; # No networking.hostName here on purpose: underscore-prefixed image hosts get diff --git a/nixos/_images/_appliance/iso.nix b/nixos/_images/appliance/iso.nix similarity index 95% rename from nixos/_images/_appliance/iso.nix rename to nixos/_images/appliance/iso.nix index da2d3c4..553042c 100644 --- a/nixos/_images/_appliance/iso.nix +++ b/nixos/_images/appliance/iso.nix @@ -15,7 +15,7 @@ # # → out/appliance-iso/iso/coder-box-appliance-*.iso (flash with `dd`, Ventoy, etc.) # # Composition: the ISO mechanics (iso-image.nix, EFI/BIOS/USB bootable, -# bootloader overrides, all-hardware) live in ../_base/iso.nix; the turn-key +# bootloader overrides, all-hardware) live in ../base/iso.nix; the turn-key # Coder box (baked /etc/nixos-repo, nixpkgs registry, coderbox autologin, Coder # admin bootstrap) lives in ../box-turnkey.nix. This module only sets the # appliance's image identity. @@ -24,7 +24,7 @@ { imports = [ - ../_base/iso.nix # shared ISO mechanics + ../base/iso.nix # shared ISO mechanics ../box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap) ]; diff --git a/nixos/_images/_base/hardware.nix b/nixos/_images/base/hardware.nix similarity index 82% rename from nixos/_images/_base/hardware.nix rename to nixos/_images/base/hardware.nix index f4832bf..f4126f9 100644 --- a/nixos/_images/_base/hardware.nix +++ b/nixos/_images/base/hardware.nix @@ -1,6 +1,6 @@ # Broad driver/firmware set so an image boots on arbitrary real hardware or # virtual machines. Single source of `all-hardware.nix` for the _images tree: -# imported by both _base/iso.nix and ../box-turnkey.nix. This replaces the +# imported by both base/iso.nix and ../box-turnkey.nix. This replaces the # per-host facter.json / hardware-configuration.nix that installed hosts rely # on (image hosts ship neither). { modulesPath, ... }: diff --git a/nixos/_images/_base/iso.nix b/nixos/_images/base/iso.nix similarity index 96% rename from nixos/_images/_base/iso.nix rename to nixos/_images/base/iso.nix index a4fe635..e2101ea 100644 --- a/nixos/_images/_base/iso.nix +++ b/nixos/_images/base/iso.nix @@ -2,7 +2,7 @@ # (appliance ISO, installer ISO). This is a _base primitive: it wires up the # nixpkgs ISO builder and the boot-loader overrides, but carries NO image # identity (volumeID / menu label / file name) — each image module under -# _images/_appliance or _images/_installer sets those. +# _images/appliance or _images/installer sets those. # # Provides `config.system.build.isoImage` and the `isoImage.*` options. { config, lib, pkgs, modulesPath, ... }: diff --git a/nixos/_images/box-turnkey.nix b/nixos/_images/box-turnkey.nix index 575d99c..cd1d5ce 100644 --- a/nixos/_images/box-turnkey.nix +++ b/nixos/_images/box-turnkey.nix @@ -2,8 +2,8 @@ # into a fully-configured, ready-to-use Coder box with no install step. # # Shared by every Coder box image flavour: -# - nixos/_images/_appliance/iso.nix (ephemeral appliance ISO: hosts/_appliance_iso) -# - nixos/_images/_installer/iso.nix (installer ISO: hosts/_installer-iso) +# - nixos/_images/appliance/iso.nix (ephemeral appliance ISO: hosts/_appliance_iso) +# - nixos/_images/installer/iso.nix (installer ISO: hosts/_installer-iso) # - hosts/_appliance-disk/ (persistent disk image: qcow2 / raw) # # On real installs these settings come from install.sh + the gitignored @@ -20,10 +20,10 @@ # virtual machines (single source for the _images tree). Replaces the # per-host facter.json / hardware-configuration.nix that installed hosts # rely on (image hosts ship neither). The ISO flavours also pull this in - # via _base/iso.nix; importing the same module twice is a harmless no-op + # via base/iso.nix; importing the same module twice is a harmless no-op # (NixOS dedups identical module paths), and the _appliance-disk host — # which imports box-turnkey but NOT iso.nix — needs it from here. - ./_base/hardware.nix + ./base/hardware.nix ]; # ── Bake the repo into the image at /etc/nixos-repo ────────────────────────── diff --git a/nixos/_images/_installer/iso.nix b/nixos/_images/installer/iso.nix similarity index 93% rename from nixos/_images/_installer/iso.nix rename to nixos/_images/installer/iso.nix index 202a512..a92b419 100644 --- a/nixos/_images/_installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -12,7 +12,7 @@ # # or: nix build .#nixosConfigurations._installer-iso.config.system.build.isoImage # # → out/installer-iso/iso/coder-box-installer-*.iso (flash with `dd`, Ventoy, etc.) # -# Composition mirrors the appliance ISO: ../_base/iso.nix (ISO mechanics) + +# Composition mirrors the appliance ISO: ../base/iso.nix (ISO mechanics) + # ../box-turnkey.nix (turn-key Coder box). Unlike the appliance, the installer # is built ONLY as an ISO (no qcow2/raw disk images). @@ -20,7 +20,7 @@ { imports = [ - ../_base/iso.nix # shared ISO mechanics + ../base/iso.nix # shared ISO mechanics ../box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap) ]; From 356cdc547ddb2ad87f3b94231f238b793738555a Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 13:37:56 +0000 Subject: [PATCH 03/18] make: put installer/iso first (default goal for bare `make`) --- Makefile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 28571a8..0691dff 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,15 @@ define box_build '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 installer/iso +.PHONY: installer/iso appliance/iso appliance/qcow2 appliance/raw + +# installer/iso is listed first so it's the default goal (bare `make`). + +# ── installer/iso — installer ISO (hosts/_installer-iso); ISO only ──────────── +installer/iso: + $(call box_build,_installer-iso,isoImage,,) +installer/iso/%: + $(call box_build,_installer-iso,isoImage,,$*) # ── appliance/iso — ephemeral appliance ISO (hosts/_appliance_iso) ─────────── appliance/iso: @@ -81,9 +89,3 @@ appliance/raw: $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "raw";,) appliance/raw/%: $(call box_build,_appliance-disk,diskoImages,disko.imageBuilder.imageFormat = "raw";,$*) - -# ── installer/iso — installer ISO (hosts/_installer-iso); ISO only ──────────── -installer/iso: - $(call box_build,_installer-iso,isoImage,,) -installer/iso/%: - $(call box_build,_installer-iso,isoImage,,$*) From 5819fb2a4a5ca8575deb95e1d0e982129f3de3ec Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 14:58:29 +0000 Subject: [PATCH 04/18] installer: auto-launch full-screen Konsole on login, cwd /etc/nixos-repo box-turnkey autologins into the Plasma desktop; for the installer ISO add a system-wide XDG autostart entry that opens Konsole full-screen on session start with --workdir /etc/nixos-repo (the baked coder/box repo). Installer-only; the appliance keeps a normal desktop. --- nixos/_images/installer/iso.nix | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index a92b419..972d7b2 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -30,6 +30,26 @@ # format; leading space is required. isoImage.appendToMenuLabel = " - Coder Box Installer"; # ISO file name, with arch suffix (e.g. coder-box-installer-x86_64-linux.iso). - # See _appliance/iso.nix for why this is mkForce + arch-suffixed. + # See ../appliance/iso.nix for why this is mkForce + arch-suffixed. image.baseName = lib.mkForce "coder-box-installer-${pkgs.stdenv.hostPlatform.system}"; + + # ── Auto-launch a full-screen Konsole on login ─────────────────────────────── + # box-turnkey.nix autologins straight into the Plasma (X11) desktop. For the + # installer we want a terminal front-and-centre, so drop a system-wide XDG + # autostart entry that opens Konsole full-screen as soon as the session starts, + # with its working directory set to /etc/nixos-repo (the baked coder/box repo) + # so the install commands are right there. This is the GUI-on stepping stone + # toward the eventual terminal-driven install flow. (--fullscreen and + # --workdir are Konsole CLI flags.) + environment.systemPackages = [ pkgs.kdePackages.konsole ]; + environment.etc."xdg/autostart/coder-box-installer-konsole.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Coder Box Installer Console + Comment=Open a full-screen terminal for installing coder/box + Exec=${pkgs.kdePackages.konsole}/bin/konsole --fullscreen --workdir /etc/nixos-repo + Terminal=false + X-GNOME-Autostart-enabled=true + OnlyShowIn=KDE; + ''; } From 3e03de00da395813372212bd782421ae22a9357d Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 15:38:48 +0000 Subject: [PATCH 05/18] installer: disable KDE screen locker so it never prompts for a password Login is already passwordless (coderbox autologin + passwordless sudo); the only remaining password gate was KDE's screen locker (idle auto-lock / lock-on-resume). Disable it system-wide for the installer via kscreenlockerrc (Autolock=false, LockOnResume=false) so the installer is never locked. Appliance keeps the default locker. --- nixos/_images/installer/iso.nix | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index 972d7b2..c493fdd 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -52,4 +52,17 @@ X-GNOME-Autostart-enabled=true OnlyShowIn=KDE; ''; + + # ── Never prompt for a password to get in ──────────────────────────────────── + # Login itself is already passwordless: box-turnkey.nix autologins the + # `coderbox` user, and configuration.nix sets passwordless sudo. The only + # remaining password gate is KDE's screen locker (idle auto-lock or + # lock-on-resume), which would force a password to get back into the session. + # Disable it system-wide for the installer so the box is never locked. (The + # appliance keeps the default locker.) + environment.etc."xdg/kscreenlockerrc".text = '' + [Daemon] + Autolock=false + LockOnResume=false + ''; } From 22ecf1e29546116f562b71125a10811ba986420c Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 18:16:36 +0000 Subject: [PATCH 06/18] installer: make install.sh work on the read-only ISO (clone upstream into writable git repo) On the installer/appliance ISO, /etc/nixos-repo is a symlink into the read-only Nix store with no .git, so install.sh's writes (mkdir hosts/) failed with 'Read-only file system', and there was no git repo to leave on the installed box. Fix: - install.sh: when REPO_DIR is read-only or not a git repo, clone the upstream repo (CODER_BOX_REPO_URL / CODER_BOX_REPO_REF, default public coder/box main) into a writable tmpdir (tmpfs/RAM on the live ISO) and re-exec from there. The clone is a real git repo (origin + tracking branch), so after cp -a to /mnt the installed /etc/nixos-repo supports 'git pull'. The normal live-USB flow (already a writable git clone) is unchanged. git add --intent-to-add is now unconditional since REPO_DIR is always a git repo. - nixos/_images/installer/iso.nix: set vm.overcommit_memory=1 (mirrors nixpkgs installation-device.nix) for low-RAM installs; document why install.sh works on the read-only ISO (writable tmpfs /nix/store overlay; closure builds into the target /mnt/nix/store). Verified: clone-relocation triggers on a read-only no-.git dir and re-execs; normal writable git repo does not clone; installer ISO still evaluates. --- hosts/_installer-iso/default.nix | 2 +- install.sh | 39 ++++++++++++++++++++++++++++++++ nixos/_images/installer/iso.nix | 15 ++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/hosts/_installer-iso/default.nix b/hosts/_installer-iso/default.nix index 98ea65d..7570ba9 100644 --- a/hosts/_installer-iso/default.nix +++ b/hosts/_installer-iso/default.nix @@ -14,7 +14,7 @@ # installer ships ONLY as an ISO (no qcow2/raw disk images). All of the # installer-ISO wiring lives in nixos/_images/installer/iso.nix. # -# This host is independent of nixos/install.sh and never participates in the +# This host is independent of install.sh and never participates in the # disk-install flow; adding it changes nothing for disko/nixos-install installs. { lib, ... }: diff --git a/install.sh b/install.sh index 2d556bf..1296e04 100755 --- a/install.sh +++ b/install.sh @@ -31,6 +31,42 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# ── Writable git working copy ────────────────────────────────────────────── +# install.sh writes generated host files into the repo, and the installed +# system keeps the repo at /etc/nixos-repo as a *git* repo so users can update +# with `git pull`. On the normal live-USB flow REPO_DIR is already a writable +# git clone, so we use it as-is. +# +# On the installer/appliance ISO, the repo is baked at /etc/nixos-repo, which is +# a symlink into the read-only Nix store and has no .git: we can neither write +# into it (`mkdir hosts/` -> "Read-only file system") nor leave a usable +# git repo on the installed system. So when REPO_DIR is read-only or not a git +# repo, clone the upstream repo into a writable tmpdir (tmpfs/RAM on a live ISO) +# and re-exec from there. The clone is a real git repo (origin + tracking +# branch), so after it's copied to /mnt the installed /etc/nixos-repo supports +# `git pull`. Override the source with CODER_BOX_REPO_URL / CODER_BOX_REPO_REF +# (e.g. to install a fork/branch instead of the public main). +REPO_URL="${CODER_BOX_REPO_URL:-https://github.com/coder/box}" +REPO_REF="${CODER_BOX_REPO_REF:-main}" +repo_writable=false +if [[ -e "$REPO_DIR/.git" ]] && ( : > "$REPO_DIR/.coder-box-write-test" ) 2>/dev/null; then + repo_writable=true +fi +rm -f "$REPO_DIR/.coder-box-write-test" 2>/dev/null || true +if ! $repo_writable; then + command -v git >/dev/null || { echo "git missing; cannot clone $REPO_URL" >&2; exit 1; } + workdir="$(mktemp -d "${TMPDIR:-/tmp}/coder-box-install.XXXXXX")" + echo "=== Repo at $REPO_DIR is read-only / not a git repo ===" >&2 + echo "=== Cloning $REPO_URL ($REPO_REF) into $workdir for a writable git repo ===" >&2 + if ! git clone --branch "$REPO_REF" "$REPO_URL" "$workdir/box"; then + echo "ERROR: failed to clone $REPO_URL ($REPO_REF)." >&2 + echo " The installer ISO needs network access to fetch the repo. Override the" >&2 + echo " source with CODER_BOX_REPO_URL / CODER_BOX_REPO_REF if needed." >&2 + exit 1 + fi + exec "$workdir/box/install.sh" "$@" +fi + # ── Flag parsing ─────────────────────────────────────────────────────────── HOSTNAME_ARG="" HARDWARE_DESC_ARG="" @@ -358,6 +394,9 @@ if [[ ! -f "$HOST_DIR/facter.json" ]]; then fi # local.nix is gitignored, so force-add as intent-to-add; the others normal. +# REPO_DIR is always a git repo here (the normal flow is a clone; the ISO flow +# clones upstream above), and a git path flake ignores untracked files, so the +# freshly written host files must be intent-to-added for the flake to see them. git -C "$REPO_DIR" add --intent-to-add -f \ "hosts/$HOSTNAME_ARG/default.nix" \ "hosts/$HOSTNAME_ARG/facter.json" \ diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index c493fdd..471cb3a 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -65,4 +65,19 @@ Autolock=false LockOnResume=false ''; + + # ── Installer ergonomics ───────────────────────────────────────────────────── + # Running `sudo ./install.sh` from the baked /etc/nixos-repo works because the + # script detects its repo dir is read-only (a symlink into the read-only Nix + # store with no .git) and instead clones the upstream repo into a writable + # tmpdir (tmpfs/RAM here) and re-execs from there. That clone is a real git + # repo, so the installed /etc/nixos-repo can `git pull` to update. The live + # /nix/store is already a writable tmpfs overlay (nixpkgs' iso-image.nix sets + # that up), and install.sh builds the system closure straight into the target + # disk's /mnt/nix/store, so RAM size isn't the limit. + # + # Mirror nixpkgs' installation-device.nix low-memory tweak so the kernel's + # overcommit heuristics don't spuriously block forks during the install on + # low-RAM machines. + boot.kernel.sysctl."vm.overcommit_memory" = "1"; } From c5dd54840ac5c65d93eaa5e93f25f0f74a7c19dd Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 18:35:02 +0000 Subject: [PATCH 07/18] installer: use the baked repo offline (no re-download) + git init for git pull MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per request, don't re-clone from the network when install.sh runs on the ISO — the full repo is already baked into the image. When REPO_DIR is read-only / not a git repo, copy the baked tree into a writable tmpdir, 'git init' it locally (commit a snapshot labeled with the baked upstream rev), and set origin + branch.main.merge so the installed /etc/nixos-repo can 'git pull' future updates. No network needed at install time. CODER_BOX_REPO_URL only sets the origin URL. Record provenance: box-turnkey.nix writes /etc/coder-box-rev from self.rev (or dirtyRev), which install.sh uses to label the initial commit. Verified: offline copy+init path materializes a git repo with origin and tracking branch (no clone); installer ISO still evaluates; rev file populated. --- install.sh | 43 ++++++++++++++++++++++------------- nixos/_images/box-turnkey.nix | 7 ++++++ 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/install.sh b/install.sh index 1296e04..be5548b 100755 --- a/install.sh +++ b/install.sh @@ -31,21 +31,22 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# ── Writable git working copy ────────────────────────────────────────────── +# ── Writable git working copy (offline) ──────────────────────────────────── # install.sh writes generated host files into the repo, and the installed # system keeps the repo at /etc/nixos-repo as a *git* repo so users can update # with `git pull`. On the normal live-USB flow REPO_DIR is already a writable # git clone, so we use it as-is. # -# On the installer/appliance ISO, the repo is baked at /etc/nixos-repo, which is +# On the installer/appliance ISO the repo is baked at /etc/nixos-repo, which is # a symlink into the read-only Nix store and has no .git: we can neither write # into it (`mkdir hosts/` -> "Read-only file system") nor leave a usable -# git repo on the installed system. So when REPO_DIR is read-only or not a git -# repo, clone the upstream repo into a writable tmpdir (tmpfs/RAM on a live ISO) -# and re-exec from there. The clone is a real git repo (origin + tracking -# branch), so after it's copied to /mnt the installed /etc/nixos-repo supports -# `git pull`. Override the source with CODER_BOX_REPO_URL / CODER_BOX_REPO_REF -# (e.g. to install a fork/branch instead of the public main). +# git repo on the installed system. We DON'T re-download — the full repo is +# already baked into the image — so when REPO_DIR is read-only or not a git +# repo we copy the baked tree into a writable tmpdir (tmpfs/RAM on a live ISO), +# turn it into a git repo locally (no network), and re-exec from there. After +# install.sh copies it to /mnt, the installed /etc/nixos-repo is a real git repo +# with `origin` set, so `git pull` fetches future updates. Override the upstream +# with CODER_BOX_REPO_URL (used only as the `origin` URL; no fetch here). REPO_URL="${CODER_BOX_REPO_URL:-https://github.com/coder/box}" REPO_REF="${CODER_BOX_REPO_REF:-main}" repo_writable=false @@ -54,16 +55,26 @@ if [[ -e "$REPO_DIR/.git" ]] && ( : > "$REPO_DIR/.coder-box-write-test" ) 2>/dev fi rm -f "$REPO_DIR/.coder-box-write-test" 2>/dev/null || true if ! $repo_writable; then - command -v git >/dev/null || { echo "git missing; cannot clone $REPO_URL" >&2; exit 1; } + command -v git >/dev/null || { echo "git missing" >&2; exit 1; } workdir="$(mktemp -d "${TMPDIR:-/tmp}/coder-box-install.XXXXXX")" echo "=== Repo at $REPO_DIR is read-only / not a git repo ===" >&2 - echo "=== Cloning $REPO_URL ($REPO_REF) into $workdir for a writable git repo ===" >&2 - if ! git clone --branch "$REPO_REF" "$REPO_URL" "$workdir/box"; then - echo "ERROR: failed to clone $REPO_URL ($REPO_REF)." >&2 - echo " The installer ISO needs network access to fetch the repo. Override the" >&2 - echo " source with CODER_BOX_REPO_URL / CODER_BOX_REPO_REF if needed." >&2 - exit 1 - fi + echo "=== Using the baked repo (no download); preparing a writable git repo at $workdir/box ===" >&2 + cp -a "$REPO_DIR/." "$workdir/box/" + chmod -R u+w "$workdir/box" + rm -f "$workdir/box/.coder-box-write-test" + baked_rev="$(cat /etc/coder-box-rev 2>/dev/null || echo unknown)" + git -C "$workdir/box" init -q -b main + git -C "$workdir/box" add -A + git -C "$workdir/box" \ + -c user.name="Coder Box installer" \ + -c user.email="installer@coder.box" \ + commit -q -m "Coder Box baked image snapshot (upstream rev: ${baked_rev})" + # Set origin so the installed box can `git pull` future updates. (No fetch + # now — offline. The first pull may need to reconcile this baked snapshot + # with upstream history; see README "Updating an installed box".) + git -C "$workdir/box" remote add origin "$REPO_URL" + git -C "$workdir/box" config "branch.main.remote" origin + git -C "$workdir/box" config "branch.main.merge" "refs/heads/${REPO_REF}" exec "$workdir/box/install.sh" "$@" fi diff --git a/nixos/_images/box-turnkey.nix b/nixos/_images/box-turnkey.nix index cd1d5ce..fdc330d 100644 --- a/nixos/_images/box-turnkey.nix +++ b/nixos/_images/box-turnkey.nix @@ -61,6 +61,13 @@ # behave like an installed system, without shipping a channel. nix.registry.nixpkgs.flake = inputs.nixpkgs; + # Record which upstream revision this image was built from. The baked repo + # under /etc/nixos-repo has no .git (flake source strips it), so install.sh + # reads this to label the initial commit of the git repo it materializes on + # the installed system. "dirty"/"unknown" when built from an uncommitted tree. + environment.etc."coder-box-rev".text = + (self.rev or self.dirtyRev or "unknown") + "\n"; + # ── Login + Coder admin bootstrap ──────────────────────────────────────────── # Autologin drops straight into the Plasma desktop, mirroring a # freshly-installed, configured box. From b8f7d053604f0a796e2c9092eb98d1403db3552a Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 21:00:16 +0000 Subject: [PATCH 08/18] installer: anchor the writable install copy to upstream (clean git pull), offline fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the manual 'copy /etc/nixos-repo somewhere writable, make it git, run install.sh there' workflow precisely: copy the baked repo (no full re-download), git init + add origin, then — if online — 'git fetch' upstream and 'reset --hard' to it so the installed /etc/nixos-repo has real upstream history and future 'git pull' is clean (no unrelated-histories reconcile). Offline / fetch failure falls back to the baked snapshot commit (install still works; first pull may need --rebase). A fetch transfers deltas, not a full clone. Verified both paths locally against a fake upstream (online anchors + sets tracking branch; offline keeps snapshot); installer ISO still evaluates. --- install.sh | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/install.sh b/install.sh index be5548b..ad27ce8 100755 --- a/install.sh +++ b/install.sh @@ -31,7 +31,7 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# ── Writable git working copy (offline) ──────────────────────────────────── +# ── Writable git working copy ────────────────────────────────────────────── # install.sh writes generated host files into the repo, and the installed # system keeps the repo at /etc/nixos-repo as a *git* repo so users can update # with `git pull`. On the normal live-USB flow REPO_DIR is already a writable @@ -40,13 +40,15 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # On the installer/appliance ISO the repo is baked at /etc/nixos-repo, which is # a symlink into the read-only Nix store and has no .git: we can neither write # into it (`mkdir hosts/` -> "Read-only file system") nor leave a usable -# git repo on the installed system. We DON'T re-download — the full repo is -# already baked into the image — so when REPO_DIR is read-only or not a git -# repo we copy the baked tree into a writable tmpdir (tmpfs/RAM on a live ISO), -# turn it into a git repo locally (no network), and re-exec from there. After -# install.sh copies it to /mnt, the installed /etc/nixos-repo is a real git repo -# with `origin` set, so `git pull` fetches future updates. Override the upstream -# with CODER_BOX_REPO_URL (used only as the `origin` URL; no fetch here). +# git repo on the installed system. So we reproduce, automatically, the manual +# workaround "copy /etc/nixos-repo to a writable dir, make it a git repo, run +# install.sh there": +# 1. copy the baked tree into a writable tmpdir (tmpfs/RAM on a live ISO) — +# no full re-download; the repo is already in the image, +# 2. make it a real git repo with `origin`, and (if online) fast-forward to +# the latest upstream so the installed repo has clean history and future +# `git pull` Just Works. Offline, keep the baked snapshot as-is. +# Override the upstream with CODER_BOX_REPO_URL / CODER_BOX_REPO_REF. REPO_URL="${CODER_BOX_REPO_URL:-https://github.com/coder/box}" REPO_REF="${CODER_BOX_REPO_REF:-main}" repo_writable=false @@ -58,7 +60,7 @@ if ! $repo_writable; then command -v git >/dev/null || { echo "git missing" >&2; exit 1; } workdir="$(mktemp -d "${TMPDIR:-/tmp}/coder-box-install.XXXXXX")" echo "=== Repo at $REPO_DIR is read-only / not a git repo ===" >&2 - echo "=== Using the baked repo (no download); preparing a writable git repo at $workdir/box ===" >&2 + echo "=== Copying baked repo to a writable dir at $workdir/box ===" >&2 cp -a "$REPO_DIR/." "$workdir/box/" chmod -R u+w "$workdir/box" rm -f "$workdir/box/.coder-box-write-test" @@ -69,12 +71,24 @@ if ! $repo_writable; then -c user.name="Coder Box installer" \ -c user.email="installer@coder.box" \ commit -q -m "Coder Box baked image snapshot (upstream rev: ${baked_rev})" - # Set origin so the installed box can `git pull` future updates. (No fetch - # now — offline. The first pull may need to reconcile this baked snapshot - # with upstream history; see README "Updating an installed box".) git -C "$workdir/box" remote add origin "$REPO_URL" - git -C "$workdir/box" config "branch.main.remote" origin - git -C "$workdir/box" config "branch.main.merge" "refs/heads/${REPO_REF}" + # If online, fast-forward the working copy to the latest upstream so the + # installed /etc/nixos-repo has real upstream history (clean `git pull` + # afterwards). A `git fetch` of an existing-ish repo transfers deltas, not a + # full re-clone. Offline (or fetch fails): keep the baked snapshot — install + # still works, but the first `git pull` may need `--rebase` to reconcile. + echo "=== Anchoring to upstream $REPO_URL ($REPO_REF) — git fetch (skipped if offline) ===" >&2 + if git -C "$workdir/box" fetch -q --no-tags origin "$REPO_REF" 2>/dev/null; then + git -C "$workdir/box" reset -q --hard FETCH_HEAD + git -C "$workdir/box" branch -q --set-upstream-to "origin/$REPO_REF" main 2>/dev/null \ + || { git -C "$workdir/box" config "branch.main.remote" origin + git -C "$workdir/box" config "branch.main.merge" "refs/heads/${REPO_REF}"; } + echo "=== Anchored to upstream $REPO_REF (clean git pull on the installed box) ===" >&2 + else + git -C "$workdir/box" config "branch.main.remote" origin + git -C "$workdir/box" config "branch.main.merge" "refs/heads/${REPO_REF}" + echo "=== Offline: installing the baked snapshot; first 'git pull' may need --rebase ===" >&2 + fi exec "$workdir/box/install.sh" "$@" fi From a750ad7ce4bbb18d443cbb6abe1b614eaf009fb4 Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 22:12:49 +0000 Subject: [PATCH 09/18] =?UTF-8?q?installer:=20simplify=20install.sh=20relo?= =?UTF-8?q?cation=20=E2=80=94=20permission=20check,=20no=20git=20wizardry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per feedback: - Drop the .coder-box-write-test probe; detect read-only REPO_DIR with [[ ! -w ]]. On NixOS /etc/nixos-repo is a read-only *mount*, so access(W_OK)=EROFS makes -w false even for root. - Remove all git init/commit/remote-add/fetch logic (it failed: the baked repo already has a .git with origin, so 'git remote add origin' errored 'already exists'). Now we just 'cp -a' the baked repo to a writable tmpdir and re-exec. cp -a keeps the baked .git (if present), so the installed /etc/nixos-repo can still 'git pull'; if absent, install still works as a path flake. - Guard the intent-to-add (skip when the copy has no .git). - Remove the now-unused /etc/coder-box-rev from box-turnkey.nix. Verified: relocation copies + re-execs cleanly with and without a baked .git (no 'origin already exists'/'re-init' noise); installer ISO still evaluates. --- install.sh | 84 +++++++++-------------------------- nixos/_images/box-turnkey.nix | 7 --- 2 files changed, 21 insertions(+), 70 deletions(-) diff --git a/install.sh b/install.sh index ad27ce8..ddab5c0 100755 --- a/install.sh +++ b/install.sh @@ -31,64 +31,20 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# ── Writable git working copy ────────────────────────────────────────────── -# install.sh writes generated host files into the repo, and the installed -# system keeps the repo at /etc/nixos-repo as a *git* repo so users can update -# with `git pull`. On the normal live-USB flow REPO_DIR is already a writable -# git clone, so we use it as-is. -# -# On the installer/appliance ISO the repo is baked at /etc/nixos-repo, which is -# a symlink into the read-only Nix store and has no .git: we can neither write -# into it (`mkdir hosts/` -> "Read-only file system") nor leave a usable -# git repo on the installed system. So we reproduce, automatically, the manual -# workaround "copy /etc/nixos-repo to a writable dir, make it a git repo, run -# install.sh there": -# 1. copy the baked tree into a writable tmpdir (tmpfs/RAM on a live ISO) — -# no full re-download; the repo is already in the image, -# 2. make it a real git repo with `origin`, and (if online) fast-forward to -# the latest upstream so the installed repo has clean history and future -# `git pull` Just Works. Offline, keep the baked snapshot as-is. -# Override the upstream with CODER_BOX_REPO_URL / CODER_BOX_REPO_REF. -REPO_URL="${CODER_BOX_REPO_URL:-https://github.com/coder/box}" -REPO_REF="${CODER_BOX_REPO_REF:-main}" -repo_writable=false -if [[ -e "$REPO_DIR/.git" ]] && ( : > "$REPO_DIR/.coder-box-write-test" ) 2>/dev/null; then - repo_writable=true -fi -rm -f "$REPO_DIR/.coder-box-write-test" 2>/dev/null || true -if ! $repo_writable; then - command -v git >/dev/null || { echo "git missing" >&2; exit 1; } +# ── Writable working copy ────────────────────────────────────────────────── +# install.sh writes generated host files into the repo (hosts//...), so +# REPO_DIR must be writable. On the normal live-USB flow it's a writable git +# clone. On the installer/appliance ISO the repo is baked at /etc/nixos-repo, a +# symlink into the read-only Nix store, so writing fails ("Read-only file +# system"). When REPO_DIR isn't writable, copy it to a writable tmpdir +# (tmpfs/RAM on a live ISO) and re-exec from there. The copy is a verbatim +# `cp -a`, so if the baked repo carries a .git the copy keeps it (with its +# origin) and the installed /etc/nixos-repo can still `git pull`. +if [[ ! -w "$REPO_DIR" ]]; then workdir="$(mktemp -d "${TMPDIR:-/tmp}/coder-box-install.XXXXXX")" - echo "=== Repo at $REPO_DIR is read-only / not a git repo ===" >&2 - echo "=== Copying baked repo to a writable dir at $workdir/box ===" >&2 + echo "=== Repo at $REPO_DIR is read-only; copying to a writable dir at $workdir/box ===" >&2 cp -a "$REPO_DIR/." "$workdir/box/" chmod -R u+w "$workdir/box" - rm -f "$workdir/box/.coder-box-write-test" - baked_rev="$(cat /etc/coder-box-rev 2>/dev/null || echo unknown)" - git -C "$workdir/box" init -q -b main - git -C "$workdir/box" add -A - git -C "$workdir/box" \ - -c user.name="Coder Box installer" \ - -c user.email="installer@coder.box" \ - commit -q -m "Coder Box baked image snapshot (upstream rev: ${baked_rev})" - git -C "$workdir/box" remote add origin "$REPO_URL" - # If online, fast-forward the working copy to the latest upstream so the - # installed /etc/nixos-repo has real upstream history (clean `git pull` - # afterwards). A `git fetch` of an existing-ish repo transfers deltas, not a - # full re-clone. Offline (or fetch fails): keep the baked snapshot — install - # still works, but the first `git pull` may need `--rebase` to reconcile. - echo "=== Anchoring to upstream $REPO_URL ($REPO_REF) — git fetch (skipped if offline) ===" >&2 - if git -C "$workdir/box" fetch -q --no-tags origin "$REPO_REF" 2>/dev/null; then - git -C "$workdir/box" reset -q --hard FETCH_HEAD - git -C "$workdir/box" branch -q --set-upstream-to "origin/$REPO_REF" main 2>/dev/null \ - || { git -C "$workdir/box" config "branch.main.remote" origin - git -C "$workdir/box" config "branch.main.merge" "refs/heads/${REPO_REF}"; } - echo "=== Anchored to upstream $REPO_REF (clean git pull on the installed box) ===" >&2 - else - git -C "$workdir/box" config "branch.main.remote" origin - git -C "$workdir/box" config "branch.main.merge" "refs/heads/${REPO_REF}" - echo "=== Offline: installing the baked snapshot; first 'git pull' may need --rebase ===" >&2 - fi exec "$workdir/box/install.sh" "$@" fi @@ -418,14 +374,16 @@ if [[ ! -f "$HOST_DIR/facter.json" ]]; then echo " wrote hosts/$HOSTNAME_ARG/facter.json" fi -# local.nix is gitignored, so force-add as intent-to-add; the others normal. -# REPO_DIR is always a git repo here (the normal flow is a clone; the ISO flow -# clones upstream above), and a git path flake ignores untracked files, so the -# freshly written host files must be intent-to-added for the flake to see them. -git -C "$REPO_DIR" add --intent-to-add -f \ - "hosts/$HOSTNAME_ARG/default.nix" \ - "hosts/$HOSTNAME_ARG/facter.json" \ - "hosts/$HOSTNAME_ARG/local.nix" >/dev/null +# A git path flake ignores untracked files, so the freshly written host files +# must be intent-to-added for the flake to see them (local.nix is gitignored, so +# force-add it). Only meaningful when REPO_DIR is a git repo; the ISO writable +# copy may have no .git (a non-git path flake already sees every file), so skip. +if git -C "$REPO_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git -C "$REPO_DIR" add --intent-to-add -f \ + "hosts/$HOSTNAME_ARG/default.nix" \ + "hosts/$HOSTNAME_ARG/facter.json" \ + "hosts/$HOSTNAME_ARG/local.nix" >/dev/null +fi # ── Validate ─────────────────────────────────────────────────────────────── echo " validating flake ..." diff --git a/nixos/_images/box-turnkey.nix b/nixos/_images/box-turnkey.nix index fdc330d..cd1d5ce 100644 --- a/nixos/_images/box-turnkey.nix +++ b/nixos/_images/box-turnkey.nix @@ -61,13 +61,6 @@ # behave like an installed system, without shipping a channel. nix.registry.nixpkgs.flake = inputs.nixpkgs; - # Record which upstream revision this image was built from. The baked repo - # under /etc/nixos-repo has no .git (flake source strips it), so install.sh - # reads this to label the initial commit of the git repo it materializes on - # the installed system. "dirty"/"unknown" when built from an uncommitted tree. - environment.etc."coder-box-rev".text = - (self.rev or self.dirtyRev or "unknown") + "\n"; - # ── Login + Coder admin bootstrap ──────────────────────────────────────────── # Autologin drops straight into the Plasma desktop, mirroring a # freshly-installed, configured box. From 8f5be99d76adbb81d57a256480dd2ed524ee3eac Mon Sep 17 00:00:00 2001 From: Phorcys Date: Wed, 10 Jun 2026 22:39:57 +0000 Subject: [PATCH 10/18] installer: exclude zram from disk picker; auto-run install.sh in Konsole; drop to bash on failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh: list_disks now skips /dev/zram* (and dm-/md/loop) — zram reports TYPE=disk RM=0 so it was wrongly offered as an install target. - installer iso.nix: the preopened full-screen Konsole now runs a launcher (konsole -e) that cd's to /etc/nixos-repo and runs 'sudo ./install.sh' (passwordless sudo), then execs an interactive bash shell if install.sh fails — so a failed install leaves the user at a usable prompt instead of a dead terminal. On success install.sh reboots. Verified: zram filter keeps vda/sda, drops zram/loop/rom; installer ISO evaluates; Konsole Exec runs the launcher; bash -n clean. --- install.sh | 5 ++- nixos/_images/installer/iso.nix | 54 ++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/install.sh b/install.sh index ddab5c0..e4363e7 100755 --- a/install.sh +++ b/install.sh @@ -188,8 +188,11 @@ sed_replacement_escape() { list_disks() { # Whole-block-devices, non-removable, non-loop, non-rom. MODEL is last so an # empty model (e.g. virtio /dev/vda) can't shift the TYPE/RM columns. + # Skip zram (compressed RAM swap, /dev/zramN) — it reports TYPE=disk RM=0 so + # it would otherwise show up as an install target, which is never what we want + # (installing onto RAM swap). Also skip device-mapper / md / loop just in case. lsblk -d -p -n -b -o NAME,SIZE,RM,TYPE,MODEL \ - | awk '$4=="disk" && $3=="0" { size_h=$2; cmd="numfmt --to=iec --suffix=B "$2; cmd|getline size_h; close(cmd); model=""; for(i=5;i<=NF;i++) model=model (model==""?"":" ") $i; print $1"\t"size_h"\t"model }' + | awk '$4=="disk" && $3=="0" && $1 !~ /\/(zram|dm-|md|loop)[0-9]+$/ { size_h=$2; cmd="numfmt --to=iec --suffix=B "$2; cmd|getline size_h; close(cmd); model=""; for(i=5;i<=NF;i++) model=model (model==""?"":" ") $i; print $1"\t"size_h"\t"model }' } # ── Gather inputs ────────────────────────────────────────────────────────── diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index 471cb3a..103c816 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -18,6 +18,33 @@ { config, lib, pkgs, ... }: +let + # Launcher run inside the preopened Konsole: cd into the baked repo, run the + # installer as root (passwordless sudo is configured in configuration.nix), + # and — whatever happens — drop the user into an interactive bash shell so a + # failed install leaves them at a prompt to inspect/retry instead of a dead + # terminal. (On success install.sh reboots, so the shell is only reached on + # failure or --no-reboot.) + installerLauncher = pkgs.writeShellScript "coder-box-installer-launch" '' + cd /etc/nixos-repo 2>/dev/null || cd / + echo "=== Coder Box installer ===" + echo "Running: sudo ./install.sh $*" + echo + if sudo ./install.sh "$@"; then + echo + echo "=== install.sh finished ===" + else + rc=$? + echo + echo "=== install.sh FAILED (exit $rc) — dropping you into a shell ===" + echo " You are in /etc/nixos-repo. Re-run with: sudo ./install.sh" + fi + echo + # Interactive login-ish shell so the user can debug/retry. exec so closing + # the shell closes Konsole. + exec ${pkgs.bashInteractive}/bin/bash -i + ''; +in { imports = [ ../base/iso.nix # shared ISO mechanics @@ -33,21 +60,20 @@ # See ../appliance/iso.nix for why this is mkForce + arch-suffixed. image.baseName = lib.mkForce "coder-box-installer-${pkgs.stdenv.hostPlatform.system}"; - # ── Auto-launch a full-screen Konsole on login ─────────────────────────────── + # ── Auto-launch a full-screen Konsole that runs the installer ──────────────── # box-turnkey.nix autologins straight into the Plasma (X11) desktop. For the - # installer we want a terminal front-and-centre, so drop a system-wide XDG - # autostart entry that opens Konsole full-screen as soon as the session starts, - # with its working directory set to /etc/nixos-repo (the baked coder/box repo) - # so the install commands are right there. This is the GUI-on stepping stone - # toward the eventual terminal-driven install flow. (--fullscreen and - # --workdir are Konsole CLI flags.) + # installer we want the install to start on its own: a system-wide XDG + # autostart entry opens Konsole full-screen on session start and runs the + # installer launcher (`konsole -e `), which `sudo ./install.sh`s and + # drops to an interactive bash shell if it fails. This is the GUI-on stepping + # stone toward the eventual terminal-driven install flow. environment.systemPackages = [ pkgs.kdePackages.konsole ]; environment.etc."xdg/autostart/coder-box-installer-konsole.desktop".text = '' [Desktop Entry] Type=Application Name=Coder Box Installer Console - Comment=Open a full-screen terminal for installing coder/box - Exec=${pkgs.kdePackages.konsole}/bin/konsole --fullscreen --workdir /etc/nixos-repo + Comment=Run the coder/box installer in a full-screen terminal + Exec=${pkgs.kdePackages.konsole}/bin/konsole --fullscreen --workdir /etc/nixos-repo -e ${installerLauncher} Terminal=false X-GNOME-Autostart-enabled=true OnlyShowIn=KDE; @@ -69,12 +95,10 @@ # ── Installer ergonomics ───────────────────────────────────────────────────── # Running `sudo ./install.sh` from the baked /etc/nixos-repo works because the # script detects its repo dir is read-only (a symlink into the read-only Nix - # store with no .git) and instead clones the upstream repo into a writable - # tmpdir (tmpfs/RAM here) and re-execs from there. That clone is a real git - # repo, so the installed /etc/nixos-repo can `git pull` to update. The live - # /nix/store is already a writable tmpfs overlay (nixpkgs' iso-image.nix sets - # that up), and install.sh builds the system closure straight into the target - # disk's /mnt/nix/store, so RAM size isn't the limit. + # store) and copies it to a writable tmpdir (tmpfs/RAM here) to re-exec from. + # The copy keeps the baked .git (if any), so the installed /etc/nixos-repo can + # `git pull` to update. install.sh builds the system closure straight into the + # target disk's /mnt/nix/store. # # Mirror nixpkgs' installation-device.nix low-memory tweak so the kernel's # overcommit heuristics don't spuriously block forks during the install on From 80e146c92ec49528ca5b3eb6e83073d653f08fb2 Mon Sep 17 00:00:00 2001 From: Phorcys Date: Thu, 11 Jun 2026 00:05:23 +0000 Subject: [PATCH 11/18] install.sh: copy pre-baked system closure into /mnt (fix chroot activate failure on the box ISO) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When installing FROM a Coder box image (installer/appliance ISO), the target's system closure already exists in the live /nix/store — but in the read-only squashfs lower layer of the overlay. nixos-install builds the flake with 'nix build --store /mnt --extra-substituters ', i.e. it substitutes the closure into /mnt from the host store; the squashfs paths lack the signatures/narinfo substitution needs, so the copy silently yields nothing. /mnt is left empty (no bash, system profile points at nixos-repo-src) and the chroot 'activate' fails: 'No such file or directory'. (Confirmed on-device: /mnt/nix/store had no bash, system-1-link -> ...-nixos-repo-src, /mnt 34M used.) Fix: when the target closure is already present in the live store, copy it into /mnt explicitly with 'nix copy --no-check-sigs' (bypasses the signature/substituter machinery); nixos-install then finds it and just activates. On a stock NixOS live ISO the closure isn't present, so we skip the copy and let nixos-install build straight into /mnt as before (no extra tmpfs/RAM use). Pure 'nix eval' (no build) gets the path so the stock-ISO case isn't forced to realise the closure in the host store. Verified: bash -n clean; nix eval --raw returns the toplevel out path for the presence check. Needs on-device confirmation on the installer ISO. --- install.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/install.sh b/install.sh index e4363e7..7e3664e 100755 --- a/install.sh +++ b/install.sh @@ -428,6 +428,33 @@ mkdir -p /mnt/etc/nixos ln -sf /etc/nixos-repo/flake.nix /mnt/etc/nixos/flake.nix # ── Install ──────────────────────────────────────────────────────────────── +# When we install FROM a Coder box image (installer/appliance ISO), the whole +# system closure for the target already exists in the live /nix/store — but in +# the read-only squashfs lower layer of the overlay. nixos-install builds the +# flake with `nix build --store /mnt --extra-substituters `, i.e. it +# *substitutes* the closure into /mnt from the host store; those squashfs paths +# lack the signatures/narinfo that substitution needs, so the copy silently +# yields nothing. /mnt is left empty (no bash, the `system` profile points at +# the wrong path) and the chroot `activate` fails with "No such file or +# directory". +# +# Fix: if the target's system closure is already present in the live store, +# copy it into /mnt explicitly with `nix copy --no-check-sigs` (which bypasses +# the signature/substituter machinery). nixos-install then finds everything +# already in /mnt and just activates it. On a stock NixOS live ISO the closure +# is NOT present, so we skip this and let nixos-install build straight into /mnt +# exactly as before (no extra tmpfs/RAM use — important on small live USBs). +# We use pure `nix eval` (no build) to get the path so the stock-ISO case isn't +# forced to realise the closure in the host store first. +SYSTEM_TOPLEVEL=$(nix --extra-experimental-features 'nix-command flakes' \ + eval --raw "/mnt/etc/nixos-repo#nixosConfigurations.${HOSTNAME_ARG}.config.system.build.toplevel" \ + 2>/dev/null || true) +if [[ -n "$SYSTEM_TOPLEVEL" && -e "$SYSTEM_TOPLEVEL" ]]; then + echo "=== System closure already in the live store; copying it into /mnt ===" + nix --extra-experimental-features 'nix-command flakes' \ + copy --no-check-sigs --to "local?root=/mnt" "$SYSTEM_TOPLEVEL" +fi + echo "=== Running nixos-install ===" echo " (closure builds into /mnt/nix/store; no tmpfs OOM risk)" nixos-install \ From 7ac3debdec1df0dd1c66d98ec7009d723a745e29 Mon Sep 17 00:00:00 2001 From: Phorcys Date: Thu, 11 Jun 2026 00:48:02 +0000 Subject: [PATCH 12/18] install.sh: build+copy the target closure into /mnt on box images (real fix for chroot activate failure) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My previous attempt gated the nix-copy on the target toplevel already being realised in the live store. But install.sh installs host 'coder-nixos', which is a DIFFERENT system than the image's own host (_installer-iso), so its toplevel is never pre-realised — the check failed, the copy was skipped, and nixos-install fell back to the broken squashfs substitution. That's why the auto-relocated install failed while the user's manual 'copy to /tmp/box and run' worked. Fix: on a baked image (relocation now exports CODER_BOX_FROM_IMAGE=1), explicitly build the coder-nixos toplevel (cheap — KDE/Coder/k3s/etc. are reused from the squashfs; only host-specific derivations are new), 'nix copy --no-check-sigs' its full closure into /mnt, then 'nixos-install --system' activates it. The squashfs-substitution-into-/mnt path (which silently no-ops on signature-less store paths) is no longer used. Stock live-USB installs keep the original 'nixos-install --flake' (direct build into /mnt; no tmpfs OOM). Verified: bash -n clean; nixos-install --system/--no-channel-copy/--no-root-passwd all valid in the pinned nixpkgs. Needs on-device confirmation. --- install.sh | 84 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/install.sh b/install.sh index 7e3664e..e2b7242 100755 --- a/install.sh +++ b/install.sh @@ -45,6 +45,11 @@ if [[ ! -w "$REPO_DIR" ]]; then echo "=== Repo at $REPO_DIR is read-only; copying to a writable dir at $workdir/box ===" >&2 cp -a "$REPO_DIR/." "$workdir/box/" chmod -R u+w "$workdir/box" + # Signal to the re-exec that we're installing FROM a baked Coder box image + # (read-only repo in the Nix store) rather than a plain live-USB clone. The + # install step uses this to copy the prebuilt closure into /mnt explicitly + # (see "Install" below). + export CODER_BOX_FROM_IMAGE=1 exec "$workdir/box/install.sh" "$@" fi @@ -428,40 +433,59 @@ mkdir -p /mnt/etc/nixos ln -sf /etc/nixos-repo/flake.nix /mnt/etc/nixos/flake.nix # ── Install ──────────────────────────────────────────────────────────────── -# When we install FROM a Coder box image (installer/appliance ISO), the whole -# system closure for the target already exists in the live /nix/store — but in -# the read-only squashfs lower layer of the overlay. nixos-install builds the -# flake with `nix build --store /mnt --extra-substituters `, i.e. it -# *substitutes* the closure into /mnt from the host store; those squashfs paths -# lack the signatures/narinfo that substitution needs, so the copy silently -# yields nothing. /mnt is left empty (no bash, the `system` profile points at -# the wrong path) and the chroot `activate` fails with "No such file or -# directory". +# Two cases: # -# Fix: if the target's system closure is already present in the live store, -# copy it into /mnt explicitly with `nix copy --no-check-sigs` (which bypasses -# the signature/substituter machinery). nixos-install then finds everything -# already in /mnt and just activates it. On a stock NixOS live ISO the closure -# is NOT present, so we skip this and let nixos-install build straight into /mnt -# exactly as before (no extra tmpfs/RAM use — important on small live USBs). -# We use pure `nix eval` (no build) to get the path so the stock-ISO case isn't -# forced to realise the closure in the host store first. -SYSTEM_TOPLEVEL=$(nix --extra-experimental-features 'nix-command flakes' \ - eval --raw "/mnt/etc/nixos-repo#nixosConfigurations.${HOSTNAME_ARG}.config.system.build.toplevel" \ - 2>/dev/null || true) -if [[ -n "$SYSTEM_TOPLEVEL" && -e "$SYSTEM_TOPLEVEL" ]]; then - echo "=== System closure already in the live store; copying it into /mnt ===" +# (A) Installing FROM a Coder box image (installer/appliance ISO; +# CODER_BOX_FROM_IMAGE=1). The live /nix/store already contains almost the +# entire closure for the target system — but in the read-only squashfs lower +# layer of the overlay. nixos-install builds the flake with +# `nix build --store /mnt --extra-substituters `, i.e. it +# *substitutes* paths into /mnt from the host store; squashfs paths lack the +# signatures/narinfo substitution needs, so the copy silently yields +# nothing. /mnt is left empty (no bash; the `system` profile points at the +# wrong path) and the chroot `activate` fails: "No such file or directory". +# (NOTE: the target host is `coder-nixos`, a *different* system than the +# image's own host, so its toplevel isn't pre-realised — it must be built. +# The build is cheap: every heavy dependency (KDE, Coder, k3s, …) is reused +# from the squashfs; only the few host-specific derivations are new.) +# +# So: build the toplevel, copy its full closure into /mnt with +# `nix copy --no-check-sigs` (bypassing the signature/substituter machinery +# that was the actual failure), then `nixos-install --system ` just +# activates it. This mirrors the working manual workaround ("copy the repo +# somewhere writable and run install.sh"). +# +# (B) Plain live-USB clone (stock NixOS ISO). The closure is NOT present, so +# building it in the host store first would balloon tmpfs/RAM. Keep the +# original `nixos-install --flake` which builds/downloads straight into +# /mnt. +if [[ "${CODER_BOX_FROM_IMAGE:-0}" == "1" ]]; then + echo "=== Building system closure (reusing the baked store) ===" + SYSTEM_TOPLEVEL=$(nix --extra-experimental-features 'nix-command flakes' \ + build --no-link --print-out-paths \ + --option download-buffer-size 268435456 \ + "/mnt/etc/nixos-repo#nixosConfigurations.${HOSTNAME_ARG}.config.system.build.toplevel") + [[ -n "$SYSTEM_TOPLEVEL" ]] || { echo "failed to build system closure" >&2; exit 1; } + + echo "=== Copying system closure into /mnt ===" nix --extra-experimental-features 'nix-command flakes' \ copy --no-check-sigs --to "local?root=/mnt" "$SYSTEM_TOPLEVEL" -fi -echo "=== Running nixos-install ===" -echo " (closure builds into /mnt/nix/store; no tmpfs OOM risk)" -nixos-install \ - --flake "/mnt/etc/nixos-repo#${HOSTNAME_ARG}" \ - --no-channel-copy \ - --no-root-passwd \ - --option download-buffer-size 268435456 + echo "=== Running nixos-install (from prebuilt system) ===" + nixos-install \ + --system "$SYSTEM_TOPLEVEL" \ + --no-channel-copy \ + --no-root-passwd \ + --option download-buffer-size 268435456 +else + echo "=== Running nixos-install ===" + echo " (closure builds into /mnt/nix/store; no tmpfs OOM risk)" + nixos-install \ + --flake "/mnt/etc/nixos-repo#${HOSTNAME_ARG}" \ + --no-channel-copy \ + --no-root-passwd \ + --option download-buffer-size 268435456 +fi echo echo "✓ Installation complete." From fb1a9f6122a2139703608c4d0039e709cadb2cec Mon Sep 17 00:00:00 2001 From: Phorcys Date: Thu, 11 Jun 2026 00:52:59 +0000 Subject: [PATCH 13/18] installer: show build revision in boot menu + at install start - nixos/_images/installer/iso.nix: take 'self', derive boxRev (self.rev / dirtyRev / unknown) and a 12-char short form; append the short rev to the boot-menu label -> 'NixOS - Coder Box Installer ()'. Also write the full rev to /etc/coder-box-rev (the baked /etc/nixos-repo has no .git, so install.sh can't get it from git on the image). - install.sh: print ' revision: ' under the banner. Resolves via git (live-USB clone / fork checkout), else /etc/coder-box-rev, else 'unknown'. Verified: installer ISO evaluates; menu label = ' - Coder Box Installer (7ac3debdec1d)'; /etc/coder-box-rev populated; bash -n clean. --- install.sh | 10 ++++++++++ nixos/_images/installer/iso.nix | 19 +++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index e2b7242..0f2d670 100755 --- a/install.sh +++ b/install.sh @@ -201,7 +201,17 @@ list_disks() { } # ── Gather inputs ────────────────────────────────────────────────────────── +# Resolve the build/commit revision for display: prefer git (the normal +# live-USB clone, or a fork checkout), else the baked /etc/coder-box-rev that +# the box image writes (its /etc/nixos-repo has no .git), else "unknown". +box_revision() { + local rev + rev="$(git -C "$REPO_DIR" rev-parse HEAD 2>/dev/null)" && { echo "$rev"; return; } + rev="$(cat /etc/coder-box-rev 2>/dev/null)" && [[ -n "$rev" ]] && { echo "$rev"; return; } + echo "unknown" +} echo "=== Coder NixOS installer ===" +echo " revision: $(box_revision)" echo # Defaults used when the corresponding flag is omitted. diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index 103c816..a3dc4b7 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -16,9 +16,15 @@ # ../box-turnkey.nix (turn-key Coder box). Unlike the appliance, the installer # is built ONLY as an ISO (no qcow2/raw disk images). -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, self, ... }: let + # Revision this image was built from. self.rev is the clean git commit; + # dirtyRev (…-dirty) when built from an uncommitted tree; "unknown" otherwise. + boxRev = self.rev or self.dirtyRev or "unknown"; + # Short form for the boot-menu label (full 40-char hashes are unwieldy there). + boxRevShort = if boxRev == "unknown" then "unknown" else builtins.substring 0 12 boxRev; + # Launcher run inside the preopened Konsole: cd into the baked repo, run the # installer as root (passwordless sudo is configured in configuration.nix), # and — whatever happens — drop the user into an interactive bash shell so a @@ -53,9 +59,14 @@ in # ── Image identity ─────────────────────────────────────────────────────────── isoImage.volumeID = "CODER_BOX_INSTALLER"; - # Boot-menu label (BIOS/isolinux + EFI/grub). See _appliance/iso.nix for the - # format; leading space is required. - isoImage.appendToMenuLabel = " - Coder Box Installer"; + # Boot-menu label (BIOS/isolinux + EFI/grub). See ../appliance/iso.nix for the + # format; leading space is required. Include the short build revision so the + # boot menu shows exactly which image you're booting. + isoImage.appendToMenuLabel = " - Coder Box Installer (${boxRevShort})"; + + # Record the full build revision for install.sh to print (the baked repo under + # /etc/nixos-repo has no .git, so the script can't derive it from git there). + environment.etc."coder-box-rev".text = boxRev + "\n"; # ISO file name, with arch suffix (e.g. coder-box-installer-x86_64-linux.iso). # See ../appliance/iso.nix for why this is mkForce + arch-suffixed. image.baseName = lib.mkForce "coder-box-installer-${pkgs.stdenv.hostPlatform.system}"; From 78ac95f4ae74dfeb153eb1818f26eab6801e2583 Mon Sep 17 00:00:00 2001 From: Phorcys Date: Thu, 11 Jun 2026 01:06:55 +0000 Subject: [PATCH 14/18] installer: disable Coder box services in the live installer ISO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The installer ISO inherits the full box config but its only job is to install coder/box onto a disk — running the Coder server, k3s, PostgreSQL, Podman, and the bootstrap/redirect/reaper/logstream units + template-sync activation in the live environment is dead weight (slow boot, wasted RAM/CPU during install). Disable them in the installer module: - services.coder-nixos.k3s-sysbox / postgresql / podman -> mkForce false - systemd coder, coder-init-admin, coder-redirect, coder-logstream-kube, coder-workspace-reaper (+ timer), coder-sync-ssh-keys -> enable = false - coder-template-sync activation script -> mkForce "" The INSTALLED system still gets everything; this only affects the live ISO. Verified: installer ISO evaluates; all listed services enable=false (k3s off); appliance still has them all enabled; coder-thinkcentre toplevel drv unchanged. --- nixos/_images/installer/iso.nix | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index a3dc4b7..eec136e 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -115,4 +115,27 @@ in # overcommit heuristics don't spuriously block forks during the install on # low-RAM machines. boot.kernel.sysctl."vm.overcommit_memory" = "1"; + + # ── Don't run the Coder box services in the installer ──────────────────────── + # The installer's only job is to install coder/box onto a disk; it inherits + # the full box config (configuration.nix + box-turnkey) but the running Coder + # server, k3s, PostgreSQL, Podman, the bootstrap/redirect/reaper units, and + # template-sync are all dead weight here (they'd boot a whole Coder stack we + # never use, slow startup, and eat RAM/CPU during the install). Disable them. + # The INSTALLED system still gets them — this only affects the live installer. + services.coder-nixos.k3s-sysbox.enable = lib.mkForce false; + services.postgresql.enable = lib.mkForce false; + virtualisation.podman.enable = lib.mkForce false; + + systemd.services.coder.enable = false; + systemd.services.coder-init-admin.enable = false; + systemd.services.coder-redirect.enable = false; + systemd.services.coder-logstream-kube.enable = false; + systemd.services.coder-workspace-reaper.enable = false; + systemd.timers.coder-workspace-reaper.enable = false; + systemd.services.coder-sync-ssh-keys.enable = false; + + # Activation script that pushes templates via terraform on every switch — + # pointless in the live installer (no running Coder, empty session token). + system.activationScripts.coder-template-sync = lib.mkForce ""; } From cc5670dd48dcbafbfb7a042efe2624710463aaa4 Mon Sep 17 00:00:00 2001 From: Phorcys Date: Thu, 11 Jun 2026 01:31:52 +0000 Subject: [PATCH 15/18] installer: fix '(unknown)' build revision in boot menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boot menu showed '(unknown)' because the Makefile builds through a *path* flakeref — getFlake (toString ./.) — which carries NO git metadata, so self.rev/dirtyRev were empty (a previous _module.args attempt also threw 'attribute coderBoxRev missing' under .# builds because the formal-arg default isn't honored). Add a proper NixOS option 'coderBox.rev' in the shared box-turnkey.nix (defined there so it exists for every image host the Makefile builds), defaulting to self.rev/dirtyRev for .# (git+file) builds. The Makefile computes the git rev (GIT_REV, full hash + -dirty) and overrides it via 'coderBox.rev' on every target. box-turnkey's config is now wrapped in 'config = { }' as required when a module declares options. Verified: installer boot label shows (78ac95f4ae74) via both Makefile-style and .# builds; appliance ISO + disk still evaluate with coderBox.rev set; all 5 hosts visible. --- .gitignore | 17 ++++ Makefile | 10 +- nixos/_images/box-turnkey.nix | 18 ++++ nixos/_images/installer/iso.nix | 159 ++++++++++++++++---------------- 4 files changed, 121 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index c2d78e8..6d05c00 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,25 @@ hosts/*/local.nix .terraform/ *.bak +# Build artifacts: tarballs produced by builds/tooling. The committed vendor +# tarball is force-kept below (gitignore can't untrack an already-tracked file, +# but the explicit un-ignore keeps intent clear and lets `git add` work). +*.tar.gz +!pkgs/sysbox-runc-vendor-*.tar.gz + # Appliance image build outputs (Makefile --out-link target dir + artifacts) out/ *.iso *.qcow2 *.raw + +# Let users drop their OWN hosts into hosts/ without git noise: ignore the +# whole hosts/ directory, then un-ignore only the hosts we manage centrally +# (the ones we ship + write to). A user-added hosts// stays +# untracked; our managed hosts remain version-controlled as usual. +hosts/* +!hosts/_appliance-disk/ +!hosts/_appliance_iso/ +!hosts/_installer-iso/ +!hosts/coder-thinkcentre/ +!hosts/qemu-arm64/ diff --git a/Makefile b/Makefile index 0691dff..ab207f4 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,14 @@ NIX ?= nix FLAKE ?= . +# Build revision injected into images (installer boot menu, /etc/coder-box-rev). +# We build through a path flakeref (getFlake (toString ./.)), which carries no +# git metadata, so self.rev/dirtyRev are empty — compute the rev here and pass +# it via the installer's `coderBox.rev` option. Full commit hash, with a -dirty +# suffix when the working tree has uncommitted changes. Empty if not a git +# checkout (the module then falls back to self.rev / "unknown"). +GIT_REV := $(shell git rev-parse HEAD 2>/dev/null)$(shell git diff-index --quiet HEAD -- 2>/dev/null || echo -dirty) + # Normalize an arch token to a *-linux triple: $(call norm_arch,aarch64) -> aarch64-linux norm_arch = $(if $(filter %-linux,$(1)),$(1),$(1)-linux) @@ -59,7 +67,7 @@ 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)' + 'let f = builtins.getFlake (toString ./.); in (f.nixosConfigurations.$(1).extendModules { modules = [ { nixpkgs.hostPlatform = "$(if $(4),$(call norm_arch,$(4)),$${builtins.currentSystem})"; coderBox.rev = "$(GIT_REV)"; $(3) } ]; }).config.system.build.$(2)' endef .PHONY: installer/iso appliance/iso appliance/qcow2 appliance/raw diff --git a/nixos/_images/box-turnkey.nix b/nixos/_images/box-turnkey.nix index cd1d5ce..b4ad6f1 100644 --- a/nixos/_images/box-turnkey.nix +++ b/nixos/_images/box-turnkey.nix @@ -26,6 +26,22 @@ ./base/hardware.nix ]; + # Build revision baked into the image (used by the installer's boot-menu label + # and /etc/coder-box-rev). Default works for `.#` (git+file) builds where + # `self` carries git metadata; the Makefile builds through a *path* flakeref + # (getFlake (toString ./.)) which has NO git metadata, so it overrides this + # with `coderBox.rev = ""` (see Makefile box_build). Defined here (in + # the shared module) so it exists for every image host the Makefile builds. + # (Because this module declares `options`, all its config must live under the + # `config = { … }` block below.) + options.coderBox.rev = lib.mkOption { + type = lib.types.str; + default = self.rev or self.dirtyRev or "unknown"; + description = "Git revision this Coder box image was built from."; + }; + + config = { + # ── 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 @@ -86,4 +102,6 @@ CODER_ADMIN_USERNAME = "admin"; CODER_ADMIN_PASSWORD = "PleaseChangeMe1234"; }; + + }; # end config } diff --git a/nixos/_images/installer/iso.nix b/nixos/_images/installer/iso.nix index eec136e..46c74e1 100644 --- a/nixos/_images/installer/iso.nix +++ b/nixos/_images/installer/iso.nix @@ -1,10 +1,10 @@ # Installer ISO module — boots the Coder box to install it onto real hardware. # # For now this is intentionally identical to the appliance ISO (full GUI box + -# turn-key Coder bootstrap), differing ONLY in image identity (volume ID, boot -# menu label, file name). The eventual plan is a minimal, GUI-less environment -# whose job is to install the coder/box repo onto a target disk — that strip- -# down is deferred; the GUI is kept on for now. +# turn-key Coder bootstrap), differing in image identity, an auto-run installer +# console, and having the Coder runtime services switched off (the installer +# doesn't need a live Coder stack). The eventual minimal, GUI-less installer is +# deferred. # # Build (hosts/_installer-iso => nixosConfigurations._installer-iso, see flake.nix): # @@ -19,9 +19,7 @@ { config, lib, pkgs, self, ... }: let - # Revision this image was built from. self.rev is the clean git commit; - # dirtyRev (…-dirty) when built from an uncommitted tree; "unknown" otherwise. - boxRev = self.rev or self.dirtyRev or "unknown"; + boxRev = config.coderBox.rev; # Short form for the boot-menu label (full 40-char hashes are unwieldy there). boxRevShort = if boxRev == "unknown" then "unknown" else builtins.substring 0 12 boxRev; @@ -53,89 +51,86 @@ let in { imports = [ - ../base/iso.nix # shared ISO mechanics + ../base/iso.nix # shared ISO mechanics ../box-turnkey.nix # shared turn-key Coder box (login + Coder bootstrap) ]; - # ── Image identity ─────────────────────────────────────────────────────────── - isoImage.volumeID = "CODER_BOX_INSTALLER"; - # Boot-menu label (BIOS/isolinux + EFI/grub). See ../appliance/iso.nix for the - # format; leading space is required. Include the short build revision so the - # boot menu shows exactly which image you're booting. - isoImage.appendToMenuLabel = " - Coder Box Installer (${boxRevShort})"; + # config.coderBox.rev is defined in ../box-turnkey.nix (shared by all image + # hosts so the Makefile can inject the rev for every target). It defaults to + # self.rev/dirtyRev for `.#` builds and is overridden by the Makefile. + config = { + # ── Image identity ───────────────────────────────────────────────────────── + isoImage.volumeID = "CODER_BOX_INSTALLER"; + # Boot-menu label (BIOS/isolinux + EFI/grub). See ../appliance/iso.nix for + # the format; leading space is required. Include the short build revision so + # the boot menu shows exactly which image you're booting. + isoImage.appendToMenuLabel = " - Coder Box Installer (${boxRevShort})"; - # Record the full build revision for install.sh to print (the baked repo under - # /etc/nixos-repo has no .git, so the script can't derive it from git there). - environment.etc."coder-box-rev".text = boxRev + "\n"; - # ISO file name, with arch suffix (e.g. coder-box-installer-x86_64-linux.iso). - # See ../appliance/iso.nix for why this is mkForce + arch-suffixed. - image.baseName = lib.mkForce "coder-box-installer-${pkgs.stdenv.hostPlatform.system}"; + # Record the full build revision for install.sh to print (the baked repo + # under /etc/nixos-repo has no .git, so the script can't get it from git). + environment.etc."coder-box-rev".text = boxRev + "\n"; - # ── Auto-launch a full-screen Konsole that runs the installer ──────────────── - # box-turnkey.nix autologins straight into the Plasma (X11) desktop. For the - # installer we want the install to start on its own: a system-wide XDG - # autostart entry opens Konsole full-screen on session start and runs the - # installer launcher (`konsole -e `), which `sudo ./install.sh`s and - # drops to an interactive bash shell if it fails. This is the GUI-on stepping - # stone toward the eventual terminal-driven install flow. - environment.systemPackages = [ pkgs.kdePackages.konsole ]; - environment.etc."xdg/autostart/coder-box-installer-konsole.desktop".text = '' - [Desktop Entry] - Type=Application - Name=Coder Box Installer Console - Comment=Run the coder/box installer in a full-screen terminal - Exec=${pkgs.kdePackages.konsole}/bin/konsole --fullscreen --workdir /etc/nixos-repo -e ${installerLauncher} - Terminal=false - X-GNOME-Autostart-enabled=true - OnlyShowIn=KDE; - ''; + # ISO file name, with arch suffix (e.g. coder-box-installer-x86_64-linux.iso). + # See ../appliance/iso.nix for why this is mkForce + arch-suffixed. + image.baseName = lib.mkForce "coder-box-installer-${pkgs.stdenv.hostPlatform.system}"; - # ── Never prompt for a password to get in ──────────────────────────────────── - # Login itself is already passwordless: box-turnkey.nix autologins the - # `coderbox` user, and configuration.nix sets passwordless sudo. The only - # remaining password gate is KDE's screen locker (idle auto-lock or - # lock-on-resume), which would force a password to get back into the session. - # Disable it system-wide for the installer so the box is never locked. (The - # appliance keeps the default locker.) - environment.etc."xdg/kscreenlockerrc".text = '' - [Daemon] - Autolock=false - LockOnResume=false - ''; + # ── Auto-launch a full-screen Konsole that runs the installer ────────────── + # box-turnkey.nix autologins straight into the Plasma (X11) desktop. For the + # installer we want the install to start on its own: a system-wide XDG + # autostart entry opens Konsole full-screen on session start and runs the + # installer launcher (`konsole -e `), which `sudo ./install.sh`s + # and drops to an interactive bash shell if it fails. + environment.systemPackages = [ pkgs.kdePackages.konsole ]; + environment.etc."xdg/autostart/coder-box-installer-konsole.desktop".text = '' + [Desktop Entry] + Type=Application + Name=Coder Box Installer Console + Comment=Run the coder/box installer in a full-screen terminal + Exec=${pkgs.kdePackages.konsole}/bin/konsole --fullscreen --workdir /etc/nixos-repo -e ${installerLauncher} + Terminal=false + X-GNOME-Autostart-enabled=true + OnlyShowIn=KDE; + ''; + + # ── Never prompt for a password to get in ────────────────────────────────── + # Login is already passwordless (box-turnkey coderbox autologin + passwordless + # sudo). Disable KDE's screen locker (idle auto-lock / lock-on-resume) so the + # installer is never locked. (The appliance keeps the default locker.) + environment.etc."xdg/kscreenlockerrc".text = '' + [Daemon] + Autolock=false + LockOnResume=false + ''; - # ── Installer ergonomics ───────────────────────────────────────────────────── - # Running `sudo ./install.sh` from the baked /etc/nixos-repo works because the - # script detects its repo dir is read-only (a symlink into the read-only Nix - # store) and copies it to a writable tmpdir (tmpfs/RAM here) to re-exec from. - # The copy keeps the baked .git (if any), so the installed /etc/nixos-repo can - # `git pull` to update. install.sh builds the system closure straight into the - # target disk's /mnt/nix/store. - # - # Mirror nixpkgs' installation-device.nix low-memory tweak so the kernel's - # overcommit heuristics don't spuriously block forks during the install on - # low-RAM machines. - boot.kernel.sysctl."vm.overcommit_memory" = "1"; + # ── Installer ergonomics ─────────────────────────────────────────────────── + # `sudo ./install.sh` from the baked /etc/nixos-repo works because the script + # detects its repo dir is read-only (a symlink into the read-only Nix store) + # and copies it to a writable tmpdir (tmpfs/RAM here) to re-exec from. The + # copy keeps the baked .git (if any) so the installed /etc/nixos-repo can + # `git pull`. Mirror nixpkgs' installation-device.nix low-memory tweak so the + # kernel's overcommit heuristics don't spuriously block forks during install. + boot.kernel.sysctl."vm.overcommit_memory" = "1"; - # ── Don't run the Coder box services in the installer ──────────────────────── - # The installer's only job is to install coder/box onto a disk; it inherits - # the full box config (configuration.nix + box-turnkey) but the running Coder - # server, k3s, PostgreSQL, Podman, the bootstrap/redirect/reaper units, and - # template-sync are all dead weight here (they'd boot a whole Coder stack we - # never use, slow startup, and eat RAM/CPU during the install). Disable them. - # The INSTALLED system still gets them — this only affects the live installer. - services.coder-nixos.k3s-sysbox.enable = lib.mkForce false; - services.postgresql.enable = lib.mkForce false; - virtualisation.podman.enable = lib.mkForce false; + # ── Don't run the Coder box services in the installer ────────────────────── + # The installer's only job is to install coder/box onto a disk; it inherits + # the full box config but the running Coder server, k3s, PostgreSQL, Podman, + # the bootstrap/redirect/reaper units, and template-sync are dead weight here + # (slow startup, wasted RAM/CPU during install). Disable them — the INSTALLED + # system still gets everything; this only affects the live installer. + services.coder-nixos.k3s-sysbox.enable = lib.mkForce false; + services.postgresql.enable = lib.mkForce false; + virtualisation.podman.enable = lib.mkForce false; - systemd.services.coder.enable = false; - systemd.services.coder-init-admin.enable = false; - systemd.services.coder-redirect.enable = false; - systemd.services.coder-logstream-kube.enable = false; - systemd.services.coder-workspace-reaper.enable = false; - systemd.timers.coder-workspace-reaper.enable = false; - systemd.services.coder-sync-ssh-keys.enable = false; + systemd.services.coder.enable = false; + systemd.services.coder-init-admin.enable = false; + systemd.services.coder-redirect.enable = false; + systemd.services.coder-logstream-kube.enable = false; + systemd.services.coder-workspace-reaper.enable = false; + systemd.timers.coder-workspace-reaper.enable = false; + systemd.services.coder-sync-ssh-keys.enable = false; - # Activation script that pushes templates via terraform on every switch — - # pointless in the live installer (no running Coder, empty session token). - system.activationScripts.coder-template-sync = lib.mkForce ""; + # Template-sync activation script — pointless in the live installer (no + # running Coder, empty session token). + system.activationScripts.coder-template-sync = lib.mkForce ""; + }; } From 676a1f9861099eb37e9deecaf7f1c1135069119b Mon Sep 17 00:00:00 2001 From: Phorcys Date: Thu, 11 Jun 2026 01:35:49 +0000 Subject: [PATCH 16/18] gitignore: stop ignoring *.tar.gz Revert the *.tar.gz ignore added in cc5670d: the only tarball in the tree is the build-critical pkgs/sysbox-runc-vendor-*.tar.gz vendor source, and tarballs here are intentionally committed, so a blanket ignore is wrong. The hosts/ ignore (user hosts vs centrally-managed) stays. --- .gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6d05c00..8d5a10f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,12 +4,6 @@ hosts/*/local.nix .terraform/ *.bak -# Build artifacts: tarballs produced by builds/tooling. The committed vendor -# tarball is force-kept below (gitignore can't untrack an already-tracked file, -# but the explicit un-ignore keeps intent clear and lets `git add` work). -*.tar.gz -!pkgs/sysbox-runc-vendor-*.tar.gz - # Appliance image build outputs (Makefile --out-link target dir + artifacts) out/ *.iso From a828e831d96cf15bdb8aa961e4ffd66e361b24dd Mon Sep 17 00:00:00 2001 From: Phorcys Date: Fri, 12 Jun 2026 14:23:15 +0000 Subject: [PATCH 17/18] iso: add dmidecode to the base ISO layer (installer + appliance) dmidecode reads SMBIOS/DMI (board model, BIOS, serial); install.sh's hardware-description auto-detection uses it and it's useful for inspecting the target machine from the live ISO. Added to nixos/_images/base/iso.nix so every ISO flavour gets it. (No logo: dropped per request.) Verified: installer + appliance ISOs evaluate; dmidecode present in environment.systemPackages for both. --- nixos/_images/base/iso.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nixos/_images/base/iso.nix b/nixos/_images/base/iso.nix index e2101ea..e0f31ac 100644 --- a/nixos/_images/base/iso.nix +++ b/nixos/_images/base/iso.nix @@ -28,4 +28,10 @@ # live system activates. boot.loader.systemd-boot.enable = lib.mkForce false; boot.loader.efi.canTouchEfiVariables = lib.mkForce false; + + # dmidecode: read SMBIOS/DMI (board model, BIOS, serial). install.sh's + # hardware-description auto-detection uses it, and it's handy for inspecting + # the target machine from the live ISO. Ship it in the base layer so every + # ISO flavour (installer + appliance) has it. + environment.systemPackages = [ pkgs.dmidecode ]; } From f85cddd3310323d3f3082d68e0fcb2d9affd7d21 Mon Sep 17 00:00:00 2001 From: Phorcys Date: Fri, 12 Jun 2026 15:56:10 +0000 Subject: [PATCH 18/18] install.sh: clean error when a value flag is missing its value A value-taking flag passed as the last token with no argument (e.g. `./install.sh --coder-admin-password`) expanded $2 under `set -u` and crashed with 'line 78: $2: unbound variable'. Add a need_value helper that checks an argument is present and exits 2 with 'flag requires a value' + usage. Applied to every value-taking flag. Verified: bash -n clean; missing value -> clear message + exit 2; flag with a value still parses normally. --- install.sh | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/install.sh b/install.sh index 0f2d670..3de6fdb 100755 --- a/install.sh +++ b/install.sh @@ -69,18 +69,24 @@ ASSUME_YES=0 usage() { sed -n '2,/^set -euo/p' "$0" | sed 's/^# \?//; s/^set -euo.*//' | sed '/^$/N;/^\n$/D'; } +# Require a value for a value-taking flag. Without this, a flag passed as the +# last token with no argument (e.g. `--coder-admin-password`) expands `$2` under +# `set -u` and crashes with "$2: unbound variable" instead of a clear message. +need_value() { + [[ $# -ge 2 ]] || { echo "flag $1 requires a value" >&2; usage >&2; exit 2; } +} while [[ $# -gt 0 ]]; do case "$1" in - --hostname) HOSTNAME_ARG="$2"; shift 2 ;; - --hardware-desc) HARDWARE_DESC_ARG="$2"; shift 2 ;; - --disk) DISK_ARG="$2"; shift 2 ;; - --coder-admin-email) ADMIN_EMAIL_ARG="$2"; shift 2 ;; - --coder-admin-password) ADMIN_PASSWORD_ARG="$2"; shift 2 ;; - --coder-admin-password-file) ADMIN_PASSWORD_FILE_ARG="$2"; shift 2 ;; - --nixos-username) NIXOS_USERNAME_ARG="$2"; shift 2 ;; - --nixos-password) NIXOS_PASSWORD_ARG="$2"; shift 2 ;; - --nixos-password-file) NIXOS_PASSWORD_FILE_ARG="$2"; shift 2 ;; - --lan-ip) LAN_IP_ARG="$2"; shift 2 ;; + --hostname) need_value "$@"; HOSTNAME_ARG="$2"; shift 2 ;; + --hardware-desc) need_value "$@"; HARDWARE_DESC_ARG="$2"; shift 2 ;; + --disk) need_value "$@"; DISK_ARG="$2"; shift 2 ;; + --coder-admin-email) need_value "$@"; ADMIN_EMAIL_ARG="$2"; shift 2 ;; + --coder-admin-password) need_value "$@"; ADMIN_PASSWORD_ARG="$2"; shift 2 ;; + --coder-admin-password-file) need_value "$@"; ADMIN_PASSWORD_FILE_ARG="$2"; shift 2 ;; + --nixos-username) need_value "$@"; NIXOS_USERNAME_ARG="$2"; shift 2 ;; + --nixos-password) need_value "$@"; NIXOS_PASSWORD_ARG="$2"; shift 2 ;; + --nixos-password-file) need_value "$@"; NIXOS_PASSWORD_FILE_ARG="$2"; shift 2 ;; + --lan-ip) need_value "$@"; LAN_IP_ARG="$2"; shift 2 ;; --no-reboot) NO_REBOOT=1; shift ;; --yes|-y) ASSUME_YES=1; shift ;; --help|-h) usage; exit 0 ;;