<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.yichya.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.yichya.dev/" rel="alternate" type="text/html" /><updated>2026-02-22T18:58:16+08:00</updated><id>https://www.yichya.dev/feed.xml</id><title type="html">Nostalgia</title><subtitle>何以解忧，唯有暴富</subtitle><author><name>yichya</name></author><entry><title type="html">Self Hosted (2) Custom Valine Server</title><link href="https://www.yichya.dev/self-hosted-2/" rel="alternate" type="text/html" title="Self Hosted (2) Custom Valine Server" /><published>2026-02-22T00:00:00+08:00</published><updated>2026-02-22T00:00:00+08:00</updated><id>https://www.yichya.dev/self-hosted-2</id><content type="html" xml:base="https://www.yichya.dev/self-hosted-2/"><![CDATA[<p>这里评论用的 Valine，虽然架好之后没多久它就闭源了 = = 不过用着也没啥毛病就没管。结果这几天突然发现 <a href="https://console.leancloud.app/docs/sdk/announcements/sunset-announcement">LeanCloud 就要停服了</a> = = 趁过年在家的功夫搞了一下咕咕咕多年的 My Own Hackday，花了一天时间快速肝了一个最小的 Valine 兼容服务端实现</p>

<h1 id="on-premise-replacements">On-Premise Replacements</h1>

<p>当时选 Valine 的原因之一就是它非常简洁干净，并且没有什么很复杂的社交集成功能，国内访问也比较稳定（即便后面 LeanCloud 国内强制要求备案的时候迁移到国外了也还可以接受）。相比之下老牌工具 Disqus 现在猛猛加私货广告，根本没法看；至于比如 utterances 之类的工具倒是也非常简洁干净，但是它绑死 GitHub 还要求登录，也不太方便数据迁移，这对草民来说也算是一票否决</p>

<p>这次 LeanCloud 停服，也属于草民一直关注的下云案例（只是没想到这么快就下到草民自己头上），再选方案的话当然必须全自建了</p>

<h2 id="waline">Waline</h2>

<p>貌似是个比较平滑的切换方案，从名字上就很容易看得出来跟 Valine 颇有渊源（事实上也是如此</p>

<ul>
  <li><a href="https://waline.js.org/guide/get-started/">https://waline.js.org/guide/get-started/</a>（这里有评论区 Demo</li>
  <li><a href="https://github.com/JQiue/waline-mini">https://github.com/JQiue/waline-mini</a>（原版服务端是 TypeScript，也有人做了第三方的 Rust 后端，不过貌似没啥人用</li>
</ul>

<p><img src="../assets/images/self-hosted-2/waline.png" alt="" /></p>

<p>看了一下官网 Demo 感觉有几点不太符合草民胃口：</p>

<ul>
  <li>实在是太重了：社交集成、用户标签、文章反应，乱七八糟的功能过多，远不如 Valine 简洁</li>
  <li>客户端文件走公共 CDN 托管，服务端自备甚至可能还是第三方的，说不定什么时候就会出点兼容性问题</li>
</ul>

<p>虽然 Waline 确实解决了 Valine 存在的一系列问题（主要是安全相关），但额外加的东西又确实是太多了，并不能满足需求</p>

<h2 id="artalk">Artalk</h2>

<p>官方后端是 Go 实现的 <a href="https://artalk.js.org/">https://artalk.js.org/</a>，也直接提供了自托管的客户端 JS 文件，跟 Waline 相比运维起来更方便一些</p>

<p><img src="../assets/images/self-hosted-2/artalk.png" alt="" /></p>

<p>这个部署相当平滑，花了十来分钟就完成了对 Valine 评论和计数器两个功能的替换（不包括数据迁移），但也有几点感觉不太好：</p>

<ul>
  <li>上传图片完全没有任何限制（虽然可以一刀切直接关掉这个功能</li>
  <li>还是略重，比如什么社交集成、各种推送之类的功能用不上</li>
  <li>侧边栏草民不怎么喜欢，尤其是连个开关都没有这件事</li>
</ul>

<p>草民博客流量极低，可能一天只有个位数请求，开个这玩意儿绝大多数时候都是空跑，还是多少有点浪费，而且这玩意儿一年半没有发新版本了，上次代码变更也是大半年前了（甚至官网自己部署的 Artalk 服务端证书都过期了 = =），感觉也相对不算靠谱</p>

<h2 id="other">Other</h2>

<p>类似上面两个的 <a href="https://twikoo.js.org/intro.html">Twikoo</a> 也有比较重的问题，而且它只有 TypeScript 服务端，这里就不专门搞了。另外还搜到了一个利用 <a href="https://usememos.com/">Memos</a> 的接口的<a href="https://www.hollowman.cn/archives/memostest.html">方案</a>，问题主要在于前端还得自己写，就算有 AI 感觉也还是会很麻烦，还是算球了</p>

<p>顺便一提，Memos 其实很符合草民对 RSS Pipe 上一些额外功能的需求，只是还是不够简洁（所以草民才做了「文件传输助手·青春版」），功能上也不如类似 ChatGPT 那种 Sessions 的形态组织起来更高效：即便有 AI 加持的情况下草民也不认为人类能长期、频繁跟进 20+ 个不同事项，所以 ChatGPT 之类左边列出十几二十个近期 Session 的形态足够好用；再复杂的需求可以通过全文检索甚至 AI 辅助的搜索完成。当然 Memos 已有的一些特性，比如标签 / 按内容类型分类之类简单的管理工具倒是也能用得上，Sessions 的形式也可以展示在日历上并搭配一些自动 / 手动操作刷新变更时间。可能后面再扩展 RSS Pipe 的能力的时候会考虑一下做一点类似的实现</p>

<h1 id="custom-valine-server">Custom Valine Server</h1>

<p>搜到的几个方案都有些偏重，于是也起了直接替代掉 LeanCloud 的想法。似乎并不难做，但抓接口得知 Valine 同时进行了三种 CRUD 方式</p>

<ul>
  <li>REST 形式的接口
    <ul>
      <li>发送评论</li>
      <li>创建阅读计数器</li>
      <li>修改阅读计数器</li>
    </ul>
  </li>
  <li>一个很类似 MongoDB 的语法
    <ul>
      <li>获取评论总数</li>
      <li>分页获取评论</li>
      <li>获取阅读计数器</li>
    </ul>
  </li>
  <li>CQL（一个很类似 SQL 的语法）
    <ul>
      <li>获取楼中楼评论（这个真的很奇怪，不知道为什么只有这一个特例，而且按说也完全可以用上面的形式实现</li>
    </ul>
  </li>
</ul>

<p>评价为这玩意儿也让人感觉不是很靠谱，再加上还不开源……但它又确实足够简单，刚好过年在家呆一周没有很好的网络条件（虽然后面又想起来家里还有个老伙计还能发挥下余热，很快就把网络搞好了），于是决定把咕咕咕了多年的 My Own Hackday 搞起来，花一天时间在 RSS Pipe 的基础上进行扩展，目标是实现 Valine 可能用到的所有 LeanCloud API 用法的最小集合</p>

<h2 id="analysis">Analysis</h2>

<p>虽然 Valine 操作 LeanCloud 的方式很多，但实际上每一种都使用了非常固定的形态去请求，分析之后完全可以在服务端做针对性实现</p>

<h3 id="relations">Relations</h3>

<p>Valine 在 LeanCloud 上定义了 <code class="language-plaintext highlighter-rouge">Comment</code> 和 <code class="language-plaintext highlighter-rouge">Counter</code> 两个表（实际上更像是 MongoDB 的 Collection，包括主键也叫 <code class="language-plaintext highlighter-rouge">objectId</code>，但这里就还当是表好了）。有的旧版本可能还会操作内置的 <code class="language-plaintext highlighter-rouge">_User</code>，新版没有这个了，这里以 1.5.3 为准，本次也只实现这两个表的操作</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Counter</code> 没啥说的，跟具体文章通过 <code class="language-plaintext highlighter-rouge">URL</code> 一一对应起来，然后就是 <strong>+1</strong> <strong>+1</strong></li>
  <li><code class="language-plaintext highlighter-rouge">Comment</code> 跟具体文章通过 <code class="language-plaintext highlighter-rouge">URL</code> 组合成一对多关系，然后再通过 <code class="language-plaintext highlighter-rouge">rid</code> 和 <code class="language-plaintext highlighter-rouge">pid</code> 建立楼中楼关系
    <ul>
      <li><code class="language-plaintext highlighter-rouge">rid</code> 是主楼的 <code class="language-plaintext highlighter-rouge">objectId</code>，会用于检索，实现时要考虑能被过滤（以及可能的索引）的存储形式</li>
      <li><code class="language-plaintext highlighter-rouge">pid</code> 的关系稍微有点乱，不过它不用于前后端交互时的检索，实现时只需要原样记录、传递就可以了</li>
      <li>因为 Valine 可以直接粘贴图片（实际上是作为 data URL 处理的），评论的内容可能会很长，考虑存储的时候要注意数据类型</li>
    </ul>
  </li>
</ul>

<p>总的来说也比较简单，随便放在什么类型的数据库上都很容易实现，草民这里就继续选择 RSS Pipe 已经用了的 SQLite</p>

<h2 id="implementation">Implementation</h2>

<p>上面有提到继续在 RSS Pipe 的基础上开发，原因有以下几个：</p>

<ul>
  <li>可以对接 RSS Pipe 已有的一些能力（比如 Bark 推送、正式发布时自动清零计数器等等</li>
  <li>已经实现的「文件传输助手·青春版」的表结构只需要简单调整就能适配上面的关系</li>
  <li>可以利用这套一对多的关系来实现对 RSS Pipe 现有能力的一些扩展
    <ul>
      <li>针对「文件传输助手·青春版」，可以实现「回复消息」与「Session 管理」两个相当有用的功能</li>
      <li>针对 RSS 内容可以扩展出私有的评论 / Bot 能力，类似 GitHub Actions 在 issue 区的互动
        <ul>
          <li>比如利用 RSSHub 从☁️拉取到了新歌，可以直接在下面评论 <code class="language-plaintext highlighter-rouge">/download</code> 通知 Bot 进行下载</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>为了理清思路，实际的实现过程中除去少许代码片段之外基本没有使用 AI 辅助，过程中也确实觉得没有 AI 的话产能低了好多（</p>

<h3 id="rest">REST</h3>

<p>啥技巧都没有，简简单单一搓完事</p>

<ul>
  <li>创建评论：<code class="language-plaintext highlighter-rouge">POST /1.1/classes/Comment</code></li>
  <li>创建阅读计数器：<code class="language-plaintext highlighter-rouge">POST /1.1/classes/Counter</code></li>
  <li>修改评论计数器：<code class="language-plaintext highlighter-rouge">PUT /1.1/classes/Counter/&lt;objectId&gt;</code></li>
</ul>

<p>具体的 Body 也很简单就不贴了，自己抓一下就好。一点细节：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">objectId</code> 的格式并不需要跟 MongoDB 的默认格式一致，Valine 也不会在客户端生成这个，草民这里直接改用 SQLite 的自增主键</li>
  <li>POST 的两个请求中会有一个 <code class="language-plaintext highlighter-rouge">ACL</code> 字段，服务端解析的时候直接忽略即可，不需要去纠结它的类型</li>
  <li>PUT 的 Body 的内容大致就是 <code class="language-plaintext highlighter-rouge">time += 1</code>，实际上完全不需要解析
    <ul>
      <li>Valine 针对这个还有个安全问题，就是可以随意构造请求去修改这个 Counter，不解析这个也可以顺便解决这个问题</li>
    </ul>
  </li>
</ul>

<h3 id="mongodb-like">MongoDB Like</h3>

<p>三个查询的请求都只用到了一个非常简单的条件，以查询这篇内容的评论与阅读数量为例</p>

<ul>
  <li>查询请求都是 <code class="language-plaintext highlighter-rouge">GET</code>，参数用 Query String 传递
    <ul>
      <li>评论：<code class="language-plaintext highlighter-rouge">GET /1.1/classes/Comment</code></li>
      <li>计数器：<code class="language-plaintext highlighter-rouge">GET /1.1/classes/Counter</code></li>
      <li>排序 <code class="language-plaintext highlighter-rouge">order</code> 均为创建时间倒序，但实际上不需要解析具体排序条件，实现的时候写死即可</li>
    </ul>
  </li>
  <li>评论查询由两步完成，所以这里总共要实现三种请求
    <ul>
      <li>一个入参包含 <code class="language-plaintext highlighter-rouge">count</code>，仅用于查询评论总数不获取具体内容</li>
      <li>另一个入参包含 <code class="language-plaintext highlighter-rouge">limit</code> 以及可能的 <code class="language-plaintext highlighter-rouge">skip</code>，用于分页获取评论</li>
    </ul>
  </li>
  <li>评论和计数器查询都用到的过滤条件：<code class="language-plaintext highlighter-rouge">where={"url": "/self-hosted-2/"}</code>
    <ul>
      <li>计数器最多只会查到 1 个，没查到的话会调用上面的 REST 接口创建一个并写入 <code class="language-plaintext highlighter-rouge">1</code></li>
    </ul>
  </li>
  <li>评论查询时会在上面的 <code class="language-plaintext highlighter-rouge">where</code> 里额外增加一个过滤掉楼中楼的条件：<code class="language-plaintext highlighter-rouge">{"$or":[{"rid":{"$exists":false}},{"rid":""}]}</code>
    <ul>
      <li>因为楼中楼只有下面的一种查询方式，实际上这里也并不需要解析这个嵌套了几层的条件，在服务端直接实现这个过滤就行了</li>
    </ul>
  </li>
</ul>

<p>这三个请求可以通过 Path 和入参带不带 Count 进行非常简单的区分，因此大部分复杂解析都不需要做，知道是哪个请求并针对实现即可</p>

<h3 id="cloudquery">CloudQuery</h3>

<p>这个针对楼中楼的查询解析起来稍微复杂一点，要把里面的 <code class="language-plaintext highlighter-rouge">rid</code> 硬抠出来。至于排序一样不用管，实现的时候写死就行了：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="k">Comment</span> <span class="k">where</span> <span class="n">rid</span> <span class="k">in</span> <span class="p">(</span><span class="nv">"1x"</span><span class="p">,</span><span class="nv">"2y"</span><span class="p">,</span><span class="nv">"3z"</span><span class="p">)</span> <span class="k">order</span> <span class="k">by</span> <span class="o">-</span><span class="n">createdAt</span><span class="p">,</span><span class="o">-</span><span class="n">createdAt</span>
</code></pre></div></div>

<p>因为查询比较固定，草民实际做的时候直接切了切字符串拿出来完事了。注意返回值有一个固定的字段 <code class="language-plaintext highlighter-rouge">{"className": "Comment"}</code>，不加上的话 Valine 不认。都完事之后给一个 <code class="language-plaintext highlighter-rouge">GET /1.1/cloudQuery</code> 的路由即可。不过另外还有一个问题原来用 LeanCloud 的时候 <code class="language-plaintext highlighter-rouge">rid</code> 是一个并不太容易枚举的 <code class="language-plaintext highlighter-rouge">objectId</code>，而这里的重新实现图省事直接用 SQLite 的自增主键，会导致这个接口可以枚举所有的评论数据，因此至少在实现的时候还需要把下面的 appid 相关检查做好（当然也可以改用类似 <code class="language-plaintext highlighter-rouge">objectId</code> 的生成器，但是对简单业务来说有点太麻烦了</p>

<h3 id="authentication-and-cors">Authentication and CORS</h3>

<p>LeanCloud 的鉴权传了两个字段，一个 appid <code class="language-plaintext highlighter-rouge">X-LC-ID</code> 和一个计算出来的签名 <code class="language-plaintext highlighter-rouge">X-LC-SIGN</code>；Valine 说白了压根不需要这么复杂的流程，也不太可能逆向出来 <code class="language-plaintext highlighter-rouge">X-LC-SIGN</code> 的相关逻辑，因此简单做一个自包含验证并且不跟 LeanCloud SDK 冲突的 appid 就可以了：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">X-LC-ID</code> 是 24 个字符加一条横线再加 8 个字符</li>
  <li>这个 appid 需要能对应到一个 RSS Pipe 的 <code class="language-plaintext highlighter-rouge">feed</code>，成本最低的方式是直接利用它传递 <code class="language-plaintext highlighter-rouge">feed_id</code></li>
  <li>为了避免伪造，利用 RSS Pipe 一个之前就有的启动参数 <code class="language-plaintext highlighter-rouge">auth</code> 跟 <code class="language-plaintext highlighter-rouge">feed_id</code> 拼接起来，再计算一个 hash 用作签名</li>
</ul>

<p>省事儿起见 hash 选了 md5，按流 base64 出来刚好 24 个字符（最后两个是等于号，但并不太影响）；后面 8 个字符是 <code class="language-plaintext highlighter-rouge">feed_id</code> 的十六进制表示，刚好 int32，对于 <code class="language-plaintext highlighter-rouge">feed_id</code> 来说肯定够用了。也尝试在里面掺了 base64 的其他字符，实测不影响 LeanCloud SDK 工作</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gVbhGkTgtIgfaI6pD0Xr5A==-00000065
</code></pre></div></div>

<p>CORS 没啥特别的，别忘了就行，图省事儿的话就全都 <code class="language-plaintext highlighter-rouge">*</code></p>

<h2 id="deployment">Deployment</h2>

<p>LeanCloud 从几年前就已经强制要求绑定用户自己的域名了，所以部署也很简单，把 RSS Pipe 通过无论什么方式（内网穿透 / VPS 部署等等）挂在这个域名上面即可；或者如果 RSS Pipe 已经挂在某个其它域名上了的话，就修改一下 Valine 的初始化配置</p>

<p>针对客户端部分，LeanCloud 官方不再提供服务，在公共 CDN 上托管的 SDK 说不定在未来的某个时间点就会被撤下来；Valine 也有类似的可能性，所以最好是把目前确定可用的前端文件保留一份自用。虽然 Valine 内部自己决定了如何加载 LeanCloud SDK（目前应该是写死了 jsdelivr），但是它也会提前检查 <code class="language-plaintext highlighter-rouge">window.LC</code>，所以可以提前自行初始化 LeanCloud SDK，就如下面这样直接加载进来就好了。另外，Valine 目前不开源，为了确保来源可信，比较推荐的方式是自行从官方提供的 CDN 地址下载两个 js 文件（注意版本号）并自行管理</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">'/assets/js/av-min.js'</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="c">&lt;!-- https://cdn.jsdelivr.net/npm/leancloud-storage@3/dist/av-min.js --&gt;</span>
<span class="nt">&lt;script </span><span class="na">src=</span><span class="s">'/assets/js/Valine.min.js'</span><span class="nt">&gt;&lt;/script&gt;</span> <span class="c">&lt;!-- https://unpkg.com/valine@1.5.3/dist/Valine.min.js --&gt;</span>
</code></pre></div></div>

<h2 id="data-import">Data Import</h2>

<p>因为主键从 <code class="language-plaintext highlighter-rouge">objectId</code> 变成了自增的关系，数据迁移会涉及到一个针对全量数据的主键重新映射</p>

<ul>
  <li>先从 LeanCloud 导出所有数据</li>
  <li>选择一个未使用的主键范围对 <code class="language-plaintext highlighter-rouge">objectId</code> 做映射（停服以避免可能的主键冲突，或者拉一个足够大的间隔出来</li>
  <li>生成 SQL 后手动批量导入</li>
</ul>

<p>对于草民这种数据总量区区一百来行的情况，直接拖进 Excel 里面查找替换，完事儿复制粘贴到 SQLite 里面齐活</p>

<h2 id="better-integration-with-rss-pipe">Better Integration with RSS Pipe</h2>

<p>写作的过程中偶尔还是会需要提前拉起 Jekyll 预览生成效果，但这又会导致阅读量计数器产生变化。为了记录更准确的阅读量数据，往往还需要在正式将内容推送到托管平台之后手动到 LeanCloud 上重置计数器。RSS Pipe 就能很好解决这个问题：工具只会拉取托管到平台的 RSS，因此在每次拉取到内容的时候搜索 <code class="language-plaintext highlighter-rouge">url</code> 匹配但 <code class="language-plaintext highlighter-rouge">title</code> 和 <code class="language-plaintext highlighter-rouge">author</code> 都为空（Valine 虽然定义了 <code class="language-plaintext highlighter-rouge">title</code> 字段，但是目前的版本并不会在调用服务端时传递它；<code class="language-plaintext highlighter-rouge">author</code> 更是只能通过 RSS 拿到；两个都过滤主要还是考虑到比如 RSSHub 抓取到的一些内容可能因为各种原因缺失部分信息）的对应记录，如果存在的话直接更新对应记录的未填写字段并将 <code class="language-plaintext highlighter-rouge">counter</code> 设置为 0 即可</p>

<h1 id="more-about-rss-pipe">More About RSS Pipe</h1>

<p>最近还在「文件传输助手·青春版」的基础上加了简单的图库（AI 生成前端代码真的很方便，写好几个对接后端的 API，前端几乎秒出</p>

<p><img src="../assets/images/self-hosted-2/grid.png" alt="" /></p>

<p>下一步考虑：</p>

<ul>
  <li>Vibe 一个有点像绿泡泡的朋友圈的 UI，对接上面的博客评论能力</li>
  <li>完善一点「文件传输助手·青春版」的界面细节，更便于检索历史内容</li>
</ul>

<p>上面提到的 RSS Pipe 部分改动在 GitHub <a href="https://github.com/yichya/rss_pipe/pull/7">yichya/rss_pipe#7</a>，不过其中还不包含「文件传输助手·青春版」（因为还差不少东西没有收拾干净），而且目前也还不能开箱即用，主要是还需要手动创建数据库之类。这部分的自动化相关也放在后面处理吧（</p>

<h1 id="next">Next</h1>

<p>过年难得回了家一次（而且说实话可能再回家的次数两只手数得过来了），于是多写了一点东西，会在下周末（3.1）发出来。</p>]]></content><author><name>yichya</name></author><category term="My Own Hackday" /><category term="NAS and OpenWrt" /><category term="nas" /><category term="self-hosted" /><summary type="html"><![CDATA[这里评论用的 Valine，虽然架好之后没多久它就闭源了 = = 不过用着也没啥毛病就没管。结果这几天突然发现 LeanCloud 就要停服了 = = 趁过年在家的功夫搞了一下咕咕咕多年的 My Own Hackday，花了一天时间快速肝了一个最小的 Valine 兼容服务端实现]]></summary></entry><entry><title type="html">About My 2025</title><link href="https://www.yichya.dev/about-my-2025/" rel="alternate" type="text/html" title="About My 2025" /><published>2025-12-31T00:00:00+08:00</published><updated>2025-12-31T00:00:00+08:00</updated><id>https://www.yichya.dev/about-my-2025</id><content type="html" xml:base="https://www.yichya.dev/about-my-2025/"><![CDATA[<p>年常系列。开头惯例，先看一下今年 Blog 更新计划完成咋样：</p>

<table>
  <thead>
    <tr>
      <th>↘</th>
      <th>Category</th>
      <th>Comment</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>❌</td>
      <td>Life</td>
      <td>也就只有老家的资产处置算是有些进度，其它着实是没啥可说的，所以也就没有新内容了</td>
    </tr>
    <tr>
      <td>❌</td>
      <td>My Own Hackday</td>
      <td>虽然有在例行推 RSS Pipe 但还没整理出来什么东西，不过是有计划了，明年一定</td>
    </tr>
    <tr>
      <td>✅</td>
      <td>NAS and OpenWrt</td>
      <td><a href="../real-nas-project-4/">Real NAS Project 4</a> 和 <a href="../tinc-improvements">Tinc Improvements</a> 两个组网相关优化，算是例行迭代</td>
    </tr>
    <tr>
      <td>✅</td>
      <td>Gadgets</td>
      <td><a href="../gadgets-2024/">Gadgets 2025</a>，然后龙芯小主机又咕咕咕了一年，目前来看是没什么机会了，等到 <a href="https://www.phoronix.com/news/Debian-LoongArch64-Official">2027 年 Debian 14 出来</a>再搞吧（</td>
    </tr>
    <tr>
      <td>✅</td>
      <td>Play Around</td>
      <td><a href="../vacation-2025-1/">Vacation 2025.1</a>、<a href="../vacation-2025-2/">Vacation 2025.2</a> 和 <a href="../guilin-experience">Guilin Experience</a>，总之就是比去年浪的更狠了</td>
    </tr>
    <tr>
      <td>✅</td>
      <td>Technology</td>
      <td><a href="../start-headless-chromium-on-demand/">Start Headless Chromium on Demand</a> 是 RSS Pipe 的延伸，<a href="../shells-and-terminals/">Shells and Terminals</a> 也算是一点还挺有用的东西</td>
    </tr>
  </tbody>
</table>

<p>回看过去一年，着实也没解锁什么新成就，跟各位老板相比称得上一事无成。摆了摆了。开始之前先叠个甲：以下内容充满各种暴论，轻喷</p>

<h1 id="big">Big</h1>

<p>今年的大环境依然是全球右转、经济下行，甚至显得比去年更加糟糕了。虽然说这种话题已经懒得年年复读了，但不得不说草民周边的门面和写字楼空置率也是越来越高，比如公司对面的一家的偏北方口味的面馆，大概是因为一些经营问题，即便味道很不错、生意也还凑合，还是在年中被转手出去变成了一家没啥特色的重庆小面，味道平平不说客单价还贵，人流量可想而知还不如从前，估计过不到一年又要跑路。今年的国际形势之紧张个人感觉也能算是新世纪以来的新记录，因此草民闲暇时间消费的鉴证内容也多了一些，算是一种大脑升级（？</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/about-my-2025/head.png" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/tail.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>年初一张万能公式</td>
      <td>年末一张盘点总结</td>
    </tr>
  </tbody>
</table>

<p>虽然确实觉得能学到一些东西，至少也多个看事情的角度，但也像某位老板说的那样，看多了、听多了之后剩下的更多是一种深深的无力感</p>

<p>AI 当然也是个绕不过去的话题。怎么说呢，除了年初 DeepSeek 火了一顿之外，之后这一年过去感觉跟去年也没什么本质上的区别，还是那样国内国外几个大厂出一堆大差不差的产品来回卷，卷了一年除了一直刷榜之外，感觉也没卷出什么跟去年明显不一样的新名堂出来</p>

<p><img src="../assets/images/about-my-2025/circle.jpg" alt="" /></p>

<p>比较有意思一点的大概算是多模态的一些工程化产品，比如 Sora 和 Nano Banana 确实让梗图、梗视频生产快了很多，但互联网上的这些东西本来就已经是远远超载的了，再用 AI 生成一大堆这种东西除了图一乐之外并没有什么很实际的意义，看多了只会觉得越来越烦。同时这些工具事实上很可能并不像它们所宣称的那样提升了生产力与信息流动效率，而是恰恰相反。考虑 2025 年的典型信息流动过程：</p>

<ul>
  <li>原始内容：最准确、信息密度最高</li>
  <li>传播者先用生成式 AI 洗一遍，混入不确定性 / 传播者刻意增加推广内容导致准确率降低、转换为视频等其它形式导致信息密度降低</li>
  <li>上述内容大量混入平台搜索与推荐流，高质量、高信息密度的原始内容与大量蹭热度内容混合，原始内容被检索到的概率显著降低</li>
  <li>平台获取内容过多，且混入大量不相关 / 低质量内容导致信息过载，用户主动 / 被动使用 AI 总结等能力，进一步降低内容的准确率</li>
</ul>

<p>虽然 ChatGPT 类的 AI 应用确实比传统搜索引擎方便，但由于其固有的不确定性也导致事实核查变得越来越重要（现在拿着一张 DeepSeek 截图在社区里乱跳的小鬼实在是太多太多了），而 AI 生成内容充斥网络又导致事实核查变得越来越困难。如果再考虑到事实核查过程中受到推荐系统（怎么不算一种 AI 呢，只不过不是生成式罢了）被反复诟病的「信息茧房」问题的影响，信息的有效流动就更是难上加难</p>

<p><img src="../assets/images/about-my-2025/slop.png" alt="" /></p>

<p>目前卷来卷去的 AI 实话讲已经让开放互联网逐渐成了个笑话：一边放任用户用 AI 生成大量的辣鸡内容，同时针对视频之类信息密度偏低且不易检索的内容进行搜广推相关权重的提升，使得用户越来越难以获取有效信息，不得不在平台停留更长时间以达成平台所期望的 DAU 指标增长；一边又在使劲儿做风控做反爬将用户锁死，比如某红色平台、某绿色平台和某蓝色平台几乎完全阻止传统搜索引擎等工具的索引，导致已经非常严重的信息孤岛现象进一步恶化。实话讲，经历过十几年前互联网「黄金时代」的话，对现在的互联网真的很难不感到失望</p>

<p><img src="../assets/images/about-my-2025/scrape.png" alt="" /></p>

<p>AI 发展还导致一个非常难绷的事情就是内存价格暴涨数倍。偶然看到一句话：这种情况下 Apple 的内存价格甚至都开始显得良心起来</p>

<p><img src="../assets/images/about-my-2025/memory.jpg" alt="" /></p>

<p>经历了这几年的 AI 发展，有时候也真的忍不住在想，十几年后回看这一波汹涌浪潮，究竟是像前面几次工业革命那样实现了生产力的极大提升，还是如千禧年前后的互联网泡沫一样最终变成一地鸡毛，甚至可能是更糟糕的赛博朋克般加剧了巨头垄断？从草民自己的感受来说，生产力提升感知不强，AI 垃圾内容成倍增加、工作岗位因 AI 明显减少、部分计算机配件越来越贵倒都是实打实的，因此真的很难乐观起来</p>

<h1 id="work">Work</h1>

<p>虽然从一个老网民的角度来说，实在是很难不对目前的 AI 发展产生一些反感，但作为沾那么一点点边的从业者又不得不说 AI 带来了新的业务增长点。公司的业务重心切换到 AI 相关的一些应用，草民负责的事情中搭基础的相对少了一些，大部分事情都在一些定制相关的事情上（然后不得不说大部分事情坑都比较多，毕竟每家客户都会有自己积累下来的一系列东西要理解学习，再喂给 AI 做事；但是能拿到的这些东西质量往往都相当差劲，沟通成本远比老板最早规划中的预期要高，需要反复拉扯才能完成交付）。目前公司在 AI 这部分选定的方向个人觉得还是有一些空间，而且能跟公司现有的业务叠加获得一些先发优势，感觉还是有机会的，现在还是想看看能在这条路上走多远</p>

<p>至于日常干活，去年也提到过要尝试一些 AI 提效，当时的结论是「用来开新坑值得一试，但是在现有项目上的迭代就不太可用」。现在一年过去，实话说草民是真没觉得这个结论有什么变化。草民日常现在会用 AI 做一些简单重复、不用费脑子写 Prompt 的事情，举两个例子：</p>

<ul>
  <li>将一个 SQL 转换为等价的 GORM 代码</li>
  <li>基于 OpenAPI 文档生成 Python 的调用代码</li>
</ul>

<p>这两件事应该说都是比较简单、规模有限、结果可验证的任务，但实话说即使如此，这两件事 2025 年年底最新的模型做的也都一般般：</p>

<ul>
  <li>简单的 SQL 出来的效果很不错，但稍微复杂一点的 SQL，比如涉及到多个子查询、窗口函数等情况时往往就会翻车，而因为代码不是自己写的，对着 AI 生成出来的一大坨东西几乎是完全没有解决的思路，硬要修的话最终要花的时间甚至可能比从头自己写还多</li>
  <li>基于文档生成代码则往往因为文档自身的问题（比如缺少术语表导致一个特定的中文名词有很多种不同的英文翻译，或者文档中给出的某一字段却在示例中被遗漏，甚至文档里的很多细节干脆就直接是错的）变成一种纯粹赌概率的行为，后续仍需大量人力去拉扯</li>
</ul>

<p>以上这些还只是发生在单个项目中的迭代。跨多个项目的迭代往往涉及到一些架构设计上的取舍，比如某类功能表面上看起来实现在模块 A 和 B 里面都行，但不同的开发者给 AI 输入的 Prompt 略有不同（甚至是 Prompt 完全相同的情况下也不一定能保证结果完全稳定）就可能导致 A 和 B 里面都散落了一部分职责相近的功能逻辑。如果整个系统的架构 / 项目本身的框架设计不到位，无法对 AI 应该修改的范围做出合理管控以规避此类问题，日常迭代的过程中又没能在前置设计阶段和后置验证阶段识别并处理掉这类问题的话，久而久之就会导致整个架构逐渐向着完全不可控的方向滑落，最终彻底变成一团乱麻。事实上真正的开发工作中，大部分的精力也往往就花费在设计与验证两个阶段</p>

<ul>
  <li>传统工作流：设计（思考某个东西应该怎么实现，30%）-&gt; 实现（写代码，30%）-&gt; 验证（Debug、测试、Code Review，40%）</li>
  <li>引入 AI 的工作流：设计（同上，30%）-&gt; 交给 AI 实现（写 Prompt，10%）-&gt; 验证（同上，但开销显著高于传统工作流，60%）</li>
</ul>

<p>上述精力分配甚至还是理想情况，实际上与直接写代码比起来，跟 AI 反复拉扯能不能节省精力还真不一定，但验证是实打实的更费劲</p>

<p><img src="../assets/images/about-my-2025/top.jpg" alt="" /></p>

<p>老板们当然会希望 AI 能像田螺姑娘一样节省大量人力成本，自媒体壬也会为了卖课之类的利益诉求不停的贩卖焦虑、吹嘘所谓「十倍生产力」，但只有实际干活的人才知道真实的业务流程充满了拉扯与妥协，在人工💩山上堆 AI 生成的💩山会是一种多么令人绝望的事情</p>

<p><img src="../assets/images/about-my-2025/vibe.png" alt="" /></p>

<p>关于之前提过的下云，今年其实也毫不意外，降本增效的趋势下 GCP、Azure 和 AWS 都炸了个大的；比较意外的大概算是 Cloudflare 连炸两次，一次 <a href="https://blog.cloudflare.com/zh-cn/18-november-2025-outage/">Rust 写的 FL2 随手一个 <code class="language-plaintext highlighter-rouge">.unwrap()</code> 遇上脏数据</a>，另一次 <a href="https://blog.cloudflare.com/zh-cn/5-december-2025-outage/">Lua 写的 FL1 少检查一个 <code class="language-plaintext highlighter-rouge">nil value</code></a>，这下某些人左右脑互搏了</p>

<p><img src="../assets/images/about-my-2025/cf1.jpg" alt="" /></p>

<p>于是著名梗图加强版就来了。一个云平台的故障就可能一波带走几十上百家公司的不同业务，但无论是小企业还是云平台都在应对越来越高的成本压力，一边削减人力一遍高强度引入 AI，运维领域尤甚，导致这年头的互联网感觉比五年甚至十年前还要脆弱</p>

<p><img src="../assets/images/about-my-2025/cf2.jpg" alt="" /></p>

