import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as Option from "effect/Option"; import * as Semaphore from "effect/Semaphore"; import * as ProcessRunner from "../processRunner.ts"; /** * A pinned runtime is an exact `t3@` npm-installed into * /runtime/versions/. The boot service points its unit or * launch agent here, and server self-update installs the target version here before * switching over, never `npx t3`, whose cache is ephemeral and whose * registry fetch at boot would make startup depend on the network. */ const PINNED_RUNTIME_DIR = "runtime"; const PINNED_RUNTIME_INSTALL_TIMEOUT = Duration.minutes(10); // Boot-service setup and remote update can construct separate layers. Serialize // the complete install transaction across every caller in this process. const pinnedRuntimeInstallLock = Semaphore.makeUnsafe(1); export interface PinnedRuntimePaths { readonly versionDir: string; readonly entryPath: string; readonly sentinelPath: string; } export function pinnedRuntimePaths( path: Path.Path, baseDir: string, version: string, ): PinnedRuntimePaths { const versionDir = path.join(baseDir, PINNED_RUNTIME_DIR, "versions", version); return { versionDir, entryPath: path.join(versionDir, "node_modules", "t3", "dist", "bin.mjs"), sentinelPath: path.join(versionDir, ".install-complete"), }; } export class PinnedRuntimeInstallError extends Schema.TaggedErrorClass()( "PinnedRuntimeInstallError", { step: Schema.String, exitCode: Schema.optional(Schema.Number), stdoutLength: Schema.optional(Schema.Number), stderrLength: Schema.optional(Schema.Number), cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { return this.exitCode === undefined ? `Pinned runtime install failed while ${this.step}.` : `Pinned runtime install failed while ${this.step} (exit code ${this.exitCode}).`; } } export class PinnedRuntimePreflightBlockedError extends Schema.TaggedErrorClass()( "PinnedRuntimePreflightBlockedError", { version: Schema.String, reason: Schema.String, }, ) { override get message(): string { return this.reason; } } /** * Installs `t3@` into the pinned runtime directory unless a complete * install is already there, and returns its paths. The sentinel is written * only after npm exits 0; checking the entry file alone is not enough. npm * extracts files before running native builds (node-pty), so a killed * install leaves a plausible-looking but broken tree behind. */ interface PinnedRuntimeInstallInput { readonly baseDir: string; readonly version: string; readonly fs: FileSystem.FileSystem; readonly path: Path.Path; readonly runner: ProcessRunner.ProcessRunner["Service"]; readonly validate: ( paths: PinnedRuntimePaths, ) => Effect.Effect; } const installPinnedRuntime = Effect.fn("cloud.pinned_runtime.ensure_installed")(function* ( input: PinnedRuntimeInstallInput, ) { const { fs, runner } = input; const paths = pinnedRuntimePaths(input.path, input.baseDir, input.version); const [versionDirExists, entryExists, sentinel] = yield* Effect.all([ fs.exists(paths.versionDir), fs.exists(paths.entryPath), fs.readFileString(paths.sentinelPath).pipe(Effect.option), ]).pipe( Effect.mapError( (cause) => new PinnedRuntimeInstallError({ step: "checking the pinned runtime", cause }), ), ); const alreadyPinned = entryExists && Option.isSome(sentinel) && sentinel.value.trim() === input.version; if (alreadyPinned) { yield* input.validate(paths); return paths; } if (versionDirExists) { yield* fs.remove(paths.versionDir, { recursive: true, force: true }).pipe( Effect.mapError( (cause) => new PinnedRuntimeInstallError({ step: "removing an incomplete pinned runtime", cause, }), ), ); } const versionsDir = input.path.dirname(paths.versionDir); yield* fs.makeDirectory(versionsDir, { recursive: true }).pipe( Effect.mapError( (cause) => new PinnedRuntimeInstallError({ step: "preparing the pinned runtime directory", cause, }), ), ); const stagingDir = yield* fs .makeTempDirectory({ directory: versionsDir, prefix: ".staging-", }) .pipe( Effect.mapError( (cause) => new PinnedRuntimeInstallError({ step: "preparing the pinned runtime directory", cause, }), ), ); const stagingPaths: PinnedRuntimePaths = { versionDir: stagingDir, entryPath: input.path.join(stagingDir, "node_modules", "t3", "dist", "bin.mjs"), sentinelPath: input.path.join(stagingDir, ".install-complete"), }; return yield* Effect.gen(function* () { const installStep = "installing the pinned t3 runtime (this can take a few minutes)"; yield* runner .run({ command: "npm", args: ["install", "--prefix", stagingDir, "--no-fund", "--no-audit", `t3@${input.version}`], // Native dependencies may compile from source on slower machines. timeout: PINNED_RUNTIME_INSTALL_TIMEOUT, }) .pipe( Effect.mapError((cause) => new PinnedRuntimeInstallError({ step: installStep, cause })), Effect.filterOrFail( (result) => result.code === 0, (result) => new PinnedRuntimeInstallError({ step: installStep, exitCode: Number(result.code), stdoutLength: result.stdout.length, stderrLength: result.stderr.length, }), ), ); yield* input.validate(stagingPaths); yield* fs .writeFileString(stagingPaths.sentinelPath, `${input.version}\n`) .pipe( Effect.mapError( (cause) => new PinnedRuntimeInstallError({ step: "recording the completed install", cause }), ), ); const published = yield* fs.rename(stagingDir, paths.versionDir).pipe( Effect.as(true), Effect.catch((cause) => Effect.all([ fs.exists(paths.entryPath), fs.readFileString(paths.sentinelPath).pipe(Effect.option), ]).pipe( Effect.mapError( (checkCause) => new PinnedRuntimeInstallError({ step: "checking a concurrently published pinned runtime", cause: checkCause, }), ), Effect.flatMap(([publishedEntryExists, publishedSentinel]) => publishedEntryExists && Option.isSome(publishedSentinel) && publishedSentinel.value.trim() === input.version ? Effect.succeed(false) : Effect.fail( new PinnedRuntimeInstallError({ step: "publishing the pinned runtime", cause, }), ), ), ), ), ); if (!published) yield* input.validate(paths); return paths; }).pipe( Effect.ensuring(fs.remove(stagingDir, { recursive: true, force: true }).pipe(Effect.ignore)), ); }); export const ensurePinnedRuntimeInstalled = (input: PinnedRuntimeInstallInput) => pinnedRuntimeInstallLock.withPermit(installPinnedRuntime(input));