systemd-ts 0.1.1 → 0.3.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 +297 -25
- package/dist/index.d.mts +461 -117
- package/dist/index.mjs +1179 -242
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,63 +1,335 @@
|
|
|
1
1
|
# systemd-ts
|
|
2
2
|
|
|
3
|
-
`systemd-ts`
|
|
4
|
-
services and timers from TypeScript.
|
|
3
|
+
`systemd-ts` lets you work with `systemd` from TypeScript.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
It is designed around a few practical jobs:
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
- keep application-owned units defined in `.timer` and `.service`up to date from TypeScript
|
|
8
|
+
- use `systemctl` from TypeScript
|
|
9
|
+
- define a service from TypeScript
|
|
10
|
+
- define a service that runs on a timer from TypeScript
|
|
11
|
+
- keep the code that runs the job close to the code that defines the service and timer
|
|
12
|
+
|
|
13
|
+
The library has two layers:
|
|
14
|
+
|
|
15
|
+
- `systemd.ts.*` is the code-owned upkeep layer for attaching, reattaching, and
|
|
16
|
+
detaching application-managed units.
|
|
17
|
+
- `systemd.*` stays close to raw `systemd` concepts like materializing units and
|
|
18
|
+
starting them directly.
|
|
19
|
+
|
|
20
|
+
That lets the README start from the application code story without giving up a
|
|
21
|
+
lower-level `systemd` surface when you want it.
|
|
22
|
+
|
|
23
|
+
It is also a good place to learn `systemd` itself. The unit directives are
|
|
24
|
+
deeply typed, documented in the TypeScript surface, and maintained against
|
|
25
|
+
versioned upstream manpage material, so the library doubles as a guided way to
|
|
26
|
+
explore what `systemd` can do without starting from loose strings and scattered
|
|
27
|
+
shell examples.
|
|
28
|
+
|
|
29
|
+
## What It Can Do
|
|
30
|
+
|
|
31
|
+
### Keep application-owned units up to date
|
|
32
|
+
|
|
33
|
+
If your application owns a service or timer and wants to keep that unit set up
|
|
34
|
+
to date across reinstalls, updates, or reconfiguration, use `systemd.ts`.
|
|
11
35
|
|
|
12
36
|
```ts
|
|
13
37
|
import { homedir } from "node:os";
|
|
14
38
|
import { join } from "node:path";
|
|
15
39
|
|
|
40
|
+
import backupJob from "./backup-job.ts";
|
|
16
41
|
import { Systemd, SystemdService, SystemdTimer } from "systemd-ts";
|
|
17
42
|
|
|
18
|
-
const
|
|
43
|
+
const systemd = new Systemd({
|
|
44
|
+
scope: `user`,
|
|
45
|
+
unitDir: join(homedir(), `.config/systemd/user`),
|
|
46
|
+
linkUnits: true,
|
|
47
|
+
});
|
|
19
48
|
|
|
20
49
|
const service = new SystemdService({
|
|
21
|
-
name:
|
|
50
|
+
name: `backup-db`,
|
|
51
|
+
service: {
|
|
52
|
+
Type: `oneshot`,
|
|
53
|
+
ExecStart: backupJob,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const timer = new SystemdTimer({
|
|
58
|
+
name: `backup-db`,
|
|
59
|
+
timer: {
|
|
60
|
+
OnCalendar: `daily`,
|
|
61
|
+
Persistent: true,
|
|
62
|
+
Unit: service.filename,
|
|
63
|
+
},
|
|
64
|
+
install: {
|
|
65
|
+
WantedBy: `timers.target`,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const attached = await systemd.ts.reattach([service, timer], {
|
|
70
|
+
owner: `com.example.backup-db`,
|
|
71
|
+
enable: true,
|
|
72
|
+
start: true,
|
|
73
|
+
prune: true,
|
|
74
|
+
});
|
|
75
|
+
if (!attached.ok) {
|
|
76
|
+
throw attached.error;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This is the highest-level upkeep workflow in the library. It is designed for
|
|
81
|
+
desktop apps, self-updating agents, OTA-managed software, and other code-owned
|
|
82
|
+
service setups where the application itself is responsible for the unit set it
|
|
83
|
+
maintains.
|
|
84
|
+
|
|
85
|
+
### Use `systemctl` from TypeScript
|
|
86
|
+
|
|
87
|
+
If you want a typed, argv-first `systemctl` client, use `Systemctl`.
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { Systemctl } from "systemd-ts";
|
|
91
|
+
|
|
92
|
+
const systemctl = new Systemctl({ scope: `user` });
|
|
93
|
+
|
|
94
|
+
const timers = await systemctl.listTimers({ all: true });
|
|
95
|
+
if (!timers.ok) {
|
|
96
|
+
throw timers.error;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const status = await systemctl.showServiceStatus(`backup-db.service`);
|
|
100
|
+
if (!status.ok) {
|
|
101
|
+
throw status.error;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(timers.value);
|
|
105
|
+
console.log(status.value);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This is a good fit when you already know the units you want to inspect or
|
|
109
|
+
control and you want to stay inside TypeScript instead of shelling out from app
|
|
110
|
+
code by hand.
|
|
111
|
+
|
|
112
|
+
### Define a service from TypeScript
|
|
113
|
+
|
|
114
|
+
If you want to describe a service unit in code, use `SystemdService`.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { SystemdService } from "systemd-ts";
|
|
118
|
+
|
|
119
|
+
const service = new SystemdService({
|
|
120
|
+
name: `backup-db`,
|
|
22
121
|
unit: {
|
|
23
|
-
Description:
|
|
122
|
+
Description: `Write a database backup`,
|
|
123
|
+
},
|
|
124
|
+
service: {
|
|
125
|
+
Type: `oneshot`,
|
|
126
|
+
ExecStart: `/srv/app/bin/backup-db`,
|
|
127
|
+
StandardOutput: `append:/srv/app/log/backup-db.log`,
|
|
128
|
+
StandardError: `append:/srv/app/log/backup-db.log`,
|
|
24
129
|
},
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
That service can be rendered into a real unit file, materialized into a unit
|
|
134
|
+
directory, and then managed through `Systemd` or `Systemctl`.
|
|
135
|
+
|
|
136
|
+
### Define a service that runs on a timer
|
|
137
|
+
|
|
138
|
+
If you want a scheduled job, pair `SystemdService` with `SystemdTimer`.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { SystemdService, SystemdTimer } from "systemd-ts";
|
|
142
|
+
|
|
143
|
+
const service = new SystemdService({
|
|
144
|
+
name: `backup-db`,
|
|
25
145
|
service: {
|
|
26
|
-
Type:
|
|
27
|
-
ExecStart:
|
|
146
|
+
Type: `oneshot`,
|
|
147
|
+
ExecStart: `/srv/app/bin/backup-db`,
|
|
28
148
|
},
|
|
29
149
|
});
|
|
30
150
|
|
|
31
151
|
const timer = new SystemdTimer({
|
|
32
|
-
name:
|
|
152
|
+
name: `backup-db`,
|
|
33
153
|
unit: {
|
|
34
|
-
Description:
|
|
154
|
+
Description: `Run the backup every night`,
|
|
35
155
|
},
|
|
36
156
|
timer: {
|
|
37
|
-
OnCalendar:
|
|
157
|
+
OnCalendar: `03:15`,
|
|
38
158
|
Persistent: true,
|
|
39
159
|
Unit: service.filename,
|
|
40
160
|
},
|
|
41
161
|
install: {
|
|
42
|
-
WantedBy:
|
|
162
|
+
WantedBy: `timers.target`,
|
|
43
163
|
},
|
|
44
164
|
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
This gives you a real `.service` plus a real `.timer`, both defined from the
|
|
168
|
+
same TypeScript module.
|
|
169
|
+
|
|
170
|
+
### Keep the executable close to the unit definitions
|
|
171
|
+
|
|
172
|
+
For many projects, the nicest workflow is to colocate the job entrypoint with
|
|
173
|
+
the service and timer that run it.
|
|
174
|
+
|
|
175
|
+
`defineExecutable()` is designed for that pattern:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { defineExecutable } from "systemd-ts";
|
|
179
|
+
|
|
180
|
+
export default defineExecutable(async () => {
|
|
181
|
+
console.log(`Writing backup...`);
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Then you can use that executable directly in a service definition:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import backupJob from "./backup-job.ts";
|
|
189
|
+
import { SystemdService, SystemdTimer } from "systemd-ts";
|
|
190
|
+
|
|
191
|
+
const service = new SystemdService({
|
|
192
|
+
name: `backup-db`,
|
|
193
|
+
service: {
|
|
194
|
+
Type: `oneshot`,
|
|
195
|
+
ExecStart: backupJob,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const timer = new SystemdTimer({
|
|
200
|
+
name: `backup-db`,
|
|
201
|
+
timer: {
|
|
202
|
+
OnCalendar: `daily`,
|
|
203
|
+
Unit: service.filename,
|
|
204
|
+
},
|
|
205
|
+
install: {
|
|
206
|
+
WantedBy: `timers.target`,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
That keeps the scheduled code, the service definition, and the timer definition
|
|
212
|
+
near each other instead of scattering them across shell scripts and unit files.
|
|
213
|
+
|
|
214
|
+
## A Typical Flow
|
|
215
|
+
|
|
216
|
+
Most applications use the library in a flow like this:
|
|
217
|
+
|
|
218
|
+
1. Define a `SystemdService`
|
|
219
|
+
2. Optionally define a matching `SystemdTimer`
|
|
220
|
+
3. Reattach the desired unit set through `systemd.ts`
|
|
221
|
+
4. Inspect or control units directly through `Systemctl` or `Systemd` when needed
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { homedir } from "node:os";
|
|
225
|
+
import { join } from "node:path";
|
|
226
|
+
|
|
227
|
+
import backupJob from "./backup-job.ts";
|
|
228
|
+
import { Systemd, SystemdService, SystemdTimer } from "systemd-ts";
|
|
229
|
+
|
|
230
|
+
const systemd = new Systemd({
|
|
231
|
+
scope: `user`,
|
|
232
|
+
unitDir: join(homedir(), `.config/systemd/user`),
|
|
233
|
+
linkUnits: true,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const service = new SystemdService({
|
|
237
|
+
name: `backup-db`,
|
|
238
|
+
service: {
|
|
239
|
+
Type: `oneshot`,
|
|
240
|
+
ExecStart: backupJob,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const timer = new SystemdTimer({
|
|
245
|
+
name: `backup-db`,
|
|
246
|
+
timer: {
|
|
247
|
+
OnCalendar: `daily`,
|
|
248
|
+
Persistent: true,
|
|
249
|
+
Unit: service.filename,
|
|
250
|
+
},
|
|
251
|
+
install: {
|
|
252
|
+
WantedBy: `timers.target`,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const reattached = await systemd.ts.reattach([service, timer], {
|
|
257
|
+
owner: `com.example.backup-db`,
|
|
258
|
+
enable: true,
|
|
259
|
+
start: true,
|
|
260
|
+
prune: true,
|
|
261
|
+
});
|
|
262
|
+
if (!reattached.ok) {
|
|
263
|
+
throw reattached.error;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Using The `Systemd` API Directly
|
|
268
|
+
|
|
269
|
+
If you want to stay closer to raw `systemd` operations, use the lower-level
|
|
270
|
+
`Systemd` methods directly:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import { homedir } from "node:os";
|
|
274
|
+
import { join } from "node:path";
|
|
275
|
+
|
|
276
|
+
import backupJob from "./backup-job.ts";
|
|
277
|
+
import { Systemd, SystemdService, SystemdTimer } from "systemd-ts";
|
|
45
278
|
|
|
46
279
|
const systemd = new Systemd({
|
|
47
|
-
scope:
|
|
48
|
-
unitDir:
|
|
280
|
+
scope: `user`,
|
|
281
|
+
unitDir: join(homedir(), `.config/systemd/user`),
|
|
49
282
|
linkUnits: true,
|
|
50
283
|
});
|
|
51
284
|
|
|
52
|
-
const
|
|
285
|
+
const service = new SystemdService({
|
|
286
|
+
name: `backup-db`,
|
|
287
|
+
service: {
|
|
288
|
+
Type: `oneshot`,
|
|
289
|
+
ExecStart: backupJob,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const timer = new SystemdTimer({
|
|
294
|
+
name: `backup-db`,
|
|
295
|
+
timer: {
|
|
296
|
+
OnCalendar: `daily`,
|
|
297
|
+
Persistent: true,
|
|
298
|
+
Unit: service.filename,
|
|
299
|
+
},
|
|
300
|
+
install: {
|
|
301
|
+
WantedBy: `timers.target`,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const materialized = await systemd.materialize(service, timer);
|
|
306
|
+
if (!materialized.ok) {
|
|
307
|
+
throw materialized.error;
|
|
308
|
+
}
|
|
53
309
|
|
|
54
|
-
|
|
55
|
-
|
|
310
|
+
const enabled = await systemd.enable(timer);
|
|
311
|
+
if (!enabled.ok) {
|
|
312
|
+
throw enabled.error;
|
|
313
|
+
}
|
|
56
314
|
|
|
57
|
-
await systemd.
|
|
58
|
-
|
|
315
|
+
const started = await systemd.start(timer);
|
|
316
|
+
if (!started.ok) {
|
|
317
|
+
throw started.error;
|
|
318
|
+
}
|
|
59
319
|
```
|
|
60
320
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
321
|
+
This lower-level flow is useful when you want exact control over
|
|
322
|
+
materialization, enablement, or startup behavior without going through the
|
|
323
|
+
code-owned upkeep layer.
|
|
324
|
+
|
|
325
|
+
## Explore Further
|
|
326
|
+
|
|
327
|
+
The README is meant to answer what the library is for.
|
|
328
|
+
|
|
329
|
+
If you want the exact method surface, result types, or lower-level details, the
|
|
330
|
+
next places to look are:
|
|
331
|
+
|
|
332
|
+
- `Systemctl` for direct `systemctl` usage from TypeScript
|
|
333
|
+
- `Systemd` for materialization plus unit lifecycle workflows
|
|
334
|
+
- `SystemdService` and `SystemdTimer` for unit definitions
|
|
335
|
+
- `Executable` and `defineExecutable()` for colocated runnable entrypoints
|