#!/usr/bin/env sh

#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"

#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640"
#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000"
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
#DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"

_DEPLOY_DOCKER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers"

_DOCKER_HOST_DEFAULT="/var/run/docker.sock"

docker_deploy() {
  _cdomain="$1"
  _ckey="$2"
  _ccert="$3"
  _cca="$4"
  _cfullchain="$5"
  _cpfx="$6"
  _debug _cdomain "$_cdomain"
  _getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL
  _debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
  if [ -z "$DEPLOY_DOCKER_CONTAINER_LABEL" ]; then
    _err "The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container."
    _err "See: $_DEPLOY_DOCKER_WIKI"
  fi

  _savedeployconf DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"

  if [ "$DOCKER_HOST" ]; then
    _saveaccountconf DOCKER_HOST "$DOCKER_HOST"
  fi

  if _exists docker && docker version | grep -i docker >/dev/null; then
    _info "Using docker command"
    export _USE_DOCKER_COMMAND=1
  else
    export _USE_DOCKER_COMMAND=
  fi

  export _USE_UNIX_SOCKET=
  if [ -z "$_USE_DOCKER_COMMAND" ]; then
    export _USE_REST=
    if [ "$DOCKER_HOST" ]; then
      _debug "Try use docker host: $DOCKER_HOST"
      export _USE_REST=1
    else
      export _DOCKER_SOCK="$_DOCKER_HOST_DEFAULT"
      _debug "Try use $_DOCKER_SOCK"
      if [ ! -e "$_DOCKER_SOCK" ] || [ ! -w "$_DOCKER_SOCK" ]; then
        _err "$_DOCKER_SOCK is not available"
        return 1
      fi
      export _USE_UNIX_SOCKET=1
      if ! _exists "curl"; then
        _err "Please install curl first."
        _err "We need curl to work."
        return 1
      fi
      if ! _check_curl_version; then
        return 1
      fi
    fi
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE
  _debug2 DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
  if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE
  _debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
  if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER
  _debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
  if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
  _debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
  if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE
  _debug2 DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
  if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE
  _debug2 DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
  if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE
  _debug2 DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"
  if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"
  fi

  _getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
  _debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
  if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
    _savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" "base64"
  fi

  _cid="$(_get_id "$DEPLOY_DOCKER_CONTAINER_LABEL")"
  _info "Container id: $_cid"
  if [ -z "$_cid" ]; then
    _err "can not find container id"
    return 1
  fi

  if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
    if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
      return 1
    fi
    if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then
      _info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER"
      if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
        _err "Can not change owner of key file in container"
        return 1
      fi
    fi
    if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then
      _info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE"
      if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
        _err "Can not change mode of key file in container"
        return 1
      fi
    fi
  fi

  if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
    if ! _docker_cp "$_cid" "$_ccert" "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"; then
      return 1
    fi
  fi

  if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
    if ! _docker_cp "$_cid" "$_cca" "$DEPLOY_DOCKER_CONTAINER_CA_FILE"; then
      return 1
    fi
  fi

  if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
    if ! _docker_cp "$_cid" "$_cfullchain" "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"; then
      return 1
    fi
  fi

  if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then
    if ! _docker_cp "$_cid" "$_cpfx" "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"; then
      return 1
    fi
  fi

  if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
    _info "Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
    if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then
      return 1
    fi
  fi
  return 0
}

#label
_get_id() {
  _label="$1"
  if [ "$_USE_DOCKER_COMMAND" ]; then
    docker ps -f label="$_label" --format "{{.ID}}"
  elif [ "$_USE_REST" ]; then
    _err "Not implemented yet."
    return 1
  elif [ "$_USE_UNIX_SOCKET" ]; then
    _req="{\"label\":[\"$_label\"]}"
    _debug2 _req "$_req"
    _req="$(printf "%s" "$_req" | _url_encode)"
    _debug2 _req "$_req"
    listjson="$(_curl_unix_sock "${_DOCKER_SOCK:-$_DOCKER_HOST_DEFAULT}" GET "/containers/json?filters=$_req")"
    _debug2 "listjson" "$listjson"
    echo "$listjson" | tr '{,' '\n' | grep -i '"id":' | _head_n 1 | cut -d '"' -f 4
  else
    _err "Not implemented yet."
    return 1
  fi
}

