ubuntu 使用备忘

2022-12-24 六 11:54 2024-07-12 五 21:58

修改历史

  • [2024-01-18 四 12:02] 添加 profile, bashrc 设置原则
  • [2023-12-08 五 13:11] 添加 "个人偏好与实用的平衡" 一节
  • [2023-12-02 六 11:30] 添加 gitkraken, 编程开发相关内容集中放到"开发环境"一章

本文记录使用 ubuntu16.04 到 ubuntu22.04 过程中的一些经验,会持续地更新。

总体原则

个人偏好与实用的平衡

用 linux 系统的好处是,可以在原始操作系统层之上比较方便地再做一次针对自己交互和使用偏好的抽象,比如定制界面、修改按键、 编写后台自动运行的服务等,当然我对开源的好感也是动机之一。个人使用 ubuntu 的核心特点是:

  • i3wm 窗口管理:使用它的主要目的是可以定制一套交互方式,比如自己设置许多快捷启动按键,popup 窗口等,并且界面比较简约,布局都是默认的,约定好把常用的窗口放在固定的工作区,那么来回切换是比较快捷的。
  • 用 xmodmap 和 xcape 修改按键,具体参考 i3wm: 无关生产力,且远不止窗口管理 中按键设置一节
  • vscode 主外

    用于写项目代码(当然不单 vscode, 一切有利于编程辅助的工具都不拒绝,比如 git 可视化用 gitkraken, 裸服务器用 vim 等)。vscode 中 不使用 evil/vim 按键风格, 基本用默认按键以保持在非个人机器上使用 vscode/pycharm 等主流 IDE 的"习惯可移植"性,不要被 vim 按键风格和自己的改键限制地太僵硬,在 IDE 里编程时多用鼠标、copilot 或其他 AI 辅助,这对放松手指也有好处。

  • emacs 主内

    使用 evil 按键风格,以 org 为核心,用于记录日程,roam 笔记,blog,写文章, 文学编程等,此外还有写 elisp 配置代码、一些复杂比如需要录制宏的编辑操作,有时也用于看知识密集的 pdf, epub,便于笔记。

    以上大部分是自己与自己交流的场景,比较注重体验和个性化,因此有大量根据自己小习惯的定制,上文的 xmodmap 改键更多是服务于此的。这方面要足够独一无二,特立独行,不需要考虑"移植性" , 因为特定场景的习惯只有在特定上下文里才会释放出来,就像在游戏里有一套自己的快捷键, 习惯后只有在玩游戏时才会自动去使用,很少会把这种习惯带到其他工具使用中。 但请别把 emacs 的外观配置成和 vscode 或 Word 一样,或者在其他软件里去复刻 emacs 里的功能,这样是在玩欺骗自己的游戏。

  • typora 预览 markdown 或做简单的修改编辑。
  • 使用 rime 输入法

    这个选择主要是 emacs 里用了 emacs-rime,但有更好的可以考虑替换。

  • 绘制 svg 示意图使用 draw.io 本地版
  • latex 用在线的 overleaf; org-mode 里用 latex-preview 写少量草稿
  • wps 作为备用的 office 软件
  • qq, 腾讯会议有官方版本,可正常使用。微信目前没有官方版本,不建议用非官方版,容易出现系统问题,少量场景下可以通过 微信文件传输助手网页版 传输文件或者消息。

以上基本满足了个人使用 95% 以上场景,剩下的部分(比如游戏)则使用其他备用系统。 另外,如果因为特别原因不能用个人机器,那么在其他系统上大部分情况只需要 vscode 即可,而 vscode 配置是可以通过账户同步的,少量数据还可以通过以上提到的微信网页助手同步,或用 notion, 语雀等云笔记同步,之后再考虑把笔记整理到 org 文件中。

至于服务器,基本没有什么配置,因为大部分时候都是通过远程 ssh 工具(包括 vscode 远程访问)来操作,但本文会记录一些场景的处理经验。

包管理原则

  1. 安装便捷方式优先级:snapd > Appimg > apt > 手动 deb 安装 > 源码编译
    • snapd 软件把所有的依赖都打包在一个独立的环境里,不依赖系统里的库。 因此好处是很独立,安装卸载都比较干净,缺点是安装包体积会大一点,并且可能无法访问一些系统资源,访问权限没有 apt 方式高,导致有些功能缺失,因此如果 snapd 的版本能满足需求就用 snapd, 不行就考虑其他方案。
    • Appimg 和 snapd 类似,也是独立的环境,但它没有 snapd 程序那样完全独立,需要依赖系统库,比如后文中提到下载 qq appimg 就需要先安装某个第三方库。它更接近 windows 里绿色免安装版,下载一个 appimg 文件,不用安装,也不用 sudo 权限。点开就能用,因此非常方便。 缺点是:
      • 不会自动更新, snapd 是有一个统一的源的(snapd store),如果 store 软件更新,系统会像 apt 一样检测出更新并提醒用户更新(也可以设置自动更新)。但 appimg 就是一个独立的包,要更新的话需要下载一个新版本的 appimg 。
    • apt 是标准的 ubuntu 的软件安装方式了,但有的时候需要手动加入软件的源,或者下载 deb 来安装。
    • 源码编译:适合最需要定制化或者追求最新版本的软件,比如 emacs 。
  2. 不要在 apt 源 安装 deepin 源的软件,source list 混在一起很容易出问题(个人遇到两次把 lsb_release 从 ubuntu 自动改成 deepin 的问题,第一次重新安装软件就解决,第二次则重装系统)
  3. 不需要修改成国内源,速度也正常(因此如果忘记修改了也不会有什么问题)。 如果发现会卡时可以改用国内源。

metaesc: 对安装和配置的一次封装

上一节原则 1 里提到了软件有许多不同的安装方式,这使得安装、更新、配置软件变得复杂。 metaesc/metaesc.sh at main · metaescape/metaesc 对各软件的不同安装和配置方式进行了统一,只留出一个简单的命令行接口来供自己使用。

当 clone 了 metaesc 仓库之后,进入到 metaesc 目录中,通过 ./metaesc.sh -i vscode 就可以安装 vscode, 类似的,通过 ./metaesc.sh -i i3 则是安装 i3 以及它所依赖的所有软件, 通过 ./metaesc.sh -s i3 则建立 i3 相关软件的配置文件在系统中的软链接。

metaesc.sh 的原理是,读取命令 ./metaesc.sh -i xxx 中 -i 或 -s 选项之后的 package 名称 xxx (如果是多个则循环读取并执行),然后以此去寻找 install_libs/ 目录里名为 pack-xxx.sh 的脚本,如果选项是 -i 那么会执行 pack-xxx.sh 脚本里的 install_xxx() 函数,如果是 -s 会执行脚本里的 setup_xxx() 函数。

如果要新增某个软件或软件集合的安装和配置方式,只需要从 metaesc/install_libs 中找到任意一个 pack-xxx.sh 脚本,复制一份后分别修改这两个函数即可。

理清 profile, bashrc 等关系

先说明个人设置 bash 的方式:

💡 删除 ~/.profile 里默认调用 bashrc 的语句,把环境变量都写在 ~/.profile 中

🖥 ~/.bashrc 只设置一些 alias 和 prompt 等纯粹用来定制 bash 交互的命令。

以下是具体细节:

首先几乎不要修改 /etc/profile 或者 /etc/bashrc 文件,这些是系统级别的设置,它对所有用户都是生效的,而即便这台机器只有一个用户,去修改他们也需要 sudo 权限,会更麻烦。

所有的个人配置的修改都应该只涉及放在 home 目录下的文件,比如 ~/.profile, ~/bashrc, ~/bashrc_profile, ~/bashrc_login , 我只使用前两个,但以下说明几个文件的关系:

我以 bash 作为默认的 shell, 因此后文都以 bash 的配置文件为例,如果是其他 shell, 比如 zsh, 把以下出现的 bash 替换成 zsh 应该就可以了。

首先名字里带有 profile 和 login 字段的文件设计上应该只执行一次,也就是在有用户登录行为后被执行。 而 ~/.bashrc 则是每次新建一个 bash 时被执行,典型场景是打开一个新的终端程序或终端工具里新建一个 shell tab, 但一些软件例如 tmux 打破了这种设计。

