From f3162fba44a6be4027e3356da089bb81451236fd Mon Sep 17 00:00:00 2001 From: Eeveid <448859157@qq.com> Date: Mon, 25 May 2026 11:44:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20Gitea=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/release.yml | 51 ++++++++ README.md | 31 +++++ scripts/publish-gitea-release.ps1 | 120 +++++++++++++++++++ scripts/publish-gitea-release.sh | 186 ++++++++++++++++++++++++++++++ 4 files changed, 388 insertions(+) create mode 100644 .gitea/workflows/release.yml create mode 100644 scripts/publish-gitea-release.ps1 create mode 100755 scripts/publish-gitea-release.sh diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..d3a86c5 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,51 @@ +name: 发布 Linux 二进制 + +on: + push: + tags: + - "v*" + +jobs: + release-linux-x86_64: + runs-on: ubuntu-latest + steps: + - name: 检出代码 + uses: actions/checkout@v4 + + - name: 安装系统依赖 + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config sqlite3 curl tar + + - name: 安装 Rust + uses: dtolnay/rust-toolchain@stable + + - name: 安装 Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: web/package-lock.json + + - name: 构建发布包 + run: | + TAG="${GITHUB_REF_NAME:-${GITEA_REF_NAME:-${GITHUB_REF##*/}}}" + VERSION="${TAG#v}" + bash scripts/build-release.sh --version "$VERSION" --target x86_64-unknown-linux-gnu + + - name: 发布到 Gitea Release + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_URL: https://gitea.kmux.cn + GITEA_OWNER: Eeveid + GITEA_REPO: lightOps + run: | + TAG="${GITHUB_REF_NAME:-${GITEA_REF_NAME:-${GITHUB_REF##*/}}}" + bash scripts/publish-gitea-release.sh \ + --tag "$TAG" \ + --title "LightOps ${TAG}" \ + --target "$GITHUB_SHA" \ + --no-create-tag \ + --no-push-tag \ + --package target/releases/*.tar.gz \ + --package target/releases/*.tar.gz.sha256 diff --git a/README.md b/README.md index 8a6aa2e..c238e37 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,37 @@ lightops/ 推荐生产环境使用“发布包安装”。目标服务器只需要下载发布包、解压并注册 systemd 服务,不需要安装 Rust、Node.js,也不需要现场编译。 +## 自动发布 Release + +仓库已内置 Gitea Actions 工作流:推送 `v*` 标签后,Linux runner 会自动构建 `x86_64-unknown-linux-gnu` 发布包,并上传到 Gitea Release。 + +首次使用前,在 Gitea 仓库设置中添加 Actions Secret: + +```text +GITEA_TOKEN=<具备当前仓库 Release 写入权限的访问令牌> +``` + +发布新版本: + +```bash +git tag -a v0.1.0 -m "LightOps v0.1.0" +git push origin v0.1.0 +``` + +如果不使用 Gitea Actions,也可以在任意构建机手动构建并上传: + +```bash +bash scripts/build-release.sh --version 0.1.0 --target x86_64-unknown-linux-gnu +GITEA_TOKEN= bash scripts/publish-gitea-release.sh --tag v0.1.0 --package target/releases/lightops-0.1.0-x86_64-unknown-linux-gnu.tar.gz --package target/releases/lightops-0.1.0-x86_64-unknown-linux-gnu.tar.gz.sha256 +``` + +Windows PowerShell 手动上传: + +```powershell +$env:GITEA_TOKEN="" +pwsh -File scripts\publish-gitea-release.ps1 -Tag v0.1.0 -Package target\releases\lightops-0.1.0-x86_64-pc-windows-gnu.zip,target\releases\lightops-0.1.0-x86_64-pc-windows-gnu.zip.sha256 +``` + 先在构建机或 CI 上生成发布包: ```bash diff --git a/scripts/publish-gitea-release.ps1 b/scripts/publish-gitea-release.ps1 new file mode 100644 index 0000000..0972f47 --- /dev/null +++ b/scripts/publish-gitea-release.ps1 @@ -0,0 +1,120 @@ +param( + [string]$GiteaUrl = $(if ($env:GITEA_URL) { $env:GITEA_URL } else { "https://gitea.kmux.cn" }), + [string]$Owner = $(if ($env:GITEA_OWNER) { $env:GITEA_OWNER } else { "Eeveid" }), + [string]$Repo = $(if ($env:GITEA_REPO) { $env:GITEA_REPO } else { "lightOps" }), + [Parameter(Mandatory = $true)][string]$Tag, + [string]$Title = "", + [string]$Notes = "", + [string]$Target = "main", + [Parameter(Mandatory = $true)][string[]]$Package, + [switch]$NoCreateTag, + [switch]$NoPushTag, + [switch]$Prerelease, + [switch]$SkipCertificateCheck +) + +$ErrorActionPreference = "Stop" + +if (-not $env:GITEA_TOKEN) { + throw "必须设置环境变量 GITEA_TOKEN" +} + +if (-not $Title) { + $Title = $Tag +} + +foreach ($item in $Package) { + if (-not (Test-Path -LiteralPath $item -PathType Leaf)) { + throw "发布包不存在:$item" + } +} + +function Write-Step { + param([string]$Message) + Write-Host "[LightOps] $Message" -ForegroundColor Green +} + +function Api-Url { + param([string]$Path) + "$($GiteaUrl.TrimEnd('/'))/api/v1/repos/$Owner/$Repo/$Path" +} + +function Invoke-Gitea { + param( + [string]$Method, + [string]$Uri, + $Body = $null, + [string]$ContentType = "application/json" + ) + $headers = @{ Authorization = "token $env:GITEA_TOKEN" } + $args = @{ + Method = $Method + Uri = $Uri + Headers = $headers + } + if ($Body -ne $null) { + $args.Body = $Body + $args.ContentType = $ContentType + } + if ($SkipCertificateCheck) { + $args.SkipCertificateCheck = $true + } + Invoke-RestMethod @args +} + +if (-not $NoCreateTag) { + git rev-parse $Tag *> $null + if ($LASTEXITCODE -ne 0) { + Write-Step "创建本地 tag:$Tag" + git tag -a $Tag $Target -m $Title + if ($LASTEXITCODE -ne 0) { + throw "创建 tag 失败" + } + } +} + +if (-not $NoPushTag) { + Write-Step "推送 tag:$Tag" + git push origin $Tag + if ($LASTEXITCODE -ne 0) { + throw "推送 tag 失败" + } +} + +$release = $null +try { + $release = Invoke-Gitea -Method Get -Uri (Api-Url "releases/tags/$Tag") + Write-Step "Release 已存在:$Tag" +} catch { + Write-Step "创建 Release:$Tag" + $payload = @{ + tag_name = $Tag + target_commitish = $Target + name = $Title + body = $Notes + draft = $false + prerelease = [bool]$Prerelease + } | ConvertTo-Json -Depth 5 + $release = Invoke-Gitea -Method Post -Uri (Api-Url "releases") -Body $payload +} + +foreach ($item in $Package) { + $file = Get-Item -LiteralPath $item + Write-Step "上传附件:$($file.Name)" + $headers = @{ Authorization = "token $env:GITEA_TOKEN" } + $uri = "$(Api-Url "releases/$($release.id)/assets")?name=$([uri]::EscapeDataString($file.Name))" + $form = @{ attachment = $file } + $args = @{ + Method = "Post" + Uri = $uri + Headers = $headers + Form = $form + } + if ($SkipCertificateCheck) { + $args.SkipCertificateCheck = $true + } + Invoke-RestMethod @args | Out-Null +} + +Write-Host "" +Write-Host "Release 发布完成:$($GiteaUrl.TrimEnd('/'))/$Owner/$Repo/releases/tag/$Tag" diff --git a/scripts/publish-gitea-release.sh b/scripts/publish-gitea-release.sh new file mode 100755 index 0000000..fde3288 --- /dev/null +++ b/scripts/publish-gitea-release.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +GITEA_URL="${GITEA_URL:-https://gitea.kmux.cn}" +OWNER="${GITEA_OWNER:-Eeveid}" +REPO="${GITEA_REPO:-lightOps}" +TAG="" +TITLE="" +NOTES="" +TARGET="main" +CREATE_TAG="true" +PUSH_TAG="true" +PRERELEASE="false" +PACKAGES=() + +usage() { + cat <<'EOF' +LightOps Gitea Release 自动发布脚本 + +用法: + GITEA_TOKEN= bash scripts/publish-gitea-release.sh --tag v0.1.0 --package target/releases/lightops.tar.gz + +选项: + --gitea-url Gitea 地址,默认 https://gitea.kmux.cn + --owner 仓库所有者,默认 Eeveid + --repo 仓库名,默认 lightOps + --tag 发布标签,例如 v0.1.0,必填 + --title Release 标题,默认等于 tag + --notes <text> Release 说明 + --target <ref> tag 指向的分支或提交,默认 main + --package <path> 要上传的发布包,可重复传入 + --no-create-tag 不自动创建本地 tag + --no-push-tag 不自动推送 tag + --prerelease 标记为预发布 + -h, --help 显示帮助 + +环境变量: + GITEA_TOKEN Gitea Access Token,必须具备仓库 Release 写入权限 +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --gitea-url) + GITEA_URL="${2:?缺少 --gitea-url 参数值}" + shift 2 + ;; + --owner) + OWNER="${2:?缺少 --owner 参数值}" + shift 2 + ;; + --repo) + REPO="${2:?缺少 --repo 参数值}" + shift 2 + ;; + --tag) + TAG="${2:?缺少 --tag 参数值}" + shift 2 + ;; + --title) + TITLE="${2:?缺少 --title 参数值}" + shift 2 + ;; + --notes) + NOTES="${2:?缺少 --notes 参数值}" + shift 2 + ;; + --target) + TARGET="${2:?缺少 --target 参数值}" + shift 2 + ;; + --package) + PACKAGES+=("${2:?缺少 --package 参数值}") + shift 2 + ;; + --no-create-tag) + CREATE_TAG="false" + shift + ;; + --no-push-tag) + PUSH_TAG="false" + shift + ;; + --prerelease) + PRERELEASE="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "未知参数:$1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +log() { + printf '\033[1;32m[LightOps]\033[0m %s\n' "$*" +} + +fail() { + printf '\033[1;31m[LightOps]\033[0m %s\n' "$*" >&2 + exit 1 +} + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "缺少命令:$1" +} + +json_escape() { + python -c 'import json,sys; print(json.dumps(sys.stdin.read()))' +} + +api_url() { + printf '%s/api/v1/repos/%s/%s/%s' "${GITEA_URL%/}" "$OWNER" "$REPO" "$1" +} + +api_get() { + curl -fsS -H "Authorization: token ${GITEA_TOKEN}" "$1" +} + +api_post_json() { + curl -fsS -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$2" \ + "$1" +} + +release_id_from_json() { + python -c 'import json,sys; print(json.load(sys.stdin)["id"])' +} + +[[ -n "$TAG" ]] || fail "必须指定 --tag" +[[ -n "${GITEA_TOKEN:-}" ]] || fail "必须设置 GITEA_TOKEN" +[[ ${#PACKAGES[@]} -gt 0 ]] || fail "至少指定一个 --package" +[[ -n "$TITLE" ]] || TITLE="$TAG" + +require_cmd curl +require_cmd git +require_cmd python + +for package in "${PACKAGES[@]}"; do + [[ -f "$package" ]] || fail "发布包不存在:$package" +done + +if [[ "$CREATE_TAG" == "true" ]] && ! git rev-parse "$TAG" >/dev/null 2>&1; then + log "创建本地 tag:$TAG" + git tag -a "$TAG" "$TARGET" -m "$TITLE" +fi + +if [[ "$PUSH_TAG" == "true" ]]; then + log "推送 tag:$TAG" + git push origin "$TAG" +fi + +release_url="$(api_url "releases/tags/${TAG}")" +release_json="" +if release_json="$(api_get "$release_url" 2>/dev/null)"; then + log "Release 已存在:$TAG" +else + log "创建 Release:$TAG" + title_json="$(printf '%s' "$TITLE" | json_escape)" + notes_json="$(printf '%s' "$NOTES" | json_escape)" + payload="{\"tag_name\":\"${TAG}\",\"target_commitish\":\"${TARGET}\",\"name\":${title_json},\"body\":${notes_json},\"draft\":false,\"prerelease\":${PRERELEASE}}" + release_json="$(api_post_json "$(api_url releases)" "$payload")" +fi + +release_id="$(printf '%s' "$release_json" | release_id_from_json)" +for package in "${PACKAGES[@]}"; do + name="$(basename "$package")" + log "上传附件:$name" + curl -fsS -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@${package}" \ + "$(api_url "releases/${release_id}/assets")?name=${name}" >/dev/null +done + +cat <<EOF + +Release 发布完成: +${GITEA_URL%/}/${OWNER}/${REPO}/releases/tag/${TAG} +EOF