openship 0.4.7 → 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-2HYDXQEH.js → bare-VRGE5MXH.js} +7 -7
- package/dist/{chunk-TGIINPVM.js → chunk-6LKHXB6T.js} +22 -7
- package/dist/{chunk-R5BHFFAV.js → chunk-6PIYRG5I.js} +56 -17
- package/dist/{chunk-IMBD5PZ5.js → chunk-BS2AXYZX.js} +430 -86
- 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-XFCDYZUQ.js → chunk-JLKKYUZA.js} +31 -49
- package/dist/{chunk-4TJC4JZQ.js → chunk-K2RPVGPN.js} +23 -4
- package/dist/{chunk-MZ2WEHHY.js → chunk-KNTJ7PSF.js} +1 -1
- package/dist/{chunk-L2VOGB2X.js → chunk-KZOBYJUW.js} +1 -1
- package/dist/{chunk-IPYPMI3S.js → chunk-NNPEG6HM.js} +1 -1
- package/dist/{chunk-UXWAVM4C.js → chunk-NXFIRK2Z.js} +398 -112
- 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-2KXREXMO.js → cloud-3M2WZ624.js} +12 -12
- package/dist/{detect-2XSCFFTF.js → detect-BAJMY6KM.js} +7 -5
- 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-ZQ4Y5HEA.js → edge-import-RIWNEIXQ.js} +21 -19
- package/dist/{ensure-container-edge-ED25XL3Z.js → ensure-container-edge-3NSYKHRX.js} +6 -6
- package/dist/{executor-YPT6RH6O.js → executor-LOYMPU67.js} +3 -3
- package/dist/index.js +309 -338
- package/dist/{nginx-XWJAOGS4.js → nginx-QE4DOM5J.js} +4 -4
- package/dist/{openresty-lua-4WLGDSER.js → openresty-lua-BTAKECCF.js} +2 -2
- package/dist/server/index.js +51705 -45369
- 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-2DMFIA74.js → setup-KELALK7H.js} +7 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,29 +3,42 @@
|
|
|
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,
|
|
9
11
|
composeInternalToken,
|
|
10
12
|
composeIsViableDefault,
|
|
13
|
+
composePrefetch,
|
|
11
14
|
composePs,
|
|
12
15
|
composeRestart,
|
|
13
16
|
composeRunning,
|
|
14
17
|
composeStart,
|
|
18
|
+
composeTrustedOriginUrls,
|
|
15
19
|
composeUninstall,
|
|
16
20
|
composeUp,
|
|
17
21
|
composeUpdate,
|
|
22
|
+
dockerGap,
|
|
18
23
|
ensureDocker,
|
|
19
24
|
hasDockerCompose,
|
|
20
25
|
importMigratedSites,
|
|
26
|
+
portMoveNotice,
|
|
21
27
|
prepareFromSource,
|
|
22
28
|
readInstallMethod,
|
|
29
|
+
readInstanceUrl,
|
|
23
30
|
readSourceInstall,
|
|
31
|
+
readStoredPorts,
|
|
24
32
|
rebuildFromSource,
|
|
25
33
|
remoteSha,
|
|
34
|
+
resolveComposePorts,
|
|
35
|
+
resolvePorts,
|
|
36
|
+
saveInstanceUrl,
|
|
26
37
|
shortSha,
|
|
27
|
-
sourceBuildDir
|
|
28
|
-
|
|
38
|
+
sourceBuildDir,
|
|
39
|
+
storedApiPort,
|
|
40
|
+
storedDashboardPort
|
|
41
|
+
} from "./chunk-BS2AXYZX.js";
|
|
29
42
|
import "./chunk-FRNAR33O.js";
|
|
30
43
|
import "./chunk-EI575GHN.js";
|
|
31
44
|
import "./chunk-6APIPIJY.js";
|
|
@@ -33,12 +46,12 @@ import "./chunk-IXCHURVK.js";
|
|
|
33
46
|
import "./chunk-OWED2GRM.js";
|
|
34
47
|
import "./chunk-VWPEKOH6.js";
|
|
35
48
|
import "./chunk-W73X43R7.js";
|
|
36
|
-
import "./chunk-
|
|
37
|
-
import "./chunk-
|
|
49
|
+
import "./chunk-6LKHXB6T.js";
|
|
50
|
+
import "./chunk-JLKKYUZA.js";
|
|
38
51
|
import "./chunk-AVIB7CT7.js";
|
|
39
52
|
import "./chunk-5MZ624VX.js";
|
|
40
53
|
import "./chunk-Q2CIRAOK.js";
|
|
41
|
-
import "./chunk-
|
|
54
|
+
import "./chunk-NNPEG6HM.js";
|
|
42
55
|
import {
|
|
43
56
|
beginEdgeTakeover,
|
|
44
57
|
collectProxyCerts,
|
|
@@ -51,39 +64,40 @@ import {
|
|
|
51
64
|
recoverInterruptedTakeover,
|
|
52
65
|
rollbackEdgeTakeover,
|
|
53
66
|
scanImportableSites
|
|
54
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-NXFIRK2Z.js";
|
|
55
68
|
import "./chunk-O6ZZF52V.js";
|
|
56
69
|
import "./chunk-JYGD5WSN.js";
|
|
57
|
-
import "./chunk-
|
|
58
|
-
import "./chunk-
|
|
59
|
-
import "./chunk-
|
|
60
|
-
import "./chunk-
|
|
61
|
-
import "./chunk-
|
|
62
|
-
import "./chunk-
|
|
63
|
-
import "./chunk-
|
|
64
|
-
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";
|
|
65
78
|
import "./chunk-RPHRPFEH.js";
|
|
66
79
|
import {
|
|
67
80
|
LocalExecutor
|
|
68
81
|
} from "./chunk-TYVME3XD.js";
|
|
69
|
-
import "./chunk-
|
|
82
|
+
import "./chunk-RB35YN3J.js";
|
|
70
83
|
import "./chunk-FPRHYBY2.js";
|
|
71
84
|
import {
|
|
72
85
|
edgeCrashReason,
|
|
73
|
-
|
|
86
|
+
edgeIsBroken,
|
|
74
87
|
ourEdgeContainerRunning
|
|
75
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-6PIYRG5I.js";
|
|
76
89
|
import "./chunk-ILFETZOM.js";
|
|
77
|
-
import "./chunk-
|
|
90
|
+
import "./chunk-EAAGSQXN.js";
|
|
78
91
|
import {
|
|
79
92
|
CLOUD_DASHBOARD_URL,
|
|
80
93
|
LOCAL_API_URL,
|
|
81
94
|
LOCAL_DASHBOARD_URL,
|
|
82
95
|
cliInstallCommand,
|
|
96
|
+
commandToArgv,
|
|
83
97
|
isValidEmail,
|
|
84
98
|
parseOpenshipConfigJson,
|
|
85
99
|
resolveCliUpdatePlan
|
|
86
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-UMQP7SYT.js";
|
|
87
101
|
import "./chunk-KI2EY3WB.js";
|
|
88
102
|
|
|
89
103
|
// src/index.ts
|
|
@@ -486,8 +500,8 @@ import chalk6 from "chalk";
|
|
|
486
500
|
import ora from "ora";
|
|
487
501
|
import { spawn } from "child_process";
|
|
488
502
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
489
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
490
|
-
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";
|
|
491
505
|
import { fileURLToPath } from "url";
|
|
492
506
|
|
|
493
507
|
// src/lib/dashboard.ts
|
|
@@ -657,7 +671,7 @@ async function fetchBundle(tag, onProgress) {
|
|
|
657
671
|
|
|
658
672
|
// src/lib/service.ts
|
|
659
673
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
660
|
-
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";
|
|
661
675
|
import { homedir } from "os";
|
|
662
676
|
import { join as join4, resolve } from "path";
|
|
663
677
|
var HOME = homedir();
|
|
@@ -875,14 +889,9 @@ function serviceStatus() {
|
|
|
875
889
|
}
|
|
876
890
|
function sweepOrphanPorts() {
|
|
877
891
|
if (process.platform === "win32") return;
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const p = JSON.parse(raw);
|
|
882
|
-
ports = [p.api, p.dashboard].filter((n) => typeof n === "number");
|
|
883
|
-
} catch {
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
892
|
+
const stored = readStoredPorts();
|
|
893
|
+
const ports = [stored.api, stored.dashboard].filter((n) => typeof n === "number");
|
|
894
|
+
if (ports.length === 0) return;
|
|
886
895
|
const self = process.pid;
|
|
887
896
|
for (const port of ports) {
|
|
888
897
|
const q = run("lsof", ["-ti", `tcp:${port}`]);
|
|
@@ -923,97 +932,10 @@ function stop() {
|
|
|
923
932
|
return result;
|
|
924
933
|
}
|
|
925
934
|
|
|
926
|
-
// src/lib/ports.ts
|
|
927
|
-
import { createServer } from "net";
|
|
928
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
929
|
-
import { join as join5 } from "path";
|
|
930
|
-
var PORTS_FILE = join5(OS_DIR, "ports.json");
|
|
931
|
-
var INSTANCE_FILE = join5(OS_DIR, "instance.json");
|
|
932
|
-
function saveInstanceUrl(publicUrl) {
|
|
933
|
-
try {
|
|
934
|
-
if (!existsSync4(OS_DIR)) mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
935
|
-
writeFileSync4(INSTANCE_FILE, JSON.stringify({ publicUrl: publicUrl ?? null }));
|
|
936
|
-
} catch {
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
function readInstanceUrl() {
|
|
940
|
-
try {
|
|
941
|
-
return JSON.parse(readFileSync3(INSTANCE_FILE, "utf8")).publicUrl ?? null;
|
|
942
|
-
} catch {
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
var DEFAULT_API = 4e3;
|
|
947
|
-
var DEFAULT_DASHBOARD = 3001;
|
|
948
|
-
function isPortFree(port) {
|
|
949
|
-
return new Promise((resolve2) => {
|
|
950
|
-
const srv = createServer();
|
|
951
|
-
srv.once("error", () => resolve2(false));
|
|
952
|
-
srv.listen(port, "127.0.0.1", () => srv.close(() => resolve2(true)));
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
|
-
async function waitPortFree(port, opts = {}) {
|
|
956
|
-
const timeoutMs = opts.timeoutMs ?? 6e3;
|
|
957
|
-
const intervalMs = opts.intervalMs ?? 250;
|
|
958
|
-
const deadline = Date.now() + timeoutMs;
|
|
959
|
-
for (; ; ) {
|
|
960
|
-
if (await isPortFree(port)) return true;
|
|
961
|
-
if (Date.now() >= deadline) return false;
|
|
962
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
function getFreePort() {
|
|
966
|
-
return new Promise((resolve2, reject2) => {
|
|
967
|
-
const srv = createServer();
|
|
968
|
-
srv.once("error", reject2);
|
|
969
|
-
srv.listen(0, "127.0.0.1", () => {
|
|
970
|
-
const addr = srv.address();
|
|
971
|
-
const port = addr && typeof addr === "object" ? addr.port : 0;
|
|
972
|
-
srv.close(() => port ? resolve2(port) : reject2(new Error("no free port")));
|
|
973
|
-
});
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
function loadStoredPorts() {
|
|
977
|
-
try {
|
|
978
|
-
return JSON.parse(readFileSync3(PORTS_FILE, "utf-8"));
|
|
979
|
-
} catch {
|
|
980
|
-
return {};
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
function saveStoredPorts(api, dashboard) {
|
|
984
|
-
try {
|
|
985
|
-
if (!existsSync4(OS_DIR)) mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
986
|
-
writeFileSync4(PORTS_FILE, JSON.stringify({ api, dashboard }));
|
|
987
|
-
} catch {
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
async function resolvePorts(prefs) {
|
|
991
|
-
const stored = loadStoredPorts();
|
|
992
|
-
const apiPref = prefs.api ?? stored.api ?? DEFAULT_API;
|
|
993
|
-
const dashPref = prefs.dashboard ?? stored.dashboard ?? DEFAULT_DASHBOARD;
|
|
994
|
-
const apiRemembered = prefs.api === void 0 && stored.api === apiPref;
|
|
995
|
-
const dashRemembered = prefs.dashboard === void 0 && stored.dashboard === dashPref;
|
|
996
|
-
let api;
|
|
997
|
-
if (await isPortFree(apiPref)) api = apiPref;
|
|
998
|
-
else if (apiRemembered && await waitPortFree(apiPref)) api = apiPref;
|
|
999
|
-
else api = await getFreePort();
|
|
1000
|
-
let dashboard;
|
|
1001
|
-
if (dashPref !== api && await isPortFree(dashPref)) dashboard = dashPref;
|
|
1002
|
-
else if (dashPref !== api && dashRemembered && await waitPortFree(dashPref)) dashboard = dashPref;
|
|
1003
|
-
else dashboard = await getFreePort();
|
|
1004
|
-
if (dashboard === api) dashboard = await getFreePort();
|
|
1005
|
-
saveStoredPorts(api, dashboard);
|
|
1006
|
-
return {
|
|
1007
|
-
api,
|
|
1008
|
-
dashboard,
|
|
1009
|
-
switched: { api: api !== apiPref, dashboard: dashboard !== dashPref }
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
935
|
// src/lib/edge-preflight.ts
|
|
1014
936
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1015
|
-
import { existsSync as
|
|
1016
|
-
import { join as
|
|
937
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
938
|
+
import { join as join5 } from "path";
|
|
1017
939
|
import chalk5 from "chalk";
|
|
1018
940
|
import { isCancel, log, note, select } from "@clack/prompts";
|
|
1019
941
|
async function planAndApplyHostEdge(opts, overrides = {}) {
|
|
@@ -1052,7 +974,7 @@ async function rollbackHostEdge() {
|
|
|
1052
974
|
return false;
|
|
1053
975
|
}
|
|
1054
976
|
}
|
|
1055
|
-
var IMPORTED_MARKER =
|
|
977
|
+
var IMPORTED_MARKER = join5(OS_DIR, "imported-proxy");
|
|
1056
978
|
function edgeServedHostnames() {
|
|
1057
979
|
const served = /* @__PURE__ */ new Set();
|
|
1058
980
|
const r = spawnSync3(
|
|
@@ -1076,13 +998,13 @@ function edgeServedHostnames() {
|
|
|
1076
998
|
}
|
|
1077
999
|
function markStoppedProxyImported() {
|
|
1078
1000
|
try {
|
|
1079
|
-
|
|
1080
|
-
|
|
1001
|
+
mkdirSync5(OS_DIR, { recursive: true, mode: 448 });
|
|
1002
|
+
writeFileSync4(IMPORTED_MARKER, (/* @__PURE__ */ new Date()).toISOString(), { mode: 384 });
|
|
1081
1003
|
} catch {
|
|
1082
1004
|
}
|
|
1083
1005
|
}
|
|
1084
1006
|
async function offerStoppedProxyImport(executor, deps) {
|
|
1085
|
-
if (!deps.interactive ||
|
|
1007
|
+
if (!deps.interactive || existsSync4(IMPORTED_MARKER)) return { proceed: true };
|
|
1086
1008
|
let proxy = null;
|
|
1087
1009
|
try {
|
|
1088
1010
|
proxy = await deps.detectInstalledProxy(executor);
|
|
@@ -1227,15 +1149,15 @@ function defaultDeps() {
|
|
|
1227
1149
|
}
|
|
1228
1150
|
|
|
1229
1151
|
// src/lib/loopback-api.ts
|
|
1230
|
-
import { existsSync as
|
|
1152
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
1231
1153
|
import { randomBytes } from "crypto";
|
|
1232
|
-
import { join as
|
|
1154
|
+
import { join as join6 } from "path";
|
|
1233
1155
|
function ensureInternalToken() {
|
|
1234
|
-
const path2 =
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
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 });
|
|
1237
1159
|
const token = randomBytes(32).toString("hex");
|
|
1238
|
-
|
|
1160
|
+
writeFileSync5(path2, token, { mode: 384 });
|
|
1239
1161
|
return token;
|
|
1240
1162
|
}
|
|
1241
1163
|
async function internalGet(port, path2, token) {
|
|
@@ -1264,17 +1186,17 @@ async function internalPost(port, path2, body, token) {
|
|
|
1264
1186
|
return { ok: false, data: { error: err2.message } };
|
|
1265
1187
|
}
|
|
1266
1188
|
}
|
|
1267
|
-
async function bootstrapAdmin(
|
|
1268
|
-
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);
|
|
1269
1191
|
if (ok3) return { ok: true };
|
|
1270
1192
|
if (data?.error === "An admin account already exists") return { ok: true, message: "already-exists" };
|
|
1271
1193
|
return { ok: false, message: data?.error || "failed" };
|
|
1272
1194
|
}
|
|
1273
|
-
async function waitHealthy(
|
|
1195
|
+
async function waitHealthy(apiPort, seconds = 90) {
|
|
1274
1196
|
for (let i = 0; i < seconds; i++) {
|
|
1275
1197
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
1276
1198
|
try {
|
|
1277
|
-
await fetch(`http://127.0.0.1:${
|
|
1199
|
+
await fetch(`http://127.0.0.1:${apiPort}/api/health`, { signal: AbortSignal.timeout(2e3) });
|
|
1278
1200
|
return true;
|
|
1279
1201
|
} catch {
|
|
1280
1202
|
}
|
|
@@ -1555,16 +1477,16 @@ function normalizePublicUrl(raw) {
|
|
|
1555
1477
|
return url;
|
|
1556
1478
|
}
|
|
1557
1479
|
var DIST_DIR = dirname2(fileURLToPath(import.meta.url));
|
|
1558
|
-
var SERVER_DIR =
|
|
1480
|
+
var SERVER_DIR = join7(DIST_DIR, "server");
|
|
1559
1481
|
function ensureAuthSecret() {
|
|
1560
|
-
const path2 =
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
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 });
|
|
1563
1485
|
const secret = randomBytes2(32).toString("hex");
|
|
1564
|
-
|
|
1486
|
+
writeFileSync6(path2, secret, { mode: 384 });
|
|
1565
1487
|
return secret;
|
|
1566
1488
|
}
|
|
1567
|
-
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(
|
|
1568
1490
|
"--public-url <url>",
|
|
1569
1491
|
"Serve remotely at this public URL (VPS): binds the dashboard to all interfaces, proxies the API same-origin, and requires login"
|
|
1570
1492
|
).option(
|
|
@@ -1683,6 +1605,32 @@ async function runCompose(opts) {
|
|
|
1683
1605
|
);
|
|
1684
1606
|
process.exit(1);
|
|
1685
1607
|
}
|
|
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
|
+
}
|
|
1616
|
+
const prefetch = ora(
|
|
1617
|
+
sourceBuildDir() ? "Building images before touching :80/:443\u2026" : "Pulling images before touching :80/:443\u2026"
|
|
1618
|
+
).start();
|
|
1619
|
+
const fetched = composePrefetch({
|
|
1620
|
+
apiPort,
|
|
1621
|
+
dashboardPort,
|
|
1622
|
+
publicUrl,
|
|
1623
|
+
trustProxy: opts.trustProxy,
|
|
1624
|
+
noHostControl: opts.hostControl === false ? true : void 0
|
|
1625
|
+
});
|
|
1626
|
+
if (!fetched) {
|
|
1627
|
+
prefetch.fail("Couldn't fetch the stack's images \u2014 nothing was changed on this box.");
|
|
1628
|
+
console.error(
|
|
1629
|
+
chalk6.dim("\n Your current proxy is untouched and still serving. Fix the pull/build error above and re-run.\n")
|
|
1630
|
+
);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
prefetch.succeed("Images ready \u2014 nothing on :80/:443 has changed yet.");
|
|
1686
1634
|
let edgePlan;
|
|
1687
1635
|
try {
|
|
1688
1636
|
edgePlan = await planAndApplyHostEdge({ edge: opts.edge });
|
|
@@ -1700,14 +1648,15 @@ async function runCompose(opts) {
|
|
|
1700
1648
|
);
|
|
1701
1649
|
process.exit(1);
|
|
1702
1650
|
}
|
|
1703
|
-
const publicUrl = effectivePublicUrl(opts.publicUrl);
|
|
1704
1651
|
const fromSource = sourceBuildDir();
|
|
1705
1652
|
const spinner5 = ora(
|
|
1706
1653
|
fromSource ? `Building Openship from ${fromSource} and starting the stack\u2026` : "Starting Openship via Docker Compose\u2026"
|
|
1707
1654
|
).start();
|
|
1708
|
-
const res = composeUp({
|
|
1709
|
-
|
|
1710
|
-
|
|
1655
|
+
const res = await composeUp({
|
|
1656
|
+
// Prefetched above, before the preflight stopped anything.
|
|
1657
|
+
alreadyFetched: true,
|
|
1658
|
+
apiPort,
|
|
1659
|
+
dashboardPort,
|
|
1711
1660
|
publicUrl,
|
|
1712
1661
|
trustProxy: opts.trustProxy,
|
|
1713
1662
|
// commander maps `--no-host-control` to hostControl === false. `undefined` when
|
|
@@ -1724,11 +1673,11 @@ async function runCompose(opts) {
|
|
|
1724
1673
|
process.exit(1);
|
|
1725
1674
|
}
|
|
1726
1675
|
spinner5.succeed("Openship is running via Docker Compose.");
|
|
1727
|
-
if (edgePlan.action &&
|
|
1676
|
+
if (edgePlan.action && await edgeIsBroken(new LocalExecutor())) {
|
|
1728
1677
|
const reason = await edgeCrashReason(new LocalExecutor());
|
|
1729
1678
|
console.error(
|
|
1730
1679
|
chalk6.red(`
|
|
1731
|
-
Openship's edge is not
|
|
1680
|
+
Openship's edge container is not running${reason ? ` \u2014 ${reason}` : "."}`)
|
|
1732
1681
|
);
|
|
1733
1682
|
const restored = await rollbackHostEdge();
|
|
1734
1683
|
console.error(
|
|
@@ -1804,8 +1753,8 @@ async function startService(opts, runOpts = {}) {
|
|
|
1804
1753
|
`) + p.content + "\n"
|
|
1805
1754
|
);
|
|
1806
1755
|
return {
|
|
1807
|
-
port: String(opts.port ||
|
|
1808
|
-
dashPort: String(opts.dashboardPort ||
|
|
1756
|
+
port: String(opts.port || DEFAULT_API_PORT),
|
|
1757
|
+
dashPort: String(opts.dashboardPort || DEFAULT_DASHBOARD_PORT),
|
|
1809
1758
|
publicUrl
|
|
1810
1759
|
};
|
|
1811
1760
|
}
|
|
@@ -1831,11 +1780,9 @@ async function startService(opts, runOpts = {}) {
|
|
|
1831
1780
|
try {
|
|
1832
1781
|
const res = installAndStart(flags);
|
|
1833
1782
|
if (!runOpts.quiet) {
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
A preferred port was busy \u2014 using API ${port}, dashboard ${dashPort}.`)
|
|
1838
|
-
);
|
|
1783
|
+
const portNotice = portMoveNotice(resolved, [publicUrl]);
|
|
1784
|
+
if (portNotice.length) {
|
|
1785
|
+
console.log("\n" + portNotice.map((l) => chalk6.yellow(` ${l}`)).join("\n"));
|
|
1839
1786
|
}
|
|
1840
1787
|
const dashboardLine = publicUrl ? chalk6.dim(` Dashboard: ${publicUrl} (login required)
|
|
1841
1788
|
`) : chalk6.dim(` Dashboard: http://localhost:${dashPort} (login required)
|
|
@@ -1866,8 +1813,8 @@ async function runForeground(opts, source) {
|
|
|
1866
1813
|
apiArgs = ["run", "src/index.ts"];
|
|
1867
1814
|
apiCwd = source.apiDir;
|
|
1868
1815
|
} else {
|
|
1869
|
-
const serverEntry =
|
|
1870
|
-
if (!
|
|
1816
|
+
const serverEntry = join7(SERVER_DIR, "index.js");
|
|
1817
|
+
if (!existsSync6(serverEntry)) {
|
|
1871
1818
|
console.error(
|
|
1872
1819
|
chalk6.red("\n Bundled server not found in this install.") + chalk6.dim("\n Reinstall with `openship update` (or `npm i -g openship`).\n")
|
|
1873
1820
|
);
|
|
@@ -1883,28 +1830,42 @@ async function runForeground(opts, source) {
|
|
|
1883
1830
|
const dashPort = String(resolved.dashboard);
|
|
1884
1831
|
const publicUrl = effectivePublicUrl(opts.publicUrl);
|
|
1885
1832
|
const managedEdge = Boolean(opts.managedEdge && publicUrl);
|
|
1886
|
-
const dataDir = opts.dataDir ||
|
|
1887
|
-
|
|
1888
|
-
const logDir =
|
|
1889
|
-
|
|
1890
|
-
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");
|
|
1891
1838
|
const instanceLog = createWriteStream2(instanceLogPath, { flags: "w" });
|
|
1892
1839
|
const env = {
|
|
1893
1840
|
...process.env,
|
|
1894
1841
|
PORT: port,
|
|
1895
1842
|
NODE_ENV: "production",
|
|
1896
|
-
// desktop
|
|
1897
|
-
|
|
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",
|
|
1898
1859
|
OPENSHIP_TARGET: "local",
|
|
1899
1860
|
OPENSHIP_JOB_RUNNER: "in-process",
|
|
1900
1861
|
PGLITE_DATA_DIR: dataDir,
|
|
1901
1862
|
BETTER_AUTH_SECRET: ensureAuthSecret()
|
|
1902
1863
|
};
|
|
1903
1864
|
if (!source) {
|
|
1904
|
-
env.OPENSHIP_MIGRATIONS_DIR =
|
|
1905
|
-
env.OPENSHIP_PGLITE_ASSETS_DIR =
|
|
1906
|
-
const engineDir =
|
|
1907
|
-
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;
|
|
1908
1869
|
}
|
|
1909
1870
|
env.OPENSHIP_REQUIRE_AUTH = "true";
|
|
1910
1871
|
env.INTERNAL_TOKEN = ensureInternalToken();
|
|
@@ -1996,7 +1957,7 @@ async function runForeground(opts, source) {
|
|
|
1996
1957
|
const uiSpinner = ora("Preparing the dashboard\u2026").start();
|
|
1997
1958
|
try {
|
|
1998
1959
|
const bundle = await ensureDashboard({
|
|
1999
|
-
tag: source ? "local" : opts.uiVersion || `v${"0.
|
|
1960
|
+
tag: source ? "local" : opts.uiVersion || `v${"0.5.0"}`,
|
|
2000
1961
|
onProgress: (received, total) => {
|
|
2001
1962
|
if (total) {
|
|
2002
1963
|
uiSpinner.text = `Downloading dashboard\u2026 ${Math.round(received / total * 100)}%`;
|
|
@@ -2126,7 +2087,7 @@ var stopCommand = new Command5("stop").description("Stop Openship (started by `o
|
|
|
2126
2087
|
import { Command as Command6 } from "commander";
|
|
2127
2088
|
import chalk8 from "chalk";
|
|
2128
2089
|
import { confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
2129
|
-
import { existsSync as
|
|
2090
|
+
import { existsSync as existsSync7, rmSync as rmSync3 } from "fs";
|
|
2130
2091
|
var uninstallCommand = new Command6("uninstall").description(
|
|
2131
2092
|
"Remove Openship from this machine: stop it, delete its data (database, certs, edge config), its images, and ~/.openship. Deployed apps are left running."
|
|
2132
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) => {
|
|
@@ -2172,7 +2133,7 @@ var uninstallCommand = new Command6("uninstall").description(
|
|
|
2172
2133
|
console.log(chalk8.yellow(` Couldn't remove the service: ${err2.message}`));
|
|
2173
2134
|
}
|
|
2174
2135
|
}
|
|
2175
|
-
if (destructive &&
|
|
2136
|
+
if (destructive && existsSync7(OS_DIR)) {
|
|
2176
2137
|
try {
|
|
2177
2138
|
rmSync3(OS_DIR, { recursive: true, force: true });
|
|
2178
2139
|
console.log(chalk8.green(` Removed ${OS_DIR}`));
|
|
@@ -2191,15 +2152,15 @@ var uninstallCommand = new Command6("uninstall").description(
|
|
|
2191
2152
|
|
|
2192
2153
|
// src/commands/init.ts
|
|
2193
2154
|
import { Command as Command7 } from "commander";
|
|
2194
|
-
import { existsSync as
|
|
2155
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
2195
2156
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
2196
2157
|
import { stdin as input2, stdout as output2 } from "process";
|
|
2197
|
-
import { join as
|
|
2158
|
+
import { join as join8 } from "path";
|
|
2198
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) => {
|
|
2199
2160
|
const root = opts.dir || process.cwd();
|
|
2200
|
-
const linkDir =
|
|
2201
|
-
const linkPath =
|
|
2202
|
-
if (
|
|
2161
|
+
const linkDir = join8(root, ".openship");
|
|
2162
|
+
const linkPath = join8(linkDir, "project.json");
|
|
2163
|
+
if (existsSync8(linkPath) && !opts.force) {
|
|
2203
2164
|
err(`Already linked (${linkPath}). Re-run with --force to overwrite.`);
|
|
2204
2165
|
process.exit(1);
|
|
2205
2166
|
}
|
|
@@ -2252,8 +2213,8 @@ var initCommand = new Command7("init").description("Link the current directory t
|
|
|
2252
2213
|
context: getActiveContext(),
|
|
2253
2214
|
defaults: { environment: opts.environment || "production" }
|
|
2254
2215
|
};
|
|
2255
|
-
|
|
2256
|
-
|
|
2216
|
+
mkdirSync8(linkDir, { recursive: true });
|
|
2217
|
+
writeFileSync7(linkPath, JSON.stringify(link, null, 2) + "\n");
|
|
2257
2218
|
if (isJsonMode()) {
|
|
2258
2219
|
printJson({ path: linkPath, link });
|
|
2259
2220
|
return;
|
|
@@ -2265,8 +2226,8 @@ var initCommand = new Command7("init").description("Link the current directory t
|
|
|
2265
2226
|
|
|
2266
2227
|
// src/commands/config.ts
|
|
2267
2228
|
import { Command as Command8 } from "commander";
|
|
2268
|
-
import { existsSync as
|
|
2269
|
-
import { join as
|
|
2229
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
2230
|
+
import { join as join9 } from "path";
|
|
2270
2231
|
var SCHEMA_URL = "https://openship.io/openship.schema.json";
|
|
2271
2232
|
var CONFIG_FILE = "openship.json";
|
|
2272
2233
|
function detectHints(dir) {
|
|
@@ -2278,15 +2239,15 @@ function detectHints(dir) {
|
|
|
2278
2239
|
["package-lock.json", "npm"]
|
|
2279
2240
|
];
|
|
2280
2241
|
for (const [file, pm] of lock) {
|
|
2281
|
-
if (
|
|
2242
|
+
if (existsSync9(join9(dir, file))) {
|
|
2282
2243
|
hints.packageManager = pm;
|
|
2283
2244
|
break;
|
|
2284
2245
|
}
|
|
2285
2246
|
}
|
|
2286
|
-
const pkgPath =
|
|
2287
|
-
if (
|
|
2247
|
+
const pkgPath = join9(dir, "package.json");
|
|
2248
|
+
if (existsSync9(pkgPath)) {
|
|
2288
2249
|
try {
|
|
2289
|
-
const pkg = JSON.parse(
|
|
2250
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
|
|
2290
2251
|
const runner = hints.packageManager ?? "npm";
|
|
2291
2252
|
const exec = runner === "npm" ? "npm run" : runner;
|
|
2292
2253
|
if (pkg.scripts?.build) hints.buildCommand = `${exec} build`;
|
|
@@ -2298,15 +2259,15 @@ function detectHints(dir) {
|
|
|
2298
2259
|
}
|
|
2299
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) => {
|
|
2300
2261
|
const dir = opts.dir || process.cwd();
|
|
2301
|
-
const path2 =
|
|
2302
|
-
if (
|
|
2262
|
+
const path2 = join9(dir, CONFIG_FILE);
|
|
2263
|
+
if (existsSync9(path2) && !opts.force) {
|
|
2303
2264
|
err(`${CONFIG_FILE} already exists. Re-run with --force to overwrite.`);
|
|
2304
2265
|
process.exit(1);
|
|
2305
2266
|
}
|
|
2306
2267
|
const hints = detectHints(dir);
|
|
2307
2268
|
const scaffold = { $schema: SCHEMA_URL, ...hints };
|
|
2308
2269
|
const text2 = JSON.stringify(scaffold, null, 2) + "\n";
|
|
2309
|
-
|
|
2270
|
+
writeFileSync8(path2, text2);
|
|
2310
2271
|
if (isJsonMode()) {
|
|
2311
2272
|
printJson({ path: path2, config: scaffold });
|
|
2312
2273
|
return;
|
|
@@ -2316,13 +2277,13 @@ var initCmd = new Command8("init").description("Scaffold an openship.json in the
|
|
|
2316
2277
|
info(" Edit it to declare framework, env, domains, resources, services\u2026 then `openship config validate`.\n");
|
|
2317
2278
|
});
|
|
2318
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) => {
|
|
2319
|
-
const path2 = file === CONFIG_FILE ?
|
|
2320
|
-
if (!
|
|
2280
|
+
const path2 = file === CONFIG_FILE ? join9(process.cwd(), CONFIG_FILE) : file;
|
|
2281
|
+
if (!existsSync9(path2)) {
|
|
2321
2282
|
if (isJsonMode()) printJson({ valid: false, errors: [`${path2} not found`], warnings: [] });
|
|
2322
2283
|
else err(`Not found: ${path2}`);
|
|
2323
2284
|
process.exit(1);
|
|
2324
2285
|
}
|
|
2325
|
-
const { config, errors, warnings } = parseOpenshipConfigJson(
|
|
2286
|
+
const { config, errors, warnings } = parseOpenshipConfigJson(readFileSync5(path2, "utf8"));
|
|
2326
2287
|
const valid = errors.length === 0 && config !== null;
|
|
2327
2288
|
if (isJsonMode()) {
|
|
2328
2289
|
printJson({ valid, errors, warnings });
|
|
@@ -2395,15 +2356,6 @@ var contextCommand = new Command9("context").alias("ctx").description("Manage co
|
|
|
2395
2356
|
// src/commands/status.ts
|
|
2396
2357
|
import { Command as Command10 } from "commander";
|
|
2397
2358
|
import chalk9 from "chalk";
|
|
2398
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
2399
|
-
import { join as join11 } from "path";
|
|
2400
|
-
function readPorts() {
|
|
2401
|
-
try {
|
|
2402
|
-
return JSON.parse(readFileSync7(join11(OS_DIR, "ports.json"), "utf8"));
|
|
2403
|
-
} catch {
|
|
2404
|
-
return {};
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
2359
|
var statusCommand = new Command10("status").description("Show the local Openship service (installed/running, ports) and the active context's API health").action(async () => {
|
|
2408
2360
|
const context = getActiveContext();
|
|
2409
2361
|
const apiUrl = getApiUrl2();
|
|
@@ -2423,7 +2375,7 @@ var statusCommand = new Command10("status").description("Show the local Openship
|
|
|
2423
2375
|
return;
|
|
2424
2376
|
}
|
|
2425
2377
|
const svc = serviceStatus();
|
|
2426
|
-
const ports =
|
|
2378
|
+
const ports = readStoredPorts();
|
|
2427
2379
|
let health = null;
|
|
2428
2380
|
let envInfo = null;
|
|
2429
2381
|
let reachable = true;
|
|
@@ -2455,7 +2407,7 @@ var statusCommand = new Command10("status").description("Show the local Openship
|
|
|
2455
2407
|
|
|
2456
2408
|
// src/commands/doctor.ts
|
|
2457
2409
|
import { Command as Command11 } from "commander";
|
|
2458
|
-
import { existsSync as
|
|
2410
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2459
2411
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2460
2412
|
import chalk11 from "chalk";
|
|
2461
2413
|
import open from "open";
|
|
@@ -2463,42 +2415,42 @@ import { intro, outro, note as note2, select as select3, log as log3, spinner as
|
|
|
2463
2415
|
|
|
2464
2416
|
// src/lib/repair.ts
|
|
2465
2417
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2466
|
-
import { existsSync as
|
|
2467
|
-
import { join as
|
|
2418
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
|
|
2419
|
+
import { join as join11 } from "path";
|
|
2468
2420
|
import chalk10 from "chalk";
|
|
2469
2421
|
import { spinner, log as log2, select as select2, isCancel as isCancel3, cancel } from "@clack/prompts";
|
|
2470
2422
|
|
|
2471
2423
|
// src/lib/heal.ts
|
|
2472
2424
|
import {
|
|
2473
|
-
existsSync as
|
|
2425
|
+
existsSync as existsSync10,
|
|
2474
2426
|
readdirSync,
|
|
2475
2427
|
statSync,
|
|
2476
2428
|
unlinkSync,
|
|
2477
|
-
mkdirSync as
|
|
2429
|
+
mkdirSync as mkdirSync9,
|
|
2478
2430
|
copyFileSync,
|
|
2479
2431
|
renameSync
|
|
2480
2432
|
} from "fs";
|
|
2481
2433
|
import { homedir as homedir2 } from "os";
|
|
2482
|
-
import { join as
|
|
2434
|
+
import { join as join10 } from "path";
|
|
2483
2435
|
import { execFileSync } from "child_process";
|
|
2484
2436
|
function resolveDataDir() {
|
|
2485
2437
|
const fromEnv = process.env.PGLITE_DATA_DIR?.trim();
|
|
2486
2438
|
if (fromEnv) {
|
|
2487
|
-
return fromEnv.startsWith("~") ?
|
|
2439
|
+
return fromEnv.startsWith("~") ? join10(homedir2(), fromEnv.slice(1).replace(/^[/\\]/, "")) : fromEnv;
|
|
2488
2440
|
}
|
|
2489
|
-
return
|
|
2441
|
+
return join10(OS_DIR, "data");
|
|
2490
2442
|
}
|
|
2491
2443
|
function dataDirExists(dataDir = resolveDataDir()) {
|
|
2492
|
-
return
|
|
2444
|
+
return existsSync10(dataDir);
|
|
2493
2445
|
}
|
|
2494
2446
|
function stamp() {
|
|
2495
2447
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2496
2448
|
}
|
|
2497
2449
|
function copyDirRecursive(src, dest) {
|
|
2498
|
-
if (!
|
|
2450
|
+
if (!existsSync10(dest)) mkdirSync9(dest, { recursive: true });
|
|
2499
2451
|
for (const entry of readdirSync(src)) {
|
|
2500
|
-
const s =
|
|
2501
|
-
const d =
|
|
2452
|
+
const s = join10(src, entry);
|
|
2453
|
+
const d = join10(dest, entry);
|
|
2502
2454
|
if (statSync(s).isDirectory()) copyDirRecursive(s, d);
|
|
2503
2455
|
else copyFileSync(s, d);
|
|
2504
2456
|
}
|
|
@@ -2510,30 +2462,30 @@ function backupDataDir(dataDir = resolveDataDir()) {
|
|
|
2510
2462
|
}
|
|
2511
2463
|
function healDataDir(dataDir = resolveDataDir()) {
|
|
2512
2464
|
const result = { removedPostmasterPid: false, trimmedWalSegment: null };
|
|
2513
|
-
const pidPath =
|
|
2514
|
-
if (
|
|
2465
|
+
const pidPath = join10(dataDir, "postmaster.pid");
|
|
2466
|
+
if (existsSync10(pidPath)) {
|
|
2515
2467
|
unlinkSync(pidPath);
|
|
2516
2468
|
result.removedPostmasterPid = true;
|
|
2517
2469
|
}
|
|
2518
|
-
const walDir =
|
|
2519
|
-
if (
|
|
2520
|
-
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);
|
|
2521
2473
|
if (segments.length > 0) {
|
|
2522
2474
|
const newest = segments[0];
|
|
2523
|
-
unlinkSync(
|
|
2475
|
+
unlinkSync(join10(walDir, newest.name));
|
|
2524
2476
|
result.trimmedWalSegment = newest.name;
|
|
2525
2477
|
}
|
|
2526
2478
|
}
|
|
2527
2479
|
return result;
|
|
2528
2480
|
}
|
|
2529
2481
|
function restoreBackup(backupDir, dataDir = resolveDataDir()) {
|
|
2530
|
-
if (!
|
|
2531
|
-
if (
|
|
2482
|
+
if (!existsSync10(backupDir)) throw new Error(`Backup not found: ${backupDir}`);
|
|
2483
|
+
if (existsSync10(dataDir)) renameSync(dataDir, `${dataDir}.discarded-${stamp()}`);
|
|
2532
2484
|
copyDirRecursive(backupDir, dataDir);
|
|
2533
2485
|
}
|
|
2534
2486
|
function freshStart(dataDir = resolveDataDir()) {
|
|
2535
2487
|
const aside = `${dataDir}.corrupt-${stamp()}`;
|
|
2536
|
-
if (
|
|
2488
|
+
if (existsSync10(dataDir)) renameSync(dataDir, aside);
|
|
2537
2489
|
return aside;
|
|
2538
2490
|
}
|
|
2539
2491
|
function findResetwal() {
|
|
@@ -2545,7 +2497,7 @@ function findResetwal() {
|
|
|
2545
2497
|
"/usr/lib/postgresql/16/bin/pg_resetwal",
|
|
2546
2498
|
"/usr/bin/pg_resetwal"
|
|
2547
2499
|
];
|
|
2548
|
-
for (const c of candidates) if (
|
|
2500
|
+
for (const c of candidates) if (existsSync10(c)) return c;
|
|
2549
2501
|
try {
|
|
2550
2502
|
const found = execFileSync("bash", ["-lc", "command -v pg_resetwal"], {
|
|
2551
2503
|
encoding: "utf8",
|
|
@@ -2566,7 +2518,7 @@ function deepHeal(dataDir = resolveDataDir()) {
|
|
|
2566
2518
|
}
|
|
2567
2519
|
|
|
2568
2520
|
// src/lib/repair.ts
|
|
2569
|
-
var LOG_DIR2 =
|
|
2521
|
+
var LOG_DIR2 = join11(OS_DIR, "logs");
|
|
2570
2522
|
function ensure(value) {
|
|
2571
2523
|
if (isCancel3(value)) {
|
|
2572
2524
|
cancel("Cancelled.");
|
|
@@ -2574,23 +2526,9 @@ function ensure(value) {
|
|
|
2574
2526
|
}
|
|
2575
2527
|
return value;
|
|
2576
2528
|
}
|
|
2577
|
-
function storedPorts() {
|
|
2578
|
-
try {
|
|
2579
|
-
const p = join13(OS_DIR, "ports.json");
|
|
2580
|
-
return existsSync12(p) ? JSON.parse(readFileSync8(p, "utf8")) : {};
|
|
2581
|
-
} catch {
|
|
2582
|
-
return {};
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
function apiPort() {
|
|
2586
|
-
return storedPorts().api ?? 4e3;
|
|
2587
|
-
}
|
|
2588
|
-
function dashboardPort() {
|
|
2589
|
-
return storedPorts().dashboard ?? 3001;
|
|
2590
|
-
}
|
|
2591
2529
|
async function internalGet2(path2, timeoutMs = 8e3) {
|
|
2592
2530
|
try {
|
|
2593
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
2531
|
+
const res = await fetch(`http://127.0.0.1:${storedApiPort()}${path2}`, {
|
|
2594
2532
|
headers: { "X-Internal-Token": ensureInternalToken() },
|
|
2595
2533
|
signal: AbortSignal.timeout(timeoutMs)
|
|
2596
2534
|
});
|
|
@@ -2602,7 +2540,7 @@ async function internalGet2(path2, timeoutMs = 8e3) {
|
|
|
2602
2540
|
}
|
|
2603
2541
|
async function apiReachable() {
|
|
2604
2542
|
try {
|
|
2605
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
2543
|
+
const res = await fetch(`http://127.0.0.1:${storedApiPort()}/api/health`, {
|
|
2606
2544
|
signal: AbortSignal.timeout(2500)
|
|
2607
2545
|
});
|
|
2608
2546
|
return res.ok;
|
|
@@ -2626,10 +2564,10 @@ async function waitApiDown(seconds = 20) {
|
|
|
2626
2564
|
}
|
|
2627
2565
|
function lastServiceError() {
|
|
2628
2566
|
for (const name of ["up.err.log", "up.log", "instance.log"]) {
|
|
2629
|
-
const p =
|
|
2630
|
-
if (!
|
|
2567
|
+
const p = join11(LOG_DIR2, name);
|
|
2568
|
+
if (!existsSync11(p)) continue;
|
|
2631
2569
|
try {
|
|
2632
|
-
const lines =
|
|
2570
|
+
const lines = readFileSync6(p, "utf8").trim().split("\n");
|
|
2633
2571
|
const hit = [...lines].reverse().find((l) => /error|locked|aborted|malformed|corrupt|throw|cannot|EADDRINUSE/i.test(l));
|
|
2634
2572
|
if (hit) return hit.trim().slice(0, 240);
|
|
2635
2573
|
} catch {
|
|
@@ -2687,11 +2625,11 @@ async function componentChecks(apiUp) {
|
|
|
2687
2625
|
checks.push({
|
|
2688
2626
|
name: "API",
|
|
2689
2627
|
state: apiUp ? "pass" : "fail",
|
|
2690
|
-
detail: apiUp ? `reachable on :${
|
|
2628
|
+
detail: apiUp ? `reachable on :${storedApiPort()}` : `not answering on :${storedApiPort()}`
|
|
2691
2629
|
});
|
|
2692
2630
|
let dashUp = false;
|
|
2693
2631
|
try {
|
|
2694
|
-
const res = await fetch(`http://127.0.0.1:${
|
|
2632
|
+
const res = await fetch(`http://127.0.0.1:${storedDashboardPort()}/`, {
|
|
2695
2633
|
redirect: "manual",
|
|
2696
2634
|
signal: AbortSignal.timeout(2500)
|
|
2697
2635
|
});
|
|
@@ -2701,7 +2639,7 @@ async function componentChecks(apiUp) {
|
|
|
2701
2639
|
checks.push({
|
|
2702
2640
|
name: "Dashboard",
|
|
2703
2641
|
state: dashUp ? "pass" : "warn",
|
|
2704
|
-
detail: dashUp ? `serving on :${
|
|
2642
|
+
detail: dashUp ? `serving on :${storedDashboardPort()}` : `not serving on :${storedDashboardPort()}`
|
|
2705
2643
|
});
|
|
2706
2644
|
const edge = dockerNameRunning("openship-edge");
|
|
2707
2645
|
checks.push(
|
|
@@ -2888,7 +2826,7 @@ function bunVersion() {
|
|
|
2888
2826
|
}
|
|
2889
2827
|
async function remotePreflight() {
|
|
2890
2828
|
const checks = [];
|
|
2891
|
-
const hasConfig =
|
|
2829
|
+
const hasConfig = existsSync12(CONFIG_PATH);
|
|
2892
2830
|
checks.push({ name: "config", status: hasConfig ? "pass" : "warn", detail: hasConfig ? CONFIG_PATH : `not found; run \`openship login\`` });
|
|
2893
2831
|
const context = getActiveContext();
|
|
2894
2832
|
const apiUrl = getApiUrl();
|
|
@@ -3026,7 +2964,7 @@ async function interactiveDoctor() {
|
|
|
3026
2964
|
}
|
|
3027
2965
|
if (action2 === "open") {
|
|
3028
2966
|
const url = readInstanceUrl();
|
|
3029
|
-
const target = url && !/^https?:\/\/localhost/i.test(url) ? url : `http://localhost:${
|
|
2967
|
+
const target = url && !/^https?:\/\/localhost/i.test(url) ? url : `http://localhost:${storedDashboardPort()}`;
|
|
3030
2968
|
await open(target).catch(() => {
|
|
3031
2969
|
});
|
|
3032
2970
|
log3.message(chalk11.dim(`Opening ${target}`));
|
|
@@ -3056,15 +2994,15 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
3056
2994
|
import ora2 from "ora";
|
|
3057
2995
|
|
|
3058
2996
|
// src/lib/project-link.ts
|
|
3059
|
-
import { readFileSync as
|
|
3060
|
-
import { join as
|
|
3061
|
-
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");
|
|
3062
3000
|
function findProjectLinkPath(from = process.cwd()) {
|
|
3063
3001
|
let dir = from;
|
|
3064
3002
|
const root = parse(dir).root;
|
|
3065
3003
|
for (; ; ) {
|
|
3066
|
-
const candidate =
|
|
3067
|
-
if (
|
|
3004
|
+
const candidate = join12(dir, LINK_REL);
|
|
3005
|
+
if (existsSync13(candidate)) return candidate;
|
|
3068
3006
|
if (dir === root) return null;
|
|
3069
3007
|
dir = dirname3(dir);
|
|
3070
3008
|
}
|
|
@@ -3073,7 +3011,7 @@ function readProjectLink(from) {
|
|
|
3073
3011
|
const path2 = findProjectLinkPath(from);
|
|
3074
3012
|
if (!path2) return null;
|
|
3075
3013
|
try {
|
|
3076
|
-
return JSON.parse(
|
|
3014
|
+
return JSON.parse(readFileSync7(path2, "utf8"));
|
|
3077
3015
|
} catch {
|
|
3078
3016
|
return null;
|
|
3079
3017
|
}
|
|
@@ -3081,21 +3019,21 @@ function readProjectLink(from) {
|
|
|
3081
3019
|
|
|
3082
3020
|
// src/lib/folder-deploy.ts
|
|
3083
3021
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3084
|
-
import { readFileSync as
|
|
3022
|
+
import { readFileSync as readFileSync8, existsSync as existsSync14, rmSync as rmSync4 } from "fs";
|
|
3085
3023
|
import { tmpdir } from "os";
|
|
3086
|
-
import { join as
|
|
3024
|
+
import { join as join13, basename } from "path";
|
|
3087
3025
|
function detectPackageManager(dir) {
|
|
3088
|
-
if (
|
|
3089
|
-
if (
|
|
3090
|
-
if (
|
|
3091
|
-
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";
|
|
3092
3030
|
return void 0;
|
|
3093
3031
|
}
|
|
3094
3032
|
function detectStack(dir) {
|
|
3095
|
-
if (
|
|
3096
|
-
if (
|
|
3097
|
-
if (
|
|
3098
|
-
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";
|
|
3099
3037
|
return void 0;
|
|
3100
3038
|
}
|
|
3101
3039
|
async function deployFolder(opts) {
|
|
@@ -3112,7 +3050,7 @@ async function deployFolder(opts) {
|
|
|
3112
3050
|
throw new Error(session.error || "Failed to open upload session");
|
|
3113
3051
|
}
|
|
3114
3052
|
step("Packaging folder");
|
|
3115
|
-
const tarball =
|
|
3053
|
+
const tarball = join13(tmpdir(), `openship-upload-${session.sessionId}.tar.gz`);
|
|
3116
3054
|
execFileSync3(
|
|
3117
3055
|
"tar",
|
|
3118
3056
|
[
|
|
@@ -3131,7 +3069,7 @@ async function deployFolder(opts) {
|
|
|
3131
3069
|
);
|
|
3132
3070
|
step("Uploading source");
|
|
3133
3071
|
try {
|
|
3134
|
-
const body =
|
|
3072
|
+
const body = readFileSync8(tarball);
|
|
3135
3073
|
const up = session.upload;
|
|
3136
3074
|
const method = up.method || "POST";
|
|
3137
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 });
|
|
@@ -3585,12 +3523,19 @@ var sslStatus = new Command13("status").description("Check SSL certificate statu
|
|
|
3585
3523
|
printJson(res);
|
|
3586
3524
|
})
|
|
3587
3525
|
);
|
|
3588
|
-
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(
|
|
3589
3527
|
run2(async (domain, opts) => {
|
|
3590
3528
|
const res = await apiRequest("/deployments/ssl/renew", {
|
|
3591
3529
|
method: "POST",
|
|
3592
3530
|
body: JSON.stringify({ domain, includeWww: opts.www === true })
|
|
3593
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
|
+
}
|
|
3594
3539
|
report(res, `SSL renewal requested for ${domain}`);
|
|
3595
3540
|
})
|
|
3596
3541
|
);
|
|
@@ -4370,8 +4315,10 @@ function mapComposeService(name, def, baseDir) {
|
|
|
4370
4315
|
const volumes = mapVolumes(d.volumes);
|
|
4371
4316
|
if (volumes.length) svc.volumes = volumes;
|
|
4372
4317
|
const command = d.command;
|
|
4373
|
-
if (
|
|
4374
|
-
|
|
4318
|
+
if (command != null) {
|
|
4319
|
+
svc.commandArgv = commandToArgv(command);
|
|
4320
|
+
svc.command = typeof command === "string" ? command : command.map(String).join(" ");
|
|
4321
|
+
}
|
|
4375
4322
|
if (typeof d.restart === "string") svc.restart = d.restart;
|
|
4376
4323
|
return svc;
|
|
4377
4324
|
}
|
|
@@ -5137,7 +5084,7 @@ var serverCommand = server;
|
|
|
5137
5084
|
// src/commands/system.ts
|
|
5138
5085
|
import { Command as Command19 } from "commander";
|
|
5139
5086
|
import ora5 from "ora";
|
|
5140
|
-
import { readFileSync as
|
|
5087
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
5141
5088
|
import { createInterface as createInterface6 } from "readline/promises";
|
|
5142
5089
|
import { stdin as input5, stdout as output5 } from "process";
|
|
5143
5090
|
async function guarded(fn) {
|
|
@@ -5437,7 +5384,7 @@ dataTransferCommand.command("export").description("Export the entire instance to
|
|
|
5437
5384
|
);
|
|
5438
5385
|
spin4?.succeed("Export ready.");
|
|
5439
5386
|
if (opts.out) {
|
|
5440
|
-
|
|
5387
|
+
writeFileSync9(opts.out, JSON.stringify(file));
|
|
5441
5388
|
const tables = Object.keys(file.dump?.tables ?? {}).length;
|
|
5442
5389
|
report2(
|
|
5443
5390
|
{ out: opts.out, tables },
|
|
@@ -5463,7 +5410,7 @@ dataTransferCommand.command("import").description("Import an instance export fil
|
|
|
5463
5410
|
}
|
|
5464
5411
|
let file;
|
|
5465
5412
|
try {
|
|
5466
|
-
file = JSON.parse(
|
|
5413
|
+
file = JSON.parse(readFileSync9(opts.file, "utf8"));
|
|
5467
5414
|
} catch {
|
|
5468
5415
|
err(`
|
|
5469
5416
|
Could not read or parse ${opts.file}.
|
|
@@ -5814,7 +5761,7 @@ var mailCommand = new Command20("mail").description("Self-hosted mail server (iR
|
|
|
5814
5761
|
// src/commands/backup.ts
|
|
5815
5762
|
import { Command as Command21 } from "commander";
|
|
5816
5763
|
import ora7 from "ora";
|
|
5817
|
-
import { readFileSync as
|
|
5764
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
5818
5765
|
async function guard3(fn) {
|
|
5819
5766
|
try {
|
|
5820
5767
|
await fn();
|
|
@@ -6107,7 +6054,7 @@ destinationCmd.command("create").description("Create a backup destination").requ
|
|
|
6107
6054
|
let sftpPrivateKey = opts.sftpPrivateKey;
|
|
6108
6055
|
if (opts.sftpPrivateKeyFile) {
|
|
6109
6056
|
try {
|
|
6110
|
-
sftpPrivateKey =
|
|
6057
|
+
sftpPrivateKey = readFileSync10(opts.sftpPrivateKeyFile, "utf8");
|
|
6111
6058
|
} catch {
|
|
6112
6059
|
throw new Error(`Cannot read key file: ${opts.sftpPrivateKeyFile}`);
|
|
6113
6060
|
}
|
|
@@ -6219,13 +6166,28 @@ var createCmd3 = new Command22("create").description("Mint a new personal access
|
|
|
6219
6166
|
"Scope the token to a resource (repeatable), e.g. project:abc123:read,write",
|
|
6220
6167
|
collectGrant,
|
|
6221
6168
|
[]
|
|
6169
|
+
).option(
|
|
6170
|
+
"--full-access",
|
|
6171
|
+
"Mint an UNSCOPED token with your own full access (required when no --grant is given)",
|
|
6172
|
+
false
|
|
6222
6173
|
).action(async (name, opts) => {
|
|
6223
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
|
+
}
|
|
6224
6185
|
const body = {
|
|
6225
6186
|
name,
|
|
6226
6187
|
readOnly: opts.readOnly || void 0,
|
|
6227
6188
|
expiresInDays: Number.isFinite(opts.expires) ? opts.expires : void 0,
|
|
6228
|
-
grants: grants.length > 0 ? grants : void 0
|
|
6189
|
+
grants: grants.length > 0 ? grants : void 0,
|
|
6190
|
+
fullAccess: grants.length === 0 ? true : void 0
|
|
6229
6191
|
};
|
|
6230
6192
|
const sp = spin3(`Creating token "${name}"\u2026`);
|
|
6231
6193
|
try {
|
|
@@ -6299,16 +6261,7 @@ var apiCommand = new Command23("api").description("Make an authenticated request
|
|
|
6299
6261
|
import { Command as Command24 } from "commander";
|
|
6300
6262
|
import chalk19 from "chalk";
|
|
6301
6263
|
import { intro as intro2, outro as outro2, password as passwordPrompt, isCancel as isCancel4, cancel as cancel3, log as log4 } from "@clack/prompts";
|
|
6302
|
-
|
|
6303
|
-
import { join as join16 } from "path";
|
|
6304
|
-
function resolvedApiPort() {
|
|
6305
|
-
try {
|
|
6306
|
-
return JSON.parse(readFileSync13(join16(OS_DIR, "ports.json"), "utf8")).api;
|
|
6307
|
-
} catch {
|
|
6308
|
-
return void 0;
|
|
6309
|
-
}
|
|
6310
|
-
}
|
|
6311
|
-
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) => {
|
|
6312
6265
|
intro2(chalk19.cyan("Reset Openship admin password"));
|
|
6313
6266
|
let pw = opts.password;
|
|
6314
6267
|
if (!pw) {
|
|
@@ -6334,7 +6287,7 @@ var resetAdminCommand = new Command24("reset-admin-password").description("Reset
|
|
|
6334
6287
|
}
|
|
6335
6288
|
pw = entered;
|
|
6336
6289
|
}
|
|
6337
|
-
const port = String(opts.port ||
|
|
6290
|
+
const port = String(opts.port || storedApiPort());
|
|
6338
6291
|
let res;
|
|
6339
6292
|
try {
|
|
6340
6293
|
res = await fetch(`http://127.0.0.1:${port}/api/system/reset-admin-password`, {
|
|
@@ -6357,10 +6310,10 @@ var resetAdminCommand = new Command24("reset-admin-password").description("Reset
|
|
|
6357
6310
|
|
|
6358
6311
|
// src/commands/install.ts
|
|
6359
6312
|
import { Command as Command25 } from "commander";
|
|
6360
|
-
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";
|
|
6361
6314
|
import { spawn as spawn2, spawnSync as spawnSync6 } from "child_process";
|
|
6362
6315
|
import { homedir as homedir3 } from "os";
|
|
6363
|
-
import { join as
|
|
6316
|
+
import { join as join14 } from "path";
|
|
6364
6317
|
import ora9 from "ora";
|
|
6365
6318
|
function assetForPlatform() {
|
|
6366
6319
|
const { platform, arch } = process;
|
|
@@ -6374,10 +6327,10 @@ function assetForPlatform() {
|
|
|
6374
6327
|
throw new Error(`Unsupported platform: ${platform} (${arch})`);
|
|
6375
6328
|
}
|
|
6376
6329
|
function installDmg(dmg) {
|
|
6377
|
-
const homeApps =
|
|
6330
|
+
const homeApps = join14(homedir3(), "Applications");
|
|
6378
6331
|
let dest = homeApps;
|
|
6379
6332
|
try {
|
|
6380
|
-
|
|
6333
|
+
mkdirSync10(homeApps, { recursive: true });
|
|
6381
6334
|
} catch {
|
|
6382
6335
|
dest = "/Applications";
|
|
6383
6336
|
}
|
|
@@ -6387,14 +6340,14 @@ function installDmg(dmg) {
|
|
|
6387
6340
|
if (attach.status !== 0) throw new Error(`hdiutil attach failed: ${attach.stderr?.trim()}`);
|
|
6388
6341
|
const mount = (attach.stdout.match(/\/Volumes\/[^\n]*/g) ?? []).pop()?.trim();
|
|
6389
6342
|
if (!mount) throw new Error("Could not determine the mounted volume");
|
|
6390
|
-
let target =
|
|
6343
|
+
let target = join14(dest, "Openship.app");
|
|
6391
6344
|
try {
|
|
6392
|
-
const appInDmg =
|
|
6393
|
-
if (!
|
|
6345
|
+
const appInDmg = join14(mount, "Openship.app");
|
|
6346
|
+
if (!existsSync15(appInDmg)) throw new Error("Openship.app not found in the disk image");
|
|
6394
6347
|
spawnSync6("rm", ["-rf", target]);
|
|
6395
6348
|
let copy = spawnSync6("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
6396
6349
|
if (copy.status !== 0 && dest === homeApps) {
|
|
6397
|
-
target =
|
|
6350
|
+
target = join14("/Applications", "Openship.app");
|
|
6398
6351
|
spawnSync6("rm", ["-rf", target]);
|
|
6399
6352
|
copy = spawnSync6("ditto", [appInDmg, target], { encoding: "utf8" });
|
|
6400
6353
|
}
|
|
@@ -6409,9 +6362,9 @@ function installAppImage(appImage) {
|
|
|
6409
6362
|
return appImage;
|
|
6410
6363
|
}
|
|
6411
6364
|
function installZip(zip) {
|
|
6412
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
6413
|
-
const target =
|
|
6414
|
-
|
|
6365
|
+
const localAppData = process.env.LOCALAPPDATA || join14(homedir3(), "AppData", "Local");
|
|
6366
|
+
const target = join14(localAppData, "Programs", "Openship");
|
|
6367
|
+
mkdirSync10(target, { recursive: true });
|
|
6415
6368
|
const expand = spawnSync6(
|
|
6416
6369
|
"powershell",
|
|
6417
6370
|
[
|
|
@@ -6438,8 +6391,8 @@ function launch(kind, target) {
|
|
|
6438
6391
|
child.unref();
|
|
6439
6392
|
return;
|
|
6440
6393
|
}
|
|
6441
|
-
const exe =
|
|
6442
|
-
const path2 =
|
|
6394
|
+
const exe = join14(target, "Openship.exe");
|
|
6395
|
+
const path2 = existsSync15(exe) ? exe : target;
|
|
6443
6396
|
spawnSync6("cmd", ["/c", "start", "", path2]);
|
|
6444
6397
|
}
|
|
6445
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) => {
|
|
@@ -6465,13 +6418,13 @@ var installCommand = new Command25("install").description("Download and install
|
|
|
6465
6418
|
process.exit(1);
|
|
6466
6419
|
}
|
|
6467
6420
|
const dir = releaseDir(tag);
|
|
6468
|
-
const assetPath =
|
|
6421
|
+
const assetPath = join14(dir, asset.name);
|
|
6469
6422
|
const sidecarPath = `${assetPath}.sha256`;
|
|
6470
6423
|
const assetUrl2 = `${RELEASES}/download/${tag}/${asset.name}`;
|
|
6471
6424
|
const sidecarUrl = `${assetUrl2}.sha256`;
|
|
6472
6425
|
let downloaded = false;
|
|
6473
6426
|
let sha;
|
|
6474
|
-
const cachedUsable = !opts.force &&
|
|
6427
|
+
const cachedUsable = !opts.force && existsSync15(assetPath) && (existsSync15(sidecarPath) || opts.verify === false);
|
|
6475
6428
|
if (cachedUsable) {
|
|
6476
6429
|
info(` Using cached ${asset.name} (${tag}).`);
|
|
6477
6430
|
} else {
|
|
@@ -6493,8 +6446,8 @@ var installCommand = new Command25("install").description("Download and install
|
|
|
6493
6446
|
const s2 = spin4("Verifying checksum\u2026");
|
|
6494
6447
|
try {
|
|
6495
6448
|
let sidecarBody;
|
|
6496
|
-
if (
|
|
6497
|
-
sidecarBody =
|
|
6449
|
+
if (existsSync15(sidecarPath) && !downloaded) {
|
|
6450
|
+
sidecarBody = readFileSync11(sidecarPath, "utf8");
|
|
6498
6451
|
} else {
|
|
6499
6452
|
sidecarBody = await fetchSidecar(sidecarUrl);
|
|
6500
6453
|
}
|
|
@@ -6517,8 +6470,8 @@ var installCommand = new Command25("install").description("Download and install
|
|
|
6517
6470
|
err(`Expected ${expected}, got ${actual}. The download may be corrupt or tampered with.`);
|
|
6518
6471
|
process.exit(1);
|
|
6519
6472
|
}
|
|
6520
|
-
|
|
6521
|
-
|
|
6473
|
+
mkdirSync10(dir, { recursive: true });
|
|
6474
|
+
writeFileSync10(sidecarPath, sidecarBody);
|
|
6522
6475
|
s2?.succeed("Checksum verified");
|
|
6523
6476
|
} catch (e) {
|
|
6524
6477
|
s2?.fail("Verification failed");
|
|
@@ -6605,7 +6558,7 @@ function detectPackageManager2(override) {
|
|
|
6605
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) => {
|
|
6606
6559
|
const source = readSourceInstall();
|
|
6607
6560
|
if (source) return runSourceUpdate(source, opts);
|
|
6608
|
-
const current = "0.
|
|
6561
|
+
const current = "0.5.0";
|
|
6609
6562
|
let latest;
|
|
6610
6563
|
try {
|
|
6611
6564
|
latest = (await resolveLatestTag()).replace(/^v/, "");
|
|
@@ -6640,7 +6593,7 @@ var updateCommand = new Command26("update").description("Update the Openship CLI
|
|
|
6640
6593
|
return;
|
|
6641
6594
|
}
|
|
6642
6595
|
if (readInstallMethod() === "compose") {
|
|
6643
|
-
const applied = composeUpdate(latest);
|
|
6596
|
+
const applied = await composeUpdate(latest);
|
|
6644
6597
|
if (isJsonMode()) {
|
|
6645
6598
|
printJson({ updated: true, from: current, to: latest, via: pm, method: "compose", applied });
|
|
6646
6599
|
} else if (applied) {
|
|
@@ -6663,17 +6616,17 @@ var updateCommand = new Command26("update").description("Update the Openship CLI
|
|
|
6663
6616
|
|
|
6664
6617
|
// src/commands/cache.ts
|
|
6665
6618
|
import { Command as Command27 } from "commander";
|
|
6666
|
-
import { existsSync as
|
|
6667
|
-
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";
|
|
6668
6621
|
function listAssets() {
|
|
6669
|
-
if (!
|
|
6622
|
+
if (!existsSync16(RELEASES_DIR)) return [];
|
|
6670
6623
|
const out = [];
|
|
6671
6624
|
for (const tag of readdirSync2(RELEASES_DIR)) {
|
|
6672
6625
|
const dir = releaseDir(tag);
|
|
6673
6626
|
if (!statSync2(dir).isDirectory()) continue;
|
|
6674
6627
|
for (const name of readdirSync2(dir)) {
|
|
6675
6628
|
if (name.endsWith(".sha256")) continue;
|
|
6676
|
-
const path2 =
|
|
6629
|
+
const path2 = join15(dir, name);
|
|
6677
6630
|
const st = statSync2(path2);
|
|
6678
6631
|
if (!st.isFile()) continue;
|
|
6679
6632
|
out.push({
|
|
@@ -6681,7 +6634,7 @@ function listAssets() {
|
|
|
6681
6634
|
name,
|
|
6682
6635
|
path: path2,
|
|
6683
6636
|
size: st.size,
|
|
6684
|
-
hasSidecar:
|
|
6637
|
+
hasSidecar: existsSync16(`${path2}.sha256`)
|
|
6685
6638
|
});
|
|
6686
6639
|
}
|
|
6687
6640
|
}
|
|
@@ -6712,7 +6665,7 @@ var verifyCmd2 = new Command27("verify").description("Re-hash cached assets and
|
|
|
6712
6665
|
results.push({ tag: a.tag, asset: a.name, result: "no-sidecar" });
|
|
6713
6666
|
continue;
|
|
6714
6667
|
}
|
|
6715
|
-
const expected = parseSha256(
|
|
6668
|
+
const expected = parseSha256(readFileSync12(`${a.path}.sha256`, "utf8"));
|
|
6716
6669
|
const actual = await hashFile(a.path);
|
|
6717
6670
|
const okMatch = expected !== null && expected === actual;
|
|
6718
6671
|
if (!okMatch) bad += 1;
|
|
@@ -6728,7 +6681,7 @@ var verifyCmd2 = new Command27("verify").description("Re-hash cached assets and
|
|
|
6728
6681
|
});
|
|
6729
6682
|
var cleanCmd = new Command27("clean").description("Delete cached release assets").argument("[tag]", "Only remove this release tag (default: all)").action((tag) => {
|
|
6730
6683
|
const target = tag ? releaseDir(tag) : RELEASES_DIR;
|
|
6731
|
-
if (!
|
|
6684
|
+
if (!existsSync16(target)) {
|
|
6732
6685
|
if (isJsonMode()) printJson({ removed: false, path: target });
|
|
6733
6686
|
else info(` Nothing to clean (${target}).`);
|
|
6734
6687
|
return;
|
|
@@ -6748,8 +6701,8 @@ var cacheCommand = new Command27("cache").description("Manage the local download
|
|
|
6748
6701
|
import chalk20 from "chalk";
|
|
6749
6702
|
import open2 from "open";
|
|
6750
6703
|
import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
|
|
6751
|
-
import { existsSync as
|
|
6752
|
-
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";
|
|
6753
6706
|
import {
|
|
6754
6707
|
intro as intro3,
|
|
6755
6708
|
outro as outro3,
|
|
@@ -6895,13 +6848,13 @@ async function streamProvision(port, sessionId, s) {
|
|
|
6895
6848
|
}
|
|
6896
6849
|
return { ok: ok3, detail };
|
|
6897
6850
|
}
|
|
6898
|
-
var SETUP_LOCK =
|
|
6851
|
+
var SETUP_LOCK = join16(OS_DIR, "setup-in-progress");
|
|
6899
6852
|
function isSetupInProgress() {
|
|
6900
|
-
return
|
|
6853
|
+
return existsSync17(SETUP_LOCK);
|
|
6901
6854
|
}
|
|
6902
6855
|
function markSetupStarted() {
|
|
6903
|
-
|
|
6904
|
-
|
|
6856
|
+
mkdirSync11(OS_DIR, { recursive: true });
|
|
6857
|
+
writeFileSync11(SETUP_LOCK, "1");
|
|
6905
6858
|
}
|
|
6906
6859
|
function markSetupDone() {
|
|
6907
6860
|
rmSync6(SETUP_LOCK, { force: true });
|
|
@@ -7162,17 +7115,22 @@ async function runWizard() {
|
|
|
7162
7115
|
}
|
|
7163
7116
|
let method = "bare";
|
|
7164
7117
|
if (process.platform === "linux") {
|
|
7165
|
-
|
|
7118
|
+
const gap = dockerGap();
|
|
7119
|
+
if (!gap) {
|
|
7166
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";
|
|
7167
7125
|
} else {
|
|
7168
|
-
log5.step(
|
|
7169
|
-
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";
|
|
7170
7128
|
if (method === "bare") {
|
|
7171
7129
|
log5.warn("Couldn't install Docker automatically \u2014 falling back to the bare process service.");
|
|
7172
7130
|
}
|
|
7173
7131
|
}
|
|
7174
7132
|
}
|
|
7175
|
-
const uiTag = `v${"0.
|
|
7133
|
+
const uiTag = `v${"0.5.0"}`;
|
|
7176
7134
|
if (method === "bare") {
|
|
7177
7135
|
const dl = spinner4();
|
|
7178
7136
|
dl.start("Pulling the Openship dist from GitHub");
|
|
@@ -7226,6 +7184,25 @@ async function runWizard() {
|
|
|
7226
7184
|
}
|
|
7227
7185
|
}
|
|
7228
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"));
|
|
7192
|
+
log5.step(
|
|
7193
|
+
sourceBuildDir() ? "Building the Openship images before touching :80/:443 (first run takes a few minutes)\u2026" : "Pulling images before touching :80/:443\u2026"
|
|
7194
|
+
);
|
|
7195
|
+
if (!composePrefetch({
|
|
7196
|
+
apiPort,
|
|
7197
|
+
dashboardPort,
|
|
7198
|
+
publicUrl,
|
|
7199
|
+
trustProxy: behindProxy,
|
|
7200
|
+
version: "0.5.0",
|
|
7201
|
+
noHostControl: !allowHostControl
|
|
7202
|
+
})) {
|
|
7203
|
+
cancel4("Couldn't fetch the Openship images \u2014 nothing on this box was changed.");
|
|
7204
|
+
process.exit(1);
|
|
7205
|
+
}
|
|
7229
7206
|
const edgePlan = await planAndApplyHostEdge({});
|
|
7230
7207
|
if (!edgePlan.proceed) {
|
|
7231
7208
|
cancel4("Left the existing proxy on 80/443 running \u2014 re-run and choose migrate/takeover when ready.");
|
|
@@ -7234,13 +7211,15 @@ async function runWizard() {
|
|
|
7234
7211
|
edgeAction = edgePlan.action;
|
|
7235
7212
|
migratedSites = edgePlan.sites;
|
|
7236
7213
|
migratedCertPems = edgePlan.certPems;
|
|
7237
|
-
log5.step(
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7214
|
+
log5.step("Starting the Docker Compose stack\u2026");
|
|
7215
|
+
const up = await composeUp({
|
|
7216
|
+
// Prefetched above, before the preflight stopped anything.
|
|
7217
|
+
alreadyFetched: true,
|
|
7218
|
+
apiPort,
|
|
7219
|
+
dashboardPort,
|
|
7241
7220
|
publicUrl,
|
|
7242
7221
|
trustProxy: behindProxy,
|
|
7243
|
-
version: "0.
|
|
7222
|
+
version: "0.5.0",
|
|
7244
7223
|
noHostControl: !allowHostControl
|
|
7245
7224
|
});
|
|
7246
7225
|
if (!up.ok) {
|
|
@@ -7471,22 +7450,14 @@ async function runWizard() {
|
|
|
7471
7450
|
byo: domainPlan.type === "byo"
|
|
7472
7451
|
});
|
|
7473
7452
|
}
|
|
7474
|
-
function storedPorts2() {
|
|
7475
|
-
const p = join19(OS_DIR, "ports.json");
|
|
7476
|
-
try {
|
|
7477
|
-
return existsSync18(p) ? JSON.parse(readFileSync16(p, "utf8")) : {};
|
|
7478
|
-
} catch {
|
|
7479
|
-
return {};
|
|
7480
|
-
}
|
|
7481
|
-
}
|
|
7482
7453
|
async function runControl() {
|
|
7483
7454
|
const svc = serviceStatus();
|
|
7484
7455
|
const isCompose = readInstallMethod() === "compose";
|
|
7485
7456
|
const running = isCompose ? composeRunning() : svc.running;
|
|
7486
7457
|
const managerLabel = isCompose ? "docker compose" : svc.kind === "unsupported" ? "none" : svc.kind;
|
|
7487
|
-
const ports =
|
|
7488
|
-
const
|
|
7489
|
-
const dashUrl = `http://localhost:${
|
|
7458
|
+
const ports = readStoredPorts();
|
|
7459
|
+
const apiPort = String(storedApiPort());
|
|
7460
|
+
const dashUrl = `http://localhost:${storedDashboardPort()}`;
|
|
7490
7461
|
const publicUrl = readInstanceUrl();
|
|
7491
7462
|
const primaryUrl = publicUrl && !/^https?:\/\/localhost/i.test(publicUrl) ? publicUrl : dashUrl;
|
|
7492
7463
|
intro3(`${chalk20.bgCyan(chalk20.black(" Openship "))}${chalk20.dim(" control")}`);
|
|
@@ -7561,7 +7532,7 @@ ${chalk20.dim("Dashboard".padEnd(11))}${dashUrl}
|
|
|
7561
7532
|
const pw = ensure2(
|
|
7562
7533
|
await password({ message: "New admin password", validate: (v) => v && v.length >= 8 ? void 0 : "At least 8 characters" })
|
|
7563
7534
|
);
|
|
7564
|
-
const rr = await internalPost(
|
|
7535
|
+
const rr = await internalPost(apiPort, "/api/system/reset-admin-password", { password: pw });
|
|
7565
7536
|
outro3(
|
|
7566
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"}`)
|
|
7567
7538
|
);
|
|
@@ -7577,7 +7548,7 @@ ${chalk20.dim("Dashboard".padEnd(11))}${dashUrl}
|
|
|
7577
7548
|
|
|
7578
7549
|
// src/index.ts
|
|
7579
7550
|
var program = new Command28();
|
|
7580
|
-
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) => {
|
|
7581
7552
|
if (thisCommand.opts().json) setJsonMode(true);
|
|
7582
7553
|
}).action(async () => {
|
|
7583
7554
|
const installed = serviceStatus().installed || readInstallMethod() === "compose";
|