openship 0.6.6 → 0.6.7
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/dist/bare-7QD623QV.js +29 -0
- package/dist/{chunk-Q7WGTQQ5.js → chunk-2ANV5ZQ5.js} +1 -1
- package/dist/{chunk-UJ6JZM65.js → chunk-2VSW6Q6C.js} +601 -382
- package/dist/{chunk-PCPVTH6M.js → chunk-5T4UVOM3.js} +6 -4
- package/dist/{chunk-YMMQRE5J.js → chunk-BLEXTYRK.js} +1 -1
- package/dist/{chunk-OBPKYXQL.js → chunk-CIJV43LH.js} +71 -32
- package/dist/{chunk-NK5N5LXN.js → chunk-DDFUESPJ.js} +24 -9
- package/dist/{chunk-6FH4AXVY.js → chunk-E2X26PXR.js} +1 -1
- package/dist/{chunk-HKRQXPTI.js → chunk-FYPFEDPZ.js} +1 -1
- package/dist/{chunk-UVNMQM7W.js → chunk-FZ6QCF6B.js} +8 -6
- package/dist/{chunk-MLYSEZQB.js → chunk-GGIAIGUL.js} +15 -66
- package/dist/{chunk-2YM3NI2E.js → chunk-GIPJNNI5.js} +38 -44
- package/dist/{chunk-ZFJXPCQP.js → chunk-H3WT3A6R.js} +4 -3
- package/dist/chunk-H4FS5BED.js +936 -0
- package/dist/{chunk-CBLD2HS5.js → chunk-J2XM3QSP.js} +8 -5
- package/dist/{chunk-FE7ZZQSE.js → chunk-J3K6XABC.js} +3 -3
- package/dist/{chunk-77QDF6UD.js → chunk-LHFXSC36.js} +428 -93
- package/dist/{chunk-YIMOCZBZ.js → chunk-MEE6OZFP.js} +3 -3
- package/dist/chunk-N5YZMO33.js +111 -0
- package/dist/{chunk-QVW76WDL.js → chunk-NRTJRNAE.js} +58 -16
- package/dist/{chunk-WVAML4H6.js → chunk-PPEBIY5F.js} +247 -61
- package/dist/{chunk-4I2BM2DT.js → chunk-PVP4RGWT.js} +3 -3
- package/dist/{chunk-RSR7PFIQ.js → chunk-QHMDCYZ3.js} +4 -4
- package/dist/chunk-R4VDPFLS.js +106 -0
- package/dist/{chunk-VTGPEC6B.js → chunk-RDXT4BQB.js} +6 -4
- package/dist/{chunk-LFE5KQ7Y.js → chunk-S6B2Z3OO.js} +142 -57
- package/dist/chunk-UWEV6XTS.js +249 -0
- package/dist/{chunk-5DMWSTOM.js → chunk-VQM6JHJB.js} +940 -224
- package/dist/{chunk-SM26HDS2.js → chunk-VSTFHUCV.js} +44 -50
- package/dist/chunk-WU54BXFE.js +70 -0
- package/dist/{chunk-LUKYPVJ4.js → chunk-XWHWTHEL.js} +313 -469
- package/dist/{chunk-A4GXYE4L.js → chunk-ZPJ74LXE.js} +22 -20
- package/dist/cloud-KNNABE3O.js +40 -0
- package/dist/{detect-OIE5V3VZ.js → detect-G4DR3U2N.js} +9 -6
- package/dist/{docker-VTR2GK5Y.js → docker-YOVPCIB4.js} +11 -8
- package/dist/{docker-edge-executor-22IBXNYJ.js → docker-edge-executor-QFXD3337.js} +8 -6
- package/dist/edge-import-CRFVSR6P.js +56 -0
- package/dist/{edge-preflight-GBNCPNVU.js → edge-preflight-MXERVAMG.js} +15 -12
- package/dist/{ensure-container-edge-KPI2CVXJ.js → ensure-container-edge-F4JUNQIK.js} +13 -10
- package/dist/{executor-7LXXZPBR.js → executor-CVPAYYHR.js} +8 -6
- package/dist/host-channel-preflight-QTDNGLCK.js +54 -0
- package/dist/index.js +84 -48
- package/dist/{local-executor-WOFZD72X.js → local-executor-ODC6RANU.js} +3 -2
- package/dist/{nginx-KUFMLMAC.js → nginx-KLFF43TC.js} +11 -8
- package/dist/{openresty-lua-ATTH35BE.js → openresty-lua-VKWUV4GB.js} +6 -5
- package/dist/{privilege-I4W3ZNZK.js → privilege-7D3SSOL3.js} +6 -5
- package/dist/server/index.js +8813 -5232
- package/dist/setup-I5AJC6NJ.js +25 -0
- package/dist/{ssh-client-JBMA7SN4.js → ssh-client-625VWGKP.js} +3 -2
- package/dist/up-QXTOOVXC.js +61 -0
- package/package.json +1 -1
- package/dist/bare-OXJVW3AK.js +0 -25
- package/dist/chunk-LH2PLRQO.js +0 -434
- package/dist/cloud-75VIT4KV.js +0 -37
- package/dist/edge-import-WJUB4M7F.js +0 -52
- package/dist/host-channel-preflight-KE6OONYK.js +0 -50
- package/dist/setup-XGLGUUFW.js +0 -22
- package/dist/up-K4BV7466.js +0 -57
|
@@ -7,42 +7,59 @@ import {
|
|
|
7
7
|
resolveProjectDir,
|
|
8
8
|
resolveStaticOutputPath,
|
|
9
9
|
runLocalBuild
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MEE6OZFP.js";
|
|
11
|
+
import {
|
|
12
|
+
isArtifactPathRef,
|
|
13
|
+
removeManagedArtifact
|
|
14
|
+
} from "./chunk-WU54BXFE.js";
|
|
11
15
|
import {
|
|
12
16
|
BuildCancelledError,
|
|
13
17
|
BuildLogger,
|
|
14
18
|
detectBuildKillHint,
|
|
19
|
+
droppedRuntimeEnvMessage,
|
|
20
|
+
isExcludedDocRootEntry,
|
|
15
21
|
killProcessesUnderDir,
|
|
16
22
|
parseLogLevel,
|
|
17
23
|
runBuildPipeline,
|
|
24
|
+
splitRuntimeEnv,
|
|
18
25
|
sq,
|
|
19
26
|
transferLocalDirectory
|
|
20
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-H4FS5BED.js";
|
|
21
28
|
import {
|
|
22
29
|
LocalExecutor
|
|
23
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-E2X26PXR.js";
|
|
24
31
|
import {
|
|
25
32
|
privilegedExecutor
|
|
26
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-PVP4RGWT.js";
|
|
27
34
|
import {
|
|
28
35
|
HOST_STATE_DIR,
|
|
29
36
|
envOps,
|
|
30
37
|
hostRefusal,
|
|
31
38
|
opScript
|
|
32
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-BLEXTYRK.js";
|
|
33
40
|
import {
|
|
34
41
|
ensureOwnedDir,
|
|
35
42
|
resolveEnvironment
|
|
36
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-FZ6QCF6B.js";
|
|
37
44
|
import {
|
|
38
45
|
sq as sq2,
|
|
39
46
|
wrapLocalBuildCommand
|
|
40
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-H3WT3A6R.js";
|
|
48
|
+
import {
|
|
49
|
+
portOccupantDetails,
|
|
50
|
+
probeListeningPort
|
|
51
|
+
} from "./chunk-VSTFHUCV.js";
|
|
41
52
|
import {
|
|
53
|
+
managedDeploymentUnitName,
|
|
42
54
|
probeExec,
|
|
43
|
-
probeListeningPort,
|
|
44
55
|
withReason
|
|
45
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-UWEV6XTS.js";
|
|
57
|
+
import {
|
|
58
|
+
formatDuration,
|
|
59
|
+
isRemoteConnectionError,
|
|
60
|
+
isRetryableRemoteConnectionError,
|
|
61
|
+
systemDebug
|
|
62
|
+
} from "./chunk-N5YZMO33.js";
|
|
46
63
|
import {
|
|
47
64
|
DeployError,
|
|
48
65
|
LANGUAGES,
|
|
@@ -50,16 +67,13 @@ import {
|
|
|
50
67
|
answered,
|
|
51
68
|
appVolumeTargets,
|
|
52
69
|
buildOutputTransferExcludes,
|
|
53
|
-
formatDuration,
|
|
54
|
-
isRemoteConnectionError,
|
|
55
|
-
isRetryableRemoteConnectionError,
|
|
56
70
|
missingOutputDirectoryMessage,
|
|
71
|
+
nodeBinPathExport,
|
|
57
72
|
packageManagerEnsureCommand,
|
|
58
73
|
refused,
|
|
59
74
|
safeErrorMessage,
|
|
60
|
-
shellQuote
|
|
61
|
-
|
|
62
|
-
} from "./chunk-UJ6JZM65.js";
|
|
75
|
+
shellQuote
|
|
76
|
+
} from "./chunk-2VSW6Q6C.js";
|
|
63
77
|
|
|
64
78
|
// ../../packages/adapters/src/system/remote-journal.ts
|
|
65
79
|
var OPSH_RUN_VERSION = 1;
|
|
@@ -1114,7 +1128,6 @@ async function sampleBareUsage(executor, pid, cgroupCandidates = []) {
|
|
|
1114
1128
|
}
|
|
1115
1129
|
|
|
1116
1130
|
// ../../packages/adapters/src/runtime/supervisor/systemd.ts
|
|
1117
|
-
var UNIT_PREFIX = "openship";
|
|
1118
1131
|
function escapeSystemdEnvValue(value) {
|
|
1119
1132
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/%/g, "%%").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
1120
1133
|
}
|
|
@@ -1126,7 +1139,7 @@ var SystemdSupervisor = class {
|
|
|
1126
1139
|
name = "systemd";
|
|
1127
1140
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
1128
1141
|
unitName(deploymentId) {
|
|
1129
|
-
return
|
|
1142
|
+
return managedDeploymentUnitName(deploymentId);
|
|
1130
1143
|
}
|
|
1131
1144
|
unitPath(deploymentId) {
|
|
1132
1145
|
return `/etc/systemd/system/${this.unitName(deploymentId)}`;
|
|
@@ -1172,7 +1185,7 @@ Restart=on-failure
|
|
|
1172
1185
|
RestartSec=3
|
|
1173
1186
|
StandardOutput=journal
|
|
1174
1187
|
StandardError=journal
|
|
1175
|
-
SyslogIdentifier=${
|
|
1188
|
+
SyslogIdentifier=${this.unitName(opts.deploymentId).replace(/\.service$/, "")}
|
|
1176
1189
|
|
|
1177
1190
|
[Install]
|
|
1178
1191
|
WantedBy=multi-user.target
|
|
@@ -1206,16 +1219,7 @@ WantedBy=multi-user.target
|
|
|
1206
1219
|
throw new DeployError(
|
|
1207
1220
|
`Port ${opts.port} is already in use` + (occupant ? ` by ${occupant.command}` : "") + ". Stop the existing process before deploying.",
|
|
1208
1221
|
"PORT_IN_USE",
|
|
1209
|
-
|
|
1210
|
-
port: opts.port,
|
|
1211
|
-
pid: occupant?.pid,
|
|
1212
|
-
command: occupant?.command,
|
|
1213
|
-
rawCommand: occupant?.rawCommand,
|
|
1214
|
-
systemdUnit: occupant?.systemdUnit,
|
|
1215
|
-
systemdDescription: occupant?.systemdDescription,
|
|
1216
|
-
deploymentId: occupant?.deploymentId,
|
|
1217
|
-
isManagedDeployment: occupant?.isManagedDeployment
|
|
1218
|
-
}
|
|
1222
|
+
portOccupantDetails(opts.port, occupant)
|
|
1219
1223
|
);
|
|
1220
1224
|
}
|
|
1221
1225
|
const msg = hint ? `Process exited immediately after start. Last output:
|
|
@@ -1424,7 +1428,7 @@ var NohupSupervisor = class {
|
|
|
1424
1428
|
throw new DeployError(
|
|
1425
1429
|
`Port ${opts.port} is already in use` + (occupant ? ` by ${occupant.command}` : "") + ". Stop the existing process before deploying.",
|
|
1426
1430
|
"PORT_IN_USE",
|
|
1427
|
-
|
|
1431
|
+
portOccupantDetails(opts.port, occupant)
|
|
1428
1432
|
);
|
|
1429
1433
|
}
|
|
1430
1434
|
const msg = hint ? `Process exited immediately after spawn. Last output:
|
|
@@ -1690,6 +1694,7 @@ var BareRuntime = class {
|
|
|
1690
1694
|
async promoteBuildArtifact(artifactPath, deploymentId, previousDeploymentId) {
|
|
1691
1695
|
const releaseDir = this.releaseDir(deploymentId);
|
|
1692
1696
|
if (artifactPath === releaseDir) return releaseDir;
|
|
1697
|
+
const consumeSource = !artifactPath.startsWith(`${this.workDir}/releases/`);
|
|
1693
1698
|
await ensureOwnedDir(this.executor, `${this.workDir}/releases`);
|
|
1694
1699
|
await this.executor.rm(releaseDir);
|
|
1695
1700
|
const previousReleaseDir = previousDeploymentId ? this.releaseDir(previousDeploymentId) : void 0;
|
|
@@ -1700,7 +1705,7 @@ var BareRuntime = class {
|
|
|
1700
1705
|
`deploy:${deploymentId}:promote-rsync`,
|
|
1701
1706
|
`rsync -a --delete --link-dest=${sq(previousReleaseDir)} ${sq(artifactPath)}/ ${sq(releaseDir)}/`
|
|
1702
1707
|
);
|
|
1703
|
-
await this.executor.rm(artifactPath).catch(() => {
|
|
1708
|
+
if (consumeSource) await this.executor.rm(artifactPath).catch(() => {
|
|
1704
1709
|
});
|
|
1705
1710
|
return releaseDir;
|
|
1706
1711
|
} catch {
|
|
@@ -1708,6 +1713,14 @@ var BareRuntime = class {
|
|
|
1708
1713
|
});
|
|
1709
1714
|
}
|
|
1710
1715
|
}
|
|
1716
|
+
if (!consumeSource) {
|
|
1717
|
+
await execReliable(
|
|
1718
|
+
this.executor,
|
|
1719
|
+
`deploy:${deploymentId}:promote-copy`,
|
|
1720
|
+
`mkdir -p ${sq(releaseDir)} && cp -a ${sq(artifactPath)}/. ${sq(releaseDir)}/`
|
|
1721
|
+
);
|
|
1722
|
+
return releaseDir;
|
|
1723
|
+
}
|
|
1711
1724
|
await execReliable(
|
|
1712
1725
|
this.executor,
|
|
1713
1726
|
`deploy:${deploymentId}:promote-mv`,
|
|
@@ -1947,19 +1960,29 @@ var BareRuntime = class {
|
|
|
1947
1960
|
) : stagedDir;
|
|
1948
1961
|
await this.linkPersistentPaths(workDir, config.projectId, config.volumes, _onLog);
|
|
1949
1962
|
const sv = await this.supervisor();
|
|
1963
|
+
const projectEnv = splitRuntimeEnv(
|
|
1964
|
+
Object.fromEntries(Object.entries(config.envVars ?? {}).map(([k, v]) => [k, String(v)]))
|
|
1965
|
+
);
|
|
1966
|
+
if (projectEnv.dropped.length > 0) {
|
|
1967
|
+
_onLog?.({
|
|
1968
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1969
|
+
level: "warn",
|
|
1970
|
+
message: droppedRuntimeEnvMessage(projectEnv.dropped)
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1950
1973
|
const env = {
|
|
1951
|
-
...Object.fromEntries(
|
|
1952
|
-
Object.entries(config.envVars ?? {}).map(([k, v]) => [k, String(v)])
|
|
1953
|
-
),
|
|
1974
|
+
...Object.fromEntries(projectEnv.entries),
|
|
1954
1975
|
PORT: String(config.port),
|
|
1955
1976
|
NODE_ENV: config.environment === "production" ? "production" : "development"
|
|
1956
1977
|
};
|
|
1978
|
+
const startBinPath = nodeBinPathExport(config.packageManager, [workDir]);
|
|
1979
|
+
const resolvedStart = config.startCommand || "npm start";
|
|
1957
1980
|
try {
|
|
1958
1981
|
await sv.deploy({
|
|
1959
1982
|
deploymentId: config.deploymentId,
|
|
1960
1983
|
projectId: config.projectId,
|
|
1961
1984
|
workDir,
|
|
1962
|
-
startCommand:
|
|
1985
|
+
startCommand: startBinPath ? `${startBinPath} && ${resolvedStart}` : resolvedStart,
|
|
1963
1986
|
port: config.port,
|
|
1964
1987
|
env
|
|
1965
1988
|
});
|
|
@@ -1979,13 +2002,40 @@ var BareRuntime = class {
|
|
|
1979
2002
|
};
|
|
1980
2003
|
}
|
|
1981
2004
|
async deployStatic(config) {
|
|
1982
|
-
const
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
config.
|
|
1986
|
-
config.
|
|
1987
|
-
|
|
1988
|
-
|
|
2005
|
+
const workDir = await this.promoteStaticRelease({
|
|
2006
|
+
artifactPath: config.imageRef ?? this.projectDir(config.projectId),
|
|
2007
|
+
releaseId: config.deploymentId,
|
|
2008
|
+
previousReleaseId: config.previousDeploymentId,
|
|
2009
|
+
outputDirectory: config.outputDirectory,
|
|
2010
|
+
promote: Boolean(config.imageRef)
|
|
2011
|
+
});
|
|
2012
|
+
return {
|
|
2013
|
+
deploymentId: config.deploymentId,
|
|
2014
|
+
containerId: workDir,
|
|
2015
|
+
status: "running"
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
/**
|
|
2019
|
+
* Promote an extracted static build into a stable release directory and PROVE it
|
|
2020
|
+
* can serve, returning the release dir.
|
|
2021
|
+
*
|
|
2022
|
+
* Split out of `deployStatic` so the compose/monorepo static path reuses the
|
|
2023
|
+
* identical promote + validation instead of registering a vhost straight at the
|
|
2024
|
+
* `.builds/<session>` staging dir. Two things came from that shortcut: the
|
|
2025
|
+
* public doc-root of a compose static sub-app was a per-build-session scratch
|
|
2026
|
+
* directory (nothing owned its lifetime, so superseded copies accumulated and
|
|
2027
|
+
* any sweep of `.builds` would take the live site down), and it skipped the only
|
|
2028
|
+
* HARD deploy-time output gate we have — so an extract that produced nothing
|
|
2029
|
+
* deployed green and 404'd.
|
|
2030
|
+
*
|
|
2031
|
+
* `releaseId` is the release's identity, not necessarily a deployment id: the
|
|
2032
|
+
* compose path passes `<deploymentId>-<serviceId>` so each static sub-app owns
|
|
2033
|
+
* its own release dir under one deployment.
|
|
2034
|
+
*/
|
|
2035
|
+
async promoteStaticRelease(opts) {
|
|
2036
|
+
const stagedDir = opts.artifactPath;
|
|
2037
|
+
const workDir = opts.promote === false ? stagedDir : await this.promoteBuildArtifact(stagedDir, opts.releaseId, opts.previousReleaseId);
|
|
2038
|
+
const staticRoot = resolveStaticOutputPath(workDir, opts.outputDirectory);
|
|
1989
2039
|
const abort = async (message) => {
|
|
1990
2040
|
if (workDir !== stagedDir) {
|
|
1991
2041
|
await this.executor.rm(workDir).catch(() => {
|
|
@@ -1994,18 +2044,47 @@ var BareRuntime = class {
|
|
|
1994
2044
|
throw new Error(message);
|
|
1995
2045
|
};
|
|
1996
2046
|
if (!await this.executor.exists(staticRoot)) {
|
|
1997
|
-
return abort(missingOutputDirectoryMessage(
|
|
2047
|
+
return abort(missingOutputDirectoryMessage(opts.outputDirectory));
|
|
2048
|
+
}
|
|
2049
|
+
if (staticRoot === workDir) {
|
|
2050
|
+
const undeleted = await this.pruneBuildFilesFromDocRoot(staticRoot);
|
|
2051
|
+
if (undeleted.length > 0) {
|
|
2052
|
+
return abort(
|
|
2053
|
+
`Refusing to publish ${staticRoot}: could not remove ${undeleted.join(", ")} from the document root. Serving them would expose the repository (and, for a private repo, the access token in .git/config) at the site's public URL.`
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
1998
2056
|
}
|
|
1999
2057
|
if (await this.isEmptyDir(staticRoot)) {
|
|
2000
2058
|
return abort(
|
|
2001
|
-
`The output directory "${
|
|
2059
|
+
`The output directory "${opts.outputDirectory || "."}" is empty \u2014 the build produced no files to serve, so every request would 404. Check the build command and the Output Directory setting.`
|
|
2002
2060
|
);
|
|
2003
2061
|
}
|
|
2004
|
-
return
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2062
|
+
return workDir;
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Remove top-level build inputs from a doc root, returning the names it could NOT
|
|
2066
|
+
* remove (empty on success, and on "there was nothing to remove").
|
|
2067
|
+
*
|
|
2068
|
+
* Reuses `isExcludedDocRootEntry` rather than re-listing the names, because the
|
|
2069
|
+
* Docker sandbox already owns that list and two copies of "what must not be
|
|
2070
|
+
* served" is precisely how the bare path came to serve `.git`.
|
|
2071
|
+
*
|
|
2072
|
+
* An unreadable listing returns empty — a probe that cannot read must not fail a
|
|
2073
|
+
* deploy — but a listed entry that survives its `rm` is reported, because the
|
|
2074
|
+
* caller must refuse to publish rather than expose a token.
|
|
2075
|
+
*/
|
|
2076
|
+
async pruneBuildFilesFromDocRoot(root) {
|
|
2077
|
+
const listed = await this.executor.exec(`ls -A ${sq(root)} 2>/dev/null; true`).catch(() => null);
|
|
2078
|
+
if (listed === null) return [];
|
|
2079
|
+
const doomed = listed.split("\n").map((l) => l.trim()).filter(Boolean).filter(isExcludedDocRootEntry);
|
|
2080
|
+
if (doomed.length === 0) return [];
|
|
2081
|
+
await this.executor.exec(`rm -rf ${doomed.map((n) => sq(`${root}/${n}`)).join(" ")}`).catch(() => {
|
|
2082
|
+
});
|
|
2083
|
+
const survivors = [];
|
|
2084
|
+
for (const name of doomed) {
|
|
2085
|
+
if (await this.executor.exists(`${root}/${name}`)) survivors.push(name);
|
|
2086
|
+
}
|
|
2087
|
+
return survivors;
|
|
2009
2088
|
}
|
|
2010
2089
|
/**
|
|
2011
2090
|
* Is this an existing directory with no entries? Any inconclusive answer (not a
|
|
@@ -2033,8 +2112,8 @@ var BareRuntime = class {
|
|
|
2033
2112
|
await sv.restart(containerId);
|
|
2034
2113
|
}
|
|
2035
2114
|
async destroy(containerId) {
|
|
2036
|
-
if (containerId
|
|
2037
|
-
await this.executor
|
|
2115
|
+
if (isArtifactPathRef(containerId)) {
|
|
2116
|
+
await removeManagedArtifact(this.executor, containerId);
|
|
2038
2117
|
return;
|
|
2039
2118
|
}
|
|
2040
2119
|
const sv = await this.supervisor();
|
|
@@ -2078,15 +2157,21 @@ var BareRuntime = class {
|
|
|
2078
2157
|
}
|
|
2079
2158
|
}
|
|
2080
2159
|
async purge(deployment) {
|
|
2160
|
+
const failures = [];
|
|
2081
2161
|
if (deployment.containerId) {
|
|
2082
|
-
|
|
2083
|
-
await this.destroy(deployment.containerId);
|
|
2084
|
-
} catch {
|
|
2085
|
-
}
|
|
2162
|
+
await this.destroy(deployment.containerId).catch((err) => failures.push(err));
|
|
2086
2163
|
}
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2164
|
+
const releaseDir = this.releaseDir(deployment.id);
|
|
2165
|
+
if (releaseDir !== deployment.containerId) {
|
|
2166
|
+
await removeManagedArtifact(this.executor, releaseDir).catch(
|
|
2167
|
+
(err) => failures.push(err)
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
if (failures.length === 1) throw failures[0];
|
|
2171
|
+
if (failures.length > 1) {
|
|
2172
|
+
throw new Error(
|
|
2173
|
+
failures.map((err) => err instanceof Error ? err.message : String(err)).join("; ")
|
|
2174
|
+
);
|
|
2090
2175
|
}
|
|
2091
2176
|
}
|
|
2092
2177
|
// ── Observability ──────────────────────────────────────────────────────
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
|
|
3
|
+
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
|
+
const require = __ospCreateRequire(import.meta.url);
|
|
5
|
+
import {
|
|
6
|
+
formatDuration,
|
|
7
|
+
isRemoteConnectionError,
|
|
8
|
+
systemDebug
|
|
9
|
+
} from "./chunk-N5YZMO33.js";
|
|
10
|
+
import {
|
|
11
|
+
safeErrorMessage,
|
|
12
|
+
shellQuote
|
|
13
|
+
} from "./chunk-2VSW6Q6C.js";
|
|
14
|
+
|
|
15
|
+
// ../../packages/adapters/src/system/probe-exec.ts
|
|
16
|
+
async function tryExec(executor, command, opts) {
|
|
17
|
+
try {
|
|
18
|
+
return opts ? await executor.exec(command, opts) : await executor.exec(command);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function probeExec(executor, command, scope) {
|
|
24
|
+
const startedAt = Date.now();
|
|
25
|
+
systemDebug(scope, `exec:start ${command}`);
|
|
26
|
+
try {
|
|
27
|
+
const result = await executor.exec(command, { timeout: 1e4 });
|
|
28
|
+
systemDebug(scope, `exec:ok ${command} (${formatDuration(startedAt)})`);
|
|
29
|
+
return { output: result.trim(), error: null };
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const msg = safeErrorMessage(err);
|
|
32
|
+
if (isRemoteConnectionError(err)) {
|
|
33
|
+
systemDebug(scope, `exec:abort ${command} (${formatDuration(startedAt)}) ${msg}`);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
systemDebug(scope, `exec:fail ${command} (${formatDuration(startedAt)}) ${msg}`);
|
|
37
|
+
return { output: "", error: msg || `\`${command}\` failed` };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function withReason(message, error) {
|
|
41
|
+
if (!error) return message;
|
|
42
|
+
const first = error.split("\n").map((line) => line.trim()).find(Boolean);
|
|
43
|
+
if (!first) return message;
|
|
44
|
+
return `${message} \u2014 ${first.length > 200 ? `${first.slice(0, 199)}\u2026` : first}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ../../packages/adapters/src/infra/mail-container.ts
|
|
48
|
+
var MAIL_HOST_STATE_DIR = "/var/lib/openship/mail";
|
|
49
|
+
var MAIL_CONTAINER_MOUNTS = [
|
|
50
|
+
{ host: `${MAIL_HOST_STATE_DIR}/vmail`, container: "/var/vmail" },
|
|
51
|
+
// The Postfix queue. A recreate on an ephemeral layer would drop in-flight and
|
|
52
|
+
// deferred mail without this — keep it on the host.
|
|
53
|
+
{ host: `${MAIL_HOST_STATE_DIR}/spool`, container: "/var/spool/postfix" },
|
|
54
|
+
// DKIM private keys. NEVER baked, never regenerated on pull — a fresh selector
|
|
55
|
+
// breaks DMARC until DNS repropagates.
|
|
56
|
+
{ host: `${MAIL_HOST_STATE_DIR}/dkim`, container: "/var/lib/dkim" },
|
|
57
|
+
// Mutable daemon config the admin layer edits (sasl_passwd, relayhost maps,
|
|
58
|
+
// amavis 50-user). Seeded from baked defaults on first boot.
|
|
59
|
+
{ host: `${MAIL_HOST_STATE_DIR}/config/postfix`, container: "/etc/postfix", seed: true },
|
|
60
|
+
{ host: `${MAIL_HOST_STATE_DIR}/config/dovecot`, container: "/etc/dovecot", seed: true },
|
|
61
|
+
{ host: `${MAIL_HOST_STATE_DIR}/config/amavis`, container: "/etc/amavis/conf.d", seed: true },
|
|
62
|
+
// ClamAV signatures — bind-mounted so a pull doesn't force a multi-hundred-MB
|
|
63
|
+
// freshclam re-download and delay readiness. `seed: true` because that same mount
|
|
64
|
+
// HIDES the database baked into the image: with nothing copying it across, clamd
|
|
65
|
+
// finds an empty database directory and exits (issue #565).
|
|
66
|
+
{ host: `${MAIL_HOST_STATE_DIR}/clamav`, container: "/var/lib/clamav", seed: true },
|
|
67
|
+
{ host: "/etc/letsencrypt", container: "/etc/letsencrypt", readonly: true }
|
|
68
|
+
];
|
|
69
|
+
var MAIL_DB_HOST_DATA_DIR = `${MAIL_HOST_STATE_DIR}/pgdata`;
|
|
70
|
+
var MAIL_DB_CONTAINER_DATA_DIR = "/var/lib/postgresql/data";
|
|
71
|
+
var MAIL_DB_PGDATA = `${MAIL_DB_CONTAINER_DATA_DIR}/pgdata`;
|
|
72
|
+
var MAIL_HOST_PATHS = {
|
|
73
|
+
saslPasswd: `${MAIL_HOST_STATE_DIR}/config/postfix/sasl_passwd`,
|
|
74
|
+
senderRelayhost: `${MAIL_HOST_STATE_DIR}/config/postfix/sender_relayhost`,
|
|
75
|
+
// Per-nexthop TLS policy. Only split ("selected"-scope) relaying writes it: the
|
|
76
|
+
// relay hop must be `encrypt` so SASL credentials never cross a plaintext
|
|
77
|
+
// connection, while direct-to-MX delivery for the un-relayed domains stays
|
|
78
|
+
// opportunistic. A GLOBAL smtp_tls_security_level can't express both.
|
|
79
|
+
relayTlsPolicy: `${MAIL_HOST_STATE_DIR}/config/postfix/openship_tls_policy`,
|
|
80
|
+
amavisUserConf: `${MAIL_HOST_STATE_DIR}/config/amavis/50-user`
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ../../packages/adapters/src/system/port-owner.ts
|
|
84
|
+
var OPENSHIP_LABEL = {
|
|
85
|
+
project: "openship.project",
|
|
86
|
+
deployment: "openship.deployment",
|
|
87
|
+
service: "openship.service",
|
|
88
|
+
build: "openship.build"
|
|
89
|
+
};
|
|
90
|
+
var PS_FIELDS = [
|
|
91
|
+
"{{.ID}}",
|
|
92
|
+
"{{.Names}}",
|
|
93
|
+
"{{.Image}}",
|
|
94
|
+
`{{.Label "${OPENSHIP_LABEL.project}"}}`,
|
|
95
|
+
`{{.Label "${OPENSHIP_LABEL.deployment}"}}`,
|
|
96
|
+
`{{.Label "${OPENSHIP_LABEL.service}"}}`,
|
|
97
|
+
`{{.Label "${OPENSHIP_LABEL.build}"}}`,
|
|
98
|
+
'{{.Label "com.docker.compose.project"}}',
|
|
99
|
+
'{{.Label "com.docker.compose.service"}}'
|
|
100
|
+
];
|
|
101
|
+
var PS_FORMAT = PS_FIELDS.join("\\t");
|
|
102
|
+
function parsePsLines(out) {
|
|
103
|
+
const containers = [];
|
|
104
|
+
for (const line of out.split("\n")) {
|
|
105
|
+
if (!line.trim()) continue;
|
|
106
|
+
const f = line.split(" ");
|
|
107
|
+
const id = f[0]?.trim();
|
|
108
|
+
const name = f[1]?.trim();
|
|
109
|
+
if (!id || !name || !/^[0-9a-f]{12,64}$/i.test(id)) continue;
|
|
110
|
+
containers.push({
|
|
111
|
+
id,
|
|
112
|
+
name: name.replace(/^\//, ""),
|
|
113
|
+
image: f[2]?.trim() ?? "",
|
|
114
|
+
...f[3]?.trim() ? { projectId: f[3].trim() } : {},
|
|
115
|
+
...f[4]?.trim() ? { deploymentId: f[4].trim() } : {},
|
|
116
|
+
...f[5]?.trim() ? { serviceName: f[5].trim() } : {},
|
|
117
|
+
...f[6]?.trim() ? { buildSessionId: f[6].trim() } : {},
|
|
118
|
+
...f[7]?.trim() ? { composeProject: f[7].trim() } : {},
|
|
119
|
+
...f[8]?.trim() ? { composeService: f[8].trim() } : {}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return containers;
|
|
123
|
+
}
|
|
124
|
+
async function queryContainers(executor, filter) {
|
|
125
|
+
const out = await tryExec(
|
|
126
|
+
executor,
|
|
127
|
+
`docker ps --no-trunc --filter ${filter} --format '${PS_FORMAT}' 2>/dev/null`
|
|
128
|
+
);
|
|
129
|
+
return out === null ? null : parsePsLines(out);
|
|
130
|
+
}
|
|
131
|
+
async function resolveContainerPublishingPort(executor, port) {
|
|
132
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return { kind: "unknown" };
|
|
133
|
+
const containers = await queryContainers(executor, `publish=${port}`);
|
|
134
|
+
if (containers === null) return { kind: "unknown" };
|
|
135
|
+
if (containers.length === 0) return { kind: "none" };
|
|
136
|
+
if (containers.length === 1) return { kind: "one", container: containers[0] };
|
|
137
|
+
return { kind: "ambiguous", containers };
|
|
138
|
+
}
|
|
139
|
+
function containerIdFromCgroup(cgroup) {
|
|
140
|
+
const match = cgroup?.match(/(?:^|[^0-9a-f])([0-9a-f]{64})(?:[^0-9a-f]|$)/);
|
|
141
|
+
return match?.[1] ?? null;
|
|
142
|
+
}
|
|
143
|
+
async function stopContainerDurably(executor, ref) {
|
|
144
|
+
await tryExec(executor, `docker update --restart=no ${shellQuote(ref)} 2>/dev/null || true`);
|
|
145
|
+
await tryExec(executor, `docker stop ${shellQuote(ref)} 2>/dev/null || true`);
|
|
146
|
+
}
|
|
147
|
+
async function describeContainerById(executor, containerId) {
|
|
148
|
+
if (!/^[0-9a-f]{12,64}$/i.test(containerId)) return null;
|
|
149
|
+
const containers = await queryContainers(executor, `id=${shellQuote(containerId)}`);
|
|
150
|
+
return containers?.[0] ?? null;
|
|
151
|
+
}
|
|
152
|
+
var EDGE_CONTAINER_NAME = "openship-edge";
|
|
153
|
+
function isOurEdgeContainer(name, image) {
|
|
154
|
+
return /openship-edge/i.test(`${name ?? ""} ${image ?? ""}`);
|
|
155
|
+
}
|
|
156
|
+
var PORT_FORWARDER_NAMES = [
|
|
157
|
+
"docker-proxy",
|
|
158
|
+
"rootlesskit",
|
|
159
|
+
"rootlesskit-docker-proxy",
|
|
160
|
+
"slirp4netns",
|
|
161
|
+
"pasta",
|
|
162
|
+
// LXD/Incus publish a container port with `forkproxy`, whose cgroup is the snap's
|
|
163
|
+
// daemon unit — the same misattribution with a different daemon.
|
|
164
|
+
"forkproxy"
|
|
165
|
+
];
|
|
166
|
+
function isContainerPortForwarder(command) {
|
|
167
|
+
const argv0 = command?.trim().split(/\s+/)[0];
|
|
168
|
+
if (!argv0) return false;
|
|
169
|
+
const base = argv0.replace(/.*\//, "");
|
|
170
|
+
return PORT_FORWARDER_NAMES.some(
|
|
171
|
+
(name) => base === name || base.startsWith(`${name}-`) || base.length >= 15 && name.startsWith(base)
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
var MANAGED_DEPLOYMENT_UNIT_RE = /^openship-(dep_[A-Za-z0-9_-]+)\.service$/;
|
|
175
|
+
function managedDeploymentUnitName(deploymentId) {
|
|
176
|
+
return `openship-${deploymentId}.service`;
|
|
177
|
+
}
|
|
178
|
+
function managedDeploymentIdFromUnit(unit) {
|
|
179
|
+
return unit?.match(MANAGED_DEPLOYMENT_UNIT_RE)?.[1] ?? null;
|
|
180
|
+
}
|
|
181
|
+
var PROTECTED_UNITS = /* @__PURE__ */ new Set([
|
|
182
|
+
"docker",
|
|
183
|
+
"docker.socket",
|
|
184
|
+
"dockerd",
|
|
185
|
+
"snap.docker.dockerd",
|
|
186
|
+
"containerd",
|
|
187
|
+
"containerd.socket",
|
|
188
|
+
"podman",
|
|
189
|
+
// Other container/VM supervisors whose forwarders land on their daemon unit.
|
|
190
|
+
"lxd",
|
|
191
|
+
"snap.lxd.daemon",
|
|
192
|
+
"incus",
|
|
193
|
+
"k3s",
|
|
194
|
+
"k3s-agent",
|
|
195
|
+
"kubelet",
|
|
196
|
+
"libvirtd",
|
|
197
|
+
"ssh",
|
|
198
|
+
"sshd",
|
|
199
|
+
"dbus",
|
|
200
|
+
"systemd-resolved",
|
|
201
|
+
"systemd-networkd",
|
|
202
|
+
"systemd-logind",
|
|
203
|
+
// The control plane itself, and a from-source dev install of it.
|
|
204
|
+
"openship",
|
|
205
|
+
"openship-dev"
|
|
206
|
+
]);
|
|
207
|
+
var USER_MANAGER_UNIT_RE = /^user@\d+$/;
|
|
208
|
+
function classifyProxy(text) {
|
|
209
|
+
if (!text) return void 0;
|
|
210
|
+
const t = text.toLowerCase();
|
|
211
|
+
if (t.includes("openresty")) return "openresty";
|
|
212
|
+
if (/(^|[\s/:])nginx/.test(t)) return "nginx";
|
|
213
|
+
if (/(^|[\s/:])caddy/.test(t)) return "caddy";
|
|
214
|
+
if (/(apache2|httpd)/.test(t)) return "apache";
|
|
215
|
+
if (/(^|[\s/:])traefik/.test(t)) return "traefik";
|
|
216
|
+
if (/(^|[\s/:])haproxy/.test(t)) return "haproxy";
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
function bareUnit(unit) {
|
|
220
|
+
return unit.replace(/\.service$/, "");
|
|
221
|
+
}
|
|
222
|
+
function isProtectedUnit(unit) {
|
|
223
|
+
const bare = bareUnit(unit);
|
|
224
|
+
return PROTECTED_UNITS.has(bare) || USER_MANAGER_UNIT_RE.test(bare);
|
|
225
|
+
}
|
|
226
|
+
function mayStopUnitForEdgeTakeover(unit) {
|
|
227
|
+
if (!unit) return false;
|
|
228
|
+
if (managedDeploymentIdFromUnit(unit)) return true;
|
|
229
|
+
return !isProtectedUnit(unit);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export {
|
|
233
|
+
MAIL_HOST_STATE_DIR,
|
|
234
|
+
tryExec,
|
|
235
|
+
probeExec,
|
|
236
|
+
withReason,
|
|
237
|
+
OPENSHIP_LABEL,
|
|
238
|
+
resolveContainerPublishingPort,
|
|
239
|
+
containerIdFromCgroup,
|
|
240
|
+
stopContainerDurably,
|
|
241
|
+
describeContainerById,
|
|
242
|
+
EDGE_CONTAINER_NAME,
|
|
243
|
+
isOurEdgeContainer,
|
|
244
|
+
isContainerPortForwarder,
|
|
245
|
+
managedDeploymentUnitName,
|
|
246
|
+
managedDeploymentIdFromUnit,
|
|
247
|
+
classifyProxy,
|
|
248
|
+
mayStopUnitForEdgeTakeover
|
|
249
|
+
};
|