feat(deploy): implement blue-green deployment strategy
This commit replaces the previous deployment mechanism with a blue-green strategy to lay the groundwork for zero-downtime deployments. Key changes: Introduces a deploy-blue-green.sh script to manage "blue" and "green" container sets, creating versioned releases. Updates the Anubis gatekeeper template to dynamically route traffic based on the active deployment color, allowing for seamless traffic switching. Modifies Docker Compose files to include color-specific labels and environment variables. Adapts the GitHub Actions workflow to execute the new blue-green deployment process. Removes the old, now-obsolete deployment and health check scripts. Note: Automated rollback on health check failure is not yet implemented. Downgrades can be performed manually by switching the active color.
This commit is contained in:
parent
a58a0e642a
commit
30ce126a07
19 changed files with 1166 additions and 591 deletions
1
scripts/common-lib.sh
Symbolic link
1
scripts/common-lib.sh
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../server/scripts/common-lib.sh
|
||||
207
scripts/deploy-blue-green.sh
Executable file
207
scripts/deploy-blue-green.sh
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Blue-Green deployment script with versioned releases
|
||||
# Usage: ./deploy-blue-green.sh
|
||||
|
||||
# Source common functions
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common-lib.sh"
|
||||
|
||||
# Validate required environment variables
|
||||
require_var "DOCKER_HOST"
|
||||
require_var "REPO_PROJECT_PATH"
|
||||
require_var "REPO_NAME_ONLY"
|
||||
require_var "REPO"
|
||||
require_var "IMAGE_TAR"
|
||||
require_var "ENV_FILE_BASE64"
|
||||
require_var "CF_PEM_CERT"
|
||||
require_var "CF_PEM_CA"
|
||||
require_var "PROD"
|
||||
require_var "PRODUCTION_DOMAIN"
|
||||
require_var "STAGING_DOMAIN"
|
||||
require_var "REPLICA_COUNT"
|
||||
|
||||
validate_deployment_env
|
||||
|
||||
echo "⚙️ Docker host: $DOCKER_HOST"
|
||||
|
||||
# Generate deployment timestamp
|
||||
DEPLOYMENT_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
NEW_RELEASE_PATH="${RELEASES_PATH}/${DEPLOYMENT_TIMESTAMP}"
|
||||
|
||||
# Use Git SHA for image tag (if available, otherwise use timestamp)
|
||||
if [ -n "${GIT_SHA:-}" ]; then
|
||||
IMAGE_TAG="sha-${GIT_SHA:0:7}"
|
||||
else
|
||||
# Fallback for local testing without GIT_SHA
|
||||
IMAGE_TAG="local-${DEPLOYMENT_TIMESTAMP}"
|
||||
fi
|
||||
|
||||
# Check for deployment in progress
|
||||
if is_deployment_in_progress; then
|
||||
echo "⚠️ ERROR: Deployment appears to be in progress (both colors are running)"
|
||||
echo " This might indicate a previous deployment didn't complete properly."
|
||||
echo " Please check the deployment status and clean up any old containers."
|
||||
echo " If you are sure that the deployment is complete, you can run the following command to clean up the old containers:"
|
||||
echo " ssh deploy 'docker compose -p pkmntrade-club-blue down && docker compose -p pkmntrade-club-green down'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current and new colors
|
||||
CURRENT_COLOR=$(get_current_color)
|
||||
NEW_COLOR=$(switch_color "$CURRENT_COLOR")
|
||||
|
||||
echo "📅 Deployment version: ${DEPLOYMENT_TIMESTAMP}"
|
||||
echo "🏷️ Image tag: ${IMAGE_TAG}"
|
||||
echo "🎨 Current: $CURRENT_COLOR → New: $NEW_COLOR"
|
||||
|
||||
echo "🚀 Enable and start docker service"
|
||||
retry run_on_target "sudo systemctl enable --now docker.service"
|
||||
|
||||
echo "💾 Load the new docker image ($IMAGE_TAR)"
|
||||
if [ ! -f "$IMAGE_TAR" ]; then
|
||||
echo "Error: Docker image tar file not found: $IMAGE_TAR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Load the image - Docker handles the transfer via DOCKER_HOST
|
||||
echo "📦 Loading Docker image..."
|
||||
#retry docker load -i "$IMAGE_TAR"
|
||||
|
||||
# Verify the expected image exists
|
||||
echo "🔍 Verifying image ${REPO}:${IMAGE_TAG} exists..."
|
||||
if ! docker images -q "${REPO}:${IMAGE_TAG}" | grep -q .; then
|
||||
echo "❌ Expected image tag ${IMAGE_TAG} not found!"
|
||||
echo "Available tags:"
|
||||
docker images "${REPO}" --format "{{.Tag}}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📁 Create versioned release directory"
|
||||
run_on_target "mkdir -p '${NEW_RELEASE_PATH}'"
|
||||
|
||||
echo "💾 Copy new files to server"
|
||||
if [ -d "./server" ]; then
|
||||
retry scp -pr ./server/* "deploy:${NEW_RELEASE_PATH}/"
|
||||
else
|
||||
echo "⚠️ No server directory found, error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📝 Create new .env file with deployment configuration"
|
||||
printf "%s" "${ENV_FILE_BASE64}" | base64 -d | run_on_target "cat > '${NEW_RELEASE_PATH}/.env' && chmod 600 '${NEW_RELEASE_PATH}/.env'"
|
||||
# Add deployment color and image tag to .env
|
||||
run_on_target "echo 'DEPLOYMENT_COLOR=${NEW_COLOR}' >> '${NEW_RELEASE_PATH}/.env'"
|
||||
run_on_target "echo 'IMAGE_TAG=${IMAGE_TAG}' >> '${NEW_RELEASE_PATH}/.env'"
|
||||
|
||||
# Add domain name based on environment
|
||||
if [ "${PROD}" = "true" ]; then
|
||||
DOMAIN_NAME="${PRODUCTION_DOMAIN:-pkmntrade.club}"
|
||||
else
|
||||
DOMAIN_NAME="${STAGING_DOMAIN:-staging.pkmntrade.club}"
|
||||
fi
|
||||
# if there is a third part to the domain name, remove it
|
||||
BASE_DOMAIN_NAME="${BASE_DOMAIN:-pkmntrade.club}"
|
||||
run_on_target "echo 'DOMAIN_NAME=${DOMAIN_NAME}' >> '${NEW_RELEASE_PATH}/.env'"
|
||||
run_on_target "echo 'BASE_DOMAIN_NAME=${BASE_DOMAIN_NAME}' >> '${NEW_RELEASE_PATH}/.env'"
|
||||
run_on_target "echo 'REPLICA_COUNT=${REPLICA_COUNT}' >> '${NEW_RELEASE_PATH}/.env'"
|
||||
|
||||
echo "🔑 Set up certs"
|
||||
run_on_target "mkdir -p '${NEW_RELEASE_PATH}/certs' && chmod 550 '${NEW_RELEASE_PATH}/certs' && chown 99:root '${NEW_RELEASE_PATH}/certs'"
|
||||
printf "%s" "$CF_PEM_CERT" | run_on_target "cat > '${NEW_RELEASE_PATH}/certs/crt.pem' && chmod 440 '${NEW_RELEASE_PATH}/certs/crt.pem' && chown 99:root '${NEW_RELEASE_PATH}/certs/crt.pem'"
|
||||
printf "%s" "$CF_PEM_CA" | run_on_target "cat > '${NEW_RELEASE_PATH}/certs/ca.pem' && chmod 440 '${NEW_RELEASE_PATH}/certs/ca.pem' && chown 99:root '${NEW_RELEASE_PATH}/certs/ca.pem'"
|
||||
|
||||
echo "📝 Save deployment metadata"
|
||||
run_on_target "echo '${DEPLOYMENT_TIMESTAMP}' > '${NEW_RELEASE_PATH}/.deployment_version'"
|
||||
run_on_target "echo '${PROD}' > '${NEW_RELEASE_PATH}/.deployment_is_prod'"
|
||||
run_on_target "echo '${NEW_COLOR}' > '${NEW_RELEASE_PATH}/.deployment_color'"
|
||||
run_on_target "echo '${IMAGE_TAG}' > '${NEW_RELEASE_PATH}/.image_tag'"
|
||||
run_on_target "echo '${GIT_SHA:-unknown}' > '${NEW_RELEASE_PATH}/.git_sha'"
|
||||
|
||||
# Save previous version info for potential rollback
|
||||
run_on_target "if [ -L '${CURRENT_LINK_PATH}' ]; then readlink -f '${CURRENT_LINK_PATH}' > '${NEW_RELEASE_PATH}/.previous_version'; fi"
|
||||
|
||||
# export PREVIOUS_RELEASE_PATH
|
||||
if [ "$CURRENT_COLOR" != "none" ]; then
|
||||
PREVIOUS_RELEASE_PATH=$(run_on_target "cat ${NEW_RELEASE_PATH}/.previous_version")
|
||||
else
|
||||
PREVIOUS_RELEASE_PATH=""
|
||||
fi
|
||||
run_on_target "export PREVIOUS_RELEASE_PATH='${PREVIOUS_RELEASE_PATH}'"
|
||||
|
||||
echo "🔗 Update current symlink to new release"
|
||||
run_on_target "ln -sfn '${NEW_RELEASE_PATH}' '${CURRENT_LINK_PATH}'"
|
||||
|
||||
# Get deployment configuration
|
||||
PROJECT_NAME=$(get_project_name "$NEW_COLOR")
|
||||
COMPOSE_FILES=$(get_compose_files)
|
||||
WEB_SERVICE=$(get_web_service_name)
|
||||
|
||||
# create network if it doesn't exist
|
||||
echo "🔗 Creating network ${PROJECT_NAME}_network"
|
||||
run_on_target "docker network create ${REPO_NAME_ONLY}_network >/dev/null 2>&1 || true"
|
||||
|
||||
# Handle core services
|
||||
if [ "$CURRENT_COLOR" = "none" ]; then
|
||||
echo "🚀 Starting core services (first deployment)"
|
||||
retry run_on_target "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -p ${CORE_PROJECT_NAME} up -d"
|
||||
sleep 10 # Give core services time to start
|
||||
else
|
||||
echo "ℹ️ Core services already running, checking for changes..."
|
||||
PREVIOUS_SHA1=$(run_on_target "sha1sum '${PREVIOUS_RELEASE_PATH}/docker-compose_core.yml' | awk '{print \$1}'")
|
||||
NEW_SHA1=$(run_on_target "sha1sum '${NEW_RELEASE_PATH}/docker-compose_core.yml' | awk '{print \$1}'")
|
||||
echo "PREV_SHA1: ${PREVIOUS_SHA1}"
|
||||
echo " NEW_SHA1: ${NEW_SHA1}"
|
||||
if [ -n "$PREVIOUS_SHA1" ] && [ -n "$NEW_SHA1" ]; then
|
||||
if [ "$PREVIOUS_SHA1" != "$NEW_SHA1" ]; then
|
||||
echo "🚀 Core services have changed, restarting..."
|
||||
retry run_on_target "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -p ${CORE_PROJECT_NAME} down"
|
||||
retry run_on_target "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -p ${CORE_PROJECT_NAME} up -d"
|
||||
else
|
||||
echo "ℹ️ Core services have not changed, still restarting due to current folder change..."
|
||||
retry run_on_target "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -p ${CORE_PROJECT_NAME} down"
|
||||
retry run_on_target "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -p ${CORE_PROJECT_NAME} up -d"
|
||||
fi
|
||||
else
|
||||
echo "❌ Current or previous core services not found, exiting..."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "🚀 Start new ${NEW_COLOR} containers with image ${IMAGE_TAG}"
|
||||
retry run_on_target "cd '${CURRENT_LINK_PATH}' && DEPLOYMENT_COLOR=${NEW_COLOR} IMAGE_TAG=${IMAGE_TAG} docker compose $COMPOSE_FILES -p ${PROJECT_NAME} up -d"
|
||||
|
||||
# Wait for new containers to be healthy
|
||||
if ! wait_for_healthy_containers "$PROJECT_NAME" "$WEB_SERVICE" "$REPLICA_COUNT"; then
|
||||
echo "❌ New containers failed health checks. Cancelling deployment..."
|
||||
run_on_target "cd '${CURRENT_LINK_PATH}' && docker compose $COMPOSE_FILES -p ${PROJECT_NAME} down"
|
||||
#echo "🔄 Rolling back deployment..."
|
||||
#TODO: implement rollback
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ New ${NEW_COLOR} deployment is healthy"
|
||||
|
||||
# Refresh gatekeepers
|
||||
refresh_gatekeepers
|
||||
|
||||
# Wait for traffic to stabilize
|
||||
wait_with_countdown 20 "⏳ Waiting for traffic to stabilize..."
|
||||
|
||||
# Clean up old containers if this isn't the first deployment
|
||||
if [ "$CURRENT_COLOR" != "none" ]; then
|
||||
# Get the old image tag before cleanup
|
||||
OLD_IMAGE_TAG=$(get_deployment_image_tag "$CURRENT_COLOR")
|
||||
echo "📷 Old deployment was using image: ${OLD_IMAGE_TAG}"
|
||||
|
||||
cleanup_color_containers "$CURRENT_COLOR"
|
||||
echo "✅ Old containers removed"
|
||||
fi
|
||||
|
||||
echo "🗑️ Clean up old releases (keep last 5)"
|
||||
run_on_target "cd '${RELEASES_PATH}' && ls -dt */ 2>/dev/null | tail -n +6 | xargs -r rm -rf || true"
|
||||
|
||||
echo "✅ Blue-Green deployment completed"
|
||||
echo " Active color: ${NEW_COLOR}"
|
||||
echo " Image tag: ${IMAGE_TAG}"
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Main deployment script with versioned releases
|
||||
# Usage: ./deploy-to-server.sh
|
||||
|
||||
# Source retry function
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/retry.sh"
|
||||
|
||||
# Required environment variables (should be set by GitHub Actions)
|
||||
: "${DOCKER_HOST:?Error: DOCKER_HOST not set}"
|
||||
: "${REPO_PROJECT_PATH:?Error: REPO_PROJECT_PATH not set}"
|
||||
: "${REPO_NAME_ONLY:?Error: REPO_NAME_ONLY not set}"
|
||||
: "${IMAGE_TAR:?Error: IMAGE_TAR not set}"
|
||||
: "${ENV_FILE_BASE64:?Error: ENV_FILE_BASE64 not set}"
|
||||
: "${CF_PEM_CERT:?Error: CF_PEM_CERT not set}"
|
||||
: "${CF_PEM_CA:?Error: CF_PEM_CA not set}"
|
||||
: "${PROD:?Error: PROD not set}"
|
||||
|
||||
echo "⚙️ Docker host: $DOCKER_HOST"
|
||||
|
||||
# Generate deployment timestamp
|
||||
DEPLOYMENT_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
RELEASES_PATH="${REPO_PROJECT_PATH}/releases"
|
||||
NEW_RELEASE_PATH="${RELEASES_PATH}/${DEPLOYMENT_TIMESTAMP}"
|
||||
CURRENT_LINK_PATH="${REPO_PROJECT_PATH}/current"
|
||||
|
||||
echo "📅 Deployment version: ${DEPLOYMENT_TIMESTAMP}"
|
||||
|
||||
echo "🚀 Enable and start docker service"
|
||||
retry ssh deploy "sudo systemctl enable --now docker.service"
|
||||
|
||||
echo "💾 Load the new docker image ($IMAGE_TAR)"
|
||||
if [ ! -f "$IMAGE_TAR" ]; then
|
||||
echo "Error: Docker image tar file not found: $IMAGE_TAR"
|
||||
exit 1
|
||||
fi
|
||||
retry docker load -i "$IMAGE_TAR"
|
||||
|
||||
echo "📁 Create versioned release directory"
|
||||
ssh deploy "mkdir -p '${NEW_RELEASE_PATH}'"
|
||||
|
||||
echo "💾 Copy new files to server"
|
||||
# Check if server directory exists before copying
|
||||
if [ -d "./server" ]; then
|
||||
retry scp -pr ./server/* "deploy:${NEW_RELEASE_PATH}/"
|
||||
else
|
||||
echo "⚠️ No server directory found, error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📝 Create new .env file"
|
||||
printf "%s" "${ENV_FILE_BASE64}" | base64 -d | ssh deploy "cat > '${NEW_RELEASE_PATH}/.env' && chmod 600 '${NEW_RELEASE_PATH}/.env'"
|
||||
|
||||
echo "🔑 Set up certs"
|
||||
ssh deploy "mkdir -p '${NEW_RELEASE_PATH}/certs' && chmod 550 '${NEW_RELEASE_PATH}/certs' && chown 99:root '${NEW_RELEASE_PATH}/certs'"
|
||||
printf "%s" "$CF_PEM_CERT" | ssh deploy "cat > '${NEW_RELEASE_PATH}/certs/crt.pem' && chmod 440 '${NEW_RELEASE_PATH}/certs/crt.pem' && chown 99:root '${NEW_RELEASE_PATH}/certs/crt.pem'"
|
||||
printf "%s" "$CF_PEM_CA" | ssh deploy "cat > '${NEW_RELEASE_PATH}/certs/ca.pem' && chmod 440 '${NEW_RELEASE_PATH}/certs/ca.pem' && chown 99:root '${NEW_RELEASE_PATH}/certs/ca.pem'"
|
||||
|
||||
echo "🔄 Prepare deployment (stop current containers)"
|
||||
# Copy script to remote and execute with parameters
|
||||
scp "${SCRIPT_DIR}/prepare-deployment.sh" deploy:/tmp/
|
||||
ssh deploy "chmod +x /tmp/prepare-deployment.sh && /tmp/prepare-deployment.sh '${REPO_PROJECT_PATH}' '${PROD}' '${CURRENT_LINK_PATH}'"
|
||||
ssh deploy "rm -f /tmp/prepare-deployment.sh"
|
||||
|
||||
echo "📝 Save deployment metadata"
|
||||
ssh deploy "echo '${DEPLOYMENT_TIMESTAMP}' > '${NEW_RELEASE_PATH}/.deployment_version'"
|
||||
ssh deploy "echo '${PROD}' > '${NEW_RELEASE_PATH}/.deployment_env'"
|
||||
|
||||
# Save previous version info for potential rollback
|
||||
ssh deploy "if [ -L '${CURRENT_LINK_PATH}' ]; then readlink -f '${CURRENT_LINK_PATH}' > '${NEW_RELEASE_PATH}/.previous_version'; fi"
|
||||
|
||||
echo "🔗 Update current symlink to new release"
|
||||
ssh deploy "ln -sfn '${NEW_RELEASE_PATH}' '${CURRENT_LINK_PATH}'"
|
||||
|
||||
# TODO: implement zero-downtime deployment
|
||||
# echo "🚀 Start the new containers, zero-downtime"
|
||||
# if [ "${PROD}" = true ]; then
|
||||
# ssh deploy <<EOF
|
||||
# cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}}
|
||||
# old_container_id=$(docker compose -f docker-compose_web.yml ps -f name=web -q | tail -n1)
|
||||
# docker compose -f docker-compose_web.yml up -d --no-build --no-recreate
|
||||
# new_container_id=$(docker compose -f docker-compose_web.yml ps -f name=web -q | head -n1)
|
||||
# # not needed, but might be useful at some point
|
||||
# #new_container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $new_container_id)
|
||||
# #new_container_name=$(docker inspect -f '{{.Name}}' $new_container_id | cut -c2-)
|
||||
# sleep 100 # change to wait for healthcheck in the future
|
||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||
# docker stop $old_container_id
|
||||
# docker rm $old_container_id
|
||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||
# EOF
|
||||
# else
|
||||
# ssh deploy <<EOF
|
||||
# cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}}
|
||||
# old_container_id=$(docker compose -f docker-compose_staging.yml ps -f name=web-staging -q | tail -n1)
|
||||
# docker compose -f docker-compose_staging.yml up -d --no-build --no-recreate
|
||||
# new_container_id=$(docker compose -f docker-compose_staging.yml ps -f name=web-staging -q | head -n1)
|
||||
# # not needed, but might be useful at some point
|
||||
# #new_container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $new_container_id)
|
||||
# #new_container_name=$(docker inspect -f '{{.Name}}' $new_container_id | cut -c2-)
|
||||
# sleep 100 # change to wait for healthcheck in the future
|
||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||
# docker stop $old_container_id
|
||||
# docker rm $old_container_id
|
||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||
# EOF
|
||||
# fi
|
||||
|
||||
echo "🚀 Start the new containers"
|
||||
if [ "$PROD" = "true" ]; then
|
||||
retry ssh deploy "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -f docker-compose_web.yml -p pkmntrade-club up -d --no-build"
|
||||
else
|
||||
retry ssh deploy "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -f docker-compose_web.yml -f docker-compose_staging.yml -p pkmntrade-club up -d --no-build"
|
||||
fi
|
||||
|
||||
echo "🧹 Prune unused Docker resources"
|
||||
ssh deploy "docker system prune -f"
|
||||
|
||||
echo "🗑️ Clean up old releases (keep last 5)"
|
||||
ssh deploy "cd '${RELEASES_PATH}' && ls -dt */ 2>/dev/null | tail -n +6 | xargs -r rm -rf || true"
|
||||
|
||||
echo "✅ Deployment completed. Version: ${DEPLOYMENT_TIMESTAMP}"
|
||||
0
scripts/generate-docker-tags.sh
Normal file → Executable file
0
scripts/generate-docker-tags.sh
Normal file → Executable file
|
|
@ -1,102 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Perform health check and rollback if necessary
|
||||
# Usage: ./health-check-and-rollback.sh REPO_PROJECT_PATH PROD HEALTH_CHECK_URL [MAX_ATTEMPTS]
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Error: Invalid number of arguments"
|
||||
echo "Usage: $0 REPO_PROJECT_PATH PROD HEALTH_CHECK_URL [MAX_ATTEMPTS]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_PROJECT_PATH="$1"
|
||||
PROD="$2"
|
||||
HEALTH_CHECK_URL="$3"
|
||||
MAX_ATTEMPTS="${4:-30}"
|
||||
|
||||
CURRENT_LINK_PATH="${REPO_PROJECT_PATH}/current"
|
||||
RELEASES_PATH="${REPO_PROJECT_PATH}/releases"
|
||||
|
||||
echo "🏥 Performing health check..."
|
||||
echo "Health check URL: $HEALTH_CHECK_URL"
|
||||
|
||||
get_current_version() {
|
||||
if [ -L "$CURRENT_LINK_PATH" ]; then
|
||||
basename "$(readlink -f "$CURRENT_LINK_PATH")"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
ATTEMPT=0
|
||||
while [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; do
|
||||
# Check if the service is responding with 200 OK
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' -m 10 "$HEALTH_CHECK_URL" || echo '000')
|
||||
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "✅ Health check passed! (HTTP $HTTP_CODE)"
|
||||
CURRENT_VERSION=$(get_current_version)
|
||||
echo "📌 Current version: ${CURRENT_VERSION}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
if [ "$ATTEMPT" -eq "$MAX_ATTEMPTS" ]; then
|
||||
echo "❌ Health check failed after $MAX_ATTEMPTS attempts (Last HTTP code: $HTTP_CODE)"
|
||||
echo "🔄 Rolling back deployment..."
|
||||
|
||||
FAILED_VERSION=$(get_current_version)
|
||||
echo "❌ Failed version: ${FAILED_VERSION}"
|
||||
|
||||
# Check if we have a previous version to roll back to
|
||||
if [ -f "${CURRENT_LINK_PATH}/.previous_version" ]; then
|
||||
PREVIOUS_VERSION_PATH=$(cat "${CURRENT_LINK_PATH}/.previous_version")
|
||||
PREVIOUS_VERSION=$(basename "$PREVIOUS_VERSION_PATH")
|
||||
|
||||
if [ -d "$PREVIOUS_VERSION_PATH" ]; then
|
||||
echo "🔄 Rolling back to version: ${PREVIOUS_VERSION}"
|
||||
|
||||
# Stop failed deployment containers
|
||||
cd "$CURRENT_LINK_PATH"
|
||||
echo "Stopping failed deployment containers..."
|
||||
docker compose -f docker-compose_web.yml -p pkmntrade-club down || true
|
||||
if [ "$PROD" = "false" ]; then
|
||||
docker compose -f docker-compose_staging.yml -p pkmntrade-club down || true
|
||||
fi
|
||||
docker compose -f docker-compose_core.yml -p pkmntrade-club down || true
|
||||
|
||||
# Switch symlink back to previous version
|
||||
ln -sfn "$PREVIOUS_VERSION_PATH" "$CURRENT_LINK_PATH"
|
||||
|
||||
# Start previous version containers
|
||||
cd "$CURRENT_LINK_PATH"
|
||||
docker compose -f docker-compose_core.yml -p pkmntrade-club up -d --no-build
|
||||
if [ "$PROD" = "true" ]; then
|
||||
docker compose -f docker-compose_web.yml -p pkmntrade-club up -d --no-build
|
||||
else
|
||||
docker compose -f docker-compose_web.yml -f docker-compose_staging.yml -p pkmntrade-club up -d --no-build
|
||||
fi
|
||||
|
||||
echo "✅ Rollback completed to version: ${PREVIOUS_VERSION}"
|
||||
|
||||
# Mark failed version
|
||||
if [ -d "${RELEASES_PATH}/${FAILED_VERSION}" ]; then
|
||||
touch "${RELEASES_PATH}/${FAILED_VERSION}/.failed"
|
||||
echo "$(date): Health check failed, rolled back to ${PREVIOUS_VERSION}" > "${RELEASES_PATH}/${FAILED_VERSION}/.failure_reason"
|
||||
fi
|
||||
else
|
||||
echo "❌ Previous version directory not found: $PREVIOUS_VERSION_PATH"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ No previous version information found. Cannot rollback!"
|
||||
echo "💡 This might be the first deployment or the previous version info is missing."
|
||||
exit 1
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "⏳ Waiting for service to be healthy... (attempt $ATTEMPT/$MAX_ATTEMPTS, HTTP code: $HTTP_CODE)"
|
||||
sleep 10
|
||||
done
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Manage deployment releases
|
||||
# Usage: ./manage-releases.sh REPO_PROJECT_PATH COMMAND [ARGS]
|
||||
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Error: Invalid number of arguments"
|
||||
echo "Usage: $0 REPO_PROJECT_PATH COMMAND [ARGS]"
|
||||
echo "Commands:"
|
||||
echo " list - List all releases"
|
||||
echo " current - Show current release"
|
||||
echo " rollback VERSION - Rollback to specific version"
|
||||
echo " cleanup [KEEP] - Clean up old releases (default: keep 5)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_PROJECT_PATH="$1"
|
||||
COMMAND="$2"
|
||||
CURRENT_LINK_PATH="${REPO_PROJECT_PATH}/current"
|
||||
RELEASES_PATH="${REPO_PROJECT_PATH}/releases"
|
||||
|
||||
case "$COMMAND" in
|
||||
list)
|
||||
echo "📋 Available releases:"
|
||||
if [ -d "$RELEASES_PATH" ]; then
|
||||
for release in $(ls -dt "${RELEASES_PATH}"/*/); do
|
||||
version=$(basename "$release")
|
||||
status=""
|
||||
|
||||
# Check if it's current
|
||||
if [ -L "$CURRENT_LINK_PATH" ] && [ "$(readlink -f "$CURRENT_LINK_PATH")" = "$(realpath "$release")" ]; then
|
||||
status=" [CURRENT]"
|
||||
fi
|
||||
|
||||
# Check if it failed
|
||||
if [ -f "${release}/.failed" ]; then
|
||||
status="${status} [FAILED]"
|
||||
fi
|
||||
|
||||
echo " - ${version}${status}"
|
||||
done
|
||||
else
|
||||
echo "No releases found"
|
||||
fi
|
||||
;;
|
||||
|
||||
current)
|
||||
if [ -L "$CURRENT_LINK_PATH" ]; then
|
||||
current_version=$(basename "$(readlink -f "$CURRENT_LINK_PATH")")
|
||||
echo "📌 Current version: ${current_version}"
|
||||
else
|
||||
echo "❌ No current deployment found"
|
||||
fi
|
||||
;;
|
||||
|
||||
rollback)
|
||||
if [ $# -lt 3 ]; then
|
||||
echo "Error: VERSION required for rollback"
|
||||
exit 1
|
||||
fi
|
||||
TARGET_VERSION="$3"
|
||||
TARGET_PATH="${RELEASES_PATH}/${TARGET_VERSION}"
|
||||
|
||||
if [ ! -d "$TARGET_PATH" ]; then
|
||||
echo "Error: Version ${TARGET_VERSION} not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔄 Rolling back to version: ${TARGET_VERSION}"
|
||||
|
||||
# Read environment from target version
|
||||
if [ -f "${TARGET_PATH}/.deployment_env" ]; then
|
||||
PROD=$(cat "${TARGET_PATH}/.deployment_env")
|
||||
else
|
||||
echo "Warning: Could not determine environment, assuming staging"
|
||||
PROD="false"
|
||||
fi
|
||||
|
||||
# Stop current containers
|
||||
if [ -L "$CURRENT_LINK_PATH" ] && [ -d "$CURRENT_LINK_PATH" ]; then
|
||||
cd "$CURRENT_LINK_PATH"
|
||||
docker compose -f docker-compose_web.yml down || true
|
||||
[ "$PROD" = "false" ] && docker compose -f docker-compose_staging.yml down || true
|
||||
docker compose -f docker-compose_core.yml down || true
|
||||
fi
|
||||
|
||||
# Update symlink
|
||||
ln -sfn "$TARGET_PATH" "$CURRENT_LINK_PATH"
|
||||
|
||||
# Start containers
|
||||
cd "$CURRENT_LINK_PATH"
|
||||
docker compose -f docker-compose_core.yml up -d --no-build
|
||||
if [ "$PROD" = "true" ]; then
|
||||
docker compose -f docker-compose_web.yml up -d --no-build
|
||||
else
|
||||
docker compose -f docker-compose_web.yml -f docker-compose_staging.yml up -d --no-build
|
||||
fi
|
||||
|
||||
echo "✅ Rollback completed"
|
||||
;;
|
||||
|
||||
cleanup)
|
||||
KEEP_COUNT="${3:-5}"
|
||||
echo "🗑️ Cleaning up old releases (keeping last ${KEEP_COUNT})"
|
||||
|
||||
if [ -d "$RELEASES_PATH" ]; then
|
||||
cd "$RELEASES_PATH"
|
||||
ls -dt */ 2>/dev/null | tail -n +$((KEEP_COUNT + 1)) | xargs -r rm -rf || true
|
||||
echo "✅ Cleanup completed"
|
||||
else
|
||||
echo "No releases directory found"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Error: Unknown command: $COMMAND"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
9
scripts/parse-repository-name.sh
Normal file → Executable file
9
scripts/parse-repository-name.sh
Normal file → Executable file
|
|
@ -16,14 +16,17 @@ echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY" > /dev/stderr
|
|||
|
||||
if [[ "$GITHUB_REPOSITORY" == *".git" ]]; then
|
||||
if [[ "$GITHUB_REPOSITORY" == "https://"* ]]; then
|
||||
echo "GITHUB_REPOSITORY ends in .git and is a URL" > /dev/stderr
|
||||
echo "GITHUB_REPOSITORY ends in .git and is an HTTPS URI" > /dev/stderr
|
||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d'/' -f4-5 | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
||||
elif [[ "$GITHUB_REPOSITORY" == "git@"* ]]; then
|
||||
echo "GITHUB_REPOSITORY ends in .git and is an SSH URI" > /dev/stderr
|
||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d':' -f2 | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
||||
else
|
||||
echo "GITHUB_REPOSITORY ends in .git and is not a URL" > /dev/stderr
|
||||
echo "GITHUB_REPOSITORY ends in .git and is not a URI" > /dev/stderr
|
||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
||||
fi
|
||||
else
|
||||
echo "GITHUB_REPOSITORY is not a URL" > /dev/stderr
|
||||
echo "GITHUB_REPOSITORY is not a URI" > /dev/stderr
|
||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Prepare deployment by stopping containers
|
||||
# Usage: ./prepare-deployment.sh REPO_PROJECT_PATH PROD CURRENT_LINK_PATH
|
||||
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Error: Invalid number of arguments"
|
||||
echo "Usage: $0 REPO_PROJECT_PATH PROD CURRENT_LINK_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_PROJECT_PATH="$1"
|
||||
PROD="$2"
|
||||
CURRENT_LINK_PATH="$3"
|
||||
|
||||
# Ensure base directory exists
|
||||
if [ ! -d "$REPO_PROJECT_PATH" ]; then
|
||||
echo "⚠️ Directory $REPO_PROJECT_PATH does not exist, creating it..."
|
||||
mkdir -p "$REPO_PROJECT_PATH"
|
||||
fi
|
||||
|
||||
# If current symlink exists, stop containers in that directory
|
||||
if [ -L "$CURRENT_LINK_PATH" ] && [ -d "$CURRENT_LINK_PATH" ]; then
|
||||
echo "🛑 Stopping containers in current deployment..."
|
||||
cd "$CURRENT_LINK_PATH"
|
||||
|
||||
# Stop containers
|
||||
if [ -f "docker-compose_web.yml" ]; then
|
||||
docker compose -f docker-compose_web.yml -p pkmntrade-club down || true
|
||||
fi
|
||||
|
||||
if [ "$PROD" = "false" ] && [ -f "docker-compose_staging.yml" ]; then
|
||||
docker compose -f docker-compose_staging.yml -p pkmntrade-club down || true
|
||||
fi
|
||||
|
||||
if [ -f "docker-compose_core.yml" ]; then
|
||||
docker compose -f docker-compose_core.yml -p pkmntrade-club down || true
|
||||
fi
|
||||
|
||||
echo "✅ Containers stopped"
|
||||
else
|
||||
echo "ℹ️ No current deployment found (symlink doesn't exist or point to valid directory)"
|
||||
fi
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Retry function with exponential backoff
|
||||
# Usage: source retry.sh && retry <command>
|
||||
|
||||
retry() {
|
||||
local max_attempts=3
|
||||
local delay=5
|
||||
local attempt=1
|
||||
|
||||
until "$@"; do
|
||||
if [ "$attempt" -ge "$max_attempts" ]; then
|
||||
echo "Command failed after $max_attempts attempts: $*"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Command failed (attempt $attempt/$max_attempts): $*"
|
||||
echo "Retrying in $delay seconds..."
|
||||
sleep "$delay"
|
||||
attempt=$((attempt + 1))
|
||||
delay=$((delay * 2)) # Exponential backoff
|
||||
done
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue