mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 03:06:38 +08:00
265 lines
9.5 KiB
YAML
265 lines
9.5 KiB
YAML
name: Build & Publish
|
||
|
||
# 版本策略(version 分支托管,main 零污染):
|
||
# - VERSION 文件单独存放在 version 分支,CI 构建时读取并自增写回 version 分支,
|
||
# main 分支不再出现任何 CI 提交,本地推送永不与远程冲突。
|
||
# - push 到 main:版本号自动 patch+1,发布 latest + 版本镜像、GitHub Release、
|
||
# 多平台单文件二进制,并部署服务器。
|
||
# - push tag v*:正式发版,版本号取 tag 名(不 bump version 分支),其余同上。
|
||
# - 手动触发:版本号在 version 分支当前值上自增,等同 push main 全量发布。
|
||
# 查看当前版本号:git show origin/version:VERSION
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
tags: ['v*']
|
||
|
||
workflow_dispatch:
|
||
|
||
permissions:
|
||
contents: write # 读写 version 分支、发布 Release 与上传二进制需要
|
||
packages: write
|
||
|
||
jobs:
|
||
build-image:
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
version: ${{ steps.version.outputs.version }}
|
||
release_tag: ${{ steps.version.outputs.release_tag }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
|
||
# 1. 解析版本号:tag 触发取 tag 名(去掉 v 前缀);其余场景读 version 分支并 patch+1
|
||
- name: Resolve version
|
||
id: version
|
||
run: |
|
||
if [ "${{ github.ref_type }}" = "tag" ]; then
|
||
VERSION="${GITHUB_REF_NAME#v}"
|
||
else
|
||
git fetch origin version
|
||
BASE=$(git show FETCH_HEAD:VERSION 2>/dev/null || echo "0.0.0")
|
||
MAJOR=$(echo "$BASE" | cut -d. -f1)
|
||
MINOR=$(echo "$BASE" | cut -d. -f2)
|
||
PATCH=$(echo "$BASE" | cut -d. -f3)
|
||
VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
|
||
fi
|
||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||
echo "release_tag=mebox-v${VERSION}" >> "$GITHUB_OUTPUT"
|
||
echo "new_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||
|
||
# 2. 把新版本号写回 version 分支(clone 单分支写入,冲突时 rebase 重试)
|
||
# tag 触发的正式发版版本号来自 tag 本身,跳过自增。
|
||
- name: Bump version branch
|
||
if: github.ref_type != 'tag'
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
run: |
|
||
REPO="https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
||
git clone --depth 1 --branch version "$REPO" "$RUNNER_TEMP/version-branch"
|
||
cd "$RUNNER_TEMP/version-branch"
|
||
git config user.name "github-actions[bot]"
|
||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||
echo "${{ steps.version.outputs.new_version }}" > VERSION
|
||
git commit -am "chore: bump version to ${{ steps.version.outputs.new_version }}"
|
||
ok=0
|
||
for i in 1 2 3 4 5; do
|
||
if git push origin version; then ok=1; break; fi
|
||
git pull --rebase origin version || true
|
||
sleep 5
|
||
done
|
||
[ "$ok" = "1" ] || { echo "::error::version 分支推送冲突,重试 5 次仍失败"; exit 1; }
|
||
|
||
# 3. 设置 Docker QEMU 和 Buildx
|
||
- uses: docker/setup-qemu-action@v3
|
||
- uses: docker/setup-buildx-action@v3
|
||
|
||
# 3. 登录 GHCR
|
||
- name: Log in to GHCR
|
||
uses: docker/login-action@v3
|
||
with:
|
||
registry: ghcr.io
|
||
username: ${{ github.actor }}
|
||
password: ${{ secrets.GITHUB_TOKEN }}
|
||
|
||
# 4. 提取镜像元数据
|
||
- name: Extract image metadata
|
||
id: meta
|
||
uses: docker/metadata-action@v5
|
||
with:
|
||
images: ghcr.io/${{ github.repository_owner }}/mebox
|
||
tags: |
|
||
type=raw,value=latest
|
||
type=raw,value=${{ steps.version.outputs.version }}
|
||
|
||
# 5. 构建并推送
|
||
- name: Build & push
|
||
uses: docker/build-push-action@v6
|
||
with:
|
||
context: .
|
||
platforms: linux/amd64,linux/arm64
|
||
push: true
|
||
provenance: false
|
||
sbom: false
|
||
tags: ${{ steps.meta.outputs.tags }}
|
||
labels: ${{ steps.meta.outputs.labels }}
|
||
build-args: |
|
||
VERSION=${{ steps.version.outputs.release_tag }}
|
||
cache-from: type=gha
|
||
cache-to: type=gha,mode=max
|
||
|
||
# 单文件可执行构建:把前端打包进二进制(go:embed),交叉编译 Windows /
|
||
# Linux / macOS 的 amd64 / arm64 产物,作为 GitHub Release 附件发布。
|
||
build-frontend:
|
||
needs: [build-image]
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version-file: '.nvmrc'
|
||
cache: 'npm'
|
||
cache-dependency-path: web/package-lock.json
|
||
- name: Install
|
||
working-directory: web
|
||
run: npm ci
|
||
- name: Build SPA
|
||
working-directory: web
|
||
run: npm run build
|
||
- name: Upload web/dist
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: web-dist
|
||
path: web/dist
|
||
retention-days: 1
|
||
|
||
# 先创建(幂等)空的 GitHub Release,供后续 build-binaries 并行上传附件,
|
||
# 也避免矩阵各 job 并发 upload 时 release 尚不存在而互相竞争。
|
||
publish-create-release:
|
||
needs: [build-image]
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: write
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- name: Create release
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
RELEASE_TAG: ${{ needs.build-image.outputs.release_tag }}
|
||
run: |
|
||
set -eux
|
||
# tag push 时 tag 已存在;手动触发时基于当前 main 创建 tag(幂等)
|
||
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
||
git tag "$RELEASE_TAG"
|
||
git push origin "$RELEASE_TAG"
|
||
fi
|
||
gh release create "$RELEASE_TAG" \
|
||
--title "MeBox ${{ needs.build-image.outputs.version }}" \
|
||
--notes "自动化发布 ${{ needs.build-image.outputs.version }}" \
|
||
--verify-tag --latest || true
|
||
|
||
build-binaries:
|
||
needs: [build-image, build-frontend, publish-create-release]
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: write
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- goos: linux
|
||
goarch: amd64
|
||
ext: ""
|
||
- goos: linux
|
||
goarch: arm64
|
||
ext: ""
|
||
- goos: windows
|
||
goarch: amd64
|
||
ext: .exe
|
||
- goos: windows
|
||
goarch: arm64
|
||
ext: .exe
|
||
- goos: darwin
|
||
goarch: amd64
|
||
ext: ""
|
||
- goos: darwin
|
||
goarch: arm64
|
||
ext: ""
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-go@v5
|
||
with:
|
||
go-version: '1.25'
|
||
cache: true
|
||
- name: Download web/dist
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
name: web-dist
|
||
path: web/dist
|
||
- name: Build binary
|
||
run: |
|
||
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
|
||
go build -trimpath -ldflags="-s -w -X main.version=${{ needs.build-image.outputs.release_tag }}" \
|
||
-o "dist/mebox-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}" ./cmd/server
|
||
- name: Package
|
||
run: |
|
||
mkdir -p package/mebox
|
||
cp "dist/mebox-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}" package/mebox/mebox${{ matrix.ext }}
|
||
cp README.md package/mebox/ 2>/dev/null || true
|
||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||
(cd package && zip -r "../mebox_${{ matrix.goos }}_${{ matrix.goarch }}.zip" mebox)
|
||
else
|
||
tar -czf "mebox_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz" -C package mebox
|
||
fi
|
||
- name: Upload to GitHub Release
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
RELEASE_TAG: ${{ needs.build-image.outputs.release_tag }}
|
||
run: |
|
||
set -eux
|
||
PKG="mebox_${{ matrix.goos }}_${{ matrix.goarch }}.zip"
|
||
TAR="mebox_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz"
|
||
# 并发上传到同一 release 各自文件,--clobber 幂等覆盖
|
||
if [ -f "$PKG" ]; then
|
||
for i in 1 2 3; do gh release upload "$RELEASE_TAG" "$PKG" --clobber && break || sleep 5; done
|
||
fi
|
||
if [ -f "$TAR" ]; then
|
||
for i in 1 2 3; do gh release upload "$RELEASE_TAG" "$TAR" --clobber && break || sleep 5; done
|
||
fi
|
||
|
||
deploy:
|
||
name: Deploy to Server
|
||
needs: [build-image]
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Deploy via SSH
|
||
uses: appleboy/ssh-action@v1.0.3
|
||
with:
|
||
host: ${{ secrets.SERVER_HOST }}
|
||
username: ${{ secrets.SERVER_USER }}
|
||
password: ${{ secrets.SERVER_PASSWORD }}
|
||
port: ${{ secrets.SERVER_PORT }}
|
||
script: |
|
||
set -e
|
||
echo "==== 开始部署 MeBox ===="
|
||
cd /root/dockerData/mebox
|
||
|
||
# 判断 compose 命令版本兼容性(docker compose 或 docker-compose)
|
||
if docker compose version >/dev/null 2>&1; then
|
||
COMPOSE_CMD="docker compose"
|
||
elif command -v docker-compose >/dev/null 2>&1; then
|
||
COMPOSE_CMD="docker-compose"
|
||
else
|
||
echo "错误: 未找到 docker compose 或 docker-compose"
|
||
exit 1
|
||
fi
|
||
|
||
echo "正在拉取最新镜像..."
|
||
$COMPOSE_CMD pull
|
||
|
||
echo "正在重启服务..."
|
||
$COMPOSE_CMD up -d
|
||
|
||
echo "清理旧的无用镜像..."
|
||
docker image prune -f
|
||
|
||
echo "==== 部署完成并已启动 ===="
|