将 Debian APT 引入 iPhone

iPhone (或 iPod Touch) 是一台配备着 128MB RAM、4 到 32 GB 闪存的 667MHz 计算机 (尽管只跑在 412 MHz 上)。软件方面,它运行的是一套裁剪过的 Mac OS X,包括 Darwin 内核上基于 FreeBSD 的用户空间程序,作为其标准组件。虽然部分人可能会奇怪怎么有人会把它作为 Unix 工作站来使,但就我和其他许多人的看法,拿它来做 Unix 工作站才是值得奇怪的。

BSD 子系统

所以给这台设备头一个安装的包是“BSD Subsystem (子系统)”也就没什么奇怪的了:这是一套标准的 Unix 实用工具,让习惯了 Unix 的开发者感到宾至如归。从 grep, sed 到 vim, pico,这套软件包提供了深夜编程活动的基础。

不幸的是,要真以这样的方式用过一会儿,你肯定会因为这些关键工具配置和编译时的缺漏而感到恼火。尽管完成到现在这样也是件好事,但对 Unix 实用工具的编译打包不应该仅仅停留在“它能安装成功”,而更应该延续到“它能正常工作”。下面是我不得不处理的一些问题:

  • bash – 命令在输入完之前就会被截短并执行
  • chmod/chown – 总以“memory exhausted (内存耗尽)”作为退出消息
  • nc/ping – 无法解析某些域名,并声称 “Unknown server error (未知的服务器错误)”
  • netstat – 无法提供任何本地 socket 的有用信息
  • passwd – 损坏 BSD 的 /etc/passwd 文件,使 1.1.3 的 SpringBoard 崩溃
  • screen – 无法通过 terminfo 找到 “linux” 这样常见的终端类型
  • vim – 没有语法高亮,退格键会导致编辑器崩溃

所有这些问题中,仅有 chmod 和 chown 的问题在公开的“BSD Subsystem”包中被修复了,这还是因为 Apple 的 1.1.3 更新带来的巨大压力 (要求所有的软件以 mobile 用户而不是 root 运行,所以必须修改文件的权限,有些程序必须 setuid 为 root)。

然而这些问题只要有人愿意花时间都是很容易解决的,却始终停留在目前这样的状况下,导致工作效率低到了可怜的程度。比如我到现在在 vim 里按下退格键之前还要犹豫一下下,就是因为用了那个损坏的版本开发了几个月所带来的后遗症。所以总得有人来改进吧。

CoreOS – iPhone Unix

剩下这些问题的一部分已经由一名叫 core 的 iPhone hacker 解决了。在他网站的 Unix Tools 部分一直提供了这些改进版本的程序,比如 chmod 和 chown (不过他提供的 passwd 还是坏的)。而在 Network Tools 部分他一直致力于改进 wget 和 ping 等有问题的工具。

可是要安装 core 这些改进过的版本必须分别手工找到、下载并安装修改过的文件:这些工作做一回还好,但三四次 jackbreak 之后再做就肯定觉得非常麻烦了。而人们不一定总是从 core 那儿获得更新,导致实际分发的二进制程序会有区别,因而跟踪起它们的行为来也就分外困难。

最后的一个原因是,core 总是没有时间把他如何神奇地修改这些软件代码的过程写下来,也没有把它用来编译的命令贴在网站上,所以别人没法知道这些问题是怎么被修正的,要修正其他类似的情况是也无法借助他的工作。

一套开放的替代品

为了缓解这些问题的困扰,我决定开始一个名叫 Telesphoreo 的项目,目的是以合作的、开放源代码的方式,给 iPhone 创建一套基于 GNU 和 BSD 的用户空间程序集。这个项目的名字来自于一个古希腊词汇,意思是“使果实成熟”,这正是我对 Apple 这个现有产品的想法:作为电话它是合格了,但要作为个人工作站它还远远不够。当然,要创建这样一个发行,还得使用正确的工具。

为了发行软件,我选择了 Debian 的 APT 系统,这是我在少量修改以配合 Apple 的网络设置后移植到这个平台的。为了方便大量用户的迁移,我还花时间用 UIKit 写了一个 APT 的图形界面,名叫 Cydia (Cydia pomonella 是苹果蠹蛾的学名,而苹果蠹蛾……就是我们常认为的苹果蛀虫,我觉得这个名字挺合适的)。

为令这个项目能够自举 (bootstrap),我先是移植好了百余软件,从 bash 到 xeyes 的所有东西,包括一系列的支持库和脚本语言 (包括比 Installer 提供版本功能更强的 Ruby 和 Python)。所有为编译所作的代码修改,以及编译整个项目的脚本,都通过 subversion 库 提供。

