Merge pull request #202 from nix-community/image-installer

Image installer
This commit is contained in:
Jörg Thalheim 2024-04-17 11:59:06 +02:00 committed by GitHub
commit 0b874a3bee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 287 additions and 54 deletions

View file

@ -27,6 +27,12 @@ build_kexec_installer() {
echo "$out/nixos-kexec-installer${variant}-$arch.tar.gz" echo "$out/nixos-kexec-installer${variant}-$arch.tar.gz"
} }
build_image_installer() {
declare -r tag=$1 arch=$2 tmp=$3
out=$(nix build --print-out-paths --option accept-flake-config true -L ".#packages.${arch}.image-installer-${tag//./}${variant}")
echo "$out/iso/nixos-installer-${arch}.iso"
}
main() { main() {
declare -r tag=${1:-nixos-unstable} arch=${2:-x86_64-linux} declare -r tag=${1:-nixos-unstable} arch=${2:-x86_64-linux}
tmp="$(mktemp -d)" tmp="$(mktemp -d)"
@ -35,6 +41,7 @@ main() {
build_kexec_installer "$tag" "$arch" "$tmp" "" build_kexec_installer "$tag" "$arch" "$tmp" ""
build_kexec_installer "$tag" "$arch" "$tmp" "-noninteractive" build_kexec_installer "$tag" "$arch" "$tmp" "-noninteractive"
build_netboot_image "$tag" "$arch" "$tmp" build_netboot_image "$tag" "$arch" "$tmp"
build_image_installer "$tag" "$arch" "$tmp"
) | readarray -t assets ) | readarray -t assets
for asset in "${assets[@]}"; do for asset in "${assets[@]}"; do
pushd "$(dirname "$asset")" pushd "$(dirname "$asset")"

View file

@ -18,6 +18,7 @@
netboot = nixpkgs: (import (nixpkgs + "/nixos/release.nix") { }).netboot.${system}; netboot = nixpkgs: (import (nixpkgs + "/nixos/release.nix") { }).netboot.${system};
kexec-installer = nixpkgs: modules: (nixpkgs.legacyPackages.${system}.nixos (modules ++ [ self.nixosModules.kexec-installer ])).config.system.build.kexecTarball; kexec-installer = nixpkgs: modules: (nixpkgs.legacyPackages.${system}.nixos (modules ++ [ self.nixosModules.kexec-installer ])).config.system.build.kexecTarball;
netboot-installer = nixpkgs: (nixpkgs.legacyPackages.${system}.nixos [ self.nixosModules.netboot-installer ]).config.system.build.netboot; netboot-installer = nixpkgs: (nixpkgs.legacyPackages.${system}.nixos [ self.nixosModules.netboot-installer ]).config.system.build.netboot;
image-installer = nixpkgs: (nixpkgs.legacyPackages.${system}.nixos [ self.nixosModules.image-installer ]).config.system.build.isoImage;
in in
{ {
netboot-nixos-unstable = netboot nixos-unstable; netboot-nixos-unstable = netboot nixos-unstable;
@ -25,6 +26,9 @@
kexec-installer-nixos-unstable = kexec-installer nixos-unstable [ ]; kexec-installer-nixos-unstable = kexec-installer nixos-unstable [ ];
kexec-installer-nixos-2311 = kexec-installer nixos-2311 [ ]; kexec-installer-nixos-2311 = kexec-installer nixos-2311 [ ];
image-installer-unstable = image-installer nixos-unstable;
image-installer-2311 = image-installer nixos-2311;
kexec-installer-nixos-unstable-noninteractive = kexec-installer nixos-unstable [ kexec-installer-nixos-unstable-noninteractive = kexec-installer nixos-unstable [
{ {
system.kexec-installer.name = "nixos-kexec-installer-noninteractive"; system.kexec-installer.name = "nixos-kexec-installer-noninteractive";
@ -46,6 +50,7 @@
noninteractive = ./nix/noninteractive.nix; noninteractive = ./nix/noninteractive.nix;
# TODO: also add a test here once we have https://github.com/NixOS/nixpkgs/pull/228346 merged # TODO: also add a test here once we have https://github.com/NixOS/nixpkgs/pull/228346 merged
netboot-installer = ./nix/netboot-installer/module.nix; netboot-installer = ./nix/netboot-installer/module.nix;
image-installer = ./nix/image-installer/module.nix;
}; };
checks = checks =
let let

View file

@ -0,0 +1,63 @@
{
config,
lib,
pkgs,
...
}:
{
options.hidden-ssh-announce = {
enable = lib.mkEnableOption "hidden-ssh-announce";
script = lib.mkOption {
type = lib.types.package;
default = pkgs.writers.writeDash "test-output" "echo $1";
description = ''
script to run when the hidden tor service was started and they hostname is known.
takes the hostname as $1
'';
};
};
config = lib.mkIf config.hidden-ssh-announce.enable {
services.openssh.enable = true;
services.tor = {
enable = true;
relay.onionServices.hidden-ssh = {
version = 3;
map = [
{
port = 22;
target.port = 22;
}
];
};
client.enable = true;
};
systemd.services.hidden-ssh-announce = {
description = "announce hidden ssh";
after = [
"tor.service"
"network-online.target"
];
wants = [
"tor.service"
"network-online.target"
];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
# ${pkgs.tor}/bin/torify
ExecStart = pkgs.writeShellScript "announce-hidden-service" ''
set -efu
until test -e ${config.services.tor.settings.DataDirectory}/onion/hidden-ssh/hostname; do
echo "still waiting for ${config.services.tor.settings.DataDirectory}/onion/hidden-ssh/hostname"
sleep 1
done
${config.hidden-ssh-announce.script} "$(cat ${config.services.tor.settings.DataDirectory}/onion/hidden-ssh/hostname)"
'';
PrivateTmp = "true";
User = "tor";
Type = "oneshot";
};
};
};
}

View file

@ -0,0 +1,122 @@
{
lib,
pkgs,
modulesPath,
...
}:
let
network-status = pkgs.writeShellScript "network-status" ''
export PATH=${
lib.makeBinPath (
with pkgs;
[
iproute2
coreutils
gnugrep
nettools
gum
]
)
}
set -efu -o pipefail
msgs=()
if [[ -e /var/shared/qrcode.utf8 ]]; then
qrcode=$(gum style --border-foreground 240 --border normal "$(< /var/shared/qrcode.utf8)")
msgs+=("$qrcode")
fi
network_status="Root password: $(cat /var/shared/root-password)
Local network addresses:
$(ip -brief -color addr | grep -v 127.0.0.1)
$([[ -e /var/shared/onion-hostname ]] && echo "Onion address: $(cat /var/shared/onion-hostname)" || echo "Onion address: Waiting for tor network to be ready...")
Multicast DNS: $(hostname).local"
network_status=$(gum style --border-foreground 240 --border normal "$network_status")
msgs+=("$network_status")
msgs+=("Press 'Ctrl-C' for console access")
gum join --vertical "''${msgs[@]}"
'';
in
{
imports = [
(modulesPath + "/installer/cd-dvd/installation-cd-base.nix")
../installer.nix
./wifi.nix
./hidden-ssh-announcement.nix
];
systemd.tmpfiles.rules = [ "d /var/shared 0777 root root - -" ];
services.openssh.settings.PermitRootLogin = "yes";
system.activationScripts.root-password = ''
mkdir -p /var/shared
${pkgs.xkcdpass}/bin/xkcdpass --numwords 3 --delimiter - --count 1 > /var/shared/root-password
echo "root:$(cat /var/shared/root-password)" | chpasswd
'';
hidden-ssh-announce = {
enable = true;
script = pkgs.writeShellScript "write-hostname" ''
set -efu
export PATH=${
lib.makeBinPath (
with pkgs;
[
iproute2
coreutils
jq
qrencode
]
)
}
mkdir -p /var/shared
echo "$1" > /var/shared/onion-hostname
local_addrs=$(ip -json addr | jq '[map(.addr_info) | flatten | .[] | select(.scope == "global") | .local]')
jq -nc \
--arg password "$(cat /var/shared/root-password)" \
--arg onion_address "$(cat /var/shared/onion-hostname)" \
--argjson local_addrs "$local_addrs" \
'{ password: $password, tor: $onion_address, addresses: $local_addrs }' \
> /var/shared/login.json
cat /var/shared/login.json | qrencode -s 2 -m 2 -t utf8 -o /var/shared/qrcode.utf8
'';
};
services.getty.autologinUser = lib.mkForce "root";
console.earlySetup = true;
console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u22n.psf.gz";
# Less ipv6 addresses to reduce the noise
networking.tempAddresses = "disabled";
# Tango theme: https://yayachiken.net/en/posts/tango-colors-in-terminal/
console.colors = lib.mkDefault [
"000000"
"CC0000"
"4E9A06"
"C4A000"
"3465A4"
"75507B"
"06989A"
"D3D7CF"
"555753"
"EF2929"
"8AE234"
"FCE94F"
"739FCF"
"AD7FA8"
"34E2E2"
"EEEEEC"
];
programs.bash.interactiveShellInit = ''
if [[ "$(tty)" =~ /dev/(tty1|hvc0|ttyS0)$ ]]; then
# workaround for https://github.com/NixOS/nixpkgs/issues/219239
systemctl restart systemd-vconsole-setup.service
watch --no-title --color ${network-status}
fi
'';
# No one got time for xz compression.
isoImage.squashfsCompression = "zstd";
isoImage.isoName = lib.mkForce "nixos-installer-${pkgs.system}.iso";
}

View file

@ -0,0 +1,17 @@
{
imports = [ ../networkd.nix ];
# use iwd instead of wpa_supplicant
networking.wireless.enable = false;
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
networking.wireless.iwd = {
enable = true;
settings = {
Network = {
EnableIPv6 = true;
RoutePriorityOffset = 300;
};
Settings.AutoConnect = true;
};
};
}

View file

@ -1,4 +1,7 @@
{ config, lib, pkgs, ... }: { { config, lib, pkgs, ... }: {
# more descriptive hostname than just "nixos"
networking.hostName = lib.mkDefault "nixos-installer";
# We are stateless, so just default to latest. # We are stateless, so just default to latest.
system.stateVersion = config.system.nixos.version; system.stateVersion = config.system.nixos.version;
@ -8,20 +11,7 @@
}).latestCompatibleLinuxPackages; }).latestCompatibleLinuxPackages;
boot.zfs.removeLinuxDRM = lib.mkDefault pkgs.hostPlatform.isAarch64; boot.zfs.removeLinuxDRM = lib.mkDefault pkgs.hostPlatform.isAarch64;
# IPMI SOL console redirection stuff
boot.kernelParams =
[ "console=tty0" ] ++
(lib.optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) "console=ttyAMA0,115200") ++
(lib.optional (pkgs.stdenv.hostPlatform.isRiscV) "console=ttySIF0,115200") ++
[ "console=ttyS0,115200" ];
documentation.enable = false; documentation.enable = false;
# Not really needed. Saves a few bytes and the only service we are running is sshd, which we want to be reachable.
networking.firewall.enable = false;
networking.useNetworkd = true;
systemd.network.enable = true;
networking.dhcpcd.enable = false;
environment.systemPackages = [ environment.systemPackages = [
# for zapping of disko # for zapping of disko
@ -36,40 +26,4 @@
# Don't add nixpkgs to the image to save space, for our intended use case we don't need it # Don't add nixpkgs to the image to save space, for our intended use case we don't need it
system.installer.channel.enable = false; system.installer.channel.enable = false;
systemd.services.log-network-status = {
wantedBy = [ "multi-user.target" ];
# No point in restarting this. We just need this after boot
restartIfChanged = false;
serviceConfig = {
Type = "oneshot";
StandardOutput = "journal+console";
ExecStart = [
# Allow failures, so it still prints what interfaces we have even if we
# not get online
"-${pkgs.systemd}/lib/systemd/systemd-networkd-wait-online"
"${pkgs.iproute2}/bin/ip -c addr"
"${pkgs.iproute2}/bin/ip -c -6 route"
"${pkgs.iproute2}/bin/ip -c -4 route"
"${pkgs.systemd}/bin/networkctl status"
];
};
};
# Restore ssh host and user keys if they are available.
# This avoids warnings of unknown ssh keys.
boot.initrd.postMountCommands = ''
mkdir -m 700 -p /mnt-root/root/.ssh
mkdir -m 755 -p /mnt-root/etc/ssh
mkdir -m 755 -p /mnt-root/root/network
if [[ -f ssh/authorized_keys ]]; then
install -m 400 ssh/authorized_keys /mnt-root/root/.ssh
fi
install -m 400 ssh/ssh_host_* /mnt-root/etc/ssh
cp *.json /mnt-root/root/network/
if [[ -f machine-id ]]; then
cp machine-id /mnt-root/etc/machine-id
fi
'';
} }

View file

@ -10,6 +10,9 @@ in
imports = [ imports = [
(modulesPath + "/installer/netboot/netboot-minimal.nix") (modulesPath + "/installer/netboot/netboot-minimal.nix")
../installer.nix ../installer.nix
../networkd.nix
../serial.nix
../restore-remote-access.nix
]; ];
options = { options = {
system.kexec-installer.name = lib.mkOption { system.kexec-installer.name = lib.mkOption {

View file

@ -78,8 +78,6 @@ MACAddress = {interface["address"]}
[Network] [Network]
# both ipv4 and ipv6 # both ipv4 and ipv6
DHCP = yes DHCP = yes
# link-local multicast name resolution
LLMNR = yes
# lets us discover the switch port we're connected to # lets us discover the switch port we're connected to
LLDP = yes LLDP = yes
# ipv6 router advertisements # ipv6 router advertisements

View file

@ -160,7 +160,7 @@ makeTest' {
assert ssh(["ls", "-la", "/run/foo"], check=False).returncode != 0, "kexeced node1 still has /run/foo" assert ssh(["ls", "-la", "/run/foo"], check=False).returncode != 0, "kexeced node1 still has /run/foo"
print(ssh(["parted", "--version"])) print(ssh(["parted", "--version"]))
host = ssh(["hostname"], stdout=subprocess.PIPE).stdout.strip() host = ssh(["hostname"], stdout=subprocess.PIPE).stdout.strip()
assert host == "nixos", f"hostname is {host}, not nixos" assert host == "nixos-installer", f"hostname is {host}, not nixos-installer"
host_ed25519_after = ssh(["cat", "/etc/ssh/ssh_host_ed25519_key.pub"], stdout=subprocess.PIPE).stdout.strip() host_ed25519_after = ssh(["cat", "/etc/ssh/ssh_host_ed25519_key.pub"], stdout=subprocess.PIPE).stdout.strip()
assert host_ed25519_before == host_ed25519_after, f"'{host_ed25519_before}' != '{host_ed25519_after}'" assert host_ed25519_before == host_ed25519_after, f"'{host_ed25519_before}' != '{host_ed25519_after}'"

View file

@ -0,0 +1,22 @@
{ pkgs, ... }:
{
systemd.services.log-network-status = {
wantedBy = [ "multi-user.target" ];
# No point in restarting this. We just need this after boot
restartIfChanged = false;
serviceConfig = {
Type = "oneshot";
StandardOutput = "journal+console";
ExecStart = [
# Allow failures, so it still prints what interfaces we have even if we
# not get online
"-${pkgs.systemd}/lib/systemd/systemd-networkd-wait-online"
"${pkgs.iproute2}/bin/ip -c addr"
"${pkgs.iproute2}/bin/ip -c -6 route"
"${pkgs.iproute2}/bin/ip -c -4 route"
"${pkgs.systemd}/bin/networkctl status"
];
};
};
}

View file

@ -1 +0,0 @@

View file

@ -3,6 +3,9 @@
imports = [ imports = [
(modulesPath + "/installer/netboot/netboot-minimal.nix") (modulesPath + "/installer/netboot/netboot-minimal.nix")
../installer.nix ../installer.nix
../networkd.nix
../serial.nix
../restore-remote-access.nix
]; ];
# We are stateless, so just default to latest. # We are stateless, so just default to latest.
@ -25,7 +28,6 @@
matchConfig.Type = "ether"; matchConfig.Type = "ether";
networkConfig = { networkConfig = {
DHCP = "yes"; DHCP = "yes";
LLMNR = "yes";
EmitLLDP = "yes"; EmitLLDP = "yes";
IPv6AcceptRA = "yes"; IPv6AcceptRA = "yes";
MulticastDNS = "yes"; MulticastDNS = "yes";

13
nix/networkd.nix Normal file
View file

@ -0,0 +1,13 @@
{ lib, ... }:
{
# Not really needed. Saves a few bytes and the only service we are running is sshd, which we want to be reachable.
networking.firewall.enable = false;
networking.useNetworkd = true;
systemd.network.enable = true;
# mdns
networking.firewall.allowedUDPPorts = [ 5353 ];
systemd.network.networks."99-ethernet-default-dhcp".networkConfig.MulticastDNS = lib.mkDefault "yes";
systemd.network.networks."99-wireless-client-dhcp".networkConfig.MulticastDNS = lib.mkDefault "yes";
}

View file

@ -0,0 +1,17 @@
{
# Restore ssh host and user keys if they are available.
# This avoids warnings of unknown ssh keys.
boot.initrd.postMountCommands = ''
mkdir -m 700 -p /mnt-root/root/.ssh
mkdir -m 755 -p /mnt-root/etc/ssh
mkdir -m 755 -p /mnt-root/root/network
if [[ -f ssh/authorized_keys ]]; then
install -m 400 ssh/authorized_keys /mnt-root/root/.ssh
fi
install -m 400 ssh/ssh_host_* /mnt-root/etc/ssh
cp *.json /mnt-root/root/network/
if [[ -f machine-id ]]; then
cp machine-id /mnt-root/etc/machine-id
fi
'';
}

11
nix/serial.nix Normal file
View file

@ -0,0 +1,11 @@
{ pkgs, lib, ... }:
{
# IPMI SOL console redirection stuff
boot.kernelParams =
[ "console=tty0" ]
++ (lib.optional (
pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64
) "console=ttyAMA0,115200")
++ (lib.optional (pkgs.stdenv.hostPlatform.isRiscV) "console=ttySIF0,115200")
++ [ "console=ttyS0,115200" ];
}