| author | |
| committer | |
| log | 64bf0161919ec0e41470af3304928d9fc8711bf4 |
| tree | 98caf7f24875e42ea5cfa05f85d07878de1c2a36 |
| parent | 5192f777fe54c2a2a359f6c25ecf5fbde46d49b0 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude <noreply@anthropic.com>2 files changed, 22 insertions(+), 15 deletions(-)
apps/server/src/cloud/bootService.test.ts+17-2| ... | @@ -33,6 +33,7 @@ it("keeps systemd pinned to the stable launcher rather than a versioned server", | ... | @@ -33,6 +33,7 @@ it("keeps systemd pinned to the stable launcher rather than a versioned server", |
| 33 | 33 | ||
| 34 | const makeHarness = Effect.fn("test.make_boot_service_harness")(function* ( | 34 | const makeHarness = Effect.fn("test.make_boot_service_harness")(function* ( |
| 35 | platform: NodeJS.Platform = "linux", | 35 | platform: NodeJS.Platform = "linux", |
| 36 | usePinnedLauncher = false, | ||
| 36 | ) { | 37 | ) { |
| 37 | const fs = yield* FileSystem.FileSystem; | 38 | const fs = yield* FileSystem.FileSystem; |
| 38 | const path = yield* Path.Path; | 39 | const path = yield* Path.Path; |
| ... | @@ -44,6 +45,10 @@ const makeHarness = Effect.fn("test.make_boot_service_harness")(function* ( | ... | @@ -44,6 +45,10 @@ const makeHarness = Effect.fn("test.make_boot_service_harness")(function* ( |
| 44 | const runtime = pinnedRuntimePaths(path, baseDir, "1.2.3"); | 45 | const runtime = pinnedRuntimePaths(path, baseDir, "1.2.3"); |
| 45 | yield* fs.makeDirectory(path.dirname(runtime.entryPath), { recursive: true }); | 46 | yield* fs.makeDirectory(path.dirname(runtime.entryPath), { recursive: true }); |
| 46 | yield* fs.writeFileString(runtime.entryPath, "export {};\n"); | 47 | yield* fs.writeFileString(runtime.entryPath, "export {};\n"); |
| 48 | yield* fs.writeFileString( | ||
| 49 | path.join(path.dirname(runtime.entryPath), "service-launcher.mjs"), | ||
| 50 | "export const source = 'pinned runtime';\n", | ||
| 51 | ); | ||
| 47 | yield* fs.writeFileString(runtime.sentinelPath, "1.2.3\n"); | 52 | yield* fs.writeFileString(runtime.sentinelPath, "1.2.3\n"); |
| 48 | 53 | ||
| 49 | const commands: string[] = []; | 54 | const commands: string[] = []; |
| ... | @@ -69,8 +74,7 @@ const makeHarness = Effect.fn("test.make_boot_service_harness")(function* ( | ... | @@ -69,8 +74,7 @@ const makeHarness = Effect.fn("test.make_boot_service_harness")(function* ( |
| 69 | cliVersion: "1.2.3", | 74 | cliVersion: "1.2.3", |
| 70 | host: { | 75 | host: { |
| 71 | execPath: "/usr/bin/node", | 76 | execPath: "/usr/bin/node", |
| 72 | cliEntryPath: path.join(home, "bin.mjs"), | 77 | ...(usePinnedLauncher ? {} : { launcherSourcePath: sourceLauncher }), |
| 73 | launcherSourcePath: sourceLauncher, | ||
| 74 | }, | 78 | }, |
| 75 | }).pipe( | 79 | }).pipe( |
| 76 | Effect.provideService(ProcessRunner.ProcessRunner, runner), | 80 | Effect.provideService(ProcessRunner.ProcessRunner, runner), |
| ... | @@ -109,6 +113,17 @@ it.layer(NodeServices.layer)("boot service install", (it) => { | ... | @@ -109,6 +113,17 @@ it.layer(NodeServices.layer)("boot service install", (it) => { |
| 109 | }), | 113 | }), |
| 110 | ); | 114 | ); |
| 111 | 115 | ||
| 116 | it.effect("copies the launcher from the prepared pinned runtime", () => | ||
| 117 | Effect.gen(function* () { | ||
| 118 | const { service, fs } = yield* makeHarness("linux", true); | ||
| 119 | const plan = yield* service.install; | ||
| 120 | |||
| 121 | expect(yield* fs.readFileString(plan.launcherPath)).toBe( | ||
| 122 | "export const source = 'pinned runtime';\n", | ||
| 123 | ); | ||
| 124 | }), | ||
| 125 | ); | ||
| 126 | |||
| 112 | it.effect("restarts an installed service when repair fails", () => | 127 | it.effect("restarts an installed service when repair fails", () => |
| 113 | Effect.gen(function* () { | 128 | Effect.gen(function* () { |
| 114 | const { service, commands, control } = yield* makeHarness(); | 129 | const { service, commands, control } = yield* makeHarness(); |
apps/server/src/cloud/bootService.ts+5-13| ... | @@ -1,8 +1,4 @@ | ... | @@ -1,8 +1,4 @@ |
| 1 | import { | 1 | import { HostProcessExecutablePath, HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 2 | HostProcessArguments, | ||
| 3 | HostProcessExecutablePath, | ||
| 4 | HostProcessPlatform, | ||
| 5 | } from "@t3tools/shared/hostProcess"; | ||
| 6 | import * as Config from "effect/Config"; | 2 | import * as Config from "effect/Config"; |
| 7 | import * as Context from "effect/Context"; | 3 | import * as Context from "effect/Context"; |
| 8 | import * as DateTime from "effect/DateTime"; | 4 | import * as DateTime from "effect/DateTime"; |
| ... | @@ -138,7 +134,6 @@ export class BootService extends Context.Service< | ... | @@ -138,7 +134,6 @@ export class BootService extends Context.Service< |
| 138 | 134 | ||
| 139 | export interface BootServiceHost { | 135 | export interface BootServiceHost { |
| 140 | readonly execPath: string; | 136 | readonly execPath: string; |
| 141 | readonly cliEntryPath: string; | ||
| 142 | readonly launcherSourcePath?: string; | 137 | readonly launcherSourcePath?: string; |
| 143 | } | 138 | } |
| 144 | 139 | ||
| ... | @@ -148,26 +143,23 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { | ... | @@ -148,26 +143,23 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { |
| 148 | readonly cliVersion: string; | 143 | readonly cliVersion: string; |
| 149 | readonly host?: BootServiceHost; | 144 | readonly host?: BootServiceHost; |
| 150 | }) { | 145 | }) { |
| 151 | const hostArguments = yield* HostProcessArguments; | ||
| 152 | const hostExecPath = yield* HostProcessExecutablePath; | 146 | const hostExecPath = yield* HostProcessExecutablePath; |
| 153 | const platform = yield* HostProcessPlatform; | 147 | const platform = yield* HostProcessPlatform; |
| 154 | const homeDir = yield* Config.string("HOME").pipe(Config.withDefault("")); | 148 | const homeDir = yield* Config.string("HOME").pipe(Config.withDefault("")); |
| 155 | const fs = yield* FileSystem.FileSystem; | 149 | const fs = yield* FileSystem.FileSystem; |
| 156 | const path = yield* Path.Path; | 150 | const path = yield* Path.Path; |
| 157 | const runner = yield* ProcessRunner.ProcessRunner; | 151 | const runner = yield* ProcessRunner.ProcessRunner; |
| 158 | const host = input.host ?? { | 152 | const host = input.host ?? { execPath: hostExecPath }; |
| 159 | execPath: hostExecPath, | ||
| 160 | cliEntryPath: hostArguments[1] ?? "", | ||
| 161 | }; | ||
| 162 | 153 | ||
| 163 | const unitDir = path.join(homeDir, ".config", "systemd", "user"); | 154 | const unitDir = path.join(homeDir, ".config", "systemd", "user"); |
| 164 | const unitPath = path.join(unitDir, BOOT_SERVICE_UNIT_FILE); | 155 | const unitPath = path.join(unitDir, BOOT_SERVICE_UNIT_FILE); |
| 165 | const logPath = path.join(input.logsDir, "boot-service.log"); | 156 | const logPath = path.join(input.logsDir, "boot-service.log"); |
| 166 | const launcherPath = path.join(input.baseDir, "runtime", SERVICE_LAUNCHER_FILE); | 157 | const launcherPath = path.join(input.baseDir, "runtime", SERVICE_LAUNCHER_FILE); |
| 167 | const statePath = path.join(input.baseDir, "runtime", SERVICE_STATE_FILE); | 158 | const statePath = path.join(input.baseDir, "runtime", SERVICE_STATE_FILE); |
| 168 | const launcherSourcePath = | ||
| 169 | host.launcherSourcePath ?? path.join(path.dirname(host.cliEntryPath), SERVICE_LAUNCHER_FILE); | ||
| 170 | const runtimePaths = pinnedRuntimePaths(path, input.baseDir, input.cliVersion); | 159 | const runtimePaths = pinnedRuntimePaths(path, input.baseDir, input.cliVersion); |
| 160 | const launcherSourcePath = | ||
| 161 | host.launcherSourcePath ?? | ||
| 162 | path.join(path.dirname(runtimePaths.entryPath), SERVICE_LAUNCHER_FILE); | ||
| 171 | const writeDurably = (filePath: string, contents: string) => | 163 | const writeDurably = (filePath: string, contents: string) => |
| 172 | Effect.scoped( | 164 | Effect.scoped( |
| 173 | Effect.gen(function* () { | 165 | Effect.gen(function* () { |