summaryrefslogtreecommitdiffstats
path: root/cac
diff options
context:
space:
mode:
authortv <tv@shackspace.de>2015-08-05 20:44:31 +0200
committertv <tv@shackspace.de>2015-08-05 20:44:31 +0200
commit66ed477cb144f7fd39afb3f748914f0683259d22 (patch)
tree8fb8e0e6c102902b131a8af86d778e2a3afda165 /cac
parentad40a05348cf9b7542a91f5430463f0bfb6f2d0c (diff)
cac: initial commit
Diffstat (limited to 'cac')
-rwxr-xr-xcac375
1 files changed, 375 insertions, 0 deletions
diff --git a/cac b/cac
new file mode 100755
index 0000000..3ee6dbb
--- /dev/null
+++ b/cac
@@ -0,0 +1,375 @@
+#! /bin/sh
+set -euf
+
+#PATH=$PWD/bin:$PATH
+#export PATH
+
+urlencode() {
+#! /bin/sh
+sed '
+ s/%/%25/g
+ s/ /%20/g
+ s/!/%21/g
+ s/"/%22/g
+ s/#/%23/g
+ s/\$/%24/g
+ s/\&/%26/g
+ s/'\''/%27/g
+ s/(/%28/g
+ s/)/%29/g
+ s/\*/%2a/g
+ s/+/%2b/g
+ s/,/%2c/g
+ s/-/%2d/g
+ s/\./%2e/g
+ s/\//%2f/g
+ s/:/%3a/g
+ s/;/%3b/g
+ s//%3e/g
+ s/?/%3f/g
+ s/@/%40/g
+ s/\[/%5b/g
+ s/\\/%5c/g
+ s/\]/%5d/g
+ s/\^/%5e/g
+ s/_/%5f/g
+ s/`/%60/g
+ s/{/%7b/g
+ s/|/%7c/g
+ s/}/%7d/g
+ s/~/%7e/g
+'
+}
+#
+
+cac_listservers_cache=$PWD/tmp/cac_listservers_cache.json
+
+
+cac() {
+ __cac_cli__command=$1
+ shift
+ __cac_cli__"$__cac_cli__command" "$@"
+}
+
+# WIP
+__cac_cli__help() {(
+ exec sed < "$0" -n '
+ s/^__cac_cli__\([^(]\+\)().*/\1/p
+ '
+)}
+
+# usage: console
+__cac_cli__console() {(
+ server=$(__cac_cli__getserver "$1")
+ sid=$(echo $server | jq -r .sid)
+ # TODO check reply status == ok
+ _cac_post_api_v1 console sid="$sid" | jq -r .console
+)}
+
+__cac_cli__listservers() {
+ jq -r . $cac_listservers_cache
+}
+
+__cac_cli__update() {(
+ umask 0077
+ servers=$(_cac_listservers)
+ echo $servers > $cac_listservers_cache.tmp
+ mv $cac_listservers_cache.tmp $cac_listservers_cache
+)}
+
+__cac_cli__getserver() {(
+
+ case $1 in
+ *:*)
+ k=${1%%:*}
+ v=${1#*:}
+ ;;
+ *)
+ k=label
+ v=${1#*:}
+ ;;
+ esac
+
+ if result=$(jq \
+ -e \
+ --arg k "$k" \
+ --arg v "$v" \
+ '
+ map(select(.[$k]==$v)) |
+ if (. | length) == 1 then
+ .[0]
+ else
+ null
+ end
+ ' \
+ $cac_listservers_cache); then
+ echo $result | jq -r .
+ else
+ echo "$0 getserver $k:$v => not unique server found" >&2
+ exit 23
+ fi
+)}
+
+__cac_cli__generatenetworking() {(
+ server=$(__cac_cli__getserver "$1")
+
+ hostname=$(echo $server | jq -r .label)
+
+ address=$(echo $server | jq -r .ip)
+ gateway=$(echo $server | jq -r .gateway)
+ nameserver=8.8.8.8
+ netmask=$(echo $server | jq -r .netmask)
+ prefix=$(netmask-to-prefix $netmask)
+
+ #printf '# Generated file: %s generatenetworking %s %s\n' "$0" "$1" "$2"
+ #printf '# on %s\n' "$(date -Is)"
+ #printf '\n'
+ printf '_:\n'
+ printf '\n'
+ printf '{\n'
+ printf ' networking.hostName = "%s";\n' $hostname
+ printf ' networking.interfaces.enp2s1.ip4 = [\n'
+ printf ' {\n'
+ printf ' address = "%s";\n' $address
+ printf ' prefixLength = %d;\n' $prefix
+ printf ' }\n'
+ printf ' ];\n'
+ printf ' networking.defaultGateway = "%s";\n' $gateway
+ printf ' networking.nameservers = [\n'
+ printf ' "%s"\n' $nameserver
+ printf ' ];\n'
+ printf '}\n'
+)}
+
+__cac_cli__powerop() {(
+ server=$(__cac_cli__getserver "$1")
+ action=$2
+
+ sid=$(echo $server | jq -r .sid)
+
+ reply=$(_cac_post_api_v1 powerop sid="$sid" action="$action")
+
+ case $(echo $reply | jq -r .status) in
+ ok)
+ echo $reply | jq -r . >&2
+ __cac_cli__update
+ ;;
+ *)
+ echo bad reply: >&2
+ echo $reply | jq -r . >&2
+ exit 23
+ ;;
+ esac
+)}
+__cac_cli__pushconfig() {(
+ server=$(__cac_cli__getserver "$1")
+
+ prefix=${2-/}
+
+ hostname=$(echo $server | jq -r .label)
+
+ address=$(echo $server | jq -r .ip)
+ target=root@$address
+
+ RSYNC_RSH='sshpass -e ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
+ SSHPASS=$(echo $server | jq -r .rootpass)
+ export RSYNC_RSH SSHPASS
+
+ pushgit . $target:$prefix/etc/nixos/
+ pushgit hosts $target:$prefix/etc/nixos/hosts/
+ pushgit tmp/nixpkgs/$hostname $target:$prefix/etc/nixos/nixpkgs/
+ pushdir secrets/$hostname/nix $target:$prefix/etc/nixos/secrets/
+ pushdir secrets/$hostname/rsync $target:$prefix/
+ echo "_:{imports=[./modules/$hostname];}" \
+ | $RSYNC_RSH "$target" tee "$prefix/etc/nixos/configuration.nix" \
+ > /dev/null
+
+ ## TODO chmod and chown secrets
+)}
+
+__cac_cli__setlabel() {(
+ server=$(__cac_cli__getserver "$1")
+ label=$2
+
+ sid=$(echo $server | jq -r .sid)
+
+ reply=$(_cac_post_api_v1 renameserver sid="$sid" name="$label")
+
+ case $(echo $reply | jq -r .status) in
+ ok)
+ echo $reply | jq -r . >&2
+ __cac_cli__update
+ ;;
+ *)
+ echo bad reply: >&2
+ echo $reply | jq -r . >&2
+ exit 23
+ ;;
+ esac
+)}
+
+__cac_cli__setmode() {(
+ server=$(__cac_cli__getserver "$1")
+ mode=$2
+
+ sid=$(echo $server | jq -r .sid)
+
+ reply=$(_cac_post_api_v1 runmode sid="$sid" mode="$mode")
+
+ case $(echo $reply | jq -r .status) in
+ ok)
+ echo $reply | jq -r . >&2
+ __cac_cli__update
+ ;;
+ *)
+ echo bad reply: >&2
+ echo $reply | jq -r .
+ exit 23
+ ;;
+ esac
+)}
+
+__cac_cli__ssh() {(
+ server=$(__cac_cli__getserver "$1")
+ shift
+
+ address=$(echo $server | jq -r .ip)
+ target=root@$address
+
+ SSHPASS=$(echo $server | jq -r .rootpass)
+ export SSHPASS
+
+ exec sshpass -e ssh \
+ -S none \
+ -o StrictHostKeyChecking=no \
+ -o UserKnownHostsFile=/dev/null \
+ $target \
+ "$@"
+)}
+
+
+# usage: ./cac waitstatus mode:Safe 'Powered On'
+# blocks until server has specfied state
+__cac_cli__waitstatus() {
+ server=$(__cac_cli__getserver "$1")
+ status=$(echo $server | jq -r .status)
+
+ case $status in
+ $2)
+ return
+ ;;
+ esac
+
+ echo "$(date -Is) Waiting for status: $2; current status: $status ..." >&2
+
+ __cac_cli__waitforcacheupdate __cac_cli__waitstatus "$@"
+}
+
+
+# XXX for __cac_cli__waitforcacheupdate and __cac_cli__poll cache means $cac_listservers_cache
+
+# blocks until cache has been updated then executes "$@"
+__cac_cli__waitforcacheupdate() {
+ case $(inotifywait --format %f -q -e moved_to $(dirname $cac_listservers_cache)) in
+ $(basename $cac_listservers_cache)) "$@";;
+ *) __cac_cli__waitforcacheupdate "$@";;
+ esac
+}
+
+# usage: with cac ./cac poll 60s
+# continuously update cache, sleeping at least $1 between updates
+__cac_cli__poll() {
+ __cac_cli__update
+ t=${1-1m}
+ echo "$(date -Is) cache updated; sleeping $t ..." >&2
+ sleep "$t"
+ __cac_cli__poll "$@"
+}
+
+
+_cac_listservers() {(
+ servers=$(_cac_get_api_v1 listservers)
+ status=$(echo $servers | jq -r .status)
+
+ if [ "$status" = ok ]; then
+ echo "$servers" | jq -r .data
+ else
+ echo "cac_listservers: bad listservers status: $status" >&2
+ exit 1
+ fi
+)}
+
+
+
+
+# rsyncfiles : lines filename |> local-dir x rsync-target -> ? |> ?
+rsyncfiles() {(
+ set -x
+ rsync \
+ --rsync-path="mkdir -p \"$2\" && rsync" \
+ -vzrlptD \
+ --files-from=- \
+ "$1"/ \
+ "$2"
+)}
+
+
+# gitfiles : git-work-tree -> lines filename
+gitfiles() {
+ git -C "$1" archive --format=tar HEAD | tar t | sed '/\/$/d'
+}
+
+# pushgit : git-work-tree x rsync-target -> ?
+pushgit() {
+ gitfiles "$1" | rsyncfiles "$1" "$2"
+}
+
+# dirfiles : local-dir -> lines filename
+dirfiles() {(
+ cd "$1"
+ find . -type f | sed 's/^\.\///'
+)}
+
+# pushdir : local-dir x rsync-target -> ?
+pushdir() {
+ dirfiles "$1" | rsyncfiles "$1" "$2"
+}
+
+
+
+
+
+
+_cac_get_api_v1() {
+ _cac_curl_api_v1 -G "$@"
+}
+
+_cac_post_api_v1() {
+ _cac_curl_api_v1 -XPOST "$@"
+}
+
+_cac_curl_api_v1() {
+ _cac_exec curl -sS "$1" "https://panel.cloudatcost.com/api/v1/$2.php" $(
+ shift 2
+ set -- "$@" login="$cac_login" key="$cac_key"
+ for arg; do
+ echo -d $(printf '%s' "$arg" | urlencode)
+ done
+ )
+}
+
+_cac_exec() {
+ if test -z "${cac_via-}"; then
+ env -- "$@"
+ else
+ ssh -q "$cac_via" -t "$@"
+ fi
+}
+
+
+
+
+
+case ${run-true} in
+ true) cac "$@";;
+esac