Apr 26

Haskell 程序一向编译起来费力,得先下个巨大的 GHC,然后从 Hackage 上下一堆包然后慢慢编译。所以我在这里把自己用的程序放出来。Arch Linux 的 Haskell 程序打包太复杂了,所以不打包了。连二进制包也懒得打。

这两个程序是 shellcheckcgrep

shellcheck 是一个 bash / POSIX sh 脚本 lint 工具。就是指出程序源码中可能出错的地方,相当于 jshint 之于 JavaScript、pylint 之于 Python(但是不含风格检查)、gcc / clang 的警告之于 C。

cgrep 就是 context-aware grep,比如搜索注释或者字符串里的东西之类的。支持解析几种编程语言。

程序是在 Arch Linux 上编译的,但其它 Linux 也许也可以使用。

下载地址:shellcheck-0.3.7.xz, cgrep-6.4.12.xz.

>>> sha1sum cgrep-6.4.12.xz shellcheck-0.3.7.xz
0588ee29a1a17c1cddc816a8193d8494db7c03cf  cgrep-6.4.12.xz
376b58d485603a7622f83f095a30bddc1da34376  shellcheck-0.3.7.xz
Apr 15

UDP 套接字是可以使用 connect 系统调用连接到指定的地址的。从此以后,这个套接字只会接收来自这个地址的数据,而且可以使用 send 系统调用直接发数据而不用指定地址。可以再次调用 connect 来连接到别的地方。但是在 Python 里,一旦调用 connect 之后,就再也回不到最初的能够接收从任意地址来的数据的状态了!

这是 Python 的 API 限制,没办法给 connect 方法传递到 AF_UNSPEC 地址簇(在 C 代码里写死了的)。C 里边就可以做到的(代码来自这里):

int disconnect_udp_sock(int fd) {
 struct sockaddr_in sin;        

 memset((char *)&sin, 0, sizeof(sin));
 sin.sin_family = AF_UNSPEC;
 return (connect(fd, (struct sockaddr *)&sin, sizeof(sin)));
}

不过既然是 Python 的限制,拿 ctypes 就可以绕过了嘛,有些麻烦就是了:

from ctypes import CDLL, create_string_buffer

def disconnect(sock):
  libc = CDLL("libc.so.6")
  buf = create_string_buffer(16) # sizeof struct sockaddr_in
  libc.connect(sock.fileno(), buf, 16)

AF_UNSPEC 的值是 0,所以把一个和 struct sockaddr_in 一样长的全零缓冲区传给 connect 就可以了 :-)

Apr 12

不少我喜欢的技术都在我喜欢的公司里使用呢。只是,那里没有我。

Apr 7

以前这服务器就经常被 OOM Killer 大开杀戒,各种服务都被杀害。虽然早就通过日志和其它信息知道是 npm 惹的祸,今天才有幸看到如此疯狂的 npm(点击查看大图):

crazy npm htop screenshot

说明一下,此 npm install 是正在安装 gulp 相关的东西。在本地跑的时候倒是没发现 npm 有如此「牛力」,只是比较费时费内存而已,不知道在这服务器上是发了什么疯。

Apr 6

这是偶然间发现的。在 GitHub 桌面版网页上,按第一下 Tab 时,会在左上角显示一个「Skip to content」的链接,并且键盘焦点就在这里:

GitHub "Skip to content"

我被这样的小小地惊艳了一下。这个链接可以用来跳过不怎么常用的导航栏上那排链接和按钮。

作为经常打字聊天和写码的人,在电脑前的大部分时间手当然是在键盘而不是鼠标上,所以使用鼠标来进行一些操作很常见。虽然我有 keynavVimFx 来辅助,但是见到 GitHub 这么贴心的细节还是挺喜欢的。

这让我想起了国内的网址,大部分都根本不考虑 accessibility。比如问答社交网站知乎。我早已不「逛」知乎了,只是收到感兴趣的邀请时去看看。因为知乎的体验实在是太差劲了。

知乎取消了所有链接在获取焦点时周围的虚线。这意味着根本没办法使用键盘导航,因为你不知道当然焦点在哪里。

知乎的提交按钮完全无法使用键盘操作。看看 GitHub,写好评论什么的,按一下 Tab 焦点就去了提交按钮上,并且有非常明显的反馈。按空格或者回车就可以提交。如果决定放弃,再按一下 Tab 就好。这两个按钮的顺序是按使用频率而不是相对位置来获取焦点的,因为提交显然比放弃用得更多。Twitter 发送新消息、Google+ 评论的提交按钮也是这样。不过 Twitter 偶尔会需要按两次 Tab 才能提交,大概是改页面的时候不小心给改坏了,过段时间会修好。Google+ 分享新信息时会比较讨厌,因为要跳过好大一堆「添加照片」什么的按钮。

