openship 0.6.5 → 0.6.6

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 (54) hide show
  1. package/dist/{bare-XQIPDO4R.js → bare-OXJVW3AK.js} +11 -10
  2. package/dist/{chunk-WE7BXW6L.js → chunk-2YM3NI2E.js} +2 -2
  3. package/dist/{chunk-RFWJE7ZN.js → chunk-4I2BM2DT.js} +3 -3
  4. package/dist/{chunk-QLX7NBEA.js → chunk-5DMWSTOM.js} +78 -1836
  5. package/dist/{chunk-VDESJSG6.js → chunk-77QDF6UD.js} +135 -21
  6. package/dist/{chunk-7334NSJL.js → chunk-A4GXYE4L.js} +67 -31
  7. package/dist/{chunk-HPXP6LXZ.js → chunk-CBLD2HS5.js} +3 -3
  8. package/dist/{chunk-6JELW75A.js → chunk-FE7ZZQSE.js} +3 -3
  9. package/dist/{chunk-XVOBL7BC.js → chunk-HKRQXPTI.js} +1 -1
  10. package/dist/{chunk-GRTXMUHA.js → chunk-LFE5KQ7Y.js} +11 -11
  11. package/dist/{chunk-TM2IQPYU.js → chunk-LH2PLRQO.js} +2 -2
  12. package/dist/{chunk-LFCSC4IU.js → chunk-LUKYPVJ4.js} +3 -3
  13. package/dist/{chunk-VHMOQL5Y.js → chunk-MLYSEZQB.js} +115 -19
  14. package/dist/{chunk-7S4USQYF.js → chunk-NK5N5LXN.js} +50 -122
  15. package/dist/{chunk-3ZAGC5NT.js → chunk-OBPKYXQL.js} +17 -6
  16. package/dist/{chunk-5OUWIPOB.js → chunk-PCPVTH6M.js} +12 -84
  17. package/dist/chunk-Q7WGTQQ5.js +88 -0
  18. package/dist/{chunk-VLQ3UIVO.js → chunk-QVW76WDL.js} +52 -26
  19. package/dist/{chunk-PZRRL5PC.js → chunk-RSR7PFIQ.js} +14 -3
  20. package/dist/{chunk-A7EWM2KA.js → chunk-SM26HDS2.js} +1 -1
  21. package/dist/{chunk-LEYSL46B.js → chunk-UJ6JZM65.js} +3426 -632
  22. package/dist/{chunk-WFRXYWK2.js → chunk-UVNMQM7W.js} +1 -1
  23. package/dist/chunk-VTGPEC6B.js +308 -0
  24. package/dist/chunk-WVAML4H6.js +2145 -0
  25. package/dist/{chunk-MSZUCNPV.js → chunk-YIMOCZBZ.js} +1 -1
  26. package/dist/{chunk-EZPPTWJQ.js → chunk-YMMQRE5J.js} +1 -1
  27. package/dist/{cloud-3DODOWTR.js → cloud-75VIT4KV.js} +16 -15
  28. package/dist/{detect-P4CIQBRH.js → detect-OIE5V3VZ.js} +3 -3
  29. package/dist/{docker-3K2NEBD6.js → docker-VTR2GK5Y.js} +7 -6
  30. package/dist/{docker-edge-executor-YZZ6P7IC.js → docker-edge-executor-22IBXNYJ.js} +4 -4
  31. package/dist/{edge-import-MCJNVTZ2.js → edge-import-WJUB4M7F.js} +23 -22
  32. package/dist/{edge-preflight-ES6FH4JK.js → edge-preflight-GBNCPNVU.js} +10 -10
  33. package/dist/{ensure-container-edge-YPITNH45.js → ensure-container-edge-KPI2CVXJ.js} +9 -9
  34. package/dist/{executor-DZGX55K2.js → executor-7LXXZPBR.js} +6 -3
  35. package/dist/{host-channel-preflight-MUXE3KZU.js → host-channel-preflight-KE6OONYK.js} +23 -22
  36. package/dist/index.js +95 -84
  37. package/dist/{nginx-NSAHWGJV.js → nginx-KUFMLMAC.js} +9 -7
  38. package/dist/{openresty-lua-DR4D3EZ6.js → openresty-lua-ATTH35BE.js} +6 -4
  39. package/dist/{privilege-YTVI4SKF.js → privilege-I4W3ZNZK.js} +4 -4
  40. package/dist/server/engine/samples/amavisd/amavisd.conf +25 -0
  41. package/dist/server/engine/samples/postfix/master.cf +14 -0
  42. package/dist/server/index.js +35002 -27533
  43. package/dist/server/migrations/0105_mail_inbound_rules.sql +80 -0
  44. package/dist/server/migrations/0106_mcp_call_tracking.sql +23 -0
  45. package/dist/server/migrations/0107_build_minute_metering_indexes.sql +20 -0
  46. package/dist/server/migrations/0108_credential.sql +68 -0
  47. package/dist/server/migrations/meta/_journal.json +28 -0
  48. package/dist/{setup-WUUZURHS.js → setup-XGLGUUFW.js} +10 -10
  49. package/dist/ssh-client-JBMA7SN4.js +28 -0
  50. package/dist/{up-XEO3SR6Z.js → up-K4BV7466.js} +26 -27
  51. package/package.json +1 -1
  52. package/dist/chunk-6HOA5JT7.js +0 -124
  53. package/dist/chunk-XRNRT37W.js +0 -316
  54. /package/dist/{dist-es-ZP5QDSJ3.js → dist-es-AXFRS7PS.js} +0 -0
