美文网首页程序员
Linux 小记 — Ubuntu 自动化配置

Linux 小记 — Ubuntu 自动化配置

作者: 捷义 | 来源:发表于2018-12-11 21:12 被阅读5次

    前言

    工欲善其事,必先利其器。经过多次的重复配置 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"
    

    结语

    终于剔除了一块疙瘩,以后一拿到服务器就可以愉快地玩耍了。当然,以上脚本只适合我个人的使用习惯,部分代码逻辑比较粗暴,各位看官参考和多多点赞就好,切勿直接使用,若有更好的想法,欢迎留言。


    我的公众号《有刻》,我们共同成长!


    相关文章

      网友评论

        本文标题:Linux 小记 — Ubuntu 自动化配置

        本文链接:https://www.haomeiwen.com/subject/vlxyhqtx.html