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 +8 -7
- package/package.json +1 -1
- package/src/commands/resurrect.ts +19 -14
- package/src/commands/save.ts +27 -16
- package/src/platform/detect.ts +37 -14
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# BS9 (Bun Sentinel 9) đ
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://github.com/xarhang/bs9)
|
|
5
5
|
[](SECURITY.md)
|
|
6
6
|
[](PRODUCTION.md)
|
|
7
7
|
[](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
|
-
|
|
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
|
|
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**:
|
|
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**:
|
|
47
|
+
- **Commands**: All commands + `bs9 windows` for service management
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
50
|
# Check your platform
|
package/package.json
CHANGED
|
@@ -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
|
|
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(` ${
|
|
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 \"${
|
|
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`);
|
package/src/commands/save.ts
CHANGED
|
@@ -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
|
-
//
|
|
243
|
-
|
|
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
|
-
|
|
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 '';
|
package/src/platform/detect.ts
CHANGED
|
@@ -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 =
|
|
45
|
-
baseInfo.logDir =
|
|
46
|
-
baseInfo.serviceDir =
|
|
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 =
|
|
52
|
-
baseInfo.logDir =
|
|
53
|
-
baseInfo.serviceDir =
|
|
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 =
|
|
59
|
-
baseInfo.logDir =
|
|
60
|
-
baseInfo.serviceDir =
|
|
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
|
+
}
|