<p>AI 一定程度上逆转了下云的趋势，毕竟 LLM 比传统的互联网基础设施更需要靠规模去压成本（于是带来了内存之类的严重短缺），这个下云确实尚早。不过因为使用的公有云设施更加不稳定（模型、插件、平台之类高频迭代），以及考虑部分公司更高的数据安全需求，公司也在着手探索私有化部署 LLM，好在目前开源模型无论是部署成本还是推理效果都凑合，而且大部分业务 RAG 加 Prompt Engineering 足够</p>

<h1 id="mainline">Mainline</h1>

<p>去年的预期是按兵不动，除了老家的资产处置想办法推一推之外避免整其它的活。实际上也跟预想的差不多，老家的资产处置终于有了些进展，春节计划回去再看看；其它重大决策今年突出一个能拖就拖，当然也为此没少跟父母争吵，没办法，就目前这个形势包亏的，甚至存在归零风险。好不容易卷这几年下来有了那么一点存粮，在这么个大环境下可一定要万分谨慎，不然恐怕真的要挨斩杀线了（</p>

<p>去年买的房子三月前后倒是租出去了，租金也完全足够覆盖贷款，估计明年暂时也用不上，还是接着租吧。旁边的医院和公园进度也算喜人：医院差不多竣工了，明年年底左右开始营业；公园一期工程也开始了，预计明年上半年就能耍上，二期就不急了慢慢来</p>

<p><img src="../assets/images/about-my-2025/park.jpg" alt="" /></p>

<p>年中去了香港（算是这辈子第一次出海关吧，虽然前后加起来也不到八个小时）办了几张卡。虽然是本着机票效益最大化的想法做的，不过也顺便把能开的账户之类都开了，后续也计划一下，考虑增值 / 灵活性 / 风险规避之类有没有能够用的上的地方</p>

<p>其它的，目前暂时对明年主线怎么推还没什么想法，大方向应该还是求稳吧，不过或许会有几个机缘可以抓一下</p>

<h1 id="health">Health</h1>

<p>身体总体上比去年好一点，主要归功于确实瘦了一些。买了体脂秤，要向着 20% 以下的体脂率努力。弓箭耍了半年余，30 米基本上勉勉强强，可惜后面公司的事情忙了起来，加上开销确实略大暂时搁置了，Flag 也是理所当然的没立住，明年再搞</p>

<p><img src="../assets/images/about-my-2025/archery.jpeg" alt="" /></p>

<p>今年当然还是逃不过例行看牙，重做了口腔 CT 结果还是发现有几个侧面情况不太好，补了一个感觉也不是很满意，在某些特定情况下还是略有不适，明年可能还需要再收拾一两次。大家还是要注意口腔健康，尤其是记得刷牙之后使用牙线 / 冲牙器清理缝隙啊</p>

<h1 id="play-around">Play Around</h1>

<p>今年比去年浪的狠多了。一直想去的「泼墨漓江」达成了 <a href="../guilin-experience/">Guilin Experience</a></p>

<p><img src="../assets/images/guilin-experience/2100003.jpeg" alt="" /></p>

<p>一直想见的🐦和🐰终于见到了 <a href="../vacation-2025-1/">Vacation 2025.1</a>（不过这张图其实是八月份的</p>

<p><img src="../assets/images/vacation-2025-2/0810.jpeg" alt="" /></p>

<p>还有王子和安静等等也都见到了 <a href="../vacation-2025-2/">Vacation 2025.2</a>，总的来说真的是玩的很开心的一年</p>

<p><img src="../assets/images/vacation-2025-2/0824.jpeg" alt="" /></p>

<p>明年目前计划中的 Live 的大概四五场的样子，包括 ChiliChill、银临、黄黄、🐦和🐰应该都有戏，其它的除了旺季之前挑战一次当天来回九寨沟之外暂时没其它想法，老板们有想法可约（</p>

<h1 id="collection">Collection</h1>

<p>例行更新一下宅男快乐柜。由于空间确实非常紧张，今年开预售的流萤手办就没买了（目前大概只想要花火和风堇</p>

<p><img src="../assets/images/about-my-2025/collection.jpeg" alt="" /></p>

<p>除了 Gadgets 2025 和 Vacation 2025.1 / 2025.2 里面提到的小玩意和碟，今年新买的塑料小人和设定集：</p>

<ul>
  <li>第二排：拿弓的「三月七·存护」和端茶的「三月七·巡猎」，以及夹在中间的「黍」</li>
  <li>第三排：符玄（斯哈斯哈）和右边小格子里的青雀</li>
  <li>最下面设定集里面觉得最好的两个是《大地巡旅》以及黑猴的《影神图》，其它的还有（大致按收货日期排序）：
    <ul>
      <li>《新神榜：杨戬》艺术设定集</li>
      <li>《小倩》官方艺术设定集</li>
      <li>《雄狮少年 2》电影艺术设定集（因为柜子空间耗尽，所以跟 1 的一起放到其它地方去了</li>
      <li>《哪吒之魔童闹海》艺术设定集</li>
      <li>《崩坏：星穹铁道》纪念册</li>
      <li>《聊斋·兰若寺》设定集（也是因为没地方所以没摆出来</li>
      <li>《平安百物语》设定集 Reset（白嫖，没想到等了这么久竟然没咕咕咕，不过翻了一下内容好像也没特别大变化</li>
      <li>《明日方舟设定集》Vol.3 + Vol.4（也是连同前面几本一起放到其它地方去了</li>
      <li>《浪浪山小妖怪》设定集</li>
    </ul>
  </li>
</ul>

<p>还有 12 月收到的一张碟《一》（感谢虾滑老师撮合）以及还在路上的《童话入侵计划》和《独白》，后面 Vacation 2026.1 再晒</p>

<h1 id="game">Game</h1>

<p>YYS 3000、3333 天了，人明显越来越少，而且今年也没整出什么好活，明年 1.22 终末地出来看看情况，要是靠谱说不定真就把 yys 弃了</p>

<p><img src="../assets/images/about-my-2025/3000.jpeg" alt="" /></p>

<p><img src="../assets/images/about-my-2025/3333.jpeg" alt="" /></p>

<p>HSR 今年的剧情里面 3.1 3.3 真的超赞，下面缇宝的剧情简直看哭 <a href="https://www.bilibili.com/video/BV1ctP7epEQw/">BV1ctP7epEQw</a>，草民将其评价为今年最佳</p>

<p><img src="../assets/images/about-my-2025/311.jpeg" alt="" /></p>

<p><img src="../assets/images/about-my-2025/312.jpeg" alt="" /></p>

<p><img src="../assets/images/about-my-2025/313.jpeg" alt="" /></p>

<p>风堇和赛飞儿的剧情也是真的非常好，尤其是下面这个设定真的非常巧妙。<a href="https://www.bilibili.com/video/BV12HJBzHERG">版本 EP「拂晓」</a>这首歌个人也觉得是今年的几首歌里最好的</p>

<p><img src="../assets/images/about-my-2025/331.jpeg" alt="" /></p>

<p><img src="../assets/images/about-my-2025/332.jpeg" alt="" /></p>

<p>后面 3.7 可以说是草民见过的最天才的一次运营操作（虽然 3.7 剧情个人觉得是有一点拉了，当时纠结了半天氪了一把弓，还被两个小保底歪没了；但是后面 3.8 那个节奏出来，突然又觉得这个弓氪的还挺值的，而且 3.8 的剧情还是挺不错的，虽然就是抽卡又歪小保底</p>

<p><img src="../assets/images/about-my-2025/37b.jpeg" alt="" /></p>

<p>还有些值得一提的大概是上半年《明末：渊虚之羽》（可惜开局翻车）和下半年 SC2 国服回归，虽然也没什么心思玩，年纪越大越电子那啥</p>

<h1 id="entertainment">Entertainment</h1>

<p>除夕之前崩铁还整了个新春会（叔叔这套 Festival 模板整的还挺好），里面🐰这首<a href="https://www.bilibili.com/festival/HSRSF2025?bvid=BV1sKfhYjE5q">《幻境中》</a>着实带劲。说起来还有点怀念「狐斋志异」这个活动了，文案各种梗信手拈来，玩法也挺有意思。后面也就「初花习剑录」有这感觉，今年的活动玩法倒是各有亮点但剧情还是差点味道</p>

<p><img src="../assets/images/about-my-2025/hsrspring.jpg" alt="" /></p>

<p><em>可以组一辈子的捉鬼小队吗</em>（可惜 mygo 这个梗晚了俩月才出来</p>

<p>叔叔今年的年货呢，几个小剧场都挺对味，主线故事也水平在线，鬼畜全明星回归更算的上惊喜，这些比起去年来说算是提升相当明显。但是音乐节目草民就觉得恰恰相反了，光从播放量看跟之前比都低了非常多，马上要一年了也不过一两百万（上面这个都快 350w 播放了）。这里面尤其是命题作文真的完全听不进去，各方面都远远不如 2023 年虚拟区年货那首<a href="https://www.bilibili.com/festival/VSF2023live?bvid=BV1Zv4y1C7Ty">《到深空更深处去》</a>；其它的歌个人感觉也都平淡了一些，幸亏还有一首<a href="https://www.bilibili.com/festival/bnj2025?bvid=BV1tsfpYwEt2">《六耳》</a>非常非常亮眼，终于还算是撑住了场子。怎么说呢，去年毕竟第一次还能理解，但今年还是觉得丢了噩梦那种神韵之后又没拿出新的东西顶上，加上 B 站重心偏向跨年晚会，出现这种结果也不意外……还是希望一个半月之后新的年货能有更好的表现</p>

<p><img src="../assets/images/about-my-2025/6.jpg" alt="" /></p>

<p>春晚版《春意红包》真的是狠狠吃了个大份的。词改了也就算了，重新编的那个曲是什么一坨给人气死，后面 B 站还有一大堆二创，越看越烦。这几年拿拜年祭老歌硬整的新活就没一个像样的，之前《横竖撇点折》，这次《春意红包》，不知道下次又轮到哪个倒霉蛋</p>

<p><img src="../assets/images/about-my-2025/sf.jpg" alt="" /></p>

<p>例行看的动画电影：《浪浪山》最佳。《Zootopia 2》很好（二刷场试了一下杜比影院但非常可惜同事一不小心选了一个中配）。《哪吒 2》还行。《兰若寺》只能说相当一般，追光连续两年端上来的东西都这个水平了，明年又开新坑……其它电影零零散散看了一些，《捕风捉影》确实带劲，《阿凡达 3》特效不错，其它的就感觉也都一般般，没什么很值得专门提的。今年国产片和进口片榜首都是动画电影，也是很难绷</p>

<p><img src="../assets/images/about-my-2025/l.jpg" alt="" /></p>

<p>叔叔这个年报今年直播的权重高了好多（毕竟这个真赚钱的），结果🐰虽然投了挺多币但只剩一个隐藏小视频（小院这个《求职》说是</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/about-my-2025/b1.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/b2.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/b3.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/b4.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>🥚</td>
      <td>🏠</td>
      <td>🐒</td>
      <td>🐰</td>
    </tr>
  </tbody>
</table>

<p>今年☁️用的就少一些（因为下午晚上基本都在🍤和🥚那里黑听），偶尔听歌的时间依然还是毫不意外的，除了难忘今宵之外大部分都给了🐰</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/about-my-2025/1630.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/1631.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/1632.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/1633.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>年中的一个活动</td>
      <td>今年的年度歌手</td>
      <td>老宇航兔了说是</td>
      <td>难忘今宵这一块</td>
    </tr>
  </tbody>
</table>

<p>另外的几个统计其实也蛮有意思，比如年报还能联动，以及后知后觉的发现今年收了《竹梦令》实体之后真的是狂听了好一阵，还有⛄️冒泡</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/about-my-2025/1634.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/1635.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/1636.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>🪭竟然不是阿离</td>
      <td>🎋这张碟真的神</td>
      <td>⛄️明年多营业啊</td>
    </tr>
  </tbody>
</table>

<p>顺便今年☁️十年了，刚好两位关注十年截图，这波真的是十年老粉了。过一两个月还会有新的一大批关注十年来袭（</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/about-my-2025/1001.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/1000.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>竹桑</td>
      <td>银临</td>
    </tr>
  </tbody>
</table>

<p>最后，年底看了一下游戏与娱乐相关的开销，miHoYo 那块还好，但叔叔那块花的钱确实还是有些多了，明年要控制一下这方面的相关支出</p>

<h1 id="projects">Projects</h1>

<p>luci-app-xray 目前 789⭐️，估计农历新年上 800 应该不成问题。今年在这块儿主要做的事情：</p>

<ul>
  <li>完善 luci-app-xray 的 Dynamic Direct，修复一些问题的同时增加了针对 DNS 的前置处理</li>
  <li>为了应对 DoH 越来越差的可用性，魔改了一番 cloudflared 实现 DoH3 0RTT，并增加一些针对性的策略（针对谁就懂得都懂</li>
</ul>

<p>出来的效果还不错，后续可能也会介绍一下 luci-app-xray 在分流上做了哪些事情以及跟 cloudflared 的搭配</p>

<p>RSS Pipe 加了 pyo3 实现了更灵活的内容处理（这个 crate 超级好用，强烈推荐），以及一个简单的类「文件传输助手」的功能，顺便利用 iOS 快捷指令实现了文字（主要是链接）和图片的分享，差不多是想象中完全体的六成水平，明年应该会进一步扩展 pyo3 的应用实现 bot 和 LLM 接入什么的。今年早些时候 Fish 4.0 如期发布、年底 Rust for Linux 实验通过进入正式状态，Rust 绝对是值得长期投入的</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/about-my-2025/list.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/notification.jpeg" alt="" /></th>
      <th><img src="../assets/images/about-my-2025/share.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>列表</td>
      <td>通知</td>
      <td>分享</td>
    </tr>
  </tbody>
</table>

<p>龙芯今年没怎么折腾，不过值得庆祝的是 Debian 正式把 LoongArch 加入官方支持了，以及 AOSC OS 终于把 Python 2.7 相关的依赖都去掉了。其它值得一提的大概是在 Volterra 上跑了 <code class="language-plaintext highlighter-rouge">Qwen3-VL-30B-A3B-Instruct-UD-Q4_K_XL.gguf</code> 偶尔玩一下，过程颇费了一些周折（只能用 MSYS2 里面的），可惜 Volterra 内存还是太小，还只能拿 CPU 硬算（10 Token/s），多模态也慢的基本没什么实用价值，纯图一乐吧</p>

<h1 id="gadgets">Gadgets</h1>

<p>端午节如期更新了 <a href="../gadgets-2025/">Gadgets 2025</a>，下半年买的东西呢：</p>

<ul>
  <li>Redmi Buds 7S：降噪在骑车的时候会自己莫名其妙的关掉，音质调的也不是很好（感觉跟 AirPods 4 那种两头翘的曲线刚好反过来</li>
  <li>Switch Lite 和 iPhone SE 电池：都还行，顺便 iPhone 16 Pro Max 的电池健康终于掉了三个点</li>
  <li>小米 BE6500 Pro：25 年年初生产的旧版本，2.4GHz 差一点，其它还行，自带中枢网关很方便，就是太高刚好塞不进电视柜下面；买了最后那个拆分线之后腾出来一个电源插座，就扔电视后面了，没想到效果意外的非常好，顺便还优化了 NAS 跟 Volterra 的通信</li>
  <li>AirPods 4 ANC：不到 1100 买到，各方面都不错，也没有上面那个自动关掉降噪的问题</li>
  <li>Logi Bolt Type-C 接收器：强迫症解决方案 +1，确实很好用，而且感觉比之前 Type-A 的稳定性也好一些。说起来 MX Master 3S 的主滚轮有点不顺畅了，拆开清理发现轮子下卡了一大团毛毡 = = 然后满血复活，这个鼠标还是挺易于维修的就是得自己买一套脚贴</li>
  <li>偏振镜：有用，不多，Vacation 2025.2 里面提过没 Diff 成，下次一定</li>
  <li>QCNCM865 无蓝牙版：R86S 上完全认不出来，龙芯上倒是没问题，只能怀疑是某个供电引脚之类的定义不完全准确吧。不过在龙芯上用着似乎信号好一些，但因为客户端电源管理的问题 Ping 延迟还是偏高，大概会留到后面把 R86S 换掉的时候用上</li>
  <li>四姑娘山捡到的红点：据说是装在相机上用来打鸟的时候快速构图的，有点意思</li>
  <li>补光灯：趁双十一入手的，以后拍塑料小人观感会好很多</li>
  <li>小米体脂秤：一步到位的八电极版（不知道为什么手柄上要放四个），折腾一年 BMI 降了一点但还是不太够</li>
  <li>水月雨 NiceBuds：性价比确实相当爆炸，虽然 AirPods 4 听习惯了确实感觉这个有些差距。黑白各买了一条支持一下本地企业</li>
  <li>翰林阅 Free2：为了把七月份买的两个🐰键帽用上，以及清理一下🐶东豆子买的，除了自带的矮青轴极烂（还好能换）之外还不错</li>
  <li>小米 BE3600 2.5G 版：小竹 140 收来发现不能刷 OpenWrt 就不想要了，遂一百块拿回来。试着扔在客厅当无线 Mesh 子节点，效果比预期的还好一些，不过把 BE6500 Pro 从次卧挪到电视下面之后就用不到再开 Mesh 了，遂回盒子里继续当备用设备</li>
  <li>12V 5.5 x 2.5 拆分线：有效释放弱电箱 / 电视柜等地方的电源插座，强烈推荐，直接促进了草民入住以来家庭网络最大一次优化</li>
</ul>

<p>更具体的内容就还是明年端午见啦。</p>

<h1 id="finally">Finally</h1>

<p>感觉现在生活算是相当稳定，甚至明年都不想立什么 Flag 了，但还是照例：</p>

<ul>
  <li>明年目前觉得还是不适合做关键决策，所以还是谨慎为主，苟过周期</li>
  <li>上面提到的几个机缘还是要关注一下，有的选的时候不要让自己后悔</li>
  <li>支线：依然是重点关注身体健康情况，日常迭代项目也继续保持推进</li>
</ul>

<p>最后依然送上草民真的很喜欢的《流年如歌》，希望大家新的一年越来越好。</p>

<iframe src="//player.bilibili.com/player.html?aid=94304902&amp;bvid=BV1BE411p7oi&amp;cid=161422087&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height="600px" width="100%"> </iframe>]]></content><author><name>yichya</name></author><category term="Life" /><category term="life" /><summary type="html"><![CDATA[年常系列。开头惯例，先看一下今年 Blog 更新计划完成咋样：]]></summary></entry><entry><title type="html">Vacation 2025.2</title><link href="https://www.yichya.dev/vacation-2025-2/" rel="alternate" type="text/html" title="Vacation 2025.2" /><published>2025-11-30T00:00:00+08:00</published><updated>2025-11-30T00:00:00+08:00</updated><id>https://www.yichya.dev/vacation-2025-2</id><content type="html" xml:base="https://www.yichya.dev/vacation-2025-2/"><![CDATA[<p>上半年的 Vacation 2025.1 不仅拖到了七月，而且现在回过头看感觉内容写的太少了，一件事就一两句话还是多少有点过分，于是 Vacation 2025.2 就早一点开始写，稍微丰富一点内容（虽然流水账也不太可能真指望上什么丰富内容），然后尽量在 11 月底左右发出来吧（</p>

<p><img src="../assets/images/vacation-2025-2/invitation.jpeg" align="right" style="width: 320px; margin-left: 0.5em;" /></p>

<h1 id="712-上海">7.12 🐰上海</h1>

<p>关注🐰也三年多了，这场线下真的是期待已久。🐰这一场印象中应该是年初预告的，当时就说了应该会跟 <a href="https://space.bilibili.com/402923390">BW</a> 安排在一起，可惜草民对 BW 实在是提不起兴趣，没办法，年纪大了，这类二刺猿展会实在是没体力跑了。当然这个安排没毛病，毕竟很多人会愿意白天 BW 晚上顺便看个演唱会，但 <a href="https://www.bilibili.com/video/BV1jj8yzLEWo">BML</a> 毕竟门票也有限还有点挑受众（比如草民真的是人都认不全，而且不太想去拼盘），所以 up 们自己开专场倒是也刚刚好。话说回来，跟🐦不一样，🐰这轮线下都是自己真人上台，然后还就戴一个跟面纱一样薄、只能当作时尚单品对待的口罩（因为戴不戴根本没什么区别，尤其是<a href="https://www.bilibili.com/opus/1107161836415877123">广州场官图</a>那个大白牙一清二楚，笑死）；以及草民完全没想到 SVIP 票竟然如此难抢，就草民这秀动未尝一败的操作还是没赶上，后面还被那个候补机制坑了一手。实话说当时甚至都有点不想去了，但又觉得天知道🐰还有没有下次，而且 VIP 多少还是有个签名海报……所以纠结一番之后，还是花了加起来得有十倍门票价格的机票酒店钱（当然结果就是出票顺序也相当靠后，最终跑这一趟的 ROI 完全低到没眼看</p>

<p>就像上面说的那样，演出前几天逐渐刷到了 BW 期间一堆 up 在上海开线下 Live 的计划，光草民知道的就还有 <a href="https://www.bilibili.com/opus/1059339941843042311">MeUmy</a> 和 <a href="https://www.bilibili.com/opus/1076872258163572736">Akie 秋绘</a>，🐰这个也是 BW 开票之后很快门票就卖完了（唯一一场卖完票的，各档加起来应该有 1000 多张的样子；另外两场应该都是 400 张左右，其实算是还可以了，而且票价不低成本不高，算下来说不定还能有点利润）。7.12 当天魔都天气相当差，前脚晴间多云，转个头可能马上就是大到暴雨。飞机落地耽误了巨久时间，中午一点才跟老同学吃上了饭，完后顺便逛了一下附近某个 Sony 旗舰店（有一说一 Sony 的电视现在看感觉也就一般）。顺便一算，贵协成员似乎只剩两三个在魔都（草民刚毕业那会儿可能还有五六个），还有另外两三个在广东的，剩下一大把得有十来个人都在帝都，也不知道为啥</p>

<p><img src="../assets/images/vacation-2025-2/sony.jpeg" alt="" /></p>

<p>环境又热又潮，前一天又没怎么睡好，反正就突出一个药丸。下午本来想回去睡会儿，但是又被堵在回酒店的路上，只来得及换个衣服就直奔场地了。场地倒是还不错，但是大概因为是第一次的关系，入场的安排真的是一团糟，其他流程上的问题也比较明显（比如周边在场内销售，导致羽织这类热门周边直接被 SVIP 通吃。是草民不想买 SVIP 嘛，根本抢不到啊）。各种因素叠加起来，到快开始的时候人整个都已经麻了，然后就看见第一套那个自己 cos 自己的造型，当时真的没绷住……好在🐰线下还是相当稳（抛开忘词和口胡不谈的话，当然这些倒是本来也不算啥大问题），而且毕竟第一次，勉勉强强算一个及格水平吧。除去上面流程问题也多少想吐槽一下某一位嘉宾，但是不展开说了</p>

<p><img src="../assets/images/vacation-2025-2/0712.jpeg" alt="" /></p>

<p>上面那个合影只能评价为跟🐦那个一样进行了一些很糟糕的切角（以及包看不到草民的，下面北京场倒是很容易找到）。出来的比较早（草民六月那会儿还认真考虑过当时看见 SVIP 排队会是个什么心态，结果真到那时候反而完全没啥想法了，只想赶紧溜），顺便线下真实某🥚群群友之后按之前计划的去看了追光新作《聊斋·兰若寺》，评价为追光刻板印象水平，画风还行、故事不咋样，总结为仅适合家长带娃来看。最后晒出一部分周边（为了把这个签名海报完好无损带回来，草民甚至拿了个专门装海报的纸筒过去，结果只能评价为有用但没必要</p>

<p><img src="../assets/images/vacation-2025-2/0713.jpeg" alt="" /></p>

<p>第二天回来，刚好体验了一下 C919，评价为跟 A320 几乎感觉不出什么体验上的区别，这么看应该可以说相当不错。飞机上的小装饰、飞机餐这些都很有特色，拿了个装垃圾的小纸袋子回家收藏了（</p>

<p><img src="../assets/images/vacation-2025-2/c919.jpeg" alt="" /></p>

<p>然后隔天北京场的预告就发出来了 = = 有一说一，去北京的成本相对低一点，有大把朋友叙旧，还可以顺便回趟家、处理一些之前逃离帝都那会儿遗留的小问题什么的，要是早点说八月份还有个北京场的话，草民大概率七月上海场就不去了，改为去 8.2🐦那一场（虽然一个月里前后脚跑两个巨远的地方也有点说不过去，再考虑一些其他原因，总之也不太可能真这样安排）。简单考虑了一下，决定抢到 SVIP 就去</p>

<p><img src="../assets/images/vacation-2025-2/ticket.jpeg" align="right" style="width: 160px; margin-left: 0.5em;" /></p>

<h1 id="89-北京">8.9 🐰北京</h1>

<p>从上海回来，时隔不到一周再次抢票。这次倒是轻松回收了上面的 Flag，抢到了个甚至还挺靠前的 SVIP（秀动这个纪念票编号的最后三位数就是出票顺序号）。虽然后面看 SVIP 还是一下就没了，现场也是好多人候补上的，但是整体比起上海还是容易太多了。那么按上面说的提升一下 ROI，用一天年假顺便回趟家，然后在帝都见些老朋友，顺便抽空处理个遗留问题（把当时没提干净的医保存折都提出来，帝都医保 2022 年 9 月之前的钱都能提现</p>

<p>起初是想直接飞到正定机场看看，但发现从正定机场回家着实有些麻烦（这地铁到底怎么修的真离谱啊），于是改为体验一下七月新开的动卧 D968，上铺比飞机便宜一百多（下铺要再 +90，就跟飞机差不多了），比高铁二等座只贵十块钱。很有意思的是还有个人拿他在隔壁的下铺跟草民的上铺换了（应该是对小夫妻吧大概，想尽量在一个包间里面），就血赚。这动卧还真不错，主要是考虑时间成本的话性价比真的拉满，就是草民这一趟空调太差劲（今年这天气是真离谱吧，哪儿都是又潮又热），最终还是完全没睡好。中间有好多个车站都一停半个多小时，大概是为检修车让路 / 到站尽量是个天亮（事实上也确实是刚好天亮），估计要是不考虑这些的话八个小时肯定是能压进去的</p>

<p><img src="../assets/images/vacation-2025-2/0807.jpeg" alt="" /></p>

<p>从火车站出来骑电动小蓝出去瞎溜达了两个多小时。修这个咱也不知道几号线地铁把友谊大街给堵了个死死的，当然对草民来说显然是好事（毕竟现在又不住这儿，地铁真修好了显然有利于草民房子出手，当然也别指望真能利个多少吧）。除此之外有一说一，二环内西北角（主要是和平路以北那一部分）变化还挺大，其他的地方倒是一如既往。午饭和晚饭直接选了两个个人必吃榜：某本地品牌炸鸡 + 某本地连品牌都没有的炸鸡，这两家真的是干了 20 多年没挪窝，味道也一点变化没有，甚至后面那家人都没换，一眼就能认出来，怎么想都很夸张。</p>

<p><img src="../assets/images/vacation-2025-2/0808.jpeg" alt="" /></p>

<p>隔天早上到帝都（走之前才想起来空调忘了拔下来了，害），中午在二环堵了一个小时终于见到了几位老朋友。一起到之前经常去的牡丹园海底捞吃了一顿，然后听到个挺令人震惊的事情（当然这里不是很方便讲），以及吃到了近几年吃到过的最难吃的一只脑花（真的差点吐出来那种）。出来顺便小坐了一下某位大佬前一天刚提的 YU7 Pro，有一说一这车个人就是觉得这个长车头的造型看不习惯（屁股也有点怪吧但是没有车头这么不顺眼），除此之外是真香啊，尤其是这位大佬选的 Pro 个人觉得真算是除了审美上的偏好之外没啥可挑剔了。</p>

<p><img src="../assets/images/vacation-2025-2/yu7pro.jpeg" alt="" /></p>

<p>下午抽空带着之前的医保存折找了家北京银行全提出来。有一说一上家不知道怎么搞的多给交了俩月，这波刚好把这次来帝都的开销完全覆盖了。存折好就好在自带强制流水展示，看着上面一行行「结息」越来越少越来越少就，耐人寻味。然后就是直奔场地买场贩（今天这个天气更不给面子了，大太阳晒一下午，还不如下点雨呢），以及晚上见🐰啦。</p>

<p><img src="../assets/images/vacation-2025-2/0809.jpeg" alt="" /></p>

<p>这次在流程、造型这些上次问题比较严重的地方做了很有效的优化（说起来这个造型啊，后面🐰有一次直播说上海场第一套衣服的造价比后面所有场次的衣服加起来都要贵，真是……），加上这次抢到了 SVIP，最终整体体验真的非常好。上面的大合照感觉也好一些，至少不切角了。总结为不虚此行，很开心（当然还要特意晒一下 SVIP 特典，尤其是成本算下来差不多是原价 100 倍的这张碟</p>

<p><img src="../assets/images/vacation-2025-2/0810.jpeg" alt="" /></p>

<p>最后一场 8.30 在广州，有一说一这日子选的确实有点不太好，甚至更惨的是当天广州还有一个阵容相当豪华的 AniSonic 全息演唱会（虽然草民对这个阵容没兴趣，而且据现场群友反馈，拼盘也就算了还是预制拼盘，票价还高的离谱，中间还出了一些视频放错了的问题 = =</p>

<p><img src="../assets/images/vacation-2025-2/0830.png" alt="" /></p>

<p>至于草民自己，实话说五月份已经跑过一趟🐦的广州场，这次实在是不好找摊平 ROI 的手段了。本来想立一个「如果当天爹妈已经回石家庄了，并且那时 SVIP 票还有剩就去」的 Flag，但这两个条件最终都没有满足，那只好期待一下之后更大的舞台再见（不过有一说一广州这一场吃的真好啊，歌单换了好多歌，时间也比前面两场都要长不少……笑死，为什么又是只要遇到皮套人就运气不好系列，算了问题不大</p>

<p>说到皮套人运气不好……8.8 那天🐦还在直播里面说明年可能会搞降低成本、提升覆盖面的 Livehouse 形式的巡演。有一说一这种确实比起🐦那两场大场地的靠谱，而且那个现场动捕是真没必要，so 许愿一个真人上台 + 有类似🐰这种 SVIP 特典环节吧（虽然感觉可能性不大</p>

<h1 id="823-黄黄">8.23 黄黄</h1>

<p>距离上次「人间」已经两年了，说天天盼可能有点夸张但确实一直都想能再来一次，尤其是新专辑《今日无事》发了之后就更加期待会不会有新的巡演安排。没想到很快，四月就等到了这个，而且第一批就安排上了成都，狂喜。这次的形式官方称之为「幕剧音乐会」，简单的说就是利用一些舞台剧表演把歌曲作品串起来。从这次的场地选择来看，比之前的 Livehouse 要高端不少，着实让人感觉十分期待</p>

<p><img src="../assets/images/vacation-2025-2/bif.jpeg" alt="" /></p>

<p>成都开始售票在 7 月 5 号。草民刚被上次抢🐰的 SVIP 失败那回搞到怀疑人生，黄黄这个前面两场抢票似乎也十分凶残（虽然草民之前帮虾滑老师顺手抢了一张，感觉也没有很难，这事儿当时还让草民自信爆棚……然后就被🐰教育了），但这次却意外的成为了很证明抢票手速的一次：7 秒付款，个人觉得很快了，但实际还是天外有天，闲鱼上一看到处都是三四秒那种。8.18 中午出座位号，虽然事实证明确实有 80 多个人比草民手速快（说明🐰那儿第一次失手也不算太离谱），但结果刚好在这个基本上是全场最佳的位置：不仅很靠中间，而且比起前面「乐池」四排来说不用一直仰着头，也不会像后面过道那样可能被抽到互动，社恐人很开心（虽然实际上跟预想的可以说完全不一样</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/vacation-2025-2/seat1.jpeg" alt="" /></th>
      <th><img src="../assets/images/vacation-2025-2/seat2.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>号码</td>
      <td>位置</td>
    </tr>
  </tbody>
</table>

<p>起初还是计划拉上马哥和维洲一起，不过马哥运气不太好第一次开票没抢到，第二次草民人刚好在🐰北京场结果所有人都把这事儿忘的死死的，维洲也因为类似原因两次都没安排上。本来以为没希望了，但是非常意外的是演出前一天出了一个比较难绷的事情：有一小部分相当好的座位主办方之前以为不能卖，然后发现实际上可以卖，那天晚上就突然拿出去卖了 = = 最后甚至还没卖完。虽然这个事情让几个群里的一大堆没开出好座位的人非常破防（有一说一这事儿确实是干的不地道），但是也因此让马哥有机会捡漏，最终还开到了一个非常好的位置（甚至后面他还在现场换到了一个更好的位置，视野比草民的还要好一些）。维洲就比较可惜，当天行程冲突彻底没机会了，下次一定（</p>

<p>下午也是又见到了虾滑老师并得到了他精心制作的 PCB 板（真有创意啊，下面晒一下）。然后直接快进到看完的感受：完完全全 next level</p>

<ul>
  <li>跟之前看过的那些 Livehouse 比完全是碾压式的体验
    <ul>
      <li>形式和内容都可以说无可挑剔，狠狠让草民这个没吃过细糠的被震撼了一把</li>
      <li>中间有好几幕真的美哭，可惜这些地方都不允许拍照（跪求后面放个官录出来吧</li>
    </ul>
  </li>
  <li>场地本身的硬件条件也是绝对没有异议的顶级，舞台和音响效果都是极佳
    <ul>
      <li>不过草民那个座位实际跟乐池一样高，手机拍照效果一般，马哥那个靠后一点的位置要好很多</li>
      <li>还有一点是这个位置比草民想象中更偏左一点，而且因为比较靠前，看舞台左右的提词器有点费劲</li>
    </ul>
  </li>
  <li>万万没想到草民所在这一排还真被黄黄抽到互动了，然而草民本质上还是个 i 人，完全没反应过来
    <ul>
      <li>马哥那一排也有演员互动（虽然他没有拿到宝玉撒的糖），只能说他实在是各种意义上的血赚</li>
    </ul>
  </li>
  <li>然后还一定要强调一点是林妹妹那个演员实在是太可爱了，真的是完全长在草民的审美上那种，最后返场怼着拍（放一个<a href="https://www.bilibili.com/video/BV1NaxiztECn/?p=2">花絮</a>在这里</li>
</ul>

<p><img src="../assets/images/vacation-2025-2/0823.jpeg" alt="" /></p>

<p>最后还是感谢各位神仙投喂（这次也是拿到了老粉证明系列）和赞助商支持（草民的香水只剩小半瓶了！！！马哥那个还有一大半！！！）</p>

<p><img src="../assets/images/vacation-2025-2/0823.jpg" alt="" /></p>

<p>现在就真的期待明年能安排在重庆一场吧，或者有什么其他不太远 / 没去过的地方也可以考虑（今年武汉就真的不跑了，预算实在是爆炸了</p>

<p><img src="../assets/images/vacation-2025-2/list.jpeg" align="right" style="width: 246px; margin-left: 0.5em;" /></p>

<h1 id="824-王子">8.24 王子</h1>

