6
1
2015
6

Wireshark 抓远程主机的包

(失眠了,干脆起来写文。)

调试时经常会有抓包的需求。通常,我在本地用图形界面的 Wireshark 来抓包及解析,而对于远程服务器,因为没有图形界面,只好使用 tcpdump 抓包到文件然后复制到本地拿 Wireshark 看了,这样就不能实时查看抓到的包了。当然 tcpdump 也可以实时输出,但是信息太少、难以阅读,功能也过于简单,比如我要跟踪流啊、不同的流用不同的颜色高亮啊、添加注释啊、时序分析啊,tcpdump 完全没办法做到。实际上复杂一点的协议解析它都做不到。

一直没去研究 Wireshark 如何从标准输入读取网络包数据。大概是某天下意识地按了一下Alt-h看到了 Wireshaark 的 man 手册,才知道原来 Wireshark 支持这么多参数!图形界面的程序支持各种可选参数的可不多见。Wireshark 指定-i -就可以从标准输入读取数据,不过要同时指定-k,不然得在图形界面里点「Start」开始抓包。

那怎么把抓到的数据包发送到标准输出呢?实际的抓包操作不是 Wireshark 直接执行的。Wireshark 又不是 360,既然能以普通用户身份执行需要特权的操作,那么就会有一个无图形界面的工具来辅助。它就是 dumpcap。查阅其 man 手册可知,把抓到的数据输出到标准输出的选项是-w - -P-P 指定使用 pcap 格式,不然会使用 pcap-ng 格式,Wireshark 不认)。还可以给定其它选项,比如只抓 lo 网络设备上的包用-i lo,或者指定一个过滤器如-f 'port 1234'(具体语法见pcap-filter的 man 手册)。一定要记住不要把传输抓包数据的数据包也抓到了哦~

比如:

ssh lxc-debian sudo dumpcap -P -w - -f "'not port 22'" | \
  wireshark -i - -k

这样就可以实时看到远程主机上的网络包了~通过 ssh 执行命令时引号得用双层的。使用 sudo 是因为我那个 Debian 的 dumpcap 没有特权。

Category: Linux | Tags: linux 网络 wireshark
5
17
2015
23

Linux 下在 Minecraft 里输入中文

Linux 下各种奇怪的地方总是会遇到输入启用不了的情况,比如 Sublime Text 就需要打补丁版。Teewords 以前能好好地使用输入法的,不知从什么时候起,启用输入法输入时,直接从键盘输入的编码和输入法提交的字符串都会被 teewords 接收并显示(teewords 版本号 0.6.3)。最近换了新本子,跑起 Minecraft 终于不那么卡了,所以也研究了一下怎么在 mc 里输入中文。

喵窝 wiki 里给出了一个脚本,是通过外部程序输入中文,然后粘贴到 mc 里来达到输入中文的效果的。然而粘贴功能在我的 mc 里是无效的。不过照着这思路,改进一下发现也能用。

首先,需要 xdotools。其次,需要一个输入文本的程序。我使用的是 zentiy,当然还有 kdialog、gdialog 之类的也可以用。最后,需要设置快捷键。

我的脚本如下:

#!/bin/bash -e

chars=$(zenity --title 中文输入 --text 中文输入 --width 500 --entry 2>/dev/null)
sleep 0.1
xdotool key --delay 150 Escape t
sleep 0.2
xdotool type --delay 150 "$chars"
xdotool key Return

原理很简单,在这个脚本被调用时,弹出一个对话框让用户输入文字。对话框关闭后,焦点应该回到 mc。发送 Escape 键「回到游戏」,然后发送「t」开启聊天。然后把文字发送过去并按回车。

很神奇,原来可以直接向它发送中文字符。不过那些延迟是需要的,不然会接收不完整。所以使用效果就是,对话框关闭之后,可以看到程序在往 mc 里一个个地输入文本并发送~

至于绑定快捷键,作为 Awesome 用户,可以做到只在 mc 的窗口绑定。定制性比较差的窗口管理器/桌面环境可能只能全局绑定了,会占用掉一个全局快捷键。

相关代码如下:

    elseif c.class and c.class:match('^Minecraft ') then
        local keys = c:keys()
        local mykey = awful.key({'Control'}, 't', function(c)
            awful.util.spawn('zhinput')
        end)
        keys = awful.util.table.join(keys, mykey)
        c:keys(keys)
    elseif c.name == '中文输入' then
        awful.util.spawn_with_shell('sleep 0.05 && fcitx-remote -T', false)

