import { createHash, randomBytes, randomUUID } from "node:crypto"; import { INSTALL_ROOT, MANAGED_MARKER_PATH, PRECHECK_COMMAND, SHARED_ROOT_MARKER_CONTENT, SHARED_ROOT_MARKER_PATH, SHARED_ROOT_TIGHTEN_COMMAND, SIDECAR_MARKER_CONTENT, SIDECAR_BUILD_IGNORE_PATH, SIDECAR_BUILD_IGNORE_CONTENT, SIDECAR_BUILD_IGNORE_GUARD, SIDECAR_FRESH_CONTEXT_GUARD, SIDECAR_MANAGED_CONTEXT_GUARD, SIWC_STAGING_PATH, SIWC_MIGRATION_PENDING_PATH, SIWC_MIGRATED_PATH, SIWC_MIGRATION_MARKER_CONTENT, assertSidecarOnlyCommands, createDeploymentCommands, createVerificationCommands, parseManagedFileHashes, safeSiwcCatalogFailure, safeSiwcRuntimeFailure, validateSiwcAuthBinding, validateSiwcRegistrationId, } from "../domain/safety.js"; import { SIDECAR_HOSTNAME, attestVpsSiwcCompose, attestVpsSiwcContainer, attestVpsSiwcImage, createComposeFile, createDockerfile, } from "../domain/templates.js"; import { validateDockerName } from "../domain/validation.js"; import { readCodexImagesCliResult } from "../domain/codex-images.js"; import { VPS_OPERATION_LOCKS, inspectVpsResumeLockCommand } from "../domain/vps-build-state.js"; import { assertVpsNetworkSupported } from "../domain/vps-network.js"; import { withVpsOperationLock } from "./vps-operation-lock.js"; import { collectSiwcRuntimeAssets } from "./siwc-runtime-assets.js"; import { finishAuthHandoff, prepareAuthHandoff, readAuthHandoff, readRegistration, readPendingAuthHandoff, readRegistrationView, validateSiwcBackgroundConsent, } from "./siwc-session.mjs"; import { usageRecordFromCli } from "./model-discovery.mjs"; const MAX_HANDOFF_BYTES = 64 * 1024; const ASSET_PATHS = new Set([ "gateway/openai-oauth-sidecar.mjs", "gateway/codex-chat.js", "gateway/codex-app-server.mjs", "services/siwc-session.mjs", "services/siwc-handoff.mjs", "infrastructure/local-process.js", "services/local-integration-lifecycle-lock.js", "services/codex-images.mjs", "services/model-discovery.mjs", "infrastructure/process-identity.js", ]); const DOCKER_ID = /^[a-f0-9]{64}$/u; const IMAGE_ID = /^sha256:[a-f0-9]{64}$/u; const RUNTIME_UPDATE_PENDING = "Finish the interrupted sidecar update from Manage the installed ChatGPT session."; export async function reviewVpsSiwcTarget({ remote, networkName, containerName, requireSupportedNetwork = false }) { const network = validateDockerName(networkName); const name = validateDockerName(containerName); if (!remote?.identity) throw new Error("Verify the selected VPS before reviewing n8n."); const networkRecord = parseHandoffOutput( (await runOrThrow(remote, `docker network inspect --format '{{json .}}' ${network}`, "Selected n8n network identity check")).stdout, "Selected n8n network identity check", 64 * 1024, ); const containerRecord = parseHandoffOutput( (await runOrThrow(remote, `docker inspect --format '{{json .}}' ${name}`, "Selected n8n container identity check")).stdout, "Selected n8n container identity check", 64 * 1024, ); const networkId = networkRecord?.Id; const n8nContainerId = containerRecord?.Id; if (!DOCKER_ID.test(networkId) || networkRecord.Name !== network || !DOCKER_ID.test(n8nContainerId) || containerRecord?.Name?.replace(/^\//u, "") !== name || containerRecord?.State?.Running !== true || containerRecord?.NetworkSettings?.Networks?.[network]?.NetworkID !== networkId) { throw new Error("The selected running n8n container or network identity could not be verified."); } // A plan review refuses a network on which a new sidecar could certainly not be reached or get out. if (requireSupportedNetwork) assertVpsNetworkSupported(networkRecord); return Object.freeze({ n8nContainerId, networkId }); } function validateReviewedTarget(remote, reviewedTarget, networkName) { if (!remote?.identity || !reviewedTarget || reviewedTarget.networkName !== networkName || validateDockerName(reviewedTarget.containerName) !== reviewedTarget.containerName || !DOCKER_ID.test(reviewedTarget.n8nContainerId) || !DOCKER_ID.test(reviewedTarget.networkId)) { throw new Error("The reviewed VPS or selected n8n destination changed."); } for (const field of ["host", "port", "fingerprint", "username", "authentication", "privilege", "loginUid", "effectiveUid"]) { if (remote.identity[field] !== reviewedTarget[field]) { throw new Error("The verified VPS identity differs from the reviewed target."); } } } async function reattestReviewedTarget(remote, reviewedTarget) { const actual = await reviewVpsSiwcTarget({ remote, networkName: reviewedTarget.networkName, containerName: reviewedTarget.containerName, }); if (actual.n8nContainerId !== reviewedTarget.n8nContainerId || actual.networkId !== reviewedTarget.networkId) { throw new Error("The reviewed n8n container or network changed. Review and confirm again."); } } async function validateReviewedAccount(registration, binding) { const record = await readRegistration(registration); if ( record?.registrationId !== binding.registrationId || record?.clientId !== binding.clientId || record?.generation !== binding.generation || record?.owner?.hostId !== binding.ownerHostId || record?.owner?.runtimeId !== binding.ownerRuntimeId || record?.handoff?.state !== "owned" || !record.planEnabled || !record.session?.scopes?.includes("chatgpt.tokens.use.direct") || !record.session?.refreshToken ) throw new Error("The selected ChatGPT account changed. Review and confirm it again."); } function parseHandoffOutput(output, label, maxBytes = 4096) { if (typeof output !== "string" || Buffer.byteLength(output) > maxBytes) { throw new Error(`${label} returned invalid attestation.`); } try { return JSON.parse(output); } catch { throw new Error(`${label} returned invalid attestation.`); } } async function runOrThrow(remote, command, label, input) { const result = await remote.exec(command, { timeoutMs: 90_000, ...(input === undefined ? {} : { input }), }); if (result.code !== 0) { throw new Error( `${label} failed. The existing n8n deployment was not changed.`, ); } return result; } // Sets an existing shared folder to mode 0700. Call it only in a path that has checked the // owner's final confirmation, after that path has re-attested what it is about to change. // Status reads, sign-in polls and cancels never call it. async function tightenSharedFolder(remote) { await runOrThrow(remote, SHARED_ROOT_TIGHTEN_COMMAND, "Shared folder permission check"); } function modelCheckFailure(result) { let recovery = "retry-later"; let code = "model_check_failed"; let status; let param; let requestId; if (typeof result?.stdout === "string" && Buffer.byteLength(result.stdout) <= 4096) { try { const parsed = JSON.parse(result.stdout); if (typeof parsed?.error?.code === "string" && /^[A-Za-z0-9_.-]{1,128}$/u.test(parsed.error.code)) { code = parsed.error.code; } if (Number.isInteger(parsed?.status) && parsed.status >= 400 && parsed.status <= 599) { status = parsed.status; } if (typeof parsed?.error?.param === "string" && /^[A-Za-z0-9_.:-]{1,128}$/u.test(parsed.error.param)) { param = parsed.error.param; } if (typeof parsed?.requestId === "string" && /^[A-Za-z0-9_.:-]{1,128}$/u.test(parsed.requestId)) { requestId = parsed.requestId; } if ([ "reauthorize", "enable-plan", "manage-usage", "fix-request", "fix-configuration", "review-again", "resolve-handoff", "retry-later", ].includes(parsed?.recovery)) recovery = parsed.recovery; } catch { // The destination owns the session; an unclassified response cannot // authorize a retry, account switch, or source-token restoration. } } const message = "The selected SIWC model check failed. The destination owns the session; n8n was not changed."; return Object.assign(new Error(message), { safeMessage: message, code, recovery, ...(status === undefined ? {} : { status }), ...(param === undefined ? {} : { param }), ...(requestId === undefined ? {} : { requestId }), }); } function parseModels(output) { try { const parsed = JSON.parse(output); if (!Array.isArray(parsed.data)) { throw new TypeError(); } const models = parsed.data .map((model) => model?.id) .filter( (id) => typeof id === "string" && id.length > 0 && id.length <= 128 && /^[a-zA-Z0-9_.:-]+$/.test(id), ); return models; } catch { throw new Error( "The sidecar started, but its model response could not be verified.", ); } } function hasPublishedHostPort(output) { try { const parsed = JSON.parse(output); const services = Array.isArray(parsed) ? parsed : [parsed]; if (services.length === 0) { throw new TypeError(); } for (const service of services) { if (!service || !Array.isArray(service.Publishers)) { throw new TypeError(); } for (const publisher of service.Publishers) { if ( !publisher || !Number.isInteger(publisher.PublishedPort) || publisher.PublishedPort < 0 || typeof publisher.URL !== "string" ) { throw new TypeError(); } if (publisher.PublishedPort > 0 || publisher.URL.trim() !== "") { return true; } } } return false; } catch { throw new Error("The published-port safety check failed."); } } const sha256 = contents => createHash("sha256").update(contents).digest("hex"); const TARGET_FIELDS = ["host", "port", "fingerprint", "username", "authentication", "privilege", "loginUid", "effectiveUid", "networkName", "containerName", "n8nContainerId", "networkId"]; const STAGES = new Set(["preparing", "context", "built", "transferred", "complete"]); // Everything the image is built from; the Compose file (one-time key verifier) is per install. function managedRuntimeFiles(assets) { const files = new Map([ [MANAGED_MARKER_PATH, SIDECAR_MARKER_CONTENT], [SIDECAR_BUILD_IGNORE_PATH, SIDECAR_BUILD_IGNORE_CONTENT], [`${INSTALL_ROOT}/Dockerfile`, createDockerfile()], [`${INSTALL_ROOT}/package.json`, assets.packageJson], [`${INSTALL_ROOT}/package-lock.json`, assets.packageLock], ]); for (const asset of assets.files) { if (!ASSET_PATHS.has(asset.path) || !Buffer.isBuffer(asset.contents)) { throw new Error("The packaged SIWC runtime assets are invalid."); } files.set(`${INSTALL_ROOT}/${asset.path}`, asset.contents); } return files; } function imageSourceDigest(files) { return sha256([...files] .filter(([path]) => path !== `${INSTALL_ROOT}/docker-compose.yml` && path !== MANAGED_MARKER_PATH) .map(([path, contents]) => `${sha256(contents)} ${path}\n`).sort().join("")); } async function inspectResumeLock(remote, allowHeld = true) { const lock = (await runOrThrow(remote, inspectVpsResumeLockCommand(VPS_OPERATION_LOCKS.oauth), "Interrupted SIWC operation lock check")).stdout.trim(); if (lock !== "none" && (!allowHeld || !/^\d+:\d+$/u.test(lock))) { throw new Error("The interrupted operation lock is uncertain. An administrator must inspect it."); } return lock === "none" ? undefined : lock; } async function attestLiveAccount(remote, registrationId) { const account = parseHandoffOutput( (await runOrThrow(remote, createVerificationCommands().accountLive, "Destination SIWC account check")).stdout, "Destination SIWC account check", ).account; if (account?.registrationId !== registrationId || account?.ownership !== "owned" || account?.ownerRuntimeId !== "vps_n8n") { throw new Error("The installed SIWC account could not be attested."); } return account; } async function readStaging(remote, reviewedTarget, { allowTargetRefresh = false } = {}) { const output = (await runOrThrow(remote, createVerificationCommands().staging, "SIWC staging ownership check")).stdout; const checkpoint = parseHandoffOutput(output, "SIWC staging ownership check", 16384); if (checkpoint === null) return null; if (checkpoint?.schemaVersion !== 1 || !STAGES.has(checkpoint.stage) || !/^[0-9a-f-]{36}$/u.test(checkpoint.installId) || !["installed", "migrated", "replaced"].includes(checkpoint.deploymentMode) || (checkpoint.stage === "complete" || allowTargetRefresh ? ["host", "port", "fingerprint", "networkId"] : TARGET_FIELDS) .some(field => checkpoint.reviewedTarget?.[field] !== reviewedTarget[field]) || checkpoint.registrationId !== checkpoint.authBinding?.registrationId) { throw new Error("The SIWC staging owner or reviewed destination changed."); } validateSiwcAuthBinding(checkpoint.authBinding); return { checkpoint, checkpointSha256: sha256(output) }; } async function attestStagedFiles(remote, checkpoint) { const output = (await runOrThrow(remote, createVerificationCommands().stagedFiles, "Staged SIWC files check")).stdout; const files = parseManagedFileHashes(output); for (const [path, digest] of Object.entries(files)) { if (checkpoint.files?.[path] !== digest && ((!["preparing", "context"].includes(checkpoint.stage) && !(checkpoint.deploymentMode === "migrated" && path === `${INSTALL_ROOT}/openai-oauth-sidecar.mjs`)) || checkpoint.previousFiles?.[path] !== digest)) { throw new Error("A staged SIWC file changed. Review the installation before resuming."); } } if (["built", "transferred", "complete"].includes(checkpoint.stage) && Object.keys(checkpoint.files ?? {}).some(path => !Object.hasOwn(files, path))) { throw new Error("A required staged SIWC file is missing."); } return files; } async function publishCheckpoint(remote, checkpoint, stage) { const next = { ...checkpoint, stage }; await remote.publishManagedFile(SIWC_STAGING_PATH, `${JSON.stringify(next)}\n`, 0o600); Object.assign(checkpoint, next); } function partialFinalization(result, error = "SIWC finalization could not be confirmed. Review recovery before using this credential.") { return { ...result, deploymentMode: "partial", readiness: "unverified", finalizationFailure: { error, recovery: "resolve-handoff" }, }; } export async function reviewVpsSiwcResume({ remote, networkName, reviewedTarget, registrationId, }) { validateReviewedTarget(remote, reviewedTarget, networkName); validateSiwcRegistrationId(registrationId); await reattestReviewedTarget(remote, reviewedTarget); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (staging?.checkpoint.runtimeUpdate) throw new Error(RUNTIME_UPDATE_PENDING); if (!staging || (staging.checkpoint.registrationId !== registrationId && staging.checkpoint.notAccepted !== true)) { throw new Error("There is no matching interrupted SIWC installation to resume."); } await attestStagedFiles(remote, staging.checkpoint); const lock = await inspectResumeLock(remote); return Object.freeze({ installId: staging.checkpoint.installId, registrationId, stage: staging.checkpoint.stage, checkpointSha256: staging.checkpointSha256, deploymentMode: staging.checkpoint.deploymentMode, ...(lock ? { operationLockIdentity: lock } : {}), ...(staging.checkpoint.registrationId === registrationId ? {} : { stagedRegistrationId: staging.checkpoint.registrationId }), }); } export async function reconcileVpsSiwcHandoff({ remote, networkName, reviewedTarget, registration, confirmed, }) { if (confirmed !== true) throw new Error("Confirm reconciliation of the frozen SIWC handoff."); validateReviewedTarget(remote, reviewedTarget, networkName); validateSiwcRegistrationId(registration?.registrationId); const pending = await readPendingAuthHandoff(registration); if (!pending) throw new Error("The selected account has no frozen SIWC handoff."); await reattestReviewedTarget(remote, reviewedTarget); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (staging?.checkpoint.runtimeUpdate) throw new Error(RUNTIME_UPDATE_PENDING); if (!staging || staging.checkpoint.registrationId !== registration.registrationId || staging.checkpoint.destination?.hostId !== pending.target.hostId || staging.checkpoint.destination?.runtimeId !== pending.target.runtimeId) { throw new Error("The frozen handoff differs from the reviewed VPS installation."); } await attestStagedFiles(remote, staging.checkpoint); await attestInstalledImage(remote, staging.checkpoint); const config = parseHandoffOutput((await runOrThrow(remote, createVerificationCommands().managementConfig, "Reconciliation Compose check")).stdout, "Reconciliation Compose check", 64 * 1024); if (attestVpsSiwcCompose(config, networkName) !== registration.registrationId) { throw new Error("The destination registration changed."); } const response = await readPendingVpsReceipt(remote, pending); if (response?.receipt === null) { await assertNoVpsOneOffContainers(remote); const lock = (await runOrThrow(remote, inspectVpsResumeLockCommand(VPS_OPERATION_LOCKS.oauth), "Frozen SIWC operation lock check")).stdout.trim(); if (lock !== "none" && !/^\d+:\d+$/u.test(lock)) throw new Error("The frozen SIWC lock is uncertain."); await withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async () => { await reattestReviewedTarget(remote, reviewedTarget); const current = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (current?.checkpointSha256 !== staging.checkpointSha256) { throw new Error("The frozen installation changed during reconciliation."); } const settled = await readPendingVpsReceipt(remote, pending); if (settled?.receipt !== null) { throw Object.assign(new Error("The SIWC handoff outcome changed. Reconcile the accepted receipt before resuming."), { remoteOutcomeUnknown: true, }); } await assertNoVpsOneOffContainers(remote); // Confirmed above; the frozen installation is the reviewed one. await tightenSharedFolder(remote); staging.checkpoint.notAccepted = true; await publishCheckpoint(remote, staging.checkpoint, staging.checkpoint.stage); }, { resumeIdentity: lock === "none" ? undefined : lock }); return { outcome: "not-accepted", account: await readRegistrationView(registration) }; } if (response?.receipt?.handoffId !== pending.handoffId || JSON.stringify(response.receipt.binding) !== JSON.stringify(pending.binding)) { throw new Error("The destination receipt differs from the frozen SIWC handoff."); } return { outcome: "finished", account: await finishAuthHandoff(registration, { handoffId: pending.handoffId, receipt: response.receipt.receipt, }) }; } async function attestInstalledImage(remote, checkpoint) { const image = parseHandoffOutput((await runOrThrow(remote, createVerificationCommands().ownerImage, "Sidecar executable image check")).stdout, "Sidecar executable image check", 64 * 1024); return attestVpsSiwcImage(image, checkpoint?.imageId); } async function readPendingVpsReceipt(remote, pending) { try { return parseHandoffOutput((await runOrThrow(remote, createVerificationCommands().receipt, "Destination receipt reconciliation", JSON.stringify({ handoffId: pending.handoffId, binding: pending.binding, identity: pending.identity }))).stdout, "Destination receipt reconciliation", 8192); } catch (cause) { throw Object.assign(new Error("The SIWC receipt outcome is unresolved. The sender remains frozen."), { remoteOutcomeUnknown: true, ...(cause?.timedOut ? { timedOut: true } : {}), }); } } async function assertNoVpsOneOffContainers(remote) { try { const result = await runOrThrow(remote, createVerificationCommands().oneOffContainers, "Frozen SIWC handoff writer check"); if (typeof result.stdout !== "string" || result.stdout.trim() !== "") { throw new Error("A one-off SIWC operation may still own the handoff."); } } catch (cause) { throw Object.assign(new Error("The SIWC handoff may still be running. The sender remains frozen."), { remoteOutcomeUnknown: true, ...(cause?.timedOut ? { timedOut: true } : {}), }); } } export async function installSidecar({ remote, networkName, registration, authBinding, reviewedTarget, backgroundConsent, replacementConsent, existingBinding, migrationConsent, legacyBinding, resume, confirmed, }, { collectAssets = collectSiwcRuntimeAssets } = {}) { if (confirmed !== true) throw new Error("Confirm the sidecar-only deployment before installing."); validateReviewedTarget(remote, reviewedTarget, networkName); const binding = validateSiwcAuthBinding(authBinding); if (registration?.registrationId !== binding.registrationId || !registration.storageRoot) { throw new Error("The selected SIWC registration does not match the reviewed plan."); } const safeConsent = Object.freeze({ ...validateSiwcBackgroundConsent(backgroundConsent, { target: "vps-n8n" }), }); const replacing = replacementConsent !== undefined || existingBinding !== undefined; const migrating = migrationConsent !== undefined || legacyBinding !== undefined; if ((migrating && replacing) || (migrating && migrationConsent !== true)) { throw new Error("Confirm the separately reviewed VPS legacy bridge migration."); } if (replacing && replacementConsent !== true) { throw new Error("Confirm the separately reviewed VPS account replacement."); } const source = await readRegistration(registration); const transferred = resume && source?.handoff?.state === "transferred"; if (!transferred) await validateReviewedAccount(registration, binding); else if (source.registrationId !== binding.registrationId || source.clientId !== binding.clientId || source.generation !== binding.generation || source.owner.hostId !== binding.ownerHostId || source.owner.runtimeId !== binding.ownerRuntimeId) { throw new Error("The transferred account changed. Review recovery again."); } const verification = createVerificationCommands(); const deploymentCommands = createDeploymentCommands(); assertSidecarOnlyCommands([PRECHECK_COMMAND, SIDECAR_MANAGED_CONTEXT_GUARD, SIDECAR_FRESH_CONTEXT_GUARD, ...deploymentCommands, ...Object.values(verification)]); let staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: !!resume }); let checkpoint = staging?.checkpoint; if (checkpoint?.runtimeUpdate) throw new Error(RUNTIME_UPDATE_PENDING); if (resume) { const current = await reviewVpsSiwcResume({ remote, networkName, reviewedTarget, registrationId: binding.registrationId, }); for (const field of ["installId", "registrationId", "stage", "checkpointSha256", "deploymentMode", "operationLockIdentity", "stagedRegistrationId"]) { if (resume[field] !== current[field]) throw new Error("The reviewed SIWC resume binding changed."); } if (resume.stagedRegistrationId) { if (transferred || source?.handoff?.state !== "owned" || checkpoint.notAccepted !== true) { throw new Error("A fresh authorized SIWC session is required after unaccepted transfer."); } } else { for (const field of ["registrationId", "clientId", "ownerHostId", "ownerRuntimeId"]) { if (checkpoint.authBinding[field] !== binding[field]) throw new Error("The staged source account changed."); } } } else { if (checkpoint && checkpoint.stage !== "complete") { throw new Error("This SIWC installation is staged. Review and confirm resume instead."); } checkpoint = null; } let reviewedLegacy; let reviewedExisting; if (!resume && migrating) { reviewedLegacy = await reviewVpsLegacyMigration({ remote, networkName, reviewedTarget }); for (const field of ["containerId", "imageId", "networkName", "networkId", "authIdentity", "running"]) { if (legacyBinding?.[field] !== reviewedLegacy[field]) { throw new Error("The old VPS sidecar or protected credential changed. Review migration again."); } } } if (!resume && replacing) { reviewedExisting = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); for (const field of ["registrationId", "containerId", "imageId", "networkName", "networkId", "running"]) { if (existingBinding?.[field] !== reviewedExisting[field]) { throw new Error("The reviewed VPS sidecar changed. Review replacement again."); } } if (existingBinding.registrationId === binding.registrationId || typeof existingBinding.ownerHostId !== "string" || typeof existingBinding.expectedGeneration !== "string") { throw new Error("The old destination owner or selected account changed."); } } if (!resume && !migrating) { const precheck = await remote.exec(PRECHECK_COMMAND); if (precheck.code !== 0 || precheck.stdout?.trim() !== (replacing ? "managed" : "new")) { throw new Error("The existing VPS bridge requires separately reviewed SIWC recovery."); } if (!replacing) await runOrThrow(remote, SIDECAR_FRESH_CONTEXT_GUARD, "Fresh sidecar directory check"); } const files = managedRuntimeFiles(await collectAssets()); const clientCredential = randomBytes(32).toString("base64url"); files.set(`${INSTALL_ROOT}/docker-compose.yml`, createComposeFile({ networkName, registrationId: binding.registrationId, runtimeId: "vps_n8n", tokenSha256: sha256(clientCredential), })); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "Managed SIWC publication targets check"); // Only the fixed root and its shared ownership marker precede the journal. const sharedRoot = (await runOrThrow(remote, verification.sharedRoot, "Shared root ownership check")).stdout.trim(); if (!["managed", "absent"].includes(sharedRoot)) throw new Error("The shared root owner changed."); await reattestReviewedTarget(remote, reviewedTarget); await runOrThrow(remote, deploymentCommands[0], "Sidecar root creation"); if (sharedRoot === "absent") { await remote.publishManagedFile(SHARED_ROOT_MARKER_PATH, SHARED_ROOT_MARKER_CONTENT, 0o644); } return withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async build => { await reattestReviewedTarget(remote, reviewedTarget); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "Managed SIWC context check"); if (resume) { staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (staging?.checkpointSha256 !== resume.checkpointSha256) { throw new Error("The staged installation changed before resume."); } await attestStagedFiles(remote, checkpoint); checkpoint.previousFiles = parseManagedFileHashes((await runOrThrow(remote, verification.stagedFiles, "Interrupted managed files check")).stdout); if (resume.stagedRegistrationId) { const pending = await readPendingAuthHandoff({ storageRoot: registration.storageRoot, registrationId: resume.stagedRegistrationId, }); if (!pending || pending.binding.registrationId !== checkpoint.registrationId || pending.binding.clientId !== checkpoint.authBinding.clientId || pending.binding.source.hostId !== checkpoint.authBinding.ownerHostId || pending.binding.source.runtimeId !== checkpoint.authBinding.ownerRuntimeId || pending.target.hostId !== checkpoint.destination?.hostId || pending.target.runtimeId !== checkpoint.destination?.runtimeId) { throw new Error("The original frozen SIWC handoff changed. Reconcile it before resuming."); } await attestInstalledImage(remote, checkpoint); const config = parseHandoffOutput((await runOrThrow(remote, verification.managementConfig, "Original handoff configuration check")).stdout, "Original handoff configuration check", 64 * 1024); if (attestVpsSiwcCompose(config, networkName) !== resume.stagedRegistrationId) { throw new Error("The original destination configuration changed."); } const original = await readPendingVpsReceipt(remote, pending); if (original?.receipt !== null) { throw new Error("The original SIWC handoff was accepted. Reconcile it before selecting a fresh registration."); } await assertNoVpsOneOffContainers(remote); checkpoint.registrationId = binding.registrationId; checkpoint.notAccepted = false; } if (!transferred) checkpoint.authBinding = binding; checkpoint.reviewedTarget = Object.fromEntries(TARGET_FIELDS.map(field => [field, reviewedTarget[field]])); } else { const previous = (await runOrThrow(remote, verification.stagedFiles, "Existing managed files check")).stdout; const previousFiles = parseManagedFileHashes(previous); checkpoint = { schemaVersion: 1, installId: randomUUID(), registrationId: binding.registrationId, authBinding: binding, reviewedTarget: Object.fromEntries(TARGET_FIELDS.map(field => [field, reviewedTarget[field]])), deploymentMode: migrating ? "migrated" : replacing ? "replaced" : "installed", previousFiles, ...(reviewedLegacy ? { legacyBinding: reviewedLegacy, legacyFiles: previousFiles } : {}), ...(reviewedExisting ? { existingBinding: { ...reviewedExisting, ownerHostId: existingBinding.ownerHostId, expectedGeneration: existingBinding.expectedGeneration } } : {}), }; } let resumedAccount; if (transferred) { await attestInstalledImage(remote, checkpoint); const config = parseHandoffOutput((await runOrThrow(remote, verification.managementConfig, "Resumed execution configuration check")).stdout, "Resumed execution configuration check", 64 * 1024); if (attestVpsSiwcCompose(config, networkName) !== binding.registrationId) { throw new Error("The resumed destination configuration changed."); } const presence = await runOrThrow(remote, verification.ownerPresence, "Resumed owner presence check"); if (presence.stdout.trim()) { let owner; try { owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget, allowStaged: true }); } catch { await attestStoppedPreviousContainer(remote, checkpoint); } if (owner && (owner.registrationId !== binding.registrationId || !await stopAttestedService(remote, reviewedTarget, owner))) { throw Object.assign(new Error("The resumed owned service stop could not be attested."), { remoteOutcomeUnknown: true }); } } resumedAccount = parseHandoffOutput((await runOrThrow(remote, verification.account, "Resumed destination account check")).stdout, "Resumed destination account check").account; if (resumedAccount?.registrationId !== binding.registrationId || resumedAccount.ownerHostId !== checkpoint.destination?.hostId || resumedAccount.ownerRuntimeId !== "vps_n8n" || resumedAccount.ownership !== "owned") { throw new Error("The resumed destination account changed."); } checkpoint.containerId = undefined; } const imageSourceSha256 = imageSourceDigest(files); const rebuild = !transferred && checkpoint.deploymentMode !== "replaced" && (!checkpoint.imageId || checkpoint.imageSourceSha256 !== imageSourceSha256); // Forget the old image before uploading, so a crash after the build never pairs // the moved tag with a recorded old image ID. if (rebuild) Object.assign(checkpoint, { imageId: undefined, imageSourceSha256: undefined }); checkpoint.files = Object.fromEntries([...files].map(([path, contents]) => [path, sha256(contents)])); await publishCheckpoint(remote, checkpoint, resume && checkpoint.stage !== "preparing" ? "context" : checkpoint.stage ?? "preparing"); if (checkpoint.stage === "preparing") { if (checkpoint.deploymentMode === "migrated") { await prepareLegacyMigration({ remote, networkName, reviewedTarget, reviewedLegacy: checkpoint.legacyBinding, verification, checkpoint }); } else if (checkpoint.deploymentMode === "replaced") { await prepareAccountReplacement({ remote, networkName, reviewedTarget, reviewedExisting: checkpoint.existingBinding, existingBinding: checkpoint.existingBinding, verification, runtimeId: "vps_n8n" }); } await publishCheckpoint(remote, checkpoint, "context"); } else if (!transferred && checkpoint.deploymentMode !== "installed") { await attestStoppedPreviousContainer(remote, checkpoint); } await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "SIWC source-directory check"); await runOrThrow(remote, deploymentCommands[1], "SIWC storage directory creation"); await runOrThrow(remote, deploymentCommands[2], "Sidecar source directory creation"); let credentialPublicationStarted = false; try { for (const [path, contents] of files) { if (path === `${INSTALL_ROOT}/docker-compose.yml`) credentialPublicationStarted = true; await remote.publishManagedFile(path, contents, 0o644); } await runOrThrow(remote, deploymentCommands[3], "Sidecar Compose validation"); if (rebuild) { await build(deploymentCommands[4]); await build.releaseState(); } checkpoint.imageId = await attestInstalledImage(remote, checkpoint); if (rebuild) checkpoint.imageSourceSha256 = imageSourceSha256; } catch (error) { if (!transferred || !credentialPublicationStarted) throw error; return partialFinalization({ baseUrl: `http://${SIDECAR_HOSTNAME}:10531/v1`, clientCredential, credentialShownOnce: true, useResponsesApi: true, models: [], account: resumedAccount, runtimeState: error?.remoteOutcomeUnknown ? "unknown" : "stopped", hostPublication: error?.remoteOutcomeUnknown ? "unknown" : "none", ...(error?.remoteOutcomeUnknown ? { remoteOutcomeUnknown: true } : {}), }); } let journalFailed = false; try { await publishCheckpoint(remote, checkpoint, transferred ? "transferred" : "built"); } catch (error) { if (!transferred) throw error; journalFailed = true; } let result; if (transferred) { result = await startTransferredSidecar({ remote, verification, deploymentCommands, binding, clientCredential, deploymentMode: checkpoint.deploymentMode, accepted: { account: resumedAccount }, destination: checkpoint.destination, reviewedTarget, checkpoint }); if (journalFailed) result = partialFinalization(result); } else { result = await transferSidecarSession({ remote, verification, deploymentCommands, registration, binding, backgroundConsent: safeConsent, runtimeId: "vps_n8n", clientCredential, deploymentMode: checkpoint.deploymentMode, reviewedTarget, checkpoint, expectedDestinationHostId: checkpoint.existingBinding?.ownerHostId }); } try { if (result.runtimeState === "running") { if (checkpoint.deploymentMode === "migrated") { await remote.publishManagedFile(SIWC_MIGRATED_PATH, SIWC_MIGRATION_MARKER_CONTENT, 0o600); } await publishCheckpoint(remote, checkpoint, "complete"); } } catch { result = partialFinalization(result); } return { ...result, ...(checkpoint.deploymentMode === "migrated" ? { legacyRetained: true, ...(result.deploymentMode === "partial" ? { migrationPending: true } : { migratedLegacy: true }), } : {}), }; }, { resumeIdentity: resume?.operationLockIdentity }); } async function transferSidecarSession({ remote, verification, deploymentCommands, registration, binding, backgroundConsent, runtimeId, clientCredential, deploymentMode, expectedDestinationHostId, reviewedTarget, checkpoint, }) { await reattestReviewedTarget(remote, reviewedTarget); await attestInstalledImage(remote, checkpoint); const config = parseHandoffOutput((await runOrThrow(remote, verification.managementConfig, "Handoff execution configuration check")).stdout, "Handoff execution configuration check", 64 * 1024); if (attestVpsSiwcCompose(config, reviewedTarget.networkName) !== binding.registrationId) { throw new Error("The destination handoff configuration changed."); } const destination = parseHandoffOutput( (await runOrThrow(remote, verification.host, "Destination host initialization")).stdout, "Destination host initialization", ); if (typeof destination.hostId !== "string" || destination.runtimeId !== runtimeId || destination.hostId === binding.ownerHostId || expectedDestinationHostId && destination.hostId !== expectedDestinationHostId) { throw new Error("The destination SIWC host identity is invalid."); } checkpoint.destination = destination; await publishCheckpoint(remote, checkpoint, "built"); await reattestReviewedTarget(remote, reviewedTarget); await validateReviewedAccount(registration, binding); const { handoffId } = await prepareAuthHandoff(registration, { expectedGeneration: binding.generation, target: destination, backgroundConsent, }); const contents = await readAuthHandoff(registration, { handoffId, expectedGeneration: binding.generation, }); if (contents.length > MAX_HANDOFF_BYTES) { throw new Error("The selected session remains frozen: protected handoff exceeds the SSH limit."); } let accepted; try { const result = await remote.exec(verification.accept, { input: contents.toString("utf8"), timeoutMs: 120_000, }); if (result.code !== 0) throw new Error("Destination SIWC handoff was not confirmed."); accepted = parseHandoffOutput(result.stdout, "Destination SIWC handoff"); } catch (error) { const failure = new Error("The SIWC transfer outcome is unresolved. The sender remains frozen; inspect the destination before retrying."); failure.remoteOutcomeUnknown = true; if (error?.timedOut === true) failure.timedOut = true; throw failure; } if (accepted?.handoffId !== handoffId || accepted.account?.registrationId !== binding.registrationId || accepted.account?.ownerHostId !== destination.hostId || accepted.account?.ownerRuntimeId !== runtimeId || accepted.account?.ownership !== "owned") { throw new Error("The SIWC handoff receipt or destination account did not match. The sender remains frozen."); } await finishAuthHandoff(registration, { handoffId, receipt: accepted.receipt }); let finalizationFailed = false; try { await publishCheckpoint(remote, checkpoint, "transferred"); } catch { finalizationFailed = true; } const result = await startTransferredSidecar({ remote, verification, deploymentCommands, binding, clientCredential, deploymentMode, accepted, destination, reviewedTarget, checkpoint }); return finalizationFailed ? partialFinalization(result) : result; } async function startTransferredSidecar({ remote, verification, deploymentCommands, binding, clientCredential, deploymentMode, accepted, destination, reviewedTarget, checkpoint, }) { const runtimeId = "vps_n8n"; try { await runOrThrow(remote, deploymentCommands.at(-1), "Sidecar start"); const running = await runOrThrow(remote, verification.runningService, "Sidecar status check"); if (!running.stdout.split(/\s+/u).includes("openai-oauth")) { throw new Error("The sidecar did not reach the running state."); } const publication = await remote.exec(verification.publicationState); if (publication.code !== 0 || hasPublishedHostPort(publication.stdout)) { throw Object.assign(new Error("The sidecar unexpectedly published a host port."), { unsafePublication: true }); } const owner = await reviewVpsSiwcReplacement({ remote, networkName: reviewedTarget.networkName, reviewedTarget, allowStaged: true, }); if (!owner.running || owner.registrationId !== binding.registrationId) { throw new Error("The started SIWC owner could not be attested."); } checkpoint.containerId = owner.containerId; } catch (error) { const stopped = await stopAttestedService(remote, reviewedTarget); return { baseUrl: `http://${SIDECAR_HOSTNAME}:10531/v1`, clientCredential, credentialShownOnce: true, useResponsesApi: true, models: [], deploymentMode: "partial", account: accepted.account, hostPublication: stopped ? "none" : "unknown", readiness: "unverified", runtimeState: stopped ? "stopped" : "unknown", runtimeFailure: error?.unsafePublication === true ? { error: "The sidecar may have published a host port. Inspect it before use.", status: 503, recovery: "resolve-handoff" } : safeSiwcRuntimeFailure(), ...(deploymentMode === "replaced" ? { replacementPending: true } : {}), }; } let models = []; let catalogFailure; try { const response = await remote.exec(verification.models, { input: clientCredential, timeoutMs: 45_000 }); if (response.code !== 0) throw modelCheckFailure(response); models = parseModels(response.stdout); } catch (error) { catalogFailure = safeSiwcCatalogFailure(error); } let account = accepted.account; let status; try { status = parseHandoffOutput( (await runOrThrow(remote, verification.accountLive, "Destination SIWC account check")).stdout, "Destination SIWC account check", ); } catch { catalogFailure ??= safeSiwcCatalogFailure({ code: "owner_status_unavailable", recovery: "retry-later", }); } if (status && (status.account?.registrationId !== binding.registrationId || status.account.ownerHostId !== destination.hostId || status.account.ownerRuntimeId !== runtimeId || status.account.ownership !== "owned")) { return partialFinalization({ baseUrl: `http://${SIDECAR_HOSTNAME}:10531/v1`, clientCredential, credentialShownOnce: true, useResponsesApi: true, models: [], account, runtimeState: "running", hostPublication: "none", }, "The destination account could not be attested. Do not use this credential until recovery is reviewed."); } if (status) account = status.account; return { baseUrl: `http://${SIDECAR_HOSTNAME}:10531/v1`, clientCredential, credentialShownOnce: true, useResponsesApi: true, models, deploymentMode, account, hostPublication: "none", runtimeState: "running", readiness: catalogFailure ? "unverified" : "verified", ...(catalogFailure ? { catalogFailure } : {}), ...(deploymentMode === "replaced" ? { replacedAccount: true } : {}), }; } async function prepareAccountReplacement({ remote, networkName, reviewedTarget, reviewedExisting, existingBinding, verification, runtimeId, }) { const current = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget, allowStaged: true, }); for (const field of [ "registrationId", "containerId", "imageId", "networkName", "networkId", "running", ]) { if (current[field] !== reviewedExisting[field]) { throw new Error("The old VPS sidecar changed before replacement. Review again."); } } const old = parseHandoffOutput( (await runOrThrow(remote, verification.account, "Old destination SIWC account check")).stdout, "Old destination SIWC account check", ).account; if (old?.registrationId !== existingBinding.registrationId || old?.ownerHostId !== existingBinding.ownerHostId || old?.ownerRuntimeId !== runtimeId || old?.generation !== existingBinding.expectedGeneration || old?.session !== "signed-out" || old?.planEnabled !== false) { throw new Error("Sign out of the exact old VPS SIWC account before reviewed replacement."); } await reattestReviewedTarget(remote, reviewedTarget); await runOrThrow(remote, verification.stop, "Owned old sidecar stop"); const running = await runOrThrow(remote, verification.runningService, "Old sidecar stopped check"); if (running.stdout.split(/\s+/u).includes("openai-oauth")) { throw new Error("The old sidecar did not stop; replacement was not attempted."); } const stopped = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget, allowStaged: true, }); if (stopped.running || stopped.containerId !== current.containerId || stopped.imageId !== current.imageId || stopped.registrationId !== current.registrationId || stopped.networkName !== current.networkName) { throw new Error("The old VPS sidecar changed while stopping; it will not be resumed automatically."); } const postStop = parseHandoffOutput( (await runOrThrow(remote, verification.account, "Stopped old SIWC account check")).stdout, "Stopped old SIWC account check", ).account; if (postStop?.registrationId !== old.registrationId || postStop?.generation !== old.generation || postStop?.session !== "signed-out" || postStop?.planEnabled !== false) { throw new Error("The old signed-out SIWC account changed. The sidecar remains stopped."); } } async function prepareLegacyMigration({ remote, networkName, reviewedTarget, reviewedLegacy, verification, checkpoint, }) { const current = await reviewVpsLegacyMigration({ remote, networkName, reviewedTarget, allowPending: true, }); for (const field of [ "containerId", "imageId", "networkName", "networkId", "authIdentity", ]) { if (current[field] !== reviewedLegacy[field]) { throw new Error("The old VPS sidecar changed before migration. Review again."); } } const checkArchive = async () => { const output = (await runOrThrow(remote, verification.legacyArchiveFiles, "Legacy archive ownership check")).stdout.trim(); if (output === "absent") return false; const archived = parseManagedFileHashes(output); for (const [path, digest] of Object.entries(archived)) { if (!path.startsWith(`${INSTALL_ROOT}/legacy/`) || checkpoint.legacyFiles?.[path.replace("/legacy/", "/")] !== digest) { throw new Error("The retained legacy archive changed; it will not be overwritten."); } } return ["Dockerfile", "docker-compose.yml", ".managed-by-n8n-openai-oauth"] .every(name => Object.hasOwn(archived, `${INSTALL_ROOT}/legacy/${name}`)); }; if (!await checkArchive()) { await runOrThrow(remote, verification.archiveLegacy, "Legacy sidecar metadata preservation"); if (!await checkArchive()) throw new Error("The retained legacy archive is incomplete."); } await remote.publishManagedFile(SIWC_MIGRATION_PENDING_PATH, SIWC_MIGRATION_MARKER_CONTENT, 0o600); await reattestReviewedTarget(remote, reviewedTarget); if (reviewedLegacy.running) { await runOrThrow(remote, verification.stop, "Attested legacy sidecar stop"); } const running = await runOrThrow(remote, verification.runningService, "Legacy sidecar stopped check"); if (running.stdout.split(/\s+/u).includes("openai-oauth")) { throw new Error("The old refresh writer did not stop; the new SIWC transfer was not attempted."); } const stopped = await reviewVpsLegacyMigration({ remote, networkName, reviewedTarget, allowPending: true, }); for (const field of [ "containerId", "imageId", "networkName", "authIdentity", ]) { if (stopped[field] !== reviewedLegacy[field]) { throw new Error("The old VPS sidecar changed while stopping. It will not be resumed automatically."); } } if (stopped.running || stopped.networkId && stopped.networkId !== reviewedLegacy.networkId) { throw new Error("The old sidecar identity or refresh-writer state is unresolved."); } } async function attestStoppedPreviousContainer(remote, checkpoint) { const previous = checkpoint.legacyBinding ?? checkpoint.existingBinding; const container = parseHandoffOutput((await runOrThrow(remote, createVerificationCommands().ownerContainer, "Staged previous writer identity check")).stdout, "Staged previous writer identity check", 64 * 1024); const root = checkpoint.legacyBinding ? "auth" : "siwc"; const destination = checkpoint.legacyBinding ? "/home/node/.codex" : "/home/node/.relmio-siwc"; if (container?.Id !== previous.containerId || container.Image !== previous.imageId || container.State?.Running !== false || container.State?.Paused !== false || container.Mounts?.length !== 1 || container.Mounts[0]?.Type !== "bind" || container.Mounts[0]?.Source !== `${INSTALL_ROOT}/${root}` || container.Mounts[0]?.Destination !== destination || container.Config?.Labels?.["com.docker.compose.project"] !== "n8n-openai-oauth" || container.Config?.Labels?.["com.docker.compose.service"] !== "openai-oauth" || container.Config?.Labels?.["io.n8n-openai-oauth.managed"] !== "true") { throw new Error("The staged previous writer changed or is not stopped."); } } export async function reviewVpsLegacyMigration({ remote, networkName, reviewedTarget, allowPending = false, }) { validateReviewedTarget(remote, reviewedTarget, networkName); const verification = createVerificationCommands(); await reattestReviewedTarget(remote, reviewedTarget); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "Legacy managed context check"); assertSidecarOnlyCommands(Object.values(verification)); const state = await runOrThrow(remote, verification.legacyStatus, "Legacy VPS bridge ownership check"); if (state.stdout.trim() !== "legacy" && !(allowPending && state.stdout.trim() === "partial")) { throw new Error("The selected VPS sidecar is not the managed legacy bridge."); } const config = parseHandoffOutput( (await runOrThrow(remote, verification.managementConfig, "Legacy Compose ownership check")).stdout, "Legacy Compose ownership check", ); const service = config?.services?.["openai-oauth"]; if (Object.keys(config?.services ?? {}).length !== 1 || config.networks?.["n8n-shared"]?.name !== networkName || config.networks?.["n8n-shared"]?.external !== true || service?.build?.context !== INSTALL_ROOT || Array.isArray(service?.ports) && service.ports.length > 0 || service?.volumes?.length !== 1 || service.volumes[0].source !== `${INSTALL_ROOT}/auth` || service.volumes[0].target !== "/home/node/.codex") { throw new Error("The old VPS bridge Compose target is not exactly owned."); } const inspected = await runOrThrow(remote, verification.ownerContainer, "Legacy container identity check"); if (typeof inspected.stdout !== "string" || Buffer.byteLength(inspected.stdout) > 64 * 1024) { throw new Error("The old VPS container identity is invalid."); } let container; try { container = JSON.parse(inspected.stdout); } catch { throw new Error("The old VPS container identity is invalid."); } const containerId = container?.Id; const imageId = container?.Image; const networkId = container?.NetworkSettings?.Networks?.[networkName]?.NetworkID; const mounts = container?.Mounts; if (!/^[a-f0-9]{64}$/u.test(containerId) || !/^sha256:[a-f0-9]{64}$/u.test(imageId) || typeof container?.State?.Running !== "boolean" || container?.State?.Paused !== false || container?.Config?.Labels?.["com.docker.compose.project"] !== "n8n-openai-oauth" || container?.Config?.Labels?.["com.docker.compose.service"] !== "openai-oauth" || container?.Config?.Labels?.["io.n8n-openai-oauth.managed"] !== "true" || !Array.isArray(mounts) || mounts.length !== 1 || mounts[0]?.Type !== "bind" || mounts[0]?.Source !== `${INSTALL_ROOT}/auth` || mounts[0]?.Destination !== "/home/node/.codex" || (networkId && networkId !== reviewedTarget.networkId) || (container.State.Running && !/^[a-f0-9]{64}$/u.test(networkId))) { throw new Error("The old VPS bridge container, image, network or credential root changed."); } const credential = await runOrThrow(remote, verification.legacyCredential, "Legacy credential ownership check"); const authIdentity = credential.stdout.trim(); if (!/^\d+:\d+:1000:600:\d+:\d+$/u.test(authIdentity)) { throw new Error("The old protected credential generation could not be attested."); } return Object.freeze({ containerId, imageId, networkName, ...(networkId ? { networkId } : {}), authIdentity, running: container.State.Running, }); } // The owner's own facts, for a target and a staging record the caller read in this request. async function attestOwnedSidecar(remote, networkName, reviewedTarget, staging) { const verification = createVerificationCommands(); await attestStagedFiles(remote, staging.checkpoint); const config = parseHandoffOutput( (await runOrThrow(remote, verification.managementConfig, "Sidecar Compose ownership check")).stdout, "Sidecar Compose ownership check", 64 * 1024, ); const registrationId = attestVpsSiwcCompose(config, networkName); const expectedImageId = staging.checkpoint.imageId ?? staging.checkpoint.existingBinding?.imageId; if (!expectedImageId) throw new Error("The owned SIWC image was not recorded."); const imageId = await attestInstalledImage(remote, { imageId: expectedImageId }); const container = parseHandoffOutput( (await runOrThrow(remote, verification.ownerContainer, "Owned sidecar identity check")).stdout, "Owned sidecar identity check", 64 * 1024, ); const owner = attestVpsSiwcContainer(container, { service: config.services["openai-oauth"], networkName, networkId: reviewedTarget.networkId, imageId, }); if (staging.checkpoint.containerId && owner.containerId !== staging.checkpoint.containerId) { throw new Error("The installed SIWC container identity changed."); } return Object.freeze({ registrationId, ...owner, networkName, networkId: reviewedTarget.networkId }); } export async function reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget, allowStaged = false, }) { validateReviewedTarget(remote, reviewedTarget, networkName); await reattestReviewedTarget(remote, reviewedTarget); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "Owned SIWC context check"); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: allowStaged }); if (!staging || (!allowStaged && staging.checkpoint.stage !== "complete")) { throw new Error("The VPS SIWC installation is not finalized. Review resume instead."); } return attestOwnedSidecar(remote, networkName, reviewedTarget, staging); } // `targetAttested` is for a caller that attested this n8n target in the same request, such as the // status route. Every other caller holds an earlier review, which is attested again here. export async function getVpsSiwcInstallationStatus({ remote, networkName, reviewedTarget, targetAttested = false, }, { collectAssets = collectSiwcRuntimeAssets } = {}) { validateReviewedTarget(remote, reviewedTarget, networkName); if (targetAttested !== true) await reattestReviewedTarget(remote, reviewedTarget); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (staging && staging.checkpoint.stage !== "complete") { const { installId, registrationId, stage } = staging.checkpoint; return { state: staging.checkpoint.runtimeUpdate ? "updating" : "staged", staging: { installId, registrationId, stage } }; } const verification = createVerificationCommands(); assertSidecarOnlyCommands(Object.values(verification)); const state = await runOrThrow(remote, verification.legacyStatus, "VPS sidecar ownership check"); if (state.stdout.trim() === "legacy") return { state: "legacy" }; if (state.stdout.trim() === "partial") { return { state: "partial", migrationRequired: true, legacyResourcesPreserved: true }; } if (state.stdout.trim() === "absent") return { state: "absent" }; if (state.stdout.trim() !== "managed") { throw new Error("The VPS sidecar ownership state is invalid."); } // The target and the staging record were read above, so only the owner's own facts follow. if (!staging) throw new Error("The VPS SIWC installation is not finalized. Review resume instead."); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "Owned SIWC context check"); const owner = await attestOwnedSidecar(remote, networkName, reviewedTarget, staging); const { registrationId } = owner; if (!owner.running) return { state: "stopped", registrationId }; const account = await attestLiveAccount(remote, registrationId); let runtimeUpdateAvailable; try { runtimeUpdateAvailable = staging.checkpoint.imageSourceSha256 !== imageSourceDigest(managedRuntimeFiles(await collectAssets())); } catch { /* Without this version's runtime files the update state is unknown, not current. */ } return { state: "owned", registrationId, account, ...(runtimeUpdateAvailable === undefined ? {} : { runtimeUpdateAvailable }) }; } export async function reviewVpsSiwcRuntimeUpdate( { remote, networkName, reviewedTarget, registrationId }, { collectAssets = collectSiwcRuntimeAssets } = {}, ) { validateReviewedTarget(remote, reviewedTarget, networkName); validateSiwcRegistrationId(registrationId); await reattestReviewedTarget(remote, reviewedTarget); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); const checkpoint = staging?.checkpoint; const update = checkpoint?.runtimeUpdate; if (!checkpoint || checkpoint.registrationId !== registrationId || (!update && checkpoint.stage !== "complete")) { throw new Error("There is no matching installed SIWC sidecar to update."); } await attestStagedFiles(remote, checkpoint); let containerId = update?.fromContainerId; let imageId = checkpoint.imageId; if (update) { if (!validRuntimeUpdate(update) || !IMAGE_ID.test(imageId)) { throw new Error("The interrupted sidecar update record is invalid."); } } else { const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.registrationId !== registrationId) { throw new Error("The installed SIWC account differs from the reviewed selection."); } if (!owner.running) throw new Error("Turn plan use back on before updating."); await attestLiveAccount(remote, registrationId); ({ containerId, imageId } = owner); } const lock = await inspectResumeLock(remote, !!update); const files = managedRuntimeFiles(await collectAssets()); const imageSourceSha256 = imageSourceDigest(files); const changedFiles = [...files].filter(([path, contents]) => checkpoint.files?.[path] !== sha256(contents)) .map(([path]) => path.slice(INSTALL_ROOT.length + 1)).sort(); return Object.freeze({ installId: checkpoint.installId, registrationId, stage: checkpoint.stage, continuing: !!update, checkpointSha256: staging.checkpointSha256, containerId, imageId, changedFiles, imageSourceSha256, rebuildRequired: !!update || changedFiles.length > 0 || checkpoint.imageSourceSha256 !== imageSourceSha256, ...(lock ? { operationLockIdentity: lock } : {}), }); } function validRuntimeUpdate(update) { return DOCKER_ID.test(update?.fromContainerId) && IMAGE_ID.test(update.fromImageId) && (update.fromImageSourceSha256 === undefined || /^[a-f0-9]{64}$/u.test(update.fromImageSourceSha256)) && (update.builtImageIds === undefined || (Array.isArray(update.builtImageIds) && update.builtImageIds.length <= 8 && update.builtImageIds.every(id => IMAGE_ID.test(id)))); } export async function updateVpsSiwcRuntime( { remote, networkName, reviewedTarget, registrationId, review, confirmed }, { collectAssets = collectSiwcRuntimeAssets } = {}, ) { if (confirmed !== true || review?.rebuildRequired !== true || review.registrationId !== registrationId) { throw new Error("Review and confirm the sidecar update before applying it."); } validateSiwcRegistrationId(registrationId); validateReviewedTarget(remote, reviewedTarget, networkName); const verification = createVerificationCommands(); const deploymentCommands = createDeploymentCommands(); assertSidecarOnlyCommands([SIDECAR_MANAGED_CONTEXT_GUARD, SHARED_ROOT_TIGHTEN_COMMAND, ...deploymentCommands.slice(1), ...Object.values(verification)]); const files = managedRuntimeFiles(await collectAssets()); const imageSourceSha256 = imageSourceDigest(files); if (imageSourceSha256 !== review.imageSourceSha256) { throw new Error("This Relmio version's sidecar files differ from the review. Review the update again."); } const reviewChanged = "The installed sidecar changed after review. Review the update again."; return withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async build => { await reattestReviewedTarget(remote, reviewedTarget); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "Managed SIWC context check"); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (!staging || staging.checkpointSha256 !== review.checkpointSha256) throw new Error(reviewChanged); const { checkpoint } = staging; if (checkpoint.runtimeUpdate && !validRuntimeUpdate(checkpoint.runtimeUpdate)) { throw new Error("The interrupted sidecar update record is invalid."); } // Whatever an earlier attempt left on disk stays attestable if this attempt is interrupted. checkpoint.previousFiles = await attestStagedFiles(remote, checkpoint); const config = parseHandoffOutput((await runOrThrow(remote, verification.managementConfig, "Sidecar Compose ownership check")).stdout, "Sidecar Compose ownership check", 64 * 1024); if (attestVpsSiwcCompose(config, networkName) !== registrationId) throw new Error(reviewChanged); const service = config.services["openai-oauth"]; if (!checkpoint.runtimeUpdate) { const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (!owner.running || owner.registrationId !== registrationId || owner.containerId !== review.containerId || owner.imageId !== review.imageId) { throw new Error(reviewChanged); } checkpoint.runtimeUpdate = { fromImageId: owner.imageId, fromContainerId: owner.containerId, fromImageSourceSha256: checkpoint.imageSourceSha256 }; } checkpoint.files = { ...checkpoint.files, ...Object.fromEntries([...files].map(([path, contents]) => [path, sha256(contents)])) }; checkpoint.reviewedTarget = Object.fromEntries(TARGET_FIELDS.map(field => [field, reviewedTarget[field]])); // Confirmed above; the installed sidecar is the reviewed one. This is the update's first write. await tightenSharedFolder(remote); await publishCheckpoint(remote, checkpoint, "context"); const inspectContainer = async () => parseHandoffOutput((await runOrThrow(remote, verification.ownerContainer, "Previous sidecar identity check")).stdout, "Previous sidecar identity check", 64 * 1024); // Only the original writer or a container on an image this update built is recognized. const attestUpdateContainer = container => { const { fromImageId, fromContainerId, builtImageIds = [] } = checkpoint.runtimeUpdate; const original = container?.Image === fromImageId; if (!original && !builtImageIds.includes(container?.Image)) { throw new Error("The previous sidecar container changed. Review the update again."); } const attested = attestVpsSiwcContainer(container, { service, networkName, networkId: reviewedTarget.networkId, imageId: container.Image }); if (original && attested.containerId !== fromContainerId) { throw new Error("The previous sidecar container changed. Review the update again."); } return attested; }; await runOrThrow(remote, deploymentCommands[1], "SIWC storage directory creation"); await runOrThrow(remote, deploymentCommands[2], "Sidecar source directory creation"); await runOrThrow(remote, SIDECAR_MANAGED_CONTEXT_GUARD, "SIWC source-directory check"); for (const [path, contents] of files) await remote.publishManagedFile(path, contents, 0o644); await runOrThrow(remote, deploymentCommands[3], "Sidecar Compose validation"); // Always rebuild: a continuation never trusts an image it did not record. try { await build(deploymentCommands[4]); } catch (error) { if (error?.remoteOutcomeUnknown || error?.timedOut) throw error; const { fromImageId, fromContainerId, fromImageSourceSha256 } = checkpoint.runtimeUpdate; try { await attestInstalledImage(remote, { imageId: fromImageId }); const owner = attestVpsSiwcContainer(await inspectContainer(), { service, networkName, networkId: reviewedTarget.networkId, imageId: fromImageId }); if (!owner.running || owner.containerId !== fromContainerId) throw new Error(reviewChanged); } catch { throw error; } // The confirmed failure left the original image tag and writer running: record them as installed. const onDisk = await attestStagedFiles(remote, checkpoint); delete checkpoint.runtimeUpdate; Object.assign(checkpoint, { imageId: fromImageId, containerId: fromContainerId, files: onDisk }); if (fromImageSourceSha256 === undefined) delete checkpoint.imageSourceSha256; else checkpoint.imageSourceSha256 = fromImageSourceSha256; await publishCheckpoint(remote, checkpoint, "complete"); const message = "The sidecar image build failed. The previous sidecar keeps running; review the update again later."; throw Object.assign(new Error(message), { safeMessage: message, recovery: "review-again", runtimeState: "running", hostPublication: "none", }); } await build.releaseState(); const imageId = await attestInstalledImage(remote, {}); const { builtImageIds = [] } = checkpoint.runtimeUpdate; checkpoint.runtimeUpdate = { ...checkpoint.runtimeUpdate, builtImageIds: [...builtImageIds.filter(id => id !== imageId), imageId].slice(-8) }; Object.assign(checkpoint, { imageId, imageSourceSha256, containerId: undefined }); await publishCheckpoint(remote, checkpoint, "built"); if ((await runOrThrow(remote, verification.ownerPresence, "Previous sidecar presence check")).stdout.trim()) { const container = await inspectContainer(); // A container on the new image was started by this update after the built journal. if (container?.Image !== imageId) { const previous = attestUpdateContainer(container); if (previous.running) { await reattestReviewedTarget(remote, reviewedTarget); try { await remote.exec(verification.stop, { timeoutMs: 90_000 }); } catch { /* A lost stop acknowledgment is resolved by exact container inspection. */ } const stopped = attestUpdateContainer(await inspectContainer()); if (stopped.running || stopped.containerId !== previous.containerId) { throw new Error("The previous sidecar did not stop. The updated sidecar was not started."); } } } } let owner; let account; try { await runOrThrow(remote, deploymentCommands.at(-1), "Updated sidecar start"); const running = await runOrThrow(remote, verification.runningService, "Updated sidecar status"); if (!running.stdout.split(/\s+/u).includes("openai-oauth")) { throw new Error("The updated sidecar did not reach the running state."); } const publication = await runOrThrow(remote, verification.publicationState, "Sidecar publication check"); if (hasPublishedHostPort(publication.stdout)) throw new Error("Unexpected sidecar host publication."); owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget, allowStaged: true }); if (!owner.running || owner.imageId !== imageId || owner.registrationId !== registrationId) { throw new Error("The updated sidecar could not be attested."); } account = await attestLiveAccount(remote, registrationId); } catch (cause) { let stopped = await stopAttestedService(remote, reviewedTarget); if (!stopped) { // The start may have failed before recreation, leaving the original writer stopped. try { stopped = !attestUpdateContainer(await inspectContainer()).running; } catch { /* An unrecognized container is never declared stopped. */ } } throw Object.assign(new Error(stopped ? "The sidecar update could not be verified. The sidecar was stopped; review the update again to finish it." : "The sidecar update could not be verified and stopping could not be confirmed. Do not use the sidecar."), { safeMessage: stopped ? "The sidecar update could not be verified. The sidecar was stopped; review the update again." : "The sidecar update and stop outcomes are unknown. Inspect the sidecar before use.", recovery: "review-again", runtimeState: stopped ? "stopped" : "unknown", hostPublication: stopped ? "none" : "unknown", ...(stopped ? {} : { remoteOutcomeUnknown: true }), ...(cause?.timedOut ? { timedOut: true } : {}), }); } delete checkpoint.runtimeUpdate; checkpoint.containerId = owner.containerId; await publishCheckpoint(remote, checkpoint, "complete"); return { registrationId, imageId, containerId: owner.containerId, account, runtimeState: "running", keyChanged: false }; }, { resumeIdentity: review.operationLockIdentity }); } export async function inspectStoppedVpsSiwcInstallation({ remote, networkName, registrationId, reviewedTarget, confirmed, }) { if (confirmed !== true) throw new Error("Confirm inspection of the installed SIWC account."); validateSiwcRegistrationId(registrationId); validateReviewedTarget(remote, reviewedTarget, networkName); const verification = createVerificationCommands(); assertSidecarOnlyCommands([PRECHECK_COMMAND, ...Object.values(verification)]); const reviewedOwner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (reviewedOwner.registrationId !== registrationId || reviewedOwner.running) { throw new Error("The selected stopped SIWC owner changed."); } return withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async () => { const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.running || owner.containerId !== reviewedOwner.containerId || owner.imageId !== reviewedOwner.imageId || owner.registrationId !== registrationId) { throw new Error("The selected stopped SIWC owner changed."); } const account = parseHandoffOutput( (await runOrThrow(remote, verification.account, "Stopped SIWC account inspection")).stdout, "Stopped SIWC account inspection", ).account; if (account?.registrationId !== registrationId || account?.ownership !== "owned" || account?.ownerRuntimeId !== "vps_n8n") { throw new Error("The stopped SIWC account could not be attested."); } return { account }; }); } export async function manageVpsSiwcInstallation({ remote, networkName, registrationId, action, expectedGeneration, reviewedTarget, backgroundConsent, confirmed, }) { if (confirmed !== true || !["sign-out", "disable-plan", "enable-plan"].includes(action) || (action === "enable-plan" && backgroundConsent !== true)) { throw new Error("Confirm the selected installed SIWC account and background workflow consent."); } validateSiwcRegistrationId(registrationId); if (typeof expectedGeneration !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(expectedGeneration)) { throw new TypeError("The selected SIWC account generation is invalid."); } validateReviewedTarget(remote, reviewedTarget, networkName); const verification = createVerificationCommands(); const startCommand = createDeploymentCommands().at(-1); assertSidecarOnlyCommands([PRECHECK_COMMAND, SHARED_ROOT_TIGHTEN_COMMAND, startCommand, ...Object.values(verification)]); const reviewedOwner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (reviewedOwner.registrationId !== registrationId) { throw new Error("The installed SIWC account differs from the reviewed selection."); } return withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async () => { const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.containerId !== reviewedOwner.containerId || owner.imageId !== reviewedOwner.imageId || owner.registrationId !== registrationId) { throw new Error("The reviewed installed SIWC owner changed."); } const before = parseHandoffOutput( (await runOrThrow(remote, verification.account, "Destination SIWC account check")).stdout, "Destination SIWC account check", ).account; if (before?.registrationId !== registrationId || before?.generation !== expectedGeneration || before?.ownership !== "owned") { throw new Error("The installed SIWC account changed; review it again."); } if (action === "enable-plan" && (before.planPermission !== "granted" || before.session !== "connected")) { throw new Error("This installed account needs a fresh authorized SIWC sign-in before plan use can resume."); } await reattestReviewedTarget(remote, reviewedTarget); // Confirmed above; the owner and its account are the reviewed ones. The change starts here. await tightenSharedFolder(remote); if (action === "sign-out") { // Best effort while the sidecar still runs: a later sign-in on this store must not inherit the image // sign-in or model checks. A sidecar from before an add-on lacks its module, so every outcome is ignored. await remote.exec(verification.imagesSignOut, { timeoutMs: 90_000 }).catch(() => {}); await remote.exec(verification.modelChecksOff, { timeoutMs: 90_000 }).catch(() => {}); } await runOrThrow(remote, verification.stop, "Owned sidecar stop"); const running = await runOrThrow(remote, verification.runningService, "Owned sidecar stopped check"); if (running.stdout.split(/\s+/u).includes("openai-oauth")) { throw new Error("The owned sidecar did not stop; account mutation was not attempted."); } const stoppedOwner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (stoppedOwner.running || stoppedOwner.containerId !== owner.containerId || stoppedOwner.imageId !== owner.imageId || stoppedOwner.registrationId !== registrationId) { throw new Error("The stopped SIWC owner changed. Account mutation was not attempted."); } const command = action === "sign-out" ? verification.signOut : action === "enable-plan" ? verification.enablePlan : verification.disablePlan; const input = JSON.stringify({ registrationId, expectedGeneration, ...(action === "enable-plan" ? { backgroundConsent: true } : {}), }); const changed = parseHandoffOutput( (await runOrThrow(remote, command, "Destination SIWC account operation", input)).stdout, "Destination SIWC account operation", ); if (changed.account?.registrationId !== registrationId || changed.account?.ownerHostId !== before.ownerHostId || changed.account?.ownerRuntimeId !== "vps_n8n" || changed.account?.ownership !== "owned") { throw new Error("The destination SIWC account result could not be attested."); } if (action === "enable-plan") { if (changed.account.planEnabled !== true || changed.account.planPermission !== "granted") { throw new Error("The destination SIWC grant could not be confirmed. The service remains stopped."); } try { await runOrThrow(remote, startCommand, "Owned SIWC sidecar start"); const started = await runOrThrow(remote, verification.runningService, "Owned SIWC sidecar status"); if (!started.stdout.split(/\s+/u).includes("openai-oauth")) { throw new Error("The owned sidecar did not reach the running state."); } const publication = await runOrThrow(remote, verification.publicationState, "Sidecar publication check"); if (hasPublishedHostPort(publication.stdout)) throw new Error("Unexpected sidecar host publication."); const live = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (live.containerId !== owner.containerId || live.imageId !== owner.imageId || !live.running) { throw new Error("The enabled SIWC owner changed."); } } catch (cause) { const stopped = await stopAttestedService(remote, reviewedTarget, owner); throw Object.assign(new Error(stopped ? "SIWC enable verification failed. The attested service was stopped; review before enabling again." : "SIWC enable verification failed and stopping could not be confirmed. Do not use the service."), { safeMessage: stopped ? "SIWC enable verification failed. The attested service was stopped." : "SIWC enable and stop outcomes are unknown. Inspect the owned service before use.", recovery: "resolve-handoff", runtimeState: stopped ? "stopped" : "unknown", hostPublication: stopped ? "none" : "unknown", ...(stopped ? {} : { remoteOutcomeUnknown: true }), ...(cause?.timedOut ? { timedOut: true } : {}), }); } } return { account: changed.account, revocation: changed.revocation, runtimeStopped: action !== "enable-plan", }; }); } const CODEX_IMAGES_COMMANDS = Object.freeze({ "login-start": "imagesLoginStart", "login-poll": "imagesLoginPoll", "login-cancel": "imagesLoginCancel", "sign-out": "imagesSignOut", }); const CODEX_IMAGES_CHANGED = "The installed sidecar changed. Check image sign-in status again."; async function runCodexImagesCommand(remote, command, options) { return readCodexImagesCliResult(await remote.exec(command, { timeoutMs: 90_000 }), options); } export async function getVpsCodexImagesStatus({ remote, networkName, reviewedTarget, registrationId }) { validateSiwcRegistrationId(registrationId); const verification = createVerificationCommands(); assertSidecarOnlyCommands(Object.values(verification)); const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.registrationId !== registrationId || !owner.running) { throw new Error("The installed SIWC sidecar is not running for the reviewed account."); } return { ...await runCodexImagesCommand(remote, verification.imagesStatus, { allowUnavailable: true }), containerId: owner.containerId }; } export async function changeVpsCodexImages({ remote, networkName, reviewedTarget, registrationId, action, expectedContainerId, confirmed, }) { if (!Object.hasOwn(CODEX_IMAGES_COMMANDS, action)) throw new TypeError("The image sign-in action is invalid."); const fullReview = action === "login-start" || action === "sign-out"; if (fullReview && confirmed !== true) throw new Error("Confirm the Codex image sign-in change for this server."); validateSiwcRegistrationId(registrationId); if (!DOCKER_ID.test(expectedContainerId)) throw new TypeError("The reviewed sidecar container is invalid."); validateReviewedTarget(remote, reviewedTarget, networkName); const verification = createVerificationCommands(); const command = verification[CODEX_IMAGES_COMMANDS[action]]; assertSidecarOnlyCommands([SIDECAR_MANAGED_CONTEXT_GUARD, SHARED_ROOT_TIGHTEN_COMMAND, command, ...Object.values(verification)]); return withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async () => { if (fullReview) { const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.registrationId !== registrationId || !owner.running || owner.containerId !== expectedContainerId) throw new Error(CODEX_IMAGES_CHANGED); // Only sign-in start and sign-out reach here, and both checked the final confirmation above. await tightenSharedFolder(remote); } else { // Polling stays light: the exact running container of the attested owner image. await reattestReviewedTarget(remote, reviewedTarget); const checkpoint = (await readStaging(remote, reviewedTarget))?.checkpoint; const imageId = checkpoint?.imageId ?? checkpoint?.existingBinding?.imageId; const container = parseHandoffOutput( (await runOrThrow(remote, verification.ownerContainer, "Owned sidecar identity check")).stdout, "Owned sidecar identity check", 64 * 1024, ); if (checkpoint?.stage !== "complete" || checkpoint.registrationId !== registrationId || !IMAGE_ID.test(imageId) || container?.Id !== expectedContainerId || container.State?.Running !== true || container.Image !== imageId) throw new Error(CODEX_IMAGES_CHANGED); } return { ...await runCodexImagesCommand(remote, command), containerId: expectedContainerId }; }); } const MODEL_STATES = new Set(["verified", "failed", "unchecked"]); const MODEL_SLUG = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u; const CATALOG_ERRORS = new Set(["catalog_unavailable", "registration_unavailable"]); const STOPPED_REASONS = new Set(["usage_limit", "reauthorize", "probe_rejected", "checks_off", "time_limit", "lease_unavailable"]); const MODEL_DISCOVERY_FAILED = "The model list step failed. Check models again."; // Local text for the CLI's error codes; the remote message itself never leaves here. const MODEL_DISCOVERY_ERRORS = new Map([ ["invalid_command", "The installed sidecar doesn't support this model command. Update the sidecar, then try again."], ["invalid_configuration", "The installed sidecar's configuration is invalid. Review the installed sidecar."], ["store_unsafe", "The sidecar's model check record is unsafe. An administrator must inspect it."], ["store_busy", "The sidecar's model check record is busy. Try again in a minute."], ["discovery_failed", "Model discovery failed on the sidecar. Try again."], ]); const isoTime = value => typeof value === "string" && !Number.isNaN(Date.parse(value)) && new Date(value).toISOString() === value; const runCount = value => Number.isInteger(value) && value >= 0 && value <= 64; const invalidModelStatus = () => new Error("Model discovery returned invalid attestation."); function copyModelRun(run) { if (!runCount(run?.checked) || !runCount(run.verified) || !runCount(run.failed) || (run.stoppedReason !== undefined && !STOPPED_REASONS.has(run.stoppedReason))) throw invalidModelStatus(); const { checked, verified, failed, stoppedReason } = run; return { checked, verified, failed, ...(stoppedReason === undefined ? {} : { stoppedReason }) }; } function copyModelRow(model) { const { id, display_name, state, listed, checkedAt } = model ?? {}; if (typeof id !== "string" || !MODEL_SLUG.test(id) || typeof display_name !== "string" || display_name.length > 256 || !MODEL_STATES.has(state) || typeof listed !== "boolean" || (checkedAt !== undefined && !isoTime(checkedAt))) throw invalidModelStatus(); return { id, display_name, state, listed, ...(checkedAt === undefined ? {} : { checkedAt }) }; } // Only the status view's known fields leave here. function copyModelStatus(output) { const { checksEnabled, clientVersion, catalogCheckedAt, catalogError, lastRun, models } = output ?? {}; if (typeof checksEnabled !== "boolean" || typeof clientVersion !== "string" || !/^\d{1,4}\.\d{1,4}\.\d{1,4}$/u.test(clientVersion) || (catalogCheckedAt !== null && !isoTime(catalogCheckedAt)) || (catalogError !== undefined && !CATALOG_ERRORS.has(catalogError)) || !Array.isArray(models) || models.length > 64) throw invalidModelStatus(); return { state: "available", checksEnabled, clientVersion, catalogCheckedAt, ...(catalogError === undefined ? {} : { catalogError }), ...(lastRun === undefined ? {} : { lastRun: copyModelRun(lastRun) }), models: models.map(model => copyModelRow(model)), }; } // The model CLI prints one JSON line; stderr and remote messages never leave here. async function runModelDiscoveryCommand(remote, command, { timeoutMs, allowUnavailable = false }) { const result = await remote.exec(command, { timeoutMs }); let output; try { output = parseHandoffOutput(result.stdout, "Model discovery", 128 * 1024); } catch { /* Classified below by exit code. */ } if (result.code === 0) return copyModelStatus(output); if (typeof output?.error === "string" && /^[a-z_]{1,64}$/u.test(output.error)) { throw Object.assign(new Error(MODEL_DISCOVERY_ERRORS.get(output.error) ?? MODEL_DISCOVERY_FAILED), { code: output.error }); } // A sidecar built before model discovery has no module to run. if (allowUnavailable) return { state: "unavailable" }; throw new Error(MODEL_DISCOVERY_FAILED); } export async function getVpsModelDiscovery({ remote, networkName, reviewedTarget, registrationId }) { validateSiwcRegistrationId(registrationId); const verification = createVerificationCommands(); assertSidecarOnlyCommands(Object.values(verification)); const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.registrationId !== registrationId || !owner.running) { throw new Error("The installed SIWC sidecar is not running for the reviewed account."); } return { ...await runModelDiscoveryCommand(remote, verification.modelsDiscoveryStatus, { timeoutMs: 90_000, allowUnavailable: true }), containerId: owner.containerId }; } // Read-only: the running sidecar's stored request counts, as usageRecordFromCli reads them. export async function getVpsUsageStatus({ remote, networkName, reviewedTarget, registrationId }) { validateSiwcRegistrationId(registrationId); const verification = createVerificationCommands(); assertSidecarOnlyCommands(Object.values(verification)); const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.registrationId !== registrationId || !owner.running) { throw new Error("The installed SIWC sidecar is not running for the reviewed account."); } return usageRecordFromCli(await remote.exec(verification.usageStatus, { timeoutMs: 90_000 }), registrationId); } export async function changeVpsModelChecks({ remote, networkName, reviewedTarget, registrationId, enabled, expectedContainerId, confirmed, }) { if (confirmed !== true) throw new Error("Confirm the model check change for this server."); if (typeof enabled !== "boolean") throw new TypeError("The model check setting is invalid."); validateSiwcRegistrationId(registrationId); if (!DOCKER_ID.test(expectedContainerId)) throw new TypeError("The reviewed sidecar container is invalid."); validateReviewedTarget(remote, reviewedTarget, networkName); const verification = createVerificationCommands(); const command = enabled ? verification.modelChecksOn : verification.modelChecksOff; assertSidecarOnlyCommands([SIDECAR_MANAGED_CONTEXT_GUARD, SHARED_ROOT_TIGHTEN_COMMAND, ...Object.values(verification)]); return withVpsOperationLock(remote, VPS_OPERATION_LOCKS.oauth, async () => { const owner = await reviewVpsSiwcReplacement({ remote, networkName, reviewedTarget }); if (owner.registrationId !== registrationId || !owner.running || owner.containerId !== expectedContainerId) throw new Error("The installed sidecar changed. Check models again."); // Confirmed above; the running sidecar is the reviewed one. await tightenSharedFolder(remote); // checks-on probes up to 12 models within the CLI's own 180 s deadline; the SSH deadline outlasts it. return { ...await runModelDiscoveryCommand(remote, command, { timeoutMs: enabled ? 360_000 : 90_000 }), containerId: expectedContainerId }; }); } async function stopAttestedService(remote, reviewedTarget, expectedOwner) { const verification = createVerificationCommands(); try { await reattestReviewedTarget(remote, reviewedTarget); const presence = await runOrThrow(remote, verification.ownerPresence, "Owned service presence check"); if (!presence.stdout.trim()) return !expectedOwner; const owner = await reviewVpsSiwcReplacement({ remote, networkName: reviewedTarget.networkName, reviewedTarget, allowStaged: true, }); if (expectedOwner && (owner.containerId !== expectedOwner.containerId || owner.imageId !== expectedOwner.imageId)) return false; try { await remote.exec(verification.stop, { timeoutMs: 90_000 }); } catch { /* A lost stop acknowledgment is resolved by exact container inspection. */ } const stopped = await reviewVpsSiwcReplacement({ remote, networkName: reviewedTarget.networkName, reviewedTarget, allowStaged: true, }); return !stopped.running && stopped.containerId === owner.containerId && stopped.imageId === owner.imageId; } catch { if (!expectedOwner) { try { await reattestReviewedTarget(remote, reviewedTarget); const staging = await readStaging(remote, reviewedTarget, { allowTargetRefresh: true }); if (staging?.checkpoint.legacyBinding || staging?.checkpoint.existingBinding) { await attestStoppedPreviousContainer(remote, staging.checkpoint); return true; } } catch { /* An unrecognized container is never stopped or declared safe. */ } } return false; } }