<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://blag.dsstudio.tech/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
    <title>豆沙工作室</title>
    <link>https://blag.dsstudio.tech/</link>
    <atom:link href="https://blag.dsstudio.tech/feed.xml" rel="self" type="application/rss+xml" />
    <description>一人多役的艺术</description>
    <language>zh</language>
    <follow_challenge>
        <feedId>107427218643910656</feedId>
        <userId>LhUoD4sIRBhBBh14M3JtnBuu2peQk7jy</userId>
    </follow_challenge>
        <item>
<title>只能作为光盘手册存在的书籍</title>
<link>/archives/books-that-are-manuals-of-a-disc.html</link>
<description><![CDATA[<p>最近（一年之内）我购买了《<a href="https://item.taobao.com/item.htm?id=666167202310&mi_id=0000LUCXW7Fk58zQIyDk9w7L_aavyEgmCXBakxJI6z3tnn4&skuId=5940363294108">来自中年玩家的十二封信</a>》，哦不，是《游戏人讲解73》这张光盘。</p><p><div class=more></div><p>当然，我在这里并不是要讨论这本书——抱歉，这张光碟本身的内容；我更感兴趣的是怎么给这份材料写一份引用。</p><p>我明白这是发行的一种妥协，尽管书本身的内容理论上没有任何不妥，但靠着既有的音像公司出版，以光盘加附带手册的形式发行这份资料，似乎比单出一本书要容易或者要便宜。</p><p>但这么做却打了我一个措手不及：和其他带着完整信息的资料不同，我没有办法只凭手头的东西给这样的数据添加引用条目。<a href=/pages/4633>引用格式</a>中一眼能够确定的就是 <code class=prettyprint>V/CD</code> 的作品类型信息和「广东嘉应音像出版有限公司」的出版方信息，剩下的都很难办。</p><p>首先需要确定这张光盘的作者是谁。打开<a href=https://pdc.capub.cn/>国家版本数据中心</a>，输入光盘上的 ISBN 号码查询，得到的结果是：</p><table><thead><tr><th>键</th><th>值</th></tr></thead><tbody><tr><td>类型</td><td>音像电子</td></tr><tr><td>选题名称</td><td>游戏人讲解73</td></tr><tr><td>ISBN</td><td>978-7-88553-357-1</td></tr><tr><td>出版单位</td><td>广东嘉应音像出版有限公司</td></tr><tr><td>著作权人</td><td></td></tr></tbody></table><p>这可不是我忘填了著作权人，而是它确实是空白。那么根据规则，这份数据「来自某个实体，如公司、政府或其他团体，使用实体名称」，得到的结果是 <code class=prettyprint>UCG</code>. 我们同时也拿到了这份光盘的正式名称：《游戏人讲解73》。</p><p>接下来是确定版本信息。光盘本身和「手册」上都没有任何出版时间相关的信息。好在光盘内容的开场词中，提到了这是 UCG 2022 年的开年之作，那么就只能精确到年了。</p><p>由于之前从未引用过离线的音视频文件，定位信息需要打一个补丁。目前决定通过时间戳来作为定位信息，毕竟书是这样定位的，影音光盘这样定位应该问题也不大。</p><p>对于附赠「手册」的引用信息相对而言就好填一些，至少作者名称那一栏除了 <code class=prettyprint>UCG</code> 以外，还可以写一下是哪十二位同志撰的稿；而且作品名称也可以直接写成<code class=prettyprint>来自中年玩家的十二封信</code>。出版信息、版本信息和 ISBN 号则继承自光盘本身；定位信息则沿袭之前的格式放页码即可。</p><hr><p>之后我们或许会需要引用更为复杂的数据，比如游戏软件本身。尽管现在的引用格式允许将其作为「可执行过程」引用，但是我们能接受「无版本信息」以及「非公开获取资料」么？数字发行的内容或许可以按在线数据整理，那卡带又该怎么办呢？</p><p>希望之后我们能想出来一个好的答案吧。</p>]]></description>
<pubDate>Mon, 06 Apr 2026 17:48:22 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/books-that-are-manuals-of-a-disc.html</guid>
</item>

    
        <item>
<title>航天飞机和马屁股的关系</title>
<link>/archives/the-space-shuttle-and-a-horses-arse.html</link>
<description><![CDATA[<p>这篇文章是由 Art 发表在 Physics Forums 的一篇帖子的翻译，其内容因个人恶趣味与原文有所出入。如需引用或考证请<a href=https://www.physicsforums.com/threads/the-link-between-a-horses-arse-and-the-space-shuttle.162604/>查看原文</a>。当然，原文本身其实没必要花心思去考证，因为<a href=https://www.snopes.com/fact-check/railroad-gauge-chariots/>它其实经不起推敲</a>，大伙看一乐就好。</p><hr><p>你是否听过这句话：“我们一直就是这么办事的”？</p><p>美国国标指定铁轨轨距为 4 英尺又 8.5 英寸。这个数为什么有零有整的？因为英国的铁路标准就是这样的。</p><p>那为什么英国铁路又选了这么一个奇怪的数呢？因为建造铁路的这批人之前是建有轨电车的，所以他们就直接沿袭了有轨电车的标准。</p><p>那有轨电车的标准又是从哪来的呢？答案是建造有轨电车的人使用了造马车的工具，而马车的轮距就是这么宽。</p><p>那马车的轮距又是怎么得出的呢？马车的轮距受制于铺设的车辙的间距。如果马车的轮距和车辙对不上的话，轮子就很容易被压坏，或者干脆就无法通行。</p><p><a href=https://imgchr.com/i/peCoTQs><img alt=peCoTQs.jpg src=https://s41.ax1x.com/2026/03/06/peCoTQs.jpg></a></p><p>图 1 铺设的车辙<sup class=footnote-ref><a href=#fn1 id=fnref1>[1]</a></sup></p><p>那么，这路又是谁铺的呢？是罗马帝国铺设的。罗马帝国是首个在欧洲（以及英格兰）建设了长距离公路的政权，这些公路铺好之后就一直在用；而其车辙标准则承袭自罗马战车的轮距。为了能在公路上通行，所有人就纷纷效仿采用了相同的轮距。</p><p>所以说，美国铁路的 4 英尺又 8.5 英寸的规矩来自于罗马帝国的战车轮距的尺寸。</p><p>当你拿到一份标准，纳闷这狗屁标准是那个马屁精拍脑袋的馊主意的时候，你可能一点没错——因为罗马战车的车辙宽度就是为了能正好塞下两个马屁股定下的！</p><p>哦对了，还有大标题的事情。航天飞机在发射台上准备发射的时候，你会看到有两个大火箭连接在主油箱的两侧。这两个大火箭叫固体火箭推进器 (SRB).</p><p>这些推进器是在犹他州的 Thiokol 公司制造的。设计这个推进器的工程师当然希望能把它做得更大一些以便携带更多燃料，但是由于推进器需要通过铁路运输，而隧道的大小又是照着铁路的宽度挖的，所以这个推进器就不得不按照铁路的承载要求进行设计——也就是约摸两个马屁股的宽度。</p><p>所以说，航天飞机——这个可以称得上是世界上最先进的交通系统——它的主要设计指标受制于两千年前的一匹马的屁股有多宽。</p><hr class=footnotes-sep><section class=footnotes><ol class=footnotes-list><li class=footnote-item id=fn1><p>By Dvortygirl - Own work, CC BY-SA 4.0, <a href="https://commons.wikimedia.org/w/index.php?curid=3490257">https://commons.wikimedia.org/w/index.php?curid=3490257</a> <a class=footnote-backref href=#fnref1>↩︎</a></p></li></ol></section>]]></description>
<pubDate>Sat, 28 Mar 2026 10:34:30 +0000</pubDate>
<dc:creator>Art (www.physicsforums.com)</dc:creator>
<guid>/archives/the-space-shuttle-and-a-horses-arse.html</guid>
</item>

    
        <item>
<title>Get an iPod</title>
<link>/archives/get-an-ipod.html</link>
<description><![CDATA[<p>关于一种可爱，一种怀旧，一种对小时候的自己的补偿，和一次对自己数字生活的质询。</p><p><div class=more></div><p>最近心血来潮，在某鱼上面买了一颗 iPod nano 6. 一方面是因为果子全家桶我现在算是集齐一整套了，所以新的电子小垃圾还是选择苹果是一个几乎自然的事情——这个「不用苹果」的 flag 现在是狠狠地被扯了下来；另一方面是两张毛爷爷这个价位，很难买到其他看起来做工精良而且音质合格的产品了。我个人比较喜欢小而轻薄形态的电子产品，而目前市面上其他的播放器要么是一块砖头，要么是看起来就让人感觉遗憾的形制。</p><p><a href=https://imgchr.com/i/pZY90Cn><img alt=ipod-nano-6.jpg src=https://s41.ax1x.com/2025/12/27/pZY90Cn.jpg></a></p><p>iPod nano 6 是整个 iPod 系列中尺寸最小的，也是唯二没有转轮的。没有转轮的 iPod 还能算是 iPod 么？我不知道。但我知道的是这个尺寸、这个形态、这个做工在我看来简直是完美！它几乎可以用「可爱」来形容。要知道，很少有电子产品可以是「可爱」而不「矫揉造作」的。</p><p>也许我也不可避免地陷入到了玫瑰色的过去当中，我的审美还停留在乔布斯时代的苹果，还停留在那个许下了美好未来诺言的时代中。当我把玩着 iOS 4 风格的界面的时候，听着耳机里播着的 Owl City 的 Firefiles, 我感觉自己又回到了 2010 年。</p><blockquote><p>I'd like to make myself believe<br> That planet Earth turns slowly</p></blockquote><p>和那时候的孩子们一样，我也曾经想过拥有一台 iPhone, 或者 iPod touch. 但也和那时候的大部分孩子一样，这个想法最终是停留在了想法阶段，因为这东西真不是靠饿几天就能从饭钱里扣出来的，而我的学业表现也还没能好到让父母愿意花费几个月工资搞一个「对学习一点帮助都没有」又死贵的玩意回来。</p><p>或许这种对于小形制的偏爱来自于那个小小的我。对于那个还在初中摸爬滚打的我，也许一部 iTouch 可以把我从受欺负的泥潭里拯救出来——或者陷入得更深。但现在的我总算是有能力给自己拿下这个想了十几年的潮品，这又何尝不是一种「延迟满足」。</p><p>但说了这么多，有一个关键问题仍然需要解释：马上都 6202 年了，买一个什么功能都没有只能放音乐的 MP3 播放器，是否也是一种罩着怀旧的面纱、打着童年的借口的消费主义作祟？</p><p>和 iPod touch 不同，iPod nano 有且只有一个主要功能：放音乐。它没有办法装任何软件进去，而且 6 代 nano 也没有视频播放能力。它也没有办法联网，没有 WiFi 更不能插卡，所以你如果需要传音乐进去，只能把它插到电脑上，然后用 iTunes 推数据进去；是的，你还必须用 iTunes, 它还不能直接拖文件进去。就连放音乐，它对音频格式的支持也很有限：AAC, ALAC, MP3, WAV. 是的，FLAC 是不支持的，你还得手动把 FLAC 转换成 ALAC 才行。智能推荐之类的高级功能自然更是没有。音质来说，iPod nano 也绝非什么 HiFi 播放器，它的耳机孔的驱动能力最多只能带动 32 欧姆的耳机。也许唯一的救赎也就是它的 24 小时连续播放的长续航，但长续航本身很多也要归功于缺少射频组件和外放，换其他更便宜的播放器也能实现。</p><p>按照现在的标准来说，这就是教科书级的「电子垃圾」。两百块干点什么不好？</p><p>我最近开始重新购买 CD.</p><p>是的，购买了 CD 意味着我需要买光驱，然后需要自己换碟或者自己把光盘里的数据拷出来，如果想要通过网络放音乐的话还得自己架设私有的音频流媒体服务器，自己想办法打洞，还没有海量曲库能随意切换，也没有智能推荐之类的功能。隔壁玩黑胶的哪怕家里连唱机都没有都还能显摆一下自己「走在时代前沿」，当一把潮哥潮姐；而 CD 甚至连这种社交属性都没有。一张正版碟花大几十块钱，一张海淘碟更是上百，买两三张就够你订一年的流媒体会员了，何必呢？</p><blockquote><p>Does one need to smell flowers more quickly, more efficiently?</p><p>-- MecklesFrog, <a href="https://www.youtube.com/watch?v=a8iOJkqYNpc">The Importance of Inconvenience</a></p></blockquote><p>选择 CD 并不是因为 Spotify 之流不香，而是因为我觉得，有些时候真正的「拥有」我喜爱的内容是一个更好的选择；而且我实在是信不过「购买数字单曲」这种事情——毕竟如果我购买了这个单曲，但它还是锁在一层 DRM 后面，没有密钥的我，不还是等于从平台无限期地赁了这首歌出来么？现在可没有多少平台再提供无 DRM 版本的无损质量音乐购买了。</p><p>购买了 CD 的我，可以把它塞到任何一台电脑的光驱里，然后就能直接播放<sup class=footnote-ref><a href=#fn1 id=fnref1>[1]</a></sup>。我不需要登录什么账号，也不需要从谁那获取密钥，也不需要下载特定的播放器。更有意思的是，我可以直接把光盘内容无损地复制到我的电脑里，再复制到我的其他播放器或者刻录到其他光盘中。只要我不和其他人分享复制的内容，这么做是完全合情合理合法的。不会有一天，因为平台没有续期版权，或者艺人和平台闹掰，或者更干脆的被封杀，我就再也听不到这首歌了。</p><p>iPod, 或者说任何一个纯粹的音乐播放器，也正好契合了这种「朴素的拥有」的概念。更确切地说，这种纯粹的播放器带来的是一种始终如一的态度：你所喜爱的东西不会随着外界的变化而变化，这首歌无论何时都是一样的好听。而现在的我们正缺这种对于稳定的许诺。</p><hr class=footnotes-sep><section class=footnotes><ol class=footnotes-list><li class=footnote-item id=fn1><p>除了某索的部分型号碟片，但某索的这种操作严格意义上是不遵循 CD 标准的，所以不算数。 <a class=footnote-backref href=#fnref1>↩︎</a></p></li></ol></section>]]></description>
<pubDate>Sun, 28 Dec 2025 09:22:02 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/get-an-ipod.html</guid>
</item>

    
        <item>
<title>从鸿蒙系统中拉取蓝牙 HCI 日志</title>
<link>/archives/fetching-hci-log-from-harmonyos-3.html</link>
<description><![CDATA[<p>环境是鸿蒙 3.0 系统，后续版本的行为可能不一样。设备端 HCI 日志目录是 <code class=prettyprint>/data/log/bt</code>.</p><p><div class=more></div><p>电脑端准备好 <code class=prettyprint>adb</code> 工具或者 <code class=prettyprint>hdc</code> 工具。</p><ol><li>打开手机的开发者模式，此处不再赘述</li><li>进入设置 - 系统和更新 - 开发人员选项，打开「开启蓝牙 HCI 信息收集日志」开关</li><li>关闭再打开蓝牙以应用更改，此时蓝牙 HCI 日志会在后台自动收集</li><li>还是在开发人员选项中，打开 USB 调试</li><li>手机插电脑，电脑端键入以下指令：<pre><code class="hljs language-text">adb pull /data/log/bt
</code></pre> 或者<pre><code class="hljs language-text">hdc file recv /data/log/bt
</code></pre> 如果手机端提示是否允许调试，点击记住并允许</li><li>HCI 日志会被保存在命令行工作目录下的 <code class=prettyprint>bt</code> 文件夹中</li><li>打开 WireShark, 将产生的 <code class=prettyprint>.log</code> 文件拖进去即可查看 HCI 日志</li><li>记得关掉「开启蓝牙 HCI 信息收集日志」开关，这玩意产生日志的量还是挺大的</li></ol>]]></description>
<pubDate>Wed, 26 Nov 2025 11:18:09 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/fetching-hci-log-from-harmonyos-3.html</guid>
</item>

    
        <item>
<title>第二类擦除</title>
<link>/archives/type-ii-erasure.html</link>
<description><![CDATA[<p>将一个东西的过去、现在和未来抹去，叫它遗忘也好，叫它封杀也罢，或许还是一件符合直觉的事情。而将过去抹去，空留现在和未来呢？</p><p><div class=more></div><blockquote><p>冰雪早已覆盖我的足迹<br> 远方的炊烟，摇曳温暖的召唤<br> 风儿无法吹断我回望的视线<br> 家园好像永远征途漫漫</p></blockquote><p>我不知道这是否是一种非常奇怪的情况，但我感觉我好像没有一个「故乡」。当然，我确实有籍贯、有身份证、有居住证、有各种一切的把我的存在和某个地址关联起来的文书，但我却没有感到我「属于」哪里。当我在<a href=/archives/4840.html>前文</a>里提到「有一代人没有历史、有一代人没有『根』」的时候，我说的就是我自己。</p><p>这种对于归属的迷茫现在愈发强烈。我开始质疑自己是否真的在那座小城里度过了我的童年，抑或是我只是梦到了自己曾消散了青春岁月。这种毫无来由的质疑并不能通过浏览过去的照片和检查文档来消灭，甚至和好友和父母对话都无济于事。这种强烈的脱离感虽然只是如潮水一样时隐时现，而且除了让我在床上翻来覆去以外暂时还没有影响到我的生活；但是这种如同能听到墙里传来窸窣声的感觉终究是不好受。</p><p>——如果我明天就死了，安静地死在这八平米的出租屋里，或许也算是一种落叶归根？</p><p>这种迷茫来自于失去锚定。人的记忆毕竟不是电脑上的文件：记忆需要某种锚定机制，通过一种特殊的感官刺激——无论是视觉、听觉、触觉或者嗅觉，来将某段故事或者某种情感定格在脑海。我的记忆里的故乡早已消失了，它已经在城建的过程中被修改得面目全非，然后彻底崩溃了。</p><p>这也许是我这种一开始就生活在城市的孩子所必须要面临的困境：它的变化实在是太快了，不到一代人的时间足以让一切天翻地覆。所有在童年记忆里的场景，现在都已消逝在历史的长河当中了。新的大楼取代了旧的家属院；新的高架盖过了旧的小车道。城市的确因发展变得更好了，这不容否认。</p><p>但我的过去便永远地遗失了。</p><p>当高大的法桐被高架桥所取代的时候，我便知道或许我再也没法回到和朋友们漫无目的地蹬自行车的夜晚了——因为高架他妈的只能上机动车。</p><p>这种割裂并不仅仅是在城建方面。</p><p>在我上幼儿园的时候，流行的是普通话教育。当然，现在的教育也是基于普通话的教育，但是我那时候会更强调这一部分。或者文雅点说，那时候的我们致力于打通南腔北调，全国上下都会说一种通行语言，这有益于社会整体的发展。</p><p>城市的高度统一化在这里就体现出来了：所有人都会讲普通话，而且开口就是普通话。你需要先讲「土话」，对方才会回「土话」。所以，小时候的我从来没接触过我的家乡话。直到我过了语言定型期之后，才通过老家的人有意义有语境地接触家乡话。</p><p>学英语的各位大概知道「哑巴英语」是怎么一回事，而学家乡话的我也差不多基本就是个小「哑巴」。</p><p>好吧，其实还有一个蛮有趣的事情，就是那时候幼儿园并不限制教学内容。所以那个时候各个幼儿园就玩得相当超前，比如小学初步课程就会放到幼儿园去教，我那时候甚至会上英语课。虽然（感谢）这些教学并不考核结果，但我几乎可以肯定现在我对于英语表述的偏好来自于这一时段的高强度暴露。</p><p>普通话的最大特点，就是它没有什么特点。它被设计成为每个人张嘴就是差不多一个样，最多就是有所谓的「南方口音」和「北方口音」的区别。而这个区别实际上仍然来源于其母语口语所带来的影响（主要是元音发音偏好和语序偏好）。</p><p>如果一个人的母语口语本身就是没有特点的普通话呢？</p><p>所以，这种文化的烙印，于我便也被抹去了。</p><p>从此，我便失去了历史。虽然出生在这里，却并不属于这里；虽然受教于这里，却并不遵从于这里；虽然工作于这里，却并不贡献于这里；虽然终结于这里，却并不归根于这里。我离开这里时，不带有一丝怀念；我回到这里时，不感到一点慰藉。</p><p>这在现在也可以说是一种优势。一种不需要考虑太多，说卷铺盖走就可以立刻走人的灵活性在这个愈发「活跃」的时局中，或许是一个必选项。</p><blockquote><p>漂泊者的家到底在哪一边？<br> 回首故乡遥远，抬头前路依旧漫漫<br> 纵然只有倒下才是终点<br> 我只有未来没有从前</p></blockquote><p>怀古伤今一直以来都是一种「时尚单品」，可以说是从周朝开始就是潮流了。我不认为过去一定比现在更好，但这种对于「我」的擦除，却实在伤透我心。</p>]]></description>
<pubDate>Sun, 26 Oct 2025 17:37:21 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/type-ii-erasure.html</guid>
</item>

    
        <item>
<title>迁移 FreshRSS 的数据库</title>
<link>/archives/migrating-database-for-freshrss.html</link>
<description><![CDATA[<p>比预想中的要简单不少。</p><p><div class=more></div><p><a href=/archives/the-right-to-be-forgotten.html>刚刚上期</a>我提到了用 FreshRSS 订阅各种信息而且永不清理订阅。这套系统在刚设置好的时候还挺好用，但是随着时间流逝，积攒的数据越来越多，FreshRSS 的响应速度就越来越慢。</p><p>这是因为默认情况下，FreshRSS 使用 SQLite 作为数据库后端。尽管 SQLite 本身作为单文件数据库的性能是充分的，但是由于 RSS 阅读器天然对并发写入有一定要求（主要是用于文章拉取和已读标记），SQLite 这种全局锁数据库在处理 1e5 条数据的时候就明显感觉到吃力了。不过直觉上，SQLite 处理 1e7 级别的数据应该是够用的，或许这和整套系统是运行在机械硬盘上也有一定关系。</p><p>FreshRSS 支持 PostgreSQL 作为数据库后端。当然，切换数据库后端肯定不是一个在管理页面上点两下鼠标就可以解决的事情。<a href=https://github.com/FreshRSS/FreshRSS/blob/edge/Docker/README.md#migrate-database>之前读到的文档</a>给出的答案基本上就是：你在安装的时候选了什么数据库就只能用什么数据库；想要切换的话就是全量导出、重装 FreshRSS 再全量导入。</p><p>全量导入导出这种事情一听就头大。所以我就本着「没坏就别去修」的心态又硬撑着用了几个月。但最近它已经迟缓到 Nginx 都开始不耐烦地报 <code class=prettyprint>504 Gateway Timeout</code> 了。好吧，既然坏了，就可以开始动手修了。</p><p>治标的方法当然是配置 Nginx 等待更长时间，但这样做肯定是毫无意义的，换数据库肯定是要做的。再次<a href=https://freshrss.github.io/FreshRSS/en/admins/05_Backup.html>查阅了文档</a>之后发现：<strong>全量导入导出是 FreshRSS 自带的功能</strong>；而且你也<strong>不需要重装 FreshRSS, 只需要修改一下配置文件就可以</strong>。</p><p>顺便抱怨一句：谁家好人会把数据库迁移操作放在「备份」章节里啊！</p><hr><p>简单地改一下 <code class=prettyprint>docker-compose.yml</code>:</p><pre><code class="hljs language-yaml"><span class=hljs-attr>services:</span>
  <span class=hljs-attr>freshrss:</span>
    <span class=hljs-attr>restart:</span> <span class=hljs-string>unless-stopped</span>
    <span class=hljs-attr>logging:</span>
      <span class=hljs-attr>options:</span>
        <span class=hljs-attr>max-size:</span> <span class=hljs-string>10m</span>
    <span class=hljs-attr>ports:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-number>19180</span><span class=hljs-string>:80</span>
    <span class=hljs-attr>environment:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>TZ=Asia/Shanghai</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>CRON_MIN=1,31</span>
    <span class=hljs-attr>volumes:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>freshrss_data:/var/www/FreshRSS/data</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>freshrss_extensions:/var/www/FreshRSS/extensions</span>
    <span class=hljs-attr>container_name:</span> <span class=hljs-string>freshrss</span>
    <span class=hljs-attr>image:</span> <span class=hljs-string>freshrss/freshrss:1.26.3</span>
    <span class=hljs-attr>depends_on:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>postgres</span>
  <span class=hljs-attr>postgres:</span>
    <span class=hljs-attr>image:</span> <span class=hljs-string>postgres:16-alpine</span>
    <span class=hljs-attr>restart:</span> <span class=hljs-string>always</span>
    <span class=hljs-attr>container_name:</span> <span class=hljs-string>freshrss_postgres</span>
    <span class=hljs-attr>shm_size:</span> <span class=hljs-string>128mb</span>
    <span class=hljs-attr>environment:</span>
      <span class=hljs-attr>POSTGRES_USER:</span> <span class=hljs-string>postgres</span>
      <span class=hljs-attr>POSTGRES_PASSWORD:</span> <span class=hljs-string>postgres</span>
      <span class=hljs-attr>POSTGRES_HOST_AUTH_METHOD:</span> <span class=hljs-string>password</span>
    <span class=hljs-attr>volumes:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>/mnt/volume:/mnt/volume</span>
<span class=hljs-attr>volumes:</span>
  <span class=hljs-attr>freshrss_data:</span> {}
  <span class=hljs-attr>freshrss_extensions:</span> {}
</code></pre><p>因为数据库是只能在容器组内访问的（它没有暴露 <code class=prettyprint>ports</code>），我就懒得改用户名密码之类的了。</p><p>重新部署容器组：</p><pre><code class="hljs language-sh">docker compose up -d
</code></pre><p>然后进入 FreshRSS 容器中：</p><pre><code class="hljs language-sh">docker <span class=hljs-built_in>exec</span> -it freshrss sh
</code></pre><p>执行一下备份操作：</p><pre><code class="hljs language-sh">./cli/db-backup.php
</code></pre><p>注意到官方文档里有 <code class=prettyprint>cd /...</code> 的一个操作，在容器中这么做是不必要的。</p><p>FreshRSS 的 Docker 镜像非常精简，以至于它里面没有任何编辑器可用，甚至连 <code class=prettyprint>ed</code> 这种<a href=https://www.gnu.org/fun/jokes/ed-msg.en.html>标准编辑器</a>都没有。所以你会需要用 <code class=prettyprint>apt</code> 单独装一个你喜欢的编辑器。记得先 <code class=prettyprint>apt update</code> 更新一下软件包数据库。</p><p>注意到这里更新的数据以及安装的编辑器会在下一次重建 FreshRSS 容器（如更新 FreshRSS 或者重启 Docker 服务）后丢失。所以如果后续还有修改 FreshRSS 配置的需要的话，你需要每次操作前都检查编辑器在不在。</p><p>编辑 <code class=prettyprint>./data/config.php</code>, 修改一下数据库配置：</p><pre><code class="hljs language-php"><span class=hljs-meta>&lt;?php</span>
<span class=hljs-keyword>return</span> <span class=hljs-keyword>array</span> (
  <span class=hljs-comment>/* --8&lt;- SNIP --8&lt;- */</span>
  <span class=hljs-string>'db'</span> =>
  <span class=hljs-keyword>array</span> (
    <span class=hljs-string>'type'</span> => <span class=hljs-string>'pgsql'</span>,
    <span class=hljs-string>'host'</span> => <span class=hljs-string>'postgres'</span>,
    <span class=hljs-string>'user'</span> => <span class=hljs-string>'postgres'</span>,
    <span class=hljs-string>'password'</span> => <span class=hljs-string>'postgres'</span>,
    <span class=hljs-string>'base'</span> => <span class=hljs-string>'postgres'</span>,
    <span class=hljs-string>'prefix'</span> => <span class=hljs-string>''</span>, <span class=hljs-comment>// 因为这个 PostgreSQL 实例是 FreshRSS 独享的，故不需要表前缀</span>
    <span class=hljs-string>'connection_uri_params'</span> => <span class=hljs-string>''</span>,
    <span class=hljs-string>'pdo_options'</span> =>
    <span class=hljs-keyword>array</span> (
    ),
  ),
  <span class=hljs-comment>/* -->8-- SNIP -->8-- */</span>
);
</code></pre><p>然后恢复之前的备份即可：</p><pre><code class="hljs language-sh">./cli/db-restore.php --delete-backup --force-overwrite
</code></pre><p>切换到 PostgreSQL 之后，FreshRSS 的响应速度有显著的提升。</p>]]></description>
<pubDate>Mon, 20 Oct 2025 02:35:57 +0000</pubDate>
<dc:creator>dousha</dc:creator>
<guid>/archives/migrating-database-for-freshrss.html</guid>
</item>

    
        <item>
<title>欸，假期结束了</title>
<link>/archives/the-end-of-the-break-2025.html</link>
<description><![CDATA[<p>2025 年的最后一个假期告一段落，下一个长休就是 19 周后的过年了。一想到这里我就感觉人生又再次稍稍失去了光辉。这次假期是成功地体验到了什么叫玩也没玩好休也没休好，总归不太是滋味。</p><p>先来看看假期里成功办成了哪些想办的事情吧：</p><ul><li>回去换了身份证</li><li>和同学聚会了</li><li>简单写了一点点关于自动化记账工具的东西</li></ul><p>除此之外就没有别的有意思的事情了。感觉这 <s>8</s> 6 天里面好像压根啥事都没咋干成的样子。为啥啊！</p><p>如果按天来观察的话：</p><ul><li>10-01: 加班</li><li>10-02: 加班</li><li>10-03: 内务整理</li><li>10-04: 高铁</li><li>10-05: 换证、聚会</li><li>10-06: 高铁</li><li>10-07: 休息</li><li>10-08: 休息</li></ul><p>好了，六天假期里面有两天是「浪费」在高铁上了，最后实际达成的效果和周末双休是完全一致的体感。</p><p>基于此，可以认定：</p><h2>长途旅行是一个很不划算的事情</h2><p>我很佩服那些能顶着舟车劳顿工作的人，因为每次我尝试在高铁上面打开电脑去搞各种事情的时候，我都会陷入一种脑子完全转不动的状态。无论是读材料还是写程序，都是五分钟之内就大脑一片空白。</p><p>当然，比较有趣的是这种「脑子转不动」的状态和我当前可以使用的网络状态有很高的相关性：在高铁上时断时续的信号让我萎靡不振，而在飞机上完全没网可用的我会直接选择昏迷直到落地。哪怕读材料这件事本身压根不需要用网，「我」好像也得「连接」到「算力共享服务器」上才能进行。</p><p>而且长途旅行似乎还带一个持久的 debuff, 需要到地方之后休息一段时间才能消除；而且你在旅途越久，这个 debuff 持续的时间越长。这就使得我这种需要在高铁上罚坐 8 小时的牛马到了地方基本上就是净等状态恢复了。</p><p>所以，对于我来说，长途旅行是非常不划算的事情。旅行时间越长，累积的痛苦就越多。尤其是不知道因为啥原因犯头疼的时候，那路上的每时每秒都是煎熬。好在我坐高铁的这两天还算集中在假期中间，所以免于再遭受一路孩子哭闹和大声外放带来的多重精神打击。在此谢谢同行人都共同保持了一个相对平静的旅途。</p><p>如果有得选，那么在路上的时间越短越好。机票，实在不行，得咬咬牙买全票了。</p><h2>也许「下午再起」不是一个好主意</h2><p>高中时期高强度的晚睡早起让我学会了怎么边骑自行车边睡觉，也让我发现自己是完全按照「当天有没有必须要早起」这个问题决定自己早上几点从床上爬起来的。从高中到了大学再到工作，睡得是越来越晚，而醒则完全看条件。如果早上早起没什么事的话，那我就真的不会早起，直到被尿憋醒、被热醒或者被冻醒，才会很勉强地爬起来；或者干脆打开空调接着睡。</p><p>再睁眼的时候就已经是下午。起来的时候完全没有任何「睡够了」的感觉，只是因为到了这个点再睡下去就跟昏迷没有任何区别了——如果当天确实身体不怎么舒服，那确实可以一直昏迷直到晚上。这种难以言表的疲乏似乎也是很多年轻人的通病：无论怎样休息都休息不够，这是为什么？</p><p>我有点怀念小时候的自己，那个时候我很早就可以醒，然后在床上可以盯着天花板发呆而不会打瞌睡。我也很久没有从床上坐起来的时候感觉神清气爽了，哪怕我的房间确实有个朝南的大窗户也无济于事，该累就是累，跟晒多少太阳没啥关系。</p><p>不过，因为 10-06 坐高铁所以休息的早，我意外地在 10-07 醒得很早。我没有选择接着睡，而是打起精神坐起来干点什么——什么都行，刷手机也算数。10-06 那天很意外得显得比其他休息日要「更长」一些，因为当我觉得现在是不是已经到了下午而回过神来的时候，我发现现在是上午 11 点。</p><p>虽然说 13:00 -- 25:00 和 08:00 -- 20:00 都是 12 个小时，但从体感上来说，好像后者是一个更好的选择。</p><h2>急性时光流逝恐惧症</h2><p>随着年龄的增长，一年的长度会从「人生的一半」指数衰减到「人生的几十分之一」；再加上能够形成新奇记忆的事情也越来越少，日子会过得越来越快。</p><p>所以，当假期结束的时候，这种对于时光流逝的莫名的恐惧总是会涌上心头。尽管技术上我应该能通过尽可能做更多的事情来驱散这种恐惧，但是对于「完成事情」这件事本身的执着反而让加剧了这种毫无缘由的恐惧，尤其是在发现自己其实啥也没干成的情况下。</p><p>这种「尽管自己什么都没做错但是我感觉我做错了什么」的感觉或许来自于十几年的「一个人一支笔一个晚上一个奇迹」。尽管现在我已经不需要再做作业，但那种「审判即将来临」的乌云似乎从未消散。</p><p>需要做什么才算是「度过了一个充实的假期」呢？目前暂时没有找到好的答案。</p>]]></description>
<pubDate>Wed, 08 Oct 2025 14:42:16 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/the-end-of-the-break-2025.html</guid>
</item>

    
        <item>
<title>Web 2.1</title>
<link>/archives/web-2-point-1.html</link>
<description><![CDATA[<p>潮流还真的是一个圈呢！</p><p><div class=more></div><blockquote><p>ifanr 2025-10-07 电：ChatGPT 现在可以直接调用第三方应用了。如果你已经订阅了某个外部服务，还能在 ChatGPT 里直接登录账户，无缝衔接。这一切的背后，是全新发布的 Apps SDK（应用开发套件）。开发者现在可以使用 Apps SDK 预览版开始构建自己的 ChatGPT 应用。 值得一提的是，Apps SDK 构建在 Model Context Protocol（MCP）之上——这是个开放标准，允许开发者把外部数据源连接到 AI 系统，同时也意味着用这个标准构建的应用能在任何兼容平台上跑。 <a href=https://www.ifanr.com/1640071>https://www.ifanr.com/1640071</a></p></blockquote><p>OpenAI 的 ChatGPT 现在可以连接其他应用来做各种事情。这本身并不是一个很新鲜的事情——毕竟，上一个尝试这么做的是 Siri, 而我们现在都已经差不多熟悉了这个玩意的基本套路了。但 OpenAI 这一次采用的底层实现让我感到十分兴奋：它是基于 MCP 协议来连接不同应用的。</p><p>如果它是那种没有对 MCP 进行爆改的实现，这对于互联网来说反而是一个好消息：因为 MCP 协议本身是完全开放的，它并不是一个「AI 专属」的东西。如果它能大规模铺开（我相信 OpenAI 有这个能力），那就意味着我们终于再次走上了互联网的正轨，那就是——</p><h2>重新让网络变得互联</h2><p>上一次集体让大公司决定干这个事情还是在搞 Web 2.0 的时候。</p><p>Web 2.0 有一个关键词就是「互联」。那个时候各大网站都在大搞社区内容、大搞开放分享，其中最重要的就是大搞底层 API 架构来支持开放分享。</p><p>那个时候虽然编程工具并不是像现在这么顺手，也没有各种 AI 工具给你打零工，但是 Ruby 和 Perl 写起来也丝毫不比 Python 逊色。更何况，还有 <a href=/archives/1527.html>Yahoo Pipes</a> 这种只需要拖拽功能块就可以搞 Web 编程的实现。</p><p>这 DevDay 的内容我是越看既视感越强。从统一 API 数据接口，到零代码开发应用集成，吃了这么多年孤岛化的苦，互联网的好日子它真的要来了吗？这能实现我心心念念的 Web 2.1 么？（按：Web 3.0 现在……只能说很遗憾，所以为了取「互联」这个主题，称此次潜在的互联网访问范式的改变为 Web 2.1, 表示它是 Web 2.0 互联的进阶版本。）</p><h2>苦日子就是从那个时候开始的</h2><p>Web 2.0 火热的时候，大公司们都在宣传自己多么开放。然而好景不长。因为大公司们搞了一段时间之后发现：它们没法在 API 上卖广告；而且用户一旦用完了它们的 API 就会拍屁股走人；而且用户完全可以选择不用它们的 App 而转用那些更好用的第三方 App.</p><p>于是很快，这些公司就决定「不玩了」。还要点脸的会留存既有的 API, 但是新上线的功能就全部锁死在 App 内；能拉下脸的就会改动既有 API 人为制造不兼容点、增加各种各样的限制，或者干脆全部下线。雅虎最终是没能让 Yahoo Pipes 挺过 2015, 而为爱发电的第三方则消散得更快。</p><p>「互联」毕竟是一个美好愿景，而没有钱赚的美好愿景终究只是个愿景罢了。毕竟，人最终是需要吃饭的。这种近乎于慈善的行为真的是说停就会停的。</p><h2>当大风刮起</h2><p>现在，Web 2.1 终于能在雷达图上看到身影。它能够通过促进互联的手段来弥补<a href=/archives/2025300.html>它对互联网内容的剥削</a>么？抑或是它反而会导致互联网变得更加封闭、更加孤岛化——因为一旦这样的体系形成，那么对于新一代的互联网用户而言，<strong>AI 聊天软件就是互联网</strong><sup class=footnote-ref><a href=#fn1 id=fnref1>[1]</a></sup>，所以任何不接入大 AI 平台的网站就会彻底消失在普罗大众的视野内？</p><p>我们不得而知。我，作为一名普通的打字员，只能希望不是后者。但我确实明白后者是完全合乎商业逻辑的，而华尔街和硅谷可能也确实计划这么做。</p><p>现在，大家都在说起风了；而我，只能默默地等风来。</p><hr class=footnotes-sep><section class=footnotes><ol class=footnotes-list><li class=footnote-item id=fn1><p>对于不少新生代网民来说，互联网就是抖音 B 站微博小红书；或者就是 Tiktok Youtube Twitter Facebook; 这并不能怪他们，而是我们的互联网商业模式不可避免地引导了新一代人的行为。这是「简单化」的一个必然的副作用：<a href="https://www.youtube.com/watch?v=lme9X2pf990">https://www.youtube.com/watch?v=lme9X2pf990</a> <a class=footnote-backref href=#fnref1>↩︎</a></p></li></ol></section>]]></description>
<pubDate>Tue, 07 Oct 2025 13:52:06 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/web-2-point-1.html</guid>
</item>

    
        <item>
<title>C 语言不适合作为「第一门编程语言」</title>
<link>/archives/c-is-not-a-good-first-language.html</link>
<description><![CDATA[<p>这话题已经不新鲜了，但牢骚总归还是要发的。</p><p><div class=more></div><p>C 语言并不适合作为从未接触过计算机程序设计的人的第一门语言，除非他们已经做好了万全的心理准备，以及有合适的人引路。抱怨这个并不是因为 C 语言难学，而是因为 C 语言实际上很难教。</p><p>编程虽然是数学，但教学的过程一点也不数学：教数学的时候你可以把一些难以在初级阶段严谨证明的东西当作公理硬塞给学生、把「不严谨」但是管用的结论先抛出来让学生先能把手上的问题解开（比如高中阶段是没法教 <eq><span class=katex><span class=katex-mathml><math xmlns=http://www.w3.org/1998/Math/MathML><semantics><mrow><mi>ϵ</mi><mo>−</mo><mi>δ</mi></mrow><annotation encoding=application/x-tex>\epsilon - \delta</annotation></semantics></math></span><span aria-hidden=true class=katex-html><span class=base><span class=strut style=vertical-align:-.0833em;height:.6667em></span><span class="mord mathnormal">ϵ</span><span class=mspace style=margin-right:.2222em></span><span class=mbin>−</span><span class=mspace style=margin-right:.2222em></span></span><span class=base><span class=strut style=height:.6944em></span><span class="mord mathnormal" style=margin-right:.03785em>δ</span></span></span></span></eq> 语言的，但这并不妨碍先把极限的直觉给抛出来）；而编程的话，就没有「不严谨」的好运可以用，因为[每个程序都是一道证明题]<sup class=footnote-ref><a href=#fn1 id=fnref1>[1]</a></sup>，而证明题会逼着你把每个步骤都准确无误地写出来。</p><p>于是就造成了一个很严重的问题：要塞给新手强行背诵的公理太多了。</p><h2>问题自第一行起</h2><pre><code class="hljs language-c"><span class=hljs-meta>#<span class=hljs-keyword>include</span> <span class=hljs-string>&lt;stdio.h></span></span>

<span class=hljs-type>int</span> <span class="hljs-title function_">main</span><span class=hljs-params>()</span> {
    <span class=hljs-built_in>printf</span>(<span class=hljs-string>"Hello, world!\n"</span>);
    <span class=hljs-keyword>return</span> <span class=hljs-number>0</span>;
}
</code></pre><p>再熟悉不过的开学第一课，但我相信没有人的第一课会展开讲第一行的 <code class=prettyprint>#include</code> 是个什么东西。怎么把这个玩意搪塞过去是一门艺术。</p><p>我们当然可以简单地说「这代表我们要使用 stdio 这个库」然后之后再考虑去理清什么是所谓的库；或者更进一步地去理清什么是宏指令、宏的各种操作和坑等等。当然，更常见的选择是永远不理清这个东西，把它当成模版一样的东西贴上去就完事了。</p><p>C 语言是一门十分特殊的语言，它的编译阶段至少有四层：预编译、编译、汇编和链接。这当然是因为各种历史包袱导致的必然结果。其他高级语言至少会把这四层藏成两个阶段；或者压根就不设计这么多的编译阶段；或者更干脆地通过解释器把整个过程都藏起来：</p><pre><code class="hljs language-python"><span class=hljs-built_in>print</span>(<span class=hljs-string>"Hello, world!"</span>)
</code></pre><p>能不能绕开第一行的 <code class=prettyprint>#include</code> 呢？当然可以：</p><pre><code class="hljs language-c"><span class=hljs-keyword>extern</span> <span class=hljs-type>int</span> <span class="hljs-title function_">puts</span><span class=hljs-params>(<span class=hljs-type>const</span> <span class=hljs-type>char</span>* s)</span>;

<span class=hljs-type>int</span> <span class="hljs-title function_">main</span><span class=hljs-params>()</span> {
    <span class=hljs-built_in>puts</span>(<span class=hljs-string>"Hello, world!\n"</span>);
    <span class=hljs-keyword>return</span> <span class=hljs-number>0</span>;
}
</code></pre><p>比起解释预编译器是什么，也许解释 <code class=prettyprint>extern</code> 和 <code class=prettyprint>const char*</code> 是什么来得更容易一些？</p><h3>问题在第三行还有</h3><p>我说的不是把 <code class=prettyprint>main</code> 打成 <code class=prettyprint>mian</code> 的那个问题。</p><p>其实 <code class=prettyprint>int main()</code> 这个写法是不标准的：这样会把 <code class=prettyprint>main</code> 声明成一个具有不定长参数的函数。C 语言标准中规定了 <code class=prettyprint>main</code> 函数只允许有以下两种形式：</p><ul><li><code class=prettyprint>int main(int argc, char **argv)</code></li><li><code class=prettyprint>int main(void)</code></li></ul><p>编译器不给你报错只有以下两种可能：</p><ul><li>你在用一个 C++ 编译器编译 C 代码 <ul><li>比如 MSVC（或者说 Visual Studio）</li><li>其实 VC60 也算数的，而且更有意思的是猜猜多少学校还在用 VC60 教 C 语言？</li></ul></li><li>这是一个十分十分常见的 C 语言扩展 <ul><li>以及你可以挑刺说：这个规则只适用于函数声明，函数定义下空括号 <code class=prettyprint>()</code> 和 <code class=prettyprint>(void)</code> 等价</li></ul></li></ul><p>以及第三种非常美好但是不太可能发生的可能：</p><ul><li>你的编译器默认使用 C23 或者更新的标准</li></ul><h3>问题潜藏在无数细节当中</h3><p>这当然只是一个无伤大雅的小问题。但无数个无伤大雅的小问题叠加起来就是灾难本身。比如：</p><ul><li><code class=prettyprint>char</code> 不一定是 1 字节</li><li><code class=prettyprint>int</code> 不一定是 4 字节</li><li>传递参数不一定非得用到栈</li><li>程序不一定是从上到下从左到右执行的 <ul><li>而且我说的不是前向声明这种东西</li></ul></li><li>写出来的程序它是可以不执行的 <ul><li>而且并不是因为你没主动调用它</li></ul></li><li><a href=/archives/common-c-footguns.html>这个列表还能继续列下去</a></li></ul><p>这些问题之所以存在，是因为——</p><h2>C 语言不是一门存在于真空中的语言</h2><p>所谓「存在于真空中的语言」即可以超脱于具体实现、可以纯粹通过逻辑推算得出结论的语言。如果它有一个现实世界的实现，那当然再好不过。</p><p>在这个定义下，C 语言是一门非常具体的语言。而所有具体的语言都有一个共性：用它写程序将会不可避免地需要处理现实世界中的各种「不完美」，而处理这些问题又需要对应的经验。</p><p>注意这里用的是经验而不是知识。经验和知识的最大不同就是：经验是无法被教给别人的。</p><p>骑自行车就是一种经验。你尽可以把所有关于自行车的知识讲给学生听，而听课的学生经过足够多的死记硬背也可以通过关于自行车的任何知识点考察。但是除非花费时间和勇气双手握把两脚离地摔那么几次，他是永远也没法通过光听课就学会骑自行车的。</p><p>但并不是所有的编程语言都必须是非常具体的语言。高德纳在他的《计算机程序设计艺术》中刻意引入了一个虚拟汇编 MIX 作为书中算法的实现；麻省理工的《计算机程序的构造和解释》则一直使用 LISP 作为程序语言。甚至计算机导论中介绍的图灵机如果不是因为编程太麻烦，也是一个可以存在于真空中的语言。</p><p>但是存在于真空中的语言通常都只能存在于真空中。MIX 汇编需要借助模拟器来运行；LISP 的现实实现则有好几种风味需要选择。短时间内，我暂时看不到用 OCaml 或者 Haskell 写 STM32F103 固件的可能性。</p><h2>除了 C 我们还能选择什么</h2><p>绝大多数人其实没得选，C 是最好的答案，因为——</p><h3><a href=https://faultlore.com/blah/c-isnt-a-language/>C 语言不仅仅只是一门编程语言</a></h3><p>它是计算机界的英语，所以身在工科的我们无一例外都得学。但就跟英语一样，学得怎么样纯靠个人自觉。</p><p>尽管英语有这样那样的缺陷，它现在就是事实上的通行语言。所以为了能和其他人交流，你只能选择学英语；哪怕有人跳出来说 Esperanto, Ido 或者 Lojban 不秒杀你英语，那也只是学有余力的人才会考虑的事情。</p><h3>Python 也许会拯救世界</h3><p>当然，对于那些并不需要和计算机底层打交道的人来说，Python 或许可以拯救他们于水火之中。毕竟，并不是所有从事计算机工作的人都一定要对 x86 架构了如指掌或者周末的时候以排查编译器 Bug 为乐；也并不是所有的计算机工作都需要阅读固件的反汇编结果来排查问题。既然我们需要做的是解决更高层面上的问题，那么使用更高级的编程语言来入门，或许是一个更好的方案。</p><p>每一门图灵完备的语言都是等价的，但每一门语言都有它适应的领域。每一把锤子都能敲东西，但蜡杆锤和叩诊锤肯定不会拿来敲同一种东西。</p><h3>也许这也是一种战略储备</h3><p>当然，教育总是具有迟滞性的。毕竟就算我这辈子可能都不会再有机会动车床，但我还是得搞定金工实习这门每个工科生必修的基础课程。</p><p>或许你可以说，每个计算机专业的人都得会写 C 语言程序就跟每个工科生都得会做小锤子一样，这也是一种居安思危的人才战略储备——当那一天真的来临时，我们已经做好了最基础的训练。</p><hr class=footnotes-sep><section class=footnotes><ol class=footnotes-list><li class=footnote-item id=fn1><p>这并不是一个比喻，而是切实的数学构建。感兴趣的同学可以搜索「柯里-霍华德同构 (Curry-Howard Correspondence)」 <a class=footnote-backref href=#fnref1>↩︎</a></p></li></ol></section>]]></description>
<pubDate>Sun, 21 Sep 2025 16:59:18 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/c-is-not-a-good-first-language.html</guid>
</item>

    
        <item>
<title>自建 Tailscale DERP 服务</title>
<link>/archives/derp-setup.html</link>
<description><![CDATA[<p>近来搬了一次家，新屋的宽带是 NAT 套 NAT 套 cgNAT, 意味着家里的 NAS 只能通过 DERP 中转来通联了。虽然从技术上来说，距离最近的公用 DERP 服务器应该是香港节点，但是不知为何，NAS 偏偏对德克萨斯情有独钟。</p><p>顶着按秒计算的延迟和按位计算的带宽连 SSH 操作都很困难，而且短时间内我也不太可能找到换宽带的方法，那看来只有选择自建 DERP 这一个方案了。</p><h2>DERP 服务器选型</h2><p>和 Headscale 服务器不同，DERP 服务是要实打实转发流量的。2M 小水管肯定是没法承接这些任务的。况且我也不太希望流量需要绕一圈出省之后再进省，万一触发了 PCDN 一刀切我还得再各种扯皮。所以需要找到一个满足以下条件的服务器：</p><ul><li>可用区和我在同一个省</li><li>带宽足够大</li><li>便宜</li><li>月度流量不要太抠门</li></ul><p>为了省钱，CPU 和内存指标都可以压得尽可能低，1 CPU + 1 GB 内存已经足够使用。一般来说，国内各个大厂云的轻量服务器就能满足要求。或者，如果你人缘够好，说不定别人已经建好了 DERP 服务器，你只需要加入即可。</p><p>之后的部分假定你有一台专门作为 DERP 的服务器（至少，80, 443 和 3478 端口空闲的服务器），安装 Debian 系的系统。同时假定你已经有一个备案过的可签发证书的域名。如果你需要直接通过 IP 访问的话，可以参考<a href=https://icloudnative.io/posts/custom-derp-servers/#%E4%BD%BF%E7%94%A8%E7%BA%AF-ip>这个</a>或者<a href=https://blog.handsomehans.uk/Tailscale-bu-xu-yao-yu-ming-he-SSL-zheng-shu--zi-jian-DERP-zhong-ji-fu-wu>这个</a>方案来爆改 DERP 服务器实现。</p><h2>配置安全组 / 防火墙</h2><p>需要放行以下端口和协议：</p><ul><li><code class=prettyprint>80/tcp</code></li><li><code class=prettyprint>443/tcp</code></li><li><code class=prettyprint>3478/udp</code></li><li><code class=prettyprint>icmp</code></li></ul><p>3478 端口是为了 STUN 打洞。</p><h2>安装 DERP 服务</h2><p>首先安装好 Go:</p><pre><code class="hljs language-bash"><span class=hljs-built_in>sudo</span> apt install golang-go
</code></pre><p>如果你的 DERP 机器选在了国内，则还需要配置好 Go 的代理服务：</p><pre><code class="hljs language-bash">go <span class=hljs-built_in>env</span> -w GO111MODULE=on
go <span class=hljs-built_in>env</span> -w GOPROXY=https://goproxy.cn,direct
</code></pre><p>安装 DERP 服务只需要一行：</p><pre><code class="hljs language-bash">go install tailscale.com/cmd/derper@latest
</code></pre><p>这会把 <code class=prettyprint>derper</code> 安装到 <code class=prettyprint>${HOME}/go/bin/</code> 下。你可以把这个路径加到 <code class=prettyprint>PATH</code> 中方便操作。</p><h2>配置 DERP 服务</h2><p>你的域名不一定非得是 <code class=prettyprint>derp.my.server.arpa</code>. 之后遇到这个域名时替换成你的实际域名即可。</p><h3>弄个证书</h3><p><code class=prettyprint>derper</code> 无法指定证书文件，它要求证书名称一定是 <code class=prettyprint>$hostname.crt</code> 和 <code class=prettyprint>$hostname.key</code>. 假如你通过 CertBot 或者 acme.sh 签发的证书不是这个格式，则还需要稍加修改。以 acme.sh 签发为例：</p><p>签发的证书在 <code class=prettyprint>${HOME}/.acme.sh/derp.my.server.arpa_ecc</code> 下，分别为 <code class=prettyprint>${HOME}/.acme.sh/derp.my.server.arpa_ecc/fullchain.cer</code> 和 <code class=prettyprint>${HOME}/.acme.sh/derp.my.server.arpa_ecc/derp.my.server.arpa.key</code>. 我们可以通过符号链接的方式创建满足 <code class=prettyprint>derper</code> 要求的证书：</p><pre><code class="hljs language-bash"><span class=hljs-built_in>sudo</span> <span class=hljs-built_in>mkdir</span> -p /etc/derp/cert
<span class=hljs-built_in>sudo</span> -E <span class=hljs-built_in>ln</span> -s <span class=hljs-variable>${HOME}</span>/.acme.sh/derp.my.server.arpa_ecc/fullchain.cer /etc/derp/cert/derp.my.server.arpa.crt
<span class=hljs-built_in>sudo</span> -E <span class=hljs-built_in>ln</span> -s <span class=hljs-variable>${HOME}</span>/.acme.sh/derp.my.server.arpa_ecc/derp.my.server.arpa.key /etc/derp/cert/derp.my.server.arpa.key
</code></pre><p>之后可以手动启动 <code class=prettyprint>derper</code> 测试配置是否正确：</p><pre><code class="hljs language-bash"><span class=hljs-built_in>sudo</span> -E <span class=hljs-variable>${HOME}</span>/go/bin/derper --certmode manual --certdir /etc/derp/cert --hostname derp.my.server.arpa
</code></pre><p><code class=prettyprint>derper</code> 启动后，访问 https://derp.my.server.arpa 应该可以看到 DERP 服务器的默认页面。</p><h3>设置 Systemd 服务</h3><p><code class=prettyprint>derper</code> 由于不是很稳定，经常会自己把自己跑崩溃，所以需要每隔 12 小时重启一下这个服务。如果你的 Systemd 版本比较新，可以通过指定 <code class=prettyprint>Restart</code> 和 <code class=prettyprint>RuntimeMaxSec</code> 属性来设置其自动重启。</p><p>你需要替换一下 <code class=prettyprint>ExecStart</code> 中 <code class=prettyprint>derper</code> 文件的路径</p><pre><code class="hljs language-ini"><span class=hljs-comment># /etc/systemd/system/derp.service</span>
<span class=hljs-section>[Unit]</span>
<span class=hljs-attr>Description</span>=Tailscale DERP Server
<span class=hljs-attr>After</span>=network.target
<span class=hljs-attr>Wants</span>=network.target

<span class=hljs-section>[Service]</span>
<span class=hljs-attr>Type</span>=simple
<span class=hljs-attr>User</span>=root
<span class=hljs-attr>Restart</span>=always
<span class=hljs-attr>RuntimeMaxSec</span>=<span class=hljs-number>12</span>h
<span class=hljs-attr>ExecStart</span>=/home/user/go/bin/derper --hostname derp.srvcdiag.com --certmode manual --certdir /etc/derp/cert
<span class=hljs-attr>RestartSec</span>=<span class=hljs-number>10</span>

<span class=hljs-section>[Install]</span>
<span class=hljs-attr>WantedBy</span>=multi-user.target
</code></pre><p>重载并使能服务即可：</p><pre><code class="hljs language-bash"><span class=hljs-built_in>sudo</span> systemctl daemon-reload
<span class=hljs-built_in>sudo</span> systemctl <span class=hljs-built_in>enable</span> derp.service --now
</code></pre><h3>设置 Headscale</h3><p>进入到 Headscale 机器，新增一个 DERP 服务器描述文件 <code class=prettyprint>/etc/headscale/derp.yml</code>:</p><pre><code class="hljs language-yaml"><span class=hljs-comment># /etc/headscale/derp.yml</span>
<span class=hljs-attr>regions:</span>
  <span class=hljs-attr>900:</span>
    <span class=hljs-attr>regionid:</span> <span class=hljs-number>900</span>
    <span class=hljs-attr>regioncode:</span> <span class=hljs-string>xaa</span>
    <span class=hljs-attr>regionname:</span> <span class=hljs-string>My</span> <span class=hljs-string>DERP</span>
    <span class=hljs-attr>nodes:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-attr>name:</span> <span class=hljs-string>900a</span>
        <span class=hljs-attr>regionid:</span> <span class=hljs-number>900</span>
        <span class=hljs-attr>hostname:</span> <span class=hljs-string>derp.my.server.arpa</span>
        <span class=hljs-attr>stunport:</span> <span class=hljs-number>3478</span>
        <span class=hljs-attr>stunonly:</span> <span class=hljs-literal>false</span>
        <span class=hljs-attr>derpport:</span> <span class=hljs-number>443</span>
</code></pre><p>然后编辑主配置文件包含 DERP 描述文件：</p><pre><code class="hljs language-yaml"><span class=hljs-comment># /etc/headscale/config.yml</span>
<span class=hljs-comment># 前略</span>
<span class=hljs-attr>derp:</span>
  <span class=hljs-attr>paths:</span>
    <span class=hljs-bullet>-</span> <span class=hljs-string>/etc/headscale/derp.yaml</span>
<span class=hljs-comment># 后略</span>
</code></pre><p>重启 Headscale 服务即可。所有客户端都会自动发现新配置的服务器并尝试与之通联。</p><h2>冲浪</h2><p>要验证自己的 DERP 服务器是否正确通联，可以在客户端节点上执行 <code class=prettyprint>tailscale netcheck</code>:</p><pre><code class="hljs language-plain">$ tailscale netcheck

Report:
        * Time: 2025-09-20T05:38:55.582472335Z
        * UDP: true
        * IPv4: yes, ---.---.---.---:-----
        * IPv6: no, but OS has support
        * MappingVariesByDestIP: false
        * PortMapping: UPnP
        * CaptivePortal: false
        * Nearest DERP: My DERP
        * DERP latency:
                - xaa: 11.7ms  (My DERP)
                - sfo: 170.2ms (San Francisco)
                - lax: 170.8ms (Los Angeles)
                - sea: 178.5ms (Seattle)
                - den: 189.4ms (Denver)
                - hel: 192.6ms (Helsinki)
                - nue: 206.7ms (Nuremberg)
                - hkg: 210.8ms (Hong Kong)
                - dfw: 214.4ms (Dallas)
                - ord: 219.8ms (Chicago)
                - iad: 221.3ms (Ashburn)
                - hnl: 222.8ms (Honolulu)
                - tor: 225.7ms (Toronto)
                - mia: 226ms   (Miami)
                - nyc: 227ms   (New York City)
                - par: 237.7ms (Paris)
                - fra: 245.2ms (Frankfurt)
                - ams: 247.1ms (Amsterdam)
                - lhr: 254.6ms (London)
                - waw: 266.8ms (Warsaw)
                - mad: 271.6ms (Madrid)
                - tok: 273.2ms (Tokyo)
                - syd: 319.9ms (Sydney)
                - sin: 343.3ms (Singapore)
                - sao: 358.9ms (São Paulo)
                - blr: 381.1ms (Bangalore)
                - nai: 390.9ms (Nairobi)
                - dbi: 416.2ms (Dubai)
                - jnb: 437.5ms (Johannesburg)
</code></pre><p>至此，即使打洞失败，也可以通过 DERP 转发来实现可接受范围内的延迟和带宽了。</p>]]></description>
<pubDate>Sat, 20 Sep 2025 05:44:48 +0000</pubDate>
<dc:creator>dousha</dc:creator>
<guid>/archives/derp-setup.html</guid>
</item>

    
        <item>
<title>你还记得电脑曾经是有声音的么？</title>
<link>/archives/computers-used-to-have-sound.html</link>
<description><![CDATA[<p>我还记得电脑是有声音的。</p><p><div class=more></div><p>这倒不是说我的电脑的声卡或者音频接口坏掉了，而是说：我们似乎已经开始遗忘了电脑的各项操作曾经是有音效的。Windows 10 已经陪伴我们走过了十年了（2015-07-29）；而在它之前的 Windows 8 更是已经服役十三年 (2012-10-26)。自 Windows 8 开始，它变得默认「安静」了起来。</p><p>十年。十年已经足够改变太多。现在的孩子们可能对「个人计算机」感到乏味，哪怕这个曾是一个当时的我们感到无比新奇的好东西。这并不是因为现在的孩子们的口味更刁了——毕竟，如果论娱乐能力的话，电脑能干的事情比手机多得去。我认为其中的一个重要原因是现在的电脑已经「被设计为」一个纯粹的工作工具了。</p><p>你在什么环境下才会需要一台「个人电脑」是安静的呢？在公共场合。只有在公共场合需要不打扰其他人，你才会把设备静音。而电脑，这样一个又大又笨重的东西，会出现在什么公共场合里呢？教室、图书馆、办公室、车间，和其他需要工作的地方。</p><p>Windows 10，或者更确切来说是 Windows 8 起，默认没有开关机音乐、没有点开文件夹或者点开网页时发出的清脆响声、没有窗口最大化和最小化的呼呼声，也没有倾倒垃圾桶的纸团摩擦的声音。它变得冷峻、压抑，或者说失去了生气。Windows 从曾经的家庭娱乐中心，变成了一个纯粹的生产力工具。</p><p>我想起了这个梗图：</p><p><a href=https://imgse.com/i/pVcJoe1><img alt=mcdonald-grey.png src=https://s21.ax1x.com/2025/08/31/pVcJoe1.png></a></p><p>如果要我猜一下为什么会有这样的变化，我想：在娱乐被注意力经济主导的当下，对手机的沉浸是随时随地的，而对电脑的沉浸则需要你去电脑房。</p><blockquote><p>硅谷的人并不会「预见未来」，他们会计划一个未来，然后使劲砸钱让它成为现实。</p></blockquote><p>而我曾想要的不是这个未来。</p>]]></description>
<pubDate>Sat, 30 Aug 2025 17:20:51 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/computers-used-to-have-sound.html</guid>
</item>

    
        <item>
<title>被遗忘的权利</title>
<link>/archives/the-right-to-be-forgotten.html</link>
<description><![CDATA[<p>「我劝你谨言慎行。」</p><p><div class=more></div><blockquote><p>是的，你发表到网络上的东西——任何东西——总归会留下痕迹的。</p></blockquote><p>我是一个数字囤积爱好者，换句话说就是我喜欢存储各种各样的数据堆到 NAS 里，但是大部分数据于我而言可能压根没用——下载的电影和电视剧从来不看、下载的游戏和软件也从来不打开；唯独有点益处的可能是我会一直挂着下载器给网上的其他人继续接力，以及照片之类的东西会有一个本地备份。</p><p>不过近来我又发现这些囤积的数据的一个新益处：它们在源站上已经被修改或者移除了，所以我下载的这份数据就变成独一份的了。</p><p>这种「原始数据可以随时消失」的不确定性令我烦扰，毕竟大部分消失的内容并不是被河蟹了：有些时候号主决定把号卖掉，而接手的人自然不会保留上一任留下的各种数据；有些时候号主决定注销这个账号，而这个操作会顺带擦除这个人在这个平台上的全部痕迹；有些时候因为利益纠葛，相关方会使出这样那样的手段对内容做一些增删改，搞得原谱佚散、风韵皆失；有些时候则是因为意外或者设计，整个平台一夜之间归化于历史的长河内，从此再无音讯。</p><p>所以我一直在开发一个自动化的存档和索引工具：你只要把 URL 贴进去，它便会自己把页面中的各种信息刮削出来。无论是网页、图片、音乐还是视频，都可以交给对应的存档实现来下载、分类、整理和归档。</p><p>但是很自然地，就会出现这样一个问题：如果我购置大量硬盘，然后存储各种各样的公开发表的数据，会不会越界？毕竟，这相当于我给我自己赋予了无穷追溯的能力；而即使是公开发表的数据，它也完全有正当理由被撤回。</p><p>我设置了 FreshRSS 为「永不清理文章」，这使得它可以一字不漏地记录我订阅过的 RSS 条目中的全部信息。配合 RSSHub 之类的信息聚合工具，我也可以做「合订本」。这件事情其实还是挺有意思的：你可以观察一件事情是如何被来自不同背景的人解读的；你也可以观察同一个媒体对于某一件事的观点流变。它会反复地提醒你世事的无常，以及为什么媒体的报道应该是贝叶斯更新而非绝对的真理。</p><p>但如果这份「合订本」是针对个人的呢？</p><p>以一个小笑话开头吧：地狱里最折磨人的刑罚并非上刀山下火海，而是把你小时候写的各种「黑历史」打印出来，然后让你面对着一大群人（或者灵魂）大声朗读。</p><p>诚然，如果我现在去翻我自己的 QQ 空间，我的脚趾估计也会尴尬到抠出两室一厅。但好在那时候的贴吧还没有那么强的追溯性，加上贴吧经历的多次堪比火烧亚历山大图书馆的数据损失，我的那些真正滚烫热辣的文字应该都化作熵了。虽然说水贴的我从不后悔，但我肯定不想再把中二时期的怪话翻出来再看一遍。</p><p>但现在，我，以及技术上来说，任何一个手上有一点闲钱和懂一点技术的人，都可以把别人的黑历史永久地保存下来了。从叛逆时期写下的一腔愤怒，到无意间透露的居所地址，所有「不该在网上出现」的东西，或许现在就静静地躺在某块硬盘里。如果这位神秘的档案员愿意干涉，他完全可以凭借着这些信息毁掉「我」的生活。</p><p>都说君子慎独，但如果那时候我还压根不是一个君子呢？</p><p>「被遗忘」正在逐渐成为一项需要被重视的权利。</p>]]></description>
<pubDate>Sat, 30 Aug 2025 13:33:05 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/the-right-to-be-forgotten.html</guid>
</item>

    
        <item>
<title>关于「安全启动」的一些想法</title>
<link>/archives/thoughts-on-secure-boot.html</link>
<description><![CDATA[<p>从技术上来说，你理应永远不需要用到这种东西。但是现实情况是：你没有太多选择。</p><p><div class=more></div><p>倒不是因为《战地 6》公测的事情。我最近在做关于 EN 18031 认证相关的事情，其中对于固件升级和系统完整性验证部分实际上是有要求的。只是恰好最近这个游戏公测了，而它的一个让人蛋疼菊紧的要求就是需要电脑打开「安全启动」功能，所以我想，或许该谈谈这件事了。</p><p>我的想法是：「安全启动」是计算机自由的又一大退步。但现在，它反而在逐渐成为标配。</p><blockquote><p>这是为了你好。</p></blockquote><h2>到底什么是「安全启动」？</h2><p>安全启动做这样一个事情：它会在操作系统启动之前校验操作系统负责启动的代码是否经过签名且受信任。如果这个条件不满足，电脑就不会进入这个操作系统。它不会监视你的动作，也不会收集额外的信息，也不会向谁汇报你在用什么配置的机器，也不会偷偷扫盘。</p><p>看起来人畜无害对么？相比于别的明目张胆的搞事情而言，「校验系统完整性」听起来像是一个十分好的功能，但它的实现却带来了一个很严重的问题：它的「受信任」的验证过程不受你的控制，而且它是刻意被设计为无法被用户所控制的。</p><p>我换一个说法，或许更好理解：这个功能可以<strong>决定你的电脑今天还能不能正常开机</strong>，而这个决策过程你完全没法控制。你<strong>无法手动添加或者删除某个系统为受信任的操作系统</strong>，也无法临时授权某个系统启动。你唯一能做的事情是关掉这个功能。</p><p>哦对了，它实际上也不会真的校验整个操作系统的完整性。它只会校验引导程序是否正常，至于之后的部分，比如操作系统内核、驱动和别的东西，一律交由引导程序去管理。某种意义上来说，这是为数不多的幸运。</p><p>你的手机实际上默认就会开启「安全启动」。如果你需要刷第三方 ROM 或者 Root 手机的话，第一个步骤通常就是解 BL 锁，这个解锁步骤实际上就是在想办法关掉这个「安全启动」。</p><h2>为什么说这是退步</h2><p>用户买了这台设备，理应可以按照他们自己的意愿去干任何事情。这里的干任何事情包括：运行自定义的操作系统。</p><p>注意到权利不是一个「要不要」的问题，而是一个「能不能」的问题。用户可以<strong>要</strong>这台电脑用到死都只运行 Windows 系统；但用户必须<strong>能</strong>选择运行 Windows 以外的系统。这里之所以强调 Windows 系统，是因为<a href=https://wiki.osdev.org/PE#Signed_PE_with_Attribute_Certificate_Table>几乎所有市售主板的信任证书都只有唯一一个：微软密钥交换证书</a>，而且大部分主板压根不支持安装新的证书。</p><p>以及还需要再强调一下：所有证书都是有有效期的。而<a href=https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-secure-boot-key-creation-and-management-guidance>微软上一任证书将会在 2026 年过期</a>。如果你的电脑买的比较早，你会需要想办法更新你的主板 BIOS 才能正常使用安全启动功能；而并不是所有主板厂商都会愿意给他们的旧主板更新新固件的。所以，不要以为「今天你的电脑因为安全启动没通过突然无法开机了」的问题离你很远——它是一柄悬在每个人头上的达摩克斯之剑。</p><h2>那反作弊……</h2><p>反作弊是永远的猫捉老鼠的游戏。安全启动机制压根无法解决任何问题，就跟 Ring 0 反作弊无法解决任何问题一样。</p><p>现在最新最热的作弊工具是软路由（很抱歉，又一个本来好好的名词在大众眼中沦陷成了作弊工具。上一个受害者是 DMA）。这套系统的工作原理是直接分析网络上的游戏数据，将游戏中的全部信息还原出来，从敌人位置显示到动态修改数据一个不差。这套系统可以完全独立于电脑运行，它甚至不需要知道后面打游戏到底是电脑还是手机。</p><p>好了，我的作弊设备压根就不在这台电脑里，就算开启这套机制，你又能检测到什么呢？我挂开得照样很爽。下一步你该干嘛了？扫我的局域网？</p><p>打着反作弊的旗号来侵害我的计算机自由，抱歉，这样的软件我不感兴趣。</p><h2>那数据安全……</h2><p>这个功能唯一能够防护的场景是有恶意软件修改你的预引导环境。当然，在这里我需要承认：确实存在这样的恶意软件，而「安全启动」功能确实可以有效地检测这种恶意修改，并阻止你的电脑在被感染之后启动。考虑到预引导环境可以配置 Windows 内核不检测驱动签名，这会使得被感染的电脑可以让任何代码以最高权限运行，包括各种恶意程序，这种阻止机制是显然必要的——如果用户知情且有选择的话。</p><p>但这套机制终究只解决这一个问题。它不是某些组织所描绘的灵丹妙药。它无法阻止更常见的恶意软件的侵害：那些疯狂弹窗的广告软件、那些卸载不掉的牛皮癣软件、那些偷偷扫盘的间谍软件、那些「借你算力一用」的恶意挖矿软件、那些默默执行遥控指令的后门软件……所有的这些东西，都可以简单地以用户身份执行。它们会小心地隐藏自己的踪迹，而修改启动环境这种会触发安全软件警告大动作它们是绝对不去做的。</p><p>更不要提那些按 GB 售卖的个人隐私数据。这种正经的数据安全问题就更没法通过「安全启动」解决了。</p><h2>那用户友好……</h2><p>手机厂商推行 BL 锁的主要理由就是：绝大多数人用不上你解锁之后要用的功能。而且锁了 BL 可以杜绝用户手机被恶意篡改的情况（或者说得直白一点，免得那群傻逼无脑跟着网上的教程一通瞎改把手机搞坏了又反过来报修投诉）。</p><p>我也是做消费电子领域的程序员，我也很烦啥也不懂的终端用户对着设备一通瞎操作把东西玩坏了又在评价里给差评。但这终究是一个无法通过技术手段解决的人文问题。为了免得麻烦而选择直接给设备阉割，这是一种削足适履。</p><p>更不要说给一台之前没有开安全启动的 Windows 电脑开安全启动功能<a href=https://sowellwell.com/ts/2/5doG>有多麻烦了</a>。你要真为用户友好着想，你最好就别让小白来鼓捣这种可能直接让他们电脑没法进系统的事情。</p><h2>推行无法关闭的固件验证就是对计算机自由的侵害</h2><p>许多物联网芯片会有 eFuse 用于烧录厂商密钥。这个密钥一旦烧录就无法被擦除。自此，这个设备就只能运行由该厂商签名的固件了。同样，这套系统也可以用于封堵调试接口，所以通过调试接口烧录未经签名的软件也是不可行的。</p><p>EN 18030-2 中明确要求了：固件升级必须校验固件完整性<sup class=footnote-ref><a href=#fn1 id=fnref1>[1]</a></sup>。这里的完整性可不仅仅是校验文件在传输过程中是否有损坏，而是还需要有效签名，才可以被安装执行<sup class=footnote-ref><a href=#fn2 id=fnref2>[2]</a></sup>。这就使得为了合规，许多在欧盟售卖的物联网设备自生产起就注定要进入电子垃圾堆——因为固件验证功能一旦开启就无法关闭，用户自然就没办法下载自制固件。即使有大神有 SDK 并且做了自制固件，那也是完全没卵用的——除非你想办法换一块没有烧录过 eFuse 的芯片上去。</p><p>我可能需要再次强调：权利是「能不能」的问题。是，一个电子温度计可能用到彻底坏掉都不需要刷固件，但是<a href=https://www.bilibili.com/opus/963208637383180336>我想要让它显示时间或者连接 HomeAssistant</a>, 就绝对需要能刷固件。</p><h2>作为普通人，我们能怎么做？</h2><p>最直接也是最有效的选择，就是拒绝使用这种侵害你的计算机自由的软件。</p><p>但我明白，很多时候我们没有其他选择。无论是受迫于同侪压力还是市场限制，我们只能接受现有的状态。但是，<a href=https://www.gnu.org/philosophy/saying-no-even-once.html>对不公说哪怕一次「不」也是在推进公正</a>。如果你能向身边的人或者网上的朋友建议停止使用这些侵害自由的软件，那么你就已经在帮助传播解放思想了——无论他们是否接受这么做。</p><p>即使是出于各种原因，你现在无法开口说这个「不」，那么能记住「这个世界仍有人愿意为之奋斗」这件事，时不时地想起「用户应有自由使用设备的权利」，你也在无形中播撒着以人为本的理念。</p><p>自由软件无法在体量上直接和背靠资本的大公司竞争，但如果我们能取得文化胜利，如果我们可以让大多数人意识到：他们有自由使用自己设备的权利，他们需要争取自由使用自己设备的权利，那么我们就能撼动这些厂商生产真正符合大众利益的产品，我们就能够让这个世界变得更好。</p><hr class=footnotes-sep><section class=footnotes><ol class=footnotes-list><li class=footnote-item id=fn1><p>CEN/CLC/JTC 13/WG 8. Common security requirements for radio equipment - Part 2: radio eqiupment processing data, namely Internet connected radio equipment, childcare radio equipment, toys radio equipment and wearable radio equipment[S/TPOL]. European Committee for Standardization. (2024-08-14)[2025-08-15]. <a href=https://www.gwst.cn/wp-content/uploads/2024/09/FPrEN-18031-2.pdf>https://www.gwst.cn/wp-content/uploads/2024/09/FPrEN-18031-2.pdf</a> || p83-84. <a class=footnote-backref href=#fnref1>↩︎</a></p></li><li class=footnote-item id=fn2><p>虽然但是，完整的标准里还是规定了不使用签名的固件更新方案的，但那样做的话，需要搞的流程就会变得更多、更复杂。猜猜大多数厂商会选择哪种合规方案吧——签名方案排在第一位可不是没有原因的。 <a class=footnote-backref href=#fnref2>↩︎</a></p></li></ol></section>]]></description>
<pubDate>Fri, 15 Aug 2025 06:31:20 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/thoughts-on-secure-boot.html</guid>
</item>

    
        <item>
<title>公厕抽烟的人只能度过一个绝对失败的人生</title>
<link>/archives/why-the-f-do-you-even-smoke.html</link>
<description><![CDATA[<p>凭什么要我忍？</p><p><div class=more></div><p>字面意思。公共厕所隔间抽烟的人是绝对失败的人。别跟我扯什么自由不自由的，我话撂这了：公共厕所抽烟的人就是<strong>绝对失败</strong>的人。</p><p>为什么？很简单：你没有一个能自己抽烟的地方。你工作的场所也没有吸烟区，你也没有在自己办公室里点烟的权力。你想抽烟，甚至只能借着上厕所的名义出来满足自己可怜的尼古丁需求。屈服于卑劣的毒品、苟活于压抑的工作，这就是一种彻头彻尾的失败。</p><p>彻彻底底的失败，老哥，别跟我谈什么故事也别吹自己是什么传奇。每个人都有自己的仗要打，谁不比谁活得精彩。你要真有点本事，也没见你抽多好的烟啊？厕所坑位点上一根，熏得整个男厕都臭不可闻。知道的懂你抽的烟瞎，不知道的以为你把自己裤裆给点了呢！</p><p>也别跟我扯谁谁谁天天抽，没见他不行，也活到了多少多少岁。我不在乎。你在公共场所抽烟了，就是碍到我了。我不在乎你一天是不是就抽这一根，我也不在乎你压力有多大需要排解。在乎是相互的，你在厕所隔间里抽烟的时候在乎过我么？</p><p>更别跟我扯什么烟草税充军费，你但凡点开财政部的网站看一眼，你就知道全国上贡的那点烟草税<a href=https://gks.mof.gov.cn/tongjishuju/202507/t20250725_3968635.htm>甚至需要跟车船税和船舶吨税并计才能凑出三位数字</a>。税收的大头是五位数的增值税、企业所得税、出口退税，你抽烟那点钱甚至不够填零头的。</p><p>楼道里抽烟我已经默许了，大街上边走边抽我已经忍过了，我不想在我上厕所的时候还需要迁就。你哪怕抖音最大音量外放我都认了，我只求一个没有臭烟味的厕所隔间。</p>]]></description>
<pubDate>Thu, 07 Aug 2025 09:57:29 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/why-the-f-do-you-even-smoke.html</guid>
</item>

    
        <item>
<title>与亿赛通 CDG 搏斗</title>
<link>/archives/debugging-the-est.html</link>
<description><![CDATA[<p>不是哥们，我程序怎么就莫名奇妙地崩了呢？</p><p><div class=more></div><p>先简单说一下这是个什么玩意：</p><blockquote><p>亿赛通电子文档安全管理系统（简称：CDG）是一款电子文档安全防护软件，该系统利用驱动层透明加密技术，通过对电子文档的加密保护，防止内部员工泄密和外部人员非法窃取企业核心重要数据资产。</p></blockquote><p>我就先按下公司配置的漏洞导致实际上任何想整活的人都有办法拿走加密文件这种事情不表；我也按下这货加解密的速度慢到一种境界导致工程编译时间长了 20 倍不止不表。我今天要来吐槽的，是这货干碎了 <code class=prettyprint>vcpkg</code>。</p><p>如果你在用 <code class=prettyprint>vcpkg</code> 的时候莫名奇妙地遇到了 <code class=prettyprint>0xc0000135</code> 错误，而且装了各种 VC++ 运行库都没卵用，那么基本可以确定是亿赛通搞的问题。我知道你已经给各个编译工具上好解密权限了，但就是这个解密权限导致程序在这个状态下无法导入一个关键 DLL——</p><h2><code class=prettyprint>DynamicDll64.dll</code></h2><p>CDG 的工作原理分两步，一步是内核驱动 <code class=prettyprint>FileLock(64)?.dll</code> 做的一些神秘工作，另一步是注入一个 DLL 到有权限读取明文的程序中来做透明解密操作。</p><p>那我请问了，你就不能行行好直接让你的内核驱动决定一个程序能不能读明文，然后在内核里面就把数据解密好么？非得注入一个 DLL 到我的程序里是嫌这个世界不够乱么？哪怕钩 <code class=prettyprint>FltCreateFile(Ex)?</code> 和 <code class=prettyprint>FltReadFile(Ex)?</code> 就已经足够玩了啊！</p><p>好吧，为了尽可能减少跟某些杀毒软件打架的可能性，以及不一直拖着内核，选择直接注入应用程序本身，说实话也无可厚非。但在 <code class=prettyprint>vcpkg</code> 下面：</p><h2>你的 <code class=prettyprint>PATH</code> 不是我的 <code class=prettyprint>PATH</code></h2><p><code class=prettyprint>vcpkg</code> 在启动编译环境的时候会有意给它调用的所有工具设置 <code class=prettyprint>PATH</code> 为非常精简的干净环境，但是亿赛通的这个 DLL 又是需要通过 <code class=prettyprint>PATH</code> 加载的。这就导致在 <code class=prettyprint>vcpkg</code> 环境下工作的需要解密权限的程序会直接因为找不到依赖而爆掉。</p><p>你可以通过设置 <code class=prettyprint>VCPKG_KEEP_ENV_VARS=PATH</code> 的环境变量让 <code class=prettyprint>vcpkg</code> 继承全局的 <code class=prettyprint>PATH</code>. 但是使用精简的 <code class=prettyprint>PATH</code> 在大多数情况下都是需要做的，否则你电脑上安装的其他东西可能会带跨整个编译过程。比如 Strawberry Perl 是自带一个 <code class=prettyprint>ccache</code> 的，如果你让 <code class=prettyprint>vcpkg</code> 继承你大环境中的 <code class=prettyprint>PATH</code> 就会导致怪事发生。</p><h2>永久的临时解决方案</h2><p>把 <code class=prettyprint>DynamicDll.dll</code> 和 <code class=prettyprint>DynamicDll64.dll</code> 复制一份到 <code class=prettyprint>C:\Windows</code> 下面。当然，最好是用 <code class=prettyprint>mklink</code> 命令创建符号链接，这样至少这个鬼玩意更新之后你不需要再手动复制一份文件过去。</p><p>考虑到 <code class=prettyprint>FileLock.dll</code> 和 <code class=prettyprint>FileLock64.dll</code> 本身就是被亿赛通塞到这个路径下的，我就实在猜不到为什么他们不干脆也把 <code class=prettyprint>DynamicDll.dll</code> 和 <code class=prettyprint>DynamicDll64.dll</code> 一并塞进来了。就这样吧，至少这么操作一波之后它能用了。</p>]]></description>
<pubDate>Sat, 26 Jul 2025 13:29:18 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/debugging-the-est.html</guid>
</item>

    
        <item>
<title>DNS: 最后一战</title>
<link>/archives/more-wrestling-with-dns.html</link>
<description><![CDATA[<p>终于让我逮到你了！</p><p>省流：记得设置路由器的系统时间。</p><p><div class=more></div><p><a href=/archives/wrestling-with-dns.html>刚刚上期</a>我们和神秘路由器缠斗出了一些结果，但终究只是临时方案。毕竟，电脑可以指定 DNS 解析器，别的设备想要做这个操作就显著地困难或者干脆不可能。（说的就是你，Quest 头显！）</p><p>在我把电脑的 DNS 重新指向路由器之后，我发现它又开始没法上网了。我实在是不想再继续折腾 <a href=/archives/4706.html>dnsmasq 和单臂路由</a>了，毕竟当初花重金买了这台华硕路由器就是图一个省心，真要这么继续改下去那我不如把工控机改成骨干路由接进来，那样还更好搞怪东西。</p><p>但问题总得是要解决。登录路由器后台后，我先手动指定了 WAN 口所使用的 DNS 服务器，当然是没啥卵用——神秘工具本身会启动一个 dnsmasq 实例，并将路由器的上游 DNS 指向这个实例。</p><p>然后是看日志。日志的内容没发现什么问题，但是日志的时间戳很不对：它是 2018 年的时间戳。</p><hr><p>这个 dnsmasq 实例自然是有配置 DoH 的。这个 DoH 请求视情况会走阿里 DNS 或者 Cloudflare DNS. 而 DoH, DNS over HTTPS, 是依赖于 TLS 加密的。</p><p>TLS 加密需要用到系统时间来校验证书是否有效。如果你的系统时间距离现实时间太远，那么你收到的证书看起来就是「到遥远的未来才会生效」或者「在遥远的过去就已经过期」的无效状态。</p><p>这就使得任何 DoH 请求都会直接失败。反应到下游就是 DNS 查询失败。</p><hr><p>华硕路由器设置系统时间的方法很奇怪：你只能通过指定一个 NTP 服务器的方式来让路由器通过网络同步时间，不能手动调整。除此之外，你还需要启动「网络监控」功能才能启动时间同步过程。</p><p>登录路由器后台，进入「系统管理」页 - 「系统设置」选项卡 - 「基本设置」栏，按以下方案执行配置：</p><p><a href=https://imgse.com/i/pVAcfGq><img alt=pVAcfGq.png src=https://s21.ax1x.com/2025/06/14/pVAcfGq.png></a></p><p>保存即可。</p>]]></description>
<pubDate>Sat, 14 Jun 2025 20:52:57 +0000</pubDate>
<dc:creator>dousha</dc:creator>
<guid>/archives/more-wrestling-with-dns.html</guid>
</item>

    
        <item>
<title>记住未来</title>
<link>/archives/remembering-the-future.html</link>
<description><![CDATA[<p>记住未来是一个基础能力。</p><p><div class=more></div><p>有些时候，有些决策需要你能够「预见未来」才能正确做出。比如，一个通信协议的头信息中会包含体长度，但编码器在完整收到体信息之前压根无法编码这个字段。所以，一个合格的编码器会需要能够记住未来，以便于进行流式编码。</p><p>我是在 <a href=https://aphyr.com/posts/341-hexing-the-technical-interview>Hexing the technical interview</a> 里学到这个说法的：</p><blockquote><p>Remember the future. This is a common trick for protocol wizards, many of whom live as Merlin did, writing constants and buffer sizes before (after) having written (unwritten) the buffers themselves.</p></blockquote><p>这段我实在没法翻译。我毕竟不从事语言文字相关的工作，其中的味道我确实难以传递。</p><p>记住未来和预见未来的最大区别，或许是被记住的未来是注定要发生的；而被预见的未来仍然会有概率偏离你的预期。记住未来不仅仅是预见未来，还包括将未来塑造成你所记住的模样。如果你在手动编写一个二进制文件，那么你就需要保证：这里写下的长度值，不多不少刚刚好。</p>]]></description>
<pubDate>Sun, 01 Jun 2025 03:20:59 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/remembering-the-future.html</guid>
</item>

    
        <item>
<title>我们的游戏服务器被 DDoS 了</title>
<link>/archives/our-game-server-got-ddos-ed.html</link>
<description><![CDATA[<p>虽然是定番，但……但这个也没啥好强调的了。</p><p><div class=more></div><p>2025-05-14 19 时左右，我们如期开始了服务器的活动。5 分钟后，服务器的 TCP 连接数开始异常地暴增，同时入站数据包数开始快速地攀升；8 分钟后，支持服务器资源分发的 CDN 流量开始暴涨。35 分钟后，我们的 IP 进入了黑洞。</p><p>That's why we cannot have good things on the internet. 即使是纯粹为爱发电的我们，也逃不过这群吃饱了没事干的骚扰——虽然当个好人并不是不被坏人打的理由吧，但这世上谁又愿意生活在不安之中呢？</p><h2>我们抗住了！但机房不这么认为</h2><p>监控系统很快就开始报警了：TCP 半开连接数过高。同时，网卡数据包处理量瞬间突破了 100kP/s. 第一波攻击比我预期的更早地到达了。</p><p>当我不断刷新监控指标的时候，服务器入站流量达到了 571Mbps 的顶峰。但是服务端撑住了！反向代理工具虽然在不停地报包解析错误，但是玩家们都还在线！他们都能正常地活动！</p><p>这波攻击持续了一分钟。也许是触发了机房那边的自动封禁动作，也许是攻击者认为一分钟就足够轰垮我们。</p><p>我顶着快跳出嗓子眼的心率敲下了一行通过反向代理日志滤出攻击 IP 的命令，开始收集数据。</p><p>5 分钟后，第二条报警弹了出来：TCP 半开连接数过高。但网卡数据包处理量相比上次收敛了很多，只有 79kP/s. 这次攻击持续了两分钟，我们的游戏端仍然在线。</p><p>但是当我的目光移动到 CDN 数据统计时，原本 2Gbps 的下行带宽突然上涨到了 9Gbps. 关于 CDN 的数据，后面再提。毕竟是腾讯云的 CDN, DDoS 腾讯云疑似是对自己有点过于自信了。</p><p>又过了 5 分钟，第三波波峰出现了，网卡入站流量达到 400Mbps, 反向代理更是疯狂滚动着包解析出错的信息。但玩家都还在线！甚至他们没有卡顿！</p><p>我以为如果能再抗一分钟，我们或许可以想办法撑到活动完成——毕竟这次活动只需要跑一个小时，而现在时间已经过去了一半。我已经收集了足够多的数据开始封禁 IP 了。</p><p>一分钟终于过去了，然后——</p><p>然后是一片死寂。</p><p>我们的 IP 进入了流量黑洞，所有玩家全部掉线。好在后台管理使用的是不同的 IP, 我的 SSH 连接并没有因此中断。</p><p>我们不得不将活动迁移到其他地方继续进行。服务器的反代被关掉，我们只能在被黑洞的时间里装死。</p><h2>等一下，你说 CDN 跑了多少流量？</h2><p>我们通过 CDN 分发的文件大小是 1GB. 没错，它是一支视频。因为技术原因我们必须串流分发 MP4 文件而非通过视频云转码并分发 m3u8. 如果你已经开始叹气摇头或者开始苦笑，那么我后面还有更劲爆的：因为技术问题，我不能部署任何验证措施。</p><p>这简直是求着人在盗刷 CDN 流量，但反正这个资源只需要用撑死 1 小时，只要能撑过这 1 小时，那问题就不大。大不了我先起够余量，再加一个 5Mbps 100QPS 的流控。就算真有人盗刷，又能拖出多少流量呢？</p><p>我们服务器的计划承载量是 100 人，所以技术上来说，最大只需要分发 100GB 也就够了。为了保证余量充足，我提前购买了 300GB 的流量包。</p><p>也许是因为那么多的技术妥协导致玩家不得不反复拉取文件，也许是有人抓出了 CDN 分发链接，我们用于分发视频文件的 CDN 流量一度达到并保持在 9Gbps 左右。这么运行了一段时间直到服务器被黑洞后，我得到的最终结算流量是 1TB.</p><p>这个时候你就会深刻地意识到：在用了这么多年的无限流量卡后，流量还是他妈的要钱的，而且不便宜！</p><p>CDN 的下行带宽曲线在服务器被黑洞后快速归零，所以很难说到底是为什么会出这种问题。也许是游戏客户端写得太成问题导致部分玩家会疯狂地从 CDN 拉取数据；也许是攻击者的诉求已经达到所以不屑于继续耗费它的资源做无意义的攻击。</p><p>好在 CDN 账单并没有刷爆我的卡——虽然这一次超过预算 300%, 但至少我还有两个子把它解决掉。搞笑的是因为 CDN 结账的滞后性，我还是接到了腾讯云的催命电话：欠费 12 元，即将停机。</p><p>交钱、删资源、停解析。闹剧该收场了。</p><h2>混在玩家中的攻击源</h2><p>一开始攻击源很好辨认：它们来自我们八竿子接触不到的地区，比如印度尼西亚，比如得克萨斯州。但还有一些攻击源，它们来自广州、江苏、山东、河南。细查之下发现有些 IP 地址甚至是来自已经登录的玩家！</p><p>我不相信有玩家会主动攻击我们服务器，更不相信有一群玩家都在主动攻击我们服务器。唯一的解释，可能还是来自于一个尴尬的事实：中国大陆的 IPv4 地址很不够用，所以有许多人会在一层 cgNAT 后面；从服务器这端看过去，他们就都是来自同一个 IP 地址的。</p><p>这使得我们不敢部署自动 ban 人脚本：因为防火墙会无差别地杀伤普通玩家和攻击者。我只得手动筛选出大陆以外的 IP 进行封禁。不过说实话，这种攻击此时已经于事无补了，该黑洞还是会黑洞，这只是为了能少看点报错而已。</p><h2>溯源？</h2><p>愉快犯是最难溯源的。我们没收到任何人或组织的威胁或勒索，也没有任何人或组织明确提出过对此次活动的反对，所以整个攻击就显得……很神奇。我们就像一只趴在路边晒太阳的小动物，总是有那么个好事的非得过来踢一脚不可。</p><p>活动结束了，服务器又开了，黑洞解除了，攻击没再继续。机房给出了攻击汇报：总共检测到了 550GB 的攻击流量。不是很大，但也不可轻视。</p><p>是有人讨厌活动的主题但是又不愿意提出么？是有人嫉妒我们可以组织这样的活动么？是有人在拿我们服务器练手么？这些无尽的猜测不会有答案。我看着攻击源为一个星号的 UDP 攻击和 SYN 泛洪攻击统计数据，只产生了更多的困惑。</p><h2>你应该……</h2><p>我知道，我们应该提前做好规划，应该上防火墙，应该上白名单，应该……</p><p>我承认这次我们确实是准备不足了。但说实话，之前我们的服务器达到一定规模的时候，也并没有人来找我们的麻烦。或许我们沉浸在这种岁月静好里太久了，所以有人决定还是得提醒我们一下：这个世界的底色仍然是战火纷飞。</p><p>当然，根据我掌握的资料来看，无论国内外，这种攻击都不少见。大多数在线游戏甚至要在发售之前就买好昂贵的防护套餐来避免自己被打成筛子——哪怕他们只是没什么钱的独立游戏工作室，这个「保护费」也是必选项。</p>]]></description>
<pubDate>Sun, 25 May 2025 01:59:21 +0000</pubDate>
<dc:creator>dousha</dc:creator>
<guid>/archives/our-game-server-got-ddos-ed.html</guid>
</item>

    
        <item>
<title>我们的游戏服务器被黑了</title>
<link>/archives/our-game-server-got-hacked.html</link>
<description><![CDATA[<p>虽然是定番，但还是要强调：不该暴露的服务不暴露，不该做出的假设不做出。先把安全做好，再上业务逻辑。</p><p><div class=more></div><p>2025-05-17 22 时左右，我们的团队发现游戏后台在报数据库写入失败的错误。我看了之后发现一个恐怖的事情：</p><blockquote><p>无法写入数据库 <code class=prettyprint>prod</code>: 数据库不存在</p></blockquote><p>当我登上 MySQL 控制台查看的时候：</p><pre><code class="hljs language-text">mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| RECOVER_YOUR_DATA  |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)
</code></pre><h2>我们的数据库被骇客勒索了</h2><p>这是一台新的裸金服务器，我们才刚将生产环境迁移过来不到十小时，许多安全加固都还没有实装。没想到这么快就吃了开门红。</p><p>从攻击手法上，不难看出这是自动化脚本攻击。互联网每时每刻都有闲着蛋疼不断地扫各种漏洞、爆破弱口令的脚本在跑。如果配置错误，那么你离中招也许只有五分钟的距离。</p><p>有意思的是：这个傻逼居然开口要价 <code class=prettyprint>0.0082 BTC</code>, 折合超过十万美刀，这也太看得起我们了。俺们这种为爱发电的是真掏不起。</p><p>虽然我们配置了异地冷备，但冷备毕竟是冷备——它每 24 小时产生一次。所以我们丢失了今天产生的数据。得，那只能认栽。该恢复恢复，该给玩家赔偿赔偿。</p><h3>根本原因：数据库弱口令</h3><p>最开始这个服务器配置的时候，开发人员为了省事，不仅使用了 <code class=prettyprint>root</code> 敢死队，还配置了一个弱口令。就是 6 个数字的那种弱口令。后来由于使用数据库的程序越来越多，等我开始执行安全加固的时候已经积重难返，所以敢死队和弱口令就一直延续地用了下去。</p><p>这一遭之后，我们换了强口令，然后给各个程序重新配置了数据库访问凭证。人教人教不会，事教人一遍通，大概就是这么个道理。</p><h3>直接原因：防火墙未到位</h3><p>这里有三个层面的防火墙均未到位。三层防火墙中如果有任何一个到位，那么它就可以抵御这样的防御。</p><p>第一层防火墙是安全组。在之前，我们的服务器是在一层 NAT 之后的。如果要放行端口，则必须通过厂商的控制面板配置映射来放行。这实际上是一层防火墙——虽然难用了一点，但它可以说是抵御绝大部分自动攻击的最外围的防线。切到裸金服务器之后，我们实际上就没有了厂商安全组设置了。</p><p>第二层防火墙是系统防火墙。新系统并没有默认启用防火墙，而我也没有去主动配置防火墙。这就导致了当自动攻击脚本来扫我们的服务器的时候，服务器处在一个大门敞开的状态。</p><p>第三层防火墙是应用级访问控制。<code class=prettyprint>root</code> 账号本来应该是只能通过本地访问的，但是由于我们的程序运行在主机内，但数据库用了 Docker, 所以 <code class=prettyprint>root</code> 账号的默认策略变成了允许任何 IP 地址登录（毕竟从容器里来看，从主机中的登录就是来自于一个外部网络）。这使得自动攻击脚本得以对我们的数据库执行爆破攻击。</p><p>除此之外，我们还可以配置的：</p><ul><li>fail2ban 检查数据库登录行为，封禁失败过多的 IP</li><li>TCP 并发连接检查，封禁短时间内尝试建立过多连接的 IP</li><li>……</li></ul><p>总之，这个锅确实在我。</p><h3><code class=prettyprint>ufw</code> 和 Docker</h3><p>即使配置了防火墙，也万不可掉以轻心。尤其是如果你的系统使用的是 <code class=prettyprint>ufw</code> 的情况下，错误的 Docker 配置仍然会将你的服务暴露于风险之中。</p><p>考虑这样一个配置：</p><pre><code class="hljs language-yml"><span class=hljs-attr>services:</span>
  <span class=hljs-attr>db:</span>
    <span class=hljs-attr>image:</span> <span class=hljs-string>postgres:16-alpine</span>
    <span class=hljs-attr>ports:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-number>5432</span><span class=hljs-string>:5432</span>
    <span class=hljs-comment># 余下从略</span>
</code></pre><p>如果你使用 <code class=prettyprint>ufw</code>, 那么这么写会直接将端口 <code class=prettyprint>5432</code> 暴露于公网访问之中，即使你从未主动开放过这个端口：</p><pre><code class="hljs language-text"># ufw status

Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere
80/tcp (v6)                ALLOW       Anywhere

# _
</code></pre><p>正确的写法是：</p><pre><code class="hljs language-yml"><span class=hljs-attr>services:</span>
  <span class=hljs-attr>db:</span>
    <span class=hljs-attr>image:</span> <span class=hljs-string>postgres:16-alpine</span>
    <span class=hljs-attr>ports:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>"127.0.0.1:5432:5432"</span>
    <span class=hljs-comment># 余下从略</span>
</code></pre><p>这样可以保证它只接受来自本地的连接。</p><p>我实在是懒得上 <code class=prettyprint>ufw-docker</code> 之类的东西了。最理想的情况是 Docker 完全不需要暴露端口，而是将所有的程序都塞到 Docker 里，然后直接通过 Docker 的内部网络实现通联：</p><pre><code class="hljs language-yml"><span class=hljs-comment># in db/docker-compose.yml</span>
<span class=hljs-attr>services:</span>
  <span class=hljs-attr>db:</span>
    <span class=hljs-attr>image:</span> <span class=hljs-string>postgres:16-alpine</span>
    <span class=hljs-attr>networks:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>db</span>
    <span class=hljs-comment># 余下从略</span>
<span class=hljs-attr>networks:</span>
  <span class=hljs-attr>db:</span>
    <span class=hljs-attr>name:</span> <span class=hljs-string>db</span>
    <span class=hljs-attr>external:</span> <span class=hljs-literal>true</span>

<span class=hljs-comment># in app/docker-compose.yml</span>
<span class=hljs-attr>services:</span>
  <span class=hljs-attr>app:</span>
    <span class=hljs-attr>image:</span> <span class=hljs-string>prod.image.my.company.arpa/game-group/the-game:1</span>
    <span class=hljs-attr>ports:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-number>23333</span><span class=hljs-string>:23333</span> <span class=hljs-comment># 这个是刻意暴露给外部的端口</span>
    <span class=hljs-attr>networks:</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>app</span>
      <span class=hljs-bullet>-</span> <span class=hljs-string>db</span>
    <span class=hljs-comment># 余下从略</span>
<span class=hljs-attr>networks:</span>
  <span class=hljs-attr>db:</span>
    <span class=hljs-attr>name:</span> <span class=hljs-string>db</span>
    <span class=hljs-attr>external:</span> <span class=hljs-literal>true</span>
  <span class=hljs-attr>app:</span>
    <span class=hljs-attr>name:</span> <span class=hljs-string>app</span>
    <span class=hljs-attr>external:</span> <span class=hljs-literal>true</span>
</code></pre><p>这样即使我家大门常打开，那最多就是进一个空无一物的院子里看看。各个房间的门是直接焊死的。</p>]]></description>
<pubDate>Sun, 18 May 2025 02:45:31 +0000</pubDate>
<dc:creator>dousha</dc:creator>
<guid>/archives/our-game-server-got-hacked.html</guid>
</item>

    
        <item>
<title>寻找一台电子笔记本</title>
<link>/archives/in-search-of-a-digital-notebook.html</link>
<description><![CDATA[<p>我说的不是笔记本电脑，我说的是可以替代纸笔的电子产品。</p><p><div class=more></div><h2>纸和笔的生态位</h2><p>尽管我平常的生活是跟电脑打交道，用键盘打字的时间比我拿笔写字的时间要长很多，但是纸笔在工作中仍然是不可或缺的组成部分：无论是信笔涂鸦解释系统设计还是快速记录各种想法，它都是「直观」的。</p><p>这种直观来自于纸笔提供的足够而不散漫的自由：你可以在纸张的任何一个位置上绘制任何东西而不需要拘泥于形式。只要你能够画出来，它就能够通过纸笔被表达出来。相比之下，通过各种绘图程序指挥计算机虽然可以绘制出更清爽、更规整的图形，但这一层「指挥」总归是不如直接上手更方便一些。</p><p>那么有没有什么电子产品可以替代纸笔的生态位呢？一种纸笔的电子化的形式：即开即用、可以被揣到包里到处跑、可以用笔直接书写。结合书写的直观的体验和数字化的分享的便利，按道理应该是一个很合理甚至很常见的需求。</p><h2>数位板</h2><p>如果不考虑显示和书写是完全一体的，而仅仅是追求绝对卓越的书写体验的话，数位板似乎是一个非常好的选择。毕竟，数位板作为专业数字制图工具经历了这么多年的发展，捕捉笔迹运动显然已经是一个相当成熟的技术。</p><p>然而，数位板的最大问题也恰恰是它不是显示和书写是一体的。它的这个形制决定了它必须被连接到一个兼容数位板的系统当中，也就意味着做不到「即开即用」。如果考虑那些显示输入一体的数位屏，比如 Wacom Cintiq 系列，则又要需要牺牲它的便携性。我看到的最小的数位屏也不是那种可以直接随时塞到包里带走的大小。</p><p>既然数位板中暂时没有好选的，那有没有其他可选的方案呢？</p><h2>平板电脑</h2><p>既然需要即开即用又需要便携，平板电脑似乎就很符合这些要求。</p><h3>基于 Android 的平板电脑</h3><p>由于接触时间太早，基于 Android 的平板电脑在我的心里一直没有特别中意的。那个时候主流的安卓平板都还在 Android 4.0.4 时代，软件生态并不是特别理想，更不必提手写笔的支持了。</p><p>最近我去华为和小米的线下体验店试了试近期平板的手写体验。Android 发展到现在，对于手写笔作为触摸输入的支持是很完整的，但也仅限如此。如果没有厂商的主动支持，只靠 Android 本身，那么手写笔实际上仍然只是一个带着特殊标志位的触摸操作。即使到现在，Android 阵营下的书写体验仍然处在聊胜于无的状态。而那些脱离 Android 系统的实现，比如华为的 HarmonyOS, 对于手写笔的支持就更不是首要任务了。</p><p>不过有一个有趣的例外：三星的 S-Pen 生态也许是一个可以考虑的选项。S-Pen 是少数几个可以在移动设备上使用被动电磁笔技术的实现，而被动电磁笔技术一直都是数位板所采用的。基于此，我想把 S-Pen 放到下面再提。</p><h3>基于 Windows (x86/x64) 的平板电脑</h3><p>我有一台联想的 Ideapad Miix 700 平板电脑。这台平板陪我读了三年的大学（因为是大二的时候从某海鲜市场买的）。除了键盘套和触摸以外，它也有配套的主动式手写笔作为纸笔输入的一种仿真。</p><p>但它的手写笔体验非常有限。尽管 Windows 经过 Windows 8/8.1 的洗礼已经对手写笔的支持做到了系统级的集成，但 Windows 的主要使用场景是桌面使用，这意味着 Windows 除了微软自家产品以外，几乎没有对手写笔做支持的生态。</p><p>我试过一段时间的 OneNote 做笔记的操作，但一学期之后我放弃了：我写字很小，而这支笔的敏感程度又不够小。所以我需要写很大的字才能不让所有东西糊在一起。这种吊着一口气写字的感觉和对于笔记内容索引能力的缺失最终把我劝退，电子笔记本的计划没能在 Windows 平板上实现。</p><p>当然，但它的平板形态确实有大用：外出调设备时不需要背着一个又大又沉的游戏本，也不需要担心脱离电源之后过几十分钟就会没电。虽然性能相对而言拉跨一些，但是坚持个四五个小时是没问题的。</p><h3>Apple 平板电脑</h3><p>是的，这就是这篇文章的那碟子醋。我买了 iPad Mini 和 Apple Pencil Pro. 为了方便就称呼这两样东西为「果子板」和「果子笔」了。经过一段时间的体验，我的结论是<strong>这玩意真没我想的那么好用</strong>。但花出去的钱如同泼出去的水，既然买了，就当是响应国家扩大内需的号召算了。给自己找找借口，想办法让它能服役三五年，也算是对得起自己的一时冲动。</p><p>如果你和我一样拒绝给设备贴膜，那么果子笔写果子板的质感就是是塑料棍棍捅玻璃板。而根据网上的一些评测显示，如果你贴钢化膜，或者用塑料直尺按在屏幕上画图的话，果子笔的性能就会出现显著的异常<sup class=footnote-ref><a href=#fn1 id=fnref1>[1]</a></sup>。所以不贴膜直接戳玻璃反而是这个产品的设计使用状态。</p><p>好吧，那想象中的纸笔仿真触感就无了。线分辨率有没有什么提升呢？答案是有，但不多。</p><p>即使是 Pro 笔，它也没能逃开前面 Windows 平板电脑的「字写小了就会糊在一起」的窘况。所以我还是得吊着一口气把字写大。而我的字本身又写得很丑，这就导致越写越难看、越难看越不想写的恶性循环。从下笔的第一画，空气中就充满了「劝退」的氛围。这是连果粉自适应都救不了的那种绝望的感觉，仿佛果子笔在给我上嘲讽，质问「就你写字这 B 样也好意思用 Pencil Pro?」</p><p>换句话说，果子笔写果子板的最恐怖的问题是：因为它的精度已经足够高，所以你很难怪罪它精度不够；而又因为它不是纸笔，它会把你写字过程里带的坏习惯无限地放大，进而产生一种脱节。</p><p>举个例子：用水笔写字时，你可以不把笔尖完全抬离纸面而直接运笔到下一个笔画的起始位置，没有足够的压力，水笔是不会出水的；而数字笔如果你这样做，只会拖出一坨细线，哪怕你把写字压力调到很重，它一样会给你拖泥带水。换更高级的绘图工具来调笔刷？俺就想随手写写画画，犯不着买 Procreate 吧。</p><p>即使通过笔刷解决了写字拖泥带水的问题，还有一个更难模拟的点则是通过把笔尖轻微下压来「刹车」以控制过冲。由于塑料戳玻璃的摩擦力远小于圆珠笔戳纸，依赖这个行为写字只会导致每个笔画都走形；在画图的时候这个问题则更为明显。</p><p>我知道这都是非常不好的书写习惯，但奈何写了十年字，再不好的习惯也已经被烙在肌肉里了。怎么办呢？要么保持原来的书写习惯，接受自己在果子板上写出来的字就是狗爬；要么就一笔一划慢慢写，想办法在果子板上写出能看的字。</p><p>算了，还是爱奇艺吧。</p><h3>逃不开的主动笔</h3><blockquote><p>笔者按：网上一般会称呼「主动式手写笔」为「电容笔」、「被动式手写笔」为「电磁笔」，我个人不是很喜欢这种称呼，因为在俺接触手写技术的那个年代，「电容笔」是头上带一个软橡胶帽的、模拟人手指触摸的小棍棍。这样的实现现在已经很少见到了，但由于它在我的认知里先占用了「电容笔」这个名字，所以我只能私取其他名字来指示现在的使用的技术了。</p></blockquote><p>比对了三类平板电脑，虽然都有手写笔，但都逃不开主动式手写笔的桎梏。只要还是主动笔，那就还是无法比拟被动笔书写带来的细腻感。</p><p>通用平板逃不开主动式手写笔，核心原因或许还是大部分买平板的人并不会需要使用手写笔——毕竟，刷视频还需要在屏幕上圈圈画画么？要给被动式手写笔加支持，则需要在平板中内置一个电磁发生层，这样做的话，不仅平板厚度会增加、工艺难度会增加，制造成本更会疯狂增加。对于厂商来说显然是不划算的。再加上被动笔技术的专利仍然由 Wacom 所持有，专利授权费用又是一笔不菲的开销。综上考虑，即使像苹果这种高溢价的产品，也还是选择了投资主动笔技术。</p><p>其实比较有趣的是，初代 Apple Pencil 于 2015 年发售，网传恰好同年 Wacom 专利过期。果子这算是起了个大早赶了个晚集么？</p><p>但根据 Wacom 公司列出的专利表，其 EMR 相关的技术仍然在专利期内。即使是最早于 2003 年注册的 US7307616B2 - Sensor for coordinate input device, 其过期时间预计为 2026 年<sup class=footnote-ref><a href=#fn2 id=fnref2>[2]</a></sup>。2015 年过期的专利是太瀚科技公司注册的 US20110297458A1 - Electromagnetic pen without a battery<sup class=footnote-ref><a href=#fn3 id=fnref3>[3]</a></sup>; 当然还有一票更早的专利，比如 US5664108A - Position detection device utilizing electromagnetic induction 和 US5565632A - Pressure sensitive stylus pen. 这三个专利叠加起来，也确实可以做出带压感的被动式的电磁笔。国产数位板品牌近年来的快速发展可能也确实得益于这些专利的过期。</p><h2>「手写本」</h2><p>看来平板电脑还是只能买前生产力买后爱奇艺。那，有没有专为手写优化的电子产品呢？就是那种支持被动笔的设备。</p><h3>基于 S-Pen 技术的平板</h3><p>对，三星平板。</p><p>相比起 iPad 的实现，三星的 S-Pen 写起来就好很多。最明显的改进就是它即使是裸屏直接写也没有那一股子塑料杆子戳玻璃的感觉。它的笔头也很尖，很符合我对于一个正经的圆珠笔的所有想象——相比之下，Apple Pencil 几个毫米粗的笔尖就像一根没削的铅笔。S-Pen 的笔头也很软，即使你用笔尖戳屏幕，也不会有很清脆（或者说出戏）的嗒嗒声。</p><p>被动式手写笔还有一些额外的优点，比如可以自由更换，也不用担心充电的问题。你甚至可以买一个外壳把手写笔直接装进去，让它拥有一个真正的圆珠笔的形态。</p><p>但是，无论硬件上怎么折腾，三星平板归根结底还是一台 Android 平板。而 Android 平板的最大硬伤是软件支持。在果子平板上的那套生态，无论是 MarginNotes 还是 Notability 都一概没有 Android 版本。要实现跨平台的话，就得从 OneNote 和 Goodnote 里选了。</p><p>OneNote 或许是目前的版本答案。但用户体验上来说，它仍然不及 Apple Pencil 带来的系统级集成所带来的「符合直觉」感。最明显的就是通过手写操作输入框只能通过三星键盘完成，而它对中文手写的识别能力，尤其是识别我这种狗爬字的能力，只能说聊胜于无。你当然可以选择自行折腾，但一旦动手很容易就会落入到「为了折腾而折腾」的陷阱里。</p><p>我目前使用的型号是 Galaxy Tab S10 FE. 这是我能找到的当前在售的最小的三星平板了。10 寸出头的机身大小比 8 寸的 iPad Mini 大了两圈，使得我只能把它放到背包的电脑层里。而且这个平板的电源键摆放的位置相当痛苦：它的电源键在长边上，而且距离短边又太远。你需要伸手去够这个电源键才能按到它。</p><p><img alt=AwkwardPowerButton src=https://s2.loli.net/2025/05/05/oQaP3NOvGHCsLKW.jpg></p><p>没辙，只能先受着了。好在至少系统相对而言干净一些，也没太多这这那那的阻碍，相对而言需要折腾的部分少一些，三板斧安排上就可以直接用了。</p><h3>各类墨水屏手写本</h3><p>如果主动去找墨水屏的手写本，而且手动筛掉那些实际上使用主动式笔的产品的话，我们还有以下选择：</p><ul><li>文石 BOOX Note X3</li><li>科大讯飞智能办公本 Air2Pro</li></ul><p>但我现在手头上已经有太多平板电脑了。我实在不想继续搞这些东西了。而且最关键的是：绝大多数的评测都不会告诉你这个笔写起来究竟是什么样子的。在屏幕上三两下一画就结束战斗，甚至大部分时间不会有点评的。</p><p>也许在下一个大预算周期内，我会考虑入手这些设备来一探究竟。不过现在我只能放弃，毕竟，怎么安排上面那堆设备也还是一个需要考虑的问题。</p><h2>寻找一台电子笔记本</h2><p>目前来看，我们距离一台可以完全媲美一支水笔和一本三十二开的电子笔记本，还有很远的道路要走。</p><hr class=footnotes-sep><section class=footnotes><ol class=footnotes-list><li class=footnote-item id=fn1><p>朕宅神授. 为什么ipad画斜线有波浪？一招解决问题[V/OL]. Bilibili. (2022-09-06)[2025-03-18]. <a href=https://www.bilibili.com/video/BV1Vg411U7up/>https://www.bilibili.com/video/BV1Vg411U7up/</a>. <a class=footnote-backref href=#fnref1>↩︎</a></p></li><li class=footnote-item id=fn2><p>Wacom Co Ltd, et al. Sensor for coordinate input device[P/OL]. United States Patent and Trademark Office. US 2005/0134260 A1. <a href=https://patents.google.com/patent/US7307616>https://patents.google.com/patent/US7307616</a>. <a class=footnote-backref href=#fnref2>↩︎</a></p></li><li class=footnote-item id=fn3><p>Waltop International Corp, et al. Electromagnetic pen without a battery[P/OL]. United States Patent and Trademark Office. US 2011/0297458 A1. <a href=https://patents.google.com/patent/US20110297458A1/en>https://patents.google.com/patent/US20110297458A1/en</a>. <a class=footnote-backref href=#fnref3>↩︎</a></p></li></ol></section>]]></description>
<pubDate>Mon, 05 May 2025 01:02:57 +0000</pubDate>
<dc:creator>dousha99</dc:creator>
<guid>/archives/in-search-of-a-digital-notebook.html</guid>
</item>

    </channel>
</rss>
