openship 0.6.0 → 0.6.5
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-FSUAM5V5.js → bare-XQIPDO4R.js} +10 -9
- package/dist/{chunk-PSZSKEPA.js → chunk-3ZAGC5NT.js} +64 -17
- package/dist/{chunk-6ODUBKUB.js → chunk-5OUWIPOB.js} +69 -7
- package/dist/chunk-6HOA5JT7.js +124 -0
- package/dist/{chunk-TWJCLD4W.js → chunk-6JELW75A.js} +4 -4
- package/dist/{chunk-GUQ3QCJ7.js → chunk-7334NSJL.js} +399 -369
- package/dist/chunk-7S4USQYF.js +1753 -0
- package/dist/{chunk-3LDWWY7U.js → chunk-A7EWM2KA.js} +41 -7
- package/dist/chunk-ELGRMZOW.js +25 -0
- package/dist/chunk-EZPPTWJQ.js +292 -0
- package/dist/{chunk-IXM5245F.js → chunk-GRTXMUHA.js} +553 -538
- package/dist/chunk-HPXP6LXZ.js +428 -0
- package/dist/{chunk-OTLSJYAC.js → chunk-LEYSL46B.js} +2135 -85
- package/dist/{chunk-EMXI4UCU.js → chunk-LFCSC4IU.js} +29 -10
- package/dist/{chunk-JIMUYEKN.js → chunk-MSZUCNPV.js} +1 -1
- package/dist/{chunk-OGY63DGD.js → chunk-PZRRL5PC.js} +71 -28
- package/dist/{chunk-VE2YOK4I.js → chunk-QLX7NBEA.js} +1218 -588
- package/dist/chunk-RFWJE7ZN.js +68 -0
- package/dist/{chunk-VDDMADOF.js → chunk-TM2IQPYU.js} +16 -2
- package/dist/{chunk-3FYIXVX5.js → chunk-VDESJSG6.js} +451 -106
- package/dist/{chunk-B3CE2OMR.js → chunk-VHMOQL5Y.js} +276 -50
- package/dist/{chunk-QEH4QUBX.js → chunk-VLQ3UIVO.js} +393 -53
- package/dist/{chunk-VZ2ZHPS2.js → chunk-WE7BXW6L.js} +3 -9
- package/dist/chunk-WFRXYWK2.js +444 -0
- package/dist/chunk-XRNRT37W.js +316 -0
- package/dist/{chunk-EP2TNALB.js → chunk-XVOBL7BC.js} +31 -8
- package/dist/cloud-3DODOWTR.js +36 -0
- package/dist/{detect-ALF4GXRX.js → detect-P4CIQBRH.js} +3 -3
- package/dist/{docker-QECQJ5P4.js → docker-3K2NEBD6.js} +15 -8
- package/dist/{docker-edge-executor-62N64MGS.js → docker-edge-executor-YZZ6P7IC.js} +5 -5
- package/dist/{edge-import-CQWHN2Q3.js → edge-import-MCJNVTZ2.js} +26 -23
- package/dist/edge-preflight-ES6FH4JK.js +42 -0
- package/dist/{ensure-container-edge-MJVXJQG5.js → ensure-container-edge-YPITNH45.js} +9 -8
- package/dist/{executor-JELARVKC.js → executor-DZGX55K2.js} +12 -5
- package/dist/host-channel-preflight-MUXE3KZU.js +49 -0
- package/dist/index.js +3668 -4063
- package/dist/{nginx-AXGNWRKZ.js → nginx-NSAHWGJV.js} +11 -5
- package/dist/node-bootstrap.js +241 -0
- package/dist/node-entry.js +52 -1
- package/dist/{openresty-lua-IE4P5YQX.js → openresty-lua-DR4D3EZ6.js} +6 -2
- package/dist/privilege-YTVI4SKF.js +19 -0
- package/dist/server/engine/samples/fail2ban/jail.d/sshd.local +4 -1
- package/dist/server/index.js +21501 -13137
- package/dist/server/lua/geo_country.lua +4 -3
- package/dist/server/lua/pipe_stream.lua +52 -39
- package/dist/server/lua/site_logger.lua +22 -24
- 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/meta/_journal.json +49 -0
- package/dist/{setup-FA4PW4NU.js → setup-WUUZURHS.js} +10 -9
- package/dist/up-XEO3SR6Z.js +58 -0
- package/package.json +4 -2
- package/dist/chunk-IW2UKBVF.js +0 -144
- package/dist/chunk-ZCFVHA25.js +0 -89
- package/dist/cloud-E5M6VMCG.js +0 -31
- package/dist/server/lua/pipe_log.lua +0 -77
- /package/dist/{dist-es-JGBPABJI.js → dist-es-ZP5QDSJ3.js} +0 -0
|
@@ -5,14 +5,12 @@ const require = __ospCreateRequire(import.meta.url);
|
|
|
5
5
|
import {
|
|
6
6
|
connectSshClient,
|
|
7
7
|
getTarCreateEnv,
|
|
8
|
+
openDockerDialStdioChannel,
|
|
8
9
|
openSftp,
|
|
9
10
|
openSshUnixSocket,
|
|
10
11
|
prepareSourceTarArgs,
|
|
11
12
|
reconcileKnownHosts
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import {
|
|
14
|
-
SshDisconnectedError
|
|
15
|
-
} from "./chunk-ZCFVHA25.js";
|
|
13
|
+
} from "./chunk-5OUWIPOB.js";
|
|
16
14
|
import {
|
|
17
15
|
LocalExecutor
|
|
18
16
|
} from "./chunk-6FH4AXVY.js";
|
|
@@ -24,13 +22,77 @@ import {
|
|
|
24
22
|
sq
|
|
25
23
|
} from "./chunk-ZFJXPCQP.js";
|
|
26
24
|
import {
|
|
25
|
+
HOST_CHANNEL_NOT_PROVISIONED,
|
|
26
|
+
HOST_CHANNEL_UNPROVISIONED,
|
|
27
|
+
HostChannelUnavailableError,
|
|
28
|
+
SshDisconnectedError,
|
|
27
29
|
TRANSFER_EXCLUDES,
|
|
30
|
+
commandForError,
|
|
31
|
+
explainHostChannelCause,
|
|
28
32
|
formatBytes,
|
|
33
|
+
hostFirewallRule,
|
|
29
34
|
safeErrorMessage
|
|
30
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-LEYSL46B.js";
|
|
31
36
|
|
|
32
37
|
// ../../packages/adapters/src/system/executor.ts
|
|
33
38
|
import { existsSync, readFileSync } from "fs";
|
|
39
|
+
import { networkInterfaces } from "os";
|
|
40
|
+
|
|
41
|
+
// ../../packages/adapters/src/system/reachability.ts
|
|
42
|
+
import { connect } from "net";
|
|
43
|
+
import { request } from "http";
|
|
44
|
+
function classifyDialError(code) {
|
|
45
|
+
switch (code) {
|
|
46
|
+
case "ECONNREFUSED":
|
|
47
|
+
case "ECONNRESET":
|
|
48
|
+
return "refused";
|
|
49
|
+
case "ENOTFOUND":
|
|
50
|
+
case "EAI_AGAIN":
|
|
51
|
+
return "unresolved";
|
|
52
|
+
case "EHOSTUNREACH":
|
|
53
|
+
case "ENETUNREACH":
|
|
54
|
+
case "ENETDOWN":
|
|
55
|
+
return "no_route";
|
|
56
|
+
// The kernel's own connect timeout, i.e. our `timeout` reported for us.
|
|
57
|
+
case "ETIMEDOUT":
|
|
58
|
+
return "timeout";
|
|
59
|
+
default:
|
|
60
|
+
return "error";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function probeTcpDetailed(host, port, timeoutMs = 2500) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
let settled = false;
|
|
66
|
+
const done = (result, socket2) => {
|
|
67
|
+
if (settled) return;
|
|
68
|
+
settled = true;
|
|
69
|
+
try {
|
|
70
|
+
socket2?.destroy();
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
resolve(result);
|
|
74
|
+
};
|
|
75
|
+
const socket = connect({ host, port });
|
|
76
|
+
socket.setTimeout(timeoutMs);
|
|
77
|
+
socket.once("connect", () => done({ ok: true }, socket));
|
|
78
|
+
socket.once(
|
|
79
|
+
"timeout",
|
|
80
|
+
() => done({ ok: false, reason: "timeout", message: `no response within ${timeoutMs}ms` }, socket)
|
|
81
|
+
);
|
|
82
|
+
socket.once("error", (err) => {
|
|
83
|
+
const errno = err;
|
|
84
|
+
done(
|
|
85
|
+
{
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: classifyDialError(errno.code),
|
|
88
|
+
...errno.code ? { code: errno.code } : {},
|
|
89
|
+
...errno.message ? { message: errno.message } : {}
|
|
90
|
+
},
|
|
91
|
+
socket
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
34
96
|
|
|
35
97
|
// ../../packages/adapters/src/system/ssh-executor.ts
|
|
36
98
|
import { createReadStream } from "fs";
|
|
@@ -240,7 +302,7 @@ var SshExecutor = class _SshExecutor {
|
|
|
240
302
|
async connect() {
|
|
241
303
|
if (this.client) return this.client;
|
|
242
304
|
if (this.connecting) return this.connecting;
|
|
243
|
-
|
|
305
|
+
const attempt = (async () => {
|
|
244
306
|
const client = await connectSshClient(this.config);
|
|
245
307
|
const onTransportDown = (cause) => {
|
|
246
308
|
if (this.client !== client) return;
|
|
@@ -252,10 +314,14 @@ var SshExecutor = class _SshExecutor {
|
|
|
252
314
|
client.on("end", () => onTransportDown());
|
|
253
315
|
client.on("error", (err) => onTransportDown(err));
|
|
254
316
|
this.client = client;
|
|
255
|
-
this.connecting = null;
|
|
256
317
|
return client;
|
|
257
318
|
})();
|
|
258
|
-
|
|
319
|
+
this.connecting = attempt;
|
|
320
|
+
const clearMemo = () => {
|
|
321
|
+
if (this.connecting === attempt) this.connecting = null;
|
|
322
|
+
};
|
|
323
|
+
attempt.then(clearMemo, clearMemo);
|
|
324
|
+
return attempt;
|
|
259
325
|
}
|
|
260
326
|
/**
|
|
261
327
|
* Subscribe to transport-level disconnects. Returns an unsubscribe fn.
|
|
@@ -402,7 +468,9 @@ var SshExecutor = class _SshExecutor {
|
|
|
402
468
|
};
|
|
403
469
|
this.inflight.add(abort);
|
|
404
470
|
timer = setTimeout(
|
|
405
|
-
() => finish(
|
|
471
|
+
() => finish(
|
|
472
|
+
() => reject(new Error(`Command timed out after ${timeout}ms: ${commandForError(command)}`))
|
|
473
|
+
),
|
|
406
474
|
timeout
|
|
407
475
|
);
|
|
408
476
|
client.exec(_SshExecutor.ENV_PREFIX + command, (err, stream) => {
|
|
@@ -426,30 +494,47 @@ var SshExecutor = class _SshExecutor {
|
|
|
426
494
|
});
|
|
427
495
|
});
|
|
428
496
|
}
|
|
429
|
-
streamExec(command, onLog) {
|
|
430
|
-
return this._streamExec(command, onLog).catch((err) => {
|
|
497
|
+
streamExec(command, onLog, opts) {
|
|
498
|
+
return this._streamExec(command, onLog, opts).catch((err) => {
|
|
431
499
|
if (_SshExecutor.isChannelError(err)) {
|
|
432
500
|
this.recoverFromChannelError();
|
|
433
|
-
return this._streamExec(command, onLog);
|
|
501
|
+
return this._streamExec(command, onLog, opts);
|
|
434
502
|
}
|
|
435
503
|
throw err;
|
|
436
504
|
});
|
|
437
505
|
}
|
|
438
|
-
async _streamExec(command, onLog) {
|
|
506
|
+
async _streamExec(command, onLog, opts) {
|
|
439
507
|
const client = await this.connect();
|
|
508
|
+
const signal = opts?.signal;
|
|
440
509
|
return new Promise((resolve, reject) => {
|
|
441
510
|
let settled = false;
|
|
511
|
+
const chunks = [];
|
|
442
512
|
const abort = (err) => finish(() => reject(err));
|
|
443
513
|
const finish = (act) => {
|
|
444
514
|
if (settled) return;
|
|
445
515
|
settled = true;
|
|
446
516
|
this.inflight.delete(abort);
|
|
517
|
+
signal?.removeEventListener("abort", onSignalAbort);
|
|
447
518
|
act();
|
|
448
519
|
};
|
|
449
520
|
this.inflight.add(abort);
|
|
521
|
+
let channel = null;
|
|
522
|
+
const onSignalAbort = () => {
|
|
523
|
+
finish(() => resolve({ code: 0, output: chunks.join("") }));
|
|
524
|
+
try {
|
|
525
|
+
channel?.close();
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
if (signal?.aborted) {
|
|
530
|
+
finish(() => resolve({ code: 0, output: "" }));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
450
533
|
client.exec(_SshExecutor.ENV_PREFIX + command, (err, stream) => {
|
|
451
534
|
if (err) return finish(() => reject(err));
|
|
452
|
-
|
|
535
|
+
channel = stream;
|
|
536
|
+
if (signal?.aborted) return onSignalAbort();
|
|
537
|
+
signal?.addEventListener("abort", onSignalAbort, { once: true });
|
|
453
538
|
const onChunk = (data, level) => {
|
|
454
539
|
const text = data.toString();
|
|
455
540
|
if (!text) return;
|
|
@@ -689,24 +774,23 @@ var SshExecutor = class _SshExecutor {
|
|
|
689
774
|
* Carry the Docker Engine API over a `docker system dial-stdio` exec channel
|
|
690
775
|
* on the pooled connection — the streamlocal-free transport used when the
|
|
691
776
|
* SSH server (or the Bun-compiled desktop runtime) can't do socket
|
|
692
|
-
* forwarding.
|
|
693
|
-
*
|
|
777
|
+
* forwarding.
|
|
778
|
+
*
|
|
779
|
+
* Deliberately WITHOUT `ENV_PREFIX`: that prefix only exports apt's
|
|
780
|
+
* non-interactive variables (it sets no PATH, despite what the comment here
|
|
781
|
+
* used to claim), and the bridge's own ephemeral dial-stdio client opens the
|
|
782
|
+
* bare command. Two dial-stdio channels that differ by a shell prefix are two
|
|
783
|
+
* transports, and the bridge picks one by probing the other.
|
|
784
|
+
*
|
|
785
|
+
* Failure diagnostics (daemon down, socket permission, `docker: command not
|
|
786
|
+
* found`, an sshd ForceCommand banner) ride ON the returned channel — see
|
|
787
|
+
* `attachDialStdioDiagnostics`. They used to go to `console.warn` while the
|
|
788
|
+
* caller got a bare socket reset, which is how a host with no running dockerd
|
|
789
|
+
* reported itself as `socket hang up`.
|
|
694
790
|
*/
|
|
695
791
|
async openDockerDialStdio() {
|
|
696
792
|
const client = await this.connect();
|
|
697
|
-
return
|
|
698
|
-
client.exec(_SshExecutor.ENV_PREFIX + "docker system dial-stdio", (err, stream) => {
|
|
699
|
-
if (err) return reject(err);
|
|
700
|
-
stream.stderr?.on("data", (d) => {
|
|
701
|
-
const text = d.toString().trim();
|
|
702
|
-
if (text) console.warn(`[docker-ssh] dial-stdio stderr: ${text}`);
|
|
703
|
-
});
|
|
704
|
-
stream.on("exit", (code) => {
|
|
705
|
-
if (code) console.warn(`[docker-ssh] dial-stdio exited early (code=${code})`);
|
|
706
|
-
});
|
|
707
|
-
resolve(stream);
|
|
708
|
-
});
|
|
709
|
-
});
|
|
793
|
+
return openDockerDialStdioChannel(client);
|
|
710
794
|
}
|
|
711
795
|
async forwardPort(remoteHost, remotePort) {
|
|
712
796
|
const client = await this.connect();
|
|
@@ -1183,7 +1267,7 @@ var SystemSshExecutor = class _SystemSshExecutor {
|
|
|
1183
1267
|
const timeout = opts?.timeout ?? 3e4;
|
|
1184
1268
|
const res = await this.runSsh(_SystemSshExecutor.ENV_PREFIX + command, { timeout });
|
|
1185
1269
|
if (res.timedOut) {
|
|
1186
|
-
throw new Error(`Command timed out after ${timeout}ms: ${command}`);
|
|
1270
|
+
throw new Error(`Command timed out after ${timeout}ms: ${commandForError(command)}`);
|
|
1187
1271
|
}
|
|
1188
1272
|
if (res.code !== 0) {
|
|
1189
1273
|
if (res.code === 255) throw describeSshFailure(res.stderr, `Exit code ${res.code}`);
|
|
@@ -1191,14 +1275,22 @@ var SystemSshExecutor = class _SystemSshExecutor {
|
|
|
1191
1275
|
}
|
|
1192
1276
|
return res.stdout.trim();
|
|
1193
1277
|
}
|
|
1194
|
-
async streamExec(command, onLog) {
|
|
1278
|
+
async streamExec(command, onLog, opts) {
|
|
1195
1279
|
await this.ensureMaster();
|
|
1280
|
+
const signal = opts?.signal;
|
|
1281
|
+
if (signal?.aborted) return { code: 0, output: "" };
|
|
1196
1282
|
return new Promise((resolve) => {
|
|
1197
1283
|
const child = spawn2(
|
|
1198
1284
|
"ssh",
|
|
1199
1285
|
[...this.baseArgs(), sshTarget(this.config), _SystemSshExecutor.ENV_PREFIX + command],
|
|
1200
1286
|
{ env: sshChildEnv(this.config), stdio: ["ignore", "pipe", "pipe"] }
|
|
1201
1287
|
);
|
|
1288
|
+
let aborted = false;
|
|
1289
|
+
const onAbort = () => {
|
|
1290
|
+
aborted = true;
|
|
1291
|
+
child.kill("SIGKILL");
|
|
1292
|
+
};
|
|
1293
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1202
1294
|
const chunks = [];
|
|
1203
1295
|
const onChunk = (chunk, level) => {
|
|
1204
1296
|
const text = chunk.toString();
|
|
@@ -1209,11 +1301,13 @@ var SystemSshExecutor = class _SystemSshExecutor {
|
|
|
1209
1301
|
child.stdout.on("data", (chunk) => onChunk(chunk, "info"));
|
|
1210
1302
|
child.stderr.on("data", (chunk) => onChunk(chunk, "warn"));
|
|
1211
1303
|
child.on("error", (err) => {
|
|
1304
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1212
1305
|
onLog(logEntry(`Process error: ${err.message}`, "error"));
|
|
1213
1306
|
resolve({ code: 1, output: err.message });
|
|
1214
1307
|
});
|
|
1215
1308
|
child.on("close", (code) => {
|
|
1216
|
-
|
|
1309
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1310
|
+
const c = aborted ? 0 : code ?? 1;
|
|
1217
1311
|
void this.maybeSignalDisconnect(c).catch(() => {
|
|
1218
1312
|
});
|
|
1219
1313
|
resolve({ code: c, output: chunks.join("") });
|
|
@@ -1507,8 +1601,12 @@ function createExecutor(ssh) {
|
|
|
1507
1601
|
}
|
|
1508
1602
|
return localExecutor;
|
|
1509
1603
|
}
|
|
1604
|
+
var hostControlOverride = null;
|
|
1605
|
+
function setHostControlOverride(disabled) {
|
|
1606
|
+
hostControlOverride = disabled;
|
|
1607
|
+
}
|
|
1510
1608
|
function hostControlDisabled() {
|
|
1511
|
-
return process.env.OPENSHIP_HOST_CONTROL?.trim().toLowerCase() === "false";
|
|
1609
|
+
return hostControlOverride ?? process.env.OPENSHIP_HOST_CONTROL?.trim().toLowerCase() === "false";
|
|
1512
1610
|
}
|
|
1513
1611
|
function runningInContainer() {
|
|
1514
1612
|
if (process.env.OPENSHIP_IN_CONTAINER?.trim().toLowerCase() === "true") return true;
|
|
@@ -1522,39 +1620,163 @@ function runningInContainer() {
|
|
|
1522
1620
|
return false;
|
|
1523
1621
|
}
|
|
1524
1622
|
}
|
|
1623
|
+
function hostChannelPort() {
|
|
1624
|
+
const raw = Number(process.env.OPENSHIP_HOST_SSH_PORT || "22");
|
|
1625
|
+
return Number.isInteger(raw) && raw > 0 && raw < 65536 ? raw : 22;
|
|
1626
|
+
}
|
|
1627
|
+
function hostChannelUser() {
|
|
1628
|
+
return process.env.OPENSHIP_HOST_SSH_USER?.trim() || "root";
|
|
1629
|
+
}
|
|
1630
|
+
function readHostChannelKey(keyPath) {
|
|
1631
|
+
let contents;
|
|
1632
|
+
try {
|
|
1633
|
+
contents = readFileSync(keyPath, "utf8");
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1636
|
+
return { reason: `Cannot read the host SSH key at ${keyPath} (${detail}).` };
|
|
1637
|
+
}
|
|
1638
|
+
if (!contents.trim()) return { reason: `The host SSH key at ${keyPath} is empty.` };
|
|
1639
|
+
return { key: contents };
|
|
1640
|
+
}
|
|
1641
|
+
function containerBridgeCidr() {
|
|
1642
|
+
for (const addrs of Object.values(networkInterfaces())) {
|
|
1643
|
+
for (const addr of addrs ?? []) {
|
|
1644
|
+
if (addr.family !== "IPv4" || addr.internal) continue;
|
|
1645
|
+
const prefix = Number(addr.cidr?.split("/")[1]);
|
|
1646
|
+
if (!Number.isInteger(prefix) || prefix < 8 || prefix > 32) continue;
|
|
1647
|
+
const octets = addr.address.split(".").map(Number);
|
|
1648
|
+
if (octets.length !== 4 || octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) continue;
|
|
1649
|
+
const asInt = (octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]) >>> 0;
|
|
1650
|
+
const mask = 4294967295 << 32 - prefix >>> 0;
|
|
1651
|
+
const net = (asInt & mask) >>> 0;
|
|
1652
|
+
return `${net >>> 24 & 255}.${net >>> 16 & 255}.${net >>> 8 & 255}.${net & 255}/${prefix}`;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
function probeDetail(probe) {
|
|
1658
|
+
const { code, message } = probe;
|
|
1659
|
+
if (code && message && !message.includes(code)) return `${code}: ${message}`;
|
|
1660
|
+
return code ?? message;
|
|
1661
|
+
}
|
|
1662
|
+
function explainDialFailure(probe, host, port, target) {
|
|
1663
|
+
const { headline, body, firewallShaped } = explainHostChannelCause(probe.reason, {
|
|
1664
|
+
target,
|
|
1665
|
+
host,
|
|
1666
|
+
detail: probeDetail(probe)
|
|
1667
|
+
});
|
|
1668
|
+
const hint = body ? `${headline}. ${body}` : `${headline}.`;
|
|
1669
|
+
if (!firewallShaped) return { hint };
|
|
1670
|
+
const cidr = containerBridgeCidr();
|
|
1671
|
+
return { hint, rule: hostFirewallRule("unknown", cidr ? [cidr] : [], port) };
|
|
1672
|
+
}
|
|
1673
|
+
async function hostChannelHealth(timeoutMs = 2500) {
|
|
1674
|
+
if (hostControlDisabled()) {
|
|
1675
|
+
return {
|
|
1676
|
+
ok: false,
|
|
1677
|
+
code: "disabled",
|
|
1678
|
+
hint: "Host control is off (OPENSHIP_HOST_CONTROL=false). Re-run `openship up` without --no-host-control."
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
const host = process.env.OPENSHIP_HOST_SSH_HOST?.trim();
|
|
1682
|
+
if (!host) {
|
|
1683
|
+
if (runningInContainer()) {
|
|
1684
|
+
return {
|
|
1685
|
+
ok: false,
|
|
1686
|
+
code: "not_configured",
|
|
1687
|
+
hint: `${HOST_CHANNEL_UNPROVISIONED} ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
return { ok: true, code: "not_applicable" };
|
|
1691
|
+
}
|
|
1692
|
+
const port = hostChannelPort();
|
|
1693
|
+
const target = `${hostChannelUser()}@${host}:${port}`;
|
|
1694
|
+
const keyPath = process.env.OPENSHIP_HOST_SSH_KEY?.trim();
|
|
1695
|
+
if (keyPath) {
|
|
1696
|
+
const { reason } = readHostChannelKey(keyPath);
|
|
1697
|
+
if (reason) {
|
|
1698
|
+
return {
|
|
1699
|
+
ok: false,
|
|
1700
|
+
code: "key_unreadable",
|
|
1701
|
+
host,
|
|
1702
|
+
port,
|
|
1703
|
+
target,
|
|
1704
|
+
hint: `${reason} ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
const probe = await probeTcpDetailed(host, port, timeoutMs);
|
|
1709
|
+
if (probe.ok) {
|
|
1710
|
+
return { ok: true, code: "ok", host, port, target };
|
|
1711
|
+
}
|
|
1712
|
+
return {
|
|
1713
|
+
ok: false,
|
|
1714
|
+
code: "unreachable",
|
|
1715
|
+
host,
|
|
1716
|
+
port,
|
|
1717
|
+
target,
|
|
1718
|
+
cause: probe.reason,
|
|
1719
|
+
...explainDialFailure(probe, host, port, target)
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
function unavailableExecutor(reason, code = "not_configured") {
|
|
1723
|
+
const refuse = async () => {
|
|
1724
|
+
throw new HostChannelUnavailableError(code, reason);
|
|
1725
|
+
};
|
|
1726
|
+
return {
|
|
1727
|
+
exec: refuse,
|
|
1728
|
+
streamExec: refuse,
|
|
1729
|
+
writeFile: refuse,
|
|
1730
|
+
readFile: refuse,
|
|
1731
|
+
exists: refuse,
|
|
1732
|
+
mkdir: refuse,
|
|
1733
|
+
rm: refuse,
|
|
1734
|
+
transferIn: refuse,
|
|
1735
|
+
// Nothing was ever opened, so releasing must not be the one call that throws.
|
|
1736
|
+
dispose: async () => {
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1525
1740
|
function createHostExecutor() {
|
|
1526
1741
|
if (hostControlDisabled()) {
|
|
1527
|
-
throw new
|
|
1742
|
+
throw new HostChannelUnavailableError(
|
|
1743
|
+
"disabled",
|
|
1528
1744
|
"Host control is disabled on this instance (OPENSHIP_HOST_CONTROL=false). Re-run `openship up` without --no-host-control to allow host operations."
|
|
1529
1745
|
);
|
|
1530
1746
|
}
|
|
1531
1747
|
const host = process.env.OPENSHIP_HOST_SSH_HOST?.trim();
|
|
1532
1748
|
if (!host) {
|
|
1533
1749
|
if (runningInContainer()) {
|
|
1534
|
-
throw new
|
|
1535
|
-
"
|
|
1750
|
+
throw new HostChannelUnavailableError(
|
|
1751
|
+
"not_configured",
|
|
1752
|
+
`This operation targets the HOST machine, but no host channel is configured (OPENSHIP_HOST_SSH_HOST is unset) and Openship is running in a container \u2014 so it would have run inside the container instead, against the wrong filesystem. ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1536
1753
|
);
|
|
1537
1754
|
}
|
|
1538
1755
|
return localExecutor;
|
|
1539
1756
|
}
|
|
1540
1757
|
const keyPath = process.env.OPENSHIP_HOST_SSH_KEY?.trim();
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1758
|
+
if (!keyPath) {
|
|
1759
|
+
throw new HostChannelUnavailableError(
|
|
1760
|
+
"not_configured",
|
|
1761
|
+
`A host channel is configured (OPENSHIP_HOST_SSH_HOST) but OPENSHIP_HOST_SSH_KEY is unset, so there is no credential to connect to the host with. ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
const read = readHostChannelKey(keyPath);
|
|
1765
|
+
if (read.reason) {
|
|
1766
|
+
throw new HostChannelUnavailableError(
|
|
1767
|
+
"not_configured",
|
|
1768
|
+
`${read.reason} ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1769
|
+
);
|
|
1552
1770
|
}
|
|
1553
1771
|
return new SshExecutor({
|
|
1554
1772
|
host,
|
|
1555
|
-
port,
|
|
1556
|
-
username:
|
|
1557
|
-
privateKey
|
|
1773
|
+
port: hostChannelPort(),
|
|
1774
|
+
username: hostChannelUser(),
|
|
1775
|
+
privateKey: read.key,
|
|
1776
|
+
// One hop over the docker bridge: it answers at once or it is being filtered.
|
|
1777
|
+
// 20s of silence per host operation reads as a hang, not as a failure.
|
|
1778
|
+
readyTimeoutMs: 8e3,
|
|
1779
|
+
hostChannel: true
|
|
1558
1780
|
});
|
|
1559
1781
|
}
|
|
1560
1782
|
|
|
@@ -1562,6 +1784,10 @@ export {
|
|
|
1562
1784
|
SshExecutor,
|
|
1563
1785
|
SystemSshExecutor,
|
|
1564
1786
|
createExecutor,
|
|
1787
|
+
setHostControlOverride,
|
|
1565
1788
|
hostControlDisabled,
|
|
1789
|
+
containerBridgeCidr,
|
|
1790
|
+
hostChannelHealth,
|
|
1791
|
+
unavailableExecutor,
|
|
1566
1792
|
createHostExecutor
|
|
1567
1793
|
};
|