Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

etc: check for existing files during checks stage #731

Merged
merged 2 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions modules/services/activate-system/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
with lib;

let
inherit (pkgs) stdenv;

cfg = config.services.activate-system;
in

Expand Down Expand Up @@ -36,6 +34,7 @@ in
# Prevent the current configuration from being garbage-collected.
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system

${config.system.activationScripts.etcChecks.text}
${config.system.activationScripts.etc.text}
${config.system.activationScripts.keyboard.text}
'';
Expand Down
4 changes: 4 additions & 0 deletions modules/system/activation-scripts.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ in
${cfg.activationScripts.preActivation.text}
# We run `etcChecks` again just in case someone runs `activate`
# directly without `activate-user`.
${cfg.activationScripts.etcChecks.text}
${cfg.activationScripts.extraActivation.text}
${cfg.activationScripts.groups.text}
${cfg.activationScripts.users.text}
Expand Down Expand Up @@ -99,6 +102,7 @@ in
${cfg.activationScripts.createRun.text}
${cfg.activationScripts.checks.text}
${cfg.activationScripts.etcChecks.text}
${cfg.activationScripts.extraUserActivation.text}
${cfg.activationScripts.userDefaults.text}
${cfg.activationScripts.userLaunchd.text}
Expand Down
132 changes: 84 additions & 48 deletions modules/system/etc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ let
mkTextDerivation = name: text: pkgs.writeText "etc-${name}" text;
};

hasDir = path: length (splitString "/" path) > 1;

etc = filter (f: f.enable) (attrValues config.environment.etc);
etcCopy = filter (f: f.copy) (attrValues config.environment.etc);
etcDirs = filter (attr: hasDir attr.target) (attrValues config.environment.etc);

in

Expand Down Expand Up @@ -42,64 +39,103 @@ in
${concatMapStringsSep "\n" (attr: "touch '${attr.target}'.copy") etcCopy}
'';

system.activationScripts.etcChecks.text = ''
declare -A etcSha256Hashes=(
${concatMapStringsSep "\n "
(attr:
"[${escapeShellArg attr.target}]=" +
escapeShellArg (concatStringsSep " " attr.knownSha256Hashes))
etc}
)
declare -a etcProblems
while IFS= read -r -d "" configFile; do
subPath=''${configFile#"$systemConfig"/etc/}
etcStaticFile=/etc/static/$subPath
etcFile=/etc/$subPath
if [[ -e $configFile.copy ]]; then
continue
fi
# We need to check files that exist and aren't already links to
# $etcStaticFile for known hashes.
if [[
-e $etcFile
&& $(readlink "$etcFile") != "$etcStaticFile"
]]; then
# Only check hashes of paths that resolve to regular files;
# everything else (e.g. directories) we complain about
# unconditionally.
if [[ -f $(readlink -f "$etcFile") ]]; then
etcFileSha256Output=$(shasum -a 256 "$etcFile")
etcFileSha256Hash=''${etcFileSha256Output%% *}
for knownSha256Hash in ''${etcSha256Hashes[$subPath]}; do
if [[ $etcFileSha256Hash == "$knownSha256Hash" ]]; then
# Hash matches, OK to overwrite; go to the next file.
continue 2
fi
done
fi
etcProblems+=("$etcFile")
fi
done < <(find -H "$systemConfig/etc" -type l -print0)
if (( ''${#etcProblems[@]} )); then
printf >&2 '\x1B[1;31merror: Unexpected files in /etc, aborting '
printf >&2 'activation\x1B[0m\n'
printf >&2 'The following files have unrecognized content and would be '
printf >&2 'overwritten:\n\n'
printf >&2 ' %s\n' "''${etcProblems[@]}"
printf >&2 '\nPlease check there is nothing critical in these files, '
printf >&2 'rename them by adding .before-nix-darwin to the end, and '
printf >&2 'then try again.\n'
exit 2
fi
'';

system.activationScripts.etc.text = ''
# Set up the statically computed bits of /etc.
echo "setting up /etc..." >&2
printf >&2 'setting up /etc...\n'
declare -A etcSha256Hashes
${concatMapStringsSep "\n" (attr: "etcSha256Hashes['/etc/${attr.target}']='${concatStringsSep " " attr.knownSha256Hashes}'") etc}
ln -sfn "$(readlink -f "$systemConfig/etc")" /etc/static
ln -sfn "$(readlink -f $systemConfig/etc)" /etc/static
while IFS= read -r -d "" etcStaticFile; do
etcFile=/etc/''${etcStaticFile#/etc/static/}
etcDir=''${etcFile%/*}
errorOccurred=false
for f in $(find /etc/static/* -type l); do
l=/etc/''${f#/etc/static/}
d=''${l%/*}
if [ ! -e "$d" ]; then
mkdir -p "$d"
if [[ ! -d $etcDir ]]; then
mkdir -p "$etcDir"
fi
if [ -e "$f".copy ]; then
cp "$f" "$l"
if [[ -e $etcStaticFile.copy ]]; then
cp "$etcStaticFile" "$etcFile"
continue
fi
if [ -e "$l" ]; then
if [ "$(readlink "$l")" != "$f" ]; then
if ! grep -q /etc/static "$l"; then
o=''$(shasum -a256 "$l")
o=''${o%% *}
for h in ''${etcSha256Hashes["$l"]}; do
if [ "$o" = "$h" ]; then
mv "$l" "$l.before-nix-darwin"
ln -s "$f" "$l"
break
else
h=
fi
done
if [ -z "$h" ]; then
echo "error: not linking environment.etc.\"''${l#/etc/}\" because $l already exists, skipping..." >&2
echo "existing file has unknown content $o, move and activate again to apply" >&2
errorOccurred=true
fi
fi
if [[ -e $etcFile ]]; then
if [[ $(readlink -- "$etcFile") == "$etcStaticFile" ]]; then
continue
else
mv "$etcFile" "$etcFile.before-nix-darwin"
fi
else
ln -s "$f" "$l"
fi
done
if [ "$errorOccurred" != "false" ]; then
exit 1
fi
ln -s "$etcStaticFile" "$etcFile"
done < <(find -H /etc/static -type l -print0)
while IFS= read -r -d "" etcFile; do
etcStaticFile=/etc/static/''${etcFile#/etc/}
for l in $(find /etc/* -type l 2> /dev/null); do
f="$(echo $l | sed 's,/etc/,/etc/static/,')"
f=/etc/static/''${l#/etc/}
if [ "$(readlink "$l")" = "$f" -a ! -e "$(readlink -f "$l")" ]; then
rm "$l"
# Delete stale links into /etc/static.
if [[
$(readlink "$etcFile") == "$etcStaticFile"
&& ! -e $etcStaticFile
]]; then
rm "$etcFile"
fi
done
done < <(find -H /etc -type l -print0)
'';

};
Expand Down