为了描述地更细一点,引入 login shell 和 nonlogin shell 的概念:

  • nonlogin shell: 当我们从图形界面输入用户名和密码登录之后,系统先会去执行 ~/.profile 文件,这与 shell 是否启动无关,因此该文件无论如何都会被执行。

    之后打开新的终端或者在已有终端里执行 bash 命令时,是不需要你在命令行里再次输入用户或密码进行登录的。

    因此在已经登录的环境下新建出来的 bash 进程都是 nonlogin shell, 它们启动时只会去执行 ~/.bashrc。

  • 相反,login shell 是在命令行里要输入用户名密码的情况下打开的 shell (比如远程 ssh),这种 shell 会执行 ~/bashrc_profile, ~/bashrc_login ,由于我几乎不对远程 ssh 的服务器做任何定制,所以日常打交道的文件只有 ~/.profile 以及 ~/.bashrc。

如果你不用 tmux, 那么需要理解的关系就到此为止了,你要做的就是把 PATH 或其他不单单是 bash 所需要的环境变量,比如 export GTK_IM_MODULE=ibus 写在 ~/.profile 里,然后把其他 bash 需要的 prompt 设置,alias 都写在 ~/.bashrc 里即可。为什么?以下例子说明

  • 如果 ~/.bashrc 中包括
export PATH=/usr/local/texlive/2023/bin/x86_64-linux:$PATH.
  • 此时你对 bashrc 修改了,然后再执行一遍 source ~/.bashrc, 以上 export 语句又会被执行一遍,那么 PATH 中就有两条相同的 texlive 路径

    /usr/local/texlive/2023/bin/x86_64-linux:/usr/local/texlive/2023/bin/x86_64-linux:...
    
  • 如果 ~/.bashrc 里有更多的像以上语句一样的通过拼接方式来扩充 PATH 的语句,那么最终 PATH 里会有很多重复的路径,虽然这不会影响功能,但会影响检查环境变量时的阅读体验。可以通过写条件判断来解决这种冗余问题,比如判断如果 PATH 中没有 textlive 路径时才进行拼接。

我更偏向把环境变量都写在 ~/.profile 里:

  • profile 文件只在用户登录时执行一次,因此不需要写额外的条件判断
  • profile 里设置的环境变量不单单影响 shell, 还会被影响其他系统应用。像 GTK_IM_MODULE 这样的控制输入法的变量,不单单 bash 需要,其他 GTK 应用也是需要的,因此在 profile 中设置更好。这也是为什么 ubuntu 默认在 ~/.profile 里加入了以下语句:

    if [ -n "$BASH_VERSION" ]; then
        # include .bashrc if it exists
        if [ -f "$HOME/.bashrc" ]; then
      	. "$HOME/.bashrc"
        fi
    fi
    

    也就是说,执行 ~/.profile 时也会执行 ~/.bashrc 的。这种出厂设置实际更倾向于让用户把所有配置都写在 ~/.basrc, 并且用条件判断来放置重复添加 PATH 。因为 bashrc 是各教程文章里谈论最多的设置。

  • 不过我在 202401 发现,如果在 ~/.profile 里 source ~/.bashrc, 会导致 conda init 的设置出 bug. 具体体现在,即便用 conda activate 切换到了另外一个环境,比如 python3.11, 执行 python 后实际启动的还是 base 环境的 python 。因此我还是把以上 ~/.profile 里默认调用 bashrc 的语句注释掉,并且把环境变量设置都放在 ~/.profile 中, bashrc 只设置一些 alias 和 prompt 等纯粹服务于 bash 的命令。

个人 ~/.profile 文件如下:

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

# for rust
. "$HOME/.cargo/env"

# for cuda
if [ -e /usr/local/cuda-10.1 ];then
	export PATH=/usr/local/cuda-10.1/bin:${PATH}
	export LD_LIBRARY_PATH=/usr/local/cuda-10.1/lib64:${LD_LIBRARY_PATH}
fi

# for texlive
if [[ -e /usr/local/texlive/2023 ]]; then
	export MANPATH=/usr/local/texlive/2023/texmf-dist/doc/man:$MANPATH
	export INFOPATH=/usr/local/texlive/2023/texmf-dist/doc/info:$INFOPATH.
	export PATH=/usr/local/texlive/2023/bin/x86_64-linux:$PATH.
fi

# for ibus-rime
export GTK_IM_MODULE=ibus
export QT_IM_MODULE=ibus
export XMODIFIERS=@im=ibus

# for nodejs
VERSION=v18.14.2
DISTRO=linux-x64
export PATH=/usr/local/lib/nodejs/node-$VERSION-$DISTRO/bin:$PATH

# for personal scripts
export PATH="$HOME/metaesc/bin:$PATH"

export PATH="$HOME/.deno/bin:$PATH"

tmux 的额外考虑

  • 如果你使用 tmux, 那么每创建一个新的 tmux pane 默认启动的是 login shell (tmux 做了类似在后台输入一遍用户名和密码的操作),对于 login shell (也就是 tmux pane), 会按以下顺序执行配置文件:
    • 如果找到 ~/.bash_profile, 则执行该文件,然后停止,
    • 如果没有找到 ~/.bash_profile, 则读取 ~/.bash_login
    • 如果没有找到 ~/.bash_login, 则读取 ~/.profile
  • 但是 ubuntu 默认情况下是没有 ~/.bash_profile 和 ~/.bash_login 文件的,所以每次创建一个新的 tmux pane, 其实它只会去执行 ~/.profile, 并不会执行 ~/.bashrc 。
  • 那么现在情况就是,从图形界面登录到 ubuntu 桌面(或者其他如 i3wm 桌面)后, ~/.profile 会被执行一遍,接着启动一个终端,终端默认启动一个不需要再次登录的 bash, 因此这个 bash 执行了 ~/.bashrc,接着从终端中启动 tmux, tmux 会自动创建一个 pane, 由于 tmux 是一个服务,它实际不会继承到终端里已经读取的 ~/.bashrc 中的配置,而是只继承 ~/.profile 里的配置,然后启动 login shell 去再次读取 ~/.profile.
  • 这意味着,如果不在 ~/.profile 里去执行 ~/.bashrc, tmux pane 根本不会去执行 ~/.bashrc. 但前文说到,如果在 ~/.profile 里去执行 ~/.bashrc 又会导致 conda 的问题,因此解决的方式就是,新建一个 ~/.bash_profile, 内容只有:

    if [ -n "$BASH_VERSION" ]; then
        # include .bashrc if it exists
        if [ -f "$HOME/.bashrc" ]; then
      	. "$HOME/.bashrc"
        fi
    fi
    

    这实际是把 ~/.profile 里默认的执行 ~/.bashrc 的配置移动到 ~/.bash_profile 中了

参考: bash - New tmux sessions do not source bashrc file - Unix & Linux Stack Exchange

Bashrc

基础设置以及 fzf

#+name: basic
#!/usr/bin/env bash
myconf_dir=$HOME/metaesc/
export CONF_DIR=$myconf_dir

export VISUAL=vim
#export TERM=screen-256color
export EDITOR="$VISUAL"

[ -f ~/.fzf.bash ] && source ~/.fzf.bash

if [ -t 1 ]; then # avoid bash bind warning: line editing not enabled.
bind -m emacs-standard '"\C-g\C-g": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
bind -m emacs-standard '"\C-j\C-l": "**\e\e"'

bind -m emacs-standard '"\C-gs": "git status\C-m"'

bind -m emacs-standard '"\C-gd": "git diff "'
fi

以上 fzf 的 source 语句会在 emacs shell 导致 line editing warning 提示,但没有什么好方法能处理。

bash 界面配色和 prompt

#+name: colors-prompt
function showcolors {
    for i in 00{2..8} {0{3,4,9},10}{0..7}; do
        echo -e "$i \e[0;${i}mcolor test \e[00m  \e[1;${i}mSubdermatoglyphic text\e[00m"
    done

    for i in 00{2..8} {0{3,4,9},10}{0..7}
    do for j in 0 1
        do echo -e "$j;$i \e[$j;${i}mSubdermatoglyphic text\e[00m"
        done
    done
}

#color for tmux, vim
pcolor() {
    for i in {0..255} ; do
        printf "\x1b[38;5;${i}mcolour${i} "
    done
}

if [ -e ~/.local/share/lscolrs.sh ]; then
    . "~/.local/share/lscolors.sh"
else
    export CLICOLOR=1
    #https://zhuanlan.zhihu.com/p/60880207
    #export LSCOLORS=ExGxFxdaCxDaDahbadeche
    #https://apple.stackexchange.com/questions/33677/how-can-i-configure-mac-terminal-to-have-color-ls-output
    export LSCOLORS=gxBxhxDxfxhxhxhxhxcxcx
