diff options
| -rwxr-xr-x | cac | 375 | 
1 files changed, 375 insertions, 0 deletions
| @@ -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 | 
