Commit 2ceafb6b7617
Changed files (3)
systems/common/desktop/default.nix
@@ -109,6 +109,17 @@
# };
};
+ # Workaround: envfs force-disables the usrbinenv activation script because it
+ # mounts a FUSE filesystem over /usr/bin at boot. But on fresh installs (or
+ # with impermanence), /usr/bin/env doesn't exist yet, and systemd refuses to
+ # boot before envfs can mount. Create it via activation as a fallback.
+ # See: https://github.com/NixOS/nixpkgs/issues/462556
+ system.activationScripts.usrbinenv = lib.mkOverride 0 ''
+ mkdir -p /usr/bin
+ chmod 0755 /usr/bin
+ ln -sfn ${config.environment.usrbinenv} /usr/bin/env
+ '';
+
services = {
envfs.enable = true;
power-profiles-daemon.enable = true;
install.sh
@@ -30,7 +30,7 @@
# Tips:
# If you run out of space on the live USB, you can either:
# - Resize the tmpfs: mount -o remount,size=28G /nix/.rw-store
-# - Build remotely and copy the closure over SSH (see README)
+# - Build remotely using ./remote-install.sh (builds on workstation, copies over SSH)
#
# All extra arguments are forwarded to disko-install. Run with --help to see
# the full list of disko-install options:
remote-install.sh
@@ -0,0 +1,211 @@
+#!/usr/bin/env bash
+# Remotely install NixOS on a target machine from your workstation.
+#
+# This script automates the full remote installation process:
+# 1. Verifies SSH connectivity to the target
+# 2. Copies your SSH public key for passwordless access (optional)
+# 3. Builds the disko partitioning script locally
+# 4. Builds the full NixOS system closure locally
+# 5. Copies the disko script to the target and runs it (format + mount)
+# 6. Resizes the live USB's tmpfs to fit the incoming closure
+# 7. Copies the system closure to the target
+# 8. Runs nixos-install on the target
+#
+# This avoids building anything on the target, which is useful when the
+# live USB doesn't have enough space for a full build.
+#
+# Prerequisites:
+# - Target machine booted from NixOS live USB/CD
+# - Password set for nixos user: passwd nixos
+# - Network connectivity between workstation and target
+# - Host has a disko config in systems/<hostname>/disks.nix
+#
+# The disk device is defined in systems/<hostname>/disks.nix.
+# LUKS-encrypted hosts will prompt for a passphrase during partitioning.
+#
+# Examples:
+# ./remote-install.sh okinawa 192.168.1.108
+# ./remote-install.sh okinawa 192.168.1.108 --no-copy-keys
+# ./remote-install.sh okinawa 192.168.1.108 --tmpfs-size 24G
+#
+# Options:
+# --no-copy-keys Skip copying SSH public key to target
+# --tmpfs-size SIZE Size for /nix/.rw-store tmpfs (default: 28G)
+# --skip-format Skip disko format, only mount (for re-running)
+# -h, --help Show this help message
+
+set -euo pipefail
+
+# --- Colors and logging ---------------------------------------------------
+
+bold='\033[1m'
+blue='\033[1;34m'
+yellow='\033[1;33m'
+red='\033[1;31m'
+green='\033[1;32m'
+reset='\033[0m'
+
+info() { echo -e "${blue}==>${reset} ${bold}$*${reset}"; }
+warn() { echo -e "${yellow}==>${reset} ${bold}$*${reset}"; }
+error() { echo -e "${red}==>${reset} ${bold}$*${reset}" >&2; }
+success() { echo -e "${green}==>${reset} ${bold}$*${reset}"; }
+
+# --- Usage -----------------------------------------------------------------
+
+usage() {
+ sed -n '/^# Remotely install/,/^[^#]/{ /^#/s/^# \{0,1\}//p }' "$0"
+ echo ""
+ echo "Usage: $0 <hostname> <target-ip> [options...]"
+ echo ""
+ echo "Arguments:"
+ echo " hostname NixOS host configuration to install (from flake)"
+ echo " target-ip IP address of the target machine (NixOS live USB)"
+}
+
+# --- Argument parsing ------------------------------------------------------
+
+COPY_KEYS=true
+TMPFS_SIZE="28G"
+SKIP_FORMAT=false
+POSITIONAL=()
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ --no-copy-keys)
+ COPY_KEYS=false
+ shift
+ ;;
+ --tmpfs-size)
+ TMPFS_SIZE="$2"
+ shift 2
+ ;;
+ --skip-format)
+ SKIP_FORMAT=true
+ shift
+ ;;
+ *)
+ POSITIONAL+=("$1")
+ shift
+ ;;
+ esac
+done
+
+if [[ ${#POSITIONAL[@]} -lt 2 ]]; then
+ usage
+ exit 1
+fi
+
+HOSTNAME="${POSITIONAL[0]}"
+TARGET_IP="${POSITIONAL[1]}"
+
+SSH_USER="nixos"
+TARGET="${SSH_USER}@${TARGET_IP}"
+SSH_OPTS=(-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
+
+# SSH commands: use -t for interactive (LUKS passphrase), plain for scripted
+# shellcheck disable=SC2029 # $@ is intentionally expanded client-side
+ssh_cmd() { ssh "${SSH_OPTS[@]}" "${TARGET}" "$@"; }
+ssh_tty() { ssh "${SSH_OPTS[@]}" -t "${TARGET}" "$@"; }
+
+# --- Preflight checks ------------------------------------------------------
+
+info "Remote NixOS installation: ${HOSTNAME} → ${TARGET_IP}"
+echo ""
+
+# Check the host config exists
+if ! nix eval ".#nixosConfigurations.${HOSTNAME}" --apply 'x: true' &>/dev/null; then
+ error "No NixOS configuration found for '${HOSTNAME}'"
+ error "Available hosts:"
+ nix eval '.#nixosConfigurations' --apply 'x: builtins.attrNames x' 2>/dev/null
+ exit 1
+fi
+
+# Verify SSH connectivity
+info "Verifying SSH connectivity to ${TARGET}..."
+if ! ssh_cmd "echo 'ok'" &>/dev/null; then
+ error "Cannot connect to ${TARGET}"
+ error "Make sure:"
+ error " 1. Target is booted from NixOS live USB"
+ error " 2. You've run 'passwd nixos' on the target"
+ error " 3. IP address ${TARGET_IP} is correct"
+ exit 1
+fi
+success "SSH connection verified"
+
+# --- Step 1: Copy SSH keys -------------------------------------------------
+
+if [[ "${COPY_KEYS}" == true ]]; then
+ info "Copying SSH public key to target..."
+ ssh-copy-id "${SSH_OPTS[@]}" "${TARGET}" 2>/dev/null || true
+fi
+
+# --- Step 2: Build disko script locally ------------------------------------
+
+DISKO_LINK="/tmp/disko-${HOSTNAME}"
+
+if [[ "${SKIP_FORMAT}" == false ]]; then
+ info "Building disko partitioning script..."
+ nix build ".#nixosConfigurations.${HOSTNAME}.config.system.build.diskoScript" \
+ -o "${DISKO_LINK}"
+ DISKO_SCRIPT=$(readlink -f "${DISKO_LINK}")
+ success "Disko script: ${DISKO_SCRIPT}"
+fi
+
+# --- Step 3: Build system closure locally -----------------------------------
+
+info "Building system closure (this may take a while)..."
+nix build ".#nixosConfigurations.${HOSTNAME}.config.system.build.toplevel"
+SYSTEM_PATH=$(readlink -f result)
+CLOSURE_SIZE=$(nix path-info -Sh "${SYSTEM_PATH}" 2>/dev/null | awk '{print $2}')
+success "System closure: ${SYSTEM_PATH} (${CLOSURE_SIZE})"
+
+# --- Step 4: Format and mount the target disk -------------------------------
+
+if [[ "${SKIP_FORMAT}" == false ]]; then
+ echo ""
+ warn "This will ERASE the target disk on ${TARGET_IP}!"
+ warn "LUKS-encrypted hosts will prompt for a passphrase."
+ echo ""
+ read -p "Continue with disk formatting? [y/N] " -n 1 -r
+ echo ""
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ error "Aborted."
+ exit 1
+ fi
+
+ info "Copying disko script to target..."
+ NIX_SSHOPTS="${SSH_OPTS[*]}" nix copy --to "ssh://${TARGET}" "${DISKO_SCRIPT}"
+
+ info "Running disko on target..."
+ ssh_tty "sudo ${DISKO_SCRIPT}"
+ success "Disk formatted and mounted"
+fi
+
+# --- Step 5: Resize tmpfs ---------------------------------------------------
+
+info "Resizing tmpfs on target to ${TMPFS_SIZE}..."
+ssh_cmd "sudo mount -o remount,size=${TMPFS_SIZE} /nix/.rw-store"
+success "tmpfs resized to ${TMPFS_SIZE}"
+
+# --- Step 6: Copy system closure to target ----------------------------------
+
+info "Copying system closure to target (${CLOSURE_SIZE})..."
+NIX_SSHOPTS="${SSH_OPTS[*]}" nix copy -v --to "ssh://${TARGET}" "${SYSTEM_PATH}"
+success "System closure copied"
+
+# --- Step 7: Install --------------------------------------------------------
+
+info "Running nixos-install on target..."
+ssh_tty "sudo nixos-install --root /mnt --system '${SYSTEM_PATH}' --no-channel-copy"
+
+info "Running system activation..."
+ssh_tty "sudo nixos-enter --root /mnt -- /run/current-system/activate"
+success "Installation complete!"
+
+echo ""
+info "You can now reboot the target:"
+echo " ssh ${SSH_OPTS[*]} ${TARGET} 'sudo reboot'"