Archived
Both existed only so the installer could boot as an LXC container and nixos-install some other host from within it, but lxc-* targets are already excluded from the install menu (nixos-install can't touch its own running root filesystem), and now have their own direct tarball path anyway. That left the installer's own LXC form with no real use case, and packages.all with only two members worth bundling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
250 lines
11 KiB
Nix
250 lines
11 KiB
Nix
{ pkgs, lib, vars, ... }:
|
|
|
|
{
|
|
imports = [
|
|
./host-keys.nix
|
|
];
|
|
|
|
networking.useDHCP = lib.mkDefault true;
|
|
|
|
# Recommended over the true default (bypasses ZFS's own import safeguards)
|
|
# per the option's own docs. This installer environment has no ZFS pools
|
|
# of its own to import, so this is a no-op here — just silences the
|
|
# eval-time warning, matching modules/common/configuration.nix.
|
|
boot.zfs.forceImportRoot = false;
|
|
|
|
time.timeZone = vars.timeZone;
|
|
|
|
# Without this, the installer only ever sees cache.nixos.org, which
|
|
# doesn't carry sops-install-secrets (it's built straight from the
|
|
# sops-nix flake's own Go source, not part of nixpkgs) — every install
|
|
# would otherwise compile it from scratch, which is what ran an 8GB LXC
|
|
# container's disk out of space. Push a built copy to nix-cache once
|
|
# (from a machine with real disk headroom) and every future install,
|
|
# of any type, fetches instead of rebuilding.
|
|
nix.settings = {
|
|
substituters = [
|
|
"http://nix-cache"
|
|
"https://cache.nixos.org/"
|
|
];
|
|
trusted-public-keys = [
|
|
"cache.local-1:usoWYanY3Kpq2+kDIS2nhWoLZiRxanmdysdzqCFBHW4="
|
|
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
|
];
|
|
};
|
|
|
|
environment = {
|
|
systemPackages = with pkgs; [
|
|
git
|
|
curl
|
|
jq
|
|
parted
|
|
e2fsprogs
|
|
btrfs-progs
|
|
util-linux
|
|
disko
|
|
];
|
|
|
|
# Write auto-install script to /root
|
|
etc."auto-install.sh" = {
|
|
text = ''
|
|
#!/run/current-system/sw/bin/bash
|
|
set -eux
|
|
|
|
set -euo pipefail
|
|
|
|
export FLAKE_BASE_URL="git+https://${vars.lanDomain}/beatzaplenty/nixos.git"
|
|
|
|
echo "Fetching available NixOS hosts from flake..."
|
|
# Two categories deliberately excluded from the menu:
|
|
# lxc-* — these build a config.system.build.tarball meant for
|
|
# `pct restore` on Proxmox directly, not an install.
|
|
# Running nixos-install against one here would
|
|
# bind-mount / onto /mnt and then refuse to touch the
|
|
# filesystem it's currently running on — see
|
|
# docs/auto-installer.md.
|
|
# installer — this *is* the installer image's own flake target,
|
|
# not a deployable host; "installing" it means
|
|
# nixos-install-ing a copy of the installer into
|
|
# itself.
|
|
mapfile -t options < <(
|
|
nix eval --json --no-use-registries --no-accept-flake-config --extra-experimental-features "flakes nix-command" \
|
|
"''${FLAKE_BASE_URL}#nixosConfigurations" \
|
|
--apply builtins.attrNames \
|
|
| jq -r '.[]
|
|
| select(startswith("lxc-") | not)
|
|
| select(. != "installer")'
|
|
)
|
|
|
|
if [[ ''${#options[@]} -eq 0 ]]; then
|
|
echo "ERROR: No NixOS hosts found in ''${FLAKE_BASE_URL}#nixosConfigurations" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Note: lxc-* targets aren't installed this way — build them with"
|
|
echo " nix build .#nixosConfigurations.<name>.config.system.build.tarball"
|
|
echo "and 'pct restore' the result on Proxmox directly. See docs/auto-installer.md."
|
|
|
|
echo "Choose the flake profile to install:"
|
|
select choice in "''${options[@]}"; do
|
|
if [[ -n "$choice" ]]; then
|
|
echo "You selected: $choice"
|
|
break
|
|
else
|
|
echo "Invalid selection. Try again."
|
|
fi
|
|
done
|
|
|
|
echo "Starting install with flake: ''${FLAKE_BASE_URL}#''${choice}"
|
|
|
|
# Optional: confirm before proceeding
|
|
read -rp "Proceed with installation? (y/N): " confirm
|
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
echo "Aborted."
|
|
exit 1
|
|
fi
|
|
|
|
# A nix-cache host is *the* substituter/remote-builder for every other
|
|
# host once installed (its own config explicitly excludes itself from
|
|
# using either — see buildType != "nix-cache" in the nixos flake.nix).
|
|
# Installing one shouldn't depend on a nix-cache substituter either,
|
|
# for the same reason — plus in practice "nix-cache" only resolves over
|
|
# Tailscale, which a fresh installer environment was never connected to
|
|
# anyway, so it's dead weight even for non-nix-cache installs until
|
|
# that's sorted out. Override it away here specifically for nix-cache
|
|
# targets to keep install-time behaviour consistent with run-time.
|
|
nix_extra_opts=()
|
|
if [[ "''${choice}" == *-nix-cache ]]; then
|
|
echo "Installing a nix-cache host — skipping the nix-cache substituter."
|
|
nix_extra_opts+=(--option substituters "https://cache.nixos.org/")
|
|
fi
|
|
|
|
if nix eval --refresh --json --extra-experimental-features "flakes nix-command" "''${nix_extra_opts[@]}" "''${FLAKE_BASE_URL}#nixosConfigurations.''${choice}.config.disko.devices.disk.main.device" >/dev/null 2>&1; then
|
|
disko --mode destroy,format,mount \
|
|
--flake "''${FLAKE_BASE_URL}#''${choice}" "''${nix_extra_opts[@]}" --yes-wipe-all-disks
|
|
else
|
|
# No raw disk to partition — true for every LXC container, which has
|
|
# no block device visible from inside it, only its already-mounted
|
|
# root filesystem. nixos-install defaults to installing into /mnt; if
|
|
# nothing points /mnt at the real root, the new system gets built and
|
|
# written to a disconnected empty directory, never actually becomes
|
|
# bootable, and the container silently keeps running this installer
|
|
# environment forever. Bind-mount / onto /mnt so the install actually
|
|
# lands on the filesystem this container boots from.
|
|
echo "Selected host has no Disko configuration — installing in place via bind mount."
|
|
mount --bind / /mnt
|
|
fi
|
|
|
|
# sops-nix derives this host's decryption key from its own SSH host key
|
|
# at *activation* time, which runs before systemd would otherwise
|
|
# generate one on first boot. Without pre-seeding it here, secrets
|
|
# (including the login password) fail to decrypt on first boot.
|
|
# Generate the key with scripts/prepare-host-key.sh first.
|
|
#
|
|
# Two places a key can come from, checked in order:
|
|
# /etc/host-keys — baked into this image at build time (see
|
|
# modules/installer/host-keys.nix; only present
|
|
# if built with NIXOS_HOST_KEYS_DIR set)
|
|
# /root/host-keys — scp'd in manually after boot (older fallback,
|
|
# still supported for images built without keys)
|
|
mkdir -p /root/host-keys
|
|
if [[ -f "/etc/host-keys/''${choice}_ssh_host_ed25519_key" ]]; then
|
|
echo "Found baked-in SSH host key for ''${choice}, installing to target..."
|
|
install -D -m 0600 "/etc/host-keys/''${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
|
install -D -m 0644 "/etc/host-keys/''${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
|
elif [[ -f "/root/host-keys/''${choice}_ssh_host_ed25519_key" ]]; then
|
|
echo "Found pre-seeded SSH host key for ''${choice}, installing to target..."
|
|
install -D -m 0600 "/root/host-keys/''${choice}_ssh_host_ed25519_key" /mnt/etc/ssh/ssh_host_ed25519_key
|
|
install -D -m 0644 "/root/host-keys/''${choice}_ssh_host_ed25519_key.pub" /mnt/etc/ssh/ssh_host_ed25519_key.pub
|
|
else
|
|
echo "WARNING: no SSH host key found for ''${choice} (checked /etc/host-keys and /root/host-keys)"
|
|
echo "sops-nix secrets (including the login password) will NOT decrypt on first boot."
|
|
echo "Run scripts/prepare-host-key.sh for host ''${choice} on your admin workstation first,"
|
|
echo "then either rebuild this image with NIXOS_HOST_KEYS_DIR set, or scp the result to"
|
|
echo "/root/host-keys/ on this machine."
|
|
read -rp "Continue without a pre-seeded key anyway? (y/N): " skip_key
|
|
if [[ ! "$skip_key" =~ ^[Yy]$ ]]; then
|
|
echo "Aborted."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
mkdir -p /mnt/install-tmp
|
|
export TMPDIR=/mnt/install-tmp
|
|
|
|
nixos-install \
|
|
--flake "''${FLAKE_BASE_URL}#''${choice}" \
|
|
"''${nix_extra_opts[@]}" \
|
|
--no-root-password
|
|
|
|
|
|
rm -rf /mnt/install-tmp
|
|
# Redundant copy of the host's private key — the real one is now at
|
|
# /etc/ssh/ssh_host_ed25519_key. Nothing NixOS-managed ever cleans this
|
|
# up on its own since it was written imperatively, not declaratively.
|
|
rm -rf /root/host-keys
|
|
sleep 10
|
|
reboot
|
|
'';
|
|
|
|
mode = "0755";
|
|
};
|
|
};
|
|
|
|
programs.git.enable = true;
|
|
|
|
# Run the installer on first login. Previously this copied an /etc file
|
|
# into the nixos user's ~/.bash_profile via an activation script that
|
|
# got dropped in a refactor (and only ever worked for that one user
|
|
# anyway) — loginShellInit is NixOS's native hook for this, applies to
|
|
# any user's login shell (root included), and needs no home-directory
|
|
# file-copying/chown.
|
|
programs.bash.loginShellInit = ''
|
|
if [ -n "$PS1" ] && [ ! -e "$HOME/.auto_install_ran" ]; then
|
|
sudo /etc/auto-install.sh
|
|
touch "$HOME/.auto_install_ran"
|
|
fi
|
|
'';
|
|
|
|
services.openssh.enable = true;
|
|
|
|
services.openssh.settings = {
|
|
PermitRootLogin = "yes";
|
|
PasswordAuthentication = true;
|
|
};
|
|
|
|
# nixpkgs' own installer profile (profiles/installation-device.nix, pulled
|
|
# in via installation-cd-minimal.nix) sets initialHashedPassword = "" for
|
|
# both users — its own passwordless-login convention for install media.
|
|
# That's a second, non-null password option alongside our hashedPassword
|
|
# below, which NixOS warns about as ambiguous precedence. Force it null
|
|
# rather than adopting passwordless login: this image now also boots over
|
|
# LAN PXE with PasswordAuthentication enabled, so passwordless root SSH
|
|
# would be reachable by anyone on the LAN, not just local console.
|
|
users.users.root = {
|
|
hashedPassword =
|
|
"$6$Kwv9KAyvcurAViQF$H4.u3feqGE7lVoNgkFXhE3n2Pmo//9JYDTCz8ifrVHBxPjwa1xMby7tEZ8Bpt5MXs9Rkx6/YbZWxs5CpH0s/70";
|
|
initialHashedPassword = lib.mkForce null;
|
|
};
|
|
|
|
users.users.${vars.primaryUser} = {
|
|
isNormalUser = true;
|
|
|
|
extraGroups = [
|
|
"wheel"
|
|
];
|
|
|
|
shell = pkgs.bashInteractive;
|
|
|
|
hashedPassword =
|
|
"$6$Kwv9KAyvcurAViQF$H4.u3feqGE7lVoNgkFXhE3n2Pmo//9JYDTCz8ifrVHBxPjwa1xMby7tEZ8Bpt5MXs9Rkx6/YbZWxs5CpH0s/70";
|
|
initialHashedPassword = lib.mkForce null;
|
|
|
|
openssh.authorizedKeys.keys = [
|
|
vars.adminSshKey
|
|
];
|
|
};
|
|
|
|
system.stateVersion = "26.05";
|
|
}
|