顺便在弹出的对话框里把输入法切换到了中文模式~(完整的配置文件在这里


2019年01月02日更新:Minecraft 已经可以直接输入中文了(虽然有些类似于 teeworlds / DDNet 的 bug)。

Category: Linux | Tags: 中文支持 minecraft
5
11
2015
28

为什么我反对普遍地静态链接?

这是我查看知乎私信时不小心瞅到的问题所触发的。由于 Go 在国内的兴起,我对这个问题也多有思考,就放在这里记录一下好了。知乎的链接我就不贴了,带 nofollow 的都懒得贴了。

首先,我们搞清楚问题是什么。或者说,我反对的究竟是什么?

静态链接,即早期唯一的一种链接出二进制可执行文件的方式,把所有程序需要用到的库全部打包到一个文件里边。后来,由于存储空间越来越不够用,所以发展出了动态共享库,也就是把库编译成 so、dll 或者 dylib 这种由动态库装载器在程序运行前或者运行时进行链接的方案。

静态链接的优点

  1. 方便分发,不会因为库的升级而导致程序无法运行。这一点没有严格指定依赖版本的 Arch Linux 用户应该都有所体会。当你更新某个库(比如 boost 或者 icu 什么的)之后,动态链接到旧库的程序会出错。

  2. 效率稍高。这个反正人类是体会不到的。

基于第一点,用于急救的重要程序最好使用静态链接,特别是 busybox。以前我会安装 busybox 的,后来因为 Arch 改用动态链接 C 库了,对于我不再有意义,所以卸载了。以后 C 库如果出问题就直接重启进救援系统了。

另外我还有静态链接的 32 位 Vim,为的是在 Vim 依赖库更新而 Vim 没更新时依旧有个顺手的编辑器可用。我一直自行编译 Vim 因此这个曾经十分有用。不过由于现在对经常变动导致问题的解释器支持采用运行时动态链接,所以基本不受影响了。

静态链接的缺点

这个可以列出长长的一串了。

  1. 占用磁盘空间。我就不怎么喜欢 Haskell 写的程序,太占硬盘了,一个程序就几十M。当然换新电脑之后目前硬盘空间有富余。但是它们还是会渐渐被我的各种源码和虚拟机什么的填满的。

  2. 占用内存空间。可执行文件在执行时是需要映射到内存中的。如果使用动态链接,那么因为是同一文件,所以在内存时只需要映射一份就可以了。而静态链接,不仅因为来源于不同的文件而需要加载、映射多次,而且因为来自于不同的构建等原因,逻辑上相同的代码往往并不会造成映射之后的内存页相同,使得内存去重机制(如 UKSM)失效。

    别说内存是白菜价,除非你来给我手上的笔记本、VPS、服务器、路由器、单板机等都配置个几十G的内存,我付给你等质量的白菜。还记得比尔·盖茨说过的话吗——「640K足够了」。够了吗?

  3. 占用 I/O 带宽。可执行文件越大,在内存里没有缓存时需要从外存读取的数据也就越多,耗时也就越长。而因为文件体积增大,内存资源越发不够用,I/O 缓存越少,导致缓存命中更低。

  4. 占用网络带宽。你可执行文件是从网上下的吧?你在国内看个视频还挺流畅,但是到世界各地去下软件你试试看?

  5. 运行时链接。我写了一个程序,支持 MySQL、SQLite3、PostgreSQL、MongoDB、Oracle 等等等等数据库。但是你显然不会用到所有的数据库支持吧?那你为什么要所有这些数据库的连接库来用一个 SQLite3 数据库的功能呢?使用运行时链接(dlopen 那些函数),程序可以在运行的时候动态判断并加载它此次运行所需要的动态库。

  6. 升级。openssl 爆出了一个很严重的安全漏洞,已经被修复了,你怎么办?当然是升级呗。那你希望是更新一个几M的包然后重启服务器解决问题,还是下载好几百M的程序、更新每一个你所用到的使用了 openssl 的程序?更何况那些程序本身不一定都更新了,也许为了安全你得自行编译其中的很大一部分(你可以期望有一个安全团队在半夜爬起床去更新一个软件,但是你觉得上千项目的开发者都会这么做吗)。你也不一定能够找到所有静态链接了有漏洞的 openssl 版本的程序,万一漏掉一个,你整个服务器的安全性就没了(所以 openssl heartbleed 漏洞更新之后建议是重启系统而不是重启各服务)。

更别说更底层的库了,比如 C 库或者 C++ 库。不至于人家更新了一行代码,你就要重装整个系统吧?

静态链接有它自身的用处,但是它并不适合所有情况,甚至并不适合大多数情况。动态链接以其微小的运行效率损失为代价,为不论是最终用户还是开发者、打包者提供了更为优秀的库管理方案。之所以很多人看到静态链接相对于动态链接的优势,我认为还是因为他们没什么机会看到静态链接、尤其是大量静态链接会带来的问题。

你不需要把程序都静态链接。你需要的只不过是一个优秀的包管理器和维护团队而已

Category: Linux | Tags: linux 编译 go 编程语言
5
11
2015
14

使用 bcache 自制「混合硬盘」

换了新本子,外存是1T机械硬盘和16G固态硬盘。这16G SSD 速度挺快的尤其是读的时候,可它拿来放 / 都不够呢,于是拿来作缓存加速。根据局部性原理,虽然数据很多,但是最常访问的只占其中一小部分呢。

搜索的结果是有三个方案:bcache、dm-cache 和 Facebook 的 flashcache。前两者在官方内核里,不需要另外安装。我是最先在 Arch Wiki 上看到 bcache 的,后来又看 dm-cache,发现需要自己指定元数组的存储什么的,略复杂。而且一些评测显示 bcache 性能要好一点,所以就它了。

$$ \require{extpfeil} \rm{SSD} + \rm{HDD} \xlongequal{\rm bcache} \rm{SSHD} $$

配置起来其实很简单。首先安装 AUR 里的 bcache-tools,然后创建存储数据的分区和用于缓存的分区:

make-bcache -B /dev/sda2
make-bcache -C /dev/sdb1

教程上使用的是 SSD 的分区。换成 SSD 的块设备本身应该也可以。

参数什么的我没调。然后是把缓存设备的 UUID 写到 /sys/block/bcache0/bcache/attach 里。

为了最优性能,往 /sys/block/bcache0/bcache/cache_mode 里写入「writeback」来更改其缓存策略为「写回」。默认是「写通」(writethrough),也就是写的时候同时写缓存和后端设备,不会在缓存出问题时丢数据,但是会慢。另一个可选的策略是「writearound」,不知道该怎么译,是只写到后端设备而不写缓存的。最后一个是「none」,不知道用了它会发生什么……

换出策略使用默认的 LRU(最近最少使用)。剩下的两个(FIFO 和随机)应该效果没 LRU 好。

这些设备是会记住的,无需在启动时重启配置。至少我用的 4.0.1 内核是这样。

弄好之后就可以折腾 /dev/bcache0 这个块设备了。我放弃了之前使用文件级的 eCryptfs,改用在备份里使用得挺爽的块设备级的 dm-crypt,然后才格式化成 ext4。也就是:

$$ 文件数据 \xrightarrow{\textrm{I/O相关系统调用}} \rm{ext4} \xrightarrow{加密} \textrm{dm-crypt} \xrightarrow{缓存} \rm{bcache} \xrightarrow{写入} \rm{SSD} + \rm{HDD} $$

所以我的 /etc/mkinitcpio.conf 里要加上 bcache 和 encrypt 两个 hook:

HOOKS="base udev autodetect modconf block bcache encrypt filesystems keyboard fsck resume"

(不过这样子不能用外接 USB 键盘输入密码的。)

然后 mkinitcpio -p linux 一下,生成新的 initramfs 镜像。

为了共享缓存,我把 / 和 /home 放一起了(不过我猜对 /dev/bcache0 进行分区也是可以的?)。虽然这样子整个 / 用去了60多G空间,但是缓存的命中率还是非常高的——

>>> bcache-status -a
--- bcache ---
Device                      /dev/bcache0 (254:0)
UUID                        07a9b6a5-7f18-4950-84d6-c90abaaf65dc
Block Size                  0.50KiB
Bucket Size                 512.00KiB
Congested?                  False
Read Congestion             2.0ms
Write Congestion            20.0ms
Total Cache Size            14.91GiB
Total Cache Used            14.91GiB    (100%)
Total Cache Unused          0B  (0%)
Dirty Data                  0.50KiB     (0%)
Evictable Cache             14.17GiB    (95%)
Replacement Policy          [lru] fifo random
Cache Mode                  writethrough [writeback] writearound none
Last 5min Hits              439 (92%)
Last 5min Misses            38
Last 5min Bypass Hits       424 (100%)
Last 5min Bypass Misses     0
Last 5min Bypassed          61.50MiB
Last Hour Hits              46003       (88%)
Last Hour Misses            6051
Last Hour Bypass Hits       94043       (100%)
Last Hour Bypass Misses     0
Last Hour Bypassed          400.00MiB
Last Day Hits               79485       (88%)
Last Day Misses             10214
Last Day Bypass Hits        170383      (100%)
Last Day Bypass Misses      0
Last Day Bypassed           602.00MiB
Total Hits                  79485       (88%)
Total Misses                10214
Total Bypass Hits           170383      (100%)
Total Bypass Misses         0
Total Bypassed              602.00MiB

bcache-status 脚本来自这里

感觉还挺快的,特别是各种程序如火狐、gvim、pidgin、zsh 的启动速度,以及 mlocate、pacman 的搜索速度都非常快。没有对比数据,因为我没有试过在这个本子上不用 bcache 的情况下把系统弄起来。之前的旧本子可能因为分区太满导致碎片严重,所以 I/O 性能很差劲的。

Category: Linux | Tags: linux Arch Linux bcache SSD 硬盘
3
4
2015
7

Awesome 的 GitHub 今日贡献指示器:今天你 push 了吗?

GitHub 用户页有个 calendar,花花绿绿的甚是好看。不过,经常一不小心断掉了几十天的 steak 着实可惜,特别是用了私有仓库之后,自己看,有贡献,可别人看不到那些私有贡献的呀。其实要维持 steak 也不难,一个小小的提交就足够了——只要我知道我今天还没 push 什么公开的东西的时候。

当然啦,写个脚本天天推个无意义的更新挺容易的,但那样就没有乐趣了不是吗?一天快结束的时候发封邮件提示一下自己不错,但可能已经来不及了。而且这种事情还是随意点好,太刻意了就不好玩了,所以不需要那么强的提醒。弄一个简单的指示器在 Awesome 面板上正好。

效果图(指示器在右上角):

GitHub 今日贡献指示器的 Awesome 桌面

如果这天没有贡献(公开的提交或者 issue 等),那么这只 Octocat 就会失去色彩(变成灰度图)。

代码已上传至 myawesomerc 仓库。以下是实现细节:

首先,创建一个显示图片的 widget:

-- {{{ GitHub contribution indicator
github_contributed = awful.util.getdir("config") .. "/image/github_contributed.png"
github_not_contributed = awful.util.getdir("config") .. "/image/github_not_contributed.png"
github_widget = wibox.widget.imagebox()
function update_github(has_contributions)
    if has_contributions then
        github_widget:set_image(github_contributed)
    else
        github_widget:set_image(github_not_contributed)
    end
end
update_github(false)
-- }}}

-- 在 wibox 中添加这个 widget,需要放到正确的地方:
right_layout:add(github_widget)

函数update_github是给外部脚本用的。不可在 Awesome 配置里直接发起 HTTP 请求,会阻塞的!

当然,还要准备前两行代码提到的图片。从这里下载 Octocat 的图片,并做成彩色和灰度小图:

convert -resize 48x48 -background white -alpha remove Octocat.png github_contributed.png
convert -resize 48x48 -background white -alpha remove -colorspace Gray Octocat.png github_not_contributed.png

把图片放到相应的地方。然后写个脚本来更新这个指示器的状态,也就是获取数据之后再通过 awesome-client 调用update_github函数了。

#!/bin/bash -e

github_contributed () {
  count=$(curl -sS "https://github.com/users/$USER/contributions" | grep -oP '(?<=data-count=")\d+' | tail -1)
  [[ $count -gt 0 ]]
}

get_display () {
  if [[ -n "$DISPLAY" ]]; then
    return
  fi

  pid=$(pgrep -U$UID -x awesome)
  if [[ -z "$pid" ]]; then
    echo >&2 "awesome not running?"
    exit 1
  fi

  display=$(tr '\0' '\n' < /proc/"$pid"/environ | grep -oP '(?<=^DISPLAY=).+')
  if [[ -z "$display" ]]; then
    echo >&2 "can't get DISPLAY of awesome (pid $pid)"
    exit 2
  fi

  export DISPLAY=$display
}

get_display

if github_contributed; then
  s='true'
else
  s='false'
fi

echo "update_github($s)" | awesome-client

GitHub calender 目前是个 SVG 图片,位于https://github.com/users/用户名/contributions

awesome-client 需要设置正确的DISPLAY环境变量才可以使用。这里使用pgrep取当前用户的 awesome 进程里的DISPLAY变量值。ps命令不太好用,不能同时指定多个条件。

万事俱备,只需要拿 cron 或者 systemd.timer 定时跑跑这个脚本就可以啦~


2015年3月14日更新:update_github脚本改用 Python 实现了,更好的错误处理(不会因为网络问题而认为今天没有贡献了),也改用当前日期而非 GitHub calender 的最后一个方块。更新后的脚本在 GitHub 上

1
20
2015
10

恢复 tmux 窗口名称的自动设置

tmux 我已经用了好几年了,然而从未使用得多么深入,偶尔有些小不满也一直没有去研究看看能不能解决,其中就包括这么一项:tmux 窗口名称(就是显示在状态栏上的那个)默认会随着前台所运行的命令的不同而自动变化。但是,如果窗口名称被设置过之后,不管是通过prefix A设置的,还是通过终端转义序列设置的,之后它就再也不会自动变化了。

本来这也不是多大的事。偶尔会因为不小心往终端输出了些二进制数据弄乱终端标题,我要么是把它重新设置成「zsh」,要么直接关掉再开一个窗口,反正是很容器的事情。可是呢,公司服务器的 zsh 会把终端标题设置成当前的工作目录,ssh 退出时也不会清除。本来呢,我是专门再开一个终端来跑,完事之后再关掉。可是,习惯的力量是巨大的,我还是会时不时地在 tmux 窗口里 ssh 连过去,然后 tmux 窗口名称就坏掉了。

今天我终于决定把此事查个水落石出。既然主动设置之后就不再变化,那么 tmux 肯定用某种方法把「主动设置过窗口名称」这个信息给记录了下来。然后我就去 tmux 源码里找啊找,结果很意外地看到一个叫「automatic-rename」选项!敢情 tmux 早知道有人会对此不爽,专门弄了个选项呀。然后直接在1500行的 man 文档里搜索这个选项名称就可以了。

默认,tmux 的「automatic-rename」选项的全局值为「on」,也就是根据正在前台运行的命令自动设置。一旦窗口获得了一个用户或者程序指定的标题,不管是创建窗口时指定的,还是后来通过「rename-window」改的,又或者是通过终端转义序列改的,窗口局部的「automatic-rename」值就会被设置为「off」,也就是不会再自动变化了。所以,想要恢复 tmux 窗口的这个行为,只要把这个选项再次打开即可:

tmux setw automatic-rename on

或者,取消设置此窗口的局部值,这样 tmux 会使用全局值:

tmux setw -u automatic-rename

终于又解决了一个困扰已久的小麻烦~话说,直接去源码里寻找,远比在比 wget 手册还要长的 manpage 里乱逛要高效呢=w=

Category: Linux | Tags: tmux
11
8
2014
6

在 Arch Linux 下安装 openSUSE LXC 虚拟机

前边,我已经尝试过在 Arch Linux 下安装 Funtoo在 Arch 中安装 Arch 就更简单了。为了测试,我还通过 Aufs 来将我的 Arch Linux 在 LXC 里复制一份。至于安装个 Debian 或者 Ubuntu LXC,由于有在任何 Linux 下都可以跑的 deboostrap,安装起来也十分容易。

现在难题来了:在 LXC 里安装一个 openSUSE。LXC 自带了个 openSUSE 模板,但是它需要 zypper 等。虽然说 AUR 里就有 zypper 和 libzypp(还都是 git 版本的,下载很耗时的),不过还是不怎么够呢。经过尝试,我发现需要以下包来运行这个模板:

  • Arch 里有的:augeas
  • Arch 里没的:libsolv-tools build rpm libzypp zypper

有的就直接安装啦。没有的,可以从它的软件源下载。x86_64 架构的在这里,那个 build 是 noarch 的,在这边

把它们全部下回来,拿 7z 解压能够得到 cpio 档。然后建立个目录并 cd 过去,使用以下命令解开:

$ mkdir t
$ cd t
$ for f in ../*.cpio; do cpio -id < $f; done

这样就把它们解压到目录t里边的。我没有把它们解压到/,因为我不想弄乱我的系统,即使能够清理也是相当麻烦的,而且一不小心还可能删错文件。

所以,又该 Aufs 上场啦。当然在此之前还有件事:openSUSE 没有进行/usr合并。所以要手动去把binusr/sbinsbin等目录下的文件移动到usr/bin下,然后删掉那些目录;把usr/lib64下的文件移动到usr/lib下,并删掉usr/lib64

然后就可以将这个目录和我的 Arch Linux 合体啦:

$ mkdir root
$ sudo mount -t aufs -o br:$PWD/root=rw:$PWD/t=ro:/=ro aufs root

但是!这样子的话,新装好的 openSUSE LXC 会在这个root目录里呢。所以要把外边真实的 LXC 目录给 bind mount 过来。我使用了自定义的 LXC 路径,所以是这样子的:

$ sudo mkdir -p root/ldata/media/temp/lxc
$ sudo mount --bind /ldata/media/temp/lxc root/ldata/media/temp/lxc

然后编辑一下 openSUSE 的模板,搜索「http」把软件源的链接全部改到中国的镜像:

$ sudo vim root/usr/share/lxc/templates/lxc-opensuse

我使用的是中科大的源镜像。

一切就绪,开始安装~

$ sudo chroot root /usr/bin/lxc-create --lxcpath=/ldata/media/temp/lxc -n opensuse -t opensuse

耐心等待哦。最终安装完成根文件系统的大小是 333MiB。

安装完毕之后卸载刚刚挂载的那些东西:

$ sudo umount -R root

然后编辑一下自动生成的 LXC 配置文件,比如属改改网络什么的。以下是我改过的配置文件:

# Template used to create this container: /usr/share/lxc/templates/lxc-opensuse
# Parameters passed to the template:
# For additional config options, please look at lxc.container.conf(5)
lxc.rootfs = /ldata/media/temp/lxc/opensuse/rootfs
lxc.utsname = opensuse
lxc.autodev=1
lxc.tty = 4
lxc.pts = 1024
lxc.mount.entry = run run tmpfs rw 0 0
lxc.mount.entry = tmp tmp tmpfs rw 0 0
lxc.mount.auto = proc sys
lxc.cap.drop = sys_module mac_admin mac_override mknod sys_time
lxc.kmsg = 0

# When using LXC with apparmor, uncomment the next line to run unconfined:
#lxc.aa_profile = unconfined

#networking
lxc.network.type = veth
lxc.network.link = br0
lxc.network.flags = up
lxc.network.ipv4 = 192.168.57.6
lxc.network.name = eth0

lxc.cgroup.devices.deny = a
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 4:0 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rm

当然别忘记修改 root 密码啦:

$ sudo chroot /ldata/media/temp/lxc/opensuse/rootfs /bin/passwd

不过默认会有两个 getty 进程跑在 console 上边。要稍微修改一下。顺手把多余的 tty 上的也关掉好了:

$ sudo rm /ldata/media/temp/lxc/opensuse/rootfs/etc/systemd/system/getty.target.wants/getty@tty*
$ sudo mv /ldata/media/temp/lxc/opensuse/rootfs/etc/systemd/system/console-{shell,getty}.service

然后就可以启动啦:

$ sudo lxc-start -n opensuse --lxcpath=/ldata/media/temp/lxc

会默认启动 sshd,所以直接 ssh 连过去就可以用啦=w=

PS: 这个模板默认安装的是 openSUSE 12.3。记得自己改改或者装好后升级一下。

Category: Linux | Tags: lxc aufs linux Arch Linux openSuse
10
2
2014
4

我从 Apache httpd 切换到 nginx 了

因为工作上一直在用 nginx,对 nginx 配置的了解日益深入,而对 Apache httpd 配置的了解依然非常少以至于不知道如何添加一个虚拟主机的配置而不用修改大量已有配置,决定将自己本地的 Apache httpd 替换成 nginx。一开始这个 httpd 只是跑了一个 MediaWiki、一些静态文件和单独的 PHP 脚本,随着时间的流逝,后来陆续添加了 phpPgAdmin、RockMongo、PHP Xcache、into2html CGI 脚本等东西。于是配置起来似乎也不那么容易了。

首先第一点:这么多 PHP 的服务,我不想每一个 location 块里边一堆相同或者相似的 FastCGI 配置。于是把相关配置写到一个文件里去。(直到这时我才意识到 location 是可以嵌套使用的!)

index   index.php index.html;
location ~ (.+\.php\d?)($|/) {
        fastcgi_pass    unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index   index.php;
        set     $script $request_filename;
        if ($request_filename ~ ^(.+\.php\d?)(/.*)$){
                set $script     $1;
                set $pathinfo   $2;
        }
        fastcgi_param   PATH_INFO       $pathinfo if_not_empty;
        fastcgi_param   SCRIPT_FILENAME $script;
        include         fastcgi_params;
}

因为是嵌套的 location,所以得在外边也写一下index,不然 nginx 会不知道的。

另一个问题是默认的 fastcgi.conf 里定义的SCRIPT_FILENAME$document_root$fastcgi_script_name。但是我的 MediaWiki 使用了alias而不是root,于是$document_root会访问错地方。网上似乎没人完全地解决或者绕过了这个问题,大概是因为他们的配置不会被包含到多个 location 里吧。后来使用$request_filename这方案是我自己读了文档之后「发明」的。

另外,nginx 自定义的变量似乎是词法作用域,不能被 include 进来的配置访问到。大概因为 FastCGI 脚本路径是自己处理的,PATH_INFO也得自己处理。

然后就可以这么用啦(这是我的 MediaWiki 配置):

location /w/ {
        alias /usr/share/webapps/mediawiki/;
        include php;
}
location /wiki {
        rewrite ^/wiki(/.*)?$ /w/index.php$1 last;
}

以前的 Apache httpd 的配置是这样的:

Alias /w /usr/share/webapps/mediawiki

RewriteEngine On
RewriteRule ^/?wiki/(.*)$ /w/index.php/$1 [PT,L,QSA]
RewriteRule ^/?wiki$ /w/index.php [PT,L,QSA]

<Directory /usr/share/webapps/mediawiki>
        Options +FollowSymLinks
        AllowOverride All
        Require all granted
</Directory>

另外一个被我写成单独的配置文件以便被 include 的限制只允许本地访问用的:

allow   127.0.0.1;
allow   ::1;
deny    all;

比 httpd 的好理解一些。

另一个问题是 CGI 脚本。nginx 是有两个方案的,fcgiwrap 或者 nginx-fcgi。前者是个二进制程序,在 Arch 和 Debian 源里都有。后者是个 Perl 脚本,已经难以下载到了(给的链接是我从互联网存档取得的)。

Arch 的 fcgiwrap 包提供了一个 systemd socket 文件,直接启动它就可以了。启动 .service 服务似乎会有问题。

$ sudo systemctl start fcgiwrap.socket

nginx 里就这么写就可以了:

fastcgi_pass    unix:/run/fcgiwrap.sock;
fastcgi_param   QUERY_STRING       $query_string;
fastcgi_param   REQUEST_METHOD     $request_method;
fastcgi_param   CONTENT_TYPE       $content_type;
fastcgi_param   CONTENT_LENGTH     $content_length;
fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param   REQUEST_URI        $request_uri;
fastcgi_param   DOCUMENT_URI       $document_uri;
fastcgi_param   DOCUMENT_ROOT      $document_root;
fastcgi_param   SERVER_PROTOCOL    $server_protocol;
fastcgi_param   GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param   SERVER_SOFTWARE    nginx;
fastcgi_param   REMOTE_ADDR        $remote_addr;
fastcgi_param   REMOTE_PORT        $remote_port;
fastcgi_param   SERVER_ADDR        $server_addr;
fastcgi_param   SERVER_PORT        $server_port;
fastcgi_param   SERVER_NAME        $server_name;
fastcgi_param   REMOTE_USER        $remote_user;

引用的时候就是这样子:

location /cgi-bin {
        root /srv/http;
        include fcgiwrap;
}

经过测试,默认参数时 php-fpm 空闲时有三个 worker,高峰时会多一点,但是 Apache httpd 平时就有八个 worker,高峰时更多。每个 worker 对内存的占用是差不多的。至于响应速度,对于 MediaWiki nginx 稍慢 httpd 一点,但是服务启动后第一次访问会很多,而且 ab 测试时失败数少不少。更重要的是,systemd-analyze blame表示 nginx + php-fpm 组合启动时间远远少于 Apache httpd(0.1 秒 vs 好几秒)!

PS: 谁能告诉我 systemd-analyze 怎么查看以前启动的时间信息呀?

Category: Linux | Tags: php nginx apache httpd
9
29
2014
4

使用 Python 自制 expect 功能

Tcl 的 expect 工具是一个十分有用的自动化工具,经常被用来喂 ssh 密码什么的。不过配置 ssh Control Master英文介绍, 中文翻译之后,我发现第一次连接之后退出,expect 脚本不肯退出了,而且Ctrl-C什么的都不管用,除非我杀掉实际连接到远程主机的 ssh 控制进程。很显然,这是因为 ssh fork 出来的 ssh 进程依旧保持着伪终端的打开状态,expect 依旧在等待来自其上的输出。

研究之后,我发现虽然我可以给我的 expect 脚本加上命令行参数的处理之类的功能,但是interact之后必须等待伪终端关闭没有办法绕过。罢了,本来对 Tcl 就不熟,拿 Python 重写一个吧。

Python 也有个类似的库,叫 pexpect,不过上次我看它的时候还只支持 Python 2,而且使用起来似乎有点问题。所以干脆自制一个满足自己需求的好了。

程序不长,一百多行,但也比预期的长了不少。主要都是些终端和文件描述符的处理。哦还有信号。

我以是否存在子进程来作为判断是否结束interact的标志,而不管是不是还有进程在用这个伪终端。在收到SIGCHLD信号时使用waitid系统调用可以清理任意一已终止的子进程,使用WNOHANG标志在有子进程但是没有已退出者时不要等待、直接返回。于是,在有子进程退出时,反复调用waitid直到它报错就说明已经没有子进程存在啦。

另一个需要处理的信号是SIGWINCH,就是终端大小改变时以此终端为控制终端的前台进程会收到的那个信号。当脚本所在的终端大小改变时,需要重新设置脚本创建的伪终端的大小。不知道为什么,pexpect 竟然没有自己处理这个。

代码照旧在 GitHub 上。以下是使用示例:

#!/usr/bin/env python3

import sys

import expect

def main(host):
  p = expect.Expect()
  p.spawn(['ssh', host])
  p.expect_line('# ')
  p.send('. ./tide\n')
  p.send('TERM=screen-256color exec zsh -l\n')
  p.interact()

if __name__ == '__main__':
  host = 'phone'
  if len(sys.argv) == 2:
    host += sys.argv[1]
  main(host)

这是之前有问题的 expect 脚本:

#!/usr/bin/expect -f

set host [if {$argc eq 0} {lindex phone} {lindex phone$argv}]
spawn -noecho ssh $host
expect "*# "
send ". ./tide\n"
send "TERM=screen-256color exec zsh -l\n"
interact

这是我的 ssh control master 相关配置:

ControlPath ~/.ssh/master-%r@%h:%p
ControlMaster auto
ControlPersist yes
Category: Linux | Tags: linux python 终端 expect tcl
9
2
2014
14

Arch Linux 自动连接可用无线网络

Arch Linux 连接网络可以使用其官方开发的 netctl 系列命令行工具。要想在开机(以及从挂起/休眠状态唤醒)时自动连接到可用的无线网络,以下是设置步骤。

首先,你得告诉 Arch Linux 你知道哪些无线热点。Arch Linux 不会自动帮你破解别人的 Wi-Fi 密码的。就算 Wi-Fi 热点没有加密,你不说 Arch Linux 怎么知道它应当连接到那个热点呢,也许那是个钓鱼用的热点也说不定哦。

cd 到 /etc/netctl 目录下,可以看到 examples 目录下有一堆示例配置。复制你所需要的配置文件到上一层目录(/etc/netctl)。比如绝大多数 Wi-Fi 热点使用的是 WPA 加密,那就复制 examples/wireless-wpa 文件。目标文件名比较随意,起个方便自己的名字就行,比如 work、home 之类的。复制完成之后记得 chmod 600 禁止非 root 用户访问,因为配置文件里会包含你的 Wi-Fi 热点密码。

然后编辑配置文件,修改 ESSID 和 Key 为你的 Wi-Fi 热点 ID 和密码就可以了。之所以要先更改权限再编辑,是因为某些编辑器(如 Vim)会生成同权限的备份文件;那里有可能也会包含密码。可以放多份配置文件在这里,netctl-auto 默认会去找一个可用的连接。有多个可用的时候不太清楚它会连上哪一个,可以使用更复杂的配置文件来指定优先级(参见 examples/wireless-wpa-configsection 示例配置)。

配置文件写好之后,当然是启动相应的服务啦。Arch Linux 一贯的传统是不启动不必要的服务,除非用户说要启动之。netctl-auto 的 systemd 服务名是 netctl-auto@interface.service(当然 .service 后缀还是可以省略的)。interface 部分写你的无线网络接口的名字,可以通过 ip linkifconfigiwconfig 等命令看到。我禁用了 systemd 的可预测网络接口名称,所以我的无线网络接口名唤 wlan0。我使用如下命令启动服务:

$ sudo systemctl start netctl-auto@wlan0.service

如果一切顺利的话一小会儿之后就应该连上网了:

$ systemctl status netctl-auto@wlan0.service
● netctl-auto@wlan0.service - Automatic wireless network connection using netctl profiles
   Loaded: loaded (/usr/lib/systemd/system/netctl-auto@.service; enabled)
   Active: active (running) since 二 2014-09-02 20:23:31 CST; 2h 45min ago
     Docs: man:netctl.special(7)
  Process: 340 ExecStart=/usr/bin/netctl-auto start %I (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/system-netctl\x2dauto.slice/netctl-auto@wlan0.service
           ├─402 wpa_supplicant -B -P /run/wpa_supplicant_wlan0.pid -i wlan0 -D nl80211,wext -c/run/network/wpa_supplicant_wlan0.conf -W
           ├─404 wpa_actiond -p /run/wpa_supplicant -i wlan0 -P /run/network/wpa_actiond_wlan0.pid -a /usr/lib/network/auto.action
           └─501 dhcpcd -4 -q -t 30 -K -L wlan0
...

或者通过 netctl-auto list 命令也可以看到连接上了哪个配置文件里指定的热点。

如果满意的话,就让它开机自启动啦:

$ sudo systemctl enable netctl-auto@wlan0.service

参考资料:ArchWiki 上的 netctl 条目

Category: Linux | Tags: linux 网络 Arch Linux

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com