前言
工欲善其事,必先利其器。經過多次的重復配置 ubuntu 開發壞境,我終於決定花點時間總結一下,並將其寫成一個自動化配置腳本。服務器實例:ubuntu 16.04,技術棧:shell,python。
1. 主機名
可以通過 hostname newname
修改主機名,不過最好是寫入 /etc/hostname 文件,重啟生效。為了讓同一內網段的主機可以通過主機名訪問,應在 /etc/hosts 中添加私有ip的解析。
2. 命令提示符
與命令提示符相關的環境變量是 PS1,初始值為:PS1='\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$'
,各字符解釋如下:
#\u:當前登陸用戶名
#\h:當前主機名(如 ubuntu)
#\H:當前主機的域名全稱(ubuntu.ubuntu.com)
#\w:當前目錄(絕對路徑)
#\W:當前目錄的 basename(只顯示最后一級路徑)
#\$:一般用戶為$,root 用戶為#
#\t:當前時間(24小時制,HH:MM:SS)
#\T:當前時間(12小時)
#\@:當前時間(Am/PM)
#\d:當前日期
#\v:Bash 版本
#\V:Bash 的發布版本號
#\S:Shell 名稱
對於我來說我只需要 \u、\h、\W(\w 如果多進幾個目錄敲命令的體驗就很差了),為了讓命令行一目了然,最好給命令提示符加個顏色 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; | 藍 |
35m | 45; | 紫 |
36m | 46; | 天藍 |
37m | 47; | 白 |
#\033[背景;字體顏色m或者\e[背景;字體顏色m
#0 重新設置屬性到缺省設置
#1 設置粗體
#2 設置一半亮度(模擬彩色顯示器的顏色)
#4 設置下划線(模擬彩色顯示器的顏色)
#5 設置閃爍
#7 設置反向圖象
#22 設置一般密度
#24 關閉下划線
#25 關閉閃爍
#27 關閉反向圖象
3. GNU Readline Library
Readline 的解釋:從終端獲取用戶輸入的字符流,辯認其中一些特定的字符序列,然后執行這些序列對應的函數或者宏。通俗一點講就是綁定熱鍵,比如在 bash 中默認按下 ctrl+a
執行的是光標回到行首的命令。
此處我需要優化的是:1、Tab
補全時忽略大小寫;2、通過 ↑↓
查詢已輸入關鍵字的歷史記錄。
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、忽略重復的歷史命令;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,我認為有幾點必須配置:
5.1 在命令提示符上顯示 git 基本信息
安裝完 git 之后,在 /etc/bash_completion.d
目錄中會生成一個 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\]" "\$ "'
接下來還需賦值幾個 git 環境變量讓提示符顯示更多 git 狀態:
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 多賬號配置
我有兩個 git 賬號,分別是 gitee 和 github,且分別擁有各自的 name、email 和 ssh-key,我需要:
Ⅰ、兩個賬號都可以使用各自的密鑰對免密碼訪問
生成密鑰對:
# 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
聯通測試:
Ⅱ、 到達各自的倉庫時自動切換用戶名和郵箱
為了保證各倉庫能夠以正確的用戶信息提交版本,需要取消全局的用戶設置(我不理解為什么 global 中的用戶信息要去覆蓋各倉庫的,反過來不是更好嗎)。
git config --global --unset user.name
git config --global --unset user.emal
實現自動切換能想到的方案有很多,我更傾向於去修改 .git-prompt.sh
,在 __git_ps1 ()
函數末尾處增加一段邏輯:
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 默認忽略文件大小寫,然而作為輕微的強迫症患者,我一定要和遠程倉庫保持完全一致:git config --global core.ignorecase false
。
6. 密鑰對管理
我可能會一次性創建n台雲服務器組成一個個集群,每個集群中有一個 leader 和 n 個 follower,follower 只是提供計算能力,它應該把自己全權交給 leader,那么在 leader 上必須能夠訪問所有的
follower。這時候統一密鑰對管理就非常有必要了,只需要一個私鑰就可以訪問所有的服務器,其實上一節提到的 git 密鑰對也可以一起管理。本節展開的話其實就是一些腳本實現,所以統一交給下一節歸納。
7. 自動配置腳本編寫
現在我需要思考的是如何使用一行命令來自動完成以上所有的配置。由於配置中涉及到一些私鑰等銘感信息,所以腳本必須放置於 git 私有庫中,但是 ubuntu 初始化的時候並沒有安裝 git,所以還需要一個公有庫來放置初始腳本,職能是安裝 git 和訪問私有庫。最終我需要實現執行以下一行代碼就完成整個 ubuntu 環境的配置:
# 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,下載私有庫並執行 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 部分的結構:
代碼比較簡單,都是一些讀寫文件和結合系統命令的操作(步驟和說明都寫在注釋中了,不再贅述)。
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
(一些可以公用的函數單獨抽離出來):
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"
結語
終於剔除了一塊疙瘩,以后一拿到服務器就可以愉快地玩耍了。當然,以上腳本只適合我個人的使用習慣,部分代碼邏輯比較粗暴,各位看官參考和多多點贊就好,切勿直接使用,若有更好的想法,歡迎留言。
我的公眾號《有刻》,我們共同成長!