awb-agent-manager 1.0.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/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/lib/agent-lockfile.d.ts +37 -0
- package/dist/lib/agent-lockfile.js +205 -0
- package/dist/lib/agent-lockfile.js.map +1 -0
- package/dist/lib/agent-manager-commands.d.ts +34 -0
- package/dist/lib/agent-manager-commands.js +531 -0
- package/dist/lib/agent-manager-commands.js.map +1 -0
- package/dist/lib/base-session-manager.d.ts +127 -0
- package/dist/lib/base-session-manager.js +667 -0
- package/dist/lib/base-session-manager.js.map +1 -0
- package/dist/lib/chat-attachment-prep.d.ts +35 -0
- package/dist/lib/chat-attachment-prep.js +180 -0
- package/dist/lib/chat-attachment-prep.js.map +1 -0
- package/dist/lib/chat-session-manager.d.ts +29 -0
- package/dist/lib/chat-session-manager.js +400 -0
- package/dist/lib/chat-session-manager.js.map +1 -0
- package/dist/lib/circuit-breaker.d.ts +34 -0
- package/dist/lib/circuit-breaker.js +146 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/cli-adapters/antigravity.d.ts +15 -0
- package/dist/lib/cli-adapters/antigravity.js +114 -0
- package/dist/lib/cli-adapters/antigravity.js.map +1 -0
- package/dist/lib/cli-adapters/base.d.ts +115 -0
- package/dist/lib/cli-adapters/base.js +111 -0
- package/dist/lib/cli-adapters/base.js.map +1 -0
- package/dist/lib/cli-adapters/claude.d.ts +19 -0
- package/dist/lib/cli-adapters/claude.js +233 -0
- package/dist/lib/cli-adapters/claude.js.map +1 -0
- package/dist/lib/cli-adapters/codex.d.ts +14 -0
- package/dist/lib/cli-adapters/codex.js +152 -0
- package/dist/lib/cli-adapters/codex.js.map +1 -0
- package/dist/lib/cli-adapters/deepseek.d.ts +15 -0
- package/dist/lib/cli-adapters/deepseek.js +70 -0
- package/dist/lib/cli-adapters/deepseek.js.map +1 -0
- package/dist/lib/cli-adapters/index.d.ts +8 -0
- package/dist/lib/cli-adapters/index.js +27 -0
- package/dist/lib/cli-adapters/index.js.map +1 -0
- package/dist/lib/cli-adapters/model-introspect.d.ts +3 -0
- package/dist/lib/cli-adapters/model-introspect.js +65 -0
- package/dist/lib/cli-adapters/model-introspect.js.map +1 -0
- package/dist/lib/cli-error-signatures.d.ts +9 -0
- package/dist/lib/cli-error-signatures.js +22 -0
- package/dist/lib/cli-error-signatures.js.map +1 -0
- package/dist/lib/cli-output-summary.d.ts +2 -0
- package/dist/lib/cli-output-summary.js +104 -0
- package/dist/lib/cli-output-summary.js.map +1 -0
- package/dist/lib/cli-resolver.d.ts +2 -0
- package/dist/lib/cli-resolver.js +177 -0
- package/dist/lib/cli-resolver.js.map +1 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.js +124 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +36 -0
- package/dist/lib/constants.js +51 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/environment-provisioner.d.ts +31 -0
- package/dist/lib/environment-provisioner.js +182 -0
- package/dist/lib/environment-provisioner.js.map +1 -0
- package/dist/lib/error-log-uploader.d.ts +19 -0
- package/dist/lib/error-log-uploader.js +138 -0
- package/dist/lib/error-log-uploader.js.map +1 -0
- package/dist/lib/event-dispatcher.d.ts +149 -0
- package/dist/lib/event-dispatcher.js +799 -0
- package/dist/lib/event-dispatcher.js.map +1 -0
- package/dist/lib/event-log-recorder.d.ts +11 -0
- package/dist/lib/event-log-recorder.js +77 -0
- package/dist/lib/event-log-recorder.js.map +1 -0
- package/dist/lib/event-stream.d.ts +26 -0
- package/dist/lib/event-stream.js +177 -0
- package/dist/lib/event-stream.js.map +1 -0
- package/dist/lib/fs-browser.d.ts +30 -0
- package/dist/lib/fs-browser.js +287 -0
- package/dist/lib/fs-browser.js.map +1 -0
- package/dist/lib/host-mcp/logs.d.ts +19 -0
- package/dist/lib/host-mcp/logs.js +102 -0
- package/dist/lib/host-mcp/logs.js.map +1 -0
- package/dist/lib/host-mcp/platform.d.ts +24 -0
- package/dist/lib/host-mcp/platform.js +133 -0
- package/dist/lib/host-mcp/platform.js.map +1 -0
- package/dist/lib/host-mcp/screens.d.ts +22 -0
- package/dist/lib/host-mcp/screens.js +172 -0
- package/dist/lib/host-mcp/screens.js.map +1 -0
- package/dist/lib/host-mcp/server.d.ts +3 -0
- package/dist/lib/host-mcp/server.js +233 -0
- package/dist/lib/host-mcp/server.js.map +1 -0
- package/dist/lib/host-mcp/tools.d.ts +172 -0
- package/dist/lib/host-mcp/tools.js +1241 -0
- package/dist/lib/host-mcp/tools.js.map +1 -0
- package/dist/lib/instance-heartbeat.d.ts +55 -0
- package/dist/lib/instance-heartbeat.js +118 -0
- package/dist/lib/instance-heartbeat.js.map +1 -0
- package/dist/lib/legacy-import.d.ts +17 -0
- package/dist/lib/legacy-import.js +70 -0
- package/dist/lib/legacy-import.js.map +1 -0
- package/dist/lib/logging.d.ts +3 -0
- package/dist/lib/logging.js +65 -0
- package/dist/lib/logging.js.map +1 -0
- package/dist/lib/managed-agent-context.d.ts +25 -0
- package/dist/lib/managed-agent-context.js +33 -0
- package/dist/lib/managed-agent-context.js.map +1 -0
- package/dist/lib/managed-agent-store.d.ts +34 -0
- package/dist/lib/managed-agent-store.js +160 -0
- package/dist/lib/managed-agent-store.js.map +1 -0
- package/dist/lib/managed-agents.d.ts +24 -0
- package/dist/lib/managed-agents.js +87 -0
- package/dist/lib/managed-agents.js.map +1 -0
- package/dist/lib/mcp-client.d.ts +8 -0
- package/dist/lib/mcp-client.js +122 -0
- package/dist/lib/mcp-client.js.map +1 -0
- package/dist/lib/orphan-cleanup.d.ts +6 -0
- package/dist/lib/orphan-cleanup.js +108 -0
- package/dist/lib/orphan-cleanup.js.map +1 -0
- package/dist/lib/presence-heartbeat.d.ts +8 -0
- package/dist/lib/presence-heartbeat.js +61 -0
- package/dist/lib/presence-heartbeat.js.map +1 -0
- package/dist/lib/prompts.d.ts +29 -0
- package/dist/lib/prompts.js +208 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/rest.d.ts +61 -0
- package/dist/lib/rest.js +307 -0
- package/dist/lib/rest.js.map +1 -0
- package/dist/lib/run-provisioner.d.ts +21 -0
- package/dist/lib/run-provisioner.js +115 -0
- package/dist/lib/run-provisioner.js.map +1 -0
- package/dist/lib/self-path.d.ts +7 -0
- package/dist/lib/self-path.js +29 -0
- package/dist/lib/self-path.js.map +1 -0
- package/dist/lib/self-update.d.ts +46 -0
- package/dist/lib/self-update.js +482 -0
- package/dist/lib/self-update.js.map +1 -0
- package/dist/lib/service-install.d.ts +23 -0
- package/dist/lib/service-install.js +673 -0
- package/dist/lib/service-install.js.map +1 -0
- package/dist/lib/setup.d.ts +16 -0
- package/dist/lib/setup.js +157 -0
- package/dist/lib/setup.js.map +1 -0
- package/dist/lib/subagent-manager.d.ts +84 -0
- package/dist/lib/subagent-manager.js +793 -0
- package/dist/lib/subagent-manager.js.map +1 -0
- package/dist/lib/subagent-monitor.d.ts +43 -0
- package/dist/lib/subagent-monitor.js +229 -0
- package/dist/lib/subagent-monitor.js.map +1 -0
- package/dist/lib/ticket-session-manager.d.ts +23 -0
- package/dist/lib/ticket-session-manager.js +471 -0
- package/dist/lib/ticket-session-manager.js.map +1 -0
- package/dist/lib/worktree-manager.d.ts +39 -0
- package/dist/lib/worktree-manager.js +226 -0
- package/dist/lib/worktree-manager.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +670 -0
- package/dist/main.js.map +1 -0
- package/dist/package.json +57 -0
- package/package.json +57 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve as pathResolve, join } from 'node:path';
|
|
3
|
+
import { homedir, tmpdir, userInfo } from 'node:os';
|
|
4
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
const SERVICE_NAME = 'awb-agent-manager';
|
|
7
|
+
const LAUNCHD_LABEL = 'com.awb.agent-manager';
|
|
8
|
+
function which(bin) {
|
|
9
|
+
if (process.platform === 'win32') {
|
|
10
|
+
const r = spawnSync('where', [bin], { encoding: 'utf8' });
|
|
11
|
+
if (r.status === 0)
|
|
12
|
+
return r.stdout.split(/\r?\n/)[0]?.trim() || null;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const r = spawnSync('which', [bin], { encoding: 'utf8' });
|
|
16
|
+
if (r.status === 0)
|
|
17
|
+
return r.stdout.trim() || null;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function detectExecPath(override) {
|
|
21
|
+
if (override)
|
|
22
|
+
return pathResolve(override);
|
|
23
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
return pathResolve(here, '..', 'main.js');
|
|
25
|
+
}
|
|
26
|
+
function detectNode() {
|
|
27
|
+
return process.execPath;
|
|
28
|
+
}
|
|
29
|
+
function detectPlatform() {
|
|
30
|
+
if (process.platform === 'win32')
|
|
31
|
+
return 'windows';
|
|
32
|
+
if (process.platform === 'darwin')
|
|
33
|
+
return 'launchd';
|
|
34
|
+
if (process.platform === 'linux') {
|
|
35
|
+
if (existsSync('/etc/synoinfo.conf') && existsSync('/usr/local/etc/rc.d')) {
|
|
36
|
+
return 'synology';
|
|
37
|
+
}
|
|
38
|
+
if (which('systemctl'))
|
|
39
|
+
return 'systemd';
|
|
40
|
+
if (existsSync('/etc/init.d'))
|
|
41
|
+
return 'sysvinit';
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`unsupported platform: ${process.platform} — pass --platform to override`);
|
|
44
|
+
}
|
|
45
|
+
function resolvePlatform(override) {
|
|
46
|
+
if (!override || override === 'auto')
|
|
47
|
+
return detectPlatform();
|
|
48
|
+
return override;
|
|
49
|
+
}
|
|
50
|
+
function writeFileWithDir(path, content, mode) {
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
writeFileSync(path, content);
|
|
53
|
+
if (mode !== undefined) {
|
|
54
|
+
try {
|
|
55
|
+
chmodSync(path, mode);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function writeWithSudo(path, content, mode) {
|
|
62
|
+
const sudo = which('sudo');
|
|
63
|
+
if (!sudo) {
|
|
64
|
+
throw new Error('--system requires sudo, which was not found in PATH.');
|
|
65
|
+
}
|
|
66
|
+
const r = spawnSync('sudo', ['tee', path], {
|
|
67
|
+
input: content,
|
|
68
|
+
encoding: 'utf8',
|
|
69
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
70
|
+
});
|
|
71
|
+
if (r.status !== 0) {
|
|
72
|
+
throw new Error(`sudo tee ${path} failed (exit ${r.status})`);
|
|
73
|
+
}
|
|
74
|
+
if (mode !== undefined) {
|
|
75
|
+
spawnSync('sudo', ['chmod', mode.toString(8), path], { stdio: 'inherit' });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function removeFile(path, withSudo) {
|
|
79
|
+
if (!existsSync(path))
|
|
80
|
+
return;
|
|
81
|
+
if (withSudo) {
|
|
82
|
+
const sudo = which('sudo');
|
|
83
|
+
if (!sudo)
|
|
84
|
+
throw new Error('--system requires sudo to remove the unit.');
|
|
85
|
+
spawnSync('sudo', ['rm', '-f', path], { stdio: 'inherit' });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
try {
|
|
89
|
+
unlinkSync(path);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function logHeader(opts) {
|
|
96
|
+
process.stderr.write('\n awb-agent-manager — service install\n\n');
|
|
97
|
+
process.stderr.write(` platform: ${opts.platform}\n`);
|
|
98
|
+
process.stderr.write(` mode: ${opts.mode === 'system' ? 'system' : 'user'}\n`);
|
|
99
|
+
process.stderr.write(` exec: ${opts.nodeBin} ${opts.execPath}\n`);
|
|
100
|
+
process.stderr.write(` unit: ${opts.unitPath}\n`);
|
|
101
|
+
process.stderr.write(` user: ${opts.user}\n\n`);
|
|
102
|
+
}
|
|
103
|
+
function systemdUnit(opts) {
|
|
104
|
+
const { execPath, nodeBin, user, isSystem } = opts;
|
|
105
|
+
const envHome = process.env.AWB_AGENT_MANAGER_HOME;
|
|
106
|
+
const userDirective = isSystem ? `User=${user}\n` : '';
|
|
107
|
+
const envLines = envHome ? `Environment=AWB_AGENT_MANAGER_HOME=${envHome}\n` : '';
|
|
108
|
+
return `[Unit]
|
|
109
|
+
Description=AWB Agent Manager
|
|
110
|
+
After=network-online.target
|
|
111
|
+
Wants=network-online.target
|
|
112
|
+
|
|
113
|
+
[Service]
|
|
114
|
+
Type=simple
|
|
115
|
+
ExecStart=${nodeBin} ${execPath}
|
|
116
|
+
# always (not on-failure): an external SIGTERM makes the manager exit 0
|
|
117
|
+
# (clean shutdown), which on-failure treats as "done" and leaves the unit
|
|
118
|
+
# inactive(dead) until a manual start. always respawns on any exit; a
|
|
119
|
+
# deliberate stop is still honored because systemctl stop/restart suppresses
|
|
120
|
+
# the restart. Self-update's exit-1 re-exec keeps working unchanged.
|
|
121
|
+
Restart=always
|
|
122
|
+
RestartSec=5
|
|
123
|
+
TimeoutStopSec=30
|
|
124
|
+
${userDirective}${envLines}KillSignal=SIGTERM
|
|
125
|
+
|
|
126
|
+
[Install]
|
|
127
|
+
WantedBy=${isSystem ? 'multi-user.target' : 'default.target'}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
async function installSystemd(options) {
|
|
131
|
+
const isSystem = !!options.system;
|
|
132
|
+
const execPath = detectExecPath(options.execPath);
|
|
133
|
+
const nodeBin = detectNode();
|
|
134
|
+
const user = userInfo().username;
|
|
135
|
+
if (!existsSync(execPath)) {
|
|
136
|
+
throw new Error(`dist/main.js not found at ${execPath} — run \`npm run build\` first, ` +
|
|
137
|
+
`or pass --exec-path <path>.`);
|
|
138
|
+
}
|
|
139
|
+
const unit = systemdUnit({ execPath, nodeBin, user, isSystem });
|
|
140
|
+
const unitPath = isSystem
|
|
141
|
+
? `/etc/systemd/system/${SERVICE_NAME}.service`
|
|
142
|
+
: `${homedir()}/.config/systemd/user/${SERVICE_NAME}.service`;
|
|
143
|
+
logHeader({ platform: 'systemd', mode: isSystem ? 'system' : 'user', nodeBin, execPath, unitPath, user });
|
|
144
|
+
if (options.dryRun) {
|
|
145
|
+
process.stderr.write(' --dry-run: would write the following unit (skipping systemctl):\n\n');
|
|
146
|
+
process.stderr.write(unit.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
147
|
+
process.stderr.write('\n');
|
|
148
|
+
return { ok: true, platform: 'systemd', unitPath, mode: isSystem ? 'system' : 'user', enabled: false, started: false, notes: ['dry-run'] };
|
|
149
|
+
}
|
|
150
|
+
if (isSystem)
|
|
151
|
+
writeWithSudo(unitPath, unit, 0o644);
|
|
152
|
+
else
|
|
153
|
+
writeFileWithDir(unitPath, unit, 0o644);
|
|
154
|
+
process.stderr.write(` ✓ wrote ${unitPath}\n`);
|
|
155
|
+
const systemctlAvailable = !!which('systemctl');
|
|
156
|
+
if (options.unitOnly || !systemctlAvailable) {
|
|
157
|
+
if (!systemctlAvailable) {
|
|
158
|
+
process.stderr.write(' warn: systemctl not found — wrote the unit but skipped daemon-reload / enable.\n');
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
platform: 'systemd',
|
|
163
|
+
unitPath,
|
|
164
|
+
mode: isSystem ? 'system' : 'user',
|
|
165
|
+
enabled: false,
|
|
166
|
+
started: false,
|
|
167
|
+
notes: ['unit written; systemctl steps skipped'],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const runSystemctl = (args) => {
|
|
171
|
+
const cmd = isSystem ? ['sudo', 'systemctl', ...args] : ['systemctl', '--user', ...args];
|
|
172
|
+
try {
|
|
173
|
+
execFileSync(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
throw new Error(`${cmd.join(' ')} failed: ${err?.message ?? err}`);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
runSystemctl(['daemon-reload']);
|
|
180
|
+
runSystemctl(['enable', SERVICE_NAME]);
|
|
181
|
+
runSystemctl(['restart', SERVICE_NAME]);
|
|
182
|
+
process.stderr.write(`\n ✓ ${SERVICE_NAME} enabled and started\n\n`);
|
|
183
|
+
const notes = [];
|
|
184
|
+
if (!isSystem) {
|
|
185
|
+
notes.push(`User-mode service stops when you log out. To keep it running:\n` +
|
|
186
|
+
` sudo loginctl enable-linger ${user}`);
|
|
187
|
+
}
|
|
188
|
+
notes.push(`Inspect:\n` +
|
|
189
|
+
` ${isSystem ? 'sudo ' : ''}systemctl ${isSystem ? '' : '--user '}status ${SERVICE_NAME}\n` +
|
|
190
|
+
` ${isSystem ? 'sudo ' : ''}journalctl ${isSystem ? '' : '--user '}-u ${SERVICE_NAME} -f`);
|
|
191
|
+
notes.push(`Uninstall:\n` +
|
|
192
|
+
` awb-agent-manager service uninstall${isSystem ? ' --system' : ''}`);
|
|
193
|
+
for (const n of notes)
|
|
194
|
+
process.stderr.write(` • ${n}\n`);
|
|
195
|
+
return { ok: true, platform: 'systemd', unitPath, mode: isSystem ? 'system' : 'user', enabled: true, started: true, notes };
|
|
196
|
+
}
|
|
197
|
+
async function uninstallSystemd(options) {
|
|
198
|
+
const isSystem = !!options.system;
|
|
199
|
+
const unitPath = isSystem
|
|
200
|
+
? `/etc/systemd/system/${SERVICE_NAME}.service`
|
|
201
|
+
: `${homedir()}/.config/systemd/user/${SERVICE_NAME}.service`;
|
|
202
|
+
process.stderr.write(`\n awb-agent-manager — service uninstall (systemd ${isSystem ? 'system' : 'user'})\n\n`);
|
|
203
|
+
if (which('systemctl')) {
|
|
204
|
+
const runSystemctl = (args, allowFail = false) => {
|
|
205
|
+
const cmd = isSystem ? ['sudo', 'systemctl', ...args] : ['systemctl', '--user', ...args];
|
|
206
|
+
const r = spawnSync(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
207
|
+
if (r.status !== 0 && !allowFail) {
|
|
208
|
+
throw new Error(`${cmd.join(' ')} failed`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
runSystemctl(['stop', SERVICE_NAME], true);
|
|
212
|
+
runSystemctl(['disable', SERVICE_NAME], true);
|
|
213
|
+
}
|
|
214
|
+
removeFile(unitPath, isSystem);
|
|
215
|
+
if (existsSync(unitPath)) {
|
|
216
|
+
process.stderr.write(` warn: ${unitPath} still exists after removal attempt\n`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
process.stderr.write(` ✓ removed ${unitPath}\n`);
|
|
220
|
+
}
|
|
221
|
+
if (which('systemctl')) {
|
|
222
|
+
const cmd = isSystem ? ['sudo', 'systemctl', 'daemon-reload'] : ['systemctl', '--user', 'daemon-reload'];
|
|
223
|
+
spawnSync(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
224
|
+
}
|
|
225
|
+
process.stderr.write(' ✓ done\n');
|
|
226
|
+
}
|
|
227
|
+
function sysvinitScript(opts) {
|
|
228
|
+
const { execPath, nodeBin, user, envHome } = opts;
|
|
229
|
+
const envExport = envHome ? `export AWB_AGENT_MANAGER_HOME='${envHome}'\n` : '';
|
|
230
|
+
return `#!/bin/sh
|
|
231
|
+
### BEGIN INIT INFO
|
|
232
|
+
# Provides: ${SERVICE_NAME}
|
|
233
|
+
# Required-Start: $network $remote_fs
|
|
234
|
+
# Required-Stop: $network $remote_fs
|
|
235
|
+
# Default-Start: 2 3 4 5
|
|
236
|
+
# Default-Stop: 0 1 6
|
|
237
|
+
# Short-Description: AWB Agent Manager
|
|
238
|
+
### END INIT INFO
|
|
239
|
+
|
|
240
|
+
NAME=${SERVICE_NAME}
|
|
241
|
+
NODE_BIN='${nodeBin}'
|
|
242
|
+
EXEC_PATH='${execPath}'
|
|
243
|
+
RUN_USER='${user}'
|
|
244
|
+
PIDFILE="/var/run/$NAME.pid"
|
|
245
|
+
LOGFILE="/var/log/$NAME.log"
|
|
246
|
+
${envExport}
|
|
247
|
+
start() {
|
|
248
|
+
if [ -e "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
|
249
|
+
echo "$NAME already running (PID $(cat "$PIDFILE"))"
|
|
250
|
+
return 0
|
|
251
|
+
fi
|
|
252
|
+
echo "Starting $NAME"
|
|
253
|
+
if [ "$(id -u)" = "0" ] && [ -n "$RUN_USER" ] && [ "$RUN_USER" != "root" ]; then
|
|
254
|
+
su -s /bin/sh -c "nohup '$NODE_BIN' '$EXEC_PATH' >> '$LOGFILE' 2>&1 & echo \\$! > '$PIDFILE'" "$RUN_USER"
|
|
255
|
+
else
|
|
256
|
+
nohup "$NODE_BIN" "$EXEC_PATH" >> "$LOGFILE" 2>&1 &
|
|
257
|
+
echo $! > "$PIDFILE"
|
|
258
|
+
fi
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
stop() {
|
|
262
|
+
if [ ! -e "$PIDFILE" ]; then echo "$NAME not running"; return 0; fi
|
|
263
|
+
PID=$(cat "$PIDFILE")
|
|
264
|
+
echo "Stopping $NAME (PID $PID)"
|
|
265
|
+
kill "$PID" 2>/dev/null
|
|
266
|
+
rm -f "$PIDFILE"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
status() {
|
|
270
|
+
if [ -e "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
|
271
|
+
echo "$NAME running (PID $(cat "$PIDFILE"))"
|
|
272
|
+
return 0
|
|
273
|
+
fi
|
|
274
|
+
echo "$NAME stopped"
|
|
275
|
+
return 1
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "$1" in
|
|
279
|
+
start) start ;;
|
|
280
|
+
stop) stop ;;
|
|
281
|
+
restart) stop; sleep 1; start ;;
|
|
282
|
+
status) status ;;
|
|
283
|
+
*) echo "Usage: $0 {start|stop|restart|status}"; exit 1 ;;
|
|
284
|
+
esac
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
async function installSysvinit(options, flavor) {
|
|
288
|
+
const execPath = detectExecPath(options.execPath);
|
|
289
|
+
const nodeBin = detectNode();
|
|
290
|
+
const user = userInfo().username;
|
|
291
|
+
if (!existsSync(execPath)) {
|
|
292
|
+
throw new Error(`dist/main.js not found at ${execPath} — run \`npm run build\` first, ` +
|
|
293
|
+
`or pass --exec-path <path>.`);
|
|
294
|
+
}
|
|
295
|
+
const isSystem = true;
|
|
296
|
+
const unitPath = flavor === 'synology'
|
|
297
|
+
? `/usr/local/etc/rc.d/${SERVICE_NAME}.sh`
|
|
298
|
+
: `/etc/init.d/${SERVICE_NAME}`;
|
|
299
|
+
const script = sysvinitScript({ execPath, nodeBin, user, envHome: process.env.AWB_AGENT_MANAGER_HOME });
|
|
300
|
+
logHeader({ platform: flavor, mode: 'system', nodeBin, execPath, unitPath, user });
|
|
301
|
+
if (options.dryRun) {
|
|
302
|
+
process.stderr.write(' --dry-run: would write the following script (skipping registration):\n\n');
|
|
303
|
+
process.stderr.write(script.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
304
|
+
process.stderr.write('\n');
|
|
305
|
+
return { ok: true, platform: flavor, unitPath, mode: 'system', enabled: false, started: false, notes: ['dry-run'] };
|
|
306
|
+
}
|
|
307
|
+
if (process.getuid && process.getuid() === 0) {
|
|
308
|
+
writeFileWithDir(unitPath, script, 0o755);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
writeWithSudo(unitPath, script, 0o755);
|
|
312
|
+
}
|
|
313
|
+
process.stderr.write(` ✓ wrote ${unitPath}\n`);
|
|
314
|
+
if (options.unitOnly) {
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
platform: flavor,
|
|
318
|
+
unitPath,
|
|
319
|
+
mode: 'system',
|
|
320
|
+
enabled: false,
|
|
321
|
+
started: false,
|
|
322
|
+
notes: ['script written; boot registration skipped'],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const notes = [];
|
|
326
|
+
let enabled = false;
|
|
327
|
+
let started = false;
|
|
328
|
+
if (flavor === 'synology') {
|
|
329
|
+
enabled = true;
|
|
330
|
+
const r = spawnSync('sudo', [unitPath, 'start'], { stdio: 'inherit' });
|
|
331
|
+
started = r.status === 0;
|
|
332
|
+
if (!started)
|
|
333
|
+
notes.push(`Failed to start: run \`sudo ${unitPath} start\` manually.`);
|
|
334
|
+
notes.push(`Inspect:\n sudo ${unitPath} status\n tail -f /var/log/${SERVICE_NAME}.log`);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
if (which('update-rc.d')) {
|
|
338
|
+
const r = spawnSync('sudo', ['update-rc.d', SERVICE_NAME, 'defaults'], { stdio: 'inherit' });
|
|
339
|
+
enabled = r.status === 0;
|
|
340
|
+
if (!enabled)
|
|
341
|
+
notes.push(`update-rc.d failed: run \`sudo update-rc.d ${SERVICE_NAME} defaults\` manually.`);
|
|
342
|
+
}
|
|
343
|
+
else if (which('chkconfig')) {
|
|
344
|
+
const r1 = spawnSync('sudo', ['chkconfig', '--add', SERVICE_NAME], { stdio: 'inherit' });
|
|
345
|
+
const r2 = spawnSync('sudo', ['chkconfig', SERVICE_NAME, 'on'], { stdio: 'inherit' });
|
|
346
|
+
enabled = r1.status === 0 && r2.status === 0;
|
|
347
|
+
if (!enabled)
|
|
348
|
+
notes.push(`chkconfig failed: run \`sudo chkconfig --add ${SERVICE_NAME} && sudo chkconfig ${SERVICE_NAME} on\` manually.`);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
notes.push(`No update-rc.d / chkconfig found — script is in place but won't auto-start at boot. Register it manually.`);
|
|
352
|
+
}
|
|
353
|
+
const r = spawnSync('sudo', [unitPath, 'start'], { stdio: 'inherit' });
|
|
354
|
+
started = r.status === 0;
|
|
355
|
+
if (!started)
|
|
356
|
+
notes.push(`Failed to start: run \`sudo ${unitPath} start\` manually.`);
|
|
357
|
+
notes.push(`Inspect:\n sudo ${unitPath} status\n tail -f /var/log/${SERVICE_NAME}.log`);
|
|
358
|
+
}
|
|
359
|
+
notes.push(`Uninstall:\n awb-agent-manager service uninstall --platform ${flavor}`);
|
|
360
|
+
process.stderr.write(`\n ✓ ${SERVICE_NAME} ${enabled ? 'enabled and ' : ''}${started ? 'started' : 'install complete'}\n\n`);
|
|
361
|
+
for (const n of notes)
|
|
362
|
+
process.stderr.write(` • ${n}\n`);
|
|
363
|
+
return { ok: true, platform: flavor, unitPath, mode: 'system', enabled, started, notes };
|
|
364
|
+
}
|
|
365
|
+
async function uninstallSysvinit(options, flavor) {
|
|
366
|
+
const unitPath = flavor === 'synology'
|
|
367
|
+
? `/usr/local/etc/rc.d/${SERVICE_NAME}.sh`
|
|
368
|
+
: `/etc/init.d/${SERVICE_NAME}`;
|
|
369
|
+
process.stderr.write(`\n awb-agent-manager — service uninstall (${flavor})\n\n`);
|
|
370
|
+
if (existsSync(unitPath)) {
|
|
371
|
+
spawnSync('sudo', [unitPath, 'stop'], { stdio: 'inherit' });
|
|
372
|
+
}
|
|
373
|
+
if (flavor === 'sysvinit') {
|
|
374
|
+
if (which('update-rc.d')) {
|
|
375
|
+
spawnSync('sudo', ['update-rc.d', '-f', SERVICE_NAME, 'remove'], { stdio: 'inherit' });
|
|
376
|
+
}
|
|
377
|
+
else if (which('chkconfig')) {
|
|
378
|
+
spawnSync('sudo', ['chkconfig', '--del', SERVICE_NAME], { stdio: 'inherit' });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
removeFile(unitPath, true);
|
|
382
|
+
if (existsSync(unitPath)) {
|
|
383
|
+
process.stderr.write(` warn: ${unitPath} still exists after removal attempt\n`);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
process.stderr.write(` ✓ removed ${unitPath}\n`);
|
|
387
|
+
}
|
|
388
|
+
process.stderr.write(' ✓ done\n');
|
|
389
|
+
}
|
|
390
|
+
function launchdPlist(opts) {
|
|
391
|
+
const { execPath, nodeBin } = opts;
|
|
392
|
+
const envHome = process.env.AWB_AGENT_MANAGER_HOME;
|
|
393
|
+
const envBlock = envHome
|
|
394
|
+
? ` <key>EnvironmentVariables</key>\n <dict>\n <key>AWB_AGENT_MANAGER_HOME</key>\n <string>${envHome}</string>\n </dict>\n`
|
|
395
|
+
: '';
|
|
396
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
397
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
398
|
+
<plist version="1.0">
|
|
399
|
+
<dict>
|
|
400
|
+
<key>Label</key>
|
|
401
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
402
|
+
<key>ProgramArguments</key>
|
|
403
|
+
<array>
|
|
404
|
+
<string>${nodeBin}</string>
|
|
405
|
+
<string>${execPath}</string>
|
|
406
|
+
</array>
|
|
407
|
+
<key>RunAtLoad</key>
|
|
408
|
+
<true/>
|
|
409
|
+
<key>KeepAlive</key>
|
|
410
|
+
<dict>
|
|
411
|
+
<key>SuccessfulExit</key>
|
|
412
|
+
<false/>
|
|
413
|
+
</dict>
|
|
414
|
+
<key>StandardOutPath</key>
|
|
415
|
+
<string>/tmp/${SERVICE_NAME}.log</string>
|
|
416
|
+
<key>StandardErrorPath</key>
|
|
417
|
+
<string>/tmp/${SERVICE_NAME}.err.log</string>
|
|
418
|
+
${envBlock} </dict>
|
|
419
|
+
</plist>
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
async function installLaunchd(options) {
|
|
423
|
+
const isSystem = !!options.system;
|
|
424
|
+
const execPath = detectExecPath(options.execPath);
|
|
425
|
+
const nodeBin = detectNode();
|
|
426
|
+
const user = userInfo().username;
|
|
427
|
+
if (!existsSync(execPath)) {
|
|
428
|
+
throw new Error(`dist/main.js not found at ${execPath} — run \`npm run build\` first, ` +
|
|
429
|
+
`or pass --exec-path <path>.`);
|
|
430
|
+
}
|
|
431
|
+
const plist = launchdPlist({ execPath, nodeBin, isSystem });
|
|
432
|
+
const unitPath = isSystem
|
|
433
|
+
? `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`
|
|
434
|
+
: `${homedir()}/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`;
|
|
435
|
+
logHeader({ platform: 'launchd', mode: isSystem ? 'system' : 'user', nodeBin, execPath, unitPath, user });
|
|
436
|
+
if (options.dryRun) {
|
|
437
|
+
process.stderr.write(' --dry-run: would write the following plist (skipping launchctl):\n\n');
|
|
438
|
+
process.stderr.write(plist.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
439
|
+
process.stderr.write('\n');
|
|
440
|
+
return { ok: true, platform: 'launchd', unitPath, mode: isSystem ? 'system' : 'user', enabled: false, started: false, notes: ['dry-run'] };
|
|
441
|
+
}
|
|
442
|
+
if (isSystem)
|
|
443
|
+
writeWithSudo(unitPath, plist, 0o644);
|
|
444
|
+
else
|
|
445
|
+
writeFileWithDir(unitPath, plist, 0o644);
|
|
446
|
+
process.stderr.write(` ✓ wrote ${unitPath}\n`);
|
|
447
|
+
if (options.unitOnly || !which('launchctl')) {
|
|
448
|
+
if (!which('launchctl')) {
|
|
449
|
+
process.stderr.write(' warn: launchctl not found — wrote the plist but skipped load.\n');
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
ok: true,
|
|
453
|
+
platform: 'launchd',
|
|
454
|
+
unitPath,
|
|
455
|
+
mode: isSystem ? 'system' : 'user',
|
|
456
|
+
enabled: false,
|
|
457
|
+
started: false,
|
|
458
|
+
notes: ['plist written; launchctl steps skipped'],
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const runLaunchctl = (args, allowFail = false) => {
|
|
462
|
+
const cmd = isSystem ? ['sudo', 'launchctl', ...args] : ['launchctl', ...args];
|
|
463
|
+
const r = spawnSync(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
464
|
+
if (r.status !== 0 && !allowFail) {
|
|
465
|
+
throw new Error(`${cmd.join(' ')} failed`);
|
|
466
|
+
}
|
|
467
|
+
return r.status === 0;
|
|
468
|
+
};
|
|
469
|
+
const domain = isSystem ? 'system' : `gui/${process.getuid?.() ?? ''}`;
|
|
470
|
+
runLaunchctl(['bootout', `${domain}/${LAUNCHD_LABEL}`], true);
|
|
471
|
+
const ok = runLaunchctl(['bootstrap', domain, unitPath], true);
|
|
472
|
+
if (!ok) {
|
|
473
|
+
runLaunchctl(['load', '-w', unitPath]);
|
|
474
|
+
}
|
|
475
|
+
process.stderr.write(`\n ✓ ${LAUNCHD_LABEL} loaded\n\n`);
|
|
476
|
+
const notes = [];
|
|
477
|
+
notes.push(`Inspect:\n` +
|
|
478
|
+
` ${isSystem ? 'sudo ' : ''}launchctl print ${domain}/${LAUNCHD_LABEL}\n` +
|
|
479
|
+
` tail -f /tmp/${SERVICE_NAME}.log /tmp/${SERVICE_NAME}.err.log`);
|
|
480
|
+
notes.push(`Uninstall:\n` +
|
|
481
|
+
` awb-agent-manager service uninstall${isSystem ? ' --system' : ''}`);
|
|
482
|
+
for (const n of notes)
|
|
483
|
+
process.stderr.write(` • ${n}\n`);
|
|
484
|
+
return { ok: true, platform: 'launchd', unitPath, mode: isSystem ? 'system' : 'user', enabled: true, started: true, notes };
|
|
485
|
+
}
|
|
486
|
+
async function uninstallLaunchd(options) {
|
|
487
|
+
const isSystem = !!options.system;
|
|
488
|
+
const unitPath = isSystem
|
|
489
|
+
? `/Library/LaunchDaemons/${LAUNCHD_LABEL}.plist`
|
|
490
|
+
: `${homedir()}/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`;
|
|
491
|
+
process.stderr.write(`\n awb-agent-manager — service uninstall (launchd ${isSystem ? 'system' : 'user'})\n\n`);
|
|
492
|
+
if (which('launchctl')) {
|
|
493
|
+
const domain = isSystem ? 'system' : `gui/${process.getuid?.() ?? ''}`;
|
|
494
|
+
const cmd = isSystem
|
|
495
|
+
? ['sudo', 'launchctl', 'bootout', `${domain}/${LAUNCHD_LABEL}`]
|
|
496
|
+
: ['launchctl', 'bootout', `${domain}/${LAUNCHD_LABEL}`];
|
|
497
|
+
spawnSync(cmd[0], cmd.slice(1), { stdio: 'inherit' });
|
|
498
|
+
if (existsSync(unitPath)) {
|
|
499
|
+
const cmd2 = isSystem
|
|
500
|
+
? ['sudo', 'launchctl', 'unload', '-w', unitPath]
|
|
501
|
+
: ['launchctl', 'unload', '-w', unitPath];
|
|
502
|
+
spawnSync(cmd2[0], cmd2.slice(1), { stdio: 'inherit' });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
removeFile(unitPath, isSystem);
|
|
506
|
+
if (existsSync(unitPath)) {
|
|
507
|
+
process.stderr.write(` warn: ${unitPath} still exists after removal attempt\n`);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
process.stderr.write(` ✓ removed ${unitPath}\n`);
|
|
511
|
+
}
|
|
512
|
+
process.stderr.write(' ✓ done\n');
|
|
513
|
+
}
|
|
514
|
+
function windowsTaskXml(opts) {
|
|
515
|
+
const { execPath, nodeBin, user, isSystem } = opts;
|
|
516
|
+
const trigger = isSystem
|
|
517
|
+
? ` <BootTrigger>\n <Enabled>true</Enabled>\n </BootTrigger>`
|
|
518
|
+
: ` <LogonTrigger>\n <Enabled>true</Enabled>\n </LogonTrigger>`;
|
|
519
|
+
const principalId = isSystem ? 'LocalSystem' : 'Author';
|
|
520
|
+
const userDomain = process.env.USERDOMAIN || process.env.COMPUTERNAME || '';
|
|
521
|
+
const userAccount = userDomain ? `${userDomain}\\${user}` : user;
|
|
522
|
+
const userBlock = isSystem
|
|
523
|
+
? ` <UserId>S-1-5-18</UserId>\n <RunLevel>HighestAvailable</RunLevel>`
|
|
524
|
+
: ` <UserId>${userAccount}</UserId>\n <LogonType>InteractiveToken</LogonType>\n <RunLevel>LeastPrivilege</RunLevel>`;
|
|
525
|
+
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
526
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
527
|
+
<RegistrationInfo>
|
|
528
|
+
<Description>AWB Agent Manager — standalone subagent runner</Description>
|
|
529
|
+
</RegistrationInfo>
|
|
530
|
+
<Triggers>
|
|
531
|
+
${trigger}
|
|
532
|
+
</Triggers>
|
|
533
|
+
<Principals>
|
|
534
|
+
<Principal id="${principalId}">
|
|
535
|
+
${userBlock}
|
|
536
|
+
</Principal>
|
|
537
|
+
</Principals>
|
|
538
|
+
<Settings>
|
|
539
|
+
<RestartOnFailure>
|
|
540
|
+
<Interval>PT1M</Interval>
|
|
541
|
+
<Count>10</Count>
|
|
542
|
+
</RestartOnFailure>
|
|
543
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
544
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
545
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
546
|
+
<AllowHardTerminate>true</AllowHardTerminate>
|
|
547
|
+
<StartWhenAvailable>true</StartWhenAvailable>
|
|
548
|
+
<Hidden>false</Hidden>
|
|
549
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
550
|
+
</Settings>
|
|
551
|
+
<Actions Context="${principalId}">
|
|
552
|
+
<Exec>
|
|
553
|
+
<Command>${nodeBin}</Command>
|
|
554
|
+
<Arguments>"${execPath}"</Arguments>
|
|
555
|
+
</Exec>
|
|
556
|
+
</Actions>
|
|
557
|
+
</Task>
|
|
558
|
+
`;
|
|
559
|
+
}
|
|
560
|
+
function utf16leWithBom(text) {
|
|
561
|
+
const bom = Buffer.from([0xff, 0xfe]);
|
|
562
|
+
const body = Buffer.from(text, 'utf16le');
|
|
563
|
+
return Buffer.concat([bom, body]);
|
|
564
|
+
}
|
|
565
|
+
async function installWindows(options) {
|
|
566
|
+
const isSystem = !!options.system;
|
|
567
|
+
const execPath = detectExecPath(options.execPath);
|
|
568
|
+
const nodeBin = detectNode();
|
|
569
|
+
const user = userInfo().username;
|
|
570
|
+
if (!existsSync(execPath)) {
|
|
571
|
+
throw new Error(`dist\\main.js not found at ${execPath} — run \`npm run build\` first, ` +
|
|
572
|
+
`or pass --exec-path <path>.`);
|
|
573
|
+
}
|
|
574
|
+
const taskName = SERVICE_NAME;
|
|
575
|
+
const xml = windowsTaskXml({ execPath, nodeBin, user, isSystem });
|
|
576
|
+
const xmlPath = join(tmpdir(), `${SERVICE_NAME}-task.xml`);
|
|
577
|
+
logHeader({ platform: 'windows', mode: isSystem ? 'system' : 'user', nodeBin, execPath, unitPath: `Task Scheduler\\${taskName}`, user });
|
|
578
|
+
if (options.dryRun) {
|
|
579
|
+
process.stderr.write(' --dry-run: would write the following task XML and run schtasks /Create:\n\n');
|
|
580
|
+
process.stderr.write(xml.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
581
|
+
process.stderr.write('\n');
|
|
582
|
+
return { ok: true, platform: 'windows', unitPath: xmlPath, mode: isSystem ? 'system' : 'user', enabled: false, started: false, notes: ['dry-run'] };
|
|
583
|
+
}
|
|
584
|
+
mkdirSync(dirname(xmlPath), { recursive: true });
|
|
585
|
+
writeFileSync(xmlPath, utf16leWithBom(xml));
|
|
586
|
+
process.stderr.write(` ✓ wrote ${xmlPath}\n`);
|
|
587
|
+
if (options.unitOnly || !which('schtasks')) {
|
|
588
|
+
if (!which('schtasks')) {
|
|
589
|
+
process.stderr.write(' warn: schtasks.exe not found — wrote the XML but skipped task registration.\n');
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
ok: true,
|
|
593
|
+
platform: 'windows',
|
|
594
|
+
unitPath: xmlPath,
|
|
595
|
+
mode: isSystem ? 'system' : 'user',
|
|
596
|
+
enabled: false,
|
|
597
|
+
started: false,
|
|
598
|
+
notes: ['XML written; schtasks /Create skipped'],
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
const createArgs = ['/Create', '/TN', taskName, '/XML', xmlPath, '/F'];
|
|
602
|
+
const createR = spawnSync('schtasks', createArgs, { stdio: 'inherit' });
|
|
603
|
+
if (createR.status !== 0) {
|
|
604
|
+
throw new Error(`schtasks /Create failed (exit ${createR.status}). ` +
|
|
605
|
+
(isSystem ? 'System mode requires running this from an elevated (Admin) PowerShell.' : ''));
|
|
606
|
+
}
|
|
607
|
+
const runR = spawnSync('schtasks', ['/Run', '/TN', taskName], { stdio: 'inherit' });
|
|
608
|
+
const started = runR.status === 0;
|
|
609
|
+
process.stderr.write(`\n ✓ Task '${taskName}' registered${started ? ' and started' : ''}\n\n`);
|
|
610
|
+
const notes = [];
|
|
611
|
+
notes.push(`Inspect:\n` +
|
|
612
|
+
` schtasks /Query /TN ${taskName} /V /FO LIST\n` +
|
|
613
|
+
` taskschd.msc (GUI — Task Scheduler Library → ${taskName})`);
|
|
614
|
+
notes.push(`Uninstall:\n` +
|
|
615
|
+
` awb-agent-manager service uninstall${isSystem ? ' --system' : ''}`);
|
|
616
|
+
if (!isSystem) {
|
|
617
|
+
notes.push(`User-mode task only fires at logon. For boot-time start, re-run with --system from an elevated shell.`);
|
|
618
|
+
}
|
|
619
|
+
for (const n of notes)
|
|
620
|
+
process.stderr.write(` • ${n}\n`);
|
|
621
|
+
return { ok: true, platform: 'windows', unitPath: xmlPath, mode: isSystem ? 'system' : 'user', enabled: true, started, notes };
|
|
622
|
+
}
|
|
623
|
+
async function uninstallWindows(options) {
|
|
624
|
+
const isSystem = !!options.system;
|
|
625
|
+
const taskName = SERVICE_NAME;
|
|
626
|
+
process.stderr.write(`\n awb-agent-manager — service uninstall (windows ${isSystem ? 'system' : 'user'})\n\n`);
|
|
627
|
+
if (which('schtasks')) {
|
|
628
|
+
const r = spawnSync('schtasks', ['/Delete', '/TN', taskName, '/F'], { stdio: 'inherit' });
|
|
629
|
+
if (r.status === 0) {
|
|
630
|
+
process.stderr.write(` ✓ deleted scheduled task '${taskName}'\n`);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
process.stderr.write(` (task '${taskName}' was not registered, or schtasks /Delete failed — exit ${r.status})\n`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
process.stderr.write(' warn: schtasks.exe not found — cannot remove the task.\n');
|
|
638
|
+
}
|
|
639
|
+
const xmlPath = join(tmpdir(), `${SERVICE_NAME}-task.xml`);
|
|
640
|
+
removeFile(xmlPath, false);
|
|
641
|
+
process.stderr.write(' ✓ done\n');
|
|
642
|
+
}
|
|
643
|
+
export async function installService(options = {}) {
|
|
644
|
+
const platform = resolvePlatform(options.platform);
|
|
645
|
+
switch (platform) {
|
|
646
|
+
case 'systemd':
|
|
647
|
+
return installSystemd(options);
|
|
648
|
+
case 'sysvinit':
|
|
649
|
+
return installSysvinit(options, 'sysvinit');
|
|
650
|
+
case 'synology':
|
|
651
|
+
return installSysvinit(options, 'synology');
|
|
652
|
+
case 'launchd':
|
|
653
|
+
return installLaunchd(options);
|
|
654
|
+
case 'windows':
|
|
655
|
+
return installWindows(options);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
export async function uninstallService(options = {}) {
|
|
659
|
+
const platform = resolvePlatform(options.platform);
|
|
660
|
+
switch (platform) {
|
|
661
|
+
case 'systemd':
|
|
662
|
+
return uninstallSystemd(options);
|
|
663
|
+
case 'sysvinit':
|
|
664
|
+
return uninstallSysvinit(options, 'sysvinit');
|
|
665
|
+
case 'synology':
|
|
666
|
+
return uninstallSysvinit(options, 'synology');
|
|
667
|
+
case 'launchd':
|
|
668
|
+
return uninstallLaunchd(options);
|
|
669
|
+
case 'windows':
|
|
670
|
+
return uninstallWindows(options);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=service-install.js.map
|