pkmntrade.club/scripts/deploy-to-server.sh
badbl0cks f20c4f9474
feat: add dynamic versioning and automated deployment with rollback capability
- Implement setuptools-scm for dynamic version management from git tags
- Refactor CI/CD into separate build and deploy jobs with artifact sharing
- Add versioned releases with timestamp-based deployment directories
- Implement health checks and automatic rollback on deployment failure
- Extract deployment logic into reusable shell scripts
- Add Docker layer caching to speed up builds
- Include version info in Django context and build args
2025-06-06 14:38:23 -07:00

124 lines
No EOL
5.7 KiB
Bash

#!/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}"
: "${IS_PROD:?Error: IS_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, erroring out"
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}' '${IS_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 '${IS_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 [ "$IS_PROD" = "true" ]; then
retry ssh deploy "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -f docker-compose_web.yml 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 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}"