@amalgm/shell 0.1.162 → 0.1.163
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 +98 -14
- package/README.md +16 -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/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 +85 -73
- package/dist/cli.js.map +1 -1
- package/dist/compose.js +4 -3
- package/dist/compose.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/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 +26 -5
- package/dist/machine-host.js +192 -69
- 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 +8 -0
- package/dist/process-lifecycle.js +32 -0
- package/dist/process-lifecycle.js.map +1 -0
- 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 +196 -0
- package/dist/service/cli.js.map +1 -0
- package/dist/service/control.d.ts +29 -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 +11 -0
- package/dist/service/keepalive.js +180 -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/registration.d.ts +82 -0
- package/dist/service/registration.js +114 -0
- package/dist/service/registration.js.map +1 -0
- package/dist/service/systemd.d.ts +9 -0
- package/dist/service/systemd.js +94 -0
- package/dist/service/systemd.js.map +1 -0
- package/dist/service/windows-task.d.ts +16 -0
- package/dist/service/windows-task.js +82 -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/user-ground-host.d.ts +3 -0
- package/dist/user-ground-host.js +13 -0
- package/dist/user-ground-host.js.map +1 -1
- package/npm-shrinkwrap.json +49 -83
- package/package.json +9 -9
|
@@ -0,0 +1,94 @@
|
|
|
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 { systemdQuote, systemdUnavailableOutput, systemdUnitPath } from "@amalgm/core/supervision";
|
|
10
|
+
import { atomicWrite, ensureUserDir } from "../filesystem.js";
|
|
11
|
+
import { keepaliveCommand, osCommand, serviceEnvironment } from "./adapter.js";
|
|
12
|
+
const RESTART_SEC = 5;
|
|
13
|
+
/** SIGTERM to the keepalive, SIGKILL to the whole group after this. */
|
|
14
|
+
const TIMEOUT_STOP_SEC = 30;
|
|
15
|
+
export function renderSystemdUnit(input) {
|
|
16
|
+
const env = Object.entries(input.environment)
|
|
17
|
+
.map(([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}`)
|
|
18
|
+
.join("\n");
|
|
19
|
+
return `[Unit]
|
|
20
|
+
Description=Amalgm always-on runtime
|
|
21
|
+
StartLimitIntervalSec=0
|
|
22
|
+
|
|
23
|
+
[Service]
|
|
24
|
+
Type=exec
|
|
25
|
+
WorkingDirectory=${systemdQuote(input.workingDirectory)}
|
|
26
|
+
${env}
|
|
27
|
+
ExecStart=${input.command.map(systemdQuote).join(" ")}
|
|
28
|
+
Restart=on-failure
|
|
29
|
+
RestartSec=${RESTART_SEC}
|
|
30
|
+
KillMode=mixed
|
|
31
|
+
TimeoutStopSec=${TIMEOUT_STOP_SEC}
|
|
32
|
+
|
|
33
|
+
[Install]
|
|
34
|
+
WantedBy=default.target
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
37
|
+
/** Does this login have a user manager to talk to? Containers and bare SSH often do not. */
|
|
38
|
+
export function userSystemdAvailable() {
|
|
39
|
+
const probe = osCommand("systemctl", ["--user", "show-environment"], { tolerate: true });
|
|
40
|
+
return probe.ok && !systemdUnavailableOutput(`${probe.stdout}\n${probe.stderr}`);
|
|
41
|
+
}
|
|
42
|
+
function unit(identity) {
|
|
43
|
+
return systemdUnitPath(identity.home, identity.label);
|
|
44
|
+
}
|
|
45
|
+
function systemctl(args, tolerate = false) {
|
|
46
|
+
return osCommand("systemctl", ["--user", ...args], { tolerate });
|
|
47
|
+
}
|
|
48
|
+
export const systemdOwner = {
|
|
49
|
+
backend: "systemd",
|
|
50
|
+
files: (identity) => ({ unit: unit(identity) }),
|
|
51
|
+
async install(identity, launcher, keepaliveArgs) {
|
|
52
|
+
await stopKeepalive(identity);
|
|
53
|
+
const file = unit(identity);
|
|
54
|
+
ensureUserDir(dirname(file));
|
|
55
|
+
atomicWrite(file, renderSystemdUnit({
|
|
56
|
+
command: keepaliveCommand(launcher, keepaliveArgs),
|
|
57
|
+
environment: serviceEnvironment(identity),
|
|
58
|
+
workingDirectory: identity.home,
|
|
59
|
+
}), 0o644);
|
|
60
|
+
systemctl(["daemon-reload"]);
|
|
61
|
+
// A selected data home need not be in the OS account's unit search path.
|
|
62
|
+
// Enabling the absolute file also links it into that account's user manager.
|
|
63
|
+
systemctl(["enable", file]);
|
|
64
|
+
systemctl(["restart", identity.label]);
|
|
65
|
+
},
|
|
66
|
+
start(identity) {
|
|
67
|
+
systemctl(["start", identity.label]);
|
|
68
|
+
},
|
|
69
|
+
async stop(identity) {
|
|
70
|
+
await stopKeepalive(identity);
|
|
71
|
+
systemctl(["stop", identity.label], true);
|
|
72
|
+
},
|
|
73
|
+
status(identity) {
|
|
74
|
+
const registered = existsSync(unit(identity));
|
|
75
|
+
const show = systemctl(["show", identity.label, "-p", "LoadState", "-p", "ActiveState", "-p", "SubState", "-p", "MainPID"], true);
|
|
76
|
+
const property = (name) => new RegExp(`^${name}=(.*)$`, "m").exec(show.stdout)?.[1]?.trim() ?? null;
|
|
77
|
+
const pid = Number(property("MainPID") ?? 0);
|
|
78
|
+
const active = property("ActiveState");
|
|
79
|
+
return {
|
|
80
|
+
registered,
|
|
81
|
+
loaded: show.ok ? property("LoadState") === "loaded" : null,
|
|
82
|
+
state: active && property("SubState") ? `${active}/${property("SubState")}` : active,
|
|
83
|
+
pid: pid > 0 ? pid : null,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
async uninstall(identity) {
|
|
87
|
+
await stopKeepalive(identity);
|
|
88
|
+
systemctl(["disable", "--now", identity.label], true);
|
|
89
|
+
rmSync(unit(identity), { force: true });
|
|
90
|
+
systemctl(["daemon-reload"], true);
|
|
91
|
+
systemctl(["reset-failed", identity.label], true);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
//# 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,16 @@
|
|
|
1
|
+
export declare function encodePowerShell(source: string): string;
|
|
2
|
+
/** Microsoft command-line quoting, applied directly to ProcessStartInfo.
|
|
3
|
+
* No cmd.exe, environment expansion, or shell interpretation is involved. */
|
|
4
|
+
export declare function quoteWindowsArgument(value: string): string;
|
|
5
|
+
export declare function powershellExecutable(env?: NodeJS.ProcessEnv): string;
|
|
6
|
+
/** The scheduler must observe the controller's real lifetime and exit code.
|
|
7
|
+
* A hidden, waiting host wrapper does not make any recovery decisions. */
|
|
8
|
+
export declare function windowsControllerAction(command: readonly string[], workingDirectory: string): string;
|
|
9
|
+
export type WindowsTaskOperation = 'install' | 'start' | 'disable' | 'stop' | 'status' | 'uninstall';
|
|
10
|
+
/** All interpolated input is encoded JSON data. Task Scheduler runs under the
|
|
11
|
+
* current account's interactive token and never stores an account password. */
|
|
12
|
+
export declare function windowsTaskOperation(input: {
|
|
13
|
+
readonly operation: WindowsTaskOperation;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly action?: string;
|
|
16
|
+
}): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { win32 } from 'node:path';
|
|
2
|
+
import { CHILD_RESTART_POLICY } from '@amalgm/core/supervision';
|
|
3
|
+
export function encodePowerShell(source) {
|
|
4
|
+
return Buffer.from(source, 'utf16le').toString('base64');
|
|
5
|
+
}
|
|
6
|
+
/** Microsoft command-line quoting, applied directly to ProcessStartInfo.
|
|
7
|
+
* No cmd.exe, environment expansion, or shell interpretation is involved. */
|
|
8
|
+
export function quoteWindowsArgument(value) {
|
|
9
|
+
if (value.includes('\0'))
|
|
10
|
+
throw new Error('A process argument cannot contain NUL.');
|
|
11
|
+
return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1')}"`;
|
|
12
|
+
}
|
|
13
|
+
export function powershellExecutable(env = process.env) {
|
|
14
|
+
return win32.join(env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
15
|
+
}
|
|
16
|
+
function jsonExpression(value) {
|
|
17
|
+
const encoded = Buffer.from(JSON.stringify(value), 'utf8').toString('base64');
|
|
18
|
+
return `ConvertFrom-Json ([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encoded}')))`;
|
|
19
|
+
}
|
|
20
|
+
/** The scheduler must observe the controller's real lifetime and exit code.
|
|
21
|
+
* A hidden, waiting host wrapper does not make any recovery decisions. */
|
|
22
|
+
export function windowsControllerAction(command, workingDirectory) {
|
|
23
|
+
if (!command.length || !command[0])
|
|
24
|
+
throw new Error('The controller needs an executable.');
|
|
25
|
+
return encodePowerShell(`
|
|
26
|
+
$ErrorActionPreference = 'Stop'
|
|
27
|
+
$launch = ${jsonExpression({ executable: command[0], arguments: command.slice(1).map(quoteWindowsArgument).join(' '), workingDirectory })}
|
|
28
|
+
$start = New-Object System.Diagnostics.ProcessStartInfo
|
|
29
|
+
$start.FileName = $launch.executable
|
|
30
|
+
$start.Arguments = $launch.arguments
|
|
31
|
+
$start.WorkingDirectory = $launch.workingDirectory
|
|
32
|
+
$start.UseShellExecute = $false
|
|
33
|
+
$start.CreateNoWindow = $true
|
|
34
|
+
$runtimeProcess = [System.Diagnostics.Process]::Start($start)
|
|
35
|
+
try {
|
|
36
|
+
$runtimeProcess.WaitForExit()
|
|
37
|
+
$result = $runtimeProcess.ExitCode
|
|
38
|
+
} finally { $runtimeProcess.Dispose() }
|
|
39
|
+
exit $result
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
/** All interpolated input is encoded JSON data. Task Scheduler runs under the
|
|
43
|
+
* current account's interactive token and never stores an account password. */
|
|
44
|
+
export function windowsTaskOperation(input) {
|
|
45
|
+
const request = { ...input, executable: powershellExecutable(),
|
|
46
|
+
restartCount: CHILD_RESTART_POLICY.maxRestarts,
|
|
47
|
+
// Task Scheduler's supported minimum is one minute.
|
|
48
|
+
// https://learn.microsoft.com/windows/win32/taskschd/taskschedulerschema-restarttype-complextype
|
|
49
|
+
restartIntervalMs: Math.max(60_000, CHILD_RESTART_POLICY.initialDelayMs) };
|
|
50
|
+
return encodePowerShell(`
|
|
51
|
+
$ErrorActionPreference = 'Stop'
|
|
52
|
+
$request = ${jsonExpression(request)}
|
|
53
|
+
$task = Get-ScheduledTask -TaskPath '\\' | Where-Object { $_.TaskName -ceq $request.name } | Select-Object -First 1
|
|
54
|
+
switch ($request.operation) {
|
|
55
|
+
'install' {
|
|
56
|
+
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
57
|
+
$principal = New-ScheduledTaskPrincipal -UserId $sid -LogonType Interactive -RunLevel Limited
|
|
58
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $sid
|
|
59
|
+
$action = New-ScheduledTaskAction -Execute $request.executable -Argument ('-NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ' + $request.action)
|
|
60
|
+
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount $request.restartCount -RestartInterval ([TimeSpan]::FromMilliseconds($request.restartIntervalMs)) -Hidden
|
|
61
|
+
Register-ScheduledTask -TaskPath '\\' -TaskName $request.name -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
|
|
62
|
+
Start-ScheduledTask -TaskPath '\\' -TaskName $request.name
|
|
63
|
+
}
|
|
64
|
+
'start' {
|
|
65
|
+
if (-not $task) { throw 'The Amalgm runtime task is not registered.' }
|
|
66
|
+
Enable-ScheduledTask -InputObject $task | Out-Null
|
|
67
|
+
Start-ScheduledTask -InputObject $task
|
|
68
|
+
}
|
|
69
|
+
'disable' { if ($task) { Disable-ScheduledTask -InputObject $task | Out-Null } }
|
|
70
|
+
'stop' { if ($task) { Stop-ScheduledTask -InputObject $task } }
|
|
71
|
+
'uninstall' { if ($task) { Unregister-ScheduledTask -InputObject $task -Confirm:$false } }
|
|
72
|
+
'status' {
|
|
73
|
+
if ($task) {
|
|
74
|
+
$info = Get-ScheduledTaskInfo -InputObject $task
|
|
75
|
+
@{ registered = $true; state = [string]$task.State; lastResult = $info.LastTaskResult } | ConvertTo-Json -Compress
|
|
76
|
+
} else { @{ registered = $false } | ConvertTo-Json -Compress }
|
|
77
|
+
}
|
|
78
|
+
default { throw 'Unknown runtime task operation.' }
|
|
79
|
+
}
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
//# 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,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC3D,CAAC;AAED;6EAC6E;AAC7E,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,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,SAAS,cAAc,CAAC,KAAc;IACpC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC9E,OAAO,kFAAkF,OAAO,MAAM,CAAC;AACzG,CAAC;AAED;0EAC0E;AAC1E,MAAM,UAAU,uBAAuB,CAAC,OAA0B,EAAE,gBAAwB;IAC1F,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;;;;;;;;;;;;;CAaxI,CAAC,CAAC;AACH,CAAC;AAID;+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,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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Windows Task Scheduler owns a hidden controller in the current account's
|
|
2
|
+
* logged-in lifetime, including Core's bounded automatic crash recovery. */
|
|
3
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { windowsStartupScriptPath } from '@amalgm/core/supervision';
|
|
6
|
+
import { atomicJson } from '../filesystem.js';
|
|
7
|
+
import { keepaliveStatus, stopKeepalive } from './control.js';
|
|
8
|
+
import { keepaliveCommand, osCommand } from './adapter.js';
|
|
9
|
+
import { powershellExecutable, windowsControllerAction, windowsTaskOperation } from './windows-task.js';
|
|
10
|
+
const definition = (identity) => join(identity.serviceDir, 'windows-task.json');
|
|
11
|
+
const task = (identity, operation, action) => osCommand(powershellExecutable(), ['-NoProfile', '-NonInteractive', '-EncodedCommand', windowsTaskOperation({
|
|
12
|
+
operation, name: identity.label, ...(action ? { action } : {}),
|
|
13
|
+
})]);
|
|
14
|
+
async function waitForController(identity) {
|
|
15
|
+
const deadline = Date.now() + 15_000;
|
|
16
|
+
while (Date.now() < deadline) {
|
|
17
|
+
if ((await keepaliveStatus(identity))?.alive)
|
|
18
|
+
return;
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`The Windows task for ${identity.label} did not report an authenticated controller; see ${identity.serviceDir}`);
|
|
22
|
+
}
|
|
23
|
+
export const windowsOwner = {
|
|
24
|
+
backend: 'windows',
|
|
25
|
+
files: (identity) => ({ task: definition(identity) }),
|
|
26
|
+
async install(identity, launcher, keepaliveArgs) {
|
|
27
|
+
await stopKeepalive(identity);
|
|
28
|
+
const action = windowsControllerAction(keepaliveCommand(launcher, keepaliveArgs), identity.home);
|
|
29
|
+
task(identity, 'install', action);
|
|
30
|
+
await waitForController(identity);
|
|
31
|
+
atomicJson(definition(identity), { name: identity.label, action, binding: identity.binding });
|
|
32
|
+
// Retire only the known previous registration after the new owner is live.
|
|
33
|
+
rmSync(windowsStartupScriptPath(identity.labelScope, { home: identity.home, appData: process.env.APPDATA }), { force: true });
|
|
34
|
+
},
|
|
35
|
+
async start(identity) {
|
|
36
|
+
if ((await keepaliveStatus(identity))?.alive)
|
|
37
|
+
return;
|
|
38
|
+
task(identity, 'start');
|
|
39
|
+
await waitForController(identity);
|
|
40
|
+
},
|
|
41
|
+
async stop(identity) {
|
|
42
|
+
task(identity, 'disable');
|
|
43
|
+
await stopKeepalive(identity);
|
|
44
|
+
task(identity, 'stop');
|
|
45
|
+
},
|
|
46
|
+
async status(identity) {
|
|
47
|
+
const native = JSON.parse(task(identity, 'status').stdout);
|
|
48
|
+
const controller = await keepaliveStatus(identity);
|
|
49
|
+
return {
|
|
50
|
+
registered: native.registered && existsSync(definition(identity)), loaded: native.registered,
|
|
51
|
+
state: controller?.alive ? 'running' : native.state?.toLowerCase() || 'not registered',
|
|
52
|
+
pid: controller?.alive ? controller.pid : null,
|
|
53
|
+
...(native.lastResult ? { detail: `Task Scheduler result: ${native.lastResult}` } : {}),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async uninstall(identity) {
|
|
57
|
+
task(identity, 'disable');
|
|
58
|
+
await stopKeepalive(identity);
|
|
59
|
+
task(identity, 'stop');
|
|
60
|
+
task(identity, 'uninstall');
|
|
61
|
+
rmSync(definition(identity), { force: true });
|
|
62
|
+
rmSync(windowsStartupScriptPath(identity.labelScope, { home: identity.home, appData: process.env.APPDATA }), { force: true });
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.js","sourceRoot":"","sources":["../../src/service/windows.ts"],"names":[],"mappings":"AAAA;4EAC4E;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAqB,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,oBAAoB,EAA6B,MAAM,mBAAmB,CAAC;AAEnI,MAAM,UAAU,GAAG,CAAC,QAAyB,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;AACjG,MAAM,IAAI,GAAG,CAAC,QAAyB,EAAE,SAA+B,EAAE,MAAe,EAAE,EAAE,CAAC,SAAS,CACrG,oBAAoB,EAAE,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,oBAAoB,CAAC;QAChG,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAC,CAAC,CACJ,CAAC;AAEF,KAAK,UAAU,iBAAiB,CAAC,QAAyB;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IACrC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK;YAAE,OAAO;QACrD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,CAAC,KAAK,oDAAoD,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;AACnI,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;IACrD,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa;QAC7C,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,uBAAuB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjG,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAClC,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAClC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9F,2EAA2E;QAC3E,MAAM,CAAC,wBAAwB,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChI,CAAC;IACD,KAAK,CAAC,KAAK,CAAC,QAAQ;QAClB,IAAI,CAAC,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK;YAAE,OAAO;QACrD,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxB,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,QAAQ;QACjB,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC1B,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzB,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,QAAQ;QACnB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAiE,CAAC;QAC3H,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QACnD,OAAO;YACL,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,UAAU;YAC5F,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,gBAAgB;YACtF,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;YAC9C,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,0BAA0B,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxF,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,SAAS,CAAC,QAAQ;QACtB,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC1B,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACvB,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC5B,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,MAAM,CAAC,wBAAwB,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChI,CAAC;CACF,CAAC"}
|
|
@@ -68,6 +68,8 @@ export interface UserGroundHostOptions {
|
|
|
68
68
|
readonly tunnelUrl: string;
|
|
69
69
|
readonly tunnelToken: string;
|
|
70
70
|
readonly runtimeVersion: string;
|
|
71
|
+
/** Installation-wide host admission, before any writer touches ground. */
|
|
72
|
+
readonly reserveWritableRoot?: (path: string) => void;
|
|
71
73
|
readonly declaration?: UserHomeDeclaration;
|
|
72
74
|
readonly now?: () => Date;
|
|
73
75
|
/** Product diagnostics consume this; Core's `/healthz` remains liveness-only. */
|
|
@@ -256,6 +258,7 @@ export declare class UserGroundHost {
|
|
|
256
258
|
* stay one read each. A lone artifact is a set of one. */
|
|
257
259
|
private downloadContentSet;
|
|
258
260
|
private downloadContent;
|
|
261
|
+
private reserveGroundRoots;
|
|
259
262
|
private ensureWatchers;
|
|
260
263
|
private scheduleRescan;
|
|
261
264
|
/** A finite cold command may suppress Detect while it owns publication, but
|
package/dist/user-ground-host.js
CHANGED
|
@@ -171,6 +171,7 @@ export class UserGroundHost {
|
|
|
171
171
|
converge: async (identity) => {
|
|
172
172
|
this.converged = false;
|
|
173
173
|
this.activeIdentity = normalizedIdentity(identity);
|
|
174
|
+
this.reserveGroundRoots(this.activeIdentity);
|
|
174
175
|
const local = this.localPort(this.activeIdentity);
|
|
175
176
|
const result = await convergeUserGround(this.activeIdentity, {
|
|
176
177
|
sha256Hex,
|
|
@@ -312,6 +313,7 @@ export class UserGroundHost {
|
|
|
312
313
|
const register = createFilesRegisterPorts({
|
|
313
314
|
userRoot,
|
|
314
315
|
deviceId: identity.deviceId,
|
|
316
|
+
reserveWritableRoot: this.options.reserveWritableRoot,
|
|
315
317
|
onReferenceDeclared: () => this.scheduleRescan(),
|
|
316
318
|
findKnownWorkspaceId: ({ absolutePath, deviceNumber, inode }) => findKnownEntityId(this.databasePath(identity), absolutePath, deviceNumber, inode),
|
|
317
319
|
});
|
|
@@ -363,6 +365,7 @@ export class UserGroundHost {
|
|
|
363
365
|
return current;
|
|
364
366
|
},
|
|
365
367
|
materializeWorkspace: async (input) => {
|
|
368
|
+
this.options.reserveWritableRoot?.(join(input.destinationParent, input.workspace.name));
|
|
366
369
|
const groundWaitStarted = performance.now();
|
|
367
370
|
this.stage({
|
|
368
371
|
primitive: "download",
|
|
@@ -2060,9 +2063,19 @@ export class UserGroundHost {
|
|
|
2060
2063
|
throw new Error(`download set did not seal ${artifact.contentHash}`);
|
|
2061
2064
|
return local;
|
|
2062
2065
|
}
|
|
2066
|
+
reserveGroundRoots(identity) {
|
|
2067
|
+
if (!this.options.reserveWritableRoot)
|
|
2068
|
+
return;
|
|
2069
|
+
const userRoot = this.userRoot(identity);
|
|
2070
|
+
this.options.reserveWritableRoot(userRoot);
|
|
2071
|
+
for (const { directory } of materializedWorkspaceBindings(workspaceBindingDir(userRoot, identity.deviceId))) {
|
|
2072
|
+
this.options.reserveWritableRoot(directory);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2063
2075
|
ensureWatchers(identity, authoritativeBaselineRootIds = []) {
|
|
2064
2076
|
if (this.closing)
|
|
2065
2077
|
return this.watchHost.health();
|
|
2078
|
+
this.reserveGroundRoots(identity);
|
|
2066
2079
|
const started = performance.now();
|
|
2067
2080
|
const root = this.userRoot(identity);
|
|
2068
2081
|
const wanted = new Map([[root, {
|