Merge pull request #202 from nix-community/image-installer
Image installer
This commit is contained in:
commit
0b874a3bee
15 changed files with 287 additions and 54 deletions
|
@ -27,6 +27,12 @@ build_kexec_installer() {
|
|||
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() {
|
||||
declare -r tag=${1:-nixos-unstable} arch=${2:-x86_64-linux}
|
||||
tmp="$(mktemp -d)"
|
||||
|
@ -35,6 +41,7 @@ main() {
|
|||
build_kexec_installer "$tag" "$arch" "$tmp" ""
|
||||
build_kexec_installer "$tag" "$arch" "$tmp" "-noninteractive"
|
||||
build_netboot_image "$tag" "$arch" "$tmp"
|
||||
build_image_installer "$tag" "$arch" "$tmp"
|
||||
) | readarray -t assets
|
||||
for asset in "${assets[@]}"; do
|
||||
pushd "$(dirname "$asset")"
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
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;
|
||||
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
|
||||
{
|
||||
netboot-nixos-unstable = netboot nixos-unstable;
|
||||
|
@ -25,6 +26,9 @@
|
|||
kexec-installer-nixos-unstable = kexec-installer nixos-unstable [ ];
|
||||
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 [
|
||||
{
|
||||
system.kexec-installer.name = "nixos-kexec-installer-noninteractive";
|
||||
|
@ -46,6 +50,7 @@
|
|||
noninteractive = ./nix/noninteractive.nix;
|
||||
# TODO: also add a test here once we have https://github.com/NixOS/nixpkgs/pull/228346 merged
|
||||
netboot-installer = ./nix/netboot-installer/module.nix;
|
||||
image-installer = ./nix/image-installer/module.nix;
|
||||
};
|
||||
checks =
|
||||
let
|
||||
|
|
63
nix/image-installer/hidden-ssh-announcement.nix
Normal file
63
nix/image-installer/hidden-ssh-announcement.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
122
nix/image-installer/module.nix
Normal file
122
nix/image-installer/module.nix
Normal 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";
|
||||
}
|
17
nix/image-installer/wifi.nix
Normal file
17
nix/image-installer/wifi.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
{ config, lib, pkgs, ... }: {
|
||||
# more descriptive hostname than just "nixos"
|
||||
networking.hostName = lib.mkDefault "nixos-installer";
|
||||
|
||||
# We are stateless, so just default to latest.
|
||||
system.stateVersion = config.system.nixos.version;
|
||||
|
||||
|
@ -8,20 +11,7 @@
|
|||
}).latestCompatibleLinuxPackages;
|
||||
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;
|
||||
# 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 = [
|
||||
# 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
|
||||
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
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ in
|
|||
imports = [
|
||||
(modulesPath + "/installer/netboot/netboot-minimal.nix")
|
||||
../installer.nix
|
||||
../networkd.nix
|
||||
../serial.nix
|
||||
../restore-remote-access.nix
|
||||
];
|
||||
options = {
|
||||
system.kexec-installer.name = lib.mkOption {
|
||||
|
|
|
@ -78,8 +78,6 @@ MACAddress = {interface["address"]}
|
|||
[Network]
|
||||
# both ipv4 and ipv6
|
||||
DHCP = yes
|
||||
# link-local multicast name resolution
|
||||
LLMNR = yes
|
||||
# lets us discover the switch port we're connected to
|
||||
LLDP = yes
|
||||
# ipv6 router advertisements
|
||||
|
|
|
@ -160,7 +160,7 @@ makeTest' {
|
|||
assert ssh(["ls", "-la", "/run/foo"], check=False).returncode != 0, "kexeced node1 still has /run/foo"
|
||||
print(ssh(["parted", "--version"]))
|
||||
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()
|
||||
assert host_ed25519_before == host_ed25519_after, f"'{host_ed25519_before}' != '{host_ed25519_after}'"
|
||||
|
|
22
nix/log-network-status.nix
Normal file
22
nix/log-network-status.nix
Normal 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"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
imports = [
|
||||
(modulesPath + "/installer/netboot/netboot-minimal.nix")
|
||||
../installer.nix
|
||||
../networkd.nix
|
||||
../serial.nix
|
||||
../restore-remote-access.nix
|
||||
];
|
||||
|
||||
# We are stateless, so just default to latest.
|
||||
|
@ -25,7 +28,6 @@
|
|||
matchConfig.Type = "ether";
|
||||
networkConfig = {
|
||||
DHCP = "yes";
|
||||
LLMNR = "yes";
|
||||
EmitLLDP = "yes";
|
||||
IPv6AcceptRA = "yes";
|
||||
MulticastDNS = "yes";
|
||||
|
|
13
nix/networkd.nix
Normal file
13
nix/networkd.nix
Normal 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";
|
||||
}
|
17
nix/restore-remote-access.nix
Normal file
17
nix/restore-remote-access.nix
Normal 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
11
nix/serial.nix
Normal 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" ];
|
||||
}
|
Loading…
Reference in a new issue