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.
Files changed (63) hide show
  1. package/README.md +7 -2
  2. package/dist/{bare-FSUAM5V5.js → bare-XQIPDO4R.js} +10 -9
  3. package/dist/{chunk-PSZSKEPA.js → chunk-3ZAGC5NT.js} +64 -17
  4. package/dist/{chunk-6ODUBKUB.js → chunk-5OUWIPOB.js} +69 -7
  5. package/dist/chunk-6HOA5JT7.js +124 -0
  6. package/dist/{chunk-TWJCLD4W.js → chunk-6JELW75A.js} +4 -4
  7. package/dist/{chunk-GUQ3QCJ7.js → chunk-7334NSJL.js} +399 -369
  8. package/dist/chunk-7S4USQYF.js +1753 -0
  9. package/dist/{chunk-3LDWWY7U.js → chunk-A7EWM2KA.js} +41 -7
  10. package/dist/chunk-ELGRMZOW.js +25 -0
  11. package/dist/chunk-EZPPTWJQ.js +292 -0
  12. package/dist/{chunk-IXM5245F.js → chunk-GRTXMUHA.js} +553 -538
  13. package/dist/chunk-HPXP6LXZ.js +428 -0
  14. package/dist/{chunk-OTLSJYAC.js → chunk-LEYSL46B.js} +2135 -85
  15. package/dist/{chunk-EMXI4UCU.js → chunk-LFCSC4IU.js} +29 -10
  16. package/dist/{chunk-JIMUYEKN.js → chunk-MSZUCNPV.js} +1 -1
  17. package/dist/{chunk-OGY63DGD.js → chunk-PZRRL5PC.js} +71 -28
  18. package/dist/{chunk-VE2YOK4I.js → chunk-QLX7NBEA.js} +1218 -588
  19. package/dist/chunk-RFWJE7ZN.js +68 -0
  20. package/dist/{chunk-VDDMADOF.js → chunk-TM2IQPYU.js} +16 -2
  21. package/dist/{chunk-3FYIXVX5.js → chunk-VDESJSG6.js} +451 -106
  22. package/dist/{chunk-B3CE2OMR.js → chunk-VHMOQL5Y.js} +276 -50
  23. package/dist/{chunk-QEH4QUBX.js → chunk-VLQ3UIVO.js} +393 -53
  24. package/dist/{chunk-VZ2ZHPS2.js → chunk-WE7BXW6L.js} +3 -9
  25. package/dist/chunk-WFRXYWK2.js +444 -0
  26. package/dist/chunk-XRNRT37W.js +316 -0
  27. package/dist/{chunk-EP2TNALB.js → chunk-XVOBL7BC.js} +31 -8
  28. package/dist/cloud-3DODOWTR.js +36 -0
  29. package/dist/{detect-ALF4GXRX.js → detect-P4CIQBRH.js} +3 -3
  30. package/dist/{docker-QECQJ5P4.js → docker-3K2NEBD6.js} +15 -8
  31. package/dist/{docker-edge-executor-62N64MGS.js → docker-edge-executor-YZZ6P7IC.js} +5 -5
  32. package/dist/{edge-import-CQWHN2Q3.js → edge-import-MCJNVTZ2.js} +26 -23
  33. package/dist/edge-preflight-ES6FH4JK.js +42 -0
  34. package/dist/{ensure-container-edge-MJVXJQG5.js → ensure-container-edge-YPITNH45.js} +9 -8
  35. package/dist/{executor-JELARVKC.js → executor-DZGX55K2.js} +12 -5
  36. package/dist/host-channel-preflight-MUXE3KZU.js +49 -0
  37. package/dist/index.js +3668 -4063
  38. package/dist/{nginx-AXGNWRKZ.js → nginx-NSAHWGJV.js} +11 -5
  39. package/dist/node-bootstrap.js +241 -0
  40. package/dist/node-entry.js +52 -1
  41. package/dist/{openresty-lua-IE4P5YQX.js → openresty-lua-DR4D3EZ6.js} +6 -2
  42. package/dist/privilege-YTVI4SKF.js +19 -0
  43. package/dist/server/engine/samples/fail2ban/jail.d/sshd.local +4 -1
  44. package/dist/server/index.js +21501 -13137
  45. package/dist/server/lua/geo_country.lua +4 -3
  46. package/dist/server/lua/pipe_stream.lua +52 -39
  47. package/dist/server/lua/site_logger.lua +22 -24
  48. package/dist/server/migrations/0098_instance_product_mode.sql +12 -0
  49. package/dist/server/migrations/0099_mail_webmail_project.sql +24 -0
  50. package/dist/server/migrations/0100_server_ssh_private_key.sql +11 -0
  51. package/dist/server/migrations/0101_mail_resume_step.sql +12 -0
  52. package/dist/server/migrations/0102_project_workload_axes.sql +19 -0
  53. package/dist/server/migrations/0103_dns_credential.sql +41 -0
  54. package/dist/server/migrations/0104_instance_host_control.sql +15 -0
  55. package/dist/server/migrations/meta/_journal.json +49 -0
  56. package/dist/{setup-FA4PW4NU.js → setup-WUUZURHS.js} +10 -9
  57. package/dist/up-XEO3SR6Z.js +58 -0
  58. package/package.json +4 -2
  59. package/dist/chunk-IW2UKBVF.js +0 -144
  60. package/dist/chunk-ZCFVHA25.js +0 -89
  61. package/dist/cloud-E5M6VMCG.js +0 -31
  62. package/dist/server/lua/pipe_log.lua +0 -77
  63. /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` (or just `openship update`). **Build from source instead:** add `-f docker/docker-compose.build.yml … up -d --build`.
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-IXM5245F.js";
9
- import "./chunk-JIMUYEKN.js";
10
- import "./chunk-IW2UKBVF.js";
11
- import "./chunk-VDDMADOF.js";
12
- import "./chunk-B3CE2OMR.js";
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-3LDWWY7U.js";
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-OTLSJYAC.js";
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-GUQ3QCJ7.js";
8
+ } from "./chunk-7334NSJL.js";
9
9
  import {
10
10
  createDockerBuildContext,
11
11
  normalizeDockerRelativePath,
12
- resolveDockerfileCandidates
13
- } from "./chunk-EMXI4UCU.js";
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-JIMUYEKN.js";
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-VDDMADOF.js";
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-OTLSJYAC.js";
38
+ } from "./chunk-LEYSL46B.js";
40
39
 
41
40
  // ../../packages/adapters/src/runtime/cloud.ts
42
41
  import { readFile } from "fs/promises";
43
- import { join, posix } from "path";
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 = join(context.contextDir, ...context.dockerfileName.split("/"));
1799
+ const dockerfilePath = resolveWithinDirectory(context.contextDir, context.dockerfileName);
1752
1800
  const dockerfile = await readFile(dockerfilePath, "utf-8");
1753
- const contextRoot = join(
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-ZCFVHA25.js";
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
- openSshExecChannel,
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-VZ2ZHPS2.js";
14
+ } from "./chunk-WE7BXW6L.js";
15
15
  import {
16
16
  logEntry,
17
17
  sq