Node.js+pm2+ssh2模块实现简单的自动化部署脚本

 更新时间:2023年10月16日 08:52:07   作者:泯泷  
本文将介绍如何使用Node.js和ssh2模块实现一个简单的部署脚本,将本地的项目文件上传到远程服务器上,我们将使用dotenv模块来管理环境变量,以及child_process模块来执行命令行操作

安装ssh2和dotenv模块

首先,我们需要安装ssh2和dotenv模块:

npm install ssh2 dotenv --save

然后,我们需要在项目根目录下创建一个.env文件,用来存放一些敏感的配置信息,例如服务器的IP地址、端口号、用户名、私钥等。这样,我们就可以避免将这些信息暴露在代码中,也方便我们根据不同的环境进行切换。.env文件的内容如下:

HOST=192.168.1.100
SSHPORT=22
USER=root
KEYFILE=~/.ssh/id_rsa
SSHKEY="
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
"
const fs = require('fs');
const Client = require('ssh2').Client;
require('dotenv').config();

其中,fs模块是Node.js内置的文件系统模块,用来读写文件;Client是ssh2模块提供的一个类,用来创建SSH连接;dotenv模块是用来加载.env文件中的配置信息到process.env对象中。

定义一些常量存放SSH连接配置

然后,我们需要定义一些常量,用来存放SSH连接配置和本地目录路径和远程目录路径:

// SSH连接配置
const sshConfig = {
    host: process.env.HOST || '127.0.0.1',
    port: process.env.SSHPORT || 22,
    username: process.env.USER || 'root',
    privateKey: process.env.SSHKEY || fs.readFileSync(process.env.KEYFILE || '/.ssh/id_rsa').toString(),
    // 这里使用的是通过密钥登入,使用密码登入也是可以的,两种配置项可以并存,其中一个失败了ssh2会则尝试另一个方法
};

// 本地目录路径和远程目录路径
const localDir = __dirname;
const remoteDir = '/www/wwwroot/img-service';

其中,我们使用了process.env对象中的属性来获取环境变量的值,如果没有定义,则使用默认值。注意,私钥需要转换为字符串格式。

创建Client实例调用connect方法建立SSH连接

接着,我们需要创建一个Client实例,并调用connect方法来建立SSH连接:

// 创建SSH连接
const conn = new Client();
conn.on('ready', () => {
    console.log('SSH连接成功');
    // ...
}).connect(sshConfig);

// 监听error事件  
conn.on('error', (err) => {  
    console.error('SSH连接失败', err);  
});  
  
// 结束SSH连接  
conn.on('end', () => {  
    console.log('SSH连接已断开');  
});

在ready事件的回调函数中,我们需要进行部署操作。

具体来说,我们需要做两件事:

一是执行npm run build命令来构建项目;

二是将构建后的文件上传到远程服务器上。(当然,构建指令也可以在连接之前进行)

// 项目构建
const { execSync } = require('child_process');
execSync('npm run build', { stdio: 'inherit' })

execSync 是 Node.js 的一个内置模块,它可以同步地执行一个子进程,并返回子进程的输出。这样可以避免异步的回调地狱,也可以保证构建的顺序和正确性。stdio 参数是用来控制子进程的输入输出的,它可以是一个数组或一个字符串。如果是一个数组,那么它表示子进程的标准输入、标准输出和标准错误的流。如果是一个字符串,那么它表示子进程的所有流的模式。inherit 表示子进程的流和父进程的流相同,也就是说,子进程的输出会显示在父进程的控制台中。

使用sftp进行文件上传

欧克,现在我们写一下将本地目录下的所有文件上传至服务器上指定目录的代码,使用sftp进行文件上传:

