openship 0.6.6 → 0.6.8

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 (72) hide show
  1. package/dist/bare-FA66N7GC.js +29 -0
  2. package/dist/{chunk-77QDF6UD.js → chunk-3MP6ACKM.js} +587 -156
  3. package/dist/{chunk-YIMOCZBZ.js → chunk-5DJOB7IX.js} +3 -3
  4. package/dist/{chunk-WVAML4H6.js → chunk-5I3COAFH.js} +274 -65
  5. package/dist/{chunk-5DMWSTOM.js → chunk-5UXHT6ED.js} +940 -224
  6. package/dist/{chunk-LUKYPVJ4.js → chunk-6GU6JSK6.js} +329 -466
  7. package/dist/{chunk-RSR7PFIQ.js → chunk-7PEXM2UI.js} +213 -45
  8. package/dist/{chunk-VTGPEC6B.js → chunk-7YBWXEM6.js} +6 -4
  9. package/dist/chunk-AHJYP7DG.js +938 -0
  10. package/dist/chunk-BO6CY4V2.js +265 -0
  11. package/dist/{chunk-FE7ZZQSE.js → chunk-D3OEMXYP.js} +3 -3
  12. package/dist/chunk-D6HU4VUZ.js +249 -0
  13. package/dist/{chunk-LFE5KQ7Y.js → chunk-ENKJ7XIF.js} +152 -57
  14. package/dist/{chunk-YMMQRE5J.js → chunk-GNOMEMNI.js} +1 -1
  15. package/dist/{chunk-QVW76WDL.js → chunk-IAMOUDHY.js} +190 -64
  16. package/dist/{chunk-OBPKYXQL.js → chunk-MUWVC5R4.js} +219 -58
  17. package/dist/chunk-N5YZMO33.js +111 -0
  18. package/dist/{chunk-A4GXYE4L.js → chunk-NIYD7MD2.js} +206 -52
  19. package/dist/{chunk-UVNMQM7W.js → chunk-NPSRGIHV.js} +8 -6
  20. package/dist/{chunk-2GWYYHOR.js → chunk-NRU2EBYZ.js} +2 -2
  21. package/dist/{chunk-CBLD2HS5.js → chunk-NUT6HKK6.js} +8 -5
  22. package/dist/{chunk-ZFJXPCQP.js → chunk-P7RCYELG.js} +4 -3
  23. package/dist/chunk-R4VDPFLS.js +106 -0
  24. package/dist/{chunk-HKRQXPTI.js → chunk-R5DC5PEF.js} +1 -1
  25. package/dist/{chunk-NK5N5LXN.js → chunk-RCMERVBL.js} +24 -9
  26. package/dist/{chunk-RXQBD4ZY.js → chunk-RHLNVZMS.js} +2 -2
  27. package/dist/chunk-SK4FFCFR.js +70 -0
  28. package/dist/{chunk-4I2BM2DT.js → chunk-T46BQOLY.js} +3 -3
  29. package/dist/{chunk-6FH4AXVY.js → chunk-T6DVUX4F.js} +1 -1
  30. package/dist/{chunk-PCPVTH6M.js → chunk-TAZZZKYO.js} +6 -4
  31. package/dist/{chunk-MLYSEZQB.js → chunk-UAWY6X7V.js} +43 -72
  32. package/dist/{chunk-Q7WGTQQ5.js → chunk-VAI5VOOI.js} +1 -1
  33. package/dist/{chunk-UJ6JZM65.js → chunk-WEZFTL4V.js} +1325 -513
  34. package/dist/{chunk-2YM3NI2E.js → chunk-X53JXRS4.js} +38 -44
  35. package/dist/{cloud-KRTZ2IL5.js → cloud-2CRDKOWQ.js} +1 -1
  36. package/dist/cloud-REV3DPJH.js +42 -0
  37. package/dist/{detect-OIE5V3VZ.js → detect-BBFT5GEU.js} +9 -6
  38. package/dist/{docker-VTR2GK5Y.js → docker-EG3QSE6I.js} +11 -8
  39. package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-E5TOTLMR.js} +8 -6
  40. package/dist/edge-import-YJVJUGYC.js +56 -0
  41. package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-QUW4BM7N.js} +15 -12
  42. package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-FOEFQ3PI.js} +15 -10
  43. package/dist/{executor-7LXXZPBR.js → executor-NTWDJ3DB.js} +8 -6
  44. package/dist/host-channel-preflight-OMCZMMKR.js +54 -0
  45. package/dist/index.js +198 -76
  46. package/dist/{local-executor-WOFZD72X.js → local-executor-OQ2GRN7K.js} +3 -2
  47. package/dist/{nginx-KUFMLMAC.js → nginx-4U4C2XUS.js} +11 -8
  48. package/dist/{noop-NJ72F46T.js → noop-CLLJHCPL.js} +1 -1
  49. package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-LLWT5XQW.js} +8 -5
  50. package/dist/{privilege-I4W3ZNZK.js → privilege-MNDVMDST.js} +6 -5
  51. package/dist/server/index.js +40049 -30378
  52. package/dist/server/migrations/0109_service_build_args.sql +9 -0
  53. package/dist/server/migrations/0110_service_deployment_host_ports.sql +18 -0
  54. package/dist/server/migrations/0111_host_port_claim.sql +381 -0
  55. package/dist/server/migrations/0112_backup_run_execution_lease.sql +12 -0
  56. package/dist/server/migrations/0113_docker_migration_execution_lease.sql +11 -0
  57. package/dist/server/migrations/0114_build_session_execution_lease_index.sql +6 -0
  58. package/dist/server/migrations/0115_orphan_target_key.sql +3 -0
  59. package/dist/server/migrations/0116_domain_ssl_challenge.sql +2 -0
  60. package/dist/server/migrations/meta/_journal.json +56 -0
  61. package/dist/setup-IFIMPQZJ.js +25 -0
  62. package/dist/{ssh-client-JBMA7SN4.js → ssh-client-2XWZCMGI.js} +3 -2
  63. package/dist/up-HQS7IR7H.js +61 -0
  64. package/package.json +1 -1
  65. package/dist/bare-OXJVW3AK.js +0 -25
  66. package/dist/chunk-LH2PLRQO.js +0 -434
  67. package/dist/chunk-SM26HDS2.js +0 -185
  68. package/dist/cloud-75VIT4KV.js +0 -37
  69. package/dist/edge-import-WJUB4M7F.js +0 -52
  70. package/dist/host-channel-preflight-KE6OONYK.js +0 -50
  71. package/dist/setup-XGLGUUFW.js +0 -22
  72. package/dist/up-K4BV7466.js +0 -57