<p>这个也是盼了很久很久，不过没想到第二场就在成都还是很惊喜的。虽然吧，杭州场有的《东风第一枝》这次给 Roll 下去了，有点怨念</p>

<p>这次还准备了各种预热，比如曹操专车的开屏和车载广告，以及万达投了几天的大屏。但是这个大屏每天都在出各种问题，真的十分无语：</p>

<ul>
  <li>第一天中间一小块是坏的</li>
  <li>第二天群友反馈轮播内容不见了</li>
  <li>第三天下午确认一切正常（下午王子还去亲自打了个卡
    <ul>
      <li>于是晚上骑车过去，结果整个屏幕都关掉了，给人气的头疼。然后偶遇一位芦荟并获得投喂的扇子一把（话说回来，这位朋友也跟马哥一样捡漏到了黄黄前一天开的好座位，笑死</li>
    </ul>
  </li>
  <li>最后一天也没时间去了，看群友返图更离谱，中间两排全坏了……这个万达实在是多少有点离谱</li>
</ul>

<p>中午过去排队，发现这帮人的成分都贼复杂：起码看见三个「今日无事」的包，还有「雾的 19 日」和「山色有无中」的袋子，还有人前一天刚买了「入梦」的衣服今天就穿来的（怎么到处都是这帮人啊，害）。这次两个嘉宾，小随不太熟（毕竟草民几乎不听男声没办法），以冬可是老熟人了而且近期也经常出现在各种拼盘里面，这次能在线下见到也是十分幸运</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/vacation-2025-2/0824_1.jpeg" alt="" /></th>
      <th><img src="../assets/images/vacation-2025-2/0824_2.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>王子</td>
      <td>以冬</td>
    </tr>
  </tbody>
</table>

<p>然后还是快进到看完的感受：本来还觉得前一天刚吃过细糠，再回来听 Livehouse 会不会感觉有落差，虽然事实上确实能感觉出来场地硬件水平的差距很明显，但王子一开口这种担忧瞬间烟消云散，甚至王子这个看完回来之后，比前一天黄黄的感觉戒断更严重一些（毕竟气氛大概是 Livehouse 跟剧场的最大区别 + 它独特的魅力所在</p>

<ul>
  <li>现场是真的太稳了，真的完全跟 CD 一模一样那种稳，这就是科班出身的专业实力啊</li>
  <li>王子真的很擅长跟观众互动、活跃气氛，大概是早年 YY 加上现在🎵直播练出来的本事</li>
  <li>上面吐槽的没有《东风第一枝》后面补了一点清唱，也行（完整版就刚好以后留个念想</li>
  <li>live 凑不够时间各种拖的经常有，头一次见中间几乎没怎么停，然后还加班加半个小时的
    <ul>
      <li>结果后面的签名 + 合影环节，还因为王子实在太好说话结果拖了两个多小时才弄完 VIP……</li>
    </ul>
  </li>
</ul>

<p>那么最后是例行合影（几乎是刚好把这个场子塞满了，所以说这次的票确实卖的挺不错的</p>

<p><img src="../assets/images/vacation-2025-2/0824.jpeg" alt="" /></p>

<p>然后晒一下签名（以及还是忍不住想吐槽一下官方售卖的明信片和书签着实是贵的离谱了</p>

<p><img src="../assets/images/vacation-2025-2/0825_1.jpeg" alt="" /></p>

<p>当然还要感谢各位神仙的投喂（</p>

<p><img src="../assets/images/vacation-2025-2/0825_2.jpeg" alt="" /></p>

<p>以及同样期待明年再来一个川渝地区的场次吧，这次成都的票卖的还不错，应该下次还能安排上附近的场次。话说回来北京场开票之前刚好是广州那个大拼盘，看🍠上似乎是狠狠圈了一波粉，这次北京场卖的更是相当不错。可惜草民肯定安排不上了，八月刚去过北京而且 9.20 还有下面这个里少的（说起来非常巧，10.19 武汉的场次也跟里少的南京场撞了，当然主要原因大概还是里少时间安排太密集，江浙沪连续三天可还行，可惜王子武汉这场也真没预算支持了……虽然也想过如果恰好又是跟黄黄前后两天的话也不是不行，但实际上隔了俩星期</p>

<h1 id="920-里少">9.20 里少</h1>

<p>这次抢票使用了一点小技巧（指支付宝小额免密），成功 4 秒付款抢到了 VIP 001，然而这一场线下排队所以抢到这个并没有什么用（</p>

<p>这次的场地跟上面王子那个是同一个，今年来这个场地都三回了（而且又是例行见到了一堆熟人，这次到处都是芦荟，上次到处都是卿卿，怎么来来回回都是你们啊）。有一说一跟去年比感觉到了很明显的场地音效加成（东郊记忆那个 MAO Livehouse 确实是不如这个场地好），而且里少这个乐队水平确实是很不错，开场唢呐很带感，以及琵琶小姐姐太棒了完全挪不开眼睛</p>

<p>这次嘉宾是不才，9.1 公布的时候着实有点吃惊，毕竟这俩虽然都是本地人但日常都不呆在本地。这次不才换了一种很松弛的风格上来，有一说一跟六月份她自己那场比确实温和多了，上次那个风格选的多少有点致郁</p>

<p><img src="../assets/images/vacation-2025-2/0920.jpeg" alt="" /></p>

<p>说回里少，依然是跟上次一样相当稳定靠谱。歌单跟上次比换掉了很多，很适合草民这种平时听新歌很少的人。这次互动还是十分的 i 人地狱，不过这次比上次好一点至少没有那么压力观众了（草民这次一开始真的有点想不抽号码牌的，甚至还特意去晚了一些，中间还跟虾滑老师换了一下挪到了个靠后一点的位置，不过还好最终没有被抽到互动</p>

<p><img src="../assets/images/vacation-2025-2/0921.jpeg" alt="" /></p>

<p>最后也是晒一下签名并感谢各位神仙投喂（</p>

<p><img src="../assets/images/vacation-2025-2/0920.jpg" alt="" /></p>

<p>目前看起来不出意外的话应该就是今年最后一场了，然后也期待一下里少和不才的新专辑，以及希望虾滑老师心愿成真，后面里少可以安排一场在重庆（实在是需要一个理由去重庆再跟黎老板吃个饭</p>

<p>礼拜一听说熊家搞了个拼盘又有小女神又有竹桑的，嘿……更觉得七月份去上海去的有点亏了。不过本来草民也不去拼盘倒是</p>

<h1 id="1018-三星堆">10.18 三星堆</h1>

<p>三星堆的 Flag 其实也算是立下来有些时日了，但实话说还是有点远，摇不到人一起的话草民也不是很想单独跑一趟。这次刚好是某位在帝都的贵协大佬远道而来，并且是周六中午到，刚好有一下午时间可以安排，于是决定一起去康康。至于攻略不攻略的感觉也没必要，小程序上能买到门票就行了，坐城际到广汉北或者三星堆都差不多，然后打车或者公交车到博物馆门口就完事</p>

<p><img src="../assets/images/vacation-2025-2/1018.jpeg" alt="" /></p>

<p>花了大概俩小时逛完，个人的感觉其实也没有觉得那么震撼（也许是因为看了太多介绍，期望值被拉太高的关系，加上之前去过成都博物馆，对这边的早期文明也多少有点概念了），当然设计语言能保持一致这件事在那个时代确实挺牛逼的。晚上回来顺便去了一趟很久没去的锦里。提到这地方草民的态度一般都是非常抗拒，毕竟之前去的几次都是人挤人，体验实在是相当糟糕。还好这次人倒是很少，大概是因为国庆刚过没多久，以及中间最窄的那条小吃街进入了翻修流程（所以这位贵协大佬想找的菠萝饭就没机会了</p>

<h1 id="1022-九寨沟-30">10.22 九寨沟 3.0</h1>

<p>同样是跟上面这位大佬一起去的，这也导致去年立下的「明年不去九寨沟了」的 Flag 终于还是倒的干干净净。可惜运气一般般，彩林差不少火候，天气也不算好（放一张同一个位置的照片对比一下，大概都是上午十点左右的样子</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/jiuzhai-valley-experience/r_01_02.jpeg" alt="" /></th>
      <th><img src="../assets/images/vacation-2025-2/1022.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>去年九月上旬</td>
      <td>今年十月下旬</td>
    </tr>
  </tbody>
</table>

<p>其他的怎么说呢，跟之前比起来就是另外补拍了几个牌子，以及去了几个没去过的地方：</p>

<ul>
  <li>原始森林（进去溜达了一圈儿大概也就花了十几分钟，感觉意义不大，走下去到天鹅海那一段更带感</li>
  <li>鹰爪洞（一个大概深度三四米的洞，岩壁上有个形似鹰爪的痕迹，是某种之前嵌在里面的石头掉下来的样子罢了</li>
  <li>五花海和珍珠滩中间，走路的话会路过一个金铃海（没找到牌子但看到高德上有人拍了，实际上看着也一般般没啥亮点</li>
  <li>去长海的路上认真盯了一下上季节海和下季节海，这次倒是多少存了点水，但也就一点点，没啥看头</li>
</ul>

<p>顺便买了个非常便宜的偏振镜，评价为有那么一点用但也有限，试图找到一组可以对比的照片结果发现忘了拍了，笑死。</p>

<p>总体上跟上回区别不大，包括刚回来一个礼拜就下雪这种事情竟然又遇上一次 = = 明年年初再找个下雪的日子挑战一次当天来回试试（</p>

<h1 id="111-四姑娘山">11.1 四姑娘山</h1>

<p>小竹五月份去四姑娘山的时候被高反折磨的非常痛苦，回来之后就一直想着再挑战一次。草民也确实有些兴趣，所以就又摇了两车六个人一起过去。九寨沟没赶上的雪这次终于是赶上了，不知道该说运气好还是不好，草民反正是很开心。</p>

<p><img src="../assets/images/vacation-2025-2/1101_1.jpeg" alt="" /></p>

<p>感觉双桥沟这里攻略也没有什么很需要攻略的，除了要关注一下高海拔地区的影响（按小竹的说法是他觉得红景天非常有用，仅供参考），其他的就跟九寨沟一样不带脑子坐车就完事，流程更是称得上轻松加愉快（除了开局先来一个 40 分钟超绝大晕车体验之外</p>

<p><img src="../assets/images/vacation-2025-2/1101_2.jpeg" alt="" /></p>

<p>中间会有一个漂流可选，票价 90 一个人。体验还不错，十分轻松愉快还能省不少脚程，两人一船能凑齐的话还挺推荐的。至于为什么大冬天有漂流，咱也不是很理解；至于为什么这次还去尝试了，那只能说是猜硬币猜出来的</p>

<p><img src="../assets/images/vacation-2025-2/1101_3.jpeg" alt="" /></p>

<p>整个流程下来早上十点左右进，下午不到五点出来，看手机记录的步数还不到一万，着实是轻松加愉快（如果没有一开始那个晕车就更好了）。下午雪就停了，这次该说不说运气是真的不错，尤其是草民这个北方人四年多没玩过雪了，这次真是玩了个爽</p>

<p><img src="../assets/images/vacation-2025-2/1101_4.jpeg" alt="" /></p>

<p>个人逛下来感觉其实像是去了一个 Pro Max 版的蓝月谷，当然毕竟海拔很接近，似乎也有几分合理。之前还打算第二天去长坪沟徒步，但考虑天气和时间，最终决定改为回程时顺便去映秀参观地震遗址。不得不说，亲眼见到确实还是相当震撼，解说也很认真且富有感染力</p>

<p><img src="../assets/images/vacation-2025-2/1102_1.jpeg" alt="" /></p>

<p>以及旁边的地震纪念馆，更深刻的感受到了当时的天灾，以及之后的众志成城。不管怎么说，还是希望大家都平安啊</p>

<p><img src="../assets/images/vacation-2025-2/1102_2.jpeg" alt="" /></p>

<p>从四姑娘山到映秀的路上还会路过一段风光相当不错的山路（当然现在更流行「理小路」自驾，据说风光更好</p>

<p><img src="../assets/images/vacation-2025-2/1108.jpeg" align="right" style="margin-left: 0.5em;" /></p>

<h1 id="118久久">11.8「久久」</h1>

<p>草民上面列出来的那些歌手没听说过也很正常，毕竟古风一直都是鄙视链底端的小众圈子。但大概不会有太多人没听过《红昭愿》和《芒种》这两首当年红到离谱的歌（不过草民是从《惊蛰》和《沉鱼》入坑的），「久久」就是她们俩的九周年出道纪念系列线下演出啦。天还热的时候看着上海场和武汉场着实是分外眼红，没想到十月份突然又安排到了一次成都的场次，而且还是生日场，狂喜。</p>

<p>话说回来，真的是最近才知道 2022 年 11 月忙着入住新家，不仅错过了音阙诗听的实体专辑《一》还错过了六周年在东郊记忆的线下演出……着实是非常非常可惜。这次线下买了票之后就在考虑互动的时候签名到底带什么东西去签，最优选择当然是去闲鱼买一个《一》但是这玩意儿闲鱼压根就没人挂，现在是根本买都买不到；买票送的礼包也是晚上才去领。看了下群友们的计划，最终决定买一把纸扇带去</p>

<p>场地又是熟悉的正火艺术中心 6 号馆，这已经是今年第四次来这个场地了，虽然这个场地音响效果确实不错但也没必要逮着一家薅不是。这次的流程安排是下午先互动晚上再演出，实际的执行是两点到三点互动一次，四点到五点又互动一次，等于中间只留了一个小时彩排，压的实在是太短了，并不算合理。当然因为这次没有请乐队的关系彩排也没有那么高的要求，所以实际出来的效果倒是也问题不大</p>

<p>两位人美歌甜自不必多夸，小道具的节目效果也很有意思。生日场当然会有一系列的互动环节，包括非常压力观众的猜歌接歌（幸亏这次真的下定决心不抽号码牌了不然要是被抽到可太吓人了）以及分蛋糕</p>

<p><img src="../assets/images/vacation-2025-2/1108_1.jpeg" alt="" /></p>

<p>这次的礼包里面最喜欢的是中间纸袋下面放的那两个决策币，当然也顺便晒一下包含签名的扇子</p>

<p><img src="../assets/images/vacation-2025-2/1108_2.jpeg" alt="" /></p>

<p>更好的消息是明年大概还会有十周年场（毕竟音阙诗听这帮人大本营就在成都，那怎么也是应该安排上的），本来看完还是有一点戒断的，现在就完全转变成了期待</p>

<h1 id="1129婧钰双声">11.29「婧钰双声」</h1>

<p>上一场结束还不到一个礼拜就看到群友又发出来了这个，怎么说呢，11 月这两场都得称得上是相当意外的安排。这次票一开始只在猫眼上卖（说起来才知道猫眼原来是美团旗下的……然后就直接大众点评启动解决掉了），后来隔了一个礼拜左右的样子才又上了秀动。上次因为各种原因没来的虾滑老师这次倒是运气很好的收了一张便宜票，加上这次结束比较早他还可以直接晚上就回学校。顺便又继续去闲鱼蹲了大半个月《一》，还是没辙，不过这次拜托虾滑老师去他的人脉里面找了，希望明年能用的上。这次听群友们的说法是个商演，而且场地好像还是个夜店。草民完全没参与过这种风格的线下，而且看放出来的歌单跟「久久」比也更倾向流行一些，当时感觉多少还是有一点慌</p>

<p><img src="../assets/images/vacation-2025-2/1129.jpeg" alt="" /></p>

<p>下午三点到场地排队，结果被主办莫名其妙的放进场地围观排练去了 = = 一脸懵逼的进去一脸懵逼的出来系列，不过趁机观察了下场地，舞台和音响确实都还不错，观众区域也确实完全就是想象中的夜店那样子。五点左右入场，坐到六点突然哗啦啦一下进来一大堆汉服小姐姐，问了下虾滑老师也是一脸懵逼，后面跟邻座打听了一下才知道今儿在旁边电子科技大学有个汉服节，这场演出是跟汉服节联动的（然后六点钟进来的这些人大部分应该都是赠票或者是价格非常便宜，怎么说呢，反正商演艺人拿固定出场费，那就感谢金主爸爸吧）。后面台上两位的 talking 也一听就是非常商单的风格，除此之外倒是跟正常的演出没啥区别，包括她们俩的互动也还是非常有趣，听的还是很开心。草民的评价是这种商单请多来点，越多越好，务必加大力度。</p>

<p><img src="../assets/images/vacation-2025-2/1129_1.jpeg" alt="" /></p>

<p>当然后面就要吐槽一下主办了。比上次好一些的是流程上至少留出了足够多的彩排时间：原来海报上写的也是三点到四点半互动，四点半入场这样，等于是又几乎没什么时间排练，然后三点多屁颠屁颠去到那边之后又被通知互动改为演出结束以后，虽然这样对演出效果更好但还是多少有点折腾。然后就是跟汉服节联动导致原定六点开始结果六点钟又突然进来一大车人要协商座位什么的，又往后拖了二十分钟；再加上场地是夜店的关系，人家晚上要正常营业以至于后面留给 VIP 观众的合影互动预定就只有大概不到半个小时，上面又拖了二十分钟导致这个环节突出一个匆匆忙忙连滚带爬……这里还要感谢某位大佬投喂明信片一包，终于不用纠结在什么东西上签名了，超级开心</p>

<p><img src="../assets/images/vacation-2025-2/1129_2.jpg" alt="" /></p>

<p>最后就要再吐槽一下礼包了，又少一个 NFC 冰箱贴，还得后面等快递。强迫症直接买了所有的票根，礼包里送的就改为送给虾滑老师了</p>

<p><img src="../assets/images/vacation-2025-2/1129_3.jpeg" alt="" /></p>

<p>这次最后没有安排大合照略显可惜，毕竟全场那么多汉服小姐姐呢。那么这个就真的是本篇最后一条内容了，剩下的 Vacation 2026.1 见</p>

<h1 id="例行晒一下碟">例行晒一下碟</h1>

<p>今年线下演出炸出来很多老古风人，出新专辑的也不少（虽然草民只支持了女歌手的，男歌手主要还是实在空间有限，只好咕咕咕了</p>

<h2 id="今日无事">《今日无事》</h2>

<p>当然是毫不犹豫买了两个版本。这个木盒子的版本是真不错啊真不错</p>

<p><img src="../assets/images/vacation-2025-2/disc1.jpeg" alt="" /></p>

<p>这次的货确实是备了相当多，根本不需要抢，甚至标准版卖了两三轮才卖完，「入梦」成都场还可以直接买到</p>

<h2 id="天兰夜复刻">《天兰夜》复刻</h2>

<p>本来是打算带去北京场找🐰签名的，但 CD 盒子太糊弄事儿了，结果八月份只好拿精选集去了</p>

<p><img src="../assets/images/vacation-2025-2/disc2.jpeg" alt="" /></p>

<p>发货过来的时候就直接在发货单上加了一个歌词本，但是跟盒子里已有的对比了一下也没发现什么区别，只能打为虚研社传统艺能</p>

<h2 id="织梦令">《织梦令》</h2>

<p>执素兮也算老熟人了，这两年也是经常在各种拼盘里面冒泡</p>

<p><img src="../assets/images/vacation-2025-2/disc3.jpeg" alt="" /></p>

<p>至于旁边的织梦镇永居契书为什么没写 ID，只能说果然是被🎵客服机器人挡在外面了，不过问题不大，以后再找机会补上就是了</p>

<h2 id="半壶纱渡风">《半壶纱》+《渡风》</h2>

<p>还有一个送的《珂笺》</p>

<p><img src="../assets/images/vacation-2025-2/disc4.jpeg" alt="" /></p>

<p>有一说一没想到现在还有全新库存甚至带签名，这真忍不住剁手</p>

<h2 id="二次元凶">《二次元凶》</h2>

<p>只买了小版支持一下</p>

<p><img src="../assets/images/vacation-2025-2/disc5.jpeg" alt="" /></p>

<p>小碟（因为是 EP 只有四首歌，用了小尺寸的盘）是好文明，收纳方便多了</p>

<h2 id="反转童话">《反转童话》</h2>

<p>虚研社稳定发挥……以及更加糊弄的盒子和里面的空白 CD-R，虽然印了图案上去但是还是觉得有点难绷</p>

<p><img src="../assets/images/vacation-2025-2/disc6.jpeg" alt="" /></p>

<p>而且上面那个《天兰夜》起码 U 盘外面还有个小盒子，这回这个也没有了，下次再找🐰签名都不知道怎么带过去（先有下次再说吧</p>

<h2 id="其他">其他</h2>

<p>还有一个《跨越数千昼夜》是 B 站今年拜年纪的设定集，虽然里面也带了一张碟……有一说一内容还是稍微少了点感觉一般。其他的还有不少设定集（今年主要是年初的哪吒 2 和暑期的兰若寺、浪浪山这几部动画电影，加一个影神图）和塑料小人（有点多）啥的，懒得挨个晒了，年常直接拍一张展示柜现状得了（感觉不能再买了啊没地方放了啊，已经不得不把粥的几本统一收起来了</p>

<p>目前在路上的碟还有：</p>

<ul>
  <li>小红泡《童话入侵计划》本来说是 12 月初左右发，目前看起来是得等到月底了</li>
  <li>云之泣《独白》刚好是今天晚上开始预售。已第一时间下单，预计是明年 1 月份发货，可惜没有签名（微博抽奖这种事情就不做梦了</li>
</ul>

<p>明年要出的目前预计，不才、叶里、银临、兰音应该都会有新碟或者 EP 发出来，狠狠期待住了。顺便也简单数了数手里的签名，感觉想要的基本上已经差不多都有了，还没有的除了一些根本想都不用想的，剩下的就🐦这个是真的非常难搞</p>

<h1 id="end">End</h1>

<p>之前计划中的张家界终于还是咕咕咕了（虽然今年还通了高铁，有一说一真该去的），然后明年可能有的计划：</p>

<ul>
  <li>银临「粼粼」（今年只开一场在杭州，还真是体育场，也是一眨眼卖完；这一阵儿牵丝戏那个事儿之后明年的票会不会好抢一些哈哈哈</li>
  <li>黄黄「入梦」的全新升级版本（今年也是只开一场在苏州，而且连开两天，结果售票又出问题了……真离谱，换平台也不知好不好抢</li>
  <li>ChiliChill 会有新的巡演（当然除了会有之外，现在完全是个饼的状态，具体安排还不知道要等到什么时候</li>
  <li>🐦可能也会有巡演（属于是只在直播里面提了一下，甚至不能确定会有，说白了连饼可能都不算的</li>
  <li>上面有提到的「安 + 静」十周年，成都场目前大概也算是有张饼了（吧</li>
</ul>

<p>其他几个想去的地方：</p>

<ul>
  <li>挑战一次当天来回九寨沟，桃花雪花二选一（今年已经玩过一次雪了所以大概会更倾向于桃花，不过也要看日子，要赶在淡季里面的话桃花肯定是没机会的</li>
  <li>今年张家界通了高铁，大概明年优先考虑这个吧</li>
  <li>泸沽湖这个，小竹国庆跟他的朋友自驾去了，颇令人羡慕，可惜到昆明的高铁恐怕还要一两年才能通，不知道啥时候能安排上</li>
</ul>

<p>另外四姑娘山明年好像也会通一种什么列车，但是暂时并没有什么二刷的想法，过两年再说吧（</p>]]></content><author><name>yichya</name></author><category term="Play Around" /><category term="life" /><summary type="html"><![CDATA[上半年的 Vacation 2025.1 不仅拖到了七月，而且现在回过头看感觉内容写的太少了，一件事就一两句话还是多少有点过分，于是 Vacation 2025.2 就早一点开始写，稍微丰富一点内容（虽然流水账也不太可能真指望上什么丰富内容），然后尽量在 11 月底左右发出来吧（]]></summary></entry><entry><title type="html">Shells and Terminals</title><link href="https://www.yichya.dev/shells-and-terminals/" rel="alternate" type="text/html" title="Shells and Terminals" /><published>2025-09-30T00:00:00+08:00</published><updated>2025-09-30T00:00:00+08:00</updated><id>https://www.yichya.dev/shells-and-terminals</id><content type="html" xml:base="https://www.yichya.dev/shells-and-terminals/"><![CDATA[<p>几个月前尝试了一下 Nushell（虽然很快就放弃了），然后就突然想做一下用过的几种 CLI Shell 和 Terminal Emulator 的对比，但后面觉得好像不是很有意义，于是大概会变成安利 fish 再搭配一些可能非常离谱的暴论</p>

<h1 id="interactive-and-scripting">Interactive and Scripting</h1>

<p>首先要强调一下这两个概念，作为 Shell 的两个不同的用法：</p>

<ul>
  <li>Interactive / REPL：指的是对着一个提示符（一般是 <code class="language-plaintext highlighter-rouge">&gt;</code>、<code class="language-plaintext highlighter-rouge">$</code>、<code class="language-plaintext highlighter-rouge">%</code>）输入一些东西，按下回车之后立即得到反馈</li>
  <li>Scripting：指的是像写代码一样写一大堆东西并保存成一个文件，然后作为一个整体丢给某个解释器去跑</li>
</ul>

