Files
iOSBuildServer/README.md
T
shenleiandClaude Opus 5 21542c88d7 feat: 版本号按分支轨道分开管理,版本号配置收归管理员
master/develop 共用 release 轨(App_Store 打包构建号自增),feature 分支走
feature 轨(构建号永不自增,始终使用手填值),两条轨道的 App_Ver 各自独立。

- versions 改为 tracks 结构,旧的单轨/单值配置在加载时自动迁移进 release 轨
- 新增 config["branch_track"] 记录分支归属,缺省按分支名推断,可在分支管理页改
- 新增 PUT /api/config/branches/track;分支名含 / 时走请求体而非路径参数
- 修复 DELETE /api/config/branches/{name} 无法删除含 / 的分支(405)
- 版本号接口不再对普通用户开放,打包设置页整页改为管理员可见

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 17:56:10 +09:00

21 KiB
Raw Blame History

iOS 自动打包服务

基于 FastAPI + Vue 3 的 iOS 应用自动打包服务,支持 Ad_Hoc 和 App_Store 两种打包类型,提供 Web 界面操作、实时日志流、构建历史管理、多用户权限和健康监测。

目录


环境要求

依赖 版本要求 说明
macOS 11+ 必须,xcodebuild 依赖
Xcode 14+ 必须,含 Command Line Tools
Python 3.9+ 后端运行
Node.js 18+ 前端构建
CocoaPods - 项目已有 Podfile 时需要

快速开始

1. 克隆项目

git clone <repo-url> BuildServer
cd BuildServer

2. 配置环境变量

cp .env.example .env
vim .env

建议优先检查的配置项:

# 分支源码根目录,默认在当前项目下
GIT_SOURCE_BASE=/path/to/iOSBuildServer/ReadoorBranches

# 打包输出目录,默认在当前项目下
BUILD_BASE_DIR=/path/to/iOSBuildServer/build

# Git 远程仓库地址(配置后可自动拉取分支)
GIT_REMOTE_URL=git@github.com:org/repo.git

# 管理员密码(建议修改)
ADMIN_PASSWORD=your_secure_password

3. 构建与启动

推荐一键部署(构建 + 启动 + 健康监测,一步到位):

# 一键部署:build + start + watchdog(强制重来)
./deploy.sh up

up 会自动构建前端、(强制)重启后端并轮询 /api/health 直到就绪才返回,再拉起 watchdog,最后打印状态。适合首次部署和更新代码后重新上线。

⚠️ up/start 采用「强制重来」语义:若检测到旧服务会先停再起,正在进行的打包任务会被中断。

也可以分步执行:

# 一键构建(安装依赖 + 构建前端)
./deploy.sh build

# 启动服务(等待就绪;端口被非本项目进程占用时会明确报错)
./deploy.sh start

启动后访问 http://<服务器IP>:8000 即可使用。

4.(可选)初始化源码目录

配置 GIT_REMOTE_URL 后,系统默认使用唯一的共享源码目录 GIT_SOURCE_DIR。任务会串行执行拉取、切分支、清理和复制,复制完成后在各自的打包目录中并行构建。

未配置远程仓库时,需要继续手动准备每个分支目录:

# 创建分支源码根目录
mkdir -p /path/to/ReadoorBranches

# 手动 clone 主分支(后续可继续手动维护)
git clone -b main git@github.com:org/repo.git /path/to/ReadoorBranches/main

配置远程仓库时,无需手动 clone;首次打包会初始化共享目录。

5.(可选)启用健康监测

./deploy.sh watchdog

6.(可选)设为开机自启

./deploy/install-service.sh install

配置说明

所有配置通过项目根目录的 .env 文件管理,修改后重启服务生效。

目录配置

变量 示例 说明
GIT_SOURCE_BASE /Users/shen/Work/Code/iOSBuildServer/ReadoorBranches 人工维护模式下的分支源码根目录
GIT_SOURCE_DIR /Users/shen/Work/Code/iOSBuildServer/ReadoorBranches/workspace 配置远程仓库时唯一的共享源码工作目录
BUILD_BASE_DIR /Users/shen/Work/Code/iOSBuildServer/build 打包产物输出基础目录
GIT_REMOTE_URL git@github.com:org/repo.git 远程仓库地址,首次构建时自动初始化共享源码目录

