Ubuntu 9.04 安装手记

此次安装是在一台 Dell OptiPlex 740 台式机上进行,处理器 AMD Athlon 64 X2 4400+,显卡 nVIDIA GeForce 6150,声卡 SigmaTel STAC9200。Ubuntu 版本为 9.04 Jaunty Jackalope Alpha 5 amd64 位版,硬盘安装。

1. 硬盘安装时在分区那步仍无法列出分区表,需要 Ctrl-Alt-F2 进入控制台 2 手动 umount -l /hd-media 后重试。

2. 开箱显示正常,但 Compiz 3D 特效无法开启,可能因为没有可用的受限驱动。

3. 默认 ALSA 驱动不支持该声卡,但选择 OSS 驱动可以支持(很奇怪)。必须在音量设备那开启对 capture 设备,才能录入声音,此时可以使用 ALSA 驱动。在使用 Mplayer、SMplayer 之类播放器播放视频时,需要手动在选项中选择声音输出的驱动为 OSS。

4. 安装 smplayer 之后即支持 avi 格式解码,不用手动下载 codecs。在播放 720p 以上高清视频时,最好将 smplayer 的本地缓存调整为 10M 以上(默认为2M),否则容易引起底层 mplayer 内存问题,会无法播放、内存占用过大死机或崩溃。

5. Socks 代理软件 Dante 源代码在该 x86_64 平台上编译后运行会出现段错误;更换为 antinat,经源码编译后运行正常。

6. Libfetion for 64 位 deb 包不可用,异常退出;经源码编译后运行正常。

7. sendsms 编译为 64 位版本会出现段错误,但短信仍能发送成功。

9. 网易的 Ubuntu 软件仓库镜像不包含 Jaunty,cn 的镜像没有总镜像快,security 的连接速度太慢。正式发布后,网易已经有了 Jaunty 源。

10. 使用 Firefox 时可直接安装 Adobe Flash Player 10 64 位版本。

11. 新的状态通知很漂亮,可以显示 Pidgin 上好友的登录和短讯。

12. 根分区使用 ext4 文件系统,数据分区为了与其它系统保持共享仍采用 ext3 文件系统。

13. 安装中文支持时可以选择是否安装该语言用户常用软件,选择是则会增加 PCManX、StarDict 等软件。PCManX 中包含非常全的港台和大陆 BBS 地址簿,但是字体有问题,未解决;QTerm 5.3 会崩溃。

14. 传说中的启动加速没有明显感觉,也许是因为启动的服务太多。

15. Ubuntu 9.04 Jave 程序中文显示不正常,部分文字变成方格。文泉驿字体从 wqy-zenhei.ttf 改为 wqy-zenhei.ttc,导致 java 程序中文字体显示不正常。在 /etc/java-6-sun/fontconfig.properties 中添加 wqy 相关内容,diff 文件可以从这里下载。Java 貌似不可用 ttc 字体,从 8.04 拷来 wqy-zenhei.ttf 到 /usr/share/fonts/truetype/wqy 目录下可解决,wqy-zenhei.ttf 也可以从这里下载。

16. 在另一台笔记本 Dell D630 上安装 9.04 正式版时,发现居然默认不支持 NVS135M 视频驱动,需要配置好网络后,使用命令 sudo apt-get install nvidia-glx-180 安装 Nvidia 180 版本的驱动,或者自己到 NV 网站上下载最新驱动。

应用程序打包技术之四(exe篇)

1. 应用程序打包技术之一(源代码篇)
2. 应用程序打包技术之二(deb篇)
3. 应用程序打包技术之三(rpm 篇)
4. 应用程序打包技术之四(exe篇)

exe 是 Windows 下通用应用程序的后缀,因此也是 Windows 下安装程序的后缀,我就以 exe 篇来命名这篇文章吧。

我们在 Windows 下写程序,尤其是带有图形界面的应用程序,往往不仅需要一个可执行的 exe 文件,还需要一些辅助的 dll 或者资源文件。一般情况下,我们可以采用打包整个目录的方式来发布软件(也是目前很多所谓“绿色软件”的做法),当然我们也可以发布传统的 Windows 安装文件。这样用户不需要额外的解压缩软件,双击执行安装文件就可以安装软件。

HM NIS Edit

将 Win 下应用程序打包成安装文件的方法有很多,这里我们主要介绍一个开源的安装文件制作工具 NSIS(Nullsoft Scriptable Install System)。比如我们耳熟能详的软件 WiNamp 和 eMule 的安装文件,就是使用 NSIS 生成的。它的主页在http://nsis.sourceforge.net,您可以从它的主页下载到最新版本。

NSIS 是一个基于脚本的安装文件制作工具,因此我们必须写好一个脚本提供给它执行。总是要学一些“微语言”真是麻烦的事情,幸好我们还有 HM NIS Edit,一个开源的 NSIS 脚本编辑工具。使用 HM NIS Edit,我们可以一步一步地按照向导生成 NSIS 所需的脚本。因此,我们这篇文章其实主要是介绍 HM NIS Edit 的使用,和 NSIS 的部分脚本格式。使用 NSIS 脚本生成安装文件那一步,实在是太简单不过了,点一下鼠标或者敲一个命令而已。

假设我们要为 foo 程序生成一个安装文件,在打包之前您应该将 foo 的可执行程序、所需 dll 和资源文件等放在一个文件夹 foo 中。比如假设 foo 程序的目录是这样的。

D:>tree /F foo
D:FOO
│  foo.dll
│  foo.exe
│  licence.txt
│
└─image
        foo.ico

下面我们就可以使用 HM NIS Edit 来创建 NSIS 脚本了。启动 HM NIS Edit,在 File 菜单中选择 New script from wizard 脚本生成向导,点击 Next 下一步,进入程序信息页。

Application name 程序名这里我们填入要打包的程序名 "foo";Application version 程序版本我们填入 foo 的版本,比如 "1.0";Application publisher 发布者我们填程序的开发公司名或者自己的名字,比如 "张三";Application website 程序主页我们填程序的主页名,没有主页的就可以不填。然后点击下一步进入安装文件选项页。

Setup icon 安装图标是您希望您的安装文件长什么样子,而不是您应用程序的图标,一般选择默认即可;Setup file 是您希望安装文件叫什么名字,比如 "foo-1.0-setup.exe";Setup lang 安装程序语言是安装过程中的提示所使用的语言,您可以根据您的需要选择,比如简体中文 "SimpChinese";GUI 是安装文件的对话框风格,随便您喜欢哪种;Compress 压缩格式是您希望使用什么格式将应用程序压缩存放在安装文件中。然后点击下一步进入应用程序默认安装目录和协议页。

Application default directory 里面填您的应用程序默认安装到哪里,比如 $PROGRAMFILESfoo 是默认安装到 C:Program Filesfoo 目录下,最好勾选上 Allow user to change the application directory,允许用户更改安装目录,这样您的程序显得更人性化一点儿;License file 是指应用程序所使用的协议文本,如果您在 foo 目录下已经准备了协议文件 license.txt,那么直接填 licence.txt 即可。这个协议就是您通常在 Windows 下安装软件时,第一个页面提示的“是否同意上述协议”的“上述协议”文本框里的内容;下面几个选项是让用户选择如何接受协议。点击下一步,进入应用程序文件选择页。

在应用程序文件选择页中会有三个文本框。这个页面的作用是分组添加应用程序所需要的程序文件,这样用户安装时就可以通过选择“最小安装”、“完全安装”、“自定义”等选择安装不同的组件。左上方的文本框是组件框,右下方的文本框是组件信息说明框,右方最大的文本框是每个组件所包含的可执行、dll 和资源文件。如果我们的程序很简单,不用分什么组件,我们就只用一个 MainSection 就行了。点中左上方文本框中的 MainSection,在右侧将所有程序文件添加进去。由于我们已经将所有文件都放置在了 D:foo 目录下,我们只需要点选第二个图标:Add directory tree,在对话框中将源目录选择为 F:Moviefoo,目标目录选择为 $INSTDIR,这样 foo 下所有的文件和目录都将会被安装到 $INSTDIR(默认是 C:Program Filesfoo)目录下。确定之后返回文件选择页,点击下一步进入应用程序图标页。

