ci: 移除七牛云依赖并优化构建流程
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed

This commit is contained in:
xinian
2026-04-30 00:02:18 +08:00
parent 4d9fe02ae0
commit 09cc1bd736
6 changed files with 125 additions and 204 deletions

View File

@@ -16,14 +16,12 @@ permissions:
actions: read
env:
# 完全还原你最初的环境变量
CGO_ENABLED: 0
GO111MODULE: on
GOSUMDB: off
QINIU_REMOTE_DIR: releases/
jobs:
build-and-upload-qiniu:
build:
runs-on: ubuntu-latest
steps:
# 修复checkout还原你最初的极简配置+补全鉴权无任何多余配置
@@ -85,37 +83,22 @@ jobs:
echo "产物名称:${BIN_NAME}" >> $GITHUB_STEP_SUMMARY
shell: bash
# 核心修复七牛云上传改回你最初的相对路径build删除所有$GITHUB_WORKSPACE
- name: 上传产物到七牛云
uses: cumt-robin/upload-to-qiniu-action@v1
with:
access_key: ${{ secrets.QINIU_AK }}
secret_key: ${{ secrets.QINIU_SK }}
bucket: ${{ secrets.QINIU_BUCKET_NAME }}
region: z2
local_dir: build # 还原你最初的写法绝对正确
remote_dir: ${{ env.QINIU_REMOTE_DIR }}
overwrite: true
# 打印信息完全还原你最初的逻辑无cd无绝对路径
- name: 打印构建完成信息
run: |
BIN_NAME="logic_${{ steps.set-version.outputs.build_version }}"
CDN_URL="https://${{ secrets.QINIU_CDN_DOMAIN }}/${{ env.QINIU_REMOTE_DIR }}${BIN_NAME}"
echo "======================================"
echo "✅ 构建&七牛云上传完成!"
echo "✅ 构建完成!"
echo "版本号:${{ steps.set-version.outputs.build_version }}"
echo "触发方式:${{ github.event_name == 'workflow_dispatch' && '手动触发' || '代码推送' }}"
echo "服务端口:${{ github.event.inputs.servicePort || 8080 }}"
echo "七牛云CDN地址${CDN_URL}"
echo "启动命令:./${BIN_NAME} -port=${{ github.event.inputs.servicePort || 8080 }}"
echo "对应Commit${{ github.sha }}"
echo "======================================"
echo "## ✅ 构建&分发完成" >> $GITHUB_STEP_SUMMARY
echo "## ✅ 构建完成" >> $GITHUB_STEP_SUMMARY
echo "- 版本号:${{ steps.set-version.outputs.build_version }}" >> $GITHUB_STEP_SUMMARY
echo "- 触发方式:${{ github.event_name == 'workflow_dispatch' && '手动触发' || '代码推送' }}" >> $GITHUB_STEP_SUMMARY
echo "- 服务端口:${{ github.event.inputs.servicePort || 8080 }}" >> $GITHUB_STEP_SUMMARY
echo "- 七牛云CDN地址[${CDN_URL}](${CDN_URL})" >> $GITHUB_STEP_SUMMARY
echo "- 启动命令:`./${BIN_NAME} -port=${{ github.event.inputs.servicePort || 8080 }}`" >> $GITHUB_STEP_SUMMARY
echo "- 对应Commit[${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }})" >> $GITHUB_STEP_SUMMARY
shell: bash

View File