虽然不是所有的东西都能正常工作,但我已经完成的部分肯定比原来 BSD Subsystem 提供的有了很大进步,也只在很少情况下会比 core 所提供的版本要糟 (不过这个情况很快会解决,因为 core 已经准备将他的更改贡献进来)。如果确实遇到了问题,我建议人们在项目的 Trac 上提交缺陷报告。

如果谁希望出来担任某个现有软件包的维护者,或者提供其他新程序的移植,请加入邮件列表 (译注: 邮件列表还未建立,请通过 IRC 联系作者)。尽管现在是这个新发行版唯一的维护者,我当然不希望一直这样 (既因为缺少对很多领域软件的经验,也因为本就不可能一个人把所有事情做完)。

安装过程

从用户的角度讲,只要将 http://apptapp.saurik.com/ 加入 Installer 的源 (source) 中,然后从它的“System”分类中安装“Cydia Packager”就可以了。你应该在安装前暂时禁用设备的自动锁定 (auto-lock) 功能,因为这个安装过程可能会花上好几分钟。

在安装过程中,下面这些操作会依次进行:

移除 BSD 子系统的旧文件

要替代 BSD 子系统,就得先把它删了,不幸的是在 Installer 里它是不能删除的,所以 Cydia 包管理器只好直接删除所有它安装的文件。不必大惊小怪,就算你非得要回 BSD 子系统,也完全可以用 Installer 来“Reinstall (重装)”它一遍。

重组文件系统以提供足够的空间

为了使升级 iPhone 固件时不会丢失个人数据,Apple 将 iPhone 的存储空间划为两个分区,分别挂载在 / 和 /private/var (它被符号链接到 /var)。前一个分区只有 300MB,所有 Apple 的软件都放在这里。可惜的是第三方软件也必须安装在这里。

为了缓解空间不足的问题,如果安装 Cydia 时发现你在另一个分区 (也就是 4-32GB 里那剩下的部分) 有足够的空间,并确信这些文件并未被 BossTool 这样的工具移动过,就会将你所有的 Applications (应用程序), Wallpapers (墙纸), Fonts (字体), Ringtones (铃声) 和 Shared data (共享数据) 通通移动到那个较大的分区中,原来那个就多出了 150MB 的空间。

安装 Cydia 和 Telesphoreo 基本包

这一步完成后会占用大约 35MB 的空间,并提供了一套常规的 Telesphoreo 子集,以供安装更多的软件包,或者升级已经安装好的部分,又或是执行一些核心的管理操作如运行终端,另外这套子集还能支持现有应用程序对旧有 BSD 子系统的依赖 (我自己、还有许多测试者们一道,给这些依赖库开了一张清单)。

上述的这三个步骤都完成了之后,以后 Cydia 自身和所有的 Telesphoreo 更新都将通过 Cydia/APT 进行,因此你不妨时常打开 Cydia 刷新来源信息看看有没有什么最新的软件。关于更新的更多信息会在下面的使用说明一节介绍。

如果过程中出现了什么错误,你可以提交一个 bug 报告 (要先注册并登录这个站点) 或者来 irc.saurik.com 的 #iphone 频道报告,我平日都在这个频道上边,就算我哪天要出门也至少会查看一次上面信息,又或者是直接给我发邮件请求帮助。(请只在前两个办法不合适的情况下才发邮件,因为前两个都能让其他看到的人帮上你的忙,以节省大家的时间。)

“伪” BSD 子系统

要注意:Telesphoreo 本身不依赖 BSD 子系统,所以如果你目前没装,也没有任何理由在装 Telesphoreo 之前装上 BSD 子系统,而更重要的是,安装过了 Telesphoreo 之后,如果你再尝试安装 BSD 子系统,则会损坏装好的 Telesphoreo (它会用就软件替换你的新系统)。而且,你还应该避免 BSD 子系统的升级 (upgrade),因为其实这和重装没什么区别,同样会把 Telesphoreo 中关键的组件替换成旧的。

如果你刚刚 jailbreak 完自己的 iPhone/iPod Touch,不必安装 BSD Subsystem,装 Cydia Packager 就够了。如果某个包要用到 Cydia Packager 缺省没有提供的功能,你完全可以以后用 Cydia 装上。

这样和 BSD 子系统包的基本冲突进一步导致了两个需要用户了解的问题:第一个是 BSD 子系统日后的更新都会列在 Installer 的 Updates 中,所以用户有可能会误装这些有问题的旧软件;第二个则是有些软件会在安装前要求 BSD 子系统已被安装,否则它们自己的安装就不能继续。

