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