类似的 accessibility 处理,国外著名网站上经常能看到很好的实践,博客上经常能看到大家在关心「少数用户」的使用便捷性。而国内很少有关注这方面的,大家都跟乔布斯一样想,「不管合不合理,我是这么设计的所以你应该这么用」。

比如验证码。不管需不需要,都是字符图。这样盲人就用不了。而国外广泛使用的 Google reCAPTCHA 就可以选择通过声音收听验证码。当然是英语的。反正这全球最大的网站之一已经消失在中国大陆人的视野之中了。我知道专门有人建立了一个 QQ 群,视力正常的人帮助盲人识别验证码用的。

这大概是发达国家与发展中国家的侧面写照的缩影了吧。

最后扔一堆链接:

Apr 4

很久以前的事情了。现在终于不再感觉到那么累了,想写,所以才写。权当日记吧,就懒得加各种链接和排版了。

这次的面试目标是百度运维工程师。

问题:为什么 Arch Linux 中文社区源的自动打包服务是自己弄的,而不使用现成的 CI 呢?

其实原因很简单:没想到。因为 lilac 自动打包脚本是由功能更简单、自动化程序也更低的脚本进化而来的,比如 nvchecker 更新、曾经自己用过的一些辅助脚本等。

当然,即使想到了,我也不会去用的,因为需要定制的东西太多。更新检查、依赖处理、错误回报、git 仓库管理什么的。传统的 CI 不像是天生支持这些的。最接近的 OBS 大家也知道,对 Arch 的支持很差。当然我可以进行二次开发,但工作量会很大、耗时会很长,因为那是我需要从头读文档、学习、尝试的一套系统,而对于 Linux 编程、Python 编程,我已经非常熟悉了。至于可重用性?首先我要解决它的可用性,东西都做不出来了,何谈可重用性呢?

当然 CI 系统对资源的消耗也不能忽视。

这个问题挺意外的,面试的时候没想这么清楚,没答到重点。

问题:为取得网站的高可用性,防止单个服务器挂掉影响整个服务,要怎么办呢?

我的回答是通过 DNS 和 anycast fallback。哪个服务器挂掉了就不用哪个了。DNS 的更新有些慢,在 DNS 应答中返回多个 IP 地址用处不大,大部分客户端都只会尝试第一个,我见过的只有 wget 会锲而不舍地穷尽一切方案去努力。Anycast 反应会更及时一些,CloudFlare 就喜欢用这个。

但是标答是 LVS。好吧这个词我没研究过。回来上网去看了一下,Linux Virtual Server,就是在前端放个负载均衡啦。我没有想到这个。今天我终于想到我为什么没有想到这个了——LVS 还是一台服务器嘛,只是逻辑上的服务器。作为一名程序员和数学爱好者而不是电器维修工,我对各种物理上的东西没什么深刻印象,反而对各种抽象的概念情有独钟。所以我自然而然地把「单个服务器」理解成单个逻辑上的服务器了,而不是或大或小的铁盒子。也就是说,我的理解是,当这个 LVS 系统挂掉了要怎么办。

我再一次把面试官想得到的答案当成了理所当然……

问题:正在进行通讯的两台主机之间的「网线」断了,一方再发送数据,这时会发生什么?

这个问题也很不明确,存在多种可能。

最简单的,直接连接发送方的网线断掉了。内核会收到硬件传回的信息,「carrier lost」,然后相应的路由表项被删除。就跟把相应的网络设备 down 掉一样。什么都不会发生,连重试都没有。应用程序就在那儿等着。

如果是位于局域网中,情形也差不多。只是会进行 TCP 重试,然后 ARP 广播「找人」。

互联网上的话,如果还有其它线路可用,数据包会走其它线路。如果所有线程都不通了,应该会返回 ICMP 主机不可达或者网络不可达。不知道这种情况下 TCP 和应用程序会发生什么。没条件实验。学校网络课实验自然不可能搞这么「高端」的东西。

当然,这么些可能我当时也没想全。

我发现我面试的时候思考能力变得很差的样子。其实凡是被人关注的时候都会发挥失常,虽然没有严重到别人盯着就写不出代码来。这也是我在社交活动常不满意的原因吧。

 

Mar 4

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 上

Feb 26

I want to be a Big Fish, but the lake is so small. I want to live in sea, but I have no way there. And they say, you aren't suitable for salty water.

