fix: move Gitea public edge to NC01
This commit is contained in:
@@ -320,7 +320,10 @@ bun scripts/cli.ts hwlab nodes control-plane legacy-cicd --help
|
||||
- 同 deliveryId 与同 payload 必须幂等,异 payload 必须返回 `409`;PVC 不可用或容量满必须返回 `503` 并使 readiness=false;
|
||||
- inbox worker 使用 YAML 声明的有界 backoff 自动重试,重启后恢复 `accepted` / `processing`;Caddy 只可在请求尚未持久化时做小于 10 秒的 body-safe 有界重试。
|
||||
- Durable inbox 只是 webhook delivery 的持久接收与重试 journal,不是业务 source/ref 的第二份真相,也不得被 PaC、Tekton、GitOps、Argo 或 runtime 当作源码/read model。最终 source authority 仍只有 Gitea authority branch 与 immutable snapshot。默认只使用 `platform-infra gitea mirror webhook status` 观察 accepted -> committed 分层。会 POST hook test 的 mutation 测试入口已经删除;连通性只能通过真正只读的 status、GET 与 readiness 观察,不能制造伪 push。平台 bootstrap 必须先落入 Git-backed YAML/controller/source,并由正常 PR merge 的自动事件验收;禁止 Gitea -> GitHub 回写、轮询/read-model fallback、Gitea Actions/act_runner fallback 或第二 source authority。
|
||||
- Gitea webhook/FRPC/Caddy 自动链变更的 closeout 必须观察修复 PR 合并后的真实 delivery、rollout、只读 status/GET/readiness 证据;Secret 或 ConfigMap 声明已生成不等于 connector Pod 已加载新 proxy/path,也不得用人工 apply 或 hook test 补齐。
|
||||
- Gitea webhook/public-edge 自动链变更的 closeout:
|
||||
- 必须观察修复 PR 合并后的真实 delivery、rollout 和只读 status/GET/readiness 证据;
|
||||
- Secret、ConfigMap 或 public-edge 候选已生成不等于 connector Pod 与公网 route 已加载新配置;
|
||||
- 不得用人工 apply 或 hook test 补齐。
|
||||
- Sentinel 内部 publish capability 的唯一真相是 `config/platform-infra/pipelines-as-code.yaml#capabilities.sentinelInternalPublish`,当前默认关闭。Pod env、ownerReferences、ServiceAccount 与 metadata 不能证明 creator;#1769 的 admission-owned provenance 落地前不得启用。`.tekton` 保持既有自动 `publish-current` 入口,禁止改成人工 publish。
|
||||
- PaC PipelineRun 必须区分外层 `outer-pac-event`、内层 `inner-deterministic-publish` 与 `unknown`。只有唯一外层事件可驱动 latest/status/debug-step/closeout;内层只能作为未绑定执行观察,缺少 admission-owned 父子证据时不得按前缀推断。
|
||||
- `cicd branch-follower` 对当前 PaC consumer 已退役,只保留 `status|events|logs` 与只读 `debug-step` 历史诊断。默认入口、任意 `--config` 和混合/未知 authority 都不能恢复 `apply|run-once|state-write` 等写能力,也不能猜测默认 node。
|
||||
|
||||
@@ -88,7 +88,11 @@ AgentRun YAML-only lane closeout 必须同时看当前 k8s git-mirror source sna
|
||||
|
||||
JD01/NC01 `agentrun-<node>-v02` 已迁移为单一路径:GitHub PR merge -> GitHub webhook -> Gitea controlled mirror -> immutable snapshot -> Gitea webhook -> Pipelines-as-Code -> Tekton -> GitOps/Argo -> k8s runtime。不要保留 Gitea Actions、act_runner、legacy branch-follower 或第二套 trigger fallback。CLI 必须组合 `config/platform-infra/gitea.yaml` 与 `config/platform-infra/pipelines-as-code.yaml` 精确解析 consumer;未知、歧义或 source identity drift 都必须 `mutation=false` 并 fail-closed。
|
||||
|
||||
Gitea 只作为受控 mirror/source authority 和 PaC webhook 源。公开 Web UI 是 `https://gitea.pikapython.com`;k8s 内部 clone/read 必须走 `http://gitea-http.devops-infra.svc.cluster.local:3000/...`,不要让内部 CI、Argo 或 runtime 通过公网域名回环。需要 CI 匿名内部 read 的 mirror repo 必须在 YAML 中声明 `publicRead: true`,bootstrap/apply 必须修正既有 repo/org visibility,而不只依赖 create-time 默认值。
|
||||
- Gitea 只作为受控 mirror/source authority 和 PaC webhook 源:
|
||||
- 公开 Web UI 是 `https://gitea.hwpod.com`,由 NC01 public-edge 直连 ClusterIP;
|
||||
- k8s 内部 clone/read 必须走 `http://gitea-http.devops-infra.svc.cluster.local:3000/...`,不得让内部 CI、Argo 或 runtime 通过公网域名回环;
|
||||
- 需要 CI 匿名内部 read 的 mirror repo 必须在 YAML 中声明 `publicRead: true`;
|
||||
- bootstrap/apply 必须修正既有 repo/org visibility,不能只依赖 create-time 默认值。
|
||||
|
||||
PaC 默认只读状态入口是:
|
||||
|
||||
|
||||
@@ -40,7 +40,12 @@ GitHub 是唯一上游写入权威。目标 source branch 的 GitHub PR merge
|
||||
- 旧人工 mutation 入口只允许在 owning YAML 精确解析为 `legacy-manual` 后从 scoped help 使用;Gitea/PaC migrated consumer 和 `unknown` authority 都不得回退到 legacy 实现。
|
||||
- 包含 `gitea-actions` 的历史 snapshot prefix 只为既有 ref 保留,不代表 Gitea Actions 仍是有效触发架构。
|
||||
- node 级状态入口是 `bun scripts/cli.ts cicd status --node <NODE>`,consumer 级默认下钻入口是 `platform-infra pipelines-as-code status|history --target <NODE> --consumer <id>`。
|
||||
- 共用 GitHub 上游的多 node PaC consumer 不得共用 webhook bridge URL。每个 target node 在 `config/platform-infra/gitea.yaml#targets[].webhookSync` 声明自己的 GitHub webhook 公网路径与 FRP remote port,并由平台自动交付渲染 node-local bridge、FRPC proxy 和 PK01 Caddy 路径;禁止在 source PR 合并后人工 apply 补齐。
|
||||
- Gitea 公网入口:
|
||||
- 公开 Web UI 与 owning webhook path 统一由 `config/platform-infra/gitea.yaml#app.publicExposure` 声明;
|
||||
- NC01 public-edge 直接连接 ClusterIP Service;
|
||||
- 不得为 Gitea 保留 FRPC、PK01 Caddy 或公网回环 fallback;
|
||||
- target 仍通过 `targets[].webhookSync.publicPath` 保持 hook 身份隔离;
|
||||
- 禁止在 source PR 合并后人工 apply 补齐。
|
||||
- PaC Repository CR `spec.url` 必须与 Gitea webhook payload 中的 URL 一致。`config/platform-infra/pipelines-as-code.yaml#repositories[].url` 保持公网 Gitea repository URL,k8s 内网 service URL 只能写入 `cloneUrl` 或 `params.git_read_url`;否则 PaC 会记录 `cannot find a repository match` 且不会创建 PipelineRun。
|
||||
- JD01 与 NC01 共用的 repository 必须传入 target-specific `node` Repository param,每个 `.tekton` PipelineRun 必须用 `pipelinesascode.tekton.dev/on-cel-expression` 过滤该参数。一个 target 的 push 不得在同一 target cluster 创建另一 target 的 PipelineRun。
|
||||
|
||||
@@ -211,7 +216,12 @@ GitHub webhook delivery 的持久接收与 refs 提交必须分层:
|
||||
- 只有同一 delivery 的 exact-after immutable snapshot 与 authority branch 经 atomic push 后重新读取 refs 证明一致,才能进入 `committed`;
|
||||
- Caddy 只可对尚未成功落盘的请求做小于 10 秒、body-safe 的有界重试,不能把代理成功当作 refs committed。
|
||||
|
||||
当前 bridge 的权威状态链是 GitHub delivery GUID -> durable inbox record -> post-push refs proof -> live refs。HTTP `202` 后记录仍是 `accepted`、`refsCommitted=false`;只有 worker 完成 exact refs 复核后才成为 committed。旧 durable journal 上线前的记录可以标记 `pre-durable-bootstrap`,但不得标记 committed。`ingressRetry` 在自动 PK01 host reconciler 完成前保持关闭,不能由操作者手工补配置。
|
||||
- 当前 bridge 的权威状态链:
|
||||
- GitHub delivery GUID -> durable inbox record -> post-push refs proof -> live refs;
|
||||
- HTTP `202` 后记录仍是 `accepted`、`refsCommitted=false`;
|
||||
- 只有 worker 完成 exact refs 复核后才成为 committed;
|
||||
- 旧 durable journal 上线前的记录可以标记 `pre-durable-bootstrap`,但不得标记 committed;
|
||||
- `ingressRetry` 由 owning YAML 与 NC01 public-edge 自动交付共同控制,不能由操作者手工补配置。
|
||||
|
||||
Durable inbox 只是 webhook delivery 的持久接收与重试 journal,不是业务 source/ref 的第二份真相,也不得被 PaC、Tekton、GitOps、Argo 或 runtime 当作源码/read model。最终 source authority 仍只有 Gitea authority branch 与 immutable snapshot。`webhook status` 默认摘要必须显示 accepted -> processing -> committed/failed 分层,并将同一 delivery 的 accepted correlation 与 committed refs proof 关联。非 `202`、缺 accepted correlation、accepted 后未 committed 或进入 failed 都是失败/stale;只能修 webhook bridge、inbox worker、受控 mirror、snapshot controller 或 owning YAML,禁止输出或执行人工 `REPAIR`、mirror sync、直接 Gitea push 等补齐命令。
|
||||
|
||||
|
||||
@@ -176,9 +176,6 @@ sourceAuthority:
|
||||
email: platform-infra-ci@unidesk.local
|
||||
cas:
|
||||
maxAttempts: 3
|
||||
frpc:
|
||||
proxyName: platform-infra-gitea-jd01-github-webhook
|
||||
remotePort: 22081
|
||||
responsibilities:
|
||||
- name: source-read
|
||||
current: legacy-git-mirror
|
||||
@@ -669,9 +666,6 @@ targets:
|
||||
enabled: true
|
||||
webhookSync:
|
||||
publicPath: /_unidesk/github-to-gitea/nc01
|
||||
frpc:
|
||||
proxyName: platform-infra-gitea-nc01-github-webhook
|
||||
remotePort: 22086
|
||||
|
||||
app:
|
||||
name: gitea
|
||||
@@ -687,37 +681,29 @@ app:
|
||||
httpPort: 3000
|
||||
sshPort: 2222
|
||||
server:
|
||||
domain: gitea.pikapython.com
|
||||
rootUrl: https://gitea.pikapython.com/
|
||||
domain: gitea.hwpod.com
|
||||
rootUrl: https://gitea.hwpod.com/
|
||||
sshDomain: gitea-http.devops-infra.svc.cluster.local
|
||||
protocol: http
|
||||
startSshServer: true
|
||||
publicExposure:
|
||||
enabled: true
|
||||
publicBaseUrl: https://gitea.pikapython.com
|
||||
secretRoot: /root/.unidesk/.state/secrets
|
||||
publicBaseUrl: https://gitea.hwpod.com
|
||||
upstream: http://gitea-http.devops-infra.svc.cluster.local:3000
|
||||
healthPath: /api/healthz
|
||||
dns:
|
||||
hostname: gitea.pikapython.com
|
||||
expectedA: 82.156.23.220
|
||||
hostname: gitea.hwpod.com
|
||||
expectedA: 152.53.229.148
|
||||
resolvers: [1.1.1.1, 8.8.8.8, 223.5.5.5, 114.114.114.114]
|
||||
frpc:
|
||||
routes:
|
||||
- path: /_unidesk/github-to-gitea/nc01*
|
||||
upstream: http://gitea-github-sync.devops-infra.svc.cluster.local:8080
|
||||
responseHeaderTimeout: 7s
|
||||
- path: "*"
|
||||
upstream: http://gitea-http.devops-infra.svc.cluster.local:3000
|
||||
legacyCleanup:
|
||||
deploymentName: gitea-frpc
|
||||
secretName: gitea-frpc-secrets
|
||||
secretKey: frpc.toml
|
||||
image: fatedier/frpc:v0.68.1
|
||||
serverAddr: 82.156.23.220
|
||||
serverPort: 22000
|
||||
proxyName: platform-infra-gitea-nc01-web
|
||||
remotePort: 22080
|
||||
localIP: gitea-http.devops-infra.svc.cluster.local
|
||||
localPort: 3000
|
||||
tokenSourceRef: platform-infra/pk01-frp.env
|
||||
tokenSourceKey: FRP_TOKEN
|
||||
pk01:
|
||||
route: PK01
|
||||
caddyConfigPath: /etc/caddy/Caddyfile
|
||||
caddyServiceName: caddy
|
||||
responseHeaderTimeoutSeconds: 600
|
||||
database:
|
||||
type: sqlite3
|
||||
path: /var/lib/gitea/gitea.db
|
||||
|
||||
@@ -146,7 +146,7 @@ repositories:
|
||||
variables:
|
||||
NODE: JD01
|
||||
LANE: v02
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-agentrun
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-agentrun
|
||||
- extends: templates.repositories.agentrunV02
|
||||
variables:
|
||||
NODE: NC01
|
||||
@@ -156,7 +156,7 @@ repositories:
|
||||
NODE: JD01
|
||||
LANE: v03
|
||||
sourceSnapshotPrefix: refs/unidesk/snapshots/gitea-actions/unidesk-master
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-unidesk
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-unidesk
|
||||
- extends: templates.repositories.sentinelV03
|
||||
variables:
|
||||
NODE: NC01
|
||||
@@ -177,7 +177,7 @@ repositories:
|
||||
name: sub2rank-nc01
|
||||
namespace: devops-infra
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-apistate
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-apistate
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-apistate.git
|
||||
owner: mirrors
|
||||
repo: pikasTech-apistate
|
||||
@@ -209,7 +209,7 @@ repositories:
|
||||
name: sub2rank-development-nc01
|
||||
namespace: devops-infra
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-apistate-development
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-apistate-development
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-apistate-development.git
|
||||
owner: mirrors
|
||||
repo: pikasTech-apistate-development
|
||||
@@ -241,7 +241,7 @@ repositories:
|
||||
name: hwpod-home-nc01
|
||||
namespace: devops-infra
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikainc-hwpod-home
|
||||
url: https://gitea.hwpod.com/mirrors/pikainc-hwpod-home
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikainc-hwpod-home.git
|
||||
owner: mirrors
|
||||
repo: pikainc-hwpod-home
|
||||
@@ -273,7 +273,7 @@ repositories:
|
||||
variables:
|
||||
NODE: JD01
|
||||
LANE: v03
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-HWLAB
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-HWLAB
|
||||
- extends: templates.repositories.hwlabV03
|
||||
variables:
|
||||
NODE: NC01
|
||||
@@ -282,7 +282,7 @@ repositories:
|
||||
name: selfmedia-nc01
|
||||
namespace: selfmedia-ci
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikainc-selfmedia
|
||||
url: https://gitea.hwpod.com/mirrors/pikainc-selfmedia
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikainc-selfmedia.git
|
||||
owner: mirrors
|
||||
repo: pikainc-selfmedia
|
||||
@@ -317,7 +317,7 @@ repositories:
|
||||
name: pikaoa-dev-nc01
|
||||
namespace: pikaoa-ci
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikainc-pikaoa
|
||||
url: https://gitea.hwpod.com/mirrors/pikainc-pikaoa
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikainc-pikaoa.git
|
||||
owner: mirrors
|
||||
repo: pikainc-pikaoa
|
||||
@@ -822,7 +822,7 @@ templates:
|
||||
name: "agentrun-${nodeLower}-v02"
|
||||
namespace: agentrun-ci
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-agentrun
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-agentrun
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-agentrun.git
|
||||
owner: mirrors
|
||||
repo: pikasTech-agentrun
|
||||
@@ -846,7 +846,7 @@ templates:
|
||||
name: "sentinel-${nodeLower}-v03"
|
||||
namespace: devops-infra
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-unidesk
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-unidesk
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-unidesk.git
|
||||
owner: mirrors
|
||||
repo: pikasTech-unidesk
|
||||
@@ -877,7 +877,7 @@ templates:
|
||||
name: "hwlab-${nodeLower}-v03"
|
||||
namespace: hwlab-ci
|
||||
providerType: gitea
|
||||
url: https://gitea.pikapython.com/mirrors/pikasTech-HWLAB
|
||||
url: https://gitea.hwpod.com/mirrors/pikasTech-HWLAB
|
||||
cloneUrl: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-HWLAB.git
|
||||
owner: mirrors
|
||||
repo: pikasTech-HWLAB
|
||||
|
||||
@@ -69,6 +69,9 @@ targets:
|
||||
# 共享入口包含长耗时流式 API,首响应头预算统一按 10 分钟配置。
|
||||
responseHeaderTimeoutSeconds: 600
|
||||
sites:
|
||||
- id: gitea
|
||||
configRef: config/platform-infra/gitea.yaml
|
||||
path: app.publicExposure
|
||||
- id: artifact-registry
|
||||
configRef: config/platform-infra/k3s-clusters.yaml
|
||||
path: targets.d601.k3s.registryMirror
|
||||
|
||||
@@ -28,7 +28,13 @@ Network identity is part of the evidence. `127.0.0.1:5000` may mean runner-pod l
|
||||
|
||||
Manual hotfixes are allowed only to recover and learn the missing contract. The durable close-out is a repo-owned preflight, desired-state check, CLI behavior, or reference update, followed by the same verification from the actual runner environment. A healthy old live service does not prove CD success; success requires the formal job to pass and live commit or digest identity to match the desired state.
|
||||
|
||||
For node-scoped HWLAB/Gitea/PaC lanes, CI/CD closeout stops at PipelineRun success, GitOps/Argo sync and health, runtime readiness/provenance, git-mirror alignment, and the formal public HTTPS `/health` probe declared by YAML. Browser automation, `web-probe`, Playwright, DOM assertions and user-path E2E are post-deploy product validation, not CI/CD gates. FRP/Caddy public exposure failures are diagnosed by aligning YAML publicExposure, GitOps-rendered frpc config, Secret key presence, frpc login/proxy logs, PK01/Caddy loopback and target-side HTTPS probe; Secret values are never recorded in docs or issue comments.
|
||||
- 对于 node-scoped HWLAB/Gitea/PaC lane:
|
||||
- CI/CD 收口只覆盖 PipelineRun 成功、GitOps/Argo 同步与健康、runtime readiness/provenance、git-mirror 对齐,以及 YAML 声明的正式公网 HTTPS `/health` 探针;
|
||||
- 浏览器自动化、`web-probe`、Playwright、DOM 断言和用户路径 E2E 属于部署后的产品验收,不是 CI/CD 门禁;
|
||||
- 公网暴露故障必须按 owning YAML 选中的 managed edge 诊断;
|
||||
- Gitea 通过 NC01 public-edge 直连 ClusterIP;
|
||||
- 仍由 YAML 选择 FRP 的服务继续检查各自声明的 FRP/Caddy 路径;
|
||||
- Secret 值不得写入文档或 issue 评论。
|
||||
|
||||
## Target Shape
|
||||
|
||||
|
||||
@@ -180,7 +180,10 @@
|
||||
|
||||
## Gitea 与 Pipelines-as-Code 边界
|
||||
|
||||
- Gitea mirror 与 Pipelines-as-Code 是 UniDesk 运维的 CI source/trigger 服务。`config/platform-infra/gitea.yaml` 和 `config/platform-infra/pipelines-as-code.yaml` 分别拥有 mirror/webhook 与 PaC Repository/consumer 配置;repo URL、snapshot、webhook、public exposure、FRP/Caddy port、token sourceRef 和 PaC params 不得隐藏在代码特例中。
|
||||
- Gitea mirror 与 Pipelines-as-Code 是 UniDesk 运维的 CI source/trigger 服务:
|
||||
- `config/platform-infra/gitea.yaml` 拥有 mirror、webhook 与 NC01 public-edge 暴露;
|
||||
- `config/platform-infra/pipelines-as-code.yaml` 拥有 PaC Repository 与 consumer 配置;
|
||||
- repo URL、snapshot、webhook、token sourceRef 和 PaC params 不得隐藏在代码特例中。
|
||||
- Delivery authority resolver 必须组合上述两份 YAML,并按 consumer id、node、lane、upstream repository、branch 和 Gitea repository 精确关联。不得通过 URL 片段、repo 名称前缀或仓库专属 `if` 推断迁移状态。零匹配、多匹配和配置错误都必须返回 `unknown`、`mutation=false` 并停止生成或执行写操作。
|
||||
- PaC consumer 的唯一正式触发是目标 source branch 的 GitHub PR merge。完整自动链固定为:GitHub PR merge -> GitHub webhook -> Gitea controlled mirror -> immutable snapshot -> Gitea webhook -> PaC/Pipelines-as-Code -> Tekton -> GitOps/Argo -> runtime 运行面。
|
||||
- PR 合并后的交付边界:
|
||||
@@ -231,8 +234,15 @@
|
||||
- cache export 使用原生 `ignore-error=true` 或等价非阻塞语义;
|
||||
- cache export 抖动不得在镜像已发布后制造业务构建失败或人工补跑需求。
|
||||
- GitHub 始终是 upstream write authority。Bridge 只允许 GitHub -> Gitea 更新 YAML 声明的 branch 与 immutable snapshot,不得反向写 GitHub,也不得增加 polling 或第二 trigger path。
|
||||
- 每个 target 必须使用 YAML 独立声明的 webhook path、bridge 与 FRP port;多个节点不得复用 hook URL。Public Gitea Web UI 与 webhook 只能通过明确 Caddy path routing 共享 hostname,k8s 内部 consumer 必须使用 ClusterIP URL。
|
||||
- Secret 或 ConfigMap 改变后,Gitea connector 的显式平台配置维护必须等待 workload rollout。成功写 Secret 不能证明 `frpc` 已加载新配置;端到端交付证据仍必须来自之后的新正常 PR merge。
|
||||
- Gitea 公网入口:
|
||||
- 公开 Web UI 与 owning webhook path 由 `app.publicExposure.routes` 明确分流;
|
||||
- NC01 public-edge 直接连接 ClusterIP Service;
|
||||
- Gitea 不得依赖 PK01、FRPC、Ingress、NodePort 或公网域名回环;
|
||||
- k8s 内部 consumer 必须使用 ClusterIP URL。
|
||||
- Secret 或 ConfigMap 改变后的收口:
|
||||
- Gitea connector 的显式平台配置维护必须等待 workload rollout;
|
||||
- 成功写 Secret 或生成 public-edge 候选不能证明 bridge 已加载新配置;
|
||||
- 端到端交付证据仍必须来自之后的新正常 PR merge。
|
||||
- Shared upstream repository 必须通过 Repository CR params 与 `pipelinesascode.tekton.dev/on-cel-expression` 隔离 target-specific `.tekton`。NC01 delivery 不得在 JD01 cluster 创建 PipelineRun,history 也不得把同一 PipelineRun 归属给多个 consumer。退役 branch-follower 的 unknown 或多 node scope 只返回 follower 自身的只读 status/events/logs 和稳定修复引用,禁止猜测 `JD01` 或其他默认 node 的 PaC status/history。
|
||||
- PaC migrated lane 保持单 trigger path,禁止新增 Gitea Actions、`act_runner`、branch-follower 或脚本 fallback。`config/cicd-gitea-actions-poc.yaml` 只是归档 POC;历史 snapshot 名称不代表该路径仍在运行。
|
||||
- k8s runtime 从拉取已构建 image 起保持 Docker-free。CI 可以使用 YAML 声明的 build tooling,但 runtime 不得依赖 Docker socket 或 daemon。
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { dirname } from "node:path";
|
||||
import { rootPath } from "./config";
|
||||
import { createYamlFieldReader, readYamlRecord } from "./platform-infra-ops-library";
|
||||
import type { PublicServiceExposure } from "./platform-infra-public-service";
|
||||
import { validateGiteaWebhookTiming } from "./platform-infra-gitea-caddy";
|
||||
import type { GiteaWebhookIngressRetry } from "./platform-infra-gitea-caddy";
|
||||
|
||||
@@ -24,9 +23,26 @@ export interface GiteaTarget {
|
||||
|
||||
export interface GiteaTargetWebhookSync {
|
||||
publicPath: string;
|
||||
frpc: {
|
||||
proxyName: string;
|
||||
remotePort: number;
|
||||
}
|
||||
|
||||
export interface GiteaPublicExposure {
|
||||
enabled: boolean;
|
||||
publicBaseUrl: string;
|
||||
upstream: string;
|
||||
healthPath: string;
|
||||
dns: {
|
||||
hostname: string;
|
||||
expectedA: string;
|
||||
resolvers: string[];
|
||||
};
|
||||
routes: Array<{
|
||||
path: string;
|
||||
upstream: string;
|
||||
responseHeaderTimeout?: string;
|
||||
}>;
|
||||
legacyCleanup: {
|
||||
deploymentName: string;
|
||||
secretName: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,7 +108,7 @@ export interface GiteaConfig {
|
||||
protocol: "http" | "https";
|
||||
startSshServer: boolean;
|
||||
};
|
||||
publicExposure: PublicServiceExposure & { secretRoot: string };
|
||||
publicExposure: GiteaPublicExposure;
|
||||
database: {
|
||||
type: "sqlite3";
|
||||
path: string;
|
||||
@@ -238,10 +254,6 @@ export interface GiteaWebhookSync {
|
||||
};
|
||||
};
|
||||
gitOpsDelivery: GiteaWebhookGitOpsDelivery;
|
||||
frpc: {
|
||||
proxyName: string;
|
||||
remotePort: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface GiteaWebhookGitOpsDelivery {
|
||||
@@ -490,7 +502,6 @@ function parseWebhookSync(record: Record<string, unknown>, path: string): GiteaW
|
||||
const candidateGate = y.objectField(bridge, "candidateGate", `${path}.bridge`);
|
||||
const inbox = y.objectField(bridge, "inbox", `${path}.bridge`);
|
||||
const gitOpsDelivery = y.objectField(record, "gitOpsDelivery", path);
|
||||
const frpc = y.objectField(record, "frpc", path);
|
||||
return {
|
||||
enabled: y.booleanField(record, "enabled", path),
|
||||
direction: y.enumField(record, "direction", path, ["github-to-gitea"] as const),
|
||||
@@ -560,10 +571,6 @@ function parseWebhookSync(record: Record<string, unknown>, path: string): GiteaW
|
||||
retry: parseWebhookRetry(y.objectField(bridge, "retry", `${path}.bridge`), `${path}.bridge.retry`),
|
||||
},
|
||||
gitOpsDelivery: parseWebhookGitOpsDelivery(gitOpsDelivery, `${path}.gitOpsDelivery`),
|
||||
frpc: {
|
||||
proxyName: y.stringField(frpc, "proxyName", `${path}.frpc`),
|
||||
remotePort: y.portField(frpc, "remotePort", `${path}.frpc`),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -612,41 +619,36 @@ function parseWebhookRetry(record: Record<string, unknown>, path: string): Gitea
|
||||
};
|
||||
}
|
||||
|
||||
function parsePublicExposure(record: Record<string, unknown>, path: string): PublicServiceExposure & { secretRoot: string } {
|
||||
function parsePublicExposure(record: Record<string, unknown>, path: string): GiteaPublicExposure {
|
||||
const dns = y.objectField(record, "dns", path);
|
||||
const frpc = y.objectField(record, "frpc", path);
|
||||
const pk01 = y.objectField(record, "pk01", path);
|
||||
const legacyCleanup = y.objectField(record, "legacyCleanup", path);
|
||||
const publicBaseUrl = httpsUrlField(record, "publicBaseUrl", path);
|
||||
const hostname = y.hostField(dns, "hostname", `${path}.dns`);
|
||||
if (new URL(publicBaseUrl).hostname !== hostname) throw new Error(`${configLabel}.${path}.dns.hostname must match publicBaseUrl`);
|
||||
const routes = y.arrayOfRecords(record.routes, `${path}.routes`).map((route, index) => {
|
||||
const routePath = `${path}.routes[${index}]`;
|
||||
return {
|
||||
path: y.stringField(route, "path", routePath),
|
||||
upstream: urlField(route, "upstream", routePath),
|
||||
...(route.responseHeaderTimeout === undefined
|
||||
? {}
|
||||
: { responseHeaderTimeout: y.stringField(route, "responseHeaderTimeout", routePath) }),
|
||||
};
|
||||
});
|
||||
return {
|
||||
enabled: y.booleanField(record, "enabled", path),
|
||||
publicBaseUrl,
|
||||
secretRoot: y.absolutePathField(record, "secretRoot", path),
|
||||
upstream: urlField(record, "upstream", path),
|
||||
healthPath: y.apiPathField(record, "healthPath", path),
|
||||
dns: {
|
||||
hostname,
|
||||
expectedA: y.stringField(dns, "expectedA", `${path}.dns`),
|
||||
resolvers: y.stringArrayField(dns, "resolvers", `${path}.dns`),
|
||||
},
|
||||
frpc: {
|
||||
deploymentName: y.kubernetesNameField(frpc, "deploymentName", `${path}.frpc`),
|
||||
secretName: y.kubernetesNameField(frpc, "secretName", `${path}.frpc`),
|
||||
secretKey: y.stringField(frpc, "secretKey", `${path}.frpc`),
|
||||
image: y.stringField(frpc, "image", `${path}.frpc`),
|
||||
serverAddr: y.hostField(frpc, "serverAddr", `${path}.frpc`),
|
||||
serverPort: y.portField(frpc, "serverPort", `${path}.frpc`),
|
||||
proxyName: y.stringField(frpc, "proxyName", `${path}.frpc`),
|
||||
remotePort: y.portField(frpc, "remotePort", `${path}.frpc`),
|
||||
localIP: y.hostField(frpc, "localIP", `${path}.frpc`),
|
||||
localPort: y.portField(frpc, "localPort", `${path}.frpc`),
|
||||
tokenSourceRef: secretSourceRefField(frpc, "tokenSourceRef", `${path}.frpc`),
|
||||
tokenSourceKey: y.envKeyField(frpc, "tokenSourceKey", `${path}.frpc`),
|
||||
},
|
||||
pk01: {
|
||||
route: y.stringField(pk01, "route", `${path}.pk01`),
|
||||
caddyConfigPath: y.absolutePathField(pk01, "caddyConfigPath", `${path}.pk01`),
|
||||
caddyServiceName: y.stringField(pk01, "caddyServiceName", `${path}.pk01`),
|
||||
responseHeaderTimeoutSeconds: y.integerField(pk01, "responseHeaderTimeoutSeconds", `${path}.pk01`),
|
||||
routes,
|
||||
legacyCleanup: {
|
||||
deploymentName: y.kubernetesNameField(legacyCleanup, "deploymentName", `${path}.legacyCleanup`),
|
||||
secretName: y.kubernetesNameField(legacyCleanup, "secretName", `${path}.legacyCleanup`),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -745,13 +747,8 @@ function parseTarget(record: Record<string, unknown>, index: number): GiteaTarge
|
||||
}
|
||||
|
||||
function parseTargetWebhookSync(record: Record<string, unknown>, path: string): GiteaTargetWebhookSync {
|
||||
const frpc = y.objectField(record, "frpc", path);
|
||||
return {
|
||||
publicPath: y.apiPathField(record, "publicPath", path),
|
||||
frpc: {
|
||||
proxyName: y.stringField(frpc, "proxyName", `${path}.frpc`),
|
||||
remotePort: y.portField(frpc, "remotePort", `${path}.frpc`),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -811,7 +808,6 @@ function validateConfig(gitea: GiteaConfig): void {
|
||||
if (gitea.sourceAuthority.webhookSync.enabled) {
|
||||
const events = new Set(gitea.sourceAuthority.webhookSync.events);
|
||||
if (!events.has("push")) throw new Error(`${configLabel}.sourceAuthority.webhookSync.events must include push for GitHub -> Gitea source sync`);
|
||||
if (gitea.sourceAuthority.webhookSync.frpc.remotePort === gitea.app.publicExposure.frpc.remotePort) throw new Error(`${configLabel}.sourceAuthority.webhookSync.frpc.remotePort must differ from app.publicExposure.frpc.remotePort`);
|
||||
const ingressRetry = gitea.sourceAuthority.webhookSync.ingressRetry;
|
||||
const hookReconcile = gitea.sourceAuthority.webhookSync.hookReconcile;
|
||||
const bridge = gitea.sourceAuthority.webhookSync.bridge;
|
||||
@@ -865,14 +861,15 @@ function validateConfig(gitea: GiteaConfig): void {
|
||||
}
|
||||
}
|
||||
const webhookPaths = new Set<string>();
|
||||
const webhookPorts = new Set<number>();
|
||||
for (const route of webhookCaddyRoutes(gitea)) {
|
||||
if (webhookPaths.has(route.publicPath)) throw new Error(`${configLabel} webhook publicPath must be unique: ${route.publicPath}`);
|
||||
if (webhookPorts.has(route.remotePort)) throw new Error(`${configLabel} webhook frpc.remotePort must be unique: ${route.remotePort}`);
|
||||
if (route.remotePort === gitea.app.publicExposure.frpc.remotePort) throw new Error(`${configLabel} webhook frpc.remotePort must differ from app public exposure port: ${route.remotePort}`);
|
||||
webhookPaths.add(route.publicPath);
|
||||
webhookPorts.add(route.remotePort);
|
||||
for (const publicPath of webhookPublicPaths(gitea)) {
|
||||
if (webhookPaths.has(publicPath)) throw new Error(`${configLabel} webhook publicPath must be unique: ${publicPath}`);
|
||||
webhookPaths.add(publicPath);
|
||||
}
|
||||
const ownerTarget = resolveTarget(gitea, gitea.sourceAuthority.webhookSync.hookReconcile.ownerTargetId);
|
||||
const ownerSync = targetWebhookSync(gitea, ownerTarget);
|
||||
const webhookRoute = gitea.app.publicExposure.routes.find((route) => route.path === `${ownerSync.publicPath}*`);
|
||||
if (webhookRoute === undefined) throw new Error(`${configLabel}.app.publicExposure.routes must expose the owner webhook path ${ownerSync.publicPath}*`);
|
||||
if (gitea.app.publicExposure.routes.at(-1)?.path !== "*") throw new Error(`${configLabel}.app.publicExposure.routes must end with the Gitea fallback route`);
|
||||
const repoKeys = new Set<string>();
|
||||
for (const repo of gitea.sourceAuthority.repositories) {
|
||||
if (repoKeys.has(repo.key)) throw new Error(`${configLabel}.sourceAuthority.repositories contains duplicate key ${repo.key}`);
|
||||
@@ -899,21 +896,19 @@ export function targetWebhookSync(gitea: GiteaConfig, target: GiteaTarget): Gite
|
||||
return {
|
||||
...base,
|
||||
publicPath: target.webhookSync.publicPath,
|
||||
frpc: target.webhookSync.frpc,
|
||||
};
|
||||
}
|
||||
|
||||
export function webhookCaddyRoutes(gitea: GiteaConfig): Array<{ publicPath: string; remotePort: number }> {
|
||||
export function webhookPublicPaths(gitea: GiteaConfig): string[] {
|
||||
const sync = gitea.sourceAuthority.webhookSync;
|
||||
if (!sync.enabled) return [];
|
||||
const routes = new Map<string, { publicPath: string; remotePort: number }>();
|
||||
routes.set(sync.publicPath, { publicPath: sync.publicPath, remotePort: sync.frpc.remotePort });
|
||||
const paths = [sync.publicPath];
|
||||
for (const target of gitea.targets) {
|
||||
if (!target.enabled || target.webhookSync === null) continue;
|
||||
const targetSync = targetWebhookSync(gitea, target);
|
||||
routes.set(targetSync.publicPath, { publicPath: targetSync.publicPath, remotePort: targetSync.frpc.remotePort });
|
||||
paths.push(targetSync.publicPath);
|
||||
}
|
||||
return [...routes.values()];
|
||||
return paths;
|
||||
}
|
||||
|
||||
function positiveInteger(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
|
||||
@@ -27,7 +27,6 @@ test("stdin file envelope materializes a payload larger than the current Gitea m
|
||||
repositories: JSON.stringify([{ key: "sentinel", branch: "master" }]),
|
||||
hookTopology: JSON.stringify({ version: 1, repositories: [] }),
|
||||
statusEvaluator: "print('ok')\n",
|
||||
frpcToml: "serverAddr = '127.0.0.1'\n",
|
||||
};
|
||||
const materializer = renderGiteaRemotePayloadMaterializer(payloads);
|
||||
const result = spawnSync("sh", ["-s"], {
|
||||
@@ -70,6 +69,8 @@ test("Gitea remote runner consumes materialized files instead of base64 environm
|
||||
expect(source).not.toContain("UNIDESK_GITEA_STATUS_EVALUATOR_B64");
|
||||
expect(source).not.toContain("UNIDESK_GITEA_REPOS_B64");
|
||||
expect(source).not.toContain("UNIDESK_GITEA_FRPC_TOML_B64");
|
||||
expect(source).not.toContain("UNIDESK_GITEA_FRPC_ENABLED");
|
||||
expect(source).not.toContain("frpc-rollout");
|
||||
expect(source).toContain("kubectl apply --server-side --force-conflicts --dry-run=server");
|
||||
expect(source).toContain("target server dry-run failed; manifest apply skipped");
|
||||
expect(source.indexOf("kubectl apply --server-side --force-conflicts --dry-run=server")).toBeLessThan(
|
||||
|
||||
@@ -5,7 +5,6 @@ export interface GiteaRemotePayloads {
|
||||
repositories: string;
|
||||
hookTopology: string;
|
||||
statusEvaluator: string;
|
||||
frpcToml: string;
|
||||
}
|
||||
|
||||
interface GiteaRemotePayloadFile {
|
||||
@@ -19,7 +18,6 @@ const payloadFiles = (payloads: GiteaRemotePayloads): GiteaRemotePayloadFile[] =
|
||||
{ role: "repositories", name: "repos.json", value: payloads.repositories },
|
||||
{ role: "hookTopology", name: "hook-topology.json", value: payloads.hookTopology },
|
||||
{ role: "statusEvaluator", name: "platform_infra_gitea_status_evaluator.py", value: payloads.statusEvaluator },
|
||||
{ role: "frpcToml", name: "frpc.toml", value: payloads.frpcToml },
|
||||
];
|
||||
|
||||
function payloadMarker(file: GiteaRemotePayloadFile): string {
|
||||
|
||||
@@ -80,26 +80,12 @@ run_apply() {
|
||||
if [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
kubectl create namespace "$UNIDESK_GITEA_NAMESPACE" --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f - >"$tmp/namespace.out" 2>"$tmp/namespace.err"
|
||||
fi
|
||||
if [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
kubectl -n "$UNIDESK_GITEA_NAMESPACE" create secret generic "$UNIDESK_GITEA_FRPC_SECRET_NAME" \
|
||||
--from-file="$UNIDESK_GITEA_FRPC_SECRET_KEY=$tmp/frpc.toml" \
|
||||
--dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f - >"$tmp/frpc-secret.out" 2>"$tmp/frpc-secret.err"
|
||||
frpc_rc=$?
|
||||
else
|
||||
frpc_rc=0
|
||||
: >"$tmp/frpc-secret.out"
|
||||
if [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ]; then
|
||||
printf '%s\n' "dry-run: frpc secret apply skipped" >"$tmp/frpc-secret.err"
|
||||
else
|
||||
printf '%s\n' "frpc disabled" >"$tmp/frpc-secret.err"
|
||||
fi
|
||||
fi
|
||||
upsert_webhook_sync_secret
|
||||
webhook_secret_rc=$?
|
||||
timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl apply --server-side --force-conflicts --dry-run=server \
|
||||
--field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f "$manifest" >"$tmp/server-dry-run.out" 2>"$tmp/server-dry-run.err"
|
||||
server_dry_run_rc=$?
|
||||
if [ "$frpc_rc" -eq 0 ] && [ "$webhook_secret_rc" -eq 0 ]; then
|
||||
if [ "$webhook_secret_rc" -eq 0 ]; then
|
||||
if [ "$server_dry_run_rc" -ne 0 ]; then
|
||||
apply_rc=1
|
||||
: >"$tmp/apply.out"
|
||||
@@ -116,7 +102,7 @@ run_apply() {
|
||||
else
|
||||
apply_rc=1
|
||||
: >"$tmp/apply.out"
|
||||
printf '%s\n' "frpc or webhook secret apply failed; manifest apply skipped" >"$tmp/apply.err"
|
||||
printf '%s\n' "webhook secret apply failed; manifest apply skipped" >"$tmp/apply.err"
|
||||
fi
|
||||
if [ "$apply_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WAIT" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout status "statefulset/$UNIDESK_GITEA_STATEFULSET_NAME" --timeout="${UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS}s" >"$tmp/rollout.out" 2>"$tmp/rollout.err"
|
||||
@@ -130,25 +116,7 @@ run_apply() {
|
||||
printf '%s\n' "rollout wait not requested" >"$tmp/rollout.err"
|
||||
fi
|
||||
fi
|
||||
if [ "$apply_rc" -eq 0 ] && [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout restart "deployment/$UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME" >"$tmp/frpc-rollout.out" 2>"$tmp/frpc-rollout.err"
|
||||
frpc_rollout_rc=$?
|
||||
if [ "$frpc_rollout_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WAIT" = "1" ]; then
|
||||
timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout status "deployment/$UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME" --timeout="${UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS}s" >>"$tmp/frpc-rollout.out" 2>>"$tmp/frpc-rollout.err"
|
||||
frpc_rollout_rc=$?
|
||||
fi
|
||||
else
|
||||
frpc_rollout_rc=0
|
||||
: >"$tmp/frpc-rollout.out"
|
||||
if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then
|
||||
printf '%s\n' "dry-run: frpc rollout skipped" >"$tmp/frpc-rollout.err"
|
||||
elif [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ]; then
|
||||
printf '%s\n' "manifest apply failed; frpc rollout skipped" >"$tmp/frpc-rollout.err"
|
||||
else
|
||||
printf '%s\n' "frpc disabled" >"$tmp/frpc-rollout.err"
|
||||
fi
|
||||
fi
|
||||
if [ "$UNIDESK_GITEA_FRPC_ENABLED" = "0" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
if [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
kubectl -n "$UNIDESK_GITEA_NAMESPACE" delete deployment "$UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME" --ignore-not-found >"$tmp/frpc-cleanup.out" 2>"$tmp/frpc-cleanup.err"
|
||||
frpc_cleanup_deploy_rc=$?
|
||||
kubectl -n "$UNIDESK_GITEA_NAMESPACE" delete secret "$UNIDESK_GITEA_FRPC_SECRET_NAME" --ignore-not-found >>"$tmp/frpc-cleanup.out" 2>>"$tmp/frpc-cleanup.err"
|
||||
@@ -163,10 +131,6 @@ run_apply() {
|
||||
: >"$tmp/frpc-cleanup.out"
|
||||
if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then
|
||||
printf '%s\n' "dry-run: frpc cleanup skipped" >"$tmp/frpc-cleanup.err"
|
||||
elif [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ]; then
|
||||
printf '%s\n' "frpc enabled" >"$tmp/frpc-cleanup.err"
|
||||
else
|
||||
printf '%s\n' "frpc cleanup skipped" >"$tmp/frpc-cleanup.err"
|
||||
fi
|
||||
fi
|
||||
if [ "$apply_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WEBHOOK_SYNC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then
|
||||
@@ -187,9 +151,9 @@ run_apply() {
|
||||
printf '%s\n' "webhook sync disabled" >"$tmp/webhook-rollout.err"
|
||||
fi
|
||||
fi
|
||||
python3 - "$frpc_rc" "$webhook_secret_rc" "$server_dry_run_rc" "$apply_rc" "$rollout_rc" "$frpc_rollout_rc" "$webhook_rollout_rc" "$frpc_cleanup_rc" "$tmp/frpc-secret.out" "$tmp/frpc-secret.err" "$tmp/frpc-cleanup.out" "$tmp/frpc-cleanup.err" "$tmp/webhook-secret.out" "$tmp/webhook-secret.err" "$tmp/server-dry-run.out" "$tmp/server-dry-run.err" "$tmp/apply.out" "$tmp/apply.err" "$tmp/rollout.out" "$tmp/rollout.err" "$tmp/frpc-rollout.out" "$tmp/frpc-rollout.err" "$tmp/webhook-rollout.out" "$tmp/webhook-rollout.err" <<'PY'
|
||||
python3 - "$webhook_secret_rc" "$server_dry_run_rc" "$apply_rc" "$rollout_rc" "$webhook_rollout_rc" "$frpc_cleanup_rc" "$tmp/frpc-cleanup.out" "$tmp/frpc-cleanup.err" "$tmp/webhook-secret.out" "$tmp/webhook-secret.err" "$tmp/server-dry-run.out" "$tmp/server-dry-run.err" "$tmp/apply.out" "$tmp/apply.err" "$tmp/rollout.out" "$tmp/rollout.err" "$tmp/webhook-rollout.out" "$tmp/webhook-rollout.err" <<'PY'
|
||||
import json, os, sys
|
||||
frpc_rc, webhook_secret_rc, server_dry_run_rc, apply_rc, rollout_rc, frpc_rollout_rc, webhook_rollout_rc, frpc_cleanup_rc = [int(value) for value in sys.argv[1:9]]
|
||||
webhook_secret_rc, server_dry_run_rc, apply_rc, rollout_rc, webhook_rollout_rc, frpc_cleanup_rc = [int(value) for value in sys.argv[1:7]]
|
||||
def text(path, limit=3000):
|
||||
try:
|
||||
return open(path, encoding="utf-8", errors="replace").read()[-limit:]
|
||||
@@ -197,7 +161,7 @@ def text(path, limit=3000):
|
||||
return ""
|
||||
dry = os.environ.get("UNIDESK_GITEA_DRY_RUN") == "1"
|
||||
payload = {
|
||||
"ok": all(value == 0 for value in [frpc_rc, webhook_secret_rc, server_dry_run_rc, apply_rc, rollout_rc, frpc_rollout_rc, webhook_rollout_rc, frpc_cleanup_rc]),
|
||||
"ok": all(value == 0 for value in [webhook_secret_rc, server_dry_run_rc, apply_rc, rollout_rc, webhook_rollout_rc, frpc_cleanup_rc]),
|
||||
"target": os.environ.get("UNIDESK_GITEA_TARGET_ID"),
|
||||
"route": os.environ.get("UNIDESK_GITEA_ROUTE"),
|
||||
"namespace": os.environ.get("UNIDESK_GITEA_NAMESPACE"),
|
||||
@@ -210,14 +174,12 @@ payload = {
|
||||
"networkPolicy": "allow-all",
|
||||
},
|
||||
"steps": {
|
||||
"frpcSecret": {"exitCode": frpc_rc, "stdoutTail": text(sys.argv[9]), "stderrTail": text(sys.argv[10])},
|
||||
"frpcCleanup": {"exitCode": frpc_cleanup_rc, "stdoutTail": text(sys.argv[11]), "stderrTail": text(sys.argv[12])},
|
||||
"webhookSyncSecret": {"exitCode": webhook_secret_rc, "stdoutTail": text(sys.argv[13]), "stderrTail": text(sys.argv[14])},
|
||||
"serverDryRun": {"exitCode": server_dry_run_rc, "stdoutTail": text(sys.argv[15]), "stderrTail": text(sys.argv[16])},
|
||||
"apply": {"exitCode": apply_rc, "stdoutTail": text(sys.argv[17]), "stderrTail": text(sys.argv[18])},
|
||||
"rollout": {"exitCode": rollout_rc, "stdoutTail": text(sys.argv[19]), "stderrTail": text(sys.argv[20])},
|
||||
"frpcRollout": {"exitCode": frpc_rollout_rc, "stdoutTail": text(sys.argv[21]), "stderrTail": text(sys.argv[22])},
|
||||
"webhookSyncRollout": {"exitCode": webhook_rollout_rc, "stdoutTail": text(sys.argv[23]), "stderrTail": text(sys.argv[24])},
|
||||
"legacyFrpcCleanup": {"exitCode": frpc_cleanup_rc, "stdoutTail": text(sys.argv[7]), "stderrTail": text(sys.argv[8])},
|
||||
"webhookSyncSecret": {"exitCode": webhook_secret_rc, "stdoutTail": text(sys.argv[9]), "stderrTail": text(sys.argv[10])},
|
||||
"serverDryRun": {"exitCode": server_dry_run_rc, "stdoutTail": text(sys.argv[11]), "stderrTail": text(sys.argv[12])},
|
||||
"apply": {"exitCode": apply_rc, "stdoutTail": text(sys.argv[13]), "stderrTail": text(sys.argv[14])},
|
||||
"rollout": {"exitCode": rollout_rc, "stdoutTail": text(sys.argv[15]), "stderrTail": text(sys.argv[16])},
|
||||
"webhookSyncRollout": {"exitCode": webhook_rollout_rc, "stdoutTail": text(sys.argv[17]), "stderrTail": text(sys.argv[18])},
|
||||
},
|
||||
"valuesPrinted": False,
|
||||
}
|
||||
|
||||
@@ -279,14 +279,11 @@ export function renderApply(result: Record<string, unknown>): RenderedCliResult
|
||||
const remote = record(result.remote);
|
||||
const steps = record(remote.steps);
|
||||
const serverDryRunStep = record(steps.serverDryRun);
|
||||
const frpcStep = record(steps.frpcSecret);
|
||||
const frpcCleanupStep = record(steps.frpcCleanup);
|
||||
const legacyFrpcCleanupStep = record(steps.legacyFrpcCleanup);
|
||||
const webhookSecretStep = record(steps.webhookSyncSecret);
|
||||
const applyStep = record(steps.apply);
|
||||
const rolloutStep = record(steps.rollout);
|
||||
const frpcRolloutStep = record(steps.frpcRollout);
|
||||
const webhookRolloutStep = record(steps.webhookSyncRollout);
|
||||
const caddy = record(result.pk01Caddy);
|
||||
return rendered(result, "platform-infra gitea apply", [
|
||||
"PLATFORM-INFRA GITEA APPLY",
|
||||
...table(["TARGET", "NAMESPACE", "MODE", "OK"], [[stringValue(target.id), stringValue(target.namespace), stringValue(result.mode), boolText(result.ok)]]),
|
||||
@@ -295,14 +292,11 @@ export function renderApply(result: Record<string, unknown>): RenderedCliResult
|
||||
"STEPS",
|
||||
...table(["STEP", "EXIT", "DETAIL"], [
|
||||
["serverDryRun", stringValue(serverDryRunStep.exitCode), compactLine(stringValue(serverDryRunStep.stderrTail, stringValue(serverDryRunStep.stdoutTail)))],
|
||||
["frpc-secret", stringValue(frpcStep.exitCode), compactLine(stringValue(frpcStep.stderrTail, stringValue(frpcStep.stdoutTail)))],
|
||||
["frpc-cleanup", stringValue(frpcCleanupStep.exitCode), compactLine(stringValue(frpcCleanupStep.stderrTail, stringValue(frpcCleanupStep.stdoutTail)))],
|
||||
["legacy-frpc-cleanup", stringValue(legacyFrpcCleanupStep.exitCode), compactLine(stringValue(legacyFrpcCleanupStep.stderrTail, stringValue(legacyFrpcCleanupStep.stdoutTail)))],
|
||||
["webhook-secret", stringValue(webhookSecretStep.exitCode), compactLine(stringValue(webhookSecretStep.stderrTail, stringValue(webhookSecretStep.stdoutTail)))],
|
||||
["apply", stringValue(applyStep.exitCode), compactLine(stringValue(applyStep.stderrTail, stringValue(applyStep.stdoutTail)))],
|
||||
["rollout", stringValue(rolloutStep.exitCode), compactLine(stringValue(rolloutStep.stderrTail, stringValue(rolloutStep.stdoutTail)))],
|
||||
["frpc-rollout", stringValue(frpcRolloutStep.exitCode), compactLine(stringValue(frpcRolloutStep.stderrTail, stringValue(frpcRolloutStep.stdoutTail)))],
|
||||
["webhook-rollout", stringValue(webhookRolloutStep.exitCode), compactLine(stringValue(webhookRolloutStep.stderrTail, stringValue(webhookRolloutStep.stdoutTail)))],
|
||||
["pk01-caddy", stringValue(caddy.exitCode ?? (caddy.ok === false ? 1 : 0)), compactLine(stringValue(caddy.error, stringValue(caddy.status, stringValue(caddy.reason))))],
|
||||
]),
|
||||
...remoteErrorLines(result),
|
||||
"",
|
||||
|
||||
@@ -7,16 +7,11 @@ import { CliInputError, type RenderedCliResult } from "./output";
|
||||
import {
|
||||
capture,
|
||||
compactCapture,
|
||||
fingerprintValues,
|
||||
parseJsonOutput,
|
||||
shQuote,
|
||||
} from "./platform-infra-ops-library";
|
||||
import { applyPk01CaddyBlock, escapeTomlString, prepareFrpcSecret, renderFrpcManifest } from "./platform-infra-public-service";
|
||||
import type { PublicServiceTarget, FrpcSecretMaterial } from "./platform-infra-public-service";
|
||||
import { applyPk01CaddySiteBlock, caddyManagedBlockMarkers } from "./pk01-caddy";
|
||||
import { deriveGiteaWebhookAttemptBudgetMs, deriveGiteaWebhookResponseHeaderTimeoutMs, renderGiteaWebhookCaddyHandle } from "./platform-infra-gitea-caddy";
|
||||
import { deriveGiteaWebhookAttemptBudgetMs, deriveGiteaWebhookResponseHeaderTimeoutMs } from "./platform-infra-gitea-caddy";
|
||||
import { renderGiteaRemotePayloadMaterializer, summarizeGiteaRemotePayloads } from "./platform-infra-gitea-payload";
|
||||
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "./secrets";
|
||||
import { PAC_AUTOMATIC_DELIVERY_REFERENCE } from "./cicd-delivery-authority";
|
||||
import { renderPlatformInfraGiteaDesiredFragments } from "./platform-infra-gitea-desired-fragments";
|
||||
import {
|
||||
@@ -41,7 +36,6 @@ import {
|
||||
readGiteaConfig,
|
||||
resolveTarget,
|
||||
targetWebhookSync,
|
||||
webhookCaddyRoutes,
|
||||
type GiteaAdminCredential,
|
||||
type GiteaConfig,
|
||||
type GiteaGithubCredential,
|
||||
@@ -92,7 +86,6 @@ interface MirrorSecrets {
|
||||
interface MirrorRemoteParams {
|
||||
repos?: GiteaMirrorRepository[];
|
||||
secrets?: MirrorSecrets;
|
||||
frpcSecret?: FrpcSecretMaterial;
|
||||
}
|
||||
|
||||
export async function runPlatformInfraGiteaCommand(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
|
||||
@@ -252,19 +245,15 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
|
||||
const manifest = renderManifest(gitea, target);
|
||||
const policy = policyChecks(gitea, target, manifest);
|
||||
if (!policy.every((check) => check.ok)) return { ok: false, action: "platform-infra-gitea-apply", mode: "policy-blocked", mutation: false, policy };
|
||||
const frpcSecret = prepareGiteaFrpcSecret(gitea, target);
|
||||
const targetRepos = repositoriesForTarget(gitea, target);
|
||||
const secrets = gitea.sourceAuthority.webhookSync.enabled && !options.dryRun
|
||||
? ensureMirrorSecrets(gitea, target, targetRepos, true, true, true)
|
||||
: undefined;
|
||||
const remote = remoteScript("apply", gitea, target, manifest, options, { frpcSecret, secrets });
|
||||
const remote = remoteScript("apply", gitea, target, manifest, options, { secrets });
|
||||
const result = await capture(config, target.route, ["sh"], remote.script);
|
||||
const parsed = parseJsonOutput(result.stdout);
|
||||
const caddy = !options.dryRun && result.exitCode === 0 && parsed?.ok === true && caddyExposureNeeded(gitea, target)
|
||||
? await applyGiteaCaddyBlock(config, gitea)
|
||||
: { ok: true, skipped: true, reason: options.dryRun ? "dry-run" : "apply-not-ready" };
|
||||
return {
|
||||
ok: result.exitCode === 0 && parsed?.ok === true && record(caddy).ok !== false,
|
||||
ok: result.exitCode === 0 && parsed?.ok === true,
|
||||
action: "platform-infra-gitea-apply",
|
||||
mode: options.dryRun ? "dry-run" : "confirmed",
|
||||
mutation: !options.dryRun,
|
||||
@@ -273,8 +262,12 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Reco
|
||||
policy,
|
||||
publicExposure: publicExposureSummary(gitea),
|
||||
payloadTransport: remote.payloadSummary,
|
||||
secrets: { frpc: frpcSecretSummary(frpcSecret), webhookSync: secrets === undefined ? { enabled: false } : webhookSecretSummary(gitea, target, secrets) },
|
||||
pk01Caddy: caddy,
|
||||
secrets: { webhookSync: secrets === undefined ? { enabled: false } : webhookSecretSummary(gitea, target, secrets) },
|
||||
publicEdge: {
|
||||
authority: "config/platform-infra/public-edge.yaml",
|
||||
mutation: false,
|
||||
next: `bun scripts/cli.ts platform-infra public-edge plan --target ${target.id}`,
|
||||
},
|
||||
remote: parsed ?? compactCapture(result, { full: true }),
|
||||
next: giteaReadOnlyNextCommands(target.id),
|
||||
};
|
||||
@@ -739,7 +732,6 @@ ${envVars(gitea, target)}
|
||||
resources:
|
||||
requests:
|
||||
storage: ${app.storage.config.size}
|
||||
${renderFrpcManifest(publicServiceTarget(gitea, target))}
|
||||
${renderGithubSyncManifest(gitea, target)}`;
|
||||
}
|
||||
|
||||
@@ -1180,7 +1172,6 @@ function envVars(gitea: GiteaConfig, target: GiteaTarget): string {
|
||||
}
|
||||
|
||||
function remoteScript(action: "apply" | "status" | "validate" | "mirror-preflight" | "mirror-bootstrap" | "mirror-sync" | "mirror-status" | "mirror-webhook-apply" | "mirror-webhook-status", gitea: GiteaConfig, target: GiteaTarget, manifest: string, options: ApplyOptions | MirrorOptions, params: MirrorRemoteParams = {}): { script: string; payloadSummary: Record<string, unknown> } {
|
||||
const frpcExposure = targetFrpcExposure(gitea, target);
|
||||
const sync = targetWebhookSync(gitea, target);
|
||||
const selectedRepositories = params.repos ?? repositoriesForTarget(gitea, target);
|
||||
const selectedCredentials = githubCredentialsForConsumers(gitea, target, selectedRepositories, false);
|
||||
@@ -1210,10 +1201,8 @@ function remoteScript(action: "apply" | "status" | "validate" | "mirror-prefligh
|
||||
.map((credential) => `${credential.gitFetchCredential.secretRef.key}=${githubTokenEnvName(credential.gitFetchCredential.secretRef.key)}`)
|
||||
.join(","),
|
||||
UNIDESK_GITEA_NO_PROXY: gitea.sourceAuthority.githubProxy.noProxy.join(","),
|
||||
UNIDESK_GITEA_FRPC_ENABLED: frpcExposure === null ? "0" : "1",
|
||||
UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME: frpcExposure?.frpc.deploymentName ?? gitea.app.publicExposure.frpc.deploymentName,
|
||||
UNIDESK_GITEA_FRPC_SECRET_NAME: frpcExposure?.frpc.secretName ?? gitea.app.publicExposure.frpc.secretName,
|
||||
UNIDESK_GITEA_FRPC_SECRET_KEY: frpcExposure?.frpc.secretKey ?? gitea.app.publicExposure.frpc.secretKey,
|
||||
UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME: gitea.app.publicExposure.legacyCleanup.deploymentName,
|
||||
UNIDESK_GITEA_FRPC_SECRET_NAME: gitea.app.publicExposure.legacyCleanup.secretName,
|
||||
UNIDESK_GITEA_WEBHOOK_SYNC_ENABLED: gitea.sourceAuthority.webhookSync.enabled ? "1" : "0",
|
||||
UNIDESK_GITEA_WEBHOOK_PUBLIC_URL: githubWebhookPublicUrl(gitea, target),
|
||||
UNIDESK_GITEA_WEBHOOK_PATH: sync.publicPath,
|
||||
@@ -1223,10 +1212,6 @@ function remoteScript(action: "apply" | "status" | "validate" | "mirror-prefligh
|
||||
UNIDESK_GITEA_WEBHOOK_SERVICE_PORT: String(sync.bridge.httpPort),
|
||||
UNIDESK_GITEA_WEBHOOK_SECRET_NAME: sync.bridge.secretName,
|
||||
};
|
||||
if (params.frpcSecret !== undefined) {
|
||||
env.UNIDESK_GITEA_FRPC_SECRET_NAME = params.frpcSecret.secretName;
|
||||
env.UNIDESK_GITEA_FRPC_SECRET_KEY = params.frpcSecret.secretKey;
|
||||
}
|
||||
if (params.secrets !== undefined) {
|
||||
env.UNIDESK_GITEA_ADMIN_USERNAME = params.secrets.adminUsername;
|
||||
env.UNIDESK_GITEA_ADMIN_PASSWORD = params.secrets.adminPassword;
|
||||
@@ -1243,7 +1228,6 @@ function remoteScript(action: "apply" | "status" | "validate" | "mirror-prefligh
|
||||
repositories: JSON.stringify((params.repos ?? []).map((repo) => remoteRepoSpec(repo))),
|
||||
hookTopology: JSON.stringify(githubHookTopology(gitea)),
|
||||
statusEvaluator: readFileSync(statusEvaluatorFile, "utf8"),
|
||||
frpcToml: params.frpcSecret?.frpcToml ?? "",
|
||||
};
|
||||
return {
|
||||
script: `${exports}\n${renderGiteaRemotePayloadMaterializer(payloads)}\n${readFileSync(remoteScriptFile, "utf8")}`,
|
||||
@@ -1368,132 +1352,18 @@ function appSummary(gitea: GiteaConfig): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function publicServiceTarget(gitea: GiteaConfig, target: GiteaTarget): PublicServiceTarget {
|
||||
return {
|
||||
id: target.id,
|
||||
route: target.route,
|
||||
namespace: target.namespace,
|
||||
replicas: 1,
|
||||
publicExposure: targetPublicExposure(gitea, target),
|
||||
};
|
||||
}
|
||||
|
||||
function publicExposureEnabled(gitea: GiteaConfig, target: GiteaTarget): boolean {
|
||||
return gitea.app.publicExposure.enabled && target.publicExposureEnabled;
|
||||
}
|
||||
|
||||
function targetPublicExposure(gitea: GiteaConfig, target: GiteaTarget): GiteaConfig["app"]["publicExposure"] {
|
||||
const frpcExposure = targetFrpcExposure(gitea, target);
|
||||
return frpcExposure === null ? { ...gitea.app.publicExposure, enabled: false } : frpcExposure;
|
||||
}
|
||||
|
||||
function prepareGiteaFrpcSecret(gitea: GiteaConfig, target: GiteaTarget): FrpcSecretMaterial | undefined {
|
||||
const exposure = targetFrpcExposure(gitea, target);
|
||||
if (exposure === null) return undefined;
|
||||
const base = prepareFrpcSecret({
|
||||
secretRoot: gitea.app.publicExposure.secretRoot,
|
||||
exposure,
|
||||
sourcePathRedactor: redactRepoPath,
|
||||
parseEnvFile,
|
||||
requiredEnvValue,
|
||||
readTextFile,
|
||||
});
|
||||
const sync = targetWebhookSync(gitea, target);
|
||||
if (!sync.enabled || !publicExposureEnabled(gitea, target)) return base;
|
||||
const extraProxy = [
|
||||
"[[proxies]]",
|
||||
`name = "${escapeTomlString(sync.frpc.proxyName)}"`,
|
||||
'type = "tcp"',
|
||||
`localIP = "${escapeTomlString(sync.bridge.serviceName)}.${escapeTomlString(target.namespace)}.svc.cluster.local"`,
|
||||
`localPort = ${sync.bridge.httpPort}`,
|
||||
`remotePort = ${sync.frpc.remotePort}`,
|
||||
"",
|
||||
].join("\n");
|
||||
const frpcToml = `${base.frpcToml.trim()}\n\n${extraProxy}`;
|
||||
return {
|
||||
...base,
|
||||
frpcToml,
|
||||
fingerprint: fingerprintValues({ frpcToml }, ["frpcToml"]),
|
||||
};
|
||||
}
|
||||
|
||||
function targetFrpcExposure(gitea: GiteaConfig, target: GiteaTarget): GiteaConfig["app"]["publicExposure"] | null {
|
||||
if (publicExposureEnabled(gitea, target)) return gitea.app.publicExposure;
|
||||
if (target.webhookSync === null || !gitea.sourceAuthority.webhookSync.enabled) return null;
|
||||
const sync = targetWebhookSync(gitea, target);
|
||||
return {
|
||||
...gitea.app.publicExposure,
|
||||
enabled: true,
|
||||
frpc: {
|
||||
...gitea.app.publicExposure.frpc,
|
||||
proxyName: sync.frpc.proxyName,
|
||||
remotePort: sync.frpc.remotePort,
|
||||
localIP: `${sync.bridge.serviceName}.${target.namespace}.svc.cluster.local`,
|
||||
localPort: sync.bridge.httpPort,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function caddyExposureNeeded(gitea: GiteaConfig, target: GiteaTarget): boolean {
|
||||
return publicExposureEnabled(gitea, target) || (gitea.sourceAuthority.webhookSync.enabled && target.webhookSync !== null);
|
||||
}
|
||||
|
||||
async function applyGiteaCaddyBlock(config: UniDeskConfig, gitea: GiteaConfig): Promise<Record<string, unknown>> {
|
||||
const exposure = gitea.app.publicExposure;
|
||||
const ingressRetry = gitea.sourceAuthority.webhookSync.ingressRetry;
|
||||
const webhookHandles = webhookCaddyRoutes(gitea)
|
||||
.sort((left, right) => right.publicPath.length - left.publicPath.length)
|
||||
.map((route) => renderGiteaWebhookCaddyHandle(
|
||||
route,
|
||||
ingressRetry,
|
||||
gitea.sourceAuthority.webhookSync.responseBudgetMs,
|
||||
exposure.pk01.responseHeaderTimeoutSeconds,
|
||||
))
|
||||
.join("\n\n");
|
||||
if (webhookHandles === "") return await applyPk01CaddyBlock(config, "gitea", exposure);
|
||||
const siteBlock = `${exposure.dns.hostname} {
|
||||
${webhookHandles}
|
||||
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:${exposure.frpc.remotePort} {
|
||||
transport http {
|
||||
response_header_timeout ${exposure.pk01.responseHeaderTimeoutSeconds}s
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
return await applyPk01CaddySiteBlock(config, "gitea", exposure, siteBlock, caddyManagedBlockMarkers("gitea"));
|
||||
}
|
||||
|
||||
|
||||
function publicExposureSummary(gitea: GiteaConfig): Record<string, unknown> {
|
||||
const exposure = gitea.app.publicExposure;
|
||||
return {
|
||||
enabled: exposure.enabled,
|
||||
publicBaseUrl: exposure.publicBaseUrl,
|
||||
upstream: exposure.upstream,
|
||||
healthPath: exposure.healthPath,
|
||||
internalReadUrlPolicy: "k3s-consumers-use-gitea-http-svc-not-public-url",
|
||||
dns: exposure.dns,
|
||||
frpc: {
|
||||
deploymentName: exposure.frpc.deploymentName,
|
||||
remotePort: exposure.frpc.remotePort,
|
||||
localIP: exposure.frpc.localIP,
|
||||
localPort: exposure.frpc.localPort,
|
||||
tokenSourceRef: exposure.frpc.tokenSourceRef,
|
||||
valuesPrinted: false,
|
||||
},
|
||||
pk01: exposure.pk01,
|
||||
};
|
||||
}
|
||||
|
||||
function frpcSecretSummary(secret: FrpcSecretMaterial | undefined): Record<string, unknown> {
|
||||
if (secret === undefined) return { enabled: false, skipped: true, reason: "target-public-exposure-disabled", valuesPrinted: false };
|
||||
return {
|
||||
sourceRef: secret.sourceRef,
|
||||
sourcePath: secret.sourcePath,
|
||||
secretName: secret.secretName,
|
||||
secretKey: secret.secretKey,
|
||||
fingerprint: secret.fingerprint,
|
||||
routes: exposure.routes,
|
||||
authority: "nc01-public-edge",
|
||||
legacyCleanup: exposure.legacyCleanup,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
@@ -1698,7 +1568,6 @@ function webhookSyncSummary(gitea: GiteaConfig, target: GiteaTarget): Record<str
|
||||
},
|
||||
bridge: sync.bridge,
|
||||
gitOpsDelivery: sync.gitOpsDelivery,
|
||||
frpc: sync.frpc,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ test("selfmedia consumers share one PaC Repository while keeping branch paramete
|
||||
const config = Bun.YAML.parse(readFileSync(resolve(import.meta.dir, "../../config/platform-infra/pipelines-as-code.yaml"), "utf8")) as Record<string, unknown>;
|
||||
const repositories = config.repositories as Array<Record<string, unknown>>;
|
||||
const consumers = config.consumers as Array<Record<string, unknown>>;
|
||||
const selfmediaRepositories = repositories.filter((item) => item.url === "https://gitea.pikapython.com/mirrors/pikainc-selfmedia");
|
||||
const selfmediaRepositories = repositories.filter((item) => item.url === "https://gitea.hwpod.com/mirrors/pikainc-selfmedia");
|
||||
expect(selfmediaRepositories).toHaveLength(1);
|
||||
const development = consumers.find((item) => item.id === "selfmedia-nc01");
|
||||
const production = consumers.find((item) => item.id === "selfmedia-production-nc01");
|
||||
|
||||
@@ -520,7 +520,7 @@ export function platformInfraHelp(): unknown {
|
||||
"bun scripts/cli.ts platform-infra pipelines-as-code help platform-bootstrap",
|
||||
"bun scripts/cli.ts platform-infra pipelines-as-code help compatibility-diagnostics",
|
||||
],
|
||||
description: "Operate YAML-controlled platform-infra services such as Sub2API, LangBot, n8n, Web Terminal, WeChat archive workflows, OpenTelemetry tracing, the independent target-scoped secret plane, the target-scoped Kafka event bus, internal Gitea source authority, and read-only Gitea + Pipelines-as-Code observation for migrated consumers. Public services use PK01 Caddy/FRP or YAML-declared PK01 Caddy upstreams rather than Kubernetes Ingress, NodePort, or LoadBalancer.",
|
||||
description: "Operate YAML-controlled platform-infra services such as Sub2API, LangBot, n8n, Web Terminal, WeChat archive workflows, OpenTelemetry tracing, the independent target-scoped secret plane, the target-scoped Kafka event bus, internal Gitea source authority, and read-only Gitea + Pipelines-as-Code observation for migrated consumers. Public services use their YAML-declared managed edge rather than Kubernetes Ingress, NodePort, or LoadBalancer.",
|
||||
target,
|
||||
codexPool: {
|
||||
usage: [
|
||||
|
||||
Reference in New Issue
Block a user