@@ -28,7 +28,7 @@ import {
28
28
  resolvePorts,
29
29
  sourceBuildDir,
30
30
  verifyHostChannel
31
- } from "./chunk-WVAML4H6.js";
31
+ } from "./chunk-5I3COAFH.js";
32
32
  import {
33
33
  bootstrapAdmin,
34
34
  importMigratedSites,
@@ -36,18 +36,18 @@ import {
36
36
  internalPost,
37
37
  mintBareInternalToken,
38
38
  waitHealthy
39
- } from "./chunk-VTGPEC6B.js";
39
+ } from "./chunk-7YBWXEM6.js";
40
40
  import {
41
41
  startUnitHint,
42
42
  thisHost
43
- } from "./chunk-5DMWSTOM.js";
43
+ } from "./chunk-5UXHT6ED.js";
44
44
  import {
45
45
  completeHostEdge,
46
46
  markStoppedProxyImported,
47
47
  planAndApplyHostEdge,
48
48
  previewHostEdge,
49
49
  rollbackHostEdge
50
- } from "./chunk-CBLD2HS5.js";
50
+ } from "./chunk-NUT6HKK6.js";
51
51
  import {
52
52
  AUTH_SECRET_FILE,
53
53
  DATA_DIR,
@@ -58,15 +58,17 @@ import {
58
58
  } from "./chunk-ELGRMZOW.js";
59
59
  import {
60
60
  LocalExecutor
61
- } from "./chunk-6FH4AXVY.js";
61
+ } from "./chunk-T6DVUX4F.js";
62
62
  import {
63
- EDGE_CONTAINER_NAME,
64
63
  edgeCrashReason,
65
64
  edgeIsBroken
66
- } from "./chunk-2YM3NI2E.js";
65
+ } from "./chunk-X53JXRS4.js";
66
+ import {
67
+ EDGE_CONTAINER_NAME
68
+ } from "./chunk-D6HU4VUZ.js";
67
69
  import {
68
70
  isValidEmail
69
- } from "./chunk-UJ6JZM65.js";
71
+ } from "./chunk-WEZFTL4V.js";
70
72
 
71
73
  // src/commands/up.ts
72
74
  import { Command } from "commander";
