阅读视图

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

小城与确定性的墙

the-city-and-its-certain-walls

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

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

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

the-city-tweet

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

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

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

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

一只特立独行的猪

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

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

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

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

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

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

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

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 中找到更多的乐趣,也欢迎我互动 (@atp@nichijou.org)。

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

承认的勇气

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

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

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

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

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

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

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

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 上搜一下相应链接说不定能在远古记录中找回来。

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

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

我的博客写作流程

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

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

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

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

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

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

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

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

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

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

网站新增 Misc 页面

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

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

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

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

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

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

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

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

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

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

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

擅长对线的鲍勃

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

「你好,外卖到了~」

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

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

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

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

下一个。

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

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

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

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

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

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

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

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

······

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

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

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

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

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

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

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

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

「是的,你是对的。」

煞笔!CNMD!

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

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

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


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

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

在 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,也能节省一些资源占用,总的来说更值得推荐。

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

再见 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 从摘要输出改为了全文输出。不过这里的阅读体验应该也过得去,也希望你能偶尔打开网页来这里逛一逛 <(´= ω =`)>

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

当我玩博客时我在玩什么

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

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 作为备用。

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

❌