希望 BSD 子系统会逐渐退休,而那些对它有需求的包则会在未来数月内更新为更聪明一点的检查 (比如判断自己需要的具体是哪些文件)。与此同时,Telesphoreo 也提供了了一个叫做“伪” BSD 子系统的变通方法,它和真正的 BSD Subsystem 的 bundle identifier (Mac OS X 用来甄别软件包的标识符) 一致,但本身不包含任何文件。只要装上了这个包,那些要求 BSD Subsystem 存在才肯安装的应用程序就能顺利装上,还能抑制 Installer 偶尔出现的那些——让你更新 BSD Subsystem 的请求。


虽然我会尽量保持伪 BSD 子系统的版本号和原始那个包的版本号对应,但可能还是会慢上几天,所以 Installer 可能会提示你有 BSD 子系统的更新。你必须避免更新,稍等几天,新的伪造版本就会出现,Installer 的更新提示也就自然会消除了。


Cydia 的使用

一般说来,Cydia 用起来和 Installer 差不多,都是为了让人们快速装上新软件的。主界面由底部的一排按钮组成,让你在下列几个页面间切换。

特色 (Featured)

这个页面是在 Cydia 一启动就显示的,其实是一个包含 Cydia 和 Telesphoreo 使用的相关新闻的站点,还列出了近来一些有趣软件包。虽然目前它只有唯一的页面,不过会迅速成为程序中的一个浏览窗口,让你访问到软件包相关的站点和更详细的信息。

安装 (Install)

在这里你可以按目录的形式浏览未装的软件包,或依分类序、或依字典序。(目前软件包的分类名称还比较混乱,所以如果见到某些怪异的分类,请稍作适应。)

更新 (Changes)

一般你会关心的两件事是:“我已经安装的软件包有没有更新”和“最近更新过的软件里有没有我想装的”。这个页面里就把这两种情况组织到一起了:已装软件的更新显示在上部,其他所有未装软件则按更新时间逆序列在下方。

这第二种使用方式类似 Installer 的“Recent Packages”分类,不过有两点重要的改变。第一,你可以回滚到任意时间点,而不仅仅是查看最近几天的更新。之所以支持这个功能,是因为只有 Installer 的死硬 fans 才会频繁地保持几天一次地查看最近更新,否则就有可能漏掉一些重要的新特性。

第二,一个包只在你第一次看到它之后才被算作被“加入”了,而不是按照它本身加入软件仓库的时间算。这一点也很重要,只要你添加了一个新的软件源,那它的库里所有的包都该被认为是“新”的。这样的定义还能避免每次软件库管理者修改包定义时又把它们推回列表的前头:只有没见过的包才能算是“新”的。

卸载 (Uninstall) 或管理 (Manage)

更为普通一点的 Cydia 使用是找出你已经装过的软件,然后或者重新配置或者重新安装。“Uninstall”界面就是为了这些情况设计的,之所以这么命名是因为 Telesphoreo 目前没用到 APT 的配置 (configuration) 系统,而 Cydia 当然也就不支持。如果这个界面以后有了更多的功能,可能会被改名为“Manage”以更好的表达其特点。

搜索 (Search)

最后一点,你经常会听说某个软件包但不确定它具体叫什么名字,又或者是你希望通过包描述中的信息找到特定功能的软件。这时就要用到“Search”了。只要输入一个字符串它就能依软件包的标识、名称或者描述将匹配的结果列出。

最后还有个放在顶部的界面元素:一个让 Cydia 与所有来源进行本地 APT 数据库同步的刷新按钮。另外顶部还显示了上次同步的时间。我希望以后这个更新的过程能被精简掉:当用户不用 Cydia 的时候同步可以自动在后台进行。如果实现,顶部这栏就完全可以去掉了。

管理来源 (Manage Sources)

Installer 中存在而 Cydia 里没有 (因为 Search 占用了空间) 的一个界面是“Sources”。虽然还没实现出来,但我们的计划是让你在 iPhone 的 Settings 程序里选择这些来源,而不必塞在 Cydia 的界面里。这种交互方式和 Apple 的 MobileMail 比较类似,其中你的帐号也是偶尔在 Settings 里设置了但通常都在 Mail 里访问的。

目前 APT 在 /etc/apt/sources.list 里维护了一个软件来源列表,你可以通过修改它来添加新的来源 (在管理来源的功能实现之前)。还有一个叫做 /etc/apt/sources.list.d 目录用来存放单独的 .list 文件,这样每个都可以简单地通过诸如 Installer 包的形式来安装卸载。