@@ -15,7 +15,7 @@ import {
15
15
  parseSystemOs,
16
16
  safeErrorMessage,
17
17
  systemDebug
18
- } from "./chunk-LEYSL46B.js";
18
+ } from "./chunk-UJ6JZM65.js";
19
19
 
20
20
  // ../../packages/adapters/src/system/elevated-executor.ts
21
21
  import { randomBytes } from "crypto";
@@ -0,0 +1,308 @@
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
+ COMPOSE_ENV_FILE,
7
+ composeInstallExists,
8
+ readComposeEnvFile,
9
+ startUnitHint
10
+ } from "./chunk-5DMWSTOM.js";
11
+ import {
12
+ INTERNAL_TOKEN_FILE,
13
+ OS_DIR
14
+ } from "./chunk-ELGRMZOW.js";
15
+ import {
16
+ LocalExecutor
17
+ } from "./chunk-6FH4AXVY.js";
18
+ import {
19
+ EDGE_CONTAINER_NAME,
20
+ edgeCrashReason
21
+ } from "./chunk-2YM3NI2E.js";
22
+
23
+ // src/lib/edge-import.ts
24
+ import chalk from "chalk";
25
+ import ora from "ora";
26
+
27
+ // src/lib/internal-token.ts
28
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
29
+ import { randomBytes } from "crypto";
30
+ function mintBareInternalToken() {
31
+ if (existsSync(INTERNAL_TOKEN_FILE)) return readFileSync(INTERNAL_TOKEN_FILE, "utf8").trim();
32
+ mkdirSync(OS_DIR, { recursive: true, mode: 448 });
33
+ const token = randomBytes(32).toString("hex");
34
+ writeFileSync(INTERNAL_TOKEN_FILE, token, { mode: 384 });
35
+ return token;
36
+ }
37
+ function bareInternalToken() {
38
+ try {
39
+ return readFileSync(INTERNAL_TOKEN_FILE, "utf8").trim() || null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ function internalTokenSources() {
45
+ const tokens = [];
46
+ const problems = [];
47
+ if (composeInstallExists()) {
48
+ const { env, unreadable } = readComposeEnvFile();
49
+ const composeToken = env.INTERNAL_TOKEN?.trim();
50
+ if (composeToken) {
51
+ tokens.push(composeToken);
52
+ } else if (unreadable) {
53
+ problems.push(
54
+ `can't read ${COMPOSE_ENV_FILE} (${unreadable}) \u2014 that file holds the running stack's token, so re-run this command with sudo`
55
+ );
56
+ } else {
57
+ problems.push(
58
+ `${COMPOSE_ENV_FILE} has no INTERNAL_TOKEN \u2014 re-run \`openship up\` to regenerate it`
59
+ );
60
+ }
61
+ }
62
+ const bare = bareInternalToken();
63
+ if (bare && !tokens.includes(bare)) tokens.push(bare);
64
+ return { tokens, problems };
65
+ }
66
+ function resolveInternalToken() {
67
+ return internalTokenSources().tokens[0] ?? null;
68
+ }
69
+ function internalTokenProblem() {
70
+ const { problems } = internalTokenSources();
71
+ if (problems.length) return problems.join("; ");
72
+ return `no internal token found on this machine (looked in ${COMPOSE_ENV_FILE} and ${INTERNAL_TOKEN_FILE}) \u2014 run \`openship up\` on this box first`;
73
+ }
74
+ function internalTokenRejectedProblem() {
75
+ const { tokens, problems } = internalTokenSources();
76
+ const plural = tokens.length > 1 ? `both of this machine's internal tokens` : `this machine's internal token`;
77
+ const unreadable = problems.length ? `${problems.join("; ")}. ` : "";
78
+ return `${unreadable}the API rejected ${plural}. It's running with a different one \u2014 restart it (\`openship up\`) so it picks up the current token, or check that this port belongs to this install`;
79
+ }
80
+
81
+ // src/lib/loopback-api.ts
82
+ function isInternalAuthRejection(body) {
83
+ try {
84
+ return JSON.parse(body).error === "Unauthorized";
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ function replay(res, body) {
90
+ return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
91
+ }
92
+ async function internalFetch(port, path, init = {}, token) {
93
+ const candidates = token ? [token] : internalTokenSources().tokens;
94
+ if (candidates.length === 0) return { kind: "no-token", detail: internalTokenProblem() };
95
+ const url = `http://127.0.0.1:${port}${path}`;
96
+ let refused;
97
+ for (const candidate of candidates) {
98
+ let res;
99
+ try {
100
+ res = await fetch(url, {
101
+ ...init,
102
+ headers: {
103
+ ...init.headers,
104
+ "X-Internal-Token": candidate
105
+ }
106
+ });
107
+ } catch (err) {
108
+ return { kind: "unreachable", detail: err.message };
109
+ }
110
+ if (res.status !== 401) return { kind: "response", res };
111
+ const body = await res.text().catch(() => "");
112
+ if (!isInternalAuthRejection(body)) return { kind: "response", res: replay(res, body) };
113
+ refused = replay(res, body);
114
+ }
115
+ return { kind: "response", res: refused, tokenRejected: true };
116
+ }
117
+ async function internalGet(port, path, token) {
118
+ const call = await internalFetch(
119
+ port,
120
+ path,
121
+ { signal: AbortSignal.timeout(1e4) },
122
+ token
123
+ );
124
+ if (call.kind !== "response" || !call.res.ok) return null;
125
+ return await call.res.json().catch(() => null);
126
+ }
127
+ async function internalPost(port, path, body, token) {
128
+ const call = await internalFetch(
129
+ port,
130
+ path,
131
+ {
132
+ method: "POST",
133
+ headers: { "Content-Type": "application/json" },
134
+ body: JSON.stringify(body),
135
+ signal: AbortSignal.timeout(3e4)
136
+ },
137
+ token
138
+ );
139
+ if (call.kind !== "response") return { ok: false, data: { error: call.detail } };
140
+ const data = await call.res.json().catch(() => ({}));
141
+ if (call.tokenRejected && !token) {
142
+ return { ok: false, data: { ...data, error: internalTokenRejectedProblem() } };
143
+ }
144
+ return { ok: call.res.ok, data };
145
+ }
146
+ async function bootstrapAdmin(apiPort, admin, token) {
147
+ const { ok, data } = await internalPost(apiPort, "/api/system/bootstrap-admin", admin, token);
148
+ if (ok) return { ok: true };
149
+ if (data?.error === "An admin account already exists") return { ok: true, message: "already-exists" };
150
+ return { ok: false, message: data?.error || "failed" };
151
+ }
152
+ async function waitHealthy(apiPort, seconds = 90) {
153
+ for (let i = 0; i < seconds; i++) {
154
+ await new Promise((r) => setTimeout(r, 1e3));
155
+ try {
156
+ await fetch(`http://127.0.0.1:${apiPort}/api/health`, { signal: AbortSignal.timeout(2e3) });
157
+ return true;
158
+ } catch {
159
+ }
160
+ }
161
+ return false;
162
+ }
163
+ async function waitDashboard(dashPort, seconds = 45) {
164
+ for (let i = 0; i < seconds; i++) {
165
+ await new Promise((r) => setTimeout(r, 1e3));
166
+ try {
167
+ const res = await fetch(`http://127.0.0.1:${dashPort}/`, {
168
+ redirect: "manual",
169
+ signal: AbortSignal.timeout(2e3)
170
+ });
171
+ if (res.status > 0) return true;
172
+ } catch {
173
+ }
174
+ }
175
+ return false;
176
+ }
177
+ async function detectPublicIp() {
178
+ for (const url of ["https://api.ipify.org", "https://ifconfig.me/ip"]) {
179
+ try {
180
+ const res = await fetch(url, { signal: AbortSignal.timeout(3e3) });
181
+ if (!res.ok) continue;
182
+ const ip = (await res.text()).trim();
183
+ if (/^[0-9.]+$/.test(ip) || ip.includes(":")) return ip;
184
+ } catch {
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+
190
+ // src/lib/edge-import.ts
191
+ async function waitForApiHealth(port, tries) {
192
+ for (let i = 0; i < tries; i++) {
193
+ try {
194
+ const r = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(2e3) });
195
+ if (r.ok) return true;
196
+ } catch {
197
+ }
198
+ await new Promise((r) => setTimeout(r, 1e3));
199
+ }
200
+ return false;
201
+ }
202
+ async function waitForEdgeRunning(tries) {
203
+ const exec = new LocalExecutor();
204
+ for (let i = 0; i < tries; i++) {
205
+ const out = await exec.exec(`docker inspect -f '{{.State.Running}}' ${EDGE_CONTAINER_NAME}`).catch(() => "");
206
+ if (out.trim() === "true") return true;
207
+ await new Promise((r) => setTimeout(r, 1e3));
208
+ }
209
+ return false;
210
+ }
211
+ async function importMigratedSites(apiPort, sites, certPems, staticRootOverrides) {
212
+ if (!resolveInternalToken()) {
213
+ const error = internalTokenProblem();
214
+ console.log(chalk.yellow(` Skipping site import \u2014 ${error}. Re-run to retry.
215
+ `));
216
+ return { ok: false, registered: [], error };
217
+ }
218
+ const spinner = ora(
219
+ `Importing ${sites.length} migrated site${sites.length === 1 ? "" : "s"} into the edge\u2026`
220
+ ).start();
221
+ if (!await waitForApiHealth(apiPort, 60)) {
222
+ const error = "API didn't become healthy in time";
223
+ spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
224
+ return { ok: false, registered: [], error };
225
+ }
226
+ if (!await waitForEdgeRunning(60)) {
227
+ const error = `the ${EDGE_CONTAINER_NAME} container isn't running yet`;
228
+ spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
229
+ return { ok: false, registered: [], error };
230
+ }
231
+ try {
232
+ const call = await internalFetch(apiPort, "/api/system/edge/import-sites", {
233
+ method: "POST",
234
+ headers: { "Content-Type": "application/json" },
235
+ body: JSON.stringify({ sites, certPems, staticRootOverrides }),
236
+ signal: AbortSignal.timeout(12e4)
237
+ });
238
+ if (call.kind !== "response") {
239
+ spinner.warn(`Site import failed: ${call.detail}. Re-run to retry.`);
240
+ return { ok: false, registered: [], error: call.detail };
241
+ }
242
+ const r = call.res;
243
+ const data = await r.json().catch(() => ({}));
244
+ if (!r.ok) {
245
+ const error = data.error ?? `HTTP ${r.status}`;
246
+ spinner.warn(`Site import failed: ${error}. Re-run to retry.`);
247
+ return { ok: false, registered: [], error };
248
+ }
249
+ const registered = data.registered ?? [];
250
+ const warnings = data.warnings ?? [];
251
+ const missed = sites.length - registered.length;
252
+ if (sites.length > 0 && registered.length === 0) {
253
+ spinner.fail(
254
+ `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.`
255
+ );
256
+ for (const w of warnings.slice(0, 8)) console.log(chalk.red(` \u2022 ${w}`));
257
+ const edgeLog = await edgeCrashReason(new LocalExecutor());
258
+ if (edgeLog) {
259
+ console.log(chalk.red(`
260
+ The edge container is not running. Its log says:`));
261
+ console.log(chalk.red(` ${edgeLog}`));
262
+ }
263
+ const back = startUnitHint("nginx");
264
+ console.log(
265
+ chalk.yellow(
266
+ `
267
+ Retry the import with \`openship up\` once the cause above is fixed, or put your
268
+ ` + (back ? ` previous proxy back: docker stop ${EDGE_CONTAINER_NAME} && ${back}
269
+ ` : ` previous proxy back by stopping ${EDGE_CONTAINER_NAME} and starting it the way
270
+ this host starts services.
271
+ `)
272
+ )
273
+ );
274
+ return { ok: false, registered, error: warnings[0] ?? "no sites were registered" };
275
+ }
276
+ if (missed > 0) {
277
+ spinner.warn(
278
+ `Migrated ${registered.length}/${sites.length} sites \u2014 ${missed} not served (see below).`
279
+ );
280
+ for (const w of warnings.slice(0, 8)) console.log(chalk.yellow(` \u2022 ${w}`));
281
+ return { ok: false, registered, error: `${missed} site(s) not registered` };
282
+ }
283
+ spinner.succeed(
284
+ `Migrated ${registered.length} site${registered.length === 1 ? "" : "s"} into Openship's edge.`
285
+ );
286
+ for (const w of warnings.slice(0, 8)) console.log(chalk.dim(` \u2022 ${w}`));
287
+ return { ok: true, registered };
288
+ } catch (e) {
289
+ const error = e.message;
290
+ spinner.warn(`Site import failed: ${error}. Re-run to retry.`);
291
+ return { ok: false, registered: [], error };
292
+ }
293
+ }
294
+
295
+ export {
296
+ mintBareInternalToken,
297
+ internalTokenRejectedProblem,
298
+ internalFetch,
299
+ internalGet,
300
+ internalPost,
301
+ bootstrapAdmin,
302
+ waitHealthy,
303
+ waitDashboard,
304
+ detectPublicIp,
305
+ waitForApiHealth,
306
+ waitForEdgeRunning,
307
+ importMigratedSites
308
+ };