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通过响应回写的方式渲染页面资源

    详解nodejs通过响应回写的方式渲染页面资源

    本篇文章主要介绍了详解nodejs通过响应回写的方式渲染页面资源,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • 基于nodejs res.end和res.send的区别

    基于nodejs res.end和res.send的区别

    今天小编就为大家分享一篇基于nodejs res.end和res.send的区别,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • node制作一个视频帧长图生成器操作分享

    node制作一个视频帧长图生成器操作分享

    这篇文章主要介绍了node制作一个视频帧长图生成器操作分享,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-08-08
  • 关于Node.js的events.EventEmitter用法介绍

    关于Node.js的events.EventEmitter用法介绍

    本篇文章主要介绍了关于Node.js的events.EventEmitter用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-04-04
  • Node.js中的events事件模块知识点总结

    Node.js中的events事件模块知识点总结

    在本篇文章里小编给大家整理的是一篇关于Node.js中的events事件模块知识点总结内容,有兴趣的朋友们可以跟着学习下。
    2021-12-12
  • 在node中如何调用python脚本

    在node中如何调用python脚本

    这篇文章主要介绍了在node中如何调用python脚本,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • 通过实例了解Nodejs模块系统及require机制

    通过实例了解Nodejs模块系统及require机制

    这篇文章主要介绍了通过实例了解Nodejs模块系统及require机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • nodejs 日志模块winston的使用方法

    nodejs 日志模块winston的使用方法

    本篇文章主要介绍了nodejs 日志模块winston的使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • windows离线环境安装node-sass全过程

    windows离线环境安装node-sass全过程

    文章介绍了如何在Windows系统上安装和配置node-sass,并提供了一个详细的步骤指南,首先,通过命令行查看支持版本;然后,下载对应版本的node-sass安装包;接着,在npm配置文件中增加SASS_BINARY_PATH路径配置;最后,执行npmi命令完成安装
    2024-12-12
  • node.js利用express自动搭建项目的全过程

    node.js利用express自动搭建项目的全过程

    这篇文章主要给大家介绍了关于node.js利用express自动搭建项目的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04

最新评论