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
package/README.md
CHANGED
|
@@ -77,10 +77,13 @@ From the desktop app you connect a server (SSH) or Openship Cloud and deploy to
|
|
|
77
77
|
Install the CLI (it bundles the API + dashboard), then run **`openship`** — an interactive wizard creates the first admin, wires your domain, and installs Openship as a boot service. Run it again anytime to manage the instance.
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
curl -fsSL https://get.openship.io | sh # install (or: npm i -g openship)
|
|
80
|
+
curl -fsSL https://get.openship.io | sh # install (or: npm i -g openship — needs Node 22+)
|
|
81
81
|
openship # guided setup, then control panel
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
The install script brings its own Node when your system one is older than 22; a package-manager
|
|
85
|
+
install runs on the Node you already have.
|
|
86
|
+
|
|
84
87
|
For CI / headless boxes, skip the wizard and drive `openship up` directly:
|
|
85
88
|
|
|
86
89
|
```bash
|
|
@@ -154,7 +157,9 @@ docker compose --env-file .env -f docker/docker-compose.yml up -d
|
|
|
154
157
|
|
|
155
158
|
The stack is **postgres + redis + api + dashboard + edge**. The `edge` is OpenResty on **:80/:443** as a container (`network_mode: host`) — routing + Let's Encrypt, no bare host install. **Linux only** (host networking); on mac/win use `openship up` (bare). The `api` container mounts the host Docker socket so the control plane can build + run your apps as host containers — it's host-privileged through the socket, so run it only on a trusted host.
|
|
156
159
|
|
|
157
|
-
**Upgrade:** pin `OPENSHIP_VERSION` in `.env` for reproducible pulls, then `docker compose --env-file .env -f docker/docker-compose.yml pull && … up -d`
|
|
160
|
+
**Upgrade:** pin `OPENSHIP_VERSION` in `.env` for reproducible pulls, then `docker compose --env-file .env -f docker/docker-compose.yml pull && … up -d`. `openship update` only reconciles a stack the CLI installed, and `openship up` would *adopt* this one — don't reach for either here. **Build from source instead:** add `-f docker/docker-compose.build.yml … up -d --build`.
|
|
161
|
+
|
|
162
|
+
**Host operations** (`:80`/`:443` takeover, the mail engine, host terminal/port scans) need the container→host SSH channel, which `openship up` provisions and this path does not — the five manual steps are in `.env.example` under *Host operations from the container*, and the failure it produces is [Troubleshooting → Host control channel](https://openship.io/docs/troubleshooting/host-channel). Everything else, including deploys, works without it.
|
|
158
163
|
|
|
159
164
|
> The **root** `docker-compose.yml` is a different file: it's the SaaS / from-source **control plane** (builds from source, ships the marketing site, no edge/socket). It does **not** self-host your apps — use `docker/docker-compose.yml` above or `openship up`.
|
|
160
165
|
|
|
@@ -5,17 +5,18 @@ const require = __ospCreateRequire(import.meta.url);
|
|
|
5
5
|
import {
|
|
6
6
|
BareRuntime,
|
|
7
7
|
STATIC_RELEASE_BASE
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-6ODUBKUB.js";
|
|
14
|
-
import "./chunk-ZCFVHA25.js";
|
|
8
|
+
} from "./chunk-GRTXMUHA.js";
|
|
9
|
+
import "./chunk-MSZUCNPV.js";
|
|
10
|
+
import "./chunk-TM2IQPYU.js";
|
|
11
|
+
import "./chunk-VHMOQL5Y.js";
|
|
12
|
+
import "./chunk-5OUWIPOB.js";
|
|
15
13
|
import "./chunk-6FH4AXVY.js";
|
|
16
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-RFWJE7ZN.js";
|
|
15
|
+
import "./chunk-A7EWM2KA.js";
|
|
16
|
+
import "./chunk-EZPPTWJQ.js";
|
|
17
|
+
import "./chunk-WFRXYWK2.js";
|
|
17
18
|
import "./chunk-ZFJXPCQP.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-LEYSL46B.js";
|
|
19
20
|
import "./chunk-PXKCZB2I.js";
|
|
20
21
|
export {
|
|
21
22
|
BareRuntime,
|
|
@@ -5,42 +5,41 @@ const require = __ospCreateRequire(import.meta.url);
|
|
|
5
5
|
import {
|
|
6
6
|
checkGit,
|
|
7
7
|
installGit
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7334NSJL.js";
|
|
9
9
|
import {
|
|
10
10
|
createDockerBuildContext,
|
|
11
11
|
normalizeDockerRelativePath,
|
|
12
|
-
resolveDockerfileCandidates
|
|
13
|
-
|
|
12
|
+
resolveDockerfileCandidates,
|
|
13
|
+
resolveWithinDirectory
|
|
14
|
+
} from "./chunk-LFCSC4IU.js";
|
|
14
15
|
import {
|
|
15
16
|
prepareStackOutput,
|
|
16
17
|
resolveProjectDir,
|
|
17
18
|
resolveStaticOutputPath,
|
|
18
19
|
runLocalBuild
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-MSZUCNPV.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-TM2IQPYU.js";
|
|
30
28
|
import {
|
|
31
29
|
STACKS,
|
|
32
30
|
SYSTEM,
|
|
33
31
|
TRANSFER_EXCLUDES,
|
|
34
32
|
buildOutputTransferExcludes,
|
|
33
|
+
isRuntimeNotFoundError,
|
|
35
34
|
missingOutputDirectoryMessage,
|
|
36
35
|
packageManagerEnsureCommand,
|
|
37
36
|
safeErrorMessage,
|
|
38
37
|
shellSplitWords
|
|
39
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-LEYSL46B.js";
|
|
40
39
|
|
|
41
40
|
// ../../packages/adapters/src/runtime/cloud.ts
|
|
42
41
|
import { readFile } from "fs/promises";
|
|
43
|
-
import {
|
|
42
|
+
import { posix } from "path";
|
|
44
43
|
|
|
45
44
|
// ../../packages/adapters/src/types.ts
|
|
46
45
|
var DEFAULT_RESOURCE_CONFIG = {
|
|
@@ -1367,6 +1366,16 @@ async function provisionCloudWorkspace(client, config, logger) {
|
|
|
1367
1366
|
throw err;
|
|
1368
1367
|
}
|
|
1369
1368
|
}
|
|
1369
|
+
var HostBuildForbiddenError = class extends Error {
|
|
1370
|
+
code = "HOST_BUILD_FORBIDDEN";
|
|
1371
|
+
constructor(message) {
|
|
1372
|
+
super(message);
|
|
1373
|
+
this.name = "HostBuildForbiddenError";
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
function isHostBuildForbiddenError(err) {
|
|
1377
|
+
return err instanceof HostBuildForbiddenError || err instanceof Error && err.name === "HostBuildForbiddenError";
|
|
1378
|
+
}
|
|
1370
1379
|
var CloudRuntime = class {
|
|
1371
1380
|
name = "cloud";
|
|
1372
1381
|
capabilities = /* @__PURE__ */ new Set([
|
|
@@ -1387,7 +1396,10 @@ var CloudRuntime = class {
|
|
|
1387
1396
|
// disk is still there to start back up (see makeActive).
|
|
1388
1397
|
"unitRestore",
|
|
1389
1398
|
"serviceShell",
|
|
1390
|
-
"inContainerExec"
|
|
1399
|
+
"inContainerExec",
|
|
1400
|
+
// Runs in the REMOTE workspace over the Oblien API, so it never touches the
|
|
1401
|
+
// control-plane host.
|
|
1402
|
+
"isolatedExec"
|
|
1391
1403
|
]);
|
|
1392
1404
|
/**
|
|
1393
1405
|
* Services map to Oblien workspaces, not Docker containers, so host-level
|
|
@@ -1396,7 +1408,13 @@ var CloudRuntime = class {
|
|
|
1396
1408
|
* logging/dns — join this set as they're added in later phases.
|
|
1397
1409
|
*/
|
|
1398
1410
|
unsupportedComposeKeys = /* @__PURE__ */ new Set([
|
|
1399
|
-
"healthcheck"
|
|
1411
|
+
"healthcheck",
|
|
1412
|
+
// Namespace sharing has no Oblien equivalent — a workspace is not a container
|
|
1413
|
+
// whose netns/pidns a peer can join. Declared here so the deploy warns once
|
|
1414
|
+
// per service and continues, rather than the workload coming up on its own
|
|
1415
|
+
// network with nothing having said so (#533).
|
|
1416
|
+
"networkMode",
|
|
1417
|
+
"pidMode"
|
|
1400
1418
|
]);
|
|
1401
1419
|
client;
|
|
1402
1420
|
// Optional admin-scoped proxy. Set when this runtime is constructed
|
|
@@ -1409,9 +1427,20 @@ var CloudRuntime = class {
|
|
|
1409
1427
|
builtArtifacts = /* @__PURE__ */ new Map();
|
|
1410
1428
|
activeBuilds = /* @__PURE__ */ new Map();
|
|
1411
1429
|
compose;
|
|
1430
|
+
/**
|
|
1431
|
+
* May this runtime do work on the machine the API process runs on?
|
|
1432
|
+
*
|
|
1433
|
+
* DEFAULT DENY, unlike every other optional knob on PlatformConfig. The permissive
|
|
1434
|
+
* default is what the two host arms below relied on, and the cost of forgetting to
|
|
1435
|
+
* set it is a tenant's build commands running on a multi-tenant control plane —
|
|
1436
|
+
* while the cost of a missed opt-IN is a self-hosted local-orchestrated cloud deploy
|
|
1437
|
+
* failing loudly with the message below. Loud beats silent here.
|
|
1438
|
+
*/
|
|
1439
|
+
allowHostBuild;
|
|
1412
1440
|
constructor(client, opts) {
|
|
1413
1441
|
this.client = client;
|
|
1414
1442
|
this.adminProxy = opts?.adminProxy;
|
|
1443
|
+
this.allowHostBuild = opts?.allowHostBuild ?? false;
|
|
1415
1444
|
this.compose = new CloudComposeSupport({
|
|
1416
1445
|
client,
|
|
1417
1446
|
builtArtifacts: this.builtArtifacts,
|
|
@@ -1420,6 +1449,16 @@ var CloudRuntime = class {
|
|
|
1420
1449
|
execAndStream: (runtime, command, onLog, timeoutSeconds) => this.execAndStream(runtime, command, onLog, timeoutSeconds)
|
|
1421
1450
|
});
|
|
1422
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Refuse work on the API's own machine unless this runtime was explicitly built to
|
|
1454
|
+
* allow it. `what` completes the sentence "… is not allowed to <what>".
|
|
1455
|
+
*/
|
|
1456
|
+
assertHostWorkAllowed(what) {
|
|
1457
|
+
if (this.allowHostBuild) return;
|
|
1458
|
+
throw new HostBuildForbiddenError(
|
|
1459
|
+
`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").`
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1423
1462
|
supports(cap) {
|
|
1424
1463
|
return this.capabilities.has(cap);
|
|
1425
1464
|
}
|
|
@@ -1496,6 +1535,11 @@ var CloudRuntime = class {
|
|
|
1496
1535
|
return activeBuild.abort.signal.aborted ? { ...result2, status: "cancelled", errorMessage: void 0 } : result2;
|
|
1497
1536
|
}
|
|
1498
1537
|
const buildLocally = config.buildStrategy === "local";
|
|
1538
|
+
if (buildLocally) {
|
|
1539
|
+
this.assertHostWorkAllowed(
|
|
1540
|
+
"run this project's install and build commands on the machine this API runs on"
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1499
1543
|
let wsId;
|
|
1500
1544
|
let rt;
|
|
1501
1545
|
try {
|
|
@@ -1629,6 +1673,9 @@ var CloudRuntime = class {
|
|
|
1629
1673
|
await this.ensureWorkspaceGit(rt, plog, "build workspace");
|
|
1630
1674
|
return;
|
|
1631
1675
|
}
|
|
1676
|
+
this.assertHostWorkAllowed(
|
|
1677
|
+
"package a source directory from the machine this API runs on"
|
|
1678
|
+
);
|
|
1632
1679
|
await transferLocalDirectory(
|
|
1633
1680
|
cfg.localPath,
|
|
1634
1681
|
{
|
|
@@ -1743,17 +1790,15 @@ var CloudRuntime = class {
|
|
|
1743
1790
|
if (!config.localPath) {
|
|
1744
1791
|
return this.resolveRemoteDockerfileBuildSource(config, logger);
|
|
1745
1792
|
}
|
|
1793
|
+
this.assertHostWorkAllowed("read and package a source tree from the machine this API runs on");
|
|
1746
1794
|
logger.log("Preparing local Dockerfile source...\n");
|
|
1747
1795
|
const context = await createDockerBuildContext(config, {
|
|
1748
1796
|
requireRepositoryDockerfile: true,
|
|
1749
1797
|
onLog: logger.callback
|
|
1750
1798
|
});
|
|
1751
|
-
const dockerfilePath =
|
|
1799
|
+
const dockerfilePath = resolveWithinDirectory(context.contextDir, context.dockerfileName);
|
|
1752
1800
|
const dockerfile = await readFile(dockerfilePath, "utf-8");
|
|
1753
|
-
const contextRoot =
|
|
1754
|
-
context.contextDir,
|
|
1755
|
-
...normalizeDockerRelativePath(context.rootDirectory).split("/").filter(Boolean)
|
|
1756
|
-
);
|
|
1801
|
+
const contextRoot = resolveWithinDirectory(context.contextDir, context.rootDirectory);
|
|
1757
1802
|
return {
|
|
1758
1803
|
kind: "local",
|
|
1759
1804
|
contextRoot,
|
|
@@ -3129,5 +3174,7 @@ function clampShellWindow(value, fallback, min, max) {
|
|
|
3129
3174
|
export {
|
|
3130
3175
|
PAGE_CONTAINER_PREFIX,
|
|
3131
3176
|
provisionCloudWorkspace,
|
|
3177
|
+
HostBuildForbiddenError,
|
|
3178
|
+
isHostBuildForbiddenError,
|
|
3132
3179
|
CloudRuntime
|
|
3133
3180
|
};
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
|
+
PACKAGE_ROOT_ONLY_EXCLUDES,
|
|
7
|
+
hostFirewallRule,
|
|
6
8
|
isSshAuthError,
|
|
9
|
+
safeErrorMessage,
|
|
7
10
|
systemDebug
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import {
|
|
10
|
-
PACKAGE_ROOT_ONLY_EXCLUDES
|
|
11
|
-
} from "./chunk-OTLSJYAC.js";
|
|
11
|
+
} from "./chunk-LEYSL46B.js";
|
|
12
12
|
|
|
13
13
|
// ../../packages/adapters/src/archive.ts
|
|
14
14
|
import { execFile } from "child_process";
|
|
@@ -107,6 +107,16 @@ function describeSshAuthFailure(config, originalMessage) {
|
|
|
107
107
|
}
|
|
108
108
|
return `SSH authentication failed for ${target}. (${originalMessage})`;
|
|
109
109
|
}
|
|
110
|
+
function describeSshConnectFailure(config, originalMessage) {
|
|
111
|
+
const target = formatSshTarget(config);
|
|
112
|
+
const port = config.port ?? 22;
|
|
113
|
+
if (config.hostChannel) {
|
|
114
|
+
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:
|
|
115
|
+
${hostFirewallRule("unknown", [], port)}
|
|
116
|
+
Or re-run \`openship up\`, which probes this and offers the exact rule. (${originalMessage})`;
|
|
117
|
+
}
|
|
118
|
+
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})`;
|
|
119
|
+
}
|
|
110
120
|
function execFileText(command, args, timeout = 5e3) {
|
|
111
121
|
return new Promise((resolve, reject) => {
|
|
112
122
|
execFile2(command, args, { timeout }, (err, stdout, stderr) => {
|
|
@@ -205,6 +215,10 @@ function toConnectConfig(config) {
|
|
|
205
215
|
passphrase: config.privateKeyPassphrase,
|
|
206
216
|
agent: config.sshAgent,
|
|
207
217
|
tryKeyboard: false,
|
|
218
|
+
// Explicit rather than relying on ssh2's implicit 20s so callers can shorten it.
|
|
219
|
+
// The container→host channel does (8s): a filtered SYN there would otherwise
|
|
220
|
+
// stall every host operation for 20s apiece with no output.
|
|
221
|
+
readyTimeout: config.readyTimeoutMs ?? 2e4,
|
|
208
222
|
keepaliveInterval: 15e3,
|
|
209
223
|
// 10 (~150s) rather than 3 (45s): a small server pegged by a heavy
|
|
210
224
|
// `docker build` / `bun install` can briefly starve sshd of keepalive
|
|
@@ -230,12 +244,12 @@ async function connectSshClient(config) {
|
|
|
230
244
|
reject(new Error(describeSshAuthFailure(config, err.message)));
|
|
231
245
|
return;
|
|
232
246
|
}
|
|
233
|
-
reject(err);
|
|
247
|
+
reject(new Error(describeSshConnectFailure(config, err.message)));
|
|
234
248
|
});
|
|
235
249
|
client.on("close", () => {
|
|
236
250
|
if (settled) return;
|
|
237
251
|
settled = true;
|
|
238
|
-
reject(new Error("SSH connection closed before ready"));
|
|
252
|
+
reject(new Error(describeSshConnectFailure(config, "SSH connection closed before ready")));
|
|
239
253
|
});
|
|
240
254
|
client.connect(toConnectConfig(config));
|
|
241
255
|
});
|
|
@@ -263,6 +277,7 @@ async function openSshUnixSocket(client, socketPath) {
|
|
|
263
277
|
});
|
|
264
278
|
});
|
|
265
279
|
}
|
|
280
|
+
var DOCKER_DIAL_STDIO_COMMAND = "docker system dial-stdio";
|
|
266
281
|
async function openSshExecChannel(client, command) {
|
|
267
282
|
return new Promise((resolve, reject) => {
|
|
268
283
|
client.exec(command, (err, stream) => {
|
|
@@ -274,6 +289,50 @@ async function openSshExecChannel(client, command) {
|
|
|
274
289
|
});
|
|
275
290
|
});
|
|
276
291
|
}
|
|
292
|
+
var DIAL_STDIO_DIAGNOSTICS = /* @__PURE__ */ Symbol("openship.dialStdioDiagnostics");
|
|
293
|
+
var DIAL_STDIO_STDERR_CAP = 4096;
|
|
294
|
+
function attachDialStdioDiagnostics(stream) {
|
|
295
|
+
const channel = stream;
|
|
296
|
+
if (channel[DIAL_STDIO_DIAGNOSTICS]) return stream;
|
|
297
|
+
const diagnostics = {
|
|
298
|
+
stderr: "",
|
|
299
|
+
exitCode: null,
|
|
300
|
+
signal: null,
|
|
301
|
+
error: null
|
|
302
|
+
};
|
|
303
|
+
channel[DIAL_STDIO_DIAGNOSTICS] = diagnostics;
|
|
304
|
+
const stderr = stream.stderr;
|
|
305
|
+
stderr?.on("data", (chunk) => {
|
|
306
|
+
if (diagnostics.stderr.length >= DIAL_STDIO_STDERR_CAP) return;
|
|
307
|
+
diagnostics.stderr = (diagnostics.stderr + chunk.toString("utf8")).slice(
|
|
308
|
+
0,
|
|
309
|
+
DIAL_STDIO_STDERR_CAP
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
stream.on("exit", (code, signal) => {
|
|
313
|
+
diagnostics.exitCode = typeof code === "number" ? code : null;
|
|
314
|
+
diagnostics.signal = typeof signal === "string" ? signal : null;
|
|
315
|
+
});
|
|
316
|
+
stream.on("error", (err) => {
|
|
317
|
+
diagnostics.error ??= safeErrorMessage(err);
|
|
318
|
+
});
|
|
319
|
+
return stream;
|
|
320
|
+
}
|
|
321
|
+
function dialStdioDiagnostics(stream) {
|
|
322
|
+
const diagnostics = stream?.[DIAL_STDIO_DIAGNOSTICS];
|
|
323
|
+
if (!diagnostics) return "";
|
|
324
|
+
const parts = [];
|
|
325
|
+
const stderr = diagnostics.stderr.trim();
|
|
326
|
+
if (stderr) parts.push(stderr.replace(/\s+/g, " "));
|
|
327
|
+
if (diagnostics.error) parts.push(diagnostics.error);
|
|
328
|
+
if (diagnostics.signal) parts.push(`killed by ${diagnostics.signal}`);
|
|
329
|
+
else if (diagnostics.exitCode) parts.push(`exit code ${diagnostics.exitCode}`);
|
|
330
|
+
if (parts.length === 0) return "";
|
|
331
|
+
return `\`${DOCKER_DIAL_STDIO_COMMAND}\` reported: ${parts.join(" \u2014 ")}`;
|
|
332
|
+
}
|
|
333
|
+
async function openDockerDialStdioChannel(client) {
|
|
334
|
+
return attachDialStdioDiagnostics(await openSshExecChannel(client, DOCKER_DIAL_STDIO_COMMAND));
|
|
335
|
+
}
|
|
277
336
|
async function execSshCommand(client, command, timeout = 1e4) {
|
|
278
337
|
return new Promise((resolve, reject) => {
|
|
279
338
|
let stdout = "";
|
|
@@ -325,6 +384,9 @@ export {
|
|
|
325
384
|
connectSshClient,
|
|
326
385
|
openSftp,
|
|
327
386
|
openSshUnixSocket,
|
|
328
|
-
|
|
387
|
+
DOCKER_DIAL_STDIO_COMMAND,
|
|
388
|
+
attachDialStdioDiagnostics,
|
|
389
|
+
dialStdioDiagnostics,
|
|
390
|
+
openDockerDialStdioChannel,
|
|
329
391
|
execSshCommand
|
|
330
392
|
};
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
composeInternalToken,
|
|
7
|
+
startUnitHint
|
|
8
|
+
} from "./chunk-QLX7NBEA.js";
|
|
9
|
+
import {
|
|
10
|
+
LocalExecutor
|
|
11
|
+
} from "./chunk-6FH4AXVY.js";
|
|
12
|
+
import {
|
|
13
|
+
EDGE_CONTAINER_NAME,
|
|
14
|
+
edgeCrashReason
|
|
15
|
+
} from "./chunk-WE7BXW6L.js";
|
|
16
|
+
|
|
17
|
+
// src/lib/edge-import.ts
|
|
18
|
+
import chalk from "chalk";
|
|
19
|
+
import ora from "ora";
|
|
20
|
+
async function waitForApiHealth(port, tries) {
|
|
21
|
+
for (let i = 0; i < tries; i++) {
|
|
22
|
+
try {
|
|
23
|
+
const r = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(2e3) });
|
|
24
|
+
if (r.ok) return true;
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
async function waitForEdgeRunning(tries) {
|
|
32
|
+
const exec = new LocalExecutor();
|
|
33
|
+
for (let i = 0; i < tries; i++) {
|
|
34
|
+
const out = await exec.exec(`docker inspect -f '{{.State.Running}}' ${EDGE_CONTAINER_NAME}`).catch(() => "");
|
|
35
|
+
if (out.trim() === "true") return true;
|
|
36
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
async function importMigratedSites(apiPort, sites, certPems, staticRootOverrides) {
|
|
41
|
+
const token = composeInternalToken();
|
|
42
|
+
if (!token) {
|
|
43
|
+
const error = "couldn't read the stack's internal token";
|
|
44
|
+
console.log(chalk.yellow(` Skipping site import \u2014 ${error}. Re-run to retry.
|
|
45
|
+
`));
|
|
46
|
+
return { ok: false, registered: [], error };
|
|
47
|
+
}
|
|
48
|
+
const spinner = ora(
|
|
49
|
+
`Importing ${sites.length} migrated site${sites.length === 1 ? "" : "s"} into the edge\u2026`
|
|
50
|
+
).start();
|
|
51
|
+
if (!await waitForApiHealth(apiPort, 60)) {
|
|
52
|
+
const error = "API didn't become healthy in time";
|
|
53
|
+
spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
|
|
54
|
+
return { ok: false, registered: [], error };
|
|
55
|
+
}
|
|
56
|
+
if (!await waitForEdgeRunning(60)) {
|
|
57
|
+
const error = `the ${EDGE_CONTAINER_NAME} container isn't running yet`;
|
|
58
|
+
spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
|
|
59
|
+
return { ok: false, registered: [], error };
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const r = await fetch(`http://127.0.0.1:${apiPort}/api/system/edge/import-sites`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json", "X-Internal-Token": token },
|
|
65
|
+
body: JSON.stringify({ sites, certPems, staticRootOverrides }),
|
|
66
|
+
signal: AbortSignal.timeout(12e4)
|
|
67
|
+
});
|
|
68
|
+
const data = await r.json().catch(() => ({}));
|
|
69
|
+
if (!r.ok) {
|
|
70
|
+
const error = data.error ?? `HTTP ${r.status}`;
|
|
71
|
+
spinner.warn(`Site import failed: ${error}. Re-run to retry.`);
|
|
72
|
+
return { ok: false, registered: [], error };
|
|
73
|
+
}
|
|
74
|
+
const registered = data.registered ?? [];
|
|
75
|
+
const warnings = data.warnings ?? [];
|
|
76
|
+
const missed = sites.length - registered.length;
|
|
77
|
+
if (sites.length > 0 && registered.length === 0) {
|
|
78
|
+
spinner.fail(
|
|
79
|
+
`Migrated NONE of the ${sites.length} site${sites.length === 1 ? "" : "s"} \u2014 Openship holds :80/:443 and ${sites.length === 1 ? "that hostname is" : "those hostnames are"} NOT being served.`
|
|
80
|
+
);
|
|
81
|
+
for (const w of warnings.slice(0, 8)) console.log(chalk.red(` \u2022 ${w}`));
|
|
82
|
+
const edgeLog = await edgeCrashReason(new LocalExecutor());
|
|
83
|
+
if (edgeLog) {
|
|
84
|
+
console.log(chalk.red(`
|
|
85
|
+
The edge container is not running. Its log says:`));
|
|
86
|
+
console.log(chalk.red(` ${edgeLog}`));
|
|
87
|
+
}
|
|
88
|
+
const back = startUnitHint("nginx");
|
|
89
|
+
console.log(
|
|
90
|
+
chalk.yellow(
|
|
91
|
+
`
|
|
92
|
+
Retry the import with \`openship up\` once the cause above is fixed, or put your
|
|
93
|
+
` + (back ? ` previous proxy back: docker stop ${EDGE_CONTAINER_NAME} && ${back}
|
|
94
|
+
` : ` previous proxy back by stopping ${EDGE_CONTAINER_NAME} and starting it the way
|
|
95
|
+
this host starts services.
|
|
96
|
+
`)
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
return { ok: false, registered, error: warnings[0] ?? "no sites were registered" };
|
|
100
|
+
}
|
|
101
|
+
if (missed > 0) {
|
|
102
|
+
spinner.warn(
|
|
103
|
+
`Migrated ${registered.length}/${sites.length} sites \u2014 ${missed} not served (see below).`
|
|
104
|
+
);
|
|
105
|
+
for (const w of warnings.slice(0, 8)) console.log(chalk.yellow(` \u2022 ${w}`));
|
|
106
|
+
return { ok: false, registered, error: `${missed} site(s) not registered` };
|
|
107
|
+
}
|
|
108
|
+
spinner.succeed(
|
|
109
|
+
`Migrated ${registered.length} site${registered.length === 1 ? "" : "s"} into Openship's edge.`
|
|
110
|
+
);
|
|
111
|
+
for (const w of warnings.slice(0, 8)) console.log(chalk.dim(` \u2022 ${w}`));
|
|
112
|
+
return { ok: true, registered };
|
|
113
|
+
} catch (e) {
|
|
114
|
+
const error = e.message;
|
|
115
|
+
spinner.warn(`Site import failed: ${error}. Re-run to retry.`);
|
|
116
|
+
return { ok: false, registered: [], error };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
waitForApiHealth,
|
|
122
|
+
waitForEdgeRunning,
|
|
123
|
+
importMigratedSites
|
|
124
|
+
};
|
|
@@ -2,16 +2,16 @@
|
|
|
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
|
-
LocalExecutor
|
|
7
|
-
} from "./chunk-6FH4AXVY.js";
|
|
8
5
|
import {
|
|
9
6
|
explainEdgeDown,
|
|
10
7
|
isEdgeDownFailure
|
|
11
8
|
} from "./chunk-I3DPMTSF.js";
|
|
9
|
+
import {
|
|
10
|
+
LocalExecutor
|
|
11
|
+
} from "./chunk-6FH4AXVY.js";
|
|
12
12
|
import {
|
|
13
13
|
edgeFailureReason
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WE7BXW6L.js";
|
|
15
15
|
import {
|
|
16
16
|
logEntry,
|
|
17
17
|
sq
|