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
|
@@ -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,26 +139,28 @@ 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";
|
|
149
149
|
import {
|
|
150
150
|
EDGE_CONTAINER_NAME,
|
|
151
151
|
edgeCrashReason,
|
|
152
|
-
invalidateEdgeContainer
|
|
153
|
-
|
|
152
|
+
invalidateEdgeContainer,
|
|
153
|
+
sanitizeEdgeVhosts
|
|
154
|
+
} from "./chunk-6PIYRG5I.js";
|
|
154
155
|
import {
|
|
155
156
|
EDGE_CONTAINER_MOUNTS,
|
|
156
157
|
EDGE_HOST_STATE_DIR
|
|
157
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-EAAGSQXN.js";
|
|
158
159
|
import {
|
|
159
160
|
DEFAULT_IMAGE_REGISTRY,
|
|
161
|
+
isAwsS3Endpoint,
|
|
160
162
|
safeErrorMessage
|
|
161
|
-
} from "./chunk-
|
|
163
|
+
} from "./chunk-UMQP7SYT.js";
|
|
162
164
|
|
|
163
165
|
// src/lib/edge-import.ts
|
|
164
166
|
import chalk from "chalk";
|
|
@@ -690,7 +692,15 @@ var DockerBackupExecutor = class {
|
|
|
690
692
|
await this.ensureImage(helperImage);
|
|
691
693
|
const hostConfig = {
|
|
692
694
|
Binds: [`${source.source}:/mnt:ro`],
|
|
693
|
-
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: {} }
|
|
694
704
|
};
|
|
695
705
|
const helper = await this.dockerode.createContainer({
|
|
696
706
|
Image: helperImage,
|
|
@@ -12190,7 +12200,7 @@ var S3DestinationImpl = class {
|
|
|
12190
12200
|
if (!accessKeyId || !secretAccessKey) {
|
|
12191
12201
|
throw new Error(`S3Destination "${row.name}" missing access credentials`);
|
|
12192
12202
|
}
|
|
12193
|
-
const isAws =
|
|
12203
|
+
const isAws = isAwsS3Endpoint(row.endpoint);
|
|
12194
12204
|
this.client = new S3Client({
|
|
12195
12205
|
endpoint: row.endpoint ?? void 0,
|
|
12196
12206
|
region: row.region ?? "auto",
|
|
@@ -12667,11 +12677,11 @@ registerDestination("sftp", (row) => new SftpDestinationImpl(row));
|
|
|
12667
12677
|
registerDestination("openship_server", (row) => new SftpDestinationImpl(row));
|
|
12668
12678
|
|
|
12669
12679
|
// src/lib/compose.ts
|
|
12670
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
12680
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
12671
12681
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
12672
|
-
import { chmodSync, existsSync as
|
|
12682
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
12673
12683
|
import { homedir as homedir2, userInfo } from "os";
|
|
12674
|
-
import { join as
|
|
12684
|
+
import { join as join7 } from "path";
|
|
12675
12685
|
|
|
12676
12686
|
// src/lib/paths.ts
|
|
12677
12687
|
import { homedir } from "os";
|
|
@@ -12681,15 +12691,185 @@ var override = process.env.OPENSHIP_HOME?.trim();
|
|
|
12681
12691
|
var OS_DIR = override ? resolve2(override) : DEFAULT_HOME;
|
|
12682
12692
|
var IS_ALT_HOME = OS_DIR !== DEFAULT_HOME;
|
|
12683
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
|
+
|
|
12684
12864
|
// src/lib/source-install.ts
|
|
12685
12865
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
12686
|
-
import { existsSync as
|
|
12687
|
-
import { join as
|
|
12866
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
12867
|
+
import { join as join6 } from "path";
|
|
12688
12868
|
|
|
12689
12869
|
// src/lib/from-source.ts
|
|
12690
12870
|
import { spawn, spawnSync } from "child_process";
|
|
12691
|
-
import { existsSync, mkdirSync } from "fs";
|
|
12692
|
-
import { join as
|
|
12871
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
12872
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
12693
12873
|
var DEFAULT_REPO = "https://github.com/oblien/openship.git";
|
|
12694
12874
|
function has(cmd) {
|
|
12695
12875
|
try {
|
|
@@ -12719,7 +12899,7 @@ function shortSha(cwd) {
|
|
|
12719
12899
|
return r2.status === 0 ? (r2.stdout ?? "").trim() || "unknown" : "unknown";
|
|
12720
12900
|
}
|
|
12721
12901
|
function isMonorepo(dir) {
|
|
12722
|
-
return
|
|
12902
|
+
return existsSync2(join5(dir, "package.json")) && existsSync2(join5(dir, "apps/api/package.json")) && existsSync2(join5(dir, "apps/dashboard/package.json"));
|
|
12723
12903
|
}
|
|
12724
12904
|
async function prepareFromSource(opts) {
|
|
12725
12905
|
if (!has("bun")) {
|
|
@@ -12743,9 +12923,9 @@ async function prepareFromSource(opts) {
|
|
|
12743
12923
|
}
|
|
12744
12924
|
ref = (opts.ref || "main").trim();
|
|
12745
12925
|
const repoUrl = opts.repo || DEFAULT_REPO;
|
|
12746
|
-
sourceDir =
|
|
12747
|
-
|
|
12748
|
-
if (!
|
|
12926
|
+
sourceDir = join5(OS_DIR, "src");
|
|
12927
|
+
mkdirSync2(OS_DIR, { recursive: true });
|
|
12928
|
+
if (!existsSync2(join5(sourceDir, ".git"))) {
|
|
12749
12929
|
console.log(` Cloning ${repoUrl} \u2192 ${sourceDir}`);
|
|
12750
12930
|
await run("git", ["clone", repoUrl, sourceDir], OS_DIR);
|
|
12751
12931
|
}
|
|
@@ -12760,29 +12940,29 @@ async function prepareFromSource(opts) {
|
|
|
12760
12940
|
console.log(` Source: ${sourceDir} @ ${ref} (${sha})`);
|
|
12761
12941
|
console.log(" Installing workspace dependencies (bun install)\u2026");
|
|
12762
12942
|
await run("bun", ["install"], sourceDir);
|
|
12763
|
-
const distDir =
|
|
12943
|
+
const distDir = join5(OS_DIR, "from-source-dist");
|
|
12764
12944
|
console.log(" Building release dist (compiles the dashboard \u2014 needs RAM/CPU)\u2026");
|
|
12765
12945
|
await run(
|
|
12766
12946
|
"bun",
|
|
12767
|
-
["run",
|
|
12947
|
+
["run", join5(sourceDir, "apps/api/scripts/build-release.ts")],
|
|
12768
12948
|
sourceDir,
|
|
12769
12949
|
{ DIST_DIR: distDir, NODE_ENV: "production", CLOUD_MODE: "false", OPENSHIP_TARGET: "local" }
|
|
12770
12950
|
);
|
|
12771
12951
|
console.log(" Installing runtime dependencies in the dist\u2026");
|
|
12772
12952
|
await run("bun", ["install", "--production", "--frozen-lockfile"], distDir);
|
|
12773
|
-
const apiDir =
|
|
12774
|
-
const dashboardDir =
|
|
12775
|
-
if (!
|
|
12953
|
+
const apiDir = join5(distDir, "api");
|
|
12954
|
+
const dashboardDir = join5(distDir, "dashboard");
|
|
12955
|
+
if (!existsSync2(join5(apiDir, "src/index.ts"))) {
|
|
12776
12956
|
throw new Error(`Build produced no API at ${apiDir}/src/index.ts \u2014 build-release layout drift?`);
|
|
12777
12957
|
}
|
|
12778
|
-
if (!
|
|
12958
|
+
if (!existsSync2(join5(dashboardDir, "apps/dashboard/server.js"))) {
|
|
12779
12959
|
throw new Error(`Build produced no dashboard at ${dashboardDir}/apps/dashboard/server.js.`);
|
|
12780
12960
|
}
|
|
12781
12961
|
return { apiDir, dashboardDir, ref, sha, sourceDir };
|
|
12782
12962
|
}
|
|
12783
12963
|
|
|
12784
12964
|
// src/lib/source-install.ts
|
|
12785
|
-
var MARKER =
|
|
12965
|
+
var MARKER = join6(OS_DIR, "source-install.json");
|
|
12786
12966
|
function parseSourceInstall(raw) {
|
|
12787
12967
|
try {
|
|
12788
12968
|
const obj = JSON.parse(raw);
|
|
@@ -12793,9 +12973,9 @@ function parseSourceInstall(raw) {
|
|
|
12793
12973
|
}
|
|
12794
12974
|
}
|
|
12795
12975
|
function readSourceInstall() {
|
|
12796
|
-
if (!
|
|
12976
|
+
if (!existsSync3(MARKER)) return null;
|
|
12797
12977
|
try {
|
|
12798
|
-
return parseSourceInstall(
|
|
12978
|
+
return parseSourceInstall(readFileSync2(MARKER, "utf8"));
|
|
12799
12979
|
} catch {
|
|
12800
12980
|
return null;
|
|
12801
12981
|
}
|
|
@@ -12816,7 +12996,7 @@ async function rebuildFromSource(info) {
|
|
|
12816
12996
|
if (!has("git")) {
|
|
12817
12997
|
throw new Error("`git` is required to update the source checkout but wasn't found on PATH.");
|
|
12818
12998
|
}
|
|
12819
|
-
if (!
|
|
12999
|
+
if (!existsSync3(join6(info.dir, ".git"))) {
|
|
12820
13000
|
throw new Error(
|
|
12821
13001
|
`Source checkout ${info.dir} is missing (no .git) \u2014 reinstall from source with scripts/install-source.sh.`
|
|
12822
13002
|
);
|
|
@@ -12825,7 +13005,7 @@ async function rebuildFromSource(info) {
|
|
|
12825
13005
|
await run("git", ["checkout", info.ref], info.dir);
|
|
12826
13006
|
await run("git", ["pull", "--ff-only", "origin", info.ref], info.dir).catch(() => {
|
|
12827
13007
|
});
|
|
12828
|
-
const cliDir =
|
|
13008
|
+
const cliDir = join6(info.dir, "apps/cli");
|
|
12829
13009
|
await run("bun", ["install"], info.dir);
|
|
12830
13010
|
await run("bun", ["run", "build"], cliDir);
|
|
12831
13011
|
await run("bun", ["run", "build/stage-dashboard.ts"], cliDir);
|
|
@@ -12838,11 +13018,11 @@ var EDGE_ACME_HOST_DIR = `${EDGE_HOST_STATE_DIR}/acme`;
|
|
|
12838
13018
|
function edgeVolumeYaml(indent) {
|
|
12839
13019
|
return EDGE_CONTAINER_MOUNTS.map((m2) => `${indent}- ${m2.host}:${m2.container}:z`).join("\n");
|
|
12840
13020
|
}
|
|
12841
|
-
var COMPOSE_DIR =
|
|
12842
|
-
var INSTALL_METHOD_FILE =
|
|
12843
|
-
var COMPOSE_FILE =
|
|
12844
|
-
var BUILD_FILE =
|
|
12845
|
-
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");
|
|
12846
13026
|
var BUILT_SERVICES = [
|
|
12847
13027
|
{ service: "api", dockerfile: "apps/api/Dockerfile" },
|
|
12848
13028
|
{ service: "dashboard", dockerfile: "apps/dashboard/Dockerfile" },
|
|
@@ -12851,21 +13031,45 @@ var BUILT_SERVICES = [
|
|
|
12851
13031
|
var ENV_CONSUMING_SERVICES = ["api", "dashboard", "edge"];
|
|
12852
13032
|
function readInstallMethod() {
|
|
12853
13033
|
try {
|
|
12854
|
-
const v2 =
|
|
13034
|
+
const v2 = readFileSync3(INSTALL_METHOD_FILE, "utf8").trim();
|
|
12855
13035
|
return v2 === "compose" || v2 === "bare" ? v2 : null;
|
|
12856
13036
|
} catch {
|
|
12857
13037
|
return null;
|
|
12858
13038
|
}
|
|
12859
13039
|
}
|
|
12860
13040
|
function writeInstallMethod(method) {
|
|
12861
|
-
|
|
12862
|
-
|
|
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;
|
|
12863
13070
|
}
|
|
12864
13071
|
function hasDockerCompose() {
|
|
12865
|
-
|
|
12866
|
-
if (docker.status !== 0) return false;
|
|
12867
|
-
const compose2 = spawnSync3("docker", ["compose", "version"], { stdio: "ignore" });
|
|
12868
|
-
return compose2.status === 0;
|
|
13072
|
+
return dockerGap() === null;
|
|
12869
13073
|
}
|
|
12870
13074
|
function composeIsViableDefault() {
|
|
12871
13075
|
return process.platform === "linux" && hasDockerCompose();
|
|
@@ -12876,9 +13080,18 @@ function hasCmd(cmd) {
|
|
|
12876
13080
|
function shQuote(s2) {
|
|
12877
13081
|
return `'${s2.replace(/'/g, `'\\''`)}'`;
|
|
12878
13082
|
}
|
|
12879
|
-
async function ensureDocker() {
|
|
12880
|
-
|
|
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;
|
|
12881
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
|
+
}
|
|
12882
13095
|
const plan = systemCatalog.installs.docker({
|
|
12883
13096
|
os: "linux",
|
|
12884
13097
|
serviceManager: "systemd"
|
|
@@ -12886,12 +13099,36 @@ async function ensureDocker() {
|
|
|
12886
13099
|
if (!plan.supported || !plan.installCommand) return false;
|
|
12887
13100
|
const asRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
12888
13101
|
const sudo = !asRoot && hasCmd("sudo") ? "sudo " : "";
|
|
12889
|
-
|
|
12890
|
-
|
|
12891
|
-
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
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;
|
|
12895
13132
|
}
|
|
12896
13133
|
var COMPOSE_YAML = `# Managed by \`openship up\` \u2014 do not edit; re-run \`openship up\` to regenerate.
|
|
12897
13134
|
services:
|
|
@@ -12899,6 +13136,12 @@ services:
|
|
|
12899
13136
|
image: postgres:16-alpine
|
|
12900
13137
|
restart: unless-stopped
|
|
12901
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}
|
|
12902
13145
|
POSTGRES_USER: \${POSTGRES_USER:-openship}
|
|
12903
13146
|
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:?missing from .env \u2014 re-run openship up to regenerate it}
|
|
12904
13147
|
POSTGRES_DB: \${POSTGRES_DB:-openship}
|
|
@@ -12994,7 +13237,7 @@ function keepSecret(existing, key) {
|
|
|
12994
13237
|
function readEnvFile() {
|
|
12995
13238
|
const out = {};
|
|
12996
13239
|
try {
|
|
12997
|
-
for (const line of
|
|
13240
|
+
for (const line of readFileSync3(ENV_FILE, "utf8").split("\n")) {
|
|
12998
13241
|
const m2 = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
12999
13242
|
if (m2) out[m2[1]] = m2[2];
|
|
13000
13243
|
}
|
|
@@ -13014,10 +13257,10 @@ function rewriteHostAuthorizedKeys(existing, pub) {
|
|
|
13014
13257
|
function provisionHostSshChannel() {
|
|
13015
13258
|
if (process.platform !== "linux") return null;
|
|
13016
13259
|
try {
|
|
13017
|
-
const sshDir =
|
|
13018
|
-
|
|
13019
|
-
const keyPath =
|
|
13020
|
-
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)) {
|
|
13021
13264
|
const g2 = spawnSync3(
|
|
13022
13265
|
"ssh-keygen",
|
|
13023
13266
|
["-t", "ed25519", "-N", "", "-q", "-f", keyPath, "-C", HOST_KEY_COMMENT],
|
|
@@ -13025,7 +13268,7 @@ function provisionHostSshChannel() {
|
|
|
13025
13268
|
);
|
|
13026
13269
|
if (g2.status !== 0) return null;
|
|
13027
13270
|
}
|
|
13028
|
-
const pub =
|
|
13271
|
+
const pub = readFileSync3(`${keyPath}.pub`, "utf8").trim();
|
|
13029
13272
|
if (!pub) return null;
|
|
13030
13273
|
const user = (() => {
|
|
13031
13274
|
try {
|
|
@@ -13034,12 +13277,12 @@ function provisionHostSshChannel() {
|
|
|
13034
13277
|
return process.env.USER || process.env.LOGNAME || "root";
|
|
13035
13278
|
}
|
|
13036
13279
|
})();
|
|
13037
|
-
const userSshDir =
|
|
13038
|
-
|
|
13039
|
-
const authKeys =
|
|
13040
|
-
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") : "";
|
|
13041
13284
|
const next = rewriteHostAuthorizedKeys(existing, pub);
|
|
13042
|
-
if (next !== existing)
|
|
13285
|
+
if (next !== existing) writeFileSync2(authKeys, next, { mode: 384 });
|
|
13043
13286
|
chmodSync(authKeys, 384);
|
|
13044
13287
|
return { user, keyPath };
|
|
13045
13288
|
} catch {
|
|
@@ -13050,6 +13293,11 @@ function composeProjectName(prev) {
|
|
|
13050
13293
|
if (prev.COMPOSE_PROJECT_NAME) return prev.COMPOSE_PROJECT_NAME;
|
|
13051
13294
|
return Object.keys(prev).length > 0 ? "compose" : "openship";
|
|
13052
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
|
+
}
|
|
13053
13301
|
function orphanedStackContainers(project) {
|
|
13054
13302
|
const r2 = spawnSync3(
|
|
13055
13303
|
"docker",
|
|
@@ -13149,7 +13397,7 @@ function migrateLegacyEdgeVolumes(project) {
|
|
|
13149
13397
|
const existing = new Set(ls.stdout.split("\n").map((l2) => l2.trim()).filter(Boolean));
|
|
13150
13398
|
for (const { volume, host } of pairs) {
|
|
13151
13399
|
if (!existing.has(volume)) continue;
|
|
13152
|
-
|
|
13400
|
+
mkdirSync3(host, { recursive: true });
|
|
13153
13401
|
const alreadyThere = spawnSync3("sh", ["-c", `ls -A ${JSON.stringify(host)} 2>/dev/null | head -1`], {
|
|
13154
13402
|
encoding: "utf8"
|
|
13155
13403
|
});
|
|
@@ -13201,9 +13449,11 @@ function keepConfig(prev, key, explicit) {
|
|
|
13201
13449
|
}
|
|
13202
13450
|
function resolveEnvConfig(prev, opts) {
|
|
13203
13451
|
const publicUrl = keepConfig(prev, "OPENSHIP_PUBLIC_URL", opts.publicUrl);
|
|
13452
|
+
const bindAddr = keepConfig(prev, "OPENSHIP_BIND_ADDR");
|
|
13204
13453
|
return {
|
|
13205
|
-
apiPort: keepConfig(prev, "API_PORT", opts.apiPort) ??
|
|
13206
|
-
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 } : {},
|
|
13207
13457
|
...publicUrl ? { publicUrl } : {},
|
|
13208
13458
|
// A public URL always implies a proxy in front; otherwise keep whatever the
|
|
13209
13459
|
// install was configured with.
|
|
@@ -13221,6 +13471,47 @@ function resolveEnvConfig(prev, opts) {
|
|
|
13221
13471
|
hostControl: opts.noHostControl === void 0 ? prev.OPENSHIP_HOST_CONTROL !== "false" : !opts.noHostControl
|
|
13222
13472
|
};
|
|
13223
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
|
+
}
|
|
13224
13515
|
function renderEnv(opts, host, cfg) {
|
|
13225
13516
|
const prev = readEnvFile();
|
|
13226
13517
|
const lines = [
|
|
@@ -13234,13 +13525,21 @@ function renderEnv(opts, host, cfg) {
|
|
|
13234
13525
|
// (hides the local row). Written explicitly so the policy is visible in .env.
|
|
13235
13526
|
`OPENSHIP_HOST_CONTROL=${cfg.hostControl ? "true" : "false"}`,
|
|
13236
13527
|
`OPENSHIP_IMAGE_REGISTRY=${cfg.registry}`,
|
|
13237
|
-
`OPENSHIP_VERSION=${opts.version || (true ? "0.
|
|
13528
|
+
`OPENSHIP_VERSION=${opts.version || (true ? "0.5.0" : "latest")}`,
|
|
13238
13529
|
`POSTGRES_PASSWORD=${keepSecret(prev, "POSTGRES_PASSWORD")}`,
|
|
13530
|
+
// Pinned once (see resolvePgData): fresh install → subdir, existing volume → root.
|
|
13531
|
+
`OPENSHIP_PGDATA=${resolvePgData(prev)}`,
|
|
13239
13532
|
`BETTER_AUTH_SECRET=${keepSecret(prev, "BETTER_AUTH_SECRET")}`,
|
|
13240
13533
|
`INTERNAL_TOKEN=${keepSecret(prev, "INTERNAL_TOKEN")}`,
|
|
13241
13534
|
`API_PORT=${cfg.apiPort}`,
|
|
13242
|
-
`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}`
|
|
13243
13541
|
];
|
|
13542
|
+
if (cfg.bindAddr) lines.push(`OPENSHIP_BIND_ADDR=${cfg.bindAddr}`);
|
|
13244
13543
|
if (cfg.publicUrl) lines.push(`OPENSHIP_PUBLIC_URL=${cfg.publicUrl}`);
|
|
13245
13544
|
if (cfg.extraTrustedOrigins) {
|
|
13246
13545
|
lines.push(`OPENSHIP_EXTRA_TRUSTED_ORIGINS=${cfg.extraTrustedOrigins}`);
|
|
@@ -13260,7 +13559,7 @@ function renderEnv(opts, host, cfg) {
|
|
|
13260
13559
|
function sourceBuildDir() {
|
|
13261
13560
|
const marker = readSourceInstall();
|
|
13262
13561
|
if (!marker?.dir) return null;
|
|
13263
|
-
const hasAll = BUILT_SERVICES.every((s2) =>
|
|
13562
|
+
const hasAll = BUILT_SERVICES.every((s2) => existsSync4(join7(marker.dir, s2.dockerfile)));
|
|
13264
13563
|
return hasAll ? marker.dir : null;
|
|
13265
13564
|
}
|
|
13266
13565
|
function renderBuildOverride(repoDir) {
|
|
@@ -13276,20 +13575,20 @@ services:
|
|
|
13276
13575
|
${services}`;
|
|
13277
13576
|
}
|
|
13278
13577
|
function materialize(opts) {
|
|
13279
|
-
|
|
13578
|
+
mkdirSync3(COMPOSE_DIR, { recursive: true, mode: 448 });
|
|
13280
13579
|
const prev = readEnvFile();
|
|
13281
13580
|
const cfg = resolveEnvConfig(prev, opts);
|
|
13282
13581
|
const host = cfg.hostControl ? provisionHostSshChannel() : null;
|
|
13283
13582
|
let before = "";
|
|
13284
13583
|
try {
|
|
13285
|
-
before =
|
|
13584
|
+
before = readFileSync3(ENV_FILE, "utf8");
|
|
13286
13585
|
} catch {
|
|
13287
13586
|
}
|
|
13288
13587
|
const rendered = renderEnv(opts, host, cfg);
|
|
13289
|
-
|
|
13290
|
-
|
|
13588
|
+
writeFileSync2(COMPOSE_FILE, COMPOSE_YAML);
|
|
13589
|
+
writeFileSync2(ENV_FILE, rendered, { mode: 384 });
|
|
13291
13590
|
const buildDir = opts.build === false ? null : sourceBuildDir();
|
|
13292
|
-
if (buildDir)
|
|
13591
|
+
if (buildDir) writeFileSync2(BUILD_FILE, renderBuildOverride(buildDir));
|
|
13293
13592
|
return { buildDir, cfg, envChanged: before !== "" && rendered !== before };
|
|
13294
13593
|
}
|
|
13295
13594
|
function dbVolumeExists(project) {
|
|
@@ -13307,7 +13606,14 @@ function compose(args, opts) {
|
|
|
13307
13606
|
});
|
|
13308
13607
|
return r2.status ?? 1;
|
|
13309
13608
|
}
|
|
13310
|
-
function
|
|
13609
|
+
function composePrefetch(opts) {
|
|
13610
|
+
const { buildDir } = materialize(opts);
|
|
13611
|
+
if (buildDir) {
|
|
13612
|
+
return compose(["pull", "postgres", "redis"], { withBuildOverride: true }) === 0 && compose(["build"], { withBuildOverride: true }) === 0;
|
|
13613
|
+
}
|
|
13614
|
+
return compose(["pull"]) === 0;
|
|
13615
|
+
}
|
|
13616
|
+
async function composeUp(opts) {
|
|
13311
13617
|
const { buildDir, cfg, envChanged } = materialize(opts);
|
|
13312
13618
|
const apiPort = cfg.apiPort;
|
|
13313
13619
|
const dashPort = cfg.dashPort;
|
|
@@ -13323,12 +13629,20 @@ function composeUp(opts) {
|
|
|
13323
13629
|
if (dbVolumeExists(project)) {
|
|
13324
13630
|
reconcileDbPassword(env.POSTGRES_USER || "openship", env.POSTGRES_PASSWORD ?? "");
|
|
13325
13631
|
}
|
|
13632
|
+
await sanitizeEdgeVhosts(
|
|
13633
|
+
new LocalExecutor(),
|
|
13634
|
+
EDGE_SITES_HOST_DIR,
|
|
13635
|
+
(l2) => console.log(` ${l2.message}`)
|
|
13636
|
+
).catch(() => {
|
|
13637
|
+
});
|
|
13326
13638
|
if (buildDir) {
|
|
13327
|
-
if (
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13639
|
+
if (!opts.alreadyFetched) {
|
|
13640
|
+
if (compose(["pull", "postgres", "redis"], { withBuildOverride: true }) !== 0) {
|
|
13641
|
+
return { ok: false, apiPort, dashPort };
|
|
13642
|
+
}
|
|
13643
|
+
if (compose(["build"], { withBuildOverride: true }) !== 0) {
|
|
13644
|
+
return { ok: false, apiPort, dashPort };
|
|
13645
|
+
}
|
|
13332
13646
|
}
|
|
13333
13647
|
if (up({ withBuildOverride: true }) !== 0) {
|
|
13334
13648
|
return { ok: false, apiPort, dashPort };
|
|
@@ -13337,7 +13651,9 @@ function composeUp(opts) {
|
|
|
13337
13651
|
writeInstallMethod("compose");
|
|
13338
13652
|
return { ok: true, apiPort, dashPort };
|
|
13339
13653
|
}
|
|
13340
|
-
if (compose(["pull"]) !== 0)
|
|
13654
|
+
if (!opts.alreadyFetched && compose(["pull"]) !== 0) {
|
|
13655
|
+
return { ok: false, apiPort, dashPort };
|
|
13656
|
+
}
|
|
13341
13657
|
if (up() !== 0) return { ok: false, apiPort, dashPort };
|
|
13342
13658
|
onEdgeContainerChanged();
|
|
13343
13659
|
writeInstallMethod("compose");
|
|
@@ -13347,12 +13663,12 @@ function onEdgeContainerChanged() {
|
|
|
13347
13663
|
invalidateEdgeContainer();
|
|
13348
13664
|
}
|
|
13349
13665
|
function composeDown() {
|
|
13350
|
-
if (!
|
|
13666
|
+
if (!existsSync4(COMPOSE_FILE)) return false;
|
|
13351
13667
|
return compose(["down"]) === 0;
|
|
13352
13668
|
}
|
|
13353
13669
|
function composeUninstall(opts = {}) {
|
|
13354
13670
|
const removedImages = [];
|
|
13355
|
-
const ok =
|
|
13671
|
+
const ok = existsSync4(COMPOSE_FILE) ? compose(["down", "-v", "--remove-orphans"]) === 0 : false;
|
|
13356
13672
|
if (opts.removeImages) {
|
|
13357
13673
|
const env = readEnvFile();
|
|
13358
13674
|
const registry = env.OPENSHIP_IMAGE_REGISTRY || DEFAULT_IMAGE_REGISTRY;
|
|
@@ -13366,9 +13682,9 @@ function composeUninstall(opts = {}) {
|
|
|
13366
13682
|
}
|
|
13367
13683
|
return { ok, removedImages };
|
|
13368
13684
|
}
|
|
13369
|
-
function composeUpdate(version) {
|
|
13370
|
-
if (!
|
|
13371
|
-
return composeUp(version ? { version } : {}).ok;
|
|
13685
|
+
async function composeUpdate(version) {
|
|
13686
|
+
if (!existsSync4(COMPOSE_FILE)) return false;
|
|
13687
|
+
return (await composeUp(version ? { version } : {})).ok;
|
|
13372
13688
|
}
|
|
13373
13689
|
function composePs() {
|
|
13374
13690
|
return compose(["ps"]);
|
|
@@ -13403,6 +13719,15 @@ async function waitForApiHealth(port, tries) {
|
|
|
13403
13719
|
}
|
|
13404
13720
|
return false;
|
|
13405
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
|
+
}
|
|
13406
13731
|
async function importMigratedSites(apiPort, sites, certPems) {
|
|
13407
13732
|
const token = composeInternalToken();
|
|
13408
13733
|
if (!token) {
|
|
@@ -13419,6 +13744,11 @@ async function importMigratedSites(apiPort, sites, certPems) {
|
|
|
13419
13744
|
spinner.warn(`${error} \u2014 migrated sites not imported. Re-run \`openship up\` to retry.`);
|
|
13420
13745
|
return { ok: false, registered: [], error };
|
|
13421
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
|
+
}
|
|
13422
13752
|
try {
|
|
13423
13753
|
const r2 = await fetch(`http://127.0.0.1:${apiPort}/api/system/edge/import-sites`, {
|
|
13424
13754
|
method: "POST",
|
|
@@ -13478,16 +13808,29 @@ async function importMigratedSites(apiPort, sites, certPems) {
|
|
|
13478
13808
|
export {
|
|
13479
13809
|
OS_DIR,
|
|
13480
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,
|
|
13481
13820
|
shortSha,
|
|
13482
13821
|
prepareFromSource,
|
|
13483
13822
|
readSourceInstall,
|
|
13484
13823
|
remoteSha,
|
|
13485
13824
|
rebuildFromSource,
|
|
13486
13825
|
readInstallMethod,
|
|
13826
|
+
dockerGap,
|
|
13487
13827
|
hasDockerCompose,
|
|
13488
13828
|
composeIsViableDefault,
|
|
13489
13829
|
ensureDocker,
|
|
13830
|
+
composeTrustedOriginUrls,
|
|
13831
|
+
resolveComposePorts,
|
|
13490
13832
|
sourceBuildDir,
|
|
13833
|
+
composePrefetch,
|
|
13491
13834
|
composeUp,
|
|
13492
13835
|
composeDown,
|
|
13493
13836
|
composeUninstall,
|
|
@@ -13498,5 +13841,6 @@ export {
|
|
|
13498
13841
|
composeRestart,
|
|
13499
13842
|
composeInternalToken,
|
|
13500
13843
|
waitForApiHealth,
|
|
13844
|
+
waitForEdgeRunning,
|
|
13501
13845
|
importMigratedSites
|
|
13502
13846
|
};
|