@afffun/codexbot 1.0.72 → 1.0.74

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afffun/codexbot",
3
- "version": "1.0.72",
3
+ "version": "1.0.74",
4
4
  "description": "Thin npm bootstrap CLI for installing and operating Codexbot nodes",
5
5
  "type": "module",
6
6
  "author": "john88188 <john88188@outlook.com>",
@@ -0,0 +1,262 @@
1
+ import fsSyncDefault from 'node:fs';
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ import readline from 'node:readline/promises';
7
+
8
+ import { buildInstallLicenseClaimUrl, getDefaultDistributionConfig } from './config_service.mjs';
9
+ import { resolveInstalledControlLayout } from './installed_layout_service.mjs';
10
+
11
+ function trim(value, fallback = '') {
12
+ const text = String(value ?? '').trim();
13
+ return text || String(fallback ?? '').trim();
14
+ }
15
+
16
+ function createPrompter({ stdin, stdout } = {}) {
17
+ const input = stdin || process.stdin;
18
+ const output = stdout || process.stdout;
19
+ if (!input?.isTTY || !output?.isTTY) {
20
+ return {
21
+ interactive: false,
22
+ ask: async () => '',
23
+ close: async () => {},
24
+ };
25
+ }
26
+ const rl = readline.createInterface({ input, output });
27
+ return {
28
+ interactive: true,
29
+ ask: async (prompt) => trim(await rl.question(prompt)),
30
+ close: async () => {
31
+ rl.close();
32
+ },
33
+ };
34
+ }
35
+
36
+ function runChildCapture(spawnFn, command, args, options = {}) {
37
+ return new Promise((resolve, reject) => {
38
+ const child = spawnFn(command, args, {
39
+ stdio: ['ignore', 'pipe', 'pipe'],
40
+ ...options,
41
+ });
42
+ let stdout = '';
43
+ let stderr = '';
44
+ child.stdout?.on('data', (chunk) => {
45
+ stdout += Buffer.from(chunk).toString('utf8');
46
+ });
47
+ child.stderr?.on('data', (chunk) => {
48
+ stderr += Buffer.from(chunk).toString('utf8');
49
+ });
50
+ child.on('error', reject);
51
+ child.on('exit', (code, signal) => {
52
+ if (signal) {
53
+ reject(new Error(`${command} exited via signal ${signal}`));
54
+ return;
55
+ }
56
+ resolve({ code: Number(code || 0), stdout, stderr });
57
+ });
58
+ });
59
+ }
60
+
61
+ function runChildInherit(spawnFn, command, args, options = {}) {
62
+ return new Promise((resolve, reject) => {
63
+ const child = spawnFn(command, args, {
64
+ stdio: 'inherit',
65
+ ...options,
66
+ });
67
+ child.on('error', reject);
68
+ child.on('exit', (code, signal) => {
69
+ if (signal) {
70
+ reject(new Error(`${command} exited via signal ${signal}`));
71
+ return;
72
+ }
73
+ resolve(Number(code || 0));
74
+ });
75
+ });
76
+ }
77
+
78
+ function resolveControlLayout({ fsSync, pathModule, processImpl, options }) {
79
+ return resolveInstalledControlLayout({
80
+ fsSync,
81
+ pathModule,
82
+ env: processImpl.env,
83
+ profile: options.layoutProfile,
84
+ appDir: options.appDir,
85
+ controlEnvFile: options.controlEnvFile,
86
+ controlUser: options.controlUser,
87
+ codexHomeDir: options.codexHomeDir,
88
+ });
89
+ }
90
+
91
+ async function readJsonResponse(response) {
92
+ const text = await response.text();
93
+ try {
94
+ return text ? JSON.parse(text) : {};
95
+ } catch {
96
+ return {};
97
+ }
98
+ }
99
+
100
+ function buildClaimFailureMessage(error, claimUrl) {
101
+ const lines = [trim(error?.message, 'license claim failed')];
102
+ lines.push('');
103
+ lines.push('The node remains installed but activation is still pending.');
104
+ lines.push(`Retry with: codexbot activate --license-key '<your-license-key>' --license-claim-url '${claimUrl}'`);
105
+ lines.push('Or complete activation manually after obtaining an activation package.');
106
+ return lines.join('\n');
107
+ }
108
+
109
+ export function createNpmDistributionActivationService(deps = {}) {
110
+ const fsPromises = deps.fsPromises || fs;
111
+ const fsSync = deps.fsSync || fsSyncDefault;
112
+ const osModule = deps.osModule || os;
113
+ const pathModule = deps.pathModule || path;
114
+ const spawnFn = deps.spawnFn || spawn;
115
+ const processImpl = deps.processImpl || process;
116
+ const fetchFn = deps.fetchFn || fetch;
117
+ const logger = deps.logger || console;
118
+ const promptFactory = deps.promptFactory || createPrompter;
119
+
120
+ async function resolveLicenseKey(options = {}) {
121
+ const explicit = trim(options.licenseKey);
122
+ if (explicit) return explicit;
123
+ const prompter = promptFactory({
124
+ stdin: processImpl.stdin,
125
+ stdout: processImpl.stdout,
126
+ });
127
+ try {
128
+ if (Boolean(options.nonInteractive) || !prompter.interactive) {
129
+ throw new Error('license key is required');
130
+ }
131
+ const entered = trim(await prompter.ask('License key: '));
132
+ if (!entered) throw new Error('license key is required');
133
+ return entered;
134
+ } finally {
135
+ await prompter.close().catch(() => {});
136
+ }
137
+ }
138
+
139
+ async function readInstalledNodeIdentity(layout, options = {}) {
140
+ const scriptPath = pathModule.join(layout.appDir, 'scripts', 'codexbot_node_identity.sh');
141
+ const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
142
+ const command = isRoot ? 'bash' : 'sudo';
143
+ const commandArgs = isRoot
144
+ ? [scriptPath, '--control-env-file', layout.controlEnvFile]
145
+ : ['bash', scriptPath, '--control-env-file', layout.controlEnvFile];
146
+ const result = await runChildCapture(spawnFn, command, commandArgs);
147
+ if (result.code !== 0) {
148
+ throw new Error(trim(result.stderr || result.stdout, 'failed to read node identity'));
149
+ }
150
+ let payload = {};
151
+ try {
152
+ payload = JSON.parse(result.stdout);
153
+ } catch {
154
+ throw new Error('node identity output is not valid JSON');
155
+ }
156
+ return payload;
157
+ }
158
+
159
+ async function fetchActivationPackage({ claimUrl, licenseKey, identity }) {
160
+ const response = await fetchFn(claimUrl, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'content-type': 'application/json',
164
+ 'user-agent': 'codexbot-npm-cli/1.0',
165
+ },
166
+ body: JSON.stringify({
167
+ licenseKey,
168
+ fingerprint: trim(identity?.machineFingerprint),
169
+ node: {
170
+ id: trim(identity?.nodeId),
171
+ ...(identity?.payload && typeof identity.payload === 'object' && !Array.isArray(identity.payload) ? identity.payload : {}),
172
+ },
173
+ }),
174
+ });
175
+ const payload = await readJsonResponse(response);
176
+ if (!response.ok) {
177
+ const err = new Error(trim(payload?.error?.message || payload?.message, `license claim http ${response.status}`));
178
+ err.code = trim(payload?.error?.code || payload?.code, `HTTP_${response.status}`);
179
+ err.statusCode = response.status;
180
+ err.payload = payload;
181
+ throw err;
182
+ }
183
+ return payload;
184
+ }
185
+
186
+ async function writeActivationPackage(tempDir, activation = {}) {
187
+ const licenseFile = pathModule.join(tempDir, 'license.json');
188
+ const licensePublicKeyFile = pathModule.join(tempDir, 'license-public.pem');
189
+ const licenseServerPublicKeyFile = pathModule.join(tempDir, 'license-server-public.pem');
190
+ await fsPromises.writeFile(licenseFile, `${JSON.stringify(activation.license || {}, null, 2)}\n`, 'utf8');
191
+ await fsPromises.writeFile(licensePublicKeyFile, `${trim(activation.licensePublicKey)}\n`, 'utf8');
192
+ await fsPromises.writeFile(licenseServerPublicKeyFile, `${trim(activation.licenseServerPublicKey)}\n`, 'utf8');
193
+ return {
194
+ licenseFile,
195
+ licensePublicKeyFile,
196
+ licenseServerPublicKeyFile,
197
+ };
198
+ }
199
+
200
+ async function runLocalActivationScript(layout, files) {
201
+ const scriptPath = pathModule.join(layout.appDir, 'scripts', 'codexbot_activate.sh');
202
+ const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
203
+ const baseArgs = [
204
+ scriptPath,
205
+ '--control-env-file', layout.controlEnvFile,
206
+ '--license-file', files.licenseFile,
207
+ '--license-public-key-file', files.licensePublicKeyFile,
208
+ '--license-server-public-key-file', files.licenseServerPublicKeyFile,
209
+ '--start',
210
+ ];
211
+ const command = isRoot ? 'bash' : 'sudo';
212
+ const commandArgs = isRoot ? baseArgs : ['bash', ...baseArgs];
213
+ return runChildInherit(spawnFn, command, commandArgs);
214
+ }
215
+
216
+ async function claimAndActivate(options = {}) {
217
+ const defaults = getDefaultDistributionConfig(processImpl.env);
218
+ const licenseKey = await resolveLicenseKey(options);
219
+ const claimUrl = buildInstallLicenseClaimUrl({
220
+ installBaseUrl: options.installBaseUrl || defaults.installBaseUrl,
221
+ claimUrl: options.licenseClaimUrl || defaults.licenseClaimUrl,
222
+ allowHttp: Boolean(options.allowHttp),
223
+ });
224
+ const layout = resolveControlLayout({
225
+ fsSync,
226
+ pathModule,
227
+ processImpl,
228
+ options,
229
+ });
230
+ const tempDir = await fsPromises.mkdtemp(pathModule.join(osModule.tmpdir(), 'codexbot-activation-'));
231
+ try {
232
+ logger.log('==> Read node identity');
233
+ const identity = await readInstalledNodeIdentity(layout, options);
234
+ logger.log(`==> Claim activation package from ${claimUrl}`);
235
+ const claim = await fetchActivationPackage({ claimUrl, licenseKey, identity });
236
+ const activation = claim?.activation || {};
237
+ if (!activation.license || !trim(activation.licensePublicKey) || !trim(activation.licenseServerPublicKey)) {
238
+ throw new Error('claim response is missing activation package files');
239
+ }
240
+ const files = await writeActivationPackage(tempDir, activation);
241
+ logger.log('==> Activate installed node');
242
+ const exitCode = await runLocalActivationScript(layout, files);
243
+ if (exitCode !== 0) {
244
+ throw new Error(`activation script exited with code ${exitCode}`);
245
+ }
246
+ return {
247
+ ok: true,
248
+ claim,
249
+ };
250
+ } catch (error) {
251
+ const wrapped = new Error(buildClaimFailureMessage(error, claimUrl));
252
+ wrapped.cause = error;
253
+ throw wrapped;
254
+ } finally {
255
+ await fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
256
+ }
257
+ }
258
+
259
+ return {
260
+ claimAndActivate,
261
+ };
262
+ }
@@ -30,6 +30,8 @@ function parseBootstrapOptions(argv = []) {
30
30
  wecomTencentDocsApiKey: '',
31
31
  openAiApiKey: '',
32
32
  deferLicenseActivation: false,
33
+ licenseKey: '',
34
+ licenseClaimUrl: '',
33
35
  licenseFile: '',
34
36
  licensePublicKeyFile: '',
35
37
  licenseServerPublicKeyFile: '',
@@ -130,6 +132,16 @@ function parseBootstrapOptions(argv = []) {
130
132
  options.deferLicenseActivation = true;
131
133
  continue;
132
134
  }
135
+ if (token === '--license-key') {
136
+ options.licenseKey = readOptionValue(argv, i, token);
137
+ i += 1;
138
+ continue;
139
+ }
140
+ if (token === '--license-claim-url') {
141
+ options.licenseClaimUrl = readOptionValue(argv, i, token);
142
+ i += 1;
143
+ continue;
144
+ }
133
145
  if (token === '--license-file') {
134
146
  options.licenseFile = readOptionValue(argv, i, token);
135
147
  i += 1;
@@ -171,7 +183,7 @@ function parseBootstrapOptions(argv = []) {
171
183
  return options;
172
184
  }
173
185
 
174
- function parseAuthOptions(argv = []) {
186
+ function parseLayoutOptions(argv = []) {
175
187
  const options = {
176
188
  layoutProfile: '',
177
189
  appDir: '',
@@ -212,6 +224,71 @@ function parseAuthOptions(argv = []) {
212
224
  return options;
213
225
  }
214
226
 
227
+ function parseActivationOptions(argv = []) {
228
+ const options = {
229
+ ...parseLayoutOptions([]),
230
+ installBaseUrl: '',
231
+ licenseClaimUrl: '',
232
+ allowHttp: false,
233
+ nonInteractive: false,
234
+ licenseKey: '',
235
+ };
236
+ for (let i = 0; i < argv.length; i += 1) {
237
+ const token = trim(argv[i]);
238
+ if (!token) continue;
239
+ if (token === '--license-key') {
240
+ options.licenseKey = readOptionValue(argv, i, token);
241
+ i += 1;
242
+ continue;
243
+ }
244
+ if (token === '--install-base-url') {
245
+ options.installBaseUrl = readOptionValue(argv, i, token);
246
+ i += 1;
247
+ continue;
248
+ }
249
+ if (token === '--license-claim-url') {
250
+ options.licenseClaimUrl = readOptionValue(argv, i, token);
251
+ i += 1;
252
+ continue;
253
+ }
254
+ if (token === '--allow-http') {
255
+ options.allowHttp = true;
256
+ continue;
257
+ }
258
+ if (token === '--non-interactive' || token === '--no-prompt') {
259
+ options.nonInteractive = true;
260
+ continue;
261
+ }
262
+ if (token === '--layout-profile') {
263
+ options.layoutProfile = readOptionValue(argv, i, token);
264
+ i += 1;
265
+ continue;
266
+ }
267
+ if (token === '--app-dir') {
268
+ options.appDir = readOptionValue(argv, i, token);
269
+ i += 1;
270
+ continue;
271
+ }
272
+ if (token === '--control-env-file') {
273
+ options.controlEnvFile = readOptionValue(argv, i, token);
274
+ i += 1;
275
+ continue;
276
+ }
277
+ if (token === '--control-user') {
278
+ options.controlUser = readOptionValue(argv, i, token);
279
+ i += 1;
280
+ continue;
281
+ }
282
+ if (token === '--codex-home-dir') {
283
+ options.codexHomeDir = readOptionValue(argv, i, token);
284
+ i += 1;
285
+ continue;
286
+ }
287
+ throw new Error(`unknown activate arg: ${token}`);
288
+ }
289
+ return options;
290
+ }
291
+
215
292
  export function parseCliArgs(argv = []) {
216
293
  const args = Array.isArray(argv) ? argv.map((item) => String(item || '')) : [];
217
294
  const command = trim(args[0]).toLowerCase();
@@ -222,7 +299,7 @@ export function parseCliArgs(argv = []) {
222
299
  return { action: 'version' };
223
300
  }
224
301
  if (command === 'doctor') {
225
- return { action: 'doctor', options: parseAuthOptions(args.slice(1)) };
302
+ return { action: 'doctor', options: parseLayoutOptions(args.slice(1)) };
226
303
  }
227
304
  if (command === 'install') {
228
305
  return { action: 'install', options: parseBootstrapOptions(args.slice(1)) };
@@ -230,12 +307,15 @@ export function parseCliArgs(argv = []) {
230
307
  if (command === 'upgrade') {
231
308
  return { action: 'upgrade', options: parseBootstrapOptions(args.slice(1)) };
232
309
  }
310
+ if (command === 'activate') {
311
+ return { action: 'activate', options: parseActivationOptions(args.slice(1)) };
312
+ }
233
313
  if (command === 'auth') {
234
314
  const sub = trim(args[1]).toLowerCase() || 'status';
235
315
  if (!['status', 'login', 'login-link', 'logout'].includes(sub)) {
236
316
  throw new Error(`unknown auth subcommand: ${sub}`);
237
317
  }
238
- return { action: 'auth', mode: sub, options: parseAuthOptions(args.slice(2)) };
318
+ return { action: 'auth', mode: sub, options: parseLayoutOptions(args.slice(2)) };
239
319
  }
240
320
  throw new Error(`unknown command: ${command}`);
241
321
  }
@@ -33,9 +33,20 @@ export function getDefaultDistributionConfig(env = process.env) {
33
33
  updateBaseUrl: normalizeBaseUrl(env.CODEXBOT_UPDATE_BASE_URL, DEFAULT_UPDATE_BASE_URL),
34
34
  channel: normalizeChannel(env.CODEXBOT_INSTALL_CHANNEL, DEFAULT_CHANNEL),
35
35
  authToken: trim(env.CODEXBOT_UPDATE_TOKEN || env.CODEX_REMOTE_UPDATE_AUTH_TOKEN, ''),
36
+ licenseClaimUrl: trim(env.CODEXBOT_LICENSE_CLAIM_URL, ''),
36
37
  };
37
38
  }
38
39
 
40
+ export function buildInstallLicenseClaimUrl({
41
+ installBaseUrl = DEFAULT_INSTALL_BASE_URL,
42
+ claimUrl = '',
43
+ allowHttp = false,
44
+ } = {}) {
45
+ const resolvedInstallBaseUrl = normalizeBaseUrl(installBaseUrl, DEFAULT_INSTALL_BASE_URL);
46
+ const resolvedClaimUrl = trim(claimUrl) || `${resolvedInstallBaseUrl}/v1/install/license/claim`;
47
+ return assertHttps(resolvedClaimUrl, { allowHttp, label: 'license claim url' });
48
+ }
49
+
39
50
  export function buildPublicInstallUrls({
40
51
  installBaseUrl = DEFAULT_INSTALL_BASE_URL,
41
52
  updateBaseUrl = DEFAULT_UPDATE_BASE_URL,
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { parseCliArgs } from './args_service.mjs';
4
+ import { createNpmDistributionActivationService } from './activation_service.mjs';
4
5
  import { createNpmDistributionInstallService } from './install_service.mjs';
5
6
  import { createNpmDistributionAuthService } from './auth_service.mjs';
6
7
 
@@ -16,6 +17,7 @@ function renderUsage() {
16
17
  'Usage:',
17
18
  ' codexbot install [bootstrap options] [install-remote flags...]',
18
19
  ' codexbot upgrade [bootstrap options] [install-remote flags...]',
20
+ ' codexbot activate [activation options]',
19
21
  ' codexbot auth status|login|login-link|logout [layout overrides]',
20
22
  ' codexbot doctor [layout overrides]',
21
23
  ' codexbot version',
@@ -32,6 +34,8 @@ function renderUsage() {
32
34
  ' --wecom-secret <secret>',
33
35
  ' --wecom-tencent-docs-api-key <apiKey>',
34
36
  ' --openai-api-key <key>',
37
+ ' --license-key <key>',
38
+ ' install will auto-claim and activate when a license key is provided.',
35
39
  ' --license-file </path/to/license.json>',
36
40
  ' --license-public-key-file </path/to/license-public.pem>',
37
41
  ' --license-server-public-key-file </path/to/license-server-public.pem>',
@@ -45,6 +49,13 @@ function renderUsage() {
45
49
  ' --token <protected update server token>',
46
50
  ' --allow-http',
47
51
  '',
52
+ 'Activation options:',
53
+ ' --license-key <key>',
54
+ ' --install-base-url <https://codexbotinstall.example.com>',
55
+ ' --license-claim-url <https://codexbotinstall.example.com/v1/install/license/claim>',
56
+ ' --non-interactive',
57
+ ' --allow-http',
58
+ '',
48
59
  'Layout overrides:',
49
60
  ' --layout-profile <split|legacy>',
50
61
  ' --app-dir <path>',
@@ -57,6 +68,7 @@ function renderUsage() {
57
68
  export async function runCodexbotCli(argv = [], deps = {}) {
58
69
  const logger = deps.logger || console;
59
70
  const installService = deps.installService || createNpmDistributionInstallService({ logger });
71
+ const activationService = deps.activationService || createNpmDistributionActivationService({ logger });
60
72
  const authService = deps.authService || createNpmDistributionAuthService({ logger });
61
73
  const cli = parseCliArgs(argv);
62
74
  if (cli.action === 'help') {
@@ -86,6 +98,10 @@ export async function runCodexbotCli(argv = [], deps = {}) {
86
98
  if (cli.action === 'upgrade') {
87
99
  return installService.runUpgradeCommand(cli.options || {});
88
100
  }
101
+ if (cli.action === 'activate') {
102
+ await activationService.claimAndActivate(cli.options || {});
103
+ return 0;
104
+ }
89
105
  if (cli.action === 'auth') {
90
106
  return authService.runAuthCommand(cli.mode, cli.options || {});
91
107
  }
@@ -283,6 +283,7 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
283
283
  seedLicenseFile: '',
284
284
  seedLicensePublicKeyFile: '',
285
285
  seedLicenseServerPublicKeyFile: '',
286
+ licenseKey: trim(options.licenseKey),
286
287
  deferLicenseActivation: Boolean(options.deferLicenseActivation),
287
288
  summary: {
288
289
  connector: 'skip',
@@ -294,6 +295,9 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
294
295
  };
295
296
 
296
297
  if (explicitSeedFiles) {
298
+ if (result.licenseKey) {
299
+ throw new Error('license key cannot be combined with explicit license seed files');
300
+ }
297
301
  result.seedLicenseFile = trim(options.licenseFile);
298
302
  result.seedLicensePublicKeyFile = trim(options.licensePublicKeyFile);
299
303
  result.seedLicenseServerPublicKeyFile = trim(options.licenseServerPublicKeyFile);
@@ -373,22 +377,24 @@ export function createNpmDistributionInstallSeedService(deps = {}) {
373
377
  result.summary.connectorSeeded = true;
374
378
  }
375
379
 
380
+ let licenseKey = result.licenseKey;
376
381
  let licenseFile = trim(options.licenseFile);
377
382
  let licensePublicKeyFile = trim(options.licensePublicKeyFile);
378
383
  let licenseServerPublicKeyFile = trim(options.licenseServerPublicKeyFile);
379
- if (!result.deferLicenseActivation && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile && !nonInteractive) {
380
- const provideLicense = normalizeYesNo(await prompter.ask('Provide license materials now? [y/N]: '), false);
381
- if (provideLicense) {
382
- licenseFile = await prompter.ask('Path to license.json: ');
383
- licensePublicKeyFile = await prompter.ask('Path to license-public.pem: ');
384
- licenseServerPublicKeyFile = await prompter.ask('Path to license-server-public.pem: ');
385
- } else {
386
- result.deferLicenseActivation = true;
387
- }
388
- } else if (!licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile) {
384
+ if (licenseKey && (licenseFile || licensePublicKeyFile || licenseServerPublicKeyFile)) {
385
+ throw new Error('license key cannot be combined with direct license file inputs');
386
+ }
387
+ if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile && !nonInteractive) {
388
+ licenseKey = trim(await prompter.ask('License key (optional, blank to skip and activate later): '));
389
+ }
390
+ if (!licenseKey && !licenseFile && !licensePublicKeyFile && !licenseServerPublicKeyFile) {
389
391
  result.deferLicenseActivation = true;
390
392
  }
391
393
 
394
+ if (licenseKey) {
395
+ result.licenseKey = licenseKey;
396
+ result.deferLicenseActivation = true;
397
+ }
392
398
  const licenseProvided = Boolean(licenseFile || licensePublicKeyFile || licenseServerPublicKeyFile);
393
399
  if (licenseProvided) {
394
400
  result.seedLicenseFile = await ensureReadableFile(fsPromises, licenseFile, 'license file');
@@ -7,6 +7,7 @@ import {
7
7
  buildRemoteInstallUrls,
8
8
  getDefaultDistributionConfig,
9
9
  } from './config_service.mjs';
10
+ import { createNpmDistributionActivationService } from './activation_service.mjs';
10
11
  import { createNpmDistributionInstallSeedService } from './install_seed_service.mjs';
11
12
 
12
13
  function trim(value, fallback = '') {
@@ -116,6 +117,14 @@ export function createNpmDistributionInstallService(deps = {}) {
116
117
  processImpl,
117
118
  logger,
118
119
  });
120
+ const activationService = deps.activationService || createNpmDistributionActivationService({
121
+ fsPromises,
122
+ pathModule,
123
+ spawnFn,
124
+ processImpl,
125
+ fetchFn,
126
+ logger,
127
+ });
119
128
 
120
129
  async function runRemoteInstall({
121
130
  mode,
@@ -151,6 +160,7 @@ export function createNpmDistributionInstallService(deps = {}) {
151
160
  effectiveOptions.licenseFile = trim(installSeed.seedLicenseFile, effectiveOptions.licenseFile);
152
161
  effectiveOptions.licensePublicKeyFile = trim(installSeed.seedLicensePublicKeyFile, effectiveOptions.licensePublicKeyFile);
153
162
  effectiveOptions.licenseServerPublicKeyFile = trim(installSeed.seedLicenseServerPublicKeyFile, effectiveOptions.licenseServerPublicKeyFile);
163
+ effectiveOptions.licenseKey = trim(installSeed.licenseKey, effectiveOptions.licenseKey);
154
164
  effectiveOptions.deferLicenseActivation = Boolean(installSeed.deferLicenseActivation || effectiveOptions.deferLicenseActivation);
155
165
  }
156
166
  const authToken = trim(effectiveOptions.token, defaults.authToken);
@@ -211,7 +221,17 @@ export function createNpmDistributionInstallService(deps = {}) {
211
221
  const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
212
222
  const command = isRoot ? 'bash' : 'sudo';
213
223
  const commandArgs = isRoot ? args : ['bash', ...args];
214
- return await runChild(spawnFn, command, commandArgs);
224
+ const exitCode = await runChild(spawnFn, command, commandArgs);
225
+ if (distributionMode === 'install' && exitCode === 0 && trim(effectiveOptions.licenseKey)) {
226
+ await activationService.claimAndActivate({
227
+ licenseKey: effectiveOptions.licenseKey,
228
+ installBaseUrl: urls.installBaseUrl,
229
+ licenseClaimUrl: effectiveOptions.licenseClaimUrl,
230
+ allowHttp: Boolean(effectiveOptions.allowHttp),
231
+ nonInteractive: true,
232
+ });
233
+ }
234
+ return exitCode;
215
235
  } finally {
216
236
  if (installSeed?.cleanup) {
217
237
  await installSeed.cleanup().catch(() => {});