Uninstall shouldn't fail fast (#382)
* Uninstall shouldn't fail fast * wip * wip * No longer fails fast * Tidy up error handling * Touchup post merge * Refactor nix tests * Some minor fixes * Uninstall fail tests * Fiddle with messaging * nixfmt * Tweak display a bit * fix docs * Fix Mac * Revert setting I was testing * Reflect feedback about a log level
This commit is contained in:
parent
5ec1d0e9b9
commit
8bb37f1bcf
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1033,7 +1033,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "nix-installer"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1-unreleased"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"atty",
|
||||
|
|
|
@ -194,6 +194,7 @@
|
|||
vm-test = import ./nix/tests/vm-test {
|
||||
inherit forSystem;
|
||||
inherit (nix.hydraJobs) binaryTarball;
|
||||
inherit (nixpkgs) lib;
|
||||
};
|
||||
container-test = import ./nix/tests/container-test {
|
||||
inherit forSystem;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Largely derived from https://github.com/NixOS/nix/blob/14f7dae3e4eb0c34192d0077383a7f2a2d630129/tests/installer/default.nix
|
||||
{ forSystem, binaryTarball }:
|
||||
{ forSystem, binaryTarball, lib }:
|
||||
|
||||
let
|
||||
nix-installer-install = ''
|
||||
|
@ -18,7 +18,7 @@ let
|
|||
tar xvf nix.tar.xz
|
||||
./nix-*/install --no-channel-add --yes --no-daemon
|
||||
'';
|
||||
installScripts = rec {
|
||||
installCases = rec {
|
||||
install-default = {
|
||||
install = nix-installer-install;
|
||||
check = ''
|
||||
|
@ -38,19 +38,25 @@ let
|
|||
fi
|
||||
if systemctl is-failed nix-daemon.socket; then
|
||||
echo "nix-daemon.socket is failed"
|
||||
systemctl status nix-daemon.socket
|
||||
exit 1
|
||||
fi
|
||||
if systemctl is-failed nix-daemon.service; then
|
||||
echo "nix-daemon.service is failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if !(sudo systemctl start nix-daemon.service); then
|
||||
echo "nix-daemon.service failed to start"
|
||||
systemctl status nix-daemon.service
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if systemctl is-failed nix-daemon.service; then
|
||||
echo "nix-daemon.service is failed"
|
||||
systemctl status nix-daemon.service
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if !(sudo systemctl stop nix-daemon.service); then
|
||||
echo "nix-daemon.service failed to stop"
|
||||
systemctl status nix-daemon.service
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -65,6 +71,62 @@ let
|
|||
out=$(nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > $out"]; }')
|
||||
[[ $(cat $out) = foobar ]]
|
||||
'';
|
||||
uninstall = ''
|
||||
/nix/nix-installer uninstall --no-confirm
|
||||
'';
|
||||
uninstallCheck = ''
|
||||
if which nix; then
|
||||
echo "nix existed on path after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for i in $(seq 1 32); do
|
||||
if id -u nixbld$i; then
|
||||
echo "User nixbld$i exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if grep "^nixbld:" /etc/group; then
|
||||
echo "Group nixbld exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if sudo -i nix store ping --store daemon; then
|
||||
echo "Could run nix store ping after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d /nix ]; then
|
||||
echo "/nix exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d /etc/nix/nix.conf ]; then
|
||||
echo "/etc/nix/nix.conf exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f /etc/systemd/system/nix-daemon.socket ]; then
|
||||
echo "/etc/systemd/system/nix-daemon.socket exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f /etc/systemd/system/nix-daemon.service ]; then
|
||||
echo "/etc/systemd/system/nix-daemon.socket exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if systemctl status nix-daemon.socket > /dev/null; then
|
||||
echo "systemd unit nix-daemon.socket still exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if systemctl status nix-daemon.service > /dev/null; then
|
||||
echo "systemd unit nix-daemon.service still exists after uninstall"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
install-no-start-daemon = {
|
||||
install = ''
|
||||
|
@ -90,6 +152,8 @@ let
|
|||
|
||||
[[ $(cat $out) = foobar ]]
|
||||
'';
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
install-daemonless = {
|
||||
install = ''
|
||||
|
@ -106,14 +170,20 @@ let
|
|||
|
||||
[[ $(cat $out) = foobar ]]
|
||||
'';
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
};
|
||||
cureCases = {
|
||||
cure-self-linux-working = {
|
||||
preinstall = ''
|
||||
${nix-installer-install-quiet}
|
||||
sudo mv /nix/receipt.json /nix/old-receipt.json
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-self-linux-broken-no-nix-path = {
|
||||
preinstall = ''
|
||||
|
@ -122,8 +192,10 @@ let
|
|||
sudo mv /nix/receipt.json /nix/old-receipt.json
|
||||
sudo rm -rf /nix/
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-self-linux-broken-missing-users = {
|
||||
preinstall = ''
|
||||
|
@ -133,8 +205,10 @@ let
|
|||
sudo userdel nixbld3
|
||||
sudo userdel nixbld16
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-self-linux-broken-missing-users-and-group = {
|
||||
preinstall = ''
|
||||
|
@ -146,8 +220,10 @@ let
|
|||
done
|
||||
sudo groupdel nixbld
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-self-linux-broken-daemon-disabled = {
|
||||
preinstall = ''
|
||||
|
@ -155,8 +231,10 @@ let
|
|||
sudo mv /nix/receipt.json /nix/old-receipt.json
|
||||
sudo systemctl disable --now nix-daemon.socket
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-self-linux-broken-no-etc-nix = {
|
||||
preinstall = ''
|
||||
|
@ -164,8 +242,10 @@ let
|
|||
sudo mv /nix/receipt.json /nix/old-receipt.json
|
||||
sudo rm -rf /etc/nix
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-self-linux-broken-unmodified-bashrc = {
|
||||
preinstall = ''
|
||||
|
@ -173,16 +253,20 @@ let
|
|||
sudo mv /nix/receipt.json /nix/old-receipt.json
|
||||
sudo sed -i '/# Nix/,/# End Nix/d' /etc/bash.bashrc
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-script-multi-self-broken-no-nix-path = {
|
||||
preinstall = ''
|
||||
${cure-script-multi-user}
|
||||
sudo rm -rf /nix/
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-script-multi-broken-missing-users = {
|
||||
preinstall = ''
|
||||
|
@ -191,44 +275,98 @@ let
|
|||
sudo userdel nixbld3
|
||||
sudo userdel nixbld16
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-script-multi-broken-daemon-disabled = {
|
||||
preinstall = ''
|
||||
${cure-script-multi-user}
|
||||
sudo systemctl disable --now nix-daemon.socket
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-script-multi-broken-no-etc-nix = {
|
||||
preinstall = ''
|
||||
${cure-script-multi-user}
|
||||
sudo rm -rf /etc/nix
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-script-multi-broken-unmodified-bashrc = {
|
||||
preinstall = ''
|
||||
${cure-script-multi-user}
|
||||
sudo sed -i '/# Nix/,/# End Nix/d' /etc/bash.bashrc
|
||||
'';
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
cure-script-multi-working = {
|
||||
preinstall = cure-script-multi-user;
|
||||
install = install-default.install;
|
||||
check = install-default.check;
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
uninstall = installCases.install-default.uninstall;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
# cure-script-single-working = {
|
||||
# preinstall = cure-script-single-user;
|
||||
# install = install-default.install;
|
||||
# check = install-default.check;
|
||||
# install = installCases.install-default.install;
|
||||
# check = installCases.install-default.check;
|
||||
# };
|
||||
};
|
||||
# Cases to test uninstalling is complete even in the face of errors.
|
||||
uninstallCases =
|
||||
let
|
||||
uninstallFailExpected = ''
|
||||
if /nix/nix-installer uninstall --no-confirm; then
|
||||
echo "/nix/nix-installer uninstall exited with 0 during a uninstall failure test"
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
uninstall-users-and-groups-missing = {
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
preuninstall = ''
|
||||
for i in $(seq 1 32); do
|
||||
sudo userdel nixbld$i
|
||||
done
|
||||
sudo groupdel nixbld
|
||||
'';
|
||||
uninstall = uninstallFailExpected;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
uninstall-nix-conf-gone = {
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
preuninstall = ''
|
||||
sudo rm -rf /etc/nix
|
||||
'';
|
||||
uninstall = uninstallFailExpected;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
uninstall-shell-profile-clobbered = {
|
||||
install = installCases.install-default.install;
|
||||
check = installCases.install-default.check;
|
||||
preuninstall = ''
|
||||
sudo rm -rf /etc/bashrc
|
||||
'';
|
||||
uninstall = uninstallFailExpected;
|
||||
uninstallCheck = installCases.install-default.uninstallCheck;
|
||||
};
|
||||
};
|
||||
|
||||
disableSELinux = "sudo setenforce 0";
|
||||
|
||||
|
@ -333,7 +471,7 @@ let
|
|||
|
||||
};
|
||||
|
||||
makeTest = imageName: testName:
|
||||
makeTest = imageName: testName: test:
|
||||
let image = images.${imageName}; in
|
||||
with (forSystem image.system ({ system, pkgs, ... }: pkgs));
|
||||
runCommand
|
||||
|
@ -342,9 +480,12 @@ let
|
|||
buildInputs = [ qemu_kvm openssh ];
|
||||
image = image.image;
|
||||
postBoot = image.postBoot or "";
|
||||
preinstallScript = installScripts.${testName}.preinstall or "echo \"Not Applicable\"";
|
||||
installScript = installScripts.${testName}.install;
|
||||
checkScript = installScripts.${testName}.check;
|
||||
preinstallScript = test.preinstall or "echo \"Not Applicable\"";
|
||||
installScript = test.install;
|
||||
checkScript = test.check;
|
||||
uninstallScript = test.uninstall;
|
||||
preuninstallScript = test.preuninstall or "echo \"Not Applicable\"";
|
||||
uninstallCheckScript = test.uninstallCheck;
|
||||
installer = nix-installer-static;
|
||||
binaryTarball = binaryTarball.${system};
|
||||
}
|
||||
|
@ -414,32 +555,38 @@ let
|
|||
echo "Running installer..."
|
||||
$ssh "set -eux; $installScript"
|
||||
|
||||
echo "Testing Nix installation..."
|
||||
echo "Checking Nix installation..."
|
||||
$ssh "set -eux; $checkScript"
|
||||
|
||||
echo "Testing Nix uninstallation..."
|
||||
$ssh "set -eux; /nix/nix-installer uninstall --no-confirm"
|
||||
echo "Running preuninstall..."
|
||||
$ssh "set -eux; $preuninstallScript"
|
||||
|
||||
echo "Running Nix uninstallation..."
|
||||
$ssh "set -eux; $uninstallScript"
|
||||
|
||||
echo "Checking Nix uninstallation..."
|
||||
$ssh "set -eux; $uninstallCheckScript"
|
||||
|
||||
echo "Done!"
|
||||
touch $out
|
||||
'';
|
||||
|
||||
vm-tests = builtins.mapAttrs
|
||||
makeTests = name: tests: builtins.mapAttrs
|
||||
(imageName: image:
|
||||
rec {
|
||||
${image.system} = (builtins.mapAttrs
|
||||
(testName: test:
|
||||
makeTest imageName testName
|
||||
makeTest imageName testName test
|
||||
)
|
||||
installScripts) // {
|
||||
all = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
tests) // {
|
||||
"${name}" = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = name;
|
||||
constituents = (
|
||||
pkgs.lib.mapAttrsToList
|
||||
(testName: test:
|
||||
makeTest imageName testName
|
||||
makeTest imageName testName test
|
||||
)
|
||||
installScripts
|
||||
tests
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -447,96 +594,35 @@ let
|
|||
)
|
||||
images;
|
||||
|
||||
allCases = lib.recursiveUpdate (lib.recursiveUpdate installCases cureCases) uninstallCases;
|
||||
|
||||
install-tests = makeTests "install" installCases;
|
||||
|
||||
cure-tests = makeTests "cure" cureCases;
|
||||
|
||||
uninstall-tests = makeTests "uninstall" uninstallCases;
|
||||
|
||||
all-tests = builtins.mapAttrs
|
||||
(imageName: image: {
|
||||
"x86_64-linux".all = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = [
|
||||
install-tests."${imageName}"."x86_64-linux".install
|
||||
cure-tests."${imageName}"."x86_64-linux".cure
|
||||
uninstall-tests."${imageName}"."x86_64-linux".uninstall
|
||||
];
|
||||
});
|
||||
})
|
||||
images;
|
||||
|
||||
joined-tests = lib.recursiveUpdate (lib.recursiveUpdate (lib.recursiveUpdate cure-tests install-tests) uninstall-tests) all-tests;
|
||||
|
||||
in
|
||||
vm-tests // rec {
|
||||
all."x86_64-linux".install-default = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-default) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".install-no-start-daemon = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-default) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".install-daemonless = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-daemonless) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-working = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-working) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-broken-no-nix-path = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-broken-no-nix-path) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-broken-missing-users = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-broken-missing-users) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-broken-missing-users-and-group = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-broken-missing-users-and-group) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-broken-daemon-disabled = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-broken-daemon-disabled) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-broken-no-etc-nix = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-broken-no-etc-nix) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-self-linux-broken-unmodified-bashrc = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-self-linux-broken-unmodified-bashrc) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-script-multi-working = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-multi-working) vm-tests;
|
||||
});
|
||||
# all."x86_64-linux".cure-script-multi-broken-no-nix-path = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
# name = "all";
|
||||
# constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-multi-broken-no-nix-path) vm-tests;
|
||||
# });
|
||||
all."x86_64-linux".cure-script-multi-broken-missing-users = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-multi-broken-missing-users) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-script-multi-broken-daemon-disabled = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-multi-broken-daemon-disabled) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-script-multi-broken-no-etc-nix = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-multi-broken-no-etc-nix) vm-tests;
|
||||
});
|
||||
all."x86_64-linux".cure-script-multi-broken-unmodified-bashrc = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-multi-broken-unmodified-bashrc) vm-tests;
|
||||
});
|
||||
# all."x86_64-linux".cure-script-single-working = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
# name = "all";
|
||||
# constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".cure-script-single-working) vm-tests;
|
||||
# });
|
||||
all."x86_64-linux".all = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||
name = "all";
|
||||
constituents = [
|
||||
all."x86_64-linux".install-default
|
||||
all."x86_64-linux".install-no-start-daemon
|
||||
all."x86_64-linux".install-daemonless
|
||||
all."x86_64-linux".cure-self-linux-working
|
||||
all."x86_64-linux".cure-self-linux-broken-no-nix-path
|
||||
all."x86_64-linux".cure-self-linux-broken-missing-users
|
||||
all."x86_64-linux".cure-self-linux-broken-missing-users-and-group
|
||||
all."x86_64-linux".cure-self-linux-broken-daemon-disabled
|
||||
all."x86_64-linux".cure-self-linux-broken-no-etc-nix
|
||||
all."x86_64-linux".cure-self-linux-broken-unmodified-bashrc
|
||||
all."x86_64-linux".cure-script-multi-working
|
||||
# all."x86_64-linux".cure-script-multi-broken-no-nix-path
|
||||
all."x86_64-linux".cure-script-multi-broken-missing-users
|
||||
all."x86_64-linux".cure-script-multi-broken-daemon-disabled
|
||||
all."x86_64-linux".cure-script-multi-broken-no-etc-nix
|
||||
all."x86_64-linux".cure-script-multi-broken-unmodified-bashrc
|
||||
# all."x86_64-linux".cure-script-single-working
|
||||
];
|
||||
});
|
||||
lib.recursiveUpdate joined-tests {
|
||||
all."x86_64-linux" = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.lib.mapAttrs (caseName: case:
|
||||
pkgs.releaseTools.aggregate {
|
||||
name = caseName;
|
||||
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux"."${caseName}") joined-tests;
|
||||
}
|
||||
)) (allCases // { "cure" = { }; "install" = { }; "uninstall" = { }; "all" = { }; });
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use target_lexicon::OperatingSystem;
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::ActionError;
|
||||
use crate::action::{ActionError, ActionErrorKind};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||
|
@ -37,22 +37,23 @@ impl AddUserToGroup {
|
|||
};
|
||||
// Ensure user does not exists
|
||||
if let Some(user) = User::from_name(name.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(name.clone(), e))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(name.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
{
|
||||
if user.uid.as_raw() != uid {
|
||||
return Err(ActionError::UserUidMismatch(
|
||||
return Err(Self::error(ActionErrorKind::UserUidMismatch(
|
||||
name.clone(),
|
||||
user.uid.as_raw(),
|
||||
uid,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
if user.gid.as_raw() != gid {
|
||||
return Err(ActionError::UserGidMismatch(
|
||||
return Err(Self::error(ActionErrorKind::UserGidMismatch(
|
||||
name.clone(),
|
||||
user.gid.as_raw(),
|
||||
gid,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
// See if group membership needs to be done
|
||||
|
@ -74,7 +75,8 @@ impl AddUserToGroup {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))
|
||||
.map_err(Self::error)?;
|
||||
match output.status.code() {
|
||||
Some(0) => {
|
||||
// yes {user} is a member of {groupname}
|
||||
|
@ -98,7 +100,9 @@ impl AddUserToGroup {
|
|||
},
|
||||
_ => {
|
||||
// Some other issue
|
||||
return Err(ActionError::command_output(&command, output));
|
||||
return Err(Self::error(ActionErrorKind::command_output(
|
||||
&command, output,
|
||||
)));
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -109,8 +113,9 @@ impl AddUserToGroup {
|
|||
.arg(&this.name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
let output_str = String::from_utf8(output.stdout)?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
let output_str = String::from_utf8(output.stdout).map_err(Self::error)?;
|
||||
let user_in_group = output_str.split(" ").any(|v| v == &this.groupname);
|
||||
|
||||
if user_in_group {
|
||||
|
@ -187,7 +192,8 @@ impl Action for AddUserToGroup {
|
|||
.arg(&name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
execute_command(
|
||||
Command::new("/usr/sbin/dseditgroup")
|
||||
.process_group(0)
|
||||
|
@ -199,7 +205,8 @@ impl Action for AddUserToGroup {
|
|||
.arg(groupname)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
_ => {
|
||||
if which::which("gpasswd").is_ok() {
|
||||
|
@ -210,7 +217,8 @@ impl Action for AddUserToGroup {
|
|||
.args([name, groupname])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if which::which("addgroup").is_ok() {
|
||||
execute_command(
|
||||
Command::new("addgroup")
|
||||
|
@ -218,9 +226,12 @@ impl Action for AddUserToGroup {
|
|||
.args([name, groupname])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
return Err(ActionError::MissingAddUserToGroupCommand);
|
||||
return Err(Self::error(Self::error(
|
||||
ActionErrorKind::MissingAddUserToGroupCommand,
|
||||
)));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -264,7 +275,8 @@ impl Action for AddUserToGroup {
|
|||
.arg(&name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
_ => {
|
||||
if which::which("gpasswd").is_ok() {
|
||||
|
@ -275,7 +287,8 @@ impl Action for AddUserToGroup {
|
|||
.args([&name.to_string(), &groupname.to_string()])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if which::which("delgroup").is_ok() {
|
||||
execute_command(
|
||||
Command::new("delgroup")
|
||||
|
@ -283,9 +296,12 @@ impl Action for AddUserToGroup {
|
|||
.args([name, groupname])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
return Err(ActionError::MissingRemoveUserFromGroupCommand);
|
||||
return Err(Self::error(
|
||||
ActionErrorKind::MissingRemoveUserFromGroupCommand,
|
||||
));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@ use nix::unistd::{chown, Group, User};
|
|||
use tokio::fs::{create_dir, remove_dir_all};
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionState};
|
||||
use crate::action::{Action, ActionDescription, ActionErrorKind, ActionState};
|
||||
use crate::action::{ActionError, StatefulAction};
|
||||
|
||||
/** Create a directory at the given location, optionally with an owning user, group, and mode.
|
||||
|
@ -40,40 +40,47 @@ impl CreateDirectory {
|
|||
let action_state = if path.exists() {
|
||||
let metadata = tokio::fs::metadata(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::GettingMetadata(path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::GettingMetadata(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
if !metadata.is_dir() {
|
||||
return Err(ActionError::PathWasNotDirectory(path.to_owned()));
|
||||
return Err(Self::error(ActionErrorKind::PathWasNotDirectory(
|
||||
path.to_owned(),
|
||||
)));
|
||||
}
|
||||
|
||||
// Does it have the right user/group?
|
||||
if let Some(user) = &user {
|
||||
// If the file exists, the user must also exist to be correct.
|
||||
let expected_uid = User::from_name(user.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(user.clone(), e))?
|
||||
.ok_or_else(|| ActionError::NoUser(user.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or_else(|| ActionErrorKind::NoUser(user.clone()))
|
||||
.map_err(Self::error)?
|
||||
.uid;
|
||||
let found_uid = metadata.uid();
|
||||
if found_uid != expected_uid.as_raw() {
|
||||
return Err(ActionError::PathUserMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathUserMismatch(
|
||||
path.clone(),
|
||||
found_uid,
|
||||
expected_uid.as_raw(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
if let Some(group) = &group {
|
||||
// If the file exists, the group must also exist to be correct.
|
||||
let expected_gid = Group::from_name(group.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
|
||||
.ok_or_else(|| ActionError::NoUser(group.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or_else(|| ActionErrorKind::NoUser(group.clone()))
|
||||
.map_err(Self::error)?
|
||||
.gid;
|
||||
let found_gid = metadata.gid();
|
||||
if found_gid != expected_gid.as_raw() {
|
||||
return Err(ActionError::PathGroupMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathGroupMismatch(
|
||||
path.clone(),
|
||||
found_gid,
|
||||
expected_gid.as_raw(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,8 +143,10 @@ impl Action for CreateDirectory {
|
|||
let gid = if let Some(group) = group {
|
||||
Some(
|
||||
Group::from_name(group.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
|
||||
.ok_or(ActionError::NoGroup(group.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or(ActionErrorKind::NoGroup(group.clone()))
|
||||
.map_err(Self::error)?
|
||||
.gid,
|
||||
)
|
||||
} else {
|
||||
|
@ -146,8 +155,10 @@ impl Action for CreateDirectory {
|
|||
let uid = if let Some(user) = user {
|
||||
Some(
|
||||
User::from_name(user.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(user.clone(), e))?
|
||||
.ok_or(ActionError::NoUser(user.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or(ActionErrorKind::NoUser(user.clone()))
|
||||
.map_err(Self::error)?
|
||||
.uid,
|
||||
)
|
||||
} else {
|
||||
|
@ -156,13 +167,17 @@ impl Action for CreateDirectory {
|
|||
|
||||
create_dir(path.clone())
|
||||
.await
|
||||
.map_err(|e| ActionError::CreateDirectory(path.clone(), e))?;
|
||||
chown(path, uid, gid).map_err(|e| ActionError::Chown(path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::CreateDirectory(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
chown(path, uid, gid)
|
||||
.map_err(|e| ActionErrorKind::Chown(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if let Some(mode) = mode {
|
||||
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
|
||||
.await
|
||||
.map_err(|e| ActionError::SetPermissions(*mode, path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::SetPermissions(*mode, path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -202,14 +217,16 @@ impl Action for CreateDirectory {
|
|||
|
||||
let is_empty = path
|
||||
.read_dir()
|
||||
.map_err(|e| ActionError::Read(path.clone(), e))?
|
||||
.map_err(|e| ActionErrorKind::Read(path.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.next()
|
||||
.is_none();
|
||||
|
||||
match (is_empty, force_prune_on_revert) {
|
||||
(true, _) | (false, true) => remove_dir_all(path.clone())
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(path.clone(), e))?,
|
||||
.map_err(|e| ActionErrorKind::Remove(path.clone(), e))
|
||||
.map_err(Self::error)?,
|
||||
(false, false) => {
|
||||
tracing::debug!("Not removing `{}`, the folder is not empty", path.display());
|
||||
},
|
||||
|
|
|
@ -10,7 +10,9 @@ use tokio::{
|
|||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
|
||||
/** Create a file at the given location with the provided `buf`,
|
||||
optionally with an owning user, group, and mode.
|
||||
|
@ -55,15 +57,17 @@ impl CreateFile {
|
|||
// If the path exists, perhaps we can just skip this
|
||||
let mut file = File::open(&this.path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(this.path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Open(this.path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.await
|
||||
.map_err(|e| ActionError::GettingMetadata(this.path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::GettingMetadata(this.path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if !metadata.is_file() {
|
||||
return Err(ActionError::PathWasNotFile(this.path));
|
||||
return Err(Self::error(ActionErrorKind::PathWasNotFile(this.path)));
|
||||
}
|
||||
|
||||
if let Some(mode) = mode {
|
||||
|
@ -73,11 +77,11 @@ impl CreateFile {
|
|||
let discovered_mode = discovered_mode & 0o777;
|
||||
|
||||
if discovered_mode != mode {
|
||||
return Err(ActionError::PathModeMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathModeMismatch(
|
||||
this.path.clone(),
|
||||
discovered_mode,
|
||||
mode,
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,31 +89,35 @@ impl CreateFile {
|
|||
if let Some(user) = &this.user {
|
||||
// If the file exists, the user must also exist to be correct.
|
||||
let expected_uid = User::from_name(user.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(user.clone(), e))?
|
||||
.ok_or_else(|| ActionError::NoUser(user.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or_else(|| ActionErrorKind::NoUser(user.clone()))
|
||||
.map_err(Self::error)?
|
||||
.uid;
|
||||
let found_uid = metadata.uid();
|
||||
if found_uid != expected_uid.as_raw() {
|
||||
return Err(ActionError::PathUserMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathUserMismatch(
|
||||
this.path.clone(),
|
||||
found_uid,
|
||||
expected_uid.as_raw(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
if let Some(group) = &this.group {
|
||||
// If the file exists, the group must also exist to be correct.
|
||||
let expected_gid = Group::from_name(group.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
|
||||
.ok_or_else(|| ActionError::NoUser(group.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or_else(|| ActionErrorKind::NoUser(group.clone()))
|
||||
.map_err(Self::error)?
|
||||
.gid;
|
||||
let found_gid = metadata.gid();
|
||||
if found_gid != expected_gid.as_raw() {
|
||||
return Err(ActionError::PathGroupMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathGroupMismatch(
|
||||
this.path.clone(),
|
||||
found_gid,
|
||||
expected_gid.as_raw(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,10 +125,13 @@ impl CreateFile {
|
|||
let mut discovered_buf = String::new();
|
||||
file.read_to_string(&mut discovered_buf)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(this.path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Read(this.path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if discovered_buf != this.buf {
|
||||
return Err(ActionError::DifferentContent(this.path.clone()));
|
||||
return Err(Self::error(ActionErrorKind::DifferentContent(
|
||||
this.path.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
tracing::debug!("Creating file `{}` already complete", this.path.display());
|
||||
|
@ -190,17 +201,21 @@ impl Action for CreateFile {
|
|||
let mut file = options
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Open(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
file.write_all(buf.as_bytes())
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Write(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let gid = if let Some(group) = group {
|
||||
Some(
|
||||
Group::from_name(group.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
|
||||
.ok_or(ActionError::NoGroup(group.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or(ActionErrorKind::NoGroup(group.clone()))
|
||||
.map_err(Self::error)?
|
||||
.gid,
|
||||
)
|
||||
} else {
|
||||
|
@ -209,14 +224,18 @@ impl Action for CreateFile {
|
|||
let uid = if let Some(user) = user {
|
||||
Some(
|
||||
User::from_name(user.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(user.clone(), e))?
|
||||
.ok_or(ActionError::NoUser(user.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or(ActionErrorKind::NoUser(user.clone()))
|
||||
.map_err(Self::error)?
|
||||
.uid,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
chown(path, uid, gid).map_err(|e| ActionError::Chown(path.clone(), e))?;
|
||||
chown(path, uid, gid)
|
||||
.map_err(|e| ActionErrorKind::Chown(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -250,7 +269,8 @@ impl Action for CreateFile {
|
|||
|
||||
remove_file(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Remove(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -259,7 +279,7 @@ impl Action for CreateFile {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use eyre::eyre;
|
||||
use color_eyre::eyre::eyre;
|
||||
use tokio::fs::write;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -346,9 +366,20 @@ mod test {
|
|||
)
|
||||
.await
|
||||
{
|
||||
Err(ActionError::DifferentContent(path)) => assert_eq!(path, test_file.as_path()),
|
||||
_ => return Err(eyre!("Should have returned an ActionError::Exists error")),
|
||||
}
|
||||
Err(error) => match error.kind() {
|
||||
ActionErrorKind::DifferentContent(path) => assert_eq!(path, test_file.as_path()),
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionErrorKind::Exists error"
|
||||
))
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionErrorKind::Exists error"
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
assert!(test_file.exists(), "File should have not been deleted");
|
||||
|
||||
|
@ -376,14 +407,21 @@ mod test {
|
|||
)
|
||||
.await
|
||||
{
|
||||
Err(ActionError::PathModeMismatch(path, got, expected)) => {
|
||||
assert_eq!(path, test_file.as_path());
|
||||
assert_eq!(expected, expected_mode);
|
||||
assert_eq!(got, initial_mode);
|
||||
Err(err) => match err.kind() {
|
||||
ActionErrorKind::PathModeMismatch(path, got, expected) => {
|
||||
assert_eq!(path, test_file.as_path());
|
||||
assert_eq!(*expected, expected_mode);
|
||||
assert_eq!(*got, initial_mode);
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionErrorKind::PathModeMismatch error"
|
||||
))
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionError::PathModeMismatch error"
|
||||
"Should have returned an ActionErrorKind::PathModeMismatch error"
|
||||
))
|
||||
},
|
||||
}
|
||||
|
@ -436,10 +474,17 @@ mod test {
|
|||
)
|
||||
.await
|
||||
{
|
||||
Err(ActionError::PathWasNotFile(path)) => assert_eq!(path, temp_dir.path()),
|
||||
Err(err) => match err.kind() {
|
||||
ActionErrorKind::PathWasNotFile(path) => assert_eq!(path, temp_dir.path()),
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionErrorKind::PathWasNotFile error"
|
||||
))
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionError::PathWasNotFile error"
|
||||
"Should have returned an ActionErrorKind::PathWasNotFile error"
|
||||
))
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use nix::unistd::Group;
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionTag};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||
|
@ -25,14 +25,15 @@ impl CreateGroup {
|
|||
};
|
||||
// Ensure group does not exists
|
||||
if let Some(group) = Group::from_name(name.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(name.clone(), e))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(name.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
{
|
||||
if group.gid.as_raw() != gid {
|
||||
return Err(ActionError::GroupGidMismatch(
|
||||
return Err(Self::error(ActionErrorKind::GroupGidMismatch(
|
||||
name.clone(),
|
||||
group.gid.as_raw(),
|
||||
gid,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
tracing::debug!("Creating group `{}` already complete", this.name);
|
||||
|
@ -96,7 +97,8 @@ impl Action for CreateGroup {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
_ => {
|
||||
if which::which("groupadd").is_ok() {
|
||||
|
@ -106,7 +108,8 @@ impl Action for CreateGroup {
|
|||
.args(["-g", &gid.to_string(), "--system", name])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if which::which("addgroup").is_ok() {
|
||||
execute_command(
|
||||
Command::new("addgroup")
|
||||
|
@ -114,9 +117,10 @@ impl Action for CreateGroup {
|
|||
.args(["-g", &gid.to_string(), "--system", name])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
return Err(ActionError::MissingGroupCreationCommand);
|
||||
return Err(Self::error(ActionErrorKind::MissingGroupCreationCommand));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -151,7 +155,8 @@ impl Action for CreateGroup {
|
|||
.args([".", "-delete", &format!("/Groups/{name}")])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
if !output.status.success() {}
|
||||
},
|
||||
_ => {
|
||||
|
@ -162,7 +167,8 @@ impl Action for CreateGroup {
|
|||
.arg(name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if which::which("delgroup").is_ok() {
|
||||
execute_command(
|
||||
Command::new("delgroup")
|
||||
|
@ -170,9 +176,10 @@ impl Action for CreateGroup {
|
|||
.arg(name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
return Err(ActionError::MissingGroupDeletionCommand);
|
||||
return Err(Self::error(ActionErrorKind::MissingGroupDeletionCommand));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use nix::unistd::{chown, Group, User};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
use rand::Rng;
|
||||
use std::{
|
||||
io::SeekFrom,
|
||||
|
@ -61,15 +63,17 @@ impl CreateOrInsertIntoFile {
|
|||
// If the path exists, perhaps we can just skip this
|
||||
let mut file = File::open(&this.path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(this.path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Open(this.path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.await
|
||||
.map_err(|e| ActionError::GettingMetadata(this.path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::GettingMetadata(this.path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if !metadata.is_file() {
|
||||
return Err(ActionError::PathWasNotFile(this.path));
|
||||
return Err(Self::error(ActionErrorKind::PathWasNotFile(this.path)));
|
||||
}
|
||||
|
||||
if let Some(mode) = mode {
|
||||
|
@ -92,31 +96,35 @@ impl CreateOrInsertIntoFile {
|
|||
if let Some(user) = &this.user {
|
||||
// If the file exists, the user must also exist to be correct.
|
||||
let expected_uid = User::from_name(user.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(user.clone(), e))?
|
||||
.ok_or_else(|| ActionError::NoUser(user.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or_else(|| ActionErrorKind::NoUser(user.clone()))
|
||||
.map_err(Self::error)?
|
||||
.uid;
|
||||
let found_uid = metadata.uid();
|
||||
if found_uid != expected_uid.as_raw() {
|
||||
return Err(ActionError::PathUserMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathUserMismatch(
|
||||
this.path.clone(),
|
||||
found_uid,
|
||||
expected_uid.as_raw(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
if let Some(group) = &this.group {
|
||||
// If the file exists, the group must also exist to be correct.
|
||||
let expected_gid = Group::from_name(group.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
|
||||
.ok_or_else(|| ActionError::NoUser(group.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or_else(|| ActionErrorKind::NoUser(group.clone()))
|
||||
.map_err(Self::error)?
|
||||
.gid;
|
||||
let found_gid = metadata.gid();
|
||||
if found_gid != expected_gid.as_raw() {
|
||||
return Err(ActionError::PathGroupMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathGroupMismatch(
|
||||
this.path.clone(),
|
||||
found_gid,
|
||||
expected_gid.as_raw(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +132,8 @@ impl CreateOrInsertIntoFile {
|
|||
let mut discovered_buf = String::new();
|
||||
file.read_to_string(&mut discovered_buf)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(this.path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Read(this.path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if discovered_buf.contains(&this.buf) {
|
||||
tracing::debug!("Inserting into `{}` already complete", this.path.display(),);
|
||||
|
@ -185,7 +194,7 @@ impl Action for CreateOrInsertIntoFile {
|
|||
let mut orig_file = match OpenOptions::new().read(true).open(&path).await {
|
||||
Ok(f) => Some(f),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
|
||||
Err(e) => return Err(ActionError::Open(path.to_owned(), e)),
|
||||
Err(e) => return Err(Self::error(ActionErrorKind::Open(path.to_owned(), e))),
|
||||
};
|
||||
|
||||
// Create a temporary file in the same directory as the one
|
||||
|
@ -209,39 +218,44 @@ impl Action for CreateOrInsertIntoFile {
|
|||
.open(&temp_file_path)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Open(temp_file_path.clone(), e)
|
||||
})?;
|
||||
ActionErrorKind::Open(temp_file_path.clone(), e)
|
||||
}).map_err(Self::error)?;
|
||||
|
||||
if *position == Position::End {
|
||||
if let Some(ref mut orig_file) = orig_file {
|
||||
tokio::io::copy(orig_file, &mut temp_file)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Copy(path.to_owned(), temp_file_path.to_owned(), e)
|
||||
})?;
|
||||
ActionErrorKind::Copy(path.to_owned(), temp_file_path.to_owned(), e)
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
}
|
||||
|
||||
temp_file
|
||||
.write_all(buf.as_bytes())
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(temp_file_path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Write(temp_file_path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if *position == Position::Beginning {
|
||||
if let Some(ref mut orig_file) = orig_file {
|
||||
tokio::io::copy(orig_file, &mut temp_file)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Copy(path.to_owned(), temp_file_path.to_owned(), e)
|
||||
})?;
|
||||
ActionErrorKind::Copy(path.to_owned(), temp_file_path.to_owned(), e)
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
}
|
||||
|
||||
let gid = if let Some(group) = group {
|
||||
Some(
|
||||
Group::from_name(group.as_str())
|
||||
.map_err(|e| ActionError::GettingGroupId(group.clone(), e))?
|
||||
.ok_or(ActionError::NoGroup(group.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or(ActionErrorKind::NoGroup(group.clone()))
|
||||
.map_err(Self::error)?
|
||||
.gid,
|
||||
)
|
||||
} else {
|
||||
|
@ -250,8 +264,10 @@ impl Action for CreateOrInsertIntoFile {
|
|||
let uid = if let Some(user) = user {
|
||||
Some(
|
||||
User::from_name(user.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(user.clone(), e))?
|
||||
.ok_or(ActionError::NoUser(user.clone()))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
.ok_or(ActionErrorKind::NoUser(user.clone()))
|
||||
.map_err(Self::error)?
|
||||
.uid,
|
||||
)
|
||||
} else {
|
||||
|
@ -261,17 +277,21 @@ impl Action for CreateOrInsertIntoFile {
|
|||
// Change ownership _before_ applying mode, to ensure that if
|
||||
// a file needs to be setuid it will never be setuid for the
|
||||
// wrong user
|
||||
chown(&temp_file_path, uid, gid).map_err(|e| ActionError::Chown(path.clone(), e))?;
|
||||
chown(&temp_file_path, uid, gid)
|
||||
.map_err(|e| ActionErrorKind::Chown(path.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if let Some(mode) = mode {
|
||||
tokio::fs::set_permissions(&temp_file_path, PermissionsExt::from_mode(*mode))
|
||||
.await
|
||||
.map_err(|e| ActionError::SetPermissions(*mode, path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::SetPermissions(*mode, path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
} else if let Some(original_file) = orig_file {
|
||||
let original_file_mode = original_file
|
||||
.metadata()
|
||||
.await
|
||||
.map_err(|e| ActionError::GettingMetadata(path.to_path_buf(), e))?
|
||||
.map_err(|e| ActionErrorKind::GettingMetadata(path.to_path_buf(), e))
|
||||
.map_err(Self::error)?
|
||||
.permissions()
|
||||
.mode();
|
||||
tokio::fs::set_permissions(
|
||||
|
@ -279,12 +299,14 @@ impl Action for CreateOrInsertIntoFile {
|
|||
PermissionsExt::from_mode(original_file_mode),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::SetPermissions(original_file_mode, path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::SetPermissions(original_file_mode, path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
tokio::fs::rename(&temp_file_path, &path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Rename(path.to_owned(), temp_file_path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Rename(path.to_owned(), temp_file_path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -323,12 +345,14 @@ impl Action for CreateOrInsertIntoFile {
|
|||
.read(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Open(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let mut file_contents = String::default();
|
||||
file.read_to_string(&mut file_contents)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Read(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if let Some(start) = file_contents.rfind(buf.as_str()) {
|
||||
let end = start + buf.len();
|
||||
|
@ -338,20 +362,25 @@ impl Action for CreateOrInsertIntoFile {
|
|||
if file_contents.is_empty() {
|
||||
remove_file(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Remove(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.await
|
||||
.map_err(|e| ActionError::Seek(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Seek(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
file.set_len(0)
|
||||
.await
|
||||
.map_err(|e| ActionError::Truncate(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Truncate(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
file.write_all(file_contents.as_bytes())
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Write(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
file.flush()
|
||||
.await
|
||||
.map_err(|e| ActionError::Flush(path.to_owned(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Flush(path.to_owned(), e))
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -360,7 +389,7 @@ impl Action for CreateOrInsertIntoFile {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use eyre::eyre;
|
||||
use color_eyre::eyre::eyre;
|
||||
use tokio::fs::{read_to_string, write};
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -534,10 +563,17 @@ mod test {
|
|||
)
|
||||
.await
|
||||
{
|
||||
Err(ActionError::PathWasNotFile(path)) => assert_eq!(path, temp_dir.path()),
|
||||
Err(err) => match err.kind() {
|
||||
ActionErrorKind::PathWasNotFile(path) => assert_eq!(path, temp_dir.path()),
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionErrorKind::PathWasNotFile error"
|
||||
))
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned an ActionError::PathWasNotFile error"
|
||||
"Should have returned an ActionErrorKind::PathWasNotFile error"
|
||||
))
|
||||
},
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ use tokio::{
|
|||
};
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
|
||||
/// The `nix.conf` configuration names that are safe to merge.
|
||||
// FIXME(@cole-h): make configurable by downstream users?
|
||||
|
@ -33,6 +35,12 @@ pub enum CreateOrMergeNixConfigError {
|
|||
UnmergeableConfig(Vec<String>, std::path::PathBuf),
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for CreateOrMergeNixConfigError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create or merge an existing `nix.conf` at the specified path.
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct CreateOrMergeNixConfig {
|
||||
|
@ -132,10 +140,10 @@ impl CreateOrMergeNixConfig {
|
|||
let path = path.to_path_buf();
|
||||
let metadata = path
|
||||
.metadata()
|
||||
.map_err(|e| ActionError::GettingMetadata(path.clone(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::GettingMetadata(path.clone(), e)))?;
|
||||
|
||||
if !metadata.is_file() {
|
||||
return Err(ActionError::PathWasNotFile(path));
|
||||
return Err(Self::error(ActionErrorKind::PathWasNotFile(path)));
|
||||
}
|
||||
|
||||
// Does the file have the right permissions?
|
||||
|
@ -144,23 +152,23 @@ impl CreateOrMergeNixConfig {
|
|||
let discovered_mode = discovered_mode & 0o777;
|
||||
|
||||
if discovered_mode != NIX_CONF_MODE {
|
||||
return Err(ActionError::PathModeMismatch(
|
||||
return Err(Self::error(ActionErrorKind::PathModeMismatch(
|
||||
path,
|
||||
discovered_mode,
|
||||
NIX_CONF_MODE,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
let existing_nix_config = NixConfig::parse_file(&path)
|
||||
.map_err(CreateOrMergeNixConfigError::ParseNixConfig)
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let (merged_nix_config, existing_nix_config) = Self::merge_pending_and_existing_nix_config(
|
||||
&pending_nix_config,
|
||||
&existing_nix_config,
|
||||
&path,
|
||||
)
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok((merged_nix_config, existing_nix_config))
|
||||
}
|
||||
|
@ -260,7 +268,7 @@ impl Action for CreateOrMergeNixConfig {
|
|||
.open(&temp_file_path)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Open(temp_file_path.clone(), e)
|
||||
Self::error(ActionErrorKind::Open(temp_file_path.clone(), e))
|
||||
})?;
|
||||
|
||||
let (mut merged_nix_config, mut existing_nix_config) = if path.exists() {
|
||||
|
@ -276,7 +284,7 @@ impl Action for CreateOrMergeNixConfig {
|
|||
if let Some(existing_nix_config) = existing_nix_config.as_mut() {
|
||||
let mut discovered_buf = tokio::fs::read_to_string(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(path.to_path_buf(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Read(path.to_path_buf(), e)))?;
|
||||
|
||||
// We append a newline to ensure that, in the case there are comments at the end of the
|
||||
// file and _NO_ trailing newline, we still preserve the entire block of comments.
|
||||
|
@ -416,13 +424,25 @@ impl Action for CreateOrMergeNixConfig {
|
|||
temp_file
|
||||
.write_all(new_config.as_bytes())
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(temp_file_path.clone(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Write(temp_file_path.clone(), e)))?;
|
||||
tokio::fs::set_permissions(&temp_file_path, PermissionsExt::from_mode(NIX_CONF_MODE))
|
||||
.await
|
||||
.map_err(|e| ActionError::SetPermissions(NIX_CONF_MODE, path.to_owned(), e))?;
|
||||
.map_err(|e| {
|
||||
Self::error(ActionErrorKind::SetPermissions(
|
||||
NIX_CONF_MODE,
|
||||
path.to_owned(),
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
tokio::fs::rename(&temp_file_path, &path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Rename(temp_file_path.to_owned(), path.to_owned(), e))?;
|
||||
.map_err(|e| {
|
||||
Self::error(ActionErrorKind::Rename(
|
||||
temp_file_path.to_owned(),
|
||||
path.to_owned(),
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -448,7 +468,7 @@ impl Action for CreateOrMergeNixConfig {
|
|||
|
||||
remove_file(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Remove(path.to_owned(), e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -457,7 +477,7 @@ impl Action for CreateOrMergeNixConfig {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use eyre::eyre;
|
||||
use color_eyre::eyre::eyre;
|
||||
use tokio::fs::write;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -600,15 +620,20 @@ mod test {
|
|||
.settings_mut()
|
||||
.insert("warn-dirty".into(), "false".into());
|
||||
match CreateOrMergeNixConfig::plan(&test_file, nix_config).await {
|
||||
Err(ActionError::Custom(e)) => match e.downcast_ref::<CreateOrMergeNixConfigError>() {
|
||||
Some(CreateOrMergeNixConfigError::UnmergeableConfig(_, path)) => {
|
||||
assert_eq!(path, test_file.as_path())
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned CreateOrMergeNixConfigError::UnmergeableConfig"
|
||||
))
|
||||
Err(err) => match err.kind() {
|
||||
ActionErrorKind::Custom(e) => {
|
||||
match e.downcast_ref::<CreateOrMergeNixConfigError>() {
|
||||
Some(CreateOrMergeNixConfigError::UnmergeableConfig(_, path)) => {
|
||||
assert_eq!(path, test_file.as_path())
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
"Should have returned CreateOrMergeNixConfigError::UnmergeableConfig"
|
||||
))
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => {
|
||||
return Err(eyre!(
|
||||
|
|
|
@ -2,7 +2,7 @@ use nix::unistd::User;
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionTag};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||
|
@ -37,22 +37,23 @@ impl CreateUser {
|
|||
};
|
||||
// Ensure user does not exists
|
||||
if let Some(user) = User::from_name(name.as_str())
|
||||
.map_err(|e| ActionError::GettingUserId(name.clone(), e))?
|
||||
.map_err(|e| ActionErrorKind::GettingUserId(name.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
{
|
||||
if user.uid.as_raw() != uid {
|
||||
return Err(ActionError::UserUidMismatch(
|
||||
return Err(Self::error(ActionErrorKind::UserUidMismatch(
|
||||
name.clone(),
|
||||
user.uid.as_raw(),
|
||||
uid,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
if user.gid.as_raw() != gid {
|
||||
return Err(ActionError::UserGidMismatch(
|
||||
return Err(Self::error(ActionErrorKind::UserGidMismatch(
|
||||
name.clone(),
|
||||
user.gid.as_raw(),
|
||||
gid,
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
tracing::debug!("Creating user `{}` already complete", this.name);
|
||||
|
@ -120,7 +121,8 @@ impl Action for CreateUser {
|
|||
.args([".", "-create", &format!("/Users/{name}")])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
execute_command(
|
||||
Command::new("/usr/bin/dscl")
|
||||
.process_group(0)
|
||||
|
@ -133,7 +135,8 @@ impl Action for CreateUser {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
execute_command(
|
||||
Command::new("/usr/bin/dscl")
|
||||
.process_group(0)
|
||||
|
@ -146,7 +149,8 @@ impl Action for CreateUser {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
execute_command(
|
||||
Command::new("/usr/bin/dscl")
|
||||
.process_group(0)
|
||||
|
@ -159,7 +163,8 @@ impl Action for CreateUser {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
execute_command(
|
||||
Command::new("/usr/bin/dscl")
|
||||
.process_group(0)
|
||||
|
@ -172,14 +177,16 @@ impl Action for CreateUser {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
execute_command(
|
||||
Command::new("/usr/bin/dscl")
|
||||
.process_group(0)
|
||||
.args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
_ => {
|
||||
if which::which("useradd").is_ok() {
|
||||
|
@ -207,7 +214,8 @@ impl Action for CreateUser {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if which::which("adduser").is_ok() {
|
||||
execute_command(
|
||||
Command::new("adduser")
|
||||
|
@ -229,9 +237,10 @@ impl Action for CreateUser {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
return Err(ActionError::MissingUserCreationCommand);
|
||||
return Err(Self::error(ActionErrorKind::MissingUserCreationCommand));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -253,14 +262,6 @@ impl Action for CreateUser {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self {
|
||||
name,
|
||||
uid: _,
|
||||
groupname: _,
|
||||
gid: _,
|
||||
comment: _,
|
||||
} = self;
|
||||
|
||||
use target_lexicon::OperatingSystem;
|
||||
match target_lexicon::OperatingSystem::host() {
|
||||
OperatingSystem::MacOSX {
|
||||
|
@ -274,25 +275,28 @@ impl Action for CreateUser {
|
|||
// Documentation on https://it.megocollector.com/macos/cant-delete-a-macos-user-with-dscl-resolution/ and http://www.aixperts.co.uk/?p=214 suggested it was a secure token
|
||||
// That is correct, however it's a bit more nuanced. It appears to be that a user must be graphically logged in for some other user on the system to be deleted.
|
||||
let mut command = Command::new("/usr/bin/dscl");
|
||||
command.args([".", "-delete", &format!("/Users/{name}")]);
|
||||
command.args([".", "-delete", &format!("/Users/{}", self.name)]);
|
||||
command.process_group(0);
|
||||
command.stdin(std::process::Stdio::null());
|
||||
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))
|
||||
.map_err(Self::error)?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
match output.status.code() {
|
||||
Some(0) => (),
|
||||
Some(40) if stderr.contains("-14120") => {
|
||||
// The user is on an ephemeral Mac, like detsys uses
|
||||
// These Macs cannot always delete users, as sometimes there is no graphical login
|
||||
tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{name}");
|
||||
tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{}", self.name);
|
||||
},
|
||||
_ => {
|
||||
// Something went wrong
|
||||
return Err(ActionError::command_output(&command, output));
|
||||
return Err(Self::error(ActionErrorKind::command_output(
|
||||
&command, output,
|
||||
)));
|
||||
},
|
||||
}
|
||||
},
|
||||
|
@ -301,20 +305,22 @@ impl Action for CreateUser {
|
|||
execute_command(
|
||||
Command::new("userdel")
|
||||
.process_group(0)
|
||||
.arg(name)
|
||||
.arg(&self.name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if which::which("deluser").is_ok() {
|
||||
execute_command(
|
||||
Command::new("deluser")
|
||||
.process_group(0)
|
||||
.arg(name)
|
||||
.arg(&self.name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
return Err(ActionError::MissingUserDeletionCommand);
|
||||
return Err(Self::error(ActionErrorKind::MissingUserDeletionCommand));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ use reqwest::Url;
|
|||
use tracing::{span, Span};
|
||||
|
||||
use crate::{
|
||||
action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction},
|
||||
action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction},
|
||||
parse_ssl_cert,
|
||||
};
|
||||
|
||||
|
@ -33,26 +33,18 @@ impl FetchAndUnpackNix {
|
|||
|
||||
match url.scheme() {
|
||||
"https" | "http" | "file" => (),
|
||||
_ => {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
FetchUrlError::UnknownUrlScheme,
|
||||
)))
|
||||
},
|
||||
_ => return Err(Self::error(FetchUrlError::UnknownUrlScheme)),
|
||||
};
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
match proxy.scheme() {
|
||||
"https" | "http" | "socks5" => (),
|
||||
_ => {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
FetchUrlError::UnknownProxyScheme,
|
||||
)))
|
||||
},
|
||||
_ => return Err(Self::error(FetchUrlError::UnknownProxyScheme)),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(ssl_cert_file) = &ssl_cert_file {
|
||||
parse_ssl_cert(&ssl_cert_file).await?;
|
||||
parse_ssl_cert(&ssl_cert_file).await.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
@ -106,41 +98,43 @@ impl Action for FetchAndUnpackNix {
|
|||
"https" | "http" => {
|
||||
let mut buildable_client = reqwest::Client::builder();
|
||||
if let Some(proxy) = &self.proxy {
|
||||
buildable_client =
|
||||
buildable_client.proxy(reqwest::Proxy::all(proxy.clone()).map_err(|e| {
|
||||
ActionError::Custom(Box::new(FetchUrlError::Reqwest(e)))
|
||||
})?)
|
||||
buildable_client = buildable_client.proxy(
|
||||
reqwest::Proxy::all(proxy.clone())
|
||||
.map_err(FetchUrlError::Reqwest)
|
||||
.map_err(Self::error)?,
|
||||
)
|
||||
}
|
||||
if let Some(ssl_cert_file) = &self.ssl_cert_file {
|
||||
let ssl_cert = parse_ssl_cert(&ssl_cert_file).await?;
|
||||
let ssl_cert = parse_ssl_cert(&ssl_cert_file).await.map_err(Self::error)?;
|
||||
buildable_client = buildable_client.add_root_certificate(ssl_cert);
|
||||
}
|
||||
let client = buildable_client
|
||||
.build()
|
||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
|
||||
.map_err(FetchUrlError::Reqwest)
|
||||
.map_err(Self::error)?;
|
||||
let req = client
|
||||
.get(self.url.clone())
|
||||
.build()
|
||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
|
||||
.map_err(FetchUrlError::Reqwest)
|
||||
.map_err(Self::error)?;
|
||||
let res = client
|
||||
.execute(req)
|
||||
.await
|
||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
|
||||
.map_err(FetchUrlError::Reqwest)
|
||||
.map_err(Self::error)?;
|
||||
res.bytes()
|
||||
.await
|
||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?
|
||||
.map_err(FetchUrlError::Reqwest)
|
||||
.map_err(Self::error)?
|
||||
},
|
||||
"file" => {
|
||||
let buf = tokio::fs::read(self.url.path())
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(PathBuf::from(self.url.path()), e))?;
|
||||
.map_err(|e| ActionErrorKind::Read(PathBuf::from(self.url.path()), e))
|
||||
.map_err(Self::error)?;
|
||||
Bytes::from(buf)
|
||||
},
|
||||
_ => {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
FetchUrlError::UnknownUrlScheme,
|
||||
)))
|
||||
},
|
||||
_ => return Err(Self::error(FetchUrlError::UnknownUrlScheme)),
|
||||
};
|
||||
|
||||
// TODO(@Hoverbear): Pick directory
|
||||
|
@ -151,7 +145,8 @@ impl Action for FetchAndUnpackNix {
|
|||
let mut archive = tar::Archive::new(decoder);
|
||||
archive
|
||||
.unpack(&dest_clone)
|
||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Unarchive(e))))?;
|
||||
.map_err(FetchUrlError::Unarchive)
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -182,3 +177,9 @@ pub enum FetchUrlError {
|
|||
#[error("Unknown proxy scheme, `https://`, `socks5://`, and `http://` supported")]
|
||||
UnknownProxyScheme,
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for FetchUrlError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
|
||||
pub(crate) const DEST: &str = "/nix/";
|
||||
|
||||
|
@ -57,32 +59,37 @@ impl Action for MoveUnpackedNix {
|
|||
|
||||
// This is the `nix-$VERSION` folder which unpacks from the tarball, not a nix derivation
|
||||
let found_nix_paths = glob::glob(&format!("{}/nix-*", unpacked_path.display()))
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?
|
||||
.map_err(|e| Self::error(MoveUnpackedNixError::from(e)))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?;
|
||||
.map_err(|e| Self::error(MoveUnpackedNixError::from(e)))?;
|
||||
if found_nix_paths.len() != 1 {
|
||||
return Err(ActionError::MalformedBinaryTarball);
|
||||
return Err(Self::error(ActionErrorKind::MalformedBinaryTarball));
|
||||
}
|
||||
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
||||
let src_store = found_nix_path.join("store");
|
||||
let mut src_store_listing = tokio::fs::read_dir(src_store.clone())
|
||||
.await
|
||||
.map_err(|e| ActionError::ReadDir(src_store.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::ReadDir(src_store.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
let dest_store = Path::new(DEST).join("store");
|
||||
if dest_store.exists() {
|
||||
if !dest_store.is_dir() {
|
||||
return Err(ActionError::PathWasNotDirectory(dest_store.clone()))?;
|
||||
return Err(Self::error(ActionErrorKind::PathWasNotDirectory(
|
||||
dest_store.clone(),
|
||||
)))?;
|
||||
}
|
||||
} else {
|
||||
tokio::fs::create_dir(&dest_store)
|
||||
.await
|
||||
.map_err(|e| ActionError::CreateDirectory(dest_store.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::CreateDirectory(dest_store.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
while let Some(entry) = src_store_listing
|
||||
.next_entry()
|
||||
.await
|
||||
.map_err(|e| ActionError::ReadDir(src_store.clone(), e))?
|
||||
.map_err(|e| ActionErrorKind::ReadDir(src_store.clone(), e))
|
||||
.map_err(Self::error)?
|
||||
{
|
||||
let entry_dest = dest_store.join(entry.file_name());
|
||||
if entry_dest.exists() {
|
||||
|
@ -92,8 +99,9 @@ impl Action for MoveUnpackedNix {
|
|||
tokio::fs::rename(&entry.path(), &entry_dest)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Rename(entry.path().clone(), entry_dest.to_owned(), e)
|
||||
})?;
|
||||
ActionErrorKind::Rename(entry.path().clone(), entry_dest.to_owned(), e)
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,3 +135,9 @@ pub enum MoveUnpackedNixError {
|
|||
glob::GlobError,
|
||||
),
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for MoveUnpackedNixError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||
use tokio::fs::remove_dir_all;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionState};
|
||||
use crate::action::{Action, ActionDescription, ActionErrorKind, ActionState};
|
||||
use crate::action::{ActionError, StatefulAction};
|
||||
|
||||
/** Remove a directory, does nothing on revert.
|
||||
|
@ -53,11 +53,13 @@ impl Action for RemoveDirectory {
|
|||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
if self.path.exists() {
|
||||
if !self.path.is_dir() {
|
||||
return Err(ActionError::PathWasNotDirectory(self.path.clone()));
|
||||
return Err(Self::error(ActionErrorKind::PathWasNotDirectory(
|
||||
self.path.clone(),
|
||||
)));
|
||||
}
|
||||
remove_dir_all(&self.path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(self.path.clone(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Remove(self.path.clone(), e)))?;
|
||||
} else {
|
||||
tracing::debug!("Directory `{}` not present, skipping", self.path.display(),);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
action::{ActionError, ActionTag, StatefulAction},
|
||||
action::{ActionError, ActionErrorKind, ActionTag, StatefulAction},
|
||||
execute_command, set_env,
|
||||
};
|
||||
|
||||
|
@ -50,9 +50,9 @@ impl Action for SetupDefaultProfile {
|
|||
// Find an `nix` package
|
||||
let nix_pkg_glob = "/nix/store/*-nix-*";
|
||||
let mut found_nix_pkg = None;
|
||||
for entry in glob(nix_pkg_glob).map_err(|e| {
|
||||
ActionError::Custom(Box::new(SetupDefaultProfileError::GlobPatternError(e)))
|
||||
})? {
|
||||
for entry in glob(nix_pkg_glob)
|
||||
.map_err(|e| Self::error(SetupDefaultProfileError::GlobPatternError(e)))?
|
||||
{
|
||||
match entry {
|
||||
Ok(path) => {
|
||||
// TODO(@Hoverbear): Should probably ensure is unique
|
||||
|
@ -65,17 +65,15 @@ impl Action for SetupDefaultProfile {
|
|||
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
|
||||
nix_pkg
|
||||
} else {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
SetupDefaultProfileError::NoNix,
|
||||
)));
|
||||
return Err(Self::error(SetupDefaultProfileError::NoNix));
|
||||
};
|
||||
|
||||
// Find an `nss-cacert` package, add it too.
|
||||
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
|
||||
let mut found_nss_ca_cert_pkg = None;
|
||||
for entry in glob(nss_ca_cert_pkg_glob).map_err(|e| {
|
||||
ActionError::Custom(Box::new(SetupDefaultProfileError::GlobPatternError(e)))
|
||||
})? {
|
||||
for entry in glob(nss_ca_cert_pkg_glob)
|
||||
.map_err(|e| Self::error(SetupDefaultProfileError::GlobPatternError(e)))?
|
||||
{
|
||||
match entry {
|
||||
Ok(path) => {
|
||||
// TODO(@Hoverbear): Should probably ensure is unique
|
||||
|
@ -88,23 +86,22 @@ impl Action for SetupDefaultProfile {
|
|||
let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
|
||||
nss_ca_cert_pkg
|
||||
} else {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
SetupDefaultProfileError::NoNssCacert,
|
||||
)));
|
||||
return Err(Self::error(SetupDefaultProfileError::NoNssCacert));
|
||||
};
|
||||
|
||||
let found_nix_paths = glob::glob(&format!("{}/nix-*", self.unpacked_path.display()))
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?
|
||||
.map_err(|e| Self::error(SetupDefaultProfileError::from(e)))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?;
|
||||
.map_err(|e| Self::error(SetupDefaultProfileError::from(e)))?;
|
||||
if found_nix_paths.len() != 1 {
|
||||
return Err(ActionError::MalformedBinaryTarball);
|
||||
return Err(Self::error(ActionErrorKind::MalformedBinaryTarball));
|
||||
}
|
||||
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
||||
let reginfo_path = PathBuf::from(found_nix_path).join(".reginfo");
|
||||
let reginfo = tokio::fs::read(®info_path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(reginfo_path.to_path_buf(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Read(reginfo_path.to_path_buf(), e))
|
||||
.map_err(Self::error)?;
|
||||
let mut load_db_command = Command::new(nix_pkg.join("bin/nix-store"));
|
||||
load_db_command.process_group(0);
|
||||
load_db_command.arg("--load-db");
|
||||
|
@ -113,9 +110,7 @@ impl Action for SetupDefaultProfile {
|
|||
load_db_command.stderr(std::process::Stdio::piped());
|
||||
load_db_command.env(
|
||||
"HOME",
|
||||
dirs::home_dir().ok_or_else(|| {
|
||||
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
||||
})?,
|
||||
dirs::home_dir().ok_or_else(|| Self::error(SetupDefaultProfileError::NoRootHome))?,
|
||||
);
|
||||
tracing::trace!(
|
||||
"Executing `{:?}` with stdin from `{}`",
|
||||
|
@ -124,17 +119,20 @@ impl Action for SetupDefaultProfile {
|
|||
);
|
||||
let mut handle = load_db_command
|
||||
.spawn()
|
||||
.map_err(|e| ActionError::command(&load_db_command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&load_db_command, e))
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let mut stdin = handle.stdin.take().unwrap();
|
||||
stdin
|
||||
.write_all(®info)
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(PathBuf::from("/dev/stdin"), e))?;
|
||||
.map_err(|e| ActionErrorKind::Write(PathBuf::from("/dev/stdin"), e))
|
||||
.map_err(Self::error)?;
|
||||
stdin
|
||||
.flush()
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(PathBuf::from("/dev/stdin"), e))?;
|
||||
.map_err(|e| ActionErrorKind::Write(PathBuf::from("/dev/stdin"), e))
|
||||
.map_err(Self::error)?;
|
||||
drop(stdin);
|
||||
tracing::trace!(
|
||||
"Wrote `{}` to stdin of `nix-store --load-db`",
|
||||
|
@ -144,9 +142,13 @@ impl Action for SetupDefaultProfile {
|
|||
let output = handle
|
||||
.wait_with_output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&load_db_command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&load_db_command, e))
|
||||
.map_err(Self::error)?;
|
||||
if !output.status.success() {
|
||||
return Err(ActionError::command_output(&load_db_command, output));
|
||||
return Err(Self::error(ActionErrorKind::command_output(
|
||||
&load_db_command,
|
||||
output,
|
||||
)));
|
||||
};
|
||||
|
||||
// Install `nix` itself into the store
|
||||
|
@ -158,16 +160,16 @@ impl Action for SetupDefaultProfile {
|
|||
.stdin(std::process::Stdio::null())
|
||||
.env(
|
||||
"HOME",
|
||||
dirs::home_dir().ok_or_else(|| {
|
||||
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
||||
})?,
|
||||
dirs::home_dir()
|
||||
.ok_or_else(|| Self::error(SetupDefaultProfileError::NoRootHome))?,
|
||||
)
|
||||
.env(
|
||||
"NIX_SSL_CERT_FILE",
|
||||
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
||||
), /* This is apparently load bearing... */
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
// Install `nix` itself into the store
|
||||
execute_command(
|
||||
|
@ -178,16 +180,16 @@ impl Action for SetupDefaultProfile {
|
|||
.stdin(std::process::Stdio::null())
|
||||
.env(
|
||||
"HOME",
|
||||
dirs::home_dir().ok_or_else(|| {
|
||||
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
||||
})?,
|
||||
dirs::home_dir()
|
||||
.ok_or_else(|| Self::error(SetupDefaultProfileError::NoRootHome))?,
|
||||
)
|
||||
.env(
|
||||
"NIX_SSL_CERT_FILE",
|
||||
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
||||
), /* This is apparently load bearing... */
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
set_env(
|
||||
"NIX_SSL_CERT_FILE",
|
||||
|
@ -234,3 +236,9 @@ pub enum SetupDefaultProfileError {
|
|||
#[error("No root home found to place channel configuration in")]
|
||||
NoRootHome,
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for SetupDefaultProfileError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::path::PathBuf;
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription};
|
||||
|
@ -39,7 +39,7 @@ pub struct ConfigureInitService {
|
|||
|
||||
impl ConfigureInitService {
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn check_if_systemd_unit_exists(src: &str, dest: &str) -> Result<(), ActionError> {
|
||||
async fn check_if_systemd_unit_exists(src: &str, dest: &str) -> Result<(), ActionErrorKind> {
|
||||
// TODO: once we have a way to communicate interaction between the library and the cli,
|
||||
// interactively ask for permission to remove the file
|
||||
|
||||
|
@ -50,21 +50,24 @@ impl ConfigureInitService {
|
|||
if unit_dest.is_symlink() {
|
||||
let link_dest = tokio::fs::read_link(&unit_dest)
|
||||
.await
|
||||
.map_err(|e| ActionError::ReadSymlink(unit_dest.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::ReadSymlink(unit_dest.clone(), e))?;
|
||||
if link_dest != unit_src {
|
||||
return Err(ActionError::SymlinkExists(unit_dest));
|
||||
return Err(ActionErrorKind::SymlinkExists(unit_dest));
|
||||
}
|
||||
} else {
|
||||
return Err(ActionError::FileExists(unit_dest));
|
||||
return Err(ActionErrorKind::FileExists(unit_dest));
|
||||
}
|
||||
}
|
||||
// NOTE: ...and if there are any overrides in the most well-known places for systemd
|
||||
if Path::new(&format!("{dest}.d")).exists() {
|
||||
return Err(ActionError::DirExists(PathBuf::from(format!("{dest}.d"))));
|
||||
return Err(ActionErrorKind::DirExists(PathBuf::from(format!(
|
||||
"{dest}.d"
|
||||
))));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn plan(
|
||||
init: InitSystem,
|
||||
|
@ -75,7 +78,7 @@ impl ConfigureInitService {
|
|||
Some(
|
||||
ssl_cert_file
|
||||
.canonicalize()
|
||||
.map_err(|e| ActionError::Canonicalize(ssl_cert_file, e))?,
|
||||
.map_err(|e| Self::error(ActionErrorKind::Canonicalize(ssl_cert_file, e)))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
|
@ -92,11 +95,15 @@ impl ConfigureInitService {
|
|||
// with systemd: https://www.freedesktop.org/software/systemd/man/sd_booted.html
|
||||
if !(Path::new("/run/systemd/system").exists() || which::which("systemctl").is_ok())
|
||||
{
|
||||
return Err(ActionError::SystemdMissing);
|
||||
return Err(Self::error(ActionErrorKind::SystemdMissing));
|
||||
}
|
||||
|
||||
Self::check_if_systemd_unit_exists(SERVICE_SRC, SERVICE_DEST).await?;
|
||||
Self::check_if_systemd_unit_exists(SOCKET_SRC, SOCKET_DEST).await?;
|
||||
Self::check_if_systemd_unit_exists(SERVICE_SRC, SERVICE_DEST)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
Self::check_if_systemd_unit_exists(SOCKET_SRC, SOCKET_DEST)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
InitSystem::None => {
|
||||
|
@ -183,11 +190,11 @@ impl Action for ConfigureInitService {
|
|||
tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Copy(
|
||||
Self::error(ActionErrorKind::Copy(
|
||||
src.to_path_buf(),
|
||||
PathBuf::from(DARWIN_NIX_DAEMON_DEST),
|
||||
e,
|
||||
)
|
||||
))
|
||||
})?;
|
||||
|
||||
execute_command(
|
||||
|
@ -197,7 +204,8 @@ impl Action for ConfigureInitService {
|
|||
.arg(DARWIN_NIX_DAEMON_DEST)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if let Some(ssl_cert_file) = ssl_cert_file {
|
||||
execute_command(
|
||||
|
@ -208,7 +216,8 @@ impl Action for ConfigureInitService {
|
|||
.arg(format!("{ssl_cert_file:?}"))
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
if *start_daemon {
|
||||
|
@ -220,7 +229,8 @@ impl Action for ConfigureInitService {
|
|||
.arg("system/org.nixos.nix-daemon")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -232,23 +242,32 @@ impl Action for ConfigureInitService {
|
|||
.arg("daemon-reload")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
// The goal state is the `socket` enabled and active, the service not enabled and stopped (it activates via socket activation)
|
||||
let socket_was_active = if is_enabled("nix-daemon.socket").await? {
|
||||
disable("nix-daemon.socket", true).await?;
|
||||
true
|
||||
} else if is_active("nix-daemon.socket").await? {
|
||||
stop("nix-daemon.socket").await?;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if is_enabled("nix-daemon.service").await? {
|
||||
let now = is_active("nix-daemon.service").await?;
|
||||
disable("nix-daemon.service", now).await?;
|
||||
} else if is_active("nix-daemon.service").await? {
|
||||
stop("nix-daemon.service").await?;
|
||||
let socket_was_active =
|
||||
if is_enabled("nix-daemon.socket").await.map_err(Self::error)? {
|
||||
disable("nix-daemon.socket", true)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
true
|
||||
} else if is_active("nix-daemon.socket").await.map_err(Self::error)? {
|
||||
stop("nix-daemon.socket").await.map_err(Self::error)?;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if is_enabled("nix-daemon.service")
|
||||
.await
|
||||
.map_err(Self::error)?
|
||||
{
|
||||
let now = is_active("nix-daemon.service").await.map_err(Self::error)?;
|
||||
disable("nix-daemon.service", now)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else if is_active("nix-daemon.service").await.map_err(Self::error)? {
|
||||
stop("nix-daemon.service").await.map_err(Self::error)?;
|
||||
};
|
||||
|
||||
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
|
||||
|
@ -256,12 +275,13 @@ impl Action for ConfigureInitService {
|
|||
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Symlink(
|
||||
ActionErrorKind::Symlink(
|
||||
PathBuf::from(TMPFILES_SRC),
|
||||
PathBuf::from(TMPFILES_DEST),
|
||||
e,
|
||||
)
|
||||
})?;
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
execute_command(
|
||||
|
@ -271,32 +291,39 @@ impl Action for ConfigureInitService {
|
|||
.arg("--prefix=/nix/var/nix")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
// TODO: once we have a way to communicate interaction between the library and the
|
||||
// cli, interactively ask for permission to remove the file
|
||||
|
||||
Self::check_if_systemd_unit_exists(SERVICE_SRC, SERVICE_DEST).await?;
|
||||
Self::check_if_systemd_unit_exists(SERVICE_SRC, SERVICE_DEST)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
tokio::fs::symlink(SERVICE_SRC, SERVICE_DEST)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Symlink(
|
||||
ActionErrorKind::Symlink(
|
||||
PathBuf::from(SERVICE_SRC),
|
||||
PathBuf::from(SERVICE_DEST),
|
||||
e,
|
||||
)
|
||||
})?;
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Self::check_if_systemd_unit_exists(SOCKET_SRC, SOCKET_DEST).await?;
|
||||
Self::check_if_systemd_unit_exists(SOCKET_SRC, SOCKET_DEST)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
tokio::fs::symlink(SOCKET_SRC, SOCKET_DEST)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Symlink(
|
||||
ActionErrorKind::Symlink(
|
||||
PathBuf::from(SOCKET_SRC),
|
||||
PathBuf::from(SOCKET_DEST),
|
||||
e,
|
||||
)
|
||||
})?;
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if *start_daemon {
|
||||
execute_command(
|
||||
|
@ -305,7 +332,8 @@ impl Action for ConfigureInitService {
|
|||
.arg("daemon-reload")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
if let Some(ssl_cert_file) = ssl_cert_file {
|
||||
|
@ -313,8 +341,9 @@ impl Action for ConfigureInitService {
|
|||
tokio::fs::create_dir(&service_conf_dir_path)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::CreateDirectory(service_conf_dir_path.clone(), e)
|
||||
})?;
|
||||
ActionErrorKind::CreateDirectory(service_conf_dir_path.clone(), e)
|
||||
})
|
||||
.map_err(Self::error)?;
|
||||
let service_conf_file_path =
|
||||
service_conf_dir_path.join("nix-ssl-cert-file.conf");
|
||||
tokio::fs::write(
|
||||
|
@ -327,13 +356,14 @@ impl Action for ConfigureInitService {
|
|||
),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(ssl_cert_file.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Write(ssl_cert_file.clone(), e))
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
if *start_daemon || socket_was_active {
|
||||
enable(SOCKET_SRC, true).await?;
|
||||
enable(SOCKET_SRC, true).await.map_err(Self::error)?;
|
||||
} else {
|
||||
enable(SOCKET_SRC, false).await?;
|
||||
enable(SOCKET_SRC, false).await.map_err(Self::error)?;
|
||||
}
|
||||
},
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
@ -373,6 +403,8 @@ impl Action for ConfigureInitService {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let mut errors = vec![];
|
||||
|
||||
match self.init {
|
||||
#[cfg(target_os = "macos")]
|
||||
InitSystem::Launchd => {
|
||||
|
@ -382,84 +414,118 @@ impl Action for ConfigureInitService {
|
|||
.arg("unload")
|
||||
.arg(DARWIN_NIX_DAEMON_DEST),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| Self::error(e))?;
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
InitSystem::Systemd => {
|
||||
// We separate stop and disable (instead of using `--now`) to avoid cases where the service isn't started, but is enabled.
|
||||
|
||||
let socket_is_active = is_active("nix-daemon.socket").await?;
|
||||
let socket_is_enabled = is_enabled("nix-daemon.socket").await?;
|
||||
let service_is_active = is_active("nix-daemon.service").await?;
|
||||
let service_is_enabled = is_enabled("nix-daemon.service").await?;
|
||||
// These have to fail fast.
|
||||
let socket_is_active = is_active("nix-daemon.socket")
|
||||
.await
|
||||
.map_err(|e| Self::error(e))?;
|
||||
let socket_is_enabled = is_enabled("nix-daemon.socket")
|
||||
.await
|
||||
.map_err(|e| Self::error(e))?;
|
||||
let service_is_active = is_active("nix-daemon.service")
|
||||
.await
|
||||
.map_err(|e| Self::error(e))?;
|
||||
let service_is_enabled = is_enabled("nix-daemon.service")
|
||||
.await
|
||||
.map_err(|e| Self::error(e))?;
|
||||
|
||||
if socket_is_active {
|
||||
execute_command(
|
||||
if let Err(err) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.args(["stop", "nix-daemon.socket"])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if socket_is_enabled {
|
||||
execute_command(
|
||||
if let Err(err) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.args(["disable", "nix-daemon.socket"])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if service_is_active {
|
||||
execute_command(
|
||||
if let Err(err) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.args(["stop", "nix-daemon.service"])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if service_is_enabled {
|
||||
execute_command(
|
||||
if let Err(err) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.args(["disable", "nix-daemon.service"])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
execute_command(
|
||||
if let Err(err) = execute_command(
|
||||
Command::new("systemd-tmpfiles")
|
||||
.process_group(0)
|
||||
.arg("--remove")
|
||||
.arg("--prefix=/nix/var/nix")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
|
||||
if self.ssl_cert_file.is_some() {
|
||||
let service_conf_dir_path = PathBuf::from(format!("{SERVICE_DEST}.d"));
|
||||
tokio::fs::remove_dir_all(&service_conf_dir_path)
|
||||
if let Err(err) = tokio::fs::remove_dir_all(&service_conf_dir_path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(service_conf_dir_path.clone(), e))?;
|
||||
.map_err(|e| ActionErrorKind::Remove(service_conf_dir_path.clone(), e))
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
tokio::fs::remove_file(TMPFILES_DEST)
|
||||
if let Err(err) = tokio::fs::remove_file(TMPFILES_DEST)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(PathBuf::from(TMPFILES_DEST), e))?;
|
||||
.map_err(|e| ActionErrorKind::Remove(PathBuf::from(TMPFILES_DEST), e))
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
|
||||
execute_command(
|
||||
if let Err(err) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.arg("daemon-reload")
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
errors.push(err);
|
||||
}
|
||||
},
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
InitSystem::None => {
|
||||
|
@ -467,7 +533,18 @@ impl Action for ConfigureInitService {
|
|||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(Self::error(
|
||||
errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"),
|
||||
))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::Multiple(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,25 +556,25 @@ pub enum ConfigureNixDaemonServiceError {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn stop(unit: &str) -> Result<(), ActionError> {
|
||||
async fn stop(unit: &str) -> Result<(), ActionErrorKind> {
|
||||
let mut command = Command::new("systemctl");
|
||||
command.arg("stop");
|
||||
command.arg(unit);
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))?;
|
||||
match output.status.success() {
|
||||
true => {
|
||||
tracing::trace!(%unit, "Stopped");
|
||||
Ok(())
|
||||
},
|
||||
false => Err(ActionError::command_output(&command, output)),
|
||||
false => Err(ActionErrorKind::command_output(&command, output)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn enable(unit: &str, now: bool) -> Result<(), ActionError> {
|
||||
async fn enable(unit: &str, now: bool) -> Result<(), ActionErrorKind> {
|
||||
let mut command = Command::new("systemctl");
|
||||
command.arg("enable");
|
||||
command.arg(unit);
|
||||
|
@ -507,18 +584,18 @@ async fn enable(unit: &str, now: bool) -> Result<(), ActionError> {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))?;
|
||||
match output.status.success() {
|
||||
true => {
|
||||
tracing::trace!(%unit, %now, "Enabled unit");
|
||||
Ok(())
|
||||
},
|
||||
false => Err(ActionError::command_output(&command, output)),
|
||||
false => Err(ActionErrorKind::command_output(&command, output)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn disable(unit: &str, now: bool) -> Result<(), ActionError> {
|
||||
async fn disable(unit: &str, now: bool) -> Result<(), ActionErrorKind> {
|
||||
let mut command = Command::new("systemctl");
|
||||
command.arg("disable");
|
||||
command.arg(unit);
|
||||
|
@ -528,25 +605,25 @@ async fn disable(unit: &str, now: bool) -> Result<(), ActionError> {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))?;
|
||||
match output.status.success() {
|
||||
true => {
|
||||
tracing::trace!(%unit, %now, "Disabled unit");
|
||||
Ok(())
|
||||
},
|
||||
false => Err(ActionError::command_output(&command, output)),
|
||||
false => Err(ActionErrorKind::command_output(&command, output)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn is_active(unit: &str) -> Result<bool, ActionError> {
|
||||
async fn is_active(unit: &str) -> Result<bool, ActionErrorKind> {
|
||||
let mut command = Command::new("systemctl");
|
||||
command.arg("is-active");
|
||||
command.arg(unit);
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))?;
|
||||
if String::from_utf8(output.stdout)?.starts_with("active") {
|
||||
tracing::trace!(%unit, "Is active");
|
||||
Ok(true)
|
||||
|
@ -557,14 +634,14 @@ async fn is_active(unit: &str) -> Result<bool, ActionError> {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
async fn is_enabled(unit: &str) -> Result<bool, ActionError> {
|
||||
async fn is_enabled(unit: &str) -> Result<bool, ActionErrorKind> {
|
||||
let mut command = Command::new("systemctl");
|
||||
command.arg("is-enabled");
|
||||
command.arg(unit);
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))?;
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
if stdout.starts_with("enabled") || stdout.starts_with("linked") {
|
||||
tracing::trace!(%unit, "Is enabled");
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
action::{
|
||||
base::SetupDefaultProfile,
|
||||
common::{ConfigureShellProfile, PlaceNixConfiguration},
|
||||
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
},
|
||||
planner::ShellProfileLocations,
|
||||
settings::{CommonSettings, SCRATCH_DIR},
|
||||
|
@ -30,7 +30,7 @@ impl ConfigureNix {
|
|||
) -> Result<StatefulAction<Self>, ActionError> {
|
||||
let setup_default_profile = SetupDefaultProfile::plan(PathBuf::from(SCRATCH_DIR))
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(SetupDefaultProfile::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let configure_shell_profile = if settings.modify_profile {
|
||||
Some(
|
||||
|
@ -39,9 +39,7 @@ impl ConfigureNix {
|
|||
settings.ssl_cert_file.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(ConfigureShellProfile::action_tag(), Box::new(e))
|
||||
})?,
|
||||
.map_err(Self::error)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
|
@ -52,7 +50,7 @@ impl ConfigureNix {
|
|||
settings.force,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(PlaceNixConfiguration::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(Self {
|
||||
place_nix_configuration,
|
||||
|
@ -112,27 +110,21 @@ impl Action for ConfigureNix {
|
|||
.try_execute()
|
||||
.instrument(setup_default_profile_span)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(setup_default_profile.action_tag(), Box::new(e))
|
||||
})
|
||||
.map_err(Self::error)
|
||||
},
|
||||
async move {
|
||||
place_nix_configuration
|
||||
.try_execute()
|
||||
.instrument(place_nix_configuration_span)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(place_nix_configuration.action_tag(), Box::new(e))
|
||||
})
|
||||
.map_err(Self::error)
|
||||
},
|
||||
async move {
|
||||
configure_shell_profile
|
||||
.try_execute()
|
||||
.instrument(configure_shell_profile_span)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(configure_shell_profile.action_tag(), Box::new(e))
|
||||
})
|
||||
.map_err(Self::error)
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
|
@ -144,18 +136,14 @@ impl Action for ConfigureNix {
|
|||
.try_execute()
|
||||
.instrument(setup_default_profile_span)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(setup_default_profile.action_tag(), Box::new(e))
|
||||
})
|
||||
.map_err(Self::error)
|
||||
},
|
||||
async move {
|
||||
place_nix_configuration
|
||||
.try_execute()
|
||||
.instrument(place_nix_configuration_span)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(place_nix_configuration.action_tag(), Box::new(e))
|
||||
})
|
||||
.map_err(Self::error)
|
||||
},
|
||||
)?;
|
||||
};
|
||||
|
@ -182,21 +170,28 @@ impl Action for ConfigureNix {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let mut errors = vec![];
|
||||
if let Some(configure_shell_profile) = &mut self.configure_shell_profile {
|
||||
configure_shell_profile.try_revert().await.map_err(|e| {
|
||||
ActionError::Child(configure_shell_profile.action_tag(), Box::new(e))
|
||||
})?;
|
||||
if let Err(err) = configure_shell_profile.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
if let Err(err) = self.place_nix_configuration.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
if let Err(err) = self.setup_default_profile.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
self.place_nix_configuration
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.place_nix_configuration.action_tag(), Box::new(e))
|
||||
})?;
|
||||
self.setup_default_profile.try_revert().await.map_err(|e| {
|
||||
ActionError::Child(self.setup_default_profile.action_tag(), Box::new(e))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use crate::action::base::{create_or_insert_into_file, CreateDirectory, CreateOrInsertIntoFile};
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
use crate::planner::ShellProfileLocations;
|
||||
|
||||
use nix::unistd::User;
|
||||
|
@ -32,9 +34,9 @@ impl ConfigureShellProfile {
|
|||
let maybe_ssl_cert_file_setting = if let Some(ssl_cert_file) = ssl_cert_file {
|
||||
format!(
|
||||
"export NIX_SSL_CERT_FILE={:?}\n",
|
||||
ssl_cert_file
|
||||
.canonicalize()
|
||||
.map_err(|e| { ActionError::Canonicalize(ssl_cert_file, e) })?
|
||||
ssl_cert_file.canonicalize().map_err(|e| {
|
||||
Self::error(ActionErrorKind::Canonicalize(ssl_cert_file, e))
|
||||
})?
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
|
@ -207,7 +209,7 @@ impl Action for ConfigureShellProfile {
|
|||
}
|
||||
|
||||
let mut set = JoinSet::new();
|
||||
let mut errors = Vec::default();
|
||||
let mut errors = vec![];
|
||||
|
||||
for (idx, create_or_insert_into_file) in
|
||||
self.create_or_insert_into_files.iter_mut().enumerate()
|
||||
|
@ -219,12 +221,7 @@ impl Action for ConfigureShellProfile {
|
|||
.try_execute()
|
||||
.instrument(span)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(
|
||||
create_or_insert_into_file_clone.action_tag(),
|
||||
Box::new(e),
|
||||
)
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
Result::<_, ActionError>::Ok((idx, create_or_insert_into_file_clone))
|
||||
});
|
||||
}
|
||||
|
@ -235,17 +232,17 @@ impl Action for ConfigureShellProfile {
|
|||
self.create_or_insert_into_files[idx] = create_or_insert_into_file
|
||||
},
|
||||
Ok(Err(e)) => errors.push(e),
|
||||
Err(e) => return Err(e)?,
|
||||
Err(e) => return Err(Self::error(e))?,
|
||||
};
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
if errors.len() == 1 {
|
||||
return Err(errors.into_iter().next().unwrap())?;
|
||||
return Err(Self::error(errors.into_iter().next().unwrap()))?;
|
||||
} else {
|
||||
return Err(ActionError::Children(
|
||||
errors.into_iter().map(|v| Box::new(v)).collect(),
|
||||
));
|
||||
return Err(Self::error(ActionErrorKind::MultipleChildren(
|
||||
errors.into_iter().collect(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,7 +259,7 @@ impl Action for ConfigureShellProfile {
|
|||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let mut set = JoinSet::new();
|
||||
let mut errors: Vec<ActionError> = Vec::default();
|
||||
let mut errors = vec![];
|
||||
|
||||
for (idx, create_or_insert_into_file) in
|
||||
self.create_or_insert_into_files.iter_mut().enumerate()
|
||||
|
@ -280,27 +277,26 @@ impl Action for ConfigureShellProfile {
|
|||
self.create_or_insert_into_files[idx] = create_or_insert_into_file
|
||||
},
|
||||
Ok(Err(e)) => errors.push(e),
|
||||
Err(e) => return Err(e)?,
|
||||
// This is quite rare and generally a very bad sign.
|
||||
Err(e) => return Err(e).map_err(|e| Self::error(ActionErrorKind::from(e)))?,
|
||||
};
|
||||
}
|
||||
|
||||
for create_directory in self.create_directories.iter_mut() {
|
||||
create_directory
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_directory.action_tag(), Box::new(e)))?;
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
if errors.len() == 1 {
|
||||
return Err(errors.into_iter().next().unwrap())?;
|
||||
} else {
|
||||
return Err(ActionError::Children(
|
||||
errors.into_iter().map(|v| Box::new(v)).collect(),
|
||||
));
|
||||
if let Err(err) = create_directory.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::base::CreateDirectory;
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
|
||||
const PATHS: &[&str] = &[
|
||||
"/nix/var",
|
||||
|
@ -36,7 +38,7 @@ impl CreateNixTree {
|
|||
create_directories.push(
|
||||
CreateDirectory::plan(path, String::from("root"), None, 0o0755, false)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateDirectory::action_tag(), Box::new(e)))?,
|
||||
.map_err(Self::error)?,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -77,10 +79,7 @@ impl Action for CreateNixTree {
|
|||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
// Just do sequential since parallelizing this will have little benefit
|
||||
for create_directory in self.create_directories.iter_mut() {
|
||||
create_directory
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_directory.action_tag(), Box::new(e)))?
|
||||
create_directory.try_execute().await.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -108,14 +107,23 @@ impl Action for CreateNixTree {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let mut errors = vec![];
|
||||
// Just do sequential since parallelizing this will have little benefit
|
||||
for create_directory in self.create_directories.iter_mut().rev() {
|
||||
create_directory
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_directory.action_tag(), Box::new(e)))?
|
||||
if let Err(err) = create_directory.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
action::{
|
||||
base::{AddUserToGroup, CreateGroup, CreateUser},
|
||||
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
},
|
||||
settings::CommonSettings,
|
||||
};
|
||||
|
@ -38,7 +38,7 @@ impl CreateUsersAndGroups {
|
|||
format!("Nix build user {index}"),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateUser::action_tag(), Box::new(e)))?,
|
||||
.map_err(Self::error)?,
|
||||
);
|
||||
add_users_to_groups.push(
|
||||
AddUserToGroup::plan(
|
||||
|
@ -48,7 +48,7 @@ impl CreateUsersAndGroups {
|
|||
settings.nix_build_group_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(AddUserToGroup::action_tag(), Box::new(e)))?,
|
||||
.map_err(Self::error)?,
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
|
@ -156,18 +156,12 @@ impl Action for CreateUsersAndGroups {
|
|||
}
|
||||
| OperatingSystem::Darwin => {
|
||||
for create_user in create_users.iter_mut() {
|
||||
create_user
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_user.action_tag(), Box::new(e)))?;
|
||||
create_user.try_execute().await.map_err(Self::error)?;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
for create_user in create_users.iter_mut() {
|
||||
create_user
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_user.action_tag(), Box::new(e)))?;
|
||||
create_user.try_execute().await.map_err(Self::error)?;
|
||||
}
|
||||
// While we may be tempted to do something like this, it can break on many older OSes like Ubuntu 18.04:
|
||||
// ```
|
||||
|
@ -190,7 +184,7 @@ impl Action for CreateUsersAndGroups {
|
|||
// match result {
|
||||
// Ok(Ok((idx, success))) => create_users[idx] = success,
|
||||
// Ok(Err(e)) => errors.push(Box::new(e)),
|
||||
// Err(e) => return Err(ActionError::Join(e))?,
|
||||
// Err(e) => return Err(ActionErrorKind::Join(e))?,
|
||||
// };
|
||||
// }
|
||||
|
||||
|
@ -198,17 +192,14 @@ impl Action for CreateUsersAndGroups {
|
|||
// if errors.len() == 1 {
|
||||
// return Err(errors.into_iter().next().unwrap().into());
|
||||
// } else {
|
||||
// return Err(ActionError::Children(errors));
|
||||
// return Err(ActionErrorKind::Children(errors));
|
||||
// }
|
||||
// }
|
||||
},
|
||||
};
|
||||
|
||||
for add_user_to_group in add_users_to_groups.iter_mut() {
|
||||
add_user_to_group
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(add_user_to_group.action_tag(), Box::new(e)))?;
|
||||
add_user_to_group.try_execute().await.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -256,21 +247,11 @@ impl Action for CreateUsersAndGroups {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self {
|
||||
create_users,
|
||||
create_group,
|
||||
add_users_to_groups: _,
|
||||
nix_build_user_count: _,
|
||||
nix_build_group_name: _,
|
||||
nix_build_group_id: _,
|
||||
nix_build_user_prefix: _,
|
||||
nix_build_user_id_base: _,
|
||||
} = self;
|
||||
for create_user in create_users.iter_mut() {
|
||||
create_user
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_user.action_tag(), Box::new(e)))?;
|
||||
let mut errors = vec![];
|
||||
for create_user in self.create_users.iter_mut() {
|
||||
if let Err(err) = create_user.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't actually need to do this, when a user is deleted they are removed from groups
|
||||
|
@ -279,11 +260,19 @@ impl Action for CreateUsersAndGroups {
|
|||
// }
|
||||
|
||||
// Create group
|
||||
create_group
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(create_group.action_tag(), Box::new(e)))?;
|
||||
if let Err(err) = self.create_group.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ use tracing::{span, Span};
|
|||
|
||||
use crate::action::base::create_or_merge_nix_config::CreateOrMergeNixConfigError;
|
||||
use crate::action::base::{CreateDirectory, CreateOrMergeNixConfig};
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
|
||||
const NIX_CONF_FOLDER: &str = "/etc/nix";
|
||||
const NIX_CONF: &str = "/etc/nix/nix.conf";
|
||||
|
@ -26,7 +28,7 @@ impl PlaceNixConfiguration {
|
|||
let extra_conf = extra_conf.join("\n");
|
||||
let mut nix_config = nix_config_parser::NixConfig::parse_string(extra_conf, None)
|
||||
.map_err(CreateOrMergeNixConfigError::ParseNixConfig)
|
||||
.map_err(|e| ActionError::Custom(Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
let settings = nix_config.settings_mut();
|
||||
|
||||
settings.insert("build-users-group".to_string(), nix_build_group_name);
|
||||
|
@ -46,12 +48,10 @@ impl PlaceNixConfiguration {
|
|||
|
||||
let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateDirectory::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
let create_or_merge_nix_config = CreateOrMergeNixConfig::plan(NIX_CONF, nix_config)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(CreateOrMergeNixConfig::action_tag(), Box::new(e))
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
Ok(Self {
|
||||
create_directory,
|
||||
create_or_merge_nix_config,
|
||||
|
@ -100,13 +100,11 @@ impl Action for PlaceNixConfiguration {
|
|||
self.create_directory
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_directory.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
self.create_or_merge_nix_config
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.create_or_merge_nix_config.action_tag(), Box::new(e))
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -123,17 +121,23 @@ impl Action for PlaceNixConfiguration {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
self.create_or_merge_nix_config
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.create_or_merge_nix_config.action_tag(), Box::new(e))
|
||||
})?;
|
||||
self.create_directory
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_directory.action_tag(), Box::new(e)))?;
|
||||
let mut errors = vec![];
|
||||
if let Err(err) = self.create_or_merge_nix_config.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
if let Err(err) = self.create_directory.try_revert().await {
|
||||
errors.push(err);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use super::{CreateNixTree, CreateUsersAndGroups};
|
|||
use crate::{
|
||||
action::{
|
||||
base::{FetchAndUnpackNix, MoveUnpackedNix},
|
||||
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
},
|
||||
settings::{CommonSettings, SCRATCH_DIR},
|
||||
};
|
||||
|
@ -33,13 +33,11 @@ impl ProvisionNix {
|
|||
.await?;
|
||||
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone())
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateUsersAndGroups::action_tag(), Box::new(e)))?;
|
||||
let create_nix_tree = CreateNixTree::plan()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateNixTree::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
let create_nix_tree = CreateNixTree::plan().await.map_err(Self::error)?;
|
||||
let move_unpacked_nix = MoveUnpackedNix::plan(PathBuf::from(SCRATCH_DIR))
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(MoveUnpackedNix::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
Ok(Self {
|
||||
fetch_nix,
|
||||
create_users_and_group,
|
||||
|
@ -86,29 +84,27 @@ impl Action for ProvisionNix {
|
|||
// We fetch nix while doing the rest, then move it over.
|
||||
let mut fetch_nix_clone = self.fetch_nix.clone();
|
||||
let fetch_nix_handle = tokio::task::spawn(async {
|
||||
fetch_nix_clone
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(fetch_nix_clone.action_tag(), Box::new(e)))?;
|
||||
fetch_nix_clone.try_execute().await.map_err(Self::error)?;
|
||||
Result::<_, ActionError>::Ok(fetch_nix_clone)
|
||||
});
|
||||
|
||||
self.create_users_and_group
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.create_users_and_group.action_tag(), Box::new(e))
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
self.create_nix_tree
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_nix_tree.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
self.fetch_nix = fetch_nix_handle.await.map_err(ActionError::Join)??;
|
||||
self.fetch_nix = fetch_nix_handle
|
||||
.await
|
||||
.map_err(ActionErrorKind::Join)
|
||||
.map_err(Self::error)??;
|
||||
self.move_unpacked_nix
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.move_unpacked_nix.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -131,34 +127,32 @@ impl Action for ProvisionNix {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
// We fetch nix while doing the rest, then move it over.
|
||||
let mut fetch_nix_clone = self.fetch_nix.clone();
|
||||
let fetch_nix_handle = tokio::task::spawn(async {
|
||||
fetch_nix_clone
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(fetch_nix_clone.action_tag(), Box::new(e)))?;
|
||||
Result::<_, ActionError>::Ok(fetch_nix_clone)
|
||||
});
|
||||
let mut errors = vec![];
|
||||
|
||||
if let Err(err) = self.fetch_nix.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
|
||||
if let Err(err) = self.create_users_and_group.try_revert().await {
|
||||
fetch_nix_handle.abort();
|
||||
return Err(err);
|
||||
errors.push(err)
|
||||
}
|
||||
if let Err(err) = self.create_nix_tree.try_revert().await {
|
||||
fetch_nix_handle.abort();
|
||||
return Err(err);
|
||||
errors.push(err)
|
||||
}
|
||||
|
||||
self.fetch_nix = fetch_nix_handle
|
||||
.await
|
||||
.map_err(ActionError::Join)?
|
||||
.map_err(|e| ActionError::Child(self.fetch_nix.action_tag(), Box::new(e)))?;
|
||||
self.move_unpacked_nix
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.move_unpacked_nix.action_tag(), Box::new(e)))?;
|
||||
if let Err(err) = self.move_unpacked_nix.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionState, ActionTag, StatefulAction};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionState, ActionTag, StatefulAction};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription};
|
||||
|
@ -28,7 +28,7 @@ impl StartSystemdUnit {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?;
|
||||
|
||||
let state = if output.status.success() {
|
||||
tracing::debug!("Starting systemd unit `{}` already complete", unit);
|
||||
|
@ -84,7 +84,8 @@ impl Action for StartSystemdUnit {
|
|||
.arg(format!("{unit}"))
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
false => {
|
||||
// TODO(@Hoverbear): Handle proxy vars
|
||||
|
@ -95,7 +96,8 @@ impl Action for StartSystemdUnit {
|
|||
.arg(format!("{unit}"))
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -111,30 +113,47 @@ impl Action for StartSystemdUnit {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self { unit, enable } = self;
|
||||
let mut errors = vec![];
|
||||
|
||||
if *enable {
|
||||
execute_command(
|
||||
if self.enable {
|
||||
if let Err(e) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.arg("disable")
|
||||
.arg(format!("{unit}"))
|
||||
.arg(format!("{}", self.unit))
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)
|
||||
{
|
||||
errors.push(e);
|
||||
}
|
||||
};
|
||||
|
||||
// We do both to avoid an error doing `disable --now` if the user did stop it already somehow.
|
||||
execute_command(
|
||||
if let Err(e) = execute_command(
|
||||
Command::new("systemctl")
|
||||
.process_group(0)
|
||||
.arg("stop")
|
||||
.arg(format!("{unit}"))
|
||||
.arg(format!("{}", self.unit))
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)
|
||||
{
|
||||
errors.push(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription};
|
||||
|
@ -40,7 +40,7 @@ impl BootstrapLaunchctlService {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?;
|
||||
if output.status.success() || output.status.code() == Some(37) {
|
||||
// We presume that success means it's found
|
||||
return Ok(StatefulAction::completed(Self {
|
||||
|
@ -102,7 +102,8 @@ impl Action for BootstrapLaunchctlService {
|
|||
.arg(path)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -120,21 +121,16 @@ impl Action for BootstrapLaunchctlService {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self {
|
||||
path,
|
||||
service: _,
|
||||
domain,
|
||||
} = self;
|
||||
|
||||
execute_command(
|
||||
Command::new("launchctl")
|
||||
.process_group(0)
|
||||
.arg("bootout")
|
||||
.arg(domain)
|
||||
.arg(path)
|
||||
.arg(&self.domain)
|
||||
.arg(&self.path)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -25,9 +25,11 @@ impl CreateApfsVolume {
|
|||
) -> Result<StatefulAction<Self>, ActionError> {
|
||||
let output =
|
||||
execute_command(Command::new("/usr/sbin/diskutil").args(["apfs", "list", "-plist"]))
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let parsed: DiskUtilApfsListOutput = plist::from_bytes(&output.stdout)?;
|
||||
let parsed: DiskUtilApfsListOutput =
|
||||
plist::from_bytes(&output.stdout).map_err(Self::error)?;
|
||||
for container in parsed.containers {
|
||||
for volume in container.volumes {
|
||||
if volume.name == name {
|
||||
|
@ -101,7 +103,8 @@ impl Action for CreateApfsVolume {
|
|||
])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -119,19 +122,14 @@ impl Action for CreateApfsVolume {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self {
|
||||
disk: _,
|
||||
name,
|
||||
case_sensitive: _,
|
||||
} = self;
|
||||
|
||||
execute_command(
|
||||
Command::new("/usr/sbin/diskutil")
|
||||
.process_group(0)
|
||||
.args(["apfs", "deleteVolume", name])
|
||||
.args(["apfs", "deleteVolume", &self.name])
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use uuid::Uuid;
|
|||
|
||||
use super::{get_uuid_for_label, CreateApfsVolume};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionState, ActionTag, StatefulAction,
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionState, ActionTag, StatefulAction,
|
||||
};
|
||||
use std::{io::SeekFrom, path::Path};
|
||||
use tokio::{
|
||||
|
@ -46,7 +46,7 @@ impl CreateFstabEntry {
|
|||
if fstab_path.exists() {
|
||||
let fstab_buf = tokio::fs::read_to_string(&fstab_path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(fstab_path.to_path_buf(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Read(fstab_path.to_path_buf(), e)))?;
|
||||
let prelude_comment = fstab_prelude_comment(&apfs_volume_label);
|
||||
|
||||
// See if a previous install from this crate exists, if so, invite the user to remove it (we may need to change it)
|
||||
|
@ -122,7 +122,9 @@ impl Action for CreateFstabEntry {
|
|||
existing_entry,
|
||||
} = self;
|
||||
let fstab_path = Path::new(FSTAB_PATH);
|
||||
let uuid = get_uuid_for_label(&apfs_volume_label).await?;
|
||||
let uuid = get_uuid_for_label(&apfs_volume_label)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let mut fstab = tokio::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
|
@ -130,14 +132,14 @@ impl Action for CreateFstabEntry {
|
|||
.read(true)
|
||||
.open(fstab_path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(fstab_path.to_path_buf(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Open(fstab_path.to_path_buf(), e)))?;
|
||||
|
||||
// Make sure it doesn't already exist before we write to it.
|
||||
let mut fstab_buf = String::new();
|
||||
fstab
|
||||
.read_to_string(&mut fstab_buf)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Read(fstab_path.to_owned(), e)))?;
|
||||
|
||||
let updated_buf = match existing_entry {
|
||||
ExistingFstabEntry::NixInstallerEntry => {
|
||||
|
@ -161,9 +163,9 @@ impl Action for CreateFstabEntry {
|
|||
}
|
||||
}
|
||||
if !(saw_prelude && updated_line) {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
return Err(Self::error(
|
||||
CreateFstabEntryError::ExistingNixInstallerEntryDisappeared,
|
||||
)));
|
||||
));
|
||||
}
|
||||
current_fstab_lines.join("\n")
|
||||
},
|
||||
|
@ -182,9 +184,9 @@ impl Action for CreateFstabEntry {
|
|||
}
|
||||
}
|
||||
if !updated_line {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
return Err(Self::error(
|
||||
CreateFstabEntryError::ExistingForeignEntryDisappeared,
|
||||
)));
|
||||
));
|
||||
}
|
||||
current_fstab_lines.join("\n")
|
||||
},
|
||||
|
@ -194,15 +196,15 @@ impl Action for CreateFstabEntry {
|
|||
fstab
|
||||
.seek(SeekFrom::Start(0))
|
||||
.await
|
||||
.map_err(|e| ActionError::Seek(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Seek(fstab_path.to_owned(), e)))?;
|
||||
fstab
|
||||
.set_len(0)
|
||||
.await
|
||||
.map_err(|e| ActionError::Truncate(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Truncate(fstab_path.to_owned(), e)))?;
|
||||
fstab
|
||||
.write_all(updated_buf.as_bytes())
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Write(fstab_path.to_owned(), e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -223,13 +225,13 @@ impl Action for CreateFstabEntry {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self {
|
||||
apfs_volume_label,
|
||||
existing_entry: _,
|
||||
} = self;
|
||||
let fstab_path = Path::new(FSTAB_PATH);
|
||||
let uuid = get_uuid_for_label(&apfs_volume_label).await?;
|
||||
let fstab_entry = fstab_lines(&uuid, apfs_volume_label);
|
||||
|
||||
let uuid = get_uuid_for_label(&self.apfs_volume_label)
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let fstab_entry = fstab_lines(&uuid, &self.apfs_volume_label);
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(false)
|
||||
|
@ -237,12 +239,12 @@ impl Action for CreateFstabEntry {
|
|||
.read(true)
|
||||
.open(&fstab_path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Open(fstab_path.to_owned(), e)))?;
|
||||
|
||||
let mut file_contents = String::default();
|
||||
file.read_to_string(&mut file_contents)
|
||||
.await
|
||||
.map_err(|e| ActionError::Read(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Read(fstab_path.to_owned(), e)))?;
|
||||
|
||||
if let Some(start) = file_contents.rfind(fstab_entry.as_str()) {
|
||||
let end = start + fstab_entry.len();
|
||||
|
@ -251,16 +253,16 @@ impl Action for CreateFstabEntry {
|
|||
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.await
|
||||
.map_err(|e| ActionError::Seek(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Seek(fstab_path.to_owned(), e)))?;
|
||||
file.set_len(0)
|
||||
.await
|
||||
.map_err(|e| ActionError::Truncate(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Truncate(fstab_path.to_owned(), e)))?;
|
||||
file.write_all(file_contents.as_bytes())
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Write(fstab_path.to_owned(), e)))?;
|
||||
file.flush()
|
||||
.await
|
||||
.map_err(|e| ActionError::Flush(fstab_path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Flush(fstab_path.to_owned(), e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -288,3 +290,9 @@ pub enum CreateFstabEntryError {
|
|||
#[error("The `/etc/fstab` entry (previously created by the official install scripts) detected during planning disappeared between planning and executing. Cannot update `/etc/fstab` as planned")]
|
||||
ExistingForeignEntryDisappeared,
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for CreateFstabEntryError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::action::{
|
|||
BootstrapLaunchctlService, CreateApfsVolume, CreateSyntheticObjects, EnableOwnership,
|
||||
EncryptApfsVolume, UnmountApfsVolume,
|
||||
},
|
||||
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
|
@ -54,23 +54,21 @@ impl CreateNixVolume {
|
|||
create_or_insert_into_file::Position::End,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateOrInsertIntoFile::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let create_synthetic_objects = CreateSyntheticObjects::plan()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateSyntheticObjects::action_tag(), Box::new(e)))?;
|
||||
let create_synthetic_objects = CreateSyntheticObjects::plan().await.map_err(Self::error)?;
|
||||
|
||||
let unmount_volume = UnmountApfsVolume::plan(disk, name.clone())
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(UnmountApfsVolume::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let create_volume = CreateApfsVolume::plan(disk, name.clone(), case_sensitive)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateApfsVolume::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let create_fstab_entry = CreateFstabEntry::plan(name.clone(), &create_volume)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateFstabEntry::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let encrypt_volume = if encrypt {
|
||||
Some(EncryptApfsVolume::plan(disk, &name, &create_volume).await?)
|
||||
|
@ -86,7 +84,7 @@ impl CreateNixVolume {
|
|||
encrypt,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(CreateVolumeService::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let bootstrap_volume = BootstrapLaunchctlService::plan(
|
||||
"system",
|
||||
|
@ -94,14 +92,12 @@ impl CreateNixVolume {
|
|||
NIX_VOLUME_MOUNTD_DEST,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(BootstrapLaunchctlService::action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
let kickstart_launchctl_service =
|
||||
KickstartLaunchctlService::plan("system", "org.nixos.darwin-store")
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(KickstartLaunchctlService::action_tag(), Box::new(e))
|
||||
})?;
|
||||
let enable_ownership = EnableOwnership::plan("/nix").await?;
|
||||
.map_err(Self::error)?;
|
||||
let enable_ownership = EnableOwnership::plan("/nix").await.map_err(Self::error)?;
|
||||
|
||||
Ok(Self {
|
||||
disk: disk.to_path_buf(),
|
||||
|
@ -172,23 +168,16 @@ impl Action for CreateNixVolume {
|
|||
self.create_or_append_synthetic_conf
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(
|
||||
self.create_or_append_synthetic_conf.action_tag(),
|
||||
Box::new(e),
|
||||
)
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
self.create_synthetic_objects
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.create_synthetic_objects.action_tag(), Box::new(e))
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
self.unmount_volume.try_execute().await.ok(); // We actually expect this may fail.
|
||||
self.create_volume
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_volume.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let mut retry_tokens: usize = 50;
|
||||
loop {
|
||||
|
@ -201,11 +190,14 @@ impl Action for CreateNixVolume {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))
|
||||
.map_err(Self::error)?;
|
||||
if output.status.success() {
|
||||
break;
|
||||
} else if retry_tokens == 0 {
|
||||
return Err(ActionError::command_output(&command, output));
|
||||
return Err(Self::error(ActionErrorKind::command_output(
|
||||
&command, output,
|
||||
)));
|
||||
} else {
|
||||
retry_tokens = retry_tokens.saturating_sub(1);
|
||||
}
|
||||
|
@ -215,28 +207,23 @@ impl Action for CreateNixVolume {
|
|||
self.create_fstab_entry
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_fstab_entry.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
if let Some(encrypt_volume) = &mut self.encrypt_volume {
|
||||
encrypt_volume
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(encrypt_volume.action_tag(), Box::new(e)))?
|
||||
encrypt_volume.try_execute().await.map_err(Self::error)?
|
||||
}
|
||||
self.setup_volume_daemon
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.setup_volume_daemon.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
self.bootstrap_volume
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.bootstrap_volume.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
self.kickstart_launchctl_service
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.kickstart_launchctl_service.action_tag(), Box::new(e))
|
||||
})?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let mut retry_tokens: usize = 50;
|
||||
loop {
|
||||
|
@ -248,11 +235,14 @@ impl Action for CreateNixVolume {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(&command, e))
|
||||
.map_err(Self::error)?;
|
||||
if output.status.success() {
|
||||
break;
|
||||
} else if retry_tokens == 0 {
|
||||
return Err(ActionError::command_output(&command, output));
|
||||
return Err(Self::error(ActionErrorKind::command_output(
|
||||
&command, output,
|
||||
)));
|
||||
} else {
|
||||
retry_tokens = retry_tokens.saturating_sub(1);
|
||||
}
|
||||
|
@ -262,7 +252,7 @@ impl Action for CreateNixVolume {
|
|||
self.enable_ownership
|
||||
.try_execute()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.enable_ownership.action_tag(), Box::new(e)))?;
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -296,44 +286,53 @@ impl Action for CreateNixVolume {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
self.enable_ownership.try_revert().await?;
|
||||
self.kickstart_launchctl_service.try_revert().await?;
|
||||
self.bootstrap_volume.try_revert().await?;
|
||||
self.setup_volume_daemon.try_revert().await?;
|
||||
if let Some(encrypt_volume) = &mut self.encrypt_volume {
|
||||
encrypt_volume.try_revert().await?;
|
||||
}
|
||||
self.create_fstab_entry
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_fstab_entry.action_tag(), Box::new(e)))?;
|
||||
let mut errors = vec![];
|
||||
|
||||
self.unmount_volume
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.unmount_volume.action_tag(), Box::new(e)))?;
|
||||
self.create_volume
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| ActionError::Child(self.create_volume.action_tag(), Box::new(e)))?;
|
||||
if let Err(err) = self.enable_ownership.try_revert().await {
|
||||
errors.push(err)
|
||||
};
|
||||
if let Err(err) = self.kickstart_launchctl_service.try_revert().await {
|
||||
errors.push(err)
|
||||
};
|
||||
if let Err(err) = self.bootstrap_volume.try_revert().await {
|
||||
errors.push(err)
|
||||
};
|
||||
if let Err(err) = self.setup_volume_daemon.try_revert().await {
|
||||
errors.push(err)
|
||||
};
|
||||
if let Some(encrypt_volume) = &mut self.encrypt_volume {
|
||||
if let Err(err) = encrypt_volume.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
}
|
||||
if let Err(err) = self.create_fstab_entry.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
|
||||
if let Err(err) = self.unmount_volume.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
if let Err(err) = self.create_volume.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
|
||||
// Purposefully not reversed
|
||||
self.create_or_append_synthetic_conf
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(
|
||||
self.create_or_append_synthetic_conf.action_tag(),
|
||||
Box::new(e),
|
||||
)
|
||||
})?;
|
||||
self.create_synthetic_objects
|
||||
.try_revert()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ActionError::Child(self.create_synthetic_objects.action_tag(), Box::new(e))
|
||||
})?;
|
||||
if let Err(err) = self.create_or_append_synthetic_conf.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
if let Err(err) = self.create_synthetic_objects.try_revert().await {
|
||||
errors.push(err)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else if errors.len() == 1 {
|
||||
Err(errors
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Expected 1 len Vec to have at least 1 item"))
|
||||
} else {
|
||||
Err(Self::error(ActionErrorKind::MultipleChildren(errors)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,10 +90,3 @@ impl Action for CreateSyntheticObjects {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CreateSyntheticObjectsError {
|
||||
#[error("Failed to execute command")]
|
||||
Command(#[source] std::io::Error),
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ use tokio::{
|
|||
io::AsyncWriteExt,
|
||||
};
|
||||
|
||||
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
|
||||
use super::get_uuid_for_label;
|
||||
|
||||
|
@ -43,27 +45,27 @@ impl CreateVolumeService {
|
|||
};
|
||||
|
||||
if this.path.exists() {
|
||||
let discovered_plist: LaunchctlMountPlist = plist::from_file(&this.path)?;
|
||||
let discovered_plist: LaunchctlMountPlist =
|
||||
plist::from_file(&this.path).map_err(Self::error)?;
|
||||
let expected_plist = generate_mount_plist(
|
||||
&this.mount_service_label,
|
||||
&this.apfs_volume_label,
|
||||
&this.mount_point,
|
||||
encrypt,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
if discovered_plist != expected_plist {
|
||||
tracing::trace!(
|
||||
?discovered_plist,
|
||||
?expected_plist,
|
||||
"Parsed plists not equal"
|
||||
);
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
CreateVolumeServiceError::DifferentPlist {
|
||||
expected: expected_plist,
|
||||
discovered: discovered_plist,
|
||||
path: this.path.clone(),
|
||||
},
|
||||
)));
|
||||
return Err(Self::error(CreateVolumeServiceError::DifferentPlist {
|
||||
expected: expected_plist,
|
||||
discovered: discovered_plist,
|
||||
path: this.path.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
tracing::debug!("Creating file `{}` already complete", this.path.display());
|
||||
|
@ -121,7 +123,8 @@ impl Action for CreateVolumeService {
|
|||
mount_point,
|
||||
*encrypt,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let mut options = OpenOptions::new();
|
||||
options.create_new(true).write(true).read(true);
|
||||
|
@ -129,13 +132,13 @@ impl Action for CreateVolumeService {
|
|||
let mut file = options
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Open(path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Open(path.to_owned(), e)))?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
plist::to_writer_xml(&mut buf, &generated_plist)?;
|
||||
plist::to_writer_xml(&mut buf, &generated_plist).map_err(Self::error)?;
|
||||
file.write_all(&buf)
|
||||
.await
|
||||
.map_err(|e| ActionError::Write(path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Write(path.to_owned(), e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -151,7 +154,7 @@ impl Action for CreateVolumeService {
|
|||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
remove_file(&self.path)
|
||||
.await
|
||||
.map_err(|e| ActionError::Remove(self.path.to_owned(), e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::Remove(self.path.to_owned(), e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -163,7 +166,7 @@ async fn generate_mount_plist(
|
|||
apfs_volume_label: &str,
|
||||
mount_point: &Path,
|
||||
encrypt: bool,
|
||||
) -> Result<LaunchctlMountPlist, ActionError> {
|
||||
) -> Result<LaunchctlMountPlist, ActionErrorKind> {
|
||||
let apfs_volume_label_with_quotes = format!("\"{apfs_volume_label}\"");
|
||||
let uuid = get_uuid_for_label(&apfs_volume_label).await?;
|
||||
// The official Nix scripts uppercase the UUID, so we do as well for compatibility.
|
||||
|
@ -208,3 +211,9 @@ pub enum CreateVolumeServiceError {
|
|||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for CreateVolumeServiceError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription};
|
||||
|
@ -62,9 +62,11 @@ impl Action for EnableOwnership {
|
|||
.arg(&path)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.map_err(Self::error)?
|
||||
.stdout;
|
||||
let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf))?;
|
||||
let the_plist: DiskUtilInfoOutput =
|
||||
plist::from_reader(Cursor::new(buf)).map_err(Self::error)?;
|
||||
|
||||
the_plist.global_permissions_enabled
|
||||
};
|
||||
|
@ -77,7 +79,8 @@ impl Action for EnableOwnership {
|
|||
.arg(path)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -100,3 +103,9 @@ pub enum EnableOwnershipError {
|
|||
#[error("Failed to execute command")]
|
||||
Command(#[source] std::io::Error),
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for EnableOwnershipError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
action::{
|
||||
macos::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, ActionState,
|
||||
ActionTag, StatefulAction,
|
||||
macos::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, ActionErrorKind,
|
||||
ActionState, ActionTag, StatefulAction,
|
||||
},
|
||||
execute_command,
|
||||
os::darwin::DiskUtilApfsListOutput,
|
||||
|
@ -51,7 +51,7 @@ impl EncryptApfsVolume {
|
|||
if command
|
||||
.status()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?
|
||||
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?
|
||||
.success()
|
||||
{
|
||||
// The user has a password matching what we would create.
|
||||
|
@ -61,24 +61,26 @@ impl EncryptApfsVolume {
|
|||
}
|
||||
|
||||
// Ask the user to remove it
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
EncryptApfsVolumeError::ExistingPasswordFound(name, disk),
|
||||
return Err(Self::error(EncryptApfsVolumeError::ExistingPasswordFound(
|
||||
name, disk,
|
||||
)));
|
||||
} else {
|
||||
if planned_create_apfs_volume.state == ActionState::Completed {
|
||||
// The user has a volume already created, but a password not set. This means we probably can't decrypt the volume.
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
return Err(Self::error(
|
||||
EncryptApfsVolumeError::MissingPasswordForExistingVolume(name, disk),
|
||||
)));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure if the disk already exists, that it's encrypted
|
||||
let output =
|
||||
execute_command(Command::new("/usr/sbin/diskutil").args(["apfs", "list", "-plist"]))
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
let parsed: DiskUtilApfsListOutput = plist::from_bytes(&output.stdout)?;
|
||||
let parsed: DiskUtilApfsListOutput =
|
||||
plist::from_bytes(&output.stdout).map_err(Self::error)?;
|
||||
for container in parsed.containers {
|
||||
for volume in container.volumes {
|
||||
if volume.name == name {
|
||||
|
@ -87,9 +89,9 @@ impl EncryptApfsVolume {
|
|||
return Ok(StatefulAction::completed(Self { disk, name }));
|
||||
},
|
||||
false => {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
return Err(Self::error(
|
||||
EncryptApfsVolumeError::ExistingVolumeNotEncrypted(name, disk),
|
||||
)));
|
||||
));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +152,9 @@ impl Action for EncryptApfsVolume {
|
|||
|
||||
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
|
||||
|
||||
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name)).await?;
|
||||
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name))
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
// Add the password to the user keychain so they can unlock it later.
|
||||
execute_command(
|
||||
|
@ -180,7 +184,8 @@ impl Action for EncryptApfsVolume {
|
|||
"/Library/Keychains/System.keychain",
|
||||
]),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
// Encrypt the mounted volume
|
||||
execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([
|
||||
|
@ -192,7 +197,8 @@ impl Action for EncryptApfsVolume {
|
|||
"-passphrase",
|
||||
password.as_str(),
|
||||
]))
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
execute_command(
|
||||
Command::new("/usr/sbin/diskutil")
|
||||
|
@ -201,7 +207,8 @@ impl Action for EncryptApfsVolume {
|
|||
.arg("force")
|
||||
.arg(&name),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -220,18 +227,16 @@ impl Action for EncryptApfsVolume {
|
|||
disk = %self.disk.display(),
|
||||
))]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self { disk, name } = self;
|
||||
|
||||
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
|
||||
let disk_str = self.disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
|
||||
|
||||
// TODO: This seems very rough and unsafe
|
||||
execute_command(
|
||||
Command::new("/usr/bin/security").process_group(0).args([
|
||||
"delete-generic-password",
|
||||
"-a",
|
||||
name.as_str(),
|
||||
self.name.as_str(),
|
||||
"-s",
|
||||
name.as_str(),
|
||||
self.name.as_str(),
|
||||
"-l",
|
||||
format!("{} encryption password", disk_str).as_str(),
|
||||
"-D",
|
||||
|
@ -243,7 +248,8 @@ impl Action for EncryptApfsVolume {
|
|||
.as_str(),
|
||||
]),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -258,3 +264,9 @@ pub enum EncryptApfsVolumeError {
|
|||
#[error("The existing APFS volume \"{0}\" on disk `{1}` is not encrypted but it should be, consider removing the volume with `diskutil apfs deleteVolume \"{0}\"` (if you receive error -69888, you may need to run `launchctl bootout system/org.nixos.darwin-store` and `launchctl bootout system/org.nixos.nix-daemon` first)")]
|
||||
ExistingVolumeNotEncrypted(String, PathBuf),
|
||||
}
|
||||
|
||||
impl Into<ActionErrorKind> for EncryptApfsVolumeError {
|
||||
fn into(self) -> ActionErrorKind {
|
||||
ActionErrorKind::Custom(Box::new(self))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::process::Output;
|
|||
use tokio::process::Command;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||
use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction};
|
||||
use crate::execute_command;
|
||||
|
||||
use crate::action::{Action, ActionDescription};
|
||||
|
@ -39,11 +39,11 @@ impl KickstartLaunchctlService {
|
|||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?;
|
||||
if output.status.success() {
|
||||
service_exists = true;
|
||||
|
||||
let output_string = String::from_utf8(output.stdout)?;
|
||||
let output_string = String::from_utf8(output.stdout).map_err(|e| Self::error(e))?;
|
||||
// We are looking for a line containing "state = " with some trailing content
|
||||
// The output is not a JSON or a plist
|
||||
// MacOS's man pages explicitly tell us not to try to parse this output
|
||||
|
@ -102,7 +102,8 @@ impl Action for KickstartLaunchctlService {
|
|||
.arg(format!("{domain}/{service}"))
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -116,26 +117,26 @@ impl Action for KickstartLaunchctlService {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self { domain, service } = self;
|
||||
|
||||
// MacOs doesn't offer an "ensure-stopped" like they do with Kickstart
|
||||
let mut command = Command::new("launchctl");
|
||||
command.process_group(0);
|
||||
command.arg("stop");
|
||||
command.arg(format!("{domain}/{service}"));
|
||||
command.arg(format!("{}/{}", self.domain, self.service));
|
||||
command.stdin(std::process::Stdio::null());
|
||||
let command_str = format!("{:?}", command.as_std());
|
||||
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(&command, e))?;
|
||||
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?;
|
||||
|
||||
// On our test Macs, a status code of `3` was reported if the service was stopped while not running.
|
||||
match output.status.code() {
|
||||
Some(3) | Some(0) | None => (),
|
||||
_ => {
|
||||
return Err(ActionError::Custom(Box::new(
|
||||
return Err(Self::error(ActionErrorKind::Custom(Box::new(
|
||||
KickstartLaunchctlServiceError::CannotStopService(command_str, output),
|
||||
)))
|
||||
))))
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ pub(crate) mod unmount_apfs_volume;
|
|||
pub use bootstrap_launchctl_service::BootstrapLaunchctlService;
|
||||
pub use create_apfs_volume::CreateApfsVolume;
|
||||
pub use create_nix_volume::{CreateNixVolume, NIX_VOLUME_MOUNTD_DEST};
|
||||
pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
|
||||
pub use create_synthetic_objects::CreateSyntheticObjects;
|
||||
pub use create_volume_service::CreateVolumeService;
|
||||
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
|
||||
pub use encrypt_apfs_volume::EncryptApfsVolume;
|
||||
|
@ -27,9 +27,9 @@ use uuid::Uuid;
|
|||
|
||||
use crate::execute_command;
|
||||
|
||||
use super::ActionError;
|
||||
use super::ActionErrorKind;
|
||||
|
||||
async fn get_uuid_for_label(apfs_volume_label: &str) -> Result<Uuid, ActionError> {
|
||||
async fn get_uuid_for_label(apfs_volume_label: &str) -> Result<Uuid, ActionErrorKind> {
|
||||
let output = execute_command(
|
||||
Command::new("/usr/sbin/diskutil")
|
||||
.process_group(0)
|
||||
|
|
|
@ -65,9 +65,11 @@ impl Action for UnmountApfsVolume {
|
|||
.arg(&name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.map_err(Self::error)?
|
||||
.stdout;
|
||||
let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf))?;
|
||||
let the_plist: DiskUtilInfoOutput =
|
||||
plist::from_reader(Cursor::new(buf)).map_err(|e| Self::error(e))?;
|
||||
|
||||
the_plist.mount_point.is_some()
|
||||
};
|
||||
|
@ -80,7 +82,8 @@ impl Action for UnmountApfsVolume {
|
|||
.arg(name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
} else {
|
||||
tracing::debug!("Volume was already unmounted, can skip unmounting")
|
||||
}
|
||||
|
@ -94,16 +97,15 @@ impl Action for UnmountApfsVolume {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
let Self { disk: _, name } = self;
|
||||
|
||||
execute_command(
|
||||
Command::new("/usr/sbin/diskutil")
|
||||
.process_group(0)
|
||||
.args(["unmount", "force"])
|
||||
.arg(name)
|
||||
.arg(&self.name)
|
||||
.stdin(std::process::Stdio::null()),
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Self::error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -255,6 +255,14 @@ pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
|
|||
state: ActionState::Uncompleted,
|
||||
}
|
||||
}
|
||||
|
||||
fn error(kind: impl Into<ActionErrorKind>) -> ActionError
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ActionError::new(Self::action_tag(), kind)
|
||||
}
|
||||
|
||||
// They should also have an `async fn plan(args...) -> Result<StatefulAction<Self>, ActionError>;`
|
||||
}
|
||||
|
||||
|
@ -299,10 +307,51 @@ impl From<&'static str> for ActionTag {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActionError {
|
||||
action_tag: ActionTag,
|
||||
kind: ActionErrorKind,
|
||||
}
|
||||
|
||||
impl ActionError {
|
||||
pub fn new(action_tag: ActionTag, kind: impl Into<ActionErrorKind>) -> Self {
|
||||
Self {
|
||||
action_tag,
|
||||
kind: kind.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &ActionErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
pub fn action_tag(&self) -> &ActionTag {
|
||||
&self.action_tag
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ActionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("Action `{}` errored", self.action_tag))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ActionError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(&self.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ActionError> for ActionErrorKind {
|
||||
fn from(value: ActionError) -> Self {
|
||||
Self::Child(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occurring during an action
|
||||
#[non_exhaustive]
|
||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||
pub enum ActionError {
|
||||
pub enum ActionErrorKind {
|
||||
/// A custom error
|
||||
#[error(transparent)]
|
||||
Custom(Box<dyn std::error::Error + Send + Sync>),
|
||||
|
@ -310,17 +359,26 @@ pub enum ActionError {
|
|||
#[error(transparent)]
|
||||
Certificate(#[from] CertificateError),
|
||||
/// A child error
|
||||
#[error("Child action `{0}`")]
|
||||
Child(ActionTag, #[source] Box<ActionError>),
|
||||
/// Several child errors
|
||||
#[error("Child action errors: {}", .0.iter().map(|v| {
|
||||
if let Some(source) = v.source() {
|
||||
format!("{v} ({source})")
|
||||
#[error(transparent)]
|
||||
Child(Box<ActionError>),
|
||||
/// Several errors
|
||||
#[error("Multiple child errors\n\n{}", .0.iter().map(|err| {
|
||||
if let Some(source) = err.source() {
|
||||
format!("{err}\n{source}\n")
|
||||
} else {
|
||||
format!("{v}")
|
||||
format!("{err}\n")
|
||||
}
|
||||
}).collect::<Vec<_>>().join(" & "))]
|
||||
Children(Vec<Box<ActionError>>),
|
||||
}).collect::<Vec<_>>().join("\n"))]
|
||||
MultipleChildren(Vec<ActionError>),
|
||||
/// Several errors
|
||||
#[error("Multiple errors\n\n{}", .0.iter().map(|err| {
|
||||
if let Some(source) = err.source() {
|
||||
format!("{err}\n{source}")
|
||||
} else {
|
||||
format!("{err}\n")
|
||||
}
|
||||
}).collect::<Vec<_>>().join("\n"))]
|
||||
Multiple(Vec<ActionErrorKind>),
|
||||
/// The path already exists with different content that expected
|
||||
#[error(
|
||||
"`{0}` exists with different content than planned, consider removing it with `rm {0}`"
|
||||
|
@ -475,7 +533,7 @@ pub enum ActionError {
|
|||
SystemdMissing,
|
||||
}
|
||||
|
||||
impl ActionError {
|
||||
impl ActionErrorKind {
|
||||
pub fn command(command: &tokio::process::Command, error: std::io::Error) -> Self {
|
||||
Self::Command {
|
||||
#[cfg(feature = "diagnostics")]
|
||||
|
@ -494,7 +552,7 @@ impl ActionError {
|
|||
}
|
||||
}
|
||||
|
||||
impl HasExpectedErrors for ActionError {
|
||||
impl HasExpectedErrors for ActionErrorKind {
|
||||
fn expected<'a>(&'a self) -> Option<Box<dyn std::error::Error + 'a>> {
|
||||
match self {
|
||||
Self::PathUserMismatch(_, _, _)
|
||||
|
@ -506,11 +564,10 @@ impl HasExpectedErrors for ActionError {
|
|||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
impl crate::diagnostics::ErrorDiagnostic for ActionError {
|
||||
impl crate::diagnostics::ErrorDiagnostic for ActionErrorKind {
|
||||
fn diagnostic(&self) -> String {
|
||||
let static_str: &'static str = (self).into();
|
||||
let context = match self {
|
||||
Self::Child(action, _) => vec![action.to_string()],
|
||||
Self::Read(path, _)
|
||||
| Self::Open(path, _)
|
||||
| Self::Write(path, _)
|
||||
|
@ -518,7 +575,8 @@ impl crate::diagnostics::ErrorDiagnostic for ActionError {
|
|||
| Self::SetPermissions(_, path, _)
|
||||
| Self::GettingMetadata(path, _)
|
||||
| Self::CreateDirectory(path, _)
|
||||
| Self::PathWasNotFile(path) => {
|
||||
| Self::PathWasNotFile(path)
|
||||
| Self::Remove(path, _) => {
|
||||
vec![path.to_string_lossy().to_string()]
|
||||
},
|
||||
Self::Rename(first_path, second_path, _)
|
||||
|
|
|
@ -15,10 +15,13 @@ use crate::{
|
|||
plan::RECEIPT_LOCATION,
|
||||
planner::Planner,
|
||||
settings::CommonSettings,
|
||||
BuiltinPlanner, InstallPlan,
|
||||
BuiltinPlanner, InstallPlan, NixInstallerError,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
use eyre::{eyre, WrapErr};
|
||||
use color_eyre::{
|
||||
eyre::{eyre, WrapErr},
|
||||
Section,
|
||||
};
|
||||
use owo_colors::OwoColorize;
|
||||
|
||||
/// Execute an install (possibly using an existing plan)
|
||||
|
@ -218,19 +221,30 @@ impl CommandExecute for Install {
|
|||
let rx2 = tx.subscribe();
|
||||
let res = install_plan.uninstall(rx2).await;
|
||||
|
||||
if let Err(err) = res {
|
||||
if let Some(expected) = err.expected() {
|
||||
eprintln!("{}", expected.red());
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
return Err(err)?;
|
||||
} else {
|
||||
println!(
|
||||
"\
|
||||
{message}\n\
|
||||
",
|
||||
message = "Partial Nix install was uninstalled successfully!".bold(),
|
||||
);
|
||||
match res {
|
||||
Err(NixInstallerError::ActionRevert(errs)) => {
|
||||
let mut report = eyre!("Multiple errors");
|
||||
for err in errs {
|
||||
report = report.error(err);
|
||||
}
|
||||
return Err(report)?;
|
||||
},
|
||||
Err(err) => {
|
||||
if let Some(expected) = err.expected() {
|
||||
eprintln!("{}", expected.red());
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
return Err(err)?;
|
||||
},
|
||||
_ => {
|
||||
println!(
|
||||
"\
|
||||
{message}\n\
|
||||
",
|
||||
message =
|
||||
"Partial Nix install was uninstalled successfully!".bold(),
|
||||
);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
if let Some(expected) = err.expected() {
|
||||
|
|
|
@ -8,10 +8,10 @@ use crate::{
|
|||
cli::{ensure_root, interaction::PromptChoice, signal_channel},
|
||||
error::HasExpectedErrors,
|
||||
plan::RECEIPT_LOCATION,
|
||||
InstallPlan,
|
||||
InstallPlan, NixInstallerError,
|
||||
};
|
||||
use clap::{ArgAction, Parser};
|
||||
use eyre::{eyre, WrapErr};
|
||||
use color_eyre::eyre::{eyre, WrapErr};
|
||||
use owo_colors::OwoColorize;
|
||||
use rand::Rng;
|
||||
|
||||
|
@ -125,17 +125,21 @@ impl CommandExecute for Uninstall {
|
|||
let (_tx, rx) = signal_channel().await?;
|
||||
|
||||
let res = plan.uninstall(rx).await;
|
||||
if let Err(err) = res {
|
||||
if let Some(expected) = err.expected() {
|
||||
println!("{}", expected.red());
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
return Err(err)?;
|
||||
match res {
|
||||
Err(err @ NixInstallerError::ActionRevert(_)) => {
|
||||
tracing::error!("Uninstallation complete, some errors encountered");
|
||||
return Err(err)?;
|
||||
},
|
||||
Err(err) => {
|
||||
if let Some(expected) = err.expected() {
|
||||
println!("{}", expected.red());
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
return Err(err)?;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// TODO(@hoverbear): It would be so nice to catch errors and offer the user a way to keep going...
|
||||
// However that will require being able to link error -> step and manually setting that step as `Uncompleted`.
|
||||
|
||||
println!(
|
||||
"\
|
||||
{success}\n\
|
||||
|
|
|
@ -102,11 +102,11 @@ impl DiagnosticData {
|
|||
let mut walker: &dyn std::error::Error = &err;
|
||||
while let Some(source) = walker.source() {
|
||||
if let Some(downcasted) = source.downcast_ref::<ActionError>() {
|
||||
let downcasted_diagnostic = downcasted.diagnostic();
|
||||
let downcasted_diagnostic = downcasted.kind().diagnostic();
|
||||
failure_chain.push(downcasted_diagnostic);
|
||||
}
|
||||
if let Some(downcasted) = source.downcast_ref::<Box<ActionError>>() {
|
||||
let downcasted_diagnostic = downcasted.diagnostic();
|
||||
let downcasted_diagnostic = downcasted.kind().diagnostic();
|
||||
failure_chain.push(downcasted_diagnostic);
|
||||
}
|
||||
if let Some(downcasted) = source.downcast_ref::<PlannerError>() {
|
||||
|
|
26
src/error.rs
26
src/error.rs
|
@ -1,18 +1,23 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{error::Error, path::PathBuf};
|
||||
|
||||
use crate::{
|
||||
action::{ActionError, ActionTag},
|
||||
planner::PlannerError,
|
||||
settings::InstallSettingsError,
|
||||
};
|
||||
use crate::{action::ActionError, planner::PlannerError, settings::InstallSettingsError};
|
||||
|
||||
/// An error occurring during a call defined in this crate
|
||||
#[non_exhaustive]
|
||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||
pub enum NixInstallerError {
|
||||
/// An error originating from an [`Action`](crate::action::Action)
|
||||
#[error("Error executing action `{0}`")]
|
||||
Action(ActionTag, #[source] ActionError),
|
||||
#[error("Error executing action")]
|
||||
Action(#[source] ActionError),
|
||||
/// An error originating from an [`Action`](crate::action::Action) while reverting
|
||||
#[error("Error reverting\n{}", .0.iter().map(|err| {
|
||||
if let Some(source) = err.source() {
|
||||
format!("{err}\n{source}\n")
|
||||
} else {
|
||||
format!("{err}\n")
|
||||
}
|
||||
}).collect::<Vec<_>>().join("\n"))]
|
||||
ActionRevert(Vec<ActionError>),
|
||||
/// An error while writing the [`InstallPlan`](crate::InstallPlan)
|
||||
#[error("Recording install receipt")]
|
||||
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
||||
|
@ -72,7 +77,8 @@ pub(crate) trait HasExpectedErrors: std::error::Error + Sized + Send + Sync {
|
|||
impl HasExpectedErrors for NixInstallerError {
|
||||
fn expected<'a>(&'a self) -> Option<Box<dyn std::error::Error + 'a>> {
|
||||
match self {
|
||||
NixInstallerError::Action(_, action_error) => action_error.expected(),
|
||||
NixInstallerError::Action(action_error) => action_error.kind().expected(),
|
||||
NixInstallerError::ActionRevert(_) => None,
|
||||
NixInstallerError::RecordingReceipt(_, _) => None,
|
||||
NixInstallerError::CopyingSelf(_) => None,
|
||||
NixInstallerError::SerializingReceipt(_) => None,
|
||||
|
@ -91,7 +97,7 @@ impl crate::diagnostics::ErrorDiagnostic for NixInstallerError {
|
|||
fn diagnostic(&self) -> String {
|
||||
let static_str: &'static str = (self).into();
|
||||
let context = match self {
|
||||
Self::Action(action, _) => vec![action.to_string()],
|
||||
Self::Action(action_error) => vec![action_error.action_tag().to_string()],
|
||||
_ => vec![],
|
||||
};
|
||||
return format!(
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -82,8 +82,6 @@ pub mod settings;
|
|||
|
||||
use std::{ffi::OsStr, path::Path, process::Output};
|
||||
|
||||
use action::{Action, ActionError};
|
||||
|
||||
pub use error::NixInstallerError;
|
||||
pub use plan::InstallPlan;
|
||||
use planner::BuiltinPlanner;
|
||||
|
@ -91,16 +89,18 @@ use planner::BuiltinPlanner;
|
|||
use reqwest::Certificate;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::action::{Action, ActionErrorKind};
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
||||
async fn execute_command(command: &mut Command) -> Result<Output, ActionError> {
|
||||
async fn execute_command(command: &mut Command) -> Result<Output, ActionErrorKind> {
|
||||
tracing::trace!("Executing");
|
||||
let output = command
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| ActionError::command(command, e))?;
|
||||
.map_err(|e| ActionErrorKind::command(command, e))?;
|
||||
match output.status.success() {
|
||||
true => Ok(output),
|
||||
false => Err(ActionError::command_output(command, output)),
|
||||
false => Err(ActionErrorKind::command_output(command, output)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
63
src/plan.rs
63
src/plan.rs
|
@ -175,12 +175,11 @@ impl InstallPlan {
|
|||
}
|
||||
|
||||
tracing::info!("Step: {}", action.tracing_synopsis());
|
||||
let typetag_name = action.inner_typetag_name();
|
||||
if let Err(err) = action.try_execute().await {
|
||||
if let Err(err) = write_receipt(self.clone()).await {
|
||||
tracing::error!("Error saving receipt: {:?}", err);
|
||||
}
|
||||
let err = NixInstallerError::Action(typetag_name.into(), err);
|
||||
let err = NixInstallerError::Action(err);
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
|
@ -296,6 +295,7 @@ impl InstallPlan {
|
|||
) -> Result<(), NixInstallerError> {
|
||||
let Self { actions, .. } = self;
|
||||
let mut cancel_channel = cancel_channel.into();
|
||||
let mut errors = vec![];
|
||||
|
||||
// This is **deliberately sequential**.
|
||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||
|
@ -324,39 +324,40 @@ impl InstallPlan {
|
|||
}
|
||||
|
||||
tracing::info!("Revert: {}", action.tracing_synopsis());
|
||||
let typetag_name = action.inner_typetag_name();
|
||||
if let Err(err) = action.try_revert().await {
|
||||
if let Err(err) = write_receipt(self.clone()).await {
|
||||
tracing::error!("Error saving receipt: {:?}", err);
|
||||
}
|
||||
let err = NixInstallerError::Action(typetag_name.into(), err);
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.failure(&err)
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Failure,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
return Err(err);
|
||||
if let Err(errs) = action.try_revert().await {
|
||||
errors.push(errs);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Success,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if errors.is_empty() {
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Success,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
} else {
|
||||
let error = NixInstallerError::ActionRevert(errors);
|
||||
#[cfg(feature = "diagnostics")]
|
||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||
diagnostic_data
|
||||
.clone()
|
||||
.failure(&error)
|
||||
.send(
|
||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||
crate::diagnostics::DiagnosticStatus::Failure,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -232,6 +232,7 @@ impl CommonSettings {
|
|||
let url;
|
||||
let nix_build_user_prefix;
|
||||
let nix_build_user_id_base;
|
||||
let ssl_cert_file;
|
||||
|
||||
use target_lexicon::{Architecture, OperatingSystem};
|
||||
match (Architecture::host(), OperatingSystem::host()) {
|
||||
|
@ -240,18 +241,21 @@ impl CommonSettings {
|
|||
url = NIX_X64_64_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 30000;
|
||||
ssl_cert_file = None;
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::X86_32(_), OperatingSystem::Linux) => {
|
||||
url = NIX_I686_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 30000;
|
||||
ssl_cert_file = None;
|
||||
},
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
|
||||
url = NIX_AARCH64_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 30000;
|
||||
ssl_cert_file = None;
|
||||
},
|
||||
#[cfg(target_os = "macos")]
|
||||
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
|
||||
|
@ -259,6 +263,7 @@ impl CommonSettings {
|
|||
url = NIX_X64_64_DARWIN_URL;
|
||||
nix_build_user_prefix = "_nixbld";
|
||||
nix_build_user_id_base = 300;
|
||||
ssl_cert_file = Some("/etc/ssl/certs/ca-certificates.crt".into());
|
||||
},
|
||||
#[cfg(target_os = "macos")]
|
||||
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
|
||||
|
@ -266,6 +271,7 @@ impl CommonSettings {
|
|||
url = NIX_AARCH64_DARWIN_URL;
|
||||
nix_build_user_prefix = "_nixbld";
|
||||
nix_build_user_id_base = 300;
|
||||
ssl_cert_file = Some("/etc/ssl/certs/ca-certificates.crt".into());
|
||||
},
|
||||
_ => {
|
||||
return Err(InstallSettingsError::UnsupportedArchitecture(
|
||||
|
@ -285,7 +291,7 @@ impl CommonSettings {
|
|||
proxy: Default::default(),
|
||||
extra_conf: Default::default(),
|
||||
force: false,
|
||||
ssl_cert_file: Default::default(),
|
||||
ssl_cert_file,
|
||||
#[cfg(feature = "diagnostics")]
|
||||
diagnostic_endpoint: Some("https://install.determinate.systems/nix/diagnostic".into()),
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue