redep 2.0.2 → 2.1.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/.dockerignore +63 -0
- package/.github/workflows/ci-cd.yml +8 -0
- package/Dockerfile +46 -16
- package/README.md +24 -0
- package/bin/index.js +227 -39
- package/docs/ADVANCED_CONFIG.md +66 -13
- package/docs/DOCKER.md +189 -0
- package/lib/client/client.js +5 -1
- package/lib/config.js +205 -1
- package/package.json +2 -5
- package/test-target/docker-compose.yml +0 -0
- package/test-target/html/index.html +14 -0
package/.dockerignore
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Node.js artifacts
|
|
2
|
+
node_modules
|
|
3
|
+
npm-debug.log*
|
|
4
|
+
yarn-debug.log*
|
|
5
|
+
yarn-error.log*
|
|
6
|
+
|
|
7
|
+
# Build and dependency artifacts
|
|
8
|
+
coverage/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.tgz
|
|
12
|
+
*.tar.gz
|
|
13
|
+
|
|
14
|
+
# Development and IDE files
|
|
15
|
+
.git/
|
|
16
|
+
.gitignore
|
|
17
|
+
.gitattributes
|
|
18
|
+
.vscode/
|
|
19
|
+
.idea/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
*~
|
|
23
|
+
|
|
24
|
+
# Environment and configuration
|
|
25
|
+
.env
|
|
26
|
+
.env.*
|
|
27
|
+
!.env.example
|
|
28
|
+
.env.local
|
|
29
|
+
.env.development.local
|
|
30
|
+
.env.test.local
|
|
31
|
+
.env.production.local
|
|
32
|
+
|
|
33
|
+
# Documentation and licenses
|
|
34
|
+
README.md
|
|
35
|
+
LICENSE
|
|
36
|
+
docs/
|
|
37
|
+
CHANGELOG.md
|
|
38
|
+
CONTRIBUTING.md
|
|
39
|
+
|
|
40
|
+
# Test files
|
|
41
|
+
test-target/
|
|
42
|
+
*.test.js
|
|
43
|
+
*.spec.js
|
|
44
|
+
__tests__/
|
|
45
|
+
__mocks__/
|
|
46
|
+
|
|
47
|
+
# Log files
|
|
48
|
+
*.log
|
|
49
|
+
logs/
|
|
50
|
+
|
|
51
|
+
# Docker files themselves
|
|
52
|
+
Dockerfile
|
|
53
|
+
.dockerignore
|
|
54
|
+
|
|
55
|
+
# OS and backup files
|
|
56
|
+
.DS_Store
|
|
57
|
+
Thumbs.db
|
|
58
|
+
|
|
59
|
+
# Package manager files
|
|
60
|
+
package-lock.json
|
|
61
|
+
npm-shrinkwrap.json
|
|
62
|
+
yarn.lock
|
|
63
|
+
sonar-project.js
|
|
@@ -46,6 +46,14 @@ jobs:
|
|
|
46
46
|
tags: ${{ steps.meta.outputs.tags }}
|
|
47
47
|
labels: ${{ steps.meta.outputs.labels }}
|
|
48
48
|
|
|
49
|
+
- name: Update Docker Hub Description
|
|
50
|
+
uses: peter-evans/dockerhub-description@v4
|
|
51
|
+
with:
|
|
52
|
+
username: ${{ secrets.DOCKER_USERNAME }}
|
|
53
|
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
54
|
+
repository: nafies1/redep
|
|
55
|
+
readme-filepath: ./docs/DOCKER.md
|
|
56
|
+
|
|
49
57
|
npm:
|
|
50
58
|
name: Publish to npm
|
|
51
59
|
runs-on: ubuntu-latest
|
package/Dockerfile
CHANGED
|
@@ -1,27 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
# Stage 1: Dependencies
|
|
2
|
+
FROM node:20-alpine AS deps
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Copy package files to install dependencies
|
|
6
|
+
COPY package.json ./
|
|
2
7
|
|
|
3
|
-
# Install
|
|
4
|
-
#
|
|
5
|
-
RUN
|
|
8
|
+
# Install only production dependencies for smaller image size
|
|
9
|
+
# First try with package-lock.json, fallback to npm install if not available
|
|
10
|
+
RUN if [ -f package-lock.json ]; then \
|
|
11
|
+
npm ci --only=production; \
|
|
12
|
+
else \
|
|
13
|
+
npm install --only=production; \
|
|
14
|
+
fi && \
|
|
15
|
+
npm cache clean --force
|
|
6
16
|
|
|
17
|
+
# Stage 2: Runtime
|
|
18
|
+
FROM node:20-alpine AS runner
|
|
7
19
|
WORKDIR /app
|
|
8
20
|
|
|
9
|
-
# Install
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
# Install minimal Docker CLI and dumb-init (remove compose plugin if not essential)
|
|
22
|
+
RUN apk add --no-cache \
|
|
23
|
+
docker-cli \
|
|
24
|
+
dumb-init && \
|
|
25
|
+
# Clean up apk cache to save space
|
|
26
|
+
rm -rf /var/cache/apk/*
|
|
27
|
+
|
|
28
|
+
# Set environment to production
|
|
29
|
+
ENV NODE_ENV=production
|
|
12
30
|
|
|
13
|
-
#
|
|
14
|
-
COPY
|
|
31
|
+
# Copy dependencies from deps stage
|
|
32
|
+
COPY --from=deps /app/node_modules ./node_modules
|
|
33
|
+
|
|
34
|
+
# Copy only essential application files (avoid copying everything)
|
|
35
|
+
COPY package.json ./
|
|
36
|
+
COPY bin/ ./bin/
|
|
37
|
+
COPY lib/ ./lib/
|
|
15
38
|
|
|
16
39
|
# Make the CLI executable
|
|
17
|
-
RUN chmod +x bin/index.js
|
|
40
|
+
RUN chmod +x bin/index.js && \
|
|
41
|
+
# Remove development files that might have been copied
|
|
42
|
+
find . -name "*.md" -delete && \
|
|
43
|
+
find . -name "*.test.js" -delete && \
|
|
44
|
+
find . -name "test" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
18
45
|
|
|
19
|
-
# Expose the
|
|
46
|
+
# Expose the port the server listens on
|
|
20
47
|
EXPOSE 3000
|
|
21
48
|
|
|
22
|
-
# Health check
|
|
23
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
24
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost
|
|
49
|
+
# Health check to ensure the server is responsive
|
|
50
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
|
51
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:${SERVER_PORT:-3000}/health || exit 1
|
|
52
|
+
|
|
53
|
+
# Use dumb-init as the entrypoint to handle signals
|
|
54
|
+
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
|
25
55
|
|
|
26
|
-
#
|
|
27
|
-
CMD ["node", "bin/index.js", "listen"]
|
|
56
|
+
# Default command to start the server
|
|
57
|
+
CMD ["node", "bin/index.js", "listen"]
|
package/README.md
CHANGED
|
@@ -49,6 +49,8 @@ The server is the agent that runs on your remote machine and executes the deploy
|
|
|
49
49
|
|
|
50
50
|
#### Option A: Using Docker (Recommended)
|
|
51
51
|
|
|
52
|
+
**Linux / macOS (Bash):**
|
|
53
|
+
|
|
52
54
|
```bash
|
|
53
55
|
docker run -d \
|
|
54
56
|
--name redep-server \
|
|
@@ -62,6 +64,21 @@ docker run -d \
|
|
|
62
64
|
nafies1/redep:latest
|
|
63
65
|
```
|
|
64
66
|
|
|
67
|
+
**Windows (PowerShell):**
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
docker run -d `
|
|
71
|
+
--name redep-server `
|
|
72
|
+
--restart always `
|
|
73
|
+
-p 3000:3000 `
|
|
74
|
+
-v /var/run/docker.sock:/var/run/docker.sock `
|
|
75
|
+
-v ${PWD}:/app/workspace `
|
|
76
|
+
-e SECRET_KEY=your-super-secret-key `
|
|
77
|
+
-e WORKING_DIR=/app/workspace `
|
|
78
|
+
-e DEPLOYMENT_COMMAND="docker compose pull && docker compose up -d" `
|
|
79
|
+
nafies1/redep:latest
|
|
80
|
+
```
|
|
81
|
+
|
|
65
82
|
#### Option B: Using npm & PM2
|
|
66
83
|
|
|
67
84
|
```bash
|
|
@@ -94,6 +111,7 @@ redep deploy prod
|
|
|
94
111
|
```
|
|
95
112
|
|
|
96
113
|
You will see:
|
|
114
|
+
|
|
97
115
|
```text
|
|
98
116
|
(INFO) Connecting to http://your-server-ip:3000...
|
|
99
117
|
(SUCCESS) Connected to server. requesting deployment...
|
|
@@ -108,6 +126,7 @@ You will see:
|
|
|
108
126
|
## ⚙️ Configuration
|
|
109
127
|
|
|
110
128
|
`redep` uses a hierarchical configuration system:
|
|
129
|
+
|
|
111
130
|
1. **Environment Variables** (Highest Priority)
|
|
112
131
|
2. **Config File** (Managed via CLI)
|
|
113
132
|
|
|
@@ -118,23 +137,27 @@ See [Advanced Configuration](docs/ADVANCED_CONFIG.md) for full details on enviro
|
|
|
118
137
|
## 💻 Development Setup
|
|
119
138
|
|
|
120
139
|
### Prerequisites
|
|
140
|
+
|
|
121
141
|
- Node.js >= 18
|
|
122
142
|
- Docker (for testing container builds)
|
|
123
143
|
|
|
124
144
|
### Local Development
|
|
125
145
|
|
|
126
146
|
1. **Clone the repository:**
|
|
147
|
+
|
|
127
148
|
```bash
|
|
128
149
|
git clone https://github.com/nafies1/redep.git
|
|
129
150
|
cd redep
|
|
130
151
|
```
|
|
131
152
|
|
|
132
153
|
2. **Install dependencies:**
|
|
154
|
+
|
|
133
155
|
```bash
|
|
134
156
|
npm install
|
|
135
157
|
```
|
|
136
158
|
|
|
137
159
|
3. **Link globally (optional):**
|
|
160
|
+
|
|
138
161
|
```bash
|
|
139
162
|
npm link
|
|
140
163
|
```
|
|
@@ -161,6 +184,7 @@ See [.github/workflows/ci-cd.yml](.github/workflows/ci-cd.yml) for details.
|
|
|
161
184
|
|
|
162
185
|
## 📚 Additional Documentation
|
|
163
186
|
|
|
187
|
+
- [Docker Guide](docs/DOCKER.md) - Full reference for Docker deployment, configuration, and security.
|
|
164
188
|
- [Troubleshooting Guide](docs/TROUBLESHOOTING.md) - Solutions for common connection and auth issues.
|
|
165
189
|
- [Advanced Configuration](docs/ADVANCED_CONFIG.md) - Deep dive into config options and PM2.
|
|
166
190
|
- [API Reference](docs/API.md) - Socket.IO events and protocol details.
|
package/bin/index.js
CHANGED
|
@@ -5,8 +5,10 @@ import { Command } from 'commander';
|
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
7
|
import inquirer from 'inquirer';
|
|
8
|
+
import Table from 'cli-table3';
|
|
9
|
+
import chalk from 'chalk';
|
|
8
10
|
import { logger } from '../lib/logger.js';
|
|
9
|
-
import { getConfig, setConfig, getAllConfig, clearConfig } from '../lib/config.js';
|
|
11
|
+
import { getConfig, setConfig, getAllConfig, clearConfig, getDetailedConfig, getClientConfig, getServerConfig, validateMandatoryConfig, getMissingConfigMessage } from '../lib/config.js';
|
|
10
12
|
import { startServer } from '../lib/server/index.js';
|
|
11
13
|
import { deploy } from '../lib/client/index.js';
|
|
12
14
|
import pkg from '../package.json' with { type: 'json' };
|
|
@@ -38,10 +40,17 @@ program
|
|
|
38
40
|
try {
|
|
39
41
|
if (type === 'client') {
|
|
40
42
|
const answers = await inquirer.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: 'input',
|
|
45
|
+
name: 'server_name',
|
|
46
|
+
message: 'Enter Server Name:',
|
|
47
|
+
default: 'prod',
|
|
48
|
+
validate: (input) => (input ? true : 'Server Name is required'),
|
|
49
|
+
},
|
|
41
50
|
{
|
|
42
51
|
type: 'input',
|
|
43
52
|
name: 'server_url',
|
|
44
|
-
message: 'Enter Server URL:',
|
|
53
|
+
message: 'Enter Server URL (Host):',
|
|
45
54
|
validate: (input) => (input ? true : 'Server URL is required'),
|
|
46
55
|
},
|
|
47
56
|
{
|
|
@@ -52,9 +61,13 @@ program
|
|
|
52
61
|
},
|
|
53
62
|
]);
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
const servers = getConfig('servers') || {};
|
|
65
|
+
servers[answers.server_name] = {
|
|
66
|
+
url: answers.server_url,
|
|
67
|
+
secret: answers.secret_key,
|
|
68
|
+
};
|
|
69
|
+
setConfig('servers', servers);
|
|
70
|
+
logger.success(`Client configuration for '${answers.server_name}' saved successfully.`);
|
|
58
71
|
} else {
|
|
59
72
|
const answers = await inquirer.prompt([
|
|
60
73
|
{
|
|
@@ -153,12 +166,182 @@ configCommand
|
|
|
153
166
|
});
|
|
154
167
|
|
|
155
168
|
configCommand
|
|
156
|
-
.command('list')
|
|
157
|
-
.description('List all
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
.command('list [type]')
|
|
170
|
+
.description('List configurations (client, server, or all)')
|
|
171
|
+
.option('--json', 'Output as JSON')
|
|
172
|
+
.option('--sort <field>', 'Sort by: key, modified, source, security', 'key')
|
|
173
|
+
.action((type, options) => {
|
|
174
|
+
// Handle different list types
|
|
175
|
+
if (type === 'client') {
|
|
176
|
+
const clientConfig = getClientConfig();
|
|
177
|
+
|
|
178
|
+
if (options.json) {
|
|
179
|
+
console.log(JSON.stringify(clientConfig, null, 2));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (clientConfig.length === 0) {
|
|
184
|
+
logger.warn('No client servers configured. Use "redep init client" to add servers.');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Create table for client config
|
|
189
|
+
const table = new Table({
|
|
190
|
+
head: ['Server', 'Host', 'Secret Key', 'Description', 'Security'],
|
|
191
|
+
style: { head: ['bold', 'white'] },
|
|
192
|
+
wordWrap: true,
|
|
193
|
+
colWidths: [15, 35, 15, 30, 12],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
clientConfig.forEach((item) => {
|
|
197
|
+
let server = item.server;
|
|
198
|
+
let host = item.host;
|
|
199
|
+
let secret = item.secret_key;
|
|
200
|
+
let description = item.description;
|
|
201
|
+
let security = item.security;
|
|
202
|
+
|
|
203
|
+
// Color coding based on security
|
|
204
|
+
if (item.security === 'high') {
|
|
205
|
+
security = chalk.green(security);
|
|
206
|
+
server = chalk.green(server);
|
|
207
|
+
} else if (item.security === 'medium') {
|
|
208
|
+
security = chalk.yellow(security);
|
|
209
|
+
server = chalk.yellow(server);
|
|
210
|
+
} else if (item.security === 'low') {
|
|
211
|
+
security = chalk.cyan(security);
|
|
212
|
+
server = chalk.cyan(server);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
table.push([server, host, secret, description, security]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
logger.info('Client Server Configurations:');
|
|
219
|
+
console.log(table.toString());
|
|
220
|
+
|
|
221
|
+
} else if (type === 'server') {
|
|
222
|
+
const serverConfig = getServerConfig();
|
|
223
|
+
|
|
224
|
+
if (options.json) {
|
|
225
|
+
console.log(JSON.stringify(serverConfig, null, 2));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Create table for server config
|
|
230
|
+
const table = new Table({
|
|
231
|
+
head: ['Key', 'Value', 'Default', 'Source', 'Updated', 'Security'],
|
|
232
|
+
style: { head: ['bold', 'white'] },
|
|
233
|
+
wordWrap: true,
|
|
234
|
+
colWidths: [20, 30, 15, 15, 25, 10],
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
serverConfig.forEach((item) => {
|
|
238
|
+
let key = item.key;
|
|
239
|
+
let value = typeof item.value === 'object' ? JSON.stringify(item.value) : String(item.value || '');
|
|
240
|
+
let def = typeof item.defaultValue === 'object' ? JSON.stringify(item.defaultValue) : String(item.defaultValue !== undefined ? item.defaultValue : '-');
|
|
241
|
+
let source = item.source;
|
|
242
|
+
let updated = item.updatedAt === 'N/A' ? '-' : new Date(item.updatedAt).toLocaleString();
|
|
243
|
+
let security = item.security || 'unknown';
|
|
244
|
+
|
|
245
|
+
// Color coding
|
|
246
|
+
if (item.source === 'Environment') {
|
|
247
|
+
source = chalk.cyan(source);
|
|
248
|
+
key = chalk.cyan(key);
|
|
249
|
+
} else if (item.isModified) {
|
|
250
|
+
source = chalk.yellow(source);
|
|
251
|
+
key = chalk.yellow(key);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (['critical', 'high'].includes(item.security)) {
|
|
255
|
+
security = chalk.red(security);
|
|
256
|
+
if (item.security === 'critical') value = '********';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
table.push([key, value, def, source, updated, security]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
logger.info('Server Configuration:');
|
|
263
|
+
console.log(table.toString());
|
|
264
|
+
|
|
265
|
+
} else {
|
|
266
|
+
// Default behavior - show all config (backward compatibility)
|
|
267
|
+
const detailed = getDetailedConfig();
|
|
268
|
+
|
|
269
|
+
// Sorting Logic
|
|
270
|
+
detailed.sort((a, b) => {
|
|
271
|
+
if (options.sort === 'key') return a.key.localeCompare(b.key);
|
|
272
|
+
if (options.sort === 'modified') return (b.updatedAt || '').localeCompare(a.updatedAt || '');
|
|
273
|
+
if (options.sort === 'source') return a.source.localeCompare(b.source);
|
|
274
|
+
if (options.sort === 'security') {
|
|
275
|
+
const levels = { critical: 0, high: 1, medium: 2, low: 3, unknown: 4 };
|
|
276
|
+
return (levels[a.security] || 4) - (levels[b.security] || 4);
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (options.json) {
|
|
282
|
+
console.log(JSON.stringify(detailed, null, 2));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Table Setup (existing code)
|
|
287
|
+
const table = new Table({
|
|
288
|
+
head: ['Key', 'Value', 'Default', 'Source', 'Updated', 'Sec'],
|
|
289
|
+
style: { head: ['bold', 'white'] },
|
|
290
|
+
wordWrap: true,
|
|
291
|
+
colWidths: [20, 30, 15, 15, 25, 10],
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
let stats = { total: 0, modified: 0, env: 0, security: 0 };
|
|
295
|
+
|
|
296
|
+
detailed.forEach((item) => {
|
|
297
|
+
stats.total++;
|
|
298
|
+
if (item.source === 'Environment') stats.env++;
|
|
299
|
+
if (item.isModified) stats.modified++;
|
|
300
|
+
if (['critical', 'high'].includes(item.security)) stats.security++;
|
|
301
|
+
|
|
302
|
+
let key = item.key;
|
|
303
|
+
let value = typeof item.value === 'object' ? JSON.stringify(item.value) : String(item.value || '');
|
|
304
|
+
let def = typeof item.defaultValue === 'object' ? JSON.stringify(item.defaultValue) : String(item.defaultValue !== undefined ? item.defaultValue : '-');
|
|
305
|
+
let source = item.source;
|
|
306
|
+
let updated = item.updatedAt === 'N/A' ? '-' : new Date(item.updatedAt).toLocaleString();
|
|
307
|
+
let sec = item.security || 'unknown';
|
|
308
|
+
|
|
309
|
+
// Color Coding
|
|
310
|
+
if (item.source === 'Environment') {
|
|
311
|
+
source = chalk.cyan(source);
|
|
312
|
+
key = chalk.cyan(key);
|
|
313
|
+
} else if (item.isModified) {
|
|
314
|
+
source = chalk.yellow(source);
|
|
315
|
+
key = chalk.yellow(key);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (['critical', 'high'].includes(item.security)) {
|
|
319
|
+
sec = chalk.red(sec);
|
|
320
|
+
if (item.security === 'critical') value = '********';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
table.push([key, value, def, source, updated, sec]);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
logger.info('Current Configuration:');
|
|
327
|
+
console.log(table.toString());
|
|
328
|
+
|
|
329
|
+
// Summary Statistics
|
|
330
|
+
console.log('\nSummary Statistics:');
|
|
331
|
+
console.log(
|
|
332
|
+
`Total: ${stats.total} | Modified: ${chalk.yellow(stats.modified)} | Env Overrides: ${chalk.cyan(
|
|
333
|
+
stats.env
|
|
334
|
+
)} | Security Critical: ${chalk.red(stats.security)}`
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Help Section
|
|
338
|
+
console.log('\nLegend:');
|
|
339
|
+
console.log(
|
|
340
|
+
`${chalk.cyan('Cyan')}: Environment Override | ${chalk.yellow(
|
|
341
|
+
'Yellow'
|
|
342
|
+
)}: Modified/File | ${chalk.red('Red')}: High Security`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
162
345
|
});
|
|
163
346
|
|
|
164
347
|
configCommand
|
|
@@ -177,6 +360,14 @@ program
|
|
|
177
360
|
.description('Start the server in background (daemon mode) using PM2 if available')
|
|
178
361
|
.option('-p, --port <port>', 'Port to listen on')
|
|
179
362
|
.action((options) => {
|
|
363
|
+
// Validate mandatory configuration before starting
|
|
364
|
+
const missingParams = validateMandatoryConfig();
|
|
365
|
+
if (missingParams.length > 0) {
|
|
366
|
+
const errorMessage = getMissingConfigMessage(missingParams);
|
|
367
|
+
logger.error(errorMessage);
|
|
368
|
+
process.exit(1);
|
|
369
|
+
}
|
|
370
|
+
|
|
180
371
|
// Try to use PM2 first
|
|
181
372
|
try {
|
|
182
373
|
// Check if PM2 is available via API
|
|
@@ -322,49 +513,46 @@ program
|
|
|
322
513
|
program
|
|
323
514
|
.command('listen')
|
|
324
515
|
.description('Start the server to listen for commands')
|
|
325
|
-
.option('-p, --port <port>', 'Port to listen on'
|
|
516
|
+
.option('-p, --port <port>', 'Port to listen on')
|
|
326
517
|
.action((options) => {
|
|
518
|
+
// Validate mandatory configuration before starting
|
|
519
|
+
const missingParams = validateMandatoryConfig();
|
|
520
|
+
if (missingParams.length > 0) {
|
|
521
|
+
const errorMessage = getMissingConfigMessage(missingParams);
|
|
522
|
+
logger.error(errorMessage);
|
|
523
|
+
process.exit(1);
|
|
524
|
+
}
|
|
525
|
+
|
|
327
526
|
const port = options.port || getConfig('server_port') || process.env.SERVER_PORT || 3000;
|
|
328
527
|
const secret = getConfig('secret_key') || process.env.SECRET_KEY;
|
|
329
528
|
|
|
330
|
-
if (!secret) {
|
|
331
|
-
logger.warn(
|
|
332
|
-
'Warning: No "secret_key" set in config or SECRET_KEY env var. Communication might be insecure or fail if client requires it.'
|
|
333
|
-
);
|
|
334
|
-
logger.info('Run "redep config set secret_key <your-secret>" or set SECRET_KEY env var.');
|
|
335
|
-
}
|
|
336
|
-
|
|
337
529
|
const workingDir = getConfig('working_dir') || process.env.WORKING_DIR;
|
|
338
|
-
if (!workingDir) {
|
|
339
|
-
logger.error(
|
|
340
|
-
'Error: "working_dir" is not set. Please set it using "redep config set working_dir <path>" or WORKING_DIR env var.'
|
|
341
|
-
);
|
|
342
|
-
process.exit(1);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
530
|
const deploymentCommand = getConfig('deployment_command') || process.env.DEPLOYMENT_COMMAND;
|
|
346
|
-
if (!deploymentCommand) {
|
|
347
|
-
logger.error(
|
|
348
|
-
'Error: "deployment_command" is not set. Please set it using "redep config set deployment_command <cmd>" or DEPLOYMENT_COMMAND env var.'
|
|
349
|
-
);
|
|
350
|
-
process.exit(1);
|
|
351
|
-
}
|
|
352
531
|
|
|
353
532
|
startServer(port, secret, workingDir, deploymentCommand);
|
|
354
533
|
});
|
|
355
534
|
|
|
356
535
|
// Client Command
|
|
357
536
|
program
|
|
358
|
-
.command('deploy <
|
|
359
|
-
.description('Deploy a
|
|
360
|
-
.action(async (
|
|
361
|
-
const
|
|
362
|
-
|
|
537
|
+
.command('deploy <serverName>')
|
|
538
|
+
.description('Deploy to a specific server (e.g., "prod")')
|
|
539
|
+
.action(async (serverName) => {
|
|
540
|
+
const servers = getConfig('servers') || {};
|
|
541
|
+
let serverUrl, secret;
|
|
542
|
+
|
|
543
|
+
if (servers[serverName]) {
|
|
544
|
+
serverUrl = servers[serverName].url;
|
|
545
|
+
secret = servers[serverName].secret;
|
|
546
|
+
} else {
|
|
547
|
+
serverUrl = getConfig('server_url') || process.env.SERVER_URL;
|
|
548
|
+
secret = getConfig('secret_key') || process.env.SECRET_KEY;
|
|
549
|
+
}
|
|
363
550
|
|
|
364
551
|
if (!serverUrl) {
|
|
365
552
|
logger.error(
|
|
366
|
-
|
|
553
|
+
`Error: Server "${serverName}" not found in config, and global "server_url" is not set.`
|
|
367
554
|
);
|
|
555
|
+
logger.info('Run "redep init client" to configure a server.');
|
|
368
556
|
process.exit(1);
|
|
369
557
|
}
|
|
370
558
|
|
|
@@ -376,7 +564,7 @@ program
|
|
|
376
564
|
}
|
|
377
565
|
|
|
378
566
|
try {
|
|
379
|
-
await deploy(
|
|
567
|
+
await deploy(serverName, serverUrl, secret);
|
|
380
568
|
} catch (error) {
|
|
381
569
|
logger.error(`Deploy failed: ${error.message}`);
|
|
382
570
|
process.exit(1);
|
package/docs/ADVANCED_CONFIG.md
CHANGED
|
@@ -26,21 +26,29 @@ The CLI `redep start` command automatically attempts to use PM2 if installed.
|
|
|
26
26
|
|
|
27
27
|
Clients can be configured to talk to multiple servers (e.g., `dev`, `staging`, `prod`).
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
29
|
+
### Client Server Configuration Table
|
|
30
|
+
|
|
31
|
+
| Servers | Host | Secret Key | Description |
|
|
32
|
+
| --------- | ----------------------------- | ---------------- | ----------------------------------- |
|
|
33
|
+
| `prod` | `https://deploy.example.com` | `prod-secret` | Production server with HTTPS |
|
|
34
|
+
| `staging` | `http://10.0.0.5:3000` | `staging-secret` | Staging server on internal network |
|
|
35
|
+
| `uat` | `http://uat.company.com:3000` | `uat-secret` | User Acceptance Testing environment |
|
|
36
|
+
| `dev` | `http://localhost:3000` | `dev-secret` | Local development server |
|
|
37
|
+
|
|
38
|
+
### Viewing Client Configuration
|
|
39
|
+
|
|
40
|
+
Use the new `redep config list client` command to display your client configurations in a structured table format:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Display all client server configurations
|
|
44
|
+
redep config list client
|
|
45
|
+
|
|
46
|
+
# Display with custom sorting
|
|
47
|
+
redep config list client --sort host
|
|
42
48
|
```
|
|
43
49
|
|
|
50
|
+
This will show a formatted table with columns for Server Name, Host URL, and Security Level, making it easy to review all your configured deployment targets.
|
|
51
|
+
|
|
44
52
|
### Managing Servers via CLI
|
|
45
53
|
|
|
46
54
|
```bash
|
|
@@ -52,6 +60,51 @@ redep config set servers.dev.secret_key mysecret
|
|
|
52
60
|
redep config get servers.dev
|
|
53
61
|
```
|
|
54
62
|
|
|
63
|
+
### Viewing Configuration with New List Commands
|
|
64
|
+
|
|
65
|
+
The enhanced `redep config list` command now supports viewing client and server configurations separately:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# View all client server configurations
|
|
69
|
+
redep config list client
|
|
70
|
+
|
|
71
|
+
# View server configuration only
|
|
72
|
+
redep config list server
|
|
73
|
+
|
|
74
|
+
# View all configurations (backward compatibility)
|
|
75
|
+
redep config list
|
|
76
|
+
|
|
77
|
+
# View with JSON output
|
|
78
|
+
redep config list client --json
|
|
79
|
+
redep config list server --json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Client Configuration Table Example:**
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
┌─────────┬──────────────────────────────┬─────────────┬─────────────────────────────────────┬──────────┐
|
|
86
|
+
│ Server │ Host │ Secret Key │ Description │ Security │
|
|
87
|
+
├─────────┼──────────────────────────────┼─────────────┼─────────────────────────────────────┼──────────┤
|
|
88
|
+
│ prod │ https://deploy.example.com │ ******** │ Production environment with HTTPS │ high │
|
|
89
|
+
│ staging │ http://10.0.0.5:3000 │ ******** │ Staging environment for testing │ medium │
|
|
90
|
+
│ uat │ http://uat.company.com:3000 │ ******** │ User Acceptance Testing environment │ medium │
|
|
91
|
+
│ dev │ http://localhost:3000 │ ******** │ Local development server │ low │
|
|
92
|
+
└─────────┴──────────────────────────────┴─────────────┴─────────────────────────────────────┴──────────┘
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Server Configuration Table Example:**
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
┌────────────────────┬──────────────────────────────┬───────────────┬───────────────┬─────────────────────────┬──────────┐
|
|
99
|
+
│ Key │ Value │ Default │ Source │ Updated │ Security │
|
|
100
|
+
├────────────────────┼──────────────────────────────┼───────────────┼───────────────┼─────────────────────────┼──────────┤
|
|
101
|
+
│ server_port │ 3000 │ 3000 │ Environment │ 2026-01-22 14:28:14 │ low │
|
|
102
|
+
│ secret_key │ ******** │ null │ File │ 2026-01-22 14:30:22 │ critical │
|
|
103
|
+
│ working_dir │ /app/workspace │ null │ Environment │ - │ medium │
|
|
104
|
+
│ deployment_command │ docker compose up -d │ null │ File │ - │ high │
|
|
105
|
+
└────────────────────┴──────────────────────────────┴───────────────┴───────────────┴─────────────────────────┴──────────┘
|
|
106
|
+
```
|
|
107
|
+
|
|
55
108
|
## Security Best Practices
|
|
56
109
|
|
|
57
110
|
1. **TLS/SSL**: Always use HTTPS for the `host` URL in production. The WebSocket connection will automatically use WSS (Secure WebSocket).
|
package/docs/DOCKER.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# 🐳 Docker Configuration & Usage Guide
|
|
2
|
+
|
|
3
|
+
This guide provides comprehensive instructions for running `redep` server using Docker. The Docker image is optimized for size and security, based on Alpine Linux.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
### 1. Pull the Image
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
docker pull nafies1/redep:latest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Run the Server
|
|
14
|
+
|
|
15
|
+
To run the server, you need to provide a **Secret Key** and mount the **Docker Socket** (so `redep` can execute Docker commands on the host).
|
|
16
|
+
|
|
17
|
+
#### Linux / macOS (Bash)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
docker run -d \
|
|
21
|
+
--name redep-server \
|
|
22
|
+
--restart always \
|
|
23
|
+
-p 3000:3000 \
|
|
24
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
25
|
+
-v $(pwd)/workspace:/app/workspace \
|
|
26
|
+
-e SECRET_KEY=your-super-secret-key \
|
|
27
|
+
-e WORKING_DIR=/app/workspace \
|
|
28
|
+
-e DEPLOYMENT_COMMAND="docker compose up -d" \
|
|
29
|
+
nafies1/redep:latest
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
#### Windows (PowerShell)
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
docker run -d `
|
|
36
|
+
--name redep-server `
|
|
37
|
+
--restart always `
|
|
38
|
+
-p 3000:3000 `
|
|
39
|
+
-v /var/run/docker.sock:/var/run/docker.sock `
|
|
40
|
+
-v ${PWD}/workspace:/app/workspace `
|
|
41
|
+
-e SECRET_KEY=your-super-secret-key `
|
|
42
|
+
-e WORKING_DIR=/app/workspace `
|
|
43
|
+
-e DEPLOYMENT_COMMAND="docker compose up -d" `
|
|
44
|
+
nafies1/redep:latest
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Windows (Command Prompt)
|
|
48
|
+
|
|
49
|
+
```cmd
|
|
50
|
+
docker run -d ^
|
|
51
|
+
--name redep-server ^
|
|
52
|
+
--restart always ^
|
|
53
|
+
-p 3000:3000 ^
|
|
54
|
+
-v /var/run/docker.sock:/var/run/docker.sock ^
|
|
55
|
+
-v %cd%/workspace:/app/workspace ^
|
|
56
|
+
-e SECRET_KEY=your-super-secret-key ^
|
|
57
|
+
-e WORKING_DIR=/app/workspace ^
|
|
58
|
+
-e DEPLOYMENT_COMMAND="docker compose up -d" ^
|
|
59
|
+
nafies1/redep:latest
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## ⚙️ Configuration Reference
|
|
65
|
+
|
|
66
|
+
### Environment Variables
|
|
67
|
+
|
|
68
|
+
| Variable | Description | Default | Required |
|
|
69
|
+
| -------------------- | ----------------------------------------------------------- | ------------ | -------- |
|
|
70
|
+
| `SECRET_KEY` | Secret token for authentication with the client. | - | **Yes** |
|
|
71
|
+
| `WORKING_DIR` | Directory inside the container where commands are executed. | - | **Yes** |
|
|
72
|
+
| `DEPLOYMENT_COMMAND` | Command to execute when a deployment is triggered. | - | **Yes** |
|
|
73
|
+
| `SERVER_PORT` | Port the server listens on inside the container. | `3000` | No |
|
|
74
|
+
| `NODE_ENV` | Node.js environment mode. | `production` | No |
|
|
75
|
+
|
|
76
|
+
### Volume Mounts
|
|
77
|
+
|
|
78
|
+
| Host Path | Container Path | Purpose |
|
|
79
|
+
| ---------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
80
|
+
| `/var/run/docker.sock` | `/var/run/docker.sock` | **Required**. Allows the container to control the host's Docker daemon. |
|
|
81
|
+
| `./workspace` | `/app/workspace` | **Recommended**. Persist your project files (e.g., `docker-compose.yml`) so they survive container restarts. |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 📦 Docker Compose Examples
|
|
86
|
+
|
|
87
|
+
### Basic Setup
|
|
88
|
+
|
|
89
|
+
Create a `docker-compose.redep.yml`:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
services:
|
|
93
|
+
redep-server:
|
|
94
|
+
image: nafies1/redep:latest
|
|
95
|
+
container_name: redep-server
|
|
96
|
+
restart: always
|
|
97
|
+
ports:
|
|
98
|
+
- '3000:3000'
|
|
99
|
+
volumes:
|
|
100
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
101
|
+
- ./my-project:/app/workspace
|
|
102
|
+
environment:
|
|
103
|
+
- SECRET_KEY=change-me-to-something-secure
|
|
104
|
+
- WORKING_DIR=/app/workspace
|
|
105
|
+
- DEPLOYMENT_COMMAND=docker compose pull && docker compose up -d
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Run with:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
docker compose -f docker-compose.redep.yml up -d
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Advanced Setup (Traefik + Watchtower)
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
services:
|
|
118
|
+
redep-server:
|
|
119
|
+
image: nafies1/redep:latest
|
|
120
|
+
networks:
|
|
121
|
+
- web
|
|
122
|
+
volumes:
|
|
123
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
124
|
+
- ./workspace:/app/workspace
|
|
125
|
+
environment:
|
|
126
|
+
- SECRET_KEY=${REDEP_SECRET}
|
|
127
|
+
- WORKING_DIR=/app/workspace
|
|
128
|
+
- DEPLOYMENT_COMMAND=docker compose up -d --build
|
|
129
|
+
labels:
|
|
130
|
+
- 'traefik.enable=true'
|
|
131
|
+
- 'traefik.http.routers.redep.rule=Host(`deploy.example.com`)'
|
|
132
|
+
|
|
133
|
+
networks:
|
|
134
|
+
web:
|
|
135
|
+
external: true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🛠️ Troubleshooting
|
|
141
|
+
|
|
142
|
+
### 1. "Docker command not found" or Permission Denied
|
|
143
|
+
|
|
144
|
+
- **Symptom**: Deployment logs show `docker: not found` or `permission denied`.
|
|
145
|
+
- **Cause**: The container cannot access the host's Docker socket.
|
|
146
|
+
- **Fix**: Ensure you passed `-v /var/run/docker.sock:/var/run/docker.sock`. On some systems, you may need to run the container with `--group-add $(getent group docker | cut -d: -f3)` or `user: root` (though root is not recommended if avoidable).
|
|
147
|
+
|
|
148
|
+
### 2. "Connection Refused"
|
|
149
|
+
|
|
150
|
+
- **Symptom**: Client says `xhr poll error`.
|
|
151
|
+
- **Cause**: The server is not running, or the port is not mapped correctly.
|
|
152
|
+
- **Fix**: Check `docker ps`. Ensure `-p 3000:3000` matches the internal `SERVER_PORT`.
|
|
153
|
+
|
|
154
|
+
### 3. "Directory not found"
|
|
155
|
+
|
|
156
|
+
- **Symptom**: Error regarding `WORKING_DIR`.
|
|
157
|
+
- **Fix**: Ensure the `WORKING_DIR` path exists inside the container. Mounting a volume automatically creates the directory.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🔒 Security Best Practices
|
|
162
|
+
|
|
163
|
+
1. **Least Privilege**: The container runs as `root` by default to access the Docker socket easily. For higher security, consider using a non-root user and adding them to the docker group (requires custom image extension).
|
|
164
|
+
2. **Network Isolation**: Don't expose port 3000 to the public internet directly. Use a reverse proxy (Nginx, Traefik) with SSL/TLS and IP whitelisting if possible.
|
|
165
|
+
3. **Secret Management**: Do not commit your `SECRET_KEY` to version control. Use `.env` files (excluded from git) or Docker Secrets.
|
|
166
|
+
4. **Socket Exposure**: Mounting `/var/run/docker.sock` gives the container full control over the host. Ensure only trusted commands are executed via `DEPLOYMENT_COMMAND`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 🚀 Performance Tuning
|
|
171
|
+
|
|
172
|
+
- **Base Image**: We use `node:20-alpine` for a lightweight footprint (<100MB).
|
|
173
|
+
- **Memory Limit**: The server is lightweight. You can limit memory usage:
|
|
174
|
+
```bash
|
|
175
|
+
--memory="256m" --cpus="0.5"
|
|
176
|
+
```
|
|
177
|
+
- **Layer Caching**: The Dockerfile uses multi-stage builds. Dependencies are cached in a separate layer, so rebuilding the image after code changes is fast.
|
|
178
|
+
|
|
179
|
+
## 📋 Version Compatibility
|
|
180
|
+
|
|
181
|
+
| redep Version | Docker Tag | Node.js Version |
|
|
182
|
+
| ------------- | ---------- | --------------- |
|
|
183
|
+
| 2.x | `latest` | 20 (LTS) |
|
|
184
|
+
| 1.x | `v1` | 18 |
|
|
185
|
+
|
|
186
|
+
## 🔗 Additional Resources
|
|
187
|
+
|
|
188
|
+
- [Official Docker Documentation](https://docs.docker.com/)
|
|
189
|
+
- [Node.js Docker Best Practices](https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md)
|
package/lib/client/client.js
CHANGED
|
@@ -18,7 +18,11 @@ export const connectAndDeploy = (serverUrl, secret) => {
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
socket.on('connect_error', (err) => {
|
|
21
|
-
|
|
21
|
+
let msg = err.message;
|
|
22
|
+
if (msg === 'xhr poll error' || msg.includes('ECONNREFUSED')) {
|
|
23
|
+
msg = `Could not connect to server at ${serverUrl}. Is the server running?`;
|
|
24
|
+
}
|
|
25
|
+
logger.error(`Connection error: ${msg}`);
|
|
22
26
|
socket.close();
|
|
23
27
|
reject(err);
|
|
24
28
|
});
|
package/lib/config.js
CHANGED
|
@@ -5,12 +5,78 @@ const config = new Conf({
|
|
|
5
5
|
encryptionKey: 'redep-secure-storage', // Obfuscates the config file
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
export const CONFIG_SCHEMA = {
|
|
9
|
+
server_port: {
|
|
10
|
+
default: 3000,
|
|
11
|
+
security: 'low',
|
|
12
|
+
env: 'SERVER_PORT',
|
|
13
|
+
description: 'Server Port',
|
|
14
|
+
},
|
|
15
|
+
secret_key: {
|
|
16
|
+
default: null,
|
|
17
|
+
security: 'critical',
|
|
18
|
+
env: 'SECRET_KEY',
|
|
19
|
+
description: 'Authentication Secret',
|
|
20
|
+
},
|
|
21
|
+
working_dir: {
|
|
22
|
+
default: null,
|
|
23
|
+
security: 'medium',
|
|
24
|
+
env: 'WORKING_DIR',
|
|
25
|
+
description: 'Working Directory',
|
|
26
|
+
},
|
|
27
|
+
deployment_command: {
|
|
28
|
+
default: null,
|
|
29
|
+
security: 'high',
|
|
30
|
+
env: 'DEPLOYMENT_COMMAND',
|
|
31
|
+
description: 'Deployment Command',
|
|
32
|
+
},
|
|
33
|
+
server_url: {
|
|
34
|
+
default: null,
|
|
35
|
+
security: 'low',
|
|
36
|
+
env: 'SERVER_URL',
|
|
37
|
+
description: 'Default Server URL',
|
|
38
|
+
},
|
|
39
|
+
servers: {
|
|
40
|
+
default: {},
|
|
41
|
+
security: 'medium',
|
|
42
|
+
env: null,
|
|
43
|
+
description: 'Server Profiles',
|
|
44
|
+
},
|
|
45
|
+
server_pid: {
|
|
46
|
+
default: null,
|
|
47
|
+
security: 'low',
|
|
48
|
+
env: null,
|
|
49
|
+
description: 'Server Process ID',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
8
53
|
export const getConfig = (key) => {
|
|
9
|
-
|
|
54
|
+
// Check environment variables first (highest priority)
|
|
55
|
+
if (CONFIG_SCHEMA[key] && CONFIG_SCHEMA[key].env) {
|
|
56
|
+
const envValue = process.env[CONFIG_SCHEMA[key].env];
|
|
57
|
+
if (envValue !== undefined) {
|
|
58
|
+
return envValue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Then check stored config
|
|
63
|
+
const value = config.get(key);
|
|
64
|
+
|
|
65
|
+
// If value is undefined but key exists in schema, return default value
|
|
66
|
+
if (value === undefined && CONFIG_SCHEMA[key]) {
|
|
67
|
+
return CONFIG_SCHEMA[key].default;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return value;
|
|
10
71
|
};
|
|
11
72
|
|
|
12
73
|
export const setConfig = (key, value) => {
|
|
13
74
|
config.set(key, value);
|
|
75
|
+
// Store metadata for timestamp
|
|
76
|
+
// We use a flat key for metadata to avoid interfering with nested config objects if possible,
|
|
77
|
+
// but since we want per-key tracking, we store it in a hidden _meta object.
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
config.set(`_meta.${key}`, { updatedAt: now });
|
|
14
80
|
};
|
|
15
81
|
|
|
16
82
|
export const clearConfig = () => {
|
|
@@ -20,3 +86,141 @@ export const clearConfig = () => {
|
|
|
20
86
|
export const getAllConfig = () => {
|
|
21
87
|
return config.store;
|
|
22
88
|
};
|
|
89
|
+
|
|
90
|
+
export const getDetailedConfig = () => {
|
|
91
|
+
const all = config.store;
|
|
92
|
+
const meta = all._meta || {};
|
|
93
|
+
const result = [];
|
|
94
|
+
|
|
95
|
+
// Process Schema Keys
|
|
96
|
+
Object.keys(CONFIG_SCHEMA).forEach((key) => {
|
|
97
|
+
const schema = CONFIG_SCHEMA[key];
|
|
98
|
+
const fileValue = config.get(key);
|
|
99
|
+
const envValue = schema.env ? process.env[schema.env] : undefined;
|
|
100
|
+
|
|
101
|
+
let currentValue = fileValue;
|
|
102
|
+
let source = 'File';
|
|
103
|
+
|
|
104
|
+
if (envValue !== undefined) {
|
|
105
|
+
currentValue = envValue;
|
|
106
|
+
source = 'Environment';
|
|
107
|
+
} else if (fileValue !== undefined) {
|
|
108
|
+
currentValue = fileValue;
|
|
109
|
+
source = 'File';
|
|
110
|
+
} else {
|
|
111
|
+
currentValue = schema.default;
|
|
112
|
+
source = 'Default';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
result.push({
|
|
116
|
+
key,
|
|
117
|
+
value: currentValue,
|
|
118
|
+
defaultValue: schema.default,
|
|
119
|
+
source,
|
|
120
|
+
security: schema.security,
|
|
121
|
+
updatedAt: meta[key]?.updatedAt || 'N/A',
|
|
122
|
+
description: schema.description,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Process Custom Keys (not in schema)
|
|
127
|
+
Object.keys(all).forEach((key) => {
|
|
128
|
+
if (!CONFIG_SCHEMA[key] && key !== '_meta') {
|
|
129
|
+
result.push({
|
|
130
|
+
key,
|
|
131
|
+
value: all[key],
|
|
132
|
+
defaultValue: undefined,
|
|
133
|
+
source: 'File', // Assumed file for custom keys
|
|
134
|
+
security: 'unknown',
|
|
135
|
+
updatedAt: meta[key]?.updatedAt || 'N/A',
|
|
136
|
+
description: 'Custom Configuration',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const getClientConfig = () => {
|
|
145
|
+
const servers = config.get('servers') || {};
|
|
146
|
+
const result = [];
|
|
147
|
+
|
|
148
|
+
Object.keys(servers).forEach((serverName) => {
|
|
149
|
+
const server = servers[serverName];
|
|
150
|
+
result.push({
|
|
151
|
+
server: serverName,
|
|
152
|
+
host: server.url || 'Not configured',
|
|
153
|
+
secret_key: server.secret ? '********' : 'Not set',
|
|
154
|
+
description: getServerDescription(serverName),
|
|
155
|
+
security: getServerSecurityLevel(server.url),
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return result;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const getServerConfig = () => {
|
|
163
|
+
const detailed = getDetailedConfig();
|
|
164
|
+
// Filter hanya konfigurasi server (bukan client servers)
|
|
165
|
+
return detailed.filter((item) =>
|
|
166
|
+
['server_port', 'secret_key', 'working_dir', 'deployment_command', 'server_pid'].includes(item.key)
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
function getServerDescription(serverName) {
|
|
171
|
+
const descriptions = {
|
|
172
|
+
prod: 'Production environment with high security',
|
|
173
|
+
staging: 'Staging environment for testing',
|
|
174
|
+
uat: 'User Acceptance Testing environment',
|
|
175
|
+
dev: 'Development environment',
|
|
176
|
+
test: 'Testing environment',
|
|
177
|
+
};
|
|
178
|
+
return descriptions[serverName.toLowerCase()] || 'Custom environment';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const validateMandatoryConfig = () => {
|
|
182
|
+
const errors = [];
|
|
183
|
+
|
|
184
|
+
// Check SECRET_KEY
|
|
185
|
+
const secretKey = getConfig('secret_key');
|
|
186
|
+
if (!secretKey) {
|
|
187
|
+
errors.push('secret_key');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check WORKING_DIR
|
|
191
|
+
const workingDir = getConfig('working_dir');
|
|
192
|
+
if (!workingDir) {
|
|
193
|
+
errors.push('working_dir');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check DEPLOYMENT_COMMAND
|
|
197
|
+
const deploymentCommand = getConfig('deployment_command');
|
|
198
|
+
if (!deploymentCommand) {
|
|
199
|
+
errors.push('deployment_command');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return errors;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const getMissingConfigMessage = (missingParams) => {
|
|
206
|
+
if (missingParams.length === 0) return null;
|
|
207
|
+
|
|
208
|
+
const paramList = missingParams.map(param => `'${param}'`).join(', ');
|
|
209
|
+
const instructions = missingParams.map(param => {
|
|
210
|
+
const envVar = CONFIG_SCHEMA[param]?.env;
|
|
211
|
+
if (envVar) {
|
|
212
|
+
return ` - Set '${param}' using 'redep config set ${param} <value>' or add '${envVar}=<value>' to your .env file`;
|
|
213
|
+
}
|
|
214
|
+
return ` - Set '${param}' using 'redep config set ${param} <value>'`;
|
|
215
|
+
}).join('\n');
|
|
216
|
+
|
|
217
|
+
return `Error: Required configuration parameter(s) ${paramList} are not set.\n${instructions}`;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
function getServerSecurityLevel(host) {
|
|
221
|
+
if (!host) return 'unknown';
|
|
222
|
+
if (host.startsWith('https://')) return 'high';
|
|
223
|
+
if (host.startsWith('http://localhost') || host.startsWith('http://127.0.0.1')) return 'low';
|
|
224
|
+
if (host.startsWith('http://')) return 'medium';
|
|
225
|
+
return 'unknown';
|
|
226
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redep",
|
|
3
3
|
"author": "nafies1",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"axios": "^1.7.9",
|
|
38
38
|
"body-parser": "^1.20.2",
|
|
39
39
|
"chalk": "^5.3.0",
|
|
40
|
+
"cli-table3": "^0.6.5",
|
|
40
41
|
"commander": "^12.0.0",
|
|
41
42
|
"conf": "^12.0.0",
|
|
42
43
|
"cors": "^2.8.5",
|
|
@@ -53,9 +54,5 @@
|
|
|
53
54
|
"@sonar/scan": "^4.3.4",
|
|
54
55
|
"prettier": "^3.8.0",
|
|
55
56
|
"sonarqube-scanner": "^4.3.4"
|
|
56
|
-
},
|
|
57
|
-
"redep": {
|
|
58
|
-
"secret_key": "4k94jLuEIT_jWPHJYPActmGxn2x72eR0",
|
|
59
|
-
"working_dir": "C:\\Users\\nafie\\Documents\\trae_projects\\remote-deploy-cli"
|
|
60
57
|
}
|
|
61
58
|
}
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Redep Test Deployment</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<h1>Redep Docker Deployment Test</h1>
|
|
8
|
+
<p>Server successfully deployed via redep!</p>
|
|
9
|
+
<p>Time: <span id="time"></span></p>
|
|
10
|
+
<script>
|
|
11
|
+
document.getElementById('time').textContent = new Date().toLocaleString();
|
|
12
|
+
</script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|