bs9 1.0.0 → 1.3.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.
@@ -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
+ }