<p>虽然这两种用法也并非完全不一样，比如真的有人会写（也可能是网上直接搜 / AI 写一个，然后看都不看就直接复制粘贴）一串非常长，里面又是 <code class="language-plaintext highlighter-rouge">IFS</code> 又是 <code class="language-plaintext highlighter-rouge">[ [[</code> 又是一些奇奇怪怪的管道操作（比如 <code class="language-plaintext highlighter-rouge">&lt;(command)</code>，这是个 bash 的私货）之类的命令到 Shell 里面跑。虽然用法还是 Interactive 但完全没用到自动补全之类的 Interactive 关注的特性，这种实际上更应该认为是 Scripting，以及强烈不推荐这种做法</p>

<p>然后第一个暴论就来了。个人认为用 Shell 去干 Scripting 这种事情几乎已经没有什么讨论的必要：能不用就不用，非得用的话遵循 Google 建议用 bash，bash 都没的用的话有 busybox 一般也就只能用它的 ash，没有就塞一个 dash 进去，这仨区别不大。具体分以下情况讨论：</p>

<ul>
  <li>Unix-Like Desktop / Server（基本上就是 macOS + Linux 吧，大部分 Web 码农应该都是这配置）
    <ul>
      <li>基本上应当假设一定有一个差不多版本的 Python（指至少 3.4），否则建议升级或者换家公司</li>
      <li>当然 macOS 需要装 Xcode 或者 Command Line Developer Tools 才行（Ruby 应该自带，但基本只能认为 macOS 限定）</li>
      <li>有 Python 之后 Scripting 需求请用它来完成，无论是性能还是功能都比搓 Shell Script 靠谱的多</li>
    </ul>
  </li>
  <li>容器、嵌入式设备等受限 Linux 环境（不包括 Android，Android 上做正事基本上都要 APK 二进制交付
    <ul>
      <li>OpenWrt 及魔改（有一说一现在覆盖面真的非常广）请使用 Lua / ucode，取决于版本</li>
      <li>Armbian 如果不是砍的特别精简的话应该也可以有 Python 或者 Lua</li>
      <li>BuildRoot / 容器之类环境高度可定制的情况，建议直接用 C / Cpp / Go 之类的编译型语言做二进制交付</li>
    </ul>
  </li>
  <li>Windows（至少 Windows 7 及以后，更早的版本真的没必要考虑了）
    <ul>
      <li>BAT / CMD 就是一坨，不建议任何精神正常的人去写那玩意儿</li>
      <li>PowerShell 好一些，而且基本能保证环境健全。还有一点是甚至可以考虑直接在里面写 C#（后面会贴个例子）</li>
      <li>Windows 的环境相对稳定，跟着脚本自己带一个 Lua 或者直接二进制交付一般也不会出什么问题（甚至大概比较推荐</li>
    </ul>
  </li>
</ul>

<p>其他实在是没的选的情况（猛一下还真举不出来例子，有的话可以评论区提供一个来分析一下）再考虑写 Shell Script，这种情况也基本只有 bash / ash（busybox）/ dash 中某一个作为唯一选择，而这三个在 Scripting 上几乎没区别，这种时候去争用哪种 Shell 完全就没有意义。因此下面的讨论会以 Interactive 为主，偶尔会提到一些 Scripting（一些非常复杂的 Interactive 也算在内</p>

<h1 id="why-fish">Why Fish</h1>

<p>观前提醒：以下内容会涉及到大量捧 fish 踩 zsh 的暴论，对此类内容比较敏感的朋友可以提前退出了（</p>

<h2 id="out-of-box-experience">Out Of Box Experience</h2>

<p>既然全称叫 Friendly Interactive Shell，它做的最好的一点毫无疑问是 OOBE，在不做任何配置的情况下就相当的 Friendly：</p>

<ul>
  <li>足够用的默认 Prompt
    <ul>
      <li>默认只比 bash 多一个 git 等版本控制工具的提示，照顾了绝大多数人（尤其是会去 <code class="language-plaintext highlighter-rouge">chsh</code> 的那些）的需求，也几乎不影响性能</li>
      <li>更多的支持大部分情况下也都是默认提供的，比如 virtualenv 就可以直接 <code class="language-plaintext highlighter-rouge">source &lt;venv&gt;/bin/activate.fish</code></li>
    </ul>
  </li>
  <li>在自动补全上可以碾压其他所有的 Shell
    <ul>
      <li>官方代码库中有很大一部分专门做这件事，因此效果非常好，Nushell 甚至还有个插件专门利用 Fish 的 Tab 自动补全</li>
      <li>历史命令补全几乎被所有的现代 Shell 学去（zsh / PowerShell / Nushell 都有支持，Warp 甚至自己做了一个）</li>
    </ul>
  </li>
</ul>

<p>草民的 fish 就完全是 OOBE 默认状态，没有做任何改动。为什么 OOBE 很关键？草民认为有以下三点</p>

<ul>
  <li>显著降低上手门槛：不用像 zsh 那样起手先开 omz 装一堆插件，很花时间折腾不说，插件装多了性能还会受到影响</li>
  <li>靠谱的持续维护：官方把 OOBE 作为核心关注点之一，与第三方插件提供的能力相比，质量和维护频率可以认为是更靠谱的</li>
  <li>不受环境影响：复杂的 Shell 配置在系统升级或重装之后很可能要进行调整，而 OOBE 做的好就几乎不需要考虑环境变更带来的影响</li>
</ul>

<p>顺便，这里先贴一个这两天看到的评论，后面也会更深入提及一下更广泛的 Project Lifecycle 这件事</p>

<p><img src="../assets/images/shells-and-terminals/phoronix.png" alt="" /></p>

<p>OOBE 太差也最终成为放弃 Nushell 的核心原因：真的很难理解为什么它在那么重的情况下 OOBE 几乎是 0，让人完全没有动力去深入探索</p>

<h2 id="performance">Performance</h2>

<p>有人会说 zsh 也有插件可以做 fish 那样的自动补全之类。上面已经提过复杂 Shell 配置可能会遇到的一些上手与维护上的问题，这里也再补充一点：zsh 的性能问题（包括启动和 Prompt）大概算是老大难了，到处都是优化 zsh 性能的教程，而其中大部分会建议把 omz 扬了自己写配置，但是这又会进一步加剧上面提到的折腾成本以及一些环境与版本的兼容性、复杂配置的维护成本相关的问题</p>

<p>草民在 2020 年底刚买 MacBook Pro M1，还没有 homebrew 用的时候短暂的尝试过在 macOS 自带的 zsh 上试过简单配置（印象中也就加了一个 fish 风格补全和一个 git prompt，尽量贴近 fish 的默认状态），但实际使用下来感觉比 fish 的反应明显慢很多；后面 homebrew 能用之后就立刻切回 fish 了。时间久远不太能拿出实际对比数据，不过这大概也算个公认的事情，应该也不需要继续深入</p>

<h2 id="compatibility">Compatibility</h2>

<p>大概算是 zsh 跟 fish 比最大的优势，也是它有很多用户（Debian 统计， <a href="https://qa.debian.org/popcon.php?package=zsh">zsh 7%</a> <a href="https://qa.debian.org/popcon.php?package=fish">fish 2%</a>）的关键原因？</p>

<p>先不管 bash 是不是 POSIX Compliant（事实上它也有些私货，只是被当成 sh 启动的时候会自动关掉），它已经是事实标准了，除了 OpenWrt 之类的嵌入式环境，所有的正经 Linux 再加上 macOS 都自带（当然 OpenWrt 也可以把 bash 塞进去，只是没见过有人这么干，而且感觉 busybox 提供的 ash 除了自动补全之外也没有比 bash 差很多，实际可能也就影响一些 <code class="language-plaintext highlighter-rouge">command-not-found</code> 之类的东西吧</p>

<p>zsh 可以很接近 POSIX Compliant（通过 <code class="language-plaintext highlighter-rouge">emulate sh</code>，它甚至还可以 <code class="language-plaintext highlighter-rouge">emulate ksh</code>，还好 ksh 基本只是 sh 上叠点私货，不然要维护三套 Scripting 规范就太可怕了；另外也可以通过跟 bash 相似的改名字 / 链接到 <code class="language-plaintext highlighter-rouge">/bin/sh</code> <code class="language-plaintext highlighter-rouge">/bin/ksh</code> 等方式控制 zsh 以某一特定模拟方式启动），但因为 bash 有私货，zsh 也并不能直接 drop-in 替换掉 bash，这正是上面特意提及要区分开 Interactive 和 Scripting 的原因：</p>

<ul>
  <li>除非脚本明确在第一行写了 <code class="language-plaintext highlighter-rouge">#!/bin/sh</code>，否则并不能保证脚本没有使用 bash 的的私货，万一 shebang 直接就是 bash 那更难说</li>
  <li>zsh 自己的 Scripting 其实跟 bash 也有区别，比如 zsh 数组从 1 开始而 bash 从 0 开始，参考 <a href="https://hyperpolyglot.org/unix-shells">https://hyperpolyglot.org/unix-shells</a></li>
</ul>

<p>这两天 fish 4.1.0 发布，看了 Phoronix 里面的几个帖子感觉还是有点难绷（当然 Phoronix 论坛里面也有一堆人喷 Rust 和 Systemd，所以也不奇怪）：很多人还是完全不能把 Interactive 和 Scripting 区分开，或者会真的把一开始说的「用 Interactive 跑巨长巨复杂的命令」当作关键用法。只能评价为各种意义上都很离谱，安不安全另说，老长一串看也没法看改也没法改，跟 Regex 可以一并称之为纯纯魔法吟唱</p>

<p>不过 fish 也有槽点，主要还是跟 bash 的区别没有大到有明显的感知（<a href="https://fishshell.com/docs/current/fish_for_bash_users.html">https://fishshell.com/docs/current/fish_for_bash_users.html</a>），虽然个人觉得 fish 留下的这些基本能覆盖 80% 甚至更多的 Interactive 用法，这很好，但是也许区别更大一点或者更小一点都更好：</p>

<ul>
  <li>如果区别再小一点比如支持 <code class="language-plaintext highlighter-rouge">`cmd`</code>，能够在 Interactive 情况下跟 bash 更加一致说不定会更好（但可能确实不太好实现）</li>
  <li>Nushell 就完全不会被拿来跟 bash 之类对比，因为区别实在太大了 <a href="https://www.nushell.sh/zh-CN/book/coming_from_bash.html">https://www.nushell.sh/zh-CN/book/coming_from_bash.html</a></li>
</ul>

<p>除了 bash 之外还有更轻量的 dash，除了 POSIX 之外几乎不考虑 Interactive 的体验（仅仅是有个 Interactive 那种水平，没有任何 Tab 补全的能力），在 Debian 上用来充当 /bin/sh（顺带一提后来才发现 macOS 竟然也自带了 dash，好离谱</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ls -al /bin/*sh</span>
<span class="nt">-r-xr-xr-x</span>  1 root  wheel  1310208 Sep 25 15:03 /bin/bash<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span>  2 root  wheel  1091936 Sep 25 15:03 /bin/csh<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span>  1 root  wheel   274272 Sep 25 15:03 /bin/dash<span class="k">*</span>
<span class="nt">-r-xr-xr-x</span>  1 root  wheel  2549488 Sep 25 15:03 /bin/ksh<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span>  1 root  wheel   101232 Sep 25 15:03 /bin/sh<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span>  2 root  wheel  1091936 Sep 25 15:03 /bin/tcsh<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span>  1 root  wheel  1361200 Sep 25 15:03 /bin/zsh<span class="k">*</span>
</code></pre></div></div>

<p>以上来自 macOS 26.0.1 arm64，这里面 csh 硬链接到 tcsh，sh 是个自动选择 bash / dash / zsh 的小工具（以下来自它的 man page</p>

<blockquote>
  <p>sh is a POSIX-compliant command interpreter (shell).  It is implemented by re-execing as either bash(1), dash(1), or zsh(1) as determined by the symbolic link located at /private/var/select/sh.  If /private/var/select/sh does not exist or does not point to a valid shell, sh will use one of the supported shells.</p>
</blockquote>

<h2 id="project-lifecycle">Project Lifecycle</h2>

<p>接下来是一个跟实现并不那么相关但非常非常重要的事情。Fish 花了差不多两年时间，整体迁移到 Rust，应该算是非常成功：</p>

<ul>
  <li>虽然整个代码库被整个重写（包含 2000+ commit），但整个迁移可以说是用户没有任何感知
    <ul>
      <li>虽然大版本号升级到了 4，但实际上 Scripting 部分的变化可以说是相当小的</li>
      <li>因为 fish 追求 OOBE 的目标，大部分用户也基本上没有很复杂的定制项目，显著降低了被迁移影响的可能性</li>
    </ul>
  </li>
  <li>过程中吸引了相当多的新 Contributor，虽然不太好统计完全准确的数据但还是列一下：
    <ul>
      <li>过去 11 年 fish 使用 cpp 的过程中只有 17 个人有超过 10 个 commit</li>
      <li>第一个 Rust 版本虽然大部分工作是 7 个人完成的（同样以超过 10 个 commit 为准），但整个过程有超过 200 人参与贡献</li>
      <li>fish 4.1 根据 GitHub 上的数据有 1396 个 commit，126 人参与，其中 70 个是新贡献者</li>
    </ul>
  </li>
</ul>

<p>迁移完成之后今年也发了五六个版本，保持在一个积极迭代的状态上。而与之相比：</p>

<ul>
  <li>bash 很难被期待有什么新功能引入进来，虽然 2025-07-23 刚发布了 bash 5.3 但是跟 2022-09-26 的 5.2 相比只有 40 个 commit</li>
  <li>zsh 稳定 tag 是 2022-05-15 发布的 5.9，三年多没 Release 了甚至比 bash 还老；过去一年在 master 分支上只有一百来个 commit</li>
  <li>Nushell 的变更频率倒是非常高，可能几周就更新一次，不过现在一直还是不稳定状态，而且 breaking 的变更挺多</li>
  <li>PowerShell 倒是也还好，毕竟大公司支撑，虽然感觉在 Windows 以外的平台上用的人确实还是相当少</li>
</ul>

<p>bash 在 macOS 上还有一个比较痛的点是因为许可证的原因，版本被固定到了 3.2.57，事实上框死了 Scripting 的能力范围</p>

<h2 id="other-shells">Other Shells</h2>

<p>一点题外话，吐槽一下 Nushell 和 PowerShell 这两个大概算是相当异端的东西。他们俩有一些共同点：</p>

<ul>
  <li>跨平台
    <ul>
      <li>Nushell 直接提供多个平台、多种架构的二进制，GitHub 直接可以下载；很多发行版、brew / winget 也都能够直接安装</li>
      <li>PowerShell 多版本并行，Windows 10 / 11 自带 5.1，Microsoft Store 里那个 / 能跨平台装上 Linux 或者 macOS 的那个是 7.x</li>
    </ul>
  </li>
  <li>结构化的数据类型
    <ul>
      <li>传统的 Shell 大部分情况下都是在输入输出文本，依赖大量外部工具（比如 <code class="language-plaintext highlighter-rouge">sort</code> <code class="language-plaintext highlighter-rouge">uniq</code> 或者复杂一点 <code class="language-plaintext highlighter-rouge">awk</code> <code class="language-plaintext highlighter-rouge">sed</code>）处理文本</li>
      <li>结构化的 Shell 则是处理对象，比如 <code class="language-plaintext highlighter-rouge">ls</code> 返回的是一个 Object List，可以利用 Shell 自身的能力对它进行投影、筛选、排序</li>
    </ul>
  </li>
  <li>很重，自带很多东西
    <ul>
      <li>算是结构化 Shell 必须做的一件事，毕竟现存的外部命令并不能直接返回结构化的数据，需要先覆盖最常见的用法</li>
      <li>Nushell 本体自带了大部分 <code class="language-plaintext highlighter-rouge">uutils</code>（当然还不只这些）并做了一些结构化的魔改，PowerShell 自带的东西更是非常非常多了</li>
    </ul>
  </li>
  <li>在上一条的基础上却几乎没有做什么 OOBE 相关的努力，默认情况下的 Interactive 体验跟传统的 Shell 几乎没有什么区别
    <ul>
      <li>Nushell 至少还默认做了历史补全，PowerShell 的这个东西还需要升级一下 PSReadline 再特意启用一下这个功能</li>
      <li>PowerShell 默认情况下的 Tab 补全能支持 Module 中的命令和参数（但是不如 fish 那样能显示一点帮助），Nushell 则约等于无</li>
    </ul>
  </li>
</ul>

<p>下面再提一些他们俩自己的特点。</p>

<h2 id="powershell">PowerShell</h2>

<p>PowerShell 除了上面提到的特性，最牛逼的一点肯定还是跟 C#（包括整个 CLR 环境）的无缝衔接，甚至拿 C# 来 Scripting 也不是不行</p>

<p><img src="../assets/images/shells-and-terminals/css.png" alt="" /></p>

<p>至于 PowerShell 最大的槽点，个人认为还是 Windows 自带的 Windows PowerShell 5.1 和上面提到的跨平台 7.x 两个版本并行导致的割裂，就因为这个割裂的事情导致 PowerShell 7.x 个人感觉就很难推广起来：</p>

<ul>
  <li>说它能跨平台吧，很多 Module 没的用，甚至就算都在 Windows 上，能支持的 Module 和一些其他环境相关也有区别
    <ul>
      <li>比如臭名昭著的 <code class="language-plaintext highlighter-rouge">curl</code> = <code class="language-plaintext highlighter-rouge">Invoke-WebRequest</code> 在 7.x 去掉了，但是 5.x 去不掉，结果还是天天被人喷</li>
    </ul>
  </li>
  <li>不考虑跨平台吧，为什么不直接用保证系统自带了的 5.1，根本就没有必要用 7.x，还得让用户专门装
    <ul>
      <li>甚至 Windows 上还要再装个 .NET 6 Runtime 才能跑起来，当然商店版本会自动搞定</li>
    </ul>
  </li>
</ul>

<p>草民使用 PowerShell 7.x 的经历十分短暂，macOS 上感觉还是很别扭，Windows 上感觉跟系统自带的那个没啥区别，所以很快就弃坑了。</p>

<h2 id="nushell">Nushell</h2>

<p>草民使用 Nushell 的经历也非常短暂，最核心的原因主要还是上面提到的 OOBE 太差劲。至于用下来最大的感受，还是让草民开始觉得 fish 是不是可以考虑离 bash 兼容性更远一点，抛掉更多技术债务的同时，也不用天天被拿来跟 zsh / bash 做对比</p>

<p>当然了，Nushell 的槽点除了 OOBE 很糟糕之外，还有很多：</p>

<ul>
  <li>为了结构化数据类型，自带了很多命令，但其中有很多都是改变了习惯的，让人觉得很不适应
    <ul>
      <li>比如很典型的一个，<code class="language-plaintext highlighter-rouge">uname</code> 不支持 <code class="language-plaintext highlighter-rouge">-a</code> 参数，因为不带参数的就会返回一个 6 x 2 的表格，包含了 <code class="language-plaintext highlighter-rouge">-a</code> 中的所有内容</li>
      <li>与自动补全之类的 Interactive 特性不同，常用命令的差异会让人感觉仿佛在用一个完全不同的操作系统那样，非常非常别扭</li>
    </ul>
  </li>
  <li>插件功能是个用 stdio 通信的外部进程，导致后台一直开着一大堆很重的东西
    <ul>
      <li>真不如像 PowerShell 那样可以动态加载进来，还可以更进一步跨平台到 iOS 上</li>
      <li>用 <code class="language-plaintext highlighter-rouge">dlopen</code> 之类的特性不好做的话，wasm 感觉也可以接受（虽然插件性能会差一些，但也只影响自动补全啥的，问题不大</li>
      <li>不确定是因为这个特性还是 <code class="language-plaintext highlighter-rouge">nu</code> 本体太重，Nushell 即使是 OOBE 状态也会给人一种反应很慢的感觉，远没有 fish 来的利落</li>
    </ul>
  </li>
  <li>前面反复提过它很重，但是重到什么地步呢：以 Manjaro amd64 提供的 <code class="language-plaintext highlighter-rouge">nushell-0.107.0-1</code> 为例
    <ul>
      <li><code class="language-plaintext highlighter-rouge">nu</code> 本体除了上面提到的 <code class="language-plaintext highlighter-rouge">uutils</code> 之外，甚至还带了 <code class="language-plaintext highlighter-rouge">sqlite</code> 和网络请求能力，结果直接干到 35M 大，实在是感觉没必要</li>
      <li>至于它的插件更是相当夸张了，最大的一个 <code class="language-plaintext highlighter-rouge">nu_plugin_polars</code> 有 66MB，这一堆加起来有 130MB 多了</li>
    </ul>
  </li>
</ul>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># ls -al bash dash fish* nu nu_plugin_* zsh</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  1162232 Aug  2 22:56 bash<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root   133792 Feb 25  2023 dash<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  4379792 Sep 19 15:32 fish<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  4408464 Sep 19 15:32 fish_indent<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  1268832 Sep 19 15:32 fish_key_reader<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root 35141032 Sep  3 11:52 nu<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  6030584 Sep  3 11:52 nu_plugin_custom_values<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  6759568 Sep  3 11:52 nu_plugin_example<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  6538264 Sep  3 11:52 nu_plugin_formats<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  6643296 Sep  3 11:52 nu_plugin_gstat<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  2794464 Sep  3 11:52 nu_plugin_inc<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root 66432592 Sep  3 11:52 nu_plugin_polars<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root  7414968 Sep  3 11:52 nu_plugin_query<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 1 root root   488336 Sep  3 11:52 nu_plugin_stress_internals<span class="k">*</span>
<span class="nt">-rwxr-xr-x</span> 2 root root   947360 Mar 15  2024 zsh<span class="k">*</span>
</code></pre></div></div>

<p>相比之下同样由 Manjaro amd64 提供的 <code class="language-plaintext highlighter-rouge">fish-4.0.8-1</code> 本体只有 4MB 多一点（虽然也确实不算小了，<code class="language-plaintext highlighter-rouge">zsh-5.9-5</code> 本体只有不到 1MB 比 <code class="language-plaintext highlighter-rouge">bash-5.3.3-2</code> 还小一点，<code class="language-plaintext highlighter-rouge">dash-0.5.12-1</code> 更是只有 133KB 左右，当然这些都没有算上链接的 so</p>

<h1 id="terminals">Terminals</h1>

<p>最后是一点个人对终端（主要是终端模拟器）的选择。首先是对个人口味的描述：</p>

<ul>
  <li>跟上面对 Shell 的选择一样，看重 OOBE 与环境稳定性，因此倾向于选择不需要花很多时间配置的工具</li>
  <li>有在服务器上干重活的需求，因此习惯于使用 tmux，进一步导致对终端模拟器提供的分屏 / Tab 能力也不感兴趣
    <ul>
      <li>分屏几乎完全不用，用的话一般也是开两个窗口然后用操作系统自带的窗口排列功能并排放在一起</li>
      <li>Tab 也不咋用，像 Windows Terminal 和 Warp 那种提供了的情况会偶尔用一下，但不会成为选择 / 放弃某款终端模拟器的理由</li>
    </ul>
  </li>
  <li>因为被 JetBrains IDEA 自带的终端模拟器整的烂活坑过很多次，故倾向于不使用终端模拟器提供的高级特性
    <ul>
      <li>比如 AI 集成这种会拦截输入的特性会严重破坏 tmux 的可用性，还有自动复制这种可能会乱动剪贴板的功能</li>
      <li>与其说「不使用这些特性」不如说「最好不提供这些特性，免得还得花时间关甚至可能压根关不掉」，做好一个终端该做的事情</li>
    </ul>
  </li>
</ul>

<h2 id="multiplatform">Multiplatform</h2>

<p>这里列出几个跨平台的选项，但其实个人最推荐 VSCode（除了启动慢点几乎没啥毛病</p>

<h3 id="alacritty">Alacritty</h3>

<p>虽然是跨平台，但只在 Windows 10 上用过几个月的时间</p>

<ul>
  <li>优点是用 OpenGL 渲染因此确实很快，而且很简洁没有什么很复杂的特性</li>
  <li>缺点是配置比较复杂，而且因为自己搞字体渲染，在低分辨率屏幕上也不能关掉抗锯齿，所以只适合在高分辨率屏幕上使用</li>
</ul>

<p>时间比较久远，并不记得最终是什么原因放弃了。</p>

<h3 id="warp">Warp</h3>

<p>虽然是跨平台，但主要在家里的 Windows 11 PC 上用</p>

<ul>
  <li>真的跨平台，一跨到底
    <ul>
      <li>尤其针对 Windows 的支持，OOBE 就同时配好了 Windows PowerShell、WSL 和 Git Bash</li>
      <li>而且官方直接提供 Windows ARM64 构建，对于一个闭源软件来说真的非常难得</li>
    </ul>
  </li>
  <li>虽然有很多乱七八糟的高级特性，但能做到不影响正常使用
    <ul>
      <li>AI 集成甚至是主推功能，但拦截输入 / 不拦截输入这两个模式的界限很明确，不像 IDEA 那样混在一起互相影响</li>
      <li>多数复杂功能可以通过很简单的步骤关闭，而 IDEA 就在做的很烂的同时还压根关不掉</li>
    </ul>
  </li>
  <li>它确实在 UI / UX 上做了非常多的工作，把 OOBE 状态下的 Interactive 做的非常好用，而且性能也不差
    <ul>
      <li>尤其对 fish 用户来说感觉会非常熟悉，包括 Tab 补全和历史命令补全都非常好用</li>
      <li>而且它完全是自己实现的这些特性，即使底下是 bash 或者 PowerShell 也能获得一致的体验（比如下图是 PowerShell）</li>
    </ul>
  </li>
</ul>

<p><img src="../assets/images/shells-and-terminals/warp.png" alt="" /></p>

<p>当然 Warp 也有一些问题：</p>

<ul>
  <li>运营更接近互联网 App，包括强制注册账号才能使用，以及会用比较打扰的方式推广新特性</li>
  <li>因为做的事情确实很多所以还是比较重，Windows 上的二进制文件 200MB+，对机器配置也有一定需求</li>
  <li>跟 Alacritty 一样的字体渲染问题，低分辨率屏幕上显示效果很不理想
    <ul>
      <li>虽然在 Linux / macOS 上的默认设置没啥问题，但 Warp 提供的字体相关的调整选项不多，字重、Fallback 这些都没有</li>
      <li>Windows 上默认字体中文就很难看了，在懒得找字体的情况下建议用系统自带的黑体，HiDPI 上中英文效果基本上比较一致</li>
    </ul>
  </li>
</ul>

<p>总体来说个人还是挺推荐使用的，尤其是对于 Windows 用户能在 WSL 之外的环境上体验到接近 fish 的 Interactive 体验</p>

<h3 id="electron-based-like-hyper">Electron Based (Like Hyper)</h3>

<p>可能会有人提，但个人的评价是不如直接用 VSCode，这里也就不展开了。</p>

<h2 id="linux-desktop">Linux Desktop</h2>

<p>一般建议是选择桌面环境自带的终端（比如 KDE 就是 Konsole），例外情况：</p>

<ul>
  <li>如果是 WSL 之类的情况又需要一个原生渲染出来的终端的话个人比较推荐 <a href="https://tracker.debian.org/pkg/stterm">st</a>，很轻而且默认情况下足够好用</li>
  <li>WSL 上直接跑 Warp for Linux 效果不太好，速度很慢且有些这样那样的兼容性问题（比如 HiDPI 一会儿小一会儿大），不推荐</li>
</ul>

<h2 id="macos">macOS</h2>

<p>现在草民在 macOS 上不干重活，重活都要 ssh / VSCode Remote / JetBrains Gateway 到其他的机器上了，macOS 上跑的基本上就只有 <code class="language-plaintext highlighter-rouge">jekyll serve</code> 和 <code class="language-plaintext highlighter-rouge">brew upgrade --greedy</code>，所以目前草民的方案是 macOS 自带终端 + fish + tmux</p>

<p>macOS 大部分人应该会用 iTerm2，草民也用过挺长一段时间，不过原因是当时 macOS 自带的终端跟 fish 有一些兼容性问题，导致滚动和 Tab 补全都很别扭。后面这个问题不知道什么时候修好了，草民又完全没用到过 iTerm2 自带的一些高级特性，于是草民就换了回来</p>

<p>至于上面提过的跨平台方案，Alacritty 和 Hyper 感觉跟自带的也没啥区别；Warp 倒是可以考虑一下的，但是如果已经装了 fish + tmux 而且用不到 Warp 提供的 AI 能力的话感觉意义不大，没有装这些的话倒是也挺建议尝试一下 Warp 的。当然 VSCode 也很推荐（</p>

<h2 id="windows">Windows</h2>

<p>除了上面提过的几个跨平台方案，还主要用过几个：</p>

<ul>
  <li>conhost.exe
    <ul>
      <li>Windows 10 自带的那个。其实因为 Windows 10 开始加了一些 Unix 终端相关的 Control Sequence 支持，所以也能凑合用</li>
    </ul>
  </li>
  <li>Windows Terminal
    <ul>
      <li>大部分情况下还是挺好用的，渲染也很快，也能关掉抗锯齿，但就是感觉 UI 还是有那种 UWP 特有的磨磨叽叽的感觉</li>
    </ul>
  </li>
  <li>Mintty（Git Bash / wsltty）
    <ul>
      <li>直接用 Win32 API 来做的，好处是很简洁，而且高低分辨率屏幕上字体渲染都很干净，坏处就是渲染是真的很慢</li>
    </ul>
  </li>
</ul>

<p>目前大部分情况下都是 Windows Terminal 搭配 Warp 来用了。</p>

<h1 id="end">End</h1>

<p>总之是花了几天时间又把这个日经问题翻出来扯了一遍，回收了一下半年前立的 Flag</p>

<p>下一个应该是 11 月底左右发 Vacation 2025.2，然后如果有精力的话可能十月份会再发一个跟 RSS 相关的一些折腾（没精力就明年见</p>]]></content><author><name>yichya</name></author><category term="Technology" /><category term="shell" /><category term="terminal" /><summary type="html"><![CDATA[几个月前尝试了一下 Nushell（虽然很快就放弃了），然后就突然想做一下用过的几种 CLI Shell 和 Terminal Emulator 的对比，但后面觉得好像不是很有意义，于是大概会变成安利 fish 再搭配一些可能非常离谱的暴论]]></summary></entry><entry><title type="html">Vacation 2025.1</title><link href="https://www.yichya.dev/vacation-2025-1/" rel="alternate" type="text/html" title="Vacation 2025.1" /><published>2025-07-11T00:00:00+08:00</published><updated>2025-07-11T00:00:00+08:00</updated><id>https://www.yichya.dev/vacation-2025-1</id><content type="html" xml:base="https://www.yichya.dev/vacation-2025-1/"><![CDATA[<p>本来 Vacation 2025.1 应该在五月更新的，但刚好 Gadgets 2025 卡在五月了于是往后挪一挪。上半年跑的地方还挺多的，频率也跟去年一样一个月最少一次。而且目前上半年四场 Live，下半年目前确定的都至少两场了，说不定还有两次整大活的计划，好像浪的有点狠了啊（</p>

<h1 id="111-滑雪">1.11 滑雪</h1>

<p>「周末偶遇初级雪道，单板光滑难如怪物，拼尽全力无法战胜」</p>

<p><img src="../assets/images/vacation-2025-1/1110.jpeg" alt="" /></p>

<p>头一次在晚上来都江堰转悠。还泡了个温泉，贼爽（去年好像也是年初去峨眉山泡的，这个能不能也安排成年常啊</p>

<p><img src="../assets/images/vacation-2025-1/1111.jpeg" alt="" /></p>

<p>花销不大，买的是融创那个滑雪加温泉加酒店的套餐吧好像是，价格也挺便宜好像一个人两三百块？具体忘了，总之还挺不错的</p>

<h1 id="118-三岔湖">1.18 三岔湖</h1>

<p>不知道是不是没去对地方，感觉比较差劲。实习期开车初体验（还好没出什么乱子</p>

<p><img src="../assets/images/vacation-2025-1/1180.jpeg" alt="" /></p>

<p>晚上去了趟山姆（蹭马老板的卡）。今年进口的樱桃确实不错（虽然我个人还是比较喜欢现摘那种稍微有点酸味的，但今年没安排上</p>

<h1 id="215-泠鸢-yousa-线下-live成都场">2.15 泠鸢 yousa 线下 Live（成都场）</h1>

<p>也是真的期待了很久啊。刚好过年被春晚版本的《春意红包》狠狠创飞了，赶紧来洗洗耳朵</p>

<p><img src="../assets/images/vacation-2025-1/2151.jpeg" alt="" /></p>

<p>真的是氛围最好的一次 Live，尤其是咖啡厅实在是太棒了，下次还去（</p>

<p><img src="../assets/images/vacation-2025-1/2152.jpeg" alt="" /></p>

<p>成功获得汐米大佬现场签名！</p>

<p><img src="../assets/images/vacation-2025-1/2153.jpeg" alt="" /></p>

<p>签名板 50% 分辨率（尽力了，原图实在是太大</p>

<p><img src="../assets/images/vacation-2025-1/2154.jpeg" alt="" /></p>

<p>但有一说一，这合影真是拍的……无力吐槽，两边怎么都能拍不全的</p>

<p><img src="../assets/images/vacation-2025-1/2150.jpeg" alt="" /></p>

<p>然后重点感谢一下马老板愿意跟我一起玩（</p>

<h1 id="219-泼墨漓江">2.19 泼墨漓江</h1>

<p>戒断 + 消磨假期。竹筏、游船、银子岩分别获得前三名</p>

<p><img src="../assets/images/guilin-experience/2100003.jpeg" alt="" /></p>

<p>这里就只摘一张点题的图占位了，具体见 <a href="../guilin-experience">Guilin Experience</a></p>

<h1 id="315-哪吒-2-创作展">3.15 哪吒 2 创作展</h1>

<p>2.14 票房过百亿的时候去工作室门口打了卡。这次去这个展览的时候应该刚好是全球票房前五，也顺便沾沾喜气。</p>

<p><img src="../assets/images/vacation-2025-1/3150.jpeg" alt="" /></p>

<p>地方不大人可真多。后面又来了几次买海报，嘻嘻。希望这波也能再带动一下成都的相关产业</p>

<h1 id="322-桃花故里">3.22 桃花故里</h1>

<p>早上起的就很晚了，结果花期更晚（目测山上只开了可能 10% 的花吧）。幸亏这次没拉人来</p>

<p><img src="../assets/images/vacation-2025-1/3220.jpeg" alt="" /></p>

<p>下午在广场上全程围观了一个多小时的，呃，某个大概只能称之为「团伙」的活动，包括：</p>

<ul>
  <li>杂技（还行吧</li>
  <li>「特技」表演（怎么看怎么像江湖骗子，具体不说了免得被喷</li>
  <li>变脸 + 喷火（也没啥毛病吧</li>
  <li>某位大哥把一条小蛇从鼻孔穿进去再从嘴里抽出来（呃……这个没得喷，是真的狠活</li>
</ul>

<p>吸引了一圈儿人之后开始现场兜售字画，倒是也不贵一幅几百块，但这个活动就，真的很抽象……旁边市集活动也一样的抽象，赞助商竟然是个私立高中，而且看起来还是刚成立没多久那种。有一说一还不如跟前两年一样承办一点少儿围棋比赛或者才艺表演什么的呢 = =</p>

<p>当然喷归喷，明年该来还是来（不过明年还是顺便计划一下洛带古镇的事情，这次早上起太晚没去成可惜了</p>

<h1 id="44-犍为县">4.4 犍为县</h1>

<p>奔着吃东西的目标去，但感觉很踩坑，不推荐</p>

<ul>
  <li>牛肉饼，像是粉蒸牛肉夹在薄饼里面，不如肉夹馍</li>
  <li>串串，从冷柜里面拿出来解个冻往红油里面一泡就端上来了，压根不入味，甚至远不如能在成都吃到的同款味道好</li>
  <li>冰粉，凑合吧就正常冰粉</li>
</ul>

<p>然后去看了犍为文庙，emmmmm 个人对这种地方也没啥兴趣，不过有意思的是还真有人在这种地方求姻缘 hhh 以及另外一些小吃：</p>

<ul>
  <li>某个什么豆腐夹萝卜丝，额，真不爱吃酸辣口，更不爱吃白萝卜</li>
  <li>葱油酥，不错，好吃</li>
  <li>洋芋坨坨，跟上面那个串串搞法差不多，总之是真的不好吃。。。</li>
  <li>蛋烘糕，还可以</li>
</ul>

<p>回家前顺便溜达了几个地方</p>

<ul>
  <li>县政府，emmmm 就西电老校区那种感觉</li>
  <li>下图这个商圈。应该是这边最大的商圈了，但还是很鬼：一楼冷冷清清，二楼原来大概是个衣服卖场直接全关，三楼儿童乐园还算有点儿人气（不多），四楼只有个轮滑好像还在开着门，五楼只剩电影院其他有什么 ai 教育什么自助餐之类全关了，封闭用的布还印着欢度猴年</li>
  <li>顺便路过了几个中介，房价差不多不到五千（差不多 130 多平六十万多一点吧，很多都挂这个价，面积小的单价贵一点），租个三室两厅挂价都是年付 14000 有一说一有点贵，还有些招工一月两三千，感觉还是有点难做</li>
</ul>

<p>这个广场上的雕像也有那么一点抽象，没太看明白是个什么造型</p>

<p><img src="../assets/images/vacation-2025-1/4040.jpeg" alt="" /></p>

<p>晚上回华阳吃了个何师烧烤，舒坦多了。</p>

<h1 id="412-司南出发梦话live">4.12 司南「出发·梦话」Live</h1>

<p>正火的效果确实很不错，比上次那个场地好太多了。挺开心（除了一点，下次还是最好不要被抽到互动了</p>

<p><img src="../assets/images/vacation-2025-1/4120.jpeg" alt="" /></p>

<p>槽点主要还是门票类型蛮怪的（这点不如上次），以及服装还是有点无力吐槽（这个跟上次差不多）。顺便在前排遇到三个鸟蛋，刚好就把上次剩下的几个签儿分给他们了（</p>

<h1 id="52---55-劳动节假期广州小转一圈">5.2 - 5.5 劳动节假期广州小转一圈</h1>

<p>虽然重点是🐦的 Live，但是为了机票效益最大化，这个安排会有点复杂：</p>

<ul>
  <li>5.2 一大早赶飞机去深圳
    <ul>
      <li>下飞机马上去福田口岸过关到香港，预计十二点半到一点左右</li>
      <li>至少呆两到三个小时（主要是为了办卡），期间简单溜达一下（也不是很想溜达）</li>
      <li>七点左右出口岸找前同事吃饭，然后晚上住在口岸附近的亚朵</li>
    </ul>
  </li>
  <li>5.3 早上赶城际前往大学同学家里，然后就看他安排了，简单耍一天。晚上住在 Livehouse 附近亚朵</li>
  <li>5.4 全天活动，晚上住在 Livehouse 附近亚朵</li>
  <li>5.5 早上赶飞机回成都</li>
</ul>

<p>整体基本上跟计划一样，没有啥偏差。虽然出去的时间不算短，但图并不多，不打算专门拆一篇出来了。</p>

<h2 id="52-机票效益最大化">5.2 机票效益最大化</h2>

<p>当天去香港的人巨多，下午一点多到海关，快两点才坐上港铁。四点做完主线任务（三个港卡开户），流程倒是意外的很顺利（也不完全顺利吧但是问题不大）。完事之后在铜锣湾和旺角简单溜达了三个小时，感觉其实除去繁体中文之外，香港跟国内其他地区也没什么很大区别。城市界面个人感觉跟重庆有几分相似，就是很多那种很破旧的高楼，就很赛博。街边会有那种，门面金灿灿的麻将馆，突出一个土气</p>

<p><img src="../assets/images/vacation-2025-1/5020.jpeg" alt="" /></p>

<p>以及有一说一，居住成本是真夸张。拍了几个旺角那边中介挂在外面的，换算一下单位：</p>

<ul>
  <li>「本店精选」66 平方米建筑面积的房子要将近 700w（等一下，这好像也就是北上深相似地段的基本操作？</li>
  <li>租的价格更是离谱，最便宜的一个 32 平米建筑面积的房子就要八千多块一个月了，中关村房租怕是都不到这个一半</li>
</ul>

<p><img src="../assets/images/vacation-2025-1/5021.jpeg" alt="" /></p>

<p>港铁的报站是粤语 &gt; 普通话 &gt; 英语，深圳和广州地铁的报站是普通话 &gt; 粤语 &gt; 英语，所以好像真的也没多大区别，就是票价是真的贵的离谱。另外 5.2 在香港感觉就像是个正常工作日，所以也没看到什么菲佣之类的（虽然看到了广告），其他的感觉就没啥了</p>

<p><img src="../assets/images/vacation-2025-1/5022.jpeg" alt="" /></p>

<p>晚上八点左右回来，跟前同事吃了个网红海鲜大排档（有一说一这家店怎么敢收那么多钱的草，太坑了）。住了一个评价为住过的倒数第二辣鸡的亚朵，没有到倒数第一是因为至少比起九寨沟那个来说呼吸还算是顺畅的，其他的只能说又贵又不舒服。害</p>

<h2 id="53-突出一个羡慕黄大人">5.3 突出一个羡慕黄大人</h2>

<p>早起坐车去深圳北（中间刚好路过民乐站，这一站要是真的能换乘家门口的成都地铁五号线就好了</p>

<p><img src="../assets/images/vacation-2025-1/5030.jpeg" alt="" /></p>

<p>城际到东莞，地铁到黄大人家附近吃了个早茶，差点吃撑了。完后参观了一下黄大人差不多装修完的新家，真不错，尤其那个床垫真种草了，之后也看看怎么买一个。下午坐另一个略显特别的城际到广州（特别的点主要是它更像是一个地铁和普通城际之间的东西</p>

<p><img src="../assets/images/vacation-2025-1/5031.jpeg" alt="" /></p>

<p>可惜当天天气太差，暴雨连下好几个小时，来得及去的地方不多。经典环节：地狱西路圣地巡礼</p>

<p><img src="../assets/images/vacation-2025-1/5032.jpeg" alt="" /></p>

<p>粗逛下来，广州除了漂亮妹纸确实多之外感觉一般，尤其是城市界面确实还是有些老旧，而且有一说一尼哥是真多。晚上入住在 Live 场地附近的亚朵，还不错，价格比上面那个便宜一些的同时也舒服不少</p>

<h2 id="54-泠鸢-yousa-线下-live广州场">5.4 泠鸢 yousa 线下 Live（广州场）</h2>

<p>早上起来肯定先去茶餐厅啦。见到了很多熟面孔（嘻嘻</p>

<p><img src="../assets/images/vacation-2025-1/5043.jpeg" alt="" /></p>

<p>成都小分队合影（后面的签名板倒是也拍了，可以到 B 站找草民的动态，里面有</p>

<p><img src="../assets/images/vacation-2025-1/5040.jpeg" alt="" /></p>

<p>虽然抽奖什么的从来抽不到，但座位倒是运气不错，刚好正中间。不过有一说一音响效果一般般，以及又是一个两边切角的大合照（</p>

<p><img src="../assets/images/vacation-2025-1/5041.jpeg" alt="" /></p>

<p>这次的签儿（跟上次区别不大所以少拍了一个），以及乱入一些上面合影里出现的某位鸟蛋的银临周边（前两天南京有活动吧应该是</p>

<p><img src="../assets/images/vacation-2025-1/5042.jpeg" alt="" /></p>

<p>整体体验也很好，虽然没有上次那种特别强烈的感觉了。再下次是 8.2 上海，因为七月还要去🐰的 Live，短时间跑两次上海成本实在是太高，而且歌单竟然没有《泼墨漓江》……总之就不去了（可能到时候想办法摇人帮带一点限定的小东西</p>

<h2 id="55-燃尽了回家">5.5 燃尽了回家</h2>

<p>后面也许会写一篇开卡和入金相关的事情（等汇丰密码涵和地址信都到了，或者确定都丢了之后再说吧</p>

<h1 id="511-街子古镇">5.11 街子古镇</h1>

<p>恭喜马老板喜提新🚗（然后给他添加一点二次元浓度</p>

<p><img src="../assets/images/vacation-2025-1/5110.jpeg" alt="" /></p>

<p>这个桥就有那么一点像上面的南桥（当然远不如那边热闹</p>

<p><img src="../assets/images/vacation-2025-1/5111.jpeg" alt="" /></p>

<p>这地方还不错，人也不太多（可能是因为是周日，而且来的也比较晚）。吃了一堆辣的</p>

<h1 id="517-锦城湖拍个小视频">5.17 锦城湖拍个小视频</h1>

<p>成都鸟蛋小聚一下。</p>

<p><img src="../assets/images/vacation-2025-1/5170.jpeg" alt="" /></p>

<p>晚上顺便去接了小竹的朋友，吃了一个🐰火锅，味道还不错（除了原汤当蘸料这种是真顶不住</p>

<h1 id="524-黄龙溪">5.24 黄龙溪</h1>

<p>骑过去 30km 骑回来 40km，隔天心率还贼快，睡了一下午才缓过来</p>

<p><img src="../assets/images/vacation-2025-1/5240.jpeg" alt="" /></p>

<p>至于这地方就，呃，一般般吧（主要还是过来一趟有点费劲，如果地铁直达那可以评价为很不错</p>

<h1 id="621-不才雾的-19-日live">6.21 不才「雾的 19 日」Live</h1>

<p>当天重庆还有个墨村的 Live 不过好像跟去年的安排一样，去年的体验也一般所以不去了（结果这次 VIP 就全都有签名，更气了）。这一场的时间也是安排的莫名其妙，一开始真的就以为不会有了（于是加价一倍买了新 EP，虽然也就一百块，无所谓了</p>

<p>时隔两年半又见面真的很开心！这次也终于成功给手里的初版《山止川行》签了名，完满了</p>

<p><img src="../assets/images/vacation-2025-1/6210.jpeg" alt="" /></p>

<p>另外还是感谢马老板跟我一起玩，以及顺便还达成了网友见面成就（</p>

<h1 id="end">End</h1>

<p>最后例行晒一下最近半年买的碟，还挺多。首先是一些零零散散的</p>

<p><img src="../assets/images/vacation-2025-1/d4.jpeg" alt="" /></p>

<p>然后是 B 站会员购专场（基本上都是眼熟就支持一下的，当然在 B 站也都多少有点名气</p>

<p><img src="../assets/images/vacation-2025-1/d0.jpeg" alt="" /></p>

<p>说起来签名的这个《星灵歌者》差点就错过了，好险。下面再专门提几个比较重要的</p>

<h2 id="山色有无中">《山色有无中》</h2>

<p>两个版本都买了（但小声 bb，有一说一这张碟也就一般，个人觉得最好的是《愚人歌》其次是《眠花去》，其他的就没那么戳</p>

<p><img src="../assets/images/vacation-2025-1/d1.jpeg" alt="" /></p>

<p>有官方拆箱视频所以这里也就懒得拆了。顺便关于近期节奏，只能说工作室还是长点心吧，多了解一下自己的粉丝群体</p>

<h2 id="茜色诗集">《茜色诗集》</h2>

<p>说起来这张碟竟然还是司夏群里的某个群友出的。稍微多花了点钱但还是很值得，也算是全收集了</p>

<p><img src="../assets/images/vacation-2025-1/d2.jpeg" alt="" /></p>

<p>顺便抽奖了两张三专都没中签（怎么就每次遇到皮套人就运气不好，什么🧊什么🐦什么🐰都是</p>

<h2 id="竹梦令">《竹梦令》</h2>

<p>老古风人绝对会懂的一张碟，不多解释。顺便签名 Get</p>

<p><img src="../assets/images/vacation-2025-1/d3.jpeg" alt="" /></p>

<p>这个卖家也是非常 nice，基本上就是原价出了，保存的也很好。这波真的是圆了多年来的心愿</p>

<h2 id="混入人类计划">《混入人类计划》</h2>

<p>有一说一，这盒子好大啊，差点就找不到地方放了（</p>

<p><img src="../assets/images/vacation-2025-1/d5.jpeg" alt="" /></p>

<p>里面的 CD 就是上面「零零散散」第一张图里面下面的那个有 ChiliChill 标的，不拿出来单独拍了</p>

<h2 id="你的灵魂长出一枝玫瑰">《你的灵魂长出一枝玫瑰》</h2>

<p>这个也是眼熟支持一下（总之草民觉得在认真创作的基本都会支持</p>

<p><img src="../assets/images/vacation-2025-1/d6.jpeg" alt="" /></p>

<p>顺便抢了复刻的一专《我从人间走过》（上面「零零散散」里面也晒了），又达成全收集啦</p>

<h2 id="白蛇青蛇ost">《白蛇·青蛇》OST</h2>

<p>最后一个本系列的周边啦（爱过）。手速很快抢到了签名！</p>

<p><img src="../assets/images/vacation-2025-1/d7.jpeg" alt="" /></p>

<p>然后也期待一下追光的新作（刚好也是 7.12 上，来得及的话也许会在🐰的 Live 结束之后在酒店附近找个电影院看完，来不及就回来看</p>

<h2 id="next">Next</h2>

<p>下半年预计：</p>

<ul>
  <li>7.12 兰音 Reine 上海场（没抢到 SVIP 好难受，而且成本着实爆炸</li>
  <li>8.23 黄黄成都场（这个倒是很顺利的抢到了，难道真的是遇到纸片人就会不顺利？</li>
  <li>应该会有：四姑娘山 / 张家界（也可能两个都去）</li>
  <li>暂时没定：玄觞 / 叶里的 Live
    <ul>
      <li>尤其是玄觞这个很关键（但是完全不知道杭州那一场之后怎么个安排
        <ul>
          <li>为此还 hold 了 8.9 囧菌那一场（如果时间不冲突的话可能也买个普票去？目前看来应该是问题不大</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>然后路上的碟还有：</p>

<ul>
  <li>《跨越数千昼夜》（预计五月发货，现在都七月了</li>
  <li>《天兰夜》复刻（买了预售，预计可能八月底或者九月初收到吧</li>
  <li>《织梦令》（运气还可以，抢到了有签名的限量版，预计也是八月初收到</li>
</ul>

<p>下一篇应该是 Shells and Terminals 吧，Flag 立的是国庆之前……感觉有点悬呢。最后放一个近期精神状态《撞地球》（</p>

<iframe src="//player.bilibili.com/player.html?isOutside=true&amp;aid=114278694656288&amp;bvid=BV1rbZdYCEc7&amp;cid=29229910196&amp;p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height="600px" width="100%"></iframe>]]></content><author><name>yichya</name></author><category term="Play Around" /><category term="life" /><summary type="html"><![CDATA[本来 Vacation 2025.1 应该在五月更新的，但刚好 Gadgets 2025 卡在五月了于是往后挪一挪。上半年跑的地方还挺多的，频率也跟去年一样一个月最少一次。而且目前上半年四场 Live，下半年目前确定的都至少两场了，说不定还有两次整大活的计划，好像浪的有点狠了啊（]]></summary></entry><entry><title type="html">Start Headless Chromium on Demand</title><link href="https://www.yichya.dev/start-headless-chromium-on-demand/" rel="alternate" type="text/html" title="Start Headless Chromium on Demand" /><published>2025-06-29T00:00:00+08:00</published><updated>2025-06-29T00:00:00+08:00</updated><id>https://www.yichya.dev/start-headless-chromium-on-demand</id><content type="html" xml:base="https://www.yichya.dev/start-headless-chromium-on-demand/"><![CDATA[<p>RSSHub 需要一个 Headless Chromium 来应对 B 站的反爬措施。去年的做法是用 systemd 直接拉起来一个，并且当它被 RSSHub 主动关掉之后自动重启，用起来倒是也没啥问题，资源占用也几乎可以忽略不计，但每次 <code class="language-plaintext highlighter-rouge">ps auxf</code> 看见一大堆 Chromium 都感觉很扎眼。</p>

<p>之前也研究过 RSSHub 推荐的几种方式，但都感觉不是很好：</p>

<ul>
  <li>RSSHub 自己的容器里面直接带一个 Chromium，按需启动
    <ul>
      <li>问题主要是在草民这里一直都很不好使，B 站反爬用这玩意儿就没有一次能成功的，自己起 Headless Chromium 就非常的顺利</li>
    </ul>
  </li>
  <li>另外有一个 <a href="https://hub.docker.com/r/browserless/chrome">browserless/chrome</a>，毒点很多
    <ul>
      <li>很重，镜像 1GB+，都不知道里面塞了一些什么鬼东西</li>
      <li>为了维持这东西的服务，要起一个 <code class="language-plaintext highlighter-rouge">conmon</code> 再起一个 <code class="language-plaintext highlighter-rouge">node</code>，加起来恐怕也不比一个常驻的 Headless Chromium 轻了</li>
    </ul>
  </li>
  <li>还有一个问题是这两个方式安装的 Chromium 更新也不是很方便，也不能提供给 Host OS 使用，浪费空间</li>
</ul>

<p>后面又在给 Ubuntu 改 SSH 端口的时候了解了 systemd 的 Socket Activation，于是想试试用这个来做一下按需启动</p>

<h1 id="socket-activation">Socket Activation</h1>

<p>这个特性实际上来自于历史非常久远的 <code class="language-plaintext highlighter-rouge">inetd</code>，它负责专门跟网络交互并给后面的服务提供 fd 用来直接读写。不知道它是什么的话可以说它跟 CGI 有点类似，甚至一定程度上这俩是可以兼容的（毕竟都可以用 <code class="language-plaintext highlighter-rouge">stdio</code>，只是 CGI 多一步解析 HTTP Request 而已，可以通过判断 CGI 特有的一些环境变量来决定要不要从 <code class="language-plaintext highlighter-rouge">stdin</code> 里面读 HTTP Header）。Socket Activation 在特定的情况下还是有一些用处：</p>

<ul>
  <li>它能够使得服务不需要提权（root 或者 <code class="language-plaintext highlighter-rouge">CAP_NET_ADMIN</code>）就可以绑定 1024 以下的限制端口，对安全性更有好处
    <ul>
      <li>虽然现在一般都会起一个 nginx 之类的东西来做这件事，而且 nginx 能做的事情要多不少（比如 SSL、路由和负载均衡）</li>
    </ul>
  </li>
  <li>CGI 或者 inetd 的传统用法下每个连接都要拉一个进程起来，处理请求的代价会很高，而且稍微有点并发就寄了
    <ul>
      <li>systemd 倒是额外支持（而且推荐）服务自己 <code class="language-plaintext highlighter-rouge">accept</code> 的用法，性能可能也不至于太难看（没试过，不确定）</li>
      <li>对于不频繁使用的短任务（比如 ssh 这种，或者像是 Cockpit 这种相当现代化的服务也用了这样的方式来启动）来说就很合适
        <ul>
          <li>毕竟不用一直开着一个进程，用完了就可以干掉，下次再用的时候再拉起来就行，整体还是能省一些资源</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>至于具体的流程，说实话有点复杂，里面很多东西也不是很清晰，还需要服务主动做代码相关的适配。有一说一，这年头也基本上用不到这种技术，简单了解下流程就好，可以参考 <a href="https://mgdm.net/weblog/systemd-socket-activation/">https://mgdm.net/weblog/systemd-socket-activation/</a> 里面的示例。</p>

<h1 id="unsupported-applications">Unsupported Applications</h1>

<p>大部分现代服务都会主动 Listen 端口，而不是使用二三十年前的 inetd 或者 CGI 风格与网络交互。对于这些不支持传递 fd 的服务，可以利用 systemd 自带的 <code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code> 进行转发。当然也有一些需要关注的问题：</p>

<ul>
  <li>抢端口，也就是 systemd 和它拉起来的服务会 bind 到同一个地方
    <ul>
      <li>不建议 <code class="language-plaintext highlighter-rouge">ReusePort</code>，这东西的行为是负载均衡，会引入不确定性</li>
      <li>可以在本地回环上随便找个地方解决，或者绑定在一个别的设备上
        <ul>
          <li>但是如果设备初始化的比较晚的话，设置一下 <code class="language-plaintext highlighter-rouge">FreeBind</code></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>可能不知道拉起来的服务要多长时间才能开始 Listen，所以还要加个 Sleep 或者健康检查
    <ul>
      <li>其实草民觉得 <code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code> 就应该能做这个
        <ul>
          <li>但后来一想健康检查的逻辑可能很复杂，那还是 <code class="language-plaintext highlighter-rouge">ExecStartPre</code> 吧</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<h1 id="activator">Activator</h1>

<p>实际做一下。首先定义一个 Socket，名字就定为 <code class="language-plaintext highlighter-rouge">chromium_activator.socket</code>：</p>

<ul>
  <li>它会把同名服务拉起来（这个服务下面定义），systemd 会通过环境变量传递 fd 过去，拉起来的服务直接用传递过来的 fd 读写即可</li>
  <li>为了避免撞端口，把它 Listen 在 Podman 的网络上
    <ul>
      <li>这个设备启动会比较慢，所以设置上 <code class="language-plaintext highlighter-rouge">FreeBind</code></li>
      <li>虽然其实放在 Podman 的网络上并没有啥意义，因为 RSSHub 还是会用接口返回的完整 URL 去访问 Headless Chromium</li>
    </ul>
  </li>
</ul>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span>
<span class="py">Description</span><span class="p">=</span><span class="s">Chromium Activator</span>

<span class="nn">[Socket]</span>
<span class="py">ListenStream</span><span class="p">=</span><span class="s">172.17.0.1:9222</span>
<span class="py">Restart</span><span class="p">=</span><span class="s">Always</span>
<span class="py">FreeBind</span><span class="p">=</span><span class="s">true</span>

<span class="nn">[Install]</span>
<span class="py">WantedBy</span><span class="p">=</span><span class="s">sockets.target</span>
</code></pre></div></div>

<p>然后再定义一个由上面那个 Socket 激活的服务，名字就叫 <code class="language-plaintext highlighter-rouge">chromium_activator.service</code>：</p>

<ul>
  <li>让它把 Chromium 拉起来</li>
  <li>拉起来之后用 curl 做健康检查</li>
  <li>健康检查没问题之后启动 <code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code> 代理</li>
  <li>顺便管理一下生命周期
    <ul>
      <li>给 <code class="language-plaintext highlighter-rouge">systemd-socket-proxy</code> 指定一个 40s 空闲就退出的参数</li>
      <li>RSSHub 会在 30s 后干掉它拉起来的 Chromium，这个是写在它的代码里面的（改不了，不用管</li>
      <li>为了避免一些意外情况，给这两个服务再指定一个 systemd 控制的最大运行时长，保证他们用完之后及时释放资源
        <ul>
          <li>Chromium 60s（下面改），<code class="language-plaintext highlighter-rouge">systemd-socket-proxy</code> 50s</li>
          <li>这两个时间要比上面的两个要长，这样 systemd 拿到的服务退出状态才是正常的</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span>
<span class="py">Requires</span><span class="p">=</span><span class="s">chromium.service</span>
<span class="py">After</span><span class="p">=</span><span class="s">chromium.service</span>
<span class="py">Requires</span><span class="p">=</span><span class="s">chromium_activator.socket</span>
<span class="py">After</span><span class="p">=</span><span class="s">chromium_activator.socket</span>
<span class="py">JoinsNamespaceOf</span><span class="p">=</span><span class="s">chromium.service</span>

<span class="nn">[Service]</span>
<span class="py">ExecStartPre</span><span class="p">=</span><span class="s">curl --retry 5 --retry-connrefused --retry-delay 1 http://127.0.0.1:9222/json/version</span>
<span class="py">ExecStart</span><span class="p">=</span><span class="s">/lib/systemd/systemd-socket-proxyd 127.0.0.1:9222 --exit-idle-time 40s</span>
<span class="py">RuntimeMaxSec</span><span class="p">=</span><span class="s">50s</span>

<span class="nn">[Install]</span>
<span class="py">WantedBy</span><span class="p">=</span><span class="s">multi-user.target</span>
</code></pre></div></div>

<p>Headless Chromium 的服务还用之前 <a href="../self-hosted-1">Self Hosted (1)</a> 里面的那个，但做一点小调整：</p>

<ul>
  <li>Disable 掉避免它自动启动</li>
  <li>按上面说的控制一下生命周期
    <ul>
      <li>把 <code class="language-plaintext highlighter-rouge">Restart=Always</code> 去掉</li>
      <li>增加 <code class="language-plaintext highlighter-rouge">RuntimeMaxSec=60s</code> 保证它用完后自动退出</li>
    </ul>
  </li>
</ul>

<p>最后 <code class="language-plaintext highlighter-rouge">sudo systemctl enable chromium_activator.socket</code> 让它自动启动就行了。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># curl 172.17.0.1:9222/json/version</span>
<span class="o">{</span>
   <span class="s2">"Browser"</span>: <span class="s2">"Chrome/138.0.7204.49"</span>,
   <span class="s2">"Protocol-Version"</span>: <span class="s2">"1.3"</span>,
   <span class="s2">"User-Agent"</span>: <span class="s2">"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36"</span>,
   <span class="s2">"V8-Version"</span>: <span class="s2">"13.8.258.19"</span>,
   <span class="s2">"WebKit-Version"</span>: <span class="s2">"537.36 (@d2b48fd5f7813ed477a2d68fa232b8178fa4fb1e)"</span>,
   <span class="s2">"webSocketDebuggerUrl"</span>: <span class="s2">"ws://172.17.0.1:9222/devtools/browser/7299ef75-0a2d-4600-bfbf-1d8f642ed028"</span>
<span class="o">}</span>
</code></pre></div></div>

<h1 id="simplify-and-monitoring">Simplify and Monitoring</h1>

<p>既然引入了 curl 做健康检查，其实也可以考虑不使用 <code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code>，不过有几点考虑：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code> 是 systemd 自带，无需额外安装，不产生额外磁盘空间占用</li>
  <li><code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code> 的生命周期短于被一起拉起来的 Chromium，不产生长期资源占用</li>
  <li>直接使用 CGI 风格返回 curl 的结果可能会导致 RSSHub 启动的时候拉起来一堆 curl，不如只起一个 <code class="language-plaintext highlighter-rouge">systemd-socket-proxyd</code> 高效</li>
</ul>

<p>另外一个可以简化的点是：如果考虑丢掉 curl，或许可以利用 nginx 来处理重试</p>

<ul>
  <li>顺便可以将 Chromium 的启动次数记录到 Prometheus 中（虽然好像也没啥用</li>
  <li>研究了一下，nginx 竟然没有很直接的方式来做针对 502 的直接重试，只能通过错误处理之类的方式绕一圈，真是令人费解，不搞了</li>
</ul>

<p>至于这么做了有啥用，实话说基本没有，基本上就只有 CPU 占用少了 1% 左右。但是观察日志，Headless Chromium 可能每 15 分钟到一个小时才会被启动一次，绝大多数情况下 <code class="language-plaintext highlighter-rouge">ps auxf</code> 都不会再看到 Chromium 相关的进程，确实要顺眼很多。</p>

<h1 id="end">End</h1>

<p>更新于 7.6 的一点题外话：开始写这篇的时候 B 站似乎刚好在做一些反爬相关的升级</p>

<ul>
  <li>最早是从 6.26 某次重启 NAS 后发现 RSSHub 的健康检查一直过不去，升级 RSSHub 也没用
    <ul>
      <li>后来抓了一下包，小改一下代码解决，但开始出现偶尔被风控拦截的情况，不过情况还算可以接受</li>
    </ul>
  </li>
  <li>7.1 15:30 错误率突然开始显著上升，健康检查连十分钟都扛不过去了，纯纯是吊着一口气那种状态
    <ul>
      <li>但是非常神奇的是，21:30 左右它竟然又完全恢复到了 6.26 之前那种一个报错都没有的状态</li>
    </ul>
  </li>
</ul>

<p>然后观察了几天，截止到 7.6 都再也没出现过一次报错，如下图。甚至从上面 Socket Activation 的日志来看连刷新 Cookie 都没操作过</p>

<p><img src="../assets/images/start-headless-chromium-on-demand/vts.png" alt="" /></p>

<p>7.11 更新：7.8 号晚上 21:30 又出现了一次跟上面提到的相似的情况，但只持续了 45 分钟左右。算了不管了再炸再说（</p>

<p>虽然不知道 B 站是在搞什么活，但反正最后一次获取到的 Cookie 留下来了，如果下次重启再炸就把它填回去。顺便其实这部分是发 Vacation 2025.1 的时候一起补上的，所以就不再这里写下期预告了（</p>]]></content><author><name>yichya</name></author><category term="NAS and OpenWrt" /><category term="Technology" /><category term="nas" /><summary type="html"><![CDATA[RSSHub 需要一个 Headless Chromium 来应对 B 站的反爬措施。去年的做法是用 systemd 直接拉起来一个，并且当它被 RSSHub 主动关掉之后自动重启，用起来倒是也没啥问题，资源占用也几乎可以忽略不计，但每次 ps auxf 看见一大堆 Chromium 都感觉很扎眼。]]></summary></entry><entry><title type="html">Tinc Network and Observability Improvements</title><link href="https://www.yichya.dev/tinc-improvements/" rel="alternate" type="text/html" title="Tinc Network and Observability Improvements" /><published>2025-06-15T00:00:00+08:00</published><updated>2025-06-15T00:00:00+08:00</updated><id>https://www.yichya.dev/tinc-improvements</id><content type="html" xml:base="https://www.yichya.dev/tinc-improvements/"><![CDATA[<p><a href="/real-nas-project-4/">Real NAS Project 4</a> 里面提到过一个 Tinc 路径的优化，当时留了一个坑：对 Tinc 自身状态的观测没有找到很好的办法。另外即便是在内网通过 RDP 访问 Volterra（Windows Dev Kit 2023）有的时候会很卡，一旦出现就非常影响使用。所以这次来填一下这两个坑。</p>

<h1 id="why-tinc">Why Tinc</h1>

<p>常见的组网方案多少都会有点问题：</p>

<ul>
  <li>Wireguard / OpenVPN 特征明显、用的人太多，很容易被针对</li>
  <li>Tailscale 边缘节点都很重，而且依赖 Wireguard，也有被针对的问题</li>
  <li>Zerotier 虽然轻量一些，但是自建很麻烦，要部署好几种不同的节点，而且目前也会被针对</li>
</ul>

<p>Tinc 作为相对小众的方案至少到目前为止都还没有被针对，而且也比较轻量，自建很容易（所有节点之间基本上是对等的），但是也有一些问题比如节点管理比较麻烦、可观测性差、NAT 打洞成功率相对偏低等等，不过目前草民用起来还算顺手</p>

<h1 id="observatory-from-firewall">Observatory from Firewall</h1>

<p>之前留下的坑简单说就是：需要找到办法确认 Tinc 节点之间的传输方式（直连 / 中转）以及使用的协议（TCP / UDP）。Tinc 自身提供的观测手段非常有限，基本上只有发信号读日志以及一个简单的 Graph Dump，没什么用。好在还可以从流量特征上观察</p>

<p>草民在 OpenWrt 上跑 Tinc，为了绕过 Xray 透明代理，将 Tinc 设置为使用 <code class="language-plaintext highlighter-rouge">network</code> 组（gid 101）启动，但为了操作 <code class="language-plaintext highlighter-rouge">tun</code> 设备，用户仍然使用 <code class="language-plaintext highlighter-rouge">root</code>。OpenWrt 上没有其他的服务使用这样的方式运行，因此可以在 nftables 上通过 <code class="language-plaintext highlighter-rouge">skuid</code> + <code class="language-plaintext highlighter-rouge">skgid</code> 过滤出 Tinc 相关的出站方向流量；至于入站方向，简单按照 <code class="language-plaintext highlighter-rouge">tcp dport 655</code> 和 <code class="language-plaintext highlighter-rouge">udp dport 655</code> 筛一下就行了。基于这样的约定，可以写出以下 nftables 规则将所有跟 Tinc 交换过数据的 IP + 端口记录在几个特定的 Set 中，放在 <code class="language-plaintext highlighter-rouge">/usr/share/nftables.d/table-pre/tinc.nft</code> 即可：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set tinc_inbound_v4 {
    type ipv4_addr . inet_service . inet_proto
    flags dynamic, timeout
}

set tinc_inbound_v6 {
    type ipv6_addr . inet_service . inet_proto 
    flags dynamic, timeout
}

set tinc_outbound_v4 {
    type ipv4_addr . inet_service . inet_proto
    flags dynamic, timeout
}

set tinc_outbound_v6 {
    type ipv6_addr . inet_service . inet_proto
    flags dynamic, timeout
}

chain tinc_inbound {
    type filter hook input priority mangle +5; policy accept;
    ip protocol udp udp dport 655 counter update @tinc_inbound_v4 {ip saddr . udp sport . ip protocol timeout 60s counter} return;
    ip6 nexthdr udp udp dport 655 counter update @tinc_inbound_v6 {ip6 saddr . udp sport . ip6 nexthdr timeout 60s counter} return;
    ip protocol tcp tcp dport 655 counter update @tinc_inbound_v4 {ip saddr . tcp sport . ip protocol timeout 60s counter} return;
    ip6 nexthdr tcp tcp dport 655 counter update @tinc_inbound_v6 {ip6 saddr . tcp sport . ip6 nexthdr timeout 60s counter} return;
}

chain tinc_outbound {
    type filter hook output priority mangle +5; policy accept;
    ip protocol udp meta skuid 0 meta skgid 101 counter update @tinc_outbound_v4 {ip daddr . udp dport . ip protocol timeout 60s counter} return;
    ip6 nexthdr udp meta skuid 0 meta skgid 101 counter update @tinc_outbound_v6 {ip6 daddr . udp dport . ip6 nexthdr timeout 60s counter} return;
    ip protocol tcp meta skuid 0 meta skgid 101 counter update @tinc_outbound_v4 {ip daddr . tcp dport . ip protocol timeout 60s counter} return;
    ip6 nexthdr tcp meta skuid 0 meta skgid 101 counter update @tinc_outbound_v6 {ip6 daddr . tcp dport . ip6 nexthdr timeout 60s counter} return;
}
</code></pre></div></div>

<p>重启 <code class="language-plaintext highlighter-rouge">fw4</code> 加载一下规则，然后就可以用 <code class="language-plaintext highlighter-rouge">nft list sets</code> 或者 LuCI 的 <code class="language-plaintext highlighter-rouge">fw4</code> 页面观察流量（但 LuCI 目前还不能显示 Set，只能看到 Chain 上每一个 Rule 对应的 Counter）。再写一个脚本转换一下格式导入 Prometheus，简单起见一样用 CGI 来做。把下面的内容扔到 <code class="language-plaintext highlighter-rouge">/www/cgi-bin/tinc.counter</code> 并 <code class="language-plaintext highlighter-rouge">chmod +x</code>，然后就可以直接通过 <code class="language-plaintext highlighter-rouge">uhttpd</code> 访问：</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#!/usr/bin/ucode
</span><span class="k">import</span> <span class="p">{</span> <span class="nx">popen</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">nft</span> <span class="o">=</span> <span class="nx">popen</span><span class="p">(</span><span class="dl">'</span><span class="s1">nft --json list sets</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">counters</span> <span class="o">=</span> <span class="nx">nft</span><span class="p">.</span><span class="nx">read</span><span class="p">(</span><span class="dl">"</span><span class="s2">all</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">nft</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>

<span class="nx">print</span><span class="p">(</span><span class="dl">"</span><span class="s2">Content-Type: text/plain; version=0.0.4; charset=utf-8; escaping=values</span><span class="se">\r\n</span><span class="s2">Connection: close</span><span class="se">\r\n\r\n</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">json</span><span class="p">(</span><span class="nx">counters</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">a</span> <span class="k">in</span> <span class="nx">value</span><span class="p">[</span><span class="dl">"</span><span class="s2">nftables</span><span class="dl">"</span><span class="p">])</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="kd">set</span> <span class="o">&amp;&amp;</span> <span class="nx">index</span><span class="p">([</span><span class="dl">"</span><span class="s2">tinc_inbound_v4</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">tinc_inbound_v6</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">tinc_outbound_v4</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">tinc_outbound_v6</span><span class="dl">"</span><span class="p">],</span> <span class="nx">a</span><span class="p">.</span><span class="kd">set</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">b</span> <span class="k">in</span> <span class="nx">a</span><span class="p">.</span><span class="kd">set</span><span class="p">.</span><span class="nx">elem</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">print</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="kd">set</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="dl">'</span><span class="s1">_bytes{address="</span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">val</span><span class="p">.</span><span class="nx">concat</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="dl">'</span><span class="s1">", port="</span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">val</span><span class="p">.</span><span class="nx">concat</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="dl">'</span><span class="s1">", protocol="</span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">val</span><span class="p">.</span><span class="nx">concat</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="dl">'</span><span class="s1">"} </span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">counter</span><span class="p">.</span><span class="nx">bytes</span><span class="p">,</span> <span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">);</span>
            <span class="nx">print</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="kd">set</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="dl">'</span><span class="s1">_packets{address="</span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">val</span><span class="p">.</span><span class="nx">concat</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="dl">'</span><span class="s1">", port="</span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">val</span><span class="p">.</span><span class="nx">concat</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="dl">'</span><span class="s1">", protocol="</span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">val</span><span class="p">.</span><span class="nx">concat</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="dl">'</span><span class="s1">"} </span><span class="dl">'</span><span class="p">,</span> <span class="nx">b</span><span class="p">.</span><span class="nx">elem</span><span class="p">.</span><span class="nx">counter</span><span class="p">.</span><span class="nx">packets</span><span class="p">,</span> <span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>配置好 Prometheus 的 Scrape 之后得到下图。观察出入流量方向，都有的 IP 肯定就是直连（图上按 IP 聚合了所以没有显示端口）。这个 nftables 规则针对 TCP 的部分写的应该还是有点问题的（可能会少统计一部分包），虽然应该也基本不影响使用。观测问题初步解决</p>

<p><img src="../assets/images/tinc-improvements/grafana_direct.png" alt="" /></p>

<p>顺便，因为这个抓取的成本很低，它甚至可以一定程度上代替之前的 Ping 直接用来观测延迟。从图上可以看出来两条线的趋势在几次网络切换的地方是可以吻合的，但 Prometheus 抓取会在其他很多地方出现一些随机波动</p>

<p><img src="../assets/images/tinc-improvements/ping.png" alt="" /></p>

<p>初步猜测是 Prometheus 抓取到的数据大概率还是会超出 MTU 导致 IP 层拆包，结果体现为抓取耗时不如 Ping 稳定。可以尝试简单补一个 gzip 来避免超过 MTU 导致的 IP 层拆包，抓下一个指标的时候介绍一下怎么做，也顺便观察一下效果</p>

<h1 id="volterra-network-debug">Volterra Network Debug</h1>

<p>然后开始解决 Volterra 的 RDP 有的时候非常卡的问题。利用上面的手段观察了流量特征，发现 RDP 很卡的时候 Tinc 的传输都是 TCP，而 RDP 不卡的时候 Tinc 的传输主要是 UDP（注意这里说的是 Tinc 使用的协议，而不是 RDP 自身选用的协议）。按说内网环境 TCP 跟 UDP 也不应该差到有如此明显的感知，不过考虑到 Volterra 放的位置无线信号不是特别好，也不完全能排除这种可能性</p>

<p>首先确认一件事就是 Tinc 在什么情况下会使用 UDP，从日志来看如果两个 Node 之间能建立直接连接（包括 NAT 打洞成功），并且探测出来的 PMTU 最小值比本次传输的包经过 MSS Clamping 之后要大，就可以使用 UDP 进行传输</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2025-06-15 01:03:24 tinc[2252]: Packet for openwrt (10.32.15.1 port 655) larger than minimum MTU, forwarding via TCP
</code></pre></div></div>

<p>简单观察了一段时间的日志，能直连但是不走 UDP 的情况下 PMTU 最小值基本都是 0，于是有两个事情可以做：</p>

<ul>
  <li>对 PMTU 进行观测，找到规律并做针对性调整，使得其能尽量保持在正常水平（可能比较困难</li>
  <li>确认一下为什么内网 TCP 也会导致 RDP 非常卡，是信号太差导致丢包还是什么别的原因。这个事情明显不对劲，下面来优先解决</li>
</ul>

<h2 id="iperf3">iPerf3</h2>

<p>先跑个 iPerf3 看看是不是底下真实网络的问题。果然，从 Volterra 上直接出去（主要是从 OpenWrt 直接出去）的话 Retransmission 严重</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@OpenWrt:~# iperf3 -c 192.168.1.5
Connecting to host 192.168.1.5, port 5201
[  5] local 10.0.0.4 port 32924 connected to 192.168.1.5 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   128 KBytes  1.05 Mbits/sec   35   4.24 KBytes       
[  5]   1.00-2.00   sec   128 KBytes  1.05 Mbits/sec   24   2.83 KBytes       
[  5]   2.00-3.00   sec   256 KBytes  2.10 Mbits/sec   34   2.83 KBytes       
[  5]   3.00-4.00   sec   256 KBytes  2.10 Mbits/sec   30   2.83 KBytes       
[  5]   4.00-5.00   sec   128 KBytes  1.05 Mbits/sec   34   2.83 KBytes       
[  5]   5.00-6.00   sec   256 KBytes  2.10 Mbits/sec   44   2.83 KBytes       
[  5]   6.00-7.00   sec   128 KBytes  1.05 Mbits/sec   28   2.83 KBytes       
[  5]   7.00-8.00   sec   384 KBytes  3.14 Mbits/sec   36   5.66 KBytes       
[  5]   8.00-9.00   sec   128 KBytes  1.05 Mbits/sec   28   5.66 KBytes       
[  5]   9.00-10.00  sec   128 KBytes  1.05 Mbits/sec   32   2.83 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  1.88 MBytes  1.57 Mbits/sec  325            sender
[  5]   0.00-10.01  sec  1.88 MBytes  1.57 Mbits/sec                  receiver

iperf Done.
</code></pre></div></div>

<p>换了几种方式跑，入站无问题，出站过一次 NAT 之后也没问题；起初怀疑是 OpenWrt 的问题于是顺手起了个 FreeBSD，但表现一样</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/tinc-improvements/freebsd1.png" alt="" /></th>
      <th><img src="../assets/images/tinc-improvements/freebsd2.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>经过 NAT 无 Retransmission</td>
      <td>不走 NAT 有 Retransmission</td>
    </tr>
  </tbody>
</table>

<p>那就只能靠抓包来分析一下为什么会出现如此严重的 Retransmission 了。</p>

<h2 id="hierarchy">Hierarchy</h2>

<p>抓包肯定要先找个设备，那么首先来看看 Volterra 的网络是怎么搭起来的，也就是下面这张图里面都是些什么鬼东西</p>

<p><img src="../assets/images/tinc-improvements/devices.png" alt="" /></p>

<p>上面有三个未连接 / 网络电缆被拔出的用不到，关注剩下的几个已启用 / 已连接的：</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">WLAN</code>：真实的无线网卡，但是只接在桥上，不直接在上面绑定 TCP/IP</li>
  <li><code class="language-plaintext highlighter-rouge">网桥</code>：Hyper-V 创建的，只有上面那个 WLAN 显示接在上面（但实际上还有别的）</li>
  <li><code class="language-plaintext highlighter-rouge">vEthernet (Default Switch)</code>：默认的 NAT 网络。客户端版本 Windows 自带的 Hyper-V 会有这个，但只能说不怎么好用</li>
  <li><code class="language-plaintext highlighter-rouge">vEthernet (OpenWrt)</code>：OpenWrt 的 LAN，暴露给 Host OS 做默认网关使用</li>
  <li><code class="language-plaintext highlighter-rouge">vEthernet (Wireless)</code>：Hyper-V 桥上的虚拟网卡，「允许管理操作系统共享此网络适配器」后就会有这个 Host OS 可用的设备</li>
</ul>

<p>说起来这还是头一次在 Hyper-V 环境里面真的见到一个桥，印象中以往都没这玩意儿。比如 R86S 上就是左图这样，物理设备的样子不变（虽然 TCP/IP 也不跑在它上面了，跟上面一样会有一个「允许管理操作系统共享此网络适配器」之后才可见的 vEthernet 设备给 Host OS</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/tinc-improvements/bridge1.png" alt="" /></th>
      <th><img src="../assets/images/tinc-improvements/bridge2.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>之前的桥（Windows Server 2022，不过是个有线网卡）</td>
      <td>这次的桥（Windows 11）</td>
    </tr>
  </tbody>
</table>

<p>但这次见到的这个桥（右边）就很奇怪，不仅有一个真的桥，而且 TCP/IP 还没有跑在上面，非得再拉出一个 <code class="language-plaintext highlighter-rouge">vEthernet (Wireless)</code> 用来跑 TCP/IP，总之就是十分诡异。后来在 Corsair One 上又试了一下，似乎无线设备都是这样，估计是驱动模型不一样吧</p>

<h2 id="wireshark">Wireshark</h2>

<p>从 OpenWrt 直接出去的包是经过上面那个奇怪的 <code class="language-plaintext highlighter-rouge">网桥</code> 再从桥上的 <code class="language-plaintext highlighter-rouge">WLAN</code> 出去。既然在 Windows 上跑 Wireshark，那就从 <code class="language-plaintext highlighter-rouge">网桥</code> 开始抓好了。结果一抓一个准，从结果里面发现大量的出包方向上的 TCP CHECKSUM INCORRECT 以及随之而来的更多的 TCP Retransmission</p>

<p><img src="../assets/images/tinc-improvements/wireshark.png" alt="" /></p>

<p>Wireshark 提示 Checksum 不对可能是 TCP checksum offload 导致（而且错误的 Checksum 几乎都一样）。虽然直接从 OpenWrt 上抓也会看到类似的情况，但是当时 Wireshark 的提示是部分正确，而且有一说一，OpenWrt 的 <a href="https://docs.kernel.org/networking/segmentation-offloads.html">GSO 和 GRO</a> 有段时间几乎是天天出问题，这次如果又是这玩意儿炸了的话还真是一点都不意外（虽然上面刚说了 FreeBSD 也一样炸啊</p>

<p>总之既然 Wireshark 说可能是 TCP checksum offload 的锅，那就先尝试关掉吧。Windows 上一开始没找到怎么搞，先从 OpenWrt 上试试</p>

<h2 id="disabling-offload">Disabling Offload</h2>

<p>先看看 OpenWrt 上面 WAN 设备都开了些什么 Offload</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@OpenWrt:~# ethtool --show-offload eth1
Features for eth1:
rx-checksumming: on
tx-checksumming: on
	tx-checksum-ipv4: on
	tx-checksum-ip-generic: off [fixed]
	tx-checksum-ipv6: on
	tx-checksum-fcoe-crc: off [fixed]
	tx-checksum-sctp: off [fixed]
scatter-gather: on
	tx-scatter-gather: on
	tx-scatter-gather-fraglist: off [fixed]
tcp-segmentation-offload: on
	tx-tcp-segmentation: on
	tx-tcp-ecn-segmentation: off [fixed]
	tx-tcp-mangleid-segmentation: off
	tx-tcp6-segmentation: on
generic-segmentation-offload: on
generic-receive-offload: on

&lt;omitted...&gt;
</code></pre></div></div>

<p>里面有 <code class="language-plaintext highlighter-rouge">tcp-segmentation-offload</code>、<code class="language-plaintext highlighter-rouge">generic-segmentation-offload</code>、<code class="language-plaintext highlighter-rouge">generic-receive-offload</code>，于是打算挨个试试看，结果试到第一个 <code class="language-plaintext highlighter-rouge">ethtool -K eth1 tso off</code> 问题立刻就消失了。。。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@OpenWrt:~# iperf3 -c 192.168.1.5
Connecting to host 192.168.1.5, port 5201
[  5] local 10.0.0.5 port 47166 connected to 192.168.1.5 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  6.38 MBytes  53.4 Mbits/sec    0    385 KBytes       
[  5]   1.00-2.00   sec  5.25 MBytes  44.0 Mbits/sec    0    601 KBytes       
[  5]   2.00-3.00   sec  4.50 MBytes  37.7 Mbits/sec    1    488 KBytes       
[  5]   3.00-4.00   sec  5.12 MBytes  43.0 Mbits/sec    0    544 KBytes       
[  5]   4.00-5.00   sec  3.75 MBytes  31.5 Mbits/sec    0    581 KBytes       
[  5]   5.00-6.00   sec  5.00 MBytes  41.9 Mbits/sec    0    607 KBytes       
[  5]   6.00-7.00   sec  6.38 MBytes  53.5 Mbits/sec    0    621 KBytes       
[  5]   7.00-8.00   sec  5.12 MBytes  43.0 Mbits/sec    0    626 KBytes       
[  5]   8.00-9.00   sec  5.12 MBytes  43.0 Mbits/sec    0    626 KBytes       
[  5]   9.00-10.00  sec  5.12 MBytes  43.0 Mbits/sec    0    626 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  51.8 MBytes  43.4 Mbits/sec    1            sender
[  5]   0.00-10.04  sec  49.2 MBytes  41.2 Mbits/sec                  receiver

iperf Done.
</code></pre></div></div>

<p>当然上面也提到这个问题显然不是 OpenWrt 自己的，所以还是要尝试在 Windows 里面关掉。因为抓包是在 <code class="language-plaintext highlighter-rouge">网桥</code> 上发现的问题，所以就优先找了一下对应的设置，在设备管理器里面。里面也有 TCP Checksum Offload，但是关掉没有用；关掉了跟 <code class="language-plaintext highlighter-rouge">tcp-segmentation-offload</code> 差不多的 <a href="https://en.wikipedia.org/wiki/TCP_offload_engine#Large_send_offload">Large Send Offload</a> 之后问题得到解决。</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/tinc-improvements/offload.png" alt="" /></th>
      <th><img src="../assets/images/tinc-improvements/bridge.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>英文（仅用来做对照）</td>
      <td>中文（出问题的网桥）</td>
    </tr>
  </tbody>
</table>

<p>Google 上一搜有挺多相关结果都说这个东西会严重影响上传速度，只能评价为微软的咖喱味儿真的是越来越浓了</p>

<h1 id="graph-dump-with-statistics">Graph Dump with Statistics</h1>

<p>解决了网络问题之后来看看怎样获取 PMTU 以便确定 Tinc 的传输状态。Tinc 自身有一个 Graph Dump 可以提供 Node 之间的连接关系，但问题在于只有连接关系，更多的细节比如每个节点的 PMTU 和每条边的权重都没有。好在 Tinc 这部分代码比较简单，稍微改一下就可以在 dump 中返回 PMTU 和 Weight 的值。另外，Tinc 的图更新有的时候不能传播到所有节点，所以再进行一点简单修改，在接到 ALRM 信号的时候触发一次主动更新 PMTU（以下代码修改自 Tinc 1.0.36）：</p>

<div class="language-patch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/src/graph.c b/src/graph.c
index c63fdf9c..cb57f0da 100644
</span><span class="gd">--- a/src/graph.c
</span><span class="gi">+++ b/src/graph.c
</span><span class="p">@@ -326,7 +326,7 @@</span> void graph(void) {
    dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
 */
 
<span class="gd">-void dump_graph(void) {
</span><span class="gi">+void dump_graph(time_t last_ping_check) {
</span> 	avl_node_t *node;
 	node_t *n;
 	edge_t *e;
<span class="p">@@ -355,18 +355,18 @@</span> void dump_graph(void) {
 		return;
 	}
 
<span class="gd">-	fprintf(file, "digraph {\n");
</span><span class="gi">+	fprintf(file, "digraph {\n	comment = \"%ld\";\n", last_ping_check);
</span> 
 	/* dump all nodes first */
 	for(node = node_tree-&gt;head; node; node = node-&gt;next) {
 		n = node-&gt;data;
<span class="gd">-		fprintf(file, "	\"%s\" [label = \"%s\"];\n", n-&gt;name, n-&gt;name);
</span><span class="gi">+		fprintf(file, "	\"%s\" [label = \"%s\", comment = \"%d %d %d\"];\n", n-&gt;name, n-&gt;name, n-&gt;mtu, n-&gt;minmtu, n-&gt;maxmtu);
</span> 	}
 
 	/* now dump all edges */
 	for(node = edge_weight_tree-&gt;head; node; node = node-&gt;next) {
 		e = node-&gt;data;
<span class="gd">-		fprintf(file, "	\"%s\" -&gt; \"%s\";\n", e-&gt;from-&gt;name, e-&gt;to-&gt;name);
</span><span class="gi">+		fprintf(file, "	\"%s\" -&gt; \"%s\" [comment = \"%d\"];\n", e-&gt;from-&gt;name, e-&gt;to-&gt;name, e-&gt;weight);
</span> 	}
 
 	fprintf(file, "}\n");
<span class="gh">diff --git a/src/graph.h b/src/graph.h
index fafffcb0..60bab17d 100644
</span><span class="gd">--- a/src/graph.h
</span><span class="gi">+++ b/src/graph.h
</span><span class="p">@@ -22,6 +22,6 @@</span>
 */
 
 extern void graph(void);
<span class="gd">-extern void dump_graph(void);
</span><span class="gi">+extern void dump_graph(time_t last_ping_check);
</span> 
 #endif
<span class="gh">diff --git a/src/net.c b/src/net.c
index 37ae1166..e3e673e0 100644
</span><span class="gd">--- a/src/net.c
</span><span class="gi">+++ b/src/net.c
</span><span class="p">@@ -583,6 +583,8 @@</span> int main_loop(void) {
 				}
 			}
 
<span class="gi">+			graph();
+
</span> 			sigalrm = false;
 		}
 
<span class="p">@@ -697,7 +699,7 @@</span> int main_loop(void) {
 		/* Dump graph if wanted every 60 seconds*/
 
 		if(last_graph_dump + 60 &lt;= now) {
<span class="gd">-			dump_graph();
</span><span class="gi">+			dump_graph(last_ping_check);
</span> 			last_graph_dump = now;
 		}
 	}
</code></pre></div></div>

<p>获得的 dump 就可以包含节点的 PMTU（三个值，主要关注中间的最小值）和连接的 Weight，以及上次 Ping 的时间戳：</p>

<div class="language-dot highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">digraph</span> <span class="p">{</span>
	<span class="n">comment</span> <span class="p">=</span> <span class="s2">"1748675964"</span><span class="p">;</span>
	<span class="s2">"cloudcone"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"cloudcone"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1518 0 1518"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"gateway"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1518 0 1518"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"greencloud"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"greencloud"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1518 0 1518"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"justhost"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"justhost"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1518 0 1518"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"nas"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1408 1408 1408"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nuc"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"nuc"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1425 0 1425"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"openwrt"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"openwrt"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1518 0 1518"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"r86s"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1408 1408 1408"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"volterra"</span> <span class="o">[</span><span class="n">label</span> <span class="p">=</span> <span class="s2">"volterra"</span><span class="p">,</span> <span class="n">comment</span> <span class="p">=</span> <span class="s2">"1408 1408 1408"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">-&gt;</span> <span class="s2">"volterra"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"25"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"volterra"</span> <span class="o">-&gt;</span> <span class="s2">"r86s"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"25"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"openwrt"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"30"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"openwrt"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"30"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"openwrt"</span> <span class="o">-&gt;</span> <span class="s2">"volterra"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"42"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"volterra"</span> <span class="o">-&gt;</span> <span class="s2">"openwrt"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"42"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"nuc"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"46"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nuc"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"46"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"r86s"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"52"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"52"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"openwrt"</span> <span class="o">-&gt;</span> <span class="s2">"r86s"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"60"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">-&gt;</span> <span class="s2">"openwrt"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"60"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"r86s"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"187"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"187"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"479"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"479"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"cloudcone"</span> <span class="o">-&gt;</span> <span class="s2">"greencloud"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"692"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"greencloud"</span> <span class="o">-&gt;</span> <span class="s2">"cloudcone"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"692"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"greencloud"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"721"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"greencloud"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"721"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"cloudcone"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"730"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"cloudcone"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"730"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"cloudcone"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"770"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"cloudcone"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"770"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"justhost"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"791"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"justhost"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"791"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"greencloud"</span> <span class="o">-&gt;</span> <span class="s2">"justhost"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"860"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"justhost"</span> <span class="o">-&gt;</span> <span class="s2">"greencloud"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"860"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"cloudcone"</span> <span class="o">-&gt;</span> <span class="s2">"justhost"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"946"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"justhost"</span> <span class="o">-&gt;</span> <span class="s2">"cloudcone"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"946"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"justhost"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1112"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"justhost"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1112"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"cloudcone"</span> <span class="o">-&gt;</span> <span class="s2">"r86s"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1124"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">-&gt;</span> <span class="s2">"cloudcone"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1124"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"greencloud"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1184"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"greencloud"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1184"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"gateway"</span> <span class="o">-&gt;</span> <span class="s2">"volterra"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1302"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"volterra"</span> <span class="o">-&gt;</span> <span class="s2">"gateway"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"1302"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"nas"</span> <span class="o">-&gt;</span> <span class="s2">"volterra"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"2549"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"volterra"</span> <span class="o">-&gt;</span> <span class="s2">"nas"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"2549"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"justhost"</span> <span class="o">-&gt;</span> <span class="s2">"r86s"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"5522"</span><span class="o">]</span><span class="p">;</span>
	<span class="s2">"r86s"</span> <span class="o">-&gt;</span> <span class="s2">"justhost"</span> <span class="o">[</span><span class="n">comment</span> <span class="p">=</span> <span class="s2">"5522"</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>同样也简单转换成 Prometheus 格式用作收集，顺便也给 Tinc 发出主动更新信号。因为这个指标会大一点，所以我们在这里补一个 gzip</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#!/usr/bin/ucode
</span><span class="dl">"</span><span class="s2">use strict</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">readfile</span><span class="p">,</span> <span class="nx">lstat</span><span class="p">,</span> <span class="nx">popen</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">convert</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">lines</span> <span class="o">=</span> <span class="nx">split</span><span class="p">(</span><span class="nx">content</span><span class="p">,</span> <span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">line</span> <span class="k">in</span> <span class="nx">lines</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">splittedLine</span> <span class="o">=</span> <span class="nx">split</span><span class="p">(</span><span class="nx">line</span><span class="p">,</span> <span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">length</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">)</span> <span class="o">==</span> <span class="mi">9</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nx">replace</span><span class="p">(</span><span class="nx">trim</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">);</span>
            <span class="nx">w</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`tinc_node_pmtu{label="</span><span class="p">${</span><span class="nx">node</span><span class="p">}</span><span class="s2">"} </span><span class="p">${</span><span class="nx">replace</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">6</span><span class="p">],</span> <span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">)}</span><span class="s2">\n`</span><span class="p">);</span>
            <span class="nx">w</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`tinc_node_min_pmtu{label="</span><span class="p">${</span><span class="nx">node</span><span class="p">}</span><span class="s2">"} </span><span class="p">${</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">7</span><span class="p">]}</span><span class="s2">\n`</span><span class="p">);</span>
            <span class="nx">w</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`tinc_node_max_pmtu{label="</span><span class="p">${</span><span class="nx">node</span><span class="p">}</span><span class="s2">"} </span><span class="p">${</span><span class="nx">replace</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">8</span><span class="p">],</span> <span class="dl">'</span><span class="s1">"];</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">)}</span><span class="s2">\n`</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">length</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">)</span> <span class="o">==</span> <span class="mi">6</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">weight</span> <span class="o">=</span> <span class="nx">replace</span><span class="p">(</span><span class="nx">replace</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span> <span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">),</span> <span class="dl">'</span><span class="s1">];</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">);</span>
            <span class="nx">w</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`tinc_edge_weight{from="</span><span class="p">${</span><span class="nx">replace</span><span class="p">(</span><span class="nx">trim</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">)}</span><span class="s2">", to="</span><span class="p">${</span><span class="nx">replace</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">)}</span><span class="s2">"} </span><span class="p">${</span><span class="nx">weight</span><span class="p">}</span><span class="s2">\n`</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">length</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">updated</span> <span class="o">=</span> <span class="nx">time</span><span class="p">()</span> <span class="o">-</span> <span class="nx">int</span><span class="p">(</span><span class="nx">replace</span><span class="p">(</span><span class="nx">replace</span><span class="p">(</span><span class="nx">splittedLine</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="dl">'</span><span class="s1">;</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">),</span> <span class="dl">'</span><span class="s1">"</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">));</span>
            <span class="nx">w</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`tinc_ping_last_updated_seconds </span><span class="p">${</span><span class="nx">updated</span><span class="p">}</span><span class="s2">\n`</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nx">print</span><span class="p">(</span><span class="dl">"</span><span class="s2">Content-Type: text/plain; version=0.0.4; charset=utf-8; escaping=values</span><span class="se">\r\n</span><span class="s2">Content-Encoding: gzip</span><span class="se">\r\n</span><span class="s2">Connection: close</span><span class="se">\r\n\r\n</span><span class="dl">"</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">dumpFileName</span> <span class="o">=</span> <span class="nx">match</span><span class="p">(</span><span class="nx">readfile</span><span class="p">(</span><span class="dl">"</span><span class="s2">/etc/tinc/tinc.conf</span><span class="dl">"</span><span class="p">),</span> <span class="sr">/GraphDumpFile</span><span class="se">\s</span><span class="sr">*=</span><span class="se">\s</span><span class="sr">*</span><span class="se">([^\n]</span><span class="sr">*</span><span class="se">)</span><span class="sr">/</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">length</span><span class="p">(</span><span class="nx">dumpFileName</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">fileStat</span> <span class="o">=</span> <span class="nx">lstat</span><span class="p">(</span><span class="nx">dumpFileName</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">fileStat</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">updated</span> <span class="o">=</span> <span class="nx">time</span><span class="p">()</span> <span class="o">-</span> <span class="nx">fileStat</span><span class="p">.</span><span class="nx">mtime</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">gzip</span> <span class="o">=</span> <span class="nx">popen</span><span class="p">(</span><span class="dl">"</span><span class="s2">gzip -c</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">w</span><span class="dl">"</span><span class="p">);</span>
        <span class="nx">gzip</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s2">`tinc_file_last_updated_seconds </span><span class="p">${</span><span class="nx">updated</span><span class="p">}</span><span class="s2">\n`</span><span class="p">);</span>
        <span class="nx">convert</span><span class="p">(</span><span class="nx">gzip</span><span class="p">,</span> <span class="nx">readfile</span><span class="p">(</span><span class="nx">dumpFileName</span><span class="p">[</span><span class="mi">1</span><span class="p">]));</span>
        <span class="nx">gzip</span><span class="p">.</span><span class="nx">flush</span><span class="p">();</span>
        <span class="nx">gzip</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">updated</span> <span class="o">&gt;</span> <span class="mi">233</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">system</span><span class="p">(</span><span class="dl">"</span><span class="s2">killall -ALRM tincd</span><span class="dl">"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>用一点技巧把 min PMTU 和 Ping 延迟画在一张图上，可以说对应关系非常准确</p>

<p><img src="../assets/images/tinc-improvements/pmtu.png" alt="" /></p>

<p>另外也尝试观察了 Prometheus 的抓取耗时看看能不能取代 Ping 探测，结果是基本上无法区分，虽然加了 gzip 但是效果也有限，估计大概是因为完成一次 CGI 调用的成本太高（但确实写起来很省事儿），还是接着用 Ping 探测吧。</p>

<p><img src="../assets/images/tinc-improvements/gzip.png" alt="" /></p>

<p>PMTU 观测的问题就解决了，后面还是想看看有没有可能观察出规律，把 NAT 打洞搞再稳定一点，不过这事儿也不太是草民能控制的</p>

<h1 id="next">Next</h1>

<p>最近在 RSS Pipe 上按之前的想法加了一些杂七杂八的小功能。感觉用起来还不错，后面就可以考虑做一点额外的能力了</p>

<p>然后是七月初发 Vacation 2025.1（🐰的 SVIP 没抢到，难受了一个多星期；以及🐦的上海场在 8.2，跑一趟上海成本太高，应该也不会去了</p>]]></content><author><name>yichya</name></author><category term="NAS and OpenWrt" /><category term="Technology" /><category term="openwrt" /><category term="lede" /><category term="nas" /><category term="router" /><summary type="html"><![CDATA[Real NAS Project 4 里面提到过一个 Tinc 路径的优化，当时留了一个坑：对 Tinc 自身状态的观测没有找到很好的办法。另外即便是在内网通过 RDP 访问 Volterra（Windows Dev Kit 2023）有的时候会很卡，一旦出现就非常影响使用。所以这次来填一下这两个坑。]]></summary></entry><entry><title type="html">Gadgets (2025)</title><link href="https://www.yichya.dev/gadgets-2025/" rel="alternate" type="text/html" title="Gadgets (2025)" /><published>2025-05-31T00:00:00+08:00</published><updated>2025-05-31T00:00:00+08:00</updated><id>https://www.yichya.dev/gadgets-2025</id><content type="html" xml:base="https://www.yichya.dev/gadgets-2025/"><![CDATA[<p>今年端午比以往略早啊，刚好可以赶在五月最后一天发出来，六月的时间就能留给下一篇 Tinc 网络结构与可观测性优化相关的内容了。过去一年有意思的新东西越来越少，以至于草民都开始偶尔买 B 站工坊上的东西了（然后事实证明最好还是不要买</p>

<h1 id="18-寸智能圆屏">1.8 寸智能圆屏</h1>

<p>这个小圆屏其实看起来还挺好的，但是工坊嘛大家懂的都懂，出来的东西质量就比较一般，比如这个的屏幕和后盖都粘的不是特别牢固，还在 ESP32-S3 模块上贴了一个又大又厚的散热垫，把后面的塑料壳都顶到鼓起来了，最后还是草民自己换了一个稍微薄一点的。</p>

<div style="overflow:hidden;">
  <img src="../assets/images/gadgets-2025/360.webp" style="margin: -135px 0;" />
</div>

<p>随机带的固件只能说也是一坨大杂烩（国产小辣鸡基本操作了属于是），但是后来可以直接刷小智 AI，就突然又变得实用了一些</p>

<ul>
  <li><a href="https://github.com/78/xiaozhi-esp32">项目代码</a>，详情可以看 README，以及 Releases 里面 taiji-pi-s3 就是这个小圆屏的版本</li>
  <li><a href="https://www.bilibili.com/video/BV1PMKWe7EZK">小圆屏厂家的视频</a>，想买的话也可以直接去他们家的店铺</li>
</ul>

<p>然而这东西不能蓝牙 A2DP，音频输出只能走 3.5mm，而且 Wi-Fi 信号相当辣鸡，无论在家还是在公司，稍微远一点的 Wi-Fi 就连不上。所以最终还是逃脱不了吃灰的命运吧 hhh 看看后续能不能更新出一点更丰富的功能</p>

<h1 id="小米的胸包">小米的胸包</h1>

<p>手头缺一个出去玩的时候用的小包（之前都是直接带通勤的包，里面刚好能装下个 MacBook Pro 13 那种，大部分情况下还是有点累赘</p>

<p><img src="../assets/images/gadgets-2025/pack.jpg" alt="" /></p>

<p>小米这个包总体来说还是很不错的，体积和空间分隔都很合理。槽点主要还是挂在胸前的话比较松，背在身后取东西又不太方便，然后就需要经常在两种形态之间切换（其实也可以试试改为挂在腰上</p>

<h1 id="小米人体存在传感器">小米人体存在传感器</h1>

<p>小米之前只有人体传感器，原理应该是基于区域内的红外辐射特征变化，会出现的问题主要是它只能判定有人移动，但如果是比如蹲在卫生间这种里面有人但几分钟不动一下的情况就没有办法区分出来。而人在传感器则是另外引入了一个 24GHz 的毫米波雷达，可以直接确认区域内有没有物体存在，跟红外信号搭配再叠一点算法就可以确定是否有人存在于空间内了。</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/gadgets-2025/s2.png" alt="" /></th>
      <th><img src="../assets/images/gadgets-2025/s1.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>传感器</td>
      <td>自动化</td>
    </tr>
  </tbody>
</table>

<p>草民买的这个目前扔在卫生间，配置成里面两分钟没人自动关灯（因为检测还是有点延迟的关系，开灯还是自己按开关），用了大半年还没有出现过什么问题，值得推荐。至于另外一个人在传感器 Pro（<a href="https://www.bilibili.com/video/BV1GdHHehEio">这里是一个介绍视频</a>），因为供电要求等限制，感觉对大多数人意义不大</p>

<h1 id="维简-cc1-电流表">维简 CC1 电流表</h1>

<p>很精致的一个小东西，而且价格只要四十来块（虽然长期缺货，蹲了应该得有一个礼拜才蹲到）。因为固件大小限制，<a href="https://www.witrn.com/?p=90">要刷不同版本的固件切换功能组</a>。大部分人应该用不到线阻和纹波，但是看 PDO / RDO 还是有用的，所以建议刷 8.2 版本。有一说一这种做法还挺不错的，这就把 Flash 的成本节省下来了，对大部分用户来说也没啥影响，真需要的时候顺手刷个固件完事</p>

<p><img src="../assets/images/gadgets-2025/cc1.jpg" alt="" /></p>

<p>其他的基本也没什么槽点，硬要说就是上面两个按钮的交互着实是不怎么样：比如说上图所示的功能，切换 Wh / Ah 和清除 Wh / Ah 累计数据这两个功能，是放在两个看起来很像但并不一样的界面上，还需要短按按钮两次切换，再长按某一特定按钮操作……着实有点令人困惑。顺便吐槽一下 iPhone 16 Pro Max 充电现在也就只能 20w，不过如果同时崩铁启动的话就能干到 35w（但只能持续一分钟</p>

<h1 id="qcncm865--硬核拆解磁吸小垫子">QCNCM865 &amp; 硬核拆解磁吸小垫子</h1>

<p>把这俩放一起主要是因为刚好拍在一张图上了，而且时间也差不多。这个磁吸小垫子还挺便宜，16 块钱两个包邮，放螺丝也挺方便，推荐</p>

<p><img src="../assets/images/gadgets-2025/qcncm865.jpeg" alt="" /></p>

<p>至于 QCNCM865 这张卡，买它又花了将近三百块。收到的时候刚好 OpenWrt 有了基础支持，再手动打上几个 patch 就能开 AP</p>

<p><img src="../assets/images/gadgets-2025/ap.jpg" alt="" /></p>

<p>当然实际用下来比较糟心（以下复制一下草民当时的测试结论）：</p>

<ul>
  <li>信道只能选 36 - 48。DFS 不能用所以 52 - 64 以及 160MHz 都凉了；149 - 161 在草民手里这张卡的发射功率异常小（紧挨着的设备都只有一格信号那种，根本不能用），这种大概率是这一张卡的校准问题，回头便宜了可能再另买个试试</li>
  <li>虽然打了 patch 但驱动也只是凑合能用，很多数据上报不上来。比如说 TX Rate 全是 0；VHT（Wi-Fi 5）和 HE（Wi-Fi 6）能显示，但手头唯一一个能开 11be 的设备就只有图上第二行那样，也不知道是协商不出来还是状态显示不对。另外，高级特性比如 MLO 啥的，因为看不到数据，也不知道是不是真的能用上</li>
  <li>抛开以上这些问题，用起来倒是能用，也挺稳定的。信道开到如图 48，能协商的 MCS（差不多对应到最高速率）比之前用的 QCA6391 似乎低一些，至于实际使用则是完全感觉不到什么差别。。。</li>
</ul>

<p>后面就扔到龙芯小主机里当客户端用了半年多，除了偶尔 ssh 进去会卡一下（应该是电源管理导致的）之外也没啥大问题，但是大概一个月左右之前突然 AOSC OS 一次更新之后就不能用了，原因似乎是卡的固件加载失败了（没细看）。于是干脆又装回了 QCNFA765，这张卡就跟 MT7925 一样，抽屉里吃灰。如果后面再买别的 Windows 小主机的话可能会换进去吧（</p>

<h1 id="iphone-16-pro-max">iPhone 16 Pro Max</h1>

<p>之前本来说是大概率不换 16，但当时看了几家测评说 16 的能效有比较明显的提升，去年也提到 15 还是有些散热和能耗问题，加上电池健康因为没开充电上限的关系掉到 90%（跟草民同时买的马哥到手就开了 80% 上限，然后那台当时就还有 94%），就还是有那么一点想换。结果运气非常好（？）的在狗东抢到了货，也成功以旧换新大概补了 4200 块钱，算下来也能接受吧</p>

<p>至于手机本身呢，只能说是如升，索然无味。散热和能效之类实话说也没觉得有很大区别，唯一一点可感知的 AI 按钮几乎从来没用到过（顺带一提马上 iOS 19 都要发布了，iOS 18 画的 AI 饼还没有兑现到国行版本）。不过这东西电池是真牛逼啊，虽然说是到手就开了 80% 充电上限，平时也几乎不太会用到 50% 以下，但是用到现在大半年过去了健康状态还是 100% 还是挺令人惊讶的</p>

<table>
  <thead>
    <tr>
      <th><img src="../assets/images/gadgets-2025/p1.png" alt="" /></th>
      <th><img src="../assets/images/gadgets-2025/p2.png" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>相机控制</td>
      <td>电池健康</td>
    </tr>
  </tbody>
</table>

<p>顺便更新了图拉斯的两个壳，最早买的是 AI 按钮那里挖孔的版本，后面更新了一个全包的，但是只是把挖孔那个地方填了一块橡胶上去……强迫症崩溃时刻了属于是，但又实在是不想再花二百多买下一个版本的壳了。凑合用吧（</p>

<p>至于今年的 iPhone 17 系列，看目前泄露的外观设计，实话说个人觉得是比较明显的倒退，材料也变回了 low 一些的铝合金 = = 而且 iPhone 17 Air（不知道最后会不会真的叫这个名字）谣传中的「摄像头居中」目前来看也没戏了，所以如果没有什么真的算是比较重大的更新的话应该会考虑观望一两代（或者电池健康度快要掉到 90% 以下的时候以旧换新也有可能</p>

<h1 id="从公司搞来的-75-寸旧电视">从公司搞来的 75 寸旧电视</h1>

<p>这个是公司规模缩减之后闲置下来的（其实很早就想买了但是没门路，这波还是从某个同事那里倒了一手，花了 800 块），型号还真忘了大概是小米电视 4 吧，总之应该是个 2019 年或者 2020 年的老东西了。因为在公司用了很久，背光老化比较明显（体现为画面左边有一大片圆点状的阴影），虽然不在纯白画面下也不太看得出来，日常感觉也无所谓。</p>

<p><img src="../assets/images/real-nas-project-4/tv.jpeg" alt="" /></p>

<p>这波终于把原来那个 45 寸的小电视给扬了。刚拉回家感觉「卧槽 75 寸好大，幸亏当时没有脑子一热买 86 寸的新电视」，后来看习惯了就感觉刚刚好（上图可能看着也没有很大，但实际上这个电视宽度差不多有客厅的一半了）。另外花了一百多（应该）买了个第三方的支架放在电视柜上，效果还不错。至于为什么不挂墙，主要还是不确定这个墙够不够结实所以没搞。搭配从另一个同事那里 50 包邮来的一套小米的键盘鼠标，坐沙发上操作 R86S 相当舒坦。</p>

<h1 id="redmi-buds-6-活力版">Redmi Buds 6 活力版</h1>

<p>之前用的耳机电池全挂了（AirPods 2 是计划报废还是怎么回事？身边统计学看下来几乎都是右耳电池暴毙，左耳一切正常</p>

<p><img src="../assets/images/gadgets-2025/6199.png" alt="" /></p>

<p>这个虽然只要 99 块钱，但是调音意外的很好，很对草民胃口。当然槽点也有不少：</p>

<ul>
  <li>没有佩戴检测（虽然好像其实也不是很大问题</li>
  <li>断联的概率略高，一般可能几十分钟就会断联一次（还是稍微有点烦</li>
  <li>长按手势关不掉比较容易误触（后来发现可以直接在小爱的设置里面把蓝牙耳机唤醒小爱给关掉，这个问题也就解决了</li>
</ul>

<p>最近看上了新款带降噪的 Redmi Buds 7S，感觉也是该有的都有，试听了一下虽然调音稍微差一点但也凑合。只能说它早出几个月就好了</p>

<h1 id="canokey-canary-正式版">CanoKey Canary 正式版</h1>

<p>跟之前的 CanoKey Pigeon 比起来，质感好很多，NFC 也好用了，固件里面的功能也强大一些（图片懒得拍了，随机找了一个群友的</p>

<p><img src="../assets/images/gadgets-2025/canary.jpeg" alt="" /></p>

<p>但是现在只能评价为 Type-C 的硬件密钥真的是不如 Passkeys 好用</p>

<ul>
  <li>目前手头能插 Type-C 的设备差不多都有 Passkeys，然后基本上都是 iCloud 钥匙串</li>
  <li>不能插 Type-C 的设备基本都用不上 CanoKey Canary
    <ul>
      <li>移动设备里面只剩下了一个用 Lightning 的 iPad Mini 5（但它又可以上 iCloud 钥匙串</li>
      <li>因为在公司用的 NUC 装的 Windows Server 2022 还是懒得整蓝牙的关系，Passkeys 没法用
        <ul>
          <li>有一说一，有那么一点想升级到 Windows Server 2025 解决掉蓝牙了，还可以顺便搞一下 Microsoft Store</li>
          <li>其实它也不是不能插 Type-C，只是它的 Type-C 接口刚好被 HDMI 挡住了所以比较难绷，转接一下倒也能用
            <ul>
              <li>其实家里的台式机 Corsair One i160 也是 Type-C 在后面够不着（但是它蓝牙没问题，可以上 Passkeys</li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>考虑到在公司还天天都得用硬件密钥，这个只能扔包里当作备份的密钥了，日常主力还是接着用上次买到的 CanoKey Pigeon</p>

<h1 id="pietone-键盘">PietOne 键盘</h1>

<p>抽奖抽到的（包括键帽和装在上面的散轴也是抽到的，还是在同一个 UP 那里，此处必须手动感谢<a href="https://space.bilibili.com/1860740010">十七鲤</a>老师和<a href="https://space.bilibili.com/5091316">枫岫泠尘</a>老板</p>

<p><img src="../assets/images/gadgets-2025/pietone.jpeg" alt="" /></p>

<p>键盘的介绍可以看 <a href="https://www.zfrontier.com/app/flow/pWmYkbJOr1G0">https://www.zfrontier.com/app/flow/pWmYkbJOr1G0</a>，简单总结一下特点：星闪方案与鸿蒙支持 + 3D 打印外壳 + 超长待机时间。个人还是无法接受这种过于精简的布局，白嫖到的线性轴用起来也不太习惯（又懒得换），所以还是接着用上面的 Cherry 了（</p>

<h1 id="sharp-sl-7500c">Sharp SL-7500C</h1>

<p>不知道从哪个角落里面翻出来的 20 多年前的库存。纯·电子辣鸡，而且寄来就暴毙了，还好卖家好说话换了一台（然而是个旧一点的</p>

<p><img src="../assets/images/gadgets-2025/7500c.jpeg" alt="" /></p>

<p>原版系统是 Linux 2.4.18 内核 + Qtopia 界面，因为是国内引进版所以自带比较完整的中文支持。简单耍了一下，提几点：</p>

<ul>
  <li>键盘手感意外的还不错，但是没有背光；电阻屏就不用解释了，用惯了电容屏的话真回不去</li>
  <li>SD 卡默认支持的容量极小，但是可以装一个驱动然后想装多大卡装多大卡（目前用了一个 32G 的 TF 转 SD</li>
  <li>充电除了用自带充电器之外也可以买一个 4.0mm 的圆孔转 USB，但是要搭配一个电压高一点的充电器，还得保持亮屏才能充进去电</li>
</ul>

<p>Qtopia 并没有想象中那么有意思，App 也没啥有意思的，虽然也可以刷自制系统之类，但刷进去大概也没啥用。这东西自身又没有什么无线能力，就算买个十几年前折腾 Windows Mobile 2003 那会儿买过的同款 CF 卡 Wi-Fi 适配器也支持不了现代的 WPA2-PSK 加密了，显然也不可能为了这么个小辣鸡专门准备一个安全性很弱的 AP，所以只能接着像去年买的 uConsole 一样当成一个小手办吃灰（</p>

<h1 id="蓝宝石无事牌">蓝宝石无事牌</h1>

<p>被<a href="https://www.zhihu.com/question/56595005/answer/72611358315">破乎上面说的</a>安利到了，虽然到手之后发现也没有那么夸张，不过也还算是一个挺新奇的小玩意儿吧。</p>

<p><img src="../assets/images/gadgets-2025/alpha.jpeg" alt="" /></p>

<p>买到的这个说是微瑕但其实细节很经不起深究，除了晶体边缘有一小片白点（大概就是卖家说的微瑕了）之外，人工打磨也完全没有精度可言。目前扔在公司盘着玩，不带回家的原因主要还是太重了（接近 220g，算下来密度稳稳超过 4g/cm³），来回拿了几天实在觉得太麻烦</p>

<h1 id="superdial-力反馈旋钮">SuperDial 力反馈旋钮</h1>

<p>本来是送给黄大人的智能小辣鸡，然而他更想把刚刚装修好的房子里面大量的展示柜都填满，于是给他换了一个「三月七·巡猎」手办，旋钮就自己拿回来用了。这个滚轮比上面那个小圆屏还粗糙一些，里面结构件基本都是 3D 打印件，而且屏幕也有点歪，无刷电机在某些特定的位置旋转还不顺畅。虽然也不太影响使用，但确实让强迫症人士有些难受，只能评价为是工坊中的工坊水平。</p>

<p><img src="../assets/images/gadgets-2025/dial.png" alt="" /></p>

<p>目前它最大的用途大概是横向滚轮：草民在公司的鼠标是罗技的 MX Master 3S（之前的 Gadgets 2023 里面说过），它有两个滚轮，横向滚轮用在比如看一个字段很多的表的时候就非常方便（而且这对草民来说大概算是日常</p>

<p><img src="../assets/images/gadgets-2023/mxmaster3s.jpg" alt="" /></p>

<p>但在家里面的鼠标就没有，遇到有时候需要临时在家上线 / 查数据的时候就会因为没有横向滚轮而头疼，然后就可以使用这个旋钮（虽然很快就意识到其实按 Shift 再搓鼠标上的纵向滚轮效果是一样的，但又会有一个很难绷的问题：按住 Shift 8 秒钟就会启动粘滞键</p>

<p>这个旋钮还另外支持 Surface Dial 模式，不过这东西现在怕是已经没什么 App 支持了，Windows 上默认也就只有音量、上一曲 / 下一曲、纵向滚轮（在这个模式下也可以通过 Shift 变成横向滚轮）、缩放和撤销。</p>

<h1 id="酷态科-cp122m-磁吸电能块--底座">酷态科 CP122M 磁吸电能块 + 底座</h1>

<p>其实是同事买的，但是他觉得太大，于是草民提出用之前买的小米进行交换（然后刚换完就后悔了</p>

<p><img src="../assets/images/gadgets-2025/cuktech.jpeg" alt="" /></p>

<p>虽然容量比小米那个大一倍，但确实还是太厚了一些，在公司的使用场景下容量本身也不是什么优势，输出功率什么的又都一样，厚重的缺点就显得十分难以忽略；以及底座太轻，每次拿起来充电宝的时候都会把底座一起带起来，只好等端午回来在桌子上贴点儿双面胶了。</p>

<h1 id="next">Next</h1>

<p>后续更新计划：</p>

<ul>
  <li>Tinc 网络结构与可观测性优化（六月中旬。填一下清明留下的坑）</li>
  <li>Vacation 2025.1（七月初）</li>
  <li>锐评用过的几种 CLI Shell（七八月事情比较多，所以先立一个国庆之前发出来的 Flag 吧）</li>
</ul>

<p>最后既然今天是端午，那就送大家一首《山鬼》吧。期待 7.12 兰音 Reine 的线下（许愿抢到 SVIP 票，以及 BW 期间的机票也太贵了</p>

<iframe src="//player.bilibili.com/player.html?isOutside=true&amp;aid=684707134&amp;bvid=BV1jU4y117YR&amp;cid=738603432&amp;p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height="600px" width="100%"></iframe>]]></content><author><name>yichya</name></author><category term="Gadgets" /><category term="gadget" /><summary type="html"><![CDATA[今年端午比以往略早啊，刚好可以赶在五月最后一天发出来，六月的时间就能留给下一篇 Tinc 网络结构与可观测性优化相关的内容了。过去一年有意思的新东西越来越少，以至于草民都开始偶尔买 B 站工坊上的东西了（然后事实证明最好还是不要买]]></summary></entry><entry><title type="html">Real NAS Project (4) Some More Connectivity Changes</title><link href="https://www.yichya.dev/real-nas-project-4/" rel="alternate" type="text/html" title="Real NAS Project (4) Some More Connectivity Changes" /><published>2025-04-05T00:00:00+08:00</published><updated>2025-04-05T00:00:00+08:00</updated><id>https://www.yichya.dev/real-nas-project-4</id><content type="html" xml:base="https://www.yichya.dev/real-nas-project-4/"><![CDATA[<p>虽然草民的 NAS 形态已经相当稳定，但过去这段时间也还是做了一点不大不小的优化（大部分是网络连接相关）。这一阵儿都没啥灵感，所以清明日常活动又咕咕咕了，以及五一应该还要出门玩，所以这次就把清明的空档拿来简单介绍下好了。</p>

<h1 id="get-rid-of-cdn">Get rid of CDN</h1>

<p>去年年底的一件大事：Cloudflare 的新服务协议已经明确禁止架设梯子（j）和优选 IP（b），草民现在也已经彻底拆除了相关的设施</p>

<table>
  <thead>
    <tr>
      <th style="width:44%;">原文</th>
      <th style="width:56%;">重点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><img src="../assets/images/real-nas-project-4/cdn.png" alt="original" /></td>
      <td>
        第 2.2.1 限制第 B 条规定：干扰、破坏更改或修改服务或其他部分，或对网络或服务<br />（包括 Cloudflare 对等合作伙伴的网络）或与服务相连的服务造成不当负担，包括但不限于导致您的 Cloudflare 代理域流量被（无论直接还是间接）发送到未由 Cloudflare 为该域分配的 IP 地址。
        <br /><br />
        第 2.2.1 限制第 J 条规定：使用服务提供虚拟专用网络或其他类似的代理服务。
      </td>
    </tr>
  </tbody>
</table>

<p>去年上半年这种用法非常热门。贴张图回顾一下优选 IP 的威力，可以看到延迟差距有多明显，最快甚至接近十分之一，重点是完全白嫖</p>

<p><img src="../assets/images/real-nas-project-4/bestproxy.png" alt="" /></p>

<p>大家可能会有疑问：许可协议废纸一张罢了，这么好用的东西说拆就拆？几点原因：</p>

<ul>
  <li>确实也觉得总是白嫖 Cloudflare 不太好，赛博菩萨也经不起这么可劲儿薅</li>
  <li>现在 Cloudflare 的协议明确禁止此种用法，草民好几个域名都转移上去了，真被封号的话影响太大</li>
  <li>白嫖优选 IP 去年下半年就已经几乎不可用，就算偶尔有几个能用的，表现也不如官方分配的 IP，已经没有实际意义</li>
  <li>走官方分配的 IP 稳定性其实也很一般，跟直连基本没啥区别，现阶段主要意义其实也就是隐藏 VPS 的 IP 避免被针对
    <ul>
      <li>但最近一两年出现的新的直连协议比如 REALITY，整体也算稳定，只要不跑 PT 之类持续大流量应用，基本上不会被针对了</li>
    </ul>
  </li>
</ul>

<p>不过这里还是可以简单回顾下当时为了白嫖做出的一些建设，以及在放弃此种用法后，这些建设能够发挥的其他价值</p>

<h2 id="all-about-dns">All about DNS</h2>

<p>当时为了更好的使用优选 IP，做了两部分的事情：Hosts 动态更新，以及将连接状况反馈到 DNS</p>

<p>DNS 记录往往会被中间各种缓存，优选 IP 域名也不例外。而且在这种用法下，通过域名服务商的 API 更新解析记录可能会非常频繁，很容易撞上服务商的频控导致更新失败。种种原因导致 DNS 方案并不可靠，所以往往有另外一些方式获取数据，比如 HTTP 接口或者直接提供一个 zip 文件。另外优选 IP 也还需要进一步的过滤（比如按照 GeoIP 筛特定区域 / ASN）。草民做的某个妙妙小工具就可以像下面这样：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># go run . -in /usr/local/share/xray/geoip.dat -type geoip -keep HK -filter ./txt/45102-1-443.txt -mode intersection | awk '{print $1}' | sort</span>
47.240.42.138
47.242.180.240
8.210.140.91
8.210.29.68
8.210.48.192
8.212.43.8
8.218.71.200
</code></pre></div></div>

<p>有了 IP 之后就该想办法提供给 Xray 了。Xray 自带的 DNS 机制不够灵活（主要是配置文件不能热更新，重启一次 Xray 感知又太强），为此专门再配置一个 DDNS 的话链路又太长，而且也会遇到上面说的缓存 / API 操作失败之类问题。于是魔改 Xray，在原有的 Hosts 配置基础上增加了一个 goroutine 去定时通过 HTTP 接口拉取对应的 IP 数据，实现 Hosts 的动态更新。这里就不贴代码了，简单指一下在哪儿改（</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>goroutine 4 [sleep, 2 minutes]:
time.Sleep(0xdf8475800)
	runtime/time.go:338 +0x158
github.com/xtls/xray-core/app/dns.(*StaticHosts).refreshDynamicConfig(0x4001770f50, {0xab84c0?, 0x16343a0?})
	github.com/xtls/xray-core/app/dns/hosts.go:159 +0x30
runtime/pprof.Do({0xdeaca8?, 0x16343a0?}, {{0x40007ff560?, 0x0?, 0x0?}}, 0x4000043ed0)
	runtime/pprof/runtime.go:51 +0x78
created by github.com/xtls/xray-core/app/dns.NewStaticHosts in goroutine 1
	github.com/xtls/xray-core/app/dns/hosts.go:97 +0x6b8
</code></pre></div></div>

<p>当时把上面的那个妙妙小工具配置成了一个 Jenkins 上的定时任务，再利用 Artifacts 就可以暴露出 HTTP 接口提供给 Xray，不过现在反正也用不到了所以就先不放出来了（主要是代码写的很乱，包括 Xray 的改动也一样一团糟还没 Unit Test，以后有机会吧</p>

<p><img src="../assets/images/real-nas-project-4/hosts.png" alt="" /></p>

<p>另外，Xray 的 DNS 缺少一个自身反馈的能力，尤其是在优选 IP 这种一个域名解析出非常多个 IP 而且大部分都不能用的情况下。之前常用 GRPC，遇到这种问题基本都只能吃一个 <code class="language-plaintext highlighter-rouge">CONNECTION_CLOSED</code>，而且会一直持续下去。虽然也可以在上面的妙妙小工具中增加一个验证，但是这种反馈整个流程走下来至少也是分钟级别，很容易被感知到。于是将 Xray 使用这些 IP 时遇到问题的情况（比如 GRPC 可能遇到 HTTP 403）反馈给 DNS 记录，将这些 IP 从后续的解析结果中剔除，使得 Xray 能够在可接受的时间（大概两分钟）内收敛到稳定的状态</p>

<p><img src="../assets/images/real-nas-project-4/dialer_check.png" alt="" /></p>

<p>上面两个机制搭配，得到的结果非常理想，狠狠爽了几个月。后面顺便还做了 SplitHTTP 支持，可惜做出来的时候已经来不及了（</p>

<h2 id="about-pt">About PT</h2>

<p>草民之前过 CDN 的梯子用法，除了上面提到的白嫖优选 IP，还有一个<a href="../diy-nas-project-4">很久以前介绍过</a>的北邮人 PT 用到的 IPv6 代理，为了避免 IP 流量太大被针对也过了 Cloudflare CDN，当然就直接用官方分的 IP 了。不过北邮人的设计有一点很有意思：它只在主站做了 IP 黑名单，Tracker 是没限制的，所以其实现在这样有正常可用的 IPv6 的情况下，下载工具不走梯子甚至效果反而更好，所以这波干脆就一起拆了。</p>

<p>话说回来，好像已经几个月都不会点开北邮人一次了（主要这一阵儿是真没什么想看的电影和连续剧），于是对硬盘空间的需求少了很多。最近硬盘迷之越来越贵，甚至真的有点想改用 8T x4 组阵列，主要是目前的阵列完全不对称，多少有些别扭（还是先凑合用吧，坏了再说</p>

<h1 id="better-dns">Better DNS</h1>

<p>内部 DNS 的一些调整。这些其实都是本地流量管理该做好的事情，后面 One more thing 需要加把劲</p>

<h2 id="optimize-tinc-path">Optimize Tinc Path</h2>

<p>Tinc 在局域网内的 Peer 探测似乎不是很好用，经常要从某个网络外的 Peer 转发（印象中 Zerotier 是做的最好的），用起来显然不如直连舒坦。如下图，目前希望能让 OpenWrt 1 与 OpenWrt 2 建立直接连接（否则可能只能从 Cloud 绕一圈），但因为隔了一层 NAT 所以常用的网络发现方案可能都不太好使，只能通过 OpenWrt 2 直接用地址 <code class="language-plaintext highlighter-rouge">192.168.1.4</code> 的方式，过一层 NAT 建立到 OpenWrt 1 的连接。</p>

<p><img src="../assets/images/real-nas-project-4/tinc.svg" alt="" /></p>

<p>很容易想到几个解决方案：</p>

<ul>
  <li>保证 OpenWrt 1 的 IP 地址不变，在 OpenWrt 2 上直接用 IP 地址连接
    <ul>
      <li>Gateway 是运营商的接入设备，上面不太好搞静态 DHCP 分配，草民也不太喜欢这么做；直接指定静态 IP 更容易出问题</li>
    </ul>
  </li>
  <li>Tinc 的节点地址更新只能通过 DNS 来完成，所以找个 DDNS 域名记录 OpenWrt 1 的内网 IP，在 OpenWrt 2 上用这个域名连接
    <ul>
      <li>也算是相对比较常规的用法，不过草民手头没有合适的域名，而且跟上面优选 IP 一样会遇到缓存 / DDNS 服务商相关的问题</li>
    </ul>
  </li>
  <li>可以先走其他节点建立初步连接，再要求其中一端提供自己局域网内的地址用以直连，可以不用依赖外部服务
    <ul>
      <li>相当于自建一个内网 DDNS 服务。刚好上面优选 IP 魔改做了利用 HTTP 更新 DNS 数据的设施，于是试着用它来做一个 DDNS</li>
    </ul>
  </li>
</ul>

<p>那么来尝试一下自建 DDNS 吧。首先准备一个 HTTP API 用来返回自身的 IPv4 地址。对于 OpenWrt 来说最省事儿的方式是 cgi</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">printf</span> <span class="s2">"Content-Type: text/plain</span><span class="se">\r\n</span><span class="s2">Connection: close</span><span class="se">\r\n\r\n</span><span class="s2">"</span>
ifstatus <span class="k">${</span><span class="nv">QUERY_STRING</span><span class="k">}</span> | jsonfilter <span class="nt">-e</span> <span class="s1">'@["ipv4-address"][0].address'</span>
</code></pre></div></div>

<p>放在 OpenWrt 1 的 <code class="language-plaintext highlighter-rouge">/www/cgi-bin/address.v4</code>，就可以通过 LuCI 用的那个 uhttpd 直接访问：</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># curl http://192.168.123.1/cgi-bin/address.v4?wan</span>
192.168.1.4
</code></pre></div></div>

<p>在 OpenWrt 2 上通过从 Cloud 绕一圈的方式拿到上面这个地址（首先保证这两台设备从 Cloud 绕一圈的路子是通的，上面 192.168.123.1 就是 Tinc 大局域网内的地址），然后再找个办法把它对应到某个域名就好了。这里就像上面说的那样使用了魔改 Xray，每分钟调用一次这个地址，指定为 openwrt.dialer.check 对应的结果</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>; &lt;&lt;&gt;&gt; DiG 9.20.7 &lt;&lt;&gt;&gt; openwrt.dialer.check @192.168.123.9
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 62269
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;openwrt.dialer.check.		IN	A

;; ANSWER SECTION:
openwrt.dialer.check.	586	IN	A	192.168.1.4

;; Query time: 0 msec
;; SERVER: 192.168.123.9#53(192.168.123.9) (UDP)
;; WHEN: Sat Apr 05 00:00:51 CST 2025
;; MSG SIZE  rcvd: 65
</code></pre></div></div>

<p>最后让 OpenWrt 2 上的 Tinc 尝试通过这个域名连接到 OpenWrt 1 就行</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cat /etc/tinc/hosts/openwrt </span>
Subnet <span class="o">=</span> 192.168.123.1/32
Address <span class="o">=</span> openwrt.dialer.check
<span class="nt">-----BEGIN</span> RSA PUBLIC KEY-----
&lt;redacted&gt;
<span class="nt">-----END</span> RSA PUBLIC KEY-----
</code></pre></div></div>

<p>OpenWrt 2 上的 Tinc 启动时还不能直接连接到 OpenWrt 1 上，但能够连接到 Cloud 加入 Tinc 大局域网，此时就可以通过 192.168.123.1 上的 HTTP 接口拿到局域网地址，然后在本地完成 <code class="language-plaintext highlighter-rouge">openwrt.dialer.check</code> -&gt; <code class="language-plaintext highlighter-rouge">192.168.1.4</code> 的解析，Tinc 就可以继续利用这个地址建立直接连接了。虽然其实这么用实际效果也难说，包括之前有提过的走公网的 Tinc 状态监控，其实也没有找到特别明确的标准来确认，这个大概后面还要做一点防火墙规则相关的过滤来区分中转和直连的流量，利用这个数据来确定状态。留作后续的坑吧（</p>

<h2 id="no-more-static-ip--static-dhcp-leases">No More Static IP &amp; Static DHCP Leases</h2>

<p>上面说过草民非常不喜欢静态 IP / 静态 DHCP 分配，原因主要有这么几个：</p>

<ul>
  <li>草民的设备很多都是没有插键盘显示器的，如果指定静态 IP 又遇上网络结构变动，很容易就失联了</li>
  <li>草民的 OpenWrt 变更十分频繁（要同时跟 OpenWrt 和 Xray 的上游改动），每天可能都要更新一两次
    <ul>
      <li>OpenWrt 重启并不一定会使所有网络中的设备重新发起 DHCP 请求，这时 OpenWrt 自身还没有 DHCP 分配记录所以没法解析
        <ul>
          <li>一般的解决方式是再配上 Hosts 记录，但静态分配偶尔会重复（网络中已经有一个设备占了坑），此时 Hosts 也会对不上</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>DHCP 不靠谱，但是对于网关来说还有其他的方式记录稳定的对应关系，比如利用 MAC 地址来查 ARP，当然也有前提：</p>

<ul>
  <li>不是网关的话 ARP 记录不能保证是全的，所以不太能这样用</li>
  <li>MAC 地址要保持不变（所以对于无线网络设备，尤其是比较新的设备一般都会有 MAC 地址自动轮换的功能，这种情况就不适用了）
    <ul>
      <li>这种情况其实 DHCP 也不一定靠谱（设备可能在 DHCP 的过程中不发送主机名），就要考虑 mDNS 之类的其他网络发现能力了</li>
    </ul>
  </li>
</ul>

<p>把 ARP 记录关联到域名这件事就可以用跟上面相似的方式解决。把下面这个脚本保存为 <code class="language-plaintext highlighter-rouge">/www/cgi-bin/address.arp</code></p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">printf</span> <span class="s2">"Content-Type: text/plain</span><span class="se">\r\n</span><span class="s2">Connection: close</span><span class="se">\r\n\r\n</span><span class="s2">"</span>
<span class="nb">grep</span> <span class="nt">-i</span> <span class="k">${</span><span class="nv">QUERY_STRING</span><span class="k">}</span> /proc/net/arp | <span class="nb">awk</span> <span class="s1">'{print $1}'</span>
</code></pre></div></div>

<p>这个脚本就可以像这样通过 MAC 地址查出对应的 IP</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># curl http://127.0.0.1/cgi-bin/address.arp?00:15:5d:0f:e9:02</span>
10.32.15.208
</code></pre></div></div>

<p>再利用跟上面一样的机制绑定一个域名即可。除了 IPv4 和 ARP，相似的做法也可以用在 IPv6、NDP 甚至 mDNS 上，也留作后续的坑吧（</p>

<h2 id="doh-and-more-stable-doh">DoH and More Stable DoH</h2>

<p>之前直接用国内的 DNS 解析域名，想的是反正都有明文 SNI 漏就漏吧。不过后面改用 REALITY 又觉得还是应该保险一点，所以魔改了一下 luci-app-xray 支持了一下使用 DoH（纯 IP 那种）解析 Outbound 地址。至于为什么不直接用 VPS IP 访问梯子？一个是懒，另一个是上面那个魔改是基于域名启用规则的（这个又涉及到一些额外的魔改，简单的说就是用 Hosts 映射前的域名过分流，映射后的域名去访问</p>

<p>用了一段时间发现：稍微有名气点儿的几个纯 IP DoH（比如草民之前常用的 <code class="language-plaintext highlighter-rouge">9.9.9.9</code> 和 <code class="language-plaintext highlighter-rouge">101.101.101.101</code>）连接还是比较不稳定，经常一抽几分钟甚至半个小时，所以还是要考虑自动的故障切换。Xray 自身不太好配这个能力，但恰好，之前 <a href="../real-nas-project-1">Real NAS Project (1)</a> 已经配过 cloudflared，它的 DoH 代理是支持这个能力的。只要在配置文件里面加上几行，就可以得到一个 DNS 代理（支持 TCP / UDP）</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">proxy-dns</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">proxy-dns-port</span><span class="pi">:</span> <span class="m">5311</span>
<span class="na">proxy-dns-upstream</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">https://146.112.41.2/dns-query"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">https://146.112.41.3/dns-query"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">https://146.112.41.4/dns-query"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">https://146.112.41.5/dns-query"</span><span class="pi">]</span>
</code></pre></div></div>