私有仓库请创建仅有 read_repository 权限的 Personal Access Token,并配置为 GIT_PASSWORDGIT_USERNAME 使用该 Token 所属账号。不要在 .envconfig.json 或 Git 仓库中保存账号登录密码。

服务配置

变量 默认值 说明
BACKEND_PORT 8000 后端服务端口
ADMIN_USERNAME admin 初始管理员用户名(首次启动自动创建)
ADMIN_PASSWORD admin123 初始管理员密码
JWT_SECRET ios-build-server-secret-key-change-in-production JWT 签名密钥(生产环境务必修改)

公网部署

不要将 Uvicorn 端口直接暴露到公网。使用 Nginx 或 Caddy 提供 HTTPS,服务仅监听 127.0.0.1。项目提供了 nginx.conf.example 作为模板。

公网 .env 至少需要如下配置:

APP_ENV=production
BIND_HOST=127.0.0.1
JWT_SECRET=<至少 32 位随机字符串>
ADMIN_PASSWORD=<至少 12 位强密码>
CORS_ALLOWED_ORIGINS=https://build.example.com
TRUSTED_HOSTS=build.example.com
GIT_USERNAME=<只读 Token 所属账号>
GIT_PASSWORD=<仅 read_repository 权限的 Personal Access Token>

设置完成后执行 chmod 600 .env,并在云安全组和系统防火墙中只开放 80/443APP_ENV=production 会在默认 JWT、弱管理员密码、通配 CORS 或未设置可信域名时拒绝启动。

打包配置

变量 默认值 说明
MAX_CONCURRENT_BUILDS 2 最大并行打包数
BUILD_DIR_RETENTION_HOURS 24 打包目录保留时间(小时),超期自动清理
BUILD_TIMEOUT_HOURS 1 打包超时时间(小时),超时自动标记失败

Web 管理配置

以下配置通过管理后台 Web 界面管理(存储在 backend/data/config.json):

⚠️ config.json运行时数据文件(网页改配置、每次 App_Store 打包写入构建号都会改写它),不纳入版本控制git pull 不会覆盖它。仓库仅提供结构模板 config.example.json

首次部署时,若项目根目录下存在 config.json,系统会自动将其复制为 backend/data/config.json 作为初始配置。可从模板起步:

cp config.example.json config.json   # 按需填入真实的 apps / servers / upload 等,再启动

导入后即可继续在后台增删改。

配置项 说明
App 管理 应用名称、Scheme、服务器环境、upload_key 等
Scheme 管理 打包 Scheme 配置
服务器环境 测试/正式/自定义环境的 API 地址、Universal Link 等
分支管理 可打包的代码分支列表,以及每个分支所属的版本轨道
版本号 服务端统一管理 App_Ver 和 Build_Ver,不修改分支源码;按版本轨道分开管理(见下)
上传配置 服务端内置 OSS / WebDAV 分发,生成 IPA、manifest、下载页和二维码

版本号与版本轨道

