shell-mirror 1.0.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 +272 -0
- package/auth.js +22 -0
- package/bin/shell-mirror +127 -0
- package/lib/config-manager.js +203 -0
- package/lib/health-checker.js +192 -0
- package/lib/server-manager.js +225 -0
- package/lib/setup-wizard.js +222 -0
- package/package.json +70 -0
- package/public/app/terminal.html +867 -0
- package/public/index.html +809 -0
- package/server.js +171 -0
package/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Shell Mirror
|
|
2
|
+
|
|
3
|
+
A simple application to securely mirror your local computer's shell to a web browser on any device. It uses Google Authentication to ensure only you can access your shell session.
|
|
4
|
+
|
|
5
|
+
## π― Project Goal
|
|
6
|
+
|
|
7
|
+
The primary goal is to allow a user to see their personal shell session (initially for macOS) running on their own computer, reflected in a web page accessible from their mobile phone or tablet. Access must be securely restricted to the user, leveraging a standard and robust authentication provider.
|
|
8
|
+
|
|
9
|
+
Key objectives:
|
|
10
|
+
- **Secure Mirroring**: Provide a real-time, read/write mirror of a user's shell.
|
|
11
|
+
- **Robust Authentication**: Use Google OAuth 2.0 to ensure that only the owner can access the terminal session.
|
|
12
|
+
- **Web-Based Access**: The web interface should allow a user to log in and connect to their running shell server.
|
|
13
|
+
- **Simplicity**: The application should be a simple, standalone server that is easy to set up and run on a personal computer.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## πΊοΈ Development Plan
|
|
18
|
+
|
|
19
|
+
To achieve this vision, the project will be pivoted from its original, more complex architecture to a streamlined solution.
|
|
20
|
+
|
|
21
|
+
### High-Level Plan
|
|
22
|
+
|
|
23
|
+
1. **Simplify the Architecture:**
|
|
24
|
+
* **Remove Docker:** Eliminate all Docker-related files (`Dockerfile`, `docker-compose.yml`, etc.) and scripts. The application will run directly on the host machine.
|
|
25
|
+
* **Remove Connection Broker:** The `broker.js` service is overly complex for the new goal and will be removed.
|
|
26
|
+
|
|
27
|
+
2. **Implement Robust Authentication:**
|
|
28
|
+
* **Replace Custom Auth:** Swap the current manual token system (`auth.js`) with a standard, secure **Google OAuth 2.0** login flow using Passport.js.
|
|
29
|
+
|
|
30
|
+
3. **Refine the User Experience:**
|
|
31
|
+
* **Login Page:** The web interface (`index.html`) will first present a "Login with Google" button.
|
|
32
|
+
* **Secure Shell Access:** The shell view will only be loaded and connected *after* a successful login.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### Detailed Implementation Steps
|
|
37
|
+
|
|
38
|
+
* **Phase 1: Cleanup & Refactoring**
|
|
39
|
+
1. Delete `Dockerfile`, `docker-compose.yml`, `docker-compose.override.yml`, and `.dockerignore`.
|
|
40
|
+
2. Delete `broker.js`.
|
|
41
|
+
3. Uninstall unused dependencies and remove Docker-related commands from `package.json`.
|
|
42
|
+
4. Update `server.js` to remove all logic related to the broker and the old authentication system.
|
|
43
|
+
|
|
44
|
+
* **Phase 2: Google Authentication**
|
|
45
|
+
1. Add new dependencies: `passport`, `passport-google-oauth20`, and `express-session`.
|
|
46
|
+
2. Create a new `auth.js` to configure Passport for the Google login flow.
|
|
47
|
+
3. Add the necessary login (`/auth/google`) and callback (`/auth/google/callback`) routes to `server.js`.
|
|
48
|
+
4. Secure the WebSocket connection by ensuring the user is authenticated via their session.
|
|
49
|
+
|
|
50
|
+
* **Phase 3: Frontend UI**
|
|
51
|
+
1. Modify `public/index.html` to conditionally show a "Login" button or the terminal.
|
|
52
|
+
2. Add client-side logic to check the user's authentication status and initiate the WebSocket connection only when logged in.
|
|
53
|
+
|
|
54
|
+
* **Phase 4: Documentation**
|
|
55
|
+
1. Update this `README.md` to reflect the new, simplified project goals, the plan outlined above, and provide clear instructions on how to set up and run the application, including how to generate Google OAuth credentials.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## π Getting Started
|
|
60
|
+
|
|
61
|
+
### 1. Prerequisites
|
|
62
|
+
|
|
63
|
+
- **Node.js**: Make sure you have Node.js (version 18 or higher) installed on your Mac.
|
|
64
|
+
- **Google Account**: You will need a Google account to generate authentication credentials.
|
|
65
|
+
|
|
66
|
+
### 2. Configure Google OAuth 2.0 Credentials
|
|
67
|
+
|
|
68
|
+
Before you can run the application, you need to get a **Client ID** and **Client Secret** from the Google Cloud Console.
|
|
69
|
+
|
|
70
|
+
1. **Go to the Google Cloud Console**: [https://console.cloud.google.com/](https://console.cloud.google.com/)
|
|
71
|
+
|
|
72
|
+
2. **Create a new project** (or select an existing one).
|
|
73
|
+
|
|
74
|
+
3. **Enable the "Google People API"**: In the navigation menu, go to **APIs & Services > Library**, search for "Google People API", and click **Enable**.
|
|
75
|
+
|
|
76
|
+
4. **Configure the OAuth Consent Screen**:
|
|
77
|
+
* Go to **APIs & Services > OAuth consent screen**.
|
|
78
|
+
* Choose **External** and click **Create**.
|
|
79
|
+
* Fill in the required fields (app name, user support email, developer contact).
|
|
80
|
+
* On the "Scopes" page, you can leave it blank.
|
|
81
|
+
* On the "Test users" page, add the Google account email you intend to log in with.
|
|
82
|
+
|
|
83
|
+
5. **Create Credentials**:
|
|
84
|
+
* Go to **APIs & Services > Credentials**.
|
|
85
|
+
* Click **+ CREATE CREDENTIALS** and select **OAuth client ID**.
|
|
86
|
+
* For **Application type**, select **Web application**.
|
|
87
|
+
|
|
88
|
+
**For Production (www.igori.eu):**
|
|
89
|
+
* Under **Authorized JavaScript origins**, add `https://www.igori.eu`.
|
|
90
|
+
* Under **Authorized redirect URIs**, add `https://www.igori.eu/auth/google/callback`.
|
|
91
|
+
|
|
92
|
+
**For Local Development:**
|
|
93
|
+
* Under **Authorized JavaScript origins**, add `http://localhost:3000`.
|
|
94
|
+
* Under **Authorized redirect URIs**, add `http://localhost:3000/auth/google/callback`.
|
|
95
|
+
|
|
96
|
+
*Note: You can add both production and development URLs to the same OAuth client.*
|
|
97
|
+
|
|
98
|
+
* Click **Create**. You will be shown your **Client ID** and **Client Secret**. Copy these.
|
|
99
|
+
|
|
100
|
+
### 3. Configure the Application
|
|
101
|
+
|
|
102
|
+
1. **Create a `.env` file** in the root of the project by copying the example:
|
|
103
|
+
```bash
|
|
104
|
+
cp .env.example .env
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. **Edit the `.env` file** and add the credentials you just created:
|
|
108
|
+
|
|
109
|
+
**For Production (www.igori.eu):**
|
|
110
|
+
```
|
|
111
|
+
BASE_URL=https://www.igori.eu
|
|
112
|
+
PORT=3000
|
|
113
|
+
HOST=0.0.0.0
|
|
114
|
+
GOOGLE_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
|
|
115
|
+
GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
|
|
116
|
+
SESSION_SECRET=a-very-strong-and-long-random-string
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**For Local Development:**
|
|
120
|
+
```
|
|
121
|
+
BASE_URL=http://localhost:3000
|
|
122
|
+
PORT=3000
|
|
123
|
+
HOST=0.0.0.0
|
|
124
|
+
GOOGLE_CLIENT_ID=YOUR_GOOGLE_CLIENT_ID
|
|
125
|
+
GOOGLE_CLIENT_SECRET=YOUR_GOOGLE_CLIENT_SECRET
|
|
126
|
+
SESSION_SECRET=a-very-strong-and-long-random-string
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
*Replace the placeholder values with your actual credentials. Use a long, random string for the `SESSION_SECRET`.*
|
|
130
|
+
|
|
131
|
+
### 4. Install & Run
|
|
132
|
+
|
|
133
|
+
1. **Install dependencies**:
|
|
134
|
+
```bash
|
|
135
|
+
npm install
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
2. **Start the server**:
|
|
139
|
+
```bash
|
|
140
|
+
npm start
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 5. Access Your Terminal
|
|
144
|
+
|
|
145
|
+
**For Production:**
|
|
146
|
+
- Open your web browser and go to `https://www.igori.eu`
|
|
147
|
+
- You will be prompted to log in with your Google account
|
|
148
|
+
- Once authenticated, you will see your shell mirrored in the browser
|
|
149
|
+
|
|
150
|
+
**For Local Development:**
|
|
151
|
+
- Open your web browser and go to `http://localhost:3000`
|
|
152
|
+
- Or access from another device on the same network: `http://<your-macs-ip-address>:3000`
|
|
153
|
+
- You will be prompted to log in with your Google account
|
|
154
|
+
- Once authenticated, you will see your shell mirrored in the browser
|
|
155
|
+
|
|
156
|
+
### 6. Deployment Notes
|
|
157
|
+
|
|
158
|
+
**For Production Deployment:**
|
|
159
|
+
- Ensure your server has SSL/TLS configured (required for HTTPS)
|
|
160
|
+
- Set up proper firewall rules and security measures
|
|
161
|
+
- Consider using a process manager like PM2 for production
|
|
162
|
+
- Make sure the `BASE_URL` environment variable matches your actual domain
|
|
163
|
+
|
|
164
|
+
**Environment Variables:**
|
|
165
|
+
The application will validate that all required environment variables are set on startup and show helpful error messages if any are missing.
|
|
166
|
+
|
|
167
|
+
### 7. Production Deployment
|
|
168
|
+
|
|
169
|
+
**Automated Deployment to www.igori.eu:**
|
|
170
|
+
|
|
171
|
+
The project includes an automated deployment script that uploads the application to the production server.
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npm run deploy
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Deployment Process:**
|
|
178
|
+
1. **Validation**: Checks that all required files exist and syntax is valid
|
|
179
|
+
2. **Upload**: Securely uploads all application files via FTP
|
|
180
|
+
3. **Verification**: Confirms all files were uploaded successfully
|
|
181
|
+
4. **Instructions**: Displays post-deployment setup steps
|
|
182
|
+
|
|
183
|
+
**Post-Deployment Setup:**
|
|
184
|
+
After running the deployment script, you'll need to SSH into your server and:
|
|
185
|
+
|
|
186
|
+
1. Install dependencies: `npm install`
|
|
187
|
+
2. Configure environment: `cp .env.example .env` and edit with your Google OAuth credentials
|
|
188
|
+
3. Start the server: `npm start` or `pm2 start server.js --name terminal-mirror`
|
|
189
|
+
4. Set up reverse proxy (nginx) if needed for HTTPS
|
|
190
|
+
|
|
191
|
+
**Security Notes:**
|
|
192
|
+
- Ensure `.env` file has proper permissions (600)
|
|
193
|
+
- Use a strong `SESSION_SECRET` (generate with: `openssl rand -hex 32`)
|
|
194
|
+
- Keep Google OAuth credentials secure
|
|
195
|
+
- Set up proper firewall rules
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## π οΈ Development Roadmap
|
|
200
|
+
|
|
201
|
+
### Current Status β
|
|
202
|
+
The application now has a solid foundation with:
|
|
203
|
+
- Google OAuth 2.0 authentication implemented
|
|
204
|
+
- Environment-based configuration for production/development
|
|
205
|
+
- Basic shell mirroring via WebSocket
|
|
206
|
+
- Secure session management
|
|
207
|
+
|
|
208
|
+
### Next Development Phases
|
|
209
|
+
|
|
210
|
+
#### Phase 1: Core Functionality Enhancement
|
|
211
|
+
**Priority: High**
|
|
212
|
+
- [ ] **WebSocket Authentication**: Secure WebSocket connections by validating user sessions
|
|
213
|
+
- [ ] **Shell Session Management**: Implement proper shell session isolation per user
|
|
214
|
+
- [ ] **Connection Resilience**: Add automatic reconnection logic for dropped WebSocket connections
|
|
215
|
+
- [ ] **Error Handling**: Improve error handling for authentication failures and connection issues
|
|
216
|
+
|
|
217
|
+
#### Phase 2: User Experience Improvements
|
|
218
|
+
**Priority: Medium**
|
|
219
|
+
- [ ] **Frontend UI Enhancement**:
|
|
220
|
+
- Modernize the terminal interface (consider using xterm.js for better terminal emulation)
|
|
221
|
+
- Add login/logout UI states
|
|
222
|
+
- Implement loading states and connection indicators
|
|
223
|
+
- [ ] **Shell Features**:
|
|
224
|
+
- Shell resizing support
|
|
225
|
+
- Copy/paste functionality
|
|
226
|
+
- Shell themes and customization
|
|
227
|
+
- [ ] **Mobile Optimization**: Ensure the interface works well on mobile devices
|
|
228
|
+
|
|
229
|
+
#### Phase 3: Security & Production Readiness
|
|
230
|
+
**Priority: High for Production**
|
|
231
|
+
- [ ] **Rate Limiting**: Add rate limiting for authentication and WebSocket connections
|
|
232
|
+
- [ ] **Logging & Monitoring**: Implement structured logging and health check endpoints
|
|
233
|
+
- [ ] **Session Security**: Add session timeout and secure session storage
|
|
234
|
+
- [ ] **HTTPS/WSS**: Ensure all connections use secure protocols in production
|
|
235
|
+
- [ ] **Environment Isolation**: Add development/staging/production environment configurations
|
|
236
|
+
|
|
237
|
+
#### Phase 4: Advanced Features
|
|
238
|
+
**Priority: Low (Future Enhancements)**
|
|
239
|
+
- [ ] **Multi-Shell Support**: Allow multiple shell sessions per user
|
|
240
|
+
- [ ] **File Upload/Download**: Add secure file transfer capabilities
|
|
241
|
+
- [ ] **Shell Recording**: Save and replay shell sessions
|
|
242
|
+
- [ ] **Collaboration Features**: Allow shared shell sessions (with proper security)
|
|
243
|
+
- [ ] **Resource Monitoring**: Display system resource usage in the interface
|
|
244
|
+
|
|
245
|
+
### Development Guidelines
|
|
246
|
+
|
|
247
|
+
#### Code Structure
|
|
248
|
+
- Keep `server.js` focused on server setup and routing
|
|
249
|
+
- Use `auth.js` for all authentication logic
|
|
250
|
+
- Create separate modules for shell management and WebSocket handling
|
|
251
|
+
- Follow Node.js best practices for async/await and error handling
|
|
252
|
+
|
|
253
|
+
#### Testing Strategy
|
|
254
|
+
- Add unit tests for authentication logic
|
|
255
|
+
- Implement integration tests for WebSocket functionality
|
|
256
|
+
- Test OAuth flow in both development and production environments
|
|
257
|
+
- Add automated security testing
|
|
258
|
+
|
|
259
|
+
#### Deployment Considerations
|
|
260
|
+
- Use PM2 or similar process manager for production
|
|
261
|
+
- Implement proper logging with log rotation
|
|
262
|
+
- Set up SSL/TLS certificates for HTTPS
|
|
263
|
+
- Configure reverse proxy (nginx) if needed
|
|
264
|
+
- Monitor application health and performance
|
|
265
|
+
|
|
266
|
+
### Contributing
|
|
267
|
+
When working on this project:
|
|
268
|
+
1. Create feature branches for each development phase
|
|
269
|
+
2. Test thoroughly in local environment before production
|
|
270
|
+
3. Update this roadmap as features are completed
|
|
271
|
+
4. Document any new environment variables in `.env.example`
|
|
272
|
+
5. Keep security as the top priority for all changes
|
package/auth.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const passport = require('passport');
|
|
2
|
+
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
|
3
|
+
|
|
4
|
+
passport.use(new GoogleStrategy({
|
|
5
|
+
clientID: process.env.GOOGLE_CLIENT_ID,
|
|
6
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
7
|
+
callbackURL: `${process.env.BASE_URL}/auth/google/callback`
|
|
8
|
+
},
|
|
9
|
+
function(accessToken, refreshToken, profile, cb) {
|
|
10
|
+
// Here, you would typically find or create a user in your database.
|
|
11
|
+
// For this simple application, we'll just pass the profile through.
|
|
12
|
+
return cb(null, profile);
|
|
13
|
+
}
|
|
14
|
+
));
|
|
15
|
+
|
|
16
|
+
passport.serializeUser(function(user, cb) {
|
|
17
|
+
cb(null, user);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
passport.deserializeUser(function(obj, cb) {
|
|
21
|
+
cb(null, obj);
|
|
22
|
+
});
|
package/bin/shell-mirror
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const package = require('../package.json');
|
|
7
|
+
|
|
8
|
+
// Import command modules
|
|
9
|
+
const setupCommand = require('../lib/setup-wizard');
|
|
10
|
+
const serverManager = require('../lib/server-manager');
|
|
11
|
+
const configManager = require('../lib/config-manager');
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('shell-mirror')
|
|
17
|
+
.description('Access your Mac shell from any device securely')
|
|
18
|
+
.version(package.version);
|
|
19
|
+
|
|
20
|
+
// Setup command - Interactive OAuth configuration wizard
|
|
21
|
+
program
|
|
22
|
+
.command('setup')
|
|
23
|
+
.description('Configure Google OAuth credentials and initialize Shell Mirror')
|
|
24
|
+
.option('-f, --force', 'Overwrite existing configuration')
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
try {
|
|
27
|
+
await setupCommand.run(options);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Setup failed:', error.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Start command - Launch the terminal server
|
|
35
|
+
program
|
|
36
|
+
.command('start')
|
|
37
|
+
.description('Start the Shell Mirror server')
|
|
38
|
+
.option('-p, --port <port>', 'Port to run the server on', '3000')
|
|
39
|
+
.option('-h, --host <host>', 'Host to bind the server to', '0.0.0.0')
|
|
40
|
+
.option('-d, --daemon', 'Run as background daemon')
|
|
41
|
+
.action(async (options) => {
|
|
42
|
+
try {
|
|
43
|
+
await serverManager.start(options);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('Failed to start server:', error.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Stop command - Stop the terminal server
|
|
51
|
+
program
|
|
52
|
+
.command('stop')
|
|
53
|
+
.description('Stop the Shell Mirror server')
|
|
54
|
+
.action(async () => {
|
|
55
|
+
try {
|
|
56
|
+
await serverManager.stop();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('Failed to stop server:', error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Status command - Check server status
|
|
64
|
+
program
|
|
65
|
+
.command('status')
|
|
66
|
+
.description('Check Shell Mirror server status')
|
|
67
|
+
.action(async () => {
|
|
68
|
+
try {
|
|
69
|
+
await serverManager.status();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Failed to check status:', error.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Config command - Manage configuration
|
|
77
|
+
program
|
|
78
|
+
.command('config')
|
|
79
|
+
.description('Manage Shell Mirror configuration')
|
|
80
|
+
.option('--show', 'Show current configuration')
|
|
81
|
+
.option('--reset', 'Reset configuration to defaults')
|
|
82
|
+
.option('--set <key=value>', 'Set a configuration value')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
try {
|
|
85
|
+
await configManager.manage(options);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Config management failed:', error.message);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Health command - Run health checks
|
|
93
|
+
program
|
|
94
|
+
.command('health')
|
|
95
|
+
.description('Run health checks and diagnostics')
|
|
96
|
+
.action(async () => {
|
|
97
|
+
try {
|
|
98
|
+
const healthChecker = require('../lib/health-checker');
|
|
99
|
+
await healthChecker.run();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error('Health check failed:', error.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Install command - Post-install setup
|
|
107
|
+
program
|
|
108
|
+
.command('install')
|
|
109
|
+
.description('Post-installation setup and verification')
|
|
110
|
+
.action(async () => {
|
|
111
|
+
try {
|
|
112
|
+
console.log('π Shell Mirror installed successfully!');
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log('Next steps:');
|
|
115
|
+
console.log('1. Run: shell-mirror setup');
|
|
116
|
+
console.log('2. Run: shell-mirror start');
|
|
117
|
+
console.log('3. Open your browser and visit the URL shown');
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log('For help: shell-mirror --help');
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('Installation verification failed:', error.message);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Parse command line arguments
|
|
127
|
+
program.parse();
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
class ConfigManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.configDir = path.join(os.homedir(), '.terminal-mirror');
|
|
8
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
9
|
+
this.envFile = path.join(process.cwd(), '.env');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async manage(options = {}) {
|
|
13
|
+
if (options.show) {
|
|
14
|
+
await this.showConfig();
|
|
15
|
+
} else if (options.reset) {
|
|
16
|
+
await this.resetConfig();
|
|
17
|
+
} else if (options.set) {
|
|
18
|
+
await this.setConfig(options.set);
|
|
19
|
+
} else {
|
|
20
|
+
console.log('Configuration Management');
|
|
21
|
+
console.log('β'.repeat(30));
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log('Usage:');
|
|
24
|
+
console.log(' terminal-mirror config --show Show current configuration');
|
|
25
|
+
console.log(' terminal-mirror config --reset Reset to defaults');
|
|
26
|
+
console.log(' terminal-mirror config --set key=value Set a value');
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log('Examples:');
|
|
29
|
+
console.log(' terminal-mirror config --set port=8080');
|
|
30
|
+
console.log(' terminal-mirror config --set baseUrl=https://example.com');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async showConfig() {
|
|
35
|
+
console.log('π Current Configuration');
|
|
36
|
+
console.log('β'.repeat(30));
|
|
37
|
+
console.log('');
|
|
38
|
+
|
|
39
|
+
// Load environment
|
|
40
|
+
require('dotenv').config({ path: this.envFile });
|
|
41
|
+
|
|
42
|
+
// Show main configuration
|
|
43
|
+
console.log('Environment Variables:');
|
|
44
|
+
const envVars = [
|
|
45
|
+
'BASE_URL',
|
|
46
|
+
'PORT',
|
|
47
|
+
'HOST',
|
|
48
|
+
'NODE_ENV',
|
|
49
|
+
'GOOGLE_CLIENT_ID'
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
envVars.forEach(key => {
|
|
53
|
+
const value = process.env[key];
|
|
54
|
+
if (key === 'GOOGLE_CLIENT_ID' && value) {
|
|
55
|
+
// Mask client ID for security
|
|
56
|
+
const masked = value.substring(0, 10) + '...' + value.substring(value.length - 10);
|
|
57
|
+
console.log(` ${key}: ${masked}`);
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` ${key}: ${value || 'Not set'}`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log('Secret Variables:');
|
|
65
|
+
console.log(' GOOGLE_CLIENT_SECRET: ' + (process.env.GOOGLE_CLIENT_SECRET ? '[SET]' : '[NOT SET]'));
|
|
66
|
+
console.log(' SESSION_SECRET: ' + (process.env.SESSION_SECRET ? '[SET]' : '[NOT SET]'));
|
|
67
|
+
|
|
68
|
+
// Show user config if exists
|
|
69
|
+
try {
|
|
70
|
+
const userConfig = JSON.parse(await fs.readFile(this.configFile, 'utf8'));
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log('User Configuration:');
|
|
73
|
+
console.log(` Setup Date: ${userConfig.setupDate || 'Unknown'}`);
|
|
74
|
+
console.log(` Environment: ${userConfig.environment || 'Unknown'}`);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log('User Configuration: Not found');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Show file locations
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log('Configuration Files:');
|
|
83
|
+
console.log(` Environment: ${this.envFile}`);
|
|
84
|
+
console.log(` User Config: ${this.configFile}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async resetConfig() {
|
|
88
|
+
console.log('ποΈ Resetting Configuration');
|
|
89
|
+
console.log('');
|
|
90
|
+
|
|
91
|
+
const { confirm } = require('inquirer').prompt([{
|
|
92
|
+
type: 'confirm',
|
|
93
|
+
name: 'confirm',
|
|
94
|
+
message: 'This will delete all configuration. Continue?',
|
|
95
|
+
default: false
|
|
96
|
+
}]);
|
|
97
|
+
|
|
98
|
+
if (!(await confirm).confirm) {
|
|
99
|
+
console.log('Reset cancelled');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Remove user config
|
|
105
|
+
try {
|
|
106
|
+
await fs.unlink(this.configFile);
|
|
107
|
+
console.log('β
User configuration removed');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.log('β οΈ User configuration not found');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Remove .env file
|
|
113
|
+
try {
|
|
114
|
+
await fs.unlink(this.envFile);
|
|
115
|
+
console.log('β
Environment file removed');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.log('β οΈ Environment file not found');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Remove PID and log files
|
|
121
|
+
const pidFile = path.join(this.configDir, 'server.pid');
|
|
122
|
+
const logFile = path.join(this.configDir, 'server.log');
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await fs.unlink(pidFile);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
// Ignore
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await fs.unlink(logFile);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Ignore
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log('β
Configuration reset complete');
|
|
138
|
+
console.log('Run "terminal-mirror setup" to reconfigure');
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('β Reset failed:', error.message);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async setConfig(keyValue) {
|
|
147
|
+
const [key, value] = keyValue.split('=');
|
|
148
|
+
|
|
149
|
+
if (!key || !value) {
|
|
150
|
+
console.error('β Invalid format. Use: key=value');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`π§ Setting ${key} = ${value}`);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Read current .env file
|
|
158
|
+
let envContent = '';
|
|
159
|
+
try {
|
|
160
|
+
envContent = await fs.readFile(this.envFile, 'utf8');
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.log('β οΈ .env file not found, creating new one');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Update or add the key
|
|
166
|
+
const lines = envContent.split('\n');
|
|
167
|
+
let found = false;
|
|
168
|
+
const updatedLines = lines.map(line => {
|
|
169
|
+
if (line.startsWith(`${key}=`)) {
|
|
170
|
+
found = true;
|
|
171
|
+
return `${key}=${value}`;
|
|
172
|
+
}
|
|
173
|
+
return line;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (!found) {
|
|
177
|
+
updatedLines.push(`${key}=${value}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Write back to file
|
|
181
|
+
await fs.writeFile(this.envFile, updatedLines.join('\n'));
|
|
182
|
+
console.log('β
Configuration updated');
|
|
183
|
+
|
|
184
|
+
// Update user config if applicable
|
|
185
|
+
if (['port', 'baseUrl'].includes(key)) {
|
|
186
|
+
try {
|
|
187
|
+
const userConfig = JSON.parse(await fs.readFile(this.configFile, 'utf8'));
|
|
188
|
+
userConfig[key] = value;
|
|
189
|
+
userConfig.lastModified = new Date().toISOString();
|
|
190
|
+
await fs.writeFile(this.configFile, JSON.stringify(userConfig, null, 2));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// User config doesn't exist, that's ok
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error('β Failed to update configuration:', error.message);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = new ConfigManager();
|