diff options
| author | Emily <vcs@emily.moe> | 2023-07-12 08:35:11 +0100 |
|---|---|---|
| committer | Emily <vcs@emily.moe> | 2023-07-16 12:15:07 +0100 |
| commit | 4eb1c549a9d43c4a85373dbf63986869aab9589b (patch) | |
| tree | f886d567391a7fc075ff38651c8c6d4c9701da4e /modules | |
| parent | c91c3519435a267dc7bcfa78bbd6ba158435f7f4 (diff) | |
etc: check for existing files during checks stage
This ensures that activation fails early if there are any `/etc` files
with unexpected state, rather than leaving the system half-activated.
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/services/activate-system/default.nix | 3 | ||||
| -rw-r--r-- | modules/system/activation-scripts.nix | 4 | ||||
| -rw-r--r-- | modules/system/etc.nix | 122 |
3 files changed, 84 insertions, 45 deletions
diff --git a/modules/services/activate-system/default.nix b/modules/services/activate-system/default.nix index 90b7d3c..19034a1 100644 --- a/modules/services/activate-system/default.nix +++ b/modules/services/activate-system/default.nix @@ -3,8 +3,6 @@ with lib; let - inherit (pkgs) stdenv; - cfg = config.services.activate-system; in @@ -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} ''; diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index b92a692..67d69be 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -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} @@ -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} diff --git a/modules/system/etc.nix b/modules/system/etc.nix index 05cc5de..9608033 100644 --- a/modules/system/etc.nix +++ b/modules/system/etc.nix @@ -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 @@ -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 - - errorOccurred=false - for etcStaticFile in $(find /etc/static/* -type l); do + while IFS= read -r -d "" etcStaticFile; do etcFile=/etc/''${etcStaticFile#/etc/static/} etcDir=''${etcFile%/*} - if [ ! -e "$etcDir" ]; then + + if [[ ! -d $etcDir ]]; then mkdir -p "$etcDir" fi - if [ -e "$etcStaticFile".copy ]; then + + if [[ -e $etcStaticFile.copy ]]; then cp "$etcStaticFile" "$etcFile" continue fi - if [ -e "$etcFile" ]; then - if [ "$(readlink "$etcFile")" != "$etcStaticFile" ]; then - if ! grep -q /etc/static "$etcFile"; then - etcFileSha256=''$(shasum -a256 "$etcFile") - etcFileSha256=''${etcFileSha256%% *} - for knownSha256Hash in ''${etcSha256Hashes["$etcFile"]}; do - if [ "$etcFileSha256" = "$knownSha256Hash" ]; then - mv "$etcFile" "$etcFile.before-nix-darwin" - ln -s "$etcStaticFile" "$etcFile" - break - else - knownSha256Hash= - fi - done - - if [ -z "$knownSha256Hash" ]; then - echo "[1;31merror: not linking environment.etc.\"''${etcFile#/etc/}\" because $etcFile already exists, skipping...[0m" >&2 - echo "[1;31mexisting file has unknown content $etcFileSha256, move and activate again to apply[0m" >&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 "$etcStaticFile" "$etcFile" fi - done - if [ "$errorOccurred" != "false" ]; then - exit 1 - fi + ln -s "$etcStaticFile" "$etcFile" + done < <(find -H /etc/static -type l -print0) - for etcFile in $(find /etc/* -type l 2> /dev/null); do - etcStaticFile="$(echo $etcFile | sed 's,/etc/,/etc/static/,')" + while IFS= read -r -d "" etcFile; do etcStaticFile=/etc/static/''${etcFile#/etc/} - if [ "$(readlink "$etcFile")" = "$etcStaticFile" -a ! -e "$(readlink -f "$etcFile")" ]; then + + # Delete stale links into /etc/static. + if [[ + $(readlink "$etcFile") == "$etcStaticFile" + && ! -e $etcStaticFile + ]]; then rm "$etcFile" fi - done + done < <(find -H /etc -type l -print0) ''; }; |
