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自动化部署的资料请关注脚本之家其它相关文章!

相关文章

  • node.js下LDAP查询实例分享

    node.js下LDAP查询实例分享

    这篇文章主要介绍了node.js下LDAP查询实例分享的相关资料,需要的朋友可以参考下
    2015-09-09
  • 基于Node.js的强大爬虫 能直接发布抓取的文章哦

    基于Node.js的强大爬虫 能直接发布抓取的文章哦

    基于Node.js的强大爬虫能直接发布抓取的文章哦!本爬虫源码基于WTFPL协议,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • nodejs实现截取上传视频中一帧作为预览图片

    nodejs实现截取上传视频中一帧作为预览图片

    这篇文章主要为大家详细介绍了nodejs实现截取上传视频中一帧作为预览图片,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • Node.js 中判断一个文件是否存在

    Node.js 中判断一个文件是否存在

    这篇文章主要记录一些 Node.js 应用中的小知识点,如果你 Google/Baidu “Node.js 如何判断文件是否存在” 发现给出的很多答案还是使用的 fs.exists,这里不推荐使用 fs.exists 你可以选择 fs.stat 或 fs.access。
    2020-08-08
  • Node.js中利用js-xlsx处理xlsx文件的实现

    Node.js中利用js-xlsx处理xlsx文件的实现

    js-xlsx库是目前Github上star数量最多的处理Excel的库,本文介绍用 Node.js中的js-xls库来处理Excel文件,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • 详解基于 Node.js 的轻量级云函数功能实现

    详解基于 Node.js 的轻量级云函数功能实现

    这篇文章主要介绍了详解基于 Node.js 的轻量级云函数功能实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-07-07
  • nodejs制作一个文档同步工具自动同步到gitee中的实现代码

    nodejs制作一个文档同步工具自动同步到gitee中的实现代码

    这篇文章主要介绍了nodejs制作一个文档同步工具自动同步到gitee中,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • node.js使用stream模块实现自定义流示例

    node.js使用stream模块实现自定义流示例

    这篇文章主要介绍了node.js使用stream模块实现自定义流,结合实例形式详细分析了node.js基于stream模块实现自定义的可读流、可写流、可读写流等相关操作技巧,需要的朋友可以参考下
    2020-02-02
  • 解决Mac安装thrift因bison报错的问题

    解决Mac安装thrift因bison报错的问题

    今天小编就为大家分享一篇解决Mac安装thrift因bison报错的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • node.js中express-session配置项详解

    node.js中express-session配置项详解

    本篇文章主要介绍了node.js中express-session配置项详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05

最新评论