@push.rocks/smartdaemon 2.2.0 → 2.4.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.
@@ -0,0 +1,242 @@
1
+ import * as plugins from './smartdaemon.plugins.js';
2
+ import { SystemdServiceDefinition } from './classes.systemdservicedefinition.js';
3
+ import { SystemdUnit, } from './classes.systemdunit.js';
4
+ export class SystemdUnitFileError extends Error {
5
+ constructor(code) {
6
+ super(`Systemd unit definition installation failed: ${code}.`);
7
+ this.code = code;
8
+ this.name = 'SystemdUnitFileError';
9
+ }
10
+ }
11
+ const requireValue = (value, code) => {
12
+ if (!value)
13
+ throw new SystemdUnitFileError(code);
14
+ };
15
+ const digest = (bytes) => plugins.crypto.createHash('sha256').update(bytes).digest('hex');
16
+ const quiet = (state) => state.mainPid === 0 && state.controlPid === 0 &&
17
+ state.controlGroup === '' && state.job === '' &&
18
+ (state.activeState === 'inactive' && state.subState === 'dead' ||
19
+ state.activeState === 'failed' && state.subState === 'failed');
20
+ const masked = (state) => state.loadState === 'masked' &&
21
+ ['masked', 'masked-runtime'].includes(state.unitFileState);
22
+ const sameFile = (left, right) => left.dev === right.dev && left.ino === right.ino;
23
+ /**
24
+ * Protected code/config installation, inside the caller's cross-process installation ownership scope.
25
+ * Other privileged writers must be serialized by that owner. Never changes enablement or masks.
26
+ */
27
+ export class SystemdUnitFile {
28
+ constructor(options) {
29
+ this.busy = false;
30
+ this.unit = new SystemdUnit(options);
31
+ this.name = options.unitName;
32
+ this.directory = options.unitDirectory ?? '/usr/local/lib/systemd/system';
33
+ this.uid = options.expectedUid ?? 0;
34
+ requireValue(typeof this.directory === 'string' && this.directory !== '/' &&
35
+ plugins.path.isAbsolute(this.directory) &&
36
+ plugins.path.normalize(this.directory) === this.directory &&
37
+ !this.directory.endsWith('/') && !/[\x00-\x1f\x7f]/.test(this.directory) &&
38
+ this.directory.length <= 4000 && Number.isSafeInteger(this.uid) && this.uid >= 0, 'invalid_options');
39
+ this.path = plugins.path.join(this.directory, this.name);
40
+ }
41
+ /** Reads only the exact protected definition; no systemd or filesystem mutation. */
42
+ async inspect() {
43
+ if (!await this.directories(false))
44
+ return null;
45
+ const file = await this.read(this.path);
46
+ return file ? Object.freeze({ path: this.path, sha256: digest(file.bytes) }) : null;
47
+ }
48
+ /**
49
+ * Expected previous digest is mandatory; null admits an absent file. Exact desired bytes are idempotent.
50
+ * Retains changed definitions beside the unit before replacement. A failed reload never rolls code back.
51
+ */
52
+ async install(definition, expectedSha256) {
53
+ requireValue(definition instanceof SystemdServiceDefinition && definition.unitName === this.name &&
54
+ (expectedSha256 === null ||
55
+ typeof expectedSha256 === 'string' && /^[a-f0-9]{64}$/.test(expectedSha256)), 'invalid_options');
56
+ requireValue(!this.busy, 'busy');
57
+ this.busy = true;
58
+ let temporary;
59
+ let temporaryInfo;
60
+ try {
61
+ const before = await this.unit.inspect();
62
+ await this.admit(before);
63
+ await this.directories(true);
64
+ const parent = await plugins.fs.lstat(this.directory);
65
+ const previous = await this.read(this.path);
66
+ const previousSha = previous ? digest(previous.bytes) : null;
67
+ const changed = previousSha !== definition.sha256;
68
+ requireValue(!changed || previousSha === expectedSha256, 'changed');
69
+ if (changed) {
70
+ if (previous) {
71
+ const backup = `${this.path}.previous-${previousSha}`;
72
+ try {
73
+ await this.create(backup, previous.bytes);
74
+ }
75
+ catch (error) {
76
+ if (error.code !== 'EEXIST')
77
+ throw error;
78
+ requireValue(digest((await this.read(backup)).bytes) === previousSha, 'changed');
79
+ }
80
+ await this.flushDirectory(parent);
81
+ }
82
+ temporary = plugins.path.join(this.directory, `.${this.name}.${plugins.crypto.randomUUID()}.tmp`);
83
+ await this.create(temporary, Buffer.from(definition.content));
84
+ temporaryInfo = await plugins.fs.lstat(temporary);
85
+ const current = await this.read(this.path);
86
+ requireValue((current ? digest(current.bytes) : null) === previousSha &&
87
+ (!current || previous && sameFile(current.info, previous.info)), 'changed');
88
+ requireValue(sameFile(parent, await plugins.fs.lstat(this.directory)), 'unsafe_path');
89
+ const rechecked = await this.unit.inspect();
90
+ requireValue(JSON.stringify(rechecked) === JSON.stringify(before), 'changed');
91
+ await this.admit(rechecked);
92
+ await plugins.fs.rename(temporary, this.path);
93
+ temporary = undefined;
94
+ await this.flushDirectory(parent);
95
+ }
96
+ try {
97
+ await this.unit.reloadConfiguration();
98
+ const after = await this.unit.inspect();
99
+ requireValue(quiet(after) &&
100
+ (before.loadState === 'not-found'
101
+ ? after.loadState === 'loaded' && after.unitFileState === 'disabled'
102
+ : after.loadState === before.loadState &&
103
+ after.unitFileState === before.unitFileState), 'changed');
104
+ const configuration = await this.unit.inspectConfiguration();
105
+ requireValue(!configuration.needsReload && configuration.dropInPaths === '' &&
106
+ configuration.fragmentPath === (masked(after) ? '/dev/null' : this.path), 'unsafe_unit');
107
+ const installed = await this.inspect();
108
+ requireValue(installed?.sha256 === definition.sha256, 'changed');
109
+ return Object.freeze({ changed, record: installed, masked: masked(after) });
110
+ }
111
+ catch {
112
+ throw new SystemdUnitFileError('reload_failed');
113
+ }
114
+ }
115
+ catch (error) {
116
+ if (error instanceof SystemdUnitFileError)
117
+ throw error;
118
+ throw new SystemdUnitFileError('write_failed');
119
+ }
120
+ finally {
121
+ try {
122
+ if (temporary && temporaryInfo) {
123
+ const named = await plugins.fs.lstat(temporary);
124
+ requireValue(sameFile(temporaryInfo, named), 'unsafe_path');
125
+ await plugins.fs.unlink(temporary);
126
+ }
127
+ }
128
+ finally {
129
+ this.busy = false;
130
+ }
131
+ }
132
+ }
133
+ async admit(state) {
134
+ requireValue(quiet(state) && (masked(state) ||
135
+ state.loadState === 'loaded' &&
136
+ ['enabled', 'enabled-runtime', 'disabled'].includes(state.unitFileState) ||
137
+ state.loadState === 'not-found' && state.unitFileState === ''), 'unsafe_unit');
138
+ const configuration = await this.unit.inspectConfiguration();
139
+ requireValue(configuration.dropInPaths === '' && configuration.fragmentPath ===
140
+ (masked(state) ? '/dev/null' : state.loadState === 'not-found' ? '' : this.path), 'unsafe_unit');
141
+ }
142
+ async directories(create) {
143
+ requireValue(process.platform === 'linux' && process.getuid?.() === this.uid, 'unsafe_path');
144
+ let directory = '/';
145
+ for (const part of this.directory.split('/').filter(Boolean)) {
146
+ directory = plugins.path.join(directory, part);
147
+ let info;
148
+ try {
149
+ info = await plugins.fs.lstat(directory);
150
+ }
151
+ catch (error) {
152
+ if (error.code !== 'ENOENT')
153
+ throw error;
154
+ if (!create)
155
+ return false;
156
+ await plugins.fs.mkdir(directory, { mode: 0o755 });
157
+ const parent = await plugins.fs.open(plugins.path.dirname(directory), plugins.fsConstants.O_RDONLY | plugins.fsConstants.O_DIRECTORY |
158
+ plugins.fsConstants.O_NOFOLLOW);
159
+ try {
160
+ await parent.sync();
161
+ }
162
+ finally {
163
+ await parent.close();
164
+ }
165
+ info = await plugins.fs.lstat(directory);
166
+ }
167
+ requireValue(info.isDirectory() && !info.isSymbolicLink() && [0, this.uid].includes(info.uid) &&
168
+ ((info.mode & 0o022) === 0 || directory === '/tmp' && info.uid === 0 &&
169
+ (info.mode & 0o7777) === 0o1777), 'unsafe_path');
170
+ }
171
+ return true;
172
+ }
173
+ async read(path) {
174
+ let file;
175
+ try {
176
+ file = await plugins.fs.open(path, plugins.fsConstants.O_RDONLY | plugins.fsConstants.O_NOFOLLOW |
177
+ plugins.fsConstants.O_NONBLOCK);
178
+ }
179
+ catch (error) {
180
+ if (error.code === 'ENOENT')
181
+ return null;
182
+ throw new SystemdUnitFileError('unsafe_path');
183
+ }
184
+ try {
185
+ const info = await file.stat();
186
+ requireValue(info.isFile() && info.uid === this.uid && info.nlink === 1 &&
187
+ (info.mode & 0o7777) === 0o644 && info.size <= 16384, 'unsafe_path');
188
+ const bytes = Buffer.alloc(16385);
189
+ let size = 0;
190
+ for (;;) {
191
+ const result = await file.read(bytes, size, bytes.length - size, size);
192
+ if (result.bytesRead === 0)
193
+ break;
194
+ size += result.bytesRead;
195
+ requireValue(size <= 16384, 'unsafe_path');
196
+ }
197
+ const after = await file.stat(), named = await plugins.fs.lstat(path);
198
+ requireValue(sameFile(info, named) && !named.isSymbolicLink() && after.size === size &&
199
+ info.size === after.size && info.mtimeMs === after.mtimeMs &&
200
+ info.ctimeMs === after.ctimeMs, 'changed');
201
+ return { bytes: bytes.subarray(0, size), info };
202
+ }
203
+ finally {
204
+ await file.close();
205
+ }
206
+ }
207
+ async create(path, bytes) {
208
+ const file = await plugins.fs.open(path, plugins.fsConstants.O_CREAT | plugins.fsConstants.O_EXCL |
209
+ plugins.fsConstants.O_WRONLY | plugins.fsConstants.O_NOFOLLOW, 0o644);
210
+ let created;
211
+ try {
212
+ try {
213
+ created = await file.stat();
214
+ await file.chmod(0o644);
215
+ await file.writeFile(bytes);
216
+ await file.sync();
217
+ }
218
+ finally {
219
+ await file.close();
220
+ }
221
+ }
222
+ catch (error) {
223
+ if (created) {
224
+ requireValue(sameFile(created, await plugins.fs.lstat(path)), 'unsafe_path');
225
+ await plugins.fs.unlink(path);
226
+ }
227
+ throw error;
228
+ }
229
+ }
230
+ async flushDirectory(expected) {
231
+ const directory = await plugins.fs.open(this.directory, plugins.fsConstants.O_RDONLY |
232
+ plugins.fsConstants.O_DIRECTORY | plugins.fsConstants.O_NOFOLLOW);
233
+ try {
234
+ requireValue(sameFile(expected, await directory.stat()), 'unsafe_path');
235
+ await directory.sync();
236
+ }
237
+ finally {
238
+ await directory.close();
239
+ }
240
+ }
241
+ }
242
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zeXN0ZW1kdW5pdGZpbGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnN5c3RlbWR1bml0ZmlsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLDBCQUEwQixDQUFDO0FBQ3BELE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBQ2pGLE9BQU8sRUFHTCxXQUFXLEdBQ1osTUFBTSwwQkFBMEIsQ0FBQztBQVlsQyxNQUFNLE9BQU8sb0JBQXFCLFNBQVEsS0FBSztJQUM3QyxZQUNrQixJQU9HO1FBRW5CLEtBQUssQ0FBQyxnREFBZ0QsSUFBSSxHQUFHLENBQUMsQ0FBQztRQVQvQyxTQUFJLEdBQUosSUFBSSxDQU9EO1FBR25CLElBQUksQ0FBQyxJQUFJLEdBQUcsc0JBQXNCLENBQUM7SUFDckMsQ0FBQztDQUNGO0FBQ0QsTUFBTSxZQUFZLEdBQUcsQ0FBQyxLQUFjLEVBQUUsSUFBa0MsRUFBUSxFQUFFO0lBQ2hGLElBQUksQ0FBQyxLQUFLO1FBQUUsTUFBTSxJQUFJLG9CQUFvQixDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ25ELENBQUMsQ0FBQztBQUNGLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBaUIsRUFBRSxFQUFFLENBQ25DLE9BQU8sQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDbEUsTUFBTSxLQUFLLEdBQUcsQ0FBQyxLQUF3QixFQUFFLEVBQUUsQ0FDekMsS0FBSyxDQUFDLE9BQU8sS0FBSyxDQUFDLElBQUksS0FBSyxDQUFDLFVBQVUsS0FBSyxDQUFDO0lBQzdDLEtBQUssQ0FBQyxZQUFZLEtBQUssRUFBRSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUssRUFBRTtJQUM3QyxDQUFDLEtBQUssQ0FBQyxXQUFXLEtBQUssVUFBVSxJQUFJLEtBQUssQ0FBQyxRQUFRLEtBQUssTUFBTTtRQUM1RCxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsUUFBUSxLQUFLLFFBQVEsQ0FBQyxDQUFDO0FBQ25FLE1BQU0sTUFBTSxHQUFHLENBQUMsS0FBd0IsRUFBRSxFQUFFLENBQzFDLEtBQUssQ0FBQyxTQUFTLEtBQUssUUFBUTtJQUM1QixDQUFDLFFBQVEsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLENBQUM7QUFDN0QsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFrQyxFQUFFLEtBQW1DLEVBQUUsRUFBRSxDQUMzRixJQUFJLENBQUMsR0FBRyxLQUFLLEtBQUssQ0FBQyxHQUFHLElBQUksSUFBSSxDQUFDLEdBQUcsS0FBSyxLQUFLLENBQUMsR0FBRyxDQUFDO0FBRW5EOzs7R0FHRztBQUNILE1BQU0sT0FBTyxlQUFlO0lBUTFCLFlBQVksT0FBZ0M7UUFGcEMsU0FBSSxHQUFHLEtBQUssQ0FBQztRQUduQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksV0FBVyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ3JDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUM3QixJQUFJLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxhQUFhLElBQUksK0JBQStCLENBQUM7UUFDMUUsSUFBSSxDQUFDLEdBQUcsR0FBRyxPQUFPLENBQUMsV0FBVyxJQUFJLENBQUMsQ0FBQztRQUNwQyxZQUFZLENBQ1YsT0FBTyxJQUFJLENBQUMsU0FBUyxLQUFLLFFBQVEsSUFBSSxJQUFJLENBQUMsU0FBUyxLQUFLLEdBQUc7WUFDMUQsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQztZQUN2QyxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssSUFBSSxDQUFDLFNBQVM7WUFDekQsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO1lBQ3hFLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxJQUFJLElBQUksSUFBSSxNQUFNLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsRUFDbEYsaUJBQWlCLENBQ2xCLENBQUM7UUFDRixJQUFJLENBQUMsSUFBSSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRCxvRkFBb0Y7SUFDN0UsS0FBSyxDQUFDLE9BQU87UUFDbEIsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUM7WUFBRSxPQUFPLElBQUksQ0FBQztRQUNoRCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3hDLE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFDdEYsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyxPQUFPLENBQ2xCLFVBQW9DLEVBQ3BDLGNBQTZCO1FBRTdCLFlBQVksQ0FDVixVQUFVLFlBQVksd0JBQXdCLElBQUksVUFBVSxDQUFDLFFBQVEsS0FBSyxJQUFJLENBQUMsSUFBSTtZQUNqRixDQUFDLGNBQWMsS0FBSyxJQUFJO2dCQUN0QixPQUFPLGNBQWMsS0FBSyxRQUFRLElBQUksZ0JBQWdCLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDLEVBQ2hGLGlCQUFpQixDQUNsQixDQUFDO1FBQ0YsWUFBWSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztRQUNqQyxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLFNBQTZCLENBQUM7UUFDbEMsSUFBSSxhQUF3QyxDQUFDO1FBQzdDLElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUN6QyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDekIsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzdCLE1BQU0sTUFBTSxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ3RELE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDNUMsTUFBTSxXQUFXLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7WUFDN0QsTUFBTSxPQUFPLEdBQUcsV0FBVyxLQUFLLFVBQVUsQ0FBQyxNQUFNLENBQUM7WUFDbEQsWUFBWSxDQUFDLENBQUMsT0FBTyxJQUFJLFdBQVcsS0FBSyxjQUFjLEVBQUUsU0FBUyxDQUFDLENBQUM7WUFDcEUsSUFBSSxPQUFPLEVBQUUsQ0FBQztnQkFDWixJQUFJLFFBQVEsRUFBRSxDQUFDO29CQUNiLE1BQU0sTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLElBQUksYUFBYSxXQUFXLEVBQUUsQ0FBQztvQkFDdEQsSUFBSSxDQUFDO3dCQUNILE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO29CQUM1QyxDQUFDO29CQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7d0JBQ2YsSUFBSyxLQUErQixDQUFDLElBQUksS0FBSyxRQUFROzRCQUFFLE1BQU0sS0FBSyxDQUFDO3dCQUNwRSxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFFLENBQUMsS0FBSyxDQUFDLEtBQUssV0FBVyxFQUFFLFNBQVMsQ0FBQyxDQUFDO29CQUNwRixDQUFDO29CQUNELE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDcEMsQ0FBQztnQkFDRCxTQUFTLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQzNCLElBQUksQ0FBQyxTQUFTLEVBQ2QsSUFBSSxJQUFJLENBQUMsSUFBSSxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsVUFBVSxFQUFFLE1BQU0sQ0FDbkQsQ0FBQztnQkFDRixNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7Z0JBQzlELGFBQWEsR0FBRyxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUNsRCxNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUMzQyxZQUFZLENBQ1YsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLFdBQVc7b0JBQ3RELENBQUMsQ0FBQyxPQUFPLElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUNqRSxTQUFTLENBQ1YsQ0FBQztnQkFDRixZQUFZLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxFQUFFLGFBQWEsQ0FBQyxDQUFDO2dCQUN0RixNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7Z0JBQzVDLFlBQVksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxLQUFLLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUM7Z0JBQzlFLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQztnQkFDNUIsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUM5QyxTQUFTLEdBQUcsU0FBUyxDQUFDO2dCQUN0QixNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDcEMsQ0FBQztZQUNELElBQUksQ0FBQztnQkFDSCxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztnQkFDdEMsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUN4QyxZQUFZLENBQ1YsS0FBSyxDQUFDLEtBQUssQ0FBQztvQkFDVixDQUFDLE1BQU0sQ0FBQyxTQUFTLEtBQUssV0FBVzt3QkFDL0IsQ0FBQyxDQUFDLEtBQUssQ0FBQyxTQUFTLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxhQUFhLEtBQUssVUFBVTt3QkFDcEUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxTQUFTLEtBQUssTUFBTSxDQUFDLFNBQVM7NEJBQ3BDLEtBQUssQ0FBQyxhQUFhLEtBQUssTUFBTSxDQUFDLGFBQWEsQ0FBQyxFQUNuRCxTQUFTLENBQ1YsQ0FBQztnQkFDRixNQUFNLGFBQWEsR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztnQkFDN0QsWUFBWSxDQUNWLENBQUMsYUFBYSxDQUFDLFdBQVcsSUFBSSxhQUFhLENBQUMsV0FBVyxLQUFLLEVBQUU7b0JBQzVELGFBQWEsQ0FBQyxZQUFZLEtBQUssQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUMxRSxhQUFhLENBQ2QsQ0FBQztnQkFDRixNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDdkMsWUFBWSxDQUFDLFNBQVMsRUFBRSxNQUFNLEtBQUssVUFBVSxDQUFDLE1BQU0sRUFBRSxTQUFTLENBQUMsQ0FBQztnQkFDakUsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxTQUFVLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDL0UsQ0FBQztZQUFDLE1BQU0sQ0FBQztnQkFDUCxNQUFNLElBQUksb0JBQW9CLENBQUMsZUFBZSxDQUFDLENBQUM7WUFDbEQsQ0FBQztRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxLQUFLLFlBQVksb0JBQW9CO2dCQUFFLE1BQU0sS0FBSyxDQUFDO1lBQ3ZELE1BQU0sSUFBSSxvQkFBb0IsQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUNqRCxDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLENBQUM7Z0JBQ0gsSUFBSSxTQUFTLElBQUksYUFBYSxFQUFFLENBQUM7b0JBQy9CLE1BQU0sS0FBSyxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7b0JBQ2hELFlBQVksQ0FBQyxRQUFRLENBQUMsYUFBYSxFQUFFLEtBQUssQ0FBQyxFQUFFLGFBQWEsQ0FBQyxDQUFDO29CQUM1RCxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUNyQyxDQUFDO1lBQ0gsQ0FBQztvQkFBUyxDQUFDO2dCQUNULElBQUksQ0FBQyxJQUFJLEdBQUcsS0FBSyxDQUFDO1lBQ3BCLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVPLEtBQUssQ0FBQyxLQUFLLENBQUMsS0FBd0I7UUFDMUMsWUFBWSxDQUNWLEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUM7WUFDNUIsS0FBSyxDQUFDLFNBQVMsS0FBSyxRQUFRO2dCQUMxQixDQUFDLFNBQVMsRUFBRSxpQkFBaUIsRUFBRSxVQUFVLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQztZQUMxRSxLQUFLLENBQUMsU0FBUyxLQUFLLFdBQVcsSUFBSSxLQUFLLENBQUMsYUFBYSxLQUFLLEVBQUUsQ0FBQyxFQUNoRSxhQUFhLENBQ2QsQ0FBQztRQUNGLE1BQU0sYUFBYSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1FBQzdELFlBQVksQ0FDVixhQUFhLENBQUMsV0FBVyxLQUFLLEVBQUUsSUFBSSxhQUFhLENBQUMsWUFBWTtZQUMxRCxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsU0FBUyxLQUFLLFdBQVcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQ3BGLGFBQWEsQ0FDZCxDQUFDO0lBQ0osQ0FBQztJQUVPLEtBQUssQ0FBQyxXQUFXLENBQUMsTUFBZTtRQUN2QyxZQUFZLENBQUMsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPLElBQUksT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLEtBQUssSUFBSSxDQUFDLEdBQUcsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUM3RixJQUFJLFNBQVMsR0FBRyxHQUFHLENBQUM7UUFDcEIsS0FBSyxNQUFNLElBQUksSUFBSSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztZQUM3RCxTQUFTLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDO1lBQy9DLElBQUksSUFBbUIsQ0FBQztZQUN4QixJQUFJLENBQUM7Z0JBQ0gsSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDM0MsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2YsSUFBSyxLQUErQixDQUFDLElBQUksS0FBSyxRQUFRO29CQUFFLE1BQU0sS0FBSyxDQUFDO2dCQUNwRSxJQUFJLENBQUMsTUFBTTtvQkFBRSxPQUFPLEtBQUssQ0FBQztnQkFDMUIsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQUUsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztnQkFDbkQsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLElBQUksQ0FDbEMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQy9CLE9BQU8sQ0FBQyxXQUFXLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsV0FBVztvQkFDNUQsT0FBTyxDQUFDLFdBQVcsQ0FBQyxVQUFVLENBQ2pDLENBQUM7Z0JBQ0YsSUFBSSxDQUFDO29CQUNILE1BQU0sTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUN0QixDQUFDO3dCQUFTLENBQUM7b0JBQ1QsTUFBTSxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBQ3ZCLENBQUM7Z0JBQ0QsSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDM0MsQ0FBQztZQUNELFlBQVksQ0FDVixJQUFJLENBQUMsV0FBVyxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxFQUFFLElBQUksQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDO2dCQUM5RSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLElBQUksU0FBUyxLQUFLLE1BQU0sSUFBSSxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUM7b0JBQ2hFLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxNQUFNLENBQUMsS0FBSyxNQUFNLENBQUMsRUFDdEMsYUFBYSxDQUNkLENBQUM7UUFDSixDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRU8sS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFZO1FBQzdCLElBQUksSUFBaUQsQ0FBQztRQUN0RCxJQUFJLENBQUM7WUFDSCxJQUFJLEdBQUcsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLElBQUksQ0FDMUIsSUFBSSxFQUNKLE9BQU8sQ0FBQyxXQUFXLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVTtnQkFDM0QsT0FBTyxDQUFDLFdBQVcsQ0FBQyxVQUFVLENBQ2pDLENBQUM7UUFDSixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUssS0FBK0IsQ0FBQyxJQUFJLEtBQUssUUFBUTtnQkFBRSxPQUFPLElBQUksQ0FBQztZQUNwRSxNQUFNLElBQUksb0JBQW9CLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDaEQsQ0FBQztRQUNELElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQy9CLFlBQVksQ0FDVixJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksSUFBSSxDQUFDLEdBQUcsS0FBSyxJQUFJLENBQUMsR0FBRyxJQUFJLElBQUksQ0FBQyxLQUFLLEtBQUssQ0FBQztnQkFDeEQsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLE1BQU0sQ0FBQyxLQUFLLEtBQUssSUFBSSxJQUFJLENBQUMsSUFBSSxJQUFJLEtBQUssRUFDdEQsYUFBYSxDQUNkLENBQUM7WUFDRixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ2xDLElBQUksSUFBSSxHQUFHLENBQUMsQ0FBQztZQUNiLFNBQVMsQ0FBQztnQkFDUixNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsTUFBTSxHQUFHLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztnQkFDdkUsSUFBSSxNQUFNLENBQUMsU0FBUyxLQUFLLENBQUM7b0JBQUUsTUFBTTtnQkFDbEMsSUFBSSxJQUFJLE1BQU0sQ0FBQyxTQUFTLENBQUM7Z0JBQ3pCLFlBQVksQ0FBQyxJQUFJLElBQUksS0FBSyxFQUFFLGFBQWEsQ0FBQyxDQUFDO1lBQzdDLENBQUM7WUFDRCxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsRUFBRSxLQUFLLEdBQUcsTUFBTSxPQUFPLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN0RSxZQUFZLENBQ1YsUUFBUSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLEVBQUUsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLElBQUk7Z0JBQ3JFLElBQUksQ0FBQyxJQUFJLEtBQUssS0FBSyxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLEtBQUssQ0FBQyxPQUFPO2dCQUMxRCxJQUFJLENBQUMsT0FBTyxLQUFLLEtBQUssQ0FBQyxPQUFPLEVBQ2hDLFNBQVMsQ0FDVixDQUFDO1lBQ0YsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsQ0FBQztRQUNsRCxDQUFDO2dCQUFTLENBQUM7WUFDVCxNQUFNLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNyQixDQUFDO0lBQ0gsQ0FBQztJQUVPLEtBQUssQ0FBQyxNQUFNLENBQUMsSUFBWSxFQUFFLEtBQWlCO1FBQ2xELE1BQU0sSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQ2hDLElBQUksRUFDSixPQUFPLENBQUMsV0FBVyxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDLE1BQU07WUFDdEQsT0FBTyxDQUFDLFdBQVcsQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQy9ELEtBQUssQ0FDTixDQUFDO1FBQ0YsSUFBSSxPQUFrQyxDQUFDO1FBQ3ZDLElBQUksQ0FBQztZQUNILElBQUksQ0FBQztnQkFDSCxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQzVCLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDeEIsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUM1QixNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUNwQixDQUFDO29CQUFTLENBQUM7Z0JBQ1QsTUFBTSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDckIsQ0FBQztRQUNILENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxPQUFPLEVBQUUsQ0FBQztnQkFDWixZQUFZLENBQUMsUUFBUSxDQUFDLE9BQU8sRUFBRSxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQUUsYUFBYSxDQUFDLENBQUM7Z0JBQzdFLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDaEMsQ0FBQztZQUNELE1BQU0sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsY0FBYyxDQUFDLFFBQXVCO1FBQ2xELE1BQU0sU0FBUyxHQUFHLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQ3JDLElBQUksQ0FBQyxTQUFTLEVBQ2QsT0FBTyxDQUFDLFdBQVcsQ0FBQyxRQUFRO1lBQzFCLE9BQU8sQ0FBQyxXQUFXLENBQUMsV0FBVyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUNuRSxDQUFDO1FBQ0YsSUFBSSxDQUFDO1lBQ0gsWUFBWSxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsTUFBTSxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUMsRUFBRSxhQUFhLENBQUMsQ0FBQztZQUN4RSxNQUFNLFNBQVMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN6QixDQUFDO2dCQUFTLENBQUM7WUFDVCxNQUFNLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQixDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=
@@ -1,2 +1,5 @@
1
1
  export * from './smartdaemon.classes.smartdaemon.js';
