openship 0.4.8 → 0.5.0
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 +1 -0
- package/dist/{bare-JY5Y5ELU.js → bare-VRGE5MXH.js} +7 -7
- package/dist/{chunk-TGIINPVM.js → chunk-6LKHXB6T.js} +22 -7
- package/dist/{chunk-2D3I5G6P.js → chunk-6PIYRG5I.js} +23 -3
- package/dist/{chunk-BBHX7T4R.js → chunk-BS2AXYZX.js} +401 -76
- package/dist/{chunk-SJIFLLX4.js → chunk-CUJXGK7R.js} +321 -85
- package/dist/{chunk-VFINNTUL.js → chunk-EAAGSQXN.js} +23 -8
- package/dist/{chunk-4Z5QO7N6.js → chunk-I6PSTQIR.js} +2 -2
- package/dist/{chunk-5NYUCONN.js → chunk-JLKKYUZA.js} +31 -49
- package/dist/{chunk-XPPZ7B2J.js → chunk-K2RPVGPN.js} +2 -2
- package/dist/{chunk-MZ2WEHHY.js → chunk-KNTJ7PSF.js} +1 -1
- package/dist/{chunk-L2VOGB2X.js → chunk-KZOBYJUW.js} +1 -1
- package/dist/{chunk-GKHUSZCD.js → chunk-NNPEG6HM.js} +1 -1
- package/dist/{chunk-SLTYVHU7.js → chunk-NXFIRK2Z.js} +381 -72
- package/dist/{chunk-FXTBHTWY.js → chunk-RB35YN3J.js} +271 -35
- package/dist/{chunk-6APULWSU.js → chunk-UMQP7SYT.js} +429 -66
- package/dist/{chunk-V6AN6XLZ.js → chunk-V7BZMGOT.js} +123 -45
- package/dist/{chunk-VDMQJHPI.js → chunk-YWMODKWL.js} +67 -4
- package/dist/{chunk-OXWDY22G.js → chunk-ZCOQJCZO.js} +1 -1
- package/dist/{cloud-KYWY3RP4.js → cloud-3M2WZ624.js} +12 -12
- package/dist/{detect-AZLOHUFX.js → detect-BAJMY6KM.js} +3 -3
- package/dist/{docker-LODK5Y2S.js → docker-2NGBRJFJ.js} +9 -7
- package/dist/{docker-edge-executor-VHW3JPJS.js → docker-edge-executor-5I4M5QT7.js} +1 -1
- package/dist/{edge-import-ABUDKKJI.js → edge-import-RIWNEIXQ.js} +21 -19
- package/dist/{ensure-container-edge-SW6HENSG.js → ensure-container-edge-3NSYKHRX.js} +6 -6
- package/dist/{executor-XJ6RP4Q2.js → executor-LOYMPU67.js} +3 -3
- package/dist/index.js +269 -331
- package/dist/{nginx-3L4SIBJA.js → nginx-QE4DOM5J.js} +4 -4
- package/dist/{openresty-lua-4WLGDSER.js → openresty-lua-BTAKECCF.js} +2 -2
- package/dist/server/index.js +51643 -45322
- package/dist/server/migrations/0073_project_volumes_object_storage.sql +3 -0
- package/dist/server/migrations/0074_project_compose_path.sql +1 -0
- package/dist/server/migrations/0075_service_command_argv.sql +1 -0
- package/dist/server/migrations/0076_rollback_retention.sql +17 -0
- package/dist/server/migrations/0077_domain_redirect.sql +13 -0
- package/dist/server/migrations/0078_grant_source_scope.sql +30 -0
- package/dist/server/migrations/0079_project_readiness.sql +18 -0
- package/dist/server/migrations/0080_deployment_error_classification.sql +26 -0
- package/dist/server/migrations/0081_project_readiness_repair.sql +34 -0
- package/dist/server/migrations/meta/_journal.json +63 -0
- package/dist/{setup-NU6T2PDU.js → setup-KELALK7H.js} +7 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
5
|
import {
|
|
6
|
+
DEFAULT_API_PORT,
|
|
7
|
+
DEFAULT_DASHBOARD_PORT,
|
|
6
8
|
IS_ALT_HOME,
|
|
7
9
|
OS_DIR,
|
|
8
10
|
composeDown,
|
|
@@ -13,20 +15,30 @@ import {
|
|
|
13
15
|
composeRestart,
|
|
14
16
|
composeRunning,
|
|
15
17
|
composeStart,
|
|
18
|
+
composeTrustedOriginUrls,
|
|
16
19
|
composeUninstall,
|
|
17
20
|
composeUp,
|
|
18
21
|
composeUpdate,
|
|
22
|
+
dockerGap,
|
|
19
23
|
ensureDocker,
|
|
20
24
|
hasDockerCompose,
|
|
21
25
|
importMigratedSites,
|
|
26
|
+
portMoveNotice,
|
|
22
27
|
prepareFromSource,
|
|
23
28
|
readInstallMethod,
|
|
29
|
+
readInstanceUrl,
|
|
24
30
|
readSourceInstall,
|
|
31
|
+
readStoredPorts,
|
|
25
32
|
rebuildFromSource,
|
|
26
33
|
remoteSha,
|
|
34
|
+
resolveComposePorts,
|
|
35
|
+
resolvePorts,
|
|
36
|
+
saveInstanceUrl,
|
|
27
37
|
shortSha,
|
|
28
|
-
sourceBuildDir
|
|
29
|
-
|
|
38
|
+
sourceBuildDir,
|
|
39
|
+
storedApiPort,
|
|
40
|
+
storedDashboardPort
|
|
41
|
+
} from "./chunk-BS2AXYZX.js";
|
|
30
42
|
import "./chunk-FRNAR33O.js";
|
|
31
43
|
import "./chunk-EI575GHN.js";
|
|
32
44
|
import "./chunk-6APIPIJY.js";
|
|
@@ -34,12 +46,12 @@ import "./chunk-IXCHURVK.js";
|
|
|
34
46
|
import "./chunk-OWED2GRM.js";
|
|
35
47
|
import "./chunk-VWPEKOH6.js";
|
|
36
48
|
import "./chunk-W73X43R7.js";
|
|
37
|
-
import "./chunk-
|
|
38
|
-
import "./chunk-
|
|
49
|
+
import "./chunk-6LKHXB6T.js";
|
|
50
|
+
import "./chunk-JLKKYUZA.js";
|
|
39
51
|
import "./chunk-AVIB7CT7.js";
|
|
40
52
|
import "./chunk-5MZ624VX.js";
|
|
41
53
|
import "./chunk-Q2CIRAOK.js";
|
|
42
|
-
import "./chunk-
|
|
54
|
+
import "./chunk-NNPEG6HM.js";
|
|
43
55
|
import {
|
|
44
56
|
beginEdgeTakeover,
|
|
45
57
|
collectProxyCerts,
|
|
@@ -52,39 +64,40 @@ import {
|
|
|
52
64
|
recoverInterruptedTakeover,
|
|
53
65
|
rollbackEdgeTakeover,
|
|
54
66
|
scanImportableSites
|
|
55
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-NXFIRK2Z.js";
|
|
56
68
|
import "./chunk-O6ZZF52V.js";
|
|
57
69
|
import "./chunk-JYGD5WSN.js";
|
|
58
|
-
import "./chunk-
|
|
59
|
-
import "./chunk-
|
|
60
|
-
import "./chunk-
|
|
61
|
-
import "./chunk-
|
|
62
|
-
import "./chunk-
|
|
63
|
-
import "./chunk-
|
|
64
|
-
import "./chunk-
|
|
65
|
-
import "./chunk-
|
|
70
|
+
import "./chunk-CUJXGK7R.js";
|
|
71
|
+
import "./chunk-V7BZMGOT.js";
|
|
72
|
+
import "./chunk-YWMODKWL.js";
|
|
73
|
+
import "./chunk-KNTJ7PSF.js";
|
|
74
|
+
import "./chunk-KZOBYJUW.js";
|
|
75
|
+
import "./chunk-I6PSTQIR.js";
|
|
76
|
+
import "./chunk-K2RPVGPN.js";
|
|
77
|
+
import "./chunk-ZCOQJCZO.js";
|
|
66
78
|
import "./chunk-RPHRPFEH.js";
|
|
67
79
|
import {
|
|
68
80
|
LocalExecutor
|
|
69
81
|
} from "./chunk-TYVME3XD.js";
|
|
70
|
-
import "./chunk-
|
|
82
|
+
import "./chunk-RB35YN3J.js";
|
|
71
83
|
import "./chunk-FPRHYBY2.js";
|
|
72
84
|
import {
|
|
73
85
|
edgeCrashReason,
|
|
74
86
|
edgeIsBroken,
|
|
75
87
|
ourEdgeContainerRunning
|
|
76
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-6PIYRG5I.js";
|
|
77
89
|
import "./chunk-ILFETZOM.js";
|
|
78
|
-
import "./chunk-
|
|
90
|
+
import "./chunk-EAAGSQXN.js";
|
|
79
91
|
import {
|
|
80
92
|
CLOUD_DASHBOARD_URL,
|
|
81
93
|
LOCAL_API_URL,
|
|
82
94
|
LOCAL_DASHBOARD_URL,
|
|
83
95
|
cliInstallCommand,
|
|
96
|
+
commandToArgv,
|
|
84
97
|
isValidEmail,
|
|
85
98
|
parseOpenshipConfigJson,
|
|
86
99
|
resolveCliUpdatePlan
|
|
87
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-UMQP7SYT.js";
|
|
88
101
|
import "./chunk-KI2EY3WB.js";
|
|
89
102
|
|
|
90
103
|
// src/index.ts
|
|
@@ -487,8 +500,8 @@ import chalk6 from "chalk";
|
|
|
487
500
|
import ora from "ora";
|
|
488
501
|
import { spawn } from "child_process";
|
|
489
502
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
490
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
491
|
-
import { dirname as dirname2, join as
|
|
503
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
504
|
+
import { dirname as dirname2, join as join7 } from "path";
|
|
492
505
|
import { fileURLToPath } from "url";
|
|
493
506
|
|
|
494
507
|
// src/lib/dashboard.ts
|
|
@@ -658,7 +671,7 @@ async function fetchBundle(tag, onProgress) {
|
|
|
658
671
|
|
|
659
672
|
// src/lib/service.ts
|
|
660
673
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
661
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2
|
|
674
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
|
|
662
675
|
import { homedir } from "os";
|
|
663
676
|
import { join as join4, resolve } from "path";
|
|
664
677
|
var HOME = homedir();
|
|
@@ -876,14 +889,9 @@ function serviceStatus() {
|
|
|
876
889
|
}
|
|
877
890
|
function sweepOrphanPorts() {
|
|
878
891
|
if (process.platform === "win32") return;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const p = JSON.parse(raw);
|
|
883
|
-
ports = [p.api, p.dashboard].filter((n) => typeof n === "number");
|
|
884
|
-
} catch {
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
892
|
+
const stored = readStoredPorts();
|
|
893
|
+
const ports = [stored.api, stored.dashboard].filter((n) => typeof n === "number");
|
|
894
|
+
if (ports.length === 0) return;
|
|
887
895
|
const self = process.pid;
|
|
888
896
|
for (const port of ports) {
|
|
889
897
|
const q = run("lsof", ["-ti", `tcp:${port}`]);
|
|
@@ -924,97 +932,10 @@ function stop() {
|
|
|
924
932
|
return result;
|
|
925
933
|
}
|
|
926
934
|
|
|
927
|
-
// src/lib/ports.ts
|
|
928
|
-
import { createServer } from "net";
|
|
929
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
930
|
-
import { join as join5 } from "path";
|
|
931
|
-
var PORTS_FILE = join5(OS_DIR, "ports.json");
|
|
932
|
-
var INSTANCE_FILE = join5(OS_DIR, "instance.json");
|
|
933
|
-
function saveInstanceUrl(publicUrl) {
|
|
934
|
-
try {
|
|
935
|
-
if (!existsSync4(OS_DIR)) mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
936
|
-
writeFileSync4(INSTANCE_FILE, JSON.stringify({ publicUrl: publicUrl ?? null }));
|
|
937
|
-
} catch {
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
function readInstanceUrl() {
|
|
941
|
-
try {
|
|
942
|
-
return JSON.parse(readFileSync3(INSTANCE_FILE, "utf8")).publicUrl ?? null;
|
|
943
|
-
} catch {
|
|
944
|
-
return null;
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
var DEFAULT_API = 4e3;
|
|
948
|
-
var DEFAULT_DASHBOARD = 3001;
|
|
949
|
-
function isPortFree(port) {
|
|
950
|
-
return new Promise((resolve2) => {
|
|
951
|
-
const srv = createServer();
|
|
952
|
-
srv.once("error", () => resolve2(false));
|
|
953
|
-
srv.listen(port, "127.0.0.1", () => srv.close(() => resolve2(true)));
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
async function waitPortFree(port, opts = {}) {
|
|
957
|
-
const timeoutMs = opts.timeoutMs ?? 6e3;
|
|
958
|
-
const intervalMs = opts.intervalMs ?? 250;
|
|
959
|
-
const deadline = Date.now() + timeoutMs;
|
|
960
|
-
for (; ; ) {
|
|
961
|
-
if (await isPortFree(port)) return true;
|
|
962
|
-
if (Date.now() >= deadline) return false;
|
|
963
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
function getFreePort() {
|
|
967
|
-
return new Promise((resolve2, reject2) => {
|
|
968
|
-
const srv = createServer();
|
|
969
|
-
srv.once("error", reject2);
|
|
970
|
-
srv.listen(0, "127.0.0.1", () => {
|
|
971
|
-
const addr = srv.address();
|
|
972
|
-
const port = addr && typeof addr === "object" ? addr.port : 0;
|
|
973
|
-
srv.close(() => port ? resolve2(port) : reject2(new Error("no free port")));
|
|
974
|
-
});
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
function loadStoredPorts() {
|
|
978
|
-
try {
|
|
979
|
-
return JSON.parse(readFileSync3(PORTS_FILE, "utf-8"));
|
|
980
|
-
} catch {
|
|
981
|
-
return {};
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
function saveStoredPorts(api, dashboard) {
|
|
985
|
-
try {
|
|
986
|
-
if (!existsSync4(OS_DIR)) mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
987
|
-
writeFileSync4(PORTS_FILE, JSON.stringify({ api, dashboard }));
|
|
988
|
-
} catch {
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
async function resolvePorts(prefs) {
|
|
992
|
-
const stored = loadStoredPorts();
|
|
993
|
-
const apiPref = prefs.api ?? stored.api ?? DEFAULT_API;
|
|
994
|
-
const dashPref = prefs.dashboard ?? stored.dashboard ?? DEFAULT_DASHBOARD;
|
|
995
|
-
const apiRemembered = prefs.api === void 0 && stored.api === apiPref;
|
|
996
|
-
const dashRemembered = prefs.dashboard === void 0 && stored.dashboard === dashPref;
|
|
997
|
-
let api;
|
|
998
|
-
if (await isPortFree(apiPref)) api = apiPref;
|
|
999
|
-
else if (apiRemembered && await waitPortFree(apiPref)) api = apiPref;
|
|
1000
|
-
else api = await getFreePort();
|
|
1001
|
-
let dashboard;
|
|
1002
|
-
if (dashPref !== api && await isPortFree(dashPref)) dashboard = dashPref;
|
|
1003
|
-
else if (dashPref !== api && dashRemembered && await waitPortFree(dashPref)) dashboard = dashPref;
|
|
1004
|
-
else dashboard = await getFreePort();
|
|
1005
|
-
if (dashboard === api) dashboard = await getFreePort();
|
|
1006
|
-
saveStoredPorts(api, dashboard);
|
|
1007
|
-
return {
|
|
1008
|
-
api,
|
|
1009
|
-
dashboard,
|
|
1010
|
-
switched: { api: api !== apiPref, dashboard: dashboard !== dashPref }
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
935
|
// src/lib/edge-preflight.ts
|
|
1015
936
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1016
|
-
import { existsSync as
|
|
1017
|
-
import { join as
|
|
937
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
938
|
+
import { join as join5 } from "path";
|
|
1018
939
|
import chalk5 from "chalk";
|
|
1019
940
|
import { isCancel, log, note, select } from "@clack/prompts";
|
|
1020
941
|
async function planAndApplyHostEdge(opts, overrides = {}) {
|
|
@@ -1053,7 +974,7 @@ async function rollbackHostEdge() {
|
|
|
1053
974
|
return false;
|
|
1054
975
|
}
|
|
1055
976
|
}
|
|
1056
|
-
var IMPORTED_MARKER =
|
|
977
|
+
var IMPORTED_MARKER = join5(OS_DIR, "imported-proxy");
|
|
1057
978
|
function edgeServedHostnames() {
|
|
1058
979
|
const served = /* @__PURE__ */ new Set();
|
|
1059
980
|
const r = spawnSync3(
|
|
@@ -1077,13 +998,13 @@ function edgeServedHostnames() {
|
|
|
1077
998
|
}
|
|
1078
999
|
function markStoppedProxyImported() {
|
|
1079
1000
|
try {
|
|
1080
|
-
|
|
1081
|
-
|
|
1001
|
+
mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
1002
|
+
writeFileSync4(IMPORTED_MARKER, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
|
|
1082
1003
|
} catch {
|
|
1083
1004
|
}
|
|
1084
1005
|
}
|
|
1085
1006
|
async function offerStoppedProxyImport(executor, deps) {
|
|
1086
|
-
if (!deps.interactive ||
|
|
1007
|
+
if (!deps.interactive || existsSync4(IMPORTED_MARKER)) return { proceed: true };
|
|
1087
1008
|
let proxy = null;
|
|
1088
1009
|
try {
|
|
1089
1010
|
proxy = await deps.detectInstalledProxy(executor);
|
|
@@ -1228,15 +1149,15 @@ function defaultDeps() {
|
|
|
1228
1149
|
}
|
|
1229
1150
|
|
|
1230
1151
|
// src/lib/loopback-api.ts
|
|
1231
|
-
import { existsSync as
|
|
1152
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1232
1153
|
import { randomBytes } from "crypto";
|
|
1233
|
-
import { join as
|
|
1154
|
+
import { join as join6 } from "path";
|
|
1234
1155
|
function ensureInternalToken() {
|
|
1235
|
-
const path2 =
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1156
|
+
const path2 = join6(OS_DIR, "internal-token");
|
|
1157
|
+
if (existsSync5(path2)) return readFileSync3(path2, "utf8").trim();
|
|
1158
|
+
mkdirSync6(OS_DIR, { recursive: true, mode: 448 });
|
|
1238
1159
|
const token = randomBytes(32).toString("hex");
|
|
1239
|
-
|
|
1160
|
+
writeFileSync5(path2, token, { mode: 384 });
|
|
1240
1161
|
return token;
|
|
1241
1162
|
}
|
|
1242
1163
|
async function internalGet(port, path2, token) {
|
|
@@ -1265,17 +1186,17 @@ async function internalPost(port, path2, body, token) {
|
|
|
1265
1186
|
return { ok: false, data: { error: err2.message } };
|
|
1266
1187
|
}
|
|
1267
1188
|
}
|
|
1268
|
-
async function bootstrapAdmin(
|
|
1269
|
-
const { ok: ok3, data } = await internalPost(
|
|
1189
|
+
async function bootstrapAdmin(apiPort, admin, token) {
|
|
1190
|
+
const { ok: ok3, data } = await internalPost(apiPort, "/api/system/bootstrap-admin", admin, token);
|
|
1270
1191
|
if (ok3) return { ok: true };
|
|
1271
1192
|
if (data?.error === "An admin account already exists") return { ok: true, message: "already-exists" };
|
|
1272
1193
|
return { ok: false, message: data?.error || "failed" };
|
|
1273
1194
|
}
|
|
1274
|
-
async function waitHealthy(
|
|
1195
|
+
async function waitHealthy(apiPort, seconds = 90) {
|
|
1275
1196
|
for (let i = 0; i < seconds; i++) {
|
|
1276
1197
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
1277
1198
|
try {
|
|
1278
|
-
await fetch(`http://127.0.0.1:${
|
|
1199
|
+
await fetch(`http://127.0.0.1:${apiPort}/api/health`, { signal: AbortSignal.timeout(2e3) });
|
|
1279
1200
|
return true;
|
|
1280
1201
|
} catch {
|
|
1281
1202
|
}
|
|
@@ -1556,16 +1477,16 @@ function normalizePublicUrl(raw) {
|
|
|
1556
1477
|
return url;
|
|
1557
1478
|
}
|
|
1558
1479
|
var DIST_DIR = dirname2(fileURLToPath(import.meta.url));
|
|
1559
|
-
var SERVER_DIR =
|
|
1480
|
+
var SERVER_DIR = join7(DIST_DIR, "server");
|
|
1560
1481
|
function ensureAuthSecret() {
|
|
1561
|
-
const path2 =
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
1482
|
+
const path2 = join7(OS_DIR, "auth-secret");
|
|
1483
|
+
if (existsSync6(path2)) return readFileSync4(path2, "utf8").trim();
|
|
1484
|
+
mkdirSync7(OS_DIR, { recursive: true, mode: 448 });
|
|
1564
1485
|
const secret = randomBytes2(32).toString("hex");
|
|
1565
|
-
|
|
1486
|
+
writeFileSync6(path2, secret, { mode: 384 });
|
|
1566
1487
|
return secret;
|
|
1567
1488
|
}
|
|
1568
|
-
var upCommand = new Command4("up").description("Start Openship as a persistent service (boot + auto-restart); --foreground to run attached").option("--port <port>", "API port to listen on
|
|
1489
|
+
var upCommand = new Command4("up").description("Start Openship as a persistent service (boot + auto-restart); --foreground to run attached").option("--port <port>", "API port to listen on (default: 4000, or the next free port if it's taken)").option("--data-dir <dir>", "Directory for the embedded database").option("--dashboard-port <port>", "Dashboard port (default: 3001, or the next free port if it's taken)").option("--no-ui", "Run the API only \u2014 don't download/serve the dashboard").option("--ui-version <tag>", "Dashboard release tag to run (default: this CLI's version)").option("-f, --foreground", "Run attached in this terminal instead of as a background service").option("--dry-run", "Print the service definition that would be installed, then exit").option(
|
|
1569
1490
|
"--public-url <url>",
|
|
1570
1491
|
"Serve remotely at this public URL (VPS): binds the dashboard to all interfaces, proxies the API same-origin, and requires login"
|
|
1571
1492
|
).option(
|
|
@@ -1685,12 +1606,19 @@ async function runCompose(opts) {
|
|
|
1685
1606
|
process.exit(1);
|
|
1686
1607
|
}
|
|
1687
1608
|
const publicUrl = effectivePublicUrl(opts.publicUrl);
|
|
1609
|
+
const ports = await resolveComposePorts({ api: opts.port, dashboard: opts.dashboardPort });
|
|
1610
|
+
const apiPort = String(ports.api);
|
|
1611
|
+
const dashboardPort = String(ports.dashboard);
|
|
1612
|
+
const portNotice = portMoveNotice(ports, [...composeTrustedOriginUrls(), publicUrl]);
|
|
1613
|
+
if (portNotice.length) {
|
|
1614
|
+
console.log("\n" + portNotice.map((l) => chalk6.yellow(` ${l}`)).join("\n"));
|
|
1615
|
+
}
|
|
1688
1616
|
const prefetch = ora(
|
|
1689
1617
|
sourceBuildDir() ? "Building images before touching :80/:443\u2026" : "Pulling images before touching :80/:443\u2026"
|
|
1690
1618
|
).start();
|
|
1691
1619
|
const fetched = composePrefetch({
|
|
1692
|
-
apiPort
|
|
1693
|
-
dashboardPort
|
|
1620
|
+
apiPort,
|
|
1621
|
+
dashboardPort,
|
|
1694
1622
|
publicUrl,
|
|
1695
1623
|
trustProxy: opts.trustProxy,
|
|
1696
1624
|
noHostControl: opts.hostControl === false ? true : void 0
|
|
@@ -1727,8 +1655,8 @@ async function runCompose(opts) {
|
|
|
1727
1655
|
const res = await composeUp({
|
|
1728
1656
|
// Prefetched above, before the preflight stopped anything.
|
|
1729
1657
|
alreadyFetched: true,
|
|
1730
|
-
apiPort
|
|
1731
|
-
dashboardPort
|
|
1658
|
+
apiPort,
|
|
1659
|
+
dashboardPort,
|
|
1732
1660
|
publicUrl,
|
|
1733
1661
|
trustProxy: opts.trustProxy,
|
|
1734
1662
|
// commander maps `--no-host-control` to hostControl === false. `undefined` when
|
|
@@ -1825,8 +1753,8 @@ async function startService(opts, runOpts = {}) {
|
|
|
1825
1753
|
`) + p.content + "\n"
|
|
1826
1754
|
);
|
|
1827
1755
|
return {
|
|
1828
|
-
port: String(opts.port ||
|
|
1829
|
-
dashPort: String(opts.dashboardPort ||
|
|
1756
|
+
port: String(opts.port || DEFAULT_API_PORT),
|
|
1757
|
+
dashPort: String(opts.dashboardPort || DEFAULT_DASHBOARD_PORT),
|
|
1830
1758
|
publicUrl
|
|
1831
1759
|
};
|
|
1832
1760
|
}
|
|
@@ -1852,11 +1780,9 @@ async function startService(opts, runOpts = {}) {
|
|
|
1852
1780
|
try {
|
|
1853
1781
|
const res = installAndStart(flags);
|
|
1854
1782
|
if (!runOpts.quiet) {
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
A preferred port was busy \u2014 using API ${port}, dashboard ${dashPort}.`)
|
|
1859
|
-
);
|
|
1783
|
+
const portNotice = portMoveNotice(resolved, [publicUrl]);
|
|
1784
|
+
if (portNotice.length) {
|
|
1785
|
+
console.log("\n" + portNotice.map((l) => chalk6.yellow(` ${l}`)).join("\n"));
|
|
1860
1786
|
}
|
|
1861
1787
|
const dashboardLine = publicUrl ? chalk6.dim(` Dashboard: ${publicUrl} (login required)
|
|
1862
1788
|
`) : chalk6.dim(` Dashboard: http://localhost:${dashPort} (login required)
|
|
@@ -1887,8 +1813,8 @@ async function runForeground(opts, source) {
|
|
|
1887
1813
|
apiArgs = ["run", "src/index.ts"];
|
|
1888
1814
|
apiCwd = source.apiDir;
|
|
1889
1815
|
} else {
|
|
1890
|
-
const serverEntry =
|
|
1891
|
-
if (!
|
|
1816
|
+
const serverEntry = join7(SERVER_DIR, "index.js");
|
|
1817
|
+
if (!existsSync6(serverEntry)) {
|
|
1892
1818
|
console.error(
|
|
1893
1819
|
chalk6.red("\n Bundled server not found in this install.") + chalk6.dim("\n Reinstall with `openship update` (or `npm i -g openship`).\n")
|
|
1894
1820
|
);
|
|
@@ -1904,28 +1830,42 @@ async function runForeground(opts, source) {
|
|
|
1904
1830
|
const dashPort = String(resolved.dashboard);
|
|
1905
1831
|
const publicUrl = effectivePublicUrl(opts.publicUrl);
|
|
1906
1832
|
const managedEdge = Boolean(opts.managedEdge && publicUrl);
|
|
1907
|
-
const dataDir = opts.dataDir ||
|
|
1908
|
-
|
|
1909
|
-
const logDir =
|
|
1910
|
-
|
|
1911
|
-
const instanceLogPath =
|
|
1833
|
+
const dataDir = opts.dataDir || join7(OS_DIR, "data");
|
|
1834
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
1835
|
+
const logDir = join7(OS_DIR, "logs");
|
|
1836
|
+
mkdirSync7(logDir, { recursive: true });
|
|
1837
|
+
const instanceLogPath = join7(logDir, "instance.log");
|
|
1912
1838
|
const instanceLog = createWriteStream2(instanceLogPath, { flags: "w" });
|
|
1913
1839
|
const env = {
|
|
1914
1840
|
...process.env,
|
|
1915
1841
|
PORT: port,
|
|
1916
1842
|
NODE_ENV: "production",
|
|
1917
|
-
// desktop
|
|
1918
|
-
|
|
1843
|
+
// BARE, not "desktop". This box is a server-host: openship runs ON it and it
|
|
1844
|
+
// is itself a deploy target. Claiming "desktop" (once done here just to get
|
|
1845
|
+
// the in-process job runner) made the API report `isServerHost: false`
|
|
1846
|
+
// (health.routes.ts), so `registerSelfServerReconcile` — gated on
|
|
1847
|
+
// modes:["selfhosted"] — never ran, "This Server" was never registered, and
|
|
1848
|
+
// the deploy wizard offered Openship Cloud as the only target on a box the
|
|
1849
|
+
// operator had just installed on purpose.
|
|
1850
|
+
//
|
|
1851
|
+
// It also mislabelled the security posture: DEPLOY_MODE=desktop relaxes the
|
|
1852
|
+
// zero-auth gate and makes INTERNAL_TOKEN optional. "desktop" belongs to
|
|
1853
|
+
// Electron alone (apps/desktop/src/main/services.ts:361) — the only launcher
|
|
1854
|
+
// that is genuinely a single-user loopback app.
|
|
1855
|
+
//
|
|
1856
|
+
// The job runner is unaffected: it picks BullMQ vs in-process by REDIS
|
|
1857
|
+
// REACHABILITY (app.ts:238), and OPENSHIP_JOB_RUNNER below already pins it.
|
|
1858
|
+
DEPLOY_MODE: "bare",
|
|
1919
1859
|
OPENSHIP_TARGET: "local",
|
|
1920
1860
|
OPENSHIP_JOB_RUNNER: "in-process",
|
|
1921
1861
|
PGLITE_DATA_DIR: dataDir,
|
|
1922
1862
|
BETTER_AUTH_SECRET: ensureAuthSecret()
|
|
1923
1863
|
};
|
|
1924
1864
|
if (!source) {
|
|
1925
|
-
env.OPENSHIP_MIGRATIONS_DIR =
|
|
1926
|
-
env.OPENSHIP_PGLITE_ASSETS_DIR =
|
|
1927
|
-
const engineDir =
|
|
1928
|
-
if (
|
|
1865
|
+
env.OPENSHIP_MIGRATIONS_DIR = join7(SERVER_DIR, "migrations");
|
|
1866
|
+
env.OPENSHIP_PGLITE_ASSETS_DIR = join7(SERVER_DIR, "pglite");
|
|
1867
|
+
const engineDir = join7(SERVER_DIR, "engine");
|
|
1868
|
+
if (existsSync6(engineDir)) env.MAIL_SERVER_ENGINE_DIR = engineDir;
|
|
1929
1869
|
}
|
|
1930
1870
|
env.OPENSHIP_REQUIRE_AUTH = "true";
|
|
1931
1871
|
env.INTERNAL_TOKEN = ensureInternalToken();
|
|
@@ -2017,7 +1957,7 @@ async function runForeground(opts, source) {
|
|
|
2017
1957
|
const uiSpinner = ora("Preparing the dashboard\u2026").start();
|
|
2018
1958
|
try {
|
|
2019
1959
|
const bundle = await ensureDashboard({
|
|
2020
|
-
tag: source ? "local" : opts.uiVersion || `v${"0.
|
|
1960
|
+
tag: source ? "local" : opts.uiVersion || `v${"0.5.0"}`,
|
|
2021
1961
|
onProgress: (received, total) => {
|
|
2022
1962
|
if (total) {
|
|
2023
1963
|
uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
|
|
@@ -2147,7 +2087,7 @@ var stopCommand = new Command5("stop").description("Stop Openship (started by `o
|
|
|
2147
2087
|
import { Command as Command6 } from "commander";
|
|
2148
2088
|
import chalk8 from "chalk";
|
|
2149
2089
|
import { confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
2150
|
-
import { existsSync as
|
|
2090
|
+
import { existsSync as existsSync7, rmSync as rmSync3 } from "fs";
|
|
2151
2091
|
var uninstallCommand = new Command6("uninstall").description(
|
|
2152
2092
|
"Remove Openship from this machine: stop it, delete its data (database, certs, edge config), its images, and ~/.openship. Deployed apps are left running."
|
|
2153
2093
|
).option("-y, --yes", "Skip the confirmation prompt (for scripts)").option("--keep-data", "Keep the database/certs volumes and ~/.openship \u2014 remove only the running stack").option("--keep-images", "Don't delete the Openship container images").action(async (opts) => {
|
|
@@ -2193,7 +2133,7 @@ var uninstallCommand = new Command6("uninstall").description(
|
|
|
2193
2133
|
console.log(chalk8.yellow(` Couldn't remove the service: ${err2.message}`));
|
|
2194
2134
|
}
|
|
2195
2135
|
}
|
|
2196
|
-
if (destructive &&
|
|
2136
|
+
if (destructive && existsSync7(OS_DIR)) {
|
|
2197
2137
|
try {
|
|
2198
2138
|
rmSync3(OS_DIR, { recursive: true, force: true });
|
|
2199
2139
|
console.log(chalk8.green(` Removed ${OS_DIR}`));
|
|
@@ -2212,15 +2152,15 @@ var uninstallCommand = new Command6("uninstall").description(
|
|
|
2212
2152
|
|
|
2213
2153
|
// src/commands/init.ts
|
|
2214
2154
|
import { Command as Command7 } from "commander";
|
|
2215
|
-
import { existsSync as
|
|
2155
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
2216
2156
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2217
2157
|
import { stdin as input2, stdout as output2 } from "process";
|
|
2218
|
-
import { join as
|
|
2158
|
+
import { join as join8 } from "path";
|
|
2219
2159
|
var initCommand = new Command7("init").description("Link the current directory to an Openship project (.openship/project.json)").option("--project <id>", "Project id to link (skips the picker)").option("--environment <name>", "Default deploy environment", "production").option("--dir <path>", "Directory to initialize", process.cwd()).option("--force", "Overwrite an existing project link").option("-y, --yes", "Non-interactive: fail instead of prompting").action(async (opts) => {
|
|
2220
2160
|
const root = opts.dir || process.cwd();
|
|
2221
|
-
const linkDir =
|
|
2222
|
-
const linkPath =
|
|
2223
|
-
if (
|
|
2161
|
+
const linkDir = join8(root, ".openship");
|
|
2162
|
+
const linkPath = join8(linkDir, "project.json");
|
|
2163
|
+
if (existsSync8(linkPath) && !opts.force) {
|
|
2224
2164
|
err(`Already linked (${linkPath}). Re-run with --force to overwrite.`);
|
|
2225
2165
|
process.exit(1);
|
|
2226
2166
|
}
|
|
@@ -2273,8 +2213,8 @@ var initCommand = new Command7("init").description("Link the current directory t
|
|
|
2273
2213
|
context: getActiveContext(),
|
|
2274
2214
|
defaults: { environment: opts.environment || "production" }
|
|
2275
2215
|
};
|
|
2276
|
-
|
|
2277
|
-
|
|
2216
|
+
mkdirSync8(linkDir, { recursive: true });
|
|
2217
|
+
writeFileSync7(linkPath, JSON.stringify(link, null, 2) + "\n");
|
|
2278
2218
|
if (isJsonMode()) {
|
|
2279
2219
|
printJson({ path: linkPath, link });
|
|
2280
2220
|
return;
|
|
@@ -2286,8 +2226,8 @@ var initCommand = new Command7("init").description("Link the current directory t
|
|
|
2286
2226
|
|
|
2287
2227
|
// src/commands/config.ts
|
|
2288
2228
|
import { Command as Command8 } from "commander";
|
|
2289
|
-
import { existsSync as
|
|
2290
|
-
import { join as
|
|
2229
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
2230
|
+
import { join as join9 } from "path";
|
|
2291
2231
|
var SCHEMA_URL = "https://openship.io/openship.schema.json";
|
|
2292
2232
|
var CONFIG_FILE = "openship.json";
|
|
2293
2233
|
function detectHints(dir) {
|
|
@@ -2299,15 +2239,15 @@ function detectHints(dir) {
|
|
|
2299
2239
|
["package-lock.json", "npm"]
|
|
2300
2240
|
];
|
|
2301
2241
|
for (const [file, pm] of lock) {
|
|
2302
|
-
if (
|
|
2242
|
+
if (existsSync9(join9(dir, file))) {
|
|
2303
2243
|
hints.packageManager = pm;
|
|
2304
2244
|
break;
|
|
2305
2245
|
}
|
|
2306
2246
|
}
|
|
2307
|
-
const pkgPath =
|
|
2308
|
-
if (
|
|
2247
|
+
const pkgPath = join9(dir, "package.json");
|
|
2248
|
+
if (existsSync9(pkgPath)) {
|
|
2309
2249
|
try {
|
|
2310
|
-
const pkg = JSON.parse(
|
|
2250
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
2311
2251
|
const runner = hints.packageManager ?? "npm";
|
|
2312
2252
|
const exec = runner === "npm" ? "npm run" : runner;
|
|
2313
2253
|
if (pkg.scripts?.build) hints.buildCommand = `${exec} build`;
|
|
@@ -2319,15 +2259,15 @@ function detectHints(dir) {
|
|
|
2319
2259
|
}
|
|
2320
2260
|
var initCmd = new Command8("init").description("Scaffold an openship.json in the current directory").option("--dir <path>", "Directory to write into", process.cwd()).option("--force", "Overwrite an existing openship.json").action((opts) => {
|
|
2321
2261
|
const dir = opts.dir || process.cwd();
|
|
2322
|
-
const path2 =
|
|
2323
|
-
if (
|
|
2262
|
+
const path2 = join9(dir, CONFIG_FILE);
|
|
2263
|
+
if (existsSync9(path2) && !opts.force) {
|
|
2324
2264
|
err(`${CONFIG_FILE} already exists. Re-run with --force to overwrite.`);
|
|
2325
2265
|
process.exit(1);
|
|
2326
2266
|
}
|
|
2327
2267
|
const hints = detectHints(dir);
|
|
2328
2268
|
const scaffold = { $schema: SCHEMA_URL, ...hints };
|
|
2329
2269
|
const text2 = JSON.stringify(scaffold, null, 2) + "\n";
|
|
2330
|
-
|
|
2270
|
+
writeFileSync8(path2, text2);
|
|
2331
2271
|
if (isJsonMode()) {
|
|
2332
2272
|
printJson({ path: path2, config: scaffold });
|
|
2333
2273
|
return;
|
|
@@ -2337,13 +2277,13 @@ var initCmd = new Command8("init").description("Scaffold an openship.json in the
|
|
|
2337
2277
|
info(" Edit it to declare framework, env, domains, resources, services\u2026 then `openship config validate`.\n");
|
|
2338
2278
|
});
|
|
2339
2279
|
var validateCmd = new Command8("validate").description("Validate an openship.json against the deploy schema").argument("[file]", "Path to the config file", CONFIG_FILE).action((file) => {
|
|
2340
|
-
const path2 = file === CONFIG_FILE ?
|
|
2341
|
-
if (!
|
|
2280
|
+
const path2 = file === CONFIG_FILE ? join9(process.cwd(), CONFIG_FILE) : file;
|
|
2281
|
+
if (!existsSync9(path2)) {
|
|
2342
2282
|
if (isJsonMode()) printJson({ valid: false, errors: [`${path2} not found`], warnings: [] });
|
|
2343
2283
|
else err(`Not found: ${path2}`);
|
|
2344
2284
|
process.exit(1);
|
|
2345
2285
|
}
|
|
2346
|
-
const { config, errors, warnings } = parseOpenshipConfigJson(
|
|
2286
|
+
const { config, errors, warnings } = parseOpenshipConfigJson(readFileSync5(path2, "utf8"));
|
|
2347
2287
|
const valid = errors.length === 0 && config !== null;
|
|
2348
2288
|
if (isJsonMode()) {
|
|
2349
2289
|
printJson({ valid, errors, warnings });
|
|
@@ -2416,15 +2356,6 @@ var contextCommand = new Command9("context").alias("ctx").description("Manage co
|
|
|
2416
2356
|
// src/commands/status.ts
|
|
2417
2357
|
import { Command as Command10 } from "commander";
|
|
2418
2358
|
import chalk9 from "chalk";
|
|
2419
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
2420
|
-
import { join as join11 } from "path";
|
|
2421
|
-
function readPorts() {
|
|
2422
|
-
try {
|
|
2423
|
-
return JSON.parse(readFileSync7(join11(OS_DIR, "ports.json"), "utf8"));
|
|
2424
|
-
} catch {
|
|
2425
|
-
return {};
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
2359
|
var statusCommand = new Command10("status").description("Show the local Openship service (installed/running, ports) and the active context's API health").action(async () => {
|
|
2429
2360
|
const context = getActiveContext();
|
|
2430
2361
|
const apiUrl = getApiUrl2();
|
|
@@ -2444,7 +2375,7 @@ var statusCommand = new Command10("status").description("Show the local Openship
|
|
|
2444
2375
|
return;
|
|
2445
2376
|
}
|
|
2446
2377
|
const svc = serviceStatus();
|
|
2447
|
-
const ports =
|
|
2378
|
+
const ports = readStoredPorts();
|
|
2448
2379
|
let health = null;
|
|
2449
2380
|
let envInfo = null;
|
|
2450
2381
|
let reachable = true;
|
|
@@ -2476,7 +2407,7 @@ var statusCommand = new Command10("status").description("Show the local Openship
|
|
|
2476
2407
|
|
|
2477
2408
|
// src/commands/doctor.ts
|
|
2478
2409
|
import { Command as Command11 } from "commander";
|
|
2479
|
-
import { existsSync as
|
|
2410
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2480
2411
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2481
2412
|
import chalk11 from "chalk";
|
|
2482
2413
|
import open from "open";
|
|
@@ -2484,42 +2415,42 @@ import { intro, outro, note as note2, select as select3, log as log3, spinner as
|
|
|
2484
2415
|
|
|
2485
2416
|
// src/lib/repair.ts
|
|
2486
2417
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2487
|
-
import { existsSync as
|
|
2488
|
-
import { join as
|
|
2418
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
|
|
2419
|
+
import { join as join11 } from "path";
|
|
2489
2420
|
import chalk10 from "chalk";
|
|
2490
2421
|
import { spinner, log as log2, select as select2, isCancel as isCancel3, cancel } from "@clack/prompts";
|
|
2491
2422
|
|
|
2492
2423
|
// src/lib/heal.ts
|
|
2493
2424
|
import {
|
|
2494
|
-
existsSync as
|
|
2425
|
+
existsSync as existsSync10,
|
|
2495
2426
|
readdirSync,
|
|
2496
2427
|
statSync,
|
|
2497
2428
|
unlinkSync,
|
|
2498
|
-
mkdirSync as
|
|
2429
|
+
mkdirSync as mkdirSync9,
|
|
2499
2430
|
copyFileSync,
|
|
2500
2431
|
renameSync
|
|
2501
2432
|
} from "fs";
|
|
2502
2433
|
import { homedir as homedir2 } from "os";
|
|
2503
|
-
import { join as
|
|
2434
|
+
import { join as join10 } from "path";
|
|
2504
2435
|
import { execFileSync } from "child_process";
|
|
2505
2436
|
function resolveDataDir() {
|
|
2506
2437
|
const fromEnv = process.env.PGLITE_DATA_DIR?.trim();
|
|
2507
2438
|
if (fromEnv) {
|
|
2508
|
-
return fromEnv.startsWith("~") ?
|
|
2439
|
+
return fromEnv.startsWith("~") ? join10(homedir2(), fromEnv.slice(1).replace(/^[/\\]/, "")) : fromEnv;
|
|
2509
2440
|
}
|
|
2510
|
-
return
|
|
2441
|
+
return join10(OS_DIR, "data");
|
|
2511
2442
|
}
|
|
2512
2443
|
function dataDirExists(dataDir = resolveDataDir()) {
|
|
2513
|
-
return
|
|
2444
|
+
return existsSync10(dataDir);
|
|
2514
2445
|
}
|
|
2515
2446
|
function stamp() {
|
|
2516
2447
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2517
2448
|
}
|
|
2518
2449
|
function copyDirRecursive(src, dest) {
|
|
2519
|
-
if (!
|
|
2450
|
+
if (!existsSync10(dest)) mkdirSync9(dest, { recursive: true });
|
|
2520
2451
|
for (const entry of readdirSync(src)) {
|
|
2521
|
-
const s =
|
|
2522
|
-
const d =
|
|
2452
|
+
const s = join10(src, entry);
|
|
2453
|
+
const d = join10(dest, entry);
|
|
2523
2454
|
if (statSync(s).isDirectory()) copyDirRecursive(s, d);
|
|
2524
2455
|
else copyFileSync(s, d);
|
|
2525
2456
|
}
|
|
@@ -2531,30 +2462,30 @@ function backupDataDir(dataDir = resolveDataDir()) {
|
|
|
2531
2462
|
}
|
|
2532
2463
|
function healDataDir(dataDir = resolveDataDir()) {
|
|
2533
2464
|
const result = { removedPostmasterPid: false, trimmedWalSegment: null };
|
|
2534
|
-
const pidPath =
|
|
2535
|
-
if (
|
|
2465
|
+
const pidPath = join10(dataDir, "postmaster.pid");
|
|
2466
|
+
if (existsSync10(pidPath)) {
|
|
2536
2467
|
unlinkSync(pidPath);
|
|
2537
2468
|
result.removedPostmasterPid = true;
|
|
2538
2469
|
}
|
|
2539
|
-
const walDir =
|
|
2540
|
-
if (
|
|
2541
|
-
const segments = readdirSync(walDir).filter((name) => /^[0-9A-F]{24}$/.test(name)).map((name) => ({ name, mtime: statSync(
|
|
2470
|
+
const walDir = join10(dataDir, "pg_wal");
|
|
2471
|
+
if (existsSync10(walDir)) {
|
|
2472
|
+
const segments = readdirSync(walDir).filter((name) => /^[0-9A-F]{24}$/.test(name)).map((name) => ({ name, mtime: statSync(join10(walDir, name)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
2542
2473
|
if (segments.length > 0) {
|
|
2543
2474
|
const newest = segments[0];
|
|
2544
|
-
unlinkSync(
|
|
2475
|
+
unlinkSync(join10(walDir, newest.name));
|
|
2545
2476
|
result.trimmedWalSegment = newest.name;
|
|
2546
2477
|
}
|
|
2547
2478
|
}
|
|
2548
2479
|
return result;
|
|
2549
2480
|
}
|
|
2550
2481
|
function restoreBackup(backupDir, dataDir = resolveDataDir()) {
|
|
2551
|
-
if (!
|
|
2552
|
-
if (
|
|
2482
|
+
if (!existsSync10(backupDir)) throw new Error(`Backup not found: ${backupDir}`);
|
|
2483
|
+
if (existsSync10(dataDir)) renameSync(dataDir, `${dataDir}.discarded-${stamp()}`);
|
|
2553
2484
|
copyDirRecursive(backupDir, dataDir);
|
|
2554
2485
|
}
|
|
2555
2486
|
function freshStart(dataDir = resolveDataDir()) {
|
|
2556
2487
|
const aside = `${dataDir}.corrupt-${stamp()}`;
|
|
2557
|
-
if (
|
|
2488
|
+
if (existsSync10(dataDir)) renameSync(dataDir, aside);
|
|
2558
2489
|
return aside;
|
|
2559
2490
|
}
|
|
2560
2491
|
function findResetwal() {
|
|
@@ -2566,7 +2497,7 @@ function findResetwal() {
|
|
|
2566
2497
|
"/usr/lib/postgresql/16/bin/pg_resetwal",
|
|
2567
2498
|
"/usr/bin/pg_resetwal"
|
|
2568
2499
|
];
|
|
2569
|
-
for (const c of candidates) if (
|
|
2500
|
+
for (const c of candidates) if (existsSync10(c)) return c;
|
|
2570
2501
|
try {
|
|
2571
2502
|
const found = execFileSync("bash", ["-lc", "command -v pg_resetwal"], {
|
|
2572
2503
|
encoding: "utf8",
|
|
@@ -2587,7 +2518,7 @@ function deepHeal(dataDir = resolveDataDir()) {
|
|
|
2587
2518
|
}
|
|
2588
2519
|
|
|
2589
2520
|
// src/lib/repair.ts
|
|
2590
|
-
var LOG_DIR2 =
|
|
2521
|
+
var LOG_DIR2 = join11(OS_DIR, "logs");
|
|
2591
2522
|
function ensure(value) {
|
|
2592
2523
|
if (isCancel3(value)) {
|
|
2593
2524
|
cancel("Cancelled.");
|
|
@@ -2595,23 +2526,9 @@ function ensure(value) {
|
|
|
2595
2526
|
}
|
|
2596
2527
|
return value;
|
|
2597
2528
|
}
|
|
2598
|
-
function storedPorts() {
|
|
2599
|
-
try {
|
|
2600
|
-
const p = join13(OS_DIR, "ports.json");
|
|
2601
|
-
return existsSync12(p) ? JSON.parse(readFileSync8(p, "utf8")) : {};
|
|
2602
|
-
} catch {
|
|
2603
|
-
return {};
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
function apiPort() {
|
|
2607
|
-
return storedPorts().api ?? 4e3;
|
|
2608
|
-
}
|
|
2609
|
-
function dashboardPort() {
|
|
2610
|
-
return storedPorts().dashboard ?? 3001;
|
|
2611
|
-
}
|
|
2612
2529
|
async function internalGet2(path2, timeoutMs = 8e3) {
|
|
2613
2530
|
try {
|
|
2614
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
2531
|
+
const res = await fetch(`http://127.0.0.1:${storedApiPort()}${path2}`, {
|
|
2615
2532
|
headers: { "X-Internal-Token": ensureInternalToken() },
|
|
2616
2533
|
signal: AbortSignal.timeout(timeoutMs)
|
|
2617
2534
|
});
|
|
@@ -2623,7 +2540,7 @@ async function internalGet2(path2, timeoutMs = 8e3) {
|
|
|
2623
2540
|
}
|
|
2624
2541
|
async function apiReachable() {
|
|
2625
2542
|
try {
|
|
2626
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
2543
|
+
const res = await fetch(`http://127.0.0.1:${storedApiPort()}/api/health`, {
|
|
2627
2544
|
signal: AbortSignal.timeout(2500)
|
|
2628
2545
|
});
|
|
2629
2546
|
return res.ok;
|
|
@@ -2647,10 +2564,10 @@ async function waitApiDown(seconds = 20) {
|
|
|
2647
2564
|
}
|
|
2648
2565
|
function lastServiceError() {
|
|
2649
2566
|
for (const name of ["up.err.log", "up.log", "instance.log"]) {
|
|
2650
|
-
const p =
|
|
2651
|
-
if (!
|
|
2567
|
+
const p = join11(LOG_DIR2, name);
|
|
2568
|
+
if (!existsSync11(p)) continue;
|
|
2652
2569
|
try {
|
|
2653
|
-
const lines =
|
|
2570
|
+
const lines = readFileSync6(p, "utf8").trim().split("\n");
|
|
2654
2571
|
const hit = [...lines].reverse().find((l) => /error|locked|aborted|malformed|corrupt|throw|cannot|EADDRINUSE/i.test(l));
|
|
2655
2572
|
if (hit) return hit.trim().slice(0, 240);
|
|
2656
2573
|
} catch {
|
|
@@ -2708,11 +2625,11 @@ async function componentChecks(apiUp) {
|
|
|
2708
2625
|
checks.push({
|
|
2709
2626
|
name: "API",
|
|
2710
2627
|
state: apiUp ? "pass" : "fail",
|
|
2711
|
-
detail: apiUp ? `reachable on :${
|
|
2628
|
+
detail: apiUp ? `reachable on :${storedApiPort()}` : `not answering on :${storedApiPort()}`
|
|
2712
2629
|
});
|
|
2713
2630
|
let dashUp = false;
|
|
2714
2631
|
try {
|
|
2715
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
2632
|
+
const res = await fetch(`http://127.0.0.1:${storedDashboardPort()}/`, {
|
|
2716
2633
|
redirect: "manual",
|
|
2717
2634
|
signal: AbortSignal.timeout(2500)
|
|
2718
2635
|
});
|
|
@@ -2722,7 +2639,7 @@ async function componentChecks(apiUp) {
|
|
|
2722
2639
|
checks.push({
|
|
2723
2640
|
name: "Dashboard",
|
|
2724
2641
|
state: dashUp ? "pass" : "warn",
|
|
2725
|
-
detail: dashUp ? `serving on :${
|
|
2642
|
+
detail: dashUp ? `serving on :${storedDashboardPort()}` : `not serving on :${storedDashboardPort()}`
|
|
2726
2643
|
});
|
|
2727
2644
|
const edge = dockerNameRunning("openship-edge");
|
|
2728
2645
|
checks.push(
|
|
@@ -2909,7 +2826,7 @@ function bunVersion() {
|
|
|
2909
2826
|
}
|
|
2910
2827
|
async function remotePreflight() {
|
|
2911
2828
|
const checks = [];
|
|
2912
|
-
const hasConfig =
|
|
2829
|
+
const hasConfig = existsSync12(CONFIG_PATH);
|
|
2913
2830
|
checks.push({ name: "config", status: hasConfig ? "pass" : "warn", detail: hasConfig ? CONFIG_PATH : `not found; run \`openship login\`` });
|
|
2914
2831
|
const context = getActiveContext();
|
|
2915
2832
|
const apiUrl = getApiUrl();
|
|
@@ -3047,7 +2964,7 @@ async function interactiveDoctor() {
|
|
|
3047
2964
|
}
|
|
3048
2965
|
if (action2 === "open") {
|
|
3049
2966
|
const url = readInstanceUrl();
|
|
3050
|
-
const target = url && !/^https?:\/\/localhost/i.test(url) ? url : `http://localhost:${
|
|
2967
|
+
const target = url && !/^https?:\/\/localhost/i.test(url) ? url : `http://localhost:${storedDashboardPort()}`;
|
|
3051
2968
|
await open(target).catch(() => {
|
|
3052
2969
|
});
|
|
3053
2970
|
log3.message(chalk11.dim(`Opening ${target}`));
|
|
@@ -3077,15 +2994,15 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
3077
2994
|
import ora2 from "ora";
|
|
3078
2995
|
|
|
3079
2996
|
// src/lib/project-link.ts
|
|
3080
|
-
import { readFileSync as
|
|
3081
|
-
import { join as
|
|
3082
|
-
var LINK_REL =
|
|
2997
|
+
import { readFileSync as readFileSync7, existsSync as existsSync13 } from "fs";
|
|
2998
|
+
import { join as join12, dirname as dirname3, parse } from "path";
|
|
2999
|
+
var LINK_REL = join12(".openship", "project.json");
|
|
3083
3000
|
function findProjectLinkPath(from = process.cwd()) {
|
|
3084
3001
|
let dir = from;
|
|
3085
3002
|
const root = parse(dir).root;
|
|
3086
3003
|
for (; ; ) {
|
|
3087
|
-
const candidate =
|
|
3088
|
-
if (
|
|
3004
|
+
const candidate = join12(dir, LINK_REL);
|
|
3005
|
+
if (existsSync13(candidate)) return candidate;
|
|
3089
3006
|
if (dir === root) return null;
|
|
3090
3007
|
dir = dirname3(dir);
|
|
3091
3008
|
}
|
|
@@ -3094,7 +3011,7 @@ function readProjectLink(from) {
|
|
|
3094
3011
|
const path2 = findProjectLinkPath(from);
|
|
3095
3012
|
if (!path2) return null;
|
|
3096
3013
|
try {
|
|
3097
|
-
return JSON.parse(
|
|
3014
|
+
return JSON.parse(readFileSync7(path2, "utf8"));
|
|
3098
3015
|
} catch {
|
|
3099
3016
|
return null;
|
|
3100
3017
|
}
|
|
@@ -3102,21 +3019,21 @@ function readProjectLink(from) {
|
|
|
3102
3019
|
|
|
3103
3020
|
// src/lib/folder-deploy.ts
|
|
3104
3021
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3105
|
-
import { readFileSync as
|
|
3022
|
+
import { readFileSync as readFileSync8, existsSync as existsSync14, rmSync as rmSync4 } from "fs";
|
|
3106
3023
|
import { tmpdir } from "os";
|
|
3107
|
-
import { join as
|
|
3024
|
+
import { join as join13, basename } from "path";
|
|
3108
3025
|
function detectPackageManager(dir) {
|
|
3109
|
-
if (
|
|
3110
|
-
if (
|
|
3111
|
-
if (
|
|
3112
|
-
if (
|
|
3026
|
+
if (existsSync14(join13(dir, "bun.lockb")) || existsSync14(join13(dir, "bun.lock"))) return "bun";
|
|
3027
|
+
if (existsSync14(join13(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
3028
|
+
if (existsSync14(join13(dir, "yarn.lock"))) return "yarn";
|
|
3029
|
+
if (existsSync14(join13(dir, "package.json"))) return "npm";
|
|
3113
3030
|
return void 0;
|
|
3114
3031
|
}
|
|
3115
3032
|
function detectStack(dir) {
|
|
3116
|
-
if (
|
|
3117
|
-
if (
|
|
3118
|
-
if (
|
|
3119
|
-
if (
|
|
3033
|
+
if (existsSync14(join13(dir, "go.mod"))) return "go";
|
|
3034
|
+
if (existsSync14(join13(dir, "Cargo.toml"))) return "rust";
|
|
3035
|
+
if (existsSync14(join13(dir, "requirements.txt")) || existsSync14(join13(dir, "pyproject.toml"))) return "python";
|
|
3036
|
+
if (existsSync14(join13(dir, "package.json"))) return "node";
|
|
3120
3037
|
return void 0;
|
|
3121
3038
|
}
|
|
3122
3039
|
async function deployFolder(opts) {
|
|
@@ -3133,7 +3050,7 @@ async function deployFolder(opts) {
|
|
|
3133
3050
|
throw new Error(session.error || "Failed to open upload session");
|
|
3134
3051
|
}
|
|
3135
3052
|
step("Packaging folder");
|
|
3136
|
-
const tarball =
|
|
3053
|
+
const tarball = join13(tmpdir(), `openship-upload-${session.sessionId}.tar.gz`);
|
|
3137
3054
|
execFileSync3(
|
|
3138
3055
|
"tar",
|
|
3139
3056
|
[
|
|
@@ -3152,7 +3069,7 @@ async function deployFolder(opts) {
|
|
|
3152
3069
|
);
|
|
3153
3070
|
step("Uploading source");
|
|
3154
3071
|
try {
|
|
3155
|
-
const body =
|
|
3072
|
+
const body = readFileSync8(tarball);
|
|
3156
3073
|
const up = session.upload;
|
|
3157
3074
|
const method = up.method || "POST";
|
|
3158
3075
|
const res = /^https?:\/\//i.test(up.url) ? await fetch(up.url, { method, headers: up.headers, body }) : await apiRaw(`/${up.url.replace(/^\/+/, "")}`, { method, headers: up.headers, body });
|
|
@@ -3606,12 +3523,19 @@ var sslStatus = new Command13("status").description("Check SSL certificate statu
|
|
|
3606
3523
|
printJson(res);
|
|
3607
3524
|
})
|
|
3608
3525
|
);
|
|
3609
|
-
var sslRenew = new Command13("renew").description("Renew (issue) an SSL certificate for a domain").argument("<domain>", "Domain to renew").option("--www", "Also
|
|
3526
|
+
var sslRenew = new Command13("renew").description("Renew (issue) an SSL certificate for a domain").argument("<domain>", "Domain to renew").option("--www", "Also renew the www subdomain (separately \u2014 its own certificate)").action(
|
|
3610
3527
|
run2(async (domain, opts) => {
|
|
3611
3528
|
const res = await apiRequest("/deployments/ssl/renew", {
|
|
3612
3529
|
method: "POST",
|
|
3613
3530
|
body: JSON.stringify({ domain, includeWww: opts.www === true })
|
|
3614
3531
|
});
|
|
3532
|
+
if (!isJsonMode() && res.results && res.results.length > 1) {
|
|
3533
|
+
for (const r of res.results) {
|
|
3534
|
+
if (r.success) ok(`${r.domain}: ${r.status}`);
|
|
3535
|
+
else err(`${r.domain}: ${r.message ?? r.status}`);
|
|
3536
|
+
}
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3615
3539
|
report(res, `SSL renewal requested for ${domain}`);
|
|
3616
3540
|
})
|
|
3617
3541
|
);
|
|
@@ -4391,8 +4315,10 @@ function mapComposeService(name, def, baseDir) {
|
|
|
4391
4315
|
const volumes = mapVolumes(d.volumes);
|
|
4392
4316
|
if (volumes.length) svc.volumes = volumes;
|
|
4393
4317
|
const command = d.command;
|
|
4394
|
-
if (
|
|
4395
|
-
|
|
4318
|
+
if (command != null) {
|
|
4319
|
+
svc.commandArgv = commandToArgv(command);
|
|
4320
|
+
svc.command = typeof command === "string" ? command : command.map(String).join(" ");
|
|
4321
|
+
}
|
|
4396
4322
|
if (typeof d.restart === "string") svc.restart = d.restart;
|
|
4397
4323
|
return svc;
|
|
4398
4324
|
}
|
|
@@ -5158,7 +5084,7 @@ var serverCommand = server;
|
|
|
5158
5084
|
// src/commands/system.ts
|
|
5159
5085
|
import { Command as Command19 } from "commander";
|
|
5160
5086
|
import ora5 from "ora";
|
|
5161
|
-
import { readFileSync as
|
|
5087
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
5162
5088
|
import { createInterface as createInterface6 } from "readline/promises";
|
|
5163
5089
|
import { stdin as input5, stdout as output5 } from "process";
|
|
5164
5090
|
async function guarded(fn) {
|
|
@@ -5458,7 +5384,7 @@ dataTransferCommand.command("export").description("Export the entire instance to
|
|
|
5458
5384
|
);
|
|
5459
5385
|
spin4?.succeed("Export ready.");
|
|
5460
5386
|
if (opts.out) {
|
|
5461
|
-
|
|
5387
|
+
writeFileSync9(opts.out, JSON.stringify(file));
|
|
5462
5388
|
const tables = Object.keys(file.dump?.tables ?? {}).length;
|
|
5463
5389
|
report2(
|
|
5464
5390
|
{ out: opts.out, tables },
|
|
@@ -5484,7 +5410,7 @@ dataTransferCommand.command("import").description("Import an instance export fil
|
|
|
5484
5410
|
}
|
|
5485
5411
|
let file;
|
|
5486
5412
|
try {
|
|
5487
|
-
file = JSON.parse(
|
|
5413
|
+
file = JSON.parse(readFileSync9(opts.file, "utf8"));
|
|
5488
5414
|
} catch {
|
|
5489
5415
|
err(`
|
|
5490
5416
|
Could not read or parse ${opts.file}.
|
|
@@ -5835,7 +5761,7 @@ var mailCommand = new Command20("mail").description("Self-hosted mail server (iR
|
|
|
5835
5761
|
// src/commands/backup.ts
|
|
5836
5762
|
import { Command as Command21 } from "commander";
|
|
5837
5763
|
import ora7 from "ora";
|
|
5838
|
-
import { readFileSync as
|
|
5764
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
5839
5765
|
async function guard3(fn) {
|
|
5840
5766
|
try {
|
|
5841
5767
|
await fn();
|
|
@@ -6128,7 +6054,7 @@ destinationCmd.command("create").description("Create a backup destination").requ
|
|
|
6128
6054
|
let sftpPrivateKey = opts.sftpPrivateKey;
|
|
6129
6055
|
if (opts.sftpPrivateKeyFile) {
|
|
6130
6056
|
try {
|
|
6131
|
-
sftpPrivateKey =
|
|
6057
|
+
sftpPrivateKey = readFileSync10(opts.sftpPrivateKeyFile, "utf8");
|
|
6132
6058
|
} catch {
|
|
6133
6059
|
throw new Error(`Cannot read key file: ${opts.sftpPrivateKeyFile}`);
|
|
6134
6060
|
}
|
|
@@ -6240,13 +6166,28 @@ var createCmd3 = new Command22("create").description("Mint a new personal access
|
|
|
6240
6166
|
"Scope the token to a resource (repeatable), e.g. project:abc123:read,write",
|
|
6241
6167
|
collectGrant,
|
|
6242
6168
|
[]
|
|
6169
|
+
).option(
|
|
6170
|
+
"--full-access",
|
|
6171
|
+
"Mint an UNSCOPED token with your own full access (required when no --grant is given)",
|
|
6172
|
+
false
|
|
6243
6173
|
).action(async (name, opts) => {
|
|
6244
6174
|
const grants = opts.grant ?? [];
|
|
6175
|
+
if (grants.length === 0 && !opts.fullAccess) {
|
|
6176
|
+
fail3(
|
|
6177
|
+
new Error(
|
|
6178
|
+
"Refusing to mint an unscoped token by default. Pass --grant <type:id:perms> to scope it, or --full-access to deliberately create one with your own full access."
|
|
6179
|
+
)
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
if (grants.length > 0 && opts.fullAccess) {
|
|
6183
|
+
fail3(new Error("--grant and --full-access are mutually exclusive: pick one."));
|
|
6184
|
+
}
|
|
6245
6185
|
const body = {
|
|
6246
6186
|
name,
|
|
6247
6187
|
readOnly: opts.readOnly || void 0,
|
|
6248
6188
|
expiresInDays: Number.isFinite(opts.expires) ? opts.expires : void 0,
|
|
6249
|
-
grants: grants.length > 0 ? grants : void 0
|
|
6189
|
+
grants: grants.length > 0 ? grants : void 0,
|
|
6190
|
+
fullAccess: grants.length === 0 ? true : void 0
|
|
6250
6191
|
};
|
|
6251
6192
|
const sp = spin3(`Creating token "${name}"\u2026`);
|
|
6252
6193
|
try {
|
|
@@ -6320,16 +6261,7 @@ var apiCommand = new Command23("api").description("Make an authenticated request
|
|
|
6320
6261
|
import { Command as Command24 } from "commander";
|
|
6321
6262
|
import chalk19 from "chalk";
|
|
6322
6263
|
import { intro as intro2, outro as outro2, password as passwordPrompt, isCancel as isCancel4, cancel as cancel3, log as log4 } from "@clack/prompts";
|
|
6323
|
-
|
|
6324
|
-
import { join as join16 } from "path";
|
|
6325
|
-
function resolvedApiPort() {
|
|
6326
|
-
try {
|
|
6327
|
-
return JSON.parse(readFileSync13(join16(OS_DIR, "ports.json"), "utf8")).api;
|
|
6328
|
-
} catch {
|
|
6329
|
-
return void 0;
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
var resetAdminCommand = new Command24("reset-admin-password").description("Reset the local admin login on THIS machine (no sign-in required)").option("--port <port>", "API port of the running service (default: the resolved port from ~/.openship/ports.json, else 4000)").option("--email <email>", "Also set the admin email").option("--name <name>", "Also set the admin display name").option("--password <password>", "New password (prompted if omitted)").action(async (opts) => {
|
|
6264
|
+
var resetAdminCommand = new Command24("reset-admin-password").description("Reset the local admin login on THIS machine (no sign-in required)").option("--port <port>", "API port of the running service (default: the port this install resolved to, else 4000)").option("--email <email>", "Also set the admin email").option("--name <name>", "Also set the admin display name").option("--password <password>", "New password (prompted if omitted)").action(async (opts) => {
|
|
6333
6265
|
intro2(chalk19.cyan("Reset Openship admin password"));
|
|
6334
6266
|
let pw = opts.password;
|
|
6335
6267
|
if (!pw) {
|
|
@@ -6355,7 +6287,7 @@ var resetAdminCommand = new Command24("reset-admin-password").description("Reset
|
|
|
6355
6287
|
}
|
|
6356
6288
|
pw = entered;
|
|
6357
6289
|
}
|
|
6358
|
-
const port = String(opts.port ||
|
|
6290
|
+
const port = String(opts.port || storedApiPort());
|
|
6359
6291
|
let res;
|
|
6360
6292
|
try {
|
|
6361
6293
|
res = await fetch(`http://127.0.0.1:${port}/api/system/reset-admin-password`, {
|
|
@@ -6378,10 +6310,10 @@ var resetAdminCommand = new Command24("reset-admin-password").description("Reset
|
|
|
6378
6310
|
|
|
6379
6311
|
// src/commands/install.ts
|
|
6380
6312
|
import { Command as Command25 } from "commander";
|
|
6381
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
6313
|
+
import { chmodSync as chmodSync2, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
6382
6314
|
import { spawn as spawn2, spawnSync as spawnSync6 } from "child_process";
|
|
6383
6315
|
import { homedir as homedir3 } from "os";
|
|
6384
|
-
import { join as
|
|
6316
|
+
import { join as join14 } from "path";
|
|
6385
6317
|
import ora9 from "ora";
|
|
6386
6318
|
function assetForPlatform() {
|
|
6387
6319
|
const { platform, arch } = process;
|
|
@@ -6395,10 +6327,10 @@ function assetForPlatform() {
|
|
|
6395
6327
|
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
6396
6328
|
}
|
|
6397
6329
|
function installDmg(dmg) {
|
|
6398
|
-
const homeApps =
|
|
6330
|
+
const homeApps = join14(homedir3(), "Applications");
|
|
6399
6331
|
let dest = homeApps;
|
|
6400
6332
|
try {
|
|
6401
|
-
|
|
6333
|
+
mkdirSync10(homeApps, { recursive: true });
|
|
6402
6334
|
} catch {
|
|
6403
6335
|
dest = "/Applications";
|
|
6404
6336
|
}
|
|
@@ -6408,14 +6340,14 @@ function installDmg(dmg) {
|
|
|
6408
6340
|
if (attach.status !== 0) throw new Error(`hdiutil attach failed: ${attach.stderr?.trim()}`);
|
|
6409
6341
|
const mount = (attach.stdout.match(/\/Volumes\/[^\n]*/g) ?? []).pop()?.trim();
|
|
6410
6342
|
if (!mount) throw new Error("Could not determine the mounted volume");
|
|
6411
|
-
let target =
|
|
6343
|
+
let target = join14(dest, "Openship.app");
|
|
6412
6344
|
try {
|
|
6413
|
-
const appInDmg =
|
|
6414
|
-
if (!
|
|
6345
|
+
const appInDmg = join14(mount, "Openship.app");
|
|
6346
|
+
if (!existsSync15(appInDmg)) throw new Error("Openship.app not found in the disk image");
|
|
6415
6347
|
spawnSync6("rm", ["-rf", target]);
|
|
6416
6348
|
let copy = spawnSync6("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
6417
6349
|
if (copy.status !== 0 && dest === homeApps) {
|
|
6418
|
-
target =
|
|
6350
|
+
target = join14("/Applications", "Openship.app");
|
|
6419
6351
|
spawnSync6("rm", ["-rf", target]);
|
|
6420
6352
|
copy = spawnSync6("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
6421
6353
|
}
|
|
@@ -6430,9 +6362,9 @@ function installAppImage(appImage) {
|
|
|
6430
6362
|
return appImage;
|
|
6431
6363
|
}
|
|
6432
6364
|
function installZip(zip) {
|
|
6433
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
6434
|
-
const target =
|
|
6435
|
-
|
|
6365
|
+
const localAppData = process.env.LOCALAPPDATA || join14(homedir3(), "AppData", "Local");
|
|
6366
|
+
const target = join14(localAppData, "Programs", "Openship");
|
|
6367
|
+
mkdirSync10(target, { recursive: true });
|
|
6436
6368
|
const expand = spawnSync6(
|
|
6437
6369
|
"powershell",
|
|
6438
6370
|
[
|
|
@@ -6459,8 +6391,8 @@ function launch(kind, target) {
|
|
|
6459
6391
|
child.unref();
|
|
6460
6392
|
return;
|
|
6461
6393
|
}
|
|
6462
|
-
const exe =
|
|
6463
|
-
const path2 =
|
|
6394
|
+
const exe = join14(target, "Openship.exe");
|
|
6395
|
+
const path2 = existsSync15(exe) ? exe : target;
|
|
6464
6396
|
spawnSync6("cmd", ["/c", "start", "", path2]);
|
|
6465
6397
|
}
|
|
6466
6398
|
var installCommand = new Command25("install").description("Download and install the Openship desktop app for this OS").option("--version <tag>", "Release tag to install (e.g. v1.2.3)").option("--latest", "Install the latest release (default)").option("--force", "Re-download even if a verified copy is cached").option("--no-verify", "Skip SHA-256 verification (allowed when no sidecar exists)").option("--no-launch", "Install without launching the app").action(async (opts) => {
|
|
@@ -6486,13 +6418,13 @@ var installCommand = new Command25("install").description("Download and install
|
|
|
6486
6418
|
process.exit(1);
|
|
6487
6419
|
}
|
|
6488
6420
|
const dir = releaseDir(tag);
|
|
6489
|
-
const assetPath =
|
|
6421
|
+
const assetPath = join14(dir, asset.name);
|
|
6490
6422
|
const sidecarPath = `${assetPath}.sha256`;
|
|
6491
6423
|
const assetUrl2 = `${RELEASES}/download/${tag}/${asset.name}`;
|
|
6492
6424
|
const sidecarUrl = `${assetUrl2}.sha256`;
|
|
6493
6425
|
let downloaded = false;
|
|
6494
6426
|
let sha;
|
|
6495
|
-
const cachedUsable = !opts.force &&
|
|
6427
|
+
const cachedUsable = !opts.force && existsSync15(assetPath) && (existsSync15(sidecarPath) || opts.verify === false);
|
|
6496
6428
|
if (cachedUsable) {
|
|
6497
6429
|
info(` Using cached ${asset.name} (${tag}).`);
|
|
6498
6430
|
} else {
|
|
@@ -6514,8 +6446,8 @@ var installCommand = new Command25("install").description("Download and install
|
|
|
6514
6446
|
const s2 = spin4("Verifying checksum\u2026");
|
|
6515
6447
|
try {
|
|
6516
6448
|
let sidecarBody;
|
|
6517
|
-
if (
|
|
6518
|
-
sidecarBody =
|
|
6449
|
+
if (existsSync15(sidecarPath) && !downloaded) {
|
|
6450
|
+
sidecarBody = readFileSync11(sidecarPath, "utf8");
|
|
6519
6451
|
} else {
|
|
6520
6452
|
sidecarBody = await fetchSidecar(sidecarUrl);
|
|
6521
6453
|
}
|
|
@@ -6538,8 +6470,8 @@ var installCommand = new Command25("install").description("Download and install
|
|
|
6538
6470
|
err(`Expected ${expected}, got ${actual}. The download may be corrupt or tampered with.`);
|
|
6539
6471
|
process.exit(1);
|
|
6540
6472
|
}
|
|
6541
|
-
|
|
6542
|
-
|
|
6473
|
+
mkdirSync10(dir, { recursive: true });
|
|
6474
|
+
writeFileSync10(sidecarPath, sidecarBody);
|
|
6543
6475
|
s2?.succeed("Checksum verified");
|
|
6544
6476
|
} catch (e) {
|
|
6545
6477
|
s2?.fail("Verification failed");
|
|
@@ -6626,7 +6558,7 @@ function detectPackageManager2(override) {
|
|
|
6626
6558
|
var updateCommand = new Command26("update").description("Update the Openship CLI + bundled server to the latest release").option("--check", "Only report the current + latest version; don't install").option("--via <manager>", "Package manager to update with: bun | npm").option("--rebuild", "From-source installs: rebuild even if already at the remote tip").action(async (opts) => {
|
|
6627
6559
|
const source = readSourceInstall();
|
|
6628
6560
|
if (source) return runSourceUpdate(source, opts);
|
|
6629
|
-
const current = "0.
|
|
6561
|
+
const current = "0.5.0";
|
|
6630
6562
|
let latest;
|
|
6631
6563
|
try {
|
|
6632
6564
|
latest = (await resolveLatestTag()).replace(/^v/, "");
|
|
@@ -6684,17 +6616,17 @@ var updateCommand = new Command26("update").description("Update the Openship CLI
|
|
|
6684
6616
|
|
|
6685
6617
|
// src/commands/cache.ts
|
|
6686
6618
|
import { Command as Command27 } from "commander";
|
|
6687
|
-
import { existsSync as
|
|
6688
|
-
import { join as
|
|
6619
|
+
import { existsSync as existsSync16, readdirSync as readdirSync2, readFileSync as readFileSync12, rmSync as rmSync5, statSync as statSync2 } from "fs";
|
|
6620
|
+
import { join as join15 } from "path";
|
|
6689
6621
|
function listAssets() {
|
|
6690
|
-
if (!
|
|
6622
|
+
if (!existsSync16(RELEASES_DIR)) return [];
|
|
6691
6623
|
const out = [];
|
|
6692
6624
|
for (const tag of readdirSync2(RELEASES_DIR)) {
|
|
6693
6625
|
const dir = releaseDir(tag);
|
|
6694
6626
|
if (!statSync2(dir).isDirectory()) continue;
|
|
6695
6627
|
for (const name of readdirSync2(dir)) {
|
|
6696
6628
|
if (name.endsWith(".sha256")) continue;
|
|
6697
|
-
const path2 =
|
|
6629
|
+
const path2 = join15(dir, name);
|
|
6698
6630
|
const st = statSync2(path2);
|
|
6699
6631
|
if (!st.isFile()) continue;
|
|
6700
6632
|
out.push({
|
|
@@ -6702,7 +6634,7 @@ function listAssets() {
|
|
|
6702
6634
|
name,
|
|
6703
6635
|
path: path2,
|
|
6704
6636
|
size: st.size,
|
|
6705
|
-
hasSidecar:
|
|
6637
|
+
hasSidecar: existsSync16(`${path2}.sha256`)
|
|
6706
6638
|
});
|
|
6707
6639
|
}
|
|
6708
6640
|
}
|
|
@@ -6733,7 +6665,7 @@ var verifyCmd2 = new Command27("verify").description("Re-hash cached assets and
|
|
|
6733
6665
|
results.push({ tag: a.tag, asset: a.name, result: "no-sidecar" });
|
|
6734
6666
|
continue;
|
|
6735
6667
|
}
|
|
6736
|
-
const expected = parseSha256(
|
|
6668
|
+
const expected = parseSha256(readFileSync12(`${a.path}.sha256`, "utf8"));
|
|
6737
6669
|
const actual = await hashFile(a.path);
|
|
6738
6670
|
const okMatch = expected !== null && expected === actual;
|
|
6739
6671
|
if (!okMatch) bad += 1;
|
|
@@ -6749,7 +6681,7 @@ var verifyCmd2 = new Command27("verify").description("Re-hash cached assets and
|
|
|
6749
6681
|
});
|
|
6750
6682
|
var cleanCmd = new Command27("clean").description("Delete cached release assets").argument("[tag]", "Only remove this release tag (default: all)").action((tag) => {
|
|
6751
6683
|
const target = tag ? releaseDir(tag) : RELEASES_DIR;
|
|
6752
|
-
if (!
|
|
6684
|
+
if (!existsSync16(target)) {
|
|
6753
6685
|
if (isJsonMode()) printJson({ removed: false, path: target });
|
|
6754
6686
|
else info(` Nothing to clean (${target}).`);
|
|
6755
6687
|
return;
|
|
@@ -6769,8 +6701,8 @@ var cacheCommand = new Command27("cache").description("Manage the local download
|
|
|
6769
6701
|
import chalk20 from "chalk";
|
|
6770
6702
|
import open2 from "open";
|
|
6771
6703
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
|
|
6772
|
-
import { existsSync as
|
|
6773
|
-
import { join as
|
|
6704
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync11, rmSync as rmSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
6705
|
+
import { join as join16 } from "path";
|
|
6774
6706
|
import {
|
|
6775
6707
|
intro as intro3,
|
|
6776
6708
|
outro as outro3,
|
|
@@ -6916,13 +6848,13 @@ async function streamProvision(port, sessionId, s) {
|
|
|
6916
6848
|
}
|
|
6917
6849
|
return { ok: ok3, detail };
|
|
6918
6850
|
}
|
|
6919
|
-
var SETUP_LOCK =
|
|
6851
|
+
var SETUP_LOCK = join16(OS_DIR, "setup-in-progress");
|
|
6920
6852
|
function isSetupInProgress() {
|
|
6921
|
-
return
|
|
6853
|
+
return existsSync17(SETUP_LOCK);
|
|
6922
6854
|
}
|
|
6923
6855
|
function markSetupStarted() {
|
|
6924
|
-
|
|
6925
|
-
|
|
6856
|
+
mkdirSync11(OS_DIR, { recursive: true });
|
|
6857
|
+
writeFileSync11(SETUP_LOCK, "1");
|
|
6926
6858
|
}
|
|
6927
6859
|
function markSetupDone() {
|
|
6928
6860
|
rmSync6(SETUP_LOCK, { force: true });
|
|
@@ -7183,17 +7115,22 @@ async function runWizard() {
|
|
|
7183
7115
|
}
|
|
7184
7116
|
let method = "bare";
|
|
7185
7117
|
if (process.platform === "linux") {
|
|
7186
|
-
|
|
7118
|
+
const gap = dockerGap();
|
|
7119
|
+
if (!gap) {
|
|
7187
7120
|
method = "compose";
|
|
7121
|
+
} else if (!gap.installable) {
|
|
7122
|
+
log5.warn(`${gap.summary} \u2014 using the bare process service instead.`);
|
|
7123
|
+
if (gap.hint) log5.info(gap.hint);
|
|
7124
|
+
method = "bare";
|
|
7188
7125
|
} else {
|
|
7189
|
-
log5.step(
|
|
7190
|
-
method = await ensureDocker() ? "compose" : "bare";
|
|
7126
|
+
log5.step(`${gap.summary} \u2014 installing via get.docker.com\u2026`);
|
|
7127
|
+
method = await ensureDocker({ onNotice: (line) => log5.info(line) }) ? "compose" : "bare";
|
|
7191
7128
|
if (method === "bare") {
|
|
7192
7129
|
log5.warn("Couldn't install Docker automatically \u2014 falling back to the bare process service.");
|
|
7193
7130
|
}
|
|
7194
7131
|
}
|
|
7195
7132
|
}
|
|
7196
|
-
const uiTag = `v${"0.
|
|
7133
|
+
const uiTag = `v${"0.5.0"}`;
|
|
7197
7134
|
if (method === "bare") {
|
|
7198
7135
|
const dl = spinner4();
|
|
7199
7136
|
dl.start("Pulling the Openship dist from GitHub");
|
|
@@ -7247,13 +7184,20 @@ async function runWizard() {
|
|
|
7247
7184
|
}
|
|
7248
7185
|
}
|
|
7249
7186
|
if (method === "compose") {
|
|
7187
|
+
const ports = await resolveComposePorts({});
|
|
7188
|
+
const apiPort = String(ports.api);
|
|
7189
|
+
const dashboardPort = String(ports.dashboard);
|
|
7190
|
+
const moved = portMoveNotice(ports, composeTrustedOriginUrls());
|
|
7191
|
+
if (moved.length) log5.info(moved.join("\n"));
|
|
7250
7192
|
log5.step(
|
|
7251
7193
|
sourceBuildDir() ? "Building the Openship images before touching :80/:443 (first run takes a few minutes)\u2026" : "Pulling images before touching :80/:443\u2026"
|
|
7252
7194
|
);
|
|
7253
7195
|
if (!composePrefetch({
|
|
7196
|
+
apiPort,
|
|
7197
|
+
dashboardPort,
|
|
7254
7198
|
publicUrl,
|
|
7255
7199
|
trustProxy: behindProxy,
|
|
7256
|
-
version: "0.
|
|
7200
|
+
version: "0.5.0",
|
|
7257
7201
|
noHostControl: !allowHostControl
|
|
7258
7202
|
})) {
|
|
7259
7203
|
cancel4("Couldn't fetch the Openship images \u2014 nothing on this box was changed.");
|
|
@@ -7271,9 +7215,11 @@ async function runWizard() {
|
|
|
7271
7215
|
const up = await composeUp({
|
|
7272
7216
|
// Prefetched above, before the preflight stopped anything.
|
|
7273
7217
|
alreadyFetched: true,
|
|
7218
|
+
apiPort,
|
|
7219
|
+
dashboardPort,
|
|
7274
7220
|
publicUrl,
|
|
7275
7221
|
trustProxy: behindProxy,
|
|
7276
|
-
version: "0.
|
|
7222
|
+
version: "0.5.0",
|
|
7277
7223
|
noHostControl: !allowHostControl
|
|
7278
7224
|
});
|
|
7279
7225
|
if (!up.ok) {
|
|
@@ -7504,22 +7450,14 @@ async function runWizard() {
|
|
|
7504
7450
|
byo: domainPlan.type === "byo"
|
|
7505
7451
|
});
|
|
7506
7452
|
}
|
|
7507
|
-
function storedPorts2() {
|
|
7508
|
-
const p = join19(OS_DIR, "ports.json");
|
|
7509
|
-
try {
|
|
7510
|
-
return existsSync18(p) ? JSON.parse(readFileSync16(p, "utf8")) : {};
|
|
7511
|
-
} catch {
|
|
7512
|
-
return {};
|
|
7513
|
-
}
|
|
7514
|
-
}
|
|
7515
7453
|
async function runControl() {
|
|
7516
7454
|
const svc = serviceStatus();
|
|
7517
7455
|
const isCompose = readInstallMethod() === "compose";
|
|
7518
7456
|
const running = isCompose ? composeRunning() : svc.running;
|
|
7519
7457
|
const managerLabel = isCompose ? "docker compose" : svc.kind === "unsupported" ? "none" : svc.kind;
|
|
7520
|
-
const ports =
|
|
7521
|
-
const
|
|
7522
|
-
const dashUrl = `http://localhost:${
|
|
7458
|
+
const ports = readStoredPorts();
|
|
7459
|
+
const apiPort = String(storedApiPort());
|
|
7460
|
+
const dashUrl = `http://localhost:${storedDashboardPort()}`;
|
|
7523
7461
|
const publicUrl = readInstanceUrl();
|
|
7524
7462
|
const primaryUrl = publicUrl && !/^https?:\/\/localhost/i.test(publicUrl) ? publicUrl : dashUrl;
|
|
7525
7463
|
intro3(`${chalk20.bgCyan(chalk20.black(" Openship "))}${chalk20.dim(" control")}`);
|
|
@@ -7594,7 +7532,7 @@ ${chalk20.dim("Dashboard".padEnd(11))}${dashUrl}
|
|
|
7594
7532
|
const pw = ensure2(
|
|
7595
7533
|
await password({ message: "New admin password", validate: (v) => v && v.length >= 8 ? void 0 : "At least 8 characters" })
|
|
7596
7534
|
);
|
|
7597
|
-
const rr = await internalPost(
|
|
7535
|
+
const rr = await internalPost(apiPort, "/api/system/reset-admin-password", { password: pw });
|
|
7598
7536
|
outro3(
|
|
7599
7537
|
rr.ok ? chalk20.green(`Password reset. Sign in at ${dashUrl} with your email + new password.`) : chalk20.red(`Couldn't reset: ${rr.data?.error || "failed"}`)
|
|
7600
7538
|
);
|
|
@@ -7610,7 +7548,7 @@ ${chalk20.dim("Dashboard".padEnd(11))}${dashUrl}
|
|
|
7610
7548
|
|
|
7611
7549
|
// src/index.ts
|
|
7612
7550
|
var program = new Command28();
|
|
7613
|
-
program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.
|
|
7551
|
+
program.name("openship").description("Openship CLI \u2014 install, run, and manage Openship from your terminal").version("0.5.0").option("--json", "Machine-readable JSON output (stdout data only)").hook("preAction", (thisCommand) => {
|
|
7614
7552
|
if (thisCommand.opts().json) setJsonMode(true);
|
|
7615
7553
|
}).action(async () => {
|
|
7616
7554
|
const installed = serviceStatus().installed || readInstallMethod() === "compose";
|