diff --git a/deploy.sh b/deploy.sh index d716385..2c9b197 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,15 +1,16 @@ #!/bin/bash # ============================================================ -# frpc-console 一键部署脚本 +# frpc-console 一键部署脚本 (Docker 优先) # 支持:Linux x86_64 / ARM64 / ARMv7 -# 自动安装:git / curl / wget / Go / Docker +# 自动安装:git / curl / wget / Docker # # 用法: -# ./deploy.sh # 完整交互流程 -# ./deploy.sh --yes # 跳过确认,直接执行 -# ./deploy.sh --check # 只检测环境,不执行 -# ./deploy.sh --dry-run # 显示将执行的操作,不实际执行 +# ./deploy.sh # 交互式选择通道 +# ./deploy.sh --channel lts # 指定 LTS 通道 +# ./deploy.sh --channel preview # 指定 Preview 通道 +# ./deploy.sh --yes # 跳过确认 +# ./deploy.sh --check # 只检测环境 # ============================================================ set -e @@ -40,22 +41,18 @@ WORK_DIR="/tmp/frpc-console-build" DEFAULT_PORT=9300 DEFAULT_DEPLOY_DIR="/opt/frpc-console" IMAGE_NAME="frpc-console" -GO_VERSION="1.25.0" # ---------- 状态变量 ---------- OS="" OS_VERSION="" ARCH="" -GO_ARCH="" HAS_GIT=false HAS_CURL=false HAS_WGET=false -HAS_GO=false HAS_DOCKER=false NEED_INSTALL_GIT=false NEED_INSTALL_CURL=false NEED_INSTALL_WGET=false -NEED_INSTALL_GO=false PORT=${DEFAULT_PORT} DEPLOY_DIR=${DEFAULT_DEPLOY_DIR} DATA_DIR="${DEPLOY_DIR}/data" @@ -64,6 +61,10 @@ CONTAINER_RUNNING=false SKIP_CONFIRM=false CHECK_ONLY=false DRY_RUN=false +CHANNEL="" +CURRENT_VERSION="" +TARGET_VERSION="" +IMAGE_TAG="" # ---------- 打印函数 ---------- print_info() { echo -e "${BLUE}[INFO]${NC} $1"; } @@ -87,19 +88,28 @@ parse_args() { --dry-run) DRY_RUN=true ;; + --channel) + shift + CHANNEL="$1" + ;; + --channel=*) + CHANNEL="${arg#*=}" + ;; --help|-h) echo "用法: ./deploy.sh [选项]" echo "" echo "选项:" - echo " --yes, -y 跳过所有确认提示,直接执行" - echo " --check 只检测环境,不执行部署" - echo " --dry-run 显示将执行的操作,不实际执行" - echo " --help, -h 显示帮助信息" + echo " --channel lts 使用 LTS 通道" + echo " --channel preview 使用 Preview 通道" + echo " --yes, -y 跳过所有确认提示" + echo " --check 只检测环境,不执行部署" + echo " --dry-run 显示将执行的操作,不实际执行" + echo " --help, -h 显示帮助信息" echo "" echo "示例:" - echo " ./deploy.sh # 完整交互流程" - echo " ./deploy.sh --yes # 无人值守部署" - echo " ./deploy.sh --check # 只检测环境" + echo " ./deploy.sh # 交互式选择通道" + echo " ./deploy.sh --channel lts # 部署 LTS 版本" + echo " ./deploy.sh --yes # 无人值守部署" exit 0 ;; *) @@ -136,14 +146,8 @@ detect_os() { detect_arch() { ARCH=$(uname -m) case $ARCH in - x86_64|amd64) - GO_ARCH="amd64" - ;; - aarch64|arm64) - GO_ARCH="arm64" - ;; - armv7l|armhf) - GO_ARCH="armv6l" + x86_64|amd64|aarch64|arm64|armv7l|armhf) + print_success "CPU 架构: $ARCH" ;; *) print_error "不支持的 CPU 架构: $ARCH" @@ -154,33 +158,10 @@ detect_arch() { # ---------- 检测工具 ---------- check_tools() { - if command -v git &> /dev/null; then - HAS_GIT=true - else - NEED_INSTALL_GIT=true - fi - - if command -v curl &> /dev/null; then - HAS_CURL=true - else - NEED_INSTALL_CURL=true - fi - - if command -v wget &> /dev/null; then - HAS_WGET=true - else - NEED_INSTALL_WGET=true - fi - - if command -v go &> /dev/null; then - HAS_GO=true - else - NEED_INSTALL_GO=true - fi - - if command -v docker &> /dev/null; then - HAS_DOCKER=true - fi + command -v git &> /dev/null && HAS_GIT=true || NEED_INSTALL_GIT=true + command -v curl &> /dev/null && HAS_CURL=true || NEED_INSTALL_CURL=true + command -v wget &> /dev/null && HAS_WGET=true || NEED_INSTALL_WGET=true + command -v docker &> /dev/null && HAS_DOCKER=true } # ---------- 检查容器状态 ---------- @@ -193,6 +174,152 @@ check_container() { fi } +# ---------- 版本管理函数 ---------- + +# read_version_file 读取仓库的 VERSION.ini 文件 +read_version_file() { + local version_file="./VERSION.ini" + if [ ! -f "$version_file" ]; then + print_error "VERSION.ini 文件不存在: $version_file" + return 1 + fi + # 去掉注释行(以 ; 或 # 开头),读取第一列为语义版本 + SEMVER=$(grep -v "^[;#]" "$version_file" | head -1 | awk '{print $1}') + LTS_DATE=$(grep -v "^[;#]" "$version_file" | head -1 | awk '{print $2}') + echo "$SEMVER $LTS_DATE" +} + +# get_version_type 判断版本类型 +get_version_type() { + local semver="$1" + local minor="${semver#*.}" + if [ "$minor" = "0" ] || [ "$minor" = "5" ]; then + echo "lts" + else + echo "preview" + fi +} + +# generate_target_version 生成目标版本字符串 +generate_target_version() { + local channel="$1" + local semver_lts_date + semver_lts_date=$(read_version_file) + SEMVER=$(echo "$semver_lts_date" | awk '{print $1}') + LTS_DATE=$(echo "$semver_lts_date" | awk '{print $2}') + + if [ -z "$SEMVER" ]; then + print_error "无法从 VERSION.ini 读取语义版本" + exit 1 + fi + + local vtype + vtype=$(get_version_type "$SEMVER") + + if [ "$channel" = "lts" ] || [ "$vtype" = "lts" ]; then + # LTS 版本必须有日期 + if [ -z "$LTS_DATE" ]; then + LTS_DATE=$(date +%Y%m%d) + print_warn "LTS 版本未指定日期,使用今天: $LTS_DATE" + fi + TARGET_VERSION="${SEMVER}-lts-${LTS_DATE}" + IMAGE_TAG="${SEMVER}-lts-${LTS_DATE}" + else + # Preview 版本使用编译时间戳 + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + TARGET_VERSION="${SEMVER}-preview-${TIMESTAMP}" + IMAGE_TAG="preview-${TIMESTAMP}" + fi + echo "$TARGET_VERSION $IMAGE_TAG" +} + +# get_current_version 获取当前运行的版本 +get_current_version() { + local version_file="${DEPLOY_DIR}/VERSION.ini" + if [ -f "$version_file" ]; then + # 去掉注释行,读取第一行内容 + grep -v "^[;#]" "$version_file" | head -1 | tr -d '\n' + else + # 兼容旧版本 + if [ "$CONTAINER_EXISTS" = true ]; then + docker exec frpc-console cat /app/VERSION.ini 2>/dev/null | grep -v "^[;#]" | head -1 | tr -d '\n' || echo "0.0.0-dev" + else + echo "0.0.0-dev" + fi + fi +} + +# extract_semver 提取语义版本 +extract_semver() { + local version="$1" + echo "$version" | sed -E 's/^([0-9]+\.[0-9]+).*$/\1/' +} + +# extract_timestamp 提取时间戳 +extract_timestamp() { + local version="$1" + echo "$version" | sed -E 's/^[0-9]+\.[0-9]+-(lts|preview)-//' +} + +# compare_version 比较两个版本 +# 返回: 0=相等, 1=目标更新, 2=目标更旧 +compare_version() { + local current="$1" + local target="$2" + + local cur_semver=$(extract_semver "$current") + local tgt_semver=$(extract_semver "$target") + + if [ "$tgt_semver" -gt "$cur_semver" ] 2>/dev/null; then + return 1 + fi + if [ "$tgt_semver" -lt "$cur_semver" ] 2>/dev/null; then + return 2 + fi + + local cur_ts=$(extract_timestamp "$current") + local tgt_ts=$(extract_timestamp "$target") + + if [ -z "$cur_ts" ]; then + cur_ts="0" + fi + if [ -z "$tgt_ts" ]; then + tgt_ts="0" + fi + + if [ "$tgt_ts" -gt "$cur_ts" ] 2>/dev/null; then + return 1 + elif [ "$tgt_ts" -eq "$cur_ts" ] 2>/dev/null; then + return 0 + else + return 2 + fi +} + +# ---------- 通道选择 ---------- +select_channel() { + if [ -n "$CHANNEL" ]; then + if [ "$CHANNEL" != "lts" ] && [ "$CHANNEL" != "preview" ]; then + print_error "无效通道: $CHANNEL (仅支持 lts / preview)" + exit 1 + fi + return + fi + + echo "" + echo -e "${CYAN}请选择部署通道:${NC}" + echo " 1. LTS (稳定版,生产推荐) [默认]" + echo " 2. Preview (技术预览版,包含新特性)" + echo "" + read -p "请选择 [1]: " CHANNEL_INPUT /dev/null; then - if ! sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='users';" 2>/dev/null | grep -q "^1$"; then - print_warn "数据库文件无效,将删除,由容器全新初始化" - rm -f "$DB_FILE" - else - print_info "数据库有效,保留" + read -p "确认执行降级? [y/N]: " CONFIRM_DOWNGRADE /etc/profile.d/go.sh - fi - - /usr/local/go/bin/go env -w GOPROXY=https://goproxy.cn,direct - /usr/local/go/bin/go env -w GOPRIVATE=git.whitetop.xyz - - print_success "Go ${GO_VERSION} (${GO_ARCH}) 安装完成" - fi - - export PATH=$PATH:/usr/local/go/bin - # ----- 拉取代码 ----- print_step "拉取代码..." rm -rf "$WORK_DIR" @@ -483,30 +550,14 @@ do_deploy() { mkdir -p bin static print_success "代码拉取完成" - # ----- 修复 go.mod ----- - print_step "检查 go.mod..." - if grep -q "go 1.2[6-9]" go.mod 2>/dev/null; then - print_warn "检测到 go.mod 版本过高,自动降级到 go 1.21" - sed -i 's/go 1.2[6-9].*/go 1.21/' go.mod - fi - - # ----- 下载依赖 ----- - print_step "下载 Go 依赖..." - go mod download - print_success "依赖下载完成" - - # ----- 编译 ----- - print_step "编译 frpc-console..." - CGO_ENABLED=0 GOOS=linux go build \ - -ldflags="-s -w" \ - -o frpc-console . - - if [ -f "frpc-console" ]; then - SIZE=$(du -h frpc-console | cut -f1) - print_success "编译完成 ($SIZE)" - else - print_error "编译失败" - exit 1 + # ----- 停止旧容器(拉取代码之后)----- + if [ "$CONTAINER_EXISTS" = true ]; then + print_step "停止旧容器..." + if [ "$CONTAINER_RUNNING" = true ]; then + docker stop frpc-console 2>/dev/null || true + fi + docker rm frpc-console 2>/dev/null || true + print_success "旧容器已清理" fi # ----- 准备部署目录 ----- @@ -514,28 +565,21 @@ do_deploy() { mkdir -p "$DEPLOY_DIR" mkdir -p "$DATA_DIR" - if [ -f "$DB_FILE" ]; then - print_info "数据库文件存在,将保留" - else - print_info "数据库文件不存在,容器启动时将自动创建" - fi - - cp frpc-console "$DEPLOY_DIR/" - print_success "二进制已复制到 $DEPLOY_DIR" - # ----- 构建 Docker 镜像 ----- print_step "构建 Docker 镜像..." - docker build -t "${IMAGE_NAME}:latest" . - print_success "Docker 镜像构建完成: ${IMAGE_NAME}:latest" + docker build -t "${IMAGE_NAME}:${IMAGE_TAG}" \ + --build-arg VERSION="${TARGET_VERSION}" \ + --build-arg IMAGE_TAG="${IMAGE_TAG}" \ + . - # ----- 停止旧容器 ----- - if [ "$CONTAINER_EXISTS" = true ]; then - print_step "处理旧容器..." - if [ "$CONTAINER_RUNNING" = true ]; then - docker stop frpc-console 2>/dev/null || true - fi - docker rm frpc-console 2>/dev/null || true - print_success "旧容器已清理" + print_success "Docker 镜像构建完成: ${IMAGE_NAME}:${IMAGE_TAG}" + + if [ "$CHANNEL" = "lts" ]; then + docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${IMAGE_NAME}:lts" + print_info "已标记 ${IMAGE_NAME}:lts" + else + docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "${IMAGE_NAME}:preview" + print_info "已标记 ${IMAGE_NAME}:preview" fi # ----- 启动新容器 ----- @@ -544,10 +588,10 @@ do_deploy() { --name frpc-console \ --restart=always \ --network host \ - -v ${DATA_DIR}:/app/data \ + -v ${DEPLOY_DIR}:/app \ -e PORT=${PORT} \ -e TZ=Asia/Shanghai \ - ${IMAGE_NAME}:latest + ${IMAGE_NAME}:${IMAGE_TAG} if docker ps | grep -q frpc-console; then print_success "容器启动成功!" @@ -556,13 +600,11 @@ do_deploy() { exit 1 fi - # ----- 检查 frpc 子进程(增强版) ----- + # ----- 检查 frpc 子进程 ----- print_step "检查 frpc 状态..." sleep 3 - # 先确认容器在运行 if docker ps --format '{{.Names}}' | grep -q "^frpc-console$"; then - # 再检查 frpc 进程 if docker exec frpc-console ps aux 2>/dev/null | grep -q "[f]rpc -c"; then print_success "frpc 进程运行正常" else @@ -573,14 +615,32 @@ do_deploy() { exit 1 fi - # ----- 事务后钩子:检测数据库是否可用 ----- + # ----- 事务后钩子:检测数据库 ----- print_step "验证数据库状态..." - if docker exec frpc-console sqlite3 /app/data/frpc-console.db "SELECT COUNT(*) FROM users;" 2>/dev/null | grep -q "^[0-9]"; then + if docker exec frpc-console sqlite3 /app/frpc-console.db "SELECT COUNT(*) FROM users;" 2>/dev/null | grep -q "^[0-9]"; then print_success "数据库可用" else print_warn "数据库为空或不可用,请通过 WebUI 注册管理员账户" fi + # ----- 验证版本写入 ----- + print_step "验证版本..." + sleep 1 + CONTAINER_VERSION=$(docker exec frpc-console cat /app/VERSION.ini 2>/dev/null | grep -v "^[;#]" | head -1 | tr -d '\n' || echo "unknown") + echo -e " 容器版本: ${CONTAINER_VERSION}" + echo -e " 目标版本: ${TARGET_VERSION}" + if [ "$CONTAINER_VERSION" = "$TARGET_VERSION" ]; then + print_success "版本写入成功" + else + print_warn "版本不匹配,请检查" + fi + + if [ -n "$BACKUP_DIR" ]; then + echo "" + print_warn "降级前数据已备份到: $BACKUP_DIR" + echo " 如需回退,请恢复: cp -r $BACKUP_DIR/frpc-console/* $DEPLOY_DIR/" + fi + # ----- 清理临时文件 ----- print_step "清理临时文件..." rm -rf "$WORK_DIR" @@ -593,15 +653,17 @@ do_deploy() { echo "" echo -e " ${CYAN}📍 访问地址:${NC} http://$(hostname -I | awk '{print $1}'):${PORT}" - echo -e " ${CYAN}📂 数据目录:${NC} ${DATA_DIR}" - echo -e " ${CYAN}📦 备份目录:${NC} ${BACKUP_DIR}" + echo -e " ${CYAN}📂 数据目录:${NC} ${DEPLOY_DIR}" + echo -e " ${CYAN}📦 备份目录:${NC} /tmp/frpc-console/db-backups" echo -e " ${CYAN}🐳 容器名称:${NC} frpc-console" + echo -e " ${CYAN}🏷️ 版本:${NC} ${TARGET_VERSION}" echo "" echo -e " ${CYAN}常用命令:${NC}" echo " docker logs frpc-console # 查看日志" echo " docker restart frpc-console # 重启服务" echo " docker stop frpc-console # 停止服务" echo " docker start frpc-console # 启动服务" + echo " cat ${DEPLOY_DIR}/VERSION.ini # 查看当前版本" echo "" echo -e " ${YELLOW}首次访问需要注册管理员账户${NC}" echo "" @@ -620,6 +682,16 @@ main() { check_tools check_container + select_channel + + generate_target_version "$CHANNEL" + + if [ "$CONTAINER_EXISTS" = true ]; then + CURRENT_VERSION=$(get_current_version) + else + CURRENT_VERSION="(首次部署)" + fi + print_environment_summary if [ "$CHECK_ONLY" = true ]; then @@ -638,11 +710,11 @@ main() { fi generate_plan - print_deployment_plan if ! confirm_deploy; then custom_config + generate_plan print_deployment_plan if ! confirm_deploy; then print_info "已取消部署" diff --git a/main.go b/main.go index eee932f..6d28a3b 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,29 @@ package main import ( "log" + "os" "time" ) +// 由 build.go 通过 -ldflags 注入 +var ( + buildVersion = "0.0.0-dev" + buildImageTag = "dev" + buildTime = "unknown" +) + func main() { + // ---- 写入 VERSION.ini 文件到持久化目录 ---- + if err := os.WriteFile("./VERSION.ini", []byte(buildVersion+"\n"), 0644); err != nil { + log.Printf("⚠️ 写入 VERSION.ini 文件失败: %v", err) + } else { + log.Printf("📌 版本: %s", buildVersion) + } + if err := InitDB(); err != nil { log.Fatal("❌ 数据库初始化失败:", err) } - // 启动时生成配置并启动 frpc if err := GenerateFrpcConfig(); err != nil { log.Println("⚠️ 生成配置文件失败:", err) } @@ -19,7 +33,6 @@ func main() { log.Println("⚠️ 启动 frpc 失败:", err) } - // ✅ 启动 Watchdog:每 30 秒检查一次 frpc 状态,挂了自动重启 go startWatchdog() r := SetupRouter() @@ -32,7 +45,6 @@ func main() { } } -// Watchdog 协程:定期检查 frpc 是否存活 func startWatchdog() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() diff --git a/script/build.go b/script/build.go index 2369f10..7285556 100644 --- a/script/build.go +++ b/script/build.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" ) type Platform struct { @@ -22,15 +23,99 @@ var platforms = []Platform{ {"windows", "amd64", "Windows x86-64", ".exe"}, {"linux", "amd64", "Linux x86-64", ""}, {"linux", "arm64", "Linux ARM64", ""}, - {"linux", "arm", "Linux ARMv7l", ""}, // GOARCH=arm, GOARM=7 + {"linux", "arm", "Linux ARMv7l", ""}, } const ( - Version = "2.0.0" - BuildTime = "2026-07-26" + BuildTime = "2026-07-29" Binary = "frpc-console" ) +// ============================================================ +// 版本管理核心 +// ============================================================ + +// readVersion 读取仓库根目录的 VERSION.ini 文件 +// Preview 格式: "2.4" +// LTS 格式: "2.5 20260729" +func readVersion() (semver string, ltsDate string) { + data, err := os.ReadFile("./VERSION.ini") + if err != nil { + return "0.0.0", "" + } + // 去掉注释行(以 ; 或 # 开头) + lines := strings.Split(string(data), "\n") + var content string + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { + continue + } + content = line + break + } + parts := strings.Fields(content) + if len(parts) < 1 { + return "0.0.0", "" + } + semver = parts[0] + if len(parts) > 1 { + ltsDate = parts[1] + } + return semver, ltsDate +} + +// getVersionType 判断版本类型 +// 规则: X.0 或 X.5 → LTS,其他 → Preview +func getVersionType(semver string) string { + parts := strings.Split(semver, ".") + if len(parts) < 2 { + return "preview" + } + minor := parts[1] + if minor == "0" || minor == "5" { + return "lts" + } + return "preview" +} + +// getFullVersion 生成完整版本字符串 +// LTS (X.0/X.5): {semver}-lts-{ltsDate} (必须有日期) +// Preview (其他): {semver}-preview-{buildTimestamp} +func getFullVersion() string { + semver, ltsDate := readVersion() + vType := getVersionType(semver) + + switch vType { + case "lts": + if ltsDate == "" { + ltsDate = time.Now().Format("20060102") + } + return semver + "-lts-" + ltsDate + default: + timestamp := time.Now().Format("20060102-150405") + return semver + "-preview-" + timestamp + } +} + +// getImageTag 生成镜像标签 +// LTS: {semver}-lts-{ltsDate} +// Preview: preview-{buildTimestamp} +func getImageTag() string { + semver, ltsDate := readVersion() + vType := getVersionType(semver) + + switch vType { + case "lts": + if ltsDate == "" { + ltsDate = time.Now().Format("20060102") + } + return semver + "-lts-" + ltsDate + default: + return "preview-" + time.Now().Format("20060102-150405") + } +} + func main() { if len(os.Args) > 1 { target := os.Args[1] @@ -44,6 +129,12 @@ func main() { case "list": listPlatforms() return + case "version": + fmt.Println(getFullVersion()) + return + case "image-tag": + fmt.Println(getImageTag()) + return } for _, p := range platforms { if target == p.OS+"/"+p.Arch { @@ -52,13 +143,17 @@ func main() { } } fmt.Println("❌ 不支持的平台:", target) - fmt.Println(" 可用: windows/amd64, linux/amd64, linux/arm64, linux/armv7") - fmt.Println(" 或: all, list, clean") + fmt.Println(" 可用: windows/amd64, linux/amd64, linux/arm64, linux/arm") + fmt.Println(" 或: all, list, clean, version, image-tag") return } interactiveMenu() } +// ============================================================ +// 构建函数 +// ============================================================ + func listPlatforms() { fmt.Println("可用平台:") for i, p := range platforms { @@ -68,15 +163,19 @@ func listPlatforms() { func interactiveMenu() { fmt.Println("========================================") - fmt.Println(" frpc-console 多平台构建工具 v" + Version) + fmt.Println(" frpc-console 多平台构建工具") fmt.Println("========================================") fmt.Println() + fmt.Println(" 当前版本:", getFullVersion()) + fmt.Println(" 镜像标签:", getImageTag()) + fmt.Println() for i, p := range platforms { fmt.Printf(" %d. %s (%s/%s)\n", i+1, p.Name, p.OS, p.Arch) } fmt.Println(" a. 全部构建") fmt.Println(" c. 清理构建产物") + fmt.Println(" v. 显示版本信息") fmt.Println(" q. 退出") fmt.Println() @@ -96,6 +195,10 @@ func interactiveMenu() { case "a": buildAll() return + case "v": + fmt.Println("版本:", getFullVersion()) + fmt.Println("镜像标签:", getImageTag()) + return default: var idx int if n, err := fmt.Sscanf(input, "%d", &idx); n == 1 && err == nil && idx >= 1 && idx <= len(platforms) { @@ -125,16 +228,22 @@ func build(p Platform, silent bool) { return } - // 构建输出文件名 + fullVersion := getFullVersion() + imageTag := getImageTag() + outName := Binary + "-" + p.OS + "-" + p.Arch if p.Arch == "arm" { - outName += "v7" // 标注 ARMv7 版本 + outName += "v7" } outName += p.Ext outPath := filepath.Join(outDir, outName) cmd := exec.Command("go", "build", - "-ldflags=-s -w -X main.version="+Version, + "-ldflags="+ + "-s -w "+ + "-X main.buildVersion="+fullVersion+" "+ + "-X main.buildImageTag="+imageTag+" "+ + "-X main.buildTime="+BuildTime, "-o", outPath, ".", ) @@ -144,7 +253,6 @@ func build(p Platform, silent bool) { "CGO_ENABLED=0", ) - // ARM 架构指定 GOARM=7 if p.Arch == "arm" { cmd.Env = append(cmd.Env, "GOARM=7") } @@ -159,6 +267,7 @@ func build(p Platform, silent bool) { if !silent { fmt.Printf("✅ 构建完成: %s\n", outPath) + fmt.Printf(" 版本: %s\n", fullVersion) if info, err := os.Stat(outPath); err == nil { size := float64(info.Size()) / 1024 / 1024 fmt.Printf(" 📦 %.2f MB\n", size) diff --git a/version.ini b/version.ini new file mode 100644 index 0000000..0ef978d --- /dev/null +++ b/version.ini @@ -0,0 +1,6 @@ +; frpc-console 版本声明文件 +; 格式: {语义版本号} [{LTS发布日期}] +; 示例: +; 2.4 # Preview 版本,无日期 +; 2.5 20260729 # LTS 版本,带发布日期 +2.4 \ No newline at end of file