光に覆われし漆黒よ。夜を纏いし爆炎よ。紅魔の名のもとに原初の崩壊を顕現す。終焉の王国の地に、力の根源を隠匿せし者。我が前に統べよ!エクスプロージョン!
13,835 words
https://typeblog.net About Links en_US

除了自己,没有人能保护你的隐私

让我们再一次翻出“隐私”这个亘古不变的话题。

不久前,国内某大厂的 App 在微博上被曝光偷偷后台读取隐私信息,也有可能在不为人知的情况下将其上传。因此,我写了一条长微博,主要内容是宣传「在互联网时代,只有自己在意隐私,自己保护隐私,隐私才能得到保护」,指出了某 App 的问题非常可能仅仅是冰山一角。微博一出,我收到了大量完全是出于误读的回复,包括但不限于诸如 「Android 真惨,我用 iOS 就不怕了」 / 「普通用户没有能力保护自己的隐私,只有政府监管才行」 等等。更有甚者认为我是在为监管的缺失开脱,甚至以为我是在给出事的某大厂 App “洗地”,以反讽的态度在评论我的微博。我实在缺乏更多与这些根本没有读懂我在说什么的人争论的动力,因此部分这样的微博用户被我暂时屏蔽 —— 没有别的意思,仅仅是感到疲倦而已,如果被我屏蔽的用户正在看这篇文章的话,我在此道歉,但是我已经停用微博,所以屏蔽与否已经没有意义;但是我仍然感觉到有必要单独写一篇更详细的文章和大家聊一聊这个话题。微博这类社交平台不是一个适合放大于 140 字的文章的地方,因此我决定写这篇博客。

首先,我还是想谈谈某些人与我争论不休的监管和个人自身的隐私意识的问题。必须明确的一点是,我对在保护隐私权方面的监管永远是欢迎的 —— 否则我也不会对 GDPR 的出现表示支持。来自强制力量的对隐私权保护的监管对企业永远是有约束作用的,永远是聊胜于无的,并且“他们”也 有责任 去保护一般人的隐私权。只是,这完全不是我所发布的内容的关注点 —— 我所关注的是,从个人的角度来看,除了自己,你没有任何其他人去信任、去依赖,期望他们能够为你的隐私权而战。换句话说,监管确实是必须的,但是一般用户没有理由信任任何政府、任何“平台”性质的企业的监管措施,所有站在个人的角度对监管的盲目信任和期待都是妄想。

因为「不保护隐私」相对于「保护隐私」实在有太多的利益隐藏在后面。不管是从企业的角度还是从监管者的角度,对隐私权保护的松懈往往可以带来丰厚的利润 —— 隐私可以用于“用户画像”,可以用于更加精准的广告投放,可以用于“人工智能”的训练,可以用于所谓“大数据”,甚至可以用于某些见不得人的交易。在今天的环境下,用户的隐私就可谓是数不尽的金钱 —— 而隐私又不像是社会治安,也不像是财产权,对很多人来说又不痛又不痒。仔细考虑这些,怎么会有人觉得一纸规定就足够保护自己了呢?假使用户自己不去关心、不去争取,还凭什么去信任别的人会愿意放弃自己更多的利益去保护与他们完全不相关的利益?从某种程度上,正是某些用户的这种信任,使得有的人肆无忌惮地获取并利用他人的隐私作为自身获利的来源 —— 因为只要大部分人不关心,作恶就没有一个能够抵消那些利益的后果。比起监管的缺失,一般人的隐私意识的缺失只会更加可怕。

从另一个角度来说,有效的监管永远只会出现在隐私意识普及之后。在隐私意识得不到普及的情况下,即使存在 GDPR 这样的法律,作用也会非常有限 —— 它总是需要有人去发现违反规定的人,需要大家知道什么时候自己合法的权利受到了侵犯,需要人们知道去利用已有的规定为自己争取权利。没有人能像父母一样随时监护着每一个人。还是像刚刚我所说的那样,只要大部分人都不关心,那么即使是违法也很可能不会出现非常严重的后果 —— 毕竟,只要没人关心,什么“交易”都是有可能存在的,监管也就成了一纸空文。在这种情况下,铤而走险反而变成了一个在追逐最大利益的情况下权衡风险以后合理的选择。而反过来,在隐私意识普及的时候,即使完善的监管体系暂时不存在,即使“他们”仍然想要利用隐私获利,“他们”也只能处处小心。如果只要露出一点点马脚就能引发大部分人的担心和唾弃,在最坏的情况下,他们的作恶行为也必须要付出比以前更高的成本,也必须承担大得多的风险 —— 因为现在,关心隐私的人变成了他们的主流用户群体。在这点上,看看那些注重隐私的服务的用户群体分布就非常明显了 —— 那些为我们所称赞的服务,往往从最初(在没有以“注重隐私”受到称赞的时候)开始面向的主流用户就是更倾向于注重隐私的群体,比如自由软件用户和开发者。他们具有足够的隐私意识,也有能力去反过来限制服务提供者的行为。

有人尝试用警察的责任的类比来反驳「一般人需要知道保护自己的隐私」,但我认为这个类比恰恰可以成为对以上内容的支撑。试想,假如大部分人不关心自己的人身安全,认为自己被杀被剐都不是一件大事,看见自己身边的人被害也想不到为他们伸冤,那么故意杀人还会成为一个(可能的)死罪吗?假如没有人会因为公共安全事件而不安,还会有真正干活的警察存在吗?我不知道他们怎么看,我只能说我认为不会。这当然 绝对不是他们不作为的理由,只是在没人意识到的情况下,他们不管怎么做都不会触发一丝疑问,那么我们就只能用最坏的可能来预测他们的行为。而这正是隐私意识的现状 —— 人们不知道也不关心什么时候自己的隐私受到了侵犯,就像不知道对人身安全感到不安一样。如若不是这样,怎么会出现诸如「不添加某支付厂商的私有系统级黑箱组件的系统就受到唾骂」的现象?因此,对于这个类比,我认为结论只能是,对于“他们”什么能做、什么不能做的共识是产生合理的监管和对监管的执行的监督的前提。

然而即使有了对于隐私权保护的共识,我们还不能忘记的一点是,每个人对于隐私的定义很可能是不同的。这一点可能永远无法改变 —— 你可能认为你的位置信息是隐私,而有的人可能认为只有敏感的私人照片才算作隐私。这是极端的例子,毕竟有隐私意识的人一般不会把隐私的范围定义弱化到这种地步,但是不同必然是存在的;科技发展也可能带来新的、潜在的、但目前还有争议的隐私数据,比如 DNA 数据。即使监管已经存在,它往往只能用于提供对共识中的隐私范围的保护,非常有限而且滞后;只要一个人的隐私有超出“默认范围”的,他就只能依靠自己,或者等待共识的范围随着隐私意识的增长渐渐扩大。后者对于一个人来说显然是不现实的:他不可能依靠任何人预先定义的一个集合来保护自己的隐私;他必须能够根据自己独立的思考做出自己的选择。这也是隐私与其他权利的一个不同:它不一定有统一的、泾渭分明的界定。

从以上内容中我想已经不难看出,那些鼓吹依靠 「XX公司」「XXOS」 甚至 「XX国」 就能解决隐私问题的想法是有多么可笑。如果自己不重视,就不可能会有人有动力为你重视。但是还有一类人,认为「普通用户想靠自己保护自己是不可能的,只有XX被改变/消灭才可能有效」,甚至以此对以上观点加以嘲讽,私以为这和前面那种事实上是同一种类型的 b*llsh*t。当然,我完全理解这类人可能遭受过无数不公正的待遇,可能感到非常无力和无助;但是这并不能成为认为普通人什么也改变不了的理由。有的人在逃避保护他人隐私权的责任,你就连自己的都不尝试保护一下,甚至对其他在尝试保护自己的人加以嘲讽?还是期望着等你换成了 XX 就再也不会遇到隐私问题?如果是这样的话,那也和前一种人没有什么区别了。我只知道,当我不敢去做某一件我认为正确的事情的时候,我不会去嘲讽任何有勇气去做的人 —— 我会暗自佩服他们。不知从什么时候开始,不管是什么东西都变成了“普通人什么也做不了”,只能躺下打滚撒娇,大喊“谁来管管”?普通人还能做什么?等着别人来喂饭吗?这就是大家追求隐私想要的结果吗?不,这正是那些敢于明目张胆侵犯隐私的人想要的 —— 当人们从不关心变成了即使了解也懒得关心,一切都结束了。况且,在彻底变成这种样子之前,保护隐私权和某些其他的“维权”也不是一回事 —— 就算你没有地方可以“哭诉”,没有人给你“主持公道”,只要稍微愿意钻研的人,还是有方法至少 尝试 保护好自己的 —— 至少目前在世界的大多数地方仍然是可能的。至于未来,我想或许连现在仅剩的保护自己的机会都会被某些人白白送给 「XX公司」「XXOS」 甚至某些更可怕的实体吧。到那时候,才是真的只能默默悲哀了。

我不认为「提高保护自己的意识」这个要求对普通人而言是过高的。我不奢望所有人都有非常高的技术能力,但是作为一个生活在互联网时代的人,有些东西是需要学习的,是需要用来武装自己的,而不是赤裸裸地、一无所知地面对所有现代技术。诚然,让每个人突然都去学习这些东西是不可能的,可是我们完全可以一步一步地来。这其中最简单的是,我们可以尝试不再对那些试图保护我们的人和事物嗤之以鼻。比如某大厂的某知名 App 以 “安全” 为理由要求 Android 系统中插入其闭源的具有系统权限的守护进程才能使用,而一开始并不是所有手机厂商都默默接受了这一要求 —— 正是用户们不分青红皂白地向这些厂商们施压甚至骂战,才是这个守护进程如今几乎存在于所有国产和国行手机的真正推手。诚然,大部分用户没有足够的知识理解这些,但是我们至少可以做到 “兼听则明”,至少可以在听取多方观点以后思考其利害关系,至少可以做到不在一些人科普其背后原因的时候还恶语相向 —— 相信我,只要做到这一点,现在的隐私环境会好得多。其次,我们可以尝试走出以 “能用” “方便” 为第一的这种思维定式,需要关心到在 “能用” 以外的更多理由,而不是像某李姓人士说的那样 「中国人愿意用隐私换取方便」。我并没有要求任何人一下子学会所有技术知识,但是不懂得技术知识并不是大多数人忽略事物的隐藏的方面的理由 —— 我认为真正的理由正是上面提及过的 “懒”,而能够 “懒” 则是因为并不了解其后果 —— 而这完全可以通过教育和科普来改变。

隐私作为一个个人权利,其保护的过程必然不可能是自顶向下的。只有当人们有了上述这些意识以后,才会出现隐私保护的共识,在这以后,“他们” 才能有足够的动力制定并且 落实 保护隐私的法规和政策,才能够反过来通过共识限制企业和政府的行为。在这个隐私的领地渐渐被缩小的时候,企业、政府必然要承担他们的责任,但是杀死隐私的真正刽子手正是每个人自己,与你我一样的人们才是导致这样的结果的最大原因。拒绝学习、自欺欺人、掩耳盗铃,这些词语放在当今的普通互联网用户上并不为过。曾经,我们都有机会阻止这一切的发生,可是放在今天,很多事情已经晚了;要夺回属于我们的领地,需要付出的努力将不只是一倍两倍之多。

希望从今以后,隐私不会再只是茶余饭后一笑而过的谈资。

重新开始博客: 使用 StandardNotes

距离上一篇博文已经过去 10 个月了,而本博客连一篇文章都没有更新,可能已经开始给人一种几乎要荒废的感觉了。而事实是,确实是这样,这个博客已经在荒废的边缘。

这几乎一年间,因为自身情绪上的一些问题,我很少有想要写一些东西的心情,与以前过个几天就想提~~笔~~键盘的状况完全不同。而在那非常少的有“想写一些东西的心情”的时候,我每次都会因为害怕麻烦而打消念头。“麻烦”的缘由则很大程度上是我自己埋下的 —— 我在多年以前抛弃了 WordPressGhost 这样的博客平台,自制了一个简易的博客程序 Typeblog,而这个程序是没有实现编辑器 UI 的。它直接依托于配置文件、插件和文章源文件,而这些文件则需要存储在 Git Repository 中。因此,我以前的做法是使用单独的 Markdown 编辑器,比如 Typora,编辑后提交到 GitHub 并通过 WebHook 来实现自动同步到服务器。这一个过程确实没有几个步骤,在以前我也就很自然地做完了。然而,也许是不良情绪放大了很多事情的复杂性,这确实就是过去这段时间里我不想动博客的主要原因。

关于那些“情绪问题”,我近期可能写一些文章描述,而这篇文章的正题并不是那些问题。总之最近,所谓情绪问题似乎有所缓解,也稍微有些能写的心情,只是回想起来之前的写作体验确实差强人意。当时也没有一个可以接受的手机写作解决方案,因为 Android 端我并没有找到非常好用的、支持同步的 Markdown 编辑器,即使找到也存在如何 push 到 GitHub 的问题;而我却经常在路上、在睡前玩手机无聊的时候想要写一些东西。当然,我的朋友 drakeet 有一款在手机上写作的 纯纯写作 应用,写作体验已经非常棒,只是我需要的并不只是写作,我还需要

  • 多平台统一 (PC Linux + Mobile Android)
  • 支持自托管 (self-hosted) 的完整同步解决方案
  • 完整加密支持 (明文数据不离开客户端)
  • 插件支持 (这样我至少可以自行实现导出到自己的博客)

于是,这就到了该开始安利的时间了。

Standard Notes

我第一次接触到 Standard Notes 与博客完全无关。正如其名,它是我在寻找笔记软件的时候发现的。

从上大学开始,我就习惯于用笔记软件来写电子版笔记,顺便用于记录一些临时起意的想法之类。原因很显然是我的凄惨手写字体和手写速度,但无论如何,经过了一两年以后,使用笔记软件这也就成为了一个习惯,经常没事就打开看一看写一写。

那时候我使用的是 Evernote 的付费订阅 —— 认识的很多人都在用,所以我也自然地使用了它。但是,使用了这个产品以后才发现,它不要说端到端加密和 Markdown 支持了,就连基本的富文本编辑都存在这样那样的很多 BUG,其中不少完全是由于自作聪明。它的扩展性也几乎是零 —— 没有任何办法做出能用的编辑器插件。大一的时候数学课的笔记需要使用 LaTeX 记录公式,唯一可行的办法就是用某些使用 Evernote API 做的 Wrapper 来输入,然后让它们代为同步到 Evernote。这既不优雅也不安全,还要多带上一个专门用来记数学笔记的软件,已经失去了统一笔记平台的意义。

后来 Evernote 出现了隐私问题,尽管后来隐私条款的修改被撤回了,我还是觉得切换自己使用的笔记平台的时间到了。Evernote 官方也迟迟不肯实现插件支持和加密等特性,整个平台也已经很长一段时间没有变动,除了隐私条款之外。如此决定之后,我就开始四处寻找新的替代,希望能够用支持加密、自托管且有足够插件支持的软件来替代。当然,使用代码编辑器 + Markdown + Git 也是完全可行的方案,我可能希望的只是一个更适合做笔记的 UI;同时也不希望自己每次打开代码编辑器,默认记录的上次打开文件都是一堆与代码无关的东西。

就是在这个时候,我从某位朋友那里听说了这个 Standard Notes。那时候,它似乎还在非常早期的阶段,功能也非常简单 —— 默认只有一个纯文本编辑功能,它背后的协议 StandardFile 也就是一个普通的文件同步协议加上客户端加密。除了加密部分[1]以外,这个东西的一切都似乎是从简单和方便长期维护的角度考虑的,这其中就包括了它的插件化的结构。开发者宣称,为了“专注安全和持久性”,

We say 'no' to feature requests.

可是说着这句话的它却有很棒的可扩展性[2],大概是因为傲娇也是一种正面属性吧。利用插件 API 几乎可以自己做任何想要的功能,事实上,官方的收费计划中就有很多他们自己利用这个 API 制作的插件,包括了 Markdown 编辑器 (LaTeX 支持),TODO 编辑器,以及各种加密备份方案等等。

这个收费订阅,也是在不影响开源的前提下的收费 —— 有能力的人完全可以很方便地自行从源码构建所有插件并导入,也可以在不交钱的前提下自行开发插件。我最初就是通过这个方式在没有付费订阅的情况下使用了插件,之后加入了付费订阅。

在当时,它的移动端仍然缺少除了同步以外的很多基本功能,包括 Markdown 编辑器;也因为我那时还有当时自己认为方便的写作方案,所以完全没有考虑使用它作为主要的写作工具,只是作为一个方便的 self-hosted 笔记工具来使用。

Listed

接下来的某个时间,我发现了同样由 Standard Notes 团队提供的工具 Listed。它是一个模仿 Medium 的平台,只是比 Medium 要简单得多,本质上就是一个导出并展示部分 Standard Notes 笔记的地方。

虽然 Listed 本身并没有开源[3],但是其实现方式和大部分关键代码都已经在开发者文档中作为“示例代码”公开了。或者倒不如说,Standard Notes 的整个 Actions API 几乎都是为了这个用例而设计的,Listed 服务则只是最简单的用例而已。

这个服务让我眼前一亮,既然我的笔记软件已经有非常好的 Markdown 编辑体验,为什么不直接把它当作写作工具?况且本来平时的写作灵感最先进入的就是笔记,若是一篇文章没有彻底完成,则行文思路也是在笔记中,使用笔记软件直接写作的话反倒可能免去了不少来回切换的麻烦。

因此,我决定先用 Listed 尝试一下这么做的可行性与实际体验,这也就有了我的英文博客。这个英文博客是直接绑定在 Listed 服务上的,想更多的记录一些想要写成正式文章又想早些公开的想法,当然目前也只有比较完整的英文文章,主要还是之前作为体验写作的几篇。

而体验就是,Standard Notes 的客户端确实是一个不错的写作工具。

重新开始写博客

虽然有了英文博客,发布也方便得多,它仍然也只比这个中文的主博客多更新了一篇文章而已,原因也就是开头提到的那些问题。

最近也许会恢复更新博客这件事情也是最先在英文版上发布的。其实,这其中的第二重要的动机就是,最近我更新了一下 Standard Notes 的客户端们,发现现在的体验比上次有心情好好使用笔记的时候又好了许多。PC 端的不少 BUG 都修复了,终于有了可以管理附件的插件,而之前插件实现还在讨论阶段的移动端也已经支持了主题和编辑器插件。尽管移动端插件实现仍然是通过使用非原生代码(JavaScript),但是考虑到为了能完全和桌面端通用插件,减少复杂度,我觉得也没有很大问题;Standard Notes 的移动端 App 也可能是我见过的体验最好的非原生代码实现的 App 之一了,在现在的状态几乎与原生体验无异。而 Listed 支持绑定自定义域名,也是我这次更新以后发现的事情。

开头已经说过,我并不再想通过老的方式来写作博客了,而且也不想使用某些写作体验更不舒服的博客方案。之前已经发现 Standard Notes 作为写作工具的潜力,但是我也不希望直接把我的整个独立博客都替换成 Listed -- 那样就失去我专门造个博客的价值了。

我需要想办法将 Standard Notes 与我自己的博客程序,Typeblog,整合。这时候,我的计划是使用 Standard Notes 的 Actions API 模仿 Listed 展示一个用于发布博客的菜单。但我很快发现了这么做的问题: Actions 是通过请求远程接口实现的,无法在 Standard Notes 客户端中加密保存任何数据,这也就无法实现从客户端自定义配置 (e.g. 指定 Typeblog 的博客所在的上游 git repository 地址)。虽然即使做出来,这个东西大概也就我一个人使用,但是这样实在令我感觉非常不优雅 —— 毕竟,就连 Typeblog 这个只有我一个人使用的东西本身,也被我加上了很多配置选项甚至扩展。

于是,我尝试给 Standard Notes 写一个 editor-stack 扩展,从自定义远端的 Git repository 里面读取配置,允许直接将当前编辑的 Note 推送到这个 Git repository 并更新 Typeblog 的 config.json 以发布文章。但是很快,我发现要这么做的话至少需要一个将 Git 的功能暴露在 HTTP 上的程序[4],而我的博客的存储部分本身就是托管在具有 HTTP API 的 GitHub 上[5];而 Standard Notes 的本来就有一个名为 GitHub Push 的插件可以直接将笔记原文推送到 GitHub Repository。尝试以后,发现它可以选择推送的目标 repository、目录前缀和文件名后缀,而文件名则是与笔记标题相同。因此,以下 workflow 似乎就可以完全满足需要:

  1. 创建标题为新文章的URL链接域名后部分的笔记,比如这篇的 blogging-with-standardnotes
  2. 写作新文章内容
  3. 使用 GitHub Push 推送到博客 repo 的 posts/ 目录下,后缀名 md
  4. 创建名为 config.json 的笔记并保证其内容与原来 repo 中的同名配置文件相同
  5. 将已经 push 的新文章的相对路径加入这个配置文件
  6. 将其推送到远端 config.json,后缀当然是 json

如果自己编写插件的话,那么手动在笔记软件中同步配置文件的不优雅的几步完全可以自动执行,但是我再次决定这点麻烦并不算麻烦。也许等下一次我因为这种问题懒得更新博客的时候,我就会想起来去解决这个问题了吧。

仍然存在的问题

  • StandardNotes 手机 App 仍未支持 Actions API 和 editor-stack 插件,其中就包括了我使用的 GitHub Push, 也就是说我无法在手机上直接更新博客
  • GitHub Push 仅限于 GitHub API,将来我的博客存储也迁移到自建方案以后需要自己重写这个插件
  • StandardNotes 的部分编辑器插件在处理非英文输入法的时候存在奇怪的 BUG
  • 我还没有实现比较方便的直接给博客贴图的插件,目前仍然是手动上传到自己的 minio
  • 这套自制博客方案实在太垃圾,而且已经多年没有维护,大概是时候重新做一个了……

总结