@@ -609,6 +611,7 @@ async function planUp(opts) {
609
611
  ...opts.hostControl === false ? { noHostControl: true } : {},
610
612
  ...opts.hostSshHost ? { hostSshHost: opts.hostSshHost } : {},
611
613
  ...opts.hostSshPort ? { hostSshPort: opts.hostSshPort } : {},
614
+ ...opts.hostSshUser ? { hostSshUser: opts.hostSshUser } : {},
612
615
  ...opts.resetSecrets ? { resetSecrets: true } : {},
613
616
  ...opts.mail ? { mail: true } : {}
614
617
  });
@@ -630,6 +633,13 @@ async function planUp(opts) {
630
633
  ] : [],
631
634
  ...stack.hostChannel.rootUnavailable ? [
632
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)`
633
643
  ] : []
634
644
  ] : [],
635
645
  ...stack.mountDirs.map((d) => `${d}/ (edge routing state + certs)`),
@@ -1071,6 +1081,9 @@ var upCommand = new Command("up").description("Start Openship as a persistent se
1071
1081
  ).option(
1072
1082
  "--host-ssh-port <port>",
1073
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."
1074
1087
  ).option(
1075
1088
  "--open-host-firewall",
1076
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."
@@ -1213,6 +1226,7 @@ async function runCompose(opts) {
1213
1226
  // so the containers would keep an environment the file no longer shows.
1214
1227
  hostSshHost: opts.hostSshHost,
1215
1228
  hostSshPort: opts.hostSshPort,
1229
+ hostSshUser: opts.hostSshUser,
1216
1230
  resetSecrets: opts.resetSecrets,
1217
1231
  mail: opts.mail
1218
1232
  });
@@ -1267,6 +1281,7 @@ async function runCompose(opts) {
1267
1281
  // configured with rather than resetting it to host.docker.internal:22.
1268
1282
  hostSshHost: opts.hostSshHost,
1269
1283
  hostSshPort: opts.hostSshPort,
1284
+ hostSshUser: opts.hostSshUser,
1270
1285
  resetSecrets: opts.resetSecrets,
1271
1286
  // Absent on a re-run keeps the install's mode (resolveEnvConfig), so this
1272
1287
  // never flips a mail box back to the platform shell.
@@ -1549,7 +1564,7 @@ async function runForeground(opts, source) {
1549
1564
  const uiSpinner = ora("Preparing the dashboard\u2026").start();
1550
1565
  try {
1551
1566
  const bundle = await ensureDashboard({
1552
- tag: source ? "local" : opts.uiVersion || `v${"0.6.6"}`,
1567
+ tag: source ? "local" : opts.uiVersion || `v${"0.6.8"}`,
1553
1568
  onProgress: (received, total) => {
1554
1569
  if (total) {
1555
1570
  uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
@@ -7,9 +7,9 @@ const require = __ospCreateRequire(import.meta.url);
7
7
  var NoopInfraProvider = class {
8
8
  async registerRoute(_route) {
9
9
  }
10
- async removeRoute(_domain) {
10
+ async removeRoute(_domain, _opts) {
11
11
  }
12
- async provisionCert(domain) {
12
+ async provisionCert(domain, _opts) {
13
13
  return { domain, expiresAt: "", issuer: "none", verified: false };
14
14
  }
15
15
  async renewCert(domain) {
@@ -0,0 +1,70 @@
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
+ sq
7
+ } from "./chunk-P7RCYELG.js";
8
+
9
+ // ../../packages/adapters/src/runtime/managed-artifact.ts
10
+ var MANAGED_ARTIFACT_BASE = "/opt/openship";
11
+ var PROTECTED_ROOTS = /* @__PURE__ */ new Set([
12
+ MANAGED_ARTIFACT_BASE,
13
+ // BareRuntime's own layout (workDir = MANAGED_ARTIFACT_BASE): releases/<depId>,
14
+ // .builds/<sessionId>. The parents hold every project's releases at once.
15
+ `${MANAGED_ARTIFACT_BASE}/releases`,
16
+ `${MANAGED_ARTIFACT_BASE}/.builds`,
17
+ // The static tree, whose parent is ALSO the one directory bind-mounted into the
18
+ // edge container — removing it stops the box serving every static site it hosts.
19
+ `${MANAGED_ARTIFACT_BASE}/static`,
20
+ `${MANAGED_ARTIFACT_BASE}/static/releases`,
21
+ `${MANAGED_ARTIFACT_BASE}/static/.builds`
22
+ ]);
23
+ function canonical(ref) {
24
+ const trimmed = ref.trim();
25
+ return trimmed.length > 1 ? trimmed.replace(/\/+$/, "") : trimmed;
26
+ }
27
+ function assertManagedArtifactPath(ref) {
28
+ const target = canonical(ref);
29
+ if (!target.startsWith("/") || target.includes("..")) {
30
+ throw new Error(
31
+ `Refusing to remove "${ref}": an artifact path must be absolute and free of "..".`
32
+ );
33
+ }
34
+ if (PROTECTED_ROOTS.has(target)) {
35
+ throw new Error(
36
+ `Refusing to remove "${ref}": that is a shared parent directory, not one deployment's output.`
37
+ );
38
+ }
39
+ if (target !== MANAGED_ARTIFACT_BASE && !target.startsWith(`${MANAGED_ARTIFACT_BASE}/`)) {
40
+ throw new Error(
41
+ `Refusing to remove "${ref}": outside ${MANAGED_ARTIFACT_BASE}. Openship removes only directories it created (release dirs, static doc-roots, build dirs).`
42
+ );
43
+ }
44
+ }
45
+ async function removeManagedArtifact(executor, ref) {
46
+ assertManagedArtifactPath(ref);
47
+ const target = canonical(ref);
48
+ if (executor) {
49
+ await executor.exec(`rm -rf ${sq(target)}`);
50
+ if (await executor.exists(target)) {
51
+ throw new Error(`Failed to remove ${target}: still present after rm -rf.`);
52
+ }
53
+ return;
54
+ }
55
+ const { rm, access } = await import("fs/promises");
56
+ await rm(target, { recursive: true, force: true });
57
+ const stillThere = await access(target).then(
58
+ () => true,
59
+ () => false
60
+ );
61
+ if (stillThere) throw new Error(`Failed to remove ${target}: still present after rm.`);
62
+ }
63
+ function isArtifactPathRef(ref) {
64
+ return ref.startsWith("/");
65
+ }
66
+
67
+ export {
68
+ removeManagedArtifact,
69
+ isArtifactPathRef
70
+ };
@@ -4,15 +4,15 @@ import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  hostRefusal
7
- } from "./chunk-YMMQRE5J.js";
7
+ } from "./chunk-GNOMEMNI.js";
8
8
  import {
9
9
  elevatedExecutor,
10
10
  resolveEnvironment
11
- } from "./chunk-UVNMQM7W.js";
11
+ } from "./chunk-NPSRGIHV.js";
12
12
  import {
13
13
  answered,
14
14
  refused
15
- } from "./chunk-UJ6JZM65.js";
15
+ } from "./chunk-WEZFTL4V.js";
16
16
 
