@push.rocks/smartdaemon 2.5.0 → 2.6.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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartdaemon',
|
|
6
|
-
version: '2.
|
|
6
|
+
version: '2.6.0',
|
|
7
7
|
description: 'Start scripts as long running daemons and manage them.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx5QkFBeUI7SUFDL0IsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHdEQUF3RDtDQUN0RSxDQUFBIn0=
|
|
@@ -25,11 +25,21 @@ export interface ISystemdUnitConfigurationState {
|
|
|
25
25
|
readonly dropInPaths: string;
|
|
26
26
|
readonly needsReload: boolean;
|
|
27
27
|
}
|
|
28
|
+
export interface ISystemdUnitRelationships {
|
|
29
|
+
readonly unitName: string;
|
|
30
|
+
readonly before: readonly string[];
|
|
31
|
+
readonly after: readonly string[];
|
|
32
|
+
readonly requires: readonly string[];
|
|
33
|
+
readonly wants: readonly string[];
|
|
34
|
+
readonly conflicts: readonly string[];
|
|
35
|
+
readonly requiredBy: readonly string[];
|
|
36
|
+
readonly wantedBy: readonly string[];
|
|
37
|
+
}
|
|
28
38
|
export declare class SystemdUnitError extends Error {
|
|
29
39
|
readonly code: 'invalid_options' | 'command_failed' | 'invalid_state';
|
|
30
40
|
constructor(code: 'invalid_options' | 'command_failed' | 'invalid_state');
|
|
31
41
|
}
|
|
32
|
-
/** Controls an existing system unit without
|
|
42
|
+
/** Controls an existing system unit without rewriting its definition or escalating privileges. */
|
|
33
43
|
export declare class SystemdUnit {
|
|
34
44
|
private readonly unitName;
|
|
35
45
|
private readonly executable;
|
|
@@ -39,6 +49,11 @@ export declare class SystemdUnit {
|
|
|
39
49
|
inspect(): Promise<ISystemdUnitState>;
|
|
40
50
|
/** Await the systemd job, then inspect. This does not enable or unmask the unit. */
|
|
41
51
|
start(): Promise<ISystemdUnitState>;
|
|
52
|
+
/** Persistently enables the existing definition, then inspects. Never starts, unmasks or forces links.
|
|
53
|
+
* Owning installers must separately verify exact file/configuration and required relationships. */
|
|
54
|
+
enable(): Promise<ISystemdUnitState>;
|
|
55
|
+
/** Loaded manager graph, including reverse edges. Enablement alone does not prove every required link. */
|
|
56
|
+
inspectRelationships(): Promise<ISystemdUnitRelationships>;
|
|
42
57
|
/** Await the systemd job, then inspect. The snapshot alone is not an application data-drain proof. */
|
|
43
58
|
stop(): Promise<ISystemdUnitState>;
|
|
44
59
|
/** Reports the manager's selected definition without reading or changing its contents. */
|
|
@@ -18,7 +18,7 @@ const properties = [
|
|
|
18
18
|
'KillMode',
|
|
19
19
|
'Job',
|
|
20
20
|
];
|
|
21
|
-
/** Controls an existing system unit without
|
|
21
|
+
/** Controls an existing system unit without rewriting its definition or escalating privileges. */
|
|
22
22
|
export class SystemdUnit {
|
|
23
23
|
constructor(optionsArg) {
|
|
24
24
|
this.unitName = optionsArg?.unitName;
|
|
@@ -70,6 +70,40 @@ export class SystemdUnit {
|
|
|
70
70
|
await this.command(['start', this.unitName]);
|
|
71
71
|
return this.inspect();
|
|
72
72
|
}
|
|
73
|
+
/** Persistently enables the existing definition, then inspects. Never starts, unmasks or forces links.
|
|
74
|
+
* Owning installers must separately verify exact file/configuration and required relationships. */
|
|
75
|
+
async enable() {
|
|
76
|
+
await this.command(['enable', this.unitName]);
|
|
77
|
+
return this.inspect();
|
|
78
|
+
}
|
|
79
|
+
/** Loaded manager graph, including reverse edges. Enablement alone does not prove every required link. */
|
|
80
|
+
async inspectRelationships() {
|
|
81
|
+
const keys = ['Id', 'Before', 'After', 'Requires', 'Wants', 'Conflicts', 'RequiredBy', 'WantedBy'];
|
|
82
|
+
const stdout = await this.command(['show', this.unitName, `--property=${keys.join(',')}`, '--all']);
|
|
83
|
+
const fields = new Map();
|
|
84
|
+
for (const line of (stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout).split('\n')) {
|
|
85
|
+
const separator = line.indexOf('='), key = line.slice(0, separator);
|
|
86
|
+
if (separator < 1 || !keys.includes(key) || fields.has(key))
|
|
87
|
+
throw new SystemdUnitError('invalid_state');
|
|
88
|
+
fields.set(key, line.slice(separator + 1));
|
|
89
|
+
}
|
|
90
|
+
if (fields.size !== keys.length || fields.get('Id') !== this.unitName)
|
|
91
|
+
throw new SystemdUnitError('invalid_state');
|
|
92
|
+
const units = (key) => {
|
|
93
|
+
const text = fields.get(key);
|
|
94
|
+
const values = text === '' ? [] : text.split(' ');
|
|
95
|
+
// Unit names are ASCII; retain systemd's literal hex escapes without decoding
|
|
96
|
+
// them into separators. Output and every individual name remain bounded.
|
|
97
|
+
if (values.some(value => value.length > 255 ||
|
|
98
|
+
!/^(?:[a-zA-Z0-9_.:@-]|\\x[0-9a-fA-F]{2})+\.(?:service|socket|device|mount|automount|swap|target|path|timer|slice|scope)$/.test(value)) ||
|
|
99
|
+
new Set(values).size !== values.length)
|
|
100
|
+
throw new SystemdUnitError('invalid_state');
|
|
101
|
+
return Object.freeze(values.sort());
|
|
102
|
+
};
|
|
103
|
+
return Object.freeze({ unitName: this.unitName, before: units('Before'), after: units('After'),
|
|
104
|
+
requires: units('Requires'), wants: units('Wants'), conflicts: units('Conflicts'),
|
|
105
|
+
requiredBy: units('RequiredBy'), wantedBy: units('WantedBy') });
|
|
106
|
+
}
|
|
73
107
|
/** Await the systemd job, then inspect. The snapshot alone is not an application data-drain proof. */
|
|
74
108
|
async stop() {
|
|
75
109
|
await this.command(['stop', this.unitName]);
|
|
@@ -122,4 +156,4 @@ export class SystemdUnit {
|
|
|
122
156
|
});
|
|
123
157
|
}
|
|
124
158
|
}
|
|
125
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
159
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zeXN0ZW1kdW5pdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuc3lzdGVtZHVuaXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQTJDcEQsTUFBTSxPQUFPLGdCQUFpQixTQUFRLEtBQUs7SUFDekMsWUFBNEIsSUFBNEQ7UUFDdEYsS0FBSyxDQUFDLGtDQUFrQyxJQUFJLEdBQUcsQ0FBQyxDQUFDO1FBRHZCLFNBQUksR0FBSixJQUFJLENBQXdEO1FBRXRGLElBQUksQ0FBQyxJQUFJLEdBQUcsa0JBQWtCLENBQUM7SUFDakMsQ0FBQztDQUNGO0FBRUQsTUFBTSxVQUFVLEdBQUc7SUFDakIsSUFBSTtJQUNKLFdBQVc7SUFDWCxhQUFhO0lBQ2IsVUFBVTtJQUNWLGVBQWU7SUFDZixTQUFTO0lBQ1QsWUFBWTtJQUNaLGNBQWM7SUFDZCxVQUFVO0lBQ1YsS0FBSztDQUNHLENBQUM7QUFFWCxrR0FBa0c7QUFDbEcsTUFBTSxPQUFPLFdBQVc7SUFLdEIsWUFBWSxVQUErQjtRQUN6QyxJQUFJLENBQUMsUUFBUSxHQUFHLFVBQVUsRUFBRSxRQUFRLENBQUM7UUFDckMsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLEVBQUUsYUFBYSxJQUFJLG9CQUFvQixDQUFDO1FBQ3BFLElBQUksQ0FBQyxPQUFPLEdBQUcsVUFBVSxFQUFFLGdCQUFnQixJQUFJLE1BQU0sQ0FBQztRQUN0RCxJQUNFLE9BQU8sSUFBSSxDQUFDLFFBQVEsS0FBSyxRQUFRO1lBQ2pDLENBQUMsK0NBQStDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7WUFDcEUsT0FBTyxJQUFJLENBQUMsVUFBVSxLQUFLLFFBQVEsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUM7WUFDaEYsSUFBSSxDQUFDLFVBQVUsQ0FBQyxR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
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -191,6 +191,23 @@ list and `needsReload` flag. `reloadConfiguration()` explicitly reloads the mana
|
|
|
191
191
|
unit definitions globally and returns a fresh configuration snapshot. Neither
|
|
192
192
|
operation changes enablement or masks.
|
|
193
193
|
|
|
194
|
+
`enable()` persistently enables the existing unit definition and returns a fresh
|
|
195
|
+
state snapshot. It does not rewrite the definition, start the unit, unmask it or
|
|
196
|
+
force conflicting links. Run it inside the owning installer's cross-process
|
|
197
|
+
installation scope, after any required offline commissioning. A failed or timed
|
|
198
|
+
out command can leave partial enablement; inspect and retry the exact operation
|
|
199
|
+
without assuming cancellation or undoing the application's retained protection.
|
|
200
|
+
|
|
201
|
+
`inspectRelationships()` returns immutable sorted arrays for `before`, `after`,
|
|
202
|
+
`requires`, `wants`, `conflicts`, `requiredBy` and `wantedBy` from the loaded manager
|
|
203
|
+
graph. Literal systemd hex escapes are preserved. The bounded snapshot rejects
|
|
204
|
+
missing fields, foreign identities, duplicate relationships and malformed names.
|
|
205
|
+
`unitFileState === 'enabled'` proves only that some enablement links exist. For a
|
|
206
|
+
boot guard, verify that its `requiredBy` and `before` both include every required
|
|
207
|
+
target and the actual network provider; inspect the provider's `requires` and
|
|
208
|
+
`after` as well. Recheck the exact file digest, selected fragment, absence of
|
|
209
|
+
drop-ins and `needsReload === false` before permitting dependent activation.
|
|
210
|
+
|
|
194
211
|
### Installing a direct systemd service definition
|
|
195
212
|
|
|
196
213
|
`SystemdServiceDefinition` encodes a literal executable and argument list, with
|
|
@@ -279,6 +296,15 @@ python3 test/native/qualify-systemd.py --image /path/to/noble-server-cloudimg-am
|
|
|
279
296
|
--work-root /tmp/smartdaemon-systemd-qualification-local
|
|
280
297
|
```
|
|
281
298
|
|
|
299
|
+
To exercise the public enablement and relationship APIs in the same guest, bundle
|
|
300
|
+
`test/native/activate-systemd.ts` as a self-contained Node ESM file and also supply
|
|
301
|
+
`--node /path/to/node --activation-bundle /path/to/activate.mjs`. Any library absent
|
|
302
|
+
from the minimal image can be supplied explicitly with repeatable
|
|
303
|
+
`--node-library /path/to/libname.so.N`. These hashed test inputs travel on a
|
|
304
|
+
read-only ISO. The API fixture checks enablement without implicit start, oneshot
|
|
305
|
+
readiness, unchanged definition/configuration, detection of a missing provider
|
|
306
|
+
link despite enabled state, exact enable retry and preservation of a mask.
|
|
307
|
+
|
|
282
308
|
Installation requires Linux, root and a quiescent exact unit with no process,
|
|
283
309
|
cgroup or job. It rejects effective drop-ins and shadowing fragments. Definitions
|
|
284
310
|
default to `/usr/local/lib/systemd/system`, below administrator configuration in
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -30,6 +30,17 @@ export interface ISystemdUnitConfigurationState {
|
|
|
30
30
|
readonly needsReload: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export interface ISystemdUnitRelationships {
|
|
34
|
+
readonly unitName: string;
|
|
35
|
+
readonly before: readonly string[];
|
|
36
|
+
readonly after: readonly string[];
|
|
37
|
+
readonly requires: readonly string[];
|
|
38
|
+
readonly wants: readonly string[];
|
|
39
|
+
readonly conflicts: readonly string[];
|
|
40
|
+
readonly requiredBy: readonly string[];
|
|
41
|
+
readonly wantedBy: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
33
44
|
export class SystemdUnitError extends Error {
|
|
34
45
|
constructor(public readonly code: 'invalid_options' | 'command_failed' | 'invalid_state') {
|
|
35
46
|
super(`Systemd unit operation failed: ${code}.`);
|
|
@@ -50,7 +61,7 @@ const properties = [
|
|
|
50
61
|
'Job',
|
|
51
62
|
] as const;
|
|
52
63
|
|
|
53
|
-
/** Controls an existing system unit without
|
|
64
|
+
/** Controls an existing system unit without rewriting its definition or escalating privileges. */
|
|
54
65
|
export class SystemdUnit {
|
|
55
66
|
private readonly unitName: string;
|
|
56
67
|
private readonly executable: string;
|
|
@@ -113,6 +124,39 @@ export class SystemdUnit {
|
|
|
113
124
|
return this.inspect();
|
|
114
125
|
}
|
|
115
126
|
|
|
127
|
+
/** Persistently enables the existing definition, then inspects. Never starts, unmasks or forces links.
|
|
128
|
+
* Owning installers must separately verify exact file/configuration and required relationships. */
|
|
129
|
+
public async enable(): Promise<ISystemdUnitState> {
|
|
130
|
+
await this.command(['enable', this.unitName]);
|
|
131
|
+
return this.inspect();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Loaded manager graph, including reverse edges. Enablement alone does not prove every required link. */
|
|
135
|
+
public async inspectRelationships(): Promise<ISystemdUnitRelationships> {
|
|
136
|
+
const keys = ['Id', 'Before', 'After', 'Requires', 'Wants', 'Conflicts', 'RequiredBy', 'WantedBy'];
|
|
137
|
+
const stdout = await this.command(['show', this.unitName, `--property=${keys.join(',')}`, '--all']);
|
|
138
|
+
const fields = new Map<string, string>();
|
|
139
|
+
for (const line of (stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout).split('\n')) {
|
|
140
|
+
const separator = line.indexOf('='), key = line.slice(0, separator);
|
|
141
|
+
if (separator < 1 || !keys.includes(key) || fields.has(key)) throw new SystemdUnitError('invalid_state');
|
|
142
|
+
fields.set(key, line.slice(separator + 1));
|
|
143
|
+
}
|
|
144
|
+
if (fields.size !== keys.length || fields.get('Id') !== this.unitName) throw new SystemdUnitError('invalid_state');
|
|
145
|
+
const units = (key: string): readonly string[] => {
|
|
146
|
+
const text = fields.get(key)!;
|
|
147
|
+
const values = text === '' ? [] : text.split(' ');
|
|
148
|
+
// Unit names are ASCII; retain systemd's literal hex escapes without decoding
|
|
149
|
+
// them into separators. Output and every individual name remain bounded.
|
|
150
|
+
if (values.some(value => value.length > 255 ||
|
|
151
|
+
!/^(?:[a-zA-Z0-9_.:@-]|\\x[0-9a-fA-F]{2})+\.(?:service|socket|device|mount|automount|swap|target|path|timer|slice|scope)$/.test(value)) ||
|
|
152
|
+
new Set(values).size !== values.length) throw new SystemdUnitError('invalid_state');
|
|
153
|
+
return Object.freeze(values.sort());
|
|
154
|
+
};
|
|
155
|
+
return Object.freeze({ unitName: this.unitName, before: units('Before'), after: units('After'),
|
|
156
|
+
requires: units('Requires'), wants: units('Wants'), conflicts: units('Conflicts'),
|
|
157
|
+
requiredBy: units('RequiredBy'), wantedBy: units('WantedBy') });
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
/** Await the systemd job, then inspect. The snapshot alone is not an application data-drain proof. */
|
|
117
161
|
public async stop(): Promise<ISystemdUnitState> {
|
|
118
162
|
await this.command(['stop', this.unitName]);
|