作为过渡,所有屏幕的左上角都有一个带圈的小 i,表示目前缺少的这个功能,并允许通过它你临时把来源添加到 APT 的配置文件中。(如果这带来了什么不便,我很抱歉。)

为何不用 Installer?

我完全能理解的一个质疑是,为何选用 Debian APT 并开发 Cydia,而不是直接用 Installer 来管理所有 Telesphoreo 中的软件包。考虑到 Installer 已经是 iPhone 上经典的软件分发方式:它是几乎所有自动 jailbreak 方式安装的第一个,也是唯一一个软件,并有大量的软件仓库支持,这些仓库一起提供了大量的软件包。

不幸的是,Installer 并非开源软件,就算有这么个许诺,也完全看不到任何相关的发布计划或者其他兑现的方式。Cydia 的源代码则已经提供了出来而其他项目 (比如 MxTube) 也正因此受益。我们也希望 Cydia 能借助开放源代码的优点,迅速得到第三方的补丁或者贡献。

所以一旦考虑到 Telesphoreo 开放的特质,我们实在找不到还用 Installer 作为其安装程序的理由。不过即便如此,这一点还一直是本项目争议最大之处:很多人都来问我,为什么非得创建一个“新”的应用程序分发系统 (APT 的移植) 而不是直接用现成的广泛存在的 Installer。而且,他们对于以后开源的许诺也被用作不要分散 iPhone 开发者精力的理由之一。

我觉得最好的回答是提供看问题的另一个角度:Cydia/APT 和 Installer 并不冲突。我在自己的 iPhone 上这两个程序都会用,也有大量用 Installer 装上的软件。目前为止也没有听说任何要求安装 BSD 子系统的程序因此出现什么兼容性问题,也没有任何理由这么怀疑。最后一点是:完全没有人——包括软件打包的人员和用户——被强制去用 Cydia 来替代 Installer。

我觉得一个关键的问题是人们觉得觉得 Installer 实现了他们以前未曾见过的功能:从许多来源、以单一界面提供了简便地程序安装管理。虽然在 Mac OS X 和 Windows 这两个 iPhone 用户集中的平台上都没有什么这方面的经验,但包管理的概念可一点也不新:几乎所有的 Linux 和 BSD 发行版都依赖着它。再说 Mac OS X 和 Windows 上还有 Fink 这样的第三方软件提供类似的功能 (顺便说一句,Fink 正好也是基于 APT 的)。

因此我专门写了一篇对包管理方式的快速剖析 (译注: 中文版见 AppTapp 的打包问题),并解释了一些这类系统的复杂情形。这篇文章还包括我个人作为软件打包者在使用 Installer 时遇到的挫折,希望它能阐明高级软件包管理中会遇到的那些问题,以及尝试开发 Telesphoreo 这样一个雄心勃勃的计划意义何在。

总结性评论

尽管有这些问题,我还是相信 Telesphoreo 和 Cydia 对 iPhone 社群会有很大的帮助。就算有人不同意其中某个具体实现上的选择,将关于移植应用程序和库的信息收集到一起也能方便我们把更多高质量的软件带到这个平台上。希望能如我所愿吧。

AppTapp 的打包问题

Debian 的 APT,Gentoo 的 Portage,FreeBSD 的 Ports……它们内部运作的方式往往迥异,有的在服务器上存储着按少量标准配置编译好的二进制程序,有的则将代码下载到本地再按需编译,但它们最终都能归结到一两个简单的命令,不会比一句 install bash 这样简单的操作更复杂。

在 iPhone 上包管理工具的时令之选是 AppTapp Installer。这款由 NullRiver 开发的 Installer 给 iPhone 开发社群提供了一套简单的程序 (该程序可在 jailbreakme.com 这样的系统上轻易自举),它可由普通用户访问,进行即时的应用程序安装维护。任何人都可以通过它来分发自己的软件,只要配置好自己的软件仓库,并将其 URL 告知用户,他们就能以此作为“source”来安装你们的软件。考虑到 iPhone 这个 Apple 设备文档匮乏又 (对开放的软件安装) 深怀敌意,Installer 能做到这样可不容易,非常值得称赞。

然而,对于软件打包者而言,AppTapp 却显得很是繁琐。它的开发者选择了 Objective-C 属性列表 (property list) 来存储所有的软件包元数据,其中包括要在安装时执行的命令——不过通常执行的是 shell 脚本。这通常导致配置文件看起来不够完美。一个用十句脚本代码就能表达的配置往往要好几页的 XML 才能描述。