17
17
  // ../../packages/adapters/src/system/privilege.ts
18
18
  function rootChecked(executor, why) {
@@ -8,7 +8,7 @@ import {
8
8
  getLocalShellPath,
9
9
  logEntry,
10
10
  sq
11
- } from "./chunk-ZFJXPCQP.js";
11
+ } from "./chunk-P7RCYELG.js";
12
12
 
13
13
  // ../../packages/adapters/src/system/local-executor.ts
14
14
  import { exec, spawn } from "child_process";
@@ -2,15 +2,17 @@
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
+ isSshAuthError,
7
+ systemDebug
8
+ } from "./chunk-N5YZMO33.js";
5
9
  import {
6
10
  HOST_CHANNEL_AUTH_REJECTED,
7
11
  HOST_CHANNEL_NOT_PROVISIONED,
8
12
  HOST_CHANNEL_ROW_CREDENTIALS_UNUSED,
9
13
  hostFirewallRule,
10
- isSshAuthError,
11
- safeErrorMessage,
12
- systemDebug
13
- } from "./chunk-UJ6JZM65.js";
14
+ safeErrorMessage
15
+ } from "./chunk-WEZFTL4V.js";
14
16
 
15
17
  // ../../packages/adapters/src/system/ssh-client.ts
16
18
  import { Client } from "ssh2";
