From 66ed477cb144f7fd39afb3f748914f0683259d22 Mon Sep 17 00:00:00 2001 From: tv Date: Wed, 5 Aug 2015 20:44:31 +0200 Subject: cac: initial commit --- cac | 375 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100755 cac 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 -- cgit v1.2.3