<array>
  <string>If</string>

  <array>
    <array>
      <string>InstalledPackage</string>
      <string>com.saurik.Cydia</string>
    </array>
  </array>

  <array>
    <array>
      <string>IfNot</string>
      <array>
        <array>
          <string>Confirm</string>
          <string>Performing... continue?</string>
          <string>Yes</string>
          <string>No</string>
        </array>
...

这本身未必就很糟糕,但 AppTapp 的开发者们并未花时间给可以使用的脚本命令定下一套规范的列表,虽然这样的命令偶尔也会在新版本发布时提及,但却时而无效或者用途难辨。我总是不得不坐下找个字串表编辑器来分析它的二进制代码,以提取出那些可能作为命令的字串。

就算格式不成问题了,其表达的语义本身也还是个问题。Installer 的包管理方式是非常简化的,但随着几个大型的软件来源仓库的需求把它推得不停往上加新功能,却没有停下来好好看看周围现有的软件包管理项目,也没有寻求外界的帮助来改进他们的代码。考虑到以往的包管理系统早已花了大量时间在这方面的代码实现和理论研究上,这样从头做起实在不是一件好事。

进阶问题

对包管理系统的许多工作与思考一开始看起来似乎并不那么好理解,尤其考虑到有如此多的竞争者,就更不好理解了:既然人人都能做包管理,它又怎么会是一件困难的事呢?但其实许多有挑战性的问题都有待这些包管理系统提供解决方案。这里就是刚接触这个领域的开发者一些常见的缺漏:

  • 包依赖关系 – 在我安装一个 blog 程序前,会先需要一个 web 服务器。所以现在我需要的只是“一个” web 服务器,却没指定具体要哪个。另一种情况则是我明确指定就要 Apache 的 2.1 版本,不要其他的。
  • 冲突软件与替代软件 – 我安装了两个 vi 的克隆版本,但只有其中之一可以占用 /bin/vi 这个路径,所以应该是“较好”的那个替换品获得这个关键的路径,但仍允许另一个在别处存在。否则的就只能删除其一了。
  • 多来源仓库 – 有时同一个软件包的多个版本会出现在不同的来源中,而我要的是最新的那个。更棘手的情况是,必须允许用户指定安装来自某处的某个特定版本。
  • 版本标定 – 就算 1.7 版本从纯数字角度上要比 13 版本老,事实上却未必如此:很多老软件都可能会小幅修改它的起始版本号,甚至完全从头算起也有可能。
  • 守护进程与初始化脚本 – 安装一个用来同步时间的守护程序很可能需要启动该守护,这个过程应被视为安装了一个系统特性,而不仅仅是一个二进制程序。
  • 废弃与替代 – 一套发行可能需要判断 Firefox 是否应该替代所有已安装的 Mozilla,又或者以前独立提供的 libffi 现在是不是已经被 gcc 包含了这样的情况。

经年以来,每个主流的包管理系统提供者都不得不消化这些问题,并提供解决方案,其中有的就比其他的更为成功。这些经验往往是在维护一个系统发行版中慢慢学得的,再一点点被加入到他们的包管理工具中去:包管理要解决的问题的复杂程度甚至可以作为一个发行版的年轮。所以 Debian 和 FreeBSD,这两个都从 1993 年开始的项目,自称其方案最被广泛认可和使用也不算虚言了:他们发展的时间最长嘛。

即便如此,我们还是能发现新的包管理工具在不断出现:没什么解决方案是完美的,人人都有自己的观点。这并非总是坏事。比如 Gentoo 创制 Portage 时,他们将过往的全部经验运用到了这个困难的环境下,并在很大程度上成功了:以允许按各自机器定制软件包编译的方式,在发行版市场分得了一杯羹。

但 Portage 大概就是 Gentoo 所做的全部工作了:如果你把这个发行版分解一下就会发现他们大部分的时间都花在了一个革命性的包管理工具上,而不是花在诸如改进每个支持的软件包对怪异编译器的支持、或是集中式配置工具、又或者是一切理想发行版所需要的东西上面。


通常有这么一个建议不错:当你开创一个新的公司或新的发行版时,应该只选择在少量几件 (最好是一件) 事情上与竞争者有区别,并把这几件事情做好。要把每件事情都做好恰是通往平庸之路,这样你永远也没时间做完其中任何一件。

尽管肯定说得简单了点,但看看每个稍著名一点的发行版和其他有何不同就能体会得到:Gentoo 之于部署,Ubuntu 之于集成,OpenBSD 之于安全,RedHat 之于支持,Slackware 之于简单,而 SuSE 之于配置。


有人可能会这么问:NullRiver 想通过 Installer 达到何种目标?考虑到这是一个封闭源代码的产品,来自一个主要目的并非这个应用的公司,我们只能认为这个公司只会在 Installer 上面花很少的时间。(把那些经常蹦到你面前的要求捐赠的链接,和最近数月毫无有价值更新这两个事实综合起来,更能证明这一点。)

