openship 0.6.1 → 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-IWS2QHJ3.js → bare-XQIPDO4R.js} +10 -9
- package/dist/{chunk-RRJJGVKE.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-IOT5ZX23.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-YIPMDGG6.js → chunk-PZRRL5PC.js} +68 -23
- package/dist/{chunk-2NZSUXOB.js → chunk-QLX7NBEA.js} +1208 -599
- 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-UC7YIEHT.js → chunk-VHMOQL5Y.js} +254 -45
- package/dist/{chunk-EW6KB2J7.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-3NZICQHF.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-SAYTRJLO.js → edge-import-MCJNVTZ2.js} +26 -23
- package/dist/edge-preflight-ES6FH4JK.js +42 -0
- package/dist/{ensure-container-edge-CFSCPGZK.js → ensure-container-edge-YPITNH45.js} +9 -8
- package/dist/{executor-PT6UEJ5M.js → executor-DZGX55K2.js} +12 -5
- package/dist/host-channel-preflight-MUXE3KZU.js +49 -0
- package/dist/index.js +4089 -4538
- package/dist/{nginx-YOEEFBTE.js → nginx-NSAHWGJV.js} +11 -5
- package/dist/node-bootstrap.js +241 -0
- package/dist/node-entry.js +52 -1
- package/dist/{openresty-lua-WGBO2M3Z.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 +21391 -13110
- 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-M2ZDGH3O.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-M2YLWEXO.js +0 -31
- /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) => {
|
|
@@ -706,24 +774,23 @@ var SshExecutor = class _SshExecutor {
|
|
|
706
774
|
* Carry the Docker Engine API over a `docker system dial-stdio` exec channel
|
|
707
775
|
* on the pooled connection — the streamlocal-free transport used when the
|
|
708
776
|
* SSH server (or the Bun-compiled desktop runtime) can't do socket
|
|
709
|
-
* forwarding.
|
|
710
|
-
*
|
|
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`.
|
|
711
790
|
*/
|
|
712
791
|
async openDockerDialStdio() {
|
|
713
792
|
const client = await this.connect();
|
|
714
|
-
return
|
|
715
|
-
client.exec(_SshExecutor.ENV_PREFIX + "docker system dial-stdio", (err, stream) => {
|
|
716
|
-
if (err) return reject(err);
|
|
717
|
-
stream.stderr?.on("data", (d) => {
|
|
718
|
-
const text = d.toString().trim();
|
|
719
|
-
if (text) console.warn(`[docker-ssh] dial-stdio stderr: ${text}`);
|
|
720
|
-
});
|
|
721
|
-
stream.on("exit", (code) => {
|
|
722
|
-
if (code) console.warn(`[docker-ssh] dial-stdio exited early (code=${code})`);
|
|
723
|
-
});
|
|
724
|
-
resolve(stream);
|
|
725
|
-
});
|
|
726
|
-
});
|
|
793
|
+
return openDockerDialStdioChannel(client);
|
|
727
794
|
}
|
|
728
795
|
async forwardPort(remoteHost, remotePort) {
|
|
729
796
|
const client = await this.connect();
|
|
@@ -1200,7 +1267,7 @@ var SystemSshExecutor = class _SystemSshExecutor {
|
|
|
1200
1267
|
const timeout = opts?.timeout ?? 3e4;
|
|
1201
1268
|
const res = await this.runSsh(_SystemSshExecutor.ENV_PREFIX + command, { timeout });
|
|
1202
1269
|
if (res.timedOut) {
|
|
1203
|
-
throw new Error(`Command timed out after ${timeout}ms: ${command}`);
|
|
1270
|
+
throw new Error(`Command timed out after ${timeout}ms: ${commandForError(command)}`);
|
|
1204
1271
|
}
|
|
1205
1272
|
if (res.code !== 0) {
|
|
1206
1273
|
if (res.code === 255) throw describeSshFailure(res.stderr, `Exit code ${res.code}`);
|
|
@@ -1208,14 +1275,22 @@ var SystemSshExecutor = class _SystemSshExecutor {
|
|
|
1208
1275
|
}
|
|
1209
1276
|
return res.stdout.trim();
|
|
1210
1277
|
}
|
|
1211
|
-
async streamExec(command, onLog) {
|
|
1278
|
+
async streamExec(command, onLog, opts) {
|
|
1212
1279
|
await this.ensureMaster();
|
|
1280
|
+
const signal = opts?.signal;
|
|
1281
|
+
if (signal?.aborted) return { code: 0, output: "" };
|
|
1213
1282
|
return new Promise((resolve) => {
|
|
1214
1283
|
const child = spawn2(
|
|
1215
1284
|
"ssh",
|
|
1216
1285
|
[...this.baseArgs(), sshTarget(this.config), _SystemSshExecutor.ENV_PREFIX + command],
|
|
1217
1286
|
{ env: sshChildEnv(this.config), stdio: ["ignore", "pipe", "pipe"] }
|
|
1218
1287
|
);
|
|
1288
|
+
let aborted = false;
|
|
1289
|
+
const onAbort = () => {
|
|
1290
|
+
aborted = true;
|
|
1291
|
+
child.kill("SIGKILL");
|
|
1292
|
+
};
|
|
1293
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1219
1294
|
const chunks = [];
|
|
1220
1295
|
const onChunk = (chunk, level) => {
|
|
1221
1296
|
const text = chunk.toString();
|
|
@@ -1226,11 +1301,13 @@ var SystemSshExecutor = class _SystemSshExecutor {
|
|
|
1226
1301
|
child.stdout.on("data", (chunk) => onChunk(chunk, "info"));
|
|
1227
1302
|
child.stderr.on("data", (chunk) => onChunk(chunk, "warn"));
|
|
1228
1303
|
child.on("error", (err) => {
|
|
1304
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1229
1305
|
onLog(logEntry(`Process error: ${err.message}`, "error"));
|
|
1230
1306
|
resolve({ code: 1, output: err.message });
|
|
1231
1307
|
});
|
|
1232
1308
|
child.on("close", (code) => {
|
|
1233
|
-
|
|
1309
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1310
|
+
const c = aborted ? 0 : code ?? 1;
|
|
1234
1311
|
void this.maybeSignalDisconnect(c).catch(() => {
|
|
1235
1312
|
});
|
|
1236
1313
|
resolve({ code: c, output: chunks.join("") });
|
|
@@ -1524,8 +1601,12 @@ function createExecutor(ssh) {
|
|
|
1524
1601
|
}
|
|
1525
1602
|
return localExecutor;
|
|
1526
1603
|
}
|
|
1604
|
+
var hostControlOverride = null;
|
|
1605
|
+
function setHostControlOverride(disabled) {
|
|
1606
|
+
hostControlOverride = disabled;
|
|
1607
|
+
}
|
|
1527
1608
|
function hostControlDisabled() {
|
|
1528
|
-
return process.env.OPENSHIP_HOST_CONTROL?.trim().toLowerCase() === "false";
|
|
1609
|
+
return hostControlOverride ?? process.env.OPENSHIP_HOST_CONTROL?.trim().toLowerCase() === "false";
|
|
1529
1610
|
}
|
|
1530
1611
|
function runningInContainer() {
|
|
1531
1612
|
if (process.env.OPENSHIP_IN_CONTAINER?.trim().toLowerCase() === "true") return true;
|
|
@@ -1539,39 +1620,163 @@ function runningInContainer() {
|
|
|
1539
1620
|
return false;
|
|
1540
1621
|
}
|
|
1541
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
|
+
}
|
|
1542
1740
|
function createHostExecutor() {
|
|
1543
1741
|
if (hostControlDisabled()) {
|
|
1544
|
-
throw new
|
|
1742
|
+
throw new HostChannelUnavailableError(
|
|
1743
|
+
"disabled",
|
|
1545
1744
|
"Host control is disabled on this instance (OPENSHIP_HOST_CONTROL=false). Re-run `openship up` without --no-host-control to allow host operations."
|
|
1546
1745
|
);
|
|
1547
1746
|
}
|
|
1548
1747
|
const host = process.env.OPENSHIP_HOST_SSH_HOST?.trim();
|
|
1549
1748
|
if (!host) {
|
|
1550
1749
|
if (runningInContainer()) {
|
|
1551
|
-
throw new
|
|
1552
|
-
"
|
|
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}`
|
|
1553
1753
|
);
|
|
1554
1754
|
}
|
|
1555
1755
|
return localExecutor;
|
|
1556
1756
|
}
|
|
1557
1757
|
const keyPath = process.env.OPENSHIP_HOST_SSH_KEY?.trim();
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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
|
+
);
|
|
1569
1770
|
}
|
|
1570
1771
|
return new SshExecutor({
|
|
1571
1772
|
host,
|
|
1572
|
-
port,
|
|
1573
|
-
username:
|
|
1574
|
-
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
|
|
1575
1780
|
});
|
|
1576
1781
|
}
|
|
1577
1782
|
|
|
@@ -1579,6 +1784,10 @@ export {
|
|
|
1579
1784
|
SshExecutor,
|
|
1580
1785
|
SystemSshExecutor,
|
|
1581
1786
|
createExecutor,
|
|
1787
|
+
setHostControlOverride,
|
|
1582
1788
|
hostControlDisabled,
|
|
1789
|
+
containerBridgeCidr,
|
|
1790
|
+
hostChannelHealth,
|
|
1791
|
+
unavailableExecutor,
|
|
1583
1792
|
createHostExecutor
|
|
1584
1793
|
};
|