524 lines
18 KiB
Bash
524 lines
18 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
|
|
ARCHIVE_PATH=""
|
|
DEPLOY_PATH=""
|
|
COMPOSE_FILE=""
|
|
PROJECT_NAME=""
|
|
SERVICE_NAME=""
|
|
NGINX_SOURCE=""
|
|
NGINX_TARGET=""
|
|
NGINX_COMPOSE_FILE=""
|
|
NGINX_PROJECT_NAME=""
|
|
MAINTENANCE_HOST_DIR=""
|
|
MAINTENANCE_FLAG_PATH=""
|
|
SEED_ADMIN_DATA=0
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name>
|
|
deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> [--service <name>]
|
|
deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> \
|
|
[--service <name>] [--nginx-source <path>] [--nginx-target <path>] \
|
|
[--nginx-compose-file <path>] [--nginx-project-name <name>] \
|
|
[--maintenance-host-dir <path>] [--maintenance-flag <path>]
|
|
|
|
This script only updates the main Goodwalk compose project at the specified
|
|
deployment path. It does not touch unrelated Docker projects or global Docker
|
|
state.
|
|
EOF
|
|
}
|
|
|
|
fail() {
|
|
echo "[deploy-remote] ERROR: $*" >&2
|
|
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)
|
|
ARCHIVE_PATH="${2:-}"
|
|
shift 2
|
|
;;
|
|
--deploy-path)
|
|
DEPLOY_PATH="${2:-}"
|
|
shift 2
|
|
;;
|
|
--compose-file)
|
|
COMPOSE_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--project-name)
|
|
PROJECT_NAME="${2:-}"
|
|
shift 2
|
|
;;
|
|
--service)
|
|
SERVICE_NAME="${2:-}"
|
|
shift 2
|
|
;;
|
|
--nginx-source)
|
|
NGINX_SOURCE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--nginx-target)
|
|
NGINX_TARGET="${2:-}"
|
|
shift 2
|
|
;;
|
|
--nginx-compose-file)
|
|
NGINX_COMPOSE_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--nginx-project-name)
|
|
NGINX_PROJECT_NAME="${2:-}"
|
|
shift 2
|
|
;;
|
|
--maintenance-host-dir)
|
|
MAINTENANCE_HOST_DIR="${2:-}"
|
|
shift 2
|
|
;;
|
|
--maintenance-flag)
|
|
MAINTENANCE_FLAG_PATH="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--seed-admin-data)
|
|
SEED_ADMIN_DATA=1
|
|
shift 1
|
|
;;
|
|
*)
|
|
fail "Unknown argument: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
[[ -n "$ARCHIVE_PATH" ]] || fail "--archive is required"
|
|
[[ -n "$DEPLOY_PATH" ]] || fail "--deploy-path is required"
|
|
[[ -n "$COMPOSE_FILE" ]] || fail "--compose-file is required"
|
|
[[ -n "$PROJECT_NAME" ]] || fail "--project-name is required"
|
|
if [[ -n "$SERVICE_NAME" ]]; then
|
|
SERVICE_NAME="$(printf '%s' "$SERVICE_NAME" | xargs)"
|
|
fi
|
|
[[ "$DEPLOY_PATH" != "/" ]] || fail "Refusing to deploy to /"
|
|
[[ -f "$ARCHIVE_PATH" ]] || fail "Archive not found: $ARCHIVE_PATH"
|
|
|
|
nginx_args=("$NGINX_SOURCE" "$NGINX_TARGET" "$NGINX_COMPOSE_FILE" "$NGINX_PROJECT_NAME")
|
|
nginx_args_present=0
|
|
for value in "${nginx_args[@]}"; do
|
|
if [[ -n "$value" ]]; then
|
|
nginx_args_present=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
if (( nginx_args_present )); then
|
|
[[ -n "$NGINX_SOURCE" ]] || fail "--nginx-source is required when nginx deployment is enabled"
|
|
[[ -n "$NGINX_TARGET" ]] || fail "--nginx-target is required when nginx deployment is enabled"
|
|
[[ -n "$NGINX_COMPOSE_FILE" ]] || fail "--nginx-compose-file is required when nginx deployment is enabled"
|
|
[[ -n "$NGINX_PROJECT_NAME" ]] || fail "--nginx-project-name is required when nginx deployment is enabled"
|
|
[[ -n "$MAINTENANCE_HOST_DIR" ]] || fail "--maintenance-host-dir is required when nginx deployment is enabled"
|
|
[[ -n "$MAINTENANCE_FLAG_PATH" ]] || fail "--maintenance-flag is required when nginx deployment is enabled"
|
|
fi
|
|
|
|
if docker compose version >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(docker compose)
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(docker-compose)
|
|
else
|
|
fail "Docker Compose is not installed on the server"
|
|
fi
|
|
|
|
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/goodwalk-deploy.XXXXXX")"
|
|
MAINTENANCE_ACTIVE=0
|
|
|
|
clear_maintenance_flag() {
|
|
if (( MAINTENANCE_ACTIVE )) && (( nginx_args_present )); then
|
|
echo "[deploy-remote] Clearing maintenance flag at $MAINTENANCE_FLAG_PATH"
|
|
rm -f "$MAINTENANCE_FLAG_PATH" || true
|
|
MAINTENANCE_ACTIVE=0
|
|
fi
|
|
}
|
|
|
|
cleanup() {
|
|
clear_maintenance_flag
|
|
rm -rf "$STAGING_DIR"
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
echo "[deploy-remote] Deploying main Goodwalk stack"
|
|
echo "[deploy-remote] Target deployment path: $DEPLOY_PATH"
|
|
echo "[deploy-remote] Compose file: $COMPOSE_FILE"
|
|
echo "[deploy-remote] Docker project: $PROJECT_NAME"
|
|
if [[ -n "$SERVICE_NAME" ]]; then
|
|
echo "[deploy-remote] Target service: $SERVICE_NAME"
|
|
fi
|
|
if (( nginx_args_present )); then
|
|
echo "[deploy-remote] Nginx config source: $NGINX_SOURCE"
|
|
echo "[deploy-remote] Nginx config target: $NGINX_TARGET"
|
|
echo "[deploy-remote] Nginx compose file: $NGINX_COMPOSE_FILE"
|
|
echo "[deploy-remote] Nginx project: $NGINX_PROJECT_NAME"
|
|
echo "[deploy-remote] Maintenance host dir: $MAINTENANCE_HOST_DIR"
|
|
echo "[deploy-remote] Maintenance flag path: $MAINTENANCE_FLAG_PATH"
|
|
fi
|
|
echo "[deploy-remote] Staging archive in: $STAGING_DIR"
|
|
|
|
mkdir -p "$DEPLOY_PATH"
|
|
tar -xzf "$ARCHIVE_PATH" -C "$STAGING_DIR"
|
|
|
|
[[ -f "$STAGING_DIR/$COMPOSE_FILE" ]] || fail "Compose file missing from uploaded archive: $COMPOSE_FILE"
|
|
|
|
if [[ -f "$DEPLOY_PATH/.env" ]]; then
|
|
echo "[deploy-remote] Preserving existing $DEPLOY_PATH/.env"
|
|
fi
|
|
|
|
echo "[deploy-remote] Copying application files into $DEPLOY_PATH"
|
|
if command -v rsync >/dev/null 2>&1; then
|
|
rsync -a \
|
|
--exclude '.env' \
|
|
--exclude '.env.*' \
|
|
"$STAGING_DIR"/ "$DEPLOY_PATH"/
|
|
else
|
|
while IFS= read -r -d '' item; do
|
|
relative_path="${item#"$STAGING_DIR"/}"
|
|
|
|
if [[ "$relative_path" == ".env" || "$relative_path" == .env.* ]]; then
|
|
continue
|
|
fi
|
|
|
|
destination="$DEPLOY_PATH/$relative_path"
|
|
|
|
if [[ -d "$item" ]]; then
|
|
mkdir -p "$destination"
|
|
continue
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$destination")"
|
|
cp -f "$item" "$destination"
|
|
done < <(find "$STAGING_DIR" -mindepth 1 -print0)
|
|
fi
|
|
|
|
[[ -f "$DEPLOY_PATH/$COMPOSE_FILE" ]] || fail "Compose file missing after copy: $DEPLOY_PATH/$COMPOSE_FILE"
|
|
|
|
if [[ ! -f "$DEPLOY_PATH/.env" ]]; then
|
|
if [[ -f "$DEPLOY_PATH/deploy.env.template" ]]; then
|
|
echo "[deploy-remote] No remote .env found. Creating $DEPLOY_PATH/.env from deploy.env.template"
|
|
cp "$DEPLOY_PATH/deploy.env.template" "$DEPLOY_PATH/.env"
|
|
else
|
|
fail "Remote .env is missing and deploy.env.template was not uploaded"
|
|
fi
|
|
fi
|
|
|
|
merge_env_file() {
|
|
local template="$1"
|
|
local live="$2"
|
|
|
|
[[ -f "$template" ]] || { echo "[deploy-remote] No env template at $template, skipping merge"; return 0; }
|
|
[[ -f "$live" ]] || { echo "[deploy-remote] No live .env at $live, skipping merge"; return 0; }
|
|
|
|
local added diffs backup
|
|
added="$(mktemp)"
|
|
diffs="$(mktemp)"
|
|
backup="${live}.bak.$(date -u +%Y%m%dT%H%M%SZ)"
|
|
|
|
awk -v live="$live" -v added_log="$added" -v diff_log="$diffs" '
|
|
function trim(s) { sub(/^[ \t]+/,"",s); sub(/[ \t]+$/,"",s); return s }
|
|
BEGIN {
|
|
while ((getline line < live) > 0) {
|
|
if (line ~ /^[ \t]*#/ || line ~ /^[ \t]*$/) continue
|
|
eq = index(line, "=")
|
|
if (eq == 0) continue
|
|
k = trim(substr(line, 1, eq-1))
|
|
v = substr(line, eq+1)
|
|
live_keys[k] = v
|
|
live_seen[k] = 1
|
|
}
|
|
close(live)
|
|
}
|
|
/^[ \t]*#/ || /^[ \t]*$/ { next }
|
|
{
|
|
eq = index($0, "=")
|
|
if (eq == 0) next
|
|
k = trim(substr($0, 1, eq-1))
|
|
v = substr($0, eq+1)
|
|
if (!(k in live_seen)) {
|
|
print k "=" v >> added_log
|
|
} else if (live_keys[k] != v) {
|
|
print k " (template=" v " | live=" live_keys[k] ")" >> diff_log
|
|
}
|
|
}
|
|
' "$template"
|
|
|
|
if [[ -s "$added" ]]; then
|
|
cp "$live" "$backup"
|
|
echo "[deploy-remote] Adding env keys present in template but missing from $live:"
|
|
sed 's/^/ + /' "$added"
|
|
{
|
|
printf '\n# Appended by deploy-remote.sh on %s from deploy.env.template\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
cat "$added"
|
|
} >> "$live"
|
|
echo "[deploy-remote] Backup of previous .env written to $backup"
|
|
else
|
|
echo "[deploy-remote] .env is up to date with template (no missing keys)"
|
|
fi
|
|
|
|
if [[ -s "$diffs" ]]; then
|
|
echo "[deploy-remote] NOTE: these keys exist in both files but values differ. Live values are PRESERVED:"
|
|
sed 's/^/ ! /' "$diffs"
|
|
echo "[deploy-remote] If a live value is stale (e.g. an old OWNER_EMAIL), edit $live and re-deploy."
|
|
fi
|
|
|
|
rm -f "$added" "$diffs"
|
|
}
|
|
|
|
merge_env_file "$DEPLOY_PATH/deploy.env.template" "$DEPLOY_PATH/.env"
|
|
|
|
cd "$DEPLOY_PATH"
|
|
|
|
echo "[deploy-remote] Validating compose configuration"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" config >/dev/null
|
|
|
|
if [[ -n "$SERVICE_NAME" ]]; then
|
|
AVAILABLE_SERVICES="$("${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" config --services)"
|
|
if ! grep -Fxq "$SERVICE_NAME" <<<"$AVAILABLE_SERVICES"; then
|
|
fail "Service '$SERVICE_NAME' was not found in $COMPOSE_FILE. Available services: $(tr '\n' ',' <<<"$AVAILABLE_SERVICES" | sed 's/,$//')"
|
|
fi
|
|
fi
|
|
|
|
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"
|
|
|
|
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"
|
|
[[ -f "$MAINTENANCE_LOGO_SRC" ]] || fail "Maintenance logo missing from deployment payload: $MAINTENANCE_LOGO_SRC"
|
|
|
|
# Pre-flight: the nginx container must have a bind mount whose source is
|
|
# MAINTENANCE_HOST_DIR and whose destination is /var/www/maintenance. If the
|
|
# one-time droplet setup has not been done, fail fast with a clear message
|
|
# rather than silently serving stale content.
|
|
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.
|
|
Expected: ${MAINTENANCE_HOST_DIR}:/var/www/maintenance:ro
|
|
One-time setup on the droplet:
|
|
mkdir -p ${MAINTENANCE_HOST_DIR}/m
|
|
# add this volume to ${NGINX_COMPOSE_FILE}:
|
|
# - ${MAINTENANCE_HOST_DIR}:/var/www/maintenance:ro
|
|
${COMPOSE_CMD[*]} -p ${NGINX_PROJECT_NAME} -f ${NGINX_COMPOSE_FILE} up -d"
|
|
fi
|
|
|
|
FLAG_DIR="$(dirname "$MAINTENANCE_FLAG_PATH")"
|
|
[[ -d "$FLAG_DIR" ]] || fail "Maintenance flag directory does not exist on host: $FLAG_DIR"
|
|
|
|
echo "[deploy-remote] Writing maintenance assets to host bind dir: $MAINTENANCE_HOST_DIR"
|
|
mkdir -p "$MAINTENANCE_HOST_DIR/m"
|
|
install -m 0644 "$MAINTENANCE_HTML_SRC" "$MAINTENANCE_HOST_DIR/maintenance.html"
|
|
install -m 0644 "$MAINTENANCE_LOGO_SRC" "$MAINTENANCE_HOST_DIR/m/logo.png"
|
|
|
|
echo "[deploy-remote] Updating shared nginx config (pre-rebuild) so maintenance routing is active"
|
|
mkdir -p "$(dirname "$NGINX_TARGET")"
|
|
cp "$DEPLOY_PATH/$NGINX_SOURCE" "$NGINX_TARGET"
|
|
|
|
echo "[deploy-remote] Validating nginx configuration"
|
|
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t
|
|
|
|
echo "[deploy-remote] Reloading shared nginx so the new config (incl. maintenance routing) is live"
|
|
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -s reload
|
|
|
|
echo "[deploy-remote] Engaging maintenance page via host flag: $MAINTENANCE_FLAG_PATH"
|
|
: > "$MAINTENANCE_FLAG_PATH"
|
|
MAINTENANCE_ACTIVE=1
|
|
fi
|
|
|
|
if [[ "$SEED_ADMIN_DATA" -eq 1 ]]; then
|
|
echo "[deploy-remote] Admin data seed requested: mail-api will overwrite admin_kv from JSON on next boot"
|
|
export ADMIN_DATA_SEED_FROM_JSON="force"
|
|
else
|
|
export ADMIN_DATA_SEED_FROM_JSON="auto"
|
|
fi
|
|
|
|
if [[ -n "$SERVICE_NAME" ]]; then
|
|
echo "[deploy-remote] Stopping only the Goodwalk service: $SERVICE_NAME"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop "$SERVICE_NAME" || true
|
|
|
|
echo "[deploy-remote] Rebuilding and starting only the Goodwalk service: $SERVICE_NAME"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d --build "$SERVICE_NAME"
|
|
else
|
|
echo "[deploy-remote] Stopping only the Goodwalk project containers"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop || true
|
|
|
|
echo "[deploy-remote] Rebuilding and starting only the Goodwalk project containers"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d --build --remove-orphans
|
|
fi
|
|
|
|
echo "[deploy-remote] Current Goodwalk container status"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" ps
|
|
|
|
if [[ -z "$SERVICE_NAME" || "$SERVICE_NAME" == "app" || "$SERVICE_NAME" == "db" ]]; then
|
|
echo "[deploy-remote] Syncing homepage content into PostgreSQL"
|
|
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" exec -T app node scripts/sync-homepage-content.mjs
|
|
fi
|
|
|
|
clear_maintenance_flag
|
|
|
|
echo "[deploy-remote] Remote deployment finished"
|