我大概可以确定,他们关注的是用少量允许的时间开发一个小的 (因而也是容易自举的) 软件部署机制,提供标准 iPhone 程序的图形界面 (正如我在前面提到的,在那个 Apple 提供的库对大家几乎是一个迷的情况下,能写出这样的程序真是值得赞叹的),他们已经有效地达到了这一目的。

由于重点在此,Installer 之所以完全忽略前述那些包管理的复杂情形也就可以理解了。此外,因为开发的时间有限,实现本身也缺乏处理边界情况的能力 (比如磁盘空间不足时)。对简单的情形下 (安装到一个较新的设备上并有比较充足的空间) 安装简单的软件包 (来自单一来源并作为单一 iPhone 应用程序的一部分提供) 它做得不差不离:一旦情形稍变,它就会出问题。这样,我觉得参与打包的社群他们的目标正在改变,尤其是在这 Apple 官方 SDK 马上就要发布的时候。我们的软件正变得越来越大、功能越来越多、也越来越多地相互依赖。与其作出完全独立的二进制程序,我们发现大部分人都越来越愿意自己的工作能与现成的库整合,这些库提供的功能从多媒体编码显示一直到网络协议的实现。

为 Installer 打包

我让 Installer 做的第一件事情就不算简单: 我想把 Java 和一系列它依赖的库及例子程序移植到 iPhone 上 (过几天等我写好关于这个成功移植的文章)。这需要安装较多的内容 (大概 30MB),应该切分为数个独立的软件包,以便人们各取所需,来执行自己想要的程序。

一开始我非常天真地开始了尝试,以为 Installer “当然”会支持我在其他包管理方案里看到过的功能,结果发现它其实不支持时我还很是惊讶。

第一个问题是缺乏依赖关系支持。Installer 中唯一类似的特性是查询某个软件包是否已被安装,如果没有的话只能停止安装 (这会导致用户只能非常困惑地寻找正确的依赖包安装顺序) 或仅显示一条警告 (这样漫不经心的用户就有可能忽略安装某个重要的软件包)。

每天我都会收到来自用户的消息,说我的软件不能正常工作,这类报告我查起来通常的结果都是一两个必须的软件包没装上。有时要向用户证明他们其实是因为缺少了某个包挺困难,不过他们总归会发现并装上,然后我的软件一般都能正常工作了。

开发社群也在逐渐变大。随着越来越多的开发者的加入 (以及越来越多来源库的加入),还应该在改进更新库元数据的时间上下功夫,争取找出更有效的措施。我想人人都体会过在刷新软件源列表时那种慢得痛苦的感觉,而 Ste (负责打包最多也是最受欢迎的一个打包者之一) 也曾因为 Installer 的同时连接数受限而遇到过一些严重的问题

符号链接

还存在一些其他的实现问题。我们要打包的内容类型变得越来越复杂:比如符号链接就很常见。但 Installer 基本不支持这个。对我而言情况是我希望打包的许多库都有这个需求,当安装动态库时,通常的习惯时将含版本号的库文件放置在 /usr/lib 中,然后以符号链接的方式来提供备用的名称,以供不挑剔版本的应用程序使用,这些备用的名称里通常不含完整的版本字符串,但都会链接到一个真实的库上。我要打包很多都是这样的库,每个至少都包含一个符号链接。

现在符号链接在现代的 InfoZip 文件中存储是没什么问题了,也能被 Installer 标准的安装方式 (CopyPath) 所解压……除非目标位置已经有了一个同名的符号链接,这样安装就会因无法覆盖而失败。虽然仅这一个算不得大问题,但考虑到 Installer 标准的卸载方法 (RemovePath) 不支持符号链接,重新安装显然就会失败了。

好吧,符号链接显然不应该用 CopyPath 来处理,考虑到 Installer 是声称支持一个叫做 LinkPath 的特性 (在它的 “Featured” 网站写了,你一打开这个程序就能看到)。可是这个函数是用 linkPath:toPath:handler: 来实现的,问题在于 iPhone 里压根没这个函数,导致你一用 Installer 立即就崩溃了。所以显然他们从来没测试过这个功能。

最后的办法是尝试用 Exec (它允许执行任意的外部程序) 来调用 /bin/ln,这倒是有效,但要假定 BSD Subsystem 已经安装了。然而因为解析 Exec 命令的机制有问题,它根本不支持包含空格的文件名 (用引号包含或者转义字符都无效)。

