2018-07-12 14:30:10 +02:00
|
|
|
with import ../../lib;
|
|
|
|
with shell;
|
|
|
|
|
2020-02-17 18:40:50 +01:00
|
|
|
{ coreutils, dash, findutils, git, jq, openssh, pass, rsync, writers }:
|
2018-02-28 21:02:21 +01:00
|
|
|
|
|
|
|
let
|
2018-07-12 14:30:10 +02:00
|
|
|
check = { force, target }: let
|
|
|
|
sentinelFile = "${target.path}/.populate";
|
2020-06-08 23:11:36 +02:00
|
|
|
in runShell target /* sh */ ''
|
2018-07-12 14:30:10 +02:00
|
|
|
${optionalString force /* sh */ ''
|
2018-11-20 00:52:29 +01:00
|
|
|
mkdir -vp ${quote (dirOf sentinelFile)} >&2
|
2018-07-12 14:30:10 +02:00
|
|
|
touch ${quote sentinelFile}
|
|
|
|
''}
|
2019-07-16 21:38:58 +02:00
|
|
|
if ! test -e ${quote sentinelFile}; then
|
2018-07-12 14:30:10 +02:00
|
|
|
>&2 printf 'error: missing sentinel file: %s\n' ${quote (
|
|
|
|
optionalString (!isLocalTarget target) "${target.host}:" +
|
|
|
|
sentinelFile
|
|
|
|
)}
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
|
2019-07-16 22:12:12 +02:00
|
|
|
do-backup = { target }: let
|
|
|
|
sentinelFile = "${target.path}/.populate";
|
|
|
|
in
|
2020-06-08 23:11:36 +02:00
|
|
|
runShell target /* sh */ ''
|
2019-07-16 22:12:12 +02:00
|
|
|
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/
|
|
|
|
'';
|
|
|
|
|
2020-06-08 23:11:36 +02:00
|
|
|
pop.derivation = target: source: runShell target /* sh */ ''
|
2019-01-28 19:24:19 +01:00
|
|
|
nix-build -E ${quote source.text} -o ${quote target.path} >&2
|
2018-12-01 04:20:50 +01:00
|
|
|
'';
|
|
|
|
|
2018-11-28 08:34:19 +01:00
|
|
|
pop.file = target: source: let
|
2021-10-26 13:05:49 +02:00
|
|
|
config = rsyncDefaultConfig // derivedConfig // sourceConfig;
|
|
|
|
derivedConfig = {
|
|
|
|
useChecksum =
|
2021-10-26 19:36:12 +02:00
|
|
|
if isStorePath source.path
|
2021-10-26 13:05:49 +02:00
|
|
|
then true
|
|
|
|
else rsyncDefaultConfig.useChecksum;
|
|
|
|
};
|
|
|
|
sourceConfig =
|
|
|
|
filterAttrs (name: _: elem name (attrNames rsyncDefaultConfig)) source;
|
|
|
|
sourcePath =
|
2021-10-26 19:36:12 +02:00
|
|
|
if isStorePath source.path
|
2021-10-26 13:05:49 +02:00
|
|
|
then quote (toString source.path)
|
|
|
|
else quote source.path;
|
2018-11-28 08:34:19 +01:00
|
|
|
in
|
2021-10-26 13:05:49 +02:00
|
|
|
rsync' target config sourcePath;
|
2018-07-12 14:30:10 +02:00
|
|
|
|
2020-06-08 23:11:36 +02:00
|
|
|
pop.git = target: source: runShell target /* sh */ ''
|
2018-11-30 18:16:20 +01:00
|
|
|
set -efu
|
2022-02-12 10:06:34 +01:00
|
|
|
# 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
|
2018-07-12 14:30:10 +02:00
|
|
|
if ! test -e ${quote target.path}; then
|
2020-07-02 09:27:55 +02:00
|
|
|
${if source.shallow then /* sh */ ''
|
|
|
|
git init ${quote target.path}
|
|
|
|
'' else /* sh */ ''
|
2022-02-12 10:07:17 +01:00
|
|
|
git clone --recurse-submodules ${quote source.url} ${quote target.path}
|
2020-07-02 09:27:55 +02:00
|
|
|
''}
|
2018-07-12 14:30:10 +02:00
|
|
|
fi
|
|
|
|
cd ${quote target.path}
|
|
|
|
if ! url=$(git config remote.origin.url); then
|
2018-09-19 19:40:36 +02:00
|
|
|
git remote add origin ${quote source.url}
|
|
|
|
elif test "$url" != ${quote source.url}; then
|
|
|
|
git remote set-url origin ${quote source.url}
|
2018-07-12 14:30:10 +02:00
|
|
|
fi
|
|
|
|
|
|
|
|
# TODO resolve git_ref to commit hash
|
2018-09-19 19:40:36 +02:00
|
|
|
hash=${quote source.ref}
|
2018-07-12 14:30:10 +02:00
|
|
|
|
|
|
|
if ! test "$(git log --format=%H -1)" = "$hash"; then
|
2019-08-08 17:12:48 +02:00
|
|
|
${if source.fetchAlways then /* sh */ ''
|
2020-07-02 09:27:55 +02:00
|
|
|
${if source.shallow then /* sh */ ''
|
|
|
|
git fetch --depth=1 origin "$hash"
|
|
|
|
'' else /* sh */ ''
|
|
|
|
git fetch origin
|
|
|
|
''}
|
2019-08-08 17:12:48 +02:00
|
|
|
'' else /* sh */ ''
|
|
|
|
if ! git log -1 "$hash" >/dev/null 2>&1; then
|
2020-07-02 09:27:55 +02:00
|
|
|
${if source.shallow then /* sh */ ''
|
|
|
|
git fetch --depth=1 origin "$hash"
|
|
|
|
'' else /* sh */ ''
|
|
|
|
git fetch origin
|
|
|
|
''}
|
2019-08-08 17:12:48 +02:00
|
|
|
fi
|
|
|
|
''}
|
2018-11-13 22:52:44 +01:00
|
|
|
git reset --hard "$hash" >&2
|
2018-08-14 19:34:22 +02:00
|
|
|
git submodule update --init --recursive
|
2018-07-12 14:30:10 +02:00
|
|
|
fi
|
|
|
|
|
2018-10-31 18:24:57 +01:00
|
|
|
git clean -dfx \
|
|
|
|
${concatMapStringsSep " "
|
|
|
|
(pattern: /* sh */ "-e ${quote pattern}")
|
|
|
|
source.clean.exclude }
|
2018-07-12 14:30:10 +02:00
|
|
|
'';
|
|
|
|
|
2018-09-19 19:40:36 +02:00
|
|
|
pop.pass = target: source: let
|
|
|
|
passPrefix = "${source.dir}/${source.name}";
|
2018-07-12 14:30:10 +02:00
|
|
|
in /* sh */ ''
|
2018-12-14 20:07:49 +01:00
|
|
|
set -efu
|
|
|
|
|
2018-07-12 14:30:10 +02:00
|
|
|
umask 0077
|
|
|
|
|
2018-11-30 09:02:46 +01:00
|
|
|
if test -e ${quote source.dir}/.git; then
|
2018-12-14 20:14:33 +01:00
|
|
|
local_pass_info=${quote source.name}\ $(${git}/bin/git -C ${quote source.dir} log -1 --format=%H ${quote source.name})
|
2020-06-08 23:11:36 +02:00
|
|
|
remote_pass_info=$(${runShell target /* sh */ ''
|
2018-11-30 09:02:46 +01:00
|
|
|
cat ${quote target.path}/.pass_info || :
|
|
|
|
''})
|
|
|
|
|
|
|
|
if test "$local_pass_info" = "$remote_pass_info"; then
|
|
|
|
exit 0
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
2018-07-12 14:30:10 +02:00
|
|
|
tmp_dir=$(${coreutils}/bin/mktemp -dt populate-pass.XXXXXXXX)
|
|
|
|
trap cleanup EXIT
|
|
|
|
cleanup() {
|
|
|
|
rm -fR "$tmp_dir"
|
|
|
|
}
|
|
|
|
|
2021-02-16 17:12:05 +01:00
|
|
|
${findutils}/bin/find ${quote passPrefix} -type f -follow ! -name .gpg-id |
|
2018-07-12 14:30:10 +02:00
|
|
|
while read -r gpg_path; do
|
|
|
|
|
|
|
|
rel_name=''${gpg_path#${quote passPrefix}}
|
|
|
|
rel_name=''${rel_name%.gpg}
|
|
|
|
|
|
|
|
pass_date=$(
|
2021-01-17 16:19:41 +01:00
|
|
|
if test -e ${quote source.dir}/.git; then
|
|
|
|
${git}/bin/git -C ${quote source.dir} log -1 --format=%aI "$gpg_path"
|
|
|
|
fi
|
2018-07-12 14:30:10 +02:00
|
|
|
)
|
2018-09-19 19:40:36 +02:00
|
|
|
pass_name=${quote source.name}/$rel_name
|
2018-07-12 14:30:10 +02:00
|
|
|
tmp_path=$tmp_dir/$rel_name
|
|
|
|
|
|
|
|
${coreutils}/bin/mkdir -p "$(${coreutils}/bin/dirname "$tmp_path")"
|
2018-09-19 19:40:36 +02:00
|
|
|
PASSWORD_STORE_DIR=${quote source.dir} ${pass}/bin/pass show "$pass_name" > "$tmp_path"
|
2021-01-17 16:19:41 +01:00
|
|
|
if [ -n "$pass_date" ]; then
|
|
|
|
${coreutils}/bin/touch -d "$pass_date" "$tmp_path"
|
|
|
|
fi
|
2018-07-12 14:30:10 +02:00
|
|
|
done
|
|
|
|
|
2018-11-30 09:02:46 +01:00
|
|
|
if test -n "''${local_pass_info-}"; then
|
|
|
|
echo "$local_pass_info" > "$tmp_dir"/.pass_info
|
|
|
|
fi
|
|
|
|
|
2021-10-26 12:48:22 +02:00
|
|
|
${rsync' target rsyncDefaultConfig /* sh */ "$tmp_dir"}
|
2018-07-12 14:30:10 +02:00
|
|
|
'';
|
|
|
|
|
2018-09-19 19:40:36 +02:00
|
|
|
pop.pipe = target: source: /* sh */ ''
|
|
|
|
${quote source.command} | {
|
2020-06-08 23:11:36 +02:00
|
|
|
${runShell target /* sh */ "cat > ${quote target.path}"}
|
2018-07-12 14:30:10 +02:00
|
|
|
}
|
|
|
|
'';
|
|
|
|
|
|
|
|
# TODO rm -fR instead of ln -f?
|
2020-06-08 23:11:36 +02:00
|
|
|
pop.symlink = target: source: runShell target /* sh */ ''
|
2018-11-28 12:07:55 +01:00
|
|
|
ln -fnsT ${quote source.target} ${quote target.path}
|
2018-07-12 14:30:10 +02:00
|
|
|
'';
|
|
|
|
|
|
|
|
populate = target: name: source: let
|
|
|
|
source' = source.${source.type};
|
|
|
|
target' = target // { path = "${target.path}/${name}"; };
|
2020-02-17 18:40:50 +01:00
|
|
|
in writers.writeDash "populate.${target'.host}.${name}" ''
|
2018-07-12 14:30:10 +02:00
|
|
|
set -efu
|
|
|
|
${pop.${source.type} target' source'}
|
|
|
|
'';
|
|
|
|
|
2018-11-28 08:34:19 +01:00
|
|
|
rsync' = target: config: sourcePath: /* sh */ ''
|
2018-07-12 14:30:10 +02:00
|
|
|
source_path=${sourcePath}
|
|
|
|
if test -d "$source_path"; then
|
|
|
|
source_path=$source_path/
|
|
|
|
fi
|
|
|
|
${rsync}/bin/rsync \
|
2021-10-26 12:48:22 +02:00
|
|
|
${optionalString config.useChecksum /* sh */ "--checksum"} \
|
2019-11-29 12:34:31 +01:00
|
|
|
${optionalString target.sudo /* sh */ "--rsync-path=\"sudo rsync\""} \
|
2019-12-22 14:07:51 +01:00
|
|
|
${concatMapStringsSep " "
|
|
|
|
(pattern: /* sh */ "--exclude ${quote pattern}")
|
2021-10-26 12:48:22 +02:00
|
|
|
config.exclude} \
|
2020-02-03 16:37:55 +01:00
|
|
|
${concatMapStringsSep " "
|
|
|
|
(filter: /* sh */ "--${filter.type} ${quote filter.pattern}")
|
2021-10-26 12:48:22 +02:00
|
|
|
config.filters} \
|
2018-07-12 14:30:10 +02:00
|
|
|
-e ${quote (ssh' target)} \
|
|
|
|
-vFrlptD \
|
2021-10-26 12:48:22 +02:00
|
|
|
${optionalString config.deleteExcluded /* sh */ "--delete-excluded"} \
|
2018-07-12 14:30:10 +02:00
|
|
|
"$source_path" \
|
|
|
|
${quote (
|
2019-11-29 00:01:37 +01:00
|
|
|
optionalString (!isLocalTarget target) (
|
|
|
|
(optionalString (target.user != "") "${target.user}@") +
|
|
|
|
"${target.host}:"
|
|
|
|
) +
|
2018-07-12 14:30:10 +02:00
|
|
|
target.path
|
2018-09-13 21:41:00 +02:00
|
|
|
)} \
|
|
|
|
>&2
|
2018-07-12 14:30:10 +02:00
|
|
|
'';
|
|
|
|
|
2021-10-26 12:48:22 +02:00
|
|
|
rsyncDefaultConfig = {
|
|
|
|
useChecksum = false;
|
|
|
|
exclude = [];
|
|
|
|
filters = [];
|
2021-10-26 13:05:49 +02:00
|
|
|
deleteExcluded = true;
|
2021-10-26 12:48:22 +02:00
|
|
|
};
|
|
|
|
|
2020-06-08 23:11:36 +02:00
|
|
|
runShell = target: command:
|
2018-07-12 14:30:10 +02:00
|
|
|
if isLocalTarget target
|
2020-06-08 23:11:36 +02:00
|
|
|
then command
|
2019-11-29 12:34:31 +01:00
|
|
|
else
|
|
|
|
if target.sudo then /* sh */ ''
|
2020-06-08 23:11:36 +02:00
|
|
|
${ssh' target} ${quote target.host} ${quote "sudo bash -c ${quote command}"}
|
2019-11-29 12:34:31 +01:00
|
|
|
'' else ''
|
2020-06-08 23:11:36 +02:00
|
|
|
${ssh' target} ${quote target.host} ${quote command}
|
2019-11-29 12:34:31 +01:00
|
|
|
'';
|
2018-07-12 14:30:10 +02:00
|
|
|
|
2019-11-29 00:01:37 +01:00
|
|
|
ssh' = target: concatMapStringsSep " " quote (flatten [
|
2018-07-12 14:30:10 +02:00
|
|
|
"${openssh}/bin/ssh"
|
2021-11-19 23:42:56 +01:00
|
|
|
(mkUserPortSSHOpts target)
|
2018-07-12 14:30:10 +02:00
|
|
|
"-T"
|
2020-04-18 23:05:18 +02:00
|
|
|
target.extraOptions
|
2019-11-29 00:01:37 +01:00
|
|
|
]);
|
2018-07-12 14:30:10 +02:00
|
|
|
|
2018-02-28 21:02:21 +01:00
|
|
|
in
|
|
|
|
|
2019-07-16 22:12:12 +02:00
|
|
|
{ backup ? false, force ? false, source, target }:
|
2020-02-17 18:40:50 +01:00
|
|
|
writers.writeDash "populate.${target.host}" ''
|
2018-07-12 14:30:10 +02:00
|
|
|
set -efu
|
|
|
|
${check { inherit force target; }}
|
|
|
|
set -x
|
2019-07-16 22:12:12 +02:00
|
|
|
${optionalString backup (do-backup { inherit target; })}
|
2018-07-12 14:30:10 +02:00
|
|
|
${concatStringsSep "\n" (mapAttrsToList (populate target) source)}
|
2018-02-28 21:02:21 +01:00
|
|
|
''
|