mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-02-11 14:43:47 +00:00
Merge pull request #136605 from helsinki-systems/feat/dry-activation-scripts
nixos: Add dry activation for users/groups
This commit is contained in:
commit
5f4004ea94
@ -1,11 +1,10 @@
|
|||||||
use strict;
|
use strict;
|
||||||
|
use warnings;
|
||||||
use File::Path qw(make_path);
|
use File::Path qw(make_path);
|
||||||
use File::Slurp;
|
use File::Slurp;
|
||||||
|
use Getopt::Long;
|
||||||
use JSON;
|
use JSON;
|
||||||
|
|
||||||
make_path("/var/lib/nixos", { mode => 0755 });
|
|
||||||
|
|
||||||
|
|
||||||
# Keep track of deleted uids and gids.
|
# Keep track of deleted uids and gids.
|
||||||
my $uidMapFile = "/var/lib/nixos/uid-map";
|
my $uidMapFile = "/var/lib/nixos/uid-map";
|
||||||
my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
|
my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
|
||||||
@ -13,12 +12,19 @@ my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
|
|||||||
my $gidMapFile = "/var/lib/nixos/gid-map";
|
my $gidMapFile = "/var/lib/nixos/gid-map";
|
||||||
my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {};
|
my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {};
|
||||||
|
|
||||||
|
my $is_dry = ($ENV{'NIXOS_ACTION'} // "") eq "dry-activate";
|
||||||
|
GetOptions("dry-activate" => \$is_dry);
|
||||||
|
make_path("/var/lib/nixos", { mode => 0755 }) unless $is_dry;
|
||||||
|
|
||||||
sub updateFile {
|
sub updateFile {
|
||||||
my ($path, $contents, $perms) = @_;
|
my ($path, $contents, $perms) = @_;
|
||||||
|
return if $is_dry;
|
||||||
write_file($path, { atomic => 1, binmode => ':utf8', perms => $perms // 0644 }, $contents) or die;
|
write_file($path, { atomic => 1, binmode => ':utf8', perms => $perms // 0644 }, $contents) or die;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub nscdInvalidate {
|
||||||
|
system("nscd", "--invalidate", $_[0]) unless $is_dry;
|
||||||
|
}
|
||||||
|
|
||||||
sub hashPassword {
|
sub hashPassword {
|
||||||
my ($password) = @_;
|
my ($password) = @_;
|
||||||
@ -28,6 +34,14 @@ sub hashPassword {
|
|||||||
return crypt($password, '$6$' . $salt . '$');
|
return crypt($password, '$6$' . $salt . '$');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub dry_print {
|
||||||
|
if ($is_dry) {
|
||||||
|
print STDERR ("$_[1] $_[2]\n")
|
||||||
|
} else {
|
||||||
|
print STDERR ("$_[0] $_[2]\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Functions for allocating free GIDs/UIDs. FIXME: respect ID ranges in
|
# Functions for allocating free GIDs/UIDs. FIXME: respect ID ranges in
|
||||||
# /etc/login.defs.
|
# /etc/login.defs.
|
||||||
@ -51,7 +65,7 @@ sub allocGid {
|
|||||||
my ($name) = @_;
|
my ($name) = @_;
|
||||||
my $prevGid = $gidMap->{$name};
|
my $prevGid = $gidMap->{$name};
|
||||||
if (defined $prevGid && !defined $gidsUsed{$prevGid}) {
|
if (defined $prevGid && !defined $gidsUsed{$prevGid}) {
|
||||||
print STDERR "reviving group '$name' with GID $prevGid\n";
|
dry_print("reviving", "would revive", "group '$name' with GID $prevGid");
|
||||||
$gidsUsed{$prevGid} = 1;
|
$gidsUsed{$prevGid} = 1;
|
||||||
return $prevGid;
|
return $prevGid;
|
||||||
}
|
}
|
||||||
@ -63,15 +77,14 @@ sub allocUid {
|
|||||||
my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1);
|
my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1);
|
||||||
my $prevUid = $uidMap->{$name};
|
my $prevUid = $uidMap->{$name};
|
||||||
if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
|
if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
|
||||||
print STDERR "reviving user '$name' with UID $prevUid\n";
|
dry_print("reviving", "would revive", "user '$name' with UID $prevUid");
|
||||||
$uidsUsed{$prevUid} = 1;
|
$uidsUsed{$prevUid} = 1;
|
||||||
return $prevUid;
|
return $prevUid;
|
||||||
}
|
}
|
||||||
return allocId(\%uidsUsed, \%uidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
|
return allocId(\%uidsUsed, \%uidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Read the declared users/groups
|
||||||
# Read the declared users/groups.
|
|
||||||
my $spec = decode_json(read_file($ARGV[0]));
|
my $spec = decode_json(read_file($ARGV[0]));
|
||||||
|
|
||||||
# Don't allocate UIDs/GIDs that are manually assigned.
|
# Don't allocate UIDs/GIDs that are manually assigned.
|
||||||
@ -134,7 +147,7 @@ foreach my $g (@{$spec->{groups}}) {
|
|||||||
if (defined $existing) {
|
if (defined $existing) {
|
||||||
$g->{gid} = $existing->{gid} if !defined $g->{gid};
|
$g->{gid} = $existing->{gid} if !defined $g->{gid};
|
||||||
if ($g->{gid} != $existing->{gid}) {
|
if ($g->{gid} != $existing->{gid}) {
|
||||||
warn "warning: not applying GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})\n";
|
dry_print("warning: not applying", "warning: would not apply", "GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})");
|
||||||
$g->{gid} = $existing->{gid};
|
$g->{gid} = $existing->{gid};
|
||||||
}
|
}
|
||||||
$g->{password} = $existing->{password}; # do we want this?
|
$g->{password} = $existing->{password}; # do we want this?
|
||||||
@ -163,7 +176,7 @@ foreach my $name (keys %groupsCur) {
|
|||||||
my $g = $groupsCur{$name};
|
my $g = $groupsCur{$name};
|
||||||
next if defined $groupsOut{$name};
|
next if defined $groupsOut{$name};
|
||||||
if (!$spec->{mutableUsers} || defined $declGroups{$name}) {
|
if (!$spec->{mutableUsers} || defined $declGroups{$name}) {
|
||||||
print STDERR "removing group ‘$name’\n";
|
dry_print("removing group", "would remove group", "‘$name’");
|
||||||
} else {
|
} else {
|
||||||
$groupsOut{$name} = $g;
|
$groupsOut{$name} = $g;
|
||||||
}
|
}
|
||||||
@ -175,7 +188,7 @@ my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members}
|
|||||||
(sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
|
(sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
|
||||||
updateFile($gidMapFile, to_json($gidMap));
|
updateFile($gidMapFile, to_json($gidMap));
|
||||||
updateFile("/etc/group", \@lines);
|
updateFile("/etc/group", \@lines);
|
||||||
system("nscd --invalidate group");
|
nscdInvalidate("group");
|
||||||
|
|
||||||
# Generate a new /etc/passwd containing the declared users.
|
# Generate a new /etc/passwd containing the declared users.
|
||||||
my %usersOut;
|
my %usersOut;
|
||||||
@ -196,7 +209,7 @@ foreach my $u (@{$spec->{users}}) {
|
|||||||
if (defined $existing) {
|
if (defined $existing) {
|
||||||
$u->{uid} = $existing->{uid} if !defined $u->{uid};
|
$u->{uid} = $existing->{uid} if !defined $u->{uid};
|
||||||
if ($u->{uid} != $existing->{uid}) {
|
if ($u->{uid} != $existing->{uid}) {
|
||||||
warn "warning: not applying UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})\n";
|
dry_print("warning: not applying", "warning: would not apply", "UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})");
|
||||||
$u->{uid} = $existing->{uid};
|
$u->{uid} = $existing->{uid};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -211,7 +224,7 @@ foreach my $u (@{$spec->{users}}) {
|
|||||||
|
|
||||||
# Ensure home directory incl. ownership and permissions.
|
# Ensure home directory incl. ownership and permissions.
|
||||||
if ($u->{createHome}) {
|
if ($u->{createHome}) {
|
||||||
make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home};
|
make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home} and ! $is_dry;
|
||||||
chown $u->{uid}, $u->{gid}, $u->{home};
|
chown $u->{uid}, $u->{gid}, $u->{home};
|
||||||
chmod 0700, $u->{home};
|
chmod 0700, $u->{home};
|
||||||
}
|
}
|
||||||
@ -250,7 +263,7 @@ foreach my $name (keys %usersCur) {
|
|||||||
my $u = $usersCur{$name};
|
my $u = $usersCur{$name};
|
||||||
next if defined $usersOut{$name};
|
next if defined $usersOut{$name};
|
||||||
if (!$spec->{mutableUsers} || defined $declUsers{$name}) {
|
if (!$spec->{mutableUsers} || defined $declUsers{$name}) {
|
||||||
print STDERR "removing user ‘$name’\n";
|
dry_print("removing user", "would remove user", "‘$name’");
|
||||||
} else {
|
} else {
|
||||||
$usersOut{$name} = $u;
|
$usersOut{$name} = $u;
|
||||||
}
|
}
|
||||||
@ -261,7 +274,7 @@ foreach my $name (keys %usersCur) {
|
|||||||
(sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
|
(sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
|
||||||
updateFile($uidMapFile, to_json($uidMap));
|
updateFile($uidMapFile, to_json($uidMap));
|
||||||
updateFile("/etc/passwd", \@lines);
|
updateFile("/etc/passwd", \@lines);
|
||||||
system("nscd --invalidate passwd");
|
nscdInvalidate("passwd");
|
||||||
|
|
||||||
|
|
||||||
# Rewrite /etc/shadow to add new accounts or remove dead ones.
|
# Rewrite /etc/shadow to add new accounts or remove dead ones.
|
||||||
@ -293,7 +306,7 @@ updateFile("/etc/shadow", \@shadowNew, 0640);
|
|||||||
my $uid = getpwnam "root";
|
my $uid = getpwnam "root";
|
||||||
my $gid = getgrnam "shadow";
|
my $gid = getgrnam "shadow";
|
||||||
my $path = "/etc/shadow";
|
my $path = "/etc/shadow";
|
||||||
chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!";
|
(chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!") unless $is_dry;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Rewrite /etc/subuid & /etc/subgid to include default container mappings
|
# Rewrite /etc/subuid & /etc/subgid to include default container mappings
|
||||||
|
@ -561,14 +561,16 @@ in {
|
|||||||
shadow.gid = ids.gids.shadow;
|
shadow.gid = ids.gids.shadow;
|
||||||
};
|
};
|
||||||
|
|
||||||
system.activationScripts.users = stringAfter [ "stdio" ]
|
system.activationScripts.users = {
|
||||||
''
|
supportsDryActivation = true;
|
||||||
|
text = ''
|
||||||
install -m 0700 -d /root
|
install -m 0700 -d /root
|
||||||
install -m 0755 -d /home
|
install -m 0755 -d /home
|
||||||
|
|
||||||
${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
|
${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
|
||||||
-w ${./update-users-groups.pl} ${spec}
|
-w ${./update-users-groups.pl} ${spec}
|
||||||
'';
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# for backwards compatibility
|
# for backwards compatibility
|
||||||
system.activationScripts.groups = stringAfter [ "users" ] "";
|
system.activationScripts.groups = stringAfter [ "users" ] "";
|
||||||
|
@ -17,6 +17,41 @@ let
|
|||||||
'';
|
'';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
systemActivationScript = set: onlyDry: let
|
||||||
|
set' = filterAttrs (_: v: onlyDry -> v.supportsDryActivation) (mapAttrs (_: v: if isString v then (noDepEntry v) // { supportsDryActivation = false; } else v) set);
|
||||||
|
withHeadlines = addAttributeName set';
|
||||||
|
in
|
||||||
|
''
|
||||||
|
#!${pkgs.runtimeShell}
|
||||||
|
|
||||||
|
systemConfig='@out@'
|
||||||
|
|
||||||
|
export PATH=/empty
|
||||||
|
for i in ${toString path}; do
|
||||||
|
PATH=$PATH:$i/bin:$i/sbin
|
||||||
|
done
|
||||||
|
|
||||||
|
_status=0
|
||||||
|
trap "_status=1 _localstatus=\$?" ERR
|
||||||
|
|
||||||
|
# Ensure a consistent umask.
|
||||||
|
umask 0022
|
||||||
|
|
||||||
|
${textClosureMap id (withHeadlines) (attrNames withHeadlines)}
|
||||||
|
|
||||||
|
'' + optionalString (!onlyDry) ''
|
||||||
|
# Make this configuration the current configuration.
|
||||||
|
# The readlink is there to ensure that when $systemConfig = /system
|
||||||
|
# (which is a symlink to the store), /run/current-system is still
|
||||||
|
# used as a garbage collection root.
|
||||||
|
ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
|
||||||
|
|
||||||
|
# Prevent the current configuration from being garbage-collected.
|
||||||
|
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
|
||||||
|
|
||||||
|
exit $_status
|
||||||
|
'';
|
||||||
|
|
||||||
path = with pkgs; map getBin
|
path = with pkgs; map getBin
|
||||||
[ coreutils
|
[ coreutils
|
||||||
gnugrep
|
gnugrep
|
||||||
@ -28,7 +63,7 @@ let
|
|||||||
util-linux # needed for mount and mountpoint
|
util-linux # needed for mount and mountpoint
|
||||||
];
|
];
|
||||||
|
|
||||||
scriptType = with types;
|
scriptType = withDry: with types;
|
||||||
let scriptOptions =
|
let scriptOptions =
|
||||||
{ deps = mkOption
|
{ deps = mkOption
|
||||||
{ type = types.listOf types.str;
|
{ type = types.listOf types.str;
|
||||||
@ -39,6 +74,19 @@ let
|
|||||||
{ type = types.lines;
|
{ type = types.lines;
|
||||||
description = "The content of the script.";
|
description = "The content of the script.";
|
||||||
};
|
};
|
||||||
|
} // optionalAttrs withDry {
|
||||||
|
supportsDryActivation = mkOption
|
||||||
|
{ type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether this activation script supports being dry-activated.
|
||||||
|
These activation scripts will also be executed on dry-activate
|
||||||
|
activations with the environment variable
|
||||||
|
<literal>NIXOS_ACTION</literal> being set to <literal>dry-activate
|
||||||
|
</literal>. it's important that these activation scripts don't
|
||||||
|
modify anything about the system when the variable is set.
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
in either str (submodule { options = scriptOptions; });
|
in either str (submodule { options = scriptOptions; });
|
||||||
|
|
||||||
@ -74,47 +122,19 @@ in
|
|||||||
idempotent and fast.
|
idempotent and fast.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
type = types.attrsOf scriptType;
|
type = types.attrsOf (scriptType true);
|
||||||
|
apply = set: set // {
|
||||||
apply = set: {
|
script = systemActivationScript set false;
|
||||||
script =
|
|
||||||
''
|
|
||||||
#! ${pkgs.runtimeShell}
|
|
||||||
|
|
||||||
systemConfig=@out@
|
|
||||||
|
|
||||||
export PATH=/empty
|
|
||||||
for i in ${toString path}; do
|
|
||||||
PATH=$PATH:$i/bin:$i/sbin
|
|
||||||
done
|
|
||||||
|
|
||||||
_status=0
|
|
||||||
trap "_status=1 _localstatus=\$?" ERR
|
|
||||||
|
|
||||||
# Ensure a consistent umask.
|
|
||||||
umask 0022
|
|
||||||
|
|
||||||
${
|
|
||||||
let
|
|
||||||
set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
|
|
||||||
withHeadlines = addAttributeName set';
|
|
||||||
in textClosureMap id (withHeadlines) (attrNames withHeadlines)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make this configuration the current configuration.
|
|
||||||
# The readlink is there to ensure that when $systemConfig = /system
|
|
||||||
# (which is a symlink to the store), /run/current-system is still
|
|
||||||
# used as a garbage collection root.
|
|
||||||
ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
|
|
||||||
|
|
||||||
# Prevent the current configuration from being garbage-collected.
|
|
||||||
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
|
|
||||||
|
|
||||||
exit $_status
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
system.dryActivationScript = mkOption {
|
||||||
|
description = "The shell script that is to be run when dry-activating a system.";
|
||||||
|
readOnly = true;
|
||||||
|
internal = true;
|
||||||
|
default = systemActivationScript (removeAttrs config.system.activationScripts [ "script" ]) true;
|
||||||
|
};
|
||||||
|
|
||||||
system.userActivationScripts = mkOption {
|
system.userActivationScripts = mkOption {
|
||||||
default = {};
|
default = {};
|
||||||
|
|
||||||
@ -137,7 +157,7 @@ in
|
|||||||
idempotent and fast.
|
idempotent and fast.
|
||||||
'';
|
'';
|
||||||
|
|
||||||
type = with types; attrsOf scriptType;
|
type = with types; attrsOf (scriptType false);
|
||||||
|
|
||||||
apply = set: {
|
apply = set: {
|
||||||
script = ''
|
script = ''
|
||||||
|
@ -36,6 +36,8 @@ EOF
|
|||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$ENV{NIXOS_ACTION} = $action;
|
||||||
|
|
||||||
# This is a NixOS installation if it has /etc/NIXOS or a proper
|
# This is a NixOS installation if it has /etc/NIXOS or a proper
|
||||||
# /etc/os-release.
|
# /etc/os-release.
|
||||||
die "This is not a NixOS installation!\n" unless
|
die "This is not a NixOS installation!\n" unless
|
||||||
@ -360,6 +362,10 @@ if ($action eq "dry-activate") {
|
|||||||
if scalar @unitsToStopFiltered > 0;
|
if scalar @unitsToStopFiltered > 0;
|
||||||
print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
|
print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
|
||||||
if scalar(keys %unitsToSkip) > 0;
|
if scalar(keys %unitsToSkip) > 0;
|
||||||
|
|
||||||
|
print STDERR "would activate the configuration...\n";
|
||||||
|
system("$out/dry-activate", "$out");
|
||||||
|
|
||||||
print STDERR "would restart systemd\n" if $restartSystemd;
|
print STDERR "would restart systemd\n" if $restartSystemd;
|
||||||
print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"
|
print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"
|
||||||
if scalar(keys %unitsToRestart) > 0;
|
if scalar(keys %unitsToRestart) > 0;
|
||||||
|
@ -56,9 +56,11 @@ let
|
|||||||
''}
|
''}
|
||||||
|
|
||||||
echo "$activationScript" > $out/activate
|
echo "$activationScript" > $out/activate
|
||||||
|
echo "$dryActivationScript" > $out/dry-activate
|
||||||
substituteInPlace $out/activate --subst-var out
|
substituteInPlace $out/activate --subst-var out
|
||||||
chmod u+x $out/activate
|
substituteInPlace $out/dry-activate --subst-var out
|
||||||
unset activationScript
|
chmod u+x $out/activate $out/dry-activate
|
||||||
|
unset activationScript dryActivationScript
|
||||||
|
|
||||||
cp ${config.system.build.bootStage2} $out/init
|
cp ${config.system.build.bootStage2} $out/init
|
||||||
substituteInPlace $out/init --subst-var-by systemConfig $out
|
substituteInPlace $out/init --subst-var-by systemConfig $out
|
||||||
@ -108,6 +110,7 @@ let
|
|||||||
config.system.build.installBootLoader
|
config.system.build.installBootLoader
|
||||||
or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
|
or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
|
||||||
activationScript = config.system.activationScripts.script;
|
activationScript = config.system.activationScripts.script;
|
||||||
|
dryActivationScript = config.system.dryActivationScript;
|
||||||
nixosLabel = config.system.nixos.label;
|
nixosLabel = config.system.nixos.label;
|
||||||
|
|
||||||
configurationName = config.boot.loader.grub.configurationName;
|
configurationName = config.boot.loader.grub.configurationName;
|
||||||
|
@ -12,6 +12,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
|
|||||||
};
|
};
|
||||||
mutable = { ... }: {
|
mutable = { ... }: {
|
||||||
users.mutableUsers = true;
|
users.mutableUsers = true;
|
||||||
|
users.users.dry-test.isNormalUser = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,5 +42,32 @@ import ./make-test-python.nix ({ pkgs, ...} : {
|
|||||||
"${mutableSystem}/bin/switch-to-configuration test"
|
"${mutableSystem}/bin/switch-to-configuration test"
|
||||||
)
|
)
|
||||||
assert "/run/wrappers/" in machine.succeed("which passwd")
|
assert "/run/wrappers/" in machine.succeed("which passwd")
|
||||||
|
|
||||||
|
with subtest("dry-activation does not change files"):
|
||||||
|
machine.succeed('test -e /home/dry-test') # home was created
|
||||||
|
machine.succeed('rm -rf /home/dry-test')
|
||||||
|
|
||||||
|
files_to_check = ['/etc/group',
|
||||||
|
'/etc/passwd',
|
||||||
|
'/etc/shadow',
|
||||||
|
'/etc/subuid',
|
||||||
|
'/etc/subgid',
|
||||||
|
'/var/lib/nixos/uid-map',
|
||||||
|
'/var/lib/nixos/gid-map',
|
||||||
|
'/var/lib/nixos/declarative-groups',
|
||||||
|
'/var/lib/nixos/declarative-users'
|
||||||
|
]
|
||||||
|
expected_hashes = {}
|
||||||
|
expected_stats = {}
|
||||||
|
for file in files_to_check:
|
||||||
|
expected_hashes[file] = machine.succeed(f"sha256sum {file}")
|
||||||
|
expected_stats[file] = machine.succeed(f"stat {file}")
|
||||||
|
|
||||||
|
machine.succeed("/run/current-system/bin/switch-to-configuration dry-activate")
|
||||||
|
|
||||||
|
machine.fail('test -e /home/dry-test') # home was not recreated
|
||||||
|
for file in files_to_check:
|
||||||
|
assert machine.succeed(f"sha256sum {file}") == expected_hashes[file]
|
||||||
|
assert machine.succeed(f"stat {file}") == expected_stats[file]
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user