我能找到的唯一一个通用的办法是提供一个独立的 shell 脚本来执行 /bin/ln,这样我就能任意做命令行参数的转义了。尽管如此,我还是遇到了问题:在 iPhone 新的 firmware 1.1.3 版本上 (Installer 以 mobile 用户运行,setuid 为 root),Installer 只能以 effective root 身份 (而不是 real root) 来执行 Exec 命令,这导致许多程序 (比如 bash) 都没有足够的权限,因为我需要在自己的 shell 脚本中用到许多 bash 专有的特性,我只好另外配一个有 setuid 的 ln 版本。

当然,这些不过是实现上的顾虑,也可能被修正,但考虑到我去年 11 月就寄去的关于符号链接处理的 bug 报告至今未有回音,我想这些问题仍然值得关注。

总结性评论

我所要做的本不该这么困难,是的,就算我忽略了一些更简单的选择,但要运用一个完全没有文档的包分发格式,而这个格式只有不到一年历史却已经不怎么维护了,这本身很成问题。

对我来说,这正是 Telesphoreo 和 APT 最重要的目的之一:运用开放源代码、人人可以获得、经过长期考验的产品来解决问题,而不是从头专为 iPhone 写一个这样的软件。如果你在使用 APT 时弄坏了什么,令人安慰的是有成千上万有其经验的人帮助你修正,有邮件列表、bug 跟踪系统可以关注,还有成百上千介绍了 APT 使用方法的网站可以参考。所以,在 Unix 的世界中,你永远不会孤单。

来写 ikiwiki 的试用报告

大家都知道,无聊的人特点之一是见到好玩的东西就找来玩一玩,ikiwiki 就是新近发现的有趣东西,把我的兴趣:Subversion, Markdown, wiki 的概念整合到了一起,算是 wiki 程序里边思路比较另类的,本来看到要装一堆 Perl Module,不愿意去折腾,但 hlb 说等我的试用报告 (笑),就试着装了装看,发现真的很好玩,值得推荐,所以把装的时候遇到的问题流水帐地记下来,或许对你有用。

说了这么多,那 ikiwiki 究竟是什么呢?它其实是一套 Perl 程序,虽然是用作 wiki,但和一般的 CGI/mod_perl 的 wiki 程序不一样的是,它把所有的 wiki 数据文件用 Subversion 来管理,这样文件的历史记录、备份和并行控制就可以直接把 Subversion 的功能搬来用,这是 ikiwiki 最大的特点。

此外,ikiwiki 有趣的地方还在于它使用了 Markdown 的语法来作标记,相比常用的 wikitext,我觉得 Markdown 实在是人性化得多的标记方式。在 Markdown 的基础上,ikiwiki 做的两个额外的处理就是 wiki 的链接 (CamelCase 或者手动指定) 和子页面的创建。

ikiwiki 还有些好玩的功能,比如当作 blog 来用、支持反向链接 等等,你可以在它的 features 页面中看到,不过我这里是还没有尝试过啦。

哦,顺便提一句,ikiwiki 的作者 Joey Hess 是一位 Debian Hacker,也是 alien, debconf 的作者。

下面开始讲实际的东西。

安装

我测试的环境是 Arch Linux 2.6.16, Perl 5.8.8, lighttpd 1.4.11, subversion 1.3.1。

首先,ikiwiki 需要以下这些 Perl Module:

  1. CGI::Session
  2. CGI::FormBuilder
  3. HTML::Template
  4. Mail::Sendmail
  5. Time::Duration
  6. Date::Parse
  7. HTML::Scrubber

我的经验是,还需要 HTML::Parser,此外,Markdown 可以使用 Text::Markdown 这个 Module。

用 cpan 装完以后,就可以下载一份 tarball,解压。此时,因为我们使用的是 Text::Markdown 提供的 markdown,所以 Ikiwiki/Render.pm 需要作一点小的修改,找到 Markdown::Markdown 那一行,改为:

use Text::Markdown 'markdown';
$content = markdown($content);

然后执行

perl Makefile.PL
make
make install

就完成的安装。现在可以试试 ikiwiki 命令有没有反应了。

配置

接下来是配置过程,视你希望实现什么功能而有不同的繁琐程度。

第一步是 (先装好 Subversion) 创建给 wiki 使用的版本仓库:

svnadmin create /svn/wikirepo
svn mkdir file:///svn/wikirepo/trunk -m create

然后从这个仓库中 checkout 一份当前的版本作为 working copy,放在自己的 home directory 下边:

svn co file:///svn/wikirepo/trunk ~/wikiwc

这样你就可以用默认的模板和配置构建你的第一个 ikiwiki 了:

