diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2be92b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +result diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..afde9e2 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ad89adc --- /dev/null +++ b/flake.nix @@ -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"); + }; + }; + }; +} diff --git a/nixos-remote b/nixos-remote index 8ed589d..60f9696 100755 --- a/nixos-remote +++ b/nixos-remote @@ -1,21 +1,23 @@ #!/usr/bin/env bash set -eufo pipefail -set -x showUsage() { cat <&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" diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..b2b0c7b --- /dev/null +++ b/package.nix @@ -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 + ''; +} diff --git a/tests/from-nixos.nix b/tests/from-nixos.nix new file mode 100644 index 0000000..6032572 --- /dev/null +++ b/tests/from-nixos.nix @@ -0,0 +1,129 @@ +{ pkgs ? import {} +, makeTest ? import +, eval-config ? import +, 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 + 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; +} diff --git a/tests/ssh-keys/ssh b/tests/ssh-keys/ssh new file mode 100644 index 0000000..db6049c --- /dev/null +++ b/tests/ssh-keys/ssh @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQAAAJDpULAq6VCw +KgAAAAtzc2gtZWQyNTUxOQAAACDM4kPg4V7DMYceuA8VIUdBvw30gM9qagERA8v1VGBBBQ +AAAECpjfl5WMMIDvEyZJTeXzRNFzpDpj4fqdIXHZauKAlE5MziQ+DhXsMxhx64DxUhR0G/ +DfSAz2pqAREDy/VUYEEFAAAACWxhc3NAbW9ycwECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/ssh-keys/ssh.pub b/tests/ssh-keys/ssh.pub new file mode 100644 index 0000000..e77d393 --- /dev/null +++ b/tests/ssh-keys/ssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMziQ+DhXsMxhx64DxUhR0G/DfSAz2pqAREDy/VUYEEF