@fased/fased 0.1.38 → 0.1.40
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/brand.js +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli/daemon-cli/install.js +38 -14
- package/dist/cli/daemon-cli/register-service-commands.js +1 -1
- package/dist/cli/update-cli/service-target.js +13 -0
- package/dist/cli/update-cli/update-command.js +122 -44
- package/dist/commands/daemon-install-helpers.js +3 -0
- package/dist/daemon/service.js +5 -0
- package/dist/daemon/systemd-system.js +237 -0
- package/dist/daemon/systemd.js +2 -2
- package/dist/infra/update-runner.js +41 -5
- package/dist/plugin-sdk/{accounts-COY-15zJ.js → accounts-C5BjfxoO.js} +1 -1
- package/dist/plugin-sdk/{allow-list-Cgjx0Mc-.js → allow-list-DqLmAAjC.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-BPK8t0tI.js → audio-preflight-CSRhaknd.js} +1 -1
- package/dist/plugin-sdk/{audio-preflight-kCa_KGRH.js → audio-preflight-DlkuhmkH.js} +2 -2
- package/dist/plugin-sdk/channel-plugin-common.js +2 -2
- package/dist/plugin-sdk/{chunk-items-DGHUeFuI.js → chunk-items-B1CvDQbk.js} +1 -1
- package/dist/plugin-sdk/command-status.js +2 -2
- package/dist/plugin-sdk/{config-C0qggNKQ.js → config-BxVjLTIv.js} +7 -7
- package/dist/plugin-sdk/{config-z0k05dwe.js → config-DLVjQxOu.js} +7 -7
- package/dist/plugin-sdk/{config-BULEb5QB.js → config-HEgnpfBH.js} +7 -7
- package/dist/plugin-sdk/{config-DgD5T-Ui.js → config-g1MG9Bet.js} +7 -7
- package/dist/plugin-sdk/discord.js +6 -6
- package/dist/plugin-sdk/{guilds-iTYlgBat.js → guilds-BivaIhOn.js} +1 -1
- package/dist/plugin-sdk/index.js +12 -2
- package/dist/plugin-sdk/{login-YveoqEvH.js → login-CfRfRNc7.js} +2 -2
- package/dist/plugin-sdk/{monitor-rOs3mUol.js → monitor-4qlGtlZm.js} +3 -3
- package/dist/plugin-sdk/{monitor-C6Ohw9BA.js → monitor-CA4i9Vhf.js} +4 -4
- package/dist/plugin-sdk/{monitor-6lkdOqhQ.js → monitor-CIQ75vj5.js} +3 -3
- package/dist/plugin-sdk/{monitor-o20KTHIi.js → monitor-uao4bwTm.js} +6 -6
- package/dist/plugin-sdk/{openresponses-http-BJONnUdy.js → openresponses-http-Do-lt3tH.js} +1 -1
- package/dist/plugin-sdk/{openresponses-http-DkWqYznu.js → openresponses-http-Y766bB2w.js} +2 -2
- package/dist/plugin-sdk/{pi-embedded-runner-DqDy0MWN.js → pi-embedded-runner-Cl-dGLKY.js} +7 -7
- package/dist/plugin-sdk/{pi-model-discovery-runtime-w4DRRv53.js → pi-model-discovery-runtime-CfDDK1n5.js} +2 -2
- package/dist/plugin-sdk/{pi-model-discovery-runtime-CQOauapl.js → pi-model-discovery-runtime-DYfUuqPN.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-C5ssE6EW.js → pi-model-discovery-runtime-DxoFm4Vd.js} +1 -1
- package/dist/plugin-sdk/{pi-model-discovery-runtime-CKKNoqwk.js → pi-model-discovery-runtime-LB-5lX5L.js} +1 -1
- package/dist/plugin-sdk/{pw-ai-Boor5vYw.js → pw-ai-SNCI9UXT.js} +1 -1
- package/dist/plugin-sdk/{resolve-channels-gIpOj1wt.js → resolve-channels-BptLzpDb.js} +3 -3
- package/dist/plugin-sdk/{resolve-users-CTKaESmf.js → resolve-users-C0YLyf20.js} +3 -3
- package/dist/plugin-sdk/{resolve-utils-bixHYDBj.js → resolve-utils-Bq0vrn3x.js} +1 -1
- package/dist/plugin-sdk/{retry-CB29XXbz.js → retry-CxA2fayk.js} +2 -2
- package/dist/plugin-sdk/{runtime-dependency-Deg_sw2k.js → runtime-dependency-B-1u41Ph.js} +9 -9
- package/dist/plugin-sdk/{slack-CGTAN4UD.js → slack-BnH97t7_.js} +3 -3
- package/dist/plugin-sdk/{slack-BErIh0uj.js → slack-DvaEXxo5.js} +4 -4
- package/dist/plugin-sdk/slack.js +2 -2
- package/dist/plugin-sdk/src/brand.d.ts +2 -2
- package/dist/plugin-sdk/src/daemon/systemd-system.d.ts +28 -0
- package/dist/plugin-sdk/{status-CCGFb0f2.js → status-DKwtdFcx.js} +15 -15
- package/dist/plugin-sdk/{sticker-cache-wxl1Me2i.js → sticker-cache-C6yLVx3S.js} +2 -2
- package/dist/plugin-sdk/{sticker-cache-CKAkkCoU.js → sticker-cache-Dmrdawoj.js} +2 -2
- package/dist/plugin-sdk/{telegram-actions-r86ntIKR.js → telegram-actions-DnvgAPkX.js} +2 -2
- package/dist/plugin-sdk/{telegram-actions-BRUNEXMr.js → telegram-actions-JyjH4UpU.js} +3 -3
- package/dist/plugin-sdk/{web-02I0vmdu.js → web-4BVlmvJD.js} +2 -2
- package/dist/plugin-sdk/{web-CXz56wyu.js → web-DsLHqCkb.js} +1 -1
- package/dist/plugin-sdk/whatsapp.js +4 -4
- package/extensions/acpx/package.json +1 -1
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/fased-federation/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +6 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +2 -2
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +6 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +6 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/sat-mining/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/synology-chat/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +6 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +6 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +6 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +6 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/scripts/start-managed.sh +2 -2
package/dist/brand.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const FASED_BRAND_NAME = "Fased";
|
|
3
3
|
const FASED_AGENT_NAME = "Fased Agent";
|
|
4
4
|
const FASED_CONTROL_NAME = "Fased Control";
|
|
5
|
-
const FASED_PRODUCT_VERSION = "0.1.
|
|
5
|
+
const FASED_PRODUCT_VERSION = "0.1.40";
|
|
6
6
|
const FASED_DISPLAY_VERSION = `v${FASED_PRODUCT_VERSION}`;
|
|
7
7
|
function formatFasedDisplayLine() {
|
|
8
8
|
return `${FASED_AGENT_NAME} ${FASED_DISPLAY_VERSION}`;
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d43118ba792b198aa36c870735aede71aa66e9f53636ca999d50426d55459175
|
|
@@ -7,6 +7,7 @@ import { randomToken } from "../../commands/onboard-helpers.js";
|
|
|
7
7
|
import { resolveGatewayAuth } from "../../gateway/auth.js";
|
|
8
8
|
import { buildGatewayInstallPlan } from "../../commands/daemon-install-helpers.js";
|
|
9
9
|
import { DEFAULT_GATEWAY_DAEMON_RUNTIME, isGatewayDaemonRuntime } from "../../commands/daemon-runtime.js";
|
|
10
|
+
import { installHostedSystemdService } from "../../daemon/systemd-system.js";
|
|
10
11
|
import { resolveGatewayService } from "../../daemon/service.js";
|
|
11
12
|
import { buildDaemonServiceSnapshot, createDaemonActionContext } from "./response.js";
|
|
12
13
|
import { parsePort } from "../shared/parse-port.js";
|
|
@@ -40,6 +41,11 @@ async function runDaemonInstall(opts) {
|
|
|
40
41
|
return;
|
|
41
42
|
}
|
|
42
43
|
const service = resolveGatewayService();
|
|
44
|
+
const systemInstall = Boolean(opts.system || service.label === "systemd system service");
|
|
45
|
+
if (opts.system && process.platform !== "linux") {
|
|
46
|
+
fail("--system is supported on Linux VPS Hosting installs only.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
43
49
|
let loaded = false;
|
|
44
50
|
try {
|
|
45
51
|
loaded = await service.isLoaded({ env: process.env });
|
|
@@ -113,7 +119,10 @@ async function runDaemonInstall(opts) {
|
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({
|
|
116
|
-
env:
|
|
122
|
+
env: systemInstall ? {
|
|
123
|
+
...process.env,
|
|
124
|
+
FASED_GATEWAY_MODE: "managed"
|
|
125
|
+
} : process.env,
|
|
117
126
|
port,
|
|
118
127
|
token,
|
|
119
128
|
runtime: runtimeRaw,
|
|
@@ -124,30 +133,45 @@ async function runDaemonInstall(opts) {
|
|
|
124
133
|
defaultRuntime.log(message);
|
|
125
134
|
}
|
|
126
135
|
},
|
|
127
|
-
config: cfg
|
|
136
|
+
config: cfg,
|
|
137
|
+
startupMode: systemInstall ? "managed-up" : undefined
|
|
128
138
|
});
|
|
129
139
|
try {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
if (systemInstall) {
|
|
141
|
+
const installed = await installHostedSystemdService({
|
|
142
|
+
programArguments,
|
|
143
|
+
workingDirectory,
|
|
144
|
+
environment
|
|
145
|
+
});
|
|
146
|
+
if (!json) {
|
|
147
|
+
defaultRuntime.log(`Installed hosted system service: ${installed.unitPath}`);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
await service.install({
|
|
151
|
+
env: process.env,
|
|
152
|
+
stdout,
|
|
153
|
+
programArguments,
|
|
154
|
+
workingDirectory,
|
|
155
|
+
environment
|
|
156
|
+
});
|
|
157
|
+
}
|
|
137
158
|
} catch (err) {
|
|
138
159
|
fail(`Gateway install failed: ${String(err)}`);
|
|
139
160
|
return;
|
|
140
161
|
}
|
|
162
|
+
const installedService = systemInstall ? resolveGatewayService() : service;
|
|
141
163
|
let installed = true;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
if (!systemInstall) {
|
|
165
|
+
try {
|
|
166
|
+
installed = await installedService.isLoaded({ env: process.env });
|
|
167
|
+
} catch {
|
|
168
|
+
installed = true;
|
|
169
|
+
}
|
|
146
170
|
}
|
|
147
171
|
emit({
|
|
148
172
|
ok: true,
|
|
149
173
|
result: "installed",
|
|
150
|
-
service: buildDaemonServiceSnapshot(
|
|
174
|
+
service: buildDaemonServiceSnapshot(installedService, installed),
|
|
151
175
|
warnings: warnings.length ? warnings : undefined
|
|
152
176
|
});
|
|
153
177
|
}
|
|
@@ -34,7 +34,7 @@ function addGatewayServiceCommands(parent, opts) {
|
|
|
34
34
|
json: Boolean(cmdOpts.json)
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
|
-
parent.command("install").description("Install the Gateway service (launchd/systemd/schtasks)").option("--port <port>", "Gateway port").option("--runtime <runtime>", "Daemon runtime (node|bun). Default: node").option("--token <token>", "Gateway token (token auth)").option("--force", "Reinstall/overwrite if already installed", false).option("--json", "Output JSON", false).action(async (cmdOpts, command) => {
|
|
37
|
+
parent.command("install").description("Install the Gateway service (launchd/systemd/schtasks)").option("--port <port>", "Gateway port").option("--runtime <runtime>", "Daemon runtime (node|bun). Default: node").option("--token <token>", "Gateway token (token auth)").option("--force", "Reinstall/overwrite if already installed", false).option("--system", "Install the root-managed VPS Hosting service", false).option("--json", "Output JSON", false).action(async (cmdOpts, command) => {
|
|
38
38
|
await runDaemonInstall(resolveInstallOptions(cmdOpts, command));
|
|
39
39
|
});
|
|
40
40
|
parent.command("uninstall").description("Uninstall the Gateway service (launchd/systemd/schtasks)").option("--json", "Output JSON", false).action(async (cmdOpts) => {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveGatewayService } from "../../daemon/service.js";
|
|
2
|
+
|
|
3
|
+
//#region src/cli/update-cli/service-target.ts
|
|
4
|
+
async function resolveUpdateGatewayServiceTarget() {
|
|
5
|
+
const service = resolveGatewayService();
|
|
6
|
+
return {
|
|
7
|
+
scope: service.label === "systemd system service" ? "system" : "platform",
|
|
8
|
+
service
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { resolveUpdateGatewayServiceTarget };
|
|
@@ -7,8 +7,8 @@ import { replaceCliName, resolveCliName } from "../cli-name.js";
|
|
|
7
7
|
import { formatCliCommand } from "../command-format.js";
|
|
8
8
|
import { readConfigFileSnapshot, writeConfigFile } from "../../config/io.js";
|
|
9
9
|
import "../../config/config.js";
|
|
10
|
+
import { loadGatewayTlsRuntime } from "../../infra/tls/gateway.js";
|
|
10
11
|
import { stylePromptMessage } from "../../terminal/prompt-style.js";
|
|
11
|
-
import { resolveGatewayService } from "../../daemon/service.js";
|
|
12
12
|
import { runDaemonInstall } from "../daemon-cli/install.js";
|
|
13
13
|
import { renderRestartDiagnostics, terminateStaleGatewayPids, waitForGatewayHealthyRestart } from "../daemon-cli/restart-health.js";
|
|
14
14
|
import { runDaemonRestart } from "../daemon-cli/lifecycle.js";
|
|
@@ -19,13 +19,14 @@ import { DEFAULT_GIT_CHANNEL, DEFAULT_PACKAGE_CHANNEL, channelToNpmTag, normaliz
|
|
|
19
19
|
import { cleanupGlobalRenameDirs, globalInstallArgs, resolveGlobalPackageRoot, resolveNodeModulesRootForPackageRoot } from "../../infra/update-global.js";
|
|
20
20
|
import { checkUpdateStatus, compareSemverStrings, resolveNpmChannelTag } from "../../infra/update-check.js";
|
|
21
21
|
import { readPackageName, readPackageVersion } from "../../infra/package-json.js";
|
|
22
|
-
import { runGatewayUpdate } from "../../infra/update-runner.js";
|
|
22
|
+
import { finalizeUpdateTransaction, rollbackUpdateTransaction, runGatewayUpdate } from "../../infra/update-runner.js";
|
|
23
23
|
import { checkShellCompletionStatus, ensureCompletionCacheExists } from "../../commands/doctor-completion.js";
|
|
24
24
|
import { runPostUpdateDoctorRepair } from "../../commands/doctor-update.js";
|
|
25
25
|
import { doctorCommand } from "../../commands/doctor.js";
|
|
26
26
|
import { DEFAULT_PACKAGE_NAME, createGlobalCommandRunner, ensureGitCheckout, normalizeTag, parseTimeoutMsOrExit, resolveGitInstallDir, resolveGlobalManager, resolveNodeRunner, resolveTargetVersion, resolveUpdateRoot, runUpdateStep, tryWriteCompletionCache } from "./shared.js";
|
|
27
27
|
import { createUpdateProgress, printResult } from "./progress.js";
|
|
28
28
|
import { prepareRestartScript, runRestartScript } from "./restart-helper.js";
|
|
29
|
+
import { resolveUpdateGatewayServiceTarget } from "./service-target.js";
|
|
29
30
|
import { suppressDeprecations } from "./suppress-deprecations.js";
|
|
30
31
|
import path from "node:path";
|
|
31
32
|
import { confirm, isCancel } from "@clack/prompts";
|
|
@@ -265,6 +266,17 @@ async function runPackageInstallUpdate(params) {
|
|
|
265
266
|
steps.push(doctorStep);
|
|
266
267
|
}
|
|
267
268
|
}
|
|
269
|
+
const expectedVersion = params.tag.trim().replace(/^v/, "");
|
|
270
|
+
if (pkgRoot && compareSemverStrings(expectedVersion, expectedVersion) === 0 && compareSemverStrings(afterVersion, expectedVersion) !== 0) {
|
|
271
|
+
steps.push({
|
|
272
|
+
name: "version verify",
|
|
273
|
+
command: `verify ${packageName}@${expectedVersion}`,
|
|
274
|
+
cwd: pkgRoot,
|
|
275
|
+
durationMs: 0,
|
|
276
|
+
exitCode: 1,
|
|
277
|
+
stderrTail: `expected ${expectedVersion}, found ${afterVersion ?? "unknown"}`
|
|
278
|
+
});
|
|
279
|
+
}
|
|
268
280
|
const failedStep = steps.find((step) => step.exitCode !== 0);
|
|
269
281
|
return {
|
|
270
282
|
status: failedStep ? "error" : "ok",
|
|
@@ -455,52 +467,68 @@ async function maybeRestartService(params) {
|
|
|
455
467
|
}
|
|
456
468
|
}
|
|
457
469
|
}
|
|
458
|
-
if (params.
|
|
470
|
+
if (params.serviceTarget.scope === "system" && params.serviceLoaded) {
|
|
471
|
+
await params.serviceTarget.service.restart({
|
|
472
|
+
env: process.env,
|
|
473
|
+
stdout: process.stdout
|
|
474
|
+
});
|
|
475
|
+
restartInitiated = true;
|
|
476
|
+
} else if (params.restartScriptPath) {
|
|
459
477
|
await runRestartScript(params.restartScriptPath);
|
|
460
478
|
restartInitiated = true;
|
|
461
|
-
} else {
|
|
479
|
+
} else if (params.serviceTarget.scope === "platform") {
|
|
462
480
|
restarted = await runDaemonRestart();
|
|
481
|
+
restartInitiated = restarted;
|
|
463
482
|
}
|
|
464
|
-
if (
|
|
465
|
-
defaultRuntime.log(theme.success("Daemon restarted successfully."));
|
|
466
|
-
defaultRuntime.log("");
|
|
467
|
-
process.env.FASED_UPDATE_IN_PROGRESS = "1";
|
|
468
|
-
try {
|
|
469
|
-
const interactiveDoctor = Boolean(process.stdin.isTTY) && !params.opts.json && params.opts.yes !== true;
|
|
470
|
-
await doctorCommand(defaultRuntime, { nonInteractive: !interactiveDoctor });
|
|
471
|
-
} catch (err) {
|
|
472
|
-
defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`));
|
|
473
|
-
} finally {
|
|
474
|
-
delete process.env.FASED_UPDATE_IN_PROGRESS;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
if (!params.opts.json && restartInitiated) {
|
|
478
|
-
const service = resolveGatewayService();
|
|
483
|
+
if (restartInitiated) {
|
|
479
484
|
let health = await waitForGatewayHealthyRestart({
|
|
480
|
-
service,
|
|
481
|
-
port: params.gatewayPort
|
|
485
|
+
service: params.serviceTarget.service,
|
|
486
|
+
port: params.gatewayPort,
|
|
487
|
+
rpc: params.rpc
|
|
482
488
|
});
|
|
483
489
|
if (!health.healthy && health.staleGatewayPids.length > 0) {
|
|
484
490
|
if (!params.opts.json) {
|
|
485
491
|
defaultRuntime.log(theme.warn(`Found stale gateway process(es) after restart: ${health.staleGatewayPids.join(", ")}. Cleaning up...`));
|
|
486
492
|
}
|
|
487
493
|
await terminateStaleGatewayPids(health.staleGatewayPids);
|
|
488
|
-
await
|
|
494
|
+
await params.serviceTarget.service.restart({
|
|
495
|
+
env: process.env,
|
|
496
|
+
stdout: process.stdout
|
|
497
|
+
});
|
|
489
498
|
health = await waitForGatewayHealthyRestart({
|
|
490
|
-
service,
|
|
491
|
-
port: params.gatewayPort
|
|
499
|
+
service: params.serviceTarget.service,
|
|
500
|
+
port: params.gatewayPort,
|
|
501
|
+
rpc: params.rpc
|
|
492
502
|
});
|
|
493
503
|
}
|
|
494
504
|
if (health.healthy) {
|
|
495
|
-
|
|
505
|
+
if (!params.opts.json) {
|
|
506
|
+
defaultRuntime.log(theme.success("Daemon restart completed."));
|
|
507
|
+
}
|
|
496
508
|
} else {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
defaultRuntime.log(theme.
|
|
509
|
+
partial = true;
|
|
510
|
+
if (!params.opts.json) {
|
|
511
|
+
defaultRuntime.log(theme.warn("Gateway did not become healthy after restart."));
|
|
512
|
+
for (const line of renderRestartDiagnostics(health)) {
|
|
513
|
+
defaultRuntime.log(theme.muted(line));
|
|
514
|
+
}
|
|
515
|
+
defaultRuntime.log(theme.muted(`Run \`${replaceCliName(formatCliCommand("fased gateway status --deep"), CLI_NAME)}\` for details.`));
|
|
500
516
|
}
|
|
501
|
-
defaultRuntime.log(theme.muted(`Run \`${replaceCliName(formatCliCommand("fased gateway status --deep"), CLI_NAME)}\` for details.`));
|
|
502
517
|
}
|
|
503
|
-
|
|
518
|
+
if (!params.opts.json) {
|
|
519
|
+
defaultRuntime.log("");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (!partial && restarted && !params.opts.json) {
|
|
523
|
+
process.env.FASED_UPDATE_IN_PROGRESS = "1";
|
|
524
|
+
try {
|
|
525
|
+
const interactiveDoctor = Boolean(process.stdin.isTTY) && params.opts.yes !== true;
|
|
526
|
+
await doctorCommand(defaultRuntime, { nonInteractive: !interactiveDoctor });
|
|
527
|
+
} catch (err) {
|
|
528
|
+
defaultRuntime.log(theme.warn(`Doctor failed: ${String(err)}`));
|
|
529
|
+
} finally {
|
|
530
|
+
delete process.env.FASED_UPDATE_IN_PROGRESS;
|
|
531
|
+
}
|
|
504
532
|
}
|
|
505
533
|
} catch (err) {
|
|
506
534
|
partial = true;
|
|
@@ -509,7 +537,10 @@ async function maybeRestartService(params) {
|
|
|
509
537
|
defaultRuntime.log(theme.muted(`You may need to restart the service manually: ${replaceCliName(formatCliCommand("fased gateway restart"), CLI_NAME)}`));
|
|
510
538
|
}
|
|
511
539
|
}
|
|
512
|
-
return {
|
|
540
|
+
return {
|
|
541
|
+
partial,
|
|
542
|
+
healthy: !partial
|
|
543
|
+
};
|
|
513
544
|
}
|
|
514
545
|
if (!params.opts.json) {
|
|
515
546
|
defaultRuntime.log("");
|
|
@@ -519,7 +550,10 @@ async function maybeRestartService(params) {
|
|
|
519
550
|
defaultRuntime.log(theme.muted(`Tip: Run \`${replaceCliName(formatCliCommand("fased gateway restart"), CLI_NAME)}\` to apply updates to a running gateway.`));
|
|
520
551
|
}
|
|
521
552
|
}
|
|
522
|
-
return {
|
|
553
|
+
return {
|
|
554
|
+
partial,
|
|
555
|
+
healthy: true
|
|
556
|
+
};
|
|
523
557
|
}
|
|
524
558
|
async function updateCommand(opts) {
|
|
525
559
|
suppressDeprecations();
|
|
@@ -678,12 +712,16 @@ async function updateCommand(opts) {
|
|
|
678
712
|
const startedAt = Date.now();
|
|
679
713
|
let restartScriptPath = null;
|
|
680
714
|
let refreshGatewayServiceEnv = false;
|
|
715
|
+
let serviceLoaded = false;
|
|
716
|
+
const serviceTarget = await resolveUpdateGatewayServiceTarget();
|
|
681
717
|
if (shouldRestart) {
|
|
682
718
|
try {
|
|
683
|
-
|
|
684
|
-
if (
|
|
685
|
-
|
|
686
|
-
|
|
719
|
+
serviceLoaded = await serviceTarget.service.isLoaded({ env: process.env });
|
|
720
|
+
if (serviceLoaded) {
|
|
721
|
+
if (serviceTarget.scope === "platform") {
|
|
722
|
+
restartScriptPath = await prepareRestartScript(process.env);
|
|
723
|
+
refreshGatewayServiceEnv = true;
|
|
724
|
+
}
|
|
687
725
|
}
|
|
688
726
|
} catch {}
|
|
689
727
|
}
|
|
@@ -727,6 +765,54 @@ async function updateCommand(opts) {
|
|
|
727
765
|
defaultRuntime.exit(0);
|
|
728
766
|
return;
|
|
729
767
|
}
|
|
768
|
+
const gatewayPort = resolveGatewayPort(configSnapshot.valid ? configSnapshot.config : undefined);
|
|
769
|
+
const gatewayConfig = configSnapshot.valid ? configSnapshot.config.gateway : undefined;
|
|
770
|
+
const tlsRuntime = await loadGatewayTlsRuntime(gatewayConfig?.tls);
|
|
771
|
+
const restartRpc = {
|
|
772
|
+
url: `${tlsRuntime.enabled ? "wss" : "ws"}://127.0.0.1:${gatewayPort}`,
|
|
773
|
+
token: gatewayConfig?.auth?.token,
|
|
774
|
+
password: gatewayConfig?.auth?.password,
|
|
775
|
+
tlsFingerprint: tlsRuntime.enabled ? tlsRuntime.fingerprintSha256 : undefined,
|
|
776
|
+
timeoutMs: 5e3
|
|
777
|
+
};
|
|
778
|
+
const restartResult = await maybeRestartService({
|
|
779
|
+
shouldRestart,
|
|
780
|
+
result,
|
|
781
|
+
opts,
|
|
782
|
+
refreshServiceEnv: refreshGatewayServiceEnv,
|
|
783
|
+
gatewayPort,
|
|
784
|
+
restartScriptPath,
|
|
785
|
+
serviceTarget,
|
|
786
|
+
serviceLoaded,
|
|
787
|
+
rpc: restartRpc
|
|
788
|
+
});
|
|
789
|
+
if (!restartResult.healthy) {
|
|
790
|
+
if (result.transaction) {
|
|
791
|
+
if (!opts.json) {
|
|
792
|
+
defaultRuntime.log(theme.warn("Gateway verification failed; restoring the previous runtime."));
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
await rollbackUpdateTransaction(result.transaction);
|
|
796
|
+
await serviceTarget.service.restart({
|
|
797
|
+
env: process.env,
|
|
798
|
+
stdout: process.stdout
|
|
799
|
+
});
|
|
800
|
+
const rollbackHealth = await waitForGatewayHealthyRestart({
|
|
801
|
+
service: serviceTarget.service,
|
|
802
|
+
port: gatewayPort,
|
|
803
|
+
rpc: restartRpc
|
|
804
|
+
});
|
|
805
|
+
if (!rollbackHealth.healthy) {
|
|
806
|
+
defaultRuntime.error("Previous runtime was restored, but the gateway is not healthy.");
|
|
807
|
+
}
|
|
808
|
+
} catch (error) {
|
|
809
|
+
defaultRuntime.error(`Automatic runtime rollback failed: ${String(error)}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
defaultRuntime.exit(1);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
await finalizeUpdateTransaction(result.transaction);
|
|
730
816
|
await updatePluginsAfterCoreUpdate({
|
|
731
817
|
root,
|
|
732
818
|
channel,
|
|
@@ -738,14 +824,6 @@ async function updateCommand(opts) {
|
|
|
738
824
|
jsonMode: Boolean(opts.json),
|
|
739
825
|
skipPrompt: Boolean(opts.yes)
|
|
740
826
|
});
|
|
741
|
-
const restartResult = await maybeRestartService({
|
|
742
|
-
shouldRestart,
|
|
743
|
-
result,
|
|
744
|
-
opts,
|
|
745
|
-
refreshServiceEnv: refreshGatewayServiceEnv,
|
|
746
|
-
gatewayPort: resolveGatewayPort(configSnapshot.valid ? configSnapshot.config : undefined),
|
|
747
|
-
restartScriptPath
|
|
748
|
-
});
|
|
749
827
|
if (!opts.json && !restartResult.partial) {
|
|
750
828
|
defaultRuntime.log(theme.muted(pickUpdateQuip()));
|
|
751
829
|
}
|
|
@@ -59,6 +59,9 @@ async function buildGatewayInstallPlan(params) {
|
|
|
59
59
|
});
|
|
60
60
|
const environment = { ...collectConfigServiceEnvVars(params.config) };
|
|
61
61
|
Object.assign(environment, serviceEnvironment);
|
|
62
|
+
if (startupMode === "managed-up") {
|
|
63
|
+
environment.FASED_GATEWAY_MODE = "managed";
|
|
64
|
+
}
|
|
62
65
|
const serviceNodeProgram = programArguments[0];
|
|
63
66
|
if (typeof serviceNodeProgram === "string" && serviceNodeProgram.includes(path.sep) && path.basename(serviceNodeProgram).toLowerCase().startsWith("node")) {
|
|
64
67
|
environment.FASED_NODE_BIN = serviceNodeProgram;
|
package/dist/daemon/service.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { installLaunchAgent, isLaunchAgentLoaded, readLaunchAgentProgramArguments, readLaunchAgentRuntime, restartLaunchAgent, stopLaunchAgent, uninstallLaunchAgent } from "./launchd.js";
|
|
2
2
|
import { installScheduledTask, isScheduledTaskInstalled, readScheduledTaskCommand, readScheduledTaskRuntime, restartScheduledTask, stopScheduledTask, uninstallScheduledTask } from "./schtasks.js";
|
|
3
3
|
import { installSystemdService, isSystemdServiceEnabled, readSystemdServiceExecStart, readSystemdServiceRuntime, restartSystemdService, stopSystemdService, uninstallSystemdService } from "./systemd.js";
|
|
4
|
+
import { resolveHostedSystemdService } from "./systemd-system.js";
|
|
4
5
|
|
|
5
6
|
//#region src/daemon/service.ts
|
|
6
7
|
function ignoreInstallResult(install) {
|
|
@@ -24,6 +25,10 @@ function resolveGatewayService() {
|
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
if (process.platform === "linux") {
|
|
28
|
+
const hostedSystemService = resolveHostedSystemdService();
|
|
29
|
+
if (hostedSystemService) {
|
|
30
|
+
return hostedSystemService;
|
|
31
|
+
}
|
|
27
32
|
return {
|
|
28
33
|
label: "systemd",
|
|
29
34
|
loadedText: "enabled",
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { execFileUtf8 } from "./exec-file.js";
|
|
2
|
+
import { buildSystemdUnit, parseSystemdEnvAssignment, parseSystemdExecStart } from "./systemd-unit.js";
|
|
3
|
+
import { parseSystemdShow } from "./systemd.js";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import fs$1 from "node:fs/promises";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
//#region src/daemon/systemd-system.ts
|
|
11
|
+
const SERVICE_NAME = "fased-gateway";
|
|
12
|
+
const INSTALL_HELPER = "/usr/local/sbin/fased-install-gateway-service";
|
|
13
|
+
const SYSTEM_UNIT_CANDIDATES = [
|
|
14
|
+
`/etc/systemd/system/${SERVICE_NAME}.service`,
|
|
15
|
+
`/usr/lib/systemd/system/${SERVICE_NAME}.service`,
|
|
16
|
+
`/lib/systemd/system/${SERVICE_NAME}.service`
|
|
17
|
+
];
|
|
18
|
+
function findHostedSystemdUnitPath(candidates = SYSTEM_UNIT_CANDIDATES) {
|
|
19
|
+
if (process.platform !== "linux") {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
23
|
+
}
|
|
24
|
+
async function readHostedSystemdCommand(unitPath) {
|
|
25
|
+
try {
|
|
26
|
+
const content = await fs$1.readFile(unitPath, "utf8");
|
|
27
|
+
let workingDirectory = "";
|
|
28
|
+
let execStart = "";
|
|
29
|
+
const environment = {};
|
|
30
|
+
for (const rawLine of content.split("\n")) {
|
|
31
|
+
const line = rawLine.trim();
|
|
32
|
+
if (line.startsWith("ExecStart=")) {
|
|
33
|
+
execStart = line.slice("ExecStart=".length).trim();
|
|
34
|
+
} else if (line.startsWith("WorkingDirectory=")) {
|
|
35
|
+
workingDirectory = line.slice("WorkingDirectory=".length).trim();
|
|
36
|
+
} else if (line.startsWith("Environment=")) {
|
|
37
|
+
const parsed = parseSystemdEnvAssignment(line.slice("Environment=".length).trim());
|
|
38
|
+
if (parsed) {
|
|
39
|
+
environment[parsed.key] = parsed.value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!execStart) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
programArguments: parseSystemdExecStart(execStart),
|
|
48
|
+
...workingDirectory ? { workingDirectory } : {},
|
|
49
|
+
...Object.keys(environment).length > 0 ? { environment } : {},
|
|
50
|
+
sourcePath: unitPath
|
|
51
|
+
};
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function resolveHostedSystemdService() {
|
|
57
|
+
const unitPath = findHostedSystemdUnitPath();
|
|
58
|
+
if (!unitPath) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const runSystemctl = async (args) => await execFileUtf8("systemctl", args);
|
|
62
|
+
const runPrivilegedSystemctl = async (args) => await execFileUtf8("sudo", [
|
|
63
|
+
"-n",
|
|
64
|
+
"systemctl",
|
|
65
|
+
...args
|
|
66
|
+
]);
|
|
67
|
+
const control = async (action, stdout) => {
|
|
68
|
+
const args = action === "restart" ? [
|
|
69
|
+
action,
|
|
70
|
+
"--no-block",
|
|
71
|
+
`${SERVICE_NAME}.service`
|
|
72
|
+
] : [action, `${SERVICE_NAME}.service`];
|
|
73
|
+
const result = await runPrivilegedSystemctl(args);
|
|
74
|
+
if (result.code !== 0) {
|
|
75
|
+
throw new Error(result.stderr || result.stdout || `system service ${action} failed`);
|
|
76
|
+
}
|
|
77
|
+
stdout.write(`${action === "restart" ? "Restarted" : "Stopped"} system service: ${SERVICE_NAME}.service\n`);
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
label: "systemd system service",
|
|
81
|
+
loadedText: "enabled",
|
|
82
|
+
notLoadedText: "disabled",
|
|
83
|
+
install: async ({ programArguments, workingDirectory, environment }) => {
|
|
84
|
+
await installHostedSystemdService({
|
|
85
|
+
programArguments,
|
|
86
|
+
workingDirectory,
|
|
87
|
+
environment
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
uninstall: async () => {
|
|
91
|
+
throw new Error("Hosted system service removal requires the VPS Hosting installer.");
|
|
92
|
+
},
|
|
93
|
+
stop: async ({ stdout }) => await control("stop", stdout),
|
|
94
|
+
restart: async ({ stdout }) => await control("restart", stdout),
|
|
95
|
+
isLoaded: async () => (await runSystemctl(["is-enabled", `${SERVICE_NAME}.service`])).code === 0,
|
|
96
|
+
readCommand: async () => await readHostedSystemdCommand(unitPath),
|
|
97
|
+
readRuntime: async () => {
|
|
98
|
+
const result = await runSystemctl([
|
|
99
|
+
"show",
|
|
100
|
+
`${SERVICE_NAME}.service`,
|
|
101
|
+
"--no-page",
|
|
102
|
+
"--property",
|
|
103
|
+
"ActiveState,SubState,MainPID,ExecMainStatus,ExecMainCode"
|
|
104
|
+
]);
|
|
105
|
+
if (result.code !== 0) {
|
|
106
|
+
return {
|
|
107
|
+
status: "unknown",
|
|
108
|
+
detail: result.stderr || result.stdout || undefined
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const parsed = parseSystemdShow(result.stdout);
|
|
112
|
+
const activeState = parsed.activeState?.toLowerCase();
|
|
113
|
+
return {
|
|
114
|
+
status: activeState === "active" ? "running" : activeState ? "stopped" : "unknown",
|
|
115
|
+
state: parsed.activeState,
|
|
116
|
+
subState: parsed.subState,
|
|
117
|
+
pid: parsed.mainPid,
|
|
118
|
+
lastExitStatus: parsed.execMainStatus,
|
|
119
|
+
lastExitReason: parsed.execMainCode
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function resolveRunAsUser() {
|
|
125
|
+
const user = process.env.USER?.trim() || process.env.LOGNAME?.trim() || os.userInfo().username;
|
|
126
|
+
if (!user || user === "root" || !/^[A-Za-z0-9_.@-]+$/.test(user)) {
|
|
127
|
+
throw new Error("Hosted gateway repair must run as the non-root Fased app user.");
|
|
128
|
+
}
|
|
129
|
+
return user;
|
|
130
|
+
}
|
|
131
|
+
function buildHostedSystemdUnit(params) {
|
|
132
|
+
const baseUnit = buildSystemdUnit({
|
|
133
|
+
description: "Fased Gateway (managed)",
|
|
134
|
+
programArguments: params.programArguments,
|
|
135
|
+
workingDirectory: params.workingDirectory,
|
|
136
|
+
environment: params.environment
|
|
137
|
+
});
|
|
138
|
+
const lines = baseUnit.split("\n");
|
|
139
|
+
const serviceIndex = lines.findIndex((line) => line.trim() === "[Service]");
|
|
140
|
+
if (serviceIndex !== -1) {
|
|
141
|
+
lines.splice(serviceIndex + 1, 0, "Type=simple", `User=${params.runAsUser}`, `Group=${params.runAsUser}`);
|
|
142
|
+
}
|
|
143
|
+
const installIndex = lines.findIndex((line) => line.trim() === "[Install]");
|
|
144
|
+
if (installIndex !== -1) {
|
|
145
|
+
lines.splice(installIndex, 0, "NoNewPrivileges=true", "PrivateTmp=true");
|
|
146
|
+
}
|
|
147
|
+
const wantedByIndex = lines.findIndex((line) => line.trim() === "WantedBy=default.target");
|
|
148
|
+
if (wantedByIndex !== -1) {
|
|
149
|
+
lines[wantedByIndex] = "WantedBy=multi-user.target";
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
async function runHelper(unit, runAsUser) {
|
|
154
|
+
await fs$1.access(INSTALL_HELPER);
|
|
155
|
+
await new Promise((resolve, reject) => {
|
|
156
|
+
const child = spawn("sudo", [
|
|
157
|
+
"-n",
|
|
158
|
+
INSTALL_HELPER,
|
|
159
|
+
SERVICE_NAME,
|
|
160
|
+
runAsUser
|
|
161
|
+
], { stdio: [
|
|
162
|
+
"pipe",
|
|
163
|
+
"pipe",
|
|
164
|
+
"pipe"
|
|
165
|
+
] });
|
|
166
|
+
let stdout = "";
|
|
167
|
+
let stderr = "";
|
|
168
|
+
const timer = setTimeout(() => {
|
|
169
|
+
child.kill("SIGTERM");
|
|
170
|
+
reject(new Error("hosted gateway service helper timed out"));
|
|
171
|
+
}, 6e4);
|
|
172
|
+
child.stdout.on("data", (chunk) => {
|
|
173
|
+
stdout += String(chunk);
|
|
174
|
+
});
|
|
175
|
+
child.stderr.on("data", (chunk) => {
|
|
176
|
+
stderr += String(chunk);
|
|
177
|
+
});
|
|
178
|
+
child.on("error", (error) => {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
reject(error);
|
|
181
|
+
});
|
|
182
|
+
child.on("close", (code) => {
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
if (code === 0) {
|
|
185
|
+
resolve();
|
|
186
|
+
} else {
|
|
187
|
+
reject(new Error((stderr || stdout || `service helper exited ${code}`).trim()));
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
child.stdin.end(unit);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async function installHostedSystemdService(params) {
|
|
194
|
+
if (process.platform !== "linux") {
|
|
195
|
+
throw new Error("Hosted system service repair is supported on Linux only.");
|
|
196
|
+
}
|
|
197
|
+
const runAsUser = resolveRunAsUser();
|
|
198
|
+
const unit = buildHostedSystemdUnit({
|
|
199
|
+
...params,
|
|
200
|
+
runAsUser
|
|
201
|
+
});
|
|
202
|
+
await spawnCommand("systemctl", [
|
|
203
|
+
"--user",
|
|
204
|
+
"disable",
|
|
205
|
+
"--now",
|
|
206
|
+
`${SERVICE_NAME}.service`
|
|
207
|
+
]).catch(() => undefined);
|
|
208
|
+
const userUnit = path.join(os.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`);
|
|
209
|
+
await fs$1.rm(userUnit, { force: true }).catch(() => undefined);
|
|
210
|
+
await fs$1.rm(`${userUnit}.d`, {
|
|
211
|
+
recursive: true,
|
|
212
|
+
force: true
|
|
213
|
+
}).catch(() => undefined);
|
|
214
|
+
await spawnCommand("systemctl", ["--user", "daemon-reload"]).catch(() => undefined);
|
|
215
|
+
await runHelper(unit, runAsUser);
|
|
216
|
+
return { unitPath: `/etc/systemd/system/${SERVICE_NAME}.service` };
|
|
217
|
+
}
|
|
218
|
+
async function spawnCommand(command, args) {
|
|
219
|
+
await new Promise((resolve, reject) => {
|
|
220
|
+
const child = spawn(command, args, { stdio: "ignore" });
|
|
221
|
+
child.on("error", reject);
|
|
222
|
+
child.on("close", (code) => {
|
|
223
|
+
if (code === 0) {
|
|
224
|
+
resolve();
|
|
225
|
+
} else {
|
|
226
|
+
reject(new Error(`${command} ${args.join(" ")} exited ${code}`));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const __testing = {
|
|
232
|
+
readHostedSystemdCommand,
|
|
233
|
+
resolveRunAsUser
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
//#endregion
|
|
237
|
+
export { installHostedSystemdService, resolveHostedSystemdService };
|
package/dist/daemon/systemd.js
CHANGED
|
@@ -3,8 +3,8 @@ import { execFileUtf8 } from "./exec-file.js";
|
|
|
3
3
|
import { formatLine, toPosixPath, writeFormattedLines } from "./output.js";
|
|
4
4
|
import { resolveHomeDir } from "./paths.js";
|
|
5
5
|
import { parseKeyValueOutput } from "./runtime-parse.js";
|
|
6
|
-
import { enableSystemdUserLinger, readSystemdUserLingerStatus } from "./systemd-linger.js";
|
|
7
6
|
import { buildSystemdUnit, parseSystemdEnvAssignment, parseSystemdExecStart } from "./systemd-unit.js";
|
|
7
|
+
import { enableSystemdUserLinger, readSystemdUserLingerStatus } from "./systemd-linger.js";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import fs from "node:fs/promises";
|
|
10
10
|
|
|
@@ -354,4 +354,4 @@ async function uninstallLegacySystemdUnits({ env, stdout }) {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
//#endregion
|
|
357
|
-
export { installSystemdService, isSystemdServiceEnabled, isSystemdUserServiceAvailable, readSystemdServiceExecStart, readSystemdServiceRuntime, resolveSystemdUserUnitPath, restartSystemdService, stopSystemdService, uninstallLegacySystemdUnits, uninstallSystemdService };
|
|
357
|
+
export { installSystemdService, isSystemdServiceEnabled, isSystemdUserServiceAvailable, parseSystemdShow, readSystemdServiceExecStart, readSystemdServiceRuntime, resolveSystemdUserUnitPath, restartSystemdService, stopSystemdService, uninstallLegacySystemdUnits, uninstallSystemdService };
|