diff --git a/README.md b/README.md index 292d576..ea780e7 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Below is a minimal example: host = "itchy.scratchy.com"; sshUser = "matthew"; buildOn = "remote"; # valid args are "local" or "remote" + hermetic = false; }; } # ... other configuration ... @@ -76,3 +77,30 @@ reloading user units for matthew... setting up tmpfiles Connection to itchy.scratchy.com closed. ``` + +# Available arguments via `_module.args.nixinate` + +- `host` *`string`* + + A string representing the hostname or IP address of a machine to connect to + via ssh. + +- `sshUser` *`string`* + + A string representing the username a machine to connect to via ssh. + +- `buildOn` *`"remote"`* or *`"local"`* + + - `"remote"` + + Push the flake to the remote, build and activate entirely remotely, + returning logs via SSH. + + - `"local"` + + Build the system closure locally, copy to the remote and activate. + +- `hermetic` *`bool`* + + Whether to copy Nix to the remote for usage when building and activating, + instead of using the Nix which is already installed on the remote. diff --git a/flake.nix b/flake.nix index 7d3215d..f174abf 100644 --- a/flake.nix +++ b/flake.nix @@ -16,32 +16,49 @@ { herculesCI.ciSystems = [ "x86_64-linux" ]; overlay = final: prev: { + nixinate = { + nix = prev.pkgs.writeShellScriptBin "nix" + ''${final.nixVersions.unstable}/bin/nix --experimental-features "nix-command flakes" "$@"''; + nixos-rebuild = prev.nixos-rebuild.override { inherit (final) nix; }; + }; generateApps = flake: let machines = builtins.attrNames flake.nixosConfigurations; validMachines = final.lib.remove "" (final.lib.forEach machines (x: final.lib.optionalString (flake.nixosConfigurations."${x}"._module.args ? nixinate) "${x}" )); mkDeployScript = { machine, dryRun }: let inherit (builtins) abort; + inherit (final.lib) getExe; + nix = "${getExe final.nix}"; + nixos-rebuild = "${getExe final.nixos-rebuild}"; + openssh = "${getExe final.openssh}"; n = flake.nixosConfigurations.${machine}._module.args.nixinate; + hermetic = n.hermetic or false; user = n.sshUser or "root"; host = n.host; where = n.buildOn or "remote"; remote = if where == "remote" then true else if where == "local" then false else abort "_module.args.nixinate.buildOn is not set to a valid value of 'local' or 'remote'"; switch = if dryRun then "dry-activate" else "switch"; - script = '' + script = + '' set -e echo "🚀 Deploying nixosConfigurations.${machine} from ${flake}" echo "👤 SSH User: ${user}" echo "🌐 SSH Host: ${host}" '' + (if remote then '' echo "🚀 Sending flake to ${machine} via nix copy:" - ( set -x; ${final.nix}/bin/nix copy ${flake} --to ssh://${user}@${host} ) - echo "🤞 Activating configuration on ${machine} via ssh:" - ( set -x; ${final.openssh}/bin/ssh -t ${user}@${host} 'sudo nixos-rebuild ${switch} --flake ${flake}#${machine}' ) + ( set -x; ${nix} copy ${flake} --to ssh://${user}@${host} ) + '' + (if hermetic then '' + echo "🤞 Activating configuration hermetically on ${machine} via ssh:" + ( set -x; ${nix} build ${nixos-rebuild} --no-link --store ssh://${user}@${host} ) + ( set -x; ${openssh} -t ${user}@${host} 'sudo ${nixos-rebuild} ${switch} --flake ${flake}#${machine}' ) '' else '' + echo "🤞 Activating configuration non-hermetically on ${machine} via ssh:" + ( set -x; ${openssh} -t ${user}@${host} 'sudo nixos-rebuild ${switch} --flake ${flake}#${machine}' ) + '') + else '' echo "🔨 Building system closure locally, copying it to remote store and activating it:" - ( set -x; NIX_SSHOPTS="-t" ${final.nixos-rebuild}/bin/nixos-rebuild ${switch} --flake ${flake}#${machine} --target-host ${user}@${host} --use-remote-sudo ) + ( set -x; NIX_SSHOPTS="-t" ${nixos-rebuild} ${switch} --flake ${flake}#${machine} --target-host ${user}@${host} --use-remote-sudo ) ''); in final.writeScript "deploy-${machine}.sh" script; in @@ -78,7 +95,7 @@ let vmTests = import ./tests { makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest; - inherit pkgs inputs; + inherit inputs; pkgs = nixpkgsFor.${system}; }; in pkgs.lib.optionalAttrs pkgs.stdenv.isLinux vmTests # vmTests can only be ran on Linux, so append them only if on Linux. diff --git a/tests/vmTest/default.nix b/tests/vmTest/default.nix index ef16371..097d4fa 100644 --- a/tests/vmTest/default.nix +++ b/tests/vmTest/default.nix @@ -1,5 +1,6 @@ { pkgs, makeTest, inputs }: let + inherit (pkgs) lib; # Return a store path with a closure containing everything including # derivations and all build dependency outputs, all the way down. allDrvOutputs = pkg: @@ -22,7 +23,7 @@ let ((import (path + "/flake.nix")).outputs (inputs // {self = r;})); in r; - mkNixinateTest = buildOn: + mkNixinateTest = { buildOn, hermetic ? false, ... }: let exampleFlake = pkgs.writeTextFile { name = "nixinate-example-flake"; @@ -41,6 +42,7 @@ let host = "nixinatee"; sshUser = "nixinator"; buildOn = "${buildOn}"; # valid args are "local" or "remote" + hermetic = ${lib.boolToString hermetic}; # valid args are true or false }; } ]; @@ -66,7 +68,9 @@ let ]; virtualisation = { writableStore = true; - additionalPaths = [] ++ pkgs.lib.optional (buildOn == "remote") (allDrvOutputs exampleSystem); + additionalPaths = [] + ++ lib.optional (buildOn == "remote") (allDrvOutputs exampleSystem) + ++ lib.optional (hermetic == true) (pkgs.nixinate.nixos-rebuild); }; }; nixinator = { ... }: { @@ -77,7 +81,7 @@ let additionalPaths = [ (allDrvOutputs exampleSystem) ] - ++ pkgs.lib.optional (buildOn == "remote") exampleFlake; + ++ lib.optional (buildOn == "remote") exampleFlake; }; }; }; @@ -103,6 +107,8 @@ let }; in { - local = (mkNixinateTest "local"); - remote = (mkNixinateTest "remote"); + local = (mkNixinateTest { buildOn = "local"; }); + remote = (mkNixinateTest { buildOn = "remote"; }); + localHermetic = (mkNixinateTest { buildOn = "local"; hermetic = true; }); + remoteHermetic = (mkNixinateTest { buildOn = "remote"; hermetic = true; }); }