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
|
@@ -5,42 +5,42 @@ const require = __ospCreateRequire(import.meta.url);
|
|
|
5
5
|
import {
|
|
6
6
|
checkGit,
|
|
7
7
|
installGit
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-A4GXYE4L.js";
|
|
9
9
|
import {
|
|
10
10
|
createDockerBuildContext,
|
|
11
11
|
normalizeDockerRelativePath,
|
|
12
|
-
resolveDockerfileCandidates
|
|
13
|
-
|
|
12
|
+
resolveDockerfileCandidates,
|
|
13
|
+
resolveWithinDirectory
|
|
14
|
+
} from "./chunk-LUKYPVJ4.js";
|
|
14
15
|
import {
|
|
15
16
|
prepareStackOutput,
|
|
16
17
|
resolveProjectDir,
|
|
17
18
|
resolveStaticOutputPath,
|
|
18
19
|
runLocalBuild
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-YIMOCZBZ.js";
|
|
20
21
|
import {
|
|
21
22
|
BuildLogger,
|
|
22
23
|
injectGitToken,
|
|
23
24
|
runBuildPipeline,
|
|
24
25
|
sq,
|
|
25
26
|
transferLocalDirectory
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import {
|
|
28
|
-
isRuntimeNotFoundError
|
|
29
|
-
} from "./chunk-ZCFVHA25.js";
|
|
27
|
+
} from "./chunk-LH2PLRQO.js";
|
|
30
28
|
import {
|
|
29
|
+
PRICING,
|
|
31
30
|
STACKS,
|
|
32
31
|
SYSTEM,
|
|
33
32
|
TRANSFER_EXCLUDES,
|
|
34
33
|
buildOutputTransferExcludes,
|
|
34
|
+
isRuntimeNotFoundError,
|
|
35
35
|
missingOutputDirectoryMessage,
|
|
36
36
|
packageManagerEnsureCommand,
|
|
37
37
|
safeErrorMessage,
|
|
38
38
|
shellSplitWords
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-UJ6JZM65.js";
|
|
40
40
|
|
|
41
41
|
// ../../packages/adapters/src/runtime/cloud.ts
|
|
42
42
|
import { readFile } from "fs/promises";
|
|
43
|
-
import {
|
|
43
|
+
import { posix } from "path";
|
|
44
44
|
|
|
45
45
|
// ../../packages/adapters/src/types.ts
|
|
46
46
|
var DEFAULT_RESOURCE_CONFIG = {
|
|
@@ -48,6 +48,11 @@ var DEFAULT_RESOURCE_CONFIG = {
|
|
|
48
48
|
memoryMb: 512,
|
|
49
49
|
diskMb: 5120
|
|
50
50
|
};
|
|
51
|
+
var DEFAULT_BUILD_RESOURCE_CONFIG = {
|
|
52
|
+
cpuCores: PRICING.oblien.buildResources.cpuCores,
|
|
53
|
+
memoryMb: PRICING.oblien.buildResources.memoryMb,
|
|
54
|
+
diskMb: PRICING.oblien.buildResources.diskGb * 1024
|
|
55
|
+
};
|
|
51
56
|
function cloudCpus(cpuCores) {
|
|
52
57
|
return Math.max(0.25, cpuCores);
|
|
53
58
|
}
|
|
@@ -1367,6 +1372,16 @@ async function provisionCloudWorkspace(client, config, logger) {
|
|
|
1367
1372
|
throw err;
|
|
1368
1373
|
}
|
|
1369
1374
|
}
|
|
1375
|
+
var HostBuildForbiddenError = class extends Error {
|
|
1376
|
+
code = "HOST_BUILD_FORBIDDEN";
|
|
1377
|
+
constructor(message) {
|
|
1378
|
+
super(message);
|
|
1379
|
+
this.name = "HostBuildForbiddenError";
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
function isHostBuildForbiddenError(err) {
|
|
1383
|
+
return err instanceof HostBuildForbiddenError || err instanceof Error && err.name === "HostBuildForbiddenError";
|
|
1384
|
+
}
|
|
1370
1385
|
var CloudRuntime = class {
|
|
1371
1386
|
name = "cloud";
|
|
1372
1387
|
capabilities = /* @__PURE__ */ new Set([
|
|
@@ -1387,7 +1402,10 @@ var CloudRuntime = class {
|
|
|
1387
1402
|
// disk is still there to start back up (see makeActive).
|
|
1388
1403
|
"unitRestore",
|
|
1389
1404
|
"serviceShell",
|
|
1390
|
-
"inContainerExec"
|
|
1405
|
+
"inContainerExec",
|
|
1406
|
+
// Runs in the REMOTE workspace over the Oblien API, so it never touches the
|
|
1407
|
+
// control-plane host.
|
|
1408
|
+
"isolatedExec"
|
|
1391
1409
|
]);
|
|
1392
1410
|
/**
|
|
1393
1411
|
* Services map to Oblien workspaces, not Docker containers, so host-level
|
|
@@ -1396,7 +1414,18 @@ var CloudRuntime = class {
|
|
|
1396
1414
|
* logging/dns — join this set as they're added in later phases.
|
|
1397
1415
|
*/
|
|
1398
1416
|
unsupportedComposeKeys = /* @__PURE__ */ new Set([
|
|
1399
|
-
"healthcheck"
|
|
1417
|
+
"healthcheck",
|
|
1418
|
+
// Namespace sharing has no Oblien equivalent — a workspace is not a container
|
|
1419
|
+
// whose netns/pidns a peer can join. Declared here so the deploy warns once
|
|
1420
|
+
// per service and continues, rather than the workload coming up on its own
|
|
1421
|
+
// network with nothing having said so (#533).
|
|
1422
|
+
"networkMode",
|
|
1423
|
+
"pidMode",
|
|
1424
|
+
// A workspace is not a container whose ENTRYPOINT we compose: Oblien resolves its
|
|
1425
|
+
// own workload command, so an override (or a clearing `[]`) has nowhere to land
|
|
1426
|
+
// here. Declared so the deploy says so once per service and continues, rather
|
|
1427
|
+
// than the image's own launcher running with nothing having mentioned it (#575).
|
|
1428
|
+
"entrypoint"
|
|
1400
1429
|
]);
|
|
1401
1430
|
client;
|
|
1402
1431
|
// Optional admin-scoped proxy. Set when this runtime is constructed
|
|
@@ -1409,9 +1438,20 @@ var CloudRuntime = class {
|
|
|
1409
1438
|
builtArtifacts = /* @__PURE__ */ new Map();
|
|
1410
1439
|
activeBuilds = /* @__PURE__ */ new Map();
|
|
1411
1440
|
compose;
|
|
1441
|
+
/**
|
|
1442
|
+
* May this runtime do work on the machine the API process runs on?
|
|
1443
|
+
*
|
|
1444
|
+
* DEFAULT DENY, unlike every other optional knob on PlatformConfig. The permissive
|
|
1445
|
+
* default is what the two host arms below relied on, and the cost of forgetting to
|
|
1446
|
+
* set it is a tenant's build commands running on a multi-tenant control plane —
|
|
1447
|
+
* while the cost of a missed opt-IN is a self-hosted local-orchestrated cloud deploy
|
|
1448
|
+
* failing loudly with the message below. Loud beats silent here.
|
|
1449
|
+
*/
|
|
1450
|
+
allowHostBuild;
|
|
1412
1451
|
constructor(client, opts) {
|
|
1413
1452
|
this.client = client;
|
|
1414
1453
|
this.adminProxy = opts?.adminProxy;
|
|
1454
|
+
this.allowHostBuild = opts?.allowHostBuild ?? false;
|
|
1415
1455
|
this.compose = new CloudComposeSupport({
|
|
1416
1456
|
client,
|
|
1417
1457
|
builtArtifacts: this.builtArtifacts,
|
|
@@ -1420,6 +1460,16 @@ var CloudRuntime = class {
|
|
|
1420
1460
|
execAndStream: (runtime, command, onLog, timeoutSeconds) => this.execAndStream(runtime, command, onLog, timeoutSeconds)
|
|
1421
1461
|
});
|
|
1422
1462
|
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Refuse work on the API's own machine unless this runtime was explicitly built to
|
|
1465
|
+
* allow it. `what` completes the sentence "… is not allowed to <what>".
|
|
1466
|
+
*/
|
|
1467
|
+
assertHostWorkAllowed(what) {
|
|
1468
|
+
if (this.allowHostBuild) return;
|
|
1469
|
+
throw new HostBuildForbiddenError(
|
|
1470
|
+
`This deployment asked to ${what}, which is not permitted on this instance. Cloud builds run inside your Oblien workspace. If this project was moved here from a self-hosted install, redeploy it so its build settings are re-resolved for the cloud (its stored settings still say "build locally").`
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1423
1473
|
supports(cap) {
|
|
1424
1474
|
return this.capabilities.has(cap);
|
|
1425
1475
|
}
|
|
@@ -1496,6 +1546,11 @@ var CloudRuntime = class {
|
|
|
1496
1546
|
return activeBuild.abort.signal.aborted ? { ...result2, status: "cancelled", errorMessage: void 0 } : result2;
|
|
1497
1547
|
}
|
|
1498
1548
|
const buildLocally = config.buildStrategy === "local";
|
|
1549
|
+
if (buildLocally) {
|
|
1550
|
+
this.assertHostWorkAllowed(
|
|
1551
|
+
"run this project's install and build commands on the machine this API runs on"
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1499
1554
|
let wsId;
|
|
1500
1555
|
let rt;
|
|
1501
1556
|
try {
|
|
@@ -1629,6 +1684,9 @@ var CloudRuntime = class {
|
|
|
1629
1684
|
await this.ensureWorkspaceGit(rt, plog, "build workspace");
|
|
1630
1685
|
return;
|
|
1631
1686
|
}
|
|
1687
|
+
this.assertHostWorkAllowed(
|
|
1688
|
+
"package a source directory from the machine this API runs on"
|
|
1689
|
+
);
|
|
1632
1690
|
await transferLocalDirectory(
|
|
1633
1691
|
cfg.localPath,
|
|
1634
1692
|
{
|
|
@@ -1743,17 +1801,15 @@ var CloudRuntime = class {
|
|
|
1743
1801
|
if (!config.localPath) {
|
|
1744
1802
|
return this.resolveRemoteDockerfileBuildSource(config, logger);
|
|
1745
1803
|
}
|
|
1804
|
+
this.assertHostWorkAllowed("read and package a source tree from the machine this API runs on");
|
|
1746
1805
|
logger.log("Preparing local Dockerfile source...\n");
|
|
1747
1806
|
const context = await createDockerBuildContext(config, {
|
|
1748
1807
|
requireRepositoryDockerfile: true,
|
|
1749
1808
|
onLog: logger.callback
|
|
1750
1809
|
});
|
|
1751
|
-
const dockerfilePath =
|
|
1810
|
+
const dockerfilePath = resolveWithinDirectory(context.contextDir, context.dockerfileName);
|
|
1752
1811
|
const dockerfile = await readFile(dockerfilePath, "utf-8");
|
|
1753
|
-
const contextRoot =
|
|
1754
|
-
context.contextDir,
|
|
1755
|
-
...normalizeDockerRelativePath(context.rootDirectory).split("/").filter(Boolean)
|
|
1756
|
-
);
|
|
1812
|
+
const contextRoot = resolveWithinDirectory(context.contextDir, context.rootDirectory);
|
|
1757
1813
|
return {
|
|
1758
1814
|
kind: "local",
|
|
1759
1815
|
contextRoot,
|
|
@@ -3129,5 +3185,7 @@ function clampShellWindow(value, fallback, min, max) {
|
|
|
3129
3185
|
export {
|
|
3130
3186
|
PAGE_CONTAINER_PREFIX,
|
|
3131
3187
|
provisionCloudWorkspace,
|
|
3188
|
+
HostBuildForbiddenError,
|
|
3189
|
+
isHostBuildForbiddenError,
|
|
3132
3190
|
CloudRuntime
|
|
3133
3191
|
};
|
|
@@ -3,102 +3,31 @@
|
|
|
3
3
|
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
|
+
HOST_CHANNEL_AUTH_REJECTED,
|
|
7
|
+
HOST_CHANNEL_NOT_PROVISIONED,
|
|
8
|
+
HOST_CHANNEL_ROW_CREDENTIALS_UNUSED,
|
|
9
|
+
hostFirewallRule,
|
|
6
10
|
isSshAuthError,
|
|
11
|
+
safeErrorMessage,
|
|
7
12
|
systemDebug
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
PACKAGE_ROOT_ONLY_EXCLUDES
|
|
11
|
-
} from "./chunk-OTLSJYAC.js";
|
|
12
|
-
|
|
13
|
-
// ../../packages/adapters/src/archive.ts
|
|
14
|
-
import { execFile } from "child_process";
|
|
15
|
-
import { mkdtemp, rm, stat, writeFile } from "fs/promises";
|
|
16
|
-
import { tmpdir } from "os";
|
|
17
|
-
import { join } from "path";
|
|
18
|
-
import { promisify } from "util";
|
|
19
|
-
var execFileAsync = promisify(execFile);
|
|
20
|
-
var ROOT_ONLY_EXCLUDES = new Set(PACKAGE_ROOT_ONLY_EXCLUDES);
|
|
21
|
-
var NOOP_CLEANUP = async () => {
|
|
22
|
-
};
|
|
23
|
-
function getTarCreateEnv() {
|
|
24
|
-
return {
|
|
25
|
-
...process.env,
|
|
26
|
-
COPYFILE_DISABLE: "1",
|
|
27
|
-
COPY_EXTENDED_ATTRIBUTES_DISABLE: "1"
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function tarCreateBaseArgs(localPath) {
|
|
31
|
-
const darwinFlags = process.platform === "darwin" ? ["--no-mac-metadata", "--no-xattrs", "--no-acls", "--no-fflags"] : [];
|
|
32
|
-
return [...darwinFlags, "-czf", "-", "-C", localPath];
|
|
33
|
-
}
|
|
34
|
-
async function gitTrackedFiles(localPath) {
|
|
35
|
-
try {
|
|
36
|
-
const { stdout } = await execFileAsync(
|
|
37
|
-
"git",
|
|
38
|
-
["-C", localPath, "ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
39
|
-
{ maxBuffer: 256 * 1024 * 1024 }
|
|
40
|
-
);
|
|
41
|
-
const files = stdout.split("\0").filter(Boolean);
|
|
42
|
-
return files.length > 0 ? files : null;
|
|
43
|
-
} catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async function existingRelativePaths(root, paths) {
|
|
48
|
-
if (!paths?.length) return [];
|
|
49
|
-
const found = [];
|
|
50
|
-
for (const p of paths) {
|
|
51
|
-
try {
|
|
52
|
-
await stat(join(root, p));
|
|
53
|
-
found.push(p);
|
|
54
|
-
} catch {
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return found;
|
|
58
|
-
}
|
|
59
|
-
async function prepareSourceTarArgs(localPath, options) {
|
|
60
|
-
if (options?.includes?.length) {
|
|
61
|
-
return { args: getTarCreateArgs(localPath, options), cleanup: NOOP_CLEANUP };
|
|
62
|
-
}
|
|
63
|
-
const files = await gitTrackedFiles(localPath);
|
|
64
|
-
if (files) {
|
|
65
|
-
const extra = await existingRelativePaths(localPath, options?.alsoInclude);
|
|
66
|
-
const entries = extra.length ? [...files, ...extra] : files;
|
|
67
|
-
const tmpDir = await mkdtemp(join(tmpdir(), "openship-tarlist-"));
|
|
68
|
-
const listFile = join(tmpDir, "files.null");
|
|
69
|
-
await writeFile(listFile, entries.join("\0"));
|
|
70
|
-
const args = [...tarCreateBaseArgs(localPath), "--null", "-T", listFile];
|
|
71
|
-
return { args, cleanup: () => rm(tmpDir, { recursive: true, force: true }) };
|
|
72
|
-
}
|
|
73
|
-
return { args: getTarCreateArgs(localPath, options), cleanup: NOOP_CLEANUP };
|
|
74
|
-
}
|
|
75
|
-
function getTarCreateArgs(localPath, options) {
|
|
76
|
-
const args = tarCreateBaseArgs(localPath);
|
|
77
|
-
if (options?.includes?.length) {
|
|
78
|
-
args.push(...options.includes);
|
|
79
|
-
return args;
|
|
80
|
-
}
|
|
81
|
-
for (const exclude of options?.excludes ?? []) {
|
|
82
|
-
const pattern = ROOT_ONLY_EXCLUDES.has(exclude) ? `./${exclude}` : exclude;
|
|
83
|
-
args.push(`--exclude=${pattern}`);
|
|
84
|
-
}
|
|
85
|
-
args.push(".");
|
|
86
|
-
return args;
|
|
87
|
-
}
|
|
13
|
+
} from "./chunk-UJ6JZM65.js";
|
|
88
14
|
|
|
89
15
|
// ../../packages/adapters/src/system/ssh-client.ts
|
|
90
16
|
import { Client } from "ssh2";
|
|
91
17
|
|
|
92
18
|
// ../../packages/adapters/src/system/ssh-support.ts
|
|
93
|
-
import { execFile
|
|
19
|
+
import { execFile } from "child_process";
|
|
94
20
|
import { access } from "fs/promises";
|
|
95
21
|
import { homedir } from "os";
|
|
96
|
-
import { join
|
|
22
|
+
import { join } from "path";
|
|
97
23
|
function formatSshTarget(config) {
|
|
98
24
|
return `${config.username ?? "root"}@${config.host}:${config.port ?? 22}`;
|
|
99
25
|
}
|
|
100
26
|
function describeSshAuthFailure(config, originalMessage) {
|
|
101
27
|
const target = formatSshTarget(config);
|
|
28
|
+
if (config.hostChannel) {
|
|
29
|
+
return `${HOST_CHANNEL_AUTH_REJECTED} Dialed ${target} from inside the Openship API container. ${HOST_CHANNEL_ROW_CREDENTIALS_UNUSED} ${HOST_CHANNEL_NOT_PROVISIONED} (${originalMessage})`;
|
|
30
|
+
}
|
|
102
31
|
if (config.password) {
|
|
103
32
|
return `SSH password authentication failed for ${target}. Check the username/password, or verify that the server allows password login. (${originalMessage})`;
|
|
104
33
|
}
|
|
@@ -107,9 +36,19 @@ function describeSshAuthFailure(config, originalMessage) {
|
|
|
107
36
|
}
|
|
108
37
|
return `SSH authentication failed for ${target}. (${originalMessage})`;
|
|
109
38
|
}
|
|
39
|
+
function describeSshConnectFailure(config, originalMessage) {
|
|
40
|
+
const target = formatSshTarget(config);
|
|
41
|
+
const port = config.port ?? 22;
|
|
42
|
+
if (config.hostChannel) {
|
|
43
|
+
return `Cannot reach the host SSH endpoint ${target} from inside the Openship API container. Host control is configured, but the connection never completed. The usual cause is a host firewall dropping traffic from the Docker bridge to the host's SSH port \u2014 allow it with:
|
|
44
|
+
${hostFirewallRule("unknown", [], port)}
|
|
45
|
+
Or re-run \`openship up\`, which probes this and offers the exact rule. (${originalMessage})`;
|
|
46
|
+
}
|
|
47
|
+
return `Cannot reach ${target} over SSH. Check that the host is up, that port ${port} is open, and that no firewall or security group is dropping the connection. (${originalMessage})`;
|
|
48
|
+
}
|
|
110
49
|
function execFileText(command, args, timeout = 5e3) {
|
|
111
50
|
return new Promise((resolve, reject) => {
|
|
112
|
-
|
|
51
|
+
execFile(command, args, { timeout }, (err, stdout, stderr) => {
|
|
113
52
|
if (err) {
|
|
114
53
|
reject(new Error(stderr.trim() || stdout.trim() || err.message));
|
|
115
54
|
return;
|
|
@@ -131,7 +70,7 @@ function parseKnownHostsEntries(text) {
|
|
|
131
70
|
return entries;
|
|
132
71
|
}
|
|
133
72
|
async function reconcileKnownHosts(config) {
|
|
134
|
-
const knownHostsPath =
|
|
73
|
+
const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
|
|
135
74
|
try {
|
|
136
75
|
await access(knownHostsPath);
|
|
137
76
|
} catch {
|
|
@@ -205,6 +144,10 @@ function toConnectConfig(config) {
|
|
|
205
144
|
passphrase: config.privateKeyPassphrase,
|
|
206
145
|
agent: config.sshAgent,
|
|
207
146
|
tryKeyboard: false,
|
|
147
|
+
// Explicit rather than relying on ssh2's implicit 20s so callers can shorten it.
|
|
148
|
+
// The container→host channel does (8s): a filtered SYN there would otherwise
|
|
149
|
+
// stall every host operation for 20s apiece with no output.
|
|
150
|
+
readyTimeout: config.readyTimeoutMs ?? 2e4,
|
|
208
151
|
keepaliveInterval: 15e3,
|
|
209
152
|
// 10 (~150s) rather than 3 (45s): a small server pegged by a heavy
|
|
210
153
|
// `docker build` / `bun install` can briefly starve sshd of keepalive
|
|
@@ -230,12 +173,12 @@ async function connectSshClient(config) {
|
|
|
230
173
|
reject(new Error(describeSshAuthFailure(config, err.message)));
|
|
231
174
|
return;
|
|
232
175
|
}
|
|
233
|
-
reject(err);
|
|
176
|
+
reject(new Error(describeSshConnectFailure(config, err.message)));
|
|
234
177
|
});
|
|
235
178
|
client.on("close", () => {
|
|
236
179
|
if (settled) return;
|
|
237
180
|
settled = true;
|
|
238
|
-
reject(new Error("SSH connection closed before ready"));
|
|
181
|
+
reject(new Error(describeSshConnectFailure(config, "SSH connection closed before ready")));
|
|
239
182
|
});
|
|
240
183
|
client.connect(toConnectConfig(config));
|
|
241
184
|
});
|
|
@@ -263,6 +206,7 @@ async function openSshUnixSocket(client, socketPath) {
|
|
|
263
206
|
});
|
|
264
207
|
});
|
|
265
208
|
}
|
|
209
|
+
var DOCKER_DIAL_STDIO_COMMAND = "docker system dial-stdio";
|
|
266
210
|
async function openSshExecChannel(client, command) {
|
|
267
211
|
return new Promise((resolve, reject) => {
|
|
268
212
|
client.exec(command, (err, stream) => {
|
|
@@ -274,6 +218,50 @@ async function openSshExecChannel(client, command) {
|
|
|
274
218
|
});
|
|
275
219
|
});
|
|
276
220
|
}
|
|
221
|
+
var DIAL_STDIO_DIAGNOSTICS = /* @__PURE__ */ Symbol("openship.dialStdioDiagnostics");
|
|
222
|
+
var DIAL_STDIO_STDERR_CAP = 4096;
|
|
223
|
+
function attachDialStdioDiagnostics(stream) {
|
|
224
|
+
const channel = stream;
|
|
225
|
+
if (channel[DIAL_STDIO_DIAGNOSTICS]) return stream;
|
|
226
|
+
const diagnostics = {
|
|
227
|
+
stderr: "",
|
|
228
|
+
exitCode: null,
|
|
229
|
+
signal: null,
|
|
230
|
+
error: null
|
|
231
|
+
};
|
|
232
|
+
channel[DIAL_STDIO_DIAGNOSTICS] = diagnostics;
|
|
233
|
+
const stderr = stream.stderr;
|
|
234
|
+
stderr?.on("data", (chunk) => {
|
|
235
|
+
if (diagnostics.stderr.length >= DIAL_STDIO_STDERR_CAP) return;
|
|
236
|
+
diagnostics.stderr = (diagnostics.stderr + chunk.toString("utf8")).slice(
|
|
237
|
+
0,
|
|
238
|
+
DIAL_STDIO_STDERR_CAP
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
stream.on("exit", (code, signal) => {
|
|
242
|
+
diagnostics.exitCode = typeof code === "number" ? code : null;
|
|
243
|
+
diagnostics.signal = typeof signal === "string" ? signal : null;
|
|
244
|
+
});
|
|
245
|
+
stream.on("error", (err) => {
|
|
246
|
+
diagnostics.error ??= safeErrorMessage(err);
|
|
247
|
+
});
|
|
248
|
+
return stream;
|
|
249
|
+
}
|
|
250
|
+
function dialStdioDiagnostics(stream) {
|
|
251
|
+
const diagnostics = stream?.[DIAL_STDIO_DIAGNOSTICS];
|
|
252
|
+
if (!diagnostics) return "";
|
|
253
|
+
const parts = [];
|
|
254
|
+
const stderr = diagnostics.stderr.trim();
|
|
255
|
+
if (stderr) parts.push(stderr.replace(/\s+/g, " "));
|
|
256
|
+
if (diagnostics.error) parts.push(diagnostics.error);
|
|
257
|
+
if (diagnostics.signal) parts.push(`killed by ${diagnostics.signal}`);
|
|
258
|
+
else if (diagnostics.exitCode) parts.push(`exit code ${diagnostics.exitCode}`);
|
|
259
|
+
if (parts.length === 0) return "";
|
|
260
|
+
return `\`${DOCKER_DIAL_STDIO_COMMAND}\` reported: ${parts.join(" \u2014 ")}`;
|
|
261
|
+
}
|
|
262
|
+
async function openDockerDialStdioChannel(client) {
|
|
263
|
+
return attachDialStdioDiagnostics(await openSshExecChannel(client, DOCKER_DIAL_STDIO_COMMAND));
|
|
264
|
+
}
|
|
277
265
|
async function execSshCommand(client, command, timeout = 1e4) {
|
|
278
266
|
return new Promise((resolve, reject) => {
|
|
279
267
|
let stdout = "";
|
|
@@ -319,12 +307,14 @@ async function execSshCommand(client, command, timeout = 1e4) {
|
|
|
319
307
|
}
|
|
320
308
|
|
|
321
309
|
export {
|
|
322
|
-
getTarCreateEnv,
|
|
323
|
-
prepareSourceTarArgs,
|
|
324
310
|
reconcileKnownHosts,
|
|
325
311
|
connectSshClient,
|
|
326
312
|
openSftp,
|
|
327
313
|
openSshUnixSocket,
|
|
314
|
+
DOCKER_DIAL_STDIO_COMMAND,
|
|
328
315
|
openSshExecChannel,
|
|
316
|
+
attachDialStdioDiagnostics,
|
|
317
|
+
dialStdioDiagnostics,
|
|
318
|
+
openDockerDialStdioChannel,
|
|
329
319
|
execSshCommand
|
|
330
320
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
|
|
3
|
+
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
|
+
const require = __ospCreateRequire(import.meta.url);
|
|
5
|
+
import {
|
|
6
|
+
PACKAGE_ROOT_ONLY_EXCLUDES
|
|
7
|
+
} from "./chunk-UJ6JZM65.js";
|
|
8
|
+
|
|
9
|
+
// ../../packages/adapters/src/archive.ts
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
import { mkdtemp, rm, stat, writeFile } from "fs/promises";
|
|
12
|
+
import { tmpdir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { promisify } from "util";
|
|
15
|
+
var execFileAsync = promisify(execFile);
|
|
16
|
+
var ROOT_ONLY_EXCLUDES = new Set(PACKAGE_ROOT_ONLY_EXCLUDES);
|
|
17
|
+
var NOOP_CLEANUP = async () => {
|
|
18
|
+
};
|
|
19
|
+
function getTarCreateEnv() {
|
|
20
|
+
return {
|
|
21
|
+
...process.env,
|
|
22
|
+
COPYFILE_DISABLE: "1",
|
|
23
|
+
COPY_EXTENDED_ATTRIBUTES_DISABLE: "1"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function tarCreateBaseArgs(localPath) {
|
|
27
|
+
const darwinFlags = process.platform === "darwin" ? ["--no-mac-metadata", "--no-xattrs", "--no-acls", "--no-fflags"] : [];
|
|
28
|
+
return [...darwinFlags, "-czf", "-", "-C", localPath];
|
|
29
|
+
}
|
|
30
|
+
async function gitTrackedFiles(localPath) {
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await execFileAsync(
|
|
33
|
+
"git",
|
|
34
|
+
["-C", localPath, "ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
35
|
+
{ maxBuffer: 256 * 1024 * 1024 }
|
|
36
|
+
);
|
|
37
|
+
const files = stdout.split("\0").filter(Boolean);
|
|
38
|
+
return files.length > 0 ? files : null;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function existingRelativePaths(root, paths) {
|
|
44
|
+
if (!paths?.length) return [];
|
|
45
|
+
const found = [];
|
|
46
|
+
for (const p of paths) {
|
|
47
|
+
try {
|
|
48
|
+
await stat(join(root, p));
|
|
49
|
+
found.push(p);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
async function prepareSourceTarArgs(localPath, options) {
|
|
56
|
+
if (options?.includes?.length) {
|
|
57
|
+
return { args: getTarCreateArgs(localPath, options), cleanup: NOOP_CLEANUP };
|
|
58
|
+
}
|
|
59
|
+
const files = await gitTrackedFiles(localPath);
|
|
60
|
+
if (files) {
|
|
61
|
+
const extra = await existingRelativePaths(localPath, options?.alsoInclude);
|
|
62
|
+
const entries = extra.length ? [...files, ...extra] : files;
|
|
63
|
+
const tmpDir = await mkdtemp(join(tmpdir(), "openship-tarlist-"));
|
|
64
|
+
const listFile = join(tmpDir, "files.null");
|
|
65
|
+
await writeFile(listFile, entries.join("\0"));
|
|
66
|
+
const args = [...tarCreateBaseArgs(localPath), "--null", "-T", listFile];
|
|
67
|
+
return { args, cleanup: () => rm(tmpDir, { recursive: true, force: true }) };
|
|
68
|
+
}
|
|
69
|
+
return { args: getTarCreateArgs(localPath, options), cleanup: NOOP_CLEANUP };
|
|
70
|
+
}
|
|
71
|
+
function getTarCreateArgs(localPath, options) {
|
|
72
|
+
const args = tarCreateBaseArgs(localPath);
|
|
73
|
+
if (options?.includes?.length) {
|
|
74
|
+
args.push(...options.includes);
|
|
75
|
+
return args;
|
|
76
|
+
}
|
|
77
|
+
for (const exclude of options?.excludes ?? []) {
|
|
78
|
+
const pattern = ROOT_ONLY_EXCLUDES.has(exclude) ? `./${exclude}` : exclude;
|
|
79
|
+
args.push(`--exclude=${pattern}`);
|
|
80
|
+
}
|
|
81
|
+
args.push(".");
|
|
82
|
+
return args;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
getTarCreateEnv,
|
|
87
|
+
prepareSourceTarArgs
|
|
88
|
+
};
|