#!/usr/bin/env bash # deploy.sh — Build and deploy OTSSignsOrchestrator to a Docker Swarm manager over SSH set -euo pipefail # ── Configuration ───────────────────────────────────────────────────────────── SWARM_HOST="${SWARM_HOST:-}" # e.g. user@10.0.0.1 or set via env IMAGE_NAME="${IMAGE_NAME:-otssigns-orchestrator}" IMAGE_TAG="${IMAGE_TAG:-latest}" STACK_NAME="${STACK_NAME:-otssigns}" COMPOSE_FILE="docker-compose.yml" REMOTE_DIR="${REMOTE_DIR:-/opt/otssigns}" ENV_FILE="${ENV_FILE:-.env}" # local .env file with secrets # ── Validate ────────────────────────────────────────────────────────────────── if [[ -z "$SWARM_HOST" ]]; then echo "ERROR: SWARM_HOST is not set." echo " Usage: SWARM_HOST=user@host ./deploy.sh" echo " Or set SWARM_HOST in your environment." exit 1 fi if [[ ! -f "$COMPOSE_FILE" ]]; then echo "ERROR: $COMPOSE_FILE not found. Run this script from the repository root." exit 1 fi FULL_IMAGE="${IMAGE_NAME}:${IMAGE_TAG}" echo "==> Deploying ${FULL_IMAGE} to swarm manager: ${SWARM_HOST}" echo " Stack: ${STACK_NAME}" echo " Remote dir: ${REMOTE_DIR}" echo "" # ── Step 1: Build the image locally ────────────────────────────────────────── echo "==> [1/5] Building Docker image..." docker build -t "${FULL_IMAGE}" . # ── Step 2: Save and stream the image to the remote host ───────────────────── echo "==> [2/5] Transferring image to ${SWARM_HOST}..." docker save "${FULL_IMAGE}" | ssh "${SWARM_HOST}" "docker load" # ── Step 3: Prepare the remote directory and upload compose file + env ──────── echo "==> [3/5] Uploading compose file and environment..." ssh "${SWARM_HOST}" "mkdir -p ${REMOTE_DIR}" scp "${COMPOSE_FILE}" "${SWARM_HOST}:${REMOTE_DIR}/docker-compose.yml" if [[ -f "$ENV_FILE" ]]; then scp "${ENV_FILE}" "${SWARM_HOST}:${REMOTE_DIR}/.env" echo " Uploaded ${ENV_FILE} -> ${REMOTE_DIR}/.env" else echo " WARNING: No .env file found at '${ENV_FILE}'. Secrets must already exist on the remote host." fi # ── Step 4: Apply EF Core migrations before deploying the new stack ─────────── # The app runs migrations on startup (UseMigrationsEndPoint / MigrateAsync in Program.cs). # If you need explicit pre-deploy migrations, uncomment the block below and # ensure the remote host can reach PostgreSQL directly. # # echo "==> [3b] Running EF Core migrations..." # ssh "${SWARM_HOST}" "docker run --rm --env-file ${REMOTE_DIR}/.env \ # -e ConnectionStrings__OrchestratorDb='...' \ # ${FULL_IMAGE} dotnet OTSSignsOrchestrator.dll --migrate-only" # ── Step 5: Deploy the stack ────────────────────────────────────────────────── echo "==> [4/5] Deploying stack '${STACK_NAME}'..." ssh "${SWARM_HOST}" "cd ${REMOTE_DIR} && \ docker stack deploy \ --compose-file docker-compose.yml \ --with-registry-auth \ --prune \ ${STACK_NAME}" # ── Step 6: Verify rollout ──────────────────────────────────────────────────── echo "==> [5/5] Waiting for service to converge..." ssh "${SWARM_HOST}" "docker service ls --filter name=${STACK_NAME}" echo "" echo "==> Deploy complete." echo " Run the following to stream logs:" echo " ssh ${SWARM_HOST} \"docker service logs -f ${STACK_NAME}_app\""