应用程序图标页的主要作用是选择将会被安装到“桌面”和“开始”菜单的快捷方式指向的可执行程序。如果您的程序名和项目名一样,或者 foo 目录下只有一个 exe 可执行文件,此处就使用默认设置即可。Create an Internet shortcut in the Start Menu folder 的意思是在“开始”菜单中添加一个到软件主页的快捷方式;Create an Uninstall shortcut in the Start Menu folder 的意思是在“开始”菜单中添加一个到卸载程序快捷方式。点击下一步进入安装后执行设置页。

安装后执行的意思是当安装程序安装完成后,用户选择安装后直接启动应用程序或者查看自述文件时,程序的行为。如果您有自述文件,就在 Readme 中填入自述文件的名字,比如 readme.txt,如果没有,就什么也不填,直接进入下一步程序卸载选项。

如果您选择了使用卸载程序 Use uninstaller,NSIS 将会为您自动生成一个卸载程序,其选项使用默认即可。点击下一步进入结束页。

最后结束时,HM NIS Edit 会询问您是否保存脚本。当然要保存了,保存了以后再需要生成安装文件时就不必使用 HM NIS Edit 重新生成脚本了。Convert file paths to relative paths 将脚本中的文件路径修改成相对于脚本文件的路径,这个选项也最好选上,这样在更改 foo 的目录时,我们只需要 NSIS 脚本与 foo 的相对位置不变就不影响脚本的使用。接下来保存脚本文件,最好将脚本文件保存在 foo 目录下,这样以后需要重新生成安装文件,只需要将 NSIS 拷贝到 foo 目录下就可以编译了。比如取名为 foo.nsi。

这样,整个脚本文件我们已经编写好了。现在我们到 D:foo 目录下,就能发现一个 foo.nsi 文件,右键点击 foo.nsi,在下拉菜单中选择 Compile NSIS script,不出错的话,就能在当前目录下生成一个名为 foo-1.0-setup.exe 安装文件了。您可以双击执行一下它,看看安装过程是否如您所料。

我们也可以使用命令行编译 NSIS 脚本,您可以使用这个命令:

C:Program FilesNSISmakensis.exe foo.nsi

如果您将 C:Program FilesNSIS 添加到了 PATH 环境变量中,就可以直接使用 makensis.exe foo.nsi 来编译了。

小技巧:

1. 当生成 NSIS 脚本之后,我们想修改设置,不需要重新执行一遍脚本生成向导。只需要用文本编辑器打开 foo.nsi,找到相应的域,更改设置即可。

2. NSIS 是一个相当强大的安装文件生成器,但是使用 HM NIS Edit 脚本生成向导生成的脚本并不具有很灵活的定制性。如果您需要更多特性,请阅读 NSIS 用户手册,您能从网上搜索到该手册的中文版本。然后直接去修改 NSIS 脚本。

3. 用 NSIS 产生的卸载程序有可能会产生卸载不干净的现象,主要原因是 NSIS 卸载程序不支持递归删除目录。如果您想要它把所有文件和目录都删除的话,就需要在 Section Uninstall 中将所有程序可能会生成的文件和目录都添加进去,这样生成的卸载程序就能卸载全部文件和目录了。

4. 您可以在这里找到更漂亮的图文教程。

应用程序打包技术之三(rpm 篇)

1. 应用程序打包技术之一(源代码篇)
2. 应用程序打包技术之二(deb篇)
3. 应用程序打包技术之三(rpm 篇)
4. 应用程序打包技术之四(exe篇)

rpm 是 RedHat 系 Linux 使用的软件包格式。流行的 Linux 发行版:Fedora, RHEL, OpenSUSE, Oracle 包括国产的红旗 Linux,都采用 rpm 来管理软件包。

我不是很喜欢 rpm 软件包格式,原因主要有两个,一个是它的依赖关系很难处理,另一个是控制文件比较复杂。但是 rpm 包有着非常广泛的应用,也是一个提高生产力的重要工具。

像前一篇文章提到的一样,checkinstall 也可以用来打 rpm 包,但我不是很熟悉这个软件。这里我仅仅介绍如何使用原始的 rpm(rpmbuild) 工具来打 rpm 软件包。

和前面 deb 包使用 fakeroot 一样,rpmbuild 也是利用沙盒的方式来构建软件包,除了控制文件比较繁琐,过程可能还更自动一些。构建 rpm 包的工作目录默认是 /usr/src/redhat,如果您在 Debian 下安装了 rpm 这个软件包,它可能是 /usr/src/rpm。您可以使用 rpm --eval %_topdir 查看自己的 rpm 工作目录。

$ tree `rpm --eval %_topdir`
/usr/src/redhat
|-- BUILD
|-- RPMS
|   |-- athlon
|   |-- i386
|   |-- i486
|   |-- i586
|   |-- i686
|   `-- noarch
|-- SOURCES
|-- SPECS
`-- SRPMS

我们可以看到,/usr/src/redhat/ 下有几个子目录:SOURCES 用来存放源代码包,SPECS 用来存放 spec 控制文件,BUILD 用来解压源代码包和构建软件,RPMS 里存放的是打好的二进制应用程序 RPM 包,SRPMS 里存放的是打好的源代码 RPM 包。但是,我们应该知道 _topdir 是可以在 spec 文件中修改的,否则我们只能用 root 才能在默认的工作目录 /usr/src/redhat 下创建文件和执行命令。我们可以先把工作目录树拷贝到用户自己的目录中:

$ cp -r /usr/src/redhat ~/rpm

这样,我们就可以在用户目录 ~/rpm 下打 rpm 包了。打 rpm 包之前的准备工作只有两件:1. 准备好源代码包并放置在 SOURCES 目录下;2. 准备好 spec 控制文件并放在 SPECS 目录下。如何打源代码包我们已经在 《源代码篇》中介绍过,下面我们主要来介绍 spec 文件的格式。下面是一个实例 spec 文件。

$ more ~/rpm/SPECS/casnet.spec
%define   _topdir    /home/solrex/rpm
Name:     casnet
Version:  1.3
Release:  1
License:  GPL
Packager: Solrex Yang
Summary:  CASNET Client
Group:    Network
Source:   %{name}-%{version}.tar.gz
URL:      http://share.solrex.org/casnet/
Prefix:   /usr

%description
CASNET is a gui client for ip gateway of GUCAS(Graduate University of Chinese
Academy of Sciences), which is written in Python and PyGtk.

%prep
%setup -q

%build

%install
make -e PREFIX=%{prefix} install

