144 lines
5.7 KiB
YAML
144 lines
5.7 KiB
YAML
name: ITAM Production Deploy
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
target_branch:
|
|
description: "Branch to deploy"
|
|
required: true
|
|
default: "main"
|
|
type: string
|
|
|
|
jobs:
|
|
deploy-production:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Setup SSH agent
|
|
uses: webfactory/ssh-agent@v0.9.0
|
|
with:
|
|
ssh-private-key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
|
|
|
|
- name: Validate required production variables
|
|
env:
|
|
PROD_HOST: ${{ vars.PROD_HOST }}
|
|
PROD_USER: ${{ vars.PROD_USER }}
|
|
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
|
PROD_GIT_URL: ${{ vars.PROD_GIT_URL }}
|
|
DB_HOST: ${{ vars.PROD_DB_HOST }}
|
|
DB_PORT: ${{ vars.PROD_DB_PORT }}
|
|
DB_USER: ${{ vars.PROD_DB_USER }}
|
|
DB_PASS: ${{ secrets.PROD_DB_PASS }}
|
|
DB_NAME: ${{ vars.PROD_DB_NAME }}
|
|
run: |
|
|
set -euo pipefail
|
|
required_keys="PROD_HOST PROD_USER PROD_DEPLOY_PATH PROD_GIT_URL DB_HOST DB_PORT DB_USER DB_PASS DB_NAME"
|
|
for key in ${required_keys}; do
|
|
if [ -z "${!key:-}" ]; then
|
|
echo "::error::Missing required variable or secret: ${key}"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: Create production env file
|
|
env:
|
|
DB_HOST: ${{ vars.PROD_DB_HOST }}
|
|
DB_PORT: ${{ vars.PROD_DB_PORT }}
|
|
DB_USER: ${{ vars.PROD_DB_USER }}
|
|
DB_PASS: ${{ secrets.PROD_DB_PASS }}
|
|
DB_NAME: ${{ vars.PROD_DB_NAME }}
|
|
LOG_LEVEL: ${{ vars.PROD_LOG_LEVEL }}
|
|
run: |
|
|
set -euo pipefail
|
|
EFFECTIVE_LOG_LEVEL="${LOG_LEVEL:-info}"
|
|
cat > .env.deploy <<EOF
|
|
DB_HOST=${DB_HOST}
|
|
DB_PORT=${DB_PORT}
|
|
DB_USER=${DB_USER}
|
|
DB_PASS=${DB_PASS}
|
|
DB_NAME=${DB_NAME}
|
|
NODE_ENV=production
|
|
PORT=3000
|
|
LOG_LEVEL=${EFFECTIVE_LOG_LEVEL}
|
|
EOF
|
|
|
|
- name: Deploy to production host
|
|
env:
|
|
PROD_HOST: ${{ vars.PROD_HOST }}
|
|
PROD_USER: ${{ vars.PROD_USER }}
|
|
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
|
PROD_BACKUP_ROOT: ${{ vars.PROD_BACKUP_ROOT }}
|
|
PROD_GIT_URL: ${{ vars.PROD_GIT_URL }}
|
|
DB_HOST: ${{ vars.PROD_DB_HOST }}
|
|
DB_PORT: ${{ vars.PROD_DB_PORT }}
|
|
DB_USER: ${{ vars.PROD_DB_USER }}
|
|
DB_PASS: ${{ secrets.PROD_DB_PASS }}
|
|
DB_NAME: ${{ vars.PROD_DB_NAME }}
|
|
TARGET_BRANCH: ${{ github.event.inputs.target_branch }}
|
|
run: |
|
|
set -euo pipefail
|
|
ssh-keyscan -H "${PROD_HOST}" >> ~/.ssh/known_hosts
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "mkdir -p '${PROD_DEPLOY_PATH}'"
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "if [ ! -d '${PROD_DEPLOY_PATH}/.git' ]; then git clone '${PROD_GIT_URL}' '${PROD_DEPLOY_PATH}'; else cd '${PROD_DEPLOY_PATH}' && git remote set-url origin '${PROD_GIT_URL}'; fi"
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && git fetch origin '${TARGET_BRANCH}' && git checkout -B '${TARGET_BRANCH}' FETCH_HEAD && git reset --hard FETCH_HEAD"
|
|
|
|
EFFECTIVE_BACKUP_ROOT="${PROD_BACKUP_ROOT:-/home/user/dachs_backups}"
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "export DEPLOY_PATH='${PROD_DEPLOY_PATH}' BACKUP_ROOT='${EFFECTIVE_BACKUP_ROOT}'; sh -eu -s" <<'REMOTE_BACKUP'
|
|
case "$BACKUP_ROOT" in
|
|
"$DEPLOY_PATH"|"$DEPLOY_PATH"/*)
|
|
echo "Backup path must be outside deploy path: $BACKUP_ROOT"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [ -d "$DEPLOY_PATH/.git" ]; then
|
|
mkdir -p "$BACKUP_ROOT"
|
|
echo "Starting pre-deploy backup..."
|
|
cd "$DEPLOY_PATH"
|
|
if [ -f Makefile ] && [ -f scripts/backup.sh ] && [ -f .env ]; then
|
|
make predeploy-backup ENV_FILE=.env BACKUP_ROOT="$BACKUP_ROOT"
|
|
else
|
|
echo "Skipping pre-deploy backup because required backup files are missing in current deployment."
|
|
fi
|
|
else
|
|
echo "Skipping pre-deploy backup because no existing deployment was found."
|
|
fi
|
|
REMOTE_BACKUP
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && git clean -fd"
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && mkdir -p uploads logs/nginx"
|
|
|
|
scp .env.deploy "${PROD_USER}@${PROD_HOST}:${PROD_DEPLOY_PATH}/.env"
|
|
|
|
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && chmod 600 .env && docker compose -f docker-compose.prod.yaml config && docker compose -f docker-compose.prod.yaml up -d --build"
|
|
|
|
- name: Post-deploy status check
|
|
env:
|
|
PROD_HOST: ${{ vars.PROD_HOST }}
|
|
PROD_USER: ${{ vars.PROD_USER }}
|
|
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
|
run: |
|
|
set -euo pipefail
|
|
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && docker compose -f docker-compose.prod.yaml ps"
|
|
|
|
- name: Post-deploy smoke checks
|
|
env:
|
|
PROD_HOST: ${{ vars.PROD_HOST }}
|
|
PROD_USER: ${{ vars.PROD_USER }}
|
|
PROD_DEPLOY_PATH: ${{ vars.PROD_DEPLOY_PATH }}
|
|
run: |
|
|
set -euo pipefail
|
|
ssh "${PROD_USER}@${PROD_HOST}" "curl -fsS http://localhost:9090/health"
|
|
ssh "${PROD_USER}@${PROD_HOST}" "cd '${PROD_DEPLOY_PATH}' && docker compose -f docker-compose.prod.yaml exec -T backend curl -fsS http://localhost:3000/health"
|
|
|
|
- name: Cleanup generated env file
|
|
if: ${{ always() }}
|
|
run: rm -f .env.deploy
|