// 将本地目录下的所有文件上传至服务器上指定目录
    const uploadPromise = [];
    conn.sftp((err, sftp) => {
        if (err) throw err;
        // 待上传文件or目录
        const files = ['dist', 'package.json', '.env'];

        const uploadFile = (file) => {
            return new Promise((resolve, reject) => {
                try {
                    const localFilePath = localDir + '/' + file;
                    const remoteFilePath = remoteDir + '/' + file;
                    const readStream = fs.createReadStream(localFilePath);
                    const writeStream = sftp.createWriteStream(remoteFilePath);
                    writeStream.on('close', () => {
                        console.log(`文件 ${file} 上传成功`);
                        resolve();
                    });
                    writeStream.on('error', (err) => {
                        console.log(`文件 ${file} 上传失败:${err}`);
                        reject(err);
                    });
                    readStream.pipe(writeStream);
                } catch (error) {
                    reject(error);
                }
            });
        }

同时我们需要有人解析文件目录,并执行我们的上传指令:

const uploadDir = (files) => {
            files.forEach((file) => {
                // 检查是否存在文件
                const isExist = fs.existsSync(file);
                const stat = fs.lstatSync(file);
                if (!isExist) {
                    console.log(`文件 ${file} 不存在`);
                }else if (stat.isDirectory(file)){
                    const dirFiles = fs.readdirSync(file);
                    uploadDir(dirFiles.map((dirFile) => file + '/' + dirFile));
                }else if (stat.isFile(file)){
                    uploadPromise.push(uploadFile(file));
                }
            });
        }
        uploadDir(files);

最后,还记得我们收集的Promise数组吗?直接用Promise.all帮我们处理等待全部文件上传后的回调:

Promise.all(uploadPromise).then(() => {
            console.log('所有文件上传成功');
            // 执行SSH命令
            conn.shell((err, stream) => {
                if (err) throw err;
                stream.on('close', () => {
                    console.log('远程命令执行完毕');
                    conn.end();
                }).on('data', (data) => {
                    console.log('远程命令输出:\n' + data);
                }).stderr.on('data', (data) => {
                    console.log('远程命令错误:\n' + data);
                });
                stream.end('ls -l /www/wwwroot/img-service\npm2 restart img-service\nexit\n');
            });
        }).catch((err) => {
            console.log('上传失败:' + err);
        });

欧克,最后附上完整代码

const fs = require('fs');
const Client = require('ssh2').Client;
require('dotenv').config();
// 项目构建
const { execSync } = require('child_process');
execSync('npm run build', { stdio: 'inherit' })
// SSH连接配置
const sshConfig = {
    host: process.env.HOST || '127.0.0.1',
    port: process.env.SSHPORT || 22,
    username: process.env.USER || 'root',
    privateKey: process.env.SSHKEY || fs.readFileSync(process.env.KEYFILE || '/.ssh/id_rsa').toString(),
};
// 本地目录路径和远程目录路径
const localDir = __dirname;
const remoteDir = '/www/wwwroot/img-service';
// 创建SSH连接
const conn = new Client();
// 监听ready事件
conn.on('ready', () => {
    console.log('SSH连接成功');
    // 将本地目录下的所有文件上传至服务器上指定目录
    const uploadPromise = [];
    conn.sftp((err, sftp) => {
        if (err) throw err;
        const files = ['dist', 'package.json', '.env'];
        const uploadFile = (file) => {
            return new Promise((resolve, reject) => {
                try {
                    const localFilePath = localDir + '/' + file;
                    const remoteFilePath = remoteDir + '/' + file;
                    const readStream = fs.createReadStream(localFilePath);
                    const writeStream = sftp.createWriteStream(remoteFilePath);
                    writeStream.on('close', () => {
                        console.log(`文件 ${file} 上传成功`);
                        resolve();
                    });
                    writeStream.on('error', (err) => {
                        console.log(`文件 ${file} 上传失败:${err}`);
                        reject(err);
                    });
                    readStream.pipe(writeStream);
                } catch (error) {
                    reject(error);
                }
            });
        }
        const uploadDir = (files) => {
            files.forEach((file) => {
                // 检查是否存在文件
                const isExist = fs.existsSync(file);
                const stat = fs.lstatSync(file);
                if (!isExist) {
                    console.log(`文件 ${file} 不存在`);
                }else if (stat.isDirectory(file)){
                    const dirFiles = fs.readdirSync(file);
                    uploadDir(dirFiles.map((dirFile) => file + '/' + dirFile));
                }else if (stat.isFile(file)){
                    uploadPromise.push(uploadFile(file));
                }
            });
        }
        uploadDir(files);
        Promise.all(uploadPromise).then(() => {
            console.log('所有文件上传成功');
            // 执行SSH命令
            conn.shell((err, stream) => {
                if (err) throw err;
                stream.on('close', () => {
                    console.log('远程命令执行完毕');
                    conn.end();
                }).on('data', (data) => {
                    console.log('远程命令输出:\n' + data);
                }).stderr.on('data', (data) => {
                    console.log('远程命令错误:\n' + data);
                });
                stream.end('ls -l /www/wwwroot/img-service\npm2 restart img-service\nexit\n');
            });
        }).catch((err) => {
            console.log('上传失败:' + err);
        });
    });
}).connect(sshConfig);
// 监听error事件
conn.on('error', (err) => {
    console.error('SSH连接失败', err);
});
// 结束SSH连接
conn.on('end', () => {
    console.log('SSH连接已断开');
});

以上就是Node.js+pm2+ssh2模块实现简单的自动化部署脚本的详细内容,更多关于Node.js pm2 ssh2自动化部署的资料请关注脚本之家其它相关文章!

相关文章

  • 解决下载了nodejs但npm -v没有反应问题的全过程

    解决下载了nodejs但npm -v没有反应问题的全过程

    最近工作中遇到了个问题,node安装成功,但npm无法使用,所以下面这篇文章主要给大家介绍了关于下载了nodejs但npm -v没有反应问题解决的相关资料,需要的朋友可以参考下
    2022-08-08
  • 详解node如何让一个端口同时支持https与http

    详解node如何让一个端口同时支持https与http

    众所周知node是一个高性能的web服务器,使用它可以很简单的创建一个http或https的服务器。这篇文章主要介绍了详解node如何让一个端口同时支持https与http
    2017-07-07
  • NodeJS感知和控制自身进程的运行环境和状态

    NodeJS感知和控制自身进程的运行环境和状态

    NodeJS可以感知和控制自身进程的运行环境和状态,也可以创建子进程并与其协同工作,这使得NodeJS可以把多个程序组合在一起共同完成某项工作,并在其中充当胶水和调度器的作用,和进程管理相关的API单独介绍起来比较枯燥,这里从一些典型的应用场景出发
    2024-01-01
  • Node.js系列之发起get/post请求(2)

    Node.js系列之发起get/post请求(2)

    这篇文章主要为大家详细介绍了Node.js系列之发起get/post请求,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • 安装nvm并使用nvm安装nodejs及配置环境变量的全过程

    安装nvm并使用nvm安装nodejs及配置环境变量的全过程

    有时候使用nvm管理node会发现无法使用node或npm,主要原因是环境变量没有配置成功,下面这篇文章主要给大家介绍了关于安装nvm并使用nvm安装nodejs及配置环境变量的相关资料,需要的朋友可以参考下
    2023-03-03
  • Node.js API详解之 console模块用法详解

    Node.js API详解之 console模块用法详解

    这篇文章主要介绍了Node.js API详解之 console模块用法,总结分析了Node.js API中console模块基本函数、使用方法与操作注意事项,需要的朋友可以参考下
    2020-05-05
  • nodejs连接ftp上传下载实现方法详解【附:踩坑记录】

    nodejs连接ftp上传下载实现方法详解【附:踩坑记录】

    这篇文章主要介绍了nodejs连接ftp上传下载实现方法,结合实例形式详细分析了node.js使用ftp模块实现针对ftp上传、下载相关操作的方法,并附带记录了传输速度慢的解决方法,需要的朋友可以参考下
    2023-04-04
  • Node实现前端本地开发接口代理服务

    Node实现前端本地开发接口代理服务

    本文主要介绍了Node实现前端本地开发接口代理服务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • Node.JS中的模块、exports和module讲解

    Node.JS中的模块、exports和module讲解

    这篇文章主要介绍了Node.JS中的模块、exports和module讲解,模块分为两类一类是核心模块一类是文件模块,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • Node.js中的异步生成器与异步迭代详解

    Node.js中的异步生成器与异步迭代详解

    这篇文章主要给大家介绍了关于Node.js中异步生成器与异步迭代的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论