<p>它的行为似乎是按顺序，也就是第一个没成功再尝试第二个这样。另外，比较有名气的纯 IP DoH 三月份之后因为种种原因都不太能用了，好在 OpenDNS 还有一些存活，下列（来自 <a href="https://t.me/yaatg_group/23">YaaTG</a>）</p>

<ul>
  <li>146.112.41.2 / 146.112.41.3 / 146.112.41.4 / 146.112.41.5</li>
  <li>146.112.70.70 / 146.112.71.71</li>
  <li>155.190.111.111 / 155.190.111.123</li>
  <li>204.194.232.200 / 204.194.234.200</li>
  <li>208.67.220.2 / 208.67.222.2</li>
  <li>208.67.220.64 / 208.67.222.64</li>
  <li>208.67.220.123 / 208.67.222.123</li>
  <li>208.67.220.220 / 208.67.222.222</li>
</ul>

<p>大部分应该都可以直接通过类似 <a href="https://1.1.1.1/dns-query">https://1.1.1.1/dns-query</a> 的形式访问，但似乎 dig 不一定会有反应，Xray 和 cloudflared 用起来都是正常的，可以自己根据自己的网络环境选择。</p>

<h1 id="r86s-direct">R86S Direct</h1>

<p>起初是 Windows Dev Kit 2023（下面用 <a href="../wdk2023-first-experience">Volterra</a> 代替）上不能直通无线网卡所以一直在这样用，后来突然意识到把 i211 挪出来可以得到许多好处。先放一张图简单介绍一下家里网络的基本结构（在前面那张图上加了一点细节），体现为 DHCP 分配关系，以及是否为默认网关</p>

