Merge branch 'acmesh-official:dev' into dev

pull/5047/head
Eddict committed by GitHub
commit a0fa04dcaa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,5 +1,6 @@
name: DNS
on:
workflow_dispatch:
push:
paths:
- 'dnsapi/*.sh'
@ -280,7 +281,7 @@ jobs:
- uses: vmactions/openbsd-vm@v1
with:
envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
prepare: pkg_add socat curl
prepare: pkg_add socat curl libiconv
usesh: true
copyback: false
run: |

@ -29,7 +29,7 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"

@ -29,12 +29,12 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"

@ -26,11 +26,11 @@ jobs:
Linux:
strategy:
matrix:
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "centos:7", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"]
os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"]
runs-on: ubuntu-latest
env:
TEST_LOCAL: 1
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
TEST_ACME_Server: "LetsEncrypt.org_test"
steps:
- uses: actions/checkout@v4

@ -29,7 +29,7 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"

@ -29,7 +29,7 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"

@ -29,12 +29,12 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"

@ -29,12 +29,12 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"

@ -37,7 +37,7 @@ jobs:
- name: Install tools
run: sudo apt-get install -y socat
- name: Run Pebble
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d
run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d
- name: Set up Pebble
run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4
- name: Clone acmetest

@ -29,12 +29,12 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"

@ -29,12 +29,12 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
- TEST_ACME_Server: "LetsEncrypt.org_test"
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
ACME_USE_WGET: 1
- TEST_ACME_Server: "ZeroSSL.com"
CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"

@ -29,7 +29,7 @@ jobs:
CA_ECDSA: ""
CA: ""
CA_EMAIL: ""
TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
TEST_PREFERRED_CHAIN: (STAGING)
#- TEST_ACME_Server: "ZeroSSL.com"
# CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
# CA: "ZeroSSL RSA Domain Secure Site CA"

@ -15,6 +15,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DOCKER_IMAGE: neilpang/acme.sh
jobs:
CheckToken:
@ -44,6 +46,11 @@ jobs:
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5.5.1
with:
images: ${DOCKER_IMAGE}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: login to docker hub
@ -51,8 +58,6 @@ jobs:
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: build and push the image
run: |
DOCKER_IMAGE=neilpang/acme.sh
if [[ $GITHUB_REF == refs/tags/* ]]; then
DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/}
fi
@ -66,8 +71,14 @@ jobs:
fi
fi
DOCKER_LABELS=()
while read -r label; do
DOCKER_LABELS+=(--label "${label}")
done <<<"${DOCKER_METADATA_OUTPUT_LABELS}"
docker buildx build \
--tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \
"${DOCKER_LABELS[@]}" \
--output "type=image,push=true" \
--build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \
--platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x .

@ -23,6 +23,7 @@ jobs:
First thing: don't send PR to the master branch, please send to the dev branch instead.
Please make sure you've read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test).
Then reply on this message, otherwise, your code will not be reviewed or merged.
Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2
We look forward to reviewing your Pull request shortly ✨
注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试.
`

@ -1,4 +1,4 @@
name: Check dns api
name: Check notify api
on:
pull_request_target:

@ -15,11 +15,11 @@ RUN apk --no-cache add -f \
jq \
cronie
ENV LE_CONFIG_HOME /acme.sh
ENV LE_CONFIG_HOME=/acme.sh
ARG AUTO_UPGRADE=1
ENV AUTO_UPGRADE $AUTO_UPGRADE
ENV AUTO_UPGRADE=$AUTO_UPGRADE
#Install
COPY ./ /install_acme.sh/

File diff suppressed because it is too large Load Diff

@ -0,0 +1,88 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034,SC2154
# Script to create certificate to Alibaba Cloud CDN
#
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
#
# This deployment required following variables
# export Ali_Key="ALIACCESSKEY"
# export Ali_Secret="ALISECRETKEY"
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
#
# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
# export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com"
# If you have multiple CDN domains using the same certificate, just
# export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
#
# For DCDN, see ali_dcdn deploy hook
Ali_CDN_API="https://cdn.aliyuncs.com/"
ali_cdn_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
# Load dnsapi/dns_ali.sh to reduce the duplicated codes
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
# shellcheck source=/dev/null
if ! . "$dnsapi_ali"; then
_err "Error loading file $dnsapi_ali. Please check your API file and try again."
return 1
fi
_prepare_ali_credentials || return 1
_getdeployconf DEPLOY_ALI_CDN_DOMAIN
if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then
_savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN"
else
DEPLOY_ALI_CDN_DOMAIN="$_cdomain"
fi
# read cert and key files and urlencode both
_cert=$(_url_encode upper-hex <"$_cfullchain")
_key=$(_url_encode upper-hex <"$_ckey")
_debug2 _cert "$_cert"
_debug2 _key "$_key"
## update domain ssl config
for domain in $DEPLOY_ALI_CDN_DOMAIN; do
_set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then
_info "Domain $domain certificate has been deployed successfully"
fi
done
return 0
}
# domain pub pri
_set_cdn_domain_ssl_certificate_query() {
endpoint=$Ali_CDN_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=SetCdnDomainSSLCertificate'
query=$query'&CertType=upload'
query=$query'&DomainName='$1
query=$query'&Format=json'
query=$query'&SSLPri='$3
query=$query'&SSLProtocol=on'
query=$query'&SSLPub='$2
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Version=2018-05-10'
}

@ -0,0 +1,88 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034,SC2154
# Script to create certificate to Alibaba Cloud DCDN
#
# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun
#
# This deployment required following variables
# export Ali_Key="ALIACCESSKEY"
# export Ali_Secret="ALISECRETKEY"
# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi
#
# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com"
# If you have multiple CDN domains using the same certificate, just
# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com"
#
# For regular CDN, see ali_cdn deploy hook
Ali_DCDN_API="https://dcdn.aliyuncs.com/"
ali_dcdn_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
# Load dnsapi/dns_ali.sh to reduce the duplicated codes
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)"
# shellcheck source=/dev/null
if ! . "$dnsapi_ali"; then
_err "Error loading file $dnsapi_ali. Please check your API file and try again."
return 1
fi
_prepare_ali_credentials || return 1
_getdeployconf DEPLOY_ALI_DCDN_DOMAIN
if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then
_savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN"
else
DEPLOY_ALI_DCDN_DOMAIN="$_cdomain"
fi
# read cert and key files and urlencode both
_cert=$(_url_encode upper-hex <"$_cfullchain")
_key=$(_url_encode upper-hex <"$_ckey")
_debug2 _cert "$_cert"
_debug2 _key "$_key"
## update domain ssl config
for domain in $DEPLOY_ALI_DCDN_DOMAIN; do
_set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key"
if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then
_info "Domain $domain certificate has been deployed successfully"
fi
done
return 0
}
# domain pub pri
_set_dcdn_domain_ssl_certificate_query() {
endpoint=$Ali_DCDN_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=SetDcdnDomainSSLCertificate'
query=$query'&CertType=upload'
query=$query'&DomainName='$1
query=$query'&Format=json'
query=$query'&SSLPri='$3
query=$query'&SSLProtocol=on'
query=$query'&SSLPub='$2
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_timestamp)
query=$query'&Version=2018-01-15'
}

@ -109,6 +109,5 @@ exim4_deploy() {
fi
return 1
fi
return 0
}

@ -36,6 +36,19 @@
# Note: This functionality requires HAProxy was compiled against
# a version of OpenSSL that supports this.
#
# export DEPLOY_HAPROXY_HOT_UPDATE="yes"
# export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock"
#
# OPTIONAL: Deploy the certificate over the HAProxy stats socket without
# needing to reload HAProxy. Default is "no".
#
# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat
# address format.
#
# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock"
#
# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead
# of a stats socket, use this variable.
######## Public functions #####################
@ -46,6 +59,7 @@ haproxy_deploy() {
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_cmdpfx=""
# Some defaults
DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
@ -53,6 +67,8 @@ haproxy_deploy() {
DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no"
DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock"
_debug _cdomain "${_cdomain}"
_debug _ckey "${_ckey}"
@ -86,6 +102,11 @@ haproxy_deploy() {
_savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
# We better not have '*' as the first character
if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then
# removes the first characters and add a _ instead
Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}"
fi
fi
# BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
@ -118,6 +139,36 @@ haproxy_deploy() {
Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
fi
# HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
_getdeployconf DEPLOY_HAPROXY_HOT_UPDATE
_debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}"
if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then
Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}"
_savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}"
elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then
Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}"
fi
# STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
_getdeployconf DEPLOY_HAPROXY_STATS_SOCKET
_debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}"
if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}"
_savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}"
fi
# MASTER_CLI is optional. No defaults are used. When the master CLI is used,
# all commands are sent with a prefix.
_getdeployconf DEPLOY_HAPROXY_MASTER_CLI
_debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}"
if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then
Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}"
_savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}"
_cmdpfx="@1 " # command prefix used for master CLI only.
fi
# Set the suffix depending if we are creating a bundle or not
if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
_info "Bundle creation requested"
@ -142,12 +193,13 @@ haproxy_deploy() {
_issuer="${_pem}.issuer"
_ocsp="${_pem}.ocsp"
_reload="${Le_Deploy_haproxy_reload}"
_statssock="${Le_Deploy_haproxy_stats_socket}"
_info "Deploying PEM file"
# Create a temporary PEM file
_temppem="$(_mktemp)"
_debug _temppem "${_temppem}"
cat "${_ccert}" "${_cca}" "${_ckey}" >"${_temppem}"
cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}"
_ret="$?"
# Check that we could create the temporary file
@ -265,15 +317,86 @@ haproxy_deploy() {
fi
fi
# Reload HAProxy
_debug _reload "${_reload}"
eval "${_reload}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Error code ${_ret} during reload"
return ${_ret}
if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then
# set the socket name for messages
if [ -n "${_cmdpfx}" ]; then
_socketname="master CLI"
else
_socketname="stats socket"
fi
# Update certificate over HAProxy stats socket or master CLI.
if _exists socat; then
# look for the certificate on the stats socket, to chose between updating or creating one
_socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'"
_debug _socat_cert_cmd "${_socat_cert_cmd}"
eval "${_socat_cert_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_newcert="1"
_info "Creating new certificate '${_pem}' over HAProxy ${_socketname}."
# certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate.
_socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'"
_debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}"
eval "${_socat_crtlist_show_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'"
return "${_ret}"
fi
# create a new certificate
_socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'"
_debug _socat_new_cmd "${_socat_new_cmd}"
eval "${_socat_new_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Couldn't create '${_pem}' in haproxy"
return "${_ret}"
fi
else
_info "Update existing certificate '${_pem}' over HAProxy ${_socketname}."
fi
_socat_cert_set_cmd="echo -e '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'"
_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}"
eval "${_socat_cert_set_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't update '${_pem}' in haproxy"
return "${_ret}"
fi
_socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'"
_debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}"
eval "${_socat_cert_commit_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't commit '${_pem}' in haproxy"
return ${_ret}
fi
if [ "${_newcert}" = "1" ]; then
# if this is a new certificate, it needs to be inserted into the crt-list`
_socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'"
_debug _socat_cert_add_cmd "${_socat_cert_add_cmd}"
eval "${_socat_cert_add_cmd}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Can't update '${_pem}' in haproxy"
return "${_ret}"
fi
fi
else
_err "'socat' is not available, couldn't update over ${_socketname}"
fi
else
_info "Reload successful"
# Reload HAProxy
_debug _reload "${_reload}"
eval "${_reload}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Error code ${_ret} during reload"
return ${_ret}
else
_info "Reload successful"
fi
fi
return 0

@ -12,6 +12,9 @@
# export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role
# export PANOS_PASS=""
#
# OPTIONAL
# export PANOS_TEMPLATE="" #Template Name of panorama managed devices
#
# The script will automatically generate a new API key if
# no key is found, or if a saved key has expired or is invalid.
@ -78,6 +81,9 @@ deployer() {
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
if [ "$_panos_template" ]; then
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
fi
fi
if [ "$type" = 'key' ]; then
panos_url="${panos_url}?type=import"
@ -87,6 +93,9 @@ deployer() {
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
if [ "$_panos_template" ]; then
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template"
fi
fi
#Close multipart
content="$content${nl}--$delim--${nl}${nl}"
@ -173,10 +182,20 @@ panos_deploy() {
unset _panos_key
fi
# PANOS_TEMPLATE
if [ "$PANOS_TEMPLATE" ]; then
_debug "Detected ENV variable PANOS_TEMPLATE. Saving to file."
_savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1
else
_debug "Attempting to load variable PANOS_TEMPLATE from file."
_getdeployconf PANOS_TEMPLATE
fi
#Store variables
_panos_host=$PANOS_HOST
_panos_user=$PANOS_USER
_panos_pass=$PANOS_PASS
_panos_template=$PANOS_TEMPLATE
#Test API Key if found. If the key is invalid, the variable _panos_key will be unset.
if [ "$_panos_host" ] && [ "$_panos_key" ]; then

@ -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"
}

@ -137,7 +137,8 @@ routeros_deploy() {
return $_err_code
fi
DEPLOY_SCRIPT_CMD="/system script add name=\"LECertDeploy-$_cdomain\" owner=$ROUTER_OS_USERNAME \
DEPLOY_SCRIPT_CMD=":do {/system script remove \"LECertDeploy-$_cdomain\" } on-error={ }; \
/system script add name=\"LECertDeploy-$_cdomain\" owner=$ROUTER_OS_USERNAME \
comment=\"generated by routeros deploy script in acme.sh\" \
source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
\n/certificate remove [ find name=$_cdomain.cer_1 ];\
@ -146,8 +147,8 @@ source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\
\n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\";\
\n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\";\
\ndelay 1;\
\n/file remove $_cdomain.cer;\
\n/file remove $_cdomain.key;\
\n:do {/file remove $_cdomain.cer; } on-error={ }\
\n:do {/file remove $_cdomain.key; } on-error={ }\
\ndelay 2;\
\n/ip service set www-ssl certificate=$_cdomain.cer_0;\
\n$ROUTER_OS_ADDITIONAL_SERVICES;\

@ -0,0 +1,172 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to Ruckus ZoneDirector / Unleashed.
#
# Public domain, 2024, Tony Rielly <https://github.com/ms264556>
#
# ```sh
# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus
# ```
#
# Then you need to set the environment variables for the
# deploy script to work.
#
# ```sh
# export RUCKUS_HOST=myruckus.example.com
# export RUCKUS_USER=myruckususername
# export RUCKUS_PASS=myruckuspassword
#
# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus
# ```
#
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
ruckus_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_err_code=0
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
_getdeployconf RUCKUS_HOST
_getdeployconf RUCKUS_USER
_getdeployconf RUCKUS_PASS
if [ -z "$RUCKUS_HOST" ]; then
_debug "Using _cdomain as RUCKUS_HOST, please set if not correct."
RUCKUS_HOST="$_cdomain"
fi
if [ -z "$RUCKUS_USER" ]; then
_err "Need to set the env variable RUCKUS_USER"
return 1
fi
if [ -z "$RUCKUS_PASS" ]; then
_err "Need to set the env variable RUCKUS_PASS"
return 1
fi
_savedeployconf RUCKUS_HOST "$RUCKUS_HOST"
_savedeployconf RUCKUS_USER "$RUCKUS_USER"
_savedeployconf RUCKUS_PASS "$RUCKUS_PASS"
_debug RUCKUS_HOST "$RUCKUS_HOST"
_debug RUCKUS_USER "$RUCKUS_USER"
_secure_debug RUCKUS_PASS "$RUCKUS_PASS"
export ACME_HTTP_NO_REDIRECTS=1
_info "Discovering the login URL"
_get "https://$RUCKUS_HOST" >/dev/null
_login_url="$(_response_header 'Location')"
if [ -n "$_login_url" ]; then
_login_path=$(echo "$_login_url" | sed 's|https\?://[^/]\+||')
if [ -z "$_login_path" ]; then
# redirect was to a different host
_err "Connection failed: redirected to a different host. Configure Unleashed with a Preferred Master or Management Interface."
return 1
fi
fi
if [ -z "${_login_url}" ]; then
_err "Connection failed: couldn't find login page."
return 1
fi
_base_url=$(dirname "$_login_url")
_login_page=$(basename "$_login_url")
if [ "$_login_page" = "index.html" ]; then
_err "Connection temporarily unavailable: Unleashed Rebuilding."
return 1
fi
if [ "$_login_page" = "wizard.jsp" ]; then
_err "Connection failed: Setup Wizard not complete."
return 1
fi
_info "Login"
_username_encoded="$(printf "%s" "$RUCKUS_USER" | _url_encode)"
_password_encoded="$(printf "%s" "$RUCKUS_PASS" | _url_encode)"
_login_query="$(printf "%s" "username=${_username_encoded}&password=${_password_encoded}&ok=Log+In")"
_post "$_login_query" "$_login_url" >/dev/null
_login_code="$(_response_code)"
if [ "$_login_code" = "200" ]; then
_err "Login failed: incorrect credentials."
return 1
fi
_info "Collect Session Cookie"
_H1="Cookie: $(_response_cookie)"
export _H1
_info "Collect CSRF Token"
_H2="X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')"
export _H2
_info "Uploading certificate"
_post_upload "uploadcert" "$_cfullchain"
_info "Uploading private key"
_post_upload "uploadprivatekey" "$_ckey"
_info "Replacing certificate"
_replace_cert_ajax='<ajax-request action="docmd" comp="system" updater="rid.0.5" xcmd="replace-cert" checkAbility="6" timeout="-1"><xcmd cmd="replace-cert" cn="'$RUCKUS_HOST'"/></ajax-request>'
_post "$_replace_cert_ajax" "$_base_url/_cmdstat.jsp" >/dev/null
_info "Rebooting"
_cert_reboot_ajax='<ajax-request action="docmd" comp="worker" updater="rid.0.5" xcmd="cert-reboot" checkAbility="6"><xcmd cmd="cert-reboot" action="undefined"/></ajax-request>'
_post "$_cert_reboot_ajax" "$_base_url/_cmdstat.jsp" >/dev/null
return 0
}
_response_code() {
_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n" | _egrep_o "^[0-9]*"
}
_response_header() {
grep <"$HTTP_HEADER" -i "^$1:" | cut -d ':' -f 2- | tr -d "\r\n\t "
}
_response_cookie() {
_response_header 'Set-Cookie' | sed 's/;.*//'
}
_post_upload() {
_post_action="$1"
_post_file="$2"
_post_boundary="----FormBoundary$(date "+%s%N")"
_post_data="$({
printf -- "--%s\r\n" "$_post_boundary"
printf -- "Content-Disposition: form-data; name=\"u\"; filename=\"%s\"\r\n" "$_post_action"
printf -- "Content-Type: application/octet-stream\r\n\r\n"
printf -- "%s\r\n" "$(cat "$_post_file")"
printf -- "--%s\r\n" "$_post_boundary"
printf -- "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
printf -- "%s\r\n" "$_post_action"
printf -- "--%s\r\n" "$_post_boundary"
printf -- "Content-Disposition: form-data; name=\"callback\"\r\n\r\n"
printf -- "%s\r\n" "uploader_$_post_action"
printf -- "--%s--\r\n\r\n" "$_post_boundary"
})"
_post "$_post_data" "$_base_url/_upload.jsp?request_type=xhr" "" "" "multipart/form-data; boundary=$_post_boundary" >/dev/null
}