ikiwiki --verbose ~/wikiwc/ ~/public_html/wiki/ \
    --url=http://host/~you/wiki/

~/public_html/wiki/ 是你的 Web server 分配给你的 HTML 文档目录。完成上边这一步之后,你就可以在这个目录中找到生成的 *.html 文件 (别忘了 ikiwiki 是一个“wiki compiler”),如果配置好了 web server 的 user directory 功能,就可以在 http://localhost/~username/wiki 看到你的 wiki 了。

这里简单说说 lighttpd 的这个配置,默认的 lighttpd 配置文件在 /etc/lighttpd/lighttpd.conf,首先,在 server.modules 变量中打开 "mod_userdir" 模块,然后设置 userdir.path,以上面的情况为例,就应该是:

userdir.path                = "public_html"

当然,输出的目录也不一定要是你的 home directory,只要你的 web server 和你都能访问到的地方都可以啦。

接下来就可以做真正有趣的定制工作了,默认用来创建 wiki 的文件在 /usr/share/ikiwiki/basewiki/,你可以从里面复制一份到 ~/wikiwc/ 作为自己的版本,比如:

cp /usr/share/ikiwiki/basewiki/index.mdwn ~/wikiwc # 复制首页 (Markdown 标记的)
svn add ~/wikiwc/index.mdwn # 加入 svn
$EDITOR ~/wikiwc/index.mdwn # 修改首页文件内容
svn commit ~/wikiwc/index.mdwn -m customised # commit 到 svn repo 中

你发现 Subversion 在这里派上用场了,凡是编辑 wiki 文件其实都在 Subversion 中维护着,这样 Subversion 就能够跟踪每步更改了。

可是我们现在的方式还很原始耶,不能在浏览器中直接编辑 (也许某些人就喜欢这样),每次要打长长的一串命令,于是,一个叫做“配置文件”的东西出来拯救我们了。

先把 ikiwiki 源文件目录下的 doc/ikiwiki.setup 复制一份到你喜欢的地方。然后开始修改这个文件。里面像 wikiname, adminemail 这样的看名字就知道是什么意思。下面是各项的简短说明,详细的可以看 Usage

  • srcdir: 你放 index.mdwn 文件的那个目录
  • destdir: 你输出 html 的那个目录
  • url: wiki 的 URL
  • cgiurl: 用于自动更新 wiki 的 cgi 程序的路径,这个程序是 ikiwiki 自动生成的,看下面的解释
  • svnrepo 和 svnpath: 和前面创建的 repo 符合,在我这里是 /svn/wikirepo 和 trunk

接下来就是最重要的两个设置,为了实现在线编辑,ikiwiki 可以自动生成一个 (编译好的) C CGI 程序,这个程序的位置就在 cgi wrapper 部分指定,你需要设置为一个你的 web server 可以访问得到,而且又能作为 CGI 程序执行的地方,比如我放在 ~/public_html/wiki/ikiwiki.cgi,那么 lighttpd 对应的配置是:

  1. 打开 mod_cgi
  2. cgi.assign 的设置中,加入 ".cgi" => "" (为空表示不使用其他程序解释,直接执行)

同时,这里设置的位置也必须和前面的 cgiurl 属性对应,如果根据我前面的这个设置,对应的 cgiurl 就是 http://localhost/~username/wiki/ikiwiki.cgi

问题在于,只是编辑了还不行,编辑后的结果得生成静态的 HTML 我们才能读啊,CGI 不负责这一步,它是通过一个 svn post-commit 的 hook 程序实现的,这个程序,同样是一个编译好的 C 程序,由 ikiwiki 自动生成,放在哪里应该是无所谓的,不过我这里是放在 /svn/wikirepo/hooks/post-commit

这样就算是设置完毕了,执行 ikiwiki --setup 配置文件路径/配置文件名 可以生成上面提到的两个程序。

小结

其实小结就是要说些坏话… 目前 ikiwiki 还没开始对 UTF-8 的支持,如果你是直接用编辑器来编辑文件的话,那不会有问题,可是如果希望通过 CGI 来在线编辑,它就搞不定了,在它的 TODO List 中也提到了目前对付的方法,如果要全面解决,还是得期待 2.0 啦。

另外,ikiwiki 这样虽然很好玩,尤其是作为个人信息管理的工具,但恐怕还是不能胜任大型的 wiki,像 wikipedia 这样,同时有成百上千人在编辑,编辑后都要 svn commit… 哦,我不敢想象了。

发现一件有趣的事情,如果按照 ikiwiki 网站上给出的图片把第二个 k 反向,这又是一个反着读也一样的名字 🙂