diff --git a/scripts/src/platform-infra-gitea-payload.test.ts b/scripts/src/platform-infra-gitea-payload.test.ts new file mode 100644 index 00000000..1fc145b4 --- /dev/null +++ b/scripts/src/platform-infra-gitea-payload.test.ts @@ -0,0 +1,68 @@ +import { expect, test } from "bun:test"; +import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { renderGiteaRemotePayloadMaterializer, summarizeGiteaRemotePayloads } from "./platform-infra-gitea-payload"; +import { renderGiteaWebhookSyncDesiredManifest } from "./platform-infra-gitea"; + +const remoteScriptPath = resolve(import.meta.dir, "platform-infra-gitea-remote.sh"); + +test("stdin file envelope materializes a payload larger than the current Gitea manifest without argv or env", () => { + const currentManifest = renderGiteaWebhookSyncDesiredManifest("NC01"); + const oversizedManifest = `${currentManifest}\n${"payload-$HOME-'quote'-$(command)\n".repeat(8192)}`; + expect(Buffer.byteLength(oversizedManifest)).toBeGreaterThan(Buffer.byteLength(currentManifest)); + expect(Buffer.byteLength(Buffer.from(oversizedManifest).toString("base64"))).toBeGreaterThan(128 * 1024); + + const payloads = { + manifest: oversizedManifest, + repositories: JSON.stringify([{ key: "sentinel", branch: "master" }]), + statusEvaluator: "print('ok')\n", + frpcToml: "serverAddr = '127.0.0.1'\n", + }; + const materializer = renderGiteaRemotePayloadMaterializer(payloads); + const result = spawnSync("sh", ["-s"], { + encoding: "utf8", + input: [ + "set -eu", + 'tmp="$(mktemp -d)"', + 'trap \'rm -rf "$tmp"\' EXIT', + materializer, + 'unidesk_gitea_materialize_payloads "$tmp"', + "python3 - \"$tmp\" <<'PY'", + "import hashlib, json, pathlib, sys", + "root = pathlib.Path(sys.argv[1])", + "print(json.dumps({p.name: {'bytes': p.stat().st_size, 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()} for p in root.iterdir()}, sort_keys=True))", + "PY", + ].join("\n"), + }); + expect(result.status).toBe(0); + expect(result.stderr).toBe(""); + const observed = JSON.parse(result.stdout) as Record; + expect(observed["gitea.k8s.yaml"]).toEqual({ + bytes: Buffer.byteLength(oversizedManifest), + sha256: createHash("sha256").update(oversizedManifest).digest("hex"), + }); + expect(materializer).not.toContain("UNIDESK_GITEA_MANIFEST_B64"); + + const summary = summarizeGiteaRemotePayloads(payloads) as any; + expect(summary.kind).toBe("trans-stdin-file-envelope"); + expect(summary.valuesPrinted).toBe(false); + expect(JSON.stringify(summary)).not.toContain("payload-$HOME"); +}); + +test("Gitea remote runner consumes materialized files instead of base64 environment variables", () => { + const source = readFileSync(remoteScriptPath, "utf8"); + expect(source).toContain('unidesk_gitea_materialize_payloads "$tmp"'); + expect(source).toContain('$tmp/gitea.k8s.yaml'); + expect(source).toContain('$tmp/repos.json'); + expect(source).not.toContain("UNIDESK_GITEA_MANIFEST_B64"); + 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).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( + source.lastIndexOf('timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl apply --server-side --force-conflicts'), + ); +}); diff --git a/scripts/src/platform-infra-gitea-payload.ts b/scripts/src/platform-infra-gitea-payload.ts new file mode 100644 index 00000000..e1343a3f --- /dev/null +++ b/scripts/src/platform-infra-gitea-payload.ts @@ -0,0 +1,62 @@ +import { createHash } from "node:crypto"; + +export interface GiteaRemotePayloads { + manifest: string; + repositories: string; + statusEvaluator: string; + frpcToml: string; +} + +interface GiteaRemotePayloadFile { + role: keyof GiteaRemotePayloads; + name: string; + value: string; +} + +const payloadFiles = (payloads: GiteaRemotePayloads): GiteaRemotePayloadFile[] => [ + { role: "manifest", name: "gitea.k8s.yaml", value: payloads.manifest }, + { role: "repositories", name: "repos.json", value: payloads.repositories }, + { 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 { + return `__UNIDESK_GITEA_${file.role.toUpperCase()}_STDIN_PAYLOAD__`; +} + +export function renderGiteaRemotePayloadMaterializer(payloads: GiteaRemotePayloads): string { + const writes = payloadFiles(payloads).map((file) => { + const encoded = Buffer.from(file.value, "utf8").toString("base64"); + const marker = payloadMarker(file); + return [ + ` if ! base64 -d >"$unidesk_gitea_payload_dir/${file.name}" <<'${marker}'`, + encoded, + marker, + " then", + " return 1", + " fi", + ].join("\n"); + }); + return [ + "unidesk_gitea_materialize_payloads() {", + " unidesk_gitea_payload_dir=$1", + " umask 077", + ...writes, + "}", + ].join("\n"); +} + +export function summarizeGiteaRemotePayloads(payloads: GiteaRemotePayloads): Record { + const files = payloadFiles(payloads).map((file) => ({ + role: file.role, + bytes: Buffer.byteLength(file.value, "utf8"), + sha256: `sha256:${createHash("sha256").update(file.value).digest("hex").slice(0, 16)}`, + })); + return { + kind: "trans-stdin-file-envelope", + materialization: "target-private-tmpdir", + totalBytes: files.reduce((total, file) => total + file.bytes, 0), + files, + valuesPrinted: false, + }; +} diff --git a/scripts/src/platform-infra-gitea-remote.sh b/scripts/src/platform-infra-gitea-remote.sh index c832b099..f665c42d 100644 --- a/scripts/src/platform-infra-gitea-remote.sh +++ b/scripts/src/platform-infra-gitea-remote.sh @@ -5,7 +5,10 @@ tmp="$(mktemp -d)" export tmp trap 'rm -rf "$tmp"' EXIT -printf '%s' "$UNIDESK_GITEA_STATUS_EVALUATOR_B64" | base64 -d >"$tmp/platform_infra_gitea_status_evaluator.py" +if ! unidesk_gitea_materialize_payloads "$tmp"; then + printf '%s\n' '{"ok":false,"error":"gitea-stdin-payload-materialization-failed","valuesPrinted":false}' + exit 1 +fi json_tail() { name="$1" @@ -31,12 +34,10 @@ capture_json() { run_apply() { manifest="$tmp/gitea.k8s.yaml" - printf '%s' "$UNIDESK_GITEA_MANIFEST_B64" | base64 -d >"$manifest" 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 - printf '%s' "$UNIDESK_GITEA_FRPC_TOML_B64" | base64 -d >"$tmp/frpc.toml" 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" @@ -67,15 +68,23 @@ run_apply() { printf '%s\n' "webhook sync disabled" >"$tmp/webhook-secret.err" fi fi - dry_arg="" - apply_args="--server-side --force-conflicts" - if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then - dry_arg="--dry-run=client" - apply_args="" - fi + 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 - timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl apply $apply_args $dry_arg --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f "$manifest" >"$tmp/apply.out" 2>"$tmp/apply.err" - apply_rc=$? + if [ "$server_dry_run_rc" -ne 0 ]; then + apply_rc=1 + : >"$tmp/apply.out" + printf '%s\n' "target server dry-run failed; manifest apply skipped" >"$tmp/apply.err" + elif [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then + apply_rc=0 + cp "$tmp/server-dry-run.out" "$tmp/apply.out" + : >"$tmp/apply.err" + else + timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl apply --server-side --force-conflicts \ + --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f "$manifest" >"$tmp/apply.out" 2>"$tmp/apply.err" + apply_rc=$? + fi else apply_rc=1 : >"$tmp/apply.out" @@ -150,9 +159,9 @@ run_apply() { printf '%s\n' "webhook sync disabled" >"$tmp/webhook-rollout.err" fi fi - python3 - "$frpc_rc" "$webhook_secret_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/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 - "$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' import json, os, sys -frpc_rc, webhook_secret_rc, apply_rc, rollout_rc, frpc_rollout_rc, webhook_rollout_rc, frpc_cleanup_rc = [int(value) for value in sys.argv[1:8]] +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]] def text(path, limit=3000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] @@ -160,7 +169,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, apply_rc, rollout_rc, frpc_rollout_rc, webhook_rollout_rc, frpc_cleanup_rc]), + "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]), "target": os.environ.get("UNIDESK_GITEA_TARGET_ID"), "route": os.environ.get("UNIDESK_GITEA_ROUTE"), "namespace": os.environ.get("UNIDESK_GITEA_NAMESPACE"), @@ -173,13 +182,14 @@ payload = { "networkPolicy": "allow-all", }, "steps": { - "frpcSecret": {"exitCode": frpc_rc, "stdoutTail": text(sys.argv[8]), "stderrTail": text(sys.argv[9])}, - "frpcCleanup": {"exitCode": frpc_cleanup_rc, "stdoutTail": text(sys.argv[10]), "stderrTail": text(sys.argv[11])}, - "webhookSyncSecret": {"exitCode": webhook_secret_rc, "stdoutTail": text(sys.argv[12]), "stderrTail": text(sys.argv[13])}, - "apply": {"exitCode": apply_rc, "stdoutTail": text(sys.argv[14]), "stderrTail": text(sys.argv[15])}, - "rollout": {"exitCode": rollout_rc, "stdoutTail": text(sys.argv[16]), "stderrTail": text(sys.argv[17])}, - "frpcRollout": {"exitCode": frpc_rollout_rc, "stdoutTail": text(sys.argv[18]), "stderrTail": text(sys.argv[19])}, - "webhookSyncRollout": {"exitCode": webhook_rollout_rc, "stdoutTail": text(sys.argv[20]), "stderrTail": text(sys.argv[21])}, + "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])}, }, "valuesPrinted": False, } @@ -356,7 +366,7 @@ PY } mirror_repos_json() { - printf '%s' "$UNIDESK_GITEA_REPOS_B64" | base64 -d >"$tmp/repos.json" + test -f "$tmp/repos.json" } admin_basic_b64() { diff --git a/scripts/src/platform-infra-gitea.ts b/scripts/src/platform-infra-gitea.ts index 6b98e320..71d4bc33 100644 --- a/scripts/src/platform-infra-gitea.ts +++ b/scripts/src/platform-infra-gitea.ts @@ -18,6 +18,7 @@ import type { PublicServiceExposure, PublicServiceTarget, FrpcSecretMaterial } f import { applyPk01CaddySiteBlock, caddyManagedBlockMarkers } from "./pk01-caddy"; import { deriveGiteaWebhookAttemptBudgetMs, deriveGiteaWebhookResponseHeaderTimeoutMs, renderGiteaWebhookCaddyHandle, validateGiteaWebhookTiming } from "./platform-infra-gitea-caddy"; import type { GiteaWebhookIngressRetry } 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"; @@ -857,7 +858,8 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise check.ok)) return { ok: false, action: "platform-infra-gitea-apply", mode: "policy-blocked", mutation: false, policy }; const frpcSecret = prepareGiteaFrpcSecret(gitea, target); const secrets = gitea.sourceAuthority.webhookSync.enabled && !options.dryRun ? ensureMirrorSecrets(gitea, true, true) : undefined; - const result = await capture(config, target.route, ["sh"], remoteScript("apply", gitea, target, manifest, options, { frpcSecret, secrets })); + const remote = remoteScript("apply", gitea, target, manifest, options, { frpcSecret, 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) @@ -871,6 +873,7 @@ async function apply(config: UniDeskConfig, options: ApplyOptions): Promise> { const gitea = readGiteaConfig(); const target = resolveTarget(gitea, options.targetId); - const result = await capture(config, target.route, ["sh"], remoteScript("status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false })); + const result = await capture(config, target.route, ["sh"], remoteScript("status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }).script); const parsed = parseJsonOutput(result.stdout); const summary = parsed === null ? null : statusSummary(parsed); return { @@ -899,7 +902,7 @@ async function status(config: UniDeskConfig, options: CommonOptions): Promise> { const gitea = readGiteaConfig(); const target = resolveTarget(gitea, options.targetId); - const result = await capture(config, target.route, ["sh"], remoteScript("validate", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false })); + const result = await capture(config, target.route, ["sh"], remoteScript("validate", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }).script); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && parsed?.ok === true, @@ -993,7 +996,7 @@ async function mirrorStatus(config: UniDeskConfig, options: CommonOptions & { re const credentials = credentialSummaries(gitea); const secrets = ensureMirrorSecrets(gitea, false, false); const selectedRepos = selectedRepositories(gitea, target, options.repoKey ?? null); - const result = await capture(config, target.route, ["sh"], remoteScript("mirror-status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }, { repos: selectedRepos, secrets })); + const result = await capture(config, target.route, ["sh"], remoteScript("mirror-status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }, { repos: selectedRepos, secrets }).script); const parsed = parseJsonOutput(result.stdout); const repositories = arrayRecords(record(parsed).repositories); return { @@ -1027,7 +1030,7 @@ async function mirrorBootstrap(config: UniDeskConfig, options: MirrorOptions): P if (!options.confirm) return { ok: false, action: "platform-infra-gitea-mirror-bootstrap", mutation: false, mode: "missing-confirm", error: "mirror bootstrap requires --confirm" }; const repos = selectedRepositories(gitea, target, options.repoKey); const secrets = ensureMirrorSecrets(gitea, true, true); - const result = await capture(config, target.route, ["sh"], remoteScript("mirror-bootstrap", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets })); + const result = await capture(config, target.route, ["sh"], remoteScript("mirror-bootstrap", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets }).script); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && parsed?.ok === true, @@ -1058,7 +1061,7 @@ async function mirrorSync(config: UniDeskConfig, options: MirrorOptions): Promis if (!options.confirm) return { ok: false, action: "platform-infra-gitea-mirror-sync", mutation: false, mode: "missing-confirm", error: "mirror sync requires --confirm" }; const repos = selectedRepositories(gitea, target, options.repoKey); const secrets = ensureMirrorSecrets(gitea, false, false); - const result = await capture(config, target.route, ["sh"], remoteScript("mirror-sync", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets })); + const result = await capture(config, target.route, ["sh"], remoteScript("mirror-sync", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets }).script); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && parsed?.ok === true, @@ -1079,7 +1082,7 @@ async function mirrorWebhookApply(config: UniDeskConfig, options: MirrorOptions) if (!options.confirm) return { ok: false, action: "platform-infra-gitea-mirror-webhook-apply", mutation: false, mode: "missing-confirm", error: "mirror webhook apply requires --confirm" }; const repos = selectedRepositories(gitea, target, options.repoKey); const secrets = ensureMirrorSecrets(gitea, false, false); - const result = await capture(config, target.route, ["sh"], remoteScript("mirror-webhook-apply", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets })); + const result = await capture(config, target.route, ["sh"], remoteScript("mirror-webhook-apply", gitea, target, "", { ...options, dryRun: false, wait: false }, { repos, secrets }).script); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && parsed?.ok === true, @@ -1098,7 +1101,7 @@ async function mirrorWebhookStatus(config: UniDeskConfig, options: MirrorOptions const target = resolveTarget(gitea, options.targetId); const repos = selectedRepositories(gitea, target, options.repoKey); const secrets = ensureMirrorSecrets(gitea, false, false); - const result = await capture(config, target.route, ["sh"], remoteScript("mirror-webhook-status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }, { repos, secrets })); + const result = await capture(config, target.route, ["sh"], remoteScript("mirror-webhook-status", gitea, target, "", { ...options, confirm: false, dryRun: true, wait: false }, { repos, secrets }).script); const parsed = parseJsonOutput(result.stdout); const repositories = arrayRecords(record(parsed).repositories); return { @@ -1652,7 +1655,7 @@ function envVars(gitea: GiteaConfig, target: GiteaTarget): string { value: ${yamlQuote(value)}`).join("\n"); } -function remoteScript(action: "apply" | "status" | "validate" | "mirror-bootstrap" | "mirror-sync" | "mirror-status" | "mirror-webhook-apply" | "mirror-webhook-status", gitea: GiteaConfig, target: GiteaTarget, manifest: string, options: ApplyOptions | MirrorOptions, params: MirrorRemoteParams = {}): string { +function remoteScript(action: "apply" | "status" | "validate" | "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 } { const frpcExposure = targetFrpcExposure(gitea, target); const sync = targetWebhookSync(gitea, target); const env: Record = { @@ -1671,10 +1674,7 @@ function remoteScript(action: "apply" | "status" | "validate" | "mirror-bootstra UNIDESK_GITEA_DRY_RUN: options.dryRun ? "1" : "0", UNIDESK_GITEA_WAIT: options.wait ? "1" : "0", UNIDESK_GITEA_FULL: options.full ? "1" : "0", - UNIDESK_GITEA_MANIFEST_B64: Buffer.from(manifest, "utf8").toString("base64"), UNIDESK_GITEA_ROOT_URL: gitea.app.server.rootUrl, - UNIDESK_GITEA_REPOS_B64: Buffer.from(JSON.stringify((params.repos ?? []).map((repo) => remoteRepoSpec(repo))), "utf8").toString("base64"), - UNIDESK_GITEA_STATUS_EVALUATOR_B64: Buffer.from(readFileSync(statusEvaluatorFile, "utf8"), "utf8").toString("base64"), UNIDESK_GITEA_GITHUB_PROXY_ENABLED: gitea.sourceAuthority.githubProxy.enabled ? "1" : "0", UNIDESK_GITEA_GITHUB_PROXY_URL: gitea.sourceAuthority.githubProxy.url, UNIDESK_GITEA_NO_PROXY: gitea.sourceAuthority.githubProxy.noProxy.join(","), @@ -1694,7 +1694,6 @@ function remoteScript(action: "apply" | "status" | "validate" | "mirror-bootstra if (params.frpcSecret !== undefined) { env.UNIDESK_GITEA_FRPC_SECRET_NAME = params.frpcSecret.secretName; env.UNIDESK_GITEA_FRPC_SECRET_KEY = params.frpcSecret.secretKey; - env.UNIDESK_GITEA_FRPC_TOML_B64 = Buffer.from(params.frpcSecret.frpcToml, "utf8").toString("base64"); } if (params.secrets !== undefined) { env.UNIDESK_GITEA_ADMIN_USERNAME = params.secrets.adminUsername; @@ -1703,7 +1702,16 @@ function remoteScript(action: "apply" | "status" | "validate" | "mirror-bootstra env.UNIDESK_GITEA_GITHUB_WEBHOOK_SECRET = params.secrets.webhookSecret; } const exports = Object.entries(env).map(([key, value]) => `export ${key}=${shQuote(value)}`).join("\n"); - return `${exports}\n${readFileSync(remoteScriptFile, "utf8")}`; + const payloads = { + manifest, + repositories: JSON.stringify((params.repos ?? []).map((repo) => remoteRepoSpec(repo))), + statusEvaluator: readFileSync(statusEvaluatorFile, "utf8"), + frpcToml: params.frpcSecret?.frpcToml ?? "", + }; + return { + script: `${exports}\n${renderGiteaRemotePayloadMaterializer(payloads)}\n${readFileSync(remoteScriptFile, "utf8")}`, + payloadSummary: summarizeGiteaRemotePayloads(payloads), + }; } function policyChecks(gitea: GiteaConfig, target: GiteaTarget, manifest: string): Array> { @@ -2423,8 +2431,10 @@ function renderPlan(result: Record): RenderedCliResult { function renderApply(result: Record): RenderedCliResult { const target = record(result.target); + const payload = record(result.payloadTransport); 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 webhookSecretStep = record(steps.webhookSyncSecret); @@ -2436,9 +2446,11 @@ function renderApply(result: Record): RenderedCliResult { 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)]]), + ...table(["TRANSPORT", "BYTES", "MATERIALIZATION", "VALUES"], [[stringValue(payload.kind), stringValue(payload.totalBytes), stringValue(payload.materialization), stringValue(payload.valuesPrinted)]]), "", "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)))], ["webhook-secret", stringValue(webhookSecretStep.exitCode), compactLine(stringValue(webhookSecretStep.stderrTail, stringValue(webhookSecretStep.stdoutTail)))],