systemd-ts 0.2.0 → 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 CHANGED
@@ -1,63 +1,312 @@
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 materializes 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`,
24
123
  },
25
124
  service: {
26
- Type: "oneshot",
27
- ExecStart: "/usr/bin/bash -lc '/srv/app/bin/backup-db >> /srv/app/log/backup-db.log 2>&1'",
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`,
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`,
145
+ service: {
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`,
155
+ },
156
+ timer: {
157
+ OnCalendar: `03:15`,
158
+ Persistent: true,
159
+ Unit: service.filename,
160
+ },
161
+ install: {
162
+ WantedBy: `timers.target`,
163
+ },
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,
35
196
  },
197
+ });
198
+
199
+ const timer = new SystemdTimer({
200
+ name: `backup-db`,
36
201
  timer: {
37
- OnCalendar: "03:15",
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`,
38
248
  Persistent: true,
39
249
  Unit: service.filename,
40
250
  },
41
251
  install: {
42
- WantedBy: "timers.target",
252
+ WantedBy: `timers.target`,
43
253
  },
44
254
  });
45
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";
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
 
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
+
52
305
  const materialized = await systemd.materialize(service, timer);
53
306
  if (!materialized.ok) {
54
307
  throw materialized.error;
55
308
  }
56
309
 
57
- console.log(systemd.pathFor(service));
58
- console.log(systemd.pathFor(timer));
59
- console.log(materialized.value.materialized);
60
-
61
310
  const enabled = await systemd.enable(timer);
62
311
  if (!enabled.ok) {
63
312
  throw enabled.error;
@@ -69,6 +318,18 @@ if (!started.ok) {
69
318
  }
70
319
  ```
71
320
 
72
- That will materialize `backup-db.service` and `backup-db.timer` into the user's
73
- unit directory, enable the timer under `timers.target`, and ask `systemd --user`
74
- to 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
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  declare namespace internal_d_exports {
2
- export { Result, cloneUnitSection, defaultCommandExecutor, defaultUnitDirForScope, fileExists, freezeUnitOptions, normalizeUnitName, parseStartStatus, renderUnitFile, resolveTimerTargetUnit, sendNotify, shellQuote, validateServiceSection };
2
+ export { Result, cloneUnitSection, defaultCommandExecutor, defaultUnitDirForScope, extractCommandOutput, fileExists, freezeUnitOptions, normalizeUnitName, parseStartStatus, renderUnitFile, resolveTimerTargetUnit, sendNotify, validateServiceSection };
3
3
  }
4
4
  type Result<TValue, TError> = {
5
5
  readonly ok: true;
@@ -17,10 +17,10 @@ declare function cloneUnitSection<TSection extends SectionLike | undefined>(sect
17
17
  declare function validateServiceSection(service: SystemdServiceSection): Result<void, InvalidExecDirectiveError>;
18
18
  declare function parseStartStatus(unit: string, output: string): StartStatus;
19
19
  declare function renderUnitFile(sections: ReadonlyArray<readonly [string, SectionLike | undefined]>): Result<string, ExecutableInferenceError>;
20
- declare function shellQuote(value: string): string;
21
- declare function defaultCommandExecutor(command: string, args: readonly string[]): Promise<CommandOutput>;
20
+ declare function defaultCommandExecutor(command: string, args: readonly string[], options?: CommandExecutionOptions): Promise<CommandOutput>;
22
21
  declare function fileExists(path: string): Promise<boolean>;
23
22
  declare function sendNotify(state: `READY=1` | `WATCHDOG=1`, options: NotifyOptions): Promise<void>;
23
+ declare function extractCommandOutput(cause: unknown): CommandOutput | undefined;
24
24
  //#endregion
25
25
  //#region src/main/executable.d.ts
26
26
  /**
@@ -1086,14 +1086,61 @@ interface SystemdOptions {
1086
1086
  readonly scope?: `system` | `user`;
1087
1087
  readonly unitDir?: string;
1088
1088
  }
1089
+ interface SystemctlOptions {
1090
+ readonly executor?: CommandExecutor;
1091
+ readonly scope?: `system` | `user`;
1092
+ }
1093
+ type SystemctlEnablementState = `enabled` | `enabled-runtime` | `linked` | `linked-runtime` | `alias` | `masked` | `masked-runtime` | `static` | `indirect` | `disabled` | `generated` | `transient` | `bad` | `not-found` | (string & {});
1094
+ type SystemctlActiveState = `active` | `reloading` | `inactive` | `failed` | `activating` | `deactivating` | `maintenance` | `refreshing`;
1089
1095
  interface CommandOutput {
1090
1096
  readonly stderr: string;
1091
1097
  readonly stdout: string;
1092
1098
  }
1093
- type CommandExecutor = (command: string, args: readonly string[]) => Promise<CommandOutput>;
1099
+ interface CommandExecutionOptions {
1100
+ readonly env?: Readonly<Record<string, string | undefined>>;
1101
+ }
1102
+ type CommandExecutor = (command: string, args: readonly string[], options?: CommandExecutionOptions) => Promise<CommandOutput>;
1094
1103
  interface LogsOptions {
1095
1104
  readonly lines?: number;
1096
1105
  }
1106
+ interface SystemctlShowOptions {
1107
+ readonly properties?: readonly string[];
1108
+ }
1109
+ interface SystemctlStatusOptions {
1110
+ readonly lines?: number;
1111
+ readonly noPager?: boolean;
1112
+ }
1113
+ interface SystemctlListTimersOptions {
1114
+ readonly all?: boolean;
1115
+ readonly noPager?: boolean;
1116
+ readonly patterns?: readonly string[];
1117
+ }
1118
+ /**
1119
+ * The official `systemctl list-timers --output=json` entry shape exposed by
1120
+ * this library.
1121
+ *
1122
+ * Timestamp fields are returned as raw integer values exactly as reported by
1123
+ * `systemctl`, so callers can compose their own normalization strategy.
1124
+ */
1125
+ interface SystemctlTimerListEntry {
1126
+ readonly next: number;
1127
+ readonly left: number;
1128
+ readonly last: number;
1129
+ readonly passed: number;
1130
+ readonly unit: `${string}.timer`;
1131
+ readonly activates: string;
1132
+ }
1133
+ interface SystemctlIsEnabledOptions {
1134
+ readonly full?: boolean;
1135
+ readonly quiet?: boolean;
1136
+ }
1137
+ interface SystemctlStateQueryOptions {
1138
+ readonly quiet?: boolean;
1139
+ }
1140
+ interface SystemctlIsSystemRunningOptions {
1141
+ readonly wait?: boolean;
1142
+ }
1143
+ type SystemctlSystemRunningState = `initializing` | `starting` | `running` | `degraded` | `maintenance` | `stopping` | `offline` | `unknown`;
1097
1144
  interface NotifyOptions {
1098
1145
  readonly executor?: CommandExecutor;
1099
1146
  readonly pid?: number;
@@ -1107,6 +1154,13 @@ interface StartStatus {
1107
1154
  readonly subState: string;
1108
1155
  readonly unit: string;
1109
1156
  }
1157
+ /**
1158
+ * The typed `systemctl show` status projection exposed for service units.
1159
+ *
1160
+ * This currently mirrors the shared start/status snapshot shape used by the
1161
+ * library's service workflows.
1162
+ */
1163
+ interface SystemctlServiceStatus extends StartStatus {}
1110
1164
  interface ExecutableOptions {
1111
1165
  readonly args?: readonly string[];
1112
1166
  readonly modulePath?: string;
@@ -1137,7 +1191,7 @@ type HasMismatchedServiceTimerPairs<TUnits extends readonly SystemdUnit[]> = Has
1137
1191
  type ValidInstallUnits<TUnits extends readonly SystemdUnit[]> = HasMismatchedServiceTimerPairs<TUnits> extends true ? never : TUnits;
1138
1192
  //#endregion
1139
1193
  //#region src/main/errors.d.ts
1140
- type SystemdTsErrorCode = `SYSTEMD_TS_INVALID_EXEC_DIRECTIVE` | `SYSTEMD_TS_NO_UNITS_PROVIDED` | `SYSTEMD_TS_EXECUTABLE_INFERENCE` | `SYSTEMD_TS_UNIT_MATERIALIZATION` | `SYSTEMD_TS_UNIT_ENABLE` | `SYSTEMD_TS_UNIT_START` | `SYSTEMD_TS_UNIT_LOGS` | `SYSTEMD_TS_NOTIFY_SEND`;
1194
+ type SystemdTsErrorCode = `SYSTEMD_TS_INVALID_EXEC_DIRECTIVE` | `SYSTEMD_TS_NO_UNITS_PROVIDED` | `SYSTEMD_TS_EXECUTABLE_INFERENCE` | `SYSTEMD_TS_UNIT_MATERIALIZATION` | `SYSTEMD_TS_UNIT_ENABLE` | `SYSTEMD_TS_UNIT_START` | `SYSTEMD_TS_UNIT_LOGS` | `SYSTEMD_TS_SYSTEMCTL` | `SYSTEMD_TS_NOTIFY_SEND`;
1141
1195
  interface SystemdTsErrorOptions {
1142
1196
  readonly cause?: unknown;
1143
1197
  }
@@ -1173,6 +1227,11 @@ interface NotifySendErrorOptions extends SystemdCommandErrorOptions {
1173
1227
  readonly reason?: NotifySendErrorReason;
1174
1228
  }
1175
1229
  type NotifySendErrorReason = `executor-failed` | `systemd-notify-failed`;
1230
+ interface SystemctlCommandErrorOptions extends SystemdCommandErrorOptions {
1231
+ readonly operation?: string;
1232
+ readonly reason?: SystemctlCommandErrorReason;
1233
+ }
1234
+ type SystemctlCommandErrorReason = `executor-failed` | `invalid-json`;
1176
1235
  type SystemdCommandEnvironmentReason = `manager-unavailable` | `missing-command` | `permission-denied`;
1177
1236
  declare class SystemdTsError extends Error {
1178
1237
  readonly code: SystemdTsErrorCode;
@@ -1245,6 +1304,18 @@ declare class NotifySendError extends SystemdTsError {
1245
1304
  readonly stdout: string | undefined;
1246
1305
  constructor(message: string, options?: NotifySendErrorOptions);
1247
1306
  }
1307
+ declare class SystemctlCommandError extends SystemdTsError {
1308
+ readonly args: readonly string[] | undefined;
1309
+ readonly command: string | undefined;
1310
+ readonly environmentReason: SystemdCommandEnvironmentReason | undefined;
1311
+ readonly exitCode: number | undefined;
1312
+ readonly operation: string | undefined;
1313
+ readonly reason: SystemctlCommandErrorReason | undefined;
1314
+ readonly stage: string | undefined;
1315
+ readonly stderr: string | undefined;
1316
+ readonly stdout: string | undefined;
1317
+ constructor(message: string, options?: SystemctlCommandErrorOptions);
1318
+ }
1248
1319
  //#endregion
1249
1320
  //#region src/main/notify.d.ts
1250
1321
  /**
@@ -1278,7 +1349,138 @@ declare const notify: {
1278
1349
  watchdog(options?: NotifyOptions): Promise<Result<void, NotifySendError>>;
1279
1350
  };
1280
1351
  //#endregion
1352
+ //#region src/main/systemctl.d.ts
1353
+ /**
1354
+ * A TypeScript-first client for invoking `systemctl` with structured argument
1355
+ * vectors instead of shell-composed command strings.
1356
+ *
1357
+ * This lower-level primitive is useful both on its own and as the subprocess
1358
+ * transport underneath higher-level abstractions such as {@link Systemd}.
1359
+ */
1360
+ declare class Systemctl {
1361
+ /** Command executor used for `systemctl` subprocesses. */
1362
+ readonly executor: CommandExecutor;
1363
+ /** The target manager scope, either `system` or `user`. */
1364
+ readonly scope: `system` | `user`;
1365
+ constructor(options?: SystemctlOptions);
1366
+ /** Reloads the targeted systemd manager. */
1367
+ daemonReload(): Promise<Result<CommandOutput, SystemctlCommandError>>;
1368
+ /** Enables a unit by filename. */
1369
+ enable(unit: string): Promise<Result<CommandOutput, SystemctlCommandError>>;
1370
+ /** Disables a unit by filename. */
1371
+ disable(unit: string): Promise<Result<CommandOutput, SystemctlCommandError>>;
1372
+ /** Links a unit file path into the targeted manager. */
1373
+ link(path: string): Promise<Result<CommandOutput, SystemctlCommandError>>;
1374
+ /**
1375
+ * Checks the install-time enablement state of a unit file.
1376
+ *
1377
+ * Source:
1378
+ * - systemd v260.1, `systemctl(1)`
1379
+ */
1380
+ isEnabled(unit: string, options?: SystemctlIsEnabledOptions): Promise<Result<SystemctlEnablementState, SystemctlCommandError>>;
1381
+ /**
1382
+ * Checks the runtime active state of a unit.
1383
+ *
1384
+ * Unless `quiet` is set, this returns the state string printed by
1385
+ * `systemctl is-active`.
1386
+ */
1387
+ isActive(unit: string, options?: SystemctlStateQueryOptions): Promise<Result<SystemctlActiveState, SystemctlCommandError>>;
1388
+ /**
1389
+ * Checks whether a unit is in the failed state.
1390
+ *
1391
+ * Unless `quiet` is set, this returns the state string printed by
1392
+ * `systemctl is-failed`.
1393
+ */
1394
+ isFailed(unit: string, options?: SystemctlStateQueryOptions): Promise<Result<SystemctlActiveState, SystemctlCommandError>>;
1395
+ /**
1396
+ * Queries the overall manager state reported by `systemctl is-system-running`.
1397
+ *
1398
+ * Source:
1399
+ * - systemd v260.1, `systemctl(1)`
1400
+ */
1401
+ isSystemRunning(options?: SystemctlIsSystemRunningOptions): Promise<Result<SystemctlSystemRunningState, SystemctlCommandError>>;
1402
+ /**
1403
+ * Queries `systemctl show` for a unit.
1404
+ *
1405
+ * When `properties` are provided, only those properties are requested.
1406
+ */
1407
+ show(unit: string, options?: SystemctlShowOptions): Promise<Result<CommandOutput, SystemctlCommandError>>;
1408
+ /**
1409
+ * Queries `systemctl show` and parses the result into a key-value object.
1410
+ *
1411
+ * Properties are returned exactly as printed by `systemctl show`.
1412
+ */
1413
+ showProperties(unit: string, options?: SystemctlShowOptions): Promise<Result<Readonly<Record<string, string>>, SystemctlCommandError>>;
1414
+ /**
1415
+ * Queries the standard status-related `show` properties for a unit and
1416
+ * returns a parsed snapshot.
1417
+ */
1418
+ showStatus(unit: string): Promise<Result<StartStatus, SystemctlCommandError>>;
1419
+ /**
1420
+ * Queries the standard service-status `show` properties for a service unit
1421
+ * and returns a parsed snapshot.
1422
+ */
1423
+ showServiceStatus(unit: string): Promise<Result<SystemctlServiceStatus, SystemctlCommandError>>;
1424
+ /**
1425
+ * Queries `systemctl list-timers` and returns the observed JSON payload
1426
+ * shape directly, with raw integer timestamps left intact.
1427
+ */
1428
+ listTimers(options?: SystemctlListTimersOptions): Promise<Result<readonly SystemctlTimerListEntry[], SystemctlCommandError>>;
1429
+ /** Starts a unit by filename. */
1430
+ start(unit: string): Promise<Result<CommandOutput, SystemctlCommandError>>;
1431
+ /** Stops a unit by filename. */
1432
+ stop(unit: string): Promise<Result<CommandOutput, SystemctlCommandError>>;
1433
+ /** Restarts a unit by filename. */
1434
+ restart(unit: string): Promise<Result<CommandOutput, SystemctlCommandError>>;
1435
+ /**
1436
+ * Resets the failed state for matching units, or for all units when no
1437
+ * patterns are provided.
1438
+ */
1439
+ resetFailed(patterns?: readonly string[]): Promise<Result<CommandOutput, SystemctlCommandError>>;
1440
+ /**
1441
+ * Queries `systemctl status` for a unit.
1442
+ *
1443
+ * This is primarily a human-oriented command and is best paired with
1444
+ * `show()` when structured state is needed.
1445
+ */
1446
+ status(unit: string, options?: SystemctlStatusOptions): Promise<Result<CommandOutput, SystemctlCommandError>>;
1447
+ /** Executes a raw `systemctl` invocation with scope-aware arguments. */
1448
+ run(...args: readonly string[]): Promise<Result<CommandOutput, SystemctlCommandError>>;
1449
+ private scopeArgs;
1450
+ }
1451
+ //#endregion
1281
1452
  //#region src/main/systemd.d.ts
1453
+ interface SystemdTsAttachOptions {
1454
+ readonly owner: string;
1455
+ readonly enable?: boolean;
1456
+ readonly start?: boolean;
1457
+ }
1458
+ interface SystemdTsDetachOptions {
1459
+ readonly deleteUnitFiles?: boolean;
1460
+ readonly disable?: boolean;
1461
+ readonly stop?: boolean;
1462
+ }
1463
+ interface SystemdTsReattachOptions extends SystemdTsAttachOptions {
1464
+ readonly disableRemoved?: boolean;
1465
+ readonly prune?: boolean;
1466
+ readonly restartUpdated?: boolean;
1467
+ readonly stopRemoved?: boolean;
1468
+ }
1469
+ interface SystemdTsAttachmentSelector {
1470
+ readonly owner: string;
1471
+ }
1472
+ interface SystemdTsAttachmentResult {
1473
+ readonly added: readonly string[];
1474
+ readonly directory: string;
1475
+ readonly enabled: readonly string[];
1476
+ readonly manifestPath: string;
1477
+ readonly owner: string;
1478
+ readonly removed: readonly string[];
1479
+ readonly restarted: readonly string[];
1480
+ readonly started: readonly string[];
1481
+ readonly unchanged: readonly string[];
1482
+ readonly updated: readonly string[];
1483
+ }
1282
1484
  /**
1283
1485
  * A successful systemd unit materialization.
1284
1486
  *
@@ -1309,6 +1511,10 @@ declare class Systemd {
1309
1511
  readonly linkUnits: boolean;
1310
1512
  /** The target manager scope, either `system` or `user`. */
1311
1513
  readonly scope: `system` | `user`;
1514
+ /** The low-level `systemctl` client for this configured target. */
1515
+ readonly systemctl: Systemctl;
1516
+ /** Higher-level `systemd-ts` upkeep helpers for application-owned units. */
1517
+ readonly ts: SystemdTs;
1312
1518
  /** The directory used when materializing unit files. */
1313
1519
  readonly unitDir: string;
1314
1520
  /**
@@ -1359,6 +1565,35 @@ declare class Systemd {
1359
1565
  private materializeUnits;
1360
1566
  private tryBestEffortCommand;
1361
1567
  }
1568
+ /**
1569
+ * Opinionated `systemd-ts` upkeep helpers for application-owned units.
1570
+ *
1571
+ * This layer is intentionally analogous to a subset of `portablectl`'s
1572
+ * ownership workflow, but it operates on regular materialized units instead of
1573
+ * portable service images.
1574
+ */
1575
+ declare class SystemdTs {
1576
+ private readonly systemd;
1577
+ constructor(systemd: Systemd);
1578
+ attach(units: readonly SystemdUnit[], options: SystemdTsAttachOptions): Promise<Result<SystemdTsAttachmentResult, ExecutableInferenceError | InvalidExecDirectiveError | NoUnitsProvidedError | UnitEnableError | UnitMaterializationError | UnitStartError>>;
1579
+ detach(selector: SystemdTsAttachmentSelector, options?: SystemdTsDetachOptions): Promise<Result<{
1580
+ readonly deleted: readonly string[];
1581
+ readonly detached: readonly string[];
1582
+ readonly disabled: readonly string[];
1583
+ readonly owner: string;
1584
+ readonly stopped: readonly string[];
1585
+ }, Error>>;
1586
+ reattach(units: readonly SystemdUnit[], options: SystemdTsReattachOptions): Promise<Result<SystemdTsAttachmentResult, ExecutableInferenceError | InvalidExecDirectiveError | NoUnitsProvidedError | UnitEnableError | UnitMaterializationError | UnitStartError | Error>>;
1587
+ private describeUnits;
1588
+ private diffManifest;
1589
+ private manifestDir;
1590
+ private manifestPathFor;
1591
+ private readManifest;
1592
+ private runSystemctl;
1593
+ private removeManagedUnitPath;
1594
+ private scopeArgs;
1595
+ private writeManifest;
1596
+ }
1362
1597
  /**
1363
1598
  * Returns the lazily created default `Systemd` instance.
1364
1599
  *
@@ -1374,4 +1609,4 @@ declare function defaultSystemd(): Systemd;
1374
1609
  */
1375
1610
  declare const Internal: typeof internal_d_exports;
1376
1611
  //#endregion
1377
- export { type AnySystemdService, type AnySystemdTimer, type CommandExecutor, type CommandOutput, Executable, ExecutableInferenceError, type ExecutableOptions, Internal, InvalidExecDirectiveError, type LogsOptions, type MaterializedUnit, NoUnitsProvidedError, type NotifyOptions, NotifySendError, type NotifySendErrorReason, type Result, type ServiceBaseName, type ServiceFilename, type StartStatus, Systemd, type SystemdCommandEnvironmentReason, SystemdMaterialization, type SystemdOptions, SystemdService, type SystemdServiceOptions, SystemdTimer, type SystemdTimerOptions, SystemdTsError, type SystemdUnit, type TimerBaseName, type TimerFilename, type TimerTargetServiceName, type TimerTargetUnit, UnitEnableError, UnitLogsReadError, type UnitLogsReadErrorReason, UnitMaterializationError, type UnitMaterializationErrorReason, UnitStartError, type UnitValue, type ValidInstallUnits, defaultSystemd, defineExecutable, notify };
1612
+ export { type AnySystemdService, type AnySystemdTimer, type CommandExecutionOptions, type CommandExecutor, type CommandOutput, Executable, ExecutableInferenceError, type ExecutableOptions, Internal, InvalidExecDirectiveError, type LogsOptions, type MaterializedUnit, NoUnitsProvidedError, type NotifyOptions, NotifySendError, type NotifySendErrorReason, type Result, type ServiceBaseName, type ServiceFilename, type StartStatus, Systemctl, type SystemctlActiveState, SystemctlCommandError, type SystemctlCommandErrorReason, type SystemctlEnablementState, type SystemctlIsEnabledOptions, type SystemctlIsSystemRunningOptions, type SystemctlListTimersOptions, type SystemctlOptions, type SystemctlServiceStatus, type SystemctlShowOptions, type SystemctlStateQueryOptions, type SystemctlStatusOptions, type SystemctlSystemRunningState, type SystemctlTimerListEntry, Systemd, type SystemdCommandEnvironmentReason, SystemdMaterialization, type SystemdOptions, SystemdService, type SystemdServiceOptions, SystemdTimer, type SystemdTimerOptions, SystemdTs, type SystemdTsAttachOptions, type SystemdTsAttachmentResult, type SystemdTsAttachmentSelector, type SystemdTsDetachOptions, SystemdTsError, type SystemdTsReattachOptions, type SystemdUnit, type TimerBaseName, type TimerFilename, type TimerTargetServiceName, type TimerTargetUnit, UnitEnableError, UnitLogsReadError, type UnitLogsReadErrorReason, UnitMaterializationError, type UnitMaterializationErrorReason, UnitStartError, type UnitValue, type ValidInstallUnits, defaultSystemd, defineExecutable, notify };