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.
Files changed (4) hide show
  1. package/README.md +297 -25
  2. package/dist/index.d.mts +461 -117
  3. package/dist/index.mjs +1179 -242
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -1,63 +1,335 @@
1
1
  # systemd-ts
2
2
 
3
- `systemd-ts` is a library for declaring, rendering, and managing `systemd`
4
- services and timers from TypeScript.
3
+ `systemd-ts` lets you work with `systemd` from TypeScript.
5
4
 
6
- ## Usage
5
+ It is designed around a few practical jobs:
7
6
 
8
- The example below installs a user-scoped service and timer for a small
9
- nightly backup job. The service does the work, the timer schedules it, and
10
- `Systemd` writes both unit files before enabling and starting the timer.
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 userUnitDir = join(homedir(), ".config/systemd/user");
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: "backup-db",
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: "Write a nightly database backup",
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: "oneshot",
27
- ExecStart: "/usr/bin/bash -lc '/srv/app/bin/backup-db >> /srv/app/log/backup-db.log 2>&1'",
146
+ Type: `oneshot`,
147
+ ExecStart: `/srv/app/bin/backup-db`,
28
148
  },
29
149
  });
30
150
 
31
151
  const timer = new SystemdTimer({
32
- name: "backup-db",
152
+ name: `backup-db`,
33
153
  unit: {
34
- Description: "Run the database backup every night",
154
+ Description: `Run the backup every night`,
35
155
  },
36
156
  timer: {
37
- OnCalendar: "03:15",
157
+ OnCalendar: `03:15`,
38
158
  Persistent: true,
39
159
  Unit: service.filename,
40
160
  },
41
161
  install: {
42
- WantedBy: "timers.target",
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: "user",
48
- unitDir: userUnitDir,
280
+ scope: `user`,
281
+ unitDir: join(homedir(), `.config/systemd/user`),
49
282
  linkUnits: true,
50
283
  });
51
284
 
52
- const installed = await systemd.install(service, timer);
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
- console.log(installed.pathFor(service));
55
- console.log(installed.pathFor(timer));
310
+ const enabled = await systemd.enable(timer);
311
+ if (!enabled.ok) {
312
+ throw enabled.error;
313
+ }
56
314
 
57
- await systemd.enable(timer);
58
- await systemd.start(timer);
315
+ const started = await systemd.start(timer);
316
+ if (!started.ok) {
317
+ throw started.error;
318
+ }
59
319
  ```
60
320
 
61
- That will write `backup-db.service` and `backup-db.timer` into the user's unit
62
- directory, enable the timer under `timers.target`, and ask `systemd --user` to
63
- start waiting for the first scheduled run.
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