在移动端和客户端开发这个圈子里,一直存在一个非常现实的矛盾:只有 Apple 硬件才能编译和签名 iOS 应用,但 Apple 硬件并不便宜,更不好远程管理。
很多团队的做法是,买一台 Mac mini 放在机柜里,有人需要打包的时候远程登录上去手动点 Xcode。遇到版本更新、证书过期、需要批量出包的时候,整个流程就变得非常痛苦。于是大家自然会想到引入 CI,但一搜方案,又撞上另一堵墙:GitHub Actions 的 macOS runner 是按分钟计费的,而且代码要推到 GitHub 上;Jenkins 倒是能自托管,但配置越来越重,插件版本、权限体系和 Pipeline 脚本维护成本都不低。
如果你正在用 Gitea,或者正打算在内网搭建一套轻量代码托管和 CI 服务,那么 Gitea Actions 值得认真看一下。这套方案可以让你把自己手里的 Mac 变成 CI 执行节点,直接在 Apple 硬件上跑 iOS 构建、macOS 打包、Xcode 测试这类任务;语法风格接近 GitHub Actions,团队迁移成本低,而且代码仓库、CI 状态、Runner 管理都在 Gitea 一个平台里完成。
这篇文章会从运行原理讲起,然后完整演示如何把一台 Mac 接入 Gitea Actions,最终跑通一个 iOS 构建工作流。读完你可以照着落地,也能避开常见的那几个坑。
1. 为什么要在 Apple 硬件上跑 Gitea Actions
先回答一个更根本的问题:为什么偏偏要把 CI 跑在 Apple 硬件上?
原因是移动端构建有硬性依赖。iOS 应用必须使用 Xcode 编译,而 Xcode 只能运行在 macOS 上;提交到 App Store 的分发包还需要签名,签名过程涉及证书和私钥,这些敏感信息放在可控的内网环境里更安全。也就是说,无论你选什么 CI 系统,最终编译那一步大概率还是得落到一台 Mac 上。
对比一下几种常见方案的处境:
- GitHub Actions 托管 macOS runner:方便,但按分钟计费,且默认策略需要你把代码放到 GitHub。对于企业内部项目、未公开代码或合规要求高的环境,这条路通常走不通。
- 自建 Jenkins + macOS 节点:老牌方案,功能强大,但 Jenkins 的安装、插件管理、权限配置、节点标签、Pipeline 脚本都有一定使用门槛。团队如果只想“快速跑一个打包脚本”,Jenkins 显得偏重。
- GitLab CI + macOS runner:也是一个成熟方案,注册 Runner 后用 tag 控制任务分发,很多团队在用。不过如果仓库本身已经迁到 Gitea,再单独维护一套 GitLab 就不太合理了。
Gitea Actions 的价值在于它把“代码托管、CI 触发、Runner 管理、日志查看”整合在了同一个轻量系统里。Gitea 本身非常轻量,一台 2 核 4G 的小机器就能长期稳定运行,Actions 功能开启后,开发者只需要在仓库里放一个.gitea/workflows/*.yml文件,推送代码就能自动触发构建。这套交互方式对用过 GitHub Actions 的开发者来说几乎是零成本。
这篇文章的核心判断是:如果你的团队已经有 Mac 设备、又在用 Gitea,那么 Gitea Actions 是当前把 Apple 硬件变成 CI 节点最平滑的方案之一。它的成本主要是前期一次性的 Runner 接入,收益是后续每次提交都能自动完成构建和检查。
2. Gitea Actions 的运行模型与核心概念
在动手操作之前,有必要把 Gitea Actions 的几个核心概念讲清楚,否则后面排查问题时容易一头雾水。
2.1 四个关键组件
Gitea Actions 的架构可以拆成四个部分:
- Gitea 服务端:负责存储仓库、提供 Web 界面、接收 Git 事件,并把事件转换成 CI 任务放进队列。
- act_runner:一个独立进程,部署在某台机器上,负责不断向 Gitea 服务端询问“有没有任务需要执行”。
- 工作流文件:存放在仓库
.gitea/workflows/目录下的 YAML 文件,描述“在什么事件触发下,执行哪些步骤”。 - 执行环境:act_runner 拿到任务后,在指定环境中执行工作流步骤。这个环境可以是容器,也可以是 Runner 所在的宿主机。
用通俗的比喻来说:Gitea 是“调度中心”,act_runner 是“快递员”,工作流文件是“配送单”,执行环境就是快递员脚下的“交通工具”。
2.2 label 是什么,为什么它决定任务能否跑起来
act_runner 启动时会向 Gitea 服务端报告自己支持哪些 label。工作流里的runs-on字段会指定这次任务需要哪个 label,Gitea 会找到匹配的 Runner 并分配任务。
举个例子:
- 某台 Linux 机器上的 Runner 声明支持
ubuntu-latest、linux/amd64; - 某台 Mac mini 上的 Runner 声明支持
macos-arm64、macos-latest; - 工作流里写
runs-on: macos-arm64,任务只会被分配给 Mac mini 上的 Runner。
很多初次使用 Gitea Actions 的用户遇到“任务一直排队,但 Runner 明明在线”的问题,十有八九就是 label 不匹配。这一点在 Apple 硬件场景尤其重要,因为 macOS Runner 默认的 label 往往需要手动指定成平台相关的名称。
2.3 与 GitHub Actions 的兼容性边界
Gitea Actions 的底层执行引擎是 act(nektos/act 的一个 fork/兼容实现),它的设计目标是尽量兼容 GitHub Actions 的语法。也就是说:
name、on、jobs、steps这些核心结构可以直接沿用;actions/checkout、actions/upload-artifact这类常见 action 大部分可用;- 但部分 GitHub 云服务专属能力,比如一些深度依赖 GitHub API 的 action、需要访问 GitHub Cache Service 的缓存逻辑,在 Gitea 上行为可能不同。
所以更稳妥的工作流写法是:把重心放在run脚本上,减少对第三方 action 的依赖,尤其是那些和 GitHub 账号体系绑定的 action。这对在 macOS 上跑 Xcode 构建来说问题不大,因为核心步骤通常就是几条xcodebuild命令。
下表总结了常见平台和 Gitea Actions 的定位差异:
| 对比维度 | GitHub Actions | GitLab CI | Jenkins | Gitea Actions |
|---|---|---|---|---|
| 仓库托管 | GitHub | GitLab | 无,独立 CI | Gitea |
| 托管 macOS Runner | 有,按分钟计费 | 有,配套收费 | 无 | 无,需自建 Runner |
| 内网部署 | 不支持 | 支持 | 支持 | 支持 |
| 工作流语法 | YAML | YAML | Pipeline/脚本 | GitHub Actions 风格 YAML |
| 资源占用 | 无需自维护 | 相对较重 | 较重 | 很轻 |
3. 在 Apple 硬件上运行 act_runner:模式与前置条件
这一节先解决两个问题:act_runner 在 macOS 上到底怎么执行任务,以及开始之前需要准备哪些东西。
3.1 Docker 模式还是宿主机模式
act_runner 默认支持容器化执行任务,也就是把每个 Job 放到一个临时容器里运行。这种方式隔离性好,适合 Linux 环境,比如你用一台服务器跑后端测试任务。
但到了 Apple 硬件场景,情况不同:iOS 构建必须用到 Xcode、模拟器、钥匙串、签名证书,这些在 macOS 的 Docker 容器里很难完整映射。更常见的做法是让 act_runner 直接以宿主机模式执行任务——也就是 Job 里的run命令直接在 macOS 系统上执行。这样xcodebuild能正常访问 Xcode 工具链,签名时也能访问钥匙串。
宿主机模式的代价是隔离性变差:只要被授予了运行权限的仓库,其工作流代码就能直接读写这台 Mac 的文件系统和部分系统配置。所以一个基本原则是:不要把不信任的仓库指向这台 Runner,更不能把公网可提交的仓库挂在宿主机模式的 Runner 上。
3.2 前置条件清单
部署之前,建议先确认以下条件:
- 一台可用的 Mac,芯片类型确认清楚:Apple Silicon 还是 Intel,这直接影响后面的 label 命名;
- macOS 版本尽量满足 Xcode 的要求;
- 已安装 Xcode,并且在终端里执行
xcode-select -p能正常输出路径; - 已安装 Git,macOS 自带的
git一般够用; - 一个可访问的 Gitea 服务端实例,版本需要支持 Actions 功能;
- 能创建一个 Gitea 访问令牌或通过后台注册 Runner。
补充一点:如果只想在 Linux 环境下体验 Gitea Actions,并不需要 Apple 硬件;但如果你要跑 iOS 构建,前面这些条件缺一不可。也有人在 Windows 上跑 Gitea,配合 Windows Runner 做 .NET 构建,那属于另一套配置,不在本文展开。
3.3 在 Gitea 服务端开启 Actions
在较新版本的 Gitea 中,Actions 功能通常需要在配置文件app.ini中显式开启。具体配置项会随版本变化,建议以你部署的 Gitea 版本官方文档为准。基本思路是:
[actions] ENABLED = true修改配置后需要重启 Gitea 服务。如果你是从旧版本升级,还需要检查实例是否已经初始化相关数据表。这一部分如果配置不对,Web 界面里不会出现 Actions 相关入口。
3.4 用户 SSH 密钥管理
由于私有仓库的克隆通常走 SSH,Runner 在宿主机上 checkout 代码时需要用 SSH 密钥。建议在 Gitea 后台为专门的 CI 账号(例如ci-bot)配置 SSH 公钥,并把私钥放到 Mac Runner 的~/.ssh/目录下,或者在 checkout 阶段使用 HTTP 方式并在 URL 中嵌入令牌。
Gitea 的用户 SSH 密钥管理很简单:登录账号后进入“设置 -> SSH 密钥”,把公钥粘进去保存即可。不要用个人账号的高权限密钥直接放在 CI 机器上,更不要把私钥提交到仓库。
4. 部署 act_runner 到 Mac 的完整步骤
前置条件确认无误后,开始把 act_runner 装到 Mac 上。
4.1 下载或安装 act_runner
act_runner 是 Gitea 官方提供的 Runner 程序。可以到 Gitea 官方 Releases 页面下载对应 macOS 架构的二进制包,也可以使用 Homebrew 等包管理工具安装。考虑到网络环境差异,这里直接使用二进制方式演示:
# 假设已下载 act_runner 并放到 /usr/local/bin 下 chmod +x act_runner sudo mv act_runner /usr/local/bin/act_runner # 验证版本 act_runner --version如果你的 Gitea 服务端是通过 Docker 部署的,act_runner 也可以作为容器启动。但如前面所说,在 macOS 上跑 iOS 构建建议用宿主机模式,因此这里直接以二进制方式运行。
4.2 在 Gitea 后台创建 Runner 注册令牌
登录 Gitea 实例,进入“管理后台 -> Runner”或“站点管理 -> Actions”,找到创建 Runner 的入口,生成一个新的注册令牌。这一步生成的 token 只用于初始注册,并不会长期保存在 Runner 配置里。
如果找不到管理入口,可以确认一下当前账号是否有管理员权限,以及 Gitea 版本是否已开启 Actions。
4.3 注册 Runner 并指定 label
在 Mac 终端执行注册命令:
act_runner register \ --instance http://gitea.example.com \ --token 这里填你的注册令牌 \ --name mac-mini-1 \ --labels macos-arm64 \ --no-interactive注意几点:
--instance是 Gitea 服务端地址,内网环境请使用内网地址,避免流量绕行;--labels这里非常关键,务必根据 Mac 芯片类型填写。Apple Silicon 写macos-arm64,Intel 写macos-amd64;--no-interactive表示非交互式注册,适合脚本化执行。
注册成功后,当前目录会生成一个config.yaml文件,这个文件保存了 Runner 的详细配置。
4.4 检查并调整 config.yaml
打开config.yaml,重点看 Runner 的 label 和执行模式。由于不同版本配置项有差异,这里只说明通用思路。需要确认 labels 和注册时一致,同时如果希望任务直接在宿主机执行,需要确保执行模式不是强制容器模式。
一个常见问题是:注册时把 label 写成了linux/amd64,导致工作流里写macos-arm64时找不到 Runner。如果发现 label 写错,可以直接修改config.yaml后重启 Runner,或者用--reset参数重新注册。
4.5 启动 Runner 并保持后台运行
前台启动方便调试:
act_runner daemon --config config.yaml看到类似“runner started successfully”的日志后,说明 Runner 已连接到 Gitea。
要长期运行,可以把它注册为 launchd 服务,或者用nohup放到后台。考虑到 Mac 重启后 CI 服务最好能自动恢复,简单起见可以写一个全局 launchd plist,也可以先用 nohup:
nohup act_runner daemon --config config.yaml >> runner.log 2>&1 &更推荐的做法是配置 launchd,让 Runner 在开机后自动拉起。如果你习惯用 Linux,也可以把 Runner 放到一台 Linux 机器上用 systemd 管理,但执行任务的 Mac 和运行 act_runner 的机器需要在同一个可访问网络内。
4.6 验证 Runner 在线状态
回到 Gitea 管理后台的 Runner 列表页面,应该能看到名为mac-mini-1的 Runner,状态为在线。如果显示离线,优先检查:
- act_runner 进程是否还活着;
config.yaml里的 Gitea 地址是否可达;- 注册 token 是否过期。
到这里,Apple 硬件接入 Gitea Actions 的基础工作已经完成。下一步开始写工作流。
5. 编写第一个跑在 macOS 上的 Actions 工作流
在仓库根目录创建.gitea/workflows/ios-build.yml,写入一个最小但完整的 iOS 构建工作流。
5.1 最小可运行示例
name: iOS Build on: push: branches: - main jobs: build: runs-on: macos-arm64 steps: - name: Checkout uses: actions/checkout@v4 - name: Show Xcode version run: | xcodebuild -version xcode-select -p - name: Build without signing run: | xcodebuild build \ -project MyApp.xcodeproj \ -scheme MyApp \ -configuration Release \ -destination 'generic/platform=iOS' \ CODE_SIGNING_ALLOWED=NO这段工作流的关键点:
runs-on: macos-arm64必须与 Runner 注册时的 label 完全一致;actions/checkout@v4用于拉取代码,如果你的内网环境访问 GitHub 不稳定,建议使用 Gitea 兼容的 checkout 方式,或者手动执行git clone;CODE_SIGNING_ALLOWED=NO适用于先验证编译是否通过,不代表最终打包可以省略签名。
5.2 增加归档和导出 IPA 的完整流程
实际发布场景下,通常需要导出可分发的 IPA 文件。下面示例增加了 archive 和 exportArchive 两步:
name: iOS Archive and Export on: push: tags: - 'v*' workflow_dispatch: jobs: archive: runs-on: macos-arm64 steps: - name: Checkout uses: actions/checkout@v4 - name: Archive run: | mkdir -p build xcodebuild archive \ -project MyApp.xcodeproj \ -scheme MyApp \ -configuration Release \ -archivePath build/MyApp.xcarchive \ CODE_SIGNING_ALLOWED=NO - name: Export IPA run: | xcodebuild -exportArchive \ -archivePath build/MyApp.xcarchive \ -exportOptionsPlist ExportOptions.plist \ -exportPath build/ipa - name: Upload IPA uses: actions/upload-artifact@v4 with: name: MyApp-ipa path: build/ipa/*.ipa如果项目还没有导出选项文件,需要先在 Xcode 中生成一份ExportOptions.plist,或者手动创建并指明导出方式(App Store / Ad Hoc / Development)。
5.3 处理签名证书
签名是 iOS 自动构建里绕不开的一步。常见做法是:
- 把
.p12证书文件和描述文件放到 Runner 机器的一个安全目录,或者作为 Base64 环境变量传入; - 在构建前把证书导入钥匙串,并设置钥匙串为默认搜索路径;
- 在
xcodebuild命令中指定DEVELOPMENT_TEAM和PROVISIONING_PROFILE_SPECIFIER。
示例片段:
- name: Import signing certificate env: CERT_BASE64: ${{ secrets.CERT_BASE64 }} CERT_PASSWORD: ${{ secrets.CERT_PASSWORD }} run: | echo "$CERT_BASE64" | base64 --decode > /tmp/signing.p12 security create-keychain -p ci temp.keychain security import /tmp/signing.p12 -k temp.keychain -P "$CERT_PASSWORD" -A security set-key-partition-list -S apple-tool:,apple: -k ci temp.keychain security list-keychains -d user -s temp.keychain这里涉及很多团队特定的证书管理方式,需要根据实际情况调整。注意secrets需在 Gitea 仓库或组织设置中提前配置,并且不要把证书密码写死在 YAML 里。
5.4 把工作流放到 Gitea 后如何触发
将 YAML 文件提交并推送后,on.push会立即触发一次任务。也可以先不写触发事件,使用 Gitea Web 界面上的 “Run workflow” 按钮手动触发(前提是配置了workflow_dispatch)。
6. 运行结果与效果验证
工作流提交后,在 Gitea 仓库页面的 “Actions” 标签页里,应该能看到新出现的运行记录。点击进入可以看到 job 列表和每个 step 的实时日志。
正常情况下,你会看到:
- Checkout 步骤成功结束;
- xcodebuild 输出 Xcode 版本信息;
- Build without signing 步骤返回
BUILD SUCCEEDED; - 如果配置了 artifact 上传,运行结束后可以下载到 IPA 或 xcarchive。
如果 Runner 一直处于等待状态,优先检查runs-on标签是否与 Runner 注册的 label 一致。在线 Runner 但任务无人接收,大多是这种情况下发生的。
在 Mac 终端也可以观察 act_runner 的实时日志。使用tail -f runner.log能看到 Runner 是否收到任务、是否开始执行工作流、每个 step 的开始和结束时间。这一步对排查问题非常有帮助,比反复刷新 Web 页面更直接。
7. 常见问题与排查思路
以下是在 Apple 硬件上跑 Gitea Actions 时最容易遇到的问题,按经验整理成表格。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Runner 在线,但任务一直 pending | 工作流runs-on与 Runner label 不一致 | 查看 Runner 详情页的 label;检查 YAML 中的runs-on | 修改 YAML 或重新注册 Runner,保证两者完全匹配 |
| Runner 显示离线 | 进程退出、网络不通、token 失效 | 查看 act_runner 日志;ping Gitea 地址 | 重新启动 daemon;必要时重新注册 |
| Checkout 失败,提示无权限 | SSH 密钥未配置或密钥权限过高 | 在 Runner 机器执行 git 克隆测试 | 为 CI 账号配置 SSH 公钥;或使用内置令牌 HTTP 克隆 |
| 提示 xcodebuild 找不到 | Command Line Tools 指向错误 | 执行xcode-select -p | 执行sudo xcode-select -s /Applications/Xcode.app/Contents/Developer |
| Build 报签名相关错误 | 未配置签名参数、证书未导入 | 查看签名 Error 中的具体描述 | 先用CODE_SIGNING_ALLOWED=NO验证编译;再补签名配置 |
| Job 在容器中执行,找不到 Xcode | 使用了容器模式跑 macOS 任务 | 检查 act_runner 执行模式配置 | 改为宿主机模式,并确保工作流使用宿主机 Runner |
| Action 内部的脚本使用 GitHub API 失败 | 第三方 action 依赖 GitHub 云服务 | 查看具体 action 的日志 | 换成 Gitea 兼容的 action,或直接用run写命令 |
| 导出的 IPA 缺少描述文件 | ExportOptions.plist 配置不正确 | 对比 Xcode 本地导出配置 | 从 Xcode 重新生成 ExportOptions.plist |
排查时有一条通用路径:先看 Gitea Web 日志,再看 act_runner 日志,最后到 Mac 本机手动执行 Xcode 命令。把手工能跑通的命令一步一步还原到工作流里,问题通常出在环境变量、PATH 或密钥访问权限上。
8. 最佳实践与工程建议
8.1 label 命名规范
建议从一开始就建立一套清晰的 label 规则,避免 Runner 一多就混乱。例如:
macos-arm64:Apple Silicon 通用任务;macos-amd64:Intel Mac 通用任务;macos-arm64-ios:专门承担 iOS 构建的 Apple Silicon 节点。
工作流中使用更具体的 label,可以有效避免任务被调度到不合适的机器上。
8.2 权限与安全边界
宿主机模式的 Runner 拥有较高系统权限,必须严格控制能触发任务的仓库范围。建议:
- 将 Runner 注册在组织级或仓库级,而不是所有仓库共享;
- 不使用个人高权限账号的 token 存放 Runner;
- 定期轮换 Runner 注册令牌和 CI 机器人账号的访问令牌;
- 存放证书和私钥的目录只允许 CI 机器人账号读取。
8.3 不要在一台 Mac 上跑过多并发
Apple 芯片性能虽强,但 iOS 构建是 CPU、磁盘高度密集的任务。如果一台 Mac 同时被多个 job 占满,会出现构建时间飙升、模拟器资源冲突等问题。建议在 Gitea Runner 配置中设置并发数限制,给每台机器留出余量。
8.4 与现有 CI/CD 链路的衔接
很多团队已经有 Jenkins 或 Drone,也有的在用 Gitea 配合 Harbor、Docker 和 Nginx 搭建完整发布链路。Gitea Actions 适合作为这整条链路里的“触发与构建”环节:
- Gitea 负责代码托管和事件触发;
- Actions 负责在 Mac 上完成 iOS/macOS 构建,或者在后端仓库里完成 Java 包构建、Docker 镜像构建;
- 产物可以推送到 Harbor 镜像仓库或内部文件服务;
- 部署环节可以继续由 Drone、Jenkins 或专门的发布系统负责。
如果你的团队目前是 “Jenkins + Gitea 实现 Spring Boot 打包部署”,不要急于推翻 Jenkins,可以先从 iOS 构建这种 Jenkins 配置成本高的任务切入,逐步把 Gitea Actions 引入流程。
8.5 日常维护清单
- 定期检查 macOS 系统更新和 Xcode 版本兼容性;
- 为 Runner 机器保留健康监控,磁盘不足时构建会莫名失败;
- 备份
config.yaml和 CI 机器人账号的配置; - Gitea 服务端升级前,先确认新版本对 Actions 的兼容性。
9. 总结与后续实践方向
这篇文章从“为什么 CI 需要 Apple 硬件”这个痛点出发,讲清楚了 Gitea Actions 的架构、act_runner 的执行模式、label 匹配机制,以及如何把一台 Mac 完整接入 Gitea 并跑通 iOS 构建工作流。核心结论是:Gitea Actions 真正降低的是自托管 CI 的接入门槛,尤其是对已有 Mac 设备和 Gitea 仓库的团队,可以用很少的成本获得一套接近 GitHub Actions 使用体验的构建系统。
下一步建议你从最小示例开始,先提交一个只打印 Xcode 版本的工作流,确认 Runner 调度正常,再逐步加入签名、归档、导出和产物上传。跑通之后,可以继续研究这套体系下的缓存策略、私有 action 管理、多 Runner 调度和与 Harbor/Drone 的联动。自托管 CI 的安全边界始终不能放松,Runner 一旦暴露给不可信任务,风险是真实存在的。