前言
工欲善其事,必先利其器。經(jīng)過多次的重復(fù)配置 ubuntu 開發(fā)壞境,我終于決定花點(diǎn)時(shí)間總結(jié)一下,并將其寫成一個(gè)自動(dòng)化配置腳本。服務(wù)器實(shí)例:ubuntu 16.04,技術(shù)棧:shell,python。

1. 主機(jī)名
可以通過 hostname newname 修改主機(jī)名,不過最好是寫入 /etc/hostname 文件,重啟生效。為了讓同一內(nèi)網(wǎng)段的主機(jī)可以通過主機(jī)名訪問,應(yīng)在 /etc/hosts 中添加私有ip的解析。
2. 命令提示符
與命令提示符相關(guān)的環(huán)境變量是 PS1,初始值為:PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$',各字符解釋如下:
#\u:當(dāng)前登陸用戶名
#\h:當(dāng)前主機(jī)名(如 ubuntu)
#\H:當(dāng)前主機(jī)的域名全稱(ubuntu.ubuntu.com)
#\w:當(dāng)前目錄(絕對路徑)
#\W:當(dāng)前目錄的 basename(只顯示最后一級路徑)
#\$:一般用戶為$,root 用戶為#
#\t:當(dāng)前時(shí)間(24小時(shí)制,HH:MM:SS)
#\T:當(dāng)前時(shí)間(12小時(shí))
#\@:當(dāng)前時(shí)間(Am/PM)
#\d:當(dāng)前日期
#\v:Bash 版本
#\V:Bash 的發(fā)布版本號
#\S:Shell 名稱
對于我來說我只需要 \u、\h、\W(\w 如果多進(jìn)幾個(gè)目錄敲命令的體驗(yàn)就很差了),為了讓命令行一目了然,最好給命令提示符加個(gè)顏色 PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\W\[\e[0m\]\$ ',顏色代碼解釋如下:
| 前景色 | 背景色 | 效果 |
|---|---|---|
| 30m | 40; | 黑 |
| 31m | 41; | 紅 |
| 32m | 42; | 綠 |
| 33m | 43; | 黃 |
| 34m | 44; | 藍(lán) |
| 35m | 45; | 紫 |
| 36m | 46; | 天藍(lán) |
| 37m | 47; | 白 |
#\033[背景;字體顏色m或者\(yùn)e[背景;字體顏色m
#0 重新設(shè)置屬性到缺省設(shè)置
#1 設(shè)置粗體
#2 設(shè)置一半亮度(模擬彩色顯示器的顏色)
#4 設(shè)置下劃線(模擬彩色顯示器的顏色)
#5 設(shè)置閃爍
#7 設(shè)置反向圖象
#22 設(shè)置一般密度
#24 關(guān)閉下劃線
#25 關(guān)閉閃爍
#27 關(guān)閉反向圖象
3. GNU Readline Library
Readline 的解釋:從終端獲取用戶輸入的字符流,辯認(rèn)其中一些特定的字符序列,然后執(zhí)行這些序列對應(yīng)的函數(shù)或者宏。通俗一點(diǎn)講就是綁定熱鍵,比如在 bash 中默認(rèn)按下 ctrl+a 執(zhí)行的是光標(biāo)回到行首的命令。
此處我需要優(yōu)化的是:1、Tab 補(bǔ)全時(shí)忽略大小寫;2、通過 ↑↓ 查詢已輸入關(guān)鍵字的歷史記錄。
vim ~/.inputrc
"\e[A": history-search-backward
"\e[B": history-search-forward
# auto complete ignoring case
set show-all-if-ambiguous on
set completion-ignore-case on
source ~/.inputrc
4. 歷史記錄
我需要:1、忽略重復(fù)的歷史命令;2、保存更多的歷史記錄;3、忽略特定的歷史記錄;4、新建的終端同步 history。
export HISTCONTROL=ignoreboth # ignoreboth=ignoredups:ignorespace
export HISTSIZE=10000
export HISTFILESIZE=20000
export HISTIGNORE='pwd:ls'
# make sure all terminals save history
shopt -s histappend
export PROMPT_COMMAND="history -a; $PROMPT_COMMAND"
5. Git 配置
想要流暢地使用 git,我認(rèn)為有幾點(diǎn)必須配置:
5.1 在命令提示符上顯示 git 基本信息
安裝完 git 之后,在 /etc/bash_completion.d 目錄中會(huì)生成一個(gè) git-prompt 文件:
if [[ -e /usr/lib/git-core/git-sh-prompt ]]; then
. /usr/lib/git-core/git-sh-prompt
fi
打開/usr/lib/git-core/git-sh-prompt,注釋里面寫了完整的操作步驟:
# To enable:
#
# 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
# 2) Add the following line to your .bashrc/.zshrc:
# source ~/.git-prompt.sh
# 3a) Change your PS1 to call __git_ps1 as
# command-substitution:
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
# ZSH: setopt PROMPT_SUBST ; PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
# the optional argument will be used as format string.
# 3b) Alternatively, for a slightly faster prompt, __git_ps1 can
# be used for PROMPT_COMMAND in Bash or for precmd() in Zsh
# with two parameters, <pre> and <post>, which are strings
# you would put in $PS1 before and after the status string
# generated by the git-prompt machinery. e.g.
# Bash: PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
# will show username, at-sign, host, colon, cwd, then
# various status string, followed by dollar and SP, as
# your prompt.
# ZSH: precmd () { __git_ps1 "%n" ":%~$ " "|%s" }
# will show username, pipe, then various status string,
# followed by colon, cwd, dollar and SP, as your prompt.
# Optionally, you can supply a third argument with a printf
# format string to finetune the output of the branch status
cp /usr/lib/git-core/git-sh-prompt .git-prompt.sh
source .git-prompt.sh
export PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\W$(__git_ps1 " (%s)")\[\e[0m\]\$ '
export PROMPT_COMMAND='__git_ps1 "\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\W\[\e[0m\]" "\$ "'
接下來還需賦值幾個(gè) git 環(huán)境變量讓提示符顯示更多 git 狀態(tài):
export GIT_PS1_SHOWDIRTYSTATE=true
export GIT_PS1_SHOWCOLORHINTS=true
export GIT_PS1_SHOWUNTRACKEDFILES=true
export GIT_PS1_SHOWUPSTREAM="auto"
git config --global alias.lg "log --color --graph --pretty=format:'%C(yellow)%h%Creset%C(cyan)%C(bold)%C(red)%d%Creset %s %C(green)[%cn] %Creset%C(cyan)[%cd]%Creset' --date=format-local:'%m-%d %H:%M'"
顯示效果:

5.2 多賬號配置
我有兩個(gè) git 賬號,分別是 gitee 和 github,且分別擁有各自的 name、email 和 ssh-key,我需要:
Ⅰ、兩個(gè)賬號都可以使用各自的密鑰對免密碼訪問
生成密鑰對:
# ssh-keygen [-q] [-b bits] [-t dsa | ecdsa | ed25519 | rsa | rsa1] [-N new_passphrase] [-C comment] [-f output_keyfile]
ssh-keygen -t rsa -C "github@youclk.com" -f ~/.ssh/github/id_rsa -N ""
ssh-keygen -t rsa -C "gitee@youclk.com" -f ~/.ssh/gitee/id_rsa -N ""
編輯~/.ssh/config
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github/id_rsa
Host gitee.com
HostName gitee.com
User git
IdentityFile ~/.ssh/gitee/id_rsa
聯(lián)通測試:

Ⅱ、 到達(dá)各自的倉庫時(shí)自動(dòng)切換用戶名和郵箱
為了保證各倉庫能夠以正確的用戶信息提交版本,需要取消全局的用戶設(shè)置(我不理解為什么 global 中的用戶信息要去覆蓋各倉庫的,反過來不是更好嗎)。
git config --global --unset user.name
git config --global --unset user.emal
實(shí)現(xiàn)自動(dòng)切換能想到的方案有很多,我更傾向于去修改 .git-prompt.sh,在 __git_ps1 () 函數(shù)末尾處增加一段邏輯:
if [ -z `git config user.name` ] && [ -z `git config user.email` ]; then
local git_remote=`git remote -v`
if [[ $git_remote =~ "github" ]]; then
`git config user.name "github" && git config user.email "github@youclk.com"`
elif [[ $git_remote =~ "gitee" ]]; then
`git config user.name "gitee" && git config user.email "gitee@youclk.com"`
fi
fi
順帶多提一下,git 默認(rèn)忽略文件大小寫,然而作為輕微的強(qiáng)迫癥患者,我一定要和遠(yuǎn)程倉庫保持完全一致:git config --global core.ignorecase false。
6. 密鑰對管理
我可能會(huì)一次性創(chuàng)建n臺(tái)云服務(wù)器組成一個(gè)個(gè)集群,每個(gè)集群中有一個(gè) leader 和 n 個(gè) follower,follower 只是提供計(jì)算能力,它應(yīng)該把自己全權(quán)交給 leader,那么在 leader 上必須能夠訪問所有的
follower。這時(shí)候統(tǒng)一密鑰對管理就非常有必要了,只需要一個(gè)私鑰就可以訪問所有的服務(wù)器,其實(shí)上一節(jié)提到的 git 密鑰對也可以一起管理。本節(jié)展開的話其實(shí)就是一些腳本實(shí)現(xiàn),所以統(tǒng)一交給下一節(jié)歸納。
7. 自動(dòng)配置腳本編寫
現(xiàn)在我需要思考的是如何使用一行命令來自動(dòng)完成以上所有的配置。由于配置中涉及到一些私鑰等銘感信息,所以腳本必須放置于 git 私有庫中,但是 ubuntu 初始化的時(shí)候并沒有安裝 git,所以還需要一個(gè)公有庫來放置初始腳本,職能是安裝 git 和訪問私有庫。最終我需要實(shí)現(xiàn)執(zhí)行以下一行代碼就完成整個(gè) ubuntu 環(huán)境的配置:
# bash -c "$(curl -fsSL https://gitee.com/youclk/auto-config-entry/raw/master/centos/startup.sh)"
bash -c "$(curl -fsSL https://gitee.com/youclk/entry/raw/master/ubuntu/setting.sh)"
初始的入口腳本比較簡單(安裝 git,下載私有庫并執(zhí)行 python 腳本):
#!/bin/bash
apt update
# install git
if [ -z `which git` ]; then
apt install git
if [ ! $? -eq 0 ]; then exit 0; fi
fi
# switch path to .auto_config
if [ ! -d ~/.auto_config ]; then
mkdir ~/.auto_config
if [ ! $? -eq 0 ]; then exit 0; fi
fi
cd ~/.auto_config
# clone tools project
if [ ! -d "tools" ]; then
git clone https://gitee.com/youclk/tools.git
if [ ! $? -eq 0 ]; then exit 0; fi
fi
cd tools/ubuntu
python3 setting.py
rm -r ~/.auto_config
以下是 python 部分的結(jié)構(gòu):

代碼比較簡單,都是一些讀寫文件和結(jié)合系統(tǒng)命令的操作(步驟和說明都寫在注釋中了,不再贅述)。
setting.py:
import os
import socket
import subprocess
import sys
sys.path.append('../')
from utility import host
def edit_hostname():
"""
edit /etc/hostname and /etc/hosts
"""
old_hostname = socket.gethostname()
new_hostname = str.strip(input('please write a hostname:'))
if new_hostname and old_hostname != new_hostname:
subprocess.check_call(['hostname', new_hostname])
hostname_dir = '/etc/hostname'
hosts_dir = '/etc/hosts'
# write hostname
with open(hostname_dir, 'w') as f:
f.write(new_hostname + '\n')
# read hosts
with open(hosts_dir, 'r') as f:
hosts_lines = f.readlines()
# write hosts
with open(hosts_dir, 'w') as f:
local_ip = host.get_local_ip()
n = 0
for i in range(0, len(hosts_lines)):
if local_ip in hosts_lines[i]:
hosts_lines[i] = hosts_lines[i].replace(old_hostname, new_hostname)
n += 1
if not n:
hosts_lines.append('\n' + local_ip + '\t' + new_hostname + '\n')
f.writelines(hosts_lines)
def copy_config_files():
"""
configure git history readLine commandPrompt
"""
subprocess.check_call('cp -r bash_script/. ~/.', shell=True)
with open('/root/.bashrc', 'r+') as f:
bashrc = f.read()
if '.bashrc_pro' not in bashrc:
f.write('\nsource ~/.bashrc_pro.sh\n')
def configure_ssh_key():
# copy ssk_key
subprocess.check_call('cp -r ssh_key/. ~/.ssh/.', shell=True)
# chmod
subprocess.check_call('chmod 400 ~/.ssh/*/id_rsa', shell=True)
# configure git config
github_config = '''
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/git/id_rsa
'''
gitee_config = '''
Host gitee.com
HostName gitee.com
User git
IdentityFile ~/.ssh/git/id_rsa
'''
if os.path.exists('/root/.ssh/config'):
with open('/root/.ssh/config', 'r+') as f:
git_config = f.read()
if 'github.com' not in git_config:
f.write(github_config)
elif 'gitee.com' not in git_config:
f.write(gitee_config)
else:
with open('/root/.ssh/config', 'w') as f:
f.write(github_config + gitee_config)
if __name__ == '__main__':
if os.getuid() == 0:
edit_hostname()
copy_config_files()
configure_ssh_key()
print('success')
else:
print('please switch user => root')
host.py(一些可以公用的函數(shù)單獨(dú)抽離出來):
import socket
def get_local_ip():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as net:
net.connect(('8.8.8.8', 80))
return net.getsockname()[0]
.bashrc_pro.sh:
#!/bin/bash
# config git
source .git_prompt.sh
export PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\W$(__git_ps1 " (%s)")\[\e[0m\]\$ '
if [ "$(whoami)" == "root" ]; then
ps1_symbol="#"
else
ps1_symbol="$"
fi
export PROMPT_COMMAND='__git_ps1 "\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\W\[\e[0m\]" "$ps1_symbol "'
export GIT_PS1_SHOWDIRTYSTATE=true
export GIT_PS1_SHOWCOLORHINTS=true
export GIT_PS1_SHOWUNTRACKEDFILES=true
export GIT_PS1_SHOWUPSTREAM="auto"
# history
export HISTCONTROL=ignoreboth # ignoreboth=ignoredups:ignorespace
export HISTSIZE=10000
export HISTFILESIZE=20000
export HISTIGNORE='pwd:ls'
shopt -s histappend
export PROMPT_COMMAND="history -a; $PROMPT_COMMAND" # make sure all terminals save history
# alias
alias aliyun="ssh -i ~/.ssh/aliyun/id_rsa"
結(jié)語
終于剔除了一塊疙瘩,以后一拿到服務(wù)器就可以愉快地玩耍了。當(dāng)然,以上腳本只適合我個(gè)人的使用習(xí)慣,部分代碼邏輯比較粗暴,各位看官參考和多多點(diǎn)贊就好,切勿直接使用,若有更好的想法,歡迎留言。
我的公眾號《有刻》,我們共同成長!
