()
| 324 | }; |
| 325 | |
| 326 | const makeLaunchdBackend = (): ServiceBackend => { |
| 327 | const serviceTarget = (uid: number): string => `gui/${uid}/${SERVICE_LABEL}`; |
| 328 | |
| 329 | return { |
| 330 | platform: "darwin", |
| 331 | automated: true, |
| 332 | install: (descriptor) => |
| 333 | Effect.gen(function* () { |
| 334 | const fs = yield* FileSystem.FileSystem; |
| 335 | const path = yield* Path.Path; |
| 336 | const uid = currentUid(); |
| 337 | const dataDir = resolveExecutorDataDir(path); |
| 338 | const logs = serviceLogDir(path); |
| 339 | |
| 340 | yield* fs.makeDirectory(launchAgentsDir(path), { recursive: true }); |
| 341 | yield* fs.makeDirectory(logs, { recursive: true }); |
| 342 | |
| 343 | const plist = generateLaunchdPlist({ |
| 344 | label: SERVICE_LABEL, |
| 345 | programArguments: serviceProgramArguments(descriptor), |
| 346 | environment: serviceEnvironment(descriptor, dataDir), |
| 347 | stdoutPath: path.join(logs, "daemon.log"), |
| 348 | stderrPath: path.join(logs, "daemon.error.log"), |
| 349 | workingDirectory: dataDir, |
| 350 | }); |
| 351 | const plistFile = launchdPlistPath(path); |
| 352 | // 0600: the plist is owner-only. It carries no secret — the daemon reads |
| 353 | // the bearer from auth.json at boot — but stays tight regardless. |
| 354 | yield* fs.writeFileString(plistFile, plist, { mode: 0o600 }); |
| 355 | |
| 356 | // Re-bootstrap cleanly: a stale registration from a prior install would |
| 357 | // make `bootstrap` fail with "service already loaded". `service |
| 358 | // uninstall` also records the label as disabled in launchd's override |
| 359 | // database; clear that before bootstrapping or a reinstall can fail with |
| 360 | // launchctl's generic "Bootstrap failed: 5" error. |
| 361 | yield* runCommand("launchctl", ["bootout", serviceTarget(uid)]).pipe(Effect.ignore); |
| 362 | yield* runCommand("launchctl", ["enable", serviceTarget(uid)]).pipe(Effect.ignore); |
| 363 | const bootstrap = yield* runCommand("launchctl", ["bootstrap", `gui/${uid}`, plistFile]); |
| 364 | if (bootstrap.code !== 0) { |
| 365 | return yield* Effect.fail( |
| 366 | new Error( |
| 367 | `launchctl bootstrap failed (exit ${bootstrap.code}): ${bootstrap.stderr.trim() || bootstrap.stdout.trim()}`, |
| 368 | ), |
| 369 | ); |
| 370 | } |
| 371 | }), |
| 372 | uninstall: () => |
| 373 | Effect.gen(function* () { |
| 374 | const fs = yield* FileSystem.FileSystem; |
| 375 | const path = yield* Path.Path; |
| 376 | const uid = currentUid(); |
| 377 | yield* runCommand("launchctl", ["bootout", serviceTarget(uid)]).pipe(Effect.ignore); |
| 378 | yield* runCommand("launchctl", ["disable", serviceTarget(uid)]).pipe(Effect.ignore); |
| 379 | yield* fs.remove(launchdPlistPath(path), { force: true }); |
| 380 | }), |
| 381 | status: () => |
| 382 | Effect.gen(function* () { |
| 383 | const fs = yield* FileSystem.FileSystem; |
no test coverage detected