281 lines · 9.6 KB · TypeScript
RawDownload
import type { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay";import * as RelayClient from "@t3tools/shared/relayClient";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Ref from "effect/Ref";import * as Result from "effect/Result";import * as Semaphore from "effect/Semaphore";import * as Scope from "effect/Scope";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts";import { CLOUD_ENDPOINT_RUNTIME_CONFIG, decodeRuntimeConfig } from "./config.ts"; function bytesToString(bytes: Uint8Array): string {  return new TextDecoder().decode(bytes);} const readRuntimeConfig = Effect.gen(function* () {  const secrets = yield* ServerSecretStore.ServerSecretStore;  const bytes = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG);  if (!bytes) {    return null;  }  return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes)));}); export interface CloudManagedEndpointRuntimeShape {  readonly applyConfig: (    config: RelayManagedEndpointRuntimeConfig | null,  ) => Effect.Effect<CloudManagedEndpointRuntimeStatus>;} export class CloudManagedEndpointRuntime extends Context.Service<  CloudManagedEndpointRuntime,  CloudManagedEndpointRuntimeShape>()("t3/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {} export type CloudManagedEndpointRuntimeStatus =  | {      readonly status: "disabled";    }  | {      readonly status: "failed";      readonly providerKind: RelayManagedEndpointRuntimeConfig["providerKind"];      readonly reason: string;      readonly tunnelId?: string;      readonly tunnelName?: string;    }  | {      readonly status: "running";      readonly providerKind: "cloudflare_tunnel";      readonly pid: number;      readonly tunnelId?: string;      readonly tunnelName?: string;    }  | {      readonly status: "unsupported";      readonly providerKind: RelayManagedEndpointRuntimeConfig["providerKind"];    }; interface ActiveConnector {  readonly child: ChildProcessSpawner.ChildProcessHandle;  readonly scope: Scope.Closeable;  readonly configKey: string;  readonly config: RelayManagedEndpointRuntimeConfig;} function runtimeConfigKey(config: RelayManagedEndpointRuntimeConfig): string {  return JSON.stringify({    providerKind: config.providerKind,    connectorToken: config.connectorToken,    tunnelId: config.tunnelId ?? null,    tunnelName: config.tunnelName ?? null,  });} const stopConnector = (connector: ActiveConnector | null) =>  connector    ? Scope.close(connector.scope, Exit.void).pipe(        Effect.tap(() =>          Effect.logInfo("Relay client stopped", {            pid: Number(connector.child.pid),          }),        ),        Effect.ignore,      )    : Effect.void; export const makeCloudManagedEndpointRuntime = Effect.gen(function* () {  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;  const relayClient = yield* RelayClient.RelayClient;  const activeRef = yield* Ref.make<ActiveConnector | null>(null);  const desiredConfigRef = yield* Ref.make<RelayManagedEndpointRuntimeConfig | null>(null);  const reconcileSemaphore = yield* Semaphore.make(1);  let reconcileConfig: CloudManagedEndpointRuntimeShape["applyConfig"];   const stopActive = Effect.gen(function* () {    const active = yield* Ref.getAndSet(activeRef, null);    yield* stopConnector(active);  });   const superviseConnector = (connector: ActiveConnector) =>    Effect.gen(function* () {      const result = yield* Effect.result(connector.child.exitCode);      yield* reconcileSemaphore.withPermits(1)(        Effect.gen(function* () {          const active = yield* Ref.get(activeRef);          if (            active?.child.pid !== connector.child.pid ||            active.configKey !== connector.configKey          ) {            return;          }          yield* Ref.set(activeRef, null);          yield* stopConnector(connector);           const desiredConfig = yield* Ref.get(desiredConfigRef);          if (            !desiredConfig ||            desiredConfig.providerKind !== "cloudflare_tunnel" ||            runtimeConfigKey(desiredConfig) !== connector.configKey          ) {            return;          }           yield* Effect.logWarning("Relay client exited; restarting", {            pid: Number(connector.child.pid),            ...(Result.isSuccess(result)              ? { exitCode: Number(result.success) }              : { cause: result.failure }),            tunnelId: connector.config.tunnelId,            tunnelName: connector.config.tunnelName,          });          yield* reconcileConfig(desiredConfig);        }),      );    }).pipe(      Effect.catchCause((cause) => Effect.logWarning("Relay client supervisor failed", { cause })),    );   reconcileConfig = Effect.fn("CloudManagedEndpointRuntime.reconcileConfig")(function* (config) {    if (!config || config.providerKind !== "cloudflare_tunnel") {      yield* stopActive;      return config        ? { status: "unsupported", providerKind: config.providerKind }        : { status: "disabled" };    }     const nextConfigKey = runtimeConfigKey(config);    const active = yield* Ref.get(activeRef);    if (active?.configKey === nextConfigKey) {      const isRunning = yield* active.child.isRunning.pipe(Effect.orElseSucceed(() => false));      if (isRunning) {        return {          status: "running",          providerKind: "cloudflare_tunnel",          pid: Number(active.child.pid),          ...(active.config.tunnelId ? { tunnelId: active.config.tunnelId } : {}),          ...(active.config.tunnelName ? { tunnelName: active.config.tunnelName } : {}),        } satisfies CloudManagedEndpointRuntimeStatus;      }    }     yield* stopActive;     const executable = yield* relayClient.resolve;    if (executable.status !== "available") {      return {        status: "failed",        providerKind: "cloudflare_tunnel",        reason:          executable.status === "unsupported"            ? `Relay client is unsupported on ${executable.platform}-${executable.arch}.`            : "The relay client is not installed.",        ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),        ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),      } satisfies CloudManagedEndpointRuntimeStatus;    }     const connectorScope = yield* Scope.make("sequential");    const child = yield* spawner      .spawn(        ChildProcess.make(executable.executablePath, ["tunnel", "run"], {          detached: false,          env: {            ...process.env,            TUNNEL_TOKEN: config.connectorToken,          },          shell: false,          stderr: "ignore",          stdout: "ignore",        }),      )      .pipe(        Effect.provideService(Scope.Scope, connectorScope),        Effect.tap(() =>          Effect.logInfo("Relay client started", {            tunnelId: config.tunnelId,            tunnelName: config.tunnelName,          }),        ),        Effect.catch((cause) =>          Effect.logWarning("Failed to start relay client", {            cause,            tunnelId: config.tunnelId,            tunnelName: config.tunnelName,          }).pipe(            Effect.andThen(Scope.close(connectorScope, Exit.void).pipe(Effect.ignore)),            Effect.as({              status: "failed",              providerKind: "cloudflare_tunnel",              reason: String(cause),              ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),              ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),            } satisfies CloudManagedEndpointRuntimeStatus),          ),        ),      );     if ("status" in child && child.status === "failed") {      return child;    }     if (!("status" in child)) {      const connector = {        child,        scope: connectorScope,        configKey: nextConfigKey,        config,      } satisfies ActiveConnector;      yield* Ref.set(activeRef, connector);      yield* Effect.forkIn(superviseConnector(connector), connectorScope);      return {        status: "running",        providerKind: "cloudflare_tunnel",        pid: Number(child.pid),        ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),        ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),      } satisfies CloudManagedEndpointRuntimeStatus;    }     return {      status: "failed",      providerKind: "cloudflare_tunnel",      reason: "Relay client did not start.",      ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}),      ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}),    } satisfies CloudManagedEndpointRuntimeStatus;  });   const applyConfig = Effect.fn("CloudManagedEndpointRuntime.applyConfig")(    (config: RelayManagedEndpointRuntimeConfig | null) =>      reconcileSemaphore.withPermits(1)(        Ref.set(desiredConfigRef, config).pipe(Effect.andThen(reconcileConfig(config))),      ),  );   return CloudManagedEndpointRuntime.of({    applyConfig,  });}); export const layer = Layer.effect(  CloudManagedEndpointRuntime,  Effect.gen(function* () {    const runtime = yield* makeCloudManagedEndpointRuntime;    const initialConfig = yield* readRuntimeConfig.pipe(      Effect.catch((cause) =>        Effect.logWarning("Failed to read managed endpoint runtime config", { cause }).pipe(          Effect.as(null),        ),      ),    );    yield* runtime.applyConfig(initialConfig);    yield* Effect.addFinalizer(() => runtime.applyConfig(null));    return runtime;  }),);