2
2
  export * from './smartdaemon.classes.service.js';
3
+ export * from './classes.systemdunit.js';
4
+ export * from './classes.systemdservicedefinition.js';
5
+ export * from './classes.systemdunitfile.js';
package/dist_ts/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './smartdaemon.classes.smartdaemon.js';
2
2
  export * from './smartdaemon.classes.service.js';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsa0NBQWtDLENBQUMifQ==
3
+ export * from './classes.systemdunit.js';
4
+ export * from './classes.systemdservicedefinition.js';
5
+ export * from './classes.systemdunitfile.js';
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNDQUFzQyxDQUFDO0FBQ3JELGNBQWMsa0NBQWtDLENBQUM7QUFDakQsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLHVDQUF1QyxDQUFDO0FBQ3RELGNBQWMsOEJBQThCLENBQUMifQ==
@@ -1,5 +1,10 @@
1
1
  import * as path from 'path';
2
- export { path };
2
+ import * as childProcess from 'node:child_process';
3
+ import * as fs from 'node:fs/promises';
4
+ import { constants as fsConstants } from 'node:fs';
5
+ import * as crypto from 'node:crypto';
6
+ export type { Stats } from 'node:fs';
7
+ export { path, childProcess, fs, fsConstants, crypto };
3
8
  import * as lik from '@push.rocks/lik';
