-
-
Notifications
You must be signed in to change notification settings - Fork 693
Expand file tree
/
Copy pathconfig-manager.ts
More file actions
225 lines (199 loc) · 7.23 KB
/
config-manager.ts
File metadata and controls
225 lines (199 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import fs from 'fs/promises';
import path from 'path';
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
import os from 'os';
import { VERSION } from './version.js';
import { CONFIG_FILE } from './config.js';
export interface ServerConfig {
blockedCommands?: string[];
defaultShell?: string;
allowedDirectories?: string[];
readOnlyDirectories?: string[]; // Directories that can be read but not modified
requireExplicitPermission?: boolean; // Require explicit flag for destructive commands
allowedSudoCommands?: string[]; // Whitelist of allowed sudo commands with pattern support
telemetryEnabled?: boolean; // New field for telemetry control
fileWriteLineLimit?: number; // Line limit for file write operations
fileReadLineLimit?: number; // Default line limit for file read operations (changed from character-based)
clientId?: string; // Unique client identifier for analytics
currentClient?: ClientInfo; // Current connected client information
[key: string]: any; // Allow for arbitrary configuration keys
}
export interface ClientInfo {
name: string;
version: string;
}
/**
* Singleton config manager for the server
*/
class ConfigManager {
private configPath: string;
private config: ServerConfig = {};
private initialized = false;
constructor() {
// Get user's home directory
// Define config directory and file paths
this.configPath = CONFIG_FILE;
}
/**
* Initialize configuration - load from disk or create default
*/
async init() {
if (this.initialized) return;
try {
// Ensure config directory exists
const configDir = path.dirname(this.configPath);
if (!existsSync(configDir)) {
await mkdir(configDir, { recursive: true });
}
// Check if config file exists
try {
await fs.access(this.configPath);
// Load existing config
const configData = await fs.readFile(this.configPath, 'utf8');
this.config = JSON.parse(configData);
} catch (error) {
// Config file doesn't exist, create default
this.config = this.getDefaultConfig();
await this.saveConfig();
}
this.config['version'] = VERSION;
this.initialized = true;
} catch (error) {
console.error('Failed to initialize config:', error);
// Fall back to default config in memory
this.config = this.getDefaultConfig();
this.initialized = true;
}
}
/**
* Alias for init() to maintain backward compatibility
*/
async loadConfig() {
return this.init();
}
/**
* Create default configuration
*/
private getDefaultConfig(): ServerConfig {
return {
blockedCommands: [
// Disk and partition management
"mkfs", // Create a filesystem on a device
"format", // Format a storage device (cross-platform)
"mount", // Mount a filesystem
"umount", // Unmount a filesystem
"fdisk", // Manipulate disk partition tables
"dd", // Convert and copy files, can write directly to disks
"parted", // Disk partition manipulator
"diskpart", // Windows disk partitioning utility
// System administration and user management
"sudo", // Execute command as superuser
"su", // Substitute user identity
"passwd", // Change user password
"adduser", // Add a user to the system
"useradd", // Create a new user
"usermod", // Modify user account
"groupadd", // Create a new group
"chsh", // Change login shell
"visudo", // Edit the sudoers file
// System control
"shutdown", // Shutdown the system
"reboot", // Restart the system
"halt", // Stop the system
"poweroff", // Power off the system
"init", // Change system runlevel
// Network and security
"iptables", // Linux firewall administration
"firewall", // Generic firewall command
"netsh", // Windows network configuration
// Windows system commands
"sfc", // System File Checker
"bcdedit", // Boot Configuration Data editor
"reg", // Windows registry editor
"net", // Network/user/service management
"sc", // Service Control manager
"runas", // Execute command as another user
"cipher", // Encrypt/decrypt files or wipe data
"takeown" // Take ownership of files
],
defaultShell: os.platform() === 'win32' ? 'powershell.exe' : '/bin/sh',
allowedDirectories: [],
readOnlyDirectories: [], // Empty by default - no directories are read-only
requireExplicitPermission: false, // Default to false for backward compatibility
allowedSudoCommands: [], // Empty array allows no sudo commands by default
telemetryEnabled: true, // Default to opt-out approach (telemetry on by default)
fileWriteLineLimit: 50, // Default line limit for file write operations (changed from 100)
fileReadLineLimit: 1000 // Default line limit for file read operations (changed from character-based)
};
}
/**
* Save config to disk
*/
private async saveConfig() {
try {
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2), 'utf8');
} catch (error) {
console.error('Failed to save config:', error);
throw error;
}
}
/**
* Get the entire config
*/
async getConfig(): Promise<ServerConfig> {
await this.init();
return { ...this.config };
}
/**
* Get a specific configuration value
*/
async getValue(key: string): Promise<any> {
await this.init();
return this.config[key];
}
/**
* Set a specific configuration value
*/
async setValue(key: string, value: any): Promise<void> {
await this.init();
// Special handling for telemetry opt-out
if (key === 'telemetryEnabled' && value === false) {
// Get the current value before changing it
const currentValue = this.config[key];
// Only capture the opt-out event if telemetry was previously enabled
if (currentValue !== false) {
// Import the capture function dynamically to avoid circular dependencies
const { capture } = await import('./utils/capture.js');
// Send a final telemetry event noting that the user has opted out
// This helps us track opt-out rates while respecting the user's choice
await capture('server_telemetry_opt_out', {
reason: 'user_disabled',
prev_value: currentValue
});
}
}
// Update the value
this.config[key] = value;
await this.saveConfig();
}
/**
* Update multiple configuration values at once
*/
async updateConfig(updates: Partial<ServerConfig>): Promise<ServerConfig> {
await this.init();
this.config = { ...this.config, ...updates };
await this.saveConfig();
return { ...this.config };
}
/**
* Reset configuration to defaults
*/
async resetConfig(): Promise<ServerConfig> {
this.config = this.getDefaultConfig();
await this.saveConfig();
return { ...this.config };
}
}
// Export singleton instance
export const configManager = new ConfigManager();