Files
michaelschiemer/.gitea/workflows/manual-deploy.yml

457 lines
18 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: 🚀 Manual Deployment
run-name: Manual Deploy - ${{ inputs.environment }} - ${{ inputs.image_tag || 'latest' }}
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
type: choice
options:
- staging
- production
image_tag:
description: 'Image tag to deploy (e.g. abc1234-1696234567, git-abc1234). Leave empty for latest'
required: false
type: string
default: ''
branch:
description: 'Branch to checkout (default: main for production, staging for staging)'
required: false
type: string
default: ''
env:
REGISTRY: registry.michaelschiemer.de
IMAGE_NAME: framework
DEPLOYMENT_HOST: 94.16.110.151
jobs:
determine-image:
name: Determine Deployment Image
runs-on: ubuntu-latest
outputs:
image_url: ${{ steps.image.outputs.image_url }}
image_tag: ${{ steps.image.outputs.image_tag }}
registry_host: ${{ env.REGISTRY }}
image_name: ${{ env.IMAGE_NAME }}
steps:
- name: Determine image to deploy
id: image
shell: bash
run: |
REGISTRY="${{ env.REGISTRY }}"
IMAGE_NAME="${{ env.IMAGE_NAME }}"
INPUT_TAG="${{ inputs.image_tag }}"
if [ -z "$INPUT_TAG" ] || [ "$INPUT_TAG" = "" ]; then
IMAGE_TAG="latest"
else
IMAGE_TAG="$INPUT_TAG"
fi
IMAGE_URL="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "image_url=${IMAGE_URL}" >> "$GITHUB_OUTPUT"
echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
echo "📦 Deployment Image:"
echo " URL: ${IMAGE_URL}"
echo " Tag: ${IMAGE_TAG}"
echo ""
echo " Image will be validated during deployment"
deploy-staging:
name: Deploy to Staging
needs: determine-image
if: inputs.environment == 'staging'
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.michaelschiemer.de
steps:
- name: Determine branch name
id: branch
shell: bash
run: |
INPUT_BRANCH="${{ inputs.branch }}"
if [ -z "$INPUT_BRANCH" ] || [ "$INPUT_BRANCH" = "" ]; then
REF_NAME="staging"
else
REF_NAME="$INPUT_BRANCH"
fi
echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT
echo "📋 Branch: $REF_NAME"
- name: Checkout deployment scripts
run: |
REF_NAME="${{ steps.branch.outputs.BRANCH }}"
REPO="${{ github.repository }}"
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
git clone --depth 1 --branch "$REF_NAME" \
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
/workspace/repo
else
git clone --depth 1 --branch "$REF_NAME" \
"https://git.michaelschiemer.de/${REPO}.git" \
/workspace/repo || \
git clone --depth 1 \
"https://git.michaelschiemer.de/${REPO}.git" \
/workspace/repo
fi
cd /workspace/repo
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
chmod 600 ~/.ssh/production
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
- name: Deploy to Staging Server
run: |
set -e
DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}"
REGISTRY_HOST="${{ needs.determine-image.outputs.registry_host }}"
IMAGE_NAME="${{ needs.determine-image.outputs.image_name }}"
DEPLOY_IMAGE="${{ needs.determine-image.outputs.image_url }}"
IMAGE_TAG="${{ needs.determine-image.outputs.image_tag }}"
DEFAULT_IMAGE="${REGISTRY_HOST}/${IMAGE_NAME}:latest"
FALLBACK_IMAGE="$DEFAULT_IMAGE"
SELECTED_IMAGE="$DEPLOY_IMAGE"
if [ -z "$SELECTED_IMAGE" ] || [ "$SELECTED_IMAGE" = "null" ]; then
SELECTED_IMAGE="$DEFAULT_IMAGE"
fi
STACK_PATH_DISPLAY="~/deployment/stacks/staging"
SELECTED_TAG="${SELECTED_IMAGE##*:}"
SELECTED_REPO="${SELECTED_IMAGE%:*}"
if [ -z "$SELECTED_REPO" ] || [ "$SELECTED_REPO" = "$SELECTED_IMAGE" ]; then
FALLBACK_IMAGE="$DEFAULT_IMAGE"
else
FALLBACK_IMAGE="${SELECTED_REPO}:latest"
fi
echo "🚀 Starting staging deployment..."
echo " Image: ${SELECTED_IMAGE}"
echo " Tag: ${SELECTED_TAG}"
echo " Host: ${DEPLOYMENT_HOST}"
echo " Stack: ${STACK_PATH_DISPLAY}"
FULL_IMAGE_ARG=$(printf '%q' "$SELECTED_IMAGE")
FALLBACK_IMAGE_ARG=$(printf '%q' "$FALLBACK_IMAGE")
IMAGE_NAME_ARG=$(printf '%q' "$IMAGE_NAME")
REGISTRY_ARG=$(printf '%q' "$REGISTRY_HOST")
ssh -i ~/.ssh/production \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
deploy@${DEPLOYMENT_HOST} "bash -s -- $FULL_IMAGE_ARG $FALLBACK_IMAGE_ARG $IMAGE_NAME_ARG $REGISTRY_ARG" <<'EOF'
set -e
FULL_IMAGE="$1"
FALLBACK_IMAGE="$2"
IMAGE_NAME="$3"
REGISTRY="$4"
shift 4
CURRENT_USER="$(whoami)"
USER_HOME="$(getent passwd "$CURRENT_USER" | cut -d: -f6 2>/dev/null)"
[ -z "$USER_HOME" ] && USER_HOME="$HOME"
[ -z "$USER_HOME" ] && USER_HOME="/home/$CURRENT_USER"
STACK_TARGET="${USER_HOME}/deployment/stacks/staging"
# Ensure staging stack directory exists
mkdir -p "${STACK_TARGET}"
cd "${STACK_TARGET}"
declare -a REGISTRY_TARGETS=()
if [ -n "${REGISTRY}" ]; then
REGISTRY_TARGETS+=("${REGISTRY}")
fi
for IMAGE_REF in "${FULL_IMAGE}" "${FALLBACK_IMAGE}"; do
if [ -n "${IMAGE_REF}" ]; then
HOST_PART="${IMAGE_REF%%/*}"
if [ -n "${HOST_PART}" ]; then
if ! printf '%s\n' "${REGISTRY_TARGETS[@]}" | grep -qx "${HOST_PART}"; then
REGISTRY_TARGETS+=("${HOST_PART}")
fi
fi
fi
done
for TARGET in "${REGISTRY_TARGETS[@]}"; do
[ -z "${TARGET}" ] && continue
echo "🔐 Logging in to Docker registry ${TARGET}..."
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login "${TARGET}" \
-u "${{ secrets.REGISTRY_USER }}" \
--password-stdin || echo "⚠️ Registry login failed for ${TARGET}, continuing..."
done
DEPLOY_IMAGE="$FULL_IMAGE"
echo "📥 Pulling image ${DEPLOY_IMAGE}..."
if ! docker pull "${DEPLOY_IMAGE}"; then
if [ -n "${FALLBACK_IMAGE}" ] && [ "${DEPLOY_IMAGE}" != "${FALLBACK_IMAGE}" ]; then
echo "⚠️ Failed to pull ${DEPLOY_IMAGE}, attempting fallback ${FALLBACK_IMAGE}"
if docker pull "${FALLBACK_IMAGE}"; then
DEPLOY_IMAGE="${FALLBACK_IMAGE}"
echo " Using fallback image ${DEPLOY_IMAGE}"
else
echo "❌ Failed to pull fallback image ${FALLBACK_IMAGE}"
exit 1
fi
else
echo "❌ Failed to pull image ${DEPLOY_IMAGE}"
exit 1
fi
fi
# Copy base and staging docker-compose files if they don't exist
if [ ! -f docker-compose.base.yml ]; then
echo "⚠️ docker-compose.base.yml not found, copying from repo..."
cp /workspace/repo/docker-compose.base.yml . || {
echo "❌ Failed to copy docker-compose.base.yml"
exit 1
}
fi
if [ ! -f docker-compose.staging.yml ]; then
echo "⚠️ docker-compose.staging.yml not found, copying from repo..."
cp /workspace/repo/docker-compose.staging.yml . || {
echo "❌ Failed to copy docker-compose.staging.yml"
exit 1
}
fi
# Update docker-compose.staging.yml with new image tag
echo "📝 Updating docker-compose.staging.yml with new image tag..."
sed -i "s|image:.*/${IMAGE_NAME}:.*|image: ${DEPLOY_IMAGE}|g" docker-compose.staging.yml
echo "✅ Updated docker-compose.staging.yml:"
grep "image:" docker-compose.staging.yml | head -5
# Ensure networks exist
echo "🔗 Ensuring Docker networks exist..."
docker network create traefik-public 2>/dev/null || true
docker network create staging-internal 2>/dev/null || true
echo "🔄 Starting/updating services..."
# Use --pull missing instead of --pull always since we already pulled the specific image
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml up -d --pull missing --force-recreate || {
echo "❌ Failed to start services"
exit 1
}
echo "⏳ Waiting for services to start..."
sleep 15
# Pull latest code from Git repository
echo "🔄 Pulling latest code from Git repository in staging-app container..."
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-app bash -c "cd /var/www/html && git -c safe.directory=/var/www/html fetch origin staging && git -c safe.directory=/var/www/html reset --hard origin/staging && git -c safe.directory=/var/www/html clean -fd" || echo "⚠️ Git pull failed, container will sync on next restart"
# Also trigger a restart to ensure entrypoint script runs
echo "🔄 Restarting staging-app to ensure all services are up-to-date..."
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml restart staging-app || echo "⚠️ Failed to restart staging-app"
# Fix nginx upstream configuration - critical fix for 502 errors
# sites-available/default uses 127.0.0.1:9000 but PHP-FPM runs in staging-app container
echo "🔧 Fixing nginx PHP-FPM upstream configuration (post-deploy fix)..."
sleep 5
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-nginx sed -i '/upstream php-upstream {/,/}/s|server 127.0.0.1:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || echo "⚠️ Upstream fix (127.0.0.1) failed"
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-nginx sed -i '/upstream php-upstream {/,/}/s|server localhost:9000;|server staging-app:9000;|g' /etc/nginx/sites-available/default || echo "⚠️ Upstream fix (localhost) failed"
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-nginx nginx -t && docker compose -f docker-compose.base.yml -f docker-compose.staging.yml restart staging-nginx || echo "⚠️ Nginx config test or restart failed"
echo "✅ Nginx configuration fixed and reloaded"
echo "⏳ Waiting for services to stabilize..."
sleep 10
echo "📊 Container status:"
docker compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
echo "✅ Staging deployment completed!"
EOF
- name: Wait for deployment to stabilize
run: sleep 30
- name: Health check
id: health
run: |
for i in {1..10}; do
if curl -f -k https://staging.michaelschiemer.de/health; then
echo "✅ Health check passed"
exit 0
fi
echo "⏳ Waiting for staging service... (attempt $i/10)"
sleep 10
done
echo "❌ Health check failed"
exit 1
- name: Notify deployment success
if: success()
run: |
echo "🚀 Staging deployment successful!"
echo "URL: https://staging.michaelschiemer.de"
echo "Image: ${{ needs.determine-image.outputs.image_url }}"
deploy-production:
name: Deploy to Production
needs: determine-image
if: inputs.environment == 'production'
runs-on: ubuntu-latest
environment:
name: production
url: https://michaelschiemer.de
steps:
- name: Determine branch name
id: branch
shell: bash
run: |
INPUT_BRANCH="${{ inputs.branch }}"
if [ -z "$INPUT_BRANCH" ] || [ "$INPUT_BRANCH" = "" ]; then
REF_NAME="main"
else
REF_NAME="$INPUT_BRANCH"
fi
echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT
echo "📋 Branch: $REF_NAME"
- name: Checkout deployment scripts
run: |
REF_NAME="${{ steps.branch.outputs.BRANCH }}"
REPO="${{ github.repository }}"
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
git clone --depth 1 --branch "$REF_NAME" \
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
/workspace/repo
else
git clone --depth 1 --branch "$REF_NAME" \
"https://git.michaelschiemer.de/${REPO}.git" \
/workspace/repo || \
git clone --depth 1 \
"https://git.michaelschiemer.de/${REPO}.git" \
/workspace/repo
fi
cd /workspace/repo
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
chmod 600 ~/.ssh/production
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
- name: Deploy to Production Server
run: |
set -e
DEPLOYMENT_HOST="${{ env.DEPLOYMENT_HOST }}"
REGISTRY="${{ needs.determine-image.outputs.registry_host }}"
IMAGE_NAME="${{ needs.determine-image.outputs.image_name }}"
IMAGE_TAG="${{ needs.determine-image.outputs.image_tag }}"
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
STACK_PATH="~/deployment/stacks/application"
echo "🚀 Starting production deployment..."
echo " Image: ${FULL_IMAGE}"
echo " Tag: ${IMAGE_TAG}"
echo " Host: ${DEPLOYMENT_HOST}"
echo " Stack: ${STACK_PATH}"
ssh -i ~/.ssh/production \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
deploy@${DEPLOYMENT_HOST} <<EOF
set -e
cd ${STACK_PATH}
echo "🔐 Logging in to Docker registry..."
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${REGISTRY} \
-u "${{ secrets.REGISTRY_USER }}" \
--password-stdin || echo "⚠️ Registry login failed, continuing..."
echo "📥 Pulling image ${FULL_IMAGE}..."
docker pull ${FULL_IMAGE} || {
echo "❌ Failed to pull image ${FULL_IMAGE}"
exit 1
}
# Copy base and production docker-compose files if they don't exist
if [ ! -f docker-compose.base.yml ]; then
echo "⚠️ docker-compose.base.yml not found, copying from repo..."
cp /workspace/repo/docker-compose.base.yml . || {
echo "❌ Failed to copy docker-compose.base.yml"
exit 1
}
fi
if [ ! -f docker-compose.production.yml ]; then
echo "⚠️ docker-compose.production.yml not found, copying from repo..."
cp /workspace/repo/docker-compose.production.yml . || {
echo "❌ Failed to copy docker-compose.production.yml"
exit 1
}
fi
echo "📝 Updating docker-compose.production.yml with new image tag..."
sed -i "s|image:.*/${IMAGE_NAME}:.*|image: ${FULL_IMAGE}|g" docker-compose.production.yml
sed -i "s|image:.*/${IMAGE_NAME}@.*|image: ${FULL_IMAGE}|g" docker-compose.production.yml
echo "✅ Updated docker-compose.production.yml:"
grep "image:" docker-compose.production.yml | head -5
echo "🔄 Restarting services..."
# Use --pull missing instead of --pull always since we already pulled the specific image
docker compose -f docker-compose.base.yml -f docker-compose.production.yml up -d --pull missing --force-recreate || {
echo "❌ Failed to restart services"
exit 1
}
echo "⏳ Waiting for services to start..."
sleep 10
echo "📊 Container status:"
docker compose -f docker-compose.base.yml -f docker-compose.production.yml ps
echo "✅ Production deployment completed!"
EOF
- name: Wait for deployment to stabilize
run: sleep 30
- name: Health check
id: health
run: |
for i in {1..10}; do
if curl -f -k https://michaelschiemer.de/health; then
echo "✅ Health check passed"
exit 0
fi
echo "⏳ Waiting for production service... (attempt $i/10)"
sleep 10
done
echo "❌ Health check failed"
exit 1
- name: Notify deployment success
if: success()
run: |
echo "🚀 Production deployment successful!"
echo "URL: https://michaelschiemer.de"
echo "Image: ${{ needs.determine-image.outputs.image_url }}"