From 3e07a55f5bab0e2f8ea4e0324ec418fafe74ce4b Mon Sep 17 00:00:00 2001 From: DannyDannyDanny Date: Sun, 8 Mar 2026 16:16:25 +0100 Subject: [PATCH] Add NixOS server installer USB (disko LUKS + WiFi, hostname prompt) - disko-server.nix: LUKS + ESP + ext4 root layout for disko-install - server-install: minimal NixOS config for new servers (hostname/WiFi via --system-config) - installer-iso: custom minimal ISO with iwlwifi; build with nix build .#installer-iso - scripts/nixos-server-install.sh: prompt hostname/disk, run disko-install - docs/server-installer-usb.md: build, write USB, optional live/installed WiFi - .gitignore: nixos/installer-wifi.nix; AGENTS.md + README.md notes Made-with: Cursor --- .gitignore | 3 + AGENTS.md | 4 + README.md | 2 +- TODO.md | 2 + docs/server-installer-usb.md | 137 ++++++++++++++++++++++++++++++++ nixos/disko-server.nix | 39 +++++++++ nixos/flake.lock | 21 +++++ nixos/flake.nix | 24 ++++++ nixos/hosts/server-install.nix | 35 ++++++++ nixos/installer-iso.nix | 14 ++++ scripts/nixos-server-install.sh | 71 +++++++++++++++++ 11 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 docs/server-installer-usb.md create mode 100644 nixos/disko-server.nix create mode 100644 nixos/hosts/server-install.nix create mode 100644 nixos/installer-iso.nix create mode 100644 scripts/nixos-server-install.sh diff --git a/.gitignore b/.gitignore index d5465f8..34b25fd 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ env/ .env **/.DS_Store + +# Installer ISO live WiFi (SSID/PSK); see docs/server-installer-usb.md +nixos/installer-wifi.nix diff --git a/AGENTS.md b/AGENTS.md index f7bd5a8..6422cc4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,10 @@ We use **one key per purpose**, not one per machine: separate keys for server ac - **Config:** Use `~/.ssh/config` with `IdentityFile` and `IdentitiesOnly yes` per host so the right key is used. Keys and sensitive config stay outside the repo. - **Server / NixOS:** Use actual key names on the machine (e.g. `id_ed25519_github`), not a generic `id_ed25519` (see Learnings below). +## Server installer USB (new machines only) + +- Build: `cd ~/dotfiles/nixos && nix build .#installer-iso`; write `result/iso/*.iso` to USB (e.g. `dd` or [scripts/make-ubuntu-usb.sh](scripts/make-ubuntu-usb.sh)). Boot from USB, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) and include in flake when building ISO. + ## Learnings (NixOS server) - Minimal ISO: use Ethernet or the graphical installer (Wi‑Fi on minimal is fiddly). diff --git a/README.md b/README.md index 79c967d..fbd92f9 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Extension of [dannydannydanny/methodology](https://github.com/DannyDannyDanny/me ## Roadmap - [firefox-scrolling](firefox-scrolling.md) via terminal -- Server: [server](server.md); NixOS flake and bootstrap [nixos/readme.md](nixos/readme.md). SSH and secrets: [docs/ssh-and-secrets.md](docs/ssh-and-secrets.md). +- Server: [server](server.md); NixOS flake and bootstrap [nixos/readme.md](nixos/readme.md). SSH and secrets: [docs/ssh-and-secrets.md](docs/ssh-and-secrets.md). New server install (USB, LUKS, WiFi): [docs/server-installer-usb.md](docs/server-installer-usb.md). - nvim checkhealth; tmux setup; [fonts](https://www.programmingfonts.org/) / nerdfonts; [HN: home server](https://news.ycombinator.com/item?id=34271167) ## Windows diff --git a/TODO.md b/TODO.md index 003fc32..5bf4673 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,8 @@ # TODO 1. Create a setup/boot USB that: installs NixOS on the server with encryption and WiFi configured from the start; only required input is the server's name (e.g. sunken-ship). + * I have a set wifi SSID/PSK, assume servers will start up and be able to reach this wifi. + * I don't know how to go about the rest of this. 2. Encrypt sunken-ship (LUKS); update hardware/config for encrypted root and boot. diff --git a/docs/server-installer-usb.md b/docs/server-installer-usb.md new file mode 100644 index 0000000..f3a7aa4 --- /dev/null +++ b/docs/server-installer-usb.md @@ -0,0 +1,137 @@ +# Server installer USB (NixOS + LUKS + WiFi) + +Bootable USB that installs NixOS on a new server with disk encryption (LUKS) and optional WiFi from first boot. Only required input is the hostname (and LUKS passphrase when disko creates the volume). Existing hosts are not modified. + +## Build the ISO + +From a machine that can build NixOS (e.g. your Mac with Nix, or a Linux box): + +```bash +cd ~/dotfiles/nixos +nix build .#installer-iso +``` + +The image is at `result/iso/nixos-minimal-*.iso`. Write it to a USB stick (replace `sdX` with your device, e.g. `sda`): + +```bash +# Linux +sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress +sync +``` + +On macOS, use the disk number (e.g. `4` for `disk4`): + +```bash +sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/rdisk4 bs=4m +diskutil eject disk4 +``` + +Or adapt [scripts/make-ubuntu-usb.sh](../scripts/make-ubuntu-usb.sh) for the NixOS ISO path. + +## Live-system WiFi (optional) + +So the live system can reach the network (and fetch the flake) without Ethernet, add WiFi to the ISO at **build time**. Do not put SSID/PSK in the repo. + +1. Create **`nixos/installer-wifi.nix`** (gitignored) with your network: + +```nix +{ + networking.wireless.enable = true; + networking.wireless.networks."YourSSID".psk = "your-password"; +} +``` + +2. Add it to the flake for the installer ISO only. In `nixos/flake.nix`, change the `installer-iso` modules to: + +```nix +installer-iso = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ./installer-iso.nix ./installer-wifi.nix ]; # add installer-wifi.nix +}; +``` + +3. Ensure `nixos/installer-wifi.nix` is in `.gitignore`, then rebuild the ISO. + +If you skip this, use Ethernet on the live system or the graphical NixOS installer to join Wi‑Fi, then run the install script. + +## Install on the server + +1. Boot the server from the USB. +2. If you did not bake WiFi into the ISO, attach Ethernet or (on graphical installer) join Wi‑Fi so the machine has network. +3. Clone this repo (or copy the install script onto the machine). For example: + +```bash +nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- clone https://github.com/USER/dotfiles.git /tmp/dotfiles +cd /tmp/dotfiles +``` + +4. Run the install script (it will prompt for hostname and target disk): + +```bash +sudo ./scripts/nixos-server-install.sh +``` + +The script uses the flake from the current repo by default (`path:$(pwd)/nixos`). To use the flake from GitHub instead: + +```bash +sudo FLAKE_REF=github:USER/dotfiles ./scripts/nixos-server-install.sh +``` + +5. When disko creates the LUKS volume, enter the encryption passphrase when prompted. +6. When the script finishes, remove the USB and reboot. The new NixOS system will have LUKS root and the hostname you chose. + +## WiFi on the installed system (optional) + +To have WiFi configured from first boot (no manual step after reboot): + +1. Create a JSON file **outside the repo** with the config to merge (hostname is set by the script from the prompt): + +```json +{ + "networking": { + "wireless": { + "networks": { + "YourSSID": { "psk": "your-password" } + } + } + } +} +``` + +2. Copy that file onto the live system (e.g. put it on the USB or scp it). If the script is run with `jq` available and `INSTALLER_SYSTEM_CONFIG_FILE` set to that file, the script will merge it and set the hostname: + +```bash +sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi-config.json ./scripts/nixos-server-install.sh +``` + +If you omit this, the installed system still has `networking.wireless.enable = true`. Add credentials after first boot (e.g. [imperative wpa_supplicant config](sunken-ship-wifi.md)). + +## Manual install (without the script) + +You can run disko-install yourself: + +```bash +sudo nix run github:nix-community/disko/latest#disko-install -- \ + --flake 'path:/tmp/dotfiles/nixos#server-install' \ + --disk main /dev/sda \ + --system-config '{"networking":{"hostName":"my-server"}}' +``` + +Adjust the flake path and `--system-config` (e.g. add WiFi) as needed. + +## After install + +- Add your SSH key: from your machine `scp ~/.ssh/id_ed25519_servers.pub danny@NEW-SERVER:/tmp/`, then on the server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`. +- To switch this machine to another host config in the same flake (e.g. a full server profile), clone the repo on the new system and run `sudo nixos-rebuild switch --flake /path/to/nixos#other-host`. + +## Summary + +| Step | Action | +|------|--------| +| Build | `nix build .#installer-iso` in `nixos/` | +| Optional live WiFi | Add `installer-wifi.nix` (gitignored), include in flake, rebuild ISO | +| Write USB | `dd` or script to write `result/iso/*.iso` to USB | +| Boot | Boot server from USB | +| Install | Clone repo, run `sudo ./scripts/nixos-server-install.sh` (set `FLAKE_REF` if not from repo) | +| Optional installed WiFi | Set `INSTALLER_SYSTEM_CONFIG_FILE` to a JSON file with wireless config | +| Reboot | Remove USB, reboot; set root password if needed, add SSH keys | diff --git a/nixos/disko-server.nix b/nixos/disko-server.nix new file mode 100644 index 0000000..67623fb --- /dev/null +++ b/nixos/disko-server.nix @@ -0,0 +1,39 @@ +# Declarative disk layout for server installs via disko-install. +# Device is injected at install time: disko-install --disk main /dev/sda +# LUKS passphrase is prompted interactively (no keyFile). +{ + disko.devices = { + disk.main = { + type = "disk"; + content = { + type = "gpt"; + partitions = { + ESP = { + size = "512M"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "fmask=0022" "dmask=0022" ]; + }; + }; + luks = { + size = "100%"; + content = { + type = "luks"; + name = "crypted"; + settings.allowDiscards = true; + # No keyFile/passwordFile => interactive passphrase at install + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + }; + }; + }; + }; + }; +} diff --git a/nixos/flake.lock b/nixos/flake.lock index d0ddfdd..65ffc7c 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -1,5 +1,25 @@ { "nodes": { + "disko": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1772867152, + "narHash": "sha256-RIFgZ4O6Eg+5ysZ8Tqb3YvcqiRaNy440GEY22ltjRrs=", + "owner": "nix-community", + "repo": "disko", + "rev": "eaafb89b56e948661d618eefd4757d9ea8d77514", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "disko", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -164,6 +184,7 @@ }, "root": { "inputs": { + "disko": "disko", "home-manager": "home-manager", "nix-darwin": "nix-darwin", "nixos-wsl": "nixos-wsl", diff --git a/nixos/flake.nix b/nixos/flake.nix index 52ffb77..14a43c5 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -15,6 +15,9 @@ zen-browser.url = "github:0xc000022070/zen-browser-flake"; zen-browser.inputs.nixpkgs.follows = "nixpkgs"; + + disko.url = "github:nix-community/disko"; + disko.inputs.nixpkgs.follows = "nixpkgs"; }; outputs = { @@ -25,6 +28,7 @@ self, home-manager, zen-browser, + disko, ... }: { nixosConfigurations = { @@ -61,8 +65,28 @@ system = "x86_64-linux"; modules = [ ./hosts/sunken-ship.nix ]; }; + + # For disko-install: LUKS + WiFi; hostname/WiFi via --system-config. + server-install = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + disko.nixosModules.disko + ./disko-server.nix + ./hosts/server-install.nix + ]; + }; + + # Custom minimal installer ISO (build with: nix build .#installer-iso). + # Optional: add ./installer-wifi.nix (gitignored) to modules for live WiFi. + installer-iso = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ./installer-iso.nix ]; + }; }; + packages.x86_64-linux.installer-iso = + self.nixosConfigurations.installer-iso.config.system.build.isoImage; + # macOS (nix-darwin) configuration darwinConfigurations."Daniel-Macbook-Air" = nix-darwin.lib.darwinSystem { specialArgs = { inherit zen-browser; }; diff --git a/nixos/hosts/server-install.nix b/nixos/hosts/server-install.nix new file mode 100644 index 0000000..070e96c --- /dev/null +++ b/nixos/hosts/server-install.nix @@ -0,0 +1,35 @@ +# Minimal NixOS config for disko-install (new servers). +# Hostname and WiFi networks are overridden at install time via: +# disko-install --system-config '{"networking":{"hostName":"my-server"},...}' +# No host-specific hardware import; filesystems and LUKS come from disko-server.nix. +{ config, lib, pkgs, ... }: + +{ + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + networking.hostName = "nixos"; # Override with --system-config at install + networking.wireless.enable = true; + # networks."SSID".psk set via --system-config or imperative.conf after boot + + time.timeZone = "Europe/Copenhagen"; + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + system.stateVersion = "24.11"; + + users.users.danny = { + isNormalUser = true; + extraGroups = [ "wheel" ]; + # SSH keys: scp pubkey to server after install, then cat >> ~/.ssh/authorized_keys + }; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + }; + + security.sudo.wheelNeedsPassword = false; + environment.systemPackages = [ pkgs.git ]; +} diff --git a/nixos/installer-iso.nix b/nixos/installer-iso.nix new file mode 100644 index 0000000..5271a02 --- /dev/null +++ b/nixos/installer-iso.nix @@ -0,0 +1,14 @@ +# Custom minimal NixOS installer ISO for server installs (disko-install). +# Optional: add nixos/installer-wifi.nix (gitignored) to the flake modules to +# preconfigure live-system WiFi so the installer can reach the network. +{ config, pkgs, modulesPath, ... }: + +{ + imports = [ + (modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix") + ]; + + # Kernel modules for typical server WiFi (Intel). Add others if needed for your hardware. + boot.kernelModules = [ "iwlwifi" ]; + boot.extraModulePackages = [ ]; +} diff --git a/scripts/nixos-server-install.sh b/scripts/nixos-server-install.sh new file mode 100644 index 0000000..5d5eb5b --- /dev/null +++ b/scripts/nixos-server-install.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Run on a NixOS minimal live system (or installer ISO) to install NixOS with +# disko (LUKS + root). Prompts for hostname and target disk; optionally use +# INSTALLER_SYSTEM_CONFIG_FILE for WiFi etc. +# +# Usage: +# Export FLAKE_REF (e.g. github:User/dotfiles or path:/path/to/dotfiles/nixos). +# Or run from repo root and use: FLAKE_REF=path:$(pwd)/nixos +# sudo ./scripts/nixos-server-install.sh +# # or: sudo FLAKE_REF=github:User/dotfiles ./scripts/nixos-server-install.sh +# +# Optional: INSTALLER_SYSTEM_CONFIG_FILE=/path/to/json with full --system-config +# (e.g. hostName + networking.wireless.networks). If unset, only hostname is passed. +set -euo pipefail + +FLAKE_REF="${FLAKE_REF:-}" +if [[ -z "$FLAKE_REF" ]]; then + if [[ -d "$(dirname "$0")/../nixos" ]] && [[ -f "$(dirname "$0")/../nixos/flake.nix" ]]; then + REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + FLAKE_REF="path:${REPO_ROOT}/nixos" + else + echo "FLAKE_REF not set and not running from dotfiles repo. Example:" + echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles/nixos" + exit 1 + fi +fi + +if [[ "$EUID" -ne 0 ]]; then + echo "Run as root (e.g. sudo $0)" + exit 1 +fi + +read -r -p "Hostname (e.g. my-server): " hostname +if [[ -z "$hostname" ]]; then + echo "Hostname cannot be empty." + exit 1 +fi + +read -r -p "Target disk [default: /dev/sda]: " disk +disk="${disk:-/dev/sda}" +if [[ ! -b "$disk" ]]; then + echo "Not a block device: $disk" + exit 1 +fi + +if [[ -n "${INSTALLER_SYSTEM_CONFIG_FILE:-}" ]] && [[ -f "$INSTALLER_SYSTEM_CONFIG_FILE" ]]; then + # Use provided JSON; ensure hostname is set + if command -v jq &>/dev/null; then + SYSTEM_CONFIG=$(jq --arg h "$hostname" '.networking.hostName = $h' "$INSTALLER_SYSTEM_CONFIG_FILE") + else + SYSTEM_CONFIG=$(cat "$INSTALLER_SYSTEM_CONFIG_FILE") + echo "Warning: jq not found, using file as-is (hostname may not match)." + fi +else + SYSTEM_CONFIG='{"networking":{"hostName":"'"$hostname"'"}}' +fi + +echo "Flake: ${FLAKE_REF}#server-install" +echo "Disk: $disk" +echo "Hostname: $hostname" +echo "System config: $SYSTEM_CONFIG" +read -r -p "Proceed? [y/N] " confirm +if [[ "${confirm,,}" != "y" && "${confirm,,}" != "yes" ]]; then + echo "Aborted." + exit 0 +fi + +exec nix run github:nix-community/disko/latest#disko-install -- \ + --flake "${FLAKE_REF}#server-install" \ + --disk main "$disk" \ + --system-config "$SYSTEM_CONFIG"