%files
%{prefix}/bin/casnetconf
%{prefix}/bin/casnet
%{prefix}/bin/casnet-gui
%{prefix}/share/casnet/casnetconf.py
%{prefix}/share/casnet/casnet.py
%{prefix}/share/casnet/casnet-gui.py
%{prefix}/share/casnet/pics/*.png
%{prefix}/share/applications/casnet.desktop
%{prefix}/share/icons/casnet.png

我们可以看到,spec 文件与 deb 包的 control 文件有很多相似之处:Name, Version, Release, License, Packager, URL 是软件的名称、版本、小版本、使用的协议、维护者和网址;Summary 和 %description 是软件的信息;Group 是软件所归类别。剩下的就有些不同了:Source 是指软件的源代码包名称,rpm 会到 SOURCES 目录下找这个包;Prefix 是软件的默认安装位置;%prep、%build、%install 下分别是软件的预处理、编译和安装命令;%files 是所有需要被打包进去的文件。

因此,作者需要将 spec 文件中的各个域填写正确。%prep 下的 %setup 宏一般是用来将源代码 tar 包释放到 BUILD 目录下;%build 下可以添加 %configure 宏和 make 命令,如果您的软件需要编译的话;%install 下是您的安装命令;%files 下是您程序运行所需要的全部文件,其路径即为您安装完软件后它应该在的位置。%{name} 可以用来指代 Name: 域的内容,%{version } 指代 Version: 域的内容,以此类推。

需要注意的是,rpmbuild 使用 BUILD/%{name}-%{version} 作为您的源代码包释放后的目录,进入其进行编译。因此当您使用 tar 打源代码包时,tar 包的顶层目录名应该为 %{name}-%{version} 而不是仅仅是 %{name},比如:

$ tar czvf casnet-1.3.tar.gz casnet-1.3
$ mv casnet-1.3.tar.gz ~/rpm/SOURCES

现在我们在 ~/rpm/SOURCES 下有了 casnet-1.3.tar.gz,在 ~/rpm/SPECS 目录下有了 casnet.spec,那么我们就可以使用下面的命令生成 rpm 包了。如果您的安装位置 Prefix 选择的是一个系统目录,比如 /usr, /usr/local 之类,您也许需要使用 root 权限来运行 rpmbuild 命令。

$ rpmbuild -ba ~/rpm/SPECS/casnet.spec

这样我们就能在 ~/rpm/RPMS/i386 目录下获得我们生成的 rpm 软件包了。rpmbuild 命令的执行过程是这样的:首先根据 spec 文件定位源代码包,然后将源代码包释放到 BUILD 目录下,使用 spec 文件中给出的命令编译和安装,然后将 spec 中列的文件提取出来,按照包信息打出来 rpm 包。

我们可以使用 rpm -qpi 命令来查看 rpm 包的信息,rpm -qpl 命令来查看 rpm 包所包含的文件列表:

$ rpm -qpi ~/rpm/RPMS/i386/casnet-1.3-1.i386.rpm
Name        : casnet                       Relocations: /usr
Version     : 1.3                               Vendor: (none)
Release     : 1                             Build Date: Thu 26 Feb 2009 08:03:50 PM CST
Install Date: (not installed)               Build Host: laptop
Group       : Network                       Source RPM: casnet-1.3-1.src.rpm
Size        : 40883                            License: GPL
Signature   : (none)
Packager    : Solrex Yang
URL         : http://share.solrex.org/casnet/
Summary     : CASNET Client
Description :
CASNET is a gui client for ip gateway of GUCAS(Graduate University of Chinese
Academy of Sciences), which is written in Python and PyGtk.

$ rpm -qpl ~/rpm/RPMS/i386/casnet-1.3-1.i386.rpm
/usr/bin/casnet
/usr/bin/casnet-gui
/usr/bin/casnetconf
/usr/share/applications/casnet.desktop
/usr/share/casnet/casnet-gui.py
/usr/share/casnet/casnet.py
/usr/share/casnet/casnetconf.py
/usr/share/casnet/pics/casnet.png
/usr/share/casnet/pics/offline.png
/usr/share/casnet/pics/online.png
/usr/share/icons/casnet.png

如果您想了解更详细的内容,您可以进一步参考 Jake's RPM Build Tutorial 这篇文章。

应用程序打包技术之二(deb篇)

1. 应用程序打包技术之一(源代码篇)
2. 应用程序打包技术之二(deb篇)
3. 应用程序打包技术之三(rpm 篇)
4. 应用程序打包技术之四(exe篇)

deb 是 Debian 系 Linux 使用的软件包格式,也是我最欣赏的软件包格式。我所知道的打 deb 软件包的方法有两种,一种是使用 checkinstall,另一种是使用 dpkg。

checkinstall 不仅仅可以用来打 deb 包,还可以打 rpm 和 tgz 包,而且使用方法相对简单。但是 checkinstall 的运行不是那么稳定,我搞不懂它在什么情况下才能正常运行,而且它的定制性不是很强,使用时老是要交互地输入些信息,所以我还是放弃了使用它来打包软件。感兴趣的朋友可以在网上搜索一下这个程序的使用方法。

dpkg 是 Debian 的“原生”包管理软件,但是很多人不太愿意使用 dpkg 来打包 deb。究其原因可能是需要写麻烦的配置文件,但是写配置文件的一个好处就是在下次打包时候可以直接用上次的配置文件,只修改一个版本号就可以了,而不用每次都需要填包信息。在介绍如何打 deb 包之前,我们现看一下如何解 deb 包。

$ sudo apt-get install tree
$ dpkg -X /var/cache/apt/archives/tree_1.5.1.1-1_i386.deb fakeroot
$ cd fakeroot
$ dpkg -e /var/cache/apt/archives/tree_1.5.1.1-1_i386.deb
$ tree
.
|-- DEBIAN
|   |-- control
|   `-- md5sums
`-- usr
    |-- bin
    |   `-- tree
    `-- share
        |-- doc
        |   `-- tree
        |       |-- README
        |       |-- changelog.Debian.gz
        |       |-- changelog.gz
        |       `-- copyright
        `-- man
            `-- man1
                `-- tree.1.gz

dpkg -X 是将 deb 包的内容文件释放出来,dpkg -e 是将 deb 包的控制信息释放出来。前面执行那个 sudo apt-get install tree 是为了将 tree_1.5.1.1-1_i386.deb 下载到本地 apt cache,如果您已经安装过 tree 这个软件,可以为 apt-get 加上 -d 参数,使其只下载而不安装。

从上面 tree 命令的执行结果我们发现,deb 包解开后分两部分:一部分是控制信息,在 DEBIAN 目录下;一部分是安装内容,在 usr 目录下。现在您大概明白为什么我们使用 fakeroot 作为目录名了,因为这个目录就是一个"假根目录",您在这个目录下所有的修改,最后都会被映射到目标机的根目录 / 下。比如 fakeroot/usr/bin/tree 这个文件,就会被安装到 /usr/bin 下,以此类推。

只要您能理解 fakeroot 这个目录映射,您就知道如何安放自己的文件了。为了让生成的包将文件 foo 安装到目录 /usr/xx/yy 目录下,您只用在 fakeroot 目录下建立 usr/xx/yy 目录,并将 foo 拷贝进去就行了。

好,下面进入关键的配置文件部分,关于 control 和 md5sums。

$ more DEBIAN/control
Package: tree
Version: 1.5.1.1-1
Architecture: i386
Maintainer: Ubuntu MOTU Developers
Original-Maintainer: Florian Ernst
Installed-Size: 92
Depends: libc6 (>= 2.6-1)
Section: utils
Priority: optional
Description: displays directory tree, in color
Displays an indented directory tree, using the same color assignments as
ls, via the LS_COLORS environment variable.
.
Homepage: http://mama.indstate.edu/users/ice/tree/

我们可以看到,control 文件中包含的主要是软件的版本和维护者信息,我相信大家都能基本看懂上面这些信息什么意思:Package 包名(tree)、Version 版本(1.5.1.1-1)、Architecture 目标机架构(i386 386及以后)、Maintainer 维护者(Ubuntu MOTU Developers)、Original-Maintainer 原维护者(Florian Ernst)、Installed-Size 安装后大小(92K)、Depends 依赖软件包(libc6 不低于 2.6-1 版本)、Section 包分类(工具)、Priority 优先级(可选)、Description 包描述、Homepage 软件主页。

由于咱们分析这个包是 Ubuntu 发布的包,所以包信息给的比较全,其实并不是上面所有的信息都有必要提供(小声说一句,就算全提供也不是很难吧?除了咱不用的,Original-Maintainer 这种就算了)。关于哪些信息比较重要,以及每个域的具体含义和可选项,可以参考 Debian 的文档 Debian Policy Manual Chapter 5 - Control files and their fields

您也可以依样画葫芦,写一个类似的 control 文件放到 DEBIAN 目录下,提供一些自己软件包的信息,基本有这个配置文件就可以打包了。

$ more DEBIAN/md5sums
d60a3b4736f761dd1108cb89e58b9d4e usr/bin/tree
981ea0343c2a3eb37d5fc8b5ac5562df usr/share/man/man1/tree.1.gz
483a56158a07a730ec60fc36b3f81282 usr/share/doc/tree/README
ea56d78ae0d54693ae8f3c0908deeeff usr/share/doc/tree/copyright
4456e04c3c268eabcd10ee9b949a9b9a usr/share/doc/tree/changelog.gz
ec104db6914cfce2865a0d8c421512bb usr/share/doc/tree/changelog.Debian.gz

md5sums,这文件名一看,就知道是保存着软件包中各文件的 md5 校验值,用来校验软件包是否被损坏了。其实这个文件纯属“腊月三十逮兔子,有它没它都过年”,您可以完全不提供它。

这样呢,我们就准备好了 deb 包的内容文件和控制信息:控制文件放在了 fakeroot/DEBIAN 目录下,内容文件放在 fakeroot/usr 下,目录树就像开头 tree 命令的结果。下面只需要一个命令就能打出来 deb 安装包了:

$ cd ..
$ dpkg -b fakeroot/ foo.deb

这时候当前目录下就出现了 foo.deb。您可以使用 dpkg -I foo.deb 查看 foo.deb 的控制信息,dpkg -c foo.deb 查看 foo.deb 包含了什么文件,sudo dpkg -i 安装 foo.deb。

小技巧:

1. 如果您懒得自己新建一个控制文件和目录树,您完全可以像本文开头那样,找一个简单的软件包,将它的内容和控制信息释放出来,对它进行修改,然后打出来自己的包。

2. 生成 md5sums 文件不是什么难事,您只需要在 fakeroot 目录使用下面这个命令:

$ md5sum `find usr -type f` > DEBIAN/md5sums
或者
$ find usr/ -type f -exec md5sum {} + > DEBIAN/md5sums

3. 将您的可执行文件拷贝到 fakeroot/usr 下并不一定要手动一个个拷。如果您使用 GNU 自动工具集,./configure 时加个参数 --prefix=fakeroot/usr/ 即可;如果您自己写的 Makefile,可以在 Makefile 中使用一个变量 PREFIX=/usr,当您不加参数时,make install 的安装目标就是 /usr 下,您可以使用 Makefile -e PREFIX=fakeroot/usr/ install 来覆盖 Makefile 中的变量设置。

应用程序打包技术之一(源代码篇)

1. 应用程序打包技术之一(源代码篇)
2. 应用程序打包技术之二(deb篇)
3. 应用程序打包技术之三(rpm 篇)
4. 应用程序打包技术之四(exe篇)

相信很多朋友都曾经为方便做某件事写过自己的小程序(像我写过的 casnetsendsms),但很多怕都是藏在深山没人识,最后不了了之,自己也把它们丢在角落里忘记了。

把这些小工具上传到技术论坛或者 CSDN 下载频道之类的网站,还是能收到一些关注的,而且还能攒积分和声望。但是为什么不把它们发布出去呢?估计有几个原因:源代码太乱,编译又挺复杂,不好意思给别人看;二进制程序包不会打,不知道该怎么发布。

因此我打算在本系列文章中分享一下我的程序打包经验,目前来讲计划有四篇:源代码篇、deb 篇、rpm 篇和 exe 篇。这些技术在网上您都能找到,因为我也是从网上学习的,算是一个笔记和汇总吧。如果您有好的批评或建议,不妨在评论中指出,帮助我完善这篇文章。

源代码篇

发行源代码包是件最简单的事情,因此也在最先介绍。有同学会说,打个压缩包不就完了嘛。的确如此,但是在压缩之前您也要有几个注意事项:

1. 删除版本管理目录,比如 .svn,.cvs 之类的目录。像 Subversion 版本管理软件,会在每个目录下都建立名为 .svn 的目录,里面一般保存着目录下文件的最新版本,可以用来 revert 修改。不删除 .svn 目录,会使源代码包臃肿,而且最重要的是可能会有安全隐患。.svn 目录下还包含您的用户名和 SVN 服务器信息,可能您并不想让别人知道。但是如果您网速够快的话,可以重新 svn export 一份,而不是仅仅从您的 svn 树上拷贝一份出来,那就没有 .svn 目录了。

2. 规整一下编译过程,如果编译过程不规范,您应该添加一个 README。如果您的代码不是脚本,很可能是需要用户编译的。如果编译过程规范,*nix(Linux/Unix, CygWin, Mingw 等) 下就是 ./configure, make, make install,用户很容易理解。但如果编译过程不规范,您就最好添加一下 README 或者 INSTALL 文件,指导用户该如何编译。使用 MS VisualStudio 的用户要注意工程文件的整洁性,最好导出一个 Makefile(是的,VS 也可以用 Makefile),这样用户就不必打开项目,在 CMD 命令行用 nmake Makefile 就可以编译了。

3. 删除二进制中间文件。在 *nix 开发者中,这一般不难做到,Makefile 中一般都会写一个 clean 目标;但是 MS VS 用户一般不会注意那些编译时生成的 .obj 文件。源代码包就应该是源代码,最多包含可执行程序和文档,而不应该包含其它任何二进制的文件。否则您的源代码包就会很大,而且对用户也是困扰,到底哪些文件有用呢?

4. 修改编译目标从 debug 版本到 release 版本。*nix 下,这一般意味着 CFLAGS 要改成 -O2 而不是 -g;VS 一般意味着将目标从 debug 转为 release。这样用户生成的可执行程序才能足够小和足够快,他们如果有能力自己调试,会知道如何将选项改回去的。

5. 添加知识产权信息,就是授权协议。小程序大家一般都不在乎,但如果是您在这个项目上花了足够的心血,还是最好选择一个自己喜欢的授权协议。可以将协议声明放在每个源文件的最前注释中,也可以在项目的根目录下放一个 license 文件。

6. 不要用 rar 包。在 Windows 下推荐使用 zip 格式压缩;*nix 下推荐使用 .tar.gz 或者 .tar.bz2 格式。因为这些格式在这些系统上不需要安装额外的软件解压。WinRAR 是一个商业软件,而且 RAR 格式也是受版权保护的。

打包命令:

在 Windows 下,如果您使用开源软件 7-zip 来压缩 zip 包,可以使用这个命令(首先将 7-zip 可执行文件目录添加到 PATH 环境变量中):

7z a foo.zip foo

*nix 下,就是下面几个命令了:

tar czvf foo.tar.gz foo
tar cjvf foo.tar.bz2 foo
zip -r foo.zip foo

刻印记

身为一个中国人,稍微对咱老祖宗的东西有那么点儿爱好的,怕都曾经念想过拥有一方刻着自己大名的篆书小印吧——反正我是想了好多年了。正好趁着今年情人节,礼物不知道选什么好,干脆刻一双对章,一方送女友,一方自己留着,也蛮有意义的。用某国新闻社通稿的套话就是:“在散发着西方腐朽浪漫主义气味的节日里,送上充满浓郁民族特色的小礼物,增添了有中国特色的西方节日的喜庆气氛,巩固和发展了和谐稳定的恋爱良好局面,为未来步入婚姻的坟墓打下了良好的基础!”

咱是一个土人,本来对印章没啥了解。见过的那些个木头的、牛角的、胶皮的印章,字字都是中规中矩,跟电脑字体似的。以为现在的印章都是用仪器刻的,因此还担心这样的印章没啥个性。在网上乱搜一通下来才发现,那些有机合成的印章的确都是机器刻字,但是石头的印章还主要是手工篆刻,也比较有收藏价值。

但是咱对石头就更不懂了。书本上学得挺好,石英、云母、长石等等乱七八糟的东西,用到时基本瞎眼。我在淘宝上徘徊了一天,也没搞明白石头应该是啥样的好,只是晓得了只有软的石头才适合刻印章,有名的印石有寿山石、巴林石、青田石、昌化石。至于这些石头之间有啥区别或者如何识别真品,一点儿也没弄明白。无奈中,只好找家店去逛逛了。

没想到学校南面就有家工艺美术市场,叫做玉都雅风工美市场,里面就有几家经营印石的店。有两家看起来经营的是高档印石,好多鸡血石往那一摆就让人望而却步了。转了一圈找到一家叫做雅子世界的店,各种价位的印石基本上都有。把从几十到一百多的对章看了一遍,价格貌似还比较靠谱。翡翠看起来还是比一般石头好看,但因为硬度高,刻字费却要翻一倍,普通印石十块钱一个字,翡翠要二十。犹豫了一下,决定先回来查查该店的口碑再说。

顾客的评价倒是没查到,只查到了店主在新浪上的一个博客。看看博客文字,觉得还是蛮厚道的一个人,再加上服务的态度也很好,就决定还是在这家店买吧。带雕钮的印石一般都比较贵一些,所以挑来挑去,看中了一对比较素的巴林石对章。店主给当场刻字,于是选了小篆字体,分别在两方印上刻上了我俩的名字,边款题了两句纪念的话。

其实刻完了还是发现有些小小遗憾的。石头比较难挑,选中的石头角上有一点点裂,以为没有大关系。刻出来才发现,刻刀肯定会让裂变得更明显的,不过只是看得出来,用手还是摸不出来。看来选石头还是尽量选那些完全无裂的。不过穷人送礼,又想有新意,又没那么多预算,只能尽力而为了。要是时间足够的话,应该多去些印石市场或者网上商店淘一淘石头的,说不定就能找到既便宜又好的。

另外,我收到的情人节礼物是一套傅译的《约翰·克利斯朵夫》,我很喜欢,非常感谢希希同学 ^_^

新购《少有人走的路》等五本书

其实本想只买《少有人走的路》一本书,但卓越网购书总价 49 元以上才给免配送费,于是就在卓越的特价店中淘了一批其它好书,均价大致在 5 折左右。后来发现当当网免运费,书价也差不多,就有点儿后悔了 :)

2 月 7 号下的订单,一直没有消息。昨天下楼偶然看到有卓越的送货人员,就上前询问,才知道卓越只给了快递公司座机号码,没有把我手机号打印在订单上,所以他们一直无法联系到我。幸好我问了一下,才拿到了书。装在一个纸箱子里,里面还有起泡塑料纸垫着。

经常在网上买书,比较蔚蓝、ChinaPub、当当和卓越,觉得在卓越网买书,包装和发票是最好的,都很正规。而且配送也比较好,尤其当时住中关村时,是卓越自己的人配送,还会带着 POS 机,付款可以直接刷卡。蔚蓝和卓越给我的印象最深,在蔚蓝网买书,总是隔一天到,期望比较大,方差比较小;在卓越网买书,一般第二天就能到,但是也很容易碰到好几天到不了的,期望比较小,方差比较大。

卓越的特价店经常有一些好东西,买书凑不够免运费的时候我就去特价店逛逛,经常有一些人文类的特价书,一般在 5 折左右。上次就在特价店以 8 元左右的价钱买了几本王小波的书,还没看完呢。这次买的书,包括《少有人走的路》,也都在特价店里面。

《少有人走的路》,M.Scott.Peck,于海生译,吉林文史出版社。这本书曾经被我的两个师兄推荐过。今天早晨翻了一下,的确是一本好书,看得我很汗颜。这种书大概需要多读几遍的。还有一本《少有人走的路2》,据说是伪书,豆瓣上的评论都很差,就不买了罢。

《人间词话手稿本全编》,王国维,内蒙古人民出版社。名声很响,大师的作品,因之很仰慕。

《中国人史纲》,柏杨,山西人民出版社。想读一读不同风格的中国史。

《悲惨世界》精装本,雨果,潘丽珍译,译林。觉得面相还不错,买来收藏。

《第二十二条军规》精装本,约瑟夫·海勒,扬恝译,译林。早有耳闻,顺道买了。

GUCAS IP 网关登录客户端 1.3 发布

如果您不知道这软件是干嘛的,那您就不用往下看了。这个软件是中科院研究生院师生使用的,更新公告发表在这里只是为了记录一下版本发布历史。

软件主页:http://share.solrex.org/casnet

最新版本 1.3-1(2009年2月11日发布) 更新

  1. 增加了单击更换登录模式功能。
  2. 增加了自动断线充连功能。
  3. 增加了余额不足提醒功能。
  4. 解决了以前版本的一些 BUG。

尽量别使用 Py2exe for Python 2.6

Py2exe 是用来将 Python 程序打包成 Windows 下可执行 exe 程序的工具。这样那些未安装 Python 开发环境的用户就可以直接使用 Python 写的软件了。

Py2exe 并不是把 Python 程序编译成 Windows 的原生程序,而是将运行 Python 所需的 dll, lib 等打包到一起供 Python 程序使用。一个很短的 Python 程序往往会生成几兆的软件包。因此 Py2exe 打包程序的执行效率并不会有提升,只是方便初级 Windows 用户使用罢了。

除了 Py2exe 之外,还有一些其它的 Python 到 exe 的打包程序,比如 Pyinstaller、cx_Freeze 等。它们在某些情况下表现比 Py2exe 要好,但是在兼容性和用户群支持上不如 Py2exe(一家之见)。

前两天我图新鲜,把 Windows 中的 Python 升级到了 2.6,PyGtk 和 Py2exe 也随之升级到了支持 2.6 的版本。然后问题就来了,用 Py2exe for Python 2.6 打包的 PyGtk 程序在打包的机器上运行正常,但拷贝到部分人的 Windows 中后却无法运行,点击就出现

“由于应用程序配置不正确,应用程序未能启动。重新安装应用程序可能会纠正这个问题。”

刚开始我以为是 Win 下 GTK 库 dll 的问题,反复地验证几次觉得应该问题不在 GTK 上。如果缺少外部库,Python 应该报缺少 dll 问题,而不是应用程序出错。怀疑是缺少微软的库,用 PE Explorer 试用版在出问题的电脑上扫描一下应用程序的依赖关系,发现缺少 msvcr90.dll,将 msvcr90.dll 拷贝到程序包中,再扫描依赖关系,所有的 dll 已经都满足了,仍然报出同样的错误。

后来基本确定是 Python 2.6 的问题。因为 Python 2.6 是使用 Microsoft Visual C++ 2008 编译的,所以要想 py2exe for 2.6 打包的程序运行,目标机器上必须装有 Python 2.6 或者 Microsoft Visual C++ 2008 Redistributable Package。否则系统就无法识别 exe 程序的 CRT, 因而它就成为无法运行的程序。

之所以程序在一部分人的机器上运行正常,是因为这些人 Windows 中安装了 VC2008 开发套件,自然也就包括了 VC2008 运行时库。

因为我们发布程序时无法强制每个人都去安装 Microsoft Visual C++ 2008 Redistributable Package,所以需要发布 exe 程序时,还是使用老版本的 Python 2.5 和 Py2exe for Python 2.5,别使用 Python 2.6 为好。

Python 不支持杀死子线程

昨天为我的 casnet 程序添加新功能。其中一个功能是断线自动重连,本来是单线程的程序,添加这个功能就需要后台有一个线程定时地查询当前状态,如果掉线就自动重连。因之遇到了一个如何设计这个守护线程的问题。

我刚开始的想法是后台线程每次运行查询后 sleep 一段时间,然后再运行查询。但是我马上遇到了一个问题:当主程序退出时,后台线程仍在运行,主窗口无法退出。

在使用其它的库时,比如 POSIX 的 pthread,可以使用 ptread_cancel(tid) 在主线程中结束子线程。但是 Python 的线程库不支持这样做,理由是我们不应该强制地结束一个线程,这样会带来很多隐患,应该让该线程自己结束自己。所以在 Python 中,推荐的一种方法是在子线程中循环判断一个标志位,在主线程中改变该标志位,子线程读到标志位改变,就结束自己。

import threading

class X(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    self.flag = 1

  def run(self):
    while self.flag == 1:
      sleep(300)
      ...

如果直接使用这种方法,那么我前面的设计就会出现问题。因为线程会被 sleep 阻塞一段时间,那么只有在 sleep 的间隙,才有可能去读取标志位。这样主线程需要等待当前 sleep 结束才能使子线程退出,进而整个程序才能退出。这种做法是行不通的,你不可能指望用户点击“关闭窗口”后等待几百秒程序才能退出。

当然,也可以使用系统命令 kill 来杀死整个进程。但问题是这样做既不 graceful,又不能保证代码对不同系统的兼容性。

只好换个思路,从原来后台进程的设计改起。定时执行未必非得使用 sleep,也可以像 crontab 那样判断当前时间能不能整除某个值,但这样做不能保证任务在某个时间间隔内只执行一次,因为除数的精度和任务的执行时间不好把握;或者使用 timer,但是 timer 会带来更多线程,增加了复杂度。

于是最后决定使用解决 Feedbuner 图标定时抓取问题的方法。在线程中保存上次查询时间,比较当前时间与上次查询时间的差,若大于某个值,就进行查询并更新保存的时间。

  def run(self):
    self.last = time.time()
    while self.flag == 1:
      Now = time.time()
      if Now - self.last > 300:
         self.last = Now
         ...

这样就既能保证子线程在 flag 改变之后尽快退出,又能保证在指定时间间隔内任务只运行一次。但是网友 earthengine 兄指出这种方法并不妥,代码中不用 sleep 就变成了忙循环,这样会造成 CPU 使用率过高的问题,仅仅在循环中间添加一个 sleep(0~1) 就能大幅度地降低 CPU 使用,而且关闭程序时 1 秒钟以内的延迟对于用户来说一般还是可以接受的。

  def run(self):
    self.last = time.time()
    while self.flag == 1:
      sleep(1)
      Now = time.time()
      if Now - self.last > 300:
         self.last = Now
         ...

再深入思考一下,虽然本文中的后台线程从功能上来看似乎用不着考虑太多同步的问题,但最后的退出过程可视为一个线程同步的过程。因此可以采用线程同步的思想来设计后台线程:在正常工作时,后台线程进行带超时的等待,超时后就执行工作;退出时主线程给后台线程发送一个信号,由于后台线程在超时等待,因此接收信号后就终止退出。这样,在用户结束程序时,就不用等待 sleep 到时了。

import threading

class X(threading.Thread):
  def __init__(self):
    threading.Thread.__init__(self)
    self.flag = 1
    self.cond = threading.Condition()

  def run(self):
    self.cond.acquire()
    self.condition.wait(300)
    while self.flag == 1:
      ...
      self.cond.release()
      self.cond.acquire()
      self.condition.wait(300)

...
x.flag = 0
x.cond.acquire()
x.cond.notify()
x.cond.release()

最后,非常感谢 earthengine 兄的精彩评论,小弟受益良多。

用 Firefox 插件控制网络沉迷

最近好几个朋友都加入了一个叫做“你为什么不关掉电脑去做爱做的事”的豆瓣小组,当然,我也加入了。

也许是大学时候养成的毛病,也许是因为现在网络是免费的,我虽然不会沉迷于游戏,但却经常在该工作的时候沉迷于网络,其中典型的包括小百合 BBS、Google Reader 等。最近我想控制一下这些浪费时间的行为,就把 bbs.nju.edu.cn, reader.google.com 等这些域名在 /etc/hosts(或 c:\windows\system32\drivers\etc\hosts)文件中写成回环地址 127.0.0.1,这样我一旦访问这些网站,就会转到我本机的服务器。

但直接写系统 hosts 文件会产生一个问题,它不仅影响浏览器,还会影响所有其它软件。某些情况下我是不希望这种事情发生的,而且每次都要手动修改策略,于是我找到了 Firefox 插件 LeechBlock。它的介绍是这样的:

LeechBlock is a simple productivity tool designed to block those time-wasting sites that can suck the life out of your working day. All you need to do is specify which sites to block and when to block them.

LeechBlock 可以设置六个策略集,策略集组成主要包括要屏蔽的网站、屏蔽策略和屏蔽时间。最狠+幽默的是可以设置 LeechBlock 为修改设置时要求输入一串随机产生的 64 位验证码,直接搞到你不想随便修改自己定下的策略集,比密码还有效。要是再长点儿,恐怕你就永远不想修改你的设置了。

虽然是否会浪费时间主要取决于自己的毅力,但是有一个软件能稍微起点儿帮助作用也是不错的。我发表完就把 blog.solrex.cn 添加到屏蔽列表中去。

最后再提醒诸君一句,在设置完策略集之前千万不要选择 Require the user to enter a random 64-character access code,切记切记!

警察哥哥,您这次真勤快!

也不知道我咋该着那么多灾多难。上次投诉完工行网银之后,今天刚回京,就过去看,旧网银是给销了,可注册新网银的时候,人家营业员小姐说:“您的身份证件信息和我们查询的不符,无法办理网银。”我凑近一看,人家系统中写着“身份证发证机关:北京市海淀分局”,TNND,老子什么时候在北京办过身份证呀?

我一直用的是南京市高新区分局给我办理的第二代公民身份证,到北京之后学校是说可以办北京身份证来着,我琢磨着反正有效期十年呢,我刚办了一年总不能就给换喽,就没办。没想到海淀分局的警察哥哥/姐姐那么勤快,没干的事儿也揽到自己头上,愣是说他给咱发证了。哥哥呀,我可没交办证费呀!

你说这叫我到哪儿说理去?

1月19日1303次回家

今年又被吓到了,春运人数同比增加 8%,天那。不是说经济不好,很多公司都提前放假了吗?

好几个人提醒我,一定要提前到火车站呀,进站要进很长时间的。恩,那就早点儿走好了。

还好在学校里订到了张有座票,可能好点儿。

该回家了,感觉就是不一样,心情轻松了一些。

博客暂停更新一段时间,也希望大家别成天在网上泡着了,跟家人、朋友一起多玩一玩。

祝大家都过个好年!牛年好运!

该投诉时一定要投诉

讲一些我遇到的和投诉有关的几个小故事,比较感慨。

1. 工商银行

上个月我的工行信用卡丢了,然后补办了一张,但是网上银行却出了问题。工行把我的老卡给销了,但是没有同时把网银销掉,导致不能以我的身份证号再注册新的网银。在柜台办理的时候,因为注册卡不存在销不掉老网银,又因为身份证已被使用无法注册新网银。

我在去年 12 月 15 日和 25 日跑了两次工行营业厅,没办法办理,营业员两次记下了我的信息和承诺有消息给我电话,但是一直都没有收到任何电话。我在今年 1 月 12 日再次去营业厅,得到的消息仍然和前两次一样。于是我决定不能这样傻乎乎地一趟趟往银行跑了,告诉营业员我要投诉。

一听说我要投诉,营业经理马上过来,给我解释无法办理也不是他们的错,然后给我读了上级信息部门的回复,说是系统的问题,要等工行的开发中心批量处理,但是仍然没有一个解决时间。

那我就不投诉网点了,投诉工行的网银部门好了。于是要过银行的 95588 电话,当着营业经理的面投诉工行的网银部门。工行的热线电话对投诉的处理还是相当之谨慎的,首先是希望能解决用户问题避免投诉,当知道解决不了之后,还要问清楚整个事情的经过,包括网点信息、接待柜员、接触时间、帐号信息、联系信息之类的。一通交流下来至少得二三十分钟,总之千万别用自己电话打 95588 投诉,要投诉就去银行自助区或者贵宾区打免费的 95588 专线电话,否则自己还得出电话费多亏呀。

你还甭说投诉还真有效果,之前他们也留了我的联系信息,问题得到了上级部门的回复也没有通知我。现在虽然问题还没有彻底解决,但今天下午就接到电话非常客气地告诉我正在处理,一有最新消息马上通知我。

2. 交通银行

去年,我在沃尔玛办了张交通银行的信用卡,后来发现还款太麻烦,就打算退掉。打了两次交行的电话,接线生均以各种理由告诉我退卡有多麻烦,要等多少天,要满足这个条件那个条件。第三次我发飙了,就在电话里冲接线生吼了一句:“我对你们的服务特别不满意,我要投诉。“结果第二天交行就给我打来电话说我的信用卡已经注销了。

3. 蔚蓝网

去年,我在蔚蓝网上买了几本教材,属于可以报销的范畴,于是就需要发票。送书过来的时候没有发票,快递员解释说最近发票用完了,过两天有发票了会再给送来,云云。但是等了一个星期也没见送来,我就直接打电话给蔚蓝的客服,客服也是给我这般解释,然后我就告诉她:“你们卖书是应该提供发票的,请你们尽快把发票给我送来,不然我会到有关部门投诉你们“,客服小姐就说,好我给您想想办法。第二天发票就送到我手上了。

很奇怪的是,之后我又买了几次书,发票都随书送到,但是我同时听到快递员给其他买书的同学解释:“发票用完了...”。难道蔚蓝网还维护着一个列表,首先满足潜在“危险”客户的需求?

这就是我跟几家商户打交道的过程。对于这些不真正把用户当回事儿的企业,遇到不平的事,不要和他们纠缠,直接找上级部门投诉可能效果更好些。总之要利用各种渠道维护自己的权利,有些东西看起来是摆设,但用起来才发现:嗬,还有点儿用处!

一本过时的黑客教科书

——评《良性入侵-道德黑客非官方指导》

如果说放在原书出版的 2002 年,本书还是有相当价值的。但是我不理解为什么 2007 年电子科技大学出版社还会去翻译一本过时的黑客技术书籍?

在计算机安全和攻击的领域,时效性非常的强。在 2007 年再读一本讲述怎么攻击 Windows 9x 的书显然是不合时宜的,而且文中提到的各种漏洞和攻击方法早就被新的补丁和协议所防护,或者已经有了新的攻击类型,那么对于当前读者的意义也就不大了。就算是仅仅用于研究,读者也很难找到一个有漏洞的原型系统来做实验。

非常值得诟病的还有本书的翻译,我不得不说,很难看到比这本书翻译得更烂的了,连文字都不通顺,随便举一个例子(第103页):

“(前面一段是代码)作为一个基本的规则,你需要等待更长时间,你会更安全。如果有三分钟的时延,显示,登出。TCP 端口扫描,像 Portscan.java 或者其他的 Windows 的端口扫描(我承认我不小心发现了一句语句重复的排版错误),像 Portscan.java 或者其他的 Windows 的端口扫描这样的 TCP 端口扫描程序将不会对主机的安全性构成威胁,主机将更加安全。”

我基本上第一次没看懂这一段是什么意思。另外还值得一提的是本书的代码量相当大,几乎能占到四成,代码量大不是问题,但是异常多的代码就让人怀疑是否用代码来充实书的内容了,会让读者觉得钱花得不值。(但也有一个可能是受美国法律的限制,不能出口一些敏感代码,只能把代码放到书里来逃避规定,一些密码学的书籍曾这样做过。)

再加上 80 元的售价,天那,给我 80 元我会买很多好书,但绝对不会是这一本。

窗外皓月当空

睁开眼睛,发现屋内亮亮堂堂,不像是夜晚,抬头一看,原来今天是满月。在被窝里想学别人掐指一算,根据月相算一算现在是几时,徒劳好久还是没记起来高中时那点儿地理知识,只好作罢。穿衣起床,看手机已经是七点一刻。

我真是搞不懂我的生活习性了。昨天睡的也不晚,起的也不早。就因为今天没睡午觉,下午在公交车上乏困加恶心,好容易撑到宿舍,脱下外套倒头就睡着了。

北京城真大呀!去看望一个朋友,也是前同事,光地铁转了三条,花了近一个小时时间。这要在南京,能在主城区跑个来回儿了。在刚来北京,顶陌生的时候,穿梭在大街小巷之间,老能觉得自己渺小。

看《叶问》,我特别佩服他那股劲儿,绷着,不惊、不怒、不沮、不屈,真了不起!

PS: 刚发现一卡通又丢了,估计掉在科图了,还得再去趟,郁闷!

Poderosa 2009 特别版

自从讨厌了 Putty 黑黑的界面之后,在 Windows 下我一直使用 Poderosa 登录 ssh 主机。与 Putty 相比,Poderosa 的优点是支持标签和 Cygwin shell。 原生的 Cygwin shell 窗口太丑陋了,和 Linux 下的终端没办法比,相信经常在 Windows 下使用 Cygwin 的同志都会有同感。Poderosa 能使 Cygwin 的终端窗口获得与 Linux 终端类似的使用感受,这是我偏爱它的一个重要原因。

当然,国产的 Fterm 也支持登录 ssh 主机,使用起来也凑合,但是很多 ssh 的高级功能是不支持的。

我以前曾在这篇文章中推荐过 Poderosa,但是和很多开源软件一样,一旦遇到困难(比如主要开发人员流失),软件的升级就陷入了停滞。Poderosa 从 2006 年 11 月 22 日发布 4.10 版本之后就再也没有更新,虽然 SF Project 的 Activity 中一片对 BUG 的抱怨之声。

一直以来我对 Poderosa 最重要的不满是编码和按键问题。Poderosa 是日本人写的,所以在编码中只有ISO-8859-1、UTF-8 和日文支持,缺少对 GBK 中文编码的支持。那么在 Cygwin shell 中执行一些 Windows 原生命令比如 ipconfig 时,命令输出的中文就会是乱码;按键问题主要体现在登录到远程主机时一些按键不支持,比如 Home 键就无法正常使用。

虽然我很早之前就想自己添加进去这些特性,因为不懂 C# 语言,一直没有动手。昨天实在忍不住了,把 Poderosa 的源代码下载下来,准备学一下 C# 语言然后去修改它。

但是很不幸幸运的是,我看到 Poderosa 的 Activity 中 4 天前(09 年 1 月 2 日)增加了一篇 post,一个咱们的同胞xjzhang1979说:他改进了 Poderosa,我下载了一看,我想要的功能都有了,真开心。

xjzhang1979 将软件包上传到了一个网络文件共享网站,您可以点击这个链接下载:http://www.box.net/shared/7n7ps57jgn。为了避免该链接失效,我在我的共享网站做了一个备份,您也可以到这里去下载:http://share.solrex.org/ibuild/

PS: 后来搜索找到了作者的博客,关于此修改版介绍的原文在这

2009-03-29: 更新的 Poderosa 特别版在这里:http://share.solrex.org/ibuild/

内网穿透反向隧道代理技术

本文的主要目的是利用使 ssh 服务器(外网)通过客户端(内网)代理上网以及反向控制客户端(内网),不需要网络管理员权限,不需要 NAT。即可上网主机 A 位于内网,不可上网主机 B 位于外网,A 能直接访问 B,但 B 无法访问防火墙内的 A,这样 B 就可以使用 SSH 隧道访问 A 主机上的代理服务器上网。(估计有这种变态需求的人只存在于大学中)

B(210.77.*.*)---->(210.77.*.*)FW(192.168.0.*)|--->(192.168.0.*)A---->(192.168.0.*)FW(123.*.*.*)---->Internet
B(210.77.*.*)< ----(210.77.*.*)FW(192.168.0.*)-----(192.168.0.*)A<----(192.168.0.*)FW(123.*.*.*)<----Internet

一、代理

首先在客户端上安装 socks 代理(HTTP 代理可以使用 Squid,同理)。socks 代理软件使用 Dante。Dante 的启动以及配置需要手动调整,下面是 Dante 的配置文件 /etc/sockd.conf:

compatibility:reuseaddr
internal:0.0.0.0 port = 1080
external:eth0
logoutput:/var/log/sockd/sockd
clientmethod:none
method:none
user.privileged:root
user.notprivileged:solrex
connecttimeout:60
iotimeout:86400

## client access rules
client pass {
 from: 127.0.0.1/0 port 1-65535 to: 0.0.0.0/0
 log: connect disconnect
}

## server operation access rules
#allow bind to ports greater than 1023
pass {
  from: 0.0.0.0/0 to: 0.0.0.0/0 port gt 1023
  command: bind bindreply udpassociate udpreply
  log: connect disconnect
}

pass {
  from: 0.0.0.0/0 to: 0.0.0.0/0 port 1-65535
  protocol: tcp udp
  log: connect disconnect
}

#allow outgoing connections (tcp and udp)
pass {
  from: 127.0.0.1/0 to: 0.0.0.0/0
  command: bind bindreply connect udpassociate udpreply
  log: connect disconnect
}

#allow replies to bind, and incoming udp packets
pass {
   from: 0.0.0.0/0 to: 0.0.0.0/0
   command: bind bindreply connect udpassociate udpreply
   log: connect error
}

#log the rest
block {
   from: 0.0.0.0/0 to: 0.0.0.0/0
   log: connect error
}

下面是 Dante 的启动脚本 /etc/init.d/sockd:

#!/bin/sh
set -e

. /lib/lsb/init-functions

[ -f /usr/local/sbin/sockd ] || exit 0

[ ! -f /etc/sockd.conf ] && exit 1
SOCKD_CONF="-f /etc/sockd.conf"

SOCKD_OPTS="-D"

case "$1" in
  start)
    # Start daemons.
    log_daemon_msg "Starting Dante socks proxy server" "sockd"
    if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sockd.pid --exec /usr/local/sbin/sockd -- $SOCKD_OPTS; then
	    log_end_msg 0
	else
	    log_end_msg 1
	fi
    ;;
  stop)
    # Stop daemons.
    log_daemon_msg "Stoping Dante socks proxy server" "sockd"
	if start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/sockd.pid; then
	    log_end_msg 0
	else
	    log_end_msg 1
	fi
	;;
  restart)
    log_daemon_msg "Restarting Dante socks proxy server" "sockd"
	start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile /var/run/sockd.pid
	if start-stop-daemon --start --quiet --oknodo --pidfile /var/run/sockd.pid --exec /usr/local/sbin/sockd -- $SOCKD_OPTS; then
	    log_end_msg 0
	else
	    log_end_msg 1
	fi
	;;
  *)
    log_action_msg "Usage: /etc/init.d/sockd {start|stop|restart}\n"
    exit 1
esac

exit 0

二、ssh 服务器

服务器端 ssh 服务器最好采用 Openssh,Windows 下应该使用 Cygwin 运行 sshd,FreeSSHd 实践证明崩溃比较频繁。同一台机器的 Windows 和 Linux 上运行的 ssh 服务器最好使用相同的 host key,将 /etc/ssh/ 下面的文件保持一致即可。最好配置服务器为使用公钥进行登录验证,这样能使用自动脚本进行端口映射。

三、手动建立隧道(客户端到服务器端口映射)

用下面命令建立客户端到服务器的端口映射,将客户端的 socks 代理端口 1080 映射到服务器的端口 8080。这样服务器就可以通过自己的 8080 端口反向隧道到客户端的 socks 代理 1080 端口上网。

ssh -C -f -N -g -o PreferredAuthentications=publickey -R SERVER:8080:127.0.0.1:1080 USRNAME@SERVER
# 参数含义:
# -C: 要求对数据进行压缩
# -f:要求 ssh 执行完交互后进入后台运行
# -N:不用建立一个终端
# -g:允许远程服务器连接客户端转发端口
# -R SERVER:8080:127.0.0.1:1080:将客户机(127.0.0.1)的 1080 端口绑定到服务器(SERVER)的 8080 端口。
# USRNAME@SERVER:ssh服务器的用户名和密码
# -p 3022:ssh服务器的端口

为了方便控制,最好也将客户端的 ssh 服务器端口映射到服务器端,服务器端就可以通过登录自己的 8022 端口来登录客户端的 ssh 服务器:

ssh -C -f -N -g -o PreferredAuthentications=publickey -R SERVER:8022:127.0.0.1:22 USRNAME@SERVER

这样两台被防火墙隔开的主机就能实现双向控制。

四、自动建立隧道

手动建立隧道的缺陷是服务器端必须长期开机,并且连接只能用户在客户端手动发起。可以考虑间接的办法,比如使用两台主机均可访问的服务器作为跳板,或者客户端自动登录聊天软件,利用聊天软件接受指令。下面给出一种使用共享服务器的方法:

#!/bin/bash
# {start|stop|restart:username:ipaddress}

COMMAND="stop"
SERVER="0.0.0.0"
USRNAME=""
PORT="22"

INFOURL="http://someserver.com/somepage"

SSHOPTS="-C -f -N -g -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no"

get_server_info()
{
  #info=`wget -nv -O - $INFOURL 2> /dev/null | iconv -f gbk -t utf8 |
        grep -o -e "{.*}" | tr -d '{}'`
  info=`wget -nv -O - $INFOURL 2> /dev/null | iconv -f gbk -t utf8 |
        sed -n "/{*}/s/.*{\(.*\)}.*/\1/p"`
  COMMAND=${info%%:*}
  SERVER=${info#*:}
  USRNAME=${SERVER%:*}
  SERVER=${SERVER#*:}
}

tunneling_status()
{
  tun_ps=`ps aux | grep "ssh -C" | wc -l`
  if [ $tun_ps -gt 4 ]; then
    echo -n "running"
  else
    echo -n "died"
  fi
}

start_tunneling()
{
  ssh $SSHOPTS -R 3128:127.0.0.1:3128 ${USRNAME}@${SERVER} -p $PORT
  ssh $SSHOPTS -R 8022:127.0.0.1:22 ${USRNAME}@${SERVER} -p $PORT
}

failsafe_tunneling()
{
  ssh $SSHOPTS -R 8022:127.0.0.1:22 ${USRNAME}@${SERVER} -p $PORT
}

stop_tunneling()
{
  killall -e ssh
}

echo -n "[`date +%F\ %R`] "
get_server_info

case "$COMMAND" in
  start)
    if [ $(tunneling_status) = "running" ]; then
      echo "Starting ssh tunneling.(started, do nothing)"
    else
      echo "Starting ssh tunneling."
      start_tunneling
    fi
    ;;
  restart)
    if [ $(tunneling_status) = "running" ]; then
      echo "Restarting ssh tunneling."
      stop_tunneling
      start_tunneling
    else
      echo "Restarting ssh tunneling."
      start_tunneling
    fi
    ;;
  stop)
    if [ $(tunneling_status) = "running" ]; then
      echo "Stoping ssh tunneling."
      stop_tunneling
    else
      echo "Stoping ssh tunneling.(stoped, do nothing)"
    fi
    ;;
  failsafe)
    echo "Starging ssh tunneling.(failsafe mode)"
    failsafe_tunneling
    ;;
  sleep)
    echo "Sleeping."
    exit 0
    ;;
  *)
    echo "Unrecogenized server command ($COMMAND)."
    exit 1
    ;;