本来这篇文章的计划是比较各种方案的优势和缺点,以及最后推荐 Standard Notes 并对它作出评测。写完之后回头看才发现,这已经变成了完全在记述“心路历程”的文章。也罢,我确实也一直不太擅长评测这样的事情。

总之,这就是我的新的博客 workflow,也希望我在这篇文章之后能告别咕咕咕……

脚注

  1. Standard Notes 背后的 Standard Files 协议本身经过了一次 Security Audit,但是其具体客户端实现并没有;这个协议也确实还有一些可以发现的问题;不过 Standard Files 至少本身是 self-hosted,协议也基本上都按照正常的 security practice 在做,不像某 T 开头的聊天工具
  2. 使用扩展会带来更高的安全风险,这是可以预料的,尤其是编辑器类的扩展,好在扩展有 iframe 隔离并且必须显式声明权限
  3. 或者我没有发现 Listed 开源在哪里
  4. 浏览器端的 JavaScript 难以操作原生 Git,而 Standard Notes 的插件并没有浏览器端和桌面端的硬性区分
  5. Typeblog 的程序使用 Git 管理文章源文件;博客还暂时没有迁移到自建的 Gitea,但是即使迁移之后本质上也是一样的,只是需要用 Gitea 的 API 重新做一个插件罢了

systemd-nspawn 踩坑记

已经有半年没更新博客了,一方面是这段时间确实情绪之类的方方面面不太稳定导致一直没心情更新,另一方面是觉得没啥好更新的,无非是一些琐碎,所以就一直拖着拖着,直到今天才发现,已经半年了。

而正好最近把自己的网络服务都迁移到了一台新的服务器,尝试了全新的部署方式(systemd-nspawn),正好踩中了一些坑,所以随便写写记录一下,也算是重新开始做起博客这件事情了吧。

What & Why

以前我使用的是一台在 online.net 捡来的特价独服,因为只有一个人使用,所以我直接在主机上开了很多个 KVM 虚拟机,使用(几乎是)一个服务一个虚拟机的方式来部署自己的服务。这在一个人使用的时候确实没有什么太大的问题,唯一的问题可能就是因为自己懒,而虚拟机的数量太多,所以经常忘记更新 / 维护那些虚拟机。

而这次则是捡特价弄了一台特别划算的 E5-2680v2 的独服(购买的时候下单的是 E5-2660v1,但是不知道是商家特别有钱还是那天机房小哥心情好,给弄了一台 E5-2680v2),几个人合用一台。因为是合用,所以大家各自开了一个 KVM 虚拟机,各自隔离。这时候,我就不能再使用虚拟机的方式来隔离自己的服务了,因为我本身已经被隔离在了一个虚拟机里,双重虚拟机可从来都不是什么好主意。

所以我转向了容器方案。其中,Docker 不太适合我的情况 —— 我并不是希望把所有服务都做成不可变的镜像然后到处部署,我的目的仅仅是简单的隔离(看起来整洁 / 给有些傲娇的应用提供最适合的环境)。因此,我转向了 systemd-nspawn,毕竟我是 Systemd 的~~卖底裤~~粉丝(雾),而且自带 SystemdArchLinux 在安装完成后就自带这个东西。

于是,我成功开始了踩坑之旅。(大量 dirty fix 预警)

非特权用户 (Private Users)

按照 ArchWiki 上的说明,使用各个发行版的 bootstrap 工具在 /var/lib/machines 下创建目录并部署系统是一个很快的工作。然而,当我部署成功并尝试启动容器的时候,我却根本看不到任何反应,无论 machinectl status 还是 systemctl status 都没法给出任何有用的信息。

再次查看 /var/lib/machines 下我部署的目录的时候,发现里面文件的权限全部被修改成了奇怪的 UID 和 GID 值。从 ArchWiki 上的描述来看,这似乎是启用了 Private Users 的正常现象。然而,死马当活马医,我尝试在 /etc/systemd/nspawn/myContainer.nspawn (myContainer 是我的容器的名字) 里面加入了

[Exec]
PrivateUsers=no

然后容器就神奇般地可以启动了。不过这样启动以后,尝试访问容器的时候,会发现里面的程序一定会报一大堆权限问题 —— 因为之前已经用 Private User 起过容器。所以,我的做法是,直接重新部署一遍……

然而这个问题并没有被彻底解决,我到现在也不能理解为什么使用 Private User 会导致无法启动,而且 systemd-nspawn 完全没有给我任何有用的错误信息。更奇怪的是,我直接用命令行的 systemd-nspawn 去启动容器是完全正常的,而使用 [email protected] 就必须关掉 Private Users 才能正常使用。鉴于我的使用场景并不需要多么严格的安全策略(另一方面,Linux 下的容器这个概念本身也不是用来做安全的),我暂时并没有去处理这个问题。所以,这算是一个 dirty fix 吧。

无法访问容器的 TTY

容器起来了,我遇到的第二个问题就是无法访问容器的 TTY。

尝试执行 machinectl login myContainer, 直接给我扔了一个 protocol error 出来。在 Google 上找了很久也没有找到任何一个人遇到类似的问题。最多只有进入了容器的登录界面却无法登录的问题,而遇到那种问题的人至少已经获得了容器的一个 Login Shell, 而我则是什么都没有……

是的,这个问题我也完全不知道怎么解决。当时我折腾了很久,然后怀疑是一个临时性的 bug —— 毕竟 ArchLinux 喜欢 break 东西。所以我决定作为一个临时的解决方案,先手动使用 systemd-nspawn 命令启动容器进去,配置好 openssh,然后用 machinectl 启动容器,并在外面直接使用 ssh 访问容器内部的 shell。是的,你没有看错,直接在 /var/lib/machines 使用 systemd-nspawn -b -D myContainer 命令启动容器是完全可以访问容器内的 shell 的,而从外面使用 machinectl login 或者 machinectl shell 就是不行的……

而当我下笔写这篇博客的时候,我尝试再次复现这个错误,却发现现在已经完全正常了,使用 machinectl login 可以获取到容器的正常的 Login Shell... 天知道把我折腾的要死要活的那个问题是怎么回事…… 而且从出现那个问题到现在我并没有更新服务器上的任何软件,也没有针对这个问题做任何处理…… 而当时我重启了不知道多少遍都完全没有作用。

假如你遇到这个问题,也许你坐和放宽一下,就好了。

容器内的内核模块问题 (OverlayFS / ip6tables / FUSE ...)

遇到这种问题其实觉得自己很智障,但是还是要花几句话说一次,容器内是没有办法加载内核模块的,而 Linux 启动的时候默认很多模块都没有加载。那些模块正常情况下会被使用它们的程序自动加载,但是在容器里这是不行的。所以如果你在使用程序的时候遇到这种问题,请记住在主机里加载它需要的模块后再试 (推荐加入 /etc/modules-load.d/ 自动载入)

Systemd-nspawn 内运行 Docker

这个需求看起来很无厘头,但是我觉得我的使用场景是有道理的。我有一个 Mastodon 节点,这个东西是主要使用 Ruby on Rails 编写但同时有很多其他依赖的东西。在以前的机器上,我是使用 docker-compose 通过容器的组合在一个虚拟机里直接部署上这一系列依赖。而现在我需要迁移到我的新独服上,我不能使用虚拟机,也不想自找麻烦手动部署,也不想让 Docker 产生的一大堆网络接口之类的东西挂在主机的 namespace 里。总而言之,因为这种 Ruby on Rails 程序是好多个大怪兽,虽然各自有笼子,但是因为数量比较多,分散放置还是感觉很凌乱,所以我想进一步把它们的笼子也都关进一个动物园里统一管理。

但是问题来了,systemd-nspawn 似乎并不支持在它内部再启动 Docker 容器。尝试使用 Docker 容器会直接带来 Operation not permitted 异常。从 https://github.com/opencontainers/runc/issues/1456 了解到,Docker 依赖了 cgroups 功能,并且需要比较高的权限,而在默认情况下,systemd-nspawn 是隔离了 cgroup 命名空间的,而且也没有给予不必要的权限。所以,作为测试,我在 /etc/systemd/nspawn/myContainer.nspawn 里加入了

[Exec]
Capability=all

[Files]
Bind=/sys/fs/cgroup

这在事实上把主机的 cgroup 命名空间共享给了容器里的系统,并给予了所有可以给予的 Capabilities。同时,还需要关闭 systemd-nspawncgroup 隔离功能,只需要 systemctl edit [email protected]

[Service]
Environment=SYSTEMD_NSPAWN_USE_CGNS=0

到了这一步,我期望 Docker 已经可以使用了,但是很不幸的是,并不能。这回出现的是一个莫名奇妙的 session key 无法创建的异常。这次这个异常我就完全没有看懂了……

