Add support for hermetic remote builds
A hermetic remote rebuild is when Nixinate sends a specific Nix binary to the remote first, then uses it when activating a system closure, rather than using the Nix that is already on the remote. This is defaulted to false for the time being, as the bandwidth usage can be high. This allows users to avoid the need to bootstrap the remote first by enabling flakes on the remote Nix binary.
This commit is contained in:
parent
2a660be779
commit
4a356409ca
3 changed files with 62 additions and 11 deletions
28
README.md
28
README.md
|
@ -32,6 +32,7 @@ Below is a minimal example:
|
||||||
host = "itchy.scratchy.com";
|
host = "itchy.scratchy.com";
|
||||||
sshUser = "matthew";
|
sshUser = "matthew";
|
||||||
buildOn = "remote"; # valid args are "local" or "remote"
|
buildOn = "remote"; # valid args are "local" or "remote"
|
||||||
|
hermetic = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
# ... other configuration ...
|
# ... other configuration ...
|
||||||
|
@ -76,3 +77,30 @@ reloading user units for matthew...
|
||||||
setting up tmpfiles
|
setting up tmpfiles
|
||||||
Connection to itchy.scratchy.com closed.
|
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.
|
||||||
|
|
29
flake.nix
29
flake.nix
|
@ -16,32 +16,49 @@
|
||||||
{
|
{
|
||||||
herculesCI.ciSystems = [ "x86_64-linux" ];
|
herculesCI.ciSystems = [ "x86_64-linux" ];
|
||||||
overlay = final: prev: {
|
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:
|
generateApps = flake:
|
||||||
let
|
let
|
||||||
machines = builtins.attrNames flake.nixosConfigurations;
|
machines = builtins.attrNames flake.nixosConfigurations;
|
||||||
validMachines = final.lib.remove "" (final.lib.forEach machines (x: final.lib.optionalString (flake.nixosConfigurations."${x}"._module.args ? nixinate) "${x}" ));
|
validMachines = final.lib.remove "" (final.lib.forEach machines (x: final.lib.optionalString (flake.nixosConfigurations."${x}"._module.args ? nixinate) "${x}" ));
|
||||||
mkDeployScript = { machine, dryRun }: let
|
mkDeployScript = { machine, dryRun }: let
|
||||||
inherit (builtins) abort;
|
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;
|
n = flake.nixosConfigurations.${machine}._module.args.nixinate;
|
||||||
|
hermetic = n.hermetic or false;
|
||||||
user = n.sshUser or "root";
|
user = n.sshUser or "root";
|
||||||
host = n.host;
|
host = n.host;
|
||||||
where = n.buildOn or "remote";
|
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'";
|
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";
|
switch = if dryRun then "dry-activate" else "switch";
|
||||||
script = ''
|
script =
|
||||||
|
''
|
||||||
set -e
|
set -e
|
||||||
echo "🚀 Deploying nixosConfigurations.${machine} from ${flake}"
|
echo "🚀 Deploying nixosConfigurations.${machine} from ${flake}"
|
||||||
echo "👤 SSH User: ${user}"
|
echo "👤 SSH User: ${user}"
|
||||||
echo "🌐 SSH Host: ${host}"
|
echo "🌐 SSH Host: ${host}"
|
||||||
'' + (if remote then ''
|
'' + (if remote then ''
|
||||||
echo "🚀 Sending flake to ${machine} via nix copy:"
|
echo "🚀 Sending flake to ${machine} via nix copy:"
|
||||||
( set -x; ${final.nix}/bin/nix copy ${flake} --to ssh://${user}@${host} )
|
( set -x; ${nix} copy ${flake} --to ssh://${user}@${host} )
|
||||||
echo "🤞 Activating configuration on ${machine} via ssh:"
|
'' + (if hermetic then ''
|
||||||
( set -x; ${final.openssh}/bin/ssh -t ${user}@${host} 'sudo nixos-rebuild ${switch} --flake ${flake}#${machine}' )
|
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 ''
|
'' 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:"
|
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 final.writeScript "deploy-${machine}.sh" script;
|
||||||
in
|
in
|
||||||
|
@ -78,7 +95,7 @@
|
||||||
let
|
let
|
||||||
vmTests = import ./tests {
|
vmTests = import ./tests {
|
||||||
makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest;
|
makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest;
|
||||||
inherit pkgs inputs;
|
inherit inputs; pkgs = nixpkgsFor.${system};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.lib.optionalAttrs pkgs.stdenv.isLinux vmTests # vmTests can only be ran on Linux, so append them only if on Linux.
|
pkgs.lib.optionalAttrs pkgs.stdenv.isLinux vmTests # vmTests can only be ran on Linux, so append them only if on Linux.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{ pkgs, makeTest, inputs }:
|
{ pkgs, makeTest, inputs }:
|
||||||
let
|
let
|
||||||
|
inherit (pkgs) lib;
|
||||||
# Return a store path with a closure containing everything including
|
# Return a store path with a closure containing everything including
|
||||||
# derivations and all build dependency outputs, all the way down.
|
# derivations and all build dependency outputs, all the way down.
|
||||||
allDrvOutputs = pkg:
|
allDrvOutputs = pkg:
|
||||||
|
@ -22,7 +23,7 @@ let
|
||||||
((import (path + "/flake.nix")).outputs (inputs // {self = r;}));
|
((import (path + "/flake.nix")).outputs (inputs // {self = r;}));
|
||||||
in
|
in
|
||||||
r;
|
r;
|
||||||
mkNixinateTest = buildOn:
|
mkNixinateTest = { buildOn, hermetic ? false, ... }:
|
||||||
let
|
let
|
||||||
exampleFlake = pkgs.writeTextFile {
|
exampleFlake = pkgs.writeTextFile {
|
||||||
name = "nixinate-example-flake";
|
name = "nixinate-example-flake";
|
||||||
|
@ -41,6 +42,7 @@ let
|
||||||
host = "nixinatee";
|
host = "nixinatee";
|
||||||
sshUser = "nixinator";
|
sshUser = "nixinator";
|
||||||
buildOn = "${buildOn}"; # valid args are "local" or "remote"
|
buildOn = "${buildOn}"; # valid args are "local" or "remote"
|
||||||
|
hermetic = ${lib.boolToString hermetic}; # valid args are true or false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -66,7 +68,9 @@ let
|
||||||
];
|
];
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
writableStore = true;
|
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 = { ... }: {
|
nixinator = { ... }: {
|
||||||
|
@ -77,7 +81,7 @@ let
|
||||||
additionalPaths = [
|
additionalPaths = [
|
||||||
(allDrvOutputs exampleSystem)
|
(allDrvOutputs exampleSystem)
|
||||||
]
|
]
|
||||||
++ pkgs.lib.optional (buildOn == "remote") exampleFlake;
|
++ lib.optional (buildOn == "remote") exampleFlake;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -103,6 +107,8 @@ let
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
local = (mkNixinateTest "local");
|
local = (mkNixinateTest { buildOn = "local"; });
|
||||||
remote = (mkNixinateTest "remote");
|
remote = (mkNixinateTest { buildOn = "remote"; });
|
||||||
|
localHermetic = (mkNixinateTest { buildOn = "local"; hermetic = true; });
|
||||||
|
remoteHermetic = (mkNixinateTest { buildOn = "remote"; hermetic = true; });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue