阅读视图

发现新文章,点击刷新页面。

我停止了探索 Fediverse

在去年的文章 Fediverse 与社交 中,最后提到 GoToSocial 的出现让我又有了一种会喜欢上在 Fediverse 中探索的错觉。但不到半年,这种错觉已然幻灭。

note

我对 Fediverse 的态度在前面的文章中已经很明确。虽然我喜欢 Fediverse 的理念,喜欢拥有自己的数据,喜欢没有广告,喜欢不被大公司收集隐私的感觉。但开局就单机社交的难度还是太高,你并不会成为自己的中心,反而只能困在自己的围城里。

我试图推荐给他人,但没几个人会愿意离开舒适圈去使用其他学习成本更高的平台。归根结底社交这件事并不在于去不去中心化,在什么平台,而在于参与的人,首先是个人的需求,再者是生活中的朋友,然后是有共同圈子的陌生人。我也曾多次花上一整晚去到处搜索发现,但除了媒体账号外,找不到几个感兴趣的,我喜欢的歌手、各类创作者们仍然活跃在中心化的平台上。

显然目前 Fediverse 的优点还远远不足以解决这个最大的问题。尊重每个人的实际需求,社交媒体也许并没有多少实质上的社交,发再多的动态,点赞再多的帖子都比不上发条消息找朋友开一局游戏,出门吃顿饭。

对我来说,Fediverse 最终变为了一个专用的「吐槽」回收站。因为我并没有多少独特或有价值的观点,也没多少人关心我写的内容,我发现不了别人,别人也发现不了我。同时,对不少人来说,Fediverse 最终会成为一个贴生活琐事自言自语的「树洞」,或者成为建政爱好者们无休止宣泄的地方。当然,也会有不少人在 Fediverse 找到舒适的社区和聊的来的朋友,找到快乐。

现在想来,离开社交媒体多年的我果然不会因为 Fediverse 的出现而找回那些失去的情绪。更多的只是想了解这些新技术,折腾软件的搭建、维护过程罢了,对我来说这个过程可能远比在 Fediverse 中互动更有趣。

或许去中心化和中心化并没有那么对立,人生苦短,即便是取乐,有时也需要考虑如何更轻松地取乐,权衡需求使用就好,如果我只是需要一种舒适的「吐槽」方式,那就有太多的选择了。到这里,我依然会继续使用 Fediverse,但放弃自建和探索,使用 BlueSky 或 Mastodon 等官方实例,回归为一个单纯的用户,作为吐槽工具或者新闻阅读器是个不错的结局。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

互联网背景噪音

网站会有持续的漏洞扫描与爬虫采集;公网 SSH 会有不间断的字典爆破;搜索一分钟,伸手半小时,还能水经验;网友奇妙发言,以钓鱼引战为乐,互相问候为趣;竞技游戏就是为了让少爷们玩得舒服;出现苹果的地方就会出现「自适应」;科技饭圈,赛博割菜… 我一直试图把这些当作互联网的背景噪音。

思来想去问题出在多样性上,人脑是产生意识的物质器官,每个人都是自己的中心,这种中心性对每个人都是不可否定,绝对正确的事实,但每个人对其他陌生人又是可有可无的存在,这种矛盾导致了多数的社交性问题。

不同的人脑对自己中心性的优先级不同,低一些可以助人为乐、舍己为人,高一些可以自我主义、我即世界,就像婴幼儿一样。高低的差异很大程度上取决于成长环境,而即便是一个「成熟」的人,在互联网上考虑他人的优先级又比现实中低很多,制造噪音就是他们所理解的获得利益和乐趣的方式。就像「存在即合理」是对黑格尔原文的污染性错误翻译,但「存在即有因」,对于他们来说正确的事,我批评他们自然是不合理的。

而互联网平台的夸张标题/争议/擦边等流量式推荐算法属于更强烈的噪音,他们是墙内养蛊的竞争者,以提高自己的毒性为最高优先级,希望你参与到背景噪音中去成为他们的养分。一旦中心性来到个别集体利益上,对于他们来说绝对正确的事,我批评他们自然也不合适。

尽管我一直都在尝试无视这些噪音,甚至把一些以前常逛的网站重定向到了 localhost,但噪音过多总会有难以忍受的时候,这些噪音已经无处不在,似乎互联网上的一切都在被噪音影响并不断侵蚀,就算你发现了净土般的地方,迟早也会被污染,有一种无处可逃的无力感。

于是我也失去了继续吐槽和对抗互联网噪音的兴趣,既然无法避免,这何尝不是为忧而更忧。或许问题出在我对互联网的打开方式上,又或许互联网并不是一种可靠的消遣方式?因此我所能做的只有减少网上冲浪和提高自己的噪音过滤能力以及,多吃薯条。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

我的数字生活降级

my-digital-downgrade

近年人们提及越来越多的断舍离、消费降级本质是大家越来越入不敷出开始重塑消费观念,无非是主动或被动的在花钱省时间和花时间省钱之间选择。

好在我在现实生活中的消费欲望向来很低,有多少钱就能过多少钱的生活。但在数字生活中却有很大的进步空间,很多时候剁手都不眨眼,眼睛还不干。于是,在各大互联网企业都在「降本增笑」的 2024 年,我也在有意的进行数字生活降级,试图进步一下。

数字消费降级。虽然很久以前就取消了各种平台会员和流媒体订阅,但还能进一步操作下。2024 年,我取消了续费用了 8 年的 Apple Music,用了 5 年的京东 Plus 会员,用了 3 年的 Microsoft 365,以及一些吃灰的域名和 VPS,手机卡套餐从 ¥129 /月的电信换为了 ¥39 /月的联通,同时也一年都没向 Steam「租赁」新游戏。下定决心到期不再续费「永不限速」的阿里云盘,目前已经连续陪伴 2273 天「永不变质」的哔哩哔哩。尽管如此,为防止化腾叔亏得坐公交车,还是在英雄联盟上赞助了 100 元左右,在 App Store 也消费了约 $70,域名续费约 $35,还有相比去年几乎没有变化的不合理 VPS 消费,需要继续展望明年。

数字体验降级。一方面要赞美买断制,另一方面要赞美开源。桌面浏览器从 Chrome 换为了 Firefox / Safari;单机社交平台从 Mastodon 换为了 GoToSocial;密码管理器从 Vaultwarden 换为了 KeePass + Strongbox;追番和听歌改用 Jellyfin + Navidrome 自建解决。其实也谈不上真的降级了多少,之所以这样说是因为在不少人眼里这些都属于反向操作,值得吐槽一番。但自己能接受就好,便利性对我的优先级并不高,少收集隐私、少作恶、尊重用户这些更重要。

出乎意料的是,数字生活降级后对我似乎没太大影响。互联网时代的娱乐方式要多少有多少,最简单的快乐是降低欲望,不看电影可以玩游戏,不玩游戏可以听音乐,不听音乐可以看书,软件够用的情况下越简单越好,服务能用的情况下越善良越好。不过也很难说从中获益了什么,最直接的大概是省下了不少开支,可以更好的度过这个冬天。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

小城与确定性的墙

the-city-and-its-certain-walls

拿到村上的新书《小城与不确定性的墙》后放了一周,等待其与书桌达成和谐后花了三天读完,读书时总静不下心,不时有触电一般的麻痹感。本想就这么独自咽下去,但回过神时已经在断断续续的写点什么了。这篇文章不是书评,也谈不上读后感,没有剧透,记录一点无关紧要的东西。

四年前写过一篇文章「我读村上春树」,而村上这次的新书与以往的风格略有不同,已然无法倒入原来的酒瓶,该说是更加清淡了还是更加醇香了,我一时间还难以分辨。不过他还是村上,我还是我,他爱写,我爱读,依旧处于某种美妙的平衡,大体上。

问题出在品酒的人身上,书的开篇起于十七岁的「我」和一座小城,想起十年前十七岁的我也发现了一座小城,不幸的,还能找到当时的推文。

the-city-tweet

如今看来略显造作,不过那时的心情还原原本本地保存在大脑里。两座城的位置不同,但基本结构可说相同,基本结构虽然相同,但细微之处肯定被修改成了为他而设的小城模样。因为那是为他而设的小城嘛。这些年来,我始终没找到那座小城的入口,我的影子不曾离开我,也绝不对我说一句话。或者,我始终没走出那座小城,即便随时可以离开,也始终未曾踏出一步。

近几年做梦的次数多了起来,世界尽头的梦中世界依旧模糊,你偶尔会出现,但醒来后很快就会被墙分隔开。我多少已经确定,就算能够重逢,那堵墙依然会在那里,它是确定的墙,无论过多久,不管是十七岁、二十七岁还是三十七岁,它永远会在那里等着我。

读完这本书,村上式的成长与寻找终于迎来了结局。我也终于下定决心拔掉同样卡在我喉中的鱼鲠,就算是确定的墙,只要闭上眼睛,向前一直冲过去,只要心里不害怕,墙就根本不存在。如果哪天突然有人找到你,就不要再疑虑,像清晨落入房间的第一束光线般,果决地离开这座小城吧。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

一只特立独行的猪

我有一位朋友在养猪场工作,其实我和他不太熟,但每三个月他轮班休假时总要拉我吃一顿饭,讲他在猪场里的趣事,我出于无聊每次都没拒绝。朋友说他这次遇到了一只特立独行的猪,当然,和王小波认识的不是同一头猪,他认识的这只猪不会跳上房顶晒太阳,也不会学汽笛叫,但有它自己的特别之处。

朋友说,现在的猪场都严格管理,有一大堆规定要遵守,严控出入、定期消毒,一旦出现问题,就要隔离淘汰。但说实在的,这只特立独行的猪,暂且称它为「特猪」,总不太合群,也不愿守规矩。每当开饭时其它猪都哼哼着埋头苦吃,就它一猪在旁边不以为然,等到其它猪都吃饱喝足赞美今日伙食时,它才在一旁嚎叫起来,仿佛在控诉伙食的差劲,完全不是给猪吃的。当然,我朋友也不会理睬它,过了饭点就该赶回猪圈中去,所以特猪总是在最后时刻冲上去胡吃几口,每次都让他忍俊不禁。

特猪还有一个奇特的地方,不爱睡觉。其它猪呼呼大睡时它总爱用头去顶它们,因此脸上总挂着一排猪蹄印子,朋友说这排猪蹄印子可能是它骄傲的勋章,有了这勋章它便无所畏惧,时刻享受自己独特的风采。有一次我朋友刚进场地,特猪远远地看见了他,便抬起头来炫耀它的勋章,朋友学着猪叫朝它哼了一声,不想它立马大嚎起来,铆足劲儿试图冲过围栏来顶我朋友,好在围栏够结实没让它得逞,落得个四脚朝天,不然我朋友的屁股可要遭殃。

上个月某一天,我朋友发现特猪不见了,赶紧查看监控,才看到是场长一大早来把特猪抓走了,询问后场长告诉他这只猪太瘦了,半年了都喂不肥,送到隔壁屠宰场止损了。朋友口头上称赞了场长的智慧,但暗地里还是有一丝失落,虽然特猪总是特立独行,但多少是个乐子啊。

「你知道最神奇的是什么吗?」朋友边说边用筷子在酸菜肉丝汤中翻了翻,似乎没找到肉丝,不等我回答赶紧补充道,「前天下午我准备休假离开猪场时,又瞥见角落里出现了一只挂着骄傲勋章的猪,一开始还以为眼花了,仔细一看,这可不止一只啊,两只、三只… 居然整个猪场里的猪脸上都有一排猪蹄印子!」

朋友用夹着烟头的手兴奋地举起啤酒杯,用力伸过来碰了碰我的,烟灰掉进了我的杯中,我们笑着一饮而尽。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

Fediverse 与社交

Fediverse(联邦宇宙)的理念很美好,简单来说就是把社交这件事从网络巨头公司中解放出来,通过去中心化让用户对自己的数据拥有更大的控制权,是一种更开放、多样的交流方式。Fediverse 有不同的通信协议可以选择,也有社交媒体、视频/图片分享、博客等不同的服务,它更像是一个生态系统,用户可以在不同服务之间自由互动,而不只是局限在单一平台内。

自建过 Misskey, Pleroma, Mastodon,但加起来的存活时间可能也没超过一年,也许是我的使用方式有问题,基本只拿来当个新闻阅读器,导致每次迁移服务器时都没动力重新部署,也可能是我逐渐发现去中心化和社交这两件事并不是那么合拍。

我的 Mastodon 实例图片
我的 Mastodon 实例删库前截图留念。

去中心化与社交

我在随想「博客的未来是去中心化吗」中提过,绝大多数人的社交习惯是趋向于中心化的,我们更享受中心化的便利和热闹,不管是现实中的朋友还是喜欢的歌手或演员,在中心化平台里都能轻松搜索找到,就算不主动关注也会在某一天突然出现在你的首页。但在 Fediverse 中找一个人只有靠缘分和口口相传,我想关注的人大多没注册账号,也可能注册了但找不到,搜索和发现内容也取决于实例中所有用户的关注。所以哪怕有 10% 的人鼓起勇气离开推特、微博等平台,在选择 Fediverse 平台和实例时也只是想找个方便、稳定且有一定用户量的社区。

当然,这不是「去中心化」的问题,整个 Fediverse 都在努力去中心化,只是人们在去中心化中仍然选择了中心化,这种选择也是合理的,社交这种事总是不嫌观众多。另一方面,只要接受用户注册,每个实例的运行、审核和管理的成本都不会小,而且几乎只靠一个人或几个人维护。去中心化从设计上就在避免广告、跟踪和隐私收集,管理员除了接受捐赠外几乎找不到其他收入来维持基本开销,一旦你加上广告,用户自然会毫不犹豫地迁移到其他实例上去。对于没有赞助的实例,要维持长期运行更像是比谁家的管理员更能用爱发电。

如果我们愿意放弃一点便利性,去中心化和社交可能会和谐得多。

Fediverse 不只 Mastodon

从 StatusNet, GNUSocial 到 OStatus,去中心化社交并不是一个新东西。现在谈到 Fediverse,很多人就默认等于 Mastodon,这不是一件好事。如今 Fediverse 集中在 Mastodon 上,而 Masodon 用户集中在几个大实例上,甚至搭建 Mastodon 的服务器也集中在 AWS 和 Hetzner 上。Fediverse 看似庞大,圈子却很小,也相对脆弱,只需关闭少数重要节点,或者某个服务托管商网络故障,就能让大半个宇宙失去联结。

这也导致了 Mastodon 越想替代推特,就越可能成为下一个推特。Mastodon 对 ActivityPub 的实现在很多时候已经偏离了规范(ActivityPub 自然也存在限制),有不少自己的特性,这对于其他平台来说,是遵守 ActivityPub 规范还是以老大 Mastodon 为标准呢?如果 Mastodon 的官方实例无限膨胀下去(毕竟在大多数人看来这是最可靠的选择)无疑又会成为下一个中心化平台。「不爽可以随时迁移」的特性也看似美好,但你要确保及时导出了数据,而且 ActivityPub 注定了数据迁移并不容易(更像是跳转的形式,无法在新实例上导入历史发文记录),相比之下 BlueSky 的 AT Protocol 协议在迁移数据的设计就要友好不少。

不过我并不反对 Mastodon 成为事实上的标准,Mastodon 对推广 ActivityPub 和联邦宇宙做了很大贡献,只要保持开放性,以用户体验为主,也没什么不好。

孤岛和隐私问题

尽管 Fediverse 的设计旨在去中心化和多样性,为用户提供自由的选择,但 Fediverse 并没有消灭权力,而是将权力分散到了每一个实例。每个实例都有自己的规则和偏好,实例管理员拥有绝对的控制权,能根据自己的爱好、文化认识或全凭心情去封禁一个话题或屏蔽另一个实例,如果一些大实例互相屏蔽,将导致用户只能在特定的社群中交流,形成信息孤岛,而在小实例中这个问题会变的更加严重。Mastodon 最近也推出了一个名为 Fediscovery 的新项目,类似于中继协议但更加轻量,来帮助中小型实例和他们的用户在 Fediverse 中更好的搜索和发现内容,不过效果还有待检验。

虽然 Fediverse 强调保护用户隐私,但目前只限于理念和程序代码本身的层面上,实例管理员完全有权限收集和分析用户数据,或者某些实例缺乏技术支持,服务器安全措施不到位等因素依然会导致用户信息泄露。同时,中心化平台至少有政府和法律监管底线,而 Fediverse 的特性注定了会更容易产生一些不友好的实例,仇恨言论、歧视、色情等,但一般管理员看到后都会选择封禁这些不友好的实例,这里也不做探讨。

Fediverse 与社交

说了这么多,那么 Fediverse 的所提倡的去中心化与社交矛盾吗?我认为不矛盾,虽然不太合拍,但无论如何 Fediverse 带来的意义是积极的,不管是真的去中心化还是在「cosplay 去中心化」,它为我们解决的是有没有的问题。

很多时候,大家只是想要一个可以畅所欲言的平台或者说多一个选择,Fediverse 在今天这个隐私收集泛滥,动不动就禁言封号的中心化平台时代为我们提供了一个更自由的选择,有一小部分人在 Fediverse 中找到了自己舒适的圈子已经足够说明这是行得通的。没有广告与流量,大家只是在纯粹的分享或吐槽,就像社交应该有的样子一样。小众不一定是坏事,很多平台和社区最美好的时候都是他们小众的时候。

为了发展一个「不断增长、健康、经济上可行和多极的 Fediverse」,一些致力于开放网络和分布式社交平台的专家和倡导者于上个月(2024 年 9 月 24 日)成立了一个名为 Social Web Foundation(社交网络基金会)的非营利组织,尽管赞助公司中有几家似乎缺乏说服力,说明 Fediverse 仍在向着更好的方向不断发展和完善。

最后,我认为 Fediverse 并不是一切问题的解决方案,可以只把它看为另一种社交方式,它没有魔法,并不适合所有人,也大概率不会成为主流,但如果你认同其理念,总是值得去探索尝试的。如果条件允许,我更推荐你自己或和几个朋友一起搭建一个实例,宇宙中如果只有几个巨无霸星球的话也太寂寥了点。

Go to social?

如开篇所说,经历几次尝试后,我觉得 Fediverse 对我来说是可有可无的,毕竟我对社交媒体的使用局限在看科技新闻和猫猫狗狗。当然多一个选择也不是坏事,没有坚持的原因还有一部分是因为目前的平台都太重了,尤其是 Mastodon,对低配 VPS 并不友好,直到最近发现了使用 Golang 开发的 GoToSocial 项目,又对自建产生了兴趣。

在 GoToSocial 的官网介绍中有这样一段话:

## Is there a flagship instance I can join?

Nope!

We also don’t believe that flagship instances with thousands and thousands of users are very good for the Fediverse, since they tend towards centralization and can easily become “too big to block”.

「我们也不认为拥有成千上万用户的旗舰实例对 Fediverse 来说有多大好处,因为它们倾向于集中化,并且很容易变得太大而无法阻挡。」

难得和我的观点契合,再看到 GoToSocial 只占用大约 250-350MB 内存,还支持 SQLite,就算迁移也十分方便,大幅降低了自建的门槛,当即决定在 Fediverse 中再复活一次。官方文档也写得很清晰易懂,很快就搭建了一个实例。

不过 GoToSocial 目前刚进入 Beta 阶段(官方 Roadmap),存在一些 bug 和功能缺失,目前也没有网页端进行交互,只有个人主页和设置等几个页面。日常使用需要配合兼容 Mastodon API 的客户端或者 Web 应用,我网页端搭配的是 Elk 或者 Phanpy,手机端直接使用 Mastodon 官方 App 或者 Ice Cubes (iOS),最近一周多体验下来确实会遇到一些 bug,但没什么大问题,能满足轻度使用的需求。

这次也立个目标,至少坚持到 GoToSocial 推出稳定版(2026 年+),希望这期间能在 Fediverse 中找到更多的乐趣。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

承认的勇气

王小波写过一篇文章叫「承认的勇气」,他说承认自己傻过也是一种美德,年轻时他没有这种美德,总觉得自己很聪明,而且永远很聪明,既不会一时糊涂,也不会受愚弄。后来终于有了一种智慧,有这种智慧也不配叫做智者,顶多叫个成年人。很不幸的是,好多同龄人连这种智慧都没有,这就错过了在他们那个年代里能学会的唯一的智慧——知道自己受了愚弄。

