#!/usr/bin/env bash set -Eeuo pipefail ARCHIVE_PATH="" DEPLOY_PATH="" COMPOSE_FILE="" PROJECT_NAME="" SERVICE_NAME="" usage() { cat <<'EOF' Usage: deploy-remote.sh --archive --deploy-path --compose-file --project-name deploy-remote.sh --archive --deploy-path --compose-file --project-name [--service ] 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 } 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 ;; -h|--help) usage exit 0 ;; *) 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" 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")" cleanup() { 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 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 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 [[ -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 echo "[deploy-remote] Remote deployment finished"