fi

LRED='\033[01;31m'
YELLOW='\033[01;32m'
BLUE='\033[01;34m'
NC='\033[00m'

if [ -f /usr/lib/git-core/git-sh-prompt ]; then
    . /usr/lib/git-core/git-sh-prompt
fi
u_name=$(basename $HOME);
if [ "${u_name}" == "root" ]; then
    export PS1="[ \$? ${LRED} \w ${NC}] []\$(__git_ps1)\n >_ ";
else
    export PS1="[ \$? ${YELLOW}\u@\h${NC}:${BLUE}\w${NC} ] []\$(__git_ps1)\n >_ "
fi

alias 集合

#+name: alias
alias ls='ls --color=always'
alias ll='ls -l -h -a'
alias la='ls -a'

alias ..='cd ..'
alias .2='cd ../..'
alias .3='cd ../../..'

#For Dirs and files
alias fhere='find . -iname '
alias disk='df -Th --total | less'
alias dsize="du -hs * | sort -hr"
alias dus="du -hs * | sort -hr"
alias ds='df -h | head -n 1;df -h | sort | grep -v "tmpfs\|loop"'
alias fs="find -type f -exec du -Sh {} + | sort -rh | head -n 5"
alias tree='tree -A -N'

if [[ $(uname) == 'Linux' ]]; then
    alias open=xdg-open
    alias wallp='find /data/resource/pictures/wallpaper-dark | fzf --bind "ctrl-l:execute(feh --bg-fill {})"'
fi

ec () {
   nohup ~/metaesc/lib/popup-emacsclient.sh $@ > /dev/null 2>&1 &
}


tmp() {
    [[ ! -d /tmp/test	]] && mkdir /tmp/test
    push /tmp/test
}

ts() { #往右侧tmux pane发命令
    args=$@
    tmux send-keys -t right "$args" C-m
}


# quick conf #
alias vsh='vim ~/.bashrc'
alias .sh='. ~/.bashrc'
alias vvi='vim ~/.vim/vimrc'
alias vmx='vim ~/.tmux.conf'
alias conf='push ${myconf_dir}'
alias vi3='vim ~/.config/i3/config'
alias vtip='vim ~/org/historical/entry.org'
alias sshall='$CONF_DIR/utils/bin/conn_server.sh ssh'
alias sharewifi='sudo iptables -t nat -A POSTROUTING -o wlp1s0 -j MASQUERADE'

对 pushd popd 的 fzf 化

#+name: fzf-pushd
fzf-down() {
    fzf --height 50% "$@" --border
}

