# pipewright
**Repository Path**: sony_song/pipewright
## Basic Information
- **Project Name**: pipewright
- **Description**: 一个轻量、自托管的 CI/CD + 部署 + 运维一体化平台。 单个 Go 静态二进制(内嵌前端,运行时零依赖), 一个工具替掉「CI + Ansible/Kamal + Portainer」三件套。
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 15
- **Created**: 2026-09-16
- **Last Updated**: 2026-09-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Pipewright
**一个轻量、自托管的 CI/CD + 部署 + 运维一体化平台。**
单个 Go 静态二进制(内嵌前端,运行时零依赖),
一个工具替掉「CI + Ansible/Kamal + Portainer」三件套。
[](https://github.com/huangchengsir/pipewright/releases)
[](https://github.com/huangchengsir/pipewright/actions/workflows/ci.yml)
[](LICENSE)
[English](README.md) | 简体中文
---
## 为什么是 Pipewright
主流方案要么重(Jenkins 一堆插件 + JVM),要么是三个工具拼装(Woodpecker/Drone + Ansible/Kamal + Portainer)。Pipewright 把「持续集成、多服务器部署、服务器/容器运维」装进**一个静态二进制**:下载、启动、打开浏览器,就是全部安装过程。
如今它已长过那三件套:把部署好的服务挂上 HTTPS 域名(原本的 nginx + certbot 那一步)、给每个 PR 一个能点开的预览地址,也都内置了。
| | Pipewright | Jenkins | Drone + Ansible + Portainer |
|---|:---:|:---:|:---:|
| 单二进制部署 | ✅ | ❌ JVM + 插件 | ❌ 三套拼装 |
| 可视化流水线编排(DAG) | ✅ 内置画布 | 插件 | YAML 手写 |
| 流水线即代码(按分支演进) | ✅ | 插件 | ✅ |
| 隔离构建 | ✅ | ✅ | ✅ |
| 多服务器部署(SSH + Kubernetes,免 Agent) | ✅ 内置 | 插件 | Ansible |
| 服务器 / 容器运维 | ✅ 内置 | ❌ | Portainer |
| 零停机 + 失败回滚 | ✅ | 插件 | 自己写 |
| 自动 HTTPS + 域名反向代理 | ✅ 内置 | ❌ | ❌ |
| Per-PR 预览环境 | ✅ 内置 | ❌ | ❌ |
| DORA 指标 | ✅ 内置 | 插件 | ❌ |
| AI 失败诊断 | ✅ 可选 | ❌ | ❌ |
| 一键自更新 | ✅ | ❌ | ❌ |
## 界面预览
**全局概览** —— 项目、运行成功率、环境部署态、服务器健康、DORA 指标,一屏全局:

**可视化流水线编排** —— 阶段/任务两级 DAG 画布:横向连线串行、纵向并排真并行,支持矩阵构建、人工审批门、旁挂服务、阶段后置步骤;画布与 YAML 双向往返:

**运行详情** —— 阶段流转、实时日志(SSE 推送 + 历史回放)、构建产物与镜像引用、逐步骤状态:

**容器管理** —— 跨主机容器/镜像/Stacks/卷/网络一站管理,生命周期操作、实时 stats、日志、交互终端:

## 能力总览
- **🔐 安全地基** —— 单管理员认证(argon2id + CSRF)· 凭据加密保险库(NaCl secretbox,掩码呈现,绝无明文)· OAuth 应用接入 Gitee / GitHub / GitLab / 自建实例(拿到的 access token 直接存成可复用的保险库凭据)· append-only 审计(SQLite trigger 硬拦 UPDATE/DELETE)+ 可选远端 sink · 按 run 解析真实凭据后对日志 / 诊断 / 通知全链路脱敏。
- **🧩 项目与流水线** —— 可视化编排画布(阶段 DAG + 阶段内任务级 DAG)· 矩阵构建 · 人工审批门(可直接在通知里点签名链接审批)· 旁挂服务(测试挂 DB/Redis)· 阶段 `when` 条件 + 阶段后置步骤 · 类型化运行参数(枚举/布尔/数字,触发时即校验)· 触发方式:webhook、分支→环境映射、5 字段 cron 定时、上游→下游流水线串联(深度门 + 路径门防环)· 项目级并发上限 + 超限 FIFO 排队 · 复用库:流水线模板 / 变量组 / 自定义节点 · 服务端权威合法性校验。
- **📝 流水线即代码** —— 把流水线结构写进 `.pipewright.yml`、按分支各自演进,画布配置始终作为兜底回退([详见下文](#流水线即代码gitops))。
- **🏗 隔离构建与产物** —— 版本钉死的容器内隔离构建(docker/nerdctl/podman)· 代码管理区:本地 bare 镜像 + 增量 fetch,秒级出工作区 · 构建依赖缓存(按分支 + lockfile hash 寻址)· 内容寻址制品库,jar/dist 存**真字节**供部署(而非占位 reference)· 镜像构建 + 推送私有仓库 + 镜像 GC · 每项目可指定远程构建机(构建经 SSH 下沉到远程,token 只留控制机)· JUnit + Cobertura 测试报告喂质量门禁,不过则阶段失败、阻断下游部署 · **安全与质量一等节点**(security_scan 镜像漏洞 / secret_scan 密钥泄露(豁免入审计)/ code_quality SARIF 汇总 / dast_scan 严格受限 DAST):平台只解析行业标准报告(SARIF / Trivy / Gitleaks / ZAP),容器内拉官方扫描器镜像,零扫描器内置;扫描证据入库即证据卡,晋级门禁可按严重/高危漏洞与质量问题设阈值,无证据 fail closed。 实时终端日志(SSE)+ 历史回放 · 只读代码浏览(Monaco)。
- **🚀 多服务器部署** —— 经 SSH 免 Agent 部署 · **Kubernetes 目标(内联 kubeconfig 驱动)**:纯 Go 直连 API Server(不依赖 kubectl),image 产物滚动更新 + 就绪门控 + 失败自动回滚镜像,清单目录 apply,或 **Helm chart 渲染 + apply**(能力集取自真实端点,不支持项如实点名) · 健康门控 · 零停机切换 + 失败回滚 · 多机并行扇出 + 部分失败可见 · 命令型部署(无产物,直接重启服务)· **环境一等公民**:逐环境部署时间线、当前活跃版本、一键回滚到上一次全成功部署 · 环境晋级流(dev→staging→prod)+ 逐环境变量/密钥 + 审批门。
- **🌐 自动 HTTPS + 域名反向代理** —— 每台目标主机一个托管 Caddy 容器,复用与容器运维同一套 SSH + docker 手法编排(渲染 Caddyfile → `docker cp` → 优雅 reload)。证书经 Let's Encrypt 自动签发/续期:HTTP-01,或**经 Cloudflare / DNSPod / 阿里云 DNS 走 DNS-01**(通配符必需)。另有:多域名别名、路径路由(`/api`→A、`/`→B)、重定向、访问控制(basic auth、IP 允许/拒绝 CIDR)、HSTS / 安全头 / 压缩、多上游负载均衡 + 主动健康检查故障转移、WebSocket / gRPC(h2c) / TCP 透传(caddy-l4)、按真实 443 握手探测的证书大盘、一键子域名。
- **🔎 Per-PR 预览环境** —— 某 PR 的运行成功部署后,自动分配一次性域名 `pr--.`(带自己的证书与路由),评审者点开链接就能看到这条 PR 真实跑起来的样子。同一 PR 幂等复用;自动回收,但**仅在**确证 PR 已关闭/合并时才回收。
- **📣 通知** —— 企业微信 / 钉钉 / 飞书 / Slack / 邮件 / 自定义 webhook · 事件→渠道细粒度路由 · 模板 + 变量自定义 · 飞书富卡片(审批/详情行动按钮 + 发版汇总)· 流水线内通知节点。
- **🔗 出站 Webhook 订阅** —— 把运行终态 / 部署事件 POST 到**你自己注册的端点**,让平台嵌进你既有的自动化(CI 触发器、工单、数据仓库、自建编排)。与上面的「通知」面向人不同,这条链路面向机器:每次请求带 `X-Pipewright-Signature: sha256=`(HMAC-SHA256,签名对象是 `timestamp + "." + body`,时间戳可用于拒绝过期重放),失败按**指数退避**重试(`5xx`/`408`/`429` 可重试,`4xx` 判永久失败),并留一份**投递台账** —— 每条「订阅 × 事件」唯一一行(幂等去重)、接收端回了什么一目了然、接收端修好后可一键重放。
- **🖥 服务器与容器运维** —— 多机状态总览(CPU/内存/磁盘)+ 指标时序趋势图 · 容器/镜像/Stacks/卷/网络管理 · 容器创建/inspect/prune · 实时 + 历史服务日志 · 实时 stats · 容器交互终端 · Web 运维终端(主机 shell,完整复制粘贴/信号支持)· 可配置异常检测:定时自动跑、冷却去重、命中走通知渠道。
- **🤖 AI 辅助(可选,完全可降级)** —— 自带 Claude / OpenAI / Ollama 端点(apiKey 密文入保险库)。构建/部署失败自动根因诊断 + 👍/👎 反馈飞轮与准确率统计 · 仓库分析 → 生成流水线草案 · 成功/失败提交差异 · 脚本风险标注 · 运维终端的自然语言→shell 助手与容器诊断。核心 CI/CD 路径完全不依赖它(NFR-10)。
- **📈 度量** —— DORA 四指标(部署频率/变更前置时长/变更失败率/平均恢复时长)开箱即用,并给 Elite/High/Medium/Low 绩效分档。
- **🧹 数据与平台** —— 运行数据保留清理器(默认关;绝不动在跑的运行)· SQLite(纯 Go)或 MySQL · 8 种界面语言(简中 / 繁中 / 英 / 日 / 韩 / 德 / 法 / 西),API 错误信息亦服务端本地化。
- **🔄 检查 + 一键自更新** —— 设置→系统 一键查 GitHub 最新发布并语义比对;二进制部署可页面**一键自动更新**(下载 + 校验和核验 + 原子替换 + 自重启),Docker 部署给出精确升级命令。
> 安全不可妥协:凭据仅以密文存储、命令 array 化防注入、出网 SSRF 收口、日志脱敏。
## 安装 / 部署
三种形态任选,平台本体是单静态二进制、**运行时零依赖**(无需 Go/Node)。
> **Docker 前置**:平台本体不依赖 Docker,但**「隔离构建 / 容器部署」需要 Docker**(没有则降级到桩 runner、不做真实构建)。控制台 / SSH 部署 / 通知不需要。一键脚本会**检测 Docker** 并在缺失时提示;Linux 下可 `INSTALL_DOCKER=1` 自动安装(经官方 get.docker.com),macOS 请装 Docker Desktop。
### ① 一键脚本(Linux / macOS)
从 GitHub Release 下载对应平台的静态二进制装到 `/usr/local/bin`(含校验和核验 + Docker 检测):
```bash
curl -fsSL https://raw.githubusercontent.com/huangchengsir/pipewright/master/install.sh | sh
# 钉版本 / 自定义目录 / Linux 顺带自动装 Docker:
VERSION=v1.0.0 INSTALL_DIR=$HOME/.local/bin INSTALL_DOCKER=1 \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/huangchengsir/pipewright/master/install.sh)"
# 运行(首次启动引导管理员;master key 用于凭据保险库)
PIPEWRIGHT_MASTER_KEY=$(openssl rand -base64 32) \
PIPEWRIGHT_ADMIN_PASSWORD=change-me \
pipewright # 打开 http://localhost:8080,用 admin / change-me 登录
```
**推荐:装为 systemd 服务**(开机自启 + 崩溃重启 + 一键自更新可用;Linux,需 root)。脚本会自动持久化 master key 到 `/etc/pipewright/master.key`、数据落 `/var/lib/pipewright`、配置写 `/etc/pipewright/pipewright.env`:
```bash
SETUP_SERVICE=1 sh -c "$(curl -fsSL https://raw.githubusercontent.com/huangchengsir/pipewright/master/install.sh)"
# 状态 / 日志:systemctl status pipewright · journalctl -u pipewright -f
# 改端口等:编辑 /etc/pipewright/pipewright.env 后 systemctl restart pipewright
# 用 MySQL 而非默认 SQLite(DSN 为 go-sql-driver 格式,parseTime=true 必带):
SETUP_SERVICE=1 PIPEWRIGHT_DB_DRIVER=mysql \
PIPEWRIGHT_DB_DSN='user:pw@tcp(host:3306)/pipewright?parseTime=true&charset=utf8mb4' \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/huangchengsir/pipewright/master/install.sh)"
```
> Windows 用户:到 [Releases](https://github.com/huangchengsir/pipewright/releases) 下载 `.zip`。
### ② docker compose(推荐自托管)
```bash
curl -fsSLO https://raw.githubusercontent.com/huangchengsir/pipewright/master/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/huangchengsir/pipewright/master/.env.example
cp .env.example .env # 至少设 PIPEWRIGHT_ADMIN_PASSWORD,并 openssl rand -base64 32 填 MASTER_KEY
docker compose up -d # 数据持久化在具名卷 pipewright-data;切 MySQL 见 .env 注释
```
### ③ docker run(最快试用)
```bash
docker run -d -p 8080:8080 -v pipewright-data:/data \
-e PIPEWRIGHT_ADMIN_PASSWORD=change-me \
-e PIPEWRIGHT_MASTER_KEY=$(openssl rand -base64 32) \
ghcr.io/huangchengsir/pipewright:latest
```
### 从源码构建
```bash
make build # 前端构建 → go:embed → 单个静态二进制 ./pipewright(纯 Go,无 CGO)
./pipewright --version
```
### 更新
打开 **设置 → 系统**,点「检查更新」查最新发布;有新版时:
- **二进制部署**:点「立即更新」即自动下载新版 + 校验和核验 + 替换 + 重启(需对二进制文件有写权限;装在 `$HOME/.local/bin` 免 sudo,或用 `SETUP_SERVICE=1` 装的 root systemd 服务亦满足)。
- **Docker 部署**:容器不替换自身镜像,按提示在宿主执行 `docker compose pull && docker compose up -d`(数据卷保留)。
### 配置(环境变量)
正常安装只需前两个(若跑在反代后面再加 `PIPEWRIGHT_PUBLIC_URL`),其余都有合理默认值。
**核心**
| 变量 | 说明 | 默认 |
|---|---|---|
| `PIPEWRIGHT_ADMIN_PASSWORD` | 首次启动管理员口令 | 无(须设置) |
| `PIPEWRIGHT_MASTER_KEY` | 凭据保险库主密钥(base64 的 32 字节);或用 `PIPEWRIGHT_MASTER_KEY_FILE` 指文件。轮换见 [master key 轮换](#master-key-轮换) | 未配则保险库禁用 |
| `PIPEWRIGHT_ROTATE_FROM_KEY` | `--rotate-master-key` 用的旧 key(格式同上;或 `PIPEWRIGHT_ROTATE_FROM_KEY_FILE` 指文件)。仅在轮换模式下被读取 | 无 |
| `PIPEWRIGHT_ADDR` | HTTP 监听地址 | `:8080` |
| `PIPEWRIGHT_PUBLIC_URL` | 外部可访问的基址(如 `https://ci.example.com`)。webhook 回调、OAuth 回跳、通知里的签名审批链接、PR 状态跳转链接都需要它 | 无 |
| `PIPEWRIGHT_ADMIN_USERNAME` | 首次启动管理员用户名 | `admin` |
| `PIPEWRIGHT_TRUST_PROXY` | 采信 `X-Forwarded-For` 首段作为审计来源 IP(`1`/`true`/`yes`/`on`)。除非前面确有可信反代,否则别开 —— 否则任意客户端都能伪造审计来源 IP | 关 |
| `PIPEWRIGHT_RELEASE_REPO` | 检查更新所查的 GitHub 仓库(fork 可改) | `huangchengsir/pipewright` |
| `PIPEWRIGHT_RUNTIME` | 设 `docker` 显式声明容器部署形态(影响自更新方式);否则经 `/.dockerenv` 自动探测 | 自动探测 |
| `PIPEWRIGHT_AUDIT_SINK` | 远端审计 sink:`http(s)://` 端点,或填其它值作为第二份本地 JSON Lines 文件路径。本地库被删后审计仍完整 | 无 |
**数据库**
| 变量 | 说明 | 默认 |
|---|---|---|
| `PIPEWRIGHT_DB_DRIVER` | 数据库驱动:`sqlite` 或 `mysql` | `sqlite` |
| `PIPEWRIGHT_DB` | SQLite 数据库路径(driver=sqlite 时) | `pipewright.db` |
| `PIPEWRIGHT_DB_DSN` | MySQL DSN(driver=mysql 时必填) | 无 |
**运行与构建**
| 变量 | 说明 | 默认 |
|---|---|---|
| `PIPEWRIGHT_RUNNER` | 运行执行器:默认 DAG(按画布 stages/script/deploy_ssh/notify 编排执行);设 `legacy` 回退旧版固定流程 | `dag` |
| `PIPEWRIGHT_BUILDER` | `auto` 探测到 docker/nerdctl/podman 用真实构建、否则回退桩;`real` 无容器 CLI 直接启动失败;`stub` 完全不碰容器 | `auto` |
| `PIPEWRIGHT_MAX_CONCURRENT` | 全局同时运行上限(超出保持 queued 排队,FIFO)。项目级上限在界面里配 | worker 数(4) |
| `PIPEWRIGHT_ARTIFACT_DIR` | 制品库目录(jar/dist 真字节) | `/artifacts` |
| `PIPEWRIGHT_REPO_CACHE_DIR` | 代码管理区(本地 bare 镜像)目录 | `/repos` |
| `PIPEWRIGHT_NO_REPO_CACHE` | `1` 关闭代码管理区(每次构建直连网络克隆) | 关 |
| `PIPEWRIGHT_CACHE_DIR` | 构建依赖缓存目录 | `/cache` |
| `PIPEWRIGHT_NO_BUILD_CACHE` | `1` 关闭构建依赖缓存(每次冷构建) | 关 |
| `PIPEWRIGHT_NO_IMAGE_GC` | `1` 保留构建出的镜像,不做垃圾回收 | 关 |
| `PIPEWRIGHT_PAC_RUNTIME` | `1` 对**所有项目**强开流水线即代码,无视各项目开关 | 关 |
| `PIPEWRIGHT_CHAIN_MAX_DEPTH` | 流水线串联深度上限(防无限链的硬兜底) | `5` |
| `PIPEWRIGHT_LOG_MAX_LINE_BYTES` | 单行运行日志的存储上限(超出按字符边界截断并附标记;先脱敏后截断) | `16384`(16 KiB) |
| `PIPEWRIGHT_LOG_MAX_RUN_BYTES` | 单个运行可落库的日志总量上限(超出写一条封口标记并停止收录) | `16777216`(16 MiB) |
| `PIPEWRIGHT_LOG_COMPRESS` | `0` 关闭终态运行日志的分块压缩。**不推荐关闭**:保留策略默认关,压缩是日志体积的主要约束 | 开 |
**集成**
| 变量 | 说明 | 默认 |
|---|---|---|
| `PIPEWRIGHT_PR_STATUS` | `1` 对**所有项目**强开 PR 状态回写,无视各项目开关 | 关 |
| `PIPEWRIGHT_PR_STATUS_GITHUB_BASE` | GitHub API 基址(GitHub Enterprise 用) | 公有 GitHub |
| `PIPEWRIGHT_PR_STATUS_GITEE_BASE` | Gitee API 基址(自建 Gitee 用) | 公有 Gitee |
| `PIPEWRIGHT_PR_STATUS_GITLAB_BASE` | GitLab API 基址。设置它同时把该 host 声明为自建 GitLab 实例 —— 只有被声明的 host 才会被识别(公有 GitLab 始终识别) | 公有 GitLab |
| `PIPEWRIGHT_CADDY_IMAGE` | 反代镜像。默认是自构建的 Caddy(含 DNS-01 / ratelimit / layer4 插件);用原版 `caddy:2` 也能跑,但会失去 DNS-01/通配符/TCP 能力 | `ghcr.io/huangchengsir/pipewright-caddy:latest` |
| `PIPEWRIGHT_PREVIEW_SWEEP_INTERVAL` | 预览环境回收扫描间隔(Go duration,如 `10m`) | `5m` |
| `PIPEWRIGHT_AI_TIMEOUT` | 单次 LLM **生成**(失败诊断 / 生成流水线 / 命令卡 / 风险标注…)的超时(秒,夹取到 `5`–`600`)。慢网关或本地大模型常需调大;「设置 › AI」里的**连通性测试**走独立短超时(8s),不受此项影响 | `120` |
**出站 Webhook 订阅**
| 变量 | 说明 | 默认 |
|---|---|---|
| `PIPEWRIGHT_WEBHOOK_MAX_ATTEMPTS` | 单条投递的最大尝试次数(含首次,夹取到 `1`–`20`)。用完转入 `dead`,不再自动重试 | `6` |
| `PIPEWRIGHT_WEBHOOK_BACKOFF_BASE` | 首次重试的等待时长,此后逐次翻倍(夹取到 `1s`–`10m`) | `30s` |
| `PIPEWRIGHT_WEBHOOK_BACKOFF_MAX` | 单次重试等待的上限(退避封顶,夹取到 `1s`–`24h`) | `30m` |
| `PIPEWRIGHT_WEBHOOK_TIMEOUT` | 单次投递的 HTTP 超时(夹取到 `1s`–`60s`) | `10s` |
| `PIPEWRIGHT_WEBHOOK_DELIVERY_RETENTION` | 投递台账的保留期,超期记录由派发器定期清理(夹取到 `1h`–`8760h`) | `168h`(7 天) |
| `PIPEWRIGHT_WEBHOOK_DISPATCH_INTERVAL` | 到期投递的轮询间隔(夹取到 `1s`–`5m`)。排障时调小可在一次会话里看完整条重试链路 | `5s` |
签名契约(接收端照此实现即可,「设置 › 通知 › 出站 Webhook 订阅」里也有同一份说明):
签名对象是 `X-Pipewright-Timestamp + "." + 原始请求体`,算法 HMAC-SHA256,头值形如 `sha256=`,
比较用常量时间。另带 `X-Pipewright-Event` / `-Delivery` / `-Event-ID` / `-Signature-Alg`。
**接收端若自己重新序列化了 JSON 再验签必然失败** —— 字节变了签名就不对。
地址校验恒拒云元数据与链路本地地址(`169.254.0.0/16`、`fe80::/10`、`0.0.0.0`/`::`);私网与回环**放行**,自托管接内网/本机接收端是正常用法。
**运维监控**
| 变量 | 说明 | 默认 |
|---|---|---|
| `PIPEWRIGHT_ANOMALY_INTERVAL` | 异常检测间隔(秒);`0` 关闭定时(仍可手动「立即检测」) | `60` |
| `PIPEWRIGHT_ANOMALY_COOLDOWN` | 同「服务器×规则」重复告警的最小间隔(秒) | `600` |
| `PIPEWRIGHT_METRICS_SAMPLE_INTERVAL` | 服务器指标采样间隔(秒,趋势图数据源);`0` 关闭采样 | `60` |
| `PIPEWRIGHT_METRICS_RETENTION_DAYS` | 指标样本保留天数 | `7` |
| `PIPEWRIGHT_LOG_LEVEL` | 日志级别:`debug`/`info`/`warn`/`error`。排障时调 `debug` 即可,无需改代码重编译 | `info` |
| `PIPEWRIGHT_LOG_FORMAT` | 日志格式:`json`(接采集器)或 `text`(人看终端) | `json` |
| `PIPEWRIGHT_LOG_SOURCE` | `1` 在日志里带调用点(文件:行号)。级别为 `debug` 时自动开 | 关(debug 时自动) |
| `PIPEWRIGHT_HTTP_ACCESS_LOG` | `0`/`off` 关闭请求访问日志(含 `requestId`/方法/路由/状态/字节/耗时/来源 IP)。`5xx` 记 ERROR、`4xx` 记 WARN,`/healthz` 与 `/metrics` 降为 DEBUG 免被探针刷屏 | 开 |
| `PIPEWRIGHT_RATE_LIMIT` | `0`/`off` 关闭按路由限流(令牌桶)。仅限登录/webhook/审批/自更新/AI/试克隆/建运行等敏感或昂贵路由,超限返回 `429` + `Retry-After`。正常交互(含一次页面加载的十几条并发请求)不会触边 | 开 |
| `PIPEWRIGHT_METRICS` | `0` 关闭 Prometheus `/metrics` 端点(关闭时该路径显式 404,而非回退到前端页面) | 开 |
| `PIPEWRIGHT_METRICS_TOKEN` | 非空时 `/metrics` 要求 `Authorization: Bearer `。靠网络边界保护的内网部署可留空 | 无 |
两个与排障直接相关的约定:
- **`/metrics`** 挂在根路径(与 `/healthz` 同级),**不走会话鉴权** —— Prometheus 不带 cookie,走会话鉴权只会永远 401。需要访问控制就用上面的 token。
- **`X-Request-ID`** 请求关联 id:客户端可自带(便于与上游反代/网关串联),不合法或缺失则服务端生成,响应头一律回填;错误响应体里也带 `error.requestId`,报障时直接给这个值即可在日志里检索到那次请求的全部痕迹(`{"msg":"http 请求","requestId":...}`)。
### master key 轮换
全部敏感数据(凭据、AI API key、OAuth client secret、webhook 密钥、通知渠道密码)都由保险库主密钥加密。**key 丢失 = 密文永久不可恢复——没有任何后门。** key 疑似泄露、掌握 key 的人员变动、或按周期,都应轮换。
轮换把库内全部密文用旧 key 解出、新 key 重加密,一个事务内完成;任何一条旧 key 解不开则整体中止、库零改动(fail closed)。轮换是**离线操作**(先停服务)的 CLI 模式,绝不启动 HTTP 服务:
```bash
# 1. 停掉 pipewright(systemctl stop / docker compose stop / kill -TERM)
# 2. MySQL 先 dump;SQLite 直接拷库文件备份
cp pipewright.db pipewright.db.bak-$(date +%F)
# 3. 轮换:旧 key 走 ROTATE_FROM,新 key 走日常的 MASTER_KEY 槽位
export PIPEWRIGHT_ROTATE_FROM_KEY="$(cat /etc/pipewright/master.key.old)"
export PIPEWRIGHT_MASTER_KEY="$(cat /etc/pipewright/master.key)" # 新 key
pipewright --rotate-master-key
# 4. 用新 key 启动 pipewright;旧 key 从环境文件/密钥管理中立即销毁
```
示例输出:
```
master key 轮换完成:共重加密 6 条密文。
- credentials: 2 条
- notification_channels: 1 条
...
请立即用新 key 重启 pipewright;旧 key(PIPEWRIGHT_ROTATE_FROM_KEY)应即刻从环境/密钥管理中销毁。
```
注意:
- key 解码后必须恰好 32 字节;生成方式 `python3 -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"`。
- 轮换前会先应用未执行的数据库迁移,升级后直接轮换是安全场景。
- 新旧 key 相同会被拒绝;旧 key 解不开既有密文时中止并点名表/行(错误信息不含任何密钥/明文)。
## 流水线即代码(GitOps)
把流水线结构写进仓库的 `.pipewright.yml`,**跟代码同源、走 PR 评审、按分支演进**——不再依赖画布配置的隐式漂移。
- **开启**:在项目的流水线页打开「流水线即代码 / Pipeline as code」开关(按项目维度)。
- **生效方式**:开启后,每次运行都从**本次构建分支**的**仓库根**读取 `.pipewright.yml`(分支为空时回退项目默认分支),用其中的流水线 spec 驱动本次运行;不同分支可携带各自的 `.pipewright.yml`。文件用项目绑定的仓库凭据临时拉取,无新增暴露面。
- **永不卡住运行的回退**:文件**缺失** → 回退到画布(UI)里已配置的流水线;文件存在但 **YAML 非法** → 同样回退到已存的画布配置。
- **作用范围**:YAML 只管**流水线结构**(阶段 / 任务 / `needs` / DAG 编排);**变量与缓存、环境与凭据、触发规则**仍来自画布(UI)设置,**不写在 YAML 里**。
- **schema** 与平台「从 YAML 导入」用的是同一套(`version` + `stages` → `jobs`,job 用嵌套 `script:` 块写 `image`/`commands`/`env`/`workdir`)。
- **节点类型**(画布与 YAML 通用):`git_source`、`script`、`build_backend`、`build_frontend`、`build_image`、`push_image`、`deploy_ssh`、`deploy_frontend`、`health_check`、`notify`、`templated`、`custom`。
```yaml
version: 1
stages:
- id: stg_src # needs 按阶段 id 引用,故跨阶段依赖须显式写 id
name: 流水线源
kind: source
jobs:
- name: Gitee 源
type: git_source
- id: stg_build
name: 构建
kind: build
needs: [stg_src]
jobs:
- name: 运行测试
type: script
script:
image: golang:1.23
commands:
- go vet ./...
- go test ./...
env:
CGO_ENABLED: "0"
workdir: src/app
- id: stg_deploy
name: 部署
kind: deploy
needs: [stg_build]
gate: true # 人工审批门
when:
branches: [main, release/*]
jobs:
- name: SSH 部署
type: deploy_ssh
config:
targetEnv: prod
```
> 也可用全局环境变量 `PIPEWRIGHT_PAC_RUNTIME=1` 对**所有项目**强制开启流水线即代码(无视各项目开关),供向后兼容 / 高级用户使用。
## 部署 Helm chart 到 Kubernetes
Kubernetes 目标有三种发布形态(部署配置 `k8sMode`):`image`(换镜像滚动,默认)、`manifests`(apply 清单目录)、以及 **`helm`** —— 从产物取一份 chart,渲染后 apply。
- **不需要 `helm` 命令,也不引 SDK/不依赖 kubectl** —— 模板由内置兼容子集**在进程内**渲染,与「单静态二进制」形态一致(Kubernetes 目标只给你一份 kubeconfig,没有可跑命令的宿主)。
- **chart 从产物来,「这一版是什么」因此有答案** —— chart 目录声明为 `artifactPath`、归档成 dist 产物即可(也可直接给 `helm package` 的 `.tgz`,按 gzip 魔数识别而非扩展名)。于是「这个环境跑的是哪一版 chart」与「这个 run 产出了什么」是同一个事实 —— 环境时间线与一键回滚**零新增机制**即可用。
- **values 四层覆盖、深度合并** —— chart 自带 `values.yaml` < 产物内的额外 values 文件(`helmValuesPath`)< 构建配置变量组的**非 secret** 变量注入(`useVarGroups`)< 节点上直接写的 YAML(`helmValues`,支持 `{{param}}` 运行参数占位)。映射按键级合并,列表整体覆盖(与 Helm 同口径)。values 用 **YAML 文本**而非 `--set`,值的类型不需要平台去猜。
- **能力集必须真实** —— `.Capabilities.KubeVersion` 与 `.Capabilities.APIVersions.Has` 来自集群真实端点(`/version`、`/api`、`/apis`);读不到就**失败**,不填一个「大概是这个版本」。否则按 1.25 写的模板分支会在 1.30 集群上静默走错。
- **如实承认自己不是 Helm** —— 不支持的东西**点名报错并给出出路**:子 chart 依赖与 `crds/`、`.Release.IsInstall` / `.IsUpgrade` / `.Revision`、`.Capabilities.HelmVersion`、`lookup`、`semverCompare`、`now` / `date` / `uuidv4` / `rand*`(会破坏渲染可复现)、`genCA` / `genSignedCert`,以及带 `helm.sh/hook` 注解的资源(报错里列出对象名)。平台不维护 release 表:`.Release.Service` 如实写 `Pipewright`,失败文案指向**环境级一键回滚**,不猜一个「上一版」。
- **渲染可复现** —— 同一份 chart + 同一份 values 逐字节一致(非确定性函数一律拒绝,模板按固定路径顺序渲染)。
支持的子集、明确不支持清单、与真 Helm 的差异:见 [`docs/helm-deploy.md`](docs/helm-deploy.md)(英文,与其它特性专文一致)。
## 技术栈
- **后端**:Go · Chi(路由)· modernc/sqlite(纯 Go,无 CGO)+ go-sql-driver/mysql · go-git(不依赖宿主 git)· NaCl secretbox(保险库)· argon2id + bcrypt · golang.org/x/crypto/ssh(免 Agent 部署)· coder/websocket(终端)· Caddy(在目标主机上编排,提供自动 HTTPS)
- **前端**:Vue 3 `