<p><img src="../assets/images/real-nas-project-4/dhcp.svg" alt="" /></p>

<p>图上还有一点没说清楚：OpenWrt 1 是 R86S 上的虚拟机，OpenWrt 2 是 Volterra 上的虚拟机，都跑在 Hyper-V 上</p>

<h2 id="rescue">Rescue</h2>

<p>作为一个 OpenWrt Master 战士，遇到些问题搞挂 OpenWrt 也是很正常的，于是希望达成这样的目标：</p>

<ul>
  <li>除了 NAS 之外的设备（图上就只有两台 OpenWrt 的宿主机），默认路由都走 OpenWrt 保证网络干净</li>
  <li>无论 OpenWrt 1 还是 OpenWrt 2 炸掉，对应的宿主机都还能正常连接公网（然后可以用 Tinc 连上去恢复）</li>
  <li>NAS 不依赖 OpenWrt，除了要走大流量 PT 之外，也要保证上一条说的事情发生的时候能访问到备份的镜像和配置</li>
</ul>

<p>原先因为直通 i211，图上 Gateway 到 R86S 这条虚线是没有的，如果 OpenWrt 1 挂了的话 R86S 就完全无法访问到了，只能跑到电视前面手动操作。现在把 i211 摘出来补上这条链路，再借助一点默认网关的自动优先级规则，问题就得到了解决。下面简单介绍一下</p>