To do it, or not to do it, it is wrong. This reminds me of my father.

I know about too many things, but none of them is good enough. 韩寒, I knew you were right, now I'm proof of your option.

It's so nice to meet you, CloudFlare. Really nice, not only your service, but your blog. You present me a larger world I'm looking forward to, a world I can only dream of, but fear that I will never reach. Even a piece of it.

There are 1724 days left. I've used more than a third now.

Feb 19

经过十二小时的旅程,转了五次车,又回到了这个熟悉的地方。因为年又来了。

依旧是简陋的陈设,依旧只有微弱的 3G 信号。可我还是回来了。所幸,今年除夕,据说是有规定,所以极近的地方并没有燃灯鞭炮,整夜只有远方并不觉得吵的连绵不断的噪音传来,正好和大雨声一样,屏蔽掉了夜里的杂音,让我第一次在除夕夜睡了个好觉。

春节一整天,却是阴沉沉的,因为外边在不间断地下着小雨。屋内灯光不甚明亮,在这种时候聊胜于无罢了。好在我有哆啦A梦的陪伴。也好在我把在北京都很少穿的毛衣带回来了。薄薄的,却抵得上小时候厚厚的两件。

回顾这一年。离开了那浑浑噩噩中选择的南京的初创公司。本来打算回家,再次为自己做一个负责的选择,却困于休息不好、压力山大,几个月过去了,尝试过几家公司,但没有成功。最后无奈之下,再次随意决定,重来到北京,继续自己的生活。

这家公司是在朝阳而不是海淀,不知道这到底算幸还是不幸。重访北京我最害怕的事情没有发生,可也发现,好多技术活动,我要花一个多小时的路程才能抵达。我是不独自坐出租车的,至少不能给差评的不会再坐了。而 Uber,它在向我要姓名,这使我很犹豫。

后来新的问题出现了。公司资金出了问题,拖欠了好几个月的工资。有段时间,我经常看到自己账户上还剩下多少银两,甚至想过罢工然后寻找新的工作。因为我很害怕很害怕。有些事情,人是永远不会想经历第二次的。我看过一部电影叫《时间规划局》(In Time)。在那个真正的「时间就是金钱」的世界中,当一个人的时间耗尽,生命也就终止了。来自社会底层的主人公意外获得了大量时间之后,飞奔着回家,要转给时间已所剩无几的母亲。他和他的母亲在路上看到了对方,飞奔着扑向对方,可是只晚了一秒钟,主人公拥抱在怀中的,已是时间归零的尸体。

还好,在我犹豫着要不要启动紧急计划的时候,工资终于到了一部分。警报解除,终于可以重新安心地生活了。

是的,属于自己的生活。不再是父母老师期望中的「好孩子」「好学生」,也不再是难得安宁地忍受网吧一样的环境,不再只是生存下去了。虽然依旧孤单,但灰色的画卷已经开始涂上的明亮的色彩。做自己喜欢的事情,做自己喜欢的自己,真好。这个世界没有我曾经历的那样充满恶意,也没有像我曾害怕的那样不能包容。虽然父母这边仍然是个麻烦。

事情在向好的方向发展。可是我依旧不知道是否还来得及,实现我那最初的梦想。上次是我自己不好,把事情搞砸了。花了很多时间去思考,去请教,去学习,可终究时间有限,大量的时间都花费在了计算机上。你们都说每一个人都值得获得那份幸福,希望对于除了编程和管理服务器之外什么都还不会的我依旧适用。

未来,我依旧有些许的迷茫和不确定。毕竟,这样的工作对于我来说太无趣了,而这样的队友也太难合作了。表达不清楚事情也就算了,我可以一点点慢慢确认;相关部分技术不懂也就算了,我可以引导你;可你丫的动不动闹情绪算个什么事!对不懂行的人有说有笑、对指出你的问题的人发脾气,能解决问题么?

本来想建立一个生活博客的,但是拖延症唉,新的一年里再努力吧!这一篇,就放在现在的博客上好了。

浓浓迷雾中,终于透出点点光亮。似乎还很远,但我知道,那是灯塔的方向。

Feb 8

通常,创建 git 提交使用的是 git add、git commit 这些高层命令。这些命令有个特点:它们都需要工作区的存在,并且很可能会改变工作区里的文件。

如果没有工作区,或者工作区不能用的时候怎么办呢?比如想在服务器上的纯(旧称:祼)版本库里的钩子脚本里创建提交,在版本库被更新时自动生成点东西什么的。一个简单的解决方案是再克隆一份,然后在那边弄好了再 push 过来。但那样的话,你得区分一次 push 是不是由你的脚本自身触发的了。这当然是可行的,但是,不觉得直接操作纯版本库更有意思吗 O(∩_∩)O~

