bs9 1.3.8 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # BS9 (Bun Sentinel 9) 🚀
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Version](https://img.shields.io/badge/version-1.3.7-blue.svg)](https://github.com/xarhang/bs9)
4
+ [![Version](https://img.shields.io/badge/version-1.3.9-blue.svg)](https://github.com/xarhang/bs9)
5
5
  [![Security](https://img.shields.io/badge/security-Enterprise-green.svg)](SECURITY.md)
6
6
  [![Production Ready](https://img.shields.io/badge/production-Ready-brightgreen.svg)](PRODUCTION.md)
7
7
  [![Cross-Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey.svg)](https://github.com/bs9/bs9)
@@ -26,24 +26,25 @@ chmod +x ~/.local/bin/bs9
26
26
 
27
27
  ---
28
28
 
29
- ### Platform Support
30
-
31
- BS9 supports all major platforms with native service management:
29
+ ### 🌐 Platform Support
30
+ - **✅ Auto-Detection**: Automatically detects platform and creates directories
31
+ - **✅ Zero Configuration**: No manual setup required
32
+ - **✅ Cross-Platform**: Same commands work on all platforms
32
33
 
33
34
  #### 🐧 Linux
34
35
  - **Service Manager**: Systemd (user-mode)
35
36
  - **Features**: Advanced security hardening, resource limits, sandboxing
36
- - **Commands**: All standard commands available
37
+ - **Commands**: All 21 commands available
37
38
 
38
39
  #### 🍎 macOS
39
40
  - **Service Manager**: Launchd
40
41
  - **Features**: Native macOS integration, automatic recovery
41
- - **Commands**: Standard commands + `bs9 macos` for launchd management
42
+ - **Commands**: All commands + `bs9 macos` for launchd management
42
43
 
43
44
  #### đŸĒŸ Windows
44
45
  - **Service Manager**: Windows Services
45
46
  - **Features**: PowerShell automation, event log integration
46
- - **Commands**: Standard commands + `bs9 windows` for service management
47
+ - **Commands**: All commands + `bs9 windows` for service management
47
48
 
48
49
  ```bash
49
50
  # Check your platform
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bs9",
3
- "version": "1.3.8",
3
+ "version": "1.4.0",
4
4
  "description": "Bun Sentinel 9 - High-performance, non-root process manager for Bun",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { execSync } from "node:child_process";
13
13
  import { join } from "node:path";
14
- import { getPlatformInfo } from "../platform/detect.js";
14
+ import { getPlatformInfo, initializePlatformDirectories } from "../platform/detect.js";
15
15
 
16
16
  interface ResurrectOptions {
17
17
  all?: boolean;
@@ -28,6 +28,9 @@ function isValidServiceName(name: string): boolean {
28
28
  }
29
29
 
30
30
  export async function resurrectCommand(name: string, options: ResurrectOptions): Promise<void> {
31
+ // Initialize platform directories
32
+ initializePlatformDirectories();
33
+
31
34
  const platformInfo = getPlatformInfo();
32
35
 
33
36
  // Handle resurrect all services
@@ -48,8 +51,7 @@ export async function resurrectCommand(name: string, options: ResurrectOptions):
48
51
  const escapedName = name.replace(/[^a-zA-Z0-9._-]/g, '');
49
52
 
50
53
  // Check if service exists in backup
51
- const backupDir = join(platformInfo.configDir, 'backups');
52
- const backupFile = join(backupDir, `${escapedName}.json`);
54
+ const backupFile = join(platformInfo.backupDir, `${escapedName}.json`);
53
55
 
54
56
  if (!require('node:fs').existsSync(backupFile)) {
55
57
  console.error(`❌ No backup found for service '${name}'`);
@@ -59,6 +61,13 @@ export async function resurrectCommand(name: string, options: ResurrectOptions):
59
61
  // Load backup configuration
60
62
  const backupConfig = JSON.parse(require('node:fs').readFileSync(backupFile, 'utf8'));
61
63
 
64
+ // Check if the file exists
65
+ const filePath = backupConfig.file;
66
+ if (!require('node:fs').existsSync(filePath)) {
67
+ console.error(`❌ Service file not found: ${filePath}`);
68
+ process.exit(1);
69
+ }
70
+
62
71
  // Restore service using backup configuration
63
72
  const { startCommand } = await import("./start.js");
64
73
  await startCommand(backupConfig.file, {
@@ -108,16 +117,12 @@ async function resurrectAllServices(platformInfo: any, options: ResurrectOptions
108
117
  try {
109
118
  console.log("🔄 Resurrecting all BS9 services from backup...");
110
119
 
120
+ // Initialize platform directories
121
+ initializePlatformDirectories();
122
+
111
123
  if (platformInfo.isLinux) {
112
- const backupDir = join(platformInfo.configDir, 'backups');
113
-
114
- if (!require('node:fs').existsSync(backupDir)) {
115
- console.log("â„šī¸ No backup directory found");
116
- return;
117
- }
118
-
119
124
  // Get all backup files
120
- const backupFiles = require('node:fs').readdirSync(backupDir)
125
+ const backupFiles = require('node:fs').readdirSync(platformInfo.backupDir)
121
126
  .filter((file: string) => file.endsWith('.json'));
122
127
 
123
128
  if (backupFiles.length === 0) {
@@ -130,7 +135,7 @@ async function resurrectAllServices(platformInfo: any, options: ResurrectOptions
130
135
  for (const backupFile of backupFiles) {
131
136
  try {
132
137
  const serviceName = backupFile.replace('.json', '');
133
- const backupPath = join(backupDir, backupFile);
138
+ const backupPath = join(platformInfo.backupDir, backupFile);
134
139
  const backupConfig = JSON.parse(require('node:fs').readFileSync(backupPath, 'utf8'));
135
140
 
136
141
  // Restore service using backup configuration
@@ -154,11 +159,11 @@ async function resurrectAllServices(platformInfo: any, options: ResurrectOptions
154
159
 
155
160
  } else if (platformInfo.isMacOS) {
156
161
  console.log("📝 To resurrect all services on macOS, you need to manually restore the plist files from:");
157
- console.log(` ${join(platformInfo.configDir, 'backups')}/*.plist`);
162
+ console.log(` ${platformInfo.backupDir}/*.plist`);
158
163
  console.log(" And then run: launchctl load ~/Library/LaunchAgents/bs9.*.plist");
159
164
  } else if (platformInfo.isWindows) {
160
165
  console.log("📝 To resurrect all services on Windows, use PowerShell:");
161
- console.log(" Get-ChildItem -Path \"${join(platformInfo.configDir, 'backups')}\" | ForEach-Object { Restore-Service $_.Name }");
166
+ console.log(" Get-ChildItem -Path \"${platformInfo.backupDir}\" | ForEach-Object { Restore-Service $_.Name }");
162
167
  }
163
168
 
164
169
  console.log(`✅ All BS9 services resurrection process completed`);
@@ -10,8 +10,8 @@
10
10
  */
11
11
 
12
12
  import { execSync } from "node:child_process";
13
- import { join } from "node:path";
14
- import { getPlatformInfo } from "../platform/detect.js";
13
+ import { join, resolve } from "node:path";
14
+ import { getPlatformInfo, initializePlatformDirectories } from "../platform/detect.js";
15
15
 
16
16
  interface SaveOptions {
17
17
  all?: boolean;
@@ -28,6 +28,9 @@ function isValidServiceName(name: string): boolean {
28
28
  }
29
29
 
30
30
  export async function saveCommand(name: string, options: SaveOptions): Promise<void> {
31
+ // Initialize platform directories
32
+ initializePlatformDirectories();
33
+
31
34
  const platformInfo = getPlatformInfo();
32
35
 
33
36
  // Handle save all services
@@ -62,12 +65,8 @@ export async function saveCommand(name: string, options: SaveOptions): Promise<v
62
65
  // Parse service configuration to extract startup parameters
63
66
  const config = parseServiceConfig(serviceConfig, statusOutput);
64
67
 
65
- // Create backup directory if it doesn't exist
66
- const backupDir = join(platformInfo.configDir, 'backups');
67
- require('node:fs').mkdirSync(backupDir, { recursive: true });
68
-
69
68
  // Save configuration to backup
70
- const backupFile = join(backupDir, `${escapedName}.json`);
69
+ const backupFile = join(platformInfo.backupDir, `${escapedName}.json`);
71
70
  const backupData = {
72
71
  name: name,
73
72
  file: extractFileFromConfig(serviceConfig),
@@ -89,7 +88,7 @@ export async function saveCommand(name: string, options: SaveOptions): Promise<v
89
88
  if (options.backup) {
90
89
  // Create additional backup with timestamp
91
90
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
92
- const timestampedBackup = join(backupDir, `${escapedName}-${timestamp}.json`);
91
+ const timestampedBackup = join(platformInfo.backupDir, `${escapedName}-${timestamp}.json`);
93
92
  require('node:fs').writeFileSync(timestampedBackup, JSON.stringify(backupData, null, 2));
94
93
  console.log(`đŸ“Ļ Additional backup created: ${timestampedBackup}`);
95
94
  }
@@ -118,6 +117,9 @@ async function saveAllServices(platformInfo: any, options: SaveOptions): Promise
118
117
  try {
119
118
  console.log("💾 Saving all BS9 service configurations...");
120
119
 
120
+ // Initialize platform directories
121
+ initializePlatformDirectories();
122
+
121
123
  if (platformInfo.isLinux) {
122
124
  // Get all BS9 services
123
125
  const listOutput = execSync("systemctl --user list-units --type=service --no-pager --no-legend", { encoding: "utf-8" });
@@ -144,10 +146,6 @@ async function saveAllServices(platformInfo: any, options: SaveOptions): Promise
144
146
 
145
147
  console.log(`Found ${bs9Services.length} BS9 services to save...`);
146
148
 
147
- // Create backup directory
148
- const backupDir = join(platformInfo.configDir, 'backups');
149
- require('node:fs').mkdirSync(backupDir, { recursive: true });
150
-
151
149
  for (const serviceName of bs9Services) {
152
150
  try {
153
151
  const serviceFile = join(platformInfo.serviceDir, `${serviceName}.service`);
@@ -157,7 +155,7 @@ async function saveAllServices(platformInfo: any, options: SaveOptions): Promise
157
155
  const statusOutput = execSync(`systemctl --user show "${serviceName}"`, { encoding: "utf-8" });
158
156
  const config = parseServiceConfig(serviceConfig, statusOutput);
159
157
 
160
- const backupFile = join(backupDir, `${serviceName}.json`);
158
+ const backupFile = join(platformInfo.backupDir, `${serviceName}.json`);
161
159
  const backupData = {
162
160
  name: serviceName,
163
161
  file: extractFileFromConfig(serviceConfig),
@@ -239,10 +237,23 @@ function extractFileFromConfig(serviceConfig: string): string {
239
237
  const execMatch = serviceConfig.match(/ExecStart=([^\n]+)/);
240
238
  if (execMatch) {
241
239
  const execLine = execMatch[1].trim();
242
- // Extract the file path from the exec command
243
- const fileMatch = execLine.match(/bun\s+([^\s]+)/);
240
+ // Handle both "bun run" and direct "bun" execution
241
+ let fileMatch;
242
+ if (execLine.includes('bun run')) {
243
+ // Extract the file path from "bun run <file>"
244
+ fileMatch = execLine.match(/bun run\s+(?:'([^']+)'|"([^"]+)"|([^\s]+))/);
245
+ } else {
246
+ // Extract the file path from "bun <file>"
247
+ fileMatch = execLine.match(/bun\s+(?:'([^']+)'|"([^"]+)"|([^\s]+))/);
248
+ }
249
+
244
250
  if (fileMatch) {
245
- return fileMatch[1];
251
+ const filePath = fileMatch[1] || fileMatch[2] || fileMatch[3];
252
+ // If it's a relative path, resolve it relative to the working directory
253
+ if (!filePath.startsWith('/') && !filePath.startsWith('~')) {
254
+ return resolve(filePath);
255
+ }
256
+ return filePath;
246
257
  }
247
258
  }
248
259
  return '';
@@ -9,7 +9,8 @@
9
9
  * https://github.com/xarhang/bs9
10
10
  */
11
11
 
12
- import { platform } from "node:os";
12
+ import { platform, homedir } from "node:os";
13
+ import { join } from "node:path";
13
14
 
14
15
  export type Platform = 'linux' | 'darwin' | 'win32';
15
16
 
@@ -22,10 +23,12 @@ export interface PlatformInfo {
22
23
  configDir: string;
23
24
  logDir: string;
24
25
  serviceDir: string;
26
+ backupDir: string;
25
27
  }
26
28
 
27
29
  export function getPlatformInfo(): PlatformInfo {
28
30
  const currentPlatform = platform() as Platform;
31
+ const userHome = homedir();
29
32
 
30
33
  const baseInfo: PlatformInfo = {
31
34
  platform: currentPlatform,
@@ -35,29 +38,33 @@ export function getPlatformInfo(): PlatformInfo {
35
38
  serviceManager: 'systemd',
36
39
  configDir: '',
37
40
  logDir: '',
38
- serviceDir: ''
41
+ serviceDir: '',
42
+ backupDir: ''
39
43
  };
40
44
 
41
45
  switch (currentPlatform) {
42
46
  case 'linux':
43
47
  baseInfo.serviceManager = 'systemd';
44
- baseInfo.configDir = `${process.env.HOME}/.config/bs9`;
45
- baseInfo.logDir = `${process.env.HOME}/.local/share/bs9/logs`;
46
- baseInfo.serviceDir = `${process.env.HOME}/.config/systemd/user`;
48
+ baseInfo.configDir = join(userHome, '.config', 'bs9');
49
+ baseInfo.logDir = join(userHome, '.local', 'share', 'bs9', 'logs');
50
+ baseInfo.serviceDir = join(userHome, '.config', 'systemd', 'user');
51
+ baseInfo.backupDir = join(baseInfo.configDir, 'backups');
47
52
  break;
48
53
 
49
54
  case 'darwin':
50
55
  baseInfo.serviceManager = 'launchd';
51
- baseInfo.configDir = `${process.env.HOME}/.bs9`;
52
- baseInfo.logDir = `${process.env.HOME}/.bs9/logs`;
53
- baseInfo.serviceDir = `${process.env.HOME}/Library/LaunchAgents`;
56
+ baseInfo.configDir = join(userHome, '.bs9');
57
+ baseInfo.logDir = join(userHome, '.bs9', 'logs');
58
+ baseInfo.serviceDir = join(userHome, 'Library', 'LaunchAgents');
59
+ baseInfo.backupDir = join(baseInfo.configDir, 'backups');
54
60
  break;
55
61
 
56
62
  case 'win32':
57
63
  baseInfo.serviceManager = 'windows-service';
58
- baseInfo.configDir = `${process.env.USERPROFILE}/.bs9`;
59
- baseInfo.logDir = `${process.env.USERPROFILE}/.bs9/logs`;
60
- baseInfo.serviceDir = `${process.env.USERPROFILE}/.bs9/services`;
64
+ baseInfo.configDir = join(userHome, '.bs9');
65
+ baseInfo.logDir = join(userHome, '.bs9', 'logs');
66
+ baseInfo.serviceDir = join(userHome, '.bs9', 'services');
67
+ baseInfo.backupDir = join(baseInfo.configDir, 'backups');
61
68
  break;
62
69
 
63
70
  default:
@@ -77,13 +84,13 @@ export function getPlatformSpecificCommands(): string[] {
77
84
 
78
85
  switch (currentPlatform) {
79
86
  case 'linux':
80
- return ['start', 'stop', 'restart', 'status', 'logs', 'monit', 'web', 'alert', 'export', 'deps', 'profile', 'loadbalancer', 'dbpool'];
87
+ return ['start', 'stop', 'restart', 'status', 'logs', 'monit', 'web', 'alert', 'export', 'deps', 'profile', 'delete', 'save', 'resurrect', 'loadbalancer', 'dbpool'];
81
88
 
82
89
  case 'darwin':
83
- return ['start', 'stop', 'restart', 'status', 'logs', 'monit', 'web', 'alert', 'export', 'deps', 'profile', 'loadbalancer', 'dbpool', 'macos'];
90
+ return ['start', 'stop', 'restart', 'status', 'logs', 'monit', 'web', 'alert', 'export', 'deps', 'profile', 'delete', 'save', 'resurrect', 'loadbalancer', 'dbpool', 'macos'];
84
91
 
85
92
  case 'win32':
86
- return ['start', 'stop', 'restart', 'status', 'logs', 'monit', 'web', 'alert', 'export', 'deps', 'profile', 'loadbalancer', 'dbpool', 'windows'];
93
+ return ['start', 'stop', 'restart', 'status', 'logs', 'monit', 'web', 'alert', 'export', 'deps', 'profile', 'delete', 'save', 'resurrect', 'loadbalancer', 'dbpool', 'windows'];
87
94
 
88
95
  default:
89
96
  return [];
@@ -144,3 +151,19 @@ Windows-specific:
144
151
  return `❌ Platform ${currentPlatform} is not supported`;
145
152
  }
146
153
  }
154
+
155
+ // Auto-detect and initialize platform-specific directories
156
+ export function initializePlatformDirectories(): void {
157
+ const platformInfo = getPlatformInfo();
158
+
159
+ // Create directories if they don't exist
160
+ const fs = require('node:fs');
161
+
162
+ try {
163
+ fs.mkdirSync(platformInfo.configDir, { recursive: true });
164
+ fs.mkdirSync(platformInfo.logDir, { recursive: true });
165
+ fs.mkdirSync(platformInfo.backupDir, { recursive: true });
166
+ } catch (error) {
167
+ console.warn(`âš ī¸ Warning: Could not create platform directories: ${error}`);
168
+ }
169
+ }