#!/bin/bash # 发布上传脚本 # 支持多种上传方式:GitHub Releases、Gitea Releases、SCP、FTP、本地复制 set -e # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # 项目信息 PROJECT_NAME="agent" BUILD_DIR="bin" VERSION="${VERSION:-$(date +%Y%m%d-%H%M%S)}" RELEASE_DIR="release" TEMP_DIR=$(mktemp -d) # 支持的平台列表 PLATFORMS=( "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64" "windows/arm64" ) # 清理函数 cleanup() { if [ -d "$TEMP_DIR" ]; then rm -rf "$TEMP_DIR" fi } trap cleanup EXIT # 显示使用说明 usage() { echo -e "${BLUE}使用方法:${NC}" echo " $0 [选项]" echo "" echo -e "${BLUE}选项:${NC}" echo " -h, --help 显示帮助信息" echo " -m, --method METHOD 上传方式: github|gitea|scp|ftp|local (默认: gitea)" echo " -v, --version VERSION 版本号 (默认: 时间戳)" echo " -p, --platform PLATFORM 只上传指定平台 (例如: linux/amd64)" echo " -t, --tag TAG Git标签 (GitHub/Gitea Releases需要)" echo " -r, --repo REPO 仓库 (格式: owner/repo,默认从.git/config读取)" echo " -b, --base-url URL Gitea基础URL (默认从.git/config读取)" echo " -T, --token TOKEN 访问令牌 (Gitea需要,也可通过GITEA_TOKEN环境变量)" echo " -d, --dest DEST 目标路径 (SCP/FTP/local需要)" echo " -H, --host HOST 主机地址 (SCP/FTP需要)" echo " -u, --user USER 用户名 (SCP/FTP需要)" echo " -P, --port PORT 端口号 (SCP/FTP需要,默认: SCP=22, FTP=21)" echo " -k, --key KEY 私钥路径 (SCP需要)" echo " --pack-only 只打包不上传" echo " --no-pack 不上传压缩包,直接上传二进制文件" echo " --notes NOTES 发布说明 (GitHub/Gitea Releases)" echo " --notes-file FILE 从文件读取发布说明" echo "" echo -e "${BLUE}上传方式说明:${NC}" echo "" echo -e "${CYAN}Gitea Releases (自动从.git/config读取):${NC}" echo " $0 -m gitea -t v1.0.0 -v 1.0.0" echo " $0 -m gitea -t v1.0.0 -v 1.0.0 -T your_token" echo "" echo -e "${CYAN}GitHub Releases:${NC}" echo " $0 -m github -r owner/repo -t v1.0.0 -v 1.0.0" echo "" echo -e "${CYAN}SCP上传:${NC}" echo " $0 -m scp -H example.com -u user -d /path/to/release" echo " $0 -m scp -H example.com -u user -d /path/to/release -k ~/.ssh/id_rsa" echo "" echo -e "${CYAN}FTP上传:${NC}" echo " $0 -m ftp -H ftp.example.com -u user -d /path/to/release" echo "" echo -e "${CYAN}本地复制:${NC}" echo " $0 -m local -d /path/to/release" echo "" echo -e "${CYAN}只打包:${NC}" echo " $0 --pack-only -v 1.0.0" } # 从 .git/config 读取 Git 信息 read_git_info() { local git_config=".git/config" if [ ! -f "$git_config" ]; then return 1 fi # 读取 origin URL local url=$(git config --get remote.origin.url 2>/dev/null || echo "") if [ -z "$url" ]; then return 1 fi # 解析 URL # 支持格式: # - https://user:pass@host/owner/repo.git # - https://host/owner/repo.git # - git@host:owner/repo.git # - ssh://user@host/owner/repo.git local base_url="" local owner="" local repo_name="" local token="" # 提取 token (如果有) if [[ "$url" =~ https://([^:]+):([^@]+)@(.+) ]]; then token="${BASH_REMATCH[2]}" url="https://${BASH_REMATCH[1]}@${BASH_REMATCH[3]}" fi # 提取 base_url, owner, repo if [[ "$url" =~ https://([^/]+)/([^/]+)/([^/]+)\.git ]]; then base_url="https://${BASH_REMATCH[1]}" owner="${BASH_REMATCH[2]}" repo_name="${BASH_REMATCH[3]}" elif [[ "$url" =~ git@([^:]+):([^/]+)/([^/]+)\.git ]]; then base_url="https://${BASH_REMATCH[1]}" owner="${BASH_REMATCH[2]}" repo_name="${BASH_REMATCH[3]}" elif [[ "$url" =~ ssh://[^@]+@([^/]+)/([^/]+)/([^/]+)\.git ]]; then base_url="https://${BASH_REMATCH[1]}" owner="${BASH_REMATCH[2]}" repo_name="${BASH_REMATCH[3]}" fi if [ -n "$base_url" ] && [ -n "$owner" ] && [ -n "$repo_name" ]; then echo "$base_url|$owner|$repo_name|$token" return 0 fi return 1 } # 检查必要的工具 check_dependencies() { local method=$1 local missing_tools=() case $method in github) if ! command -v gh &> /dev/null; then missing_tools+=("gh (GitHub CLI)") fi ;; gitea) if ! command -v curl &> /dev/null; then missing_tools+=("curl") fi if ! command -v jq &> /dev/null; then echo -e "${YELLOW}警告: jq 未安装,某些功能可能受限${NC}" fi ;; scp) if ! command -v scp &> /dev/null; then missing_tools+=("scp") fi ;; ftp) if ! command -v curl &> /dev/null && ! command -v ftp &> /dev/null; then missing_tools+=("curl 或 ftp") fi ;; esac if [ ${#missing_tools[@]} -gt 0 ]; then echo -e "${RED}错误: 缺少必要的工具:${NC}" for tool in "${missing_tools[@]}"; do echo " - $tool" done exit 1 fi } # 检查构建文件是否存在 check_build_files() { if [ ! -d "$BUILD_DIR" ] || [ -z "$(ls -A $BUILD_DIR 2>/dev/null)" ]; then echo -e "${RED}错误: 构建目录为空或不存在${NC}" echo "请先运行 ./build-all.sh 编译项目" exit 1 fi local found=0 for platform in "${PLATFORMS[@]}"; do local os=$(echo $platform | cut -d'/' -f1) local arch=$(echo $platform | cut -d'/' -f2) local file="${BUILD_DIR}/${PROJECT_NAME}-${os}-${arch}" if [ "$os" = "windows" ]; then file="${file}.exe" fi if [ -f "$file" ]; then found=1 break fi done if [ $found -eq 0 ]; then echo -e "${RED}错误: 未找到任何构建文件${NC}" echo "请先运行 ./build-all.sh 编译项目" exit 1 fi } # 打包文件 pack_files() { local platform=$1 local os=$(echo $platform | cut -d'/' -f1) local arch=$(echo $platform | cut -d'/' -f2) local binary="${BUILD_DIR}/${PROJECT_NAME}-${os}-${arch}" if [ "$os" = "windows" ]; then binary="${binary}.exe" fi if [ ! -f "$binary" ]; then echo -e "${YELLOW}[跳过]${NC} ${platform} - 文件不存在" return 1 fi local pack_name="${PROJECT_NAME}-${os}-${arch}-${VERSION}" local pack_file if [ "$os" = "windows" ]; then pack_file="${TEMP_DIR}/${pack_name}.zip" echo -e "${BLUE}[打包]${NC} ${platform} -> ${pack_name}.zip" (cd "$BUILD_DIR" && zip -q "${pack_file}" "$(basename $binary)") else pack_file="${TEMP_DIR}/${pack_name}.tar.gz" echo -e "${BLUE}[打包]${NC} ${platform} -> ${pack_name}.tar.gz" tar -czf "$pack_file" -C "$BUILD_DIR" "$(basename $binary)" fi echo "$pack_file" return 0 } # 创建发布说明 create_release_notes() { local notes_file="${TEMP_DIR}/release_notes.md" cat > "$notes_file" </dev/null || echo "N/A") - **Git分支**: $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "N/A") ## 支持的平台 EOF for platform in "${PLATFORMS[@]}"; do local os=$(echo $platform | cut -d'/' -f1) local arch=$(echo $platform | cut -d'/' -f2) local binary="${BUILD_DIR}/${PROJECT_NAME}-${os}-${arch}" if [ "$os" = "windows" ]; then binary="${binary}.exe" fi if [ -f "$binary" ]; then local size=$(ls -lh "$binary" | awk '{print $5}') echo "- ${os}/${arch} (${size})" >> "$notes_file" fi done echo "" >> "$notes_file" echo "## 安装说明" >> "$notes_file" echo "" >> "$notes_file" echo "### Linux/macOS" >> "$notes_file" echo "\`\`\`bash" >> "$notes_file" echo "tar -xzf ${PROJECT_NAME}-linux-amd64-${VERSION}.tar.gz" >> "$notes_file" echo "chmod +x ${PROJECT_NAME}-linux-amd64" >> "$notes_file" echo "./${PROJECT_NAME}-linux-amd64" >> "$notes_file" echo "\`\`\`" >> "$notes_file" echo "" >> "$notes_file" echo "### Windows" >> "$notes_file" echo "\`\`\`powershell" >> "$notes_file" echo "Expand-Archive ${PROJECT_NAME}-windows-amd64-${VERSION}.zip" >> "$notes_file" echo ".\\${PROJECT_NAME}-windows-amd64.exe" >> "$notes_file" echo "\`\`\`" >> "$notes_file" echo "$notes_file" } # GitHub Releases 上传 upload_github() { local repo=$1 local tag=$2 local notes_file=$3 if [ -z "$repo" ]; then echo -e "${RED}错误: GitHub仓库未指定,使用 -r owner/repo${NC}" exit 1 fi if [ -z "$tag" ]; then echo -e "${RED}错误: Git标签未指定,使用 -t TAG${NC}" exit 1 fi echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}上传到 GitHub Releases${NC}" echo -e "${GREEN}========================================${NC}" echo -e "仓库: ${BLUE}${repo}${NC}" echo -e "标签: ${BLUE}${tag}${NC}" echo -e "版本: ${BLUE}${VERSION}${NC}" echo "" # 检查是否已存在release if gh release view "$tag" --repo "$repo" &>/dev/null; then echo -e "${YELLOW}警告: Release ${tag} 已存在${NC}" read -p "是否删除并重新创建? (y/N): " confirm if [[ "$confirm" =~ ^[Yy]$ ]]; then gh release delete "$tag" --repo "$repo" --yes else echo "取消上传" exit 0 fi fi # 创建release echo -e "${BLUE}[创建Release]${NC} ${tag}" if [ -f "$notes_file" ]; then gh release create "$tag" \ --repo "$repo" \ --title "${PROJECT_NAME} ${VERSION}" \ --notes-file "$notes_file" \ --draft=false \ --prerelease=false else gh release create "$tag" \ --repo "$repo" \ --title "${PROJECT_NAME} ${VERSION}" \ --notes "Release ${VERSION}" \ --draft=false \ --prerelease=false fi # 上传文件 local upload_count=0 shopt -s nullglob for file in "${TEMP_DIR}"/*.tar.gz "${TEMP_DIR}"/*.zip; do if [ -f "$file" ]; then echo -e "${BLUE}[上传]${NC} $(basename $file)" gh release upload "$tag" "$file" --repo "$repo" --clobber ((upload_count++)) fi done shopt -u nullglob if [ $upload_count -eq 0 ] && [ "$NO_PACK" != "true" ]; then echo -e "${YELLOW}警告: 没有找到要上传的文件${NC}" else echo -e "${GREEN}[完成]${NC} 已上传 ${upload_count} 个文件" echo -e "${CYAN}Release链接:${NC} https://github.com/${repo}/releases/tag/${tag}" fi } # Gitea Releases 上传 upload_gitea() { local base_url=$1 local owner=$2 local repo=$3 local tag=$4 local token=$5 local notes_file=$6 if [ -z "$base_url" ] || [ -z "$owner" ] || [ -z "$repo" ]; then echo -e "${RED}错误: Gitea仓库信息不完整${NC}" exit 1 fi if [ -z "$tag" ]; then echo -e "${RED}错误: Git标签未指定,使用 -t TAG${NC}" exit 1 fi if [ -z "$token" ]; then token="${GITEA_TOKEN}" fi if [ -z "$token" ]; then echo -e "${RED}错误: 访问令牌未指定,使用 -T TOKEN 或设置 GITEA_TOKEN 环境变量${NC}" exit 1 fi echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}上传到 Gitea Releases${NC}" echo -e "${GREEN}========================================${NC}" echo -e "基础URL: ${BLUE}${base_url}${NC}" echo -e "仓库: ${BLUE}${owner}/${repo}${NC}" echo -e "标签: ${BLUE}${tag}${NC}" echo -e "版本: ${BLUE}${VERSION}${NC}" echo "" local api_base="${base_url}/api/v1" local repo_api="${api_base}/repos/${owner}/${repo}" # 读取发布说明 local notes="Release ${VERSION}" if [ -f "$notes_file" ]; then notes=$(cat "$notes_file") fi # 转义 JSON 字符串(不使用 jq) local notes_escaped=$(echo "$notes" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g') # 检查是否已存在release local existing_release=$(curl -s -X GET \ "${repo_api}/releases/tags/${tag}" \ -H "Authorization: token ${token}" \ -H "Content-Type: application/json" 2>/dev/null) if echo "$existing_release" | grep -q '"id"'; then echo -e "${YELLOW}警告: Release ${tag} 已存在${NC}" read -p "是否删除并重新创建? (y/N): " confirm if [[ "$confirm" =~ ^[Yy]$ ]]; then local release_id=$(echo "$existing_release" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2) if [ -n "$release_id" ]; then curl -s -X DELETE \ "${repo_api}/releases/${release_id}" \ -H "Authorization: token ${token}" > /dev/null echo -e "${BLUE}[删除]${NC} 已删除现有 Release" fi else echo "取消上传" exit 0 fi fi # 创建release echo -e "${BLUE}[创建Release]${NC} ${tag}" local release_data=$(cat < /dev/null done echo "" echo -e "${GREEN}打包完成${NC}" echo -e "${BLUE}打包文件:${NC}" shopt -s nullglob ls -lh "${TEMP_DIR}"/*.tar.gz "${TEMP_DIR}"/*.zip 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' shopt -u nullglob echo "" fi # 如果只是打包,则复制到release目录 if [ "$pack_only" = "true" ]; then mkdir -p "$RELEASE_DIR" shopt -s nullglob cp "${TEMP_DIR}"/*.tar.gz "${TEMP_DIR}"/*.zip "$RELEASE_DIR/" 2>/dev/null || true shopt -u nullglob echo -e "${GREEN}文件已复制到 ${RELEASE_DIR} 目录${NC}" exit 0 fi # 创建发布说明 local release_notes="" if [ -n "$notes_file" ] && [ -f "$notes_file" ]; then release_notes="$notes_file" elif [ -n "$notes" ]; then echo "$notes" > "${TEMP_DIR}/release_notes.md" release_notes="${TEMP_DIR}/release_notes.md" elif [ "$method" = "github" ] || [ "$method" = "gitea" ]; then release_notes=$(create_release_notes) fi # 根据方法上传 case $method in gitea) # 从 .git/config 读取信息(如果未指定) local git_info="" if [ -z "$base_url" ] || [ -z "$repo" ]; then git_info=$(read_git_info) if [ $? -eq 0 ] && [ -n "$git_info" ]; then IFS='|' read -r git_base_url git_owner git_repo_name git_token <<< "$git_info" if [ -z "$base_url" ]; then base_url="$git_base_url" fi if [ -z "$repo" ]; then repo="${git_owner}/${git_repo_name}" fi if [ -z "$token" ] && [ -n "$git_token" ]; then token="$git_token" fi echo -e "${CYAN}[信息]${NC} 从 .git/config 读取仓库信息: ${repo}" fi fi if [ -z "$base_url" ] || [ -z "$repo" ]; then echo -e "${RED}错误: Gitea仓库信息不完整${NC}" echo "请指定 -b BASE_URL 和 -r owner/repo,或确保 .git/config 中有正确的远程仓库配置" exit 1 fi IFS='/' read -r owner repo_name <<< "$repo" if [ -z "$owner" ] || [ -z "$repo_name" ]; then echo -e "${RED}错误: 仓库格式不正确,应为 owner/repo${NC}" exit 1 fi upload_gitea "$base_url" "$owner" "$repo_name" "$tag" "$token" "$release_notes" ;; github) upload_github "$repo" "$tag" "$release_notes" ;; scp) upload_scp "$host" "$user" "$dest" "$port" "$key" ;; ftp) upload_ftp "$host" "$user" "$dest" "$port" ;; local) upload_local "$dest" ;; *) echo -e "${RED}错误: 不支持的上传方式: ${method}${NC}" echo "支持的方式: gitea, github, scp, ftp, local" exit 1 ;; esac echo "" echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}发布完成${NC}" echo -e "${GREEN}========================================${NC}" } # 运行主函数 main "$@"