diff --git a/scripts/src/platform-infra-gitea-github-sync-entrypoint.mjs b/scripts/src/platform-infra-gitea-github-sync-entrypoint.mjs new file mode 100644 index 00000000..cda55cb4 --- /dev/null +++ b/scripts/src/platform-infra-gitea-github-sync-entrypoint.mjs @@ -0,0 +1,3 @@ +import { startServer } from "./server.mjs"; + +startServer(); diff --git a/scripts/src/platform-infra-gitea-github-sync-server.mjs b/scripts/src/platform-infra-gitea-github-sync-server.mjs index 5b190524..5461e642 100644 --- a/scripts/src/platform-infra-gitea-github-sync-server.mjs +++ b/scripts/src/platform-infra-gitea-github-sync-server.mjs @@ -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(); diff --git a/scripts/src/platform-infra-gitea-github-sync-server.test.mjs b/scripts/src/platform-infra-gitea-github-sync-server.test.mjs index 2c5333c8..65dca783 100644 --- a/scripts/src/platform-infra-gitea-github-sync-server.test.mjs +++ b/scripts/src/platform-infra-gitea-github-sync-server.test.mjs @@ -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 }); } diff --git a/scripts/src/platform-infra-gitea-gitops-delivery.test.ts b/scripts/src/platform-infra-gitea-gitops-delivery.test.ts index 89fe82f0..39ce0515 100644 --- a/scripts/src/platform-infra-gitea-gitops-delivery.test.ts +++ b/scripts/src/platform-infra-gitea-gitops-delivery.test.ts @@ -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"'); diff --git a/scripts/src/platform-infra-gitea.ts b/scripts/src/platform-infra-gitea.ts index 6b98e320..c8cfe9a4 100644 --- a/scripts/src/platform-infra-gitea.ts +++ b/scripts/src/platform-infra-gitea.ts @@ -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}