Files
iOSBuildServer/README.md
T
shenleiandClaude Opus 5 9b4667cab5 refactor: master 沿用 release 轨道名,develop 用 develop,feature 用 feature
上一版把 master 的轨道也改名成 master,会把历史构建号搬到新键上。
改为 master/main 继续用现有的 release 轨(build_map 原地保留),
只有 develop 等其它上架分支新建以分支名命名的轨道并复制一份构建号。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJaZ913U5GVdTkYHiRpvDU
2026-09-02 10:39:52 +09:00

666 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# iOS 自动打包服务
基于 FastAPI + Vue 3 的 iOS 应用自动打包服务,支持 Ad_Hoc 和 App_Store 两种打包类型,提供 Web 界面操作、实时日志流、构建历史管理、多用户权限和健康监测。
## 目录
- [环境要求](#环境要求)
- [快速开始](#快速开始)
- [配置说明](#配置说明)
- [公网部署](#公网部署)
- [服务管理](#服务管理)
- [多用户管理](#多用户管理)
- [Watchdog 健康监测](#watchdog-健康监测)
- [macOS 开机自启](#macos-开机自启)
- [开发模式](#开发模式)
- [推送代码到 Git 服务器](#推送代码到-git-服务器)
- [自动化测试](#自动化测试)
- [项目结构](#项目结构)
- [常见问题](#常见问题)
---
## 环境要求
| 依赖 | 版本要求 | 说明 |
|------|----------|------|
| macOS | 11+ | 必须,xcodebuild 依赖 |
| Xcode | 14+ | 必须,含 Command Line Tools |
| Python | 3.9+ | 后端运行 |
| Node.js | 18+ | 前端构建 |
| CocoaPods | - | 项目已有 Podfile 时需要 |
## 快速开始
### 1. 克隆项目
```bash
git clone <repo-url> BuildServer
cd BuildServer
```
### 2. 配置环境变量
```bash
cp .env.example .env
vim .env
```
**建议优先检查的配置项:**
```bash
# 分支源码根目录,默认在当前项目下
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. 构建与启动
推荐一键部署(构建 + 启动 + 健康监测,一步到位):
```bash
# 一键部署:build + start + watchdog(强制重来)
./deploy.sh up
```
`up` 会自动构建前端、(强制)重启后端并**轮询 `/api/health` 直到就绪**才返回,再拉起 watchdog,最后打印状态。适合首次部署和更新代码后重新上线。
> ⚠️ `up`/`start` 采用「强制重来」语义:若检测到旧服务会先停再起,正在进行的打包任务会被中断。
也可以分步执行:
```bash
# 一键构建(安装依赖 + 构建前端)
./deploy.sh build
# 启动服务(等待就绪;端口被非本项目进程占用时会明确报错)
./deploy.sh start
```
启动后访问 `http://<服务器IP>:8000` 即可使用。
### 4.(可选)初始化源码目录
配置 `GIT_REMOTE_URL` 后,系统默认使用唯一的共享源码目录 `GIT_SOURCE_DIR`。任务会串行执行拉取、切分支、清理和复制,复制完成后在各自的打包目录中并行构建。
未配置远程仓库时,需要继续手动准备每个分支目录:
```bash
# 创建分支源码根目录
mkdir -p /path/to/ReadoorBranches
# 手动 clone 主分支(后续可继续手动维护)
git clone -b main git@github.com:org/repo.git /path/to/ReadoorBranches/main
```
配置远程仓库时,无需手动 clone;首次打包会初始化共享目录。
### 5.(可选)启用健康监测
```bash
./deploy.sh watchdog
```
### 6.(可选)设为开机自启
```bash
./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_PASSWORD``GIT_USERNAME` 使用该 Token 所属账号。不要在 `.env``config.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](/Users/shen/Work/Code/iOSBuildServer/deploy/nginx.conf.example) 作为模板。
公网 `.env` 至少需要如下配置:
```bash
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/443``APP_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` 作为初始配置。可从模板起步:
>
> ```bash
> 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 / main | App_Store 打包时按当前 App_Ver 自动 +1 |
| `develop` 等以分支名命名的上架轨 | develop、release-*、hotfix-* 等其它上架分支,每个分支一条 | 同上,与 `release` 轨互不影响 |
| `feature`(自测) | feature/* 等其他分支,共用一条 | 永不自增,始终使用配置页填写的值(默认 `.0` |
- 分支归属默认按名字推断(master/main → `release` 轨,develop/dev/release-*/hotfix-* → 以分支名命名的轨,其余 → `feature`),可在「分支管理」页逐个改;改成「上架」即为该分支建一条独立轨道。
- 「打包设置」页按上架分支分块填写版本号,最后一块是共用的自测版本号。
- 每条轨道内的构建号按 App_Ver 独立记录(`build_map`),回退到打过包的旧版本号时会从它自己的最大值继续,不会与 App Store Connect 上已传构建号冲突。
- App Store 的构建号唯一性按 App_Ver 全局判定,因此两个上架分支若临时用了同一个 App_Ver,递增时会跨轨道取最大值 +1,避免重号被拒。
- 旧配置(master/develop 共用的 `release` 轨)首次启动时自动拆开:master 留在 `release` 轨,develop 新建 `develop` 轨并复制一份已用构建号。
### 服务端自动化资源
项目内的 `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` | 重启方式:`nohup`PID 文件托管)或 `launchd`(用 `launchctl kickstart` 交回 launchd 托管,避免与 KeepAlive 抢端口) |
| `MAX_RESTART_ATTEMPTS` | `3` | 连续重启上限,超过后进入冷却 |
| `COOLDOWN_SECONDS` | `300` | 冷却时间(秒) |
---
## 服务管理
所有服务管理通过 `deploy.sh` 完成:
```bash
./deploy.sh <command>
```
### 命令一览
| 命令 | 说明 |
|------|------|
| `up` | **一键部署**build + start + watchdog(强制重来),启动后等待就绪 |
| `build` | 构建前端、创建 Python 虚拟环境、安装所有依赖 |
| `start` | 后台启动服务(强制重启并等待 `/api/health` 就绪;端口被非本项目进程占用会报错) |
| `stop` | 停止服务和 watchdog |
| `restart` | 重启服务 |
| `status` | 查看服务和 watchdog 运行状态 |
| `test` | 一键运行全部测试(后端 + 前端) |
| `watchdog` | 启动健康监测(后台守护) |
| `watchdog-stop` | 停止健康监测 |
| `watchdog-fg` | 前台运行健康监测(调试用) |
### 常用操作
```bash
# 首次部署(一键: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` 秒),冷却结束后重置计数器重新尝试
### 运行方式
```bash
# 后台守护(推荐)
./deploy.sh watchdog
# 前台运行(调试,可看到实时输出)
./deploy.sh watchdog-fg
# 查看 watchdog 日志
tail -f logs/watchdog.log
# 停止 watchdog
./deploy.sh watchdog-stop
```
---
## macOS 开机自启
通过 macOS 原生的 launchd 实现开机自启和进程守护。
### 安装
```bash
# 前置条件:先完成构建
./deploy.sh build
# 安装服务
./deploy/install-service.sh install
```
安装后会注册两个 launchd 服务:
| 服务 | 说明 |
|------|------|
| `com.readoor.buildserver` | 主服务,进程崩溃自动拉起 |
| `com.readoor.buildserver.watchdog` | 健康监测,检测 HTTP 卡死并重启 |
### 卸载
```bash
./deploy/install-service.sh uninstall
```
### 手动管理 launchd 服务
```bash
# 查看服务状态
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
```
---
## 开发模式
一键启动本地开发环境(后端 + 前端同时运行):
```bash
./start.sh
```
- 前端界面:`http://localhost:3000`(自动代理 API 请求到后端)
- 后端 API`http://localhost:8000`
- 前端修改自动热更新,后端修改自动 reload
- `Ctrl+C` 同时停止所有服务
---
## 推送代码到 Git 服务器
本项目托管在自建 Git 服务(`origin` 走 HTTPS),推送需要账号凭据:
```bash
# 提交改动
git add -A
git commit -m "feat: 描述你的改动"
# 推送到远程 main
git push origin main
```
首次推送会提示输入用户名和密码/访问令牌。若不想每次都输入,可启用 macOS 钥匙串缓存凭据(只需配置一次):
```bash
git config --global credential.helper osxkeychain
```
配置后,下一次 `git push` 输入的账号密码会被安全保存到钥匙串,后续免输入。
> 若 Git 服务使用**访问令牌**认证:用户名填账号、密码填令牌即可。
---
## 自动化测试
### 运行测试
```bash
# 一键运行全部测试(后端 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
```bash
xcode-select --install
xcodebuild -version
```
### Q: 前端页面打不开
检查前端是否已构建:
```bash
ls backend/static/index.html # 应该存在
# 如果不存在:
./deploy.sh build
./deploy.sh restart
```
### Q: 端口被占用
修改 `.env` 中的 `BACKEND_PORT`,然后重启:
```bash
vim .env
./deploy.sh restart
```
### Q: 打包目录占满磁盘
调整保留时间或手动清理:
```bash
# 缩短保留时间
vim .env # BUILD_DIR_RETENTION_HOURS=12
./deploy.sh restart
# 手动清理所有打包目录
rm -rf /path/to/build/output/build_readoor_*
```
### Q: Watchdog 频繁重启
查看 watchdog 日志定位原因:
```bash
tail -50 logs/watchdog.log
tail -50 logs/server.log
```
常见原因:
- `GIT_SOURCE_BASE` 下对应分支目录不存在,且未配置 `GIT_REMOTE_URL`
- 磁盘空间不足
- Python 依赖缺失(重新执行 `./deploy.sh build`
### Q: 如何修改管理员密码
**方法一**:通过管理后台「用户管理」页面修改(推荐)
**方法二**:编辑 `.env` 后重启(仅影响初始管理员账号):
```bash
ADMIN_PASSWORD=new_password
./deploy.sh restart
```
注意:`.env` 中的管理员账号仅在首次启动时创建,之后的密码修改请通过管理后台操作。
### Q: 多台 Mac 部署
每台 Mac 上:
```bash
git clone <repo>
cd BuildServer
cp .env.example .env
# 编辑 .env 配置路径和密码
./deploy.sh build
./deploy.sh start
./deploy.sh watchdog
```
各机器独立运行,配置互不影响。
### Q: 如何按分支打包
1.`.env` 中配置 `GIT_SOURCE_BASE``GIT_SOURCE_DIR``GIT_REMOTE_URL`
2. 在管理页面「分支管理」中添加需要打包的分支(默认已有 `main`
3. 打包时在「代码分支」下拉框中选择分支
4. 首次打包会初始化共享源码目录;每次任务会串行 `git fetch`、切换目标分支、清理并复制源码快照
5. 快照复制完成后,依赖安装和 Xcode 打包仍在独立目录中并行执行
### Q: 源码目录占满磁盘怎么办
```bash
# 远程仓库模式下只有一个共享工作目录
du -sh /path/to/ReadoorBranches/workspace
# 打包目录由 BUILD_DIR_RETENTION_HOURS 自动清理;可查看其占用
du -sh /path/to/build/build_readoor_*
```
共享工作目录由服务在每次打包前自动同步和清理。请勿在服务运行期间手动删除或修改该目录。
### Q: 如何运行测试
```bash
# 一键运行全部测试(后端 + 前端)
./deploy.sh test
# 只运行后端测试
.venv/bin/python -m pytest tests/ -v
# 只运行前端测试
cd frontend && npx vitest run
# 运行指定测试
./deploy.sh test -k test_login
```