#!/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" } detect_json_runtime() { if command -v node >/dev/null 2>&1; then echo "node" elif command -v python3 >/dev/null 2>&1; then echo "python3" elif command -v python >/dev/null 2>&1; then echo "python" else return 1 fi } json_escape() { case "$JSON_RUNTIME" in node) node -e 'let s=""; process.stdin.setEncoding("utf8"); process.stdin.on("data", d => s += d); process.stdin.on("end", () => process.stdout.write(JSON.stringify(s)));' ;; *) "$JSON_RUNTIME" -c 'import json,sys; print(json.dumps(sys.stdin.read()))' ;; esac } 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" } api_post_json_with_status() { local output_file status output_file="$(mktemp)" status="$(curl -sS -o "$output_file" -w '%{http_code}' -X POST \ -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "$2" \ "$1")" || { cat "$output_file" >&2 || true rm -f "$output_file" return 1 } cat "$output_file" rm -f "$output_file" printf '\n%s' "$status" } release_id_from_json() { case "$JSON_RUNTIME" in node) node -e 'let s=""; process.stdin.setEncoding("utf8"); process.stdin.on("data", d => s += d); process.stdin.on("end", () => process.stdout.write(String(JSON.parse(s).id)));' ;; *) "$JSON_RUNTIME" -c 'import json,sys; print(json.load(sys.stdin)["id"])' ;; esac } [[ -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 JSON_RUNTIME="$(detect_json_runtime)" || fail "缺少命令:node、python 或 python3" 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="" release_status="$(curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: token ${GITEA_TOKEN}" "$release_url" || true)" if [[ "$release_status" == "200" ]] && 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}}" create_result="$(api_post_json_with_status "$(api_url releases)" "$payload")" || fail "创建 Release 请求失败" create_status="${create_result##*$'\n'}" release_json="${create_result%$'\n'*}" [[ "$create_status" == "201" || "$create_status" == "200" ]] || fail "创建 Release 失败,HTTP 状态码:$create_status" 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