2013年11月15日星期五

Linux下用Mencoder转录VCD

有一些 VCD 光碟在 Windows 下可以直接通过复制 /MPEGAV/AVSEQ*.DAT 来转录。而到了 Linux 下面会发现有时可以,有时不可以。
那么,既然 MPlayer 可以播放 VCD,Mencoder 就可以转录 VCD。
用下面的命令:
mencoder -o AVSEQ01.mpg -of mpeg -ovc copy -oac copy vcd://1
斜体字的“01”可以提换成轨道编号。

送给有需要的人。

2013年11月8日星期五

与Ripple XRP有关的垃圾邮件

自从 Ripple 向所有有资格的 GitHub 用户赠送 2020 XRP 的活动开始之后,我收到了各种不请自来的邮件想让我把这 2020 XRP 送给他们然后返还我 ¥30……
摘录两则:
于 2013年10月29日 20:32, z******@sina.com (MD5: bfd8b2538b0aacaabebdd403a75feaff) 写道:
> 你好!
> ripple在github上做活动,每个在5月1日之前注册github账户并有代码的,都可以
> 获得ripple。 活动时间可能马上就要结束,如果你拿ripple没有什么用,你可以
> 免费按照以下步骤几秒钟获取,然后将将获取的代码回复邮件给我,我给你30元烟
> 钱补偿。谢谢!也可以加qq1123969311详细询问。
> 1:访问:https://giveaway.ripple.com/
>
> 2:登录github帐号
> 3:
> 1):在Ripple address中填入rEeUEELwP2qX42kegfY6itmBp4K5rmPELS 然后点击下
> 面的按钮,生成code(会获得一大堆字符串)
>
> 2): 将生成后的code和你的支付宝帐号发送到 我这个邮箱地址。
> 我看到了后,就会给你们福利或者折现30元,转账到你的支付宝或者给你们冲手机
> 话费都可以的。
于 2013年11月7日 23:40, c***********@gmail.com (MD5: 0d80e06fe66b8d4ea1e246e6ae160a98) 写道:
> 帐号要求2013年5月1日前注册,并发布过东西
> 任务主要流程:登录github,关注那个开源代码,然后就能生成code了
> 详细介绍文章:
> http://hi.baidu.com/yhtmetxfblbfmuq/item/49a3ca5d61cb7fe6d48bac11
> 如果以上地址访问不了,你还可以试试这个地址:
> http://note.youdao.com/share/?id=ff7bb19251ffa5519cc9ed9e1e01a395&type=note
我就不吐槽“冲手机话费”的错别字了。
你们是在考验程序员的智商么?!
我想不会有很多程序员认为 2020 XRP 和 ¥30 是等价的。
拜托你们以后发不请自来的邮件时请不要暴露你们的智商。

2013年11月3日星期日

Lenovo Y510PT Haswell Linux 启动黑屏解决

昨天在 #archlinux-cn @ freenode 频道,phoenixlzx 求助 Lenovo Y510P Arch Linux 启动黑屏问题
Lenovo Y510PT,核心显卡 i7 Haswell,独立显卡 NVIDIA GT755M,Linux 内核版本 3.11.6。
症状是不光黑屏,而且屏幕没有背光。
Google一番,找到以下两个帖子:
果然是亮度问题!双显卡惹的祸!
解决方法很简单,加入引导参数 acpi_backlight=vendor 即可。

最后想到我自己的 Acer Aspire 4830TG 笔记本,不是亮度锁定在最小,而是亮度锁定在最大,引导参数 acpi_backlight=vendor 没有用,而是需要用引导参数 acpi_osi=Linux。更奇怪的是 felixonmars 的亮度锁定在 30%。如果你的笔记本也有类似的症状,不妨尝试这两个引导选项。
总结一句,这些奇怪的亮度问题只发生在 Intel + NVIDIA 双显卡的笔记本上,所以……
“F**k you NVIDIA!”
—Linus Torvalds

2013年11月2日星期六

Danmaku2ASS离线播放弹幕——弹幕转换为ASS字幕

Danmaku2ASS 截图
Danmaku2ASS 是一个可以转换 Niconico/Acfun/Bilibili 弹幕到 ASS 格式字幕的程序。
使用 Danmaku2ASS,可以使本地的高清视频源和弹幕视频站点的弹幕配合观看。
转换产生的 ASS 格式字幕,被各大媒体播放器(MPlayer、VLC、PotPlayer等)完美支持。你也可以使用 ffmpeg 将字幕压制到视频中。在项目 GitHub 页面有详细的操作说明。
目前 Danmaku2ASS 支持以下站点的弹幕文件格式:
  • ニコニコ動画
  • Acfun
  • 哔哩哔哩动画
  • ひまわり動画
  • tucao.cc
  • 土豆豆泡
  • Komica 彈幕測試版
  • MioMio 弹幕网
  • 所有兼容 niconico 弹幕格式的弹幕网站
  • 所有兼容 MukioPlayer 弹幕格式的弹幕网站
支持的功能有:
  • 可以合并多个弹幕文件
  • 可以自定义字体、字号
  • 可以在屏幕底部预留空白以防止遮挡字幕
  • 可以设置半透明文字
  • 可以自定义文字移动速度
示例用法:
./danmaku2ass -o foo.ass -s 1920x1080 -fn "MS PGothic" -fs 48 -a 0.8 -l 5 foo.xml
但是,目前 Danmaku2ASS 不支持从互联网下载弹幕,也就是说需要另外的软件(如 you-get)将弹幕下载到本地之后才可以导入到 Danmaku2ASS 中处理。
现在 Danmaku2ASS 还没有 GUI,但是开放了一个函数调用接口。欢迎在 Danmaku2ASS 的基础上进行开发。目前 Danmaku2ASS 以通用公共许可证第三版许可协议发布,如有特殊需求可以和我联系取得授权。
另外感谢 niconvert 软件的作者 muzuiget,他给了我创造 Danmaku2ASS 的启发。

最后,希望我的项目能够给大家带来方便,也希望大家在 GitHub 上 star 我的项目,给我更多支持。

2013年10月27日星期日

