From 3df59ef345d59afbc2d5a526229ca4ee2d2dd3c1 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Tue, 2 Jun 2026 20:45:05 -0700 Subject: [PATCH] Add GitHub staging deployment workflow --- .github/workflows/deploy-staging.yml | 171 +++++++++++++++++++++++++++ docker-compose.cloud.yml | 4 +- ops/github-staging-deploy.md | 26 ++++ 3 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/deploy-staging.yml create mode 100644 ops/github-staging-deploy.md diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 0000000..b35a2dd --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,171 @@ +name: Deploy Staging + +on: + push: + branches: + - main + - internal + workflow_dispatch: + +permissions: + contents: read + packages: write + +env: + IMAGE_NAME: ghcr.io/tawcorp/hifast-server + DEPLOY_HOST: 154.12.35.103 + DEPLOY_PATH: /opt/hifast-server + HEALTHCHECK_URL: http://127.0.0.1:8080/v1/common/heartbeat + +jobs: + build-and-deploy: + name: Build image and deploy to staging + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + build-args: | + TARGETARCH=amd64 + VERSION=${{ github.sha }} + tags: | + ${{ env.IMAGE_NAME }}:${{ github.sha }} + ${{ env.IMAGE_NAME }}:staging + + - name: Copy compose file + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ env.DEPLOY_HOST }} + username: ${{ secrets.STAGING_SSH_USER }} + key: ${{ secrets.STAGING_SSH_KEY }} + port: ${{ secrets.STAGING_SSH_PORT || 22 }} + source: docker-compose.cloud.yml + target: /tmp/hifast-server-deploy/ + + - name: Deploy on staging server + uses: appleboy/ssh-action@v1.0.3 + env: + GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }} + GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} + with: + host: ${{ env.DEPLOY_HOST }} + username: ${{ secrets.STAGING_SSH_USER }} + key: ${{ secrets.STAGING_SSH_KEY }} + port: ${{ secrets.STAGING_SSH_PORT || 22 }} + timeout: 300s + command_timeout: 600s + envs: GHCR_USERNAME,GHCR_TOKEN + script: | + set -euo pipefail + + IMAGE_NAME="${{ env.IMAGE_NAME }}" + NEW_TAG="${{ github.sha }}" + DEPLOY_PATH="${{ env.DEPLOY_PATH }}" + HEALTHCHECK_URL="${{ env.HEALTHCHECK_URL }}" + ROLLBACK_TAG="rollback-${NEW_TAG}" + + if command -v sudo >/dev/null 2>&1 && ! docker ps >/dev/null 2>&1; then + SUDO="sudo" + else + SUDO="" + fi + + docker_cmd() { + if [ -n "$SUDO" ]; then + sudo docker "$@" + else + docker "$@" + fi + } + + compose_cmd() { + tag="$1" + shift + if docker compose version >/dev/null 2>&1; then + if [ -n "$SUDO" ]; then + sudo env PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker compose -f docker-compose.cloud.yml "$@" + else + PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker compose -f docker-compose.cloud.yml "$@" + fi + else + if [ -n "$SUDO" ]; then + sudo env PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@" + else + PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@" + fi + fi + } + + health_check() { + attempt=1 + while [ "$attempt" -le 6 ]; do + if curl -fsS "$HEALTHCHECK_URL"; then + echo + return 0 + fi + echo "Health check ${attempt}/6 failed; retrying in 10s..." + attempt=$((attempt + 1)) + sleep 10 + done + return 1 + } + + if [ -n "${GHCR_USERNAME:-}" ] && [ -n "${GHCR_TOKEN:-}" ]; then + echo "$GHCR_TOKEN" | docker_cmd login ghcr.io -u "$GHCR_USERNAME" --password-stdin + fi + + if [ -n "$SUDO" ]; then + sudo mkdir -p "$DEPLOY_PATH" + sudo cp /tmp/hifast-server-deploy/docker-compose.cloud.yml "$DEPLOY_PATH/docker-compose.cloud.yml" + else + mkdir -p "$DEPLOY_PATH" + cp /tmp/hifast-server-deploy/docker-compose.cloud.yml "$DEPLOY_PATH/docker-compose.cloud.yml" + fi + + cd "$DEPLOY_PATH" + + PREVIOUS_IMAGE_ID="$(docker_cmd inspect --format '{{.Image}}' ppanel-server 2>/dev/null || true)" + echo "Previous image ID: ${PREVIOUS_IMAGE_ID:-none}" + + echo "Pulling ${IMAGE_NAME}:${NEW_TAG}" + compose_cmd "$NEW_TAG" pull ppanel-server + + echo "Starting ppanel-server" + compose_cmd "$NEW_TAG" up -d ppanel-server + + echo "Checking ${HEALTHCHECK_URL}" + if health_check; then + docker_cmd image prune -f || true + echo "Staging deployment succeeded" + exit 0 + fi + + echo "Health check failed; attempting rollback" + if [ -n "$PREVIOUS_IMAGE_ID" ]; then + docker_cmd tag "$PREVIOUS_IMAGE_ID" "${IMAGE_NAME}:${ROLLBACK_TAG}" + compose_cmd "$ROLLBACK_TAG" up -d ppanel-server + health_check || true + else + echo "No previous image found; rollback skipped" + fi + + docker_cmd image prune -f || true + exit 1 diff --git a/docker-compose.cloud.yml b/docker-compose.cloud.yml index de93cc1..6c04d54 100644 --- a/docker-compose.cloud.yml +++ b/docker-compose.cloud.yml @@ -19,10 +19,10 @@ services: # ---------------------------------------------------- # 1. 业务后端 (PPanel Server) # host 网络:可出外网,直接访问 AWS RDS/Redis;通过 127.0.0.1 访问 Tempo - # PPANEL_SERVER_TAG 由 CI/CD 传入不可变镜像标签(如 git SHA) + # PPANEL_SERVER_IMAGE/PPANEL_SERVER_TAG 由 CI/CD 传入不可变镜像(如 ghcr.io/org/repo:git SHA) # ---------------------------------------------------- ppanel-server: - image: registry.kxsw.us/vpn-server:${PPANEL_SERVER_TAG:?please set PPANEL_SERVER_TAG to an immutable image tag} + image: ${PPANEL_SERVER_IMAGE:-registry.kxsw.us/vpn-server}:${PPANEL_SERVER_TAG:?please set PPANEL_SERVER_TAG to an immutable image tag} container_name: ppanel-server restart: always volumes: diff --git a/ops/github-staging-deploy.md b/ops/github-staging-deploy.md new file mode 100644 index 0000000..a81e8a6 --- /dev/null +++ b/ops/github-staging-deploy.md @@ -0,0 +1,26 @@ +# GitHub Staging Deploy + +This repository deploys the `internal` branch to the staging server `154.12.35.103` with GitHub Actions. + +## Required GitHub secrets + +- `STAGING_SSH_USER`: SSH user for `154.12.35.103`. +- `STAGING_SSH_KEY`: Private SSH key that can log in to the staging server. +- `STAGING_SSH_PORT`: SSH port. Use `22` if the server uses the default port. +- `GHCR_USERNAME`: Optional. Required only if the GHCR package is private and the server needs to log in before pulling. +- `GHCR_TOKEN`: Optional. A GitHub PAT with `read:packages` for private GHCR pulls. + +## Server requirements + +- Docker and either `docker compose` or `docker-compose` are installed. +- The deployment directory is `/opt/hifast-server`. +- Runtime files already exist beside `docker-compose.cloud.yml`, especially `configs/ppanel.yaml`, `logs/`, `cache/`, `loki/`, `grafana/`, `prometheus/`, and `tempo/`. + +## What the workflow does + +1. Builds the Docker image for `linux/amd64`. +2. Pushes `ghcr.io/tawcorp/hifast-server:` and `ghcr.io/tawcorp/hifast-server:staging`. +3. Copies `docker-compose.cloud.yml` to the staging server. +4. Pulls the immutable commit image and restarts `ppanel-server`. +5. Checks `http://127.0.0.1:8080/v1/common/heartbeat`. +6. Attempts rollback to the previous container image if the health check fails.