openship 0.6.1 → 0.6.5
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 +7 -2
- package/dist/{bare-IWS2QHJ3.js → bare-XQIPDO4R.js} +10 -9
- package/dist/{chunk-RRJJGVKE.js → chunk-3ZAGC5NT.js} +64 -17
- package/dist/{chunk-6ODUBKUB.js → chunk-5OUWIPOB.js} +69 -7
- package/dist/chunk-6HOA5JT7.js +124 -0
- package/dist/{chunk-TWJCLD4W.js → chunk-6JELW75A.js} +4 -4
- package/dist/{chunk-IOT5ZX23.js → chunk-7334NSJL.js} +399 -369
- package/dist/chunk-7S4USQYF.js +1753 -0
- package/dist/{chunk-3LDWWY7U.js → chunk-A7EWM2KA.js} +41 -7
- package/dist/chunk-ELGRMZOW.js +25 -0
- package/dist/chunk-EZPPTWJQ.js +292 -0
- package/dist/{chunk-IXM5245F.js → chunk-GRTXMUHA.js} +553 -538
- package/dist/chunk-HPXP6LXZ.js +428 -0
- package/dist/{chunk-OTLSJYAC.js → chunk-LEYSL46B.js} +2135 -85
- package/dist/{chunk-EMXI4UCU.js → chunk-LFCSC4IU.js} +29 -10
- package/dist/{chunk-JIMUYEKN.js → chunk-MSZUCNPV.js} +1 -1
- package/dist/{chunk-YIPMDGG6.js → chunk-PZRRL5PC.js} +68 -23
- package/dist/{chunk-2NZSUXOB.js → chunk-QLX7NBEA.js} +1208 -599
- package/dist/chunk-RFWJE7ZN.js +68 -0
- package/dist/{chunk-VDDMADOF.js → chunk-TM2IQPYU.js} +16 -2
- package/dist/{chunk-3FYIXVX5.js → chunk-VDESJSG6.js} +451 -106
- package/dist/{chunk-UC7YIEHT.js → chunk-VHMOQL5Y.js} +254 -45
- package/dist/{chunk-EW6KB2J7.js → chunk-VLQ3UIVO.js} +393 -53
- package/dist/{chunk-VZ2ZHPS2.js → chunk-WE7BXW6L.js} +3 -9
- package/dist/chunk-WFRXYWK2.js +444 -0
- package/dist/chunk-XRNRT37W.js +316 -0
- package/dist/{chunk-3NZICQHF.js → chunk-XVOBL7BC.js} +31 -8
- package/dist/cloud-3DODOWTR.js +36 -0
- package/dist/{detect-ALF4GXRX.js → detect-P4CIQBRH.js} +3 -3
- package/dist/{docker-QECQJ5P4.js → docker-3K2NEBD6.js} +15 -8
- package/dist/{docker-edge-executor-62N64MGS.js → docker-edge-executor-YZZ6P7IC.js} +5 -5
- package/dist/{edge-import-SAYTRJLO.js → edge-import-MCJNVTZ2.js} +26 -23
- package/dist/edge-preflight-ES6FH4JK.js +42 -0
- package/dist/{ensure-container-edge-CFSCPGZK.js → ensure-container-edge-YPITNH45.js} +9 -8
- package/dist/{executor-PT6UEJ5M.js → executor-DZGX55K2.js} +12 -5
- package/dist/host-channel-preflight-MUXE3KZU.js +49 -0
- package/dist/index.js +4089 -4538
- package/dist/{nginx-YOEEFBTE.js → nginx-NSAHWGJV.js} +11 -5
- package/dist/node-bootstrap.js +241 -0
- package/dist/node-entry.js +52 -1
- package/dist/{openresty-lua-WGBO2M3Z.js → openresty-lua-DR4D3EZ6.js} +6 -2
- package/dist/privilege-YTVI4SKF.js +19 -0
- package/dist/server/engine/samples/fail2ban/jail.d/sshd.local +4 -1
- package/dist/server/index.js +21391 -13110
- package/dist/server/migrations/0098_instance_product_mode.sql +12 -0
- package/dist/server/migrations/0099_mail_webmail_project.sql +24 -0
- package/dist/server/migrations/0100_server_ssh_private_key.sql +11 -0
- package/dist/server/migrations/0101_mail_resume_step.sql +12 -0
- package/dist/server/migrations/0102_project_workload_axes.sql +19 -0
- package/dist/server/migrations/0103_dns_credential.sql +41 -0
- package/dist/server/migrations/0104_instance_host_control.sql +15 -0
- package/dist/server/migrations/meta/_journal.json +49 -0
- package/dist/{setup-M2ZDGH3O.js → setup-WUUZURHS.js} +10 -9
- package/dist/up-XEO3SR6Z.js +58 -0
- package/package.json +4 -2
- package/dist/chunk-IW2UKBVF.js +0 -144
- package/dist/chunk-ZCFVHA25.js +0 -89
- package/dist/cloud-M2YLWEXO.js +0 -31
- /package/dist/{dist-es-JGBPABJI.js → dist-es-ZP5QDSJ3.js} +0 -0
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
":" //# ; exec "$(command -v node || command -v bun)" "$0" "$@"
|
|
3
3
|
import { createRequire as __ospCreateRequire } from "node:module";
|
|
4
4
|
const require = __ospCreateRequire(import.meta.url);
|
|
5
|
+
import {
|
|
6
|
+
formatDuration,
|
|
7
|
+
isRemoteConnectionError,
|
|
8
|
+
safeErrorMessage,
|
|
9
|
+
systemDebug
|
|
10
|
+
} from "./chunk-LEYSL46B.js";
|
|
11
|
+
|
|
12
|
+
// ../../packages/adapters/src/system/probe-exec.ts
|
|
13
|
+
async function tryExec(executor, command, opts) {
|
|
14
|
+
try {
|
|
15
|
+
return opts ? await executor.exec(command, opts) : await executor.exec(command);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function probeExec(executor, command, scope) {
|
|
21
|
+
const startedAt = Date.now();
|
|
22
|
+
systemDebug(scope, `exec:start ${command}`);
|
|
23
|
+
try {
|
|
24
|
+
const result = await executor.exec(command, { timeout: 1e4 });
|
|
25
|
+
systemDebug(scope, `exec:ok ${command} (${formatDuration(startedAt)})`);
|
|
26
|
+
return { output: result.trim(), error: null };
|
|
27
|
+
} catch (err) {
|
|
28
|
+
const msg = safeErrorMessage(err);
|
|
29
|
+
if (isRemoteConnectionError(err)) {
|
|
30
|
+
systemDebug(scope, `exec:abort ${command} (${formatDuration(startedAt)}) ${msg}`);
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
systemDebug(scope, `exec:fail ${command} (${formatDuration(startedAt)}) ${msg}`);
|
|
34
|
+
return { output: "", error: msg || `\`${command}\` failed` };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function withReason(message, error) {
|
|
38
|
+
if (!error) return message;
|
|
39
|
+
const first = error.split("\n").map((line) => line.trim()).find(Boolean);
|
|
40
|
+
if (!first) return message;
|
|
41
|
+
return `${message} \u2014 ${first.length > 200 ? `${first.slice(0, 199)}\u2026` : first}`;
|
|
42
|
+
}
|
|
5
43
|
|
|
6
44
|
// ../../packages/adapters/src/system/port-listen.ts
|
|
7
45
|
var PROC_NET_TCP_CMD = "cat /proc/net/tcp 2>/dev/null; cat /proc/net/tcp6 2>/dev/null; true";
|
|
@@ -75,13 +113,6 @@ async function waitForPortFree(executor, port, opts = {}) {
|
|
|
75
113
|
}
|
|
76
114
|
|
|
77
115
|
// ../../packages/adapters/src/runtime/port-conflict.ts
|
|
78
|
-
async function tryExec(executor, command) {
|
|
79
|
-
try {
|
|
80
|
-
return await executor.exec(command);
|
|
81
|
-
} catch {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
116
|
async function resolveSystemdUnit(executor, pid) {
|
|
86
117
|
const cgroup = await tryExec(executor, `cat /proc/${pid}/cgroup 2>/dev/null || true`);
|
|
87
118
|
const unitMatch = cgroup?.match(/(?:^|\/)([^/\n]+\.service)(?:$|\n|\/)/m) ?? cgroup?.match(/(?:^|\/)([^/\n]+\.service)(?:$|\n|\/)/m);
|
|
@@ -144,6 +175,9 @@ async function describeProcess(executor, pid) {
|
|
|
144
175
|
}
|
|
145
176
|
|
|
146
177
|
export {
|
|
178
|
+
tryExec,
|
|
179
|
+
probeExec,
|
|
180
|
+
withReason,
|
|
147
181
|
waitForPortListening,
|
|
148
182
|
waitForPortFree,
|
|
149
183
|
probeListeningPort,
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
|
|
6
|
+
// src/lib/paths.ts
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join, resolve } from "path";
|
|
9
|
+
var DEFAULT_HOME = join(homedir(), ".openship");
|
|
10
|
+
var override = process.env.OPENSHIP_HOME?.trim();
|
|
11
|
+
var OS_DIR = override ? resolve(override) : DEFAULT_HOME;
|
|
12
|
+
var IS_ALT_HOME = OS_DIR !== DEFAULT_HOME;
|
|
13
|
+
var DATA_DIR = join(OS_DIR, "data");
|
|
14
|
+
var LOG_DIR = join(OS_DIR, "logs");
|
|
15
|
+
var AUTH_SECRET_FILE = join(OS_DIR, "auth-secret");
|
|
16
|
+
var INTERNAL_TOKEN_FILE = join(OS_DIR, "internal-token");
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
OS_DIR,
|
|
20
|
+
IS_ALT_HOME,
|
|
21
|
+
DATA_DIR,
|
|
22
|
+
LOG_DIR,
|
|
23
|
+
AUTH_SECRET_FILE,
|
|
24
|
+
INTERNAL_TOKEN_FILE
|
|
25
|
+
};
|
|
@@ -0,0 +1,292 @@
|
|
|
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
|
+
answered,
|
|
7
|
+
firewallAllowSteps,
|
|
8
|
+
firewallManager,
|
|
9
|
+
firewallPersists,
|
|
10
|
+
refused,
|
|
11
|
+
shellQuote
|
|
12
|
+
} from "./chunk-LEYSL46B.js";
|
|
13
|
+
|
|
14
|
+
// ../../packages/adapters/src/system/environment-ops.ts
|
|
15
|
+
function opScript(steps) {
|
|
16
|
+
return steps.join(" && ");
|
|
17
|
+
}
|
|
18
|
+
function releaseArchOf(profile) {
|
|
19
|
+
return profile.arch === "unknown" ? refused(
|
|
20
|
+
"This host's CPU architecture could not be determined, and Openship will not default to amd64 \u2014 a release artifact for the wrong architecture installs cleanly and then fails to execute."
|
|
21
|
+
) : answered(profile.arch);
|
|
22
|
+
}
|
|
23
|
+
var HOST_STATE_DIR = "/root/.openship";
|
|
24
|
+
var PACKAGE_NAME = /^[A-Za-z0-9][A-Za-z0-9._+@-]*$/;
|
|
25
|
+
function joinPackages(packages) {
|
|
26
|
+
if (packages.length === 0) {
|
|
27
|
+
return refused("No packages were named, so there is nothing to run.");
|
|
28
|
+
}
|
|
29
|
+
const bad = packages.find((name) => !PACKAGE_NAME.test(name));
|
|
30
|
+
if (bad !== void 0) {
|
|
31
|
+
return refused(`${JSON.stringify(bad)} is not a package name.`);
|
|
32
|
+
}
|
|
33
|
+
return answered(packages.join(" "));
|
|
34
|
+
}
|
|
35
|
+
var INSTALL = {
|
|
36
|
+
// `update` first because an install against a stale index fails on a
|
|
37
|
+
// just-provisioned box; `-qq` because this output streams into the UI.
|
|
38
|
+
apt: (names) => answered(["apt-get update -qq", `apt-get install -y -qq ${names}`]),
|
|
39
|
+
dnf: (names) => answered([`dnf install -y ${names}`]),
|
|
40
|
+
yum: (names) => answered([`yum install -y ${names}`]),
|
|
41
|
+
apk: (names) => answered([`apk add --no-cache ${names}`]),
|
|
42
|
+
brew: (names) => answered([`brew install ${names}`]),
|
|
43
|
+
none: (names) => refused(
|
|
44
|
+
`No package manager is available to install ${names} \u2014 Openship looked for apt-get, dnf, yum, apk and brew and found none.`
|
|
45
|
+
)
|
|
46
|
+
};
|
|
47
|
+
var REMOVE = {
|
|
48
|
+
// `purge` + `autoremove`, matching what the uninstallers did before this table: a
|
|
49
|
+
// component Openship installed should leave no config behind when removed.
|
|
50
|
+
apt: (names) => answered([`apt-get purge -y -qq ${names}`, "apt-get autoremove -y -qq"]),
|
|
51
|
+
dnf: (names) => answered([`dnf remove -y ${names}`]),
|
|
52
|
+
yum: (names) => answered([`yum remove -y ${names}`]),
|
|
53
|
+
apk: (names) => answered([`apk del ${names}`]),
|
|
54
|
+
brew: (names) => answered([`brew uninstall --force ${names}`]),
|
|
55
|
+
none: (names) => refused(`No package manager is available to remove ${names}.`)
|
|
56
|
+
};
|
|
57
|
+
var AVAILABLE_VERSION = {
|
|
58
|
+
apt: (names) => answered([`apt-cache policy ${names} 2>/dev/null`]),
|
|
59
|
+
dnf: (names) => answered([`dnf -q --cacheonly list available ${names} 2>/dev/null | tail -n1`]),
|
|
60
|
+
yum: (names) => answered([`yum -q --cacheonly list available ${names} 2>/dev/null | tail -n1`]),
|
|
61
|
+
apk: (names) => answered([`apk policy ${names} 2>/dev/null`]),
|
|
62
|
+
brew: (names) => refused(
|
|
63
|
+
`Openship does not read Homebrew's available version for ${names} \u2014 it reports the installed version only on macOS.`
|
|
64
|
+
),
|
|
65
|
+
none: (names) => refused(`No package manager is available to look up an installable version of ${names}.`)
|
|
66
|
+
};
|
|
67
|
+
function noServiceManager(reason) {
|
|
68
|
+
const refuse = () => refused(reason);
|
|
69
|
+
return { enableStart: refuse, isActive: refuse };
|
|
70
|
+
}
|
|
71
|
+
var SERVICE = {
|
|
72
|
+
systemd: {
|
|
73
|
+
enableStart: (unit) => answered([`systemctl enable --now ${unit}`]),
|
|
74
|
+
// `--quiet` because the caller wants the exit code, and a unit name echoed onto
|
|
75
|
+
// stdout is the kind of thing a `!output` check reads as "running".
|
|
76
|
+
isActive: (unit) => answered([`systemctl is-active --quiet ${unit}`])
|
|
77
|
+
},
|
|
78
|
+
openrc: {
|
|
79
|
+
// Two steps, not one: `rc-update add` is persistence and `rc-service start` is now.
|
|
80
|
+
// OpenRC has no `--now`, which is why Alpine used to get a start command of
|
|
81
|
+
// `undefined` from a table that assumed systemd.
|
|
82
|
+
enableStart: (unit) => answered([`rc-update add ${unit} default`, `rc-service ${unit} start`]),
|
|
83
|
+
// Exits 0 started / 3 stopped, and prints to stdout — silenced so the contract
|
|
84
|
+
// matches systemd's `is-active --quiet`.
|
|
85
|
+
isActive: (unit) => answered([`rc-service ${unit} status >/dev/null 2>&1`])
|
|
86
|
+
},
|
|
87
|
+
launchd: noServiceManager(
|
|
88
|
+
"This host runs launchd, and Openship does not manage macOS system services \u2014 launchd jobs are addressed by label rather than by unit name, and starting or stopping one is the operator's call."
|
|
89
|
+
),
|
|
90
|
+
none: noServiceManager(
|
|
91
|
+
"This host has no service manager Openship can drive (no running systemd, no OpenRC, no launchd), so services cannot be started, stopped or inspected. A container started without an init system is the usual cause."
|
|
92
|
+
)
|
|
93
|
+
};
|
|
94
|
+
var GET_DOCKER_IDS = /* @__PURE__ */ new Set([
|
|
95
|
+
"ubuntu",
|
|
96
|
+
"debian",
|
|
97
|
+
"raspbian",
|
|
98
|
+
"centos",
|
|
99
|
+
"fedora",
|
|
100
|
+
"rhel",
|
|
101
|
+
"rocky"
|
|
102
|
+
]);
|
|
103
|
+
var DOCKER_CE_REPO_FILE = "/etc/yum.repos.d/docker-ce.repo";
|
|
104
|
+
function dockerCeRepoSteps(profile) {
|
|
105
|
+
const write = `curl -fsSL https://download.docker.com/linux/centos/docker-ce.repo -o ${DOCKER_CE_REPO_FILE}`;
|
|
106
|
+
const major = /^(\d+)/.exec(profile.versionId ?? "")?.[1];
|
|
107
|
+
if (!major) return [write];
|
|
108
|
+
return [write, `sed -i 's|\\$releasever|${major}|g' ${DOCKER_CE_REPO_FILE}`];
|
|
109
|
+
}
|
|
110
|
+
var DOCKER_CE_PACKAGES = "docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin";
|
|
111
|
+
var COMPOSE_PLUGIN_VERSION = "v5.4.0";
|
|
112
|
+
var COMPOSE_PLUGIN_DIR = "/usr/local/lib/docker/cli-plugins";
|
|
113
|
+
var COMPOSE_PLUGIN_PATH = `${COMPOSE_PLUGIN_DIR}/docker-compose`;
|
|
114
|
+
var COMPOSE_PLUGIN_ASSET = {
|
|
115
|
+
amd64: "x86_64",
|
|
116
|
+
arm64: "aarch64"
|
|
117
|
+
};
|
|
118
|
+
function withComposePlugin(profile, engine) {
|
|
119
|
+
const arch = releaseArchOf(profile);
|
|
120
|
+
if (!arch.supported) return arch;
|
|
121
|
+
const asset = `docker-compose-linux-${COMPOSE_PLUGIN_ASSET[arch.value]}`;
|
|
122
|
+
return answered([
|
|
123
|
+
...engine,
|
|
124
|
+
`mkdir -p ${COMPOSE_PLUGIN_DIR}`,
|
|
125
|
+
`curl -fsSL https://github.com/docker/compose/releases/download/${COMPOSE_PLUGIN_VERSION}/${asset} -o ${COMPOSE_PLUGIN_PATH}`,
|
|
126
|
+
`chmod 0755 ${COMPOSE_PLUGIN_PATH}`
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
function dockerInstallSteps(profile) {
|
|
130
|
+
if (profile.os === "darwin") {
|
|
131
|
+
return refused(
|
|
132
|
+
"Openship does not install Docker on macOS \u2014 Docker Desktop (or OrbStack/Colima) is a GUI application an operator installs and licenses themselves."
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (profile.os !== "linux") {
|
|
136
|
+
return refused("Docker can only be installed on a Linux or macOS host.");
|
|
137
|
+
}
|
|
138
|
+
const id = profile.distroId?.trim().toLowerCase() ?? "";
|
|
139
|
+
if (GET_DOCKER_IDS.has(id)) {
|
|
140
|
+
return answered(["curl -fsSL https://get.docker.com | sh"]);
|
|
141
|
+
}
|
|
142
|
+
switch (profile.distroFamily) {
|
|
143
|
+
case "rhel":
|
|
144
|
+
if (id === "amzn") {
|
|
145
|
+
return withComposePlugin(
|
|
146
|
+
profile,
|
|
147
|
+
profile.packageManager === "yum" ? (
|
|
148
|
+
// `amazon-linux-extras` exits 13 with "Installation failed" AFTER yum has
|
|
149
|
+
// printed "Complete!" and left dockerd on disk — seen once in four runs
|
|
150
|
+
// against a real amazonlinux:2. `opScript` joins with `&&`, so that exit
|
|
151
|
+
// aborted the plugin steps below and produced the engine-without-`docker
|
|
152
|
+
// compose` host {@link withComposePlugin} exists to prevent. Tolerate a
|
|
153
|
+
// non-zero exit only when the binary is actually there; a real failure has
|
|
154
|
+
// nothing to find and still aborts. Braced so it stays one step if a step
|
|
155
|
+
// is ever prepended — `&&` and `||` bind equally and left-associatively.
|
|
156
|
+
["{ amazon-linux-extras install -y docker || command -v docker >/dev/null 2>&1; }"]
|
|
157
|
+
) : ["dnf install -y docker"]
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return answered([
|
|
161
|
+
...dockerCeRepoSteps(profile),
|
|
162
|
+
`${profile.packageManager} install -y ${DOCKER_CE_PACKAGES}`
|
|
163
|
+
]);
|
|
164
|
+
case "debian":
|
|
165
|
+
return withComposePlugin(profile, [
|
|
166
|
+
"apt-get update -qq",
|
|
167
|
+
"apt-get install -y -qq docker.io"
|
|
168
|
+
]);
|
|
169
|
+
case "alpine":
|
|
170
|
+
return answered(["apk add --no-cache docker docker-cli docker-cli-compose"]);
|
|
171
|
+
case "suse":
|
|
172
|
+
case "arch":
|
|
173
|
+
case "unknown":
|
|
174
|
+
return refused(
|
|
175
|
+
`Openship has no Docker install path for this host (os-release ID=${JSON.stringify(profile.distroId ?? "")}, family ${profile.distroFamily}).`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function homeOf(profile) {
|
|
180
|
+
const home = profile.home.trim();
|
|
181
|
+
if (home) return home;
|
|
182
|
+
if (profile.isRoot) return "/root";
|
|
183
|
+
const user = profile.loginUser.trim();
|
|
184
|
+
return user ? `/home/${user}` : "/root";
|
|
185
|
+
}
|
|
186
|
+
function describeProfile(profile) {
|
|
187
|
+
const name = profile.prettyName?.trim() || profile.distroId?.trim() || profile.os;
|
|
188
|
+
const parts = [
|
|
189
|
+
name,
|
|
190
|
+
`${profile.os}/${profile.arch}`,
|
|
191
|
+
`pm=${profile.packageManager}`,
|
|
192
|
+
`init=${profile.serviceManager}`,
|
|
193
|
+
`firewall=${profile.firewall}`,
|
|
194
|
+
profile.isRoot ? "root" : profile.canSudo ? `${profile.loginUser}+sudo` : profile.loginUser
|
|
195
|
+
];
|
|
196
|
+
if (profile.isContainer) parts.push("container");
|
|
197
|
+
return parts.filter(Boolean).join(" \xB7 ");
|
|
198
|
+
}
|
|
199
|
+
function sshdEnableHintFor(profile) {
|
|
200
|
+
switch (profile.serviceManager) {
|
|
201
|
+
case "systemd":
|
|
202
|
+
return profile.distroFamily === "debian" ? "sudo systemctl enable --now ssh" : "sudo systemctl enable --now sshd";
|
|
203
|
+
case "openrc":
|
|
204
|
+
return "sudo rc-update add sshd default && sudo rc-service sshd start";
|
|
205
|
+
case "launchd":
|
|
206
|
+
return "sudo systemsetup -setremotelogin on";
|
|
207
|
+
case "none":
|
|
208
|
+
return "start this host's SSH server (no service manager was detected, so start it however this host does)";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function hostFacts(profile) {
|
|
212
|
+
const home = homeOf(profile);
|
|
213
|
+
return {
|
|
214
|
+
profile,
|
|
215
|
+
describe: () => describeProfile(profile),
|
|
216
|
+
sshdEnableHint: () => sshdEnableHintFor(profile),
|
|
217
|
+
// Both delegate: these are facts about a `SystemFirewall`, not about a profile, and
|
|
218
|
+
// the CLI reads them off a locally-measured firewall with no profile in hand.
|
|
219
|
+
firewallManager: () => firewallManager(profile.firewall),
|
|
220
|
+
firewallPersists: () => firewallPersists(profile.firewall),
|
|
221
|
+
home: () => home,
|
|
222
|
+
// macOS has no /root to own anything, and there it is a local target rather than
|
|
223
|
+
// a provisioned server, so the invoking user's home is the only sensible anchor.
|
|
224
|
+
stateDir: () => profile.os === "darwin" ? `${home}/.openship` : HOST_STATE_DIR,
|
|
225
|
+
scratchDir: () => `${home}/.openship`,
|
|
226
|
+
installsNeedRoot: () => profile.os !== "darwin"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function withPackages(packages, rule) {
|
|
230
|
+
const names = joinPackages(packages);
|
|
231
|
+
return names.supported ? rule(names.value) : names;
|
|
232
|
+
}
|
|
233
|
+
function hostCommands(profile) {
|
|
234
|
+
const pm = profile.packageManager;
|
|
235
|
+
const service = SERVICE[profile.serviceManager];
|
|
236
|
+
const unit = (name) => shellQuote(name);
|
|
237
|
+
const firewallScoped = (scope, steps) => {
|
|
238
|
+
const manager = firewallManager(profile.firewall);
|
|
239
|
+
return manager.supported ? steps(manager.value, scope) : manager;
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
pkgInstall: (packages) => withPackages(packages, INSTALL[pm]),
|
|
243
|
+
pkgInstallVariants: (variants) => {
|
|
244
|
+
if (pm === "none") return INSTALL.none("this package");
|
|
245
|
+
const chosen = variants[pm];
|
|
246
|
+
return chosen.supported ? withPackages(chosen.value, INSTALL[pm]) : chosen;
|
|
247
|
+
},
|
|
248
|
+
pkgRemove: (packages) => withPackages(packages, REMOVE[pm]),
|
|
249
|
+
pkgAvailableVersion: (pkg) => withPackages([pkg], AVAILABLE_VERSION[pm]),
|
|
250
|
+
// Unit names are quoted rather than validated: unlike package names they are
|
|
251
|
+
// data-derived (a project slug, a unit read off a `systemctl list-units` scan), so
|
|
252
|
+
// the guarantee has to hold for a string nobody vetted.
|
|
253
|
+
serviceEnableStart: (name) => service.enableStart(unit(name)),
|
|
254
|
+
serviceIsActive: (name) => service.isActive(unit(name)),
|
|
255
|
+
firewallAllow: (scope) => firewallScoped(scope, firewallAllowSteps),
|
|
256
|
+
dockerInstall: () => dockerInstallSteps(profile),
|
|
257
|
+
// Through `unit()` like every other unit name, so there is one path to a service verb
|
|
258
|
+
// rather than one path plus an exception that happens to be safe today.
|
|
259
|
+
dockerStart: () => service.enableStart(unit("docker")),
|
|
260
|
+
releaseArch: () => releaseArchOf(profile)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function mapRefusals(commands, onRefusal, gate) {
|
|
264
|
+
const out = {};
|
|
265
|
+
for (const name of Object.keys(commands)) {
|
|
266
|
+
const fn = commands[name];
|
|
267
|
+
out[name] = ((...args) => {
|
|
268
|
+
if (gate !== null) return refused(gate);
|
|
269
|
+
const answer = fn(...args);
|
|
270
|
+
return answer.supported ? answer : refused(onRefusal(answer.reason));
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
function hostRefusal(profile) {
|
|
276
|
+
if (profile.supported) return null;
|
|
277
|
+
const reason = profile.unsupportedReason ?? "Openship cannot drive this host, and the detector recorded no reason.";
|
|
278
|
+
return `${reason} (host: ${describeProfile(profile)})`;
|
|
279
|
+
}
|
|
280
|
+
function envOps(profile) {
|
|
281
|
+
const facts = hostFacts(profile);
|
|
282
|
+
const label = describeProfile(profile);
|
|
283
|
+
const withHost = (reason) => `${reason} (host: ${label})`;
|
|
284
|
+
return { ...facts, ...mapRefusals(hostCommands(profile), withHost, hostRefusal(profile)) };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
opScript,
|
|
289
|
+
HOST_STATE_DIR,
|
|
290
|
+
hostRefusal,
|
|
291
|
+
envOps
|
|
292
|
+
};
|