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
|
@@ -101,10 +101,10 @@ import {
|
|
|
101
101
|
} from "./chunk-W73X43R7.js";
|
|
102
102
|
import {
|
|
103
103
|
CloudRuntime
|
|
104
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-JLKKYUZA.js";
|
|
105
105
|
import {
|
|
106
106
|
systemCatalog
|
|
107
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-NXFIRK2Z.js";
|
|
108
108
|
import {
|
|
109
109
|
AwsCrc32,
|
|
110
110
|
__awaiter,
|
|
@@ -139,10 +139,10 @@ import {
|
|
|
139
139
|
DockerRuntime,
|
|
140
140
|
isHostPathSource,
|
|
141
141
|
scopedVolumeName
|
|
142
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-CUJXGK7R.js";
|
|
143
143
|
import {
|
|
144
144
|
BareRuntime
|
|
145
|
-
} from "./chunk-
|
|
145
|
+
} from "./chunk-YWMODKWL.js";
|
|
146
146
|
import {
|
|
147
147
|
LocalExecutor
|
|
148
148
|
} from "./chunk-TYVME3XD.js";
|
|
@@ -151,15 +151,16 @@ import {
|
|
|
151
151
|
edgeCrashReason,
|
|
152
152
|
invalidateEdgeContainer,
|
|
153
153
|
sanitizeEdgeVhosts
|
|
154
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-6PIYRG5I.js";
|
|
155
155
|
import {
|
|
156
156
|
EDGE_CONTAINER_MOUNTS,
|
|
157
157
|
EDGE_HOST_STATE_DIR
|
|
158
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-EAAGSQXN.js";
|
|
159
159
|
import {
|
|
160
160
|
DEFAULT_IMAGE_REGISTRY,
|
|
161
|
+
isAwsS3Endpoint,
|
|
161
162
|
safeErrorMessage
|
|
162
|
-
} from "./chunk-
|
|
163
|
+
} from "./chunk-UMQP7SYT.js";
|
|
163
164
|
|
|
164
165
|
// src/lib/edge-import.ts
|
|
165
166
|
import chalk from "chalk";
|
|
@@ -691,7 +692,15 @@ var DockerBackupExecutor = class {
|
|
|
691
692
|
await this.ensureImage(helperImage);
|
|
692
693
|
const hostConfig = {
|
|
693
694
|
Binds: [`${source.source}:/mnt:ro`],
|
|
694
|
-
AutoRemove: true
|
|
695
|
+
AutoRemove: true,
|
|
696
|
+
// The archive streams over `attach()` below — the daemon's default
|
|
697
|
+
// json-file driver would ALSO write every byte of it to
|
|
698
|
+
// /var/lib/docker/containers as a log. That's a second copy of the whole
|
|
699
|
+
// backup, and worse than a copy: json-file escapes binary, so a 4.6 GB
|
|
700
|
+
// volume ballooned to a 12 GB log file here. Backups then spike the
|
|
701
|
+
// host's disk by more than the data they read and only release it when
|
|
702
|
+
// the helper exits. Nothing ever reads these logs.
|
|
703
|
+
LogConfig: { Type: "none", Config: {} }
|
|
695
704
|
};
|
|
696
705
|
const helper = await this.dockerode.createContainer({
|
|
697
706
|
Image: helperImage,
|
|
@@ -12191,7 +12200,7 @@ var S3DestinationImpl = class {
|
|
|
12191
12200
|
if (!accessKeyId || !secretAccessKey) {
|
|
12192
12201
|
throw new Error(`S3Destination "${row.name}" missing access credentials`);
|
|
12193
12202
|
}
|
|
12194
|
-
const isAws =
|
|
12203
|
+
const isAws = isAwsS3Endpoint(row.endpoint);
|
|
12195
12204
|
this.client = new S3Client({
|
|
12196
12205
|
endpoint: row.endpoint ?? void 0,
|
|
12197
12206
|
region: row.region ?? "auto",
|
|
@@ -12668,11 +12677,11 @@ registerDestination("sftp", (row) => new SftpDestinationImpl(row));
|
|
|
12668
12677
|
registerDestination("openship_server", (row) => new SftpDestinationImpl(row));
|
|
12669
12678
|
|
|
12670
12679
|
// src/lib/compose.ts
|
|
12671
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
12680
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
12672
12681
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
12673
|
-
import { chmodSync, existsSync as
|
|
12682
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12674
12683
|
import { homedir as homedir2, userInfo } from "os";
|
|
12675
|
-
import { join as
|
|
12684
|
+
import { join as join7 } from "path";
|
|
12676
12685
|
|
|
12677
12686
|
// src/lib/paths.ts
|
|
12678
12687
|
import { homedir } from "os";
|
|
@@ -12682,15 +12691,185 @@ var override = process.env.OPENSHIP_HOME?.trim();
|
|
|
12682
12691
|
var OS_DIR = override ? resolve2(override) : DEFAULT_HOME;
|
|
12683
12692
|
var IS_ALT_HOME = OS_DIR !== DEFAULT_HOME;
|
|
12684
12693
|
|
|
12694
|
+
// src/lib/ports.ts
|
|
12695
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
12696
|
+
import { join as join4 } from "path";
|
|
12697
|
+
|
|
12698
|
+
// ../../packages/core/src/ports.ts
|
|
12699
|
+
import { createServer } from "net";
|
|
12700
|
+
var DEFAULT_API_PORT = 4e3;
|
|
12701
|
+
var DEFAULT_DASHBOARD_PORT = 3001;
|
|
12702
|
+
var LOOPBACK = "127.0.0.1";
|
|
12703
|
+
var SCAN_SPAN = 20;
|
|
12704
|
+
var MAX_PORT = 65535;
|
|
12705
|
+
function isPortFree(port, host = LOOPBACK) {
|
|
12706
|
+
return new Promise((resolve4) => {
|
|
12707
|
+
const srv = createServer();
|
|
12708
|
+
srv.once("error", () => resolve4(false));
|
|
12709
|
+
srv.listen(port, host, () => srv.close(() => resolve4(true)));
|
|
12710
|
+
});
|
|
12711
|
+
}
|
|
12712
|
+
async function waitPortFree(port, opts = {}) {
|
|
12713
|
+
const timeoutMs = opts.timeoutMs ?? 6e3;
|
|
12714
|
+
const intervalMs = opts.intervalMs ?? 250;
|
|
12715
|
+
const host = opts.host ?? LOOPBACK;
|
|
12716
|
+
const deadline = Date.now() + timeoutMs;
|
|
12717
|
+
for (; ; ) {
|
|
12718
|
+
if (await isPortFree(port, host)) return true;
|
|
12719
|
+
if (Date.now() >= deadline) return false;
|
|
12720
|
+
await new Promise((r2) => setTimeout(r2, intervalMs));
|
|
12721
|
+
}
|
|
12722
|
+
}
|
|
12723
|
+
function getFreePort(host = LOOPBACK) {
|
|
12724
|
+
return new Promise((resolve4, reject) => {
|
|
12725
|
+
const srv = createServer();
|
|
12726
|
+
srv.once("error", reject);
|
|
12727
|
+
srv.listen(0, host, () => {
|
|
12728
|
+
const addr = srv.address();
|
|
12729
|
+
const port = addr && typeof addr === "object" ? addr.port : 0;
|
|
12730
|
+
srv.close(() => port ? resolve4(port) : reject(new Error("no free port")));
|
|
12731
|
+
});
|
|
12732
|
+
});
|
|
12733
|
+
}
|
|
12734
|
+
async function findPortNear(from, opts = {}) {
|
|
12735
|
+
const host = opts.host ?? LOOPBACK;
|
|
12736
|
+
const span = opts.span ?? SCAN_SPAN;
|
|
12737
|
+
const skip = opts.skip ?? (() => false);
|
|
12738
|
+
for (let port2 = from; port2 <= Math.min(from + span, MAX_PORT); port2++) {
|
|
12739
|
+
if (!skip(port2) && await isPortFree(port2, host)) return port2;
|
|
12740
|
+
}
|
|
12741
|
+
let port = await getFreePort(host);
|
|
12742
|
+
for (let tries = 0; tries < 10 && skip(port); tries++) port = await getFreePort(host);
|
|
12743
|
+
return port;
|
|
12744
|
+
}
|
|
12745
|
+
async function resolvePortPair(prefs = {}) {
|
|
12746
|
+
const host = prefs.bindAddr?.trim() || LOOPBACK;
|
|
12747
|
+
const ours = new Set(prefs.reclaimable ?? []);
|
|
12748
|
+
const prefFor = (kind) => prefs[kind] ?? prefs.previous?.[kind] ?? prefs.stored?.[kind] ?? prefs.defaults?.[kind];
|
|
12749
|
+
const remembered = (kind, pref) => prefs[kind] === void 0 && (prefs.previous?.[kind] === pref || prefs.stored?.[kind] === pref);
|
|
12750
|
+
const availableForPref = async (port) => ours.has(port) || await isPortFree(port, host);
|
|
12751
|
+
const apiPref = prefFor("api");
|
|
12752
|
+
const dashPref = prefFor("dashboard");
|
|
12753
|
+
let api;
|
|
12754
|
+
if (apiPref === void 0) api = await getFreePort(host);
|
|
12755
|
+
else if (await availableForPref(apiPref)) api = apiPref;
|
|
12756
|
+
else if (remembered("api", apiPref) && await waitPortFree(apiPref, { host })) api = apiPref;
|
|
12757
|
+
else {
|
|
12758
|
+
api = await findPortNear(apiPref + 1, {
|
|
12759
|
+
host,
|
|
12760
|
+
skip: (p2) => ours.has(p2) || p2 === dashPref
|
|
12761
|
+
});
|
|
12762
|
+
}
|
|
12763
|
+
const skipForDash = (p2) => ours.has(p2) || p2 === api;
|
|
12764
|
+
let dashboard;
|
|
12765
|
+
if (dashPref === void 0) {
|
|
12766
|
+
dashboard = await getFreePort(host);
|
|
12767
|
+
for (let tries = 0; tries < 10 && dashboard === api; tries++) {
|
|
12768
|
+
dashboard = await getFreePort(host);
|
|
12769
|
+
}
|
|
12770
|
+
} else if (dashPref !== api && await availableForPref(dashPref)) {
|
|
12771
|
+
dashboard = dashPref;
|
|
12772
|
+
} else if (dashPref !== api && remembered("dashboard", dashPref) && await waitPortFree(dashPref, { host })) {
|
|
12773
|
+
dashboard = dashPref;
|
|
12774
|
+
} else {
|
|
12775
|
+
dashboard = await findPortNear(dashPref + 1, { host, skip: skipForDash });
|
|
12776
|
+
}
|
|
12777
|
+
return {
|
|
12778
|
+
api,
|
|
12779
|
+
dashboard,
|
|
12780
|
+
switched: {
|
|
12781
|
+
api: apiPref !== void 0 && api !== apiPref,
|
|
12782
|
+
dashboard: dashPref !== void 0 && dashboard !== dashPref
|
|
12783
|
+
},
|
|
12784
|
+
preferred: { api: apiPref ?? api, dashboard: dashPref ?? dashboard }
|
|
12785
|
+
};
|
|
12786
|
+
}
|
|
12787
|
+
|
|
12788
|
+
// src/lib/ports.ts
|
|
12789
|
+
var PORTS_FILE = join4(OS_DIR, "ports.json");
|
|
12790
|
+
var INSTANCE_FILE = join4(OS_DIR, "instance.json");
|
|
12791
|
+
function saveInstanceUrl(publicUrl) {
|
|
12792
|
+
try {
|
|
12793
|
+
if (!existsSync(OS_DIR)) mkdirSync(OS_DIR, { recursive: true, mode: 448 });
|
|
12794
|
+
writeFileSync(INSTANCE_FILE, JSON.stringify({ publicUrl: publicUrl ?? null }));
|
|
12795
|
+
} catch {
|
|
12796
|
+
}
|
|
12797
|
+
}
|
|
12798
|
+
function readInstanceUrl() {
|
|
12799
|
+
try {
|
|
12800
|
+
return JSON.parse(readFileSync(INSTANCE_FILE, "utf8")).publicUrl ?? null;
|
|
12801
|
+
} catch {
|
|
12802
|
+
return null;
|
|
12803
|
+
}
|
|
12804
|
+
}
|
|
12805
|
+
function readStoredPorts() {
|
|
12806
|
+
try {
|
|
12807
|
+
return JSON.parse(readFileSync(PORTS_FILE, "utf-8"));
|
|
12808
|
+
} catch {
|
|
12809
|
+
return {};
|
|
12810
|
+
}
|
|
12811
|
+
}
|
|
12812
|
+
function storedApiPort() {
|
|
12813
|
+
return readStoredPorts().api ?? DEFAULT_API_PORT;
|
|
12814
|
+
}
|
|
12815
|
+
function storedDashboardPort() {
|
|
12816
|
+
return readStoredPorts().dashboard ?? DEFAULT_DASHBOARD_PORT;
|
|
12817
|
+
}
|
|
12818
|
+
function saveStoredPorts(api, dashboard) {
|
|
12819
|
+
try {
|
|
12820
|
+
if (!existsSync(OS_DIR)) mkdirSync(OS_DIR, { recursive: true, mode: 448 });
|
|
12821
|
+
writeFileSync(PORTS_FILE, JSON.stringify({ api, dashboard }));
|
|
12822
|
+
} catch {
|
|
12823
|
+
}
|
|
12824
|
+
}
|
|
12825
|
+
function portMoveNotice(resolved, trustedOriginUrls = []) {
|
|
12826
|
+
if (!resolved.switched.api && !resolved.switched.dashboard) return [];
|
|
12827
|
+
const lines = [
|
|
12828
|
+
`A preferred port was busy \u2014 using API ${resolved.api}, dashboard ${resolved.dashboard}.`
|
|
12829
|
+
];
|
|
12830
|
+
const stale = [...new Set(stalePortOrigins(resolved.preferred.dashboard, trustedOriginUrls))];
|
|
12831
|
+
if (stale.length) {
|
|
12832
|
+
lines.push(
|
|
12833
|
+
`Heads up: ${stale.join(", ")} still names port ${resolved.preferred.dashboard}, so logins`,
|
|
12834
|
+
`from that origin will be rejected. Point it at :${resolved.dashboard} (or free port`,
|
|
12835
|
+
`${resolved.preferred.dashboard} and re-run to move back).`
|
|
12836
|
+
);
|
|
12837
|
+
}
|
|
12838
|
+
return lines;
|
|
12839
|
+
}
|
|
12840
|
+
function stalePortOrigins(movedFrom, urls) {
|
|
12841
|
+
const stale = [];
|
|
12842
|
+
for (const raw of urls) {
|
|
12843
|
+
for (const candidate of (raw ?? "").split(",")) {
|
|
12844
|
+
const value = candidate.trim();
|
|
12845
|
+
if (!value) continue;
|
|
12846
|
+
try {
|
|
12847
|
+
if (new URL(value).port === String(movedFrom)) stale.push(value);
|
|
12848
|
+
} catch {
|
|
12849
|
+
}
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
return stale;
|
|
12853
|
+
}
|
|
12854
|
+
async function resolvePorts(prefs) {
|
|
12855
|
+
const resolved = await resolvePortPair({
|
|
12856
|
+
...prefs,
|
|
12857
|
+
stored: readStoredPorts(),
|
|
12858
|
+
defaults: { api: DEFAULT_API_PORT, dashboard: DEFAULT_DASHBOARD_PORT }
|
|
12859
|
+
});
|
|
12860
|
+
saveStoredPorts(resolved.api, resolved.dashboard);
|
|
12861
|
+
return resolved;
|
|
12862
|
+
}
|
|
12863
|
+
|
|
12685
12864
|
// src/lib/source-install.ts
|
|
12686
12865
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
12687
|
-
import { existsSync as
|
|
12688
|
-
import { join as
|
|
12866
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
12867
|
+
import { join as join6 } from "path";
|
|
12689
12868
|
|
|
12690
12869
|
// src/lib/from-source.ts
|
|
12691
12870
|
import { spawn, spawnSync } from "child_process";
|
|
12692
|
-
import { existsSync, mkdirSync } from "fs";
|
|
12693
|
-
import { join as
|
|
12871
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
12872
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
12694
12873
|
var DEFAULT_REPO = "https://github.com/oblien/openship.git";
|
|
12695
12874
|
function has(cmd) {
|
|
12696
12875
|
try {
|
|
@@ -12720,7 +12899,7 @@ function shortSha(cwd) {
|
|
|
12720
12899
|
return r2.status === 0 ? (r2.stdout ?? "").trim() || "unknown" : "unknown";
|
|
12721
12900
|
}
|
|
12722
12901
|
function isMonorepo(dir) {
|
|
12723
|
-
return
|
|
12902
|
+
return existsSync2(join5(dir, "package.json")) && existsSync2(join5(dir, "apps/api/package.json")) && existsSync2(join5(dir, "apps/dashboard/package.json"));
|
|
12724
12903
|
}
|
|
12725
12904
|
async function prepareFromSource(opts) {
|
|
12726
12905
|
if (!has("bun")) {
|
|
@@ -12744,9 +12923,9 @@ async function prepareFromSource(opts) {
|
|
|
12744
12923
|
}
|
|
12745
12924
|
ref = (opts.ref || "main").trim();
|
|
12746
12925
|
const repoUrl = opts.repo || DEFAULT_REPO;
|
|
12747
|
-
sourceDir =
|
|
12748
|
-
|
|
12749
|
-
if (!
|
|
12926
|
+
sourceDir = join5(OS_DIR, "src");
|
|
12927
|
+
mkdirSync2(OS_DIR, { recursive: true });
|
|
12928
|
+
if (!existsSync2(join5(sourceDir, ".git"))) {
|
|
12750
12929
|
console.log(` Cloning ${repoUrl} \u2192 ${sourceDir}`);
|
|
12751
12930
|
await run("git", ["clone", repoUrl, sourceDir], OS_DIR);
|
|
12752
12931
|
}
|
|
@@ -12761,29 +12940,29 @@ async function prepareFromSource(opts) {
|
|
|
12761
12940
|
console.log(` Source: ${sourceDir} @ ${ref} (${sha})`);
|
|
12762
12941
|
console.log(" Installing workspace dependencies (bun install)\u2026");
|
|
12763
12942
|
await run("bun", ["install"], sourceDir);
|
|
12764
|
-
const distDir =
|
|
12943
|
+
const distDir = join5(OS_DIR, "from-source-dist");
|
|
12765
12944
|
console.log(" Building release dist (compiles the dashboard \u2014 needs RAM/CPU)\u2026");
|
|
12766
12945
|
await run(
|
|
12767
12946
|
"bun",
|
|
12768
|
-
["run",
|
|
12947
|
+
["run", join5(sourceDir, "apps/api/scripts/build-release.ts")],
|
|
12769
12948
|
sourceDir,
|
|
12770
12949
|
{ DIST_DIR: distDir, NODE_ENV: "production", CLOUD_MODE: "false", OPENSHIP_TARGET: "local" }
|
|
12771
12950
|
);
|
|
12772
12951
|
console.log(" Installing runtime dependencies in the dist\u2026");
|
|
12773
12952
|
await run("bun", ["install", "--production", "--frozen-lockfile"], distDir);
|
|
12774
|
-
const apiDir =
|
|
12775
|
-
const dashboardDir =
|
|
12776
|
-
if (!
|
|
12953
|
+
const apiDir = join5(distDir, "api");
|
|
12954
|
+
const dashboardDir = join5(distDir, "dashboard");
|
|
12955
|
+
if (!existsSync2(join5(apiDir, "src/index.ts"))) {
|
|
12777
12956
|
throw new Error(`Build produced no API at ${apiDir}/src/index.ts \u2014 build-release layout drift?`);
|
|
12778
12957
|
}
|
|
12779
|
-
if (!
|
|
12958
|
+
if (!existsSync2(join5(dashboardDir, "apps/dashboard/server.js"))) {
|
|
12780
12959
|
throw new Error(`Build produced no dashboard at ${dashboardDir}/apps/dashboard/server.js.`);
|
|
12781
12960
|
}
|
|
12782
12961
|
return { apiDir, dashboardDir, ref, sha, sourceDir };
|
|
12783
12962
|
}
|
|
12784
12963
|
|
|
12785
12964
|
// src/lib/source-install.ts
|
|
12786
|
-
var MARKER =
|
|
12965
|
+
var MARKER = join6(OS_DIR, "source-install.json");
|
|
12787
12966
|
function parseSourceInstall(raw) {
|
|
12788
12967
|
try {
|
|
12789
12968
|
const obj = JSON.parse(raw);
|
|
@@ -12794,9 +12973,9 @@ function parseSourceInstall(raw) {
|
|
|
12794
12973
|
}
|
|
12795
12974
|
}
|
|
12796
12975
|
function readSourceInstall() {
|
|
12797
|
-
if (!
|
|
12976
|
+
if (!existsSync3(MARKER)) return null;
|
|
12798
12977
|
try {
|
|
12799
|
-
return parseSourceInstall(
|
|
12978
|
+
return parseSourceInstall(readFileSync2(MARKER, "utf8"));
|
|
12800
12979
|
} catch {
|
|
12801
12980
|
return null;
|
|
12802
12981
|
}
|
|
@@ -12817,7 +12996,7 @@ async function rebuildFromSource(info) {
|
|
|
12817
12996
|
if (!has("git")) {
|
|
12818
12997
|
throw new Error("`git` is required to update the source checkout but wasn't found on PATH.");
|
|
12819
12998
|
}
|
|
12820
|
-
if (!
|
|
12999
|
+
if (!existsSync3(join6(info.dir, ".git"))) {
|
|
12821
13000
|
throw new Error(
|
|
12822
13001
|
`Source checkout ${info.dir} is missing (no .git) \u2014 reinstall from source with scripts/install-source.sh.`
|
|
12823
13002
|
);
|
|
@@ -12826,7 +13005,7 @@ async function rebuildFromSource(info) {
|
|
|
12826
13005
|
await run("git", ["checkout", info.ref], info.dir);
|
|
12827
13006
|
await run("git", ["pull", "--ff-only", "origin", info.ref], info.dir).catch(() => {
|
|
12828
13007
|
});
|
|
12829
|
-
const cliDir =
|
|
13008
|
+
const cliDir = join6(info.dir, "apps/cli");
|
|
12830
13009
|
await run("bun", ["install"], info.dir);
|
|
12831
13010
|
await run("bun", ["run", "build"], cliDir);
|
|
12832
13011
|
await run("bun", ["run", "build/stage-dashboard.ts"], cliDir);
|
|
@@ -12839,11 +13018,11 @@ var EDGE_ACME_HOST_DIR = `${EDGE_HOST_STATE_DIR}/acme`;
|
|
|
12839
13018
|
function edgeVolumeYaml(indent) {
|
|
12840
13019
|
return EDGE_CONTAINER_MOUNTS.map((m2) => `${indent}- ${m2.host}:${m2.container}:z`).join("\n");
|
|
12841
13020
|
}
|
|
12842
|
-
var COMPOSE_DIR =
|
|
12843
|
-
var INSTALL_METHOD_FILE =
|
|
12844
|
-
var COMPOSE_FILE =
|
|
12845
|
-
var BUILD_FILE =
|
|
12846
|
-
var ENV_FILE =
|
|
13021
|
+
var COMPOSE_DIR = join7(OS_DIR, "compose");
|
|
13022
|
+
var INSTALL_METHOD_FILE = join7(OS_DIR, "install-method");
|
|
13023
|
+
var COMPOSE_FILE = join7(COMPOSE_DIR, "docker-compose.yml");
|
|
13024
|
+
var BUILD_FILE = join7(COMPOSE_DIR, "docker-compose.build.yml");
|
|
13025
|
+
var ENV_FILE = join7(COMPOSE_DIR, ".env");
|
|
12847
13026
|
var BUILT_SERVICES = [
|
|
12848
13027
|
{ service: "api", dockerfile: "apps/api/Dockerfile" },
|
|
12849
13028
|
{ service: "dashboard", dockerfile: "apps/dashboard/Dockerfile" },
|
|
@@ -12852,21 +13031,45 @@ var BUILT_SERVICES = [
|
|
|
12852
13031
|
var ENV_CONSUMING_SERVICES = ["api", "dashboard", "edge"];
|
|
12853
13032
|
function readInstallMethod() {
|
|
12854
13033
|
try {
|
|
12855
|
-
const v2 =
|
|
13034
|
+
const v2 = readFileSync3(INSTALL_METHOD_FILE, "utf8").trim();
|
|
12856
13035
|
return v2 === "compose" || v2 === "bare" ? v2 : null;
|
|
12857
13036
|
} catch {
|
|
12858
13037
|
return null;
|
|
12859
13038
|
}
|
|
12860
13039
|
}
|
|
12861
13040
|
function writeInstallMethod(method) {
|
|
12862
|
-
|
|
12863
|
-
|
|
13041
|
+
mkdirSync3(OS_DIR, { recursive: true, mode: 448 });
|
|
13042
|
+
writeFileSync2(INSTALL_METHOD_FILE, method, { mode: 384 });
|
|
13043
|
+
}
|
|
13044
|
+
function dockerState() {
|
|
13045
|
+
const ok = (args) => spawnSync3("docker", args, { stdio: "ignore" }).status === 0;
|
|
13046
|
+
if (!ok(["--version"])) return { binary: false, plugin: false, daemon: false };
|
|
13047
|
+
return { binary: true, plugin: ok(["compose", "version"]), daemon: ok(["version"]) };
|
|
13048
|
+
}
|
|
13049
|
+
function dockerGap(state = dockerState()) {
|
|
13050
|
+
if (!state.binary) {
|
|
13051
|
+
return { summary: "Docker isn't installed", installable: true };
|
|
13052
|
+
}
|
|
13053
|
+
if (!state.plugin) {
|
|
13054
|
+
return {
|
|
13055
|
+
summary: "Docker is installed but the Compose plugin (`docker compose`) is missing",
|
|
13056
|
+
installable: true
|
|
13057
|
+
};
|
|
13058
|
+
}
|
|
13059
|
+
if (!state.daemon) {
|
|
13060
|
+
const asRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
13061
|
+
return {
|
|
13062
|
+
summary: "Docker is installed but its daemon isn't reachable",
|
|
13063
|
+
// Reinstalling changes nothing: a group change only applies to NEW logins,
|
|
13064
|
+
// and a stopped daemon needs starting, not installing.
|
|
13065
|
+
installable: false,
|
|
13066
|
+
hint: asRoot ? "Start it with: systemctl start docker" : `Add your user to the docker group: sudo usermod -aG docker ${userInfo().username} \u2014 then log out and back in (or run: newgrp docker). If the daemon is stopped: sudo systemctl start docker`
|
|
13067
|
+
};
|
|
13068
|
+
}
|
|
13069
|
+
return null;
|
|
12864
13070
|
}
|
|
12865
13071
|
function hasDockerCompose() {
|
|
12866
|
-
|
|
12867
|
-
if (docker.status !== 0) return false;
|
|
12868
|
-
const compose2 = spawnSync3("docker", ["compose", "version"], { stdio: "ignore" });
|
|
12869
|
-
return compose2.status === 0;
|
|
13072
|
+
return dockerGap() === null;
|
|
12870
13073
|
}
|
|
12871
13074
|
function composeIsViableDefault() {
|
|
12872
13075
|
return process.platform === "linux" && hasDockerCompose();
|
|
@@ -12877,9 +13080,18 @@ function hasCmd(cmd) {
|
|
|
12877
13080
|
function shQuote(s2) {
|
|
12878
13081
|
return `'${s2.replace(/'/g, `'\\''`)}'`;
|
|
12879
13082
|
}
|
|
12880
|
-
async function ensureDocker() {
|
|
12881
|
-
|
|
13083
|
+
async function ensureDocker(opts = {}) {
|
|
13084
|
+
const notice = opts.onNotice ?? ((line) => process.stderr.write(` ${line}
|
|
13085
|
+
`));
|
|
13086
|
+
const state = dockerState();
|
|
13087
|
+
const gap = dockerGap(state);
|
|
13088
|
+
if (!gap) return true;
|
|
12882
13089
|
if (process.platform !== "linux") return false;
|
|
13090
|
+
if (!gap.installable) {
|
|
13091
|
+
notice(gap.summary + ".");
|
|
13092
|
+
if (gap.hint) notice(gap.hint);
|
|
13093
|
+
return false;
|
|
13094
|
+
}
|
|
12883
13095
|
const plan = systemCatalog.installs.docker({
|
|
12884
13096
|
os: "linux",
|
|
12885
13097
|
serviceManager: "systemd"
|
|
@@ -12887,12 +13099,36 @@ async function ensureDocker() {
|
|
|
12887
13099
|
if (!plan.supported || !plan.installCommand) return false;
|
|
12888
13100
|
const asRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
12889
13101
|
const sudo = !asRoot && hasCmd("sudo") ? "sudo " : "";
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
13102
|
+
notice("This can take 2-5 minutes on a small VPS (~150 MB of packages) and stays quiet while apt works.");
|
|
13103
|
+
if (state.binary) {
|
|
13104
|
+
notice("Docker's installer will warn that docker already exists and pause ~20s before continuing \u2014 that's expected.");
|
|
13105
|
+
}
|
|
13106
|
+
const sh = (script) => new Promise((resolve4) => {
|
|
13107
|
+
const child = spawn2("sh", ["-c", sudo ? `${sudo}sh -c ${shQuote(script)}` : script], {
|
|
13108
|
+
stdio: "inherit"
|
|
13109
|
+
});
|
|
13110
|
+
const started = Date.now();
|
|
13111
|
+
const tick = setInterval(() => {
|
|
13112
|
+
const s2 = Math.round((Date.now() - started) / 1e3);
|
|
13113
|
+
notice(`still installing Docker \u2014 ${Math.floor(s2 / 60)}m${String(s2 % 60).padStart(2, "0")}s elapsed\u2026`);
|
|
13114
|
+
}, 3e4);
|
|
13115
|
+
const done = (code) => {
|
|
13116
|
+
clearInterval(tick);
|
|
13117
|
+
resolve4(code);
|
|
13118
|
+
};
|
|
13119
|
+
child.on("error", () => done(1));
|
|
13120
|
+
child.on("close", (code) => done(code ?? 1));
|
|
13121
|
+
});
|
|
13122
|
+
if (await sh(plan.installCommand) !== 0) return false;
|
|
13123
|
+
if (plan.startCommand) await sh(plan.startCommand);
|
|
13124
|
+
const after = dockerGap();
|
|
13125
|
+
if (!after) {
|
|
13126
|
+
notice("Docker ready.");
|
|
13127
|
+
return true;
|
|
13128
|
+
}
|
|
13129
|
+
notice(after.summary + ".");
|
|
13130
|
+
if (after.hint) notice(after.hint);
|
|
13131
|
+
return false;
|
|
12896
13132
|
}
|
|
12897
13133
|
var COMPOSE_YAML = `# Managed by \`openship up\` \u2014 do not edit; re-run \`openship up\` to regenerate.
|
|
12898
13134
|
services:
|
|
@@ -12900,6 +13136,12 @@ services:
|
|
|
12900
13136
|
image: postgres:16-alpine
|
|
12901
13137
|
restart: unless-stopped
|
|
12902
13138
|
environment:
|
|
13139
|
+
# Keep the data dir in a subdirectory of the volume so a fresh install never
|
|
13140
|
+
# runs initdb against a bare mount root (which fails on quirky host
|
|
13141
|
+
# filesystems with EPERM \u2014 #350). OPENSHIP_PGDATA is decided ONCE at install
|
|
13142
|
+
# by the CLI (fresh \u2192 subdir, pre-existing volume \u2192 root) and preserved in
|
|
13143
|
+
# .env, so this never moves an existing database.
|
|
13144
|
+
PGDATA: \${OPENSHIP_PGDATA:-/var/lib/postgresql/data/pgdata}
|
|
12903
13145
|
POSTGRES_USER: \${POSTGRES_USER:-openship}
|
|
12904
13146
|
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:?missing from .env \u2014 re-run openship up to regenerate it}
|
|
12905
13147
|
POSTGRES_DB: \${POSTGRES_DB:-openship}
|
|
@@ -12995,7 +13237,7 @@ function keepSecret(existing, key) {
|
|
|
12995
13237
|
function readEnvFile() {
|
|
12996
13238
|
const out = {};
|
|
12997
13239
|
try {
|
|
12998
|
-
for (const line of
|
|
13240
|
+
for (const line of readFileSync3(ENV_FILE, "utf8").split("\n")) {
|
|
12999
13241
|
const m2 = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
13000
13242
|
if (m2) out[m2[1]] = m2[2];
|
|
13001
13243
|
}
|
|
@@ -13015,10 +13257,10 @@ function rewriteHostAuthorizedKeys(existing, pub) {
|
|
|
13015
13257
|
function provisionHostSshChannel() {
|
|
13016
13258
|
if (process.platform !== "linux") return null;
|
|
13017
13259
|
try {
|
|
13018
|
-
const sshDir =
|
|
13019
|
-
|
|
13020
|
-
const keyPath =
|
|
13021
|
-
if (!
|
|
13260
|
+
const sshDir = join7(COMPOSE_DIR, "host-ssh");
|
|
13261
|
+
mkdirSync3(sshDir, { recursive: true, mode: 448 });
|
|
13262
|
+
const keyPath = join7(sshDir, "id_ed25519");
|
|
13263
|
+
if (!existsSync4(keyPath)) {
|
|
13022
13264
|
const g2 = spawnSync3(
|
|
13023
13265
|
"ssh-keygen",
|
|
13024
13266
|
["-t", "ed25519", "-N", "", "-q", "-f", keyPath, "-C", HOST_KEY_COMMENT],
|
|
@@ -13026,7 +13268,7 @@ function provisionHostSshChannel() {
|
|
|
13026
13268
|
);
|
|
13027
13269
|
if (g2.status !== 0) return null;
|
|
13028
13270
|
}
|
|
13029
|
-
const pub =
|
|
13271
|
+
const pub = readFileSync3(`${keyPath}.pub`, "utf8").trim();
|
|
13030
13272
|
if (!pub) return null;
|
|
13031
13273
|
const user = (() => {
|
|
13032
13274
|
try {
|
|
@@ -13035,12 +13277,12 @@ function provisionHostSshChannel() {
|
|
|
13035
13277
|
return process.env.USER || process.env.LOGNAME || "root";
|
|
13036
13278
|
}
|
|
13037
13279
|
})();
|
|
13038
|
-
const userSshDir =
|
|
13039
|
-
|
|
13040
|
-
const authKeys =
|
|
13041
|
-
const existing =
|
|
13280
|
+
const userSshDir = join7(homedir2(), ".ssh");
|
|
13281
|
+
mkdirSync3(userSshDir, { recursive: true, mode: 448 });
|
|
13282
|
+
const authKeys = join7(userSshDir, "authorized_keys");
|
|
13283
|
+
const existing = existsSync4(authKeys) ? readFileSync3(authKeys, "utf8") : "";
|
|
13042
13284
|
const next = rewriteHostAuthorizedKeys(existing, pub);
|
|
13043
|
-
if (next !== existing)
|
|
13285
|
+
if (next !== existing) writeFileSync2(authKeys, next, { mode: 384 });
|
|
13044
13286
|
chmodSync(authKeys, 384);
|
|
13045
13287
|
return { user, keyPath };
|
|
13046
13288
|
} catch {
|
|
@@ -13051,6 +13293,11 @@ function composeProjectName(prev) {
|
|
|
13051
13293
|
if (prev.COMPOSE_PROJECT_NAME) return prev.COMPOSE_PROJECT_NAME;
|
|
13052
13294
|
return Object.keys(prev).length > 0 ? "compose" : "openship";
|
|
13053
13295
|
}
|
|
13296
|
+
var PGDATA_ROOT = "/var/lib/postgresql/data";
|
|
13297
|
+
function resolvePgData(prev) {
|
|
13298
|
+
if (prev.OPENSHIP_PGDATA) return prev.OPENSHIP_PGDATA;
|
|
13299
|
+
return dbVolumeExists(composeProjectName(prev)) ? PGDATA_ROOT : `${PGDATA_ROOT}/pgdata`;
|
|
13300
|
+
}
|
|
13054
13301
|
function orphanedStackContainers(project) {
|
|
13055
13302
|
const r2 = spawnSync3(
|
|
13056
13303
|
"docker",
|
|
@@ -13150,7 +13397,7 @@ function migrateLegacyEdgeVolumes(project) {
|
|
|
13150
13397
|
const existing = new Set(ls.stdout.split("\n").map((l2) => l2.trim()).filter(Boolean));
|
|
13151
13398
|
for (const { volume, host } of pairs) {
|
|
13152
13399
|
if (!existing.has(volume)) continue;
|
|
13153
|
-
|
|
13400
|
+
mkdirSync3(host, { recursive: true });
|
|
13154
13401
|
const alreadyThere = spawnSync3("sh", ["-c", `ls -A ${JSON.stringify(host)} 2>/dev/null | head -1`], {
|
|
13155
13402
|
encoding: "utf8"
|
|
13156
13403
|
});
|
|
@@ -13202,9 +13449,11 @@ function keepConfig(prev, key, explicit) {
|
|
|
13202
13449
|
}
|
|
13203
13450
|
function resolveEnvConfig(prev, opts) {
|
|
13204
13451
|
const publicUrl = keepConfig(prev, "OPENSHIP_PUBLIC_URL", opts.publicUrl);
|
|
13452
|
+
const bindAddr = keepConfig(prev, "OPENSHIP_BIND_ADDR");
|
|
13205
13453
|
return {
|
|
13206
|
-
apiPort: keepConfig(prev, "API_PORT", opts.apiPort) ??
|
|
13207
|
-
dashPort: keepConfig(prev, "DASHBOARD_PORT", opts.dashboardPort) ??
|
|
13454
|
+
apiPort: keepConfig(prev, "API_PORT", opts.apiPort) ?? String(DEFAULT_API_PORT),
|
|
13455
|
+
dashPort: keepConfig(prev, "DASHBOARD_PORT", opts.dashboardPort) ?? String(DEFAULT_DASHBOARD_PORT),
|
|
13456
|
+
...bindAddr ? { bindAddr } : {},
|
|
13208
13457
|
...publicUrl ? { publicUrl } : {},
|
|
13209
13458
|
// A public URL always implies a proxy in front; otherwise keep whatever the
|
|
13210
13459
|
// install was configured with.
|
|
@@ -13222,6 +13471,47 @@ function resolveEnvConfig(prev, opts) {
|
|
|
13222
13471
|
hostControl: opts.noHostControl === void 0 ? prev.OPENSHIP_HOST_CONTROL !== "false" : !opts.noHostControl
|
|
13223
13472
|
};
|
|
13224
13473
|
}
|
|
13474
|
+
function toPort(value) {
|
|
13475
|
+
const n2 = Number(value);
|
|
13476
|
+
return Number.isInteger(n2) && n2 > 0 && n2 <= 65535 ? n2 : void 0;
|
|
13477
|
+
}
|
|
13478
|
+
function composeEnvPorts() {
|
|
13479
|
+
const env = readEnvFile();
|
|
13480
|
+
return { api: toPort(env.API_PORT), dashboard: toPort(env.DASHBOARD_PORT) };
|
|
13481
|
+
}
|
|
13482
|
+
function composeBindAddr() {
|
|
13483
|
+
return readEnvFile().OPENSHIP_BIND_ADDR?.trim() || "0.0.0.0";
|
|
13484
|
+
}
|
|
13485
|
+
function composeTrustedOriginUrls() {
|
|
13486
|
+
const env = readEnvFile();
|
|
13487
|
+
return [env.OPENSHIP_PUBLIC_URL, env.OPENSHIP_EXTRA_TRUSTED_ORIGINS].filter(
|
|
13488
|
+
(v2) => !!v2?.trim()
|
|
13489
|
+
);
|
|
13490
|
+
}
|
|
13491
|
+
function composeHeldPorts() {
|
|
13492
|
+
const r2 = spawnSync3(
|
|
13493
|
+
"docker",
|
|
13494
|
+
["ps", "--format", '{{.Label "com.docker.compose.project.config_files"}} {{.Ports}}'],
|
|
13495
|
+
{ encoding: "utf8" }
|
|
13496
|
+
);
|
|
13497
|
+
if (r2.status !== 0 || !r2.stdout) return [];
|
|
13498
|
+
const ports = /* @__PURE__ */ new Set();
|
|
13499
|
+
for (const line of r2.stdout.split("\n")) {
|
|
13500
|
+
const [configFiles = "", published = ""] = line.split(" ");
|
|
13501
|
+
if (!configFiles.split(",").some((f2) => f2.trim() === COMPOSE_FILE)) continue;
|
|
13502
|
+
for (const m2 of published.matchAll(/:(\d+)->/g)) ports.add(Number(m2[1]));
|
|
13503
|
+
}
|
|
13504
|
+
return [...ports];
|
|
13505
|
+
}
|
|
13506
|
+
async function resolveComposePorts(prefs) {
|
|
13507
|
+
return resolvePorts({
|
|
13508
|
+
api: toPort(prefs.api),
|
|
13509
|
+
dashboard: toPort(prefs.dashboard),
|
|
13510
|
+
previous: composeEnvPorts(),
|
|
13511
|
+
bindAddr: composeBindAddr(),
|
|
13512
|
+
reclaimable: composeHeldPorts()
|
|
13513
|
+
});
|
|
13514
|
+
}
|
|
13225
13515
|
function renderEnv(opts, host, cfg) {
|
|
13226
13516
|
const prev = readEnvFile();
|
|
13227
13517
|
const lines = [
|
|
@@ -13235,13 +13525,21 @@ function renderEnv(opts, host, cfg) {
|
|
|
13235
13525
|
// (hides the local row). Written explicitly so the policy is visible in .env.
|
|
13236
13526
|
`OPENSHIP_HOST_CONTROL=${cfg.hostControl ? "true" : "false"}`,
|
|
13237
13527
|
`OPENSHIP_IMAGE_REGISTRY=${cfg.registry}`,
|
|
13238
|
-
`OPENSHIP_VERSION=${opts.version || (true ? "0.
|
|
13528
|
+
`OPENSHIP_VERSION=${opts.version || (true ? "0.5.0" : "latest")}`,
|
|
13239
13529
|
`POSTGRES_PASSWORD=${keepSecret(prev, "POSTGRES_PASSWORD")}`,
|
|
13530
|
+
// Pinned once (see resolvePgData): fresh install → subdir, existing volume → root.
|
|
13531
|
+
`OPENSHIP_PGDATA=${resolvePgData(prev)}`,
|
|
13240
13532
|
`BETTER_AUTH_SECRET=${keepSecret(prev, "BETTER_AUTH_SECRET")}`,
|
|
13241
13533
|
`INTERNAL_TOKEN=${keepSecret(prev, "INTERNAL_TOKEN")}`,
|
|
13242
13534
|
`API_PORT=${cfg.apiPort}`,
|
|
13243
|
-
`DASHBOARD_PORT=${cfg.dashPort}
|
|
13535
|
+
`DASHBOARD_PORT=${cfg.dashPort}`,
|
|
13536
|
+
// The api's OWN view of the dashboard port: the self-app boot reconcile points
|
|
13537
|
+
// the operator's domain at the dashboard through this (self-deploy.ts), and it
|
|
13538
|
+
// silently defaults to 3001 when unset. Ports are dynamic now, so leaving it
|
|
13539
|
+
// out publishes a domain routed to a port nothing is listening on.
|
|
13540
|
+
`OPENSHIP_DASHBOARD_PORT=${cfg.dashPort}`
|
|
13244
13541
|
];
|
|
13542
|
+
if (cfg.bindAddr) lines.push(`OPENSHIP_BIND_ADDR=${cfg.bindAddr}`);
|
|
13245
13543
|
if (cfg.publicUrl) lines.push(`OPENSHIP_PUBLIC_URL=${cfg.publicUrl}`);
|
|
13246
13544
|
if (cfg.extraTrustedOrigins) {
|
|
13247
13545
|
lines.push(`OPENSHIP_EXTRA_TRUSTED_ORIGINS=${cfg.extraTrustedOrigins}`);
|
|
@@ -13261,7 +13559,7 @@ function renderEnv(opts, host, cfg) {
|
|
|
13261
13559
|
function sourceBuildDir() {
|
|
13262
13560
|
const marker = readSourceInstall();
|
|
13263
13561
|
if (!marker?.dir) return null;
|
|
13264
|
-
const hasAll = BUILT_SERVICES.every((s2) =>
|
|
13562
|
+
const hasAll = BUILT_SERVICES.every((s2) => existsSync4(join7(marker.dir, s2.dockerfile)));
|
|
13265
13563
|
return hasAll ? marker.dir : null;
|
|
13266
13564
|
}
|
|
13267
13565
|
function renderBuildOverride(repoDir) {
|
|
@@ -13277,20 +13575,20 @@ services:
|
|
|
13277
13575
|
${services}`;
|
|
13278
13576
|
}
|
|
13279
13577
|
function materialize(opts) {
|
|
13280
|
-
|
|
13578
|
+
mkdirSync3(COMPOSE_DIR, { recursive: true, mode: 448 });
|
|
13281
13579
|
const prev = readEnvFile();
|
|
13282
13580
|
const cfg = resolveEnvConfig(prev, opts);
|
|
13283
13581
|
const host = cfg.hostControl ? provisionHostSshChannel() : null;
|
|
13284
13582
|
let before = "";
|
|
13285
13583
|
try {
|
|
13286
|
-
before =
|
|
13584
|
+
before = readFileSync3(ENV_FILE, "utf8");
|
|
13287
13585
|
} catch {
|
|
13288
13586
|
}
|
|
13289
13587
|
const rendered = renderEnv(opts, host, cfg);
|
|
13290
|
-
|
|
13291
|
-
|
|
13588
|
+
writeFileSync2(COMPOSE_FILE, COMPOSE_YAML);
|
|
13589
|
+
writeFileSync2(ENV_FILE, rendered, { mode: 384 });
|
|
13292
13590
|
const buildDir = opts.build === false ? null : sourceBuildDir();
|
|
13293
|
-
if (buildDir)
|
|
13591
|
+
if (buildDir) writeFileSync2(BUILD_FILE, renderBuildOverride(buildDir));
|
|
13294
13592
|
return { buildDir, cfg, envChanged: before !== "" && rendered !== before };
|
|
13295
13593
|
}
|
|
13296
13594
|
function dbVolumeExists(project) {
|
|
@@ -13365,12 +13663,12 @@ function onEdgeContainerChanged() {
|
|
|
13365
13663
|
invalidateEdgeContainer();
|
|
13366
13664
|
}
|
|
13367
13665
|
function composeDown() {
|
|
13368
|
-
if (!
|
|
13666
|
+
if (!existsSync4(COMPOSE_FILE)) return false;
|
|
13369
13667
|
return compose(["down"]) === 0;
|
|
13370
13668
|
}
|
|
13371
13669
|
function composeUninstall(opts = {}) {
|
|
13372
13670
|
const removedImages = [];
|
|
13373
|
-
const ok =
|
|
13671
|
+
const ok = existsSync4(COMPOSE_FILE) ? compose(["down", "-v", "--remove-orphans"]) === 0 : false;
|
|
13374
13672
|
if (opts.removeImages) {
|
|
13375
13673
|
const env = readEnvFile();
|
|
13376
13674
|
const registry = env.OPENSHIP_IMAGE_REGISTRY || DEFAULT_IMAGE_REGISTRY;
|
|
@@ -13385,7 +13683,7 @@ function composeUninstall(opts = {}) {
|
|
|
13385
13683
|
return { ok, removedImages };
|
|
13386
13684
|
}
|
|
13387
13685
|
async function composeUpdate(version) {
|
|
13388
|
-
if (!
|
|
13686
|
+
if (!existsSync4(COMPOSE_FILE)) return false;
|
|
13389
13687
|
return (await composeUp(version ? { version } : {})).ok;
|
|
13390
13688
|
}
|
|
13391
13689
|
function composePs() {
|
|
@@ -13421,6 +13719,15 @@ async function waitForApiHealth(port, tries) {
|
|
|
13421
13719
|
}
|
|
13422
13720
|
return false;
|
|
13423
13721
|
}
|
|
13722
|
+
async function waitForEdgeRunning(tries) {
|
|
13723
|
+
const exec = new LocalExecutor();
|
|
13724
|
+
for (let i2 = 0; i2 < tries; i2++) {
|
|
13725
|
+
const out = await exec.exec(`docker inspect -f '{{.State.Running}}' ${EDGE_CONTAINER_NAME}`).catch(() => "");
|
|
13726
|
+
if (out.trim() === "true") return true;
|
|
13727
|
+
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
13728
|
+
}
|
|
13729
|
+
return false;
|
|
13730
|
+
}
|
|
13424
13731
|
async function importMigratedSites(apiPort, sites, certPems) {
|
|
13425
13732
|
const token = composeInternalToken();
|
|
13426
13733
|
if (!token) {
|
|
@@ -13437,6 +13744,11 @@ async function importMigratedSites(apiPort, sites, certPems) {
|
|
|
13437
13744
|
spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
|
|
13438
13745
|
return { ok: false, registered: [], error };
|
|
13439
13746
|
}
|
|
13747
|
+
if (!await waitForEdgeRunning(60)) {
|
|
13748
|
+
const error = `the ${EDGE_CONTAINER_NAME} container isn't running yet`;
|
|
13749
|
+
spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
|
|
13750
|
+
return { ok: false, registered: [], error };
|
|
13751
|
+
}
|
|
13440
13752
|
try {
|
|
13441
13753
|
const r2 = await fetch(`http://127.0.0.1:${apiPort}/api/system/edge/import-sites`, {
|
|
13442
13754
|
method: "POST",
|
|
@@ -13496,15 +13808,27 @@ async function importMigratedSites(apiPort, sites, certPems) {
|
|
|
13496
13808
|
export {
|
|
13497
13809
|
OS_DIR,
|
|
13498
13810
|
IS_ALT_HOME,
|
|
13811
|
+
DEFAULT_API_PORT,
|
|
13812
|
+
DEFAULT_DASHBOARD_PORT,
|
|
13813
|
+
saveInstanceUrl,
|
|
13814
|
+
readInstanceUrl,
|
|
13815
|
+
readStoredPorts,
|
|
13816
|
+
storedApiPort,
|
|
13817
|
+
storedDashboardPort,
|
|
13818
|
+
portMoveNotice,
|
|
13819
|
+
resolvePorts,
|
|
13499
13820
|
shortSha,
|
|
13500
13821
|
prepareFromSource,
|
|
13501
13822
|
readSourceInstall,
|
|
13502
13823
|
remoteSha,
|
|
13503
13824
|
rebuildFromSource,
|
|
13504
13825
|
readInstallMethod,
|
|
13826
|
+
dockerGap,
|
|
13505
13827
|
hasDockerCompose,
|
|
13506
13828
|
composeIsViableDefault,
|
|
13507
13829
|
ensureDocker,
|
|
13830
|
+
composeTrustedOriginUrls,
|
|
13831
|
+
resolveComposePorts,
|
|
13508
13832
|
sourceBuildDir,
|
|
13509
13833
|
composePrefetch,
|
|
13510
13834
|
composeUp,
|
|
@@ -13517,5 +13841,6 @@ export {
|
|
|
13517
13841
|
composeRestart,
|
|
13518
13842
|
composeInternalToken,
|
|
13519
13843
|
waitForApiHealth,
|
|
13844
|
+
waitForEdgeRunning,
|
|
13520
13845
|
importMigratedSites
|
|
13521
13846
|
};
|