mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-07 03:15:56 +00:00
refactor(cert-deploy): make cert deploy script TLS-source agnostic
- Remove cert_folder_find module (no longer needed) - Update deploy script to accept a cert source directory (not LETSENCRYPT_LIVE_PATH) - Derive cert source dir via cert_plan in systemd unit - Keep Mailu key.pem/cert.pem mapping with LE/selfsigned fallback - Minor shell hardening (set -eu) and wording fixes https://chatgpt.com/share/697cb143-e68c-800f-9823-c78a8d833d4e
This commit is contained in:
4 files changed
+68
-96
No files matched your search
@@ -1,29 +0,0 @@
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.cert_utils import CertUtils
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
domain=dict(type="str", required=True),
|
||||
cert_base_path=dict(
|
||||
type="str", required=False, default="/etc/letsencrypt/live"
|
||||
),
|
||||
debug=dict(type="bool", required=False, default=False),
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=module_args)
|
||||
|
||||
domain = module.params["domain"]
|
||||
cert_base_path = module.params["cert_base_path"]
|
||||
debug = module.params["debug"]
|
||||
|
||||
folder = CertUtils.find_cert_for_domain(domain, cert_base_path, debug)
|
||||
|
||||
if folder is None:
|
||||
module.fail_json(msg=f"No certificate covering domain {domain} found.")
|
||||
else:
|
||||
module.exit_json(folder=folder)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,29 +1,45 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Check if the necessary parameters are provided
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo "Usage: $0 <ssl_cert_folder> <docker_compose_instance_directory> <letsencrypt_live_path>"
|
||||
exit 1
|
||||
# Usage: script.sh <ssl_cert_source_dir> <docker_compose_instance_directory>
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <ssl_cert_source_dir> <docker_compose_instance_directory>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Assign parameters
|
||||
ssl_cert_folder="$1"
|
||||
ssl_cert_source_dir="$1"
|
||||
docker_compose_instance_directory="$2"
|
||||
letsencrypt_live_path="$3"
|
||||
|
||||
# Keep your existing target layout (minimal change)
|
||||
docker_compose_cert_directory="${docker_compose_instance_directory}volumes/certs"
|
||||
|
||||
if [ ! -d "$ssl_cert_source_dir" ]; then
|
||||
echo "ERROR: ssl_cert_source_dir does not exist or is not a directory: $ssl_cert_source_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure the target cert directory exists
|
||||
if [ ! -d "$docker_compose_cert_directory" ]; then
|
||||
echo "Creating certs directory: $docker_compose_cert_directory"
|
||||
mkdir -p "$docker_compose_cert_directory" || exit 1
|
||||
echo "Creating certs directory: $docker_compose_cert_directory"
|
||||
mkdir -p "$docker_compose_cert_directory"
|
||||
fi
|
||||
|
||||
# Copy all certificates (generic)
|
||||
cp -RvL "${letsencrypt_live_path}/${ssl_cert_folder}/"* "$docker_compose_cert_directory" || exit 1
|
||||
echo "Copying certificates from: $ssl_cert_source_dir -> $docker_compose_cert_directory"
|
||||
cp -RvL "${ssl_cert_source_dir}/"* "$docker_compose_cert_directory"
|
||||
|
||||
# Mailu optimization: explicit key/cert mapping
|
||||
cp -v "${letsencrypt_live_path}/${ssl_cert_folder}/privkey.pem" "${docker_compose_cert_directory}/key.pem" || exit 1
|
||||
cp -v "${letsencrypt_live_path}/${ssl_cert_folder}/fullchain.pem" "${docker_compose_cert_directory}/cert.pem" || exit 1
|
||||
# Mailu optimization: create key.pem/cert.pem from whatever exists
|
||||
# Prefer LE naming if present
|
||||
if [ -f "${ssl_cert_source_dir}/privkey.pem" ] && [ -f "${ssl_cert_source_dir}/fullchain.pem" ]; then
|
||||
cp -v "${ssl_cert_source_dir}/privkey.pem" "${docker_compose_cert_directory}/key.pem"
|
||||
cp -v "${ssl_cert_source_dir}/fullchain.pem" "${docker_compose_cert_directory}/cert.pem"
|
||||
elif [ -f "${ssl_cert_source_dir}/key.pem" ] && [ -f "${ssl_cert_source_dir}/cert.pem" ]; then
|
||||
cp -v "${ssl_cert_source_dir}/key.pem" "${docker_compose_cert_directory}/key.pem"
|
||||
cp -v "${ssl_cert_source_dir}/cert.pem" "${docker_compose_cert_directory}/cert.pem"
|
||||
else
|
||||
echo "ERROR: Could not determine key/cert mapping for Mailu." >&2
|
||||
echo "Looked for: privkey.pem+fullchain.pem OR key.pem+cert.pem in: $ssl_cert_source_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set correct reading rights
|
||||
chmod a+r -v "${docker_compose_cert_directory}/"* || exit 1
|
||||
@@ -36,45 +52,42 @@ failed_services=""
|
||||
# Reload Nginx in all containers within the Docker Compose setup
|
||||
cd "$docker_compose_instance_directory" || exit 1
|
||||
|
||||
echo "Wait for 5 minutes to prevent interuption of setup procedures"
|
||||
echo "Wait for 5 minutes to prevent interruption of setup procedures"
|
||||
sleep 300
|
||||
|
||||
# Iterate over all services
|
||||
for service in $(docker compose ps --services); do
|
||||
echo "Checking service: $service"
|
||||
echo "Checking service: $service"
|
||||
|
||||
# Check if Nginx exists in the container
|
||||
if docker compose exec -T "$service" which nginx > /dev/null 2>&1; then
|
||||
echo "Testing Nginx config for service: $service"
|
||||
if ! docker compose exec -T "$service" nginx -t; then
|
||||
echo "Nginx config test FAILED for service: $service" >&2
|
||||
nginx_reload_failed=true
|
||||
failed_services="$failed_services $service"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Reloading Nginx for service: $service"
|
||||
if docker compose exec -T "$service" nginx -s reload; then
|
||||
nginx_reload_successful=true
|
||||
echo "Successfully reloaded Nginx for service: $service"
|
||||
else
|
||||
echo "Failed to reload Nginx for service: $service" >&2
|
||||
nginx_reload_failed=true
|
||||
failed_services="$failed_services $service"
|
||||
fi
|
||||
else
|
||||
echo "Nginx not found in service: $service, skipping."
|
||||
if docker compose exec -T "$service" which nginx > /dev/null 2>&1; then
|
||||
echo "Testing Nginx config for service: $service"
|
||||
if ! docker compose exec -T "$service" nginx -t; then
|
||||
echo "Nginx config test FAILED for service: $service" >&2
|
||||
nginx_reload_failed=true
|
||||
failed_services="$failed_services $service"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Reloading Nginx for service: $service"
|
||||
if docker compose exec -T "$service" nginx -s reload; then
|
||||
nginx_reload_successful=true
|
||||
echo "Successfully reloaded Nginx for service: $service"
|
||||
else
|
||||
echo "Failed to reload Nginx for service: $service" >&2
|
||||
nginx_reload_failed=true
|
||||
failed_services="$failed_services $service"
|
||||
fi
|
||||
else
|
||||
echo "Nginx not found in service: $service, skipping."
|
||||
fi
|
||||
done
|
||||
|
||||
# Optional auto-healing: restart only the services whose reload failed
|
||||
if [ "$nginx_reload_failed" = true ]; then
|
||||
echo "At least one Nginx reload failed. Affected services:${failed_services}"
|
||||
echo "Restarting affected services to apply the new certificates..."
|
||||
# shellcheck disable=SC2086
|
||||
(sleep 120 && docker compose restart $failed_services) || (sleep 120 && docker compose restart) || exit 1
|
||||
echo "At least one Nginx reload failed. Affected services:${failed_services}"
|
||||
echo "Restarting affected services to apply the new certificates..."
|
||||
# shellcheck disable=SC2086
|
||||
(sleep 120 && docker compose restart $failed_services) || (sleep 120 && docker compose restart) || exit 1
|
||||
elif [ "$nginx_reload_successful" = true ]; then
|
||||
echo "At least one Nginx reload was successful. No restart needed."
|
||||
echo "At least one Nginx reload was successful. No restart needed."
|
||||
else
|
||||
echo "No Nginx instances found in any service. Nothing to reload."
|
||||
echo "No Nginx instances found in any service. Nothing to reload."
|
||||
fi
|
||||
@@ -1,7 +1,9 @@
|
||||
[Unit]
|
||||
Description=Let's Encrypt deploy to {{ docker_compose.directories.instance }}
|
||||
Description=TLS cert deploy to {{ docker_compose.directories.instance }}
|
||||
OnFailure={{ SYS_SERVICE_ON_FAILURE_COMPOSE }}
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart={{ system_service_script_exec }} {{ ssl_cert_folder }} {{ docker_compose.directories.instance }} {{ LETSENCRYPT_LIVE_PATH }}
|
||||
ExecStart={{ system_service_script_exec }} \
|
||||
{{ lookup('cert_plan', domain, want='files.cert') | dirname }} \
|
||||
{{ docker_compose.directories.instance }}
|
||||
@@ -1,3 +1,4 @@
|
||||
---
|
||||
- block:
|
||||
- name: Include dependency 'sys-svc-webserver-https'
|
||||
include_role:
|
||||
@@ -12,34 +13,19 @@
|
||||
#- name: "Cleanup dedicated cert for '{{ domain }}'"
|
||||
# command: >-
|
||||
# certbot delete --cert-name {{ domain }} --non-interactive
|
||||
# when:
|
||||
# when:
|
||||
# - MODE_CLEANUP | bool
|
||||
# # Cleanup mode is enabled
|
||||
# - CERTBOT_FLAVOR != 'dedicated'
|
||||
# # Wildcard certificate is enabled
|
||||
# - domain.split('.') | length == (DOMAIN_PRIMARY.split('.') | length + 1) and domain.endswith(DOMAIN_PRIMARY)
|
||||
# # AND: The domain is a direct first-level subdomain of the primary domain
|
||||
# - domain != DOMAIN_PRIMARY
|
||||
# - domain != DOMAIN_PRIMARY
|
||||
# # The domain is not the primary domain
|
||||
# register: certbot_result
|
||||
# failed_when: certbot_result.rc != 0 and ("No certificate found with name" not in certbot_result.stderr)
|
||||
# changed_when: certbot_result.rc == 0 and ("No certificate found with name" not in certbot_result.stderr)
|
||||
|
||||
- name: "Find SSL cert folder for '{{ domain }}'"
|
||||
cert_folder_find:
|
||||
domain: "{{ domain }}"
|
||||
cert_base_path: "{{ LETSENCRYPT_LIVE_PATH }}"
|
||||
debug: "{{ MODE_DEBUG | bool }}"
|
||||
register: cert_folder_result
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
changed_when: false
|
||||
|
||||
- name: "Set ssl_cert_folder fact to '{{ cert_folder_result.folder }}'"
|
||||
set_fact:
|
||||
ssl_cert_folder: "{{ cert_folder_result.folder }}"
|
||||
changed_when: false
|
||||
|
||||
- name: "Ensure ssl_cert_folder is set for domain {{ domain }}"
|
||||
fail:
|
||||
msg: "No certificate folder found for domain {{ domain }}"
|
||||
when: ssl_cert_folder is undefined or ssl_cert_folder is none
|
||||
# NOTE (minimal invasive change):
|
||||
# ssl_cert_folder is now derived from tls_resolve so it works for both
|
||||
# letsencrypt and self_signed.
|
||||
Reference in new issue
Block a user