openship 0.6.5 → 0.6.7
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/dist/bare-7QD623QV.js +29 -0
- package/dist/chunk-2ANV5ZQ5.js +88 -0
- package/dist/{chunk-LEYSL46B.js → chunk-2VSW6Q6C.js} +3911 -898
- package/dist/{chunk-5OUWIPOB.js → chunk-5T4UVOM3.js} +16 -86
- package/dist/{chunk-EZPPTWJQ.js → chunk-BLEXTYRK.js} +1 -1
- package/dist/{chunk-3ZAGC5NT.js → chunk-CIJV43LH.js} +83 -33
- package/dist/{chunk-7S4USQYF.js → chunk-DDFUESPJ.js} +67 -124
- package/dist/{chunk-6FH4AXVY.js → chunk-E2X26PXR.js} +1 -1
- package/dist/{chunk-XVOBL7BC.js → chunk-FYPFEDPZ.js} +1 -1
- package/dist/{chunk-WFRXYWK2.js → chunk-FZ6QCF6B.js} +8 -6
- package/dist/{chunk-VHMOQL5Y.js → chunk-GGIAIGUL.js} +125 -80
- package/dist/{chunk-WE7BXW6L.js → chunk-GIPJNNI5.js} +38 -44
- package/dist/{chunk-ZFJXPCQP.js → chunk-H3WT3A6R.js} +4 -3
- package/dist/chunk-H4FS5BED.js +936 -0
- package/dist/{chunk-HPXP6LXZ.js → chunk-J2XM3QSP.js} +8 -5
- package/dist/{chunk-6JELW75A.js → chunk-J3K6XABC.js} +5 -5
- package/dist/{chunk-VDESJSG6.js → chunk-LHFXSC36.js} +558 -109
- package/dist/{chunk-MSZUCNPV.js → chunk-MEE6OZFP.js} +3 -3
- package/dist/chunk-N5YZMO33.js +111 -0
- package/dist/{chunk-VLQ3UIVO.js → chunk-NRTJRNAE.js} +105 -37
- package/dist/chunk-PPEBIY5F.js +2331 -0
- package/dist/{chunk-RFWJE7ZN.js → chunk-PVP4RGWT.js} +3 -3
- package/dist/{chunk-PZRRL5PC.js → chunk-QHMDCYZ3.js} +15 -4
- package/dist/chunk-R4VDPFLS.js +106 -0
- package/dist/chunk-RDXT4BQB.js +310 -0
- package/dist/{chunk-GRTXMUHA.js → chunk-S6B2Z3OO.js} +145 -60
- package/dist/chunk-UWEV6XTS.js +249 -0
- package/dist/{chunk-QLX7NBEA.js → chunk-VQM6JHJB.js} +1004 -2046
- package/dist/{chunk-A7EWM2KA.js → chunk-VSTFHUCV.js} +44 -50
- package/dist/chunk-WU54BXFE.js +70 -0
- package/dist/{chunk-LFCSC4IU.js → chunk-XWHWTHEL.js} +313 -469
- package/dist/{chunk-7334NSJL.js → chunk-ZPJ74LXE.js} +76 -38
- package/dist/cloud-KNNABE3O.js +40 -0
- package/dist/{detect-P4CIQBRH.js → detect-G4DR3U2N.js} +9 -6
- package/dist/{docker-3K2NEBD6.js → docker-YOVPCIB4.js} +11 -7
- package/dist/{docker-edge-executor-YZZ6P7IC.js → docker-edge-executor-QFXD3337.js} +8 -6
- package/dist/edge-import-CRFVSR6P.js +56 -0
- package/dist/{edge-preflight-ES6FH4JK.js → edge-preflight-MXERVAMG.js} +15 -12
- package/dist/{ensure-container-edge-YPITNH45.js → ensure-container-edge-F4JUNQIK.js} +13 -10
- package/dist/{executor-DZGX55K2.js → executor-CVPAYYHR.js} +10 -5
- package/dist/host-channel-preflight-QTDNGLCK.js +54 -0
- package/dist/index.js +139 -92
- package/dist/{local-executor-WOFZD72X.js → local-executor-ODC6RANU.js} +3 -2
- package/dist/{nginx-NSAHWGJV.js → nginx-KLFF43TC.js} +13 -8
- package/dist/{openresty-lua-DR4D3EZ6.js → openresty-lua-VKWUV4GB.js} +8 -5
- package/dist/{privilege-YTVI4SKF.js → privilege-7D3SSOL3.js} +6 -5
- package/dist/server/engine/samples/amavisd/amavisd.conf +25 -0
- package/dist/server/engine/samples/postfix/master.cf +14 -0
- package/dist/server/index.js +44526 -33476
- 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 +28 -0
- package/dist/setup-I5AJC6NJ.js +25 -0
- package/dist/ssh-client-625VWGKP.js +29 -0
- package/dist/up-QXTOOVXC.js +61 -0
- package/package.json +1 -1
- package/dist/bare-XQIPDO4R.js +0 -24
- package/dist/chunk-6HOA5JT7.js +0 -124
- package/dist/chunk-TM2IQPYU.js +0 -434
- package/dist/chunk-XRNRT37W.js +0 -316
- package/dist/cloud-3DODOWTR.js +0 -36
- package/dist/edge-import-MCJNVTZ2.js +0 -51
- package/dist/host-channel-preflight-MUXE3KZU.js +0 -49
- package/dist/setup-WUUZURHS.js +0 -22
- package/dist/up-XEO3SR6Z.js +0 -58
- /package/dist/{dist-es-ZP5QDSJ3.js → dist-es-AXFRS7PS.js} +0 -0
|
@@ -2,19 +2,6 @@
|
|
|
2
2
|
":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
|
|
3
3
|
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
|
-
import {
|
|
6
|
-
importMigratedSites
|
|
7
|
-
} from "./chunk-6HOA5JT7.js";
|
|
8
|
-
import {
|
|
9
|
-
completeHostEdge,
|
|
10
|
-
markStoppedProxyImported,
|
|
11
|
-
planAndApplyHostEdge,
|
|
12
|
-
previewHostEdge,
|
|
13
|
-
rollbackHostEdge
|
|
14
|
-
} from "./chunk-HPXP6LXZ.js";
|
|
15
|
-
import {
|
|
16
|
-
verifyHostChannel
|
|
17
|
-
} from "./chunk-XRNRT37W.js";
|
|
18
5
|
import {
|
|
19
6
|
DEFAULT_REPO,
|
|
20
7
|
PORTS_FILE,
|
|
@@ -40,9 +27,27 @@ import {
|
|
|
40
27
|
resolveComposePorts,
|
|
41
28
|
resolvePorts,
|
|
42
29
|
sourceBuildDir,
|
|
30
|
+
verifyHostChannel
|
|
31
|
+
} from "./chunk-PPEBIY5F.js";
|
|
32
|
+
import {
|
|
33
|
+
bootstrapAdmin,
|
|
34
|
+
importMigratedSites,
|
|
35
|
+
internalFetch,
|
|
36
|
+
internalPost,
|
|
37
|
+
mintBareInternalToken,
|
|
38
|
+
waitHealthy
|
|
39
|
+
} from "./chunk-RDXT4BQB.js";
|
|
40
|
+
import {
|
|
43
41
|
startUnitHint,
|
|
44
42
|
thisHost
|
|
45
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-VQM6JHJB.js";
|
|
44
|
+
import {
|
|
45
|
+
completeHostEdge,
|
|
46
|
+
markStoppedProxyImported,
|
|
47
|
+
planAndApplyHostEdge,
|
|
48
|
+
previewHostEdge,
|
|
49
|
+
rollbackHostEdge
|
|
50
|
+
} from "./chunk-J2XM3QSP.js";
|
|
46
51
|
import {
|
|
47
52
|
AUTH_SECRET_FILE,
|
|
48
53
|
DATA_DIR,
|
|
@@ -53,23 +58,25 @@ import {
|
|
|
53
58
|
} from "./chunk-ELGRMZOW.js";
|
|
54
59
|
import {
|
|
55
60
|
LocalExecutor
|
|
56
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-E2X26PXR.js";
|
|
57
62
|
import {
|
|
58
|
-
EDGE_CONTAINER_NAME,
|
|
59
63
|
edgeCrashReason,
|
|
60
64
|
edgeIsBroken
|
|
61
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-GIPJNNI5.js";
|
|
66
|
+
import {
|
|
67
|
+
EDGE_CONTAINER_NAME
|
|
68
|
+
} from "./chunk-UWEV6XTS.js";
|
|
62
69
|
import {
|
|
63
70
|
isValidEmail
|
|
64
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-2VSW6Q6C.js";
|
|
65
72
|
|
|
66
73
|
// src/commands/up.ts
|
|
67
74
|
import { Command } from "commander";
|
|
68
75
|
import chalk2 from "chalk";
|
|
69
76
|
import ora from "ora";
|
|
70
77
|
import { spawn } from "child_process";
|
|
71
|
-
import { randomBytes
|
|
72
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
78
|
+
import { randomBytes } from "crypto";
|
|
79
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
73
80
|
import { dirname as dirname2, join as join5 } from "path";
|
|
74
81
|
import { fileURLToPath } from "url";
|
|
75
82
|
|
|
@@ -604,6 +611,7 @@ async function planUp(opts) {
|
|
|
604
611
|
...opts.hostControl === false ? { noHostControl: true } : {},
|
|
605
612
|
...opts.hostSshHost ? { hostSshHost: opts.hostSshHost } : {},
|
|
606
613
|
...opts.hostSshPort ? { hostSshPort: opts.hostSshPort } : {},
|
|
614
|
+
...opts.hostSshUser ? { hostSshUser: opts.hostSshUser } : {},
|
|
607
615
|
...opts.resetSecrets ? { resetSecrets: true } : {},
|
|
608
616
|
...opts.mail ? { mail: true } : {}
|
|
609
617
|
});
|
|
@@ -615,9 +623,23 @@ async function planUp(opts) {
|
|
|
615
623
|
...stack.buildFile ? [stack.buildFile] : [],
|
|
616
624
|
...stack.hostChannel ? [
|
|
617
625
|
stack.hostChannel.keyPath,
|
|
618
|
-
`${stack.hostChannel.authKeysPath}
|
|
626
|
+
`${stack.hostChannel.authKeysPath} (authorizes that key for ${stack.hostChannel.user}'s host ops; --no-host-control skips it)`,
|
|
627
|
+
// Say which route to root this channel will take, rather than repeating a root
|
|
628
|
+
// warning at a box that reaches root perfectly well via sudo. `sudo` is a
|
|
629
|
+
// supported shape now, not a degradation, and the preview is where an operator
|
|
630
|
+
// decides whether that is what they wanted.
|
|
631
|
+
...stack.hostChannel.elevation === "sudo" ? [
|
|
632
|
+
`logs in as ${stack.hostChannel.user} and elevates with sudo for root host ops (no root login is created)`
|
|
633
|
+
] : [],
|
|
619
634
|
...stack.hostChannel.rootUnavailable ? [
|
|
620
|
-
"\u26A0 host control needs root \u2014 this user isn't root and passwordless sudo isn't available, so mail/edge host ops will fail (re-run as root to fix)"
|
|
635
|
+
"\u26A0 host control needs root \u2014 this user isn't root and passwordless sudo isn't available, so mail/edge host ops will fail (re-run as root, or enable passwordless sudo, to fix)"
|
|
636
|
+
] : [],
|
|
637
|
+
// A plan that named one account and then settled on another would read as the
|
|
638
|
+
// plan being wrong, and this run genuinely cannot know which: only a dial can
|
|
639
|
+
// establish whether sshd accepts the first account, and dialing means writing
|
|
640
|
+
// the key a dry run exists not to write (#527, previewHostChannel).
|
|
641
|
+
...stack.hostChannel.fallbacks?.length ? [
|
|
642
|
+
`if this host's sshd refuses that account, falls back to ${stack.hostChannel.fallbacks.join(", ")} (pin one with --host-ssh-user)`
|
|
621
643
|
] : []
|
|
622
644
|
] : [],
|
|
623
645
|
...stack.mountDirs.map((d) => `${d}/ (edge routing state + certs)`),
|
|
@@ -768,87 +790,6 @@ function serviceWord(kind) {
|
|
|
768
790
|
}
|
|
769
791
|
}
|
|
770
792
|
|
|
771
|
-
// src/lib/loopback-api.ts
|
|
772
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
773
|
-
import { randomBytes } from "crypto";
|
|
774
|
-
function ensureInternalToken() {
|
|
775
|
-
const path = INTERNAL_TOKEN_FILE;
|
|
776
|
-
if (existsSync4(path)) return readFileSync3(path, "utf8").trim();
|
|
777
|
-
mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
778
|
-
const token = randomBytes(32).toString("hex");
|
|
779
|
-
writeFileSync4(path, token, { mode: 384 });
|
|
780
|
-
return token;
|
|
781
|
-
}
|
|
782
|
-
async function internalGet(port, path, token) {
|
|
783
|
-
try {
|
|
784
|
-
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
785
|
-
headers: { "X-Internal-Token": token ?? ensureInternalToken() },
|
|
786
|
-
signal: AbortSignal.timeout(1e4)
|
|
787
|
-
});
|
|
788
|
-
if (!res.ok) return null;
|
|
789
|
-
return await res.json();
|
|
790
|
-
} catch {
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
async function internalPost(port, path, body, token) {
|
|
795
|
-
try {
|
|
796
|
-
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
797
|
-
method: "POST",
|
|
798
|
-
headers: { "Content-Type": "application/json", "X-Internal-Token": token ?? ensureInternalToken() },
|
|
799
|
-
body: JSON.stringify(body),
|
|
800
|
-
signal: AbortSignal.timeout(3e4)
|
|
801
|
-
});
|
|
802
|
-
const data = await res.json().catch(() => ({}));
|
|
803
|
-
return { ok: res.ok, data };
|
|
804
|
-
} catch (err) {
|
|
805
|
-
return { ok: false, data: { error: err.message } };
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
async function bootstrapAdmin(apiPort, admin, token) {
|
|
809
|
-
const { ok, data } = await internalPost(apiPort, "/api/system/bootstrap-admin", admin, token);
|
|
810
|
-
if (ok) return { ok: true };
|
|
811
|
-
if (data?.error === "An admin account already exists") return { ok: true, message: "already-exists" };
|
|
812
|
-
return { ok: false, message: data?.error || "failed" };
|
|
813
|
-
}
|
|
814
|
-
async function waitHealthy(apiPort, seconds = 90) {
|
|
815
|
-
for (let i = 0; i < seconds; i++) {
|
|
816
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
817
|
-
try {
|
|
818
|
-
await fetch(`http://127.0.0.1:${apiPort}/api/health`, { signal: AbortSignal.timeout(2e3) });
|
|
819
|
-
return true;
|
|
820
|
-
} catch {
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
async function waitDashboard(dashPort, seconds = 45) {
|
|
826
|
-
for (let i = 0; i < seconds; i++) {
|
|
827
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
828
|
-
try {
|
|
829
|
-
const res = await fetch(`http://127.0.0.1:${dashPort}/`, {
|
|
830
|
-
redirect: "manual",
|
|
831
|
-
signal: AbortSignal.timeout(2e3)
|
|
832
|
-
});
|
|
833
|
-
if (res.status > 0) return true;
|
|
834
|
-
} catch {
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
return false;
|
|
838
|
-
}
|
|
839
|
-
async function detectPublicIp() {
|
|
840
|
-
for (const url of ["https://api.ipify.org", "https://ifconfig.me/ip"]) {
|
|
841
|
-
try {
|
|
842
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(3e3) });
|
|
843
|
-
if (!res.ok) continue;
|
|
844
|
-
const ip = (await res.text()).trim();
|
|
845
|
-
if (/^[0-9.]+$/.test(ip) || ip.includes(":")) return ip;
|
|
846
|
-
} catch {
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
793
|
// src/lib/instance-provision.ts
|
|
853
794
|
var HeadlessInputError = class extends Error {
|
|
854
795
|
code = "HEADLESS_INPUT";
|
|
@@ -927,10 +868,14 @@ async function bootstrapOrReset(port, admin, token) {
|
|
|
927
868
|
async function drainProvisionStream(port, sessionId, token, onLog) {
|
|
928
869
|
let detail;
|
|
929
870
|
try {
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
871
|
+
const call = await internalFetch(
|
|
872
|
+
port,
|
|
873
|
+
`/api/system/self-register/stream?id=${sessionId}`,
|
|
874
|
+
{ signal: AbortSignal.timeout(18e4) },
|
|
875
|
+
token
|
|
876
|
+
);
|
|
877
|
+
if (call.kind !== "response") return { completed: false, detail: call.detail };
|
|
878
|
+
const res = call.res;
|
|
934
879
|
if (!res.body) return { completed: false };
|
|
935
880
|
const reader = res.body.getReader();
|
|
936
881
|
const decoder = new TextDecoder();
|
|
@@ -1100,10 +1045,10 @@ var DIST_DIR = dirname2(fileURLToPath(import.meta.url));
|
|
|
1100
1045
|
var SERVER_DIR = join5(DIST_DIR, "server");
|
|
1101
1046
|
function ensureAuthSecret() {
|
|
1102
1047
|
const path = AUTH_SECRET_FILE;
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
const secret =
|
|
1106
|
-
|
|
1048
|
+
if (existsSync4(path)) return readFileSync3(path, "utf8").trim();
|
|
1049
|
+
mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
1050
|
+
const secret = randomBytes(32).toString("hex");
|
|
1051
|
+
writeFileSync4(path, secret, { mode: 384 });
|
|
1107
1052
|
return secret;
|
|
1108
1053
|
}
|
|
1109
1054
|
var upCommand = new Command("up").description("Start Openship as a persistent service (boot + auto-restart); --foreground to run attached").option("--port <port>", "API port to listen on (default: 4000, or the next free port if it's taken)").option("--data-dir <dir>", "Directory for the embedded database").option("--dashboard-port <port>", "Dashboard port (default: 3001, or the next free port if it's taken)").option("--no-ui", "Run the API only \u2014 don't download/serve the dashboard").option("--ui-version <tag>", "Dashboard release tag to run (default: this CLI's version)").option(
|
|
@@ -1136,6 +1081,9 @@ var upCommand = new Command("up").description("Start Openship as a persistent se
|
|
|
1136
1081
|
).option(
|
|
1137
1082
|
"--host-ssh-port <port>",
|
|
1138
1083
|
"Compose mode: port the host's sshd listens on for host operations (default 22). Preserved across re-runs."
|
|
1084
|
+
).option(
|
|
1085
|
+
"--host-ssh-user <name>",
|
|
1086
|
+
"Compose mode: the host account the api container logs in as for host operations. Normally left unset \u2014 provisioning authorizes a key and DIALS to find an account this host's sshd actually accepts (root when invoked as root, otherwise the invoking user, falling back to $SUDO_USER when a root login is refused). Set this when neither is the account you want, or when sshd's rules mean neither works: the named account is used as-is and never falls back, so it must be able to log in over SSH and should have passwordless sudo (host operations elevate per command). Preserved across re-runs."
|
|
1139
1087
|
).option(
|
|
1140
1088
|
"--open-host-firewall",
|
|
1141
1089
|
"Compose mode: if the api container can't reach this machine's SSH port, add the host firewall rule that allows it (scoped to the container subnet, ufw/firewalld only). Without this the blocked channel is reported and left alone. Not implied by --yes."
|
|
@@ -1278,6 +1226,7 @@ async function runCompose(opts) {
|
|
|
1278
1226
|
// so the containers would keep an environment the file no longer shows.
|
|
1279
1227
|
hostSshHost: opts.hostSshHost,
|
|
1280
1228
|
hostSshPort: opts.hostSshPort,
|
|
1229
|
+
hostSshUser: opts.hostSshUser,
|
|
1281
1230
|
resetSecrets: opts.resetSecrets,
|
|
1282
1231
|
mail: opts.mail
|
|
1283
1232
|
});
|
|
@@ -1332,6 +1281,7 @@ async function runCompose(opts) {
|
|
|
1332
1281
|
// configured with rather than resetting it to host.docker.internal:22.
|
|
1333
1282
|
hostSshHost: opts.hostSshHost,
|
|
1334
1283
|
hostSshPort: opts.hostSshPort,
|
|
1284
|
+
hostSshUser: opts.hostSshUser,
|
|
1335
1285
|
resetSecrets: opts.resetSecrets,
|
|
1336
1286
|
// Absent on a re-run keeps the install's mode (resolveEnvConfig), so this
|
|
1337
1287
|
// never flips a mail box back to the platform shell.
|
|
@@ -1471,7 +1421,7 @@ async function runForeground(opts, source) {
|
|
|
1471
1421
|
apiCwd = source.apiDir;
|
|
1472
1422
|
} else {
|
|
1473
1423
|
const serverEntry = join5(SERVER_DIR, "index.js");
|
|
1474
|
-
if (!
|
|
1424
|
+
if (!existsSync4(serverEntry)) {
|
|
1475
1425
|
console.error(
|
|
1476
1426
|
chalk2.red("\n Bundled server not found in this install.") + chalk2.dim("\n Reinstall with `openship update` (or `npm i -g openship`).\n")
|
|
1477
1427
|
);
|
|
@@ -1488,8 +1438,8 @@ async function runForeground(opts, source) {
|
|
|
1488
1438
|
const publicUrl = effectivePublicUrl(opts.publicUrl);
|
|
1489
1439
|
const managedEdge = Boolean(opts.managedEdge && publicUrl);
|
|
1490
1440
|
const dataDir = opts.dataDir || DATA_DIR;
|
|
1491
|
-
|
|
1492
|
-
|
|
1441
|
+
mkdirSync5(dataDir, { recursive: true });
|
|
1442
|
+
mkdirSync5(LOG_DIR, { recursive: true });
|
|
1493
1443
|
const instanceLogPath = join5(LOG_DIR, "instance.log");
|
|
1494
1444
|
const instanceLog = createWriteStream2(instanceLogPath, { flags: "w" });
|
|
1495
1445
|
const env = {
|
|
@@ -1521,10 +1471,10 @@ async function runForeground(opts, source) {
|
|
|
1521
1471
|
env.OPENSHIP_MIGRATIONS_DIR = join5(SERVER_DIR, "migrations");
|
|
1522
1472
|
env.OPENSHIP_PGLITE_ASSETS_DIR = join5(SERVER_DIR, "pglite");
|
|
1523
1473
|
const engineDir = join5(SERVER_DIR, "engine");
|
|
1524
|
-
if (
|
|
1474
|
+
if (existsSync4(engineDir)) env.MAIL_SERVER_ENGINE_DIR = engineDir;
|
|
1525
1475
|
}
|
|
1526
1476
|
env.OPENSHIP_REQUIRE_AUTH = "true";
|
|
1527
|
-
env.INTERNAL_TOKEN =
|
|
1477
|
+
env.INTERNAL_TOKEN = mintBareInternalToken();
|
|
1528
1478
|
if (opts.mail) env.OPENSHIP_PRODUCT = "mail";
|
|
1529
1479
|
if (!env.OPENSHIP_AUTH_MODE) env.OPENSHIP_AUTH_MODE = "local";
|
|
1530
1480
|
env.OPENSHIP_API_HOST = "127.0.0.1";
|
|
@@ -1614,7 +1564,7 @@ async function runForeground(opts, source) {
|
|
|
1614
1564
|
const uiSpinner = ora("Preparing the dashboard\u2026").start();
|
|
1615
1565
|
try {
|
|
1616
1566
|
const bundle = await ensureDashboard({
|
|
1617
|
-
tag: source ? "local" : opts.uiVersion || `v${"0.6.
|
|
1567
|
+
tag: source ? "local" : opts.uiVersion || `v${"0.6.7"}`,
|
|
1618
1568
|
onProgress: (received, total) => {
|
|
1619
1569
|
if (total) {
|
|
1620
1570
|
uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
|
|
@@ -1738,13 +1688,6 @@ export {
|
|
|
1738
1688
|
restart,
|
|
1739
1689
|
serviceStatus,
|
|
1740
1690
|
stop,
|
|
1741
|
-
ensureInternalToken,
|
|
1742
|
-
internalGet,
|
|
1743
|
-
internalPost,
|
|
1744
|
-
bootstrapAdmin,
|
|
1745
|
-
waitHealthy,
|
|
1746
|
-
waitDashboard,
|
|
1747
|
-
detectPublicIp,
|
|
1748
1691
|
headlessProvision,
|
|
1749
1692
|
normalizeUrl,
|
|
1750
1693
|
upCommand,
|
|
@@ -4,18 +4,20 @@ import { createRequire as __ospCreateRequire } from "node:module";
|
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
6
|
sq
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-H3WT3A6R.js";
|
|
8
8
|
import {
|
|
9
|
-
assessHostSupport,
|
|
10
|
-
classifyDistro,
|
|
11
9
|
formatDuration,
|
|
12
10
|
isRemoteConnectionError,
|
|
11
|
+
systemDebug
|
|
12
|
+
} from "./chunk-N5YZMO33.js";
|
|
13
|
+
import {
|
|
14
|
+
assessHostSupport,
|
|
15
|
+
classifyDistro,
|
|
13
16
|
parseOsRelease,
|
|
14
17
|
parseSystemArch,
|
|
15
18
|
parseSystemOs,
|
|
16
|
-
safeErrorMessage
|
|
17
|
-
|
|
18
|
-
} from "./chunk-LEYSL46B.js";
|
|
19
|
+
safeErrorMessage
|
|
20
|
+
} from "./chunk-2VSW6Q6C.js";
|
|
19
21
|
|
|
20
22
|
// ../../packages/adapters/src/system/elevated-executor.ts
|
|
21
23
|
import { randomBytes } from "crypto";
|
|
@@ -3,97 +3,51 @@
|
|
|
3
3
|
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
probeTcpDetailed
|
|
7
|
+
} from "./chunk-R4VDPFLS.js";
|
|
8
|
+
import {
|
|
7
9
|
getTarCreateEnv,
|
|
10
|
+
prepareSourceTarArgs
|
|
11
|
+
} from "./chunk-2ANV5ZQ5.js";
|
|
12
|
+
import {
|
|
13
|
+
connectSshClient,
|
|
8
14
|
openDockerDialStdioChannel,
|
|
9
15
|
openSftp,
|
|
10
16
|
openSshUnixSocket,
|
|
11
|
-
prepareSourceTarArgs,
|
|
12
17
|
reconcileKnownHosts
|
|
13
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-5T4UVOM3.js";
|
|
14
19
|
import {
|
|
15
20
|
LocalExecutor
|
|
16
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-E2X26PXR.js";
|
|
17
22
|
import {
|
|
18
23
|
emitBufferedLines,
|
|
19
24
|
flushBufferedLines,
|
|
20
25
|
hasLocalCommand,
|
|
21
26
|
logEntry,
|
|
22
27
|
sq
|
|
23
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-H3WT3A6R.js";
|
|
24
29
|
import {
|
|
25
|
-
HOST_CHANNEL_NOT_PROVISIONED,
|
|
26
|
-
HOST_CHANNEL_UNPROVISIONED,
|
|
27
30
|
HostChannelUnavailableError,
|
|
28
31
|
SshDisconnectedError,
|
|
29
|
-
TRANSFER_EXCLUDES,
|
|
30
32
|
commandForError,
|
|
33
|
+
isSshAuthError
|
|
34
|
+
} from "./chunk-N5YZMO33.js";
|
|
35
|
+
import {
|
|
36
|
+
HOST_CHANNEL_AUTH_REJECTED,
|
|
37
|
+
HOST_CHANNEL_NOT_PROVISIONED,
|
|
38
|
+
HOST_CHANNEL_UNPROVISIONED,
|
|
39
|
+
TRANSFER_EXCLUDES,
|
|
31
40
|
explainHostChannelCause,
|
|
32
41
|
formatBytes,
|
|
42
|
+
hostChannelAccount,
|
|
33
43
|
hostFirewallRule,
|
|
34
44
|
safeErrorMessage
|
|
35
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-2VSW6Q6C.js";
|
|
36
46
|
|
|
37
47
|
// ../../packages/adapters/src/system/executor.ts
|
|
38
48
|
import { existsSync, readFileSync } from "fs";
|
|
39
49
|
import { networkInterfaces } from "os";
|
|
40
50
|
|
|
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
|
-
}
|
|
96
|
-
|
|
97
51
|
// ../../packages/adapters/src/system/ssh-executor.ts
|
|
98
52
|
import { createReadStream } from "fs";
|
|
99
53
|
import { mkdtemp as mkdtemp2, rm as fsRm2, stat } from "fs/promises";
|
|
@@ -1625,7 +1579,7 @@ function hostChannelPort() {
|
|
|
1625
1579
|
return Number.isInteger(raw) && raw > 0 && raw < 65536 ? raw : 22;
|
|
1626
1580
|
}
|
|
1627
1581
|
function hostChannelUser() {
|
|
1628
|
-
return process.env
|
|
1582
|
+
return hostChannelAccount(process.env);
|
|
1629
1583
|
}
|
|
1630
1584
|
function readHostChannelKey(keyPath) {
|
|
1631
1585
|
let contents;
|
|
@@ -1670,6 +1624,80 @@ function explainDialFailure(probe, host, port, target) {
|
|
|
1670
1624
|
const cidr = containerBridgeCidr();
|
|
1671
1625
|
return { hint, rule: hostFirewallRule("unknown", cidr ? [cidr] : [], port) };
|
|
1672
1626
|
}
|
|
1627
|
+
var AUTH_MEMO_OK_MS = 3e4;
|
|
1628
|
+
var AUTH_MEMO_FAIL_MS = 5e3;
|
|
1629
|
+
var authMemo = null;
|
|
1630
|
+
function invalidateHostChannelAuth() {
|
|
1631
|
+
authMemo = null;
|
|
1632
|
+
}
|
|
1633
|
+
function probeForwarding(client, timeoutMs) {
|
|
1634
|
+
return new Promise((resolve) => {
|
|
1635
|
+
let settled = false;
|
|
1636
|
+
const finish = (verdict) => {
|
|
1637
|
+
if (settled) return;
|
|
1638
|
+
settled = true;
|
|
1639
|
+
clearTimeout(timer);
|
|
1640
|
+
resolve(verdict);
|
|
1641
|
+
};
|
|
1642
|
+
const timer = setTimeout(() => finish("unknown"), timeoutMs);
|
|
1643
|
+
timer.unref?.();
|
|
1644
|
+
try {
|
|
1645
|
+
client.forwardOut("127.0.0.1", 0, "127.0.0.1", 1, (err, stream) => {
|
|
1646
|
+
if (stream) {
|
|
1647
|
+
stream.destroy();
|
|
1648
|
+
finish("ok");
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const reason = err?.reason;
|
|
1652
|
+
const message = err?.message;
|
|
1653
|
+
if (reason === 1 || reason === 3) return finish("blocked");
|
|
1654
|
+
if (typeof message === "string" && /administratively prohibited/i.test(message)) {
|
|
1655
|
+
return finish("blocked");
|
|
1656
|
+
}
|
|
1657
|
+
finish("ok");
|
|
1658
|
+
});
|
|
1659
|
+
} catch {
|
|
1660
|
+
finish("unknown");
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
async function verifyHostChannelAuth(config) {
|
|
1665
|
+
const memoKey = `${config.username}@${config.host}:${config.port}#${config.privateKey.length}`;
|
|
1666
|
+
if (authMemo?.key === memoKey && authMemo.expires > Date.now()) {
|
|
1667
|
+
return authMemo.hint ? { hint: authMemo.hint, forwarding: authMemo.forwarding } : { forwarding: authMemo.forwarding };
|
|
1668
|
+
}
|
|
1669
|
+
let hint = null;
|
|
1670
|
+
let forwarding = "unknown";
|
|
1671
|
+
try {
|
|
1672
|
+
const { connectSshClient: connectSshClient2 } = await import("./ssh-client-625VWGKP.js");
|
|
1673
|
+
const client = await connectSshClient2({
|
|
1674
|
+
host: config.host,
|
|
1675
|
+
port: config.port,
|
|
1676
|
+
username: config.username,
|
|
1677
|
+
privateKey: config.privateKey,
|
|
1678
|
+
readyTimeoutMs: config.timeoutMs,
|
|
1679
|
+
// Load-bearing: it selects describeSshAuthFailure's host-channel wording, so the
|
|
1680
|
+
// hint an operator reads here is the same sentence the deploy log gives them.
|
|
1681
|
+
hostChannel: true
|
|
1682
|
+
});
|
|
1683
|
+
forwarding = await probeForwarding(
|
|
1684
|
+
client,
|
|
1685
|
+
config.timeoutMs
|
|
1686
|
+
);
|
|
1687
|
+
client.end();
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
if (isSshAuthError(err)) {
|
|
1690
|
+
hint = err instanceof Error ? err.message : HOST_CHANNEL_AUTH_REJECTED;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
authMemo = {
|
|
1694
|
+
key: memoKey,
|
|
1695
|
+
hint,
|
|
1696
|
+
forwarding,
|
|
1697
|
+
expires: Date.now() + (hint ? AUTH_MEMO_FAIL_MS : AUTH_MEMO_OK_MS)
|
|
1698
|
+
};
|
|
1699
|
+
return hint ? { hint, forwarding } : { forwarding };
|
|
1700
|
+
}
|
|
1673
1701
|
async function hostChannelHealth(timeoutMs = 2500) {
|
|
1674
1702
|
if (hostControlDisabled()) {
|
|
1675
1703
|
return {
|
|
@@ -1692,32 +1720,48 @@ async function hostChannelHealth(timeoutMs = 2500) {
|
|
|
1692
1720
|
const port = hostChannelPort();
|
|
1693
1721
|
const target = `${hostChannelUser()}@${host}:${port}`;
|
|
1694
1722
|
const keyPath = process.env.OPENSHIP_HOST_SSH_KEY?.trim();
|
|
1723
|
+
let privateKey;
|
|
1695
1724
|
if (keyPath) {
|
|
1696
|
-
const
|
|
1697
|
-
if (reason) {
|
|
1725
|
+
const read = readHostChannelKey(keyPath);
|
|
1726
|
+
if (read.reason) {
|
|
1698
1727
|
return {
|
|
1699
1728
|
ok: false,
|
|
1700
1729
|
code: "key_unreadable",
|
|
1701
1730
|
host,
|
|
1702
1731
|
port,
|
|
1703
1732
|
target,
|
|
1704
|
-
hint: `${reason} ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1733
|
+
hint: `${read.reason} ${HOST_CHANNEL_NOT_PROVISIONED}`
|
|
1705
1734
|
};
|
|
1706
1735
|
}
|
|
1736
|
+
privateKey = read.key;
|
|
1707
1737
|
}
|
|
1708
1738
|
const probe = await probeTcpDetailed(host, port, timeoutMs);
|
|
1709
|
-
if (probe.ok) {
|
|
1710
|
-
return {
|
|
1739
|
+
if (!probe.ok) {
|
|
1740
|
+
return {
|
|
1741
|
+
ok: false,
|
|
1742
|
+
code: "unreachable",
|
|
1743
|
+
host,
|
|
1744
|
+
port,
|
|
1745
|
+
target,
|
|
1746
|
+
cause: probe.reason,
|
|
1747
|
+
...explainDialFailure(probe, host, port, target)
|
|
1748
|
+
};
|
|
1711
1749
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1750
|
+
let forwarding = "unknown";
|
|
1751
|
+
if (privateKey) {
|
|
1752
|
+
const verdict = await verifyHostChannelAuth({
|
|
1753
|
+
host,
|
|
1754
|
+
port,
|
|
1755
|
+
username: hostChannelUser(),
|
|
1756
|
+
privateKey,
|
|
1757
|
+
timeoutMs
|
|
1758
|
+
});
|
|
1759
|
+
if (verdict.hint) {
|
|
1760
|
+
return { ok: false, code: "auth_rejected", host, port, target, hint: verdict.hint };
|
|
1761
|
+
}
|
|
1762
|
+
forwarding = verdict.forwarding;
|
|
1763
|
+
}
|
|
1764
|
+
return { ok: true, code: "ok", host, port, target, forwarding };
|
|
1721
1765
|
}
|
|
1722
1766
|
function unavailableExecutor(reason, code = "not_configured") {
|
|
1723
1767
|
const refuse = async () => {
|
|
@@ -1787,6 +1831,7 @@ export {
|
|
|
1787
1831
|
setHostControlOverride,
|
|
1788
1832
|
hostControlDisabled,
|
|
1789
1833
|
containerBridgeCidr,
|
|
1834
|
+
invalidateHostChannelAuth,
|
|
1790
1835
|
hostChannelHealth,
|
|
1791
1836
|
unavailableExecutor,
|
|
1792
1837
|
createHostExecutor
|