push() {
    if [[ $# == 0 ]]; then # selection pushd
        local targets=$(dirs -v -l | tail -n +2  |\
                fzf-down -0 --reverse --ansi -m --preview-window right:40% \
                --query "$READLINE_LINE" \
            --preview 'ls --color=always {-1}' --expect "ctrl-d" )
        READLINE_LINE=''
        [[ -z ${targets} ]] && return;
        echo "$targets" | grep 'ctrl-d' > /dev/null
        if [ $? != 0 ]; then
            while read id path; do
                [[ ${id} == 0 ]] && continue
                popd +${id} > /dev/null
                pushd ${path} > /dev/null
            done <<< $(echo "${targets}" | tail -n +2)
        else #pop
            while read id path; do
                popd +${id} > /dev/null
            done <<< $(echo "${targets}" | tail -n +2 | sort -r)
        fi
    else #manual pushd
        local origin_path=$(pwd)
        for path in $@; do
            local full_path=$(cd ${origin_path}; cd "${path}"; pwd)
            local target=$(dirs -v -l | grep "${full_path}$")
            if [[ ! -z ${target} ]]; then
                read id path <<< ${target}
                [[ ${id} == 0 ]] && continue #current dir
                popd +${id} > /dev/null
                pushd ${full_path} > /dev/null
            else
                pushd ${full_path} > /dev/null
            fi
        done
    fi
}

alias j='push'

# 本机默认编辑器为vi,需要改成vim
vi() {
	if [ -d $1 ]; then
        j $1
    else
        vim $1
    fi
}

设计思路

  • stack 中不应包括重复路径。因此 push dir 时,先检查 stack 中是否已经有 dir,如果没有,则直接调用 pushd 将 dir 压到栈顶,如果有,则先 popd 掉原先的 dir(假如编号为 n,则执行 popd +n),再执行 pushd dir 进去,这样可以保证栈顶第二个元素是执行 push dir 的目录,方便来回切换(back and forth),符合局部性原理。
  • 考虑边界条件,当 shell 初始时,stack 为空(实际包括当前目录,编号为 0),此时执行 push pwd(当前目录),那么会先将编号为 0 的目录 popd,再 pushd pwd,但在 popd 的时候会报“目录栈为空”的错误提示。因此,需要判断要 push 的目录是否为当前目录,如果是则不进行任何操作。

    local target=$(dirs -v -l | grep "${full_path}$")
    if [[ ! -z ${target} ]]; then
          read id path <<< ${target}
          [[ ${id} == 0 ]] && continue #判断是否为当前目录
    
  • 直接执行 push 时,会调用 fzf 得到选择菜单,菜单的第一项应该是编号为 1 的目录(上次执行 push 时所在目录),这样可以马上选择跳回到上次所在的目录。有两种策略来实现

    local targets=$(dirs -v -l | tail -n +2  | fzf)
    

    以上直接将 dirs -v -l 中的第 1 个 entry 删除(编号为 0),从第 2 个开始输入到 fzf 中

    targets=$(dirs -v -l| sed '1{h;d;};2{x;H;x;}' | fzf)
    

    以上将第一行与第二行互换。

  • 在 fzf 中选择到目录后,按 enter 直接进行 pushd,按 ctrl-r 则进行 popd。
  • 对于 pushd,仍然要先 popd 再 pushd,因为默认的 pushd 是 ring buffer 的形式,不能保证局部性

conda 初始以及 fzf 化

#+name: conda
_conda_activate() {
    local target=$(conda env list | grep -v "^#" \
            | fzf-down -0 --reverse --ansi \
        | cut -d " " -f1)
    [[ -n ${target} ]] && conda activate ${target}
}
alias cona='_conda_activate'
alias conls='conda list | fzf --reverse -0 --ansi'

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/home/pipz/miniconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/home/pipz/miniconda3/etc/profile.d/conda.sh" ]; then
        . "/home/pipz/miniconda3/etc/profile.d/conda.sh"
    else
        export PATH="/home/pipz/miniconda3/bin:$PATH"
    fi
fi
conda activate usr
unset __conda_setup
# <<< conda initialize <<<

最后一般是 conda 自动加入的,但可以自己维持,默认自动激活 usr

基础软件

以下是必要的基础软件列表,可以放在安装脚本

pkgs="build-essential net-tools git curl ssh-tools ripgrep"
echo $pkgs | xargs sudo apt-get install -y

ifconfig 命令需要 net-tools; ssh-ping 命令需要 ssh-tools

i3wm

gnome 桌面会对很多功能覆盖,包括对 xcape, xmodmap 的改键等,因此还是使用 i3, 这样重装系统后可以更精准地复现出自己定制的系统,保持个人使用的稳定性

安装和配置脚本如下:

set -ue

source $(dirname "${BASH_SOURCE[0]:-$0}")/utilfuncs.sh

function install_i3() {
	local distro
	distro=$(whichdistro)

	if [[ $distro == "debian" ]]; then
        print_info "i3 引入了大量的生态软件,并且与个性化密切相关 "
        print_info "例如 feh 设置壁纸,dunst 通知系统,wmctrl,xdotool,xcape 与改建相关"
        print_info "arandr compton 设置透明度"
        print_info "scrot 是截取动态图需要, emacs 中也会用到"
	sudo apt update
        pkgs="i3 feh dunst zenity wmctrl arandr compton xcape xdotool scrot gnome-screenshot"
        echo $pkgs | xargs sudo apt-get install -y
        print_success "i3 生态安装完毕"
	elif [[ $distro == "redhat" ]]; then
        :
	elif [[ $distro == "arch" ]]; then
        :
	fi
}

function setup_i3() {
	local distro
	distro=$(whichdistro)
        _cur_dir=$(dirname "${BASH_SOURCE[0]:-$0}")
        conf_dir="$(builtin cd "$_cur_dir" && git rev-parse --show-toplevel)"

	if [[ $distro == "debian" ]]; then

        print_info "i3 相关软件的配置大部分都在 ~/.config 中"

        set -uex
        [ ! -d $HOME/.config ] && mkdir $HOME/.config
        ln -sf $conf_dir/.config/i3/config $HOME/.config/i3/config
        ln -sf $conf_dir/.config/i3status/config $HOME/.config/i3status/config
        ln -sf $conf_dir/.config/compton.conf $HOME/.config/compton.conf
        [ -d $HOME/.config/dunst ] && rm -rf $HOME/.config/dunst
        ln -sfn $conf_dir/.config/dunst $HOME/.config/dunst

        set -ue
        print_success "i3 相关配置软链接已建立"

	elif [[ $distro == "redhat" ]]; then
        :
	elif [[ $distro == "arch" ]]; then
        :
	fi
}

ubuntu 22.04 取消了 gnome-screenshot, 因此在以上脚本里手动安装。具体的配置和说明参考 i3wm: 无关生产力,且远不止窗口管理

rime 输入法

输入法使用的是 ibus-rime,如果基于 fcitx5 可以参考 Emacs 里用雾凇拼音实现流畅中文输入

安装方式见以下脚本中 install_rime 函数:

set -ue

source $(dirname "${BASH_SOURCE[0]:-$0}")/utilfuncs.sh

function install_rime() {
	local distro
	distro=$(whichdistro)
	if [[ $distro == "debian" ]]; then
		print_info "开始安装/更新 rime, 主要需要 ibus 和 ibus-rime, librime-dev"
		print_info "librime-dev 是 emacs-rime 所需"
		sudo apt update
		sudo apt install ibus ibus-rime librime-dev -y
		print_success "ibus, ibus-rime, librime-dev 安装完毕"
		print_info "使用 ibus-daemon --xim -d -r 启动 daemon, 之后可以使用 ibus restart 重启"
		print_info "安装后默认可以使用 luna 拼音,使用 setup rime 添加配置 \n"
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

function setup_rime() {
	local distro
	distro=$(whichdistro)
	_cur_dir=$(dirname "${BASH_SOURCE[0]:-$0}")
	conf_dir="$(builtin cd "$current_dir" && git rev-parse --show-toplevel)"
	if [[ $distro == "debian" ]]; then
		print_info "开始配置 rime, 首先添加环境变量,考虑到输入法是主要是在图形界面用的"
		print_info "因此环境变量加入到 ~/.xprofile 中,但放 ~/.profile 也无妨,这里主要建立软链接 "
		set -uex
		ln -sf $conf_dir/.profile $HOME/.profile

		print_info "接着考虑词库 emacs rime 和 ibus rime 的词库和输入法配置文件的链接"

		print_info "从 https://github.com/iDvel/rime-ice 下载 rim-ice"
		pushd ~/codes/
		
		if [ -d "rime-ice" ]; then
			echo "rime-ice 目录已存在,正在更新仓库..."
			cd rime-ice
			git pull
		else
			echo "正在克隆 rime-ice 仓库..."
		    git clone https://github.com/iDvel/rime-ice.git --depth=1
		fi
		cp -r ./rime-ice/* ~/.config/ibus/rime/
		cp -r ./rime-ice/* ~/.config/emacs_rime/

		default_custom_yaml=$conf_dir/.config/ibus/rime/default.custom.yaml
		installation_ibus=$conf_dir/.config/ibus/rime/installation.yaml
		installation_emacs=$conf_dir/.config/emacs_rime/installation.yaml
		ln -sf $default_custom_yaml $HOME/.config/ibus/rime/default.custom.yaml
		ln -sf $default_custom_yaml $HOME/.config/emacs_rime/default.custom.yaml
		ln -sf $installation_ibus $HOME/.config/ibus/rime/installation.yaml
		ln -sf $installation_emacs $HOME/.config/emacs_rime/installation.yaml 
		popd
		print_success "ibus-rime 配置完成,重新 deploy 后可以生效"
		print_success "emacs-rime 词库配置完成,可以在 emacs 中执行 rime-deploy 生效"
		print_notice "ibus-rime 和 emacs-rime 是共享静态词库的,但他们的静态词库运行时不共享, sue sync to  share"
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

安装完后需要在 profile 中添加:

export GTK_IM_MODULE=ibus
export QT_IM_MODULE=ibus
export XMODIFIERS=@im=ibus

ibus-rime 配置位于 ~/.config/ibus/rime ,用 ibus-daemon –xim -d -r 初次启动 ibus 服务后,该目录下会出现以下四个文件或目录

build/  installation.yaml  luna_pinyin.userdb/  user.yaml

这是因为默认使用了 luna_pinyin 。默认的词库是繁体而且很小,而 rime 的文档比较难读,配置起来也复杂,因此最好的方法是找一个其他人维护的配置以及词库(能经常更新),基于此做一点自己的修改。这里使用 iDvel/rime-ice: Rime 配置:雾凇拼音 | 长期维护的简体词库, 先通过 github 下载 rime-ice 目录到本地。

如果 ~/.config/ibus/rime 已经有了以上四个文件,先清空,然后把 rime-ice 目录里所有文件拷贝到该目录下,再将 default.custom.yaml 和 installaton.yaml 文件软链接到对应目录中,这是仅有的两个需要自己维护的文件,放在 ~/metaesc/.config/ibus/rime 下。

以上的 setup_rime() 函数脚本化了这个过程,其中还额外考虑了对 emacs-rime 的设置,其配置目录为 ~/.config/emacs_rime 。

以下是 default.custom.yaml 文件,其中用 patch 的方式去覆盖 default.yaml 里的一些配置,这里主要就是只采用 rime_ice 全拼输入方式(default.yaml 中还有双拼等方案),并且用逗号 , 和句号 . 进行上下翻页(因此要覆盖 recognizer/patterns/url: null, 否则 default.yaml 里的配置会把句号 . 识别成网址的一部分)

patch:
  schema_list:
    - schema: rime_ice
  switcher/caption: "[方案菜单]"
  switcher/hotkeys:
    - F2
  switcher/save_options:
    - full_shape
    - ascii_punct
  menu/page_size: 9 # 候选项数
  ascii_composer/good_old_caps_lock: false
  ascii_composer/switch_key:
    Shift_L: noop
    Shift_R: commit_code 
    Control_L: noop
    Control_R: noop
    Caps_Lock: commit_code
    Eisu_toggle: clear
  # 取消 url 识别,以使用 , 和 . 翻页
  recognizer/patterns/url: null
  key_binder/bindings: # 设置哪些键可以翻页,需要哪个取消注释即可
    - { accept: comma, send: Page_Up, when: composing} # 逗号向上翻页
    - { accept: period, send: Page_Down, when: composing}

与 xcape 和 xmodmap 改建的冲突的解决

由于我将原键盘的空格建改成了 shift_L, 只有完整按键之后才发送 space, 导致按空格选词的时候 rime 能够识别出这是 Shift_L, 而 Shift_L 在 rime 下默认是 inline-ascii,显示出来的效果是把后选词隐藏掉,只留下拼音,只有再按一次空格才将中文发出。因此这里需要修改 Shift_L 的行为,这里在 ascii_composer/switch_key 中将 Shift_L 从 inline-ascii 改为 noop 。

rime 的 Shift_R 默认则会将 rime 转入到 inline 模式,这里改成直接 commit_code, 等价于 enter

ibus/rime/installation.yaml 中设置 ibus-rime 的词库同步目录(如果只在一个机器上使用,那么不需要考虑同步,即使在多个机器上或者使用了 emacs-rime, 同步也不是很必要的,但这里还是配置了):

distribution_code_name: "ibus-rime"
distribution_name: Rime
distribution_version: 1.5.0
install_time: "Tue Apr  4 16:30:03 2023"
installation_id: ibus
rime_version: 1.7.3
sync_dir: "/data/resource/rime"
update_time: "Sat Jan 20 09:22:16 2024"

installation.yaml 中 update_time 信息会在每次同步后更新。

其他命令:

# 启动
ibus-daemon --xim -r -d
# 启动时打印额外信息,有时候可以用来检查问题
ibus-daemon --xim -r --verbose
# 进入设置(相当于点击 preference 图标)
ibus-setup
# 重启,如果显示 cannot connect to ibus 则使用第一条命令重启整个服务
ibus restart

emacs_rime 的额外设置

~/.config/emacs_rime/installation.yaml 中设置 emacs-rime 的同步信息:

distribution_code_name: "emacs-rime"
distribution_name: Rime
distribution_version: 1.0.1
install_time: "Tue Apr  4 16:30:03 2023"
installation_id: emacs
rime_version: 1.7.3
sync_dir: "/data/resource/rime"
update_time: "Sat Jan 20 00:10:00 2024"

在 emacs 中额外定义以下函数,用于同步 ibus 和 emacs 的词库(注意 ibus rime 要先退出 ibus, 然后进入到 rime 目录执行同步命令后再重启)

(defun sync-ibus-and-emacs-rime-userdb ()
  (interactive)
  (rime-sync)
  (start-process-shell-command
   ""
   nil
   "ibus exit;cd ~/.config/ibus/rime; rime_dict_manager -s;ibus-daemon --xim -d -r")
  (message "ibus-rime and emacs rime sync done"))

emacs 和 ibus 对应的两个 installation.yaml 中 sync_dir 字段应该保持一致,但 installation_id 要有所区分,根据 关于词库合并的问题 · Issue #186 · rime/squirrel 描述,词库同步的顺序如下:

  • 找到 installation 中 sync_dir 将其所有子目录下的后缀为 userdb.txt 文件读取到当前的 xxx.userdb/ 目录中,比如同步 ibus-rime,那么 sync_dir 里的 xxx.userdb.txt 会被全部加载到 ~/.config/ibus/rime/xxx.userdb/ 中与该目录里目前缓存的用户最近输入的词库合并。
  • 然后把 ~/.config/ibus/rime/xxx.userdb/ 里词库导出为 sync/<installation_id>/xxx.userdb.txt.
  • 以上提到的 xxx 取决于配置里对词库的命名,比如默认是 luna_pinyin.userdb/, 但 rime-ice 是 extended.userdb 。

google chrome

安装脚本 metaesc/install_libs/pack-chrome.sh at main · metaescape/metaesc · GitHub 内容如下

set -ue

source $(dirname "${BASH_SOURCE[0]:-$0}")/utilfuncs.sh

function install_chrome() {
	local distro
	distro=$(whichdistro)
	if [[ $distro == "debian" ]]; then

        if ! command -v google-chrome-stable &> /dev/null
        then
			print_info "安装 google-chrome"
            pushd /data/installer/
			chrome=$(ls google-chrome-stable_current_amd64.deb 2>/dev/null)
			if  [ ! ${chrome[@]} -gt 0 ]; then
		        print_info "请手动从 https://www.google.com/intl/zh-CN/chrome/ 中下载 deb 包"
                exit
            fi
			# 安装.deb文件
			for file in "${chrome[@]}"; do
				sudo apt install "./$file"
			done
			print_success "google chrome 安装完毕"
            popd
		else 
			print_success "google chrome 已安装,尝试更新"
            sudo apt update
            sudo apt install google-chrome-stable
        fi
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

function setup_chrome() {
	local distro
	distro=$(whichdistro)
    _cur_dir=$(dirname "${BASH_SOURCE[0]:-$0}")
    conf_dir="$(builtin cd "$_cur_dir" && git rev-parse --show-toplevel)"
	if [[ $distro == "debian" ]]; then
		print_info "都是安装插件的工作"

	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

这里要先手动下载一次 deb 包,下载页 有如下提示:

注意:安装 Google Chrome 会添加 Google 存储区,以便您的系统自动更新 Google Chrome。
如果您不需要 Google 存储区,请先执行“sudo touch /etc/default/google-chrome”,然后再安装程序包。

也就是第一次执行以下命令(或者执行 ./metaesc.sh -i chrome)后,以后升级只需要用 apt update 然后 sudo apt upgrade 或者 sudo install google-chrome-stable 即可。

sudo apt install ./xxx.deb #注意需要加./来区别apt源,chrome不在源中

原因是用以上方式安装时会将 chrome 的源自动加入 sources list:

 >_ cat /etc/apt/sources.list.d/google-chrome.list
### THIS FILE IS AUTOMATICALLY CONFIGURED ###
# You may comment out this entry, but any other modifications may be lost.
deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main

查看版本:

apt list --installed | grep chrome

安装浏览器 flash 插件驱动(也许不用)

sudo apt install adobe-flashplugin
which ffmpeg || sudo apt install ffmpeg

另外,chrome 的插件的安装缓存是在 home 目录下,因此重装系统只要不破坏 home 的信息,插件是自动恢复的

字体管理

和其他 linxu 上的软件类似,保存字体的目录一般分为系统级别和用户级别两种:

系统目录在:

ls /usr/share/fonts/
| cmap       |
| cMap       |
| opentype   |
| truetype   |
| type1      |
| wps-office |
| X11        |

默认的用户专用目录在:

ls ~/.fonts
| cn       |
| Consolas |
| Courier  |
| noto     |

通过 fc-list 可以显示系统中所有字体的列表,它们就是从自于这两个地方,以下是列出的某个字体条目的格式:

/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc: Noto Serif CJK SC:style=Regular
  • 这些字体是 ttc 或者 ttf 后缀的(也有其他格式)
  • 其中第一个冒号后到第二个冒号之间的内容是字体名称,latex 、emacs 或者其他软件设置字体时一般需要用这个名字。

安装字体的方式

有些字体在 apt 源里就有,比如 fonts-noto 字体可以通过 sudo apt-get install fonts-noto 安装。

这一般是安装到了系统目录,因为 apt install 是系统级别命令(往往需要 sudo)

但很多开源的字体没有加入到 apt 仓库,这时候可以手动下载字体文件(比如 .ttf 格式)或文件夹(包含多个 ttf 的目录),放在 ~/.fonts/ 下, 然后执行

fc-cache -f -v

将新下载的字体更新到系统字体列表里去, fc-list 显示的列表中 就会包含新的字体了,其他软件的字体选项里也能找到新添加的字体。

比如以 Releases · laishulu/Sarasa-Term-SC-Nerd 字体为例,根据根据说明下载压缩包并解压获得 sarasa-term-sc-nerd.ttf, 注意这是个目录,其中有多个具体的 ttf 文件。

将整个目录移动到 =~/fonts/=, 然后更新 fc chache

mv ~/Downloads/sarasa-term-sc-nerd.ttf ~/.fonts/
fc-cache -f -v

腾讯软件

  • wemeet 和 qq 都有官方版,qq 可以下载 AppImg 版本,需要依赖以下 ibfuse2 库
sudo apt install libfuse2
  • wechat 还没有官方 linux 版本,不建议装 deepin 版或其他 docker 版本等,见包管理原则 2 可以用 在线文件传输助手 在电脑和手机之间传文件或者信息。

远程访问

远程访问 windows 桌面

sudo apt-get install rdesktop

sudo apt-get install freerdp2-x11 freerdp2-shadow-x11

传文件到 windows, 以下命令是把 ubuntu 中的目录挂载到 windows 中

rdesktop -u username ip.b.c.d -p my-password -r disk:LinuxPictures=/home/username/Pictures

FreeRDP和rdesktop都是用于连接和远程控制Windows桌面的开源软件。

主要的区别在于它们的开发和设计目标、支持的协议版本和功能特性。

FreeRDP是从RDPv4协议开始设计的,因此它支持更多的RDP协议版本,包括RDPv5、RDPv6和RDPv7等。FreeRDP的设计目标是提供一个高性能、跨平台的远程桌面客户端,它支持多种操作系统,包括Windows、Linux、Mac OS X和Android等。FreeRDP的功能特性包括:音频重定向、USB重定向、剪贴板重定向、打印机重定向、智能卡重定向等。

rdesktop是一个较老的远程桌面客户端,它最初是为连接到Windows 2000服务器而设计的。rdesktop的主要目标是提供一个轻量级的远程桌面客户端,它的功能相对较少,但对于一些特殊需求和低配置的系统,它可能是一个更好的选择。rdesktop的特性包括:图形界面(GUI)、音频支持、剪贴板支持、与Windows Server 2008兼容、IPv6支持等

– chatgpt

总的来说,如果 rdesktop 登录不上,用 freerdp2. rdesktp 一般连比较旧的 windows 服务器。

校园网访问 EasyConnect

用于连校园网,通过火狐访问 https://sslvpn3.scut.edu.cn/portal/#!/login 或者 https://sslvpn.xmu.edu.cn/portal/#!/down_client (google chrome 里找不到下载链接)

下载 EasyConnect_x64_7_6_7_3.deb (或其他新版)并安装, sudo dpkg -i EasyConnect_x64_7_6_7_3.deb 默认安装在 /usr/share/sangfor/EasyConnect

注意,该 deb 文件有的时候名字里的版本号是滞后于真实版本号的,比如以上例子中是 7_6_7_3, 但点开后实际可能是 7.6.7.6 版本,通过以下命令查看版本,并且可以手动修改名称

dpkg -I EasyConnect_x64_7_6_7_3.deb
  • 动态链接库的问题:
https://packages.ubuntu.com/bionic/libs/libpango-1.0-0
https://packages.ubuntu.com/bionic/libpangocairo-1.0-0
https://packages.ubuntu.com/bionic/libpangoft2-1.0-0

从以上链接分别下载三个 pango 1.0.0.40 的库文件,目的是覆盖系统的库,使其降级,为了不删除掉系统的同名 pango 库, 可以将下载的 so 文件放到 EasyConnect 项目主目录下,这样动态链接库的解析优先是找当前目录,因此自己下载的库会优先被选择。

步骤如下:

首先将三个库安装到所在目录下,它会生成 usr 目录,将其中 usr/lib/x86_64-linux-gnu 目录拷贝到 EasyConnect 主目录下

mkdir /data/installer/pango1.0
cp ~/Downloads/libpango* /data/installer/pango1.0
cd /data/installer/pango1.0
dpkg -X libpango-1.0-0_1.40.14-1ubuntu0.1_amd64.deb ./
dpkg -X libpangocairo-1.0-0_1.40.14-1ubuntu0.1_amd64.deb ./
dpkg -X libpangoft2-1.0-0_1.40.14-1ubuntu0.1_amd64.deb ./
sudo cp /data/installer/pango1.0/usr/lib/x86_64-linux-gnu/* /usr/share/sangfor/EasyConnect

然后通过以下命令启动

/usr/share/sangfor/EasyConnect/EasyConnect

加密

对本地目录加密

sudo apt install ecryptfs-utils

这是一个加密文件系统,因此用 mount 的方式来加密,用 umount 来解密。

mount 时候要 sudo 权限。

标准挂载的例子是 sudo mount /dev/sdb1 /mnt, 意思是把设备 sdb1 挂到 /mnt 路径下,但用 ecryptfs 对路径加密则是把目录挂在到自身路径上。

比如假设要加密的文件目录为 ~/org/self/.deep 以下是交互式加密命令:

sudo mount -t ecryptfs ~/org/self/.deep ~/org/self/.deep

该命令会弹出许多选项:

  • 设置加密密码:手动输入密码
  • 设置加密方式,用默认的 aes, 并且 32 位
  • Plaintext passthrough: n (default), 不手动输入
  • Filename encryption: n (default), 可以选择 y 对文件名也加密
  • 输入两个 yes 使得签名加入到/root/.ecryptfs/sig-cache.txt 中

    Would you like to append sig [e07728d0a571f95b] to [/root/.ecryptfs/sig-cache.txt]
    

以下进行解密命令

sudo umount ~/org/self/.deep

每次加密要输入这么多信息还是比较繁琐,这对于那种长期都不会打开的目录进行加密是适合的,如果想跳过交互式,可以把选项都写在命令参数里(包括密码)

sudo mount -t ecryptfs -o key=passphrase:passphrase_passwd=设置的密码,ecryptfs_cipher=aes,ecryptfs_key_bytes=32,ecryptfs_passthrough=no,ecryptfs_enable_filename_crypto=yes ~/org/self/.deep ~/org/self/.deep

这个命令很长,可以 bashrc 中。

确认是否加密

mount | grep self

参考 20.04 - I want to encrypt folder - Ask Ubuntu

对 git 远程仓库加密

参考: git-crypt guide

sudo apt install git-crypt gnupg

初始化仓库:

git-crypt init

然后添加 .gitattributes 文件,内容大致:

.gitattributes !filter !diff
self/** filter=git-crypt diff=git-crypt

其原理和 .gitignore 类似,这是这里的意思是对与当前 repo 下的 self/ 目录下所有文件,在 push 前都进行加密。

以下命令查看加密状态:

git-crypt status -e

如果要在另外一台电脑上解密,则要提供一个密钥,用以下方式生成

git-crypt export-key keyfile

把 keyfile 拷贝到另外一台电脑,然后执行以下命令解密:

git-crypt unlock path/to/key

开发环境

vscode

vscode 的优点在于,开箱即用并且用户多,编程并不是一个多么个性化的活动,这方面应该尊重集体的实践结果,对于插件,尽可能保持默认功能或者官方推荐的配置,因为这一般是最多人使用的配置,也大概率是最佳实践。

此外 vscode 还集成 jupyter notebook, 终端自动跟随 conda 环境等。通过命令行启动某个项目比如 code ~/proj, vscode 在启动时会将自身进程与终端会话自动分离,因此之后关闭终端也不会导致 vscode 退出(但大部分其他 gui 界面都没有自动分离,一旦关闭启动该程序的终端,程序也会结束,比如 emacs, typora 等)

vscode 官网 Running Visual Studio Code on Linux 提供了几种安装方式,其中包括 snap 版本,根据包管理原则,应该优先尝试 snap, 但该版本(2023.2)无法支持中文,因此转而用 deb 安装方式。 这种方式的原理和 chrome 是类似的,先手动下下载一次 deb 包,然后执行 apt install ./xxx.deb, 安装完后会把 vscode 源的信息加入到本机 source list 中,之后用 apt update 就可以更新了,但官网同时给出了用脚本添加源的方式,也就不需要手动去下载 deb 文件,这些命令包装在 metaesc · pack-vscode.sh 中:

set -ue

source $(dirname "${BASH_SOURCE[0]:-$0}")/utilfuncs.sh

function install_vscode() {
	local distro
	distro=$(whichdistro)
	if [[ $distro == "debian" ]]; then
		print_info "开始安装/更新 vscode, 如有问题参考 vscode 官网 https://code.visualstudio.com/docs/setup/linux"

		print_info "添加 vscode 官方源到 sources.list.d 目录下"
		sudo apt-get install wget gpg
		wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
		sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg
		sudo sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
		rm -f packages.microsoft.gpg
		print_success "vscode 源添加完毕"

		print_info "安装依赖,获取最新的软件列表并安装最新的 vscode"
		sudo apt install apt-transport-https
		sudo apt update
		sudo apt install code
		print_success "vscode 安装/更新完毕"
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}


function setup_vscode() {
    print_notice "vscode 没什么需要本地配置的,使用 vscode 账户同步即可"
}

emacs

snap 和 apt 源都提供 emacs, snap 一般会比较新,apt 则比较旧,而 snap 版本有时候会缺少一些功能,比如以前安装过一个版本缺少支持动态链接模块,用不了 emacs-rime (也有可能是 snap 的封闭性本身导致其无法调用系统的动态链接文件)

因此最好还是自己编译,比如安装和配置 emacs28.2 的脚本如下

set -ue

source $(dirname "${BASH_SOURCE[0]:-$0}")/utilfuncs.sh

function install_emacs28.2() {
	local distro
	distro=$(whichdistro)
	if [[ $distro == "debian" ]]; then
		print_info "采用编译的方式来安装 emacs, 首先要准备安装条件,包括 build 需要的库,包括 build-essential, 剩下的可以用 `sudo apt build-dep --no-install-recommends emacs` 来查看"
        sudo apt install build-essential -y
        sudo apt build-dep emacs -y

		print_info "sqlite3 用于 org-roam"
        sudo apt install sqlite3

        if [ ! -e $CODES/emacs-28.2 ]; then
            pushd $CODES
            wget http://mirrors.aliyun.com/gnu/emacs/emacs-28.2.tar.gz
            print_notice "下载 emacs-28.2.tar.gz"
            tar -xf emacs-28.2.tar.gz
            pushd $CODES/emacs-28.2
            ./autogen.sh
            ./configure 
            make -j16
            popd 
            rm -rf emacs-28.2.tar.gz
            print_notice "删除 emacs-28.2.tar.gz"
            popd
		    print_success "emacs28.2 编译完毕"
        fi
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

function setup_emacs28.2() {
	local distro
	distro=$(whichdistro)
    _cur_dir=$(dirname "${BASH_SOURCE[0]:-$0}")
    conf_dir="$(builtin cd "$_cur_dir" && git rev-parse --show-toplevel)"
	if [[ $distro == "debian" ]]; then
        script_path=$(realpath "${BASH_SOURCE[0]}")
        countdown_warning 7 "Emacs" "$script_path"
        set -uex
        # Emacs/chemacs2
		print_info "开始配置 emacs, 链接主目录文件"
        CONFIG_HOME=$HOME/.wemacs/
        
        # Wemacs
        [ ! -d $CONFIG_HOME ] && mkdir $CONFIG_HOME
        ln -sf $conf_dir/.wemacs/early-init.el $CONFIG_HOME/early-init.el
        ln -sf $conf_dir/.wemacs/init.el $CONFIG_HOME/init.el
        ln -sfT $conf_dir/.wemacs/snippets $CONFIG_HOME/snippets

		print_info "把 emacsclient 和 emacs 以软链接方式添加到 ~/.local/bin/, 以供全局使用"
        ln -sf $CODES/emacs-28.2/src/emacs $HOME/.local/bin/emacs
        ln -sf $CODES/emacs-28.2/lib-src/emacsclient $HOME/.local/bin/emacsclient

		print_info "把 $HOME/.emacs.d 设置为 $HOME/.wemacs 目录的软链接"
        if [ -d "$HOME/.emacs.d" ] && [ ! -L "$HOME/.emacs.d" ]; then
            # 如果 $HOME/.emacs.d 是一个目录且不是软链接
            mv "$HOME/.emacs.d" "$HOME/.emacs.d.bak"
            print_info "$HOME/.emacs.d 备份到了 $HOME/.emacs.d.bak"
        fi
        if [ -L "$HOME/.emacs.d" ]; then
            print_info "$HOME/.emacs.d 链接情况如下"
            ls -l "$HOME/.emacs.d"
        elif [ ! -e "$HOME/.emacs.d" ]; then
            # 如果 $HOME/.emacs.d 不存在
            ln -s "$HOME/.wemacs" "$HOME/.emacs.d"
        fi
        set -ue

		print_success "emacs 配置完成"
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

这个过程一旦脚本化,使用起来也是方便的,而且如果要尝试更新版本,比如 emacs29.2, 那么可以新建一个 pack-emacs29.2.sh, 尽量保持每个版本用一个脚本。

另外以上脚本 setup 函数里还有对 emacs 和 emacsclient 两个二进制建立软连接的设置,这样可以全局使用这两个命令,当要切换到其他版本,执行 ./metaesc -s emacs29.2 即可。

安装脚本: metaesc/install_libs/pack-emacs28.2.sh at main · metaescape/metaesc · GitHub

draw.io

个人使用 draw io 来画示意图,可以方便导出成 svg,png,pdf 等格式。

官方 github 链接为: jgraph/drawio-desktop: Official electron build of diagrams.net

安装方式是从 Releases · jgraph/drawio-desktop 下载最新 release 的 deb 。

pack-drawoi.sh 脚本

set -ue

source $(dirname "${BASH_SOURCE[0]:-$0}")/utilfuncs.sh

function install_drawio() {
	local distro
	distro=$(whichdistro)
	if [[ $distro == "debian" ]]; then
		print_info "使用 snap 安装有许多 bug(2023.5)"
		print_info "到 https://github.com/jgraph/drawio-desktop/releases 手动下载 deb: "
		print_notice "将 deb 放置在 /data/installer 下"
		print_notice "目前还不清楚如何卸载这个 deb 包, 因此之考虑覆盖安装"
		sudo dpkg -i /data/installer/drawio-amd64-21.2.8.deb
		# sudo snap install drawio
		print_success "drawio 安装完毕, 无需额外配置,直接系统启动 drawio"
	elif [[ $distro == "redhat" ]]; then
		:
	elif [[ $distro == "arch" ]]; then
		:
	fi
}

执行 ./metaesc.sh -i drawio 进行安装

用以下命令测试:

drawio -x ~/org/lib/mE.drawio -o /tmp/drawio.png -p 2 --width 500

不用 snap 的原因

sudo snap install drawio 安装后用命令行导出失败, Linux-App doesnt start · Issue #1018 · jgraph/drawio-desktop 中讨论了相关问题,作者提到 v19.0.0 should work in snap, 但 [2023-05-25 四 23:05] 安装了 21 版本,还是有以下问题

/usr/share/libdrm/amdgpu.ids: No such file or directory
/home/pipz/org/lib/mE.drawio -> /tmp/drawio.png

作者又说在 20 版本已经 fix 了,不过自己试用后发现还是有问题。

因此稳妥做法是从手动下载 release 的 deb

gitkraken

sudo snap install gitkraken --classic

nodejs/deno

sudo apt install npm 会自动安装 nodejs 等依赖,但这个版本很老

去官网下载 tar.xz 文件

VERSION=v18.14.2
DISTRO=linux-x64
sudo mkdir -p /usr/local/lib/nodejs
cd ~/Downloads
sudo tar -xJvf node-$VERSION-$DISTRO.tar.xz -C /usr/local/lib/nodejs 

添加以下边境变量到 ~/.bashrc 或其他 shell 的配置文件中,也可以写在 ~/.profile 里,所有 shell 都会继承

# Nodejs
VERSION=v18.14.2
DISTRO=linux-x64
export PATH=/usr/local/lib/nodejs/node-$VERSION-$DISTRO/bin:$PATH

然后跟更新 . ~/.bashrc. ~/.profile

测试:

node -v
npm version
npx -v

安装 yarn (被 node 打包了)

corepack enable

deno 安装,用以下方式一般不会有网络问题

curl -fsSL https://x.deno.js.cn/install.sh | sh
  inflating: $HOME/.deno/bin/deno
Deno 已经成功安装
可执行文件位置为 $HOME/.deno/bin/deno
您需要手动将 Deno 目录添加到 $HOME/.bash_profile (或者其它类似目录)
  export DENO_INSTALL="$HOME/.deno"
  export PATH="$DENO_INSTALL/bin:$PATH"
运行 '$HOME/.deno/bin/deno --help' 查看 Deno 帮助信息

根据前文提到的原则,将以上环境变量添加到 ~/.profile 中

Rust

以下方式安装,会进入交互式 cmd 提示,基本按默认走.

export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
export RUSTUP_DIST_SERVER=https://mirrors.tuna.tsinghua.edu.cn/rustup

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

国内源: ~/.cargo/config 内容如下(参考 Rust下载包太慢怎么办?配置国内源介绍 | SherlockGy's Blog

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = "rustcc"

[source.rustcc]
registry = "https://code.aliyun.com/rustcc/crates.io-index"

或者

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'ustc'
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"

typora

使用了 snapd 版本的 typora 一段时间,发现打开的文件路径经常是在 /var/run/ 目录下的一个备份文件,查看官网后发现已经列出了该版本下 typora 的局限: Install via Snap - Typora Support, 包括访问 home 目录,

因此不建议用以下方式安装

snap install typora

Install Typora on Linux - Typora Support 有提供 apt 添加源的方式:

wget -qO - https://typoraio.cn/linux/public-key.asc | sudo tee /etc/apt/trusted.gpg.d/typora.asc

# add Typora's repository
sudo add-apt-repository 'deb https://typora.io/linux ./'
sudo apt-get update

# install typora
sudo apt-get install typora

但这个源经常连不上,因此从官网下载 deb 方式安装,参考 metaesc/install_libs/pack-typora.sh at main · metaescape/metaesc

其他问题:

  • typora 目前不支持自动读取修改后的文件并渲染,要手动执行 file->reopen with encoding, 因此如果有大量的密集的文本编辑就在 emacs 里,编辑完后在 typora 中预览格式、微调。当然如果是代码里的文档,直接 vscode 编辑。
  • 当前的 workaround 的是:
    • 通过 preferences -> general -> open advance setting 打开 typora 的设置文件,添加以下设置:

      "KeyBinding": {
       "Auto": "F5"
      }
      

      这里 Auto 实际是 Reopen with Encoding 里的子菜单选项,但 typora 的设置里只需要子菜单名称就可以,尽管有点奇怪,但这是有效的。

其他常用软件

软件安装脚本都放在 metaesc/install_libs at main · metaescape/metaesc 中,比如一些没有提及但经常使用的:

  • termial: 其中包括 rxvt-unicode, fzf, tmux,neovim,
  • wps: office 备用
  • masterpdfeditor5: 用于编辑 pdf 目录。需要注意的是,如果之前的 pdf 里用的是 outline 或者是与 masterpdf 不兼容的软件添加的 bookmark, 需要清空之后再重新添加 bookmark, 之后用其他软件如 pdf-tools, Document viewer 打开就能看到是 outline 了。
  • texlive
  • zotero: 文献管理
  • vlc: 视频播放器
  • ibus-rime
  • foliate: 一款界面很输入法 epub 阅读器,用 snap 安装会遇到和 typora 类似的问题,比如无法访问到额外挂载 的硬盘,因此这里改成 ppa 源下的 apt 安装,参见 pack-foliate.sh 脚本
  • anki: 用于制作记忆卡片的工具

命令行备忘

cd, ls 这类比较常用的就不记录了,主要是一些不太常用,但需要的时候又很有用的命令

包管理命令

  • 查找某个包是否安装

    apt list --installed | grep code
    
  • 卸载: apt purge
  • 如果是 deb 安装,则先用 dpkg -l | grep -i pkg 找到完整的包名,再 apt remove
snap list  # 查看所有已经用 snap 安装的 package
snap find emacs  # 查看 snap 源里 emacs 版本
snap info emacs  # 查看 snap 源里 emacs 详细信息

硬件信息查询

CPU 信息

lscpu

网卡信息

lshw -class network 

解压

对一些含有中文文件名的 zip 包解压:

unzip -l -O GB18030 xxx.zip

nvidia 显卡驱动相关

相关命令

查看 bios 是否开启 secureBoot, 一般禁用

mokutil --sb-state
SecureBoot disabled
Platform is in Setup Mode

显示显卡信息,包括设备 ID

lspci | grep 'VGA'

显示显卡使用情况

nivida-smi

系统安装、引导和网络设置

安装方式

Rufus官方下载的 ubuntu 镜像 iso 文件导入到 U 盘(u 盘内容会被清空),只要能从 u 盘启动后,安装过程选项 几乎默认(记得安装时不要连接网络)。

启动引导相关: GRUB

引导windows

在硬盘的一个新分区安装 windows 之后,如果 ubuntu 启动时没有显示 windows 选项可以打开 /etc/default/grub 添加以下参数,然后 update-grub,使得 ubuntu 的 grub 去检测其他盘上的引导区,再次重启应该就显示

GRUB_DISABLE_OS_PROBER=false 

启动到非图形界面

在 grub 中 linux ... ro quite 后添加 3 则系统启动到 level3, 也就是命令行界面

网络相关

网络通用设置

18.04 以后,ubuntu 使用 systemd-networkd 来管理网络,配置文件是 /etc/netplan 下的 yaml 格式文件。 但如果安装了 gnome 桌面版 ubuntu, 网络管理实际交接给了 NetworkManager, 用 systemctl status NetworkManager 查看状态发现是启动的,而 systemctl status systemd-networkd 则是不启动的。

/etc/netplan 目录中默认的 yaml 文件里有会有如下设置:

renderer: NetworkManager

这也是表明网络的管理者转交给了 NetworkManager

NetworkManager 的好处是直接提供了设置网络的 gnome 图形界面,在 setting-network 里的设置就会被 NetworkManager 读取并应用。

它也有配置文件,在 /etc/NetworkManager/system-connections/ 中,每个文件对应一个网络连接的配置,可以直接修改其中文件(这是 ini 格式的,需要 sudo),但既然安装了 gnome, 用图形界面设置也方便,并且永久生效。如果图形界面失效,可以也不建议直接修改配置文件,而是用 nmcli 来设置,比如以下是给有线网接口添加一个 dhcp 分配 ip 的配置并应用。

# 查看所有网络设备的状态
nmcli device status

# 设置设备为自动获取IP(假设设备名为eth0, 通过 ifconfig 获取)
nmcli con add con-name "wired-auto" ifname eth0 type ethernet

# 设置为自动(DHCP)
nmcli con modify "wired-auto" ipv4.method auto

# 重新启动网络连接
nmcli con up "wired-auto"

此外,网络配置还有些内核参数,由 sysctl 控制,配置文件在 /etc/sysctl.conf

局域网相关

可以用 nmap 工具扫描局域网里链接的设备,比如先用 ifconfig 查看局域网段,例如发现自己设备 ip 是 192.168.1.33, 那么可以用 nmap -sn 192.168.1.0/24 去扫描相同局域网其他设备的 ip, 知道 ip 后可以进行后续 ssh 连接等操作。

有线拨号上网

安装以下工具:

sudo apt install pppoeconf

执行以下命令,会自动扫描各个网口,如果有提供拨号服务,会弹出选项,输入用户名和密码(其他设置都用默认推荐选项)

sudo pppoeconf

以下命令显示拨号连接状态

ip addr show ppp0 
# or
plog

连接

pon dsl-provider

断开

poff

两台 ubuntu 用同一根网线直连的案例

A, B 两台机器都是 ubuntu , A 是笔记本,有自己的无线网卡,也有额外的局域网网口,B 是台式机只有网线接口。 想要达到的目的是, B 可以通过 A 的无限网口来连接网络。

操作步骤:

  • 用网线直连 A 和 B 网口,在 A, B 的网络设置(都是 gnome 图形界面)的 有线连接的详细信息页面中,选择“IPV4设置”,配置方法选择“手动”。
  • 然后分配一个静态 IP 地址。例如,A 设置 IP 地址为“192.168.168.1”,子网掩码为“255.255.255.0”。 B 地址为“192.168.168.2”,子网掩码为“255.255.255.0”, 由于 B 要通过 A 上网,因此 B 的配置中额外添加网关,设置成 A 的地址 192.168.168.1 即可。
  • 在 A B 终端都执行 ifconfig, 看是否显示类似如下信息,

    eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 192.168.168.1  netmask 255.255.255.0  broadcast 192.168.168.255
    

以上是在 A 上的部分结果,说明 eno1 网口静态地址设置成功

注意不要把静态 ip 设置成 A 电脑里 wifi 网口下相同网段的 ip, 例如如果 A 用 wifi 连接路由器,路由自动分配的局域网地址可能是 192.168.31.xx ,这个时候如果把有线网口也设置成 192.168.31.x 或者 192.168.0.x 网段的 ip, 会出现冲突,导致莫名奇妙的问题。只要区分开 mask 后的网段且避开 0 即可,例如以上用 192.168.168.x

可选说明: B 机器上设置网关也可以用以下命令行,但如果在图形界面中设置了就没有必要。

sudo ip route add default via 192.168.168.1 # 最后是 A 的有线网口地址
  • 在 A 执行 ping 192.168.168.2 来检查连通,同样在 B 执行 ping 192.168.168.1
  • 为了让 B 能够通过 A 来上网,需要在 A 上通过网络地址转换(NAT)来它自身的 Wi-Fi , 在电脑 A上 执行以下命令启动 IPv4 数据包转发功能:
sudo sysctl -w net.ipv4.ip_forward=1
这个命令将系统内核的参数 "net.ipv4.ip_forward" 设置为 1,表示启用 IP 数据包转发功能。对于作为路由器或网关的 Linux 主机非常有用,因为它允许主机在不同网络之间转发IP数据包,从而实现网络通信。

为了持久保留以上配置,应该执行 sudo vi /etc/sysctl.conf 打开配置文件,找到 net.ipv4.ip_forward=1 取消注释(没有的话手动添加改行) 然后 sudo sysctl -p 或重启生效

  • 在 B 上的有限网口的网络设置里(前几步中设置过静态地址的页面)继续添加一个 DNS 服务器(可以使用 A 电脑所连接的 DNS服务器 IP 地址,在 A 中执行以下命令可以看到)。
nmcli dev show | grep 'IP4.DNS'
IP4.DNS[1]:                             192.168.31.1
  • 创建 iptables 规则:

    先确定电脑A的 Wi-Fi 网卡名称,可以用 iwconfig, ip a 或者 ifconfig : 例如假设 A 电脑上的 wifi 网口名显示为 wlp1s0 , 在电脑A上运行以下命令,创建一个新的 iptables 规则,允许连接者通过电脑 A 访问互联网,并将 Wi-Fi 网卡名称替换为上一步查询到的名称。

    sudo iptables -t nat -A POSTROUTING -o wlp1s0 -j MASQUERADE
    
这个命令并不是持久的,重启后失效,需要用额外工具来持久化,比如用 nft 命令设置,但个人感觉比较麻烦,因此直接用 alias 形式写在 bashrc 里,需要的时候就执行。
alias sharewifi='sudo iptables -t nat -A POSTROUTING -o wlp1s0 -j MASQUERADE'
  • 此时 B 电脑应该就可以使用 A电脑的 Wi-Fi 来访问互联网了。
radioLinkPopups

如对本文有任何疑问,欢迎通过 github issue 邮件 metaescape at foxmail dot com 进行反馈