fix(ci): remove dead Mirror step that references undefined ACR_REGIST… #39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: deploy-test | |
| on: | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - develop | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: deploy-test | |
| cancel-in-progress: false | |
| env: | |
| ACR_REPO: registry.cn-hangzhou.aliyuncs.com/muxi/memento | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Validate required secrets | |
| env: | |
| TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }} | |
| TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }} | |
| TEST_SERVER_SSH_KEY: ${{ secrets.TEST_SERVER_SSH_KEY }} | |
| TEST_CONFIG_YAML: ${{ secrets.TEST_CONFIG_YAML }} | |
| TEST_POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }} | |
| ACR_USERNAME: ${{ secrets.ACR_USERNAME }} | |
| ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} | |
| run: | | |
| set -eu | |
| test -n "$TEST_SERVER_HOST" | |
| test -n "$TEST_SERVER_USERNAME" | |
| test -n "$TEST_SERVER_SSH_KEY" | |
| test -n "$TEST_CONFIG_YAML" | |
| test -n "$TEST_POSTGRES_PASSWORD" | |
| test -n "$ACR_USERNAME" | |
| test -n "$ACR_PASSWORD" | |
| - name: Login to ACR | |
| env: | |
| ACR_USERNAME: ${{ secrets.ACR_USERNAME }} | |
| ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} | |
| run: | | |
| echo "$ACR_PASSWORD" | docker login --username "$ACR_USERNAME" --password-stdin registry.cn-hangzhou.aliyuncs.com | |
| - name: Build and push cixing-api to ACR | |
| run: | | |
| set -eu | |
| IMAGE_TAG="${GITHUB_SHA}" | |
| IMAGE_ACR="${ACR_REPO}:${IMAGE_TAG}" | |
| docker build --platform linux/amd64 -f deploy/docker/Dockerfile.api -t "cixing-api:${IMAGE_TAG}" -t "$IMAGE_ACR" . | |
| docker push "$IMAGE_ACR" | |
| - name: Prepare deployment files | |
| env: | |
| TEST_CONFIG_YAML: ${{ secrets.TEST_CONFIG_YAML }} | |
| TEST_POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }} | |
| run: | | |
| set -eu | |
| mkdir -p /tmp/cixing-deploy | |
| cp deploy/compose/docker-compose.prod.yml /tmp/cixing-deploy/docker-compose.yml | |
| printf '%s' "$TEST_CONFIG_YAML" > /tmp/cixing-deploy/config.yaml | |
| { | |
| echo "CIXING_IMAGE=${ACR_REPO}:${GITHUB_SHA}" | |
| echo "POSTGRES_IMAGE=postgres:16-alpine" | |
| echo "REDIS_IMAGE=redis:7-alpine" | |
| echo "MAILPIT_IMAGE=axllent/mailpit:v1.29.7" | |
| echo "POSTGRES_PASSWORD=${TEST_POSTGRES_PASSWORD}" | |
| echo "API_PORT=8080" | |
| echo "MAILPIT_UI_PORT=8025" | |
| } > /tmp/cixing-deploy/deploy.env | |
| - name: Configure SSH | |
| env: | |
| TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }} | |
| TEST_SERVER_SSH_KEY: ${{ secrets.TEST_SERVER_SSH_KEY }} | |
| run: | | |
| set -eu | |
| mkdir -p ~/.ssh | |
| printf '%s\n' "$TEST_SERVER_SSH_KEY" > ~/.ssh/id_test_server | |
| chmod 600 ~/.ssh/id_test_server | |
| ssh-keyscan -H "$TEST_SERVER_HOST" >> ~/.ssh/known_hosts | |
| - name: Upload deployment files | |
| env: | |
| TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }} | |
| TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }} | |
| run: | | |
| set -eu | |
| SCP_OPTS="-o ConnectTimeout=10 -o ServerAliveInterval=15 -i ~/.ssh/id_test_server" | |
| ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" \ | |
| "sudo mkdir -p /opt/cixing/config && sudo chown -R $TEST_SERVER_USERNAME /opt/cixing" | |
| scp $SCP_OPTS /tmp/cixing-deploy/docker-compose.yml \ | |
| "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/docker-compose.yml" | |
| scp $SCP_OPTS /tmp/cixing-deploy/deploy.env \ | |
| "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/deploy.env" | |
| scp $SCP_OPTS /tmp/cixing-deploy/config.yaml \ | |
| "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/config/config.yaml" | |
| - name: Deploy on ECS | |
| env: | |
| TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }} | |
| TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }} | |
| ACR_USERNAME: ${{ secrets.ACR_USERNAME }} | |
| ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }} | |
| run: | | |
| set -eu | |
| ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" "ACR_USERNAME='${ACR_USERNAME}' ACR_PASSWORD='${ACR_PASSWORD}' /bin/sh -s" <<'REMOTE' | |
| set -eu | |
| cd /opt/cixing | |
| DOCKER="docker" | |
| if ! docker info >/dev/null 2>&1; then | |
| DOCKER="sudo docker" | |
| fi | |
| compose() { | |
| $DOCKER compose --env-file /opt/cixing/deploy.env -f /opt/cixing/docker-compose.yml "$@" | |
| } | |
| cleanup_existing_compose_state() { | |
| echo "cleaning existing compose state" | |
| compose down --remove-orphans 2>/dev/null || true | |
| legacy_container_ids="$($DOCKER ps -aq --filter label=com.docker.compose.project=cixing-prod)" | |
| if [ -n "$legacy_container_ids" ]; then | |
| $DOCKER rm -f $legacy_container_ids >/dev/null 2>&1 || true | |
| fi | |
| legacy_network_ids="$($DOCKER network ls -q --filter label=com.docker.compose.project=cixing-prod)" | |
| if [ -n "$legacy_network_ids" ]; then | |
| $DOCKER network rm $legacy_network_ids >/dev/null 2>&1 || true | |
| fi | |
| if command -v ss >/dev/null 2>&1; then | |
| if ss -lntp 2>/dev/null | grep -q ':8080 '; then | |
| echo "port 8080 is still occupied before deployment" >&2 | |
| ss -lntp 2>/dev/null | grep ':8080 ' >&2 || true | |
| exit 1 | |
| fi | |
| fi | |
| } | |
| dump_api_diagnostics() { | |
| compose ps | |
| compose logs --tail=200 api | |
| } | |
| cleanup_old_images() { | |
| echo "cleaning old cixing-api images" | |
| current_tag="$(awk -F= '$1=="CIXING_IMAGE"{print $2}' /opt/cixing/deploy.env | sed 's|.*:||')" | |
| if [ -z "$current_tag" ]; then | |
| return 0 | |
| fi | |
| for image_ref in $($DOCKER images --format '{{.Repository}}:{{.Tag}}' | grep 'cixing\|memento' | grep -v "$current_tag" || true); do | |
| $DOCKER rmi "$image_ref" >/dev/null 2>&1 || true | |
| done | |
| } | |
| api_ready() { | |
| api_container_id="$(compose ps -q api)" | |
| if [ -z "$api_container_id" ]; then | |
| api_state="missing" | |
| api_health="missing" | |
| return 1 | |
| fi | |
| if ! api_state="$($DOCKER inspect --format '{{.State.Status}}' "$api_container_id" 2>/dev/null)"; then | |
| api_container_id="" | |
| api_state="missing" | |
| api_health="missing" | |
| return 1 | |
| fi | |
| if ! api_health="$($DOCKER inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}missing{{end}}' "$api_container_id" 2>/dev/null)"; then | |
| api_container_id="" | |
| api_state="missing" | |
| api_health="missing" | |
| return 1 | |
| fi | |
| if [ "$api_state" != "running" ] || [ "$api_health" != "healthy" ]; then | |
| return 1 | |
| fi | |
| if ! $DOCKER exec "$api_container_id" /bin/sh -c 'wget -qO- http://127.0.0.1:8080/healthz >/dev/null'; then | |
| api_health="healthz_failed" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| chmod 600 /opt/cixing/deploy.env | |
| chmod 644 /opt/cixing/config/config.yaml | |
| cleanup_existing_compose_state | |
| echo "pulling images from ACR" | |
| # Only pull cixing-api from ACR (base images already exist on host) | |
| CIXING_IMAGE="$(grep '^CIXING_IMAGE=' /opt/cixing/deploy.env | cut -d= -f2-)" | |
| echo "$ACR_PASSWORD" | $DOCKER login --username "$ACR_USERNAME" --password-stdin registry.cn-hangzhou.aliyuncs.com | |
| echo "pulling $CIXING_IMAGE" | |
| $DOCKER pull "$CIXING_IMAGE" | |
| echo "starting dependency services" | |
| compose up -d postgres redis mailpit | |
| echo "running database migrations" | |
| compose run --rm -T migrate up </dev/null | |
| echo "database migrations completed" | |
| echo "starting api service" | |
| compose up -d api | |
| attempts=0 | |
| api_state="" | |
| api_health="" | |
| echo "waiting for api readiness" | |
| while [ "$attempts" -lt 30 ]; do | |
| if api_ready; then | |
| break | |
| fi | |
| if [ "$api_state" = "exited" ] || [ "$api_state" = "dead" ]; then | |
| break | |
| fi | |
| attempts=$((attempts + 1)) | |
| sleep 4 | |
| done | |
| if ! api_ready; then | |
| dump_api_diagnostics | |
| if [ "$api_health" = "healthz_failed" ]; then | |
| echo "container /healthz check failed" >&2 | |
| elif [ "$api_state" = "missing" ]; then | |
| echo "api container was not created" >&2 | |
| else | |
| echo "api container state=$api_state health=$api_health" >&2 | |
| fi | |
| exit 1 | |
| fi | |
| echo "api initial readiness check passed: container=$api_container_id state=$api_state health=$api_health" | |
| echo "rechecking api stability after warmup" | |
| sleep 12 | |
| if ! api_ready; then | |
| dump_api_diagnostics | |
| if [ "$api_health" = "healthz_failed" ]; then | |
| echo "api container became unstable after initial readiness: /healthz failed" >&2 | |
| elif [ "$api_state" = "missing" ]; then | |
| echo "api container disappeared after initial readiness" >&2 | |
| else | |
| echo "api container became unstable after initial readiness: state=$api_state health=$api_health" >&2 | |
| fi | |
| exit 1 | |
| fi | |
| final_api_container_id="$(compose ps -q api)" | |
| if [ -z "$final_api_container_id" ]; then | |
| dump_api_diagnostics | |
| echo "api container disappeared before final status output" >&2 | |
| exit 1 | |
| fi | |
| final_api_state="$($DOCKER inspect --format '{{.State.Status}}' "$final_api_container_id")" | |
| final_api_health="$($DOCKER inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}missing{{end}}' "$final_api_container_id")" | |
| echo "final api status: container=$final_api_container_id state=$final_api_state health=$final_api_health" | |
| cleanup_old_images | |
| compose ps | |
| compose logs --tail=80 api | |
| REMOTE |