Skip to content

fix: 修复 COS CLI 安装问题 #68

fix: 修复 COS CLI 安装问题

fix: 修复 COS CLI 安装问题 #68

name: Build and Publish Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
publish-linux:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Debug environment
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "Tag name: ${{ github.ref_name }}"
echo "Repository: ${{ github.repository }}"
echo "Is tag push: ${{ startsWith(github.ref, 'refs/tags/') }}"
echo "GITHUB_TOKEN exists: ${{ secrets.GITHUB_TOKEN != '' }}"
node --version
npm --version
- name: Publish Linux (AppImage and deb)
run: npm run publish:linux
env:
GH_TOKEN: ${{ secrets.atriordsa }}
DEBUG: electron-builder
publish-windows:
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Debug environment
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "Tag name: ${{ github.ref_name }}"
echo "Repository: ${{ github.repository }}"
echo "Is tag push: ${{ startsWith(github.ref, 'refs/tags/') }}"
echo "GITHUB_TOKEN exists: ${{ secrets.GITHUB_TOKEN != '' }}"
node --version
npm --version
- name: Publish Windows
run: npm run publish:win
env:
GH_TOKEN: ${{ secrets.atriordsa }}
DEBUG: electron-builder
publish-macos:
runs-on: macos-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Debug environment
run: |
echo "GitHub ref: ${{ github.ref }}"
echo "Tag name: ${{ github.ref_name }}"
echo "Repository: ${{ github.repository }}"
echo "Is tag push: ${{ startsWith(github.ref, 'refs/tags/') }}"
echo "GITHUB_TOKEN exists: ${{ secrets.GITHUB_TOKEN != '' }}"
node --version
npm --version
- name: Publish macOS
run: npm run publish:mac
env:
GH_TOKEN: ${{ secrets.atriordsa }}
DEBUG: electron-builder
cleanup-release:
runs-on: ubuntu-latest
needs: [publish-linux, publish-windows, publish-macos]
if: always() && (needs.publish-linux.result == 'success' || needs.publish-windows.result == 'success' || needs.publish-macos.result == 'success')
permissions:
contents: write
actions: read
steps:
- name: Debug GitHub token and permissions
run: |
echo "🔍 Debugging GitHub environment..."
echo "Repository: ${{ github.repository }}"
echo "Actor: ${{ github.actor }}"
echo "Token exists: ${{ secrets.atriordsa != '' }}"
echo "Token length: ${#GITHUB_TOKEN}"
# 尝试不同的 API 调用
echo "🧪 Testing different API endpoints..."
# 测试基本用户信息
echo "Testing /user endpoint:"
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user | jq -r '.login // "Failed"' || echo "curl failed"
# 测试仓库信息
echo "Testing repository endpoint:"
curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ github.repository }} | jq -r '.name // "Failed"' || echo "curl failed"
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
- name: Clean unwanted files from release
run: |
set -e # 启用错误时退出
echo "🧹 Starting release cleanup for tag: ${{ github.ref_name }}"
# 直接使用 curl 而不是 gh CLI,因为 gh CLI 可能有权限问题
echo "🔗 Testing GitHub API with curl..."
# 验证环境变量
if [ -z "$GITHUB_TOKEN" ]; then
echo "❌ GITHUB_TOKEN not set"
exit 1
fi
# 测试 API 连接
USER_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user)
if ! echo "$USER_RESPONSE" | jq -e '.login' > /dev/null 2>&1; then
echo "❌ Cannot authenticate with GitHub API"
echo "Response: $USER_RESPONSE"
exit 1
fi
echo "✅ GitHub API authentication successful"
# 等待确保所有上传完成
echo "⏰ Waiting 30 seconds for uploads to complete..."
sleep 30
echo "📡 Getting release for tag: ${{ github.ref_name }}"
# 首先尝试通过标签获取 release,如果失败则从所有 releases 中查找
RELEASE_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }})
# 如果直接通过标签获取失败,则从所有 releases 中查找
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "⚠️ Direct tag lookup failed, searching in all releases..."
echo "Direct response: $RELEASE_RESPONSE"
ALL_RELEASES=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases)
RELEASE_RESPONSE=$(echo "$ALL_RELEASES" | jq ".[] | select(.tag_name == \"${{ github.ref_name }}\")")
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "❌ Could not find release for tag ${{ github.ref_name }}"
echo "Available releases:"
echo "$ALL_RELEASES" | jq -r '.[] | "- \(.tag_name) (ID: \(.id))"' | head -10
exit 1
fi
fi
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
echo "✅ Found release ID: $RELEASE_ID"
# 获取 assets
echo "📁 Getting release assets..."
ASSETS_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets)
if ! echo "$ASSETS_RESPONSE" | jq -e '. | length' > /dev/null 2>&1; then
echo "❌ Failed to get assets"
echo "Response: $ASSETS_RESPONSE"
exit 1
fi
echo "📁 Current release assets:"
echo "$ASSETS_RESPONSE" | jq -r '.[] | "- \(.name) (ID: \(.id))"'
# 查找并删除不需要的文件
echo "🔍 Looking for unwanted assets..."
# 删除 .yml, .yaml, .blockmap 文件
echo "Finding .yml, .yaml, .blockmap files..."
YML_ASSETS=$(echo "$ASSETS_RESPONSE" | jq -r 'if type == "array" then .[] | select(.name | test("\\.(yml|yaml|blockmap)$")) | .id else empty end' 2>/dev/null || echo "")
# 删除不带架构标识的 Windows 文件
echo "Finding Windows files without architecture identifiers..."
WINDOWS_NO_ARCH_ASSETS=$(echo "$ASSETS_RESPONSE" | jq -r 'if type == "array" then .[] | select(.name | test("windows.*\\.(exe)$") and (.name | test("_(x64|arm64)_") | not)) | .id else empty end' 2>/dev/null || echo "")
# 合并所有要删除的 assets,过滤空行
ALL_UNWANTED_ASSETS=""
if [ -n "$YML_ASSETS" ]; then
ALL_UNWANTED_ASSETS="$YML_ASSETS"
fi
if [ -n "$WINDOWS_NO_ARCH_ASSETS" ]; then
if [ -n "$ALL_UNWANTED_ASSETS" ]; then
ALL_UNWANTED_ASSETS="$ALL_UNWANTED_ASSETS\n$WINDOWS_NO_ARCH_ASSETS"
else
ALL_UNWANTED_ASSETS="$WINDOWS_NO_ARCH_ASSETS"
fi
fi
if [ -n "$ALL_UNWANTED_ASSETS" ]; then
echo "🎯 Found unwanted assets to delete:"
# 显示要删除的文件列表
echo "$ASSETS_RESPONSE" | jq -r 'if type == "array" then .[] | select((.name | test("\\.(yml|yaml|blockmap)$")) or (.name | test("windows.*\\.(exe)$") and (.name | test("_(x64|arm64)_") | not))) | "- \(.name) (ID: \(.id))" else empty end' 2>/dev/null || echo "Error displaying file list"
echo -e "$ALL_UNWANTED_ASSETS" | while read -r asset_id; do
if [ -n "$asset_id" ] && [ "$asset_id" != "null" ] && [ "$asset_id" != "" ]; then
ASSET_NAME=$(echo "$ASSETS_RESPONSE" | jq -r "if type == \"array\" then .[] | select(.id == $asset_id) | .name else \"unknown\" end" 2>/dev/null)
echo "🗑️ Deleting: $ASSET_NAME (ID: $asset_id)"
DELETE_RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null \
-X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id)
if [ "$DELETE_RESPONSE" = "204" ]; then
echo "✅ Successfully deleted: $ASSET_NAME"
else
echo "❌ Failed to delete: $ASSET_NAME (HTTP: $DELETE_RESPONSE)"
fi
fi
done
else
echo "🎉 No unwanted files found!"
fi
echo "🏁 Cleanup completed successfully!"
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
upload-to-cos:
runs-on: ubuntu-latest
needs: [cleanup-release]
if: always() && needs.cleanup-release.result == 'success'
steps:
- name: Get release info
id: release
run: |
set -e
echo "🔍 Getting release info for tag: ${{ github.ref_name }}"
# 验证环境变量
if [ -z "$GITHUB_TOKEN" ]; then
echo "❌ GITHUB_TOKEN not set"
exit 1
fi
# 等待确保 cleanup 完成
echo "⏰ Waiting 15 seconds for cleanup to complete..."
sleep 15
# 首先尝试通过标签获取 release(与 cleanup-release 相同的方法)
RELEASE_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }})
# 如果直接通过标签获取失败,则从所有 releases 中查找
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "⚠️ Direct tag lookup failed, searching in all releases..."
echo "Direct response: $RELEASE_RESPONSE"
ALL_RELEASES=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/${{ github.repository }}/releases)
RELEASE_RESPONSE=$(echo "$ALL_RELEASES" | jq ".[] | select(.tag_name == \"${{ github.ref_name }}\")")
if ! echo "$RELEASE_RESPONSE" | jq -e '.id' > /dev/null 2>&1; then
echo "❌ Could not find release for tag ${{ github.ref_name }}"
echo "Available releases:"
echo "$ALL_RELEASES" | jq -r '.[] | "- \(.tag_name) (ID: \(.id))"' | head -10
exit 1
fi
fi
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
TAG_NAME=$(echo "$RELEASE_RESPONSE" | jq -r '.tag_name')
ASSETS=$(echo "$RELEASE_RESPONSE" | jq -c '.assets')
echo "✅ Found release: $TAG_NAME (ID: $RELEASE_ID)"
echo "📦 Assets count: $(echo "$ASSETS" | jq length)"
# 输出到 GitHub Actions outputs
echo "release_id=$RELEASE_ID" >> $GITHUB_OUTPUT
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "assets<<EOF" >> $GITHUB_OUTPUT
echo "$ASSETS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "✅ Release info prepared for COS upload"
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
- name: Download release assets
id: download
env:
GITHUB_TOKEN: ${{ secrets.atriordsa }}
run: |
echo "📥 Starting download of release assets..."
# 验证环境变量
if [ -z "$GITHUB_TOKEN" ]; then
echo "❌ GITHUB_TOKEN not set"
exit 1
fi
# 创建下载目录
mkdir -p ./downloads
cd ./downloads
# 解析 assets JSON
echo '${{ steps.release.outputs.assets }}' > assets.json
# 检查是否有assets
ASSET_COUNT=$(jq 'length' assets.json)
echo "📦 Found $ASSET_COUNT assets to download"
if [ "$ASSET_COUNT" -eq 0 ]; then
echo "❌ No assets found to download"
exit 1
fi
# 直接从tagged release构建正确的URL
TAG_NAME="${{ steps.release.outputs.tag_name }}"
REPO="${{ github.repository }}"
# 使用 jq 处理每个 asset,但构建正确的URL
jq -r '.[] | .name' assets.json | while read -r name; do
echo "📥 Downloading: $name"
# 构建正确的下载URL(使用tag而不是untagged)
CORRECT_URL="https://github.com/${REPO}/releases/download/${TAG_NAME}/${name}"
echo " URL: $CORRECT_URL"
# 下载文件,使用认证和更详细的错误处理
if curl -L -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/octet-stream" \
--fail --show-error --silent \
--connect-timeout 30 \
--max-time 300 \
-o "$name" "$CORRECT_URL"; then
echo "✅ Downloaded: $name ($(du -h "$name" | cut -f1))"
# 验证文件不为空
if [ ! -s "$name" ]; then
echo "❌ Downloaded file is empty: $name"
exit 1
fi
else
echo "❌ Failed to download: $name"
echo "❌ curl exit code: $?"
# 尝试无认证下载(对于公开文件)
echo "🔄 Trying without authentication..."
if curl -L --fail --show-error --silent \
--connect-timeout 30 \
--max-time 300 \
-o "$name" "$CORRECT_URL"; then
echo "✅ Downloaded without auth: $name ($(du -h "$name" | cut -f1))"
else
echo "❌ Failed even without authentication"
echo "❌ Trying alternative URL construction..."
# 最后尝试:使用GitHub API下载
ASSET_ID=$(echo '${{ steps.release.outputs.assets }}' | jq -r ".[] | select(.name == \"$name\") | .id")
if [ -n "$ASSET_ID" ] && [ "$ASSET_ID" != "null" ]; then
echo "🔄 Trying GitHub API download (Asset ID: $ASSET_ID)..."
if curl -L -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/octet-stream" \
--fail --show-error --silent \
--connect-timeout 30 \
--max-time 300 \
-o "$name" \
"https://api.github.com/repos/${REPO}/releases/assets/${ASSET_ID}"; then
echo "✅ Downloaded via API: $name ($(du -h "$name" | cut -f1))"
else
echo "❌ API download also failed"
exit 1
fi
else
echo "❌ Could not find asset ID"
exit 1
fi
fi
fi
done
echo "📄 Downloaded files:"
ls -la
- name: Setup and Upload to Tencent Cloud COS
run: |
echo "🔧 Setting up Tencent Cloud COS CLI..."
# 安装 COS CLI - 使用更稳定的方式
if ! command -v coscli &> /dev/null; then
echo "Installing COS CLI..."
# 尝试多个版本和下载方式
VERSIONS=("v1.0.6" "v1.0.5" "v1.0.4")
DOWNLOAD_SUCCESS=false
for VERSION in "${VERSIONS[@]}"; do
echo "Trying version $VERSION..."
# 尝试不同的下载方式
if wget -q --timeout=30 "https://github.com/tencentyun/coscli/releases/download/${VERSION}/coscli-linux" -O coscli; then
echo "✅ Downloaded version $VERSION"
DOWNLOAD_SUCCESS=true
break
elif curl -L --fail --silent --connect-timeout 30 --max-time 60 "https://github.com/tencentyun/coscli/releases/download/${VERSION}/coscli-linux" -o coscli; then
echo "✅ Downloaded version $VERSION via curl"
DOWNLOAD_SUCCESS=true
break
else
echo "❌ Failed to download version $VERSION"
rm -f coscli
fi
done
if [ "$DOWNLOAD_SUCCESS" = false ]; then
echo "❌ All COS CLI downloads failed"
exit 1
fi
# 检查下载的文件
if [ ! -f "coscli" ] || [ ! -s "coscli" ]; then
echo "❌ Downloaded file is empty or missing"
exit 1
fi
chmod +x coscli
# 尝试移动到系统路径,如果失败则使用本地路径
if sudo mv coscli /usr/local/bin/ 2>/dev/null; then
echo "✅ COS CLI installed to /usr/local/bin/"
COSCLI_CMD="coscli"
else
echo "⚠️ Could not install globally, using local binary"
mv coscli ./coscli_local
COSCLI_CMD="./coscli_local"
fi
else
echo "✅ COS CLI already installed"
COSCLI_CMD="coscli"
fi
# 验证 COS CLI 工作
echo "Testing COS CLI..."
if ! $COSCLI_CMD version; then
echo "❌ COS CLI not working properly"
exit 1
fi
# 配置 COS CLI
echo "Configuring COS CLI..."
$COSCLI_CMD config set -e cos.${{ secrets.COS_REGION }}.myqcloud.com \
-i ${{ secrets.COS_SECRET_ID }} \
-k ${{ secrets.COS_SECRET_KEY }} \
-a ${{ secrets.COS_BUCKET }}
echo "📁 Uploading files to COS..."
echo "Target path: releases/${{ steps.release.outputs.tag_name }}/"
# 创建远程目录并上传所有文件
cd ./downloads
UPLOAD_SUCCESS=true
for file in *; do
if [ -f "$file" ] && [ "$file" != "assets.json" ]; then
echo "📤 Uploading: $file"
echo " Original filename will be preserved: $file"
# 使用cp命令确保文件名完整保留
if $COSCLI_CMD cp "$file" "cos://${{ secrets.COS_BUCKET }}/releases/${{ steps.release.outputs.tag_name }}/$file"; then
echo "✅ Successfully uploaded: $file"
# 验证上传
if $COSCLI_CMD ls "cos://${{ secrets.COS_BUCKET }}/releases/${{ steps.release.outputs.tag_name }}/$file" &>/dev/null; then
echo "✅ Verified upload: $file"
else
echo "⚠️ Warning: Could not verify upload of $file"
fi
else
echo "❌ Failed to upload: $file"
UPLOAD_SUCCESS=false
fi
fi
done
if [ "$UPLOAD_SUCCESS" = true ]; then
echo "✅ All files uploaded to COS successfully with original filenames"
else
echo "❌ Some uploads failed"
exit 1
fi
- name: Create release index
uses: actions/github-script@v7
with:
github-token: ${{ secrets.atriordsa }}
script: |
const fs = require('fs');
const path = require('path');
const tagName = '${{ steps.release.outputs.tag_name }}';
const cdnUrl = '${{ secrets.CDN_URL }}';
const versionPath = `releases/${tagName}`;
console.log('📋 Creating release index...');
// 读取下载的文件信息
const downloadDir = './downloads';
const files = [];
if (fs.existsSync(downloadDir)) {
const fileList = fs.readdirSync(downloadDir);
for (const fileName of fileList) {
if (fileName !== 'assets.json') {
const filePath = path.join(downloadDir, fileName);
const stats = fs.statSync(filePath);
const safeKey = `${versionPath}/${fileName}`.replace(/ /g, '_');
const cdnFileUrl = `${cdnUrl.replace(/\/$/, '')}/${safeKey}`;
files.push({
name: fileName,
cos_key: safeKey,
cdn_url: cdnFileUrl,
size: stats.size
});
console.log(`📄 ${fileName} -> ${cdnFileUrl}`);
}
}
}
// 创建索引数据
const indexData = {
version: tagName,
upload_time: Math.floor(Date.now() / 1000),
files: files,
total_files: files.length,
repository: context.repo.owner + '/' + context.repo.repo
};
// 保存索引文件
const indexFile = path.join(downloadDir, 'index.json');
fs.writeFileSync(indexFile, JSON.stringify(indexData, null, 2));
console.log(`✅ Created index with ${files.length} files`);
console.log(`📁 Version directory: ${cdnUrl.replace(/\/$/, '')}/${versionPath}/`);
- name: Upload index to COS using CLI
run: |
echo "📤 Uploading index.json to COS..."
# 确定COS CLI命令路径
if command -v coscli &> /dev/null; then
COSCLI_CMD="coscli"
elif [ -f "./coscli" ]; then
COSCLI_CMD="./coscli"
else
echo "❌ COS CLI not found"
exit 1
fi
cd ./downloads
if [ -f "index.json" ]; then
echo "📋 Uploading index file..."
if $COSCLI_CMD cp "index.json" "cos://${{ secrets.COS_BUCKET }}/releases/${{ steps.release.outputs.tag_name }}/index.json"; then
echo "✅ Successfully uploaded index.json"
# 验证上传
if $COSCLI_CMD ls "cos://${{ secrets.COS_BUCKET }}/releases/${{ steps.release.outputs.tag_name }}/index.json" &>/dev/null; then
echo "✅ Verified index.json upload"
else
echo "⚠️ Warning: Could not verify index.json upload"
fi
else
echo "❌ Failed to upload index.json"
exit 1
fi
else
echo "❌ index.json not found"
ls -la
exit 1
fi
- name: Display upload summary
run: |
echo "🎉 COS upload completed successfully!"
echo "📁 Version: ${{ steps.release.outputs.tag_name }}"
echo "🌐 CDN Base URL: ${{ secrets.CDN_URL }}"
echo "📂 Release Directory: ${{ secrets.CDN_URL }}/releases/${{ steps.release.outputs.tag_name }}/"
echo "📋 Index File: ${{ secrets.CDN_URL }}/releases/${{ steps.release.outputs.tag_name }}/index.json"