name: Build # Documentation-only changes skip this workflow entirely. # # A push to master here does not just build - it pushes an image, runs the # migrations and restarts the demo container, so the site takes a short outage. # Paying that for a README edit is waste at best; at worst a deploy fails for a # reason unrelated to anything in the change. Code coverage is unaffected, # because go.yml still builds every push and pull request. on: push: branches: [ master ] paths-ignore: - '**.md' - 'docs/**' - 'LICENSE*' - '.github/ISSUE_TEMPLATE/**' pull_request: branches: [ master ] paths-ignore: - '**.md' - 'docs/**' - 'LICENSE*' - '.github/ISSUE_TEMPLATE/**' # One deploy at a time. Two merges seconds apart raced here: both runs did # docker rm -f then docker run, the second removed the container the first had # just created, and the first's docker run then failed on a name conflict - # leaving the demo on the older image with a red deploy. concurrency: group: deploy-${{ github.ref }} cancel-in-progress: false env: IMAGE_NAME: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api # 镜像名称 TAG: ${{ github.sha }} IMAGE_NAME_TAG: registry.ap-northeast-1.aliyuncs.com/go-admin/go-admin-api:${{ github.sha }} jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: 1.26.5 - name: Tidy run: go mod tidy - name: Build run: env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags "sqlite3,json1" --ldflags "-extldflags -static" -o main . # 以下推镜像与重启步骤仅在 master 收到 push 时执行。 # pull_request 事件同样会触发本工作流,若不加限制,任何指向 master 的 # PR 一经创建就会把 PR 分支的镜像推上仓库,并直接重启线上 API 服务, # 且发生在合并之前。构建与编译校验不受影响,PR 仍会执行。 - name: Build the Docker image and push if: github.event_name == 'push' && github.ref == 'refs/heads/master' run: | docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }} echo "************ docker login end" docker build -t go-admin-api:latest . echo "************ docker build end" docker tag go-admin-api ${{ env.IMAGE_NAME_TAG }} echo "************ docker tag end" docker images echo "************ docker images end" docker push ${{ env.IMAGE_NAME_TAG }} # 推送 echo "************ docker push end" - name: Restart server # 第五步,重启服务 if: github.event_name == 'push' && github.ref == 'refs/heads/master' uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5 env: GITHUB_SHA_X: ${GITHUB_SHA} with: host: ${{ secrets.SSH_HOST }} # 下面三个配置与上一步类似 username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.DEPLOY_KEY }} # 重启的脚本,根据自身情况做相应改动,一般要做的是migrate数据库以及重启服务器 # # 配置从宿主机挂载,不使用镜像里的那份:演示站连的是托管数据库, # 而 config/settings.demo.yml 会随仓库公开、也会打进镜像,凭据不能写在那里。 # 镜像里那份保持 sqlite,供 clone 仓库的人开箱即用。 # # 路径本身走 secret:它不是凭据,但本仓库公开,没有理由把服务器的 # 目录结构一并公布。DEMO_CONFIG_PATH 指向宿主机上那份配置。 # # 顺序是有意的:迁移先跑,跑不过就保持现有版本不动; # 旧容器改名保留而不是删除,新容器不健康时能原样恢复。 # 健康检查两条都要过——HTTP 活着不代表数据库通了。 script: | set -u CFG="${{ secrets.DEMO_CONFIG_PATH }}" IMG="${{ env.IMAGE_NAME_TAG }}" NAME=go-admin-api PREV="$NAME-prev" test -f "$CFG" || { echo "宿主机配置缺失,中止部署"; exit 1; } sudo docker login --username=${{ secrets.DOCKER_USERNAME }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PASSWORD }} sudo docker pull "$IMG" || { echo "拉取镜像失败,中止部署"; exit 1; } # 迁移用新镜像跑。失败时线上仍是旧版本配旧 schema,是自洽的; # 硬切过去才会得到代码与表对不上的服务。 if ! sudo docker run --rm -v "$CFG":/config/settings.yml:ro "$IMG" \ /main migrate -c /config/settings.yml; then echo "迁移失败,保持现有版本"; exit 1 fi if sudo docker ps -a --format '{{.Names}}' | grep -qx "$NAME"; then sudo docker rm -f "$PREV" >/dev/null 2>&1 || true sudo docker rename "$NAME" "$PREV" sudo docker stop "$PREV" >/dev/null fi sudo docker run -d -p 8000:8000 \ -v "$CFG":/config/settings.yml:ro \ --name "$NAME" "$IMG" ok=0 for i in $(seq 1 20); do sleep 3 code=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://127.0.0.1:8000/api/v1/captcha 2>/dev/null || true) if [ "$code" = "200" ] && sudo docker logs "$NAME" 2>&1 | grep -q 'connect success'; then ok=1; echo "健康检查通过(第 $i 次探测)"; break fi done if [ "$ok" = "1" ]; then sudo docker rm -f "$PREV" >/dev/null 2>&1 || true else echo "健康检查失败,回滚到上一版本" sudo docker logs --tail 40 "$NAME" 2>&1 || true sudo docker rm -f "$NAME" >/dev/null 2>&1 || true if sudo docker ps -a --format '{{.Names}}' | grep -qx "$PREV"; then sudo docker rename "$PREV" "$NAME" sudo docker start "$NAME" >/dev/null echo "已恢复" fi exit 1 fi