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>
This commit is contained in:
parent
6fd240860c
commit
7e32bbc430
1 changed files with 96 additions and 0 deletions
96
deploy.sh
Normal file
96
deploy.sh
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/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"
|
||||
Loading…
Add table
Reference in a new issue