为了拥有这种美德,我也尝试承认自己是傻X,但王小波说的也不一定对,因此我也可能不是傻X。但可以确定的是现在的人普遍缺少这种美德,总不肯承认自己傻过,仿佛这样就能使自己显得聪明。比如在网上总有相当多的人热衷于信誓旦旦的胡说八道,就算超出自己知识和理解范围,明知错误也要自信的强答或者强骂几句,被指正也无所谓,他们不在乎对错,只是想证明自己更聪明。

阶级上层的人希望阶级下层的人是傻X,这样他们可以长久保住阶级地位;阶级下层的人认为阶级上层的人是傻X,只顾自己不想法缩短阶级差距。事实上他们都不是傻X,只是愚弄他人和被愚弄的人。

我一直都很反感「世界是个巨大的草台班子」这句话,无疑是忽略了除阶级外的一些重要东西,无论归因于出生、成长环境还是运气,成为所谓的草台班子大致也需要相当的努力,如果不需要努力,那他更不大可能是个傻X。同时,说这句话也不会让我觉得自己更聪明或好受一些,因为我也终于有了一种智慧——知道自己受了愚弄。如果要安慰自己自然有更好使的句子,我们只是恰好在宇宙边缘的一个破球上过家家。

假如这世上有人愚弄了我,我更是心服口服:既然你能耍了我,那就没什么说的——我是傻X。人生在世有如棋局,输一着就是当了回傻X,懂得这个才叫会下棋。

除了要有勇气承认自己是傻X,还要有勇气承认自己会犯错,承认自己爱,承认自己恨。但如此直白的承认会被大家指认为一个低情商的人,所以有时也要学会委婉的承认,至少你自己心里要明白。就像我承认自己只是想水一篇文章而已,如果你不信,那我就是个有美德的人。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

mediaX - 轻量书影音记录管理工具

前段时间写 Misc 页面的时候就在考虑书影音记录的数据源问题,最后得出结论还得有一份自己本地的备份才好。Excel 固然可以满足,但不够优雅,搜索 media tracker 也能找到不少已经比较完善的项目,但我只想要一个小而美。于是决定着手开发一个自己的书影音记录管理工具。

一开始准备用 Python 或者 PHP 写,不过既然主要目的是备份,运行环境要求自然越低越好,最好只需要一个可执行文件和 SQLite 数据库。目光最终转向了 Rust 和 Go 语言。

上一次接触 Go 还是多年前的 Hello World,所以这个项目也是抱着学习的态度,想到哪写到哪,写到哪查到哪。奋发图强了半个多月,基本功能也比较完善了,如有需要欢迎使用体验。

Github 项目地址:https://github.com/scenery/mediax

项目简介

mediaX 是一款使用 Go 语言开发的个人阅读/观影/看剧/追番/游戏记录 Web 管理工具。

特点:

  • Go 原生 Web 开发,无外部框架
  • 轻量,简单,无任何 JavaScript 代码
  • 数据库使用 SQLite + 纯 Go 实现的驱动 glebarez/sqlite,无 CGO 依赖
  • 支持从豆瓣或 Bangumi 导入已有历史记录
  • 支持新增条目时自动从豆瓣或 Bangumi 获取数据
  • 支持导出内部记录为 JSON 数据

预览:

主页和分类页
主页和分类页。
详情页和添加条目页面
详情页和添加条目页面。

条目的信息字段没有设计太多,只记录一些关键数据。网页样式上延续了本站的风格没有风格,同样没有使用任何 JavaScript 代码和 Cookie,因此部分功能实现上比较受限。用户登录和多用户功能一开始设计时就决定不做,预期是一个运行在树莓派、NAS 等设备上的局域网应用,如需放到公网使用请使用加密 tunnel 连接,或在 web server 上做好鉴权和 IP 限制。

* 该项目主要是为了满足个人学习和使用需要,暂不接受新功能建议,如果有额外功能需求欢迎 fork 修改(MIT 协议)。

使用说明

mediaX 支持导入豆瓣(图书/电影/剧集)或 Bangumi 番组计划(图书/电影/剧集/番剧/游戏)数据来源的个人历史记录,其中 Bangumi 的电影和剧集记录因 API 返回内容限制未做详细区分,简单的判断如果只有一集归类为电影,大于一集则归类为剧集。

  • 导入豆瓣数据:首先使用「豆伴」导出数据,安装好插件后,进入设置连接账号,然后点击浏览器任务栏插件图标选择 新建任务,选择备份的项目中只勾选第一个 影/音/书/游/剧,等待任务完成后,点击右上方 浏览备份 - 备份数据库,解压下载的文件,其中的 tofu[xxxxxx].json 为需要的文件。
  • 导入 Bangumi 数据:可以直接使用 Bangumi 提供的 API 获得数据,在返回结果最后的 total 属性中可以看到你的记录总数,由于单次请求最多返回 50 条记录,如果超过 50 条需要修改 offset 分页参数多获取几次,最后将所有数据汇总保存为 JSON 文件。

导入数据

将 JSON 文件放到 mediaX 程序相同目录下,执行命令:

# Linux / macOS
./mediax --import <douban|bangumi> --file <file.json> [--download-image]
# Windows
mediax.exe --import <douban|bangumi> --file <file.json> [--download-image]

# e.g. 导入豆瓣数据
# ./mediax --import douban --file tofu[xxxxxx].json --download-image

最后的 --download-image 为可选参数,如果加上则导入的时候会尝试下载封面图片,如果数据量大的话会比较耗时(为了避免 IP 被 ban 下载间隔为 1s),请耐心等待。

不推荐重复导入一个文件,如果重复导入文件,目前只是简单的比对已导入的记录(原链接)和已下载的图片文件是否已经存在,如果存在则跳过导入。

导出数据

mediaX 支持导出内部数据为 JSON 文件:

{
  "subjects": [
    {
      "uuid": string,
      "subject_type": string,
      "title": string,
      "alt_title": string,
      "pub_date": string,
      "creator": string,
      "press": string,
      "status": int,
      "rating": int,
      "summary": string,
      "comment": string,
      "external_url": string,
      "mark_date": string,
      "created_at": string
    }
  ],
  "export_time": string,
  "total_count": int
}

导出命令:

# Linux / macOS
./mediax --export <all|anime|movie|book|tv|game> [--limit <number>]
# Windows
mediax.exe --export <all|anime|movie|book|tv|game> [--limit <number>]

# e.g. 导出最近 5 条图书数据,如果不加 --limit 参数则导出该类型全部记录
# ./mediax --export book --limit 5

导出的文件将自动保存在程序目录下。

API

目前 mediaX 支持通过 API 获取基本的个人收藏条目数据,请求接口如下:

/api/v0/collection

返回格式:

{
  "subjects": [
    {
      "uuid": string,
      "subject_type": string,
      "title": string,
      "alt_title": string,
      "pub_date": string,
      "creator": string,
      "press": string,
      "status": int,
      "rating": int,
      "summary": string,
      "comment": string,
      "external_url": string,
      "mark_date": string,
      "created_at": string
    }
  ],
  "response_tim": string,
  "total_count": int,
  "limit": int,
  "offset": int
}

可选参数:

  • type: 获取数据的类型,默认为所有类型,可选 book, movie, tv, anime, game
  • limit: 获取数据的数量限制,默认(最大)为 50
  • offset: 获取数据的起始位置(跳过的记录数量),默认为 0

例如,使用 curl 命令获取数据:

# 获取最近 5 条图书数据
curl "http://localhost:8080/api/v0/collection?type=book&limit=5"
# 获取第 6 至 10 条图书数据
curl "http://localhost:8080/api/v0/collection?type=book&limit=5&offset=5"

Linux 上持久化运行

一个简单的持久化运行配置示例,新建 systemd 服务:

vim /etc/systemd/system/mediax.service

添加下面内容,其中 User 为运行服务的用户,需要有 mediaX 工作目录的读写权限, --port 8080 可以修改为你需要的运行端口。

[Unit]
Description=mediaX <https://github.com/scenery/mediax>
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/path/to/mediax
ExecStart=/path/to/mediax/mediax --port 8080
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target

运行管理:

# 重新加载 systemd 配置:
sudo systemctl daemon-reload
# 设置开机自启:
sudo systemctl enable mediax
# 启动服务:
sudo systemctl start mediax
# 停止服务:
sudo systemctl stop mediax
# 服务状态:
sudo systemctl status mediax

写在最后

因为 Go 语言入门比较友好,整个项目也比较简单,开发过程中除了写缓存功能时大脑多次宕机外,其他都还算比较顺利。不过是个体力活,赞美 if err != nil

数据库最初设计时按类别建了多张表,差不多写完的时候突然觉得数据量又不大,一个正常地球人一生也读不了看不了多少,多表徒增复杂度,最后又把数据库重新设计合并为一张表。如果你是三体人可能会遇到数据库性能瓶颈。

导入记录功能需要用户提前准备数据,虽然可以写进程序里但没必要。新增条目时自动获取数据依赖 API,如果未来获取豆瓣数据失效,会考虑改为从 The Movie Database (TMDB) 上获取数据。顺便提一下导入豆瓣数据后搜索“未知”,可能会发现一些被豆瓣下架的条目,比如「V 字仇杀队」等,到 Wayback Machine 上搜一下相应链接说不定能在远古记录中找回来。

最后,这应该不是一个长期维护项目,稳定之后可能不怎么更新。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

我的博客写作流程

两年前写了篇文章叫「谈谈写字这件事」,而现在这篇文章的标题却斗胆将「写字」改成了「写作」,并非已经大功练成,只是又吃了两年饭,多少要鼓励自己是有进步的才好。

去年也试图找些捷径,学习了斯蒂芬·金的《写作这回事》和村上春树的《我的职业是小说家》两本书,包括在阅读其他各类书时,常常感叹于作家们的妙笔生花,我是怎么挤脑袋也写不出来的。因此我认为写作这件事对我来说仍旧遥远,有时回看以前发布的一些文章甚至会觉得狗屁不通,不过除非发现错别字或者重大错误,都倾向于不作修改,这样开头说的进步就有了证据。

我的博客文章从写作目的上来区分大致可以分为三类:

  • 短文随笔。供读者一乐,往往一小时内一气呵成,如「短信的消失」。对一件小事的简单吐槽或看法,读完三分钟后即可忘记。
  • 技术分享。热心服务读者,通常半天至一周完成,如「如何提高用户网页阅读体验」。本着负责的态度,需要查阅大量文档,尽量保证准确,拿走所需就可关闭。
  • 输出观点。尝试与读者沟通,一般几小时至两天完成,如「为什么我的博客没有友链页面」。想到哪写到哪,最后组合拼凑出大体符合逻辑的东西,赞同就微微颔首,不赞同就默念一句 SuperBoy。

从决定写一篇文章到最后发布,这几年也逐渐养成了一套自己的流程和习惯:

  1. 确定主题。最重要的原则是想写的时候才写,既然没稿费,也不存在交稿压力,我是不情愿立下每天一更或者每周一更的目标折磨自己的。通常是按照惯例,某个东西或某个阶段折腾结束,考虑写一篇记录或教程;有感而发,就某个东西或者事情想锐评一下;一时兴起,写一下某方面的经验分享或总结,比如这篇文章就是前天凌晨两点躺在床上时想写的。
  2. 完成初稿。写的时机和时间很重要,多半是在晚上,打开 Typora 和 Apple Music,能写多少取决于状态好坏,写到困了或者厌烦为止,为此有不少文章是写了一半时直接删掉,终不见天日。技术文章自然简单,一板一眼平铺直叙即可,但耗费精力;其他文章则坚持人生苦短、多用短句的原则,谨言慎行随心所欲的写,出于对以前中学的时候语文科目上被迫做的无数的阅读理解的语句赏析题的无声反抗
  3. 修改润色。主要是在初稿的基础上调整结构和斟酌表述,换句话说就是想办法让文章看起来更厉害一点。有时也会偷偷加上一些看不懂的无聊幽默,比如心情不好的我吃了两个汉堡,感觉更沉重了。
  4. 删繁就简。如今我的文章发布时相比初稿基本上都会删减 20% 以上。有人问如何写出真正帮助到人的文章,我认为站在如何不浪费读者时间的角度上很容易得出答案。尽管我认为废话也很重要,但不宜过多,尤其是在技术文章上。过于发散的内容完全可以大段删掉,除非表达作者思乡之情的时候,表述上也尽量保持简洁。
  5. 正式发布。这个阶段还会再通读几遍文章。发布前先用 hugo server 在浏览器上预览文章读一遍,然后休息一会儿后读一遍,最后同步到服务器发布后再马上打开网站读一遍。有趣的是经常在发布后又找到错误或者错别字。

这样写累吗?不算轻松。从快乐的分配上,大体可以将非盈利博客分为两类,一类是为了让自己快乐,就算只是写写今天又做了什么,摘录一段文字,又或是复制一些技术点做备忘录,只要在自己博客上记录下来分享出去就能感到快乐;一类是为了让他人快乐,绞尽脑汁写出有趣或有深度的内容,分享有干货的教程,等到获得了读者的正向反馈后才欣慰快乐。当然也有能同时获得两份快乐的优质博客,不过只是少数。我个人应该是从后者转变为了前者,再加上一点莫名其妙的习惯坚持。

其实我对自己文章的要求实在不算高(目前也写不出高水平的文章),不在意是否引经据典、富有文采,读起来通畅、能说服自己即可,算是对自己负责,也是为环保做贡献。因为对于网上的文章容内绝大多数人也不会味品细读,大概是扫读几眼会领大意即可。我也在不少文章中埋下过自以为精妙的对应或暗示,但想必有时间和心情去发现的人不多。

因此,博客写作流程这个东西,大家都不尽相同,不存在标准化或者更好的方案。无论何种方式,只要还能在博客写作上获得些许快乐,尽管随意发挥、随意潇洒。如果自己感到恰到好处,那就是恰到好处了。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

网站新增 Misc 页面

作为一个「只读」网站,偶尔点进来的时候偶尔也会觉得有点冷清,于是酝酿了几天,准备新增一个 Miscellaneous 大杂烩页面。迫于不再使用 JavaScript 代码 的 flag,内容形式比较受限制,于是就先添加一些无关紧要的东西。

初步计划是添加分享我的常用电子设备自建服务阅读和影视记录等内容。

前两个比较简单,找相应图片抠下透明背景,写写 CSS 就好了。比较麻烦的阅读和影视记录,我一直习惯使用豆瓣进行标记,即便豆瓣的问题已经老生常谈,API 很早就不再向个人用户开放,条目受环境影响随时可能消失,越来越封闭之类的。也在很久以前就想过换其他方案,但不得不承认豆瓣的记录功能对中文用户来说确实是独一档的存在。

常用设备与自建服务
常用电子设备(已去除)与自建服务。

想要展示最近阅读和影视记录,就 Hugo 来说,只要能获取相应的 JSON 数据就行。目前豆瓣可以在不登录的情况下看到指定用户的公开阅读和影视记录,如观影记录可通过 https://movie.douban.com/people/{username}/collect 查看。于是 Python 启动,很快就提取了想要的数据,但是抓阅读记录时却一直 403,折腾半天宣告失败。

要更简单直接的办法也有,比如通过油猴脚本或者浏览器插件,手动导出记录后再处理,但这样显得不够人性化。网上搜索后发现了怡红院落doumark-action 项目,看了下应该是使用了小程序的接口,赶紧测试后终于成功获取了相应数据。

而对于动漫番剧,我很早以前就换到 Bangumi 上记录了,通过官方 API 获取非常方便。将所有的数据汇总处理后就可以交给 Hugo 读取,之后再慢慢完善页面样式就行,最后的效果如下:

阅读和影视记录
最近阅读和影视记录。

但我还是比较担心这种方式获取数据的长期稳定性,之后可能会考虑将豆瓣的记录数据迁移到 NeoDB 上面进行管理。

至于该页面的更多内容,还需要多喝一些咖啡,不过 Misc. 的意义就在于啥都可以往里倒,未来的我应该能想到些有趣的东西。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

擅长对线的鲍勃

夏日,周末,鲍勃坐在电脑前忙碌着。

「你好,外卖到了~」

鲍勃开门接过外卖,一言不发,快步回到电脑前继续和网友激烈对线。

「你这 SB 是不是脑子没发育好,这么垃圾的剧情你打 10 分?你也只配看看厕纸了」

鲍勃骂骂咧咧的同时快速切换到了另一个标签页。

「狗汉奸,买苹果送钱给你美国爹造子弹?」

下一个。

「笑死,真有人喜欢六步朗啊,抱团冠军只会刷数据,太监真多」

鲍勃感到意犹未尽,又加急回复了一条:

「怎么不说话?太监们说话啊???」

房间里的时钟指针似乎卡在了某个无尽的瞬间,下一秒就跳到了晚上九点,期间只有键盘敲打和鲍勃不时的骂咧声不断循环。

等等,这个人说的好像也有道理,鲍勃突然愣住。但此时的对线已经不再是为了争个对错了,因为鲍勃坚信赢的只能是自己。鲍勃心率飙升,用颤抖的手敲下了吹响胜利号角的一句话:

「你X死了,懒得和SB解释,再回复我死XX」

对线数百回合之后鲍勃终于迎来了一场大胜,心满意足的关掉浏览器,瘫坐在椅子上,想起外卖点的豪华咖喱猪排盖饭还没吃。

什么嘛,这家外卖好难吃啊,写个差评再说。

······

睡前,看着节节败退不再回复的灰色头像,鲍勃对今天的战绩感到满意。当然,对线的机会也不总是有,所以鲍勃睡前还有另一个习惯,打开浏览器在几个不同的论坛用小号发布了几个帖子,就像夜里散落种子的仙人掌一样。

「地质大佬进,能否预测下富士山什么时候爆发?」

「A股再次失守3000点,但我依旧充满信心!」

「恕我直言可口可乐完爆百事可乐不服来辩」

明天也是充满乐趣的一天呐,正准备入睡时,鲍勃看到这样一条回复:

「大师,快乐的秘诀是什么?」

「不要试图和愚者争论。」

「大师,我完全不认同你的说法。」

「是的,你是对的。」

煞笔!CNMD!

凌晨三点,鲍勃仍难以入眠,尽管开着 24 度的空调,却还是感到燥热。终于,鲍勃坐了起来,点亮手机,屏幕发出的光格外刺眼,照到鲍勃的灵魂深处。此时鲍勃已然明白,这是只属于他的胜利曙光,无论如何自己也不能输,会赢的,能赢两次。

窗外,晨曦逐渐撕开了夜的幕布。此时的鲍勃已疲惫不堪,嘴角终于勾起一丝胜利的微笑,他强顶着如潮水般袭来的困意自言自语道:

如果我一直都在赢,那么谁在输呢?


* 本故事含一定的虚构成分。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

在 Chroot 环境下使用 Rsync 同步

我平时在备份或者同步服务器文件时一般会用到 rsync 工具,rsync 本身使用起来很方便,也比较可靠,但备份任务主要是通过密钥文件进行连接,不管是从 A 同步到 B 还是将 B 拉取 A,如果任一个服务器出现漏洞导致密钥泄漏,可能会导致另一个服务器买一送一。虽然一般会限制同步用户的权限,不一定会造成严重后果,但还是应该尽可能将 rsync 任务限制在更安全的环境下执行。

限制同步文件的 SSH 用户只能访问指定的文件目录似乎是个不错的解决方案。一开始想到的方法是直接使用 Docker,只映射需要同步的目录,这样可以简单粗暴的进行隔离。果然万能的 linuxserver.io 有现成的项目 docker-openssh-server 可以使用。在尝试中虽然遇到了一些问题,好在最后测试成功,也算实现了目的。但总感觉这种方式不够优雅,后了解到可以使用通过 chroot jail 的方式限制 SSH 用户在指定目录下活动,看起来非常符合需求,值得一试。

简单来说,chroot (change root) 是一种 Unix 操作,它指定一个目录作为某个进程的根目录(该目录称为 chroot jail),从而限制这个进程的权限,使其无法读取或写入该指定目录之外的文件夹和文件。

* 以下操作演示在 Debian 12 系统 root 用户下进行,实现在备份服务器(B)上通过 rsync 命令拉取生产服务器(A)上的文件,因此所有操作都在生产服务器(A)上进行,反之同理。