<h2 id="default-gateway-and-automatic-metric">Default Gateway and Automatic Metric</h2>

<p>Windows 的 IPv4 路由自动 Metric 是用链路速度对应出来的：</p>

<ul>
  <li>正常的物理网卡一般链路速度跑 1Gbps，Metric 是 25；有的富哥可能多一点比如 2.5Gbps，Metric 是 20</li>
  <li>Hyper-V 起手就是个 10Gbps，绝大部分情况下比物理网络都要快，对应到的自动 Metric 是 15</li>
  <li>具体规则可见 <a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/automatic-metric-for-ipv4-routes">An explanation of the Automatic Metric feature for IPv4 routes</a></li>
</ul>

<p>这样得到的网关优先级规则，在上面这种虚拟机里跑 OpenWrt 的用法上非常符合预期，并且能在各种情况下保证 Tinc 可用：</p>

<ul>
  <li>OpenWrt 宿主机都有一个备用的 DHCP 地址并且都不作为默认网关，保证正常情况下网络干净</li>
  <li>OpenWrt 出问题的时候，遵循上面说的自动网关规则，宿主机的网关会自动切换到物理网卡，不影响 Tinc 的连接
    <ul>
      <li>随便找一个什么方式接入 Tinc（甚至可以手机开热点，然后通过 Cloud 接入）即可操作宿主机</li>
    </ul>
  </li>
</ul>

<p>其实还有一点可以完善的，就是希望能让 Tinc 始终走某个出口出去，目前只能部分走对路由（当然用是没问题的）：</p>

<ul>
  <li>从 NAS 连到 R86S 的可以直接走 Gateway 那条路，因为 NAS 上 systemd-resolved 能直接用 mDNS 解析出 R86S 的地址</li>
  <li>反过来不行，主要是 R86S 没有办法直接解析出 Gateway 给 NAS 分配的地址，所以还是要从默认网关 OpenWrt 1 过一次 NAT</li>
  <li>如果 OpenWrt 1 炸了，NAS 到 R86S 的连接仍然不受影响，R86S 自己也可以用正常切换了的的默认网关通过 Cloud 接入 Tinc</li>
</ul>

<p>不过似乎 Windows 下没有什么办法能做到将某个程序的所有网络请求都绑定到某个网络设备上。欢迎探讨</p>

<p>另外，这种用法还可以进行推广：在 MicroPC 之类便携设备上装一个这样的 OpenWrt，在家不启动，正常接入家庭网络；在外面启动，直接获得透明代理 + Tinc，而且除了启动虚拟机之外不需要做任何其他事情，默认网关自动切换。这个会留作五一出远门之前要做的设置（</p>

<h2 id="about-drivers">About Drivers</h2>

<p>复读：Windows Server 2022 给 i211 装驱动直接装系统自带的 I219-LM，稳定运行半年，毫无问题</p>

<p><img src="../assets/images/real-nas-project-4/i211.png" alt="" /></p>

<p>顺便简化了一下公司那台网络配置差不多的机器（去年八月公司搬家之后再也没有网线用了），网口甚至可以直接接一个 AP 出来用</p>

<h1 id="wireless">Wireless</h1>

<p>之前在 NAS 上插了个螃蟹无线网卡用来连到给父母用的 AP 上（Volterra 也连在上面，NAS 连这个 AP 主要就是提供给 Volterra 一个备用连接方案，用来在它上面的 OpenWrt 炸了的时候连上去处理）。但辣鸡螃蟹卡实在太不稳定了，动不动就暴毙，于是干脆加了个自动重置</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cat /etc/systemd/system/usbreset.service
</span><span class="nn">[Unit]</span>
<span class="py">Description</span><span class="p">=</span><span class="s">Restart Wireless Adapter</span>
<span class="py">After</span><span class="p">=</span><span class="s">network.target</span>

<span class="nn">[Service]</span>
<span class="py">ExecStart</span><span class="p">=</span><span class="s">/bin/bash -c 'ping -c 10 -i 2 10.0.0.1 || usbreset 0bda:c811'</span>
<span class="py">RuntimeMaxSec</span><span class="p">=</span><span class="s">60</span>
</code></pre></div></div>

<p>再配一个五分钟触发一次的 Timer</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cat /etc/systemd/system/usbreset.timer
</span><span class="nn">[Unit]</span>
<span class="py">Description</span><span class="p">=</span><span class="s">Restart Wireless Adapter</span>

<span class="nn">[Timer]</span>
<span class="py">OnCalendar</span><span class="p">=</span><span class="s">*:0/5</span>

<span class="nn">[Install]</span>
<span class="py">WantedBy</span><span class="p">=</span><span class="s">timers.target</span>
</code></pre></div></div>

<p>实际用下来也并不是很好用，有的时候 <code class="language-plaintext highlighter-rouge">usbreset</code> 也拉不起来（设备有了但 <code class="language-plaintext highlighter-rouge">nmtui</code> 上还是无法操作），只能重启。后面还是考虑换个好点的方式吧，比如拿 hap ac^2 做个无线中继。觉得有必要考虑一下再把网络弄的更健壮一些的事情（多准备几个接入方式、修复预案什么的</p>

<h1 id="ansible">Ansible</h1>

<p>自动升级几台机器上的 Ubuntu / Xray 等等，非常方便</p>

<h2 id="specify-ssh-port">Specify SSH Port</h2>

<p>Inventory 里面可以一次指定一组目标机器的 SSH 端口和凭据</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[xray]</span>
<span class="err">192.168.123.11</span>
<span class="err">192.168.123.13</span>

<span class="nn">[xray:vars]</span>
<span class="py">ansible_port</span><span class="p">=</span><span class="s">23333</span>
<span class="py">ansible_user</span><span class="p">=</span><span class="s">yichya</span>
<span class="py">ansible_connection</span><span class="p">=</span><span class="s">ssh</span>
<span class="py">ansible_ssh_pass</span><span class="p">=</span><span class="s">&lt;redacted&gt;</span>
<span class="py">ansible_become_pass</span><span class="p">=</span><span class="s">&lt;redacted&gt;</span>
</code></pre></div></div>

<p>其实密码是不应该直接写在 Inventory 文件里面的，但自己用也懒得讲究那么多</p>

<h2 id="change-ssh-port-correctly">Change SSH Port Correctly</h2>

<p>另外，过程中顺便学到了 <a href="https://serverfault.com/a/1159600">https://serverfault.com/a/1159600</a>，很难理解这 tm 是个什么莫名其妙的设计</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cat /etc/systemd/system/ssh.socket.d/override.conf
</span><span class="nn">[Socket]</span>
<span class="py">ListenStream</span><span class="p">=</span>
<span class="py">ListenStream</span><span class="p">=</span><span class="s">28251</span>
</code></pre></div></div>

<p>用 <code class="language-plaintext highlighter-rouge">sudo systemctl edit ssh.socket</code> 来编辑这个文件，<code class="language-plaintext highlighter-rouge">ListenStream</code> 写两行，一个空的（必须写）再写一个期望的端口。Ubuntu 上默认编辑器可能是 Nano，用不惯的话可以 <code class="language-plaintext highlighter-rouge">sudo update-alternatives --config editor</code> 改成 Vim</p>

<h2 id="playbook-example">Playbook Example</h2>

<p>比如 apt 三步走，让 Gemini 写了一个，再稍微改改（按说这事儿很简单，但它事实上写的很烂，一开始给出的版本根本不能 autoremove</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="pi">-</span> <span class="na">hosts</span><span class="pi">:</span> <span class="s">ubuntu, work, xray</span>
  <span class="na">become</span><span class="pi">:</span> <span class="no">true</span>
  <span class="na">tasks</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update apt cache</span>
      <span class="na">apt</span><span class="pi">:</span>
        <span class="na">update_cache</span><span class="pi">:</span> <span class="s">yes</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upgrade all packages</span>
      <span class="na">apt</span><span class="pi">:</span>
        <span class="na">upgrade</span><span class="pi">:</span> <span class="s">yes</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Clean</span>
      <span class="na">apt</span><span class="pi">:</span>
        <span class="na">autoclean</span><span class="pi">:</span> <span class="s">yes</span>
        <span class="na">autoremove</span><span class="pi">:</span> <span class="s">yes</span>
</code></pre></div></div>

<p>有一说一，<code class="language-plaintext highlighter-rouge">become</code> 真的是个好东西，没这玩意儿着实是寸步难行。运行的时候会返回 OK 代表什么也没做，或者 Changed 代表做了一些事情，不过不能直接看到 stdout / stderr（需要写一大堆很麻烦的配置</p>

<h1 id="reality-fallback-speed-limit">Reality Fallback Speed Limit</h1>

