普通视图

发现新文章,点击刷新页面。
昨天以前atpX

承认的勇气

作者 ATP
2024年10月3日 01:09

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

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

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

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

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

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

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

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

作者 ATP
2024年8月22日 01:29

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

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

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

我的博客写作流程

作者 ATP
2024年8月2日 22:30

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

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

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

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

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

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

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

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

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

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

网站新增 Misc 页面

作者 ATP
2024年8月1日 01:08

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

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

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

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

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

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

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

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

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

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

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

擅长对线的鲍勃

作者 ATP
2024年7月26日 17:05

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

「你好,外卖到了~」

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

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

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

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

下一个。

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

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

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

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

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

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

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

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

······

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

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

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

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

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

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

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

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

「是的,你是对的。」

煞笔!CNMD!

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

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

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


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

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

在 Chroot 环境下使用 Rsync 同步

作者 ATP
2024年5月6日 20:11

我平时在备份或者同步服务器文件时一般会用到 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

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

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

当我玩博客时我在玩什么

作者 ATP
2024年3月13日 04:35

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

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 的碳基生命。好在除了考虑这些无聊的意义之外,还可以跳舞。

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

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

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

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

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

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

作者 ATP
2024年3月10日 19:07

我的博客似乎一直都缺少一个在多数中文博客上不谋而合的页面——友情链接(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 照片测试)

作者 ATP
2024年2月18日 23:06

最近对小熊猫(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 支持了吗

作者 ATP
2024年2月1日 01:16

关于 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 服务

作者 ATP
2024年1月25日 19:03

众所周知,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,这里不再赘述。

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

谈谈读书与消遣

作者 ATP
2024年1月18日 20:55
leisure-reading

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

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

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

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

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

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

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

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

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

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

2023 年终总结

作者 ATP
2023年12月26日 19:30

如果说之前是消失的三年,那接下来的 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 年没生过病,生活上也没有受大锤,还能继续生猛,也希望大家都能一直生猛下去。最后,预祝大家新年快乐 🎉。

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

西安两日游

作者 ATP
2023年11月30日 20:08

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

Day 1

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

肉夹馍

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

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

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

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

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

大唐不夜城

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

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

大唐不夜城夜景

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

大雁塔音乐喷泉

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

Day 2

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

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

游客观赏错金杜虎符

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

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

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

志亮灌汤蒸饺

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

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

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

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

西安钟楼
西安钟楼。

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

Day 3

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

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

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

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

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

太液池

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

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

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

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

从 AirPods「升级」到 EarPods

作者 ATP
2023年11月6日 19:55

前段时间苹果秋季新品发布会推出了 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

作者 ATP
2023年10月20日 22:34
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 使用感受

作者 ATP
2023年10月5日 00:40

手里用了四年多的 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 二次验证存放在密码管理器里

作者 ATP
2023年9月18日 02:09
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,你都不信任密码管理器了,那为什么还要使用它呢?别让自己一直处于如果被三体人破解了怎么办的忧虑中,在够用以及一定强度的安全保证下,更加便捷也是不错的选择,不过如果你也纠结的话,我肯定是建议分开存放的。

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

❌
❌