remote-deploy-cli 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/.env.example +7 -0
- package/.github/workflows/npm-publish.yml +31 -0
- package/Dockerfile +23 -0
- package/README.md +119 -0
- package/bin/index.js +105 -0
- package/docker-compose.server.yml +22 -0
- package/lib/client/client.js +26 -0
- package/lib/client/index.js +31 -0
- package/lib/config.js +21 -0
- package/lib/logger.js +9 -0
- package/lib/server/executor.js +23 -0
- package/lib/server/index.js +17 -0
- package/lib/server/server.js +63 -0
- package/package.json +28 -0
- package/test-target/docker-compose.yml +0 -0
package/.env.example
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
packages: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '22'
|
|
23
|
+
registry-url: 'https://registry.npmjs.org'
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm ci
|
|
27
|
+
|
|
28
|
+
- name: Publish to npm
|
|
29
|
+
run: npm publish
|
|
30
|
+
env:
|
|
31
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
FROM node:18-alpine
|
|
2
|
+
|
|
3
|
+
# Install Docker CLI and Docker Compose plugin
|
|
4
|
+
# We need these to execute docker commands from the slave
|
|
5
|
+
RUN apk add --no-cache docker-cli docker-cli-compose
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
# Install app dependencies
|
|
10
|
+
COPY package*.json ./
|
|
11
|
+
RUN npm install --production
|
|
12
|
+
|
|
13
|
+
# Bundle app source
|
|
14
|
+
COPY . .
|
|
15
|
+
|
|
16
|
+
# Make the CLI executable
|
|
17
|
+
RUN chmod +x bin/index.js
|
|
18
|
+
|
|
19
|
+
# Expose the default port
|
|
20
|
+
EXPOSE 3000
|
|
21
|
+
|
|
22
|
+
# Start the server listener by default
|
|
23
|
+
CMD ["node", "bin/index.js", "listen"]
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# redep (Remote Deploy CLI)
|
|
2
|
+
|
|
3
|
+
A lightweight Node.js CLI tool for remote execution using a Client-Server architecture. Designed to simplify deployment workflows (e.g., triggering Docker Compose on a remote server).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Client-Server Architecture**: Centralized control where Client sends commands and Server executes them.
|
|
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.
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
1. **Client Machine**: The client that issues deployment commands (e.g., your laptop or CI runner).
|
|
17
|
+
2. **Server Machine**: The server that hosts the application and executes the deployment commands (e.g., `docker compose up`).
|
|
18
|
+
|
|
19
|
+
Communication happens via HTTP/REST with a shared secret for authentication.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You can install this package globally using npm:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Clone the repository
|
|
27
|
+
git clone <repo-url>
|
|
28
|
+
cd remote-deploy-cli
|
|
29
|
+
|
|
30
|
+
# Install globally (for development/local use)
|
|
31
|
+
npm install -g .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### 1. Configure the Server Machine
|
|
37
|
+
|
|
38
|
+
#### Option A: Running directly on Node.js
|
|
39
|
+
|
|
40
|
+
On the machine where you want to run your application (Server):
|
|
41
|
+
|
|
42
|
+
1. **Set the Working Directory**: This is where your `docker-compose.yml` is located.
|
|
43
|
+
```bash
|
|
44
|
+
redep config set working_dir /path/to/your/project
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **Set a Secret Key**: A secure password for authentication.
|
|
48
|
+
```bash
|
|
49
|
+
redep config set secret_key super-secure-secret-123
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
3. **Start the Listener**:
|
|
53
|
+
```bash
|
|
54
|
+
redep listen
|
|
55
|
+
# Optional: Specify port (default 3000)
|
|
56
|
+
# redep listen --port 4000
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Option B: Running with Docker (Recommended)
|
|
60
|
+
|
|
61
|
+
You can run the server itself inside a Docker container. We provide a `docker-compose.server.yml` as an example.
|
|
62
|
+
|
|
63
|
+
1. **Prerequisites**: Ensure Docker and Docker Compose are installed on the server machine.
|
|
64
|
+
2. **Run the Server**:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Edit docker-compose.server.yml to map your project directory
|
|
68
|
+
# Then run:
|
|
69
|
+
docker compose -f docker-compose.server.yml up -d --build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Configuration via `docker-compose.server.yml`**:
|
|
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.
|
|
79
|
+
|
|
80
|
+
### 2. Configure the Client Machine
|
|
81
|
+
|
|
82
|
+
On your local machine or CI/CD server (Client):
|
|
83
|
+
|
|
84
|
+
1. **Set the Server URL**: The address of your server machine.
|
|
85
|
+
```bash
|
|
86
|
+
redep config set server_url http://<server-ip>:3000
|
|
87
|
+
```
|
|
88
|
+
Or use `SERVER_URL` env var.
|
|
89
|
+
|
|
90
|
+
2. **Set the Secret Key**: Must match the key set on the Server.
|
|
91
|
+
```bash
|
|
92
|
+
redep config set secret_key super-secure-secret-123
|
|
93
|
+
```
|
|
94
|
+
Or use `SECRET_KEY` env var.
|
|
95
|
+
|
|
96
|
+
### 3. Execute Deployment
|
|
97
|
+
|
|
98
|
+
To deploy the Frontend (triggers `docker compose up -d` on the server):
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
redep deploy fe
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Security Best Practices
|
|
105
|
+
|
|
106
|
+
- **Secret Management**: Ensure your `secret_key` is strong and not committed to version control.
|
|
107
|
+
- **Network Security**: In production, it is recommended to run the Server behind a reverse proxy (like Nginx) with SSL/TLS (HTTPS) to encrypt the traffic, especially since the secret key is sent in the header.
|
|
108
|
+
- **Firewall**: Restrict access to the Server port (default 3000) to only allow IPs from known Client machines.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
- `bin/index.js`: CLI entry point.
|
|
113
|
+
- `lib/server/`: Server and execution logic.
|
|
114
|
+
- `lib/client/`: Client and command logic.
|
|
115
|
+
- `lib/config.js`: Configuration management using `conf`.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
ISC
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { logger } from '../lib/logger.js';
|
|
6
|
+
import { getConfig, setConfig, getAllConfig, clearConfig } from '../lib/config.js';
|
|
7
|
+
import { startServer } from '../lib/server/index.js';
|
|
8
|
+
import { deploy } from '../lib/client/index.js';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('redep')
|
|
14
|
+
.description('Remote execution CLI for deployment')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
|
|
17
|
+
// Configuration Command
|
|
18
|
+
const configCommand = new Command('config')
|
|
19
|
+
.description('Manage configuration');
|
|
20
|
+
|
|
21
|
+
configCommand
|
|
22
|
+
.command('set <key> <value>')
|
|
23
|
+
.description('Set a configuration key')
|
|
24
|
+
.action((key, value) => {
|
|
25
|
+
setConfig(key, value);
|
|
26
|
+
logger.success(`Configuration updated: ${key} = ${value}`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
configCommand
|
|
30
|
+
.command('get <key>')
|
|
31
|
+
.description('Get a configuration key')
|
|
32
|
+
.action((key) => {
|
|
33
|
+
const value = getConfig(key);
|
|
34
|
+
logger.info(`${key}: ${value}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
configCommand
|
|
38
|
+
.command('list')
|
|
39
|
+
.description('List all configurations')
|
|
40
|
+
.action(() => {
|
|
41
|
+
const all = getAllConfig();
|
|
42
|
+
logger.info('Current Configuration:');
|
|
43
|
+
console.table(all);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
configCommand
|
|
47
|
+
.command('clear')
|
|
48
|
+
.description('Clear all configurations')
|
|
49
|
+
.action(() => {
|
|
50
|
+
clearConfig();
|
|
51
|
+
logger.success('All configurations have been cleared.');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
program.addCommand(configCommand);
|
|
55
|
+
|
|
56
|
+
// Server Command
|
|
57
|
+
program
|
|
58
|
+
.command('listen')
|
|
59
|
+
.description('Start the server to listen for commands')
|
|
60
|
+
.option('-p, --port <port>', 'Port to listen on', 3000)
|
|
61
|
+
.action((options) => {
|
|
62
|
+
const port = options.port || process.env.SERVER_PORT || getConfig('server_port') || 3000;
|
|
63
|
+
const secret = process.env.SECRET_KEY || getConfig('secret_key');
|
|
64
|
+
|
|
65
|
+
if (!secret) {
|
|
66
|
+
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
|
+
logger.info('Run "redep config set secret_key <your-secret>" or set SECRET_KEY env var.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const workingDir = process.env.WORKING_DIR || getConfig('working_dir');
|
|
71
|
+
if (!workingDir) {
|
|
72
|
+
logger.error('Error: "working_dir" is not set. Please set it using "redep config set working_dir <path>" or WORKING_DIR env var.');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
startServer(port, secret, workingDir);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Client Command
|
|
80
|
+
program
|
|
81
|
+
.command('deploy <type>')
|
|
82
|
+
.description('Deploy a service (e.g., "fe") to the server machine')
|
|
83
|
+
.action(async (type) => {
|
|
84
|
+
const serverUrl = process.env.SERVER_URL || getConfig('server_url');
|
|
85
|
+
const secret = process.env.SECRET_KEY || getConfig('secret_key');
|
|
86
|
+
|
|
87
|
+
if (!serverUrl) {
|
|
88
|
+
logger.error('Error: "server_url" is not set. Set SERVER_URL env var or run "redep config set server_url <url>"');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!secret) {
|
|
93
|
+
logger.error('Error: "secret_key" is not set. Set SECRET_KEY env var or run "redep config set secret_key <your-secret>"');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await deploy(type, serverUrl, secret);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
logger.error(`Deploy failed: ${error.message}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
services:
|
|
2
|
+
deploy-server:
|
|
3
|
+
build: .
|
|
4
|
+
container_name: deploy-server
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
|
|
7
|
+
env_file:
|
|
8
|
+
- .env
|
|
9
|
+
|
|
10
|
+
ports:
|
|
11
|
+
- "${SERVER_PORT}:3000"
|
|
12
|
+
environment:
|
|
13
|
+
# Override WORKING_DIR to match the internal container path
|
|
14
|
+
- WORKING_DIR=/workspace
|
|
15
|
+
volumes:
|
|
16
|
+
# Required: Give access to the host's Docker Daemon
|
|
17
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
18
|
+
|
|
19
|
+
# Required: Mount the project directory that contains docker-compose.yml
|
|
20
|
+
# Host Path : Container Path
|
|
21
|
+
# CHANGE THIS to your actual project path
|
|
22
|
+
- ${WORKING_DIR}:/workspace
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
export const sendCommand = async (url, endpoint, secret, data) => {
|
|
4
|
+
try {
|
|
5
|
+
// Ensure url has no trailing slash and endpoint has leading slash
|
|
6
|
+
const cleanUrl = url.replace(/\/$/, '');
|
|
7
|
+
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
8
|
+
const fullUrl = `${cleanUrl}${cleanEndpoint}`;
|
|
9
|
+
|
|
10
|
+
const response = await axios.post(fullUrl, data, {
|
|
11
|
+
headers: {
|
|
12
|
+
'Authorization': `Bearer ${secret}`,
|
|
13
|
+
'Content-Type': 'application/json'
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return response.data;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error.response) {
|
|
19
|
+
throw new Error(`Server error (${error.response.status}): ${JSON.stringify(error.response.data)}`);
|
|
20
|
+
} else if (error.request) {
|
|
21
|
+
throw new Error(`Connection failed: No response from ${url}. Is the server running?`);
|
|
22
|
+
} else {
|
|
23
|
+
throw new Error(`Request error: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sendCommand } from './client.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
export const deploy = async (type, serverUrl, secret) => {
|
|
5
|
+
logger.info(`Starting deployment sequence for target: ${type}`);
|
|
6
|
+
|
|
7
|
+
if (type === 'fe') {
|
|
8
|
+
logger.info('Sending deployment instruction to server machine...');
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const result = await sendCommand(serverUrl, '/deploy/fe', secret, {});
|
|
12
|
+
|
|
13
|
+
if (result.status === 'success') {
|
|
14
|
+
logger.success('Server successfully executed the deployment command.');
|
|
15
|
+
logger.log('--- Remote Output ---');
|
|
16
|
+
if (result.output.stdout) console.log(result.output.stdout);
|
|
17
|
+
if (result.output.stderr) console.log(result.output.stderr);
|
|
18
|
+
logger.log('---------------------');
|
|
19
|
+
} else {
|
|
20
|
+
logger.error('Server reported failure.');
|
|
21
|
+
console.error(result);
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} else {
|
|
28
|
+
logger.error(`Deployment type "${type}" is not supported yet.`);
|
|
29
|
+
throw new Error(`Unsupported deployment type: ${type}`);
|
|
30
|
+
}
|
|
31
|
+
};
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({
|
|
4
|
+
projectName: 'redep'
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const getConfig = (key) => {
|
|
8
|
+
return config.get(key);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const setConfig = (key, value) => {
|
|
12
|
+
config.set(key, value);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const clearConfig = () => {
|
|
16
|
+
config.clear();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const getAllConfig = () => {
|
|
20
|
+
return config.store;
|
|
21
|
+
};
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export const logger = {
|
|
4
|
+
info: (msg) => console.log(chalk.blue('ℹ') + ' ' + msg),
|
|
5
|
+
success: (msg) => console.log(chalk.green('✔') + ' ' + msg),
|
|
6
|
+
warn: (msg) => console.log(chalk.yellow('⚠') + ' ' + msg),
|
|
7
|
+
error: (msg) => console.error(chalk.red('✖') + ' ' + msg),
|
|
8
|
+
log: (msg) => console.log(msg)
|
|
9
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
export const executeCommand = (command, workingDir) => {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
logger.info(`Executing: ${command} in ${workingDir}`);
|
|
7
|
+
|
|
8
|
+
exec(command, { cwd: workingDir }, (error, stdout, stderr) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
logger.error(`Execution error: ${error.message}`);
|
|
11
|
+
return reject({ error: error.message, stderr });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (stderr) {
|
|
15
|
+
// Docker often outputs to stderr even for info, so we log it but don't fail unless error is set
|
|
16
|
+
logger.warn(`Stderr: ${stderr}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logger.success('Execution successful');
|
|
20
|
+
resolve({ stdout, stderr });
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createServer } from './server.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
export const startServer = (port, secret, workingDir) => {
|
|
5
|
+
if (!secret) {
|
|
6
|
+
logger.error('Cannot start server: Secret key is required for security.');
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const app = createServer(secret, workingDir);
|
|
11
|
+
|
|
12
|
+
app.listen(port, () => {
|
|
13
|
+
logger.success(`Server is running on port ${port}`);
|
|
14
|
+
logger.info(`Working Directory: ${workingDir}`);
|
|
15
|
+
logger.info(`Waiting for commands...`);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import bodyParser from 'body-parser';
|
|
3
|
+
import cors from 'cors';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import morgan from 'morgan';
|
|
6
|
+
import { logger } from '../logger.js';
|
|
7
|
+
import { executeCommand } from './executor.js';
|
|
8
|
+
|
|
9
|
+
export const createServer = (secretKey, workingDir) => {
|
|
10
|
+
const app = express();
|
|
11
|
+
|
|
12
|
+
app.use(helmet());
|
|
13
|
+
app.use(cors());
|
|
14
|
+
app.use(bodyParser.json());
|
|
15
|
+
app.use(morgan('tiny'));
|
|
16
|
+
|
|
17
|
+
// Auth Middleware
|
|
18
|
+
const authenticate = (req, res, next) => {
|
|
19
|
+
const authHeader = req.headers['authorization'];
|
|
20
|
+
if (!authHeader) {
|
|
21
|
+
return res.status(401).json({ error: 'Missing Authorization header' });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const token = authHeader.replace('Bearer ', '');
|
|
25
|
+
if (token !== secretKey) {
|
|
26
|
+
return res.status(403).json({ error: 'Invalid token' });
|
|
27
|
+
}
|
|
28
|
+
next();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
app.post('/execute', authenticate, async (req, res) => {
|
|
32
|
+
const { command } = req.body;
|
|
33
|
+
|
|
34
|
+
if (!command) {
|
|
35
|
+
return res.status(400).json({ error: 'Missing command' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = await executeCommand(command, workingDir);
|
|
40
|
+
res.json({ status: 'success', output: result });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
res.status(500).json({ status: 'error', error: error });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Specific endpoint for deploy fe to map the requirement exactly
|
|
47
|
+
app.post('/deploy/fe', authenticate, async (req, res) => {
|
|
48
|
+
logger.info('Received deploy fe request');
|
|
49
|
+
|
|
50
|
+
// Requirement: cd to working dir and run docker compose up -d
|
|
51
|
+
// We use 'docker compose pull && docker compose up -d' to ensure latest image is used
|
|
52
|
+
const command = 'docker compose pull && docker compose up -d';
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = await executeCommand(command, workingDir);
|
|
56
|
+
res.json({ status: 'success', output: result });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
res.status(500).json({ status: 'error', error: error });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return app;
|
|
63
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "remote-deploy-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"redep": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [],
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"description": "Remote execution CLI for deployment",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"axios": "^1.7.9",
|
|
18
|
+
"body-parser": "^1.20.2",
|
|
19
|
+
"chalk": "^5.3.0",
|
|
20
|
+
"commander": "^12.0.0",
|
|
21
|
+
"conf": "^12.0.0",
|
|
22
|
+
"cors": "^2.8.5",
|
|
23
|
+
"dotenv": "^17.2.3",
|
|
24
|
+
"express": "^4.18.2",
|
|
25
|
+
"helmet": "^7.1.0",
|
|
26
|
+
"morgan": "^1.10.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
Binary file
|