Merge pull request #1779 from pikasTech/fix/1774-explicit-bridge-entrypoint
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

fix: 使用显式入口启动 durable bridge
This commit is contained in:
Lyon
2026-07-11 19:47:50 +08:00
committed by GitHub
5 changed files with 26 additions and 25 deletions
@@ -0,0 +1,3 @@
import { startServer } from "./server.mjs";
startServer();
@@ -1,11 +1,10 @@
import { spawn } from "node:child_process";
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
import { mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { mkdir, open, readFile, readdir, rename, stat, unlink } from "node:fs/promises";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
export function createGithubSyncHandler(config) {
const controller = config.controller ?? createDurableSyncController(config);
@@ -1358,14 +1357,3 @@ function positiveIntegerEnv(name) {
if (!Number.isFinite(value) || value < 1) throw new Error(`invalid positive integer env ${name}`);
return value;
}
export function isMainModule(argvPath = process.argv[1], moduleUrl = import.meta.url) {
if (!argvPath) return false;
try {
return pathToFileURL(realpathSync(argvPath)).href === pathToFileURL(realpathSync(new URL(moduleUrl))).href;
} catch {
return pathToFileURL(argvPath).href === moduleUrl;
}
}
if (isMainModule()) startServer();
@@ -5,14 +5,12 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import {
createGithubSyncHandler,
createDurableInboxStore,
createDurableSyncController,
createSyncCoordinator,
isMainModule,
run,
runResult,
syncRepository,
@@ -20,17 +18,21 @@ import {
const zeroDelay = async () => {};
test("main module detection follows Kubernetes ConfigMap symlinks", () => {
test("explicit entrypoint starts through Kubernetes ConfigMap symlinks", () => {
const root = mkdtempSync(join(tmpdir(), "gitea-sync-main-"));
try {
const dataDirectory = join(root, "..data");
const source = join(dataDirectory, "server.mjs");
const mounted = join(root, "server.mjs");
const serverSource = join(dataDirectory, "server.mjs");
const entrypointSource = join(dataDirectory, "entrypoint.mjs");
const marker = join(root, "started");
mkdirSync(dataDirectory);
writeFileSync(source, "export {};\n", { flag: "wx" });
symlinkSync(join("..data", "server.mjs"), mounted);
writeFileSync(serverSource, `import { writeFileSync } from "node:fs";\nexport function startServer() { writeFileSync(${JSON.stringify(marker)}, "started\\n"); }\n`, { flag: "wx" });
writeFileSync(entrypointSource, 'import { startServer } from "./server.mjs";\nstartServer();\n', { flag: "wx" });
symlinkSync(join("..data", "server.mjs"), join(root, "server.mjs"));
symlinkSync(join("..data", "entrypoint.mjs"), join(root, "entrypoint.mjs"));
assert.equal(isMainModule(mounted, pathToFileURL(source).href), true);
execFileSync(process.execPath, [join(root, "entrypoint.mjs")], { stdio: "pipe" });
assert.equal(existsSync(marker), true);
} finally {
rmSync(root, { recursive: true, force: true });
}
@@ -60,7 +60,9 @@ test("owning YAML renders one child Application and the complete durable bridge
expect(manifest).toContain("argocd.argoproj.io/hook: PreSync");
expect(manifest).toContain('argocd.argoproj.io/sync-wave: "-2"');
expect(manifest).toContain('argocd.argoproj.io/sync-wave: "-1"');
expect(manifest).toContain("node /etc/gitea-github-sync-candidate/server.mjs &");
expect(manifest).toContain("node /etc/gitea-github-sync-candidate/entrypoint.mjs &");
expect(manifest).toContain('import { startServer } from "./server.mjs";');
expect(manifest).toContain("- /etc/gitea-github-sync/entrypoint.mjs");
expect(manifest).toContain("mountPath: /etc/gitea-github-sync-candidate");
expect(manifest).toContain("name: candidate-inbox\n emptyDir: {}");
expect(manifest).toContain('gate: "gitea-github-sync-candidate-startup"');
+9 -3
View File
@@ -25,6 +25,7 @@ const configFile = rootPath("config", "platform-infra", "gitea.yaml");
const configLabel = "config/platform-infra/gitea.yaml";
const remoteScriptFile = rootPath("scripts", "src", "platform-infra-gitea-remote.sh");
const githubSyncServerFile = rootPath("scripts", "src", "platform-infra-gitea-github-sync-server.mjs");
const githubSyncEntrypointFile = rootPath("scripts", "src", "platform-infra-gitea-github-sync-entrypoint.mjs");
const statusEvaluatorFile = rootPath("scripts", "src", "platform_infra_gitea_status_evaluator.py");
const fieldManager = "unidesk-platform-infra-gitea";
const y = createYamlFieldReader(configLabel);
@@ -1288,7 +1289,8 @@ function renderGithubSyncManifest(gitea: GiteaConfig, target: GiteaTarget): stri
app.kubernetes.io/managed-by: unidesk`;
const reposJson = JSON.stringify(repositoriesForTarget(gitea, target).map(remoteRepoSpec), null, 2);
const serverSource = readFileSync(githubSyncServerFile, "utf8");
const configHash = createHash("sha256").update(reposJson).update("\0").update(serverSource).digest("hex").slice(0, 16);
const entrypointSource = readFileSync(githubSyncEntrypointFile, "utf8");
const configHash = createHash("sha256").update(reposJson).update("\0").update(serverSource).update("\0").update(entrypointSource).digest("hex").slice(0, 16);
const gate = sync.bridge.candidateGate;
const candidateManifest = gate.enabled ? `---
apiVersion: v1
@@ -1307,6 +1309,8 @@ data:
${indentBlock(reposJson, 4)}
server.mjs: |
${indentBlock(serverSource, 4)}
entrypoint.mjs: |
${indentBlock(entrypointSource, 4)}
---
apiVersion: batch/v1
kind: Job
@@ -1341,7 +1345,7 @@ spec:
- -eu
- -c
- |
node /etc/gitea-github-sync-candidate/server.mjs &
node /etc/gitea-github-sync-candidate/entrypoint.mjs &
server_pid=$!
trap 'kill "$server_pid" 2>/dev/null || true' EXIT INT TERM
node --input-type=module - ${gate.healthTimeoutMs} ${gate.pollIntervalMs} <<'NODE'
@@ -1455,6 +1459,8 @@ data:
${indentBlock(reposJson, 4)}
server.mjs: |
${indentBlock(serverSource, 4)}
entrypoint.mjs: |
${indentBlock(entrypointSource, 4)}
---
apiVersion: v1
kind: PersistentVolumeClaim
@@ -1525,7 +1531,7 @@ spec:
imagePullPolicy: IfNotPresent
command:
- node
- /etc/gitea-github-sync/server.mjs
- /etc/gitea-github-sync/entrypoint.mjs
ports:
- name: http
containerPort: ${sync.bridge.httpPort}