Some checks failed
Basil CI/CD Pipeline / Shared Package Tests (push) Successful in 3m18s
Basil CI/CD Pipeline / Code Linting (push) Successful in 3m39s
Basil CI/CD Pipeline / Web Tests (push) Successful in 4m1s
Basil CI/CD Pipeline / API Tests (push) Failing after 4m7s
Basil CI/CD Pipeline / Trigger Deployment (push) Has been skipped
Basil CI/CD Pipeline / Security Scanning (push) Successful in 3m42s
Basil CI/CD Pipeline / Build All Packages (push) Has been skipped
Basil CI/CD Pipeline / E2E Tests (push) Has been skipped
Basil CI/CD Pipeline / Build & Push Docker Images (push) Has been skipped
Moves migration handling into the pipeline and production deploy so schema changes ship atomically with the code that depends on them. Previously migrations were manual and the migrations/ directory was gitignored, which caused silent drift between environments. - Track packages/api/prisma/migrations/ in git (including the baseline 20260416000000_init and the family-tenant delta). - Add `prisma:deploy` script that runs `prisma migrate deploy` (the non-interactive, CI-safe command). `prisma:migrate` still maps to `migrate dev` for local authoring. - Pipeline test-api and e2e-tests jobs now use `prisma:deploy` and test-api adds a drift check (`prisma migrate diff --exit-code`) that fails the build if schema.prisma has changes without a corresponding migration. - deploy.sh runs migrations against prod using `docker run --rm` with the freshly pulled API image before restarting containers, so a failing migration aborts the deploy with the old containers still serving traffic. The [skip-deploy] tag avoids re-triggering deployment for this infrastructure commit; the updated deploy.sh must be pulled to the production host out-of-band before the next deployment benefits from the new migration step. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
261 lines
7.0 KiB
Bash
Executable File
261 lines
7.0 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Basil Deployment Script
|
|
# This script pulls the latest Docker images and restarts the containers
|
|
|
|
set -e # Exit on error
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
|
|
# Load environment variables from .env file if it exists
|
|
if [ -f "$PROJECT_DIR/.env" ]; then
|
|
set -a # automatically export all variables
|
|
source "$PROJECT_DIR/.env"
|
|
set +a
|
|
fi
|
|
LOG_FILE="$PROJECT_DIR/deploy.log"
|
|
BACKUP_DIR="$PROJECT_DIR/backups"
|
|
DOCKER_REGISTRY="${DOCKER_REGISTRY:-harbor.pkartchner.com}"
|
|
DOCKER_USERNAME="${DOCKER_USERNAME}"
|
|
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Logging function
|
|
log() {
|
|
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR:${NC} $1" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
warning() {
|
|
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING:${NC} $1" | tee -a "$LOG_FILE"
|
|
}
|
|
|
|
# Check if Docker is running
|
|
check_docker() {
|
|
if ! docker info > /dev/null 2>&1; then
|
|
error "Docker is not running. Please start Docker and try again."
|
|
exit 1
|
|
fi
|
|
log "Docker is running"
|
|
}
|
|
|
|
# Login to Harbor registry
|
|
login_to_harbor() {
|
|
log "Logging in to Harbor registry..."
|
|
|
|
if [ -z "$HARBOR_PASSWORD" ]; then
|
|
error "HARBOR_PASSWORD environment variable not set"
|
|
exit 1
|
|
fi
|
|
|
|
echo "$HARBOR_PASSWORD" | docker login "$DOCKER_REGISTRY" \
|
|
--username "robot\$${DOCKER_USERNAME}+basil-cicd" \
|
|
--password-stdin || {
|
|
error "Failed to login to Harbor"
|
|
exit 1
|
|
}
|
|
|
|
log "Successfully logged in to Harbor"
|
|
}
|
|
|
|
# Create backup before deployment
|
|
create_backup() {
|
|
log "Creating pre-deployment backup..."
|
|
|
|
# Ensure backup directory exists
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
# Note: Automatic API backup is skipped because port 3001 is not exposed to localhost
|
|
# To create a backup manually, use: docker exec basil-api npm run backup
|
|
log "Skipping automatic backup (API port not exposed to host)"
|
|
log "Manual backup command: docker exec basil-api npm run backup"
|
|
}
|
|
|
|
# Pull latest images from registry
|
|
pull_images() {
|
|
log "Pulling latest Docker images..."
|
|
|
|
if [ -z "$DOCKER_USERNAME" ]; then
|
|
error "DOCKER_USERNAME environment variable not set"
|
|
exit 1
|
|
fi
|
|
|
|
# Pull API image
|
|
log "Pulling API image: ${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-api:${IMAGE_TAG}"
|
|
docker pull "${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-api:${IMAGE_TAG}" || {
|
|
error "Failed to pull API image"
|
|
exit 1
|
|
}
|
|
|
|
# Pull Web image
|
|
log "Pulling Web image: ${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-web:${IMAGE_TAG}"
|
|
docker pull "${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-web:${IMAGE_TAG}" || {
|
|
error "Failed to pull Web image"
|
|
exit 1
|
|
}
|
|
|
|
log "Successfully pulled all images"
|
|
}
|
|
|
|
# Update docker-compose.yml to use registry images
|
|
update_docker_compose() {
|
|
log "Updating docker-compose configuration..."
|
|
|
|
# Create docker-compose.override.yml to use registry images
|
|
cat > "$PROJECT_DIR/docker-compose.override.yml" <<EOF
|
|
services:
|
|
api:
|
|
image: ${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-api:${IMAGE_TAG}
|
|
build:
|
|
# Override build to prevent building from source
|
|
context: .
|
|
dockerfile: packages/api/Dockerfile
|
|
|
|
web:
|
|
image: ${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-web:${IMAGE_TAG}
|
|
build:
|
|
context: .
|
|
dockerfile: packages/web/Dockerfile
|
|
EOF
|
|
|
|
log "Docker Compose override file created"
|
|
}
|
|
|
|
# Apply database migrations using the newly-pulled API image.
|
|
# Runs before restart so a failed migration leaves the old containers running.
|
|
run_migrations() {
|
|
log "Applying database migrations..."
|
|
|
|
if [ -z "$DATABASE_URL" ]; then
|
|
error "DATABASE_URL not set in .env — cannot apply migrations"
|
|
exit 1
|
|
fi
|
|
|
|
local API_IMAGE="${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-api:${IMAGE_TAG}"
|
|
|
|
# Use --network=host so the container can reach the same DB host the app uses.
|
|
# schema.prisma and migrations/ ship inside the API image.
|
|
docker run --rm \
|
|
--network host \
|
|
-e DATABASE_URL="$DATABASE_URL" \
|
|
"$API_IMAGE" \
|
|
npx prisma migrate deploy || {
|
|
error "Migration failed — aborting deploy. Old containers are still running."
|
|
exit 1
|
|
}
|
|
|
|
log "Migrations applied successfully"
|
|
}
|
|
|
|
# Restart containers
|
|
restart_containers() {
|
|
log "Restarting containers..."
|
|
|
|
cd "$PROJECT_DIR"
|
|
|
|
# Stop containers
|
|
log "Stopping containers..."
|
|
docker compose down || warning "Failed to stop some containers"
|
|
|
|
# Start containers with new images
|
|
log "Starting containers with new images..."
|
|
docker compose up -d || {
|
|
error "Failed to start containers"
|
|
exit 1
|
|
}
|
|
|
|
log "Containers restarted successfully"
|
|
}
|
|
|
|
# Health check
|
|
health_check() {
|
|
log "Performing health checks..."
|
|
|
|
# Wait for containers to be running
|
|
log "Waiting for containers to start..."
|
|
sleep 5
|
|
|
|
# Check if API container is running
|
|
if ! docker ps | grep -q basil-api; then
|
|
error "API container is not running"
|
|
docker compose logs api
|
|
exit 1
|
|
fi
|
|
log "API container is running"
|
|
|
|
# Check if Web container is running
|
|
if ! docker ps | grep -q basil-web; then
|
|
error "Web container is not running"
|
|
docker compose logs web
|
|
exit 1
|
|
fi
|
|
log "Web container is running"
|
|
|
|
# Wait for API to finish initializing (check logs for startup message)
|
|
log "Waiting for API to finish initialization..."
|
|
MAX_RETRIES=30
|
|
RETRY_COUNT=0
|
|
|
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
if docker compose logs api 2>&1 | grep -q "Basil API server running"; then
|
|
log "API has finished initialization"
|
|
break
|
|
fi
|
|
|
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
|
|
warning "API startup message not detected after $MAX_RETRIES attempts"
|
|
log "API container is running, continuing anyway..."
|
|
break
|
|
fi
|
|
|
|
sleep 2
|
|
done
|
|
|
|
log "All health checks passed"
|
|
}
|
|
|
|
# Cleanup old images
|
|
cleanup_old_images() {
|
|
log "Cleaning up old Docker images..."
|
|
docker image prune -f > /dev/null 2>&1 || warning "Failed to prune some images"
|
|
log "Cleanup complete"
|
|
}
|
|
|
|
# Main deployment flow
|
|
main() {
|
|
log "========================================="
|
|
log "Starting Basil deployment"
|
|
log "Registry: ${DOCKER_REGISTRY}"
|
|
log "Username: ${DOCKER_USERNAME}"
|
|
log "Tag: ${IMAGE_TAG}"
|
|
log "========================================="
|
|
|
|
check_docker
|
|
login_to_harbor
|
|
create_backup
|
|
pull_images
|
|
run_migrations
|
|
update_docker_compose
|
|
restart_containers
|
|
health_check
|
|
cleanup_old_images
|
|
|
|
log "========================================="
|
|
log "Deployment completed successfully!"
|
|
log "========================================="
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|