@ -10,46 +10,89 @@
#domain keyfile certfile cafile fullchain
strongswan_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_cdomain="${1}"
_ckey="${2}"
_ccert="${3}"
_cca="${4}"
_cfullchain="${5}"
_info "Using strongswan"
if [ -x /usr/sbin/ipsec ]; then
_ipsec=/usr/sbin/ipsec
elif [ -x /usr/sbin/strongswan ]; then
_ipsec=/usr/sbin/strongswan
elif [ -x /usr/local/sbin/ipsec ]; then
_ipsec=/usr/local/sbin/ipsec
else
if _exists ipsec; then
_ipsec=ipsec
elif _exists strongswan; then
_ipsec=strongswan
fi
if _exists swanctl; then
_swanctl=swanctl
fi
# For legacy stroke mode
if [ -n "${_ipsec}" ]; then
_info "${_ipsec} command detected"
_confdir=$(${_ipsec} --confdir)
if [ -z "${_confdir}" ]; then
_err "no strongswan --confdir is detected"
return 1
fi
_info _confdir "${_confdir}"
__deploy_cert "$@" "stroke" "${_confdir}"
${_ipsec} reload
fi
# For modern vici mode
if [ -n "${_swanctl}" ]; then
_info "${_swanctl} command detected"
for _dir in /usr/local/etc/swanctl /etc/swanctl /etc/strongswan/swanctl; do
if [ -d ${_dir} ]; then
_confdir=${_dir}
_info _confdir "${_confdir}"
break
fi
done
if [ -z "${_confdir}" ]; then
_err "no swanctl config dir is found"
return 1
fi
__deploy_cert "$@" "vici" "${_confdir}"
${_swanctl} --load-creds
fi
if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then
_err "no strongswan or ipsec command is detected"
_err "no swanctl is detected"
return 1
fi
}
_info _ipsec "$_ipsec"
#################### Private functions below ##################################
_confdir=$($_ipsec --confdir)
if [ $? -ne 0 ] || [ -z "$_confdir" ]; then
_err "no strongswan --confdir is detected"
__deploy_cert() {
_cdomain="${1}"
_ckey="${2}"
_ccert="${3}"
_cca="${4}"
_cfullchain="${5}"
_swan_mode="${6}"
_confdir="${7}"
_debug _cdomain "${_cdomain}"
_debug _ckey "${_ckey}"
_debug _ccert "${_ccert}"
_debug _cca "${_cca}"
_debug _cfullchain "${_cfullchain}"
_debug _swan_mode "${_swan_mode}"
_debug _confdir "${_confdir}"
if [ "${_swan_mode}" = "vici" ]; then
_dir_private="private"
_dir_cert="x509"
_dir_ca="x509ca"
elif [ "${_swan_mode}" = "stroke" ]; then
_dir_private="ipsec.d/private"
_dir_cert="ipsec.d/certs"
_dir_ca="ipsec.d/cacerts"
else
_err "unknown StrongSwan mode ${_swan_mode}"
return 1
fi
_info _confdir "$_confdir"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
cat "$_ckey" >"${_confdir}/ipsec.d/private/$(basename "$_ckey")"
cat "$_ccert" >"${_confdir}/ipsec.d/certs/$(basename "$_ccert")"
cat "$_cca" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cca")"
cat "$_cfullchain" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cfullchain")"
$_ipsec reload
cat "${_ckey}" >"${_confdir}/${_dir_private}/$(basename "${_ckey}")"
cat "${_ccert}" >"${_confdir}/${_dir_cert}/$(basename "${_ccert}")"
cat "${_cca}" >"${_confdir}/${_dir_ca}/$(basename "${_cca}")"
if [ "${_swan_mode}" = "stroke" ]; then
cat "${_cfullchain}" >"${_confdir}/${_dir_ca}/$(basename "${_cfullchain}")"
fi
}

