Skip to content

Multideploy: Deploy to multiple hooks of the same type #6241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cd9c764
Merge pull request #1 from tomo2403/deployhook-docker
tomo2403 Feb 15, 2025
0ce2830
implemented checking deploy file
tomo2403 Feb 20, 2025
cfe3226
fixed indents
tomo2403 Feb 20, 2025
f1d214a
refactored getting services
tomo2403 Feb 20, 2025
18575b1
fixed formatting and private var names
tomo2403 Feb 21, 2025
a6060f9
implemented handling envs
tomo2403 Feb 21, 2025
4fbade3
implemented deploying to services
tomo2403 Feb 21, 2025
f275f3c
added yq to dockerfile
tomo2403 Feb 21, 2025
06e75de
implemented checking for different kinds of deploy file
tomo2403 Feb 21, 2025
6189aa5
added debug messages
tomo2403 Feb 21, 2025
ccd9d9a
improved preprocessing and fixed bug with wrong param of services
tomo2403 Feb 21, 2025
8d77bef
fixed IFS problems
tomo2403 Feb 21, 2025
a9c2435
added docs
tomo2403 Feb 21, 2025
7a35d68
added docs and enhanced log messages
tomo2403 Feb 21, 2025
b6a6e67
added header doc
tomo2403 Feb 21, 2025
98aaff6
allowed using varaibles in deploy file
tomo2403 Feb 21, 2025
f26d404
fixed missing wiki link
tomo2403 Feb 21, 2025
488d147
Merge branch 'dev' into multideploy-yaml
tomo2403 Feb 21, 2025
69858fb
Update links in multideploy.sh
tomo2403 Mar 1, 2025
e9b5bbf
Merge branch 'dev' into multideploy-yaml
tomo2403 Mar 1, 2025
67b5176
removed configs and implemented specification of deploy file name
tomo2403 Mar 27, 2025
2e6065c
Merge branch 'dev' into multideploy-yaml
tomo2403 Mar 27, 2025
bc23e8c
fixed formatting
tomo2403 Mar 27, 2025
7949c10
fixed IFS problems for some hooks
tomo2403 Apr 10, 2025
c6c672b
Merge branch 'dev' into multideploy-yaml
tomo2403 Apr 10, 2025
5189a31
simplified deploy method
tomo2403 Apr 13, 2025
ccf11cc
Merge branch 'dev' into multideploy-yaml
tomo2403 Apr 13, 2025
d3a8c7f
Merge remote-tracking branch 'origin/multideploy-yaml' into multidepl…
tomo2403 Apr 13, 2025
eca6e69
fixed bug with envs due to the use of a wrong function
tomo2403 May 27, 2025
1dc597b
implemented exiting with 1 if at least one deployment fails
tomo2403 May 28, 2025
3551e4a
implemented exiting with the number of failed deployments
tomo2403 May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ RUN apk --no-cache add -f \
tar \
libidn \
jq \
yq \
cronie

ENV LE_CONFIG_HOME=/acme.sh
Expand Down
269 changes: 269 additions & 0 deletions deploy/multideploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
#!/usr/bin/env sh

################################################################################
# ACME.sh 3rd party deploy plugin for multiple (same) services
################################################################################
# Authors: tomo2403 (creator), https://github.com/tomo2403
# 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
# - 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
################################################################################
# Dependencies:
# - yq
################################################################################
# Return value:
# 0 means success, otherwise error.
################################################################################

MULTIDEPLOY_VERSION="1.0"

# 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 file.
#
# 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"
_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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need this DOMAIN_DIR ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be used in the deploy file to specify the path for a cert if you are going to have multiple certs on the same service. This makes it easier to copy the deploy file to another domain dir.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which deploy file uses it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have a look at the sample config above

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which one ?

Copy link
Contributor Author

@tomo2403 tomo2403 Apr 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first comment after pr opening. Both services use DOMAIN_DIR. I use it for all my domains because they are deployed to the same service. If I change something in one deploy file I can just copy the file to all other domains without having to change the path for each domain in my docker container

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"
_info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'."
else
_savedeployconf "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
_debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME"
fi

OLDIFS=$IFS
if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then
_err "Failed to preprocess deploy file."
return 1
fi
_debug3 "File" "$file"

# Deploy to services
_deploy_services "$file"

# Save deployhook for renewals
_debug2 "Setting Le_DeployHook"
_savedomainconf "Le_DeployHook" "multideploy"

return 0
}

# 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 "<deploy_file1>" "<deploy_file2>?"
_preprocess_deployfile() {
# 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
IFS=$(printf '\n')
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
IFS=$OLDIFS

if [ -n "$found_file" ]; then
_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
fi

echo "$DOMAIN_PATH/$found_file"
}

# 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 "<deploy_file_path>"
_check_deployfile() {
_deploy_file="$1"
_debug2 "Deploy file" "$_deploy_file"

# 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
_debug2 "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."

IFS=$(printf '\n')
# 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
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 have an environment."
return 1
fi
done
IFS=$OLDIFS
}

# 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"

_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
_value=$(eval echo "$_value")
_savedomainconf "$_key" "$_value"
_secure_debug3 "Saved $_key" "$_value"
done
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 "<yaml_string>"
_clear_envs() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a hook has optional envs and you have two services that use the same hook, but one service uses the optional envs and the other doesn't, both services will use the optional envs because they are saved by the first service. Clearing them ensures that multideploy only uses the envs specified in the deploy file

_env_list="$1"

_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:
# 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_file_path>" "<services_list>"
_deploy_services() {
_deploy_file="$1"
_debug3 "Deploy file" "$_deploy_file"

_services=$(yq e '.services[].name' "$_deploy_file")
_debug3 "Services" "$_services"

_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")

_export_envs "$_envs"
_deploy_service "$_service" "$_hook"
_clear_envs "$_envs"
done
}

# 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 <service_name> <hook>
_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
}