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