@@ -2,103 +2,52 @@
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
+ probeTcpDetailed
7
+ } from "./chunk-R4VDPFLS.js";
5
8
  import {
6
9
  getTarCreateEnv,
7
10
  prepareSourceTarArgs
8
- } from "./chunk-Q7WGTQQ5.js";
11
+ } from "./chunk-VAI5VOOI.js";
9
12
  import {
10
13
  connectSshClient,
11
14
  openDockerDialStdioChannel,
12
15
  openSftp,
13
16
  openSshUnixSocket,
14
17
  reconcileKnownHosts
15
- } from "./chunk-PCPVTH6M.js";
18
+ } from "./chunk-TAZZZKYO.js";
16
19
  import {
17
20
  LocalExecutor
18
- } from "./chunk-6FH4AXVY.js";
21
+ } from "./chunk-T6DVUX4F.js";
19
22
  import {
20
23
  emitBufferedLines,
21
24
  flushBufferedLines,
22
25
  hasLocalCommand,
23
26
  logEntry,
24
27
  sq
25
- } from "./chunk-ZFJXPCQP.js";
28
+ } from "./chunk-P7RCYELG.js";
29
+ import {
30
+ HostChannelUnavailableError,
31
+ SshDisconnectedError,
32
+ commandForError,
33
+ isSshAuthError
34
+ } from "./chunk-N5YZMO33.js";
26
35
  import {
27
36
  HOST_CHANNEL_AUTH_REJECTED,
28
37
  HOST_CHANNEL_NOT_PROVISIONED,
29
38
  HOST_CHANNEL_UNPROVISIONED,
30
- HostChannelUnavailableError,
31
- SshDisconnectedError,
32
39
  TRANSFER_EXCLUDES,
33
- commandForError,
34
40
  explainHostChannelCause,
35
41
  formatBytes,
36
42
  hostChannelAccount,
37
43
  hostFirewallRule,
38
- isSshAuthError,
39
44
  safeErrorMessage
40
- } from "./chunk-UJ6JZM65.js";
45
+ } from "./chunk-WEZFTL4V.js";
41
46
 
42
47
  // ../../packages/adapters/src/system/executor.ts
43
48
  import { existsSync, readFileSync } from "fs";
44
49
  import { networkInterfaces } from "os";
45
50
 
46
- // ../../packages/adapters/src/system/reachability.ts
47
- import { connect } from "net";
48
- import { request } from "http";
49
- function classifyDialError(code) {
50
- switch (code) {
51
- case "ECONNREFUSED":
52
- case "ECONNRESET":
53
- return "refused";
54
- case "ENOTFOUND":
55
- case "EAI_AGAIN":
56
- return "unresolved";
57
- case "EHOSTUNREACH":
58
- case "ENETUNREACH":
59
- case "ENETDOWN":
60
- return "no_route";
61
- // The kernel's own connect timeout, i.e. our `timeout` reported for us.
62
- case "ETIMEDOUT":
63
- return "timeout";
64
- default:
65
- return "error";
66
- }
67
- }
68
- function probeTcpDetailed(host, port, timeoutMs = 2500) {
69
- return new Promise((resolve) => {
70
- let settled = false;
71
- const done = (result, socket2) => {
72
- if (settled) return;
73
- settled = true;
74
- try {
75
- socket2?.destroy();
76
- } catch {
77
- }
78
- resolve(result);
79
- };
80
- const socket = connect({ host, port });
81
- socket.setTimeout(timeoutMs);
82
- socket.once("connect", () => done({ ok: true }, socket));
83
- socket.once(
84
- "timeout",
85
- () => done({ ok: false, reason: "timeout", message: `no response within ${timeoutMs}ms` }, socket)
86
- );
87
- socket.once("error", (err) => {
88
- const errno = err;
89
- done(
90
- {
91
- ok: false,
92
- reason: classifyDialError(errno.code),
93
- ...errno.code ? { code: errno.code } : {},
94
- ...errno.message ? { message: errno.message } : {}
95
- },
96
- socket
97
- );
98
- });
99
- });
100
- }
101
-
102
51
  // ../../packages/adapters/src/system/ssh-executor.ts
103
52
  import { createReadStream } from "fs";
104
53
  import { mkdtemp as mkdtemp2, rm as fsRm2, stat } from "fs/promises";
@@ -113,7 +62,10 @@ import { tmpdir } from "os";
113
62
  import { join } from "path";
