十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Openship Actions:在自有服务器上运行仓库自身的 GitHub Actions 工作流

Openship Actions:在自有服务器上运行仓库自身的 GitHub Actions 工作流 Openship Actions在自有服务器上运行仓库自身的 GitHub Actions 工作流【免费下载链接】openshipSelf-hosted deployment platform项目地址: https://gitcode.com/GitHub_Trending/ope/openshipOpenShip 的 Actions 本地 Runner 是一套正在设计中文档状态为 proposed的模块方案让部署平台直接读取并执行仓库自带的.github/workflows/*.yml把运行结果以 Check Runs 的形式镜像回 GitHub使 PR / 提交页面的状态展示与 GitHub 托管运行完全一致。本文基于 docs/actions-local-runner.md 展开结合仓库中已沉淀的容器执行原语、GitHub Checks 封装、部署检测等源码实现逐层拆解其架构决策、端到端运行流程、表达式引擎、uses:兼容策略、数据模型与实施阶段帮助你理解做一个 GitHub Actions 兼容 Runner真正的成本中心在哪里。1. 核心设计决策与 GitHub 共用同一份工作流文件整个方案的第一条原则是GitHub 读取的工作流文件就是 OpenShip 读取的工作流文件不发明任何新配置格式。决策点结论理由配置格式原样采用.github/workflows/*.yml与 GitHub 使用同一套文件是全部意义所在——仓库保持可移植用户可以随时迁走项目级覆盖存 DB 行绝不放入仓库文件一个.openship/actions.json会分裂事实源source of truth并迫使仓库提交 OpenShip 专属改动启用/禁用、runner 镜像映射、密钥绑定都放在action_workflow/ 项目设置里UI 归属独立于 Jobs 标签页的项目级Actions标签页见第 3 节对 jobs 模块的分析执行底座容器底座DockerRuntimedocker-exec-stream复用仓库已调试好的容器执行链而不是 jobs 的 SSH 执行器步骤隔离每个 job 一个容器每个 step 一次docker execstep 之间必须共享文件系统、$GITHUB_PATH与已安装的软件包容器-per-step 会让apt-get install的持久化失效。这正是act采用的模型v1 托管模式仅自托管云端需要一个可计费、隔离的 runner 池加一套 docker-in-docker 方案见第 9 节风险以PLATFORM_FEATURES特性开关门控这套决策的直接后果是复用优先。文档的第 3 节是一张完整的复用地图绝大部分最难、且已被反复调试过的机制在仓库中已经存在。2. 为什么这不是 jobs 模块的扩展jobs 模块是一个很好的定义级DAG 调度器但它不是合适的运行时。文档用一张对比表说明了差异维度jobs现状Actions 需要节点体单个 shell 命令串CommandConfig.command有序 steps每个 step 有自己的 env / shell /if/ timeoutDAG 形态定义级DAG边跨全时段的运行状态解析assertDependencyGraphOk每次运行的 DAG在计划阶段固定pinned、按矩阵展开执行器sshManager.withExecutor → streamExec容器 exec超时尽力而为杀不掉远端进程必须真正取消——cancel-in-progress、Cancel 按钮、check_run取消文件系统无跨 step 共享的检出工作区workspace运行状态running / success / failedqueued/in_progress/completed×success/failure/cancelled/skipped/neutral/timed_out/action_required把 workflow job 建模成job行意味着加宽CommandConfig、给assertDependencyGraphOk增加第二套 DAG 语义、给 SSH 执行器塞进容器执行——三条路都得不偿失因此结论是独立建表、独立 runner。关于尽力而为超时这一点源码可以佐证。jobs 模块在 apps/api/src/modules/jobs/job-command.ts 的注释中明确写道timeout 是对 stream 的Promise.racebest-effort无法杀死远端进程但能释放 run 和连接。Actions 需要的是杀掉整个进程树这正是容器底座要解决的问题。从 jobs 复用的只有两样东西cron 注册表支撑on: schedule和通知分发器notification dispatcher。3. 复用地图已有机制即最大资产3.1 执行层复用件位置提供能力buildInContainerExecCmd(cmd, timeoutMs)packages/adapters/src/runtime/docker.ts超时时真正杀死进程树的 step 执行setsid 负 pgid。这是 step runner 中最难的一环且已经完成resolveExecExitCode(exec)docker.ts拒绝在 dockerode 的Running:true/ExitCode:null窗口期猜测退出码杜绝失败 step 被报成绿色splitDockerFrames/docker-demux.tsdocker.tsstep 日志的 stdout/stderr 分流docker-exec-stream.tspackages/adapters/src/runtime/docker-exec-stream.tsBun 安全的原始 hijack。dockerode 自身的 hijack在 Bun 下会永久挂起——这里已解决见 3.1.1DockerRuntime、docker-transport.tsruntime/docker.ts通过 unix socket、TCPSSH 桥或 TLS 完成 container 的 create/start/stop/rm即可在任意目标服务器上运行buildNetworkAliasesdocker.ts为services:容器提供东西向 DNStoDockerHealthcheckdocker.tsservices.*.options --health-cmd支持resource-limits.tsruntime/每 job 的 cpu/mem 上限git-clone.ts、clone-auth.ts、github-known-hosts.tsruntime/、modules/github/actions/checkout所需——认证、known-hosts 与 App 安装令牌均已解决packages/adapters/src/toolchain—actions/setup-node|python|go的 shim 基础3.1.1 看门狗如何保证超时即杀buildInContainerExecCmd把命令作为位置参数传给一个固定的 wrapper 脚本IN_CONTAINER_EXEC_WATCHDOG脚本在容器内用setsid把命令放进独立进程组sleep秒级计时后向负 pgid 发kill -TERM。测试 docker-exec-in-container.test.ts 验证了五个关键性质命令作为参数而非拼进脚本防注入、超时向上取整到秒且下限 1 秒、stdout/stderr/退出码原样透传、快速命令不会因持有输出管道而被拖满整个超时、到点确实杀掉命令而非孤儿化sleep 30配 1s 超时返回 143即 128SIGTERM且镜像缺sleep时优雅降级为不装看门狗而非立即杀。3.1.2 为什么需要自研 docker-exec-streamdockerode 通过 docker-modem 的{hijack: true}拿原始 socket而 Bun 的node:http不会以 modem 期望的方式暴露101 UPGRADED响应——实测 Bun 1.3.1 下请求永不返回。docker-exec-stream.ts用原生 socket 自己写 HTTP 升级请求、读 101 响应头、把 socket 交还调用方且对 unix socket / TCPSSH 桥本地端口/ TLS 三种传输一致工作bridgeSocket还把与101响应头同包到达的首字节通常是 shell 的首个提示符缓冲进返回流规避了 Bun 下unshift丢字节的问题。3.2 GitHub 接线层复用件位置说明createCheckRun/updateCheckRunapps/api/src/modules/github/github.service.ts / L832已固定credential: [app-installation]——Checks API 只允许 App 调用未钉住凭据链时自托管链会先拿到操作者的 gh-CLI token 然后静默 403。切勿解除该钉扎deployment_check_run表形态packages/db/src/schema/deployment-check-run.ts复刻 rollup 逐项的双 flavor 模式包括部分唯一索引——普通唯一索引因 PG 把 NULL 视为互不相同会让重复 rollup 行并存service-checks.tsapps/api/src/modules/deployments/service-checks.ts尽力而为、绝不阻塞的 emitter 模式及其血泪教训开了一个in_progresscheck 却没有保证的终结器它会永远挂在该 PR 上webhook-check-run.tsmodules/github/check_run.rerequested对部署已处理扩展它按action_job.checkRunId查表 → 重跑该 jobwebhook-push.ts、webhook-changed-files.tsapps/api/src/modules/github/webhook-changed-files.tspush 触发 已计算变更文件集——正是paths:/paths-ignore:过滤器需要的github.token.ts、github-access.tsmodules/github/铸造 workflow 的GITHUB_TOKENgit/trees/:ref?recursive1github.service.ts发现.github/workflows/*文件递归树 截断回退3.2.1 Check Run 的 rollup/逐项双 flavor 模式deployment_check_run用kind列区分两种行rollup单个项目级汇总 check名为openship/deploy结论聚合任一失败→failure、全跳过→neutral、全成功→success与service逐服务镜像名为openship/deploy/service。两条部分唯一索引uq_deployment_check_run_rollup、uq_deployment_check_run_service用WHERE kind rollup/WHERE service_deployment_id IS NOT NULL精确锁定不变量。Actions 的action_job/action_step表将照搬这一形态。3.3 平台层复用件位置说明resolveProjectInfoprojectInfoToScanResponsemodules/deployments/prepare.service.ts、github.controller.ts:827向导检测接缝加一个workflows[]字段github / local-folder / upload 三种来源的检测面板免费获得yamlv2apps/api/package.json已是依赖支持锚点/别名与良好的错误定位compose-parser.tsapps/api/src/lib/compose-parser.ts约 1011 行架构模板手工归一化为窄类型形状长尾塞进advancedJSONB错误回传给向导boundedStorableText、build-log-sanitize.tsmodules/deployments/日志截断 密钥遮蔽同时支撑::add-mask::encryptEnvMap/decryptEnvMapapps/api/src/lib/encryption.ts密钥静态加密、运行时解密备份目标抽象modules/backup-destinations/S3 兼容对象存储 → artifacts cachebackup-stale-sweep.tsmodules/backups/孤儿 check-run / 搁浅 run 清理器的模板Build 分钟计费迁移 0107_build_minute_metering_indexes.sqlActions 分钟计费已有归属部署 SSE hook SSE_PRIMERdashboard api实时日志nginx 会在下游剥离X-Accel-Bufferingprimer 前缀是必须的PLATFORM_FEATURES/useFeature—特性开关3.4 真正需要新建的部分工作流 YAML 解析 校验器workflow-parser.ts${{ }}表达式引擎第 5 节运行规划器矩阵展开、needs拓扑排序、if:求值、并发组concurrency groupsstep runner 与 runner 文件契约$GITHUB_OUTPUT/ENV/PATH/STEP_SUMMARY及::工作流命令解析uses:解析第 6 节——成本中心artifact cache 服务Actions 标签页、run 详情 DAG 视图、向导面板。4. 端到端运行流程push / PR / dispatch / schedule → 触发匹配branches、tags、paths —— 经 webhook-changed-files → action_run 行event, headSha, actor, inputs → PLAN解析 YAML → 求值 job 级 if: → 展开矩阵 → 对 needs 拓扑排序 → 把解析出的 DAG 快照进 action_run.plan (jsonb) ← 固定住运行中途 仓库变更不能改写历史 → 创建 rollup check run openship/actions/workflow (in_progress) → 对每个就绪 job尊重 needs max-parallel 创建 per-job check run openship/actions/workflow/job (matrix) 创建 workspace 卷 job 网络 启动 job 容器runs-on → 镜像detachedsleep infinity 逐 step 求值 step 的 if: → 渲染 ${{ }} → 注入 env docker exec buildInContainerExecCmd(script, timeoutMs) 流式帧 → action_step.logs SSE遮蔽、截断 resolveExecExitCode → 状态 读回 $GITHUB_OUTPUT/$GITHUB_ENV/$GITHUB_PATH → step outputs / job env 解析 :: 命令 → annotations、masks、groups job outputs → needs.job.outputs 完成 per-job check run annotations STEP_SUMMARY 作为 output.summary 拆除容器卷按 cache 策略保留 → 完成 rollup check run聚合 conclusion关键点有两个。其一plan 快照pinnedDAG 在计划阶段被固化为 jsonb运行中途仓库改动不可能改写历史——这与部署模块migration.inputSnapshot的 JSONB 逃生舱习惯一致。其二取消是真实的concurrencycancel-in-progress会取消同组内的先前运行并把它标记为cancelled因为 exec 看门狗会真的杀掉进程组见 3.1.1 的测试证据而不是像 jobs 那样只能释放连接。5. 表达式引擎${{ }}的正确性陷阱新目录apps/api/src/modules/actions/expr/——分词器、Pratt 解析器、求值器。纯函数无 I/O。上下文contextsgithub、env、vars、job、jobs、steps、runner、secrets、strategy、matrix、needs、inputs运算符!、、、、、、!、、||、.、[]以及*对象过滤器 glob函数contains、startsWith、endsWith、format、join、toJSON、fromJSON、hashFiles、success()、failure()、cancelled()、always()。文档特别点出一个陷阱GitHub 的宽松相等会做类型强转null 0 false且success()/failure()/always()在 step 级与 job 级依赖的截至目前的状态语义不同——这是静默出错的重灾区因此要求用**穷举式表驱动测试table tests**而非示例式测试。另外hashFiles要 glob 工作区所以它跑在容器 exec 里而不是进程内。预估约 900–1300 行加一个大 fixture 表——有界且可测这不是风险点。6.uses:解析——成本中心分阶段推进层级内容策略Tier 1原生 shimactions/checkout→git-clone.ts、actions/cache→ destination store、actions/upload-artifact/download-artifact、actions/setup-*→toolchain反正需要且因认证已解决而优于 GitHub 的通用路径Tier 2通用 JS actionowner/reporef→ tarball → 解压到工作区/opt/actions/...→ 读action.yml→node /opt/actions/.../index.js遵守 toolkit 契约INPUT_*、GITHUB_OUTPUT、…尊重pre/post解析 执行Tier 3docker actionusing: dockerdocker run镜像或 Dockerfile挂载工作区参数模板化容器执行Tier 4composite action递归 step 展开递归必须响亮拒绝带可操作提示绝不假装支持id-token/ OIDC、environment:审批、workflow_callTier 4 之前、using: node16映射到 node20 并警告。⚠️actions/upload-artifactv4无法与手写端点兼容。v4 走的是专有的 Twirp 风格服务不是 v3 的 REST 形态。要么实现该协议要么交付 Tier-1 原生 shim 并拒绝 v4 通用路径——这是每个 Actions 克隆都会踩的经典坑。7. 风险 / 杀手级问题——承诺前必须先讲清楚runner 镜像不可克隆。ubuntu-latest是约 30 GB、预装数百工具的镜像真实工作流会静默假设gh、jq、docker、node、python 存在。缓解采用catthehacker/ubuntu:act-*专为此构建的镜像把runs-on→ 镜像做成可配置映射并在兼容性 linter 里预先暴露差异而不是在运行第 3 分钟才失败。Docker-in-docker。很多 workflow 要docker build。把宿主 socket 挂进 job 容器等于在宿主机上以 root 运行——单租户自托管可以接受加警告对云是多租户则是否决项除非 rootless / kata。这个决策直接门控了第 1 节的仅自托管。GITHUB_TOKEN权限保真度。能铸造安装令牌但其权限无法 1:1 映射到permissions:。要做权限范围映射并文档化不要过度授权。永不完成的in_progresscheck——service-checks.ts的教训每个 run 都需要一个过期清扫器stale sweeper。该文件在 apps/api/src/modules/deployments/service-checks.ts 中记录了那次重构原本有一个分支会为失败的部署开in_progress起始 check但没有任何东西能终结它——现在改成不会搁浅in_progresscheck 的形态。兼容性是跑步机不是里程碑。Actions 在变marketplace action 假设 runner 内部实现。要为持续维护做预算永远别宣称 100%。8. 自研 vs. 采购vendor——建议在 Phase 2 之前先用2 周 spike 评估nektos/actMIT 许可、Go 编写以二进制形式在容器内调用把它的输出解析进我们的action_job/action_step模型。若保真度好Phase 2 5表达式引擎 action 运行时约 10–15 周可坍缩为约 3–4 周的集成工作。仍然保留本就属于我们的部分发现、建表、标签页、DAG UI、Check Runs、触发器、密钥、artifacts。若保真度差则按上面的规格自研引擎。无论哪种结果spike 都买到设计无法产出的唯一东西我们用户的实际 workflow 有多少能跑通。在承诺 Phase 5 之前先拿一批真实的.github/workflows跑一遍。采购的代价一个 Go 二进制依赖、更粗的逐 step 状态粒度、继承 act 自身的缺口尤其它的 artifact/cache 服务。9. 数据模型——迁移0109_actions.sql沿用仓库的 JSONB 逃生舱习惯如ComposeAdvanced、migration.inputSnapshot让 v2 功能不必各自开迁移。表关键列action_workflowprojectId, path, name, yamlHash, parsed (jsonb), triggers (jsonb), compat (jsonb), enabled, lastSeenShaaction_runworkflowId, event, ref, headSha, actor, inputs (jsonb),plan (jsonb —— 固定的 DAG), concurrencyGroup, status, conclusion, checkRunId, startedAt, finishedAtaction_jobrunId, jobKey, name, matrix (jsonb), needs (jsonb), serverId, containerId, checkRunId, outputs (jsonb), status, conclusion, startedAt, finishedAtaction_stepjobId, idx, name, uses, run, status, conclusion, exitCode, outputs (jsonb), logs (text, 有界), startedAt, finishedAtaction_artifactrunId, name, sizeBytes, destinationId, objectKey, expiresAtaction_cacheprojectId, key, version, scopeRef, sizeBytes, objectKey, lastUsedAt密钥复用现有加密凭据存储encryptEnvMap只有当需要按 workflow 隔离作用域时才新增action_secret作用域表。保留策略日志、artifacts、cache 从第一天起就复用 retention-prune 模式——CI 比平台上任何东西都更能填满磁盘。10. UI 设计向导——Detected workflows面板。检测之后每个 workflow 展示名称、触发器、迷你needsDAG 预览、兼容性徽章绿 / 琥珀色N steps will be skipped / 红、启用开关。检测只返回解析摘要绝不返回原始 YAML 字节——/detect被刻意设计为元数据级路由绝不能成为仓库内容的旁路通道side-channel。Actions 标签页项目级。运行列表workflow、event、分支、提交、actor、时长、状态胶囊使用状态色 token绝不硬编码 emerald/red/amber。Run 详情。左侧是 job DAG右侧是选中 job 的 steps可折叠行 实时日志。仪表盘没有图库依赖无 reactflow/xyflow/dagre/d3。DAG 用手写 SVG 实现按needs深度分层的左→右布局最长路径分层约 250 行且零新增依赖——xyflow 对单个视图而言是过重的引入。强制的原语useModalui/Modal、CustomSelect绝不用原生 select、无边框无点胶囊、bg-card外壳 实心bg-muted骨架屏、行内禁止裸破坏性图标取消/删除归入行⋯菜单。全部 locale 做 i18n 预算。11. 实施阶段与成本阶段范围工作量交付价值0发现树扫描、解析器、兼容 linter、向导面板、只读 Actions 标签页2–3 周我们看见了你的 CI——零执行风险的真实价值1引擎核心建表、规划器、容器/step runner、日志、SSE、取消、run 详情 DAG4–6 周run:步骤真正在本地执行2表达式、矩阵、if:、needs输出2–3 周真实 workflow 不再因语法报错3Check Runs 触发器push/PR/dispatch/schedule、rerequested、并发2–3 周核心卖点本地执行GitHub 原生状态4Tier-1uses:shim artifacts cache3–4 周checkout/setup/cache/artifacts——覆盖多数真实流水线5通用 JS / docker / composite action 运行时4–6 周Marketplace 兼容悬崖6加固runner 镜像、i18n、配额、保留策略、文档2–3 周可发布Phase 0–3 ≈ 10–15 周→ 一个真正有用的本地 CI GitHub 原生状态全部阶段 ≈ 19–28 工程师周→ 能跑通大多数真实 workflow。第 8 节的 act spike 插在 Phase 2 之前最多可省约 10 周。难吗——三个问题三个价格带 node/DAG 图的流水线引擎——不难约 4–6 周。执行原语、DAG 习惯、SSE 日志都是现成的图只是 250 行 SVG。消化 GitHub Actions YAML GitHub 原生 Check Runs——中等在此基础上约 6–9 周。这是差异化、高价值的部分且大部分是把既有部件接线。做到 GitHub-Actions-兼容任意 marketplace action 不改动就能跑——难且无边界。不是难在巧妙而是难在无穷无尽。这正是克隆体死掉的地方也是该采购第 8 节而非自研的部分。引擎不是风险生态才是。结语用已有的最难机制换取 GitHub 原生的 CI 体验这份设计文档的价值在于把做一个 Actions 兼容 Runner拆成了三个难度迥异的问题并明确指出最难的部分进程树级超时杀灭、Bun 下的 exec hijack、Check API 的 App-only 凭据钉扎、rollup/逐项双 flavor 的部分唯一索引、in_progress必须有终结器在 OpenShip 仓库中已经解决且被测试覆盖。真正的成本中心是表达式语义的正确性宽松相等的强转陷阱与 marketplace 生态的无限兼容面。对自托管场景而言Phase 0–3 的本地执行 GitHub 原生状态路径清晰、风险可控是值得投入的方向对生态兼容则建议以 2 周 spike 验证act路线把无限问题外包给成熟方案。文中引用的所有源码与测试路径均为当前仓库实际存在的内容可在 packages/adapters/src/runtime、apps/api/src/modules/github、packages/db/src/schema 等目录中继续深入阅读。【免费下载链接】openshipSelf-hosted deployment platform项目地址: https://gitcode.com/GitHub_Trending/ope/openship创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表