准备工作

新建一个专门用于 rsync 同步的用户和用户组:

groupadd rsyncuser
useradd rsyncuser -g rsyncuser -s /bin/bash -d /home/rsyncuser

设置用户目录权限,注意 chroot jail 目录的所有者必须为 root 用户,并且普通用户和组没有写入权限:

mkdir /home/rsyncuser
chown root:root /home/rsyncuser
chmod 755 /home/rsyncuser

为该用户生成 SSH 密钥,具体方法可以参考文章:修改 SSH 端口使用密钥登录并配置防火墙,如果后面需要通过 cron 任务自动同步,建议生成密钥对时不设置私钥密码,创建 /home/rsyncuser/.ssh 目录,将公钥添加到该目录下 authorized_keys 文件中。然后设置权限:

chmod 700 /home/rsyncuser/.ssh
chmod 600 /home/rsyncuser/.ssh/authorized_keys
chown -R rsyncuser:rsyncuser /home/rsyncuser/.ssh
# 或者保留 root 对 .ssh 的所有权,允许普通用户读取,两种方式各有利弊:
# chmod 755 /home/rsyncuser/.ssh
# chmod 644 /home/rsyncuser/.ssh/authorized_keys
# chown -R root:root /home/rsyncuser/.ssh

创建一个 rsyncuser 用户可以读写的目录 backup 用来同步文件:

mkdir /home/rsyncuser/backup
chmod 755 /home/rsyncuser/backup
chown rsyncuser:rsyncuser /home/rsyncuser/backup

测试 SFTP 连接(可选)

如果你只需要使用 SFTP 进行同步,可以简单的通过以下几个步骤实现,并保证了最小化权限。确认 /etc/ssh/sshd_config 中下行内容未注释:

Subsystem   sftp    /usr/lib/openssh/sftp-server

然后在文件最后添加:

Match User rsyncuser
    ChrootDirectory /home/rsyncuser
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no
    PasswordAuthentication no

保存后重启 SSH 进程:

systemctl restart sshd.service

此时你应该已经可以在其他服务器中通过 sftp 连接本服务器,通过下面命令测试:

sftp -i /path/to/private_key -P <port> rsyncuser@<ip>

配置 rysnc 环境

在 chroot 中运行 rsync 需要一些额外的依赖文件,相比只使用 SFTP 会麻烦一些。在 /etc/ssh/sshd_config 文件最后添加/修改为:

Match User rsyncuser
    ChrootDirectory /home/rsyncuser
    AllowTcpForwarding no
    X11Forwarding no
    PasswordAuthentication no

/bin/bash/usr/bin/rsync 的二进制文件(路径用 which rsync 查看)复制到 chroot 目录中:

mkdir /home/rsyncuser/bin && mkdir -p /home/rsyncuser/usr/bin
cp /bin/bash /home/rsyncuser/bin/
cp /usr/bin/rsync /home/rsyncuser/usr/bin/

使用 ldd 命令查看 bash 和 rsync 依赖的库:

ldd /bin/bash
# linux-vdso.so.1 (0x...)
# libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x...)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x...)
# /lib64/ld-linux-x86-64.so.2 (0x...)
	
ldd /usr/bin/rsync
# linux-vdso.so.1 (0x...)
# libacl.so.1 => /lib/x86_64-linux-gnu/libacl.so.1 (0x...)
# libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x...)
# libpopt.so.0 => /lib/x86_64-linux-gnu/libpopt.so.0 (0x...)
# liblz4.so.1 => /lib/x86_64-linux-gnu/liblz4.so.1 (0x...)
# libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x...)
# libxxhash.so.0 => /lib/x86_64-linux-gnu/libxxhash.so.0 (0x...)
# libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x...)
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x...)
# /lib64/ld-linux-x86-64.so.2 (0x...)

其中第一个 linux-vdso* 是虚拟库,不需要考虑。根据你的情况将其他依赖库文件整理后复制到 chroot 目录中:

cd /home/rsyncuser
mkdir lib64 && mkdir -p lib/x86_64-linux-gnu
cp /lib/x86_64-linux-gnu/libtinfo.so.6 ./lib/x86_64-linux-gnu/libtinfo.so.6
cp /lib/x86_64-linux-gnu/libc.so.6 ./lib/x86_64-linux-gnu/libc.so.6
cp /lib64/ld-linux-x86-64.so.2 ./lib64/ld-linux-x86-64.so.2

cp /lib/x86_64-linux-gnu/libacl.so.1 ./lib/x86_64-linux-gnu/libacl.so.1
cp /lib/x86_64-linux-gnu/libz.so.1 ./lib/x86_64-linux-gnu/libz.so.1
cp /lib/x86_64-linux-gnu/libpopt.so.0 ./lib/x86_64-linux-gnu/libpopt.so.0
cp /lib/x86_64-linux-gnu/liblz4.so.1 ./lib/x86_64-linux-gnu/liblz4.so.1
cp /lib/x86_64-linux-gnu/libzstd.so.1 ./lib/x86_64-linux-gnu/libzstd.so.1
cp /lib/x86_64-linux-gnu/libxxhash.so.0 ./lib/x86_64-linux-gnu/libxxhash.so.0
cp /lib/x86_64-linux-gnu/libcrypto.so.3 ./lib/x86_64-linux-gnu/libcrypto.so.3

复制完成后的文件目录应该如下:

chroot-folder-tree
/home/rsyncuser 文件夹列表。

完成后,可以先进入 chroot 环境检查 rsync 是否能正常运行:

chroot /home/rsyncuser
rsync --version

然后在 backup 目录下新建一个 1.txt 文件用来测试,在其他服务器上执行 rsync 命令获取 1.txt 文件:

rsync -av -e "ssh -p <port> -i /path/to/private_key" rsyncuser@<ip>:/backup/1.txt /home/1.txt 

如果一切顺利,你的备份服务器上会出现 /home/1.txt 文件,到此就实现了在 chroot 环境下安全的使用 rsync 备份。之后只需要设置一个自动任务将需要备份的文件打包放到 /home/rsyncuser/backup 目录下,再定时同步即可。

虽然使用 chroot 相比直接使用 Docker 的方式复杂一些,但不需要额外安装 Docker,也能节省一些资源占用,总的来说更值得推荐。

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

再见 JavaScript

JavaScript book

去年早些时候,为了放弃 jQuery,我用原生 JavaScript 重写了一遍本站的 JS 文件。现在,我有了一个更激进的想法,彻底去掉本站所有的 JavaScript 代码。

也没什么非去掉不可的原因,更没什么好处,几乎出于一时兴起,二话不说,一个通宵,两杯咖啡。所以我也不会想方设法的罗列 JavaScript 的各项「罪状」,如果非要找个一个理由的话,就当是为了减少碳排放吧。

在我们日常的网上冲浪中 JavaScript 几乎无处不在,随便打开一个可以交互的网站,少则加载一两个 JS 资源文件,多则几十个,还有各种前端框架开发的网站在挑战极限。就算没有单独引入 JS 文件,HTML 中 <script> 标签以及 onsubmit, onload 等事件也不会少,因为表单验证、评论留言、动态加载内容这些都很难离开 JavaScript。毫无疑问,JavaScript 在 Web 2.0 的发展中发挥着中流砥柱的作用,这点在以后的历史书中要考。

但也不是完全离不开,用可以但没必要来形容更合适。Github 在一篇博客文章 How we think about browsers 中提到,即使关闭了 JS,你仍然可以正常登录 Github,使用评论、搜索等功能:

With JavaScript disabled, you’re still able to log in, comment on issues and pull requests (although our rich markdown toolbar won’t work), browse source code (with syntax highlighting), search for repositories, and even star, watch, or fork them.

可想而知这需要大量额外的适配工作。因此更普遍的情况是,禁用 JS 会让交互式网站的功能受到不同程度的影响甚至直接罢工,如「去掉 JavaScript 后,这些网站会变成这样」这篇文章中所测试的结果。如果你也想看看自己的网站或者常用的网站没有 JS 的样子,Chrome 和 Firefox 都可以在开发者工具中暂时禁用 JS。

选项栏中或者 F12 打开开发者工具,找到并点击上方的小齿轮或者直接 F1 打开设置选项卡,滑动到最下方找到并勾选停用/禁用 JavaScript 选项,关闭开发者工具窗口后自动失效,刷新页面即可恢复 JS 加载。

Chrome 与 Firefox 禁用 JavaScript
Chrome (123) 与 Firefox (124) 中禁用 JavaScript。

好在我的网站本来就很简单(用 Hugo 生成的静态网站),关闭 JS 后基本不影响使用。在下手之前,也十分好奇那些真正极简的网站是什么样子,发现从小众派 No JS Club, No CSS Club 到极限派到 1MB Club, 512KB Club, 250KB Club1KB Club 应有尽有,越到后面越抽象,我还没有这样的魄力,以后应该也不会。

如何去掉

首先要打开网站的所有模版页面和 JS 文件,包括 HTML 中 onclick, onload 等事件在内,找出哪些地方调用了 JS,一个一个处理。

文章目录滚动高亮

文章目录跟随页面滚动高亮显示对应标题

这个功能的实现很简单,使用 IntersectionObserver 监听页面元素,页面滚动到相应标题时绑定 CSS 样式就行了,我在「从 Typecho 迁移到 Hugo」一文中有详细介绍。但目前还想不到办法如何不依赖 JS 实现,所以该特性直接丢弃。

不过滚动一定距离后再显示目录这个效果还可以抢救一下。Chrome 去年开始支持了一个新特性:CSS 滚动驱动动画,可以实现很多原本必须依赖 JS 才能实现的滚动效果,操作起来也很简单:

.post-toc {
    ...
    animation: toc-reveal 1s linear forwards;
    animation-timeline: scroll();
    animation-range: entry 0 100px;
}
@keyframes toc-reveal {
    from { opacity: 0 }
    to { opacity: 1 }
}

可惜的是目前只有 Chrome (115+) 支持,Firefox 和 Safari 就更不用提了。如果你用的是较新版本的 Chrome 浏览器,可以在本文右侧看到效果。

文章图片加载

文章图片加载

目前文章页面用了 JS 加载图片和错误处理,成功加载后去掉动画,失败则显示错误信息;监听页面滚动,使图片上浮渐变显示。图片错误处理只能放弃,但显示动画依然可以用上面滚动驱动动画方法实现:

