@nowcrew/daemon 0.6.77-beta.7 → 0.6.77-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/automatic-daemon-installer.js +15 -6
- package/dist/automatic-install-target.js +34 -9
- package/dist/computer-cli.js +39 -11
- package/dist/computer-profile.js +15 -2
- package/dist/daemon-installation-lease.js +7 -4
- package/dist/daemon-local-home.js +108 -0
- package/dist/daemon-migration-controller.js +15 -4
- package/dist/daemon-update-eligibility.js +15 -7
- package/dist/host-execution-coordinator.js +4 -3
- package/dist/main.js +13 -2
- package/dist/managed-service-diagnostics.js +1 -0
- package/dist/managed-service-registry.js +12 -7
- package/dist/managed-service-startup.js +4 -1
- package/dist/serve.js +5 -2
- package/dist/slog.js +14 -1
- package/dist/storage-control.js +12 -1
- package/dist/storage-identity.js +15 -1
- package/dist/storage-migration-controller.js +25 -4
- package/dist/storage-ownership.js +55 -0
- package/dist/storage-runtime-controller.js +29 -7
- package/dist/storage-runtime-readiness.js +26 -4
- package/dist/storage-runtime.js +18 -2
- package/dist/storage-status-delivery.js +4 -0
- package/dist/storage-status-replay.js +16 -1
- package/dist/storage-worker-executor.js +1 -0
- package/dist/storage-worker-operation.js +4 -0
- package/package.json +9 -10
package/README.md
CHANGED
|
@@ -479,6 +479,33 @@ failure matrix, rollout limits, and the server-native follow-up design.
|
|
|
479
479
|
|
|
480
480
|
## Code Map
|
|
481
481
|
|
|
482
|
+
## Local daemon installation root (experimental)
|
|
483
|
+
|
|
484
|
+
New installations use `~/.nowwork` for machine-local daemon configuration and npm runtimes. The
|
|
485
|
+
Workspace persistence root is still selected independently by `NOWWORK_HOME`; it may be a local
|
|
486
|
+
directory or an ECS-specific NAS directory. Runtime packages, locks, sockets, PIDs and temporary
|
|
487
|
+
files must remain on the machine running the daemon.
|
|
488
|
+
|
|
489
|
+
The same local root also owns shared daemon control state:
|
|
490
|
+
`daemon/managed-services/registry.json`, `daemon/managed-services/installation-leases/`,
|
|
491
|
+
`daemon/compute-execution/`, and `logs/sls-spool/`. This keeps service ownership, upgrade leases,
|
|
492
|
+
host-wide execution admission, and offline log delivery on the daemon host rather than on NAS.
|
|
493
|
+
|
|
494
|
+
The selector is controlled by `NOWWORK_LOCAL_HOME_MODE`:
|
|
495
|
+
|
|
496
|
+
- `auto` (default): use an existing `.nowwork` installation, otherwise preserve an existing `.crew`
|
|
497
|
+
installation; a brand-new profile is created under `.nowwork`.
|
|
498
|
+
- `nowwork`: require the `.nowwork` installation root.
|
|
499
|
+
- `legacy`: force the old `.crew` root for rollback.
|
|
500
|
+
|
|
501
|
+
`NOWWORK_STORAGE_MODE` is accepted as a compatibility alias. `NOWWORK_LOCAL_HOME` can override the
|
|
502
|
+
local root and should point to a local filesystem, never to the NAS Workspace root; the mount/device
|
|
503
|
+
check remains a doctor/migration gate. If the same profile
|
|
504
|
+
is found in both roots, startup/install is refused until the duplicate is resolved explicitly. The
|
|
505
|
+
old `.crew` tree is not deleted by this compatibility layer.
|
|
506
|
+
For shared machine control state, `auto` prefers an existing `~/.nowwork` root; during a mixed
|
|
507
|
+
migration window, set `NOWWORK_LOCAL_HOME_MODE=legacy` on services that still use the old registry.
|
|
508
|
+
|
|
482
509
|
- `src/serve.ts`: connection, negotiation, routing, sync, and legacy boundary.
|
|
483
510
|
- `src/daemon-update-eligibility.ts`, `src/daemon-updater.ts`, and `src/daemon-update-controller.ts`:
|
|
484
511
|
managed-update eligibility, exact npm replacement, host-wide admission barrier, and restart handoff.
|
|
@@ -13,6 +13,7 @@ import { daemonReleaseChannel, isDaemonReleaseVersion } from "./daemon-release-v
|
|
|
13
13
|
import { daemonRegistryCandidates } from "./daemon-registry.js";
|
|
14
14
|
import { npmInvocation } from "./npm-command.js";
|
|
15
15
|
import { discoverExactLegacyService, } from "./automatic-service-migration.js";
|
|
16
|
+
import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
|
|
16
17
|
function profileStorage(platform, userHome, runner) {
|
|
17
18
|
return {
|
|
18
19
|
platform,
|
|
@@ -31,6 +32,7 @@ function sameHostname(left, right) {
|
|
|
31
32
|
function defaultDependencies() {
|
|
32
33
|
const platform = process.platform;
|
|
33
34
|
const userHome = homedir();
|
|
35
|
+
const localHome = resolveDaemonLocalStateRootFromEnv({ userHome, platform, environment: process.env });
|
|
34
36
|
const runner = systemCommandRunner;
|
|
35
37
|
const storage = profileStorage(platform, userHome, runner);
|
|
36
38
|
let managedRecords = [];
|
|
@@ -39,6 +41,7 @@ function defaultDependencies() {
|
|
|
39
41
|
platform,
|
|
40
42
|
userHome,
|
|
41
43
|
uid: process.getuid?.(),
|
|
44
|
+
environment: process.env,
|
|
42
45
|
nodePath: process.execPath,
|
|
43
46
|
version: daemonVersion(),
|
|
44
47
|
...(process.env.PATH ? { runtimePath: process.env.PATH } : {}),
|
|
@@ -58,7 +61,7 @@ function defaultDependencies() {
|
|
|
58
61
|
},
|
|
59
62
|
captureService: async (spec, target) => {
|
|
60
63
|
if (platform !== "win32") {
|
|
61
|
-
managedRecords = await readManagedServiceRegistry({ userHome });
|
|
64
|
+
managedRecords = await readManagedServiceRegistry({ userHome, localHome });
|
|
62
65
|
const entryPath = spec.daemonCommand[1];
|
|
63
66
|
const installation = daemonGlobalInstallation(entryPath, platform);
|
|
64
67
|
if (installation === null) {
|
|
@@ -92,7 +95,7 @@ function defaultDependencies() {
|
|
|
92
95
|
managedServices: managedRecords,
|
|
93
96
|
}),
|
|
94
97
|
stopService: (spec) => serviceAction(spec, "stop", runner),
|
|
95
|
-
acquireInstallationLease: (npmPrefix) => acquireDaemonInstallationLease(npmPrefix, { userHome }),
|
|
98
|
+
acquireInstallationLease: (npmPrefix) => acquireDaemonInstallationLease(npmPrefix, { userHome, localHome }),
|
|
96
99
|
installPackage: installExactDaemonPackage,
|
|
97
100
|
saveTargetProfile: async (profile, daemonHome) => {
|
|
98
101
|
await saveProfile(profile, daemonHome, storage);
|
|
@@ -119,7 +122,7 @@ function defaultDependencies() {
|
|
|
119
122
|
spec,
|
|
120
123
|
record,
|
|
121
124
|
runner,
|
|
122
|
-
registry: { userHome },
|
|
125
|
+
registry: { userHome, localHome },
|
|
123
126
|
});
|
|
124
127
|
},
|
|
125
128
|
removeLegacyService: async (legacy) => {
|
|
@@ -146,13 +149,13 @@ function defaultDependencies() {
|
|
|
146
149
|
await restoreExactService(spec, previous, runner);
|
|
147
150
|
return;
|
|
148
151
|
}
|
|
149
|
-
const current = (await readManagedServiceRegistry({ userHome })).find((record) => record.serviceId === spec.id);
|
|
152
|
+
const current = (await readManagedServiceRegistry({ userHome, localHome })).find((record) => record.serviceId === spec.id);
|
|
150
153
|
if (current !== undefined && JSON.stringify(current) !== JSON.stringify(previousManagedRecord)) {
|
|
151
154
|
await uninstallManagedService({
|
|
152
155
|
spec,
|
|
153
156
|
record: current,
|
|
154
157
|
runner,
|
|
155
|
-
registry: { userHome },
|
|
158
|
+
registry: { userHome, localHome },
|
|
156
159
|
});
|
|
157
160
|
}
|
|
158
161
|
if (previousManagedRecord !== undefined) {
|
|
@@ -160,7 +163,7 @@ function defaultDependencies() {
|
|
|
160
163
|
spec,
|
|
161
164
|
record: previousManagedRecord,
|
|
162
165
|
runner,
|
|
163
|
-
registry: { userHome },
|
|
166
|
+
registry: { userHome, localHome },
|
|
164
167
|
});
|
|
165
168
|
if (previous.running)
|
|
166
169
|
await serviceAction(spec, "start", runner);
|
|
@@ -231,6 +234,10 @@ function recoveryError(primary, failures) {
|
|
|
231
234
|
}
|
|
232
235
|
export async function installDaemonAutomatically(input, dependencies = defaultDependencies()) {
|
|
233
236
|
const machineToken = MachineTokenSchema.parse(input.machineToken);
|
|
237
|
+
const requestedLocalHome = input.requestedLocalHome ?? dependencies.environment.NOWWORK_LOCAL_HOME;
|
|
238
|
+
const requestedLocalHomeMode = input.requestedLocalHomeMode
|
|
239
|
+
?? dependencies.environment.NOWWORK_LOCAL_HOME_MODE
|
|
240
|
+
?? dependencies.environment.NOWWORK_STORAGE_MODE;
|
|
234
241
|
const target = resolveAutomaticInstallTarget({
|
|
235
242
|
serverUrl: input.serverUrl,
|
|
236
243
|
...(input.requestedProfile === undefined ? {} : { requestedProfile: input.requestedProfile }),
|
|
@@ -238,6 +245,8 @@ export async function installDaemonAutomatically(input, dependencies = defaultDe
|
|
|
238
245
|
...(input.requestedRegistryUrl === undefined ? {} : { requestedRegistryUrl: input.requestedRegistryUrl }),
|
|
239
246
|
userHome: dependencies.userHome,
|
|
240
247
|
platform: dependencies.platform,
|
|
248
|
+
...(requestedLocalHome === undefined ? {} : { requestedLocalHome }),
|
|
249
|
+
...(requestedLocalHomeMode === undefined ? {} : { requestedLocalHomeMode }),
|
|
241
250
|
});
|
|
242
251
|
if (!isDaemonReleaseVersion(dependencies.version)) {
|
|
243
252
|
throw new Error(`Invoking daemon package has an invalid release version '${dependencies.version}'`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { posix, win32 } from "node:path";
|
|
2
2
|
import { profileLayout, validateLocalInstanceName } from "./profile-layout.js";
|
|
3
3
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
4
|
+
import { resolveDaemonLocalHome } from "./daemon-local-home.js";
|
|
4
5
|
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,47}$/;
|
|
5
6
|
const KNOWN_TARGETS = {
|
|
6
7
|
"nowwork-in.nowcoder.com": {
|
|
@@ -72,13 +73,21 @@ function knownTargetForProfile(environment, requestedProfile, input, parsed) {
|
|
|
72
73
|
const legacyProfile = environment === "production" ? "in" : environment;
|
|
73
74
|
assertReleaseTagForServerUrl(input.serverUrl, input.requestedReleaseTag);
|
|
74
75
|
if (requestedProfile === undefined || requestedProfile === legacyProfile) {
|
|
76
|
+
const local = resolveDaemonLocalHome({
|
|
77
|
+
userHome: input.userHome,
|
|
78
|
+
platform: input.platform,
|
|
79
|
+
profile: legacyProfile,
|
|
80
|
+
...(input.requestedLocalHome === undefined ? {} : { explicit: input.requestedLocalHome }),
|
|
81
|
+
...(input.requestedLocalHomeMode === undefined ? {} : { mode: input.requestedLocalHomeMode }),
|
|
82
|
+
...(input.pathExists === undefined ? {} : { exists: input.pathExists }),
|
|
83
|
+
});
|
|
75
84
|
return Object.freeze({
|
|
76
85
|
kind: "known",
|
|
77
86
|
profile: legacyProfile,
|
|
78
87
|
serverUrl: normalizedServerUrl(parsed),
|
|
79
|
-
daemonHome:
|
|
80
|
-
agentsRoot: paths.join(
|
|
81
|
-
npmPrefix:
|
|
88
|
+
daemonHome: local.daemonHome,
|
|
89
|
+
agentsRoot: paths.join(local.root, environment === "production" ? "agents" : "agents-dev"),
|
|
90
|
+
npmPrefix: local.runtimePrefix,
|
|
82
91
|
releaseTag: environment === "dev" ? "beta" : "latest",
|
|
83
92
|
});
|
|
84
93
|
}
|
|
@@ -99,15 +108,23 @@ function knownTargetForProfile(environment, requestedProfile, input, parsed) {
|
|
|
99
108
|
userHome: input.userHome,
|
|
100
109
|
platform: input.platform,
|
|
101
110
|
});
|
|
111
|
+
const local = resolveDaemonLocalHome({
|
|
112
|
+
userHome: input.userHome,
|
|
113
|
+
platform: input.platform,
|
|
114
|
+
profile: layout.profile,
|
|
115
|
+
...(input.requestedLocalHome === undefined ? {} : { explicit: input.requestedLocalHome }),
|
|
116
|
+
...(input.requestedLocalHomeMode === undefined ? {} : { mode: input.requestedLocalHomeMode }),
|
|
117
|
+
...(input.pathExists === undefined ? {} : { exists: input.pathExists }),
|
|
118
|
+
});
|
|
102
119
|
return Object.freeze({
|
|
103
120
|
kind: "known",
|
|
104
121
|
environment,
|
|
105
122
|
localInstanceName,
|
|
106
123
|
profile: layout.profile,
|
|
107
124
|
serverUrl: normalizedServerUrl(parsed),
|
|
108
|
-
daemonHome:
|
|
109
|
-
agentsRoot: layout.
|
|
110
|
-
npmPrefix:
|
|
125
|
+
daemonHome: local.daemonHome,
|
|
126
|
+
agentsRoot: paths.join(local.root, "agents-" + layout.environment + "-" + layout.localInstanceName),
|
|
127
|
+
npmPrefix: local.runtimePrefix,
|
|
111
128
|
releaseTag: environment === "dev" ? "beta" : "latest",
|
|
112
129
|
});
|
|
113
130
|
}
|
|
@@ -127,13 +144,21 @@ export function resolveAutomaticInstallTarget(input) {
|
|
|
127
144
|
return registryUrl === undefined ? target : Object.freeze({ ...target, registryUrl });
|
|
128
145
|
}
|
|
129
146
|
const profile = requireCustomProfile(hostname, input.requestedProfile);
|
|
147
|
+
const local = resolveDaemonLocalHome({
|
|
148
|
+
userHome: input.userHome,
|
|
149
|
+
platform: input.platform,
|
|
150
|
+
profile,
|
|
151
|
+
...(input.requestedLocalHome === undefined ? {} : { explicit: input.requestedLocalHome }),
|
|
152
|
+
...(input.requestedLocalHomeMode === undefined ? {} : { mode: input.requestedLocalHomeMode }),
|
|
153
|
+
...(input.pathExists === undefined ? {} : { exists: input.pathExists }),
|
|
154
|
+
});
|
|
130
155
|
return Object.freeze({
|
|
131
156
|
kind: "custom",
|
|
132
157
|
profile,
|
|
133
158
|
serverUrl: normalizedServerUrl(parsed),
|
|
134
|
-
daemonHome:
|
|
135
|
-
agentsRoot: paths.join(
|
|
136
|
-
npmPrefix:
|
|
159
|
+
daemonHome: local.daemonHome,
|
|
160
|
+
agentsRoot: paths.join(local.root, `agents-${profile}`),
|
|
161
|
+
npmPrefix: local.runtimePrefix,
|
|
137
162
|
releaseTag: input.requestedReleaseTag ?? "latest",
|
|
138
163
|
...(registryUrl === undefined ? {} : { registryUrl }),
|
|
139
164
|
});
|
package/dist/computer-cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { resolve } from "node:path";
|
|
3
|
+
import { posix, resolve, win32 } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { assertProfileAgentsRootUnique, daemonHome, inspectProfileAgentsRoot, listProfiles, loadProfile, PROFILE_AGENTS_ROOT_CONFLICT_MESSAGE, ProfileAgentsRootConflictError, profileIsPrivate, profilePath, publicProfile, removeProfile, resolveAgentsRoot, saveProfile, windowsDpapiProtector, } from "./computer-profile.js";
|
|
6
6
|
import { buildServiceSpec, doctorService, hardenWindowsProfile, installService, readServiceDescriptor, serviceAction, serviceStatus, systemCommandRunner, uninstallService, upgradeDaemon, windowsProfileIsPrivate, } from "./computer-service.js";
|
|
@@ -18,6 +18,7 @@ import { assertManagedServiceIdentity, buildManagedServiceRecord, installManaged
|
|
|
18
18
|
import { inspectManagedServiceDiagnostics } from "./managed-service-diagnostics.js";
|
|
19
19
|
import { readManagedServiceRegistry } from "./managed-service-registry.js";
|
|
20
20
|
import { installDaemonAutomatically, } from "./automatic-daemon-installer.js";
|
|
21
|
+
import { resolveDaemonLocalHome, resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
21
22
|
const COMPUTER_COMMANDS = new Set(["profile", "doctor", "install", "uninstall", "start", "stop", "restart", "status", "upgrade"]);
|
|
22
23
|
export function builtDaemonEntry(moduleUrl = import.meta.url) {
|
|
23
24
|
return moduleUrl.endsWith(".js")
|
|
@@ -67,6 +68,7 @@ function usage(td) {
|
|
|
67
68
|
" crew-daemon install --server-url <url> --api-key <sk_machine_*> [--profile <custom-name>] [--release-tag <latest|beta>] [--registry <https-url>]",
|
|
68
69
|
" crew-daemon doctor|install|uninstall|start|stop|restart|status --profile <name>",
|
|
69
70
|
" crew-daemon upgrade [--profile <name>] [--release-tag <latest|beta>] [--registry <https-url>]",
|
|
71
|
+
" NOWWORK_LOCAL_HOME_MODE=auto|nowwork|legacy NOWWORK_LOCAL_HOME=~/.nowwork",
|
|
70
72
|
"",
|
|
71
73
|
td("profile save reads the machine token from CREW_MACHINE_TOKEN, or stdin with --token-stdin."),
|
|
72
74
|
].join("\n") + "\n";
|
|
@@ -154,6 +156,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
154
156
|
?? deps.env.CREW_MACHINE_TOKEN)?.trim();
|
|
155
157
|
if (!machineToken)
|
|
156
158
|
throw new Error(td("Missing machine token for automatic installation"));
|
|
159
|
+
const requestedLocalHomeMode = deps.env.NOWWORK_LOCAL_HOME_MODE ?? deps.env.NOWWORK_STORAGE_MODE;
|
|
157
160
|
const result = await deps.installAutomatically({
|
|
158
161
|
serverUrl: parsed.values["server-url"],
|
|
159
162
|
machineToken,
|
|
@@ -162,11 +165,34 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
162
165
|
...(configuredRegistryUrl === undefined
|
|
163
166
|
? {}
|
|
164
167
|
: { requestedRegistryUrl: configuredRegistryUrl }),
|
|
168
|
+
...(deps.env.NOWWORK_LOCAL_HOME === undefined
|
|
169
|
+
? {}
|
|
170
|
+
: { requestedLocalHome: deps.env.NOWWORK_LOCAL_HOME }),
|
|
171
|
+
...(requestedLocalHomeMode === undefined ? {} : { requestedLocalHomeMode }),
|
|
165
172
|
});
|
|
166
173
|
deps.stdout(`${td("Installed daemon {{version}} for profile '{{profile}}'; service '{{service}}' is running.", { version: result.version, profile: result.profile, service: result.serviceId })}\n`);
|
|
167
174
|
return 0;
|
|
168
175
|
}
|
|
169
|
-
const
|
|
176
|
+
const resolvedProfile = profileName ?? positionalName;
|
|
177
|
+
const configuredLocalHomeMode = deps.env.NOWWORK_LOCAL_HOME_MODE ?? deps.env.NOWWORK_STORAGE_MODE;
|
|
178
|
+
const home = resolvedProfile !== undefined
|
|
179
|
+
&& (deps.env.NOWWORK_LOCAL_HOME !== undefined
|
|
180
|
+
|| deps.env.NOWWORK_LOCAL_HOME_MODE !== undefined
|
|
181
|
+
|| deps.env.NOWWORK_STORAGE_MODE !== undefined)
|
|
182
|
+
? resolveDaemonLocalHome({
|
|
183
|
+
userHome: deps.userHome,
|
|
184
|
+
platform: deps.platform,
|
|
185
|
+
profile: resolvedProfile,
|
|
186
|
+
...(deps.env.NOWWORK_LOCAL_HOME === undefined ? {} : { explicit: deps.env.NOWWORK_LOCAL_HOME }),
|
|
187
|
+
...(configuredLocalHomeMode === undefined ? {} : { mode: configuredLocalHomeMode }),
|
|
188
|
+
}).daemonHome
|
|
189
|
+
: daemonHome(deps.env);
|
|
190
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
191
|
+
userHome: deps.userHome,
|
|
192
|
+
platform: deps.platform,
|
|
193
|
+
explicit: deps.env.NOWWORK_LOCAL_HOME,
|
|
194
|
+
mode: configuredLocalHomeMode,
|
|
195
|
+
});
|
|
170
196
|
const profileStorage = {
|
|
171
197
|
platform: deps.platform,
|
|
172
198
|
userHome: deps.userHome,
|
|
@@ -221,7 +247,9 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
221
247
|
}),
|
|
222
248
|
...(parsed.values["agents-root"]
|
|
223
249
|
? { agentsRoot: resolveAgentsRoot(parsed.values["agents-root"], deps.userHome, deps.platform) }
|
|
224
|
-
:
|
|
250
|
+
: (deps.env.NOWWORK_LOCAL_HOME !== undefined || configuredLocalHomeMode !== undefined)
|
|
251
|
+
? { agentsRoot: resolveAgentsRoot((deps.platform === "win32" ? win32 : posix).join((deps.platform === "win32" ? win32 : posix).dirname(home), `agents-${name}`), deps.userHome, deps.platform) }
|
|
252
|
+
: {}),
|
|
225
253
|
...(deps.env.PATH ? { runtimePath: deps.env.PATH } : {}),
|
|
226
254
|
};
|
|
227
255
|
await saveProfile(profile, home, profileStorage);
|
|
@@ -276,7 +304,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
276
304
|
try {
|
|
277
305
|
installationLease = installation === null
|
|
278
306
|
? null
|
|
279
|
-
: await deps.acquireInstallationLease(installation.npmPrefix, { userHome: deps.userHome });
|
|
307
|
+
: await deps.acquireInstallationLease(installation.npmPrefix, { userHome: deps.userHome, localHome });
|
|
280
308
|
await upgradeDaemon(deps.platform, deps.runner, installation?.npmPrefix, releaseTag, configuredRegistryUrl === undefined
|
|
281
309
|
? restartProfile?.registryUrl
|
|
282
310
|
?? configuredRegistryUrl
|
|
@@ -358,7 +386,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
358
386
|
spec,
|
|
359
387
|
record: managedRecord,
|
|
360
388
|
runner: deps.runner,
|
|
361
|
-
registry: { userHome: deps.userHome },
|
|
389
|
+
registry: { userHome: deps.userHome, localHome },
|
|
362
390
|
});
|
|
363
391
|
deps.stdout(`${td("Installed '{{id}}'. Use status to confirm runtime state.", { id: spec.id })}\n`);
|
|
364
392
|
return 0;
|
|
@@ -367,7 +395,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
367
395
|
if (managedRecord === null)
|
|
368
396
|
await uninstallService(spec, deps.runner);
|
|
369
397
|
else {
|
|
370
|
-
const current = (await readManagedServiceRegistry({ userHome: deps.userHome })).find((record) => record.serviceId === spec.id
|
|
398
|
+
const current = (await readManagedServiceRegistry({ userHome: deps.userHome, localHome })).find((record) => record.serviceId === spec.id
|
|
371
399
|
&& record.daemonHome === managedRecord.daemonHome
|
|
372
400
|
&& record.profile === name);
|
|
373
401
|
if (current === undefined) {
|
|
@@ -378,7 +406,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
378
406
|
daemonHome: home,
|
|
379
407
|
npmPrefix: installation.npmPrefix,
|
|
380
408
|
runner: deps.runner,
|
|
381
|
-
registry: { userHome: deps.userHome },
|
|
409
|
+
registry: { userHome: deps.userHome, localHome },
|
|
382
410
|
acquireInstallationLease: deps.acquireInstallationLease,
|
|
383
411
|
});
|
|
384
412
|
}
|
|
@@ -398,7 +426,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
398
426
|
daemonHome: home,
|
|
399
427
|
npmPrefix: installation.npmPrefix,
|
|
400
428
|
runner: deps.runner,
|
|
401
|
-
registry: { userHome: deps.userHome },
|
|
429
|
+
registry: { userHome: deps.userHome, localHome },
|
|
402
430
|
acquireInstallationLease: deps.acquireInstallationLease,
|
|
403
431
|
});
|
|
404
432
|
}
|
|
@@ -408,7 +436,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
408
436
|
spec,
|
|
409
437
|
record: current,
|
|
410
438
|
runner: deps.runner,
|
|
411
|
-
registry: { userHome: deps.userHome },
|
|
439
|
+
registry: { userHome: deps.userHome, localHome },
|
|
412
440
|
});
|
|
413
441
|
}
|
|
414
442
|
}
|
|
@@ -420,7 +448,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
420
448
|
await assertManagedServiceIdentity({
|
|
421
449
|
spec,
|
|
422
450
|
record: managedRecord,
|
|
423
|
-
registry: { userHome: deps.userHome },
|
|
451
|
+
registry: { userHome: deps.userHome, localHome },
|
|
424
452
|
});
|
|
425
453
|
}
|
|
426
454
|
await serviceAction(spec, command, deps.runner);
|
|
@@ -451,7 +479,7 @@ export async function runComputerCommand(argv, overrides = {}) {
|
|
|
451
479
|
: await inspectManagedServiceDiagnostics({
|
|
452
480
|
spec,
|
|
453
481
|
expectedRecord: managedRecord,
|
|
454
|
-
registry: { userHome: deps.userHome },
|
|
482
|
+
registry: { userHome: deps.userHome, localHome },
|
|
455
483
|
});
|
|
456
484
|
const checks = [
|
|
457
485
|
...serviceChecks,
|
package/dist/computer-profile.js
CHANGED
|
@@ -9,6 +9,7 @@ import { acquireProfileSaveLock, } from "./computer-profile-lock.js";
|
|
|
9
9
|
import { formatDaemonText } from "./i18n.js";
|
|
10
10
|
import { atomicPrivateWrite } from "./atomic-private-write.js";
|
|
11
11
|
import { normalizeDaemonNpmRegistry } from "./npm-registry.js";
|
|
12
|
+
import { resolveDaemonLocalHome } from "./daemon-local-home.js";
|
|
12
13
|
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,47}$/;
|
|
13
14
|
export const MachineTokenSchema = z.string().startsWith("sk_machine_").max(4096);
|
|
14
15
|
const ServerUrlSchema = z.string().url().max(2048).refine((value) => {
|
|
@@ -94,8 +95,20 @@ export class ProfileAgentsRootConflictError extends Error {
|
|
|
94
95
|
}
|
|
95
96
|
const quotePosixArgument = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
96
97
|
const quotePowerShellArgument = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
97
|
-
export function daemonHome(env = process.env) {
|
|
98
|
-
|
|
98
|
+
export function daemonHome(env = process.env, profile) {
|
|
99
|
+
if (env.CREW_DAEMON_HOME !== undefined)
|
|
100
|
+
return resolve(env.CREW_DAEMON_HOME);
|
|
101
|
+
const mode = env.NOWWORK_LOCAL_HOME_MODE ?? env.NOWWORK_STORAGE_MODE;
|
|
102
|
+
if (profile !== undefined && (env.NOWWORK_LOCAL_HOME !== undefined || mode !== undefined)) {
|
|
103
|
+
return resolveDaemonLocalHome({
|
|
104
|
+
userHome: homedir(),
|
|
105
|
+
platform: process.platform,
|
|
106
|
+
profile,
|
|
107
|
+
...(env.NOWWORK_LOCAL_HOME === undefined ? {} : { explicit: env.NOWWORK_LOCAL_HOME }),
|
|
108
|
+
...(mode === undefined ? {} : { mode }),
|
|
109
|
+
}).daemonHome;
|
|
110
|
+
}
|
|
111
|
+
return resolve(homedir(), ".crew/daemon");
|
|
99
112
|
}
|
|
100
113
|
export function resolveAgentsRoot(configured, userHome = homedir(), platform = process.platform) {
|
|
101
114
|
const pathApi = platform === "win32" ? win32 : posix;
|
|
@@ -3,6 +3,7 @@ import { realpathSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, open } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
6
7
|
import { defaultProcessController } from "./execution-journal.js";
|
|
7
8
|
import { createJournalLease, createJournalLeaseRegistry, inspectJournalLock, } from "./execution-journal-lock.js";
|
|
8
9
|
const registry = createJournalLeaseRegistry();
|
|
@@ -18,9 +19,10 @@ function canonicalPrefix(npmPrefix) {
|
|
|
18
19
|
return absolute;
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
function leaseDirectory(npmPrefix, userHome) {
|
|
22
|
+
function leaseDirectory(npmPrefix, userHome, localHome) {
|
|
22
23
|
const fingerprint = createHash("sha256").update(resolve(npmPrefix)).digest("hex");
|
|
23
|
-
|
|
24
|
+
const root = localHome ?? resolveDaemonLocalStateRoot({ userHome, platform: process.platform });
|
|
25
|
+
return resolve(root, "daemon", "managed-services", "installation-leases", fingerprint);
|
|
24
26
|
}
|
|
25
27
|
async function syncDirectory(path) {
|
|
26
28
|
if (process.platform === "win32")
|
|
@@ -35,7 +37,8 @@ async function syncDirectory(path) {
|
|
|
35
37
|
}
|
|
36
38
|
export async function acquireDaemonInstallationLease(npmPrefix, options = {}) {
|
|
37
39
|
const normalizedPrefix = canonicalPrefix(npmPrefix);
|
|
38
|
-
const
|
|
40
|
+
const userHome = options.userHome ?? homedir();
|
|
41
|
+
const directory = leaseDirectory(normalizedPrefix, userHome, options.localHome);
|
|
39
42
|
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
40
43
|
const lease = createJournalLease({
|
|
41
44
|
directory,
|
|
@@ -75,7 +78,7 @@ export function daemonInstallationLeaseHeld(npmPrefix) {
|
|
|
75
78
|
export async function inspectDaemonInstallationLease(npmPrefix, options = {}) {
|
|
76
79
|
const normalizedPrefix = canonicalPrefix(npmPrefix);
|
|
77
80
|
return inspectJournalLock({
|
|
78
|
-
directory: leaseDirectory(normalizedPrefix, options.userHome ?? homedir()),
|
|
81
|
+
directory: leaseDirectory(normalizedPrefix, options.userHome ?? homedir(), options.localHome),
|
|
79
82
|
inspectIdentity: options.inspectIdentity ?? defaultProcessController.inspectIdentity,
|
|
80
83
|
});
|
|
81
84
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { posix, win32 } from "node:path";
|
|
4
|
+
const PROFILE = /^[a-z0-9][a-z0-9_-]{0,47}$/u;
|
|
5
|
+
const CONTROL = /[\0\r\n]/u;
|
|
6
|
+
const pathsFor = (platform) => platform === "win32" ? win32 : posix;
|
|
7
|
+
export function parseDaemonLocalHomeMode(value) {
|
|
8
|
+
if (value === undefined || value.trim() === "")
|
|
9
|
+
return "auto";
|
|
10
|
+
const normalized = value.trim().toLowerCase();
|
|
11
|
+
if (normalized === "auto" || normalized === "nowwork" || normalized === "legacy")
|
|
12
|
+
return normalized;
|
|
13
|
+
throw new Error("NOWWORK_LOCAL_HOME_MODE must be auto, nowwork or legacy");
|
|
14
|
+
}
|
|
15
|
+
function absoluteRoot(value, userHome, platform) {
|
|
16
|
+
const paths = pathsFor(platform);
|
|
17
|
+
if (value.length === 0 || CONTROL.test(value))
|
|
18
|
+
throw new Error("NOWWORK_LOCAL_HOME must be a non-empty single-line path");
|
|
19
|
+
const expanded = value === "~"
|
|
20
|
+
? userHome
|
|
21
|
+
: value.startsWith("~/") || (platform === "win32" && value.startsWith("~\\"))
|
|
22
|
+
? paths.join(userHome, value.slice(2))
|
|
23
|
+
: value;
|
|
24
|
+
if (!paths.isAbsolute(expanded))
|
|
25
|
+
throw new Error("NOWWORK_LOCAL_HOME must be absolute or start with '~/'");
|
|
26
|
+
return paths.normalize(expanded);
|
|
27
|
+
}
|
|
28
|
+
function legacyDaemonDirectory(profile) {
|
|
29
|
+
if (profile === "in" || profile === "production")
|
|
30
|
+
return "daemon";
|
|
31
|
+
if (profile === "dev")
|
|
32
|
+
return "daemon-dev";
|
|
33
|
+
if (profile.startsWith("nw-"))
|
|
34
|
+
return `daemon-${profile.slice(3)}`;
|
|
35
|
+
return `daemon-${profile}`;
|
|
36
|
+
}
|
|
37
|
+
function resolution(mode, root, profile, platform, source) {
|
|
38
|
+
const paths = pathsFor(platform);
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
mode,
|
|
41
|
+
root,
|
|
42
|
+
daemonHome: mode === "nowwork" ? paths.join(root, "config") : paths.join(root, legacyDaemonDirectory(profile)),
|
|
43
|
+
runtimePrefix: paths.join(root, "daemon-runtimes", profile),
|
|
44
|
+
source,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function resolveDaemonLocalHome(input) {
|
|
48
|
+
if (!PROFILE.test(input.profile))
|
|
49
|
+
throw new Error("daemon profile is invalid");
|
|
50
|
+
const mode = parseDaemonLocalHomeMode(input.mode);
|
|
51
|
+
const paths = pathsFor(input.platform);
|
|
52
|
+
const nowworkRoot = absoluteRoot(input.explicit ?? paths.join(input.userHome, ".nowwork"), input.userHome, input.platform);
|
|
53
|
+
const legacyRoot = absoluteRoot(paths.join(input.userHome, ".crew"), input.userHome, input.platform);
|
|
54
|
+
if (input.explicit !== undefined && mode !== "legacy") {
|
|
55
|
+
return resolution("nowwork", nowworkRoot, input.profile, input.platform, "explicit");
|
|
56
|
+
}
|
|
57
|
+
if (mode === "nowwork")
|
|
58
|
+
return resolution("nowwork", nowworkRoot, input.profile, input.platform, "nowwork_forced");
|
|
59
|
+
if (mode === "legacy")
|
|
60
|
+
return resolution("legacy", legacyRoot, input.profile, input.platform, "legacy_forced");
|
|
61
|
+
const exists = input.exists ?? existsSync;
|
|
62
|
+
const nowwork = resolution("nowwork", nowworkRoot, input.profile, input.platform, "auto_nowwork_existing");
|
|
63
|
+
const legacy = resolution("legacy", legacyRoot, input.profile, input.platform, "auto_legacy_existing");
|
|
64
|
+
const hasNowwork = exists(nowwork.runtimePrefix) || exists(paths.join(nowwork.daemonHome, "profiles", `${input.profile}.json`));
|
|
65
|
+
const hasLegacy = exists(legacy.runtimePrefix) || exists(paths.join(legacy.daemonHome, "profiles", `${input.profile}.json`));
|
|
66
|
+
if (hasNowwork && hasLegacy) {
|
|
67
|
+
throw new Error(`Daemon local home conflict: profile '${input.profile}' exists under both .crew and .nowwork`);
|
|
68
|
+
}
|
|
69
|
+
if (hasNowwork)
|
|
70
|
+
return nowwork;
|
|
71
|
+
if (hasLegacy)
|
|
72
|
+
return legacy;
|
|
73
|
+
return resolution("nowwork", nowworkRoot, input.profile, input.platform, "auto_new");
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the local machine state root shared by service metadata, leases,
|
|
77
|
+
* host coordination and offline logs. It intentionally stays separate from
|
|
78
|
+
* NOWWORK_HOME, which may point at a NAS persistent data root.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveDaemonLocalStateRoot(input) {
|
|
81
|
+
const paths = pathsFor(input.platform);
|
|
82
|
+
const mode = parseDaemonLocalHomeMode(input.mode);
|
|
83
|
+
const nowworkRoot = absoluteRoot(input.explicit ?? paths.join(input.userHome, ".nowwork"), input.userHome, input.platform);
|
|
84
|
+
const legacyRoot = absoluteRoot(paths.join(input.userHome, ".crew"), input.userHome, input.platform);
|
|
85
|
+
if (mode === "legacy")
|
|
86
|
+
return legacyRoot;
|
|
87
|
+
if (input.explicit !== undefined || mode === "nowwork")
|
|
88
|
+
return nowworkRoot;
|
|
89
|
+
const exists = input.exists ?? existsSync;
|
|
90
|
+
// Once the new root exists, shared state must follow it even while the
|
|
91
|
+
// legacy root is retained as a rollback source.
|
|
92
|
+
if (exists(nowworkRoot))
|
|
93
|
+
return nowworkRoot;
|
|
94
|
+
if (exists(legacyRoot))
|
|
95
|
+
return legacyRoot;
|
|
96
|
+
return nowworkRoot;
|
|
97
|
+
}
|
|
98
|
+
export function resolveDaemonLocalStateRootFromEnv(input = {}) {
|
|
99
|
+
const environment = input.environment ?? process.env;
|
|
100
|
+
return resolveDaemonLocalStateRoot({
|
|
101
|
+
userHome: input.userHome ?? homedir(),
|
|
102
|
+
platform: input.platform ?? process.platform,
|
|
103
|
+
...(environment.NOWWORK_LOCAL_HOME === undefined ? {} : { explicit: environment.NOWWORK_LOCAL_HOME }),
|
|
104
|
+
...(environment.NOWWORK_LOCAL_HOME_MODE === undefined && environment.NOWWORK_STORAGE_MODE === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { mode: environment.NOWWORK_LOCAL_HOME_MODE ?? environment.NOWWORK_STORAGE_MODE }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -21,6 +21,9 @@ const MigrationMessageSchema = z.object({
|
|
|
21
21
|
function migrationBackupRoot(input, id) {
|
|
22
22
|
return resolve(input.backupRoot ?? resolve(input.userHome ?? homedir(), ".crew", "migration-backups"), id);
|
|
23
23
|
}
|
|
24
|
+
function legacyLocalHome(input) {
|
|
25
|
+
return resolve(input.userHome ?? homedir(), ".crew");
|
|
26
|
+
}
|
|
24
27
|
async function assertEmptyOrMissing(path) {
|
|
25
28
|
try {
|
|
26
29
|
const entries = await readdir(path);
|
|
@@ -77,10 +80,10 @@ async function backupAndCopy(input, id, targetHome, targetAgents, service) {
|
|
|
77
80
|
}
|
|
78
81
|
async function cleanupCurrentServiceDefault(spec, input) {
|
|
79
82
|
const userHome = input.userHome ?? homedir();
|
|
80
|
-
const records = await readManagedServiceRegistry({ userHome });
|
|
83
|
+
const records = await readManagedServiceRegistry({ userHome, localHome: legacyLocalHome(input) });
|
|
81
84
|
const record = records.find((candidate) => candidate.profile === spec.profile && candidate.daemonHome === input.currentDaemonHome);
|
|
82
85
|
if (record !== undefined && (spec.platform === "darwin" || spec.platform === "linux")) {
|
|
83
|
-
await uninstallManagedService({ spec, record, runner: systemCommandRunner, registry: { userHome } });
|
|
86
|
+
await uninstallManagedService({ spec, record, runner: systemCommandRunner, registry: { userHome, localHome: legacyLocalHome(input) } });
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
89
|
// Windows and legacy descriptors are handled by the normal CLI/service lifecycle.
|
|
@@ -125,7 +128,15 @@ export function createDaemonMigrationController(input) {
|
|
|
125
128
|
if (currentProfile.machineToken !== input.machineToken)
|
|
126
129
|
throw new Error("active profile token does not match this daemon");
|
|
127
130
|
currentRegistryUrl = currentProfile.registryUrl;
|
|
128
|
-
const target = resolveAutomaticInstallTarget({
|
|
131
|
+
const target = resolveAutomaticInstallTarget({
|
|
132
|
+
serverUrl: input.serverUrl,
|
|
133
|
+
requestedProfile: message.targetProfile,
|
|
134
|
+
userHome,
|
|
135
|
+
platform,
|
|
136
|
+
// This controller migrates the legacy service layout; the new local-home
|
|
137
|
+
// standard is handled by the storage migration controller.
|
|
138
|
+
requestedLocalHomeMode: "legacy",
|
|
139
|
+
});
|
|
129
140
|
if (!isAbsolute(message.targetDaemonHome) || !isAbsolute(message.targetAgentsRoot)
|
|
130
141
|
|| resolve(message.targetDaemonHome) !== resolve(target.daemonHome)
|
|
131
142
|
|| resolve(message.targetAgentsRoot) !== resolve(target.agentsRoot))
|
|
@@ -140,7 +151,7 @@ export function createDaemonMigrationController(input) {
|
|
|
140
151
|
throw new Error("current managed service identity is not exact and running");
|
|
141
152
|
currentService = { installed: exact.installed, running: exact.running };
|
|
142
153
|
if (platform === "darwin" || platform === "linux") {
|
|
143
|
-
const records = await (input.readManagedRecords ?? (() => readManagedServiceRegistry({ userHome })))();
|
|
154
|
+
const records = await (input.readManagedRecords ?? (() => readManagedServiceRegistry({ userHome, localHome: legacyLocalHome(input) })))();
|
|
144
155
|
managedRecord = records.find((record) => record.serviceId === currentSpec.id && record.profile === input.profileName && record.daemonHome === input.currentDaemonHome) ?? null;
|
|
145
156
|
if (managedRecord === null)
|
|
146
157
|
throw new Error("current managed service registry record is missing");
|
|
@@ -9,6 +9,7 @@ import { daemonGlobalInstallation } from "./daemon-installation.js";
|
|
|
9
9
|
import { daemonInstallationLeaseHeld } from "./daemon-installation-lease.js";
|
|
10
10
|
import { inspectLegacyServiceInstallation } from "./legacy-service-installation.js";
|
|
11
11
|
import { inspectWindowsServiceUpdateScope } from "./windows-scheduled-task.js";
|
|
12
|
+
import { resolveDaemonLocalStateRoot } from "./daemon-local-home.js";
|
|
12
13
|
import { assertRegistryCoversDescriptors, listManagedServiceDescriptorPaths, managedServiceIdentityMatches, readManagedServiceRegistry, serviceDescriptorSha256, } from "./managed-service-registry.js";
|
|
13
14
|
export async function managedDaemonCapabilities(eligibility) {
|
|
14
15
|
try {
|
|
@@ -20,24 +21,31 @@ export async function managedDaemonCapabilities(eligibility) {
|
|
|
20
21
|
return [];
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
|
-
function defaults() {
|
|
24
|
+
function defaults(profileName) {
|
|
25
|
+
const userHome = homedir();
|
|
26
|
+
const localHome = resolveDaemonLocalStateRoot({
|
|
27
|
+
userHome,
|
|
28
|
+
platform: process.platform,
|
|
29
|
+
explicit: process.env.NOWWORK_LOCAL_HOME,
|
|
30
|
+
mode: process.env.NOWWORK_LOCAL_HOME_MODE ?? process.env.NOWWORK_STORAGE_MODE,
|
|
31
|
+
});
|
|
24
32
|
return {
|
|
25
33
|
platform: process.platform,
|
|
26
|
-
profileHome: daemonHome(),
|
|
27
|
-
userHome
|
|
34
|
+
profileHome: daemonHome(process.env, profileName),
|
|
35
|
+
userHome,
|
|
28
36
|
uid: process.getuid?.(),
|
|
29
37
|
nodePath: process.execPath,
|
|
30
38
|
entryPath: builtDaemonEntry(),
|
|
31
39
|
loadProfile,
|
|
32
|
-
readManagedServices: () => readManagedServiceRegistry(),
|
|
33
|
-
listManagedDescriptorPaths: () => listManagedServiceDescriptorPaths(process.platform,
|
|
40
|
+
readManagedServices: () => readManagedServiceRegistry({ userHome, localHome }),
|
|
41
|
+
listManagedDescriptorPaths: () => listManagedServiceDescriptorPaths(process.platform, userHome),
|
|
34
42
|
readServiceDescriptor,
|
|
35
43
|
serviceStatus,
|
|
36
44
|
assertWritable: (path) => access(path, constants.W_OK),
|
|
37
45
|
installationLeaseHeld: daemonInstallationLeaseHeld,
|
|
38
46
|
installationRealpath: realpathSync.native,
|
|
39
47
|
inspectWindowsServiceUpdateScope,
|
|
40
|
-
inspectLegacyDescriptorInstallation: (descriptorPath) => inspectLegacyServiceInstallation(descriptorPath, process.platform,
|
|
48
|
+
inspectLegacyDescriptorInstallation: (descriptorPath) => inspectLegacyServiceInstallation(descriptorPath, process.platform, userHome, process.getuid?.()),
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
async function registryCoversUpdateScope(services, descriptorPaths, targetDescriptorPath, npmPrefix, inspectLegacyDescriptorInstallation) {
|
|
@@ -57,7 +65,7 @@ async function registryCoversUpdateScope(services, descriptorPaths, targetDescri
|
|
|
57
65
|
return true;
|
|
58
66
|
}
|
|
59
67
|
export async function detectDaemonUpdateEligibility(profileName, overrides = {}) {
|
|
60
|
-
const deps = { ...defaults(), ...overrides };
|
|
68
|
+
const deps = { ...defaults(profileName), ...overrides };
|
|
61
69
|
if (deps.platform !== "darwin" && deps.platform !== "linux" && deps.platform !== "win32") {
|
|
62
70
|
return { eligible: false, reason: "unsupported_platform" };
|
|
63
71
|
}
|