还好,经过一番 Google,我了解到这其实是因为 Docker 在尝试使用 kernel keyring,而这个功能是不支持(ref: https://github.com/moby/moby/issues/10939)命名空间隔离的。所以,为了安全,systemd-nspawn 默认把与此相关的系统调用都过滤掉了,不允许内部的系统调用。因此,只需要开启这两个系统调用的权限(在 /etc/systemd/nspawn/myContainer.nspawn[Exec] 段中加入)

SystemCallFilter=add_key keyctl

然后重启 nspawn 容器即可使用 Docker

Docker 正常运行之后,我发现一个问题,那就是它在使用非常慢、非常不科学的 vfs 作为存储后端。根据文档,这个存储后端会对每个 layer 都创建一个拷贝。于是我想起来了遇到的上一个问题 —— 主机没有加载 OverlayFS 的内核模块,因此默认的 overlay2 存储后端加载失败了。尝试在主机上加载 overlay 模块,然后重新启动容器里的 Docker,发现 overlay2 存储后端果然已经在正确运行了。

以上文档我已经写入 ArchWiki 上的对应章节, 因为我发现我在整个网络上都找不到关于这件事情的文档,有的只是一段 Twitter 对话,而且他们其实还并没有解决这个问题……希望我并不是唯一一个有这种奇葩需求的人吧。

当然,这么做以后,这个 nspawn 容器就成为了名副其实的特权容器,拥有很多很多高权限操作的能力。考虑到我的本意仅仅是出于洁癖一般的理由,这个问题我觉得并不是非常大……总之,给大家一个参考。

容器内使用 FUSE

FUSE 是指用户态文件系统,比如 sshfs, ntfs-3g 等。想要直接在 systemd-nspawn 容器里使用它们是会直接失败的。当然,这个解决办法很简单,因为这仅仅是因为容器里没有 /dev/fuse

首先要确保主机上加载了 fuse 内核模块。然后,你需要在 /etc/systemd/nspawn/myContainer.nspawn 加入

[Exec]
Capability=CAP_MKNOD
DeviceAllow=/dev/fuse rwm

(注:如果你前面已经 Capability 设为 all 了,那就不用再单独设置一次 CAP_MKNOD 了)

然后在容器里执行

mknod /dev/fuse c 10 229

即可。

其他:网络配置

网络配置这算是一点附加说明,就是如果你想要给容器使用静态 IP,或者你想给容器使用 IPv6,你需要首先在 /etc/systemd/nspawn/myContainer.nspawn 里给容器增加一个网络接口

[Network]
VirtualEthernetExtra=name_on_host:name_in_container

然后分别在主机和容器里配置对应的网络接口即可。

当然,你可能也需要

[Network]
Private=true
VirtualEthernet=true

虽然这些应该是默认的。

结论

Systemd 坑很多,而且很玄学,但这并不影响我卖底裤(笑)。

GPD Pocket 上手 & ArchLinux

之前在 archlinux-cn-offtopic 群组里偶然看见 farseerfc 教授在晒图,是一台看起来非常非常小的电脑,但是却赫然写着 x86_64 并运行着 ArchLinux。我当时就起了兴趣,因为我一直苦于整天搬着一台 1.7kg 重的 XPS15,想要一台比较迷你而且便携的 x86 设备。我想要 x86 设备的理由是在我眼里只有 x86 才能算是完整的 PC 体验:有些 ARM 平台确实性能很好,可是连主线 Linux 都跑不了,又能算什么 PC 呢……

在 YouTube 上逛了一圈以后,感觉负面评价不是很多,加上双十一又有一定程度的打折活动,自己又真的非常想要,就在本周早些入手了一台。入手价格是 3000 人民币。

于是,这里是一个简单的评测和对我装 ArchLinux 过程中遇到的坑的记录。

(本文有补充编辑的内容,可能下面提及的部分问题已经被我解决,如果想看请直接翻到最后。)

配置

首先看一下它的配置。

  • CPU: Intel Atom x7-Z8750 (1.6GHz)
  • 存储: 128 GB _eMMC _(不是 SSD)
  • RAM: 8 GB DDRIII 1066MHz
  • 图形: Intel HD Graphics 405
  • WiFi / Bluetooth: BCM4356
  • 显示屏: 7吋 IPS 多点触控 (1920 x 1200)
  • 音频: Realtek ALC5645 双声道 (扬声器为单声道)
  • 电池: 7000 mAh
  • 接口: 1x USB Type-A, 1x 3.5mm 音频, 1x HDMI MINI, 1x USB Type-C (可充电, 使用 PD 协议)
  • 系统: 预装 Windows 10 Home, 可运行 Linux

买之前最担心的是 CPU 和 WiFi / 蓝牙 芯片。CPU 感觉性能可能不太足够,而 BCM4356 这个……看名字就知道,又是 BCM,进了 Linux 必然有坑 :( 斜眼看我的同样是 BCM 芯片的 XPS15……

但是担心终于没有战胜我想要剁手的心情。思考了几天之后我还是没忍住入手了。这也是我第一次发现某宝上还有 “拍下后联系卖家改优惠价” 一说 XD

上手

拿到手的第一感觉是这个东西真的很精致。7 英寸的身体看起来很娇小,但是拿在手里又感觉非常结实。重量 480g 可以算是一款比较轻便的设备了,装在口袋里也不显得比较沉,而且它 确实 可以装进口袋里,是名副其实的「Pocket Device」。

铝制的外壳看起来则非常 MacBook,正如大部分评测里所说的一样。我不想评价这是不是好事,我只能说这个外壳的手感算是我碰过的设备里最好的一个。而这块 IPS 屏幕则绝对是一个惊喜 —— 颜色非常正,没有坏点,也没有漏光问题。7吋的 1920x1200 屏幕看起来非常清晰(当然,这也导致了之后运行 Linux 的时候遇到的一些问题)。整个屏幕看起来的舒适程度要胜过我的 XPS15。当然,我手上的这台 XPS15 的屏幕有几个坏点,而且不属于 HiDPI 范围,似乎也没有什么可比较的……这里有一点要吐槽的是官方送的那一块贴膜的 正反面标反了,直接导致我把那块贴膜给贴废了……从其他几位用户那里得知这似乎是普遍情况,请各位想要购买的朋友注意了。

预装的是 Windows 10 Home。我不知道盒子上贴的序列号有什么用,因为一开机就已经是激活状态了 —— 也许是给重装使用的?我只开机了一次稍微测试了一下各种功能确认没有问题以后就把 Windows 10 格式化掉然后安装 ArchLinux 了。在讨论这个设备上安装 Linux 的过程之前,我想先讨论几个别的问题。

键盘

之所以把键盘单独拿出来说,是因为这个键盘是很多评测吐槽的对象。确实,因为只有 7 吋的大小,这个键盘的布局非常奇葩 —— 大写锁定缩的小小的被塞在 A 的左边,整个 A 行被往右平移了,退格在上删除在下,几个特殊符号被塞在了右下角。适应了全键盘以后,再使用这个键盘显得非常困难。

不过,把完整的键盘塞在这么小一个设备上也不是什么容易的事情。又要完整的键盘,又要键的大小足够手指敲击,相当的困难。GPD 家的前代作品 GPD Win 就有一个非常奇葩的键盘,我上上周使用过一会儿别人的 GPD Win,觉得那个超级迷你版的全键盘才更加的恐怖 —— 问题不在于布局,而在于那个东西上的键盘的键都只有不到一个指甲盖的大小……

在使用了两天之后,我觉得 GPD Pocket 的这块键盘还算可以接受。稍微适应以后双手打字并没有太大的问题,两个手也不会撞在一起,只是当用到一些键位特殊的键的时候需要反应一段时间。当然,我也不会建议谁在这个设备上输入大段的文字。还有一种操作方法是双手握持设备然后用拇指敲击键盘,但是这样的话使用指点杆稍微有点难受。说到指点杆,我觉得这个设备上使用指点杆简直是绝配了,有完整的鼠标体验而且节省了空间 —— 只可惜这个指点杆不支持中键滚动。

性能

性能是很多人关注的问题,而实话实说,这个设备的性能绝对不算好,也算是比较长的续航的代价之一吧。原装的 Windows 我没有详细测试过,但是我运行的 GNOME 时常有卡顿的现象存在。考虑到 GNOME 大量依赖 JavaScript,我猜测如果使用 KDE Plasma 的话可能会好很多。

不过我使用 Firefox Nightly 进行基本的网页浏览并没有遇到太大的问题,基本上都能够胜任。看 YouTube 1080P 也没有太大的压力,而 4K 则经常出现掉帧。在访问大量使用 JS 的网站的时候,例如淘宝,耗电量会有一定程度上的上升。这也在合情合理的范围之中。

我粗略尝试运行了一下 Visual Studio Code,发现基本的功能使用上是没问题的。虽然我不指望用这个进行什么高性能的开发,但是我估计应急写写代码也是完全可行的操作。在安装新字体更新 fontcache 的时候则会感受到明显的卡死现象,这时候 CPU 占用变成 100%,显然是性能不足了。好在这种操作也不会天天执行。

作为一个(伪)音乐爱好者,我也尝试了使用这个东西作为 MIDI 合成器,结果是几乎完美。只是扬声器比较烂,需要自己插耳机解决 :(

也算是终于不用拖老远的线把它接到我的笔记本电脑上了(这个键盘附近已经放不下我的大 XPS 了)

对于性能这个话题,总而言之,它不是一个高性能设备,如果你是为了性能而来,那有更多的设备可供选择。但是它是绝对可以满足基本的使用需求的,甚至可以进行一点低性能要求的开发工作。游戏运行我暂时没有测试,根据其他的评测所言,进行一些微调以后,这个 Intel HD Graphics 405 是足够胜任简单的 3D 游戏的。

P.S. 我并没有进行跑分,但是昨天晚上运行了一下 openssl speed rsa2048openssl speed ecdsap256,结果分别是 219.7 sign/s + 7584.8 verify/s5965.6 sign/s + 2638.2 verify/s,供各位参考。

ArchLinux 安装

オニーチャン、ArchLinux をインストールしてください。

emmm 开玩笑的。不过说了这么多,是时候安装 ArchLinux 了。我们伟大的先驱者(雾)们已经在 ArchWiki 上给 GPDP 开了一个页面来描述可能遇到的问题和解决方案,链接在 这里。下面对于这些已经提及的问题可能就不再描述了。

首先是安装的方式。要从 USB 启动,你需要首先进入 BIOS 关闭 Fast boot。进入 BIOS 的方法是开机狂敲 Del。BIOS 内屏幕的方向是错误的,你需要把设备旋转过来才能操作。建议不要使用鼠标而是使用方向键来选择,电源键确认。关闭之后,插上 ArchLinux 引导介质,然后在开机的时候按 F7 (注意你需要按住 Fn 键以使用 F 系列按键),即可选择 USB 引导。需要注意的是它只能使用 UEFI 的引导介质。

引导进入 ArchLinux 安装环境之后默认的屏幕旋转也是错误的。要想解决这个问题,需要在引导进入安装环境之前的启动菜单(systemd-boot)界面上按 e 编辑内核命令行,在最后加入 fbcon=rotate:1。如此操作之后启动就是正确的屏幕方向了。进入之后的命令行的字实在太小,可以暂时使用 setfont sun12x22 来获得一个稍微大点的字体。

之后的操作和标准的 ArchLinux 安装过程一样,只是磁盘路径比较特殊,是 /dev/mmcblk0,因为这个小家伙使用的是 eMMC。不过,在安装盘里是没法正常使用 WiFi 的,你可以选择使用 USB 有线网络或者干脆直接用安卓手机来共享网络进行安装。安装之后参照 ArchWiki 上的 WiFi 部分,把两个文件 brcmfmac4356-pcie.{txt,bin} 放入 /lib/firmware/brcm/ 就可以正常使用无线网络了。

在设置声音的时候,似乎 ArchWiki 上提供的配置中的最后一行

set-sink-port alsa_output.platform-cht-bsw-rt5645.HiFi__hw_chtrt5645__sink [Out] Speaker

会导致 PulseAudio 直接启动不了。我直接删除了这一行,~~然后在桌面环境里选择默认输出,解决了这个问题。~~ 后来发现正确的配置应当是这样

set-card-profile alsa_card.platform-cht-bsw-rt5645 HiFi
set-default-sink alsa_output.platform-cht-bsw-rt5645.HiFi__hw_chtrt5645_0__sink

以上内容添加进 /etc/pulse/default.pa 即可

ArchLinux 默认安装的是主线内核。使用主线内核是可以正常启动 GPDP 的,大部分功能也是可用的,除了 亮度调节、蓝牙、电池充电状态 这些功能以外。另外,主线内核的音频还存在撕裂问题。要使用这些功能,你需要使用 linux-jwrdegoede —— 这是一个以前玩 Allwinner 的大佬做的内核,使用它的话几乎全部功能都正常(你需要学会如何给 ArchLinux 使用非默认内核,这个教程网上一大堆)。当然,蓝牙的话,需要手动载入一下 btusb 模块,编辑 /etc/modules-load.d/ 里面的内容让它自动载入即可。

我一般习惯在安装环境里把命令行和网络配好就重启进入系统继续安装。在这里需要注意的问题是,当你配置 bootloader 的时候,一定要记得给内核命令行加上 fbcon=rotate:1,否则重启以后你的屏幕就又不对了 :(

桌面环境

桌面环境安装和标准方式一样,我选择了 GNOME,所以 pacman -S gnome 即可。由于 ArchWiki 上没有包含关于 GNOME Wayland 的内容,我在这里稍微描述一下遇到的问题。

首先是屏幕旋转。你需要编辑 ~/.config/monitors.xml 用以下配置把它转过来(不知道为什么我的 GNOME 没有自动生成这个文件的默认内容,以下来自于 farseerfc 提供的配置)

<monitors version="2">
  <configuration>
    <logicalmonitor>
      <x>0</x>
      <y>0</y>
      <scale>2</scale>
      <primary>yes</primary>
      <transform>
        <rotation>right</rotation>
        <flipped>no</flipped>
      </transform>
      <monitor>
        <monitorspec>
          <connector>DSI-1</connector>
          <vendor>unknown</vendor>
          <product>unknown</product>
          <serial>unknown</serial>
        </monitorspec>
        <mode>
          <width>1200</width>
          <height>1920</height>
          <rate>60.384620666503906</rate>
        </mode>
      </monitor>
    </logicalmonitor>
  </configuration>
</monitors>

保存后重新进入 GNOME 即可。这会同时把显示内容缩放为两倍大小(一倍大小在旋转正确以后实在看不见任何内容……)但是两倍有点大了,要想使用分数缩放需要执行

gsettings set org.gnome.mutter experimental-features "['scale-monitor-framebuffer']"

然后继续编辑 ~/.config/monitors.xml<scale> 那边的数值改成 1.5 之类的就可以了。不过这样设置以后部分界面会显得有点模糊,大概得等 GNOME 和软件开发者们修复 HiDPI 的问题了。有的软件也有自己的缩放设置,可能需要单独调节。另外推荐在 GNOME Tweak Tool 里把字体缩放也设置成 1.1 或者更大,这样看起来舒服一些。

如果你想要把登录界面也转过来,你需要在 /var/lib/gdm/.config/monitors.xml 中键入同样的内容。~~不过我暂时没有找到让登录界面也使用分数缩放的方法,所以我直接让它两倍缩放了。~~ 请看本文最后 EDIT 部分中让登录界面(GDM)也能分数缩放的方法。

P.S. 我从奇怪的地方看见了下面这句东西

gsettings set org.gnome.desktop.interface scaling-factor 2

似乎也是设置缩放的,但好像并不管用。

总结

似乎要说的暂时就这么多,Linux 上的更多问题在 ArchWiki 上都有详细的说明。一篇博客也差不多水完了,下面是总结

优点:

  • 便携
  • x86 完整 PC 体验
  • 屏幕养眼
  • 做工精致
  • 接口足够多

缺点:

  • 性能较低
  • 键盘布局很谜
  • WiFi 信号似乎有时候不太好
  • 自带扬声器不行,不过耳机输出还好
  • BIOS 对屏幕默认旋转设置不对导致自己装系统有点麻烦
  • 比较贵

这并不是针对每一个人的设备。如果你需要的是一个非常便携而且可爱的 x86 设备,而且你又是一个折腾党,喜欢玩各种各样的东西,那么它可能正是你的菜。否则,可能安卓平板会是更好的选择。当然,在购买之前请详细阅读各种 Wiki 和其他人的各种评测再做决定。

剩下的图

emm 还有几张和 XPS 的合照



EDIT1: 蓝牙耳机

之前蓝牙正常了一直没测试过,今天突然想起来测试一下蓝牙耳机是否可用,结果当然是 —— 默认配置下并不能工作。连接以后识别不出 A2DP,导致直接没办法输出音频……

我首先按照各种奇奇怪怪的论坛上的说明在 /etc/pulse/system.pa 里加入了

load-module module-bluez5-device
load-module module-bluez5-discover

然后按照 ArchWiki 上的说明,我禁用了 gdm 开启的 PulseAudio (创建一个 /var/lib/gdm/.config/systemd/user/pulseaudio.socket,把它软链接到 /dev/null 即可),然后使用

bluetoothctl
pair YOUR_HEADPHONE_MAC_ADDRESS
connect YOUR_HEADPHONE_MAC_ADDRESS

手动连接。之后,使用 pacmd ls 查看你的蓝牙耳机的设备编号(假设它是 INDEX),然后执行

pacmd set-card-profile INDEX a2dp_sink

耳机就可用了。不过在这之后,每次连接的时候似乎都要重新连接几次并在 GNOME 的音频设置里手动选择耳机为音频设备以后才能使用…… 至少是能用啦。

EDIT2: GDM 分数缩放

sudo machinectl [email protected]
gsettings set org.gnome.mutter experimental-features "['scale-monitor-framebuffer']"
exit

以上命令会 登录进 gdm 用户并开启分数缩放功能。注意此处用 sudo -u gdm 或者 su gdm 是无效的。执行以后将 /var/lib/gdm/.config/monitors.xml 里面的 <scale> 设置为 1.5 然后重启即可。(如果没有这个文件,参考我上面的步骤,把自己用户目录下面的那个复制过去就好。)

EDIT2: 右键模拟鼠标滚动

GPD Pocket 的指点杆不自带滚动功能,于是有人提出让它的右键变成一个模拟滚轮,也就是「按住右键并移动鼠标」这一动作来代替滚轮。

这是在 Xorg 下面可以通过配置 libinput 实现的功能,然而在 Wayland 下得看 compositor 的脸色 —— 很不幸,GNOME 并没有提供这个功能,于是很长一段时间我认为这是不可能的,然后忍受着没有滚轮功能的指点杆。

后来实在有点受不了,甚至试图修改内核来实现这个功能 —— 当然,因为不了解内核驱动,我瞎改了半天并没有起作用。后来晚上做梦的时候梦见了 LD_PRELOAD,突然惊醒,觉得我完全可以利用 LD_PRELOAD 来 hook 进 libinput 的函数,强行开启这个功能。

花了不到一个小时研究了一下 libinput 和使用 LD_PRELOAD 的方法,写出了这么一个简单的小程序 https://github.com/PeterCxy/scroll-emulation,按照使用说明编译后加入 LD_PRELOAD 即可。

基本原理是劫持桌面环境对 libinput_device_get_name 的调用,在返回之前使用这个指令序列

libinput_device_config_middle_emulation_set_enabled(device, LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED);
libinput_device_config_scroll_set_method(device, LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN);
libinput_device_config_scroll_set_button(device, 273);

对所有可以开启的设备开启 libinput 的中键滚动模拟功能。开启以后,即可使用「按住鼠标右键并移动鼠标」来模拟滚轮。

EDIT2: 键位调整

GPD Pocket 上的键盘键位很谜,尤其是退格和 Delete 放在一起,以及那个超级小的大写锁定键。我本来也想通过 hook libinput 来解决,然而 archlinux-cn-offtopic 里的大佬给了我一个更好的解决方法,那就是使用 udev 自带的 hwdb 来修改 Keymap.

简单研究了一下这玩意怎么用,写出了下面这个配置:

evdev:input:b0003v258Ap0111*
 KEYBOARD_KEY_7004c=backspace
 KEYBOARD_KEY_7002a=delete
 KEYBOARD_KEY_70039=a

以上配置的作用是 1) 交换退格和删除键 2) 将大写锁定键去掉,改成另一个A键(大写锁定可以用按住 SHIFT 来代替)。将这个配置文件放在 /etc/udev/hwdb.d/90-gpdp.hwdb 即可。

你们要的 Inline ASM 疑难解答

背景

我校 (西交利物浦) 的 信息与计算科学 专业的大二的课程中,有一门 (CSE101,计算机系统) 涉及到一个使用 MSVC Inline ASM 完成的作业。由于大部分人此前没有接触过 ASM 甚至没有接触过编程,而课程本身因为 一些原因 难以理解 (I_cant_hear_you.jpg),所以最近几天我一直收到关于这个作业的各种各样的问题,其中很多都是重复的类似的问题。当然,这也怪我本人在群里 “假装自己很会 ASM”(手写 x86 ASM 真的是第一次)。不管怎么说,因为大家都会遇到类似的问题,所以我想着我要不还是干脆写篇博客一起回答一下,以减轻我的多线程工作负担(x)。

当然,写这篇文章的直接原因是应某同学的要求 —— 她发给我一篇来自其他同学的教程,让我修改一下其中可能存在的没有讲清楚的点。于是我决定还是自己从头写一份好了……

作业要求

使用 MSVC Inline ASM 编写一个满足如下要求的程序

  1. 让用户输入一个在 [2, 5] 之间的整数 n
  2. 循环 n 次,每次问用户索取一个新的正整数(如果不是,退出循环,跳到 3)
  3. 当循环退出的时候,打印退出消息并打印总循环次数
  4. 从小到大排序用户输入的数字并输出
  5. 输出用户输入的数字的总和

示例输出

Select total number of positive integers (between 2-5): 5

Enter positive integer 1: 67

Enter positive integer 2: 13

Enter positive integer 3: 21

Enter positive integer 4: 39

Enter positive integer 5: -9

Program terminates and has looped 4 times.

Your integers from lowest to highest is 13, 21, 39, 67

The total amount is 140

技能和前置要求

部分来自 来自其他同学的教程

  • Microsoft Visual Studio, 非 Windows 用户请使用虚拟机或者双系统
  • Visual Studio 的基本用法和调试器使用方法
  • C / C++ 的 基础的基础 (变量定义,数组定义,printfscanf
  • 基本的 x86 汇编指令和寄存器知识
  • MSVC Inline ASM 中与 C 部分交互的知识
  • 指针 和 值 的区别 (leamov 作用有啥不同,方括号有啥作用)
  • 什么是 esp
  • 理解冒泡排序算法

声明

这篇文章仅作参考使用,本文中不会附带完整的源代码片段,更不会直接给出作业的答案。本人无法保证该文的绝对正确性,以下全部是本人在自己完成这个作业的时候得出的经验性结论。我此前没有学习过 x86 ASM, 也无法对该话题作出最精确的描述。

作为未来的软件工程师,应该善用搜索引擎,知识库和问答网站(此处指 Google / Bing / DuckDuckGo,问答网站包括 StackOverflow,对于这个作业的知识库包括 Wikipedia / MSVC 官方文档不包括 百度搜索 / 百度文库 / 百度百科 / 百度知道)。我也从来没有专门学习过 x86 ASM,以下所有知识都是我在国庆假期前做这个作业的时候通过这些渠道获得。没有人能一直给出答案或者专门为了一个问题编写教程和问答,请一定要明白这一点。

(偷偷附加一点,我其实对这个课程使用 MSVC Inline ASM 挺有微词,不过主要是因为我的日常系统是 Linux,而 C 的不同实现之间的内联汇编并不兼容。况且如此使用 ASM 还依赖了一部分 C 的知识 —— 倒不如给学生提供一个能进行输入输出的库,然后让学生直接使用一个专门的汇编器来做这个作业。)

输入 / 输出

大部分人遇到的第一个问题就是如何输出字符串,如何读入来自用户的输入。

这个问题实际上就是使用 C 标准库中的两个函数

// 输出
printf(format, arg1, arg2, arg3....);
// 输入
scanf(format, &some_variable);

我在这里不想细讲这两个函数是怎么回事,而是提供在本作业中会用到的特殊情况。

1. 输出一个字符串,例如 Hello World

在 C 中是这样写的

printf("Hello World");

转换成内联汇编

const char msg1[] = "Hello World";

__asm {
  ........
  lea    eax, msg1
  push   eax
  call   printf
  add    esp, 4
}

由于这时候 printf 只消耗了一个参数(要打印的那个字符串),所以后面对栈顶指针 esp 加了 4 (4 = 1 * 4)

2. 输出一个整数变量,例如循环变量 i

在 C 中是这样写的

int i = 1; // 假设你有个变量 i,不一定是定值 1
printf("%d", i);

这里的 %d 是一个占位符,表示稍后输出的时候把这里替换成一个整数。

转换成内联汇编

int i = 1; // 同上
const char format1[] = "%d";

__asm {
  ........
  mov   eax, i
  push  eax
  lea   eax, format1
  push  eax
  call  printf
  add   esp, 8
}

当然,这里的 %d 还可以附加上其他内容,比如 Your number is: %d, 输出的时候这个 %d 所在的位置还是会被换成你传入的那个整数变量。

注意,这里由于 printf 消耗了两个参数,所以对 esp 增加了 8 (8 = 2 * 4)

同时,给 printf 传值的时候,如果传入整数变量,请使用 mov,而字符串请使用 lea,这是传值和传指针的区别,我不想在这里细讲(涉及到 C 的字符串和内存结构的问题)

3. 请用户输入一个整数,并存储到一个变量,比如 j

使用 C

int j; // 来个空变量
scanf("%d", &j);

应该不难看出来,这里的第一个参数是和 printf 一样的格式。%d 表示此处将要读入一个整数输入。

转换成内联汇编

int j;
const char format2[] = "%d";

__asm {
  ........
  lea   eax, j
  push  eax
  lea   eax, format
  push  eax
  call  scanf
  add   esp, 8
}

此处 j 同样是个整数变量,但是却使用了 lea 而不是 mov,原因是 scanf 需要一个内存指针以便把读取的内容写入变量(注意 C 版本中的那个 &j,即取指针)。

这里由于 scanf 消耗了两个参数,所以同样给 esp 增加 8。

当然,你在要求用户输入之前,可能最好先输出一个消息,提示用户需要输入什么内容。

_P.S.: 如果要换行,可以在字符串中使用 \n _

总而言之,在内联汇编中调用 C 的函数,主要的点就是把参数或者参数的指针倒序推进栈里,然后 call 函数,然后根据参数数量调整 esp 的值。

循环

循环其实在课件里讲的挺清楚的,就是 loop 这个指令,它会把 ecx 减 1,如果此时 ecx 是零了,那就跳出循环,否则跳回循环开始的地方继续循环。

只有一个可能踩到的坑,那就是 ecx 这个值实际上是 没有保证 的 —— 当你调用其他函数,比如说按照上面讲到的方法进行输入输出的时候,由于被调用的函数里也可能存在循环,当输入输出完成的时候,这个 ecx 的值就可能已经被改变了,由此会导致你的 loop 判断失效。

解决这个问题的办法非常简单,只需要在循环的开始保存 ecx 的值,并在调用 loop 指令之前将其恢复。有两种办法:

1. 利用栈来保存

我们可以在循环的开始把 ecx 推进栈,再在循环的结尾把它弹出

__asm {
  ........
  L1:
  push  ecx
  ........
  pop   ecx
  loop  L1
}

有的人问过我,这个栈不是用来传参数的吗?它确实 可以 用来给 printf 这样的函数传参数,但是,如果你给函数传了两个参数,当函数被调用结束以后,我们自行恢复了栈顶指针 esp 的值(你应该已经注意到上面对 esp 进行的加操作),由此保证了我们自己推进去的 ecx 的值永远在栈顶 —— 所以最后只需要一次 pop 就可以拿到 ecx 的值。这也是为什么 call 完成以后,对 esp 的加操作的数值 (4, 8, 12...) 只依赖于被调用的函数消耗了多少参数(参数数量 * 4)而不是总共进行了多少次 push

2. 利用变量来保存

其实是一回事,只不过这次我们让 C 自己帮我们在栈上分配空间来保存而已。

int i;

__asm {
  ........
  L1:
  mov   i, ecx
  ........
  mov   ecx, i
  loop  L1
}

我想我就不必再多加解释了。

不使用 loop

你也可以完全不使用 loop,自己使用 jmp 系列指令并自己维护循环计数器来完成循环操作。这也是我使用的方法,但在这里不想细说 —— 基本的逻辑和 loop 是完全一样的。

计算循环次数

这是部分人碰到的一个问题。他们直接把循环中的 ecx 当成「第几次循环」来输出,结果发现这个值是倒着走的 —— 请仔细看 loop 指令的说明,你就明白为什么了。

要想输出正向递增的循环次数,你有两种途径,一是自行维护一个往上走的循环变量(或者干脆像我一样不使用 loop

int k = 0;

__asm {
  ........
  L1:
  push  ecx
  mov   eax, k
  add   eax, 1
  mov   k, eax
  ........
  pop   ecx
  loop  L1
}

这个 k 就是当前循环的次数了。另一个方法是你只要对 ecx 进行一点简单的运算就可以

int total;

__asm {
  ........
  mov   total, ecx // 把总循环次数保存下来
  L1:
  push  ecx
  ........
  // 要用到当前循环次数的地方
  pop   ecx // 还原 ecx
  push  ecx // 赶紧把它保存回去,这时候 ecx 仍然是读出来的值
  mov   eax, total
  sub   eax, ecx
  add   eax, 1 // total - ecx + 1
  // 此时 eax 的值就是当前循环次数了
  ........
  pop   ecx
  loop  L1
}

数组读写

  1. 循环 n 次,每次问用户索取一个新的正整数(如果不是,退出循环,跳到 3)

中,由于你需要循环 n 次向用户获取正整数,所以你需要一个数组来保存输入的数值。如果你不知道数组是什么,请自行搜索。

这个作业中我们只需要定义一个长度为 5 的数组就好了,因为 n 最大只能是 5 (在 [2, 5] 之外的输入是非法的,你需要进行判断并输出错误信息)。像这样在 C 中定义一个长度为 5 的数组

int arr[5];

这个 arr 变量就是一个长度为 5 的数组。C 中的整型数组实际上是内存中的连续区域,以 4 字节划分,而变量名 arr 对应的就是它的第一个成员的地址。在这个地址上,+4 即可获取第二个成员,+8 即可获取第三个成员,以此类推。

比如我们修改上面 输入 / 输出 部分的例子,把用户的输入存进 arr 的第 (i + 1) 个成员里(i 大概会是你的循环的当前次数,请看上面关于循环部分的描述)

int i; // 这个变量你可以用来保存当前循环次数,我不作强制规定,这里只是个定义
int arr[5];
const char format2[] = "%d";

__asm {
  ........
  lea   eax, arr[i * 4]
  push  eax
  lea   eax, format2
  push  eax
  call  scanf
  add   esp, 8
}

以上汇编对应 C 的代码

int i; // 只是个定义
int arr[5]; // 也只是个定义
scanf("%d", &arr[i]); // C 版本里不需要乘 4

比对一下这个汇编代码和原本读取输入的汇编代码的区别,你就知道如果要输出数组成员该怎么做了。实际上就是把原来的变量名替换成了 arr[i * 4],之所以乘 4,就是因为我上面提及的内存结构。请再次注意,这里我说的是使用第 (i + 1) 个成员,而不是第 i 个。也就是说,这个计数 i 是要从零开始的,i = 0 代表第一个。

当然,你也可以学习来自其他同学的教程中的做法

int num_array[5];
_asm{
  .......
  lea   ebx, num_array 
  Loop1:
  ........
  //只写存值的部分
  mov   edi, temp // temp 是一个临时变量,内容应该是本次接受的用户输入的整数
  mov   [ebx], edi 
  add   ebx, 4
  ........
}

这里的做法是,在循环开始之前,先把 num_array 的起始成员的地址放进 ebx 中,每次循环的结尾对 ebx 加 4,这就意味着,在第 k 次循环的时候,ebx 中永远是数组 num_array 的第 k 个成员的 指针。于是,mov [ebx], something 就意味着把 something 的值复制到 ebx 这个寄存器中的 那个指针 所指向的内存区域,也就是 num_array 的第 k 个成员。([ebx] 这个中括号的作用就在这里。如果没有中括号,就是直接赋值给 ebx 寄存器,而不是 它所含有的指针 所指向的内存区域)

当然,这么做的话,你会需要像保存 ecx 一样,保存 ebx 的值,以免它被莫名其妙修改 —— 具体怎么做,我在上面的 循环 部分已经描述过了。

排序

这里大部分人都打算使用冒泡排序 —— 反正我也不高兴用汇编写什么快排……

冒泡排序的思路和具体算法,我就不想在此赘述了,作为最简单的排序算法之一,到处都能查询到。不过要注意的是,由于课件上给出的示例代码是 MASM,你并不能直接把它用在内联汇编中 —— 这个算法过于简单,也没有这个必要。排序本身并没有什么难的,之所以很多人卡在这里,其实大部分都是因为不会使用循环和数组操作。

我们来看看冒泡排序中涉及到的操作

  1. 两层循环: 和一层循环在操作上并没有什么区别,仍然是那几个注意事项。只要你在内外层都做好 ecx 的保护工作,两层循环就完全不会互相影响
  2. 读取数组成员: 上面已经讲过
  3. 比较大小: 就是 cmp 系列指令和 jmp 系列指令组合
  4. 交换数组成员: 和读取数组成员是一样的,上面也已经讲过,无非几次 movarr[i * 4] 来替代变量名)

就这么多了。最后的输出,你还需要另一个循环,把排序好的数组成员一个个输出,这也是之前已经提及的。

“按任意键退出”

大部分人都遇到了这么一个问题:写好的程序,戳运行,然后黑框框一闪而过,输出了什么都看不到。

所以你需要做一个按任意键退出的功能,其实就是等待用户输入一个任意的字符。实现也很简单,在汇编部分的最后

__asm {
  ........
  call getchar
  call getchar
}

是的,大部分情况下你需要 call 两次 getchar,原因是前面 scanf 会在标准输入中留下一个换行符,然后被 getchar 读取,导致第一个 getchar 立即返回从而失效。

你可以在调用两次 getchar 之前先打印一条消息,比如 Press any key to exit... 来获得更好的 用户体验

后记

关于这个问题,我能说的也就这么多。也感谢一直在问我这些问题的同学(们),否则我可能到现在还在看错作业要求(……)

以上大部分问题都可以在搜索引擎/问答网站上找到答案,这也是我完成这个作业的方法。当然我觉得这个课程最好在讲一定的 C 语言知识以后再开设,因为其实整个过程就是在充当 C 的人脑编译器,把 C 源代码(的一部分)人肉编译成 ASM。

作为工程师,解决问题的能力肯定是必要条件。

从 root 手机说起

昨天在知乎上回答了一个问题

如何评价魅族Flyme系统即将关闭root功能?

问题的内容大致是,魅族计划以「安全」为理由去除系统中的 root 功能,请问大家如何看待。我在回答中主要提及了「用户对自己购买的手机的控制权」这一问题。当然,在知乎上发表这类言论,必然的引来了评论区的一场“大战”。由于知乎实在不是一个保存和展示文字内容的好地方,所以我选择把更详细的内容放在这篇文章里阐述。

写在前面

在讨论评论中一些人的问题之前,我想我有必要重新摆明我在这种事情上的立场。

第一,我认为自己购买的设备应当是完全属于自己,也就是说「购买」这个行为是针对所有权而不应该是使用权。当我购买手机的时候,我不是在购买手机这个设备的使用权,而是完整的所有权。我应该能够在这个设备上运行任何我希望且能够运行的软件而不受厂商的控制。当然,在今天,这个要求其实是很高的 —— 比如越狱就是一个合法性一直饱受争议的行为 [1],尽管从我的角度来看,「越狱违法」是很不可理喻的。同时,也有很多人提及,购买手机时立下的用户协议中,可能就是仅仅授予了使用权,而其他一切都与最终用户无关 —— 这正是我想要反对的东西。令人欣慰的是,EFF [2] 等组织也一直在为了实现这个目标而努力。

第二,「Root」这一行为的目的是获取自己设备的更高控制权,这是正当而且合理的行为,而不是需要隐藏的不齿行为。「Root」不是为了破解别人的软件而存在,不是为了侵犯开发者的利益而存在,是用户为了保护自己的利益和获取自己应有的控制权的正当手段。通过「Root」可以更好地定制自己的 Android 设备,包括但不限于内核参数调节、使用主题或自定义字体、更方便地使用非官方应用市场、控制某些软件滥用权限或滥用后台等等。

第三,厂商应该通过为用户提供产品和服务获取利益,而不是通过限制用户的自由获取利益。在这里我想强调的是,用户花钱买的是产品和服务,而不是花钱去买“爹”。厂商的目标是切合用户需求而不是控制用户的需求,是听取用户想要什么而不是告诉用户应该要什么。所有举措,包括所谓安全举措,前提都必须是用户拥有知情权和选择权,他应当有选择拒绝的权利,哪怕是「拒绝安全」,这也都是自己的选择。厂商没有必要为用户的选择承担责任,但必须要确保用户有能力进行自己的选择。对于不能满足自己要求的厂商,用户可以选择不买,不买的同时更加有权利向他人说明自己拒绝该厂商的理由以供他人参考。

第四,本人关于该话题的所有观点,其重点都在于「能不能」,而不是「会不会」。也就是说,如果有的人,他追求的是简单方便,他没有那个心情去折腾什么自定义,也没有兴趣去获取更高的控制权,那么我完全不反对,因为这是他的选择。但是,同样也会有人不喜欢什么简单方便,什么傻瓜式设计,他需要运行他自己的软件,因此需要更高的权限,他也应该有这个能力去作出这样的选择 —— 毕竟,这是智能设备,不是大哥大板砖。「我不会去 root」「他也不会去 root」「我认识的人都不 root」都不能成为直接关闭这个功能的理由 —— 因为它不是一个简单的功能,它是用户控制设备的一个途径。

以上就是我对于这个问题的立场,以下所有讨论都是在上面的立场的基础上进行的。

苹果设备

发布了这些言论以后,最早收到的评论就是「那么苹果呢?」,更有人说我对苹果就“跪舔”而对 Android 就要求很多。老实说,我完全没有在知乎上发表过我对苹果设备的看法,因此我完全不知道他们是从哪里看出来我对苹果设备的态度,不过既然这样说了,我就可以顺便阐述一下自己对苹果设备(注:此处相应地只讨论 iOS 设备,即 iPhone, iPad, iPod Touch)的观点。

首先我完全不否认苹果在乔布斯时代的设计水平,乔布斯也是我比较佩服的人物之一,我自己和我的家人也使用过或者正在使用部分苹果产品。但是,这并不代表我认同他们的产品理念,也并不代表我认同他们在软件方面的策略。我不喜欢用「X宗罪」的形式评价一个产品,但是在这一点上我实在觉得自己有点数不清苹果究竟有多少「没法用」的地方。因此,我想概述 Richard M. Stallman 在自己的博客 [3] 上写明的 一部分 我认同的「不使用苹果设备的理由」

  1. 苹果的应用商店有严格的审查制度,并且在正常的苹果设备上使用第三方商店是不可能的。苹果对于自己商店里的应有有比法律法规更严格的审查,直接导致有些应用是根本无法在苹果的设备上使用的。同时,这个审查制度也具有双重标准,比如 iOS 上至今没有一款非 Safari 内核的浏览器(因为可执行代码的问题),可是一些厂商的“热更新”技术却能在应用商店上架。

  2. 苹果曾(且仍然在)尝试阻止用户修理自己的设备,并且试图阻止使其合法的法案通过 [4]。iPhone 7 甚至有通过让设备“变砖”来阻止拆开修理的技术手段 [5]。他们不把用户当成客户,而是当成自己控制的对象。他们想要成为你的「老大哥」。

这两点正是最重要的两点。总而言之,从这样的商家购买设备,你花钱买来的不是产品,也不是服务,而是一个远在千里之外的“爹”。也许有的人会喜欢这样,不需要自己动手或者动脑子,但是对不起,至少我很反感,而且我也不会再去购买苹果设备 —— 即使他们的设计再吸引人。

为什么不买个开发板

于是有人就问我,「你为什么不买个开发板,自己DIY一个手机出来用」。

这个问题非常有意思。答案是很简单的:因为我没有选择。

最重要的问题就是「基带」—— 手机的“移动通讯”功能的底层部件。现存的基带基本都是黑盒 [6],同时具有相当高的系统权限(它们本身甚至也运行了一套完整的操作系统),完全可以用于大规模监视。更严重的是,由于太过大量的通讯标准以及专利壁垒的存在,开源实现几乎是不可能的事情,这直接导致了目前市面上几乎不存在「能用的」开源实现的基带 —— 即使有,也基本上只实现了一万年前的协议和功能…… 我在 Indiegogo 上找到了一个通过逆向工程制造开源基带硬件的众筹项目 [7],但那是 2015 年的事情,众筹目标并没有达成,而且似乎很久没有后续更新了……

而没有了通讯功能,还做什么“手机”……做出来了也只是个板砖罢了。

什么?你说你想自己发明一套完全开放的标准?不好意思,你还得通过各大机构的认证,还得想办法让全世界,至少很多国家的通讯企业都接受这个标准 —— 可是这个行业是非常非常喜欢自己造轮子、自己搞自己的一套的。因为标准拿在手上就是利润,可以大肆向同行甚至竞争对手收取费用。在这些企业面前,开放的力量几乎是不存在的。

所以,对于这个问题,结论就是:不存在的。

大部分人不需要 ROOT

这是我也认同的一个观点。大部分人确实没有那个时间或者能力去折腾自己买来的手机,只要够用就好了。

可是我们这里讨论的问题是「能不能」,而不是「要不要」,「会不会」。对于一个手机小白,我也绝对不会跟他说「你买了手机先 ROOT 一下」,因为这本身是很危险的事情,他们自己都不一定知道自己在做些什么。自己不知道后果的选择不能称之为用户的选择。同理,我在这里再怎么说我 ROOT,也绝对不会给任何欺骗用户授权 ROOT 以获得更高权限的流氓软件带来任何的合理性。

换句话说,我所支持的不是 ROOT 这个行为本身,而是在关注我是否被允许、是否可以对自己购买的设备作出修改,是否能够在它上面运行官方之外的软件。你做不做这件事情不重要,那是你自己选择的事情,问题在于是否可以这么做。「是否可以」意味着身为一个用户的自由和被尊重,而自己放弃是完全合理而且没有任何问题的事情。

从另外一个角度,“大部分人”的论述真的能成立吗?比如,大部分人都不关心也搞不懂窃听之类的东西,搞不懂什么叫中间人攻击,搞不懂 HTTPS 和 HTTP 有什么区别,也搞不懂为什么使用无加密的公共 WiFi 非常危险,于是操作系统、浏览器里面实现的安全功能就能说是多余的吗?他们不关心自己的隐私和安全,就是作为软件开发者 / 厂商不关心用户的隐私和安全的理由吗?用户不关心自己是否拥有自己该有的权利和自由,就是厂商漠视这些的理由吗?不是的。作为开发者,作为厂商,当然需要(在一定程度上,在用户知情且有选择权的前提下)保护用户的权益(比如说,为了安全默认关闭 ROOT,同时允许部分用户 ROOT,但是给 ROOT 操作设置一定的门槛,让懂得后果的人才能执行),否则谈何“服务”呢。虽然作为用户,完全信任商业公司是一件比较危险的事情,但这也不能成为商业公司作恶的理由。

总而言之,关于这个问题,我想说的是

  1. 用户做不做 ROOT 之类的事情和我讨论的「能不能」是无关的
  2. 我尊重每个人自己选择
  3. 「大部分人」这种论述方式存在一些问题

即使 ROOT 了,也仍然不能控制设备

是的,这正是现在的移动平台存在的问题。除了上面我提及的基带问题之外,现在 Android 设备上都存在大量的私有驱动,通过 HAL 绕过了 Linux 内核的协议限制。同时,很多设备的 bootloader 都是不开源的,这直接导致启动过程的第一环就已经不受控制。再加上 TrustZone 等私有固件,你想要完全控制你手上的那个设备,恐怕还有很远的路要走 —— 而且这里我还仅仅列举了软件部分。到了硬件层面,那就是个更加无解的复杂问题。

所以我们能做的,只能是

  1. 争取到可能获取到的最多的权限
  2. 拒绝那些不信任用户、不给用户合理的权限的厂商
  3. 让人们知道这些不合理的东西的存在

至于更多的,就像我上面提及的关于自己做手机的问题一样,会变成非常非常困难的事情。虽然还是期待有人能够这么做,但是凭我自己的能力已经不可能了。

其他

上面已经讨论了之前评论区里被提及的几个很重要的问题。不过,评论区里还有一些我觉得非常可笑的问题,想要拿出来放在这里。

同理建议空客波音特斯拉开放系统硬件最高权限

以及

你在天朝跟政府和公司领导要root权限了嘛

首先,这完全就是不同的概念。政府和你的关系是一种契约,公司和你的关系是一种劳务关系,而你乘坐空客波音的时候仅仅是临时使用他们的服务而已。你本来就不可能拥有你的政府和你的公司,谈何索取权限?而关于波音的飞机,第一如果你只是临时乘坐,你当然不可能索取权限;第二如果你真的购买了一架飞机,你还要考虑到高权限可能给他人带来的人身损害(当然,如果你愿意把所有的锅都放到自己头上,为所有一切可能出现的问题埋单的话,你大可以这么做)。可是手机只是一个贴身的设备而已,即使真的发生了爆炸,基本上也只能损伤你自己而已。

好吧,也许你买的只是一堆没用的金属塑料玻璃材料,我们买的都是体验。

不好意思,我想我比你更注重体验。

你要那么多权限为的是什么啊?不折腾,手机就差到没法使用吗?

不好意思,我想我比你更懂手机。

手机丢了以后,root的就等着个人信息满天飞,指不定还有更有意思的发生

不好意思,你的个人信息很可能早就被你的可爱的手机厂商们、应用厂商们卖掉了 [8] —— 而你的手机还在你的手上。另外,厂商的责任永远不是告诉我应该做什么,而是告诉我我的操作的后果,让我知道后果并且在我能够接受后果的前提下进行操作。况且,ROOT 之后真的说不定更安全(笑)。一个注重隐私的人,更应该对开放的硬件和软件有更高的需求。

在美国,公民拥枪是权利。但我感觉像中国这样,把公民的这个权利剥夺了更好。当然,如果某天政府的人对我实施非法侵害,我又希望能拥枪。但一般情况下,可能我成为那59个死者和那数百伤者中的一员的可能性要大得多

我一点也看不懂这个类比是怎么回事。ROOT 手机,和禁止持枪,有任何可比性吗?持枪不需要任何多余操作就可以危及他人的人身安全,可是 ROOT 一下手机会吗?你就算把手机的温度保护之类的措施强行去掉,也不能当成炸弹使用。再者,菜刀也能杀人,菜刀比一个 ROOT 之后的手机杀伤力要强得多,可是菜刀被禁止了吗?(菜刀确实是实名购买的,但是你的手机要入网,也需要实名)因此这根本就是一个很站不住脚的偷换概念……

补充

还有一个问题(不属于上面提及的可笑的问题)

反对。买手机并使用,是遵守用户协议的合同行为。买的不是手机本身,而是合同里面规定的内容。

是的,正是这样。但是我并不是在讨论是否应该遵守用户协议,而是这些严重限制用户的协议是否合理,或者说,用户在不知不觉间订立的这些协议到底给用户带来了什么,又让用户损失了些什么。如果一定要说是协议的话,那么我的结论是这样的协议不能去订立,也就是说我,从我的观点得出,我会拒绝购买这样的手机。

参考资料

  1. US government says it's now okay to jailbreak your tablet and smart TV
  2. Jailbreaking Is Not A Crime—And EFF Is Fighting To Keep It That Way
  3. Reasons not to use Apple
  4. Apple Is Fighting A Secret War To Keep You From Repairing Your Phone
  5. The iPhone 7 Has Arbitrary Software Locks That Prevent Repair
  6. The second operating system hiding in every mobile phone
  7. Free Software Cellular Baseband
  8. Privacy Change: Apple Knows Where Your Phone Is And Is Telling People

在西浦的一年

刚刚期末考完,想到 2017 年高考也已经结束,突然意识到自己已经在西交利物浦呆了一个学年。去年的这个时候,我也是刚刚高考结束,还在想着自己那个成绩可以报什么大学,还在思考如何处理我高中所谓的“初恋”(不,是暗恋),想着要不要送生日礼物什么的。转眼已经这个时候,到了下一届的小朋友们要开始考虑这些问题的时候了。看看我的博客,也已经一整个学期都没有更新了。于是,我想以此再水一篇博客,就当回顾一下这一年来在这个学校的体验,(也许)能给这一届同学们一些参考。


为什么来这里

本来,去年这时候,我希望报的大学是隔壁的苏州大学。在高中的时候,曾经有同学跟我提起过要不要参加西交利物浦的自招,我当时根本没有把它放在考虑的列表里 —— 因为很多人报名的目的实际上是看中两年后可以出国到利物浦以及它的双文凭,而我当时并没有这些想法,再加上对学费望而生畏,所以我完全没有上这个大学的想法。

但是后来高考出成绩,我一下子就慌了。去年江苏的数学特别简单然而我砸了,这就意味着我的分数一下子比预期低了十多分(江苏的分数密集性我想不用多解释……)。这样的分数上苏大是不可能的。而由于我行动不便,希望找一个本地的大学,当时我父母建议我直接放弃一本然后去二本中比较好的苏州科技大学 —— 这在我心理上是过不去的。这就是我后来抱着试试看的态度在一本批次里填写了西交利物浦的原因 —— 至少它是个一本,虽然学费很贵。很幸运地,我恰好比录取线高出了一分,进入了这个学校。

现在想起来,其实来这里是个不错的选择。虽然有不少值得吐槽的地方,但是这里有一个好,那就是无障碍设施很完善(毕竟学费 88k 一年呢)。之前随合唱团去了一趟苏大,发现这样一个 211 学校的新校区里的无障碍设施也依然是非常令人担忧。一旦课程不在一楼,我就只能彻底抓瞎。还有食堂等地方,也是非常困难的 —— 苏科大也有同样的问题。在西交利物浦,至少这一点我是非常满意的,大部分地方都有办法无障碍地到达。

所以这些大概就是我来这里的理由。


英语(EAP)

来之前我父母曾经担心过我的英语水平是不是足够,因为学校反复说,英语教学,英语教学,英语教学……但实际上我发现,大概正常经过高考的考生,英语水平都是足够的 —— 大一一开始并不会上来就全英文教学,还有很长很长时间的 EAP (English for Academic Purposes) 课程,足够慢慢练习听力练习口语了。

但是英语在某些程度上决定在这里能不能学到点什么东西。为什么这么说呢?因为 EAP 这一系列课程有一个分层机制,就是入学初有英语水平测试,然后将学生分为 High-Level, C, B, A 四个层级。就我目前的观察来看,虽然实际上四个层级的区别并不大,但是在非 High-Level 班级里,你更可能遇到坑爹老师和坑爹队友 —— 有很多 EAP 老师甚至就只是来中国混日子的,并不是真的想要教书。当然,High-Level 班级并没有高到哪里去,High-Level 班级中也存在很多这样的现象,比如说我这学期的 EAP 课程就有一个比较坑的老师,甚至在对我们的计入总成绩的作业给分的时候都不认真读,不认真给分,很多人除了分数一样以外连老师给的反馈也一模一样,而其他老师则都是很认真地给每个人写很有针对性的反馈。(在此还是想为那些在这些人的影响下还在认真工作的 EAP 老师们点个赞……)

总体上来说,EAP 系列课程的水分还是非常大的,这并不是高或者低分层的问题……而且我对 EAP 课程的评级标准表示严重怀疑。他们声称是按照 CEFR 标准来的,但我自己的感觉是所有标准明显偏低(各位可以参考其他地方的 CEFR 测试),有放水的嫌疑。我只能说,在这个学校,EAP 课程很重要,但是为了更多地提高自己,还是要尽量进 High-Level (入学测试 >70 或者第一学期 EAP 成绩 >70),而且所学到的并非英语本身,而是学术写作的基本知识。至于英语水平本身的提高,靠学校的课程肯定是远远不够的,毕竟其标准本身的水分在那里。我也只能说,在 High-Level 里面,你更可能遇到上进的同学和负责的老师。另外,西浦的国际学生都是自动进入 High-Level, 也就意味着更多的差异性 —— 至少我是享受多样性的,我并不喜欢一整个班级清一色全都是中国人,否则我还到这个学校来做什么呢?

稍微扯远一点话题,我真的想对很多人说,你们并不是英语不好,你们只是不想读,不想看,不想听英语的内容而已。我在某些技术群里也经常看到这样的人,明明官网 Wiki 上写的很清楚,却偏偏要在群里一遍又一遍地问,还说得很可怜,“大佬们帮帮我这个萌新吧”。问之,则曰,“Wiki 是英文的,我看不懂啊”。但中国的英语教育并没有这么不堪 —— 至少,阅读,在高中/初中阶段的英语教育中,是非常重视的。我不相信任何好好上过高中/初中的人,在认真阅读的情况下,会看不懂这些文档,况且我们并不是在阅读文学著作,这些实用性的文本一般不会故意设置什么阅读障碍。即使真的不懂,借助在线英语词典,也完全足够了。不要自己以为自己不能完成,而白白放弃了学习的机会。


“政治课”

实际上这里并没有传统中国大学意义上的「政治课」。我这里所说的是带引号的,它就是指大一学生的公共课程「中国文化」(CCT)系列。我没有上过其他大学的马哲、思修,所以具体的对比我是做不到的。我只能就这个课程本身来说,它比我想象中的政治课要可以接受得多,至少上半学期的中西文化比较,下半学期的中国现代化,我所遇到的老师,在描述一些历史问题的时候,有一个比较中立的立场,并且确实是在引导大家自己思考而不是轻易相信一些被政治立场左右的观点。当然,我也有朋友告诉我说,有一些老师的立场则有明显的倾向性,就是「中国的都不好,西方的都好」然后 blahblahblah 批判一番,在此我无法考证,只能以我自己的经历为参考。遇到一个好的老师,也是一件非常重要的事情,虽然自己是无法掌控的。

当然,你大可以质疑这类课程作为通识课程存在的必要性。就我来说,如果存在这么一个要求,大学必须开设类似「政治课」的课程的话,我将会倾向于这种模式。


学风

另一个经常被吐槽的就是我校的学风。这点我是同意的。这一年里,我见过不少人,他们 CCT 课每次都只在最后几分钟到场(因为每节课都有随堂测验),然后随便抄抄完事;也有不少人,他们直到计算机课作业要交的前一天晚上,还在四处询问 “LaTeX这玩意咋用啊” “能不能帮我做一下”;也有不少人数学从头旷课到尾,最后混个及格分(不排除有些大佬认为老师讲的实在没有价值,自行学完了整个课程,这样的大佬还是挺多的;我校的数学课程也确实要求比较低;当然,这就不属于“混个及格分”的范畴了);还有人在上了一年的 EAP 课以后还在说 “凭什么我自己的观点写在文章里还要 citation”。比较宽松的环境(每天最多四节课,6.9开始的暑假和其他大量的假期)给了很多人全天摸鱼的机会 —— 包括我自己,也觉得自己来了这个学校以后少了很多干劲。

我不知道其他的中国大学是不是这么一个样子,但是这些人的行为至少和开学典礼上校长所说的景象大相径庭。好在这一年里我遇到了不少优秀的人,比如一位愿意在我的~~安利~~推荐下入 Linux 坑的同学;几位搞人工智能项目的学长;一个同样是大一但是写出了一个游戏以期赶上绿光计划末班车的同学(~~更感动的是他的游戏竟然兼容 Linux~~);一位一样喜欢二次元但认真学习并不像我一样整天死宅的小姐姐;还有艺术社团的各位辛苦工作的小哥哥小姐姐们。或许,在大学生中,这样的人才是少数?我无法回答这个问题。

或者可以这么说,这里更多地是一个培养商人的地方,而不是一个培养科学家和工程师的地方。很多人热衷于如何以最小的时间投入通过课程,而不是考虑自己要从课程里学到什么东西。私以为这不是正确的学习的逻辑。

也许,中国的大学生,在刚刚结束极端高压的高中课程之后,并不能直接适应大学的环境?不过我个人还是比较感激这样的环境,因为至少我们没有严重神经质的网络限制策略,没有严重作秀的强行参加xxxx活动,没有为了领导调研而故意拔高的出勤率(笑)。

不过我的 GitHub 绿格子,怕是彻底没救了哦。


上面可能吐槽得比较多,希望各位见谅,也不要对号入座。正是因为在这里呆了一年,对这个地方比较喜爱,所以才会看到这么多觉得无法接受的地方。如果只是写一些优势,恐怕就变成了学校官网的腔调,也就失去了意义。我自己非常享受这样一种中国国内的国际化的氛围 —— 一种少见的「自由」的氛围。

非常担心这么一篇文章发出来以后我会被喷 —— 然后自己看了一下自己博客可怜的访问量,也就释然了。各位若有不同意见,就把这篇文章权当笑话吧。请记住以上所有内容并不特指任何一个人。

说一些题外话。距离去年的高考已经一年了,而去年的这时候,就像我在开头提及的一样,正在为高中的一些很 naïve 的事情而苦恼。才不过一年,当时所谓心中很重要的人,就已经说再见了。毕竟最后我们仍然不是一个世界的人,仍然没有机会走到一起。有时候有点想嘲笑过去的自己。

所谓重要的事情,重要的人,大抵如此吧,时间会消灭一切的 —— 啊,也算是在西浦一年的体验之一吧。

差不多了,希望各位学弟学妹们也能开启自己新的生活。

以上。

自由的消逝

我第一次听说「驴得水」这部电影,是在 中西文化比较 课上。当时那一节课,是关于中西方人对性和爱情的态度的,于是在课前放了「驴得水」的宣传曲「我要你」。当时我就被这首歌曲打动了,大概是旋律非常优美,而我又正好处在这样一个渴望爱情的年龄。后来老师也向我们推荐了这一部电影。然而,在这之后我只是找到了这首歌的两个版本和吉他伴奏谱,当成一首好听的情歌循环并练习了好久。

直到昨天,我终于想起来应当把这部电影看一遍。在这之前,我所听说的对这部电影的定性都是「喜剧」。可是,在进度条走到一半之后,我却再也笑不出来了。

这绝对不是一部关于性观念和爱情的电影,更绝对不是一部喜剧。这是一部彻头彻尾的人性的悲剧。


张一曼

所有的事情,都要从这个人物说起。

在写这篇博客之前,我看过许多关于这部电影的影评。其中,关于张一曼的评价,有两个极端:一个是视其为女权的「英雄」,认为她具有一种超前的性观念; 另一种是视其为「渣女」,「荡妇」,认为这样的生活态度绝对不能作为女权的代表。

从这部电影的背景来看,它设定的年代是上个世纪,新中国成立之前。这是一个比较久远的年代。而张一曼对于性的态度简直有当代女权主义的影子 —— 我有处置我身体的权利; 性是生理需要,而爱是心理需要,两者应当分离。她为了脱离「被人管」的境地,为了自由地生活而来到了偏僻的乡村。

你看我像是那种能跟你过一辈子的人吗?

她对裴魁山的一句话就道出了她的不羁。她不愿意因为爱而被拘束,不愿意因为家庭而被拘束。因而,她可以「睡服」一个人,但不会因此而被这个人拥有 —— 没有人拥有她。她的「放荡」,看的是自己是否愿意,而不是别人的心情。这与以出卖自己身体为生的人截然不同。·她所具有的是一种追求自由的信仰。如此看来,她不失为一个性观念超前的女性,不失为一位失败的英雄。

而对她持反面观点的人所看到的是这样一件事情,一件足以毁掉她的形象的事情 —— 为了利用铜匠而和他发生关系。这一次,与其他不同,她有明显的目的,那就是利用铜匠骗过特派员; 这一次,带有明显的「交易」特性。同时,铜匠有自己的家庭,而这次的关系显然打破了这个家庭本身的平静 —— 铜匠与妻子之间因为这次事情爆发了严重的矛盾。这也是铜匠本人所不能理解、不能接受和不愿看到的。毕竟,铜匠和铜匠的妻子并没有一曼的这种性观念。

所以,既然有了这样一次事情,她还能被称为女权的先驱,还能被认为具有超前的性观念吗?还能说,她是一个追求自由的女性吗?

作为本片中唯一死亡的人物,我觉得不管怎么说,电影本身是要借她来表达一些东西的。而我对于这个人物,至少是喜欢的,并且非常同情她,惋惜她遭遇了自己所不该遭遇的不幸。我呢,不认为应当把这个人物看作「英雄」或者「流氓」中的任何一种 —— 张一曼她本身是一个矛盾的、复杂的形象。她骨子里确实有超脱世俗、追求自由的性格,这无论如何是无法否认的,从「救火」一事中也可以看出来 —— 一种「傻」、「善良」和「天真」。然而,这与她周围的人格格不入,与时代格格不入,所以她才来到了偏僻的乡村来追寻自己想要的自由。可是她并没有找到她想要的自由 —— 人性的阴暗即使在这样一个地方也存在着。她自己也无法逃脱一些人性的弱点,为了自己的「事业」不得不与阴暗的利益集团同流合污 —— 也为了自己所爱的学生们 —— 不得不做出一些违背信念的事情。正是这样的矛盾给这个角色加上了更深重的悲剧色彩。

况且,从她对铜匠的那个灿烂的微笑看来,又怎么能说,铜匠在她眼里就真的只是个牲口呢?又怎么能说,她就真的没有对这个天真可爱的铜匠动过情呢?

她也许不想利用铜匠,但是,他们都是被利用的对象。直到最后,都是这样。为了一场闹剧,没有下限地牺牲着两个无辜的小人物。


铜匠

铜匠也是一个很有意思的角色。一方面,他就是个淳朴的村民,他落后、封闭、愚昧,对新的文化毫无感知; 但另一方面,他却能够接受新的东西,具有相当的学习能力(或许只是因为对张一曼动情?),向往知识(「我也要去美国!」)。当张一曼迫于压力不得不中伤他之后,他像一个小孩子一样对张一曼发着脾气。经由旁观者们的放大,这样的脾气终于对张一曼造成了毁灭性的打击。

铜匠希望脱离自己的生活 —— 他自己也表示过对自己的生活已经受够了; 从他和妻子的关系中,我猜测他与妻子的婚姻应该也不是自由恋爱的结果,而是包办买卖式的婚姻。他并未对自己的妻子动过情,因而,对于使他动情的张一曼,他呵护有加; 因为使他动情的张一曼侮辱了他,他的情绪犹如火山爆发。在这个方面,他就像一个初恋的男孩一样,什么也不懂,什么也不会。当然,他受到利益诱惑,一样会不择手段,一样会搬出封建的那一套观点 —— 否则,他怎么会接受毫无感情基础孙佳作为自己的「新妻子」,仅仅是为了去美国留学呢?所谓「底层劳动人民」的矛盾本质,在这个人物身上得到了集中的体现。

当然,铜匠这个形象,比起其他人来说,还是简单很多的。但他,应当也是一位主角。他的悲剧,则是寻求改变自己的生活,误打误撞撞上了腐败的利益集团,最后落得个什么也没有的下场,重又回到自己百般无奈的日常。

比起张一曼来,他是幸运得多了。至少他不会因为追求什么自由而自己烦恼自己。所谓「庸人方自扰」吧。


孙佳 / 周铁男

与前面两位不一样的是,这两位,借用特派员的话说,有着典型的「知识分子脾气」。他们所追求的不是什么自由,不是什么高大上的东西,而是自己心里的那一份「正义」。他们为了自己所认准的道理,为了自己所爱的人和物,可以放弃一切,甚至置亲生父亲于不顾。

这一对,可爱得像中学时代情窦初开的情侣们。那种含苞待放的情愫,那种想说而不敢说出口的心情,那种故作矜持的姿态……让人少女心爆棚。我一度觉得,他们是这部沉重的电影中的一股清流。

然而电影的结尾给了我重重的一记打脸。这部电影的每一个角色最后面临的都是一个悲剧,没有任何一个人有着圆满的结局。

周铁男尝试顶撞特派员,被用枪指脑袋; 子弹打偏,逃过一劫的周铁男彻底放弃了自己的那一套所谓的理念,开始不再以「正义」而是以「武力」为标准,甚至看着张一曼被强奸,因对方有枪而见死不救,最后甚至「劝说」自己所喜欢的孙佳去和铜匠演一场结婚的闹剧,口口声声说着「没办法」。而孙佳,空有一腔热情,却从未下定决心付诸事实,屈从于看似「没办法」的现实。可以说,在那一枪之后,这所谓知识分子骨子里的奴性暴露得淋漓尽致。

用时髦一点的话来说,这就是对「键盘侠」的完美的演绎 —— 其中可能也包括我自己。那些道貌岸然却软弱无能的人们啊!


至于影片里的其他人,我不想再一一赘述,无非都是利益集团中的一分子或者被利益集团利用的对象。

从影片一开始几位老师无拘无束的生活,到最后张一曼自杀、孙佳离开,我似乎感觉到这部电影所描绘的是这种自由的消逝。校长屈从于利益集团,裴魁山因无法占有而心生厌恨,周铁男和孙佳放弃了自己的主张,没有一个人获得了张一曼所追求的自由,口口声声说着爱她的人们纷纷离她而去,与她背道而驰。「我要你」所讲述的,恐怕是张一曼对真正的自由的人和自由的爱的追求,而显然,剧中没有人符合这个标准。

张一曼在追求自由,而大家则离自由越来越远。自由是什么?自由就是拒绝成为自己所讨厌的样子。影片里没有人做到了这一点。人性的弱点使他们不自由,同时也使屏幕面前的我们不自由。而影片在喜剧的外衣下,用黑色幽默的手法,很讽刺地体现了这一点。这些人的嘴脸,无法避免地,会在你的亲人、你的爱人、你的朋友、你的上司,甚至整个社会的身上找到影子。

过去的让它过去,只会越来越糟

而如果我们这样下去,会不会越来越糟呢?

一声枪响,张一曼绝望地结束了自己的生命,同时带走了她一生的追求。从此以后,如同「驴得水」这样的闹剧,可能会一次又一次地上演,无论是在电影中,还是在现实中。

人性使我们追求无拘无束的自由,同时,人性又让我们放弃自己信仰的东西。成也人性,败也人性。自由是宣扬人性解放,同时又不能避免地必须战胜人性中的那些威胁着自由的阴暗的成分。我们都身负这样的双重性,我们都是这样的矛盾角色。

所谓自由,恐怕只会随着岁月的流转而渐渐消逝吧,正如整个电影的剧情一样。

我要 你在我身旁

我要 看着你梳妆

这夜色太紧张 时间太漫长 我的姑娘

我在他乡 望着月亮

这样理想的人啊,你又在何方?

致12岁

12岁,一个稍微有那么一点遥远的年龄,一段仿佛又刚刚过去仍有余味的时光。

在知乎看到了两个问题

假设现在的你看到 12 岁的你,你想对他说什么?

假设 12 岁的你看到现在的你,ta最震惊的事会是什么?

实际上是同一个问题。

看了几位答主的回答,感慨万千。看似调侃的文字后面都隐藏着各位对那时生活的回忆和各种各样一言难尽的遗憾。确实,在一段时间以后,回首过去的那些事情,会有一番不一样的体验。即便是黑历史,也仿佛变得可爱和值得怀念了。

我12岁的那一年则是在六年之前。相比一些20-30岁的答主来说,那段时光离我还不算太远。然而,短短的六年之间,我似乎已经变了。我可能已经不会认识那时的自己,而那时的自己恐怕也不太会承认现在这样一个我了。

不太喜欢知乎的编辑体验和排版。所以,我把我想写给12岁的我的那些话,作为一篇博客,放在这里。当然,我也不太喜欢调侃这些事情,所以可能稍显沉重(?),希望不要介意。


1

上课打 Minecraft 吗?

你之后会经常在上一些无聊的课之前邀请周边的几个朋友「不务正业」。

一是那时候在初中,有些课程,的确就是反反复复反反复复,为了某些跟不上的同学而一遍遍重复,一次次听确实令人厌烦; 二是在管理严格的地方做这种突破规则的事情,不免有些刺激感。

可是这些反复,往往就是为了我那几个朋友。

恕我直言,你实在有一些自我中心。看起来似乎是几个人一起「同生死共患难」,可是他们和你不一样,你即使不听几堂课也能考上隔壁高中,他们是徘徊在职高边缘的那种学生。

你会安慰自己,说你已经寻求了他们的意见,得到了同意,他们是自愿的。

这一点我无法反驳,可是他们,和你一样,未必真的知道后果。

Joy Neop 说过

我们手上都沾满鲜血

即使我们做的是我们从来都以为正确的事情。在不知不觉之间,你可能毁掉了一个人的前程。

那几位「朋友」,初中毕业以后,我就再也没有见过了,也没有联系过。我所知道的只是一位去了某职高,其他的只能说下落不明,基本都没有能上高中。

是吧。难以想象。很悲伤的故事。

你们根本就不是什么朋友。他们是被你利用来取乐的工具罢了。

如此对你说的我,却也对自己的观点打上了一个问号。或许我会再见到他们,然后将一切疑问解决,将自己的一万个「对不起」说出; 若是不再见到,或许此生也没有这个机会了。

很不好意思一开篇就说这么冲的话。尽管我也不知道。也许再过个六年我就明白了,吧。


2

啊,那就换个话题吧,谈谈技术。

实际上我现在做的事情跟你并没有什么区别……如果我没记错,你不久之后买了一个 Android 手机,开始折腾 ROM,开始尝试编译,尝试修改源代码。而在不久之前,你刚刚抛弃了易语言这个大坑。

而初三毕业以后,你会把一整个暑假花在这些上面。再过不久,你会因为高中的学业而对此感到十分疲倦,然后转向 App 开发,再后来因为实在太忙而暂停……

这些,怎么说呢,还是要坚持吧。只是我可能更愿意你没有忽略一些其他的事情。这些我稍后再谈。

不久之前你可能也刚刚开始做主机商,出售博客用的虚拟主机之类。这些是你接触 Linux 的开始。不过显然,我已经放弃了做主机商这件事情。就现在的我来说,我觉得我是对勾心斗角的市场环境非常反感的。如果我没记错的话,放弃这件事情,就在对你来说的不久之后了。但是这些不可否认地是宝贵的经历。

然而这个领域发展太快,现在的有些事情,即使早那么多年知道了也不会起作用,还是慢慢来吧。

其实倒反而希望自己当年多读了一点书; 可是学到这些东西,也未必就是坏事。只是经常觉得自己文笔太差罢了……

~~(小声) 不过你倒是真的可以尝试用CPU挖几个比特币出来留着~~


3

不久之后你可能会遭遇一系列不太好的事情。你会因此躺一整个暑假,会这样度过半个初三。

不过好像这点事情在我经历过的所有不太好的事情中,也不算什么。

往往让我不安的只是害怕这些不好的事情发生 —— 这种「害怕」的情绪本身会使我不舒服。

现在的我其实也遇到了和你之后会遇到的事情一样的情况。所以那个时候的各种感觉仿佛又重新浮现在脑海中。简直一模一样。

而你也会对将要到来的「人生第一场重大考试」感到迷茫。

无论如何,我也只能说,相信这一切都会过去。

Everything will be fine.

把它当作一个信条吧。

或许,不幸会让自己在其他方面变得更加幸运呢 —— 缘分啊,考运啊什么的。哈哈。

对了,你考不上清北的,不如踏踏实实做最好的自己吧。—— 好像打击自己也不是个好主意?

一切都需要自己慢慢去走过,这才是人生的快乐所在。


4

请务必保持自己雪亮的双眼,不要和某些人同流合污。

很不幸的是,你会这样,我改变不了。

初中开始没过多久,班上的人就会一起排挤某一个女同学,像这样

这个东西被XX碰过了,真恶心,不能用了

这种感觉。

你呢,也会为了保持和其他朋友的关系,加入这个队伍之中。即便当时的班主任一再劝说,也丝毫不会起作用。

可是能做几年的同学是多么难得的事情啊。可是你知不知道这样会对一个少女的心灵造成多大的创伤 —— 仅仅是因为她没那么漂亮?

我认为这种现象的原因还是在于从众。自始至终,没有任何一个人说过,如果你不排挤某某,就不和你做朋友 —— 绝对,绝对,绝对,没有任何一个人。自始至终,大家加入这样一个队伍,仅仅是出于害怕自己和她一样被孤立。

害怕

真是一件可怕的事情。

无形中的暴力,无形中的欺凌,就这样产生了。

我真希望你能打破这样一个怪圈。虽然我明明知道,你不能,我深深地理解自己懦弱的本性,因而也无法要求太多……

可是,能不能稍微努力一下呢。

我告诉现在的自己,也告诉你。

请珍惜自己身边的人,善待他们,因为他们都会善待你,因此他们会善待你。

共勉。

还是那句话,我们手上都沾满鲜血。


5

还有一件事不知道当讲不当讲。

三年之后,也就是上高一的时候,你会遇到你第一个喜欢的女孩子。

我知道,你这时候还持对这种言情小说一般的事情表示「噫」的态度,还完全不会想到自己有一天会喜欢上一个人,也对早恋这种事情非常忌讳,害怕老师和家长知道。

我想说的是,请不要欺骗自己。

你遇到的老师,都会是非常好的人。即使班上有那么多明显的情侣,他们也从来没有暴力拆散过一对; 而因此他们也不会做出什么出格的事情,所谓「互相信任」。

所以还有什么好害怕的呢?请不要欺骗自己。

即便最后不能永远在一起,这也一定会是一段宝贵的经历。想想,假如在高三的压力之下,有一个人能和你牵着手去吃个饭,去稍微休息一会儿,那么一天都会变得开心吧。

况且,没有经历过这种情感,今后又怎么会知道如何应对呢?

请一定要抓住这次机会。可以剧透一下,她和你所上的大学,距离不会超过500米。还有什么好害怕的吗?

当然,我没有抓住。好在机会并不是只有一次。

请我也务必要抓住这第二次机会。


说了这么多,其实自己也清楚,这些并不是给过去的自己,而是给未来的自己看的。

再多的文字也改变不了那些既成事实,只能告诉自己,以后要成为一个更好的人。

成为一个温柔的人,成为一个善良的人。

写着写着感觉自己像在写小学生的作文……

不知道再过六年,我看见这些,会有什么感想呢。抑或我根本就不会再看见这些。

差不多了,也就这么多话了,再说就变成扯家常了。如果还会想到其他重要的事情,就今后再补充吧。

以上。

再多一点点时间

我呢,今天去看了「你的名字。」。是的,和你们想象的一样,我和喜欢的人一起去看了「你的名字。」。

于是产生了一种非常讽刺性的感觉。

当电影中的三叶和泷在夕阳中短暂见面,没来得及写完名字,然后被隔离在不同的时空,因为忘记对方的名字而痛苦不堪的时候,我喜欢的人,那个重要的人,就坐在我身边,看着同样的片段。

可是我呢。泷在三叶的手上写下了「すきだ」(「喜欢你」),而我连说出这三个字的勇气都没有啊。

而且我的这一切并不是一场梦,我们活在同一个时代,同一个城市,曾经是同班同学,我打电话的话,也不会出现「不在服务区」。

这部电影就是这样,在看完以后,独自回味的时候,就不禁热泪盈眶,比在电影院里产生的情感还要强烈。因为它把几乎每个人都会经历的故事加上了奇幻的色彩,再配上精良的画面和音乐,这种「可回味」的程度,这种可以引起共鸣的力量,就突显出来了。

这个故事平凡化的版本,确实是几乎人人都会经历的。它就是一个讲述着无法传达的爱意的故事 —— 曾经互相喜欢过,但没有互相表示过; 后来随着时间的流逝,可能去了不同的学校,不同的公司,不同的城市,甚至不同的国家,于是渐渐忘了对方,忘了TA的模样,忘了TA的名字。这一段未曾发生过的恋情,也就慢慢埋没在时间的长河里了,可能到死也不会互相知道,原来某某人和我曾经互相喜欢过。

泷和三叶的故事,在这之上加上了各种各样的奇迹。从一开始的相识,即跨越三年时间的交换身体,或者说交换灵魂,这就已经是不可能发生的事情。后来,得知三叶已故之后,通过喝「口嚼酒」连接未来和过去,靠一己之力说服镇长拯救全镇三分之一人口的性命,然后在多年后与泷君团聚,这就更是只存在于日漫中的天方夜谭。可是,正是因为这个故事太过完美,太过梦幻,才会给人带来那种看完电影过后的空虚和失落的感觉。电影的情节和现实的自己一对比……

它奇幻,但它处处是真实的影子。还有一个桥段,就是两人交换身体以后互相帮助对方追喜欢的人的那一段,就是非常典型的。类似的故事,「龙与虎」就曾经演绎过。因为互相喜欢,所以想帮助对方得到幸福,然后在这个过程中终于发现自己其实深爱着对方 —— 这绝对不仅仅是桥段而已。我的某个朋友就亲身经历过这样的故事……

看过「告白实行委员会」,那部番里面的喜欢,就远远没有「你的名字。」中的爱情完美,因为它少了这样的曲折的过程,仅仅是甜美而已。相差三年的平行世界,生与死的阻隔,都拦不住这份喜欢 —— 这大概是很多人一生都在苦苦追求的完美爱情吧。

可是当他们在夕阳中的山顶,跨越时间而相会的时候,这份完美的爱情或许仅仅是希望能再多一点时间在一起,哪怕再大声说几次自己的名字,哪怕就是那么互相看几眼。—— 这些在那个时候都成了奢求。

对我来说也就是如此。我真的不敢说什么一生一世,什么「无论你在世界的哪个地方,只要记得你的名字,我一定会去见你」,或是我能为你而如何如何如何的誓言。人容易改变,世界也容易改变,甚至可能某一天就突然再也不可能见面……这都不是不可能发生的事情。我即使有中二病,也深深地知道自己不可能做到只靠自己拯救一个城镇啊。

她是我三年前,也就是高一的同班同学。我提到过,我害怕周围人的目光,我害怕老师和父母的责骂,所以我连搭讪都不敢,连聊两句天都不敢,连碰面以后看见她的笑容都感到小鹿乱撞。但我只是觉得,能和她见面的每一天都那么美好……

想来我也是比较幸运的。新海诚让「你的名字。」的男主和女主在8年后仍能再见,而我,明明已经错过了最好的时光,却仍然能够和她去只隔一条马路的两所学校。错过的还能相遇,这也许是我一生中遇到的最幸运的事情之一吧。

所以,我才终于鼓起勇气,送礼物,请吃饭,约出来一起看电影……说实话,我之前从来没有和异性朋友一起做过这些事情。

正如「你的名字。」中美丽的彗星却带来了小镇的灭顶之灾一样,任何美好的事情都可能消逝,甚至变成悲伤的回忆。但是,在这之前,在这一切发生之前 ——

请给我多一点点时间和你在一起吧,一点点也好。

拜托了。

近期随想

距离上次更新博客,已经有好几个月的时间了。我已经好久没有写博客的心情了。这几天得流感在家休息,打开自己的博客,看见很久没有维护过的首页,再看看自己高中时写博客的豪情壮志,不禁感慨万千。这么久没有更新博客,想说的话积累了太多,无奈表达和组织语言能力太差……终于决定写这么一篇没什么主题的文章,权当灌水和发泄了。


1

其实不如这么说,进入大学以后,我似乎就再也没有更新过博客了。

我不敢说我自己很忙,毕竟我仔细算了一下,每周上课不过20+小时,双休,没有晚自习,周三下午没有课程。和在其他学校的高中同学比一比,这生活简直在度假。

然后我仔细回忆了一下这几个月我做了什么在大学课程之外的事情。

答案是基本上没有做什么。除了写了几行没什么用的,没有成为完整项目的代码以外,确实基本上什么也没做。干的最多的事情也许就是开着代码编辑器发呆,然后就这么度过几个小时。

看了看自己的 GitHub 贡献数量,发现我贡献最多的时候就是高二的那一年。然而高中是绝对不可能有这么多闲暇时间的。

也许这就叫做「犯贱」吧。没有时间的时候拼命挤出时间要做自己的所谓爱好,等到有时间了,反而觉得这「爱好」没那么重要了。

呜呼。


2

十月的时候,和一个高中同学一起回高中去玩了一趟。因为大学就在本市,所以去一趟也没有什么困难的地方。

在那里,一切感受都可以用两个字来形容,那就是「怀念」。至于高三时候那种压抑、怨恨以及一点点的戾气,根本就不会进入脑海。

所以说,记忆也许就是这么一个过滤器,剩下的总是美好的,那些苦涩全部都会化作一句句轻描淡写的说笑。

这大概就是为什么,作为「过来人」,仿佛总是难以理解那些后来的人; 这大概就是为什么,每一代人都会觉得下一代人「药丸」。

因为我们善于遗忘。


3

有时候觉得自己和这所大学的氛围格格不入。

我说的就是社团和社团活动。形形色色的社团活动。不知道有什么目的的社团活动。甚至出现吉他社搞「非诚勿扰」的让人摸不着头脑的事情……

我说话可能有点冲,但这些活动中的很多一部分,确实有点为了活动而活动的感觉。想起那个莫名其妙的秋日祭……宣传海报上各种浪漫,结果到头来只是一帮死宅摆着零散的几个摊位,一群 coser 莫名其妙地到处闲逛。

要我说,活动本来就在于质量而不是数量。有些社团几乎每隔一两个星期就要搞个大新闻,其质量可想而知。

对于社团成员的招收也应该同理。某些社团,空有数百名成员,一半以上是划水,整个社团组织松散,什么也做不成。

哦不,也许这一整个学校都有种这样的感觉。推行一个想法,几乎必定不会有多少人积极回应……

整个学校似乎都是划水的人居多 —— 讲到这我又忍不住要提到我之前参与又退出的某个创业项目,开始了几个月结果连一个靠谱的前端都没有,整个一「我们就差一个前端了」的感觉,还希望在圣诞节前(后来改成明年四月)上线……作为负责后端的人,我,实在是看不下去……

有毒。


4

喜欢就追。

这句话我想说给三年前的自己听。

我在之前的博客里应该已经有提到。高一的时候,有一个跟我同班的女孩子 —— 我喜欢她。大概是第一次见到以后没多久就有这种朦朦胧胧的感觉。

按照其他人的惯例,这时候应该多多尝试去和她说话,和她瞎扯,然后要到她的手机号,QQ号……我呢?见到她就紧张得一个字都说不出来。

后来高二分了班,选了一样的科目,不在同一个班但经常在门口遇见。她见到我的时候经常对我一笑,笑得实在太美,又让我一个字都说不出来……

高三毕业前,也就是我之前博客里提及这件事的时候,我在收集毕业留言的时候想到了她。因为不敢直接和她说话,我就让一位同学代我去请求她给我写一份留言。她认认真真写了千字还多,当然没有少「吐槽」我表现地那么冷漠。

那天下午就又碰面了。她冲我微笑,我挥手致意。

阴差阳错,大学竟然也只隔一条马路。我终于开始觉得,再不尝试一下,就太没有道理了,也太对不起过去的自己了。

其实,说说话,聊聊天,请出来玩玩,也没有那么难。只是高中时的自己太怯弱,害怕别人的目光,害怕老师,害怕父母……现在的我也没有摆脱这些问题,但是我在尝试着,尝试着传达这一份心意。

这一切已经晚了。我所做的不过是想弥补自己早就应该做而没做的事情所带来的缺憾 —— 请允许我这一点点的自私。我知道,我不可能被接受,她不太可能喜欢我。只要传达到,就够了吧。「届かない恋」的事情,还是不要在自己身上上演比较好 —— 我现在正是有这么一个机会去改变。

打算在圣诞节左右表白,了结这一切。祝我好运 —— 尽管成功的可能性太过渺茫。

我喜欢你。谢谢你给我带来的美好回忆。

如果可能重来的话,我愿意为这样的尝试付出一切。

感情是不能人为控制它开始,也不能人为强制结束的

—— 「恋爱管理」课程

早点听这课的话,就好了。


5

人渣总是比较受欢迎。

我不想指名道姓,也没有必要。我说的并不是某一个具体的人。

他们想要控制。恋情是他们的财产。他们热衷于后宫。他们视感情为游戏。

然而,恋爱中的人是没有理性的。

所以他们从来不会失手。

这是我在听了某朋友的故事,看了近期某微博上热门事件以后的感想。

保持文明,不多说。

自己不要变成这样的人就好了。—— 如此希望着。


6

还有一些事情,似乎不太适合在博客上公开发表,只能继续憋在心里了。

文笔实在有限,好多话还是无法精准地表达。我可能会在今后,就这篇文章中的某些话题,详细展开写一些单独的文章。

罢了,这样已经舒服许多了。

谢谢大家耐心看完我的牢骚。

在 ArchLinux 上配置 shadowsocks + iptables + ipset 实现自动分流

本来我是决定不再写这样的文章了的。但是呢,最近连续配置了两次 ArchLinux,在配置这种东西的时候连续撞到了同样的坑,加上今天 Issac 亲问我关于 Linux 下的 shadowsocks 的问题,所以我想了想还是写一篇记录一下吧,也免得自己以后再忘记了。


2017-01-25 更新:

我编写了一个脚本来自动化原文所述过程,源码和使用方法在

https://github.com/PeterCxy/shadowsocks-auto-redir.sh

比本文中介绍的方法要方便很多。

以下是原文。


本篇的目标是使用 ipset 载入 chnroute 的 IP 列表并使用 iptables 实现带自动分流国内外流量的全局代理。为什么不用 PAC 呢?因为 PAC 这种东西只对浏览器有用。难道你在浏览器之外就不需要科学上网了吗?反正我是不信的……

前置条件

  • 一个能使用的 shadowsocks 服务端,假设它的 IP 是 192.168.1.100, 端口是 6666, 加密方式是 chacha20, 密码是 1234
  • 一个安装了 shadowsocks-libev 的 ArchLinux; 其他发行版不保证可用,但如果有 shadowsocks-libev 以及 [email protected] 的话,步骤应该大同小异
  • ipset 和 iptables 工具
  • systemd ~~卖底裤~~全家桶

创建配置

首先创建配置目录 /etc/shadowsocks/config.json

{
  "server": "192.168.1.100",
  "server_port": 6666,
  "local_port": 1080,
  "method": "chacha20",
  "password": "1234"
}

然后运行

systemctl start [email protected]
systemctl status [email protected]

看看有无异常输出,此时你也可以打开浏览器连接到 1080 端口的 socks5 代理测试服务器是否正常。

获取 IP 列表

接下来我们需要获取中国的 IP 列表。在此之前我们需要创建一个目录来储存需要的脚本和其他文件。我建议放在 $HOME 下,或者 /opt 下。这里假设我们创建并切换到了目录 /home/peter/shadowsocks

curl 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | grep ipv4 | grep CN | awk -F\| '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > chnroute.txt

这是来自 ChinaDNS 的指令。

创建启动和关闭脚本

创建 /home/peter/shadowsocks/ss-up.sh

#!/bin/bash

# Setup the ipset
ipset -N chnroute hash:net maxelem 65536

for ip in $(cat '/home/peter/shadowsocks/chnroute.txt'); do
  ipset add chnroute $ip
done

# Setup iptables
iptables -t nat -N SHADOWSOCKS

# Allow connection to the server
iptables -t nat -A SHADOWSOCKS -d 192.168.1.100 -j RETURN

# Allow connection to reserved networks
iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# Allow connection to chinese IPs
iptables -t nat -A SHADOWSOCKS -p tcp -m set --match-set chnroute dst -j RETURN

# Redirect to Shadowsocks
iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-port 1080

# Redirect to SHADOWSOCKS
iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS

大部分代码还是来自 shadowsocks-libev 项目。

这是在启动 shadowsocks 之前执行的脚本,用来设置 iptables 规则,对全局应用代理并将 chnroute 导入 ipset 来实现自动分流。注意要把服务器 IP 和本地端口相关的代码全部替换成你自己的。

这里就有一个坑了,就是在把 chnroute.txt 加入 ipset 的时候。因为 chnroute.txt 是一个 IP 段列表,而中国持有的 IP 数量上还是比较大的,所以如果使用 hash:ip 来导入的话会使内存溢出。我在第二次重新配置的时候就撞进了这个大坑……

但是你也不能尝试把整个列表导入 iptables。虽然导入 iptables 不会导致内存溢出,但是 iptables 是线性查表,即使你全部导入进去,也会因为低下的性能而抓狂。

然后再创建 /home/peter/ss-down.sh, 这是用来清除上述规则的脚本,比较简单

#!/bin/bash

iptables -t nat -D OUTPUT -p tcp -j SHADOWSOCKS
iptables -t nat -F SHADOWSOCKS
iptables -t nat -X SHADOWSOCKS
ipset destroy chnroute

接着执行

chmod +x ss-up.sh
chmod +x ss-down.sh

至此需要的脚本和配置文件已经全部准备完成了。

配置 systemd

首先,默认的 ss-local 并不能用来作为 iptables 流量转发的目标,因为它是 socks5 代理而非透明代理。我们至少要把 systemd 执行的程序改成 ss-redir。其次,上述两个脚本还不能自动执行,必须让 systemd 分别在启动 shadowsocks 之前和关闭之后将脚本执行,这样才能自动配置好 iptables 规则。

执行

sudo EDITOR=vim systemctl edit [email protected]

然后键入如下内容

[Service]
User=root
CapabilityBoundingSet=~CAP_SYS_ADMIN
ExecStart=
ExecStartPre=/home/peter/shadowsocks/ss-up.sh
ExecStart=/usr/bin/ss-redir -u -A -c /etc/shadowsocks/%i.json
ExecStopPost=/home/peter/shadowsocks/ss-down.sh

是的,那两个脚本必须以 root 权限才能执行,所以我把整个服务的执行用户都设为 root。这显然是存在安全隐患的,但是因为我的懒癌,所以我没有专门处理。如果要提高安全性的话,应该把两个脚本的执行单独抽出来做一个 shadowsocks-iptables.service, 然后利用 Systemd Unit 的依赖特性来实现自动执行。

至此,带自动国内外分流的 shadowsocks 客户端已经配置完毕。要启动的话

systemctl restart [email protected]

还可以设置自动启动

systemctl enable [email protected]

以上。

修订:

2016.9.24 - 由于 ArchLinux 更新,添加关于 CapabilityBoundingSet 的设定

结束的最后

我之前写过一篇题为 结束的开端 的文章。那是在高三的最后一个学期到来之前写下的。当时的我,对于如今的生活,是怀着一种恐惧和期待交加的心情。而现在,高考已经过去了一个多月,我的录取结果也已经知晓。不知不觉之间我自己早已身处当时我所憧憬的那个「结束」。

有开端则必有结束。身处高考之后一个多月的我,大概已经有足够的理智来重新审视刚刚过去的这三年的时光。最近看见 Touko 聚聚也更新了博客 关于高考, 我才终于决定写下这么一篇文章,以完成过去的我所留下的所谓「伏笔」。权当这是我的自我反省吧。当然,如果能传授一点人生经验,那就是坠吼的了。

抓住机会

所谓抓住机会,换一句话说,便是「不要怂」。这里咱可以拿自己作反面教材。

三月到四月的时候,西交利物浦大学有面向高中生的自主招生活动。那时候有同学邀请我一起参加这自招。那时的我的目标还是「至少上个苏大」,因为

考个苏大,还不容易?

我就抱着这样的想法错过了这次自招活动。

然而现在的情况是,我的分数和苏大差了十万八千里,倒是以压线的成绩进了西交利物浦。这也真是惊险的一幕。那时的我如果尝试了自招,如果万一就那么让我通过了,这情况就会发生一百八十度的转变了。

当然,说自招有什么黑幕,说难以以实力通过自招,那便是另一个话题了。我则是连「报名」的机会都拱手相让了。

所谓「怂」者,大抵如此,我在之前几篇讲述「情感生活」的博客中已经无数次吐槽过自己的这一点了。踌躇,寡断,选择困难,这就是高中时代的我。我最近将自己的高中时代定性为「失败的」,因为

既没有当成学霸,也没有追到喜欢的女孩子,就连自己所热爱的东西也没有做出多大的成就。

面对机会的我往往会经历这么一个流程

  1. 机会来了,一定要抓住
  2. 机会快到了,可是好像……不太好?
  3. 算了,过去吧
  4. woc 我为什么没有抓住机会啊!!!!!!!!!!!

专业马后炮,大抵如此。啊,顺便一提,曾经的我甚至连喜欢的女孩子抛给我的一个大好机会都没有抓住。那可是摆在眼前的……然而,在高考这种时候,生命是不允许你对自己的方向有任何含糊的。如果你想,就去做,如果有~~喜欢的人~~梦,就去追,这大概就是所谓「抓住机会」了吧。

如果有学弟~~/学妹~~看到这篇文章的话,我就这么装作学长的样子灌一灌心灵鸡汤吧。毕竟,就算这是我自己都看过千百遍的心灵鸡汤,我也没有学到任何东西……

我已经从你的全世界路过

像一颗流星划过命运的天空

—— 「从你的全世界路过」

珍惜身边的人

真实的故事是,当我知道我自己所选择的专业的性别比例是 9(♂):1(♀) 的时候,我几乎是要崩溃的,虽然我早就做好了这样的心理准备。

犹记得我们可爱的副校长在高考前的一次演讲上说过这么一句话

大学里有更多的人,有更多的……,有更多的机♂会♀在等着你

可是高中时代的那些朴素的情感,师生情也好,友情也好,所谓懵懂的「爱情」也罢,恐怕是再也不会有了。

林妹妹一蹦一跳地推开了门,吓了大家一大跳;Jessie 又开始晒和女儿的种种故事;赵太爷又说书般地讲述着文革往事;而我喜欢的女孩子又露出了开心的笑颜……

当时的我所没有察觉到的是,终于有这么一天我会必须向这一切说再见。终于有一天,那个她会穿上高跟鞋与工作服,那个他会套上西装打上领带,然后世界都变了模样。

这个三年只有一次。这个机会,若不抓住,便不会有下一次了。成为学霸也好,谈一场恋爱也好,那都是我们所活过的无法修改、不能美化的真真实实的人生啊。而且还有一个痛苦的领悟就是:对于很多人来讲,这两件事情是完全不冲突的。关于这个问题我可能会在另一篇文章中详细阐述——真正的爱,不应该成为一种负担。所谓学习,也是一样的道理。

总而言之,珍惜这一切,珍惜你在教室里所见到的每一张面孔。如果喜欢,那就努力地靠近那个人,努力地去追求。如果有朋友,那就成为真正的好哥们、好闺蜜。这样的心情,可能再也不会有了。

珍惜那些不会因为小事而嘲笑你,总是向你敞开心扉并准备好接受你的倾诉的那些人。

~~而我也差不多是一条废虾了。~~

きみとがよかった

ほかの谁でもない

でも目覚めた朝

きみは居ないんだね

—— 「一番の宝物」

Talk is cheap.

也就是说,少听别人关于高考「技巧」「方法」的瞎扯。

是的,我不是针对某一个人。我的意思是,在座的所有「技巧」和「方法」都是垃圾。

为什么这么说呢,因为就我个人的体验而言,真的到了考场上,真的到了高考这种紧张的考场上,一个人的所有行为,靠的都几乎是一种本能,很少有人能冷静到能够斟酌「技巧」与「方法」的程度。而当你满脑子都是各种方法和技巧的时候,又怎么可能真的能冷静下来去面对题目呢?

况且,这些「技巧」和「方法」的「提供者」们,自己也不过就像我们一样,曾经只是普通的高中生。他们很可能只是迫于母校、迫于过去的老师、迫于旧时好友的要求,而赶 deadline 般地写出来的。这种「技巧」和「方法」,价值能有多少?不过是一堆被咀嚼过的二手垃圾罢了。与其如此,不如好好自己看几页书,刷几道题。

Talk is cheap. Show me the code.

这句话是 Linus Torvalds 大神说的。我权且借用一下这句话。

总之,再听到有人关于「应试技巧」的话,就把它当作胡扯就好了。

不需要脆弱的伪装

我不要卑微的祈祷

我只要打开我的触角

去寻找、去寻找

—— 「去寻找」

给!我!好!好!睡!觉!

这句话一共有六个字,我用了六个感叹号。因为睡觉真的很重要。

不知道是不是我「提前衰老」,我初中的时候还能做到晚上12点以后睡觉,上了高中以后反而不行了。如果12点以后睡觉,那么第二天一醒来就会感觉~~身体被掏空~~疲倦至极,从上午开始就会在课上睡着。然而其实对于高考来说,最重要的就是在课上这点时间了,晚上即使睡得再晚也无济于事。

这也没有什么再多的话好说了。总之,给!我!好!好!睡!觉!

只想看你熟睡的样子

我只想走进你梦中

—— 「晚安,晚安」

我不后悔

成功也好,失败也罢,高中生活也算是就这么画上句号了。不管完美不完美,它都有值得回忆的事情。那便是「最珍贵的宝物」。

我在微博上发过我刚刚提及的描述我「失败的高中生活」的时候甚至有人这么评论

可是你成了网红

然而网红并不会只有4000+的微博关注。不过不管怎么说,这三年里,我也算是开了 BlackLight 等几个大坑,填上了一部分,并获得了这么一群关心着我的人。当然,真正开始理解「博客」,开始写博客,这也是很重要的。

反观这三年,尽管我没当成学霸,也怂得没有追到喜欢的人,但是我至少没心没肺地过得很开心,还成功由「Angel Beats」入了宅。至于这最后的成绩嘛,用我同学的一句话说,就是 I deserve it. 认识到这一点,便好了。

过去终于会化为记忆中的一团泡影,即使它再美好,再绚丽。那个穿着长裙的她,也会永远只活在那幅画里,「拉不出来,自己也回不去」。我并不会后悔自己所错过的这一切,因为即便是错过,那也是所真实地活过的人生之一。不可篡改,不可美化,不可磨灭。

不小心写成了鸡汤文的风格。恳请大家再允许我这么鸡汤一次吧。

ふわふわる ふわふわり

あなたが笑っている それだけで笑顔になる

神様ありがとう 運命のいたずらでも

めぐり逢えたことが しあわせなの

—— 「恋愛サーキュレーション」

再见,Ghost

旧爱

我切换到 Ghost 这个博客引擎,其实也没有很久的时间。当时切换到 Ghost,主要原因是 Jekyll 这样的博客引擎没有一个好用的网页编辑器或者客户端,而当时的我还是高中生,经常需要在手机上编辑并发布博客。而 Ghost 恰好有一个好用的网页前端,所以我当时就决定把博客迁移到 Ghost 平台上。

但是 Ghost 也存在相当多的问题

  • 插件系统较为鸡肋,难以扩展
  • 服务端不能执行代码高亮,代码高亮需要在客户端执行
  • 不能自定义主题的参数,导致不修改主题文件难以实现自定义
  • 编辑器不自带 Markdown 语法高亮

再加上我现在已经可以使用电脑写作,使用手机的时间大大减少,这就是换一个博客系统的好时机了。

新欢?

我曾经是 Jekyll 的用户,当时使用中比较蛋疼的一个问题就是自动更新与缓存刷新 —— 因为那是纯静态博客,所以必须再单独实现一个服务来监听更新并同步。虽然说博客这种东西本身静态和动态就没什么大的差别,但是我还是更倾向于「半动态」的博客,这样也更便于实现插件系统。

而我又恰好正在为「手生」烦恼 —— 整个高三没写几句代码,突然放暑假,想要填上自己的那些坑,却猛然发现自己已经不习惯于写代码,对着 IDE 无从下手。博客系统这种东西,说复杂,也没什么复杂的地方,倒不如就此机会自己开个坑,也好练练手,满足一下自己的「虚荣心」,咱也不指望会有其他人使用我写的博客系统了……

一个博客系统,无非就这样几个部分

  • HTTP 服务器
  • 渲染引擎
  • 模板引擎
  • RSS生成器

至于评论系统,我暂时觉得依赖 Disqus 还不是什么大问题。这几个部分,我稍微思考了一下,发现并没有什么特别难做的 —— 毕竟我的目标并不是完全从头造一遍轮子。HTTP 服务完全可以依赖 express.js 之类的现成框架,模板引擎可以使用 Handlebars.js —— 这可以极大地方便我把 Ghost 的模板直接移植过来。所以,说开坑,我就这么开坑了。

这个博客系统坑的名字就叫 Typeblog,和本博客的名字一样,因为这个名字具有特殊的含义。下文中如不作特殊说明, Typeblog 均指该博客系统。

存储结构

按照我的设计,一个博客应该是一个目录 —— 与博客程序独立的目录。这个目录应该同时是一个 npm 包,即含有 package.json。这个包通过依赖的形式把 Typeblog 安装至自己的 nodemodules 下。Typeblog 程序有一个主可执行文件 typeblog(当使用 npm 安装时,它将会位于 `nodemodules/.bin/typeblog`),执行即代表启动该博客。程序将会自动载入当前目录下(博客根目录)的 config.json 作为配置文件。这个 config.json 应该具有类似这样的结构

{
  "title": "MyBlog",
  "description": "Just my blog",
  "url": "http://example.com",
  "plugins": [
    "..."
  ],
  "posts": [
    "posts/post1.md",
    "posts/post2.md"
  ]
}

其中配置的值和键的语义相符,我就不一一解释了。其中 posts 字段下的是一个文件路径数组,里面指定的是相对于博客根目录(即程序工作目录)的路径。程序启动和重载时将载入这些路径上的文章,至于解析过程稍后会提及。这里需要提前说明的是,该数组是一个有序数组,排序靠前的文章在最终生成的博客中也将靠前(因为我受够了按日期自动排序的博客系统 —— 不是所有的文章都能按日期排序!)

同时,博客程序还会监听这个 config.json 的改动(使用 chokidar 实现),一旦发生改动,程序将自动触发配置重载,同时重载文章列表。这主要是为了方便本地调试。在服务器端,我们将采用其他方式触发配置的重载。

插件系统

我这个博客程序,非常重要的一部分就是插件。我的计划是,渲染引擎中的大部分将使用插件的形式呈现,包括文章格式解析, Markdown 解析,代码高亮等。这就要求我在一开始就考虑到插件的存在。

于是呢,我设计了一个插件基类

class Plugin
  constructor: ->
    registerPlugin @

plugins = []
registerPlugin = (plugin) ->
  plugins.push plugin

当一个继承了 Plugin 类的子类被实例化时,它就会自动被加入这里的数组 plugins。子类需要实现它们自己可以实现的方法。当主程序需要调用一个支持插件的方法时,它将使用这个过程

callPluginMethod = (name, args) ->
  for p in plugins
    if p[name]? and (typeof p[name] is 'function')
      [ok, promise] = p[name].apply @, args
      return promise if ok
  [ok, promise] = defaultPlugin[name].apply @, args
  return promise if ok
  throw new Error "No plugin for #{name} found"

将会遍历整个 plugins 数组,寻找含有需要的方法的插件类实现。当找到以后,程序将试图调用这个方法。方法的返回值是 [ok, promise] 的形式,如果 ok 为真,表示该方法支持当前的输入,此时 promise 将不会为 null,这个 promise 将会在方法内容执行完成后完成,它将直接作为这个函数的返回值返回。如果 ok 为假,表示该方法不支持当前输入,于是程序将继续搜寻其他支持该输入的实现。如果循环已经结束而没有任何实现被找到,程序将使用默认的实现。这个默认实现是在一开始就被实例化的,它默认被载入,不属于 plugins 数组,提供所有已知的插件方法的默认实现。

这种机制主要是考虑到类似这种需要

[
  "posts/my-blog.rst",
  "posts/hello-blog.md",
  "some-remote://xxxxx.md"
]

同一个博客中出现了不同格式的文章,还有不同的存储后端 —— 有的文章甚至存在于远端。这就需要实现同一个方法的插件能够互相分工各司其职。

在刚刚的 config.json 中,大家也能看见我专门安排了一个 plugins 字段。当程序启动或重载时,将执行这样一个过程来载入全部插件

loadPlugins = (config) ->
  return if not config.plugins?
  config.plugins.forEach (it) ->
    if it.startsWith 'npm://'
      require it.replace 'npm://', ''
    else
      require "#{process.cwd()}/#{it}"

如果以 npm:// 开头,程序将作为一个 npm 包来载入这个插件,否则将作为相对于当前路径的单个文件来载入。插件是可以直接使用 CoffeeScript 编写的 —— 博客程序已经载入了 coffee-script/register

当然,这里还存在一个问题,就是被载入的插件无法载入它的父模块 plugins。而在这个父模块里,我将必须的依赖及 Plugin 基类都作为 module.exports 导出了。这就十分尴尬了。于是我使用了一个小小的 hack

require.cache['plugin'] = module # Enable this to be directly required
Module = require 'module'
realResolve = Module._resolveFilename
Module._resolveFilename = (request, parent) ->
  if request is 'plugin'
    return 'plugin'
  realResolve request, parent

将这个模块强制加入 require.cache 并替换 _resolveFilename 方法使其不会找不到模块。于是,在其他插件中,只需要 require 'plugin' 即可载入这个父模块,也就能够继承基类了。

文章格式

完成了插件系统,下面就该提供文章格式解析的默认实现了。

当载入文章时,程序将会调用插件系统的 parsePost 方法,这个方法只有一个参数,就是文件的原始内容。默认实现中,文章的头部应该包含文章的元数据。因为我自己多数时候使用 Markdown 格式写作,所以我提供了兼容 Markdown 的默认元数据格式,即类似这样

json
{
"title": "再见,Ghost",
"url": "goodbye-ghost",
"date": "2016-07-11",
"tags": ["Tech", "Blog"],
"parser": "Markdown"
}

这样,在解析文章时,只需要找到这一个代码块,然后使用 JSON.parse 解析元数据即可。

  parsePost: (content) ->
    end = content.indexOf '```\n'
    return [false, null] if not (content.startsWith('```json') and end > 0)
    start = '```json'.length + 1

    promise = Promise.try ->
      json = content[start...end]
      data = JSON.parse json
      data.content = content[end + '```'.length...].trim()
      return data
    .then (data) ->
      if not (data.title? and data.date?)
        throw new Error 'You must provide at least `title` and `date`'
      if not data.parser?
        data.parser = 'Default'
      if not data.url?
        data.url = encodeURIComponent data.title
      if not data.template?
        data.template = "post"
      return data
    .then (data) ->
      data.date = new Date data.date
      return data

这个 parser 指定的是解析程序,我将稍后解释。

所有的文章载入和解析的过程,都是在程序启动和重载的过程中完成的,不会在每次请求时执行,这是因为文章的内容一般不会随意变化,除非被触发重载事件。然而,模板的渲染却是在每次请求时实时执行的 —— 因为有的插件可能需要实时影响渲染的结果。

解析器

解析好元数据以后,程序会把剩下的内容丢给元数据中指定的解析器。所有的解析器都是插件方法,格式是 parseContent#{parser_name}。比如说 Markdown 的解析方法就是 parseMarkdown。对于未指定解析器的文章,程序提供了一个默认解析器 parseContentDefault

  parseContentDefault: (content) ->
    promise = Promise.try ->
      return content # Do no change on the content
    return [true, promise]

而我自己实现的 typeblog-markdown 插件则提供了一个 Markdown 解析器 (基于 marked)

{Plugin, dependencies, callPluginMethod} = require 'plugin'
{Promise} = dependencies
marked = require 'marked'

marked.setOptions highlight: (code, lang, cb) ->
  callPluginMethod 'highlight', [code, lang]
    .then (result) -> cb null, result
    .catch (err) -> cb null, code

class MarkdownPlugin extends Plugin
  parseContentMarkdown: (content) ->
    promise = new Promise (resolve, reject) ->
      marked content, (err, result) ->
        if err?
          reject err
        else
          resolve result

    return [true, promise]

  highlight: (code, lang) ->
    return [true, Promise.try ->
      return code
    ]

module.exports = new MarkdownPlugin

这个插件又需要一个名为 highlight 的插件方法。这个插件方法的作用是给代码块加上高亮。这个插件里提供了一个默认实现,就是什么也不支持的默认实现。要真正实现高亮需要再载入实现了 highlight 方法的插件。注意,为了覆盖这个默认实现,所载入的代码高亮插件必须在这个 Markdown 解析插件之前载入,即在 plugins 列表中位于 (npm://)typeblog-markdown 之前。我自己也实现了一个基于 highlight.js 的代码高亮插件,大家可以在文章尾部的 GitHub 仓库中找到。

主题引擎

主题引擎我采用了 Handlebars.js ,以便于移植我给 Ghost 做的主题。所有的主题文件都存放于博客根目录的 template 目录下,该目录的结构

/
- /assets
- - .....
- /partials
- - .....
- default.hbs
- index.hbs
- post.hbs

其中 {default,index,post}.hbs 是必须的。assets 目录会被映射到 blog_url/assets ,可用于存放 css 等。 partials 目录下的 .hbs 文件会在启动时被注册为 Handlebars 的 partial。同样,为了调试方便,当这个目录的文件有改动时,主题引擎会自动重载。不过,主题的重载支持是有限的,要完全刷新主题,还是最好重启。

default.hbs 是最后被渲染的,它是博客所有页面共享的框架,包含头部和尾部。index.hbs 和 post.hbs 都是具体页面的模板。渲染首页、首页的分页、Tag 页面和 Tag 页面的分页时,程序将调用 index.hbs 作为模板,它将被传递这样的上下文

{
  "blog": {
    "title": "..",
    "description": "..",
    "url": "..",
    "isHome": true
  },
  "firstPage": true,
  "lastPage": false,
  "nextPage": "/page/2",
  "prevPage": "",
  "curPage": 1
}

而当渲染具体文章时,它将被传递这样的上下文

{
  "blog": {
    "title": "..",
    "description": "..",
    "url": "..",
    "isHome": true
  },
  "post": {
    "...": "..."
  }
}

其中 post 就是文章的元数据,加上 content 字段即文章的解析后的内容。当 post 或 index 渲染完成后,其内容将被作为 content 字段传递给 default.hbs 作最后的渲染,它将收到这样的上下文

{
  "blog": {
    "title": "..",
    "description": "..",
    "url": "..",
    "isHome": true
  },
  "content": "...",
  "pageContext": {
    "...": "..."
  }
}

其中 pageContext 是当前页面的上下文,即刚才传给 index 或 post 的上下文对象。

另外,如果在 config.json 里面定义了 template_arguments 字段,那么这个字段会被传递到所有模板的上下文里,字段名称为 arguments。在文章元数据里定义的其他扩展字段也会被原样传递到 post.hbs 的上下文中。

默认我也提供了几个 helper, 包括用于引用 asset 并通过 md5 后缀强制更新浏览器缓存的 helper 和格式化日期的 helper 等。大家还是直接去 GitHub 仓库里面看。

其他

至于 RSS,我直接使用了 node-rss 来生成。

在搭建我自己的博客的时候,我又实现了两个(可能)只有我自己会用到的插件,比如一个 chinese-cdn 插件用于把 Google Fonts 和 cdnjs 等资源替换到国内 CDN,和一个 github-webhook 用于接收 GitHub 的更新通知并重载博客。这些插件都可以在本博客的仓库 https://github.com/PeterCxy/typeblog.net 看到。

我自己移植了一个主题 typeblog-diaspora 使用,就是之前移植的 ghost-diaspora 的修改版。这个主题需要在 config.json 的 template_arguments 内定义如下字段

  • cover: 博客的封面
  • disqus_username: Disqus 用户名
  • navigation: 博客主导航。是一个数组,每个成员都应该有 label 和 url 两个属性
  • social: 社交链接。同样是数组,每个成员都有 url 和 icon 两个属性。其中 icon 是 material-design-icons 中的图标名称(去掉 mdi- )

每篇文章的元数据中也可以指定每篇文章自己的封面。详细的配置方式还是请看我的博客配置。

该博客系统以 WTFPL 开源于 https://github.com/PeterCxy/Typeblog。另外,目前没有什么文档,如果有人真的想要使用这个博客引擎的话,我将会在有空的时候逐步完成文档。当然,你也可以直接联系 Telegram 上的 @PeterCxy 也就是我,我可以直接回答你的问题。

我给你买辣条,我们在一起好吗?

1

在微博上看见了一个(据说)真实的有趣的故事。

小学生在街头闹分手。女孩歇斯底里地问男孩,为什么「男友」就这么成了「前男友」,并发怒道

你把我当什么?

然后继续数落

你吃的辣条是我买的,你喝的酸奶也是我买的!

仿佛一个凄凉的爱情故事。


2

我有这么一个同学。

下午在玩「真心话大冒险」,被问及他喜欢过多少个女孩子,他掰了半天手指头以后回答

十几个吧。

一片哗然。

又有一个问题,问的是「男女间有纯洁的友谊吗」。此问一出,争端四起。有人坚持「没有」,并表示有时候「好感度」就是通过所谓「友情」提升的;而有坚持「有」者则说道:

这种「喜欢」和那种「喜欢」是不一样的。

然而,不管怎么说,那十几个女孩子,恐怕,没有一个是他喜欢的。

连朋友都做不成。


3

还有一个哥们儿。

之前一直在霓虹国四处浪。回来以后谈他对霓虹国的印象

妹子漂亮!随便拉出来一个就秒杀国内!

几杯酒下肚以后就开始胡说八道

我以后一定要去霓虹国找妹子!

是的,他是个名副其实的「土豪」。可是他并没有妹子,甚至没有「曾经」有过妹子。

和他同为「土豪」的另一个哥们,之前和一位妹子保持了两年的关系,后来因为他要出国,他无法接受异国恋而不得不分手。

我想第一个哥们儿并不懂这些。


4

喜欢一个女孩子。

那天傍晚,和几个朋友在讨论这类问题。于是我就被问到了是否有喜欢的人,我说「有」,然后他们通过一系列看似没什么关系的问题套出了我喜欢的人的名字。

于是他们便有一堆八卦了

啊,那个妹子啊,我听说之前她和A在一起来着

不对,后来被B给NTR了

诶?为什么?

B有钱啊!你看他每天从家里出来都坐什么车!

是的。在这种问题上,我已经完全败下阵来了。

我呢,也不过只是一个会写写代码、玩玩情怀、刷刷口水文的高中毕业生。

但是呢,辣条,我还是买得起的;Q币,我也是充得起的。

所以呢?我看着我面前的 XPS ,说不出什么话。

毕竟我没有听从某人的建议,买个最高配的 MacBook Pro 用来「撩妹」。

我大概永远也学不会了。


5

其实开头那个例子倒是很有戏剧性,也很有讽刺意味。

我随手开了个 Amazon US 查了一下辣条的价格,结果是五包 $13。平均下来与国内的价格相比,翻了十倍多。

辣条这种东西尚且如此,有些东西就更是这样了。

有一次我问,应该送女孩子什么生日礼物。有人给我这样的「建议」:

什么 LV 之类的东西你自己选选吧。

是的,我都懂。我懂你们什么意思。可是这样的事情,难道不就和「我给你买辣条,我们在一起」一样搞笑吗?

所以我每次听到这样的建议我都很窝火。是的,你可以说我小气,你可以说我舍不得,但容我反问你一句:

我给你买辣条,我给你充Q币,我们在一起好吗?

不好。

自己想把自己当成什么呢?


6

如今已经毕业了,但我永远舍不得这样一段回忆。

毕竟从今以后,可能很多人的「标准」也就降到这种「买辣条」的程度了。

长大其实是在变小;真正的「长大」过程其实早就在高中毕业之前完成了。这以后的所谓「成熟」,不过是在把自己降低到和社会大众一样的标准,把自己的刚刚形成的那些价值观又统统抛掉,从此平庸化,沦为庸庸碌碌的「群众」的一员。我们长大了,可我们的行为却也只是小学生的水平罢了。

有趣。

我给你买辣条,我们在一起好吗?

这样的话,竟然还能被这些所谓「成人」嘲笑。

亏他们笑得出来。

我理想中的现充生活

我并不是一位现充1,但我羡慕现充的生活很久了。我有成为现充的理想,只是一直没有实现罢了。于是,这是


理想的爱,应当是一种平等。而所谓平等,可能就体现于有争执的问题上了。

见过很多人吵架,往往是这么一个模式:

你说说看,这个A2是怎么回事?

那是因为我B2啊!

那你B有什么理由吗?

那是因为我C2

C你个***!还不老实告诉我?

嘿!我说了B啊!B这事有错吗?

我不管!你给我********

跟你说了B就是B!

那你A又是怎么回事?

于是便无限循环下去。我父母的吵架就是个典型的例子。在吵架的时候,他们可以把两三句话重复千百次,一两个小时都听他们不断重复一两句话,最后双方都没力气了才停下来。

这种呢,很多人觉得是吵架,其实可能连吵架都算不上,充其量是个无理取闹。在这种争执中,双方都试图凌驾于另一方之上,试图把自己的观点强加给对方。

我理想中的现充生活,是不能存在这样的争执的。当然,我并不是在说没有争执,而是认为争执的解决不应该以这样一种除了能够表现自己的嗓子能吼多大音以外没有任何作用的方式。

我所期待的是,晚上能够就今天白天未解决的问题查找资料,为第二天做准备;白天和「她」见面以后,几句寒暄,然后拿出昨天没有解决的问题来讨论。气氛很愉快,而语气应该尽量平和。即便观点分歧很大,「我」和「她」之间也应该做到互相尊重,并且应该就事论事,不要把问题扩展到问题本身之外,更不要作无意义的争吵。

只有两个人能心平气和地交谈,这才是平等的体现。失去了平等的基础的爱,我无法想象它会是一个什么样的存在。

于是几个小时就这么充实而愉悦地度过了。

这样的生活,才能叫「充实」,不是吗?


现充的生活应该充满了牵挂。


脚注


  1. 现充一词是源自日语“リア充”(リアじゅう,REAL→リアル→リア)的网络语言,指三次元的住民,也就是无需ACGN,单凭现实生活就能过得很充实的人[1],也可指某些二次元角色。常含贬义,特别是与“去死”二字连用时。近义词为人生赢家、人生胜利组和土豪等等。一般来说,容姿端丽、学业有成、财力雄厚、交际广泛和恋爱幸福,是现充的决定性要素。 —— Wikipedia 

  2. 这一段文字中所有A, B, C均指某一个事件或一个话题。 

那朵花

昨天一位小学同学在微博上找到了我。我们已经失联多年了,因为我从去年这时候开始就抛弃了QQ。按理说,多年未见,应当有很多话说才是——可是聊了几句以后却发现我们仿佛已经身处不同的世界,即使想要找话说,也有一种莫名的语塞之感。或者说,可能从某个时刻起,我们的人生轨迹就再也不会相交了。

这让我猛地想起「那朵花」中的情节。一群儿时的玩伴,因为升学等种种原因,不得不互相分离。虽然住所并不远,却各自有着自己的生活而不再相聚,不再像儿时那样共同玩耍。所谓的「超平和Busters」也名存实亡。剧中的「面码」这一灵魂的角色,则百般尝试,希望让这些朋友们找回儿时的那种感觉,重新组成那个失散多年的「超平和Busters」,找回那些属于自己的回忆。

「那朵花」中,面码是成功的。这也是为什么这部番成为了「神作」之一,使无数宅男热泪盈眶——每个人的心中都有那么一群儿时的玩伴,他们不远也不近,可你却永远也无法将他们唤回,他们仿佛只活在记忆中。我曾经想过,「那朵花」的「花」到底在比喻些什么?吾辈以为,这就是这部番的主题——那些失落的人和失落的友谊。

可是我们更多面对的情况,与二次元世界中的情况是截然相反的。当我们与一位「老朋友」多年未见,当我们各自经历了许多不同的事件之后,我们又如何找到所谓的共同话题呢?即便是再深厚的友谊,也是要建立在一定的共同话题之上的啊。而如果仅仅是叙旧,那所谓友情也只能停滞于过去,永远无法扩展到未来了。可能所谓的朋友,都是注定要分离的……

也是这两天的事情。在我们的 Telegram 群里,某位老司机谈论他的爱情观,说他不能接受那种两人明显不合的爱情。于是我插了一句嘴,「既然注定要分离,又为什么要在一起?」。老司机表示十分赞同。然而我自己想了一想,却觉得这句话很有问题,因为我们完全可以类比成「既然注定要死亡,又为什么要生活?」。这就显然与很多人的价值观相左了。

可能我们无法选择。既然我们已经提及了「注定」这个词,为何不可以把它扩展一下——不仅仅只有分离、死亡这类事情是被注定的,连「在一起」和「生活」也是被注定的。仔细想想,我们活在这里,并不是我们自己选择要活在这里;我们出生的这一个事实,不是由「我」这个实体决定的,而是由上一辈人的行为决定的。而上一辈的出生,又是由上上辈决定的……我们生活不是因为我们选择要生活,而是因为当我们知道这一事实时,我们已经活在这个世界上了。无论结局是否注定,「出生」这一事实都无法再被改变。所以我们才选择生活,所谓「向死而生」。而友情、爱情这类东西,看起来具有很强的主观性,实际上很多时候也不能完全由我们自己决定。我们与什么样的人相遇,对什么样的人抱有一种特殊的情愫,这些都太过微妙,也许一丁点的改变就能使这些发生极大的变化。或许我们的未来早已由某些从前发生的事件决定好了。我们和我们那些重要的人们,就这样分分合合,如人生之过客。尽管注定了要分离,可我们却注定了要相处这么一段时间。既然互相都是无法选择的人生过客,为什么不能把这一段回忆塑造得完美一些呢?

又或者,没有什么是注定的。也就是说,「注定要分离」和「注定要死亡」这样的前提本身就是不成立的。没有哪两个人真的没有一点点和平相处的可能。某国产剧里面有这么一句台词

探亲探亲,探着探着,就亲了!

我倒是觉得这句话挺有道理。刚刚也提到了,友情和爱情这一类的东西,主观性还是可以很强的。你可能觉得自己很讨厌一个人,可是慢慢地,经过了一系列的事件,你忽然觉得这个人也很可爱。这样的故事实在太多,都成了一种套路,我就不扯了。这也是我的黑历史——我在之前的文章中也提及过,我从前参与了某些同学的「团体」,故意排挤某个同学。实际上那些被排挤的同学,又为什么不能成为我的朋友呢?没有什么人是注定的路人。所有的机会,所有的靠近的可能,都要靠自己的这双手去争取,而不是借所谓「注定要分离」就能逃避的事情。就像「那朵花」中的几位主人公一样,我们的那些破碎的友谊,其成也在我们自己,其破也在我们自己。是我们自己的心结导致了珍贵的友谊的破碎。

然而,无论注定与否,当友谊之花凋零的时候,我们便各自踏上了自己不同的旅途。有一件事情是可以确定的,就是一切凋零的花朵都不可能以原来的那个样子再次盛开。友谊既然已经成为过去,很多时候,我们便最好不要再试图将它捡起,让那些美好的瞬间定格在回忆之中。

因为,在未来的道路上的那些人,还是需要靠自己去争取的。

儿童节之思

昨天在知乎上看了一个问题的回答

如何避免孩子早恋?

昨天我看了这个回答以后,差点一晚上都没睡好觉。尤其是答主的这一段话

为什么在我漫长而苦闷的青春期里,一提到这件事,我父母就说,因为我早恋,让他俩在所有的亲戚朋友面前没办法做人,抬不起头来。(可是明明是他们主动把这件事告诉大家的啊,他们如果不说,别人怎么可能知道?!这个情况,我是真的很想知道答案,为什么他们自己把我早恋的事到处说,把细节和情书的内容一遍遍讲,然后又责备我让他们抬不起头来?)

这样的父母,也是极品了。可是我想来想去,却觉得这样的影子,在每个父母/老师身上,甚至是每个同龄人的身上,都有那么一点。

初三的时候,我有两个同学,一男一女,两个人『早恋』。说是早恋,其实可能更多的只是被贴上的标签。他们两个,可能只是互相之间有那么一点点微妙的好感,可能根本就没有到恋爱的程度。可是当时呢,当时我们全班,甚至全年级的人,都『公认』了『他们俩是一对』这个事实。当他们俩走在路上,即便没有肩并肩手拉手,也会惹来围观者的『嗤嗤』声。老师经常找他们俩谈话,虽然他们俩一直不当回事。后来他们俩几乎是班级里的反面典型。老师当众暗示过我们不要像他们俩一样;而他们自己,则处于看起来常被戏弄实际上被孤立的那种局面。再后来怎么样,我也不必再说下去了,大家都知道。

而我,也是那些人中的一员;现在想起来,我也是『作恶』者之一,觉得特别对不起他们。

我呢?我从小学开始,一贯被当成『好孩子』,因而从那时候开始,我最怕的就是被人非议 —— 同学也好,老师也好,家长也好。我的某个小学老师甚至说过,『因为批评而哭过的都是好学生』。当然,我不是想把这位老师批判一番,毕竟她对我还是有很好的影响的。然而正是因为这样的话,我变得越来越胆小,或者说,『怂』。我所怕的是那些大人口中『好孩子』不会做的事情,就比如说早恋。因而初中以后,我为了『避嫌』,甚至都不和女孩子说话,一句话也不说。怕的就是像我上面描述的那两位同学一样,被人在背后议论,在背后被嗤之以鼻,『给父母/老师丢脸』。这种心理慢慢地演化成了一种恐惧感,一种根植于内心深处的恐惧感 —— 现在,我几乎没有办法正常地和女孩子们交流,哪怕就是打个招呼聊聊天,也会紧张的不行,同时不由自主地避开目光,打量四周,尽管我知道正常的交流并不会带来什么非议。

还是在初中。那个时候,我拥有了第一部智能手机。这件事情呢,后来被我当时的班主任知道了。我的班主任便找我去谈话,大概的意思是他脑补了一下我会用手机做什么,然后教育我了一番『手机害人啊』『互联网害人啊』之类的大道理,总而言之就是叫我不要用手机。然而问题是,当时的他并不知道我拿手机来干了啥;我也从来没有表现出来过『被害』的现象(找不到妹子算被害吗?);他就这么认为这样的事情一定会发生在我身上。这就和上述的那个知乎问题,有异曲同工之妙了:有的人总是喜欢幻想孩子会做出什么不对的事情,然后添油加醋加以恐吓,仿佛这样的事情已经带来了不得了的灾难;而殊不知其实这只是一种妄想,这只是在推卸责任罢了。

这篇文章不是在宣扬早恋,更不是在说沉迷网络游戏是什么好事情;我只是在说,很多人在面对这样的问题的时候,往往在不由自主地推卸责任,把全部责任强加到无辜的当事人身上 —— 而当事人往往只是个孩子。而且在很多情况下,老师、家长甚至旁观者们所持有的价值观未必就是正确的,而这样的观点总是倾向于在孩子身上留下无法磨灭的影响。『早恋』这个问题便是一个典型的例子。有个笑话讲得好,说咱中国的家长啊,在22岁以前总是想方设法地阻止『恋爱』的产生,而在这之后则疯了似的『逼婚』,催着找对象。而仔细想想看,一个连『爱』这种情感的产生都被扼杀了的人,怎么可能真的『找到对象』?即便是找到了,又如何能够正常地生活一辈子?这里我就不想提『幸福』二字了,这已经是奢求。

所谓德高望重的师长,很多时候,他们所持有的教条便是和『早恋』一样的伪概念。他们道貌岸然,仿佛一切真理都在他们那一边,仿佛他们的那一套全都是『为了孩子好』。很多旁观者也往往持有『为了你好』这样一种奇怪的想法。这倒也是很有意思,好像所有行为套上了这么一个帽子便可登大雅之堂。我送你们两个字:恶心。

前两天坐车回家的时候,收音机里正好在讨论一个问题,就是『成年人可不可以过儿童节』。私以为,儿童节不应当仅仅局限于『玩』一个字上。如果仅仅是为了让儿童玩,那么恕我直言,单独安排这一天,一点意义都没有。记得家长们在儿童节这一天常挂在嘴边的一句话是,『今天是儿童节先不管你,但是明天blahblahblah』。这便是儿童节异化的表现了 —— 它绝对不仅仅是让熊孩子们疯一天而已。

你们花了钱,给孩子买了各种新奇的玩具;你们用了心,给他们做了各种礼物;你们耗了时间,陪他们到处疯狂。可是你们没有想过,儿童节到底是为了什么。妇女节是为了让妇女疯玩吗?母亲节和父亲节是让父母疯玩吗?不是的,它们都是为了 呼吁保护某一类人的权利, 或者 呼吁社会关注和平等对待某一类人。儿童节不仅仅是物质化了,还是被异化了,它早已偏离了它的初衷。儿童节就是给这些成年人,或者同龄人中的旁观者,来反思自己的教育方式,自己对待儿童的方式的。你们以为自己关心儿童,却忘记了自己对他们的成长所应该担负起的那些责任,如此恶性循环,一代又一代,我无法想象这样的未来。

吾辈以为,只有当本文开头提到的那种事情真正消失之时,才是儿童节真正到来之日。

使用 LetsEncrypt.sh + Nginx 实现SSL证书自动签发/续签

随着运营商劫持、政府网络监控行为的加强,HTTPS 在今年已经几乎成为了网站的标配。吾辈之前使用的一直是 COMODO PositiveSSL Wildcard, 但是这证书的价格,已经到了我今年很可能续不起命的地步。本来想着高考完以后把它换成单域名的 PositiveSSL 算了,但是我这个人呢,喜欢瞎折腾,经常弄出一大堆子域名,而且我又开启了全部子域名的 HSTS,因而使用这种证书的话成本可不太低。我之前尝试过使用 LetsEncrypt 的证书,但是无奈他家的官方客户端对于 Nginx 的支持……实在是太简陋而无法使用。作为忠实 Nginx 信徒,要我去使用 Apache, 是不太可能的。当然,之前我也使用过 Caddy 的自动签发证书功能,但是 Caddy 相比 Nginx,功能还是太简陋,我总不至于在 Caddy 后面再套一层 Nginx 吧?!

不过今天我找到了 LetsEncrypt 的另一个客户端

letsencrypt.sh

这是用 Bash 写的客户端,符合核心价值观,~~很清真~~,而且可定制性非常高。我初步看一了一下文档以后,发现这非常尊重我的决定权,没有任何钦定的感觉。使用这个脚本,加上一定的配置,应该就可以完成 LetsEncrypt 证书的自动签发和续期了。

准备

首先使用 git 把那个 repository 克隆下来

git clone https://github.com/lukas2511/letsencrypt.sh
cd letsencrypt.sh

接下来的所有操作都假定在该目录内进行

配置

创建文件 config

#!/bin/bash

CA="https://acme-staging.api.letsencrypt.org/directory"
CHALLENGETYPE="http-01"
CERTDIR="${BASEDIR}/certs"
HOOK="${BASEDIR}/hook.sh"
CONTACT_EMAIL='[email protected]'
WELLKNOWN="/tmp/acme-wellknown"

mkdir -p $WELLKNOWN
chmod -R 777 $WELLKNOWN

注意,在这个配置中,CA 暂时被指定到了一个测试用的地址,这是为了防止配置失败导致证书数量超限。稍后配置好后我们会将它指向到正确的地址。

你应该把 CONTACT_EMAIL 指向自己的邮箱。

如果需要签发 ECC 证书,只要加一行

KEY_ALGO="secp384r1"

这个配置文件将自动创建 /tmp/acme-wellknown, 用来存储验证域名所有权需要的文件。我一开始想要采用 DNS验证, 但是发现这实在太过复杂,而 HTTP 要简单得多。当然,你也可以修改这个路径,但是等会儿的 Nginx 配置也将要作出对应的修改。

接下来我们需要配置 Nginx 让它能够接受 HTTP 验证请求。

Nginx

LetsEncrypt 在签发证书前将对你的域名发送请求以验证你对域名的所有权。刚刚我们也已经让脚本把验证需要的文件存储到 /tmp/acme-wellknown 里面。一个简单的做法是,对于每一个域名,让 Nginx 自动把到 /.well-known/acme-challenge 下的请求转移到 /tmp/acme-wellknown,但是,当域名很多的时候,这将会非常麻烦,因而这不是一个好主意。所以,我们需要一个更好的方案。

在一个开启了 HSTS 的服务器上,任何 HTTP 请求只有一个作用,就是 301 永久跳转到 HTTPS 下。当 Nginx 配置中同一个 server 同时监听了 80443 ssl 的时候,Nginx 将自动完成跳转工作。为了实现域名验证,我们只能将 80 分开监听,重新设置跳转规则。

首先,删除所有域名下的 80 端口监听。然后新建一个 server 配置

server {
    listen 80 default_server;
    location /.well-known/acme-challenge {
        alias /tmp/acme-wellknown;
    }
    location / {
        rewrite ^(.*)$ https://$host$request_uri permanent;
    }
}

这个默认配置做了两件事

  1. 将目标是 /.well-known/acme-challenge 的请求全部转移到 /tmp/acme-wellknown
  2. 将其他请求全部重写到对应网站的 https 地址

因为这是默认配置,所以它将对任意域名起作用。这样,Nginx 已经可以用作域名验证了

hook.sh

然而,就算完成了以上两步,签发后的证书还是不能自动被配置到服务器上。对于这种问题,我暂时没有找到一个非常完美的解决方案 -- 我是极度反对用脚本重写 nginx.conf 的。所以我决定使用脚本自动生成一个只包含 SSL 配置的部分配置文件,然后 include 到完整的配置文件里即可。

所以我们需要一个 hook.sh -- 根据我们前面的配置,这个 hook.sh 会在特定的时候自动被 letsencrypt.sh 调用

#!/usr/bin/env bash
CONF_DIR="/path/to/somewhere"

function deploy_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
}

function clean_challenge {
    local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
}

function deploy_cert {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
    local CONF="$CONF_DIR/$DOMAIN.conf"
    if [[ -e "$CONF" ]]; then
        rm -rf "$CONF"
    fi
    echo "ssl_certificate $FULLCHAINFILE;" >> $CONF
    echo "ssl_certificate_key $KEYFILE;" >> $CONF
    echo "ssl_protocols TLSv1 TLSv1.1 TLSv1.2;" >> $CONF
    echo "ssl_ciphers HIGH:!aNULL:!MD5;" >> $CONF
}

function unchanged_cert {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
}

HANDLER=$1; shift; $HANDLER [email protected]

你需要把 CONF_DIR 变量的内容改为一个具体的目录的完整路径。对应的配置文件将存储在这里。

当证书签发成功后,函数 deploy_cert 将被调用。在这个函数中,我们将自动创建一个 $DOMAIN.conf,在里面写入对应域名的证书配置。注意,如果一个证书包含了多个域名,那么这个证书的文件名将是那个主要域名。

要使用证书,只要在 Nginx 配置里面加入

include /path/to/somewhere/domain.conf;

其中 /path/to/somewhere/CONF_DIR 一致,domain 与主域名一致。

测试

创建 domains.txt, 在里面输入你要签发证书的域名。一行表示一个证书,如果一个证书需要包含多个域名,请在一行内使用空格隔开。每行的第一个域名将作为证书的主要域名。

然后就可以执行测试了 --

./letsencrypt.sh -c

如果执行成功,我们就可以退出测试模式了。把 config 中的 CA 改为

CA="https://acme-v01.api.letsencrypt.org/directory"

接下来,删除 certsprivate_key.*, 重新执行

./letsencrypt.sh -c

即可获取有效的证书。接下来在 Nginx 中引用配置文件即可。

自动续期

我选择使用 systemd-timer 实现。

letsencrypt.service

[Unit]
Description=LetsEncrypt

[Service]
Type=oneshot
User=your_user
ExecStart=/path/to/letsencrypt/letsencrypt.sh
WorkingDirectory=/path/to/letsencrypt

letsencrypt.timer

[Unit]
Description=Daily LE Task

[Timer]
OnBootSec=15min
OnUnitActiveSec=1d

[Install]
WantedBy=timers.target

然后执行

systemctl start letsencrypt.timer
systemctl enable letsencrypt.timer

即可每日自动检测是否需要续期。

啊,还是要注意替换那些 PLACEHOLDER 哟呜。

我们不需要同情

上个星期,我去某酒店吃饭。饭前去了趟洗手间,出门的时候遇到两个小男孩,看见我坐着轮椅,于是问

你腿坏掉了吗?

我回答了

是。

于是他们俩笑着跑开了。

这种情况我已经习以为常了,不过这次有一点不一样,就是这两个小男孩的家长不在身边。按照惯例,如果这时候家长在身边的话,多半会有以下两种反应之一。一是赶紧拉走小孩子,鬼鬼祟祟地像是看贼一般斜眼看着我;二是当面教育小孩子要同情残疾人,不能怎么怎么样blahblahblah……

有时候我倒觉得,家长这种做法,反而会让我感觉更难受。你们可能觉得是在同情人,你们可能觉得这种同情是必须的,可是也许我们并不需要所谓同情。

星期五在阅览室看杂志,看到一篇文章,讲的是香港的学校是如何教育小朋友不要歧视残疾人的。文章里谈到,香港有些学校并没有专门对学生讲人人平等,讲拒绝歧视,而是在每个班级里设置一定量的残疾学生,让他们融入到这个集体中去,所谓『习以为常』。这样时间一久,自然也就不会再对街上的 与自己不一样的人 投去异样的目光了。文章里就提到了一个例子,说一个小孩谈论他的同学的义眼掉出来就好像在谈论稀松平常的事情一样,没有一点点惊讶的语气。要是摆在国内,估计小朋友们都会把这件事当作新鲜事和家长『分享』。

这就和 同情 全然不一样了。sympathy同情 时的解释为

feelings of pity and sorrow for someone else's misfortune.

是对其他人的不幸表示的一种遗憾。认为他人不幸的前提,首先是觉得这个人没有融入自己所在的群体,觉得这个人与自己有不同,且这种不同是不幸的。在香港的那个例子中,孩子们并不觉得那些残疾人是遭遇了什么不幸,因为那些孩子把他们看得和自己同等,在潜意识中 接受并认同 了他们与自己不一样的那些地方,并把这种不一样视为和相貌、信仰、民族的不同一样的存在。如此一来,自然也就没有同情一说了。

我不想得罪一些人,但是我真的想说,同情 二字和 歧视 并没有什么本质上的区别,它们都是在把某一类人隔离出自己所在的群体,只不过前者的方式比较温和,看起来更 人性 罢了。当你走在大街上,所有人都避开你的目光,都不想看见你这种人,还在背后对你议论纷纷,这叫歧视;你走在大街上,所有人都回头看你,嘴里啧啧啧说一些 真可怜 一样的话,这叫同情。两种情况你都不会自在。而且如果你想和那些路人说话,你都没有办法与他们正常交流 -- 在第一种情况下,他们绝对不会理睬;在第二种情况下,无论你尝试引起什么话题,他们都会把话题转移到 你怎么了 你遭遇了什么不幸 唉真可怜 这种话题上去。说白了,无论是歧视你还是同情你,他们都没有把你看作一个能和他们平起平坐、融入社会的个体,他们都在尝试孤立你。同情被套上了一个人性的光环,骨子里却是一样的野蛮。正是因为这人性的光环,很多人都以为同情是难得的品质,都以为所谓特殊人群需要的就是同情,从而随意使用他们的同情。其实只要真正地换位思考一下,就知道这样的同情不是我们这类人群所需要的了。

我们都说现代社会是多元化的,那么什么是多元化呢?多元化,就是这样过分的同情或者歧视被消除的过程。比如说,从前你会觉得同性恋是种病,会觉得得了这种病的人好可怜,而现在你可能会尊重他们的性取向;从前女性地位低下,你家要是生了个女孩子,你可能会可怜她为何生来女儿身,而现在你可能承认女性与男性具有同样的地位,你不会因为一个人的性别而对他另眼相看。这就是多元化,是不同的人群各自争取平等的生存权利的结果。所以好的现代城市里为什么要设置无障碍设施?这也是残疾人这个人群争取自身的平等生存权利的结果,是其他群体对于这个群体的 接受与认同, 承认他们与自己平等,可以融入自己的群体,和自己一同生活。

诚然,我们这个群体总是需要帮助,这在无障碍设施不齐全的时候是无法避免的事情。但是帮助和同情,也是两个不同的概念。你不会因为某人需要帮助而同情他,更不会因为同情某人而自觉帮助他 -- 这种事情我倒是见得多了。对于路边的乞丐就是个例子,有些人可能是真的可怜,有些人可能是通过某些手段『雇佣』来装可怜的。无论是自己可怜还是被雇来做这种工作,很多人都会同情他们,可是少有人满足他们的乞讨请求,更少有人设法改变那些被雇佣者的生活。这就是 同情而无动于衷, 有点 十分感动然后拒绝 的味道。换一个情境,今天考试,小明没带橡皮,问小红借了一块,小红不会因此同情小明;可是小红要是借机大谈 小明好可怜哦, 那么小明就会想要把小红批判一番了。帮助是一种尊重,你是因为尊重一个人,接受并认可 他的人格,才会在他遇到困难的时候对他伸出援助之手,而反过来,一个受到 接受和认可 的人也会在必要的时候帮助那些帮助过他的人。这样,他便融入了这个社会。而基于同情的所谓帮助往往是 单向 的,你会因为同情而帮助一个人,却不会需要他的帮助。当他想反过来帮助你的时候,你会拒绝他,并表示你不需要 来帮助你。这就十分尴尬了,因为这个被你帮助的人以后也会因此而不敢接受你的帮助,他被你隔离了,你不 接受并认可 他的与你平等的存在。他无法融入你所在的群体。实际上,对于特殊群体的帮助,最最最最最最最重要的就是让他们融入社会,以自己的能力生存于这个世界,并获得来自他人的尊重。这是一千一万倍的同情都做不到的 -- 这也是为什么从前的我选择了向互联网这个方向发展。

这里我不得不讲一个例子,就是某助残项目。从前他们联系过我,因为我是 Android 开发者,他们希望我能为他们制作一个客户端。我了解了一下他们的项目,然后拒绝了。他的项目是什么呢?通过大量的志愿者无偿在线提供 人肉验证码识别 服务来帮助残疾人(主要是盲人)使用在线服务。我就觉得这个很可笑,因为他们这种行为,并不能真正地帮助到盲人,只能徒增盲人对他们的依赖性,只能显示他们自己的所谓同情心。要想让盲人正常地使用网络,我们就必须要让所谓基础服务的提供商们解决这个问题 -- 连有声验证码都没有,算是个合格的网络服务商吗?所谓验证码便是盲人在网络时代的平等生存权利之一。你动用大量的志愿者,耗费大量的时间无偿帮助,你就是在承认盲人就是无法自己拥有这种生存权。这种所谓帮助,就是不能 接受和认可 的体现。试想,如果网络服务提供商们都和我提到的香港的小朋友一样,对盲人这类特殊人群习以为常,他们怎么会在设计验证码的时候考虑不到这一点?我以为,要想让盲人融入这个网络时代,我们只有协助他们一起去争取应得的权利,而不是白白地浪费劳动力。

我们真的不需要所谓的同情,我们需要的仅仅是 接受和认可, 我们需要的是平等的生存权利。同情对于我们来说更多的是一种伤害。我已经习惯了,我可以免疫这种伤害,但是世界上残疾人不是只有我一个。在网络上,没有人知道你是什么人,可是没有人能只活在网络上。希望大家都能好好地、平等地与他们成为朋友。

愿世界永保和平。