Skip to content

CD Deploy to Server (SSH) #141

CD Deploy to Server (SSH)

CD Deploy to Server (SSH) #141

Workflow file for this run

name: CD Deploy to Server (SSH)
on:
workflow_run:
workflows:
- Build & Push Docker Images
- Build & Push coding-service
- Build & Push file-service
types: [ completed ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: deploy-${{ github.event_name }}-${{ github.run_id }}
cancel-in-progress: false
jobs:
##
deploy:
if: >
(github.event_name == 'workflow_dispatch') ||
(
github.event_name == 'workflow_run' &&
(
github.event.workflow_run.head_branch == 'main' ||
startsWith(github.event.workflow_run.head_branch, 'v')
)
)
runs-on: ubuntu-latest
environment: production
env:
# Truyền DEPLOY_DIR qua env (có thể rỗng); xử lý default ở shell
DEPLOY_DIR_SECRET: ${{ secrets.DEPLOY_DIR }}
steps:
- name: Show trigger context (debug)
run: |
echo "event_name: ${{ github.event_name }}"
echo "run.conclusion: ${{ github.event.workflow_run.conclusion }}"
echo "run.head_branch: ${{ github.event.workflow_run.head_branch }}"
echo "run.head_sha: ${{ github.event.workflow_run.head_sha }}"
- name: Checkout
uses: actions/checkout@v4
- name: Derive IMAGE_TAG (from workflow_run or manual)
shell: bash
run: |
echo "Resolved IMAGE_TAG=latest"
echo "IMAGE_TAG=latest" >> "$GITHUB_ENV"
- name: Compute .env SHA256
run: |
if [ ! -f .env ]; then
echo "::error::.env không tồn tại trong repo tại thời điểm deploy."
exit 1
fi
echo "ENV_SHA=$(sha256sum .env | awk '{print $1}')" >> "$GITHUB_ENV"
- name: Prepare deploy bundle
run: |
set -euo pipefail
mkdir -p deploy_bundle
cp -v .env deploy_bundle/.env
cp -v docker-compose.prod-infra.yml deploy_bundle/
mkdir -p deploy_bundle/init
cp -vr init/postgres deploy_bundle/init/postgres
cp -vr init/mongo deploy_bundle/init/mongo
cp -v docker-compose.prod-services.yml deploy_bundle/
mkdir -p deploy_bundle/monitoring/grafana/provisioning
cp -v monitoring/prometheus.yml deploy_bundle/monitoring/
cp -v monitoring/loki-config.yml deploy_bundle/monitoring/
cp -v monitoring/promtail-config.yml deploy_bundle/monitoring/
cp -vr monitoring/grafana/provisioning/* deploy_bundle/monitoring/grafana/provisioning/ 2>/dev/null || true
mkdir -p ops
cat > ops/deploy.sh <<'EOS'
#!/usr/bin/env bash
set -euo pipefail
DEPLOY_DIR="${DEPLOY_DIR:-$HOME/codecampus}"
mkdir -p "$DEPLOY_DIR"
cd "$DEPLOY_DIR"
# Backup .env
cp -f .env ".env.bak.$(date +%Y%m%d-%H%M%S)" || true
# Ghi IMAGE_TAG vào .env:
# - nếu IMAGE_TAG=latest thì giữ latest (hoặc đặt latest nếu chưa có)
# - nếu là version cụ thể/SHA thì cập nhật đúng giá trị
if grep -q '^IMAGE_TAG=' .env; then
if [ "${IMAGE_TAG}" = "latest" ]; then
sed -i "s/^IMAGE_TAG=.*/IMAGE_TAG=latest/" .env
else
sed -i "s/^IMAGE_TAG=.*/IMAGE_TAG=${IMAGE_TAG}/" .env
fi
else
echo "IMAGE_TAG=${IMAGE_TAG}" >> .env
fi
# Load env
set -a
source .env
set +a
# docker compose wrapper
compose() {
if docker compose version >/dev/null 2>&1; then
docker compose "$@"
else
docker-compose "$@"
fi
}
# docker login Docker Hub (nếu có)
if [ -n "${DOCKERHUB_USER:-}" ] && [ -n "${DOCKERHUB_TOKEN:-}" ]; then
echo "docker login Docker Hub..."
echo "$DOCKERHUB_TOKEN" | docker login -u "$DOCKERHUB_USER" --password-stdin
else
echo "Thiếu DOCKERHUB_USER/DOCKERHUB_TOKEN trong .env (image public thì vẫn OK)."
fi
# docker login GHCR (nếu có)
if [ -n "${GHCR_USERNAME:-}" ] && [ -n "${GHCR_TOKEN:-}" ]; then
echo "docker login GHCR..."
echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin
fi
echo "Hạ tầng (idempotent)..."
compose -f docker-compose.prod-infra.yml --env-file .env up -d
echo "Pull images tag ${IMAGE_TAG}..."
compose -f docker-compose.prod-services.yml --env-file .env pull
echo "Up services..."
compose -f docker-compose.prod-services.yml --env-file .env up -d
echo "Prune dangling images..."
docker image prune -f || true
echo "Deploy xong."
EOS
chmod +x ops/deploy.sh
cp -v ops/deploy.sh deploy_bundle/
- name: Decide remote target dir
id: targetdir
run: |
set -euo pipefail
if [ -n "${DEPLOY_DIR_SECRET:-}" ]; then
echo "DEPLOY_DIR_FINAL=${DEPLOY_DIR_SECRET%/}" >> "$GITHUB_OUTPUT"
else
# Mặc định
echo "DEPLOY_DIR_FINAL=~/codecampus" >> "$GITHUB_OUTPUT"
fi
- name: Ensure remote dir exists
uses: appleboy/[email protected]
env:
DEPLOY_DIR: ${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
mkdir -p '${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}'
# Kiểm tra bundle có file hay không
- name: Verify local deploy bundle
run: |
set -euo pipefail
ls -la deploy_bundle
test -f deploy_bundle/.env
test -f deploy_bundle/docker-compose.prod-infra.yml
test -f deploy_bundle/docker-compose.prod-services.yml
test -f deploy_bundle/deploy.sh
test -f deploy_bundle/monitoring/prometheus.yml
test -f deploy_bundle/monitoring/loki-config.yml
test -f deploy_bundle/monitoring/promtail-config.yml
test -d deploy_bundle/init/postgres
test -d deploy_bundle/init/mongo
- name: Pre-clean monitoring paths on server
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
set -euo pipefail
DEPLOY_DIR='${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}'
mkdir -p "$DEPLOY_DIR/monitoring/grafana/provisioning"
mkdir -p "$DEPLOY_DIR/init/postgres" "$DEPLOY_DIR/init/mongo"
# Nếu lỡ có THƯ MỤC trùng tên file, xóa đi
for f in prometheus.yml loki-config.yml promtail-config.yml; do
if [ -d "$DEPLOY_DIR/monitoring/$f" ]; then
echo "Found directory at $DEPLOY_DIR/monitoring/$f -> removing"
rm -rf "$DEPLOY_DIR/monitoring/$f"
fi
done
- name: Upload bundle to server
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
source: "deploy_bundle/.env,deploy_bundle/docker-compose.prod-infra.yml,deploy_bundle/docker-compose.prod-services.yml,deploy_bundle/deploy.sh,deploy_bundle/monitoring/**,deploy_bundle/init/**"
target: "${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}"
overwrite: true
strip_components: 1
- name: Verify files exist on server (debug)
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
script: |
ls -la '${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}'
- name: Verify .env identical & run deploy
uses: appleboy/[email protected]
env:
IMAGE_TAG: ${{ env.IMAGE_TAG }}
ENV_SHA: ${{ env.ENV_SHA }}
DEPLOY_DIR: ${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT }}
envs: IMAGE_TAG,ENV_SHA
script_stop: true
script: |
set -euo pipefail
DEPLOY_DIR='${{ steps.targetdir.outputs.DEPLOY_DIR_FINAL }}'
mkdir -p "$DEPLOY_DIR"
cd "$DEPLOY_DIR"
if [ ! -f .env ]; then
echo "::error::Không thấy $DEPLOY_DIR/.env trên server."
exit 1
fi
SERVER_SHA=$(sha256sum .env | awk '{print $1}')
echo "Local .env sha: $ENV_SHA"
echo "Server .env sha: $SERVER_SHA"
if [ "$SERVER_SHA" != "$ENV_SHA" ]; then
echo "::error::.env trên server KHÁC repo. Hủy deploy để tránh sai lệch."
exit 1
fi
IMAGE_TAG="latest" DEPLOY_DIR="$DEPLOY_DIR" bash ./deploy.sh