@ours.network/cli 0.5.0 → 1.0.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 +13 -13
- package/dist/boot-35BOHNSZ.js +9 -0
- package/dist/{chunk-6W3RHW3C.js → chunk-6K2JBKHI.js} +5 -6
- package/dist/{chunk-G2GHHOYX.js → chunk-DRQEV4SN.js} +2 -2
- package/dist/chunk-FXKFSNKS.js +166 -0
- package/dist/chunk-MHVYJY3H.js +2530 -0
- package/dist/{chunk-4IFFMMQO.js → chunk-QZIL3IT7.js} +3 -3
- package/dist/{chunk-YSHMCQ2B.js → chunk-SDNTPCKW.js} +3 -3
- package/dist/chunk-TNNBVUKK.js +241 -0
- package/dist/{chunk-3RLAHOPS.js → chunk-UZQWSOUB.js} +15 -1
- package/dist/{chunk-VGWQD7CJ.js → chunk-ZFE47KH3.js} +1 -2
- package/dist/commands.d.ts +3 -3
- package/dist/commands.js +1 -1
- package/dist/config-command.js +2 -2
- package/dist/connection.js +1 -1
- package/dist/daemon-UUG6P6GK.js +250 -0
- package/dist/help.js +2 -2
- package/dist/lifecycle.js +2 -2
- package/dist/main.js +11 -13
- package/dist/server-TDJBEWTU.js +2077 -0
- package/dist/service-instance.d.ts +3 -0
- package/dist/service-instance.js +7 -1
- package/dist/service.d.ts +30 -0
- package/dist/service.js +10 -4
- package/package.json +4 -2
- package/dist/chunk-227DBTDK.js +0 -132
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const DEFAULT_SYSTEMD_UNIT = "ours.service";
|
|
2
|
+
export declare const DEFAULT_LAUNCHD_LABEL = "solutions.adaptframework.ours";
|
|
2
3
|
export interface InstanceResult {
|
|
3
4
|
ok: boolean;
|
|
4
5
|
/** '' means "the default, unnamed daemon" — the historical single-unit behaviour. */
|
|
@@ -7,6 +8,8 @@ export interface InstanceResult {
|
|
|
7
8
|
}
|
|
8
9
|
export declare function normalizeInstanceName(raw: unknown): InstanceResult;
|
|
9
10
|
export declare function systemdUnitName(instance?: string): string;
|
|
11
|
+
export declare function launchdLabel(instance?: string): string;
|
|
10
12
|
export declare function serviceDescription(instance?: string): string;
|
|
11
13
|
export declare function instanceNameFromStateDir(stateDir: string): InstanceResult;
|
|
12
14
|
export declare function unitNameForStateDir(stateDir: string): string;
|
|
15
|
+
export declare function labelForStateDir(stateDir: string): string;
|
package/dist/service-instance.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_LAUNCHD_LABEL,
|
|
2
3
|
DEFAULT_SYSTEMD_UNIT,
|
|
3
4
|
instanceNameFromStateDir,
|
|
5
|
+
labelForStateDir,
|
|
6
|
+
launchdLabel,
|
|
4
7
|
normalizeInstanceName,
|
|
5
8
|
serviceDescription,
|
|
6
9
|
systemdUnitName,
|
|
7
10
|
unitNameForStateDir
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UZQWSOUB.js";
|
|
9
12
|
export {
|
|
13
|
+
DEFAULT_LAUNCHD_LABEL,
|
|
10
14
|
DEFAULT_SYSTEMD_UNIT,
|
|
11
15
|
instanceNameFromStateDir,
|
|
16
|
+
labelForStateDir,
|
|
17
|
+
launchdLabel,
|
|
12
18
|
normalizeInstanceName,
|
|
13
19
|
serviceDescription,
|
|
14
20
|
systemdUnitName,
|
package/dist/service.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export interface ServiceContext {
|
|
|
47
47
|
export interface ServiceDeps {
|
|
48
48
|
platform: NodeJS.Platform;
|
|
49
49
|
run(command: string, args: string[]): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* launchd only — the uid whose GUI domain the agent is bootstrapped into.
|
|
52
|
+
* Injected rather than read from `process.getuid()` so the domain a plan
|
|
53
|
+
* targets is assertable off macOS, where the rest of this file is developed.
|
|
54
|
+
*/
|
|
55
|
+
uid?: number;
|
|
50
56
|
}
|
|
51
57
|
export interface ServiceAdapter {
|
|
52
58
|
readonly name: string;
|
|
@@ -58,4 +64,28 @@ export interface ServiceAdapter {
|
|
|
58
64
|
* unit written before this CLI baked one in.
|
|
59
65
|
*/
|
|
60
66
|
export declare function bakedStateDir(unit: string): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* What an already-present service definition says about itself, in the terms
|
|
69
|
+
* the guard needs: did this CLI write it, and which daemon is it for. Reading
|
|
70
|
+
* it is per-format; judging it is not, which is what keeps the systemd and
|
|
71
|
+
* launchd guards from drifting into two different sets of refusals.
|
|
72
|
+
*/
|
|
73
|
+
export interface ExistingDefinition {
|
|
74
|
+
managed: boolean;
|
|
75
|
+
/** The baked state directory, or undefined for a definition that bakes none. */
|
|
76
|
+
owner?: string;
|
|
77
|
+
}
|
|
61
78
|
export declare function createLinuxUserSystemdAdapter(deps?: ServiceDeps): ServiceAdapter;
|
|
79
|
+
/**
|
|
80
|
+
* The state directory a CLI-managed launchd agent was installed for. The plist
|
|
81
|
+
* counterpart of `bakedStateDir`, and the reason a label — which is not
|
|
82
|
+
* injective over state directories — is still enough to find its daemon.
|
|
83
|
+
*/
|
|
84
|
+
export declare function bakedStateDirFromPlist(plist: string): string | undefined;
|
|
85
|
+
export declare function createMacosUserLaunchdAdapter(deps?: ServiceDeps): ServiceAdapter;
|
|
86
|
+
/**
|
|
87
|
+
* The adapter for the platform this CLI is running on. The two factories keep
|
|
88
|
+
* refusing every platform but their own — the selector is the only thing that
|
|
89
|
+
* is allowed to know which platform maps to which service manager.
|
|
90
|
+
*/
|
|
91
|
+
export declare function createServiceAdapter(deps?: ServiceDeps): ServiceAdapter;
|
package/dist/service.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SERVICE_NAME,
|
|
3
3
|
bakedStateDir,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
bakedStateDirFromPlist,
|
|
5
|
+
createLinuxUserSystemdAdapter,
|
|
6
|
+
createMacosUserLaunchdAdapter,
|
|
7
|
+
createServiceAdapter
|
|
8
|
+
} from "./chunk-TNNBVUKK.js";
|
|
9
|
+
import "./chunk-UZQWSOUB.js";
|
|
7
10
|
export {
|
|
8
11
|
SERVICE_NAME,
|
|
9
12
|
bakedStateDir,
|
|
10
|
-
|
|
13
|
+
bakedStateDirFromPlist,
|
|
14
|
+
createLinuxUserSystemdAdapter,
|
|
15
|
+
createMacosUserLaunchdAdapter,
|
|
16
|
+
createServiceAdapter
|
|
11
17
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Transport-neutral operator CLI for the ours shared daemon",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@
|
|
26
|
+
"@adapt-toolkit/sdk": "0.10.12",
|
|
27
|
+
"@adapt-toolkit/sdk-native": "0.10.12",
|
|
28
|
+
"@ours.network/sdk": "^2.0.0"
|
|
27
29
|
}
|
|
28
30
|
}
|
package/dist/chunk-227DBTDK.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
instanceNameFromStateDir,
|
|
3
|
-
serviceDescription,
|
|
4
|
-
unitNameForStateDir
|
|
5
|
-
} from "./chunk-3RLAHOPS.js";
|
|
6
|
-
|
|
7
|
-
// src/service.ts
|
|
8
|
-
import { execFile as nodeExecFile } from "node:child_process";
|
|
9
|
-
import { promisify } from "node:util";
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
import { dirname, join, resolve } from "node:path";
|
|
13
|
-
var execFile = promisify(nodeExecFile);
|
|
14
|
-
var MARKER = "# Managed by @ours.network/cli";
|
|
15
|
-
var SERVICE_NAME = "ours.service";
|
|
16
|
-
var defaultDeps = {
|
|
17
|
-
platform: process.platform,
|
|
18
|
-
run: async (command, args) => {
|
|
19
|
-
await execFile(command, args, { windowsHide: true });
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
function systemdEscapeArgument(value) {
|
|
23
|
-
if (/[\n\r]/.test(value)) throw new Error("service arguments must not contain newlines");
|
|
24
|
-
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
|
|
25
|
-
}
|
|
26
|
-
function systemdUnescapeArgument(value) {
|
|
27
|
-
const trimmed = value.trim();
|
|
28
|
-
if (!trimmed.startsWith('"') || !trimmed.endsWith('"') || trimmed.length < 2) return void 0;
|
|
29
|
-
return trimmed.slice(1, -1).replaceAll("%%", "%").replaceAll('\\"', '"').replaceAll("\\\\", "\\");
|
|
30
|
-
}
|
|
31
|
-
function bakedStateDir(unit) {
|
|
32
|
-
for (const line of unit.split("\n")) {
|
|
33
|
-
if (!line.startsWith("Environment=OURS_STATE_DIR=")) continue;
|
|
34
|
-
return systemdUnescapeArgument(line.slice("Environment=OURS_STATE_DIR=".length));
|
|
35
|
-
}
|
|
36
|
-
return void 0;
|
|
37
|
-
}
|
|
38
|
-
function describeConflict(previous, serviceFile, stateDir, verb = "overwrite") {
|
|
39
|
-
if (!previous.startsWith(MARKER)) {
|
|
40
|
-
return {
|
|
41
|
-
kind: "unmanaged",
|
|
42
|
-
serviceFile,
|
|
43
|
-
message: verb === "remove" ? `${serviceFile} is not managed by @ours.network/cli; refusing to remove it` : `${serviceFile} is not managed by @ours.network/cli; refusing to overwrite it without --force`
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const owner = bakedStateDir(previous);
|
|
47
|
-
if (owner !== void 0 && owner !== stateDir) {
|
|
48
|
-
return {
|
|
49
|
-
kind: "other-state-dir",
|
|
50
|
-
serviceFile,
|
|
51
|
-
owner,
|
|
52
|
-
message: `${serviceFile} is the boot service for the daemon at ${owner}, not ${stateDir}; refusing to ${verb} it without --force`
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return void 0;
|
|
56
|
-
}
|
|
57
|
-
function resolveStateDir(context) {
|
|
58
|
-
return resolve(context.stateDir ?? join(context.homeDir ?? homedir(), ".ours"));
|
|
59
|
-
}
|
|
60
|
-
function createLinuxUserSystemdAdapter(deps = defaultDeps) {
|
|
61
|
-
if (deps.platform !== "linux") throw new Error(`service management is not supported on ${deps.platform}; use an external launcher for \`ours daemon serve\``);
|
|
62
|
-
return {
|
|
63
|
-
name: "systemd-user",
|
|
64
|
-
async install(context) {
|
|
65
|
-
const home = context.homeDir ?? homedir();
|
|
66
|
-
const stateDir = resolveStateDir(context);
|
|
67
|
-
const unitName = unitNameForStateDir(stateDir);
|
|
68
|
-
const serviceFile = join(home, ".config", "systemd", "user", unitName);
|
|
69
|
-
const unitConfig = context.configPath ? resolve(context.configPath) : void 0;
|
|
70
|
-
const args = [process.execPath, resolve(context.cliPath), "daemon", "serve"];
|
|
71
|
-
if (unitConfig) args.push("--config", unitConfig);
|
|
72
|
-
const environment = `Environment=OURS_STATE_DIR=${systemdEscapeArgument(stateDir)}
|
|
73
|
-
`;
|
|
74
|
-
const unit = `${MARKER}
|
|
75
|
-
[Unit]
|
|
76
|
-
Description=${serviceDescription(instanceNameFromStateDir(stateDir).name)}
|
|
77
|
-
After=network-online.target
|
|
78
|
-
|
|
79
|
-
[Service]
|
|
80
|
-
Type=simple
|
|
81
|
-
ExecStart=${args.map(systemdEscapeArgument).join(" ")}
|
|
82
|
-
${environment}Restart=on-failure
|
|
83
|
-
RestartSec=2
|
|
84
|
-
|
|
85
|
-
[Install]
|
|
86
|
-
WantedBy=default.target
|
|
87
|
-
`;
|
|
88
|
-
if (existsSync(serviceFile)) {
|
|
89
|
-
const previous = readFileSync(serviceFile, "utf8");
|
|
90
|
-
const conflict = describeConflict(previous, serviceFile, stateDir);
|
|
91
|
-
if (conflict && !context.force) {
|
|
92
|
-
if (!context.dryRun) throw new Error(conflict.message);
|
|
93
|
-
return { adapter: this.name, action: "install", serviceFile, unitName, stateDir, configPath: unitConfig, commands: [], changed: false, dryRun: true, conflict };
|
|
94
|
-
}
|
|
95
|
-
if (previous === unit) return { adapter: this.name, action: "install", serviceFile, unitName, stateDir, configPath: unitConfig, commands: [], changed: false, dryRun: context.dryRun === true };
|
|
96
|
-
}
|
|
97
|
-
const commands = [["systemctl", "--user", "daemon-reload"], ["systemctl", "--user", "enable", "--now", unitName]];
|
|
98
|
-
if (!context.dryRun) {
|
|
99
|
-
mkdirSync(dirname(serviceFile), { recursive: true });
|
|
100
|
-
writeFileSync(serviceFile, unit, { mode: 420 });
|
|
101
|
-
for (const [command, ...args2] of commands) await deps.run(command, args2);
|
|
102
|
-
}
|
|
103
|
-
return { adapter: this.name, action: "install", serviceFile, unitName, stateDir, configPath: unitConfig, commands, changed: true, dryRun: context.dryRun === true };
|
|
104
|
-
},
|
|
105
|
-
async uninstall(context) {
|
|
106
|
-
const home = context.homeDir ?? homedir();
|
|
107
|
-
const stateDir = resolveStateDir(context);
|
|
108
|
-
const unitName = unitNameForStateDir(stateDir);
|
|
109
|
-
const serviceFile = join(home, ".config", "systemd", "user", unitName);
|
|
110
|
-
if (!existsSync(serviceFile)) return { adapter: this.name, action: "uninstall", serviceFile, unitName, stateDir, commands: [], changed: false, dryRun: context.dryRun === true };
|
|
111
|
-
const previous = readFileSync(serviceFile, "utf8");
|
|
112
|
-
const conflict = describeConflict(previous, serviceFile, stateDir, "remove");
|
|
113
|
-
if (conflict && !(context.force && previous.startsWith(MARKER))) {
|
|
114
|
-
if (!context.dryRun) throw new Error(conflict.message);
|
|
115
|
-
return { adapter: this.name, action: "uninstall", serviceFile, unitName, stateDir, commands: [], changed: false, dryRun: true, conflict };
|
|
116
|
-
}
|
|
117
|
-
const commands = [["systemctl", "--user", "disable", "--now", unitName], ["systemctl", "--user", "daemon-reload"]];
|
|
118
|
-
if (!context.dryRun) {
|
|
119
|
-
await deps.run(commands[0][0], commands[0].slice(1));
|
|
120
|
-
unlinkSync(serviceFile);
|
|
121
|
-
await deps.run(commands[1][0], commands[1].slice(1));
|
|
122
|
-
}
|
|
123
|
-
return { adapter: this.name, action: "uninstall", serviceFile, unitName, stateDir, commands, changed: true, dryRun: context.dryRun === true };
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export {
|
|
129
|
-
SERVICE_NAME,
|
|
130
|
-
bakedStateDir,
|
|
131
|
-
createLinuxUserSystemdAdapter
|
|
132
|
-
};
|