<p>二月份很突然的某台 VPS 流量一晚上就被拉爆了，看了一下本地监控也没看出个名堂，怀疑是被人当中转用了所以补了一个 nginx 流限速。首先装一下 <code class="language-plaintext highlighter-rouge">libnginx-mod-stream</code>（草民是 Ubuntu，apt 就行），并在配置文件最顶上加一个 <code class="language-plaintext highlighter-rouge">stream</code> 指定一个非常低的限速</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">stream</span> <span class="p">{</span>
    <span class="kn">upstream</span> <span class="s">reality</span> <span class="p">{</span>
        <span class="kn">server</span> <span class="nf">genshin.hoyoverse.com</span><span class="p">:</span><span class="mi">443</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kn">server</span> <span class="p">{</span>
        <span class="kn">listen</span> <span class="nf">127.0.0.233</span><span class="p">:</span><span class="mi">2333</span><span class="p">;</span>
        <span class="kn">proxy_download_rate</span> <span class="mi">10k</span><span class="p">;</span>
        <span class="kn">proxy_pass</span> <span class="s">reality</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>然后把 Xray 的 <code class="language-plaintext highlighter-rouge">streamSettings.realitySettings.dest</code> 改成 <code class="language-plaintext highlighter-rouge">127.0.0.233:2333</code> 就行了。</p>

<p>有个问题是 nginx 似乎只在启动的时候解析一次 upstream 中的域名，解析不成功会直接炸掉，upstream 的 IP 变了的话也会导致握手出问题。新版本的 nginx 可以在 <code class="language-plaintext highlighter-rouge">upstream</code> 上加一个 <code class="language-plaintext highlighter-rouge">resolve</code>（1.27.3 以后，<a href="https://nginx.org/en/docs/stream/ngx_stream_upstream_module.html">Module ngx_stream_upstream_module</a>，以前的版本想用这个特性的话要收费。不得不说刀法精准……仔细一想，难怪上家的网络接入层当年还有一个 watch 组件专门负责在服务 IP 产生变化的时候去 reload nginx），但 Ubuntu 24.04 自带的是 1.24.0 所以没的用，干脆用上面的办法改了 systemd service，无限次数重启 + 十分钟强行重启</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cat /etc/systemd/system/nginx.service.d/override.conf 
</span><span class="nn">[Service]</span>
<span class="py">Restart</span><span class="p">=</span><span class="s">always</span>
<span class="py">RuntimeMaxSec</span><span class="p">=</span><span class="s">600</span>
</code></pre></div></div>

<p>其实主要是懒得给好几台机器挨个装 PPA 不然全升级到主线 nginx 也不是不行。先凑合用吧（有一说一 Xray 真的应该自带限速能力的</p>

<h1 id="next">Next</h1>

<p>二月份还偶然用了一下 <a href="https://github.com/nushell/nushell">Nushell</a>，初见印象很不错，彻底抛弃 POSIX Compliant 的历史包袱设计出来的 Nu 在 Interactive 和 Scripting 的表现都很有亮点（而且做到了应该可以说是恰到好处的平衡），但尝试深度使用的过程中也有发现一些实现上草民觉得不太理解的地方，比如插件功能的设计居然还是开个进程用 stdio，以及虽然非常重（就不说加起来可能有上百 MB 的几个插件了，光是本体就自带 uutils、HTTP 甚至 sqlite）但 OOBE 简直是跟 bash 差不多，远没有 fish 那种默认就足够好的 Prompt 以及非常非常好用的自动补全。</p>

<p>顺便说起来研究的过程中才知道 macOS 自带了四种 shell，不过什么 ksh csh 应该是都没啥人用（虽然还有在积极维护的分支 / 兼容实现</p>

<p><img src="../assets/images/real-nas-project-4/shell.png" alt="" /></p>

<ul>
  <li>ksh 的兼容实现 mksh 常见于 <a href="https://android.googlesource.com/platform/system/core/+/master/shell_and_utilities/README.md">Android’s shell and utilities</a></li>
  <li>csh 的兼容实现 tcsh 之前是 FreeBSD 的默认 shell（从 14 开始换成了个非常菜但 POSIX compliant 的 sh</li>
</ul>

<p>后面可能会写一篇介绍一下草民用过的几种 shell 包括 bash、fish、Windows Powershell 和 Nushell，也包括草民为什么还是坚持使用 fish</p>

<p>然后是一些计划中的内容。Gadgets（2025）包含：</p>

<ul>
  <li>1.8 寸智能圆屏（吃灰</li>
  <li>小米的胸包（还不错就是有点松</li>
  <li>小米人体存在传感器（好使，推荐</li>
  <li>CC1 电流表（这个价格要什么自行车</li>
  <li>QCNCM865（暂时不太用的上，扔龙芯里了</li>
  <li>iPhone 16 Pro Max（用下来只能说是如升，索然无味</li>
  <li>捡来的旧电视（搭配蹭来的键盘鼠标，坐沙发上操作 R86S 意外的舒坦</li>
  <li>Redmi Buds 6 活力版（之前用的耳机电池全挂了，这个调音意外的很好</li>
  <li>CanoKey Canary 正式版（只能评价为 Type-C 真的是不如 Passkeys 好用</li>
  <li>Sharp SL-7500C（纯·电子辣鸡，而且寄来就暴毙了，还好卖家好说话换了一台</li>
</ul>

<p>东西不太多。作为预告简单晒一下电视好了，有一说一这个尺寸刚刚好，再大恐怕真有点别扭</p>

<p><img src="../assets/images/real-nas-project-4/tv.jpeg" alt="" /></p>

<p>还有 Vacation 2025.1 为时尚早（内容倒是可能不少），慢慢填。</p>]]></content><author><name>yichya</name></author><category term="NAS and OpenWrt" /><category term="openwrt" /><category term="lede" /><category term="nas" /><category term="router" /><summary type="html"><![CDATA[虽然草民的 NAS 形态已经相当稳定，但过去这段时间也还是做了一点不大不小的优化（大部分是网络连接相关）。这一阵儿都没啥灵感，所以清明日常活动又咕咕咕了，以及五一应该还要出门玩，所以这次就把清明的空档拿来简单介绍下好了。]]></summary></entry><entry><title type="html">Guilin Experience</title><link href="https://www.yichya.dev/guilin-experience/" rel="alternate" type="text/html" title="Guilin Experience" /><published>2025-03-03T00:00:00+08:00</published><updated>2025-03-03T00:00:00+08:00</updated><id>https://www.yichya.dev/guilin-experience</id><content type="html" xml:base="https://www.yichya.dev/guilin-experience/"><![CDATA[<p>其实本来不太想在天这么冷的时候出去玩，但是又得把马上要过期的三天年假消耗掉。<a href="../vacation-2024-2">Vacation 2024.2</a> 里面提了两个 Flag，泸沽寻梦太麻烦了而且还要留给新高铁线路（看新闻预计得 2026 年年底了），所以泼墨漓江吧，刚好演唱会的戒断反应也需要想办法缓解一下。</p>

<p>简单摇了三个大哥（两个大学同学 + 他们俩其中一人的同事），翻了一顿小红书再加一些讨论之后得到初步计划：</p>

<ul>
  <li>第一天到桂林。晚上才能把人凑齐，草民到的早，就简单四处溜溜，看看城市界面什么的</li>
  <li>第二天桂林市区游，因为时间不太多就简单找了个能比较容易串下来的攻略</li>
  <li>第三天上午坐船去阳朔，下午相公山，晚上西街逛</li>
  <li>第四天上午银子岩，下午遇龙河竹筏 + 十里画廊骑车，晚上摆</li>
  <li>第五天各凭本事，各回各家</li>
</ul>

<h1 id="preparations">Preparations</h1>

<p>小红书上全是避雷，不做提前准备还真是不行。初步计划有了，那就先确认酒店，以及很多项目需要提前订票，按照时间安排好</p>

<h2 id="hotel">Hotel</h2>

<p>提前看了天气据说要连续下五天的雨（事实上真的下了五天雨），肯定得要洗衣服。那么不纠结，直接亚朵</p>

<ul>
  <li>桂林 2.19（周三）、2.20（周四）两晚
    <ul>
      <li>选了高新的酒店，主要考虑坐船方便，而且桂林并不大，也有共享自行车什么的，交通不算太大问题</li>
    </ul>
  </li>
  <li>阳朔 2.21（周五）、2.22（周六）两晚
    <ul>
      <li>只有一家，没的选，不过离西街比较近，也算比较方便</li>
    </ul>
  </li>
</ul>

<h2 id="tickets">Tickets</h2>

<p>下面按照浏览顺序简单列一下。靖江王府要 100 块钱但这次没去，象鼻山不要钱，日月双塔没上去，这些未发生的开销就不列出来了</p>

<ul>
  <li>漓江游船（桂林 - 阳朔）
    <ul>
      <li>公众号「漓江售票处」可选 3 星和 4 星两种，价格分别是 215（不带饭，加 30 可获得盒饭一份）/ 360（带个不咋样的自助餐）</li>
      <li>开船时间，3 星有 9:30 10:00 12:05 可选，4 星只有 9:35；要求提前 40 分钟到码头，公众号也可以约直达车，一人 20</li>
      <li>放票提前 5 天，具体来说是指 120 个小时，也就是 2.16 9:35 可以买到 2.21 9:35 的 4 星船票；3 星随便买，4 星票放的少</li>
    </ul>
  </li>
  <li>相公山
    <ul>
      <li>旁边离兴坪古镇不远，想去的话可以考虑一下交通安排（但一，这地方真没啥可去的，下面会说；二，交通就要自己想办法了）</li>
      <li>只去相公山的话可以在 OTA 平台（比如美团和飞猪）上搜到 80 块左右的「门票 + 往返大巴」套餐，会有司机电话沟通上车点</li>
      <li>分三个时间段：日出（4:00 出发那种），上午（9:30 左右出发），日落（15:00 左右出发）
        <ul>
          <li>日出起的贼早不说，人还非常非常多，价格也要贵十块钱，如果不是很有执念的话建议选日落</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>银子岩
    <ul>
      <li>跟相公山的方案类似，9:00 和 14:30 两个时间可选，OTA 平台上的价格也差不多</li>
      <li>这个在小红书上被提到最多的是很难打车回来，所以比较建议买带往返大巴的</li>
    </ul>
  </li>
  <li>遇龙河竹筏
    <ul>
      <li>提前一天 20:00 在「遇龙河」公众号买竹筏票，可选路线比较多，这次选择了「骥马码头 - 综合码头」线路，320 一个竹筏</li>
      <li>另外还有「水厄底码头 - 综合码头」、「金龙桥码头 - 旧县综合码头」都是比较推荐的（有两个综合码头不要混了），线路具体特点下面会贴个图，详情也可以小红书启动</li>
    </ul>
  </li>
</ul>

<p>漓江游船是出发前就提前订了，相公山和银子岩因为还有些不太确定的地方所以是到桂林的第一天定的，竹筏则是只能提前一天买票。后面这几个会把具体的图贴在下面。</p>

<h2 id="traffic">Traffic</h2>

<p>除了上面提到的船和大巴这种买好的，其他的交通也简单做了一下考虑</p>

<ul>
  <li>桂林高铁站到酒店
    <ul>
      <li>桂林那边从成都过去的车大多都在桂林西站，但西站在小红书上的评价都是交通很不方便</li>
      <li>看了几个攻略决定还是 303 路到北站再做打算</li>
    </ul>
  </li>
  <li>桂林市区
    <ul>
      <li>有共享电动车，但最初没有找到办法提前确认运营区范围</li>
      <li>打车看攻略又说黑车偏多，计划还是共享电动车为主，解决不了的再随机应变</li>
    </ul>
  </li>
  <li>阳朔（主要是十里画廊）
    <ul>
      <li>随处可租电动车，价格一般二十到三十块钱一天</li>
    </ul>
  </li>
  <li>阳朔酒店到高铁站
    <ul>
      <li>阳朔站离西街还着实是挺远，没有找到特别靠谱的方案（公交时间太长了），最终还是决定打车</li>
    </ul>
  </li>
</ul>

<p>另外也讨论了一下包车方案，结论是一天 400 不怎么值（具体不太记得了），放弃</p>

<h1 id="219">2.19</h1>

<p>早上八点高铁启动，五个小时左右到桂林西站。高铁上遇到一对英国（大概）小情侣（大概）换座位，成功从 D 座换到 A 座，睡了个爽（刚好他们俩也是在桂林西站下的车）。现在免签之后外国人直接来玩的是真的多</p>

<h2 id="桂林西站到北站">桂林西站到北站</h2>

<p>西站出来实话说真的是很荒凉。13:20 左右在出站口右手边找到 303 路，13:40 左右发车（甚至又遇上了上面提到那两位，不过他们在北站前面一两站就下车了）。路上收集了一下共享交通工具的服务区范围：小绿面积小的可怜，小蓝面积最大但好像只有自行车，所以推荐小黄的电动车（服务区范围截图下附，最后一张图右下角蓝点就是酒店位置）</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/bike1.jpeg" alt="" /></th>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/bike2.jpeg" alt="" /></th>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/bike3.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">小绿（青桔）</td>
      <td style="text-align: center">小蓝（哈啰）</td>
      <td style="text-align: center">小黄（美团）</td>
    </tr>
  </tbody>
</table>

<p>14:10 左右到北站。北站附近其实就比较像一个正常的城市了，从这里出发选择交通工具会方便很多。</p>

<h2 id="从北站前往酒店">从北站前往酒店</h2>

<p>下车的地方旁边就有一大堆美团电动车，找了个电最多的直接骑到高新区的酒店，顺便路过了一下中心城市的部分关键点位。观察下来感觉城市界面很像十几年前的石家庄，老化比较明显，高新区也完全没有高新区的感觉。说几个印象比较深刻的：</p>

<ul>
  <li>很小，上面骑车到酒店甚至还是特意绕了一些路（打算去漓江边上看一眼），但从差不多正北到东南角也只花了一个小时多一点</li>
  <li>跑了一路一个房产中介都没见到，简直是有些反直觉；但到处都是卖船票门票啥的店（并非什么好事，使人感觉售卖渠道非常混乱</li>
  <li>见过的唯一一个会把【自行车左转】作为单独信号灯处理的地方，虽然好像大部分时候也都是跟其他信号灯同步（下图恰好是个例外</li>
</ul>

<p><img src="../assets/images/guilin-experience/1900001.jpeg" alt="" /></p>

<p>下午入住亚朵。桂林高新亚朵酒店是个很正常的亚朵，价格印象中是全市所有亚朵里面最低，交通也还算方便，尤其是如果跟草民的计划一样要早起去坐漓江游船的话，可以节省很多坐车的时间，打车也比约的直通车便宜（当然前提是人多），还不用担心早上堵车，值得推荐</p>

<p>出来吃了个塔斯汀，刚好有个什么活动打折，量大管饱。晚上跟小队成员碰面确认行程，顺便定了阳朔那边两个地方的票</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/ticket2.jpeg" alt="" /></th>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/ticket1.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">相公山（周五下午三点）</td>
      <td style="text-align: center">银子岩（周六早上九点）</td>
    </tr>
  </tbody>
</table>

<p>上图是美团，也可以找找携程、飞猪之类平台，或许会有个几块钱的差价优惠之类的，总之一定记得选带接送的就行</p>

<h1 id="220">2.20</h1>

<p>断断续续下了一天雨。早上差不多十点才出门，计划照着下图抄作业，再考虑一个晚上来的顺序（省流：6 - 7 - 2）</p>

<p><img src="../assets/images/guilin-experience/plan1.jpg" alt="" /></p>

<p>小伙伴们要买个帽子和小包什么的临时用一下，于是先去酒店附近的万达广场溜达了一圈。经营状态是个正常的商圈该有的样子，还算有些人气，品牌入驻也比较像样</p>

<h2 id="象鼻山">象鼻山</h2>

<p>不需要门票，门口扫码并提交个人信息就行了。也可以预约（但很搞笑：早上七点开始预约当天，就怎么说呢，连个限流的作用都没有</p>

<p><img src="../assets/images/guilin-experience/2000002.jpeg" alt="" /></p>

<p>一个多小时差不多就能逛完，可惜低估了雨势，应该带伞去的。山顶俯瞰视野也很好，整体来说作为一个城市里的免费公园算是相当优秀</p>

<p><img src="../assets/images/guilin-experience/2000001.jpeg" alt="" /></p>

<p>攻略上 1 号门进 3 号门出的顺序没啥毛病，建议就这么来，全程基本上不走回头路（除了象鼻子那个洞要折返回来一点再上山）。出来就是两江四湖 + 日月双塔，顺便瞄了一眼，但没啥看头，天黑了再说</p>

<h2 id="正阳步行街闲逛">正阳步行街闲逛</h2>

<p>其实就是个很普通的步行街，两边都是那种全国哪儿都有的经典商铺（加上几个卖船票的），但不确定是因为工作日的大白天的关系，还是天气不好，给人的感觉是多少有点衰败（就这张图一眼望去，一大半店都是关着的），尤其是中间广场里面商铺几乎全空，很鬼</p>

<p><img src="../assets/images/guilin-experience/2000003.jpeg" alt="" /></p>

<p>其他的，路上买了一点明信片，感觉还不错。午饭啤酒鱼 + 炒粉 + 一点别的，啤酒鱼感觉一般般（但有的小伙伴觉得很不错），炒粉确实好吃，很香，不过后面稍微凉一点就会有那种酸笋的味道出来，虽然甚至已经有点适应了（这东西真的做什么都要放嘛？</p>

<h2 id="王城商厦">王城商厦</h2>

<p>也是那种十几二十年前的商场的即视感，唯一的一点新意大概算是一楼有家小米，旁边有家瑞幸咖啡。五楼六楼是那种很古早的电脑城，死去的记忆系列（真的，上次逛这种地方大概算是 23 年去五块石那儿，再早根本想不起来是多久以前了</p>

<p><img src="../assets/images/guilin-experience/computer.jpg" alt="" /></p>

<p>有一说一，这种破电脑套餐跟这个商场的年头可以说十分相似，就这种十几年前的配置现在卖这个价怕是能有 70% 毛利，多少有点离谱了</p>

<h2 id="东西巷">东西巷</h2>

<p>商厦对面就是东西巷和靖江王府。王府要钱（还是其次，主要是小红书上避雷太多了，比竹筏都多）所以就没进去，下图左右就分别是西巷和东巷了。顺便真的，哪哪哪都是卖门票和船票的，真的很难评</p>

<p><img src="../assets/images/guilin-experience/2000005.jpeg" alt="" /></p>

<p>东西巷更像那种现代化的古镇的形态。放一张经典的天上挂伞（也是小队同框</p>

<p><img src="../assets/images/guilin-experience/2000006.jpeg" alt="" /></p>

<p>整个逛下来感觉其实比正阳步行街好一些，最起码更有人气一些（东巷挺热闹，西巷还是比较鬼）</p>

<h2 id="逍遥楼">逍遥楼</h2>

<p>从东巷穿过去的路上终于看到一个勉强算中介的地方。墙上只看到了出租，价格其实比预期要高一点（最便宜的一个一室一厅 950，其他有些都一两千了，对本地平均经济水平来说怕是相当难负担），当然毕竟是在核心区周围，估计都被老板包下开民宿了，贵一点也说得过去</p>

<p><img src="../assets/images/guilin-experience/2000004.jpeg" alt="" /></p>

<p>至于逍遥楼，这个楼的视野比预想中的要低不少，还没有象鼻山山顶高。俯瞰漓江还可以，整个城市就想太多了。</p>

<p><img src="../assets/images/guilin-experience/2000009.jpeg" alt="" /></p>

<p>这会儿差不多三点多一点。上午淋了点雨，天黑之前也没什么地方打算去，于是决定回去拿伞 + 暖和一下换换衣服什么的。路过解放桥</p>

<p><img src="../assets/images/guilin-experience/2000010.jpeg" alt="" /></p>

<p>五点半左右回到东西巷浅浅吃了一顿海底捞，就是很正常的海底捞，跟其他大部分城市没有任何区别（此处就要点名批评杭州西湖附近某家海底捞了）。吃完原路回到逍遥楼，晚上的逍遥楼确实挺好看，但显然就这么两层高不太能俯瞰多大区域</p>

<p><img src="../assets/images/guilin-experience/2000007.jpeg" alt="" /></p>

<p>旁边的解放桥，也做了相当不错的灯光设置。某位小伙伴说「如果这时路过一个船就更好了」，评价为确实，可惜这个点游船不营业，漓江水位看着也不太像能货运（包括这个桥做这么矮，显然也完全没考虑货运需求</p>

<p><img src="../assets/images/guilin-experience/2000008.jpeg" alt="" /></p>

<p>然后顺着漓江边儿溜达回日月双塔，耗时半个钟头</p>

<h2 id="两江四湖--日月双塔">两江四湖 + 日月双塔</h2>

<p>这个塔夜景确实不错，所以确实没有必要白天来</p>

<p><img src="../assets/images/guilin-experience/2000012.jpeg" alt="" /></p>

<p>至于两江四湖嘛，只看了一个湖，另外几个懒得走了，下次一定（有没有下次就比较悬了</p>

<h2 id="漓江桥">漓江桥</h2>

<p>这个就没有跟解放桥一样做很好的灯光布置，而且突然想起来这地方应该白天抽空来一下的，略显遗憾（下次一定</p>

<p><img src="../assets/images/guilin-experience/2000011.jpeg" alt="" /></p>

<p>刚好九点左右了。淋了一天雨，赶紧回去洗洗睡，明儿早起去坐船。</p>

<h1 id="221">2.21</h1>

<p>又是断断续续下了一天雨，估计下午相公山就没啥可看的了（日落肯定是别想了</p>

<h2 id="漓江游船">漓江游船</h2>

<p>八点一刻叫到车，二十分钟刚好到竹江客运港。下面这张地图的外语选择很有意思，没有日语但是有越南语</p>

<p><img src="../assets/images/guilin-experience/2100001.jpeg" alt="" /></p>

<p>先自助取票，票上会有坐哪个船、去哪个泊位。提前半小时检票上船</p>

<p><img src="../assets/images/guilin-experience/2100002.jpeg" alt="" /></p>

<p>「迷离间如水山歌绕过一弯又一弯」嘻嘻</p>

<p><img src="../assets/images/guilin-experience/2100003.jpeg" alt="" /></p>

<p>枯水期会稍微慢一点（有一说一水位真有点低的吓人），9:35 开船，13:50 下的船。攻略也没啥可攻略的，看就完了，提几个关注的点：</p>

<ul>
  <li>20 块钱人民币打卡点看上面地图「黄布倒影」，但是在行船方向的屁股后面。快到的时候会有广播，赶紧去船尾找位置</li>
  <li>过了兴坪渔村基本上就没什么可看的了，跟前面的画风差很多（两边山上全是塑料棚子），基本可以安心下去吃饭等到阳朔下船</li>
  <li>午饭虽然是自助但也只能说非常糊弄事儿，船上额外提供（指要花钱，而且挺贵，但是忘了拍菜单）的鱼看起来还行，可以考虑一下</li>
  <li>推销的东西（印象中有桂花香水和驱蚊小葫芦，好像还有点儿啥不记得了）倒是都不贵，想买就买吧</li>
</ul>

<p><img src="../assets/images/guilin-experience/2100004.jpeg" alt="" /></p>

<p>这个船个人评价还是相当不错的（如果不是枯水期就更好了），相比之下前一天桂林根本就可以说是转不转都行</p>

<h2 id="酒店">酒店</h2>

<p>依然是亚朵。有一说一不如上面那个高新区的，小一些不说还挺贵（比上面那个贵一半），虽然住着倒是各方面也都没啥问题，交通也算方便（走路去西街也就十分钟不到，就是从码头过来的时候被高德的步行导航给搞懵逼了</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/hotel1.jpeg" alt="" /></th>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/hotel2.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">桂林</td>
      <td style="text-align: center">阳朔</td>
    </tr>
  </tbody>
</table>

<p>之前在小红书上翻到不少各种各样的民宿，有些价格可能都能做到亚朵的一半不到，所以这个就不做推荐了（懒得找的话也可以选这个，至少体验的下限还是能保证的，尤其是如果还有洗衣服之类的需求的话</p>

<h2 id="相公山">相公山</h2>

<p>扔下东西就去坐车了。15:00 从对面「碧莲江景大酒店」上车出发，车程一个小时多一点，然后停在半山腰上的大门口。需要爬的部分不太高，从下车点往上爬十分钟左右就到顶了，但有一说一视野确实很不错，可惜天气原因导致能见度很差</p>

<p><img src="../assets/images/guilin-experience/2100005.jpeg" alt="" /></p>

<p>日落线回来会路过一个什么泉来着，在那里停留十分钟看日落（这次当然是毛都看不到一根），然后差不多 18:30 之前能回到西街。说到价格，门票加车票八十块确实 emmm，天气好的话个人觉得还可以，不好的话就谨慎考虑吧</p>

<h2 id="晚上吃啥">晚上吃啥</h2>

<p>酒店旁边的啤酒鱼一个比一个卷，贼逗（左边两张是同一个品牌的两家店</p>

<p><img src="../assets/images/guilin-experience/cards.jpeg" alt="" /></p>

<p>选了离酒店最近的一家「大师傅」，据说大众点评 5.0 满分而且评论数还挺多</p>

<ul>
  <li>啤酒鱼，比昨儿那个好的真不止一点半点</li>
  <li>小炒小河虾，个人觉得很对胃口，有那么一点点微辣很香</li>
  <li>某种竹筒鸡，喝汤很不错（当然汤里的鸡肉吃起来肯定就很柴</li>
  <li>其他的一点普通菜品味道也不错，推荐蒜蓉油麦菜、韭菜炒鸡蛋</li>
</ul>

<p>总之是值得推荐的。亚朵酒店每一层都能拿到 9 折券儿，记得薅一张</p>

<h2 id="西街">西街</h2>

<p>基本上就是那种典型的商业化古镇，参考大理古城（当然要小很多）。晚上去遛个弯就得，想买啥买啥，累了就回，反正离酒店很近。</p>

<p><img src="../assets/images/guilin-experience/2100006.jpeg" alt="" /></p>

<p>有一说一阳朔这边比桂林要热闹不少，人气足还可能是因为周末的关系，但这边的门脸空置的也很少，跟桂林对比还是挺明显</p>

<p><img src="../assets/images/guilin-experience/2100007.jpeg" alt="" /></p>

<p>买了几个冰箱贴。这玩意儿有的五块有的十块有的十五，很随意，建议多转悠。顺便买了竹筏的票，结果是随便买，根本不需要花心思抢</p>

<table>
  <thead>
    <tr>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/raft2.jpeg" alt="" /></th>
      <th style="text-align: center"><img src="../assets/images/guilin-experience/raft1.jpeg" alt="" /></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">特点（还有些往返的可以看 <a href="https://www.xiaohongshu.com/discovery/item/67906a51000000001701da7e?source=webshare&amp;xsec_token=CBqvMTYQ7tpFg3hF0hR1LgVM0mm83eUFE2DdRsHj19I1E=&amp;xsec_source=pc_share">https://www.xiaohongshu.com/~</a>，顺便还有十里画廊路线）</td>
      <td style="text-align: center">须知</td>
    </tr>
  </tbody>
</table>

<p>西街旁边还有个徐悲鸿故居，有兴趣的话可以去看看（当然得白天去，晚上去是进不到室内的</p>

<h1 id="222">2.22</h1>

<p>又是下雨，这天气着实是有点不给面子了。。。</p>

<h2 id="银子岩">银子岩</h2>

<p>早上 9:00 京东家电门口上车，车程半个多小时。不得不说，这地方着实是相当不错，比几年前去过的苏州某个溶洞壮观很多，还好没有因为交通问题直接放弃这个地方。每个人都会标配一个讲解设备，里面有些词车速还挺快（比如某个只可意会不可言传的生命之源</p>

<p><img src="../assets/images/guilin-experience/2200001.jpeg" alt="" /></p>

<p>唯一的问题是洞里真的又闷又热，好在也没有什么岔路之类的，一个小时左右就出来了。出门有个溜索可以选，30 块，个人不推荐（尤其天气不好的情况下）。旁边还有个什么地球记忆（好像是陨石展吧），要钱，没看。</p>

<p>11:30 左右返程，中午十二点多就回到酒店了。导游很有意思，回来的路上一边吐槽一边推销东西（晒的桂圆、柚子皮、啤酒鱼、烤香菇，好像还有个啥忘了）。回忆几个个人比较感兴趣的地方：</p>

<ul>
  <li>桂林这边经济 95% 旅游 5% 农业</li>
  <li>阳朔房价平均一万一二，是桂林差不多两倍，买得起的都是外地游客或者来做生意的人</li>
  <li>本地人除了体制内大多都在打零工，注册导游五六万 + 没注册的两三万，但做这个一月也就两三千收入，根本买不起商品房</li>
</ul>

<p>听的时候也考虑了一下在这里生活的可能性，结果怎么说呢，别的先不提，现在没地铁的城市是真的不太可能考虑定居了</p>

<h2 id="遇龙河竹筏">遇龙河竹筏</h2>

<p>午饭旁边简简单单吃了个粉，然后去坐竹筏。另外两位小伙伴对竹筏不太感兴趣，加上体重方面的搭配问题所以没去，只有草民和另外一位大学同学。这次选择骥马 - 综合码头路线。体验下来真的强烈推荐，比漓江游船还带感，各方面都非常完美（值得一张 Live Photo</p>

<video src="../assets/images/guilin-experience/raft.mov" width="100%"></video>

<p>几个点关注：</p>

<ul>
  <li>下雨天会更带感！千万不要因为下雨就放弃了（除非雨实在是太大，竹筏完全不营业那种</li>
  <li>同一个竹筏两个人体重加起来不能超过 160kg，要现场称重，超重就只能拆筏或者现场找人拼了。草民这次是刚刚好，一点点都不多</li>
  <li>骥马码头没啥人，所以开始那一段体验会非常好；中间的水厄底码头人很多，需要拼船的话可以考虑去水厄底码头</li>
  <li>鞋套草民那个船没让穿，说会很滑可能有危险，但是看其他船又有穿了的。实际上也没用到，过水坝的时候把脚抬起来就行</li>
  <li>虽然兴坪古镇那边也有漓江竹筏，但那个是电动的（小伙伴说有点赛博），而且已经坐过船的话就没必要再专门跑去那边玩一次竹筏了</li>
</ul>

<p>从竹筏下来就是综合码头，旁边就是工农桥。桥上桥下的视野都非常好，可以稍微多呆一小会儿</p>

<p><img src="../assets/images/guilin-experience/2200002.jpeg" alt="" /></p>

<p>这个时节旁边还有一片油菜花，也可以去抓一下</p>

<h2 id="十里画廊">十里画廊</h2>

<p>十里画廊这一部分其实后面回想下来是体验比较差的，主要是下雨天真的不能骑电动车到处跑，太冷了 = = 应该准备个 Plan B</p>

<ul>
  <li>这次租车就在十里画廊北门门口，一天 25 比酒店附近贵五块钱，押金一辆车 100 回来原地还车之后会退。
    <ul>
      <li>总体来说区别不大，主要还是看天气吧，天气好就多骑车，不好的话就避免骑车、优先打车</li>
    </ul>
  </li>
  <li>一部分收费项目情况（其实所有的收费项目攻略上都不推荐
    <ul>
      <li>月亮山不营业（听说去年国庆就歇业了</li>
      <li>小火车也不营业（估计是天气原因吧，虽然本来也没打算去</li>
    </ul>
  </li>
</ul>

<p>比较开心的是中间路过一个小水坝，让两位没坐上竹筏的朋友也看到了遇龙河的风光</p>

<p><img src="../assets/images/guilin-experience/2200003.jpeg" alt="" /></p>

<p>另外，半山咖啡视野不错，不过天气不好的话就不太值得专门跑一趟，旁边温泉啥的也没开</p>

<h2 id="最后一顿">最后一顿</h2>

<p>纠结了半天选了附近另外一家也是评分 5.0 的啤酒鱼，但实际吃下来只能评价为：价格跟大师傅差不多，但东西明显不如大师傅</p>

<ul>
  <li>啤酒鱼，个人感觉还凑合，比桂林那家还是好不少，但小伙伴们反馈比大师傅差一些</li>
  <li>蒜蓉油麦菜，相当翻车，不仅老，调味也不到位，着实乏善可陈</li>
  <li>其他几个菜凑合吧，不至于很难吃但也没啥亮点</li>
</ul>

<p>按说这一天的体力消耗远高于上次，评价还不高只能说明真不太行。这家在前一天的小卡片里面也出现了，猜一猜是哪家（当然是无奖竞猜</p>

<h1 id="221-1">2.21</h1>

<p>终于不下雨了，早一天停也行啊。早上其他人就都打车回桂林机场了，草民自己一个人从阳朔高铁站回去</p>

<h2 id="兴坪古镇">兴坪古镇</h2>

<p>本来没打算去，但发现刚好高铁站在它旁边，公交打车都要路过。早上刚好有一个小时左右时间，干脆去转一圈。从酒店过去打车三十几块</p>

<ul>
  <li>古镇那部分就是那种全国各地都一样的非常同质化的古镇，并没有什么特别的点
    <ul>
      <li>而且比西街还小很多（可能也就比东西巷稍微大一点？），就一条小路，十分钟不到走完</li>
    </ul>
  </li>
  <li>出去码头上有个 20 块钱固定打卡点（下图
    <ul>
      <li>怎么说呢，如果坐了漓江游船的话，就完全没必要再跑过来一趟（没坐的话也不是很有必要</li>
    </ul>
  </li>
  <li>一边考虑怎么解决午饭问题一边溜达到了兴坪商贸城，实际上更像是一个超市加一个菜市场（甚至规模还不算大，不过很热闹）
    <ul>
      <li>午饭最终吃了客运站旁边一个汉堡，味道凑合吧但不如塔斯汀。叫什么忘了，但总之是那附近唯一的一个相对标准化的的选择</li>
    </ul>
  </li>
</ul>

<p><img src="../assets/images/guilin-experience/2300001.jpeg" alt="" /></p>

<p>结论是完全不推荐。幸亏没有跟相公山赶在一起去，太没意思了，要因为这个浪费时间的话可真是亏大了</p>

<h2 id="回家">回家</h2>

<p>这波坐火车运气意外的好，又从 C 座换到了 A 座。来回两趟还都是全程不用挪窝，睡了个爽。最后考虑一些改进点：</p>

<ul>
  <li>十里画廊那块没整太好。下雨天真的不要骑电动车（回来非常不意外的感冒了 = =），但好像也没有什么很好的备用方案（害</li>
  <li>桂林真的没必要花一整天时间，安排的好的话半天完全足够逛完重要地点
    <ul>
      <li>早上出发、中午到酒店，路边随便吃个粉，下午先去漓江桥瞅一眼，然后象鼻山溜一圈（天气好还能看日落），出来东西巷吃饭</li>
      <li>吃完刚好晚上，逍遥楼、解放桥、日月双塔打个卡，还有力气的话两江四湖随便逛逛，累了直接回去睡觉，第二天去坐船</li>
    </ul>
  </li>
</ul>

<p>最后也把这首歌送给大家吧（关注泠鸢 yousa 喵，关注泠鸢 yousa 谢谢喵</p>

<iframe src="//player.bilibili.com/player.html?aid=8086541&amp;bvid=BV1Ds411h7T1&amp;cid=13295504&amp;page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" height="600px" width="100%"> </iframe>]]></content><author><name>yichya</name></author><category term="Play Around" /><category term="life" /><summary type="html"><![CDATA[其实本来不太想在天这么冷的时候出去玩，但是又得把马上要过期的三天年假消耗掉。Vacation 2024.2 里面提了两个 Flag，泸沽寻梦太麻烦了而且还要留给新高铁线路（看新闻预计得 2026 年年底了），所以泼墨漓江吧，刚好演唱会的戒断反应也需要想办法缓解一下。]]></summary></entry></feed>