esac

exit 0

将上面脚本加入 crontab 每十分钟运行一次,就能实现在服务器端对客户端进行有限的控制。

那些 ssh 教我的事

我以前以为 ssh 只能登录主机执行命令,ssh 教我它还会 scp;

我以为会 scp 很厉害了,ssh 教我它还会 sftp;

我以为会 sftp 了不得了,ssh 教我它还会 sshfs;

我以为会 sshfs 已经相当牛了,ssh 教我它还能把服务器端口绑定到本机端口(ssh -L);

我以为把服务器端口绑定到本机端口就已经上天了,ssh 教我它还能把本机端口绑定到服务器端口(ssh -R)...

然后,我怨念已久的共享上网终于实现了!

PS: 用软件还得选老牌开源软件,openssh 那么多年没更新了,在 Windows 下用 cygwin 运行都比 freesshd 稳定,不得不赞一个。Win 下的一些软件,窗口搞得花里胡哨的,功能和稳定性却不知道放到第几位了,跟人一样,长得好看的未必靠谱。

IPV6 获取地址却无法使用的解决方案

最近我的 WinXP 经常无法连接 IPV6 站点,但是 IPV6 地址的获取是正常的,同一台电脑上的 Ubuntu IPV6 工作也正常。经过一些摸索发现可能是以下两个原因造成的:

