前端到全栈:MAC中使用iTerm2一秒钟打开多台服务器阵列
背景
随着前端行业的不断发展,尤其是NodeJs的横空出世,前端能够做的事情越来越多,从刚开始单纯的做网页到现在遍布于互联网的各个领域,如:前端页面、移动端嵌入式H5(Hybird APP )、React Native等的原生级移动端应用、Electron等开发桌面应用、express、eggjs、koa开发的NodeJs服务、以及基于NodeJs开发的各种实用工具webpack、gulp、grunt等等,可以说,如今的前端已经从以往仅关注与页面的“小前端”演变为如今涉及各个领域的“大前端”了,前端工程师最终也都向着全栈工程师不断的前进着。而作为前端工程师迈向全栈工程师的标志性节点,前端项目的服务部署、nginx配置、负载均衡等,都是极为重要的。工欲善其事必先利其器,要对服务器进行操作,首先我们需要能够迅速登录目标服务器,便捷的操作服务,这也就是本文需要聊的一些小技巧
为什么使用iTerm2
有服务端开发经验的小伙伴们可能会疑惑了,链接服务直接使用[secureCRT][https://www.vandyke.com/download/securecrt/4.0/index.html]工具不就行了么,方便快捷。当然,使用secureCRT
确实能够便捷的连接服务器,对于一些常规的服务器操作,也都支持的很好的,我们也可以直接使用这个现成工具,但是,本人在实际工作中使用secureCRT
也遇到了一些不方便的点。
- 软件是付费的,经济拮据的小伙伴们需要PJ才能使用
- 作为一个前端,简直无法接受如此粗糙的界面设计
- 仅能在新的tab中打开会话,无法一键打开常用
服务器阵列
- 服务器标识很不明显,只能通过tab标题或者自己查询服务器ip才能知道自己当前连接的是哪一台服务器,在遇到需要同时操作多台服务器的场景时容易混乱出错
上面说了个人使用secureCRT
的时候用得不是很爽的几个点,当然,这也仅仅是个人的感受,说不定有其他小伙伴已经解决了上述的问题或者是已经习惯了。
接下来就说一下为啥选择使用iTerm2
-
免费:作为一个相当强大的终端工具,居然是免费的,简直是开发者的福音
-
界面风格符合我们前端的审美,贴张图看看
- 可以一键打开常用服务器阵列,阵列中每一个会话都能打开各自的目标服务器
-
不仅可以新建tab,还可以在同一个tab中切割页面,实现同一个tab操作多台服务器的操作,如上图
-
服务器标识明显,直接在右上角以透明的水印直接显示服务器标识,可以自由设置服务器标识,方便识别,如设置为:
- 正式环境: 127.0.0.1
- 测试环境: 127.0.0.2
- 预发布环境: 127.0.0.3
-
查找服务器方便,直接快捷键
command+o
即可打开搜索窗口进行搜索或者使用快捷键command+shift+b
直接打开常驻页面右侧的服务器列表窗口
如何使用iTerm2
连接服务器
无论使用什么工具,登录服务器都绕不开ssh
,我们使用item2
也一样,都是通过ssh命令进行连接。不过,使用ssh
连接服务器,也分为几种情况:
1. 使用密码登录(一般只有一些个人服务器会使用密码登录)
2. 使用密钥登录(基本上企业服务器都是采用此种方式进行登陆的,安全、无需输入密码)
本文要讲的也是使用密钥登录的方案,至于密码登录方案的话,如果感兴趣,大可自行网上搜索,已经有了很多现成的方案,此处就不再赘述。
那么,直入正题,我们要如何使用ssh
登录服务器呢?
首先,需要生成秘钥对
# 邮件改为自己的邮箱,将密钥保存在指定目录,可直接一路回车,密钥默认保存在用户目录的.ssh目录下
# 生成两个文件:xxxx xxxx.pub
ssh-keygen -t rsa -C "email@xxx.com"
# 将密钥添加入系统钥匙串,这样就不需要每次都执行ssh-add xxxx,注意,新版的mac系统此方法失效,重启系统后仍需要重新执行ssh-add xxxx方能连接福气,解决方案参考下一步
ssh-add -K ~/xxxx
具体操作流程此处不再赘述,可参考文章:Mac环境下生成ssh密钥
密钥准备好了,就可以直接通过ssh
进行登录
# -p后面是服务器的ssh的端口,根据服务器不同自定义
# 端口后面分别是:用户名@服务器地址(ip或域名)
ssh -p 30000 userName@serverHost
MAC升级系统后,每次重启都需要重新执行ssh-add
命令的解决方案
在系统升级之前,我们可以通过命令
ssh-add -K ~/.ssh/Identity
ssh-add ~/.ssh/Identity
这样,重启系统之后,就会自动加入密钥,不用手动操作了。
为了避免每次都需要手动添加私钥的操作,这边采取了使用自动化脚本的方案,在用户登录时自动执行脚本,从而避免手动执行。自动化脚本文件的创建及使用流程如下:
将我们的私钥加入到mac的钥匙串中,重启之后依然生效,但是,不止从哪一个版本开始,这个方法就失效了,每次重启系统之后,都需要手动执行一下
根据给定服务器ip生成iTem2
配置文件
iTerm2
给我们提供了一种可以快速创建profile
的方式,即批量到如profile.json文件,不过,如果只有1~2台服务器还好,直接根据模版手写json
文件即可,但是大部分企业,服务器的数量可不止一台两台的,因此,我们需要一个批量生成profile.json
文件的工具。本文采用NodeJs
实现这个小工具,如采用本文提供的方案,请确保本地已经安装了NodeJs
环境。此处提供的工具只是作为一个简单的示例和提供思路,有能力的小伙伴可以自己丰富工具的功能,如:根据业务模块和环境进行分类、解析csv文件等作为数据源
/create-item2-profile/package.json
{
"name": "create-item2-profile",
"description": "批量创建item2配置文件",
"version": "0.1.0",
"repository": "",
"keywords": [],
"author": {
"name": "kiner"
},
"scripts": {
},
"dependencies": {
"yargs": "^15.4.1"
},
"devDependencies": {
}
}
/create-item2-profile/index.js
const fs = require('fs');
const path = require('path');
const argv = require('yargs').argv;
// item2 的默认工作目录
// 参数传递说明:node ./index.js --wd=/Users/xxxx
const workDir = argv['wd'];
// 参数传递说明:node ./index.js --account=userName
const account = argv['account'];
// 需要生成的服务器要放在哪些标签下面,如此处设置为跳板机,则到时候生成的配置中,我们所有的服务器都会成为跳板机的子分类
// 参数传递说明:node ./index.js --tags=跳板机
let argTags = argv['tags'];
let tags = [];
if(argTags){
tags = argTags.split(',').filter(item=>!!item);
}else{
tags = ["跳板机"];
}
// 用于登录到跳板机后执行的脚本,用来登录目标服务器
const loginShellPath = path.resolve(__dirname, './login.exp');
// 服务器类型映射表,区分测试环境、预发布环境、正式环境,会根据不同的环境给服务器打上不同的标签,方便识别,未列举的服务器,将会打上"未分类环境"标签
// 参数传递说明:node ./index.js --sts-testing=127.0.0.1 --sts-pre=192.168.0.1 -sts-prod=0.0.0.0,0.0.0.1
const argServerTypeTesting = argv['sts-testing'];
const argServerTypePre = argv['sts-pre'];
const argServerTypeProd = argv['sts-prod'];
let serverType = {
testing: ['127.0.0.1'],
pre: ['192.168.0.1'],
prod: [
'0.0.0.0',
'0.0.0.1'
]
};
if(argServerTypeTesting){
serverType.testing = argServerTypeTesting.split(',').filter(item=>!!item);
}
if(argServerTypePre){
serverType.pre = argServerTypePre.split(',').filter(item=>!!item);
}
if(argServerTypeProd){
serverType.prod = argServerTypeProd.split(',').filter(item=>!!item);
}
// ip列表文件路径
// 参数传递说明:node ./index.js --ips=/path/to/ips.txt
const ipsPath = argv['ips'] || path.resolve(__dirname, './ips.txt');
// 输出配置文件路径
// 参数传递说明:node ./index.js --o=/path/to/create-item2-profile/profiles.json
const outputFilePath = argv['o'] || path.resolve(__dirname, './profiles.json');
function resolveJSON(ips, tpl){
const res = {
Profiles: []
};
ips.forEach(ip=>{
const copyTpl = Object.assign({}, tpl);
// 拼接登录到跳板季候需要执行的命名,需传入目标服务器ip、和登录账号(因我们登录服务器使用的是密钥,因此无需密码)
const currentInitialCommand = `${loginShellPath} ${ip} ${account}`;
// 根据不同的环境为服务器打上标签
let label = '';
if(serverType.testing.includes(ip)){
label = '测试环境';
}else if(serverType.pre.includes(ip)){
label = '预发布环境';
}else if(serverType.prod.includes(ip)){
label = '正式环境'
}else{
label = '未分类环境';
}
// 服务器名称
const name = `${ip}-${label}`;
// 服务器的标记
const badgeText = `${label}:\n${ip}`;
copyTpl.Tags = tags;
copyTpl.Name = name;
copyTpl["Badge Text"] = badgeText;
copyTpl["Initial Text"] = currentInitialCommand;
copyTpl["Working Directory"] = workDir;
res.Profiles.push(copyTpl);
});
return res;
}
// 读取ip列表,过滤空行和注释
const ips = fs.readFileSync(ipsPath).toString('utf8').split('\n').filter(item=>!!item&&!item.startsWith('#'));
let tpl = fs.readFileSync(path.resolve(__dirname,'./profiles-tpl.json')).toString('utf8');
try{
tpl = JSON.parse(tpl);
const resJSON = resolveJSON(ips, tpl);
fs.writeFileSync(outputFilePath, JSON.stringify(resJSON, null, 4));
console.info('生成成功');
}catch (e) {
console.error('模版json文件格式错误', e);
}
-
/create-item2-profile/ips.txt
# 在此文件把所有服务器的ip都列出来,工具扫描时会忽略#开头的行 127.0.0.1 192.168.0.1 xxx.xxx.x.x
-
/create-item2-profile/profiles-tpl.json
{ "Ansi 5 Color" : { "Red Component" : 0.752197265625, "Color Space" : "sRGB", "Blue Component" : 0.74494361877441406, "Alpha Component" : 1, "Green Component" : 0.24931684136390686 }, "Tags" : [], "Ansi 12 Color" : { "Red Component" : 0.65349078178405762, "Color Space" : "sRGB", "Blue Component" : 0.9485321044921875, "Alpha Component" : 1, "Green Component" : 0.67044717073440552 }, "Use Non-ASCII Font" : false, "Ansi 7 Color" : { "Red Component" : 0.7810397744178772, "Color Space" : "sRGB", "Blue Component" : 0.78104829788208008, "Alpha Component" : 1, "Green Component" : 0.78105825185775757 }, "Bold Color" : { "Red Component" : 0.99999600648880005, "Color Space" : "sRGB", "Blue Component" : 1, "Alpha Component" : 1, "Green Component" : 1 }, "Ansi 8 Color" : { "Red Component" : 0.8836669921875, "Color Space" : "sRGB", "Blue Component" : 0.63775148615241051, "Alpha Component" : 1, "Green Component" : 0.86831978729730963 }, "Ansi 9 Color" : { "Red Component" : 0.8659515380859375, "Color Space" : "sRGB", "Blue Component" : 0.45833224058151245, "Alpha Component" : 1, "Green Component" : 0.47524076700210571 }, "Horizontal Spacing" : 1, "Rows" : 25, "Default Bookmark" : "No", "Ansi 4 Color" : { "Red Component" : 0.15404300391674042, "Color Space" : "sRGB", "Blue Component" : 0.78216177225112915, "Alpha Component" : 1, "Green Component" : 0.26474356651306152 }, "Cursor Guide Color" : { "Red Component" : 0.70213186740875244, "Color Space" : "sRGB", "Blue Component" : 1, "Alpha Component" : 0.25, "Green Component" : 0.9268307089805603 }, "Non-ASCII Anti Aliased" : true, "Use Bright Bold" : true, "Ansi 10 Color" : { "Red Component" : 0.3450070321559906, "Color Space" : "sRGB", "Blue Component" : 0.56541937589645386, "Alpha Component" : 1, "Green Component" : 0.9042816162109375 }, "Ambiguous Double Width" : false, "Jobs to Ignore" : [ "rlogin", "ssh", "slogin", "telnet" ], "Ansi 15 Color" : { "Red Component" : 0.99999600648880005, "Color Space" : "sRGB", "Blue Component" : 1, "Alpha Component" : 1, "Green Component" : 1 }, "Foreground Color" : { "Red Component" : 0.63807237893342972, "Color Space" : "sRGB", "Blue Component" : 0.6499385232465309, "Alpha Component" : 1, "Green Component" : 0.66375732421875 }, "Working Directory" : "", "Allow Title Setting" : false, "Blinking Cursor" : false, "Disable Window Resizing" : true, "Sync Title" : false, "Prompt Before Closing 2" : false, "BM Growl" : true, "Command" : "", "Description" : "Default", "Mouse Reporting" : true, "Screen" : -1, "Selection Color" : { "Red Component" : 0.70196080207824707, "Color Space" : "sRGB", "Blue Component" : 1, "Alpha Component" : 1, "Green Component" : 0.84313726425170898 }, "Columns" : 80, "Idle Code" : 0, "Ansi 13 Color" : { "Red Component" : 0.8821563720703125, "Color Space" : "sRGB", "Blue Component" : 0.8821563720703125, "Alpha Component" : 1, "Green Component" : 0.4927266538143158 }, "Custom Command" : "No", "ASCII Anti Aliased" : true, "Non Ascii Font" : "Monaco 12", "Vertical Spacing" : 1, "Icon" : 1, "Use Bold Font" : true, "Option Key Sends" : 0, "Title Components" : 64, "Selected Text Color" : { "Red Component" : 0, "Color Space" : "sRGB", "Blue Component" : 0, "Alpha Component" : 1, "Green Component" : 0 }, "Background Color" : { "Red Component" : 0.0806884765625, "Color Space" : "sRGB", "Blue Component" : 0.12103271484375, "Alpha Component" : 1, "Green Component" : 0.099111050367355347 }, "Character Encoding" : 4, "Ansi 11 Color" : { "Red Component" : 0.9259033203125, "Color Space" : "sRGB", "Blue Component" : 0, "Alpha Component" : 1, "Green Component" : 0.8833775520324707 }, "Use Italic Font" : true, "Unlimited Scrollback" : false, "Keyboard Map" : { "0xf700-0x260000" : { "Text" : "[1;6A", "Action" : 10 }, "0x37-0x40000" : { "Text" : "0x1f", "Action" : 11 }, "0x32-0x40000" : { "Text" : "0x00", "Action" : 11 }, "0xf709-0x20000" : { "Text" : "[17;2~", "Action" : 10 }, "0xf70c-0x20000" : { "Text" : "[20;2~", "Action" : 10 }, "0xf729-0x20000" : { "Text" : "[1;2H", "Action" : 10 }, "0xf72b-0x40000" : { "Text" : "[1;5F", "Action" : 10 }, "0xf705-0x20000" : { "Text" : "[1;2Q", "Action" : 10 }, "0xf703-0x260000" : { "Text" : "[1;6C", "Action" : 10 }, "0xf700-0x220000" : { "Text" : "[1;2A", "Action" : 10 }, "0xf701-0x280000" : { "Text" : "0x1b 0x1b 0x5b 0x42", "Action" : 11 }, "0x38-0x40000" : { "Text" : "0x7f", "Action" : 11 }, "0x33-0x40000" : { "Text" : "0x1b", "Action" : 11 }, "0xf703-0x220000" : { "Text" : "[1;2C", "Action" : 10 }, "0xf701-0x240000" : { "Text" : "[1;5B", "Action" : 10 }, "0xf70d-0x20000" : { "Text" : "[21;2~", "Action" : 10 }, "0xf702-0x260000" : { "Text" : "[1;6D", "Action" : 10 }, "0xf729-0x40000" : { "Text" : "[1;5H", "Action" : 10 }, "0xf706-0x20000" : { "Text" : "[1;2R", "Action" : 10 }, "0x34-0x40000" : { "Text" : "0x1c", "Action" : 11 }, "0xf700-0x280000" : { "Text" : "0x1b 0x1b 0x5b 0x41", "Action" : 11 }, "0x2d-0x40000" : { "Text" : "0x1f", "Action" : 11 }, "0xf70e-0x20000" : { "Text" : "[23;2~", "Action" : 10 }, "0xf702-0x220000" : { "Text" : "[1;2D", "Action" : 10 }, "0xf703-0x280000" : { "Text" : "0x1b 0x1b 0x5b 0x43", "Action" : 11 }, "0xf700-0x240000" : { "Text" : "[1;5A", "Action" : 10 }, "0xf707-0x20000" : { "Text" : "[1;2S", "Action" : 10 }, "0xf70a-0x20000" : { "Text" : "[18;2~", "Action" : 10 }, "0x35-0x40000" : { "Text" : "0x1d", "Action" : 11 }, "0xf70f-0x20000" : { "Text" : "[24;2~", "Action" : 10 }, "0xf703-0x240000" : { "Text" : "[1;5C", "Action" : 10 }, "0xf701-0x260000" : { "Text" : "[1;6B", "Action" : 10 }, "0xf702-0x280000" : { "Text" : "0x1b 0x1b 0x5b 0x44", "Action" : 11 }, "0xf72b-0x20000" : { "Text" : "[1;2F", "Action" : 10 }, "0x36-0x40000" : { "Text" : "0x1e", "Action" : 11 }, "0xf708-0x20000" : { "Text" : "[15;2~", "Action" : 10 }, "0xf701-0x220000" : { "Text" : "[1;2B", "Action" : 10 }, "0xf70b-0x20000" : { "Text" : "[19;2~", "Action" : 10 }, "0xf702-0x240000" : { "Text" : "[1;5D", "Action" : 10 }, "0xf704-0x20000" : { "Text" : "[1;2P", "Action" : 10 } }, "Window Type" : 0, "Initial Text" : "", "Background Image Location" : "", "Blur" : false, "Badge Color" : { "Red Component" : 1, "Color Space" : "sRGB", "Blue Component" : 1, "Alpha Component" : 0.3, "Green Component" : 1 }, "Scrollback Lines" : 1000, "Send Code When Idle" : false, "Close Sessions On End" : true, "Terminal Type" : "xterm-256color", "Visual Bell" : true, "Flashing Bell" : false, "Badge Text" : "", "Silence Bell" : false, "Ansi 14 Color" : { "Red Component" : 0.37597531080245972, "Color Space" : "sRGB", "Blue Component" : 1, "Alpha Component" : 1, "Green Component" : 0.99263292551040649 }, "Name" : "", "Cursor Text Color" : { "Red Component" : 0, "Color Space" : "sRGB", "Blue Component" : 0, "Alpha Component" : 1, "Green Component" : 0 }, "Minimum Contrast" : 0, "Shortcut" : "", "Triggers" : [ { "partial" : true, "parameter" : "\/usr\/local\/bin\/iterm2-send-zmodem.sh", "regex" : "\\*\\*B0100", "action" : "MuteCoprocessTrigger" }, { "partial" : true, "parameter" : "\/usr\/local\/bin\/iterm2-recv-zmodem.sh", "regex" : "\\*\\*B00000000000000", "action" : "MuteCoprocessTrigger" } ], "Ansi 0 Color" : { "Red Component" : 0.078431375324726105, "Color Space" : "sRGB", "Blue Component" : 0.11764705926179886, "Alpha Component" : 1, "Green Component" : 0.098039217293262482 }, "Ansi 1 Color" : { "Red Component" : 0.7074432373046875, "Color Space" : "sRGB", "Blue Component" : 0.16300037503242493, "Alpha Component" : 1, "Green Component" : 0.23660069704055786 }, "Ansi 2 Color" : { "Red Component" : 0, "Color Space" : "sRGB", "Blue Component" : 0, "Alpha Component" : 1, "Green Component" : 0.7607843279838562 }, "Link Color" : { "Red Component" : 0.19802422821521759, "Color Space" : "sRGB", "Blue Component" : 0.9337158203125, "Alpha Component" : 1, "Green Component" : 0.55789834260940552 }, "Cursor Color" : { "Red Component" : 0.99997633695602417, "Color Space" : "sRGB", "Blue Component" : 0.99998724460601807, "Alpha Component" : 1, "Green Component" : 1 }, "Right Option Key Sends" : 0, "Ansi 6 Color" : { "Red Component" : 0, "Color Space" : "sRGB", "Blue Component" : 0.78166204690933228, "Alpha Component" : 1, "Green Component" : 0.77425903081893921 }, "Transparency" : 0, "Normal Font" : "MesloLGSForPowerline-Regular 12", "Guid" : "8338DBCE-7E89-4945-BA07-B187AD8676FB", "Ansi 3 Color" : { "Red Component" : 0.78058648109436035, "Color Space" : "sRGB", "Blue Component" : 0, "Alpha Component" : 1, "Green Component" : 0.76959484815597534 }, "Custom Directory" : "No" }
-
/create-item2-profile/login.exp
#!/usr/bin/expect -f set timeout -1 # 跳板机的域名或ip set RELAYSVR "xxx.xxx.com" # 目标服务器host set sshhost [lindex $argv 0] # 登录目标服务器的用户名 set sshuser [lindex $argv 1] # 登录目标服务器后,需要执行的命令 set sshcmd [lindex $argv 2] spawn ssh -p 30000 -t $sshuser@$RELAYSVR "ssh -p 30000 $sshhost;$sshcmd" interact exit
以上便是此方案的所有代码,接下来我们来运行一下试试,为了方便,这里直接写了一个
bash
脚本用来执行:/create-item2-profile/run.sh
#!/usr/bin/env bash # wd: 默认工作目录,一般直接设置为用户根目录 # account: 登录账号 # tags: 将服务器放在哪些分类下面,如设置tags=跳板机,companyName,那么我们就能跳板机和companyName分类下都能找到服务器 # sts-testing: 为目标ip服务器打上测试环境标签 # sts-pre: 为目标ip服务器打上预发布环境标签 # sts-prod: 为目标ip服务器打上正式环境标签 # ips: 服务器ip列表文件路径,默认为当前目录下的ips.txt # o: 生成的iTerm2配置文件输出目录,默认输出到当前目录的profiles.json文件 node ./index.js \ --wd=/Users/userName \ --account=tangwenhui1 \ --tags=跳板机,companyName \ --sts-testing=127.0.0.1 \ --sts-pre=192.168.0.1 \ --sts-prod=0.0.0.1,0.0.0.2 \ #--ips=/path/to/ips.txt \ #--o=/path/to/ \
直接运行脚本
bash ./run.sh
此时,便会在输出目录生成一个
profiles.json
文件,接下来,根据下面的步骤导入文件即可:
创建并保存服务器阵列
到上一部为止,我们就已经把服务器的配置导入到item2了,接下来,我们可以把一些常用的服务器放到一起形成服务器阵列,下次需要时,直接可以打开这个服务器阵列,一次性显示多台服务器,步骤如下:
需要将多台服务器组合成阵列,只需要打开目标服务器,然后拖动该服务器对应的标题栏到想要放置的位置就可以了。服务阵列组合好之后,直接保存就可以了。
iTerm2中批量清理导入的profiles
有些时候,可能因为模版的某些参数需要调整,我们可能会多次尝试导入配置文件,导致iTerm2
中产生一推无用配置文件,我们可以采用下述的方式删除无用的配置。
使用xcode
打开文件~/Library/Preferences/com.googlecode.iterm2.plist
- 操作之前记得备份