diff --git a/nix/extra-builtins.nix b/nix/extra-builtins.nix index c95b450..94b0f87 100644 --- a/nix/extra-builtins.nix +++ b/nix/extra-builtins.nix @@ -22,8 +22,11 @@ in lenContent >= lenSuffix && builtins.substring (lenContent - lenSuffix) lenContent content == suffix; in { + # Instead of calling rage directly here, we call a wrapper script that will cache the output + # in a predictable path in /tmp, which allows us to only require the password for each encrypted + # file once. rageImportEncrypted = identities: nixFile: assert assertMsg (builtins.isPath nixFile) "The file to decrypt must be given as a path to prevent impurity."; assert assertMsg (hasSuffix ".nix.age" nixFile) "The content of the decrypted file must be a nix expression and should therefore end in .nix.age"; - exec (["rage" "-d"] ++ (builtins.concatMap (x: ["-i" x]) identities) ++ [nixFile]); + exec ([./rage-decrypt.sh nixFile] ++ identities); } diff --git a/nix/rage-decrypt.sh b/nix/rage-decrypt.sh new file mode 100755 index 0000000..5ac1780 --- /dev/null +++ b/nix/rage-decrypt.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -euo pipefail + +file="$1" +[[ "$file" == "/nix/store/"* ]] || { echo "Input must be a store path!"; exit 1; } +shift +identities=("$@") + +# Strip .age suffix and store path prefix +basename="${file%".age"}" +basename="${basename#*"-"}" + +# Calculate a unique content-based identifier (relocations of +# the source file in the nix store should not affect caching) +new_name="$(sha512sum "$file")" +new_name="${new_name:0:32}-${basename//"/"/"%"}" + +# Derive the path where the decrypted file will be stored +out="/tmp/nix-import-encrypted/$new_name" +mkdir -p "$(dirname "$out")" + +# Decrypt only if necessary +if [[ ! -e "$out" ]]; then + args=() + for i in "${identities[@]}"; do + args+=("-i" "$i") + done + rage -d "${args[@]}" -o "$out" "$file" +fi + +# Print decrypted content +cat "$out"