Merge pull request #6 from numtide/terraform

flakeify
This commit is contained in:
Jonas Chevalier 2022-12-21 19:01:35 +01:00 committed by GitHub
commit 8db4c0a233
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 313 additions and 23 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
result

49
flake.lock Normal file
View File

@ -0,0 +1,49 @@
{
"nodes": {
"disko": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1670529818,
"narHash": "sha256-flPaFV2V4SBtGY+u5CYJzQWJutd1D6zSwomILcSgtCI=",
"owner": "nix-community",
"repo": "disko",
"rev": "66add2cd9edab96f5e87477229772bf2b3ca513a",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "master",
"repo": "disko",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1670559856,
"narHash": "sha256-xUkgQRFqE6HIFQXs9SIXMZiXcLaH2415UR6w/FnsgcY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "6bc6f77cb171a74001033d94f17f49043a9f1804",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"disko": "disko",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

35
flake.nix Normal file
View File

@ -0,0 +1,35 @@
{
description = "A universal nixos installer, just needs ssh access to the target system";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
inputs.disko.url = "github:nix-community/disko/master";
inputs.disko.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self, disko, nixpkgs, ... }: let
supportedSystems = [
"x86_64-linux"
"i686-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
in {
packages = forAllSystems (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
nixos-remote = pkgs.callPackage ./package.nix {};
default = self.packages.${system}.nixos-remote;
});
checks.x86_64-linux = let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
from-nixos = import ./tests/from-nixos.nix {
inherit pkgs;
disko = disko.nixosModules.disko;
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
eval-config = import (pkgs.path + "/nixos/lib/eval-config.nix");
};
};
};
}

View File