114
63
  function packLocalArchive(tarArgs, outFile) {
115
64
  return new Promise((resolve, reject) => {
116
- const tar = spawn("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: getTarCreateEnv() });
65
+ const tar = spawn("tar", tarArgs, {
66
+ stdio: ["ignore", "pipe", "pipe"],
67
+ env: getTarCreateEnv()
68
+ });
117
69
  const out = createWriteStream(outFile);
118
70
  let stderr = "";
119
71
  let tarCode = null;
@@ -122,7 +74,12 @@ function packLocalArchive(tarArgs, outFile) {
122
74
  const settle = () => {
123
75
  if (!tarClosed || !outClosed) return;
124
76
  if (tarCode === 0) resolve();
125
- else reject(new Error(`tar failed (exit ${tarCode})${stderr.trim() ? `: ${stderr.trim().slice(-300)}` : ""}`));
77
+ else
78
+ reject(
79
+ new Error(
80
+ `tar failed (exit ${tarCode})${stderr.trim() ? `: ${stderr.trim().slice(-300)}` : ""}`
81
+ )
82
+ );
126
83
  };
127
84
  tar.stderr.on("data", (d) => {
128
85
  stderr += d.toString();
@@ -153,9 +110,17 @@ async function extractRemoteArchive(exec, remoteArchive, remotePath, expectedByt
153
110
  );
154
111
  onLog?.(logEntry(`Transferred ${formatBytes(expectedBytes)} and extracted on the server.`));
155
112
  }
113
+ function formatPrivateKeyForOpenSsh(privateKey) {
114
+ const normalized = privateKey.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
115
+ return normalized.endsWith("\n") ? normalized : `${normalized}
116
+ `;
117
+ }
156
118
  async function canUseRemoteRsync(deps) {
157
119
  if (deps.config.privateKey && deps.config.privateKeyPassphrase && !deps.config.sshAgent) {
158
- return { ok: false, reason: "encrypted SSH keys without an agent are not supported by non-interactive rsync" };
120
+ return {
121
+ ok: false,
122
+ reason: "encrypted SSH keys without an agent are not supported by non-interactive rsync"
123
+ };
159
124
  }
160
125
  const [localRsync, localSsh, localSshpass, remoteRsync] = await Promise.all([
161
126
  hasLocalCommand("rsync"),
@@ -165,7 +130,8 @@ async function canUseRemoteRsync(deps) {
165
130
  ]);
166
131
  if (!localRsync) return { ok: false, reason: "local rsync is not installed" };
167
132
  if (!localSsh) return { ok: false, reason: "local ssh is not installed" };
168
- if (!localSshpass) return { ok: false, reason: "local sshpass is not installed for password-based rsync" };
133
+ if (!localSshpass)
134
+ return { ok: false, reason: "local sshpass is not installed for password-based rsync" };
169
135
  if (!remoteRsync) return { ok: false, reason: "remote rsync is not installed" };
170
136
  return { ok: true };
171
137
  }
@@ -208,7 +174,7 @@ async function withTemporaryPrivateKey(config, fn) {
208
174
  const tempDir = await mkdtemp(join(tmpdir(), "openship-rsync-key-"));
209
175
  const keyPath = join(tempDir, "id_rsa");
210
176
  try {
211
- await fsWriteFile(keyPath, config.privateKey);
177
+ await fsWriteFile(keyPath, formatPrivateKeyForOpenSsh(config.privateKey), { mode: 384 });
212
178
  await chmod(keyPath, 384);
213
179
  return await fn(keyPath);
214
180
  } finally {
@@ -254,7 +220,12 @@ async function uploadFileWithRsync(localFile, remoteFile, deps, onLog, opts) {
254
220
  let lastCode = 1;
255
221
  for (let attempt = 1; attempt <= retries; attempt++) {
256
222
  if (attempt > 1) {
257
- onLog?.(logEntry(`Connection dropped \u2014 resuming upload (attempt ${attempt}/${retries})...`, "warn"));
223
+ onLog?.(
224
+ logEntry(
225
+ `Connection dropped \u2014 resuming upload (attempt ${attempt}/${retries})...`,
226
+ "warn"
227
+ )
228
+ );
258
229
  }
259
230
  const args = [
260
231
  "-a",
@@ -1720,7 +1691,7 @@ async function verifyHostChannelAuth(config) {
1720
1691
  let hint = null;
1721
1692
  let forwarding = "unknown";
1722
1693
  try {
1723
- const { connectSshClient: connectSshClient2 } = await import("./ssh-client-JBMA7SN4.js");
1694
+ const { connectSshClient: connectSshClient2 } = await import("./ssh-client-2XWZCMGI.js");
1724
1695
  const client = await connectSshClient2({
1725
1696
  host: config.host,
1726
1697
  port: config.port,
@@ -4,7 +4,7 @@ import { createRequire as __ospCreateRequire } from "node:module";
4
4
  const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  PACKAGE_ROOT_ONLY_EXCLUDES
7
- } from "./chunk-UJ6JZM65.js";
7
+ } from "./chunk-WEZFTL4V.js";
8
8
 
9
9
  // ../../packages/adapters/src/archive.ts
10
10
  import { execFile } from "child_process";