fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled
Some checks failed
Deploy Application / deploy (push) Has been cancelled
This commit is contained in:
306
deployment/scripts/deploy-production.sh
Executable file
306
deployment/scripts/deploy-production.sh
Executable file
@@ -0,0 +1,306 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# ============================================================================
|
||||
# Production Deployment Script
|
||||
# ============================================================================
|
||||
# Based on .gitea/workflows/deploy-production.yml
|
||||
# Deploys to production environment at michaelschiemer.de
|
||||
# ============================================================================
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
REGISTRY="${REGISTRY:-localhost:5000}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-framework}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
||||
COMPOSE_PROJECT_NAME="framework-production"
|
||||
|
||||
# Remote server configuration (from environment or defaults)
|
||||
REMOTE_USER="${PRODUCTION_USER:-deploy}"
|
||||
REMOTE_HOST="${PRODUCTION_HOST:-michaelschiemer.de}"
|
||||
REMOTE_PORT="${PRODUCTION_SSH_PORT:-22}"
|
||||
REMOTE_PATH="/opt/framework-production"
|
||||
|
||||
# Deployment options
|
||||
SKIP_BACKUP="${SKIP_BACKUP:-false}"
|
||||
FORCE_REBUILD="${FORCE_REBUILD:-false}"
|
||||
|
||||
# Validation
|
||||
if [ -z "$REMOTE_HOST" ]; then
|
||||
echo -e "${RED}❌ Error: PRODUCTION_HOST environment variable is required${NC}"
|
||||
echo "Export PRODUCTION_HOST before running this script:"
|
||||
echo " export PRODUCTION_HOST=michaelschiemer.de"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=================================================="
|
||||
echo "🚀 Starting Production Deployment"
|
||||
echo "=================================================="
|
||||
echo "Registry: ${REGISTRY}"
|
||||
echo "Image: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
echo "Remote: ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
|
||||
echo "Path: ${REMOTE_PATH}"
|
||||
echo "Skip Backup: ${SKIP_BACKUP}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Build Docker image
|
||||
echo -e "${GREEN}[1/8] Building Docker image...${NC}"
|
||||
docker build \
|
||||
--file docker/php/Dockerfile \
|
||||
--tag "${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" \
|
||||
--build-arg ENV=production \
|
||||
--build-arg COMPOSER_INSTALL_FLAGS="--no-dev --optimize-autoloader --no-interaction" \
|
||||
.
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Docker build failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Push image to registry
|
||||
echo -e "${GREEN}[2/8] Pushing image to registry...${NC}"
|
||||
docker push "${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Docker push failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: Prepare deployment files
|
||||
echo -e "${GREEN}[3/8] Preparing deployment files...${NC}"
|
||||
rm -rf deployment-production-tmp
|
||||
mkdir -p deployment-production-tmp
|
||||
|
||||
cp docker-compose.base.yml deployment-production-tmp/
|
||||
cp docker-compose.prod.yml deployment-production-tmp/
|
||||
cp -r docker deployment-production-tmp/
|
||||
|
||||
# Create deployment script
|
||||
cat > deployment-production-tmp/deploy.sh << 'DEPLOY_SCRIPT'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=================================================="
|
||||
echo "Starting Production Deployment on Server"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Database backup (unless explicitly skipped)
|
||||
if [ "${SKIP_BACKUP}" != "true" ]; then
|
||||
echo "[0/6] Creating database backup..."
|
||||
BACKUP_FILE="backup_$(date +%Y%m%d_%H%M%S).sql"
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
|
||||
php console.php db:backup --output="/var/www/html/storage/backups/${BACKUP_FILE}" || {
|
||||
echo "⚠️ Database backup failed - deployment aborted"
|
||||
exit 1
|
||||
}
|
||||
echo "✅ Database backup created: ${BACKUP_FILE}"
|
||||
else
|
||||
echo "⚠️ Database backup skipped (not recommended for production)"
|
||||
fi
|
||||
|
||||
# Pull latest images
|
||||
echo "[1/6] Pulling latest Docker images..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml pull
|
||||
|
||||
# Stop existing containers gracefully
|
||||
echo "[2/6] Stopping existing containers (graceful shutdown)..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml stop
|
||||
|
||||
# Start new containers
|
||||
echo "[3/6] Starting new containers..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Wait for services to be healthy (longer timeout for production)
|
||||
echo "[4/6] Waiting for services to be healthy..."
|
||||
sleep 30
|
||||
|
||||
# Run database migrations
|
||||
echo "[5/6] Running database migrations..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
|
||||
php console.php db:migrate --force || {
|
||||
echo "⚠️ Database migration failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Verify deployment
|
||||
echo "[6/6] Verifying deployment..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# Cleanup old containers
|
||||
echo "Cleaning up old containers..."
|
||||
docker system prune -f
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo "✅ Production Deployment Complete"
|
||||
echo "=================================================="
|
||||
DEPLOY_SCRIPT
|
||||
|
||||
chmod +x deployment-production-tmp/deploy.sh
|
||||
|
||||
# Step 4: Create remote directory and backup
|
||||
echo -e "${GREEN}[4/8] Creating remote directory and backup...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
mkdir -p ${REMOTE_PATH}
|
||||
cd ${REMOTE_PATH}
|
||||
|
||||
# Backup current deployment
|
||||
if [ -d 'current' ]; then
|
||||
echo 'Backing up current deployment...'
|
||||
timestamp=\$(date +%Y%m%d_%H%M%S)
|
||||
mv current backup_\${timestamp}
|
||||
|
||||
# Keep only last 10 backups for production
|
||||
ls -dt backup_* 2>/dev/null | tail -n +11 | xargs rm -rf 2>/dev/null || true
|
||||
echo 'Backup created: backup_'\${timestamp}
|
||||
fi
|
||||
|
||||
# Create new deployment directory
|
||||
mkdir -p current
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ SSH connection or remote directory creation failed${NC}"
|
||||
rm -rf deployment-production-tmp
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 5: Copy deployment files to server
|
||||
echo -e "${GREEN}[5/8] Copying deployment files to server...${NC}"
|
||||
scp -P "${REMOTE_PORT}" -r deployment-production-tmp/* "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/current/"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ SCP file transfer failed${NC}"
|
||||
rm -rf deployment-production-tmp
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup local temp directory
|
||||
rm -rf deployment-production-tmp
|
||||
|
||||
# Step 6: Execute deployment on server
|
||||
echo -e "${GREEN}[6/8] Executing deployment on server...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
export SKIP_BACKUP=${SKIP_BACKUP}
|
||||
./deploy.sh
|
||||
"
|
||||
|
||||
DEPLOY_EXIT_CODE=$?
|
||||
|
||||
if [ $DEPLOY_EXIT_CODE -ne 0 ]; then
|
||||
echo -e "${RED}❌ Deployment execution failed${NC}"
|
||||
echo -e "${YELLOW}⚠️ Attempting automatic rollback...${NC}"
|
||||
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}
|
||||
|
||||
if [ -d \"\$(ls -dt backup_* 2>/dev/null | head -n1)\" ]; then
|
||||
echo '🚨 Rolling back to previous deployment...'
|
||||
latest_backup=\$(ls -dt backup_* | head -n1)
|
||||
|
||||
# Stop current broken deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml down 2>/dev/null || true
|
||||
cd ..
|
||||
|
||||
# Restore backup
|
||||
rm -rf current
|
||||
cp -r \"\$latest_backup\" current
|
||||
|
||||
# Start restored deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Wait for services
|
||||
sleep 30
|
||||
|
||||
# Verify rollback
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
echo '✅ Rollback complete - previous version restored'
|
||||
else
|
||||
echo '❌ No backup available for rollback'
|
||||
echo '⚠️ MANUAL INTERVENTION REQUIRED'
|
||||
fi
|
||||
"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 7: Health check
|
||||
echo -e "${GREEN}[7/8] Performing health checks...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
|
||||
# Wait for services to be fully ready (longer for production)
|
||||
echo 'Waiting 60 seconds for services to initialize...'
|
||||
sleep 60
|
||||
|
||||
# Check container status
|
||||
echo 'Checking container status...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# Check service health
|
||||
echo 'Checking service health...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app php -v || echo 'PHP health check skipped'
|
||||
|
||||
# Check PHP-FPM is running
|
||||
echo 'Checking PHP-FPM process...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app pgrep php-fpm || echo 'PHP-FPM check skipped'
|
||||
|
||||
# Check Redis connection
|
||||
echo 'Checking Redis connection...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-redis redis-cli ping || echo 'Redis check skipped'
|
||||
|
||||
echo ''
|
||||
echo '✅ All health checks passed!'
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ Health check had issues but deployment may still be functional${NC}"
|
||||
fi
|
||||
|
||||
# Step 8: Smoke tests
|
||||
echo -e "${GREEN}[8/8] Running smoke tests...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
echo 'Running production smoke tests...'
|
||||
|
||||
# Test main page
|
||||
curl -f -k https://michaelschiemer.de/ > /dev/null 2>&1 && echo '✅ Main page accessible' || {
|
||||
echo '❌ Main page failed'
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Test API health
|
||||
curl -f -k https://michaelschiemer.de/api/health > /dev/null 2>&1 && echo '✅ API health check passed' || {
|
||||
echo '❌ API health check failed'
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo '✅ Smoke tests completed successfully'
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Smoke tests failed${NC}"
|
||||
echo -e "${YELLOW}⚠️ Deployment completed but smoke tests did not pass${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo -e "${GREEN}✅ Production Deployment Successful${NC}"
|
||||
echo "=================================================="
|
||||
echo "URL: https://michaelschiemer.de"
|
||||
echo "Deployed at: $(date)"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " - Monitor logs: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose logs -f'"
|
||||
echo " - Check status: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose ps'"
|
||||
echo ""
|
||||
235
deployment/scripts/deploy-staging.sh
Executable file
235
deployment/scripts/deploy-staging.sh
Executable file
@@ -0,0 +1,235 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# ============================================================================
|
||||
# Staging Deployment Script
|
||||
# ============================================================================
|
||||
# Based on .gitea/workflows/deploy-staging.yml
|
||||
# Deploys to staging environment at staging.michaelschiemer.de
|
||||
# ============================================================================
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
REGISTRY="${REGISTRY:-localhost:5000}"
|
||||
IMAGE_NAME="${IMAGE_NAME:-framework}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-staging}"
|
||||
COMPOSE_PROJECT_NAME="framework-staging"
|
||||
|
||||
# Remote server configuration (from environment or defaults)
|
||||
REMOTE_USER="${STAGING_USER:-deploy}"
|
||||
REMOTE_HOST="${STAGING_HOST:-staging.michaelschiemer.de}"
|
||||
REMOTE_PORT="${STAGING_SSH_PORT:-22}"
|
||||
REMOTE_PATH="/opt/framework-staging"
|
||||
|
||||
# Validation
|
||||
if [ -z "$REMOTE_HOST" ]; then
|
||||
echo -e "${RED}❌ Error: STAGING_HOST environment variable is required${NC}"
|
||||
echo "Export STAGING_HOST before running this script:"
|
||||
echo " export STAGING_HOST=staging.michaelschiemer.de"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=================================================="
|
||||
echo "🚀 Starting Staging Deployment"
|
||||
echo "=================================================="
|
||||
echo "Registry: ${REGISTRY}"
|
||||
echo "Image: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
echo "Remote: ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
|
||||
echo "Path: ${REMOTE_PATH}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Build Docker image
|
||||
echo -e "${GREEN}[1/7] Building Docker image...${NC}"
|
||||
docker build \
|
||||
--file docker/php/Dockerfile \
|
||||
--tag "${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" \
|
||||
--build-arg ENV=staging \
|
||||
--build-arg COMPOSER_INSTALL_FLAGS="--no-dev --optimize-autoloader --no-interaction" \
|
||||
.
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Docker build failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Push image to registry
|
||||
echo -e "${GREEN}[2/7] Pushing image to registry...${NC}"
|
||||
docker push "${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Docker push failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: Prepare deployment files
|
||||
echo -e "${GREEN}[3/7] Preparing deployment files...${NC}"
|
||||
rm -rf deployment-staging-tmp
|
||||
mkdir -p deployment-staging-tmp
|
||||
|
||||
cp docker-compose.base.yml deployment-staging-tmp/
|
||||
cp docker-compose.staging.yml deployment-staging-tmp/
|
||||
cp -r docker deployment-staging-tmp/
|
||||
|
||||
# Create deployment script
|
||||
cat > deployment-staging-tmp/deploy.sh << 'DEPLOY_SCRIPT'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=================================================="
|
||||
echo "Starting Staging Deployment on Server"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Pull latest images
|
||||
echo "[1/5] Pulling latest Docker images..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml pull
|
||||
|
||||
# Stop existing containers
|
||||
echo "[2/5] Stopping existing containers..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml down
|
||||
|
||||
# Start new containers
|
||||
echo "[3/5] Starting new containers..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml up -d
|
||||
|
||||
# Wait for services to be healthy
|
||||
echo "[4/5] Waiting for services to be healthy..."
|
||||
sleep 10
|
||||
|
||||
# Verify health
|
||||
echo "[5/5] Verifying deployment..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo "✅ Staging Deployment Complete"
|
||||
echo "=================================================="
|
||||
DEPLOY_SCRIPT
|
||||
|
||||
chmod +x deployment-staging-tmp/deploy.sh
|
||||
|
||||
# Step 4: Create remote directory and backup
|
||||
echo -e "${GREEN}[4/7] Creating remote directory and backup...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
mkdir -p ${REMOTE_PATH}
|
||||
cd ${REMOTE_PATH}
|
||||
|
||||
# Backup current deployment
|
||||
if [ -d 'current' ]; then
|
||||
echo 'Backing up current deployment...'
|
||||
timestamp=\$(date +%Y%m%d_%H%M%S)
|
||||
mv current backup_\${timestamp}
|
||||
|
||||
# Keep only last 5 backups
|
||||
ls -dt backup_* 2>/dev/null | tail -n +6 | xargs rm -rf 2>/dev/null || true
|
||||
echo 'Backup created: backup_'\${timestamp}
|
||||
fi
|
||||
|
||||
# Create new deployment directory
|
||||
mkdir -p current
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ SSH connection or remote directory creation failed${NC}"
|
||||
rm -rf deployment-staging-tmp
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 5: Copy deployment files to server
|
||||
echo -e "${GREEN}[5/7] Copying deployment files to server...${NC}"
|
||||
scp -P "${REMOTE_PORT}" -r deployment-staging-tmp/* "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/current/"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ SCP file transfer failed${NC}"
|
||||
rm -rf deployment-staging-tmp
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup local temp directory
|
||||
rm -rf deployment-staging-tmp
|
||||
|
||||
# Step 6: Execute deployment on server
|
||||
echo -e "${GREEN}[6/7] Executing deployment on server...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
./deploy.sh
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Deployment execution failed${NC}"
|
||||
echo -e "${YELLOW}⚠️ Attempting automatic rollback...${NC}"
|
||||
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}
|
||||
|
||||
if [ -d \"\$(ls -dt backup_* 2>/dev/null | head -n1)\" ]; then
|
||||
echo 'Rolling back to previous deployment...'
|
||||
latest_backup=\$(ls -dt backup_* | head -n1)
|
||||
|
||||
# Stop current broken deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml down 2>/dev/null || true
|
||||
cd ..
|
||||
|
||||
# Restore backup
|
||||
rm -rf current
|
||||
cp -r \"\$latest_backup\" current
|
||||
|
||||
# Start restored deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml up -d
|
||||
|
||||
echo '✅ Rollback complete!'
|
||||
else
|
||||
echo '❌ No backup available for rollback'
|
||||
fi
|
||||
"
|
||||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 7: Health check
|
||||
echo -e "${GREEN}[7/7] Performing health checks...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
|
||||
# Wait for services to be fully ready
|
||||
echo 'Waiting 30 seconds for services to initialize...'
|
||||
sleep 30
|
||||
|
||||
# Check container status
|
||||
echo 'Checking container status...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
|
||||
# Check service health
|
||||
echo 'Checking service health...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-app php -v || echo 'PHP health check skipped'
|
||||
|
||||
# Test HTTP endpoint (via internal network)
|
||||
echo 'Testing HTTP endpoint...'
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-nginx wget -q -O- http://localhost/health || echo 'Health check endpoint not yet available'
|
||||
|
||||
echo ''
|
||||
echo '✅ Health check complete!'
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ Health check had issues but deployment may still be functional${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo -e "${GREEN}✅ Staging Deployment Successful${NC}"
|
||||
echo "=================================================="
|
||||
echo "URL: https://staging.michaelschiemer.de"
|
||||
echo "Deployed at: $(date)"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " - Monitor logs: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose logs -f'"
|
||||
echo " - Check status: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose ps'"
|
||||
echo ""
|
||||
176
deployment/scripts/deploy.sh
Executable file
176
deployment/scripts/deploy.sh
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# Application Deployment Script
|
||||
# ==============================================================================
|
||||
# Deploys application to staging or production environment
|
||||
# Usage: ./deploy.sh <environment> [options]
|
||||
# ==============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌${NC} $1"
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
ENVIRONMENT=$1
|
||||
BUILD_IMAGES=${2:-false}
|
||||
|
||||
if [ -z "$ENVIRONMENT" ]; then
|
||||
print_error "Usage: $0 <environment> [build]"
|
||||
print_info "Environments: staging, production"
|
||||
print_info "Options: build - Build Docker images before deployment"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$ENVIRONMENT" != "staging" ] && [ "$ENVIRONMENT" != "production" ]; then
|
||||
print_error "Invalid environment: $ENVIRONMENT"
|
||||
print_info "Valid environments: staging, production"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set compose files
|
||||
COMPOSE_BASE="docker-compose.base.yml"
|
||||
if [ "$ENVIRONMENT" = "staging" ]; then
|
||||
COMPOSE_ENV="docker-compose.staging.yml"
|
||||
elif [ "$ENVIRONMENT" = "production" ]; then
|
||||
COMPOSE_ENV="docker-compose.prod.yml"
|
||||
fi
|
||||
|
||||
COMPOSE_FILES="-f $COMPOSE_BASE -f $COMPOSE_ENV"
|
||||
|
||||
print_info "Deploying to $ENVIRONMENT environment..."
|
||||
|
||||
# Check if secrets exist
|
||||
SECRETS_DIR="deployment/secrets/$ENVIRONMENT"
|
||||
if [ ! -d "$SECRETS_DIR" ]; then
|
||||
print_warning "Secrets directory not found: $SECRETS_DIR"
|
||||
print_info "Creating secrets directory..."
|
||||
mkdir -p "$SECRETS_DIR"
|
||||
fi
|
||||
|
||||
MISSING_SECRETS=()
|
||||
REQUIRED_SECRETS=("db_password.txt" "redis_password.txt" "app_key.txt")
|
||||
|
||||
for secret_file in "${REQUIRED_SECRETS[@]}"; do
|
||||
if [ ! -f "$SECRETS_DIR/$secret_file" ]; then
|
||||
MISSING_SECRETS+=("$secret_file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#MISSING_SECRETS[@]} -gt 0 ]; then
|
||||
print_error "Missing required secrets:"
|
||||
for secret in "${MISSING_SECRETS[@]}"; do
|
||||
print_error " - $SECRETS_DIR/$secret"
|
||||
done
|
||||
print_info "See deployment/infrastructure/SECRETS.md for instructions"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if infrastructure networks exist
|
||||
print_info "Checking infrastructure networks..."
|
||||
if ! docker network ls | grep -q "traefik-public"; then
|
||||
print_error "traefik-public network not found"
|
||||
print_info "Please deploy infrastructure stacks first:"
|
||||
print_info " cd deployment/infrastructure && ./deploy.sh traefik"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker network ls | grep -q "app-internal"; then
|
||||
print_error "app-internal network not found"
|
||||
print_info "Please deploy infrastructure stacks first:"
|
||||
print_info " cd deployment/infrastructure && ./deploy.sh postgresql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build images if requested
|
||||
if [ "$BUILD_IMAGES" = "build" ]; then
|
||||
print_info "Building Docker images..."
|
||||
docker compose $COMPOSE_FILES build
|
||||
fi
|
||||
|
||||
# Pull latest images
|
||||
print_info "Pulling latest images..."
|
||||
docker compose $COMPOSE_FILES pull || print_warning "Failed to pull some images, continuing..."
|
||||
|
||||
# Deploy stack
|
||||
print_info "Deploying application stack..."
|
||||
docker compose $COMPOSE_FILES up -d
|
||||
|
||||
# Wait for services to be healthy
|
||||
print_info "Waiting for services to be healthy..."
|
||||
sleep 10
|
||||
|
||||
# Check service status
|
||||
print_info "Checking service status..."
|
||||
docker compose $COMPOSE_FILES ps
|
||||
|
||||
# Health checks
|
||||
print_info "Running health checks..."
|
||||
HEALTH_CHECK_FAILED=0
|
||||
|
||||
# Check PHP service
|
||||
if docker compose $COMPOSE_FILES exec -T php php -v > /dev/null 2>&1; then
|
||||
print_success "PHP service is healthy"
|
||||
else
|
||||
print_error "PHP service health check failed"
|
||||
HEALTH_CHECK_FAILED=1
|
||||
fi
|
||||
|
||||
# Check Redis service
|
||||
if docker compose $COMPOSE_FILES exec -T redis redis-cli ping > /dev/null 2>&1; then
|
||||
print_success "Redis service is healthy"
|
||||
else
|
||||
print_error "Redis service health check failed"
|
||||
HEALTH_CHECK_FAILED=1
|
||||
fi
|
||||
|
||||
# Check Nginx service
|
||||
if docker compose $COMPOSE_FILES exec -T nginx wget --quiet --tries=1 --spider http://localhost/health > /dev/null 2>&1; then
|
||||
print_success "Nginx service is healthy"
|
||||
else
|
||||
print_warning "Nginx health check endpoint not available (this may be normal)"
|
||||
fi
|
||||
|
||||
if [ $HEALTH_CHECK_FAILED -eq 1 ]; then
|
||||
print_error "Some health checks failed. Check logs:"
|
||||
print_info " docker compose $COMPOSE_FILES logs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Deployment to $ENVIRONMENT completed successfully!"
|
||||
|
||||
# Show service URLs
|
||||
if [ "$ENVIRONMENT" = "production" ]; then
|
||||
print_info "Application URL: https://michaelschiemer.de"
|
||||
elif [ "$ENVIRONMENT" = "staging" ]; then
|
||||
print_info "Application URL: https://staging.michaelschiemer.de"
|
||||
fi
|
||||
|
||||
print_info "View logs: docker compose $COMPOSE_FILES logs -f"
|
||||
print_info "View status: docker compose $COMPOSE_FILES ps"
|
||||
|
||||
236
deployment/scripts/rollback.sh
Executable file
236
deployment/scripts/rollback.sh
Executable file
@@ -0,0 +1,236 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# ============================================================================
|
||||
# Rollback Deployment Script
|
||||
# ============================================================================
|
||||
# Restores previous deployment from backup
|
||||
# Based on rollback logic from Gitea workflows
|
||||
# ============================================================================
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Usage function
|
||||
usage() {
|
||||
echo "Usage: $0 <environment> [backup_name]"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " environment staging or production"
|
||||
echo " backup_name (optional) specific backup to restore"
|
||||
echo " If not specified, restores latest backup"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 staging # Restore latest staging backup"
|
||||
echo " $0 production # Restore latest production backup"
|
||||
echo " $0 production backup_20250124_143022 # Restore specific backup"
|
||||
echo ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Validate arguments
|
||||
if [ $# -lt 1 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
ENVIRONMENT="$1"
|
||||
SPECIFIC_BACKUP="${2:-}"
|
||||
|
||||
# Validate environment
|
||||
if [ "$ENVIRONMENT" != "staging" ] && [ "$ENVIRONMENT" != "production" ]; then
|
||||
echo -e "${RED}❌ Error: Invalid environment '${ENVIRONMENT}'${NC}"
|
||||
echo "Must be 'staging' or 'production'"
|
||||
usage
|
||||
fi
|
||||
|
||||
# Set environment-specific variables
|
||||
if [ "$ENVIRONMENT" = "staging" ]; then
|
||||
REMOTE_USER="${STAGING_USER:-deploy}"
|
||||
REMOTE_HOST="${STAGING_HOST:-staging.michaelschiemer.de}"
|
||||
REMOTE_PORT="${STAGING_SSH_PORT:-22}"
|
||||
REMOTE_PATH="/opt/framework-staging"
|
||||
COMPOSE_FILES="docker-compose.base.yml -f docker-compose.staging.yml"
|
||||
else
|
||||
REMOTE_USER="${PRODUCTION_USER:-deploy}"
|
||||
REMOTE_HOST="${PRODUCTION_HOST:-michaelschiemer.de}"
|
||||
REMOTE_PORT="${PRODUCTION_SSH_PORT:-22}"
|
||||
REMOTE_PATH="/opt/framework-production"
|
||||
COMPOSE_FILES="docker-compose.base.yml -f docker-compose.prod.yml"
|
||||
fi
|
||||
|
||||
# Validation
|
||||
if [ -z "$REMOTE_HOST" ]; then
|
||||
echo -e "${RED}❌ Error: ${ENVIRONMENT^^}_HOST environment variable is required${NC}"
|
||||
echo "Export ${ENVIRONMENT^^}_HOST before running this script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=================================================="
|
||||
echo "🔄 Starting Rollback: ${ENVIRONMENT}"
|
||||
echo "=================================================="
|
||||
echo "Remote: ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PORT}"
|
||||
echo "Path: ${REMOTE_PATH}"
|
||||
if [ -n "$SPECIFIC_BACKUP" ]; then
|
||||
echo "Target Backup: ${SPECIFIC_BACKUP}"
|
||||
else
|
||||
echo "Target Backup: Latest available"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Confirm rollback
|
||||
echo -e "${YELLOW}⚠️ WARNING: This will rollback the ${ENVIRONMENT} deployment${NC}"
|
||||
echo -e "${YELLOW} Current deployment will be stopped and replaced with backup${NC}"
|
||||
echo ""
|
||||
read -p "Are you sure you want to continue? (yes/no): " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "yes" ]; then
|
||||
echo "Rollback cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Step 1: List available backups
|
||||
echo -e "${GREEN}[1/5] Listing available backups...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}
|
||||
|
||||
echo 'Available backups:'
|
||||
ls -dt backup_* 2>/dev/null || {
|
||||
echo '❌ No backups found'
|
||||
exit 1
|
||||
}
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ No backups available for rollback${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Determine which backup to restore
|
||||
echo -e "${GREEN}[2/5] Determining backup to restore...${NC}"
|
||||
if [ -n "$SPECIFIC_BACKUP" ]; then
|
||||
BACKUP_TO_RESTORE="$SPECIFIC_BACKUP"
|
||||
echo "Using specified backup: ${BACKUP_TO_RESTORE}"
|
||||
else
|
||||
BACKUP_TO_RESTORE=$(ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}
|
||||
ls -dt backup_* 2>/dev/null | head -n1
|
||||
")
|
||||
echo "Using latest backup: ${BACKUP_TO_RESTORE}"
|
||||
fi
|
||||
|
||||
# Verify backup exists
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}
|
||||
if [ ! -d '${BACKUP_TO_RESTORE}' ]; then
|
||||
echo '❌ Backup ${BACKUP_TO_RESTORE} not found'
|
||||
exit 1
|
||||
fi
|
||||
echo '✅ Backup ${BACKUP_TO_RESTORE} verified'
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Backup verification failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: Stop current deployment
|
||||
echo -e "${GREEN}[3/5] Stopping current deployment...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
|
||||
echo 'Stopping containers...'
|
||||
docker-compose -f ${COMPOSE_FILES} down 2>/dev/null || {
|
||||
echo '⚠️ Some containers may not have stopped cleanly'
|
||||
}
|
||||
|
||||
echo '✅ Current deployment stopped'
|
||||
"
|
||||
|
||||
# Step 4: Restore backup
|
||||
echo -e "${GREEN}[4/5] Restoring backup...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}
|
||||
|
||||
# Backup current (failed) deployment for investigation
|
||||
if [ -d 'current' ]; then
|
||||
failed_backup=\"failed_\$(date +%Y%m%d_%H%M%S)\"
|
||||
echo \"Archiving failed deployment as \${failed_backup}...\"
|
||||
mv current \"\${failed_backup}\"
|
||||
fi
|
||||
|
||||
# Restore backup
|
||||
echo 'Restoring backup ${BACKUP_TO_RESTORE}...'
|
||||
cp -r '${BACKUP_TO_RESTORE}' current
|
||||
|
||||
echo '✅ Backup restored'
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Backup restoration failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 5: Start restored deployment
|
||||
echo -e "${GREEN}[5/5] Starting restored deployment...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
|
||||
echo 'Starting containers...'
|
||||
docker-compose -f ${COMPOSE_FILES} up -d
|
||||
|
||||
# Wait for services
|
||||
echo 'Waiting for services to start...'
|
||||
sleep 30
|
||||
|
||||
# Verify deployment
|
||||
echo 'Verifying deployment...'
|
||||
docker-compose -f ${COMPOSE_FILES} ps
|
||||
|
||||
echo '✅ Restored deployment is running'
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}❌ Failed to start restored deployment${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Health check
|
||||
echo ""
|
||||
echo -e "${GREEN}Performing health check...${NC}"
|
||||
ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" "
|
||||
cd ${REMOTE_PATH}/current
|
||||
|
||||
# Wait a bit more for full initialization
|
||||
sleep 10
|
||||
|
||||
# Check container health
|
||||
healthy_containers=\$(docker-compose -f ${COMPOSE_FILES} ps --filter 'status=running' | wc -l)
|
||||
echo \"Healthy containers: \${healthy_containers}\"
|
||||
|
||||
if [ \"\${healthy_containers}\" -gt 0 ]; then
|
||||
echo '✅ Containers are running'
|
||||
else
|
||||
echo '⚠️ No containers running - manual investigation required'
|
||||
fi
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo -e "${GREEN}✅ Rollback Complete${NC}"
|
||||
echo "=================================================="
|
||||
echo "Environment: ${ENVIRONMENT}"
|
||||
echo "Restored: ${BACKUP_TO_RESTORE}"
|
||||
echo "Completed at: $(date)"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " - Verify application: https://${REMOTE_HOST}"
|
||||
echo " - Monitor logs: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose logs -f'"
|
||||
echo " - Check status: ssh ${REMOTE_USER}@${REMOTE_HOST} 'cd ${REMOTE_PATH}/current && docker-compose ps'"
|
||||
echo ""
|
||||
echo "Failed deployment archived as: failed_YYYYMMDD_HHMMSS (if applicable)"
|
||||
echo "You can investigate the failure by SSH'ing to the server and examining:"
|
||||
echo " ${REMOTE_PATH}/failed_*"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user