baerengraben
4 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