GNOME环境下用Ctrl-[代替损坏的Esc键

笔记本电脑的 Esc 键被我弄坏了。(万恶的 Vim!)
有人建议我用 xmodmap 把 Esc 映射到 Caps Lock 键上,但是这样就不方便输入大写字母了。
于是决定用 Ctrl-[ 来代替 Esc 键,反正这个组合键本身就有 Esc 的含义。
工具用 xdotool,以及 GNOME 自带的快捷键功能。
进入控制中心,在“键盘”选项里新建一个快捷键,按键组合选择 Ctrl-[,命令为 xdotool key --delay 200 ctrl+Escape (主意大小写)
为什么要用 ctrl+Escape 呢?那是因为按下 Ctrl-[ 之后命令触发的瞬间,Ctrl 键还没有释放,这里让 xdotool 再按一次 Ctrl 键就可以放开 Ctrl 了。
最后效果是这样:
GNOME控制中心截图
偶尔会遇到失灵的情况,估计和那个 Ctrl 键有关,这个时候把键盘左右 Ctrl 键都按一次基本上就能解决了。

2013年10月25日星期五

闯过了腾讯CodeStar第一季《前端特工》

无聊打开 w.qq.com 准备登陆QQ聊天,发现页面源代码里面原来藏着注释:
<!--

【云云无情,腾腾有爱】

如果你没有100个“往来”好友,那就来腾讯领红包吧!红包精彩,腾讯更精彩!
http://www.ipresst.com/jointencent

-------------华丽的分割线-------------

【前端特工】

“据内线消息,TX公司将于近期推出一个新的HTML5重磅产品。
公司担心该产品会带来威胁,特命你潜入TX,探查底细……”
http://codestar.alloyteam.com

-->
于是就打开看看。
貌似是一个闯关游戏。一步步走,最后通过了。
具体怎么通过的就不详细说了,事后在网上发现了另一位博主写的记录,有兴趣的点击看看就好了。

然后是吐槽:
第一关的时间戳,我用 date +%s 命令获取 UNIX 时间戳然后填进去怎么都不对啊!后来发现 JavaScript 的时间戳等于 UNIX 时间戳乘以 1000……
第二关动用 Google 大法,轻松找到使用CSS3绘制腾讯QQ的企鹅Logo一文,顺利通过。
第三关最坑了!调试了很长时间都没有搞定,结果一次偶然,我的坦克把敌方坦克撞到了战场的角落,并且因为我程序的 bug,导致继续往墙壁顶,活生生地把敌方坦克撞死了……
第四关是考JavaScript,小菜一碟。
第五关是数塔问题。(没错,就是我当年NOIP做过的数塔问题!)找到当年做过的源代码,跑一遍,OK!
然后,然后就没有然后了。

来个图纪念一下!
通关界面

2013年10月7日星期一

Nginx 根据 User-Agent 屏蔽访问

有以下的需求:
  • 对所有的机器人、爬虫、蜘蛛屏蔽内部测试用的站点。
  • 对使用 Microsoft Windows Internet Explorer 浏览器的用户展示一个“请更换浏览器”的页面。
set $botblocker_flag "0";
if ($http_user_agent ~* "bot|spider|crawl") {
    set $botblocker_flag "${botblocker_flag}1";
}
if ($uri !~ "/robots.txt(\?.*)?") {
    set $botblocker_flag "${botblocker_flag}2";
}
if ($botblocker_flag = "012") {
    return 403;
}
set $botblocker_flag "";
location = /noie.html {
    return 403;
    error_page 403 =403 @noie;
}
location @noie {
    root /var/www/error;
    index noie.html;
}
if ($http_user_agent ~* "MSIE| (360)?se |360spider|sou?gou|tencenttraveler") {
    rewrite .* /noie.html;
}
把上面的内容放到 /etc/nginx/botblocker 里。某一个站点需要用的时候就 include botblocker; 插在 server{} 块内。
然后写一个劝说用户更换浏览器的页面放在 /var/www/error/noie.html 里面。

2013年10月4日星期五

Tornado 做简易反向代理

在用 Tornado 建站,打算将 UI 渲染和后端操作独立为两个进程,用 API 互相操作数据。
我的做法是前后端可以同时被外部访问,类似于这样的 nginx 配置:
location / {
    proxy_pass http://test_tornado;
    include proxy_params;
}
location ~ ^/api {
    proxy_pass http://test_tornado_backend;
    include proxy_params;
}
在本地测试的时候是没有 nginx 的,是让 Tornado 直接跑。
于是在前端的代码中绑定 /api 到下面这个 Handler:
import tornado
import tornado.gen
import tornado.options
import tornado.httpclient
import tornado.web


class APIProxyHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        yield self.post()

    @tornado.gen.coroutine
    def post(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        request_headers = self.request.headers.copy()
        request_headers.add('X-Forwarded-For', self.request.remote_ip)
        request_options = {
            'url': '?'.join([tornado.options.options.backend]+self.request.uri.split('?', 1)[1:]),
            'method': self.request.method,
            'headers': request_headers,
            'body': self.request.body,
            'user_agent': self.request.headers.get('User-Agent', 'Tornado/%s' % tornado.version),
            'connect_timeout': 60,
            'request_timeout': 60,
            'follow_redirects': False,
            'use_gzip': True,
            'allow_nonstandard_methods': True,
            'allow_ipv6': True,
        }
        try:
            response = yield http_client.fetch(tornado.httpclient.HTTPRequest(**request_options))
        except tornado.httpclient.HTTPError as e:
            response = e.response
        self.set_status(response.code, response.reason)
        existed_headers = set()
        for header_name, header_value in response.headers.get_all():
            if header_name.lower() in ('content-encoding', 'content-length'):
                continue
            elif header_name in existed_headers:
                self.add_header(header_name, header_value)
            else:
                self.set_header(header_name, header_value)
                existed_headers.add(header_name)
        self.finish(response.body or None)

2013年10月3日星期四

“程序员遇到一个问题”系列

今天在 #archlinux-cn @ irc.freenode.net 讨论 CMake 在多任务同步进行时会产生混乱的输出的问题,于是谈到下面这句话:
One day, a programmer met one problem. He said, 'I will solve it with threads.' Then has he problems two now.
接着,biergaizi 找到了“程序员遇到一个问题”系列的出处,大家或是引用,或是原创,写了很多。
One day, a programmer met a problem. He said, 'I will solve it with Java.' Then he has a ProblemFactory.

One day, a programmer met one problem. He said, 'I will solve it with regular experssions.' Then he has two problems now.

'I know, I’ll use Python.' Now they import solution and have a beer.

'I know, I’ll use LISP.' Now their problem is recursive.

One day, a programmer met a problem. He said, 'I will use pointers in C.' Now thSegment Fault (core dumped)

One day, a programmer met an encoding problem. He said, 'I will switch from UTF-8 to GBK.' 锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐

2013年10月2日星期三

记一次遇到 HIM

在 phoenixlzx 的服务器,phoenixlzx 说没有装奇怪的 mod,突然发现服务器传回奇怪的聊天内容:
Client> 2013-10-02 23:28:11 [CLIENT] [INFO] [CHAT] [Server] Herobrine is near you...
Client> 2013-10-02 23:28:18 [CLIENT] [INFO] [CHAT] <StarBrilliant> herobine...
Client> 2013-10-02 23:28:40 [CLIENT] [INFO] [CHAT] <Icewater> what is it
Client> 2013-10-02 23:28:57 [CLIENT] [INFO] [CHAT] <StarBrilliant> you don't know herobine?
Client> 2013-10-02 23:29:08 [CLIENT] [INFO] [CHAT] <Icewater> No, I don't
Client> 2013-10-02 23:29:28 [CLIENT] [INFO] [CHAT] 已设置床
Client> 2013-10-02 23:29:52 [CLIENT] [INFO] [CHAT] StarBrilliant 暂时离开了
Client> 2013-10-02 23:30:17 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> You.
Client> 2013-10-02 23:30:23 [CLIENT] [INFO] [CHAT] StarBrilliant 回来了
Client> 2013-10-02 23:30:23 [CLIENT] [INFO] [CHAT] <StarBrilliant> herobine...
Client> 2013-10-02 23:31:06 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> I'm watching.
Client> 2013-10-02 23:32:08 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> Eyes...
Client> 2013-10-02 23:32:47 [CLIENT] [INFO] [CHAT] <StarBrilliant> .................
Client> 2013-10-02 23:33:11 [CLIENT] [INFO] [CHAT] <Icewater> kill
Client> 2013-10-02 23:35:03 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> You, miner, StarBrilliant. You should expect me.
Client> 2013-10-02 23:35:11 [CLIENT] [INFO] [CHAT] <StarBrilliant> Herobine?
Client> 2013-10-02 23:37:18 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> God will strike you.
Client> 2013-10-02 23:37:32 [CLIENT] [INFO] [CHAT] 你刚刚被雷击中了
Client> 2013-10-02 23:37:35 [CLIENT] [INFO] [CHAT] <StarBrilliant> oh!
Client> 2013-10-02 23:37:48 [CLIENT] [INFO] [CHAT] <StarBrilliant> hi Herobine
Client> 2013-10-02 23:38:56 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> God will strike you.
Client> 2013-10-02 23:38:58 [CLIENT] [INFO] [CHAT] 你刚刚被雷击中了
Client> 2013-10-02 23:39:09 [CLIENT] [INFO] [CHAT] 已设置床
Client> 2013-10-02 23:39:09 [CLIENT] [INFO] [CHAT] You can only sleep at night
Client> 2013-10-02 23:39:47 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> You can never escape...
Client> 2013-10-02 23:39:59 [CLIENT] [INFO] [CHAT] 你刚刚被雷击中了
Client> 2013-10-02 23:40:10 [CLIENT] [INFO] [CHAT] <StarBrilliant> ...
Client> 2013-10-02 23:40:18 [CLIENT] [INFO] [CHAT] <StarBrilliant> HIM!
Client> 2013-10-02 23:40:23 [CLIENT] [INFO] [CHAT] <StarBrilliant> I saw HIM!
Client> 2013-10-02 23:41:17 [CLIENT] [INFO] [CHAT] <dmql> wait
Client> 2013-10-02 23:41:22 [CLIENT] [INFO] [CHAT] <StarBrilliant> I saw HIM!
Client> 2013-10-02 23:41:25 [CLIENT] [INFO] [CHAT] <StarBrilliant> I saw Herobine!
Client> 2013-10-02 23:41:55 [CLIENT] [INFO] [CHAT] [Server] <phoenixlzx> Herobrine please stop trolling my players. :-)
Client> 2013-10-02 23:42:06 [CLIENT] [INFO] [CHAT] <StarBrilliant> lol
Client> 2013-10-02 23:42:07 [CLIENT] [INFO] [CHAT] <Icewater> ................
Client> 2013-10-02 23:42:19 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> Yes, Master.
哎……不知道是不是 phoenixlzx 在控制台干的,还是哪个 mod 干的好事。
Update: 谣言破解,原来是 phoenixlzx 装的 mod。好你个 phoenixlzx……

切换到 fish shell

打算从 bash 切换到 fish shell
之所以不是 zsh,是因为 zsh 不能开箱即用,虽然 fish 的社区没有 zsh 成熟。
Fish 有许多现代化的功能,如更高级的 Tab 补全、自动分析 man page 来提供命令行帮助、历史记录补全等。
缺点也很明显:语法不兼容 bash。
装好之后先 fish_update_completions 来分析 man page 并创建缓存。
然后打算迁移曾经的 bash 配置。
~/.bash_history 已经被 fish 自动迁移到了 ~/.config/fish/fish_history
可是还有 ~/.bashrc,这也是最要命的。Fish 的语法不兼容 bash,于是 ~/.bashrc 不能直接拿去用。
我的 ~/.bashrc 里主要是 exportalias,还有 alias ssss="export http_proxy=http://localhost:8118 https_proxy=http://localhost:8118" 这样使用了 aliasexport 两者的快捷命令。
Fish 已经有 alias 了,据说是在内部转换成函数来处理。而 fish 里等价于 export variable=value 的是 set -x variable value
干脆把 export 移植过来吧。写在 ~/.config/fish/functions/export.fish 里:
function export
    for i in $argv
        set -l __export_var (echo -n $i | grep -o '^[^=]*')
        if test $__export_var = $i
            set -g -x $__export_var $$__export_var
        else
            set -g -x $__export_var (echo -n $i | sed -e 's/^[^=]*=//')
        end
    end
end
再写一行到 ~/.config/fish/config.fish 里,这是等价于 ~/.bashrc 的启动文件,让他执行 ~/.bashrc 里的有关行:
cat ~/.bashrc | grep '^alias \|^export ' | .
就这样,配置完成了!

无损合并视频的多种方法

众所周知,从某些视频网站下载的视频是分段的。比如新浪视频每隔6分钟分段,俗称“6分钟诅咒”。
现在的任务是将这些视频片段合并起来,并且尽量无损。

方法一:FFmpeg concat 协议

对于 MPEG 格式的视频,可以直接连接:
ffmpeg -i "concat:input1.mpg|input2.mpg|input3.mpg" -c copy output.mpg
对于非 MPEG 格式容器,但是是 MPEG 编码器(H.264、DivX、XviD、MPEG4、MPEG2、AAC、MP2、MP3 等),可以包装进 TS 格式的容器再合并。在新浪视频,有很多视频使用 H.264 编码器,可以采用这个方法
ffmpeg -i input1.flv -c copy -bsf:v h264_mp4toannexb -f mpegts input1.ts
ffmpeg -i input2.flv -c copy -bsf:v h264_mp4toannexb -f mpegts input2.ts
ffmpeg -i input3.flv -c copy -bsf:v h264_mp4toannexb -f mpegts input3.ts
ffmpeg -i "concat:input1.ts|input2.ts|input3.ts" -c copy -bsf:a aac_adtstoasc -movflags +faststart output.mp4
保存 QuickTime/MP4 格式容器的时候,建议加上 -movflags +faststart。这样分享文件给别人的时候可以边下边看。

方法二:FFmpeg concat 分离器

这种方法成功率很高,也是最好的,但是需要 FFmpeg 1.1 以上版本。先创建一个文本文件 filelist.txt
file 'input1.mkv'
file 'input2.mkv'
file 'input3.mkv'
然后:
ffmpeg -f concat -i filelist.txt -c copy output.mkv
注意:使用 FFmpeg concat 分离器时,如果文件名有奇怪的字符,要在 filelist.txt 中转义。

方法三:Mencoder 连接文件并重建索引

这种方法只对很少的视频格式生效。幸运的是,新浪视频使用的 FLV 格式是可以这样连接的。对于没有使用 MPEG 编码器的视频(如 FLV1 编码器),可以尝试这种方法,或许能够成功。
mencoder -forceidx -of lavf -oac copy -ovc copy -o output.flv input1.flv input2.flv input3.flv

方法四:使用 FFmpeg concat 过滤器重新编码(有损)

语法有点复杂,但是其实不难。这个方法可以合并不同编码器的视频片段,也可以作为其他方法失效的后备措施。
ffmpeg -i input1.mp4 -i input2.webm -i input3.avi -filter_complex '[0:0] [0:1] [1:0] [1:1] [2:0] [2:1] concat=n=3:v=1:a=1 [v] [a]' -map '[v]' -map '[a]' <编码器选项> output.mkv
如你所见,上面的命令合并了三种不同格式的文件,FFmpeg concat 过滤器会重新编码它们。注意这是有损压缩。
[0:0] [0:1] [1:0] [1:1] [2:0] [2:1] 分别表示第一个输入文件的视频、音频、第二个输入文件的视频、音频、第三个输入文件的视频、音频。concat=n=3:v=1:a=1 表示有三个输入文件,输出一条视频流和一条音频流。[v] [a] 就是得到的视频流和音频流的名字,注意在 bash 等 shell 中需要用引号,防止通配符扩展。

提示

  1. 以上三种方法,在可能的情况下,最好使用第二种。第一种次之,第三种更次。第四种是后备方案,尽量避免。
  2. 规格不同的视频合并后可能会有无法预测的结果。
  3. 有些媒体需要先分离视频和音频,合并完成后再封装回去。
  4. 对于 Packed B-Frames 的视频,如果封装成 MKV 格式的时候提示 Can't write packet with unknown timestamp,尝试在 FFmpeg 命令的 ffmpeg 后面加上 -fflags +genpts

参考资料

  1. http://trac.ffmpeg.org/wiki/How to concatenate (join, merge) media files
  2. http://ubuntuforums.org/showthread.php?t=956769

2013年7月31日星期三

测试 IRC 网址机器人代码

最近在优化 IRC 链接机器人。这是一种在 IRC 频道中自动识别用户发出的网址并获取对应页面的标题或对应图片的尺寸等信息的机器人。
有一个要求很重要,就是发一些奇怪的网址也能正确识别,并且能妥善处理超时等问题。
下面列出一段代码,使用 Tornado 技术:
#!/usr/bin/env python

import time

import tornado.gen
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web


tornado.options.define('port', default=10489,
                       help='run on the given port', type=int)


class CountHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        for i in range(1, 101):
            yield tornado.gen.Task(
                tornado.ioloop.IOLoop.instance().add_timeout, time.time()+1
            )
            self.write(('%s\n') % i)
            self.flush()
        self.finish()


class BombHandler(tornado.web.RequestHandler):
    def get(self):
        self.set_header('Content-Encoding', 'gzip')
        self.set_header('Content-Type', 'text/html')
        self.set_header('Content-Length', '2147483647')
        with open('bomb.gz', 'rb') as f:
            self.write(f.read())


def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        ('/count', CountHandler),
        ('/bomb', BombHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(tornado.options.options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == '__main__':
    main()
然后我们还需要生成 bomb.gz。使用如下命令:
dd if=/dev/zero bs=4M count=1k | gzip -9 >bomb.gz
这将一个 4 GiB 大小的空文件压缩成 gzip 格式,生成文件大小约 4 MiB。
我们将测试 http://localhost:10489/count 和 http://localhost:10489/bomb 两个链接。

某些机器人有超时设定,而 /count 测试会分批传输数据,某些具有 bug 的机器人会一直等待下去。此时如果发送很多这个链接,在机器人等待的时候中断服务器的 Tornado 程序,可能会导致机器人不断高速发送错误信息而被踢出。
由于某些站点,如哔哩哔哩,会不顾浏览器发送的 Accept-Encoding 请求而始终返回 gzip 压缩的数据。导致有的机器人需要解压返回的数据。/bomb 就是测试机器人是否能够控制解压的数据量而不占用过多内存。
最后,提醒一下:如果你打算测试某个机器人,最好征得其主人同意哦。

2013年7月27日星期六

多人服务器的软件包和服务管理

现维护一台服务器,有很多人要登录,需要管理好软件包和服务。
为了避免服务器重启之后挨个通知大家的问题,我写了一个简单的服务管理脚本。有一些简陋(直接用 rc.local 而不是服务),也有一些 bug(比如关机脚本在某些发行版上不执行),但是安全性是有保障的。
那么软件包呢?服务器是 Debian 的,很多软件包在源里没有,或者版本太旧。这就需要用户自己编译。于是在 .bashrc 中写:
export PATH="$HOME/.local/bin:$HOME/.local/sbin:$PATH"
export LD_LIBRARY_PATH="$HOME/.local/lib"
export LD_RUN_PATH="$HOME/.local/lib"
export PREFIX="$HOME/.local"
export LDFLAGS="-L$HOME/.local/lib"
export CFLAGS="-I$HOME/.local/include"
export CPPFLAGS="-I$HOME/.local/include"
不对所有软件包生效(比如 cmake 生成的软件包,但是有解决方法),但是大部分的都可以这么做了。
可能读者朋友已经使用类似的方法了,这篇文章是个备忘。

2013年7月9日星期二

Nginx alias try_files php-fpm work together

There is such a configuration on my Nginx 1.2.1 server:
server {
 listen 80 default_server;
 listen [::]:80 default_server ipv6only=on;
 root /var/www;
 index index.php index.html;
 autoindex on;
 autoindex_localtime on;
 try_files $uri $uri/ =404;
 location ~ ^/~([^/]+)(/.*)?\.php(/.*)?$ {
  alias /home/$1/public_html$2.php;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME /home/$1/public_html$2.php;
  fastcgi_param PATH_INFO $3;
 }
 location ~ ^/~([^/]+)(/.*)?$ {
  alias /home/$1/public_html$2;
 }
}
It aims to map /~username/ to /home/username/public_html/ so that users on my server can access and manage their own files.
However, this brought a problem. If one tried to access a non-exist .php file, such as /~username/asdfasdf.php, php-fpm would complain about “Invalid Argument” instead of displaying the “404 Not Found” page of Nginx.
I have browsed Nginx Wiki and found:
Note that there is a longstanding bug that alias and try_files don't work together.
But I would never give up!
I observed the environment variables that Nginx passed to PHP and found that $_SERVER["DOCUMENT_ROOT"] became the same value as $_SERVER["SCRIPT_FILENAME"] instead of /var/www.
try_files directive tries files from $document_root. For example, try_files $uri =404; tests the existence of the file named $document_root$uri.
It is clear after I noticed this. The workaround is just append try_files "" =404; after the line of alias.

Nginx alias try_files php-fpm 共存

Nginx 1.2.1 服务器有这么一个配置:
server {
 listen 80 default_server;
 listen [::]:80 default_server ipv6only=on;
 root /var/www;
 index index.php index.html;
 autoindex on;
 autoindex_localtime on;
 try_files $uri $uri/ =404;
 location ~ ^/~([^/]+)(/.*)?\.php(/.*)?$ {
  alias /home/$1/public_html$2.php;
  fastcgi_pass unix:/var/run/php5-fpm.sock;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME /home/$1/public_html$2.php;
  fastcgi_param PATH_INFO $3;
 }
 location ~ ^/~([^/]+)(/.*)?$ {
  alias /home/$1/public_html$2;
 }
}
目的是把所有的 /~username/ 映射到 /home/username/public_html/,让服务器的用户能够存放自己的文件。
但这样带来了一个问题,如果访问一个不存在的 .php 文件,比如 /~username/asdfasdf.php,php-fpm 就会报错,显示“Invalid Argument”而不是 Nginx 的“404 Not Found”页面。
查看了 Nginx 的 Wiki,有这么一句话:
Note that there is a longstanding bug that alias and try_files don't work together.
但是我不会就此罢休的!
于是我观察了 Nginx 传递给 PHP 的环境变量,发现 $_SERVER["DOCUMENT_ROOT"] 变成了和 $_SERVER["SCRIPT_FILENAME"] 一样的值,而不是 /var/www 了。
try_files 指令是从 $document_root 算起的,比如 try_files $uri =404; 就是检测 $document_root$uri 是否存在。
弄明白了这个,就豁然开朗了。在 alias 那一行后面加上 try_files "" =404; 即可。

2013年6月8日星期六

Niconico 切换到正体中文

今年2月起登陆 niconico 发现页面底部的语言切换消失了。找了几个月之后终于找到了解决方法。
你需要一款修改HTTP头的插件,比如我用的 Chrome 插件
然后,对 nicovideo.jp 设置修改 Accept-Language 到 zh-TW 即可。
就这么简单的方法,我竟然搜索了几个月……

Update: 发现这个扩展更好。是开源的,用得放心,而且能够自动同步设置,功能也更强大。

2013年6月6日星期四

我的 ArchLinux ABS 包定制

Arch Linux 有 ABS (Arch Build System) 就是好。如果自带的包不满意,比如需要什么功能没有编译进去,可以自己编译。
下面贴上我自己定制的几个 ABS 包,以 PKGBUILD patch 的形式提供。算作备忘,也算是分享。

  1. ffmpeg 加入一些因版权问题没有自带的解码器,比如我经常需要压制 mp4 视频,需要 aac 编码器:
    文件名:ffmpeg.diff
    --- a/ffmpeg/PKGBUILD 2013-06-06 01:43:40.319846062 +0800
    +++ b/ffmpeg/PKGBUILD 2013-05-26 21:35:51.118143876 +0800
    @@ -57,7 +57,9 @@
         --enable-shared \
         --enable-vdpau \
         --enable-version3 \
    -    --enable-x11grab
    +    --enable-x11grab \
    +    --enable-nonfree \
    +    --enable-libfaac
      
     
       make
    
    
  2. Timidity++ 允许播放超过 1,048,575 个音符的 MIDI 文件
    文件名:timidity++-maxalloc.diff
    --- a/timidity/timidity.h 2012-06-01 11:40:44.000000000 +0800
    +++ b/timidity/timidity.h 2013-06-06 01:48:14.186722904 +0800
    @@ -436,7 +436,7 @@
     /* you cannot but use safe_malloc(). */
     #define HAVE_SAFE_MALLOC 1
     /* malloc's limit */
    -#define MAX_SAFE_MALLOC_SIZE (1<<23) /* 8M */
    +#define MAX_SAFE_MALLOC_SIZE (1<<26) /* 64M */
     
     #define DEFAULT_SOUNDFONT_ORDER 0
     
    
    文件名:timidity++.diff

    --- a/timidity++/PKGBUILD 2013-06-06 01:45:15.627480183 +0800
    +++ b/timidity++/PKGBUILD 2013-06-06 01:56:25.255489134 +0800
    @@ -14,13 +14,16 @@
                 'xaw3d: for using the Xaw interface')
     backup=('etc/timidity++/timidity.cfg')
     source=(http://downloads.sourceforge.net/timidity/TiMidity++-${pkgver}.tar.xz \
    +        timidity++-maxalloc.diff
             timidity.cfg timidity.service)
     sha1sums=('15ec27f1ea3e718a8d61603521fc16df5c0dd24b'
    +          'SKIP'
               '660b3afbb720d26e8f008034cee66dd8da082d6e'
               'f0351b9eb8928d3d82c380107654a7dbe7cd2d54')
     
     build() {
       cd "${srcdir}/TiMidity++-${pkgver}"
    +  patch -p1 <"${srcdir}/timidity++-maxalloc.diff"
       sed -i -e 's/tcl8.5/tcl8.6 tcl8.5/' -e 's/tk8.5/tk8.6 tk8.5/' configure
       sed -i 's/my_interp->result/(char *) Tcl_GetObjResult(my_interp)/' interface/tk_c.c
       ./configure --prefix=/usr --mandir=/usr/share/man --with-default-path=/etc/timidity++/ \
    
  3. lib32-fontconfig 中保留 fc-cache fc-list fc-match 等命令。
    文件名:lib32-fontconfig.diff
    --- a/lib32-fontconfig/PKGBUILD 2013-06-05 06:07:34.000000000 +0800
    +++ b/lib32-fontconfig/PKGBUILD 2013-06-05 22:44:01.613122245 +0800
    @@ -54,7 +54,10 @@
     
       make DESTDIR="${pkgdir}" install
     
    -  rm -rf "${pkgdir}"/usr/{include,share,bin} "$pkgdir"/{etc,var}
    +  rm -rf "${pkgdir}"/usr/{include,share} "$pkgdir"/{etc,var}
    +  for i in "${pkgdir}"/usr/bin/fc-*; do
    +    mv "$i" "$i"32
    +  done
       mkdir -p "$pkgdir/usr/share/licenses"
       ln -s $_pkgbasename "$pkgdir/usr/share/licenses/$pkgname"
     }
    
后续还会继续追加……比如开启 Pidgin 的视频聊天功能等。

2013年4月30日星期二

当 GNOME Terminal 3.8 不再支持透明背景时——LilyTerm

这个功能确实很有用啊,比如一边开着网页看手册,一边敲命令,只要把终端设置成半透明+置顶,就可以避免挡住网页文字;再比如同时编辑几个源代码文件,即使屏幕不够大,窗口有的叠在了一起,也不必担心看不见某个窗口的文字。
确实也有终端模拟器支持半透明背景,但是它们的透明其实是机械地复制下面窗口的内容,而不是向 Compositor 请求真正的透明,如果拖动窗口速度比较快就会看出破绽。
找了很多替代方案,最后看好 LilyTerm 这款台湾人写的终端模拟器。
需要的很多功能都有,可配置性非常非常非常强!稍微配置一下就能完美找回 GNOME Terminal 的感觉。
所有的配置都在右键菜单,配置完成别忘记选择 右键→用户配置文件→储存设置 来储存。

下面贴上我的配置文件,位于 ~/.config/lilyterm/default.conf。99.999% 的 GNOME Terminal 习惯都能找回来(包括快捷键)!
[main]

# Auto save settings when closing window.
auto_save = 0

# The version of this profile's format. DO NOT EDIT IT!
version = 0.9.9

# The default font name of vte terminal.
font_name = Monospace 11

# The default column of vte terminal.
column = 80

# The default row of vte terminal.
row = 24

# Use true opacity in vte box.
# 0: do NOT use rgba, 1: force to use rgba.
# Left it blank will enable it automatically
# if the window manager were composited.
# Disable it will disable transparent_window, too.
use_rgba = 1

# Start up with fullscreen.
fullscreen = 0

# Transparent window. Only enabled when the window manager were composited.
transparent_window = 0

# The opacity of transparent window.
window_opacity = 0.150

# The opacity of transparent window when inactive.
# Left it blank to disable this feature.
window_opacity_inactive = 0.200

# Use transparent background.
# It will use true transparent if the window manager were composited.
transparent_background = 1

# The saturation of transparent background.
background_saturation = 0.201

# Scroll the background image along with the text.
scroll_background = 0

# Sets a background image.
background_image = 

# Confirm to execute command with -e/-x/--execute option.
confirm_to_execute_command = 1

# Don't need to confirm for executing a program if it's in the whitelist,
# separate with <space>.
execute_command_whitelist = 

# Launching executed command in a new tab instead of opening a new window.
execute_command_in_new_tab = 1

# If a program is running on foreground,
# Don't need to confirm for terminating it if it's in the whitelist,
# separate with <space>.
foreground_program_whitelist = bash dash csh ksh tcsh zsh screen

# If a program is running in background,
# Don't need to confirm for terminating it if it's in the whitelist,
# separate with <space>.
background_program_whitelist = bash dash csh ksh tcsh zsh su

# Confirm before pasting texts to vte terminal.
confirm_to_paste = 1

# If the program is running on foreground,,
# Don't need to confirm for pasting texts to it if it's in the whitelist,
# separate with <space>.
paste_texts_whitelist = editor vi vim elvis nano emacs emacs23 nano joe ne mg ssh

# Confirm to close multi tabs.
confirm_to_close_multi_tabs = 0

# Shows [Transparent Background], [Background Saturation]
# [Transparent Window] and [Window Opacity] on right click menu.
show_background_menu = 1

# Shows [Change the foreground color]
# and [Change the background color] on right click menu.
show_color_selection_menu = 1

# The normal text color used in vte terminal.
# You may use black, #000000 or #000000000000 here.
foreground_color = #000000000000

# Sets the background color for text which is under the cursor.
# You may use black, #000000 or #000000000000 here.
cursor_color = #808080808080

# The background color used in vte terminal.
# You may use black, #000000 or #000000000000 here.
background_color = #ffffffffffff

# Shows [Increase window size], [Decrease window size],
# [Reset to default font/size] and [Reset to system font/size]
# on right click menu.
show_resize_menu = 1

# The ratio when resizing font via function key <Ctrl><+> and <Ctrl><->.
# 0: the font size is +/- 1 when resizing.
font_resize_ratio = 0.000

# The ratio when resizing window via right click menu.
# 0: the font size is +/- 1 when resizing window.
window_resize_ratio = 0.000

# When user double clicks on a text, which character will be selected.
word_chars = -A-Za-z0-9_.+!@&=/~%

# The lines of scrollback history. -1 means unlimited (vte >= 0.22.3).
scrollback_lines = -1

# Shows scroll_bar or not.
# 0: Never shows the scroll_bar; 1: Always shows the scroll_bar.
# Left it blank: Hide when fullscreen, or scrollback_lines = 0.
show_scroll_bar =

# The position of scroll_bar.
# 0: scroll_bar is on left; 1: scroll_bar is on right.
scroll_bar_position = 1

# Shows input method menu on right click menu.
show_input_method_menu = 0

# Shows change page name menu on right click menu.
show_change_page_name_menu = 1

# Shows exit menu on right click menu.
show_exit_menu = 1

# Enable hyperlink in vte terminal.
enable_hyperlink = 1

# Sets whether or not the cursor will blink in vte terminal.
# 0: Follow GTK+ settings for cursor blinking.
# 1: Cursor blinks.
# 2: Cursor does not blink.
cursor_blinks = 0

# Shows copy/paste menu on right click menu.
show_copy_paste_menu = 1

# Embed the copy/paste menu to the main menu.
embedded_copy_paste_menu = 1

# Sets whether or not the terminal will beep
# when the child outputs the "bl" sequence.
audible_bell = 1

# Sets whether or not the terminal will flash
# when the child outputs the "bl" sequence.
visible_bell = 0

# Sets whether or not the window's urgent tag will be set
# when the child outputs the "bl" sequence.
urgent_bell = 1

# Which string the terminal should send to an application
# when the user presses the Delete or Backspace keys.
# 0: VTE_ERASE_AUTO
# 1: VTE_ERASE_ASCII_BACKSPACE
# 2: VTE_ERASE_ASCII_DELETE
# 3: VTE_ERASE_DELETE_SEQUENCE
# 4: VTE_ERASE_TTY
erase_binding = 2

# Sets the shape of the cursor drawn.
# 0: VTE_CURSOR_SHAPE_BLOCK
# 1: VTE_CURSOR_SHAPE_IBEAM
# 2: VTE_CURSOR_SHAPE_UNDERLINE
cursor_shape = 0

# The default locale used when initing a vte terminal.
# You may use "zh_TW", "zh_TW.Big5", or "zh_TW.UTF-8" here.
default_locale = 

# The locales list on right click menu, separate with <space>.
# You may use "ja_JP", "ja_JP.EUC-JP", or "ja_JP.UTF-8" here.
# You may want to use "UTF-8" here if you have no locale data installed.
# Left it blank will disable locale and encoding select menu items.
locales_list = 

# Sets what type of terminal attempts to emulate.
# It will also set the TERM environment.
# Unless you are interested in this feature, always use "xterm".
emulate_term = xterm

# The environment 'VTE_CJK_WIDTH' used when initing a vte terminal.
# 0: get via environment; 1: use narrow ideograph; 2: use wide ideograph.
VTE_CJK_WIDTH = 1

# The geometry of window when starting.
# A reasonable example value is "80x24+0+0",
# witch means "WIDTH x HEIGHT {+-} XOFFSET {+-} YOFFSET", and NO SPACE in it.
# Notice that it will overwrite the default column and row settings above.
geometry = 


[page]

# The max character width of page name.
page_width = 16

# Show the tabs bar or not.
# 0: Never shows the tabs ; 1: Always shows the tabs bar.
# Left it blank: Hide when fullscreen, or tabs number = 1.
show_tabs_bar =

# The position of tabs bar.
# 0: Top, 1: bottom.
tabs_bar_position = 0

# The label of tabs will fill the tab bar.
fill_tabs_bar = 0

# The page name used for a new page.
page_name = Terminal

# The page names list used for new pages, separate with <space>.
page_names = Terminal

# Reuse the page name in the page names list.
reuse_page_names = 1

# Shows a (number no) on the page name.
page_shows_number = 1

# Shows the foreground running command on the page name.
page_shows_current_cmdline = 1

# Shows the terminal's idea of what the window's title should be.
page_shows_window_title = 1

# Shows current directory on the page name.
page_shows_current_dir = 1

# Check if the running command is root privileges.
check_root_privileges = 1

# Shows current encoding on the page name.
page_shows_encoding = 1

# Bold the text of current page name.
bold_current_page_name = 1

# Bold the text of action page name.
bold_action_page_name = 1

# Shows the page name of current page on window title.
window_title_shows_current_page = 1

# Append a package name (- LilyTerm) to the window title.
window_title_append_package_name = 1

# Shows a close button [X] on current tab.
show_close_button_on_tab = 1

# Shows a close button [X] on all tabs.
show_close_button_on_all_tabs = 0

# Use colorful text on page.
use_color_page = 1

# The color used for showing Window Title on page name.
# You may use black, #000000 or #000000000000 here.
page_win_title_color = #9A6401

# The color used for showing Running Command on page name.
# You may use black, #000000 or #000000000000 here.
page_cmdline_color = #1C1CDC

# The color used for showing Current Dir on page name.
# You may use black, #000000 or #000000000000 here.
page_dir_color = #215E3E

# The color used for showing Custom Tab Name on page name.
# You may use black, #000000 or #000000000000 here.
page_custom_color = #9C0A81

# The color used for showing Root Privileges on page name.
# You may use black, #000000 or #000000000000 here.
page_root_color = #BE0020

# The color used for showing Normal Text on page name.
# You may use black, #000000 or #000000000000 here.
page_normal_color = #333333


[key]

# Disable/Enable hyperlinks, function keys and right click menu.
# Left it blank to disable this function key.
disable_key_binding = 

# Add a new tab.
# Left it blank to disable this function key.
new_tab_key = Shift+Ctrl T

# Close current tab.
# Left it blank to disable this function key.
close_tab_key = Shift+Ctrl W

# Rename the page name of current tab.
# Left it blank to disable this function key.
edit_label_key = 

# Find the strings matching the search regex.
# Left it blank to disable this function key.
find_key = Shift+Ctrl F

# Find the previous string matching the search regex.
# Left it blank to disable this function key.
find_key_prev = Shift+Ctrl G

# Find the next string matching the search regex.
# Left it blank to disable this function key.
find_key_next = Shift+Ctrl H

# Switch to prev tab.
# Left it blank to disable this function key.
prev_tab_key = Ctrl Page_Up

# Switch to next tab.
# Left it blank to disable this function key.
next_tab_key = Ctrl Page_Down

# Switch to first tab.
# Left it blank to disable this function key.
first_tab_key = 

# Switch to last tab.
# Left it blank to disable this function key.
last_tab_key = 

# Move current page forward.
# Left it blank to disable this function key.
move_tab_forward = Shift+Ctrl Page_Up

# Move current page backward.
# Left it blank to disable this function key.
move_tab_backward = Shift+Ctrl Page_Down

# Move current page to first.
# Left it blank to disable this function key.
move_tab_first = 

# Move current page to last.
# Left it blank to disable this function key.
move_tab_last = 

# Switch to #1 tab directly.
# Left it blank to disable this function key.
switch_to_tab_1 = Alt 1

# Switch to #2 tab directly.
# Left it blank to disable this function key.
switch_to_tab_2 = Alt 2

# Switch to #3 tab directly.
# Left it blank to disable this function key.
switch_to_tab_3 = Alt 3

# Switch to #4 tab directly.
# Left it blank to disable this function key.
switch_to_tab_4 = Alt 4

# Switch to #5 tab directly.
# Left it blank to disable this function key.
switch_to_tab_5 = Alt 5

# Switch to #6 tab directly.
# Left it blank to disable this function key.
switch_to_tab_6 = Alt 6

# Switch to #7 tab directly.
# Left it blank to disable this function key.
switch_to_tab_7 = Alt 7

# Switch to #8 tab directly.
# Left it blank to disable this function key.
switch_to_tab_8 = Alt 8

# Switch to #9 tab directly.
# Left it blank to disable this function key.
switch_to_tab_9 = Alt 9

# Switch to #10 tab directly.
# Left it blank to disable this function key.
switch_to_tab_10 = Alt 0

# Switch to #11 tab directly.
# Left it blank to disable this function key.
switch_to_tab_11 = 

# Switch to #12 tab directly.
# Left it blank to disable this function key.
switch_to_tab_12 = 

# Open a new window with current dir.
# Left it blank to disable this function key.
new_window = Shift+Ctrl N

# Select all the text in the Vte Terminal box.
# Left it blank to disable this function key.
select_all = 

# Copy the text to clipboard.
# Left it blank to disable this function key.
copy_clipboard = Shift+Ctrl C

# Paste the text in clipboard.
# Left it blank to disable this function key.
paste_clipboard = Shift+Ctrl V

# Paste the text in the primary clipboard.
# Left it blank to disable this function key.
paste_clipboard in primary = Shift Insert

# Increase the font size of current tab.
# Left it blank to disable this function key.
increase_font_size = Shift+Ctrl plus

# Decrease the font size of current tab.
# Left it blank to disable this function key.
decrease_font_size = Ctrl minus

# Reset the font of current tab to original size.
# Left it blank to disable this function key.
reset_font_size = Ctrl 0

# Try to maximum the window to use all available space on your display.
# Left it blank to disable this function key.
max_window = F11

# Asks to place window in the fullscreen/unfullscreen state.
# Left it blank to disable this function key.
full_screen = Alt Return

# Emulate a mouse scroll up event on Vte Terminal box.
# Left it blank to disable this function key.
scroll_up = Shift Page_Up

# Emulate a mouse scroll down event on Vte Terminal box.
# Left it blank to disable this function key.
scroll_down = Shift Page_Down

# Asks to scroll up 1 line on Vte Terminal box.
# Left it blank to disable this function key.
scroll_up_1_line = Shift Up

# Asks to scroll down 1 line on Vte Terminal box.
# Left it blank to disable this function key.
scroll_down_1_line = Shift Down


[color]

# The main ansi color theme used in vte.
# Possible values are linux, xterm, rxvt, and tango.
# or left it blank to use the default settings form libvte.
theme = tango

# Invert the ansi colors, like invert the darkred to red, darkblue to bule.
invert_color = 0

# The brightness for ansi colors used in terminal.
brightness = 0.200

# The brightness for ansi colors used in terminal when inactive.
# Left it blank to disable this feature.
inactive_brightness = 0.200

# The ANSI color code for Normal Black
# You may use black, #000000 or #000000000000 here.
Color0 = 

# The ANSI color code for Normal Red
# You may use black, #000000 or #000000000000 here.
Color1 = 

# The ANSI color code for Normal Green
# You may use black, #000000 or #000000000000 here.
Color2 = 

# The ANSI color code for Normal Yellow
# You may use black, #000000 or #000000000000 here.
Color3 = 

# The ANSI color code for Normal Blue
# You may use black, #000000 or #000000000000 here.
Color4 = 

# The ANSI color code for Normal Magenta
# You may use black, #000000 or #000000000000 here.
Color5 = 

# The ANSI color code for Normal Cyan
# You may use black, #000000 or #000000000000 here.
Color6 = 

# The ANSI color code for Normal White
# You may use black, #000000 or #000000000000 here.
Color7 = 

# The ANSI color code for Bright Black
# You may use black, #000000 or #000000000000 here.
Color8 = 

# The ANSI color code for Bright Red
# You may use black, #000000 or #000000000000 here.
Color9 = 

# The ANSI color code for Bright Green
# You may use black, #000000 or #000000000000 here.
Color10 = 

# The ANSI color code for Bright Yellow
# You may use black, #000000 or #000000000000 here.
Color11 = 

# The ANSI color code for Bright Blue
# You may use black, #000000 or #000000000000 here.
Color12 = 

# The ANSI color code for Bright Magenta
# You may use black, #000000 or #000000000000 here.
Color13 = 

# The ANSI color code for Bright Cyan
# You may use black, #000000 or #000000000000 here.
Color14 = 

# The ANSI color code for Bright White
# You may use black, #000000 or #000000000000 here.
Color15 = 


[command]

# The parameters of the APPLICATION should be separated with <tab>, if any.
#
# method = {0,1,2}
# 0: Open the hyperlink in new tab.
#    Use it if the command were using CLI, like w3m.
# 1: Open the hyperlink with gdk_spawn_on_screen_with_pipes().
#    Use it if the command were using GUI, like firefox.
# 2: Open the hyperlink in new window,
#    Use it if you not sure.
#
# VTE_CJK_WIDTH = {0,1,2}
# 0: get via environment
# 1: use narrow ideograph
# 2: use wide ideograph.
#
# The ENVIRONS will apply to the application, separated with <tab>, too.
#
# The LOCALE will apply to the application as locale environs.
# You may use "zh_TW", "zh_TW.Big5", or "zh_TW.UTF-8" here.
# Left it blank to use the locale environs from current page.

# The web browser using for http(s)://
web_browser = xdg-open
web_method = 1
web_VTE_CJK_WIDTH = 0
web_environ = 
web_locale = 

# The ftp client using for ftp(s)://
ftp_client = xdg-open
ftp_method = 1
ftp_VTE_CJK_WIDTH = 0
ftp_environ = 
ftp_locale = 

# The file manager using for file:// and [Open current directory with file manager]
file_manager = xdg-open
file_method = 1
file_VTE_CJK_WIDTH = 0
file_environ = 
file_locale = 

# The email client using for user@host
email_client = xdg-open
email_method = 1
email_VTE_CJK_WIDTH = 0
email_environ = 
email_locale = 


P.S. 发现一个很贴心的功能,如果复制了几百行文字不小心选了粘贴,会提醒你是否真的要贴。从这可见 LilyTerm 的开发人员的细心,和 GNOME 开发团队形成鲜明对比啊!(当然我不否认 GNOME 3.8 也带来了很多贴心的小功能。)

2013年4月13日星期六

解决 wget 中文文件名乱码

wget 从服务器下载文件经常遇到中文文件名变成乱码的问题。
Google 了一下,网上有人写了一个 patch 来解决这个问题。
其实不必要像那样修改源代码,wgetman 页面里就有解决方法。
正解是参数 --restrict-file-names=nocontrol

Update: +筱百合 提供了他的 ~/.wgetrc 文件,方便大家借鉴:
# 不要乱转义中文
--restrict-file-names=nocontrol
# 使用重定向后的文件名
--trust-server-names=on
--content-disposition=on

2013年3月29日星期五

在 VirtualBox 里安装了 OS X Lion 黑苹果

先来图:
OS X Lion Hackintosh

教程是跟着这个来的。引导工具是 HJMac.iso,因为 iso 里自带的引导工具貌似有问题。
安装镜像在教程里。或者直接用我给的磁力链接:
当然还有虚拟机软件 VirtualBox 咯。
为什么我要装 Lion 而不是目前最新的 Mountain Lion 呢?那是因为我上次提取字体的时候已经下载过一个 Lion 镜像了。这次不想重新下载 Mountain Lion 了。:-)

下面说一点注意事项。
VirtualBox 设置据说要打开「绝对指点设备」,但是我正好相反,所以如果键盘鼠标失灵可以试试看关闭「绝对指点设备」,这样就会用标准的 PS/2 键鼠。
安装的时候一定不要选择「登陆时需要密码」。据说有人选择了并且成功了,但是至少我是导致键鼠失灵了。
第一次登陆进去之后会要求调整 USB 键盘选项,请一定要忽视掉。因为 VirtualBox 的键盘是 PS/2 接口的,不是 USB 接口的。调整之后反而键盘会失灵。
开机分辨率可以这样设置,首先执行 VBoxManage setextradata 你的虚拟机名字 CustomVideoMode1 1400x900x32,然后在引导的时候输入 mach_kernel -v "Graphics Mode"="1400x900x32"
最后,如果开机缓慢或者进入系统之后鼠标卡住或者死机等等,请有一点耐心,毕竟这是虚拟机而且不支持 GPU 加速,很多东西会比较慢。耐心一点就好了。

P.S. 目前 OS X 最吸引我的一点是,我的 VirtualBox 崩溃了,重新开启之后不光我的文档和工作进度全部恢复,而且所有窗口按照关机前的样子排列好!
据说 GNOME 2 曾经也支持注销前保存进度,重新登陆后恢复。但是可能是因为应用程序都不支持,这个功能很少起作用过,到了 GNOME 3 就干脆去掉这个功能了。

2014年4月25日回复 +Tom Li 的留言:“这个恢复功能最烦人的地方就是,有时候是某个程序导致系统崩溃,重启后这个程序又回来了,这时候要以最快的速度退出它……”
OS X Mavericks rebooting after crash

2013年3月17日星期日

iPhone 反向 USB 网络共享 - Linux 篇

所谓“反向共享”,就是电脑连接上网络,iPhone 没有,然后把网络连接共享给 iPhone。
网上有一些方案是利用代理服务器的,不完美,这里利用的是 OpenVPN,可以说是 90% 完美了。
为了避免伸手党(其实是我自己懒得写),我就不说详细了,把大概的内容说一下。如果谁感兴趣可以写一份更详细的教程。

  1. 为什么需要反向网络共享?
    你可能在一个没有 Wi-Fi 接入的地方(或者你的机器是行货 3GS 无 Wi-Fi),然后想向 iPhone 里下载一些东西,这个时候反向共享就有用了。
  2. iPhone 端安装软件
    首先,你的 iPhone 必须越狱(废话),然后从 Cydia 安装这些包:
    openssh GuizmOVPN
    如果你找不到 openssh 请把身份设置成“开发者”,后面那个是收费的,可以下载免费试用版,我们不需要主程序,只需要里面打过特殊补丁的 OpenVPN 客户端。
    2015年1月1日更新:请不要升级 GuizmOVPN 到 1.2.1!因为它将 TUNEMU 换成了 UTUN,因而不能工作!幸好我有备份:com.guizmo.openvpn_1.2.0_iphoneos-arm.deb
    至于怎么安装我就不说了,有 2G/3G/4G 的连上网下。没有的可以用 Cyder 2。截至发文,Cyder 2 仍然没有支持 iOS 5 以上系统,所以可以用 Cyder 下载 deb 包及其依赖,然后用 i-FunBox 拷贝进 iPhone 的 Cyder 自动安装目录。
    如果你打算用命令行 apt-get install,请用下面的软件包名:
    openssh com.guizmo.openvpn
  3. 电脑端安装软件
    你需要这些:
    openssh openvpn usbmuxd 你可能还想要 libimobiledevice ifuse
    根据发行版不同,你用的发行版可能是别的名字。
  4. 配置 OpenVPN
    怎么生成证书不在本文讨论的范畴(Google is your friend)。下面我们来讨论特殊的地方。
    假设你已经生成好 ca.crt ca.key dh1024.pem server.crt server.key client.crt client.key 了。我们来写 server.ovpn
    local 127.0.0.1
    port 1194
    proto tcp
    dev tun
    ca ca.crt
    cert server.crt
    key server.key
    dh dh1024.pem
    server 10.8.0.0 255.255.255.0
    push "dhcp-option DNS 8.8.8.8"
    push "dhcp-option DNS 8.8.4.4"
    client-to-client
    duplicate-cn
    keepalive 10 120
    verb 3
    然后是 client.ovpn
    client
    dev tun
    proto tcp
    remote localhost 1194
    nobind
    persist-key
    persist-tun
    ca ca.crt
    cert client.crt
    key client.key
    ns-cert-type server
    verb 3
    up ./guizmovpn_updown.sh
    down ./guizmovpn_updown.sh
    然后是关键的 guizmovpn_updown.sh,这个文件应该在 /Applications/GuizmOVPN.app 有。但是我的这个版本貌似可以用,如果你的版本不能用,试试看我的:
    #!/bin/bash
    
    #######################
    # GuizmOVPN_updown.sh #
    # Version 1.0.6       #
    #######################
    
    function CopyKey
    {
     if [ "`KeyExists $1`" == "NO" ] ; then
      return
     fi
            source=$1;
            dest=$2;
     error="0";
            echo "d.init" >/tmp/tmpscutil
            for param in `echo "show $1" | /usr/sbin/scutil | grep -v "<dictionary>" | tr -d '\n' | tr '}' '\n' | awk '{ print $1 }'` ; do
                    value=`echo "show $1" | /usr/sbin/scutil| grep -v "<dictionary>" | tr -d '\n' | tr '}' '\n' | grep $param`
      if $( echo $value | grep --quiet '<array>' ) ; then
                            unset array_val
                            for arraynum in 2 3 4 5 6 7 8 9 ; do
                                    val=`echo $value | cut -d '{' -f 2 | cut -d ':' -f $arraynum | cut -d ' ' -f 2`
                                    if [ "$val" != "" ] ; then
                                            array_val[$(($arraynum-1))]=$val;
                                    fi
                            done
                            if [ ${#array_val[@]} ]; then
                                    echo "d.add $param * ${array_val[*]}" >>/tmp/tmpscutil
                            else
        error="1" 
       fi
                    else
                            val=`echo "$value" | awk '{ print $3 }'`
                            if [ "`echo "$val" | tr -d ' '`"  == "" ] ; then
                                    error="1";
                            else
                                    echo "d.add $param $val" >>/tmp/tmpscutil
                            fi
                    fi
    
            done
            echo "set $dest" >>/tmp/tmpscutil
     if [ "$error"=="0" ] ; then
      cat /tmp/tmpscutil | /usr/sbin/scutil
     else
      echo "Error reading key $1"
     fi 
    }
    
    function GetActualDNS
    {
     OLDDNS=`echo "show State:/Network/Service/$1/DNS" | /usr/sbin/scutil | grep -A 1 "ServerAddresses : " | grep "0 : " | awk '{print $3}' | tr -d " "`
     echo $OLDDNS  
    }
    
    function KeyExists
    {
     if [ "`echo "show $1" | /usr/sbin/scutil | tr -d " "`" == "Nosuchkey" ] ; then
      echo "NO" ;
     else
      echo "YES" ;
     fi
    }
    
    PSID=$( (/usr/sbin/scutil | grep PrimaryService | sed -e 's/.*PrimaryService : //')<< EOF
    open
    get State:/Network/Global/IPv4
    d.show
    quit
    EOF
    )
    
    case "$script_type" in
       up)
         # Stop the APSd
         #/bin/launchctl unload /System/Library/LaunchDaemons/com.apple.apsd.plist
         
         # Remove informations about a previous connection
         echo "remove State:/Network/Service/OpenVPN/DNS" | /usr/sbin/scutil >/dev/null 2>&1
         echo "remove State:/Network/Service/OpenVPN/IPv4" | /usr/sbin/scutil >/dev/null 2>&1
         echo "remove State:/Network/Service/OpenVPN/PPP" | /usr/sbin/scutil >/dev/null 2>&1
         echo "remove State:/Network/Service/OpenVPN/PSID" | /usr/sbin/scutil >/dev/null 2>&1
     
         # Check if we need to handle DNSPush
         unset dns
         unset domain
         if [ "$DNSPush" == "Y" ] ; then
     # Store the PSID that we modify, in order to restore the correct one on disconnection
            if [ "$PSID" != "" ] ; then
                    echo "d.init" >/tmp/tmpscutil
                    echo "d.add PSID $PSID" >>/tmp/tmpscutil
                    echo "set State:/Network/Service/OpenVPN/PSID" >>/tmp/tmpscutil
                    cat /tmp/tmpscutil | /usr/sbin/scutil
            fi
    
     # Save the DNS settings
     # If it has already been set, don't save it again
     if [ "`KeyExists State:/Network/Global/DNSold`" == "NO" ] ; then 
      CopyKey Setup:/Network/Service/$PSID/DNS Setup:/Network/Service/$PSID/DNSold ; 
      CopyKey State:/Network/Service/$PSID/DNS State:/Network/Service/$PSID/DNSold ;
      CopyKey State:/Network/Global/DNS State:/Network/Global/DNSold ; 
     fi
    
          n=1; i=0; j=0;
          while o=foreign_option_${n}; o=${!o}; [ "$o" ]
          do
               case $o in
                'dhcp-option DNS '*)        dns[i++]=${o/dhcp-option DNS /};;
                  'dhcp-option DOMAIN '*)     domain[j++]=${o/dhcp-option DOMAIN /} ;;
               esac;
               let n++
          done
    
     # Check if we need to try to keep local DNS
          if [ "$DNSKeep" == "Y" ] ; then
      # If DNS push has been set, add the actual DNS in the list, in case the DNS pushed don't respond
      if [ $i != 0 ] && [ $i -le 3 ] ; then
              dns[i]=`GetActualDNS $PSID`;
      
        # Check if the DNS is different from the gateway IP.
        # If it's the case, add a route to it
        if [ "$traffic_is_redirected" == "1" ] && [ "$dns[i]" != "" ] && [ "$route_net_gateway" != "${dns[i]}" ] && [ "$route_net_gateway" != "" ] ; then
        echo "Add a route to ${dns[i]} with gateway $route_net_gateway" ;
        /sbin/route add -net ${dns[i]} $route_net_gateway 255.255.255.255;
        fi 
           fi
     fi
    
          if [ "${dns[0]}" != "" ] ; then
      echo "Setting DNS to /Network/Service/$PSID/DNS (${dns[*]})"
      echo "d.init" >/tmp/tmpscutil
             echo "d.add ServerAddresses * ${dns[*]}" >>/tmp/tmpscutil 
      if [ "${domain[0]}" != "" ] ; then
       echo "Setting domain to /Network/Service/$PSID/DNS (${domain[*]})" 
       echo "d.add SupplementalMatchDomains * ${domain[*]}" >>/tmp/tmpscutil
      fi
      echo "set State:/Network/Service/$PSID/DNS" >>/tmp/tmpscutil 
      
      cat /tmp/tmpscutil | /usr/sbin/scutil 
    
      # For a strange reason, some devices don't use the "State" but the "Setup", so we copy the key.
      if [ "`KeyExists Setup:/Network/Service/$PSID/DNS`" == "YES" ] ; then
                     CopyKey State:/Network/Service/$PSID/DNS Setup:/Network/Service/$PSID/DNS 
                    fi
      CopyKey State:/Network/Service/$PSID/DNS State:/Network/Service/OpenVPN/DNS 
                    CopyKey State:/Network/Service/$PSID/DNS State:/Network/Global/DNS 
     fi
         fi
         
         # Add informations about the connection
         # IPv4
         echo "d.init" >/tmp/tmpscutil
         echo "d.add DestAddresses * $trusted_ip" >>/tmp/tmpscutil
         echo "d.add ServerAddress $trusted_ip" >>/tmp/tmpscutil
         echo "d.add InterfaceName ppp0" >>/tmp/tmpscutil
         echo "d.add Addresses * $ifconfig_local" >>/tmp/tmpscutil
         echo "d.add SubnetMasks * 255.255.255.255" >>/tmp/tmpscutil
         echo "d.add NetworkSignature VPN.RemoteAddress=$trusted_ip" >>/tmp/tmpscutil
         echo "set Setup:/Network/Service/OpenVPN/IPv4" >>/tmp/tmpscutil
         echo "set State:/Network/Service/OpenVPN/IPv4" >>/tmp/tmpscutil
    
         # PPP
         echo "d.init" >>/tmp/tmpscutil
         echo "d.add Status 8" >>/tmp/tmpscutil
         echo "d.add InterfaceName ppp0" >>/tmp/tmpscutil
         echo "set Setup:/Network/Service/OpenVPN/PPP" >>/tmp/tmpscutil
         echo "set State:/Network/Service/OpenVPN/PPP" >>/tmp/tmpscutil
         
         # Interface
         echo "d.init" >>/tmp/tmpscutil
         echo "d.add Type PPP" >>/tmp/tmpscutil
         echo "d.add SubType OpenVPN" >>/tmp/tmpscutil
         echo "set Setup:/Network/Service/OpenVPN/Interface" >>/tmp/tmpscutil
     
         cat /tmp/tmpscutil | /usr/sbin/scutil
    
         # Add infos about connection to be displayed in GUI
         /Applications/GuizmOVPN.app/tools writeprefs InfosIPAddress "$ifconfig_local"
         if [ "$ifconfig_netmask" == "" ] ; then
              /Applications/GuizmOVPN.app/tools writeprefs InfosSubnetMask "255.255.255.255"
       /Applications/GuizmOVPN.app/tools writeprefs InfosGateway "$ifconfig_remote"
         else
              /Applications/GuizmOVPN.app/tools writeprefs InfosSubnetMask "$ifconfig_netmask"
       /Applications/GuizmOVPN.app/tools writeprefs InfosGateway "$InfosGateway"
         fi
         /Applications/GuizmOVPN.app/tools writeprefs InfosTrafficRedirected "$traffic_is_redirected"
    
         # Start the APSd
         #/bin/launchctl load /System/Library/LaunchDaemons/com.apple.apsd.plist
       ;;
    
       down)
         # Remove the connection infos
         /Applications/GuizmOVPN.app/tools writeprefs InfosIPAddress "" 
         /Applications/GuizmOVPN.app/tools writeprefs InfosSubnetMask ""
         /Applications/GuizmOVPN.app/tools writeprefs InfosGateway ""
         /Applications/GuizmOVPN.app/tools writeprefs InfosTrafficRedirected ""
    
          # Check if we have a stored PSID. If not, take the actual one
          OLD_PSID=`echo "show State:/Network/Service/OpenVPN/PSID" | /usr/sbin/scutil | grep PSID | awk '{print $3}'`
          if [ "$OLD_PSID" == "" ] ; then
              OLD_PSID=$PSID
          fi
     
          # If we have saved DNS, restore them
          if [ "$OLD_PSID" != "" ] && [ "`KeyExists State:/Network/Global/DNSold`" == "YES" ] ; then
              echo "Restoring DNS to $OLD_PSID";
              CopyKey Setup:/Network/Service/$OLD_PSID/DNSold Setup:/Network/Service/$OLD_PSID/DNS ;
              CopyKey State:/Network/Service/$OLD_PSID/DNSold State:/Network/Service/$OLD_PSID/DNS ;
              CopyKey State:/Network/Global/DNSold State:/Network/Global/DNS ;
    
       echo "remove Setup:/Network/Service/$OLD_PSID/DNSold" | /usr/sbin/scutil >/dev/null 2>&1 ;
       echo "remove State:/Network/Service/$OLD_PSID/DNSold" | /usr/sbin/scutil >/dev/null 2>&1 ;
       echo "remove State:/Network/Global/DNSold" | /usr/sbin/scutil >/dev/null 2>&1 ;
          fi
    
          # Check if we need to remove the route we added for the DNS
          if [ "$traffic_is_redirected" == "1" ] && [ "$route_net_gateway" != "`GetActualDNS $OLD_PSID`" ] && [ "$route_net_gateway" != "" ] && [ "`GetActualDNS $OLD_PSID`" != "" ] ; then
        echo "Removing route to `GetActualDNS $OLD_PSID` with gateway $route_net_gateway" 
       /sbin/route delete -net `GetActualDNS $OLD_PSID` $route_net_gateway 255.255.255.255 
          fi
    
          # Remove informations about the connection
          echo "remove State:/Network/Service/OpenVPN/DNS" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove State:/Network/Service/OpenVPN/IPv4" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove State:/Network/Service/OpenVPN/PPP" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove State:/Network/Service/OpenVPN/PSID" | /usr/sbin/scutil >/dev/null 2>&1
    
          echo "remove Setup:/Network/Service/OpenVPN/IPv4" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove Setup:/Network/Service/OpenVPN/PPP" | /usr/sbin/scutil >/dev/null 2>&1
          echo "remove Setup:/Network/Service/OpenVPN/Interface" | /usr/sbin/scutil >/dev/null 2>&1
       ;;
       *) echo "$0: invalid script_type $script_type" && exit 1 ;;
    esac
    
    #####
    
  5. 启动脚本
    我们来写一个启动脚本吧,connect.sh
    #!/bin/sh

    sudo -v
    sudo usbmuxd
    iproxy 2222 22 &
    sudo -n openvpn --config server.ovpn &
    sleep 1
    exec ssh root@localhost -p 2222 -R 1194:localhost:1194 /var/root/iptether/ovpncon.sh
    然后是 iPhone 端的启动脚本 ovpncon.sh
    #!/bin/sh

    (cd /var/root/iptether; /Applications/GuizmOVPN.app/openvpn --config client.ovpn --script-security 2) &
    还有 DNS 修复脚本,一般情况下是不需要的,但如果 DNS 不工作,就试试看 iPhone 端执行 ovpndns.sh
    #!/bin/sh

    /usr/sbin/scutil << EOF
    open
    d.init
    d.add ServerAddresses * 8.8.8.8 8.8.4.4
    set State:/Network/Service/OpenVPN/DNS
    quit
    EOF
  6. 配置 NAT 转发
    echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
    但是这会在关机后失效。如果希望下次开机自动开启转发,再修改 /etc/sysctl.conf
    net.ipv4.ip_forward = 1
    然后配置防火墙:
    sudo iptables -t nat -A POSTROUTING -j MASQUERADE
    同样,这个也是关机后丢失,如果想要保留,再修改 /etc/iptables/iptables.rules 并且开启系统的 iptables 服务(可能会因发行版而不同):
    *nat
    -A POSTROUTING -j MASQUERADE
    COMMIT
  7. Copy & Run!
    现在,把一大堆 OpenVPN 的配置文件和证书文件用 SCP 拷贝到 iPhone 的 /var/root/iptether 里面,用户名 root,密码 alpine(如果你没有修改,i-FunBox 之类的软件也没有替你修改的话)。
    还有,所有 .sh 结尾的文件都要给 0755 权限。
    然后,在电脑上 ./connect.sh,会自动调用刚刚拷贝过去的 ovpncon.sh 然后连接上网。Enjoy then!
    可能会有一些小 Bug,比如 iCloud 无线同步时好时坏(亲测 iOS 5.0.1 工作,iOS 6.1 不工作),或者是推送(亲测 iOS 4.2.1 不工作,iOS 5.0.1 工作,iOS 6.1 工作),又或者是 iMessage(亲测 iOS 5.0.1 不工作,iOS 6.1 工作)。