@ -8,25 +8,38 @@
# Updated: 2023-07-03
# Issues: https://github.com/acmesh-official/acme.sh/issues/2727
################################################################################
# Usage:
# - Create temp admin user automatically:
# Usage (shown values are the examples):
# 1. Set required environment variables:
# - use automatically created temp admin user to authenticate
# export SYNO_USE_TEMP_ADMIN=1
# - Or provide your own admin user credential:
# 1. export SYNO_Username="adminUser"
# 2. export SYNO_Password="adminPassword"
# Optional exports (shown values are the defaults):
# - export SYNO_Certificate="" - to replace a specific certificate via description
# - export SYNO_Scheme="http"
# - export SYNO_Hostname="localhost"
# - export SYNO_Port="5000"
# - export SYNO_Create=1 - to allow creating the certificate if it doesn't exist
# - export SYNO_Device_Name="CertRenewal" - required if 2FA-OTP enabled
# - export SYNO_Device_ID="" - required for skipping 2FA-OTP
# 3. acme.sh --deploy --deploy-hook synology_dsm -d example.com
# - or provide your own admin user credential to authenticate
# 1. export SYNO_USERNAME="adminUser"
# 2. export SYNO_PASSWORD="adminPassword"
# 2. Set optional environment variables
# - common optional variables
# - export SYNO_SCHEME="http" - defaults to "http"
# - export SYNO_HOSTNAME="localhost" - defaults to "localhost"
# - export SYNO_PORT="5000" - defaults to "5000"
# - export SYNO_CREATE=1 - to allow creating the cert if it doesn't exist
# - export SYNO_CERTIFICATE="" - to replace a specific cert by its
# description
# - temp admin optional variables
# - export SYNO_LOCAL_HOSTNAME=1 - if set to 1, force to treat hostname is
# targeting current local machine (since
# this method only locally supported)
# - exsiting admin 2FA-OTP optional variables
# - export SYNO_OTP_CODE="XXXXXX" - if set, script won't require to
# interactive input the OTP code
# - export SYNO_DEVICE_NAME="CertRenewal" - if set, script won't require to
# interactive input the device name
# - export SYNO_DEVICE_ID="" - (deprecated, auth with OTP code instead)
# required for omitting 2FA-OTP
# 3. Run command:
# acme.sh --deploy --deploy-hook synology_dsm -d example.com
################################################################################
# Dependencies:
# - jq & curl
# - synouser & synogroup (When available and SYNO_USE_TEMP_ADMIN is set)
# - curl
# - synouser & synogroup & synosetkeyvalue (Required for SYNO_USE_TEMP_ADMIN=1)
################################################################################
# Return value:
# 0 means success, otherwise error.
@ -42,74 +55,89 @@ synology_dsm_deploy() {
_debug _cdomain "$_cdomain"
# Get username & password, but don't save until we authenticated successfully
# Get username and password, but don't save until we authenticated successfully
_migratedeployconf SYNO_Username SYNO_USERNAME
_migratedeployconf SYNO_Password SYNO_PASSWORD
_migratedeployconf SYNO_Device_ID SYNO_DEVICE_ID
_migratedeployconf SYNO_Device_Name SYNO_DEVICE_NAME
_getdeployconf SYNO_USERNAME
_getdeployconf SYNO_PASSWORD
_getdeployconf SYNO_DEVICE_ID
_getdeployconf SYNO_DEVICE_NAME
# Prepare to use temp admin if SYNO_USE_TEMP_ADMIN is set
_getdeployconf SYNO_USE_TEMP_ADMIN
_getdeployconf SYNO_Username
_getdeployconf SYNO_Password
_getdeployconf SYNO_Create
_getdeployconf SYNO_DID
_getdeployconf SYNO_TOTP_SECRET
_getdeployconf SYNO_Device_Name
_getdeployconf SYNO_Device_ID
# Prepare temp admin user info if SYNO_USE_TEMP_ADMIN is set
if [ -n "${SYNO_USE_TEMP_ADMIN:-}" ]; then
if ! _exists synouser; then
if ! _exists synogroup; then
_err "Tools are missing for creating temp admin user, please set SYNO_Username & SYNO_Password instead."
return 1
fi
_check2cleardeployconfexp SYNO_USE_TEMP_ADMIN
_debug2 SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then
_err "Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead."
_err "Notice: temp admin user authorization method only supports local deployment on DSM."
return 1
fi
if synouser --help 2>&1 | grep -q 'Permission denied'; then
_err "For creating temp admin user, the deploy script must be run as root."
return 1
fi
[ -n "$SYNO_USERNAME" ] || _savedeployconf SYNO_USERNAME ""
[ -n "$SYNO_PASSWORD" ] || _savedeployconf SYNO_PASSWORD ""
_debug "Setting temp admin user credential..."
SYNO_Username=sc-acmesh-tmp
SYNO_Password=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
# Ignore 2FA-OTP settings which won't be needed.
SYNO_Device_Name=
SYNO_Device_ID=
SYNO_USERNAME=sc-acmesh-tmp
SYNO_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
# Set 2FA-OTP settings to empty consider they won't be needed.
SYNO_DEVICE_ID=
SYNO_DEVICE_NAME=
SYNO_OTP_CODE=
else
_debug2 SYNO_USERNAME "$SYNO_USERNAME"
_secure_debug2 SYNO_PASSWORD "$SYNO_PASSWORD"
_debug2 SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
fi
if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then
_err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_Username and SYNO_Password."
if [ -z "$SYNO_USERNAME" ] || [ -z "$SYNO_PASSWORD" ]; then
_err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_USERNAME and SYNO_PASSWORD."
return 1
fi
_debug2 SYNO_Username "$SYNO_Username"
_secure_debug2 SYNO_Password "$SYNO_Password"
_debug2 SYNO_Create "$SYNO_Create"
_debug2 SYNO_Device_Name "$SYNO_Device_Name"
_secure_debug2 SYNO_Device_ID "$SYNO_Device_ID"
# Optional scheme, hostname & port for Synology DSM
_getdeployconf SYNO_Scheme
_getdeployconf SYNO_Hostname
_getdeployconf SYNO_Port
# Default values for scheme, hostname & port
# Defaulting to localhost & http, because it's localhost…
[ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http"
[ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost"
[ -n "${SYNO_Port}" ] || SYNO_Port="5000"
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
_savedeployconf SYNO_Scheme "$SYNO_Scheme"
_savedeployconf SYNO_Hostname "$SYNO_Hostname"
_savedeployconf SYNO_Port "$SYNO_Port"
_debug2 SYNO_Scheme "$SYNO_Scheme"
_debug2 SYNO_Hostname "$SYNO_Hostname"
_debug2 SYNO_Port "$SYNO_Port"
# Optional scheme, hostname and port for Synology DSM
_migratedeployconf SYNO_Scheme SYNO_SCHEME
_migratedeployconf SYNO_Hostname SYNO_HOSTNAME
_migratedeployconf SYNO_Port SYNO_PORT
_getdeployconf SYNO_SCHEME
_getdeployconf SYNO_HOSTNAME
_getdeployconf SYNO_PORT
# Default values for scheme, hostname and port
# Defaulting to localhost and http, because it's localhost…
[ -n "$SYNO_SCHEME" ] || SYNO_SCHEME=http
[ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME=localhost
[ -n "$SYNO_PORT" ] || SYNO_PORT=5000
_savedeployconf SYNO_SCHEME "$SYNO_SCHEME"
_savedeployconf SYNO_HOSTNAME "$SYNO_HOSTNAME"
_savedeployconf SYNO_PORT "$SYNO_PORT"
_debug2 SYNO_SCHEME "$SYNO_SCHEME"
_debug2 SYNO_HOSTNAME "$SYNO_HOSTNAME"
_debug2 SYNO_PORT "$SYNO_PORT"
# Get the certificate description, but don't save it until we verify it's real
_getdeployconf SYNO_Certificate
_debug SYNO_Certificate "${SYNO_Certificate:-}"
_migratedeployconf SYNO_Certificate SYNO_CERTIFICATE "base64"
_getdeployconf SYNO_CERTIFICATE
_check2cleardeployconfexp SYNO_CERTIFICATE
_debug SYNO_CERTIFICATE "${SYNO_CERTIFICATE:-}"
# shellcheck disable=SC1003 # We are not trying to escape a single quote
if printf "%s" "$SYNO_Certificate" | grep '\\'; then
if printf "%s" "$SYNO_CERTIFICATE" | grep '\\'; then
_err "Do not use a backslash (\) in your certificate description"
return 1
fi
_base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port"
_debug "Getting API version..."
_base_url="$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT"
_debug _base_url "$_base_url"
_debug "Getting API version"
response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth")
api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p')
api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p')
@ -117,13 +145,14 @@ synology_dsm_deploy() {
_debug3 api_path "$api_path"
_debug3 api_version "$api_version"
# Login, get the session ID & SynoToken from JSON
_info "Logging into $SYNO_Hostname:$SYNO_Port"
encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)"
encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)"
# Login, get the session ID and SynoToken from JSON
_info "Logging into $SYNO_HOSTNAME:$SYNO_PORT..."
encoded_username="$(printf "%s" "$SYNO_USERNAME" | _url_encode)"
encoded_password="$(printf "%s" "$SYNO_PASSWORD" | _url_encode)"
# ## START ## - DEPRECATED, for backward compatibility
_getdeployconf SYNO_TOTP_SECRET
otp_code=""
# START - DEPRECATED, only kept for legacy compatibility reasons
if [ -n "$SYNO_TOTP_SECRET" ]; then
_info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!"
_info " See synology_dsm.sh script or ACME.sh Wiki page for details:"
@ -132,48 +161,141 @@ synology_dsm_deploy() {
_err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET"
return 1
fi
DEPRECATED_otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)"
DEPRECATED_otp_code="$(oathtool --base32 --totp "$SYNO_TOTP_SECRET" 2>/dev/null)"
if [ -n "$SYNO_DID" ]; then
_H1="Cookie: did=$SYNO_DID"
if [ -z "$SYNO_DEVICE_ID" ]; then
_getdeployconf SYNO_DID
[ -n "$SYNO_DID" ] || SYNO_DEVICE_ID="$SYNO_DID"
fi
if [ -n "$SYNO_DEVICE_ID" ]; then
_H1="Cookie: did=$SYNO_DEVICE_ID"
export _H1
_debug3 H1 "${_H1}"
fi
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes")
response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DEVICE_ID" "$_base_url/webapi/$api_path?enable_syno_token=yes")
_debug3 response "$response"
# END - DEPRECATED, only kept for legacy compatibility reasons
# If SYNO_DeviceDevice_ID & SYNO_Device_Name both empty, just log in normally
elif [ -z "${SYNO_Device_ID:-}" ] && [ -z "${SYNO_Device_Name:-}" ]; then
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_debug "Creating temp admin user in Synology DSM"
synouser --del "$SYNO_Username" >/dev/null 2>/dev/null
synouser --add "$SYNO_Username" "$SYNO_Password" "" 0 "" 0 >/dev/null
synogroup --memberadd administrators "$SYNO_Username" >/dev/null
fi
response=$(_get "$_base_url/webapi/entry.cgi?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes")
_debug3 response "$response"
# Get device ID if still empty first, otherwise log in right away
# If SYNO_Device_Name is set, we treat that account enabled two-factor authorization, consider SYNO_Device_ID is not set, so it won't be able to login without requiring the OTP code.
elif [ -n "${SYNO_Device_Name:-}" ] && [ -z "${SYNO_Device_ID:-}" ]; then
printf "Enter OTP code for user '%s': " "$SYNO_Username"
read -r otp_code
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&otp_code=$otp_code&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_Device_Name")
_secure_debug3 response "$response"
id_property='device_id'
[ "${api_version}" -gt '6' ] || id_property='did'
SYNO_Device_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p')
_secure_debug2 SYNO_Device_ID "$SYNO_Device_ID"
# Otherwise, if SYNO_Device_ID is set, we can just use it to login.
# ## END ## - DEPRECATED, for backward compatibility
# If SYNO_DEVICE_ID or SYNO_OTP_CODE is set, we treat current account enabled 2FA-OTP.
# Notice that if SYNO_USE_TEMP_ADMIN=1, both variables will be unset
else
if [ -z "${SYNO_Device_Name:-}" ]; then
if [ -n "$SYNO_DEVICE_ID" ] || [ -n "$SYNO_OTP_CODE" ]; then
response='{"error":{"code":403}}'
# Assume the current account disabled 2FA-OTP, try to log in right away.
else
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_getdeployconf SYNO_LOCAL_HOSTNAME
_debug SYNO_LOCAL_HOSTNAME "${SYNO_LOCAL_HOSTNAME:-}"
if [ "$SYNO_LOCAL_HOSTNAME" != "1" ] && [ "$SYNO_LOCAL_HOSTNAME" == "$SYNO_HOSTNAME" ]; then
if [ "$SYNO_HOSTNAME" != "localhost" ] && [ "$SYNO_HOSTNAME" != "127.0.0.1" ]; then
_err "SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun."
return 1
fi
fi
_debug "Creating temp admin user in Synology DSM..."
if synogroup --help | grep -q '\-\-memberadd '; then
_temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD"
synogroup --memberadd administrators "$SYNO_USERNAME" >/dev/null
elif synogroup --help | grep -q '\-\-member '; then
# For supporting DSM 6.x which only has `--member` parameter.
cur_admins=$(synogroup --get administrators | awk -F '[][]' '/Group Members/,0{if(NF>1)printf "%s ", $2}')
if [ -n "$cur_admins" ]; then
_temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD"
_secure_debug3 admin_users "$cur_admins$SYNO_USERNAME"
# shellcheck disable=SC2086
synogroup --member administrators $cur_admins $SYNO_USERNAME >/dev/null
else
_err "The tool synogroup may be broken, please set SYNO_USERNAME and SYNO_PASSWORD instead."
return 1
fi
else
_err "Unsupported synogroup tool detected, please set SYNO_USERNAME and SYNO_PASSWORD instead."
return 1
fi
# havig a workaround to temporary disable enforce 2FA-OTP, will restore
# it soon (after a single request), though if any accident occurs like
# unexpected interruption, this setting can be easily reverted manually.
otp_enforce_option=$(synogetkeyvalue /etc/synoinfo.conf otp_enforce_option)
if [ -n "$otp_enforce_option" ] && [ "${otp_enforce_option:-"none"}" != "none" ]; then
synosetkeyvalue /etc/synoinfo.conf otp_enforce_option none
_info "Enforcing 2FA-OTP has been disabled to complete temp admin authentication."
_info "Notice: it will be restored soon, if not, you can restore it manually via Control Panel."
_info "previous_otp_enforce_option" "$otp_enforce_option"
else
otp_enforce_option=""
fi
fi
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes")
if [ -n "$SYNO_USE_TEMP_ADMIN" ] && [ -n "$otp_enforce_option" ]; then
synosetkeyvalue /etc/synoinfo.conf otp_enforce_option "$otp_enforce_option"
_info "Restored previous enforce 2FA-OTP option."
fi
_debug3 response "$response"
fi
fi
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
_debug2 error_code "$error_code"
# Account has 2FA-OTP enabled, since error 403 reported.
# https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf
if [ "$error_code" == "403" ]; then
if [ -z "$SYNO_DEVICE_NAME" ]; then
printf "Enter device name or leave empty for default (CertRenewal): "
read -r SYNO_Device_Name
[ -n "${SYNO_Device_Name}" ] || SYNO_Device_Name="CertRenewal"
read -r SYNO_DEVICE_NAME
[ -n "$SYNO_DEVICE_NAME" ] || SYNO_DEVICE_NAME="CertRenewal"
fi
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_Device_Name&device_id=$SYNO_Device_ID")
_secure_debug3 response "$response"
if [ -n "$SYNO_DEVICE_ID" ]; then
# Omit OTP code with SYNO_DEVICE_ID.
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_DEVICE_NAME&device_id=$SYNO_DEVICE_ID")
_secure_debug3 response "$response"
else
# Require the OTP code if still unset.
if [ -z "$SYNO_OTP_CODE" ]; then
printf "Enter OTP code for user '%s': " "$SYNO_USERNAME"
read -r SYNO_OTP_CODE
fi
_secure_debug SYNO_OTP_CODE "${SYNO_OTP_CODE:-}"
if [ -z "$SYNO_OTP_CODE" ]; then
response='{"error":{"code":404}}'
else
response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_DEVICE_NAME&otp_code=$SYNO_OTP_CODE")
_secure_debug3 response "$response"
id_property='device_id'
[ "${api_version}" -gt '6' ] || id_property='did'
SYNO_DEVICE_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p')
_secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
fi
fi
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
_debug2 error_code "$error_code"
fi
if [ -n "$error_code" ]; then
if [ "$error_code" == "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then
_cleardeployconf SYNO_DEVICE_ID
_err "Failed to authenticate with SYNO_DEVICE_ID (may expired or invalid), please try again in a new terminal window."
elif [ "$error_code" == "404" ]; then
_err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window."
elif [ "$error_code" == "406" ]; then
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_err "Failed with unexcepted error, please report this by providing full log with '--debug 3'."
else
_err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue."
fi
elif [ "$error_code" == "400" ]; then
_err "Failed to authenticate, no such account or incorrect password."
elif [ "$error_code" == "401" ]; then
_err "Failed to authenticate with a non-existent account."
elif [ "$error_code" == "408" ] || [ "$error_code" == "409" ] || [ "$error_code" == "410" ]; then
_err "Failed to authenticate, the account password has expired or must be changed."
else
_err "Failed to authenticate with error: $error_code."
fi
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
@ -181,11 +303,9 @@ synology_dsm_deploy() {
_debug "Session ID" "$sid"
_debug SynoToken "$token"
if [ -z "$sid" ] || [ -z "$token" ]; then
_err "Unable to authenticate to $_base_url - check your username & password."
_err "If two-factor authentication is enabled for the user:"
_err "- set SYNO_Device_Name then input *correct* OTP-code manually"
_err "- get & set SYNO_Device_ID via your browser cookies"
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
# Still can't get necessary info even got no errors, may Synology have API updated?
_err "Unable to authenticate to $_base_url, you may report this by providing full log with '--debug 3'."
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
@ -193,39 +313,62 @@ synology_dsm_deploy() {
export _H1
_debug2 H1 "${_H1}"
# Now that we know the username & password are good, save them
_savedeployconf SYNO_Username "$SYNO_Username"
_savedeployconf SYNO_Password "$SYNO_Password"
if [ -z "${SYNO_USE_TEMP_ADMIN:-}" ]; then
_savedeployconf SYNO_Device_Name "$SYNO_Device_Name"
_savedeployconf SYNO_Device_ID "$SYNO_Device_ID"
# Now that we know the username and password are good, save them if not in temp admin mode.
if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then
_cleardeployconf SYNO_USERNAME
_cleardeployconf SYNO_PASSWORD
_cleardeployconf SYNO_DEVICE_ID
_cleardeployconf SYNO_DEVICE_NAME
_savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN"
_savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_HOSTNAME"
else
_savedeployconf SYNO_USERNAME "$SYNO_USERNAME"
_savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD"
_savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID"
_savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME"
fi
_info "Getting certificates in Synology DSM"
_info "Getting certificates in Synology DSM..."
response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi")
_debug3 response "$response"
escaped_certificate="$(printf "%s" "$SYNO_Certificate" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')"
escaped_certificate="$(printf "%s" "$SYNO_CERTIFICATE" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')"
_debug escaped_certificate "$escaped_certificate"
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p")
_debug2 id "$id"
if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then
_err "Unable to find certificate: $SYNO_Certificate & \$SYNO_Create is not set"
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*')
_debug2 error_code "$error_code"
if [ -n "$error_code" ]; then
if [ "$error_code" -eq 105 ]; then
_err "Current user is not administrator and does not have sufficient permission for deploying."
else
_err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more."
fi
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
_migratedeployconf SYNO_Create SYNO_CREATE
_getdeployconf SYNO_CREATE
_debug2 SYNO_CREATE "$SYNO_CREATE"
if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then
_err "Unable to find certificate: $SYNO_CERTIFICATE and $SYNO_CREATE is not set."
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
return 1
fi
# We've verified this certificate description is a thing, so save it
_savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64"
_savedeployconf SYNO_CERTIFICATE "$SYNO_CERTIFICATE" "base64"
_info "Generate form POST request"
_info "Generating form POST request..."
nl="\0015\0012"
delim="--------------------------$(_utc_date | tr -d -- '-: ')"
content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_CERTIFICATE}"
if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
_debug2 default "This is the default certificate"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true"
@ -236,22 +379,22 @@ synology_dsm_deploy() {
content="$(printf "%b_" "$content")"
content="${content%_}" # protect trailing \n
_info "Upload certificate to the Synology DSM"
_info "Upload certificate to the Synology DSM."
response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}")
_debug3 response "$response"
if ! echo "$response" | grep '"error":' >/dev/null; then
if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
_info "Restarting HTTP services succeeded"
_info "Restart HTTP services succeeded."
else
_info "Restarting HTTP services failed"
_info "Restart HTTP services failed."
fi
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
_logout
return 0
else
_remove_temp_admin "$SYNO_USE_TEMP_ADMIN" "$SYNO_Username"
_err "Unable to update certificate, error code $response"
_temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME"
_err "Unable to update certificate, got error response: $response."
_logout
return 1
fi
@ -264,12 +407,39 @@ _logout() {
_debug3 response "$response"
}
_remove_temp_admin() {
flag=$1
username=$2
_temp_admin_create() {
_username="$1"
_password="$2"
synouser --del "$_username" >/dev/null 2>/dev/null
synouser --add "$_username" "$_password" "" 0 "scruelt@hotmail.com" 0 >/dev/null
}
_temp_admin_cleanup() {
_flag=$1
_username=$2
if [ -n "${_flag}" ]; then
_debug "Cleanuping temp admin info..."
synouser --del "$_username" >/dev/null
fi
}
#_cleardeployconf key
_cleardeployconf() {
_cleardomainconf "SAVED_$1"
}
if [ -n "${flag}" ]; then
_debug "Removing temp admin user in Synology DSM"
synouser --del "$username" >/dev/null
# key
_check2cleardeployconfexp() {
_key="$1"
_clear_key="CLEAR_$_key"
# Clear saved settings if explicitly requested
if [ -n "$(eval echo \$"$_clear_key")" ]; then
_debug2 "$_key: value cleared from config, exported value will be ignored."
_cleardeployconf "$_key"
eval "$_key"=
export "$_key"=
eval SAVED_"$_key"=
export SAVED_"$_key"=
fi
}

@ -9,7 +9,7 @@
#
# Following environment variables must be set:
#
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI"
# export DEPLOY_TRUENAS_APIKEY="<API_KEY_GENERATED_IN_THE_WEB_UI>"
#
# The following environmental variables may be set if you don't like their
# default values:
@ -64,6 +64,20 @@ truenas_deploy() {
_response=$(_get "$_api_url/system/state")
_info "TrueNAS system state: $_response."
_info "Getting TrueNAS version"
_response=$(_get "$_api_url/system/version")
if echo "$_response" | grep -q "SCALE"; then
_truenas_os=$(echo "$_response" | cut -d '-' -f 2)
_truenas_version=$(echo "$_response" | cut -d '-' -f 3 | tr -d '"' | cut -d '.' -f 1,2)
else
_truenas_os="unknown"
_truenas_version="unknown"
fi
_info "Detected TrueNAS system os: $_truenas_os"
_info "Detected TrueNAS system version: $_truenas_version"
if [ -z "$_response" ]; then
_err "Unable to authenticate to $_api_url."
_err 'Check your connection settings are correct, e.g.'
@ -115,27 +129,106 @@ truenas_deploy() {
_debug3 _activate_result "$_activate_result"
_info "Checking if WebDAV certificate is the same as the TrueNAS web UI"
_webdav_list=$(_get "$_api_url/webdav")
_webdav_cert_id=$(echo "$_webdav_list" | grep '"certssl":' | tr -d -- '"certsl: ,')
if [ "$_webdav_cert_id" = "$_active_cert_id" ]; then
_info "Updating the WebDAV certificate"
_debug _webdav_cert_id "$_webdav_cert_id"
_webdav_data="{\"certssl\": \"${_cert_id}\"}"
_activate_webdav_cert="$(_post "$_webdav_data" "$_api_url/webdav" "" "PUT" "application/json")"
_webdav_new_cert_id=$(echo "$_activate_webdav_cert" | _json_decode | grep '"certssl":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
if [ "$_webdav_new_cert_id" -eq "$_cert_id" ]; then
_info "WebDAV certificate updated successfully"
else
_err "Unable to set WebDAV certificate"
_debug3 _activate_webdav_cert "$_activate_webdav_cert"
_truenas_version_23_10="23.10"
_truenas_version_24_10="24.10"
_check_version=$(printf "%s\n%s" "$_truenas_version_23_10" "$_truenas_version" | sort -V | head -n 1)
if [ "$_truenas_os" != "SCALE" ] || [ "$_check_version" != "$_truenas_version_23_10" ]; then
_info "Checking if WebDAV certificate is the same as the TrueNAS web UI"
_webdav_list=$(_get "$_api_url/webdav")
_webdav_cert_id=$(echo "$_webdav_list" | grep '"certssl":' | tr -d -- '"certsl: ,')
if [ "$_webdav_cert_id" = "$_active_cert_id" ]; then
_info "Updating the WebDAV certificate"
_debug _webdav_cert_id "$_webdav_cert_id"
_webdav_data="{\"certssl\": \"${_cert_id}\"}"
_activate_webdav_cert="$(_post "$_webdav_data" "$_api_url/webdav" "" "PUT" "application/json")"
_webdav_new_cert_id=$(echo "$_activate_webdav_cert" | _json_decode | grep '"certssl":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
if [ "$_webdav_new_cert_id" -eq "$_cert_id" ]; then
_info "WebDAV certificate updated successfully"
else
_err "Unable to set WebDAV certificate"
_debug3 _activate_webdav_cert "$_activate_webdav_cert"
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
return 1
fi
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
return 1
else
_info "WebDAV certificate is not configured or is not the same as TrueNAS web UI"
fi
_info "Checking if S3 certificate is the same as the TrueNAS web UI"
_s3_list=$(_get "$_api_url/s3")
_s3_cert_id=$(echo "$_s3_list" | grep '"certificate":' | tr -d -- '"certifa:_ ,')
if [ "$_s3_cert_id" = "$_active_cert_id" ]; then
_info "Updating the S3 certificate"
_debug _s3_cert_id "$_s3_cert_id"
_s3_data="{\"certificate\": \"${_cert_id}\"}"
_activate_s3_cert="$(_post "$_s3_data" "$_api_url/s3" "" "PUT" "application/json")"
_s3_new_cert_id=$(echo "$_activate_s3_cert" | _json_decode | grep '"certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
if [ "$_s3_new_cert_id" -eq "$_cert_id" ]; then
_info "S3 certificate updated successfully"
else
_err "Unable to set S3 certificate"
_debug3 _activate_s3_cert "$_activate_s3_cert"
_debug3 _s3_new_cert_id "$_s3_new_cert_id"
return 1
fi
_debug3 _activate_s3_cert "$_activate_s3_cert"
else
_info "S3 certificate is not configured or is not the same as TrueNAS web UI"
fi
fi
if [ "$_truenas_os" = "SCALE" ]; then
_check_version=$(printf "%s\n%s" "$_truenas_version_24_10" "$_truenas_version" | sort -V | head -n 1)
if [ "$_check_version" != "$_truenas_version_24_10" ]; then
_info "Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
if _exists jq; then
_info "Query all chart release"
_release_list=$(_get "$_api_url/chart/release")
_related_name_list=$(printf "%s" "$_release_list" | jq -r "[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique")
_release_length=$(printf "%s" "$_related_name_list" | jq -r "length")
_info "Found $_release_length related chart release in list: $_related_name_list"
for i in $(seq 0 $((_release_length - 1))); do
_release_name=$(echo "$_related_name_list" | jq -r ".[$i]")
_info "Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name"
#Read the chart release configuration
_chart_config=$(printf "%s" "$_release_list" | jq -r ".[] | select(.name==\"$_release_name\")")
#Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress
_updated_chart_config=$(printf "%s" "$_chart_config" | jq "(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert ) |= $_cert_id | .config.ingress ")
_update_chart_result="$(_post "{\"values\" : { \"ingress\" : $_updated_chart_config } }" "$_api_url/chart/release/id/$_release_name" "" "PUT" "application/json")"
_debug3 _update_chart_result "$_update_chart_result"
done
else
_info "Tool 'jq' does not exists, skip chart release checking"
fi
else
_info "Checking if any app is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
if _exists jq; then
_info "Query all apps"
_app_list=$(_get "$_api_url/app")
_app_id_list=$(printf "%s" "$_app_list" | jq -r '.[].name')
_app_length=$(echo "$_app_id_list" | wc -l)
_info "Found $_app_length apps"
_info "Checking for each app if an update is needed"
for i in $(seq 1 "$_app_length"); do
_app_id=$(echo "$_app_id_list" | sed -n "${i}p")
_app_config="$(_post "\"$_app_id\"" "$_api_url/app/config" "" "POST" "application/json")"
# Check if the app use the same certificate TrueNAS web UI
_app_active_cert_config=$(echo "$_app_config" | tr -d '\000-\037' | _json_decode | jq -r ".ix_certificates[\"$_active_cert_id\"]")
if [ "$_app_active_cert_config" != "null" ]; then
_info "Updating certificate from $_active_cert_id to $_cert_id for app: $_app_id"
#Replace the old certificate id with the new one in path
_update_app_result="$(_post "{\"values\" : { \"network\": { \"certificate_id\": $_cert_id } } }" "$_api_url/app/id/$_app_id" "" "PUT" "application/json")"
_debug3 _update_app_result "$_update_app_result"
fi
done
else
_info "Tool 'jq' does not exists, skip app checking"
fi
fi
_debug3 _webdav_new_cert_id "$_webdav_new_cert_id"
else
_info "WebDAV certificate is not configured or is not the same as TrueNAS web UI"
fi
_info "Checking if FTP certificate is the same as the TrueNAS web UI"
@ -161,50 +254,6 @@ truenas_deploy() {
_info "FTP certificate is not configured or is not the same as TrueNAS web UI"
fi
_info "Checking if S3 certificate is the same as the TrueNAS web UI"
_s3_list=$(_get "$_api_url/s3")
_s3_cert_id=$(echo "$_s3_list" | grep '"certificate":' | tr -d -- '"certifa:_ ,')
if [ "$_s3_cert_id" = "$_active_cert_id" ]; then
_info "Updating the S3 certificate"
_debug _s3_cert_id "$_s3_cert_id"
_s3_data="{\"certificate\": \"${_cert_id}\"}"
_activate_s3_cert="$(_post "$_s3_data" "$_api_url/s3" "" "PUT" "application/json")"
_s3_new_cert_id=$(echo "$_activate_s3_cert" | _json_decode | grep '"certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p')
if [ "$_s3_new_cert_id" -eq "$_cert_id" ]; then
_info "S3 certificate updated successfully"
else
_err "Unable to set S3 certificate"
_debug3 _activate_s3_cert "$_activate_s3_cert"
_debug3 _s3_new_cert_id "$_s3_new_cert_id"
return 1
fi
_debug3 _activate_s3_cert "$_activate_s3_cert"
else
_info "S3 certificate is not configured or is not the same as TrueNAS web UI"
fi
_info "Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required"
if _exists jq; then
_info "Query all chart release"
_release_list=$(_get "$_api_url/chart/release")
_related_name_list=$(printf "%s" "$_release_list" | jq -r "[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique")
_release_length=$(printf "%s" "$_related_name_list" | jq -r "length")
_info "Found $_release_length related chart release in list: $_related_name_list"
for i in $(seq 0 $((_release_length - 1))); do
_release_name=$(echo "$_related_name_list" | jq -r ".[$i]")
_info "Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name"
#Read the chart release configuration
_chart_config=$(printf "%s" "$_release_list" | jq -r ".[] | select(.name==\"$_release_name\")")
#Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress
_updated_chart_config=$(printf "%s" "$_chart_config" | jq "(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert ) |= $_cert_id | .config.ingress ")
_update_chart_result="$(_post "{\"values\" : { \"ingress\" : $_updated_chart_config } }" "$_api_url/chart/release/id/$_release_name" "" "PUT" "application/json")"
_debug3 _update_chart_result "$_update_chart_result"
done
else
_info "Tool 'jq' does not exists, skip chart release checking"
fi
_info "Deleting old certificate"
_delete_result="$(_post "" "$_api_url/certificate/id/$_active_cert_id" "" "DELETE" "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!"
}

@ -5,6 +5,15 @@
# - self-hosted Unifi Controller
# - Unifi Cloud Key (Gen1/2/2+)
# - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only)
# - Unifi Dream Machine
# This has not been tested on other "all-in-one" devices such as
# UDM Pro or Unifi Express.
#
# OS Version v2.0.0+
# Network Application version 7.0.0+
# OS version ~3.1 removed java and keytool from the UnifiOS.
# Using PKCS12 format keystore appears to work fine.
#
# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359
#returns 0 means success, otherwise error.
@ -21,7 +30,9 @@
# Keystore password (built into Unifi Controller, not a user-set password):
#DEPLOY_UNIFI_KEYPASS="aircontrolenterprise"
# Command to restart Unifi Controller:
#DEPLOY_UNIFI_RELOAD="service unifi restart"
# DEPLOY_UNIFI_RELOAD="systemctl restart unifi"
# System Properties file location for controller
#DEPLOY_UNIFI_SYSTEM_PROPERTIES="/usr/lib/unifi/data/system.properties"
#
# Settings for Unifi Cloud Key Gen1 (nginx admin pages):
# Directory where cloudkey.crt and cloudkey.key live:
@ -34,7 +45,7 @@
# Directory where unifi-core.crt and unifi-core.key live:
#DEPLOY_UNIFI_CORE_CONFIG="/data/unifi-core/config/"
# Command to restart unifi-core:
#DEPLOY_UNIFI_RELOAD="systemctl restart unifi-core"
# DEPLOY_UNIFI_OS_RELOAD="systemctl restart unifi-core"
#
# At least one of DEPLOY_UNIFI_KEYSTORE, DEPLOY_UNIFI_CLOUDKEY_CERTDIR,
# or DEPLOY_UNIFI_CORE_CONFIG must exist to receive the deployed certs.
@ -60,12 +71,16 @@ unifi_deploy() {
_getdeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR
_getdeployconf DEPLOY_UNIFI_CORE_CONFIG
_getdeployconf DEPLOY_UNIFI_RELOAD
_getdeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES
_getdeployconf DEPLOY_UNIFI_OS_RELOAD
_debug2 DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
_debug2 DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
_debug2 DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
_debug2 DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
_debug2 DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
_debug2 DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD"
_debug2 DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES"
# Space-separated list of environments detected and installed:
_services_updated=""
@ -74,14 +89,16 @@ unifi_deploy() {
_reload_cmd=""
# Unifi Controller environment (self hosted or any Cloud Key) --
# auto-detect by file /usr/lib/unifi/data/keystore:
# auto-detect by file /usr/lib/unifi/data/keystore
_unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}"
if [ -f "$_unifi_keystore" ]; then
_info "Installing certificate for Unifi Controller (Java keystore)"
_debug _unifi_keystore "$_unifi_keystore"
if ! _exists keytool; then
_err "keytool not found"
return 1
_do_keytool=0
_info "Installing certificate for Unifi Controller (PKCS12 keystore)."
else
_do_keytool=1
_info "Installing certificate for Unifi Controller (Java keystore)"
fi
if [ ! -w "$_unifi_keystore" ]; then
_err "The file $_unifi_keystore is not writable, please change the permission."
@ -92,6 +109,7 @@ unifi_deploy() {
_debug "Generate import pkcs12"
_import_pkcs12="$(_mktemp)"
_debug "_toPkcs $_import_pkcs12 $_ckey $_ccert $_cca $_unifi_keypass unifi root"
_toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
# shellcheck disable=SC2181
if [ "$?" != "0" ]; then
@ -99,22 +117,77 @@ unifi_deploy() {
return 1
fi
_debug "Import into keystore: $_unifi_keystore"
if keytool -importkeystore \
-deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
-srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
-alias unifi -noprompt; then
_debug "Import keystore success!"
rm "$_import_pkcs12"
# Save the existing keystore in case something goes wrong.
mv -f "${_unifi_keystore}" "${_unifi_keystore}"_original
_info "Previous keystore saved to ${_unifi_keystore}_original."
if [ "$_do_keytool" -eq 1 ]; then
_debug "Import into keystore: $_unifi_keystore"
if keytool -importkeystore \
-deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
-srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
-alias unifi -noprompt; then
_debug "Import keystore success!"
else
_err "Error importing into Unifi Java keystore."
_err "Please re-run with --debug and report a bug."
_info "Restoring original keystore."
mv -f "${_unifi_keystore}"_original "${_unifi_keystore}"
rm "$_import_pkcs12"
return 1
fi
else
_err "Error importing into Unifi Java keystore."
_err "Please re-run with --debug and report a bug."
rm "$_import_pkcs12"
return 1
_debug "Copying new keystore to $_unifi_keystore"
cp -f "$_import_pkcs12" "$_unifi_keystore"
fi
# correct file ownership according to the directory, the keystore is placed in
_unifi_keystore_dir=$(dirname "${_unifi_keystore}")
_unifi_keystore_dir_owner=$(find "${_unifi_keystore_dir}" -maxdepth 0 -printf '%u\n')
_unifi_keystore_owner=$(find "${_unifi_keystore}" -maxdepth 0 -printf '%u\n')
if ! [ "${_unifi_keystore_owner}" = "${_unifi_keystore_dir_owner}" ]; then
_debug "Changing keystore owner to ${_unifi_keystore_dir_owner}"
chown "$_unifi_keystore_dir_owner" "${_unifi_keystore}" >/dev/null 2>&1 # fail quietly if we're not running as root
fi
if systemctl -q is-active unifi; then
_reload_cmd="${_reload_cmd:+$_reload_cmd && }service unifi restart"
# Update unifi service for certificate cipher compatibility
_unifi_system_properties="${DEPLOY_UNIFI_SYSTEM_PROPERTIES:-/usr/lib/unifi/data/system.properties}"
if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \
-in "$_import_pkcs12" \
-password pass:aircontrolenterprise \
-nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \
-noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then
if [ -f "$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" ]; then
_unifi_system_properties="$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties"
else
_unifi_system_properties="/usr/lib/unifi/data/system.properties"
fi
if [ -f "${_unifi_system_properties}" ]; then
cp -f "${_unifi_system_properties}" "${_unifi_system_properties}"_original
_info "Updating system configuration for cipher compatibility."
_info "Saved original system config to ${_unifi_system_properties}_original"
sed -i '/unifi\.https\.ciphers/d' "${_unifi_system_properties}"
echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>"${_unifi_system_properties}"
sed -i '/unifi\.https\.sslEnabledProtocols/d' "${_unifi_system_properties}"
echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>"${_unifi_system_properties}"
_info "System configuration updated."
fi
fi
rm "$_import_pkcs12"
# Restarting unifi-core will bring up unifi, doing it out of order results in
# a certificate error, and breaks wifiman.
# Restart if we aren't doing Unifi OS (e.g. unifi-core service), otherwise stop for later restart.
_unifi_reload="${DEPLOY_UNIFI_RELOAD:-systemctl restart unifi}"
if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload"
else
_info "Stopping Unifi Controller for later restart."
_unifi_stop=$(echo "${_unifi_reload}" | sed -e 's/restart/stop/')
$_unifi_stop
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload"
_info "Unifi Controller stopped."
fi
_services_updated="${_services_updated} unifi"
_info "Install Unifi Controller certificate success!"
@ -134,13 +207,24 @@ unifi_deploy() {
return 1
fi
# Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks.
# Normally /usr/lib/unifi/data/keystore is a symlink there (so the keystore was
# updated above), but if not, we don't know how to handle this installation:
if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then
_err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'"
return 1
# It appears that unifi won't start if this is a symlink, so we'll copy it instead.
# if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then
# _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'"
# return 1
# fi
_info "Updating ${_cloudkey_certdir}/unifi.keystore.jks"
if [ -e "${_cloudkey_certdir}/unifi.keystore.jks" ]; then
if [ -L "${_cloudkey_certdir}/unifi.keystore.jks" ]; then
rm -f "${_cloudkey_certdir}/unifi.keystore.jks"
else
mv "${_cloudkey_certdir}/unifi.keystore.jks" "${_cloudkey_certdir}/unifi.keystore.jks_original"
fi
fi
cp "${_unifi_keystore}" "${_cloudkey_certdir}/unifi.keystore.jks"
cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt"
cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key"
(cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks)
@ -165,12 +249,17 @@ unifi_deploy() {
return 1
fi
# Save the existing certs in case something goes wrong.
cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt
cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key
_info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt.key."
cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt"
cat "$_ckey" >"${_unifi_core_config}/unifi-core.key"
if systemctl -q is-active unifi-core; then
_reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi-core"
fi
_unifi_os_reload="${DEPLOY_UNIFI_OS_RELOAD:-systemctl restart unifi-core}"
_reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_os_reload"
_info "Install UnifiOS certificate success!"
_services_updated="${_services_updated} unifi-core"
elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then
@ -209,6 +298,8 @@ unifi_deploy() {
_savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
_savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
_savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
_savedeployconf DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD"
_savedeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES"
return 0
}

@ -70,10 +70,10 @@ vault_deploy() {
# JSON does not allow multiline strings.
# So replacing new-lines with "\n" here
_ckey=$(sed -z 's/\n/\\n/g' <"$2")
_ccert=$(sed -z 's/\n/\\n/g' <"$3")
_cca=$(sed -z 's/\n/\\n/g' <"$4")
_cfullchain=$(sed -z 's/\n/\\n/g' <"$5")
_ckey=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$2")
_ccert=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$3")
_cca=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$4")
_cfullchain=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$5")
export _H1="X-Vault-Token: $VAULT_TOKEN"

@ -106,5 +106,5 @@ vsftpd_deploy() {
fi
return 1
fi
return 0
}

@ -1,22 +1,18 @@
#!/usr/bin/env sh
# This file name is "dns_1984hosting.sh"
# So, here must be a method dns_1984hosting_add()
# Which will be called by acme.sh to add the txt record to your api system.
# returns 0 means success, otherwise error.
# Author: Adrian Fedoreanu
# Report Bugs here: https://github.com/acmesh-official/acme.sh
# or here... https://github.com/acmesh-official/acme.sh/issues/2851
# shellcheck disable=SC2034
dns_1984hosting_info='1984.hosting
Domains: 1984.is
Site: 1984.hosting
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_1984hosting
Options:
One984HOSTING_Username Username
One984HOSTING_Password Password
Issues: github.com/acmesh-official/acme.sh/issues/2851
Author: Adrian Fedoreanu
'
######## Public functions #####################
# Export 1984HOSTING username and password in following variables
#
# One984HOSTING_Username=username
# One984HOSTING_Password=password
#
# username/password and csrftoken/sessionid cookies are saved in ~/.acme.sh/account.conf
# Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Add a text record.
dns_1984hosting_add() {
@ -215,8 +211,8 @@ _get_root() {
return 1
fi
_authget "https://1984.hosting/domains/soacheck/?zone=$h&nameserver=ns0.1984.is."
if _contains "$_response" "serial" && ! _contains "$_response" "null"; then
_authget "https://1984.hosting/domains/zonestatus/$h/?cached=no"
if _contains "$_response" '"ok": true'; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
@ -250,7 +246,6 @@ _authget() {
}
# Truncate huge HTML response
# Echo: Argument list too long
_htmlget() {
export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE"
_response=$(_get "$1" | grep "$2")

@ -1,18 +1,18 @@
#!/usr/bin/env sh
#
#Author: Wolfgang Ebner
#Author: Sven Neubuaer
#Report Bugs here: https://github.com/dampfklon/acme.sh
#
# Usage:
# export ACMEDNS_BASE_URL="https://auth.acme-dns.io"
#
# You can optionally define an already existing account:
#
# export ACMEDNS_USERNAME="<username>"
# export ACMEDNS_PASSWORD="<password>"
# export ACMEDNS_SUBDOMAIN="<subdomain>"
#
# shellcheck disable=SC2034
dns_acmedns_info='acme-dns Server API
The acme-dns is a limited DNS server with RESTful API to handle ACME DNS challenges.
Site: github.com/joohoi/acme-dns
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_acmedns
Options:
ACMEDNS_USERNAME Username. Optional.
ACMEDNS_PASSWORD Password. Optional.
ACMEDNS_SUBDOMAIN Subdomain. Optional.
ACMEDNS_BASE_URL API endpoint. Default: "https://auth.acme-dns.io".
Issues: github.com/dampfklon/acme.sh
Author: Wolfgang Ebner, Sven Neubuaer
'
######## Public functions #####################
#Usage: dns_acmedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"

@ -1,9 +1,17 @@
#!/usr/bin/env sh
## Acmeproxy DNS provider to be used with acmeproxy (https://github.com/mdbraber/acmeproxy)
## API integration by Maarten den Braber
##
## Report any bugs via https://github.com/mdbraber/acme.sh
# shellcheck disable=SC2034
dns_acmeproxy_info='AcmeProxy Server API
AcmeProxy can be used to as a single host in your network to request certificates through a DNS API.
Clients can connect with the one AcmeProxy host so you do not need to store DNS API credentials on every single host.
Site: github.com/mdbraber/acmeproxy
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_acmeproxy
Options:
ACMEPROXY_ENDPOINT API Endpoint
ACMEPROXY_USERNAME Username
ACMEPROXY_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/2251
Author: Maarten den Braber
'
dns_acmeproxy_add() {
fulldomain="${1}"

@ -1,6 +1,13 @@
#!/usr/bin/env sh
#ACTIVE24_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
# shellcheck disable=SC2034
dns_active24_info='Active24.com
Site: Active24.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24
Options:
ACTIVE24_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2059
Author: Milan Pála
'
ACTIVE24_Api="https://api.active24.com"
@ -76,10 +83,10 @@ _get_root() {
return 1
fi
i=2
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug "h" "$h"
if [ -z "$h" ]; then
#not valid
@ -87,7 +94,7 @@ _get_root() {
fi
if _contains "$response" "\"$h\"" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,12 +1,13 @@
#!/usr/bin/env sh
#
#AD_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
#This is the Alwaysdata api wrapper for acme.sh
#
#Author: Paul Koppen
#Report Bugs here: https://github.com/wpk-/acme.sh
# shellcheck disable=SC2034
dns_ad_info='AlwaysData.com
Site: AlwaysData.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ad
Options:
AD_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/pull/503
Author: Paul Koppen
'
AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1"
@ -94,7 +95,7 @@ _get_root() {
if _ad_rest GET "domain/"; then
response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -105,7 +106,7 @@ _get_root() {
if [ "$hostedzone" ]; then
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,27 +1,27 @@
#!/usr/bin/env sh
Ali_API="https://alidns.aliyuncs.com/"
#Ali_Key="LTqIA87hOKdjevsf5"
#Ali_Secret="0p5EYueFNq501xnCPzKNbx6K51qPH2"
# shellcheck disable=SC2034
dns_ali_info='AlibabaCloud.com
Domains: Aliyun.com
Site: AlibabaCloud.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ali
Options:
Ali_Key API Key
Ali_Secret API Secret
'
# NOTICE:
# This file is referenced by Alibaba Cloud Services deploy hooks
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
# Be careful when modifying this file, especially when making breaking changes for common functions
Ali_DNS_API="https://alidns.aliyuncs.com/"
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ali_add() {
fulldomain=$1
txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
Ali_Key=""
Ali_Secret=""
_err "You don't specify aliyun api key and secret yet."
return 1
fi
#save the api key and secret to the account conf file.
_saveaccountconf_mutable Ali_Key "$Ali_Key"
_saveaccountconf_mutable Ali_Secret "$Ali_Secret"
_prepare_ali_credentials || return 1
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
@ -46,14 +46,74 @@ dns_ali_rm() {
_clean
}
#################### Private functions below ##################################
#################### Alibaba Cloud common functions below ####################
_prepare_ali_credentials() {
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
Ali_Key=""
Ali_Secret=""
_err "You don't specify aliyun api key and secret yet."
return 1
fi
#save the api key and secret to the account conf file.
_saveaccountconf_mutable Ali_Key "$Ali_Key"
_saveaccountconf_mutable Ali_Secret "$Ali_Secret"
}
# act ign mtd
_ali_rest() {
act="$1"
ign="$2"
mtd="${3:-GET}"
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
signature=$(printf "%s" "$signature" | _url_encode upper-hex)
url="$endpoint?Signature=$signature"
if [ "$mtd" = "GET" ]; then
url="$url&$query"
response="$(_get "$url")"
else
response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")"
fi
_ret="$?"
_debug2 response "$response"
if [ "$_ret" != "0" ]; then
_err "Error <$act>"
return 1
fi
if [ -z "$ign" ]; then
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
if [ "$message" ]; then
_err "$message"
return 1
fi
fi
}
_ali_nonce() {
#_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
#Not so good...
date +"%s%N" | sed 's/%N//g'
}
_timestamp() {
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
}
#################### Private functions below ####################
_get_root() {
domain=$1
i=2
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
@ -65,7 +125,7 @@ _get_root() {
fi
if _contains "$response" "PageNumber"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_debug _sub_domain "$_sub_domain"
_domain="$h"
_debug _domain "$_domain"
@ -77,52 +137,10 @@ _get_root() {
return 1
}
_ali_rest() {
signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
signature=$(_ali_urlencode "$signature")
url="$Ali_API?$query&Signature=$signature"
if ! response="$(_get "$url")"; then
_err "Error <$1>"
return 1
fi
_debug2 response "$response"
if [ -z "$2" ]; then
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
if [ "$message" ]; then
_err "$message"
return 1
fi
fi
}
_ali_urlencode() {
_str="$1"
_str_len=${#_str}
_u_i=1
while [ "$_u_i" -le "$_str_len" ]; do
_str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
case $_str_c in [a-zA-Z0-9.~_-])
printf "%s" "$_str_c"
;;
*)
printf "%%%02X" "'$_str_c"
;;
esac
_u_i="$(_math "$_u_i" + 1)"
done
}
_ali_nonce() {
#_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
#Not so good...
date +"%s%N" | sed 's/%N//g'
}
_check_exist_query() {
_qdomain="$1"
_qsubdomain="$2"
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DescribeDomainRecords'
@ -138,6 +156,7 @@ _check_exist_query() {
}
_add_record_query() {
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=AddDomainRecord'
@ -154,6 +173,7 @@ _add_record_query() {
}
_delete_record_query() {
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DeleteDomainRecord'
@ -167,6 +187,7 @@ _delete_record_query() {
}
_describe_records_query() {
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DescribeDomainRecords'
@ -197,7 +218,3 @@ _clean() {
fi
}
_timestamp() {
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
}

@ -0,0 +1,185 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_alviy_info='Alviy.com
Site: Alviy.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_alviy
Options:
Alviy_token API token. Get it from the https://cloud.alviy.com/token
Issues: github.com/acmesh-official/acme.sh/issues/5115
'
Alviy_Api="https://cloud.alviy.com/api/v1"
######## Public functions #####################
#Usage: dns_alviy_add _acme-challenge.www.domain.com "content"
dns_alviy_add() {
fulldomain=$1
txtvalue=$2
Alviy_token="${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}"
if [ -z "$Alviy_token" ]; then
Alviy_token=""
_err "Please specify Alviy token."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable Alviy_token "$Alviy_token"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting existing records"
if _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then
_info "This record already exists, skipping"
return 0
fi
_add_data="{\"content\":\"$txtvalue\",\"type\":\"TXT\"}"
_debug2 _add_data "$_add_data"
_info "Adding record"
if _alviy_rest POST "zone/$_domain/domain/$fulldomain/" "$_add_data"; then
_debug "Checking updated records of '${fulldomain}'"
if ! _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then
_err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!"
return 1
fi
else
_err "Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set."
return 1
fi
_sleep 10
_info "Added TXT record '${txtvalue}' for '${fulldomain}'."
return 0
}
#fulldomain
dns_alviy_rm() {
fulldomain=$1
txtvalue=$2
Alviy_token="${Alviy_token:-$(_readaccountconf_mutable Alviy_token)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _alviy_txt_exists "$_domain" "$fulldomain" "$txtvalue"; then
_info "The record does not exist, skip"
return 0
fi
_add_data=""
uuid=$(echo "$response" | tr "{" "\n" | grep "$txtvalue" | tr "," "\n" | grep uuid | cut -d \" -f4)
# delete record
_debug "Delete TXT record for '${fulldomain}'"
if ! _alviy_rest DELETE "zone/$_domain/record/$uuid" "{\"confirm\":1}"; then
_err "Cannot delete empty TXT record for '$fulldomain'"
return 1
fi
_info "The record '$fulldomain'='$txtvalue' deleted"
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
i=3
a="init"
while [ -n "$a" ]; do
a=$(printf "%s" "$domain" | cut -d . -f $i-)
i=$((i + 1))
done
n=$((i - 3))
h=$(printf "%s" "$domain" | cut -d . -f $n-)
if [ -z "$h" ]; then
#not valid
_alviy_rest GET "zone/$domain/"
_debug "can't get host from $domain"
return 1
fi
if ! _alviy_rest GET "zone/$h/"; then
return 1
fi
if _contains "$response" '"code":"NOT_FOUND"'; then
_debug "$h not found"
else
s=$((n - 1))
_sub_domain=$(printf "%s" "$domain" | cut -d . -f -$s)
_domain="$h"
return 0
fi
return 1
}
_alviy_txt_exists() {
zone=$1
domain=$2
content_data=$3
_debug "Getting existing records"
if ! _alviy_rest GET "zone/$zone/domain/$domain/TXT/"; then
_info "The record does not exist"
return 1
fi
if ! _contains "$response" "$3"; then
_info "The record has other value"
return 1
fi
# GOOD code return - TRUE function
return 0
}
_alviy_rest() {
method=$1
path="$2"
content_data="$3"
_debug "$path"
export _H1="Authorization: Bearer $Alviy_token"
export _H2="Content-Type: application/json"
if [ "$content_data" ] || [ "$method" = "DELETE" ]; then
_debug "data ($method): " "$content_data"
response="$(_post "$content_data" "$Alviy_Api/$path" "" "$method")"
else
response="$(_get "$Alviy_Api/$path")"
fi
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
if [ "$_code" = "401" ]; then
_err "It seems that your api key or secret is not correct."
return 1
fi
if [ "$_code" != "200" ]; then
_err "API call error ($method): $path Response code $_code"
fi
if [ "$?" != "0" ]; then
_err "error on rest call ($method): $path. Response:"
_err "$response"
return 1
fi
_debug2 response "$response"
return 0
}

@ -1,9 +1,12 @@
#!/usr/bin/env sh
# Anexia CloudDNS acme.sh hook
# Author: MA
#ANX_Token="xxxx"
# shellcheck disable=SC2034
dns_anx_info='Anexia.com CloudDNS
Site: Anexia.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_anx
Options:
ANX_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/3238
'
ANX_API='https://engine.anexia-it.com/api/clouddns/v1'
@ -127,18 +130,17 @@ _get_root() {
i=1
p=1
_anx_rest GET "zone.json"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
_anx_rest GET "zone.json/${h}"
if _contains "$response" "\"name\":\"$h\""; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,17 +1,14 @@
#!/usr/bin/env sh
################################################################################
# ACME.sh 3rd party DNS API plugin for ArtFiles.de
################################################################################
# Author: Martin Arndt, https://troublezone.net/
# Released: 2022-02-27
# Issues: https://github.com/acmesh-official/acme.sh/issues/4718
################################################################################
# Usage:
# 1. export AF_API_USERNAME='api12345678'
# 2. export AF_API_PASSWORD='apiPassword'
# 3. acme.sh --issue -d example.com --dns dns_artfiles
################################################################################
# shellcheck disable=SC2034
dns_artfiles_info='ArtFiles.de
Site: ArtFiles.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_artfiles
Options:
AF_API_USERNAME API Username
AF_API_PASSWORD API Password
Issues: github.com/acmesh-official/acme.sh/issues/4718
Author: Martin Arndt <https://troublezone.net/>
'
########## API configuration ###################################################

@ -1,11 +1,16 @@
#!/usr/bin/env sh
# Arvan_Token="Apikey xxxx"
# shellcheck disable=SC2034
dns_arvan_info='ArvanCloud.ir
Site: ArvanCloud.ir
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_arvan
Options:
Arvan_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2796
Author: Vahid Fardi
'
ARVAN_API_URL="https://napi.arvancloud.ir/cdn/4.0/domains"
# Author: Vahid Fardi
# Report Bugs here: https://github.com/Neilpang/acme.sh
#
######## Public functions #####################
#Usage: dns_arvan_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@ -102,7 +107,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -115,7 +120,7 @@ _get_root() {
if _contains "$response" "\"domain\":\"$h\""; then
_domain_id=$(echo "$response" | cut -d : -f 3 | cut -d , -f 1 | tr -d \")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,9 +1,15 @@
#!/usr/bin/env sh
#
#AURORA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
#AURORA_Secret="sdfsdfsdfljlbjkljlkjsdfoiwje"
# shellcheck disable=SC2034
dns_aurora_info='versio.nl AuroraDNS
Domains: pcextreme.nl
Site: versio.nl
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_aurora
Options:
AURORA_Key API Key
AURORA_Secret API Secret
Issues: github.com/acmesh-official/acme.sh/issues/3459
Author: Jasper Zonneveld
'
AURORA_Api="https://api.auroradns.eu"
@ -111,7 +117,7 @@ _get_root() {
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -126,7 +132,7 @@ _get_root() {
_domain_id=$(echo "$response" | _normalizeJson | tr -d "{}" | tr "," "\n" | grep "\"id\": *\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,16 +1,15 @@
#!/usr/bin/env sh
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
# This is the InternetX autoDNS xml api wrapper for acme.sh
# Author: auerswald@gmail.com
# Created: 2018-01-14
#
# export AUTODNS_USER="username"
# export AUTODNS_PASSWORD="password"
# export AUTODNS_CONTEXT="context"
#
# Usage:
# acme.sh --issue --dns dns_autodns -d example.com
# shellcheck disable=SC2034
dns_autodns_info='InternetX autoDNS
InternetX autoDNS XML API
Site: InternetX.com/autodns/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_autodns
Options:
AUTODNS_USER Username
AUTODNS_PASSWORD Password
AUTODNS_CONTEXT Context
Author: <auerswald@gmail.com>
'
AUTODNS_API="https://gateway.autodns.com"
@ -111,7 +110,7 @@ _get_autodns_zone() {
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
@ -129,7 +128,7 @@ _get_autodns_zone() {
if _contains "$autodns_response" "<summary>1</summary>" >/dev/null; then
_zone="$(echo "$autodns_response" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_system_ns="$(echo "$autodns_response" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
return 0
fi

@ -1,13 +1,15 @@
#!/usr/bin/env sh
#
#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
#AWS_SECRET_ACCESS_KEY="xxxxxxx"
#This is the Amazon Route53 api wrapper for acme.sh
#All `_sleep` commands are included to avoid Route53 throttling, see
#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
# shellcheck disable=SC2034
dns_aws_info='Amazon AWS Route53 domain API
Site: docs.aws.amazon.com/route53/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_aws
Options:
AWS_ACCESS_KEY_ID API Key ID
AWS_SECRET_ACCESS_KEY API Secret
'
# All `_sleep` commands are included to avoid Route53 throttling, see
# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
AWS_HOST="route53.amazonaws.com"
AWS_URL="https://$AWS_HOST"
@ -145,7 +147,6 @@ dns_aws_rm() {
fi
_sleep 1
return 1
}
#################### Private functions below ##################################
@ -157,7 +158,7 @@ _get_root() {
# iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100 | sed 's/\./\\./g')
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100 | sed 's/\./\\./g')
_debug "Checking domain: $h"
if [ -z "$h" ]; then
_error "invalid domain"
@ -173,7 +174,7 @@ _get_root() {
if [ "$hostedzone" ]; then
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi
@ -207,24 +208,40 @@ _use_container_role() {
}
_use_instance_role() {
_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
_debug "_url" "$_url"
if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then
_instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then
_debug "Using IMDSv2"
_token_url="http://169.254.169.254/latest/api/token"
export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600"
_token="$(_post "" "$_token_url" "" "PUT")"
_secure_debug3 "_token" "$_token"
if [ -z "$_token" ]; then
_debug "Unable to fetch IMDSv2 token from instance metadata"
return 1
fi
export _H1="X-aws-ec2-metadata-token: $_token"
fi
if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then
_debug "Unable to fetch IAM role from instance metadata"
return 1
fi
_aws_role=$(_get "$_url" "" 1)
_debug "_aws_role" "$_aws_role"
_use_metadata "$_url$_aws_role"
_instance_role_name=$(_get "$_instance_role_name_url" "" 1)
_debug "_instance_role_name" "$_instance_role_name"
_use_metadata "$_instance_role_name_url$_instance_role_name" "$_token"
}
_use_metadata() {
export _H1="X-aws-ec2-metadata-token: $2"
_aws_creds="$(
_get "$1" "" 1 |
_normalizeJson |
tr '{,}' '\n' |
while read -r _line; do
_key="$(echo "${_line%%:*}" | tr -d '"')"
_key="$(echo "${_line%%:*}" | tr -d '\"')"
_value="${_line#*:}"
_debug3 "_key" "$_key"
_secure_debug3 "_value" "$_value"

@ -1,9 +1,13 @@
#!/usr/bin/env sh
#
#AZION_Email=""
#AZION_Password=""
#
# shellcheck disable=SC2034
dns_azion_info='Azion.om
Site: Azion.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_azion
Options:
AZION_Email Email
AZION_Password Password
Issues: github.com/acmesh-official/acme.sh/issues/3555
'
AZION_Api="https://api.azionapi.net"
@ -96,7 +100,7 @@ _get_root() {
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
# not valid
@ -107,7 +111,7 @@ _get_root() {
_domain_id=$(echo "$response" | tr '{' "\n" | grep "\"domain\":\"$h\"" | _egrep_o "\"id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \")
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,13 +1,25 @@
#!/usr/bin/env sh
WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS"
# shellcheck disable=SC2034
dns_azure_info='Azure
Site: Azure.microsoft.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_azure
Options:
AZUREDNS_SUBSCRIPTIONID Subscription ID
AZUREDNS_TENANTID Tenant ID
AZUREDNS_APPID App ID. App ID of the service principal
AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal
AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false"
AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Optional.
'
wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
#
# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate
# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/create-or-update?view=rest-dns-2018-05-01&tabs=HTTP
#
dns_azure_add() {
@ -20,6 +32,7 @@ dns_azure_add() {
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure Subscription ID"
return 1
fi
@ -34,37 +47,45 @@ dns_azure_add() {
_saveaccountconf_mutable AZUREDNS_TENANTID ""
_saveaccountconf_mutable AZUREDNS_APPID ""
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET ""
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN ""
else
_info "You didn't ask to use Azure managed identity, checking service principal credentials"
_info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token"
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
if [ -z "$AZUREDNS_TENANTID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure Tenant ID "
return 1
fi
if [ -z "$AZUREDNS_TENANTID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
_err "You didn't specify the Azure Tenant ID "
return 1
fi
if [ -z "$AZUREDNS_APPID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
_err "You didn't specify the Azure App ID"
return 1
fi
if [ -z "$AZUREDNS_APPID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure App ID"
return 1
fi
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
_err "You didn't specify the Azure Client Secret"
return 1
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure Client Secret"
return 1
fi
else
_info "Using provided bearer token"
fi
#save account details to account conf file, don't opt in for azure manages identity check.
@ -72,9 +93,14 @@ dns_azure_add() {
_saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID"
_saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID"
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET"
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$AZUREDNS_BEARERTOKEN"
fi
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
else
accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g")
fi
if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
_err "invalid domain"
@ -124,7 +150,7 @@ dns_azure_add() {
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
#
# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete
# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/delete?view=rest-dns-2018-05-01&tabs=HTTP
#
dns_azure_rm() {
fulldomain=$1
@ -136,6 +162,7 @@ dns_azure_rm() {
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure Subscription ID "
return 1
fi
@ -144,40 +171,51 @@ dns_azure_rm() {
if [ "$AZUREDNS_MANAGEDIDENTITY" = true ]; then
_info "Using Azure managed identity"
else
_info "You didn't ask to use Azure managed identity, checking service principal credentials"
_info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token"
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}"
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}"
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}"
AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
if [ -z "$AZUREDNS_TENANTID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure Tenant ID "
return 1
fi
if [ -z "$AZUREDNS_TENANTID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
_err "You didn't specify the Azure Tenant ID "
return 1
fi
if [ -z "$AZUREDNS_APPID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
_err "You didn't specify the Azure App ID"
return 1
fi
if [ -z "$AZUREDNS_APPID" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure App ID"
return 1
fi
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
_err "You didn't specify the Azure Client Secret"
return 1
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then
AZUREDNS_SUBSCRIPTIONID=""
AZUREDNS_TENANTID=""
AZUREDNS_APPID=""
AZUREDNS_CLIENTSECRET=""
AZUREDNS_BEARERTOKEN=""
_err "You didn't specify the Azure Client Secret"
return 1
fi
else
_info "Using provided bearer token"
fi
fi
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
if [ -z "$AZUREDNS_BEARERTOKEN" ]; then
accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET")
else
accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g")
fi
if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then
_err "invalid domain"
@ -256,10 +294,10 @@ _azure_rest() {
if [ "$_code" = "401" ]; then
# we have an invalid access token set to expired
_saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "0"
_err "access denied make sure your Azure settings are correct. See $WIKI"
_err "Access denied. Invalid access token. Make sure your Azure settings are correct. See: $wiki"
return 1
fi
# See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes
# See https://learn.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes
if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then
_request_retry_times="$(_math "$_request_retry_times" + 1)"
_info "REST call error $_code retrying $ep in $_request_retry_times s"
@ -277,14 +315,14 @@ _azure_rest() {
return 0
}
## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token
## Ref: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#request-an-access-token
_azure_getaccess_token() {
managedIdentity=$1
tenantID=$2
clientID=$3
clientSecret=$4
accesstoken="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}"
accesstoken="${AZUREDNS_ACCESSTOKEN:-$(_readaccountconf_mutable AZUREDNS_ACCESSTOKEN)}"
expires_on="${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}"
# can we reuse the bearer token?
@ -301,7 +339,7 @@ _azure_getaccess_token() {
_debug "getting new bearer token"
if [ "$managedIdentity" = true ]; then
# https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
# https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
export _H1="Metadata: true"
response="$(_get http://169.254.169.254/metadata/identity/oauth2/token\?api-version=2018-02-01\&resource=https://management.azure.com/)"
response="$(echo "$response" | _normalizeJson)"
@ -321,14 +359,14 @@ _azure_getaccess_token() {
fi
if [ -z "$accesstoken" ]; then
_err "no acccess token received. Check your Azure settings see $WIKI"
_err "No acccess token received. Check your Azure settings. See: $wiki"
return 1
fi
if [ "$_ret" != "0" ]; then
_err "error $response"
return 1
fi
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$accesstoken"
_saveaccountconf_mutable AZUREDNS_ACCESSTOKEN "$accesstoken"
_saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "$expires_on"
printf "%s" "$accesstoken"
return 0
@ -341,15 +379,18 @@ _get_root() {
i=1
p=1
## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
## returns up to 100 zones in one response therefore handling more results is not not implemented
## (ZoneListResult with continuation token for the next page of results)
## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
## Ref: https://learn.microsoft.com/en-us/rest/api/dns/zones/list?view=rest-dns-2018-05-01&tabs=HTTP
## returns up to 100 zones in one response. Handling more results is not implemented
## (ZoneListResult with continuation token for the next page of results)
##
## TODO: handle more than 100 results, as per:
## https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-dns-limits
## The new limit is 250 Public DNS zones per subscription, while the old limit was only 100
##
_azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\$top=500&api-version=2017-09-01" "" "$accesstoken"
# Find matching domain name in Json response
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug2 "Checking domain: $h"
if [ -z "$h" ]; then
#not valid
@ -364,7 +405,7 @@ _get_root() {
#create the record at the domain apex (@) if only the domain name was provided as --domain-alias
_sub_domain="@"
else
_sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
_sub_domain=$(echo "$domain" | cut -d . -f 1-"$p")
fi
_domain=$h
return 0

@ -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":.*}}$'
}

@ -1,18 +1,17 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_bookmyname_info='BookMyName.com
Site: BookMyName.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bookmyname
Options:
BOOKMYNAME_USERNAME Username
BOOKMYNAME_PASSWORD Password
Issues: github.com/acmesh-official/acme.sh/issues/3209
Author: Neilpang
'
#Here is a sample custom api script.
#This file name is "dns_bookmyname.sh"
#So, here must be a method dns_bookmyname_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
#
#Author: Neilpang
#Report Bugs here: https://github.com/acmesh-official/acme.sh
#
######## Public functions #####################
# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
# BookMyName urls:
# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=add&value="XXXXXXXX"'
# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=remove&value="XXXXXXXX"'

@ -1,16 +1,13 @@
#!/usr/bin/env sh
## Will be called by acme.sh to add the TXT record via the Bunny DNS API.
## returns 0 means success, otherwise error.
## Author: nosilver4u <nosilver4u at ewww.io>
## GitHub: https://github.com/nosilver4u/acme.sh
##
## Environment Variables Required:
##
## BUNNY_API_KEY="75310dc4-ca77-9ac3-9a19-f6355db573b49ce92ae1-2655-3ebd-61ac-3a3ae34834cc"
##
# shellcheck disable=SC2034
dns_bunny_info='Bunny.net
Site: Bunny.net/dns/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bunny
Options:
BUNNY_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/4296
Author: <nosilver4u@ewww.io>
'
##################### Public functions #####################
@ -199,7 +196,7 @@ _get_base_domain() {
_debug2 domain_list "$domain_list"
i=1
while [ $i -gt 0 ]; do
while [ "$i" -gt 0 ]; do
## get next longest domain
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
## check we got something back from our cut (or are we at the end)
@ -211,7 +208,7 @@ _get_base_domain() {
## check if it exists
if [ -n "$found" ]; then
## exists - exit loop returning the parts
sub_point=$(_math $i - 1)
sub_point=$(_math "$i" - 1)
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
_domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
_debug _domain_id "$_domain_id"
@ -221,11 +218,11 @@ _get_base_domain() {
return 0
fi
## increment cut point $i
i=$(_math $i + 1)
i=$(_math "$i" + 1)
done
if [ -z "$found" ]; then
page=$(_math $page + 1)
page=$(_math "$page" + 1)
nextpage="https://api.bunny.net/dnszone?page=$page"
## Find the next page if we don't have a match.
hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")"

@ -1,13 +1,16 @@
#!/usr/bin/env sh
#
#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
#CF_Email="xxxx@sss.com"
#CF_Token="xxxx"
#CF_Account_ID="xxxx"
#CF_Zone_ID="xxxx"
# shellcheck disable=SC2034
dns_cf_info='CloudFlare
Site: CloudFlare.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf
Options:
CF_Key API Key
CF_Email Your account email
OptionsAlt:
CF_Token API Token
CF_Account_ID Account ID
CF_Zone_ID Zone ID. Optional.
'
CF_Api="https://api.cloudflare.com/client/v4"
@ -183,7 +186,7 @@ _get_root() {
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -203,7 +206,7 @@ _get_root() {
if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_count":1'; then
_domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,10 +1,15 @@
#!/usr/bin/env sh
# Author: Radek Sprta <sprta@vshosting.cz>
#CLOUDDNS_EMAIL=XXXXX
#CLOUDDNS_PASSWORD="YYYYYYYYY"
#CLOUDDNS_CLIENT_ID=XXXXX
# shellcheck disable=SC2034
dns_clouddns_info='vshosting.cz CloudDNS
Site: github.com/vshosting/clouddns
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_clouddns
Options:
CLOUDDNS_EMAIL Email
CLOUDDNS_PASSWORD Password
CLOUDDNS_CLIENT_ID Client ID
Issues: github.com/acmesh-official/acme.sh/issues/2699
Author: Radek Sprta <sprta@vshosting.cz>
'
CLOUDDNS_API='https://admin.vshosting.cloud/clouddns'
CLOUDDNS_LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login'

@ -1,12 +1,15 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_cloudns_info='ClouDNS.net
Site: ClouDNS.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cloudns
Options:
CLOUDNS_AUTH_ID Regular auth ID
CLOUDNS_SUB_AUTH_ID Sub auth ID
CLOUDNS_AUTH_PASSWORD Auth Password
Author: Boyan Peychev <boyan@cloudns.net>
'
# Author: Boyan Peychev <boyan at cloudns dot net>
# Repository: https://github.com/ClouDNS/acme.sh/
# Editor: I Komang Suryadana
#CLOUDNS_AUTH_ID=XXXXX
#CLOUDNS_SUB_AUTH_ID=XXXXX
#CLOUDNS_AUTH_PASSWORD="YYYYYYYYY"
CLOUDNS_API="https://api.cloudns.net"
DOMAIN_TYPE=
DOMAIN_MASTER=
@ -161,7 +164,7 @@ _dns_cloudns_get_zone_info() {
_dns_cloudns_get_zone_name() {
i=2
while true; do
zoneForCheck=$(printf "%s" "$1" | cut -d . -f $i-100)
zoneForCheck=$(printf "%s" "$1" | cut -d . -f "$i"-100)
if [ -z "$zoneForCheck" ]; then
return 1

@ -1,7 +1,14 @@
#!/usr/bin/env sh
# DNS API for acme.sh for Core-Networks (https://beta.api.core-networks.de/doc/).
# created by 5ll and francis
# shellcheck disable=SC2034
dns_cn_info='Core-Networks.de
Site: beta.api.Core-Networks.de/doc/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cn
Options:
CN_User User
CN_Password Password
Issues: github.com/acmesh-official/acme.sh/issues/2142
Author: 5ll, francis
'
CN_API="https://beta.api.core-networks.de"
@ -124,7 +131,7 @@ _cn_get_root() {
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
_debug _H1 "${_H1}"
@ -142,7 +149,7 @@ _cn_get_root() {
fi
if _contains "$_cn_zonelist" "\"name\":\"$h\"" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
else

@ -1,4 +1,15 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_conoha_info='ConoHa.jp
Domains: ConoHa.io
Site: ConoHa.jp
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_conoha
Options:
CONOHA_Username Username
CONOHA_Password Password
CONOHA_TenantId TenantId
CONOHA_IdentityServiceApi Identity Service API. E.g. "https://identity.xxxx.conoha.io/v2.0"
'
CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\."
@ -226,7 +237,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100).
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100).
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -240,7 +251,7 @@ _get_root() {
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,10 +1,16 @@
#!/usr/bin/env sh
# Author: Wout Decre <wout@canodus.be>
# shellcheck disable=SC2034
dns_constellix_info='Constellix.com
Site: Constellix.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_constellix
Options:
CONSTELLIX_Key API Key
CONSTELLIX_Secret API Secret
Issues: github.com/acmesh-official/acme.sh/issues/2724
Author: Wout Decre <wout@canodus.be>
'
CONSTELLIX_Api="https://api.dns.constellix.com/v1"
#CONSTELLIX_Key="XXX"
#CONSTELLIX_Secret="XXX"
######## Public functions #####################
@ -116,7 +122,7 @@ _get_root() {
p=1
_debug "Detecting root zone"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
return 1
fi
@ -128,7 +134,7 @@ _get_root() {
if _contains "$response" "\"name\":\"$h\""; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]*" | cut -d ':' -f 2)
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-"$p")
_domain="$h"
_debug _domain_id "$_domain_id"

@ -1,18 +1,18 @@
#!/usr/bin/env sh
#
#Author: Bjarne Saltbaek
#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/3732
#
#
# shellcheck disable=SC2034
dns_cpanel_info='cPanel Server API
Manage DNS via cPanel Dashboard.
Site: cPanel.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_cpanel
Options:
cPanel_Username Username
cPanel_Apitoken API Token
cPanel_Hostname Server URL. E.g. "https://hostname:port"
Issues: github.com/acmesh-official/acme.sh/issues/3732
Author: Bjarne Saltbaek
'
######## Public functions #####################
#
# Export CPANEL username,api token and hostname in the following variables
#
# cPanel_Username=username
# cPanel_Apitoken=apitoken
# cPanel_Hostname=hostname
#
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_cpanel_add() {

@ -1,9 +1,15 @@
#!/usr/bin/env sh
#Script to use with curanet.dk, scannet.dk, wannafind.dk, dandomain.dk DNS management.
#Requires api credentials with scope: dns
#Author: Peter L. Hansen <peter@r12.dk>
#Version 1.0
# shellcheck disable=SC2034
dns_curanet_info='Curanet.dk
Domains: scannet.dk wannafind.dk dandomain.dk
Site: Curanet.dk
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_curanet
Options:
CURANET_AUTHCLIENTID Auth ClientID. Requires scope dns
CURANET_AUTHSECRET Auth Secret
Issues: github.com/acmesh-official/acme.sh/issues/3933
Author: Peter L. Hansen <peter@r12.dk>
'
CURANET_REST_URL="https://api.curanet.dk/dns/v1/Domains"
CURANET_AUTH_URL="https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token"
@ -136,7 +142,7 @@ _get_root() {
i=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid

@ -1,21 +1,15 @@
#!/usr/bin/env sh
########
# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/acmesh-official/acme.sh)
#
# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com
#
# Dependencies:
# -------------
# - oathtool (When using 2 Factor Authentication)
#
# Issues:
# -------
# Any issues / questions / suggestions can be posted here:
# https://github.com/noplanman/cyon-api/issues
#
# Author: Armando Lüscher <armando@noplanman.ch>
########
# shellcheck disable=SC2034
dns_cyon_info='cyon.ch
Site: cyon.ch
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cyon
Options:
CY_Username Username
CY_Password API Token
CY_OTP_Secret OTP token. Only required if using 2FA
Issues: github.com/noplanman/cyon-api/issues
Author: Armando Lüscher <armando@noplanman.ch>
'
dns_cyon_add() {
_cyon_load_credentials &&
@ -221,10 +215,8 @@ _cyon_change_domain_env() {
if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi
domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)"
# Bail if domain environment change fails.
if [ "${domain_env_success}" != "true" ]; then
if [ "$(printf "%s" "${domain_env_response}" | _cyon_get_environment_change_status)" != "true" ]; then
_err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)"
_err ""
return 1
@ -238,7 +230,7 @@ _cyon_add_txt() {
_info " - Adding DNS TXT entry..."
add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async"
add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}"
add_txt_data="name=${fulldomain_idn}.&ttl=900&type=TXT&dnscontent=${txtvalue}"
add_txt_response="$(_post "$add_txt_data" "$add_txt_url")"
_debug add_txt_response "${add_txt_response}"
@ -247,9 +239,10 @@ _cyon_add_txt() {
add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)"
add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)"
add_txt_validation="$(printf "%s" "${add_txt_response}" | _cyon_get_validation_status)"
# Bail if adding TXT entry fails.
if [ "${add_txt_status}" != "true" ]; then
if [ "${add_txt_status}" != "true" ] || [ "${add_txt_validation}" != "true" ]; then
_err " ${add_txt_message}"
_err ""
return 1
@ -311,13 +304,21 @@ _cyon_get_response_message() {
}
_cyon_get_response_status() {
_egrep_o '"status":\w*' | cut -d : -f 2
_egrep_o '"status":[a-zA-z0-9]*' | cut -d : -f 2
}
_cyon_get_validation_status() {
_egrep_o '"valid":[a-zA-z0-9]*' | cut -d : -f 2
}
_cyon_get_response_success() {
_egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"'
}
_cyon_get_environment_change_status() {
_egrep_o '"authenticated":[a-zA-z0-9]*' | cut -d : -f 2
}
_cyon_check_if_2fa_missed() {
# Did we miss the 2FA?
if test "${1#*multi_factor_form}" != "${1}"; then

@ -1,31 +1,14 @@
#!/usr/bin/env sh
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
# vim: et ts=2 sw=2
#
# DirectAdmin 1.41.0 API
# The DirectAdmin interface has it's own Let's encrypt functionality, but this
# script can be used to generate certificates for names which are not hosted on
# DirectAdmin
#
# User must provide login data and URL to DirectAdmin incl. port.
# You can create login key, by using the Login Keys function
# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to
# - CMD_API_DNS_CONTROL
# - CMD_API_SHOW_DOMAINS
#
# See also https://www.directadmin.com/api.php and
# https://www.directadmin.com/features.php?id=1298
#
# Report bugs to https://github.com/TigerP/acme.sh/issues
#
# Values to export:
# export DA_Api="https://remoteUser:remotePassword@da.example.com:8443"
# export DA_Api_Insecure=1
#
# Set DA_Api_Insecure to 1 for insecure and 0 for secure -> difference is
# whether ssl cert is checked for validity (0) or whether it is just accepted
# (1)
#
# shellcheck disable=SC2034
dns_da_info='DirectAdmin Server API
Site: DirectAdmin.com/api.php
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da
Options:
DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443"
DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept
Issues: github.com/TigerP/acme.sh/issues
'
######## Public functions #####################
# Usage: dns_myapi_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@ -78,7 +61,7 @@ _get_root() {
# response will contain "list[]=example.com&list[]=example.org"
_da_api CMD_API_SHOW_DOMAINS "" "${domain}"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
# not valid
@ -86,7 +69,7 @@ _get_root() {
return 1
fi
if _contains "$response" "$h" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,16 +1,13 @@
#!/usr/bin/env sh
#Created by RaidenII, to use DuckDNS's API to add/remove text records
#modified by helbgd @ 03/13/2018 to support ddnss.de
#modified by mod242 @ 04/24/2018 to support different ddnss domains
#Please note: the Wildcard Feature must be turned on for the Host record
#and the checkbox for TXT needs to be enabled
# Pass credentials before "acme.sh --issue --dns dns_ddnss ..."
# --
# export DDNSS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
# --
#
# shellcheck disable=SC2034
dns_ddnss_info='DDNSS.de
Site: DDNSS.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss
Options:
DDNSS_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2230
Author: RaidenII, helbgd, mod242
'
DDNSS_DNS_API="https://ddnss.de/upd.php"

@ -1,11 +1,13 @@
#!/usr/bin/env sh
#
# deSEC.io Domain API
#
# Author: Zheng Qian
#
# deSEC API doc
# https://desec.readthedocs.io/en/latest/
# shellcheck disable=SC2034
dns_desec_info='deSEC.io
Site: desec.readthedocs.io/en/latest/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec
Options:
DDNSS_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2180
Author: Zheng Qian
'
REST_API="https://desec.io/api/v1/domains"
@ -174,7 +176,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -186,7 +188,7 @@ _get_root() {
fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,18 +1,15 @@
#!/usr/bin/env sh
########################################################################
# https://dyndnsfree.de hook script for acme.sh
#
# Environment variables:
#
# - $DF_user (your dyndnsfree.de username)
# - $DF_password (your dyndnsfree.de password)
#
# Author: Thilo Gass <thilo.gass@gmail.com>
# Git repo: https://github.com/ThiloGa/acme.sh
#-- dns_df_add() - Add TXT record --------------------------------------
# Usage: dns_df_add _acme-challenge.subdomain.domain.com "XyZ123..."
# shellcheck disable=SC2034
dns_df_info='DynDnsFree.de
Domains: dynup.de
Site: DynDnsFree.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_df
Options:
DF_user Username
DF_password Password
Issues: github.com/acmesh-official/acme.sh/issues/2897
Author: Thilo Gass <thilo.gass@gmail.com>
'
dyndnsfree_api="https://dynup.de/acme.php"

@ -1,16 +1,12 @@
#!/usr/bin/env sh
## Will be called by acme.sh to add the txt record to your api system.
## returns 0 means success, otherwise error.
## Author: thewer <github at thewer.com>
## GitHub: https://github.com/gitwer/acme.sh
##
## Environment Variables Required:
##
## DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc"
##
# shellcheck disable=SC2034
dns_dgon_info='DigitalOcean.com
Site: DigitalOcean.com/help/api/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dgon
Options:
DO_API_KEY API Key
Author: <github@thewer.com>
'
##################### Public functions #####################
@ -207,7 +203,7 @@ _get_base_domain() {
_debug2 domain_list "$domain_list"
i=1
while [ $i -gt 0 ]; do
while [ "$i" -gt 0 ]; do
## get next longest domain
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
## check we got something back from our cut (or are we at the end)
@ -219,14 +215,14 @@ _get_base_domain() {
## check if it exists
if [ -n "$found" ]; then
## exists - exit loop returning the parts
sub_point=$(_math $i - 1)
sub_point=$(_math "$i" - 1)
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
_debug _domain "$_domain"
_debug _sub_domain "$_sub_domain"
return 0
fi
## increment cut point $i
i=$(_math $i + 1)
i=$(_math "$i" + 1)
done
if [ -z "$found" ]; then

@ -1,13 +1,16 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_dnsexit_info='DNSExit.com
Site: DNSExit.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit
Options:
DNSEXIT_API_KEY API Key
DNSEXIT_AUTH_USER Username
DNSEXIT_AUTH_PASS Password
Issues: github.com/acmesh-official/acme.sh/issues/4719
Author: Samuel Jimenez
'
#use dns-01 at DNSExit.com
#Author: Samuel Jimenez
#Report Bugs here: https://github.com/acmesh-official/acme.sh
#DNSEXIT_API_KEY=ABCDEFGHIJ0123456789abcdefghij
#DNSEXIT_AUTH_USER=login@email.address
#DNSEXIT_AUTH_PASS=aStrongPassword
DNSEXIT_API_URL="https://api.dnsexit.com/dns/"
DNSEXIT_HOSTS_URL="https://update.dnsexit.com/ipupdate/hosts.jsp"
@ -81,7 +84,7 @@ _get_root() {
domain=$1
i=1
while true; do
_domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
_domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$_domain"
if [ -z "$_domain" ]; then
return 1

@ -1,15 +1,14 @@
#!/usr/bin/env sh
# dnsHome.de API for acme.sh
#
# This Script adds the necessary TXT record to a Subdomain
#
# Author dnsHome.de (https://github.com/dnsHome-de)
#
# Report Bugs to https://github.com/acmesh-official/acme.sh/issues/3819
#
# export DNSHOME_Subdomain=""
# export DNSHOME_SubdomainPassword=""
# shellcheck disable=SC2034
dns_dnshome_info='dnsHome.de
Site: dnsHome.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnshome
Options:
DNSHOME_Subdomain Subdomain
DNSHOME_SubdomainPassword Subdomain Password
Issues: github.com/acmesh-official/acme.sh/issues/3819
Author: dnsHome.de https://github.com/dnsHome-de
'
# Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record

@ -1,12 +1,12 @@
#!/usr/bin/env sh
# DNSimple domain api
# https://github.com/pho3nixf1re/acme.sh/issues
#
# This is your oauth token which can be acquired on the account page. Please
# note that this must be an _account_ token and not a _user_ token.
# https://dnsimple.com/a/<your account id>/account/access_tokens
# DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje"
# shellcheck disable=SC2034
dns_dnsimple_info='DNSimple.com
Site: DNSimple.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple
Options:
DNSimple_OAUTH_TOKEN OAuth Token
Issues: github.com/pho3nixf1re/acme.sh/issues
'
DNSimple_API="https://api.dnsimple.com/v2"
@ -92,7 +92,7 @@ _get_root() {
i=2
previous=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
# not valid
return 1
@ -105,7 +105,7 @@ _get_root() {
if _contains "$response" 'not found'; then
_debug "$h not found"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$previous)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$previous")
_domain="$h"
_debug _domain "$_domain"

@ -1,12 +1,15 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_dnsservices_info='DNS.Services
Site: DNS.Services
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsservices
Options:
DnsServices_Username Username
DnsServices_Password Password
Issues: github.com/acmesh-official/acme.sh/issues/4152
Author: Bjarke Bruun <bbruun@gmail.com>
'
#This file name is "dns_dnsservices.sh"
#Script for Danish DNS registra and DNS hosting provider https://dns.services
#Author: Bjarke Bruun <bbruun@gmail.com>
#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/4152
# Global variable to connect to the DNS.Services API
DNSServices_API=https://dns.services/api
######## Public functions #####################

@ -1,14 +1,16 @@
#!/usr/bin/env sh
# Official Let's Encrypt API for do.de / Domain-Offensive
#
# This is different from the dns_do adapter, because dns_do is only usable for enterprise customers
# This API is also available to private customers/individuals
#
# Provide the required LetsEncrypt token like this:
# DO_LETOKEN="FmD408PdqT1E269gUK57"
DO_API="https://www.do.de/api/letsencrypt"
# shellcheck disable=SC2034
dns_doapi_info='Domain-Offensive do.de
Official LetsEncrypt API for do.de / Domain-Offensive.
This API is also available to private customers/individuals.
Site: do.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_doapi
Options:
DO_LETOKEN LetsEncrypt Token
Issues: github.com/acmesh-official/acme.sh/issues/2057
'
DO_API="https://my.do.de/api/letsencrypt"
######## Public functions #####################

@ -1,4 +1,13 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_domeneshop_info='DomeneShop.no
Site: DomeneShop.no
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_domeneshop
Options:
DOMENESHOP_Token Token
DOMENESHOP_Secret Secret
Issues: github.com/acmesh-official/acme.sh/issues/2457
'
DOMENESHOP_Api_Endpoint="https://api.domeneshop.no/v0"
@ -84,7 +93,7 @@ _get_domainid() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug "h" "$h"
if [ -z "$h" ]; then
#not valid
@ -93,7 +102,7 @@ _get_domainid() {
if _contains "$response" "\"$h\"" >/dev/null; then
# We have found the domain name.
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
_domainid=$(printf "%s" "$response" | _egrep_o "[^{]*\"domain\":\"$_domain\"[^}]*" | _egrep_o "\"id\":[0-9]+" | cut -d : -f 2)
return 0

@ -1,10 +1,12 @@
#!/usr/bin/env sh
# Dnspod.cn Domain api
#
#DP_Id="1234"
#
#DP_Key="sADDsdasdgdsf"
# shellcheck disable=SC2034
dns_dp_info='DNSPod.cn
Site: DNSPod.cn
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dp
Options:
DP_Id Id
DP_Key Key
'
REST_API="https://dnsapi.cn"
@ -107,7 +109,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
@ -121,7 +123,7 @@ _get_root() {
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_debug _sub_domain "$_sub_domain"
_domain="$h"
_debug _domain "$_domain"

@ -1,10 +1,12 @@
#!/usr/bin/env sh
# Dnspod.com Domain api
#
#DPI_Id="1234"
#
#DPI_Key="sADDsdasdgdsf"
# shellcheck disable=SC2034
dns_dpi_info='DNSPod.com
Site: DNSPod.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dpi
Options:
DPI_Id Id
DPI_Key Key
'
REST_API="https://api.dnspod.com"
@ -107,7 +109,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
@ -121,7 +123,7 @@ _get_root() {
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_debug _sub_domain "$_sub_domain"
_domain="$h"
_debug _domain "$_domain"

@ -1,10 +1,14 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_dreamhost_info='DreamHost.com
Site: DreamHost.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dreamhost
Options:
DH_API_KEY API Key
Issues: github.com/RhinoLance/acme.sh
Author: RhinoLance
'
#Author: RhinoLance
#Report Bugs here: https://github.com/RhinoLance/acme.sh
#
#define the api endpoint
DH_API_ENDPOINT="https://api.dreamhost.com/"
querystring=""

@ -1,14 +1,12 @@
#!/usr/bin/env sh
#Created by RaidenII, to use DuckDNS's API to add/remove text records
#06/27/2017
# Pass credentials before "acme.sh --issue --dns dns_duckdns ..."
# --
# export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
# --
#
# Due to the fact that DuckDNS uses StartSSL as cert provider, --insecure may need to be used with acme.sh
# shellcheck disable=SC2034
dns_duckdns_info='DuckDNS.org
Site: www.DuckDNS.org
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns
Options:
DuckDNS_Token API Token
Author: RaidenII
'
DuckDNS_API="https://www.duckdns.org/update"

@ -1,7 +1,13 @@
#!/usr/bin/env sh
#DD_API_User="xxxxx"
#DD_API_Key="xxxxxx"
# shellcheck disable=SC2034
dns_durabledns_info='DurableDNS.com
Site: DurableDNS.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_durabledns
Options:
DD_API_User API User
DD_API_Key API Key
Issues: github.com/acmesh-official/acme.sh/issues/2281
'
_DD_BASE="https://durabledns.com/services/dns"
@ -104,7 +110,7 @@ _get_root() {
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -112,7 +118,7 @@ _get_root() {
fi
if _contains "$response" ">$h.</origin>"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,10 +1,16 @@
#!/usr/bin/env sh
#
# Dyn.com Domain API
#
# Author: Gerd Naschenweng
# https://github.com/magicdude4eva
#
# shellcheck disable=SC2034
dns_dyn_info='Dyn.com
Domains: dynect.net
Site: Dyn.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dyn
Options:
DYN_Customer Customer
DYN_Username API Username
DYN_Password Secret
Author: Gerd Naschenweng <https://github.com/magicdude4eva>
'
# Dyn Managed DNS API
# https://help.dyn.com/dns-api-knowledge-base/
#
@ -20,13 +26,6 @@
# ZoneRemoveNode
# ZonePublish
# --
#
# Pass credentials before "acme.sh --issue --dns dns_dyn ..."
# --
# export DYN_Customer="customer"
# export DYN_Username="apiuser"
# export DYN_Password="secret"
# --
DYN_API="https://api.dynect.net/REST"

@ -1,20 +1,21 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_dynu_info='Dynu.com
Site: Dynu.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dynu
Options:
Dynu_ClientId Client ID
Dynu_Secret Secret
Issues: github.com/shar0119/acme.sh
Author: Dynu Systems Inc
'
#Client ID
#Dynu_ClientId="0b71cae7-a099-4f6b-8ddf-94571cdb760d"
#
#Secret
#Dynu_Secret="aCUEY4BDCV45KI8CSIC3sp2LKQ9"
#
#Token
Dynu_Token=""
#
#Endpoint
Dynu_EndPoint="https://api.dynu.com/v2"
#
#Author: Dynu Systems, Inc.
#Report Bugs here: https://github.com/shar0119/acme.sh
#
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@ -125,7 +126,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -139,7 +140,7 @@ _get_root() {
if _contains "$response" "\"domainName\":\"$h\"" >/dev/null; then
dnsId=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 2 | cut -d : -f 2)
_domain_name=$h
_node=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_node=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
return 0
fi
p=$i

@ -1,16 +1,23 @@
#!/usr/bin/env sh
#Author StefanAbl
#Usage specify a private keyfile to use with dynv6 'export KEY="path/to/keyfile"'
#or use the HTTP REST API by by specifying a token 'export DYNV6_TOKEN="value"
#if no keyfile is specified, you will be asked if you want to create one in /home/$USER/.ssh/dynv6 and /home/$USER/.ssh/dynv6.pub
# shellcheck disable=SC2034
dns_dynv6_info='DynV6.com
Site: DynV6.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dynv6
Options:
DYNV6_TOKEN REST API token. Get from https://DynV6.com/keys
OptionsAlt:
KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6"
Issues: github.com/acmesh-official/acme.sh/issues/2702
Author: StefanAbl
'
dynv6_api="https://dynv6.com/api/v2"
######## Public functions #####################
# Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
#Usage: dns_dynv6_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dynv6_add() {
fulldomain=$1
txtvalue=$2
fulldomain="$(echo "$1" | _lower_case)"
txtvalue="$2"
_info "Using dynv6 api"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
@ -36,15 +43,14 @@ dns_dynv6_add() {
_err "Something went wrong! it does not seem like the record was added successfully"
return 1
fi
return 1
fi
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_dynv6_rm() {
fulldomain=$1
txtvalue=$2
fulldomain="$(echo "$1" | _lower_case)"
txtvalue="$2"
_info "Using dynv6 API"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
@ -199,7 +205,7 @@ _get_zone_id() {
return 1
fi
zone_id="$(echo "$response" | tr '}' '\n' | grep "$selected" | tr ',' '\n' | grep id | tr -d '"')"
zone_id="$(echo "$response" | tr '}' '\n' | grep "$selected" | tr ',' '\n' | grep '"id":' | tr -d '"')"
_zone_id="${zone_id#id:}"
_debug "zone id: $_zone_id"
}

@ -1,14 +1,17 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_easydns_info='easyDNS.net
Site: easyDNS.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_easydns
Options:
EASYDNS_Token API Token
EASYDNS_Key API Key
Issues: github.com/acmesh-official/acme.sh/issues/2647
Author: Neilpang, wurzelpanzer <wurzelpanzer@maximolider.net>
'
#######################################################
#
# easyDNS REST API for acme.sh by Neilpang based on dns_cf.sh
#
# API Documentation: https://sandbox.rest.easydns.net:3001/
#
# Author: wurzelpanzer [wurzelpanzer@maximolider.net]
# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2647
#
#################### Public functions #################
#EASYDNS_Key="xxxxxxxxxxxxxxxxxxxxxxxx"
@ -118,7 +121,7 @@ _get_root() {
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -130,7 +133,7 @@ _get_root() {
fi
if _contains "$response" "\"status\":200"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,4 +1,15 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_edgedns_info='Akamai.com Edge DNS
Site: techdocs.Akamai.com/edge-dns/reference/edge-dns-api
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgedns
Options: Specify individual credentials
AKAMAI_HOST Host
AKAMAI_ACCESS_TOKEN Access token
AKAMAI_CLIENT_TOKEN Client token
AKAMAI_CLIENT_SECRET Client secret
Issues: github.com/acmesh-official/acme.sh/issues/3157
'
# Akamai Edge DNS v2 API
# User must provide Open Edgegrid API credentials to the EdgeDNS installation. The remote user in EdgeDNS must have CRUD access to
@ -6,18 +17,10 @@
# Report bugs to https://control.akamai.com/apps/support-ui/#/contact-support
# Values to export:
# --EITHER--
# *** TBD. NOT IMPLEMENTED YET ***
# specify Edgegrid credentials file and section
# AKAMAI_EDGERC=<full file path>
# AKAMAI_EDGERC_SECTION="default"
## --OR--
# specify indiviual credentials
# export AKAMAI_HOST = <host>
# export AKAMAI_ACCESS_TOKEN = <access token>
# export AKAMAI_CLIENT_TOKEN = <client token>
# export AKAMAI_CLIENT_SECRET = <client secret>
# Specify Edgegrid credentials file and section.
# AKAMAI_EDGERC Edge RC. Full file path
# AKAMAI_EDGERC_SECTION Edge RC Section. E.g. "default"
ACME_EDGEDNS_VERSION="0.1.0"

@ -1,18 +1,14 @@
#!/usr/bin/env sh
#This is the euserv.eu api wrapper for acme.sh
#
#Author: Michael Brueckner
#Report Bugs: https://www.github.com/initit/acme.sh or mbr@initit.de
#
#EUSERV_Username="username"
#
#EUSERV_Password="password"
#
# Dependencies:
# -------------
# - none -
# shellcheck disable=SC2034
dns_euserv_info='EUserv.com
Domains: EUserv.eu
Site: EUserv.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_euserv
Options:
EUSERV_Username Username
EUSERV_Password Password
Author: Michael Brueckner
'
EUSERV_Api="https://api.euserv.net"
@ -155,7 +151,7 @@ _get_root() {
response="$_euserv_domain_orders"
while true; do
h=$(echo "$domain" | cut -d . -f $i-100)
h=$(echo "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -163,7 +159,7 @@ _get_root() {
fi
if _contains "$response" "$h"; then
_sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
_sub_domain=$(echo "$domain" | cut -d . -f 1-"$p")
_domain="$h"
if ! _euserv_get_domain_id "$_domain"; then
_err "invalid domain"

@ -1,4 +1,12 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_exoscale_info='Exoscale.com
Site: Exoscale.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_exoscale
Options:
EXOSCALE_API_KEY API Key
EXOSCALE_SECRET_KEY API Secret key
'
EXOSCALE_API=https://api.exoscale.com/dns/v1
@ -111,7 +119,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -122,7 +130,7 @@ _get_root() {
_domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
_domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_token" ] && [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,8 +1,15 @@
#!/usr/bin/env sh
#Author: Timur Umarov <inbox@tumarov.com>
FORNEX_API_URL="https://fornex.com/api/dns/v0.1"
# shellcheck disable=SC2034
dns_fornex_info='Fornex.com
Site: Fornex.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_fornex
Options:
FORNEX_API_KEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/3998
Author: Timur Umarov <inbox@tumarov.com>
'
FORNEX_API_URL="https://fornex.com/api"
######## Public functions #####################
@ -23,12 +30,10 @@ dns_fornex_add() {
fi
_info "Adding record"
if _rest POST "$_domain/entry_set/add/" "host=$fulldomain&type=TXT&value=$txtvalue&apikey=$FORNEX_API_KEY"; then
if _rest POST "dns/domain/$_domain/entry_set/" "{\"host\" : \"${fulldomain}\" , \"type\" : \"TXT\" , \"value\" : \"${txtvalue}\" , \"ttl\" : null}"; then
_debug _response "$response"
if _contains "$response" '"ok": true' || _contains "$response" 'Такая запись уже существует.'; then
_info "Added, OK"
return 0
fi
_info "Added, OK"
return 0
fi
_err "Add txt record error."
return 1
@ -51,21 +56,21 @@ dns_fornex_rm() {
fi
_debug "Getting txt records"
_rest GET "$_domain/entry_set.json?apikey=$FORNEX_API_KEY"
_rest GET "dns/domain/$_domain/entry_set?type=TXT&q=$fulldomain"
if ! _contains "$response" "$txtvalue"; then
_err "Txt record not found"
return 1
fi
_record_id="$(echo "$response" | _egrep_o "{[^{]*\"value\"*:*\"$txtvalue\"[^}]*}" | sed -n -e 's#.*"id": \([0-9]*\).*#\1#p')"
_record_id="$(echo "$response" | _egrep_o "\{[^\{]*\"value\"*:*\"$txtvalue\"[^\}]*\}" | sed -n -e 's#.*"id":\([0-9]*\).*#\1#p')"
_debug "_record_id" "$_record_id"
if [ -z "$_record_id" ]; then
_err "can not find _record_id"
return 1
fi
if ! _rest POST "$_domain/entry_set/$_record_id/delete/" "apikey=$FORNEX_API_KEY"; then
if ! _rest DELETE "dns/domain/$_domain/entry_set/$_record_id/"; then
_err "Delete record error."
return 1
fi
@ -83,18 +88,18 @@ _get_root() {
i=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if ! _rest GET "domain_list.json?q=$h&apikey=$FORNEX_API_KEY"; then
if ! _rest GET "dns/domain/"; then
return 1
fi
if _contains "$response" "\"$h\"" >/dev/null; then
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain=$h
return 0
else
@ -127,7 +132,9 @@ _rest() {
data="$3"
_debug "$ep"
export _H1="Accept: application/json"
export _H1="Authorization: Api-Key $FORNEX_API_KEY"
export _H2="Content-Type: application/json"
export _H3="Accept: application/json"
if [ "$m" != "GET" ]; then
_debug data "$data"

@ -1,14 +1,15 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_freedns_info='FreeDNS
Site: FreeDNS.afraid.org
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_freedns
Options:
FREEDNS_User Username
FREEDNS_Password Password
Issues: github.com/acmesh-official/acme.sh/issues/2305
Author: David Kerr <https://github.com/dkerr64>
'
#This file name is "dns_freedns.sh"
#So, here must be a method dns_freedns_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
#
#Author: David Kerr
#Report Bugs here: https://github.com/dkerr64/acme.sh
#or here... https://github.com/acmesh-official/acme.sh/issues/2305
#
######## Public functions #####################
# Export FreeDNS userid and password in following variables...

@ -1,16 +1,19 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_gandi_livedns_info='Gandi.net LiveDNS
Site: Gandi.net/domain/dns
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gandi_livedns
Options:
GANDI_LIVEDNS_KEY API Key
Issues: github.com/fcrozat/acme.sh
Author: Frédéric Crozat <fcrozat@suse.com>, Dominik Röttsches <drott@google.com>
'
# Gandi LiveDNS v5 API
# https://api.gandi.net/docs/livedns/
# https://api.gandi.net/docs/authentication/ for token + apikey (deprecated) authentication
# currently under beta
#
# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable
#
#Author: Frédéric Crozat <fcrozat@suse.com>
# Dominik Röttsches <drott@google.com>
#Report Bugs here: https://github.com/fcrozat/acme.sh
#
######## Public functions #####################
GANDI_LIVEDNS_API="https://api.gandi.net/v5/livedns"
@ -92,7 +95,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -109,7 +112,7 @@ _get_root() {
elif _contains "$response" '"code": 404'; then
_debug "$h not found"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
fi

@ -1,6 +1,12 @@
#!/usr/bin/env sh
# Author: Janos Lenart <janos@lenart.io>
# shellcheck disable=SC2034
dns_gcloud_info='Google Cloud DNS
Site: Cloud.Google.com/dns
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcloud
Options:
CLOUDSDK_ACTIVE_CONFIG_NAME Active config name. E.g. "default"
Author: Janos Lenart <janos@lenart.io>
'
######## Public functions #####################

@ -1,8 +1,12 @@
#!/usr/bin/env sh
#
#GCORE_Key='773$7b7adaf2a2b32bfb1b83787b4ff32a67eb178e3ada1af733e47b1411f2461f7f4fa7ed7138e2772a46124377bad7384b3bb8d87748f87b3f23db4b8bbe41b2bb'
#
# shellcheck disable=SC2034
dns_gcore_info='Gcore.com
Site: Gcore.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcore
Options:
GCORE_Key API Key
Issues: github.com/acmesh-official/acme.sh/issues/4460
'
GCORE_Api="https://api.gcore.com/dns/v2"
GCORE_Doc="https://api.gcore.com/docs/dns"
@ -24,7 +28,7 @@ dns_gcore_add() {
fi
#save the api key to the account conf file.
_saveaccountconf_mutable GCORE_Key "$GCORE_Key"
_saveaccountconf_mutable GCORE_Key "$GCORE_Key" "base64"
_debug "First detect the zone name"
if ! _get_root "$fulldomain"; then
@ -134,7 +138,7 @@ _get_root() {
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -148,7 +152,7 @@ _get_root() {
if _contains "$response" "\"name\":\"$h\""; then
_zone_name=$h
if [ "$_zone_name" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,12 +1,12 @@
#!/usr/bin/env sh
#Godaddy domain api
# Get API key and secret from https://developer.godaddy.com/
#
# GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
# GD_Secret="asdfsdfsfsdfsdfdfsdf"
#
# Ex.: acme.sh --issue --staging --dns dns_gd -d "*.s.example.com" -d "s.example.com"
# shellcheck disable=SC2034
dns_gd_info='GoDaddy.com
Site: GoDaddy.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gd
Options:
GD_Key API Key
GD_Secret API Secret
'
GD_Api="https://api.godaddy.com/v1"
@ -148,7 +148,7 @@ _get_root() {
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
@ -161,7 +161,7 @@ _get_root() {
if _contains "$response" '"code":"NOT_FOUND"'; then
_debug "$h not found"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
fi

@ -1,12 +1,12 @@
#!/usr/bin/env sh
########################################################################
# Geoscaling hook script for acme.sh
#
# Environment variables:
#
# - $GEOSCALING_Username (your Geoscaling username - this is usually NOT an amail address)
# - $GEOSCALING_Password (your Geoscaling password)
# shellcheck disable=SC2034
dns_geoscaling_info='GeoScaling.com
Site: GeoScaling.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_geoscaling
Options:
GEOSCALING_Username Username. This is usually NOT an email address
GEOSCALING_Password Password
'
#-- dns_geoscaling_add() - Add TXT record --------------------------------------
# Usage: dns_geoscaling_add _acme-challenge.subdomain.domain.com "XyZ123..."
@ -202,7 +202,7 @@ find_zone() {
# Walk through all possible zone names
strip_counter=1
while true; do
attempted_zone=$(echo "${domain}" | cut -d . -f ${strip_counter}-)
attempted_zone=$(echo "${domain}" | cut -d . -f "${strip_counter}"-)
# All possible zone names have been tried
if [ -z "${attempted_zone}" ]; then

@ -1,10 +1,15 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_googledomains_info='Google Domains
Site: Domains.Google.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_googledomains
Options:
GOOGLEDOMAINS_ACCESS_TOKEN API Access Token
GOOGLEDOMAINS_ZONE Zone
Issues: github.com/acmesh-official/acme.sh/issues/4545
Author: Alex Leigh <leigh@alexleigh.me>
'
# Author: Alex Leigh <leigh at alexleigh dot me>
# Created: 2023-03-02
#GOOGLEDOMAINS_ACCESS_TOKEN="xxxx"
#GOOGLEDOMAINS_ZONE="xxxx"
GOOGLEDOMAINS_API="https://acmedns.googleapis.com/v1/acmeChallengeSets"
######## Public functions ########
@ -127,7 +132,7 @@ _dns_googledomains_get_zone() {
i=2
while true; do
curr=$(printf "%s" "$domain" | cut -d . -f $i-100)
curr=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug curr "$curr"
if [ -z "$curr" ]; then

@ -1,15 +1,14 @@
#!/usr/bin/env sh
########################################################################
# Hurricane Electric hook script for acme.sh
#
# Environment variables:
#
# - $HE_Username (your dns.he.net username)
# - $HE_Password (your dns.he.net password)
#
# Author: Ondrej Simek <me@ondrejsimek.com>
# Git repo: https://github.com/angel333/acme.sh
# shellcheck disable=SC2034
dns_he_info='Hurricane Electric HE.net
Site: dns.he.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_he
Options:
HE_Username Username
HE_Password Password
Issues: github.com/angel333/acme.sh/issues/
Author: Ondrej Simek <me@ondrejsimek.com>
'
#-- dns_he_add() - Add TXT record --------------------------------------
# Usage: dns_he_add _acme-challenge.subdomain.domain.com "XyZ123..."
@ -144,7 +143,7 @@ _find_zone() {
# Walk through all possible zone names
_strip_counter=1
while true; do
_attempted_zone=$(echo "$_domain" | cut -d . -f ${_strip_counter}-)
_attempted_zone=$(echo "$_domain" | cut -d . -f "${_strip_counter}"-)
# All possible zone names have been tried
if [ -z "$_attempted_zone" ]; then

@ -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).

@ -1,8 +1,12 @@
#!/usr/bin/env sh
#
#HETZNER_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
# shellcheck disable=SC2034
dns_hetzner_info='Hetzner.com
Site: Hetzner.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_hetzner
Options:
HETZNER_Token API Token
Issues: github.com/acmesh-official/acme.sh/issues/2943
'
HETZNER_Api="https://dns.hetzner.com/api/v1"
@ -177,7 +181,7 @@ _get_root() {
_debug "Trying to get zone id by domain name for '$domain_without_acme'."
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
@ -189,7 +193,7 @@ _get_root() {
if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_entries":1'; then
_domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
HETZNER_Zone_ID=$_domain_id
_savedomainconf "$domain_param_name" "$HETZNER_Zone_ID"

@ -1,9 +1,13 @@
#!/usr/bin/env sh
#
# Hexonet_Login="username!roleId"
#
# Hexonet_Password="rolePassword"
# shellcheck disable=SC2034
dns_hexonet_info='Hexonet.com
Site: Hexonet.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hexonet
Options:
Hexonet_Login Login. E.g. "username!roleId"
Hexonet_Password Role Password
Issues: github.com/acmesh-official/acme.sh/issues/2389
'
Hexonet_Api="https://coreapi.1api.net/api/call.cgi"
@ -119,7 +123,7 @@ _get_root() {
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -131,7 +135,7 @@ _get_root() {
fi
if _contains "$response" "CODE=200"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain=$h
return 0
fi

@ -1,10 +1,13 @@
#!/usr/bin/env sh
# hosting.de API
# Values to export:
# export HOSTINGDE_ENDPOINT='https://secure.hosting.de'
# export HOSTINGDE_APIKEY='xxxxx'
# shellcheck disable=SC2034
dns_hostingde_info='Hosting.de
Site: Hosting.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_hostingde
Options:
HOSTINGDE_ENDPOINT Endpoint. E.g. "https://secure.hosting.de"
HOSTINGDE_APIKEY API Key
Issues: github.com/acmesh-official/acme.sh/issues/2058
'
######## Public functions #####################

@ -1,8 +1,14 @@
#!/usr/bin/env sh
# HUAWEICLOUD_Username
# HUAWEICLOUD_Password
# HUAWEICLOUD_DomainName
# shellcheck disable=SC2034
dns_huaweicloud_info='HuaweiCloud.com
Site: HuaweiCloud.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_huaweicloud
Options:
HUAWEICLOUD_Username Username
HUAWEICLOUD_Password Password
HUAWEICLOUD_DomainName DomainName
Issues: github.com/acmesh-official/acme.sh/issues/3265
'
iam_api="https://iam.myhuaweicloud.com"
dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
@ -204,7 +210,7 @@ _get_recordset_id() {
_zoneid=$3
export _H1="X-Auth-Token: ${_token}"
response=$(_get "${dns_api}/v2/zones/${_zoneid}/recordsets?name=${_domain}")
response=$(_get "${dns_api}/v2/zones/${_zoneid}/recordsets?name=${_domain}&status=ACTIVE")
if _contains "${response}" '"id"'; then
_id="$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")"
printf "%s" "${_id}"
@ -221,7 +227,7 @@ _add_record() {
# Get Existing Records
export _H1="X-Auth-Token: ${_token}"
response=$(_get "${dns_api}/v2/zones/${zoneid}/recordsets?name=${_domain}")
response=$(_get "${dns_api}/v2/zones/${zoneid}/recordsets?name=${_domain}&status=ACTIVE")
_debug2 "${response}"
_exist_record=$(echo "${response}" | _egrep_o '"records":[^]]*' | sed 's/\"records\"\:\[//g')

@ -1,8 +1,14 @@
#!/usr/bin/env sh
## Infoblox API integration by Jason Keller and Elijah Tenai
##
## Report any bugs via https://github.com/jasonkeller/acme.sh
# shellcheck disable=SC2034
dns_infoblox_info='Infoblox.com
Site: Infoblox.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_infoblox
Options:
Infoblox_Creds Credentials. E.g. "username:password"
Infoblox_Server Server hostname. IP or FQDN of infoblox appliance
Issues: github.com/jasonkeller/acme.sh
Author: Jason Keller, Elijah Tenai
'
dns_infoblox_add() {

@ -1,19 +1,20 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_infomaniak_info='Infomaniak.com
Site: Infomaniak.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infomaniak
Options:
INFOMANIAK_API_TOKEN API Token
Issues: github.com/acmesh-official/acme.sh/issues/3188
'
###############################################################################
# Infomaniak API integration
#
# To use this API you need visit the API dashboard of your account
# once logged into https://manager.infomaniak.com add /api/dashboard to the URL
#
# Please report bugs to
# https://github.com/acmesh-official/acme.sh/issues/3188
#
# Note: the URL looks like this:
# https://manager.infomaniak.com/v3/<account_id>/api/dashboard
# Then generate a token with the scope Domain
# this is given as an environment variable INFOMANIAK_API_TOKEN
###############################################################################
# base variables

@ -1,12 +1,14 @@
#!/usr/bin/env sh
#This is the Internet.BS api wrapper for acme.sh
#
#Author: <alexey@nelexa.ru> Ne-Lexa
#Report Bugs here: https://github.com/Ne-Lexa/acme.sh
#INTERNETBS_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
#INTERNETBS_API_PASSWORD="sdfsdfsdfljlbjkljlkjsdfoiwje"
# shellcheck disable=SC2034
dns_internetbs_info='InternetBS.net
Site: InternetBS.net
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_internetbs
Options:
INTERNETBS_API_KEY API Key
INTERNETBS_API_PASSWORD API Password
Issues: github.com/acmesh-official/acme.sh/issues/2261
Author: Ne-Lexa <alexey@nelexa.ru>
'
INTERNETBS_API_URL="https://api.internet.bs"
@ -131,7 +133,7 @@ _get_root() {
fi
while true; do
h=$(printf "%s" "$domain" | cut -d . -f ${i}-100)
h=$(printf "%s" "$domain" | cut -d . -f "${i}"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -139,7 +141,7 @@ _get_root() {
fi
if _contains "$response" "\"$h\""; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-${p})
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"${p}")
_domain=${h}
return 0
fi

@ -1,10 +1,13 @@
#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_inwx_info='INWX.de
Site: INWX.de
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx
Options:
INWX_User Username
INWX_Password Password
'
#
#INWX_User="username"
#
#INWX_Password="password"
#
# Dependencies:
# -------------
# - oathtool (When using 2 Factor Authentication)
@ -160,6 +163,15 @@ _inwx_check_cookie() {
return 1
}
_htmlEscape() {
_s="$1"
_s=$(echo "$_s" | sed "s/&/&amp;/g")
_s=$(echo "$_s" | sed "s/</\&lt;/g")
_s=$(echo "$_s" | sed "s/>/\&gt;/g")
_s=$(echo "$_s" | sed 's/"/\&quot;/g')
printf -- %s "$_s"
}
_inwx_login() {
if _inwx_check_cookie; then
@ -167,6 +179,8 @@ _inwx_login() {
return 0
fi
XML_PASS=$(_htmlEscape "$INWX_Password")
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>account.login</methodName>
@ -190,7 +204,7 @@ _inwx_login() {
</value>
</param>
</params>
</methodCall>' "$INWX_User" "$INWX_Password")
</methodCall>' "$INWX_User" "$XML_PASS")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
@ -279,7 +293,7 @@ _get_root() {
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
@ -287,7 +301,7 @@ _get_root() {
fi
if _contains "$response" "$h"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_domain="$h"
return 0
fi

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save