前言
工欲善其事, 必先利其器. 经过多次的重复配置 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"
结语
终于剔除了一块疙瘩, 以后一拿到服务器就可以愉快地玩耍了. 当然, 以上脚本只适合我个人的使用习惯, 部分代码逻辑比较粗暴, 各位看官参考和多多点赞就好, 切勿直接使用, 若有更好的想法, 欢迎留言.
来源: http://www.jianshu.com/p/6f01795840a0