v4.0.0.1
This commit is contained in:
+151
-16
@@ -35,6 +35,108 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
get_mount_source_for_destination() {
|
||||
local container_id="$1"
|
||||
local destination="$2"
|
||||
|
||||
docker inspect -f '{{range .Mounts}}{{.Source}}|{{.Destination}}{{println}}{{end}}' "$container_id" \
|
||||
| awk -F'|' -v wanted="$destination" '$2 == wanted { print $1; exit }'
|
||||
}
|
||||
|
||||
write_acme_bootstrap_config() {
|
||||
local source_config="$1"
|
||||
local target_config="$2"
|
||||
shift 2
|
||||
|
||||
awk -v skip_domains="$*" '
|
||||
BEGIN {
|
||||
split(skip_domains, items, " ")
|
||||
for (i in items) {
|
||||
if (items[i] != "") skip[items[i]] = 1
|
||||
}
|
||||
in_server = 0
|
||||
depth = 0
|
||||
block = ""
|
||||
is_ssl = 0
|
||||
has_skipped_domain = 0
|
||||
}
|
||||
function emit_block() {
|
||||
if (!(is_ssl && has_skipped_domain)) {
|
||||
printf "%s", block
|
||||
}
|
||||
in_server = 0
|
||||
depth = 0
|
||||
block = ""
|
||||
is_ssl = 0
|
||||
has_skipped_domain = 0
|
||||
}
|
||||
{
|
||||
line = $0 ORS
|
||||
|
||||
if (!in_server) {
|
||||
if ($0 ~ /^[[:space:]]*server[[:space:]]*\{[[:space:]]*$/) {
|
||||
in_server = 1
|
||||
depth = 1
|
||||
block = line
|
||||
next
|
||||
}
|
||||
|
||||
printf "%s", line
|
||||
next
|
||||
}
|
||||
|
||||
block = block line
|
||||
|
||||
if ($0 ~ /^[[:space:]]*listen[[:space:]]+443[[:space:]]+ssl([[:space:]]|;)/) {
|
||||
is_ssl = 1
|
||||
}
|
||||
|
||||
if ($0 ~ /^[[:space:]]*server_name[[:space:]]+/) {
|
||||
count = split($0, parts, /[[:space:];]+/)
|
||||
for (i = 2; i <= count; i++) {
|
||||
if (parts[i] in skip) {
|
||||
has_skipped_domain = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opens = gsub(/\{/, "{", $0)
|
||||
closes = gsub(/\}/, "}", $0)
|
||||
depth += opens - closes
|
||||
|
||||
if (depth == 0) {
|
||||
emit_block()
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (in_server) {
|
||||
emit_block()
|
||||
}
|
||||
}
|
||||
' "$source_config" > "$target_config"
|
||||
}
|
||||
|
||||
obtain_certificate() {
|
||||
local domain="$1"
|
||||
local cert_root="$2"
|
||||
local acme_webroot="$3"
|
||||
local certbot_email="info@goodwalk.co.nz"
|
||||
|
||||
echo "[deploy-remote] Obtaining TLS certificate for $domain"
|
||||
docker run --rm \
|
||||
-v "$cert_root:/etc/letsencrypt" \
|
||||
-v "$acme_webroot:/var/www/certbot" \
|
||||
certbot/certbot:latest \
|
||||
certonly \
|
||||
--webroot \
|
||||
-w /var/www/certbot \
|
||||
--cert-name "$domain" \
|
||||
-d "$domain" \
|
||||
--non-interactive \
|
||||
--agree-tos \
|
||||
-m "$certbot_email"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--archive)
|
||||
@@ -292,22 +394,6 @@ if (( nginx_args_present )); then
|
||||
[[ -f "$DEPLOY_PATH/$NGINX_SOURCE" ]] || fail "Nginx config missing from deployment payload: $DEPLOY_PATH/$NGINX_SOURCE"
|
||||
[[ -f "$NGINX_COMPOSE_FILE" ]] || fail "Nginx compose file was not found on the server: $NGINX_COMPOSE_FILE"
|
||||
|
||||
# Pre-flight: SSL certificate for onboarding.goodwalk.co.nz must exist before
|
||||
# nginx can reload — the config references this cert path directly.
|
||||
ONBOARDING_CERT="/etc/letsencrypt/live/onboarding.goodwalk.co.nz/fullchain.pem"
|
||||
ONBOARDING_KEY="/etc/letsencrypt/live/onboarding.goodwalk.co.nz/privkey.pem"
|
||||
if [[ ! -f "$ONBOARDING_CERT" || ! -f "$ONBOARDING_KEY" ]]; then
|
||||
fail "SSL certificate for onboarding.goodwalk.co.nz is not present on this server.
|
||||
Expected: $ONBOARDING_CERT
|
||||
One-time setup on the droplet:
|
||||
1. Ensure the DNS A record for onboarding.goodwalk.co.nz points to this server's IP
|
||||
2. Bring nginx up so the ACME webroot is reachable, then obtain the certificate:
|
||||
certbot certonly --webroot -w /var/www/certbot \\
|
||||
-d onboarding.goodwalk.co.nz \\
|
||||
--non-interactive --agree-tos -m info@goodwalk.co.nz
|
||||
3. Re-run this deploy script"
|
||||
fi
|
||||
|
||||
MAINTENANCE_HTML_SRC="$DEPLOY_PATH/nginx/maintenance.html"
|
||||
MAINTENANCE_LOGO_SRC="$DEPLOY_PATH/nginx/logo.png"
|
||||
[[ -f "$MAINTENANCE_HTML_SRC" ]] || fail "Maintenance page missing from deployment payload: $MAINTENANCE_HTML_SRC"
|
||||
@@ -320,6 +406,55 @@ if (( nginx_args_present )); then
|
||||
NGINX_CID="$(docker ps -qf name=^nginx$ | head -n1 || true)"
|
||||
[[ -n "$NGINX_CID" ]] || fail "Shared nginx container is not running (expected name 'nginx'). Bring it up before deploying."
|
||||
|
||||
CERT_ROOT_HOST_DIR="$(get_mount_source_for_destination "$NGINX_CID" "/etc/letsencrypt")"
|
||||
[[ -n "$CERT_ROOT_HOST_DIR" ]] || fail "nginx container is missing the certificate bind mount for /etc/letsencrypt."
|
||||
|
||||
CERTBOT_WEBROOT_HOST_DIR="$(get_mount_source_for_destination "$NGINX_CID" "/var/www/certbot")"
|
||||
[[ -n "$CERTBOT_WEBROOT_HOST_DIR" ]] || fail "nginx container is missing the ACME webroot bind mount for /var/www/certbot."
|
||||
|
||||
ONBOARDING_CERT="$CERT_ROOT_HOST_DIR/live/onboarding.goodwalk.co.nz/fullchain.pem"
|
||||
ONBOARDING_KEY="$CERT_ROOT_HOST_DIR/live/onboarding.goodwalk.co.nz/privkey.pem"
|
||||
CLIENTS_CERT="$CERT_ROOT_HOST_DIR/live/clients.goodwalk.co.nz/fullchain.pem"
|
||||
CLIENTS_KEY="$CERT_ROOT_HOST_DIR/live/clients.goodwalk.co.nz/privkey.pem"
|
||||
CP_CERT="$CERT_ROOT_HOST_DIR/live/cp.goodwalk.co.nz/fullchain.pem"
|
||||
CP_KEY="$CERT_ROOT_HOST_DIR/live/cp.goodwalk.co.nz/privkey.pem"
|
||||
MISSING_CERT_DOMAINS=()
|
||||
if [[ ! -f "$ONBOARDING_CERT" || ! -f "$ONBOARDING_KEY" ]]; then
|
||||
MISSING_CERT_DOMAINS+=("onboarding.goodwalk.co.nz")
|
||||
fi
|
||||
if [[ ! -f "$CLIENTS_CERT" || ! -f "$CLIENTS_KEY" ]]; then
|
||||
MISSING_CERT_DOMAINS+=("clients.goodwalk.co.nz")
|
||||
fi
|
||||
if [[ ! -f "$CP_CERT" || ! -f "$CP_KEY" ]]; then
|
||||
MISSING_CERT_DOMAINS+=("cp.goodwalk.co.nz")
|
||||
fi
|
||||
|
||||
if (( ${#MISSING_CERT_DOMAINS[@]} > 0 )); then
|
||||
echo "[deploy-remote] Missing TLS certificates detected: ${MISSING_CERT_DOMAINS[*]}"
|
||||
echo "[deploy-remote] Bootstrapping nginx HTTP config so ACME challenges can be served"
|
||||
mkdir -p "$(dirname "$NGINX_TARGET")"
|
||||
BOOTSTRAP_CONFIG="$(mktemp "${TMPDIR:-/tmp}/goodwalk-nginx-acme.XXXXXX.conf")"
|
||||
write_acme_bootstrap_config "$DEPLOY_PATH/$NGINX_SOURCE" "$BOOTSTRAP_CONFIG" "${MISSING_CERT_DOMAINS[@]}"
|
||||
cp "$BOOTSTRAP_CONFIG" "$NGINX_TARGET"
|
||||
rm -f "$BOOTSTRAP_CONFIG"
|
||||
|
||||
echo "[deploy-remote] Validating bootstrap nginx configuration"
|
||||
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t
|
||||
|
||||
echo "[deploy-remote] Reloading shared nginx with bootstrap config"
|
||||
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -s reload
|
||||
|
||||
for domain in "${MISSING_CERT_DOMAINS[@]}"; do
|
||||
echo "[deploy-remote] Ensure the DNS A record for $domain points to this server before certificate issuance"
|
||||
obtain_certificate "$domain" "$CERT_ROOT_HOST_DIR" "$CERTBOT_WEBROOT_HOST_DIR" \
|
||||
|| fail "Automatic certificate issuance failed for $domain. Confirm DNS resolves here and port 80 is reachable."
|
||||
done
|
||||
|
||||
[[ -f "$ONBOARDING_CERT" && -f "$ONBOARDING_KEY" ]] || fail "Automatic certificate issuance did not create onboarding.goodwalk.co.nz at $ONBOARDING_CERT"
|
||||
[[ -f "$CLIENTS_CERT" && -f "$CLIENTS_KEY" ]] || fail "Automatic certificate issuance did not create clients.goodwalk.co.nz at $CLIENTS_CERT"
|
||||
[[ -f "$CP_CERT" && -f "$CP_KEY" ]] || fail "Automatic certificate issuance did not create cp.goodwalk.co.nz at $CP_CERT"
|
||||
fi
|
||||
|
||||
if ! docker inspect -f '{{range .Mounts}}{{.Source}}|{{.Destination}}{{println}}{{end}}' "$NGINX_CID" \
|
||||
| grep -Fxq "${MAINTENANCE_HOST_DIR}|/var/www/maintenance"; then
|
||||
fail "nginx container is missing the maintenance bind mount.
|
||||
|
||||
Reference in New Issue
Block a user