mirror of
https://cgit.krebsco.de/krops
synced 2024-06-26 16:38:38 +02:00
this fixes folders with symlinks not getting updated if the symlinked file is in another subfolder of the pass repo
255 lines
7.6 KiB
Nix
255 lines
7.6 KiB
Nix
with import ../../lib;
|
|
with shell;
|
|
|
|
{ coreutils, dash, findutils, git, jq, openssh, pass, rsync, writers }:
|
|
|
|
let
|
|
check = { force, target }: let
|
|
sentinelFile = "${target.path}/.populate";
|
|
in runShell target /* sh */ ''
|
|
${optionalString force /* sh */ ''
|
|
mkdir -vp ${quote (dirOf sentinelFile)} >&2
|
|
touch ${quote sentinelFile}
|
|
''}
|
|
if ! test -e ${quote sentinelFile}; then
|
|
>&2 printf 'error: missing sentinel file: %s\n' ${quote (
|
|
optionalString (!isLocalTarget target) "${target.host}:" +
|
|
sentinelFile
|
|
)}
|
|
exit 1
|
|
fi
|
|
'';
|
|
|
|
do-backup = { target }: let
|
|
sentinelFile = "${target.path}/.populate";
|
|
in
|
|
runShell target /* sh */ ''
|
|
if ! test -d ${quote sentinelFile}; then
|
|
>&2 printf 'error" sentinel file is not a directory: %s\n' ${quote (
|
|
optionalString (!isLocalTarget target) "${target.host}:" +
|
|
sentinelFile
|
|
)}
|
|
exit 1
|
|
fi
|
|
rsync >&2 \
|
|
-aAXF \
|
|
--delete \
|
|
--exclude /.populate \
|
|
--link-dest=${quote target.path} \
|
|
${target.path}/ \
|
|
${target.path}/.populate/backup/
|
|
'';
|
|
|
|
pop.derivation = target: source: runShell target /* sh */ ''
|
|
nix-build -E ${quote source.text} -o ${quote target.path} >&2
|
|
'';
|
|
|
|
pop.file = target: source: let
|
|
config = rsyncDefaultConfig // derivedConfig // sourceConfig;
|
|
derivedConfig = {
|
|
useChecksum =
|
|
if isStorePath source.path
|
|
then true
|
|
else rsyncDefaultConfig.useChecksum;
|
|
};
|
|
sourceConfig =
|
|
filterAttrs (name: _: elem name (attrNames rsyncDefaultConfig)) source;
|
|
sourcePath =
|
|
if isStorePath source.path
|
|
then quote (toString source.path)
|
|
else quote source.path;
|
|
in
|
|
rsync' target config sourcePath;
|
|
|
|
pop.git = target: source: runShell target /* sh */ ''
|
|
set -efu
|
|
# Remove target path if it doesn't look like a git worktree.
|
|
# This can happen e.g. when it had a different type earlier.
|
|
if ! test -e ${quote target.path}/.git; then
|
|
rm -fR ${quote target.path}
|
|
fi
|
|
if ! test -e ${quote target.path}; then
|
|
${if source.shallow then /* sh */ ''
|
|
git init ${quote target.path}
|
|
'' else /* sh */ ''
|
|
git clone --recurse-submodules ${quote source.url} ${quote target.path}
|
|
''}
|
|
fi
|
|
cd ${quote target.path}
|
|
if ! url=$(git config remote.origin.url); then
|
|
git remote add origin ${quote source.url}
|
|
elif test "$url" != ${quote source.url}; then
|
|
git remote set-url origin ${quote source.url}
|
|
fi
|
|
|
|
# TODO resolve git_ref to commit hash
|
|
hash=${quote source.ref}
|
|
|
|
if ! test "$(git log --format=%H -1)" = "$hash"; then
|
|
${if source.fetchAlways then /* sh */ ''
|
|
${if source.shallow then /* sh */ ''
|
|
git fetch --depth=1 origin "$hash"
|
|
'' else /* sh */ ''
|
|
git fetch origin
|
|
''}
|
|
'' else /* sh */ ''
|
|
if ! git log -1 "$hash" >/dev/null 2>&1; then
|
|
${if source.shallow then /* sh */ ''
|
|
git fetch --depth=1 origin "$hash"
|
|
'' else /* sh */ ''
|
|
git fetch origin
|
|
''}
|
|
fi
|
|
''}
|
|
git reset --hard "$hash" >&2
|
|
git submodule update --init --recursive
|
|
fi
|
|
|
|
git clean -dfx \
|
|
${concatMapStringsSep " "
|
|
(pattern: /* sh */ "-e ${quote pattern}")
|
|
source.clean.exclude }
|
|
'';
|
|
|
|
pop.pass = target: source: let
|
|
passPrefix = "${source.dir}/${source.name}";
|
|
in /* sh */ ''
|
|
set -efu
|
|
|
|
umask 0077
|
|
|
|
if test -e ${quote source.dir}/.git; then
|
|
local_pass_info=${quote source.name}\ $(
|
|
${git}/bin/git -C ${quote source.dir} log -1 --format=%H ${quote source.name}
|
|
# we append a hash for every symlink, otherwise we would miss updates on
|
|
# files where the symlink points to
|
|
${findutils}/bin/find ${quote source.dir}/${quote source.name} -type l \
|
|
-exec ${coreutils}/bin/realpath {} + |
|
|
${coreutils}/bin/sort |
|
|
${findutils}/bin/xargs -r -n 1 ${git}/bin/git -C ${quote source.dir} log -1 --format=%H
|
|
)
|
|
remote_pass_info=$(${runShell target /* sh */ ''
|
|
cat ${quote target.path}/.pass_info || :
|
|
''})
|
|
|
|
if test "$local_pass_info" = "$remote_pass_info"; then
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
tmp_dir=$(${coreutils}/bin/mktemp -dt populate-pass.XXXXXXXX)
|
|
trap cleanup EXIT
|
|
cleanup() {
|
|
rm -fR "$tmp_dir"
|
|
}
|
|
|
|
${findutils}/bin/find ${quote passPrefix} -type f -follow ! -name .gpg-id |
|
|
while read -r gpg_path; do
|
|
|
|
rel_name=''${gpg_path#${quote passPrefix}}
|
|
rel_name=''${rel_name%.gpg}
|
|
|
|
pass_date=$(
|
|
if test -e ${quote source.dir}/.git; then
|
|
${git}/bin/git -C ${quote source.dir} log -1 --format=%aI "$gpg_path"
|
|
fi
|
|
)
|
|
pass_name=${quote source.name}/$rel_name
|
|
tmp_path=$tmp_dir/$rel_name
|
|
|
|
${coreutils}/bin/mkdir -p "$(${coreutils}/bin/dirname "$tmp_path")"
|
|
PASSWORD_STORE_DIR=${quote source.dir} ${pass}/bin/pass show "$pass_name" > "$tmp_path"
|
|
if [ -n "$pass_date" ]; then
|
|
${coreutils}/bin/touch -d "$pass_date" "$tmp_path"
|
|
fi
|
|
done
|
|
|
|
if test -n "''${local_pass_info-}"; then
|
|
echo "$local_pass_info" > "$tmp_dir"/.pass_info
|
|
fi
|
|
|
|
${rsync' target rsyncDefaultConfig /* sh */ "$tmp_dir"}
|
|
'';
|
|
|
|
pop.pipe = target: source: /* sh */ ''
|
|
${quote source.command} | {
|
|
${runShell target /* sh */ "cat > ${quote target.path}"}
|
|
}
|
|
'';
|
|
|
|
# TODO rm -fR instead of ln -f?
|
|
pop.symlink = target: source: runShell target /* sh */ ''
|
|
ln -fnsT ${quote source.target} ${quote target.path}
|
|
'';
|
|
|
|
populate = target: name: source: let
|
|
source' = source.${source.type};
|
|
target' = target // { path = "${target.path}/${name}"; };
|
|
in writers.writeDash "populate.${target'.host}.${name}" ''
|
|
set -efu
|
|
${pop.${source.type} target' source'}
|
|
'';
|
|
|
|
rsync' = target: config: sourcePath: /* sh */ ''
|
|
source_path=${sourcePath}
|
|
if test -d "$source_path"; then
|
|
source_path=$source_path/
|
|
fi
|
|
${rsync}/bin/rsync \
|
|
${optionalString config.useChecksum /* sh */ "--checksum"} \
|
|
${optionalString target.sudo /* sh */ "--rsync-path=\"sudo rsync\""} \
|
|
${concatMapStringsSep " "
|
|
(pattern: /* sh */ "--exclude ${quote pattern}")
|
|
config.exclude} \
|
|
${concatMapStringsSep " "
|
|
(filter: /* sh */ "--${filter.type} ${quote filter.pattern}")
|
|
config.filters} \
|
|
-e ${quote (ssh' target)} \
|
|
-vFrlptD \
|
|
${optionalString config.deleteExcluded /* sh */ "--delete-excluded"} \
|
|
"$source_path" \
|
|
${quote (
|
|
optionalString (!isLocalTarget target) (
|
|
(optionalString (target.user != "") "${target.user}@") +
|
|
"${target.host}:"
|
|
) +
|
|
target.path
|
|
)} \
|
|
>&2
|
|
'';
|
|
|
|
rsyncDefaultConfig = {
|
|
useChecksum = false;
|
|
exclude = [];
|
|
filters = [];
|
|
deleteExcluded = true;
|
|
};
|
|
|
|
runShell = target: command:
|
|
if isLocalTarget target
|
|
then command
|
|
else
|
|
if target.sudo then /* sh */ ''
|
|
${ssh' target} ${quote target.host} ${quote "sudo bash -c ${quote command}"}
|
|
'' else ''
|
|
${ssh' target} ${quote target.host} ${quote command}
|
|
'';
|
|
|
|
ssh' = target: concatMapStringsSep " " quote (flatten [
|
|
"${openssh}/bin/ssh"
|
|
(mkUserPortSSHOpts target)
|
|
"-T"
|
|
target.extraOptions
|
|
]);
|
|
|
|
in
|
|
|
|
{ backup ? false, force ? false, source, target }:
|
|
writers.writeDash "populate.${target.host}" ''
|
|
set -efu
|
|
${check { inherit force target; }}
|
|
set -x
|
|
${optionalString backup (do-backup { inherit target; })}
|
|
${concatStringsSep "\n" (mapAttrsToList (populate target) source)}
|
|
''
|