1. 分配到 2002 开头的 IPV6 地址并使用了它。2002::/16 格式的地址是 6to4 的地址,不是 native 的 IPV6 地址,所以在 IPV4+V6 双栈网络中不应该使用 2002::/16 格式的地址。执行 ping6 ipv6.google.com 可以看到自己使用的是什么 IPV6 地址。

之所以会产生 2002::/16 格式的地址,一个很可能的原因是网络中的 Windows Vista 操作系统默认会发送 IPV6 的路由器公告。使用

netsh interface ipv6 show interface "本地连接"

命令可以查看本地连接的参数,其中有一条:“发送路由器公告”,一定要设置为“否”。如果您的这个选项是“是”,那么您可以使用这个命令关闭它:

netsh interface ipv6 set interface “本地连接” advertise=disabled

如果获得的全部是 2002 开头的地址,可以使用下面命令进行重分配:

netsh interface ipv6 reset

2. IPV6 的路由表(网关)不对。tracert6 ipv6.google.com 就能看到本机是经过什么路由到 ipv6.google.com 的。如果从第一跳就显示连接超时,应该就是路由表出了问题。

两个问题的解决方案如下:

netsh interface ipv6 set prefixpolicy 2001::/16 1 1 persistent

上面这条命令的意思是设置 Windows 更偏好使用 2001 开头的 IPV6 地址,避免使用 2002 开头的地址。如果您 ping ipv6.google.com 使用的是 2001 开头的地址,那么您不必执行上面这条命令。

