baerengraben
5 days ago
22 changed files with 1469 additions and 146 deletions
-
4acme.sh
-
120deploy/proxmoxbs.sh
-
2deploy/truenas.sh
-
294deploy/truenas_ws.sh
-
91deploy/unifi.sh
-
2dnsapi/dns_azure.sh
-
281dnsapi/dns_beget.sh
-
19dnsapi/dns_cyon.sh
-
38dnsapi/dns_he_ddns.sh
-
18dnsapi/dns_limacity.sh
-
215dnsapi/dns_mijnhost.sh
-
10dnsapi/dns_myapi.sh
-
6dnsapi/dns_netcup.sh
-
4dnsapi/dns_omglol.sh
-
1dnsapi/dns_openprovider.sh
-
2dnsapi/dns_pdns.sh
-
463dnsapi/dns_selectel.sh
-
7dnsapi/dns_technitium.sh
-
14dnsapi/dns_west_cn.sh
-
2dnsapi/dns_yandex360.sh
-
20dnsapi/dns_zoneedit.sh
-
2notify/aws_ses.sh
@ -0,0 +1,120 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# Deploy certificates to a proxmox backup server using the API. |
||||
|
# |
||||
|
# Environment variables that can be set are: |
||||
|
# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to |
||||
|
# _cdomain. |
||||
|
# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on. |
||||
|
# Defaults to 8007. |
||||
|
# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root. |
||||
|
# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates |
||||
|
# with. Defaults to pam. |
||||
|
# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the |
||||
|
# user account. Defaults to acme. |
||||
|
# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required. |
||||
|
|
||||
|
proxmoxbs_deploy() { |
||||
|
_cdomain="$1" |
||||
|
_ckey="$2" |
||||
|
_ccert="$3" |
||||
|
_cca="$4" |
||||
|
_cfullchain="$5" |
||||
|
|
||||
|
_debug _cdomain "$_cdomain" |
||||
|
_debug2 _ckey "$_ckey" |
||||
|
_debug _ccert "$_ccert" |
||||
|
_debug _cca "$_cca" |
||||
|
_debug _cfullchain "$_cfullchain" |
||||
|
|
||||
|
# "Sane" defaults. |
||||
|
_getdeployconf DEPLOY_PROXMOXBS_SERVER |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_SERVER" ]; then |
||||
|
_target_hostname="$_cdomain" |
||||
|
else |
||||
|
_target_hostname="$DEPLOY_PROXMOXBS_SERVER" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_SERVER "$DEPLOY_PROXMOXBS_SERVER" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_SERVER "$_target_hostname" |
||||
|
|
||||
|
_getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_SERVER_PORT" ]; then |
||||
|
_target_port="8007" |
||||
|
else |
||||
|
_target_port="$DEPLOY_PROXMOXBS_SERVER_PORT" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT "$DEPLOY_PROXMOXBS_SERVER_PORT" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_SERVER_PORT "$_target_port" |
||||
|
|
||||
|
# Complete URL. |
||||
|
_target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom" |
||||
|
_debug TARGET_URL "$_target_url" |
||||
|
|
||||
|
# More "sane" defaults. |
||||
|
_getdeployconf DEPLOY_PROXMOXBS_USER |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_USER" ]; then |
||||
|
_proxmoxbs_user="root" |
||||
|
else |
||||
|
_proxmoxbs_user="$DEPLOY_PROXMOXBS_USER" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_USER "$DEPLOY_PROXMOXBS_USER" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_USER "$_proxmoxbs_user" |
||||
|
|
||||
|
_getdeployconf DEPLOY_PROXMOXBS_USER_REALM |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_USER_REALM" ]; then |
||||
|
_proxmoxbs_user_realm="pam" |
||||
|
else |
||||
|
_proxmoxbs_user_realm="$DEPLOY_PROXMOXBS_USER_REALM" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_USER_REALM "$DEPLOY_PROXMOXBS_USER_REALM" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_USER_REALM "$_proxmoxbs_user_realm" |
||||
|
|
||||
|
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" ]; then |
||||
|
_proxmoxbs_api_token_name="acme" |
||||
|
else |
||||
|
_proxmoxbs_api_token_name="$DEPLOY_PROXMOXBS_API_TOKEN_NAME" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME "$_proxmoxbs_api_token_name" |
||||
|
|
||||
|
# This is required. |
||||
|
_getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY |
||||
|
if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" ]; then |
||||
|
_err "API key not provided." |
||||
|
return 1 |
||||
|
else |
||||
|
_proxmoxbs_api_token_key="$DEPLOY_PROXMOXBS_API_TOKEN_KEY" |
||||
|
_savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" |
||||
|
fi |
||||
|
_debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY "$_proxmoxbs_api_token_key" |
||||
|
|
||||
|
# PBS API Token header value. Used in "Authorization: PBSAPIToken". |
||||
|
_proxmoxbs_header_api_token="${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}" |
||||
|
_debug2 "Auth Header" "$_proxmoxbs_header_api_token" |
||||
|
|
||||
|
# Ugly. I hate putting heredocs inside functions because heredocs don't |
||||
|
# account for whitespace correctly but it _does_ work and is several times |
||||
|
# cleaner than anything else I had here. |
||||
|
# |
||||
|
# This dumps the json payload to a variable that should be passable to the |
||||
|
# _psot function. |
||||
|
_json_payload=$( |
||||
|
cat <<HEREDOC |
||||
|
{ |
||||
|
"certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')", |
||||
|
"key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')", |
||||
|
"node":"localhost", |
||||
|
"restart":true, |
||||
|
"force":true |
||||
|
} |
||||
|
HEREDOC |
||||
|
) |
||||
|
_debug2 Payload "$_json_payload" |
||||
|
|
||||
|
_info "Push certificates to server" |
||||
|
export HTTPS_INSECURE=1 |
||||
|
export _H1="Authorization: PBSAPIToken=${_proxmoxbs_header_api_token}" |
||||
|
_post "$_json_payload" "$_target_url" "" POST "application/json" |
||||
|
|
||||
|
} |
@ -0,0 +1,294 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
|
||||
|
# TrueNAS deploy script for SCALE/CORE using websocket |
||||
|
# It is recommend to use a wildcard certificate |
||||
|
# |
||||
|
# Websocket Documentation: https://www.truenas.com/docs/api/scale_websocket_api.html |
||||
|
# |
||||
|
# Tested with TrueNAS Scale - Electric Eel 24.10 |
||||
|
# Changes certificate in the following services: |
||||
|
# - Web UI |
||||
|
# - FTP |
||||
|
# - iX Apps |
||||
|
# |
||||
|
# The following environment variables must be set: |
||||
|
# ------------------------------------------------ |
||||
|
# |
||||
|
# # API KEY |
||||
|
# # Use the folowing URL to create a new API token: <TRUENAS_HOSTNAME OR IP>/ui/apikeys |
||||
|
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI" |
||||
|
# |
||||
|
|
||||
|
### Private functions |
||||
|
|
||||
|
# Call websocket method |
||||
|
# Usage: |
||||
|
# _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'") |
||||
|
# _info "$_ws_response" |
||||
|
# |
||||
|
# Output: |
||||
|
# {"z": 9} |
||||
|
# |
||||
|
# Arguments: |
||||
|
# $@ - midclt arguments for call |
||||
|
# |
||||
|
# Returns: |
||||
|
# JSON/JOBID |
||||
|
_ws_call() { |
||||
|
_debug "_ws_call arg1" "$1" |
||||
|
_debug "_ws_call arg2" "$2" |
||||
|
_debug "_ws_call arg3" "$3" |
||||
|
if [ $# -eq 3 ]; then |
||||
|
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") |
||||
|
fi |
||||
|
if [ $# -eq 2 ]; then |
||||
|
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") |
||||
|
fi |
||||
|
if [ $# -eq 1 ]; then |
||||
|
_ws_response=$(midclt -K "$DEPLOY_TRUENAS_APIKEY" call "$1") |
||||
|
fi |
||||
|
_debug "_ws_response" "$_ws_response" |
||||
|
printf "%s" "$_ws_response" |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# Check argument is a number |
||||
|
# Usage: |
||||
|
# |
||||
|
# Output: |
||||
|
# n/a |
||||
|
# |
||||
|
# Arguments: |
||||
|
# $1 - Anything |
||||
|
# |
||||
|
# Returns: |
||||
|
# 0: true |
||||
|
# 1: false |
||||
|
_ws_check_jobid() { |
||||
|
case "$1" in |
||||
|
[0-9]*) |
||||
|
return 0 |
||||
|
;; |
||||
|
esac |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# Wait for job to finish and return result as JSON |
||||
|
# Usage: |
||||
|
# _ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
# _new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"') |
||||
|
# |
||||
|
# Output: |
||||
|
# JSON result of the job |
||||
|
# |
||||
|
# Arguments: |
||||
|
# $1 - JobID |
||||
|
# |
||||
|
# Returns: |
||||
|
# n/a |
||||
|
_ws_get_job_result() { |
||||
|
while true; do |
||||
|
sleep 2 |
||||
|
_ws_response=$(_ws_call "core.get_jobs" "[[\"id\", \"=\", $1]]") |
||||
|
if [ "$(printf "%s" "$_ws_response" | jq -r '.[]."state"')" != "RUNNING" ]; then |
||||
|
_ws_result="$(printf "%s" "$_ws_response" | jq '.[]."result"')" |
||||
|
_debug "_ws_result" "$_ws_result" |
||||
|
printf "%s" "$_ws_result" |
||||
|
_ws_error="$(printf "%s" "$_ws_response" | jq '.[]."error"')" |
||||
|
if [ "$_ws_error" != "null" ]; then |
||||
|
_err "Job $1 failed:" |
||||
|
_err "$_ws_error" |
||||
|
return 7 |
||||
|
fi |
||||
|
break |
||||
|
fi |
||||
|
done |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
######################## |
||||
|
### Public functions ### |
||||
|
######################## |
||||
|
|
||||
|
# truenas_ws_deploy |
||||
|
# |
||||
|
# Deploy new certificate to TrueNAS services |
||||
|
# |
||||
|
# Arguments |
||||
|
# 1: Domain |
||||
|
# 2: Key-File |
||||
|
# 3: Certificate-File |
||||
|
# 4: CA-File |
||||
|
# 5: FullChain-File |
||||
|
# Returns: |
||||
|
# 0: Success |
||||
|
# 1: Missing API Key |
||||
|
# 2: TrueNAS not ready |
||||
|
# 3: Not a JobID |
||||
|
# 4: FTP cert error |
||||
|
# 5: WebUI cert error |
||||
|
# 6: Job error |
||||
|
# 7: WS call error |
||||
|
# 10: No CORE or SCALE detected |
||||
|
# |
||||
|
truenas_ws_deploy() { |
||||
|
_domain="$1" |
||||
|
_file_key="$2" |
||||
|
_file_cert="$3" |
||||
|
_file_ca="$4" |
||||
|
_file_fullchain="$5" |
||||
|
_debug _domain "$_domain" |
||||
|
_debug _file_key "$_file_key" |
||||
|
_debug _file_cert "$_file_cert" |
||||
|
_debug _file_ca "$_file_ca" |
||||
|
_debug _file_fullchain "$_file_fullchain" |
||||
|
|
||||
|
########## Environment check |
||||
|
|
||||
|
_info "Checking environment variables..." |
||||
|
_getdeployconf DEPLOY_TRUENAS_APIKEY |
||||
|
# Check API Key |
||||
|
if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then |
||||
|
_err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable." |
||||
|
return 1 |
||||
|
fi |
||||
|
_secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" |
||||
|
_info "Environment variables: OK" |
||||
|
|
||||
|
########## Health check |
||||
|
|
||||
|
_info "Checking TrueNAS health..." |
||||
|
_ws_response=$(_ws_call "system.ready" | tr '[:lower:]' '[:upper:]') |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
_err "Error calling system.ready:" |
||||
|
_err "$_ws_response" |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
|
||||
|
if [ "$_ws_response" != "TRUE" ]; then |
||||
|
_err "TrueNAS is not ready." |
||||
|
_err "Please check environment variables DEPLOY_TRUENAS_APIKEY, DEPLOY_TRUENAS_HOSTNAME and DEPLOY_TRUENAS_PROTOCOL." |
||||
|
_err "Verify API key." |
||||
|
return 2 |
||||
|
fi |
||||
|
_savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" |
||||
|
_info "TrueNAS health: OK" |
||||
|
|
||||
|
########## System info |
||||
|
|
||||
|
_info "Gather system info..." |
||||
|
_ws_response=$(_ws_call "system.info") |
||||
|
_truenas_system=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 2 | tr '[:lower:]' '[:upper:]') |
||||
|
_truenas_version=$(printf "%s" "$_ws_response" | jq -r '."version"' | cut -d '-' -f 3) |
||||
|
_info "TrueNAS system: $_truenas_system" |
||||
|
_info "TrueNAS version: $_truenas_version" |
||||
|
if [ "$_truenas_system" != "SCALE" ] && [ "$_truenas_system" != "CORE" ]; then |
||||
|
_err "Cannot gather TrueNAS system. Nor CORE oder SCALE detected." |
||||
|
return 10 |
||||
|
fi |
||||
|
|
||||
|
########## Gather current certificate |
||||
|
|
||||
|
_info "Gather current WebUI certificate..." |
||||
|
_ws_response="$(_ws_call "system.general.config")" |
||||
|
_ui_certificate_id=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"') |
||||
|
_ui_certificate_name=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."name"') |
||||
|
_info "Current WebUI certificate ID: $_ui_certificate_id" |
||||
|
_info "Current WebUI certificate name: $_ui_certificate_name" |
||||
|
|
||||
|
########## Upload new certificate |
||||
|
|
||||
|
_info "Upload new certificate..." |
||||
|
_certname="acme_$(_utc_date | tr -d '\-\:' | tr ' ' '_')" |
||||
|
_info "New WebUI certificate name: $_certname" |
||||
|
_debug _certname "$_certname" |
||||
|
_ws_jobid=$(_ws_call "certificate.create" "{\"name\": \"${_certname}\", \"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"certificate\": \"$(_json_encode <"$_file_fullchain")\", \"privatekey\": \"$(_json_encode <"$_file_key")\", \"passphrase\": \"\"}") |
||||
|
_debug "_ws_jobid" "$_ws_jobid" |
||||
|
if ! _ws_check_jobid "$_ws_jobid"; then |
||||
|
_err "No JobID returned from websocket method." |
||||
|
return 3 |
||||
|
fi |
||||
|
_ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
_debug "_ws_result" "$_ws_result" |
||||
|
_new_certid=$(printf "%s" "$_ws_result" | jq -r '."id"') |
||||
|
_info "New certificate ID: $_new_certid" |
||||
|
|
||||
|
########## FTP |
||||
|
|
||||
|
_info "Replace FTP certificate..." |
||||
|
_ws_response=$(_ws_call "ftp.update" "{\"ssltls_certificate\": $_new_certid}") |
||||
|
_ftp_certid=$(printf "%s" "$_ws_response" | jq -r '."ssltls_certificate"') |
||||
|
if [ "$_ftp_certid" != "$_new_certid" ]; then |
||||
|
_err "Cannot set FTP certificate." |
||||
|
_debug "_ws_response" "$_ws_response" |
||||
|
return 4 |
||||
|
fi |
||||
|
|
||||
|
########## ix Apps (SCALE only) |
||||
|
|
||||
|
if [ "$_truenas_system" = "SCALE" ]; then |
||||
|
_info "Replace app certificates..." |
||||
|
_ws_response=$(_ws_call "app.query") |
||||
|
for _app_name in $(printf "%s" "$_ws_response" | jq -r '.[]."name"'); do |
||||
|
_info "Checking app $_app_name..." |
||||
|
_ws_response=$(_ws_call "app.config" "$_app_name") |
||||
|
if [ "$(printf "%s" "$_ws_response" | jq -r '."network" | has("certificate_id")')" = "true" ]; then |
||||
|
_info "App has certificate option, setup new certificate..." |
||||
|
_info "App will be redeployed after updating the certificate." |
||||
|
_ws_jobid=$(_ws_call "app.update" "$_app_name" "{\"values\": {\"network\": {\"certificate_id\": $_new_certid}}}") |
||||
|
_debug "_ws_jobid" "$_ws_jobid" |
||||
|
if ! _ws_check_jobid "$_ws_jobid"; then |
||||
|
_err "No JobID returned from websocket method." |
||||
|
return 3 |
||||
|
fi |
||||
|
_ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
_debug "_ws_result" "$_ws_result" |
||||
|
_info "App certificate replaced." |
||||
|
else |
||||
|
_info "App has no certificate option, skipping..." |
||||
|
fi |
||||
|
done |
||||
|
fi |
||||
|
|
||||
|
########## WebUI |
||||
|
|
||||
|
_info "Replace WebUI certificate..." |
||||
|
_ws_response=$(_ws_call "system.general.update" "{\"ui_certificate\": $_new_certid}") |
||||
|
_changed_certid=$(printf "%s" "$_ws_response" | jq -r '."ui_certificate"."id"') |
||||
|
if [ "$_changed_certid" != "$_new_certid" ]; then |
||||
|
_err "WebUI certificate change error.." |
||||
|
return 5 |
||||
|
else |
||||
|
_info "WebUI certificate replaced." |
||||
|
fi |
||||
|
_info "Restarting WebUI..." |
||||
|
_ws_response=$(_ws_call "system.general.ui_restart") |
||||
|
_info "Waiting for UI restart..." |
||||
|
sleep 6 |
||||
|
|
||||
|
########## Certificates |
||||
|
|
||||
|
_info "Deleting old certificate..." |
||||
|
_ws_jobid=$(_ws_call "certificate.delete" "$_ui_certificate_id") |
||||
|
if ! _ws_check_jobid "$_ws_jobid"; then |
||||
|
_err "No JobID returned from websocket method." |
||||
|
return 3 |
||||
|
fi |
||||
|
_ws_result=$(_ws_get_job_result "$_ws_jobid") |
||||
|
_ws_ret=$? |
||||
|
if [ $_ws_ret -gt 0 ]; then |
||||
|
return $_ws_ret |
||||
|
fi |
||||
|
|
||||
|
_info "Have a nice day...bye!" |
||||
|
|
||||
|
} |
@ -0,0 +1,281 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_beget_info='Beget.com |
||||
|
Site: Beget.com |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_beget |
||||
|
Options: |
||||
|
BEGET_User API user |
||||
|
BEGET_Password API password |
||||
|
Issues: github.com/acmesh-official/acme.sh/issues/6200 |
||||
|
Author: ARNik arnik@arnik.ru |
||||
|
' |
||||
|
|
||||
|
Beget_Api="https://api.beget.com/api" |
||||
|
|
||||
|
#################### Public functions #################### |
||||
|
|
||||
|
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
||||
|
# Used to add txt record |
||||
|
dns_beget_add() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
_debug "dns_beget_add() $fulldomain $txtvalue" |
||||
|
fulldomain=$(echo "$fulldomain" | _lower_case) |
||||
|
|
||||
|
Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" |
||||
|
Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" |
||||
|
|
||||
|
if [ -z "$Beget_Username" ] || [ -z "$Beget_Password" ]; then |
||||
|
Beget_Username="" |
||||
|
Beget_Password="" |
||||
|
_err "You must export variables: Beget_Username, and Beget_Password" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
#save the credentials to the account conf file. |
||||
|
_saveaccountconf_mutable Beget_Username "$Beget_Username" |
||||
|
_saveaccountconf_mutable Beget_Password "$Beget_Password" |
||||
|
|
||||
|
_info "Prepare subdomain." |
||||
|
if ! _prepare_subdomain "$fulldomain"; then |
||||
|
_err "Can't prepare subdomain." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Get domain records" |
||||
|
data="{\"fqdn\":\"$fulldomain\"}" |
||||
|
res=$(_api_call "$Beget_Api/dns/getData" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Add new TXT record" |
||||
|
data="{\"fqdn\":\"$fulldomain\",\"records\":{" |
||||
|
data=${data}$(_parce_records "$res" "A") |
||||
|
data=${data}$(_parce_records "$res" "AAAA") |
||||
|
data=${data}$(_parce_records "$res" "CAA") |
||||
|
data=${data}$(_parce_records "$res" "MX") |
||||
|
data=${data}$(_parce_records "$res" "SRV") |
||||
|
data=${data}$(_parce_records "$res" "TXT") |
||||
|
data=$(echo "$data" | sed 's/,$//') |
||||
|
data=${data}'}}' |
||||
|
|
||||
|
str=$(_txt_to_dns_json "$txtvalue") |
||||
|
data=$(_add_record "$data" "TXT" "$str") |
||||
|
|
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't change domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# Usage: fulldomain txtvalue |
||||
|
# Used to remove the txt record after validation |
||||
|
dns_beget_rm() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
_debug "dns_beget_rm() $fulldomain $txtvalue" |
||||
|
fulldomain=$(echo "$fulldomain" | _lower_case) |
||||
|
|
||||
|
Beget_Username="${Beget_Username:-$(_readaccountconf_mutable Beget_Username)}" |
||||
|
Beget_Password="${Beget_Password:-$(_readaccountconf_mutable Beget_Password)}" |
||||
|
|
||||
|
_info "Get current domain records" |
||||
|
data="{\"fqdn\":\"$fulldomain\"}" |
||||
|
res=$(_api_call "$Beget_Api/dns/getData" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_info "Remove TXT record" |
||||
|
data="{\"fqdn\":\"$fulldomain\",\"records\":{" |
||||
|
data=${data}$(_parce_records "$res" "A") |
||||
|
data=${data}$(_parce_records "$res" "AAAA") |
||||
|
data=${data}$(_parce_records "$res" "CAA") |
||||
|
data=${data}$(_parce_records "$res" "MX") |
||||
|
data=${data}$(_parce_records "$res" "SRV") |
||||
|
data=${data}$(_parce_records "$res" "TXT") |
||||
|
data=$(echo "$data" | sed 's/,$//') |
||||
|
data=${data}'}}' |
||||
|
|
||||
|
str=$(_txt_to_dns_json "$txtvalue") |
||||
|
data=$(_rm_record "$data" "$str") |
||||
|
|
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't change domain records." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
#################### Private functions below #################### |
||||
|
|
||||
|
# Create subdomain if needed |
||||
|
# Usage: _prepare_subdomain [fulldomain] |
||||
|
_prepare_subdomain() { |
||||
|
fulldomain=$1 |
||||
|
|
||||
|
_info "Detect the root zone" |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
_err "invalid domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
_debug _domain_id "$_domain_id" |
||||
|
_debug _sub_domain "$_sub_domain" |
||||
|
_debug _domain "$_domain" |
||||
|
|
||||
|
if [ -z "$_sub_domain" ]; then |
||||
|
_debug "$fulldomain is a root domain." |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
_info "Get subdomain list" |
||||
|
res=$(_api_call "$Beget_Api/domain/getSubdomainList") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get subdomain list." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if _contains "$res" "\"fqdn\":\"$fulldomain\""; then |
||||
|
_debug "Subdomain $fulldomain already exist." |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
_info "Subdomain $fulldomain does not exist. Let's create one." |
||||
|
data="{\"subdomain\":\"$_sub_domain\",\"domain_id\":$_domain_id}" |
||||
|
res=$(_api_call "$Beget_Api/domain/addSubdomainVirtual" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't create subdomain." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug "Cleanup subdomen records" |
||||
|
data="{\"fqdn\":\"$fulldomain\",\"records\":{}}" |
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_debug "Can't cleanup $fulldomain records." |
||||
|
fi |
||||
|
|
||||
|
data="{\"fqdn\":\"www.$fulldomain\",\"records\":{}}" |
||||
|
res=$(_api_call "$Beget_Api/dns/changeRecords" "$data") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_debug "Can't cleanup www.$fulldomain records." |
||||
|
fi |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
# Usage: _get_root _acme-challenge.www.domain.com |
||||
|
#returns |
||||
|
# _sub_domain=_acme-challenge.www |
||||
|
# _domain=domain.com |
||||
|
# _domain_id=32436365 |
||||
|
_get_root() { |
||||
|
fulldomain=$1 |
||||
|
i=1 |
||||
|
p=1 |
||||
|
|
||||
|
_debug "Get domain list" |
||||
|
res=$(_api_call "$Beget_Api/domain/getList") |
||||
|
if ! _is_api_reply_ok "$res"; then |
||||
|
_err "Can't get domain list." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
while true; do |
||||
|
h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) |
||||
|
_debug h "$h" |
||||
|
|
||||
|
if [ -z "$h" ]; then |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if _contains "$res" "$h"; then |
||||
|
_domain_id=$(echo "$res" | _egrep_o "\"id\":[0-9]*,\"fqdn\":\"$h\"" | cut -d , -f1 | cut -d : -f2) |
||||
|
if [ "$_domain_id" ]; then |
||||
|
if [ "$h" != "$fulldomain" ]; then |
||||
|
_sub_domain=$(echo "$fulldomain" | cut -d . -f 1-"$p") |
||||
|
else |
||||
|
_sub_domain="" |
||||
|
fi |
||||
|
_domain=$h |
||||
|
return 0 |
||||
|
fi |
||||
|
return 1 |
||||
|
fi |
||||
|
p="$i" |
||||
|
i=$(_math "$i" + 1) |
||||
|
done |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# Parce DNS records from json string |
||||
|
# Usage: _parce_records [j_str] [record_name] |
||||
|
_parce_records() { |
||||
|
j_str=$1 |
||||
|
record_name=$2 |
||||
|
res="\"$record_name\":[" |
||||
|
res=${res}$(echo "$j_str" | _egrep_o "\"$record_name\":\[.*" | cut -d '[' -f2 | cut -d ']' -f1) |
||||
|
res=${res}"]," |
||||
|
echo "$res" |
||||
|
} |
||||
|
|
||||
|
# Usage: _add_record [data] [record_name] [record_data] |
||||
|
_add_record() { |
||||
|
data=$1 |
||||
|
record_name=$2 |
||||
|
record_data=$3 |
||||
|
echo "$data" | sed "s/\"$record_name\":\[/\"$record_name\":\[$record_data,/" | sed "s/,\]/\]/" |
||||
|
} |
||||
|
|
||||
|
# Usage: _rm_record [data] [record_data] |
||||
|
_rm_record() { |
||||
|
data=$1 |
||||
|
record_data=$2 |
||||
|
echo "$data" | sed "s/$record_data//g" | sed "s/,\+/,/g" | |
||||
|
sed "s/{,/{/g" | sed "s/,}/}/g" | |
||||
|
sed "s/\[,/\[/g" | sed "s/,\]/\]/g" |
||||
|
} |
||||
|
|
||||
|
_txt_to_dns_json() { |
||||
|
echo "{\"ttl\":600,\"txtdata\":\"$1\"}" |
||||
|
} |
||||
|
|
||||
|
# Usage: _api_call [api_url] [input_data] |
||||
|
_api_call() { |
||||
|
api_url="$1" |
||||
|
input_data="$2" |
||||
|
|
||||
|
_debug "_api_call $api_url" |
||||
|
_debug "Request: $input_data" |
||||
|
|
||||
|
# res=$(curl -s -L -D ./http.header \ |
||||
|
# "$api_url" \ |
||||
|
# --data-urlencode login=$Beget_Username \ |
||||
|
# --data-urlencode passwd=$Beget_Password \ |
||||
|
# --data-urlencode input_format=json \ |
||||
|
# --data-urlencode output_format=json \ |
||||
|
# --data-urlencode "input_data=$input_data") |
||||
|
|
||||
|
url="$api_url?login=$Beget_Username&passwd=$Beget_Password&input_format=json&output_format=json" |
||||
|
if [ -n "$input_data" ]; then |
||||
|
url=${url}"&input_data=" |
||||
|
url=${url}$(echo "$input_data" | _url_encode) |
||||
|
fi |
||||
|
res=$(_get "$url") |
||||
|
|
||||
|
_debug "Reply: $res" |
||||
|
echo "$res" |
||||
|
} |
||||
|
|
||||
|
# Usage: _is_api_reply_ok [api_reply] |
||||
|
_is_api_reply_ok() { |
||||
|
_contains "$1" '^{"status":"success","answer":{"status":"success","result":.*}}$' |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_he_ddns_info='Hurricane Electric HE.net DDNS |
||||
|
Site: dns.he.net |
||||
|
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns |
||||
|
Options: |
||||
|
HE_DDNS_KEY The DDNS key |
||||
|
Author: Markku Leiniö |
||||
|
' |
||||
|
|
||||
|
HE_DDNS_URL="https://dyn.dns.he.net/nic/update" |
||||
|
|
||||
|
######## Public functions ##################### |
||||
|
|
||||
|
#Usage: dns_he_ddns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
||||
|
dns_he_ddns_add() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
HE_DDNS_KEY="${HE_DDNS_KEY:-$(_readaccountconf_mutable HE_DDNS_KEY)}" |
||||
|
if [ -z "$HE_DDNS_KEY" ]; then |
||||
|
HE_DDNS_KEY="" |
||||
|
_err "You didn't specify a DDNS key for accessing the TXT record in HE API." |
||||
|
return 1 |
||||
|
fi |
||||
|
#Save the DDNS key to the account conf file. |
||||
|
_saveaccountconf_mutable HE_DDNS_KEY "$HE_DDNS_KEY" |
||||
|
|
||||
|
_info "Using Hurricane Electric DDNS API" |
||||
|
_debug fulldomain "$fulldomain" |
||||
|
_debug txtvalue "$txtvalue" |
||||
|
|
||||
|
response="$(_post "hostname=$fulldomain&password=$HE_DDNS_KEY&txt=$txtvalue" "$HE_DDNS_URL")" |
||||
|
_info "Response: $response" |
||||
|
_contains "$response" "good" && return 0 || return 1 |
||||
|
} |
||||
|
|
||||
|
# dns_he_ddns_rm() is not implemented because the API call always updates the |
||||
|
# contents of the existing record (that the API key gives access to). |
@ -0,0 +1,215 @@ |
|||||
|
#!/usr/bin/env sh |
||||
|
# shellcheck disable=SC2034 |
||||
|
dns_mijnhost_info='mijn.host |
||||
|
Domains: mijn.host |
||||
|
Site: mijn.host |
||||
|
Docs: https://mijn.host/api/doc/ |
||||
|
Issues: https://github.com/acmesh-official/acme.sh/issues/6177 |
||||
|
Author: peterv99 |
||||
|
Options: |
||||
|
MIJNHOST_API_KEY API Key |
||||
|
' |
||||
|
|
||||
|
######## Public functions ###################### Constants for your mijn-host API |
||||
|
MIJNHOST_API="https://mijn.host/api/v2" |
||||
|
|
||||
|
# Add TXT record for domain verification |
||||
|
dns_mijnhost_add() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
|
||||
|
MIJNHOST_API_KEY="${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}" |
||||
|
if [ -z "$MIJNHOST_API_KEY" ]; then |
||||
|
MIJNHOST_API_KEY="" |
||||
|
_err "You haven't specified your mijn-host API key yet." |
||||
|
_err "Please add MIJNHOST_API_KEY to the env." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
# Save the API key for future use |
||||
|
_saveaccountconf_mutable MIJNHOST_API_KEY "$MIJNHOST_API_KEY" |
||||
|
|
||||
|
_debug "First detect the root zone" |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
_err "Invalid domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug2 _sub_domain "$_sub_domain" |
||||
|
_debug2 _domain "$_domain" |
||||
|
_debug "Adding DNS record" "${fulldomain}." |
||||
|
|
||||
|
# Construct the API URL |
||||
|
api_url="$MIJNHOST_API/domains/$_domain/dns" |
||||
|
|
||||
|
# Getting previous records |
||||
|
_mijnhost_rest GET "$api_url" "" |
||||
|
|
||||
|
if [ "$_code" != "200" ]; then |
||||
|
_err "Error getting current DNS enties ($_code)" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
records=$(echo "$response" | _egrep_o '"records":\[.*\]' | sed 's/"records"://') |
||||
|
|
||||
|
_debug2 "Current records" "$records" |
||||
|
|
||||
|
# Build the payload for the API |
||||
|
data="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"value\":\"$txtvalue\",\"ttl\":300}" |
||||
|
|
||||
|
_debug2 "Record to add" "$data" |
||||
|
|
||||
|
# Updating the records |
||||
|
updated_records=$(echo "$records" | sed -E "s/\]( *$)/,$data\]/") |
||||
|
|
||||
|
_debug2 "Updated records" "$updated_records" |
||||
|
|
||||
|
# data |
||||
|
data="{\"records\": $updated_records}" |
||||
|
|
||||
|
_mijnhost_rest PUT "$api_url" "$data" |
||||
|
|
||||
|
if [ "$_code" = "200" ]; then |
||||
|
_info "DNS record succesfully added." |
||||
|
return 0 |
||||
|
else |
||||
|
_err "Error adding DNS record ($_code)." |
||||
|
return 1 |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# Remove TXT record after verification |
||||
|
dns_mijnhost_rm() { |
||||
|
fulldomain=$1 |
||||
|
txtvalue=$2 |
||||
|
|
||||
|
MIJNHOST_API_KEY="${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}" |
||||
|
if [ -z "$MIJNHOST_API_KEY" ]; then |
||||
|
MIJNHOST_API_KEY="" |
||||
|
_err "You haven't specified your mijn-host API key yet." |
||||
|
_err "Please add MIJNHOST_API_KEY to the env." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug "Detecting root zone for" "${fulldomain}." |
||||
|
if ! _get_root "$fulldomain"; then |
||||
|
_err "Invalid domain" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug "Removing DNS record for TXT value" "${txtvalue}." |
||||
|
|
||||
|
# Construct the API URL |
||||
|
api_url="$MIJNHOST_API/domains/$_domain/dns" |
||||
|
|
||||
|
# Get current records |
||||
|
_mijnhost_rest GET "$api_url" "" |
||||
|
|
||||
|
if [ "$_code" != "200" ]; then |
||||
|
_err "Error getting current DNS enties ($_code)" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
_debug2 "Get current records response:" "$response" |
||||
|
|
||||
|
records=$(echo "$response" | _egrep_o '"records":\[.*\]' | sed 's/"records"://') |
||||
|
|
||||
|
_debug2 "Current records:" "$records" |
||||
|
|
||||
|
updated_records=$(echo "$records" | sed -E "s/\{[^}]*\"value\":\"$txtvalue\"[^}]*\},?//g" | sed 's/,]/]/g') |
||||
|
|
||||
|
_debug2 "Updated records:" "$updated_records" |
||||
|
|
||||
|
# Build the new payload |
||||
|
data="{\"records\": $updated_records}" |
||||
|
|
||||
|
# Use the _put method to update the records |
||||
|
_mijnhost_rest PUT "$api_url" "$data" |
||||
|
|
||||
|
if [ "$_code" = "200" ]; then |
||||
|
_info "DNS record removed successfully." |
||||
|
return 0 |
||||
|
else |
||||
|
_err "Error removing DNS record ($_code)." |
||||
|
return 1 |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
# Helper function to detect the root zone |
||||
|
_get_root() { |
||||
|
domain=$1 |
||||
|
|
||||
|
# Get current records |
||||
|
_debug "Getting current domains" |
||||
|
_mijnhost_rest GET "$MIJNHOST_API/domains" "" |
||||
|
|
||||
|
if [ "$_code" != "200" ]; then |
||||
|
_err "error getting current domains ($_code)" |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
# Extract root domains from response |
||||
|
rootDomains=$(echo "$response" | _egrep_o '"domain":"[^"]*"' | sed -E 's/"domain":"([^"]*)"/\1/') |
||||
|
_debug "Root domains:" "$rootDomains" |
||||
|
|
||||
|
for rootDomain in $rootDomains; do |
||||
|
if _contains "$domain" "$rootDomain"; then |
||||
|
_domain="$rootDomain" |
||||
|
_sub_domain=$(echo "$domain" | sed "s/.$rootDomain//g") |
||||
|
_debug "Found root domain" "$_domain" "and subdomain" "$_sub_domain" "for" "$domain" |
||||
|
return 0 |
||||
|
fi |
||||
|
done |
||||
|
return 1 |
||||
|
} |
||||
|
|
||||
|
# Helper function for rest calls |
||||
|
_mijnhost_rest() { |
||||
|
m=$1 |
||||
|
ep="$2" |
||||
|
data="$3" |
||||
|
|
||||
|
MAX_REQUEST_RETRY_TIMES=15 |
||||
|
_request_retry_times=0 |
||||
|
_retry_sleep=5 #Initial sleep time in seconds. |
||||
|
|
||||
|
while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do |
||||
|
_debug2 _request_retry_times "$_request_retry_times" |
||||
|
export _H1="API-Key: $MIJNHOST_API_KEY" |
||||
|
export _H2="Content-Type: application/json" |
||||
|
# clear headers from previous request to avoid getting wrong http code on timeouts |
||||
|
: >"$HTTP_HEADER" |
||||
|
_debug "$ep" |
||||
|
if [ "$m" != "GET" ]; then |
||||
|
_debug2 "data $data" |
||||
|
response="$(_post "$data" "$ep" "" "$m")" |
||||
|
else |
||||
|
response="$(_get "$ep")" |
||||
|
fi |
||||
|
_ret="$?" |
||||
|
_debug2 "response $response" |
||||
|
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" |
||||
|
_debug "http response code $_code" |
||||
|
if [ "$_code" = "401" ]; then |
||||
|
# we have an invalid API token, maybe it is expired? |
||||
|
_err "Access denied. Invalid API token." |
||||
|
return 1 |
||||
|
fi |
||||
|
|
||||
|
if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "400" ] || _contains "$response" "DNS records not managed by mijn.host"; then #Sometimes API errors out |
||||
|
_request_retry_times="$(_math "$_request_retry_times" + 1)" |
||||
|
_info "REST call error $_code retrying $ep in ${_retry_sleep}s" |
||||
|
_sleep "$_retry_sleep" |
||||
|
_retry_sleep="$(_math "$_retry_sleep" \* 2)" |
||||
|
continue |
||||
|
fi |
||||
|
break |
||||
|
done |
||||
|
if [ "$_request_retry_times" = "$MAX_REQUEST_RETRY_TIMES" ]; then |
||||
|
_err "Error mijn.host API call was retried $MAX_REQUEST_RETRY_TIMES times." |
||||
|
_err "Calling $ep failed." |
||||
|
return 1 |
||||
|
fi |
||||
|
response="$(echo "$response" | _normalizeJson)" |
||||
|
return 0 |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue