agenshield 0.1.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/CHANGELOG.md +16 -0
- package/LICENSE +201 -0
- package/README.md +91 -0
- package/package.json +61 -0
- package/src/cli.d.ts +25 -0
- package/src/cli.d.ts.map +1 -0
- package/src/cli.js +78 -0
- package/src/cli.js.map +1 -0
- package/src/commands/daemon.d.ts +11 -0
- package/src/commands/daemon.d.ts.map +1 -0
- package/src/commands/daemon.js +107 -0
- package/src/commands/daemon.js.map +1 -0
- package/src/commands/dev.d.ts +15 -0
- package/src/commands/dev.d.ts.map +1 -0
- package/src/commands/dev.js +387 -0
- package/src/commands/dev.js.map +1 -0
- package/src/commands/doctor.d.ts +11 -0
- package/src/commands/doctor.d.ts.map +1 -0
- package/src/commands/doctor.js +129 -0
- package/src/commands/doctor.js.map +1 -0
- package/src/commands/index.d.ts +12 -0
- package/src/commands/index.d.ts.map +1 -0
- package/src/commands/index.js +12 -0
- package/src/commands/index.js.map +1 -0
- package/src/commands/setup.d.ts +13 -0
- package/src/commands/setup.d.ts.map +1 -0
- package/src/commands/setup.js +203 -0
- package/src/commands/setup.js.map +1 -0
- package/src/commands/status.d.ts +11 -0
- package/src/commands/status.d.ts.map +1 -0
- package/src/commands/status.js +63 -0
- package/src/commands/status.js.map +1 -0
- package/src/commands/uninstall.d.ts +11 -0
- package/src/commands/uninstall.d.ts.map +1 -0
- package/src/commands/uninstall.js +164 -0
- package/src/commands/uninstall.js.map +1 -0
- package/src/detect/index.d.ts +9 -0
- package/src/detect/index.d.ts.map +1 -0
- package/src/detect/index.js +9 -0
- package/src/detect/index.js.map +1 -0
- package/src/dev-tui/DevApp.d.ts +13 -0
- package/src/dev-tui/DevApp.d.ts.map +1 -0
- package/src/dev-tui/DevApp.js +118 -0
- package/src/dev-tui/DevApp.js.map +1 -0
- package/src/dev-tui/DevSetupApp.d.ts +22 -0
- package/src/dev-tui/DevSetupApp.d.ts.map +1 -0
- package/src/dev-tui/DevSetupApp.js +407 -0
- package/src/dev-tui/DevSetupApp.js.map +1 -0
- package/src/dev-tui/components/ActionMenu.d.ts +11 -0
- package/src/dev-tui/components/ActionMenu.d.ts.map +1 -0
- package/src/dev-tui/components/ActionMenu.js +25 -0
- package/src/dev-tui/components/ActionMenu.js.map +1 -0
- package/src/dev-tui/components/ActionResult.d.ts +12 -0
- package/src/dev-tui/components/ActionResult.d.ts.map +1 -0
- package/src/dev-tui/components/ActionResult.js +27 -0
- package/src/dev-tui/components/ActionResult.js.map +1 -0
- package/src/dev-tui/components/DevConfirm.d.ts +22 -0
- package/src/dev-tui/components/DevConfirm.d.ts.map +1 -0
- package/src/dev-tui/components/DevConfirm.js +73 -0
- package/src/dev-tui/components/DevConfirm.js.map +1 -0
- package/src/dev-tui/components/DevModeSelect.d.ts +12 -0
- package/src/dev-tui/components/DevModeSelect.d.ts.map +1 -0
- package/src/dev-tui/components/DevModeSelect.js +69 -0
- package/src/dev-tui/components/DevModeSelect.js.map +1 -0
- package/src/dev-tui/components/LogViewer.d.ts +11 -0
- package/src/dev-tui/components/LogViewer.d.ts.map +1 -0
- package/src/dev-tui/components/LogViewer.js +18 -0
- package/src/dev-tui/components/LogViewer.js.map +1 -0
- package/src/dev-tui/components/PathPrompt.d.ts +13 -0
- package/src/dev-tui/components/PathPrompt.d.ts.map +1 -0
- package/src/dev-tui/components/PathPrompt.js +48 -0
- package/src/dev-tui/components/PathPrompt.js.map +1 -0
- package/src/dev-tui/components/StatusBar.d.ts +13 -0
- package/src/dev-tui/components/StatusBar.d.ts.map +1 -0
- package/src/dev-tui/components/StatusBar.js +36 -0
- package/src/dev-tui/components/StatusBar.js.map +1 -0
- package/src/dev-tui/index.d.ts +7 -0
- package/src/dev-tui/index.d.ts.map +1 -0
- package/src/dev-tui/index.js +5 -0
- package/src/dev-tui/index.js.map +1 -0
- package/src/dev-tui/runner.d.ts +17 -0
- package/src/dev-tui/runner.d.ts.map +1 -0
- package/src/dev-tui/runner.js +53 -0
- package/src/dev-tui/runner.js.map +1 -0
- package/src/dev-tui/state.d.ts +34 -0
- package/src/dev-tui/state.d.ts.map +1 -0
- package/src/dev-tui/state.js +77 -0
- package/src/dev-tui/state.js.map +1 -0
- package/src/index.d.ts +15 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +19 -0
- package/src/index.js.map +1 -0
- package/src/setup-server/index.d.ts +8 -0
- package/src/setup-server/index.d.ts.map +1 -0
- package/src/setup-server/index.js +7 -0
- package/src/setup-server/index.js.map +1 -0
- package/src/setup-server/routes.d.ts +22 -0
- package/src/setup-server/routes.d.ts.map +1 -0
- package/src/setup-server/routes.js +279 -0
- package/src/setup-server/routes.js.map +1 -0
- package/src/setup-server/server.d.ts +16 -0
- package/src/setup-server/server.d.ts.map +1 -0
- package/src/setup-server/server.js +125 -0
- package/src/setup-server/server.js.map +1 -0
- package/src/setup-server/sse.d.ts +31 -0
- package/src/setup-server/sse.d.ts.map +1 -0
- package/src/setup-server/sse.js +76 -0
- package/src/setup-server/sse.js.map +1 -0
- package/src/setup-server/static.d.ts +11 -0
- package/src/setup-server/static.d.ts.map +1 -0
- package/src/setup-server/static.js +33 -0
- package/src/setup-server/static.js.map +1 -0
- package/src/utils/daemon.d.ts +63 -0
- package/src/utils/daemon.d.ts.map +1 -0
- package/src/utils/daemon.js +377 -0
- package/src/utils/daemon.js.map +1 -0
- package/src/utils/find-test-harness.d.ts +5 -0
- package/src/utils/find-test-harness.d.ts.map +1 -0
- package/src/utils/find-test-harness.js +23 -0
- package/src/utils/find-test-harness.js.map +1 -0
- package/src/utils/index.d.ts +8 -0
- package/src/utils/index.d.ts.map +1 -0
- package/src/utils/index.js +8 -0
- package/src/utils/index.js.map +1 -0
- package/src/utils/privileges.d.ts +51 -0
- package/src/utils/privileges.d.ts.map +1 -0
- package/src/utils/privileges.js +125 -0
- package/src/utils/privileges.js.map +1 -0
- package/src/utils/sudo-env.d.ts +27 -0
- package/src/utils/sudo-env.d.ts.map +1 -0
- package/src/utils/sudo-env.js +63 -0
- package/src/utils/sudo-env.js.map +1 -0
- package/src/wizard/App.d.ts +6 -0
- package/src/wizard/App.d.ts.map +1 -0
- package/src/wizard/App.js +215 -0
- package/src/wizard/App.js.map +1 -0
- package/src/wizard/Uninstall.d.ts +16 -0
- package/src/wizard/Uninstall.d.ts.map +1 -0
- package/src/wizard/Uninstall.js +163 -0
- package/src/wizard/Uninstall.js.map +1 -0
- package/src/wizard/components/AdvancedConfig.d.ts +42 -0
- package/src/wizard/components/AdvancedConfig.d.ts.map +1 -0
- package/src/wizard/components/AdvancedConfig.js +131 -0
- package/src/wizard/components/AdvancedConfig.js.map +1 -0
- package/src/wizard/components/Confirm.d.ts +31 -0
- package/src/wizard/components/Confirm.d.ts.map +1 -0
- package/src/wizard/components/Confirm.js +148 -0
- package/src/wizard/components/Confirm.js.map +1 -0
- package/src/wizard/components/Header.d.ts +6 -0
- package/src/wizard/components/Header.d.ts.map +1 -0
- package/src/wizard/components/Header.js +11 -0
- package/src/wizard/components/Header.js.map +1 -0
- package/src/wizard/components/ModeSelect.d.ts +12 -0
- package/src/wizard/components/ModeSelect.d.ts.map +1 -0
- package/src/wizard/components/ModeSelect.js +74 -0
- package/src/wizard/components/ModeSelect.js.map +1 -0
- package/src/wizard/components/PasscodeSetup.d.ts +16 -0
- package/src/wizard/components/PasscodeSetup.d.ts.map +1 -0
- package/src/wizard/components/PasscodeSetup.js +119 -0
- package/src/wizard/components/PasscodeSetup.js.map +1 -0
- package/src/wizard/components/ProgressBar.d.ts +12 -0
- package/src/wizard/components/ProgressBar.d.ts.map +1 -0
- package/src/wizard/components/ProgressBar.js +18 -0
- package/src/wizard/components/ProgressBar.js.map +1 -0
- package/src/wizard/components/StepList.d.ts +12 -0
- package/src/wizard/components/StepList.d.ts.map +1 -0
- package/src/wizard/components/StepList.js +37 -0
- package/src/wizard/components/StepList.js.map +1 -0
- package/src/wizard/components/Summary.d.ts +12 -0
- package/src/wizard/components/Summary.d.ts.map +1 -0
- package/src/wizard/components/Summary.js +44 -0
- package/src/wizard/components/Summary.js.map +1 -0
- package/src/wizard/engine.d.ts +29 -0
- package/src/wizard/engine.d.ts.map +1 -0
- package/src/wizard/engine.js +866 -0
- package/src/wizard/engine.js.map +1 -0
- package/src/wizard/index.d.ts +8 -0
- package/src/wizard/index.d.ts.map +1 -0
- package/src/wizard/index.js +8 -0
- package/src/wizard/index.js.map +1 -0
- package/src/wizard/types.d.ts +190 -0
- package/src/wizard/types.d.ts.map +1 -0
- package/src/wizard/types.js +193 -0
- package/src/wizard/types.js.map +1 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon management utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for starting, stopping, and monitoring the AgenShield daemon.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn, execSync } from 'node:child_process';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { isSecretEnvVar } from '@agenshield/sandbox';
|
|
12
|
+
import { captureCallingUserEnv } from './sudo-env.js';
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
/**
|
|
16
|
+
* Daemon configuration
|
|
17
|
+
*/
|
|
18
|
+
export const DAEMON_CONFIG = {
|
|
19
|
+
PID_FILE: '/var/run/agenshield/agenshield.pid',
|
|
20
|
+
PORT: 5200,
|
|
21
|
+
HOST: '127.0.0.1', // Use IPv4 for actual connections (avoids IPv6 issues)
|
|
22
|
+
DISPLAY_HOST: 'localhost', // Use localhost for user-facing URLs
|
|
23
|
+
LOG_DIR: '/var/log/agenshield',
|
|
24
|
+
SOCKET_DIR: '/var/run/agenshield',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get the current daemon status
|
|
28
|
+
*/
|
|
29
|
+
export async function getDaemonStatus() {
|
|
30
|
+
// Check via HTTP health endpoint first
|
|
31
|
+
try {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
34
|
+
const response = await fetch(`http://${DAEMON_CONFIG.HOST}:${DAEMON_CONFIG.PORT}/api/health`, {
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
if (response.ok) {
|
|
39
|
+
const data = (await response.json());
|
|
40
|
+
const pid = findDaemonPid() || findDaemonPidByPort(DAEMON_CONFIG.PORT);
|
|
41
|
+
return {
|
|
42
|
+
running: true,
|
|
43
|
+
pid: pid ?? undefined,
|
|
44
|
+
port: DAEMON_CONFIG.PORT,
|
|
45
|
+
uptime: data.uptime,
|
|
46
|
+
url: `http://${DAEMON_CONFIG.HOST}:${DAEMON_CONFIG.PORT}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Daemon not responding via HTTP
|
|
52
|
+
}
|
|
53
|
+
// Check PID files (home dir + legacy location)
|
|
54
|
+
const pid = findDaemonPid();
|
|
55
|
+
if (pid) {
|
|
56
|
+
return { running: true, pid };
|
|
57
|
+
}
|
|
58
|
+
return { running: false };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Find the daemon executable path
|
|
62
|
+
*/
|
|
63
|
+
export function findDaemonExecutable() {
|
|
64
|
+
const searchPaths = [
|
|
65
|
+
// Relative to CLI dist
|
|
66
|
+
path.join(__dirname, '../../../shield-daemon/dist/main.js'),
|
|
67
|
+
// Installed location
|
|
68
|
+
'/opt/agenshield/bin/agenshield-daemon',
|
|
69
|
+
// Development location from project root
|
|
70
|
+
path.join(process.cwd(), 'libs/shield-daemon/dist/main.js'),
|
|
71
|
+
];
|
|
72
|
+
for (const p of searchPaths) {
|
|
73
|
+
if (fs.existsSync(p)) {
|
|
74
|
+
return p;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Find the daemon TypeScript source (for tsx fallback in dev)
|
|
81
|
+
*/
|
|
82
|
+
export function findDaemonSource() {
|
|
83
|
+
const searchPaths = [
|
|
84
|
+
path.join(__dirname, '../../../shield-daemon/src/main.ts'),
|
|
85
|
+
path.join(process.cwd(), 'libs/shield-daemon/src/main.ts'),
|
|
86
|
+
];
|
|
87
|
+
return searchPaths.find(p => fs.existsSync(p)) || null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find tsx binary for running TypeScript directly
|
|
91
|
+
*/
|
|
92
|
+
function findTsx() {
|
|
93
|
+
const searchPaths = [
|
|
94
|
+
path.join(process.cwd(), 'node_modules/.bin/tsx'),
|
|
95
|
+
];
|
|
96
|
+
return searchPaths.find(p => fs.existsSync(p)) || null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Find daemon PID from known PID file locations
|
|
100
|
+
*/
|
|
101
|
+
function findDaemonPid() {
|
|
102
|
+
const homePidPath = path.join(os.homedir(), '.agenshield', 'daemon.pid');
|
|
103
|
+
const legacyPidPath = DAEMON_CONFIG.PID_FILE;
|
|
104
|
+
const pidPaths = [homePidPath, legacyPidPath];
|
|
105
|
+
// When running as root via sudo, the daemon runs as SUDO_USER and writes
|
|
106
|
+
// its PID to ~sudouser/.agenshield/daemon.pid (not /var/root/).
|
|
107
|
+
const sudoUser = process.env['SUDO_USER'];
|
|
108
|
+
if (sudoUser) {
|
|
109
|
+
try {
|
|
110
|
+
const userHome = execSync(`eval echo ~${sudoUser}`, {
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
113
|
+
timeout: 3000,
|
|
114
|
+
}).trim();
|
|
115
|
+
pidPaths.splice(1, 0, path.join(userHome, '.agenshield', 'daemon.pid'));
|
|
116
|
+
}
|
|
117
|
+
catch { /* ignore */ }
|
|
118
|
+
}
|
|
119
|
+
for (const pidPath of pidPaths) {
|
|
120
|
+
try {
|
|
121
|
+
if (fs.existsSync(pidPath)) {
|
|
122
|
+
const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
|
|
123
|
+
if (!isNaN(pid)) {
|
|
124
|
+
process.kill(pid, 0); // throws if not running
|
|
125
|
+
return pid;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch { /* stale or inaccessible */ }
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Find daemon PID by checking which process is listening on the daemon port
|
|
135
|
+
*/
|
|
136
|
+
function findDaemonPidByPort(port) {
|
|
137
|
+
try {
|
|
138
|
+
const output = execSync(`lsof -ti :${port}`, {
|
|
139
|
+
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 3000,
|
|
140
|
+
}).trim();
|
|
141
|
+
if (output) {
|
|
142
|
+
const pid = parseInt(output.split('\n')[0], 10);
|
|
143
|
+
if (!isNaN(pid))
|
|
144
|
+
return pid;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch { /* lsof failed */ }
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Start the daemon
|
|
152
|
+
*/
|
|
153
|
+
export async function startDaemon(options = {}) {
|
|
154
|
+
// Check if already running
|
|
155
|
+
const status = await getDaemonStatus();
|
|
156
|
+
if (status.running) {
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
message: 'Daemon is already running',
|
|
160
|
+
pid: status.pid,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Find daemon executable
|
|
164
|
+
let daemonPath = findDaemonExecutable();
|
|
165
|
+
let runner = 'node';
|
|
166
|
+
if (!daemonPath) {
|
|
167
|
+
// Fallback: run from TypeScript source via tsx
|
|
168
|
+
const source = findDaemonSource();
|
|
169
|
+
const tsx = findTsx();
|
|
170
|
+
if (source && tsx) {
|
|
171
|
+
daemonPath = source;
|
|
172
|
+
runner = tsx;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
message: 'Daemon executable not found. Build first: npx nx build shield-daemon',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const env = {
|
|
182
|
+
...process.env,
|
|
183
|
+
AGENSHIELD_PORT: String(DAEMON_CONFIG.PORT),
|
|
184
|
+
AGENSHIELD_HOST: DAEMON_CONFIG.HOST,
|
|
185
|
+
};
|
|
186
|
+
const isUnderSudo = !!process.env['SUDO_USER'];
|
|
187
|
+
if (isUnderSudo) {
|
|
188
|
+
// Legacy path: running via "sudo agenshield daemon start"
|
|
189
|
+
const userEnv = captureCallingUserEnv();
|
|
190
|
+
if (userEnv) {
|
|
191
|
+
const secretNames = Object.keys(userEnv).filter(k => userEnv[k] && isSecretEnvVar(k));
|
|
192
|
+
if (secretNames.length > 0) {
|
|
193
|
+
env['AGENSHIELD_USER_SECRETS'] = secretNames.join(',');
|
|
194
|
+
}
|
|
195
|
+
if (userEnv['PATH']) {
|
|
196
|
+
env['PATH'] = userEnv['PATH'];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// User-mode: process.env already has correct PATH and secrets
|
|
202
|
+
const secretNames = Object.keys(process.env).filter(k => process.env[k] && isSecretEnvVar(k));
|
|
203
|
+
if (secretNames.length > 0) {
|
|
204
|
+
env['AGENSHIELD_USER_SECRETS'] = secretNames.join(',');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Ensure system dirs exist and are writable (daemon runs as root)
|
|
208
|
+
for (const dir of [DAEMON_CONFIG.LOG_DIR, DAEMON_CONFIG.SOCKET_DIR]) {
|
|
209
|
+
try {
|
|
210
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// May already exist
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Ensure user config dir
|
|
217
|
+
const configDir = path.join(os.homedir(), '.agenshield');
|
|
218
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
219
|
+
if (options.foreground) {
|
|
220
|
+
// Run in foreground (blocking)
|
|
221
|
+
const spawnOpts = {
|
|
222
|
+
stdio: 'inherit',
|
|
223
|
+
env,
|
|
224
|
+
};
|
|
225
|
+
const child = spawn(runner, [daemonPath], spawnOpts);
|
|
226
|
+
return new Promise((resolve) => {
|
|
227
|
+
child.on('exit', (code) => {
|
|
228
|
+
resolve({
|
|
229
|
+
success: code === 0,
|
|
230
|
+
message: code === 0 ? 'Daemon exited' : `Daemon exited with code ${code}`,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// Run in background
|
|
236
|
+
try {
|
|
237
|
+
// Try launchctl first (macOS preferred)
|
|
238
|
+
try {
|
|
239
|
+
execSync('launchctl list com.agenshield.daemon 2>/dev/null', { stdio: 'pipe' });
|
|
240
|
+
execSync('launchctl start com.agenshield.daemon');
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
message: 'Daemon started via launchd',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Not using launchd, fall back to nohup
|
|
248
|
+
}
|
|
249
|
+
let logDir = env['AGENSHIELD_LOG_DIR'] || DAEMON_CONFIG.LOG_DIR;
|
|
250
|
+
let logFile = path.join(logDir, 'daemon.log');
|
|
251
|
+
let logFd;
|
|
252
|
+
try {
|
|
253
|
+
logFd = fs.openSync(logFile, 'a');
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// File open failed — fall back to user-local log
|
|
257
|
+
logDir = path.join(os.homedir(), '.agenshield', 'logs');
|
|
258
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
259
|
+
logFile = path.join(logDir, 'daemon.log');
|
|
260
|
+
logFd = fs.openSync(logFile, 'a');
|
|
261
|
+
}
|
|
262
|
+
const bgSpawnOpts = {
|
|
263
|
+
detached: true,
|
|
264
|
+
stdio: ['ignore', logFd, logFd],
|
|
265
|
+
env,
|
|
266
|
+
};
|
|
267
|
+
const child = spawn(runner, [daemonPath], bgSpawnOpts);
|
|
268
|
+
child.unref();
|
|
269
|
+
// Write PID file
|
|
270
|
+
try {
|
|
271
|
+
fs.writeFileSync(DAEMON_CONFIG.PID_FILE, String(child.pid));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// May require sudo
|
|
275
|
+
}
|
|
276
|
+
// Wait a moment and verify
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
278
|
+
const newStatus = await getDaemonStatus();
|
|
279
|
+
if (newStatus.running) {
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
message: 'Daemon started',
|
|
283
|
+
pid: child.pid,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
message: `Daemon failed to start. Check logs at ${logFile}`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
return {
|
|
295
|
+
success: false,
|
|
296
|
+
message: `Failed to start daemon: ${err.message}`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Stop the daemon
|
|
302
|
+
*/
|
|
303
|
+
export async function stopDaemon() {
|
|
304
|
+
const status = await getDaemonStatus();
|
|
305
|
+
if (!status.running) {
|
|
306
|
+
return {
|
|
307
|
+
success: true,
|
|
308
|
+
message: 'Daemon is not running',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Try launchctl first
|
|
312
|
+
try {
|
|
313
|
+
execSync('launchctl list com.agenshield.daemon 2>/dev/null', { stdio: 'pipe' });
|
|
314
|
+
execSync('launchctl stop com.agenshield.daemon');
|
|
315
|
+
return {
|
|
316
|
+
success: true,
|
|
317
|
+
message: 'Daemon stopped via launchd',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// Not using launchd
|
|
322
|
+
}
|
|
323
|
+
// Try killing by PID
|
|
324
|
+
if (status.pid) {
|
|
325
|
+
try {
|
|
326
|
+
process.kill(status.pid, 'SIGTERM');
|
|
327
|
+
// Wait for process to exit
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
329
|
+
// Clean up PID file
|
|
330
|
+
try {
|
|
331
|
+
fs.unlinkSync(DAEMON_CONFIG.PID_FILE);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
// Ignore
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
success: true,
|
|
338
|
+
message: `Daemon stopped (PID ${status.pid})`,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
message: `Failed to stop daemon: ${err.message}`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Fallback: find PID by port
|
|
349
|
+
const portPid = findDaemonPidByPort(DAEMON_CONFIG.PORT);
|
|
350
|
+
if (portPid) {
|
|
351
|
+
try {
|
|
352
|
+
process.kill(portPid, 'SIGTERM');
|
|
353
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
354
|
+
return { success: true, message: `Daemon stopped (PID ${portPid}, via port lookup)` };
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
return { success: false, message: `Failed to stop daemon: ${err.message}` };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
message: 'Could not determine daemon PID. Try: pkill -f agenshield-daemon',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Restart the daemon
|
|
367
|
+
*/
|
|
368
|
+
export async function restartDaemon() {
|
|
369
|
+
const stopResult = await stopDaemon();
|
|
370
|
+
if (!stopResult.success && stopResult.message !== 'Daemon is not running') {
|
|
371
|
+
return stopResult;
|
|
372
|
+
}
|
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
374
|
+
const startResult = await startDaemon({ foreground: false });
|
|
375
|
+
return startResult;
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../src/utils/daemon.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Daemon management utilities\n *\n * Provides functions for starting, stopping, and monitoring the AgenShield daemon.\n */\n\nimport { spawn, execSync } from 'node:child_process';\nimport type { SpawnOptions } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { isSecretEnvVar } from '@agenshield/sandbox';\nimport { captureCallingUserEnv } from './sudo-env.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Daemon configuration\n */\nexport const DAEMON_CONFIG = {\n PID_FILE: '/var/run/agenshield/agenshield.pid',\n PORT: 5200,\n HOST: '127.0.0.1', // Use IPv4 for actual connections (avoids IPv6 issues)\n DISPLAY_HOST: 'localhost', // Use localhost for user-facing URLs\n LOG_DIR: '/var/log/agenshield',\n SOCKET_DIR: '/var/run/agenshield',\n};\n\n/**\n * Status of the daemon\n */\nexport interface DaemonStatus {\n running: boolean;\n pid?: number;\n port?: number;\n uptime?: string;\n url?: string;\n}\n\n/**\n * Get the current daemon status\n */\nexport async function getDaemonStatus(): Promise<DaemonStatus> {\n // Check via HTTP health endpoint first\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n\n const response = await fetch(`http://${DAEMON_CONFIG.HOST}:${DAEMON_CONFIG.PORT}/api/health`, {\n signal: controller.signal,\n });\n clearTimeout(timeout);\n\n if (response.ok) {\n const data = (await response.json()) as { uptime?: string };\n const pid = findDaemonPid() || findDaemonPidByPort(DAEMON_CONFIG.PORT);\n return {\n running: true,\n pid: pid ?? undefined,\n port: DAEMON_CONFIG.PORT,\n uptime: data.uptime,\n url: `http://${DAEMON_CONFIG.HOST}:${DAEMON_CONFIG.PORT}`,\n };\n }\n } catch {\n // Daemon not responding via HTTP\n }\n\n // Check PID files (home dir + legacy location)\n const pid = findDaemonPid();\n if (pid) {\n return { running: true, pid };\n }\n\n return { running: false };\n}\n\n/**\n * Find the daemon executable path\n */\nexport function findDaemonExecutable(): string | null {\n const searchPaths = [\n // Relative to CLI dist\n path.join(__dirname, '../../../shield-daemon/dist/main.js'),\n // Installed location\n '/opt/agenshield/bin/agenshield-daemon',\n // Development location from project root\n path.join(process.cwd(), 'libs/shield-daemon/dist/main.js'),\n ];\n\n for (const p of searchPaths) {\n if (fs.existsSync(p)) {\n return p;\n }\n }\n\n return null;\n}\n\n/**\n * Find the daemon TypeScript source (for tsx fallback in dev)\n */\nexport function findDaemonSource(): string | null {\n const searchPaths = [\n path.join(__dirname, '../../../shield-daemon/src/main.ts'),\n path.join(process.cwd(), 'libs/shield-daemon/src/main.ts'),\n ];\n return searchPaths.find(p => fs.existsSync(p)) || null;\n}\n\n/**\n * Find tsx binary for running TypeScript directly\n */\nfunction findTsx(): string | null {\n const searchPaths = [\n path.join(process.cwd(), 'node_modules/.bin/tsx'),\n ];\n return searchPaths.find(p => fs.existsSync(p)) || null;\n}\n\n/**\n * Find daemon PID from known PID file locations\n */\nfunction findDaemonPid(): number | null {\n const homePidPath = path.join(os.homedir(), '.agenshield', 'daemon.pid');\n const legacyPidPath = DAEMON_CONFIG.PID_FILE;\n const pidPaths = [homePidPath, legacyPidPath];\n\n // When running as root via sudo, the daemon runs as SUDO_USER and writes\n // its PID to ~sudouser/.agenshield/daemon.pid (not /var/root/).\n const sudoUser = process.env['SUDO_USER'];\n if (sudoUser) {\n try {\n const userHome = execSync(`eval echo ~${sudoUser}`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n timeout: 3000,\n }).trim();\n pidPaths.splice(1, 0, path.join(userHome, '.agenshield', 'daemon.pid'));\n } catch { /* ignore */ }\n }\n\n for (const pidPath of pidPaths) {\n try {\n if (fs.existsSync(pidPath)) {\n const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);\n if (!isNaN(pid)) {\n process.kill(pid, 0); // throws if not running\n return pid;\n }\n }\n } catch { /* stale or inaccessible */ }\n }\n return null;\n}\n\n/**\n * Find daemon PID by checking which process is listening on the daemon port\n */\nfunction findDaemonPidByPort(port: number): number | null {\n try {\n const output = execSync(`lsof -ti :${port}`, {\n encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 3000,\n }).trim();\n if (output) {\n const pid = parseInt(output.split('\\n')[0], 10);\n if (!isNaN(pid)) return pid;\n }\n } catch { /* lsof failed */ }\n return null;\n}\n\n/**\n * Start the daemon\n */\nexport async function startDaemon(options: { foreground?: boolean } = {}): Promise<{\n success: boolean;\n message: string;\n pid?: number;\n}> {\n // Check if already running\n const status = await getDaemonStatus();\n if (status.running) {\n return {\n success: true,\n message: 'Daemon is already running',\n pid: status.pid,\n };\n }\n\n // Find daemon executable\n let daemonPath = findDaemonExecutable();\n let runner = 'node';\n\n if (!daemonPath) {\n // Fallback: run from TypeScript source via tsx\n const source = findDaemonSource();\n const tsx = findTsx();\n if (source && tsx) {\n daemonPath = source;\n runner = tsx;\n } else {\n return {\n success: false,\n message: 'Daemon executable not found. Build first: npx nx build shield-daemon',\n };\n }\n }\n\n const env: Record<string, string | undefined> = {\n ...process.env,\n AGENSHIELD_PORT: String(DAEMON_CONFIG.PORT),\n AGENSHIELD_HOST: DAEMON_CONFIG.HOST,\n };\n\n const isUnderSudo = !!process.env['SUDO_USER'];\n\n if (isUnderSudo) {\n // Legacy path: running via \"sudo agenshield daemon start\"\n const userEnv = captureCallingUserEnv();\n if (userEnv) {\n const secretNames = Object.keys(userEnv).filter(k => userEnv[k] && isSecretEnvVar(k));\n if (secretNames.length > 0) {\n env['AGENSHIELD_USER_SECRETS'] = secretNames.join(',');\n }\n if (userEnv['PATH']) {\n env['PATH'] = userEnv['PATH'];\n }\n }\n } else {\n // User-mode: process.env already has correct PATH and secrets\n const secretNames = Object.keys(process.env).filter(\n k => process.env[k] && isSecretEnvVar(k)\n );\n if (secretNames.length > 0) {\n env['AGENSHIELD_USER_SECRETS'] = secretNames.join(',');\n }\n }\n\n // Ensure system dirs exist and are writable (daemon runs as root)\n for (const dir of [DAEMON_CONFIG.LOG_DIR, DAEMON_CONFIG.SOCKET_DIR]) {\n try {\n fs.mkdirSync(dir, { recursive: true });\n } catch {\n // May already exist\n }\n }\n\n // Ensure user config dir\n const configDir = path.join(os.homedir(), '.agenshield');\n fs.mkdirSync(configDir, { recursive: true });\n\n if (options.foreground) {\n // Run in foreground (blocking)\n const spawnOpts: SpawnOptions = {\n stdio: 'inherit',\n env,\n };\n const child = spawn(runner, [daemonPath], spawnOpts);\n\n return new Promise((resolve) => {\n child.on('exit', (code) => {\n resolve({\n success: code === 0,\n message: code === 0 ? 'Daemon exited' : `Daemon exited with code ${code}`,\n });\n });\n });\n }\n\n // Run in background\n try {\n // Try launchctl first (macOS preferred)\n try {\n execSync('launchctl list com.agenshield.daemon 2>/dev/null', { stdio: 'pipe' });\n execSync('launchctl start com.agenshield.daemon');\n return {\n success: true,\n message: 'Daemon started via launchd',\n };\n } catch {\n // Not using launchd, fall back to nohup\n }\n\n let logDir = env['AGENSHIELD_LOG_DIR'] || DAEMON_CONFIG.LOG_DIR;\n let logFile = path.join(logDir, 'daemon.log');\n let logFd: number;\n try {\n logFd = fs.openSync(logFile, 'a');\n } catch {\n // File open failed — fall back to user-local log\n logDir = path.join(os.homedir(), '.agenshield', 'logs');\n fs.mkdirSync(logDir, { recursive: true });\n logFile = path.join(logDir, 'daemon.log');\n logFd = fs.openSync(logFile, 'a');\n }\n\n const bgSpawnOpts: SpawnOptions = {\n detached: true,\n stdio: ['ignore', logFd, logFd],\n env,\n };\n const child = spawn(runner, [daemonPath], bgSpawnOpts);\n\n child.unref();\n\n // Write PID file\n try {\n fs.writeFileSync(DAEMON_CONFIG.PID_FILE, String(child.pid));\n } catch {\n // May require sudo\n }\n\n // Wait a moment and verify\n await new Promise((resolve) => setTimeout(resolve, 1000));\n const newStatus = await getDaemonStatus();\n\n if (newStatus.running) {\n return {\n success: true,\n message: 'Daemon started',\n pid: child.pid,\n };\n } else {\n return {\n success: false,\n message: `Daemon failed to start. Check logs at ${logFile}`,\n };\n }\n } catch (err) {\n return {\n success: false,\n message: `Failed to start daemon: ${(err as Error).message}`,\n };\n }\n}\n\n/**\n * Stop the daemon\n */\nexport async function stopDaemon(): Promise<{\n success: boolean;\n message: string;\n}> {\n const status = await getDaemonStatus();\n\n if (!status.running) {\n return {\n success: true,\n message: 'Daemon is not running',\n };\n }\n\n // Try launchctl first\n try {\n execSync('launchctl list com.agenshield.daemon 2>/dev/null', { stdio: 'pipe' });\n execSync('launchctl stop com.agenshield.daemon');\n return {\n success: true,\n message: 'Daemon stopped via launchd',\n };\n } catch {\n // Not using launchd\n }\n\n // Try killing by PID\n if (status.pid) {\n try {\n process.kill(status.pid, 'SIGTERM');\n\n // Wait for process to exit\n await new Promise((resolve) => setTimeout(resolve, 1000));\n\n // Clean up PID file\n try {\n fs.unlinkSync(DAEMON_CONFIG.PID_FILE);\n } catch {\n // Ignore\n }\n\n return {\n success: true,\n message: `Daemon stopped (PID ${status.pid})`,\n };\n } catch (err) {\n return {\n success: false,\n message: `Failed to stop daemon: ${(err as Error).message}`,\n };\n }\n }\n\n // Fallback: find PID by port\n const portPid = findDaemonPidByPort(DAEMON_CONFIG.PORT);\n if (portPid) {\n try {\n process.kill(portPid, 'SIGTERM');\n await new Promise(r => setTimeout(r, 1000));\n return { success: true, message: `Daemon stopped (PID ${portPid}, via port lookup)` };\n } catch (err) {\n return { success: false, message: `Failed to stop daemon: ${(err as Error).message}` };\n }\n }\n\n return {\n success: false,\n message: 'Could not determine daemon PID. Try: pkill -f agenshield-daemon',\n };\n}\n\n/**\n * Restart the daemon\n */\nexport async function restartDaemon(): Promise<{\n success: boolean;\n message: string;\n}> {\n const stopResult = await stopDaemon();\n if (!stopResult.success && stopResult.message !== 'Daemon is not running') {\n return stopResult;\n }\n\n await new Promise((resolve) => setTimeout(resolve, 500));\n\n const startResult = await startDaemon({ foreground: false });\n return startResult;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-test-harness.d.ts","sourceRoot":"","sources":["../../../src/utils/find-test-harness.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAe/C"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate the test harness binary path
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
export function findTestHarness() {
|
|
10
|
+
const searchPaths = [
|
|
11
|
+
// Development location from project root
|
|
12
|
+
path.join(process.cwd(), 'tools/test-harness/bin/dummy-openclaw.js'),
|
|
13
|
+
// Relative to CLI dist
|
|
14
|
+
path.join(__dirname, '../../../../tools/test-harness/bin/dummy-openclaw.js'),
|
|
15
|
+
];
|
|
16
|
+
for (const p of searchPaths) {
|
|
17
|
+
if (fs.existsSync(p)) {
|
|
18
|
+
return path.resolve(p);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=find-test-harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-test-harness.js","sourceRoot":"","sources":["../../../src/utils/find-test-harness.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,MAAM,UAAU,eAAe;IAC7B,MAAM,WAAW,GAAG;QAClB,yCAAyC;QACzC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,0CAA0C,CAAC;QACpE,uBAAuB;QACvB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sDAAsD,CAAC;KAC7E,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * Locate the test harness binary path\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport function findTestHarness(): string | null {\n const searchPaths = [\n // Development location from project root\n path.join(process.cwd(), 'tools/test-harness/bin/dummy-openclaw.js'),\n // Relative to CLI dist\n path.join(__dirname, '../../../../tools/test-harness/bin/dummy-openclaw.js'),\n ];\n\n for (const p of searchPaths) {\n if (fs.existsSync(p)) {\n return path.resolve(p);\n }\n }\n\n return null;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC","sourcesContent":["/**\n * CLI Utilities\n */\n\nexport * from './privileges.js';\nexport * from './daemon.js';\nexport * from './find-test-harness.js';\nexport * from './sudo-env.js';\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privilege detection utilities
|
|
3
|
+
*
|
|
4
|
+
* Detects user privileges and provides utilities for privilege-related operations.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Information about the current user's privileges
|
|
8
|
+
*/
|
|
9
|
+
export interface PrivilegeInfo {
|
|
10
|
+
/** Whether running as root (UID 0) */
|
|
11
|
+
isRoot: boolean;
|
|
12
|
+
/** Current user ID */
|
|
13
|
+
uid: number;
|
|
14
|
+
/** Current group ID */
|
|
15
|
+
gid: number;
|
|
16
|
+
/** Current username */
|
|
17
|
+
username: string;
|
|
18
|
+
/** Whether the user can use sudo */
|
|
19
|
+
canSudo: boolean;
|
|
20
|
+
/** Whether sudo can be used without a password */
|
|
21
|
+
sudoNoPassword: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Detect the current user's privileges
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectPrivileges(): PrivilegeInfo;
|
|
27
|
+
/**
|
|
28
|
+
* Ensure sudo credentials are cached so subsequent `sudo` child-process
|
|
29
|
+
* calls succeed without a TTY prompt.
|
|
30
|
+
* Call this once before a batch of privileged operations.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureSudoAccess(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Start a keepalive interval that refreshes sudo credentials every 2 minutes
|
|
35
|
+
* so the 5-minute sudo cache doesn't expire during long-running setup phases.
|
|
36
|
+
*/
|
|
37
|
+
export declare function startSudoKeepalive(): NodeJS.Timeout;
|
|
38
|
+
/**
|
|
39
|
+
* Check if a command requires root privileges
|
|
40
|
+
*/
|
|
41
|
+
export declare function requiresRoot(command: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Print a warning about missing privileges
|
|
44
|
+
*/
|
|
45
|
+
export declare function printPrivilegeWarning(command: string, priv?: PrivilegeInfo): void;
|
|
46
|
+
/**
|
|
47
|
+
* Ensure the command is running with root privileges
|
|
48
|
+
* Exits the process if not running as root
|
|
49
|
+
*/
|
|
50
|
+
export declare function ensureRoot(command: string): void;
|
|
51
|
+
//# sourceMappingURL=privileges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privileges.d.ts","sourceRoot":"","sources":["../../../src/utils/privileges.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAiChD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAmBvC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,OAAO,CAInD;AAQD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,IAAI,CAoBjF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMhD"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Privilege detection utilities
|
|
3
|
+
*
|
|
4
|
+
* Detects user privileges and provides utilities for privilege-related operations.
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
/**
|
|
9
|
+
* Detect the current user's privileges
|
|
10
|
+
*/
|
|
11
|
+
export function detectPrivileges() {
|
|
12
|
+
const uid = process.getuid?.() ?? -1;
|
|
13
|
+
const gid = process.getgid?.() ?? -1;
|
|
14
|
+
const username = os.userInfo().username;
|
|
15
|
+
const isRoot = uid === 0;
|
|
16
|
+
// Check if user can use sudo
|
|
17
|
+
let canSudo = false;
|
|
18
|
+
let sudoNoPassword = false;
|
|
19
|
+
if (!isRoot) {
|
|
20
|
+
try {
|
|
21
|
+
// Check if user is in admin/sudo group
|
|
22
|
+
const groups = execSync('groups', { encoding: 'utf8' });
|
|
23
|
+
canSudo = groups.includes('admin') || groups.includes('wheel') || groups.includes('sudo');
|
|
24
|
+
// Check if SUDO_ASKPASS is set
|
|
25
|
+
if (process.env['SUDO_ASKPASS']) {
|
|
26
|
+
sudoNoPassword = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Ignore errors
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
isRoot,
|
|
35
|
+
uid,
|
|
36
|
+
gid,
|
|
37
|
+
username,
|
|
38
|
+
canSudo,
|
|
39
|
+
sudoNoPassword,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ensure sudo credentials are cached so subsequent `sudo` child-process
|
|
44
|
+
* calls succeed without a TTY prompt.
|
|
45
|
+
* Call this once before a batch of privileged operations.
|
|
46
|
+
*/
|
|
47
|
+
export function ensureSudoAccess() {
|
|
48
|
+
const priv = detectPrivileges();
|
|
49
|
+
if (priv.isRoot)
|
|
50
|
+
return; // Already root, no need
|
|
51
|
+
// Check if sudo credentials are already cached (non-interactive check)
|
|
52
|
+
try {
|
|
53
|
+
execSync('sudo -n true', { stdio: 'pipe', timeout: 5_000 });
|
|
54
|
+
return; // Credentials already cached, no prompt needed
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Credentials not cached — need to prompt
|
|
58
|
+
}
|
|
59
|
+
console.log('\nThis operation requires administrator privileges.\n');
|
|
60
|
+
try {
|
|
61
|
+
execSync('sudo -v', { stdio: 'inherit', timeout: 60_000 });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.error('Failed to obtain sudo access.');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Start a keepalive interval that refreshes sudo credentials every 2 minutes
|
|
70
|
+
* so the 5-minute sudo cache doesn't expire during long-running setup phases.
|
|
71
|
+
*/
|
|
72
|
+
export function startSudoKeepalive() {
|
|
73
|
+
return setInterval(() => {
|
|
74
|
+
try {
|
|
75
|
+
execSync('sudo -v', { stdio: 'pipe', timeout: 2000 });
|
|
76
|
+
}
|
|
77
|
+
catch { /* ignore */ }
|
|
78
|
+
}, 120_000);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Commands that require root privileges
|
|
82
|
+
* (Empty — no command requires root upfront anymore; sudo is requested on demand)
|
|
83
|
+
*/
|
|
84
|
+
const ROOT_COMMANDS = [];
|
|
85
|
+
/**
|
|
86
|
+
* Check if a command requires root privileges
|
|
87
|
+
*/
|
|
88
|
+
export function requiresRoot(command) {
|
|
89
|
+
return ROOT_COMMANDS.some((c) => command.startsWith(c));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Print a warning about missing privileges
|
|
93
|
+
*/
|
|
94
|
+
export function printPrivilegeWarning(command, priv) {
|
|
95
|
+
const p = priv ?? detectPrivileges();
|
|
96
|
+
console.log('\x1b[33m⚠ Warning: This command requires elevated privileges.\x1b[0m');
|
|
97
|
+
console.log('');
|
|
98
|
+
console.log('You are currently running as:');
|
|
99
|
+
console.log(` User: ${p.username} (UID: ${p.uid})`);
|
|
100
|
+
console.log('');
|
|
101
|
+
if (p.canSudo) {
|
|
102
|
+
console.log('Run with sudo:');
|
|
103
|
+
console.log(` \x1b[36msudo agenshield ${command}\x1b[0m`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.log('You need administrator privileges. Either:');
|
|
107
|
+
console.log(' 1. Run as root user');
|
|
108
|
+
console.log(' 2. Add your user to the admin group');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(` \x1b[36msudo agenshield ${command}\x1b[0m`);
|
|
111
|
+
}
|
|
112
|
+
console.log('');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Ensure the command is running with root privileges
|
|
116
|
+
* Exits the process if not running as root
|
|
117
|
+
*/
|
|
118
|
+
export function ensureRoot(command) {
|
|
119
|
+
const priv = detectPrivileges();
|
|
120
|
+
if (!priv.isRoot) {
|
|
121
|
+
printPrivilegeWarning(command, priv);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=privileges.js.map
|