summaryrefslogtreecommitdiffstats
path: root/bin
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2017-12-15 00:12:49 +0100
committertv <tv@krebsco.de>2017-12-15 00:12:49 +0100
commit05534c806db2883e28f9905bffae9bd30c73b838 (patch)
tree2974c0f57644097a45ece944dff9ee8d85309e00 /bin
parentca12db51773bfcc3247842fb68a7a896bd0b04d1 (diff)
replace jq-generated sh by direct shv2.0.0
Diffstat (limited to 'bin')
-rwxr-xr-xbin/populate302
1 files changed, 236 insertions, 66 deletions
diff --git a/bin/populate b/bin/populate
index fdc4c5d..ab7dce5 100755
--- a/bin/populate
+++ b/bin/populate
@@ -1,75 +1,245 @@
#! /bin/sh
set -efu
-self=$(readlink -f "$0")
-basename=${0##*/}
-prefix=${self%/bin/*}
-libdir=$prefix/lib
+main() {(
+ self=$(readlink -f "$0")
+ basename=${0##*/}
-debug=false
-force=false
-origin_host=${HOSTNAME-cat /proc/sys/kernel/hostname}
-origin_user=$LOGNAME
-ssh_cmd=ssh
-target_spec=
+ debug=false
+ force=false
+ origin_host=${HOSTNAME-cat /proc/sys/kernel/hostname}
+ origin_user=$LOGNAME
+ target_spec=
-fail=true
+ fail=true
-error() {
- echo "$basename: error: $1" >&2
- fail=false
-}
+ error() {
+ echo "$basename: error: $1" >&2
+ fail=false
+ }
-for arg; do
- case $arg in
- --debug)
- debug=true
- ;;
- --force)
- force=true
- ;;
- --ssh=*)
- ssh_cmd=${arg#--ssh=}
- ;;
- -*)
- error "bad argument: $arg"
- ;;
- *)
- if test -n "$target_spec"; then
+ for arg; do
+ case $arg in
+ --force)
+ force=true
+ ;;
+ -*)
error "bad argument: $arg"
- else
- target_spec=$arg
- fi
- ;;
- esac
-done
-
-if test -z "$target_spec"; then
- error 'no target specified'
-fi
-
-if test "$fail" != true; then
- exit 11
-fi
-
-
-script=$(jq -e -r \
- --argjson use_force "$force" \
- --arg ssh_cmd "$ssh_cmd" \
- --arg target_spec "$target_spec" \
- --arg origin_host "$origin_host" \
- --arg origin_user "$origin_user" \
- -f "$libdir/populate.jq")
-
-if test -z "$script"; then
- error 'no script produced'
- exit 12
-fi
-
-
-if test "$debug" = true; then
- echo "$script"
-else
- eval "$script"
-fi
+ ;;
+ *)
+ if test -n "$target_spec"; then
+ error "bad argument: $arg"
+ else
+ target_spec=$arg
+ fi
+ ;;
+ esac
+ done
+
+ if test -z "$target_spec"; then
+ error 'no target specified'
+ fi
+
+ if test "$fail" != true; then
+ exit 11
+ fi
+
+ target=$(
+ echo "$target_spec" | jq -R '
+ def default(value; f): if . == null then value else f end;
+ def default(value): default(value; .);
+
+ match("^(?:([^@]+)@)?([^:/]+)(?::([^/]*))?(/.*)?")
+ | {
+ user: .captures[0].string | default("root"),
+ host: .captures[1].string,
+ port: .captures[2].string | default(22;
+ if test("^[0-9]+$") then fromjson else
+ error(@json "bad target port: \(.)")
+ end),
+ path: .captures[3].string | default("/var/src"),
+ }
+ '
+ )
+
+ target_host=$(echo $target | jq -r .host)
+ target_path=$(echo $target | jq -r .path)
+ target_port=$(echo $target | jq -r .port)
+ target_user=$(echo $target | jq -r .user)
+
+ if test "$force" = true; then
+ force_target
+ else
+ check_target
+ fi
+
+ jq -c 'to_entries | group_by(.value.type) | flatten[]' |
+ while read -r source; do
+ key=$(echo "$source" | jq -r .key)
+ type=$(echo "$source" | jq -r .value.type)
+ case $type in
+ file)
+ file_path=$(echo "$source" | jq -r .value.file.path)
+ populate_file "$key" "$file_path"
+ ;;
+ git)
+ git_url=$(echo "$source" | jq -r .value.git.url)
+ git_ref=$(echo "$source" | jq -r .value.git.ref)
+ populate_git "$key" "$git_url" "$git_ref"
+ ;;
+ symlink)
+ symlink_target=$(echo "$source" | jq -r .value.symlink.target)
+ populate_symlink "$key" "$symlink_target"
+ ;;
+ *)
+ echo "Warning: ignoring $source" >&2
+ ;;
+ esac
+ done
+)}
+
+# Safeguard to prevent clobbering of misspelled targets.
+# This function has to be called first.
+check_target() {
+ {
+ echo target_host=$(quote "$target_host")
+ echo target_path=$(quote "$target_path")
+ echo 'sentinel_file=$target_path/.populate'
+ echo 'if ! test -f "$sentinel_file"; then'
+ echo ' echo "error: missing sentinel file: $target_host:$sentinel_file" >&2'
+ echo ' exit 1'
+ echo 'fi'
+ } \
+ |
+ target_shell
+}
+
+force_target() {
+ {
+ echo target_path=$(quote "$target_path")
+ echo 'sentinel_file=$target_path/.populate'
+ echo 'mkdir -vp "$target_path"'
+ echo 'touch "$sentinel_file"'
+ } \
+ |
+ target_shell
+}
+
+is_local_target() {
+ test "$target_host" = "$origin_host" &&
+ test "$target_user" = "$origin_user"
+}
+
+populate_file() {(
+ print_info populate_file "$@"
+
+ file_name=$1
+ file_path=$2
+
+ if is_local_target; then
+ file_target=$target_path/$file_name
+ else
+ file_target=$target_user@$target_host:$target_path/$file_name
+ fi
+
+ rsync \
+ -vFrlptD \
+ --delete-excluded \
+ "$file_path"/ \
+ -e "ssh -o ControlPersist=no -p $target_port" \
+ "$file_target"
+)}
+
+populate_git() {(
+ print_info populate_git "$@"
+
+ git_name=$1
+ git_url=$2
+ git_ref=$3
+
+ git_work_tree=$target_path/$git_name
+
+ {
+ echo set -efu
+
+ echo git_name=$(quote "$git_name")
+ echo git_url=$(quote "$git_url")
+ echo git_ref=$(quote "$git_ref")
+
+ echo git_work_tree=$(quote "$git_work_tree")
+
+ echo 'if ! test -e "$git_work_tree"; then'
+ echo ' git clone "$git_url" "$git_work_tree"'
+ echo 'fi'
+
+ echo 'cd $git_work_tree'
+
+ echo 'if ! url=$(git config remote.origin.url); then'
+ echo ' git remote add origin "$git_url"'
+ echo 'elif test "$url" != "$git_url"; then'
+ echo ' git remote set-url origin "$git_url"'
+ echo 'fi'
+
+ # TODO resolve git_ref to commit hash
+ echo 'hash=$git_ref'
+
+ echo 'if ! test "$(git log --format=%H -1)" = "$hash"; then'
+ echo ' if ! git log -1 "$hash" >/dev/null 2>&1; then'
+ echo ' git fetch origin'
+ echo ' fi'
+ echo ' git checkout "$hash" -- "$git_work_tree"'
+ echo ' git -c advice.detachedHead=false checkout -f "$hash"'
+ echo 'fi'
+
+ echo 'git clean -dfx'
+
+ } \
+ |
+ target_shell
+)}
+
+populate_symlink() {(
+ print_info populate_symlink "$@"
+
+ symlink_name=$1
+ symlink_source=$2
+
+ symlink_target=$target_path/$symlink_name
+
+ {
+ # TODO rm -fR instead of ln -f?
+ echo ln -fns $(quote "$symlink_source" "$symlink_target")
+ } \
+ |
+ target_shell
+)}
+
+print_info() {
+ printf '\e[1;33m* %s\e[m\n' "$*" >&2
+}
+
+quote() {
+ printf %s "$1" | sed 's/./\\&/g'
+ while test $# -gt 1; do
+ printf ' '
+ shift
+ printf %s "$1" | sed 's/./\\&/g'
+ done
+ echo
+}
+
+target_shell() {
+ if is_local_target; then
+ /bin/sh
+ else
+ ssh "$target_host" \
+ -l "$target_user" \
+ -o ControlPersist=no \
+ -p "$target_port" \
+ -T \
+ /bin/sh
+ fi
+}
+
+main "$@"