- 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
124 lines
No EOL
5.7 KiB
Bash
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}" |