netsh interface ipv6 add route 2001::/16 "本地连接" fe80::21a:30ff:fe4f:7000 persistent

上面这条命令的意思是为 2001 开头的 IPV6 地址使用正确的网关 fe80::21a:30ff:fe4f:7000(中科院某公寓)。这个网关可能随着用户所在网络的不同而不同,简单点儿的方法可以去看正常用户的 ifconfig 网关地址。

之后执行 ping6 ipv6.google.com 看能否 ping 通,如果能 ping 通就说明 IPV6 工作正常了。

如果您经过以上两步之后仍然无法解决问题的话,您可以使用 netsh interface ipv6 reset 命令来重置所有修改。

PS:

1. 如何确定网络中哪些主机在发送路由器公告?

2002 后面的两个字段就是该主机的 IPV4 地址,比如 2002:3b41:177e:8:18fc:7649:9e1d:2880,其中 3b41:177e 从 16 进制显示换算成 IPV4 的 10 进制显示地址就是 59.65.23.126。一般来说,V4 地址的分配更有规律,您可以从 V4 地址大致确定该主机的位置。

2. 如何确定虚假的路由记录?

一般来说,IPV6 地址的最后 4 个域应该从网卡的物理地址中获得,假设网卡物理地址是:00-17-31-94-99-EA,在 3,4 字节之间插入 FFFE 换成 EUI-64 格式是:00-17-31-FF-FE-94-99-EA,再对第一个字节的第二位取反,就变成 02-17-31-FF-FE-94-99-EA,然后装载到 IPV6 的本地地址中,就变成本地地址 fe80::217:31ff:fe94:99ea。根据网段的不同,在前面加上 4 个网络域,就是主机的公网地址 2001:xxxx:xxxx:xxxx:217:31ff:fe94:99ea。

由于双栈路由器是使用同一个网卡提供 V4 和 V6 的路由,那么路由器的 V4 地址和 V6 地址的物理地址是一样的。通过 ping gateway_ipaddress_v4,然后 arp -a 看 V4 的网关地址对应的物理地址,与上面 V6 网关本地地址中获得的物理地址相对照,就可以确定某路由记录是否为可用的记录。