#id  cmd
_docker_exec() {
  _eargs="$*"
  _debug2 "_docker_exec $_eargs"
  _dcid="$1"
  shift
  if [ "$_USE_DOCKER_COMMAND" ]; then
    docker exec -i "$_dcid" sh -c "$*"
  elif [ "$_USE_REST" ]; then
    _err "Not implemented yet."
    return 1
  elif [ "$_USE_UNIX_SOCKET" ]; then
    _cmd="$*"
    #_cmd="$(printf "%s" "$_cmd" | sed 's/ /","/g')"
    _debug2 _cmd "$_cmd"
    #create exec instance:
    cjson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/containers/$_dcid/exec" "{\"Cmd\": [\"sh\", \"-c\", \"$_cmd\"]}")"
    _debug2 cjson "$cjson"
    execid="$(echo "$cjson" | cut -d '"' -f 4)"
    _debug execid "$execid"
    #Detach:true is required for podman's docker-compatible API: with
    #Detach:false it streams the command output on the connection, so the
    #non-empty response was misread as an error (issue #4977). The real
    #result is checked via the exec inspect ExitCode below instead.
    ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")"
    _debug2 ejson "$ejson"
    _et=0
    ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
    while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do
      sleep 1
      _et="$(_math "$_et" + 1)"
      ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")"
    done
    _debug2 ijson "$ijson"
    if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then
      _err "docker exec error: $ijson"
      return 1
    fi
  else
    _err "Not implemented yet."
    return 1
  fi
}

#id from  to
_docker_cp() {
  _dcid="$1"
  _from="$2"
  _to="$3"
  _info "Copying file from $_from to $_to"
  _dir="$(dirname "$_to")"
  _debug2 _dir "$_dir"
  if ! _docker_exec "$_dcid" mkdir -p "$_dir"; then
    _err "Can not create dir: $_dir"
    return 1
  fi
  if [ "$_USE_DOCKER_COMMAND" ]; then
    if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
      _docker_exec "$_dcid" tee "$_to" <"$_from"
    else
      _docker_exec "$_dcid" tee "$_to" <"$_from" >/dev/null
    fi
    if [ "$?" = "0" ]; then
      _info "Success"
      return 0
    else
      _info "Error"
      return 1
    fi
  elif [ "$_USE_REST" ]; then
    _err "Not implemented yet."
    return 1
  elif [ "$_USE_UNIX_SOCKET" ]; then
    _frompath="$_from"
    if _startswith "$_frompath" '/'; then
      _frompath="$(echo "$_from" | cut -b 2-)" #remove the first '/' char
    fi
    _debug2 "_frompath" "$_frompath"
    _toname="$(basename "$_to")"
    _debug2 "_toname" "$_toname"
    _debug2 "_from" "$_from"
    if ! tar --transform="s,$(printf "%s" "$_frompath" | tr '*' .),$_toname," -cz "$_from" 2>/dev/null | _curl_unix_sock "$_DOCKER_SOCK" PUT "/containers/$_dcid/archive?noOverwriteDirNonDir=1&path=$(printf "%s" "$_dir" | _url_encode)" '@-' "Content-Type: application/octet-stream"; then
      _err "copy error"
      return 1
    fi
    return 0
  else
    _err "Not implemented yet."
    return 1
  fi

}

#sock method  endpoint data content-type
_curl_unix_sock() {
  _socket="$1"
  _method="$2"
  _endpoint="$3"
  _data="$4"
  _ctype="$5"
  if [ -z "$_ctype" ]; then
    _ctype="Content-Type: application/json"
  fi
  _debug _data "$_data"
  _debug2 "url" "http://localhost$_endpoint"
  if [ "$_CURL_NO_HOST" ]; then
    _cux_url="http:$_endpoint"
  else
    _cux_url="http://localhost$_endpoint"
  fi

  if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
    curl -vvv --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
  else
    curl --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
  fi

}

_check_curl_version() {
  _cversion="$(curl -V | grep '^curl ' | cut -d ' ' -f 2)"
  _debug2 "_cversion" "$_cversion"

  _major="$(_getfield "$_cversion" 1 '.')"
  _debug2 "_major" "$_major"

  _minor="$(_getfield "$_cversion" 2 '.')"
  _debug2 "_minor" "$_minor"

  if [ "$_major" -ge "8" ]; then
    #ok
    return 0
  fi
  if [ "$_major" = "7" ]; then
    if [ "$_minor" -lt "40" ]; then
      _err "curl v$_cversion doesn't support unit socket"
      _err "Please upgrade to curl 7.40 or later."
      return 1
    fi
    if [ "$_minor" -lt "50" ]; then
      _debug "Use short host name"
      export _CURL_NO_HOST=1
    else
      export _CURL_NO_HOST=
    fi
    return 0
  else
    _err "curl v$_cversion doesn't support unit socket"
    _err "Please upgrade to curl 7.40 or later."
    return 1
  fi

}
