From 2d8cc8ebe5ef803142f884c1e2f32896f5b083b2 Mon Sep 17 00:00:00 2001 From: Flik <2470982985@qq.com> Date: Thu, 19 Mar 2026 20:08:00 +0800 Subject: [PATCH] Add GitHub CI and release workflows --- .github/workflows/build.yml | 202 ++++++++++++++------------------ .github/workflows/release.yml | 214 ++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+), 115 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c7208a..beffdbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,145 +1,117 @@ -name: Build Multi-Platform Binaries +name: CI Build + on: push: - paths: - - '**.go' - - 'go.mod' - - 'go.sum' - - 'web/**' - - '.github/workflows/**' + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build-frontend: - runs-on: node + frontend: + name: Build frontend + runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - # 直接挂载宿主机的缓存目录 - - name: Build Frontend - run: | - cd web - # 使用共享的 node_modules 缓存 - if [ -d /data/cache/node_modules_cache ]; then - echo "Restoring node_modules from cache..." - cp -r /data/cache/node_modules_cache/node_modules . 2>/dev/null || true - fi - - npm ci --prefer-offline --no-audit - - # 保存缓存 - mkdir -p /data/cache/node_modules_cache - cp -r node_modules /data/cache/node_modules_cache/ 2>/dev/null || true - - npm run build - - - name: Upload Frontend Artifact - uses: actions/upload-artifact@v3 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Install frontend dependencies + working-directory: web + run: npm ci + + - name: Build frontend + working-directory: web + run: npm run build + + - name: Upload frontend artifact + uses: actions/upload-artifact@v4 with: name: frontend-dist path: web/dist retention-days: 1 - build-binaries: - needs: build-frontend - runs-on: golang + build: + name: Build ${{ matrix.goos }}/${{ matrix.goarch }} + needs: frontend + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - - { goos: linux, goarch: amd64, target: server, upx: true } - - { goos: linux, goarch: amd64, target: client, upx: true } - - { goos: linux, goarch: arm64, target: server, upx: true } - - { goos: linux, goarch: arm64, target: client, upx: true } - - { goos: linux, goarch: arm, goarm: 7, target: server, upx: true } - - { goos: linux, goarch: arm, goarm: 7, target: client, upx: true } - - { goos: windows, goarch: amd64, target: server, upx: true } - - { goos: windows, goarch: amd64, target: client, upx: true } - - { goos: windows, goarch: arm64, target: server, upx: false } - - { goos: darwin, goarch: amd64, target: server, upx: false } - - { goos: darwin, goarch: arm64, target: server, upx: false } - + - runner: ubuntu-latest + goos: linux + goarch: amd64 + - runner: ubuntu-latest + goos: linux + goarch: arm64 + - runner: windows-latest + goos: windows + goarch: amd64 + - runner: macos-latest + goos: darwin + goarch: amd64 + - runner: macos-latest + goos: darwin + goarch: arm64 + steps: - - name: Install Dependencies - run: | - if command -v apk > /dev/null; then - apk add --no-cache nodejs upx - elif command -v apt-get > /dev/null; then - apt-get update && apt-get install -y nodejs upx-ucl - else - echo "Unsupported package manager" && exit 1 - fi - - name: Checkout code uses: actions/checkout@v4 - - # 使用挂载卷缓存 Go 模块 - - name: Setup Go Cache - run: | - # 创建缓存目录 - mkdir -p /data/cache/go-pkg-mod - mkdir -p /data/cache/go-build-cache - mkdir -p ~/go/pkg/mod - mkdir -p ~/.cache/go-build - - # 恢复缓存(使用硬链接以节省空间和时间) - if [ -d /data/cache/go-pkg-mod ] && [ "$(ls -A /data/cache/go-pkg-mod)" ]; then - echo "Restoring Go pkg cache..." - cp -al /data/cache/go-pkg-mod/* ~/go/pkg/mod/ 2>/dev/null || true - fi - - if [ -d /data/cache/go-build-cache ] && [ "$(ls -A /data/cache/go-build-cache)" ]; then - echo "Restoring Go build cache..." - cp -al /data/cache/go-build-cache/* ~/.cache/go-build/ 2>/dev/null || true - fi - - - name: Download Go dependencies - run: go mod download - - - name: Download Frontend Artifact - uses: actions/download-artifact@v3 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Download frontend artifact + uses: actions/download-artifact@v4 with: name: frontend-dist path: internal/server/app/dist - - - name: Build Binary + + - name: Download Go modules + run: go mod download + + - name: Build server and client + shell: bash env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} - GOARM: ${{ matrix.goarm }} CGO_ENABLED: 0 run: | - ARM_VAL="" - [ "${{ matrix.goarch }}" = "arm" ] && ARM_VAL="v${{ matrix.goarm }}" - + mkdir -p build/${GOOS}_${GOARCH} EXT="" - [ "${{ matrix.goos }}" = "windows" ] && EXT=".exe" - - FILENAME="gotunnel-${{ matrix.target }}-${{ matrix.goos }}-${{ matrix.goarch }}${ARM_VAL}${EXT}" - - go build -trimpath -ldflags="-s -w" -o "${FILENAME}" ./cmd/${{ matrix.target }} - - echo "CURRENT_FILENAME=${FILENAME}" >> $GITHUB_ENV - - # 保存 Go 缓存(异步,不阻塞主流程) - - name: Save Go Cache - if: always() - run: | - # 只在缓存有更新时保存(使用 rsync 可以更高效) - rsync -a --delete ~/go/pkg/mod/ /data/cache/go-pkg-mod/ 2>/dev/null || \ - cp -r ~/go/pkg/mod/* /data/cache/go-pkg-mod/ 2>/dev/null || true - - rsync -a --delete ~/.cache/go-build/ /data/cache/go-build-cache/ 2>/dev/null || \ - cp -r ~/.cache/go-build/* /data/cache/go-build-cache/ 2>/dev/null || true - - - name: Run UPX Compression - if: matrix.upx == true - run: | - upx --best --lzma "${{ env.CURRENT_FILENAME }}" || echo "UPX skipped for this platform" - - - name: Upload Binary - uses: actions/upload-artifact@v3 + if [ "$GOOS" = "windows" ]; then + EXT=".exe" + fi + + BUILD_TIME="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + GIT_COMMIT="${GITHUB_SHA::7}" + VERSION="${GITHUB_REF_NAME}" + if [ "${GITHUB_REF_TYPE}" != "tag" ]; then + VERSION="${GITHUB_SHA::7}" + fi + + LDFLAGS="-s -w -X 'main.Version=${VERSION}' -X 'main.BuildTime=${BUILD_TIME}' -X 'main.GitCommit=${GIT_COMMIT}'" + + go build -trimpath -ldflags "$LDFLAGS" -o "build/${GOOS}_${GOARCH}/server${EXT}" ./cmd/server + go build -trimpath -ldflags "$LDFLAGS" -o "build/${GOOS}_${GOARCH}/client${EXT}" ./cmd/client + + - name: Upload binaries artifact + uses: actions/upload-artifact@v4 with: - name: ${{ env.CURRENT_FILENAME }} - path: ${{ env.CURRENT_FILENAME }} + name: gotunnel-${{ matrix.goos }}-${{ matrix.goarch }} + path: build/${{ matrix.goos }}_${{ matrix.goarch }}/ retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0215321 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,214 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Release tag to publish, e.g. v1.2.3' + required: true + type: string + +permissions: + contents: write + +concurrency: + group: release-${{ github.event.inputs.tag || github.ref }} + cancel-in-progress: false + +jobs: + frontend: + name: Build frontend + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: npm + cache-dependency-path: web/package-lock.json + + - name: Install frontend dependencies + working-directory: web + run: npm ci + + - name: Build frontend + working-directory: web + run: npm run build + + - name: Upload frontend artifact + uses: actions/upload-artifact@v4 + with: + name: frontend-dist + path: web/dist + retention-days: 1 + + build-assets: + name: Package ${{ matrix.component }} ${{ matrix.goos }}/${{ matrix.goarch }} + needs: frontend + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - component: server + goos: linux + goarch: amd64 + archive_ext: tar.gz + - component: client + goos: linux + goarch: amd64 + archive_ext: tar.gz + - component: server + goos: linux + goarch: arm64 + archive_ext: tar.gz + - component: client + goos: linux + goarch: arm64 + archive_ext: tar.gz + - component: server + goos: darwin + goarch: amd64 + archive_ext: tar.gz + - component: client + goos: darwin + goarch: amd64 + archive_ext: tar.gz + - component: server + goos: darwin + goarch: arm64 + archive_ext: tar.gz + - component: client + goos: darwin + goarch: arm64 + archive_ext: tar.gz + - component: server + goos: windows + goarch: amd64 + archive_ext: zip + - component: client + goos: windows + goarch: amd64 + archive_ext: zip + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Download frontend artifact + uses: actions/download-artifact@v4 + with: + name: frontend-dist + path: internal/server/app/dist + + - name: Download Go modules + run: go mod download + + - name: Resolve release metadata + id: meta + shell: bash + run: | + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${GITHUB_REF_NAME}" + fi + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "commit=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + echo "build_time=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" + + - name: Build binary + shell: bash + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: 0 + VERSION: ${{ steps.meta.outputs.tag }} + GIT_COMMIT: ${{ steps.meta.outputs.commit }} + BUILD_TIME: ${{ steps.meta.outputs.build_time }} + run: | + mkdir -p dist/package + EXT="" + if [ "$GOOS" = "windows" ]; then + EXT=".exe" + fi + + OUTPUT_NAME="${{ matrix.component }}${EXT}" + LDFLAGS="-s -w -X 'main.Version=${VERSION}' -X 'main.BuildTime=${BUILD_TIME}' -X 'main.GitCommit=${GIT_COMMIT}'" + go build -trimpath -ldflags "$LDFLAGS" -o "dist/package/${OUTPUT_NAME}" ./cmd/${{ matrix.component }} + + - name: Create release archive + id: package + shell: bash + run: | + TAG="${{ steps.meta.outputs.tag }}" + ARCHIVE="gotunnel-${{ matrix.component }}-${TAG}-${{ matrix.goos }}-${{ matrix.goarch }}.${{ matrix.archive_ext }}" + mkdir -p dist/out + if [ "${{ matrix.archive_ext }}" = "zip" ]; then + (cd dist/package && zip -r "../out/${ARCHIVE}" .) + else + tar -C dist/package -czf "dist/out/${ARCHIVE}" . + fi + echo "archive=dist/out/${ARCHIVE}" >> "$GITHUB_OUTPUT" + + - name: Upload release asset artifact + uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.component }}-${{ matrix.goos }}-${{ matrix.goarch }} + path: ${{ steps.package.outputs.archive }} + retention-days: 1 + + publish: + name: Publish release + needs: build-assets + runs-on: ubuntu-latest + steps: + - name: Download packaged assets + uses: actions/download-artifact@v4 + with: + path: release-artifacts + pattern: release-* + merge-multiple: true + + - name: Resolve release metadata + id: meta + shell: bash + run: | + if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${GITHUB_REF_NAME}" + fi + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + - name: Generate checksums + shell: bash + run: | + cd release-artifacts + sha256sum * > SHA256SUMS.txt + + - name: Create or update GitHub release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.meta.outputs.tag }} + target_commitish: ${{ github.sha }} + generate_release_notes: true + fail_on_unmatched_files: true + files: | + release-artifacts/*