hp-prod-tracker/deploy.sh
Vadym Samoilenko 7e32bbc430 Add idempotent deploy script
Handles initial deploy and updates: git pull via SSH, docker compose
rebuild, health check with timeout, pre-flight .env validation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 15:08:29 +01:00

96 lines
4.8 KiB
Bash

#!/usr/bin/env bash
# deploy.sh — idempotent deploy script for hp-prod-tracker
# Usage: bash deploy.sh [--skip-pull]
#
# Works for both initial deployment and updates.
# Run from /opt/hp-prod-tracker as any user with docker + git access.
set -euo pipefail
# ─── Config ──────────────────────────────────────────────────────────────────
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_FILE="$APP_DIR/docker-compose.yml"
HEALTH_URL="http://localhost:3001/api/health"
HEALTH_RETRIES=30
HEALTH_INTERVAL=3 # seconds between retries
# ─── Colours ─────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
log() { echo -e "${GREEN}[deploy]${NC} $*"; }
warn() { echo -e "${YELLOW}[deploy]${NC} $*"; }
fail() { echo -e "${RED}[deploy]${NC} $*" >&2; exit 1; }
# ─── Args ─────────────────────────────────────────────────────────────────────
SKIP_PULL=false
for arg in "$@"; do
[[ "$arg" == "--skip-pull" ]] && SKIP_PULL=true
done
cd "$APP_DIR"
# ─── Pre-flight checks ───────────────────────────────────────────────────────
log "Running pre-flight checks..."
command -v docker >/dev/null 2>&1 || fail "docker is not installed"
docker compose version >/dev/null 2>&1 || fail "docker compose plugin is not installed"
command -v git >/dev/null 2>&1 || fail "git is not installed"
[[ -f "$APP_DIR/.env" ]] || fail ".env file not found at $APP_DIR/.env — copy .env.example and fill in the values"
# Warn if critical vars are empty
for var in AUTH_SECRET AUTH_MICROSOFT_ENTRA_ID_ID AUTH_MICROSOFT_ENTRA_ID_SECRET AUTH_MICROSOFT_ENTRA_ID_TENANT_ID AUTH_URL; do
val=$(grep -E "^${var}=" "$APP_DIR/.env" | cut -d= -f2- | tr -d '"' || true)
[[ -z "$val" ]] && warn "WARNING: $var is not set in .env"
done
# ─── Pull latest code ────────────────────────────────────────────────────────
if [[ "$SKIP_PULL" == false ]]; then
log "Pulling latest code..."
# Ensure remote uses SSH (avoids HTTPS credential prompts)
current_remote=$(git remote get-url origin 2>/dev/null || true)
if [[ "$current_remote" == https://* ]]; then
ssh_remote=$(echo "$current_remote" \
| sed 's|https://bitbucket.org/|git@bitbucket.org:|' \
| sed 's|\.git$||')
git remote set-url origin "${ssh_remote}.git"
log "Switched remote to SSH: $(git remote get-url origin)"
fi
git fetch origin
LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse @{u} 2>/dev/null || echo "")
if [[ "$LOCAL" == "$REMOTE" ]]; then
log "Already up to date ($(git rev-parse --short HEAD))"
else
git pull --ff-only || fail "git pull failed — resolve conflicts manually then re-run"
log "Updated to $(git rev-parse --short HEAD)"
fi
else
warn "Skipping git pull (--skip-pull)"
fi
# ─── Deploy ──────────────────────────────────────────────────────────────────
log "Stopping existing containers..."
docker compose -f "$COMPOSE_FILE" down --remove-orphans
log "Building and starting containers (this may take a few minutes)..."
docker compose -f "$COMPOSE_FILE" up -d --build
# ─── Health check ────────────────────────────────────────────────────────────
log "Waiting for app to be healthy..."
attempt=0
until curl -sf "$HEALTH_URL" >/dev/null 2>&1; do
attempt=$((attempt + 1))
if [[ $attempt -ge $HEALTH_RETRIES ]]; then
fail "App did not become healthy after $((HEALTH_RETRIES * HEALTH_INTERVAL))s — check logs:\n docker compose logs app --tail 50"
fi
echo -n "."
sleep $HEALTH_INTERVAL
done
echo ""
# ─── Done ────────────────────────────────────────────────────────────────────
log "Deploy complete!"
log " Commit : $(git rev-parse --short HEAD)$(git log -1 --pretty=%s)"
log " App : https://optical-dev.oliver.solutions/hp-prod-tracker"
log " Logs : docker compose logs -f app"