@amalgm/shell 0.1.162 → 0.1.164-main.34182640231
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/PURPOSE.md +163 -17
- package/README.md +74 -11
- package/config/releases/trust/runtime/main.root.json +84 -0
- package/config/releases/trust/runtime/preview.root.json +84 -0
- package/dist/authorization/recovery-host.d.ts +1 -0
- package/dist/authorization/recovery-host.js +2 -3
- package/dist/authorization/recovery-host.js.map +1 -1
- package/dist/boot.js +28 -20
- package/dist/boot.js.map +1 -1
- package/dist/cli-arguments.d.ts +8 -0
- package/dist/cli-arguments.js +47 -0
- package/dist/cli-arguments.js.map +1 -0
- package/dist/cli.d.ts +6 -4
- package/dist/cli.js +91 -73
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +4 -3
- package/dist/compose.js.map +1 -1
- package/dist/content-cache-index.js +2 -2
- package/dist/content-cache-index.js.map +1 -1
- package/dist/detection/runtime.js +2 -2
- package/dist/detection/runtime.js.map +1 -1
- package/dist/development/profile.d.ts +27 -0
- package/dist/development/profile.js +142 -0
- package/dist/development/profile.js.map +1 -0
- package/dist/entity-apply-host.js +2 -2
- package/dist/entity-apply-host.js.map +1 -1
- package/dist/entity-apply-store.d.ts +2 -1
- package/dist/entity-apply-store.js +52 -37
- package/dist/entity-apply-store.js.map +1 -1
- package/dist/files-register-host.d.ts +1 -0
- package/dist/files-register-host.js +1 -0
- package/dist/files-register-host.js.map +1 -1
- package/dist/installation/environment.d.ts +30 -0
- package/dist/installation/environment.js +28 -0
- package/dist/installation/environment.js.map +1 -0
- package/dist/installation/lease.d.ts +6 -0
- package/dist/installation/lease.js +28 -0
- package/dist/installation/lease.js.map +1 -0
- package/dist/installation/ownership.d.ts +30 -0
- package/dist/installation/ownership.js +156 -0
- package/dist/installation/ownership.js.map +1 -0
- package/dist/installation/selection.d.ts +8 -0
- package/dist/installation/selection.js +29 -0
- package/dist/installation/selection.js.map +1 -0
- package/dist/machine-host.d.ts +27 -5
- package/dist/machine-host.js +244 -96
- package/dist/machine-host.js.map +1 -1
- package/dist/main.js +56 -2
- package/dist/main.js.map +1 -1
- package/dist/platform-product-host.d.ts +9 -9
- package/dist/platform-product-host.js +17 -17
- package/dist/platform-product-host.js.map +1 -1
- package/dist/platform-tools.d.ts +4 -3
- package/dist/platform-tools.js +34 -37
- package/dist/platform-tools.js.map +1 -1
- package/dist/process-lifecycle.d.ts +6 -0
- package/dist/process-lifecycle.js +42 -0
- package/dist/process-lifecycle.js.map +1 -0
- package/dist/pty-host.d.ts +1 -1
- package/dist/pty-host.js +70 -21
- package/dist/pty-host.js.map +1 -1
- package/dist/runtime-command-host.d.ts +7 -0
- package/dist/runtime-command-host.js +49 -13
- package/dist/runtime-command-host.js.map +1 -1
- package/dist/service/adapter.d.ts +36 -0
- package/dist/service/adapter.js +24 -0
- package/dist/service/adapter.js.map +1 -0
- package/dist/service/cli.d.ts +8 -0
- package/dist/service/cli.js +237 -0
- package/dist/service/cli.js.map +1 -0
- package/dist/service/control.d.ts +38 -0
- package/dist/service/control.js +68 -0
- package/dist/service/control.js.map +1 -0
- package/dist/service/index.d.ts +9 -0
- package/dist/service/index.js +10 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/keepalive.d.ts +18 -0
- package/dist/service/keepalive.js +418 -0
- package/dist/service/keepalive.js.map +1 -0
- package/dist/service/launchd.d.ts +9 -0
- package/dist/service/launchd.js +105 -0
- package/dist/service/launchd.js.map +1 -0
- package/dist/service/owner.d.ts +4 -0
- package/dist/service/owner.js +14 -0
- package/dist/service/owner.js.map +1 -0
- package/dist/service/portable.d.ts +10 -0
- package/dist/service/portable.js +32 -0
- package/dist/service/portable.js.map +1 -0
- package/dist/service/recovery.d.ts +27 -0
- package/dist/service/recovery.js +58 -0
- package/dist/service/recovery.js.map +1 -0
- package/dist/service/registration.d.ts +82 -0
- package/dist/service/registration.js +114 -0
- package/dist/service/registration.js.map +1 -0
- package/dist/service/systemd-unit.d.ts +7 -0
- package/dist/service/systemd-unit.js +43 -0
- package/dist/service/systemd-unit.js.map +1 -0
- package/dist/service/systemd.d.ts +5 -0
- package/dist/service/systemd.js +72 -0
- package/dist/service/systemd.js.map +1 -0
- package/dist/service/windows-action.d.ts +9 -0
- package/dist/service/windows-action.js +55 -0
- package/dist/service/windows-action.js.map +1 -0
- package/dist/service/windows-task.d.ts +9 -0
- package/dist/service/windows-task.js +46 -0
- package/dist/service/windows-task.js.map +1 -0
- package/dist/service/windows.d.ts +2 -0
- package/dist/service/windows.js +65 -0
- package/dist/service/windows.js.map +1 -0
- package/dist/sqlite.d.ts +4 -0
- package/dist/sqlite.js +16 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/updates/artifacts.d.ts +40 -0
- package/dist/updates/artifacts.js +299 -0
- package/dist/updates/artifacts.js.map +1 -0
- package/dist/updates/bootstrap.d.ts +48 -0
- package/dist/updates/bootstrap.js +384 -0
- package/dist/updates/bootstrap.js.map +1 -0
- package/dist/updates/controller.d.ts +66 -0
- package/dist/updates/controller.js +322 -0
- package/dist/updates/controller.js.map +1 -0
- package/dist/updates/isolated-command-lifetime.d.ts +1 -0
- package/dist/updates/isolated-command-lifetime.js +63 -0
- package/dist/updates/isolated-command-lifetime.js.map +1 -0
- package/dist/updates/store.d.ts +41 -0
- package/dist/updates/store.js +80 -0
- package/dist/updates/store.js.map +1 -0
- package/dist/updates/trust.d.ts +20 -0
- package/dist/updates/trust.js +138 -0
- package/dist/updates/trust.js.map +1 -0
- package/dist/user-ground-host.d.ts +4 -1
- package/dist/user-ground-host.js +16 -3
- package/dist/user-ground-host.js.map +1 -1
- package/dist/watching/host.js +4 -2
- package/dist/watching/host.js.map +1 -1
- package/dist/workspace-add-staging.js +2 -2
- package/dist/workspace-add-staging.js.map +1 -1
- package/examinations/fixtures/machine-control-plane.js +57 -0
- package/examinations/fixtures/private-wire-authority.js +1065 -0
- package/examinations/native-service.mjs +92 -0
- package/examinations/network-view.mjs +202 -0
- package/examinations/runtime-upgrade.native-test.mjs +198 -0
- package/npm-shrinkwrap.json +2097 -218
- package/package.json +95 -14
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ChildRestartPolicy } from '@amalgm/core/supervision';
|
|
2
|
+
import type { ServiceIdentity } from './registration.js';
|
|
3
|
+
export interface RecoveryState {
|
|
4
|
+
readonly attempts: number;
|
|
5
|
+
readonly maxAttempts: number;
|
|
6
|
+
readonly generation: string | null;
|
|
7
|
+
readonly lastFailure: string | null;
|
|
8
|
+
readonly lastHealthyGeneration: string | null;
|
|
9
|
+
readonly lastHealthyAt: string | null;
|
|
10
|
+
readonly parked: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Only the controller holding the installation lease opens this journal.
|
|
13
|
+
* Charge Core's allowance before the host effect: even death during spawn
|
|
14
|
+
* cannot turn controller replacement into a fresh recovery budget. */
|
|
15
|
+
export declare class RuntimeRecovery {
|
|
16
|
+
readonly policy: ChildRestartPolicy;
|
|
17
|
+
private readonly database;
|
|
18
|
+
readonly maxAttempts: number;
|
|
19
|
+
constructor(identity: ServiceIdentity, policy?: ChildRestartPolicy);
|
|
20
|
+
get state(): RecoveryState;
|
|
21
|
+
get delayMs(): number;
|
|
22
|
+
begin(generation: string): void;
|
|
23
|
+
healthy(generation: string): void;
|
|
24
|
+
exited(generation: string, failure: string | null): void;
|
|
25
|
+
retry(): void;
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { CHILD_RESTART_POLICY } from '@amalgm/core/supervision';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { openFileDatabase } from '../sqlite.js';
|
|
4
|
+
/** Only the controller holding the installation lease opens this journal.
|
|
5
|
+
* Charge Core's allowance before the host effect: even death during spawn
|
|
6
|
+
* cannot turn controller replacement into a fresh recovery budget. */
|
|
7
|
+
export class RuntimeRecovery {
|
|
8
|
+
policy;
|
|
9
|
+
database;
|
|
10
|
+
maxAttempts;
|
|
11
|
+
constructor(identity, policy = CHILD_RESTART_POLICY) {
|
|
12
|
+
this.policy = policy;
|
|
13
|
+
this.maxAttempts = policy.maxRestarts + 1;
|
|
14
|
+
this.database = openFileDatabase(join(identity.serviceDir, 'recovery.sqlite'));
|
|
15
|
+
try {
|
|
16
|
+
this.database.pragma('synchronous = FULL');
|
|
17
|
+
this.database.exec(`CREATE TABLE IF NOT EXISTS recovery (
|
|
18
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
19
|
+
attempts INTEGER NOT NULL CHECK (attempts >= 0),
|
|
20
|
+
generation TEXT, lastFailure TEXT, lastHealthyGeneration TEXT, lastHealthyAt TEXT
|
|
21
|
+
);
|
|
22
|
+
INSERT OR IGNORE INTO recovery (id, attempts) VALUES (1, 0);
|
|
23
|
+
UPDATE recovery SET generation = NULL,
|
|
24
|
+
lastFailure = 'Controller exited before its runtime drained'
|
|
25
|
+
WHERE generation IS NOT NULL;`);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
this.database.close();
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
get state() {
|
|
33
|
+
const row = this.database.prepare('SELECT attempts, generation, lastFailure, lastHealthyGeneration, lastHealthyAt FROM recovery WHERE id = 1').get();
|
|
34
|
+
return { ...row, maxAttempts: this.maxAttempts, parked: row.generation === null && row.attempts >= this.maxAttempts };
|
|
35
|
+
}
|
|
36
|
+
get delayMs() {
|
|
37
|
+
const { attempts } = this.state;
|
|
38
|
+
return attempts === 0 ? 0 : Math.min(this.policy.initialDelayMs * 2 ** (attempts - 1), this.policy.maxDelayMs);
|
|
39
|
+
}
|
|
40
|
+
begin(generation) {
|
|
41
|
+
const result = this.database.prepare('UPDATE recovery SET attempts = attempts + 1, generation = ? WHERE id = 1 AND generation IS NULL AND attempts < ?').run(generation, this.maxAttempts);
|
|
42
|
+
if (result.changes !== 1)
|
|
43
|
+
throw new Error('The runtime recovery allowance is exhausted or a generation is still owned.');
|
|
44
|
+
}
|
|
45
|
+
healthy(generation) {
|
|
46
|
+
this.database.prepare('UPDATE recovery SET attempts = 0, lastFailure = NULL, lastHealthyGeneration = ?, lastHealthyAt = ? WHERE id = 1 AND generation = ?')
|
|
47
|
+
.run(generation, new Date().toISOString(), generation);
|
|
48
|
+
}
|
|
49
|
+
exited(generation, failure) {
|
|
50
|
+
this.database.prepare('UPDATE recovery SET generation = NULL, lastFailure = COALESCE(?, lastFailure) WHERE id = 1 AND generation = ?')
|
|
51
|
+
.run(failure?.slice(0, 1_024) ?? null, generation);
|
|
52
|
+
}
|
|
53
|
+
retry() {
|
|
54
|
+
this.database.prepare('UPDATE recovery SET attempts = 0, lastFailure = NULL WHERE id = 1 AND generation IS NULL AND attempts >= ?').run(this.maxAttempts);
|
|
55
|
+
}
|
|
56
|
+
close() { this.database.close(); }
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/service/recovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAA2B,MAAM,0BAA0B,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAahD;;sEAEsE;AACtE,MAAM,OAAO,eAAe;IAIsB;IAH/B,QAAQ,CAAC;IACjB,WAAW,CAAS;IAE7B,YAAY,QAAyB,EAAW,SAA6B,oBAAoB;QAAjD,WAAM,GAAN,MAAM,CAA2C;QAC/F,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC/E,IAAI,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;YAC3C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;;;;;;;;sCAQa,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YAAC,MAAM,KAAK,CAAC;QAAC,CAAC;IACzD,CAAC;IAED,IAAI,KAAK;QACP,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,2GAA2G,CAAC,CAAC,GAAG,EAAmD,CAAC;QACtM,OAAO,EAAE,GAAG,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;IACxH,CAAC;IAED,IAAI,OAAO;QACT,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QAChC,OAAO,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjH,CAAC;IAED,KAAK,CAAC,UAAkB;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,kHAAkH,CAAC,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3L,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IAC3H,CAAC;IAED,OAAO,CAAC,UAAkB;QACxB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,oIAAoI,CAAC;aACxJ,GAAG,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,UAAU,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,CAAC,UAAkB,EAAE,OAAsB;QAC/C,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,+GAA+G,CAAC;aACnI,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,UAAU,CAAC,CAAC;IACvD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,4GAA4G,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5J,CAAC;IAED,KAAK,KAAW,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;CACzC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type RuntimeBinding } from "@amalgm/core/identity";
|
|
2
|
+
import { type ServiceState } from "@amalgm/core/supervision";
|
|
3
|
+
/** The OS owners. `portable` is "no OS owner: you run the keepalive in the foreground". */
|
|
4
|
+
export type Backend = "launchd" | "systemd" | "windows" | "portable";
|
|
5
|
+
/** What selects one service: one user, one runtime label, one Amalgm root. */
|
|
6
|
+
export interface ServiceSelection {
|
|
7
|
+
readonly user: string;
|
|
8
|
+
readonly runtimeLabel?: string | undefined;
|
|
9
|
+
readonly branch?: string | undefined;
|
|
10
|
+
readonly localSession?: string | undefined;
|
|
11
|
+
readonly home?: string | undefined;
|
|
12
|
+
readonly amalgmRoot?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
export interface ServiceIdentity extends ServiceSelection {
|
|
15
|
+
readonly binding: RuntimeBinding;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly labelScope: string;
|
|
18
|
+
readonly home: string;
|
|
19
|
+
readonly amalgmRoot: string;
|
|
20
|
+
/** `<amalgmRoot>/.amalgm/service/<label>`: registration, keepalive state, logs. */
|
|
21
|
+
readonly serviceDir: string;
|
|
22
|
+
}
|
|
23
|
+
/** The Node and Shell entry the service runs; both are absolute and version-stable. */
|
|
24
|
+
export interface Launcher {
|
|
25
|
+
readonly node: string;
|
|
26
|
+
readonly bin: string;
|
|
27
|
+
readonly packageRoot: string;
|
|
28
|
+
readonly packageVersion: string;
|
|
29
|
+
}
|
|
30
|
+
/** The registration record, shaped as Core's `ServiceState` plus what to run. */
|
|
31
|
+
export interface Registration extends ServiceState {
|
|
32
|
+
readonly binding: RuntimeBinding;
|
|
33
|
+
readonly user: string;
|
|
34
|
+
readonly amalgm_root: string;
|
|
35
|
+
readonly backend: Backend;
|
|
36
|
+
readonly label: string;
|
|
37
|
+
readonly package_root: string;
|
|
38
|
+
readonly package_version: string;
|
|
39
|
+
readonly node: string;
|
|
40
|
+
readonly bin: string;
|
|
41
|
+
readonly runtime_host: "node";
|
|
42
|
+
readonly local_only: false;
|
|
43
|
+
readonly files: Readonly<Record<string, string>>;
|
|
44
|
+
/** Selects the service dir: passed to `amalgm service keepalive`. */
|
|
45
|
+
readonly keepalive_args: readonly string[];
|
|
46
|
+
/** Selects the runtime: passed to `amalgm run` by the keepalive. */
|
|
47
|
+
readonly run_args: readonly string[];
|
|
48
|
+
readonly installed_at: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function serviceIdentity(selection: ServiceSelection): ServiceIdentity;
|
|
51
|
+
/**
|
|
52
|
+
* The launcher that is running right now. A desktop bundle may not own the
|
|
53
|
+
* service: registering the bundled copy is the auto-update loop (npm
|
|
54
|
+
* updates one tree while the OS reboots the frozen bundle forever), and an
|
|
55
|
+
* Electron binary posing as Node is not the Node the unit must name.
|
|
56
|
+
*/
|
|
57
|
+
export declare function currentLauncher(facts?: {
|
|
58
|
+
readonly execPath?: string;
|
|
59
|
+
readonly electron?: boolean;
|
|
60
|
+
}): Launcher;
|
|
61
|
+
/**
|
|
62
|
+
* Which owner keeps the service alive. `auto` asks the platform: launchd on
|
|
63
|
+
* macOS, Task Scheduler on Windows, the user's systemd where it answers,
|
|
64
|
+
* and otherwise portable. An explicit mode must fit the platform.
|
|
65
|
+
*/
|
|
66
|
+
export declare function detectBackend(mode: unknown, facts: {
|
|
67
|
+
readonly platform: string;
|
|
68
|
+
readonly userSystemd: () => boolean;
|
|
69
|
+
}): Backend;
|
|
70
|
+
export declare function registrationFile(identity: ServiceIdentity): string;
|
|
71
|
+
export declare function readRegistration(identity: ServiceIdentity): Registration | null;
|
|
72
|
+
export declare function writeRegistration(identity: ServiceIdentity, registration: Registration): void;
|
|
73
|
+
/**
|
|
74
|
+
* An existing registration already says everything this install would say:
|
|
75
|
+
* same package tree and version, same owner, same arguments, files present.
|
|
76
|
+
* Re-running install then leaves the running service alone.
|
|
77
|
+
*/
|
|
78
|
+
export declare function registrationSatisfies(existing: Registration | null, wanted: {
|
|
79
|
+
readonly launcher: Launcher;
|
|
80
|
+
readonly backend: Backend;
|
|
81
|
+
readonly run_args: readonly string[];
|
|
82
|
+
}): boolean;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* The service drawer: who this service is (its label), where its files live,
|
|
4
|
+
* which launcher installed it, and which OS owner keeps it alive.
|
|
5
|
+
*
|
|
6
|
+
* Core states the law — the label (`serviceLabel`, `serviceLabelScope`), the
|
|
7
|
+
* launcher-artifact paths, the registration shape (`ServiceState`), and the
|
|
8
|
+
* decisions over it (`stateMatchesRequest`, `normalizeMode`,
|
|
9
|
+
* `pathLooksLikeDesktopBundle`). This file only reads the machine and writes
|
|
10
|
+
* one JSON record; it never loads the runtime.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { USER_CONTROL_DIR_NAME, resolveRuntimeBinding, runtimeBindingsMatch, runtimeRootSegments } from "@amalgm/core/identity";
|
|
17
|
+
import { normalizeMode, pathLooksLikeDesktopBundle, serviceLabel, stateMatchesRequest, } from "@amalgm/core/supervision";
|
|
18
|
+
import { atomicJson, readJson } from "../filesystem.js";
|
|
19
|
+
export function serviceIdentity(selection) {
|
|
20
|
+
const user = selection.user.trim().toLowerCase();
|
|
21
|
+
const binding = resolveRuntimeBinding(selection);
|
|
22
|
+
if (!user)
|
|
23
|
+
throw new Error("A service is registered for one user: pass --user EMAIL");
|
|
24
|
+
const home = selection.home ? resolve(selection.home) : homedir();
|
|
25
|
+
const amalgmRoot = selection.amalgmRoot ? resolve(selection.amalgmRoot) : join(home, ...runtimeRootSegments(binding));
|
|
26
|
+
const labelScope = `${binding.runtimeLabel}.${createHash("sha256").update(JSON.stringify([user, binding])).digest("hex").slice(0, 32)}`;
|
|
27
|
+
const label = serviceLabel(labelScope);
|
|
28
|
+
return {
|
|
29
|
+
...selection,
|
|
30
|
+
...binding,
|
|
31
|
+
binding,
|
|
32
|
+
user,
|
|
33
|
+
home,
|
|
34
|
+
amalgmRoot,
|
|
35
|
+
labelScope,
|
|
36
|
+
label,
|
|
37
|
+
serviceDir: join(amalgmRoot, USER_CONTROL_DIR_NAME, "service", label),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const PACKAGE_ROOT = fileURLToPath(new URL("../../", import.meta.url)).replace(/[\\/]+$/, "");
|
|
41
|
+
const MAIN_ENTRY = fileURLToPath(new URL("../main.js", import.meta.url));
|
|
42
|
+
/**
|
|
43
|
+
* The launcher that is running right now. A desktop bundle may not own the
|
|
44
|
+
* service: registering the bundled copy is the auto-update loop (npm
|
|
45
|
+
* updates one tree while the OS reboots the frozen bundle forever), and an
|
|
46
|
+
* Electron binary posing as Node is not the Node the unit must name.
|
|
47
|
+
*/
|
|
48
|
+
export function currentLauncher(facts = {}) {
|
|
49
|
+
const node = facts.execPath ?? process.execPath;
|
|
50
|
+
const electron = facts.electron ?? Boolean(process.versions.electron);
|
|
51
|
+
if (electron || pathLooksLikeDesktopBundle(node) || pathLooksLikeDesktopBundle(MAIN_ENTRY)) {
|
|
52
|
+
throw new Error("The always-on service must be installed from the npm package (npm install -g @amalgm/shell); "
|
|
53
|
+
+ "a desktop bundle cannot own it");
|
|
54
|
+
}
|
|
55
|
+
const packageJson = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
56
|
+
return { node, bin: MAIN_ENTRY, packageRoot: PACKAGE_ROOT, packageVersion: String(packageJson.version) };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Which owner keeps the service alive. `auto` asks the platform: launchd on
|
|
60
|
+
* macOS, Task Scheduler on Windows, the user's systemd where it answers,
|
|
61
|
+
* and otherwise portable. An explicit mode must fit the platform.
|
|
62
|
+
*/
|
|
63
|
+
export function detectBackend(mode, facts) {
|
|
64
|
+
const requested = normalizeMode(mode);
|
|
65
|
+
const native = facts.platform === "darwin" ? "launchd"
|
|
66
|
+
: facts.platform === "win32" ? "windows"
|
|
67
|
+
: facts.userSystemd() ? "systemd"
|
|
68
|
+
: "portable";
|
|
69
|
+
if (requested === "auto")
|
|
70
|
+
return native;
|
|
71
|
+
if (requested === "portable")
|
|
72
|
+
return "portable";
|
|
73
|
+
if ((requested === "launchd") !== (facts.platform === "darwin")
|
|
74
|
+
|| (requested === "windows") !== (facts.platform === "win32")
|
|
75
|
+
|| (requested === "systemd" && native !== "systemd")) {
|
|
76
|
+
throw new Error(`Service mode "${requested}" is not available on this machine (auto would choose ${native})`);
|
|
77
|
+
}
|
|
78
|
+
return requested;
|
|
79
|
+
}
|
|
80
|
+
export function registrationFile(identity) {
|
|
81
|
+
return join(identity.serviceDir, "registration.json");
|
|
82
|
+
}
|
|
83
|
+
export function readRegistration(identity) {
|
|
84
|
+
const record = readJson(registrationFile(identity));
|
|
85
|
+
if (!record)
|
|
86
|
+
return null;
|
|
87
|
+
if (record.label !== identity.label || typeof record.bin !== "string"
|
|
88
|
+
|| record.user !== identity.user || record.amalgm_root !== identity.amalgmRoot
|
|
89
|
+
|| !runtimeBindingsMatch(record.binding, identity.binding)) {
|
|
90
|
+
throw new Error('The service registration belongs to a different installation.');
|
|
91
|
+
}
|
|
92
|
+
return record;
|
|
93
|
+
}
|
|
94
|
+
export function writeRegistration(identity, registration) {
|
|
95
|
+
atomicJson(registrationFile(identity), registration);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* An existing registration already says everything this install would say:
|
|
99
|
+
* same package tree and version, same owner, same arguments, files present.
|
|
100
|
+
* Re-running install then leaves the running service alone.
|
|
101
|
+
*/
|
|
102
|
+
export function registrationSatisfies(existing, wanted) {
|
|
103
|
+
return existing !== null
|
|
104
|
+
&& stateMatchesRequest(existing, { mode: wanted.backend }, {
|
|
105
|
+
packageRoot: wanted.launcher.packageRoot,
|
|
106
|
+
packageVersion: wanted.launcher.packageVersion,
|
|
107
|
+
autoBackend: wanted.backend,
|
|
108
|
+
})
|
|
109
|
+
&& existing.node === wanted.launcher.node
|
|
110
|
+
&& existing.bin === wanted.launcher.bin
|
|
111
|
+
&& JSON.stringify(existing.run_args) === JSON.stringify(wanted.run_args)
|
|
112
|
+
&& Object.values(existing.files).every((file) => existsSync(file));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=registration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.js","sourceRoot":"","sources":["../../src/service/registration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** systemd directives have distinct grammars; this module owns their encoding. */
|
|
2
|
+
export interface SystemdUnitInput {
|
|
3
|
+
readonly command: readonly string[];
|
|
4
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
5
|
+
readonly workingDirectory: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function renderSystemdUnit(input: SystemdUnitInput): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Quoted words use C escapes followed by systemd's percent-specifier expansion. */
|
|
2
|
+
function word(value) {
|
|
3
|
+
if (value.includes('\0'))
|
|
4
|
+
throw new Error('A service argument or environment value cannot contain NUL');
|
|
5
|
+
return `"${value.replace(/[\x01-\x1f\x7f"\\%]/g, (character) => {
|
|
6
|
+
if (character === '%')
|
|
7
|
+
return '%%';
|
|
8
|
+
if (character === '"' || character === '\\')
|
|
9
|
+
return `\\${character}`;
|
|
10
|
+
return `\\x${character.charCodeAt(0).toString(16).padStart(2, '0')}`;
|
|
11
|
+
})}"`;
|
|
12
|
+
}
|
|
13
|
+
export function renderSystemdUnit(input) {
|
|
14
|
+
if (!input.command.length || !input.command[0].startsWith('/'))
|
|
15
|
+
throw new Error('A service requires an absolute executable');
|
|
16
|
+
if (!input.workingDirectory.startsWith('/'))
|
|
17
|
+
throw new Error('A service requires an absolute working directory');
|
|
18
|
+
// systemd trims WorkingDirectory again when serializing its execution context.
|
|
19
|
+
// GNU env changes directory from literal argv, then execs the same owned PID.
|
|
20
|
+
const command = ['/usr/bin/env', '--chdir', input.workingDirectory, '--', ...input.command];
|
|
21
|
+
const env = Object.entries(input.environment).map(([key, value]) => {
|
|
22
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
23
|
+
throw new Error('Invalid service environment name');
|
|
24
|
+
return `Environment=${word(`${key}=${value}`)}`;
|
|
25
|
+
}).join('\n');
|
|
26
|
+
return `[Unit]
|
|
27
|
+
Description=Amalgm always-on runtime
|
|
28
|
+
StartLimitIntervalSec=0
|
|
29
|
+
|
|
30
|
+
[Service]
|
|
31
|
+
Type=exec
|
|
32
|
+
${env}
|
|
33
|
+
ExecStart=:${command.map(word).join(' ')}
|
|
34
|
+
Restart=on-failure
|
|
35
|
+
RestartSec=5
|
|
36
|
+
KillMode=mixed
|
|
37
|
+
TimeoutStopSec=30
|
|
38
|
+
|
|
39
|
+
[Install]
|
|
40
|
+
WantedBy=default.target
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=systemd-unit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemd-unit.js","sourceRoot":"","sources":["../../src/service/systemd-unit.ts"],"names":[],"mappings":"AAOA,oFAAoF;AACpF,SAAS,IAAI,CAAC,KAAa;IACzB,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;IACxG,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,SAAS,EAAE,EAAE;QAC7D,IAAI,SAAS,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,SAAS,KAAK,GAAG,IAAI,SAAS,KAAK,IAAI;YAAE,OAAO,KAAK,SAAS,EAAE,CAAC;QACrE,OAAO,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IACvE,CAAC,CAAC,GAAG,CAAC;AACR,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAuB;IACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC7H,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACjH,+EAA+E;IAC/E,8EAA8E;IAC9E,MAAM,OAAO,GAAG,CAAC,cAAc,EAAE,SAAS,EAAE,KAAK,CAAC,gBAAgB,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5F,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACjE,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAC/F,OAAO,eAAe,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO;;;;;;EAMP,GAAG;aACQ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;;;;;;;;CAQvC,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ServiceOwner } from "./adapter.js";
|
|
2
|
+
export { renderSystemdUnit } from './systemd-unit.js';
|
|
3
|
+
/** Does this login have a user manager to talk to? Containers and bare SSH often do not. */
|
|
4
|
+
export declare function userSystemdAvailable(): boolean;
|
|
5
|
+
export declare const systemdOwner: ServiceOwner;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { stopKeepalive } from "./control.js";
|
|
2
|
+
/**
|
|
3
|
+
* Linux: a systemd user unit. The user manager starts it at first login and
|
|
4
|
+
* stops it at last logout; `Restart=on-failure` with no start limit means a
|
|
5
|
+
* dying keepalive is always brought back, never parked as `failed`.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, rmSync } from "node:fs";
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { systemdUnavailableOutput, systemdUnitPath } from "@amalgm/core/supervision";
|
|
10
|
+
import { atomicWrite, ensureUserDir } from "../filesystem.js";
|
|
11
|
+
import { keepaliveCommand, osCommand, serviceEnvironment } from "./adapter.js";
|
|
12
|
+
import { renderSystemdUnit } from './systemd-unit.js';
|
|
13
|
+
export { renderSystemdUnit } from './systemd-unit.js';
|
|
14
|
+
/** Does this login have a user manager to talk to? Containers and bare SSH often do not. */
|
|
15
|
+
export function userSystemdAvailable() {
|
|
16
|
+
const probe = osCommand("systemctl", ["--user", "show-environment"], { tolerate: true });
|
|
17
|
+
return probe.ok && !systemdUnavailableOutput(`${probe.stdout}\n${probe.stderr}`);
|
|
18
|
+
}
|
|
19
|
+
function unit(identity) {
|
|
20
|
+
return systemdUnitPath(identity.home, identity.label);
|
|
21
|
+
}
|
|
22
|
+
function systemctl(args, tolerate = false) {
|
|
23
|
+
return osCommand("systemctl", ["--user", ...args], { tolerate });
|
|
24
|
+
}
|
|
25
|
+
export const systemdOwner = {
|
|
26
|
+
backend: "systemd",
|
|
27
|
+
files: (identity) => ({ unit: unit(identity) }),
|
|
28
|
+
async install(identity, launcher, keepaliveArgs) {
|
|
29
|
+
const description = renderSystemdUnit({
|
|
30
|
+
command: keepaliveCommand(launcher, keepaliveArgs),
|
|
31
|
+
environment: serviceEnvironment(identity),
|
|
32
|
+
workingDirectory: identity.home,
|
|
33
|
+
});
|
|
34
|
+
await stopKeepalive(identity);
|
|
35
|
+
const file = unit(identity);
|
|
36
|
+
ensureUserDir(dirname(file));
|
|
37
|
+
atomicWrite(file, description, 0o644);
|
|
38
|
+
systemctl(["daemon-reload"]);
|
|
39
|
+
// A selected data home need not be in the OS account's unit search path.
|
|
40
|
+
// Enabling the absolute file also links it into that account's user manager.
|
|
41
|
+
systemctl(["enable", file]);
|
|
42
|
+
systemctl(["restart", identity.label]);
|
|
43
|
+
},
|
|
44
|
+
start(identity) {
|
|
45
|
+
systemctl(["start", identity.label]);
|
|
46
|
+
},
|
|
47
|
+
async stop(identity) {
|
|
48
|
+
await stopKeepalive(identity);
|
|
49
|
+
systemctl(["stop", identity.label], true);
|
|
50
|
+
},
|
|
51
|
+
status(identity) {
|
|
52
|
+
const registered = existsSync(unit(identity));
|
|
53
|
+
const show = systemctl(["show", identity.label, "-p", "LoadState", "-p", "ActiveState", "-p", "SubState", "-p", "MainPID"], true);
|
|
54
|
+
const property = (name) => new RegExp(`^${name}=(.*)$`, "m").exec(show.stdout)?.[1]?.trim() ?? null;
|
|
55
|
+
const pid = Number(property("MainPID") ?? 0);
|
|
56
|
+
const active = property("ActiveState");
|
|
57
|
+
return {
|
|
58
|
+
registered,
|
|
59
|
+
loaded: show.ok ? property("LoadState") === "loaded" : null,
|
|
60
|
+
state: active && property("SubState") ? `${active}/${property("SubState")}` : active,
|
|
61
|
+
pid: pid > 0 ? pid : null,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
async uninstall(identity) {
|
|
65
|
+
await stopKeepalive(identity);
|
|
66
|
+
systemctl(["disable", "--now", identity.label], true);
|
|
67
|
+
rmSync(unit(identity), { force: true });
|
|
68
|
+
systemctl(["daemon-reload"], true);
|
|
69
|
+
systemctl(["reset-failed", identity.label], true);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=systemd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemd.js","sourceRoot":"","sources":["../../src/service/systemd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ChildRestartPolicy } from '@amalgm/core/supervision';
|
|
2
|
+
export declare const encodePowerShell: (source: string) => string;
|
|
3
|
+
export declare function jsonExpression(value: unknown): string;
|
|
4
|
+
export declare function powershellExecutable(env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
/** Microsoft argv quoting goes directly to ProcessStartInfo, without a shell. */
|
|
6
|
+
export declare function quoteWindowsArgument(value: string): string;
|
|
7
|
+
/** Scheduler owns this action's logged-in lifetime. Its launch-retry setting
|
|
8
|
+
* cannot observe controller exits, so this native port applies Core's policy. */
|
|
9
|
+
export declare function windowsControllerAction(command: readonly string[], workingDirectory: string, restartPolicy?: ChildRestartPolicy): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { win32 } from 'node:path';
|
|
2
|
+
import { CHILD_RESTART_POLICY } from '@amalgm/core/supervision';
|
|
3
|
+
export const encodePowerShell = (source) => Buffer.from(source, 'utf16le').toString('base64');
|
|
4
|
+
export function jsonExpression(value) {
|
|
5
|
+
return `ConvertFrom-Json ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.from(JSON.stringify(value), 'utf8').toString('base64')}')))`;
|
|
6
|
+
}
|
|
7
|
+
export function powershellExecutable(env = process.env) {
|
|
8
|
+
return win32.join(env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
9
|
+
}
|
|
10
|
+
/** Microsoft argv quoting goes directly to ProcessStartInfo, without a shell. */
|
|
11
|
+
export function quoteWindowsArgument(value) {
|
|
12
|
+
if (value.includes('\0'))
|
|
13
|
+
throw new Error('A process argument cannot contain NUL.');
|
|
14
|
+
return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1')}"`;
|
|
15
|
+
}
|
|
16
|
+
/** Scheduler owns this action's logged-in lifetime. Its launch-retry setting
|
|
17
|
+
* cannot observe controller exits, so this native port applies Core's policy. */
|
|
18
|
+
export function windowsControllerAction(command, workingDirectory, restartPolicy = CHILD_RESTART_POLICY) {
|
|
19
|
+
if (!command.length || !command[0])
|
|
20
|
+
throw new Error('The controller needs an executable.');
|
|
21
|
+
return encodePowerShell(`
|
|
22
|
+
$ErrorActionPreference = 'Stop'
|
|
23
|
+
$launch = ${jsonExpression({ executable: command[0], arguments: command.slice(1).map(quoteWindowsArgument).join(' '), workingDirectory })}
|
|
24
|
+
$policy = ${jsonExpression(restartPolicy)}
|
|
25
|
+
$start = New-Object System.Diagnostics.ProcessStartInfo
|
|
26
|
+
$start.FileName = $launch.executable
|
|
27
|
+
$start.Arguments = $launch.arguments
|
|
28
|
+
$start.WorkingDirectory = $launch.workingDirectory
|
|
29
|
+
$start.UseShellExecute = $false
|
|
30
|
+
$start.CreateNoWindow = $true
|
|
31
|
+
$clock = [Diagnostics.Stopwatch]::StartNew()
|
|
32
|
+
$history = @()
|
|
33
|
+
$delay = $policy.initialDelayMs
|
|
34
|
+
while ($true) {
|
|
35
|
+
$started = $clock.ElapsedMilliseconds
|
|
36
|
+
$runtimeProcess = $null
|
|
37
|
+
$result = 1
|
|
38
|
+
try {
|
|
39
|
+
$runtimeProcess = [System.Diagnostics.Process]::Start($start)
|
|
40
|
+
$runtimeProcess.WaitForExit()
|
|
41
|
+
$result = $runtimeProcess.ExitCode
|
|
42
|
+
} catch { [Console]::Error.WriteLine($_.Exception.Message) }
|
|
43
|
+
finally { if ($runtimeProcess) { $runtimeProcess.Dispose() } }
|
|
44
|
+
if ($result -eq 0) { exit 0 }
|
|
45
|
+
$now = $clock.ElapsedMilliseconds
|
|
46
|
+
if ($now - $started -ge $policy.stableAfterMs) { $delay = $policy.initialDelayMs; $history = @() }
|
|
47
|
+
$history = @($history | Where-Object { $now - $_ -le $policy.windowMs })
|
|
48
|
+
if ($history.Count -ge $policy.maxRestarts) { exit $result }
|
|
49
|
+
$history += $now
|
|
50
|
+
Start-Sleep -Milliseconds $delay
|
|
51
|
+
$delay = [Math]::Min($delay * 2, $policy.maxDelayMs)
|
|
52
|
+
}
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=windows-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-action.js","sourceRoot":"","sources":["../../src/service/windows-action.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAA2B,MAAM,0BAA0B,CAAC;AAEzF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC9G,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,kFAAkF,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;AAC/J,CAAC;AACD,MAAM,UAAU,oBAAoB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACvE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,aAAa,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAChH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpF,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;AAC9E,CAAC;AAED;iFACiF;AACjF,MAAM,UAAU,uBAAuB,CAAC,OAA0B,EAAE,gBAAwB,EAC1F,gBAAoC,oBAAoB;IACxD,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAC3F,OAAO,gBAAgB,CAAC;;YAEd,cAAc,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,CAAC;YAC7H,cAAc,CAAC,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BxC,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { encodePowerShell, powershellExecutable, quoteWindowsArgument, windowsControllerAction } from './windows-action.js';
|
|
2
|
+
export type WindowsTaskOperation = 'install' | 'start' | 'disable' | 'stop' | 'status' | 'uninstall';
|
|
3
|
+
/** All interpolated input is encoded JSON data. Task Scheduler runs under the
|
|
4
|
+
* current account's interactive token and never stores an account password. */
|
|
5
|
+
export declare function windowsTaskOperation(input: {
|
|
6
|
+
readonly operation: WindowsTaskOperation;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly action?: string;
|
|
9
|
+
}): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CHILD_RESTART_POLICY } from '@amalgm/core/supervision';
|
|
2
|
+
import { encodePowerShell, jsonExpression, powershellExecutable } from './windows-action.js';
|
|
3
|
+
export { encodePowerShell, powershellExecutable, quoteWindowsArgument, windowsControllerAction } from './windows-action.js';
|
|
4
|
+
/** All interpolated input is encoded JSON data. Task Scheduler runs under the
|
|
5
|
+
* current account's interactive token and never stores an account password. */
|
|
6
|
+
export function windowsTaskOperation(input) {
|
|
7
|
+
const request = { ...input, executable: powershellExecutable(),
|
|
8
|
+
restartCount: CHILD_RESTART_POLICY.maxRestarts,
|
|
9
|
+
// MS-TSCH 2.5.4.2: these retries cover failure to start an action, not its exit.
|
|
10
|
+
// https://learn.microsoft.com/openspecs/windows_protocols/ms-tsch/2ff4aa5a-7bc4-449f-bbb1-27475645867f
|
|
11
|
+
// Task Scheduler's supported minimum is one minute.
|
|
12
|
+
// https://learn.microsoft.com/windows/win32/taskschd/taskschedulerschema-restarttype-complextype
|
|
13
|
+
restartIntervalMs: Math.max(60_000, CHILD_RESTART_POLICY.initialDelayMs) };
|
|
14
|
+
return encodePowerShell(`
|
|
15
|
+
$ErrorActionPreference = 'Stop'
|
|
16
|
+
$request = ${jsonExpression(request)}
|
|
17
|
+
$task = Get-ScheduledTask -TaskPath '\\' | Where-Object { $_.TaskName -ceq $request.name } | Select-Object -First 1
|
|
18
|
+
switch ($request.operation) {
|
|
19
|
+
'install' {
|
|
20
|
+
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
21
|
+
$principal = New-ScheduledTaskPrincipal -UserId $sid -LogonType Interactive -RunLevel Limited
|
|
22
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $sid
|
|
23
|
+
$action = New-ScheduledTaskAction -Execute $request.executable -Argument ('-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ' + $request.action)
|
|
24
|
+
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount $request.restartCount -RestartInterval ([TimeSpan]::FromMilliseconds($request.restartIntervalMs)) -Hidden
|
|
25
|
+
Register-ScheduledTask -TaskPath '\\' -TaskName $request.name -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
|
|
26
|
+
Start-ScheduledTask -TaskPath '\\' -TaskName $request.name
|
|
27
|
+
}
|
|
28
|
+
'start' {
|
|
29
|
+
if (-not $task) { throw 'The Amalgm runtime task is not registered.' }
|
|
30
|
+
Enable-ScheduledTask -InputObject $task | Out-Null
|
|
31
|
+
Start-ScheduledTask -InputObject $task
|
|
32
|
+
}
|
|
33
|
+
'disable' { if ($task) { Disable-ScheduledTask -InputObject $task | Out-Null } }
|
|
34
|
+
'stop' { if ($task) { Stop-ScheduledTask -InputObject $task } }
|
|
35
|
+
'uninstall' { if ($task) { Unregister-ScheduledTask -InputObject $task -Confirm:$false } }
|
|
36
|
+
'status' {
|
|
37
|
+
if ($task) {
|
|
38
|
+
$info = Get-ScheduledTaskInfo -InputObject $task
|
|
39
|
+
@{ registered = $true; state = [string]$task.State; lastResult = $info.LastTaskResult } | ConvertTo-Json -Compress
|
|
40
|
+
} else { @{ registered = $false } | ConvertTo-Json -Compress }
|
|
41
|
+
}
|
|
42
|
+
default { throw 'Unknown runtime task operation.' }
|
|
43
|
+
}
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=windows-task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-task.js","sourceRoot":"","sources":["../../src/service/windows-task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAI5H;+EAC+E;AAC/E,MAAM,UAAU,oBAAoB,CAAC,KAIpC;IACC,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE;QAC5D,YAAY,EAAE,oBAAoB,CAAC,WAAW;QAC9C,iFAAiF;QACjF,uGAAuG;QACvG,oDAAoD;QACpD,iGAAiG;QACjG,iBAAiB,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,cAAc,CAAC,EAAE,CAAC;IAC7E,OAAO,gBAAgB,CAAC;;aAEb,cAAc,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BnC,CAAC,CAAC;AACH,CAAC"}
|