openship 0.6.1 → 0.6.6
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 +7 -2
- package/dist/{bare-IWS2QHJ3.js → bare-OXJVW3AK.js} +11 -9
- package/dist/{chunk-VZ2ZHPS2.js → chunk-2YM3NI2E.js} +3 -9
- package/dist/chunk-4I2BM2DT.js +68 -0
- package/dist/{chunk-2NZSUXOB.js → chunk-5DMWSTOM.js} +310 -1459
- package/dist/{chunk-3FYIXVX5.js → chunk-77QDF6UD.js} +581 -122
- package/dist/{chunk-IOT5ZX23.js → chunk-A4GXYE4L.js} +449 -383
- package/dist/chunk-CBLD2HS5.js +428 -0
- package/dist/chunk-ELGRMZOW.js +25 -0
- package/dist/{chunk-TWJCLD4W.js → chunk-FE7ZZQSE.js} +5 -5
- package/dist/{chunk-3NZICQHF.js → chunk-HKRQXPTI.js} +31 -8
- package/dist/{chunk-IXM5245F.js → chunk-LFE5KQ7Y.js} +553 -538
- package/dist/{chunk-VDDMADOF.js → chunk-LH2PLRQO.js} +16 -2
- package/dist/{chunk-EMXI4UCU.js → chunk-LUKYPVJ4.js} +29 -10
- package/dist/{chunk-UC7YIEHT.js → chunk-MLYSEZQB.js} +352 -47
- package/dist/chunk-NK5N5LXN.js +1681 -0
- package/dist/{chunk-RRJJGVKE.js → chunk-OBPKYXQL.js} +75 -17
- package/dist/{chunk-6ODUBKUB.js → chunk-PCPVTH6M.js} +78 -88
- package/dist/chunk-Q7WGTQQ5.js +88 -0
- package/dist/{chunk-EW6KB2J7.js → chunk-QVW76WDL.js} +440 -74
- package/dist/{chunk-YIPMDGG6.js → chunk-RSR7PFIQ.js} +79 -23
- package/dist/{chunk-3LDWWY7U.js → chunk-SM26HDS2.js} +41 -7
- package/dist/{chunk-OTLSJYAC.js → chunk-UJ6JZM65.js} +5185 -341
- package/dist/chunk-UVNMQM7W.js +444 -0
- package/dist/chunk-VTGPEC6B.js +308 -0
- package/dist/chunk-WVAML4H6.js +2145 -0
- package/dist/{chunk-JIMUYEKN.js → chunk-YIMOCZBZ.js} +1 -1
- package/dist/chunk-YMMQRE5J.js +292 -0
- package/dist/cloud-75VIT4KV.js +37 -0
- package/dist/{detect-ALF4GXRX.js → detect-OIE5V3VZ.js} +3 -3
- package/dist/{docker-QECQJ5P4.js → docker-VTR2GK5Y.js} +16 -8
- package/dist/{docker-edge-executor-62N64MGS.js → docker-edge-executor-22IBXNYJ.js} +5 -5
- package/dist/{edge-import-SAYTRJLO.js → edge-import-WJUB4M7F.js} +27 -23
- package/dist/edge-preflight-GBNCPNVU.js +42 -0
- package/dist/{ensure-container-edge-CFSCPGZK.js → ensure-container-edge-KPI2CVXJ.js} +9 -8
- package/dist/{executor-PT6UEJ5M.js → executor-7LXXZPBR.js} +15 -5
- package/dist/host-channel-preflight-KE6OONYK.js +50 -0
- package/dist/index.js +3783 -4221
- package/dist/{nginx-YOEEFBTE.js → nginx-KUFMLMAC.js} +13 -5
- package/dist/node-bootstrap.js +241 -0
- package/dist/node-entry.js +52 -1
- package/dist/{openresty-lua-WGBO2M3Z.js → openresty-lua-ATTH35BE.js} +8 -2
- package/dist/privilege-I4W3ZNZK.js +19 -0
- package/dist/server/engine/samples/amavisd/amavisd.conf +25 -0
- package/dist/server/engine/samples/fail2ban/jail.d/sshd.local +4 -1
- package/dist/server/engine/samples/postfix/master.cf +14 -0
- package/dist/server/index.js +77659 -61909
- package/dist/server/migrations/0098_instance_product_mode.sql +12 -0
- package/dist/server/migrations/0099_mail_webmail_project.sql +24 -0
- package/dist/server/migrations/0100_server_ssh_private_key.sql +11 -0
- package/dist/server/migrations/0101_mail_resume_step.sql +12 -0
- package/dist/server/migrations/0102_project_workload_axes.sql +19 -0
- package/dist/server/migrations/0103_dns_credential.sql +41 -0
- package/dist/server/migrations/0104_instance_host_control.sql +15 -0
- package/dist/server/migrations/0105_mail_inbound_rules.sql +80 -0
- package/dist/server/migrations/0106_mcp_call_tracking.sql +23 -0
- package/dist/server/migrations/0107_build_minute_metering_indexes.sql +20 -0
- package/dist/server/migrations/0108_credential.sql +68 -0
- package/dist/server/migrations/meta/_journal.json +77 -0
- package/dist/{setup-M2ZDGH3O.js → setup-XGLGUUFW.js} +10 -9
- package/dist/ssh-client-JBMA7SN4.js +28 -0
- package/dist/up-K4BV7466.js +57 -0
- package/package.json +4 -2
- package/dist/chunk-IW2UKBVF.js +0 -144
- package/dist/chunk-ZCFVHA25.js +0 -89
- package/dist/cloud-M2YLWEXO.js +0 -31
- /package/dist/{dist-es-JGBPABJI.js → dist-es-AXFRS7PS.js} +0 -0
|
@@ -7,56 +7,70 @@ import {
|
|
|
7
7
|
resolveProjectDir,
|
|
8
8
|
resolveStaticOutputPath,
|
|
9
9
|
runLocalBuild
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import {
|
|
12
|
-
resolveEnvironment
|
|
13
|
-
} from "./chunk-IW2UKBVF.js";
|
|
10
|
+
} from "./chunk-YIMOCZBZ.js";
|
|
14
11
|
import {
|
|
15
12
|
BuildCancelledError,
|
|
16
13
|
BuildLogger,
|
|
17
14
|
detectBuildKillHint,
|
|
15
|
+
killProcessesUnderDir,
|
|
18
16
|
parseLogLevel,
|
|
19
17
|
runBuildPipeline,
|
|
20
18
|
sq,
|
|
21
19
|
transferLocalDirectory
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import {
|
|
24
|
-
formatDuration,
|
|
25
|
-
isRemoteConnectionError,
|
|
26
|
-
isRetryableRemoteConnectionError,
|
|
27
|
-
systemDebug
|
|
28
|
-
} from "./chunk-ZCFVHA25.js";
|
|
20
|
+
} from "./chunk-LH2PLRQO.js";
|
|
29
21
|
import {
|
|
30
22
|
LocalExecutor
|
|
31
23
|
} from "./chunk-6FH4AXVY.js";
|
|
32
24
|
import {
|
|
33
|
-
|
|
34
|
-
} from "./chunk-
|
|
25
|
+
privilegedExecutor
|
|
26
|
+
} from "./chunk-4I2BM2DT.js";
|
|
27
|
+
import {
|
|
28
|
+
HOST_STATE_DIR,
|
|
29
|
+
envOps,
|
|
30
|
+
hostRefusal,
|
|
31
|
+
opScript
|
|
32
|
+
} from "./chunk-YMMQRE5J.js";
|
|
33
|
+
import {
|
|
34
|
+
ensureOwnedDir,
|
|
35
|
+
resolveEnvironment
|
|
36
|
+
} from "./chunk-UVNMQM7W.js";
|
|
35
37
|
import {
|
|
36
38
|
sq as sq2,
|
|
37
39
|
wrapLocalBuildCommand
|
|
38
40
|
} from "./chunk-ZFJXPCQP.js";
|
|
41
|
+
import {
|
|
42
|
+
probeExec,
|
|
43
|
+
probeListeningPort,
|
|
44
|
+
withReason
|
|
45
|
+
} from "./chunk-SM26HDS2.js";
|
|
39
46
|
import {
|
|
40
47
|
DeployError,
|
|
41
48
|
LANGUAGES,
|
|
42
49
|
STACKS,
|
|
50
|
+
answered,
|
|
43
51
|
appVolumeTargets,
|
|
44
52
|
buildOutputTransferExcludes,
|
|
53
|
+
formatDuration,
|
|
54
|
+
isRemoteConnectionError,
|
|
55
|
+
isRetryableRemoteConnectionError,
|
|
45
56
|
missingOutputDirectoryMessage,
|
|
46
57
|
packageManagerEnsureCommand,
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
refused,
|
|
59
|
+
safeErrorMessage,
|
|
60
|
+
shellQuote,
|
|
61
|
+
systemDebug
|
|
62
|
+
} from "./chunk-UJ6JZM65.js";
|
|
49
63
|
|
|
50
64
|
// ../../packages/adapters/src/system/remote-journal.ts
|
|
51
65
|
var OPSH_RUN_VERSION = 1;
|
|
52
|
-
var DEFAULT_JOURNAL_BASE =
|
|
66
|
+
var DEFAULT_JOURNAL_BASE = HOST_STATE_DIR;
|
|
53
67
|
var REMOTE_ENV_PREFIX = "export DEBIAN_FRONTEND=noninteractive DPKG_FORCE=confnew && ";
|
|
54
68
|
var OPSH_RUN_SCRIPT = `#!/bin/sh
|
|
55
69
|
# opsh-run \u2014 openship reliable command journal. Managed file; do not edit.
|
|
56
70
|
VERSION=${OPSH_RUN_VERSION}
|
|
57
71
|
|
|
58
72
|
BASE="$OPSH_BASE"
|
|
59
|
-
[ -z "$BASE" ] && BASE
|
|
73
|
+
[ -z "$BASE" ] && BASE=${HOST_STATE_DIR}
|
|
60
74
|
OPS="$BASE/ops"
|
|
61
75
|
|
|
62
76
|
if [ "$1" = "--version" ]; then echo "$VERSION"; exit 0; fi
|
|
@@ -139,6 +153,14 @@ ERR="$(openssl base64 -A < "$DIR/stderr" 2>/dev/null)"
|
|
|
139
153
|
echo "OPSH1 $CODE $OUT $ERR"
|
|
140
154
|
exit 0
|
|
141
155
|
`;
|
|
156
|
+
async function resolveJournalBase(exec) {
|
|
157
|
+
try {
|
|
158
|
+
return envOps(await resolveEnvironment(exec)).scratchDir();
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (isRemoteConnectionError(err)) throw err;
|
|
161
|
+
return DEFAULT_JOURNAL_BASE;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
142
164
|
async function ensureRemoteJournal(exec, baseDir) {
|
|
143
165
|
const runner = `${baseDir}/bin/opsh-run`;
|
|
144
166
|
let current = -1;
|
|
@@ -213,7 +235,7 @@ function sleep(ms) {
|
|
|
213
235
|
});
|
|
214
236
|
}
|
|
215
237
|
async function runReliable(acquire, opId, command, opts) {
|
|
216
|
-
|
|
238
|
+
let baseDir = opts.baseDir ?? null;
|
|
217
239
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_RELIABLE_TIMEOUT_MS;
|
|
218
240
|
const deadline = Date.now() + timeoutMs;
|
|
219
241
|
const minMs = opts.reconnectMinMs ?? DEFAULT_RECONNECT_MIN_MS;
|
|
@@ -235,6 +257,7 @@ async function runReliable(acquire, opId, command, opts) {
|
|
|
235
257
|
continue;
|
|
236
258
|
}
|
|
237
259
|
try {
|
|
260
|
+
baseDir ??= await resolveJournalBase(executor);
|
|
238
261
|
if (!opts.ensured || !opts.ensured.has(executor)) {
|
|
239
262
|
await ensureRemoteJournal(executor, baseDir);
|
|
240
263
|
opts.ensured?.add(executor);
|
|
@@ -290,472 +313,475 @@ async function execReliable(executor, opId, command, opts = {}) {
|
|
|
290
313
|
}
|
|
291
314
|
|
|
292
315
|
// ../../packages/adapters/src/toolchain/catalog.ts
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
316
|
+
var CHECKS = {
|
|
317
|
+
node: {
|
|
318
|
+
label: "Node.js",
|
|
319
|
+
versionCommand: "node --version",
|
|
320
|
+
parseVersion: (output) => output.replace(/^v/, "").trim(),
|
|
321
|
+
missingMessage: "Node.js is not installed",
|
|
322
|
+
installable: true
|
|
323
|
+
},
|
|
324
|
+
bun: {
|
|
325
|
+
label: "Bun",
|
|
326
|
+
versionCommand: "bun --version",
|
|
327
|
+
parseVersion: (output) => output.trim(),
|
|
328
|
+
missingMessage: "Bun is not installed",
|
|
329
|
+
installable: true
|
|
330
|
+
},
|
|
331
|
+
npm: {
|
|
332
|
+
label: "npm",
|
|
333
|
+
versionCommand: "npm --version",
|
|
334
|
+
parseVersion: (output) => output.trim(),
|
|
335
|
+
missingMessage: "npm is not installed",
|
|
336
|
+
installable: false,
|
|
337
|
+
providedBy: "node"
|
|
338
|
+
},
|
|
339
|
+
go: {
|
|
340
|
+
label: "Go",
|
|
341
|
+
versionCommand: "go version",
|
|
342
|
+
parseVersion: (output) => output.match(/go(\d+\.\d+(?:\.\d+)?)/)?.[1] ?? output.trim(),
|
|
343
|
+
missingMessage: "Go is not installed",
|
|
344
|
+
installable: true
|
|
345
|
+
},
|
|
346
|
+
rustc: {
|
|
347
|
+
label: "Rust compiler",
|
|
348
|
+
versionCommand: "rustc --version",
|
|
349
|
+
parseVersion: (output) => output.match(/rustc (\S+)/)?.[1] ?? output.trim(),
|
|
350
|
+
missingMessage: "Rust compiler is not installed",
|
|
351
|
+
installable: true
|
|
352
|
+
},
|
|
353
|
+
cargo: {
|
|
354
|
+
label: "Cargo",
|
|
355
|
+
versionCommand: "cargo --version",
|
|
356
|
+
parseVersion: (output) => output.match(/cargo (\S+)/)?.[1] ?? output.trim(),
|
|
357
|
+
missingMessage: "Cargo is not installed",
|
|
358
|
+
installable: false,
|
|
359
|
+
providedBy: "rustc"
|
|
360
|
+
},
|
|
361
|
+
python3: {
|
|
362
|
+
label: "Python 3",
|
|
363
|
+
versionCommand: "python3 --version",
|
|
364
|
+
parseVersion: (output) => output.match(/Python (\S+)/)?.[1] ?? output.trim(),
|
|
365
|
+
missingMessage: "Python 3 is not installed",
|
|
366
|
+
installable: true
|
|
367
|
+
},
|
|
368
|
+
pip: {
|
|
369
|
+
label: "pip",
|
|
370
|
+
versionCommand: "pip3 --version || pip --version",
|
|
371
|
+
parseVersion: (output) => output.match(/pip (\S+)/)?.[1] ?? output.trim(),
|
|
372
|
+
missingMessage: "pip is not installed",
|
|
373
|
+
installable: true,
|
|
374
|
+
providedBy: "python3"
|
|
375
|
+
},
|
|
376
|
+
ruby: {
|
|
377
|
+
label: "Ruby",
|
|
378
|
+
versionCommand: "ruby --version",
|
|
379
|
+
parseVersion: (output) => output.match(/ruby (\S+)/)?.[1] ?? output.trim(),
|
|
380
|
+
missingMessage: "Ruby is not installed",
|
|
381
|
+
installable: true
|
|
382
|
+
},
|
|
383
|
+
bundler: {
|
|
384
|
+
label: "Bundler",
|
|
385
|
+
versionCommand: "bundler --version",
|
|
386
|
+
parseVersion: (output) => output.match(/(\d+\.\d+\.\d+)/)?.[1] ?? output.trim(),
|
|
387
|
+
missingMessage: "Bundler is not installed",
|
|
388
|
+
installable: true,
|
|
389
|
+
providedBy: "ruby"
|
|
390
|
+
},
|
|
391
|
+
php: {
|
|
392
|
+
label: "PHP",
|
|
393
|
+
versionCommand: "php --version",
|
|
394
|
+
parseVersion: (output) => output.match(/PHP (\S+)/)?.[1] ?? output.trim(),
|
|
395
|
+
missingMessage: "PHP is not installed",
|
|
396
|
+
installable: true
|
|
397
|
+
},
|
|
398
|
+
composer: {
|
|
399
|
+
label: "Composer",
|
|
400
|
+
versionCommand: "composer --version",
|
|
401
|
+
parseVersion: (output) => output.match(/(\d+\.\d+\.\d+)/)?.[1] ?? output.trim(),
|
|
402
|
+
missingMessage: "Composer is not installed",
|
|
403
|
+
installable: true,
|
|
404
|
+
providedBy: "php"
|
|
405
|
+
},
|
|
406
|
+
java: {
|
|
407
|
+
label: "Java",
|
|
408
|
+
versionCommand: "java --version 2>&1 || java -version 2>&1",
|
|
409
|
+
parseVersion: (output) => output.match(/(\d+\.\d+)/)?.[1] ?? output.trim(),
|
|
410
|
+
missingMessage: "Java is not installed",
|
|
411
|
+
installable: true
|
|
412
|
+
},
|
|
413
|
+
javac: {
|
|
414
|
+
label: "Java compiler",
|
|
415
|
+
versionCommand: "javac --version 2>&1 || javac -version 2>&1",
|
|
416
|
+
parseVersion: (output) => output.match(/javac (\S+)/)?.[1] ?? output.trim(),
|
|
417
|
+
missingMessage: "Java compiler (javac) is not installed",
|
|
418
|
+
installable: false,
|
|
419
|
+
providedBy: "java"
|
|
420
|
+
},
|
|
421
|
+
maven: {
|
|
422
|
+
label: "Maven",
|
|
423
|
+
versionCommand: "mvn -version 2>&1",
|
|
424
|
+
parseVersion: (output) => output.match(/Apache Maven (\S+)/)?.[1] ?? output.trim(),
|
|
425
|
+
missingMessage: "Maven is not installed",
|
|
426
|
+
installable: true
|
|
427
|
+
},
|
|
428
|
+
gradle: {
|
|
429
|
+
label: "Gradle",
|
|
430
|
+
versionCommand: "gradle --version 2>&1",
|
|
431
|
+
parseVersion: (output) => output.match(/Gradle (\S+)/)?.[1] ?? output.trim(),
|
|
432
|
+
missingMessage: "Gradle is not installed",
|
|
433
|
+
installable: true
|
|
434
|
+
},
|
|
435
|
+
dotnet: {
|
|
436
|
+
label: ".NET SDK",
|
|
437
|
+
versionCommand: "dotnet --version",
|
|
438
|
+
parseVersion: (output) => output.trim(),
|
|
439
|
+
missingMessage: ".NET SDK is not installed",
|
|
440
|
+
installable: true
|
|
441
|
+
},
|
|
442
|
+
elixir: {
|
|
443
|
+
label: "Elixir",
|
|
444
|
+
versionCommand: "elixir --version 2>&1",
|
|
445
|
+
parseVersion: (output) => output.match(/Elixir (\S+)/)?.[1] ?? output.trim(),
|
|
446
|
+
missingMessage: "Elixir is not installed",
|
|
447
|
+
installable: true
|
|
448
|
+
},
|
|
449
|
+
mix: {
|
|
450
|
+
label: "Mix",
|
|
451
|
+
versionCommand: "mix --version 2>&1",
|
|
452
|
+
parseVersion: (output) => output.match(/Mix (\S+)/)?.[1] ?? output.trim(),
|
|
453
|
+
missingMessage: "Mix is not installed",
|
|
454
|
+
installable: false,
|
|
455
|
+
providedBy: "elixir"
|
|
357
456
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
function pythonInstallPlan(profile) {
|
|
371
|
-
const commands = {
|
|
372
|
-
apt: "apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv",
|
|
373
|
-
dnf: "dnf install -y python3 python3-pip",
|
|
374
|
-
yum: "yum install -y python3 python3-pip",
|
|
375
|
-
brew: "brew install python3"
|
|
376
|
-
};
|
|
377
|
-
const installCommand = commands[profile.packageManager];
|
|
378
|
-
if (!installCommand) {
|
|
379
|
-
return {
|
|
380
|
-
supported: false,
|
|
381
|
-
unsupportedReason: "No supported package manager found for Python installation"
|
|
382
|
-
};
|
|
457
|
+
};
|
|
458
|
+
function label(as, parts) {
|
|
459
|
+
const steps = [];
|
|
460
|
+
for (const part of parts) {
|
|
461
|
+
if (typeof part === "string") {
|
|
462
|
+
steps.push({ command: part, as });
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (!part.supported) return part;
|
|
466
|
+
for (const command of part.value) steps.push({ command, as });
|
|
383
467
|
}
|
|
384
|
-
return
|
|
385
|
-
supported: true,
|
|
386
|
-
installCommand,
|
|
387
|
-
verifyCommand: "python3 --version"
|
|
388
|
-
};
|
|
468
|
+
return answered(steps);
|
|
389
469
|
}
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
apt: "apt-get update -qq && apt-get install -y -qq ruby-full build-essential",
|
|
393
|
-
dnf: "dnf install -y ruby ruby-devel",
|
|
394
|
-
yum: "yum install -y ruby ruby-devel",
|
|
395
|
-
brew: "brew install ruby"
|
|
396
|
-
};
|
|
397
|
-
const installCommand = commands[profile.packageManager];
|
|
398
|
-
if (!installCommand) {
|
|
399
|
-
return {
|
|
400
|
-
supported: false,
|
|
401
|
-
unsupportedReason: "No supported package manager found for Ruby installation"
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
return {
|
|
405
|
-
supported: true,
|
|
406
|
-
installCommand,
|
|
407
|
-
verifyCommand: "ruby --version"
|
|
408
|
-
};
|
|
470
|
+
function asRoot(...parts) {
|
|
471
|
+
return label("root", parts);
|
|
409
472
|
}
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
return {
|
|
413
|
-
supported: true,
|
|
414
|
-
installCommand: [
|
|
415
|
-
"apt-get update -qq",
|
|
416
|
-
"apt-get install -y -qq php-cli php-mbstring php-xml php-curl unzip",
|
|
417
|
-
"curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer"
|
|
418
|
-
].join(" && "),
|
|
419
|
-
verifyCommand: "php --version && composer --version"
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
const commands = {
|
|
423
|
-
dnf: "dnf install -y php-cli php-mbstring php-xml php-curl composer",
|
|
424
|
-
yum: "yum install -y php-cli php-mbstring php-xml php-curl",
|
|
425
|
-
brew: "brew install php composer"
|
|
426
|
-
};
|
|
427
|
-
const installCommand = commands[profile.packageManager];
|
|
428
|
-
if (!installCommand) {
|
|
429
|
-
return {
|
|
430
|
-
supported: false,
|
|
431
|
-
unsupportedReason: "No supported package manager found for PHP installation"
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
supported: true,
|
|
436
|
-
installCommand,
|
|
437
|
-
verifyCommand: "php --version"
|
|
438
|
-
};
|
|
473
|
+
function asLogin(...parts) {
|
|
474
|
+
return label("login", parts);
|
|
439
475
|
}
|
|
440
|
-
function
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
brew: "brew install openjdk@21"
|
|
446
|
-
};
|
|
447
|
-
const installCommand = commands[profile.packageManager];
|
|
448
|
-
if (!installCommand) {
|
|
449
|
-
return {
|
|
450
|
-
supported: false,
|
|
451
|
-
unsupportedReason: "No supported package manager found for Java installation"
|
|
452
|
-
};
|
|
476
|
+
function chain(...parts) {
|
|
477
|
+
const steps = [];
|
|
478
|
+
for (const part of parts) {
|
|
479
|
+
if (!part.supported) return part;
|
|
480
|
+
steps.push(...part.value);
|
|
453
481
|
}
|
|
454
|
-
return
|
|
455
|
-
supported: true,
|
|
456
|
-
installCommand,
|
|
457
|
-
verifyCommand: "java --version 2>&1"
|
|
458
|
-
};
|
|
482
|
+
return answered(steps);
|
|
459
483
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
apt: "apt-get update -qq && apt-get install -y -qq maven",
|
|
463
|
-
dnf: "dnf install -y maven",
|
|
464
|
-
yum: "yum install -y maven",
|
|
465
|
-
brew: "brew install maven"
|
|
466
|
-
};
|
|
467
|
-
const installCommand = commands[profile.packageManager];
|
|
468
|
-
if (!installCommand) {
|
|
469
|
-
return {
|
|
470
|
-
supported: false,
|
|
471
|
-
unsupportedReason: "No supported package manager found for Maven installation"
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
return {
|
|
475
|
-
supported: true,
|
|
476
|
-
installCommand,
|
|
477
|
-
verifyCommand: "mvn -version"
|
|
478
|
-
};
|
|
484
|
+
function isAmazon(profile) {
|
|
485
|
+
return profile.distroId?.trim().toLowerCase() === "amzn";
|
|
479
486
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
return {
|
|
493
|
-
supported: true,
|
|
494
|
-
installCommand,
|
|
495
|
-
verifyCommand: "gradle --version"
|
|
496
|
-
};
|
|
487
|
+
var NODE_LTS_MAJOR = 22;
|
|
488
|
+
var GO_VERSION = "1.22.5";
|
|
489
|
+
var BUN_VERSION = "1.2.0";
|
|
490
|
+
var DOTNET_CHANNEL = "8.0";
|
|
491
|
+
var COMPOSER_INSTALL = "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer";
|
|
492
|
+
var NO_EPEL = "and Openship does not enable third-party repositories (EPEL) on a host it did not provision. Install it yourself, or build in a container.";
|
|
493
|
+
var NO_GRADLE_RPM = `The RHEL family ships no gradle package ${NO_EPEL} Most projects include a ./gradlew wrapper, which the build prefers anyway and which needs only a JDK.`;
|
|
494
|
+
var NO_ELIXIR_RPM = `The RHEL family ships no elixir or erlang package ${NO_EPEL}`;
|
|
495
|
+
function ensureUnzip(ops) {
|
|
496
|
+
if (ops.profile.os === "darwin") return answered([]);
|
|
497
|
+
const install = ops.pkgInstall(["unzip"]);
|
|
498
|
+
if (!install.supported) return answered([]);
|
|
499
|
+
return asRoot(`command -v unzip >/dev/null 2>&1 || { ${opScript(install.value)}; }`);
|
|
497
500
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
501
|
+
var RECIPES = {
|
|
502
|
+
node: {
|
|
503
|
+
verify: "node --version",
|
|
504
|
+
// Total over `SystemPackageManager` with no `default`, so a new manager is a
|
|
505
|
+
// "function lacks ending return statement" error rather than a silent fallthrough.
|
|
506
|
+
steps: (ops) => {
|
|
507
|
+
switch (ops.profile.packageManager) {
|
|
508
|
+
case "apt":
|
|
509
|
+
return asRoot(
|
|
510
|
+
"rm -f /etc/apt/sources.list.d/nodesource.list",
|
|
511
|
+
ops.pkgInstall(["curl", "gnupg", "ca-certificates"]),
|
|
512
|
+
"curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /usr/share/keyrings/nodesource.gpg",
|
|
513
|
+
`echo "deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_LTS_MAJOR}.x nodistro main" > /etc/apt/sources.list.d/nodesource.list`,
|
|
514
|
+
ops.pkgInstall(["nodejs"])
|
|
515
|
+
);
|
|
516
|
+
case "dnf":
|
|
517
|
+
return asRoot(
|
|
518
|
+
`curl -fsSL https://rpm.nodesource.com/setup_${NODE_LTS_MAJOR}.x | bash -`,
|
|
519
|
+
ops.pkgInstall(["nodejs"])
|
|
520
|
+
);
|
|
521
|
+
case "yum":
|
|
522
|
+
return refused(
|
|
523
|
+
`Node ${NODE_LTS_MAJOR} needs glibc 2.28 or newer and this host's release (${ops.profile.prettyName ?? ops.profile.distroId ?? "unknown"}) ships an older one, so the vendor package would install and then fail to load. Use a newer OS release, or build in a container.`
|
|
524
|
+
);
|
|
525
|
+
case "apk":
|
|
526
|
+
return asRoot(ops.pkgInstall(["nodejs", "npm"]));
|
|
527
|
+
case "brew":
|
|
528
|
+
return asRoot(ops.pkgInstall(["node"]));
|
|
529
|
+
case "none":
|
|
530
|
+
return asRoot(ops.pkgInstall(["nodejs"]));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
bun: {
|
|
535
|
+
verify: "bun --version",
|
|
536
|
+
steps: (ops) => {
|
|
537
|
+
if (ops.profile.libc === "musl") {
|
|
538
|
+
return refused(
|
|
539
|
+
"Bun's official installer is a bash script and this host is musl-based (Alpine), which ships busybox ash and no bash. Openship will not add a shell to a host to satisfy an installer. Use Node for this project, or build in a container."
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
return chain(
|
|
543
|
+
ensureUnzip(ops),
|
|
544
|
+
// The download lands in the login user's own home, so it must NOT be elevated:
|
|
545
|
+
// under sudo, HOME becomes root's and root would own ~/.bun, leaving `bun install`
|
|
546
|
+
// unable to write its own cache while `verify` still passes because it only reads.
|
|
547
|
+
//
|
|
548
|
+
// HOME is pinned to `home()` rather than left to `$HOME` so the install prefix and
|
|
549
|
+
// the symlink target below are derived from the same value and cannot diverge. The
|
|
550
|
+
// assignment sits on the `bash`, not the `curl` — it prefixes exactly one command,
|
|
551
|
+
// and it is the interpreter that chooses the prefix.
|
|
552
|
+
asLogin(
|
|
553
|
+
`curl -fsSL https://bun.sh/install | HOME=${shellQuote(ops.home())} bash -s 'bun-v${BUN_VERSION}'`
|
|
554
|
+
),
|
|
555
|
+
// /usr/local/bin is root-owned, and this is what puts bun on PATH for a build.
|
|
556
|
+
asRoot(`ln -sf ${shellQuote(`${ops.home()}/.bun/bin/bun`)} /usr/local/bin/bun`)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
go: {
|
|
561
|
+
verify: "go version",
|
|
562
|
+
steps: (ops) => {
|
|
563
|
+
if (ops.profile.libc === "musl") return asRoot(ops.pkgInstall(["go"]));
|
|
564
|
+
if (ops.profile.os === "darwin") return asRoot(ops.pkgInstall(["go"]));
|
|
565
|
+
const arch = ops.releaseArch();
|
|
566
|
+
if (!arch.supported) return arch;
|
|
567
|
+
return asRoot(
|
|
568
|
+
`curl -fsSL https://go.dev/dl/go${GO_VERSION}.linux-${arch.value}.tar.gz -o /tmp/go.tar.gz`,
|
|
569
|
+
"rm -rf /usr/local/go",
|
|
570
|
+
"tar -C /usr/local -xzf /tmp/go.tar.gz",
|
|
571
|
+
"rm -f /tmp/go.tar.gz",
|
|
572
|
+
// The profile.d line is for an operator's interactive login only. A build runs
|
|
573
|
+
// through a NON-login `ssh host 'go build'`, which sources nothing, so this was
|
|
574
|
+
// the only recipe of the fifteen whose payload never reached a build's PATH: the
|
|
575
|
+
// install went green and `go build` then exited 127. The symlink is what actually
|
|
576
|
+
// puts it there — Go resolves GOROOT from /proc/self/exe, so a link is transparent.
|
|
577
|
+
"ln -sf /usr/local/go/bin/go /usr/local/bin/go",
|
|
578
|
+
"ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt",
|
|
579
|
+
"echo 'export PATH=$PATH:/usr/local/go/bin' > /etc/profile.d/go.sh"
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
rustc: {
|
|
584
|
+
verify: ". $HOME/.cargo/env 2>/dev/null; rustc --version",
|
|
585
|
+
steps: (ops) => (
|
|
586
|
+
// rustup's default host triple is gnu, so on musl it fetches a toolchain that
|
|
587
|
+
// cannot run. Alpine's rust package is built for the box it's on.
|
|
588
|
+
ops.profile.libc === "musl" ? asRoot(ops.pkgInstall(["rust", "cargo"])) : chain(
|
|
589
|
+
// Unelevated for the same reason as bun, and doubly so here: rustup owns BOTH
|
|
590
|
+
// ~/.cargo and ~/.rustup, and `cargo build` writes into the registry cache
|
|
591
|
+
// under ~/.cargo on every build. Pinned HOME also makes `verify`
|
|
592
|
+
// (`. $HOME/.cargo/env`, run unelevated) look where the toolchain landed.
|
|
593
|
+
asLogin(
|
|
594
|
+
`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | HOME=${shellQuote(ops.home())} sh -s -- -y --no-modify-path`
|
|
595
|
+
),
|
|
596
|
+
asRoot(
|
|
597
|
+
`ln -sf ${shellQuote(`${ops.home()}/.cargo/bin/cargo`)} ${shellQuote(`${ops.home()}/.cargo/bin/rustc`)} /usr/local/bin/`
|
|
598
|
+
)
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
},
|
|
602
|
+
python3: {
|
|
603
|
+
verify: "python3 --version",
|
|
604
|
+
steps: (ops) => asRoot(
|
|
605
|
+
ops.pkgInstallVariants({
|
|
606
|
+
apt: answered(["python3", "python3-pip", "python3-venv"]),
|
|
607
|
+
// No `python3-venv` on the RPM side — venv lives in `python3-libs` there.
|
|
608
|
+
dnf: answered(["python3", "python3-pip"]),
|
|
609
|
+
yum: answered(["python3", "python3-pip"]),
|
|
610
|
+
apk: answered(["python3", "py3-pip"]),
|
|
611
|
+
brew: answered(["python3"])
|
|
612
|
+
})
|
|
613
|
+
)
|
|
614
|
+
},
|
|
615
|
+
pip: {
|
|
616
|
+
verify: "pip3 --version || pip --version",
|
|
617
|
+
steps: (ops) => asRoot(
|
|
618
|
+
ops.pkgInstallVariants({
|
|
619
|
+
apt: answered(["python3-pip"]),
|
|
620
|
+
dnf: answered(["python3-pip"]),
|
|
621
|
+
yum: answered(["python3-pip"]),
|
|
622
|
+
apk: answered(["py3-pip"]),
|
|
623
|
+
// Homebrew has no `pip` formula; pip arrives with its python.
|
|
624
|
+
brew: refused(
|
|
625
|
+
"Homebrew installs pip as part of Python \u2014 install the python3 tool instead."
|
|
626
|
+
)
|
|
627
|
+
})
|
|
628
|
+
)
|
|
629
|
+
},
|
|
630
|
+
ruby: {
|
|
631
|
+
verify: "ruby --version",
|
|
632
|
+
steps: (ops) => asRoot(
|
|
633
|
+
ops.pkgInstallVariants({
|
|
634
|
+
apt: answered(["ruby-full", "build-essential"]),
|
|
635
|
+
// AL2023 has no unversioned `ruby`; Rocky/Alma/RHEL/Fedora do. Both are dnf,
|
|
636
|
+
// which is exactly the split a package-manager-keyed table cannot express.
|
|
637
|
+
dnf: answered(
|
|
638
|
+
isAmazon(ops.profile) ? ["ruby3.2", "ruby3.2-devel"] : ["ruby", "ruby-devel"]
|
|
639
|
+
),
|
|
640
|
+
yum: answered(["ruby", "ruby-devel"]),
|
|
641
|
+
// `build-base` because musl has no prebuilt native gems to fall back on.
|
|
642
|
+
apk: answered(["ruby", "ruby-dev", "build-base"]),
|
|
643
|
+
brew: answered(["ruby"])
|
|
644
|
+
})
|
|
645
|
+
)
|
|
646
|
+
},
|
|
647
|
+
bundler: {
|
|
648
|
+
verify: "bundler --version",
|
|
649
|
+
// Ships with every supported Ruby as a default gem; `gem install` only upgrades it.
|
|
650
|
+
// No distro package is worth preferring here.
|
|
651
|
+
steps: () => asRoot("gem install bundler")
|
|
652
|
+
},
|
|
653
|
+
php: {
|
|
654
|
+
verify: "php --version",
|
|
655
|
+
steps: (ops) => asRoot(
|
|
656
|
+
ops.pkgInstallVariants({
|
|
657
|
+
apt: answered(["php-cli", "php-mbstring", "php-xml", "php-curl", "unzip"]),
|
|
658
|
+
// There is no `php-curl` anywhere on the RPM side — curl support ships in
|
|
659
|
+
// `php-common`, which the cli package pulls in. AL2023 versions every name.
|
|
660
|
+
dnf: answered(
|
|
661
|
+
isAmazon(ops.profile) ? ["php8.2-cli", "php8.2-mbstring", "php8.2-xml"] : ["php-cli", "php-mbstring", "php-xml"]
|
|
662
|
+
),
|
|
663
|
+
yum: answered(["php-cli", "php-mbstring", "php-xml"]),
|
|
664
|
+
// Alpine versions the package and symlinks /usr/bin/php → php83, so `verify`
|
|
665
|
+
// holds without the recipe naming a version.
|
|
666
|
+
apk: answered(["php83"]),
|
|
667
|
+
brew: answered(["php"])
|
|
668
|
+
})
|
|
669
|
+
)
|
|
670
|
+
},
|
|
671
|
+
composer: {
|
|
672
|
+
verify: "composer --version",
|
|
673
|
+
steps: (ops) => {
|
|
674
|
+
if (ops.profile.packageManager === "brew") return asRoot(ops.pkgInstall(["composer"]));
|
|
675
|
+
if (ops.profile.libc === "musl") {
|
|
676
|
+
return refused(
|
|
677
|
+
"Composer needs PHP's phar and openssl extensions, and on musl hosts (Alpine) those ship as separate packages that Openship does not install. Add php-phar and php-openssl for your PHP version yourself, or build in a container."
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
return asRoot(COMPOSER_INSTALL);
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
java: {
|
|
684
|
+
verify: "java --version 2>&1",
|
|
685
|
+
steps: (ops) => asRoot(
|
|
686
|
+
ops.pkgInstallVariants({
|
|
687
|
+
apt: answered(["openjdk-21-jdk"]),
|
|
688
|
+
// Amazon ships Corretto in place of OpenJDK — `java-21-openjdk-devel` does not
|
|
689
|
+
// exist on AL2023, so a Java build there installed nothing and then failed on a
|
|
690
|
+
// missing `javac`.
|
|
691
|
+
dnf: answered(
|
|
692
|
+
isAmazon(ops.profile) ? ["java-21-amazon-corretto-devel"] : ["java-21-openjdk-devel"]
|
|
693
|
+
),
|
|
694
|
+
// AL2 and CentOS 7 top out far below 21 (AL2's newest is
|
|
695
|
+
// `java-1.8.0-openjdk-devel`). Installing Java 8 for a recipe that promises 21 is
|
|
696
|
+
// the silent wrong answer, so name the release as the problem instead.
|
|
697
|
+
yum: refused(
|
|
698
|
+
"This host's release carries no OpenJDK 21 package \u2014 the newest it has is Java 8/11, and Openship will not install a JDK older than the toolchain asks for. Use a newer OS release, or build in a container."
|
|
699
|
+
),
|
|
700
|
+
apk: answered(["openjdk21-jdk"]),
|
|
701
|
+
brew: answered(["openjdk@21"])
|
|
702
|
+
})
|
|
703
|
+
)
|
|
704
|
+
},
|
|
705
|
+
maven: {
|
|
706
|
+
verify: "mvn -version",
|
|
707
|
+
steps: (ops) => asRoot(ops.pkgInstall(["maven"]))
|
|
708
|
+
},
|
|
709
|
+
gradle: {
|
|
710
|
+
verify: "gradle --version",
|
|
711
|
+
steps: (ops) => asRoot(
|
|
712
|
+
ops.pkgInstallVariants({
|
|
713
|
+
apt: answered(["gradle"]),
|
|
714
|
+
dnf: refused(NO_GRADLE_RPM),
|
|
715
|
+
yum: refused(NO_GRADLE_RPM),
|
|
716
|
+
apk: answered(["gradle"]),
|
|
717
|
+
brew: answered(["gradle"])
|
|
718
|
+
})
|
|
719
|
+
)
|
|
720
|
+
},
|
|
721
|
+
dotnet: {
|
|
722
|
+
verify: "dotnet --version",
|
|
723
|
+
steps: (ops) => {
|
|
724
|
+
if (ops.profile.libc === "musl") return asRoot(ops.pkgInstall(["dotnet8-sdk"]));
|
|
725
|
+
if (ops.profile.packageManager === "brew") return asRoot(ops.pkgInstall(["dotnet"]));
|
|
726
|
+
if (ops.profile.os !== "linux") {
|
|
727
|
+
return refused(
|
|
728
|
+
`Openship installs the .NET SDK on Linux, and on macOS through Homebrew. This host reports os=${ops.profile.os}.`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
return asRoot(
|
|
732
|
+
"curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh",
|
|
733
|
+
`bash /tmp/dotnet-install.sh --channel ${DOTNET_CHANNEL} --install-dir /usr/local/share/dotnet`,
|
|
734
|
+
"rm -f /tmp/dotnet-install.sh",
|
|
735
|
+
"ln -sf /usr/local/share/dotnet/dotnet /usr/local/bin/dotnet"
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
},
|
|
739
|
+
elixir: {
|
|
740
|
+
verify: "elixir --version 2>&1",
|
|
741
|
+
steps: (ops) => asRoot(
|
|
742
|
+
ops.pkgInstallVariants({
|
|
743
|
+
apt: answered(["erlang", "elixir"]),
|
|
744
|
+
// Keyed on the distro, not the package manager, because the two disagree here:
|
|
745
|
+
// Fedora carries elixir and erlang in its own repos, while AL2023 and Rocky 9
|
|
746
|
+
// — same `dnf` — carry neither. Refusing all of dnf told a Fedora operator to go
|
|
747
|
+
// enable EPEL for a package Fedora already has, and EPEL does not target Fedora
|
|
748
|
+
// at all, so the advice was not just unnecessary but impossible to follow.
|
|
749
|
+
//
|
|
750
|
+
// This is the first production reader of `profile.distro`.
|
|
751
|
+
dnf: ops.profile.distro === "fedora" ? answered(["erlang", "elixir"]) : refused(NO_ELIXIR_RPM),
|
|
752
|
+
yum: refused(NO_ELIXIR_RPM),
|
|
753
|
+
apk: answered(["elixir", "erlang"]),
|
|
754
|
+
brew: answered(["elixir"])
|
|
755
|
+
})
|
|
756
|
+
)
|
|
581
757
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
758
|
+
};
|
|
759
|
+
var INSTALLS = Object.fromEntries(
|
|
760
|
+
Object.entries(RECIPES).map(([tool, recipe]) => [
|
|
761
|
+
tool,
|
|
762
|
+
(profile) => {
|
|
763
|
+
const gate = hostRefusal(profile);
|
|
764
|
+
if (gate !== null) return refused(gate);
|
|
765
|
+
const ops = envOps(profile);
|
|
766
|
+
const steps = recipe.steps(ops);
|
|
767
|
+
return steps.supported ? answered({ steps: steps.value, verifyCommand: recipe.verify }) : steps;
|
|
768
|
+
}
|
|
769
|
+
])
|
|
770
|
+
);
|
|
588
771
|
var toolchainCatalog = {
|
|
589
772
|
/**
|
|
590
|
-
* Check recipes
|
|
773
|
+
* Check recipes — how to detect each tool and parse its version.
|
|
591
774
|
*
|
|
592
|
-
* Every tool in LANGUAGES[lang].requiredTools must have an entry here.
|
|
775
|
+
* Every tool named in `LANGUAGES[lang].requiredTools` must have an entry here.
|
|
593
776
|
*/
|
|
594
|
-
checks:
|
|
595
|
-
node: {
|
|
596
|
-
label: "Node.js",
|
|
597
|
-
versionCommand: "node --version",
|
|
598
|
-
parseVersion: (output) => output.replace(/^v/, "").trim(),
|
|
599
|
-
missingMessage: "Node.js is not installed",
|
|
600
|
-
installable: true
|
|
601
|
-
},
|
|
602
|
-
bun: {
|
|
603
|
-
label: "Bun",
|
|
604
|
-
versionCommand: "bun --version",
|
|
605
|
-
parseVersion: (output) => output.trim(),
|
|
606
|
-
missingMessage: "Bun is not installed",
|
|
607
|
-
installable: true
|
|
608
|
-
},
|
|
609
|
-
npm: {
|
|
610
|
-
label: "npm",
|
|
611
|
-
versionCommand: "npm --version",
|
|
612
|
-
parseVersion: (output) => output.trim(),
|
|
613
|
-
missingMessage: "npm is not installed",
|
|
614
|
-
installable: false,
|
|
615
|
-
providedBy: "node"
|
|
616
|
-
},
|
|
617
|
-
go: {
|
|
618
|
-
label: "Go",
|
|
619
|
-
versionCommand: "go version",
|
|
620
|
-
parseVersion: (output) => output.match(/go(\d+\.\d+(\.\d+)?)/)?.[1] ?? output.trim(),
|
|
621
|
-
missingMessage: "Go is not installed",
|
|
622
|
-
installable: true
|
|
623
|
-
},
|
|
624
|
-
rustc: {
|
|
625
|
-
label: "Rust compiler",
|
|
626
|
-
versionCommand: "rustc --version",
|
|
627
|
-
parseVersion: (output) => output.match(/rustc (\S+)/)?.[1] ?? output.trim(),
|
|
628
|
-
missingMessage: "Rust compiler is not installed",
|
|
629
|
-
installable: true
|
|
630
|
-
},
|
|
631
|
-
cargo: {
|
|
632
|
-
label: "Cargo",
|
|
633
|
-
versionCommand: "cargo --version",
|
|
634
|
-
parseVersion: (output) => output.match(/cargo (\S+)/)?.[1] ?? output.trim(),
|
|
635
|
-
missingMessage: "Cargo is not installed",
|
|
636
|
-
installable: false,
|
|
637
|
-
providedBy: "rustc"
|
|
638
|
-
},
|
|
639
|
-
python3: {
|
|
640
|
-
label: "Python 3",
|
|
641
|
-
versionCommand: "python3 --version",
|
|
642
|
-
parseVersion: (output) => output.match(/Python (\S+)/)?.[1] ?? output.trim(),
|
|
643
|
-
missingMessage: "Python 3 is not installed",
|
|
644
|
-
installable: true
|
|
645
|
-
},
|
|
646
|
-
pip: {
|
|
647
|
-
label: "pip",
|
|
648
|
-
versionCommand: "pip3 --version || pip --version",
|
|
649
|
-
parseVersion: (output) => output.match(/pip (\S+)/)?.[1] ?? output.trim(),
|
|
650
|
-
missingMessage: "pip is not installed",
|
|
651
|
-
installable: true,
|
|
652
|
-
providedBy: "python3"
|
|
653
|
-
},
|
|
654
|
-
ruby: {
|
|
655
|
-
label: "Ruby",
|
|
656
|
-
versionCommand: "ruby --version",
|
|
657
|
-
parseVersion: (output) => output.match(/ruby (\S+)/)?.[1] ?? output.trim(),
|
|
658
|
-
missingMessage: "Ruby is not installed",
|
|
659
|
-
installable: true
|
|
660
|
-
},
|
|
661
|
-
bundler: {
|
|
662
|
-
label: "Bundler",
|
|
663
|
-
versionCommand: "bundler --version",
|
|
664
|
-
parseVersion: (output) => output.match(/(\d+\.\d+\.\d+)/)?.[1] ?? output.trim(),
|
|
665
|
-
missingMessage: "Bundler is not installed",
|
|
666
|
-
installable: true,
|
|
667
|
-
providedBy: "ruby"
|
|
668
|
-
},
|
|
669
|
-
php: {
|
|
670
|
-
label: "PHP",
|
|
671
|
-
versionCommand: "php --version",
|
|
672
|
-
parseVersion: (output) => output.match(/PHP (\S+)/)?.[1] ?? output.trim(),
|
|
673
|
-
missingMessage: "PHP is not installed",
|
|
674
|
-
installable: true
|
|
675
|
-
},
|
|
676
|
-
composer: {
|
|
677
|
-
label: "Composer",
|
|
678
|
-
versionCommand: "composer --version",
|
|
679
|
-
parseVersion: (output) => output.match(/(\d+\.\d+\.\d+)/)?.[1] ?? output.trim(),
|
|
680
|
-
missingMessage: "Composer is not installed",
|
|
681
|
-
installable: true,
|
|
682
|
-
providedBy: "php"
|
|
683
|
-
},
|
|
684
|
-
java: {
|
|
685
|
-
label: "Java",
|
|
686
|
-
versionCommand: "java --version 2>&1 || java -version 2>&1",
|
|
687
|
-
parseVersion: (output) => output.match(/(\d+\.\d+)/)?.[1] ?? output.trim(),
|
|
688
|
-
missingMessage: "Java is not installed",
|
|
689
|
-
installable: true
|
|
690
|
-
},
|
|
691
|
-
javac: {
|
|
692
|
-
label: "Java compiler",
|
|
693
|
-
versionCommand: "javac --version 2>&1 || javac -version 2>&1",
|
|
694
|
-
parseVersion: (output) => output.match(/javac (\S+)/)?.[1] ?? output.trim(),
|
|
695
|
-
missingMessage: "Java compiler (javac) is not installed",
|
|
696
|
-
installable: false,
|
|
697
|
-
providedBy: "java"
|
|
698
|
-
},
|
|
699
|
-
maven: {
|
|
700
|
-
label: "Maven",
|
|
701
|
-
versionCommand: "mvn -version 2>&1",
|
|
702
|
-
parseVersion: (output) => output.match(/Apache Maven (\S+)/)?.[1] ?? output.trim(),
|
|
703
|
-
missingMessage: "Maven is not installed",
|
|
704
|
-
installable: true
|
|
705
|
-
},
|
|
706
|
-
gradle: {
|
|
707
|
-
label: "Gradle",
|
|
708
|
-
versionCommand: "gradle --version 2>&1",
|
|
709
|
-
parseVersion: (output) => output.match(/Gradle (\S+)/)?.[1] ?? output.trim(),
|
|
710
|
-
missingMessage: "Gradle is not installed",
|
|
711
|
-
installable: true
|
|
712
|
-
},
|
|
713
|
-
dotnet: {
|
|
714
|
-
label: ".NET SDK",
|
|
715
|
-
versionCommand: "dotnet --version",
|
|
716
|
-
parseVersion: (output) => output.trim(),
|
|
717
|
-
missingMessage: ".NET SDK is not installed",
|
|
718
|
-
installable: true
|
|
719
|
-
},
|
|
720
|
-
elixir: {
|
|
721
|
-
label: "Elixir",
|
|
722
|
-
versionCommand: "elixir --version 2>&1",
|
|
723
|
-
parseVersion: (output) => output.match(/Elixir (\S+)/)?.[1] ?? output.trim(),
|
|
724
|
-
missingMessage: "Elixir is not installed",
|
|
725
|
-
installable: true
|
|
726
|
-
},
|
|
727
|
-
mix: {
|
|
728
|
-
label: "Mix",
|
|
729
|
-
versionCommand: "mix --version 2>&1",
|
|
730
|
-
parseVersion: (output) => output.match(/Mix (\S+)/)?.[1] ?? output.trim(),
|
|
731
|
-
missingMessage: "Mix is not installed",
|
|
732
|
-
installable: false,
|
|
733
|
-
providedBy: "elixir"
|
|
734
|
-
}
|
|
735
|
-
},
|
|
777
|
+
checks: CHECKS,
|
|
736
778
|
/**
|
|
737
|
-
* Install plan factories
|
|
779
|
+
* Install plan factories — how to install each tool on a given host.
|
|
738
780
|
*
|
|
739
|
-
* Only tools
|
|
740
|
-
*
|
|
781
|
+
* Only `installable` tools have one. npm, cargo, javac and mix arrive with their
|
|
782
|
+
* parent and say so through `providedBy`.
|
|
741
783
|
*/
|
|
742
|
-
installs:
|
|
743
|
-
node: nodeInstallPlan,
|
|
744
|
-
bun: bunInstallPlan,
|
|
745
|
-
go: goInstallPlan,
|
|
746
|
-
rustc: rustInstallPlan,
|
|
747
|
-
python3: pythonInstallPlan,
|
|
748
|
-
pip: pipInstallPlan,
|
|
749
|
-
ruby: rubyInstallPlan,
|
|
750
|
-
bundler: bundlerInstallPlan,
|
|
751
|
-
php: phpInstallPlan,
|
|
752
|
-
composer: composerInstallPlan,
|
|
753
|
-
java: javaInstallPlan,
|
|
754
|
-
maven: mavenInstallPlan,
|
|
755
|
-
gradle: gradleInstallPlan,
|
|
756
|
-
dotnet: dotnetInstallPlan,
|
|
757
|
-
elixir: elixirInstallPlan
|
|
758
|
-
}
|
|
784
|
+
installs: INSTALLS
|
|
759
785
|
};
|
|
760
786
|
|
|
761
787
|
// ../../packages/adapters/src/toolchain/checks.ts
|
|
@@ -775,31 +801,8 @@ function compareVersions(actual, minimum) {
|
|
|
775
801
|
}
|
|
776
802
|
return 0;
|
|
777
803
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
systemDebug("toolchain", `exec:start ${command}`);
|
|
781
|
-
try {
|
|
782
|
-
const result = await executor.exec(command, { timeout: 1e4 });
|
|
783
|
-
systemDebug(
|
|
784
|
-
"toolchain",
|
|
785
|
-
`exec:ok ${command} (${formatDuration(startedAt)})`
|
|
786
|
-
);
|
|
787
|
-
return result;
|
|
788
|
-
} catch (err) {
|
|
789
|
-
if (isRemoteConnectionError(err)) {
|
|
790
|
-
systemDebug(
|
|
791
|
-
"toolchain",
|
|
792
|
-
`exec:abort ${command} (${formatDuration(startedAt)}) ${safeErrorMessage(err)}`
|
|
793
|
-
);
|
|
794
|
-
throw err;
|
|
795
|
-
}
|
|
796
|
-
const msg = safeErrorMessage(err);
|
|
797
|
-
systemDebug(
|
|
798
|
-
"toolchain",
|
|
799
|
-
`exec:fail ${command} (${formatDuration(startedAt)}) ${msg}`
|
|
800
|
-
);
|
|
801
|
-
return null;
|
|
802
|
-
}
|
|
804
|
+
function meetsMinVersion(actual, minimum) {
|
|
805
|
+
return compareVersions(actual, minimum) >= 0;
|
|
803
806
|
}
|
|
804
807
|
async function checkTool(executor, name, opts) {
|
|
805
808
|
const recipe = toolchainCatalog.checks[name];
|
|
@@ -812,18 +815,21 @@ async function checkTool(executor, name, opts) {
|
|
|
812
815
|
message: `Unknown tool: ${name}`
|
|
813
816
|
};
|
|
814
817
|
}
|
|
815
|
-
const
|
|
816
|
-
if (!output) {
|
|
817
|
-
systemDebug("toolchain", `${name}:missing`);
|
|
818
|
+
const probe = await probeExec(executor, recipe.versionCommand, "toolchain");
|
|
819
|
+
if (!probe.output) {
|
|
820
|
+
systemDebug("toolchain", `${name}:missing ${probe.error ?? "exited 0 with no version"}`);
|
|
818
821
|
return {
|
|
819
822
|
name,
|
|
820
823
|
label: recipe.label,
|
|
821
824
|
installed: false,
|
|
822
825
|
healthy: false,
|
|
823
|
-
|
|
826
|
+
// A tool that isn't there and a tool whose `--version` was refused ("permission
|
|
827
|
+
// denied", "Command timed out") both land here, and only the second can say why —
|
|
828
|
+
// #408, the same conflation `system/checks.ts` reports its way out of.
|
|
829
|
+
message: withReason(recipe.missingMessage, probe.error)
|
|
824
830
|
};
|
|
825
831
|
}
|
|
826
|
-
const version = recipe.parseVersion(output);
|
|
832
|
+
const version = recipe.parseVersion(probe.output);
|
|
827
833
|
if (opts?.minVersion && compareVersions(version, opts.minVersion) < 0) {
|
|
828
834
|
systemDebug("toolchain", `${name}:outdated v${version} < ${opts.minVersion}`);
|
|
829
835
|
return {
|
|
@@ -883,6 +889,16 @@ async function checkToolchainForStack(executor, stackId) {
|
|
|
883
889
|
}
|
|
884
890
|
|
|
885
891
|
// ../../packages/adapters/src/toolchain/installer.ts
|
|
892
|
+
function groupStepsByAuthority(steps, hostNeedsRoot) {
|
|
893
|
+
const groups = [];
|
|
894
|
+
for (const step of steps) {
|
|
895
|
+
const as = step.as === "root" && hostNeedsRoot ? "root" : "login";
|
|
896
|
+
const open = groups.at(-1);
|
|
897
|
+
if (open && open.as === as) open.commands.push(step.command);
|
|
898
|
+
else groups.push({ as, commands: [step.command] });
|
|
899
|
+
}
|
|
900
|
+
return groups;
|
|
901
|
+
}
|
|
886
902
|
async function installTool(executor, name, onLog, requiredVersion) {
|
|
887
903
|
const startedAt = Date.now();
|
|
888
904
|
const recipe = toolchainCatalog.checks[name];
|
|
@@ -907,13 +923,26 @@ async function installTool(executor, name, onLog, requiredVersion) {
|
|
|
907
923
|
const profile = await resolveEnvironment(executor);
|
|
908
924
|
const plan = factory(profile);
|
|
909
925
|
if (!plan.supported) {
|
|
910
|
-
const msg = plan.unsupportedReason ?? `${recipe.label} installation not supported on this system`;
|
|
911
926
|
onLog?.({
|
|
912
927
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
913
|
-
message:
|
|
928
|
+
message: `Cannot install ${recipe.label} on ${envOps(profile).describe()}: ${plan.reason}`,
|
|
914
929
|
level: "error"
|
|
915
930
|
});
|
|
916
|
-
return { tool: name, success: false, error:
|
|
931
|
+
return { tool: name, success: false, error: plan.reason };
|
|
932
|
+
}
|
|
933
|
+
const groups = groupStepsByAuthority(plan.value.steps, envOps(profile).installsNeedRoot());
|
|
934
|
+
let elevated = null;
|
|
935
|
+
if (groups.some((group) => group.as === "root")) {
|
|
936
|
+
const grant = await privilegedExecutor(executor, `Installing ${recipe.label}`);
|
|
937
|
+
if (!grant.supported) {
|
|
938
|
+
onLog?.({
|
|
939
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
940
|
+
message: grant.reason,
|
|
941
|
+
level: "error"
|
|
942
|
+
});
|
|
943
|
+
return { tool: name, success: false, error: grant.reason };
|
|
944
|
+
}
|
|
945
|
+
elevated = grant.value.executor;
|
|
917
946
|
}
|
|
918
947
|
systemDebug("toolchain", `install:start ${name}`);
|
|
919
948
|
onLog?.({
|
|
@@ -922,33 +951,25 @@ async function installTool(executor, name, onLog, requiredVersion) {
|
|
|
922
951
|
level: "info"
|
|
923
952
|
});
|
|
924
953
|
try {
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
onLog?.({
|
|
932
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
933
|
-
message: `Primary install failed, trying fallback...`,
|
|
934
|
-
level: "warn"
|
|
935
|
-
});
|
|
936
|
-
const fb = await executor.streamExec(fallback, (entry) => onLog?.(entry));
|
|
937
|
-
if (fb.code === 0) break;
|
|
938
|
-
}
|
|
939
|
-
} else {
|
|
954
|
+
for (const group of groups) {
|
|
955
|
+
const runner = group.as === "root" ? elevated : executor;
|
|
956
|
+
const { code } = await runner.streamExec(opScript(group.commands), (entry) => {
|
|
957
|
+
onLog?.(entry);
|
|
958
|
+
});
|
|
959
|
+
if (code !== 0) {
|
|
940
960
|
throw new Error(`Install command failed with exit code ${code}`);
|
|
941
961
|
}
|
|
942
962
|
}
|
|
943
|
-
|
|
944
|
-
await executor.streamExec(plan.startCommand, (entry) => onLog?.(entry));
|
|
945
|
-
}
|
|
946
|
-
const verifyCmd = plan.verifyCommand ?? recipe.versionCommand;
|
|
947
|
-
const verifyResult = await executor.exec(verifyCmd, { timeout: 1e4 }).catch(() => null);
|
|
963
|
+
const verifyResult = await executor.exec(plan.value.verifyCommand, { timeout: 1e4 }).catch(() => null);
|
|
948
964
|
if (!verifyResult) {
|
|
949
965
|
throw new Error(`${recipe.label} installed but verification failed`);
|
|
950
966
|
}
|
|
951
967
|
const version = recipe.parseVersion(verifyResult);
|
|
968
|
+
if (requiredVersion && !meetsMinVersion(version, requiredVersion)) {
|
|
969
|
+
throw new Error(
|
|
970
|
+
`${recipe.label} ${version} was installed but this project needs ${requiredVersion} or newer, and this host has no newer package. Use a newer OS release, or build in a container.`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
952
973
|
systemDebug("toolchain", `install:done ${name} v${version} (${formatDuration(startedAt)})`);
|
|
953
974
|
onLog?.({
|
|
954
975
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1503,21 +1524,18 @@ ${hint}` : "Process exited immediately after spawn (no output captured)";
|
|
|
1503
1524
|
|
|
1504
1525
|
// ../../packages/adapters/src/runtime/supervisor/detect.ts
|
|
1505
1526
|
async function detectSupervisor(executor, workDir) {
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1508
|
-
return
|
|
1509
|
-
}
|
|
1510
|
-
return new
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
`[ -d /run/systemd/system ] && command -v systemctl >/dev/null 2>&1 && echo y || echo n`
|
|
1527
|
+
const profile = await resolveEnvironment(executor).catch((err) => {
|
|
1528
|
+
systemDebug("supervisor", `detect: host unreachable, using nohup \u2014 ${safeErrorMessage(err)}`);
|
|
1529
|
+
return null;
|
|
1530
|
+
});
|
|
1531
|
+
if (profile?.serviceManager === "systemd") return new SystemdSupervisor(executor, workDir);
|
|
1532
|
+
const unmeasured = profile?.probeError ?? (profile?.supported === false ? profile.unsupportedReason : null);
|
|
1533
|
+
if (unmeasured) {
|
|
1534
|
+
console.warn(
|
|
1535
|
+
`[supervisor] could not confirm this host's init system, so the service will run under nohup and will NOT restart after a reboot: ${unmeasured}`
|
|
1516
1536
|
);
|
|
1517
|
-
return result.trim() === "y";
|
|
1518
|
-
} catch {
|
|
1519
|
-
return false;
|
|
1520
1537
|
}
|
|
1538
|
+
return new NohupSupervisor(executor, workDir);
|
|
1521
1539
|
}
|
|
1522
1540
|
|
|
1523
1541
|
// ../../packages/adapters/src/runtime/bare.ts
|
|
@@ -1672,7 +1690,7 @@ var BareRuntime = class {
|
|
|
1672
1690
|
async promoteBuildArtifact(artifactPath, deploymentId, previousDeploymentId) {
|
|
1673
1691
|
const releaseDir = this.releaseDir(deploymentId);
|
|
1674
1692
|
if (artifactPath === releaseDir) return releaseDir;
|
|
1675
|
-
await this.executor
|
|
1693
|
+
await ensureOwnedDir(this.executor, `${this.workDir}/releases`);
|
|
1676
1694
|
await this.executor.rm(releaseDir);
|
|
1677
1695
|
const previousReleaseDir = previousDeploymentId ? this.releaseDir(previousDeploymentId) : void 0;
|
|
1678
1696
|
if (previousReleaseDir && await this.executor.exists(previousReleaseDir)) {
|
|
@@ -1898,10 +1916,7 @@ var BareRuntime = class {
|
|
|
1898
1916
|
abort.abort();
|
|
1899
1917
|
this.activeBuilds.delete(sessionId);
|
|
1900
1918
|
}
|
|
1901
|
-
|
|
1902
|
-
const scan = (sig) => `for p in /proc/[0-9]*; do c=$(readlink "$p/cwd" 2>/dev/null); case "$c" in ${dir}|${dir}/*) kill -${sig} "\${p##*/}" 2>/dev/null || true;; esac; done`;
|
|
1903
|
-
await this.executor.exec(`${scan("TERM")}; sleep 2; ${scan("KILL")}`).catch(() => {
|
|
1904
|
-
});
|
|
1919
|
+
await killProcessesUnderDir(this.executor, this.buildDir(sessionId));
|
|
1905
1920
|
}
|
|
1906
1921
|
async getBuildLogs(sessionId) {
|
|
1907
1922
|
void sessionId;
|