Node.js+pm2+ssh2模塊實現簡單的自動化部署腳本
安裝ssh2和dotenv模塊
首先,我們需要安裝ssh2和dotenv模塊:
npm install ssh2 dotenv --save
然后,我們需要在項目根目錄下創(chuàng)建一個.env文件,用來存放一些敏感的配置信息,例如服務器的IP地址、端口號、用戶名、私鑰等。這樣,我們就可以避免將這些信息暴露在代碼中,也方便我們根據不同的環(huán)境進行切換。.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模塊提供的一個類,用來創(chuàng)建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對象中的屬性來獲取環(huán)境變量的值,如果沒有定義,則使用默認值。注意,私鑰需要轉換為字符串格式。
創(chuàng)建Client實例調用connect方法建立SSH連接
接著,我們需要創(chuàng)建一個Client實例,并調用connect方法來建立SSH連接:
// 創(chuàng)建SSH連接
const conn = new Client();
conn.on('ready', () => {
console.log('SSH連接成功');
// ...
}).connect(sshConfig);
// 監(jiān)聽error事件
conn.on('error', (err) => {
console.error('SSH連接失敗', err);
});
// 結束SSH連接
conn.on('end', () => {
console.log('SSH連接已斷開');
});在ready事件的回調函數中,我們需要進行部署操作。
具體來說,我們需要做兩件事:
一是執(zhí)行npm run build命令來構建項目;
二是將構建后的文件上傳到遠程服務器上。(當然,構建指令也可以在連接之前進行)
// 項目構建
const { execSync } = require('child_process');
execSync('npm run build', { stdio: 'inherit' })execSync 是 Node.js 的一個內置模塊,它可以同步地執(zhí)行一個子進程,并返回子進程的輸出。這樣可以避免異步的回調地獄,也可以保證構建的順序和正確性。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);
}
});
}同時我們需要有人解析文件目錄,并執(zhí)行我們的上傳指令:
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('所有文件上傳成功');
// 執(zhí)行SSH命令
conn.shell((err, stream) => {
if (err) throw err;
stream.on('close', () => {
console.log('遠程命令執(zhí)行完畢');
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';
// 創(chuàng)建SSH連接
const conn = new Client();
// 監(jiān)聽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('所有文件上傳成功');
// 執(zhí)行SSH命令
conn.shell((err, stream) => {
if (err) throw err;
stream.on('close', () => {
console.log('遠程命令執(zhí)行完畢');
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);
// 監(jiān)聽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 res.end和res.send的區(qū)別
今天小編就為大家分享一篇基于nodejs res.end和res.send的區(qū)別,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05
關于Node.js的events.EventEmitter用法介紹
本篇文章主要介紹了關于Node.js的events.EventEmitter用法,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04
windows離線環(huán)境安裝node-sass全過程
文章介紹了如何在Windows系統上安裝和配置node-sass,并提供了一個詳細的步驟指南,首先,通過命令行查看支持版本;然后,下載對應版本的node-sass安裝包;接著,在npm配置文件中增加SASS_BINARY_PATH路徑配置;最后,執(zhí)行npmi命令完成安裝2024-12-12

