import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings"; import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import * as Encoding from "effect/Encoding"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; import * as Ref from "effect/Ref"; import * as DesktopBackendManager from "./DesktopBackendManager.ts"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; import * as DesktopObservability from "../app/DesktopObservability.ts"; import * as DesktopServerExposure from "./DesktopServerExposure.ts"; export interface DesktopBackendConfigurationShape { readonly resolve: Effect.Effect< DesktopBackendManager.DesktopBackendStartConfig, PlatformError.PlatformError >; } export class DesktopBackendConfiguration extends Context.Service< DesktopBackendConfiguration, DesktopBackendConfigurationShape >()("@t3tools/desktop/backend/DesktopBackendConfiguration") {} interface BackendObservabilitySettings { readonly otlpTracesUrl: Option.Option; readonly otlpMetricsUrl: Option.Option; } const emptyBackendObservabilitySettings: BackendObservabilitySettings = { otlpTracesUrl: Option.none(), otlpMetricsUrl: Option.none(), }; const DESKTOP_BACKEND_ENV_NAMES = [ "T3CODE_PORT", "T3CODE_MODE", "T3CODE_NO_BROWSER", "T3CODE_HOST", "T3CODE_DESKTOP_WS_URL", "T3CODE_DESKTOP_LAN_ACCESS", "T3CODE_DESKTOP_LAN_HOST", "T3CODE_DESKTOP_HTTPS_ENDPOINTS", "T3CODE_TAILSCALE_SERVE", "T3CODE_TAILSCALE_SERVE_PORT", ] as const; const backendChildEnvPatch = (): Record => Object.fromEntries(DESKTOP_BACKEND_ENV_NAMES.map((name) => [name, undefined])); const { logWarning: logBackendConfigurationWarning } = DesktopObservability.makeComponentLogger( "desktop-backend-configuration", ); const readPersistedBackendObservabilitySettings: Effect.Effect< BackendObservabilitySettings, never, FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment > = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const environment = yield* DesktopEnvironment.DesktopEnvironment; const exists = yield* fileSystem .exists(environment.serverSettingsPath) .pipe(Effect.orElseSucceed(() => false)); if (!exists) { return emptyBackendObservabilitySettings; } const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe(Effect.option); if (Option.isNone(raw)) { yield* logBackendConfigurationWarning( "failed to read persisted backend observability settings", ); return emptyBackendObservabilitySettings; } const parsed = parsePersistedServerObservabilitySettings(raw.value); return { otlpTracesUrl: Option.fromNullishOr(parsed.otlpTracesUrl), otlpMetricsUrl: Option.fromNullishOr(parsed.otlpMetricsUrl), }; }); const resolveBackendStartConfig = Effect.fn("desktop.backendConfiguration.resolveStartConfig")( function* (input: { readonly bootstrapToken: string; readonly observabilitySettings: BackendObservabilitySettings; }): Effect.fn.Return< DesktopBackendManager.DesktopBackendStartConfig, never, DesktopEnvironment.DesktopEnvironment | DesktopServerExposure.DesktopServerExposure > { const environment = yield* DesktopEnvironment.DesktopEnvironment; const serverExposure = yield* DesktopServerExposure.DesktopServerExposure; const backendExposure = yield* serverExposure.backendConfig; return { executablePath: process.execPath, entryPath: environment.backendEntryPath, cwd: environment.backendCwd, env: { ...backendChildEnvPatch(), ELECTRON_RUN_AS_NODE: "1", }, bootstrap: { mode: "desktop", noBrowser: true, port: backendExposure.port, t3Home: environment.baseDir, host: backendExposure.bindHost, desktopBootstrapToken: input.bootstrapToken, tailscaleServeEnabled: backendExposure.tailscaleServeEnabled, tailscaleServePort: backendExposure.tailscaleServePort, ...Option.match(input.observabilitySettings.otlpTracesUrl, { onNone: () => ({}), onSome: (otlpTracesUrl) => ({ otlpTracesUrl }), }), ...Option.match(input.observabilitySettings.otlpMetricsUrl, { onNone: () => ({}), onSome: (otlpMetricsUrl) => ({ otlpMetricsUrl }), }), }, httpBaseUrl: backendExposure.httpBaseUrl, captureOutput: true, }; }, ); export const layer = Layer.effect( DesktopBackendConfiguration, Effect.gen(function* () { const environment = yield* DesktopEnvironment.DesktopEnvironment; const fileSystem = yield* FileSystem.FileSystem; const serverExposure = yield* DesktopServerExposure.DesktopServerExposure; const crypto = yield* Crypto.Crypto; const tokenRef = yield* Ref.make(Option.none()); const getOrCreateBootstrapToken = Effect.gen(function* () { const existing = yield* Ref.get(tokenRef); if (Option.isSome(existing)) { return existing.value; } const token = Encoding.encodeHex(yield* crypto.randomBytes(24)); yield* Ref.set(tokenRef, Option.some(token)); return token; }); return DesktopBackendConfiguration.of({ resolve: Effect.gen(function* () { const bootstrapToken = yield* getOrCreateBootstrapToken; const observabilitySettings = yield* readPersistedBackendObservabilitySettings.pipe( Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment), ); return yield* resolveBackendStartConfig({ bootstrapToken, observabilitySettings, }).pipe( Effect.provideService(DesktopEnvironment.DesktopEnvironment, environment), Effect.provideService(DesktopServerExposure.DesktopServerExposure, serverExposure), ); }).pipe(Effect.withSpan("desktop.backendConfiguration.resolve")), }); }), );