bs9 1.0.0 → 1.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/README.md +97 -9
- package/bin/bs9 +58 -7
- package/dist/bs9-064xs9r9. +148 -0
- package/dist/bs9-0gqcrp5t. +144 -0
- package/dist/bs9-nv7nseny. +197 -0
- package/dist/bs9-r6b9zpw0. +156 -0
- package/dist/bs9.js +2 -0
- package/package.json +3 -4
- package/src/commands/deps.ts +295 -0
- package/src/commands/profile.ts +338 -0
- package/src/commands/restart.ts +28 -3
- package/src/commands/start.ts +201 -21
- package/src/commands/status.ts +154 -109
- package/src/commands/stop.ts +31 -3
- package/src/commands/web.ts +29 -3
- package/src/database/pool.ts +335 -0
- package/src/loadbalancer/manager.ts +481 -0
- package/src/macos/launchd.ts +402 -0
- package/src/platform/detect.ts +137 -0
- package/src/windows/service.ts +391 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, writeFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { join, dirname } from "node:path";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
|
|
8
|
+
interface WindowsServiceConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
displayName: string;
|
|
11
|
+
description: string;
|
|
12
|
+
executable: string;
|
|
13
|
+
arguments: string[];
|
|
14
|
+
workingDirectory: string;
|
|
15
|
+
environment: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface WindowsServiceStatus {
|
|
19
|
+
name: string;
|
|
20
|
+
state: 'running' | 'stopped' | 'paused' | 'starting' | 'stopping';
|
|
21
|
+
startType: 'auto' | 'demand' | 'disabled';
|
|
22
|
+
processId?: number;
|
|
23
|
+
startTime?: Date;
|
|
24
|
+
description?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class WindowsServiceManager {
|
|
28
|
+
private configPath: string;
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
this.configPath = join(homedir(), '.bs9', 'windows-services.json');
|
|
32
|
+
this.ensureConfigDir();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private ensureConfigDir(): void {
|
|
36
|
+
const configDir = dirname(this.configPath);
|
|
37
|
+
if (!existsSync(configDir)) {
|
|
38
|
+
mkdirSync(configDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private loadConfigs(): Record<string, WindowsServiceConfig> {
|
|
43
|
+
try {
|
|
44
|
+
if (existsSync(this.configPath)) {
|
|
45
|
+
const content = require('fs').readFileSync(this.configPath, 'utf-8');
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.warn('Failed to load Windows service configs:', error);
|
|
50
|
+
}
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private saveConfigs(configs: Record<string, WindowsServiceConfig>): void {
|
|
55
|
+
try {
|
|
56
|
+
writeFileSync(this.configPath, JSON.stringify(configs, null, 2));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to save Windows service configs:', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private checkAdminPrivileges(): boolean {
|
|
63
|
+
try {
|
|
64
|
+
execSync('net session', { stdio: 'ignore' });
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private generateServiceScript(config: WindowsServiceConfig): string {
|
|
72
|
+
const envVars = Object.entries(config.environment)
|
|
73
|
+
.map(([key, value]) => `$env:${key}="${value}"`)
|
|
74
|
+
.join('\n');
|
|
75
|
+
|
|
76
|
+
const args = config.arguments.map(arg => `"${arg}"`).join(' ');
|
|
77
|
+
|
|
78
|
+
return `# PowerShell script for BS9 Windows Service
|
|
79
|
+
# Generated by BS9 Service Manager
|
|
80
|
+
|
|
81
|
+
${envVars}
|
|
82
|
+
|
|
83
|
+
$serviceName = "${config.name}"
|
|
84
|
+
$displayName = "${config.displayName}"
|
|
85
|
+
$description = "${config.description}"
|
|
86
|
+
$executable = "${config.executable}"
|
|
87
|
+
$arguments = "${args}"
|
|
88
|
+
$workingDirectory = "${config.workingDirectory}"
|
|
89
|
+
|
|
90
|
+
# Check if service exists
|
|
91
|
+
$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
|
|
92
|
+
|
|
93
|
+
if (-not $service) {
|
|
94
|
+
Write-Host "Creating Windows service: $serviceName"
|
|
95
|
+
|
|
96
|
+
# Create service
|
|
97
|
+
New-Service -Name $serviceName -DisplayName $displayName -Description $description -BinaryPathName "$executable $arguments" -StartupType Automatic -WorkingDirectory $workingDirectory
|
|
98
|
+
|
|
99
|
+
Write-Host "Service created successfully"
|
|
100
|
+
} else {
|
|
101
|
+
Write-Host "Service already exists: $serviceName"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Configure service recovery
|
|
105
|
+
sc.exe failure $serviceName reset= 86400 actions= restart/5000/restart/10000/restart/20000
|
|
106
|
+
|
|
107
|
+
Write-Host "Service configuration completed"
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async createService(config: WindowsServiceConfig): Promise<void> {
|
|
112
|
+
if (!this.checkAdminPrivileges()) {
|
|
113
|
+
throw new Error('Administrator privileges required to create Windows services');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const configs = this.loadConfigs();
|
|
117
|
+
configs[config.name] = config;
|
|
118
|
+
this.saveConfigs(configs);
|
|
119
|
+
|
|
120
|
+
// Generate PowerShell script
|
|
121
|
+
const scriptPath = join(homedir(), '.bs9', `${config.name}-setup.ps1`);
|
|
122
|
+
writeFileSync(scriptPath, this.generateServiceScript(config));
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
// Execute PowerShell script
|
|
126
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${scriptPath}"`, { stdio: 'inherit' });
|
|
127
|
+
console.log(`✅ Windows service '${config.name}' created successfully`);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error(`❌ Failed to create Windows service: ${error}`);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async startService(serviceName: string): Promise<void> {
|
|
135
|
+
if (!this.checkAdminPrivileges()) {
|
|
136
|
+
throw new Error('Administrator privileges required to start Windows services');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
execSync(`net start "${serviceName}"`, { stdio: 'inherit' });
|
|
141
|
+
console.log(`✅ Windows service '${serviceName}' started successfully`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(`❌ Failed to start Windows service: ${error}`);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async stopService(serviceName: string): Promise<void> {
|
|
149
|
+
if (!this.checkAdminPrivileges()) {
|
|
150
|
+
throw new Error('Administrator privileges required to stop Windows services');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
execSync(`net stop "${serviceName}"`, { stdio: 'inherit' });
|
|
155
|
+
console.log(`✅ Windows service '${serviceName}' stopped successfully`);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(`❌ Failed to stop Windows service: ${error}`);
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async deleteService(serviceName: string): Promise<void> {
|
|
163
|
+
if (!this.checkAdminPrivileges()) {
|
|
164
|
+
throw new Error('Administrator privileges required to delete Windows services');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// Stop service first
|
|
169
|
+
try {
|
|
170
|
+
await this.stopService(serviceName);
|
|
171
|
+
} catch {
|
|
172
|
+
// Service might already be stopped
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Delete service
|
|
176
|
+
execSync(`sc.exe delete "${serviceName}"`, { stdio: 'inherit' });
|
|
177
|
+
|
|
178
|
+
// Remove from config
|
|
179
|
+
const configs = this.loadConfigs();
|
|
180
|
+
delete configs[serviceName];
|
|
181
|
+
this.saveConfigs(configs);
|
|
182
|
+
|
|
183
|
+
console.log(`✅ Windows service '${serviceName}' deleted successfully`);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(`❌ Failed to delete Windows service: ${error}`);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getServiceStatus(serviceName: string): Promise<WindowsServiceStatus | null> {
|
|
191
|
+
try {
|
|
192
|
+
const output = execSync(`sc.exe query "${serviceName}"`, { encoding: 'utf-8' });
|
|
193
|
+
|
|
194
|
+
const status: WindowsServiceStatus = {
|
|
195
|
+
name: serviceName,
|
|
196
|
+
state: 'stopped',
|
|
197
|
+
startType: 'demand'
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// Parse output
|
|
201
|
+
const lines = output.split('\n');
|
|
202
|
+
for (const line of lines) {
|
|
203
|
+
if (line.includes('STATE')) {
|
|
204
|
+
if (line.includes('RUNNING')) status.state = 'running';
|
|
205
|
+
else if (line.includes('STOPPED')) status.state = 'stopped';
|
|
206
|
+
else if (line.includes('PAUSED')) status.state = 'paused';
|
|
207
|
+
else if (line.includes('STARTING')) status.state = 'starting';
|
|
208
|
+
else if (line.includes('STOPPING')) status.state = 'stopping';
|
|
209
|
+
}
|
|
210
|
+
if (line.includes('START_TYPE')) {
|
|
211
|
+
if (line.includes('AUTO_START')) status.startType = 'auto';
|
|
212
|
+
else if (line.includes('DEMAND_START')) status.startType = 'demand';
|
|
213
|
+
else if (line.includes('DISABLED')) status.startType = 'disabled';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return status;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async listServices(): Promise<WindowsServiceStatus[]> {
|
|
224
|
+
try {
|
|
225
|
+
const configs = this.loadConfigs();
|
|
226
|
+
const services: WindowsServiceStatus[] = [];
|
|
227
|
+
|
|
228
|
+
for (const serviceName of Object.keys(configs)) {
|
|
229
|
+
const status = await this.getServiceStatus(serviceName);
|
|
230
|
+
if (status) {
|
|
231
|
+
status.description = configs[serviceName].description;
|
|
232
|
+
services.push(status);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return services;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error('Failed to list Windows services:', error);
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async enableAutoStart(serviceName: string): Promise<void> {
|
|
244
|
+
if (!this.checkAdminPrivileges()) {
|
|
245
|
+
throw new Error('Administrator privileges required to configure Windows services');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
execSync(`sc.exe config "${serviceName}" start= auto`, { stdio: 'inherit' });
|
|
250
|
+
console.log(`✅ Windows service '${serviceName}' set to auto-start`);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error(`❌ Failed to configure auto-start: ${error}`);
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async disableAutoStart(serviceName: string): Promise<void> {
|
|
258
|
+
if (!this.checkAdminPrivileges()) {
|
|
259
|
+
throw new Error('Administrator privileges required to configure Windows services');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
execSync(`sc.exe config "${serviceName}" start= demand`, { stdio: 'inherit' });
|
|
264
|
+
console.log(`✅ Windows service '${serviceName}' set to manual start`);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error(`❌ Failed to configure auto-start: ${error}`);
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export async function windowsCommand(action: string, options: any): Promise<void> {
|
|
273
|
+
console.log('🪟 BS9 Windows Service Management');
|
|
274
|
+
console.log('='.repeat(80));
|
|
275
|
+
|
|
276
|
+
const manager = new WindowsServiceManager();
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
switch (action) {
|
|
280
|
+
case 'create':
|
|
281
|
+
if (!options.name || !options.file) {
|
|
282
|
+
console.error('❌ --name and --file are required for create action');
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const config: WindowsServiceConfig = {
|
|
287
|
+
name: options.name,
|
|
288
|
+
displayName: options.displayName || options.name,
|
|
289
|
+
description: options.description || `BS9 Service: ${options.name}`,
|
|
290
|
+
executable: options.file,
|
|
291
|
+
arguments: options.args || [],
|
|
292
|
+
workingDirectory: options.workingDir || process.cwd(),
|
|
293
|
+
environment: options.env ? JSON.parse(options.env) : {}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await manager.createService(config);
|
|
297
|
+
await manager.startService(options.name);
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
case 'start':
|
|
301
|
+
if (!options.name) {
|
|
302
|
+
console.error('❌ --name is required for start action');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
await manager.startService(options.name);
|
|
306
|
+
break;
|
|
307
|
+
|
|
308
|
+
case 'stop':
|
|
309
|
+
if (!options.name) {
|
|
310
|
+
console.error('❌ --name is required for stop action');
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
await manager.stopService(options.name);
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'restart':
|
|
317
|
+
if (!options.name) {
|
|
318
|
+
console.error('❌ --name is required for restart action');
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
await manager.stopService(options.name);
|
|
322
|
+
await manager.startService(options.name);
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case 'delete':
|
|
326
|
+
if (!options.name) {
|
|
327
|
+
console.error('❌ --name is required for delete action');
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
await manager.deleteService(options.name);
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'status':
|
|
334
|
+
if (options.name) {
|
|
335
|
+
const status = await manager.getServiceStatus(options.name);
|
|
336
|
+
if (status) {
|
|
337
|
+
console.log(`📊 Service Status: ${status.name}`);
|
|
338
|
+
console.log(` State: ${status.state}`);
|
|
339
|
+
console.log(` Start Type: ${status.startType}`);
|
|
340
|
+
if (status.description) console.log(` Description: ${status.description}`);
|
|
341
|
+
} else {
|
|
342
|
+
console.log(`❌ Service '${options.name}' not found`);
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
const services = await manager.listServices();
|
|
346
|
+
console.log('📋 BS9 Windows Services:');
|
|
347
|
+
console.log('-'.repeat(80));
|
|
348
|
+
console.log('SERVICE'.padEnd(25) + 'STATE'.padEnd(15) + 'START TYPE'.padEnd(15) + 'DESCRIPTION');
|
|
349
|
+
console.log('-'.repeat(80));
|
|
350
|
+
|
|
351
|
+
for (const service of services) {
|
|
352
|
+
console.log(
|
|
353
|
+
service.name.padEnd(25) +
|
|
354
|
+
service.state.padEnd(15) +
|
|
355
|
+
service.startType.padEnd(15) +
|
|
356
|
+
(service.description || '')
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (services.length === 0) {
|
|
361
|
+
console.log('No BS9 Windows services found.');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
|
|
366
|
+
case 'enable':
|
|
367
|
+
if (!options.name) {
|
|
368
|
+
console.error('❌ --name is required for enable action');
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
await manager.enableAutoStart(options.name);
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
case 'disable':
|
|
375
|
+
if (!options.name) {
|
|
376
|
+
console.error('❌ --name is required for disable action');
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
await manager.disableAutoStart(options.name);
|
|
380
|
+
break;
|
|
381
|
+
|
|
382
|
+
default:
|
|
383
|
+
console.error(`❌ Unknown action: ${action}`);
|
|
384
|
+
console.log('Available actions: create, start, stop, restart, delete, status, enable, disable');
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
} catch (error) {
|
|
388
|
+
console.error(`❌ Failed to ${action} Windows service: ${error}`);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
}
|