版本号按 轨道(track 分开管理,两条轨道的 App_Ver 各自独立填写、互不影响:

轨道 适用分支 构建号行为
release(上架) master、develop(共用同一套版本号与构建号序列) App_Store 打包时按当前 App_Ver 自动 +1
feature(自测) feature/* 等其他分支 永不自增,始终使用配置页填写的值(默认 .0
  • 分支归属默认按名字推断(master/main/develop/dev/release-/hotfix- → release,其余 → feature),可在「分支管理」页逐个改。
  • 每条轨道内的构建号按 App_Ver 独立记录(build_map),回退到打过包的旧版本号时会从它自己的最大值继续,不会与 App Store Connect 上已传构建号冲突。

服务端自动化资源

项目内的 backend/automation/ 是 Web 打包服务唯一使用的自动化资源:默认皮肤和混淆工具都在此处维护。分支源码中的 AutoPacking/ 不会被复制、执行或修改,仍可供开发者手动打包使用。

Watchdog 配置

变量 默认值 说明
WATCHDOG_INTERVAL 30 健康检查间隔(秒)
WATCHDOG_TIMEOUT 10 HTTP 健康检查超时(秒)
WATCHDOG_STARTUP_GRACE 10 首次检查前的宽限(秒),避开服务正常启动窗口,防止误重启
STARTUP_READY_TIMEOUT 20 start 后等待 /api/health 就绪的最长时间(秒)
WATCHDOG_RESTART_MODE nohup 重启方式:nohupPID 文件托管)或 launchd(用 launchctl kickstart 交回 launchd 托管,避免与 KeepAlive 抢端口)
MAX_RESTART_ATTEMPTS 3 连续重启上限,超过后进入冷却
COOLDOWN_SECONDS 300 冷却时间(秒)

服务管理

所有服务管理通过 deploy.sh 完成:

./deploy.sh <command>

命令一览

命令 说明
up 一键部署build + start + watchdog(强制重来),启动后等待就绪
build 构建前端、创建 Python 虚拟环境、安装所有依赖
start 后台启动服务(强制重启并等待 /api/health 就绪;端口被非本项目进程占用会报错)
stop 停止服务和 watchdog
restart 重启服务
status 查看服务和 watchdog 运行状态
test 一键运行全部测试(后端 + 前端)
watchdog 启动健康监测(后台守护)
watchdog-stop 停止健康监测
watchdog-fg 前台运行健康监测(调试用)

常用操作

# 首次部署(一键:build + start + watchdog
./deploy.sh up

# 查看状态
./deploy.sh status

# 更新代码后重新部署
git pull
./deploy.sh up

# 单独启用健康监测
./deploy.sh watchdog

# 停止所有服务
./deploy.sh stop

日志

日志文件位于 logs/ 目录:

logs/
├── server.log           # 服务运行日志
├── watchdog.log         # Watchdog 操作日志(重启记录等)
├── server-stdout.log    # launchd 模式下的标准输出
├── server-stderr.log    # launchd 模式下的标准错误
├── watchdog-stdout.log  # launchd watchdog 标准输出
└── watchdog-stderr.log  # launchd watchdog 标准错误

多用户管理

系统支持多用户管理,基于 JWT 认证。首次启动时自动创建 .env 中配置的管理员账号。

用户角色

角色 权限
管理员 所有功能 + 用户管理(增删改查、角色切换)
普通用户 打包、查看历史、管理 Apps 配置(不可修改版本号及其余配置、不可管理用户)

管理用户

在管理后台「用户管理」页面(仅管理员可见):

  • 创建用户:设置用户名、密码(至少 6 位)、是否管理员
  • 修改密码:管理员可修改任意用户密码
  • 切换角色:在管理员/普通用户之间切换
  • 删除用户:不能删除自己

认证流程

  1. POST /api/auth/login — 用户名+密码登录,返回 JWT token
  2. 后续请求在 Authorization: Bearer <token> 头中携带 token
  3. Token 有效期 24 小时,过期需重新登录

Watchdog 健康监测

Watchdog 定期检查服务健康状态,发现异常自动重启。

检测逻辑

每 N 秒执行一次
    │
    ├─ 进程是否存在? (kill -0)
    │   └─ 否 → 重启
    │
    └─ HTTP /api/health 返回 200
        ├─ 是 → 正常,继续轮询
        └─ 否 → 重启

重启保护

  • 连续失败次数 <= MAX_RESTART_ATTEMPTS:立即重启
  • 连续失败次数 > MAX_RESTART_ATTEMPTS:进入冷却期(COOLDOWN_SECONDS 秒),冷却结束后重置计数器重新尝试

运行方式

# 后台守护(推荐)
./deploy.sh watchdog

# 前台运行(调试,可看到实时输出)
./deploy.sh watchdog-fg

# 查看 watchdog 日志
tail -f logs/watchdog.log

# 停止 watchdog
./deploy.sh watchdog-stop

macOS 开机自启

通过 macOS 原生的 launchd 实现开机自启和进程守护。

安装

# 前置条件:先完成构建
./deploy.sh build

# 安装服务
./deploy/install-service.sh install

安装后会注册两个 launchd 服务:

服务 说明
com.readoor.buildserver 主服务,进程崩溃自动拉起
com.readoor.buildserver.watchdog 健康监测,检测 HTTP 卡死并重启

卸载

./deploy/install-service.sh uninstall

手动管理 launchd 服务

# 查看服务状态
launchctl list | grep readoor

# 手动停止
launchctl unload ~/Library/LaunchAgents/com.readoor.buildserver.plist
launchctl unload ~/Library/LaunchAgents/com.readoor.buildserver.watchdog.plist

# 手动启动
launchctl load ~/Library/LaunchAgents/com.readoor.buildserver.plist
launchctl load ~/Library/LaunchAgents/com.readoor.buildserver.watchdog.plist

开发模式

一键启动本地开发环境(后端 + 前端同时运行):

./start.sh
  • 前端界面:http://localhost:3000(自动代理 API 请求到后端)
  • 后端 APIhttp://localhost:8000
  • 前端修改自动热更新,后端修改自动 reload
  • Ctrl+C 同时停止所有服务

推送代码到 Git 服务器

本项目托管在自建 Git 服务(origin 走 HTTPS),推送需要账号凭据:

# 提交改动
git add -A
git commit -m "feat: 描述你的改动"

# 推送到远程 main
git push origin main

首次推送会提示输入用户名和密码/访问令牌。若不想每次都输入,可启用 macOS 钥匙串缓存凭据(只需配置一次):

git config --global credential.helper osxkeychain

配置后,下一次 git push 输入的账号密码会被安全保存到钥匙串,后续免输入。

若 Git 服务使用访问令牌认证:用户名填账号、密码填令牌即可。


自动化测试

运行测试

# 一键运行全部测试(后端 89 + 前端 35 = 124 个)
./deploy.sh test

# 只运行匹配的测试
./deploy.sh test -k test_login

# 单独运行后端测试
.venv/bin/python -m pytest tests/ -v

# 单独运行前端测试
cd frontend && npx vitest run

测试覆盖

测试文件 覆盖内容 数量
test_api_health.py 健康检查 1
test_api_auth.py 登录认证 3
test_api_config.py App/Scheme/Branch/Server/Build 配置 CRUD 20
test_api_tasks.py 任务创建/列表/详情/取消 9
test_api_apps.py 打包选择接口 3
test_websocket.py 日志流订阅/发送/完成/清理 10
test_downloads.py dSYM/混淆映射/二维码下载 11
test_build_service.py 源码更新/拷贝/配置生成/目录清理 11
test_build_queue.py 并发控制/任务执行/取消 7
test_edge_cases.py 边界情况/无效参数/配置备份 14
App.test.js 登录/退出/导航 8
BuildView.test.js 打包表单/任务列表/提交 8
ConfigView.test.js 配置管理/分支增删/设置保存 8
HistoryView.test.js 历史列表/过滤/下载/二维码 11

项目结构

BuildServer/
├── .env.example            # 环境变量模板
├── .env                    # 实际配置(不入库)
├── .gitignore
├── README.md               # 项目文档
├── deploy.sh               # 部署与管理脚本
├── start.sh                # 开发模式启动脚本(一键启动前后端)
├── requirements.txt        # Python 依赖
├── requirements-dev.txt    # 测试依赖(pytest, httpx
├── pytest.ini              # pytest 配置
│
├── deploy/                 # 部署相关
│   └── install-service.sh  # macOS launchd 服务安装
│
├── tests/                  # 后端测试(pytest
│   ├── conftest.py         # 测试 fixtures
│   ├── test_api_*.py       # API 接口测试
│   ├── test_websocket.py   # WebSocket 测试
│   ├── test_downloads.py   # 文件下载测试
│   ├── test_build_service.py # 打包服务测试
│   ├── test_build_queue.py # 队列测试
│   └── test_edge_cases.py  # 边界情况测试
│
├── backend/                # 后端(FastAPI
│   ├── main.py             # 入口,路由注册,静态文件服务
│   ├── config.py           # 配置加载(读取 .env
│   ├── database.py         # SQLAlchemy 数据库初始化
│   ├── models.py           # 数据库模型(Task, BuildConfig, User
│   ├── schemas.py          # Pydantic 请求/响应模型
│   ├── deps.py             # JWT 认证依赖
│   ├── routers/
│   │   ├── auth.py         # 登录认证(JWT
│   │   ├── users.py        # 用户管理(仅管理员)
│   │   ├── config.py       # 配置管理(App/Scheme/Server/Upload/Branch/Version CRUD
│   │   ├── apps.py         # 打包选择(只读)
│   │   └── tasks.py        # 任务管理(创建/列表/取消/下载)
│   └── services/
│       ├── build_service.py # 打包核心流程
│       ├── build_queue.py   # 异步任务队列
│       └── log_streamer.py  # WebSocket 日志流
│
└── frontend/               # 前端(Vue 3 + Vite
    ├── vite.config.js       # 构建配置,开发代理
    ├── vitest.config.js     # 前端测试配置
    ├── index.html
    └── src/
        ├── App.vue          # 根组件(导航/登录)
        ├── router/index.js  # 路由
        ├── composables/
        │   └── useWebSocket.js
        ├── views/
        │   ├── BuildView.vue    # 打包页面
        │   ├── HistoryView.vue  # 历史记录
        │   └── ConfigView.vue   # 管理后台
        └── __tests__/           # 前端测试(vitest
            ├── App.test.js
            ├── BuildView.test.js
            ├── ConfigView.test.js
            └── HistoryView.test.js

常见问题

Q: 打包时提示 "未找到 xcodebuild"

确保安装了 Xcode 和 Command Line Tools

xcode-select --install
xcodebuild -version

Q: 前端页面打不开

检查前端是否已构建:

ls backend/static/index.html  # 应该存在
# 如果不存在:
./deploy.sh build
./deploy.sh restart

Q: 端口被占用

修改 .env 中的 BACKEND_PORT,然后重启:

vim .env
./deploy.sh restart

Q: 打包目录占满磁盘

调整保留时间或手动清理:

# 缩短保留时间
vim .env  # BUILD_DIR_RETENTION_HOURS=12
./deploy.sh restart

# 手动清理所有打包目录
rm -rf /path/to/build/output/build_readoor_*

Q: Watchdog 频繁重启

查看 watchdog 日志定位原因:

tail -50 logs/watchdog.log
tail -50 logs/server.log

常见原因:

  • GIT_SOURCE_BASE 下对应分支目录不存在,且未配置 GIT_REMOTE_URL
  • 磁盘空间不足
  • Python 依赖缺失(重新执行 ./deploy.sh build

Q: 如何修改管理员密码

方法一:通过管理后台「用户管理」页面修改(推荐)

方法二:编辑 .env 后重启(仅影响初始管理员账号):

ADMIN_PASSWORD=new_password
./deploy.sh restart

注意:.env 中的管理员账号仅在首次启动时创建,之后的密码修改请通过管理后台操作。

Q: 多台 Mac 部署

每台 Mac 上:

git clone <repo>
cd BuildServer
cp .env.example .env
# 编辑 .env 配置路径和密码
./deploy.sh build
./deploy.sh start
./deploy.sh watchdog

各机器独立运行,配置互不影响。

Q: 如何按分支打包

  1. .env 中配置 GIT_SOURCE_BASEGIT_SOURCE_DIRGIT_REMOTE_URL
  2. 在管理页面「分支管理」中添加需要打包的分支(默认已有 main
  3. 打包时在「代码分支」下拉框中选择分支
  4. 首次打包会初始化共享源码目录;每次任务会串行 git fetch、切换目标分支、清理并复制源码快照
  5. 快照复制完成后,依赖安装和 Xcode 打包仍在独立目录中并行执行

Q: 源码目录占满磁盘怎么办

# 远程仓库模式下只有一个共享工作目录
du -sh /path/to/ReadoorBranches/workspace

# 打包目录由 BUILD_DIR_RETENTION_HOURS 自动清理;可查看其占用
du -sh /path/to/build/build_readoor_*

共享工作目录由服务在每次打包前自动同步和清理。请勿在服务运行期间手动删除或修改该目录。

Q: 如何运行测试

# 一键运行全部测试(后端 + 前端)
./deploy.sh test

# 只运行后端测试
.venv/bin/python -m pytest tests/ -v

# 只运行前端测试
cd frontend && npx vitest run

# 运行指定测试
./deploy.sh test -k test_login