From 9980ad0fef9634b105c59711dd5f470a4b35f080 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:33:19 +0100 Subject: [PATCH 01/73] add HostUp DNS --- dnsapi/dns_hostup.sh | 473 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 dnsapi/dns_hostup.sh diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh new file mode 100644 index 00000000..4da0dd9d --- /dev/null +++ b/dnsapi/dns_hostup.sh @@ -0,0 +1,473 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034,SC2154 + +dns_hostup_info='HostUp DNS +Site: hostup.se +Docs: https://hostup.se/en/support/api-autentisering/ +Options: + HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes. + HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api). + HOSTUP_TTL Optional. TTL for TXT records (default: 60 seconds). + HOSTUP_ZONE_ID Optional. Force a specific zone ID (skip auto-detection). +Author: HostUp (https://cloud.hostup.se/contact/en) +' + +HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api" +HOSTUP_DEFAULT_TTL=60 + +# Public: add TXT record +# Usage: dns_hostup_add _acme-challenge.example.com "txt-value" +dns_hostup_add() { + fulldomain="$1" + txtvalue="$2" + + _info "Using HostUp DNS API" + + if ! _hostup_init; then + return 1 + fi + + if ! _hostup_detect_zone "$fulldomain"; then + _err "Unable to determine HostUp zone for $fulldomain" + return 1 + fi + + record_name="$(_hostup_record_name "$fulldomain" "$HOSTUP_ZONE_DOMAIN")" + record_name="$(_hostup_sanitize_name "$record_name")" + record_value="$(_hostup_json_escape "$txtvalue")" + + ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}" + + _debug "zone_id" "$HOSTUP_ZONE_ID" + _debug "zone_domain" "$HOSTUP_ZONE_DOMAIN" + _debug "record_name" "$record_name" + _debug "ttl" "$ttl" + + request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$record_value\",\"ttl\":$ttl}" + + if ! _hostup_rest "POST" "/dns/zones/$HOSTUP_ZONE_ID/records" "$request_body"; then + return 1 + fi + + if ! _contains "$_hostup_response" '"success":true'; then + _err "HostUp DNS API: failed to create TXT record for $fulldomain" + _debug2 "_hostup_response" "$_hostup_response" + return 1 + fi + + record_id="$(_hostup_extract_record_id "$_hostup_response")" + if [ -n "$record_id" ]; then + _hostup_save_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_id" + _debug "hostup_saved_record_id" "$record_id" + fi + + _info "Added TXT record for $fulldomain" + return 0 +} + +# Public: remove TXT record +# Usage: dns_hostup_rm _acme-challenge.example.com "txt-value" +dns_hostup_rm() { + fulldomain="$1" + txtvalue="$2" + + _info "Using HostUp DNS API" + + if ! _hostup_init; then + return 1 + fi + + if ! _hostup_detect_zone "$fulldomain"; then + _err "Unable to determine HostUp zone for $fulldomain" + return 1 + fi + + record_name_fqdn="$(_hostup_fqdn "$fulldomain")" + record_value="$txtvalue" + + record_id_cached="$(_hostup_get_saved_record_id "$HOSTUP_ZONE_ID" "$fulldomain")" + if [ -n "$record_id_cached" ]; then + _debug "hostup_record_id_cached" "$record_id_cached" + if _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$record_id_cached"; then + _info "Deleted TXT record $record_id_cached" + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + HOSTUP_ZONE_ID="" + return 0 + fi + fi + + if ! _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$record_value"; then + _info "TXT record not found for $record_name_fqdn. Skipping removal." + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + return 0 + fi + + _debug "Deleting record" "$HOSTUP_RECORD_ID" + + if ! _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$HOSTUP_RECORD_ID"; then + return 1 + fi + + _info "Deleted TXT record $HOSTUP_RECORD_ID" + _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" + HOSTUP_ZONE_ID="" + return 0 +} + +########################## +# Private helper methods # +########################## + +_hostup_init() { + HOSTUP_API_KEY="${HOSTUP_API_KEY:-$(_readaccountconf_mutable HOSTUP_API_KEY)}" + HOSTUP_API_BASE="${HOSTUP_API_BASE:-$(_readaccountconf_mutable HOSTUP_API_BASE)}" + HOSTUP_TTL="${HOSTUP_TTL:-$(_readaccountconf_mutable HOSTUP_TTL)}" + HOSTUP_ZONE_ID="${HOSTUP_ZONE_ID:-$(_readaccountconf_mutable HOSTUP_ZONE_ID)}" + + if [ -z "$HOSTUP_API_BASE" ]; then + HOSTUP_API_BASE="$HOSTUP_API_BASE_DEFAULT" + fi + + if [ -z "$HOSTUP_API_KEY" ]; then + HOSTUP_API_KEY="" + _err "HOSTUP_API_KEY is not set." + _err "Please export your HostUp API key with read:dns and write:dns scopes." + return 1 + fi + + _saveaccountconf_mutable HOSTUP_API_KEY "$HOSTUP_API_KEY" + _saveaccountconf_mutable HOSTUP_API_BASE "$HOSTUP_API_BASE" + + if [ -n "$HOSTUP_TTL" ]; then + _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL" + fi + + if [ -n "$HOSTUP_ZONE_ID" ]; then + _saveaccountconf_mutable HOSTUP_ZONE_ID "$HOSTUP_ZONE_ID" + fi + + return 0 +} + +_hostup_detect_zone() { + fulldomain="$1" + + if [ -n "$HOSTUP_ZONE_ID" ] && [ -n "$HOSTUP_ZONE_DOMAIN" ]; then + return 0 + fi + + HOSTUP_ZONE_DOMAIN="" + _debug "hostup_full_domain" "$fulldomain" + + if [ -n "$HOSTUP_ZONE_ID" ] && [ -z "$HOSTUP_ZONE_DOMAIN" ]; then + # Attempt to fetch domain name for provided zone ID + if _hostup_fetch_zone_details "$HOSTUP_ZONE_ID"; then + return 0 + fi + HOSTUP_ZONE_ID="" + fi + + if ! _hostup_load_zones; then + return 1 + fi + + _domain_candidate="$(printf "%s" "$fulldomain" | tr 'A-Z' 'a-z')" + _debug "hostup_initial_candidate" "$_domain_candidate" + + while [ -n "$_domain_candidate" ]; do + _debug "hostup_zone_candidate" "$_domain_candidate" + if _hostup_lookup_zone "$_domain_candidate"; then + HOSTUP_ZONE_DOMAIN="$_lookup_zone_domain" + HOSTUP_ZONE_ID="$_lookup_zone_id" + return 0 + fi + + if ! printf "%s" "$_domain_candidate" | _contains "."; then + break + fi + + _domain_candidate="${_domain_candidate#*.}" + done + + HOSTUP_ZONE_ID="" + return 1 +} + +_hostup_record_name() { + fulldomain="$1" + zonedomain="$2" + + # Remove trailing dot, if any + fulldomain="${fulldomain%.}" + zonedomain="${zonedomain%.}" + + if [ "$fulldomain" = "$zonedomain" ]; then + printf "%s" "@" + return 0 + fi + + suffix=".$zonedomain" + case "$fulldomain" in + *"$suffix") + printf "%s" "${fulldomain%$suffix}" + ;; + *) + # Domain not within zone, fall back to full host + printf "%s" "$fulldomain" + ;; + esac +} + +_hostup_sanitize_name() { + name="$1" + + if [ -z "$name" ] || [ "$name" = "." ]; then + printf "%s" "@" + return 0 + fi + + # Remove any trailing dot + name="${name%.}" + printf "%s" "$name" +} + +_hostup_fqdn() { + domain="$1" + printf "%s" "${domain%.}" +} + +_hostup_fetch_zone_details() { + zone_id="$1" + + if ! _hostup_rest "GET" "/dns/zones/$zone_id/records" ""; then + return 1 + fi + + zonedomain="$(printf "%s" "$_hostup_response" | _egrep_o '"domain":"[^"]*"' | sed -n '1p' | cut -d ':' -f 2 | tr -d '"')" + if [ -n "$zonedomain" ]; then + HOSTUP_ZONE_DOMAIN="$zonedomain" + return 0 + fi + + return 1 +} + +_hostup_load_zones() { + if ! _hostup_rest "GET" "/dns/zones" ""; then + return 1 + fi + + HOSTUP_ZONES_CACHE="" + data="$(printf "%s" "$_hostup_response" | tr '{' '\n')" + + while IFS= read -r line; do + case "$line" in + *'"domain_id"'*'"domain"'*) + zone_id="$(printf "%s" "$line" | _hostup_json_extract "domain_id")" + zone_domain="$(printf "%s" "$line" | _hostup_json_extract "domain")" + if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then + HOSTUP_ZONES_CACHE="${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id} +" + _debug "hostup_zone_loaded" "$zone_domain|$zone_id" + fi + ;; + esac + done < Date: Mon, 1 Dec 2025 13:49:12 +0100 Subject: [PATCH 02/73] chore: trigger DNS API test From b8e394e76ad6198fb856f479aa5a63549231a21d Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:48:48 +0100 Subject: [PATCH 03/73] Update dns_hostup.sh bug fix Omnios fial --- dnsapi/dns_hostup.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 4da0dd9d..8d0600f7 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -182,9 +182,10 @@ _hostup_detect_zone() { return 0 fi - if ! printf "%s" "$_domain_candidate" | _contains "."; then - break - fi + case "$_domain_candidate" in + *.*) ;; + *) break ;; + esac _domain_candidate="${_domain_candidate#*.}" done From d97b4477b2dcf2753ea0afbd24eea8487625aed2 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:55:17 +0100 Subject: [PATCH 04/73] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 8d0600f7..3547f006 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -171,7 +171,7 @@ _hostup_detect_zone() { return 1 fi - _domain_candidate="$(printf "%s" "$fulldomain" | tr 'A-Z' 'a-z')" + _domain_candidate="$(printf "%s" "$fulldomain" | tr '[:upper:]' '[:lower:]')" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -181,10 +181,10 @@ _hostup_detect_zone() { HOSTUP_ZONE_ID="$_lookup_zone_id" return 0 fi - + case "$_domain_candidate" in - *.*) ;; - *) break ;; + *.*) ;; + *) break ;; esac _domain_candidate="${_domain_candidate#*.}" @@ -210,7 +210,7 @@ _hostup_record_name() { suffix=".$zonedomain" case "$fulldomain" in *"$suffix") - printf "%s" "${fulldomain%$suffix}" + printf "%s" "${fulldomain%"$suffix"}" ;; *) # Domain not within zone, fall back to full host @@ -371,7 +371,7 @@ _hostup_record_key() { zone_id="$1" domain="$2" safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" - safe_domain="$(printf "%s" "$domain" | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]/_/g')" + safe_domain="$(printf "%s" "$domain" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g')" printf "%s_%s" "$safe_zone" "$safe_domain" } @@ -449,7 +449,7 @@ _hostup_rest() { _debug2 "_hostup_response" "$_hostup_response" case "$http_status" in - 200|201|204) return 0 ;; + 200 | 201 | 204) return 0 ;; 401) _err "HostUp API returned 401 Unauthorized. Check HOSTUP_API_KEY scopes and IP restrictions." return 1 From 64a6ea68fa704ce4df35b03cff7c29fe531c38b3 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:58:36 +0100 Subject: [PATCH 05/73] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 3547f006..ca49096f 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -181,7 +181,7 @@ _hostup_detect_zone() { HOSTUP_ZONE_ID="$_lookup_zone_id" return 0 fi - + case "$_domain_candidate" in *.*) ;; *) break ;; From 51b4fa00800ae2aad88a81fe76783d374044318c Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:19:16 +0100 Subject: [PATCH 06/73] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 49 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index ca49096f..347f34d1 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -319,10 +319,14 @@ _hostup_find_record() { records="$(printf "%s" "$_hostup_response" | tr '{' '\n')" while IFS= read -r line; do - case "$line" in + # Normalize line to make TXT value matching reliable + line_clean="$(printf "%s" "$line" | tr -d '\r\n')" + line_value_clean="$(printf "%s" "$line_clean" | sed 's/\\"//g')" + + case "$line_clean" in *'"type":"TXT"'*'"name"'*'"value"'*) - name_value="$(printf "%s" "$line" | _hostup_json_extract "name")" - record_value="$(printf "%s" "$line" | _hostup_json_extract "value")" + name_value="$(_hostup_json_extract "name" "$line_clean")" + record_value="$(_hostup_json_extract "value" "$line_value_clean")" _debug "hostup_record_raw" "$record_value" if [ "${record_value#\"}" != "$record_value" ] && [ "${record_value%\"}" != "$record_value" ]; then @@ -337,7 +341,7 @@ _hostup_find_record() { _debug "hostup_record_value" "$record_value" if [ "$name_value" = "$fqdn" ] && [ "$record_value" = "$txtvalue" ]; then - record_id="$(printf "%s" "$line" | _hostup_json_extract "id")" + record_id="$(_hostup_json_extract "id" "$line_clean")" if [ -n "$record_id" ]; then HOSTUP_RECORD_ID="$record_id" return 0 @@ -354,13 +358,30 @@ EOF _hostup_json_extract() { key="$1" - printf "%s" "$line" | - _egrep_o "\"$key\":\"[^\"]*\"" | - head -n1 | - cut -d : -f2- | - sed 's/^"//' | - sed 's/"$//' | - sed 's/\\"/"/g' + input="${2:-$line}" + + # First try to extract quoted values (strings) + quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | head -n1)" + if [ -n "$quoted_match" ]; then + printf "%s" "$quoted_match" | + cut -d : -f2- | + sed 's/^"//' | + sed 's/"$//' | + sed 's/\\"/"/g' + return 0 + fi + + # Fallback for unquoted values (e.g., numeric IDs) + unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | head -n1)" + if [ -n "$unquoted_match" ]; then + printf "%s" "$unquoted_match" | + cut -d : -f2- | + tr -d '", ' | + tr -d '\r\n' + return 0 + fi + + return 1 } _hostup_json_escape() { @@ -398,6 +419,12 @@ _hostup_clear_record_id() { } _hostup_extract_record_id() { + record_id="$(_hostup_json_extract "id" "$1")" + if [ -n "$record_id" ]; then + printf "%s" "$record_id" + return 0 + fi + printf "%s" "$1" | _egrep_o '"id":[0-9]+' | head -n1 | cut -d: -f2 } From 1413aa332bb48d6cb60a4ef7114fe0be57f3e303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Miri=C4=87?= <1009277+imiric@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:12:27 +0100 Subject: [PATCH 07/73] fix: update Exoscale DNS script This updates the Exoscale DNS script to work with v2 of their API. --- dnsapi/dns_exoscale.sh | 226 ++++++++++++++++++++++++----------------- 1 file changed, 132 insertions(+), 94 deletions(-) mode change 100755 => 100644 dnsapi/dns_exoscale.sh diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh old mode 100755 new mode 100644 index 6898ce38..ddd526a4 --- a/dnsapi/dns_exoscale.sh +++ b/dnsapi/dns_exoscale.sh @@ -8,9 +8,9 @@ Options: EXOSCALE_SECRET_KEY API Secret key ' -EXOSCALE_API=https://api.exoscale.com/dns/v1 +EXOSCALE_API="https://api-ch-gva-2.exoscale.com/v2" -######## Public functions ##################### +######## Public functions ######## # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record @@ -18,159 +18,197 @@ dns_exoscale_add() { fulldomain=$1 txtvalue=$2 - if ! _checkAuth; then + _debug "Using Exoscale DNS v2 API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if ! _check_auth; then return 1 fi - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" + root_domain_id=$(_get_root_domain_id "$fulldomain") + if [ -z "$root_domain_id" ]; then + _err "Unable to determine root domain ID for $fulldomain" return 1 fi + _debug root_domain_id "$root_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" + # Always get the subdomain part first + sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id") + _debug sub_domain "$sub_domain" - _info "Adding record" - if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then - if _contains "$response" "$txtvalue"; then - _info "Added, OK" - return 0 - fi + # Build the record name properly + if [ -z "$sub_domain" ]; then + record_name="_acme-challenge" + else + record_name="_acme-challenge.$sub_domain" fi - _err "Add txt record error." - return 1 + payload=$(printf '{"name":"%s","type":"TXT","content":"%s","ttl":120}' "$record_name" "$txtvalue") + _debug payload "$payload" + + response=$(_exoscale_rest POST "/dns-domain/${root_domain_id}/record" "$payload") + if _contains "$response" "\"id\""; then + _info "TXT record added successfully." + return 0 + else + _err "Error adding TXT record: $response" + return 1 + fi } -# Usage: fulldomain txtvalue -# Used to remove the txt record after validation dns_exoscale_rm() { fulldomain=$1 - txtvalue=$2 - if ! _checkAuth; then + _debug "Using Exoscale DNS v2 API for removal" + _debug fulldomain "$fulldomain" + + if ! _check_auth; then return 1 fi - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" + root_domain_id=$(_get_root_domain_id "$fulldomain") + if [ -z "$root_domain_id" ]; then + _err "Unable to determine root domain ID for $fulldomain" return 1 fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting txt records" - _exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token" - if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then - _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") + record_name="_acme-challenge" + sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id") + if [ -n "$sub_domain" ]; then + record_name="_acme-challenge.$sub_domain" fi - if [ -z "$_record_id" ]; then - _err "Can not get record id to remove." + record_id=$(_find_record_id "$root_domain_id" "$record_name") + if [ -z "$record_id" ]; then + _err "TXT record not found for deletion." return 1 fi - _debug "Deleting record $_record_id" - - if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then - _err "Delete record error." + response=$(_exoscale_rest DELETE "/dns-domain/$root_domain_id/record/$record_id") + if _contains "$response" "\"state\":\"success\""; then + _info "TXT record deleted successfully." + return 0 + else + _err "Error deleting TXT record: $response" return 1 fi - - return 0 } -#################### Private functions below ################################## +######## Private helpers ######## -_checkAuth() { +_check_auth() { EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}" EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}" - if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then - EXOSCALE_API_KEY="" - EXOSCALE_SECRET_KEY="" - _err "You don't specify Exoscale application key and application secret yet." - _err "Please create you key and try again." + _err "EXOSCALE_API_KEY and EXOSCALE_SECRET_KEY must be set." return 1 fi - _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY" _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY" - return 0 } -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -# _domain_id=sdjkglgdfewsdfg -# _domain_token=sdjkglgdfewsdfg -_get_root() { - - if ! _exoscale_rest GET "domains"; then - return 1 - fi - +_get_root_domain_id() { domain=$1 - i=2 - p=1 + i=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid - return 1 - fi - - if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _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") - _domain=$h - return 0 + candidate=$(printf "%s" "$domain" | cut -d . -f "${i}-100") + [ -z "$candidate" ] && return 1 + _debug "Trying root domain candidate: $candidate" + domains=$(_exoscale_rest GET "/dns-domain") + # Extract from dns-domains array + result=$(echo "$domains" | _egrep_o '"dns-domains":\[.*\]' | _egrep_o '\{"id":"[^"]*","created-at":"[^"]*","unicode-name":"[^"]*"\}' | while read -r item; do + name=$(echo "$item" | _egrep_o '"unicode-name":"[^"]*"' | cut -d'"' -f4) + id=$(echo "$item" | _egrep_o '"id":"[^"]*"' | cut -d'"' -f4) + if [ "$name" = "$candidate" ]; then + echo "$id" + break fi - return 1 + done) + if [ -n "$result" ]; then + echo "$result" + return 0 fi - p=$i i=$(_math "$i" + 1) done - return 1 } -# returns response +_get_sub_domain() { + fulldomain=$1 + root_id=$2 + root_info=$(_exoscale_rest GET "/dns-domain/$root_id") + _debug root_info "$root_info" + root_name=$(echo "$root_info" | _egrep_o "\"unicode-name\":\"[^\"]*\"" | cut -d\" -f4) + sub=${fulldomain%%."$root_name"} + + if [ "$sub" = "_acme-challenge" ]; then + echo "" + else + # Remove _acme-challenge. prefix to get the actual subdomain + echo "${sub#_acme-challenge.}" + fi +} + +_find_record_id() { + root_id=$1 + name=$2 + records=$(_exoscale_rest GET "/dns-domain/$root_id/record") + + # Convert search name to lowercase for case-insensitive matching + name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]') + + echo "$records" | _egrep_o '\{[^}]*"name":"[^"]*"[^}]*\}' | while read -r record; do + record_name=$(echo "$record" | _egrep_o '"name":"[^"]*"' | cut -d'"' -f4) + record_name_lower=$(echo "$record_name" | tr '[:upper:]' '[:lower:]') + if [ "$record_name_lower" = "$name_lower" ]; then + echo "$record" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d'"' -f4 + break + fi + done +} + +_exoscale_sign() { + k=$1 + shift + hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ') + printf %s "$@" | _hmac sha256 "$hex_key" +} + _exoscale_rest() { method=$1 - path="$2" - data="$3" - token="$4" - request_url="$EXOSCALE_API/$path" - _debug "$path" + path=$2 + data=$3 + + url="${EXOSCALE_API}${path}" + expiration=$(_math "$(date +%s)" + 300) # 5m from now + + # Build the message with the actual body or empty line + message=$(printf "%s %s\n%s\n\n\n%s" "$method" "/v2$path" "$data" "$expiration") + signature=$(_exoscale_sign "$EXOSCALE_SECRET_KEY" "$message" | _base64) + auth="EXO2-HMAC-SHA256 credential=${EXOSCALE_API_KEY},expires=${expiration},signature=${signature}" + + _debug "API request: $method $url" + _debug "Signed message: [$message]" + _debug "Authorization header: [$auth]" export _H1="Accept: application/json" - - if [ "$token" ]; then - export _H2="X-DNS-Domain-Token: $token" - else - export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY" - fi + export _H2="Authorization: ${auth}" if [ "$data" ] || [ "$method" = "DELETE" ]; then export _H3="Content-Type: application/json" _debug data "$data" - response="$(_post "$data" "$request_url" "" "$method")" + response="$(_post "$data" "$url" "" "$method")" else - response="$(_get "$request_url" "" "" "$method")" + response="$(_get "$url" "" "" "$method")" fi - if [ "$?" != "0" ]; then - _err "error $request_url" + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ]; then + _err "error $url" return 1 fi _debug2 response "$response" + echo "$response" return 0 } From b4042d5ccb5082643f4e4eb3ac63bf9437205f4a Mon Sep 17 00:00:00 2001 From: as-kholin Date: Tue, 16 Dec 2025 14:51:42 -0500 Subject: [PATCH 08/73] Updated checks for empty parameters to actually trigger, and added a validation check against the omg.lol API to confirm address and apikey are good before proceeding --- dnsapi/dns_omglol.sh | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index df080bcf..ddde8f2d 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -35,7 +35,7 @@ dns_omglol_add() { _debug "omg.lol Address" "$OMG_Address" omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" - if [ ! $? ]; then + if [ 1 = $? ]; then return 1 fi @@ -67,7 +67,7 @@ dns_omglol_rm() { _debug "omg.lol Address" "$OMG_Address" omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" - if [ ! $? ]; then + if [ 1 = $? ]; then return 1 fi @@ -100,18 +100,48 @@ omg_validate() { fi _endswith "$fulldomain" "omg.lol" - if [ ! $? ]; then + if [ 1 = $? ]; then _err "Domain name requested is not under omg.lol" return 1 fi _endswith "$fulldomain" "$omg_address.omg.lol" - if [ ! $? ]; then + if [ 1 = $? ]; then _err "Domain name is not a subdomain of provided omg.lol address $omg_address" return 1 fi - _debug "Required environment parameters are all present" + omg_testconnect "$omg_apikey" "$omg_address" + if [ 1 = $? ]; then + _err "Authentication to omg.lol for address $omg_address using provided API key failed" + return 1 + fi + + _debug "Required environment parameters are all present and validated" +} + +# Validate that the address and API key are both correct and associated to each other +omg_testconnect() { + omg_apikey=$1 + omg_address=$2 + + _debug2 "Function" "omg_testconnect" + _secure_debug2 "omg.lol API key" "$omg_apikey" + _debug2 "omg.lol Address" "$omg_address" + + export _H1=$(_createAuthHeader "$omg_apikey") + endpoint="https://api.omg.lol/address/$omg_address/info" + _debug2 "Endpoint for validation" "$endpoint" + + response=$(_get "$endpoint" "" 30) + + _jsonResponseCheck "$response" "status_code" 200 + if [ 1 = $? ]; then + _debug2 "Failed to query omg.lol for $omg_address with provided API key" + _secure_debug2 "API Key" "omg_apikey" + _secure_debug3 "Raw response" "$response" + return 1 + fi } # Add (or modify) an entry for a new ACME query From 4a7e5d07209fa3d50ac9ecf3b7df2febef903084 Mon Sep 17 00:00:00 2001 From: as-kholin Date: Tue, 16 Dec 2025 15:00:05 -0500 Subject: [PATCH 09/73] Updating Auth header to satisfy shellcheck --- dnsapi/dns_omglol.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index ddde8f2d..652cfd15 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -129,7 +129,8 @@ omg_testconnect() { _secure_debug2 "omg.lol API key" "$omg_apikey" _debug2 "omg.lol Address" "$omg_address" - export _H1=$(_createAuthHeader "$omg_apikey") + authheader="$(_createAuthHeader "$omg_apikey")" + export _H1="$authheader" endpoint="https://api.omg.lol/address/$omg_address/info" _debug2 "Endpoint for validation" "$endpoint" From 85ff92170b1810fe7005c65f240c2b34f0cfe468 Mon Sep 17 00:00:00 2001 From: as-kholin Date: Wed, 17 Dec 2025 17:15:10 -0500 Subject: [PATCH 10/73] Updated comment to be more clear on variable vs. definition --- dnsapi/dns_omglol.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh index 652cfd15..fd38d046 100644 --- a/dnsapi/dns_omglol.sh +++ b/dnsapi/dns_omglol.sh @@ -4,8 +4,8 @@ dns_omglol_info='omg.lol Site: omg.lol Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol Options: - OMG_ApiKey API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account - OMG_Address Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard + OMG_ApiKey - API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account + OMG_Address - Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard Issues: github.com/acmesh-official/acme.sh/issues/5299 Author: @Kholin ' From e3b1bccb6aa5e1cda2a1d65c5bae66cec54edb30 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Thu, 18 Dec 2025 16:00:55 +0000 Subject: [PATCH 11/73] Fixes to support INWX again Fixes #6688 --- dnsapi/dns_inwx.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 808fc3a9..89ab67ee 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -125,7 +125,7 @@ dns_inwx_rm() { if ! printf "%s" "$response" | grep "count" >/dev/null; then _info "Do not need to delete record" else - _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') + _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') _info "Deleting record" _inwx_delete_record "$_record_id" fi @@ -324,7 +324,7 @@ _inwx_delete_record() { id - %s + %s @@ -362,7 +362,7 @@ _inwx_update_record() { id - %s + %s From 00aaed1b14aaaf15a5c548f36e9b60ca20b7d069 Mon Sep 17 00:00:00 2001 From: Sergey Parfenov Date: Fri, 19 Dec 2025 18:05:01 +0300 Subject: [PATCH 12/73] Fix strongswan deploy hook Make it more resistant to deploy hooks api change by passing custom arguments first --- deploy/strongswan.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deploy/strongswan.sh b/deploy/strongswan.sh index 14567d17..80353c54 100644 --- a/deploy/strongswan.sh +++ b/deploy/strongswan.sh @@ -33,7 +33,7 @@ strongswan_deploy() { return 1 fi _info _confdir "${_confdir}" - __deploy_cert "$@" "stroke" "${_confdir}" + __deploy_cert "stroke" "${_confdir}" "$@" ${_ipsec} reload fi # For modern vici mode @@ -50,7 +50,7 @@ strongswan_deploy() { _err "no swanctl config dir is found" return 1 fi - __deploy_cert "$@" "vici" "${_confdir}" + __deploy_cert "vici" "${_confdir}" "$@" ${_swanctl} --load-creds fi if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then @@ -63,13 +63,13 @@ strongswan_deploy() { #################### Private functions below ################################## __deploy_cert() { - _cdomain="${1}" - _ckey="${2}" - _ccert="${3}" - _cca="${4}" - _cfullchain="${5}" - _swan_mode="${6}" - _confdir="${7}" + _swan_mode="${1}" + _confdir="${2}" + _cdomain="${3}" + _ckey="${4}" + _ccert="${5}" + _cca="${6}" + _cfullchain="${7}" _debug _cdomain "${_cdomain}" _debug _ckey "${_ckey}" _debug _ccert "${_ccert}" From 987882ea37f28b581b44614d93b0a9ab2f9823e4 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 17:49:24 +0100 Subject: [PATCH 13/73] fix delete --- dnsapi/dns_inwx.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 89ab67ee..afe2c465 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -110,11 +110,17 @@ dns_inwx_rm() { %s + + content + + %s + + - ' "$_domain" "$_sub_domain") + ' "$_domain" "$_sub_domain" "$txtvalue") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! _contains "$response" "Command completed successfully"; then From 1c65c04b54b56a24c6f45d122b594c126b6a45c2 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 17:51:13 +0100 Subject: [PATCH 14/73] update dns_inwx_info --- dnsapi/dns_inwx.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index afe2c465..d83e08d6 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -6,6 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx Options: INWX_User Username INWX_Password Password + INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional) ' # Dependencies: From 27ebf09c5c3834011e29d5adf0f12d0f43540107 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:02:18 +0100 Subject: [PATCH 15/73] Improve _htmlEscape function robustness by using printf instead of echo small commit to trigger github actions --- dnsapi/dns_inwx.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index d83e08d6..a7ce9769 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -172,10 +172,10 @@ _inwx_check_cookie() { _htmlEscape() { _s="$1" - _s=$(echo "$_s" | sed "s/&/&/g") - _s=$(echo "$_s" | sed "s//\>/g") - _s=$(echo "$_s" | sed 's/"/\"/g') + _s=$(printf '%s' "$_s" | sed "s/&/&/g") + _s=$(printf '%s' "$_s" | sed "s//\>/g") + _s=$(printf '%s' "$_s" | sed 's/"/\"/g') printf -- %s "$_s" } From a5ad15be0212cefc2ffa99bcd4c611d15576d4ee Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:07:46 +0100 Subject: [PATCH 16/73] Add oathtool to Docker job for 2FA support --- .github/workflows/DNS.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index ccce2ff6..8f7ebd56 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -67,6 +67,8 @@ jobs: TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 + - name: Install oathtool + run: sudo apt-get update && sudo apt-get install -y oathtool - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file @@ -116,7 +118,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install tools - run: brew install socat + run: brew install socat oath-toolkit - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -173,7 +175,7 @@ jobs: shell: cmd - name: Install cygwin additional packages run: | - C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git + C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,oath-toolkit shell: cmd - name: Set ENV shell: cmd @@ -230,7 +232,7 @@ jobs: - uses: vmactions/freebsd-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 install -y socat curl + prepare: pkg install -y socat curl oath-toolkit usesh: true copyback: false run: | @@ -281,7 +283,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 libiconv + prepare: pkg_add socat curl libiconv oath-toolkit usesh: true copyback: false run: | @@ -333,7 +335,7 @@ jobs: 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: | - /usr/sbin/pkg_add curl socat + /usr/sbin/pkg_add curl socat oath-toolkit usesh: true copyback: false run: | @@ -385,7 +387,7 @@ jobs: 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 install -y curl socat libnghttp2 + pkg install -y curl socat libnghttp2 oath-toolkit usesh: true copyback: false run: | @@ -443,7 +445,7 @@ jobs: copyback: false prepare: | pkgutil -U - pkgutil -y -i socat + pkgutil -y -i socat oathtool run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH @@ -494,7 +496,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false - prepare: pkg install socat + prepare: pkg install socat oath-toolkit run: | if [ "${{ secrets.TokenName1}}" ] ; then export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" From 0e5aab346f7958fdbe35ffa6a133614a59826a3f Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:17:05 +0100 Subject: [PATCH 17/73] Add oathtool to acmetest Docker image for 2FA support --- .github/workflows/DNS.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 8f7ebd56..781c13fb 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -67,10 +67,15 @@ jobs: TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - - name: Install oathtool - run: sudo apt-get update && sudo apt-get install -y oathtool - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Ensure oathtool in test container + run: | + cd ../acmetest + # Add oathtool installation to acmetest Dockerfile if it exists + if [ -f Dockerfile ]; then + sed -i 's/tzdata/tzdata oath-toolkit-oathtool/g' Dockerfile + fi - name: Set env file run: | cd ../acmetest From e92d0a74926855b607fdfb948eafe6bf44e0fdfa Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:20:56 +0100 Subject: [PATCH 18/73] Fix: Install oathtool at runtime in test container --- .github/workflows/DNS.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 781c13fb..e6319c09 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -69,13 +69,11 @@ jobs: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Ensure oathtool in test container + - name: Install oathtool in test container run: | cd ../acmetest - # Add oathtool installation to acmetest Dockerfile if it exists - if [ -f Dockerfile ]; then - sed -i 's/tzdata/tzdata oath-toolkit-oathtool/g' Dockerfile - fi + # Modify letest.sh to install oathtool at runtime + sed -i '/TEST_LOCAL skip setup/a apt-get update -qq && apt-get install -y -qq oathtool > /dev/null 2>&1 || true' letest.sh - name: Set env file run: | cd ../acmetest From b6523c230110f5477f3d4a45a020e4b99d230e69 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 18:42:28 +0100 Subject: [PATCH 19/73] Install oathtool in container via _setup function --- .github/workflows/DNS.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index e6319c09..64fa85d2 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -69,11 +69,11 @@ jobs: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Install oathtool in test container + - name: Patch acmetest to install oathtool in container run: | cd ../acmetest - # Modify letest.sh to install oathtool at runtime - sed -i '/TEST_LOCAL skip setup/a apt-get update -qq && apt-get install -y -qq oathtool > /dev/null 2>&1 || true' letest.sh + # Add oathtool installation before the test runs inside container + sed -i '/^_setup() {$/a \ if command -v apt-get >/dev/null 2>&1; then\n apt-get update -qq && apt-get install -y -qq oathtool >/dev/null 2>&1 || true\n fi' letest.sh - name: Set env file run: | cd ../acmetest From cba0ff832116b2fc322c3dd51401322789a62d4a Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 19:09:23 +0100 Subject: [PATCH 20/73] Add oathtool to ubuntu package list in plat.conf --- .github/workflows/DNS.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 64fa85d2..84cebf94 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -69,11 +69,11 @@ jobs: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Patch acmetest to install oathtool in container + - name: Add oathtool to ubuntu package list run: | cd ../acmetest - # Add oathtool installation before the test runs inside container - sed -i '/^_setup() {$/a \ if command -v apt-get >/dev/null 2>&1; then\n apt-get update -qq && apt-get install -y -qq oathtool >/dev/null 2>&1 || true\n fi' letest.sh + # Add oathtool to the ubuntu platform package list + sed -i 's/unzip,openssl,cron,socat,curl,idn,wget/unzip,openssl,cron,socat,curl,idn,wget,oathtool/' plat.conf - name: Set env file run: | cd ../acmetest From 65892453be0cff43cf1b6f996f5cbc2707d540d7 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Fri, 19 Dec 2025 19:23:13 +0100 Subject: [PATCH 21/73] take a fork of acmetest --- .github/workflows/DNS.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 84cebf94..5650c9e8 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -68,12 +68,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Add oathtool to ubuntu package list - run: | - cd ../acmetest - # Add oathtool to the ubuntu platform package list - sed -i 's/unzip,openssl,cron,socat,curl,idn,wget/unzip,openssl,cron,socat,curl,idn,wget,oathtool/' plat.conf + run: cd .. && git clone --depth=1 https://github.com/flybyray/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file run: | cd ../acmetest From 3fb4c313ec3d28967ed22de769872ef8336d40a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E6=B6=9B?= Date: Fri, 19 Dec 2025 16:41:22 +0800 Subject: [PATCH 22/73] Support list IPv6 address certificate --- acme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acme.sh b/acme.sh index ba0f62bc..053f16db 100755 --- a/acme.sh +++ b/acme.sh @@ -5840,7 +5840,7 @@ list() { if [ -z "$_domain" ]; then printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Profile${_sep}CA${_sep}Created${_sep}Renew" fi - for di in "${CERT_HOME}"/*.*/; do + for di in "${CERT_HOME}"/{*.*,*:*}/; do d=$(basename "$di") _debug d "$d" ( From 1b2630dc0d4030f4507bcfeba60bdc2e102c1410 Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Sat, 20 Dec 2025 13:26:38 +0100 Subject: [PATCH 23/73] stop using oathtool --- .github/workflows/DNS.yml | 2 +- dnsapi/dns_inwx.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 5650c9e8..39d2a121 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -68,7 +68,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/flybyray/acmetest.git && cp -r acme.sh acmetest/ + run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file run: | cd ../acmetest diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index a7ce9769..08809bf0 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -6,7 +6,7 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx Options: INWX_User Username INWX_Password Password - INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional) + INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional requires oathtool) ' # Dependencies: From 5fb42b7339b82d3397b2003dedd3302b439d745c Mon Sep 17 00:00:00 2001 From: Robert Rettig Date: Sat, 20 Dec 2025 14:53:01 +0100 Subject: [PATCH 24/73] remove prior additions which tried to use oathtool in tests --- .github/workflows/DNS.yml | 16 ++++++++-------- dnsapi/dns_inwx.sh | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 39d2a121..ccce2ff6 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -116,7 +116,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install tools - run: brew install socat oath-toolkit + run: brew install socat - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -173,7 +173,7 @@ jobs: shell: cmd - name: Install cygwin additional packages run: | - C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,oath-toolkit + C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git shell: cmd - name: Set ENV shell: cmd @@ -230,7 +230,7 @@ jobs: - uses: vmactions/freebsd-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 install -y socat curl oath-toolkit + prepare: pkg install -y socat curl usesh: true copyback: false run: | @@ -281,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 libiconv oath-toolkit + prepare: pkg_add socat curl libiconv usesh: true copyback: false run: | @@ -333,7 +333,7 @@ jobs: 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: | - /usr/sbin/pkg_add curl socat oath-toolkit + /usr/sbin/pkg_add curl socat usesh: true copyback: false run: | @@ -385,7 +385,7 @@ jobs: 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 install -y curl socat libnghttp2 oath-toolkit + pkg install -y curl socat libnghttp2 usesh: true copyback: false run: | @@ -443,7 +443,7 @@ jobs: copyback: false prepare: | pkgutil -U - pkgutil -y -i socat oathtool + pkgutil -y -i socat run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH @@ -494,7 +494,7 @@ jobs: with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false - prepare: pkg install socat oath-toolkit + prepare: pkg install socat run: | if [ "${{ secrets.TokenName1}}" ] ; then export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 08809bf0..2cf91404 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -172,10 +172,10 @@ _inwx_check_cookie() { _htmlEscape() { _s="$1" - _s=$(printf '%s' "$_s" | sed "s/&/&/g") - _s=$(printf '%s' "$_s" | sed "s//\>/g") - _s=$(printf '%s' "$_s" | sed 's/"/\"/g') + _s=$(echo "$_s" | sed "s/&/&/g") + _s=$(echo "$_s" | sed "s//\>/g") + _s=$(echo "$_s" | sed 's/"/\"/g') printf -- %s "$_s" } From f85de2b0d3560bab9fb553b53e9564edcad1b01d Mon Sep 17 00:00:00 2001 From: Erfan Gholizade Date: Wed, 3 Dec 2025 18:26:58 +0330 Subject: [PATCH 25/73] Added Sotoon dns api handle the case of metadata 404 in old api domains fix index of domain start fix shfmt Revert "handle the case of metadata 404 in old api domains" This reverts commit 9fe4616664b897c9891271006e7489b10bb818ca. fix 404 on dot ad hyphen fix shfmt --- dnsapi/dns_sotoon.sh | 319 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 dnsapi/dns_sotoon.sh diff --git a/dnsapi/dns_sotoon.sh b/dnsapi/dns_sotoon.sh new file mode 100644 index 00000000..4a0fc034 --- /dev/null +++ b/dnsapi/dns_sotoon.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env sh +# shellcheck disable=SC2034 +dns_sotoon_info='Sotoon.ir +Site: Sotoon.ir +Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sotoon +Options: + Sotoon_Token API Token + Sotoon_WorkspaceUUID Workspace UUID + Sotoon_WorkspaceName Workspace Name +Issues: github.com/acmesh-official/acme.sh/issues/6656 +Author: Erfan Gholizade +' + +SOTOON_API_URL="https://api.sotoon.ir/delivery/v2/global" + +######## Public functions ##################### + +#Adding the txt record for validation. +#Usage: dns_sotoon_add fulldomain TXT_record +#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_sotoon_add() { + fulldomain=$1 + txtvalue=$2 + _info_sotoon "Using Sotoon" + + Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" + Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" + Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" + + if [ -z "$Sotoon_Token" ]; then + _err_sotoon "You didn't specify \"Sotoon_Token\" token yet." + _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/tokens" + return 1 + fi + if [ -z "$Sotoon_WorkspaceUUID" ]; then + _err_sotoon "You didn't specify \"Sotoon_WorkspaceUUID\" Workspace UUID yet." + _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" + return 1 + fi + if [ -z "$Sotoon_WorkspaceName" ]; then + _err_sotoon "You didn't specify \"Sotoon_WorkspaceName\" Workspace Name yet." + _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" + return 1 + fi + + #save the info to the account conf file. + _saveaccountconf_mutable Sotoon_Token "$Sotoon_Token" + _saveaccountconf_mutable Sotoon_WorkspaceUUID "$Sotoon_WorkspaceUUID" + _saveaccountconf_mutable Sotoon_WorkspaceName "$Sotoon_WorkspaceName" + + _debug_sotoon "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err_sotoon "invalid domain" + return 1 + fi + + _info_sotoon "Adding record" + + _debug_sotoon _domain_id "$_domain_id" + _debug_sotoon _sub_domain "$_sub_domain" + _debug_sotoon _domain "$_domain" + + # First, GET the current domain zone to check for existing TXT records + # This is needed for wildcard certs which require multiple TXT values + _info_sotoon "Checking for existing TXT records" + if ! _sotoon_rest GET "$_domain_id"; then + _err_sotoon "Failed to get domain zone" + return 1 + fi + + # Check if there are existing TXT records for this subdomain + _existing_txt="" + if _contains "$response" "\"$_sub_domain\""; then + _debug_sotoon "Found existing records for $_sub_domain" + # Extract existing TXT values from the response + # The format is: "_acme-challenge":[{"TXT":"value1","type":"TXT","ttl":10},{"TXT":"value2",...}] + _existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") + _debug_sotoon "Existing TXT records: $_existing_txt" + fi + + # Build the new record entry + _new_record="{\"TXT\":\"$txtvalue\",\"type\":\"TXT\",\"ttl\":120}" + + # If there are existing records, append to them; otherwise create new array + if [ -n "$_existing_txt" ] && [ "$_existing_txt" != "[]" ] && [ "$_existing_txt" != "null" ]; then + # Check if this exact TXT value already exists (avoid duplicates) + if _contains "$_existing_txt" "\"$txtvalue\""; then + _info_sotoon "TXT record already exists, skipping" + return 0 + fi + # Remove the closing bracket and append new record + _combined_records="$(echo "$_existing_txt" | sed 's/]$//'),$_new_record]" + _debug_sotoon "Combined records: $_combined_records" + else + # No existing records, create new array + _combined_records="[$_new_record]" + fi + + # Prepare the DNS record data in Kubernetes CRD format + _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_combined_records}}}" + + _debug_sotoon "DNS record payload: $_dns_record" + + # Use PATCH to update/add the record to the domain zone + _info_sotoon "Updating domain zone $_domain_id with TXT record" + if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then + if _contains "$response" "$txtvalue" || _contains "$response" "\"$_sub_domain\""; then + _info_sotoon "Added, OK" + return 0 + else + _debug_sotoon "Response: $response" + _err_sotoon "Add txt record error." + return 1 + fi + fi + + _err_sotoon "Add txt record error." + return 1 +} + +#Remove the txt record after validation. +#Usage: dns_sotoon_rm fulldomain TXT_record +#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_sotoon_rm() { + fulldomain=$1 + txtvalue=$2 + _info_sotoon "Using Sotoon" + _debug_sotoon fulldomain "$fulldomain" + _debug_sotoon txtvalue "$txtvalue" + + Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" + Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" + Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" + + _debug_sotoon "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err_sotoon "invalid domain" + return 1 + fi + _debug_sotoon _domain_id "$_domain_id" + _debug_sotoon _sub_domain "$_sub_domain" + _debug_sotoon _domain "$_domain" + + _info_sotoon "Removing TXT record" + + # First, GET the current domain zone to check for existing TXT records + if ! _sotoon_rest GET "$_domain_id"; then + _err_sotoon "Failed to get domain zone" + return 1 + fi + + # Check if there are existing TXT records for this subdomain + _existing_txt="" + if _contains "$response" "\"$_sub_domain\""; then + _debug_sotoon "Found existing records for $_sub_domain" + _existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") + _debug_sotoon "Existing TXT records: $_existing_txt" + fi + + # If no existing records, nothing to remove + if [ -z "$_existing_txt" ] || [ "$_existing_txt" = "[]" ] || [ "$_existing_txt" = "null" ]; then + _info_sotoon "No TXT records found, nothing to remove" + return 0 + fi + + # Remove the specific TXT value from the array + # This handles the case where there are multiple TXT values (wildcard certs) + _remaining_records=$(echo "$_existing_txt" | sed "s/{\"TXT\":\"$txtvalue\"[^}]*},*//g" | sed 's/,]/]/g' | sed 's/\[,/[/g') + _debug_sotoon "Remaining records after removal: $_remaining_records" + + # If no records remain, set to null to remove the subdomain entirely + if [ "$_remaining_records" = "[]" ] || [ -z "$_remaining_records" ]; then + _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":null}}}" + else + _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_remaining_records}}}" + fi + + _debug_sotoon "Remove record payload: $_dns_record" + + # Use PATCH to remove the record from the domain zone + if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then + _info_sotoon "Record removed, OK" + return 0 + else + _debug_sotoon "Response: $response" + _err_sotoon "Error removing record" + return 1 + fi +} + +#################### Private functions below ################################## + +_get_root() { + domain=$1 + i=1 + p=1 + + _debug_sotoon "Getting root domain for: $domain" + _debug_sotoon "Sotoon WorkspaceUUID: $Sotoon_WorkspaceUUID" + _debug_sotoon "Sotoon WorkspaceName: $Sotoon_WorkspaceName" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _debug_sotoon "Checking domain part: $h" + + if [ -z "$h" ]; then + #not valid + _err_sotoon "Could not find valid domain" + return 1 + fi + + _debug_sotoon "Fetching domain zones from Sotoon API" + if ! _sotoon_rest GET ""; then + _err_sotoon "Failed to get domain zones from Sotoon API" + _err_sotoon "Please check your Sotoon_Token, Sotoon_WorkspaceUUID, and Sotoon_WorkspaceName" + return 1 + fi + + _debug2_sotoon "API Response: $response" + + # Check if the response contains our domain + # Sotoon API uses Kubernetes CRD format with spec.origin for domain matching + if _contains "$response" "\"origin\":\"$h\""; then + _debug_sotoon "Found domain by origin: $h" + + # In Kubernetes CRD format, the metadata.name is the resource identifier + # The name can be either: + # 1. Same as origin + # 2. Origin with dots replaced by hyphens + # We check both patterns in the response to determine which one exists + + # Convert origin to hyphenated version for checking + _h_hyphenated=$(echo "$h" | tr '.' '-') + + # Check if the hyphenated name exists in the response + if _contains "$response" "\"name\":\"$_h_hyphenated\""; then + _domain_id="$_h_hyphenated" + _debug_sotoon "Found domain ID (hyphenated): $_domain_id" + # Check if the origin itself is used as name + elif _contains "$response" "\"name\":\"$h\""; then + _domain_id="$h" + _debug_sotoon "Found domain ID (same as origin): $_domain_id" + else + # Fallback: use the hyphenated version (more common) + _domain_id="$_h_hyphenated" + _debug_sotoon "Using hyphenated domain ID as fallback: $_domain_id" + fi + + if [ -n "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$h + _debug_sotoon "Domain ID (metadata.name): $_domain_id" + _debug_sotoon "Sub domain: $_sub_domain" + _debug_sotoon "Domain (origin): $_domain" + return 0 + fi + _err_sotoon "Found domain $h but could not extract domain ID" + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_sotoon_rest() { + mtd="$1" + resource_id="$2" + data="$3" + + token_trimmed=$(echo "$Sotoon_Token" | tr -d '"') + + # Construct the API endpoint + _api_path="$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/namespaces/$Sotoon_WorkspaceName/domainzones" + + if [ -n "$resource_id" ]; then + _api_path="$_api_path/$resource_id" + fi + + _debug_sotoon "API Path: $_api_path" + _debug_sotoon "Method: $mtd" + + # Set authorization header - Sotoon API uses Bearer token + export _H1="Authorization: Bearer $token_trimmed" + + if [ "$mtd" = "GET" ]; then + # GET request + _debug_sotoon "GET" "$_api_path" + response="$(_get "$_api_path")" + elif [ "$mtd" = "PATCH" ]; then + # PATCH Request + export _H2="Content-Type: application/merge-patch+json" + _debug_sotoon data "$data" + response="$(_post "$data" "$_api_path" "" "$mtd")" + else + _err_sotoon "Unknown method: $mtd" + return 1 + fi + + _debug2_sotoon response "$response" + return 0 +} + +#Wrappers for logging +_info_sotoon() { + _info "[Sotoon]" "$@" +} + +_err_sotoon() { + _err "[Sotoon]" "$@" +} + +_debug_sotoon() { + _debug "[Sotoon]" "$@" +} + +_debug2_sotoon() { + _debug2 "[Sotoon]" "$@" +} From 03d8d3bc1b20fe356feb823069424868e149898e Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 22 Dec 2025 04:29:50 +0100 Subject: [PATCH 26/73] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index 347f34d1..af722297 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -171,7 +171,7 @@ _hostup_detect_zone() { return 1 fi - _domain_candidate="$(printf "%s" "$fulldomain" | tr '[:upper:]' '[:lower:]')" + _domain_candidate="$(_lower_case "$fulldomain")" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -361,7 +361,7 @@ _hostup_json_extract() { input="${2:-$line}" # First try to extract quoted values (strings) - quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | head -n1)" + quoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":\"[^\"]*\"" | _head_n 1)" if [ -n "$quoted_match" ]; then printf "%s" "$quoted_match" | cut -d : -f2- | @@ -372,7 +372,7 @@ _hostup_json_extract() { fi # Fallback for unquoted values (e.g., numeric IDs) - unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | head -n1)" + unquoted_match="$(printf "%s" "$input" | _egrep_o "\"$key\":[^,}]*" | _head_n 1)" if [ -n "$unquoted_match" ]; then printf "%s" "$unquoted_match" | cut -d : -f2- | @@ -392,7 +392,7 @@ _hostup_record_key() { zone_id="$1" domain="$2" safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" - safe_domain="$(printf "%s" "$domain" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g')" + safe_domain="$(printf "%s" "$(_lower_case "$domain")" | sed 's/[^a-z0-9]/_/g')" printf "%s_%s" "$safe_zone" "$safe_domain" } @@ -425,7 +425,7 @@ _hostup_extract_record_id() { return 0 fi - printf "%s" "$1" | _egrep_o '"id":[0-9]+' | head -n1 | cut -d: -f2 + printf "%s" "$1" | _egrep_o '"id":[0-9]+' | _head_n 1 | cut -d: -f2 } _hostup_delete_record_by_id() { From e321b3c75c0eabe6341f9d7b5643a9e9ef4019a6 Mon Sep 17 00:00:00 2001 From: hostup <52465293+hostup@users.noreply.github.com> Date: Mon, 22 Dec 2025 04:53:21 +0100 Subject: [PATCH 27/73] Update dns_hostup.sh --- dnsapi/dns_hostup.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh index af722297..b3211069 100644 --- a/dnsapi/dns_hostup.sh +++ b/dnsapi/dns_hostup.sh @@ -3,7 +3,7 @@ dns_hostup_info='HostUp DNS Site: hostup.se -Docs: https://hostup.se/en/support/api-autentisering/ +Docs: https://developer.hostup.se/ Options: HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes. HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api). @@ -171,7 +171,7 @@ _hostup_detect_zone() { return 1 fi - _domain_candidate="$(_lower_case "$fulldomain")" + _domain_candidate="$(printf "%s" "$fulldomain" | _lower_case)" _debug "hostup_initial_candidate" "$_domain_candidate" while [ -n "$_domain_candidate" ]; do @@ -392,7 +392,7 @@ _hostup_record_key() { zone_id="$1" domain="$2" safe_zone="$(printf "%s" "$zone_id" | sed 's/[^A-Za-z0-9]/_/g')" - safe_domain="$(printf "%s" "$(_lower_case "$domain")" | sed 's/[^a-z0-9]/_/g')" + safe_domain="$(printf "%s" "$domain" | _lower_case | sed 's/[^a-z0-9]/_/g')" printf "%s_%s" "$safe_zone" "$safe_domain" } From 0eb40c6ce6ce5c0bf17f39893e1b6ea4056ae362 Mon Sep 17 00:00:00 2001 From: DreamSlave <50408721+mq00fc@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:00:22 +0800 Subject: [PATCH 28/73] Update timestamp variable in ali_cdn.sh --- deploy/ali_cdn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ali_cdn.sh b/deploy/ali_cdn.sh index 70a2e532..3c28674e 100644 --- a/deploy/ali_cdn.sh +++ b/deploy/ali_cdn.sh @@ -83,6 +83,6 @@ _set_cdn_domain_ssl_certificate_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2018-05-10' } From fd6a14de8a69193a5367dfaae71857d804a83528 Mon Sep 17 00:00:00 2001 From: DreamSlave <50408721+mq00fc@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:00:31 +0800 Subject: [PATCH 29/73] Update timestamp function in ali_dcdn.sh --- deploy/ali_dcdn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/ali_dcdn.sh b/deploy/ali_dcdn.sh index 14ac500a..27d3a726 100644 --- a/deploy/ali_dcdn.sh +++ b/deploy/ali_dcdn.sh @@ -83,6 +83,6 @@ _set_dcdn_domain_ssl_certificate_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2018-01-15' } From 383557df61d616f0b5d1e01c97bba48d2cb74553 Mon Sep 17 00:00:00 2001 From: Erfan Gholizade Date: Wed, 24 Dec 2025 17:19:17 +0330 Subject: [PATCH 30/73] improve: change sotoon api to v2.1 with simplification --- dnsapi/dns_sotoon.sh | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/dnsapi/dns_sotoon.sh b/dnsapi/dns_sotoon.sh index 4a0fc034..b94a220f 100644 --- a/dnsapi/dns_sotoon.sh +++ b/dnsapi/dns_sotoon.sh @@ -6,12 +6,11 @@ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sotoon Options: Sotoon_Token API Token Sotoon_WorkspaceUUID Workspace UUID - Sotoon_WorkspaceName Workspace Name Issues: github.com/acmesh-official/acme.sh/issues/6656 Author: Erfan Gholizade ' -SOTOON_API_URL="https://api.sotoon.ir/delivery/v2/global" +SOTOON_API_URL="https://api.sotoon.ir/delivery/v2.1/global" ######## Public functions ##################### @@ -25,7 +24,6 @@ dns_sotoon_add() { Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" - Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" if [ -z "$Sotoon_Token" ]; then _err_sotoon "You didn't specify \"Sotoon_Token\" token yet." @@ -37,16 +35,10 @@ dns_sotoon_add() { _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" return 1 fi - if [ -z "$Sotoon_WorkspaceName" ]; then - _err_sotoon "You didn't specify \"Sotoon_WorkspaceName\" Workspace Name yet." - _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" - return 1 - fi #save the info to the account conf file. _saveaccountconf_mutable Sotoon_Token "$Sotoon_Token" _saveaccountconf_mutable Sotoon_WorkspaceUUID "$Sotoon_WorkspaceUUID" - _saveaccountconf_mutable Sotoon_WorkspaceName "$Sotoon_WorkspaceName" _debug_sotoon "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -130,7 +122,6 @@ dns_sotoon_rm() { Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" - Sotoon_WorkspaceName="${Sotoon_WorkspaceName:-$(_readaccountconf_mutable Sotoon_WorkspaceName)}" _debug_sotoon "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -197,7 +188,6 @@ _get_root() { _debug_sotoon "Getting root domain for: $domain" _debug_sotoon "Sotoon WorkspaceUUID: $Sotoon_WorkspaceUUID" - _debug_sotoon "Sotoon WorkspaceName: $Sotoon_WorkspaceName" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) @@ -212,7 +202,7 @@ _get_root() { _debug_sotoon "Fetching domain zones from Sotoon API" if ! _sotoon_rest GET ""; then _err_sotoon "Failed to get domain zones from Sotoon API" - _err_sotoon "Please check your Sotoon_Token, Sotoon_WorkspaceUUID, and Sotoon_WorkspaceName" + _err_sotoon "Please check your Sotoon_Token, Sotoon_WorkspaceUUID" return 1 fi @@ -271,7 +261,7 @@ _sotoon_rest() { token_trimmed=$(echo "$Sotoon_Token" | tr -d '"') # Construct the API endpoint - _api_path="$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/namespaces/$Sotoon_WorkspaceName/domainzones" + _api_path="$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/domainzones" if [ -n "$resource_id" ]; then _api_path="$_api_path/$resource_id" From b9c877adb973d6efa128664ded3761aee1fa0872 Mon Sep 17 00:00:00 2001 From: xiagw Date: Sat, 27 Dec 2025 14:33:03 +0800 Subject: [PATCH 31/73] fix: update timestamp variable for CDN and DCDN SSL certificate queries --- deploy/ali_cdn.sh | 2 +- deploy/ali_dcdn.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/ali_cdn.sh b/deploy/ali_cdn.sh index 70a2e532..3c28674e 100644 --- a/deploy/ali_cdn.sh +++ b/deploy/ali_cdn.sh @@ -83,6 +83,6 @@ _set_cdn_domain_ssl_certificate_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2018-05-10' } diff --git a/deploy/ali_dcdn.sh b/deploy/ali_dcdn.sh index 14ac500a..27d3a726 100644 --- a/deploy/ali_dcdn.sh +++ b/deploy/ali_dcdn.sh @@ -83,6 +83,6 @@ _set_dcdn_domain_ssl_certificate_query() { query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_timestamp) + query=$query'&Timestamp='$(_ali_timestamp) query=$query'&Version=2018-01-15' } From 5a730bf00d705c7914dc630ecc5149b3bd9ad87e Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Thu, 20 Feb 2025 21:42:11 +0100 Subject: [PATCH 32/73] implemented checking deploy file --- deploy/multideploy.sh | 114 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 deploy/multideploy.sh diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh new file mode 100644 index 00000000..ba57afa1 --- /dev/null +++ b/deploy/multideploy.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env sh + +# MULTIDEPLOY_CONFIG="default" + +######## Public functions ##################### + +MULTIDEPLOY_VERSION="1.0" +MULTIDEPLOY_FILENAME="multideploy.yaml" + +# domain keyfile certfile cafile fullchain pfx +multideploy_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + _cpfx="$6" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + _debug _cpfx "$_cpfx" + + DOMAIN_DIR=$_cdomain + if echo "$DOMAIN_PATH" | grep -q "$ECC_SUFFIX"; then + DOMAIN_DIR="$DOMAIN_DIR"_ecc + fi + _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" + + _preprocess_deployfile "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" + + MULTIDEPLOY_CONFIG="${MULTIDEPLOY_CONFIG:-$(_getdeployconf MULTIDEPLOY_CONFIG)}" + if [ -z "$MULTIDEPLOY_CONFIG" ]; then + MULTIDEPLOY_CONFIG="default" + _info "MULTIDEPLOY_CONFIG is not set, so I will use 'default'." + else + _savedeployconf "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" + _debug2 "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" + fi + + # TODO: Deploy to services +} + +#################### Private functions below ##################### + +# deploy_filepath +_preprocess_deployfile() { + deploy_file="$1" + + # Check if yq is installed + if ! command -v yq >/dev/null 2>&1; then + _err "yq is not installed! Please install yq and try again." + return 1 + fi + + # Check if deploy file exists and create a default template if not + if [ -f "$deploy_file" ]; then + _debug3 "Deploy file found." + _check_deployfile "$deploy_file" "$MULTIDEPLOY_CONFIG" + else + # TODO: Replace URL with wiki link + _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." + return 1 + fi +} + +# deploy_filepath deploy_config +_check_deployfile() { + deploy_file="$1" + deploy_config="$3" + + # Check version + deploy_file_version=$(yq '.version' "$deploy_file") + if [ "$MULTIDEPLOY_VERSION" != "$deploy_file_version" ]; then + _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $deploy_file_version." + return 1 + fi + + # Check if config exists + if ! yq e ".configs[] | select(.name == \"$deploy_config\")" "$deploy_file" >/dev/null; then + _err "Config '$deploy_config' not found." + return 1 + fi + + # Extract all services from config + services=$(yq e ".configs[] | select(.name == \"$deploy_config\").services[]" "$deploy_file") + + if [ -z "$services" ]; then + _err "Config '$deploy_config' does not have any services to deploy to." + return 1 + fi + + # Check if extracted services exist in services list + for service in $services; do + if ! yq e ".services[] | select(.name == \"$service\")" "$deploy_file" >/dev/null; then + _err "Service '$service' not found." + return 1 + fi + + # Check if service has hook + if ! yq e ".services[] | select(.name == \"$service\").hook" "$deploy_file" >/dev/null; then + _err "Service '$service' does not have a hook." + return 1 + fi + + # Check if service has environment + if ! yq e ".services[] | select(.name == \"$service\").environment" "$deploy_file" >/dev/null; then + _err "Service '$service' does not an environment." + return 1 + fi + done +} From 3c184486c3b0749bfa6d1812071d3f099a155815 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Thu, 20 Feb 2025 21:56:28 +0100 Subject: [PATCH 33/73] fixed indents --- deploy/multideploy.sh | 158 +++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index ba57afa1..08e0aba6 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -9,106 +9,106 @@ MULTIDEPLOY_FILENAME="multideploy.yaml" # domain keyfile certfile cafile fullchain pfx multideploy_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - _cpfx="$6" + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + _cpfx="$6" - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - _debug _cpfx "$_cpfx" + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + _debug _cpfx "$_cpfx" - DOMAIN_DIR=$_cdomain - if echo "$DOMAIN_PATH" | grep -q "$ECC_SUFFIX"; then - DOMAIN_DIR="$DOMAIN_DIR"_ecc - fi - _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" + DOMAIN_DIR=$_cdomain + if echo "$DOMAIN_PATH" | grep -q "$ECC_SUFFIX"; then + DOMAIN_DIR="$DOMAIN_DIR"_ecc + fi + _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" - _preprocess_deployfile "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" + _preprocess_deployfile "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" - MULTIDEPLOY_CONFIG="${MULTIDEPLOY_CONFIG:-$(_getdeployconf MULTIDEPLOY_CONFIG)}" - if [ -z "$MULTIDEPLOY_CONFIG" ]; then - MULTIDEPLOY_CONFIG="default" - _info "MULTIDEPLOY_CONFIG is not set, so I will use 'default'." - else - _savedeployconf "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" - _debug2 "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" - fi + MULTIDEPLOY_CONFIG="${MULTIDEPLOY_CONFIG:-$(_getdeployconf MULTIDEPLOY_CONFIG)}" + if [ -z "$MULTIDEPLOY_CONFIG" ]; then + MULTIDEPLOY_CONFIG="default" + _info "MULTIDEPLOY_CONFIG is not set, so I will use 'default'." + else + _savedeployconf "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" + _debug2 "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" + fi - # TODO: Deploy to services + # TODO: Deploy to services } #################### Private functions below ##################### # deploy_filepath _preprocess_deployfile() { - deploy_file="$1" + deploy_file="$1" - # Check if yq is installed - if ! command -v yq >/dev/null 2>&1; then - _err "yq is not installed! Please install yq and try again." - return 1 - fi + # Check if yq is installed + if ! command -v yq >/dev/null 2>&1; then + _err "yq is not installed! Please install yq and try again." + return 1 + fi - # Check if deploy file exists and create a default template if not - if [ -f "$deploy_file" ]; then - _debug3 "Deploy file found." - _check_deployfile "$deploy_file" "$MULTIDEPLOY_CONFIG" - else - # TODO: Replace URL with wiki link - _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." - return 1 - fi + # Check if deploy file exists and create a default template if not + if [ -f "$deploy_file" ]; then + _debug3 "Deploy file found." + _check_deployfile "$deploy_file" "$MULTIDEPLOY_CONFIG" + else + # TODO: Replace URL with wiki link + _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." + return 1 + fi } # deploy_filepath deploy_config _check_deployfile() { - deploy_file="$1" - deploy_config="$3" + deploy_file="$1" + deploy_config="$3" - # Check version - deploy_file_version=$(yq '.version' "$deploy_file") - if [ "$MULTIDEPLOY_VERSION" != "$deploy_file_version" ]; then - _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $deploy_file_version." - return 1 + # Check version + deploy_file_version=$(yq '.version' "$deploy_file") + if [ "$MULTIDEPLOY_VERSION" != "$deploy_file_version" ]; then + _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $deploy_file_version." + return 1 + fi + + # Check if config exists + if ! yq e ".configs[] | select(.name == \"$deploy_config\")" "$deploy_file" >/dev/null; then + _err "Config '$deploy_config' not found." + return 1 + fi + + # Extract all services from config + services=$(yq e ".configs[] | select(.name == \"$deploy_config\").services[]" "$deploy_file") + + if [ -z "$services" ]; then + _err "Config '$deploy_config' does not have any services to deploy to." + return 1 + fi + + # Check if extracted services exist in services list + for service in $services; do + if ! yq e ".services[] | select(.name == \"$service\")" "$deploy_file" >/dev/null; then + _err "Service '$service' not found." + return 1 fi - # Check if config exists - if ! yq e ".configs[] | select(.name == \"$deploy_config\")" "$deploy_file" >/dev/null; then - _err "Config '$deploy_config' not found." - return 1 + # Check if service has hook + if ! yq e ".services[] | select(.name == \"$service\").hook" "$deploy_file" >/dev/null; then + _err "Service '$service' does not have a hook." + return 1 fi - # Extract all services from config - services=$(yq e ".configs[] | select(.name == \"$deploy_config\").services[]" "$deploy_file") - - if [ -z "$services" ]; then - _err "Config '$deploy_config' does not have any services to deploy to." - return 1 + # Check if service has environment + if ! yq e ".services[] | select(.name == \"$service\").environment" "$deploy_file" >/dev/null; then + _err "Service '$service' does not an environment." + return 1 fi - - # Check if extracted services exist in services list - for service in $services; do - if ! yq e ".services[] | select(.name == \"$service\")" "$deploy_file" >/dev/null; then - _err "Service '$service' not found." - return 1 - fi - - # Check if service has hook - if ! yq e ".services[] | select(.name == \"$service\").hook" "$deploy_file" >/dev/null; then - _err "Service '$service' does not have a hook." - return 1 - fi - - # Check if service has environment - if ! yq e ".services[] | select(.name == \"$service\").environment" "$deploy_file" >/dev/null; then - _err "Service '$service' does not an environment." - return 1 - fi - done + done } From b2eb1d2bbc8fed4f940afa1fb20a4e0beb33bf3a Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:02:56 +0100 Subject: [PATCH 34/73] refactored getting services --- deploy/multideploy.sh | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 08e0aba6..2a83a19f 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -85,7 +85,7 @@ _check_deployfile() { fi # Extract all services from config - services=$(yq e ".configs[] | select(.name == \"$deploy_config\").services[]" "$deploy_file") + services=$(_get_services_list "$deploy_file" "$deploy_config") if [ -z "$services" ]; then _err "Config '$deploy_config' does not have any services to deploy to." @@ -112,3 +112,29 @@ _check_deployfile() { fi done } + +# deploy_filepath deploy_config +_get_services_list() { + deploy_file="$1" + deploy_config="$2" + + services=$(yq e ".configs[] | select(.name == \"$deploy_config\").services[]" "$deploy_file") + echo "$services" +} + +# deploy_filepath service_names +_get_full_services_list() { + deploy_file="$1" + shift + service_names="$*" + + full_services="" + for service in $service_names; do + full_service=$(yq e ".services[] | select(.name == \"$service\")" "$deploy_file") + full_services="$full_services +$full_service" + done + + echo "$full_services" +} + From 0ed5e21232ddcf821f47a1bd223fd05511494775 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:49:08 +0100 Subject: [PATCH 35/73] fixed formatting and private var names --- deploy/multideploy.sh | 68 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 2a83a19f..245c95a6 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -29,7 +29,7 @@ multideploy_deploy() { fi _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" - _preprocess_deployfile "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" + _preprocess_deployfile "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" || return 1 MULTIDEPLOY_CONFIG="${MULTIDEPLOY_CONFIG:-$(_getdeployconf MULTIDEPLOY_CONFIG)}" if [ -z "$MULTIDEPLOY_CONFIG" ]; then @@ -47,7 +47,7 @@ multideploy_deploy() { # deploy_filepath _preprocess_deployfile() { - deploy_file="$1" + _deploy_file="$1" # Check if yq is installed if ! command -v yq >/dev/null 2>&1; then @@ -56,9 +56,9 @@ _preprocess_deployfile() { fi # Check if deploy file exists and create a default template if not - if [ -f "$deploy_file" ]; then + if [ -f "$_deploy_file" ]; then _debug3 "Deploy file found." - _check_deployfile "$deploy_file" "$MULTIDEPLOY_CONFIG" + _check_deployfile "$_deploy_file" "$MULTIDEPLOY_CONFIG" else # TODO: Replace URL with wiki link _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." @@ -66,48 +66,48 @@ _preprocess_deployfile() { fi } -# deploy_filepath deploy_config +# deploy_filepath _deploy_config _check_deployfile() { - deploy_file="$1" - deploy_config="$3" + _deploy_file="$1" + _deploy_config="$3" # Check version - deploy_file_version=$(yq '.version' "$deploy_file") - if [ "$MULTIDEPLOY_VERSION" != "$deploy_file_version" ]; then - _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $deploy_file_version." + _deploy_file_version=$(yq '.version' "$_deploy_file") + if [ "$MULTIDEPLOY_VERSION" != "$_deploy_file_version" ]; then + _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version." return 1 fi # Check if config exists - if ! yq e ".configs[] | select(.name == \"$deploy_config\")" "$deploy_file" >/dev/null; then - _err "Config '$deploy_config' not found." + if ! yq e ".configs[] | select(.name == \"$_deploy_config\")" "$_deploy_file" >/dev/null; then + _err "Config '$_deploy_config' not found." return 1 fi # Extract all services from config - services=$(_get_services_list "$deploy_file" "$deploy_config") + _services=$(_get_services_list "$_deploy_file" "$_deploy_config") - if [ -z "$services" ]; then - _err "Config '$deploy_config' does not have any services to deploy to." + if [ -z "$_services" ]; then + _err "Config '$_deploy_config' does not have any services to deploy to." return 1 fi # Check if extracted services exist in services list - for service in $services; do - if ! yq e ".services[] | select(.name == \"$service\")" "$deploy_file" >/dev/null; then - _err "Service '$service' not found." + for _service in $_services; do + if ! yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file" >/dev/null; then + _err "Service '$_service' not found." return 1 fi # Check if service has hook - if ! yq e ".services[] | select(.name == \"$service\").hook" "$deploy_file" >/dev/null; then - _err "Service '$service' does not have a hook." + if ! yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file" >/dev/null; then + _err "Service '$_service' does not have a hook." return 1 fi # Check if service has environment - if ! yq e ".services[] | select(.name == \"$service\").environment" "$deploy_file" >/dev/null; then - _err "Service '$service' does not an environment." + if ! yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file" >/dev/null; then + _err "Service '$_service' does not an environment." return 1 fi done @@ -115,26 +115,26 @@ _check_deployfile() { # deploy_filepath deploy_config _get_services_list() { - deploy_file="$1" - deploy_config="$2" + _deploy_file="$1" + _deploy_config="$2" - services=$(yq e ".configs[] | select(.name == \"$deploy_config\").services[]" "$deploy_file") - echo "$services" + _services=$(yq e ".configs[] | select(.name == \"$_deploy_config\").services[]" "$_deploy_file") + echo "$_services" } # deploy_filepath service_names _get_full_services_list() { - deploy_file="$1" + _deploy_file="$1" shift - service_names="$*" + _service_names="$*" - full_services="" - for service in $service_names; do - full_service=$(yq e ".services[] | select(.name == \"$service\")" "$deploy_file") - full_services="$full_services -$full_service" + _full_services="" + for _service in $_service_names; do + _full_service=$(yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file") + _full_services="$_full_services +$_full_service" done - echo "$full_services" + echo "$_full_services" } From 67d58a12e76944389fab15632e078c3c10717b79 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:50:11 +0100 Subject: [PATCH 36/73] implemented handling envs --- deploy/multideploy.sh | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 245c95a6..d1642538 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -138,3 +138,25 @@ $_full_service" echo "$_full_services" } +# env_list +_export_envs() { + _env_list="$1" + + for _env in $_env_list; do + _key=$(echo "$_env" | cut -d '=' -f1) + _value=$(echo "$_env" | cut -d '=' -f2-) + _savedomainconf "$_key" "$_value" + _secure_debug3 "Saved $_key" "$_value" + done +} + +_clear_envs() { + _env_list="$1" + + for _env in $_env_list; do + _key=$(echo "$_env" | cut -d '=' -f1) + _debug3 "Deleting key" "$_key" + _cleardomainconf "SAVED_$_key" + unset "$_key" + done +} From 23e1a53ec8d7b33e44fe81d5cf19534f1a5de296 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:50:35 +0100 Subject: [PATCH 37/73] implemented deploying to services --- deploy/multideploy.sh | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index d1642538..caca02e2 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -40,7 +40,16 @@ multideploy_deploy() { _debug2 "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" fi - # TODO: Deploy to services + # Deploy to services + _services=$(_get_services_list "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_CONFIG") + _full_services=$(_get_full_services_list "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" "$_services") + _deploy_services "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" "$_full_services" + + # Save deployhook for renewals + _debug2 "Setting Le_DeployHook" + _savedomainconf "Le_DeployHook" "multideploy" + + return 0 } #################### Private functions below ##################### @@ -160,3 +169,34 @@ _clear_envs() { unset "$_key" done } + +# deploy_filepath services_array +_deploy_services() { + _deploy_file="$1" + shift + _services="$*" + + for _service in $_services; do + _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") + _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") + _export_envs "$_envs" + _deploy_service "$_service" "$_hook" + _clear_envs "$_envs" + done +} + +_deploy_service() { + _name="$1" + _hook="$2" + + _debug2 "SERVICE" "$_name" + _debug2 "HOOK" "$_hook" + + _info "$(__green "Deploying") to '$_name' using '$_hook'" + if echo "$DOMAIN_PATH" | grep -q "$ECC_SUFFIX"; then + _debug2 "User wants to use ECC." + deploy "$_cdomain" "$_hook" "isEcc" + else + deploy "$_cdomain" "$_hook" + fi +} From 34eb2a655a8da40827478931ce984ad3f265356f Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:50:50 +0100 Subject: [PATCH 38/73] added yq to dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 88edc4a2..4852cde4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN apk --no-cache add -f \ tar \ libidn \ jq \ + yq \ cronie ENV LE_WORKING_DIR=/acmebin From fb0926dc81ccbdcbfc833f07de307f029e4289bc Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:51:02 +0100 Subject: [PATCH 39/73] implemented checking for different kinds of deploy file --- deploy/multideploy.sh | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index caca02e2..9df2c423 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -5,7 +5,8 @@ ######## Public functions ##################### MULTIDEPLOY_VERSION="1.0" -MULTIDEPLOY_FILENAME="multideploy.yaml" +MULTIDEPLOY_FILENAME="multideploy.yml" +MULTIDEPLOY_FILENAME2="multideploy.yaml" # domain keyfile certfile cafile fullchain pfx multideploy_deploy() { @@ -56,18 +57,30 @@ multideploy_deploy() { # deploy_filepath _preprocess_deployfile() { - _deploy_file="$1" - # Check if yq is installed if ! command -v yq >/dev/null 2>&1; then _err "yq is not installed! Please install yq and try again." return 1 fi + _debug3 "yq is installed." - # Check if deploy file exists and create a default template if not - if [ -f "$_deploy_file" ]; then - _debug3 "Deploy file found." - _check_deployfile "$_deploy_file" "$MULTIDEPLOY_CONFIG" + # Check if deploy file exists + for file in "$@"; do + _debug3 "Checking file" "$DOMAIN_PATH/$file" + if [ -f "$DOMAIN_PATH/$file" ]; then + _debug3 "File found" + if [ -n "$found_file" ]; then + _err "Multiple deploy files found. Please keep only one deploy file." + return 1 + fi + found_file="$file" + else + _debug3 "File not found" + fi + done + + if [ -n "$found_file" ]; then + _check_deployfile "$DOMAIN_PATH/$found_file" "$MULTIDEPLOY_CONFIG" else # TODO: Replace URL with wiki link _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." From db1dc4de0deb66b75bf83a4b1d6a274b7b5e6f09 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:52:02 +0100 Subject: [PATCH 40/73] added debug messages --- deploy/multideploy.sh | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 9df2c423..3c243c04 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -30,8 +30,6 @@ multideploy_deploy() { fi _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" - _preprocess_deployfile "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" || return 1 - MULTIDEPLOY_CONFIG="${MULTIDEPLOY_CONFIG:-$(_getdeployconf MULTIDEPLOY_CONFIG)}" if [ -z "$MULTIDEPLOY_CONFIG" ]; then MULTIDEPLOY_CONFIG="default" @@ -91,7 +89,10 @@ _preprocess_deployfile() { # deploy_filepath _deploy_config _check_deployfile() { _deploy_file="$1" - _deploy_config="$3" + _deploy_config="$2" + + _debug2 "Deploy file" "$_deploy_file" + _debug2 "Deploy config" "$_deploy_config" # Check version _deploy_file_version=$(yq '.version' "$_deploy_file") @@ -99,23 +100,29 @@ _check_deployfile() { _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version." return 1 fi + _debug2 "Deploy file version is compatible: $_deploy_file_version" # Check if config exists if ! yq e ".configs[] | select(.name == \"$_deploy_config\")" "$_deploy_file" >/dev/null; then _err "Config '$_deploy_config' not found." return 1 fi + _debug2 "Config found: $_deploy_config" # Extract all services from config _services=$(_get_services_list "$_deploy_file" "$_deploy_config") + _debug2 "Services" "$_services" if [ -z "$_services" ]; then _err "Config '$_deploy_config' does not have any services to deploy to." return 1 fi + _debug2 "Config has services." # Check if extracted services exist in services list for _service in $_services; do + _debug2 "Checking service" "$_service" + # Check if service exists if ! yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file" >/dev/null; then _err "Service '$_service' not found." return 1 @@ -140,6 +147,10 @@ _get_services_list() { _deploy_file="$1" _deploy_config="$2" + _debug2 "Getting services list" + _debug3 "Deploy file" "$_deploy_file" + _debug3 "Deploy config" "$_deploy_config" + _services=$(yq e ".configs[] | select(.name == \"$_deploy_config\").services[]" "$_deploy_file") echo "$_services" } @@ -150,6 +161,9 @@ _get_full_services_list() { shift _service_names="$*" + _debug3 "Deploy file" "$_deploy_file" + _debug3 "Service names" "$_service_names" + _full_services="" for _service in $_service_names; do _full_service=$(yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file") @@ -164,6 +178,8 @@ $_full_service" _export_envs() { _env_list="$1" + _secure_debug3 "Exporting envs" "$_env_list" + for _env in $_env_list; do _key=$(echo "$_env" | cut -d '=' -f1) _value=$(echo "$_env" | cut -d '=' -f2-) @@ -175,6 +191,8 @@ _export_envs() { _clear_envs() { _env_list="$1" + _secure_debug3 "Clearing envs" "$_env_list" + for _env in $_env_list; do _key=$(echo "$_env" | cut -d '=' -f1) _debug3 "Deleting key" "$_key" @@ -189,6 +207,9 @@ _deploy_services() { shift _services="$*" + _debug3 "Deploy file" "$_deploy_file" + _debug3 "Services" "$_services" + for _service in $_services; do _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") From 768de270bf6943047f3f52d38b0af28c423dd765 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:23:13 +0100 Subject: [PATCH 41/73] improved preprocessing and fixed bug with wrong param of services --- deploy/multideploy.sh | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 3c243c04..7b4e1400 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -39,10 +39,13 @@ multideploy_deploy() { _debug2 "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" fi + OLDIFS=$IFS + file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME2") || return 1 + _debug3 "File" "$file" + # Deploy to services - _services=$(_get_services_list "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_CONFIG") - _full_services=$(_get_full_services_list "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" "$_services") - _deploy_services "$DOMAIN_DIR/$MULTIDEPLOY_FILENAME" "$_full_services" + _services=$(_get_services_list "$file" "$MULTIDEPLOY_CONFIG") + _deploy_services "$file" "$_services" # Save deployhook for renewals _debug2 "Setting Le_DeployHook" @@ -84,6 +87,8 @@ _preprocess_deployfile() { _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." return 1 fi + + echo "$DOMAIN_PATH/$found_file" } # deploy_filepath _deploy_config @@ -155,25 +160,6 @@ _get_services_list() { echo "$_services" } -# deploy_filepath service_names -_get_full_services_list() { - _deploy_file="$1" - shift - _service_names="$*" - - _debug3 "Deploy file" "$_deploy_file" - _debug3 "Service names" "$_service_names" - - _full_services="" - for _service in $_service_names; do - _full_service=$(yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file") - _full_services="$_full_services -$_full_service" - done - - echo "$_full_services" -} - # env_list _export_envs() { _env_list="$1" @@ -211,6 +197,7 @@ _deploy_services() { _debug3 "Services" "$_services" for _service in $_services; do + _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") _export_envs "$_envs" From ba7c368ee535c198187320a740d5da816126c51b Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:23:29 +0100 Subject: [PATCH 42/73] fixed IFS problems --- deploy/multideploy.sh | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 7b4e1400..9e2fb1d2 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -66,6 +66,7 @@ _preprocess_deployfile() { _debug3 "yq is installed." # Check if deploy file exists + IFS=$(printf '\n') for file in "$@"; do _debug3 "Checking file" "$DOMAIN_PATH/$file" if [ -f "$DOMAIN_PATH/$file" ]; then @@ -79,6 +80,7 @@ _preprocess_deployfile() { _debug3 "File not found" fi done + IFS=$OLDIFS if [ -n "$found_file" ]; then _check_deployfile "$DOMAIN_PATH/$found_file" "$MULTIDEPLOY_CONFIG" @@ -124,6 +126,7 @@ _check_deployfile() { fi _debug2 "Config has services." + IFS=$(printf '\n') # Check if extracted services exist in services list for _service in $_services; do _debug2 "Checking service" "$_service" @@ -145,6 +148,7 @@ _check_deployfile() { return 1 fi done + IFS=$OLDIFS } # deploy_filepath deploy_config @@ -166,25 +170,27 @@ _export_envs() { _secure_debug3 "Exporting envs" "$_env_list" - for _env in $_env_list; do - _key=$(echo "$_env" | cut -d '=' -f1) - _value=$(echo "$_env" | cut -d '=' -f2-) + IFS=$(printf '\n') + echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do _savedomainconf "$_key" "$_value" _secure_debug3 "Saved $_key" "$_value" done + IFS=$OLDIFS } _clear_envs() { _env_list="$1" _secure_debug3 "Clearing envs" "$_env_list" + env_pairs=$(echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value') - for _env in $_env_list; do - _key=$(echo "$_env" | cut -d '=' -f1) + IFS=$(printf '\n') + echo "$env_pairs" | while IFS='=' read -r _key _value; do _debug3 "Deleting key" "$_key" _cleardomainconf "SAVED_$_key" unset "$_key" done + IFS="$OLDIFS" } # deploy_filepath services_array @@ -196,14 +202,17 @@ _deploy_services() { _debug3 "Deploy file" "$_deploy_file" _debug3 "Services" "$_services" + IFS=$(printf '\n') for _service in $_services; do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") + _export_envs "$_envs" _deploy_service "$_service" "$_hook" _clear_envs "$_envs" done + IFS=$OLDIFS } _deploy_service() { From 2cc5e66517908eed21bbc03ba5066919dd27365e Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:31:24 +0100 Subject: [PATCH 43/73] added docs --- deploy/multideploy.sh | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 9e2fb1d2..129bb38f 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -151,7 +151,13 @@ _check_deployfile() { IFS=$OLDIFS } -# deploy_filepath deploy_config +# Description: +# This function retrieves a list of services from the deploy configuration file. +# Arguments: +# $1 - The path to the deploy configuration file. +# $2 - The name of the deploy configuration to use. +# Usage: +# _get_services_list "" "" _get_services_list() { _deploy_file="$1" _deploy_config="$2" @@ -164,7 +170,12 @@ _get_services_list() { echo "$_services" } -# env_list +# Description: This function takes a list of environment variables in YAML format, +# parses them, and exports each key-value pair as environment variables. +# Arguments: +# $1 - A string containing the list of environment variables in YAML format. +# Usage: +# _export_envs "$env_list" _export_envs() { _env_list="$1" @@ -178,6 +189,13 @@ _export_envs() { IFS=$OLDIFS } +# Description: +# This function takes a YAML formatted string of environment variables, parses it, +# and clears each environment variable. It logs the process of clearing each variable. +# Arguments: +# $1 - A YAML formatted string containing environment variable key-value pairs. +# Usage: +# _clear_envs "" _clear_envs() { _env_list="$1" @@ -188,7 +206,7 @@ _clear_envs() { echo "$env_pairs" | while IFS='=' read -r _key _value; do _debug3 "Deleting key" "$_key" _cleardomainconf "SAVED_$_key" - unset "$_key" + unset -v "$_key" done IFS="$OLDIFS" } From 74ed0354a3f135884b37c5b5adf3557a22afb2d2 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 18:44:46 +0100 Subject: [PATCH 44/73] added docs and enhanced log messages --- deploy/multideploy.sh | 57 +++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 129bb38f..81754f1c 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -2,13 +2,21 @@ # MULTIDEPLOY_CONFIG="default" -######## Public functions ##################### - MULTIDEPLOY_VERSION="1.0" MULTIDEPLOY_FILENAME="multideploy.yml" MULTIDEPLOY_FILENAME2="multideploy.yaml" -# domain keyfile certfile cafile fullchain pfx +# Description: This function handles the deployment of certificates to multiple services. +# It processes the provided certificate files and deploys them according to the +# configuration specified in the MULTIDEPLOY_CONFIG. +# +# Parameters: +# _cdomain - The domain name for which the certificate is issued. +# _ckey - The private key file for the certificate. +# _ccert - The certificate file. +# _cca - The CA (Certificate Authority) file. +# _cfullchain - The full chain certificate file. +# _cpfx - The PFX (Personal Information Exchange) file. multideploy_deploy() { _cdomain="$1" _ckey="$2" @@ -40,7 +48,10 @@ multideploy_deploy() { fi OLDIFS=$IFS - file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME2") || return 1 + if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME2"); then + _err "Failed to preprocess deploy file." + return 1 + fi _debug3 "File" "$file" # Deploy to services @@ -54,9 +65,13 @@ multideploy_deploy() { return 0 } -#################### Private functions below ##################### - -# deploy_filepath +# Description: +# This function preprocesses the deploy file by checking if 'yq' is installed, +# verifying the existence of the deploy file, and ensuring only one deploy file is present. +# Arguments: +# $@ - Posible deploy file names. +# Usage: +# _preprocess_deployfile "" "" _preprocess_deployfile() { # Check if yq is installed if ! command -v yq >/dev/null 2>&1; then @@ -93,7 +108,13 @@ _preprocess_deployfile() { echo "$DOMAIN_PATH/$found_file" } -# deploy_filepath _deploy_config +# Description: +# This function checks the deploy file for version compatibility and the existence of the specified configuration and services. +# Arguments: +# $1 - The path to the deploy configuration file. +# $2 - The name of the deploy configuration to use. +# Usage: +# _check_deployfile "" "" _check_deployfile() { _deploy_file="$1" _deploy_config="$2" @@ -144,7 +165,7 @@ _check_deployfile() { # Check if service has environment if ! yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file" >/dev/null; then - _err "Service '$_service' does not an environment." + _err "Service '$_service' does not have an environment." return 1 fi done @@ -211,7 +232,13 @@ _clear_envs() { IFS="$OLDIFS" } -# deploy_filepath services_array +# Description: +# This function deploys services listed in the deploy configuration file. +# Arguments: +# $1 - The path to the deploy configuration file. +# $2 - The list of services to deploy. +# Usage: +# _deploy_services "" "" _deploy_services() { _deploy_file="$1" shift @@ -220,8 +247,7 @@ _deploy_services() { _debug3 "Deploy file" "$_deploy_file" _debug3 "Services" "$_services" - IFS=$(printf '\n') - for _service in $_services; do + printf '%s\n' "$_services" | while IFS= read -r _service; do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") @@ -230,9 +256,14 @@ _deploy_services() { _deploy_service "$_service" "$_hook" _clear_envs "$_envs" done - IFS=$OLDIFS } +# Description: Deploys a service using the specified hook. +# Arguments: +# $1 - The name of the service to deploy. +# $2 - The hook to use for deployment. +# Usage: +# _deploy_service _deploy_service() { _name="$1" _hook="$2" From 88d4637ee31e4d27a6e9978d8bfb1b52e55ef8d7 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:02:55 +0100 Subject: [PATCH 45/73] added header doc --- deploy/multideploy.sh | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 81754f1c..bea4622b 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -1,6 +1,25 @@ #!/usr/bin/env sh -# MULTIDEPLOY_CONFIG="default" +################################################################################ +# ACME.sh 3rd party deploy plugin for multiple (same) services +################################################################################ +# Authors: tomo2403 (creator), https://github.com/tomo2403 +# Updated: 2024-07-03 +# Issues: https://github.com/acmesh-official/acme.sh/issues/XXXXX +################################################################################ +# Usage (shown values are the examples): +# 1. Set optional environment variables +# - export MULTIDEPLOY_CONFIG="default" - "default" will be automatically used if not set" +# +# 2. Run command: +# acme.sh --deploy --deploy-hook multideploy -d example.com +################################################################################ +# Dependencies: +# - yq +################################################################################ +# Return value: +# 0 means success, otherwise error. +################################################################################ MULTIDEPLOY_VERSION="1.0" MULTIDEPLOY_FILENAME="multideploy.yml" From c16e059535b873a46f6ee2a47f8810843f0b2292 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:15:42 +0100 Subject: [PATCH 46/73] allowed using varaibles in deploy file --- deploy/multideploy.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index bea4622b..58df0482 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -223,6 +223,7 @@ _export_envs() { IFS=$(printf '\n') echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do + _value=$(eval echo "$_value") _savedomainconf "$_key" "$_value" _secure_debug3 "Saved $_key" "$_value" done From 88cde7be6db1901932f9458f624b0362f348edd4 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:26:56 +0100 Subject: [PATCH 47/73] fixed missing wiki link --- deploy/multideploy.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 58df0482..9e3f4e17 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -119,8 +119,7 @@ _preprocess_deployfile() { if [ -n "$found_file" ]; then _check_deployfile "$DOMAIN_PATH/$found_file" "$MULTIDEPLOY_CONFIG" else - # TODO: Replace URL with wiki link - _err "Deploy file not found. Go to https://CHANGE_URL_TO_WIKI to see how to create one." + _err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one." return 1 fi From c1e17c366fd367cc68a3b89d9eca2abd161cbbde Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Sat, 1 Mar 2025 13:26:53 +0100 Subject: [PATCH 48/73] Update links in multideploy.sh --- deploy/multideploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 9e3f4e17..b002f068 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -4,8 +4,8 @@ # ACME.sh 3rd party deploy plugin for multiple (same) services ################################################################################ # Authors: tomo2403 (creator), https://github.com/tomo2403 -# Updated: 2024-07-03 -# Issues: https://github.com/acmesh-official/acme.sh/issues/XXXXX +# Updated: 2025-03-01 +# Issues: https://github.com/acmesh-official/acme.sh/issues and mention @tomo2403 ################################################################################ # Usage (shown values are the examples): # 1. Set optional environment variables From 95c75460513f8faa5cdfbee869cc70d0354c9bce Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:11:31 +0100 Subject: [PATCH 49/73] removed configs and implemented specification of deploy file name --- deploy/multideploy.sh | 67 +++++++++++-------------------------------- 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index b002f068..24177e66 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -9,7 +9,7 @@ ################################################################################ # Usage (shown values are the examples): # 1. Set optional environment variables -# - export MULTIDEPLOY_CONFIG="default" - "default" will be automatically used if not set" +# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set" # # 2. Run command: # acme.sh --deploy --deploy-hook multideploy -d example.com @@ -22,12 +22,10 @@ ################################################################################ MULTIDEPLOY_VERSION="1.0" -MULTIDEPLOY_FILENAME="multideploy.yml" -MULTIDEPLOY_FILENAME2="multideploy.yaml" # Description: This function handles the deployment of certificates to multiple services. # It processes the provided certificate files and deploys them according to the -# configuration specified in the MULTIDEPLOY_CONFIG. +# configuration specified in the multideploy file. # # Parameters: # _cdomain - The domain name for which the certificate is issued. @@ -57,25 +55,24 @@ multideploy_deploy() { fi _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" - MULTIDEPLOY_CONFIG="${MULTIDEPLOY_CONFIG:-$(_getdeployconf MULTIDEPLOY_CONFIG)}" - if [ -z "$MULTIDEPLOY_CONFIG" ]; then - MULTIDEPLOY_CONFIG="default" - _info "MULTIDEPLOY_CONFIG is not set, so I will use 'default'." + MULTIDEPLOY_FILENAME="${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}" + if [ -z "$MULTIDEPLOY_FILENAME" ]; then + MULTIDEPLOY_FILENAME="multideploy.yml" + _info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'." else - _savedeployconf "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" - _debug2 "MULTIDEPLOY_CONFIG" "$MULTIDEPLOY_CONFIG" + _savedeployconf "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME" + _debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME" fi OLDIFS=$IFS - if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME2"); then + if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then _err "Failed to preprocess deploy file." return 1 fi _debug3 "File" "$file" # Deploy to services - _services=$(_get_services_list "$file" "$MULTIDEPLOY_CONFIG") - _deploy_services "$file" "$_services" + _deploy_services "$file" # Save deployhook for renewals _debug2 "Setting Le_DeployHook" @@ -90,7 +87,7 @@ multideploy_deploy() { # Arguments: # $@ - Posible deploy file names. # Usage: -# _preprocess_deployfile "" "" +# _preprocess_deployfile "" "?" _preprocess_deployfile() { # Check if yq is installed if ! command -v yq >/dev/null 2>&1; then @@ -117,7 +114,7 @@ _preprocess_deployfile() { IFS=$OLDIFS if [ -n "$found_file" ]; then - _check_deployfile "$DOMAIN_PATH/$found_file" "$MULTIDEPLOY_CONFIG" + _check_deployfile "$DOMAIN_PATH/$found_file" else _err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one." return 1 @@ -132,13 +129,10 @@ _preprocess_deployfile() { # $1 - The path to the deploy configuration file. # $2 - The name of the deploy configuration to use. # Usage: -# _check_deployfile "" "" +# _check_deployfile "" _check_deployfile() { _deploy_file="$1" - _deploy_config="$2" - _debug2 "Deploy file" "$_deploy_file" - _debug2 "Deploy config" "$_deploy_config" # Check version _deploy_file_version=$(yq '.version' "$_deploy_file") @@ -148,19 +142,12 @@ _check_deployfile() { fi _debug2 "Deploy file version is compatible: $_deploy_file_version" - # Check if config exists - if ! yq e ".configs[] | select(.name == \"$_deploy_config\")" "$_deploy_file" >/dev/null; then - _err "Config '$_deploy_config' not found." - return 1 - fi - _debug2 "Config found: $_deploy_config" - # Extract all services from config - _services=$(_get_services_list "$_deploy_file" "$_deploy_config") + _services=$(yq e '.services[].name' "$_deploy_file") _debug2 "Services" "$_services" if [ -z "$_services" ]; then - _err "Config '$_deploy_config' does not have any services to deploy to." + _err "Config does not have any services to deploy to." return 1 fi _debug2 "Config has services." @@ -190,25 +177,6 @@ _check_deployfile() { IFS=$OLDIFS } -# Description: -# This function retrieves a list of services from the deploy configuration file. -# Arguments: -# $1 - The path to the deploy configuration file. -# $2 - The name of the deploy configuration to use. -# Usage: -# _get_services_list "" "" -_get_services_list() { - _deploy_file="$1" - _deploy_config="$2" - - _debug2 "Getting services list" - _debug3 "Deploy file" "$_deploy_file" - _debug3 "Deploy config" "$_deploy_config" - - _services=$(yq e ".configs[] | select(.name == \"$_deploy_config\").services[]" "$_deploy_file") - echo "$_services" -} - # Description: This function takes a list of environment variables in YAML format, # parses them, and exports each key-value pair as environment variables. # Arguments: @@ -260,10 +228,9 @@ _clear_envs() { # _deploy_services "" "" _deploy_services() { _deploy_file="$1" - shift - _services="$*" - _debug3 "Deploy file" "$_deploy_file" + + _services=$(yq e '.services[].name' "$_deploy_file") _debug3 "Services" "$_services" printf '%s\n' "$_services" | while IFS= read -r _service; do From 17e0bbcbb67c32fff1cb620d08a0c1b277565332 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:16:07 +0100 Subject: [PATCH 50/73] fixed formatting --- deploy/multideploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 24177e66..c297878c 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -229,7 +229,7 @@ _clear_envs() { _deploy_services() { _deploy_file="$1" _debug3 "Deploy file" "$_deploy_file" - + _services=$(yq e '.services[].name' "$_deploy_file") _debug3 "Services" "$_services" From 88e4d64c1a242342a60f7aa1bddd4ba842c5b500 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Thu, 10 Apr 2025 10:24:32 +0200 Subject: [PATCH 51/73] fixed IFS problems for some hooks --- deploy/multideploy.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index c297878c..b728ee77 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -233,7 +233,9 @@ _deploy_services() { _services=$(yq e '.services[].name' "$_deploy_file") _debug3 "Services" "$_services" - printf '%s\n' "$_services" | while IFS= read -r _service; do + _service_list=$(printf '%s\n' "$_services") + + for _service in $(printf '%s\n' "$_service_list"); do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") From c1c49d5a01e929d326b8a7f4a8fb2d3780f6e8ec Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:38:28 +0200 Subject: [PATCH 52/73] simplified deploy method --- deploy/multideploy.sh | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index b728ee77..93809e6e 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -260,10 +260,5 @@ _deploy_service() { _debug2 "HOOK" "$_hook" _info "$(__green "Deploying") to '$_name' using '$_hook'" - if echo "$DOMAIN_PATH" | grep -q "$ECC_SUFFIX"; then - _debug2 "User wants to use ECC." - deploy "$_cdomain" "$_hook" "isEcc" - else - deploy "$_cdomain" "$_hook" - fi + _deploy "$_cdomain" "$_hook" } From a55d40be972e7ebd4f64599211b0e861ed2f83e9 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Tue, 27 May 2025 22:07:22 +0200 Subject: [PATCH 53/73] fixed bug with envs due to the use of a wrong function --- deploy/multideploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 93809e6e..c77d1fdf 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -191,7 +191,7 @@ _export_envs() { IFS=$(printf '\n') echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do _value=$(eval echo "$_value") - _savedomainconf "$_key" "$_value" + _savedeployconf "$_key" "$_value" _secure_debug3 "Saved $_key" "$_value" done IFS=$OLDIFS From e5b47f6402567d75db58edcb5b4b67194de58202 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Wed, 28 May 2025 20:02:07 +0200 Subject: [PATCH 54/73] implemented exiting with 1 if at least one deployment fails --- deploy/multideploy.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index c77d1fdf..5b323303 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -72,13 +72,17 @@ multideploy_deploy() { _debug3 "File" "$file" # Deploy to services - _deploy_services "$file" + if _deploy_services "$file"; then + _deploymentOk=0 + else + _deploymentOk=1 + fi # Save deployhook for renewals _debug2 "Setting Le_DeployHook" _savedomainconf "Le_DeployHook" "multideploy" - return 0 + return "$_deploymentOk" } # Description: @@ -235,15 +239,26 @@ _deploy_services() { _service_list=$(printf '%s\n' "$_services") + _errors="" for _service in $(printf '%s\n' "$_service_list"); do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") _export_envs "$_envs" - _deploy_service "$_service" "$_hook" + if ! _deploy_service "$_service" "$_hook"; then + _errors="$_service, $_errors" + fi _clear_envs "$_envs" done + + if [ -n "$_errors" ]; then + _err "Deployment failed for services: $_errors" + return 1 + else + _debug "All services deployed successfully." + return 0 + fi } # Description: Deploys a service using the specified hook. @@ -261,4 +276,5 @@ _deploy_service() { _info "$(__green "Deploying") to '$_name' using '$_hook'" _deploy "$_cdomain" "$_hook" + return $? } From 093f36b4d670a03b571ea68f28b4d180ad6f6261 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Wed, 28 May 2025 20:19:18 +0200 Subject: [PATCH 55/73] implemented exiting with the number of failed deployments --- deploy/multideploy.sh | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 5b323303..fd04043a 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -72,17 +72,14 @@ multideploy_deploy() { _debug3 "File" "$file" # Deploy to services - if _deploy_services "$file"; then - _deploymentOk=0 - else - _deploymentOk=1 - fi + _deploy_services "$file" + _exitCode="$?" # Save deployhook for renewals _debug2 "Setting Le_DeployHook" _savedomainconf "Le_DeployHook" "multideploy" - return "$_deploymentOk" + return "$_exitCode" } # Description: @@ -239,7 +236,8 @@ _deploy_services() { _service_list=$(printf '%s\n' "$_services") - _errors="" + _failedServices="" + _failedCount=0 for _service in $(printf '%s\n' "$_service_list"); do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") @@ -247,18 +245,21 @@ _deploy_services() { _export_envs "$_envs" if ! _deploy_service "$_service" "$_hook"; then - _errors="$_service, $_errors" + _failedServices="$_service, $_failedServices" + _failedCount=$((_failedCount + 1)) fi _clear_envs "$_envs" done - if [ -n "$_errors" ]; then - _err "Deployment failed for services: $_errors" - return 1 + _debug3 "Failed services" "$_failedServices" + _debug2 "Failed count" "$_failedCount" + if [ -n "$_failedServices" ]; then + _info "$(__red "Deployment failed") for services: $_failedServices" else _debug "All services deployed successfully." - return 0 fi + + return "$_failedCount" } # Description: Deploys a service using the specified hook. From 7b16526e7f0f217a4d0e02505d32f4260e884c40 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Sat, 31 May 2025 19:21:10 +0200 Subject: [PATCH 56/73] removed dead code --- deploy/multideploy.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index fd04043a..85a8dfd4 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -49,12 +49,6 @@ multideploy_deploy() { _debug _cfullchain "$_cfullchain" _debug _cpfx "$_cpfx" - DOMAIN_DIR=$_cdomain - if echo "$DOMAIN_PATH" | grep -q "$ECC_SUFFIX"; then - DOMAIN_DIR="$DOMAIN_DIR"_ecc - fi - _debug2 "DOMAIN_DIR" "$DOMAIN_DIR" - MULTIDEPLOY_FILENAME="${MULTIDEPLOY_FILENAME:-$(_getdeployconf MULTIDEPLOY_FILENAME)}" if [ -z "$MULTIDEPLOY_FILENAME" ]; then MULTIDEPLOY_FILENAME="multideploy.yml" From 37c25aa107e0c05e2ddc4131cc0d9efc0dad5971 Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Sat, 31 May 2025 20:19:49 +0200 Subject: [PATCH 57/73] removed unneeded return value --- deploy/multideploy.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 85a8dfd4..88a2c6cc 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -271,5 +271,4 @@ _deploy_service() { _info "$(__green "Deploying") to '$_name' using '$_hook'" _deploy "$_cdomain" "$_hook" - return $? } From d375012c5db3d688daba40425430778ac0106aaf Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:02:50 +0200 Subject: [PATCH 58/73] fixed yml file env list --- deploy/multideploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 88a2c6cc..9ab16d0d 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -235,7 +235,7 @@ _deploy_services() { for _service in $(printf '%s\n' "$_service_list"); do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") - _envs=$(yq e ".services[] | select(.name == \"$_service\").environment[]" "$_deploy_file") + _envs=$(yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file") _export_envs "$_envs" if ! _deploy_service "$_service" "$_hook"; then From f850e8d0e466514262ecb6ee6d051da228dad115 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 13:45:34 +0100 Subject: [PATCH 59/73] Support spaces in service names - Prefer using a pipe to `while read` - But use a temp file when the loop needs to modify variables that need to be used outside the loop, as the pipe creates a subshell and modifications do not survive after the loop exits. --- deploy/multideploy.sh | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 9ab16d0d..f5d6f587 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -147,9 +147,8 @@ _check_deployfile() { fi _debug2 "Config has services." - IFS=$(printf '\n') # Check if extracted services exist in services list - for _service in $_services; do + echo "$_services" | while read -r _service; do _debug2 "Checking service" "$_service" # Check if service exists if ! yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file" >/dev/null; then @@ -169,7 +168,6 @@ _check_deployfile() { return 1 fi done - IFS=$OLDIFS } # Description: This function takes a list of environment variables in YAML format, @@ -225,14 +223,15 @@ _deploy_services() { _deploy_file="$1" _debug3 "Deploy file" "$_deploy_file" - _services=$(yq e '.services[].name' "$_deploy_file") - _debug3 "Services" "$_services" + _tempfile=$(mktemp) + trap "rm -f $_tempfile" EXIT - _service_list=$(printf '%s\n' "$_services") + yq e '.services[].name' "$_deploy_file" > $_tempfile + _debug3 "Services" "$(cat $_tempfile)" _failedServices="" _failedCount=0 - for _service in $(printf '%s\n' "$_service_list"); do + while read -r _service; do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file") @@ -243,7 +242,7 @@ _deploy_services() { _failedCount=$((_failedCount + 1)) fi _clear_envs "$_envs" - done + done < "$_tempfile" _debug3 "Failed services" "$_failedServices" _debug2 "Failed count" "$_failedCount" From 986a6138ebda928297a859f12ab72f2b040d7e27 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:19:44 +0100 Subject: [PATCH 60/73] Fix config file checks The config file checks were returning okay even when there were errors. The yq tool returns "null" when it cannot find what's queried, but exists with a 0 rc still. --- deploy/multideploy.sh | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index f5d6f587..5ed62ec3 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -127,7 +127,7 @@ _preprocess_deployfile() { # _check_deployfile "" _check_deployfile() { _deploy_file="$1" - _debug2 "Deploy file" "$_deploy_file" + _debug2 "check: Deploy file" "$_deploy_file" # Check version _deploy_file_version=$(yq '.version' "$_deploy_file") @@ -135,38 +135,44 @@ _check_deployfile() { _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version." return 1 fi - _debug2 "Deploy file version is compatible: $_deploy_file_version" + _debug2 "check: Deploy file version is compatible: $_deploy_file_version" # Extract all services from config _services=$(yq e '.services[].name' "$_deploy_file") - _debug2 "Services" "$_services" if [ -z "$_services" ]; then _err "Config does not have any services to deploy to." return 1 fi - _debug2 "Config has services." + _debug2 "check: Config has services." + echo "$_services" | while read -r _service; do + _debug3 " - $_service" + done # Check if extracted services exist in services list echo "$_services" | while read -r _service; do - _debug2 "Checking service" "$_service" + _debug2 "check: Checking service: $_service" # Check if service exists - if ! yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file" >/dev/null; then + _service_config=$(yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file") + if [ -z "$_service_config" ] || [ "$_service_config" = "null" ]; then _err "Service '$_service' not found." return 1 fi + _secure_debug3 "check: Service '$_service' configuration" "$_service_config" - # Check if service has hook - if ! yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file" >/dev/null; then + _service_hook=$(echo "$_service_config" | yq e ".hook" -) + if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then _err "Service '$_service' does not have a hook." return 1 fi + _debug3 "check: Service '$_service' hook" "$_service_hook" - # Check if service has environment - if ! yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file" >/dev/null; then + _service_environment=$(echo "$_service_config" | yq e ".environment" -) + if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then _err "Service '$_service' does not have an environment." return 1 fi + _secure_debug3 "check: Service '$_service' environment" "$_service_environment" done } From 1d8788767f0173423a03201ece84562268beccb9 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:20:38 +0100 Subject: [PATCH 61/73] Make failure to check file stop the deployment Before this, checker issues were only logged. This stops the deployment if any configuration is incorrect. --- deploy/multideploy.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 5ed62ec3..18b6d7ce 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -108,12 +108,15 @@ _preprocess_deployfile() { done IFS=$OLDIFS - if [ -n "$found_file" ]; then - _check_deployfile "$DOMAIN_PATH/$found_file" - else + if [ -z "$found_file" ]; + then _err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one." return 1 fi + if ! _check_deployfile "$DOMAIN_PATH/$found_file"; then + _err "Deploy file is not valid: $DOMAIN_PATH/$found_file" + return 1 + fi echo "$DOMAIN_PATH/$found_file" } From 1eee4dee9cfd165587f8a95104b5f4251c440416 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:22:21 +0100 Subject: [PATCH 62/73] Update dependency name from yq to yq-go --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4852cde4..36b2adac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN apk --no-cache add -f \ tar \ libidn \ jq \ - yq \ + yq-go \ cronie ENV LE_WORKING_DIR=/acmebin From a961e03a597537f09ce9475bd9609ddb858d3773 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:32:52 +0100 Subject: [PATCH 63/73] Explain the use of eval --- deploy/multideploy.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 18b6d7ce..8c6f4267 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -192,6 +192,7 @@ _export_envs() { IFS=$(printf '\n') echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do + # Using eval to expand nested variables in the configuration file _value=$(eval echo "$_value") _savedeployconf "$_key" "$_value" _secure_debug3 "Saved $_key" "$_value" From 69dd2cf78b71d1e616011428b5500a15f463f9d3 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:36:07 +0100 Subject: [PATCH 64/73] Explain _clear_envs rationale --- deploy/multideploy.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 8c6f4267..8a2fbd37 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -203,6 +203,14 @@ _export_envs() { # Description: # This function takes a YAML formatted string of environment variables, parses it, # and clears each environment variable. It logs the process of clearing each variable. +# +# Note: Environment variables for a hook may be optional and differ between +# services using the same hook. +# If one service sets optional environment variables and another does not, the +# variables may persist and affect subsequent deployments. +# Clearing these variables after each service ensures that only the +# environment variables explicitly specified for each service in the deploy +# file are used. # Arguments: # $1 - A YAML formatted string containing environment variable key-value pairs. # Usage: From 8a788651745b39ba15d8b6a078180822f91cd1da Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:41:12 +0100 Subject: [PATCH 65/73] Quote paths to prevent word splitting --- deploy/multideploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 8a2fbd37..2db9a758 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -244,8 +244,8 @@ _deploy_services() { _tempfile=$(mktemp) trap "rm -f $_tempfile" EXIT - yq e '.services[].name' "$_deploy_file" > $_tempfile - _debug3 "Services" "$(cat $_tempfile)" + yq e '.services[].name' "$_deploy_file" > "$_tempfile" + _debug3 "Services" "$(cat "$_tempfile")" _failedServices="" _failedCount=0 From 4f0a4850a68ed0b7882f463fe755516fee4b1ecc Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 19:43:49 +0100 Subject: [PATCH 66/73] Remove unnecessary resetting of IFS --- deploy/multideploy.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 2db9a758..eec7b8ac 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -58,7 +58,6 @@ multideploy_deploy() { _debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME" fi - OLDIFS=$IFS if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then _err "Failed to preprocess deploy file." return 1 @@ -92,7 +91,6 @@ _preprocess_deployfile() { _debug3 "yq is installed." # Check if deploy file exists - IFS=$(printf '\n') for file in "$@"; do _debug3 "Checking file" "$DOMAIN_PATH/$file" if [ -f "$DOMAIN_PATH/$file" ]; then @@ -106,7 +104,6 @@ _preprocess_deployfile() { _debug3 "File not found" fi done - IFS=$OLDIFS if [ -z "$found_file" ]; then @@ -190,14 +187,12 @@ _export_envs() { _secure_debug3 "Exporting envs" "$_env_list" - IFS=$(printf '\n') echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do # Using eval to expand nested variables in the configuration file _value=$(eval echo "$_value") _savedeployconf "$_key" "$_value" _secure_debug3 "Saved $_key" "$_value" done - IFS=$OLDIFS } # Description: @@ -221,13 +216,11 @@ _clear_envs() { _secure_debug3 "Clearing envs" "$_env_list" env_pairs=$(echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value') - IFS=$(printf '\n') echo "$env_pairs" | while IFS='=' read -r _key _value; do _debug3 "Deleting key" "$_key" _cleardomainconf "SAVED_$_key" unset -v "$_key" done - IFS="$OLDIFS" } # Description: From 6b66e734a92d1d6a5b2bfedeb81ded69f04d02ec Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Sun, 31 Aug 2025 20:02:56 +0100 Subject: [PATCH 67/73] Remove explicit save of the deployhook acme.sh takes care of that --- deploy/multideploy.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index eec7b8ac..e1b8bf87 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -68,10 +68,6 @@ multideploy_deploy() { _deploy_services "$file" _exitCode="$?" - # Save deployhook for renewals - _debug2 "Setting Le_DeployHook" - _savedomainconf "Le_DeployHook" "multideploy" - return "$_exitCode" } From b8b1f1e9b40765dbe917f510e205a5f81f70b0f9 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Mon, 1 Sep 2025 00:04:42 +0100 Subject: [PATCH 68/73] Remove config logging when checking Because it causes a mysterious crash and it's honestly not worth it. --- deploy/multideploy.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index e1b8bf87..2d2a7a37 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -154,21 +154,18 @@ _check_deployfile() { _err "Service '$_service' not found." return 1 fi - _secure_debug3 "check: Service '$_service' configuration" "$_service_config" _service_hook=$(echo "$_service_config" | yq e ".hook" -) if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then _err "Service '$_service' does not have a hook." return 1 fi - _debug3 "check: Service '$_service' hook" "$_service_hook" _service_environment=$(echo "$_service_config" | yq e ".environment" -) if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then _err "Service '$_service' does not have an environment." return 1 fi - _secure_debug3 "check: Service '$_service' environment" "$_service_environment" done } From ab7835ec58a74d002dfc2eba593000789edda9b3 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Mon, 1 Sep 2025 00:06:07 +0100 Subject: [PATCH 69/73] Fix eval bug by quoting Before this, the eval call would try to run some commands (if they were compound commands) in the yaml file on the machine running acme.sh Eval might not be worth it for the little benefit it brings. --- deploy/multideploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 2d2a7a37..460e06d5 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -182,7 +182,7 @@ _export_envs() { echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do # Using eval to expand nested variables in the configuration file - _value=$(eval echo "$_value") + _value=$(eval 'echo "'"$_value"'"') _savedeployconf "$_key" "$_value" _secure_debug3 "Saved $_key" "$_value" done From 96f38655b4bc8a7147eec36742c5ac3e10779384 Mon Sep 17 00:00:00 2001 From: Jacobo de Vera Date: Mon, 1 Sep 2025 00:07:44 +0100 Subject: [PATCH 70/73] Use file descriptor 3 for main deployment loop Before this, some deployment scripts would interact with STDIN and that would cause this loop to skip some elements. By using descriptor 3 we avoid clashing with the very common stdin and stdout. --- deploy/multideploy.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 460e06d5..590cfba7 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -235,7 +235,7 @@ _deploy_services() { _failedServices="" _failedCount=0 - while read -r _service; do + while read -r _service <&3; do _debug2 "Service" "$_service" _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") _envs=$(yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file") @@ -246,7 +246,7 @@ _deploy_services() { _failedCount=$((_failedCount + 1)) fi _clear_envs "$_envs" - done < "$_tempfile" + done 3< "$_tempfile" _debug3 "Failed services" "$_failedServices" _debug2 "Failed count" "$_failedCount" From 61b59831c491e1f038c64bd31f863fc8fd8e280b Mon Sep 17 00:00:00 2001 From: tomo <49612544+tomo2403@users.noreply.github.com> Date: Sat, 6 Sep 2025 12:31:56 +0200 Subject: [PATCH 71/73] minor code style adjustments in multideploy script --- deploy/multideploy.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 590cfba7..9e8b7164 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -101,8 +101,7 @@ _preprocess_deployfile() { fi done - if [ -z "$found_file" ]; - then + if [ -z "$found_file" ]; then _err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one." return 1 fi @@ -228,9 +227,9 @@ _deploy_services() { _debug3 "Deploy file" "$_deploy_file" _tempfile=$(mktemp) - trap "rm -f $_tempfile" EXIT + trap 'rm -f $_tempfile' EXIT - yq e '.services[].name' "$_deploy_file" > "$_tempfile" + yq e '.services[].name' "$_deploy_file" >"$_tempfile" _debug3 "Services" "$(cat "$_tempfile")" _failedServices="" @@ -246,7 +245,7 @@ _deploy_services() { _failedCount=$((_failedCount + 1)) fi _clear_envs "$_envs" - done 3< "$_tempfile" + done 3<"$_tempfile" _debug3 "Failed services" "$_failedServices" _debug2 "Failed count" "$_failedCount" From 11cae37405e2f82b6356fd662551be625a16cb6f Mon Sep 17 00:00:00 2001 From: invario <67800603+invario@users.noreply.github.com> Date: Sat, 20 Dec 2025 11:04:04 -0500 Subject: [PATCH 72/73] make compatible with both yq versions kislyuk yq (used by Debian packages) does not accept `yq e` and also returns strings with double quotes. mikefarah's yq-go (used by Alpine) accepts `yq e` and `yq`. replace `yq e` with `yq` and also use `-r` switch to remove double quoting to ensure uniform return values from both yq versions. Signed-off-by: invario <67800603+invario@users.noreply.github.com> --- deploy/multideploy.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh index 9e8b7164..ef920f64 100644 --- a/deploy/multideploy.sh +++ b/deploy/multideploy.sh @@ -125,7 +125,7 @@ _check_deployfile() { _debug2 "check: Deploy file" "$_deploy_file" # Check version - _deploy_file_version=$(yq '.version' "$_deploy_file") + _deploy_file_version=$(yq -r '.version' "$_deploy_file") if [ "$MULTIDEPLOY_VERSION" != "$_deploy_file_version" ]; then _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version." return 1 @@ -133,7 +133,7 @@ _check_deployfile() { _debug2 "check: Deploy file version is compatible: $_deploy_file_version" # Extract all services from config - _services=$(yq e '.services[].name' "$_deploy_file") + _services=$(yq -r '.services[].name' "$_deploy_file") if [ -z "$_services" ]; then _err "Config does not have any services to deploy to." @@ -148,19 +148,19 @@ _check_deployfile() { echo "$_services" | while read -r _service; do _debug2 "check: Checking service: $_service" # Check if service exists - _service_config=$(yq e ".services[] | select(.name == \"$_service\")" "$_deploy_file") + _service_config=$(yq -r ".services[] | select(.name == \"$_service\")" "$_deploy_file") if [ -z "$_service_config" ] || [ "$_service_config" = "null" ]; then _err "Service '$_service' not found." return 1 fi - _service_hook=$(echo "$_service_config" | yq e ".hook" -) + _service_hook=$(echo "$_service_config" | yq -r ".hook" -) if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then _err "Service '$_service' does not have a hook." return 1 fi - _service_environment=$(echo "$_service_config" | yq e ".environment" -) + _service_environment=$(echo "$_service_config" | yq -r ".environment" -) if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then _err "Service '$_service' does not have an environment." return 1 @@ -179,7 +179,7 @@ _export_envs() { _secure_debug3 "Exporting envs" "$_env_list" - echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do + echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do # Using eval to expand nested variables in the configuration file _value=$(eval 'echo "'"$_value"'"') _savedeployconf "$_key" "$_value" @@ -206,7 +206,7 @@ _clear_envs() { _env_list="$1" _secure_debug3 "Clearing envs" "$_env_list" - env_pairs=$(echo "$_env_list" | yq e -r 'to_entries | .[] | .key + "=" + .value') + env_pairs=$(echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value') echo "$env_pairs" | while IFS='=' read -r _key _value; do _debug3 "Deleting key" "$_key" @@ -229,15 +229,15 @@ _deploy_services() { _tempfile=$(mktemp) trap 'rm -f $_tempfile' EXIT - yq e '.services[].name' "$_deploy_file" >"$_tempfile" + yq -r '.services[].name' "$_deploy_file" >"$_tempfile" _debug3 "Services" "$(cat "$_tempfile")" _failedServices="" _failedCount=0 while read -r _service <&3; do _debug2 "Service" "$_service" - _hook=$(yq e ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") - _envs=$(yq e ".services[] | select(.name == \"$_service\").environment" "$_deploy_file") + _hook=$(yq -r ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") + _envs=$(yq -r ".services[] | select(.name == \"$_service\").environment" "$_deploy_file") _export_envs "$_envs" if ! _deploy_service "$_service" "$_hook"; then From fc7168e11d3b5af9f7f7e93f7887d797639c9cb4 Mon Sep 17 00:00:00 2001 From: neil Date: Sat, 27 Dec 2025 11:20:22 +0100 Subject: [PATCH 73/73] change default renew to 30 days change default renew to 30 days and fix readme --- README.md | 390 ++++++++++++++++++++++++++++++------------------------ acme.sh | 2 +- 2 files changed, 220 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 4afd90a8..d6ddf36e 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,70 @@ -[![zerossl.com](https://github.com/user-attachments/assets/7531085e-399b-4ac2-82a2-90d14a0b7f05)](https://zerossl.com/?fromacme.sh) +

+ + zerossl.com + +

-# An ACME Shell script: acme.sh +

๐Ÿ” acme.sh

+

An ACME Protocol Client Written Purely in Shell

-[![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml) -[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml) -[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml) -[![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml) -[![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml) -[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml) -[![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml) -[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml) -[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml) +

+ FreeBSD + OpenBSD + NetBSD + MacOS + Ubuntu + Windows + Solaris + DragonFlyBSD + Omnios +

-![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg) -![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg) -![DockerHub](https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg) +

+ Shellcheck + PebbleStrict + DockerHub +

+ +

+ Financial Contributors on Open Collective + Join the chat at Gitter + Docker stars + Docker pulls +

- -[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Docker stars](https://img.shields.io/docker/stars/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") -[![Docker pulls](https://img.shields.io/docker/pulls/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") +--- +## โœจ Features +- ๐Ÿš An ACME protocol client written **purely in Shell** (Unix shell) language +- ๐Ÿ“œ Full ACME protocol implementation +- ๐Ÿ”‘ Support **ECDSA** certificates +- ๐ŸŒ Support **SAN** and **wildcard** certificates +- โšก Simple, powerful and very easy to use โ€” only **3 minutes** to learn! +- ๐Ÿ”ง Compatible with **Bash**, **dash** and **sh** +- ๐Ÿšซ No dependencies on Python +- ๐Ÿ”„ One script to issue, renew and install your certificates automatically +- ๐Ÿ‘ค **DOES NOT** require `root/sudoer` access +- ๐Ÿณ Docker ready +- ๐ŸŒ IPv6 ready +- ๐Ÿ“ง Cron job notifications for renewal or error -- An ACME protocol client written purely in Shell (Unix shell) language. -- Full ACME protocol implementation. -- Support ECDSA certs -- Support SAN and wildcard certs -- Simple, powerful and very easy to use. You only need 3 minutes to learn it. -- Bash, dash and sh compatible. -- Purely written in Shell with no dependencies on python. -- Just one script to issue, renew and install your certificates automatically. -- DOES NOT require `root/sudoer` access. -- Docker ready -- IPv6 ready -- Cron job notifications for renewal or error etc. +> ๐Ÿ’ก It's probably the **easiest & smartest** shell script to automatically issue & renew free certificates. -It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates. +

+ ๐Ÿ“š Wiki โ€ข + ๐Ÿณ Docker Guide โ€ข + ๐Ÿฆ Twitter +

-Wiki: https://github.com/acmesh-official/acme.sh/wiki +--- -For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker) +## ๐ŸŒ [ไธญๆ–‡่ฏดๆ˜Ž](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E) -Twitter: [@neilpangxa](https://twitter.com/neilpangxa) +--- - -# [ไธญๆ–‡่ฏดๆ˜Ž](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E) - -# Who: +## ๐Ÿ† Who Uses acme.sh? - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) - [ruby-china.org](https://ruby-china.org/topics/31983) - [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management) @@ -62,7 +78,9 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) - [lnmp.org](https://lnmp.org/) - [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials) -# Tested OS +--- + +## ๐Ÿ–ฅ๏ธ Tested OS | NO | Status| Platform| |----|-------|---------| @@ -91,50 +109,60 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) |24|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management) -Check our [testing project](https://github.com/acmesh-official/acmetest): +> ๐Ÿงช Check our [testing project](https://github.com/acmesh-official/acmetest) +> +> ๐Ÿ–ฅ๏ธ The testing VMs are supported by [vmactions.org](https://vmactions.org) -https://github.com/acmesh-official/acmetest +--- -# Supported CA +## ๐Ÿ›๏ธ Supported CA -- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default) -- Letsencrypt.org CA -- [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) -- [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) -- [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) -- [Pebble strict Mode](https://github.com/letsencrypt/pebble) -- Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA +| CA | Status | +|---|---| +| [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) | โญ **Default** | +| Letsencrypt.org CA | โœ… Supported | +| [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) | โœ… Supported | +| [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) | โœ… Supported | +| [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) | โœ… Supported | +| [Pebble strict Mode](https://github.com/letsencrypt/pebble) | โœ… Supported | +| Any [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA | โœ… Supported | -# Supported modes +--- -- Webroot mode -- Standalone mode -- Standalone tls-alpn mode -- Apache mode -- Nginx mode -- DNS mode -- [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) -- [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) +## โš™๏ธ Supported Modes +| Mode | Description | +|------|-------------| +| ๐Ÿ“ Webroot mode | Use existing webroot directory | +| ๐Ÿ–ฅ๏ธ Standalone mode | Built-in webserver on port 80 | +| ๐Ÿ” Standalone tls-alpn mode | Built-in webserver on port 443 | +| ๐Ÿชถ Apache mode | Use Apache for verification | +| โšก Nginx mode | Use Nginx for verification | +| ๐ŸŒ DNS mode | Use DNS TXT records | +| ๐Ÿ”— [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification | +| ๐Ÿ“ก [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification | -# 1. How to install +--- -### 1. Install online +## ๐Ÿ“– Usage Guide -Check this project: https://github.com/acmesh-official/get.acme.sh +### 1๏ธโƒฃ How to Install + +#### ๐Ÿ“ฅ Install Online + +> Check this project: https://github.com/acmesh-official/get.acme.sh ```bash curl https://get.acme.sh | sh -s email=my@example.com ``` -Or: +**Or:** ```bash wget -O - https://get.acme.sh | sh -s email=my@example.com ``` - -### 2. Or, Install from git +#### ๐Ÿ“ฆ Install from Git Clone this project and launch installation: @@ -144,11 +172,11 @@ cd ./acme.sh ./acme.sh --install -m my@example.com ``` -You `don't have to be root` then, although `it is recommended`. +> ๐Ÿ’ก You `don't have to be root` then, although `it is recommended`. -Advanced Installation: https://github.com/acmesh-official/acme.sh/wiki/How-to-install +๐Ÿ“š **Advanced Installation:** https://github.com/acmesh-official/acme.sh/wiki/How-to-install -The installer will perform 3 actions: +**The installer will perform 3 actions:** 1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. All certs will be placed in this folder too. @@ -161,17 +189,19 @@ Cron entry example: 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null ``` -After the installation, you must close the current terminal and reopen it to make the alias take effect. +> โš ๏ธ After the installation, you must close the current terminal and reopen it to make the alias take effect. -Ok, you are ready to issue certs now. +โœ… **You are ready to issue certs now!** -Show help message: +**Show help message:** ```sh -root@v1:~# acme.sh -h +acme.sh -h ``` -# 2. Just issue a cert +--- + +### 2๏ธโƒฃ Issue a Certificate **Example 1:** Single domain. @@ -206,19 +236,21 @@ You must point and bind all the domains to the same webroot dir: `/home/wwwroot/ The certs will be placed in `~/.acme.sh/example.com/` -The certs will be renewed automatically every **60** days. +> ๐Ÿ”„ The certs will be renewed automatically every **30** days. -The certs will default to ECC certificates. +> ๐Ÿ” The certs will default to **ECC** certificates. -More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +๐Ÿ“š **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +--- -# 3. Install the cert to Apache/Nginx etc. +### 3๏ธโƒฃ Install the Certificate to Apache/Nginx After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers. -You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. -**Apache** example: +> โš ๏ธ **IMPORTANT:** You **MUST** use this command to copy the certs to the target files. **DO NOT** use the certs files in `~/.acme.sh/` folder โ€” they are for internal use only, the folder structure may change in the future. + +#### ๐Ÿชถ Apache Example: ```bash acme.sh --install-cert -d example.com \ --cert-file /path/to/certfile/in/apache/cert.pem \ @@ -227,7 +259,7 @@ acme.sh --install-cert -d example.com \ --reloadcmd "service apache2 force-reload" ``` -**Nginx** example: +#### โšก Nginx Example: ```bash acme.sh --install-cert -d example.com \ --key-file /path/to/keyfile/in/nginx/key.pem \ @@ -241,91 +273,89 @@ The ownership and permission info of existing files are preserved. You can pre-c Install/copy the cert/key to the production Apache or Nginx path. -The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. +> ๐Ÿ”„ The cert will be renewed every **30** days by default (configurable). Once renewed, the Apache/Nginx service will be reloaded automatically. +> โš ๏ธ **IMPORTANT:** The `reloadcmd` is very important. The cert can be automatically renewed, but without a correct `reloadcmd`, the cert may not be flushed to your server (like nginx or apache), then your website will not be able to show the renewed cert. -**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** +--- -# 4. Use Standalone server to issue cert +### 4๏ธโƒฃ Use Standalone Server to Issue Certificate -**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** +> ๐Ÿ” Requires root/sudoer or permission to listen on port **80** (TCP) -Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. +> โš ๏ธ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com ``` -More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +๐Ÿ“š **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert -# 5. Use Standalone ssl server to issue cert +--- -**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** +### 5๏ธโƒฃ Use Standalone TLS Server to Issue Certificate -Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. +> ๐Ÿ” Requires root/sudoer or permission to listen on port **443** (TCP) + +> โš ๏ธ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com ``` -More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +๐Ÿ“š **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +--- -# 6. Use Apache mode +### 6๏ธโƒฃ Use Apache Mode -**(requires you to be root/sudoer, since it is required to interact with Apache server)** +> ๐Ÿ” Requires root/sudoer to interact with Apache server If you are running a web server, it is recommended to use the `Webroot mode`. Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. -Just set string "apache" as the second argument and it will force use of apache plugin automatically. - ```sh acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com ``` -**This apache mode is only to issue the cert, it will not change your apache config files. -You will need to configure your website config files to use the cert by yourself. -We don't want to mess with your apache server, don't worry.** +> ๐Ÿ’ก **Note:** This Apache mode is only to issue the cert, it will **not** change your Apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Apache server, don't worry! -More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +๐Ÿ“š **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert -# 7. Use Nginx mode +--- -**(requires you to be root/sudoer, since it is required to interact with Nginx server)** +### 7๏ธโƒฃ Use Nginx Mode + +> ๐Ÿ” Requires root/sudoer to interact with Nginx server If you are running a web server, it is recommended to use the `Webroot mode`. -Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. +Particularly, if you are running an Nginx server, you can use Nginx mode instead. This mode doesn't write any files to your web root folder. -Just set string "nginx" as the second argument. - -It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. - -So, the config is not changed. +It will configure Nginx server automatically to verify the domain and then restore the Nginx config to the original version. So, the config is not changed. ```sh acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com ``` -**This nginx mode is only to issue the cert, it will not change your nginx config files. -You will need to configure your website config files to use the cert by yourself. -We don't want to mess with your nginx server, don't worry.** +> ๐Ÿ’ก **Note:** This Nginx mode is only to issue the cert, it will **not** change your Nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Nginx server, don't worry! -More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +๐Ÿ“š **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert -# 8. Automatic DNS API integration +--- + +### 8๏ธโƒฃ Automatic DNS API Integration If your DNS provider supports API access, we can use that API to automatically issue the certs. -You don't have to do anything manually! +> โœจ **You don't have to do anything manually!** -### Currently acme.sh supports most of the dns providers: +๐Ÿ“š **Currently acme.sh supports most DNS providers:** https://github.com/acmesh-official/acme.sh/wiki/dnsapi -https://github.com/acmesh-official/acme.sh/wiki/dnsapi +--- -# 9. Use DNS manual mode: +### 9๏ธโƒฃ Use DNS Manual Mode See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first. @@ -355,67 +385,74 @@ Then just rerun with `renew` argument: acme.sh --renew -d example.com ``` -Ok, it's done. +โœ… **Done!** -**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.** +> โš ๏ธ **WARNING:** This is DNS manual mode โ€” it **cannot** be renewed automatically. You will have to add a new TXT record to your domain manually when you renew your cert. **Please use DNS API mode instead.** -**Please use dns api mode instead.** +--- -# 10. Issue certificates of different key types and lengths (ECC or RSA) +### ๐Ÿ”Ÿ Issue Certificates of Different Key Types (ECC or RSA) -Just set the `keylength` to a valid, supported, value. +Just set the `keylength` to a valid, supported value. -Valid values for the `keylength` parameter are: +**Valid values for the `keylength` parameter:** -1. **ec-256 (prime256v1, "ECDSA P-256", which is the default key type)** -2. **ec-384 (secp384r1, "ECDSA P-384")** -3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** -4. **2048 (RSA2048)** -5. **3072 (RSA3072)** -6. **4096 (RSA4096)** +| Key Length | Description | +|------------|-------------| +| `ec-256` | prime256v1, "ECDSA P-256" โญ **Default** | +| `ec-384` | secp384r1, "ECDSA P-384" | +| `ec-521` | secp521r1, "ECDSA P-521" โš ๏ธ Not supported by Let's Encrypt yet | +| `2048` | RSA 2048-bit | +| `3072` | RSA 3072-bit | +| `4096` | RSA 4096-bit | -For example: +**Examples:** -### Single domain with ECDSA P-384 certificate +#### Single domain with ECDSA P-384 certificate ```bash acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384 ``` -### SAN multi domain with RSA4096 certificate +#### SAN multi domain with RSA4096 certificate ```bash acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096 ``` -# 11. Issue Wildcard certificates +--- -It's simple, just give a wildcard domain as the `-d` parameter. +### 1๏ธโƒฃ1๏ธโƒฃ Issue Wildcard Certificates + +It's simple! Just give a wildcard domain as the `-d` parameter: ```sh -acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf +acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf ``` -# 12. How to renew the certs +--- -No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. +### 1๏ธโƒฃ2๏ธโƒฃ How to Renew Certificates -However, you can also force to renew a cert: +> ๐Ÿ”„ No need to renew manually! All certs will be renewed automatically every **30** days. + +However, you can force a renewal: ```sh acme.sh --renew -d example.com --force ``` -or, for ECC cert: +**For ECC cert:** ```sh acme.sh --renew -d example.com --force --ecc ``` +--- -# 13. How to stop cert renewal +### 1๏ธโƒฃ3๏ธโƒฃ How to Stop Certificate Renewal To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: @@ -425,73 +462,78 @@ acme.sh --remove -d example.com [--ecc] The cert/key file is not removed from the disk. -You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself. +> ๐Ÿ’ก You can remove the respective directory (e.g. `~/.acme.sh/example.com`) manually. +--- -# 14. How to upgrade `acme.sh` +### 1๏ธโƒฃ4๏ธโƒฃ How to Upgrade acme.sh -acme.sh is in constant development, so it's strongly recommended to use the latest code. +> ๐Ÿš€ acme.sh is in constant development โ€” it's strongly recommended to use the latest code. -You can update acme.sh to the latest code: +**Update to latest:** ```sh acme.sh --upgrade ``` -You can also enable auto upgrade: +**Enable auto upgrade:** ```sh acme.sh --upgrade --auto-upgrade ``` -Then **acme.sh** will be kept up to date automatically. - -Disable auto upgrade: +**Disable auto upgrade:** ```sh acme.sh --upgrade --auto-upgrade 0 ``` +--- -# 15. Issue a cert from an existing CSR +### 1๏ธโƒฃ5๏ธโƒฃ Issue a Certificate from an Existing CSR -https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR +๐Ÿ“š https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR +--- -# 16. Send notifications in cronjob +### 1๏ธโƒฃ6๏ธโƒฃ Send Notifications in Cronjob -https://github.com/acmesh-official/acme.sh/wiki/notify +๐Ÿ“š https://github.com/acmesh-official/acme.sh/wiki/notify +--- -# 17. Under the Hood +### 1๏ธโƒฃ7๏ธโƒฃ Under the Hood -Speak ACME language using shell, directly to "Let's Encrypt". +> ๐Ÿ”ง Speak ACME language using shell, directly to "Let's Encrypt". -TODO: +--- +### 1๏ธโƒฃ8๏ธโƒฃ Acknowledgments -# 18. Acknowledgments +| Project | Link | +|---------|------| +| ๐Ÿ™ Acme-tiny | https://github.com/diafygi/acme-tiny | +| ๐Ÿ“œ ACME protocol | https://github.com/ietf-wg-acme/acme | -1. Acme-tiny: https://github.com/diafygi/acme-tiny -2. ACME protocol: https://github.com/ietf-wg-acme/acme +--- +## ๐Ÿ‘ฅ Contributors -## Contributors - -### Code Contributors +### ๐Ÿ’ป Code Contributors This project exists thanks to all the people who contribute. + -### Financial Contributors +### ๐Ÿ’ฐ Financial Contributors Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)] -#### Individuals +#### ๐Ÿ‘ค Individuals -#### Organizations +#### ๐Ÿข Organizations Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)] @@ -506,25 +548,31 @@ Support this project with your organization. Your logo will show up here with a +--- +### 1๏ธโƒฃ9๏ธโƒฃ License & Others -# 19. License & Others +๐Ÿ“„ **License:** GPLv3 -License is GPLv3 +โญ Please **Star** and **Fork** this project! -Please Star and Fork me. +๐Ÿ› [Issues](https://github.com/acmesh-official/acme.sh/issues) and ๐Ÿ”€ [Pull Requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome. -[Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome. +--- +### 2๏ธโƒฃ0๏ธโƒฃ Donate -# 20. Donate -Your donation makes **acme.sh** better: +> ๐Ÿ’ Your donation makes **acme.sh** better! -1. PayPal/Alipay(ๆ”ฏไป˜ๅฎ)/Wechat(ๅพฎไฟก): [https://donate.acme.sh/](https://donate.acme.sh/) +| Method | Link | +|--------|------| +| PayPal / Alipay(ๆ”ฏไป˜ๅฎ) / Wechat(ๅพฎไฟก) | [https://donate.acme.sh/](https://donate.acme.sh/) | -[Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list) +๐Ÿ“œ [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list) -# 21. About this repository +--- + +### 2๏ธโƒฃ1๏ธโƒฃ About This Repository > [!NOTE] > This repository is officially maintained by ZeroSSL as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community! @@ -532,7 +580,7 @@ Your donation makes **acme.sh** better: > > All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL.

- + diff --git a/acme.sh b/acme.sh index 053f16db..5cd2cb3f 100755 --- a/acme.sh +++ b/acme.sh @@ -65,7 +65,7 @@ ID_TYPE_IP="ip" LOCAL_ANY_ADDRESS="0.0.0.0" -DEFAULT_RENEW=60 +DEFAULT_RENEW=30 NO_VALUE="no"