# 自动化发版流水线 # # 触发方式: # 1. 推送 tag:git tag v1.2.3 && git push --tags # 2. 手动触发:GitHub Actions 页面 → Run workflow → 输入版本号 # # 产出物: # - GitHub Release:linux/amd64 + linux/arm64 预编译 tar.gz # - Docker Hub:awuqing/backupx:latest + awuqing/backupx:v1.2.3(多架构) # # 前置配置: # 在仓库 Settings → Secrets → Actions 添加: # - DOCKERHUB_USERNAME(Docker Hub 用户名) # - DOCKERHUB_TOKEN(Docker Hub Access Token) name: Release on: push: tags: - 'v*' workflow_dispatch: inputs: version: description: '版本号(如 v1.2.3)' required: true type: string permissions: contents: read # 统一版本号:tag 推送取 ref_name,手动触发取 inputs.version env: VERSION: ${{ github.event.inputs.version || github.ref_name }} concurrency: group: release-${{ github.ref }}-${{ github.event.inputs.version || 'tag' }} cancel-in-progress: false jobs: # ─── Job 0: 校验版本与测试 ─── verify: name: Validate Release runs-on: ubuntu-latest timeout-minutes: 25 steps: - uses: actions/checkout@v4 - name: Validate release input shell: bash env: RELEASE_VERSION: ${{ env.VERSION }} RELEASE_EVENT: ${{ github.event_name }} RELEASE_REF: ${{ github.ref }} run: | if [[ ! "$RELEASE_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then echo "Version must be a SemVer tag such as v1.2.3 or v1.2.3-rc.1" exit 1 fi if [[ "$RELEASE_EVENT" == "workflow_dispatch" && "$RELEASE_REF" != "refs/heads/main" ]]; then echo "Manual releases must run from the main branch" exit 1 fi - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.25' cache-dependency-path: server/go.sum - name: Verify backend working-directory: server run: | go mod verify unformatted="$(gofmt -l .)" if [ -n "$unformatted" ]; then printf '%s\n' "$unformatted" exit 1 fi go vet ./... go test ./... - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '24' cache: 'npm' cache-dependency-path: web/package-lock.json - name: Verify frontend working-directory: web run: | npm ci npm run test # ─── Job 1: 构建前端 ─── build-web: name: Build Frontend needs: verify runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '24' cache: 'npm' cache-dependency-path: web/package-lock.json - name: Install & Build working-directory: web run: | npm ci npm run build - name: Upload frontend artifact uses: actions/upload-artifact@v4 with: name: web-dist path: web/dist retention-days: 1 # ─── Job 2: 预编译二进制 → GitHub Release ─── build-release: name: Build ${{ matrix.goarch }} needs: build-web runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: write strategy: matrix: include: - goos: linux goarch: amd64 - goos: linux goarch: arm64 steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.25' cache-dependency-path: server/go.sum - name: Download frontend artifact uses: actions/download-artifact@v4 with: name: web-dist path: web/dist - name: Build binary working-directory: server env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} CGO_ENABLED: '0' run: | go build \ -trimpath \ -ldflags "-s -w -X main.version=${VERSION}" \ -o ../backupx \ ./cmd/backupx - name: Package release run: | ARCHIVE_NAME="backupx-${VERSION}-${{ matrix.goos }}-${{ matrix.goarch }}" mkdir -p "${ARCHIVE_NAME}" cp backupx "${ARCHIVE_NAME}/" cp -r web/dist "${ARCHIVE_NAME}/web" cp server/config.example.yaml "${ARCHIVE_NAME}/" cp deploy/install.sh "${ARCHIVE_NAME}/" 2>/dev/null || true cp deploy/backupx.service "${ARCHIVE_NAME}/" 2>/dev/null || true # v2.2+: 随发布包提供 Grafana dashboard 与 nginx.conf 模板 if [ -d deploy/grafana ]; then cp -r deploy/grafana "${ARCHIVE_NAME}/grafana" fi cp deploy/nginx.conf "${ARCHIVE_NAME}/nginx.conf" 2>/dev/null || true tar czf "${ARCHIVE_NAME}.tar.gz" "${ARCHIVE_NAME}" cp "${ARCHIVE_NAME}.tar.gz" "backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz" sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256" sha256sum "backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz" > "backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256" - name: Upload to GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ env.VERSION }} files: | backupx-${{ env.VERSION }}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz backupx-${{ env.VERSION }}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256 backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz backupx-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz.sha256 fail_on_unmatched_files: true generate_release_notes: true # ─── Job 3: Docker 多架构 → Docker Hub ─── build-docker: name: Build & Push Docker needs: build-web runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build & Push uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: true build-args: | VERSION=${{ env.VERSION }} tags: | ${{ secrets.DOCKERHUB_USERNAME }}/backupx:latest ${{ secrets.DOCKERHUB_USERNAME }}/backupx:${{ env.VERSION }} cache-from: type=gha cache-to: type=gha,mode=max