@afffun/codexbot 1.0.71 → 1.0.72
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/bin/codexbot.mjs +8 -3
- package/package.json +1 -1
- package/src/args_service.mjs +94 -0
- package/src/config_service.mjs +40 -0
- package/src/entrypoint.mjs +22 -1
- package/src/install_seed_service.mjs +411 -0
- package/src/install_service.mjs +139 -16
package/bin/codexbot.mjs
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { runCodexbotCli } from '../src/entrypoint.mjs';
|
|
3
|
+
import { formatCliError, runCodexbotCli } from '../src/entrypoint.mjs';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
process.
|
|
5
|
+
try {
|
|
6
|
+
const exitCode = await runCodexbotCli(process.argv.slice(2));
|
|
7
|
+
process.exit(exitCode);
|
|
8
|
+
} catch (err) {
|
|
9
|
+
process.stderr.write(`${formatCliError(err)}\n`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
package/package.json
CHANGED
package/src/args_service.mjs
CHANGED
|
@@ -12,6 +12,7 @@ function readOptionValue(argv, index, name) {
|
|
|
12
12
|
function parseBootstrapOptions(argv = []) {
|
|
13
13
|
const options = {
|
|
14
14
|
channel: '',
|
|
15
|
+
installBaseUrl: '',
|
|
15
16
|
updateBaseUrl: '',
|
|
16
17
|
manifestUrl: '',
|
|
17
18
|
publicKeyUrl: '',
|
|
@@ -20,6 +21,21 @@ function parseBootstrapOptions(argv = []) {
|
|
|
20
21
|
token: '',
|
|
21
22
|
allowHttp: false,
|
|
22
23
|
noSignature: false,
|
|
24
|
+
nonInteractive: false,
|
|
25
|
+
connector: '',
|
|
26
|
+
telegramBotToken: '',
|
|
27
|
+
telegramChatIds: '',
|
|
28
|
+
wecomBotId: '',
|
|
29
|
+
wecomSecret: '',
|
|
30
|
+
wecomTencentDocsApiKey: '',
|
|
31
|
+
openAiApiKey: '',
|
|
32
|
+
deferLicenseActivation: false,
|
|
33
|
+
licenseFile: '',
|
|
34
|
+
licensePublicKeyFile: '',
|
|
35
|
+
licenseServerPublicKeyFile: '',
|
|
36
|
+
seedEnvFile: '',
|
|
37
|
+
seedConnectorsStateFile: '',
|
|
38
|
+
seedConnectorsCredentialsFile: '',
|
|
23
39
|
forwardedArgs: [],
|
|
24
40
|
};
|
|
25
41
|
|
|
@@ -31,6 +47,11 @@ function parseBootstrapOptions(argv = []) {
|
|
|
31
47
|
i += 1;
|
|
32
48
|
continue;
|
|
33
49
|
}
|
|
50
|
+
if (token === '--install-base-url') {
|
|
51
|
+
options.installBaseUrl = readOptionValue(argv, i, token);
|
|
52
|
+
i += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
34
55
|
if (token === '--update-base-url') {
|
|
35
56
|
options.updateBaseUrl = readOptionValue(argv, i, token);
|
|
36
57
|
i += 1;
|
|
@@ -66,6 +87,79 @@ function parseBootstrapOptions(argv = []) {
|
|
|
66
87
|
options.forwardedArgs.push(token);
|
|
67
88
|
continue;
|
|
68
89
|
}
|
|
90
|
+
if (token === '--non-interactive' || token === '--no-prompt') {
|
|
91
|
+
options.nonInteractive = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (token === '--connector') {
|
|
95
|
+
options.connector = readOptionValue(argv, i, token);
|
|
96
|
+
i += 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (token === '--telegram-bot-token') {
|
|
100
|
+
options.telegramBotToken = readOptionValue(argv, i, token);
|
|
101
|
+
i += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (token === '--telegram-chat-ids') {
|
|
105
|
+
options.telegramChatIds = readOptionValue(argv, i, token);
|
|
106
|
+
i += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (token === '--wecom-bot-id') {
|
|
110
|
+
options.wecomBotId = readOptionValue(argv, i, token);
|
|
111
|
+
i += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (token === '--wecom-secret') {
|
|
115
|
+
options.wecomSecret = readOptionValue(argv, i, token);
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (token === '--wecom-tencent-docs-api-key') {
|
|
120
|
+
options.wecomTencentDocsApiKey = readOptionValue(argv, i, token);
|
|
121
|
+
i += 1;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (token === '--openai-api-key') {
|
|
125
|
+
options.openAiApiKey = readOptionValue(argv, i, token);
|
|
126
|
+
i += 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (token === '--defer-license-activation') {
|
|
130
|
+
options.deferLicenseActivation = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (token === '--license-file') {
|
|
134
|
+
options.licenseFile = readOptionValue(argv, i, token);
|
|
135
|
+
i += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (token === '--license-public-key-file') {
|
|
139
|
+
options.licensePublicKeyFile = readOptionValue(argv, i, token);
|
|
140
|
+
i += 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (token === '--license-server-public-key-file') {
|
|
144
|
+
options.licenseServerPublicKeyFile = readOptionValue(argv, i, token);
|
|
145
|
+
i += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (token === '--seed-env-file') {
|
|
149
|
+
options.seedEnvFile = readOptionValue(argv, i, token);
|
|
150
|
+
i += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (token === '--seed-connectors-state-file') {
|
|
154
|
+
options.seedConnectorsStateFile = readOptionValue(argv, i, token);
|
|
155
|
+
i += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (token === '--seed-connectors-credentials-file') {
|
|
159
|
+
options.seedConnectorsCredentialsFile = readOptionValue(argv, i, token);
|
|
160
|
+
i += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
69
163
|
if (token === '--no-signature') {
|
|
70
164
|
options.noSignature = true;
|
|
71
165
|
options.forwardedArgs.push(token);
|
package/src/config_service.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const DEFAULT_INSTALL_BASE_URL = 'https://codexbotinstall.afffun.com';
|
|
1
2
|
const DEFAULT_UPDATE_BASE_URL = 'https://codexbotupdate.afffun.com';
|
|
2
3
|
const DEFAULT_CHANNEL = 'stable';
|
|
3
4
|
|
|
@@ -28,12 +29,51 @@ function assertHttps(value, { allowHttp = false, label = 'url' } = {}) {
|
|
|
28
29
|
|
|
29
30
|
export function getDefaultDistributionConfig(env = process.env) {
|
|
30
31
|
return {
|
|
32
|
+
installBaseUrl: normalizeBaseUrl(env.CODEXBOT_INSTALL_BASE_URL, DEFAULT_INSTALL_BASE_URL),
|
|
31
33
|
updateBaseUrl: normalizeBaseUrl(env.CODEXBOT_UPDATE_BASE_URL, DEFAULT_UPDATE_BASE_URL),
|
|
32
34
|
channel: normalizeChannel(env.CODEXBOT_INSTALL_CHANNEL, DEFAULT_CHANNEL),
|
|
33
35
|
authToken: trim(env.CODEXBOT_UPDATE_TOKEN || env.CODEX_REMOTE_UPDATE_AUTH_TOKEN, ''),
|
|
34
36
|
};
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
export function buildPublicInstallUrls({
|
|
40
|
+
installBaseUrl = DEFAULT_INSTALL_BASE_URL,
|
|
41
|
+
updateBaseUrl = DEFAULT_UPDATE_BASE_URL,
|
|
42
|
+
channel = DEFAULT_CHANNEL,
|
|
43
|
+
manifestUrl = '',
|
|
44
|
+
publicKeyUrl = '',
|
|
45
|
+
installRemoteUrl = '',
|
|
46
|
+
runtimeManifestUrl = '',
|
|
47
|
+
packageDownloadBaseUrl = '',
|
|
48
|
+
allowHttp = false,
|
|
49
|
+
} = {}) {
|
|
50
|
+
const resolvedInstallBaseUrl = normalizeBaseUrl(installBaseUrl, DEFAULT_INSTALL_BASE_URL);
|
|
51
|
+
const resolvedUpdateBaseUrl = normalizeBaseUrl(updateBaseUrl, DEFAULT_UPDATE_BASE_URL);
|
|
52
|
+
const resolvedChannel = normalizeChannel(channel, DEFAULT_CHANNEL);
|
|
53
|
+
const publicRoot = `${resolvedInstallBaseUrl}/v1/install/${resolvedChannel}`;
|
|
54
|
+
const updateRoot = `${resolvedUpdateBaseUrl}/v1/channels/${resolvedChannel}`;
|
|
55
|
+
const urls = {
|
|
56
|
+
installBaseUrl: resolvedInstallBaseUrl,
|
|
57
|
+
updateBaseUrl: resolvedUpdateBaseUrl,
|
|
58
|
+
channel: resolvedChannel,
|
|
59
|
+
manifestUrl: trim(manifestUrl) || `${publicRoot}/manifest.json`,
|
|
60
|
+
publicKeyUrl: trim(publicKeyUrl) || `${publicRoot}/packages/latest/update-public.pem`,
|
|
61
|
+
installRemoteUrl: trim(installRemoteUrl) || `${publicRoot}/packages/latest/install-remote.sh`,
|
|
62
|
+
runtimeManifestUrl: trim(runtimeManifestUrl) || `${updateRoot}/manifest.json`,
|
|
63
|
+
packageDownloadBaseUrl: trim(packageDownloadBaseUrl) || `${publicRoot}/packages`,
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
installBaseUrl: assertHttps(urls.installBaseUrl, { allowHttp, label: 'install base url' }),
|
|
67
|
+
updateBaseUrl: assertHttps(urls.updateBaseUrl, { allowHttp, label: 'update base url' }),
|
|
68
|
+
channel: urls.channel,
|
|
69
|
+
manifestUrl: assertHttps(urls.manifestUrl, { allowHttp, label: 'manifest url' }),
|
|
70
|
+
publicKeyUrl: assertHttps(urls.publicKeyUrl, { allowHttp, label: 'public key url' }),
|
|
71
|
+
installRemoteUrl: assertHttps(urls.installRemoteUrl, { allowHttp, label: 'install-remote url' }),
|
|
72
|
+
runtimeManifestUrl: assertHttps(urls.runtimeManifestUrl, { allowHttp, label: 'runtime manifest url' }),
|
|
73
|
+
packageDownloadBaseUrl: assertHttps(urls.packageDownloadBaseUrl, { allowHttp, label: 'package download base url' }),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
37
77
|
export function buildRemoteInstallUrls({
|
|
38
78
|
updateBaseUrl = DEFAULT_UPDATE_BASE_URL,
|
|
39
79
|
channel = DEFAULT_CHANNEL,
|
package/src/entrypoint.mjs
CHANGED
|
@@ -22,12 +22,27 @@ function renderUsage() {
|
|
|
22
22
|
'',
|
|
23
23
|
'Bootstrap options:',
|
|
24
24
|
' --channel <stable|canary|...>',
|
|
25
|
+
' --install-base-url <https://codexbotinstall.example.com>',
|
|
25
26
|
' --update-base-url <https://codexbotupdate.example.com>',
|
|
27
|
+
' install defaults to an interactive seed wizard unless --non-interactive is used.',
|
|
28
|
+
' --connector <telegram|wecom|skip>',
|
|
29
|
+
' --telegram-bot-token <token>',
|
|
30
|
+
' --telegram-chat-ids <id1,id2,...>',
|
|
31
|
+
' --wecom-bot-id <botId>',
|
|
32
|
+
' --wecom-secret <secret>',
|
|
33
|
+
' --wecom-tencent-docs-api-key <apiKey>',
|
|
34
|
+
' --openai-api-key <key>',
|
|
35
|
+
' --license-file </path/to/license.json>',
|
|
36
|
+
' --license-public-key-file </path/to/license-public.pem>',
|
|
37
|
+
' --license-server-public-key-file </path/to/license-server-public.pem>',
|
|
38
|
+
' --defer-license-activation',
|
|
39
|
+
' --non-interactive',
|
|
40
|
+
' advanced seed flags such as --seed-env-file remain supported for automation',
|
|
26
41
|
' --manifest-url <url>',
|
|
27
42
|
' --public-key-url <url>',
|
|
28
43
|
' --install-remote-url <url>',
|
|
29
44
|
' --public-key-file </path/to/update-public.pem>',
|
|
30
|
-
' --token <update server token>',
|
|
45
|
+
' --token <protected update server token>',
|
|
31
46
|
' --allow-http',
|
|
32
47
|
'',
|
|
33
48
|
'Layout overrides:',
|
|
@@ -76,3 +91,9 @@ export async function runCodexbotCli(argv = [], deps = {}) {
|
|
|
76
91
|
}
|
|
77
92
|
throw new Error(`unsupported action: ${cli.action}`);
|
|
78
93
|
}
|
|
94
|
+
|
|
95
|
+
export function formatCliError(err) {
|
|
96
|
+
const message = String(err?.message || err || '').trim();
|
|
97
|
+
if (!message) return 'codexbot failed';
|
|
98
|
+
return message;
|
|
99
|
+
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import readline from 'node:readline/promises';
|
|
5
|
+
|
|
6
|
+
function trim(value, fallback = '') {
|
|
7
|
+
const text = String(value ?? '').trim();
|
|
8
|
+
return text || String(fallback ?? '').trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeConnectorChoice(value) {
|
|
12
|
+
const raw = trim(value).toLowerCase();
|
|
13
|
+
if (raw === 'telegram') return 'telegram';
|
|
14
|
+
if (raw === 'wecom') return 'wecom';
|
|
15
|
+
if (raw === 'skip' || raw === 'none') return 'skip';
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeList(input) {
|
|
20
|
+
return Array.from(new Set(
|
|
21
|
+
String(input || '')
|
|
22
|
+
.split(/[,\n]/)
|
|
23
|
+
.map((item) => trim(item))
|
|
24
|
+
.filter(Boolean),
|
|
25
|
+
));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeYesNo(input, fallback = false) {
|
|
29
|
+
const raw = trim(input).toLowerCase();
|
|
30
|
+
if (!raw) return Boolean(fallback);
|
|
31
|
+
if (['y', 'yes', '1', 'true'].includes(raw)) return true;
|
|
32
|
+
if (['n', 'no', '0', 'false'].includes(raw)) return false;
|
|
33
|
+
return Boolean(fallback);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function buildConnectorCapabilities(type) {
|
|
37
|
+
if (type === 'telegram') {
|
|
38
|
+
return {
|
|
39
|
+
supportsEdit: true,
|
|
40
|
+
supportsButtons: true,
|
|
41
|
+
supportsAudioIn: true,
|
|
42
|
+
supportsAudioOut: true,
|
|
43
|
+
supportsFileUpload: true,
|
|
44
|
+
supportsStreaming: true,
|
|
45
|
+
supportsWebSocket: false,
|
|
46
|
+
supportsWebhook: true,
|
|
47
|
+
supportsPolling: true,
|
|
48
|
+
supportsOAuth: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (type === 'wecom') {
|
|
52
|
+
return {
|
|
53
|
+
supportsEdit: false,
|
|
54
|
+
supportsButtons: false,
|
|
55
|
+
supportsAudioIn: true,
|
|
56
|
+
supportsAudioOut: false,
|
|
57
|
+
supportsFileUpload: true,
|
|
58
|
+
supportsStreaming: true,
|
|
59
|
+
supportsWebSocket: true,
|
|
60
|
+
supportsWebhook: false,
|
|
61
|
+
supportsPolling: false,
|
|
62
|
+
supportsOAuth: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildTelegramSeed(nowIso, {
|
|
69
|
+
botToken,
|
|
70
|
+
allowedChatIds,
|
|
71
|
+
baseUrl = 'https://api.telegram.org',
|
|
72
|
+
fileBaseUrl = 'https://api.telegram.org/file',
|
|
73
|
+
} = {}) {
|
|
74
|
+
const connectorId = 'telegram-primary';
|
|
75
|
+
const configured = Boolean(trim(botToken) && allowedChatIds.length > 0);
|
|
76
|
+
const updatedAt = nowIso();
|
|
77
|
+
return {
|
|
78
|
+
connectorId,
|
|
79
|
+
descriptor: {
|
|
80
|
+
connectorId,
|
|
81
|
+
type: 'telegram',
|
|
82
|
+
name: connectorId,
|
|
83
|
+
displayName: 'Telegram',
|
|
84
|
+
source: 'managed',
|
|
85
|
+
managed: true,
|
|
86
|
+
status: configured ? 'authorized' : 'draft',
|
|
87
|
+
mode: 'polling',
|
|
88
|
+
capabilities: buildConnectorCapabilities('telegram'),
|
|
89
|
+
config: {
|
|
90
|
+
baseUrl,
|
|
91
|
+
fileBaseUrl,
|
|
92
|
+
},
|
|
93
|
+
binding: {
|
|
94
|
+
allowedChats: allowedChatIds,
|
|
95
|
+
},
|
|
96
|
+
runtime: {
|
|
97
|
+
desiredEnabled: true,
|
|
98
|
+
configured,
|
|
99
|
+
authModes: ['bot_token'],
|
|
100
|
+
},
|
|
101
|
+
health: {
|
|
102
|
+
state: configured ? 'idle' : 'misconfigured',
|
|
103
|
+
lastError: configured ? '' : 'missing telegram bot token or allowed chats',
|
|
104
|
+
},
|
|
105
|
+
auth: {
|
|
106
|
+
supportedModes: ['bot_token'],
|
|
107
|
+
mode: configured ? 'bot_token' : '',
|
|
108
|
+
state: configured ? 'configured' : 'unconfigured',
|
|
109
|
+
configured,
|
|
110
|
+
credentialRef: `cred_${connectorId}`,
|
|
111
|
+
storedKeys: configured ? ['botToken'] : [],
|
|
112
|
+
redirectUri: '',
|
|
113
|
+
authorizeUrl: '',
|
|
114
|
+
scopes: [],
|
|
115
|
+
tokenExchangeConfigured: false,
|
|
116
|
+
hasAccessToken: false,
|
|
117
|
+
hasRefreshToken: false,
|
|
118
|
+
lastAuthorizedAt: configured ? updatedAt : '',
|
|
119
|
+
lastError: configured ? '' : 'missing telegram bot token or allowed chats',
|
|
120
|
+
lastErrorAt: configured ? '' : updatedAt,
|
|
121
|
+
},
|
|
122
|
+
createdAt: updatedAt,
|
|
123
|
+
updatedAt,
|
|
124
|
+
},
|
|
125
|
+
credentials: {
|
|
126
|
+
connectorId,
|
|
127
|
+
credentialRef: `cred_${connectorId}`,
|
|
128
|
+
authMode: 'bot_token',
|
|
129
|
+
values: configured ? { botToken: trim(botToken) } : {},
|
|
130
|
+
oauth: {},
|
|
131
|
+
updatedAt,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildWecomSeed(nowIso, {
|
|
137
|
+
botId,
|
|
138
|
+
secret,
|
|
139
|
+
tencentDocsApiKey = '',
|
|
140
|
+
websocketUrl = 'wss://openws.work.weixin.qq.com',
|
|
141
|
+
} = {}) {
|
|
142
|
+
const connectorId = 'wecom-primary';
|
|
143
|
+
const configured = Boolean(trim(botId) && trim(secret));
|
|
144
|
+
const updatedAt = nowIso();
|
|
145
|
+
const storedKeys = [];
|
|
146
|
+
if (trim(botId)) storedKeys.push('botId');
|
|
147
|
+
if (trim(secret)) storedKeys.push('secret');
|
|
148
|
+
if (trim(tencentDocsApiKey)) storedKeys.push('tencentDocsApiKey');
|
|
149
|
+
return {
|
|
150
|
+
connectorId,
|
|
151
|
+
descriptor: {
|
|
152
|
+
connectorId,
|
|
153
|
+
type: 'wecom',
|
|
154
|
+
name: connectorId,
|
|
155
|
+
displayName: 'WeCom',
|
|
156
|
+
source: 'managed',
|
|
157
|
+
managed: true,
|
|
158
|
+
status: configured ? 'authorized' : 'draft',
|
|
159
|
+
mode: 'websocket',
|
|
160
|
+
capabilities: buildConnectorCapabilities('wecom'),
|
|
161
|
+
config: {
|
|
162
|
+
botId: trim(botId),
|
|
163
|
+
websocketUrl,
|
|
164
|
+
dmPolicy: 'open',
|
|
165
|
+
groupPolicy: 'open',
|
|
166
|
+
sendThinkingMessage: false,
|
|
167
|
+
tencentDocsEnabled: Boolean(trim(tencentDocsApiKey)),
|
|
168
|
+
},
|
|
169
|
+
binding: {},
|
|
170
|
+
runtime: {
|
|
171
|
+
desiredEnabled: true,
|
|
172
|
+
configured,
|
|
173
|
+
authModes: ['bot_secret'],
|
|
174
|
+
},
|
|
175
|
+
health: {
|
|
176
|
+
state: configured ? 'idle' : 'misconfigured',
|
|
177
|
+
lastError: configured ? '' : 'missing fields: botId, secret',
|
|
178
|
+
},
|
|
179
|
+
auth: {
|
|
180
|
+
supportedModes: ['bot_secret'],
|
|
181
|
+
mode: configured ? 'bot_secret' : '',
|
|
182
|
+
state: configured ? 'configured' : 'unconfigured',
|
|
183
|
+
configured,
|
|
184
|
+
credentialRef: `cred_${connectorId}`,
|
|
185
|
+
storedKeys,
|
|
186
|
+
redirectUri: '',
|
|
187
|
+
authorizeUrl: '',
|
|
188
|
+
scopes: [],
|
|
189
|
+
tokenExchangeConfigured: false,
|
|
190
|
+
hasAccessToken: false,
|
|
191
|
+
hasRefreshToken: false,
|
|
192
|
+
lastAuthorizedAt: configured ? updatedAt : '',
|
|
193
|
+
lastError: configured ? '' : 'missing fields: botId, secret',
|
|
194
|
+
lastErrorAt: configured ? '' : updatedAt,
|
|
195
|
+
},
|
|
196
|
+
createdAt: updatedAt,
|
|
197
|
+
updatedAt,
|
|
198
|
+
},
|
|
199
|
+
credentials: {
|
|
200
|
+
connectorId,
|
|
201
|
+
credentialRef: `cred_${connectorId}`,
|
|
202
|
+
authMode: 'bot_secret',
|
|
203
|
+
values: {
|
|
204
|
+
...(trim(botId) ? { botId: trim(botId) } : {}),
|
|
205
|
+
...(trim(secret) ? { secret: trim(secret) } : {}),
|
|
206
|
+
...(trim(tencentDocsApiKey) ? { tencentDocsApiKey: trim(tencentDocsApiKey) } : {}),
|
|
207
|
+
},
|
|
208
|
+
oauth: {},
|
|
209
|
+
updatedAt,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function createPrompter({ stdin, stdout } = {}) {
|
|
215
|
+
const input = stdin || process.stdin;
|
|
216
|
+
const output = stdout || process.stdout;
|
|
217
|
+
if (!input?.isTTY || !output?.isTTY) {
|
|
218
|
+
return {
|
|
219
|
+
interactive: false,
|
|
220
|
+
ask: async () => '',
|
|
221
|
+
close: async () => {},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const rl = readline.createInterface({ input, output });
|
|
225
|
+
return {
|
|
226
|
+
interactive: true,
|
|
227
|
+
ask: async (prompt) => trim(await rl.question(prompt)),
|
|
228
|
+
close: async () => {
|
|
229
|
+
rl.close();
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function ensureReadableFile(fsPromises, filePath, label) {
|
|
235
|
+
const target = trim(filePath);
|
|
236
|
+
if (!target) return '';
|
|
237
|
+
try {
|
|
238
|
+
await fsPromises.access(target);
|
|
239
|
+
} catch {
|
|
240
|
+
throw new Error(`${label} not found: ${target}`);
|
|
241
|
+
}
|
|
242
|
+
return target;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function renderEnvFile(values = {}) {
|
|
246
|
+
const lines = Object.entries(values)
|
|
247
|
+
.filter(([, value]) => trim(value))
|
|
248
|
+
.map(([key, value]) => `${key}=${String(value)}`);
|
|
249
|
+
return lines.length ? `${lines.join('\n')}\n` : '';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function createNpmDistributionInstallSeedService(deps = {}) {
|
|
253
|
+
const fsPromises = deps.fsPromises || fs;
|
|
254
|
+
const osModule = deps.osModule || os;
|
|
255
|
+
const pathModule = deps.pathModule || path;
|
|
256
|
+
const processImpl = deps.processImpl || process;
|
|
257
|
+
const logger = deps.logger || console;
|
|
258
|
+
const nowIso = deps.nowIso || (() => new Date().toISOString());
|
|
259
|
+
const promptFactory = deps.promptFactory || createPrompter;
|
|
260
|
+
|
|
261
|
+
async function prepareInstallSeed(options = {}) {
|
|
262
|
+
const explicitSeedFiles = Boolean(
|
|
263
|
+
trim(options.seedEnvFile)
|
|
264
|
+
|| trim(options.seedConnectorsStateFile)
|
|
265
|
+
|| trim(options.seedConnectorsCredentialsFile),
|
|
266
|
+
);
|
|
267
|
+
const prompter = promptFactory({
|
|
268
|
+
stdin: processImpl.stdin,
|
|
269
|
+
stdout: processImpl.stdout,
|
|
270
|
+
});
|
|
271
|
+
const nonInteractive = Boolean(options.nonInteractive) || !prompter.interactive;
|
|
272
|
+
const tempDir = await fsPromises.mkdtemp(pathModule.join(osModule.tmpdir(), 'codexbot-install-seed-'));
|
|
273
|
+
const cleanup = async () => {
|
|
274
|
+
await prompter.close().catch(() => {});
|
|
275
|
+
await fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const result = {
|
|
280
|
+
seedEnvFile: trim(options.seedEnvFile),
|
|
281
|
+
seedConnectorsStateFile: trim(options.seedConnectorsStateFile),
|
|
282
|
+
seedConnectorsCredentialsFile: trim(options.seedConnectorsCredentialsFile),
|
|
283
|
+
seedLicenseFile: '',
|
|
284
|
+
seedLicensePublicKeyFile: '',
|
|
285
|
+
seedLicenseServerPublicKeyFile: '',
|
|
286
|
+
deferLicenseActivation: Boolean(options.deferLicenseActivation),
|
|
287
|
+
summary: {
|
|
288
|
+
connector: 'skip',
|
|
289
|
+
connectorSeeded: false,
|
|
290
|
+
openAiSeeded: false,
|
|
291
|
+
licenseDeferred: false,
|
|
292
|
+
},
|
|
293
|
+
cleanup,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
if (explicitSeedFiles) {
|
|
297
|
+
result.seedLicenseFile = trim(options.licenseFile);
|
|
298
|
+
result.seedLicensePublicKeyFile = trim(options.licensePublicKeyFile);
|
|
299
|
+
result.seedLicenseServerPublicKeyFile = trim(options.licenseServerPublicKeyFile);
|
|
300
|
+
if (!result.deferLicenseActivation
|
|
301
|
+
&& !result.seedLicenseFile
|
|
302
|
+
&& !result.seedLicensePublicKeyFile
|
|
303
|
+
&& !result.seedLicenseServerPublicKeyFile) {
|
|
304
|
+
result.deferLicenseActivation = true;
|
|
305
|
+
}
|
|
306
|
+
result.summary.licenseDeferred = result.deferLicenseActivation;
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let connector = normalizeConnectorChoice(options.connector);
|
|
311
|
+
const hasExplicitConnectorArgs = Boolean(
|
|
312
|
+
connector
|
|
313
|
+
|| trim(options.telegramBotToken)
|
|
314
|
+
|| trim(options.telegramChatIds)
|
|
315
|
+
|| trim(options.wecomBotId)
|
|
316
|
+
|| trim(options.wecomSecret)
|
|
317
|
+
|| trim(options.wecomTencentDocsApiKey),
|
|
318
|
+
);
|
|
319
|
+
if (!connector && hasExplicitConnectorArgs) {
|
|
320
|
+
connector = trim(options.wecomBotId || options.wecomSecret || options.wecomTencentDocsApiKey) ? 'wecom' : 'telegram';
|
|
321
|
+
}
|
|
322
|
+
if (!connector && !nonInteractive) {
|
|
323
|
+
logger.log('==> Remote control channel setup');
|
|
324
|
+
connector = normalizeConnectorChoice(await prompter.ask('Choose connector [telegram/wecom/skip] (default: skip): ')) || 'skip';
|
|
325
|
+
}
|
|
326
|
+
if (!connector) connector = 'skip';
|
|
327
|
+
|
|
328
|
+
const openAiApiKey = trim(options.openAiApiKey || (!nonInteractive ? await prompter.ask('OPENAI_API_KEY (optional, blank to skip): ') : ''));
|
|
329
|
+
if (openAiApiKey) {
|
|
330
|
+
const envFile = pathModule.join(tempDir, 'seed.env');
|
|
331
|
+
await fsPromises.writeFile(envFile, renderEnvFile({ OPENAI_API_KEY: openAiApiKey }), 'utf8');
|
|
332
|
+
result.seedEnvFile = envFile;
|
|
333
|
+
result.summary.openAiSeeded = true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (connector === 'telegram') {
|
|
337
|
+
const telegramBotToken = trim(options.telegramBotToken || (!nonInteractive ? await prompter.ask('Telegram bot token: ') : ''));
|
|
338
|
+
const telegramChatIds = normalizeList(options.telegramChatIds || (!nonInteractive ? await prompter.ask('Telegram allowed chat IDs (comma-separated): ') : ''));
|
|
339
|
+
if (!telegramBotToken || telegramChatIds.length === 0) {
|
|
340
|
+
throw new Error('Telegram connector requires bot token and at least one allowed chat ID');
|
|
341
|
+
}
|
|
342
|
+
const seed = buildTelegramSeed(nowIso, {
|
|
343
|
+
botToken: telegramBotToken,
|
|
344
|
+
allowedChatIds: telegramChatIds,
|
|
345
|
+
});
|
|
346
|
+
const stateFile = pathModule.join(tempDir, 'connectors.json');
|
|
347
|
+
const credentialsFile = pathModule.join(tempDir, 'connectors.credentials.json');
|
|
348
|
+
await fsPromises.writeFile(stateFile, `${JSON.stringify({ connectors: { [seed.connectorId]: seed.descriptor } }, null, 2)}\n`, 'utf8');
|
|
349
|
+
await fsPromises.writeFile(credentialsFile, `${JSON.stringify({ connectors: { [seed.connectorId]: seed.credentials } }, null, 2)}\n`, 'utf8');
|
|
350
|
+
result.seedConnectorsStateFile = stateFile;
|
|
351
|
+
result.seedConnectorsCredentialsFile = credentialsFile;
|
|
352
|
+
result.summary.connector = 'telegram';
|
|
353
|
+
result.summary.connectorSeeded = true;
|
|
354
|
+
} else if (connector === 'wecom') {
|
|
355
|
+
const wecomBotId = trim(options.wecomBotId || (!nonInteractive ? await prompter.ask('WeCom bot ID: ') : ''));
|
|
356
|
+
const wecomSecret = trim(options.wecomSecret || (!nonInteractive ? await prompter.ask('WeCom bot secret: ') : ''));
|
|
357
|
+
const wecomTencentDocsApiKey = trim(options.wecomTencentDocsApiKey || (!nonInteractive ? await prompter.ask('Tencent Docs API key (optional, blank to skip): ') : ''));
|
|
358
|
+
if (!wecomBotId || !wecomSecret) {
|
|
359
|
+
throw new Error('WeCom connector requires bot ID and secret');
|
|
360
|
+
}
|
|
361
|
+
const seed = buildWecomSeed(nowIso, {
|
|
362
|
+
botId: wecomBotId,
|
|
363
|
+
secret: wecomSecret,
|
|
364
|
+
tencentDocsApiKey: wecomTencentDocsApiKey,
|
|
365
|
+
});
|
|
366
|
+
const stateFile = pathModule.join(tempDir, 'connectors.json');
|
|
367
|
+
const credentialsFile = pathModule.join(tempDir, 'connectors.credentials.json');
|
|
368
|
+
await fsPromises.writeFile(stateFile, `${JSON.stringify({ connectors: { [seed.connectorId]: seed.descriptor } }, null, 2)}\n`, 'utf8');
|
|
369
|
+
await fsPromises.writeFile(credentialsFile, `${JSON.stringify({ connectors: { [seed.connectorId]: seed.credentials } }, null, 2)}\n`, 'utf8');
|
|
370
|
+
result.seedConnectorsStateFile = stateFile;
|
|
371
|
+
result.seedConnectorsCredentialsFile = credentialsFile;
|
|
372
|
+
result.summary.connector = 'wecom';
|
|
373
|
+
result.summary.connectorSeeded = true;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let licenseFile = trim(options.licenseFile);
|
|
377
|
+
let licensePublicKeyFile = trim(options.licensePublicKeyFile);
|
|
378
|
+
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) {
|
|
389
|
+
result.deferLicenseActivation = true;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const licenseProvided = Boolean(licenseFile || licensePublicKeyFile || licenseServerPublicKeyFile);
|
|
393
|
+
if (licenseProvided) {
|
|
394
|
+
result.seedLicenseFile = await ensureReadableFile(fsPromises, licenseFile, 'license file');
|
|
395
|
+
result.seedLicensePublicKeyFile = await ensureReadableFile(fsPromises, licensePublicKeyFile, 'license public key file');
|
|
396
|
+
result.seedLicenseServerPublicKeyFile = await ensureReadableFile(fsPromises, licenseServerPublicKeyFile, 'license server public key file');
|
|
397
|
+
result.deferLicenseActivation = false;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
result.summary.licenseDeferred = result.deferLicenseActivation;
|
|
401
|
+
return result;
|
|
402
|
+
} catch (error) {
|
|
403
|
+
await cleanup();
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
prepareInstallSeed,
|
|
410
|
+
};
|
|
411
|
+
}
|
package/src/install_service.mjs
CHANGED
|
@@ -2,16 +2,79 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
buildPublicInstallUrls,
|
|
7
|
+
buildRemoteInstallUrls,
|
|
8
|
+
getDefaultDistributionConfig,
|
|
9
|
+
} from './config_service.mjs';
|
|
10
|
+
import { createNpmDistributionInstallSeedService } from './install_seed_service.mjs';
|
|
6
11
|
|
|
7
12
|
function trim(value, fallback = '') {
|
|
8
13
|
const text = String(value ?? '').trim();
|
|
9
14
|
return text || String(fallback ?? '').trim();
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
function buildAuthFailureMessage({
|
|
18
|
+
label,
|
|
19
|
+
status,
|
|
20
|
+
statusText,
|
|
21
|
+
installBaseUrl,
|
|
22
|
+
channel,
|
|
23
|
+
updateBaseUrl,
|
|
24
|
+
authTokenPresent,
|
|
25
|
+
distributionMode = 'upgrade',
|
|
26
|
+
} = {}) {
|
|
27
|
+
const lines = [
|
|
28
|
+
`${label} download failed: ${status} ${statusText}`,
|
|
29
|
+
];
|
|
30
|
+
if (status === 401 || status === 403) {
|
|
31
|
+
lines.push('');
|
|
32
|
+
if (distributionMode === 'install') {
|
|
33
|
+
lines.push('The public Codexbot install facade rejected this request or could not proxy it upstream.');
|
|
34
|
+
lines.push('Retry with one of the following:');
|
|
35
|
+
lines.push(`- codexbot install --channel ${channel || 'stable'}`);
|
|
36
|
+
lines.push(`- codexbot install --install-base-url '${installBaseUrl || 'https://codexbotinstall.afffun.com'}' --update-base-url '${updateBaseUrl || 'https://codexbotupdate.afffun.com'}'`);
|
|
37
|
+
lines.push('- Verify the install facade is configured with upstream access to the protected update server.');
|
|
38
|
+
if (authTokenPresent) {
|
|
39
|
+
lines.push('- A token was provided, but public install should normally not require it; verify the install facade is not pointing at a protected route.');
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
lines.push('The Codexbot npm package is public, but the protected update channel still requires an update token.');
|
|
43
|
+
lines.push('Retry with one of the following:');
|
|
44
|
+
lines.push(`- codexbot upgrade --channel ${channel || 'stable'} --token '<UPDATE_SERVER_AUTH_TOKEN>'`);
|
|
45
|
+
lines.push(`- export CODEXBOT_UPDATE_TOKEN='<UPDATE_SERVER_AUTH_TOKEN>' && codexbot upgrade --channel ${channel || 'stable'}`);
|
|
46
|
+
lines.push(`- target update base: ${updateBaseUrl || 'https://codexbotupdate.afffun.com'}`);
|
|
47
|
+
if (authTokenPresent) {
|
|
48
|
+
lines.push('- A token was provided, but the server rejected it. Check that the token is valid for this update channel.');
|
|
49
|
+
} else {
|
|
50
|
+
lines.push('- No update token was provided for this request.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function ensureOkResponse(response, {
|
|
58
|
+
label,
|
|
59
|
+
installBaseUrl,
|
|
60
|
+
channel,
|
|
61
|
+
updateBaseUrl,
|
|
62
|
+
authTokenPresent = false,
|
|
63
|
+
distributionMode = 'upgrade',
|
|
64
|
+
} = {}) {
|
|
13
65
|
if (!response.ok) {
|
|
14
|
-
|
|
66
|
+
const error = new Error(buildAuthFailureMessage({
|
|
67
|
+
label,
|
|
68
|
+
status: response.status,
|
|
69
|
+
statusText: response.statusText,
|
|
70
|
+
installBaseUrl,
|
|
71
|
+
channel,
|
|
72
|
+
updateBaseUrl,
|
|
73
|
+
authTokenPresent,
|
|
74
|
+
distributionMode,
|
|
75
|
+
}));
|
|
76
|
+
error.code = `HTTP_${response.status}`;
|
|
77
|
+
throw error;
|
|
15
78
|
}
|
|
16
79
|
return response;
|
|
17
80
|
}
|
|
@@ -46,53 +109,113 @@ export function createNpmDistributionInstallService(deps = {}) {
|
|
|
46
109
|
const spawnFn = deps.spawnFn || spawn;
|
|
47
110
|
const processImpl = deps.processImpl || process;
|
|
48
111
|
const logger = deps.logger || console;
|
|
112
|
+
const installSeedService = deps.installSeedService || createNpmDistributionInstallSeedService({
|
|
113
|
+
fsPromises,
|
|
114
|
+
osModule,
|
|
115
|
+
pathModule,
|
|
116
|
+
processImpl,
|
|
117
|
+
logger,
|
|
118
|
+
});
|
|
49
119
|
|
|
50
120
|
async function runRemoteInstall({
|
|
51
121
|
mode,
|
|
52
122
|
options = {},
|
|
53
123
|
} = {}) {
|
|
54
124
|
const defaults = getDefaultDistributionConfig(processImpl.env);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
125
|
+
const distributionMode = trim(mode, 'auto') === 'install' ? 'install' : 'upgrade';
|
|
126
|
+
const urls = distributionMode === 'install'
|
|
127
|
+
? buildPublicInstallUrls({
|
|
128
|
+
installBaseUrl: options.installBaseUrl || defaults.installBaseUrl,
|
|
129
|
+
updateBaseUrl: options.updateBaseUrl || defaults.updateBaseUrl,
|
|
130
|
+
channel: options.channel || defaults.channel,
|
|
131
|
+
manifestUrl: options.manifestUrl,
|
|
132
|
+
publicKeyUrl: options.publicKeyUrl,
|
|
133
|
+
installRemoteUrl: options.installRemoteUrl,
|
|
134
|
+
allowHttp: Boolean(options.allowHttp),
|
|
135
|
+
})
|
|
136
|
+
: buildRemoteInstallUrls({
|
|
137
|
+
updateBaseUrl: options.updateBaseUrl || defaults.updateBaseUrl,
|
|
138
|
+
channel: options.channel || defaults.channel,
|
|
139
|
+
manifestUrl: options.manifestUrl,
|
|
140
|
+
publicKeyUrl: options.publicKeyUrl,
|
|
141
|
+
installRemoteUrl: options.installRemoteUrl,
|
|
142
|
+
allowHttp: Boolean(options.allowHttp),
|
|
143
|
+
});
|
|
144
|
+
let installSeed = null;
|
|
145
|
+
const effectiveOptions = { ...options };
|
|
146
|
+
if (distributionMode === 'install') {
|
|
147
|
+
installSeed = await installSeedService.prepareInstallSeed(options);
|
|
148
|
+
effectiveOptions.seedEnvFile = trim(installSeed.seedEnvFile, effectiveOptions.seedEnvFile);
|
|
149
|
+
effectiveOptions.seedConnectorsStateFile = trim(installSeed.seedConnectorsStateFile, effectiveOptions.seedConnectorsStateFile);
|
|
150
|
+
effectiveOptions.seedConnectorsCredentialsFile = trim(installSeed.seedConnectorsCredentialsFile, effectiveOptions.seedConnectorsCredentialsFile);
|
|
151
|
+
effectiveOptions.licenseFile = trim(installSeed.seedLicenseFile, effectiveOptions.licenseFile);
|
|
152
|
+
effectiveOptions.licensePublicKeyFile = trim(installSeed.seedLicensePublicKeyFile, effectiveOptions.licensePublicKeyFile);
|
|
153
|
+
effectiveOptions.licenseServerPublicKeyFile = trim(installSeed.seedLicenseServerPublicKeyFile, effectiveOptions.licenseServerPublicKeyFile);
|
|
154
|
+
effectiveOptions.deferLicenseActivation = Boolean(installSeed.deferLicenseActivation || effectiveOptions.deferLicenseActivation);
|
|
155
|
+
}
|
|
156
|
+
const authToken = trim(effectiveOptions.token, defaults.authToken);
|
|
64
157
|
const tempDir = await fsPromises.mkdtemp(pathModule.join(osModule.tmpdir(), 'codexbot-npm-'));
|
|
65
158
|
const tempInstallPath = pathModule.join(tempDir, 'install-remote.sh');
|
|
66
159
|
const tempKeyPath = pathModule.join(tempDir, 'update-public.pem');
|
|
67
160
|
const headers = authToken ? { Authorization: `Bearer ${authToken}` } : {};
|
|
68
161
|
try {
|
|
69
162
|
logger.log(`==> Fetch install entry from ${urls.installRemoteUrl}`);
|
|
70
|
-
const installResponse = await ensureOkResponse(await fetchFn(urls.installRemoteUrl, { headers }),
|
|
163
|
+
const installResponse = await ensureOkResponse(await fetchFn(urls.installRemoteUrl, { headers }), {
|
|
164
|
+
label: 'install-remote',
|
|
165
|
+
installBaseUrl: urls.installBaseUrl,
|
|
166
|
+
channel: urls.channel,
|
|
167
|
+
updateBaseUrl: urls.updateBaseUrl,
|
|
168
|
+
authTokenPresent: Boolean(authToken),
|
|
169
|
+
distributionMode,
|
|
170
|
+
});
|
|
71
171
|
await writeResponseToFile(installResponse, tempInstallPath, fsPromises);
|
|
72
172
|
await fsPromises.chmod(tempInstallPath, 0o755);
|
|
73
173
|
|
|
74
|
-
const publicKeyFile = trim(
|
|
174
|
+
const publicKeyFile = trim(effectiveOptions.publicKeyFile, '');
|
|
75
175
|
const resolvedPublicKeyFile = publicKeyFile || tempKeyPath;
|
|
76
176
|
if (!publicKeyFile) {
|
|
77
177
|
logger.log(`==> Fetch update public key from ${urls.publicKeyUrl}`);
|
|
78
|
-
const keyResponse = await ensureOkResponse(await fetchFn(urls.publicKeyUrl, { headers }),
|
|
178
|
+
const keyResponse = await ensureOkResponse(await fetchFn(urls.publicKeyUrl, { headers }), {
|
|
179
|
+
label: 'update public key',
|
|
180
|
+
installBaseUrl: urls.installBaseUrl,
|
|
181
|
+
channel: urls.channel,
|
|
182
|
+
updateBaseUrl: urls.updateBaseUrl,
|
|
183
|
+
authTokenPresent: Boolean(authToken),
|
|
184
|
+
distributionMode,
|
|
185
|
+
});
|
|
79
186
|
await writeResponseToFile(keyResponse, tempKeyPath, fsPromises);
|
|
80
187
|
}
|
|
81
188
|
|
|
82
189
|
const args = [
|
|
83
190
|
tempInstallPath,
|
|
84
191
|
'--manifest-url', urls.manifestUrl,
|
|
192
|
+
...(distributionMode === 'install'
|
|
193
|
+
? ['--runtime-remote-update-manifest-url', urls.runtimeManifestUrl]
|
|
194
|
+
: []),
|
|
195
|
+
...(distributionMode === 'install'
|
|
196
|
+
? ['--package-download-base-url', urls.packageDownloadBaseUrl]
|
|
197
|
+
: []),
|
|
85
198
|
'--public-key-file', resolvedPublicKeyFile,
|
|
86
199
|
'--channel', urls.channel,
|
|
87
200
|
'--mode', trim(mode, 'auto'),
|
|
88
201
|
...(authToken ? ['--auth-token', authToken] : []),
|
|
89
|
-
...(
|
|
202
|
+
...(effectiveOptions.deferLicenseActivation ? ['--defer-license-activation'] : []),
|
|
203
|
+
...(trim(effectiveOptions.seedEnvFile) ? ['--seed-env-file', trim(effectiveOptions.seedEnvFile)] : []),
|
|
204
|
+
...(trim(effectiveOptions.seedConnectorsStateFile) ? ['--seed-connectors-state-file', trim(effectiveOptions.seedConnectorsStateFile)] : []),
|
|
205
|
+
...(trim(effectiveOptions.seedConnectorsCredentialsFile) ? ['--seed-connectors-credentials-file', trim(effectiveOptions.seedConnectorsCredentialsFile)] : []),
|
|
206
|
+
...(trim(effectiveOptions.licenseFile) ? ['--seed-license-file', trim(effectiveOptions.licenseFile)] : []),
|
|
207
|
+
...(trim(effectiveOptions.licensePublicKeyFile) ? ['--seed-license-public-key-file', trim(effectiveOptions.licensePublicKeyFile)] : []),
|
|
208
|
+
...(trim(effectiveOptions.licenseServerPublicKeyFile) ? ['--seed-license-server-public-key-file', trim(effectiveOptions.licenseServerPublicKeyFile)] : []),
|
|
209
|
+
...(Array.isArray(effectiveOptions.forwardedArgs) ? effectiveOptions.forwardedArgs : []),
|
|
90
210
|
];
|
|
91
211
|
const isRoot = typeof processImpl.getuid === 'function' ? processImpl.getuid() === 0 : false;
|
|
92
212
|
const command = isRoot ? 'bash' : 'sudo';
|
|
93
213
|
const commandArgs = isRoot ? args : ['bash', ...args];
|
|
94
214
|
return await runChild(spawnFn, command, commandArgs);
|
|
95
215
|
} finally {
|
|
216
|
+
if (installSeed?.cleanup) {
|
|
217
|
+
await installSeed.cleanup().catch(() => {});
|
|
218
|
+
}
|
|
96
219
|
await fsPromises.rm(tempDir, { recursive: true, force: true }).catch(() => {});
|
|
97
220
|
}
|
|
98
221
|
}
|