@ -1,21 +1,23 @@
#!/usr/bin/env bash
set -eufo pipefail
set -x
showUsage() {
cat <<USAGE
Usage: $0 [options] ssh-host
Usage: nixos-remote [options] ssh-host
Options:
* -f, --flake flake
set the flake to install the system from
* --arg name value
pass value to nix-build. can be used to set disk-names for example
* --argstr name value
pass value to nix-build as string
* -s, --store-paths
set the store paths to the disko-script and nixos-system directly
if this is give, flake is not needed
* --no-ssh-copy
skip copying ssh-keys to target system
* --kexec url
use another kexec tarball to bootstrap NixOS
* --debug
enable debug output
USAGE
}
@ -24,8 +26,8 @@ abort() {
exit 1
}
nix_args=()
kexec_url=https://github.com/nix-community/nixos-images/releases/download/nixos-22.05/nixos-kexec-installer-x86_64-linux.tar.gz
kexec_url=https://github.com/nix-community/nixos-images/releases/download/nixos-22.11/nixos-kexec-installer-x86_64-linux.tar.gz
debug=n
while [[ $# -gt 0 ]]; do
case "$1" in
@ -33,8 +35,9 @@ while [[ $# -gt 0 ]]; do
flake=$2
shift
;;
--argstr | --arg)
nix_args+=("$1" "$2" "$3")
-s | --store-paths)
disko_script=$2
nixos_system=$3
shift
shift
;;
@ -49,9 +52,17 @@ while [[ $# -gt 0 ]]; do
--no-ssh-copy-id)
no_ssh_copy=y
;;
--debug)
debug=y
set -x
;;
*)
if [ -z ${ssh_connection+x} ]; then
ssh_connection=$1
if [[ "$1" =~ ^ssh:// ]]; then
ssh_connection=$1
else
ssh_connection="ssh://$1"
fi
else
showUsage
exit 1
@ -61,6 +72,7 @@ while [[ $# -gt 0 ]]; do
shift
done
# ssh wrapper
timeout_ssh_() {
timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$ssh_connection" "$@"
@ -80,20 +92,29 @@ nix_build() {
"$@"
}
nixos_system=""
if [ -z ${ssh_connection+x} ]; then
abort "ssh-host must be set"
fi
# parse flake nixos-install style syntax, get the system attr
if [[ ! -z "${flake+x}" ]]; then
if [[ -n "${flake+x}" ]]; then
if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then
flake="${BASH_REMATCH[1]}"
flakeAttr="${BASH_REMATCH[2]}"
fi
if [[ -z "$flakeAttr" ]]; then
if [[ -z "${flakeAttr+x}" ]]; then
echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri."
echo "For example, to use the output nixosConfigurations.foo from the flake.nix, append \"#foo\" to the flake-uri."
exit 1
fi
disko_script=$(nix_build "${flake}#nixosConfigurations.${flakeAttr}.config.system.build.disko")
nixos_system=$(nix_build "${flake}#nixosConfigurations.${flakeAttr}.config.system.build.toplevel")
elif [[ -n "${disko_script+x}" ]] && [[ -n "${nixos_system+x}" ]]; then
if [[ ! -e "${disko_script}" ]] || [[ ! -e "${nixos_system}" ]]; then
echo "${disko_script} and ${nixos_system} must be existing store-paths"
exit 1
fi
:
else
abort "flake must be set"
fi
@ -101,20 +122,23 @@ fi
# wait for machine to become reachable (possibly forever)
if [ ${no_ssh_copy-n} != "y" ]; then
until ssh-copy-id "$ssh_connection"; do sleep 5; done
until ssh-copy-id -o ConnectTimeout=10 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$ssh_connection"; do sleep 5; done
else
until ssh_ -o ConnectTimeout=10 -- exit 0; do sleep 5; done
fi
# first check if the remote system is kexec booted
if $(ssh_ -- test -e /etc/is_kexec); then
if ssh_ -- test -e /etc/is_kexec; then
is_kexec=y
fi
if [ ${is_kexec-n} != "y" ]; then
# TODO we probably need an architecture detection here
ssh_ << SSH
set -efux
set -efu
if [ "$debug" = "y" ]; then
set -x
fi
os=\$(uname)
if [[ "\$os" != "Linux" ]]; then
echo "This script requires Linux as the operating system, but got \${os}" >&2
@ -129,17 +153,26 @@ rm -rf /root/kexec
mkdir -p /root/kexec
SSH
if [[ -e "$kexec_url" ]]; then
cat "$kexec_url" | ssh_ 'tar -C /root/kexec -xvzf-'
if [[ -f "$kexec_url" ]]; then
ssh_ 'tar -C /root/kexec -xvzf-' < "$kexec_url"
else
ssh_ << SSH
set -eu -o pipefail
if [ "$debug" = "y" ]; then
set -x
fi
fetch(){
if command -v curl >/dev/null 2>&1; then
curl --fail -Ss -L "\$1"
elif command -v wget >/dev/null 2>&1; then
wget "\$1" -O-
elif command -v apt-get >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl
curl --fail -Ss -L "\$1"
else
echo "no downloader (curl or wget) found, bailing out"
echo "no downloader (curl or wget) found, bailing out" >&2
exit 1
fi
}
@ -159,10 +192,10 @@ SSH
until ssh_ -o ConnectTimeout=10 -- exit 0; do sleep 5; done
fi
nixCopy --to "ssh://$ssh_connection" "$disko_script"
ssh_ $disko_script
nixCopy --to "$ssh_connection" "$disko_script"
ssh_ "$disko_script"
nixCopy --to "ssh://$ssh_connection?remote-store=local?root=/mnt" "$nixos_system"
nixCopy --to "$ssh_connection?remote-store=local?root=/mnt" "$nixos_system"
ssh_ << SSH
set -efu
nixos-install --no-root-passwd --no-channel-copy --system "$nixos_system"

35
package.nix Normal file
View File

@ -0,0 +1,35 @@
{ stdenvNoCC
, makeWrapper
, lib
, openssh
, gitMinimal
, nix
, coreutils
, shellcheck
}:
let
runtimeDeps = [
openssh
gitMinimal # for git flakes
nix
coreutils
];
in
stdenvNoCC.mkDerivation {
name = "nixos-remote";
src = ./.;
nativeBuildInputs = [
makeWrapper
shellcheck
];
installPhase = ''
install -D -m755 nixos-remote $out/bin/nixos-remote
wrapProgram "$out/bin/nixos-remote" \
--prefix PATH : "${lib.makeBinPath runtimeDeps}"
'';
doCheck = true;
checkPhase = ''
shellcheck ./nixos-remote
'';
}

129
tests/from-nixos.nix Normal file
View File

@ -0,0 +1,129 @@
{ pkgs ? import <nixpkgs> {}
, makeTest ? import <nixpkgs/nixos/tests/make-test-python.nix>
, eval-config ? import <nixpkgs/nixos/lib/eval-config.nix>
, disko ? "${builtins.fetchTarball "https://github.com/nix-community/disko/archive/master.tar.gz"}/module.nix"
, ... }:
let
kexec_tarball = builtins.fetchurl {
url = "https://github.com/nix-community/nixos-images/releases/download/nixos-22.05/nixos-kexec-installer-x86_64-linux.tar.gz";
sha256 = "sha256:1ff7rc0n8w1vab7k9sir3029sizfaq8yx7y8r1id94gcqrr2n1sd";
};
systemToInstall = { modulesPath, ... }: {
imports = [
disko
(modulesPath + "/testing/test-instrumentation.nix")
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/profiles/minimal.nix")
];
networking.hostName = "nixos-remote";
documentation.enable = false;
hardware.enableAllFirmware = false;
networking.hostId = "8425e349"; # from profiles/base.nix, needed for zfs
boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms
boot.loader.grub.devices = [ "/dev/vda" ];
disko.devices = {
disk = {
vda = {
device = "/dev/vda";
type = "disk";
content = {
type = "table";
format = "gpt";
partitions = [
{
name = "boot";
type = "partition";
start = "0";
end = "1M";
part-type = "primary";
flags = ["bios_grub"];
}
{
type = "partition";
name = "ESP";
start = "1MiB";
end = "100MiB";
bootable = true;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
}
{
name = "root";
type = "partition";
start = "100MiB";
end = "100%";
part-type = "primary";
bootable = true;
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
}
];
};
};
};
};
};
evaledSystem = eval-config {
modules = [ systemToInstall ];
system = "x86_64-linux";
};
in
makeTest {
name = "nixos-remote";
nodes = {
installer = {
documentation.enable = false;
environment.etc.sshKey = {
source = ./ssh-keys/ssh;
mode = "0600";
};
programs.ssh.startAgent = true;
system.extraDependencies = [
evaledSystem.config.system.build.disko
evaledSystem.config.system.build.toplevel
];
};
installed = {
virtualisation.memorySize = 4096;
documentation.enable = false;
services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keyFiles = [ ./ssh-keys/ssh.pub ];
};
};
testScript = ''
def create_test_machine(oldmachine=None, args={}): # taken from <nixpkgs/nixos/tests/installer.nix>
machine = create_machine({
"qemuFlags":
'-cpu max -m 1024 -virtfs local,path=/nix/store,security_model=none,mount_tag=nix-store,'
f' -drive file={oldmachine.state_dir}/installed.qcow2,id=drive1,if=none,index=1,werror=report'
f' -device virtio-blk-pci,drive=drive1',
} | args)
driver.machines.append(machine)
return machine
start_all()
installed.wait_for_unit("sshd.service")
installer.succeed("""
eval $(ssh-agent)
ssh-add /etc/sshKey
${../nixos-remote} \
--no-ssh-copy-id \
--kexec ${kexec_tarball} \
--store-paths ${toString evaledSystem.config.system.build.disko} ${toString evaledSystem.config.system.build.toplevel} \
root@installed >&2
""")
installed.shutdown()
new_machine = create_test_machine(oldmachine=installed, args={ "name": "after_install" })
new_machine.start()
assert "nixos-remote" == new_machine.succeed("hostname").strip()
'';
} {
pkgs = pkgs;
system = pkgs.system;
}

7
tests/ssh-keys/ssh Normal file
View File

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQAAAJDpULAq6VCw
KgAAAAtzc2gtZWQyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQ
AAAECpjfl5WMMIDvEyZJTeXzRNFzpDpj4fqdIXHZauKAlE5MziQ+DhXsMxhx64DxUhR0G/
DfSAz2pqAREDy/VUYEEFAAAACWxhc3NAbW9ycwECAwQ=
-----END OPENSSH PRIVATE KEY-----

1
tests/ssh-keys/ssh.pub Normal file
View File

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMziQ+DhXsMxhx64DxUhR0G/DfSAz2pqAREDy/VUYEEF