@@ -59,9 +59,8 @@ steps:
- git config --global core.compression 0
- export GIT_CONFIG_URL="https://cnb:$CNB_ACCK@cnb.cool/blzing/blazing"
- echo "🔍 $CNB_ACCK调试 $CNB_ACCK"
- git config --global http.sslVerify false
- git clone --depth 1 --progress -v $GIT_CONFIG_URL
- git clone --depth 1 --single-branch --branch main --quiet $GIT_CONFIG_URL
# 拉取代码
- echo "✅ 代码拉取完成"
@@ -76,32 +75,30 @@ steps:
CGO_ENABLED: 0
GO111MODULE: on
GOSUMDB: off
GOPROXY: https://mirrors.aliyun.com/goproxy/,https://goproxy.cn
GOMODCACHE: /woodpecker/go/pkg/mod
GOCACHE: /woodpecker/.cache/go-build
UPX_ARGS: --best --lzma
commands:
# 2. 清空主源文件关键先删空再写入
- >
echo "" > /etc/apt/sources.list
# 3. 写入阿里云trixie源匹配golang:1.25的系统版本避免版本混跑
- >
echo "deb http://mirrors.aliyun.com/debian/ trixie main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian/ trixie-updates main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian-security/ trixie-security main contrib non-free non-free-firmware" > /etc/apt/sources.list
# 4. 删除sources.list.d下的所有额外源彻底杜绝官方源
- rm -rf /etc/apt/sources.list.d/*
# 5. 强制更新加超时和缓存清理解决卡住问题
- apt-get clean && apt-get update -y -o Acquire::Timeout=30
# 2. 安装正确的 upx Debian 中包名是 upx-ucl不是 upx
- apt-get install -y upx-ucl
- |
. /etc/os-release
DEBIAN_CODENAME="${VERSION_CODENAME:-trixie}"
cat > /etc/apt/sources.list <<EOF
deb http://mirrors.aliyun.com/debian/ ${DEBIAN_CODENAME} main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian/ ${DEBIAN_CODENAME}-updates main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian-security/ ${DEBIAN_CODENAME}-security main contrib non-free non-free-firmware
EOF
rm -rf /etc/apt/sources.list.d/*
- apt-get update -y -o Acquire::Timeout=30
- apt-get install -y --no-install-recommends upx-ucl
- cd blazing
- mkdir -p build
- BUILD_PKG_PARALLELISM="$(nproc 2>/dev/null || echo 4)"
- echo "Go package build parallelism=$BUILD_PKG_PARALLELISM"
- BIN_NAME="login_${CI_PIPELINE_CREATED}"
- export GO111MODULE=on
- export GOPROXY=https://goproxy.cn
- |
go build -v \
-p=24 \
-p="$BUILD_PKG_PARALLELISM" \
-trimpath \
-buildvcs=false \
-ldflags "-s -w -buildid= -extldflags '-static'" \
@@ -120,15 +117,13 @@ steps:
- BIN_NAME="logic_${CI_PIPELINE_CREATED}"
- |
go build -v \
-p=24 \
-p="$BUILD_PKG_PARALLELISM" \
-trimpath \
-buildvcs=false \
-ldflags "-s -w -buildid= -extldflags '-static'" \
-o ./build/$BIN_NAME \
./logic
- |
strip ./build/$BIN_NAME
upx --best --lzma ./build/$BIN_NAME
- upx $UPX_ARGS ./build/$BIN_NAME
- |
if [ ! -f ./build/$BIN_NAME ]; then
echo "❌ 编译失败:产物$BIN_NAME不存在"

View File

@@ -115,14 +115,6 @@ func hasDebugArg() bool {
return false
}
// qiniu 七牛云配置
type qiniu struct {
AccessKey string `json:"ak"`
SecretKey string `json:"sk"`
Bucket string `json:"bucket"`
CDN string `json:"cdn"`
}
// Config config
var Config = newConfig()

View File

@@ -4,7 +4,6 @@ go 1.23
require (
github.com/panjf2000/gnet v1.6.7
github.com/qiniu/go-sdk/v7 v7.18.1
)
require github.com/gogf/gf/v2 v2.9.7

View File

@@ -13,6 +13,7 @@ import (
"fmt"
"io"
"net"
"strconv"
"strings"
"sync/atomic"
"time"
@@ -27,7 +28,7 @@ const (
PingInterval = 10 * time.Second
defaultWorkDir = "$HOME" // 全环境兼容
randomStrLength = 4 // 缩短随机串长度
cmdTimeout = 180 * time.Second // 延长超时适配Screen安装+下载)
cmdTimeout = 600 * time.Second // 延长超时适配Screen安装+下载)
)
// SSHConfig SSH连接配置
@@ -85,69 +86,44 @@ func (s *ServerHandler) executeScript(scriptContent, scriptName string) (string,
// 生成临时脚本路径
scriptPath := fmt.Sprintf("/tmp/%s.sh", scriptName)
// 直接将脚本内容写入文件
writeScriptCmd := fmt.Sprintf("cat > '%s' << 'DEPLOYMENT_SCRIPT_END'\n%s\nDEPLOYMENT_SCRIPT_END\n", scriptPath, scriptContent)
writeScriptCmd := fmt.Sprintf("stty -echo\ncat > '%s' << 'DEPLOYMENT_SCRIPT_END'\n%s\nDEPLOYMENT_SCRIPT_END\nchmod +x '%s'\nbash '%s' 2>&1\nSCRIPT_STATUS=$?\nrm -f '%s'\nstty echo\necho '#SCRIPT_EXIT_STATUS:'$SCRIPT_STATUS'#'\necho '#SCRIPT_EXECUTION_COMPLETE#'\n", scriptPath, scriptContent, scriptPath, scriptPath, scriptPath)
_, err := s.session.Stdin.Write([]byte(writeScriptCmd))
if err != nil {
return "", err
}
// 等待一会儿确保脚本写入完成
time.Sleep(time.Second)
// 设置执行权限
_, err = s.session.Stdin.Write([]byte(fmt.Sprintf("chmod +x %s\n", scriptPath)))
if err != nil {
return "", err
}
// 等待权限设置完成
time.Sleep(time.Second)
// 执行脚本并将输出发送到WebSocket在命令中直接删除脚本文件
executeCmd := fmt.Sprintf("bash %s 2>&1; rm -f %s\n", scriptPath, scriptPath)
_, err = s.session.Stdin.Write([]byte(executeCmd))
if err != nil {
return "", err
}
// 读取脚本执行输出
output := ""
done := make(chan bool)
exitStatus := 0
hasExitStatus := false
timeout := time.After(cmdTimeout)
go func() {
defer func() {
if r := recover(); r != nil {
glog.Error(context.Background(), "Script execution goroutine panic:", r)
}
}()
scanner := bufio.NewScanner(s.session.Stdout)
for scanner.Scan() {
line := scanner.Text()
// 检测到脚本执行完成标记则退出
for {
select {
case line := <-s.session.outputBuf:
if strings.Contains(line, "#SCRIPT_EXECUTION_COMPLETE#") {
break
if hasExitStatus && exitStatus != 0 {
return strings.TrimSpace(output), fmt.Errorf("script exited with status %d", exitStatus)
}
return strings.TrimSpace(output), nil
}
// 忽略一些可能导致连接断开的输出
if strings.Contains(line, "logout") || strings.Contains(line, "exit") {
if strings.Contains(line, "#SCRIPT_EXIT_STATUS:") {
statusText := strings.TrimPrefix(line, "#SCRIPT_EXIT_STATUS:")
statusText = strings.TrimSuffix(statusText, "#")
if status, convErr := strconv.Atoi(strings.TrimSpace(statusText)); convErr == nil {
exitStatus = status
hasExitStatus = true
}
continue
}
if strings.Contains(line, "logout") || strings.Contains(line, "exit") {
continue
}
output += line + "\n"
s.sendTerminalOutput(s.session.WebSocket, line)
case <-timeout:
return strings.TrimSpace(output), fmt.Errorf("script execution timeout")
}
done <- true
}()
// 等待脚本执行完成或超时
select {
case <-done:
return strings.TrimSpace(output), nil
case <-time.After(cmdTimeout):
return strings.TrimSpace(output), nil
}
}
@@ -180,8 +156,8 @@ func (s *ServerHandler) executeFullDeployment() error {
// 3. 定义部署脚本(给每个%s加唯一标记方便核对
deploymentScriptTpl := `
set -e
set -x
#!/bin/bash
set -euo pipefail
# ===== 检查并安装screen =====
echo "检查Screen是否已安装..."
@@ -204,13 +180,6 @@ else
command -v screen || { echo "❌ Screen安装失败"; exit 1; }
fi
#!/bin/bash
# 核心无任何可能阻塞的命令全程实时输出100%不卡住 + 彻底清理所有logic会话
set -euo pipefail
# ===== 准备下载目录 =====
echo "创建工作目录:%s{work_dir}"
mkdir -p "%s{work_dir}" || { echo "❌ 创建目录失败"; exit 1; }
@@ -220,71 +189,104 @@ echo "===== 开始下载程序 ====="
echo "下载链接:%s{file_url}"
echo "目标路径:%s{exe_path}"
# 定义要删除文件的目录(根据你的实际路径修改,比如 /usr/local/game/ 或当前目录 .
target_dir="."
target_dir="%s{work_dir}"
tmp_path="%s{exe_path}.downloading"
# 删除所有 logic_ 开头的文件
echo "开始删除 ${target_dir} 目录下 logic_ 开头的文件..."
for file in "${target_dir}"/logic_*; do
# 检查文件是否存在(避免匹配不到时删除空值)
if [ -f "$file" ]; then
echo "删除旧文件:$file"
rm -f "$file"
fi
echo "清理部署目录 ${target_dir} 下的 logic_ 和 login_ 旧文件..."
for pattern in logic_ login_; do
for file in "${target_dir}/${pattern}"*; do
if [ -f "$file" ]; then
echo "删除旧文件:$file"
rm -f "$file"
fi
done
done
echo "logic_ 开头的文件删除完成"
rm -f "${tmp_path}"
# ===== 准备下载目录 =====
echo "创建工作目录:%s{work_dir}"
mkdir -p "%s{work_dir}" || { echo "❌ 创建目录失败"; exit 1; }
file_size() {
stat -c%s "$1" 2>/dev/null || stat -f%z "$1" 2>/dev/null || echo 0
}
# ===== 下载程序 =====
echo "===== 开始下载程序 ====="
echo "下载链接:%s{file_url}"
echo "目标路径:%s{exe_path}"
human_bytes() {
bytes="$1"
if [ "$bytes" -ge 1048576 ]; then
awk -v b="$bytes" 'BEGIN { printf "%.2fMB", b / 1048576 }'
elif [ "$bytes" -ge 1024 ]; then
awk -v b="$bytes" 'BEGIN { printf "%.2fKB", b / 1024 }'
else
printf "%sB" "$bytes"
fi
}
# 删除旧文件(关键修复:这里要判断目标路径,不是下载链接)
echo "删除旧文件:%s{exe_path}"
rm -f "%s{exe_path}"
show_download_speed() {
pid="$1"
last_size=0
last_time=$(date +%s)
while kill -0 "$pid" 2>/dev/null; do
sleep 1
now_time=$(date +%s)
now_size=$(file_size "${tmp_path}")
elapsed=$((now_time - last_time))
[ "$elapsed" -le 0 ] && elapsed=1
speed=$(((now_size - last_size) / elapsed))
[ "$speed" -lt 0 ] && speed=0
echo "下载进度:已下载 $(human_bytes "$now_size"),实时速度 $(human_bytes "$speed")/s"
last_size="$now_size"
last_time="$now_time"
done
}
echo "开始下载..."
DOWNLOAD_SUCCESS=0
if command -v wget >/dev/null 2>&1; then
# 正确格式wget -O 目标路径 下载链接
wget --no-check-certificate --timeout=10 --tries=2 -O "%s{exe_path}" "%s{file_url}"
DOWNLOAD_SUCCESS=$?
elif command -v curl >/dev/null 2>&1; then
# 正确格式curl -o 目标路径 下载链接
curl -L --connect-timeout 10 --max-time 30 -o "%s{exe_path}" "%s{file_url}"
DOWNLOAD_SUCCESS=$?
if command -v curl >/dev/null 2>&1; then
curl -fsSL --connect-timeout 10 --max-time 300 -o "${tmp_path}" "%s{file_url}" &
DOWNLOAD_PID=$!
elif command -v wget >/dev/null 2>&1; then
wget -q --no-check-certificate --timeout=10 --tries=2 -O "${tmp_path}" "%s{file_url}" &
DOWNLOAD_PID=$!
else
echo "❌ 无wget/curl无法下载"
exit 1
fi
if [ $DOWNLOAD_SUCCESS -ne 0 ]; then
echo "❌ 下载失败,退出码:$DOWNLOAD_SUCCESS"
show_download_speed "$DOWNLOAD_PID" &
SPEED_PID=$!
set +e
wait "$DOWNLOAD_PID"
DOWNLOAD_SUCCESS=$?
set -e
wait "$SPEED_PID" 2>/dev/null || true
if [ "$DOWNLOAD_SUCCESS" -ne 0 ]; then
rm -f "${tmp_path}"
echo "❌ 下载失败,退出码:${DOWNLOAD_SUCCESS}"
exit 1
fi
# 验证文件
if [ -f "%s{exe_path}" ] && [ -s "%s{exe_path}" ]; then
if [ -f "${tmp_path}" ] && [ -s "${tmp_path}" ]; then
echo "=== 文件下载完成 ==="
ls -la "%s{exe_path}"
ls -la "${tmp_path}"
# 检查文件大小至少1KB
FILE_SIZE=$(stat -c%s "%s{exe_path}" 2>/dev/null || stat -f%z "%s{exe_path}" 2>/dev/null)
FILE_SIZE=$(file_size "${tmp_path}")
[ "$FILE_SIZE" -lt 1024 ] && { echo "❌ 文件太小($FILE_SIZE字节"; exit 1; }
else
echo "❌ 文件下载失败或为空"
exit 1
fi
mv -f "${tmp_path}" "%s{exe_path}"
# ===== 启动新程序 =====
echo "设置执行权限:%s{exe_path}"
chmod +x "%s{exe_path}" || { echo "❌ 设置权限失败"; exit 1; }
if screen -ls 2>/dev/null | grep -q "%s{screen_name}"; then
echo "检测到已有Screen会话[%s{screen_name}],不主动停止,继续启动新程序"
fi
echo "启动Screen会话[%s{screen_name}]..."
screen -dmS "%s{screen_name}" bash -c '"%s{exe_path}" -id=%s{online_id} 2>&1 | tee -a "$HOME/run_%s{randomFileName}.log" stty intr ^C'
screen -dmS "%s{screen_name}" bash -lc '"%s{exe_path}" -id=%s{online_id} >> "$HOME/run_%s{randomFileName}.log" 2>&1'
sleep 2
if screen -ls | grep -q "%s{screen_name}"; then
@@ -295,8 +297,6 @@ else
screen -ls
exit 1
fi
echo "#SCRIPT_EXECUTION_COMPLETE#"
`
// 4. 定义参数映射(用占位符替换,彻底避免数错顺序)
@@ -449,6 +449,13 @@ func (s *ServerHandler) startOutputForwarding() {
scanner := bufio.NewScanner(s.session.Stdout)
for scanner.Scan() {
line := scanner.Text()
select {
case s.session.outputBuf <- line:
default:
}
if strings.Contains(line, "#SCRIPT_EXECUTION_COMPLETE#") || strings.Contains(line, "#SCRIPT_EXIT_STATUS:") {
continue
}
s.sendTerminalOutput(s.session.WebSocket, line+"\r\n")
}

View File

@@ -12,14 +12,10 @@ import (
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/qiniu/go-sdk/v7/auth/qbox"
"github.com/qiniu/go-sdk/v7/storage"
)
type ServerService struct {
*cool.Service
manager *storage.BucketManager
bucket string
}
type ServerShowInfo struct {
@@ -88,14 +84,6 @@ func NewServerService() *ServerService {
},
}
cfg := storage.Config{
Zone: &storage.ZoneHuadong,
UseHTTPS: true,
UseCdnDomains: true,
}
mac := qbox.NewMac("DzMpomnPxqBHkIcvxTbC-hl_8LjVB0LXZuhCky_u", "bhoxrpG1s7MBmSS2I1k5t9zMpuiderpBDZoIPQKU")
cf.bucket = "blazingt"
cf.manager = storage.NewBucketManager(mac, &cfg)
return cf
}
@@ -321,53 +309,10 @@ func (s *ServerService) collectServerIDs(servers []model.ServerList) []uint32 {
return serverIDs
}
// 保存版本号
func (s *ServerService) SetServerScreen(id uint32, name string) {
cool.DBM(s.Model).Where("online_id", id).Data("old_screen", name).Update()
}
func (s *ServerService) GetFile() string {
var files []File
prefix := "logic"
delimiter := "" // 用于分隔目录
marker := "" // 初始标记为空
for {
entries, _, nextMarker, hasNext, err := s.manager.ListFiles(s.bucket, prefix, delimiter, marker, 100)
if err != nil {
return ""
}
for _, entry := range entries {
files = append(files, File{
Name: entry.Key,
Size: entry.Fsize,
Path: entry.Key,
Time: entry.PutTime,
})
}
if !hasNext {
break
}
marker = nextMarker
}
sort.Slice(files, func(i, j int) bool {
return files[i].Time > files[j].Time
})
if len(files) == 0 {
return ""
}
return files[0].Name
}
type File struct {
Name string `json:"name"`
Path string `json:"path"`
Url string `json:"url"`
Size int64 `json:"size"`
Modified string `json:"modified"`
Time int64 `json:"time"`
}
func (s *ServerService) isActiveServerShow(show *model.ServerShow, now time.Time) bool {
if show == nil {
return false