4
9
  import * as smartfm from '@push.rocks/smartfm';
5
10
  import * as smartfs from '@push.rocks/smartfs';
@@ -1,6 +1,10 @@
1
1
  // node native scope
2
2
  import * as path from 'path';
3
- export { path };
3
+ import * as childProcess from 'node:child_process';
4
+ import * as fs from 'node:fs/promises';
5
+ import { constants as fsConstants } from 'node:fs';
6
+ import * as crypto from 'node:crypto';
7
+ export { path, childProcess, fs, fsConstants, crypto };
4
8
  // @pushrocks scope
5
9
  import * as lik from '@push.rocks/lik';
6
10
  import * as smartfm from '@push.rocks/smartfm';
@@ -12,4 +16,4 @@ import * as smartshell from '@push.rocks/smartshell';
12
16
  import * as smartsystem from '@push.rocks/smartsystem';
13
17
  export { lik, smartfm, smartfs, smartlog, smartlogDestinationLocal, smartpath, smartshell, smartsystem, };
14
18
  // third party
15
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24ucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0ZGFlbW9uLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBRTdCLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQztBQUVoQixtQkFBbUI7QUFDbkIsT0FBTyxLQUFLLEdBQUcsTUFBTSxpQkFBaUIsQ0FBQztBQUN2QyxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBQy9DLE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxLQUFLLFFBQVEsTUFBTSxzQkFBc0IsQ0FBQztBQUNqRCxPQUFPLEtBQUssd0JBQXdCLE1BQU0sd0NBQXdDLENBQUM7QUFDbkYsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssVUFBVSxNQUFNLHdCQUF3QixDQUFDO0FBQ3JELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFFdkQsT0FBTyxFQUNMLEdBQUcsRUFDSCxPQUFPLEVBQ1AsT0FBTyxFQUNQLFFBQVEsRUFDUix3QkFBd0IsRUFDeEIsU0FBUyxFQUNULFVBQVUsRUFDVixXQUFXLEdBQ1osQ0FBQztBQUVGLGNBQWMifQ==
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYWVtb24ucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0ZGFlbW9uLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxZQUFZLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxLQUFLLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN2QyxPQUFPLEVBQUUsU0FBUyxJQUFJLFdBQVcsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNuRCxPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUd0QyxPQUFPLEVBQUUsSUFBSSxFQUFFLFlBQVksRUFBRSxFQUFFLEVBQUUsV0FBVyxFQUFFLE1BQU0sRUFBRSxDQUFDO0FBRXZELG1CQUFtQjtBQUNuQixPQUFPLEtBQUssR0FBRyxNQUFNLGlCQUFpQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUMvQyxPQUFPLEtBQUssUUFBUSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sS0FBSyx3QkFBd0IsTUFBTSx3Q0FBd0MsQ0FBQztBQUNuRixPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sS0FBSyxVQUFVLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUV2RCxPQUFPLEVBQ0wsR0FBRyxFQUNILE9BQU8sRUFDUCxPQUFPLEVBQ1AsUUFBUSxFQUNSLHdCQUF3QixFQUN4QixTQUFTLEVBQ1QsVUFBVSxFQUNWLFdBQVcsR0FDWixDQUFDO0FBRUYsY0FBYyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartdaemon",
3
- "version": "2.2.0",
3
+ "version": "2.4.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": "^4.4.2",
30
- "@git.zone/tsrun": "^2.0.4",
31
- "@git.zone/tstest": "^3.6.6",
32
- "@types/node": "^25.9.1"
30
+ "@git.zone/tsrun": "^2.0.6",
31
+ "@git.zone/tstest": "^6.0.0",
32
+ "@types/node": "^26.4.1"
33
33
  },