.img-wrapper {
    view-timeline-name: --revealing-image;
    view-timeline-axis: block;
    animation: linear img-reveal both;
    animation-timeline: --revealing-image;
    animation-range: entry 20% cover 50%;
}
@keyframes img-reveal {
    from {
        opacity: 0;
        transform: translateY(25%);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

缺点是动画速度和页面滚动的速度相关,并且不能删除动画,向上回滚时图片又会淡出,不过效果也不错。

分页异步加载

分页异步加载

类似 Ajax 局部刷新,也是常说的瀑布流加载的原理,使用 FetchAPI 获取下一页内容后通过 innerHTML 插入到当前页面页尾,同时还有懒加载模糊加载图片。除浏览器支持图片设置 loading=lazy 懒加载外,其余功能必须依赖 JS 实现,所以只能回归简单的分页导航。

搜索功能

网站搜索功能

搜索功能依靠表单提交,通过 FetchAPI 获取包含网站所有文章关键信息的 JSON 文件(JSON 文件通过 Hugo 生成),然后处理数据并高亮关键词展示。该功能必须依赖 JS 实现,所以直接去掉。

工具页面

网站小工具页面
网站小工具页面。
UUID 生成器
UUID 生成工具。

除了 WHOIS 查询外,用 JS 写的几个小工具,我平时用的频率还挺高。代码都很简单,而且网上一大堆类似的,只是出于安全性考虑选择了自己重复造轮子。这个页面也只能删除,不过可以直接把 JS 和 CSS 文件提取出来本地使用,或者以后放到 Cloudflare Pages 上,问题不大。

网站访问统计

本站月度访客数据
本站 2024 年 3 月份访问数据统计。

目前使用了自建的 Plausible 来统计网站访问数据,如上图,最近一段时间每日访客数量为 100 左右。一般来说,如果不插入 JS,网站流量统计主要有以下三种方式:

  • 通过网络服务商。如 CDN 服务商一般会自带请求统计,缺点是数据偏大,因为包含了大量的 Bot 请求。
  • 通过日志文件。读取分析 Web Server 的访问日志,如 GoAccess 工具,缺点是数据非实时,并且模拟正常请求的恶意 Bot 数据很难过滤。
  • 通过请求文件。不少统计工具支持 <noscript> 的方式,其原理就是网页中插入一个像素文件,通过该文件的请求数据分析,缺点是无法获取 referrer 来源(其实也有办法实现:How Bear does analytics with CSS )以及过滤 Bot 请求。

一开始时计划换成第三种方式,可惜的是 Plausible 短期内不会支持非 JS 插入统计,好在以前使用过的 Shynet, GoatCounter 等工具都支持,最后决定使用 GoatCounter。配置得差不多了的时候又突然改变了想法,添加访问统计后除了每天纠结那点数据外也没什么用,没必要让自己流量焦虑,干脆直接去掉。

因此我的网站现在是个黑盒子,不知道流量多少,不知道引荐来源(其实 Google 和 Bing 的站长后台中可以看到一部分),但说不定过几年我又加上的时候会有意想不到的惊喜呢。

其它

此外还有不少功能都是直接去掉或者改写为静态展示,不再细说。这里简单的分享下如何用纯 CSS 代码实现开关按钮。以下拉菜单举例,你可以添加一个使用 checkbox 属性的 <input> 标签配合 CSS 的选择器来实现,例如有这样一个 HTML 结构:

<input type="checkbox" id="toggle" style="display: none">
<label for="toggle">
    <button>Switch<button>
</label>
<div class="menu-box">...</div>

编辑 CSS,默认不显示 menu-box 下拉菜单,设置 <label> 的伪类选择器 :checked,配合兄弟选择器 + 定位到下拉菜单,设置为显示:

.menu-box {
    display: none
}
#toggle:checked + .menu-box {
    display: block
}

这样你就可以通过点击 label 来改变选中状态从而控制显示开关。虽然这种实现不是什么新花样,属于很老的 tricks,但效果和用 JS 实现几乎没有区别,兼容性也不错,比较实用。

去掉之后

其实本站所有的 JS 文件加起来也才 12.4 KB,压缩后网络传输为 4.2 KB,我想没有人会抱怨多消耗这 4.2 KB 额外流量。同时每一行代码都花了很多功夫和时间,删除的时候倒挺解压。

去掉之后自然是不如原来好,失去了静态网站仅剩的一点灵气,好在多了一丝清爽的错觉。要说还剩什么的话,仿佛回到了早期的 Web 1.0 时代,仅仅作为一个「只读」网站而存在。

对了,我顺便将本站 RSS 从摘要输出改为了全文输出。不过这里的阅读体验应该也过得去,也希望你能偶尔打开网页来这里逛一逛 <(´= ω =`)>

EOF

如果你觉得这篇文章还不错,可以考虑支持作者

当我玩博客时我在玩什么

就像一场不会停止的接力赛,博主们或早或晚会写一篇关于自己为何写博客的文章。至于我的回答,已在「本站的历史」中有所提到,目前也作不了更多补充,只怕到时候连自己也说服不了了,于是快速地将接力棒交替出去。不过在这里,我想分享一下关于「玩博客」的故事。

2010 年暑假,还是初中生的我经常一个人顶着午后的烈日,兴致高昂地到附近的一个电脑城探险。当然不是去买东西,那时我连一根老冰棍都舍不得买,辛苦攒下来的零花钱要留着做更重要的事。

还记得每次踏入电脑城的大门,总是有一阵冷气猛拍在被汗水浸透的衣服上,就像冬天里洗澡脱得只剩裤衩时的那道妖风。

「小兄弟,配电脑哇?」

「对,你这有 AMD 的 HD 4770 显卡吗?512M 的多少钱?」

「512M 的性能不行,我这里有 1G 的,来,进来坐」

接下来的几十分钟里我们谈笑风生,具体谈了什么不记得,只记得那时很喜欢在配置单上纸上谈兵的感觉,听他们讲各种「完爆」的故事。虽然那时的我已经是显卡吧十一级「大神」,哪里有坑基本都知道,他们觉得我是小傻子,我觉得他们是大傻子。直到心满意足后,才留下一句「我回去问问我妈再说」,直奔隔壁黑网吧打开心爱的穿越火线,fire in the hole!

然而快乐总是短暂,关键时刻耳机里总会传来叮的一声,「您的余额已用完,请及时充值!」自然,对这个穷小孩来说兜里已经没有子弹可以让他坚守战场,即使作为己方大腿,战绩豪华,但果断 Alt + F4!光速打开 IE 浏览器,输入 hao123.com,游戏分栏里找到死神VS火影,一阵乱敲后赶紧跑回家吃晚饭,不然屁股要遭遇。

如今,电脑城和网吧不再去了,穿越火线不再玩了,hao123 作为一代流氓头子也不再可能出现在我的浏览器中。

初中和高中时在 QQ 空间里写了不少日志。初中语文老师以前将我的作文归类为朦胧派,爱当范文读,同学们也爱看,我自然更加努力的写得自己都不知所云。

今天打开了几年都没打开过的 QQ 空间,意外的发现私密日志里还剩几篇以前没删掉的。这哪是什么朦胧派,简直就是井里快渴死的青蛙吐出的泡泡。要说尴尬,这种情况应该每个人都会,尴尬之外,还看到了许多十多年前就明白的道理和错误如今依旧在犯,那当时写下来的意义是什么呢?鞭策?自我感动?

又一遍读完这些日志后,内心却变得平静,也不会再有因为尴尬就想去删除的想法。我发现,对于很多遗忘的故事,这是我唯一能找到的线索了。那些一个个已经忘掉的片段重新浮现出来,睡一觉之后也许会再次忘记,但至少此时此刻… 干!高中老师没收的诺基亚功能机还没还我!

和不少人一样,刚接触网站那两年,总是乐此不疲的尝试各种「文具」,网站样式改了又改,后端架构换了又换,隔几天就囤一个学费域名,看到「传家宝」服务器就忍不住去买,买了后吃灰还舍不得取消。

我很高兴自己去折腾了。博客从 WordPress 到 Typecho 再到 hugo 平台换了一圈,每次都乐此不疲花上几个通宵重新写一遍主题;关于域名和服务器运维,该会的不该会的都会一点,网盘、图床等各种自建服务玩了一圈,还写了一堆没用的脚本和网页工具。要是把这些时间用在搬砖上,我是不是已经 KFC 自由了呢?不过其实很高兴学到了很多东西,让自己离计算机和互联网的世界更近了一点。

更高兴现在自己不再折腾了。玩博客时总有种快感,就像有博友将博客比喻为新型电子游戏一样,十分贴切。游戏有好有坏,对于管不住自己的人来说,自然弊大于利,好在再喜欢游戏人的也会有「电子阳痿」的那天,去年开始我终于不再沉迷这个游戏。即使现在的 Hugo 用得并不十分舒坦,但也不想再换博客平台,也不想再大刀阔斧的修改主题样式。生活不就是到处凑合嘛,乔布斯所追求的科技与人文的结合,在我这里科技似乎有一点,人文还差得很远。

玩博客也好,被博客玩也罢,都是十分短暂且意义不大的事。博客之于我,无非是确立自我,作为短暂存在的又一个证明;我之于博客,无非是碰巧出现,某个随机敲下 0 和 1 的碳基生命。好在除了考虑这些无聊的意义之外,还可以跳舞。

“跳舞,”羊男说,“只要音乐在响,就尽管跳下去。明白我的话?跳舞,不停地跳舞。不要考虑为什么跳,不要考虑意义不意义,意义那玩意儿本来就是没有的,要是考虑这个,脚步势必停下来。一旦停下来,我就再也爱莫能助了,并且连接你的线索也将全部消失,永远消失。那一来,你就只能在这里生存,只能不由自主地陷进这边的世界。因此不能停住脚步,不管你如何觉得滑稽好笑,也不能半途而废,务必咬紧牙关踩着舞点跳下去。跳着跳着,原先坚固的东西便会一点点酥软,有的东西还没有完全不可救药。能用的全部用上去,全力以赴,不足为惧的。你的确很疲劳,筋疲力竭,惶惶不可终日。谁都有这种时候,觉得一切都错得不可收拾,以致停下脚步。” 我抬起眼睛,再次凝视墙上的暗影。 “但只有跳下去,”羊男继续道,“而且要跳得出类拔萃,跳得大家心悦诚服。这样,我才有可能助你一臂之力。总之一定要跳要舞,只要音乐没停。” 要跳要舞,只要音乐没停。

——《舞!舞!舞!》村上春树

或者也可以说,当我玩博客时我在「玩自己」。因为博客的第一个访问者和第一个读者永远是你自己,你做的一切都是建立在对自己负责的基础上,无论意义如何,都得由你自己去接受。当然,我希望每个人都能从中得到好的东西。

最后,对我来说博客还不算是个无聊到不想玩的玩意儿,所以在那时之前,要玩要写,只要音乐没停。

如果你觉得这篇文章还不错,可以考虑支持作者

为什么我的博客没有友链页面

我的博客似乎一直都缺少一个在多数中文博客上不谋而合的页面——友情链接(Link exchange)。说实话,我挺喜欢友链这个东西,通过别人的一个个友链发现了很多有意思的博客。也想过很多次要不要给自己的博客加个友链页面,只不过最后结论都是:以后再说吧。

为什么我们需要友链

友链的作用不必多说,可以是为了增加收录权重、带来流量,也可以是方便交流互动、让博客之间不在孤立,还可以是单纯的为了分享、推荐优秀的博客。

鲁迅说过,创作是有社会性的。就独立博客来说,流量来源一直都是个问题。曾经丰富的博客和网站选择在不可抗力和少数主导平台的强大压力下被压缩,各家都在关起门来打造垂直平台,加上 AI 和 AI 类搜索引擎的出现,互联网的入口变得越来越少,这意味着独立博客被发现的机会也越来越少。

又对于中文独立博客来说,不少人是不愿意进行 ICP 备案的,由此这些博客尤其是新博客很难被国内搜索引擎收录,只能在「玛利亚之墙」外颠沛流离,搜索来源大部分要靠谷歌,天生输在了起跑线。好内容没人看最后也只会成为内容农场和 AI 的养料,此时友链带来的流量虽杯水车薪,也弥足珍贵。

从 SEO 考虑

于是不少人在建站之初就添加了友链页面并迫不及待的加上很多链接,原因很简单,对于新建立的网站,站长总会面临这样一个问题:

「怎样让更多的人访问我的网站?」

然后不可避免的接触到 SEO,搜到的结果千篇一律的会推荐你通过和别人交换链接,甚至购买外链(backlinks)来提高网站排名权重。我不熟悉 SEO,有没有效不知道,但是谷歌很早就在站长指南中指出过度的链接交换会视为违反 Spam policies

Google 站长指南中关于 Link Spam 的说明
Google 站长指南中关于 Link spam 的说明。

不过多少应该还是有用的,毕竟 SEO 专家们比谷歌更了解谷歌,不然中文搜索也不会被玩坏。只是如果你抱着 SEO 的目的交换友链的话建议适量而行,同时多关注下对方网站上你的链接 <a> 标签中的 ref 属性,避免单方面传递权重。

从互动考虑

如果你获取流量不是为了挂广告赚钱,那多半是为了让更多的人能发现自己,一起交流互动。这一点很好,孤岛式的独立博客需要一定的反馈来让博主保持创作动力,不少人刚开始时都是充满干劲的,但洋洋洒洒写了一堆,却长期得不到反馈,于是得出结论:博客已死。觉得反正没人看,就懒得再更新了。

因此友链也颇有「挽尊」之意,这是一个很有效的互动渠道。类似的还有评论和留言功能,就像十几年前的 QQ 空间互踩一样,今天我去你博客看看有没有新文章可以吐槽的,明天你来我这里锐评几句。长久以来自然会认识许多志同道合的朋友,互相鼓励、坚持更新,甚至线下面基、一同快活。

就算你没有太多的考虑,只想分享下自己朋友或者感兴趣的博客,又或是只当个书签使用,无论如何,友链都是个好东西。

为什么我不添加友链页面

抛开那些功成名就的博客不谈,也有不少不知名博客是没有友链页面的,想必每个人都有自己的考虑。其实我也很少在非中文博客中见到友链页面,友链可以说是中文博客圈的特色,当然前面说了,这种特色是好的。

我不添加友链页面的原因,一定是主观的,有不少槽点的。

不需要太多更新的动力。以前的文章提到过,我不喜欢用社交软件,自然不需要友链页面带来的社交属性。如你所见,这个博客甚至没有评论功能,写文章只是因为想写而已,长期没有反馈大概也会坚持下去。

不希望友链成为交易。你先加上我的链接,然后我加上你的,等哪天发现你的网站打不开了或者你把我的链接删掉了,我也删掉你,即使从结果上来看在「互助」期间双方都是受益的,但我不喜欢这种方式。

很难定义友链的标准。一旦有了友链页面,想必会有热心博友看得起我的小破站来申请友链。其中总会有不错的博客,会因为我对内容方向不感兴趣、背景太花、排版不好看等各种钻牛角尖的原因不喜欢,就像肯定有人不喜欢我的博客一样。但我始终认为每一个坚持原创的博客都是平等的,筛选可以,拒绝也合理,但很麻烦,而且伤感情。

当然可以直接写明不接受友链申请,只是此时又有更简单的友链方式。我有一个专门的书签文件夹存放着关注的博客,想阅读时可以直接打开页面。同时也通过 RSS 订阅了一些博客,想来这样阅读的频率也会更高,不必先到自己的博客中转一下。

不过最大的原因还是个人的固执,我自然也是想要友链带来的流量的。但总体而言,还是希望自己的博客尽可能简单,一个我只管胡扯,读者只管消遣的地方。有人碰巧点开我的文章,有帮助是好事,没帮助就直接关闭,想进一步探讨或吐槽就邮件交流,满足如此基本的功能即可。

所以,每当想到这些理由时,足以让我放弃思考,「留到以后再说了」。那以后会添加友链页面吗?可能会,但一定是我单向分享的。

题外话 1:除了友链外,我们还有什么

就友链的引流功能来说,RSS 一直是个好东西。其实从 RSS 聚合到博客聚合,随便搜下关键词就会发现从来都不缺人做,但一年、两年后还能打开的并不多,因为没有稳定商业模式,用爱发电总会有尽头。

RSS 聚合和博客聚合也有其它问题。RSS 是主动订阅,需要解决先有鸡还是先有蛋的问题,你不先发现感兴趣的博客自然无法订阅;而博客聚合大都靠平台管理者筛选,博客类型和质量取决于管理者的口味,其中有不少是单链接式的博客聚合平台,只有一个博客名和地址,有人能在成百上千的博客中点到你的网站还是需要很大的缘分。这点上目前发现积薪做得不错,收录文章列表除了标题外,还有 AI 生产的总结和自动分类,点击文章直接跳转源站,博主的网站也能得到展示。

我一直喜欢 Hacker News 的思路。比如有一个公共平台会收录一些博客,你也可以主动分享,大家在文章列表发现并点击跳转到你的博客阅读文章,然后回到该平台留言讨论。这样既保护了你的私域流量,又有一个公共的平台可以分享探讨,还解决了部分博客没有评论功能的问题。同时这个平台也能有足够的聚合流量维持运营。

这个思路肯定有人想到过,也有人尝试过,对症下药时应该会面临不少问题。那如果这个平台是去中心化的呢?每个博主的吃灰服务器都可以成为节点参与,好比一个更简单,只能分享博客文章,而且评论所有人可见的 Mastodon 社区…

题外话 2:博客的未来是去中心化吗

前段时间看完美剧《硅谷》后我一直在思考,到底什么样的互联网才是 The Internet We Deserve ?

Silicon Valley (TV series)
Silicon Valley (TV series).

这里需要简单聊聊去中心化。World Wide Web 的创建者伯纳斯·李(Tim Berners-Lee)在 1989 年写下「信息管理建议书」时就概述了对 Web 的愿景,能以去中心化方式访问网络,并允许系统在不需要任何中央控制或协调的情况下连接在一起。显然 Web 2.0 的中心化与他最初的想法背道而驰。2016 年,他首次提到了关于 Web 3.0 的想法:

“People keep asking what Web 3.0 is,” Berners-Lee said. “I think maybe when you’ve got an overlay of scalable vector graphics - everything rippling and folding and looking misty - and access to a semantic Web integrated across a huge space of data, you’ll have access to an unbelievable data resource.”

###

人们不停地询问 Web 3.0 是什么。我认为当 SVG 在 Web 2.0 的基础上大面积使用——所有东西都起波纹、被折叠并且看起来没有棱角——以及一整张语义网涵盖著大量的资料,你就可以存取这难以置信的资料资源。

一年后,他在麻省理工学院启动了一个名为 Solid 的去中心化项目 ,旨在从根本上改变 Web 应用程序的中心化趋势,将用户的个人数据从大型科技公司的控制中脱离出来。只是直到今天,发展得仍不理想。

至于大家近几年经常听到的 Web3,是由以太坊创始人之一加文·伍德(Gavin Wood)提出,我个人并不喜欢。区块链是可靠的技术,在未来的信息技术行业中很重要,但也有缺点。如今网络上到处都有神秘的力量引导,区块链就是加密货币,加密货币就是 Web3,Web3 就是未来。但区块链不等于加密货币,Web3 也不一定是未来。我认为未来的互联网可以是去中心化的,但不应该是基于区块链的去中心化,更不会和加密货币捆绑,应该是更快、更基础的东西。最重要的是,目前 Web3 的主要市场仍是由少数主导地位的公司占领,这很不去中心化。

不过对于个人博客,尝鲜 Web3 也未必不可,已经可以解决不少问题。你的内容属于你自己,永远也不会消失,不用担心攻击,不用担心审查,甚至可以解决普通人如何通过内容创作获利的问题。当然问题也有,内容无法删除,每次修改记录都会保留,访问速度较慢,流量主要靠平台内部,缺少外部来源等。如果你感兴趣,可以了解一些不以加密金融和空投奖励为主要卖点的平台,例如 MattersxLogPlanet 等项目。

目前来看,我认为即使去中心化的博客是未来的趋势,也解决不了个人博客的流量焦虑问题。趋势的终点是半个理想国,另一半是未知的黑暗,就和大同社会到底能不能实现一样,我们都知道答案。独立博客的圈子本来就没有中心,这些年个人博客的衰落除了不可抗力因素外,更重要的原因是如今的吃瓜群众喜欢中心化的东西,动动手指就有永远刷不完的「感兴趣内容」,比自己大海捞针强多了。比如去中心化的 Mastodon,理念很好,但大多数人还是选择了使用官方实例或者用户足够多的大型实例;又比如我要解决一个编程问题,Google 搜索和 ChatGPT 我肯定优先考虑后者。

所以,去中心化的博客面临的问题也和现在一样,互联网留给个人博客的流量越来越少的原因是人们更喜欢并享受中心化的便捷,拥有基础设施或者足够多资源的巨头也总会想办法吸引大量有便利性需求的用户,超过 51%,成为新的中心。但这也不是说去中心化没用,去中心化的主要意义还是解决归属权和隐私问题,虽然不能将中心化的流量还给个人,也是值得期待的,有了选择的权利,也意味着更多的可能。

说到底,不管是现在还是未来,个人博客最好的出路还是要靠博主创作出更多、更好的内容。共勉。

如果你觉得这篇文章还不错,可以考虑支持作者

小熊猫与大熊猫 (HDR 照片测试)

最近对小熊猫(Red Panda, a.k.a. Firefox)产生了浓厚的兴趣,于是春节期间去了趟成都熊猫基地,近距离观察下憨憨的小熊猫。

因为是假期,基地里人非常多,只要有 🐼 出现的地方都寸步难行,更难受的是 95% 的大/小熊猫都在睡觉… 碰到一只会动的都不容易,从南大门到西大门走了四个多小时,一张大熊猫的正脸都没拍到。好在碰见了两只「活泼」的小熊猫,也算是完成任务,其余的权当散步了。

想起去年一位博友和我分享过 HDR 照片,于是这里也尝试分享几张 HDR 照片看看效果。但目前 HDR 照片普及率还很低,要想在网页中看到 HDR 效果,除了需要你的显示设备支持外,还需要浏览器支持。目前只有基于 Chromium 的浏览器可以正确显示 AVIF 格式的 HDR 照片,Firefox 和 Safari 浏览器上仅支持 SDR。同时由于 Apple 限制了 iOS 和 iPadOS 上的浏览器只能使用 WebKit 内核,因此 iOS/iPad OS Chrome 上目前也无法完美显示。

hdr-compatibility
Chrome / Firefox / Safari 浏览器 HDR 图片兼容性对比。

* 以下照片使用 iPhone 15 Pro 三倍长焦镜头拍摄,仅使用 Pixelmator Pro 做裁剪和压缩处理,照片版权归 ATP 所有。

小熊猫图片-1
小熊猫图片-2
小熊猫图片-3

当然,躺平的大熊猫也得来两张。

大熊猫图片-1
大熊猫图片-2

iPhone 15 Pro 的长焦镜头的细节表现没想象中好,毕竟只有 1200 万像素,拍摄时也忘了试试 ProRAW 格式,不过也算差强人意,简单拍拍够用了。

如果你觉得这篇文章还不错,可以考虑支持作者

是时候为网站开启 HTTP/3 支持了吗

关于 HTTP/3,很久以前了解过,被多数文章和网友的一句国内运营商 UDP QoS 劝退,只留下了个「鸡肋」的印象。前段时间看了 Robin Marx 关于 HTTP/3 的系列文章,深入浅出的介绍了 HTTP/3 协议的历史和由来,性能优势和存在的问题,读完后受益匪浅,让我开始重新看待 HTTP/3。

互联网工程任务组 (IETF) 于 2022 年 6 月正式发布了 HTTP/3 。根据 W3Techs 统计,截至 2024 年 1 月,互联网上已经有 28.8% 的网站使用了 HTTP/3,那么,是时候为自己的网站开启 HTTP/3 支持了吗?

HTTP/3 简单介绍

首先,HTTP/3 的出现是为了解决一些历史问题,HTTP/1.1HTTP/2 都是基于 TCP 协议,TCP 作为互联网上使用和部署最广泛的协议之一,最早标准化于 1981 年发布的 RFC 793,如今已经显示出了一些瓶颈,QUIC(Quick UDP Internet Connections,读作 “quick”)协议成为它的预期替代品。

QUIC 协议于 2012 年由 Google 设计,并由 IETF 于 2021 年公布了 RFC 9000 标准。QUIC 是一个基于用户数据报协议 (UDP) 的传输协议,与 TLS 1.3 深度集成,保留 TCP 大多数优点的同时,带来了更高的安全性、更低的延迟以及更好的拥塞控制等特性。QUIC 之所以基于 UDP 也不是出于性能考虑,理想情况下应该是一个全新的独立协议,只是为了方便在现有的网络上部署。

但大家最初发现在 HTTP/2 上运行 QUIC 效率很低,有些特性很难实现,在几个关键地方进行修改后,推出了 HTTP/2-over-QUIC,出于多种原因,后命名为了 HTTP/3。也就是说,是为了使用 QUIC,才有了所谓的 HTTP/3。

这里需要泼一点冷水,HTTP/3 本身是对 HTTP/2 的修改,可能会让你的网上冲浪体验更好,但还不至于起飞,如果你的网络本来就很快,感知可能并不明显。我们常看到的会话恢复和 0-RTT 也不是 QUIC 的特定功能,实际上是 TLS 的功能,可以从下图中看到,即使开启了 0-RTT,QUIC (f) 仍然只比 TCP + TLS 1.3 (e) 快一次往返,所谓的快三次是和 TCP + TLS 1.2 (a) 相比,同时开启 0-RTT 还需要考虑更多的安全问题。但对于网速较慢且不太稳定的用户来说提升仍是很大的。

不同协议握手流程对比
不同协议握手流程对比。

还有一个需要考虑的现实是,UDP 通常被用于 DoS 攻击,因此许多企业网络和防火墙会选择禁止 UDP,QUIC 自然也无法使用。虽然 Google, Cloudflare, Amazon, Netflix 等网站早已经全面支持 HTTP/3,环大陆互联网上风生水起,但国内不少地区的运营商还在研究怎么 QoS UDP,国内用户目前有可能会得到更慢的体验。

一句话总结就是 HTTP/3 比 HTTP/2 更快更强,但普及还需要相当的时间,目前 HTTP/2 基本够用,不支持 HTTP/3 也无伤大雅。但 QUIC 作为下一代传输协议的趋势是肯定的,就我自己以文字为主的博客来说,先开为敬。

网站开启 HTTP/3

配置 Nginx 开启 HTTP/3 支持

最简单开启 HTTP/3 的方式就是使用免费的 Cloudflare CDN,会默认开启。如果不使用 CDN,HTTP/3 的普及很大程度上取决于 Nginx,Caddy,Apache 等 Web 服务器的支持程度。这里主要介绍 Nginx 开启 HTTP/3 支持的方法。

* 写本文时,Nginx 最新的稳定版本为 1.24.0,主线版本为 1.25.3。

Nginx 从 1.25.0 版本开始支持 QUIC 和 HTTP/3 协议 ,但目前还不能开启 early data(即 0-RTT,允许客户端在 TLS 握手完成之前发送应用程序数据),可能至少要等到明年 OpenSSL 发布支持 QUIC 的版本后才行,具体说明可以见文章:QUIC+HTTP/3 Support for OpenSSL with NGINX

安装 Nginx

如果你不需要开启 0-RTT,可以通过 Ngxin 官方库安装主线版本,这里以 Debian 系统 root 用户举例,其他 Linux 系统可以参考官方文档

# Install the prerequisites
apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring

# Import the official nginx signing key
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

# Set up the apt repository for mainline nginx packages
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/mainline/debian `lsb_release -cs` nginx" \
    | tee /etc/apt/sources.list.d/nginx.list

# Install nginx
apt update
apt install nginx=1.25.3

如果要想开启 0-RTT 可以选择自行编译,Nginx 建议使用一个支持 QUIC 的 SSL 库,例如 BoringSSLLibreSSL 或者 QuicTLS,三者最初都是 fork 自 OpenSSL,目前分别由 Google、OpenBSD、Akamai & Microsoft 维护。

我测试了使用 BoringSSL和 LibreSSL 构建都可以成功开启 0-RTT。需要注意的是 BoringSSL 虽然综合性能最好,但目前不支持 ssl_stapling,也就是 OCSP Stapling,需要曲线救国实现。同时还有个神奇的事,使用同样的 Nginx 配置文件和系统在三台服务器上测试脚本,编译 LibreSSL 后发现在其中两台服务器上能正常运行和访问,在另一台服务器上能正常运行 Nginx,但一访问就会报无限进程退出错误 worker process xxxxxxx exited on signal 11,排查通宵未果… 第二天又花了一下午排查才发现是权限问题,Nginx 配置中使用 user www-data 就会报错,使用 root 才能正常访问网站。然而三台服务器环境都一样,且所有 Nginx 目录包括证书文件和网站文件的权限也都是一样的,另两台服务器编译 BoringSSL 版本也不需要指定 Nginx 用户为 root,能正常运行和访问,就很迷惑,但已不想再深究。

这里不再详细介绍如何进行编译,如有需要可以前往 Github 参考我的 Nginx 编译安装脚本(不建议直接运行)。

配置 Nginx

一个简单的配置示例如下:

server {
    listen 80;
    listen [::]:80;
    server_name atpx.com;
    location / {
        add_header alt-svc 'h3=":443"; ma=86400';
        return 301 https://atpx.com$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    http2 on;

    server_name atpx.com;

    root /var/www/atpx.com;
    index index.html;

    ssl_certificate /path/to/ssl/atpx.com.pem;
    ssl_certificate_key /path/to/ssl/atpx.com.key;

    ssl_protocols TLSv1.3;
    ssl_prefer_server_ciphers off;
    
    # Enabling 0-RTT.
    # ssl_early_data on;
    ssl_session_timeout 1h;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
  
    # Informs the client that HTTP/3 is available.
    add_header alt-svc 'h3=":443"; ma=86400';
}

更多具体配置你可以使用 Mozilla 的 SSL 配置生成工具。然后 Nginx 日志格式也可以在 http 段中修改:

# Log http3 requests
log_format  quic  '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent" "$http3"';
access_log /var/log/nginx/access.log quic;

需要注意的是:

  • 从 Nginx 1.25.0 开始,不再支持在 listen 中使用 http3 参数,需要修改为 quic
  • 从 Nginx 1.25.1 开始,不再支持在 listen 中使用 http2 参数,需要单独一行 http2 on
  • listen 中不能同时使用 sslquic 参数,须分为两行;
  • listen 段中的 reuseport 复用端口参数在所有 server 块里只允许出现一次;
  • 开启 0-RTT 可能会遭受重放攻击。

* 你可能会在配置文件中发现华点,Alt-Svc 标头要建立连接后才能获取,那首次连接呢?目前浏览器为了安全起见,首次连接仍会通过 HTTP2 或 HTTP/1.1 进行,发现 Alt-Svc 标头后才会尝试在后面的连接中使用 HTTP/3,这也是为什么你测试时会发现需要刷新一次网页后才能使用 HTTP3。解决办法是 DNS 中添加 SVCB/HTTPS 记录。

完成配置后需要重启 Nginx 并记得在防火墙放行 443 UDP。

最后,你可以通过 https://quic.nginx.org/ 检查你的浏览器是否支持 HTTP/3 连接(主流浏览器 Chrome 91+,Firefox 89+,Edge 90+ 等都已经默认开启 );通过 https://http3check.net/ 检查你的网站是否成功开启 HTTP/3 支持。

如果你觉得这篇文章还不错,可以考虑支持作者

使用 AdGuard Home 搭建自用 DoH 服务

众所周知,AdGuard Home 是一款很不错的自建 DNS 服务器软件,除了广告拦截之外的功能都挺好用。自己搭建也很简单,参照官方文档几行命令就能搞定。这几年我也一直在使用家里树莓派上搭建的 AdGuard Home 作为局域网 DNS 服务器,体验很不错。这里要分享的是如何通过 Docker Compose 部署 AdGuard Home 并搭建一个私有自用的 DoH 服务,以及如何使用 AdGuard Home 实现 DNS 分流解析。

搭建 AdGuard Home

国内搭建公共 DNS 服务需要申请无法申请的牌照,用国外服务器搭建也会被抢答或者阻断,53(UDP)和 853(DoT)都是重点关注对象,所以目前只推荐使用 443(DoH 或 DoQ)的方式连接。

Docker 的安装使用可以参考前面的文章:Debian 系统安装 Docker 教程

在合适的路径下(假设为 /home/adguardhome)新建一个 docker-compose.yaml 文件,我们只需要 DoH 服务,因此可以只映射容器内的 443 端口出来,3000 端口是 Web 管理页面,可以根据自己需求修改。此外,这里可以选择添加一个 Volume 用来映射域名证书文件,避免后面手动粘贴的麻烦。

免费的 SSL 证书申请可以参考前面的文章:使用 acme.sh 自动签发和更新证书

同时,当你全部配置好正常运行后会发现控制面板首页的统计数据中来源 IP 全是 Docker 的 172 内网 IP 段,这里需要在配置文件中设置 trusted_proxies 参数,指定受信任的来源 IP 地址列表,AdGuard Home 才会获取代理标头(如 X-Real-IP,X-Forwarded-For 等)中的客户端真实 IP 地址。这里也可以先设置 Docker 容器的 IP 地址段,方便后面在配置文件中添加。

综上,你的 docker-compose.yaml 文件可以写成下面这样:

version: "3"

services:
  adguardhome:
    image: adguard/adguardhome:latest
    container_name: adguardhome
    restart: unless-stopped
    ports:
      - 127.0.0.1:8443:443/tcp
      - 127.0.0.1:3000:3000/tcp
    volumes:
      - /home/adguardhome/work:/opt/adguardhome/work
      - /home/adguardhome/conf:/opt/adguardhome/conf
      - /path/to/ssl:/opt/adguardhome/cert
    networks:
      agh_net:
        ipv4_address: 172.18.1.2

networks:
  agh_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.18.1.0/24

使用 docker compose up 命令拉取镜像生成相关文件后再 ctrl + c 停止,修改 conf/AdGuardHome.yaml 配置文件,找到 trusted_proxies 参数,后面添加一行 172.18.1.0/24:

trusted_proxies:
  - 127.0.0.0/8
  - ::1/128
  - 172.18.1.0/24

然后 docker compose up -d 启动容器服务。

配置 Nginx 和 DoH 服务

在公网搭建 AdGuard Home 最怕的是主动探测和各种扫描,地址暴露后可能会变成公共 DNS 或者被各种攻击,导致你的域名或服务器被玩坏。你可以自行探索 Nginx 鉴权的方法 ,同时 AdGuard Home 本身也支持设置白名单客户端,两种方式相结合可以提高安全性,降低暴露风险。

配置 Nginx

你应该将 AdGuard Home 服务写在 443 server 块中,这里略去其它相关配置,只贴出关键 location 块:

location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://127.0.0.1:3000/;
}
    
location /dns-query {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass https://127.0.0.1:8443/dns-query;
}

这里请将 location /dns-query 路径修改为其它随机字符串,防止主动探测,同时也可以将控制面板路径修改为复杂地址。

你还可以顺便在这里限制来源 IP,只允许你常用的 IP 访问,一般可以设置为你所在地区的公网 IP 段,这种限制方式的缺点是不够灵活。最后你的 Nginx 配置可以类似这样:

location /aghome/ {
    allow 192.168.1.1;
    allow 172.16.0.0/16;
    deny all;
    proxy_cookie_path / /aghome/;
    proxy_redirect / /aghome/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://127.0.0.1:3000/;
}
    
location /random-string {
    allow 192.168.1.1;
    allow 172.16.0.0/16;
    deny all;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass https://127.0.0.1:8443/dns-query;
}

重载 Nginx 后打开你的域名地址进行安装,只需将第一步中的管理页面地址(Admin Web Interface)端口修改为和上面对应的 3000,其余可以保持默认一直下一步直到完成安装。

配置 DoH 服务

登陆控制面板后台,进入 设置-加密设置 页面,☑️ 启用加密,服务器名称中输入域名,HTTPS 端口填写 443,DNS-over-TLS 端口和 DNS-over-QUIC 端口如果不需要用到留空即可。下面的证书路径填写前面 docker-compose.yaml 中对应的文件路径。

adguard-home-doh

限制 ClientID

AdGuard Home 0.105.0 及之后版本支持为部分加密查询方式设置 ClientID,你可以限制白名单内 ClientID 才能进行查询。对于 DoH 查询,只需要在地址最后加上一段字符串即可,这里以随机 UUID(7017d85e-99d3-46df-bcbc-77a0a33c9cee)举例。

为了方便查看日志,你可以先设置一个持久客户端名称,在 设置-客户端 中添加客户端:

adguard-home-clientid

设置-DNS 设置-访问设置-允许的客户端 中输入该 UUID:

adguard-home-whitelist

完成以上设置后,你可以尝试通过 https://xxx.example.org/random-string/7017d85e-99d3-46df-bcbc-77a0a33c9cee 链接进行 DNS 查询了,并且基本可以确保只有你自己才能使用。

题外话:自建 DNS 服务器在代理过程中的作用

这里说点题外话,如果你是为了搭建无污染的 DNS 在代理工具中使用,效果可能并不明显。在基于 IP 规则分流的代理过程中,DNS 的作用仅仅是在客户端发起请求的第一步中解析域名获得 IP 地址,然后用该 IP 地址来匹配规则,即使得到的是错误的 IP,只要最终命中代理规则,代理工具依然会将域名发送到远程服务器上再次解析并建立连接,此时与你客户端解析获得的 IP 没有关系,所以不会对访问造成影响。

而且这一作用也被今天主流使用的 fake-ip 所弱化,fake-ip 跳过了上面所述的第一步,命中规则的域名不会在本地进行 DNS 解析,代理工具会直接返回一个内网 IP 地址并建立映射关系,同时在远程服务器上解析并连接后通过该内网 IP 地址进行代理。fake-ip 的优势很明显,加快了响应速度,同时也不会造成 DNS 泄漏。但域名规则是写不完的,总有些规则之外的漏网之鱼(小众域名)需要一个可靠的 DNS 解析兜底。 此外 fake-ip 有一些老生常谈的小问题,我个人是不大喜欢的,仍在使用 redir-host 模式。

由此,自建无污染 DNS 服务器在代理过程中的意义只是为了应对部分特殊情况以及获得更好的隐私性,顺便解决被各种「焦虑化」的 DNS 泄漏问题,对大多数人来说属于可有可无的东西。

设置 DNS 分流解析

AdGuard Home 0.107.3 及之后版本支持为指定域名设置 DNS 上游的,也可以直接使用文件列表。前面提到我还有一台树莓派在局域网内运行 AdGuard Home 服务,这时便解锁了一个新的玩法,树莓派上的 AdGuard Home 设置 DNS 分流解析。

可以实现国内域名通过 AliDNS 或 DNSPod 进行解析,其余域名通过自建的海外 DoH 服务进行解析,配合 AdGuard Home 的乐观缓存,也不会对速度造成多大影响。不过由于国内域名列表肯定是不完整的,不在列表中的域名会通过海外 DoH 解析,得到的 IP 结果是国内的还好,可以直接连接,否则会通过远程服务器连接,此时要是该域名有国内 CDN 节点就绕远了。因此比较依赖规则文件的及时更新和准确性,最后选择了使用 dnsmasq-china-list 项目。

这里写了一个 shell 脚本将 dnsmasq-china-list 发布的文件转换为 AdGuard Home 能识别的格式,如有需要可以参考修改。

#!/bin/bash
# Writen by ATP on Jan 25, 2024
# Website: https://atpx.com

# AdGuard Home 项目路径
AGH_PATH="/home/adguardhome"
# 国内 DNS 服务器,多个用空格隔开
CN_DNS="https://223.6.6.6/dns-query https://120.53.53.53/dns-query"
# 海外 DNS 服务器
GLOBAL_DNS="https://xxx.example.org/random-string/7017d85e-99d3-46df-bcbc-77a0a33c9cee"
# dnsmasq-china-list 规则文件,无法下载的话可以找 CDN 或自建反代
accelerated_domains="https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf"
apple_domains="https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/apple.china.conf"

# 下载文件并合并
wget -O "$AGH_PATH/cn-domains.conf" $accelerated_domains
wget -O "$AGH_PATH/cn-apple.conf" $apple_domains
cat "$AGH_PATH/cn-apple.conf" >> "$AGH_PATH/cn-domains.conf"

# 转换为 AdGuard Home 格式
awk -v CN_DNS="$CN_DNS" -F/ '/server=/{print "[/"$2"/]"CN_DNS}' "$AGH_PATH/cn-domains.conf" > "$AGH_PATH/cn-dns.txt"

# 添加默认海外 DNS
sed -i "1i\\$GLOBAL_DNS" "$AGH_PATH/cn-dns.txt"

# 移动文件到 AdGuard Home 目录
mv "$AGH_PATH/cn-dns.txt" "$AGH_PATH/work/data/"

# 清理临时文件
rm "$AGH_PATH/cn-domains.conf"
rm "$AGH_PATH/cn-apple.conf"

echo "规则转换完成"

# 重启 AdGuard Home
# cd $AGH_PATH
# docker compose restart

停止 AdGuard Home 后编辑目录下配置文件 conf/AdGuardHome.yaml,找到 upstream_dns_file: "" 参数,修改为:

upstream_dns_file: /opt/adguardhome/work/data/cn-dns.txt

然后启动 AdGuard Home 即可实现 DNS 分流解析。最后你可以设置一个 crontab 任务,定时执行脚本自动更新域名规则并重启 AdGuard Home,这里不再赘述。

如果你觉得这篇文章还不错,可以考虑支持作者

谈谈读书与消遣

leisure-reading

今天偶然看了林少华老师一个谈读书的视频,略有所感,胡诌几句。

视频中提到了「乐活」的生活方式。乐活,由 LOHAS(Lifestyles of Health and Sustainability)音译而来,意为以健康及可持续发展的型态过生活。其中的精神需求可以通过阅读获得满足,也就是「乐活读书」,主要有以下三点:

  • 非功利性:阅读要排除所有功利性,不期待物质上的即时回报,而是作为一种近乎本能近乎生理性的欲望和需求,无论身处何处,想读就读,自得其乐。
  • 非求证性:不必受传统教育模式的影响,阅读时总忘不了归纳段落大意,主体思想,普通人文学阅读是为了享受文学、享受阅读,阅读本身就是主题、就是意义。
  • 非祛魅性:祛魅(Disenchantment),指祛除神秘性和一切不确定的东西,不少人喜欢以现代人的眼光对以前的文学作品评头论足、标新立异,试图消除其原有的魅力,最后的得到的只是历史虚无主义,文化虚无主义。

我理解的「乐活读书」就是将读书作为一种有节制的消遣方式,如果只是为了获得精神满足,不是为了做文学研究的话,想读啥读啥,怎么舒服怎么来,也不必刨根问底,把读书当作吃饭、睡觉一般自然对待。但得保持节制,不能对书的内容过于发散思维以致于神经错乱、胡言乱语。我过往的阅读方式也大致如此,大多数书读完无非是一番感慨然后过几天就忘得一干二净,当真要有什么重大感想和领悟之类的也是自然而然的事情,不必强求。

至于读书与消遣更进一步的关系,有人说读书是最好的消遣方式,也有人说抱着消遣的心态读书是不行的。说到底现在的我读书是为了消遣,所以我觉得后一句是在放屁,消遣的方式很多,也不必靠读书,因此前一句也是在扯淡。

这里也要反省一下自己,对读书的热度总是一阵一阵的,已经记不起上一次酣畅淋漓的时候了。人一旦习惯碎片化的生活之后很难合理的利用整块时间了,什么,碎片化的时间也可以阅读?此言差矣,阅读嘛,当然得泡上一杯咖啡,坐上一个下午才爽,看个书都挤时间算什么事。但又想啊,一整个下午,压书和翻页都很累,看久了脖子也会痛。结果是只有等到实在无聊或者实在焦虑的时候才会想到要不来一本。

当然,狡辩的理由也是有的,要论消遣,玩游戏刷剧哪个不比读书高效,既然是消遣,自不必分个高低贵贱,甚至小黄书和经典文学也别无二致,哪怕是睡觉,效率上也要略胜一筹。硬要找点区别,大概是阅读对脑子来说是个熵减的过程,玩游戏刷剧是个熵增的过程,而睡觉是个做梦的过程。

以前的文章也提到过,如果我很长一段时间没读书,又会觉得难受,而且还挑食,不是备受好评的书不读,难免有好高骛远的嫌疑。不过可以确定的是,我们虽然只是生活在小说发生的地方,而不是写小说的地方,但读书消遣还是可以的,消遣自然是为了享受,全身心的放松,既然不为了寻求什么意义,也能从中找到些乐子,倒也罢了,姑且可以把读书留在我的消遣方式列表上。

人活在这世上,不必什么都知道,读书也一样,读几本好的或者自己有兴趣的就行了,只要不是为了考试考证之类的,在书里读到的,应该是有趣本身。读什么类型,记不记摘抄,写不写感想,全看你自己心情,想写自然是缘分到了,不写也无大碍,只要没人在你旁边念叨,「哎呀,你读的这本书不好,没意义。」「哎呀,你好爱读书,能不能告诉我这本书表达了作者怎样的思想感情?」至于消不消遣,也都无所谓了,无论如何只要时间花过去了,目的也就达成了。

如果你觉得这篇文章还不错,可以考虑支持作者

2023 年终总结

如果说之前是消失的三年,那接下来的 2023 年对我来说就是「悄无声息」的一年。年初时辞去工作,想着先休息一下,但现实是后面就很难找到工作了,因此有了大把的时间。刚开始本信心满满,想着做个网站或者开发个 App 搞点大事,但没几天就束之高阁,不了了之。日往月来,2023 年还是这样悄无声息的过去了。

老实说很难总结出什么名堂,所以不如写一些无关紧要的事,可以从这个网站说起。

博客总结

不算本文,今年一共发布了 17 篇文章(约 40424 字)。3 月份时从 Typecho 迁移到了现在的 Hugo,接下来的几个月里陆续改版了几个页面,直到现在的样式。目前没什么特别不爽的地方,相比去年少折腾了很多,明年应该也不会怎么修改。

浏览量从上半年的每日 100 IP 左右下降到了 80 IP 左右。具体来看,来自 Google 的流量减少了近一半,来日 Bing 的流量略有增加,来自 Baidu 的流量依旧为零忽略不计。这个趋势也符合预期,和我写作方向的转变有关。

在成本支出上,除了时间和精力成本外,本站本身要求不高,域名 + VPS 年付 $30 就能搞定,但总有一些无法控制的虚空成本。

首先域名上,值得表扬的是比起去年花了 $1000+ 购买新域名,今年只在续费上花了约 $50,没有购买任何域名,并且手里的域名精简到了 4 个,明年有望保持。而服务器上,新购加续费共计支出约 $300,当然大都不是给这个网站用的,那就大材小用了,而是更有意义的用途——吃灰。其实包括一些自建服务、开发测试和特殊用途这些刚需使用在内,完全能将年消费控制到 $60 左右,只能说黑五害人不浅,明年争取先打五折。

此外还有不少软件服务订阅支出,属于偏题,不再提及。

阅读书单

严格说来我不算是个爱阅读的人,也没给自己定下一年要看多少本书的目标,但如果每年不看几本书的话会觉得不自在,权当个消遣的手段,总的来说还不赖。这些书基本都是上半年看的,很多时候看完一本书过不了多久就会忘记了内容,只留个大概,这里也还是用一句话大概一下。

  • 《三体 Ⅲ》——刘慈欣。认知之外的世界,毁灭是永恒的主题,圣母也不能避免。
  • 《你想活出怎样的人生》——吉野源三郎,第 2 次阅读。生而为人,做一个好人。
  • 《春雪》——三岛由纪夫。爱不会带来悲剧,傲娇才会,但作为故事来说,又是含蓄唯美的。
  • 《球状闪电》——刘慈欣。用半生去追寻一个量子态的梦。
  • 《当我谈跑步时,我谈些什么》——村上春树。至少村上春树会跑到最后。
  • 《黄金时代》——王小波。庸俗又脱俗的,荒诞又真实的敦伟大友谊的时代。
  • 《史蒂夫·乔布斯传》——沃尔特·艾萨克森。史蒂夫·乔布斯,一个疯狂到改变世界的人。
  • 《成为乔布斯》——布伦特·施兰德、里克·特策利。关于史蒂夫·乔布斯的更有温度的成长故事。
  • 《我的职业是小说家》——村上春树。村上风格的小说和小说家是怎样炼成的。
  • 《写作这回事》——斯蒂芬·金。大师的成长故事和写作课程。
  • 《了不起的盖茨比》——弗朗西斯·菲茨杰拉德。叙事、描写、对话,我心中的优秀小说范本。
  • 《且听风吟》——村上春树,第 3 次阅读。又做了一个云淡风轻的梦。
  • 《局外人》——阿尔贝·加缪,第 2 次阅读。人们从自己的眼中定义他人,你终究是他们的局外人,有时也是自己的。
  • 《长眠不醒》——雷蒙德·钱德勒。对故事本身并不感到惊艳,但在一层层薄雾中娓娓道来就很能抓住人。
  • 《不能承受的生命之轻》——米兰·昆德拉。读了一半,暂时搁置。

工作学习

工作上就没什么可说的,用高情商总结是躺平得比较到位。

学习上,主要是机器语言和人类语言学习。去年尝试了学习 Rust 语言,今年尝试了学习 Go 语言和 SwiftUI,殊途同归、浅尝辄止,停留在 Hello World 的阶段。逐渐察觉到对目标性编程失去了兴趣,但就爱好性编程来说还留有余热,用 Python 写了简单的自动追番下载工具,重构了以前的库存监控工具;用 Shell 继续完善了一些简单的服务器运维脚本;Java 和 PHP 已脱坑勿念。最大的感受是 ChatGPT 对基础编程的帮助非常大,虽然错误率还是不低,但比起在网上搜索一堆抄来抄去的采集文章和机翻文章,或者看令人眼花头痛的英文社区,体验已经要好上太多。关键是还可以一直叠 Buff 提要求,帮你找 Bug 做优化,这么耐心的人现实中可没有。

人类语言学习上,背英语单词依旧处于三天打鱼,两天晒网的状态,可能没什么动力,简单单词基本都会,复杂单词使用率太低背了也容易忘。而日语学习终于向每年常规节目「背五十音」之外迈出了一步,开始学语法和背单词,虽然中间断了几个月,10 月份开始使用多邻国后倒是坚持住了每天打卡。

其它

- 看了不少美剧,最开心的是刷完了 生活大爆炸

the-big-bang-theory

- 听了不少音乐,听得最多的专辑是 結束バンド

apple-music-replay-23

- 看了不少新番,个人心中年度最佳是 葬送的芙莉莲

frieren

- 没怎么玩游戏,但终于通关了搁置多年的 荒野大镖客:救赎 2

red-dead-redemption-2

- 没怎么去影院,但体验了 蜘蛛侠闪电侠 电影连场串烧,尽管一言难尽。

spiderman-and-flash-movie

- 去了西安旅游,至今怀念各种 肉夹馍面食

- 尝试单机社交,偶尔会在自建 Mastodon 实例上吐槽。

- 惯例开源捐赠,迫于待业今年只捐赠了 $5 给 维基百科

……

总的来说一整年都在小打小闹,心态和体重都变化不大,大方向上踏足不前。虽然愈发能自娱自乐,低限度的社交,拒绝网友对线,不过总归是有清醒的时候。晚上总是习惯很困了再睡,免得突然顿悟什么了不得的事情。

其实这种状态也不是不行,宏观经济形势上稳中有升,一片向好,大家都在很努力地充实自己,甚至带动他人一起努力。我如果能悠然自得,把机会让给别人是在做好事,但就人类来说,到底还是要自己吃饱饭才作数,因此明年还得先找个厂。而对于自己吃饱了还要用一张纸把别人的馒头没收掉这种行为,很有艺术气息,但不妥,不过你要说是为了大局着想,那就妥了。

往好的看,好在 2023 年没生过病,生活上也没有受大锤,还能继续生猛,也希望大家都能一直生猛下去。最后,预祝大家新年快乐 🎉。

如果你觉得这篇文章还不错,可以考虑支持作者

西安两日游

作为普通的面食爱好者,每次看到陕西的面食视频都让我垂涎三尺,同时一直都对这座历史古都充满好奇,恰逢最近又在深夜看到了饼叔的关中吃饭故事,再也忍不住了,便带着我母亲前往西安游玩两日。

Day 1

第一晚的酒店定在翠华路,中午到达后直接去子午路张记肉夹馍(翠华路店)揭开序幕。

肉夹馍

每人点了一份肉夹馍和凉皮,肉夹馍非常香,第一口下去就很惊艳,肉厚软烂、汁水充足、油香四溢,凉皮相较之下没那么出彩,加几勺辣子后也算好吃。一口肉夹馍一口凉皮,非常快乐。

大雁塔
大雁塔(大慈恩寺内)。

吃完饭走十几分钟就到了“烂怂”大雁塔,虽然确实没啥好看的,但有句话叫「来都来了」,还是买票进去感受了一圈。旁边大悦城有孙悟空的超大手办,顶楼露天观景台也很适合拍照。

大悦城
大悦城商场内/顶。

傍晚时分,沿着大唐不夜城步行街一直走到了开元广场,一路上都有各种雕塑讲述着盛唐历史。

大唐不夜城

当然,不夜城的夜景才是我期待的,此时只是为了去银泰百货的袁家村补充碳水。点了份 biangbiang 面,感觉味道一般,加上店里很热只吃了半碗就吃不下了,本来还准备吃完继续探索店里其它小吃的。

好在夜幕已至,开始慢悠悠的往回走,游客纷至沓来,人多了不少,夜景确实漂亮,流光溢彩、美轮美奂。

大唐不夜城夜景

刚好在七点回到了大雁塔北广场观看音乐喷泉,可惜早已人山人海,没走到最佳的观赏位置,但也足够震撼,十分钟的演出恍然如梦、物理鼓动人心。

大雁塔音乐喷泉

经历喷泉洗礼之后就回酒店休息了,准备第二天再接受文化熏陶。

Day 2

已经提前预约好了陕西历史博物馆,而且博物馆就位于大雁塔旁边,早上随便吃了点后直奔目的地。

博物馆内几个展厅以周、秦、汉、唐四个朝代为重点展示陕西历史,文物之多,种类之盛。我没有找跟团讲解,而是选择自己慢慢欣赏,逛了近四个小时,但人真的超多,可以从下图中感受一下吃瓜群众围观杜虎符的热闹场面。

游客观赏错金杜虎符

顺便放几张我拍的文物图:

错金杜虎符
错金杜虎符。
镶金兽首玛瑙杯
镶金兽首玛瑙杯。
鎏金舞马衔杯纹银壶
鎏金舞马衔杯纹银壶。

从博物馆出来后感觉身上的文化气息也厚重了不少。又到了补充碳水的时候,第二晚的酒店订在洒金桥附近,理所当然的,要去洒金桥解决午饭。此时下午一点多,可能时间不对,洒金桥内车比人多。最后去了志亮灌汤蒸饺,点上一份牛肉蒸饺加麻酱凉皮,再配上一瓶冰峰,回味无穷,现在想起应该再来一份蒸饺的。

志亮灌汤蒸饺

马不停蹄,去酒店开好房间后就出发上西安城墙。本来准备从安定门上走到永宁门下的,结果没看地图走反了,一直走到了安远门,问题也不大,有不一样的风景。

西安城墙上
西安城墙上。
安远门
西安城墙(安远门)。

城墙外的环城公园我也很喜欢,感觉能走上一天。

下墙后坐车到了醉长安(钟楼店)吃晚饭,他家的大肘子已经让我流口水很久了。一份炫富肘子确实贵、一条老醋松鼠鱼、一碗腊汁肉拌油泼面,再来一小瓶桂花稠酒,十分满足。

西安钟楼
西安钟楼。

吃完饭后得散散步帮助消化,走几步就到了市中心的钟楼鼓楼,驻足观望、映入眼中即可,之后穿过西羊市闻闻街边味儿再一直到莲湖公园,虽然走了一天,但只要在湖边坐上一会儿,便感到神清气爽。

Day 3

由于下午赶飞机,只有半天可玩,纠结后抛弃美食去了大明宫遗址公园。从丹凤门进到玄武门出,一进来就感受到公园的空旷,里面人很少,暖阳伴随着和风一吹十分怡人。

从含元殿看丹凤门
从含元殿看丹凤门。

当然也去了里面的大明宫遗址博物馆学习历史,馆内同样有不少珍贵的文物。随后一路向北,穿过宣政殿紫宸殿,感受唐朝皇帝们的会议室和办公室。

从宣政殿看紫宸殿
从宣政殿看紫宸殿。

再往上走就到了太液池,太液池的水很清,微波粼粼,实乃修身养性之地。

太液池

最后穿过玄武门,走出大明宫,本次西安之旅也基本结束。

玄武门
玄武门(唐隆政变)。

也还有很多景点没去、美食没吃,期待下次能有机会来陕西放个长假,慢慢感受这里的生活。

如果你觉得这篇文章还不错,可以考虑支持作者

从 AirPods「升级」到 EarPods

前段时间苹果秋季新品发布会推出了 iPhone 15 系列,并首次更换为了 USB-C 接口,不出意外的,祖传的 EarPods 也获得了相应更新。也不由感叹,近年来有线耳机逐渐淡出了普通用户的视野,在大街上越来越少见,EarPods 更是成为了普通用户不考虑,发烧用户看不上的存在。曾经的顶级「白开水」和 K 歌神器早已不复当年风采。

  • 2001 年,苹果发布初代 iPod,并开始生产和销售经典圆形耳机;
  • 2012 年,苹果发布研发了三年的 EarPods 耳机;
  • 2016 年,苹果在 iPhone 7 上首次取消了3.5 mm 耳机孔并发布了第一代 AirPods 无线耳机;
  • 2020 年,苹果发布 iPhone 12 时宣布「为了环保」不再附赠耳机和充电头,同时 EarPods 价格从 29 美元(228 元)下降到 19 美元(149 元);
  • 2023 年,苹果在 iPhone 15 系列上首次采用 USB-C 接口并更新 USB-C 接口版 EarPods 耳机。
EarPods (USB-C)
EarPods (USB-C).

我为什么在今天仍选择购买 EarPods 呢?原因很简单,那就是穷。 用了多年的 AirPods 2 续航已经被榨干了,听一个多小时剩 50% 多电量的时候就会直接归零,充一分钟电后又显示 80% 多电量,然后坚持不了多久又归零,很影响码字和刷剧的节奏。虽然以前也坚持过几年森海塞尔 IE60,最终还是发现自己不喜欢入耳式耳机,戴久了是真的难受,因此 AirPods Pro 不考虑。而如果这个时间点买 AirPods 第三代就很微妙,明年应该会发布第四代被背刺。最重要的是,AirPods 虽然体验好,但真的不便宜… 以前用过 3.5 mm 和 Lightning 接口的 EarPods 印象都还好,刚好这次 USB-C 接口的 EarPods 在网上的评价也很不错,不如试试回归有线。

AirPods 2 vs EarPods (USB-C)
AirPods 2 & EarPods (USB-C).

要说音质的话,我不好评价,因为听不出和 AirPods 2 有多大区别,可能更好一些?至少对我来说都属于够听的水平。其实苹果当年 Mac 电脑时运不济时很大程度上是靠音乐服务(iTunes 和 iPod)翻盘的,而后演变为 iPhone 和 iPad 登上山巅,乔布斯作为狂热的音乐爱好者,一直在为其产品注入音乐基因,因此苹果对 EarPods 的调教是花了相当的功夫的,高情商说法是各方面都比较均衡。日常使用体验如下:

优点:

  • 便宜,音质也过得去;
  • 不用担心续航和延迟;
  • 麦克风收音还行;
  • 线控挺方便不用再敲耳朵;
  • 佩戴更舒适,AirPods 戴久了也会有点胀耳朵,EarPods 轻松很多。

缺点:

  • 想起了每次拿出来都要理线几分钟的恐惧;
  • 如果要走动或者出门很不方便,挤车这些更不用说;
  • 占用了手机唯一的接口。

值得一提的是,虽然图一乐,但 EarPods (USB-C) 配备支持 16bit / 44.1kHz 至 24bit / 48kHz 的解码器(DAC),真的可以传输无损音乐

* 常说的无损音乐以 CD 音质为标准:44.1kHz 采样率、16bit 位深,码率为 1411.2kb/s。Apple Music 提供两种无损选择,16bit / 44.1kHz 至 24bit / 48kHz 的 CD 级别无损音质和 192kHz / 24bit 的高解析无损。

usb-c-earpods-speed
EarPods (USB-C) 最高传输速度为 12Mb/s。
usb-c-earpods-bit-rate
macOS 在「音频 MIDI 设置」中设置采样率。

然后我又翻出吃灰多年的 IE60 对比了一下,好吧,不该翻出来的… 虽然 IE60 也只是入门级,但确实降维打击,本来还挺舒服的,这下回不去了。所以耳机这东西还得浅尝辄止,不可深入。

但综合来说,EarPods 仍算得上一百元级耳机最好的选择之一,属于苹果为数不多的良心产品。这次购买在某些场景下也算得上是小小的「升级」,目前如果需要在电脑前长时间佩戴耳机的话,我会直接使用 EarPods,但出门的话,还得再压榨下 AirPods,EarPods 作为备用。

如果你觉得这篇文章还不错,可以考虑支持作者

Docker 搭建去中心化的微博客平台 Mastodon

mastodon

自从老马被迫收购 Twitter 后,操作不断,改名 X 后更是在成为微信的路上一去不复返,虽然我只是偶尔上 Twitter 看看猫猫狗狗的萌图,但感觉老马迟早不给免费用户玩。于是未雨绸缪,搭建一个自己用。以前分享过使用 Docker 搭建去中心化的微博客平台 Misskey,这次又探索了一些类似平台,如 SoapboxMinds 以及基于 Misskey 的 firefish 等,迫于兼容性和文档考虑,最后还是准备使用 Mastodon

虽然网上有很多详细的教程,但大多是很早以前的了。我在搭建时可以说是充满曲折,这里也记录下来,希望对遇到了同样问题的朋友有帮助。

* 演示安装环境为 Debian 12,使用 root 用户,采用预编译的官方 Docker 镜像通过 Docker Compose 部署。

准备工作

Docker 和 Docker Compose 的安装与基础使用可以参考文章 Debian 系统安装 Docker 教程,不建议在中国大陆的服务器上部署 Mastodon。

在合适的路径下创建目录:

mkdir -p /home/mastodon && cd /home/mastodon

修改 docker-compose.yml 配置文件

wget https://raw.githubusercontent.com/mastodon/mastodon/main/docker-compose.yml
vim docker-compose.yml

注释掉所有镜像(web, streaming, sidekiq)的编译命令 build: .,并修改你想要的镜像版本,如 image: ghcr.io/mastodon/mastodon:v4.2.1,如果你想要最新版本则设置为 image: ghcr.io/mastodon/mastodon:latest。如果你需要全文搜索功能,则将 es 服务整块取消注释。

修改 .env.production 配置文件

这里有手动和交互式向导两种方式配置环境。但如果直接使用自带向导配置可能会在最后一步设置管理员账户的时候弹出一个 Redis 连接的错误:

ERROR -- : RedisCacheStore: read_entry failed, returned nil: Redis::CannotConnectError: Error connecting to Redis on localhost:6379 (Errno::ECONNREFUSED)

失败几次后发现可能的原因是在向导配置时没有写入参数导致找不到 Redis Host。有两个办法解决,先手动设置 REDIS_HOST 后再通过交互式命令配置,或者先不要设置管理员账户,之后用 tootctl 工具创建,这里我选择前者。

获取并修改 .env.production 配置文件:

wget https://raw.githubusercontent.com/mastodon/mastodon/main/.env.production.sample -O .env.production
vim .env.production

提前修改部分参数,如果在生产环境中,这里建议修改 DB_USER 设置一个 Mastodon 数据库专用用户(例如 mastodon)并设置 DB_PASS 密码,下例为了方便使用 Postgres 数据库的默认账户 postgres 进行演示:

# Federation
# ----------
# This identifies your server and cannot be changed safely later
# ----------
LOCAL_DOMAIN=<你的域名>

# Redis
# -----
REDIS_HOST=mastodon-redis-1
REDIS_PORT=6379
REDIS_URL=redis://@mastodon-redis-1:6379

# PostgreSQL
# ----------
DB_HOST=mastodon-db-1
DB_USER=postgres
DB_NAME=mastodon_production
DB_PASS=
DB_PORT=5432

...

配置 Mastodon

运行配置命令:

docker compose run --rm web bundle exec rake mastodon:setup

第一次拉取镜像时可能要花些时间(下载 1G+),耐心等待。这里注意在配置时数据库和 Redis 的相关参数和前面文件中的保持一致。

Mastodon 向导安装配置界面
Mastodon 向导安装配置界面。

最后一步完成后记下输出的管理员密码。并将上方打印的 .env.production 相应参数手动覆盖到文件中,主要是 SECRET_KEY_BASE, OTP_SECRET, VAPID_PRIVATE_KEY, VAPID_PUBLIC_KEY 这几个,完整的参数列表和作用请查阅文档

启动 Mastodon

修改 public 目录权限:

chown -R 991:991 public

启动 Mastodon 服务:

docker compose up -d

配置 Nginx

参照官方模版,修改文件中的域名和 root 路径,并将所有的 try_files $uri =404; 替换为 try_files $uri @proxy;

如果你幸运的话此时可以正常打开网站使用了。

但我到这里折腾了一个通宵也没成功,打开网站一片空白,所有静态资源无限报 net::ERR_HTTP2_PROTOCOL_ERROR 200 (OK) 错误,以前在其它程序也遇到过,当时参考 Stack Overflow 上一篇帖子关闭 proxy_max_temp_file_size 缓存限制就好了。

server {
  ...
  ...
  gzip off;
  proxy_max_temp_file_size 0;
  location / {
    proxy_pass http://127.0.0.1:3000/;
    ...

然而这里却没有起作用,网上也搜不到相关的错误。几经放弃,最后又复盘了下,能建立链接但是资源请求失败,肯定是反代那里出了问题,终于在尝试取消反代缓存后居然成功进入了网站界面。

location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://backend;
    proxy_buffering on;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    # proxy_cache CACHE;
    # proxy_cache_valid 200 7d;
    # proxy_cache_valid 410 24h;
    # proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    # add_header X-Cached $upstream_cache_status;

    tcp_nodelay on;
}

接着又经过无数次探索和尝试,最后发现万恶之源在 proxy_cache_path 上面。根据 Nginx 文档的说法:

The directory for temporary files is set based on the use_temp_path parameter (1.7.10). If this parameter is omitted or set to the value on, the directory set by the proxy_temp_path directive for the given location will be used. If the value is set to off, temporary files will be put directly in the cache directory.

临时文件的目录默认为 on,Nginx 会先把要缓存的文件放到临时存储区,如果为 off,则会将缓存文件直接写入指定的 cache 文件中,而不使用 temp_path 指定的临时存储路径。

可能就是这里读取缓存文件时出现了玄学问题导致我一直没成功,在尝试添加 use_temp_path=off 后终于完美运行。

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g use_temp_path=off;
Mastodon 安装完成后的关于页面
Mastodon 安装完成后的实例关于页面(搭配 mastodon-bird-ui 主题)。

运维管理操作

这里记录一些常用的使用 Docker 部署的 Mastodon 服务器运维管理操作。

使用 tootctl 管理

管理 Mastodon 推荐通过内置的 tootctl 命令进行,Docker 部署的 Mastodon 最直接的方式可以使用 docker exec 命令管理:

docker exec mastodon-web-1 tootctl <command>

例如通过使用 tootctl 创建一个 id 为 newuser 的普通用户:

docker exec mastodon-web-1 tootctl accounts create \
  newuser \
  --email newusr@example.com \
  --confirmed

如果是一些需要交互的设置,可以进入容器后再在执行命令:

docker exec -it mastodon-web-1 /bin/bash

此时,你可以配合 crontab 执行定时任务来自动化管理,例如每天凌晨 3 点定时清理本地缓存的其它实例媒体文件:

0 3 * * * docker exec mastodon-web-1 tootctl media remove --days=7
0 3 * * * docker exec mastodon-web-1 tootctl media remove-orphans
0 3 * * * docker exec mastodon-web-1 tootctl statuses remove --days=90
0 3 * * * docker exec mastodon-web-1 tootctl preview_cards remove --days=180

其中:

  • tootctl media remove :清除缓存超过指定天数的外站媒体附件
  • tootctl media remove-orphans :移除不属于任何媒体附件的文件
  • tootctl statuses remove :从数据库中删除超过指定天数未被引用的嘟文
  • tootctl preview_cards remove : 移除超过指定天数的本地预览卡片缩略图

迁移服务器

如果旧服务器正常运行的话,最简单的方法是直接迁移 Docker 容器。这里主要结合官方文档,给出旧服务器忘记续费/rm -rf/爆炸后,只有备份文件的情况下的恢复示例 为了验证流程有效性,我直接把数据库删了 QAQ

你平时可以通过下面的命令导出数据库文件,和 .env.production 文件以及媒体文件(public/system)一起使用 rsync 或者 rclone 定时自动备份到安全的地方。

docker exec mastodon-db-1 pg_dump -U postgres -Fc mastodon_production > /path/to/db-backup.dump
  1. 恢复数据库

在新服务器上将数据库备份文件、docker-compose.yml.env.production 文件复制到你的项目路径下。注意不能和部署新实例时一样运行向导配置。

进入你的项目路径,启动数据库容器:

docker compose up -d db

使用 docker ps 找到该容器 id,拷入数据库文件之后进入容器创建一个新的空白(UTF8/en_US.utf8)数据库:

docker cp ./db-backup.dump <container id>:/tmp/db-backup.dump
docker exec -it <container id> /bin/bash
su - postgres
createdb -T template0 mastodon_production

* 如果你此前使用了其它账户管理数据库,这里还需要新建相应的数据库用户,假设该用户为 mastodon,密码和 .env.production 中一致:

psql
CREATE USER mastodon WITH PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE mastodon_production TO mastodon;
\q

导入数据库,注意修改 -U--role 参数相应的用户名:

pg_restore -U mastodon -n public --no-owner --role=mastodon \
  -d mastodon_production /tmp/db-backup.dump

最后退出并停止容器。

  1. 导入媒体文件

直接将备份的 /public/system/ 目录上传到项目路径下并修改权限即可:

chown 991:991 -R ./public

如果你没有备份外站用户的缓存(例如头像、头图等),之后会出现 404,可以刷新所有用户缓存:

docker compose run --rm web bin/tootctl accounts refresh --all

媒体附件同理可以通过 tootctl media refresh 刷新。

  1. 重建主页时间流
docker compose run --rm web bin/tootctl feeds build

等待完成后就可以启动 Mastodon 服务了:

docker compose up -d

如果你准备开放实例供他人注册使用的话,需要配置和优化的地方还有很多,连接中继、云端存储、定时备份、性能优化等等,我这里由于服务器性能限制,暂时不打算开放注册,就不再研究了。

如果你觉得这篇文章还不错,可以考虑支持作者

聊聊 iPhone 15 Pro 使用感受

手里用了四年多的 iPhone Xr 除了续航挺不住了之外,3G RAM 也经不起现在的 App 折腾了,每次被迫打开「小而美」时都会让你远望地球,顺便干掉其它 App 的后台。所以此前看到爆料 15 Pro 系列会使用 USB-C 接口和升级到 8G 内存的时候就感觉是时候换机了。

9 月 13 日熬夜看发布会的时候除了「环保」外给我留下最深印象的就是 A17 Pro 芯片,首个 3nm 芯片不说,NPU 性能翻倍,又是光追又是 MetalFX,直接默秒全。但等首批评测解禁之后就不对劲了,性能提升有限,热量却拉满,续航也倒退,让我不得不选择先观望一下。不过又体验了一周出门半天就没电的 Xr 之后还是忍不住了,然而此时官网已经排到了 10 月底,好在国庆当天凌晨在某东自营买到一台 15 Pro 256G 原色版本。

iPhone 15 Pro 开箱
iPhone 15 Pro 原色钛金属开箱。

使用体验

此前已经去店里体验了一会,所以开箱上手时没太多新奇的感觉。第一感觉是握持比 Xr 舒服不少,重量轻 7 克,机身小上一圈手感好很多,只是我习惯左手持机,背部的摄像头模组太大有点碍事。着重检查了下摄像头灰尘和背板边框缝隙,也没发现问题。比较在意的是看评测这次钛合金边框比较硬,摔地上的话屏幕和背板可能会比以往更容易碎,作为常年裸机党还是准备先买个保护壳戴一段时间。

先说发热问题,我不玩手游,正常使用没什么不舒服的地方。第一次开机后是有一点热量,但不至于烫手,新 iPhone 刚转移后系统后台会建立索引,此时肯定是会发热的,用了半天后就基本没感觉了。开摄像头会有点温热,这个不可避免,如果长时间录制 4K 视频就会有点烫手了,但还是不如我的 Xr 给劲。具体还得看明年夏天的表现,从使用 Xr 这几年的夏天户外体验来看,是相当爆炸的。苹果最近解释说过热问题是由于 iOS 17.0 以及第三方 App 的一些 bug 造成,将通过后续系统更新改善(Updated:本文发布一小时后苹果就推送了 iOS 17.0.3 更新用于修复了一个可能导致过热的问题),希望到时表现会更好。

至于续航,我是不抱太大希望的,中度使用能坚持一天就行,从官网纸面参数来看比 Xr 强,实际用下来也是如此,但不多,好在也够用。如果你喜欢打游戏或者户外一直高亮度的话就不顶不住了,有这类需求建议买 Pro Max。

iPhone 15 Pro 系列与 iPhone Xr 电池续航对比
Apple 官网上 iPhone 15 Pro 系列与 iPhone Xr 电池续航对比。

对比 Xr 感知提升较大的地方分别是屏幕、摄像和后台存活。

屏幕我算是更喜欢 LCD,OLED 虽然对比度和色彩更好,但烧屏和低亮度 PWM 频闪是两个比较大的缺点,不过也没得选择。长期使用 165 Hz 刷新率的显示器导致现在对刷新率有点敏感,ProMotion 的加持对操作手感提升很大,同时也能看出是不同场景动态变化的,并不是全局锁定 120 Hz。摄像上如同单车变轿车,一代「最强单摄」的 Xr 相比之下只能算能扫码的水平,这 5 年的进步闭眼可见,让我出门的时候更愿意拿出手机随手拍一拍。8G 内存带来的提升很简单很直观,就是杀后台变少了,够「小而美」再升级几次多干点坏事。

iPhone Xr 对比 iPhone 15 Pro
iPhone Xr 与 iPhone 15 Pro 外观对比。

此外,扬声器同样音量设置声音更大了;信号感觉差别不大,还有待去一些更刁钻的地方进一步体验;Action Button 和灵动岛对我来说属于锦上添花就不再谈了。虽然提升是全方位的,但初次开机配置好后把玩了十分钟就「索然无味」了,其实作为不玩手游的人,A12 的性能已经基本够我用了,用上 A17 Pro 有当年显卡吧「十万神机刷贴吧」的感觉,如果普通版有 ProMotion 或 8G 内存任意一个升级的话我应该会买普通版,这里特别点赞一下库克的刀法。

3nm 的 A17 Pro 到底翻车没有?

媒体评测解禁第一时间看到极客湾发布 A17 Pro 评测视频 时我对这次 Pro 系列挺失望的,峰值功耗拉满、续航倒退、发热严重。之后又看了几个国外博主的测试,高负载发热确实是有问题,但续航似乎各家评测差距较大,由此我对 3nm 工艺的提升究竟如何产生了兴趣,搜索学习了下相关的资料,想知道 3nm 的 A17 Pro 到底翻车没有?

台积电的 N3 系列针对不同方向优化分为 N3B、N3E、N3P、N3X 和 N3S,A17 Pro 采用的是第一代 3nm 工艺 N3B,使用 FinFET(Fin Field-Effect Transistor)工艺,该工艺的好处是让晶体管密度大幅增加,得以实现 190 亿晶体管(A16 为 160 亿)。与之相对,三星在 3nm 上选择了压宝 GAA(Gate-All-Around)工艺,虽然目前还未大规模量产,但有望提供比 FinFET 更好的功耗与密度。业内认为在过去 7 nm 制程以上时,FinFET 表现很好,但到了 7nm 制程以下,静态漏电的问题越来越大,FinFET 无法满足 3nm 及更先进制程的需求。尽管台积电表示已对其 FinFET 技术进行了重大更新,大幅度提高能效,但目前良率依旧不算高,导致自己根据与苹果的协议还在为次品买单,后期肯定还会想法设法不断优化的。

那么是台积电的锅吗?也不见得。A17 Pro CPU 性能只提升了约 10%,主要提升在 NPU 性能和多塞了一个 GPU 核心、AV1 解码器、USB 3 控制器等东西,看得出来是 M3 的试验田。从实际表现上看,A17 Pro 的小核对比 A16 能耗是有提升的,但苹果为了打上 CPU 性能提升 10% 的 PPT 标语,把大核频率拉得有点高导致峰值功耗收不住,还不做散热,加上目前系统性能调度可能有问题,最终赢得了「火龙果」的称号。因此我认为即使 N3B 算不上太给力,锅主要还是苹果自己的,至于翻没翻车,对于游戏和摄影用户来说目前是翻了,对于其他没有持续高负载使用场景的用户来说其实也还好。

据称明年登场的 N3E 工艺可能会有所改善,虽然在晶体管密度上不如 N3B,但功耗控制上会更加理想,包括苹果在内的多家厂商都有意届时采用这项工艺。如果你是 13 Pro 及以后的用户,纠结是否换手机的话等明年 16 系列可能是更好的选择等等党永远不亏

题外话:苹果生态在国内的未来

继前段时间要求所有手机 App 备案的通知后,9 月 30 日,苹果更新了 App Store Connect 在线支持文档,要求在中国大陆上架的 App 必须具备有效的互联网信息服务提供者(ICP)备案号。国内互联网连年收紧已不是什么新鲜话题,但还是难免感叹。虽然苹果天天宣传隐私保护,但目前为了中国市场肯定会选择配合一切要求。

App Store 上架要求备案不是什么大事,对绝大多数只用国内软件的用户不会有影响,“无非就是大量独立开发者失业”,有其它需求的用户自然会有一个外区 Apple ID。怕的是这个加速停不下来(目前是这个趋势),如果未来采取硬件限制,国行版本不允许登录其他区的 Apple ID 并且采用 Apple News 一样多因素判断限制,可能就锁死了。再加上现在美版 iPhone 取消 SIM 卡槽,全面 eSIM 化,要是未来其他海外版本也跟进就更没辙了。此外,国外很多公司和开发者不可能专门花精力为中国用户备案上架,必然会造成很多优秀的 App 无法使用,比如 GoodNotes、Notion 等,除非支持欧盟尽快要求苹果开放侧载或者回到越狱的时代,但国内大厂们自家的招牌 App 肯定也会搞出商店阉割,要求去自己应用商店下载「完整版」的操作,用户始终是被拿捏的。

苹果如今在国内的处境也很微妙,作为一个美国公司,遭受魔法攻击是不可避免的,这次发布 iPhone 15 系列的各种节奏就比往年规模和范围大多了。再远一点来说,现在体制内禁用苹果手机的趋势愈演愈烈,如果哪一天手机在国内卖不动了,所谓的苹果生态在国内自然也会崩盘,毕竟最近教育、互联网和房地产倒下来的速度一点也不含糊,都是一句话的问题。隔壁微软即使是在中国工程院外籍院士——比尔·盖茨的长年拉扯下依然半死不活,苹果估计会更不好过。

好消息是以上都是我在瞎扯,至少目前该怎么用就怎么用,说到底也不是担心以后用不了苹果,重铸安卓荣光也不错,只是不喜欢特色流氓软件,App Store 审核和沙盒机制多少能管一管,限制一下权限。不过真要有哪天安排下来肯定坚决支持,毕竟到那时谁还会去担心一个手机。

如果你觉得这篇文章还不错,可以考虑支持作者

能否将 TOTP 二次验证存放在密码管理器里

totp-2fa

最近在纠结一个问题,要不要把密码 TOTP (Time-Based One-Time Password) 形式的 2FA (2-Factor Authentication) 合并到密码管理器中,这样可以更加方便地使用和管理,不过弊端也很明显,2FA 验证实际上变成了 1FA 验证,有被一锅端的风险,但仔细一想,似乎问题也没那么大?

目前密码管理使用的是自建 Vaultwarden (Bitwarden),TOTP 使用的是 Microsoft Authenticator,两者配合达到了“较高”的安全性。如果将 TOTP 添加到 Vaultwarden (Bitwarden) 中,一旦 Vaultwarden (Bitwarden) 发生泄漏,密码和 2FA 将一同送上,会造成严重损失。因此,需要做一个权衡:更安全还是更便捷。

首先好处是显而易见的,将 TOTP 放到 Vaultwarden (Bitwarden) 里之后,登录网站时不必专门打开手机 Authenticator 应用去看验证码,Vaultwarden (Bitwarden) 自动填写用户名和密码之后,二次验证码也会自动复制到剪贴板,只需粘贴即可完成登录。

还有就是备份。开启 TOTP 之后,两步验证工具的可靠性也很重要,手机丢失/损坏,同步/恢复出错也是一大风险点。虽然 iOS 上 Microsoft Authenticator 支持 iCloud 备份,但不支持跨平台同步(到 Android),也看到了一些系统升级导致数据丢失的例子。即便大多数账号在开启 2FA 时我都在本地加密备份了恢复代码,但随着注册的账号越来越多,难免有漏网之鱼,添加到 Vaultwarden (Bitwarden) 中管理也相当于多一份备份,而且我也早就想将所有的 2FA 重新设置整理一遍了。

相对于便捷性来说,安全性上唯一的增加的风险来自密码管理器自身的泄漏,但 Vaultwarden (Bitwarden) 本身也支持开启 2FA,你仍需要使用第三方应用/硬件来管理该 2FA,也就是说此时将两者一起存在和分开存放的安全性其实是差不多的,如果你的 Vaultwarden (Bitwarden) 账号 TOTP 泄漏,也意味着存在于该 Authenticator 应用上的其它账号的 TOTP 同时泄漏了。主要差距在于密码管理器的一大特点是多终端同步,你的 2FA 也会保存到所有你登录的终端,这就增加了一定的隐患,此时建议你将密码管理器的超时时间以及超时动作设置激进一些,或者直接创建两个账号分开存放并通过单独的设备管理 TOTP 账号。剩下的就取决于你是否有自信做到以下几点:

  • 完全信任你使用的密码管理器
  • 保证主密码的强度以及不会泄漏/遗忘
  • 不会被邮件/短信轻易钓鱼
  • * 自建服务器的可靠性以及定时备份,数据库文件不会被泄漏并破解
  • 不会被人趁你不注意从解锁的客户端中导出数据

如果能保证这些,安全性其实依然不错。此外 TOTP 本来也不算特别的安全的 2FA 形式,要保证安全,就不建议进行任何形式的网络同步,并且 2FA 也不应该和密码同时存在于一个设备上,保存到本地离线设备才是最优解,TOTP 发展到今天被大众接受,本就是不断的向便捷性妥协的结果。更好的办法还是使用基于 FIDO2 (Fast IDentity Online 2) 的硬件密钥,不过这又回到了开始的问题,要更安全还是更便捷。

在我看来,鸡蛋不放同一个篮子的担心没错,不过篮子本身的可靠性也需要考虑,如果有同步 2FA 的需求,比起使用微软、谷歌等 Authenticator 工具的同步功能,我还是更愿意自己把控风险,一切都是 trade-off,你都不信任密码管理器了,那为什么还要使用它呢?别让自己一直处于如果被三体人破解了怎么办的忧虑中,在够用以及一定强度的安全保证下,更加便捷也是不错的选择,不过如果你也纠结的话,我肯定是建议分开存放的。

如果你觉得这篇文章还不错,可以考虑支持作者

认识史蒂夫·乔布斯

Book: Steve Jobs

让时间回到 2020 年的秋天,那年苹果第一次将发布会搬到线上,推出了又一次带来变革的 Apple M1 芯片。当时作为科技爱好者,感叹苹果技术与创造力的同时,更让我想去了解苹果背后的故事,赶紧买来《史蒂夫·乔布斯传》这本书,可惜的是看了前几章之后便丢在了一旁。

如果再把时间往前推,我在很长的一段时间里都是 Google 的忠实粉丝,Android 3.0 开始使用,坚信自由开源的精神,经历过国内各种定制 ROM 包百花齐放的年代,自己也魔改过不少,也写过安卓软件。而苹果在我眼里更多的是一家喜欢去教用户如何使用产品,封闭、固执己见的公司。后来逐渐体会到了 Google could be evil sometimes,同时国内安卓环境问题日益严重,各种无下限的 App 层出不穷,权限索取、隐私问题尤其严重,感到十分焦虑。也尝试过诺基亚 Lumia 系列,WP 的结果大家都知道。由此开始认为有人帮自己把关未必是坏事,从 2015 年开始尝试苹果的产品。

每当提起用过的第一款苹果产品,都要惋惜一下那个伴随枕套意外丢掉的 iPod shuffle,轻便、精致,每晚入睡的好伙伴。之后又买了 iPad,大部分时间都在吃灰,后来手机也换成了 iPhone,但仍只算得上普通路过的用户,随时都可以重回 Android。

回到 2023 年的今天,作为一个小学时就能闭眼组装电脑,初中时就在电脑城和奸商谈笑风生,绝对热爱的 PC 用户也转向了 Mac。这才发现已经掉进了苹果的圈套,也许现在作为科技爱好者,少折腾对我才是最大的吸引力。

于是我又翻出了《史蒂夫·乔布斯传》这本书,再次尝试去了解乔布斯。

乔布斯的故事无论是辉煌时还是落幕时都足够耀眼。他在事业上的成就是伟大的,苹果的成功无须多言,在父母的车库里开创的一家企业,成为了全球最有价值的公司。

我们相互间诚实到残酷的地步,任何人都可以跟我说,他们认为我就是一坨狗屎,我也可以这样说他们。我们有过一些激烈的争吵,互相吼叫,但那可以说是我最美好的一段时光。

——乔布斯回顾过去时的自述

多数人不知道的是乔布斯可能算不上一个好老板,他对员工脾气暴躁、喜怒无常,时常吼骂员工甚至商场服务员和陌生人,像高压交流电一样善变,喜欢两极分化评价人和事,要么你是天才,要么你是白痴;要么你的工作是最棒的,要么是垃圾。他总是直视你的内心毫无顾忌的进行攻击,但如果你能经受住摧残,据理力争,即便是对骂,只要证明你是对的,他就会开始尊重你。

生活和为人上也有很多无法认同的地方,大学后以水果和蔬菜为主的素食主义,曾吸收大麻和迷幻药并认为使用迷幻药是他人生中最重要的两三件事之一,总之二战后美国那一代嬉皮士的特点都能在他身上找到。年轻时候个人卫生也堪忧,很少洗澡,经常不穿鞋子,同事都十分嫌弃。在 23 岁时,他的女友生下了他们的女儿丽萨,但他在很长时间里都却拒绝承认自己是丽萨的父亲,虽然后来捡起了责任并感到懊悔。

Steve Jobs at home
乔布斯在库比蒂诺的家中,1982 年。(Diana Walker)

但不得不承认乔布斯是个天才,是个疯狂的天才。小学时就展现出超越年龄的智慧和捣蛋能力,高一时给惠普 CEO 打电话并获得了一份暑假工作,之后通过自己各种各样的工作攒钱买了一辆汽车。和不少大佬的故事一样,乔布斯的故事里也少不了退学,大学上了半年后出于对必修课的厌恶选择了退学,继续旁听感兴趣的课程,每天睡在朋友房间的地板上。之后回老家找了份工作,然后 19 岁时又辞职去印度进行一趟精神之旅,在印度的村庄待了 7 个月。

The people who are crazy enough to think that they can change the world, are the ones who do. // 只有那些疯狂到以为自己能够改变世界的人,才能真正地改变世界。

——1997 年乔布斯回归苹果后发布的广告“不同凡想”(Think Different)

他也是一个有强烈控制欲和完美主义的人,规定 Mac 上的操作系统只能在自己的硬件上运行,从最初的 Mac 到最后的 iPhone,每次广告的拍摄,零售店和公司大楼的设计,甚至是机箱里的一颗螺丝也要绝对控制。他把所有的精力都献给了事业——源于“去为历史的长河加上一点什么,去打造一家能传世的公司”这一理念。他甚至还是个爱哭的人。和别人吵架会哭,没拿到 “1 号”员工编号会哭,发现 Windows 抄袭了 Mac 的图形界面会哭,没得到朋友支持也会哭。

乔布斯的系统一直是封闭的,他不希望别人破坏他优雅的设计,不相信顾客永远是正确的,虽然他对硬件和软件技术可能了解得不够深入,但完美的软硬件结合与控制权是他一直追求的,并且总能找到方法给员工注入持久的热情,创造革命性的产品。乔布斯一直希望苹果公司能建立起独立统一的乌托邦,在这个神奇的围墙花园里,硬件、软件和外围设备完美结合,创造一种绝妙的体验,某一个产品的成功也能促进所有关联产品的销售。

Steve Jobs unveil iMac
发布第一代 iMac,1998 年。(Mousse Mousse/Reuters)

乔布斯的事业也并不风顺。Apple Ⅱ 的推出名噪一时,但 Apple Ⅲ、Lisa 都反响平平,Mac 刚开始也是雷声大雨点小,他想要制造面向大众的电脑、操作简单、价格低廉,同时又对产品设计的细节和用料及其严格,不愿他人涉足,不听取他人的意见的刚烈性格也惹恼了公司其他管理层。1980 年公司重组,他以前亲自找来的 CEO 联合董事会将乔布斯的权力架空,终于在 1985 年离开苹果,一家由他在成立后 10 年里打造成 20 亿美元市值的公司。

离开苹果后,没多久乔布斯又建立了 NeXT 公司,专注于面向高校的高性能计算机,但刚开始的乔布斯过于傲慢,完全没有了解市场需求,只忙着打造自己想象中的产品,把大量的钱花在设计昂贵的 logo、建造一流的工厂上面,把更多的关注点放在了漂亮的字体、完美的电路板走线和镁质机箱上。注定的失败也让他离开苹果时一同追随而来的核心团队失去了信心,先后离职。NeXT 公司挣扎了几年,最后还是无奈取消产品线,卖了硬件和工厂,专注于 NeXTSTEP 操作系统和 WebObjects 软件的开发,1996 年,NeXT 公司终于靠着软件站稳了脚跟。

而收购卢卡斯影业动画部门可以说是乔布斯职业生涯的转折点。他在苹果时就对这家 3D 图像技术的公司感兴趣,在 1986 年以 500 万美元和继续投入 500 万美元建设公司的承诺收购了卢卡斯影业的电脑动画部改名为皮克斯图像电脑(Pixar Images Computer)进军动画行业,1995 年,凭借和迪士尼合作的推出的《玩具总动员》大获成功,终于找回了他的魔法。后来 2006 年乔布斯以 74 亿美元的高价将皮克斯出售给了迪士尼,他也因此成为迪士尼在 2006 年至 2011 年的最大个人股东。

1996 年,由于苹果缺乏创新以及在微软的攻势下,市场份额从最高的 16% 下降到了 4%,第一季度亏损近 7.5 亿美元,已经岌岌可危,到了四处寻求收购买家甚至考虑申请破产的地步。乔布斯说服了苹果时任 CEO 吉尔·阿梅里奥(Gil Amelio)收购自己的 NeXT 公司继而以非正式的兼职顾问的身份重新回到苹果,并用了半年重新掌握大权。

乔布斯正式回到苹果后马上开始了大刀阔斧的改革,大量的裁员,部门架构调整,砍掉多余的产品线,简化公司的目标。回归前一个财年苹果亏损了 10.4 亿美元,回归后的 1998 年财年实现了 3.09 亿美元的盈利。之后就是我们熟悉的故事了,iMac、Mac OS X、iPod、iTunes、iPhone、iPad,他总能用最敏锐的洞察力看到问题所在,把关注点放在真正有价值的地方,坚持人文与科技的结合,然后用完美主义去实现。

生命也许就像个开关一样,啪!然后你就没了。也许这就是为什么我从不喜欢给苹果产品加上开关吧。

——乔布斯思考死亡时的想法

乔布斯经历了癌症的反复发作,2003 年 10 月确诊了癌症,随后发现癌症有向肝脏转移的迹象,但一直没有向外公布;2008 年夏天开始迅速变得极度消瘦,癌症复发,接受了肝脏移植手术后继续回到苹果工作;2011 年初,再次发现新肿瘤,医生告诉乔布斯,已经没有治愈的可能了,但他依然坚持出席了后面 iPad 2 和 iCloud 的发布会;2011 年 8 月,乔布斯宣布辞去苹果公司 CEO 职务;2011 年 10 月 5 日,乔布斯逝世。

Steve Jobs unveil iPad
发布第一代 iPad,2010 年。(Wikimedia Commons)

其实在这本书读到一半的时候,我几乎快要讨厌上乔布斯,我肯定不想有一个这样暴脾气的朋友或者领导。而读完后我又认为他是个极富魅力的人,如果一个人只“混蛋”一半的时间,那他是个确确实实的混蛋,如果能一直“混蛋”到最后并让人尊敬,那他是个了不起的人。乔布斯不止“混蛋”到了最后,还坚持热爱到了最后。


当然,如果只从一本书去认识一个人就太片面了,这只是作者沃尔特·艾萨克森(Walter Isaacson)笔下的乔布斯,进一步了解后发现似乎乔布斯身边的朋友都不太喜欢这本书。蒂姆·库克(Tim Cook)认为:“沃尔特·艾萨克森写的传记并没有真实全面地反映史蒂夫的性格,只是在重复一些陈词滥调,只反映出他性格里很小的一部分。看了那本书后,你会觉得史蒂夫就是个贪婪自私的利己主义者,但这不是事实。我永远都不想和书中描绘的那个人共事,生命太短了,不值得浪费。” 同时乔布斯在苹果的「精神伴侣」乔尼·艾维(Jony Ive)也表示只看了《史蒂夫·乔布斯传》的一部分内容,但其中的不准确性已足以让自己去讨厌这本书,“我对这本书的评价不能再低了。”

相比之下不少人认为布伦特·施兰和里克·特策利的《成为乔布斯》(Becoming Steve Jobs)这本书更能让人信服,至少乔布斯的亲人和朋友,以及苹果的同事们这样认为。Apple Books 服务的官推说:“《成为乔布斯》是最熟悉乔布斯的那群人唯一推荐的一本传记。” 接下来要做的事很简单,再把《成为乔布斯》这本书找来读一遍。

确实,在这本书中作者布兰特向我们展现了一个完全不同的乔布斯,他认为现在关于乔布斯的评价都是基于早期特别是在苹果的第一个 9 年里表现出来的行为,那时乔布斯刚成名,十分享受聚光灯下的感觉,沉浸与媒体的追捧中,但某些行为却粗鄙不堪而且常常失控,以至于去世后给大家留下了这样一个印象:史蒂夫是一位天才,在设计方面天赋过人,讲故事的能力超凡脱俗,可以产生“扭曲现实”的魔力;他就是个自以为是的混蛋,一味的追求完美,完全不顾及他人的感受;他觉得自己比任何人都聪明,从来不听取任何建议,而且从出生开始,就是天才与混蛋的结合体。 布兰特认为这些成见都不正确,乔布斯是一个更复杂、更有人性、更多愁善感,甚至更聪明的人。

他的书中更注重乔布斯的成长经历,对于乔布斯的琐事写得很精炼,不过也没有避重就轻的只谈论乔布斯的优点,他的那些坏毛病一样写得很详实:始终有着不良的习惯、持续的幼稚行为和冲动的个性,对于不喜欢的人和公司,他能憎恨几十年,即使你曾经是个功臣,当你无法再做出贡献时也会冷酷无情的裁掉你。但主要描写了他是如何从一个大家不愿相处人慢慢改变成一个真正的领袖的故事,他的故事不是成功的故事,而是成长的故事。同时他认为乔布斯不喜欢曝光自己的生活,其实他在生活上并不是媒体描写的和工作中一样不友好的人,通过多年来的接触近距离观察他的家庭生活,他就是一个普通人,善良、坦诚、有趣、富有爱心的普通人。

又比如乔布斯第一次确诊癌症时,艾萨克森的《史蒂夫·乔布斯传》写的是首次确诊癌症时乔布斯不听任何家人好友的劝诫,一意孤行地为自己制定了食疗计划,坚持通过素食、针刺、草药等自然疗法而不愿意手术,直到九个月后,他的肿瘤恶化,变得不可治愈。而布兰特进一步写了这其中的原因,当时乔布斯得的肿瘤很罕见,他的肿瘤学外科医生也承认以现有的数据看无法判断手术、化疗或综合疗法哪种效果更好,乔布斯去了很多地方看医生,并通过视频会议至少与 6 位美国权威癌症医生讨论过方案,最后才决定先尝试食疗或其它与他有机生活相融合的疗法,虽然后来证明这些方法并不奏效,病情恶化最大的原因还是因为他一开始坚持不做手术,但至少说明乔布斯并不是那种不可理喻的人。

Steve Jobs at Stanford
乔布斯在斯坦福进行毕业演讲,2005 年。(YouTube)

书中还有两个故事让我印象深刻。一个是当时作者布兰特想写一本书,邀请了乔布斯、比尔·盖茨、安迪·葛洛夫和迈克尔·戴尔进行一场联合访谈,其他三人因为乔布斯也参加很快答应了,前后商量了好几次后时间定在了 2008 年 12 月,可是准备了 6 个月后,在会议的前一周乔布斯联系到布兰特说由于身体很虚弱无法参加会议,因为此时他已经十分消瘦,无法通过食物吸收营养,必须专心治疗,还希望布兰特不要向别人透露他的身体情况。布兰特问乔布斯该如何向另外三人解释,沉默一会后乔布斯说到,“告诉他们我就是一个混蛋,也许他们心里就是这么想的,你就帮他们说出心声吧。”

另一个故事是在 2009 年 1 月,乔布斯终于被说服进行肝移植手术,但当时加州进行该手术需要进行排号,并且无法插队,更糟糕的是匹配乔布斯血型的肝源很少,能获得合适肝源的概率非常低,如果排到乔布斯已经过了 6 月了,而医生们认为他的肝在 4 月左右就会出问题。蒂姆·库克在探望乔布斯后悄悄去了一家很远的医院做了血型测试和活体移植检测发现奇迹般的匹配,第二天他去了乔布斯家提议捐赠自己的部分肝脏,但话还没说完就被乔布斯打断,“不行,我绝不允许,我永远不会接受!” 奄奄一息的乔布斯差点从床上跳下来吼道,这是库克在他们共事 13 年间为数不多被乔布斯吼骂的一次。最后乔布斯在 3 月底去田纳西州孟菲斯接受了手术,此时癌细胞已经扩散到了其它部位。


当然,也不能说《史蒂夫·乔布斯传》就写得不好,彷佛故意突出他的缺点和傲慢一样,很多事情我们无法知道真实情况如何,两本书结合来看能有更全面的认识。我也认为没有人真正的了解乔布斯,他的故事也不是几本书几十万字就能写明白的,但至少我更愿意相信他可能是个怪人,但绝不是混蛋。

最后,我想说的是乔布斯是一个先驱者,Mac 的图像界面和窗口模式打开了个人电脑的新时代,iPhone 的无键盘设计和多点触控重新定义了手机,iPod 和 iTunes 让音乐产业重获新生,处于这个时代的我们可能对这些历史性的变革无法感同身受,虽然很多功能并不是他第一个想出来的,也是“借鉴”了其他公司,但只有他能把这些整合起来创造出跨越时代的革命性产品。用时任美国总统奥巴马的悼文来说,“他改变了我们的生活,重新定义了整个行业,实现了人类历史上一个罕见的壮举:他改变了我们每一个人观察世界的方式。”

如此看来,我对于苹果封闭的抱怨也情有所原,乔布斯做出了自己的产品,而我自己进行了选择。如今很多人都认为苹果自从乔布斯后已经失去了灵魂,后来乔尼·艾维的辞职更是带走了苹果仅剩的一点创新能力。也不无道理,现在的新品总是在发布前就被曝光的差不多,越缺少惊喜就越发凸显这种“平庸”,但我认为乔布斯的精神并不是只有创新,每天都要想着如何改变世界,更多的还是如何将人文与科技相结合,去打造更好的产品。你不能一边看着苹果今天历史性的 3 万亿美元的市值一边说它已经没落,乔布斯的精神永远会是苹果的基石,在这基础上,还要大胆的开拓下去。

虽然我不认为自己会成为忠实的果粉,或许如今在选择产品时会多一些优先考虑的情怀加成,但内存和硬盘当金条卖这类做法我始终是不能认同的,不过有一点能肯定的是,你可能讨厌苹果,但不会讨厌史蒂夫·乔布斯。

与“锤哥”克里斯·海姆斯沃斯一起养生

limitless-with-chris-hemsworth

最近看了“锤哥”克里斯·海姆斯沃斯的纪录片 Limitless with Chris Hemsworth,由国家地理出品,主要讲述锤哥通过经历六个挑战,探索健康长寿、延缓衰老的奥秘,尝试通过养成一些习惯来降低某些疾病的患病风险以及最后对死亡的思考。这是一部有趣的纪录片,看完后也让我有写一篇文章记笔记的冲动。

* 文中所涉及到的“养生”内容均来自该纪录片,纪录片不等于科学,每个人身体情况不同,建议将本文当成民科来看。

Stress-Proof

stress-proof

Episode 1 // 锤哥在心理学家莫杜佩·阿基诺拉(Modupe Akinola)的指导下,通过防溺水训练、火灾演习等极端压力测试到最后尝试在 900 英尺(275 米)高的悉尼皇冠大厦顶部行走。

长期处于的高压的环境可能影响睡眠,导致高血压、糖尿病等健康问题。可以通过一些方法或者极限挑战来帮助自己控制压力,并对抗它对长期健康构成的风险。

Tips:

  • 每周练习三次冥想
  • 紧张时使用盒式呼吸法(吸气四秒—屏住四秒—吐气四秒—屏住四秒)
  • 感到压力时,尝试正向自我鼓励

Shock

shock

Episode 2 // 在极限运动员罗斯·埃吉利(Ross Edgley,这哥们是个狠人)的三天的寒冷适应训练下,最后完成在挪威北极圈游过冰冷刺骨的 250 码(230 米)峡湾的挑战。

为了延长寿命,你可能认为最好的办法是保持生活在舒适的环境中,但有研究指出,让身体接触极端温度能够降低感染疾病风险甚至增长寿命。通过降低我们对寒冷的反应可以重置我们的免疫系统,并降低老年时患心脏病和糖尿病等疾病的风险。而通过桑拿可以让体内产生热休克蛋白(heat shock proteins)来处理其它的废物细胞,可以将心血管疾病的死亡风险降低 50% 和将患阿兹海默症的几率降低 65%(可能并不严谨)。

Tips:

  • 每周三次早上泡冰浴
  • 定期 175 ℉(79.4 ℃)桑拿 20 分钟
  • 每次洗澡最后 30 秒冲冷水

Fasting

fasting

Episode 3 // 挑战在四天里不进食并在期间接受一系列的潜水训练,最后在潜海捕鱼的挑战中为自己赢得断食之后的第一餐。

我们的细胞需要葡萄糖等营养物质,但除了正常细胞外,那些老化、损坏的僵尸细胞也会吸收葡萄糖,同时还会释放有毒物质感染其他健康细胞加速老化过程。但研究发现当我们断食时,这些僵尸细胞将缺乏能量、丧失能力,而健康的细胞虽然也受到影响,但能转换成修复模式处理损伤、清理废物从而增强身体抗衰老能力。

Tips:

  • 每周至少三次中午前不进食
  • 每月一次 24 小时不进食
  • 每年一次断食四天…maybe

Strength

strength

Episode 4 // 再次与罗斯·埃吉利合作,通过三个月的训练,尽管期间因拍摄《雷神4:爱与雷霆》需要增重,最后还是坚持完成 100 英尺(30.48 米)的爬绳挑战。

肌肉中的纤维每次收缩都会释放化学物质,有研究表明这可能会帮助我们抵抗身体老化。同时随着年龄增长,细胞的“发电站”线粒体(mitochondrion)会衰退老化,而通过耐力训练能够刺激肌肉修复受损的线粒体,从而推迟老化的到来。

Tips:

  • 每周进行两小时耐力训练
  • 多进行攀爬,锻炼不同肌肉
  • 走出健身房进行每日训练

Memory

memory

Episode 5 // 锤哥被检测出有两组 apoE4 基因,导致他比普通人患上阿兹海默症的几率大 8-12 倍,在医生张莎(Sharon Sha)的建议下,和他的艺术家朋友奥蒂斯·霍普·凯里(Otis Hope Carey)在没有导航的情况下走进澳大利亚 Dunghutti, Anaiwan and Gumbaynggirr 等原住民地区,凭记忆最终抵达目的地,尝试通过方向训练降低患病几率。

随着年龄增长,人脑中的海马体会萎缩,这会提高阿兹海默症的患病风险。通过一些刺激活动可以使海马体变得更大和更健康。例如保持良好的睡眠,睡眠时我们的胶状淋巴系统(glymphatic system)会清除脑组织中的异常蛋白质和代谢物。此外,年纪大时跳坝坝舞也是一个不错的选择。

Acceptance

acceptance

Episode 6 // 穿上特制的老化模拟服装,在搭建的养老社区中体验人生的最后三天,通过与老人们交谈互动和一系列实验展开对衰老与死亡的思考。

对抗衰老和对死亡的最好方法是接受它。

一点感想

写文章的同时我也简单的搜索了下,那些 tips 确实能找到不少相关的研究和报告,但其中一些观点也有待验证,甚至可能伴随副作用,如果你想尝试,请结合自身实际情况考虑安排。

前面几集讲的是如何延缓衰老,虽然从小就被灌输洗澡水温要合适,一日三餐不能少,片中的观点和印象中的健康生活有很大冲突,不过我觉得改变和尝试总是有趣的。从大学毕业后就几乎没再运动过,想起那时也试过冬天洗冷水澡,一天吃一顿饭(当然,是被迫的),可以肯定的是你的身体比你的大脑想象中更强,突破自己更多的时候需要的是意志力。

或许我会挑几条试一试,可能只坚持一个月、一年或者可以一直坚持下去,倒不是想活得更久,只是如果能有一个更健康的身体不算坏事。我们绝大多数人不可能有机会像锤哥在片中一样拥有顶尖的团队和资源去深刻体验生命中的种种,但去跑个步、去冲个澡或者偶尔少吃两顿饭并不算难。

最后一集的话题有点深刻,关于死亡,即便年轻,我可能也已经想得够多了,每次思考的结论总是和片中的一句话差不多:

Most of us are less afraid by our own frailty, our own deaths than we are at the deaths of people we care for.

关于死亡更多的探讨,写了也没多大意义,活在当下,就此打住。

总的来说是一部不错的纪录片,觉得靠谱可以当成纪录片看,不靠谱也可当成 vlog 或是综艺看,锤哥肯定是真的投入了并思考了不少,不然也不会宣布暂时息影,从这一点来说,不管哪种心态看都多少都能有点收获,值得推荐。

逃离爆炸的信息

这里谈到的爆炸的信息,是指如今中文互联网上的“主流”信息。一直都对网上冲浪这件事有着复杂的情绪,这里不谈论其好坏与意义,总之我应该算是个喜欢网上冲浪的人,但长期以来都有意识去尝试远离“主流”信息。

Continue Reading

你想活出怎样的人生

how-do-you-live

上个月突然想起吉卜力今年要出电影这回事——你想活出怎样的人生。作为吉卜力粉丝,自然是相当期待,可能是“最后一次”复出的宫崎骏老爷子,在耄耋之年创作的一部以人生为主题的电影会是什么样的呢?于是去书店淘了本“原作”,这周末终于想起拿出来读一读。

Continue Reading

重温《龙珠》动画

dragon-ball

最近终于完成以前立下的小目标,把《龙珠》动画重温了一遍,小时侯在电视上总是看得断断续续的,对于龙珠更多的记忆是放学后跑去游戏厅和小伙伴一起搓手柄,这次终于从头到尾的热血了一遍。不得不说,很难想象这是 80 年代的作品,能在动漫历史上保三争一是有原因的。

Continue Reading

从域名注册商到 DNS 服务,找到自己的组合

choose-domain-registrar-and-dns

我玩过的域名不算多,不到 50 个,和域名大佬们完全不能比,但也交了不少学费,现在除了剩下几个砸手里的基本退烧了应该,按照博客惯例,是时候水篇文章阶段总结下自己的经验了。本文将聊聊我用过的部分国外域名注册商和 DNS 解析服务。顺便穿插两个小剧场,纸上谈兵一下成为 ICANN 认证注册商要花多少钱以及关于 CNAME Flattening 和 DNSSEC 的简单介绍与看法。

Continue Reading

如何提高用户网页阅读体验

improving-online-reading-experience

如何提高网页中文字的阅读体验,这是可以写一本书的问题,仅仅是粗略的搜索一番,也是众说纷纭。我倒认为这是一件很主观的事,即使是同样的排版设置,在不同的设备,不同的系统甚至不同的浏览器中表现都不一样,也可以说在网页设计中排版是极其自由的,加上每个读者阅读喜好不同,关于如何提高用户阅读体验这件事,自然没有统一的标准。

Continue Reading

从 Typecho 迁移到 Hugo

typecho-to-hugo

在上一篇文章 个人博客的最终归宿是静态网站吗 决定从 Typecho 迁移到 Hugo 后,为了保持博客样式不变,从 hugo new theme 开始,从零写了一个主题,经过这一周来 50+ 小时的努力,终于能 rm -rf typecho 后将网站重新上线了。

总体来说,样式还原度 98%,功能还原度 90%,整个过程主要分为数据迁移和主题制作两大部分,本文将简单记录下实现过程。

Continue Reading

❌