remote-deploy-cli 1.0.0 → 1.1.1
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 +192 -77
- package/bin/index.js +141 -5
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,119 +1,234 @@
|
|
|
1
1
|
# redep (Remote Deploy CLI)
|
|
2
2
|
|
|
3
|
-
A lightweight Node.js CLI tool for remote execution using a Client-Server architecture. Designed to simplify deployment workflows
|
|
3
|
+
> A lightweight, secure Node.js CLI tool for remote execution using a Client-Server architecture. Designed to simplify deployment workflows by triggering commands (like Docker Compose) on remote servers securely.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+

|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- **Secure Communication**: Uses Token-based authentication (Bearer Token) to ensure only authorized clients can execute commands.
|
|
9
|
-
- **Configurable**: Easy configuration management for both Client and Server via CLI or Environment Variables.
|
|
10
|
-
- **Docker Integration**: Built-in support for `deploy fe` which pulls and restarts containers using Docker Compose.
|
|
11
|
-
- **Docker Support**: Can be run as a Docker container itself on the server machine.
|
|
12
|
-
- **Logging**: Detailed logs for tracking execution status.
|
|
8
|
+
**Last Updated:** 2026-01-17
|
|
13
9
|
|
|
14
|
-
##
|
|
10
|
+
## Table of Contents
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
- [Description](#description)
|
|
13
|
+
- [Architecture Overview](#architecture-overview)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Client Commands](#client-commands)
|
|
16
|
+
- [deploy](#deploy)
|
|
17
|
+
- [Client Configuration](#client-configuration)
|
|
18
|
+
- [Server Commands](#server-commands)
|
|
19
|
+
- [listen](#listen)
|
|
20
|
+
- [start (Background)](#start-background)
|
|
21
|
+
- [Server Configuration](#server-configuration)
|
|
22
|
+
- [Command Interactions](#command-interactions)
|
|
23
|
+
- [Configuration Reference](#configuration-reference)
|
|
24
|
+
- [Troubleshooting](#troubleshooting)
|
|
25
|
+
- [License](#license)
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Description
|
|
30
|
+
|
|
31
|
+
**redep** allows you to securely trigger deployment scripts on a remote server from your local machine or CI/CD pipeline.
|
|
32
|
+
|
|
33
|
+
## Architecture Overview
|
|
34
|
+
|
|
35
|
+
1. **Server (Remote Machine)**: Runs the listener process (`redep listen`). It waits for authenticated HTTP requests and executes local shell commands (e.g., `docker compose up`).
|
|
36
|
+
2. **Client (Local Machine/CI)**: Sends commands (`redep deploy`) to the Server URL.
|
|
37
|
+
|
|
38
|
+
---
|
|
20
39
|
|
|
21
40
|
## Installation
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
### Global Installation (Recommended)
|
|
43
|
+
|
|
44
|
+
To use `redep` as a command-line tool anywhere on your system:
|
|
24
45
|
|
|
25
46
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
cd remote-deploy-cli
|
|
47
|
+
npm install -g remote-deploy-cli
|
|
48
|
+
```
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
### Local Installation
|
|
51
|
+
|
|
52
|
+
If you prefer to use it within a specific project (e.g., via `npm scripts`):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install remote-deploy-cli --save-dev
|
|
32
56
|
```
|
|
33
57
|
|
|
34
|
-
|
|
58
|
+
---
|
|
35
59
|
|
|
36
|
-
|
|
60
|
+
## Client Commands
|
|
37
61
|
|
|
38
|
-
|
|
62
|
+
These commands are executed on your **local machine** or **CI/CD runner**.
|
|
39
63
|
|
|
40
|
-
|
|
64
|
+
### `deploy`
|
|
41
65
|
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
redep config set working_dir /path/to/your/project
|
|
45
|
-
```
|
|
66
|
+
Triggers a deployment on the remote server.
|
|
46
67
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
**Syntax:**
|
|
69
|
+
```bash
|
|
70
|
+
redep deploy <type>
|
|
71
|
+
```
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
redep listen
|
|
55
|
-
# Optional: Specify port (default 3000)
|
|
56
|
-
# redep listen --port 4000
|
|
57
|
-
```
|
|
73
|
+
**Parameters:**
|
|
74
|
+
- `<type>`: The service type to deploy. Currently supports `fe` (frontend).
|
|
58
75
|
|
|
59
|
-
|
|
76
|
+
**Requirements:**
|
|
77
|
+
- `SERVER_URL` must be configured.
|
|
78
|
+
- `SECRET_KEY` must match the server's key.
|
|
60
79
|
|
|
61
|
-
|
|
80
|
+
**Example:**
|
|
81
|
+
```bash
|
|
82
|
+
# Deploy frontend service
|
|
83
|
+
redep deploy fe
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Expected Output:**
|
|
87
|
+
```
|
|
88
|
+
[INFO] Deploying fe to http://192.168.1.50:3000...
|
|
89
|
+
[SUCCESS] Deployment triggered successfully.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Client Configuration
|
|
62
93
|
|
|
63
|
-
|
|
64
|
-
2. **Run the Server**:
|
|
94
|
+
Configure the client to know where the server is.
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
```bash
|
|
97
|
+
# Set Server URL
|
|
98
|
+
redep config set server_url http://<server-ip>:3000
|
|
99
|
+
|
|
100
|
+
# Set Secret Key
|
|
101
|
+
redep config set secret_key my-secret-key
|
|
102
|
+
```
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
- `volumes`:
|
|
74
|
-
- `/var/run/docker.sock:/var/run/docker.sock`: Required to allow the server to manage sibling containers.
|
|
75
|
-
- `./your-project:/workspace`: Mount your project directory containing `docker-compose.yml` to `/workspace` inside the container.
|
|
76
|
-
- `environment`:
|
|
77
|
-
- `WORKING_DIR=/workspace`: Must match the container mount path.
|
|
78
|
-
- `SECRET_KEY=your-secret-key`: Set the shared secret.
|
|
104
|
+
---
|
|
79
105
|
|
|
80
|
-
|
|
106
|
+
## Server Commands
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
These commands are executed on the **remote server** (VPS, VM, etc.).
|
|
83
109
|
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
redep config set server_url http://<server-ip>:3000
|
|
87
|
-
```
|
|
88
|
-
Or use `SERVER_URL` env var.
|
|
110
|
+
### `listen`
|
|
89
111
|
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
redep config set secret_key super-secure-secret-123
|
|
93
|
-
```
|
|
94
|
-
Or use `SECRET_KEY` env var.
|
|
112
|
+
Starts the server in the foreground. Useful for debugging or running inside Docker.
|
|
95
113
|
|
|
96
|
-
|
|
114
|
+
**Syntax:**
|
|
115
|
+
```bash
|
|
116
|
+
redep listen [--port <number>]
|
|
117
|
+
```
|
|
97
118
|
|
|
98
|
-
|
|
119
|
+
**Options:**
|
|
120
|
+
- `-p, --port`: Specify port (default: 3000).
|
|
99
121
|
|
|
122
|
+
**Example:**
|
|
100
123
|
```bash
|
|
101
|
-
redep
|
|
124
|
+
redep listen --port 4000
|
|
102
125
|
```
|
|
103
126
|
|
|
104
|
-
|
|
127
|
+
### `start` (Background)
|
|
128
|
+
|
|
129
|
+
Starts the server in background mode (Daemon).
|
|
130
|
+
* **Auto-PM2**: If `pm2` is installed, it uses PM2 for process management.
|
|
131
|
+
* **Native Fallback**: If `pm2` is missing, it uses Node.js `child_process` to detach.
|
|
132
|
+
|
|
133
|
+
**Syntax:**
|
|
134
|
+
```bash
|
|
135
|
+
redep start [--port <number>]
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Related Commands:**
|
|
139
|
+
- `redep stop`: Stops the background server.
|
|
140
|
+
- `redep status`: Checks if the server is running.
|
|
141
|
+
|
|
142
|
+
**Example:**
|
|
143
|
+
```bash
|
|
144
|
+
redep start
|
|
145
|
+
# [SUCCESS] Server started in background using PM2
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Server Configuration
|
|
149
|
+
|
|
150
|
+
Configure the runtime environment for the server.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Set Working Directory (Where docker-compose.yml lives)
|
|
154
|
+
redep config set working_dir /path/to/project
|
|
155
|
+
|
|
156
|
+
# Set Secret Key
|
|
157
|
+
redep config set secret_key my-secret-key
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Running with Docker (Recommended)
|
|
161
|
+
|
|
162
|
+
Instead of manual configuration, run the server as a container:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
# docker-compose.server.yml
|
|
166
|
+
services:
|
|
167
|
+
deploy-server:
|
|
168
|
+
image: remote-deploy-cli
|
|
169
|
+
volumes:
|
|
170
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
171
|
+
- ./my-app:/workspace
|
|
172
|
+
environment:
|
|
173
|
+
- WORKING_DIR=/workspace
|
|
174
|
+
- SECRET_KEY=secure-key
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Command Interactions
|
|
180
|
+
|
|
181
|
+
### Workflow: Deployment
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
sequenceDiagram
|
|
185
|
+
participant Client (CI/Local)
|
|
186
|
+
participant Server (Remote)
|
|
187
|
+
|
|
188
|
+
Note over Client: User runs "redep deploy fe"
|
|
189
|
+
Client->>Client: Read Config (URL, Secret)
|
|
190
|
+
Client->>Server: POST /deploy { type: "fe" } (Auth: Bearer Token)
|
|
191
|
+
|
|
192
|
+
Note over Server: Verify Secret Key
|
|
193
|
+
alt Invalid Key
|
|
194
|
+
Server-->>Client: 403 Forbidden
|
|
195
|
+
Client->>User: Error: Authentication Failed
|
|
196
|
+
else Valid Key
|
|
197
|
+
Server->>Server: Execute "docker compose up" in WORKING_DIR
|
|
198
|
+
Server-->>Client: 200 OK { status: "success" }
|
|
199
|
+
Client->>User: Success Message
|
|
200
|
+
end
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Configuration Reference
|
|
206
|
+
|
|
207
|
+
| Config Key | Env Variable | Description | Context |
|
|
208
|
+
| :------------ | :------------ | :----------------------------------------------- | :--------------- |
|
|
209
|
+
| `server_port` | `SERVER_PORT` | Port for the server to listen on (Default: 3000) | **Server** |
|
|
210
|
+
| `working_dir` | `WORKING_DIR` | Directory to execute commands in | **Server** |
|
|
211
|
+
| `server_url` | `SERVER_URL` | URL of the remote `redep` server | **Client** |
|
|
212
|
+
| `secret_key` | `SECRET_KEY` | Shared secret for authentication | **Both** |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Troubleshooting
|
|
217
|
+
|
|
218
|
+
### `Error: "working_dir" is not set`
|
|
219
|
+
- **Context**: Server
|
|
220
|
+
- **Fix**: Run `redep config set working_dir /path` or check `docker-compose.yml` environment.
|
|
105
221
|
|
|
106
|
-
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
222
|
+
### `Connection Refused`
|
|
223
|
+
- **Context**: Client
|
|
224
|
+
- **Fix**: Ensure server is running (`redep status` or `docker ps`) and port 3000 is open.
|
|
109
225
|
|
|
110
|
-
|
|
226
|
+
### `403 Forbidden`
|
|
227
|
+
- **Context**: Client
|
|
228
|
+
- **Fix**: Re-check `SECRET_KEY` on both machines. They must match exactly.
|
|
111
229
|
|
|
112
|
-
|
|
113
|
-
- `lib/server/`: Server and execution logic.
|
|
114
|
-
- `lib/client/`: Client and command logic.
|
|
115
|
-
- `lib/config.js`: Configuration management using `conf`.
|
|
230
|
+
---
|
|
116
231
|
|
|
117
232
|
## License
|
|
118
233
|
|
|
119
|
-
ISC
|
|
234
|
+
ISC © 2026
|
package/bin/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import 'dotenv/config';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
5
6
|
import { logger } from '../lib/logger.js';
|
|
6
7
|
import { getConfig, setConfig, getAllConfig, clearConfig } from '../lib/config.js';
|
|
7
8
|
import { startServer } from '../lib/server/index.js';
|
|
@@ -53,21 +54,156 @@ configCommand
|
|
|
53
54
|
|
|
54
55
|
program.addCommand(configCommand);
|
|
55
56
|
|
|
57
|
+
// Background Process Management
|
|
58
|
+
program
|
|
59
|
+
.command('start')
|
|
60
|
+
.description('Start the server in background (daemon mode) using PM2 if available')
|
|
61
|
+
.option('-p, --port <port>', 'Port to listen on')
|
|
62
|
+
.action((options) => {
|
|
63
|
+
// Try to use PM2 first
|
|
64
|
+
try {
|
|
65
|
+
// Check if PM2 is available via API
|
|
66
|
+
// We'll use a dynamic import or checking for the pm2 binary in a real scenario
|
|
67
|
+
// But here we can just try to spawn 'pm2' command
|
|
68
|
+
|
|
69
|
+
const args = ['start', process.argv[1], '--name', 'redep-server', '--', 'listen'];
|
|
70
|
+
if (options.port) {
|
|
71
|
+
args.push('--port', options.port);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const pm2 = spawn('pm2', args, {
|
|
75
|
+
stdio: 'inherit',
|
|
76
|
+
shell: true // Required to find pm2 in PATH
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
pm2.on('error', () => {
|
|
80
|
+
// Fallback to native spawn if PM2 is not found/fails
|
|
81
|
+
logger.info('PM2 not found, falling back to native background process...');
|
|
82
|
+
startNativeBackground(options);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
pm2.on('close', (code) => {
|
|
86
|
+
if (code !== 0) {
|
|
87
|
+
logger.warn('PM2 start failed, falling back to native background process...');
|
|
88
|
+
startNativeBackground(options);
|
|
89
|
+
} else {
|
|
90
|
+
logger.success('Server started in background using PM2');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
} catch (e) {
|
|
95
|
+
startNativeBackground(options);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function startNativeBackground(options) {
|
|
100
|
+
const existingPid = getConfig('server_pid');
|
|
101
|
+
|
|
102
|
+
if (existingPid) {
|
|
103
|
+
try {
|
|
104
|
+
process.kill(existingPid, 0);
|
|
105
|
+
logger.warn(`Server is already running with PID ${existingPid}`);
|
|
106
|
+
return;
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// Process doesn't exist, clear stale PID
|
|
109
|
+
setConfig('server_pid', null);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const args = ['listen'];
|
|
114
|
+
if (options.port) {
|
|
115
|
+
args.push('--port', options.port);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const child = spawn(process.argv[0], [process.argv[1], ...args], {
|
|
119
|
+
detached: true,
|
|
120
|
+
stdio: 'ignore',
|
|
121
|
+
windowsHide: true
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
child.unref();
|
|
125
|
+
setConfig('server_pid', child.pid);
|
|
126
|
+
logger.success(`Server started in background (native) with PID ${child.pid}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
program
|
|
130
|
+
.command('stop')
|
|
131
|
+
.description('Stop the background server')
|
|
132
|
+
.action(() => {
|
|
133
|
+
// Try PM2 stop first
|
|
134
|
+
const pm2 = spawn('pm2', ['stop', 'redep-server'], { stdio: 'ignore', shell: true });
|
|
135
|
+
|
|
136
|
+
pm2.on('close', (code) => {
|
|
137
|
+
if (code === 0) {
|
|
138
|
+
logger.success('Server stopped (PM2)');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fallback to native stop
|
|
143
|
+
const pid = getConfig('server_pid');
|
|
144
|
+
if (!pid) {
|
|
145
|
+
logger.warn('No active server found.');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
process.kill(pid);
|
|
151
|
+
setConfig('server_pid', null);
|
|
152
|
+
logger.success(`Server stopped (PID ${pid})`);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
if (e.code === 'ESRCH') {
|
|
155
|
+
logger.warn(`Process ${pid} not found. Cleaning up config.`);
|
|
156
|
+
setConfig('server_pid', null);
|
|
157
|
+
} else {
|
|
158
|
+
logger.error(`Failed to stop server: ${e.message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
program
|
|
165
|
+
.command('status')
|
|
166
|
+
.description('Check server status')
|
|
167
|
+
.action(() => {
|
|
168
|
+
// Try PM2 status first
|
|
169
|
+
const pm2 = spawn('pm2', ['describe', 'redep-server'], { stdio: 'inherit', shell: true });
|
|
170
|
+
|
|
171
|
+
pm2.on('close', (code) => {
|
|
172
|
+
if (code !== 0) {
|
|
173
|
+
// Fallback to native status
|
|
174
|
+
const pid = getConfig('server_pid');
|
|
175
|
+
|
|
176
|
+
if (!pid) {
|
|
177
|
+
logger.info('Server is NOT running.');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
process.kill(pid, 0);
|
|
183
|
+
logger.success(`Server is RUNNING (PID ${pid})`);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
logger.warn(`Server is NOT running (Stale PID ${pid} found).`);
|
|
186
|
+
setConfig('server_pid', null);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
56
192
|
// Server Command
|
|
57
193
|
program
|
|
58
194
|
.command('listen')
|
|
59
195
|
.description('Start the server to listen for commands')
|
|
60
196
|
.option('-p, --port <port>', 'Port to listen on', 3000)
|
|
61
197
|
.action((options) => {
|
|
62
|
-
const port = options.port ||
|
|
63
|
-
const secret =
|
|
198
|
+
const port = options.port || getConfig('server_port') || process.env.SERVER_PORT || 3000;
|
|
199
|
+
const secret = getConfig('secret_key') || process.env.SECRET_KEY;
|
|
64
200
|
|
|
65
201
|
if (!secret) {
|
|
66
202
|
logger.warn('Warning: No "secret_key" set in config or SECRET_KEY env var. Communication might be insecure or fail if client requires it.');
|
|
67
203
|
logger.info('Run "redep config set secret_key <your-secret>" or set SECRET_KEY env var.');
|
|
68
204
|
}
|
|
69
205
|
|
|
70
|
-
const workingDir =
|
|
206
|
+
const workingDir = getConfig('working_dir') || process.env.WORKING_DIR;
|
|
71
207
|
if (!workingDir) {
|
|
72
208
|
logger.error('Error: "working_dir" is not set. Please set it using "redep config set working_dir <path>" or WORKING_DIR env var.');
|
|
73
209
|
process.exit(1);
|
|
@@ -81,8 +217,8 @@ program
|
|
|
81
217
|
.command('deploy <type>')
|
|
82
218
|
.description('Deploy a service (e.g., "fe") to the server machine')
|
|
83
219
|
.action(async (type) => {
|
|
84
|
-
const serverUrl =
|
|
85
|
-
const secret =
|
|
220
|
+
const serverUrl = getConfig('server_url') || process.env.SERVER_URL;
|
|
221
|
+
const secret = getConfig('secret_key') || process.env.SECRET_KEY;
|
|
86
222
|
|
|
87
223
|
if (!serverUrl) {
|
|
88
224
|
logger.error('Error: "server_url" is not set. Set SERVER_URL env var or run "redep config set server_url <url>"');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remote-deploy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"dotenv": "^17.2.3",
|
|
24
24
|
"express": "^4.18.2",
|
|
25
25
|
"helmet": "^7.1.0",
|
|
26
|
-
"morgan": "^1.10.0"
|
|
26
|
+
"morgan": "^1.10.0",
|
|
27
|
+
"pm2": "^6.0.14"
|
|
27
28
|
}
|
|
28
29
|
}
|