34
34
  "dependencies": {
35
35
  "@push.rocks/lik": "^6.4.1",
package/readme.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @push.rocks/smartdaemon 🚀
2
2
  **Turn your Node.js scripts into production-ready system daemons**
3
3
 
4
+ ## Issue Reporting and Security
5
+
6
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
7
+
4
8
  [![npm version](https://img.shields.io/npm/v/@push.rocks/smartdaemon.svg?style=flat-square)](https://www.npmjs.com/package/@push.rocks/smartdaemon)
5
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
10
 
@@ -147,6 +151,104 @@ class SmartDaemon {
147
151
  }
148
152
  ```
149
153
 
154
+ ### Inspecting and controlling an existing systemd unit
155
+
156
+ `SystemdUnit` works with an exact canonical `.service` name. Construction performs
157
+ no I/O. It does not create or rewrite a unit, enable it, remove a mask, or invoke
158
+ sudo. The caller must already have permission for lifecycle commands.
159
+
160
+ ```typescript
161
+ import { SystemdUnit } from '@push.rocks/smartdaemon';
162
+
163
+ const unit = new SystemdUnit({ unitName: 'smartdaemon_my-api-server.service' });
164
+ const state = await unit.inspect();
165
+ console.log(state.loadState, state.activeState, state.unitFileState);
166
+
167
+ // Application code decides whether stopping or restarting is appropriate.
168
+ // await unit.stop();
169
+ // await unit.start();
170
+ ```
171
+
172
+ Snapshots are immutable and also include `subState`, `mainPid`, `controlPid`,
173
+ `controlGroup`, `killMode`, and `job`. An empty `job` means no systemd job was
174
+ reported. Missing and masked units remain distinct from inactive or disabled
175
+ units. Aliases resolving to a different canonical unit are rejected.
176
+
177
+ `start()` and `stop()` await the systemctl command and return a fresh snapshot.
178
+ They do not establish application readiness or prove database shutdown; callers
179
+ must apply the owning application's lifecycle contract. A command failure or
180
+ timeout rejects with `SystemdUnitError` and a local code, without returning raw
181
+ command diagnostics. The systemd job can continue after a client timeout, so a
182
+ failed command must not be treated as cancelled or successfully completed.
183
+
184
+ Calls use an absolute `systemctlPath` (default `/usr/bin/systemctl`), a clean
185
+ environment, no shell or password prompt, and bounded output. Trusted owning code
186
+ can choose another absolute executable path and `commandTimeoutMs` (default
187
+ 120000, maximum 600000). This timeout bounds the client process only.
188
+
189
+ `inspectConfiguration()` reports the selected `fragmentPath`, opaque `dropInPaths`
190
+ list and `needsReload` flag. `reloadConfiguration()` explicitly reloads the manager's
191
+ unit definitions globally and returns a fresh configuration snapshot. Neither
192
+ operation changes enablement or masks.
193
+
194
+ ### Installing a direct systemd service definition
195
+
196
+ `SystemdServiceDefinition` encodes a literal executable and argument list, with
197
+ explicit user, group, restart policy, kill mode and stop timeout. It uses `Type=exec`,
198
+ journal output and null stdin. It introduces no shell; percent specifiers and
199
+ argument environment expansion are escaped. Paths must be absolute and normalized;
200
+ control characters are rejected. Descriptions and working directories cannot have
201
+ backslashes or surrounding whitespace; executable paths also reject quotes and
202
+ backslashes. Arguments may contain spaces, quotes, backslashes and empty strings.
203
+
204
+ ```typescript
205
+ import { SystemdServiceDefinition, SystemdUnitFile } from '@push.rocks/smartdaemon';
206
+
207
+ const definition = new SystemdServiceDefinition({
208
+ unitName: 'smartdaemon_my-api-server.service',
209
+ description: 'My API server',
210
+ executable: '/opt/my-api/bin/server',
211
+ args: ['serve'],
212
+ workingDirectory: '/opt/my-api',
213
+ user: 'root',
214
+ group: 'root',
215
+ restart: 'no',
216
+ killMode: 'mixed',
217
+ timeoutStopSeconds: 360,
218
+ });
219
+ const file = new SystemdUnitFile({ unitName: definition.unitName });
220
+ // Inside the privileged installer's existing cross-process ownership scope:
221
+ const previous = await file.inspect();
222
+ const installed = await file.install(definition, previous?.sha256 ?? null);
223
+ console.log(installed.changed, installed.masked, installed.record.sha256);
224
+ ```
225
+
226
+ Installation requires Linux, root and a quiescent exact unit with no process,
227
+ cgroup or job. It rejects effective drop-ins and shadowing fragments. Definitions
228
+ default to `/usr/local/lib/systemd/system`, below administrator configuration in
229
+ the load path, so existing persistent/runtime masks remain intact. Existing
230
+ enablement is preserved; an absent unit becomes disabled. It never enables,
231
+ unmasks, starts or stops a service, or invokes sudo.
232
+
233
+ The caller must already serialize privileged installation across processes;
234
+ `SystemdUnitFile` only excludes simultaneous operations on the same instance.
235
+ The expected previous SHA-256 is mandatory (`null` requires an absent file),
236
+ while already matching bytes are idempotent. Installation checks protected
237
+ directories and regular single-link mode-0644 files, retains changed definitions
238
+ as `<unit>.previous-<sha256>`, flushes them before atomic replacement, reloads
239
+ the manager and checks its selected fragment and final bytes. A reload failure
240
+ leaves the new file and retained previous definition in place; recovery requires
241
+ inspection, never an automatic rollback. Filesystem and definition errors use
242
+ `SystemdUnitFileError` with a local code. Trusted qualification code can set
243
+ `unitDirectory`, `expectedUid` and the inherited `SystemdUnit` command options.
244
+
245
+ With `killMode: 'mixed'`, the main process owns graceful child shutdown and must
246
+ remain alive until its children have joined. Systemd sends SIGTERM to the main
247
+ process first and can send SIGKILL to the whole cgroup when the main process exits
248
+ or the stop deadline expires. `control-group` sends the initial signal to the
249
+ whole group. A successful systemd start still requires application readiness
250
+ verification, and a completed stop alone does not prove database drain.
251
+
150
252
  ### SmartDaemonService Class
151
253
 
152
254
  ```typescript
@@ -424,4 +526,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
424
526
 
425
527
  For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
426
528
 
427
- By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
529
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdaemon',
6
- version: '2.2.0',
6
+ version: '2.4.0',
7
7
  description: 'Start scripts as long running daemons and manage them.'
8
8
  }
@@ -0,0 +1,134 @@
1
+ import * as plugins from './smartdaemon.plugins.js';
2
+
3
+ export interface ISystemdServiceDefinitionOptions {
4
+ unitName: string;
5
+ description: string;
6
+ executable: string;
7
+ args: readonly string[];
8
+ workingDirectory: string;
9
+ user: string;
10
+ group: string;
11
+ restart: 'no' | 'on-failure' | 'always';
12
+ killMode: 'mixed' | 'control-group';
13
+ timeoutStopSeconds: number;
14
+ }
15
+
16
+ export class SystemdServiceDefinitionError extends Error {
17
+ constructor() {
18
+ super('Invalid direct systemd service definition.');
19
+ this.name = 'SystemdServiceDefinitionError';
20
+ }
21
+ }
22
+
23
+ /** Literal direct-execution definition; never introduces a shell or environment expansion. */
24
+ export class SystemdServiceDefinition {
25
+ public readonly unitName: string;
26
+ public readonly content: string;
27
+ public readonly sha256: string;
28
+
29
+ constructor(options: ISystemdServiceDefinitionOptions) {
30
+ const requireValue = (value: unknown): void => {
31
+ if (!value) throw new SystemdServiceDefinitionError();
32
+ };
33
+ const text = (value: unknown, maximum = 4096): value is string =>
34
+ typeof value === 'string' && value.length <= maximum && !/[\x00-\x1f\x7f]/.test(value) &&
35
+ !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value);
36
+ const absolute = (value: unknown) =>
37
+ text(value) && plugins.path.isAbsolute(value) &&
38
+ plugins.path.normalize(value) === value;
39
+ const keys = [
40
+ 'unitName',
41
+ 'description',
42
+ 'executable',
43
+ 'args',
44
+ 'workingDirectory',
45
+ 'user',
46
+ 'group',
47
+ 'restart',
48
+ 'killMode',
49
+ 'timeoutStopSeconds',
50
+ ];
51
+ requireValue(
52
+ options && Object.getPrototypeOf(options) === Object.prototype &&
53
+ Reflect.ownKeys(options).length === keys.length && keys.every((key) => {
54
+ const descriptor = Object.getOwnPropertyDescriptor(options, key);
55
+ return descriptor && 'value' in descriptor;
56
+ }),
57
+ );
58
+ requireValue(
59
+ typeof options.unitName === 'string' &&
60
+ /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,230}\.service$/.test(options.unitName),
61
+ );
62
+ requireValue(
63
+ text(options.description, 1024) && options.description.length > 0 &&
64
+ options.description.trim() === options.description && !options.description.includes('\\'),
65
+ );
66
+ requireValue(
67
+ absolute(options.executable) && options.executable !== '/' &&
68
+ !/[\\"']/.test(options.executable) && !options.executable.endsWith('/') &&
69
+ absolute(options.workingDirectory) &&
70
+ options.workingDirectory.trim() === options.workingDirectory &&
71
+ !options.workingDirectory.includes('\\'),
72
+ );
73
+ requireValue(
74
+ Array.isArray(options.args) && Object.getPrototypeOf(options.args) === Array.prototype &&
75
+ options.args.length <= 128 &&
76
+ Reflect.ownKeys(options.args).length === options.args.length + 1 &&
77
+ Array.from(
78
+ { length: options.args.length },
79
+ (_, index) => Object.getOwnPropertyDescriptor(options.args, String(index)),
80
+ )
81
+ .every((descriptor) => descriptor && 'value' in descriptor && text(descriptor.value)),
82
+ );
83
+ requireValue(
84
+ [options.user, options.group].every((value) =>
85
+ typeof value === 'string' && /^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/.test(value)
86
+ ),
87
+ );
88
+ requireValue(
89
+ ['no', 'on-failure', 'always'].includes(options.restart) &&
90
+ ['mixed', 'control-group'].includes(options.killMode) &&
91
+ Number.isSafeInteger(options.timeoutStopSeconds) &&
92
+ options.timeoutStopSeconds >= 1 && options.timeoutStopSeconds <= 600,
93
+ );
94
+ // Quoting and percent/dollar escaping follow systemd.syntax and systemd.service.
95
+ const quote = (value: string, command = false) =>
96
+ '"' + value
97
+ .replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%')
98
+ .replaceAll('$', () => command ? '$$' : '$') +
99
+ '"';
100
+ this.unitName = options.unitName;
101
+ this.content = [
102
+ '[Unit]',
103
+ `Description=${options.description.replaceAll('%', '%%')}`,
104
+ 'After=local-fs.target',
105
+ '',
106
+ '[Service]',
107
+ 'Type=exec',
108
+ `User=${options.user}`,
109
+ `Group=${options.group}`,
110
+ `ExecStart=${
111
+ [quote(options.executable), ...options.args.map((value) => quote(value, true))].join(' ')
112
+ }`,
113
+ // WorkingDirectory is a raw path value, not an ExecStart word list.
114
+ `WorkingDirectory=${options.workingDirectory.replaceAll('%', '%%')}`,
115
+ `Restart=${options.restart}`,
116
+ `KillMode=${options.killMode}`,
117
+ 'KillSignal=SIGTERM',
118
+ 'FinalKillSignal=SIGKILL',
119
+ 'SendSIGKILL=yes',
120
+ 'SendSIGHUP=no',
121
+ `TimeoutStopSec=${options.timeoutStopSeconds}s`,
122
+ 'StandardInput=null',
123
+ 'StandardOutput=journal',
124
+ 'StandardError=journal',
125
+ '',
126
+ '[Install]',
127
+ 'WantedBy=multi-user.target',
128
+ '',
129
+ ].join('\n');
130
+ requireValue(Buffer.byteLength(this.content) <= 16384);
131
+ this.sha256 = plugins.crypto.createHash('sha256').update(this.content).digest('hex');
132
+ Object.freeze(this);
133
+ }
134
+ }