或者,如果你的工作区里有一个文件叫「test」,另一个文件叫「Test」,而你当前能使用的文件系统和/或操作系统是不区分文件名大小写的,这样可能就没有办法通过工作区做想要的改动了。

我之前也曾用 pygit2 来直接创建提交。那个还是比较基础的,还是需要工作区。这次让我向大家介绍一下我的新玩法,深入 git 底层,围观一下一个提交的诞生历程~~(其实是早就玩过了的,只是一直没有分享出来而已 >_<

首先,克隆一个版本库来玩儿。直接在已有的版本库里玩太危险了,万一不小心玩坏了就囧了(虽然也不是多大的问题,毕竟我有备份的嘛=w=

git clone --bare ~/.vim dotvim

读者想要一起玩的话,可以从网络克隆我的 vim 配置版本库,如

git clone --bare git://github.com/lilydjwg/dotvim dotvim

进去 dotvim 目录里 ls 一下,可以看到,只剩下以前在 .git 目录里会见到的文件了呢:

>>> ls
branches  config  description  HEAD  hooks  index  info  objects  packed-refs  refs

要创建的提交是在 master 分支上。我们先使用 ls-tree 命令看看这个分支上有哪些文件。ls-tree 其实是列出 tree 对象用的,加上 -r 参数就会递归地把 tree 对象里的 tree 对象给列出来,就像 -R 之于 ls 命令一样。

不过你给它提交(commit)对象也可以的啦。它会自动取这个提交所指向的 tree 对象:

git ls-tree -r master

相当于

git ls-tree -r 'master^{tree}'

嗯,分支名实际上是指向这个分支上的最后一个对象的符号引用。

我要把 vimrc 文件里的注释行全删掉。要想修改一个文件,就得先找到要修改的文件,而不像添加文件那样直接加进去就可以了。让我们把要修改的 vimrc 文件的 hash 值找出来:

old_vimrc=$(git ls-tree -r master | awk '$4 == "vimrc" { print $3 }')

当然这里是不必用 -r 的啦。写在这里方便嘛,下一次想改 plugin 目录下的东西可以直接改路径就可以了,不用担心要改其它可能会忘记的东西。

然后拿 cat-file 命令看看这个 blob 对象。cat-file 命令我用得挺多的,因为经常会想看看另一个分支、或者另一个提交里某个文件长什么样子。

git cat-file -p $old_vimrc

其实这里可以直接指定要显示的对象的,比如master:vimrc就是 master 对应的提交上的 vimrc 文件。如果使用 zsh 的话,冒号后边的路径部分也是可以补全的哦。这里为了阐述原理,就做「分解动作」了。

得到了文件内容,就可以修改了。修改完毕,使用 hash-object 命令将文件存入 git 对象数据库里。这一句命令相当于修改好文件之后再做 git add 操作。hash-object 会返回对象的 hash 值。我们得把它记下来。

new_vimrc=$(git cat-file -p $old_vimrc | sed '/^"/d' | git hash-object -w --stdin --path vimrc)

不管之前有没有,先删一下 index,也就是所谓的「staging area」。已经添加但是还未提交的目录树就存在这个文件里边了。

rm -f index

然后,创建我们需要的 index。得使用 ls-tree 命令列出所有文件的信息,然后把我们修改过的信息加到末尾,会自动覆盖之前已有的项。如果删除这个列表里的某些项的话,就相当于是删除了那些文件。如果添加原本不存在的项,就是添加文件了。

{git ls-tree -r master; echo -e "100644 blob $new_vimrc\tvimrc";} | git update-index --add --index-info

index 已经准备好了。我们把目录树写到 git 对象数据库里吧:

new_master_tree=$(git write-tree)

我们得到了一个新的 tree 对象的 hash 值。当然因为目录树是树状的,以上命令实际上会写入多个 tree 对象。我们只要有根 tree 对象的 hash 值就可以了。

该创建提交对象了:

commit=$(git commit-tree -p master -m '在没有工作区的情况下创建的提交' $new_master_tree)

提交对象创建好还不够。那只是一个提交对象而已,我们还没有更新分支的信息呢。我们把这个提交作为新的 master 分支的头:

git update-ref refs/heads/master "$commit"

大功告成!

可以使用git showgit log看看成果了哦~

当然,如果想要钩子脚本里使用的话,记得在修改前加锁哦。

参考资料