@push.rocks/smartdaemon 2.7.2 → 2.9.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.systemdservicedefinition.d.ts +14 -4
- package/dist_ts/classes.systemdservicedefinition.js +30 -3
- package/dist_ts/classes.systemdunit.d.ts +27 -0
- package/dist_ts/classes.systemdunit.js +78 -1
- package/dist_ts/classes.systemdunitfile.d.ts +34 -1
- package/dist_ts/classes.systemdunitfile.js +38 -16
- package/dist_ts/systemd.definition.d.ts +11 -1
- package/dist_ts/systemd.definition.js +18 -7
- package/package.json +4 -4
- package/readme.md +110 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.systemdservicedefinition.ts +46 -6
- package/ts/classes.systemdunit.ts +98 -0
- package/ts/classes.systemdunitfile.ts +81 -20
- package/ts/systemd.definition.ts +29 -7
|
@@ -3,6 +3,16 @@ export interface ISystemdServiceStartup {
|
|
|
3
3
|
remainAfterExit: boolean;
|
|
4
4
|
timeoutStartSeconds: number;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Ready when a process admitted by `notifyAccess` sends `READY=1`. `NotifyAccess=none` is not
|
|
8
|
+
* offered: systemd would discard every notification and the start could only time out.
|
|
9
|
+
*/
|
|
10
|
+
export interface ISystemdServiceNotifyStartup {
|
|
11
|
+
type: 'notify';
|
|
12
|
+
notifyAccess: 'main' | 'exec' | 'all';
|
|
13
|
+
timeoutStartSeconds: number;
|
|
14
|
+
}
|
|
15
|
+
export type TSystemdServiceStartup = ISystemdServiceStartup | ISystemdServiceNotifyStartup;
|
|
6
16
|
/** Complete literal relations. System services always retain After=local-fs.target. */
|
|
7
17
|
export interface ISystemdServiceDependencies {
|
|
8
18
|
defaultDependencies: boolean;
|
|
@@ -19,7 +29,7 @@ export interface ISystemdServiceInstallTargets {
|
|
|
19
29
|
}
|
|
20
30
|
/** Internal validated renderer; no systemd/process/filesystem effects. */
|
|
21
31
|
export declare const renderServiceRelations: (options: {
|
|
22
|
-
startup?:
|
|
32
|
+
startup?: TSystemdServiceStartup;
|
|
23
33
|
dependencies?: ISystemdServiceDependencies;
|
|
24
34
|
install?: ISystemdServiceInstallTargets;
|
|
25
35
|
}, unitName: string, restart: string, scope: "system" | "user", fail: () => never) => {
|
|
@@ -3,9 +3,10 @@ import * as plugins from './smartdaemon.plugins.js';
|
|
|
3
3
|
export const renderServiceRelations = (options, unitName, restart, scope, fail) => {
|
|
4
4
|
const requireValue = (value) => { if (!value)
|
|
5
5
|
fail(); };
|
|
6
|
+
const plain = (value) => requireValue(value !== null && typeof value === 'object'
|
|
7
|
+
&& !plugins.util.types.isProxy(value) && Object.getPrototypeOf(value) === Object.prototype);
|
|
6
8
|
const record = (value, keys) => {
|
|
7
|
-
|
|
8
|
-
&& Object.getPrototypeOf(value) === Object.prototype);
|
|
9
|
+
plain(value);
|
|
9
10
|
const actual = Reflect.ownKeys(value);
|
|
10
11
|
requireValue(actual.length === keys.length && actual.every(key => typeof key === 'string' && keys.includes(key)
|
|
11
12
|
&& Object.getOwnPropertyDescriptor(value, key)?.enumerable && 'value' in Object.getOwnPropertyDescriptor(value, key)));
|
|
@@ -34,10 +35,20 @@ export const renderServiceRelations = (options, unitName, restart, scope, fail)
|
|
|
34
35
|
};
|
|
35
36
|
if (options.startup !== undefined) {
|
|
36
37
|
const value = options.startup;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
plain(value);
|
|
39
|
+
// Select the shape from the data descriptor so no accessor runs before record() refuses it.
|
|
40
|
+
const kind = Object.getOwnPropertyDescriptor(value, 'type');
|
|
41
|
+
record(value, kind !== undefined && 'value' in kind && kind.value === 'notify'
|
|
42
|
+
? ['type', 'notifyAccess', 'timeoutStartSeconds'] : ['type', 'remainAfterExit', 'timeoutStartSeconds']);
|
|
43
|
+
requireValue(Number.isSafeInteger(value.timeoutStartSeconds) && value.timeoutStartSeconds >= 1 && value.timeoutStartSeconds <= 600);
|
|
44
|
+
if (value.type === 'notify') {
|
|
45
|
+
requireValue(['main', 'exec', 'all'].includes(value.notifyAccess));
|
|
46
|
+
result.service = ['Type=notify', `NotifyAccess=${value.notifyAccess}`, `TimeoutStartSec=${value.timeoutStartSeconds}s`];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
requireValue(value.type === 'oneshot' && typeof value.remainAfterExit === 'boolean' && restart !== 'always');
|
|
50
|
+
result.service = ['Type=oneshot', `RemainAfterExit=${value.remainAfterExit ? 'yes' : 'no'}`, `TimeoutStartSec=${value.timeoutStartSeconds}s`];
|
|
51
|
+
}
|
|
41
52
|
}
|
|
42
53
|
if (options.dependencies !== undefined) {
|
|
43
54
|
const value = options.dependencies;
|
|
@@ -58,4 +69,4 @@ export const renderServiceRelations = (options, unitName, restart, scope, fail)
|
|
|
58
69
|
}
|
|
59
70
|
return result;
|
|
60
71
|
};
|
|
61
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
72
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3lzdGVtZC5kZWZpbml0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc3lzdGVtZC5kZWZpbml0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sMEJBQTBCLENBQUM7QUFvQ3BELDBFQUEwRTtBQUMxRSxNQUFNLENBQUMsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLE9BSXRDLEVBQUUsUUFBZ0IsRUFBRSxPQUFlLEVBQUUsS0FBd0IsRUFBRSxJQUFpQixFQUUvRSxFQUFFO0lBQ0YsTUFBTSxZQUFZLEdBQUcsQ0FBQyxLQUFjLEVBQVEsRUFBRSxHQUFHLElBQUksQ0FBQyxLQUFLO1FBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDdkUsTUFBTSxLQUFLLEdBQUcsQ0FBQyxLQUFjLEVBQVEsRUFBRSxDQUFDLFlBQVksQ0FBQyxLQUFLLEtBQUssSUFBSSxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVE7V0FDM0YsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsS0FBSyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDOUYsTUFBTSxNQUFNLEdBQUcsQ0FBQyxLQUFjLEVBQUUsSUFBdUIsRUFBUSxFQUFFO1FBQy9ELEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNiLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsS0FBZSxDQUFDLENBQUM7UUFDaEQsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEtBQUssSUFBSSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDO2VBQzFHLE1BQU0sQ0FBQyx3QkFB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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartdaemon",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Start scripts as long running daemons and manage them.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"homepage": "https://code.foss.global/push.rocks/smartdaemon",
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@git.zone/tsbuild": "^5.0.0",
|
|
30
|
-
"@git.zone/tsrun": "^3.0.
|
|
31
|
-
"@git.zone/tstest": "^6.2
|
|
32
|
-
"@types/node": "^26.6.
|
|
30
|
+
"@git.zone/tsrun": "^3.0.1",
|
|
31
|
+
"@git.zone/tstest": "^6.3.2",
|
|
32
|
+
"@types/node": "^26.6.2"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@push.rocks/lik": "^6.4.1",
|
package/readme.md
CHANGED
|
@@ -212,6 +212,23 @@ list and `needsReload` flag. `reloadConfiguration()` explicitly reloads the mana
|
|
|
212
212
|
unit definitions globally and returns a fresh configuration snapshot. Neither
|
|
213
213
|
operation changes enablement or masks.
|
|
214
214
|
|
|
215
|
+
`inspectServiceSettings()` reports the loaded service settings a definition renders, drop-ins
|
|
216
|
+
included: `type`, `notifyAccess`, `restart`, `killMode` (systemd's own words; `type` is empty
|
|
217
|
+
for a unit systemd could not load), `delegate`, `restartMicroseconds` and
|
|
218
|
+
`timeoutStopMicroseconds` (exact integers or `'infinity'`), `limitNoFile` and `limitNoFileSoft`
|
|
219
|
+
(hard and soft), `tasksMax` (integers or `'infinity'`) and `oomScoreAdjust`. A missing, repeated,
|
|
220
|
+
foreign or malformed property rejects with `invalid_state`, so a manager too old to report one of
|
|
221
|
+
them fails closed. Reload changed files first; the snapshot describes what the manager loaded.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const settings = await new SystemdUnit({ unitName: 'pallet-containerd.service' }).inspectServiceSettings();
|
|
225
|
+
const carriesContract = settings.type === 'notify' && settings.notifyAccess === 'all' &&
|
|
226
|
+
settings.killMode === 'process' && settings.delegate && settings.limitNoFile === 'infinity' &&
|
|
227
|
+
settings.limitNoFileSoft === 'infinity' && settings.tasksMax === 'infinity' &&
|
|
228
|
+
settings.oomScoreAdjust === -999 && settings.restart === 'always' &&
|
|
229
|
+
settings.restartMicroseconds === 2_000_000 && settings.timeoutStopMicroseconds === 60_000_000;
|
|
230
|
+
```
|
|
231
|
+
|
|
215
232
|
`enable()` persistently enables the existing unit definition and returns a fresh
|
|
216
233
|
state snapshot. It does not rewrite the definition, start the unit, unmask it or
|
|
217
234
|
force conflicting links. Run it inside the owning installer's cross-process
|
|
@@ -315,12 +332,100 @@ a login session:
|
|
|
315
332
|
const installed = await new SystemdUnitFile({ scope: 'user', unitName: 'authswitch.service' }).inspect();
|
|
316
333
|
```
|
|
317
334
|
|
|
335
|
+
`inspect()` and `install()` check every component of the unit directory from `/`
|
|
336
|
+
down, then the unit file itself. A refusal is a `SystemdUnitFileUnsafePathError`, a
|
|
337
|
+
`SystemdUnitFileError` with code `unsafe_path` that names the offending `path` and a
|
|
338
|
+
`reason` of type `TSystemdUnitFileUnsafePathReason`, so the owner knows exactly what
|
|
339
|
+
to fix:
|
|
340
|
+
|
|
341
|
+
| `reason` | `path` | Refused because |
|
|
342
|
+
| --- | --- | --- |
|
|
343
|
+
| `unsupported_platform` | unit directory | the process does not run on Linux |
|
|
344
|
+
| `foreign_process` | unit directory | the process does not run as the expected uid |
|
|
345
|
+
| `symlink` | directory component or unit file | it is a symbolic link |
|
|
346
|
+
| `not_directory` | directory component | it is not a directory |
|
|
347
|
+
| `foreign_owner` | directory component or unit file | a directory is owned by neither root nor the expected uid, or the file not by the expected uid |
|
|
348
|
+
| `group_or_world_writable` | directory component | it is group- or world-writable (only a root-owned mode-1777 `/tmp` is admitted) |
|
|
349
|
+
| `unreadable` | unit file | it cannot be opened; the operating-system error is the `cause` |
|
|
350
|
+
| `not_regular_file` | unit file | it is not a regular file |
|
|
351
|
+
| `hard_linked` | unit file | it has more than one hard link |
|
|
352
|
+
| `wrong_mode` | unit file | its mode is not exactly 0644 |
|
|
353
|
+
| `too_large` | unit file | it is larger than 16384 bytes |
|
|
354
|
+
| `replaced` | unit directory or a file being written | it changed identity between two checks during `install()` |
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { SystemdUnitFileUnsafePathError } from '@push.rocks/smartdaemon';
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
await file.inspect();
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error instanceof SystemdUnitFileUnsafePathError) {
|
|
363
|
+
// e.g. 'group_or_world_writable' '/home/user/.local' after `chmod 0775 ~/.local`
|
|
364
|
+
console.error(error.reason, error.path);
|
|
365
|
+
}
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
318
370
|
For a boot prerequisite that must finish before dependent services start, supply
|
|
319
371
|
`startup: { type: 'oneshot', remainAfterExit: true, timeoutStartSeconds: 120 }`.
|
|
320
372
|
Systemd waits for successful process exit; `RemainAfterExit` keeps the completed
|
|
321
373
|
unit active. The start timeout is an integer from 1 to 600 seconds. Oneshot
|
|
322
374
|
services reject `restart: 'always'`; the default exec definition remains unchanged.
|
|
323
375
|
|
|
376
|
+
A long-running service that reports its own readiness uses
|
|
377
|
+
`startup: { type: 'notify', notifyAccess: 'all', timeoutStartSeconds: 120 }`, rendered as
|
|
378
|
+
`Type=notify`, `NotifyAccess=` and `TimeoutStartSec=`. Systemd counts the start complete when a
|
|
379
|
+
process admitted by `notifyAccess` sends `READY=1`: `'main'` admits only the main process,
|
|
380
|
+
`'exec'` also the processes of the unit's `Exec*=` commands, `'all'` every process in the unit's
|
|
381
|
+
cgroup, as a ready notification from a child of the main process needs. `NotifyAccess=none` is
|
|
382
|
+
not offered because it discards every notification. Notify services may use `restart: 'always'`.
|
|
383
|
+
|
|
384
|
+
These optional settings render one directive each, after `TimeoutStopSec=` in this order, and
|
|
385
|
+
nothing when omitted, so existing definitions keep their exact bytes:
|
|
386
|
+
|
|
387
|
+
| Option | Directive | Accepted values |
|
|
388
|
+
| --- | --- | --- |
|
|
389
|
+
| `restartSeconds` | `RestartSec=` (after `Restart=`) | integer 1–600; refused with `restart: 'no'` |
|
|
390
|
+
| `delegate` | `Delegate=` | boolean |
|
|
391
|
+
| `limitNoFile` | `LimitNOFILE=` (soft and hard) | positive integer or `'infinity'` |
|
|
392
|
+
| `tasksMax` | `TasksMax=` | positive integer or `'infinity'` |
|
|
393
|
+
| `oomScoreAdjust` | `OOMScoreAdjust=` | integer −1000–1000 |
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const containerd = new SystemdServiceDefinition({
|
|
397
|
+
unitName: 'pallet-containerd.service',
|
|
398
|
+
description: 'Pallet containerd',
|
|
399
|
+
executable: '/opt/pallet/current/pallet-control',
|
|
400
|
+
args: ['containerd-serve'],
|
|
401
|
+
workingDirectory: '/',
|
|
402
|
+
user: 'root',
|
|
403
|
+
group: 'root',
|
|
404
|
+
restart: 'always',
|
|
405
|
+
restartSeconds: 2,
|
|
406
|
+
killMode: 'process',
|
|
407
|
+
timeoutStopSeconds: 60,
|
|
408
|
+
startup: { type: 'notify', notifyAccess: 'all', timeoutStartSeconds: 120 },
|
|
409
|
+
dependencies: {
|
|
410
|
+
defaultDependencies: true,
|
|
411
|
+
after: ['pallet-guard.service'],
|
|
412
|
+
before: ['pallet-node.service'],
|
|
413
|
+
requires: ['pallet-guard.service'],
|
|
414
|
+
wants: [],
|
|
415
|
+
conflicts: [],
|
|
416
|
+
},
|
|
417
|
+
delegate: true,
|
|
418
|
+
limitNoFile: 'infinity',
|
|
419
|
+
tasksMax: 'infinity',
|
|
420
|
+
oomScoreAdjust: -999,
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Systemd applies these within what the manager itself may grant. Under systemd 255 in an
|
|
425
|
+
unprivileged container, this unit started with the manager's own open-file hard limit for
|
|
426
|
+
`LimitNOFILE=infinity` and without the lowered OOM score, and still reported `active`.
|
|
427
|
+
`inspectServiceSettings()` reports the configured values, not the running process's limits.
|
|
428
|
+
|
|
324
429
|
Optional `dependencies` describes the complete literal runtime relationships:
|
|
325
430
|
|
|
326
431
|
```typescript
|
|
@@ -407,7 +512,8 @@ as `<unit>.previous-<sha256>`, flushes them before atomic replacement, reloads
|
|
|
407
512
|
the manager and checks its selected fragment and final bytes. A reload failure
|
|
408
513
|
leaves the new file and retained previous definition in place; recovery requires
|
|
409
514
|
inspection, never an automatic rollback. Filesystem and definition errors use
|
|
410
|
-
`SystemdUnitFileError` with a local code
|
|
515
|
+
`SystemdUnitFileError` with a local code; every `unsafe_path` refusal is a
|
|
516
|
+
`SystemdUnitFileUnsafePathError` naming the offending `path` and its `reason`. Trusted qualification code can set
|
|
411
517
|
`unitDirectory`, `expectedUid` and the inherited `SystemdUnit` command options;
|
|
412
518
|
user scope requires `expectedUid` to match the caller's actual uid. The manager's
|
|
413
519
|
selected fragment must match the installed file after reload, so an unrecognized
|
|
@@ -417,7 +523,9 @@ With `killMode: 'mixed'`, the main process owns graceful child shutdown and must
|
|
|
417
523
|
remain alive until its children have joined. Systemd sends SIGTERM to the main
|
|
418
524
|
process first and can send SIGKILL to the whole cgroup when the main process exits
|
|
419
525
|
or the stop deadline expires. `control-group` sends the initial signal to the
|
|
420
|
-
whole group.
|
|
526
|
+
whole group. `process` signals only the main process, on stop and on a stop timeout; every
|
|
527
|
+
other process in the unit survives, so the main process must own their shutdown or their
|
|
528
|
+
deliberate retention (as with `Delegate=yes` workloads). A successful systemd start still requires application readiness
|
|
421
529
|
verification, and a completed stop alone does not prove database drain.
|
|
422
530
|
|
|
423
531
|
### SmartDaemonService Class
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as plugins from './smartdaemon.plugins.js';
|
|
2
|
-
import { renderServiceRelations, type
|
|
2
|
+
import { renderServiceRelations, type TSystemdServiceStartup, type ISystemdServiceDependencies,
|
|
3
3
|
type ISystemdServiceInstallTargets } from './systemd.definition.js';
|
|
4
|
-
export type { ISystemdServiceStartup,
|
|
4
|
+
export type { ISystemdServiceStartup, ISystemdServiceNotifyStartup, TSystemdServiceStartup,
|
|
5
|
+
ISystemdServiceDependencies, ISystemdServiceInstallTargets } from './systemd.definition.js';
|
|
5
6
|
|
|
6
7
|
interface ISystemdServiceDefinitionBaseOptions {
|
|
7
8
|
unitName: string;
|
|
@@ -10,11 +11,21 @@ interface ISystemdServiceDefinitionBaseOptions {
|
|
|
10
11
|
args: readonly string[];
|
|
11
12
|
workingDirectory: string;
|
|
12
13
|
restart: 'no' | 'on-failure' | 'always';
|
|
13
|
-
killMode: 'mixed' | 'control-group';
|
|
14
|
+
killMode: 'mixed' | 'control-group' | 'process';
|
|
14
15
|
timeoutStopSeconds: number;
|
|
15
|
-
startup?:
|
|
16
|
+
startup?: TSystemdServiceStartup;
|
|
16
17
|
dependencies?: ISystemdServiceDependencies;
|
|
17
18
|
install?: ISystemdServiceInstallTargets;
|
|
19
|
+
/** Integer seconds from 1 to 600 before a restart (`RestartSec=`). Refused with `restart: 'no'`. */
|
|
20
|
+
restartSeconds?: number;
|
|
21
|
+
/** Leaves the cgroups of the processes this service starts to them (`Delegate=`). */
|
|
22
|
+
delegate?: boolean;
|
|
23
|
+
/** Soft and hard open-file limit (`LimitNOFILE=`): a positive integer or `'infinity'`. */
|
|
24
|
+
limitNoFile?: number | 'infinity';
|
|
25
|
+
/** Task limit of the unit's cgroup (`TasksMax=`): a positive integer or `'infinity'`. */
|
|
26
|
+
tasksMax?: number | 'infinity';
|
|
27
|
+
/** Integer from -1000 to 1000 (`OOMScoreAdjust=`). */
|
|
28
|
+
oomScoreAdjust?: number;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export type ISystemdServiceDefinitionOptions = ISystemdServiceDefinitionBaseOptions & (
|
|
@@ -67,8 +78,10 @@ export class SystemdServiceDefinition {
|
|
|
67
78
|
'timeoutStopSeconds',
|
|
68
79
|
...(scope === 'system' ? ['user', 'group'] : []),
|
|
69
80
|
];
|
|
81
|
+
const optionalKeys = ['scope', 'startup', 'dependencies', 'install', 'restartSeconds', 'delegate',
|
|
82
|
+
'limitNoFile', 'tasksMax', 'oomScoreAdjust'];
|
|
70
83
|
requireValue(
|
|
71
|
-
Reflect.ownKeys(options).every(key => typeof key === 'string' && [...keys,
|
|
84
|
+
Reflect.ownKeys(options).every(key => typeof key === 'string' && [...keys, ...optionalKeys].includes(key)
|
|
72
85
|
&& Object.getOwnPropertyDescriptor(options, key)?.enumerable && 'value' in Object.getOwnPropertyDescriptor(options, key)!) && keys.every((key) => {
|
|
73
86
|
const descriptor = Object.getOwnPropertyDescriptor(options, key);
|
|
74
87
|
return descriptor && 'value' in descriptor;
|
|
@@ -108,10 +121,35 @@ export class SystemdServiceDefinition {
|
|
|
108
121
|
}
|
|
109
122
|
requireValue(
|
|
110
123
|
['no', 'on-failure', 'always'].includes(options.restart) &&
|
|
111
|
-
['mixed', 'control-group'].includes(options.killMode) &&
|
|
124
|
+
['mixed', 'control-group', 'process'].includes(options.killMode) &&
|
|
112
125
|
Number.isSafeInteger(options.timeoutStopSeconds) &&
|
|
113
126
|
options.timeoutStopSeconds >= 1 && options.timeoutStopSeconds <= 600,
|
|
114
127
|
);
|
|
128
|
+
const integer = (value: unknown, minimum: number, maximum: number): value is number =>
|
|
129
|
+
Number.isSafeInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
|
|
130
|
+
const limit = (value: unknown): value is number | 'infinity' =>
|
|
131
|
+
value === 'infinity' || integer(value, 1, Number.MAX_SAFE_INTEGER);
|
|
132
|
+
// Each optional directive is absent unless supplied, so existing definitions keep their bytes.
|
|
133
|
+
const service: string[] = [];
|
|
134
|
+
if (options.restartSeconds !== undefined) {
|
|
135
|
+
requireValue(integer(options.restartSeconds, 1, 600) && options.restart !== 'no');
|
|
136
|
+
}
|
|
137
|
+
if (options.delegate !== undefined) {
|
|
138
|
+
requireValue(typeof options.delegate === 'boolean');
|
|
139
|
+
service.push(`Delegate=${options.delegate ? 'yes' : 'no'}`);
|
|
140
|
+
}
|
|
141
|
+
if (options.limitNoFile !== undefined) {
|
|
142
|
+
requireValue(limit(options.limitNoFile));
|
|
143
|
+
service.push(`LimitNOFILE=${options.limitNoFile}`);
|
|
144
|
+
}
|
|
145
|
+
if (options.tasksMax !== undefined) {
|
|
146
|
+
requireValue(limit(options.tasksMax));
|
|
147
|
+
service.push(`TasksMax=${options.tasksMax}`);
|
|
148
|
+
}
|
|
149
|
+
if (options.oomScoreAdjust !== undefined) {
|
|
150
|
+
requireValue(integer(options.oomScoreAdjust, -1000, 1000));
|
|
151
|
+
service.push(`OOMScoreAdjust=${options.oomScoreAdjust}`);
|
|
152
|
+
}
|
|
115
153
|
// Quoting and percent/dollar escaping follow systemd.syntax and systemd.service.
|
|
116
154
|
const quote = (value: string, command = false) =>
|
|
117
155
|
'"' + value
|
|
@@ -135,12 +173,14 @@ export class SystemdServiceDefinition {
|
|
|
135
173
|
// WorkingDirectory is a raw path value, not an ExecStart word list.
|
|
136
174
|
`WorkingDirectory=${options.workingDirectory.replaceAll('%', '%%')}`,
|
|
137
175
|
`Restart=${options.restart}`,
|
|
176
|
+
...(options.restartSeconds !== undefined ? [`RestartSec=${options.restartSeconds}s`] : []),
|
|
138
177
|
`KillMode=${options.killMode}`,
|
|
139
178
|
'KillSignal=SIGTERM',
|
|
140
179
|
'FinalKillSignal=SIGKILL',
|
|
141
180
|
'SendSIGKILL=yes',
|
|
142
181
|
'SendSIGHUP=no',
|
|
143
182
|
`TimeoutStopSec=${options.timeoutStopSeconds}s`,
|
|
183
|
+
...service,
|
|
144
184
|
'StandardInput=null',
|
|
145
185
|
'StandardOutput=journal',
|
|
146
186
|
'StandardError=journal',
|
|
@@ -56,6 +56,29 @@ export interface ISystemdUnitRelationships {
|
|
|
56
56
|
readonly wantedBy: readonly string[];
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Service settings as the manager loaded them, drop-ins included. Timespans are exact integer
|
|
61
|
+
* microseconds; a limit systemd reports as unbounded is `'infinity'`. Mode strings are systemd's
|
|
62
|
+
* own vocabulary, so a value this package does not render is still reported; `type` is empty for
|
|
63
|
+
* a unit systemd could not load.
|
|
64
|
+
*/
|
|
65
|
+
export interface ISystemdServiceSettings {
|
|
66
|
+
readonly unitName: string;
|
|
67
|
+
readonly type: string;
|
|
68
|
+
readonly notifyAccess: string;
|
|
69
|
+
readonly restart: string;
|
|
70
|
+
readonly restartMicroseconds: number | 'infinity';
|
|
71
|
+
readonly killMode: string;
|
|
72
|
+
readonly timeoutStopMicroseconds: number | 'infinity';
|
|
73
|
+
readonly delegate: boolean;
|
|
74
|
+
/** Hard open-file limit (`LimitNOFILE`). */
|
|
75
|
+
readonly limitNoFile: number | 'infinity';
|
|
76
|
+
/** Soft open-file limit (`LimitNOFILESoft`). */
|
|
77
|
+
readonly limitNoFileSoft: number | 'infinity';
|
|
78
|
+
readonly tasksMax: number | 'infinity';
|
|
79
|
+
readonly oomScoreAdjust: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
59
82
|
export class SystemdUnitError extends Error {
|
|
60
83
|
constructor(public readonly code: 'invalid_options' | 'command_failed' | 'invalid_state') {
|
|
61
84
|
super(`Systemd unit operation failed: ${code}.`);
|
|
@@ -63,6 +86,37 @@ export class SystemdUnitError extends Error {
|
|
|
63
86
|
}
|
|
64
87
|
}
|
|
65
88
|
|
|
89
|
+
// systemd's format_timespan() units, largest first; a year and a month are 365.25 and 30.44 days.
|
|
90
|
+
const timespanUnits = new Map<string, bigint>([
|
|
91
|
+
['y', 31557600000000n], ['month', 2629800000000n], ['w', 604800000000n], ['d', 86400000000n],
|
|
92
|
+
['h', 3600000000n], ['min', 60000000n], ['s', 1000000n], ['ms', 1000n], ['us', 1n],
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
/** Parses `systemctl show` timespans such as `0`, `100ms`, `1min 30s`, `1.500000s` or `infinity`. */
|
|
96
|
+
const parseTimespan = (text: string): number | 'infinity' | undefined => {
|
|
97
|
+
if (text === 'infinity') return 'infinity';
|
|
98
|
+
if (text === '0') return 0;
|
|
99
|
+
const names = [...timespanUnits.keys()];
|
|
100
|
+
let total = 0n;
|
|
101
|
+
let previous = -1;
|
|
102
|
+
for (const part of text.split(' ')) {
|
|
103
|
+
const match = /^(0|[1-9]\d{0,19})(?:\.(\d{1,6}))?([a-z]+)$/.exec(part);
|
|
104
|
+
const index = match ? names.indexOf(match[3]) : -1;
|
|
105
|
+
if (!match || index <= previous) return undefined;
|
|
106
|
+
previous = index;
|
|
107
|
+
const scale = timespanUnits.get(match[3])!;
|
|
108
|
+
const fraction = match[2] === undefined ? 0n : BigInt(match[2].padEnd(6, '0')) * scale;
|
|
109
|
+
if (fraction % 1000000n !== 0n) return undefined;
|
|
110
|
+
total += BigInt(match[1]) * scale + fraction / 1000000n;
|
|
111
|
+
}
|
|
112
|
+
return total > 0n && total <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(total) : undefined;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** `infinity` or a decimal unsigned integer within the safe integer range. */
|
|
116
|
+
const parseLimit = (text: string): number | 'infinity' | undefined =>
|
|
117
|
+
text === 'infinity' ? 'infinity'
|
|
118
|
+
: /^(0|[1-9]\d*)$/.test(text) && Number.isSafeInteger(Number(text)) ? Number(text) : undefined;
|
|
119
|
+
|
|
66
120
|
const properties = [
|
|
67
121
|
'Id',
|
|
68
122
|
'LoadState',
|
|
@@ -217,6 +271,50 @@ export class SystemdUnit {
|
|
|
217
271
|
});
|
|
218
272
|
}
|
|
219
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Reports the loaded service settings that a definition renders, so an owner can verify an
|
|
276
|
+
* installed unit carries them. Reads only; the manager must have reloaded changed files first.
|
|
277
|
+
*/
|
|
278
|
+
public async inspectServiceSettings(): Promise<ISystemdServiceSettings> {
|
|
279
|
+
const keys = ['Id', 'Type', 'NotifyAccess', 'Restart', 'RestartUSec', 'KillMode', 'TimeoutStopUSec',
|
|
280
|
+
'Delegate', 'LimitNOFILE', 'LimitNOFILESoft', 'TasksMax', 'OOMScoreAdjust'];
|
|
281
|
+
const stdout = await this.command(['show', this.unitName, `--property=${keys.join(',')}`, '--all']);
|
|
282
|
+
const fields = new Map<string, string>();
|
|
283
|
+
for (const line of (stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout).split('\n')) {
|
|
284
|
+
const separator = line.indexOf('='), key = line.slice(0, separator);
|
|
285
|
+
if (separator < 1 || !keys.includes(key) || fields.has(key)) throw new SystemdUnitError('invalid_state');
|
|
286
|
+
fields.set(key, line.slice(separator + 1));
|
|
287
|
+
}
|
|
288
|
+
// An older manager omits a property it does not know; the count check refuses that snapshot.
|
|
289
|
+
if (fields.size !== keys.length || fields.get('Id') !== this.unitName) throw new SystemdUnitError('invalid_state');
|
|
290
|
+
const mode = (key: string, empty = false): string => {
|
|
291
|
+
const value = fields.get(key)!;
|
|
292
|
+
if (!(empty && value === '') && !/^[a-z][a-z-]{0,31}$/.test(value)) throw new SystemdUnitError('invalid_state');
|
|
293
|
+
return value;
|
|
294
|
+
};
|
|
295
|
+
const parsed = <T>(key: string, parse: (text: string) => T | undefined): T => {
|
|
296
|
+
const value = parse(fields.get(key)!);
|
|
297
|
+
if (value === undefined) throw new SystemdUnitError('invalid_state');
|
|
298
|
+
return value;
|
|
299
|
+
};
|
|
300
|
+
const oomScoreAdjust = parsed('OOMScoreAdjust', (text) =>
|
|
301
|
+
/^(0|-?[1-9]\d{0,3})$/.test(text) && Math.abs(Number(text)) <= 1000 ? Number(text) : undefined);
|
|
302
|
+
return Object.freeze({
|
|
303
|
+
unitName: this.unitName,
|
|
304
|
+
type: mode('Type', true),
|
|
305
|
+
notifyAccess: mode('NotifyAccess'),
|
|
306
|
+
restart: mode('Restart'),
|
|
307
|
+
restartMicroseconds: parsed('RestartUSec', parseTimespan),
|
|
308
|
+
killMode: mode('KillMode'),
|
|
309
|
+
timeoutStopMicroseconds: parsed('TimeoutStopUSec', parseTimespan),
|
|
310
|
+
delegate: parsed('Delegate', (text) => text === 'yes' ? true : text === 'no' ? false : undefined),
|
|
311
|
+
limitNoFile: parsed('LimitNOFILE', parseLimit),
|
|
312
|
+
limitNoFileSoft: parsed('LimitNOFILESoft', parseLimit),
|
|
313
|
+
tasksMax: parsed('TasksMax', parseLimit),
|
|
314
|
+
oomScoreAdjust,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
220
318
|
/** Reloads unit definitions globally; does not enable, unmask, start or stop a unit. */
|
|
221
319
|
public async reloadConfiguration(): Promise<ISystemdUnitConfigurationState> {
|
|
222
320
|
await this.command(['daemon-reload']);
|